Translated using Weblate (Finnish)

Currently translated at 22.6% (144 of 635 strings)

update overlay

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Pas <74743263+Pasithea0@users.noreply.github.com>
Translate-URL: http://weblate.pstream.org/projects/p-stream/p-stream/fi/
Translation: P-Stream/p-stream
This commit is contained in:
Weblate 2025-05-22 16:39:06 +00:00
parent 3af0a8aaa2
commit e29d485c04
2 changed files with 417 additions and 267 deletions

View file

@ -1,288 +1,291 @@
{
"about": {
"description": "P-Stream on verkkosovellus, joka etsii suoratoistoja internetistä. Tiimi pyrkii enimmäkseen minimalistiseen lähestymistapaan sisällön kuluttamiseen.",
"faqTitle": "Yleisiä kysymyksiä",
"q1": {
"body": "P-Stream ei isännöi mitään sisältöä. Kun klikkaat jotain katsottavaa, valittua mediaa etsitään internetistä (latausnäytössä ja 'videolähteet'-välilehdellä näet, mitä lähdettä käytät). P-Stream ei koskaan lataa mediaa, kaikki tapahtuu tämän hakumekanismin kautta.",
"title": "Mistä sisältö tulee?"
"about": {
"description": "P-Stream on verkkosovellus, joka etsii suoratoistoja internetistä. Tiimi pyrkii enimmäkseen minimalistiseen lähestymistapaan sisällön kuluttamiseen.",
"faqTitle": "Yleisiä kysymyksiä",
"q1": {
"body": "P-Stream ei isännöi mitään sisältöä. Kun klikkaat jotain katsottavaa, valittua mediaa etsitään internetistä (latausnäytössä ja 'videolähteet'-välilehdellä näet, mitä lähdettä käytät). P-Stream ei koskaan lataa mediaa, kaikki tapahtuu tämän hakumekanismin kautta.",
"title": "Mistä sisältö tulee?"
},
"q2": {
"body": "Ohjelmaa tai elokuvaa ei voi pyytää, P-Stream ei hallinnoi sisältöä. Kaikki sisältö katsotaan internetin lähteistä.",
"title": "Missä voin pyytää sarjaa tai elokuvaa?"
},
"q3": {
"body": "Hakutuloksemme perustuvat The Movie Database (TMDB) -tietokantaan, ja ne näkyvät riippumatta siitä, onko sisältöä oikeasti lähteissämme.",
"title": "Hakutuloksissa näytetään ohjelma tai elokuva. Miksi en voi toistaa sitä?"
},
"title": "Tietoja P-Streamistä"
},
"q2": {
"body": "Ohjelmaa tai elokuvaa ei voi pyytää, P-Stream ei hallinnoi sisältöä. Kaikki sisältö katsotaan internetin lähteistä.",
"title": "Missä voin pyytää sarjaa tai elokuvaa?"
"actions": {
"copied": "Kopioitu",
"copy": "Kopioi"
},
"q3": {
"body": "Hakutuloksemme perustuvat The Movie Database (TMDB) -tietokantaan, ja ne näkyvät riippumatta siitä, onko sisältöä oikeasti lähteissämme.",
"title": "Hakutuloksissa näytetään ohjelma tai elokuva. Miksi en voi toistaa sitä?"
"auth": {
"createAccount": "Eikö sinulla ole vielä tiliä? <0>Luo tili.</0>",
"deviceNameLabel": "Laitteen nimi",
"deviceNamePlaceholder": "Henkilökohtainen puhelin",
"generate": {
"description": "Tunnuslauseesi toimii käyttäjätunnuksena ja salasanana. Varmista, että pidät sen turvassa, sillä sinun on annettava se kirjautuaksesi tilillesi",
"next": "Olen tallentanut tunnuslauseeni",
"passphraseFrameLabel": "Tunnuslause",
"title": "Sinun tunnuslause"
},
"hasAccount": "Onko sinulla jo tili? <0>Kirjaudu sisään tästä.</0>",
"login": {
"description": "Anna tunnuslauseesi kirjautuaksesi tilillesi",
"deviceLengthError": "Anna laitteen nimi",
"passphraseLabel": "12-sanainen tunnuslause",
"passphrasePlaceholder": "Tunnuslause",
"submit": "Kirjaudu sisään",
"title": "Kirjaudu tilillesi",
"validationError": "Väärä tai puutteellinen tunnuslause"
},
"register": {
"information": {
"color1": "Profiilin väri yksi",
"color2": "Profiilin väri kaksi",
"header": "Anna laitteellesi nimi ja valitse haluamasi värit ja käyttäjäkuvake",
"icon": "Käyttäjäkuvake",
"next": "Seuraava",
"title": "Tilitiedot"
}
},
"trust": {
"failed": {
"text": "Oletko määrittänyt sen oikein?",
"title": "Palvelimeen ei saada yhteyttä"
},
"host": "Olet muodostamassa yhteyttä <0>{{hostname}}</0> - vahvista, että luotat siihen ennen kuin luot tilin",
"no": "Mene takaisin",
"title": "Luotatko tähän palvelimeen?",
"yes": "Luotan tähän palvelimeen"
},
"verify": {
"description": "Anna aikaisemmin saamasi tunnuslause vahvistaaksesi, että olet tallentanut sen ja luodaksesi tilisi",
"invalidData": "Tiedot eivät kelpaa",
"noMatch": "Tunnuslause ei täsmää",
"passphraseLabel": "12-sanainen tunnuslauseesi",
"recaptchaFailed": "ReCaptcha-tarkistus epäonnistui",
"register": "Luo tili",
"title": "Vahvista tunnuslauseesi"
}
},
"title": "Tietoja P-Streamistä"
},
"actions": {
"copied": "Kopioitu",
"copy": "Kopioi"
},
"auth": {
"createAccount": "Eikö sinulla ole vielä tiliä? <0>Luo tili.</0>",
"deviceNameLabel": "Laitteen nimi",
"deviceNamePlaceholder": "Henkilökohtainen puhelin",
"generate": {
"description": "Tunnuslauseesi toimii käyttäjätunnuksena ja salasanana. Varmista, että pidät sen turvassa, sillä sinun on annettava se kirjautuaksesi tilillesi",
"next": "Olen tallentanut tunnuslauseeni",
"passphraseFrameLabel": "Tunnuslause",
"title": "Sinun tunnuslause"
"errors": {
"badge": "Se hajosi",
"details": "Virheen tiedot",
"reloadPage": "Lataa sivu uudelleen",
"showError": "Näytä virheen tiedot",
"title": "Havaitsimme virheen!"
},
"hasAccount": "Onko sinulla jo tili? <0>Kirjaudu sisään tästä.</0>",
"login": {
"description": "Anna tunnuslauseesi kirjautuaksesi tilillesi",
"deviceLengthError": "Anna laitteen nimi",
"passphraseLabel": "12-sanainen tunnuslause",
"passphrasePlaceholder": "Tunnuslause",
"submit": "Kirjaudu sisään",
"title": "Kirjaudu tilillesi",
"validationError": "Väärä tai puutteellinen tunnuslause"
"footer": {
"legal": {
"disclaimer": "Vastuuvapauslauseke",
"disclaimerText": "P-Stream ei isännöi tiedostoja, se vain linkittää kolmannen osapuolen palveluihin. Lakiasioista tulee ottaa yhteyttä tiedostoisäntään ja palveluntarjoajiin. P-Stream ei ole vastuussa videontarjoajien näyttämistä mediatiedostoista."
},
"links": {
"discord": "Discord",
"dmca": "DMCA",
"github": "GitHub"
},
"tagline": "Katso suosikkiohjelmiasi ja elokuviasi tällä avoimen lähdekoodin suoratoistosovelluksella."
},
"register": {
"information": {
"color1": "Profiilin väri yksi",
"color2": "Profiilin väri kaksi",
"header": "Anna laitteellesi nimi ja valitse haluamasi värit ja käyttäjäkuvake",
"icon": "Käyttäjäkuvake",
"next": "Seuraava",
"title": "Tilitiedot"
}
"global": {
"name": "P-Stream",
"pages": {
"about": "Meistä",
"dmca": "DMCA",
"login": "Kirjaudu sisään",
"onboarding": "asetus",
"pagetitle": "{{title}} - P-Stream",
"register": "Rekisteröidy",
"settings": "Asetukset"
}
},
"trust": {
"failed": {
"text": "Oletko määrittänyt sen oikein?",
"title": "Palvelimeen ei saada yhteyttä"
},
"host": "Olet muodostamassa yhteyttä <0>{{hostname}}</0> - vahvista, että luotat siihen ennen kuin luot tilin",
"no": "Mene takaisin",
"title": "Luotatko tähän palvelimeen?",
"yes": "Luotan tähän palvelimeen"
"home": {
"bookmarks": {
"sectionTitle": "Kirjanmerkit"
},
"continueWatching": {
"sectionTitle": "Jatka katselua"
},
"mediaList": {
"stopEditing": "Lopeta muokkaaminen"
},
"search": {
"allResults": "Siinä kaikki mitä meillä on!",
"failed": "Mediaa ei löytynyt, yritä uudelleen!",
"loading": "Ladataan...",
"noResults": "Emme löytäneet mitään!",
"placeholder": {
"default": "Mitä haluat katsoa?"
},
"sectionTitle": "Hakutulokset"
},
"titles": {
"day": {
"default": "Mitä haluaisit katsoa tänä iltapäivänä?",
"extra": [
"Onko seikkailunhaluinen olo? Jurassic Park saattaa olla täydellinen valinta."
]
},
"morning": {
"default": "Mitä haluaisit katsoa tänä aamuna?",
"extra": [
"Kuulen, että Rakkautta ennen aamua (Before Sunrise) on hyvä"
]
},
"night": {
"default": "Mitä haluaisit katsoa tänä iltana?",
"extra": [
"Väsynyt? Kuulin, että Manaaja (The Exorcist) on hyvä."
]
}
}
},
"verify": {
"description": "Anna aikaisemmin saamasi tunnuslause vahvistaaksesi, että olet tallentanut sen ja luodaksesi tilisi",
"invalidData": "Tiedot eivät kelpaa",
"noMatch": "Tunnuslause ei täsmää",
"passphraseLabel": "12-sanainen tunnuslauseesi",
"recaptchaFailed": "ReCaptcha-tarkistus epäonnistui",
"register": "Luo tili",
"title": "Vahvista tunnuslauseesi"
}
},
"errors": {
"badge": "Se hajosi",
"details": "Virheen tiedot",
"reloadPage": "Lataa sivu uudelleen",
"showError": "Näytä virheen tiedot",
"title": "Havaitsimme virheen!"
},
"footer": {
"legal": {
"disclaimer": "Vastuuvapauslauseke",
"disclaimerText": "P-Stream ei isännöi tiedostoja, se vain linkittää kolmannen osapuolen palveluihin. Lakiasioista tulee ottaa yhteyttä tiedostoisäntään ja palveluntarjoajiin. P-Stream ei ole vastuussa videontarjoajien näyttämistä mediatiedostoista."
"media": {
"episodeDisplay": "K{{season}} J{{episode}}",
"types": {
"movie": "Elokuva",
"show": "Sarja"
}
},
"links": {
"discord": "Discord",
"dmca": "DMCA",
"github": "GitHub"
"navigation": {
"banner": {
"offline": "Tarkista Internet-yhteytesi"
},
"menu": {
"about": "Meistä",
"logout": "Kirjaudu ulos",
"register": "Synkronoi pilveen",
"settings": "Asetukset",
"support": "Tuki"
}
},
"tagline": "Katso suosikkiohjelmiasi ja elokuviasi tällä avoimen lähdekoodin suoratoistosovelluksella."
},
"global": {
"name": "P-Stream",
"pages": {
"about": "Meistä",
"dmca": "DMCA",
"login": "Kirjaudu sisään",
"onboarding": "asetus",
"pagetitle": "{{title}} - P-Stream",
"register": "Rekisteröidy",
"settings": "Asetukset"
}
},
"home": {
"bookmarks": {
"sectionTitle": "Kirjanmerkit"
"notFound": {
"badge": "Ei löydetty",
"goHome": "Takaisin kotiin",
"message": "Etsimme kaikkialta: roskakorien alta, kaapista, välityspalvelimen takaa, mutta emme lopulta löytäneet etsimääsi sivua.",
"title": "Sivua ei löytynyt"
},
"continueWatching": {
"sectionTitle": "Jatka katselua"
},
"mediaList": {
"stopEditing": "Lopeta muokkaaminen"
},
"search": {
"allResults": "Siinä kaikki mitä meillä on!",
"failed": "Mediaa ei löytynyt, yritä uudelleen!",
"loading": "Ladataan...",
"noResults": "Emme löytäneet mitään!",
"placeholder": {
"default": "Mitä haluat katsoa?",
"extra": []
},
"sectionTitle": "Hakutulokset"
},
"titles": {
"day": {
"default": "Mitä haluaisit katsoa tänä iltapäivänä?",
"extra": [
"Onko seikkailunhaluinen olo? Jurassic Park saattaa olla täydellinen valinta."
]
},
"morning": {
"default": "Mitä haluaisit katsoa tänä aamuna?",
"extra": ["Kuulen, että Rakkautta ennen aamua (Before Sunrise) on hyvä"]
},
"night": {
"default": "Mitä haluaisit katsoa tänä iltana?",
"extra": ["Väsynyt? Kuulin, että Manaaja (The Exorcist) on hyvä."]
}
}
},
"media": {
"episodeDisplay": "K{{season}} J{{episode}}",
"types": {
"movie": "Elokuva",
"show": "Sarja"
}
},
"navigation": {
"banner": {
"offline": "Tarkista Internet-yhteytesi"
},
"menu": {
"about": "Meistä",
"logout": "Kirjaudu ulos",
"register": "Synkronoi pilveen",
"settings": "Asetukset",
"support": "Tuki"
}
},
"notFound": {
"badge": "Ei löydetty",
"goHome": "Takaisin kotiin",
"message": "Etsimme kaikkialta: roskakorien alta, kaapista, välityspalvelimen takaa, mutta emme lopulta löytäneet etsimääsi sivua.",
"title": "Sivua ei löytynyt"
},
"onboarding": {
"defaultConfirm": {
"cancel": "Peruuta",
"confirm": "Käytä oletuksia",
"description": "Oletuksissa ei ole parhaita suoratoistoja ja se voi olla sietämättömän hidasta.",
"title": "Oletko varma?"
},
"extension": {
"back": "Mene takaisin",
"explainer": "Käyttämällä selainlaajennusta voit saada parhaat tarjoamamme suoratoistot. Yksinkertaisella asennuksella.",
"explainerIos": "Valitettavasti selainlaajennusta ei tueta iOS:ssä. Valitse toinen vaihtoehto painamalla <bold>Palaa</bold>.",
"extensionHelp": "Jos olet asentanut laajennuksen, mutta se ei havaitse sitä, <bold>avaa laajennus selaimen laajennusvalikosta</bold> ja noudata näytön ohjeita.",
"linkChrome": "Asenna Chromen laajennus",
"linkFirefox": "Asenna Firefoxin laajennus",
"notDetecting": "Asensitko sen Chromelle, mutta sivusto ei havaitse sitä? Kokeile ladata sivu uudelleen!",
"notDetectingAction": "Lataa sivu uudelleen",
"status": {
"disallowed": "Laajennus ei ole otettu käyttöön tälle sivulle",
"disallowedAction": "Ota laajennus käyttöön",
"failed": "Pyynnön tilan hakeminen epäonnistui",
"loading": "Odottaa, että asennat laajennuksen",
"outdated": "Laajennuksen versio on liian vanha",
"success": "Laajennus toimii odotetusti!"
},
"submit": "Jatketaan",
"title": "Aloitetaan laajennuksella"
},
"proxy": {
"back": "Mene takaisin",
"explainer": "Proxy-menetelmällä voit saada erinomaisen laadukkaita suoratoistoja luomalla itsepalveluvaltuutetun proxyn.",
"input": {
"errorConnection": "Yhteys proxyn kanssa ei onnistunut",
"errorInvalidUrl": "Ei kelvollinen URL-osoite",
"errorNotProxy": "Odotettiin proxya, mutta saatiinkin verkkosivu",
"label": "Proxyn URL-osoite",
"placeholder": "https://"
},
"link": "Opi luomaan proxy",
"submit": "Toimita proxy",
"title": "Luodaan uusi proxy"
},
"start": {
"explainer": "Parhaiden suoratoistojen saamiseksi sinun täytyy valita, mitä suoratoistomenetelmää haluat käyttää.",
"options": {
"default": {
"text": "En halua hyvälaatuisia suoratoistoja,<0 /> <1>käytä oletusasetusta</1>"
"onboarding": {
"defaultConfirm": {
"cancel": "Peruuta",
"confirm": "Käytä oletuksia",
"description": "Oletuksissa ei ole parhaita suoratoistoja ja se voi olla sietämättömän hidasta.",
"title": "Oletko varma?"
},
"extension": {
"action": "Asenna laajennus",
"description": "Asenna selaimen laajennus ja saa pääsy parhaisiin lähteisiin."
}
},
"title": "Aloitetaan asennus elokuva-webin kanssa"
}
},
"player": {
"menus": {
"episodes": {
"emptyState": "Tässä kaudessa ei ole jaksoja, tarkista myöhemmin!"
},
"quality": {
"hint": "Voit kokeilla <0>vaihtaa lähdettä</0> saadaksesi eri laatuasetuksia."
},
"sources": {
"noEmbeds": {
"text": "Emme löytäneet upotuksia, kokeile toista lähdettä."
"back": "Mene takaisin",
"explainer": "Käyttämällä selainlaajennusta voit saada parhaat tarjoamamme suoratoistot. Yksinkertaisella asennuksella.",
"explainerIos": "Valitettavasti selainlaajennusta ei tueta iOS:ssä. Valitse toinen vaihtoehto painamalla <bold>Palaa</bold>.",
"extensionHelp": "Jos olet asentanut laajennuksen, mutta se ei havaitse sitä, <bold>avaa laajennus selaimen laajennusvalikosta</bold> ja noudata näytön ohjeita.",
"linkChrome": "Asenna Chromen laajennus",
"linkFirefox": "Asenna Firefoxin laajennus",
"notDetecting": "Asensitko sen Chromelle, mutta sivusto ei havaitse sitä? Kokeile ladata sivu uudelleen!",
"notDetectingAction": "Lataa sivu uudelleen",
"status": {
"disallowed": "Laajennus ei ole otettu käyttöön tälle sivulle",
"disallowedAction": "Ota laajennus käyttöön",
"failed": "Pyynnön tilan hakeminen epäonnistui",
"loading": "Odottaa, että asennat laajennuksen",
"outdated": "Laajennuksen versio on liian vanha",
"success": "Laajennus toimii odotetusti!"
},
"submit": "Jatketaan",
"title": "Aloitetaan laajennuksella"
},
"noStream": {
"text": "Tässä lähteessä ei ole suoratoistoja tälle elokuvalle tai ohjelmalle."
"proxy": {
"back": "Mene takaisin",
"explainer": "Proxy-menetelmällä voit saada erinomaisen laadukkaita suoratoistoja luomalla itsepalveluvaltuutetun proxyn.",
"input": {
"errorConnection": "Yhteys proxyn kanssa ei onnistunut",
"errorInvalidUrl": "Ei kelvollinen URL-osoite",
"errorNotProxy": "Odotettiin proxya, mutta saatiinkin verkkosivu",
"label": "Proxyn URL-osoite",
"placeholder": "https://"
},
"link": "Opi luomaan proxy",
"submit": "Toimita proxy",
"title": "Luodaan uusi proxy"
},
"start": {
"explainer": "Parhaiden suoratoistojen saamiseksi sinun täytyy valita, mitä suoratoistomenetelmää haluat käyttää.",
"options": {
"default": {
"text": "En halua hyvälaatuisia suoratoistoja,<0 /> <1>käytä oletusasetusta</1>"
},
"extension": {
"action": "Asenna laajennus",
"description": "Asenna selaimen laajennus ja saa pääsy parhaisiin lähteisiin."
}
},
"title": "Aloitetaan asennus elokuva-webin kanssa"
}
}
},
"metadata": {
"api": {
"text": "API-metatietoja ei voitu ladata, tarkista internet-yhteys.",
"title": "Ei voitu ladata API:n metatietoja."
}
},
"playbackError": {
"errors": {
"errorAborted": "Mediatiedon haku keskeytettiin käyttäjän pyynnöstä.",
"errorNotSupported": "Mediaa tai mediantarjoajaa ei tueta."
},
"text": "Tapahtui virhe yritettäessä toistaa mediaa. Yritä uudelleen."
},
"time": {
"remaining": "{{timeLeft}} jäljellä • Päättyy {{timeFinished, datetime}}"
},
"turnstile": {
"error": "Ei voitu varmistaa inhimillisyyttäsi. Yritä uudelleen.",
"title": "Meidän täytyy varmistaa, että olet ihminen."
}
},
"screens": {
"migration": {
"inProgress": "Pysy hetkisen odotuksessa, siirrämme tietojasi. Tämä ei kestä kauan."
}
},
"settings": {
"account": {
"register": {
"text": "Jaa katseluetenemisesi laitteiden välillä ja pidä ne synkronoituina."
}
},
"connections": {
"setup": {
"unsetStatus": {
"description": "Käynnistä asennusprosessi napsauttamalla oikealla olevaa painiketta."
"player": {
"menus": {
"episodes": {
"emptyState": "Tässä kaudessa ei ole jaksoja, tarkista myöhemmin!"
},
"quality": {
"hint": "Voit kokeilla <0>vaihtaa lähdettä</0> saadaksesi eri laatuasetuksia."
},
"sources": {
"noEmbeds": {
"text": "Emme löytäneet upotuksia, kokeile toista lähdettä."
},
"noStream": {
"text": "Tässä lähteessä ei ole suoratoistoja tälle elokuvalle tai ohjelmalle."
}
}
},
"metadata": {
"api": {
"text": "API-metatietoja ei voitu ladata, tarkista internet-yhteys.",
"title": "Ei voitu ladata API:n metatietoja."
}
},
"playbackError": {
"errors": {
"errorAborted": "Mediatiedon haku keskeytettiin käyttäjän pyynnöstä.",
"errorNotSupported": "Mediaa tai mediantarjoajaa ei tueta."
},
"text": "Tapahtui virhe yritettäessä toistaa mediaa. Yritä uudelleen."
},
"time": {
"remaining": "{{timeLeft}} jäljellä • Päättyy {{timeFinished, datetime}}"
},
"turnstile": {
"error": "Ei voitu varmistaa inhimillisyyttäsi. Yritä uudelleen.",
"title": "Meidän täytyy varmistaa, että olet ihminen."
}
},
"workers": {
"emptyState": "Ei vielä työntekijöitä, lisää yksi alle"
}
},
"preferences": {
"languageDescription": "Kieli sovellettu koko sovellukseen."
"screens": {
"migration": {
"inProgress": "Pysy hetkisen odotuksessa, siirrämme tietojasi. Tämä ei kestä kauan."
}
},
"subtitles": {
"previewQuote": "Minun ei pidä pelätä. Pelko on mielen tappaja."
"settings": {
"account": {
"register": {
"text": "Jaa katseluetenemisesi laitteiden välillä ja pidä ne synkronoituina."
}
},
"connections": {
"setup": {
"unsetStatus": {
"description": "Käynnistä asennusprosessi napsauttamalla oikealla olevaa painiketta."
}
},
"workers": {
"emptyState": "Ei vielä työntekijöitä, lisää yksi alle"
}
},
"preferences": {
"languageDescription": "Kieli sovellettu koko sovellukseen."
},
"subtitles": {
"previewQuote": "Minun ei pidä pelätä. Pelko on mielen tappaja."
}
}
}
}

View file

@ -0,0 +1,147 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/buttons/Button";
import { Icon, Icons } from "@/components/Icon";
import { useWatchPartySync } from "@/hooks/useWatchPartySync";
import { useWatchPartyStore } from "@/stores/watchParty";
export function WatchPartyStatus() {
const { t } = useTranslation();
const { enabled, roomCode, isHost, showStatusOverlay } = useWatchPartyStore();
const [expanded, setExpanded] = useState(false);
const [showNotification, setShowNotification] = useState(false);
const [lastUserCount, setLastUserCount] = useState(1);
const {
roomUsers,
hostUser,
isBehindHost,
isAheadOfHost,
timeDifferenceFromHost,
syncWithHost,
isSyncing,
userCount,
} = useWatchPartySync();
// Show notification when users join
useEffect(() => {
if (userCount > lastUserCount) {
setShowNotification(true);
const timer = setTimeout(() => setShowNotification(false), 3000);
return () => clearTimeout(timer);
}
setLastUserCount(userCount);
}, [userCount, lastUserCount]);
// If watch party is not enabled or overlay is hidden, don't show anything
if (!enabled || !roomCode || !showStatusOverlay) return null;
// Toggle expanded state
const handleToggleExpanded = () => {
setExpanded(!expanded);
};
return (
<div
className={`absolute top-2 right-2 z-50 p-2 bg-mediaCard-shadow bg-opacity-70 backdrop-blur-sm rounded-md text-white text-xs
flex flex-col items-end gap-1 max-w-[250px] transition-all duration-300
${showNotification ? "ring-1 ring-buttons-purple shadow-lg shadow-buttons-purple" : ""}`}
>
<div className="flex gap-2 w-full justify-between items-center">
<div className="flex items-center gap-2">
<Icon icon={Icons.WATCH_PARTY} className="w-4 h-4" />
<span className="font-bold pr-1">
{isHost ? t("watchParty.hosting") : t("watchParty.watching")}
</span>
</div>
<span className="text-type-logo font-mono tracking-wider">
{roomCode}
</span>
</div>
<div className="w-full text-type-secondary flex justify-between items-center space-x-2">
<div className="cursor-pointer" onClick={handleToggleExpanded}>
<Icon
icon={expanded ? Icons.CHEVRON_DOWN : Icons.CHEVRON_RIGHT}
className="w-3 h-3"
/>
</div>
<span>
{roomUsers.length <= 1
? t("watchParty.alone")
: t("watchParty.withCount", { count: roomUsers.length - 1 })}
</span>
{/* Sync status indicator */}
{!isHost && hostUser && (
<div className="flex items-center gap-1">
<div
className={`w-2 h-2 rounded-full ${
isBehindHost || isAheadOfHost ? "bg-red-500" : "bg-green-500"
}`}
/>
<span className="text-xs">
{isBehindHost || isAheadOfHost
? t("watchParty.status.outOfSync")
: t("watchParty.status.inSync")}
</span>
</div>
)}
</div>
{expanded && roomUsers.length > 1 && (
<div className="w-full mt-1 border-t border-mediaCard-hoverBackground pt-1">
<div className="text-xs text-type-secondary mb-1">Viewers:</div>
<div className="space-y-1 max-h-24 overflow-y-auto">
{roomUsers.map((user) => (
<div
key={user.userId}
className="flex items-center justify-between text-xs"
>
<span className="flex items-center gap-1">
<Icon
icon={user.isHost ? Icons.RISING_STAR : Icons.USER}
className={`w-3 h-3 ${user.isHost ? "text-onboarding-best" : ""}`}
/>
<span className={user.isHost ? "text-onboarding-best" : ""}>
{user.userId.substring(0, 8)}...
</span>
</span>
<span className="text-type-secondary">
{user.player.duration > 0
? `${Math.floor((user.player.time / user.player.duration) * 100)}%`
: `${Math.floor(user.player.time)}s`}
</span>
</div>
))}
</div>
</div>
)}
{!isHost && hostUser && (isBehindHost || isAheadOfHost) && (
<div className="mt-1 w-full">
<Button
theme="secondary"
className="text-xs py-1 px-2 bg-buttons-purple bg-opacity-50 hover:bg-buttons-purpleHover hover:bg-opacity-80 w-full flex items-center justify-center gap-1"
onClick={syncWithHost}
disabled={isSyncing}
>
<Icon icon={Icons.CLOCK} className="w-3 h-3" />
<span className="whitespace-nowrap">
{isSyncing
? t("watchParty.syncing")
: isBehindHost
? t("watchParty.behindHost", {
seconds: Math.abs(Math.round(timeDifferenceFromHost)),
})
: t("watchParty.aheadOfHost", {
seconds: Math.round(timeDifferenceFromHost),
})}
</span>
</Button>
</div>
)}
</div>
);
}