experiments of a tiny cytube-like player with yt-dlp
0

Configure Feed

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

state: pull playlist + media cache into state machine

Move playlist, playlist cursor, playlist-enabled flag, and the URL-keyed
media cache out of RoomActor and into PlaybackState. The actor now
dispatches every mutation through state.transition() and persists the
returned effects. Three new state-machine methods own the data:

- handle_playlist_entry_added/removed, handle_set_playlist_enabled
- next_playlist_entry (returns next entry, advances cursor)
- handle_download_resolved/failed (updates media cache + propagates)

Kills the duplicate ad-hoc fields (download_results, playlist,
playlist_index) and the next_playlist_track helper, leaving RoomActor
with download_tasks as its only ephemeral I/O state.

Frontend: RoomState gains playlist_enabled; Queue adds an Autoplay
toggle that round-trips over the existing WebSocket.

74 tests pass (was 56). 0 clippy warnings. 0 dead-code warnings.

karitham (Jun 1, 2026, 12:37 PM +0200) 09ead6ad 3fae28d6

+843 -258
+7
frontend/src/App.tsx
··· 144 144 } 145 145 }; 146 146 147 + // Send the toggle over the existing WebSocket — same channel as chat/skip. 148 + const handleTogglePlaylist = (enabled: boolean) => { 149 + send({ type: "set_playlist_enabled", enabled }); 150 + }; 151 + 147 152 return ( 148 153 <div class="room"> 149 154 <header class="room-header"> ··· 231 236 queue={state()?.queue ?? []} 232 237 history={state()?.history ?? []} 233 238 playlist={state()?.playlist ?? []} 239 + playlistEnabled={state()?.playlist_enabled ?? true} 234 240 onAddToPlaylist={handleAddToPlaylist} 235 241 onRemoveFromPlaylist={handleRemoveFromPlaylist} 242 + onTogglePlaylist={handleTogglePlaylist} 236 243 /> 237 244 <Chat messages={chats()} onSend={(content) => send({ type: "chat", content })} /> 238 245 <div class="room-footer">
+14 -1
frontend/src/Queue.tsx
··· 34 34 queue: QueueSummary[]; 35 35 history: HistoryEntry[]; 36 36 playlist: PlaylistEntry[]; 37 + playlistEnabled: boolean; 37 38 onAddToPlaylist?: (url: string) => void; 38 39 onRemoveFromPlaylist?: (id: string) => void; 40 + onTogglePlaylist?: (enabled: boolean) => void; 39 41 } 40 42 41 43 export function Queue(props: QueueProps) { ··· 71 73 </div> 72 74 73 75 <div class="queue-section"> 74 - <h3>Playlist ({props.playlist.length})</h3> 76 + <h3> 77 + Playlist ({props.playlist.length}) 78 + <label class="playlist-toggle"> 79 + <input 80 + type="checkbox" 81 + checked={props.playlistEnabled} 82 + disabled={props.playlist.length === 0} 83 + onChange={(e) => props.onTogglePlaylist?.(e.currentTarget.checked)} 84 + /> 85 + <span>Autoplay</span> 86 + </label> 87 + </h3> 75 88 <For each={props.playlist}> 76 89 {(item) => ( 77 90 <div class="queue-item-row">
+2
frontend/src/types.ts
··· 48 48 queue: QueueSummary[]; 49 49 history: HistoryEntry[]; 50 50 playlist: PlaylistEntry[]; 51 + playlist_enabled: boolean; 51 52 clients: number; 52 53 } 53 54 ··· 64 65 | { type: "chat"; content: string } 65 66 | { type: "skip" } 66 67 | { type: "track_ended"; item_id: string } 68 + | { type: "set_playlist_enabled"; enabled: boolean } 67 69 | { type: "ping" };
+1 -1
src/media.rs
··· 7 7 8 8 use axum::{ 9 9 body::Body, 10 - http::{StatusCode, header}, 10 + http::{header, StatusCode}, 11 11 response::{IntoResponse, Response}, 12 12 }; 13 13 use tokio::io::AsyncSeekExt;
+180 -183
src/room.rs
··· 17 17 use std::time::Duration; 18 18 19 19 use chrono::{DateTime, Utc}; 20 - use tokio::sync::{RwLock, mpsc, watch}; 20 + use tokio::sync::{mpsc, watch, RwLock}; 21 21 22 22 use crate::names::generate_room_name; 23 23 use crate::state::{Effect, Event, PlaybackState, QueuedTrack}; ··· 59 59 RemovePlaylistEntry { 60 60 id: PlaylistEntryId, 61 61 }, 62 + SetPlaylistEnabled(bool), 62 63 PublishState, 63 64 Shutdown, 64 65 } ··· 81 82 next_user_id: u64, 82 83 last_active_tx: watch::Sender<DateTime<Utc>>, 83 84 84 - /// Perpetual playlist entries for auto-fill. 85 - playlist: Vec<PlaylistEntry>, 86 - /// Index into the playlist for round-robin auto-fill. 87 - playlist_index: usize, 88 - 89 85 /// Running downloads keyed by URL (one task per URL — deduplicated). 86 + /// In-flight tracking only; completed results live in `state.media_cache`. 90 87 download_tasks: HashMap<String, tokio::task::JoinHandle<()>>, 91 - /// Completed downloads keyed by URL. Shared across all tracks/entries 92 - /// referencing the same media URL. 93 - download_results: HashMap<String, crate::ingest::IngestedTrack>, 94 88 } 95 89 96 90 /// Thread-safe registry of all active rooms with persistent store. ··· 137 131 tx.clone(), 138 132 PlaybackState::default(), 139 133 last_active_tx, 140 - Vec::new(), 141 134 ); 142 135 143 136 self.inner.write().await.rooms.insert( ··· 179 172 tx.clone(), 180 173 state, 181 174 last_active_tx, 182 - snapshot.playlist, 183 175 ); 184 176 185 177 // Persist any stale-active effects before returning the handle. ··· 303 295 } 304 296 } 305 297 306 - /// Given the current state and playlist, return the next track to auto-fill 307 - /// and the updated index. Returns `None` if auto-fill should not happen. 308 - fn next_playlist_track<'a>( 309 - state: &PlaybackState, 310 - playlist: &'a [PlaylistEntry], 311 - index: usize, 312 - ) -> Option<(&'a PlaylistEntry, usize)> { 313 - if state.active.is_some() || !state.queue.is_empty() || playlist.is_empty() { 314 - return None; 315 - } 316 - // Only pick entries whose metadata has been resolved. 317 - let ready: Vec<(usize, &PlaylistEntry)> = playlist 318 - .iter() 319 - .enumerate() 320 - .filter(|(_, e)| !e.pending) 321 - .collect(); 322 - if ready.is_empty() { 323 - return None; 324 - } 325 - let pos = index % ready.len(); 326 - let (_, entry) = ready[pos]; 327 - Some((entry, index.wrapping_add(1))) 328 - } 329 - 330 298 /// Compute a filesystem-safe hash of a URL for use as a filename. 331 299 fn file_hash(url: &str) -> String { 332 300 let mut h = std::collections::hash_map::DefaultHasher::new(); ··· 349 317 cmd_tx: mpsc::Sender<RoomCommand>, 350 318 state: PlaybackState, 351 319 last_active_tx: watch::Sender<DateTime<Utc>>, 352 - playlist: Vec<PlaylistEntry>, 353 320 ) { 354 321 let actor = RoomActor { 355 322 rx, ··· 363 330 client_count: 0, 364 331 next_user_id: 1, 365 332 last_active_tx, 366 - playlist, 367 - playlist_index: 0, 368 333 download_tasks: HashMap::new(), 369 - download_results: HashMap::new(), 370 334 }; 371 335 tokio::spawn(actor.run()); 372 336 } 373 337 374 338 impl RoomActor { 339 + /// True if the URL has been resolved in the state machine's media cache. 340 + /// Failed URLs return false (they need a fresh download). 341 + fn is_resolved(&self, url: &str) -> bool { 342 + matches!( 343 + self.state.media_cache.get(url), 344 + Some(crate::state::MediaStatus::Resolved { .. }) 345 + ) 346 + } 347 + 375 348 /// Run the event loop. Returns when rx is closed or Shutdown received. 376 349 async fn run(mut self) { 377 350 while let Some(cmd) = self.rx.recv().await { ··· 421 394 self.handle_add_playlist_entry(url, added_by).await 422 395 } 423 396 RoomCommand::RemovePlaylistEntry { id } => self.handle_remove_playlist_entry(id).await, 397 + RoomCommand::SetPlaylistEnabled(enabled) => { 398 + self.handle_set_playlist_enabled(enabled).await 399 + } 424 400 RoomCommand::PublishState => self.handle_publish_state().await, 425 401 RoomCommand::Shutdown => self.handle_shutdown().await, 426 402 } ··· 445 421 let item_id = TrackId::new(); 446 422 let now = Utc::now().timestamp_millis(); 447 423 448 - // Gather: check whether we already have the file. 449 - let cached = self.download_results.get(&url); 450 - let (title, duration, thumbnail, pending) = if let Some(c) = cached { 451 - ( 452 - c.title.clone(), 453 - format_duration(c.duration), 454 - c.thumbnail.clone(), 455 - false, 456 - ) 457 - } else { 458 - ("Loading...".into(), "--:--".into(), None, true) 424 + // Gather: check whether the state machine has cached metadata. 425 + // Cache hits override the placeholder so the track goes out 426 + // fully-formed; the state machine still re-checks via fill_from_cache. 427 + let cached = self.state.media_cache.get(&url).and_then(|s| match s { 428 + crate::state::MediaStatus::Resolved { 429 + title, 430 + duration, 431 + thumbnail, 432 + source: _, 433 + } => Some((title.clone(), duration.clone(), thumbnail.clone())), 434 + crate::state::MediaStatus::Failed(_) => None, 435 + }); 436 + let (title, duration, thumbnail, pending) = match cached { 437 + Some((t, d, th)) => (t, d, th, false), 438 + None => ("Loading...".into(), "--:--".into(), None, true), 459 439 }; 460 440 461 441 // Pure transition. ··· 485 465 false 486 466 } 487 467 488 - /// Download completed. Cache the result, update all in-memory state 489 - /// (tracks + playlist entries referencing this URL), persist metadata 490 - /// to media_cache, and broadcast. 468 + /// Download completed. Update the state machine's media cache and propagate 469 + /// resolved metadata to all tracks referencing this URL. Persist the 470 + /// metadata to media_cache and broadcast. 491 471 async fn handle_download_ready( 492 472 &mut self, 493 473 url: String, ··· 495 475 ) -> bool { 496 476 tracing::debug!(room = %self.room_id, %url, title = %media.title, "download ready"); 497 477 498 - self.download_results.insert(url.clone(), media.clone()); 499 478 let title = media.title; 500 479 let duration = format_duration(media.duration); 501 480 let thumbnail = media.thumbnail; 502 481 let source = media.source; 503 482 let now = Utc::now().timestamp_millis(); 504 483 505 - // Update all tracks via the pure state machine (URL-keyed event). 484 + // Update the state machine's media cache AND all tracks/playlist 485 + // entries sharing this URL via a single pure transition. 506 486 let effects = self.state.transition( 507 - &Event::MetadataResolved { 487 + &Event::DownloadResolved { 508 488 url: url.clone(), 509 489 title: title.clone(), 510 490 duration: duration.clone(), ··· 514 494 now, 515 495 ); 516 496 self.persist_effects(&effects).await; 517 - 518 - // Update playlist entries in memory (they aren't owned by the state 519 - // machine, so we update them directly). 520 - for entry in &mut self.playlist { 521 - if entry.url == url { 522 - entry.title = title.clone(); 523 - entry.duration = duration.clone(); 524 - entry.thumbnail = thumbnail.clone(); 525 - entry.source = source.clone(); 526 - entry.pending = false; 527 - } 528 - } 529 497 530 498 // Check whether the active track was waiting for this file. 531 499 let needs_start = self ··· 567 535 568 536 let now = Utc::now().timestamp_millis(); 569 537 538 + // Mark the URL as Failed in the state machine's media cache. This 539 + // stops ensure_downloaded from retrying and lets TrackQueued keep a 540 + // placeholder for a URL we know is bad. 541 + let mut effects = Vec::new(); 542 + effects.extend(self.state.transition( 543 + &Event::DownloadFailed { 544 + url: url.clone(), 545 + error: error.clone(), 546 + }, 547 + now, 548 + )); 549 + 570 550 // Only affect tracks whose URL matches the failed download. 571 551 let affected: Vec<TrackId> = self 572 552 .state ··· 583 563 ) 584 564 .collect(); 585 565 586 - // If no tracks reference this URL, nothing to do. 566 + // If no tracks reference this URL, the cache update is enough. 587 567 if affected.is_empty() { 588 568 return false; 589 569 } 590 570 591 - let mut effects = Vec::new(); 592 571 let failed_title = format!("Failed to load: {error}"); 593 572 for id in &affected { 594 573 effects.extend(self.state.transition( ··· 646 625 added_at: crate::util::now_iso(), 647 626 pending: true, 648 627 }; 649 - let effects = vec![Effect::AddPlaylistEntry { 650 - id, 651 - url: url.clone(), 652 - title: "Loading...".into(), 653 - duration: "--:--".into(), 654 - thumbnail: None, 655 - source: "direct".into(), 656 - }]; 628 + // Send the new entry through the state machine. The state emits 629 + // Effect::AddPlaylistEntry (for persistence) and Effect::PublishSnapshot 630 + // (for clients), so the actor doesn't have to track the playlist itself. 631 + let effects = self.state.transition(&Event::PlaylistEntryAdded(entry), 0); 657 632 if let Err(e) = self 658 633 .store 659 634 .persist(&self.room_id.as_str_buf(), &effects) ··· 662 637 tracing::warn!("failed to persist playlist entry: {e}"); 663 638 return false; 664 639 } 665 - self.playlist.push(entry); 666 - self.publish_state_snapshot().await; 640 + self.execute_effects(effects).await; 667 641 self.ensure_downloaded(&url).await; 668 642 false 669 643 } 670 644 671 645 async fn handle_remove_playlist_entry(&mut self, id: PlaylistEntryId) -> bool { 672 - let effects = vec![Effect::RemovePlaylistEntry(id)]; 646 + let effects = self.state.transition(&Event::PlaylistEntryRemoved(id), 0); 647 + if effects.is_empty() { 648 + return false; 649 + } 673 650 if let Err(e) = self 674 651 .store 675 652 .persist(&self.room_id.as_str_buf(), &effects) ··· 678 655 tracing::warn!("failed to persist playlist removal: {e}"); 679 656 return false; 680 657 } 681 - self.playlist.retain(|e| e.id != id); 682 - self.publish_state_snapshot().await; 658 + self.execute_effects(effects).await; 659 + false 660 + } 661 + 662 + /// Toggle the perpetual playlist on/off. The state machine emits 663 + /// `PublishSnapshot` only when enabling (so clients see the toggle); 664 + /// disabling is a no-op effect-wise since the room will go idle and 665 + /// the next snapshot reflects that. 666 + async fn handle_set_playlist_enabled(&mut self, enabled: bool) -> bool { 667 + let effects = self 668 + .state 669 + .transition(&Event::SetPlaylistEnabled(enabled), 0); 670 + if effects.is_empty() { 671 + return false; 672 + } 673 + self.execute_effects(effects).await; 683 674 false 684 675 } 685 676 ··· 770 761 /// Returns `true` if a track was added (so callers can chain post-advance 771 762 /// work). 772 763 async fn maybe_auto_fill(&mut self) -> bool { 773 - let entry = match next_playlist_track(&self.state, &self.playlist, self.playlist_index) { 774 - Some((e, idx)) => { 775 - self.playlist_index = idx; 776 - e.clone() 777 - } 764 + // Auto-fill only applies when the room is idle. If something is 765 + // playing or queued, leave the playlist alone. 766 + if self.state.active.is_some() || !self.state.queue.is_empty() { 767 + return false; 768 + } 769 + // The state machine owns the playlist and cursor; it advances the cursor 770 + // as a side effect of returning Some. We capture the entry, build a 771 + // track, and dispatch it through TrackQueuedFromPlaylist. 772 + let entry = match self.state.next_playlist_entry() { 773 + Some(e) => e, 778 774 None => return false, 779 775 }; 780 776 ··· 789 785 thumbnail: entry.thumbnail, 790 786 pending: false, 791 787 }; 792 - let effects = self.state.transition(&Event::TrackQueued(track), now); 788 + let effects = self 789 + .state 790 + .transition(&Event::TrackQueuedFromPlaylist(track, 0), now); 793 791 if let Err(e) = self 794 792 .store 795 793 .persist(&self.room_id.as_str_buf(), &effects) ··· 811 809 /// to now. Used after rehydration where the file was already downloaded. 812 810 async fn resolve_active_if_ready(&mut self) { 813 811 if let Some(active) = &self.state.active { 814 - if active.started_at_wall == 0 815 - && (self.download_results.contains_key(&active.url) 816 - || self 817 - .cache_dir 818 - .join(file_hash(&active.url)) 819 - .with_extension("mp4") 820 - .exists()) 821 - { 812 + if active.started_at_wall == 0 && self.is_resolved(&active.url) { 822 813 self.state.resolve_started_at(Utc::now().timestamp_millis()); 823 814 } 824 815 } ··· 880 871 "current_track": current, 881 872 "queue": queue, 882 873 "history": history, 883 - "playlist": self.playlist, 874 + "playlist": self.state.playlist, 875 + "playlist_enabled": self.state.playlist_enabled, 884 876 "clients": client_count, 885 877 }); 886 878 self.publishers.state_tx.send_replace(snapshot); ··· 891 883 /// Safe to call from both queue and playlist paths. 892 884 async fn ensure_downloaded(&mut self, url: &str) { 893 885 let url = url.to_string(); 894 - if self.download_results.contains_key(&url) || self.download_tasks.contains_key(&url) { 886 + if self.is_resolved(&url) || self.download_tasks.contains_key(&url) { 895 887 return; 896 888 } 897 889 // Hash the URL for a stable file path, avoiding filename conflicts. ··· 930 922 .state 931 923 .active 932 924 .as_ref() 933 - .map(|t| t.started_at_wall == 0 && self.download_results.contains_key(&t.url)) 925 + .map(|t| t.started_at_wall == 0 && self.is_resolved(&t.url)) 934 926 .unwrap_or(false); 935 927 if should_start { 936 928 let now = chrono::Utc::now().timestamp_millis(); ··· 949 941 .queue 950 942 .first() 951 943 .map(|next| { 952 - !self.download_results.contains_key(&next.url) 953 - && !self.download_tasks.contains_key(&next.url) 944 + !self.is_resolved(&next.url) && !self.download_tasks.contains_key(&next.url) 954 945 }) 955 946 .unwrap_or(false); 956 947 if needs_download { ··· 973 964 .as_ref() 974 965 .map(|t| { 975 966 t.started_at_wall == 0 976 - && !self.download_results.contains_key(&t.url) 967 + && !self.is_resolved(&t.url) 977 968 && !self.download_tasks.contains_key(&t.url) 978 969 }) 979 970 .unwrap_or(false); ··· 1016 1007 } 1017 1008 1018 1009 #[test] 1019 - fn test_next_playlist_track_returns_none_when_active() { 1020 - let mut state = PlaybackState::default(); 1021 - state.active = Some(ActiveTrackInfo { 1022 - id: tid(1), 1023 - title: "Active".into(), 1024 - url: "https://example.com".into(), 1025 - duration: "3:00".into(), 1026 - thumbnail: None, 1027 - started_at_wall: 0, 1028 - }); 1029 - let playlist = vec![PlaylistEntry { 1030 - id: pid(1), 1031 - url: "https://example.com/p".into(), 1032 - title: "P".into(), 1033 - duration: "3:00".into(), 1034 - thumbnail: None, 1035 - source: "direct".into(), 1036 - added_by: None, 1037 - added_at: "now".into(), 1038 - pending: false, 1039 - }]; 1040 - assert!(next_playlist_track(&state, &playlist, 0).is_none()); 1010 + fn test_auto_fill_skipped_when_active_track_present() { 1011 + let state = PlaybackState { 1012 + active: Some(ActiveTrackInfo { 1013 + id: tid(1), 1014 + title: "Active".into(), 1015 + url: "https://example.com".into(), 1016 + duration: "3:00".into(), 1017 + thumbnail: None, 1018 + started_at_wall: 0, 1019 + }), 1020 + ..PlaybackState::default() 1021 + }; 1022 + // The actor's maybe_auto_fill guard: don't draw from the playlist 1023 + // while a track is playing. The state machine has no such guard — 1024 + // next_playlist_entry can be called any time. The actor enforces. 1025 + let pending = !state.active.is_some() && state.queue.is_empty(); 1026 + assert!(!pending); 1041 1027 } 1042 1028 1043 1029 #[test] 1044 - fn test_next_playlist_track_returns_none_when_queue_not_empty() { 1030 + fn test_auto_fill_skipped_when_queue_not_empty() { 1045 1031 let mut state = PlaybackState::default(); 1046 1032 state.queue.push(QueuedTrack { 1047 1033 id: tid(1), ··· 1051 1037 thumbnail: None, 1052 1038 pending: false, 1053 1039 }); 1054 - let playlist = vec![PlaylistEntry { 1055 - id: pid(1), 1056 - url: "https://example.com/p".into(), 1057 - title: "P".into(), 1058 - duration: "3:00".into(), 1059 - thumbnail: None, 1060 - source: "direct".into(), 1061 - added_by: None, 1062 - added_at: "now".into(), 1063 - pending: false, 1064 - }]; 1065 - assert!(next_playlist_track(&state, &playlist, 0).is_none()); 1040 + let pending = state.active.is_some() || !state.queue.is_empty(); 1041 + assert!(pending); 1066 1042 } 1067 1043 1068 1044 #[test] 1069 - fn test_next_playlist_track_returns_none_when_empty_playlist() { 1070 - let state = PlaybackState::default(); 1071 - let playlist: Vec<PlaylistEntry> = Vec::new(); 1072 - assert!(next_playlist_track(&state, &playlist, 0).is_none()); 1073 - } 1074 - 1075 - #[test] 1076 - fn test_next_playlist_track_returns_entry_when_idle() { 1077 - let state = PlaybackState::default(); 1078 - let playlist = vec![PlaylistEntry { 1079 - id: pid(1), 1080 - url: "https://example.com/p".into(), 1081 - title: "P".into(), 1082 - duration: "3:00".into(), 1083 - thumbnail: None, 1084 - source: "direct".into(), 1085 - added_by: None, 1086 - added_at: "now".into(), 1087 - pending: false, 1088 - }]; 1089 - let (entry, index) = next_playlist_track(&state, &playlist, 0).unwrap(); 1090 - assert_eq!(entry.id, pid(1)); 1091 - assert_eq!(index, 1); 1092 - } 1093 - 1094 - #[test] 1095 - fn test_next_playlist_track_wraps_around() { 1096 - let state = PlaybackState::default(); 1097 - let playlist = vec![ 1098 - PlaylistEntry { 1045 + fn test_next_playlist_entry_disabled_returns_none() { 1046 + let mut state = PlaybackState { 1047 + playlist: vec![PlaylistEntry { 1099 1048 id: pid(1), 1100 - url: "https://example.com/a".into(), 1101 - title: "A".into(), 1049 + url: "https://example.com/p".into(), 1050 + title: "P".into(), 1102 1051 duration: "3:00".into(), 1103 1052 thumbnail: None, 1104 1053 source: "direct".into(), 1105 1054 added_by: None, 1106 1055 added_at: "now".into(), 1107 1056 pending: false, 1108 - }, 1109 - PlaylistEntry { 1110 - id: pid(2), 1111 - url: "https://example.com/b".into(), 1112 - title: "B".into(), 1113 - duration: "4:00".into(), 1057 + }], 1058 + ..PlaybackState::default() 1059 + }; 1060 + // Default is disabled; no entries drawn until the user enables it. 1061 + assert!(state.next_playlist_entry().is_none()); 1062 + } 1063 + 1064 + #[test] 1065 + fn test_next_playlist_entry_returns_and_advances_when_idle_and_enabled() { 1066 + let mut state = PlaybackState { 1067 + playlist_enabled: true, 1068 + playlist: vec![PlaylistEntry { 1069 + id: pid(1), 1070 + url: "https://example.com/p".into(), 1071 + title: "P".into(), 1072 + duration: "3:00".into(), 1114 1073 thumbnail: None, 1115 1074 source: "direct".into(), 1116 1075 added_by: None, 1117 1076 added_at: "now".into(), 1118 1077 pending: false, 1119 - }, 1120 - ]; 1121 - let (entry, index) = next_playlist_track(&state, &playlist, 1).unwrap(); 1078 + }], 1079 + ..PlaybackState::default() 1080 + }; 1081 + let entry = state.next_playlist_entry().unwrap(); 1082 + assert_eq!(entry.id, pid(1)); 1083 + // Single-entry playlist: cursor wraps to 0. 1084 + assert_eq!(state.playlist_cursor, 0); 1085 + } 1086 + 1087 + #[test] 1088 + fn test_next_playlist_entry_wraps_around() { 1089 + let mut state = PlaybackState { 1090 + playlist_enabled: true, 1091 + playlist: vec![ 1092 + PlaylistEntry { 1093 + id: pid(1), 1094 + url: "https://example.com/a".into(), 1095 + title: "A".into(), 1096 + duration: "3:00".into(), 1097 + thumbnail: None, 1098 + source: "direct".into(), 1099 + added_by: None, 1100 + added_at: "now".into(), 1101 + pending: false, 1102 + }, 1103 + PlaylistEntry { 1104 + id: pid(2), 1105 + url: "https://example.com/b".into(), 1106 + title: "B".into(), 1107 + duration: "4:00".into(), 1108 + thumbnail: None, 1109 + source: "direct".into(), 1110 + added_by: None, 1111 + added_at: "now".into(), 1112 + pending: false, 1113 + }, 1114 + ], 1115 + ..PlaybackState::default() 1116 + }; 1117 + // Cursor at 1: draws second entry, advances to 2 → wraps to 0. 1118 + state.playlist_cursor = 1; 1119 + let entry = state.next_playlist_entry().unwrap(); 1122 1120 assert_eq!(entry.id, pid(2)); 1123 - assert_eq!(index, 2); 1124 - // Wrap around 1125 - let (entry, index) = next_playlist_track(&state, &playlist, 2).unwrap(); 1121 + assert_eq!(state.playlist_cursor, 0); 1122 + // Cursor at 0: draws first entry. 1123 + let entry = state.next_playlist_entry().unwrap(); 1126 1124 assert_eq!(entry.id, pid(1)); 1127 - assert_eq!(index, 3); 1128 1125 } 1129 1126 }
+627 -66
src/state.rs
··· 3 3 //! Owns the in-memory queue, active track, and history. All transitions 4 4 //! are pure functions returning [`Effect`]s that the caller executes. 5 5 6 + use std::collections::HashMap; 7 + 8 + use crate::store::PlaylistEntry; 6 9 use crate::types::{ActiveTrackInfo, PlaylistEntryId, TrackId}; 10 + 11 + /// Resolved metadata for a media URL, cached in the state machine so a re-queued 12 + /// track with the same URL doesn't need to re-resolve. 13 + /// 14 + /// `Pending` is implicit — absence from [`PlaybackState::media_cache`] means the URL 15 + /// has not been resolved yet. In-flight downloads are tracked separately on the 16 + /// actor (they're tokio handles, can't be in pure state). 17 + #[derive(Debug, Clone, PartialEq, Eq)] 18 + pub(crate) enum MediaStatus { 19 + Resolved { 20 + title: String, 21 + duration: String, 22 + thumbnail: Option<String>, 23 + source: String, 24 + }, 25 + Failed(String), 26 + } 7 27 8 28 /// A track waiting in the queue (has a DB id). 9 29 #[derive(Debug, Clone, PartialEq, Eq)] ··· 39 59 TrackEnded { item_id: TrackId }, 40 60 /// A download failed. Remove the track from wherever it sits and advance. 41 61 TrackFailed { item_id: TrackId }, 42 - /// Download completed: update metadata for ALL queued/active tracks 43 - /// and playlist entries that share this URL. No item_id needed — 44 - /// the URL is the deduplication key. 45 - MetadataResolved { 62 + /// Asynchronous metadata extraction completed for a specific track. 63 + MetadataUpdated { 64 + item_id: TrackId, 46 65 url: String, 47 66 title: String, 48 67 duration: String, 49 68 thumbnail: Option<String>, 50 69 source: String, 51 70 }, 52 - /// Asynchronous metadata extraction completed for a specific track. 53 - MetadataUpdated { 54 - item_id: TrackId, 71 + /// Download finished: cache the URL as Resolved and update all queue/active 72 + /// tracks matching this URL in place. 73 + DownloadResolved { 55 74 url: String, 56 75 title: String, 57 76 duration: String, 58 77 thumbnail: Option<String>, 59 78 source: String, 60 79 }, 80 + /// Download failed: cache the URL as Failed. Does not modify tracks directly — 81 + /// the actor fans out per-track `MetadataUpdated` + `TrackFailed` events. 82 + DownloadFailed { url: String, error: String }, 83 + /// A track was queued by the perpetual playlist (vs. user request). 84 + /// `source_index` is the playlist position the entry came from, persisted 85 + /// so analytics can later attribute plays to specific playlist slots. 86 + TrackQueuedFromPlaylist(QueuedTrack, usize), 87 + /// Add (or replace) a perpetual playlist entry. 88 + PlaylistEntryAdded(PlaylistEntry), 89 + /// Remove a perpetual playlist entry by id. 90 + PlaylistEntryRemoved(PlaylistEntryId), 91 + /// Toggle the perpetual playlist on/off. 92 + SetPlaylistEnabled(bool), 61 93 } 62 94 63 95 /// Side effects to execute after a transition. ··· 114 146 pub queue: Vec<QueuedTrack>, 115 147 /// Recently finished tracks (newest first). 116 148 pub history: Vec<FinishedTrack>, 149 + /// Resolved metadata for URLs this room has encountered, keyed by URL. 150 + /// When a track is queued, the state machine looks up its URL here to fill 151 + /// in title/duration/thumbnail without re-resolving. 152 + pub media_cache: HashMap<String, MediaStatus>, 153 + /// Perpetual playlist: idle rooms auto-fill from this queue. 154 + pub playlist: Vec<PlaylistEntry>, 155 + /// Index of the next playlist entry to draw from (`next_playlist_entry`). 156 + pub playlist_cursor: usize, 157 + /// When false, the perpetual playlist is paused; the room will not auto-fill. 158 + pub playlist_enabled: bool, 117 159 } 118 160 119 161 impl PlaybackState { ··· 127 169 Event::Skip => self.handle_skip(now), 128 170 Event::TrackEnded { item_id } => self.handle_track_ended(*item_id, now), 129 171 Event::TrackFailed { item_id } => self.handle_track_failed(*item_id, now), 130 - Event::MetadataResolved { 172 + Event::MetadataUpdated { 173 + item_id, 131 174 url, 132 175 title, 133 176 duration, 134 177 thumbnail, 135 178 source, 136 - } => self.handle_metadata_resolved(url, title, duration, thumbnail, source), 137 - Event::MetadataUpdated { 138 - item_id, 179 + } => self.handle_metadata_updated(*item_id, url, title, duration, thumbnail, source), 180 + Event::DownloadResolved { 139 181 url, 140 182 title, 141 183 duration, 142 184 thumbnail, 143 185 source, 144 - } => self.handle_metadata_updated(*item_id, url, title, duration, thumbnail, source), 186 + } => self.handle_download_resolved(url, title, duration, thumbnail, source), 187 + Event::DownloadFailed { url, error } => self.handle_download_failed(url, error), 188 + Event::TrackQueuedFromPlaylist(track, _source_index) => self.handle_queued(track), 189 + Event::PlaylistEntryAdded(entry) => self.handle_playlist_entry_added(entry), 190 + Event::PlaylistEntryRemoved(id) => self.handle_playlist_entry_removed(*id), 191 + Event::SetPlaylistEnabled(enabled) => self.handle_set_playlist_enabled(*enabled), 145 192 } 146 193 } 147 194 148 195 /// A track was added to the queue. 149 196 fn handle_queued(&mut self, track: &QueuedTrack) -> Vec<Effect> { 197 + // Resolve placeholder values from media_cache if the URL has been 198 + // resolved before. The actor can always send a placeholder track — 199 + // the state machine fills in real metadata when available. 200 + let track = self.fill_from_cache(track); 201 + 150 202 self.queue.push(track.clone()); 151 203 152 204 if self.active.is_some() { ··· 162 214 effects 163 215 } 164 216 217 + /// If `media_cache` has resolved metadata for this track's URL, return a 218 + /// new track with `pending = false` and the cached title/duration/thumbnail. 219 + /// Otherwise return the track unchanged. 220 + fn fill_from_cache(&self, track: &QueuedTrack) -> QueuedTrack { 221 + match self.media_cache.get(&track.url) { 222 + Some(MediaStatus::Resolved { 223 + title, 224 + duration, 225 + thumbnail, 226 + .. 227 + }) => QueuedTrack { 228 + id: track.id, 229 + title: title.clone(), 230 + url: track.url.clone(), 231 + duration: duration.clone(), 232 + thumbnail: thumbnail.clone(), 233 + pending: false, 234 + }, 235 + // Failed or absent: keep placeholder, pending stays true. 236 + _ => track.clone(), 237 + } 238 + } 239 + 165 240 /// User requested skip. 166 241 fn handle_skip(&mut self, _now: i64) -> Vec<Effect> { 167 242 self.finish_active_and_advance(true) ··· 269 344 /// Download completed: update ALL queued and active tracks matching `url`. 270 345 /// Produces a single PersistMetadata effect (media_cache is URL-keyed) 271 346 /// and a PublishSnapshot to broadcast the updated metadata. 272 - fn handle_metadata_resolved( 347 + /// Cache the URL as Resolved and update all matching queue/active tracks. 348 + /// Writes the result to `self.media_cache` so future `TrackQueued` events for 349 + /// the same URL can pre-fill from cache. 350 + fn handle_download_resolved( 273 351 &mut self, 274 352 url: &str, 275 353 title: &str, ··· 277 355 thumbnail: &Option<String>, 278 356 source: &str, 279 357 ) -> Vec<Effect> { 358 + // 1. Update media_cache. 359 + self.media_cache.insert( 360 + url.to_string(), 361 + MediaStatus::Resolved { 362 + title: title.to_string(), 363 + duration: duration.to_string(), 364 + thumbnail: thumbnail.clone(), 365 + source: source.to_string(), 366 + }, 367 + ); 368 + 369 + // 2. Update all matching queue tracks. 280 370 for item in &mut self.queue { 281 371 if item.url == url { 282 372 item.title = title.to_string(); ··· 285 375 item.pending = false; 286 376 } 287 377 } 378 + 379 + // 3. Update active track if URL matches. 288 380 if let Some(ref mut active) = self.active { 289 381 if active.url == url { 290 382 active.title = title.to_string(); ··· 292 384 active.thumbnail.clone_from(thumbnail); 293 385 } 294 386 } 387 + 295 388 vec![ 296 389 Effect::PersistMetadata { 297 390 // item_id unused — media_cache is keyed by url ··· 306 399 ] 307 400 } 308 401 402 + /// Cache the URL as Failed. Does not modify tracks directly — the actor 403 + /// fans out per-track `MetadataUpdated` + `TrackFailed` events for removal. 404 + fn handle_download_failed(&mut self, url: &str, error: &str) -> Vec<Effect> { 405 + self.media_cache 406 + .insert(url.to_string(), MediaStatus::Failed(error.to_string())); 407 + // No track mutation here. No publish either — the actor's per-track 408 + // TrackFailed events will trigger their own PublishSnapshot. 409 + Vec::new() 410 + } 411 + 412 + /// Add a playlist entry, replacing any existing entry with the same URL. 413 + /// Cursor unchanged: existing slot stays current. 414 + fn handle_playlist_entry_added(&mut self, entry: &PlaylistEntry) -> Vec<Effect> { 415 + if let Some(slot) = self.playlist.iter().position(|e| e.url == entry.url) { 416 + self.playlist[slot] = entry.clone(); 417 + } else { 418 + self.playlist.push(entry.clone()); 419 + } 420 + // Enable by default when the first entry is added. 421 + if !self.playlist_enabled { 422 + self.playlist_enabled = true; 423 + } 424 + vec![ 425 + Effect::AddPlaylistEntry { 426 + id: entry.id, 427 + url: entry.url.clone(), 428 + title: entry.title.clone(), 429 + duration: entry.duration.clone(), 430 + thumbnail: entry.thumbnail.clone(), 431 + source: entry.source.clone(), 432 + }, 433 + Effect::PublishSnapshot, 434 + ] 435 + } 436 + 437 + /// Remove a playlist entry. Clamp the cursor so it doesn't point past the end. 438 + fn handle_playlist_entry_removed(&mut self, id: PlaylistEntryId) -> Vec<Effect> { 439 + let prev_len = self.playlist.len(); 440 + self.playlist.retain(|e| e.id != id); 441 + if self.playlist.len() != prev_len { 442 + if self.playlist_cursor >= self.playlist.len() && !self.playlist.is_empty() { 443 + self.playlist_cursor = self.playlist.len() - 1; 444 + } 445 + if self.playlist.is_empty() { 446 + self.playlist_enabled = false; 447 + } 448 + return vec![Effect::RemovePlaylistEntry(id), Effect::PublishSnapshot]; 449 + } 450 + Vec::new() 451 + } 452 + 453 + /// Toggle the perpetual playlist on/off. Disabling is always a no-op for state. 454 + /// Re-enabling only takes effect if there's something to play. 455 + fn handle_set_playlist_enabled(&mut self, enabled: bool) -> Vec<Effect> { 456 + if enabled && self.playlist.is_empty() { 457 + // Re-enabling an empty playlist: leave it disabled, no-op. 458 + return Vec::new(); 459 + } 460 + if self.playlist_enabled == enabled { 461 + return Vec::new(); 462 + } 463 + self.playlist_enabled = enabled; 464 + // Don't publish on disable: it has no observable effect until something 465 + // else triggers a snapshot. Publish on enable so clients see the toggle. 466 + if enabled { 467 + vec![Effect::PublishSnapshot] 468 + } else { 469 + Vec::new() 470 + } 471 + } 472 + 473 + /// Pop the next playlist entry: returns the entry at the cursor and advances 474 + /// the cursor. Returns `None` when disabled, empty, or past the end. 475 + /// 476 + /// The actor converts the returned entry into a `TrackQueuedFromPlaylist` event. 477 + /// Keeping this as a separate step (helper + event) means the state machine 478 + /// doesn't need a "draw from playlist" event that mutates two fields in one go. 479 + pub(crate) fn next_playlist_entry(&mut self) -> Option<PlaylistEntry> { 480 + if !self.playlist_enabled { 481 + return None; 482 + } 483 + let entry = self.playlist.get(self.playlist_cursor)?.clone(); 484 + self.playlist_cursor = self.playlist_cursor.saturating_add(1); 485 + if self.playlist_cursor >= self.playlist.len() { 486 + self.playlist_cursor = 0; 487 + } 488 + Some(entry) 489 + } 490 + 309 491 /// Pop the first track from the queue and start playing it. 310 492 /// 311 493 /// Returns effects for starting the pipeline and publishing state. ··· 361 543 362 544 // Append existing history 363 545 state.history.extend(snapshot.history); 546 + 547 + // Restore perpetual playlist. If there are entries, the playlist is 548 + // enabled by default (no way to persist "disabled" yet, so we treat 549 + // a non-empty playlist as the user's intent to autoplay). 550 + state.playlist = snapshot.playlist; 551 + if !state.playlist.is_empty() { 552 + state.playlist_enabled = true; 553 + } 364 554 365 555 if !effects.is_empty() { 366 556 effects.push(Effect::PublishSnapshot); ··· 839 1029 } 840 1030 841 1031 #[test] 842 - fn test_metadata_resolved_updates_all_matching_by_url() { 1032 + fn test_download_resolved_updates_all_matching_by_url() { 843 1033 // Four tracks: first advances to active (different URL), three in queue 844 1034 // where two share the same URL. 845 1035 let mut s = PlaybackState::default(); ··· 859 1049 // Queue: [B (shared), C (other), D (shared)] — indices 0, 1, 2. 860 1050 861 1051 let effects = s.transition( 862 - &Event::MetadataResolved { 1052 + &Event::DownloadResolved { 863 1053 url: "https://example.com/shared".into(), 864 1054 title: "Real Title".into(), 865 1055 duration: "3:30".into(), ··· 885 1075 // Active track has different URL, should be unchanged. 886 1076 assert_eq!(s.active.as_ref().unwrap().title, "Active"); 887 1077 888 - assert_eq!( 889 - effects, 890 - vec![ 891 - Effect::PersistMetadata { 892 - item_id: TrackId::nil(), 893 - url: "https://example.com/shared".into(), 894 - title: "Real Title".into(), 895 - duration: "3:30".into(), 896 - thumbnail: Some("https://img.example/thumb.jpg".into()), 897 - source: "ytdlp".into(), 898 - }, 899 - Effect::PublishSnapshot, 900 - ] 901 - ); 1078 + // Cache populated, snapshot published. 1079 + assert!(matches!( 1080 + s.media_cache.get("https://example.com/shared"), 1081 + Some(crate::state::MediaStatus::Resolved { .. }) 1082 + )); 1083 + assert!(effects.contains(&Effect::PublishSnapshot)); 902 1084 } 903 1085 904 1086 #[test] 905 - fn test_metadata_resolved_updates_active_track() { 1087 + fn test_download_resolved_updates_active_track() { 906 1088 let mut s = PlaybackState::default(); 907 1089 // Queue one track — room is idle, so it advances to active. 908 1090 s.transition(&Event::TrackQueued(queued("Active", 1)), 0); 909 1091 s.active.as_mut().unwrap().url = "https://example.com/active".into(); 910 1092 911 - let effects = s.transition( 912 - &Event::MetadataResolved { 1093 + s.transition( 1094 + &Event::DownloadResolved { 913 1095 url: "https://example.com/active".into(), 914 1096 title: "Resolved Active".into(), 915 1097 duration: "2:00".into(), ··· 921 1103 922 1104 assert_eq!(s.active.as_ref().unwrap().title, "Resolved Active"); 923 1105 assert_eq!(s.active.as_ref().unwrap().duration, "2:00"); 1106 + } 1107 + 1108 + // --- media_cache tests --- 924 1109 1110 + #[test] 1111 + fn test_download_resolved_populates_media_cache() { 1112 + let mut s = PlaybackState::default(); 1113 + s.transition( 1114 + &Event::DownloadResolved { 1115 + url: "https://example.com/x".into(), 1116 + title: "Real Title".into(), 1117 + duration: "3:00".into(), 1118 + thumbnail: Some("https://img/x.jpg".into()), 1119 + source: "ytdlp".into(), 1120 + }, 1121 + 0, 1122 + ); 925 1123 assert_eq!( 926 - effects, 927 - vec![ 928 - Effect::PersistMetadata { 929 - item_id: TrackId::nil(), 930 - url: "https://example.com/active".into(), 931 - title: "Resolved Active".into(), 932 - duration: "2:00".into(), 933 - thumbnail: None, 934 - source: "ytdlp".into(), 935 - }, 936 - Effect::PublishSnapshot, 937 - ] 1124 + s.media_cache.get("https://example.com/x"), 1125 + Some(&MediaStatus::Resolved { 1126 + title: "Real Title".into(), 1127 + duration: "3:00".into(), 1128 + thumbnail: Some("https://img/x.jpg".into()), 1129 + source: "ytdlp".into(), 1130 + }) 1131 + ); 1132 + } 1133 + 1134 + #[test] 1135 + fn test_download_resolved_updates_matching_queue_tracks() { 1136 + let mut s = PlaybackState::default(); 1137 + // First track advances to active. 1138 + s.transition(&Event::TrackQueued(queued("Active", 1)), 0); 1139 + // Two queued tracks sharing a URL. 1140 + let mut q1 = queued("B-pending", 2); 1141 + q1.url = "https://example.com/shared".into(); 1142 + q1.pending = true; 1143 + s.transition(&Event::TrackQueued(q1), 0); 1144 + s.transition(&Event::TrackQueued(queued("C-other", 3)), 0); 1145 + let mut q2 = queued("D-pending", 4); 1146 + q2.url = "https://example.com/shared".into(); 1147 + q2.pending = true; 1148 + s.transition(&Event::TrackQueued(q2), 0); 1149 + 1150 + s.transition( 1151 + &Event::DownloadResolved { 1152 + url: "https://example.com/shared".into(), 1153 + title: "Shared Title".into(), 1154 + duration: "4:20".into(), 1155 + thumbnail: Some("https://img/shared.jpg".into()), 1156 + source: "ytdlp".into(), 1157 + }, 1158 + 0, 1159 + ); 1160 + 1161 + // Both B and D in queue now have resolved metadata. 1162 + assert_eq!(s.queue[0].title, "Shared Title"); 1163 + assert_eq!(s.queue[0].duration, "4:20"); 1164 + assert!(!s.queue[0].pending); 1165 + assert_eq!(s.queue[2].title, "Shared Title"); 1166 + assert!(!s.queue[2].pending); 1167 + // C (different URL) is unchanged. 1168 + assert_eq!(s.queue[1].title, "C-other"); 1169 + } 1170 + 1171 + #[test] 1172 + fn test_download_resolved_updates_active_track_when_url_matches() { 1173 + let mut s = PlaybackState::default(); 1174 + s.transition(&Event::TrackQueued(queued("Active", 1)), 0); 1175 + s.active.as_mut().unwrap().url = "https://example.com/active".into(); 1176 + 1177 + s.transition( 1178 + &Event::DownloadResolved { 1179 + url: "https://example.com/active".into(), 1180 + title: "Resolved".into(), 1181 + duration: "2:00".into(), 1182 + thumbnail: None, 1183 + source: "ytdlp".into(), 1184 + }, 1185 + 0, 1186 + ); 1187 + 1188 + assert_eq!(s.active.as_ref().unwrap().title, "Resolved"); 1189 + assert_eq!(s.active.as_ref().unwrap().duration, "2:00"); 1190 + } 1191 + 1192 + #[test] 1193 + fn test_download_failed_marks_cache_failed() { 1194 + let mut s = PlaybackState::default(); 1195 + s.transition( 1196 + &Event::DownloadFailed { 1197 + url: "https://example.com/bad".into(), 1198 + error: "404".into(), 1199 + }, 1200 + 0, 1201 + ); 1202 + assert_eq!( 1203 + s.media_cache.get("https://example.com/bad"), 1204 + Some(&MediaStatus::Failed("404".into())) 1205 + ); 1206 + } 1207 + 1208 + #[test] 1209 + fn test_download_resolved_overwrites_failed() { 1210 + let mut s = PlaybackState::default(); 1211 + s.transition( 1212 + &Event::DownloadFailed { 1213 + url: "https://example.com/x".into(), 1214 + error: "transient".into(), 1215 + }, 1216 + 0, 1217 + ); 1218 + s.transition( 1219 + &Event::DownloadResolved { 1220 + url: "https://example.com/x".into(), 1221 + title: "Recovered".into(), 1222 + duration: "1:00".into(), 1223 + thumbnail: None, 1224 + source: "ytdlp".into(), 1225 + }, 1226 + 0, 1227 + ); 1228 + assert!(matches!( 1229 + s.media_cache.get("https://example.com/x"), 1230 + Some(MediaStatus::Resolved { .. }) 1231 + )); 1232 + } 1233 + 1234 + #[test] 1235 + fn test_track_queued_uses_cached_metadata() { 1236 + // The headline behavior: re-queuing a URL that's been resolved 1237 + // returns a fully-formed track, not a placeholder. 1238 + let mut s = PlaybackState::default(); 1239 + let mut t = QueuedTrack { 1240 + id: track_id(1), 1241 + title: "Loading...".into(), 1242 + url: "https://example.com/cached".into(), 1243 + duration: "--:--".into(), 1244 + thumbnail: None, 1245 + pending: true, 1246 + }; 1247 + s.transition(&Event::TrackQueued(t.clone()), 0); 1248 + s.transition( 1249 + &Event::DownloadResolved { 1250 + url: "https://example.com/cached".into(), 1251 + title: "Real".into(), 1252 + duration: "5:00".into(), 1253 + thumbnail: Some("https://img/real.jpg".into()), 1254 + source: "ytdlp".into(), 1255 + }, 1256 + 0, 1257 + ); 1258 + // Now queue another track with the same URL. 1259 + t.id = track_id(2); 1260 + t.title = "Loading...".into(); 1261 + t.duration = "--:--".into(); 1262 + t.thumbnail = None; 1263 + t.pending = true; 1264 + let effects = s.transition(&Event::TrackQueued(t.clone()), 1); 1265 + // The track in the queue should be the resolved one. 1266 + assert_eq!(s.queue.last().unwrap().title, "Real"); 1267 + assert_eq!(s.queue.last().unwrap().duration, "5:00"); 1268 + assert!(!s.queue.last().unwrap().pending); 1269 + // And the active track (track 1) shouldn't be affected by this. 1270 + assert_eq!(s.active.as_ref().unwrap().title, "Real"); 1271 + // Sanity: the persist effect should carry the resolved track. 1272 + assert!(effects 1273 + .iter() 1274 + .any(|e| matches!(e, Effect::PersistQueuedTrack(q) if q.title == "Real"))); 1275 + } 1276 + 1277 + #[test] 1278 + fn test_track_queued_keeps_placeholder_after_failed_download() { 1279 + let mut s = PlaybackState::default(); 1280 + s.transition( 1281 + &Event::DownloadFailed { 1282 + url: "https://example.com/x".into(), 1283 + error: "500".into(), 1284 + }, 1285 + 0, 1286 + ); 1287 + let t = QueuedTrack { 1288 + id: track_id(1), 1289 + title: "Loading...".into(), 1290 + url: "https://example.com/x".into(), 1291 + duration: "--:--".into(), 1292 + thumbnail: None, 1293 + pending: true, 1294 + }; 1295 + s.transition(&Event::TrackQueued(t), 0); 1296 + // Active track is the just-queued one. Placeholder preserved. 1297 + assert_eq!(s.active.as_ref().unwrap().title, "Loading..."); 1298 + assert_eq!(s.active.as_ref().unwrap().duration, "--:--"); 1299 + } 1300 + 1301 + // --- playlist tests --- 1302 + 1303 + fn playlist_entry(id: u64, url: &str) -> PlaylistEntry { 1304 + PlaylistEntry { 1305 + id: PlaylistEntryId(uuid::Uuid::from_u64_pair(0, id)), 1306 + url: url.into(), 1307 + title: format!("P-{id}"), 1308 + duration: "1:00".into(), 1309 + thumbnail: None, 1310 + source: "user".into(), 1311 + added_by: None, 1312 + added_at: "2026-01-01T00:00:00Z".into(), 1313 + pending: false, 1314 + } 1315 + } 1316 + 1317 + #[test] 1318 + fn test_playlist_add_appends_and_enables() { 1319 + let mut s = PlaybackState::default(); 1320 + let e = playlist_entry(1, "https://example.com/p1"); 1321 + let effects = s.transition(&Event::PlaylistEntryAdded(e.clone()), 0); 1322 + assert_eq!(s.playlist.len(), 1); 1323 + assert!(s.playlist_enabled); 1324 + assert!(effects 1325 + .iter() 1326 + .any(|e| matches!(e, Effect::AddPlaylistEntry { .. }))); 1327 + assert!(effects.iter().any(|e| matches!(e, Effect::PublishSnapshot))); 1328 + } 1329 + 1330 + #[test] 1331 + fn test_playlist_add_replaces_same_url() { 1332 + let mut s = PlaybackState::default(); 1333 + s.transition( 1334 + &Event::PlaylistEntryAdded(playlist_entry(1, "https://example.com/p1")), 1335 + 0, 938 1336 ); 1337 + // Same URL, different id: should replace, not append. 1338 + s.transition( 1339 + &Event::PlaylistEntryAdded(playlist_entry(2, "https://example.com/p1")), 1340 + 0, 1341 + ); 1342 + assert_eq!(s.playlist.len(), 1); 1343 + assert_eq!(s.playlist[0].id.0, uuid::Uuid::from_u64_pair(0, 2)); 1344 + } 1345 + 1346 + #[test] 1347 + fn test_playlist_remove_filters_and_clamps_cursor() { 1348 + let mut s = PlaybackState::default(); 1349 + s.transition( 1350 + &Event::PlaylistEntryAdded(playlist_entry(1, "https://example.com/a")), 1351 + 0, 1352 + ); 1353 + s.transition( 1354 + &Event::PlaylistEntryAdded(playlist_entry(2, "https://example.com/b")), 1355 + 0, 1356 + ); 1357 + s.transition( 1358 + &Event::PlaylistEntryAdded(playlist_entry(3, "https://example.com/c")), 1359 + 0, 1360 + ); 1361 + s.playlist_cursor = 3; // past the end after removal 1362 + let id = PlaylistEntryId(uuid::Uuid::from_u64_pair(0, 3)); 1363 + let effects = s.transition(&Event::PlaylistEntryRemoved(id), 0); 1364 + assert_eq!(s.playlist.len(), 2); 1365 + // Cursor should be clamped to last valid index (1). 1366 + assert_eq!(s.playlist_cursor, 1); 1367 + assert!(effects 1368 + .iter() 1369 + .any(|e| matches!(e, Effect::RemovePlaylistEntry(_)))); 1370 + } 1371 + 1372 + #[test] 1373 + fn test_playlist_remove_last_disables() { 1374 + let mut s = PlaybackState::default(); 1375 + s.transition( 1376 + &Event::PlaylistEntryAdded(playlist_entry(1, "https://example.com/a")), 1377 + 0, 1378 + ); 1379 + let id = PlaylistEntryId(uuid::Uuid::from_u64_pair(0, 1)); 1380 + s.transition(&Event::PlaylistEntryRemoved(id), 0); 1381 + assert!(s.playlist.is_empty()); 1382 + assert!(!s.playlist_enabled); 1383 + } 1384 + 1385 + #[test] 1386 + fn test_set_playlist_enabled_noop_when_same() { 1387 + let mut s = PlaybackState { 1388 + playlist_enabled: true, 1389 + playlist: vec![playlist_entry(1, "https://example.com/a")], 1390 + ..PlaybackState::default() 1391 + }; 1392 + let effects = s.transition(&Event::SetPlaylistEnabled(true), 0); 1393 + assert!(effects.is_empty()); 1394 + } 1395 + 1396 + #[test] 1397 + fn test_set_playlist_enabled_disable_is_quiet() { 1398 + let mut s = PlaybackState { 1399 + playlist_enabled: true, 1400 + playlist: vec![playlist_entry(1, "https://example.com/a")], 1401 + ..PlaybackState::default() 1402 + }; 1403 + let effects = s.transition(&Event::SetPlaylistEnabled(false), 0); 1404 + assert!(!s.playlist_enabled); 1405 + assert!(effects.is_empty()); 1406 + } 1407 + 1408 + #[test] 1409 + fn test_set_playlist_enabled_enable_publishes() { 1410 + let mut s = PlaybackState { 1411 + playlist_enabled: false, 1412 + playlist: vec![playlist_entry(1, "https://example.com/a")], 1413 + ..PlaybackState::default() 1414 + }; 1415 + let effects = s.transition(&Event::SetPlaylistEnabled(true), 0); 1416 + assert!(s.playlist_enabled); 1417 + assert!(effects.iter().any(|e| matches!(e, Effect::PublishSnapshot))); 1418 + } 1419 + 1420 + #[test] 1421 + fn test_set_playlist_enabled_re_enable_empty_noop() { 1422 + let mut s = PlaybackState { 1423 + playlist_enabled: false, 1424 + ..PlaybackState::default() 1425 + }; 1426 + let effects = s.transition(&Event::SetPlaylistEnabled(true), 0); 1427 + assert!(!s.playlist_enabled); 1428 + assert!(effects.is_empty()); 1429 + } 1430 + 1431 + #[test] 1432 + fn test_next_playlist_entry_disabled_returns_none() { 1433 + let mut s = PlaybackState { 1434 + playlist_enabled: false, 1435 + playlist: vec![playlist_entry(1, "https://example.com/a")], 1436 + ..PlaybackState::default() 1437 + }; 1438 + assert!(s.next_playlist_entry().is_none()); 1439 + } 1440 + 1441 + #[test] 1442 + fn test_next_playlist_entry_advances_cursor() { 1443 + let mut s = PlaybackState { 1444 + playlist_enabled: true, 1445 + playlist: vec![ 1446 + playlist_entry(1, "https://example.com/a"), 1447 + playlist_entry(2, "https://example.com/b"), 1448 + ], 1449 + ..PlaybackState::default() 1450 + }; 1451 + let first = s.next_playlist_entry().unwrap(); 1452 + assert_eq!(first.id.0, uuid::Uuid::from_u64_pair(0, 1)); 1453 + assert_eq!(s.playlist_cursor, 1); 1454 + let second = s.next_playlist_entry().unwrap(); 1455 + assert_eq!(second.id.0, uuid::Uuid::from_u64_pair(0, 2)); 1456 + // Cursor wraps to 0. 1457 + assert_eq!(s.playlist_cursor, 0); 1458 + } 1459 + 1460 + #[test] 1461 + fn test_track_queued_from_playlist_uses_handle_queued_path() { 1462 + let mut s = PlaybackState::default(); 1463 + let q = QueuedTrack { 1464 + id: track_id(1), 1465 + title: "PL".into(), 1466 + url: "https://example.com/pl".into(), 1467 + duration: "1:00".into(), 1468 + thumbnail: None, 1469 + pending: true, 1470 + }; 1471 + s.transition(&Event::TrackQueuedFromPlaylist(q, 0), 0); 1472 + // Active track should be the queued one. 1473 + assert_eq!(s.active.as_ref().unwrap().title, "PL"); 939 1474 } 940 1475 } 941 1476 ··· 958 1493 url: format!("https://example.com/track-{n}"), 959 1494 duration: "3:00".into(), 960 1495 thumbnail: None, 961 - pending: n % 3 == 0, 1496 + pending: n.is_multiple_of(3), 962 1497 } 963 1498 } 964 1499 ··· 968 1503 Skip, 969 1504 TrackEnded(u64), 970 1505 TrackFailed(u64), 971 - MetadataResolved(u64), 972 1506 MetadataUpdated(u64), 1507 + DownloadResolved(u64), 1508 + DownloadFailed(u64), 973 1509 } 974 1510 975 1511 fn op_strategy() -> impl Strategy<Value = Vec<Op>> { ··· 978 1514 Just(Op::Skip), 979 1515 (0u64..12).prop_map(Op::TrackEnded), 980 1516 (0u64..12).prop_map(Op::TrackFailed), 981 - (0u64..12).prop_map(Op::MetadataResolved), 982 1517 (0u64..12).prop_map(Op::MetadataUpdated), 1518 + (0u64..12).prop_map(Op::DownloadResolved), 1519 + (0u64..12).prop_map(Op::DownloadFailed), 983 1520 ]; 984 1521 proptest::collection::vec(op, 0..50) 985 1522 } ··· 1023 1560 } 1024 1561 effects 1025 1562 } 1026 - Op::MetadataResolved(n) => { 1563 + Op::MetadataUpdated(n) => { 1564 + let q = queued(*n); 1565 + state.transition( 1566 + &Event::MetadataUpdated { 1567 + item_id: q.id, 1568 + url: q.url, 1569 + title: format!("Updated-{n}"), 1570 + duration: "5:00".into(), 1571 + thumbnail: None, 1572 + source: "ytdlp".into(), 1573 + }, 1574 + 0, 1575 + ) 1576 + } 1577 + Op::DownloadResolved(n) => { 1027 1578 let q = queued(*n); 1579 + let url = q.url.clone(); 1028 1580 let effects = state.transition( 1029 - &Event::MetadataResolved { 1581 + &Event::DownloadResolved { 1030 1582 url: q.url.clone(), 1031 - title: format!("Resolved-{n}"), 1583 + title: format!("Cached-{n}"), 1032 1584 duration: "4:20".into(), 1033 - thumbnail: None, 1585 + thumbnail: Some(format!("https://img/{n}.jpg")), 1034 1586 source: "ytdlp".into(), 1035 1587 }, 1036 1588 0, 1037 1589 ); 1038 - // Check per-event: all queue tracks matching this URL 1039 - // must now be !pending. 1590 + // After DownloadResolved, the cache must hold a Resolved entry. 1591 + assert!( 1592 + matches!( 1593 + state.media_cache.get(&url), 1594 + Some(crate::state::MediaStatus::Resolved { .. }) 1595 + ), 1596 + "DownloadResolved({url}) did not populate media_cache as Resolved", 1597 + ); 1598 + // And any queue track with that URL must be !pending. 1040 1599 for t in &state.queue { 1041 - if t.url == q.url { 1600 + if t.url == url { 1042 1601 assert!( 1043 1602 !t.pending, 1044 - "queue track {id} still pending after MetadataResolved({url})", 1603 + "queue track {id} still pending after DownloadResolved({url})", 1045 1604 id = t.id, 1046 - url = q.url, 1047 1605 ); 1048 1606 } 1049 1607 } 1050 1608 effects 1051 1609 } 1052 - Op::MetadataUpdated(n) => { 1610 + Op::DownloadFailed(n) => { 1053 1611 let q = queued(*n); 1054 - state.transition( 1055 - &Event::MetadataUpdated { 1056 - item_id: q.id, 1612 + let url = q.url.clone(); 1613 + let effects = state.transition( 1614 + &Event::DownloadFailed { 1057 1615 url: q.url, 1058 - title: format!("Updated-{n}"), 1059 - duration: "5:00".into(), 1060 - thumbnail: None, 1061 - source: "ytdlp".into(), 1616 + error: format!("err-{n}"), 1062 1617 }, 1063 1618 0, 1064 - ) 1619 + ); 1620 + assert_eq!( 1621 + state.media_cache.get(&url), 1622 + Some(&crate::state::MediaStatus::Failed(format!("err-{n}"))), 1623 + "DownloadFailed({url}) did not record Failed in media_cache", 1624 + ); 1625 + effects 1065 1626 } 1066 1627 }; 1067 1628 ··· 1072 1633 // should never panic. 1073 1634 let _ = state.transition(&Event::Skip, 0); 1074 1635 let _ = state.transition( 1075 - &Event::MetadataResolved { 1636 + &Event::DownloadResolved { 1076 1637 url: "https://example.com/ghost".into(), 1077 1638 title: "Ghost".into(), 1078 1639 duration: "0:00".into(),
+1 -1
src/store.rs
··· 92 92 /// Retrieve the most recent chat messages for a room, newest first. 93 93 #[allow(dead_code)] 94 94 async fn recent_chat(&self, room_id: &str, limit: i64) 95 - -> Result<Vec<ChatMessage>, sqlx::Error>; 95 + -> Result<Vec<ChatMessage>, sqlx::Error>; 96 96 97 97 /// Delete a room and all its associated data (CASCADE). 98 98 #[allow(dead_code)]
+5
src/transport.rs
··· 11 11 //! - `{ "type": "chat", "content": "..." }` 12 12 //! - `{ "type": "skip" }` 13 13 //! - `{ "type": "track_ended", "item_id": "..." }` 14 + //! - `{ "type": "set_playlist_enabled", "enabled": true|false }` 14 15 //! - `{ "type": "ping" }` 15 16 16 17 use axum::body::Bytes; ··· 28 29 Chat { content: String }, 29 30 Skip, 30 31 TrackEnded { item_id: TrackId }, 32 + SetPlaylistEnabled { enabled: bool }, 31 33 Ping, 32 34 } 33 35 ··· 119 121 } 120 122 WsClientMessage::TrackEnded { item_id } => { 121 123 let _ = cmd_tx.send(RoomCommand::TrackEnded { item_id }).await; 124 + } 125 + WsClientMessage::SetPlaylistEnabled { enabled } => { 126 + let _ = cmd_tx.send(RoomCommand::SetPlaylistEnabled(enabled)).await; 122 127 } 123 128 WsClientMessage::Ping => { 124 129 let _ = ws_tx.send(Message::Ping(Bytes::new())).await;
+4 -4
src/web.rs
··· 9 9 use std::sync::Arc; 10 10 11 11 use axum::{ 12 - Router, 13 12 extract::{ConnectInfo, Path, Query, State, WebSocketUpgrade}, 14 - http::{StatusCode, header}, 13 + http::{header, StatusCode}, 15 14 response::{IntoResponse, Json, Response}, 16 15 routing::{get, post}, 16 + Router, 17 17 }; 18 18 use tokio::sync::Mutex; 19 19 ··· 344 344 #[cfg(test)] 345 345 mod tests { 346 346 use super::*; 347 - use axum::body::{Body, to_bytes}; 348 - use axum::http::{Request, header}; 347 + use axum::body::{to_bytes, Body}; 348 + use axum::http::{header, Request}; 349 349 use std::path::PathBuf; 350 350 use tower::ServiceExt; 351 351
-1
static/dist/assets/index-OCnj7PcQ.js
··· 1 - (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const o of i)if(o.type==="childList")for(const s of o.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&r(s)}).observe(document,{childList:!0,subtree:!0});function n(i){const o={};return i.integrity&&(o.integrity=i.integrity),i.referrerPolicy&&(o.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?o.credentials="include":i.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function r(i){if(i.ep)return;i.ep=!0;const o=n(i);fetch(i.href,o)}})();class K extends Error{source;constructor(t){super(),this.source=t}}class qt extends Error{source;constructor(t,n){super(n instanceof Error?n.message:String(n),{cause:n}),this.source=t}}class vn extends Error{constructor(){super("")}}class cr extends Error{constructor(){super("")}}const $n=0,je=1,Ce=2,mt=4,le=8,We=16,J=32,de=64,me=128,Wt=256,ut=512,Ve=1024,pt=1,ar=2,yt=4,fr=8,_n=16,Re=32,Vt=64,O=1,U=2,D=4,Se=1,G=2,ve=3,_={},dr=typeof Proxy=="function",zt={},hr=Symbol("refresh");function Sn(e,t){const n=(e.i?.t?e.i.u?.o:e.i?.o)??-1;n>=e.o&&(e.o=n+1);const r=e.o,i=t.l[r];if(i===void 0)t.l[r]=e;else{const o=i.S;o.T=e,e.S=o,i.S=e}r>t._&&(t._=r)}function Je(e,t){let n=e.O;n&(le|mt|Ve)||(n&je?e.O=n&-4|Ce|le:e.O=n|le,n&We||Sn(e,t))}function En(e,t){let n=e.O;n&(le|mt|We|Ve)||(e.O=n|We,Sn(e,t))}function Pe(e,t){const n=e.O;if(!(n&(le|We)))return;e.O=n&-25;const r=e.o;if(e.S===e)t.l[r]=void 0;else{const i=e.T,o=t.l[r],s=i??o;e===o?t.l[r]=i:e.S.T=i,s.S=e.S}e.S=e,e.T=void 0}function gr(e){if(!e.R){e.R=!0;for(let t=0;t<=e._;t++)for(let n=e.l[t];n!==void 0;n=n.T)n.O&le&&ct(n)}}function ct(e,t=Ce){const n=e.O;if(!((n&(je|Ce))>=t)){e.O=n&-4|t;for(let r=e.I;r!==null;r=r.p)ct(r.h,je);if(e.N!==null)for(let r=e.N;r!==null;r=r.A)for(let i=r.I;i!==null;i=i.p)ct(i.h,je)}}function Ne(e,t){for(e.R=!1,e.C=0;e.C<=e._;e.C++){let n=e.l[e.C];for(;n!==void 0;)n.O&le?t(n):mr(n,e),n=e.l[e.C]}e._=0}function mr(e,t){Pe(e,t);let n=e.o;for(let r=e.P;r;r=r.D){const i=r.m,o=i.V||i;o.L&&o.o>=n&&(n=o.o+1)}if(e.o!==n){e.o=n;for(let r=e.I;r!==null;r=r.p)En(r.h,t)}}const at=new WeakMap,te=new Set;function pr(e){let t=at.get(e);if(t)return W(t);const n=e.U,r=n?.G?W(n.G):null;return t={k:e,F:new Set,W:[[],[]],H:null,M:b,j:r},at.set(e,t),te.add(t),e.$=!1,t}function W(e){for(;e.H;)e=e.H;return e}function Cn(e,t){if(e=W(e),t=W(t),e===t)return e;t.H=e;for(const n of t.F)e.F.add(n);return e.W[0].push(...t.W[0]),e.W[1].push(...t.W[1]),e}function Ee(e){const t=e.G;if(!t)return;const n=W(t);if(te.has(n))return n;e.G=void 0}function qe(e){return Ee(e)?.M??e.M}function wt(e){return e.K!==void 0&&e.K!==_}function Gt(e,t){const n=W(t),r=e.G;if(r){if(r.H){e.G=t;return}const i=W(r);if(te.has(i)){i!==n&&!wt(e)&&(n.j&&W(n.j)===i?e.G=t:i.j&&W(i.j)===n||Cn(n,i));return}}e.G=t}const Me=new Set,x={l:new Array(2e3).fill(void 0),R:!1,C:0,_:0},H={l:new Array(2e3).fill(void 0),R:!1,C:0,_:0};let M=0,b=null,ze=!1,Mt=0,ot=null;const De=new Set;function yr(e){return Me.size===0&&te.size===0&&e.Y.length===0&&e.Z.length===0&&e.q.size===0&&De.size===0}function wr(){if(De.size!==0)for(const e of De){if(e.I!==null){De.delete(e);continue}e.B===_&&(e.K!==void 0&&e.K!==_||(De.delete(e),e.X?.()))}}function br(e){return!!ot?.has(e)}function et(e){for(const t of te){if(t.H||t.F.size>0)continue;const n=t.W[e-1];n.length&&(t.W[e-1]=[],dt(n,e))}}function vr(e){for(let t=e.I;t!==null;t=t.p){const n=t.h;if(!n.J)continue;if(n.J===ve){n.ee||(n.ee=!0,n.te.enqueue(G,n.ne));continue}const r=n.O&J?H:x;r.C>n.o&&(r.C=n.o),Je(n,r)}}function $r(e,t){t.ie=e,e.re.push(...t.re);for(const n of te)n.M===t&&(n.M=e);e.Z.push(...t.Z);for(const n of t.q)e.q.add(n);for(const[n,r]of t.oe){let i=e.oe.get(n);i||e.oe.set(n,i=new Set);for(const o of r)i.add(o)}for(const n of t.se)e.se.add(n)}function _r(e){for(let t=0;t<e.length;t++){const n=e[t];n.G=void 0,n.B!==_&&(n.ue=n.B,n.B=_);const r=n.K;n.K=_,r!==_&&n.ue!==r&&Oe(n,!0),n.M=null}e.length=0}function Sr(e){for(const t of te)(e?t.M===e:!t.M)&&(t.H||(t.W[0].length&&dt(t.W[0],Se),t.W[1].length&&dt(t.W[1],G)),t.k.G===t&&(t.k.G=void 0),t.F.clear(),t.W[0].length=0,t.W[1].length=0,te.delete(t),at.delete(t.k))}function we(){ze||(ze=!0,!Mt&&!E.ce&&queueMicrotask(Te))}let Er=class{i=null;le=[[],[]];Y=[];created=M;addChild(t){this.Y.push(t),t.i=this}removeChild(t){const n=this.Y.indexOf(t);n>=0&&(this.Y.splice(n,1),t.i=null)}notify(t,n,r,i){return this.i?this.i.notify(t,n,r,i):!1}run(t){if(this.le[t-1].length){const n=this.le[t-1];this.le[t-1]=[],dt(n,t)}for(let n=0;n<this.Y.length;n++)this.Y[n].run?.(t)}enqueue(t,n){t&&(z?W(z).W[t-1].push(n):this.le[t-1].push(n)),we()}stashQueues(t){t.le[0].push(...this.le[0]),t.le[1].push(...this.le[1]),this.le=[[],[]];for(let n=0;n<this.Y.length;n++){let r=this.Y[n],i=t.Y[n];i||(i={le:[[],[]],Y:[]},t.Y[n]=i),r.stashQueues(i)}}restoreQueues(t){this.le[0].push(...t.le[0]),this.le[1].push(...t.le[1]);for(let n=0;n<t.Y.length;n++){const r=t.Y[n];let i=this.Y[n];i&&i.restoreQueues(r)}}};class Q extends Er{ce=!1;ae=null;fe=[];Z=[];q=new Set;static Ee;static Se;static Te;static de=null;flush(){if(!this.ce){this.ce=!0;try{if(Ne(x,Q.Ee),b){if(!Or(b)){const r=b;if(Ne(H,Q.Ee),this.ae=null,this.fe=[],this.Z=[],this.q=new Set,et(Se),et(G),this.stashQueues(r._e),M++,ze=x._>=x.C,sn(r.fe),b=null,!r.re.length&&!r.oe.size&&r.Z.length){ot=new Set;for(let i=0;i<r.Z.length;i++){const o=r.Z[i];o.L||o.Oe&pt||(ot.add(o),vr(o))}}try{At(null,!0)}finally{ot=null}return}this.fe!==b.fe&&this.fe.push(...b.fe),this.restoreQueues(b._e),Me.delete(b);const n=b;b=null,sn(this.fe),At(n)}else yr(this)?(ft(),x._>=x.C&&(Ne(x,Q.Ee),ft())):(Me.size&&Ne(H,Q.Ee),At());M++,ze=x._>=x.C,te.size&&et(Se),this.run(Se),te.size&&et(G),this.run(G)}finally{this.ce=!1}}}notify(t,n,r,i){if(n&O){if(r&O){const o=i!==void 0?i:t.Re;if(b&&o){const s=o.source;let u=b.oe.get(s);u||b.oe.set(s,u=new Set);const l=u.size;u.add(t),u.size!==l&&we()}}return!0}return!1}initTransition(t){if(t&&(t=Tn(t)),!(t&&t===b)&&!(!t&&b&&b.Ie===M)){if(!b)b=t??{Ie:M,fe:[],oe:new Map,Z:[],q:new Set,re:[],_e:{le:[[],[]],Y:[]},ie:!1,se:new Set};else if(t){const n=b;$r(t,n),Me.delete(n),b=t}if(Me.add(b),b.Ie=M,this.ae!==null&&(this.ae.M=b,b.fe.push(this.ae),this.ae=null),this.fe!==b.fe){for(let n=0;n<this.fe.length;n++){const r=this.fe[n];r.M=b,b.fe.push(r)}this.fe=b.fe}if(this.Z!==b.Z){for(let n=0;n<this.Z.length;n++){const r=this.Z[n];r.M=b,b.Z.push(r)}this.Z=b.Z}for(const n of te)n.M||(n.M=b);if(this.q!==b.q){for(const n of this.q)b.q.add(n);this.q=b.q}}}}function bt(e){if(b){E.fe.push(e);return}if(E.ae===null&&E.fe.length===0){E.ae=e;return}E.ae!==null&&(E.fe.push(E.ae),E.ae=null),E.fe.push(e)}function Oe(e,t=!1){const n=e.G||z,r=e.pe!==void 0;for(let i=e.I;i!==null;i=i.p){if(r&&i.h.Oe&fr){i.h.O|=Wt;continue}t&&n?(i.h.O|=me,Gt(i.h,n)):t&&(i.h.O|=me,i.h.G=void 0);const o=i.h;if(o.J===ve){o.ee||(o.ee=!0,o.te.enqueue(G,o.ne));continue}const s=i.h.O&J?H:x;s.C>i.h.o&&(s.C=i.h.o),Je(i.h,s)}}function on(e){const t=e;if(!t.L){e.B!==_&&(e.ue=e.B,e.B=_);return}e.B!==_&&(e.ue=e.B,e.B=_,e.J&&e.J!==ve&&(e.ee=!0)),t.O&=~Ve,t.he&O||(t.he&=~D),(t.Ne!==null||t.Ae!==null)&&Q.Se(t,!1,!0)}function ft(){E.ae!==null&&(on(E.ae),E.ae=null);const e=E.fe;for(let t=0;t<e.length;t++)on(e[t]);e.length=0}function At(e=null,t=!1){const n=!t;n&&ft(),!t&&E.Y.length&&On(E);const r=x._>=x.C;if(r&&Ne(x,Q.Ee),n){if(r&&ft(),_r(e?e.Z:E.Z),e&&e.se.size){for(const i of e.se){if(i.O&de)continue;if(i.J===ve){i.ee||(i.ee=!0,i.te.enqueue(G,i.ne));continue}const o=i.O&J?H:x;o.C>i.o&&(o.C=i.o),Je(i,o)}e.se.clear()}e?e.q:E.q,wr(),Sr(e)}}function On(e){for(const t of e.Y)t.checkSources?.(),On(t)}function sn(e){for(let t=0;t<e.length;t++)e[t].M=b}const E=new Q;function Te(e){if(e){Mt++;try{return e()}finally{Te(),Mt--}}if(!E.ce)for(;ze||b;)E.flush()}function dt(e,t){for(let n=0;n<e.length;n++)e[n](t)}function Cr(e,t){if(e.O&(J|de))return!1;if(e.Ce===t||e.Pe?.has(t))return!0;for(let n=e.P;n;n=n.D){let r=n.m;for(;r;){if(r===t||r.V===t)return!0;r=r.U}}return!!(e.he&O&&e.Re instanceof K&&e.Re.source===t)}function Or(e){if(e.ie)return!0;if(e.re.length)return!1;let t=!0;for(const[n,r]of e.oe){let i=!1;for(const o of r){if(Cr(o,n)){i=!0;break}r.delete(o)}if(!i)e.oe.delete(n);else if(n.he&O&&n.Re?.source===n){t=!1;break}}if(t)for(let n=0;n<e.Z.length;n++){const r=e.Z[n];if(wt(r)&&"he"in r&&r.he&O&&r.Re instanceof K&&r.Re.source!==r){t=!1;break}}return t&&(e.ie=!0),t}function Tn(e){for(;e.ie&&typeof e.ie=="object";)e=e.ie;return e}function Tr(e,t){const n=b;try{return b=Tn(e),t()}finally{b=n}}function An(e){let t=e.ge;for(;t;)t.O|=J,t.O&le&&(Pe(t,x),Je(t,H)),An(t),t=t.De}function He(e,t=!1,n){const r=e.O;if(r&de)return;t&&(e.O=r|de),t&&e.L&&(e.ve=null);let i=n?e.Ne:e.ge;for(;i;){const o=i.De;if(i.P){const s=i;Pe(s,s.O&J?H:x);let u=s.P;do u=Yt(u);while(u!==null);s.P=null,s.ye=null}He(i,!0),i=o}if(n?e.Ne=null:(e.ge=null,e.me=0),t&&!n&&!(r&J)&&e.i!==null&&!(e.i.O&de)){const o=e.we,s=e.De;o!==null?o.De=s:e.i.ge=s,s!==null&&(s.we=o),e.we=null}Ar(e,n)}function Ar(e,t){let n=t?e.Ae:e.be;if(n){if(Array.isArray(n))for(let r=0;r<n.length;r++){const i=n[r];i.call(i)}else n.call(n);t?e.Ae=null:e.be=null}}function Rr(e,t){let n=e;for(;n.Oe&yt&&n.i;)n=n.i;if(n.id!=null)return Pr(n.id,n.me++);throw new Error("Cannot get child id from owner without an id")}function Jt(e){return Rr(e)}function Pr(e,t){const n=t.toString(36),r=n.length-1;return e+(r?String.fromCharCode(64+r):"")+n}function Ye(){return $}function vt(e){return $&&($.be?Array.isArray($.be)?$.be.push(e):$.be=[$.be,e]:$.be=e),e}function kr(e=!0){He(this,e)}function Ke(e){const t=$,n=e?.transparent??!1,r={id:e?.id??(n?t?.id:t?.id!=null?Jt(t):void 0),Oe:n?yt:0,t:!0,u:t?.t?t.u:t,ge:null,De:null,we:null,be:null,te:t?.te??E,Ve:t?.Ve||zt,me:0,Ae:null,Ne:null,i:t,dispose:kr};if(t){const i=t.ge;i===null||(r.De=i,i.we=r),t.ge=r}return r}function Ht(e,t){const n=Ke(t);return se(n,()=>e(()=>n.dispose()))}function Yt(e){const t=e.m,n=e.D,r=e.p,i=e.Le;if(r!==null?r.Le=i:t.Ue=i,i!==null)i.p=r;else if(t.I=r,r===null){t.X?.();const o=t;o.L&&o.Oe&Re&&!(o.O&J)&&Pn(o)}return n}function Rn(e){const t=e.ye;let n=t!==null?t.D:e.P;if(n!==null){do n=Yt(n);while(n!==null);t!==null?t.D=null:e.P=null}}function Pn(e){Pe(e,e.O&J?H:x);let t=e.P;for(;t!==null;)t=Yt(t);e.P=null,e.ye=null,He(e,!0)}function Le(e,t){const n=t.ye;if(n!==null&&n.m===e)return;let r=null;const i=t.O&mt;if(i&&(r=n!==null?n.D:t.P,r!==null&&r.m===e)){t.ye=r;return}const o=e.Ue;if(o!==null&&o.h===t&&(!i||Ir(o,t)))return;const s=t.ye=e.Ue={m:e,h:t,D:r,Le:o,p:null};n!==null?n.D=s:t.P=s,o!==null?o.p=s:e.I=s}function Ir(e,t){const n=t.ye;if(n!==null){let r=t.P;do{if(r===e)return!0;if(r===n)break;r=r.D}while(r!==null)}return!1}function xr(e,t){return e.Ce===t||e.Pe?.has(t)?!1:e.Ce?(e.Pe?e.Pe.add(t):e.Pe=new Set([e.Ce,t]),e.Ce=void 0,!0):(e.Ce=t,!0)}function Lr(e,t){return e.Ce?e.Ce!==t?!1:(e.Ce=void 0,!0):e.Pe?.delete(t)?(e.Pe.size===1?(e.Ce=e.Pe.values().next().value,e.Pe=void 0):e.Pe.size===0&&(e.Pe=void 0),!0):!1}function kn(e){e.Ce=void 0,e.Pe?.clear(),e.Pe=void 0}function ht(e,t,n){if(!t){e.Re=null;return}if(n instanceof K&&n.source===t){e.Re=n;return}const r=e.Re;(!(r instanceof K)||r.source!==t)&&(e.Re=new K(t))}function Dt(e,t){for(let n=e.I;n!==null;n=n.p)t(n.h);for(let n=e.N;n!==null;n=n.A)for(let r=n.I;r!==null;r=r.p)t(r.h)}function Nr(e){let t=!1;const n=new Set,r=i=>{if(n.has(i)||!Lr(i,e))return;n.add(i),i.Ie=M;const o=i.Ce??i.Pe?.values().next().value;if(o)ht(i,o),be(i);else{if(i.he&=~O,ht(i),be(i),i.Ge){if(i.J===ve){const s=i;s.ee||(s.ee=!0,s.te.enqueue(G,s.ne))}else{const s=i.O&J?H:x;s.C>i.o&&(s.C=i.o),Je(i,s)}t=!0}i.Ge=!1}Dt(i,r)};Dt(e,r),t&&we()}function qr(e,t,n){let r=!1,i=!1;if(typeof t=="object"&&t!==null&&X(()=>{r=t[Symbol.asyncIterator],i=!r&&typeof t.then=="function"}),!i&&!r)return e.ve=null,t;e.ve=t;let o;const s=l=>{e.ve===t&&(E.initTransition(qe(e)),Zt(e,l instanceof K?O:U,l),e.Ie=M)},u=(l,f)=>{if(e.ve!==t||e.O&(Ce|me))return;E.initTransition(qe(e));const g=!!(e.he&D);Rn(e),In(e);const c=Ee(e);if(c&&c.F.delete(e),e.K!==void 0)e.K!==void 0&&e.K!==_?e.B=l:(e.ue=l,Oe(e)),e.Ie=M;else if(c){const d=e.J,p=e.ue,m=e.ke;(!d&&g||!m||!m(l,p))&&(e.ue=l,e.Ie=M,e.Fe&&ce(e.Fe,l),Oe(e,!0))}else ce(e,()=>l);Nr(e),we(),Te(),f?.()};if(i){let l=!1,f=!0;if(t.then(g=>{f?(o=g,l=!0):u(g)},g=>{f||s(g)}),f=!1,!l)throw E.initTransition(qe(e)),new K($)}if(r){const l=t[Symbol.asyncIterator]();let f=!1,g=!1;vt(()=>{if(!g){g=!0;try{const p=l.return?.();p&&typeof p.then=="function"&&p.then(void 0,()=>{})}catch{}}});const c=()=>{let p,m=!1,a=!0;return l.next().then(h=>{if(a)p=h,m=!0,h.done&&(g=!0);else{if(e.ve!==t)return;h.done?(g=!0,we(),Te()):u(h.value,c)}},h=>{!a&&e.ve===t&&(g=!0,s(h))}),a=!1,m&&!p.done?(o=p.value,f=!0,c()):m&&p.done},d=c();if(!f&&!d)throw E.initTransition(qe(e)),new K($)}return o}function In(e,t=!1){(e.Ce||e.Pe)&&kn(e),e.Ge&&(e.Ge=!1),e.he=t?0:e.he&D,e.Re&&ht(e),e.We&&be(e),e.xe&&e.xe()}function Zt(e,t,n,r,i){t===U&&!(n instanceof qt)&&!(n instanceof K)&&(n=new qt(e,n));const o=t===O&&n instanceof K?n.source:void 0,s=o===e,u=t===O&&e.K!==void 0&&!s,l=u&&wt(e);r||(t===O&&o?(xr(e,o),e.he=O|e.he&D,ht(e,o,n)):(kn(e),e.he=t|(t!==U?e.he&D:0),e.Re=n),be(e)),i&&!r&&Gt(e,i);const f=r||l,g=r||u?void 0:i;if(e.xe){if(r&&t===O)return;f?e.xe(t,n):e.xe();return}Dt(e,c=>{c.Ie=M,(t===O&&o&&c.Ce!==o&&!c.Pe?.has(o)||t!==O&&(c.Re!==n||c.Ce||c.Pe))&&(!f&&!c.M&&bt(c),Zt(c,t,n,f,g))})}let Mr=null;Q.Ee=ue;Q.Se=He;let B=!1,re=!1,$=null,z=null;function ue(e,t=!1){const n=e.J;t||(e.M&&(!n||b)&&b!==e.M&&E.initTransition(e.M),Pe(e,e.O&J?H:x),e.ve=null,e.M||n===ve?He(e):(e.ge!==null||e.be!==null)&&(An(e),e.Ae=e.be,e.Ne=e.ge,e.be=null,e.ge=null,e.me=0));let r=!!(e.O&me);const i=e.K!==void 0&&e.K!==_,o=!!(e.he&O),s=!!(e.he&D),u=$;$=e,e.ye=null,e.O=mt,e.Ie=M;let l=e.B===_?e.ue:e.B,f=e.o,g=B,c=z;if(B=!0,r){const a=Ee(e);a&&(z=a)}else if(b&&!t&&b.Z.length)for(let a=e.P;a;a=a.D){const h=a.m;if(h.O&me){const y=Ee(h);if(y){r=!0,z=y,e.O|=me,Gt(e,y);break}}}const d=n&&n!==G,p=re;d&&(re=!0);try{if(e.Oe&Vt)l=e.L(l),e.ve=null;else{const a=e.ve,h=e.L(l),y=typeof h=="object"&&h!==null,w=e.ve!==a;l=w||!y?h:qr(e,h),!w&&!y&&(e.ve=null)}if(In(e,t),e.G){const a=Ee(e);a&&(a.F.delete(e),be(a.k))}}catch(a){if(a instanceof K&&z){const h=W(z);h.k!==e&&(h.F.add(e),e.G=h,be(h.k))}a instanceof K&&(e.Ge=!0),Zt(e,a instanceof K?O:U,a,void 0,a instanceof K?e.G:void 0)}finally{B=g,d&&(re=p),e.O=$n|(t?e.O&Wt:0),$=u}if(!e.Re){Rn(e);const a=i?e.K:e.B===_?e.ue:e.B,h=!n&&s||!e.ke||!e.ke(a,l);if(n&&h&&(e.ee=!e.Re,t||e.te.enqueue(n,Q.Te.bind(null,e))),h){const y=i?e.K:void 0;t||n&&b!==e.M||r?(e.ue=l,i&&r&&(e.K=l,e.B=l)):e.B=l,i&&!r&&o&&!e.$&&(e.K=l),(!i||r||e.K!==y)&&Oe(e,r||i)}else if(i)e.B=l;else if(e.o!=f)for(let y=e.I;y!==null;y=y.p)En(y.h,y.h.O&J?H:x)}z=c,(e.B!==_||e.Ne!==null||e.Ae!==null||!!(e.he&(O|D)))&&(!t||e.he&O)&&!e.M&&!(b&&i)&&bt(e),e.M&&n&&b!==e.M&&Tr(e.M,()=>ue(e))}function xn(e){if(e.O&je)for(let t=e.P;t;t=t.D){const n=t.m,r=n.V||n;if(r.L&&xn(r),e.O&Ce)break}(e.O&(Ce|me)||e.Re&&e.Ie<M&&!e.ve)&&ue(e),e.O=e.O&(Wt|le|We)}function $t(e,t){const n=t?.transparent??!1,r={id:t?.id??(n?$?.id:$?.id!=null?Jt($):void 0),Oe:(n?yt:0)|(t?.ownedWrite?pt:0)|(!$||t?.lazy?Re:0)|(t?.sync?Vt:0)|0,ke:t?.equals!=null?t.equals:Nn,X:t?.unobserved,be:null,te:$?.te??E,Ve:$?.Ve??zt,me:0,L:e,ue:void 0,o:0,N:null,T:void 0,S:null,P:null,ye:null,I:null,Ue:null,i:$,De:null,we:null,ge:null,O:t?.lazy?ut:$n,he:D,Ie:M,B:_,Ae:null,Ne:null,ve:null,M:null};return Ln(r,t),r}function Dr(e,t,n,r,i,o){const s=o?.transparent??!1,u={id:o?.id??(s?$?.id:$?.id!=null?Jt($):void 0),Oe:(s?yt:0)|(o?.ownedWrite?pt:0)|(o?.sync?Vt:0)|0,ke:!1,X:o?.unobserved,be:null,te:$?.te??E,Ve:$?.Ve??zt,me:0,L:e,ue:void 0,o:0,N:null,T:void 0,S:null,P:null,ye:null,I:null,Ue:null,i:$,De:null,we:null,ge:null,O:ut,he:D,Ie:M,B:_,Ae:null,Ne:null,ve:null,M:null,ee:!1,Me:void 0,Qe:t,je:n,$e:void 0,Ke:!1,J:r,xe:i};return Ln(u,Fr),u}const Fr={lazy:!0};function Ln(e,t){e.S=e;const n=$?.t?$.u:$;if($){const r=$.ge;r===null||(e.De=r,r.we=e),$.ge=e}n&&(e.o=n.o+1),!t?.lazy&&ue(e,!0)}function Fe(e,t,n=null){const r={ke:t?.equals!=null?t.equals:Nn,Oe:(t?.ownedWrite?pt:0)|(t?.Ye?ar:0),X:t?.unobserved,ue:e,I:null,Ue:null,Ie:M,V:n,A:n?.N||null,B:_};return n&&(n.N=r),r}function Nn(e,t){return e===t}function X(e,t){if(!B)return e();const n=B;B=!1;try{return e()}finally{B=n}}function qn(e){let t=$;t?.t&&(t=t.u);const n=e;if(typeof n.L=="function"){const o=e;o.O&ut?(o.O&=~ut,ue(o,!0)):o.O&de&&ue(o,!0)}const r=e.V||e;if(!n.L&&r===e&&e.K===void 0&&e.pe===void 0&&b===null&&z===null)return t&&B&&Le(e,t),!t||e.B===_?e.ue:e.B;if(t&&B&&(Le(e,t),r.L)){const o=e.O&J;r.o>=(o?H.C:x.C)&&(ct(t),gr(o?H:x),xn(r));const s=r.o;s>=t.o&&e.i!==t&&(t.o=s+1)}if(r.he&O)if(t&&!(re&&r.M&&b!==r.M))if(z){const o=r.G,s=W(z);if(o&&W(o)===s&&!wt(r))throw!B&&e!==t&&Le(e,t),r.Re}else throw!B&&e!==t&&Le(e,t),r.Re;else{if(t&&r!==e&&r.he&D)throw!B&&e!==t&&Le(e,t),r.Re;if(!t&&r.he&D)throw r.Re}if(e.L&&e.he&U){if(e.Ie<M)return ue(e),qn(e);throw e.Re}if(e.K!==void 0&&e.K!==_)return t&&re&&br(e)?e.ue:e.K;if(b!==null&&z!==null&&e.B!==_&&r===e&&!e.L&&t)return b.se.add(t),e.ue;const i=!t||z!==null&&(e.K!==void 0||e.G||r===e&&re||r.he&O)||e.B===_||re&&e.M&&b!==e.M?e.ue:e.B;return!t&&r===e&&typeof n.L=="function"&&e.Oe&Re&&!(r.he&O)&&!e.I&&Pn(e),i}function ce(e,t){e.M&&b!==e.M&&E.initTransition(e.M);const n=e.K!==void 0&&!0,r=e.K!==void 0&&e.K!==_,i=n?r?e.K:e.ue:e.B===_?e.ue:e.B;if(typeof t=="function"&&(t=t(i)),!(!e.ke||!e.ke(i,t)||!!(e.he&D)))return n&&r&&e.L&&(Oe(e,!0),we()),t;if(n){const s=e.K===_;s||E.initTransition(qe(e)),s&&(e.B=e.ue,E.Z.push(e)),e.$=!0;const u=pr(e);e.G=u,e.K=t}else e.B===_&&bt(e),e.B=t;return e.We&&be(e),e.Fe&&ce(e.Fe,t),e.Ie=M,Oe(e,n),we(),t}function Br(e){Pe(e,e.O&J?H:x),!(e.O&Ve)&&e.B===_&&bt(e),e.O=e.O&-4|Ve}function jr(e,t){const n=ce(e,t);return Br(e),n}function se(e,t){const n=$,r=B;$=e,B=!1;try{return t()}finally{$=n,B=r}}function Kr(e){const t=e,n=e.V;if(e.U){const r=e.U;return r.he&O&&!(r.he&D)?!0:e.B!==_&&!(t.he&D)}if(n&&e.B!==_)return!n.ve&&!(n.he&O);if(e.K!==void 0&&e.K!==_){if(t.he&O&&!(t.he&D))return!0;if(e.U){const r=e.G?W(e.G):null;return!!(r&&r.F.size>0)}return!0}return e.K!==void 0&&e.K===_&&!e.U?!1:e.B!==_&&!(t.he&D)?!0:!!(t.he&O&&!(t.he&D))}function be(e){if(e.We){const t=Kr(e),n=e.We;if(ce(n,t),!t&&n.G){const r=Ee(e);if(r&&r.F.size>0){const i=W(n.G);i!==r&&Cn(r,i)}at.delete(n),n.G=void 0}}}function Ur(e,t=!0){const n=re;re=t;try{return e()}finally{re=n}}function Wr(e,t=Ye()){if(!t)throw new vn;const n=zr(e,t)?t.Ve[e.id]:e.defaultValue;if(Qt(n))throw new cr;return n}function Vr(e,t,n=Ye()){if(!n)throw new vn;n.Ve={...n.Ve,[e.id]:Qt(t)?e.defaultValue:t}}function zr(e,t){return!Qt(t?.Ve[e.id])}function Qt(e){return typeof e>"u"}function Mn(e,t,n,r){const i=!!r?.user,o=Dr(e,t,n,i?G:Se,Gr,r);ue(o,!0),!r?.defer&&(o.J===G||r?.schedule?o.te.enqueue(o.J,Ft.bind(null,o)):Ft(o))}function Gr(e,t){const n=e!==void 0?e:this.he,r=t!==void 0?t:this.Re;if(n&U){let i=r;if(this.te.notify(this,O,0),this.J===G)try{return this.je?this.je(i,()=>{this.$e?.(),this.$e=void 0}):console.error(i)}catch(o){i=o}if(!this.te.notify(this,U,U))throw i}else this.J===Se&&this.te.notify(this,O|U,n,r)}function Ft(e){if(!(!e.ee||e.O&de)){e.$e?.(),e.$e=void 0;try{const t=e.Qe(e.ue,e.Me);e.$e=t,e.$e&&!e.Ke&&(e.Ke=!0,se(e.i,()=>vt(()=>e.$e?.())))}catch(t){if(e.Re=new qt(e,t),e.he|=U,!e.te.notify(e,U,U))throw t}finally{e.Me=e.ue,e.ee=!1}}}Q.Te=Ft;function Jr(e,t){const n=()=>{if(!(!r.ee||r.O&de))try{r.ee=!1,ue(r)}finally{}},r=$t(()=>{r.$e?.(),r.$e=void 0;const i=Ur(e);r.$e=i},{...t,lazy:!0});r.$e=void 0,r.Oe=r.Oe&~Re|_n,r.ee=!0,r.J=ve,r.xe=(i,o)=>{if((i!==void 0?i:r.he)&U){r.te.notify(r,O,0);const u=o!==void 0?o:r.Re;if(!r.te.notify(r,U,U))throw u}},r.ne=n,r.te.enqueue(G,n),vt(()=>r.$e?.())}function Dn(e){return vt(e)}function fe(e){const t=qn.bind(null,e);return t[hr]=e,t}function Hr(e,t){if(typeof e=="function"){const r=$t(e,t);return r.Oe&=~Re,[fe(r),jr.bind(null,r)]}const n=Fe(e,t);return[fe(n),ce.bind(null,n)]}function pe(e,t){return fe($t(e,t))}function Yr(e,t,n){Mn(e,t.effect||t,t.error,{user:!0,...n})}function Zr(e,t,n){Mn(e,t,void 0,n)}function Qr(e,t){Jr(e,t)}function Xr(e){const t=Ye();t&&!(t.Oe&_n)?Qr(()=>X(e),void 0):E.enqueue(G,()=>{e()?.()})}const ei=Symbol(0),Bt=Symbol(0);function ln(e){return e==null||typeof e!="object"||Object.isFrozen(e)?!1:typeof Node>"u"||!(e instanceof Node)}const Fn=Symbol(0);function un(e){return e==="__proto__"||e==="constructor"||e==="prototype"}function Be(e,t,n=0){let r,i=e;if(n<t.length-1){r=t[n];const s=typeof r,u=Array.isArray(e);if(s==="string"&&un(r))return;if(Array.isArray(r)){for(let l=0;l<r.length;l++)t[n]=r[l],Be(e,t,n);t[n]=r;return}else if(u&&s==="function"){for(let l=0;l<e.length;l++)r(e[l],l)&&(t[n]=l,Be(e,t,n));t[n]=r;return}else if(u&&s==="object"){const{from:l=0,to:f=e.length-1,by:g=1}=r;for(let c=l;c<=f;c+=g)t[n]=c,Be(e,t,n);t[n]=r;return}else if(n<t.length-2){Be(e[r],t,n+1);return}i=e[r]}let o=t[t.length-1];if(!(typeof o=="function"&&(o=o(i),o===i))&&!(r===void 0&&o==null))if(o===Fn)delete e[r];else if(r===void 0||ln(i)&&ln(o)&&!Array.isArray(o)){const s=r!==void 0?e[r]:e,u=Object.keys(o);for(let l=0;l<u.length;l++){const f=u[l];if(un(f))continue;const g=Object.getOwnPropertyDescriptor(o,f);g.get||g.set?Object.defineProperty(s,f,g):s[f]=g.value}}else e[r]=o}Object.assign(function(...t){return n=>{Be(n,t)}},{DELETE:Fn});function tt(){return!0}const ti={get(e,t,n){return t===Bt?n:e.get(t)},has(e,t){return t===Bt?!0:e.has(t)},set:tt,deleteProperty:tt,getOwnPropertyDescriptor(e,t){return{configurable:!0,enumerable:!0,get(){return e.get(t)},set:tt,deleteProperty:tt}},ownKeys(e){return e.keys()}};function Rt(e){return(e=typeof e=="function"?e():e)?e:{}}const Pt=Symbol(0);function ni(...e){if(e.length===1&&typeof e[0]!="function")return e[0];let t=!1;const n=[];for(let l=0;l<e.length;l++){const f=e[l];t=t||!!f&&Bt in f;const g=!!f&&f[Pt];if(g)for(let c=0;c<g.length;c++)n.push(g[c]);else n.push(typeof f=="function"?(t=!0,pe(f)):f)}if(dr&&t)return new Proxy({get(l){if(l===Pt)return n;for(let f=n.length-1;f>=0;f--){const g=Rt(n[f]);if(l in g)return g[l]}},has(l){for(let f=n.length-1;f>=0;f--)if(l in Rt(n[f]))return!0;return!1},keys(){const l=new Set;for(let f=0;f<n.length;f++){const g=Object.keys(Rt(n[f]));for(let c=0;c<g.length;c++)l.add(g[c])}return[...l]}},ti);const r=Object.create(null);let i=!1,o=n.length-1;for(let l=o;l>=0;l--){const f=n[l];if(!f){l===o&&o--;continue}const g=Object.getOwnPropertyNames(f);for(let c=g.length-1;c>=0;c--){const d=g[c];if(!(d==="__proto__"||d==="constructor")&&!r[d]){i=i||l!==o;const p=Object.getOwnPropertyDescriptor(f,d);r[d]=p.get?{enumerable:!0,configurable:!0,get:p.get.bind(f)}:p}}}if(!i)return n[o];const s={},u=Object.keys(r);for(let l=u.length-1;l>=0;l--){const f=u[l],g=r[f];g.get?Object.defineProperty(s,f,g):s[f]=g.value}return s[Pt]=n,s}function ri(e,t,n){const r=typeof n?.keyed=="function"?n.keyed:void 0,i=t.length>1,o=t,s={Ze:Ke(),qe:0,Be:e,ze:[],Xe:o,Je:[],et:[],tt:r,nt:r||n?.keyed===!1?[]:void 0,it:i&&n?.keyed!==!1?[]:void 0,rt:n?.keyed===!1,ot:n?.fallback},u=$t(ii.bind(s));return s.Ze.u=u,u.Oe&=~Re,fe(u)}const nt={ownedWrite:!0};function ii(){const e=this.Be()||[],t=e.length;return e[ei],se(this.Ze,()=>{let n,r,i=this.nt?this.rt?()=>(this.nt[r]=Fe(e[r],nt),this.Xe(fe(this.nt[r]),r)):()=>(this.nt[r]=Fe(e[r],nt),this.it&&(this.it[r]=Fe(r,nt)),this.Xe(fe(this.nt[r]),this.it?fe(this.it[r]):void 0)):this.it?()=>{const o=e[r];return this.it[r]=Fe(r,nt),this.Xe(o,fe(this.it[r]))}:()=>{const o=e[r];return this.Xe(o)};if(t===0)this.qe!==0&&(this.Ze.dispose(!1),this.et=[],this.ze=[],this.Je=[],this.qe=0,this.nt&&(this.nt=[]),this.it&&(this.it=[])),this.ot&&!this.Je[0]&&(this.Je[0]=se(this.et[0]=Ke(),this.ot));else if(this.qe===0){for(this.et[0]&&this.et[0].dispose(),this.Je=new Array(t),r=0;r<t;r++)this.ze[r]=e[r],this.Je[r]=se(this.et[r]=Ke(),i);this.qe=t}else{let o,s,u,l,f,g,c,d=new Array(t),p=new Array(t),m=this.nt?new Array(t):void 0,a=this.it?new Array(t):void 0;for(o=0,s=Math.min(this.qe,t);o<s&&(this.ze[o]===e[o]||this.nt&&cn(this.tt,this.ze[o],e[o]));o++)this.nt&&ce(this.nt[o],e[o]);for(s=this.qe-1,u=t-1;s>=o&&u>=o&&(this.ze[s]===e[u]||this.nt&&cn(this.tt,this.ze[s],e[u]));s--,u--)d[u]=this.Je[s],p[u]=this.et[s],m&&(m[u]=this.nt[s]),a&&(a[u]=this.it[s]);for(g=new Map,c=new Array(u+1),r=u;r>=o;r--)l=e[r],f=this.tt?this.tt(l):l,n=g.get(f),c[r]=n===void 0?-1:n,g.set(f,r);for(n=o;n<=s;n++)l=this.ze[n],f=this.tt?this.tt(l):l,r=g.get(f),r!==void 0&&r!==-1?(d[r]=this.Je[n],p[r]=this.et[n],m&&(m[r]=this.nt[n]),a&&(a[r]=this.it[n]),r=c[r],g.set(f,r)):this.et[n].dispose();for(r=o;r<t;r++)r in d?(this.Je[r]=d[r],this.et[r]=p[r],m&&(this.nt[r]=m[r],ce(this.nt[r],e[r])),a&&(this.it[r]=a[r],ce(this.it[r],r))):this.Je[r]=se(this.et[r]=Ke(),i);this.Je=this.Je.slice(0,this.qe=t),this.ze=e.slice(0)}}),this.Je}function cn(e,t,n){return e?e(t)===e(n):!0}function Xt(e,t){if(typeof e=="function"&&!e.length){if(t?.doNotUnwrap)return e;do e=e();while(typeof e=="function"&&!e.length)}if(!(t?.skipNonRendered&&(e==null||e===!0||e===!1||e===""))){if(Array.isArray(e)){let n=[];return jt(e,n,t)?()=>{let r=[];return jt(n,r,{...t,doNotUnwrap:!1}),r}:n}return e}}function jt(e,t=[],n){let r=null,i=!1;for(let o=0;o<e.length;o++)try{let s=e[o];if(typeof s=="function"&&!s.length){if(n?.doNotUnwrap){t.push(s),i=!0;continue}do s=s();while(typeof s=="function"&&!s.length)}Array.isArray(s)?i=jt(s,t,n):n?.skipNonRendered&&(s==null||s===!0||s===!1||s==="")||t.push(s)}catch(s){if(!(s instanceof K))throw s;r=s}if(r)throw r;return i}function Bn(e,t){const n=Symbol("");function r(i){return Ht(()=>(Vr(r,i.value),en(()=>i.children)))}return r.id=n,r.defaultValue=e,r}function jn(e){return Wr(e)}function en(e){const t=pe(e,{lazy:!0}),n=pe(()=>Xt(t()),{lazy:!0,sync:!0});return n.toArray=()=>{const r=n();return Array.isArray(r)?r:r!=null?[r]:[]},n}class _e{static{for(const t of["all","allSettled","any","race","reject","resolve"])_e[t]=()=>new _e}catch(){return new _e}then(){return new _e}finally(){return new _e}}const Z=(...e)=>pe(...e),q=(...e)=>Hr(...e),oi=(...e)=>Zr(...e),_t=(...e)=>Yr(...e);function L(e,t){return X(()=>e(t||{}))}const si=e=>`Stale read from <${e}>.`;function st(e){const t="fallback"in e?{keyed:e.keyed,fallback:()=>e.fallback}:{keyed:e.keyed};return ri(()=>e.each,e.children,t)}function Kn(e){const t=e.keyed,n=pe(()=>e.when,void 0),r=t?n:pe(n,{equals:(i,o)=>!i==!o,sync:!0});return pe(()=>{const i=r();if(i){const o=e.children;return typeof o=="function"&&o.length>0?X(t?()=>o(i):()=>o(()=>{if(!X(r))throw si("Show");return n()})):o}return e.fallback},{sync:!0})}const F=Symbol("slot"),li={transparent:!0,sync:!0},ui={sync:!0},ee=(e,t,n)=>oi(e,t,n?{transparent:!0,sync:!0,...n}:li),j=e=>Z(()=>e(),ui);function ci(e,t,n,r){let i=n.length,o=t.length,s=i,u=0,l=0,f=t[o-1],g=f[F],c=f.parentNode===e&&(!g||g===r)?f.nextSibling:r||null,d=null,p,m;for(;u<o||l<s;){if(t[u]===n[l]){u++,l++;continue}for(;t[o-1]===n[s-1];)o--,s--;if(o===u){let a;if(s<i)if(l){const h=n[l-1],y=h[F];a=h.parentNode===e&&(!y||y===r)?h.nextSibling:c}else a=n[s-l];else a=c;for(;l<s;){const h=n[l++];e.insertBefore(h,a),r&&(h[F]=r)}}else if(s===l)for(;u<o;){const a=t[u++];if(!d||!d.has(a)){const h=a[F];a.parentNode===e&&(!h||h===r)&&a.remove()}}else if((p=t[u])===n[s-1]&&n[l]===t[o-1]&&p.parentNode===e&&(!(m=p[F])||m===r))if(r)do{const a=t[--o];if(e.insertBefore(a,p),a[F]=r,l++,u>=o-1||l>=s)break}while(t[u]===n[s-1]&&n[l]===t[o-1]);else do if(e.insertBefore(t[--o],p),l++,u>=o-1||l>=s)break;while(t[u]===n[s-1]&&n[l]===t[o-1]);else{if(!d){d=new Map;let h=l;for(;h<s;)d.set(n[h],h++)}const a=d.get(t[u]);if(a!=null)if(l<a&&a<s){let h=u,y=1,w;for(;++h<o&&h<s&&!((w=d.get(t[h]))==null||w!==a+y);)y++;if(y>a-l){const T=t[u],I=T[F],N=T.parentNode===e&&(!I||I===r)?T:c;for(;l<a;){const Y=n[l++];e.insertBefore(Y,N),r&&(Y[F]=r)}}else{const T=t[u++],I=n[l++],N=T[F];T.parentNode===e&&(!N||N===r)?e.replaceChild(I,T):e.insertBefore(I,c),r&&(I[F]=r)}}else u++;else{const h=t[u++],y=h[F];h.parentNode===e&&(!y||y===r)&&h.remove()}}}}const an="_$DX_EVENT_OWNER",fn={},Kt=new Set,Ae=new Map;function ai(e,t,n,r={}){let i;di(t);try{Ht(o=>{if(i=o,t===document){const s=e();ee(()=>Xt(s),()=>{})}else{const s=e();v(t,()=>s,t.firstChild?null:void 0,n,r.insertOptions)}},{id:r.renderId})}catch(o){throw i&&i(),dn(t),o}return()=>{i(),dn(t),t.textContent=""}}function fi(e,t,n){const r=document.createElement("template");return r.innerHTML=e,n===2?r.content.firstChild.firstChild:r.content.firstChild}function A(e,t){let n;return i=>(n||(n=fi(e,i,t))).cloneNode(!0)}function Ze(e){for(let t=0,n=e.length;t<n;t++){const r=e[t];Kt.has(r)||(Kt.add(r),Ae.forEach((i,o)=>Un(r,o,i)))}}function di(e){const t=hi(e,e);t&&(t.roots=(t.roots||0)+1)}function dn(e){const t=Ae.get(e);t&&(t.roots>1?t.roots--:delete t.roots),gi(e,e)}function hi(e,t=e){if(!e||!t)return;let n=Ae.get(e);return n||Ae.set(e,n={owners:new Map,handlers:new Map}),n.owners.set(t,(n.owners.get(t)||0)+1),Kt.forEach(r=>Un(r,e,n)),n}function gi(e,t=e){const n=Ae.get(e);if(!n)return;const r=n.owners.get(t);r>1?n.owners.set(t,r-1):n.owners.delete(t),!n.owners.size&&(n.handlers.forEach((i,o)=>e.removeEventListener(o,i)),Ae.delete(e))}function Un(e,t,n){if(n.handlers.has(e))return;const r=i=>yi(i,t,n);n.handlers.set(e,r),t.addEventListener(e,r)}function mi(e,t){let n=e,r=0;for(;n;){if(t.owners.has(n))return{owner:n,distance:r};r++,n=n._$host||n.parentNode||n.host}}function ye(e,t,n){n==null||n===!1?e.removeAttribute(t):e.setAttribute(t,n===!0?"":n)}function Ge(e,t,n){if(t==null||t===!1){n&&e.removeAttribute("class");return}if(typeof t=="string"){t!==n&&e.setAttribute("class",t);return}typeof n=="string"?(n={},e.removeAttribute("class")):n=gn(n||{}),t=gn(t);const r=Object.keys(t||{}),i=Object.keys(n);let o,s;for(o=0,s=i.length;o<s;o++){const u=i[o];!u||u==="undefined"||t[u]||e.classList.remove(u)}for(o=0,s=r.length;o<s;o++){const u=r[o],l=!!t[u];!u||u==="undefined"||n[u]===l||!l||e.classList.add(u)}}function hn(e,t,n,r){if(Array.isArray(n)){const i=n[0];e.addEventListener(t,n[0]=o=>i.call(e,n[1],o))}else e.addEventListener(t,n,typeof n!="function"&&n)}function pi(e,t){Array.isArray(e)?e.flat(1/0).forEach(n=>n&&n(t)):e(t)}function gt(e,t){const n=X(e);se(null,()=>pi(n,t))}function v(e,t,n,r,i){const o=n!==void 0;if(o&&!r&&(r=[]),typeof t!="function"&&(t=It(t,r,o,!0),typeof t!="function"))return kt(e,t,r,n);if(o&&r.length===0){const u=document.createTextNode("");e.insertBefore(u,n),r=[u]}let s=r;ee(u=>{const l=It(t(),s,o,!0);return typeof l!="function"?l:(ee(()=>It(l,s,o),f=>{kt(e,f,s,n),s=f},u!==void 0&&!(i&&i.schedule)?{...i,schedule:!0}:i),fn)},u=>{u!==fn&&(kt(e,u,s,n),s=u)},i)}function gn(e){if(Array.isArray(e)){const t={};Wn(e,t),e=t}if(e&&typeof e=="object"){const t={},n=Object.keys(e);for(let r=0,i=n.length;r<i;r++){const o=n[r];if(!e[o])continue;const s=o.trim().split(/\s+/);for(let u=0,l=s.length;u<l;u++)s[u]&&(t[s[u]]=!0)}return t}return e}function Wn(e,t){for(let n=0,r=e.length;n<r;n++){const i=e[n];Array.isArray(i)?Wn(i,t):typeof i=="object"&&i!=null?Object.assign(t,i):(i||i===0)&&(t[i]=!0)}}function yi(e,t,n){if(e[an])return;const r=n&&(n.owners.size===1&&n.owners.has(t)?t:mi(e.target,n)?.owner);if(n&&!r)return;e[an]=r||!0;let i=e.target;const o=`$$${e.type}`,s=e.target,u=r||t||e.currentTarget,l=c=>Object.defineProperty(e,"target",{configurable:!0,value:c}),f=()=>{const c=i[o];if(c&&!i.disabled){const d=i[`${o}Data`];if(d!==void 0?c.call(i,d,e):c.call(i,e),e.cancelBubble)return}return i.host&&typeof i.host!="string"&&!i.host._$host&&i.contains(e.target)&&l(i.host),!0},g=()=>{for(;f()&&!(i===u||i.parentNode===u);)i=i._$host||i.parentNode||i.host};if(Object.defineProperty(e,"currentTarget",{configurable:!0,get(){return i||u||document}}),e.composedPath){const c=e.composedPath();if(c.length){l(c[0]);for(let d=0;d<c.length&&(i=c[d],!!f());d++){if(i._$host){i=i._$host,g();break}if(i===u||i.parentNode===u)break}}else g()}else g();l(s)}function kt(e,t,n,r){if(t===n)return;const i=typeof t,o=r!==void 0;if(i==="string"||i==="number"){const s=typeof n;s==="string"||s==="number"?e.firstChild.data=t:e.textContent=t}else if(t===void 0)rt(e,n,r);else if(t.nodeType)Array.isArray(n)?rt(e,n,o?r:null,t):n&&n.nodeType?n.parentNode===e?e.replaceChild(t,n):e.appendChild(t):n&&e.firstChild?e.replaceChild(t,e.firstChild):e.appendChild(t),r&&(t[F]=r);else if(Array.isArray(t)){const s=n&&Array.isArray(n);t.length===0?rt(e,n,r):s?n.length===0?mn(e,t,r):ci(e,n,t,r):(n&&rt(e),mn(e,t))}}function It(e,t,n,r){if(e=Xt(e,{skipNonRendered:!0,doNotUnwrap:r}),r&&typeof e=="function")return e;if(n&&!Array.isArray(e)&&(e=[e??""]),Array.isArray(e))for(let i=0,o=e.length;i<o;i++){const s=e[i],u=t&&t[i],l=typeof s;(l==="string"||l==="number")&&(e[i]=u&&u.nodeType===3&&u.data===""+s?u:document.createTextNode(s))}return e}function mn(e,t,n=null){for(let r=0,i=t.length;r<i;r++){const o=t[r];e.insertBefore(o,n),n&&(o[F]=n)}}function rt(e,t,n,r){if(n===void 0)return e.textContent="";if(t.length){let i=!1;for(let o=t.length-1;o>=0;o--){const s=t[o];if(r!==s){const u=s[F],l=s.parentNode===e&&(!u||u===n);r&&!i&&!o?l?e.replaceChild(r,s):e.insertBefore(r,n):l&&s.remove()}else i=!0}}else r&&e.insertBefore(r,n);r&&n&&(r[F]=n)}const wi=!1;function bi(e,t,n,r={}){try{const i=ai(e,t,n,{...r,insertOptions:{schedule:!0}});return Te(),i}finally{}}function Vn(){let e=new Set;function t(i){return e.add(i),()=>e.delete(i)}let n=!1;function r(i,o){if(n)return!(n=!1);const s={to:i,options:o,defaultPrevented:!1,preventDefault:()=>s.defaultPrevented=!0};for(const u of e)u.listener({...s,from:u.location,retry:l=>{l&&(n=!0),u.navigate(i,{...o,resolve:!1})}});return!s.defaultPrevented}return{subscribe:t,confirm:r}}let Ut;function tn(){(!window.history.state||window.history.state._depth==null)&&window.history.replaceState({...window.history.state,_depth:window.history.length-1},""),Ut=window.history.state._depth}tn();function vi(e){return{...e,_depth:window.history.state&&window.history.state._depth}}function $i(e,t){let n=!1;return()=>{const r=Ut;tn();const i=r==null?null:Ut-r;if(n){n=!1;return}i&&t(i)?(n=!0,window.history.go(-i)):e()}}const _i=/^(?:[a-z0-9]+:)?\/\//i,Si=/^\/+|(\/)\/+$/g,zn="http://sr";function Ue(e,t=!1){const n=e.replace(Si,"$1");return n?t||/^[?#]/.test(n)?n:"/"+n:""}function lt(e,t,n){if(_i.test(t))return;const r=Ue(e),i=n&&Ue(n);let o="";return!i||t.startsWith("/")?o=r:i.toLowerCase().indexOf(r.toLowerCase())!==0?o=r+i:o=i,(o||"/")+Ue(t,!o)}function Ei(e,t){if(e==null)throw new Error(t);return e}function Ci(e,t){return Ue(e).replace(/\/*(\*.*)?$/g,"")+Ue(t)}function Gn(e){const t={};return e.searchParams.forEach((n,r)=>{r in t?Array.isArray(t[r])?t[r].push(n):t[r]=[t[r],n]:t[r]=n}),t}function Oi(e,t,n){const[r,i]=e.split("/*",2),o=r.split("/").filter(Boolean),s=o.length;return u=>{const l=u.split("/").filter(Boolean),f=l.length-s;if(f<0||f>0&&i===void 0&&!t)return null;const g={path:s?"":"/",params:{}},c=d=>n===void 0?void 0:n[d];for(let d=0;d<s;d++){const p=o[d],m=p[0]===":",a=m?l[d]:l[d].toLowerCase(),h=m?p.slice(1):p.toLowerCase();if(m&&xt(a,c(h)))g.params[h]=a;else if(m||!xt(a,h))return null;g.path+=`/${a}`}if(i){const d=f?l.slice(-f).join("/"):"";if(xt(d,c(i)))g.params[i]=d;else return null}return g}}function xt(e,t){const n=r=>r===e;return t===void 0?!0:typeof t=="string"?n(t):typeof t=="function"?t(e):Array.isArray(t)?t.some(n):t instanceof RegExp?t.test(e):!1}function Ti(e){const[t,n]=e.pattern.split("/*",2),r=t.split("/").filter(Boolean);return r.reduce((i,o)=>i+(o.startsWith(":")?2:3),r.length-(n===void 0?0:1))}function Jn(e){const t=new Map,n=Ye();return new Proxy({},{get(r,i){return t.has(i)||se(n,()=>t.set(i,Z(()=>e()[i]))),t.get(i)()},getOwnPropertyDescriptor(){return{enumerable:!0,configurable:!0}},ownKeys(){return Reflect.ownKeys(e())},has(r,i){return i in e()}})}function Hn(e){let t=/(\/?\:[^\/]+)\?/.exec(e);if(!t)return[e];let n=e.slice(0,t.index),r=e.slice(t.index+t[0].length);const i=[n,n+=t[1]];for(;t=/^(\/\:[^\/]+)\?/.exec(r);)i.push(n+=t[1]),r=r.slice(t[0].length);return Hn(r).reduce((o,s)=>[...o,...i.map(u=>u+s)],[])}const Ai=100,Yn=Bn(),Zn=Bn();function Ri(e){try{return jn(e)}catch{return}}const Qn=()=>Ei(jn(Yn),"<A> and 'use' router primitives can be only used inside a Route."),Xn=()=>Qn().navigatorFactory(),Pi=()=>Qn().params;function ki(e,t=""){const{component:n,preload:r,children:i,info:o}=e,s=!i||Array.isArray(i)&&!i.length,u={key:e,component:n,preload:r,info:o};return er(e.path).reduce((l,f)=>{for(const g of Hn(f)){const c=Ci(t,g);let d=s?c:c.split("/*",1)[0];d=d.split("/").map(p=>p.startsWith(":")||p.startsWith("*")?p:encodeURIComponent(p)).join("/"),l.push({...u,originalPath:f,pattern:d,matcher:Oi(d,!s,e.matchFilters)})}return l},[])}function Ii(e,t=0){return{routes:e,score:Ti(e[e.length-1])*1e4-t,matcher(n){const r=[];for(let i=e.length-1;i>=0;i--){const o=e[i],s=o.matcher(n);if(!s)return null;r.unshift({...s,route:o})}return r}}}function er(e){return Array.isArray(e)?e:[e]}function tr(e,t="",n=[],r=[]){const i=er(e);for(let o=0,s=i.length;o<s;o++){const u=i[o];if(u&&typeof u=="object"){u.hasOwnProperty("path")||(u.path="");const l=ki(u,t);for(const f of l){n.push(f);const g=Array.isArray(u.children)&&u.children.length===0;if(u.children&&!g)tr(u.children,f.pattern,n,r);else{const c=Ii([...n],r.length);r.push(c)}n.pop()}}}return n.length?r:r.sort((o,s)=>s.score-o.score)}function Lt(e,t){for(let n=0,r=e.length;n<r;n++){const i=e[n].matcher(t);if(i)return i}return[]}function xi(e,t,n){const r=new URL(zn),i=Z((g=r)=>{const c=e();try{return new URL(c,r)}catch{return console.error(`Invalid path ${c}`),g}},{equals:(g,c)=>g.href===c.href}),o=Z(()=>i().pathname),s=Z(()=>i().search),u=Z(()=>i().hash),l=()=>"",f=Z(()=>Gn(i()));return{get pathname(){return o()},get search(){return s()},get hash(){return u()},get state(){return t()},get key(){return l()},query:n?n(f):Jn(f)}}let ge;function Li(){return ge}function Ni(e,t,n,r={}){const{signal:[i,o],utils:s={}}=e,u=s.parsePath||(R=>R),l=s.renderPath||(R=>R),f=s.beforeLeave||Vn(),g=lt("",r.base||""),c=X(i);if(g===void 0)throw new Error(`${g} is not a valid base path`);g&&!c.value&&o({value:g,replace:!0,scroll:!1});const[d,p]=q(!1,{ownedWrite:!0}),[m,a]=q(void 0,{ownedWrite:!0});let h;const y=Z(()=>m()??i()),w=xi(()=>y().value,()=>y().state,s.queryWrapper),T=[],I=q([],{ownedWrite:!0}),N=Z(()=>typeof r.transformUrl=="function"?Lt(t(),r.transformUrl(w.pathname)):Lt(t(),w.pathname)),Y=()=>{const R=N(),P={};for(let V=0;V<R.length;V++)Object.assign(P,R[V].params);return P},St=s.paramsWrapper?s.paramsWrapper(Y,t):Jn(Y),Qe={pattern:g,path:()=>g,outlet:()=>null,resolvePath(R){return lt(g,R)}};return{base:Qe,location:w,params:St,isRouting:d,renderPath:l,parsePath:u,navigatorFactory:Ie,matches:N,beforeLeave:f,preloadRoute:Et,singleFlight:r.singleFlight===void 0?!0:r.singleFlight,submissions:I};function ke(R,P,V){X(()=>{if(typeof P=="number"){P&&(s.go?s.go(P):console.warn("Router integration does not support relative routing"));return}const ae=!P||P[0]==="?",{replace:$e,resolve:ne,scroll:he,state:ie}={replace:!1,resolve:!ae,scroll:!0,...V},S=ne?R.resolvePath(P):lt(ae&&w.pathname||"",P);if(S===void 0)throw new Error(`Path '${P}' is not a routable path`);if(T.length>=Ai)throw new Error("Too many redirects");const C=y();if((S!==C.value||ie!==C.state)&&!wi){if(f.confirm(S,V)){T.push({value:C.value,replace:$e,scroll:he,state:C.state});const k={value:S,state:ie};h===void 0&&(p(!0),Te()),ge="navigate",h=k,h===k&&(a({...h}),queueMicrotask(()=>{h===k&&(ge=void 0,Xe(h),a(void 0),p(!1),h=void 0)}))}}})}function Ie(R){return R=R||Ri(Zn)||Qe,(P,V)=>ke(R,P,V)}function Xe(R){const P=T[0];P&&(o({...R,replace:P.replace,scroll:P.scroll}),T.length=0)}function Et(R,P){const V=Lt(t(),R.pathname),ae=ge;ge="preload";for(let $e in V){const{route:ne,params:he}=V[$e];ne.component&&ne.component.preload&&ne.component.preload();const{preload:ie}=ne;P&&ie&&se(n(),()=>ie({params:he,location:{pathname:R.pathname,search:R.search,hash:R.hash,query:Gn(R),state:null,key:""},intent:"preload"}))}ge=ae}}function qi(e,t,n,r){const{base:i,location:o,params:s}=e,{pattern:u,component:l,preload:f}=r().route,g=Z(()=>r().path);l&&l.preload&&l.preload();const c=f?f({params:s,location:o,intent:ge||"initial"}):void 0;return{parent:t,pattern:u,path:g,outlet:()=>l?L(l,{params:s,location:o,data:c,get children(){return n()}}):n(),resolvePath(p){return lt(i.path(),p,g())}}}const Mi=e=>function(n){const{base:r,singleFlight:i,transformUrl:o,root:s,rootPreload:u,routeChildren:l}=X(()=>({base:n.base,singleFlight:n.singleFlight,transformUrl:n.transformUrl,root:n.root,rootPreload:n.rootPreload,routeChildren:n.children})),f=en(()=>l),g=Z(()=>tr(f(),r||""));let c;const d=Ni(e,g,()=>c,{base:r,singleFlight:i,transformUrl:o});return e.create&&e.create(d),L(Yn,{value:d,get children(){return L(Di,{routerState:d,root:s,preload:u,get children(){return[j(()=>(c=Ye())&&null),L(Fi,{routerState:d,get branches(){return g()}})]}})}})};function Di(e){const t=e.routerState.location,n=e.routerState.params,r=Z(()=>e.preload&&X(()=>{e.preload({params:n,location:t,intent:Li()||"initial"})})),i=e.root;return i?L(i,{params:n,location:t,get data(){return r()},get children(){return e.children}}):e.children}function Fi(e){const t=[];let n,r;const i=Z(s=>{const u=e.routerState.matches(),l=r;let f=l&&u.length===l.length;const g=[];for(let c=0,d=u.length;c<d;c++){const p=l&&l[c],m=u[c];s&&p&&m.route.key===p.route.key?g[c]=s[c]:(f=!1,t[c]&&t[c](),Ht(a=>{t[c]=a,g[c]=qi(e.routerState,g[c-1]||e.routerState.base,pn(()=>i()?.[c+1]),()=>{const h=e.routerState.matches();return h[c]??h[0]})}))}return t.splice(u.length).forEach(c=>c()),s&&f?(r=u,s):(n=g[0],r=u,g)}),o=pn(()=>i()&&n);return j(o)}const pn=e=>()=>{const t=e();if(t)return L(Zn,{value:t,get children(){return t.outlet()}})},yn=e=>{const t=en(()=>e.children);return ni(e,{get children(){return t()}})};function Bi(e){let t=!1;const n=s=>typeof s=="string"?{value:s}:s,[r,i]=q(n(e.get()),{equals:(s,u)=>s.value===u.value&&s.state===u.state,ownedWrite:!0}),o=[r,s=>{!t&&e.set(s),i(s)}];return e.init&&Dn(e.init((s=e.get())=>{t=!0,o[1](n(s)),t=!1})),Mi({signal:o,create:e.create,utils:e.utils})}function ji(e,t,n){return e.addEventListener(t,n),()=>e.removeEventListener(t,n)}function Ki(e,t){const n=e&&document.getElementById(e);n?n.scrollIntoView():t&&window.scrollTo(0,0)}const Ui=new Map;function Wi({preload:e=!0,explicitLinks:t=!1,actionBase:n="/_server",transformUrl:r}={}){return i=>{const o=i.base.path(),s=i.navigatorFactory(i.base);let u,l;function f(a){return a.namespaceURI==="http://www.w3.org/2000/svg"}function g(a){if(a.defaultPrevented||a.button!==0||a.metaKey||a.altKey||a.ctrlKey||a.shiftKey)return;const h=a.composedPath().find(Y=>Y instanceof Node&&Y.nodeName.toUpperCase()==="A");if(!h||t&&!h.hasAttribute("link"))return;const y=f(h),w=y?h.href.baseVal:h.href;if((y?h.target.baseVal:h.target)||!w&&!h.hasAttribute("state"))return;const I=(h.getAttribute("rel")||"").split(/\s+/);if(h.hasAttribute("download")||I&&I.includes("external"))return;const N=y?new URL(w,document.baseURI):new URL(w);if(!(N.origin!==window.location.origin||o&&N.pathname&&!N.pathname.toLowerCase().startsWith(o.toLowerCase())))return[h,N]}function c(a){const h=g(a);if(!h)return;const[y,w]=h,T=i.parsePath(w.pathname+w.search+w.hash),I=y.getAttribute("state");a.preventDefault(),s(T,{resolve:!1,replace:y.hasAttribute("replace"),scroll:!y.hasAttribute("noscroll"),state:I?JSON.parse(I):void 0})}function d(a){const h=g(a);if(!h)return;const[y,w]=h;r&&(w.pathname=r(w.pathname)),i.preloadRoute(w,y.getAttribute("preload")!=="false")}function p(a){clearTimeout(u);const h=g(a);if(!h)return l=null;const[y,w]=h;l!==y&&(r&&(w.pathname=r(w.pathname)),u=setTimeout(()=>{i.preloadRoute(w,y.getAttribute("preload")!=="false"),l=y},20))}function m(a){if(a.defaultPrevented)return;let h=a.submitter&&a.submitter.hasAttribute("formaction")?a.submitter.getAttribute("formaction"):a.target.getAttribute("action");if(!h)return;if(!h.startsWith("https://action/")){const w=new URL(h,zn);if(h=i.parsePath(w.pathname+w.search),!h.startsWith(n))return}if(a.target.method.toUpperCase()!=="POST")throw new Error("Only POST forms are supported for Actions");const y=Ui.get(h);if(y){a.preventDefault();const w=new FormData(a.target,a.submitter);y.call({r:i,f:a.target},a.target.enctype==="multipart/form-data"?w:new URLSearchParams(w))}}Ze(["click","submit"]),document.addEventListener("click",c),e&&(document.addEventListener("mousemove",p,{passive:!0}),document.addEventListener("focusin",d,{passive:!0}),document.addEventListener("touchstart",d,{passive:!0})),document.addEventListener("submit",m),Dn(()=>{document.removeEventListener("click",c),e&&(document.removeEventListener("mousemove",p),document.removeEventListener("focusin",d),document.removeEventListener("touchstart",d)),document.removeEventListener("submit",m)})}}function Vi(e){const t=()=>{const r=window.location.pathname.replace(/^\/+/,"/")+window.location.search,i=window.history.state&&window.history.state._depth&&Object.keys(window.history.state).length===1?void 0:window.history.state;return{value:r+window.location.hash,state:i}},n=Vn();return Bi({get:t,set({value:r,replace:i,scroll:o,state:s}){i?window.history.replaceState(vi(s),"",r):window.history.pushState(s,"",r),Ki(decodeURIComponent(window.location.hash.slice(1)),o),tn()},init:r=>ji(window,"popstate",$i(r,i=>{if(i)return!n.confirm(i);{const o=t();return!n.confirm(o.value,{state:o.state})}})),create:Wi({preload:e.preload,explicitLinks:e.explicitLinks,actionBase:e.actionBase,transformUrl:e.transformUrl}),utils:{go:r=>window.history.go(r),beforeLeave:n}})(e)}var zi=A('<div class=chat><div class=chat-messages></div><div class=chat-input><input type=text placeholder="Type a message..."><button type=button>Send'),Gi=A("<span class=chat-user>"),Ji=A("<span class=chat-time>"),Hi=A("<div><div class=chat-content>");function Yi(e){let t,n;_t(()=>e.messages.length,()=>{n&&n.lastElementChild&&n.lastElementChild.scrollIntoView({behavior:"instant"})});const[r,i]=q(""),o=()=>{const a=r().trim();a&&(e.onSend(a),i(""),t?.focus())},s=a=>{a.key==="Enter"&&!a.shiftKey&&(a.preventDefault(),o())},u=a=>{try{const h=new Date(a),y=Math.floor((Date.now()-h.getTime())/1e3);if(y<60)return"just now";const w=Math.floor(y/60);if(w<60)return`${w}m ago`;const T=Math.floor(w/60);return T<24?`${T}h ago`:h.toLocaleDateString([],{month:"short",day:"numeric"})}catch{return a}};var l=zi(),f=l.firstChild,g=f.nextSibling,c=g.firstChild,d=c.nextSibling,p=n;typeof p=="function"||Array.isArray(p)?gt(()=>p,f):n=f,v(f,L(st,{get each(){return e.messages},children:a=>(()=>{var h=Hi(),y=h.firstChild;return v(h,L(Kn,{get when(){return a.msg_type!=="system"},get children(){return[(()=>{var w=Gi();return v(w,()=>a.user_name),w})(),(()=>{var w=Ji();return v(w,()=>u(a.created_at)),w})()]}}),y),v(y,()=>a.content),ee(()=>`chat-message${a.msg_type==="system"?" system":""}`,(w,T)=>{Ge(h,w,T)}),h})()})),c.$$keydown=s,c.$$input=a=>i(a.currentTarget.value);var m=t;return typeof m=="function"||Array.isArray(m)?gt(()=>m,c):t=c,d.$$click=o,ee(()=>({e:r(),t:!r().trim()}),({e:a,t:h},y)=>{c.value=a??"",h!==y?.t&&ye(d,"disabled",h)}),l}Ze(["input","keydown","click"]);function Zi(e){return!e||typeof e!="object"?null:"room_name"in e&&typeof e.room_name=="string"?{kind:"state",data:e}:"user_name"in e&&"content"in e?{kind:"chat",data:e}:null}function Qi(e,t,n){const i=`${location.protocol==="https:"?"wss:":"ws:"}//${location.host}/api/v1/ws/${e}?name=${encodeURIComponent(t)}`;let o=new WebSocket(i),s=[],u=!1;function l(f){o?.readyState===WebSocket.OPEN?o.send(f):s.push(f)}return o.onopen=()=>{u=!0;for(const f of s)o?.send(f);s=[]},o.onmessage=f=>{if(typeof f.data=="string")try{const g=JSON.parse(f.data),c=Zi(g);c?.kind==="state"?n.onState(c.data):c?.kind==="chat"&&n.onChat(c.data)}catch{}},o.onclose=f=>{o=null,u?f.code!==1e3&&f.code!==1001&&n.onError(`Connection lost (code: ${f.code})`):n.onError("Failed to connect to room"),n.onDisconnect()},o.onerror=()=>{o?.close()},f=>{l(JSON.stringify(f))}}function nr([e,t],n={}){_t(()=>e(),r=>{if(!r||n.unless?.(r))return;const i=setTimeout(()=>t(null),n.ms??5e3);return()=>clearTimeout(i)})}function Xi(e,t){const[n,r]=q(null),[i,o]=q([]),[s,u]=q(!1),[l,f]=q(null),[g,c]=q(null),d={current:!0},p=()=>{if(g())return;const a=Qi(e,t,{onState:h=>{d.current=!1,r(h)},onChat:h=>o(y=>{const w=[...y,h];return w.length>200?w.slice(-200):w}),onDisconnect:()=>{u(!1),c(null)},onError:h=>{f(h)}});c(()=>a),u(!0),f(null)};return Xr(()=>{p(),setTimeout(()=>{d.current&&f("Room not found or unavailable")},8e3)}),_t(()=>[s(),g()],([a,h])=>{if(!a&&!h){const y=setTimeout(p,3e3);return()=>clearTimeout(y)}}),nr([l,f],{unless:a=>a.includes("not found")}),{state:n,chats:i,connected:s,error:l,setError:f,send:a=>g()?.(a)}}var eo=A("<div class=player>"),to=A("<div class=player-idle>Paste a URL to start watching together"),no=A("<div class=player-info><div class=player-meta><strong class=player-title></strong><span class=player-duration-row><span class=duration-label></span><span class=elapsed-label>"),ro=A("<div class=player-media-wrap><div class=player-media></div><button type=button>"),io=A("<img class=player-thumb alt>"),oo=A("<video controls><track kind=captions>"),so=A("<audio controls><track kind=captions>");const rr="moqbox-volume",wn=()=>parseFloat(localStorage.getItem(rr)||"0.5");function lo(e){const t=Math.round(e.volume*100)/100;localStorage.setItem(rr,t.toString())}function uo(e){const t=e.url.split(".").pop()?.toLowerCase();return t?["mp3","m4a","ogg","wav","flac","aac"].includes(t)?!1:(["mp4","webm","mov","mkv","avi","m4v"].includes(t),!0):!0}function Nt(e,t){const n=(Date.now()-t)/1e3;e.currentTime=n}function co(e){let t;const[n,r]=q(!0),i=e.roomId,o=c=>{t&&t.removeEventListener("volumechange",s),t=c??void 0,c&&(c.volume=wn(),c.addEventListener("volumechange",s))},s=()=>{const c=t;c&&lo(c)};let u=null;_t(()=>{const c=e.track;return u=c,c?.started_at?c.id:null},(c,d)=>{if(c===d)return;const p=u,m=t;if(!m||!p?.started_at){m&&m.pause(),r(!0);return}const a=p.started_at,h=`/api/v1/rooms/${i}/media/${p.hash}`;m.src=h,m.volume=wn();let y;const w=()=>{Nt(m,a);const I=()=>{r(!0),m.play().catch(()=>{}),y=()=>{const N=u?.started_at;if(!N)return;const Y=(Date.now()-N)/1e3;r(Math.abs(m.currentTime-Y)<.5)},m.addEventListener("timeupdate",y),m.addEventListener("pause",T)};m.addEventListener("seeked",I,{once:!0})};m.addEventListener("loadedmetadata",w,{once:!0}),m.addEventListener("canplay",w,{once:!0});const T=()=>{m.ended||Nt(m,a)};return()=>{m.removeEventListener("loadedmetadata",w),m.removeEventListener("canplay",w),y&&m.removeEventListener("timeupdate",y),m.removeEventListener("pause",T)}});const l=c=>{if(c<1)return"--:--";const d=Math.floor(c/1e3),p=Math.floor(d/60),m=d%60;return`${p}:${m.toString().padStart(2,"0")}`},f=()=>{const c=t,d=e.track?.started_at;c&&d&&(Nt(c,d),r(!0))};var g=eo();return v(g,L(Kn,{get when(){return e.track},get fallback(){return to()},children:c=>{const d=c();return[(()=>{var p=no(),m=p.firstChild,a=m.firstChild,h=a.nextSibling,y=h.firstChild,w=y.nextSibling;return v(p,(()=>{var T=j(()=>!!d.thumbnail);return()=>T()&&(()=>{var I=io();return I.addEventListener("error",N=>N.currentTarget.hidden=!0),ee(()=>d.thumbnail,N=>{ye(I,"src",N)}),I})()})(),m),v(a,()=>d.title),v(y,()=>d.duration),v(w,()=>l(Date.now()-d.started_at)),p})(),(()=>{var p=ro(),m=p.firstChild,a=m.nextSibling;return v(m,(()=>{var h=j(()=>!!uo(d));return()=>h()?(()=>{var y=oo();return hn(y,"ended",e.onEnded),gt(()=>o,y),y})():(()=>{var y=so();return hn(y,"ended",e.onEnded),gt(()=>o,y),y})()})()),a.$$click=f,v(a,()=>n()?"Live":"Go live"),ee(()=>["live-btn",n()&&"live-btn--on"],(h,y)=>{Ge(a,h,y)}),p})()]}})),g}Ze(["click"]);var ao=A("<div><span class=queue-title></span><span class=queue-duration>"),fo=A("<img class=queue-thumb alt>"),ho=A("<div class=queue-panel><div class=queue-section><h3>Now Playing</h3></div><div class=queue-section><h3>Up Next (<!>)</h3></div><div class=queue-section><h3>Playlist (<!>)</h3></div><div class=queue-section><h3>Recently Played"),go=A("<div class=queue-empty>Paste a URL to start watching together"),mo=A("<div class=queue-empty>Tracks you add will appear here"),po=A('<div class=queue-item-row><button type=button class="queue-item-action queue-item-remove"title="Remove from playlist">×'),yo=A("<div class=queue-empty>Add links to build a perpetual playlist"),wo=A('<div class=queue-item-row><button type=button class=queue-item-action title="Add to playlist">+'),bo=A("<div class=queue-empty>Recently played tracks show up here");function it(e){var t=ao(),n=t.firstChild,r=n.nextSibling;return v(t,(()=>{var i=j(()=>!!e.thumbnail);return()=>i()&&(()=>{var o=fo();return o.addEventListener("error",s=>s.currentTarget.hidden=!0),ee(()=>e.thumbnail,s=>{ye(o,"src",s)}),o})()})(),n),v(n,()=>e.pending?"⏳ ":"",null),v(n,()=>e.title,null),v(r,()=>e.duration),ee(()=>`queue-item${e.extraClass?" "+e.extraClass:""}`,(i,o)=>{Ge(t,i,o)}),t}function vo(e){var t=ho(),n=t.firstChild;n.firstChild;var r=n.nextSibling,i=r.firstChild,o=i.firstChild,s=o.nextSibling;s.nextSibling;var u=r.nextSibling,l=u.firstChild,f=l.firstChild,g=f.nextSibling;g.nextSibling;var c=u.nextSibling;return c.firstChild,v(n,(()=>{var d=j(()=>!!e.currentTrack);return()=>d()?L(it,{get title(){return e.currentTrack.title},get duration(){return e.currentTrack.duration},get thumbnail(){return e.currentTrack.thumbnail},extraClass:"current"}):go()})(),null),v(i,()=>e.queue.length,s),v(r,L(st,{get each(){return e.queue},children:d=>L(it,{get title(){return d.title},get duration(){return d.duration},get thumbnail(){return d.thumbnail},get pending(){return d.pending}})}),null),v(r,(()=>{var d=j(()=>e.queue.length===0);return()=>d()&&mo()})(),null),v(l,()=>e.playlist.length,g),v(u,L(st,{get each(){return e.playlist},children:d=>(()=>{var p=po(),m=p.firstChild;return v(p,L(it,{get title(){return d.title},get duration(){return d.duration},get thumbnail(){return d.thumbnail},get pending(){return d.pending}}),m),m.$$click=()=>e.onRemoveFromPlaylist?.(d.id),p})()}),null),v(u,(()=>{var d=j(()=>e.playlist.length===0);return()=>d()&&yo()})(),null),v(c,L(st,{get each(){return e.history},children:d=>(()=>{var p=wo(),m=p.firstChild;return v(p,L(it,{get title(){return d.title},get duration(){return d.duration},get thumbnail(){return d.thumbnail},extraClass:"history"}),m),m.$$click=()=>e.onAddToPlaylist?.(d.url),p})()}),null),v(c,(()=>{var d=j(()=>e.history.length===0);return()=>d()&&bo()})(),null),t}Ze(["click"]);const ir="moqbox-theme";function or(e){const t=document.documentElement;e==="latte"?(t.setAttribute("data-theme","latte"),t.style.colorScheme="light"):e==="macchiato"?(t.setAttribute("data-theme","macchiato"),t.style.colorScheme="dark"):(t.removeAttribute("data-theme"),t.style.colorScheme="light dark"),localStorage.setItem(ir,e)}function $o(e){return e==="macchiato"?"latte":e==="latte"?"system":"macchiato"}function sr(){return localStorage.getItem(ir)||"system"}var _o=A('<div class=home><h1>moqbox</h1><p>Watch videos together with friends</p><div class=home-form><input type=text placeholder="Your name"><button type=button>'),So=A('<div class="toast toast-error"role=alert>'),Eo=A("<div class=room><header class=room-header><h2></h2><span class=client-count></span></header><div class=room-layout><div class=room-main><div class=room-actions><button type=button></button><button type=button class=secondary>Skip</button></div></div><div class=room-sidebar><div class=room-footer><button type=button class=theme-toggle-sm>"),Co=A("<span class=disconnected>Disconnected"),Oo=A('<div class="toast toast-error"role=alert><span></span><button type=button class=toast-action>Go back'),To=A('<div class=inline-form><input type=text placeholder="Paste a YouTube / media URL..."><div class=add-target-toggle><button type=button>Queue</button><button type=button>Playlist</button></div><button type=button>');const Ao=()=>{const e=Xn(),[t,n]=q(""),[r,i]=q(!1),[o,s]=q(null);nr([o,s]);const u=async()=>{if(t().trim()){i(!0);try{const m=await fetch("/api/v1/rooms",{method:"POST"});if(!m.ok)throw new Error(`create room: ${m.status}`);const a=await m.json();e(`/room/${a.room_id}?name=${encodeURIComponent(t().trim())}`)}catch(m){console.error("Failed to create room:",m),s("Failed to create room"),i(!1)}}};var l=_o(),f=l.firstChild,g=f.nextSibling,c=g.nextSibling,d=c.firstChild,p=d.nextSibling;return d.$$keydown=m=>m.key==="Enter"&&u(),d.$$input=m=>n(m.currentTarget.value),p.$$click=u,v(p,()=>r()?"Creating...":"Create Room"),v(l,(()=>{var m=j(()=>!!o());return()=>m()&&(()=>{var a=So();return v(a,o),a})()})(),null),ee(()=>({e:t(),t:r()||!t().trim()}),({e:m,t:a},h)=>{d.value=m??"",a!==h?.t&&ye(p,"disabled",a)}),l},Ro=()=>{const e=Pi(),t=X(()=>e.id),r=new URLSearchParams(location.search).get("name")||"anonymous",i=Xn(),[o,s]=q(sr()),u=()=>{const S=$o(o());or(S),s(S)},{state:l,chats:f,connected:g,error:c,setError:d,send:p}=Xi(t,r),[m,a]=q(!1),[h,y]=q(""),[w,T]=q("queue"),[I,N]=q(!1),Y=async()=>{const S=h().trim();if(S){N(!0);try{const C=w()==="queue"?"/api/v1/ingest":"/api/v1/playlist/add",k=await fetch(C,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:S,room_id:t})});if(!k.ok){if(k.status===429){const oe=k.headers.get("Retry-After");d(oe?`Too fast — try again in ${oe}s`:"Too fast — try again in a moment")}else{const oe=await k.json().catch(()=>({error:k.statusText}));d(oe.error||`${w()} failed`)}return}y(""),a(!1),d(null)}catch(C){d(String(C))}finally{N(!1)}}},St=async S=>{try{const C=await fetch("/api/v1/playlist/add",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:S,room_id:t})});if(!C.ok){const k=await C.json().catch(()=>({error:C.statusText}));d(k.error||"Failed to add to playlist")}}catch(C){d(String(C))}},Qe=async S=>{try{const C=await fetch("/api/v1/playlist/remove",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({room_id:t,id:S})});if(!C.ok){const k=await C.json().catch(()=>({error:C.statusText}));d(k.error||"Failed to remove from playlist")}}catch(C){d(String(C))}};var ke=Eo(),Ie=ke.firstChild,Xe=Ie.firstChild,Et=Xe.nextSibling,R=Ie.nextSibling,P=R.firstChild,V=P.firstChild,ae=V.firstChild,$e=ae.nextSibling,ne=P.nextSibling,he=ne.firstChild,ie=he.firstChild;return v(Xe,()=>l()?.room_name??"Loading..."),v(Et,(()=>{var S=j(()=>l()?.clients!=null);return()=>S()?`${l()?.clients} connected`:""})()),v(Ie,(()=>{var S=j(()=>!g());return()=>S()&&Co()})(),null),v(ke,(()=>{var S=j(()=>!!c());return()=>S()&&(()=>{var C=Oo(),k=C.firstChild,oe=k.nextSibling;return v(k,c),oe.$$click=()=>i("/"),C})()})(),R),v(P,L(co,{get track(){return l()?.current_track??null},roomId:t,onEnded:()=>{const S=l()?.current_track;S&&p({type:"track_ended",item_id:S.id})}}),V),ae.$$click=()=>{a(!m())},v(ae,()=>m()?"Cancel":"Add"),$e.$$click=()=>p({type:"skip"}),v(P,(()=>{var S=j(()=>!!m());return()=>S()&&(()=>{var C=To(),k=C.firstChild,oe=k.nextSibling,Ct=oe.firstChild,nn=Ct.nextSibling,Ot=oe.nextSibling;return k.$$keydown=xe=>xe.key==="Enter"&&Y(),k.$$input=xe=>y(xe.currentTarget.value),Ct.$$click=()=>T("queue"),nn.$$click=()=>T("playlist"),Ot.$$click=Y,v(Ot,()=>I()?"Adding...":"Add"),ee(()=>({e:h(),t:w()==="queue"?"active":"",a:w()==="playlist"?"active":"",o:I()||!h().trim()}),({e:xe,t:lr,a:ur,o:rn},Tt)=>{k.value=xe??"",Ge(Ct,lr,Tt?.t),Ge(nn,ur,Tt?.a),rn!==Tt?.o&&ye(Ot,"disabled",rn)}),C})()})(),null),v(ne,L(vo,{get currentTrack(){return l()?.current_track??null},get queue(){return l()?.queue??[]},get history(){return l()?.history??[]},get playlist(){return l()?.playlist??[]},onAddToPlaylist:St,onRemoveFromPlaylist:Qe}),he),v(ne,L(Yi,{get messages(){return f()},onSend:S=>p({type:"chat",content:S})}),he),ie.$$click=u,v(ie,(()=>{var S=j(()=>o()==="macchiato");return()=>S()?"Dark":o()==="latte"?"Light":"System"})()),ee(()=>({e:!l()?.current_track,t:`Theme: ${o()}`}),({e:S,t:C},k)=>{S!==k?.e&&ye($e,"disabled",S),C!==k?.t&&ye(ie,"title",C)}),ke},Po=()=>(or(sr()),L(Vi,{get children(){return[L(yn,{path:"/",component:Ao}),L(yn,{path:"/room/:id",component:Ro})]}}));Ze(["input","keydown","click"]);const bn=document.getElementById("root");bn&&bi(()=>L(Po,{}),bn);
+1
static/dist/assets/index-dTCvdWLw.js
··· 1 + (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const o of i)if(o.type==="childList")for(const s of o.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&r(s)}).observe(document,{childList:!0,subtree:!0});function n(i){const o={};return i.integrity&&(o.integrity=i.integrity),i.referrerPolicy&&(o.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?o.credentials="include":i.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function r(i){if(i.ep)return;i.ep=!0;const o=n(i);fetch(i.href,o)}})();class U extends Error{source;constructor(t){super(),this.source=t}}class Mt extends Error{source;constructor(t,n){super(n instanceof Error?n.message:String(n),{cause:n}),this.source=t}}class $n extends Error{constructor(){super("")}}class ar extends Error{constructor(){super("")}}const _n=0,Ke=1,Oe=2,pt=4,le=8,Ve=16,J=32,de=64,we=128,Vt=256,ct=512,ze=1024,mt=1,fr=2,wt=4,dr=8,Sn=16,Pe=32,zt=64,C=1,W=2,D=4,Ee=1,G=2,_e=3,S={},hr=typeof Proxy=="function",Gt={},gr=Symbol("refresh");function En(e,t){const n=(e.i?.t?e.i.u?.o:e.i?.o)??-1;n>=e.o&&(e.o=n+1);const r=e.o,i=t.l[r];if(i===void 0)t.l[r]=e;else{const o=i.S;o.T=e,e.S=o,i.S=e}r>t._&&(t._=r)}function He(e,t){let n=e.O;n&(le|pt|ze)||(n&Ke?e.O=n&-4|Oe|le:e.O=n|le,n&Ve||En(e,t))}function Cn(e,t){let n=e.O;n&(le|pt|Ve|ze)||(e.O=n|Ve,En(e,t))}function ke(e,t){const n=e.O;if(!(n&(le|Ve)))return;e.O=n&-25;const r=e.o;if(e.S===e)t.l[r]=void 0;else{const i=e.T,o=t.l[r],s=i??o;e===o?t.l[r]=i:e.S.T=i,s.S=e.S}e.S=e,e.T=void 0}function yr(e){if(!e.R){e.R=!0;for(let t=0;t<=e._;t++)for(let n=e.l[t];n!==void 0;n=n.T)n.O&le&&at(n)}}function at(e,t=Oe){const n=e.O;if(!((n&(Ke|Oe))>=t)){e.O=n&-4|t;for(let r=e.I;r!==null;r=r.p)at(r.h,Ke);if(e.N!==null)for(let r=e.N;r!==null;r=r.A)for(let i=r.I;i!==null;i=i.p)at(i.h,Ke)}}function qe(e,t){for(e.R=!1,e.C=0;e.C<=e._;e.C++){let n=e.l[e.C];for(;n!==void 0;)n.O&le?t(n):pr(n,e),n=e.l[e.C]}e._=0}function pr(e,t){ke(e,t);let n=e.o;for(let r=e.P;r;r=r.D){const i=r.m,o=i.V||i;o.L&&o.o>=n&&(n=o.o+1)}if(e.o!==n){e.o=n;for(let r=e.I;r!==null;r=r.p)Cn(r.h,t)}}const ft=new WeakMap,te=new Set;function mr(e){let t=ft.get(e);if(t)return V(t);const n=e.U,r=n?.G?V(n.G):null;return t={k:e,F:new Set,W:[[],[]],H:null,M:b,j:r},ft.set(e,t),te.add(t),e.$=!1,t}function V(e){for(;e.H;)e=e.H;return e}function On(e,t){if(e=V(e),t=V(t),e===t)return e;t.H=e;for(const n of t.F)e.F.add(n);return e.W[0].push(...t.W[0]),e.W[1].push(...t.W[1]),e}function Ce(e){const t=e.G;if(!t)return;const n=V(t);if(te.has(n))return n;e.G=void 0}function Me(e){return Ce(e)?.M??e.M}function bt(e){return e.K!==void 0&&e.K!==S}function Jt(e,t){const n=V(t),r=e.G;if(r){if(r.H){e.G=t;return}const i=V(r);if(te.has(i)){i!==n&&!bt(e)&&(n.j&&V(n.j)===i?e.G=t:i.j&&V(i.j)===n||On(n,i));return}}e.G=t}const De=new Set,k={l:new Array(2e3).fill(void 0),R:!1,C:0,_:0},H={l:new Array(2e3).fill(void 0),R:!1,C:0,_:0};let M=0,b=null,Ge=!1,Dt=0,st=null;const Fe=new Set;function wr(e){return De.size===0&&te.size===0&&e.Y.length===0&&e.Z.length===0&&e.q.size===0&&Fe.size===0}function br(){if(Fe.size!==0)for(const e of Fe){if(e.I!==null){Fe.delete(e);continue}e.B===S&&(e.K!==void 0&&e.K!==S||(Fe.delete(e),e.X?.()))}}function vr(e){return!!st?.has(e)}function tt(e){for(const t of te){if(t.H||t.F.size>0)continue;const n=t.W[e-1];n.length&&(t.W[e-1]=[],ht(n,e))}}function $r(e){for(let t=e.I;t!==null;t=t.p){const n=t.h;if(!n.J)continue;if(n.J===_e){n.ee||(n.ee=!0,n.te.enqueue(G,n.ne));continue}const r=n.O&J?H:k;r.C>n.o&&(r.C=n.o),He(n,r)}}function _r(e,t){t.ie=e,e.re.push(...t.re);for(const n of te)n.M===t&&(n.M=e);e.Z.push(...t.Z);for(const n of t.q)e.q.add(n);for(const[n,r]of t.oe){let i=e.oe.get(n);i||e.oe.set(n,i=new Set);for(const o of r)i.add(o)}for(const n of t.se)e.se.add(n)}function Sr(e){for(let t=0;t<e.length;t++){const n=e[t];n.G=void 0,n.B!==S&&(n.ue=n.B,n.B=S);const r=n.K;n.K=S,r!==S&&n.ue!==r&&Te(n,!0),n.M=null}e.length=0}function Er(e){for(const t of te)(e?t.M===e:!t.M)&&(t.H||(t.W[0].length&&ht(t.W[0],Ee),t.W[1].length&&ht(t.W[1],G)),t.k.G===t&&(t.k.G=void 0),t.F.clear(),t.W[0].length=0,t.W[1].length=0,te.delete(t),ft.delete(t.k))}function ve(){Ge||(Ge=!0,!Dt&&!E.ce&&queueMicrotask(Ae))}let Cr=class{i=null;le=[[],[]];Y=[];created=M;addChild(t){this.Y.push(t),t.i=this}removeChild(t){const n=this.Y.indexOf(t);n>=0&&(this.Y.splice(n,1),t.i=null)}notify(t,n,r,i){return this.i?this.i.notify(t,n,r,i):!1}run(t){if(this.le[t-1].length){const n=this.le[t-1];this.le[t-1]=[],ht(n,t)}for(let n=0;n<this.Y.length;n++)this.Y[n].run?.(t)}enqueue(t,n){t&&(z?V(z).W[t-1].push(n):this.le[t-1].push(n)),ve()}stashQueues(t){t.le[0].push(...this.le[0]),t.le[1].push(...this.le[1]),this.le=[[],[]];for(let n=0;n<this.Y.length;n++){let r=this.Y[n],i=t.Y[n];i||(i={le:[[],[]],Y:[]},t.Y[n]=i),r.stashQueues(i)}}restoreQueues(t){this.le[0].push(...t.le[0]),this.le[1].push(...t.le[1]);for(let n=0;n<t.Y.length;n++){const r=t.Y[n];let i=this.Y[n];i&&i.restoreQueues(r)}}};class X extends Cr{ce=!1;ae=null;fe=[];Z=[];q=new Set;static Ee;static Se;static Te;static de=null;flush(){if(!this.ce){this.ce=!0;try{if(qe(k,X.Ee),b){if(!Tr(b)){const r=b;if(qe(H,X.Ee),this.ae=null,this.fe=[],this.Z=[],this.q=new Set,tt(Ee),tt(G),this.stashQueues(r._e),M++,Ge=k._>=k.C,ln(r.fe),b=null,!r.re.length&&!r.oe.size&&r.Z.length){st=new Set;for(let i=0;i<r.Z.length;i++){const o=r.Z[i];o.L||o.Oe&mt||(st.add(o),$r(o))}}try{Rt(null,!0)}finally{st=null}return}this.fe!==b.fe&&this.fe.push(...b.fe),this.restoreQueues(b._e),De.delete(b);const n=b;b=null,ln(this.fe),Rt(n)}else wr(this)?(dt(),k._>=k.C&&(qe(k,X.Ee),dt())):(De.size&&qe(H,X.Ee),Rt());M++,Ge=k._>=k.C,te.size&&tt(Ee),this.run(Ee),te.size&&tt(G),this.run(G)}finally{this.ce=!1}}}notify(t,n,r,i){if(n&C){if(r&C){const o=i!==void 0?i:t.Re;if(b&&o){const s=o.source;let u=b.oe.get(s);u||b.oe.set(s,u=new Set);const l=u.size;u.add(t),u.size!==l&&ve()}}return!0}return!1}initTransition(t){if(t&&(t=An(t)),!(t&&t===b)&&!(!t&&b&&b.Ie===M)){if(!b)b=t??{Ie:M,fe:[],oe:new Map,Z:[],q:new Set,re:[],_e:{le:[[],[]],Y:[]},ie:!1,se:new Set};else if(t){const n=b;_r(t,n),De.delete(n),b=t}if(De.add(b),b.Ie=M,this.ae!==null&&(this.ae.M=b,b.fe.push(this.ae),this.ae=null),this.fe!==b.fe){for(let n=0;n<this.fe.length;n++){const r=this.fe[n];r.M=b,b.fe.push(r)}this.fe=b.fe}if(this.Z!==b.Z){for(let n=0;n<this.Z.length;n++){const r=this.Z[n];r.M=b,b.Z.push(r)}this.Z=b.Z}for(const n of te)n.M||(n.M=b);if(this.q!==b.q){for(const n of this.q)b.q.add(n);this.q=b.q}}}}function vt(e){if(b){E.fe.push(e);return}if(E.ae===null&&E.fe.length===0){E.ae=e;return}E.ae!==null&&(E.fe.push(E.ae),E.ae=null),E.fe.push(e)}function Te(e,t=!1){const n=e.G||z,r=e.pe!==void 0;for(let i=e.I;i!==null;i=i.p){if(r&&i.h.Oe&dr){i.h.O|=Vt;continue}t&&n?(i.h.O|=we,Jt(i.h,n)):t&&(i.h.O|=we,i.h.G=void 0);const o=i.h;if(o.J===_e){o.ee||(o.ee=!0,o.te.enqueue(G,o.ne));continue}const s=i.h.O&J?H:k;s.C>i.h.o&&(s.C=i.h.o),He(i.h,s)}}function sn(e){const t=e;if(!t.L){e.B!==S&&(e.ue=e.B,e.B=S);return}e.B!==S&&(e.ue=e.B,e.B=S,e.J&&e.J!==_e&&(e.ee=!0)),t.O&=~ze,t.he&C||(t.he&=~D),(t.Ne!==null||t.Ae!==null)&&X.Se(t,!1,!0)}function dt(){E.ae!==null&&(sn(E.ae),E.ae=null);const e=E.fe;for(let t=0;t<e.length;t++)sn(e[t]);e.length=0}function Rt(e=null,t=!1){const n=!t;n&&dt(),!t&&E.Y.length&&Tn(E);const r=k._>=k.C;if(r&&qe(k,X.Ee),n){if(r&&dt(),Sr(e?e.Z:E.Z),e&&e.se.size){for(const i of e.se){if(i.O&de)continue;if(i.J===_e){i.ee||(i.ee=!0,i.te.enqueue(G,i.ne));continue}const o=i.O&J?H:k;o.C>i.o&&(o.C=i.o),He(i,o)}e.se.clear()}e?e.q:E.q,br(),Er(e)}}function Tn(e){for(const t of e.Y)t.checkSources?.(),Tn(t)}function ln(e){for(let t=0;t<e.length;t++)e[t].M=b}const E=new X;function Ae(e){if(e){Dt++;try{return e()}finally{Ae(),Dt--}}if(!E.ce)for(;Ge||b;)E.flush()}function ht(e,t){for(let n=0;n<e.length;n++)e[n](t)}function Or(e,t){if(e.O&(J|de))return!1;if(e.Ce===t||e.Pe?.has(t))return!0;for(let n=e.P;n;n=n.D){let r=n.m;for(;r;){if(r===t||r.V===t)return!0;r=r.U}}return!!(e.he&C&&e.Re instanceof U&&e.Re.source===t)}function Tr(e){if(e.ie)return!0;if(e.re.length)return!1;let t=!0;for(const[n,r]of e.oe){let i=!1;for(const o of r){if(Or(o,n)){i=!0;break}r.delete(o)}if(!i)e.oe.delete(n);else if(n.he&C&&n.Re?.source===n){t=!1;break}}if(t)for(let n=0;n<e.Z.length;n++){const r=e.Z[n];if(bt(r)&&"he"in r&&r.he&C&&r.Re instanceof U&&r.Re.source!==r){t=!1;break}}return t&&(e.ie=!0),t}function An(e){for(;e.ie&&typeof e.ie=="object";)e=e.ie;return e}function Ar(e,t){const n=b;try{return b=An(e),t()}finally{b=n}}function Rn(e){let t=e.ge;for(;t;)t.O|=J,t.O&le&&(ke(t,k),He(t,H)),Rn(t),t=t.De}function Ye(e,t=!1,n){const r=e.O;if(r&de)return;t&&(e.O=r|de),t&&e.L&&(e.ve=null);let i=n?e.Ne:e.ge;for(;i;){const o=i.De;if(i.P){const s=i;ke(s,s.O&J?H:k);let u=s.P;do u=Zt(u);while(u!==null);s.P=null,s.ye=null}Ye(i,!0),i=o}if(n?e.Ne=null:(e.ge=null,e.me=0),t&&!n&&!(r&J)&&e.i!==null&&!(e.i.O&de)){const o=e.we,s=e.De;o!==null?o.De=s:e.i.ge=s,s!==null&&(s.we=o),e.we=null}Rr(e,n)}function Rr(e,t){let n=t?e.Ae:e.be;if(n){if(Array.isArray(n))for(let r=0;r<n.length;r++){const i=n[r];i.call(i)}else n.call(n);t?e.Ae=null:e.be=null}}function Pr(e,t){let n=e;for(;n.Oe&wt&&n.i;)n=n.i;if(n.id!=null)return kr(n.id,n.me++);throw new Error("Cannot get child id from owner without an id")}function Ht(e){return Pr(e)}function kr(e,t){const n=t.toString(36),r=n.length-1;return e+(r?String.fromCharCode(64+r):"")+n}function Ze(){return $}function $t(e){return $&&($.be?Array.isArray($.be)?$.be.push(e):$.be=[$.be,e]:$.be=e),e}function Ir(e=!0){Ye(this,e)}function Ue(e){const t=$,n=e?.transparent??!1,r={id:e?.id??(n?t?.id:t?.id!=null?Ht(t):void 0),Oe:n?wt:0,t:!0,u:t?.t?t.u:t,ge:null,De:null,we:null,be:null,te:t?.te??E,Ve:t?.Ve||Gt,me:0,Ae:null,Ne:null,i:t,dispose:Ir};if(t){const i=t.ge;i===null||(r.De=i,i.we=r),t.ge=r}return r}function Yt(e,t){const n=Ue(t);return se(n,()=>e(()=>n.dispose()))}function Zt(e){const t=e.m,n=e.D,r=e.p,i=e.Le;if(r!==null?r.Le=i:t.Ue=i,i!==null)i.p=r;else if(t.I=r,r===null){t.X?.();const o=t;o.L&&o.Oe&Pe&&!(o.O&J)&&kn(o)}return n}function Pn(e){const t=e.ye;let n=t!==null?t.D:e.P;if(n!==null){do n=Zt(n);while(n!==null);t!==null?t.D=null:e.P=null}}function kn(e){ke(e,e.O&J?H:k);let t=e.P;for(;t!==null;)t=Zt(t);e.P=null,e.ye=null,Ye(e,!0)}function Ne(e,t){const n=t.ye;if(n!==null&&n.m===e)return;let r=null;const i=t.O&pt;if(i&&(r=n!==null?n.D:t.P,r!==null&&r.m===e)){t.ye=r;return}const o=e.Ue;if(o!==null&&o.h===t&&(!i||xr(o,t)))return;const s=t.ye=e.Ue={m:e,h:t,D:r,Le:o,p:null};n!==null?n.D=s:t.P=s,o!==null?o.p=s:e.I=s}function xr(e,t){const n=t.ye;if(n!==null){let r=t.P;do{if(r===e)return!0;if(r===n)break;r=r.D}while(r!==null)}return!1}function Lr(e,t){return e.Ce===t||e.Pe?.has(t)?!1:e.Ce?(e.Pe?e.Pe.add(t):e.Pe=new Set([e.Ce,t]),e.Ce=void 0,!0):(e.Ce=t,!0)}function Nr(e,t){return e.Ce?e.Ce!==t?!1:(e.Ce=void 0,!0):e.Pe?.delete(t)?(e.Pe.size===1?(e.Ce=e.Pe.values().next().value,e.Pe=void 0):e.Pe.size===0&&(e.Pe=void 0),!0):!1}function In(e){e.Ce=void 0,e.Pe?.clear(),e.Pe=void 0}function gt(e,t,n){if(!t){e.Re=null;return}if(n instanceof U&&n.source===t){e.Re=n;return}const r=e.Re;(!(r instanceof U)||r.source!==t)&&(e.Re=new U(t))}function Ft(e,t){for(let n=e.I;n!==null;n=n.p)t(n.h);for(let n=e.N;n!==null;n=n.A)for(let r=n.I;r!==null;r=r.p)t(r.h)}function qr(e){let t=!1;const n=new Set,r=i=>{if(n.has(i)||!Nr(i,e))return;n.add(i),i.Ie=M;const o=i.Ce??i.Pe?.values().next().value;if(o)gt(i,o),$e(i);else{if(i.he&=~C,gt(i),$e(i),i.Ge){if(i.J===_e){const s=i;s.ee||(s.ee=!0,s.te.enqueue(G,s.ne))}else{const s=i.O&J?H:k;s.C>i.o&&(s.C=i.o),He(i,s)}t=!0}i.Ge=!1}Ft(i,r)};Ft(e,r),t&&ve()}function Mr(e,t,n){let r=!1,i=!1;if(typeof t=="object"&&t!==null&&ee(()=>{r=t[Symbol.asyncIterator],i=!r&&typeof t.then=="function"}),!i&&!r)return e.ve=null,t;e.ve=t;let o;const s=l=>{e.ve===t&&(E.initTransition(Me(e)),Qt(e,l instanceof U?C:W,l),e.Ie=M)},u=(l,d)=>{if(e.ve!==t||e.O&(Oe|we))return;E.initTransition(Me(e));const h=!!(e.he&D);Pn(e),xn(e);const a=Ce(e);if(a&&a.F.delete(e),e.K!==void 0)e.K!==void 0&&e.K!==S?e.B=l:(e.ue=l,Te(e)),e.Ie=M;else if(a){const g=e.J,m=e.ue,p=e.ke;(!g&&h||!p||!p(l,m))&&(e.ue=l,e.Ie=M,e.Fe&&ce(e.Fe,l),Te(e,!0))}else ce(e,()=>l);qr(e),ve(),Ae(),d?.()};if(i){let l=!1,d=!0;if(t.then(h=>{d?(o=h,l=!0):u(h)},h=>{d||s(h)}),d=!1,!l)throw E.initTransition(Me(e)),new U($)}if(r){const l=t[Symbol.asyncIterator]();let d=!1,h=!1;$t(()=>{if(!h){h=!0;try{const m=l.return?.();m&&typeof m.then=="function"&&m.then(void 0,()=>{})}catch{}}});const a=()=>{let m,p=!1,c=!0;return l.next().then(f=>{if(c)m=f,p=!0,f.done&&(h=!0);else{if(e.ve!==t)return;f.done?(h=!0,ve(),Ae()):u(f.value,a)}},f=>{!c&&e.ve===t&&(h=!0,s(f))}),c=!1,p&&!m.done?(o=m.value,d=!0,a()):p&&m.done},g=a();if(!d&&!g)throw E.initTransition(Me(e)),new U($)}return o}function xn(e,t=!1){(e.Ce||e.Pe)&&In(e),e.Ge&&(e.Ge=!1),e.he=t?0:e.he&D,e.Re&&gt(e),e.We&&$e(e),e.xe&&e.xe()}function Qt(e,t,n,r,i){t===W&&!(n instanceof Mt)&&!(n instanceof U)&&(n=new Mt(e,n));const o=t===C&&n instanceof U?n.source:void 0,s=o===e,u=t===C&&e.K!==void 0&&!s,l=u&&bt(e);r||(t===C&&o?(Lr(e,o),e.he=C|e.he&D,gt(e,o,n)):(In(e),e.he=t|(t!==W?e.he&D:0),e.Re=n),$e(e)),i&&!r&&Jt(e,i);const d=r||l,h=r||u?void 0:i;if(e.xe){if(r&&t===C)return;d?e.xe(t,n):e.xe();return}Ft(e,a=>{a.Ie=M,(t===C&&o&&a.Ce!==o&&!a.Pe?.has(o)||t!==C&&(a.Re!==n||a.Ce||a.Pe))&&(!d&&!a.M&&vt(a),Qt(a,t,n,d,h))})}let Dr=null;X.Ee=ue;X.Se=Ye;let j=!1,ne=!1,$=null,z=null;function ue(e,t=!1){const n=e.J;t||(e.M&&(!n||b)&&b!==e.M&&E.initTransition(e.M),ke(e,e.O&J?H:k),e.ve=null,e.M||n===_e?Ye(e):(e.ge!==null||e.be!==null)&&(Rn(e),e.Ae=e.be,e.Ne=e.ge,e.be=null,e.ge=null,e.me=0));let r=!!(e.O&we);const i=e.K!==void 0&&e.K!==S,o=!!(e.he&C),s=!!(e.he&D),u=$;$=e,e.ye=null,e.O=pt,e.Ie=M;let l=e.B===S?e.ue:e.B,d=e.o,h=j,a=z;if(j=!0,r){const c=Ce(e);c&&(z=c)}else if(b&&!t&&b.Z.length)for(let c=e.P;c;c=c.D){const f=c.m;if(f.O&we){const y=Ce(f);if(y){r=!0,z=y,e.O|=we,Jt(e,y);break}}}const g=n&&n!==G,m=ne;g&&(ne=!0);try{if(e.Oe&zt)l=e.L(l),e.ve=null;else{const c=e.ve,f=e.L(l),y=typeof f=="object"&&f!==null,w=e.ve!==c;l=w||!y?f:Mr(e,f),!w&&!y&&(e.ve=null)}if(xn(e,t),e.G){const c=Ce(e);c&&(c.F.delete(e),$e(c.k))}}catch(c){if(c instanceof U&&z){const f=V(z);f.k!==e&&(f.F.add(e),e.G=f,$e(f.k))}c instanceof U&&(e.Ge=!0),Qt(e,c instanceof U?C:W,c,void 0,c instanceof U?e.G:void 0)}finally{j=h,g&&(ne=m),e.O=_n|(t?e.O&Vt:0),$=u}if(!e.Re){Pn(e);const c=i?e.K:e.B===S?e.ue:e.B,f=!n&&s||!e.ke||!e.ke(c,l);if(n&&f&&(e.ee=!e.Re,t||e.te.enqueue(n,X.Te.bind(null,e))),f){const y=i?e.K:void 0;t||n&&b!==e.M||r?(e.ue=l,i&&r&&(e.K=l,e.B=l)):e.B=l,i&&!r&&o&&!e.$&&(e.K=l),(!i||r||e.K!==y)&&Te(e,r||i)}else if(i)e.B=l;else if(e.o!=d)for(let y=e.I;y!==null;y=y.p)Cn(y.h,y.h.O&J?H:k)}z=a,(e.B!==S||e.Ne!==null||e.Ae!==null||!!(e.he&(C|D)))&&(!t||e.he&C)&&!e.M&&!(b&&i)&&vt(e),e.M&&n&&b!==e.M&&Ar(e.M,()=>ue(e))}function Ln(e){if(e.O&Ke)for(let t=e.P;t;t=t.D){const n=t.m,r=n.V||n;if(r.L&&Ln(r),e.O&Oe)break}(e.O&(Oe|we)||e.Re&&e.Ie<M&&!e.ve)&&ue(e),e.O=e.O&(Vt|le|Ve)}function _t(e,t){const n=t?.transparent??!1,r={id:t?.id??(n?$?.id:$?.id!=null?Ht($):void 0),Oe:(n?wt:0)|(t?.ownedWrite?mt:0)|(!$||t?.lazy?Pe:0)|(t?.sync?zt:0)|0,ke:t?.equals!=null?t.equals:qn,X:t?.unobserved,be:null,te:$?.te??E,Ve:$?.Ve??Gt,me:0,L:e,ue:void 0,o:0,N:null,T:void 0,S:null,P:null,ye:null,I:null,Ue:null,i:$,De:null,we:null,ge:null,O:t?.lazy?ct:_n,he:D,Ie:M,B:S,Ae:null,Ne:null,ve:null,M:null};return Nn(r,t),r}function Fr(e,t,n,r,i,o){const s=o?.transparent??!1,u={id:o?.id??(s?$?.id:$?.id!=null?Ht($):void 0),Oe:(s?wt:0)|(o?.ownedWrite?mt:0)|(o?.sync?zt:0)|0,ke:!1,X:o?.unobserved,be:null,te:$?.te??E,Ve:$?.Ve??Gt,me:0,L:e,ue:void 0,o:0,N:null,T:void 0,S:null,P:null,ye:null,I:null,Ue:null,i:$,De:null,we:null,ge:null,O:ct,he:D,Ie:M,B:S,Ae:null,Ne:null,ve:null,M:null,ee:!1,Me:void 0,Qe:t,je:n,$e:void 0,Ke:!1,J:r,xe:i};return Nn(u,Br),u}const Br={lazy:!0};function Nn(e,t){e.S=e;const n=$?.t?$.u:$;if($){const r=$.ge;r===null||(e.De=r,r.we=e),$.ge=e}n&&(e.o=n.o+1),!t?.lazy&&ue(e,!0)}function Be(e,t,n=null){const r={ke:t?.equals!=null?t.equals:qn,Oe:(t?.ownedWrite?mt:0)|(t?.Ye?fr:0),X:t?.unobserved,ue:e,I:null,Ue:null,Ie:M,V:n,A:n?.N||null,B:S};return n&&(n.N=r),r}function qn(e,t){return e===t}function ee(e,t){if(!j)return e();const n=j;j=!1;try{return e()}finally{j=n}}function Mn(e){let t=$;t?.t&&(t=t.u);const n=e;if(typeof n.L=="function"){const o=e;o.O&ct?(o.O&=~ct,ue(o,!0)):o.O&de&&ue(o,!0)}const r=e.V||e;if(!n.L&&r===e&&e.K===void 0&&e.pe===void 0&&b===null&&z===null)return t&&j&&Ne(e,t),!t||e.B===S?e.ue:e.B;if(t&&j&&(Ne(e,t),r.L)){const o=e.O&J;r.o>=(o?H.C:k.C)&&(at(t),yr(o?H:k),Ln(r));const s=r.o;s>=t.o&&e.i!==t&&(t.o=s+1)}if(r.he&C)if(t&&!(ne&&r.M&&b!==r.M))if(z){const o=r.G,s=V(z);if(o&&V(o)===s&&!bt(r))throw!j&&e!==t&&Ne(e,t),r.Re}else throw!j&&e!==t&&Ne(e,t),r.Re;else{if(t&&r!==e&&r.he&D)throw!j&&e!==t&&Ne(e,t),r.Re;if(!t&&r.he&D)throw r.Re}if(e.L&&e.he&W){if(e.Ie<M)return ue(e),Mn(e);throw e.Re}if(e.K!==void 0&&e.K!==S)return t&&ne&&vr(e)?e.ue:e.K;if(b!==null&&z!==null&&e.B!==S&&r===e&&!e.L&&t)return b.se.add(t),e.ue;const i=!t||z!==null&&(e.K!==void 0||e.G||r===e&&ne||r.he&C)||e.B===S||ne&&e.M&&b!==e.M?e.ue:e.B;return!t&&r===e&&typeof n.L=="function"&&e.Oe&Pe&&!(r.he&C)&&!e.I&&kn(e),i}function ce(e,t){e.M&&b!==e.M&&E.initTransition(e.M);const n=e.K!==void 0&&!0,r=e.K!==void 0&&e.K!==S,i=n?r?e.K:e.ue:e.B===S?e.ue:e.B;if(typeof t=="function"&&(t=t(i)),!(!e.ke||!e.ke(i,t)||!!(e.he&D)))return n&&r&&e.L&&(Te(e,!0),ve()),t;if(n){const s=e.K===S;s||E.initTransition(Me(e)),s&&(e.B=e.ue,E.Z.push(e)),e.$=!0;const u=mr(e);e.G=u,e.K=t}else e.B===S&&vt(e),e.B=t;return e.We&&$e(e),e.Fe&&ce(e.Fe,t),e.Ie=M,Te(e,n),ve(),t}function jr(e){ke(e,e.O&J?H:k),!(e.O&ze)&&e.B===S&&vt(e),e.O=e.O&-4|ze}function Kr(e,t){const n=ce(e,t);return jr(e),n}function se(e,t){const n=$,r=j;$=e,j=!1;try{return t()}finally{$=n,j=r}}function Ur(e){const t=e,n=e.V;if(e.U){const r=e.U;return r.he&C&&!(r.he&D)?!0:e.B!==S&&!(t.he&D)}if(n&&e.B!==S)return!n.ve&&!(n.he&C);if(e.K!==void 0&&e.K!==S){if(t.he&C&&!(t.he&D))return!0;if(e.U){const r=e.G?V(e.G):null;return!!(r&&r.F.size>0)}return!0}return e.K!==void 0&&e.K===S&&!e.U?!1:e.B!==S&&!(t.he&D)?!0:!!(t.he&C&&!(t.he&D))}function $e(e){if(e.We){const t=Ur(e),n=e.We;if(ce(n,t),!t&&n.G){const r=Ce(e);if(r&&r.F.size>0){const i=V(n.G);i!==r&&On(r,i)}ft.delete(n),n.G=void 0}}}function Wr(e,t=!0){const n=ne;ne=t;try{return e()}finally{ne=n}}function Vr(e,t=Ze()){if(!t)throw new $n;const n=Gr(e,t)?t.Ve[e.id]:e.defaultValue;if(Xt(n))throw new ar;return n}function zr(e,t,n=Ze()){if(!n)throw new $n;n.Ve={...n.Ve,[e.id]:Xt(t)?e.defaultValue:t}}function Gr(e,t){return!Xt(t?.Ve[e.id])}function Xt(e){return typeof e>"u"}function Dn(e,t,n,r){const i=!!r?.user,o=Fr(e,t,n,i?G:Ee,Jr,r);ue(o,!0),!r?.defer&&(o.J===G||r?.schedule?o.te.enqueue(o.J,Bt.bind(null,o)):Bt(o))}function Jr(e,t){const n=e!==void 0?e:this.he,r=t!==void 0?t:this.Re;if(n&W){let i=r;if(this.te.notify(this,C,0),this.J===G)try{return this.je?this.je(i,()=>{this.$e?.(),this.$e=void 0}):console.error(i)}catch(o){i=o}if(!this.te.notify(this,W,W))throw i}else this.J===Ee&&this.te.notify(this,C|W,n,r)}function Bt(e){if(!(!e.ee||e.O&de)){e.$e?.(),e.$e=void 0;try{const t=e.Qe(e.ue,e.Me);e.$e=t,e.$e&&!e.Ke&&(e.Ke=!0,se(e.i,()=>$t(()=>e.$e?.())))}catch(t){if(e.Re=new Mt(e,t),e.he|=W,!e.te.notify(e,W,W))throw t}finally{e.Me=e.ue,e.ee=!1}}}X.Te=Bt;function Hr(e,t){const n=()=>{if(!(!r.ee||r.O&de))try{r.ee=!1,ue(r)}finally{}},r=_t(()=>{r.$e?.(),r.$e=void 0;const i=Wr(e);r.$e=i},{...t,lazy:!0});r.$e=void 0,r.Oe=r.Oe&~Pe|Sn,r.ee=!0,r.J=_e,r.xe=(i,o)=>{if((i!==void 0?i:r.he)&W){r.te.notify(r,C,0);const u=o!==void 0?o:r.Re;if(!r.te.notify(r,W,W))throw u}},r.ne=n,r.te.enqueue(G,n),$t(()=>r.$e?.())}function Fn(e){return $t(e)}function fe(e){const t=Mn.bind(null,e);return t[gr]=e,t}function Yr(e,t){if(typeof e=="function"){const r=_t(e,t);return r.Oe&=~Pe,[fe(r),Kr.bind(null,r)]}const n=Be(e,t);return[fe(n),ce.bind(null,n)]}function be(e,t){return fe(_t(e,t))}function Zr(e,t,n){Dn(e,t.effect||t,t.error,{user:!0,...n})}function Qr(e,t,n){Dn(e,t,void 0,n)}function Xr(e,t){Hr(e,t)}function ei(e){const t=Ze();t&&!(t.Oe&Sn)?Xr(()=>ee(e),void 0):E.enqueue(G,()=>{e()?.()})}const ti=Symbol(0),jt=Symbol(0);function un(e){return e==null||typeof e!="object"||Object.isFrozen(e)?!1:typeof Node>"u"||!(e instanceof Node)}const Bn=Symbol(0);function cn(e){return e==="__proto__"||e==="constructor"||e==="prototype"}function je(e,t,n=0){let r,i=e;if(n<t.length-1){r=t[n];const s=typeof r,u=Array.isArray(e);if(s==="string"&&cn(r))return;if(Array.isArray(r)){for(let l=0;l<r.length;l++)t[n]=r[l],je(e,t,n);t[n]=r;return}else if(u&&s==="function"){for(let l=0;l<e.length;l++)r(e[l],l)&&(t[n]=l,je(e,t,n));t[n]=r;return}else if(u&&s==="object"){const{from:l=0,to:d=e.length-1,by:h=1}=r;for(let a=l;a<=d;a+=h)t[n]=a,je(e,t,n);t[n]=r;return}else if(n<t.length-2){je(e[r],t,n+1);return}i=e[r]}let o=t[t.length-1];if(!(typeof o=="function"&&(o=o(i),o===i))&&!(r===void 0&&o==null))if(o===Bn)delete e[r];else if(r===void 0||un(i)&&un(o)&&!Array.isArray(o)){const s=r!==void 0?e[r]:e,u=Object.keys(o);for(let l=0;l<u.length;l++){const d=u[l];if(cn(d))continue;const h=Object.getOwnPropertyDescriptor(o,d);h.get||h.set?Object.defineProperty(s,d,h):s[d]=h.value}}else e[r]=o}Object.assign(function(...t){return n=>{je(n,t)}},{DELETE:Bn});function nt(){return!0}const ni={get(e,t,n){return t===jt?n:e.get(t)},has(e,t){return t===jt?!0:e.has(t)},set:nt,deleteProperty:nt,getOwnPropertyDescriptor(e,t){return{configurable:!0,enumerable:!0,get(){return e.get(t)},set:nt,deleteProperty:nt}},ownKeys(e){return e.keys()}};function Pt(e){return(e=typeof e=="function"?e():e)?e:{}}const kt=Symbol(0);function ri(...e){if(e.length===1&&typeof e[0]!="function")return e[0];let t=!1;const n=[];for(let l=0;l<e.length;l++){const d=e[l];t=t||!!d&&jt in d;const h=!!d&&d[kt];if(h)for(let a=0;a<h.length;a++)n.push(h[a]);else n.push(typeof d=="function"?(t=!0,be(d)):d)}if(hr&&t)return new Proxy({get(l){if(l===kt)return n;for(let d=n.length-1;d>=0;d--){const h=Pt(n[d]);if(l in h)return h[l]}},has(l){for(let d=n.length-1;d>=0;d--)if(l in Pt(n[d]))return!0;return!1},keys(){const l=new Set;for(let d=0;d<n.length;d++){const h=Object.keys(Pt(n[d]));for(let a=0;a<h.length;a++)l.add(h[a])}return[...l]}},ni);const r=Object.create(null);let i=!1,o=n.length-1;for(let l=o;l>=0;l--){const d=n[l];if(!d){l===o&&o--;continue}const h=Object.getOwnPropertyNames(d);for(let a=h.length-1;a>=0;a--){const g=h[a];if(!(g==="__proto__"||g==="constructor")&&!r[g]){i=i||l!==o;const m=Object.getOwnPropertyDescriptor(d,g);r[g]=m.get?{enumerable:!0,configurable:!0,get:m.get.bind(d)}:m}}}if(!i)return n[o];const s={},u=Object.keys(r);for(let l=u.length-1;l>=0;l--){const d=u[l],h=r[d];h.get?Object.defineProperty(s,d,h):s[d]=h.value}return s[kt]=n,s}function ii(e,t,n){const r=typeof n?.keyed=="function"?n.keyed:void 0,i=t.length>1,o=t,s={Ze:Ue(),qe:0,Be:e,ze:[],Xe:o,Je:[],et:[],tt:r,nt:r||n?.keyed===!1?[]:void 0,it:i&&n?.keyed!==!1?[]:void 0,rt:n?.keyed===!1,ot:n?.fallback},u=_t(oi.bind(s));return s.Ze.u=u,u.Oe&=~Pe,fe(u)}const rt={ownedWrite:!0};function oi(){const e=this.Be()||[],t=e.length;return e[ti],se(this.Ze,()=>{let n,r,i=this.nt?this.rt?()=>(this.nt[r]=Be(e[r],rt),this.Xe(fe(this.nt[r]),r)):()=>(this.nt[r]=Be(e[r],rt),this.it&&(this.it[r]=Be(r,rt)),this.Xe(fe(this.nt[r]),this.it?fe(this.it[r]):void 0)):this.it?()=>{const o=e[r];return this.it[r]=Be(r,rt),this.Xe(o,fe(this.it[r]))}:()=>{const o=e[r];return this.Xe(o)};if(t===0)this.qe!==0&&(this.Ze.dispose(!1),this.et=[],this.ze=[],this.Je=[],this.qe=0,this.nt&&(this.nt=[]),this.it&&(this.it=[])),this.ot&&!this.Je[0]&&(this.Je[0]=se(this.et[0]=Ue(),this.ot));else if(this.qe===0){for(this.et[0]&&this.et[0].dispose(),this.Je=new Array(t),r=0;r<t;r++)this.ze[r]=e[r],this.Je[r]=se(this.et[r]=Ue(),i);this.qe=t}else{let o,s,u,l,d,h,a,g=new Array(t),m=new Array(t),p=this.nt?new Array(t):void 0,c=this.it?new Array(t):void 0;for(o=0,s=Math.min(this.qe,t);o<s&&(this.ze[o]===e[o]||this.nt&&an(this.tt,this.ze[o],e[o]));o++)this.nt&&ce(this.nt[o],e[o]);for(s=this.qe-1,u=t-1;s>=o&&u>=o&&(this.ze[s]===e[u]||this.nt&&an(this.tt,this.ze[s],e[u]));s--,u--)g[u]=this.Je[s],m[u]=this.et[s],p&&(p[u]=this.nt[s]),c&&(c[u]=this.it[s]);for(h=new Map,a=new Array(u+1),r=u;r>=o;r--)l=e[r],d=this.tt?this.tt(l):l,n=h.get(d),a[r]=n===void 0?-1:n,h.set(d,r);for(n=o;n<=s;n++)l=this.ze[n],d=this.tt?this.tt(l):l,r=h.get(d),r!==void 0&&r!==-1?(g[r]=this.Je[n],m[r]=this.et[n],p&&(p[r]=this.nt[n]),c&&(c[r]=this.it[n]),r=a[r],h.set(d,r)):this.et[n].dispose();for(r=o;r<t;r++)r in g?(this.Je[r]=g[r],this.et[r]=m[r],p&&(this.nt[r]=p[r],ce(this.nt[r],e[r])),c&&(this.it[r]=c[r],ce(this.it[r],r))):this.Je[r]=se(this.et[r]=Ue(),i);this.Je=this.Je.slice(0,this.qe=t),this.ze=e.slice(0)}}),this.Je}function an(e,t,n){return e?e(t)===e(n):!0}function en(e,t){if(typeof e=="function"&&!e.length){if(t?.doNotUnwrap)return e;do e=e();while(typeof e=="function"&&!e.length)}if(!(t?.skipNonRendered&&(e==null||e===!0||e===!1||e===""))){if(Array.isArray(e)){let n=[];return Kt(e,n,t)?()=>{let r=[];return Kt(n,r,{...t,doNotUnwrap:!1}),r}:n}return e}}function Kt(e,t=[],n){let r=null,i=!1;for(let o=0;o<e.length;o++)try{let s=e[o];if(typeof s=="function"&&!s.length){if(n?.doNotUnwrap){t.push(s),i=!0;continue}do s=s();while(typeof s=="function"&&!s.length)}Array.isArray(s)?i=Kt(s,t,n):n?.skipNonRendered&&(s==null||s===!0||s===!1||s==="")||t.push(s)}catch(s){if(!(s instanceof U))throw s;r=s}if(r)throw r;return i}function jn(e,t){const n=Symbol("");function r(i){return Yt(()=>(zr(r,i.value),tn(()=>i.children)))}return r.id=n,r.defaultValue=e,r}function Kn(e){return Vr(e)}function tn(e){const t=be(e,{lazy:!0}),n=be(()=>en(t()),{lazy:!0,sync:!0});return n.toArray=()=>{const r=n();return Array.isArray(r)?r:r!=null?[r]:[]},n}class Se{static{for(const t of["all","allSettled","any","race","reject","resolve"])Se[t]=()=>new Se}catch(){return new Se}then(){return new Se}finally(){return new Se}}const Z=(...e)=>be(...e),q=(...e)=>Yr(...e),si=(...e)=>Qr(...e),St=(...e)=>Zr(...e);function I(e,t){return ee(()=>e(t||{}))}const li=e=>`Stale read from <${e}>.`;function lt(e){const t="fallback"in e?{keyed:e.keyed,fallback:()=>e.fallback}:{keyed:e.keyed};return ii(()=>e.each,e.children,t)}function Un(e){const t=e.keyed,n=be(()=>e.when,void 0),r=t?n:be(n,{equals:(i,o)=>!i==!o,sync:!0});return be(()=>{const i=r();if(i){const o=e.children;return typeof o=="function"&&o.length>0?ee(t?()=>o(i):()=>o(()=>{if(!ee(r))throw li("Show");return n()})):o}return e.fallback},{sync:!0})}const B=Symbol("slot"),ui={transparent:!0,sync:!0},ci={sync:!0},Q=(e,t,n)=>si(e,t,n?{transparent:!0,sync:!0,...n}:ui),K=e=>Z(()=>e(),ci);function ai(e,t,n,r){let i=n.length,o=t.length,s=i,u=0,l=0,d=t[o-1],h=d[B],a=d.parentNode===e&&(!h||h===r)?d.nextSibling:r||null,g=null,m,p;for(;u<o||l<s;){if(t[u]===n[l]){u++,l++;continue}for(;t[o-1]===n[s-1];)o--,s--;if(o===u){let c;if(s<i)if(l){const f=n[l-1],y=f[B];c=f.parentNode===e&&(!y||y===r)?f.nextSibling:a}else c=n[s-l];else c=a;for(;l<s;){const f=n[l++];e.insertBefore(f,c),r&&(f[B]=r)}}else if(s===l)for(;u<o;){const c=t[u++];if(!g||!g.has(c)){const f=c[B];c.parentNode===e&&(!f||f===r)&&c.remove()}}else if((m=t[u])===n[s-1]&&n[l]===t[o-1]&&m.parentNode===e&&(!(p=m[B])||p===r))if(r)do{const c=t[--o];if(e.insertBefore(c,m),c[B]=r,l++,u>=o-1||l>=s)break}while(t[u]===n[s-1]&&n[l]===t[o-1]);else do if(e.insertBefore(t[--o],m),l++,u>=o-1||l>=s)break;while(t[u]===n[s-1]&&n[l]===t[o-1]);else{if(!g){g=new Map;let f=l;for(;f<s;)g.set(n[f],f++)}const c=g.get(t[u]);if(c!=null)if(l<c&&c<s){let f=u,y=1,w;for(;++f<o&&f<s&&!((w=g.get(t[f]))==null||w!==c+y);)y++;if(y>c-l){const O=t[u],P=O[B],x=O.parentNode===e&&(!P||P===r)?O:a;for(;l<c;){const Y=n[l++];e.insertBefore(Y,x),r&&(Y[B]=r)}}else{const O=t[u++],P=n[l++],x=O[B];O.parentNode===e&&(!x||x===r)?e.replaceChild(P,O):e.insertBefore(P,a),r&&(P[B]=r)}}else u++;else{const f=t[u++],y=f[B];f.parentNode===e&&(!y||y===r)&&f.remove()}}}}const fn="_$DX_EVENT_OWNER",dn={},Ut=new Set,Re=new Map;function fi(e,t,n,r={}){let i;hi(t);try{Yt(o=>{if(i=o,t===document){const s=e();Q(()=>en(s),()=>{})}else{const s=e();v(t,()=>s,t.firstChild?null:void 0,n,r.insertOptions)}},{id:r.renderId})}catch(o){throw i&&i(),hn(t),o}return()=>{i(),hn(t),t.textContent=""}}function di(e,t,n){const r=document.createElement("template");return r.innerHTML=e,n===2?r.content.firstChild.firstChild:r.content.firstChild}function A(e,t){let n;return i=>(n||(n=di(e,i,t))).cloneNode(!0)}function Qe(e){for(let t=0,n=e.length;t<n;t++){const r=e[t];Ut.has(r)||(Ut.add(r),Re.forEach((i,o)=>Wn(r,o,i)))}}function hi(e){const t=gi(e,e);t&&(t.roots=(t.roots||0)+1)}function hn(e){const t=Re.get(e);t&&(t.roots>1?t.roots--:delete t.roots),yi(e,e)}function gi(e,t=e){if(!e||!t)return;let n=Re.get(e);return n||Re.set(e,n={owners:new Map,handlers:new Map}),n.owners.set(t,(n.owners.get(t)||0)+1),Ut.forEach(r=>Wn(r,e,n)),n}function yi(e,t=e){const n=Re.get(e);if(!n)return;const r=n.owners.get(t);r>1?n.owners.set(t,r-1):n.owners.delete(t),!n.owners.size&&(n.handlers.forEach((i,o)=>e.removeEventListener(o,i)),Re.delete(e))}function Wn(e,t,n){if(n.handlers.has(e))return;const r=i=>wi(i,t,n);n.handlers.set(e,r),t.addEventListener(e,r)}function pi(e,t){let n=e,r=0;for(;n;){if(t.owners.has(n))return{owner:n,distance:r};r++,n=n._$host||n.parentNode||n.host}}function he(e,t,n){n==null||n===!1?e.removeAttribute(t):e.setAttribute(t,n===!0?"":n)}function Je(e,t,n){if(t==null||t===!1){n&&e.removeAttribute("class");return}if(typeof t=="string"){t!==n&&e.setAttribute("class",t);return}typeof n=="string"?(n={},e.removeAttribute("class")):n=yn(n||{}),t=yn(t);const r=Object.keys(t||{}),i=Object.keys(n);let o,s;for(o=0,s=i.length;o<s;o++){const u=i[o];!u||u==="undefined"||t[u]||e.classList.remove(u)}for(o=0,s=r.length;o<s;o++){const u=r[o],l=!!t[u];!u||u==="undefined"||n[u]===l||!l||e.classList.add(u)}}function gn(e,t,n,r){if(Array.isArray(n)){const i=n[0];e.addEventListener(t,n[0]=o=>i.call(e,n[1],o))}else e.addEventListener(t,n,typeof n!="function"&&n)}function mi(e,t){Array.isArray(e)?e.flat(1/0).forEach(n=>n&&n(t)):e(t)}function yt(e,t){const n=ee(e);se(null,()=>mi(n,t))}function v(e,t,n,r,i){const o=n!==void 0;if(o&&!r&&(r=[]),typeof t!="function"&&(t=xt(t,r,o,!0),typeof t!="function"))return It(e,t,r,n);if(o&&r.length===0){const u=document.createTextNode("");e.insertBefore(u,n),r=[u]}let s=r;Q(u=>{const l=xt(t(),s,o,!0);return typeof l!="function"?l:(Q(()=>xt(l,s,o),d=>{It(e,d,s,n),s=d},u!==void 0&&!(i&&i.schedule)?{...i,schedule:!0}:i),dn)},u=>{u!==dn&&(It(e,u,s,n),s=u)},i)}function yn(e){if(Array.isArray(e)){const t={};Vn(e,t),e=t}if(e&&typeof e=="object"){const t={},n=Object.keys(e);for(let r=0,i=n.length;r<i;r++){const o=n[r];if(!e[o])continue;const s=o.trim().split(/\s+/);for(let u=0,l=s.length;u<l;u++)s[u]&&(t[s[u]]=!0)}return t}return e}function Vn(e,t){for(let n=0,r=e.length;n<r;n++){const i=e[n];Array.isArray(i)?Vn(i,t):typeof i=="object"&&i!=null?Object.assign(t,i):(i||i===0)&&(t[i]=!0)}}function wi(e,t,n){if(e[fn])return;const r=n&&(n.owners.size===1&&n.owners.has(t)?t:pi(e.target,n)?.owner);if(n&&!r)return;e[fn]=r||!0;let i=e.target;const o=`$$${e.type}`,s=e.target,u=r||t||e.currentTarget,l=a=>Object.defineProperty(e,"target",{configurable:!0,value:a}),d=()=>{const a=i[o];if(a&&!i.disabled){const g=i[`${o}Data`];if(g!==void 0?a.call(i,g,e):a.call(i,e),e.cancelBubble)return}return i.host&&typeof i.host!="string"&&!i.host._$host&&i.contains(e.target)&&l(i.host),!0},h=()=>{for(;d()&&!(i===u||i.parentNode===u);)i=i._$host||i.parentNode||i.host};if(Object.defineProperty(e,"currentTarget",{configurable:!0,get(){return i||u||document}}),e.composedPath){const a=e.composedPath();if(a.length){l(a[0]);for(let g=0;g<a.length&&(i=a[g],!!d());g++){if(i._$host){i=i._$host,h();break}if(i===u||i.parentNode===u)break}}else h()}else h();l(s)}function It(e,t,n,r){if(t===n)return;const i=typeof t,o=r!==void 0;if(i==="string"||i==="number"){const s=typeof n;s==="string"||s==="number"?e.firstChild.data=t:e.textContent=t}else if(t===void 0)it(e,n,r);else if(t.nodeType)Array.isArray(n)?it(e,n,o?r:null,t):n&&n.nodeType?n.parentNode===e?e.replaceChild(t,n):e.appendChild(t):n&&e.firstChild?e.replaceChild(t,e.firstChild):e.appendChild(t),r&&(t[B]=r);else if(Array.isArray(t)){const s=n&&Array.isArray(n);t.length===0?it(e,n,r):s?n.length===0?pn(e,t,r):ai(e,n,t,r):(n&&it(e),pn(e,t))}}function xt(e,t,n,r){if(e=en(e,{skipNonRendered:!0,doNotUnwrap:r}),r&&typeof e=="function")return e;if(n&&!Array.isArray(e)&&(e=[e??""]),Array.isArray(e))for(let i=0,o=e.length;i<o;i++){const s=e[i],u=t&&t[i],l=typeof s;(l==="string"||l==="number")&&(e[i]=u&&u.nodeType===3&&u.data===""+s?u:document.createTextNode(s))}return e}function pn(e,t,n=null){for(let r=0,i=t.length;r<i;r++){const o=t[r];e.insertBefore(o,n),n&&(o[B]=n)}}function it(e,t,n,r){if(n===void 0)return e.textContent="";if(t.length){let i=!1;for(let o=t.length-1;o>=0;o--){const s=t[o];if(r!==s){const u=s[B],l=s.parentNode===e&&(!u||u===n);r&&!i&&!o?l?e.replaceChild(r,s):e.insertBefore(r,n):l&&s.remove()}else i=!0}}else r&&e.insertBefore(r,n);r&&n&&(r[B]=n)}const bi=!1;function vi(e,t,n,r={}){try{const i=fi(e,t,n,{...r,insertOptions:{schedule:!0}});return Ae(),i}finally{}}function zn(){let e=new Set;function t(i){return e.add(i),()=>e.delete(i)}let n=!1;function r(i,o){if(n)return!(n=!1);const s={to:i,options:o,defaultPrevented:!1,preventDefault:()=>s.defaultPrevented=!0};for(const u of e)u.listener({...s,from:u.location,retry:l=>{l&&(n=!0),u.navigate(i,{...o,resolve:!1})}});return!s.defaultPrevented}return{subscribe:t,confirm:r}}let Wt;function nn(){(!window.history.state||window.history.state._depth==null)&&window.history.replaceState({...window.history.state,_depth:window.history.length-1},""),Wt=window.history.state._depth}nn();function $i(e){return{...e,_depth:window.history.state&&window.history.state._depth}}function _i(e,t){let n=!1;return()=>{const r=Wt;nn();const i=r==null?null:Wt-r;if(n){n=!1;return}i&&t(i)?(n=!0,window.history.go(-i)):e()}}const Si=/^(?:[a-z0-9]+:)?\/\//i,Ei=/^\/+|(\/)\/+$/g,Gn="http://sr";function We(e,t=!1){const n=e.replace(Ei,"$1");return n?t||/^[?#]/.test(n)?n:"/"+n:""}function ut(e,t,n){if(Si.test(t))return;const r=We(e),i=n&&We(n);let o="";return!i||t.startsWith("/")?o=r:i.toLowerCase().indexOf(r.toLowerCase())!==0?o=r+i:o=i,(o||"/")+We(t,!o)}function Ci(e,t){if(e==null)throw new Error(t);return e}function Oi(e,t){return We(e).replace(/\/*(\*.*)?$/g,"")+We(t)}function Jn(e){const t={};return e.searchParams.forEach((n,r)=>{r in t?Array.isArray(t[r])?t[r].push(n):t[r]=[t[r],n]:t[r]=n}),t}function Ti(e,t,n){const[r,i]=e.split("/*",2),o=r.split("/").filter(Boolean),s=o.length;return u=>{const l=u.split("/").filter(Boolean),d=l.length-s;if(d<0||d>0&&i===void 0&&!t)return null;const h={path:s?"":"/",params:{}},a=g=>n===void 0?void 0:n[g];for(let g=0;g<s;g++){const m=o[g],p=m[0]===":",c=p?l[g]:l[g].toLowerCase(),f=p?m.slice(1):m.toLowerCase();if(p&&Lt(c,a(f)))h.params[f]=c;else if(p||!Lt(c,f))return null;h.path+=`/${c}`}if(i){const g=d?l.slice(-d).join("/"):"";if(Lt(g,a(i)))h.params[i]=g;else return null}return h}}function Lt(e,t){const n=r=>r===e;return t===void 0?!0:typeof t=="string"?n(t):typeof t=="function"?t(e):Array.isArray(t)?t.some(n):t instanceof RegExp?t.test(e):!1}function Ai(e){const[t,n]=e.pattern.split("/*",2),r=t.split("/").filter(Boolean);return r.reduce((i,o)=>i+(o.startsWith(":")?2:3),r.length-(n===void 0?0:1))}function Hn(e){const t=new Map,n=Ze();return new Proxy({},{get(r,i){return t.has(i)||se(n,()=>t.set(i,Z(()=>e()[i]))),t.get(i)()},getOwnPropertyDescriptor(){return{enumerable:!0,configurable:!0}},ownKeys(){return Reflect.ownKeys(e())},has(r,i){return i in e()}})}function Yn(e){let t=/(\/?\:[^\/]+)\?/.exec(e);if(!t)return[e];let n=e.slice(0,t.index),r=e.slice(t.index+t[0].length);const i=[n,n+=t[1]];for(;t=/^(\/\:[^\/]+)\?/.exec(r);)i.push(n+=t[1]),r=r.slice(t[0].length);return Yn(r).reduce((o,s)=>[...o,...i.map(u=>u+s)],[])}const Ri=100,Zn=jn(),Qn=jn();function Pi(e){try{return Kn(e)}catch{return}}const Xn=()=>Ci(Kn(Zn),"<A> and 'use' router primitives can be only used inside a Route."),er=()=>Xn().navigatorFactory(),ki=()=>Xn().params;function Ii(e,t=""){const{component:n,preload:r,children:i,info:o}=e,s=!i||Array.isArray(i)&&!i.length,u={key:e,component:n,preload:r,info:o};return tr(e.path).reduce((l,d)=>{for(const h of Yn(d)){const a=Oi(t,h);let g=s?a:a.split("/*",1)[0];g=g.split("/").map(m=>m.startsWith(":")||m.startsWith("*")?m:encodeURIComponent(m)).join("/"),l.push({...u,originalPath:d,pattern:g,matcher:Ti(g,!s,e.matchFilters)})}return l},[])}function xi(e,t=0){return{routes:e,score:Ai(e[e.length-1])*1e4-t,matcher(n){const r=[];for(let i=e.length-1;i>=0;i--){const o=e[i],s=o.matcher(n);if(!s)return null;r.unshift({...s,route:o})}return r}}}function tr(e){return Array.isArray(e)?e:[e]}function nr(e,t="",n=[],r=[]){const i=tr(e);for(let o=0,s=i.length;o<s;o++){const u=i[o];if(u&&typeof u=="object"){u.hasOwnProperty("path")||(u.path="");const l=Ii(u,t);for(const d of l){n.push(d);const h=Array.isArray(u.children)&&u.children.length===0;if(u.children&&!h)nr(u.children,d.pattern,n,r);else{const a=xi([...n],r.length);r.push(a)}n.pop()}}}return n.length?r:r.sort((o,s)=>s.score-o.score)}function Nt(e,t){for(let n=0,r=e.length;n<r;n++){const i=e[n].matcher(t);if(i)return i}return[]}function Li(e,t,n){const r=new URL(Gn),i=Z((h=r)=>{const a=e();try{return new URL(a,r)}catch{return console.error(`Invalid path ${a}`),h}},{equals:(h,a)=>h.href===a.href}),o=Z(()=>i().pathname),s=Z(()=>i().search),u=Z(()=>i().hash),l=()=>"",d=Z(()=>Jn(i()));return{get pathname(){return o()},get search(){return s()},get hash(){return u()},get state(){return t()},get key(){return l()},query:n?n(d):Hn(d)}}let me;function Ni(){return me}function qi(e,t,n,r={}){const{signal:[i,o],utils:s={}}=e,u=s.parsePath||(R=>R),l=s.renderPath||(R=>R),d=s.beforeLeave||zn(),h=ut("",r.base||""),a=ee(i);if(h===void 0)throw new Error(`${h} is not a valid base path`);h&&!a.value&&o({value:h,replace:!0,scroll:!1});const[g,m]=q(!1,{ownedWrite:!0}),[p,c]=q(void 0,{ownedWrite:!0});let f;const y=Z(()=>p()??i()),w=Li(()=>y().value,()=>y().state,s.queryWrapper),O=[],P=q([],{ownedWrite:!0}),x=Z(()=>typeof r.transformUrl=="function"?Nt(t(),r.transformUrl(w.pathname)):Nt(t(),w.pathname)),Y=()=>{const R=x(),L={};for(let F=0;F<R.length;F++)Object.assign(L,R[F].params);return L},Et=s.paramsWrapper?s.paramsWrapper(Y,t):Hn(Y),Xe={pattern:h,path:()=>h,outlet:()=>null,resolvePath(R){return ut(h,R)}};return{base:Xe,location:w,params:Et,isRouting:g,renderPath:l,parsePath:u,navigatorFactory:Ie,matches:x,beforeLeave:d,preloadRoute:et,singleFlight:r.singleFlight===void 0?!0:r.singleFlight,submissions:P};function Ct(R,L,F){ee(()=>{if(typeof L=="number"){L&&(s.go?s.go(L):console.warn("Router integration does not support relative routing"));return}const ge=!L||L[0]==="?",{replace:ye,resolve:re,scroll:pe,state:ie}={replace:!1,resolve:!ge,scroll:!0,...F},ae=re?R.resolvePath(L):ut(ge&&w.pathname||"",L);if(ae===void 0)throw new Error(`Path '${L}' is not a routable path`);if(O.length>=Ri)throw new Error("Too many redirects");const _=y();if((ae!==_.value||ie!==_.state)&&!bi){if(d.confirm(ae,F)){O.push({value:_.value,replace:ye,scroll:pe,state:_.state});const T={value:ae,state:ie};f===void 0&&(m(!0),Ae()),me="navigate",f=T,f===T&&(c({...f}),queueMicrotask(()=>{f===T&&(me=void 0,xe(f),c(void 0),m(!1),f=void 0)}))}}})}function Ie(R){return R=R||Pi(Qn)||Xe,(L,F)=>Ct(R,L,F)}function xe(R){const L=O[0];L&&(o({...R,replace:L.replace,scroll:L.scroll}),O.length=0)}function et(R,L){const F=Nt(t(),R.pathname),ge=me;me="preload";for(let ye in F){const{route:re,params:pe}=F[ye];re.component&&re.component.preload&&re.component.preload();const{preload:ie}=re;L&&ie&&se(n(),()=>ie({params:pe,location:{pathname:R.pathname,search:R.search,hash:R.hash,query:Jn(R),state:null,key:""},intent:"preload"}))}me=ge}}function Mi(e,t,n,r){const{base:i,location:o,params:s}=e,{pattern:u,component:l,preload:d}=r().route,h=Z(()=>r().path);l&&l.preload&&l.preload();const a=d?d({params:s,location:o,intent:me||"initial"}):void 0;return{parent:t,pattern:u,path:h,outlet:()=>l?I(l,{params:s,location:o,data:a,get children(){return n()}}):n(),resolvePath(m){return ut(i.path(),m,h())}}}const Di=e=>function(n){const{base:r,singleFlight:i,transformUrl:o,root:s,rootPreload:u,routeChildren:l}=ee(()=>({base:n.base,singleFlight:n.singleFlight,transformUrl:n.transformUrl,root:n.root,rootPreload:n.rootPreload,routeChildren:n.children})),d=tn(()=>l),h=Z(()=>nr(d(),r||""));let a;const g=qi(e,h,()=>a,{base:r,singleFlight:i,transformUrl:o});return e.create&&e.create(g),I(Zn,{value:g,get children(){return I(Fi,{routerState:g,root:s,preload:u,get children(){return[K(()=>(a=Ze())&&null),I(Bi,{routerState:g,get branches(){return h()}})]}})}})};function Fi(e){const t=e.routerState.location,n=e.routerState.params,r=Z(()=>e.preload&&ee(()=>{e.preload({params:n,location:t,intent:Ni()||"initial"})})),i=e.root;return i?I(i,{params:n,location:t,get data(){return r()},get children(){return e.children}}):e.children}function Bi(e){const t=[];let n,r;const i=Z(s=>{const u=e.routerState.matches(),l=r;let d=l&&u.length===l.length;const h=[];for(let a=0,g=u.length;a<g;a++){const m=l&&l[a],p=u[a];s&&m&&p.route.key===m.route.key?h[a]=s[a]:(d=!1,t[a]&&t[a](),Yt(c=>{t[a]=c,h[a]=Mi(e.routerState,h[a-1]||e.routerState.base,mn(()=>i()?.[a+1]),()=>{const f=e.routerState.matches();return f[a]??f[0]})}))}return t.splice(u.length).forEach(a=>a()),s&&d?(r=u,s):(n=h[0],r=u,h)}),o=mn(()=>i()&&n);return K(o)}const mn=e=>()=>{const t=e();if(t)return I(Qn,{value:t,get children(){return t.outlet()}})},wn=e=>{const t=tn(()=>e.children);return ri(e,{get children(){return t()}})};function ji(e){let t=!1;const n=s=>typeof s=="string"?{value:s}:s,[r,i]=q(n(e.get()),{equals:(s,u)=>s.value===u.value&&s.state===u.state,ownedWrite:!0}),o=[r,s=>{!t&&e.set(s),i(s)}];return e.init&&Fn(e.init((s=e.get())=>{t=!0,o[1](n(s)),t=!1})),Di({signal:o,create:e.create,utils:e.utils})}function Ki(e,t,n){return e.addEventListener(t,n),()=>e.removeEventListener(t,n)}function Ui(e,t){const n=e&&document.getElementById(e);n?n.scrollIntoView():t&&window.scrollTo(0,0)}const Wi=new Map;function Vi({preload:e=!0,explicitLinks:t=!1,actionBase:n="/_server",transformUrl:r}={}){return i=>{const o=i.base.path(),s=i.navigatorFactory(i.base);let u,l;function d(c){return c.namespaceURI==="http://www.w3.org/2000/svg"}function h(c){if(c.defaultPrevented||c.button!==0||c.metaKey||c.altKey||c.ctrlKey||c.shiftKey)return;const f=c.composedPath().find(Y=>Y instanceof Node&&Y.nodeName.toUpperCase()==="A");if(!f||t&&!f.hasAttribute("link"))return;const y=d(f),w=y?f.href.baseVal:f.href;if((y?f.target.baseVal:f.target)||!w&&!f.hasAttribute("state"))return;const P=(f.getAttribute("rel")||"").split(/\s+/);if(f.hasAttribute("download")||P&&P.includes("external"))return;const x=y?new URL(w,document.baseURI):new URL(w);if(!(x.origin!==window.location.origin||o&&x.pathname&&!x.pathname.toLowerCase().startsWith(o.toLowerCase())))return[f,x]}function a(c){const f=h(c);if(!f)return;const[y,w]=f,O=i.parsePath(w.pathname+w.search+w.hash),P=y.getAttribute("state");c.preventDefault(),s(O,{resolve:!1,replace:y.hasAttribute("replace"),scroll:!y.hasAttribute("noscroll"),state:P?JSON.parse(P):void 0})}function g(c){const f=h(c);if(!f)return;const[y,w]=f;r&&(w.pathname=r(w.pathname)),i.preloadRoute(w,y.getAttribute("preload")!=="false")}function m(c){clearTimeout(u);const f=h(c);if(!f)return l=null;const[y,w]=f;l!==y&&(r&&(w.pathname=r(w.pathname)),u=setTimeout(()=>{i.preloadRoute(w,y.getAttribute("preload")!=="false"),l=y},20))}function p(c){if(c.defaultPrevented)return;let f=c.submitter&&c.submitter.hasAttribute("formaction")?c.submitter.getAttribute("formaction"):c.target.getAttribute("action");if(!f)return;if(!f.startsWith("https://action/")){const w=new URL(f,Gn);if(f=i.parsePath(w.pathname+w.search),!f.startsWith(n))return}if(c.target.method.toUpperCase()!=="POST")throw new Error("Only POST forms are supported for Actions");const y=Wi.get(f);if(y){c.preventDefault();const w=new FormData(c.target,c.submitter);y.call({r:i,f:c.target},c.target.enctype==="multipart/form-data"?w:new URLSearchParams(w))}}Qe(["click","submit"]),document.addEventListener("click",a),e&&(document.addEventListener("mousemove",m,{passive:!0}),document.addEventListener("focusin",g,{passive:!0}),document.addEventListener("touchstart",g,{passive:!0})),document.addEventListener("submit",p),Fn(()=>{document.removeEventListener("click",a),e&&(document.removeEventListener("mousemove",m),document.removeEventListener("focusin",g),document.removeEventListener("touchstart",g)),document.removeEventListener("submit",p)})}}function zi(e){const t=()=>{const r=window.location.pathname.replace(/^\/+/,"/")+window.location.search,i=window.history.state&&window.history.state._depth&&Object.keys(window.history.state).length===1?void 0:window.history.state;return{value:r+window.location.hash,state:i}},n=zn();return ji({get:t,set({value:r,replace:i,scroll:o,state:s}){i?window.history.replaceState($i(s),"",r):window.history.pushState(s,"",r),Ui(decodeURIComponent(window.location.hash.slice(1)),o),nn()},init:r=>Ki(window,"popstate",_i(r,i=>{if(i)return!n.confirm(i);{const o=t();return!n.confirm(o.value,{state:o.state})}})),create:Vi({preload:e.preload,explicitLinks:e.explicitLinks,actionBase:e.actionBase,transformUrl:e.transformUrl}),utils:{go:r=>window.history.go(r),beforeLeave:n}})(e)}var Gi=A('<div class=chat><div class=chat-messages></div><div class=chat-input><input type=text placeholder="Type a message..."><button type=button>Send'),Ji=A("<span class=chat-user>"),Hi=A("<span class=chat-time>"),Yi=A("<div><div class=chat-content>");function Zi(e){let t,n;St(()=>e.messages.length,()=>{n&&n.lastElementChild&&n.lastElementChild.scrollIntoView({behavior:"instant"})});const[r,i]=q(""),o=()=>{const c=r().trim();c&&(e.onSend(c),i(""),t?.focus())},s=c=>{c.key==="Enter"&&!c.shiftKey&&(c.preventDefault(),o())},u=c=>{try{const f=new Date(c),y=Math.floor((Date.now()-f.getTime())/1e3);if(y<60)return"just now";const w=Math.floor(y/60);if(w<60)return`${w}m ago`;const O=Math.floor(w/60);return O<24?`${O}h ago`:f.toLocaleDateString([],{month:"short",day:"numeric"})}catch{return c}};var l=Gi(),d=l.firstChild,h=d.nextSibling,a=h.firstChild,g=a.nextSibling,m=n;typeof m=="function"||Array.isArray(m)?yt(()=>m,d):n=d,v(d,I(lt,{get each(){return e.messages},children:c=>(()=>{var f=Yi(),y=f.firstChild;return v(f,I(Un,{get when(){return c.msg_type!=="system"},get children(){return[(()=>{var w=Ji();return v(w,()=>c.user_name),w})(),(()=>{var w=Hi();return v(w,()=>u(c.created_at)),w})()]}}),y),v(y,()=>c.content),Q(()=>`chat-message${c.msg_type==="system"?" system":""}`,(w,O)=>{Je(f,w,O)}),f})()})),a.$$keydown=s,a.$$input=c=>i(c.currentTarget.value);var p=t;return typeof p=="function"||Array.isArray(p)?yt(()=>p,a):t=a,g.$$click=o,Q(()=>({e:r(),t:!r().trim()}),({e:c,t:f},y)=>{a.value=c??"",f!==y?.t&&he(g,"disabled",f)}),l}Qe(["input","keydown","click"]);function Qi(e){return!e||typeof e!="object"?null:"room_name"in e&&typeof e.room_name=="string"?{kind:"state",data:e}:"user_name"in e&&"content"in e?{kind:"chat",data:e}:null}function Xi(e,t,n){const i=`${location.protocol==="https:"?"wss:":"ws:"}//${location.host}/api/v1/ws/${e}?name=${encodeURIComponent(t)}`;let o=new WebSocket(i),s=[],u=!1;function l(d){o?.readyState===WebSocket.OPEN?o.send(d):s.push(d)}return o.onopen=()=>{u=!0;for(const d of s)o?.send(d);s=[]},o.onmessage=d=>{if(typeof d.data=="string")try{const h=JSON.parse(d.data),a=Qi(h);a?.kind==="state"?n.onState(a.data):a?.kind==="chat"&&n.onChat(a.data)}catch{}},o.onclose=d=>{o=null,u?d.code!==1e3&&d.code!==1001&&n.onError(`Connection lost (code: ${d.code})`):n.onError("Failed to connect to room"),n.onDisconnect()},o.onerror=()=>{o?.close()},d=>{l(JSON.stringify(d))}}function rr([e,t],n={}){St(()=>e(),r=>{if(!r||n.unless?.(r))return;const i=setTimeout(()=>t(null),n.ms??5e3);return()=>clearTimeout(i)})}function eo(e,t){const[n,r]=q(null),[i,o]=q([]),[s,u]=q(!1),[l,d]=q(null),[h,a]=q(null),g={current:!0},m=()=>{if(h())return;const c=Xi(e,t,{onState:f=>{g.current=!1,r(f)},onChat:f=>o(y=>{const w=[...y,f];return w.length>200?w.slice(-200):w}),onDisconnect:()=>{u(!1),a(null)},onError:f=>{d(f)}});a(()=>c),u(!0),d(null)};return ei(()=>{m(),setTimeout(()=>{g.current&&d("Room not found or unavailable")},8e3)}),St(()=>[s(),h()],([c,f])=>{if(!c&&!f){const y=setTimeout(m,3e3);return()=>clearTimeout(y)}}),rr([l,d],{unless:c=>c.includes("not found")}),{state:n,chats:i,connected:s,error:l,setError:d,send:c=>h()?.(c)}}var to=A("<div class=player>"),no=A("<div class=player-idle>Paste a URL to start watching together"),ro=A("<div class=player-info><div class=player-meta><strong class=player-title></strong><span class=player-duration-row><span class=duration-label></span><span class=elapsed-label>"),io=A("<div class=player-media-wrap><div class=player-media></div><button type=button>"),oo=A("<img class=player-thumb alt>"),so=A("<video controls><track kind=captions>"),lo=A("<audio controls><track kind=captions>");const ir="moqbox-volume",bn=()=>parseFloat(localStorage.getItem(ir)||"0.5");function uo(e){const t=Math.round(e.volume*100)/100;localStorage.setItem(ir,t.toString())}function co(e){const t=e.url.split(".").pop()?.toLowerCase();return t?["mp3","m4a","ogg","wav","flac","aac"].includes(t)?!1:(["mp4","webm","mov","mkv","avi","m4v"].includes(t),!0):!0}function qt(e,t){const n=(Date.now()-t)/1e3;e.currentTime=n}function ao(e){let t;const[n,r]=q(!0),i=e.roomId,o=a=>{t&&t.removeEventListener("volumechange",s),t=a??void 0,a&&(a.volume=bn(),a.addEventListener("volumechange",s))},s=()=>{const a=t;a&&uo(a)};let u=null;St(()=>{const a=e.track;return u=a,a?.started_at?a.id:null},(a,g)=>{if(a===g)return;const m=u,p=t;if(!p||!m?.started_at){p&&p.pause(),r(!0);return}const c=m.started_at,f=`/api/v1/rooms/${i}/media/${m.hash}`;p.src=f,p.volume=bn();let y;const w=()=>{qt(p,c);const P=()=>{r(!0),p.play().catch(()=>{}),y=()=>{const x=u?.started_at;if(!x)return;const Y=(Date.now()-x)/1e3;r(Math.abs(p.currentTime-Y)<.5)},p.addEventListener("timeupdate",y),p.addEventListener("pause",O)};p.addEventListener("seeked",P,{once:!0})};p.addEventListener("loadedmetadata",w,{once:!0}),p.addEventListener("canplay",w,{once:!0});const O=()=>{p.ended||qt(p,c)};return()=>{p.removeEventListener("loadedmetadata",w),p.removeEventListener("canplay",w),y&&p.removeEventListener("timeupdate",y),p.removeEventListener("pause",O)}});const l=a=>{if(a<1)return"--:--";const g=Math.floor(a/1e3),m=Math.floor(g/60),p=g%60;return`${m}:${p.toString().padStart(2,"0")}`},d=()=>{const a=t,g=e.track?.started_at;a&&g&&(qt(a,g),r(!0))};var h=to();return v(h,I(Un,{get when(){return e.track},get fallback(){return no()},children:a=>{const g=a();return[(()=>{var m=ro(),p=m.firstChild,c=p.firstChild,f=c.nextSibling,y=f.firstChild,w=y.nextSibling;return v(m,(()=>{var O=K(()=>!!g.thumbnail);return()=>O()&&(()=>{var P=oo();return P.addEventListener("error",x=>x.currentTarget.hidden=!0),Q(()=>g.thumbnail,x=>{he(P,"src",x)}),P})()})(),p),v(c,()=>g.title),v(y,()=>g.duration),v(w,()=>l(Date.now()-g.started_at)),m})(),(()=>{var m=io(),p=m.firstChild,c=p.nextSibling;return v(p,(()=>{var f=K(()=>!!co(g));return()=>f()?(()=>{var y=so();return gn(y,"ended",e.onEnded),yt(()=>o,y),y})():(()=>{var y=lo();return gn(y,"ended",e.onEnded),yt(()=>o,y),y})()})()),c.$$click=d,v(c,()=>n()?"Live":"Go live"),Q(()=>["live-btn",n()&&"live-btn--on"],(f,y)=>{Je(c,f,y)}),m})()]}})),h}Qe(["click"]);var fo=A("<div><span class=queue-title></span><span class=queue-duration>"),ho=A("<img class=queue-thumb alt>"),go=A("<div class=queue-panel><div class=queue-section><h3>Now Playing</h3></div><div class=queue-section><h3>Up Next (<!>)</h3></div><div class=queue-section><h3>Playlist (<!>)<label class=playlist-toggle><input type=checkbox><span>Autoplay</span></label></h3></div><div class=queue-section><h3>Recently Played"),yo=A("<div class=queue-empty>Paste a URL to start watching together"),po=A("<div class=queue-empty>Tracks you add will appear here"),mo=A('<div class=queue-item-row><button type=button class="queue-item-action queue-item-remove"title="Remove from playlist">×'),wo=A("<div class=queue-empty>Add links to build a perpetual playlist"),bo=A('<div class=queue-item-row><button type=button class=queue-item-action title="Add to playlist">+'),vo=A("<div class=queue-empty>Recently played tracks show up here");function ot(e){var t=fo(),n=t.firstChild,r=n.nextSibling;return v(t,(()=>{var i=K(()=>!!e.thumbnail);return()=>i()&&(()=>{var o=ho();return o.addEventListener("error",s=>s.currentTarget.hidden=!0),Q(()=>e.thumbnail,s=>{he(o,"src",s)}),o})()})(),n),v(n,()=>e.pending?"⏳ ":"",null),v(n,()=>e.title,null),v(r,()=>e.duration),Q(()=>`queue-item${e.extraClass?" "+e.extraClass:""}`,(i,o)=>{Je(t,i,o)}),t}function $o(e){var t=go(),n=t.firstChild;n.firstChild;var r=n.nextSibling,i=r.firstChild,o=i.firstChild,s=o.nextSibling;s.nextSibling;var u=r.nextSibling,l=u.firstChild,d=l.firstChild,h=d.nextSibling,a=h.nextSibling,g=a.nextSibling,m=g.firstChild,p=u.nextSibling;return p.firstChild,v(n,(()=>{var c=K(()=>!!e.currentTrack);return()=>c()?I(ot,{get title(){return e.currentTrack.title},get duration(){return e.currentTrack.duration},get thumbnail(){return e.currentTrack.thumbnail},extraClass:"current"}):yo()})(),null),v(i,()=>e.queue.length,s),v(r,I(lt,{get each(){return e.queue},children:c=>I(ot,{get title(){return c.title},get duration(){return c.duration},get thumbnail(){return c.thumbnail},get pending(){return c.pending}})}),null),v(r,(()=>{var c=K(()=>e.queue.length===0);return()=>c()&&po()})(),null),v(l,()=>e.playlist.length,h),m.addEventListener("change",c=>e.onTogglePlaylist?.(c.currentTarget.checked)),v(u,I(lt,{get each(){return e.playlist},children:c=>(()=>{var f=mo(),y=f.firstChild;return v(f,I(ot,{get title(){return c.title},get duration(){return c.duration},get thumbnail(){return c.thumbnail},get pending(){return c.pending}}),y),y.$$click=()=>e.onRemoveFromPlaylist?.(c.id),f})()}),null),v(u,(()=>{var c=K(()=>e.playlist.length===0);return()=>c()&&wo()})(),null),v(p,I(lt,{get each(){return e.history},children:c=>(()=>{var f=bo(),y=f.firstChild;return v(f,I(ot,{get title(){return c.title},get duration(){return c.duration},get thumbnail(){return c.thumbnail},extraClass:"history"}),y),y.$$click=()=>e.onAddToPlaylist?.(c.url),f})()}),null),v(p,(()=>{var c=K(()=>e.history.length===0);return()=>c()&&vo()})(),null),Q(()=>({e:e.playlistEnabled,t:e.playlist.length===0}),({e:c,t:f},y)=>{m.checked=c,f!==y?.t&&he(m,"disabled",f)}),t}Qe(["click"]);const or="moqbox-theme";function sr(e){const t=document.documentElement;e==="latte"?(t.setAttribute("data-theme","latte"),t.style.colorScheme="light"):e==="macchiato"?(t.setAttribute("data-theme","macchiato"),t.style.colorScheme="dark"):(t.removeAttribute("data-theme"),t.style.colorScheme="light dark"),localStorage.setItem(or,e)}function _o(e){return e==="macchiato"?"latte":e==="latte"?"system":"macchiato"}function lr(){return localStorage.getItem(or)||"system"}var So=A('<div class=home><h1>moqbox</h1><p>Watch videos together with friends</p><div class=home-form><input type=text placeholder="Your name"><button type=button>'),Eo=A('<div class="toast toast-error"role=alert>'),Co=A("<div class=room><header class=room-header><h2></h2><span class=client-count></span></header><div class=room-layout><div class=room-main><div class=room-actions><button type=button></button><button type=button class=secondary>Skip</button></div></div><div class=room-sidebar><div class=room-footer><button type=button class=theme-toggle-sm>"),Oo=A("<span class=disconnected>Disconnected"),To=A('<div class="toast toast-error"role=alert><span></span><button type=button class=toast-action>Go back'),Ao=A('<div class=inline-form><input type=text placeholder="Paste a YouTube / media URL..."><div class=add-target-toggle><button type=button>Queue</button><button type=button>Playlist</button></div><button type=button>');const Ro=()=>{const e=er(),[t,n]=q(""),[r,i]=q(!1),[o,s]=q(null);rr([o,s]);const u=async()=>{if(t().trim()){i(!0);try{const p=await fetch("/api/v1/rooms",{method:"POST"});if(!p.ok)throw new Error(`create room: ${p.status}`);const c=await p.json();e(`/room/${c.room_id}?name=${encodeURIComponent(t().trim())}`)}catch(p){console.error("Failed to create room:",p),s("Failed to create room"),i(!1)}}};var l=So(),d=l.firstChild,h=d.nextSibling,a=h.nextSibling,g=a.firstChild,m=g.nextSibling;return g.$$keydown=p=>p.key==="Enter"&&u(),g.$$input=p=>n(p.currentTarget.value),m.$$click=u,v(m,()=>r()?"Creating...":"Create Room"),v(l,(()=>{var p=K(()=>!!o());return()=>p()&&(()=>{var c=Eo();return v(c,o),c})()})(),null),Q(()=>({e:t(),t:r()||!t().trim()}),({e:p,t:c},f)=>{g.value=p??"",c!==f?.t&&he(m,"disabled",c)}),l},Po=()=>{const e=ki(),t=ee(()=>e.id),r=new URLSearchParams(location.search).get("name")||"anonymous",i=er(),[o,s]=q(lr()),u=()=>{const _=_o(o());sr(_),s(_)},{state:l,chats:d,connected:h,error:a,setError:g,send:m}=eo(t,r),[p,c]=q(!1),[f,y]=q(""),[w,O]=q("queue"),[P,x]=q(!1),Y=async()=>{const _=f().trim();if(_){x(!0);try{const T=w()==="queue"?"/api/v1/ingest":"/api/v1/playlist/add",N=await fetch(T,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:_,room_id:t})});if(!N.ok){if(N.status===429){const oe=N.headers.get("Retry-After");g(oe?`Too fast — try again in ${oe}s`:"Too fast — try again in a moment")}else{const oe=await N.json().catch(()=>({error:N.statusText}));g(oe.error||`${w()} failed`)}return}y(""),c(!1),g(null)}catch(T){g(String(T))}finally{x(!1)}}},Et=async _=>{try{const T=await fetch("/api/v1/playlist/add",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:_,room_id:t})});if(!T.ok){const N=await T.json().catch(()=>({error:T.statusText}));g(N.error||"Failed to add to playlist")}}catch(T){g(String(T))}},Xe=async _=>{try{const T=await fetch("/api/v1/playlist/remove",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({room_id:t,id:_})});if(!T.ok){const N=await T.json().catch(()=>({error:T.statusText}));g(N.error||"Failed to remove from playlist")}}catch(T){g(String(T))}},Ct=_=>{m({type:"set_playlist_enabled",enabled:_})};var Ie=Co(),xe=Ie.firstChild,et=xe.firstChild,R=et.nextSibling,L=xe.nextSibling,F=L.firstChild,ge=F.firstChild,ye=ge.firstChild,re=ye.nextSibling,pe=F.nextSibling,ie=pe.firstChild,ae=ie.firstChild;return v(et,()=>l()?.room_name??"Loading..."),v(R,(()=>{var _=K(()=>l()?.clients!=null);return()=>_()?`${l()?.clients} connected`:""})()),v(xe,(()=>{var _=K(()=>!h());return()=>_()&&Oo()})(),null),v(Ie,(()=>{var _=K(()=>!!a());return()=>_()&&(()=>{var T=To(),N=T.firstChild,oe=N.nextSibling;return v(N,a),oe.$$click=()=>i("/"),T})()})(),L),v(F,I(ao,{get track(){return l()?.current_track??null},roomId:t,onEnded:()=>{const _=l()?.current_track;_&&m({type:"track_ended",item_id:_.id})}}),ge),ye.$$click=()=>{c(!p())},v(ye,()=>p()?"Cancel":"Add"),re.$$click=()=>m({type:"skip"}),v(F,(()=>{var _=K(()=>!!p());return()=>_()&&(()=>{var T=Ao(),N=T.firstChild,oe=N.nextSibling,Ot=oe.firstChild,rn=Ot.nextSibling,Tt=oe.nextSibling;return N.$$keydown=Le=>Le.key==="Enter"&&Y(),N.$$input=Le=>y(Le.currentTarget.value),Ot.$$click=()=>O("queue"),rn.$$click=()=>O("playlist"),Tt.$$click=Y,v(Tt,()=>P()?"Adding...":"Add"),Q(()=>({e:f(),t:w()==="queue"?"active":"",a:w()==="playlist"?"active":"",o:P()||!f().trim()}),({e:Le,t:ur,a:cr,o:on},At)=>{N.value=Le??"",Je(Ot,ur,At?.t),Je(rn,cr,At?.a),on!==At?.o&&he(Tt,"disabled",on)}),T})()})(),null),v(pe,I($o,{get currentTrack(){return l()?.current_track??null},get queue(){return l()?.queue??[]},get history(){return l()?.history??[]},get playlist(){return l()?.playlist??[]},get playlistEnabled(){return l()?.playlist_enabled??!0},onAddToPlaylist:Et,onRemoveFromPlaylist:Xe,onTogglePlaylist:Ct}),ie),v(pe,I(Zi,{get messages(){return d()},onSend:_=>m({type:"chat",content:_})}),ie),ae.$$click=u,v(ae,(()=>{var _=K(()=>o()==="macchiato");return()=>_()?"Dark":o()==="latte"?"Light":"System"})()),Q(()=>({e:!l()?.current_track,t:`Theme: ${o()}`}),({e:_,t:T},N)=>{_!==N?.e&&he(re,"disabled",_),T!==N?.t&&he(ae,"title",T)}),Ie},ko=()=>(sr(lr()),I(zi,{get children(){return[I(wn,{path:"/",component:Ro}),I(wn,{path:"/room/:id",component:Po})]}}));Qe(["input","keydown","click"]);const vn=document.getElementById("root");vn&&vi(()=>I(ko,{}),vn);
+1 -1
static/dist/index.html
··· 10 10 if (t === "latte") { document.documentElement.style.colorScheme = "light"; } 11 11 else if (t === "macchiato") { document.documentElement.setAttribute("data-theme", "macchiato"); document.documentElement.style.colorScheme = "dark"; } 12 12 </script> 13 - <script type="module" crossorigin src="/assets/index-OCnj7PcQ.js"></script> 13 + <script type="module" crossorigin src="/assets/index-dTCvdWLw.js"></script> 14 14 <link rel="stylesheet" crossorigin href="/assets/index-DGSP7qCt.css"> 15 15 </head> 16 16 <body>