mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 20:18:14 +00:00
Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f02a47d98 | |||
| c3827fa4b7 | |||
| 61ba32addf | |||
| 140e2c0d00 | |||
| cc53494cc0 | |||
| fd3c3e6cdb | |||
| 7d6ca9fd68 | |||
| 84526cf8e9 | |||
| c16bf30ad5 | |||
| e559b41c93 | |||
| a9687219e0 | |||
| dfe0b42252 | |||
| d29c842e96 | |||
| 07839a907b | |||
| 78efb04b6a | |||
| e093a8afae | |||
| b8484f77d6 | |||
| 8d7b7f68f3 | |||
| 2848a4450c | |||
| 4c519a81e0 | |||
| db70828c82 | |||
| fd0ad310ce | |||
| 917afd795e | |||
| da681e9e3f | |||
| 37df4b267c | |||
| 793248053c | |||
| 1aa95079cf | |||
| 0976e765c3 | |||
| ee2c284cb4 | |||
| 883807afd7 | |||
| 46b2004f78 | |||
| 3fd02246ab | |||
| 14c9ab1d5e | |||
| 0127d62a49 | |||
| 2d94cc10e4 | |||
| c5c140d31c | |||
| 5a53abc636 | |||
| f0328fb865 | |||
| e1e18aa26c | |||
| cece816e00 | |||
| 9690868e33 | |||
| cb6d7eb98c | |||
| ab6356c5ad | |||
| 5460ac3a01 | |||
| 60c80ad7fb | |||
| c8df8b6d08 | |||
| 49712a7151 | |||
| 23aa0c8a15 | |||
| ae3b837bb4 | |||
| 14ab6b2d50 | |||
| 82b90d86a9 | |||
| 537c8f3c60 | |||
| 6b81668e16 | |||
| d6a23f2eb5 | |||
| 573847486b | |||
| e5a4032710 | |||
| 44d452a82f | |||
| 82d0bd4eb8 | |||
| d3ca2a2378 | |||
| b7622a8347 | |||
| 3a733934c5 | |||
| 25ad3e8d59 | |||
| 223c3949d2 | |||
| 3392f0b3a7 | |||
| 72ea5d1ee9 | |||
| 869c64171b | |||
| 6ccaf45f8e | |||
| 1b7be23b29 | |||
| 9ec0e9dd4e | |||
| 9ac8c28442 | |||
| 076157fef1 | |||
| 236da46f5d | |||
| 2fbcbc4f83 | |||
| 8fe1c6dfbc | |||
| 51da169d69 | |||
| a9f5c0b9bf | |||
| 705fbc146b | |||
| 09d35f3e48 | |||
| 32994aaaa5 | |||
| bea0f6925c | |||
| 71fa6e7315 | |||
| 54b806d270 | |||
| 066d2c9407 | |||
| ce522e2ab7 | |||
| 9cbeb661d6 | |||
| beda924b65 | |||
| 01762b0c2f | |||
| 0af22998c8 | |||
| 52265e84eb | |||
| 57cd071d58 | |||
| 8a21fbe07f | |||
| 7781d418ba | |||
| c022f6f490 | |||
| 9fe83f89b2 | |||
| e51e8ae436 | |||
| e74abd00f7 | |||
| dab7368537 | |||
| 463f871958 | |||
| 1f5196e1e6 |
@@ -2,7 +2,7 @@
|
||||
name: 🚀 Feature Request
|
||||
about: Suggest a new feature or enhancement for KoalaSync
|
||||
title: ''
|
||||
labels: enhancement
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ representative at an online or offline event.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the project maintainer at **koaladev@koalamail.rocks**.
|
||||
reported to the project maintainer at **koalasync@koalastuff.net**.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All project maintainers are obligated to respect the privacy and security of the
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
> **TEMPORÄR:** Diese Datei dient nur der gemeinsamen Planung des Landingpage-UI-Overhauls
|
||||
> auf dem Branch `landingpage-ui-overhaul`. Sie ist **kein** Teil des finalen Features und
|
||||
> **muss vor dem Öffnen des PRs wieder gelöscht werden**.
|
||||
|
||||
# Landingpage UI Overhaul — Hero Mockup
|
||||
|
||||
## Ausgangslage
|
||||
Die Landingpage (`website/template.html`, `website/style.css`, `website/app.js`, kompiliert
|
||||
via `website/build.cjs` nach `website/www/`) ist bereits sehr gut, soll aber **statisch und
|
||||
backend-frei** bleiben. Einziger Fokus dieses Overhauls: das rechte Hero-Mockup
|
||||
(`.hero-mockup-wrapper` / `.extension-mockup`, `template.html:304-579`).
|
||||
|
||||
**Heutiger Zustand:** Ein 1:1-Nachbau des Extension-Popups mit 4 Tabs (Room / Sync /
|
||||
Settings / Status). Tab-Wechsel nur per Klick (`app.js:391-407`), keine Animationen, kein
|
||||
Auto-Play, kein Bezug zu den eigentlichen Video-Tabs, die synchronisiert werden.
|
||||
|
||||
## Ziel
|
||||
Ein dynamischeres, platzoptimiertes Hero-Visual, das das Kernversprechen von KoalaSync
|
||||
("zwei Browser-Tabs synchron abspielen") direkt zeigt — mit echten Animationen, einem
|
||||
automatischen Durchlauf beim ersten Sichtbarwerden und voller manueller Bedienbarkeit
|
||||
danach. Auf Mobile (Bildschirmgröße) bleibt exakt das heutige, statische Popup-Mockup
|
||||
erhalten — kein neues Mobile-Design nötig, kein zusätzlicher Wartungsaufwand dort.
|
||||
|
||||
## Getroffene Entscheidungen (aus Rückfrage-Runde)
|
||||
|
||||
1. **Layout:** Gestapelt/Layered — zwei Browser-Tab-Karten leicht versetzt im Hintergrund
|
||||
(Tiefeneffekt via `transform`/`box-shadow`), Extension-Popup öffnet sich als Overlay
|
||||
davor/darüber.
|
||||
2. **Tab-Inhalt:** Abstraktes Video-Mock — Gradient-Placeholder als "Video", Play-Button,
|
||||
Fortschrittsbalken, Timestamp. Kein echtes Videomaterial, keine Marken/Logos, im
|
||||
bestehenden minimalistischen `mock-*`-Stil.
|
||||
3. **Auto-Play-Verhalten:** Einmaliger Durchlauf beim Sichtbarwerden (IntersectionObserver),
|
||||
danach reiner manueller Modus. Kein Endlos-Loop im Hintergrund.
|
||||
4. **Popup-Umfang:** Alle 4 Tabs (Room/Sync/Settings/Status) bleiben inhaltlich unverändert
|
||||
— nur in die neue Animations-/Overlay-Logik eingebettet, kein Content-Kahlschlag.
|
||||
|
||||
## Vorgeschlagenes Storyboard (einmaliger Auto-Durchlauf)
|
||||
|
||||
1. **Ausgangszustand:** Zwei Tab-Karten ("Tab A" / "Tab B") sichtbar, Video-Mocks pausiert,
|
||||
Fortschrittsbalken bei 0. Popup geschlossen, nur ein kleiner Launcher-Button sichtbar.
|
||||
2. Popup öffnet sich (scale/slide-in) vor den Tab-Karten.
|
||||
3. Simulierter Klick auf "Play" im Sync-Tab → beide Tab-Karten starten gleichzeitig
|
||||
(Fortschrittsbalken laufen synchron), begleitet von einem kurzen "Sync-Pulse"
|
||||
(Glow/Connection-Line) zwischen den beiden Karten.
|
||||
4. Nach ein paar Sekunden: simulierter Klick auf "Pause" → beide Tabs pausieren synchron.
|
||||
5. Popup schließt sich, Endzustand bleibt stehen (beide Tabs pausiert, synchron,
|
||||
"Synced"-Indikator dezent sichtbar) — kein Reset auf Ausgangszustand.
|
||||
6. **Danach/parallel volle manuelle Steuerung:**
|
||||
- Klick auf eine Tab-Karte togglet deren eigenes Play/Pause *unabhängig* — spielt eine
|
||||
Karte allein, laufen die Fortschrittsbalken bewusst auseinander ("out of sync"), um den
|
||||
Schmerzpunkt zu illustrieren, den KoalaSync löst.
|
||||
- Klick auf Play/Pause im Popup synchronisiert immer beide Tabs.
|
||||
- Popup lässt sich jederzeit manuell öffnen/schließen, Tabs (Room/Sync/Settings/Status)
|
||||
bleiben wie heute klickbar.
|
||||
- Jede manuelle Interaktion während des Auto-Durchlaufs bricht diesen sofort ab und
|
||||
übergibt die volle Kontrolle.
|
||||
|
||||
## Technischer Ansatz (Vorschlag)
|
||||
|
||||
- **Keine neuen Dependencies** — reines Vanilla JS + CSS, passend zur "Zero-Dependency"-
|
||||
Philosophie des Projekts (siehe `website/README.md`).
|
||||
- **Eine "Director"-Funktion** in `app.js`, die exakt dieselben Click-Handler wie die
|
||||
manuelle Bedienung aufruft (kein doppelter Code-Pfad zwischen Auto- und Manual-Modus).
|
||||
- **IntersectionObserver** triggert den einmaligen Durchlauf beim ersten Sichtbarwerden.
|
||||
- **`prefers-reduced-motion`**: Auto-Durchlauf und Cursor-Ripple-Indikatoren werden
|
||||
übersprungen, Endzustand wird direkt gesetzt (Pattern existiert bereits in
|
||||
`style.css:2915-2939`).
|
||||
- **Kein DOM-Duplikat für Mobile:** Das Popup-Markup bleibt eine einzige Quelle. Der neue
|
||||
`.hero-mockup-dynamic`-Wrapper enthält die Tab-Karten + das Popup; unterhalb des
|
||||
bestehenden Breakpoints (768px, siehe `style.css:1913`) werden die Tab-Karten und
|
||||
Animationen per CSS ausgeblendet, das Popup fällt optisch auf das heutige, statische
|
||||
Erscheinungsbild zurück, JS initialisiert dort keine Animationen. Vermeidet doppelte
|
||||
i18n-Pflege der ~270 Zeilen Popup-Markup.
|
||||
- **i18n-Vorsicht:** 15 Locale-Dateien unter `website/locales/*.json`. Neue sichtbare Texte
|
||||
(Tab-Labels etc.) nach Möglichkeit vermeiden oder wie bestehende Mock-Inhalte
|
||||
(z. B. "CoolUsername", "Stranger Things - S4E1") als nicht-lokalisierte Platzhalter
|
||||
behandeln, um Übersetzungsaufwand in 15 Sprachen zu vermeiden. Neue `{{MOCK_XX}}`-Keys nur
|
||||
falls wirklich nötig, dann mit Übersetzung in allen 15 Dateien.
|
||||
- **Build:** Änderungen nur in den Source-Dateien (`template.html`, `style.css`, `app.js`),
|
||||
niemals in `website/www/**` (generiert via `website/build.cjs`).
|
||||
|
||||
## Detailentscheidungen (bei der Umsetzung getroffen)
|
||||
|
||||
- [x] Breakpoint Mobile-Fallback: bestehende 768px übernommen (`matchMedia('(min-width: 769px)')`
|
||||
im JS, `@media (max-width: 768px)` im CSS).
|
||||
- [x] Auto-Durchlauf-Timing: Start ~1,4s nach 45% Sichtbarkeit (IntersectionObserver), dann
|
||||
Cursor → Launcher → Sync-Tab → Play (läuft ~3s synchron) → Pause → Popup schließt.
|
||||
Gesamtdauer ca. 10s.
|
||||
- [x] Tab-Karten haben User-Badges passend zur Peer-Liste im Popup: "CoolUsername" (indigo)
|
||||
und "KoalaPC" (grün), gleicher Episodentitel "Stranger Things - S4E1".
|
||||
- [x] Echter animierter Ghost-Mauszeiger (SVG-Cursor mit Klick-Ripple), kein reiner
|
||||
Ripple-Indikator — verkauft den Durchlauf deutlich besser.
|
||||
- [x] Keine externe Referenz — eigener Stil, konsistent mit dem bestehenden `mock-*`-Design.
|
||||
- [x] ~~Drift-Konzept~~ **Verworfen nach Feedback** — es stellte die Extension falsch dar
|
||||
(als bräuchte sie das offene Popup). Ersetzt durch das **Broadcast-Modell**: Play/
|
||||
Pause/Seek auf *irgendeinem* Video wird automatisch zum Peer gespiegelt (Puls wandert
|
||||
über die Verbindungslinie, Peer folgt ~260ms später, Engine gleicht die Zeit an). Der
|
||||
Status-Chip bleibt grün "In sync" und blitzt bei jedem Broadcast kurz das Event auf
|
||||
("▶ CoolUsername", "❚❚ KoalaPC", "» …" für Seek). Das Popup ist nur die Fernbedienung
|
||||
obendrauf, nie Voraussetzung.
|
||||
- [x] Rework 2 nach Feedback: Kein reservierter Popup-Platz mehr; KoalaSync-Icon als
|
||||
Extension-Button in der Toolbar des vorderen Fensters; Fortschrittsbalken klickbar
|
||||
(Seek wird mitgesynct, Scrub-Thumb bei Hover).
|
||||
- [x] Rework 3 nach Feedback: Fenster-Kaskade statt starker Überlappung — das **rechte**
|
||||
Fenster (CoolUsername, mit dem Toolbar-Icon oben rechts) liegt im Vordergrund, das
|
||||
linke (KoalaPC) versetzt dahinter unten links. Das Popup öffnet **rechts außen**
|
||||
(right: 0, verankert unterm Icon), die beiden Tabs weichen dabei animiert nach links
|
||||
aus und bleiben sichtbar. Verbindungslinie komplett entfernt (Sync-Feedback = Puls-
|
||||
Ring auf beiden Karten + Event-Chip). Peer-Latenz von 260ms auf 90ms gesenkt — wirkt
|
||||
quasi instant.
|
||||
- [x] Rework 4 nach Feedback: **Story-Walkthrough in 5 Akten** beim ersten Sichtbarwerden —
|
||||
(1) Extension über das Toolbar-Icon öffnen, (2) "Raum erstellen" (eigener Pre-Room-
|
||||
Screen im Popup, `#demo-room-empty`), (3) Invite-Link fliegt animiert vom Popup zum
|
||||
zweiten Browserfenster (Toast "Raum beigetreten", KoalaPC erscheint in der Peer-Liste,
|
||||
"IN SYNC"-Chip blendet erst jetzt ein), (4) beide wählen den Video-Tab (Select-Flash im
|
||||
Popup + Toast am zweiten Fenster), (5) Play für alle → Popup schließt → Pause vom
|
||||
anderen Tab, Seek mit Nachziehen, Play. Danach voll manuell. Jede Interaktion während
|
||||
der Story bricht ab und springt via idempotentem `finishDemo()` sofort in den End-
|
||||
zustand (Raum verbunden, Chip an, Hint an; Popup schließt bei Klicks außerhalb);
|
||||
der Nutzer-Klick wirkt anschließend normal. 4 neue Locale-Keys (DEMO_NO_ROOM,
|
||||
DEMO_CREATE_ROOM, DEMO_JOINED, DEMO_TAB_SELECTED) in allen 15 Sprachen.
|
||||
- [x] Abspiel-Animation: abstrakter **Strichfigur-Kurzfilm** pro Video (SVG: Hügel-
|
||||
Silhouetten, laufende Figur mit schwingenden Armen/Beinen, hüpfender Ball) — läuft
|
||||
nur bei `playing`, Pause friert beide Filme im selben Frame ein
|
||||
(`animation-play-state`), was die Synchronität sofort sichtbar macht.
|
||||
|
||||
## Umsetzungsstatus
|
||||
|
||||
- [x] `website/template.html`: Hero-Mockup in `.hero-demo-scene` eingebettet (Chip, 2 Tab-
|
||||
Karten, Verbindungslinie, Launcher, Ghost-Cursor, Hint); Popup-Inhalt unverändert,
|
||||
nur IDs für Play/Pause/Force-Sync ergänzt.
|
||||
- [x] `website/style.css`: Demo-Styles mit Container-Queries (cqw + px-Fallbacks),
|
||||
Mobile-Fallback, `prefers-reduced-motion`-Support.
|
||||
- [x] `website/app.js`: `initHeroDemo()` — Playback-Uhr (rAF, 8x Zeitraffer), Broadcast-
|
||||
Logik (Play/Pause/Seek → Peer folgt), manuelle Steuerung, einmaliger Auto-Durchlauf
|
||||
(Play in Tab A → Seek in Tab B → Pause in Tab B → Popup öffnen/Play/schließen); jede
|
||||
Interaktion bricht den Durchlauf sofort ab. Auto- und Manuell-Modus nutzen dieselben
|
||||
Click-Handler. Launcher-Klicks sind vom Karten-Play/Pause entkoppelt (closest-Guard).
|
||||
- [x] `website/locales/*.json`: 3 neue Keys (`DEMO_SYNC`, `DEMO_DRIFT`, `DEMO_HINT`) in
|
||||
allen 15 Sprachen.
|
||||
- [x] `eslint.config.mjs`: `requestAnimationFrame`/`cancelAnimationFrame` zu den Globals.
|
||||
- [x] Verifiziert im Browser-Preview: Auto-Durchlauf, Drift-Szenario, Chip-Rettungsweg,
|
||||
Re-Sync, Mobile-Fallback (statisches Popup wie vorher), keine Konsolenfehler,
|
||||
Lint sauber, Build ohne offene Platzhalter.
|
||||
- [ ] `.claude/launch.json` zeigt auf einen Session-Scratchpad-Server — vor dem PR prüfen,
|
||||
ob die Datei ins Repo soll (ggf. löschen oder auf dauerhaften Pfad umstellen).
|
||||
|
||||
## Scope / Nicht-Ziele
|
||||
|
||||
- Kein Backend, keine echten Videos/Assets, keine neuen npm-Dependencies.
|
||||
- Kein Umbau des restlichen Landingpage-Contents (Features, Compat-Section, Footer etc.) —
|
||||
ausschließlich das Hero-Mockup.
|
||||
- Mobile-Ansicht bekommt **kein** neues Design, bleibt 1:1 der heutige Stand.
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Shik3i/KoalaSync/actions/workflows/release.yml"><img src="https://github.com/Shik3i/KoalaSync/actions/workflows/release.yml/badge.svg" alt="Release Status"></a>
|
||||
<a href="https://github.com/Shik3i/KoalaSync/releases"><img src="https://img.shields.io/badge/Release-v2.5.0-blue?logo=github" alt="GitHub release"></a>
|
||||
<a href="https://github.com/Shik3i/KoalaSync/releases"><img src="https://img.shields.io/badge/Release-v2.5.3-blue?logo=github" alt="GitHub release"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue" alt="License"></a>
|
||||
<a href="https://addons.mozilla.org/de/firefox/addon/koalasync/"><img src="https://img.shields.io/badge/Firefox-Download-orange?logo=firefoxbrowser&logoColor=white" alt="Firefox Add-on"></a>
|
||||
<a href="https://chromewebstore.google.com/detail/koalasync/obbnmkmlaaddodakcbdljknjpagklifc"><img src="https://img.shields.io/badge/Chrome-Download-blue?logo=googlechrome&logoColor=white" alt="Chrome Extension"></a>
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<p align="center"><i>KoalaSync is a lightweight Browser Extension and Relay Server for synchronized video playback on almost any website with a video element—YouTube, Twitch, Netflix, Emby, Jellyfin, and beyond. Built with a focus on <b>Data Sovereignty</b> and <b>Performance</b>.</i></p>
|
||||
|
||||
<p align="center"><a href="docs/CHANGELOG.md"><b>New v2.5.0 Release!</b> — See what's changed</a></p>
|
||||
<p align="center"><a href="docs/CHANGELOG.md"><b>New v2.5.3 Release!</b> — See what's changed</a></p>
|
||||
|
||||
### 🌟 Why KoalaSync?
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
- **Global Synchronization**: Synchronize Play, Pause, and Seeking on any website with a `<video>` tag.
|
||||
- **Episode Auto-Sync**: Perfectly sync series binges. All peers wait until everyone has loaded the next episode before starting together.
|
||||
- **Host Control & Co-Hosts**: Room hosts can lock playback control to trusted controllers while guests keep watching in sync.
|
||||
- **Smart Matching**: Automatically highlights tabs containing matching video titles.
|
||||
- **Dual Heartbeat Architecture**: Robust session tracking that prevents ghost rooms and stale connections.
|
||||
- **Efficient Relay**: Minimal overhead WebSocket message forwarding.
|
||||
@@ -58,7 +59,7 @@ The easiest and safest way to install KoalaSync is directly through the official
|
||||
|
||||
### 🌐 Localization & Translations
|
||||
|
||||
Both the official KoalaSync website and the **v2.0 Browser Extension** feature full dynamic localization:
|
||||
Both the official KoalaSync website and the browser extension feature dynamic localization:
|
||||
- **Available Languages**: Support is included for 15 languages: English (`en`), German (`de`), French (`fr`), Spanish (`es`), Portuguese (Brazil) (`pt-BR`), Russian (`ru`), Italian (`it`), Polish (`pl`), Turkish (`tr`), Dutch (`nl`), Japanese (`ja`), Korean (`ko`), Chinese (Simplified) (`zh`), Ukrainian (`uk`), and European Portuguese (`pt`).
|
||||
- **Real-Time Extension Localization**: Inside the extension Settings panel, users can swap languages instantly. The entire interface, notifications, Empty States, and onboarding guides re-translate dynamically in real-time.
|
||||
- **Contributing**: We welcome community translations for both the website and the extension! Please refer directly to the [TRANSLATION.md](docs/TRANSLATION.md) guide for step-by-step instructions on how to audit, refine, or add new languages.
|
||||
@@ -86,16 +87,20 @@ npm run build:extension
|
||||
The compiled artifacts will be available in the `dist/` directory.
|
||||
|
||||
#### For Self-Hosting (Docker)
|
||||
Deploy your own private relay server using our official image:
|
||||
For local development or a simple private relay, use the root Compose file:
|
||||
```bash
|
||||
# Pull the latest image
|
||||
docker pull ghcr.io/shik3i/koalasync:latest
|
||||
|
||||
# Or use our example compose file
|
||||
cp examples/docker-compose.caddy.example.yml docker-compose.yml
|
||||
docker-compose up -d
|
||||
cp server/.env.example server/.env
|
||||
# Edit server/.env and set SERVER_SALT to a unique random value:
|
||||
# openssl rand -base64 32
|
||||
docker compose up -d --build
|
||||
```
|
||||
The server will be available at `ws://localhost:3000`. See [Docker network compose](examples/docker-compose.caddy.example.yml) or [Static IP compose](examples/docker-compose.ip.example.yml) for ready-to-use Docker Compose files.
|
||||
The local relay will be available at `ws://localhost:3000`.
|
||||
|
||||
For production, use the official image and one of the ready-to-edit examples:
|
||||
- [Docker network compose](examples/docker-compose.caddy.example.yml): for a Caddy or reverse-proxy network. This file does not publish `localhost:3000`; Caddy routes traffic to the container.
|
||||
- [Static IP compose](examples/docker-compose.ip.example.yml): for a pre-existing Docker network with a fixed container IP.
|
||||
|
||||
In every real deployment, set a unique `SERVER_SALT`. Without it the relay still starts, but it warns that room-password hashes are using the public fallback salt from the repository.
|
||||
|
||||
To connect your extension to a self-hosted server, open the popup → **Room** tab → select **Custom Server** → enter your server's WebSocket URL (e.g., `ws://localhost:3000`).
|
||||
|
||||
@@ -131,8 +136,10 @@ gh attestation verify dist/koalasync-chrome.zip \
|
||||
- **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)**: Our community standards and expectations.
|
||||
- **[HOW_IT_WORKS.md](docs/HOW_IT_WORKS.md)**: Step-by-step walkthrough of the complete user flow.
|
||||
- **[ARCHITECTURE.md](docs/ARCHITECTURE.md)**: Deep-dive into the two-phase sync and heartbeat logic.
|
||||
- **[PROTOCOL.md](docs/PROTOCOL.md)**: WebSocket protocol specification and event reference.
|
||||
- **[ROADMAP.md](docs/ROADMAP.md)**: Planned features, backlog, and rejected ideas.
|
||||
- **[SECURITY.md](SECURITY.md)**: Disclosure policy and security practices.
|
||||
- **[AI_INIT.md](docs/AI_INIT.md)**: Maintainer and agent onboarding notes for safe code changes.
|
||||
- **[Caddyfile.example](examples/Caddyfile.example)**: Production Caddy configuration for website and relay.
|
||||
|
||||
---
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ The following components are within scope for security reports:
|
||||
|
||||
Instead, email the project maintainer privately:
|
||||
|
||||
**`koalasync_admin@koalamail.rocks`**
|
||||
**`koalasync@koalastuff.net`**
|
||||
|
||||
Encrypt sensitive findings with our PGP key (available on request).
|
||||
|
||||
|
||||
+23
-15
@@ -7,7 +7,7 @@ Welcome to the KoalaSync project. This file is the primary entry point for any d
|
||||
> - **Font Stack**: Use a modern system font stack (e.g., -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif) to maintain a premium look without external dependencies. Prohibit the use of `@import` or `<link>` for external font services.
|
||||
>
|
||||
> [!IMPORTANT]
|
||||
> **Caveman Communication Protocol**: To conserve context window and tokens, AI agents MUST communicate with the user in **caveman language** (broken, short German/English hybrid caveman style, e.g., "Ich Antigravity. Ich machen Git pull. Code fertig.") at all times. This rule is highly strict and must NOT be broken under any circumstances unless the user explicitly requests to drop it.
|
||||
> **Communication Standard**: Be concise, concrete, and professional. Match the user's language when practical, explain risky changes before making them, and report exactly what was changed and verified. Do not use joke protocols or intentionally broken language in contributor-facing work.
|
||||
|
||||
---
|
||||
|
||||
@@ -23,26 +23,28 @@ KoalaSync is a specialized tool for **synchronized video playback** across multi
|
||||
- `website/`: **Landing Page** & Invitation Bridge (Marketing, Tutorials, and Downloads).
|
||||
- **`build.cjs`**: Zero-dependency static site compiler. Translates `template.html` + `locales/*.json` → `www/`. Also minifies CSS/JS automatically.
|
||||
- **`www/` is auto-generated**: Never edit files in `www/` directly. Always edit source files (`template.html`, `style.css`, `app.js`, `lang-init.js`, `locales/*.json`) and run `node website/build.cjs` to regenerate. CSS/JS are output as `.min.*` files — a built-in cleanup step removes stale artifacts on each build.
|
||||
- `shared/`: **Single Source of Truth** for protocol constants and event names.
|
||||
- `shared/`: **Single Source of Truth** for protocol constants, event names, blacklist data, and generated usernames.
|
||||
- `scripts/`: Development utilities (e.g., `build-extension.cjs`).
|
||||
- `docker-compose.yml`: Root-level orchestration for the relay server.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Single Source of Truth**: `shared/constants.js` and `shared/blacklist.js` are the master files. They must be synchronized to the `extension/shared/` directory using `node scripts/build-extension.cjs`.
|
||||
> **Single Source of Truth**: `shared/constants.js`, `shared/blacklist.js`, and `shared/names.js` are the master shared files. They must be synchronized to the `extension/shared/` directory using `node scripts/build-extension.cjs`.
|
||||
> - **Extension Modules** (`background.js`, `popup.js`) import directly from `./shared/constants.js`.
|
||||
> - **Content Scripts** (`content.js`) use a **marker-injected synchronous copy** of the constants. The build script automatically replaces the marked blocks — no manual mirroring needed.
|
||||
|
||||
## 3. Mandatory Reading
|
||||
Before touching any code, you MUST read the following documents in order:
|
||||
1. [ARCHITECTURE.md](ARCHITECTURE.md) – Detailed communication flows, Dual Heartbeat, and two-phase sync protocol.
|
||||
2. [extension/README.md](../extension/README.md) – Extension components, tab structure, and loading process.
|
||||
3. [SYNC_GUIDE.md](SYNC_GUIDE.md) – Protocol constants and synchronization requirements.
|
||||
1. [../README.md](../README.md) – User, developer, and self-hosting overview.
|
||||
2. [README.md](README.md) – Documentation map by role.
|
||||
3. [ARCHITECTURE.md](ARCHITECTURE.md) – Detailed communication flows, Dual Heartbeat, and two-phase sync protocol.
|
||||
4. [../extension/README.md](../extension/README.md) – Extension components, tab structure, and loading process.
|
||||
5. [SYNC_GUIDE.md](SYNC_GUIDE.md) – Protocol constants and synchronization requirements.
|
||||
|
||||
## 4. The "Vanilla JS Mirror" Pattern
|
||||
To avoid boot-time race conditions in Manifest V3 without a bundler, the following architectural trade-off is enforced:
|
||||
- **Synchronous Execution**: `content.js` MUST execute synchronously to catch early media events.
|
||||
- **Automated Injection**: The build script (`node scripts/build-extension.cjs`) automatically injects `EVENTS` and `HEARTBEAT_INTERVAL` into `content.js` using marker-based replacement (see `../scripts/README.md` for marker details).
|
||||
- **Maintenance**: After modifying `shared/constants.js`, simply run the build script. No manual mirroring is required.
|
||||
- **Automated Injection**: The build script (`node scripts/build-extension.cjs`) automatically injects `EVENTS`, `HEARTBEAT_INTERVAL`, and episode utility functions into `content.js` using marker-based replacement (see `../scripts/README.md` for marker details).
|
||||
- **Maintenance**: After modifying any root `shared/` file, run the build script. No manual mirroring is required.
|
||||
|
||||
## 5. File Responsibility Map
|
||||
|
||||
@@ -52,13 +54,16 @@ To avoid boot-time race conditions in Manifest V3 without a bundler, the followi
|
||||
| `content.js` | Video element detection, media control, event origin detection (loop prevention) |
|
||||
| `popup.js` | UI rendering, user input handling, peer display, invitation link generation |
|
||||
| `bridge.js` | Landing page ↔ extension communication for invitation join flow |
|
||||
| `episode-utils.js` | Shared episode parsing, imported by background and injected into content |
|
||||
| `title-privacy.js` | Tab/media title privacy normalization and sanitization |
|
||||
| `page-api-seek-overrides.js` | Page-level seek bridge for site-specific player APIs |
|
||||
| `server/index.js` | Room management, message relay, rate limiting, authentication, peer lifecycle |
|
||||
|
||||
## 6. Design Guidelines
|
||||
The popup UI follows a strict design system. Do not modify these variables or the layout structure without explicit approval.
|
||||
- **Font**: System font stack. **MANDATORY**: No external CDNs or Google Fonts to ensure 100% privacy.
|
||||
- **Popup Width**: Fixed at `320px`.
|
||||
- **Tab Structure**: Must maintain the **Room**, **Sync**, **Settings**, and **Dev** tabs.
|
||||
- **Tab Structure**: Must maintain the visible **Room**, **Sync**, **Settings**, and **Status** tabs. The hidden **Dev** tab is available only for developer diagnostics.
|
||||
- **CSS Variables**:
|
||||
| Variable | Value | Purpose |
|
||||
| :--- | :--- | :--- |
|
||||
@@ -72,6 +77,7 @@ The popup UI follows a strict design system. Do not modify these variables or th
|
||||
The following features are critical and must not be removed or fundamentally altered:
|
||||
- **Two-Phase Force Sync**: The `Prepare` → `ACK` → `Execute` flow ensures all peers are buffered before playback resumes.
|
||||
- **Episode Auto-Sync**: Ensures series binges stay perfectly synced. A lobby initiates during title transitions, freezing peers until everyone is ready.
|
||||
- **Host Control Mode**: In `host-only` rooms, only the host and promoted controllers may initiate room-moving playback events. Both client-side UX and server-side gates must remain intact.
|
||||
- **Dual Heartbeat**:
|
||||
- **Background Heartbeat (1m)**: Ensures session persistence even without a video element.
|
||||
- **Content Heartbeat (15s)**: Transmits current video metadata (time, title).
|
||||
@@ -80,8 +86,9 @@ The following features are critical and must not be removed or fundamentally alt
|
||||
- **Platform Specifics**: Specialized click-logic for YouTube (`.ytp-play-button`) and Twitch.
|
||||
- **pollSeekReady()**: Polling mechanism that checks `video.readyState` before acknowledging sync.
|
||||
- **SW Keep-alive**: Use of `chrome.alarms` to prevent the Manifest V3 Service Worker from suspending.
|
||||
- **Diagnostics**: The "Dev" tab provides real-time access to the underlying `<video>` state for troubleshooting.
|
||||
- **Diagnostics**: The visible **Status** tab provides real-time access to connection state, ping, logs, history, and underlying `<video>` state for troubleshooting.
|
||||
- **Persistence**: `peerId` and `username` must be stored to remain stable across sessions.
|
||||
- **Title Privacy**: Do not accidentally reintroduce tab/media title sharing when users disabled or reduced it.
|
||||
- **Room ID Format**: Room IDs are restricted to `[a-zA-Z0-9-]` only (alphanumeric + hyphens). This is enforced server-side.
|
||||
|
||||
## 8. Technical Constraints
|
||||
@@ -89,7 +96,8 @@ The following features are critical and must not be removed or fundamentally alt
|
||||
- **Manual Protocol**: `background.js` implements a subset of the Socket.IO wire protocol natively.
|
||||
- **Server Transport**: Restricted to `websocket` only. Polling is disabled.
|
||||
- **Docker Context**: The Docker build must run from the **Repo Root**.
|
||||
- **Manifest Settings**: `run_at` must remain `document_idle`, and `all_frames` must remain `false`.
|
||||
- **Content Script Scope**: `bridge.js` is the only static content script in the manifest and runs on `https://sync.koalastuff.net/*` at `document_start`. Video control scripts are injected only into the selected tab via `chrome.scripting`; do not add broad persistent video content-script matches.
|
||||
- **Self-Hosting Salt**: Production/self-hosted relay examples must include a unique `SERVER_SALT` so room-password hashes are not derived with the public fallback salt.
|
||||
- **Strict Backward & Forward Compatibility (Store Delay Rule)**: Browser extensions are distributed through stores (e.g., Chrome Web Store, Firefox Add-ons) which can take up to 2 weeks to approve updates. Therefore, the server MUST NOT reject older extension clients unless a critical protocol version bump is explicitly authorized, and new extension versions MUST remain fully operational when connected to older servers (e.g., by silently falling back if a new feature is not supported). This is a core architectural requirement.
|
||||
|
||||
## 9. Security & Deployment
|
||||
@@ -117,7 +125,7 @@ Before starting any task, committing, or pushing, you **MUST** run `git pull --r
|
||||
> **AI AGENTS MUST FOLLOW THIS EXACT SEQUENCE WHEN RELEASING A NEW VERSION OR TAGGING.**
|
||||
>
|
||||
> **🚫 NO MANUAL VERSION BUMPING**: You MUST **NEVER** manually modify the version strings in `package.json`, `extension/manifest.base.json`, or `website/version.json`. The GitHub Actions CI pipeline automatically extracts the version from the git tag (e.g. `v2.0.5` -> `2.0.5`), injects it into all target files, and commits the updates back to `main` with `[skip ci]`. Manual bumps will cause merge conflicts and build failures.
|
||||
> - **Website Versioning**: **NEVER** manually modify the version fallback strings in `website/index.html`. The website dynamically fetches the latest version and release date from `website/version.json` at runtime using `website/app.js`. Manual bumps in the HTML file are completely redundant and should be avoided.
|
||||
> - **Website Versioning**: **NEVER** manually modify generated version strings in `website/www/`. The website build injects version data from `website/version.json` into generated output.
|
||||
1. **MANDATORY SYNTAX & LINT CHECKS**: Before staging, committing, or pushing any changes, you **MUST** run both checks on every modified JavaScript file:
|
||||
- **Syntax Validation**: Run `node -c` on every single modified JavaScript file (e.g., `node -c extension/background.js` and `node -c extension/content.js`). **NEVER** commit or push code that fails this check.
|
||||
- **ESLint Validation**: Run `npm run lint` (or `npx eslint .`). The output must show **zero errors and zero warnings**. ESLint is configured to catch undefined variables, unused vars, unreachable code, and other semantic issues. **NEVER** commit or push code that fails this check.
|
||||
@@ -143,16 +151,16 @@ Before starting any task, committing, or pushing, you **MUST** run `git pull --r
|
||||
### Making Website Changes
|
||||
1. Edit source files in `website/` (`template.html`, `style.css`, `app.js`, `lang-init.js`, or `locales/*.json`).
|
||||
2. Run the compiler: `node website/build.cjs`. This generates the multilingual pages in `www/` and minifies CSS/JS.
|
||||
3. Verify the output: `node --check website/www/app.js && node --check website/www/lang-init.js`.
|
||||
3. Verify the output: `node --check website/www/app.min.js && node --check website/www/lang-init.min.js`.
|
||||
4. Test locally: `npx serve website/www` or `python3 -m http.server 8080 -d website/www`.
|
||||
5. Commit both source changes and the updated `www/` output.
|
||||
|
||||
### Testing Locally
|
||||
1. Run the build script: `node scripts/build-extension.cjs`.
|
||||
2. Load `dist/chrome/` as an "Unpacked Extension" in Chrome (or `dist/firefox/` in Firefox).
|
||||
3. Start the server from the root: `docker-compose up --build`.
|
||||
3. Start the server from the root: `docker compose up --build`.
|
||||
4. Use **different browser profiles** or vendors to test multi-peer logic.
|
||||
5. Use the **Dev tab** to verify real-time video element metadata.
|
||||
5. Use the **Status tab** to verify real-time connection state, logs, and video element metadata.
|
||||
|
||||
### Locking Old Versions
|
||||
1. Update `MIN_VERSION` in the server's `.env` file to the minimum acceptable version.
|
||||
|
||||
@@ -6,8 +6,35 @@ All notable changes to the KoalaSync browser extension and relay server.
|
||||
|
||||
## Unreleased
|
||||
|
||||
---
|
||||
|
||||
## [v2.5.3] — 2026-07-02
|
||||
|
||||
### Fixed
|
||||
- **Extension: Disney+ force sync and seeking** — Fixed force sync on Disney+ failing or jumping to wrong positions. The extension now relies solely on accurate player-API time and fails cleanly when it isn't available yet, instead of falling back to unreliable raw video data.
|
||||
- **Extension: Force-sync accuracy** — When syncing to the group, peers whose current position isn't known yet (e.g. a Disney+ peer that just loaded) no longer pull the sync target toward the start of the video.
|
||||
- **Extension: Disney+ Host Control Mode** — Regular Disney+ content is no longer misclassified as a live stream, which had silently disabled the desync dialog and snap-back for guests in host-controlled rooms. YouTube and Twitch live detection is unchanged.
|
||||
- **Extension: Disney+ episode auto-sync** — Episode transitions and the "waiting for peers" lobby flow now work reliably on Disney+ again.
|
||||
|
||||
---
|
||||
|
||||
## [v2.5.2] — 2026-07-02
|
||||
|
||||
### Added
|
||||
- **Extension: Privacy title controls** - Advanced users can now disable sending browser tab titles separately from media titles. Media titles can still be sent in full, reduced to detected episode identifiers such as `S01E04`, or hidden entirely. Defaults remain full titles for backwards compatibility.
|
||||
- **Relay: Cleaner restart handling** — Connected clients are now disconnected explicitly during relay shutdown so reconnects recover more predictably.
|
||||
- **Relay: Stronger abuse protection** — Rapid room-leave spam is now rate-limited.
|
||||
- **Extension: Hidden remote seek diagnostics** — KoalaDev can use the hidden Dev tab to simulate remote seeks and inspect precise native/page-API timing while debugging playback integrations.
|
||||
|
||||
### Changed
|
||||
- **Extension: Shared page-API seek bridge** — Netflix and Disney+ now use a common page-level seek bridge so private player APIs can be invoked from the page context while the default HTML5 path stays unchanged.
|
||||
- **Build: Release build timestamp** — Extension builds now inject a build timestamp into the hidden Dev tab for easier local package verification.
|
||||
|
||||
### Fixed
|
||||
- **Extension: Disney+ precise sync** — Disney+ now reads time and seeks through the real page media-player API, and the temporary DOM timeline/button scraping fallback has been removed.
|
||||
- **Extension: Netflix seek reliability** — Netflix seeking keeps using the page player API with a safer session lookup path.
|
||||
- **Extension: Tab-title counter cleanup** — Leading browser notification counters such as `(14)` or `[7]` are removed from shared tab titles and matching logic without changing the existing privacy controls.
|
||||
- **Extension: Tab navigation reinjection** — Reinjecting the content script after selected-tab navigation now uses the same page-API-aware injection path.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
# WebSocket Protocol Reference
|
||||
|
||||
This document describes the relay behavior implemented by `server/index.js` and
|
||||
the event names defined in `shared/constants.js`.
|
||||
|
||||
## Transport
|
||||
|
||||
- The relay uses Socket.IO v4 events over WebSocket.
|
||||
- Long-polling is disabled (`transports: ['websocket']`, `allowUpgrades: false`).
|
||||
- Messages are Socket.IO event packets whose payload is an event name plus an
|
||||
object payload.
|
||||
- The relay caps incoming Socket.IO message size at 4 KB.
|
||||
|
||||
## Connection Handshake
|
||||
|
||||
The Socket.IO handshake must include:
|
||||
|
||||
- `token`: must match `OFFICIAL_SERVER_TOKEN`.
|
||||
- `version`: optional app version. If present, it must be a valid semver-like
|
||||
string and not older than `MIN_VERSION` (default `1.0.0`).
|
||||
|
||||
If the token is invalid, the relay emits `error` and disconnects the socket.
|
||||
If `version` is invalid or too old, the relay emits `error` and disconnects the
|
||||
socket.
|
||||
|
||||
After the socket is connected, `join_room` must include `protocolVersion`.
|
||||
It must equal `PROTOCOL_VERSION` exactly. A mismatch emits `error` and rejects
|
||||
the join attempt; it does not currently disconnect the socket.
|
||||
|
||||
## Room Join
|
||||
|
||||
### `join_room` (client -> server)
|
||||
|
||||
Payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"roomId": "string, sanitized to [A-Za-z0-9-], max 64",
|
||||
"peerId": "string, max 16",
|
||||
"username": "string, max 30",
|
||||
"password": "string, max 128, optional",
|
||||
"tabTitle": "string, max 100, optional",
|
||||
"mediaTitle": "string, max 100, optional",
|
||||
"protocolVersion": "string, max 16"
|
||||
}
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- Creates the room if it does not exist and capacity allows it.
|
||||
- The first peer becomes `hostPeerId`.
|
||||
- Rooms may have an optional password hash.
|
||||
- Joining with a duplicate `peerId` disconnects the previous socket for that peer.
|
||||
- Joining the same room with the same socket and peer is ignored as a no-op.
|
||||
- Switching rooms removes the socket from the old room first.
|
||||
|
||||
On success, the joining socket receives `room_data`.
|
||||
Other room members receive `peer_status` with `status: "joined"`.
|
||||
|
||||
### `room_data` (server -> client)
|
||||
|
||||
Payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"roomId": "string",
|
||||
"peers": ["peer state objects"],
|
||||
"activeLobby": "object or null",
|
||||
"hostPeerId": "string or null",
|
||||
"controlMode": "everyone | host-only",
|
||||
"controllers": ["peerId"],
|
||||
"capabilities": ["host-control", "co-host"]
|
||||
}
|
||||
```
|
||||
|
||||
`room_data` is sent to the joining socket. It is not the general broadcast used
|
||||
for every later room update.
|
||||
|
||||
## Room Leave
|
||||
|
||||
### `leave_room` (client -> server)
|
||||
|
||||
Payload: none.
|
||||
|
||||
Behavior:
|
||||
|
||||
- Rate-limited to 10 events per socket per minute.
|
||||
- If the socket is mapped to a room, the relay removes it from that room.
|
||||
- Remaining room members receive `peer_status` with `status: "left"` when the
|
||||
peer is no longer represented by another socket.
|
||||
- Empty rooms are deleted.
|
||||
- If the host leaves and peers remain, the relay assigns the next peer as host,
|
||||
falls back to `controlMode: "everyone"`, resets controllers to the new host,
|
||||
and broadcasts `control_mode`.
|
||||
|
||||
Exceeding the `leave_room` limit is logged and the socket is disconnected.
|
||||
|
||||
## Relayed Room Events
|
||||
|
||||
The relay accepts and sanitizes these events, then emits the same event to other
|
||||
peers in the room:
|
||||
|
||||
- `play`
|
||||
- `pause`
|
||||
- `seek`
|
||||
- `peer_status`
|
||||
- `force_sync_prepare`
|
||||
- `force_sync_ack`
|
||||
- `force_sync_execute`
|
||||
- `episode_lobby`
|
||||
- `episode_ready`
|
||||
- `episode_lobby_cancel`
|
||||
|
||||
Relayed payload fields are sanitized and may include:
|
||||
|
||||
```json
|
||||
{
|
||||
"senderId": "peerId of sender",
|
||||
"seq": "number",
|
||||
"currentTime": "number 0..86400 or null",
|
||||
"targetTime": "number 0..86400",
|
||||
"playbackState": "playing | paused",
|
||||
"username": "string, max 30",
|
||||
"tabTitle": "string, max 100 or null",
|
||||
"mediaTitle": "string, max 100 or null",
|
||||
"volume": "number 0..1",
|
||||
"muted": "boolean",
|
||||
"desynced": "boolean",
|
||||
"peerId": "sender peerId",
|
||||
"status": "string, max 16",
|
||||
"expectedTitle": "string, max 100",
|
||||
"title": "string, max 100",
|
||||
"actionTimestamp": "number"
|
||||
}
|
||||
```
|
||||
|
||||
Undefined fields are removed before relay. Raw client payloads are not forwarded.
|
||||
|
||||
## Media Control
|
||||
|
||||
### `play`, `pause`, `seek`
|
||||
|
||||
These are room-moving actions. In `host-only` mode, the relay drops them unless
|
||||
the sender is a controller.
|
||||
|
||||
Common payload fields:
|
||||
|
||||
- `currentTime` for `play`/`pause`.
|
||||
- `targetTime` for `seek`.
|
||||
- `seq` and `actionTimestamp` when the extension needs stale-command or ACK
|
||||
handling.
|
||||
|
||||
The content script applies additional client-side filtering for noisy native
|
||||
player events before it sends these events.
|
||||
|
||||
## Peer Status
|
||||
|
||||
### `peer_status`
|
||||
|
||||
Used for heartbeats and peer state updates. The extension sends it every
|
||||
`HEARTBEAT_INTERVAL` while syncing is active.
|
||||
|
||||
Typical fields:
|
||||
|
||||
- `peerId`
|
||||
- `username`
|
||||
- `tabTitle`
|
||||
- `mediaTitle`
|
||||
- `playbackState`
|
||||
- `currentTime`
|
||||
- `volume`
|
||||
- `muted`
|
||||
- `desynced`
|
||||
- `status`
|
||||
|
||||
The relay stores sanitized peer state and relays the sanitized update to other
|
||||
peers.
|
||||
|
||||
## Force Sync
|
||||
|
||||
Force sync coordination is implemented primarily in the extension. The relay
|
||||
sanitizes and relays the events.
|
||||
|
||||
### `force_sync_prepare`
|
||||
|
||||
Payload includes `targetTime`. The initiator waits for ACKs or for
|
||||
`FORCE_SYNC_TIMEOUT` before sending `force_sync_execute`.
|
||||
|
||||
In `host-only` mode, only controllers may initiate it.
|
||||
|
||||
### `force_sync_ack`
|
||||
|
||||
The extension sends ACKs with peer identity and sequence data. The relay relays
|
||||
them with the same sanitized relay envelope as other room events, including
|
||||
`senderId`.
|
||||
|
||||
### `force_sync_execute`
|
||||
|
||||
Payload includes `targetTime`. In `host-only` mode, only controllers may send it.
|
||||
The relay also allows a matching initiator's execute event after that initiator
|
||||
started the prepare step, even if their controller state changed before execute.
|
||||
|
||||
## Episode Lobby
|
||||
|
||||
Episode lobby coordination is implemented primarily in the extension. The relay
|
||||
tracks enough state to include `activeLobby` in `room_data` for later joiners.
|
||||
|
||||
### `episode_lobby`
|
||||
|
||||
Payload uses `expectedTitle`. The relay creates `activeLobby` when this field is
|
||||
present and no lobby is already active.
|
||||
|
||||
In `host-only` mode, only controllers may initiate it.
|
||||
|
||||
### `episode_ready`
|
||||
|
||||
Payload may include `title`. The relay adds the sender to the active lobby's
|
||||
ready list when a lobby exists.
|
||||
|
||||
### `episode_lobby_cancel`
|
||||
|
||||
Clears the active lobby and is relayed to peers. In `host-only` mode, only
|
||||
controllers may initiate it.
|
||||
|
||||
## Host Control Mode
|
||||
|
||||
### `set_control_mode` (client -> server)
|
||||
|
||||
Payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"controlMode": "everyone | host-only"
|
||||
}
|
||||
```
|
||||
|
||||
Only the room host may change the mode. Non-host attempts are ignored and the
|
||||
sender receives the current `control_mode` snapshot.
|
||||
|
||||
Mode changes are debounced per room with `CONTROL_MODE_MIN_INTERVAL_MS` (500 ms).
|
||||
|
||||
### `set_peer_role` (client -> server)
|
||||
|
||||
Payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"peerId": "string, max 16",
|
||||
"controller": "boolean"
|
||||
}
|
||||
```
|
||||
|
||||
Only the room host may promote or demote controllers. The host cannot demote
|
||||
themself. Role changes use the same 500 ms per-room debounce as mode changes.
|
||||
|
||||
### `control_mode` (server -> client)
|
||||
|
||||
Payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"controlMode": "everyone | host-only",
|
||||
"hostPeerId": "string or null",
|
||||
"controllers": ["peerId"]
|
||||
}
|
||||
```
|
||||
|
||||
Sent when mode or controller state changes, when host migration changes room
|
||||
authority, and when unauthorized role/mode attempts need to resync the sender.
|
||||
|
||||
## Room List
|
||||
|
||||
### `get_rooms` (client -> server)
|
||||
|
||||
Payload: none.
|
||||
|
||||
No admin token is required for this Socket.IO event.
|
||||
|
||||
Limits:
|
||||
|
||||
- Counts against the per-socket event limit.
|
||||
- Also has a 10 second per-socket cooldown.
|
||||
|
||||
### `room_list` (server -> client)
|
||||
|
||||
Payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"rooms": [
|
||||
{
|
||||
"id": "room id",
|
||||
"peerCount": 2,
|
||||
"hasPassword": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Ping, Pong, and ACK
|
||||
|
||||
### `ping`
|
||||
|
||||
Payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"t": 1234567890,
|
||||
"target": "peerId, optional"
|
||||
}
|
||||
```
|
||||
|
||||
If `target` is omitted, the relay responds to the sender with `pong`.
|
||||
If `target` is another peer in the same room, the relay sends `ping` to that peer
|
||||
with `{ "t": ..., "sender": "senderPeerId" }`.
|
||||
|
||||
### `pong`
|
||||
|
||||
Payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"t": 1234567890,
|
||||
"target": "peerId, optional"
|
||||
}
|
||||
```
|
||||
|
||||
If `target` is a peer in the same room, the relay sends `pong` to that peer with
|
||||
`{ "t": ... }`.
|
||||
|
||||
### `event_ack`
|
||||
|
||||
Client payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"targetId": "peerId",
|
||||
"actionTimestamp": 1234567890
|
||||
}
|
||||
```
|
||||
|
||||
If sender and target are still in the same room, the relay emits:
|
||||
|
||||
```json
|
||||
{
|
||||
"senderId": "sender peerId",
|
||||
"actionTimestamp": 1234567890
|
||||
}
|
||||
```
|
||||
|
||||
## Rate Limits
|
||||
|
||||
- Connections: 10 per IP per minute; excess connections are disconnected.
|
||||
- Relayed/events: 50 per socket per 10 seconds; excess disconnects the socket.
|
||||
- `get_rooms`: 10 second cooldown per socket plus the event limit.
|
||||
- `leave_room`: 10 per socket per minute; excess disconnects the socket.
|
||||
- Invalid room passwords: tracked per IP and room. Five recent failures block
|
||||
more password attempts for that room until the failure window ages out.
|
||||
- HTTP health and admin-metrics endpoints have their own rate limits outside this
|
||||
Socket.IO protocol.
|
||||
|
||||
## Capabilities
|
||||
|
||||
`room_data.capabilities` advertises server-backed features:
|
||||
|
||||
- `host-control`
|
||||
- `co-host`
|
||||
|
||||
Clients should treat a missing or unknown capabilities list as unsupported.
|
||||
+37
-5
@@ -1,8 +1,40 @@
|
||||
# Technical Documentation
|
||||
|
||||
This directory contains deep-dives into the KoalaSync protocol and architecture.
|
||||
This directory contains deep-dives into the KoalaSync protocol, architecture, roadmap, and operational guidelines.
|
||||
|
||||
- [HOW_IT_WORKS.md](HOW_IT_WORKS.md): Step-by-step walkthrough of every user flow, from room creation to synchronized playback. Ideal for store reviewers and manual testers.
|
||||
- [ARCHITECTURE.md](ARCHITECTURE.md): Communication flows, Dual Heartbeat, and Sync logic.
|
||||
- [SYNC_GUIDE.md](SYNC_GUIDE.md): Protocol constants and sync requirements.
|
||||
- [TRANSLATION.md](TRANSLATION.md): Translation and localization guide for the extension and website.
|
||||
## Start Here by Role
|
||||
|
||||
- **Users and reviewers**: Start with [HOW_IT_WORKS.md](HOW_IT_WORKS.md), then [PRIVACY.md](PRIVACY.md) and [TESTED_SERVICES.md](TESTED_SERVICES.md).
|
||||
- **Self-hosters**: Start with the root [README.md](../README.md), then [devops.md](devops.md), [PROTOCOL.md](PROTOCOL.md), and the examples in `../examples/`.
|
||||
- **Contributors**: Start with [../CONTRIBUTING.md](../CONTRIBUTING.md), then [ARCHITECTURE.md](ARCHITECTURE.md), [SYNC_GUIDE.md](SYNC_GUIDE.md), and the README for the subdirectory you are editing.
|
||||
- **AI agents**: Start with [AI_INIT.md](AI_INIT.md), then read the relevant subdirectory README before changing files.
|
||||
- **Security reviewers**: Start with [SECURITY.md](../SECURITY.md), [KNOWN_LIMITATIONS.md](KNOWN_LIMITATIONS.md), [PRIVACY.md](PRIVACY.md), and [PROTOCOL.md](PROTOCOL.md).
|
||||
|
||||
## 🏗️ Core Architecture & Design
|
||||
|
||||
- **[ARCHITECTURE.md](ARCHITECTURE.md)**: Overview of the communication flows, Dual Heartbeat architecture, and synchronization logic.
|
||||
- **[HOW_IT_WORKS.md](HOW_IT_WORKS.md)**: Step-by-step walkthrough of every user flow, from room creation to synchronized playback. Ideal for store reviewers and manual testers.
|
||||
- **[host-control-mode.md](host-control-mode.md)**: Design, requirements, and edge cases of the Host Control feature.
|
||||
- **[AI_INIT.md](AI_INIT.md)**: Maintainer and AI-agent onboarding: non-negotiables, workflow order, and safety checks.
|
||||
|
||||
## 📡 Protocol & Synchronization
|
||||
|
||||
- **[PROTOCOL.md](PROTOCOL.md)**: Low-level message format and payload descriptions for the KoalaSync sync protocol.
|
||||
- **[SYNC_GUIDE.md](SYNC_GUIDE.md)**: Guide on keeping protocol constants synchronized across the workspace.
|
||||
|
||||
## 📋 Compatibility, Roadmap & Contribution
|
||||
|
||||
- **[TESTED_SERVICES.md](TESTED_SERVICES.md)**: Status of compatibility with major streaming services and contribution guidelines for testing new platforms.
|
||||
- **[KNOWN_LIMITATIONS.md](KNOWN_LIMITATIONS.md)**: Threat model and accepted design limitations (NOFIX entries) for security audits.
|
||||
- **[PRIVACY.md](PRIVACY.md)**: Privacy model and data-handling policy for users, reviewers, and contributors.
|
||||
- **[ROADMAP.md](ROADMAP.md)**: Planned features, backlog items, and rejected proposals.
|
||||
- **[TRANSLATION.md](TRANSLATION.md)**: Guide for native speakers to contribute and audit dynamic extension/website translations.
|
||||
|
||||
## 🚀 DevOps & Releases
|
||||
|
||||
- **[devops.md](devops.md)**: Guide on the automated tag-based release pipeline.
|
||||
- **[CHANGELOG.md](CHANGELOG.md)**: Detailed history of releases and changes.
|
||||
|
||||
---
|
||||
|
||||
*For high-level project information and developer setup instructions, refer to the root [README.md](../README.md).*
|
||||
|
||||
+16
-1
@@ -60,6 +60,22 @@
|
||||
- **Legal/moderation:** Unclear what moderation requirements would apply if users can exchange chat messages. Could be relevant depending on jurisdiction.
|
||||
- **Status:** Under evaluation, may come later.
|
||||
|
||||
### Cross-frame video detection and control
|
||||
|
||||
- **Priority:** P3
|
||||
- **Category:** Compatibility / Embedded Players
|
||||
- **Background:** KoalaSync currently injects on demand into the selected tab's top frame. This works for normal top-frame players, including current Emby/Jellyfin usage, but does not cover cases where the real `<video>` lives inside a cross-origin iframe or an `about:blank`/`srcdoc` player frame.
|
||||
- **Possible approach:** Add an opt-in frame bridge where child frames announce detected videos to the top frame, and the top frame routes remote play/pause/seek commands to the active child video.
|
||||
- **Status:** Future compatibility work, not needed for current Emby behavior.
|
||||
|
||||
### Local extension E2E smoke tests
|
||||
|
||||
- **Priority:** P2
|
||||
- **Category:** Testing / Release Confidence
|
||||
- **Background:** The release verification covers unit tests, server integration, syntax, lint, audits, and builds, but it does not currently run a real browser extension flow. A small local E2E smoke suite would catch regressions in content-script injection, tab navigation reinjection, remote seek handling, and iframe player support.
|
||||
- **Possible approach:** Add a separate local-only Playwright smoke command that loads the unpacked extension, opens two controlled video pages, and verifies play/pause/seek through the actual extension path. Keep it outside `npm run verify` until it is stable enough for CI.
|
||||
- **Status:** Backlog, recommended before larger content-script or frame-bridge changes.
|
||||
|
||||
---
|
||||
|
||||
## ❌ Rejected
|
||||
@@ -70,4 +86,3 @@
|
||||
|---|---|
|
||||
| *(none yet)* | |
|
||||
|
||||
|
||||
|
||||
+7
-5
@@ -10,8 +10,9 @@ You MUST run the build script in any of the following scenarios:
|
||||
1. **After a fresh `git clone` or `git pull`** (as the synced files are ignored by git).
|
||||
2. **After modifying** `shared/constants.js`.
|
||||
3. **After modifying** `shared/blacklist.js`.
|
||||
4. **Before committing** changes to the repository if any protocol-related files were touched.
|
||||
5. **Before deploying** the server or releasing the extension.
|
||||
4. **After modifying** `shared/names.js`.
|
||||
5. **Before committing** changes to the repository if any shared protocol or extension-mirrored files were touched.
|
||||
6. **Before deploying** the server or releasing the extension.
|
||||
|
||||
## How to sync
|
||||
|
||||
@@ -24,10 +25,11 @@ npm run build:extension
|
||||
|
||||
## What does it do?
|
||||
The build script performs the following actions:
|
||||
1. Synchronizes protocol constants by copying `shared/constants.js`, `shared/blacklist.js`, and `shared/README.md` into `extension/shared/`.
|
||||
1. Synchronizes shared files by copying `shared/constants.js`, `shared/blacklist.js`, `shared/names.js`, and `shared/README.md` into `extension/shared/`.
|
||||
2. Injects `EVENTS`, `HEARTBEAT_INTERVAL`, and `episode-utils.js` functions (`extractEpisodeId`, `sameEpisode`) into `content.js` via marker-based replacement.
|
||||
3. Compiles browser-specific manifest files.
|
||||
4. Packages the final ready-to-publish extension artifacts for Chrome and Firefox into the `dist/` directory.
|
||||
3. Injects browser-specific uninstall URL constants into `background.js` and a build timestamp into `popup.html`.
|
||||
4. Compiles browser-specific manifest files.
|
||||
5. Packages the final ready-to-publish extension artifacts for Chrome and Firefox into the `dist/` directory.
|
||||
|
||||
## Protocol Versioning
|
||||
The system enforces a strict `protocolVersion` check during the `JOIN_ROOM` handshake.
|
||||
|
||||
+44
-24
@@ -1,38 +1,58 @@
|
||||
# Tested Streaming Services
|
||||
# 🎬 Tested Streaming Services & Compatibility
|
||||
|
||||
This document tracks which streaming platforms and media servers have been tested with KoalaSync.
|
||||
This document tracks which streaming platforms and media servers are supported by the KoalaSync extension.
|
||||
|
||||
| Service | Sync Works | Media Title | Episode Auto-Sync | Notes |
|
||||
|---------|:----------:|:-----------:|:-----------------:|-------|
|
||||
| **YouTube** | ✅ Full | ✅ Full | ❌ | Individual videos, not episodes — no episode auto-sync. |
|
||||
| **Twitch** | ✅ Full | ✅ Full | ❌ | Individual streams/VODs, not episodes — no episode auto-sync. |
|
||||
| **Netflix** | ✅ Full | ❌ | ❌ | No media title exposed. |
|
||||
| **Emby** | ✅ Full | ✅ Full | ✅ Full | Best-in-class support. |
|
||||
| **Jellyfin** | ✅ Full | ✅ Full | ✅ Full | — |
|
||||
| **Plex** | Not tested | Not tested | Not tested | — |
|
||||
| **Disney+** | ✅ Full | ⚠️ Partial | ❌ | Series title only (e.g. "The Simpsons"), no episode info. |
|
||||
| **Prime Video** | ✅ Full | ✅ Full | ❌ | — |
|
||||
| **HBO Max / Max** | Not tested | Not tested | Not tested | — |
|
||||
| **Crunchyroll** | Not tested | Not tested | Not tested | — |
|
||||
| **Vimeo** | Not tested | Not tested | Not tested | — |
|
||||
| **Dailymotion** | Not tested | Not tested | Not tested | — |
|
||||
| **ARD / ZDF Mediathek** | Not tested | Not tested | Not tested | — |
|
||||
> [!TIP]
|
||||
> **Contributions are highly welcome!** 🤝 Anyone can easily update this list. If you have tested a streaming service (whether it works, has issues, or is not yet listed), please help the project by submitting a quick Pull Request. See the [How to Contribute](#how-to-contribute) guide below!
|
||||
|
||||
## Legend
|
||||
---
|
||||
|
||||
## Compatibility Matrix
|
||||
|
||||
| Service | Sync Works | Media Title | Episode Auto-Sync | Last Tested | Tested By | Extension Version | Notes |
|
||||
| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :--- |
|
||||
| **YouTube** | ✅ Full | ✅ Full | ❌ N/A | — | — | — | Individual videos, not episodes. |
|
||||
| **Twitch** | ✅ Full | ✅ Full | ❌ N/A | — | — | — | Individual streams/VODs. |
|
||||
| **Netflix** | ✅ Full | ❌ | ❌ | — | — | — | No media title exposed. |
|
||||
| **Emby** | ✅ Full | ✅ Full | ✅ Full | — | — | — | Best-in-class support. |
|
||||
| **Jellyfin** | ✅ Full | ✅ Full | ✅ Full | — | — | — | — |
|
||||
| **Plex** | Not tested | Not tested | Not tested | — | — | — | — |
|
||||
| **Disney+** | ✅ Full | ⚠️ Partial | ❌ | — | — | — | Series title only (e.g. "The Simpsons"), no episode info. |
|
||||
| **Prime Video** | ✅ Full | ✅ Full | ❌ | — | — | — | — |
|
||||
| **HBO Max / Max** | Not tested | Not tested | Not tested | — | — | — | — |
|
||||
| **Crunchyroll** | Not tested | Not tested | Not tested | — | — | — | — |
|
||||
| **Vimeo** | Not tested | Not tested | Not tested | — | — | — | — |
|
||||
| **Dailymotion** | Not tested | Not tested | Not tested | — | — | — | — |
|
||||
| **ARD / ZDF Mediathek** | Not tested | Not tested | Not tested | — | — | — | — |
|
||||
| **Vix** | ✅ Full | ✅ Full | ✅ Full | — | — | — | Everything works correctly. |
|
||||
|
||||
### Legend
|
||||
|
||||
| Symbol | Meaning |
|
||||
|--------|---------|
|
||||
| :---: | :--- |
|
||||
| ✅ Full | Works without limitations. |
|
||||
| ⚠️ Partial | Works with caveats (see Notes). |
|
||||
| ❌ N/A | Not applicable or not supported. |
|
||||
| ❌ | Not supported / does not work. |
|
||||
| ❌ N/A | Not applicable (feature does not exist on the platform). |
|
||||
| **Not tested** | Has not been tested yet. |
|
||||
|
||||
---
|
||||
|
||||
## How to Contribute
|
||||
|
||||
Tested a service that's not listed? Found different behavior than documented?
|
||||
Updating this compatibility list is quick and easy! You don't need deep coding skills to contribute:
|
||||
|
||||
1. Test KoalaSync on the service with two browser profiles
|
||||
2. Use the extension's **Dev tab** to check `readyState`, `currentTime`, and media title
|
||||
3. Open a GitHub issue or PR updating this table
|
||||
1. **Fork the Repository**: Click the **Fork** button at the top of the [KoalaSync GitHub Repository](https://github.com/Shik3i/KoalaSync).
|
||||
2. **Edit this File**: Open [docs/TESTED_SERVICES.md](TESTED_SERVICES.md) in your fork's browser editor (or clone it locally) and update the table with your testing details.
|
||||
3. **Commit & Push**: Commit your changes with a clear message (e.g., `docs: update Netflix compatibility status`).
|
||||
4. **Create a Pull Request**: Submit the Pull Request (PR) from your fork to our `main` branch.
|
||||
|
||||
> [!NOTE]
|
||||
> **Reporting Problems:** If you notice a bug or partial support on a service, please open a [GitHub Issue](https://github.com/Shik3i/KoalaSync/issues) describing the problem, and link it in the **Notes** column of the table.
|
||||
>
|
||||
> _If you are unsure how to create/link an issue, don't worry! Simply submit the PR anyway, and the maintainers will gladly create and link the issue for you._
|
||||
|
||||
---
|
||||
|
||||
## Technical Background
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
# Co-Host (Multi-Controller) — Implementation Plan
|
||||
|
||||
Branch base: `feature/host-control-mode` (builds directly on it).
|
||||
Goal: let the room owner grant **playback control to several peers** (co-hosts), not
|
||||
just one — e.g. 4 of N people in a room may drive play/pause/seek, the rest are guests.
|
||||
|
||||
This is the second server-gated feature the `capabilities` hook was designed for
|
||||
(`CAPABILITIES.CO_HOST = 'co-host'`, already stubbed in `shared/constants.js`).
|
||||
|
||||
---
|
||||
|
||||
## 1. Roles
|
||||
|
||||
| Role | Can drive (play/pause/seek/force-sync/episode-lobby) | Can promote/demote + toggle mode |
|
||||
|------|------|------|
|
||||
| **Owner** (room creator, = today's "host") | yes (always a controller) | **yes** |
|
||||
| **Controller** (co-host) | yes (in `host-only` mode) | no |
|
||||
| **Guest** | only in `everyone` mode | no |
|
||||
|
||||
The single-host feature is just the special case `controllers = { owner }`.
|
||||
|
||||
## 2. Data model
|
||||
|
||||
### Server (`room` object)
|
||||
- `ownerPeerId` — the creator / manager. Keep `hostPeerId` as an **alias** (= ownerPeerId)
|
||||
so older clients keep working.
|
||||
- `controllers: Set<peerId>` — peers allowed to drive. **Always contains ownerPeerId.**
|
||||
- `controlMode: 'everyone' | 'host-only'` — unchanged wire values (`'host-only'` now means
|
||||
"restricted to controllers", not "single host").
|
||||
- `MAX_CONTROLLERS` cap (e.g. 10) to bound the set + payload.
|
||||
|
||||
### Shared constants
|
||||
- `CAPABILITIES.CO_HOST = 'co-host'` (un-stub it) → add to `SERVER_CAPABILITIES`.
|
||||
- New events:
|
||||
- `SET_PEER_ROLE` (client→server): `{ peerId, controller: boolean }` — owner promotes/demotes.
|
||||
- Extend `CONTROL_MODE` (server→client) payload: `{ controlMode, ownerPeerId, hostPeerId, controllers: [peerId...] }`.
|
||||
- `ROOM_DATA` gains `ownerPeerId` + `controllers`.
|
||||
|
||||
## 3. Gate generalization (the core change)
|
||||
|
||||
Today the gate compares against a single `hostPeerId`. Generalize to set membership:
|
||||
|
||||
- **Server relay gate** (`server/index.js`): `controlMode === 'host-only' && !room.controllers.has(mapping.peerId)` → drop. (Was `mapping.peerId !== room.hostPeerId`.)
|
||||
- **Background gates** (sender + receiver): replace `amHost()` / `senderId !== hostPeerId`
|
||||
with controller-set membership: `controllers.includes(myPeerId)` / `senderId ∈ controllers`.
|
||||
- **Helpers:** split `amHost()` into `amOwner()` (manage rights) and `amController()`
|
||||
(drive rights). The desync/snap-back path keys on `!amController()` instead of `!amHost()`.
|
||||
|
||||
`SET_PEER_ROLE` handler (server): validate sender is owner, target is a current peer in the
|
||||
room, enforce `MAX_CONTROLLERS`, always keep owner in the set, then broadcast `CONTROL_MODE`
|
||||
with the new `controllers`.
|
||||
|
||||
## 4. Client + UI
|
||||
|
||||
- **Owner** sees the peer list with a per-peer **"Controller" toggle** (promote/demote) plus
|
||||
the existing mode toggle.
|
||||
- **Controllers** see a "Controller" badge and are NOT locked out of the remote-control buttons.
|
||||
- **Guests** see "Guest" + the host-only notice (unchanged).
|
||||
- The promote UI + co-host badges render only when the relay advertises the `co-host`
|
||||
capability (feature detection, same pattern as `hostControlSupported`).
|
||||
- i18n: new keys (`ROLE_CONTROLLER`, `BTN_PROMOTE`, `BTN_DEMOTE`, …) across all locales.
|
||||
|
||||
## 5. Backwards compatibility
|
||||
|
||||
- **New client + old server** (host-control only, no `co-host` capability): no co-host UI;
|
||||
behaves as today's single-host. ✓
|
||||
- **Old client + new server**: ignores `controllers` / `SET_PEER_ROLE`. An old client that
|
||||
the owner promotes still **gates itself** (its sender-gate only knows `!amHost`), so it
|
||||
can't drive — it degrades to a guest. Co-host requires a client that understands
|
||||
`controllers`. Document this; not a crash. ✓
|
||||
- No `PROTOCOL_VERSION` bump needed — purely additive, same as host-control.
|
||||
|
||||
## 6. Edge cases
|
||||
- **Controller leaves** → `removePeerFromRoom` also does `room.controllers.delete(peerId)`.
|
||||
- **Owner leaves** → fallback: promote the earliest remaining **controller** to owner (prefer
|
||||
a controller over a random peer); if none, earliest peer; keep the rest of the set. Reuse
|
||||
the `peerJoinLocks` guard so a reconnect/second-tab doesn't demote (same fix as host).
|
||||
- **Promote a peer not in the room** → server rejects (target must be a live peer).
|
||||
- **Promote beyond `MAX_CONTROLLERS`** → server rejects, re-syncs the owner's UI.
|
||||
- **`everyone` mode** → the `controllers` set is still maintained (so flipping to `host-only`
|
||||
keeps the chosen co-hosts), it just isn't enforced while in `everyone`.
|
||||
- **peerId spoofing** → unchanged accepted limitation (see `docs/KNOWN_LIMITATIONS.md`);
|
||||
co-host doesn't widen it materially (still bounded to a temporary room).
|
||||
|
||||
## 7. Scale: the "4 of 510 people" part — read this
|
||||
|
||||
The role change above is moderate. **Putting 510 people in one room is a separate, larger
|
||||
problem** and should be its own track:
|
||||
|
||||
- `MAX_PEERS_PER_ROOM` is **25** today. 510 needs a large raise + load testing.
|
||||
- **The real bottleneck at scale is heartbeat fan-out, not control events.** Every peer
|
||||
heartbeats and the relay broadcasts each to all peers → O(N²) per interval. At 510 that's
|
||||
~510×509 / 15s ≈ **17k msg/s just for heartbeats** — the scaling wall.
|
||||
- **Co-host actually *helps* the control-event side:** in `host-only` mode only the few
|
||||
controllers emit play/pause/seek, so event *sources* drop from N to K (e.g. 4). Restricting
|
||||
who can drive is synergistic with big rooms.
|
||||
- Large rooms therefore need (independent of co-host):
|
||||
- **Heartbeat fan-out reduction** — e.g. only relay controller/owner heartbeats to everyone,
|
||||
relay guest heartbeats only to the owner/controllers (for the UI), or server-side
|
||||
aggregation into periodic snapshots instead of per-peer relay.
|
||||
- **`ROOM_DATA` payload trimming** — a 510-entry peer list is large; send counts + controller
|
||||
details, lazy-load the full roster.
|
||||
- Possibly the **socket.io Redis adapter** for horizontal scaling, and broadcast tuning.
|
||||
|
||||
## 8. Effort estimate
|
||||
- **Co-host roles** (server gate generalization + `SET_PEER_ROLE` + owner-leave fallback +
|
||||
client gates + promote UI + i18n), at the current ≤25-peer scale: **~3–4 dev days** (same
|
||||
shape as host-control itself — mostly generalizing host→controller-set).
|
||||
- **Large-room scaling (510)**: a **separate ~1–2 week** track (heartbeat redesign + payload
|
||||
trimming + cap raise + load testing), independent of co-host. Recommend shipping co-host at
|
||||
the current cap first, then scaling rooms as its own project.
|
||||
|
||||
## 9. Suggested sequencing
|
||||
1. `CAPABILITIES.CO_HOST` + `controllers`/`ownerPeerId` in room state + `ROOM_DATA` (additive).
|
||||
2. Server `SET_PEER_ROLE` + gate generalization + owner-leave fallback + WS tests.
|
||||
3. Background: controller-set membership in both gates + `amOwner`/`amController`.
|
||||
4. Popup: promote/demote toggles (owner) + Controller badge + i18n.
|
||||
5. (Separate track) large-room scaling.
|
||||
@@ -1,283 +0,0 @@
|
||||
# Host Control Mode — Branch Overview, Goals & Edge Cases
|
||||
|
||||
> **This is the canonical entry-point doc for branch `feature/host-control-mode`.**
|
||||
> If you're an agent or contributor picking this up: read this file first, then the
|
||||
> implementation plan in [`host-control-mode-plan.md`](./host-control-mode-plan.md).
|
||||
> Temporary working doc — delete or fold into permanent docs before merging to `main`.
|
||||
>
|
||||
> Status legend: 🔴 open / unresolved · 🟡 idea, needs testing · 🟢 decided
|
||||
|
||||
---
|
||||
|
||||
## 0. Implementation status
|
||||
|
||||
All five layers are implemented & pushed (server + background + content + popup + i18n).
|
||||
Automated checks green: ESLint, WS integration tests (incl. host-only gate, toggle
|
||||
reject, host-leave fallback), content video-finder, locale consistency (15 langs),
|
||||
full release verification.
|
||||
|
||||
**Still needs real-device testing** — the EC test matrix in §7 (YouTube/Netflix/
|
||||
Twitch/Disney+/Jellyfin): involuntary-pause classification (EC-1/EC-5/EC-8), snap-back
|
||||
reliability and fight-loops (EC-4), and the desync/resync flow across players. The
|
||||
intent classifier (EC-9) and snap-back cooldown are first-pass heuristics tuned by
|
||||
reading the code, not yet by watching them behave on each site.
|
||||
|
||||
Deferred by decision (see §8): host grace period on disconnect (EC-10).
|
||||
|
||||
### Capability detection (forward-compat hook)
|
||||
The relay advertises `capabilities: ['host-control']` in `ROOM_DATA`
|
||||
(`SERVER_CAPABILITIES` in server, `CAPABILITIES` in shared/constants). The client
|
||||
enables host-control UI/behavior only when the flag is present, so the feature
|
||||
degrades cleanly on an older relay (absent → off) and old clients ignore the
|
||||
field. This is the extensible hook for the planned **co-host** feature (owner
|
||||
promotes guests to additional controllers): it will add a `'co-host'` capability
|
||||
+ events without a protocol bump or breaking older relays/clients. Add new flags
|
||||
to `CAPABILITIES` / `SERVER_CAPABILITIES` as features land.
|
||||
|
||||
### Pre-test self-audit (fixed)
|
||||
- **Popup remote buttons froze for guests** — in host-only a guest's Play/Pause/SYNC
|
||||
click was gated server-side but the button stuck on "Playing"/disabled with no
|
||||
feedback. Now the remote controls are locked (disabled + tooltip) for guests, with
|
||||
backstop guards in the handlers. (popup.js)
|
||||
- **Desync dialog could break under strict CSP** — it used `innerHTML` with inline
|
||||
`style=""` attributes, which Netflix/YouTube/Disney+ strip via `style-src`. Rebuilt
|
||||
with the DOM API (CSSOM `.style` is CSP-safe) inside a **Shadow DOM** so page CSS
|
||||
can't restyle/hide it. (content.js)
|
||||
- **Live-DVR not detected (EC-15)** — `duration === Infinity` misses Twitch/YouTube
|
||||
live-DVR (finite, sliding duration). Added a `seekable.start(0) > 1` sliding-window
|
||||
heuristic in `hcmIsLive()`. (content.js)
|
||||
|
||||
### Pre-test self-audit
|
||||
- ~~**EC-4/EC-1 snap-back thrash:**~~ FIXED — implemented the **buffer-aware deferred
|
||||
snap-back** (`hcmDeferredSnapBack`): on an involuntary event, if the player isn't
|
||||
ready (`readyState<3` or seeking) we wait (poll, 8s cap) until it can play, then snap
|
||||
ONCE to the host's re-queried position instead of repeatedly fighting the buffer.
|
||||
Done defensively/player-agnostically — we can't enumerate every site, so this is safe
|
||||
whether a player fires `pause()` or only `waiting`. Aborts if the user goes solo or is
|
||||
no longer a gated guest; single pending poll (no stacking).
|
||||
- ~~**Control-mode race at join:**~~ FIXED — `hcmHandleBlocked` now treats `HOST_BLOCKED`
|
||||
as authoritative (adopts host-only/guest role) instead of re-checking local mode,
|
||||
since background only sends it to gated guests.
|
||||
- ~~**Dialog/badge text is English-only**~~ FIXED — background resolves the strings
|
||||
via GET_HCM_STRINGS (it has the i18n loader); content fetches them on init with
|
||||
English fallback. 6 new keys (HCM_DIALOG_*/HCM_BADGE_*) across all 15 locales.
|
||||
|
||||
---
|
||||
|
||||
## 1. What this branch is for
|
||||
|
||||
Origin: a GitHub feature request. When watching with larger groups, anyone can pause
|
||||
or seek and disrupt everyone else. The requester wants the room creator to optionally
|
||||
restrict control to a single **host**, the way Teleparty works — guests who try to
|
||||
pause get asked whether they want to pause *only their own* player (and desync), and
|
||||
otherwise get snapped back to the room's position.
|
||||
|
||||
**Goal:** Add an optional per-room **Host Control Mode**. A room can be switched
|
||||
between:
|
||||
- **`everyone`** (default, current behavior): anyone can play/pause/seek for the room.
|
||||
- **`host-only`**: only the host drives the room. A guest's deliberate play/pause/seek
|
||||
is not broadcast; instead they're snapped back to the room position — unless they
|
||||
explicitly choose to desync (go solo) with a "Resync" escape hatch.
|
||||
|
||||
## 2. Trust model (read this before over-engineering)
|
||||
|
||||
This is **client-side trust, by design**. It's a watch party, not a security boundary.
|
||||
The point is preventing *accidental* and *casual* disruption, not stopping someone
|
||||
determined to patch their own extension. We do **not** add auth, tokens, or
|
||||
cryptographic host identity. `peerId` is unauthenticated and that's fine here.
|
||||
|
||||
(We still gate server-side as the robust chokepoint — see plan — but that's about
|
||||
killing spam reliably, not about defeating a hostile client.)
|
||||
|
||||
## 3. Scope / non-goals
|
||||
|
||||
In scope:
|
||||
- Host designation (first joiner = host), mode toggle, host-only gating of all
|
||||
room-moving events, guest snap-back, deliberate-desync flow + resync, host UI.
|
||||
|
||||
Explicit non-goals (for this branch):
|
||||
- Authenticated / spoof-proof host identity.
|
||||
- Persistent host across server restarts (room state is in-memory).
|
||||
- Syncing around personalized ad breaks.
|
||||
- Host transfer UI (auto-fallback to `everyone` when host leaves; manual transfer
|
||||
is a possible later add).
|
||||
|
||||
## 4. Architecture summary
|
||||
|
||||
Three-layer gate for room-moving events from a non-host in host-only mode
|
||||
(`PLAY`, `PAUSE`, `SEEK`, `FORCE_SYNC_PREPARE`, `FORCE_SYNC_EXECUTE`, `EPISODE_LOBBY`):
|
||||
1. **Server** — doesn't relay them (robust chokepoint, kills spam regardless of client).
|
||||
2. **Sender (guest)** — doesn't emit; shows confirm dialog / disables host-only buttons.
|
||||
3. **Receiver** — drops any that slip through (covers old/buggy/modified clients).
|
||||
|
||||
Snap-back reuses the existing `_setSuppress` mechanism (content.js:442) so applying
|
||||
the room state programmatically doesn't echo back as a new event. Target position is
|
||||
extrapolated from the host's last known state (±1s). Full detail + code hooks in the
|
||||
plan doc.
|
||||
|
||||
---
|
||||
|
||||
## 5. The central challenge
|
||||
|
||||
Everything hard about this feature reduces to **one question** (see EC-9):
|
||||
|
||||
> How do we reliably tell a **deliberate** guest pause/seek from an **involuntary**
|
||||
> player/browser event (buffering, ads, tab throttling, source swaps, DRM hiccups)?
|
||||
|
||||
If we get this wrong, guests get spammed with desync dialogs and snap-back loops for
|
||||
things they never did. The host/role plumbing is the easy part; this classifier is the
|
||||
real work. **Design the intent-classifier before writing the gate.**
|
||||
|
||||
---
|
||||
|
||||
## 6. Edge cases
|
||||
|
||||
### EC-1 🔴 Buffering / loading fires a `pause` event
|
||||
content.js listens to `play`/`pause`/`seeked`/`loadeddata` only (content.js:1000-1003),
|
||||
not `waiting`/`stalled`. Pure HTML5 buffering fires `waiting` → harmless. But custom
|
||||
players (Netflix/YouTube/Twitch/JW) often call `video.pause()` during buffering/ads →
|
||||
real `pause` → guest gate would mis-classify as deliberate. Sub-cases: (a) initial load
|
||||
sits paused, no event, fine; (b) mid-stream stall, player-dependent; (c) seek-induced
|
||||
re-buffering may outlast the suppress window and leak. Mitigation: `isBuffering` flag
|
||||
from `waiting`/`playing`, or grace window; in host-only the guest's own state is
|
||||
irrelevant so just ignore involuntary pauses and let catch-up logic (content.js:489)
|
||||
re-sync them.
|
||||
|
||||
### EC-2 🟢 Force-Sync / Episode-Lobby abuse by guests
|
||||
Guest could seek + spam Force-Sync to drag everyone, or spam Episode-Lobby to pause
|
||||
everyone. Decision: host-only blocks guest *initiation* of `FORCE_SYNC_*` and
|
||||
`EPISODE_LOBBY`; guests may only respond (`FORCE_SYNC_ACK`, `EPISODE_READY`). Guests'
|
||||
legitimate path is the personal "Resync" button.
|
||||
|
||||
### EC-3 🟢 Host leaves the room
|
||||
Fall back to `controlMode = 'everyone'`, broadcast `CONTROL_MODE`. Never a stuck locked
|
||||
room. (Auto-promote next peer deferred.)
|
||||
|
||||
### EC-4 🔴 Snap-back fight loop (pause/play/skip back/pause/play)
|
||||
Mashing controls or a janky player → each snap-back may emit events → ping-pong.
|
||||
Mitigation: cooldown (~600ms) after snap-back; ensure snap-back runs fully under
|
||||
suppress. Also: if target is unreachable (seek past buffered range), retry K times then
|
||||
give up — no infinite loop.
|
||||
|
||||
### EC-5 🔴 Ad breaks (YouTube/Twitch/…)
|
||||
Mid-roll ads pause/swap the media element, differ per peer → desync is unavoidable and
|
||||
must NOT spam the dialog. Probably covered by EC-1 buffering grace; flag for explicit
|
||||
testing.
|
||||
|
||||
### EC-6 🟡 Snap-back target accuracy
|
||||
No continuous room clock; extrapolate from host's `currentTime` + `lastHeartbeat`
|
||||
(±1s, worse if stale). The follow-up host correction must also be suppressed so it
|
||||
doesn't read as guest input.
|
||||
|
||||
### EC-7 🟢 Old / buggy / modified guest client
|
||||
Covered by receiver-side + server-side gates.
|
||||
|
||||
### EC-8 🔴 Tab throttling / background tab
|
||||
Backgrounding throttles timers and may pause media. There's existing
|
||||
`visibilityGraceUntil` handling for seeks (content.js:892). Confirm a
|
||||
background-induced pause isn't treated as deliberate in host-only; reuse the grace flag.
|
||||
|
||||
### EC-9 🔴 What counts as "deliberate" — the central unresolved question
|
||||
Collapses EC-1/EC-5/EC-8. Candidate signals: `readyState`/`networkState`/`video.seeking`
|
||||
at event time; recent `waiting`; recent user gesture (`navigator.userActivation`,
|
||||
keydown/click); visibility/focus. Build one shared **intent-classifier** helper in
|
||||
content.js that all host-only gating flows through.
|
||||
|
||||
### EC-10 🔴 Host brief disconnect / reconnect (network blip)
|
||||
Host's wifi drops for 3s and reconnects. With "host leaves → fallback to everyone",
|
||||
a blip would silently unlock the room and demote the host (peerId persists in
|
||||
chrome.storage so they rejoin with the same id, but the server already cleared
|
||||
`hostPeerId`). Mitigation idea: short **host grace period** (e.g. keep `hostPeerId`
|
||||
reserved for ~30s after disconnect; if the same peerId rejoins, restore host + mode).
|
||||
Needs the server reaper (server:644) and `removePeerFromRoom` (server:168) to cooperate.
|
||||
|
||||
### EC-11 🔴 New guest joins mid-session in host-only mode
|
||||
On join they must (a) immediately sync to the host's current position without the host
|
||||
doing anything, and (b) see they're a guest in the UI. ROOM_DATA already carries peers;
|
||||
add `hostPeerId`/`controlMode` so a fresh client knows its role instantly. Verify the
|
||||
existing "newcomer syncs without waiting" path (content.js:542) still fires.
|
||||
|
||||
### EC-12 🔴 Desync semantics — what does "solo" actually mean?
|
||||
When a guest chooses "pause only me", do they (a) fully ignore all subsequent host
|
||||
events until they Resync, or (b) keep receiving but not auto-applying? Define clearly.
|
||||
Proposed: full solo — ignore host play/pause/seek while desynced; Resync re-attaches and
|
||||
snaps to current host position. Also: what state does Resync land them in if the host is
|
||||
currently paused vs playing?
|
||||
|
||||
### EC-13 🔴 Race: host flips to host-only exactly as a guest pauses
|
||||
Event ordering between `SET_CONTROL_MODE`/`CONTROL_MODE` and an in-flight guest `PAUSE`.
|
||||
The `seq` ordering helps, but define the tie-break. Likely: server is authoritative —
|
||||
once it has `host-only`, it drops the guest event regardless of client-side timing.
|
||||
|
||||
### EC-14 🟡 Volume / mute / audio-options must NOT be gated
|
||||
Those are per-peer, not room control. The gate must target only play/pause/seek +
|
||||
forcesync/episode — not `PEER_STATUS` volume/mute fields. Easy to over-block; add a test.
|
||||
|
||||
### EC-15 🔴 Live streams (Twitch live, live DVR)
|
||||
"Room timestamp" is fuzzy on live edge; seeking semantics differ. Decide whether
|
||||
host-only even makes sense for live, or degrade gracefully. Low priority but log it.
|
||||
|
||||
### EC-16 🟡 Host's own involuntary events still drive the room
|
||||
If the host buffers and the player auto-pauses, that pause is "allowed" and pauses
|
||||
everyone. That's existing behavior, but in host-only it means the host's buffering
|
||||
stalls the whole room. Acceptable? Probably yes (host is authoritative), but note it.
|
||||
|
||||
### EC-17 🟡 Server restart drops room state
|
||||
`hostPeerId`/`controlMode` are in-memory. After a server restart, whoever rejoins first
|
||||
becomes the new host and mode resets to `everyone`. Acceptable for now (non-goal), but
|
||||
document so it's not a surprise.
|
||||
|
||||
### EC-18 🟡 Dialog dismissed without choosing
|
||||
Guest clicks away / presses Esc on the desync prompt. Default = treat as "No" → snap
|
||||
back. Make sure an un-answered dialog can't leave them in limbo (paused + not desynced +
|
||||
no dialog).
|
||||
|
||||
### EC-19 🟡 Multiple video elements / element swap (SPA, ad → content)
|
||||
Players that swap the `<video>` element mid-session: re-attach handlers (content.js
|
||||
already re-binds on `loadeddata`) and make sure host-only gating follows the new element.
|
||||
|
||||
### EC-20 🟡 Peer list shape (object vs legacy string)
|
||||
Throughout background.js peers may be objects or bare peerId strings
|
||||
(`typeof p === 'object' ? p.peerId : p`). All new `hostPeerId` comparisons must handle
|
||||
both forms, or we get a host that's never recognized.
|
||||
|
||||
### EC-21 🟡 Mode toggle spam / rate limiting
|
||||
Host hammering the toggle → many `SET_CONTROL_MODE`. Covered by existing
|
||||
`checkEventRate` (server), but debounce in the UI and ignore no-op transitions.
|
||||
|
||||
---
|
||||
|
||||
## 7. Test matrix (fill in during dev)
|
||||
|
||||
| Player | Buffer→`pause`? | Ad behavior | Snap-back works? | Element swap? | Notes |
|
||||
|-------------------|-----------------|-------------|------------------|---------------|-------|
|
||||
| Generic HTML5 | | | | | |
|
||||
| YouTube | | | | | |
|
||||
| Netflix | | | | | |
|
||||
| Twitch (VOD) | | | | | |
|
||||
| Twitch (live) | | | | | |
|
||||
| Disney+ / DRM | | | | | |
|
||||
| Jellyfin / Emby | | | | | |
|
||||
|
||||
## 8. Decisions (audited)
|
||||
- [x] **Intent-classifier (EC-9):** A `pause`/`seek` is **involuntary** if ANY of:
|
||||
`readyState < 3`, `video.seeking`, a `waiting` fired < ~1500ms ago (`isBuffering`
|
||||
flag), inside `visibilityGraceUntil`, OR no own-tracked user gesture
|
||||
(`Date.now() - lastUserGestureAt < 1000`, via capturing keydown/pointerdown — do
|
||||
NOT use sticky `navigator.userActivation.hasBeenActive`). Bias: only *clearly*
|
||||
involuntary is ignored; everything else = deliberate. **Note:** in host-only the
|
||||
guest never broadcasts anyway, so this only decides dialog-vs-silent — a UX call,
|
||||
not a room-integrity call. Start simple, tune later.
|
||||
- [x] **Host grace on disconnect (EC-10): NOT in v1.** Immediate fallback to
|
||||
`everyone` (EC-3). A grace window risks a multi-second hard-lock if the host never
|
||||
returns. Revisit as polish once the core flow works.
|
||||
- [x] **Desync semantics (EC-12): full solo.** Ignore host play/pause/seek while
|
||||
desynced; Resync snaps to host position + adopts host play/pause state. Desync
|
||||
auto-clears on new media/episode. Requires a persistent, obvious "You are desynced"
|
||||
UI.
|
||||
- [x] **Snap-back cooldown (EC-4): until-settled, not fixed.** Suppress re-trigger
|
||||
until `readyState>=3 && playing && |Δt|<tol`, hard-cap ~1500ms. Retry target up to
|
||||
3×, then give up (no infinite loop).
|
||||
- [x] **Live streams (EC-15): degrade.** Disable the gate when
|
||||
`video.duration === Infinity`. Caveat: live-DVR may report finite duration and slip
|
||||
through — acceptable for v1.
|
||||
@@ -1,83 +0,0 @@
|
||||
# Host Control Mode — Beta Testing Guide
|
||||
|
||||
> Temporary doc for branch `feature/host-control-mode`. Remove before merge.
|
||||
> The feature needs the **server** half too — the official relay
|
||||
> (`wss://syncserver.koalastuff.net`) doesn't run it yet, so test against a beta
|
||||
> server first.
|
||||
|
||||
## 1. Run the beta relay (Docker)
|
||||
|
||||
The branch publishes the server image to GHCR under non-production tags
|
||||
(`:beta` = newest branch build, `:sha-<commit>` = immutable pin). `:latest` is
|
||||
never touched.
|
||||
|
||||
```bash
|
||||
# Log in (the package is private → PAT with read:packages)
|
||||
echo "$GHCR_PAT" | docker login ghcr.io -u <your-gh-user> --password-stdin
|
||||
|
||||
# Pull + (re)create — NOTE: `docker restart` does NOT pick up a new image,
|
||||
# you must remove and re-run (or use compose / Watchtower).
|
||||
docker pull ghcr.io/shik3i/koalasync:beta
|
||||
docker rm -f koala-beta 2>/dev/null || true
|
||||
docker run -d --name koala-beta -p 3000:3000 \
|
||||
-e SERVER_SALT='choose-your-own-salt' \
|
||||
ghcr.io/shik3i/koalasync:beta
|
||||
```
|
||||
|
||||
To always run the newest beta automatically, point **Watchtower** at the
|
||||
container — it does the pull → recreate whenever `:beta` moves.
|
||||
|
||||
The `OFFICIAL_SERVER_TOKEN` is baked into `shared/constants.js`, so no token env
|
||||
is needed. Set `SERVER_SALT` (used for room-password hashing).
|
||||
|
||||
## 2. Connect the extension to it
|
||||
|
||||
⚠️ The client **force-upgrades `ws://` to `wss://` for any non-localhost host**
|
||||
(see background.js "Upgraded to wss:// for remote host"). So a bare
|
||||
`ws://your-server:3000` will fail without TLS. Two options:
|
||||
|
||||
- **Quick (no TLS):** SSH-tunnel the port so it counts as local:
|
||||
```bash
|
||||
ssh -L 3000:localhost:3000 your-beta-server
|
||||
```
|
||||
then in the popup → **Manual Connect / Advanced → Custom →** `ws://localhost:3000`.
|
||||
- **Proper:** put the container behind a TLS reverse proxy (Caddy does automatic
|
||||
HTTPS) → `wss://beta.yourdomain`.
|
||||
|
||||
Then create/join a room.
|
||||
|
||||
## 3. ⚠️ Use two *new* clients
|
||||
|
||||
Test with **two browser profiles both running this branch build** (load unpacked
|
||||
from `extension/`, or install the built zip). A stock release client as a guest
|
||||
will be correctly gated by the server but has none of the content-side code, so
|
||||
it silently desyncs with no dialog/snap-back — that's expected degradation, not a
|
||||
bug, but it looks like one during testing.
|
||||
|
||||
## 4. Verification checklist
|
||||
|
||||
| # | Step | Expect |
|
||||
|---|------|--------|
|
||||
| 1 | Create a room (host) | Host Control card shows **Host** + the toggle |
|
||||
| 2 | Second profile joins | Guest sees no card while mode is "everyone" |
|
||||
| 3 | Host enables "Only I can control" | Guest's Play/Pause/SYNC buttons lock; card shows **Guest** |
|
||||
| 4 | Guest presses pause/space on the video | Brief flicker, snaps back to host position |
|
||||
| 5 | Guest pauses again | Dialog: "Stay in sync" / "Watch on my own" |
|
||||
| 6 | Guest → "Watch on my own" | Persistent "Solo" badge; host's peer list shows **Solo** |
|
||||
| 7 | Guest → "Resync" | Snaps back to host; Solo badge clears on both sides |
|
||||
| 8 | Guest tries Force-Sync / seek spam | Nothing propagates to the room |
|
||||
| 9 | Host disables host-only | Card hides for guest; controls unlock |
|
||||
| 10 | Host leaves the room | Room falls back to "everyone"; a remaining peer becomes host |
|
||||
| 11 | Reload the guest's page while desynced | Still shows Solo (state survives reload) |
|
||||
| 12 | Switch the popup language | Dialog/badge text is localized |
|
||||
|
||||
## 5. Capability detection
|
||||
|
||||
The relay advertises `capabilities: ['host-control']` in `ROOM_DATA`. The client
|
||||
only enables the feature when that flag is present, so:
|
||||
- against this beta server → feature on;
|
||||
- against the old official server → feature cleanly hidden (no errors).
|
||||
|
||||
This is the extensible hook for the planned **co-host** feature (owner promotes
|
||||
guests to additional controllers) — it'll add a `'co-host'` capability + events
|
||||
without breaking older relays/clients.
|
||||
@@ -1,122 +0,0 @@
|
||||
# Host Control Mode — Implementierungsplan
|
||||
|
||||
Branch: `feature/host-control-mode`
|
||||
Issue: GitHub feature request (wasserrutschentester) — nur Host darf den Raum steuern; Gäste werden zurückgesnappt oder gehen bewusst in Desync.
|
||||
|
||||
## Ziel
|
||||
|
||||
Ein Raum kann zwischen zwei Modi umgeschaltet werden:
|
||||
- **`everyone`** (Default, heutiges Verhalten): jeder kann play/pause/seek für alle auslösen.
|
||||
- **`host-only`**: nur der Host steuert den Raum. Pause/Seek eines Gasts wird **nicht** gebroadcastet; stattdessen snappt die eigene Extension den Gast zurück auf den Raum-Zustand — es sei denn, der Gast entscheidet sich bewusst für Desync.
|
||||
|
||||
Trust-Modell: client-seitig durchgesetzt. Kein Token, keine Auth. Es geht um versehentliches Stören, nicht um Angriffsschutz.
|
||||
|
||||
---
|
||||
|
||||
## Datenmodell
|
||||
|
||||
### Server (`server/index.js`, Room-Objekt ~Z.331)
|
||||
Room bekommt zwei neue Felder:
|
||||
```js
|
||||
room = {
|
||||
...,
|
||||
hostPeerId: peerId, // gesetzt beim Anlegen = erster Joiner
|
||||
controlMode: 'everyone', // 'everyone' | 'host-only'
|
||||
}
|
||||
```
|
||||
- In `ROOM_DATA` (~Z.418) mitschicken: `hostPeerId`, `controlMode`.
|
||||
- Neues Event `SET_CONTROL_MODE` (siehe unten): nur akzeptieren, wenn `senderPeerId === room.hostPeerId`. Server setzt `room.controlMode`, broadcastet die Änderung an alle.
|
||||
- **Host-Migration:** in `removePeerFromRoom` (~Z.168) — wenn der gehende Peer `hostPeerId` war: entweder neuen Host bestimmen (nächster Peer) **oder** `controlMode` auf `everyone` zurückfallen lassen. → Entscheidung: **Fallback auf `everyone`** (simpel, nie verwaister gesperrter Raum). Optional später: Host-Transfer-Button.
|
||||
|
||||
### Shared Constants (`shared/constants.js`)
|
||||
Neue Events im `EVENTS`-Objekt:
|
||||
```js
|
||||
SET_CONTROL_MODE: "set_control_mode", // Client->Server: Host ändert Modus
|
||||
CONTROL_MODE: "control_mode", // Server->Client: Modus geändert { controlMode, hostPeerId }
|
||||
```
|
||||
⚠️ Danach `node scripts/build-extension.cjs` laufen lassen (Single Source of Truth propagieren). Ggf. `PROTOCOL_VERSION` bumpen — **nein, nur wenn alte Clients brechen würden**. Da alles additiv ist und alte Clients die neuen Felder/Events einfach ignorieren, ist KEIN Protokoll-Bump nötig. (Alter Client in host-only-Raum kennt den Modus nicht und sendet weiter → Host-Extensions ignorieren fremde Events nicht... → siehe Edge Case 7. Evtl. doch Bump erwägen.)
|
||||
|
||||
### Extension State (`background.js`)
|
||||
```js
|
||||
let controlMode = 'everyone';
|
||||
let hostPeerId = null;
|
||||
// abgeleitet: const amHost = () => hostPeerId === peerId;
|
||||
```
|
||||
Aus `ROOM_DATA` / `CONTROL_MODE` befüllen, in `chrome.storage.session` persistieren (wie `currentRoom`).
|
||||
|
||||
---
|
||||
|
||||
## Implementierung nach Schichten
|
||||
|
||||
### 1. Server (`server/index.js`)
|
||||
- [ ] Room-Objekt um `hostPeerId` + `controlMode` erweitern (~Z.331).
|
||||
- [ ] `ROOM_DATA`-Payload erweitern (~Z.418).
|
||||
- [ ] Handler `SET_CONTROL_MODE`: validieren (Host-Check + Wert in {everyone, host-only}), setzen, `CONTROL_MODE` an Raum broadcasten.
|
||||
- [ ] Host-Migration in `removePeerFromRoom`: Fallback auf `everyone` + neues `CONTROL_MODE` broadcasten, wenn Host geht.
|
||||
- [ ] `SET_CONTROL_MODE` in die `relayEvents`-Liste? **Nein** — eigener Handler, da Sonderlogik + Host-Check. (relayEvents broadcastet blind.)
|
||||
|
||||
### 2. Shared / Build
|
||||
- [ ] `EVENTS.SET_CONTROL_MODE`, `EVENTS.CONTROL_MODE` ergänzen.
|
||||
- [ ] `node scripts/build-extension.cjs`.
|
||||
|
||||
### 3. background.js (Gast-Logik = Kern)
|
||||
- [ ] `controlMode` / `hostPeerId` aus `ROOM_DATA` (~Z.875) und neuem `CONTROL_MODE`-Case übernehmen + persistieren + an Popup/Content pushen.
|
||||
- [ ] **Emit-Gate** im SEND-Pfad (~Z.1786): bei `host-only && !amHost()` und action ∈ {play, pause, seek}:
|
||||
- NICHT `emit`en.
|
||||
- Stattdessen Content-Script anweisen: "snap back" ODER Desync-Confirm anzeigen.
|
||||
- [ ] **Snap-Back-Zielzeit berechnen:** aus Host-Peer-State (`playbackState`, `currentTime`, `lastHeartbeat`) extrapolieren:
|
||||
`targetTime = host.currentTime + (host.playbackState==='playing' ? (now - host.lastHeartbeat)/1000 : 0)`.
|
||||
Genauigkeit ~±1s, für Watchparty ok. (Force-Sync-Maschinerie als Referenz für Ziel-Zeit-Koordination.)
|
||||
- [ ] Nachricht an content.js: `{ type: 'HOST_BLOCK', action, targetTime, hostPlaybackState }`.
|
||||
|
||||
### 4. content.js (Player-Reaktion + Dialog)
|
||||
- [ ] Handler für `HOST_BLOCK`: Confirm-Dialog im Player-Overlay rendern:
|
||||
"Pause only your own player and desync from the group? [Yes] [No]".
|
||||
- **No** (Default): Player via bestehende `_setSuppress`-Mechanik ([content.js:442](../extension/content.js:442)) wieder in Raum-Zustand zwingen (play + seek auf targetTime). Suppress verhindert Re-Broadcast.
|
||||
- **Yes:** lokal pausiert lassen, `isDesynced = true` setzen, dezenten "Desynced — Resync"-Button zeigen.
|
||||
- [ ] "Resync"-Button → snappt zurück auf aktuelle Raum-Zeit, `isDesynced = false`.
|
||||
- [ ] **Loop-Schutz:** nach einer Snap-Back-Aktion kurzes Cooldown-Fenster (z.B. 600ms), in dem weitere lokale pause/seek-Events nicht erneut den Dialog triggern (verhindert pause→play→pause-Pingpong).
|
||||
|
||||
### 5. popup (Host-UI)
|
||||
- [ ] Host-Toggle "Only I can control" (nur sichtbar wenn `amHost()`), sendet `SET_CONTROL_MODE`.
|
||||
- [ ] Rollen-Badge: "Host" / "Guest" + aktueller Modus.
|
||||
- [ ] Gast-Hinweis wenn host-only aktiv: "The host controls playback".
|
||||
- [ ] i18n-Keys in `extension/_locales` / `locales` für ~15 Sprachen.
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases (Test-Checkliste)
|
||||
|
||||
1. **Pause nicht verhinderbar, nur revidierbar** → kurzer Flicker (~½s) beim Gast ist erwartet/akzeptabel.
|
||||
2. **Snap-Back-Zielzeit** aus Heartbeat extrapoliert, ±1s. Bei stark veraltetem Host-State (kein Heartbeat) → letzten bekannten Wert nehmen.
|
||||
3. **Kampf-Loop pause/play/skip back/pause/play** → Cooldown-Fenster nach Snap-Back. Testen mit aggressivem Mashing.
|
||||
4. **Desync-Escape:** Gast kann bewusst pausieren (Klo/Telefon) → "Yes" → solo, dann Resync.
|
||||
5. **Host verlässt Raum** → Fallback auf `everyone`, alle bekommen `CONTROL_MODE`-Update. Testen: Host schließt Tab / Disconnect / Netzabbruch.
|
||||
6. **host-only + Episode-Auto-Sync / Force-Sync** (server/index.js:503-516): **Gast darf NICHT initiieren.** Force-Sync trägt eine `targetTime` und zwingt ALLE darauf (background.js:1261) — ein Gast könnte seeken → Force-Sync spammen und damit host-only komplett aushebeln. Episode-Lobby pausiert ebenfalls alle. → Im host-only-Modus dürfen `FORCE_SYNC_PREPARE`/`FORCE_SYNC_EXECUTE` und `EPISODE_LOBBY` nur vom Host **initiiert** werden. Gäste dürfen weiterhin nur **reagieren**: `FORCE_SYNC_ACK`, `EPISODE_READY`. Gäste brauchen Force-Sync nicht — ihr legitimer Fall ist der "Resync"-Button (snappt nur sie selbst, nicht alle).
|
||||
7. **Alter Client (ohne Feature) in host-only-Raum** → kennt Modus nicht, sendet weiter pause/seek → andere Extensions wenden es an. Mitigation: Empfänger-seitiges Gate (host-only-Clients ignorieren play/pause/seek von Nicht-Host) ODER `MIN_VERSION`/Protokoll-Bump. → **Empfehlung: zusätzlich Empfänger-seitig filtern** (robuster als nur Sender-Gate).
|
||||
8. **Seek getrennt von Pause** → host-only blockt auch Gast-Seeks, nicht nur Pausen.
|
||||
9. **Mehrere Tabs / Multi-Peer mit gleicher peerId** (Dedup, server:381) → Host-Identität bleibt an peerId hängen, ok.
|
||||
10. **DAU-Verwirrung** "warum kann ich nicht mehr pausieren?" → klare UI-Botschaft + der Desync-Dialog erklärt sich selbst.
|
||||
|
||||
## Architektur-Entscheidung zu Edge Case 7 (wichtig)
|
||||
Wir setzen das Gate **doppelt** und über **alle raum-verschiebenden Events**, nicht nur play/pause/seek:
|
||||
Geblockte Initiierungen für Nicht-Host im host-only-Modus:
|
||||
`PLAY`, `PAUSE`, `SEEK`, `FORCE_SYNC_PREPARE`, `FORCE_SYNC_EXECUTE`, `EPISODE_LOBBY`.
|
||||
Weiterhin erlaubt für Gäste (reine Reaktion, verschiebt niemanden): `FORCE_SYNC_ACK`, `EPISODE_READY`, `PEER_STATUS`, `PING`/`PONG`.
|
||||
|
||||
- **Sender-seitig** (Gast sendet erst gar nicht) → saubere UX, Confirm-Dialog bei play/pause/seek; Force-Sync-/Episode-Lobby-Buttons im Gast-UI deaktiviert/ausgeblendet.
|
||||
- **Empfänger-seitig** (in `handleServerEvent`, background.js:969 + Force-Sync-/Episode-Cases): wenn `host-only` und `data.senderId !== hostPeerId` → Event verwerfen (nicht an Content routen, keine State-Mutation).
|
||||
So sind auch alte/buggy/manipulierte Clients abgedeckt, ohne harten Protokoll-Bump.
|
||||
|
||||
Optional zusätzlich **server-seitig** in den `relayEvents` (server/index.js:445): im host-only-Modus Initiierungs-Events von Nicht-Host gar nicht erst relayen. Spart Traffic + deckt alles zentral ab. Empfehlenswert, da der Server `hostPeerId`/`controlMode` ohnehin kennt.
|
||||
|
||||
---
|
||||
|
||||
## Reihenfolge der Umsetzung (kleine, testbare Schritte)
|
||||
1. Constants + Build (Events da, nichts kaputt).
|
||||
2. Server: hostPeerId/controlMode + ROOM_DATA + SET_CONTROL_MODE + Migration.
|
||||
3. background.js: State übernehmen + Empfänger-seitiges Gate (Edge 7) — testbar ohne UI.
|
||||
4. background.js: Sender-seitiges Gate + Snap-Back-Zielzeit.
|
||||
5. content.js: Snap-Back-Apply + Confirm-Dialog + Loop-Cooldown.
|
||||
6. popup: Host-Toggle + Badge + i18n.
|
||||
7. Durchtesten der Edge-Case-Liste auf YT / Netflix / generischem HTML5-Player.
|
||||
@@ -0,0 +1,151 @@
|
||||
# Host Control Mode
|
||||
|
||||
This document describes the Host Control Mode implementation in the relay and
|
||||
extension. It only covers behavior implemented in the current codebase.
|
||||
|
||||
## Modes
|
||||
|
||||
### `everyone`
|
||||
|
||||
- Default room mode.
|
||||
- Any peer may send room-moving playback events.
|
||||
|
||||
### `host-only`
|
||||
|
||||
- Only controllers may send room-moving playback events.
|
||||
- The host is always a controller.
|
||||
- The host can promote additional peers to controllers.
|
||||
- Guests can keep watching locally in solo/desynced mode, but their local actions
|
||||
still do not drive the shared room.
|
||||
|
||||
Room-moving events are:
|
||||
|
||||
- `play`
|
||||
- `pause`
|
||||
- `seek`
|
||||
- `force_sync_prepare`
|
||||
- `force_sync_execute`
|
||||
- `episode_lobby`
|
||||
- `episode_lobby_cancel`
|
||||
|
||||
Heartbeats, force-sync ACKs, episode-ready events, ping/pong, and command ACKs
|
||||
remain allowed for guests.
|
||||
|
||||
## Server State
|
||||
|
||||
Rooms store Host Control state in memory:
|
||||
|
||||
```js
|
||||
{
|
||||
hostPeerId,
|
||||
controlMode,
|
||||
controllers,
|
||||
lastControlModeChangeAt,
|
||||
lastRoleChangeAt,
|
||||
forceSyncInitiator
|
||||
}
|
||||
```
|
||||
|
||||
`controllers` is a `Set` on the server and is serialized as an array in
|
||||
`room_data` and `control_mode`.
|
||||
|
||||
State is not persisted across relay restarts.
|
||||
|
||||
## Authority Rules
|
||||
|
||||
### Changing mode
|
||||
|
||||
Only `hostPeerId` may send `set_control_mode`.
|
||||
|
||||
Valid values:
|
||||
|
||||
- `everyone`
|
||||
- `host-only`
|
||||
|
||||
Invalid values are ignored. Non-host attempts are ignored and the sender receives
|
||||
the current `control_mode` snapshot so optimistic UI can revert.
|
||||
|
||||
Mode changes are debounced per room for 500 ms.
|
||||
|
||||
### Promoting and demoting controllers
|
||||
|
||||
Only `hostPeerId` may send `set_peer_role`.
|
||||
|
||||
The host cannot demote themself. No-op role changes are ignored. Role changes are
|
||||
debounced per room for 500 ms.
|
||||
|
||||
### Host leaving
|
||||
|
||||
When the host leaves and peers remain:
|
||||
|
||||
- the next peer becomes `hostPeerId`;
|
||||
- `controlMode` falls back to `everyone`;
|
||||
- `controllers` is reset to the new host;
|
||||
- the relay broadcasts `control_mode`.
|
||||
|
||||
When a non-host controller leaves, the relay removes that peer from
|
||||
`controllers` and broadcasts `control_mode`.
|
||||
|
||||
## Enforcement
|
||||
|
||||
The implementation has two enforcement points:
|
||||
|
||||
- The extension background script blocks local guest attempts in `host-only` and
|
||||
sends `HOST_BLOCKED` to the content script for local UX.
|
||||
- The relay drops room-moving events from non-controllers in `host-only`, so old
|
||||
or modified clients cannot drive the room.
|
||||
|
||||
The relay is the authority for room-wide effects.
|
||||
|
||||
## Guest UX
|
||||
|
||||
When a guest action is blocked locally, the content script classifies it:
|
||||
|
||||
- deliberate user action: show the host-control dialog;
|
||||
- likely involuntary player action (buffering, tab refocus, no recent gesture):
|
||||
silently snap back when safe;
|
||||
- live/DVR stream: degrade without forcing snap-back.
|
||||
|
||||
The dialog offers:
|
||||
|
||||
- stay in sync: resync to the host;
|
||||
- watch on my own: enter solo/desynced mode.
|
||||
|
||||
In solo/desynced mode:
|
||||
|
||||
- the guest can control their local video;
|
||||
- host room commands are ignored locally, except force-sync preparation is ACKed
|
||||
so the host's flow can continue;
|
||||
- the guest can resync to the host.
|
||||
|
||||
The extension reports `desynced` in peer status so the host UI can show that a
|
||||
guest is watching solo.
|
||||
|
||||
## Force Sync Edge Case
|
||||
|
||||
The relay tracks `forceSyncInitiator` after a controller sends
|
||||
`force_sync_prepare`.
|
||||
|
||||
This allows that same initiator's `force_sync_execute` through even if their
|
||||
controller role changes before execute arrives. Without this, a demotion in the
|
||||
middle of a force-sync flow could leave peers waiting after prepare.
|
||||
|
||||
The relay clears `forceSyncInitiator` after execute or when the initiator leaves.
|
||||
|
||||
## Capabilities
|
||||
|
||||
The relay advertises Host Control support in `room_data.capabilities`:
|
||||
|
||||
- `host-control`
|
||||
- `co-host`
|
||||
|
||||
The extension hides or disables matching UI when capabilities are missing.
|
||||
|
||||
## Related Events
|
||||
|
||||
See [PROTOCOL.md](PROTOCOL.md) for payloads and relay behavior for:
|
||||
|
||||
- `set_control_mode`
|
||||
- `control_mode`
|
||||
- `set_peer_role`
|
||||
- host-only gated relay events
|
||||
+4
-2
@@ -1,6 +1,6 @@
|
||||
export default [
|
||||
{
|
||||
ignores: ["dist/**", "node_modules/**", "scratch/**"]
|
||||
ignores: ["coverage/**", "dist/**", "node_modules/**", "scratch/**", "website/www/**"]
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
@@ -18,6 +18,8 @@ export default [
|
||||
setInterval: "readonly",
|
||||
clearTimeout: "readonly",
|
||||
clearInterval: "readonly",
|
||||
requestAnimationFrame: "readonly",
|
||||
cancelAnimationFrame: "readonly",
|
||||
fetch: "readonly",
|
||||
CustomEvent: "readonly",
|
||||
MutationObserver: "readonly",
|
||||
@@ -58,7 +60,7 @@ export default [
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["server/**/*.js", "scripts/**/*.js", "scripts/**/*.cjs", "website/build.cjs"],
|
||||
files: ["server/**/*.js", "scripts/**/*.js", "scripts/**/*.cjs", "website/build.cjs", "website/**/*.cjs"],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
require: "readonly",
|
||||
|
||||
@@ -9,6 +9,7 @@ services:
|
||||
- MIN_VERSION=1.0.0 # Minimum client version allowed to connect
|
||||
- MAX_ROOMS=100 # Maximum number of rooms that can exist
|
||||
- MAX_PEERS_PER_ROOM=25 # Maximum number of peers allowed per room
|
||||
- SERVER_SALT=CHANGE_ME_GENERATE_WITH_OPENSSL_RAND_BASE64_32 # Required: unique random salt for room-password hashes
|
||||
- ADMIN_METRICS_TOKEN= # Optional: 32+ char random token for aggregate-only /health metrics
|
||||
pids_limit: 2048 # Limits the container to 2048 process IDs for safety
|
||||
networks: # Attaches the service to the networks listed below
|
||||
|
||||
@@ -11,6 +11,7 @@ services:
|
||||
- MIN_VERSION=1.0.0 # Minimum client version allowed to connect
|
||||
- MAX_ROOMS=100 # Maximum number of rooms that can exist
|
||||
- MAX_PEERS_PER_ROOM=25 # Maximum number of peers allowed per room
|
||||
- SERVER_SALT=CHANGE_ME_GENERATE_WITH_OPENSSL_RAND_BASE64_32 # Required: unique random salt for room-password hashes
|
||||
- ADMIN_METRICS_TOKEN= # Optional: 32+ char random token for aggregate-only /health metrics
|
||||
pids_limit: 2048 # Limits the container to 2048 process IDs for safety
|
||||
networks: # Attaches the service to the networks listed below
|
||||
|
||||
+86
-32
@@ -1,52 +1,106 @@
|
||||
# KoalaSync Browser Extension
|
||||
|
||||
A Manifest V3 Browser Extension (Chrome & Firefox) for synchronized video playback across any website.
|
||||
This directory contains the Manifest V3 browser extension for Chrome and Firefox. It owns the popup UI, background service worker, content-script video control, invitation bridge, audio processing, and all browser-local settings.
|
||||
|
||||
## Where You Are
|
||||
|
||||
- `manifest.base.json` is the source manifest. The build script creates browser-specific `manifest.json` files in `dist/chrome/` and `dist/firefox/`.
|
||||
- `background.js` is the long-lived coordinator: WebSocket client, room state, host-control authority, heartbeat, reconnects, tab selection, and content-script injection.
|
||||
- `content.js` runs in the selected video tab. It detects video state, applies remote play/pause/seek, handles episode transitions, and applies local audio processing.
|
||||
- `popup.html` and `popup.js` implement the visible extension UI.
|
||||
- `extension/shared/` is generated by `npm run build:extension` from the root `shared/` directory. Do not edit it directly.
|
||||
|
||||
## Key Features
|
||||
- **Manifest V3**: Optimized Service Worker architecture with session persistence.
|
||||
- **Pure Vanilla JS**: No external dependencies or heavy libraries.
|
||||
- **Smart Peer IDs**: Hexadecimal IDs combined with customizable Usernames for easy identification.
|
||||
- **On-Demand Connection**: The service worker only maintains a WebSocket connection while you're in a room. No persistent background connections — privacy-first architecture. Based on `connectIntent` flag that gates all reconnect attempts.
|
||||
- **Live Diagnostics**: Built-in "Dev" tab for real-time video state debugging (ReadyState, CurrentTime, etc.).
|
||||
- **Dynamic i18n (Multi-Language)**: Fully localized in 13 languages (`en`, `de`, `fr`, `es`, `it`, `pl`, `tr`, `nl`, `ja`, `ko`, `pt-BR`, `pt`, `ru`) with auto-detected fallback and dynamic on-the-fly language selectors.
|
||||
|
||||
- **Manifest V3**: Service-worker architecture with session persistence and explicit keep-alive handling.
|
||||
- **Pure Vanilla JS**: No extension runtime dependencies and no bundler inside `extension/`.
|
||||
- **On-Demand Connection**: The service worker connects only while the user intends to be in a room.
|
||||
- **Host Control & Co-Hosts**: Hosts can switch a room into `host-only` mode and grant controller rights to trusted peers.
|
||||
- **Episode Auto-Sync**: Title/episode changes can open a lobby so peers resume together once everyone is ready.
|
||||
- **Smart Matching & Title Privacy**: Matching video tabs are highlighted, while tab/media title sharing can be reduced or disabled.
|
||||
- **Audio Processing**: Optional local compressor settings live in the dedicated audio options page.
|
||||
- **Status Diagnostics**: The Status tab exposes connection state, ping, video debug data, action history, and copyable logs.
|
||||
- **Dynamic i18n**: 15 languages are supported: `en`, `de`, `fr`, `es`, `it`, `nl`, `pl`, `pt`, `pt-BR`, `tr`, `ru`, `ja`, `ko`, `zh`, and `uk`.
|
||||
|
||||
## Tab Overview
|
||||
1. **Room**: Manage connections, view active peers, and share invitation links.
|
||||
2. **Sync**: Control video playback (Play/Pause/Force Sync) and view recent activity.
|
||||
3. **Settings**: Customize your Username, toggle domain-based Noise Filtering, and switch the App Language.
|
||||
4. **Dev**: Monitor connection status and view real-time video element metadata for debugging.
|
||||
|
||||
1. **Room**: Select official/custom server, create or join rooms, view peers, share invite links, and manage Host Control when supported by the relay.
|
||||
2. **Sync**: Select the video tab, send play/pause/seek/force-sync actions, and view episode lobby state.
|
||||
3. **Settings**: Configure username, title sharing, noise filtering, auto episode sync, notifications, language, and audio options.
|
||||
4. **Status**: Inspect connection state, latency, video debug info, history, and logs for bug reports.
|
||||
5. **Dev**: Hidden developer-only controls shown for the `KoalaDev` username.
|
||||
|
||||
## Privacy & Permissions
|
||||
KoalaSync requires `<all_urls>` permission to detect and interact with video elements (`<video>`) on websites.
|
||||
- **No Browsing History**: We do not track or store your browsing history.
|
||||
- **State Management**: Sensitive data (Room Passwords) is stored locally using `chrome.storage`.
|
||||
- **Zero Telemetry**: No analytics or external tracking scripts.
|
||||
- **Zero Runtime Dependencies**: The extension is built with pure Vanilla JS and contains no external libraries or tracking scripts, ensuring performance and privacy.
|
||||
|
||||
KoalaSync requires `<all_urls>` host permission so it can detect and control `<video>` elements on arbitrary sites.
|
||||
|
||||
- No browsing history is collected or uploaded.
|
||||
- Room credentials and user settings are stored locally with `chrome.storage`.
|
||||
- No analytics, external scripts, external fonts, or tracking libraries are used by the extension.
|
||||
- Audio processing is local to the selected tab.
|
||||
- Title privacy controls decide whether tab/media titles are shared with room peers.
|
||||
|
||||
## Installation
|
||||
1. **Prepare Extension**: From the repository root, run:
|
||||
```bash
|
||||
npm run build:extension
|
||||
```
|
||||
2. Open Chrome and go to `chrome://extensions/`.
|
||||
3. Enable **Developer mode** (top right).
|
||||
4. Click **Load unpacked** and select the `dist/chrome` folder.
|
||||
|
||||
From the repository root:
|
||||
```bash
|
||||
npm install
|
||||
npm run build:extension
|
||||
```
|
||||
|
||||
Then load the generated bundle:
|
||||
|
||||
- Chrome/Chromium: open `chrome://extensions/`, enable Developer Mode, and load `dist/chrome`.
|
||||
- Firefox: open `about:debugging`, choose **This Firefox**, and load `dist/firefox/manifest.json`.
|
||||
|
||||
## Development
|
||||
If you modify `shared/constants.js`, you must synchronize the changes by running the build script from the root:
|
||||
|
||||
Run the build whenever shared protocol files or extension packaging inputs change:
|
||||
```bash
|
||||
npm run build:extension
|
||||
```
|
||||
This ensures that the `extension/shared` folder is updated with the latest protocol constants.
|
||||
|
||||
The build copies `shared/constants.js`, `shared/blacklist.js`, `shared/names.js`, and `shared/README.md` into `extension/shared/`, injects synchronous constants into `content.js`, generates browser manifests, and creates zip artifacts in `dist/`.
|
||||
|
||||
Useful focused checks from the repository root:
|
||||
```bash
|
||||
node -c extension/background.js
|
||||
node -c extension/content.js
|
||||
node -c extension/popup.js
|
||||
node scripts/test-episode-utils.mjs
|
||||
node scripts/test-title-privacy.mjs
|
||||
node scripts/test-audio-settings.mjs
|
||||
node scripts/test-locales.cjs
|
||||
```
|
||||
|
||||
For the full suite, run:
|
||||
```bash
|
||||
npm run verify
|
||||
```
|
||||
|
||||
## Do Not Break
|
||||
|
||||
- Keep `content.js` synchronous and IIFE-based; it cannot import ES modules directly.
|
||||
- Keep the injection markers used by `scripts/build-extension.cjs`.
|
||||
- Keep protocol names in `shared/constants.js` as the source of truth.
|
||||
- Keep extension runtime dependencies at zero unless the project explicitly decides to introduce a bundler.
|
||||
- Keep all extension assets self-hosted.
|
||||
|
||||
## Module Structure
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `background.js` | Service worker: message routing, tab listeners, startup |
|
||||
| `content.js` | Video detection, audio processing, episode transition (IIFE) |
|
||||
| `popup.js` | Popup UI: join/create, tabs, status, settings |
|
||||
| `bridge.js` | Landing page bridge (injected into sync.koalastuff.net) |
|
||||
| `episode-utils.js` | Shared `extractEpisodeId()` / `sameEpisode()` — used by background.js, injected into content.js at build time |
|
||||
| `i18n.js` | Translation loader |
|
||||
| `shared/` | Constants, blacklist, name generator |
|
||||
| `background.js` | Service worker: WebSocket protocol, room state, host control, tab/content routing, reconnects |
|
||||
| `content.js` | Video detection/control, audio processing, episode transition, host-only guest behavior |
|
||||
| `popup.js` | Popup UI: room join/create, tabs, settings, peer list, status, diagnostics |
|
||||
| `popup.html` | Popup markup, tabs, onboarding, status/debug surfaces |
|
||||
| `bridge.js` | Invitation bridge injected into `sync.koalastuff.net` |
|
||||
| `episode-utils.js` | Shared episode-title parser imported by background and injected into content at build time |
|
||||
| `title-privacy.js` | Tab/media title privacy modes and sanitization helpers |
|
||||
| `audio-options.html` / `audio-options.js` / `audio-options.css` | Dedicated local audio-processing settings page |
|
||||
| `page-api-seek-overrides.js` | Page-level seek bridge for site-specific player APIs |
|
||||
| `modules/tab-manager.js` | Tab lifecycle helper used by the background service worker |
|
||||
| `i18n.js` | Dynamic locale loader and DOM translation helper |
|
||||
| `locales/` | Runtime popup translations |
|
||||
| `_locales/` | Browser-store manifest translations |
|
||||
| `shared/` | Generated mirror of root shared constants, blacklist, names, and README |
|
||||
|
||||
+249
-10
@@ -4,6 +4,7 @@ import { loadLocale, getMessage, getSystemLanguage } from './i18n.js';
|
||||
import { sameEpisode } from './episode-utils.js';
|
||||
import { applyTitlePrivacyToPayload, sanitizeSharedTitle, sanitizeTabTitle, normalizeSendTabTitle, normalizeTitlePrivacyMode } from './title-privacy.js';
|
||||
import { initTabManager } from './modules/tab-manager.js';
|
||||
import './page-api-seek-overrides.js';
|
||||
|
||||
// --- Uninstall URL Initialization ---
|
||||
let uninstallURLInitPromise = null;
|
||||
@@ -1592,6 +1593,188 @@ async function routeToContent(action, payload) {
|
||||
_routeToContentInternal(tabId, action, payload, actionTimestamp, commandSenderId, 0);
|
||||
}
|
||||
|
||||
function getTabVideoState(tabId) {
|
||||
return new Promise((resolve) => {
|
||||
chrome.tabs.sendMessage(tabId, { type: 'GET_VIDEO_STATE' }, (res) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
resolve({ error: chrome.runtime.lastError.message });
|
||||
return;
|
||||
}
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function getReadyTabVideoState(tabId) {
|
||||
let state = await getTabVideoState(tabId);
|
||||
if (!state || state.error) {
|
||||
await injectContentScript(tabId);
|
||||
await new Promise(resolve => setTimeout(resolve, 250));
|
||||
state = await getTabVideoState(tabId);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
async function simulateRemoteSeek(delta, explicitTargetTime = null) {
|
||||
if (!currentTabId) return { status: 'no_tab' };
|
||||
const tabId = parseInt(currentTabId);
|
||||
if (isNaN(tabId)) return { status: 'no_tab' };
|
||||
|
||||
const state = await getReadyTabVideoState(tabId);
|
||||
if (!state || state.error) return { status: 'error', message: state?.error || 'No video state' };
|
||||
if (!state.found || !Number.isFinite(state.currentTime)) return { status: 'no_video' };
|
||||
|
||||
let targetTime = explicitTargetTime !== null ? explicitTargetTime : Math.max(0, state.currentTime + (delta || 0));
|
||||
if (Number.isFinite(state.duration) && state.duration > 0) {
|
||||
targetTime = Math.min(targetTime, Math.max(0, state.duration - 0.1));
|
||||
}
|
||||
|
||||
const senderId = 'KoalaDev';
|
||||
const timestamp = Date.now();
|
||||
const payload = {
|
||||
senderId,
|
||||
actionTimestamp: timestamp,
|
||||
currentTime: targetTime,
|
||||
targetTime
|
||||
};
|
||||
|
||||
addToHistory(EVENTS.SEEK, senderId);
|
||||
showNotification(senderId, EVENTS.SEEK);
|
||||
updateLastAction(EVENTS.SEEK, senderId, timestamp);
|
||||
lastActionState.targetTime = targetTime;
|
||||
if (storageInitialized) chrome.storage.session.set({ lastActionState });
|
||||
updateLocalPeerState(senderId, { currentTime: targetTime });
|
||||
routeToContent(EVENTS.SEEK, payload);
|
||||
|
||||
return { status: 'ok', targetTime };
|
||||
}
|
||||
|
||||
async function devRemoteToolsAllowed() {
|
||||
const data = await chrome.storage.local.get(['username']);
|
||||
return data.username === 'KoalaDev';
|
||||
}
|
||||
|
||||
function shouldUsePageApiSeek(url) {
|
||||
return typeof globalThis.koalaFindPageApiSeekProvider === 'function' &&
|
||||
!!globalThis.koalaFindPageApiSeekProvider(url);
|
||||
}
|
||||
|
||||
function installPageApiSeekBridge() {
|
||||
if (window.__koalaPageApiSeekBridgeInstalled) return;
|
||||
window.__koalaPageApiSeekBridgeInstalled = true;
|
||||
|
||||
function currentMatch() {
|
||||
return typeof window.koalaFindPageApiSeekProvider === 'function'
|
||||
? window.koalaFindPageApiSeekProvider(window.location.hostname)
|
||||
: null;
|
||||
}
|
||||
|
||||
// Disney+ ("hive"/BAM) player: the real media player hangs off the
|
||||
// <disney-web-player> custom element as `.mediaPlayer`, exposing precise
|
||||
// seek(ms) and timeline.info (playhead/duration in ms).
|
||||
function disneyMediaPlayer() {
|
||||
const el = document.querySelector('disney-web-player');
|
||||
return el && el.mediaPlayer ? el.mediaPlayer : null;
|
||||
}
|
||||
|
||||
function seekWithPageApi(time) {
|
||||
const match = currentMatch();
|
||||
if (!match) return;
|
||||
|
||||
try {
|
||||
if (match.provider === 'netflix') {
|
||||
const videoPlayer = window.netflix?.appContext?.state?.playerApp?.getAPI?.().videoPlayer;
|
||||
const ids = videoPlayer?.getAllPlayerSessionIds?.();
|
||||
const sessionId = ids ? ids[0] : null;
|
||||
const player = sessionId ? videoPlayer.getVideoPlayerBySessionId(sessionId) : null;
|
||||
player?.seek(Math.round(time * 1000));
|
||||
} else if (match.provider === 'disney') {
|
||||
const mp = disneyMediaPlayer();
|
||||
if (mp && typeof mp.seek === 'function') mp.seek(Math.round(time * 1000));
|
||||
}
|
||||
} catch (_e) {
|
||||
// Player not ready or private API changed; the next sync tick can retry.
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.source !== window) return;
|
||||
const data = event.data;
|
||||
if (!data || data.__koalaPageApiSeek !== 1 || data.kind !== 'seek' || typeof data.time !== 'number') return;
|
||||
seekWithPageApi(data.time);
|
||||
});
|
||||
|
||||
// Disney+'s <video> currentTime is blob-relative and its scrubber lags, so
|
||||
// the isolated-world content script can't read an accurate position. Push
|
||||
// the real playhead/duration (seconds) from the page's media player.
|
||||
setInterval(() => {
|
||||
try {
|
||||
const match = currentMatch();
|
||||
if (!match || match.provider !== 'disney') return;
|
||||
const mp = disneyMediaPlayer();
|
||||
const info = mp && mp.timeline && mp.timeline.info;
|
||||
if (!info || typeof info.playheadPositionMs !== 'number' || typeof info.programDurationMs !== 'number') return;
|
||||
if (info.programDurationMs <= 0) return;
|
||||
window.postMessage({
|
||||
__koalaPlayerTime: 1,
|
||||
provider: 'disney',
|
||||
position: info.playheadPositionMs / 1000,
|
||||
duration: info.programDurationMs / 1000
|
||||
}, '*');
|
||||
} catch (_e) {
|
||||
// Ignore transient errors (player teardown / element swap).
|
||||
}
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function setPageApiSeekEnabled(enabled) {
|
||||
window.KOALA_PAGE_API_SEEK_ENABLED = enabled === true;
|
||||
}
|
||||
|
||||
async function injectContentScript(tabId) {
|
||||
let needsPageApiSeek = false;
|
||||
let pageApiSeekReady = false;
|
||||
try {
|
||||
const tab = await chrome.tabs.get(tabId);
|
||||
const url = tab?.url || '';
|
||||
needsPageApiSeek = shouldUsePageApiSeek(url);
|
||||
} catch (_e) {
|
||||
// Fall through to the generic content script injection.
|
||||
}
|
||||
|
||||
if (needsPageApiSeek) {
|
||||
try {
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
world: 'MAIN',
|
||||
files: ['page-api-seek-overrides.js']
|
||||
});
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
world: 'MAIN',
|
||||
func: installPageApiSeekBridge
|
||||
});
|
||||
pageApiSeekReady = true;
|
||||
} catch (err) {
|
||||
addLog(`Page API seek bridge injection failed: ${err.message}`, 'warn');
|
||||
}
|
||||
}
|
||||
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
files: ['page-api-seek-overrides.js']
|
||||
});
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
func: setPageApiSeekEnabled,
|
||||
args: [pageApiSeekReady]
|
||||
});
|
||||
return chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
files: ['content.js']
|
||||
});
|
||||
}
|
||||
|
||||
function _routeToContentInternal(tabId, action, payload, actionTimestamp, commandSenderId, retries) {
|
||||
chrome.tabs.sendMessage(tabId, {
|
||||
type: 'SERVER_COMMAND',
|
||||
@@ -1606,10 +1789,7 @@ function _routeToContentInternal(tabId, action, payload, actionTimestamp, comman
|
||||
return;
|
||||
}
|
||||
if (err.message.includes('Receiving end does not exist') || err.message.includes('Extension context invalidated')) {
|
||||
chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
files: ['content.js']
|
||||
}).then(() => {
|
||||
injectContentScript(tabId).then(() => {
|
||||
setTimeout(() => _routeToContentInternal(tabId, action, payload, actionTimestamp, commandSenderId, retries + 1), 500);
|
||||
}).catch(_err => {
|
||||
addLog(`Auto-reinject failed for tab ${tabId}`, 'warn');
|
||||
@@ -1997,6 +2177,22 @@ async function handleAsyncMessage(message, sender, sendResponse) {
|
||||
sendResponse(res);
|
||||
}
|
||||
});
|
||||
} else if (message.type === 'DEV_SIMULATE_REMOTE_SEEK') {
|
||||
if (!(await devRemoteToolsAllowed())) {
|
||||
sendResponse({ status: 'forbidden' });
|
||||
return;
|
||||
}
|
||||
const delta = message.delta !== null && message.delta !== undefined ? Number(message.delta) : null;
|
||||
const targetTime = message.targetTime !== null && message.targetTime !== undefined ? Number(message.targetTime) : null;
|
||||
|
||||
if (delta === null && targetTime === null) {
|
||||
sendResponse({ status: 'invalid_params' });
|
||||
return;
|
||||
}
|
||||
simulateRemoteSeek(delta, targetTime).then(sendResponse).catch(err => {
|
||||
addLog(`Remote seek simulation failed: ${err.message}`, 'warn');
|
||||
sendResponse({ status: 'error', message: err.message });
|
||||
});
|
||||
} else if (message.type === 'CONTENT_EVENT') {
|
||||
const processEvent = async () => {
|
||||
// Host Control Mode (sender-side): a non-controller in host-only mode must
|
||||
@@ -2031,12 +2227,40 @@ async function handleAsyncMessage(message, sender, sendResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = message.payload && typeof message.payload === 'object' ? message.payload : {};
|
||||
const payloadNumber = (value) => value !== undefined && value !== null && value !== '' ? Number(value) : NaN;
|
||||
if (message.action === EVENTS.FORCE_SYNC_PREPARE) {
|
||||
const targetTime = payloadNumber(payload.targetTime);
|
||||
if (!Number.isFinite(targetTime)) {
|
||||
sendResponse({ status: 'invalid_params' });
|
||||
return;
|
||||
}
|
||||
payload.targetTime = targetTime;
|
||||
} else if (message.action === EVENTS.SEEK) {
|
||||
const targetTime = payloadNumber(payload.targetTime !== undefined ? payload.targetTime : payload.currentTime);
|
||||
if (!Number.isFinite(targetTime)) {
|
||||
sendResponse({ status: 'invalid_params' });
|
||||
return;
|
||||
}
|
||||
payload.currentTime = targetTime;
|
||||
payload.targetTime = targetTime;
|
||||
}
|
||||
|
||||
const timestamp = Date.now();
|
||||
localSeq++;
|
||||
chrome.storage.session.set({ localSeq });
|
||||
updateLastAction(message.action, 'You', timestamp);
|
||||
|
||||
const payload = message.payload || {};
|
||||
const hasPlaybackTime = Number.isFinite(payload.currentTime) || Number.isFinite(payload.targetTime);
|
||||
if (!sender?.tab && (message.action === EVENTS.PLAY || message.action === EVENTS.PAUSE) && !hasPlaybackTime) {
|
||||
const tabId = currentTabId ? parseInt(currentTabId) : NaN;
|
||||
if (!isNaN(tabId)) {
|
||||
const state = await getReadyTabVideoState(tabId);
|
||||
if (state && !state.error && state.found && Number.isFinite(state.currentTime)) {
|
||||
payload.currentTime = state.currentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastActionState.targetTime = payload.targetTime !== undefined ? payload.targetTime : payload.currentTime;
|
||||
if (storageInitialized) chrome.storage.session.set({ lastActionState });
|
||||
|
||||
@@ -2050,6 +2274,10 @@ async function handleAsyncMessage(message, sender, sendResponse) {
|
||||
currentTime: payload.currentTime !== undefined ? payload.currentTime : (payload.targetTime !== undefined ? payload.targetTime : undefined)
|
||||
});
|
||||
|
||||
if (!sender?.tab && (message.action === EVENTS.PLAY || message.action === EVENTS.PAUSE || message.action === EVENTS.SEEK)) {
|
||||
routeToContent(message.action, message.payload);
|
||||
}
|
||||
|
||||
if (message.action === EVENTS.FORCE_SYNC_PREPARE) {
|
||||
isForceSyncInitiator = true;
|
||||
forceSyncAcks.clear();
|
||||
@@ -2103,7 +2331,6 @@ async function handleAsyncMessage(message, sender, sendResponse) {
|
||||
sendResponse({ status: 'error' });
|
||||
});
|
||||
} else {
|
||||
routeToContent(message.action, message.payload);
|
||||
processEvent().catch(err => {
|
||||
addLog('Content event privacy error: ' + err.message, 'error');
|
||||
sendResponse({ status: 'error' });
|
||||
@@ -2196,6 +2423,20 @@ async function handleAsyncMessage(message, sender, sendResponse) {
|
||||
addLog('Heartbeat settings error: ' + err.message, 'error');
|
||||
sendResponse({ status: 'ok' });
|
||||
});
|
||||
} else if (message.type === 'INJECT_CONTENT_SCRIPT') {
|
||||
const tabId = Number(message.tabId);
|
||||
if (!Number.isInteger(tabId)) {
|
||||
sendResponse({ status: 'invalid_tab' });
|
||||
return true;
|
||||
}
|
||||
|
||||
injectContentScript(tabId).then(() => {
|
||||
sendResponse({ status: 'ok' });
|
||||
}).catch(err => {
|
||||
addLog(`Failed to inject into tab: ${err.message}`, 'warn');
|
||||
sendResponse({ status: 'error', message: err.message });
|
||||
});
|
||||
return true;
|
||||
} else if (message.type === 'SET_TARGET_TAB') {
|
||||
const previousTabId = currentTabId;
|
||||
currentTabId = message.tabId;
|
||||
@@ -2213,10 +2454,7 @@ async function handleAsyncMessage(message, sender, sendResponse) {
|
||||
|
||||
if (currentTabId) {
|
||||
const selectedTabId = currentTabId;
|
||||
chrome.scripting.executeScript({
|
||||
target: { tabId: selectedTabId },
|
||||
files: ['content.js']
|
||||
})
|
||||
injectContentScript(selectedTabId)
|
||||
.then(() => applyAudioSettingsToTab(selectedTabId))
|
||||
.catch(err => {
|
||||
addLog(`Failed to inject into tab: ${err.message}`, 'warn');
|
||||
@@ -2399,6 +2637,7 @@ initTabManager({
|
||||
getSettings,
|
||||
emit,
|
||||
applyAudioSettingsToTab,
|
||||
injectContentScript,
|
||||
ensureState,
|
||||
EVENTS
|
||||
});
|
||||
|
||||
+1638
-1506
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* KoalaSync Episode Title Utilities
|
||||
* Single source of truth — synced to content.js by build-extension.js.
|
||||
* Single source of truth — synced to content.js by build-extension.cjs.
|
||||
* Keep in sync with the injection block in content.js!
|
||||
*/
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"default_locale": "en",
|
||||
"name": "KoalaSync",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.3",
|
||||
"description": "Synchronize video playback on YouTube, Netflix, Emby, Jellyfin, and any HTML5 site in real-time with friends.",
|
||||
"permissions": [
|
||||
"storage",
|
||||
|
||||
@@ -12,6 +12,7 @@ export function initTabManager({
|
||||
getSettings,
|
||||
emit,
|
||||
applyAudioSettingsToTab,
|
||||
injectContentScript,
|
||||
ensureState,
|
||||
EVENTS
|
||||
}) {
|
||||
@@ -83,10 +84,7 @@ export function initTabManager({
|
||||
await ensureState();
|
||||
const curTabId = getCurrentTabId();
|
||||
if (curTabId && tabId === parseInt(curTabId) && changeInfo.status === 'complete') {
|
||||
chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
files: ['content.js']
|
||||
})
|
||||
injectContentScript(tabId)
|
||||
.then(() => applyAudioSettingsToTab(tabId))
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
(function(root) {
|
||||
const PAGE_API_SEEK_FIXES = [
|
||||
{
|
||||
name: 'netflix-page-api-seek',
|
||||
urls: ['netflix.com'],
|
||||
provider: 'netflix'
|
||||
},
|
||||
{
|
||||
name: 'disney-page-api-seek',
|
||||
urls: ['disneyplus.com'],
|
||||
provider: 'disney'
|
||||
}
|
||||
];
|
||||
|
||||
function normalizeHost(input) {
|
||||
try {
|
||||
return new URL(input).hostname.toLowerCase();
|
||||
} catch (_e) {
|
||||
return String(input || '').toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
function matchesDomain(host, domain) {
|
||||
const normalizedDomain = normalizeHost(domain);
|
||||
return normalizedDomain && (host === normalizedDomain || host.endsWith(`.${normalizedDomain}`));
|
||||
}
|
||||
|
||||
root.KOALA_PAGE_API_SEEK_FIXES = PAGE_API_SEEK_FIXES;
|
||||
root.KOALA_PAGE_API_SEEK_PROVIDERS = PAGE_API_SEEK_FIXES;
|
||||
root.koalaFindPageApiSeekProvider = (input) => {
|
||||
const host = normalizeHost(input);
|
||||
return PAGE_API_SEEK_FIXES.find(entry =>
|
||||
Array.isArray(entry.urls) && entry.urls.some(url => matchesDomain(host, url))
|
||||
) || null;
|
||||
};
|
||||
})(globalThis);
|
||||
@@ -391,6 +391,7 @@
|
||||
<button class="tab-btn" data-tab="tab-sync" data-i18n="TAB_SYNC" data-i18n-title="TAB_SYNC_TOOLTIP" title="Video sync controls and remote actions">Sync</button>
|
||||
<button class="tab-btn" data-tab="tab-settings" data-i18n="TAB_SETTINGS" data-i18n-title="TAB_SETTINGS_TOOLTIP" title="Extension preferences">Settings</button>
|
||||
<button class="tab-btn" data-tab="tab-dev" data-i18n="TAB_STATUS" data-i18n-title="TAB_STATUS_TOOLTIP" title="Advanced Diagnostics & Logs">Status</button>
|
||||
<button id="devToolsTabBtn" class="tab-btn" data-tab="tab-devtools" style="display:none;">Dev</button>
|
||||
</div>
|
||||
|
||||
<!-- Room Tab -->
|
||||
@@ -692,6 +693,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-devtools" class="tab-content">
|
||||
<label>Remote Seek</label>
|
||||
<div class="info-card" style="display:flex; gap:8px; margin-bottom:15px;">
|
||||
<button id="remoteSeekBack" class="secondary" style="flex:1; font-size:12px;">-30s</button>
|
||||
<button id="remoteSeekForward" class="secondary" style="flex:1; font-size:12px;">+30s</button>
|
||||
</div>
|
||||
<button id="remoteSeekFiveMin" class="secondary" style="width:100%; font-size:12px; margin-bottom:15px;">Seek 5:00</button>
|
||||
<div style="font-size:10px; color:var(--text-muted); text-align:center; margin-top:10px; opacity:0.7;">
|
||||
Build: <span id="buildIdentifier">__BUILD_TIMESTAMP__</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="popup.js" type="module"></script>
|
||||
|
||||
<!-- Onboarding Overlay -->
|
||||
|
||||
+120
-28
@@ -2,7 +2,7 @@ import { EVENTS, OFFICIAL_LANDING_PAGE_URL, SUPPORT_URL, getReviewUrl } from './
|
||||
import { BLACKLIST_DOMAINS } from './shared/blacklist.js';
|
||||
import { getAvatarForName, generateUsername, USERNAME_ADJECTIVES, USERNAME_NOUNS } from './shared/names.js';
|
||||
import { loadLocale, translateDOM, getMessage, getSystemLanguage } from './i18n.js';
|
||||
import { TITLE_PRIVACY_MODES, normalizeSendTabTitle } from './title-privacy.js';
|
||||
import { TITLE_PRIVACY_MODES, normalizeSendTabTitle, normalizeTabTitle } from './title-privacy.js';
|
||||
|
||||
|
||||
const elements = {
|
||||
@@ -71,7 +71,11 @@ const elements = {
|
||||
settingsVersion: document.getElementById('settingsVersion'),
|
||||
devSupportLink: document.getElementById('devSupportLink'),
|
||||
devReviewLink: document.getElementById('devReviewLink'),
|
||||
syncTabCopyInvite: document.getElementById('syncTabCopyInvite')
|
||||
syncTabCopyInvite: document.getElementById('syncTabCopyInvite'),
|
||||
devToolsTabBtn: document.getElementById('devToolsTabBtn'),
|
||||
remoteSeekBack: document.getElementById('remoteSeekBack'),
|
||||
remoteSeekForward: document.getElementById('remoteSeekForward'),
|
||||
remoteSeekFiveMin: document.getElementById('remoteSeekFiveMin')
|
||||
};
|
||||
|
||||
let localPeerId = null;
|
||||
@@ -88,6 +92,19 @@ let errorToken = 0;
|
||||
let forceSyncDone = false;
|
||||
let connectionErrorTimer = null;
|
||||
let pendingConnectionErrorMsg = null;
|
||||
|
||||
function devToolsEnabled() {
|
||||
return elements.username && elements.username.value.trim() === 'KoalaDev';
|
||||
}
|
||||
|
||||
function syncDevToolsVisibility() {
|
||||
if (!elements.devToolsTabBtn) return;
|
||||
const enabled = devToolsEnabled();
|
||||
elements.devToolsTabBtn.style.display = enabled ? '' : 'none';
|
||||
if (!enabled && document.getElementById('tab-devtools')?.classList.contains('active')) {
|
||||
document.querySelector('.tab-btn[data-tab="tab-settings"]')?.click();
|
||||
}
|
||||
}
|
||||
let roomListRefreshTimer = null;
|
||||
let roomListRefreshInterval = null;
|
||||
const ROOM_LIST_REFRESH_COOLDOWN_MS = 11000;
|
||||
@@ -210,6 +227,7 @@ async function init() {
|
||||
elements.roomId.value = localData.roomId || '';
|
||||
elements.password.value = localData.password || '';
|
||||
elements.username.value = username;
|
||||
syncDevToolsVisibility();
|
||||
if (elements.filterNoise) elements.filterNoise.checked = localData.filterNoise !== false;
|
||||
if (elements.autoSyncNextEpisode) elements.autoSyncNextEpisode.checked = localData.autoSyncNextEpisode !== false;
|
||||
const legacyTitlePrivacyMode = Object.values(TITLE_PRIVACY_MODES).includes(localData.titlePrivacyMode) ? localData.titlePrivacyMode : TITLE_PRIVACY_MODES.FULL;
|
||||
@@ -277,13 +295,13 @@ async function init() {
|
||||
const syncTabBtn = document.querySelector('.tab-btn[data-tab="tab-sync"]');
|
||||
if (syncTabBtn) syncTabBtn.click();
|
||||
showSelectVideoHint();
|
||||
} else if (localData.activeTab) {
|
||||
} else if (localData.activeTab && (localData.activeTab !== 'tab-devtools' || devToolsEnabled())) {
|
||||
const btn = document.querySelector(`.tab-btn[data-tab="${localData.activeTab}"]`);
|
||||
if (btn) btn.click();
|
||||
}
|
||||
} else {
|
||||
await populateTabs();
|
||||
if (localData.activeTab) {
|
||||
if (localData.activeTab && (localData.activeTab !== 'tab-devtools' || devToolsEnabled())) {
|
||||
const btn = document.querySelector(`.tab-btn[data-tab="${localData.activeTab}"]`);
|
||||
if (btn) btn.click();
|
||||
}
|
||||
@@ -903,7 +921,7 @@ async function populateTabs(providedPeers = null, providedTargetTabId = null) {
|
||||
// Smart Matching Logic — exclude own tabTitle to prevent self-match (computed once)
|
||||
const cleanTitle = (rawTitle) => {
|
||||
if (!rawTitle) return '';
|
||||
return rawTitle
|
||||
return (normalizeTabTitle(rawTitle) || '')
|
||||
.replace(/(?:\s*[-\|•]\s*(?:YouTube|Twitch|Jellyfin|Emby|Netflix|Vimeo|Dailymotion).*)$/i, '')
|
||||
.replace(/^(?:Netflix|Twitch|YouTube|Emby|Jellyfin)\s*[-\|•]\s*/i, '')
|
||||
.trim();
|
||||
@@ -1275,8 +1293,10 @@ elements.serverUrl.addEventListener('input', () => {
|
||||
chrome.storage.local.set({ serverUrl: elements.serverUrl.value });
|
||||
});
|
||||
|
||||
elements.username.addEventListener('input', syncDevToolsVisibility);
|
||||
elements.username.addEventListener('change', () => {
|
||||
chrome.storage.local.set({ username: elements.username.value });
|
||||
syncDevToolsVisibility();
|
||||
});
|
||||
|
||||
if (elements.langSelector) {
|
||||
@@ -1568,8 +1588,9 @@ elements.forceSyncBtn.addEventListener('click', async () => {
|
||||
}
|
||||
const peers = status.peers || [];
|
||||
const otherTimes = peers
|
||||
.filter(p => typeof p === 'object' && p.peerId !== localPeerId && p.currentTime != null && !isNaN(p.currentTime))
|
||||
.map(p => p.currentTime);
|
||||
.filter(p => typeof p === 'object' && p.peerId !== localPeerId && p.currentTime != null && p.currentTime !== '')
|
||||
.map(p => Number(p.currentTime))
|
||||
.filter(Number.isFinite);
|
||||
|
||||
if (otherTimes.length === 0) {
|
||||
showError(getMessage('ERR_NO_PEERS_TIME'));
|
||||
@@ -1598,38 +1619,57 @@ elements.forceSyncBtn.addEventListener('click', async () => {
|
||||
forceSyncResetTimer = setTimeout(forceSyncReset, syncTimeoutMs);
|
||||
const tabId = parseInt(status.targetTabId);
|
||||
|
||||
const failForceSyncTime = () => {
|
||||
if (forceSyncResetTimer) { clearTimeout(forceSyncResetTimer); forceSyncResetTimer = null; }
|
||||
showError(getMessage('ERR_NO_VIDEO_TAB'));
|
||||
forceSyncDone = true;
|
||||
elements.forceSyncBtn.disabled = false;
|
||||
elements.forceSyncBtn.textContent = originalText;
|
||||
};
|
||||
|
||||
const sendForceSync = (time) => {
|
||||
if (time === null || time === undefined) {
|
||||
failForceSyncTime();
|
||||
return;
|
||||
}
|
||||
const target = Number(time);
|
||||
if (!Number.isFinite(target)) {
|
||||
failForceSyncTime();
|
||||
return;
|
||||
}
|
||||
chrome.runtime.sendMessage({
|
||||
type: 'CONTENT_EVENT',
|
||||
action: EVENTS.FORCE_SYNC_PREPARE,
|
||||
payload: { targetTime: parseFloat(time) }
|
||||
payload: { targetTime: target }
|
||||
});
|
||||
};
|
||||
|
||||
if (mode === 'jump-to-me') {
|
||||
const retryQueryTime = () => {
|
||||
chrome.tabs.sendMessage(tabId, { action: 'get_current_time' }, (retryResponse) => {
|
||||
if (chrome.runtime.lastError || !retryResponse || !Number.isFinite(retryResponse.currentTime)) {
|
||||
failForceSyncTime();
|
||||
return;
|
||||
}
|
||||
sendForceSync(retryResponse.currentTime);
|
||||
});
|
||||
};
|
||||
chrome.tabs.sendMessage(tabId, { action: 'get_current_time' }, (response) => {
|
||||
if (chrome.runtime.lastError || !response || response.currentTime === undefined) {
|
||||
chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
files: ['content.js']
|
||||
}).then(() => {
|
||||
setTimeout(() => {
|
||||
chrome.tabs.sendMessage(tabId, { action: 'get_current_time' }, (retryResponse) => {
|
||||
if (chrome.runtime.lastError) return;
|
||||
if (retryResponse && retryResponse.currentTime !== undefined) {
|
||||
sendForceSync(retryResponse.currentTime);
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
}).catch(() => {
|
||||
showError(getMessage('ERR_NO_VIDEO_TAB'));
|
||||
forceSyncDone = true;
|
||||
elements.forceSyncBtn.disabled = false;
|
||||
elements.forceSyncBtn.textContent = originalText;
|
||||
if (Number.isFinite(response?.currentTime)) {
|
||||
sendForceSync(response.currentTime);
|
||||
return;
|
||||
}
|
||||
if (chrome.runtime.lastError || !response) {
|
||||
chrome.runtime.sendMessage({ type: 'INJECT_CONTENT_SCRIPT', tabId }, (injectResponse) => {
|
||||
if (chrome.runtime.lastError || !injectResponse || injectResponse.status !== 'ok') {
|
||||
failForceSyncTime();
|
||||
return;
|
||||
}
|
||||
setTimeout(retryQueryTime, 500);
|
||||
});
|
||||
return;
|
||||
}
|
||||
sendForceSync(response.currentTime);
|
||||
setTimeout(retryQueryTime, 500);
|
||||
});
|
||||
} else {
|
||||
sendForceSync(targetTime);
|
||||
@@ -1690,6 +1730,27 @@ elements.pauseBtn.addEventListener('click', () => {
|
||||
}, 2500);
|
||||
});
|
||||
|
||||
function simulateRemoteSeek({ delta = null, targetTime = null }) {
|
||||
chrome.runtime.sendMessage({ type: 'DEV_SIMULATE_REMOTE_SEEK', delta, targetTime }, (res) => {
|
||||
if (chrome.runtime.lastError || !res || res.status !== 'ok') {
|
||||
showToast(res?.message || 'Remote seek failed', 'error');
|
||||
return;
|
||||
}
|
||||
showToast(`Remote seek -> ${formatTime(res.targetTime)}`, 'success', 1200);
|
||||
refreshDebugInfo();
|
||||
});
|
||||
}
|
||||
|
||||
if (elements.remoteSeekBack) {
|
||||
elements.remoteSeekBack.addEventListener('click', () => simulateRemoteSeek({ delta: -30 }));
|
||||
}
|
||||
if (elements.remoteSeekForward) {
|
||||
elements.remoteSeekForward.addEventListener('click', () => simulateRemoteSeek({ delta: 30 }));
|
||||
}
|
||||
if (elements.remoteSeekFiveMin) {
|
||||
elements.remoteSeekFiveMin.addEventListener('click', () => simulateRemoteSeek({ targetTime: 300 }));
|
||||
}
|
||||
|
||||
elements.clearLogs.addEventListener('click', () => {
|
||||
chrome.runtime.sendMessage({ type: 'CLEAR_LOGS' }, () => {
|
||||
elements.logList.innerHTML = '';
|
||||
@@ -2047,6 +2108,26 @@ elements.copyLogs.addEventListener('click', () => {
|
||||
lines.push(`- **ReadyState:** ${readyOk ? '\u2705' : '\u26A0\uFE0F'} ${safe(vs.readyState, '?')} (${readyLabel})`);
|
||||
lines.push(`- **Network:** ${safe(vs.networkState, '?')} (${netLabel})`);
|
||||
lines.push(`- **Buffered:** ${safe(vs.buffered, '?')}`);
|
||||
if (vs.nativeCurrentTime != null || vs.nativeDuration != null) {
|
||||
lines.push(`- **Native Time:** ${safe(vs.nativeCurrentTime, '?')}s / ${safe(vs.nativeDuration, '?')}s`);
|
||||
}
|
||||
if (vs.siteQuirk) {
|
||||
const quirk = vs.siteQuirk;
|
||||
const label = quirk.name || quirk.key || 'site';
|
||||
if (quirk.timeline) {
|
||||
lines.push(`- **${label} Timeline:** ${safe(quirk.timeline.current, '?')}s / ${safe(quirk.timeline.duration, '?')}s`);
|
||||
}
|
||||
const candidates = Array.isArray(quirk.timelineCandidates) ? quirk.timelineCandidates : [];
|
||||
if (candidates.length > 0) {
|
||||
lines.push(`- **${label} Timeline Candidates:**`);
|
||||
candidates.forEach(c => lines.push(` - ${safe(c.source, '?')}: ${safe(c.current, '?')}s / ${safe(c.duration, '?')}s`));
|
||||
}
|
||||
const buttons = Array.isArray(quirk.seekButtons) ? quirk.seekButtons : [];
|
||||
if (buttons.length > 0) {
|
||||
lines.push(`- **${label} Buttons:**`);
|
||||
buttons.forEach(label => lines.push(` - ${label}`));
|
||||
}
|
||||
}
|
||||
lines.push(`- **Dimensions:** ${safe(vs.videoWidth, '?')}x${safe(vs.videoHeight, '?')}${dimOk ? '' : ' \u26A0\uFE0F 0x0'}`);
|
||||
lines.push(`- **Muted:** ${safe(vs.muted, '?')} | **Volume:** ${safe(vs.volume, '?')} | **Speed:** ${safe(vs.playbackRate, '?')}x`);
|
||||
lines.push(`- **Seeking:** ${safe(vs.seeking, '?')} | **Ended:** ${safe(vs.ended, '?')} | **Loop:** ${safe(vs.loop, '?')}`);
|
||||
@@ -2220,7 +2301,18 @@ function refreshDebugInfo() {
|
||||
addField('Network', `${state.networkState} (${state.networkStateLabel || '?'})`,
|
||||
state.networkState === 1 ? '#22c55e' : state.networkState === 3 ? '#ef4444' : 'var(--text-muted)');
|
||||
addField('Buffered', state.buffered || '?');
|
||||
|
||||
if (state.nativeCurrentTime != null || state.nativeDuration != null) {
|
||||
addField('Native Time', `${state.nativeCurrentTime ?? '?'}s / ${state.nativeDuration ?? '?'}s`);
|
||||
}
|
||||
if (state.siteQuirk) {
|
||||
const quirk = state.siteQuirk;
|
||||
const label = quirk.name || quirk.key || 'Site';
|
||||
if (quirk.timeline) {
|
||||
addField(`${label} Timeline`, `${quirk.timeline.current ?? '?'}s / ${quirk.timeline.duration ?? '?'}s`);
|
||||
}
|
||||
const buttons = Array.isArray(quirk.seekButtons) ? quirk.seekButtons.slice(0, 4).join(' | ') : '';
|
||||
if (buttons) addField(`${label} Buttons`, buttons);
|
||||
}
|
||||
addSection('Properties');
|
||||
addField('Seeking', String(state.seeking));
|
||||
addField('Ended', String(state.ended));
|
||||
|
||||
@@ -17,9 +17,15 @@ export function normalizeSendTabTitle(sendTabTitle, legacyMode = TITLE_PRIVACY_M
|
||||
return normalizeTitlePrivacyMode(legacyMode) === TITLE_PRIVACY_MODES.FULL;
|
||||
}
|
||||
|
||||
export function normalizeTabTitle(title) {
|
||||
if (typeof title !== 'string') return null;
|
||||
const normalized = title.replace(/^\s*(?:\(\d{1,2}\)|\[\d{1,2}\])\s+/, '').trim();
|
||||
return normalized.length > 0 ? normalized : null;
|
||||
}
|
||||
|
||||
export function sanitizeTabTitle(title, sendTabTitle) {
|
||||
if (!sendTabTitle) return null;
|
||||
return typeof title === 'string' && title.length > 0 ? title : null;
|
||||
return normalizeTabTitle(title);
|
||||
}
|
||||
|
||||
export function sanitizeSharedTitle(title, mode) {
|
||||
|
||||
Generated
+1496
-6
File diff suppressed because it is too large
Load Diff
+8
-3
@@ -1,22 +1,27 @@
|
||||
{
|
||||
"name": "koalasync",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.3",
|
||||
"description": "KoalaSync Build Scripts",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build:extension": "node scripts/build-extension.cjs",
|
||||
"indexnow": "node website/submit-indexnow.cjs",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"test": "node scripts/verify-release.mjs",
|
||||
"test": "npm run verify",
|
||||
"test:unit": "vitest run",
|
||||
"verify": "node scripts/verify-release.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.61.1",
|
||||
"@vitest/coverage-v8": "^4.1.9",
|
||||
"archiver": "^7.0.1",
|
||||
"esbuild": "^0.28.1",
|
||||
"eslint": "^10.4.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"sharp": "^0.34.5",
|
||||
"svgo": "^4.0.1"
|
||||
"svgo": "^4.0.1",
|
||||
"vitest": "^4.1.9"
|
||||
}
|
||||
}
|
||||
|
||||
+78
-18
@@ -1,33 +1,93 @@
|
||||
# Development Scripts
|
||||
|
||||
This directory contains utility scripts for the KoalaSync development workflow.
|
||||
This directory contains build, synchronization, and verification scripts for the KoalaSync workspace. Run all commands from the repository root unless a script says otherwise.
|
||||
|
||||
## Main Commands
|
||||
|
||||
```bash
|
||||
npm run build:extension
|
||||
npm run verify
|
||||
npm run lint
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
- `npm run build:extension` runs `scripts/build-extension.cjs`.
|
||||
- `npm run verify` runs the full release-safety suite in `scripts/verify-release.mjs`.
|
||||
- `npm run lint` runs ESLint across the repository.
|
||||
- `npm run test:unit` runs Vitest tests.
|
||||
|
||||
## build-extension.cjs
|
||||
|
||||
The primary build tool for KoalaSync. This Node.js script automates two critical tasks:
|
||||
The primary extension build tool performs these steps:
|
||||
|
||||
1. **Protocol Synchronization**: Copies the "Single Source of Truth" constants (`shared/constants.js`) and the domain blacklist (`shared/blacklist.js`) from the root `/shared` directory into the `extension/shared/` directory.
|
||||
2. **Content Script Injection**: Injects protocol constants directly into `content.js` using marker-based replacement. This is necessary because `content.js` executes synchronously and cannot use ES module imports.
|
||||
3. **Artifact Generation**: Compiles the extension into browser-specific bundles for Chrome and Firefox, located in the `dist/` directory.
|
||||
|
||||
### Usage
|
||||
|
||||
From the **repository root**, run:
|
||||
1. Recreates `dist/`.
|
||||
2. Copies `shared/constants.js`, `shared/blacklist.js`, `shared/names.js`, and `shared/README.md` into `extension/shared/`.
|
||||
3. Injects synchronous shared values into `content.js`.
|
||||
4. Injects browser-specific uninstall URL constants into `background.js`.
|
||||
5. Injects the build timestamp into `popup.html`.
|
||||
6. Generates browser-specific manifests for Chrome and Firefox.
|
||||
7. Creates `dist/koalasync-chrome.zip` and `dist/koalasync-firefox.zip`.
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
node scripts/build-extension.cjs
|
||||
# or
|
||||
npm run build:extension
|
||||
```
|
||||
|
||||
### Why this script exists
|
||||
KoalaSync uses **Vanilla JS** in the extension to maintain zero runtime dependencies and maximum privacy. Since we don't use a bundler (like Webpack or Vite) inside the extension, this script serves as our lightweight "pre-build" step to ensure that the protocol constants remain synchronized between the extension and the relay server.
|
||||
## Injection Markers
|
||||
|
||||
### Content Injection Markers
|
||||
The build script uses marker comments/placeholders. Missing markers are a hard build failure so release artifacts cannot silently contain stale protocol data.
|
||||
|
||||
The build script uses marker comments in `content.js` to locate and replace constant blocks:
|
||||
| Target | Marker / Placeholder | Injected Value | Source |
|
||||
|:---|:---|:---|:---|
|
||||
| `content.js` | `SHARED_EVENTS_INJECT_START` / `END` | Full `EVENTS` object | `shared/constants.js` |
|
||||
| `content.js` | `SHARED_HEARTBEAT_INJECT_START` / `END` | `HEARTBEAT_INTERVAL` | `shared/constants.js` |
|
||||
| `content.js` | `SHARED_EPISODE_UTILS_INJECT_START` / `END` | `extractEpisodeId()` and `sameEpisode()` | `extension/episode-utils.js` |
|
||||
| `background.js` | `UNINSTALL_URL_INJECT_START` / `END` | Uninstall URL and browser type | `scripts/build-extension.cjs` |
|
||||
| `popup.html` | `__BUILD_TIMESTAMP__` | UTC build timestamp | Build time |
|
||||
|
||||
| Marker Pair | Injected Value | Source |
|
||||
|:---|:---|:---|
|
||||
| `SHARED_EVENTS_INJECT_START` / `END` | The full `EVENTS` object | `shared/constants.js` |
|
||||
| `SHARED_HEARTBEAT_INJECT_START` / `END` | `HEARTBEAT_INTERVAL` value | `shared/constants.js` |
|
||||
Do not remove or rename these markers without updating the build script and tests.
|
||||
|
||||
> **⚠️ Do NOT remove or modify these marker comments in `content.js`.** They are required for the build script to function. If the markers are missing, the build will fail with a clear error message.
|
||||
## Verification Suite
|
||||
|
||||
`scripts/verify-release.mjs` is the best single command before release, PR review, or handoff:
|
||||
|
||||
```bash
|
||||
npm run verify
|
||||
```
|
||||
|
||||
It currently runs:
|
||||
|
||||
- Vitest unit tests.
|
||||
- Server ops, route, WebSocket, and rate-limiter checks.
|
||||
- Episode parser, title privacy, audio settings, popup cooldown, names, and content-video-finder checks.
|
||||
- JavaScript syntax checks for server and extension entry points.
|
||||
- Extension and website locale coverage checks.
|
||||
- ESLint.
|
||||
- Production `npm audit` checks for root and server dependencies.
|
||||
- Extension build and website build.
|
||||
|
||||
## Focused Scripts
|
||||
|
||||
| Script | Purpose |
|
||||
|:---|:---|
|
||||
| `test-server-ops.mjs` | Health payload and admin metrics helpers |
|
||||
| `test-server-routes.mjs` | HTTP health routes, caching, and admin metrics access |
|
||||
| `test-server-ws.mjs` | Socket.IO relay integration, including host-control behavior |
|
||||
| `test-rate-limiter.mjs` | Rate-limiter map and cooldown behavior |
|
||||
| `test-episode-utils.mjs` | Episode-title extraction and comparison |
|
||||
| `test-title-privacy.mjs` | Tab/media title privacy sanitization |
|
||||
| `test-audio-settings.mjs` | Audio settings defaults and normalization |
|
||||
| `test-popup-refresh-cooldown.mjs` | Popup refresh throttling behavior |
|
||||
| `test-names.mjs` | Generated username format and coverage |
|
||||
| `test-content-video-finder.cjs` | Content-script video selection helpers |
|
||||
| `test-locales.cjs` | Extension runtime and browser-store locale coverage |
|
||||
| `test-website-locales.mjs` | Website locale coverage |
|
||||
|
||||
## Do Not Break
|
||||
|
||||
- Keep scripts runnable from the repository root.
|
||||
- Keep build output under `dist/` and generated website output under `website/www/`.
|
||||
- Keep shared protocol sync automated; do not add manual copy steps.
|
||||
- Treat warnings in verification scripts as release blockers unless the script explicitly documents them as informational.
|
||||
|
||||
+28
-27
@@ -36,6 +36,13 @@ console.log('✓ constants.js, blacklist.js, names.js, and README.md synced to e
|
||||
// Read the base manifest
|
||||
const baseManifest = JSON.parse(fs.readFileSync(baseManifestPath, 'utf8'));
|
||||
|
||||
function replaceRequiredBlock(content, pattern, replacement, description) {
|
||||
if (!pattern.test(content)) {
|
||||
throw new Error(`CRITICAL: ${description} markers not found. Aborting build to prevent stale artifacts.`);
|
||||
}
|
||||
return content.replace(pattern, replacement);
|
||||
}
|
||||
|
||||
// Helper to copy files, ignoring manifest.json and manifest.base.json
|
||||
// Also injects shared constants into content.js
|
||||
function copyExtensionFiles(targetDir, browserName) {
|
||||
@@ -79,11 +86,7 @@ function copyExtensionFiles(targetDir, browserName) {
|
||||
const ePattern = new RegExp(`${eStart}[\\s\\S]+?${eEnd}`);
|
||||
const eRep = `${eStart}\n // This block is automatically updated by /scripts/build-extension.cjs\n const EVENTS = ${eventsObject};\n ${eEnd}`;
|
||||
|
||||
if (ePattern.test(content)) {
|
||||
content = content.replace(ePattern, eRep);
|
||||
} else {
|
||||
console.warn('⚠️ WARNING: Event markers not found in content.js');
|
||||
}
|
||||
content = replaceRequiredBlock(content, ePattern, eRep, 'Event injection');
|
||||
|
||||
// 2. Inject Heartbeat
|
||||
const hStart = '// --- SHARED_HEARTBEAT_INJECT_START ---';
|
||||
@@ -91,30 +94,23 @@ function copyExtensionFiles(targetDir, browserName) {
|
||||
const hPattern = new RegExp(`${hStart}[\\s\\S]+?${hEnd}`);
|
||||
const hRep = `${hStart}\n const HEARTBEAT_INTERVAL_VAL = ${heartbeatVal};\n ${hEnd}`;
|
||||
|
||||
if (hPattern.test(content)) {
|
||||
content = content.replace(hPattern, hRep);
|
||||
} else {
|
||||
console.warn('⚠️ WARNING: Heartbeat markers not found in content.js');
|
||||
}
|
||||
content = replaceRequiredBlock(content, hPattern, hRep, 'Heartbeat injection');
|
||||
|
||||
// 3. Inject Episode Utils
|
||||
const euStart = '// --- SHARED_EPISODE_UTILS_INJECT_START ---';
|
||||
const euEnd = '// --- SHARED_EPISODE_UTILS_INJECT_END ---';
|
||||
const euPattern = new RegExp(euStart.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[\\s\\S]+?' + euEnd.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
||||
const euPath = path.join(rootDir, 'extension', 'episode-utils.js');
|
||||
if (fs.existsSync(euPath)) {
|
||||
const euContent = fs.readFileSync(euPath, 'utf8');
|
||||
const stripped = euContent
|
||||
.replace(/^\/\*\*[\s\S]*?\*\/\s*/m, '')
|
||||
.replace(/export function /g, 'function ')
|
||||
.trim();
|
||||
const euRep = `${euStart}\n // This block is automatically updated by /scripts/build-extension.cjs\n${stripped.split('\n').map(l => ' ' + l).join('\n')}\n ${euEnd}`;
|
||||
if (euPattern.test(content)) {
|
||||
content = content.replace(euPattern, euRep);
|
||||
} else {
|
||||
console.warn('⚠ WARNING: Episode utils markers not found in content.js');
|
||||
}
|
||||
if (!fs.existsSync(euPath)) {
|
||||
throw new Error(`CRITICAL: Episode utils source missing: ${euPath}. Aborting build.`);
|
||||
}
|
||||
const euContent = fs.readFileSync(euPath, 'utf8');
|
||||
const stripped = euContent
|
||||
.replace(/^\/\*\*[\s\S]*?\*\/\s*/m, '')
|
||||
.replace(/export function /g, 'function ')
|
||||
.trim();
|
||||
const euRep = `${euStart}\n // This block is automatically updated by /scripts/build-extension.cjs\n${stripped.split('\n').map(l => ' ' + l).join('\n')}\n ${euEnd}`;
|
||||
content = replaceRequiredBlock(content, euPattern, euRep, 'Episode utils injection');
|
||||
|
||||
fs.writeFileSync(destPath, content);
|
||||
console.log('✓ Injected shared constants into content.js');
|
||||
@@ -132,14 +128,19 @@ function copyExtensionFiles(targetDir, browserName) {
|
||||
uRep += ` const BROWSER_TYPE = "${browserName}";\n`;
|
||||
uRep += ` ${uEnd}`;
|
||||
|
||||
if (uPattern.test(content)) {
|
||||
content = content.replace(uPattern, uRep);
|
||||
} else {
|
||||
console.warn('⚠️ WARNING: Uninstall URL markers not found in background.js');
|
||||
}
|
||||
content = replaceRequiredBlock(content, uPattern, uRep, 'Uninstall URL injection');
|
||||
|
||||
fs.writeFileSync(destPath, content);
|
||||
console.log(`✓ Injected uninstall URL constants for ${browserName} into background.js`);
|
||||
} else if (item === 'popup.html') {
|
||||
let content = fs.readFileSync(srcPath, 'utf8');
|
||||
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19) + ' UTC';
|
||||
if (!content.includes('__BUILD_TIMESTAMP__')) {
|
||||
throw new Error('CRITICAL: Build timestamp placeholder not found in popup.html. Aborting build.');
|
||||
}
|
||||
content = content.replace(/__BUILD_TIMESTAMP__/g, timestamp);
|
||||
fs.writeFileSync(destPath, content);
|
||||
console.log(`✓ Injected build timestamp into popup.html: ${timestamp}`);
|
||||
} else {
|
||||
fs.copyFileSync(srcPath, destPath);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,14 @@ function extractFunction(name, text) {
|
||||
throw new Error(`${name} body did not terminate`);
|
||||
}
|
||||
|
||||
function makeSeekable(ranges = []) {
|
||||
return {
|
||||
length: ranges.length,
|
||||
start(i) { return ranges[i][0]; },
|
||||
end(i) { return ranges[i][1]; }
|
||||
};
|
||||
}
|
||||
|
||||
function makeVideo(name, width, height, options = {}) {
|
||||
return {
|
||||
name,
|
||||
@@ -28,7 +36,9 @@ function makeVideo(name, width, height, options = {}) {
|
||||
offsetWidth: width,
|
||||
offsetHeight: height,
|
||||
muted: options.muted ?? true,
|
||||
duration: options.duration ?? 0
|
||||
duration: options.duration ?? 0,
|
||||
currentTime: options.currentTime ?? 0,
|
||||
seekable: options.seekable ?? makeSeekable()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,4 +71,83 @@ assert.strictEqual(
|
||||
'findVideo should score Shadow DOM videos together with light DOM videos'
|
||||
);
|
||||
|
||||
function makeDocument(nodes = []) {
|
||||
return {
|
||||
querySelectorAll() { return nodes; }
|
||||
};
|
||||
}
|
||||
|
||||
function loadTimelineFns(hostname, document = makeDocument(), pageApiTime = null) {
|
||||
const disneyPageApiTime = pageApiTime
|
||||
? `let disneyPageApiTime = { position: ${pageApiTime.position}, duration: ${pageApiTime.duration}, at: Date.now() - ${pageApiTime.ageMs || 0} };`
|
||||
: 'let disneyPageApiTime = null;';
|
||||
return Function('window', 'document', [
|
||||
disneyPageApiTime,
|
||||
extractFunction('hostMatchesUrl', source),
|
||||
extractFunction('matchesPlayerUrls', source),
|
||||
extractFunction('isDisneyPlusHost', source),
|
||||
extractFunction('getDisneyPlusTimeline', source),
|
||||
extractFunction('getSiteQuirkAdapters', source),
|
||||
extractFunction('getActiveSiteQuirk', source),
|
||||
extractFunction('getSiteQuirkTimeline', source),
|
||||
extractFunction('getSiteQuirkDebug', source),
|
||||
extractFunction('getSyncCurrentTime', source),
|
||||
extractFunction('getSyncDuration', source),
|
||||
extractFunction('toNativeSeekTime', source),
|
||||
'return { getActiveSiteQuirk, getSyncCurrentTime, getSyncDuration, toNativeSeekTime };'
|
||||
].join('\n'))({ location: { hostname } }, document);
|
||||
}
|
||||
|
||||
function loadPlayerFixFns(hostname) {
|
||||
return Function('window', [
|
||||
extractFunction('hostMatchesUrl', source),
|
||||
extractFunction('matchesPlayerUrls', source),
|
||||
extractFunction('getPlayerActionFixes', source),
|
||||
extractFunction('getActivePlayerActionFix', source),
|
||||
'return { getPlayerActionFixes, getActivePlayerActionFix };'
|
||||
].join('\n'))({ location: { hostname } });
|
||||
}
|
||||
|
||||
const disneyFns = loadTimelineFns('www.disneyplus.com', makeDocument(), {
|
||||
position: 9,
|
||||
duration: 10800
|
||||
});
|
||||
assert.equal(disneyFns.getActiveSiteQuirk().name, 'disneyplus-page-api');
|
||||
assert.deepEqual(disneyFns.getActiveSiteQuirk().urls, ['disneyplus.com']);
|
||||
const disneyVideo = makeVideo('disney-offset', 1920, 1080, {
|
||||
currentTime: 29,
|
||||
duration: 0,
|
||||
seekable: makeSeekable([[0, 32400]])
|
||||
});
|
||||
assert.equal(disneyFns.getSyncCurrentTime(disneyVideo), 9);
|
||||
assert.equal(disneyFns.getSyncDuration(disneyVideo), 10800);
|
||||
assert.equal(disneyFns.toNativeSeekTime(disneyVideo, 39), 39);
|
||||
assert.equal(disneyFns.getSyncCurrentTime(makeVideo('disney-native-broken', 1920, 1080, {
|
||||
currentTime: Number.NaN,
|
||||
duration: 0
|
||||
})), 9);
|
||||
|
||||
const disneyNoPageApiFns = loadTimelineFns('www.disneyplus.com');
|
||||
const disneyOffsetVideo = makeVideo('disney-offset', 1920, 1080, {
|
||||
currentTime: 29,
|
||||
duration: 0,
|
||||
seekable: makeSeekable([[20, 10820]])
|
||||
});
|
||||
assert.equal(disneyNoPageApiFns.getSyncCurrentTime(disneyOffsetVideo), null);
|
||||
assert.equal(disneyNoPageApiFns.getSyncDuration(disneyOffsetVideo), 0);
|
||||
assert.equal(disneyNoPageApiFns.toNativeSeekTime(disneyOffsetVideo, 39), 39);
|
||||
|
||||
const genericFns = loadTimelineFns('example.com');
|
||||
assert.equal(genericFns.getActiveSiteQuirk(), null);
|
||||
assert.equal(genericFns.getSyncCurrentTime(disneyVideo), 29);
|
||||
assert.equal(genericFns.getSyncDuration(disneyVideo), 0);
|
||||
assert.equal(genericFns.toNativeSeekTime(disneyVideo, 39), 39);
|
||||
|
||||
const twitchFixFns = loadPlayerFixFns('player.twitch.tv');
|
||||
assert.equal(twitchFixFns.getActivePlayerActionFix().name, 'twitch-player-buttons');
|
||||
assert.deepEqual(twitchFixFns.getActivePlayerActionFix().urls, ['twitch.tv']);
|
||||
|
||||
const genericFixFns = loadPlayerFixFns('example.com');
|
||||
assert.equal(genericFixFns.getActivePlayerActionFix(), null);
|
||||
|
||||
console.log('content video finder tests passed');
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
checkEventRate,
|
||||
checkHealthRate,
|
||||
checkAdminMetricsAuthRate,
|
||||
checkLeaveRoomRate,
|
||||
checkAuthRate,
|
||||
recordAuthFailure,
|
||||
clearRateLimitMaps,
|
||||
@@ -13,11 +14,13 @@ import {
|
||||
healthCounts,
|
||||
adminMetricsAuthCounts,
|
||||
roomListCooldowns,
|
||||
leaveRoomCounts,
|
||||
rateLimitDenied,
|
||||
startRateLimitCleanup,
|
||||
stopRateLimitCleanup,
|
||||
CONNECTION_RATE_LIMIT,
|
||||
EVENT_RATE_LIMIT
|
||||
EVENT_RATE_LIMIT,
|
||||
LEAVE_ROOM_RATE_LIMIT
|
||||
} from '../server/rate-limiter.js';
|
||||
|
||||
// Helper: mock io for cleanup
|
||||
@@ -26,7 +29,7 @@ const mockIo = { sockets: { sockets: new Map() } };
|
||||
// Reset state before each test group
|
||||
function reset() {
|
||||
clearRateLimitMaps();
|
||||
Object.assign(rateLimitDenied, { connections: 0, events: 0, health: 0, adminMetricsAuth: 0, roomList: 0 });
|
||||
Object.assign(rateLimitDenied, { connections: 0, events: 0, health: 0, adminMetricsAuth: 0, roomList: 0, leaveRoom: 0 });
|
||||
stopRateLimitCleanup();
|
||||
}
|
||||
|
||||
@@ -52,6 +55,16 @@ assert.equal(rateLimitDenied.events, 1);
|
||||
reset();
|
||||
assert.equal(checkEventRate('sock2'), true, 'separate socket independent');
|
||||
|
||||
// --- checkLeaveRoomRate ---
|
||||
reset();
|
||||
assert.equal(checkLeaveRoomRate('sock-leave-1'), true, 'first leave-room event allowed');
|
||||
for (let i = 0; i < LEAVE_ROOM_RATE_LIMIT - 1; i++) checkLeaveRoomRate('sock-leave-1');
|
||||
assert.equal(checkLeaveRoomRate('sock-leave-1'), false, `leave-room beyond ${LEAVE_ROOM_RATE_LIMIT}/window blocked`);
|
||||
assert.equal(rateLimitDenied.leaveRoom, 1);
|
||||
|
||||
reset();
|
||||
assert.equal(checkLeaveRoomRate('sock-leave-2'), true, 'separate leave-room socket independent');
|
||||
|
||||
// --- checkHealthRate ---
|
||||
reset();
|
||||
assert.equal(checkHealthRate('1.2.3.4'), true, 'first health check allowed');
|
||||
@@ -91,12 +104,14 @@ eventCounts.set('sock1', { count: 1, resetTime: Date.now() + 10000 });
|
||||
healthCounts.set('ip2', { count: 1, resetTime: Date.now() + 60000 });
|
||||
adminMetricsAuthCounts.set('ip3', { count: 1, resetTime: Date.now() + 60000 });
|
||||
roomListCooldowns.set('sock2', Date.now());
|
||||
leaveRoomCounts.set('sock3', { count: 1, resetTime: Date.now() + 60000 });
|
||||
clearRateLimitMaps();
|
||||
assert.equal(connectionCounts.size, 0, 'connectionCounts cleared');
|
||||
assert.equal(eventCounts.size, 0, 'eventCounts cleared');
|
||||
assert.equal(healthCounts.size, 0, 'healthCounts cleared');
|
||||
assert.equal(adminMetricsAuthCounts.size, 0, 'adminMetricsAuthCounts cleared');
|
||||
assert.equal(roomListCooldowns.size, 0, 'roomListCooldowns cleared');
|
||||
assert.equal(leaveRoomCounts.size, 0, 'leaveRoomCounts cleared');
|
||||
|
||||
// --- startRateLimitCleanup / stopRateLimitCleanup ---
|
||||
reset();
|
||||
@@ -108,7 +123,9 @@ assert.ok(true, 'cleanup start/stop does not throw');
|
||||
// --- rateLimitDenied reset ---
|
||||
reset();
|
||||
rateLimitDenied.connections = 5;
|
||||
Object.assign(rateLimitDenied, { connections: 0, events: 0, health: 0, adminMetricsAuth: 0, roomList: 0 });
|
||||
rateLimitDenied.leaveRoom = 5;
|
||||
Object.assign(rateLimitDenied, { connections: 0, events: 0, health: 0, adminMetricsAuth: 0, roomList: 0, leaveRoom: 0 });
|
||||
assert.equal(rateLimitDenied.connections, 0, 'denial counter resettable');
|
||||
assert.equal(rateLimitDenied.leaveRoom, 0, 'leave-room denial counter resettable');
|
||||
|
||||
console.log('rate-limiter tests passed');
|
||||
|
||||
@@ -52,7 +52,7 @@ const basicHealth = buildHealthPayload({
|
||||
now: 1234,
|
||||
uptime: 99,
|
||||
memoryUsage: () => ({ rss: 10, heapUsed: 5, heapTotal: 8 }),
|
||||
rateLimitSizes: { connections: 1, events: 2, health: 3, adminMetricsAuth: 4, authFailures: 5, roomList: 6 }
|
||||
rateLimitSizes: { connections: 1, events: 2, health: 3, adminMetricsAuth: 4, authFailures: 5, roomList: 6, leaveRoom: 7 }
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
@@ -68,7 +68,8 @@ const adminHealth = buildHealthPayload({
|
||||
now: 1234,
|
||||
uptime: 99,
|
||||
memoryUsage: () => ({ rss: 10, heapUsed: 5, heapTotal: 8 }),
|
||||
rateLimitSizes: { connections: 1, events: 2, health: 3, adminMetricsAuth: 4, authFailures: 5, roomList: 6 }
|
||||
rateLimitSizes: { connections: 1, events: 2, health: 3, adminMetricsAuth: 4, authFailures: 5, roomList: 6, leaveRoom: 7 },
|
||||
rateLimitDenied: { leaveRoom: 8 }
|
||||
});
|
||||
|
||||
assert.equal(adminHealth.peers, 5, 'admin metrics should include aggregate peer count');
|
||||
@@ -79,8 +80,8 @@ assert.deepEqual(adminHealth.memory, { rss: 10, heapUsed: 5, heapTotal: 8 }, 'ad
|
||||
assert.deepEqual(
|
||||
adminHealth.rateLimits,
|
||||
{
|
||||
trackedClients: { connections: 1, events: 2, health: 3, adminMetricsAuth: 4, authFailures: 5, roomList: 6 },
|
||||
denied: { connections: 0, events: 0, health: 0, adminMetricsAuth: 0, roomList: 0 }
|
||||
trackedClients: { connections: 1, events: 2, health: 3, adminMetricsAuth: 4, authFailures: 5, roomList: 6, leaveRoom: 7 },
|
||||
denied: { connections: 0, events: 0, health: 0, adminMetricsAuth: 0, roomList: 0, leaveRoom: 8 }
|
||||
},
|
||||
'admin metrics should expose rate-limit tracking and denial counts'
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
TITLE_PRIVACY_MODES,
|
||||
applyTitlePrivacyToPayload,
|
||||
normalizeSendTabTitle,
|
||||
normalizeTabTitle,
|
||||
normalizeTitlePrivacyMode,
|
||||
sanitizeSharedTitle,
|
||||
sanitizeTabTitle
|
||||
@@ -15,8 +16,13 @@ assert.equal(normalizeSendTabTitle(undefined, TITLE_PRIVACY_MODES.FULL), true);
|
||||
assert.equal(normalizeSendTabTitle(undefined, TITLE_PRIVACY_MODES.EPISODE), false);
|
||||
assert.equal(normalizeSendTabTitle(true, TITLE_PRIVACY_MODES.HIDDEN), true);
|
||||
assert.equal(normalizeSendTabTitle(false, TITLE_PRIVACY_MODES.FULL), false);
|
||||
assert.equal(normalizeTabTitle('(12) Testvideo - YouTube'), 'Testvideo - YouTube');
|
||||
assert.equal(normalizeTabTitle('[7] Testvideo - YouTube'), 'Testvideo - YouTube');
|
||||
assert.equal(normalizeTabTitle('(500) Days of Summer'), '(500) Days of Summer');
|
||||
assert.equal(normalizeTabTitle(' '), null);
|
||||
|
||||
assert.equal(sanitizeTabTitle('Private Tab', true), 'Private Tab');
|
||||
assert.equal(sanitizeTabTitle('(12) Private Tab', true), 'Private Tab');
|
||||
assert.equal(sanitizeTabTitle('Private Tab', false), null);
|
||||
assert.equal(sanitizeTabTitle('', true), null);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import path from 'node:path';
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
||||
const checks = [
|
||||
['vitest unit tests', 'npm', ['run', 'test:unit']],
|
||||
['server ops', 'node', ['scripts/test-server-ops.mjs']],
|
||||
['server routes', 'node', ['scripts/test-server-routes.mjs'], {
|
||||
env: { ADMIN_METRICS_TOKEN: 'verify-admin-token-with-more-than-32-chars' }
|
||||
|
||||
@@ -2,6 +2,11 @@ PORT="3000"
|
||||
MIN_VERSION="1.0.0"
|
||||
MAX_ROOMS="1000"
|
||||
MAX_PEERS_PER_ROOM="25"
|
||||
|
||||
# Required for production/self-hosting: unique random salt used for room-password hashes.
|
||||
# Generate one with: openssl rand -base64 32
|
||||
SERVER_SALT=""
|
||||
|
||||
# Optional: enables aggregate-only admin metrics on /health with Authorization: Bearer <token>
|
||||
# Use a long random token, 32+ characters recommended.
|
||||
ADMIN_METRICS_TOKEN=""
|
||||
|
||||
@@ -16,6 +16,11 @@ PORT="3000"
|
||||
MAX_ROOMS="1000"
|
||||
MAX_PEERS_PER_ROOM="25"
|
||||
MIN_VERSION="1.0.0"
|
||||
|
||||
# Required for production/self-hosting: unique random salt used for room-password hashes.
|
||||
# Generate one with: openssl rand -base64 32
|
||||
SERVER_SALT=""
|
||||
|
||||
# Optional: enables aggregate-only admin metrics on /health with Authorization: Bearer <token>
|
||||
# Use a long random token, 32+ characters recommended.
|
||||
ADMIN_METRICS_TOKEN=""
|
||||
@@ -24,6 +29,19 @@ ADMIN_METRICS_TOKEN=""
|
||||
DEBUG_LOGGING="0"
|
||||
```
|
||||
|
||||
### Room Password Salt
|
||||
`SERVER_SALT` is used to HMAC room passwords before they are stored in RAM. The relay has a built-in fallback so development keeps working, but that fallback is public in the repository and the server logs a startup warning when it is used.
|
||||
|
||||
For every real self-hosted deployment, set a unique random value:
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
Then add it to `server/.env` or your Compose environment:
|
||||
```bash
|
||||
SERVER_SALT=replace-with-a-long-random-salt
|
||||
```
|
||||
|
||||
### Health & Metrics
|
||||
`GET /` and `GET /health` are IP-rate-limited to 10 requests per minute per client IP. These health-style responses are cached server-side for 60 seconds and refreshed lazily on request. By default `/health` returns only basic service status, uptime, room count, connection count, and a timestamp.
|
||||
|
||||
@@ -63,11 +81,20 @@ npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
### Verification
|
||||
The server is covered by the root verification suite. From the repository root, run:
|
||||
```bash
|
||||
npm run verify
|
||||
```
|
||||
|
||||
For focused server checks, see `scripts/test-server-ops.mjs`, `scripts/test-server-routes.mjs`, `scripts/test-server-ws.mjs`, and `scripts/test-rate-limiter.mjs`.
|
||||
|
||||
## Security
|
||||
- **Rate Limiting**: IP-based connection limits and socket-based event limits.
|
||||
- **Health Endpoint Throttle**: `GET /` and `GET /health` are limited to 10 requests per minute per IP, with 60-second lazy server-side response caching and stricter throttling for wrong admin bearer attempts.
|
||||
- **Room Discovery Throttle**: Room-list refreshes are rate-limited server-side to one request every 10 seconds per socket.
|
||||
- **Token Handshake**: Requires a valid token defined in the root `shared/constants.js`.
|
||||
- **Password Hash Salt**: Set `SERVER_SALT` in every deployment so room-password hashes are not derived with the public fallback salt.
|
||||
- **Single Source of Truth**: The server imports constants directly from the root `shared/` directory.
|
||||
- **In-Memory**: Rooms are automatically pruned after 2 hours of inactivity.
|
||||
- **Reverse Proxy Boundary**: The server trusts one reverse proxy hop for client IP detection. Keep the Node port private/firewalled so clients can only reach it through Caddy or another trusted proxy.
|
||||
|
||||
+15
-4
@@ -22,6 +22,7 @@ import {
|
||||
healthCounts,
|
||||
adminMetricsAuthCounts,
|
||||
roomListCooldowns,
|
||||
leaveRoomCounts,
|
||||
rateLimitDenied,
|
||||
checkAuthRate,
|
||||
recordAuthFailure,
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
checkEventRate,
|
||||
checkHealthRate,
|
||||
checkAdminMetricsAuthRate,
|
||||
checkLeaveRoomRate,
|
||||
startRateLimitCleanup,
|
||||
stopRateLimitCleanup,
|
||||
clearRateLimitMaps
|
||||
@@ -114,7 +116,8 @@ app.get('/health', (req, res) => {
|
||||
health: healthCounts.size,
|
||||
adminMetricsAuth: adminMetricsAuthCounts.size,
|
||||
authFailures: failedAuthAttempts.size,
|
||||
roomList: roomListCooldowns.size
|
||||
roomList: roomListCooldowns.size,
|
||||
leaveRoom: leaveRoomCounts.size
|
||||
},
|
||||
rateLimitDenied
|
||||
})
|
||||
@@ -661,6 +664,11 @@ io.on('connection', (socket) => {
|
||||
});
|
||||
|
||||
socket.on(EVENTS.LEAVE_ROOM, () => {
|
||||
if (!checkLeaveRoomRate(socket.id)) {
|
||||
log('SECURITY', `LEAVE_ROOM rate limit exceeded for socket: ${socket.id}`);
|
||||
socket.disconnect(true);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const mapping = socketToRoom.get(socket.id);
|
||||
if (mapping) {
|
||||
@@ -839,6 +847,7 @@ io.on('connection', (socket) => {
|
||||
socket.on('disconnect', () => {
|
||||
eventCounts.delete(socket.id);
|
||||
roomListCooldowns.delete(socket.id);
|
||||
leaveRoomCounts.delete(socket.id);
|
||||
const mapping = socketToRoom.get(socket.id);
|
||||
if (mapping) {
|
||||
try {
|
||||
@@ -919,12 +928,14 @@ function gracefulShutdown(signal) {
|
||||
log('SERVER', `${signal} received — starting graceful shutdown...`);
|
||||
// 1. Notify all connected clients so they can display a meaningful message
|
||||
io.emit(EVENTS.ERROR, { message: 'Server is restarting. Reconnecting automatically...' });
|
||||
// 2. Stop accepting new HTTP connections
|
||||
// 2. Gracefully disconnect all Socket.IO clients
|
||||
io.disconnectSockets(true);
|
||||
// 3. Stop accepting new HTTP connections
|
||||
httpServer.close(() => {
|
||||
log('SERVER', 'HTTP server closed. Exiting.');
|
||||
process.exit(0);
|
||||
});
|
||||
// 3. Safety net: force-exit after 5s if connections don't drain
|
||||
// 4. Safety net: force-exit after 5s if connections don't drain
|
||||
setTimeout(() => {
|
||||
log('SERVER', 'Force-exit after timeout.');
|
||||
process.exit(1);
|
||||
@@ -943,7 +954,7 @@ export async function stopServerForTests() {
|
||||
healthResponseCache.clear();
|
||||
io.removeAllListeners();
|
||||
io.disconnectSockets(true);
|
||||
Object.assign(rateLimitDenied, { connections: 0, events: 0, health: 0, adminMetricsAuth: 0, roomList: 0 });
|
||||
Object.assign(rateLimitDenied, { connections: 0, events: 0, health: 0, adminMetricsAuth: 0, roomList: 0, leaveRoom: 0 });
|
||||
if (!httpServer.listening) return;
|
||||
await new Promise((resolve, reject) => {
|
||||
httpServer.close((err) => err ? reject(err) : resolve());
|
||||
|
||||
+4
-2
@@ -97,14 +97,16 @@ export function buildHealthPayload({
|
||||
health: rateLimitSizes.health || 0,
|
||||
adminMetricsAuth: rateLimitSizes.adminMetricsAuth || 0,
|
||||
authFailures: rateLimitSizes.authFailures || 0,
|
||||
roomList: rateLimitSizes.roomList || 0
|
||||
roomList: rateLimitSizes.roomList || 0,
|
||||
leaveRoom: rateLimitSizes.leaveRoom || 0
|
||||
},
|
||||
denied: {
|
||||
connections: rateLimitDenied.connections || 0,
|
||||
events: rateLimitDenied.events || 0,
|
||||
health: rateLimitDenied.health || 0,
|
||||
adminMetricsAuth: rateLimitDenied.adminMetricsAuth || 0,
|
||||
roomList: rateLimitDenied.roomList || 0
|
||||
roomList: rateLimitDenied.roomList || 0,
|
||||
leaveRoom: rateLimitDenied.leaveRoom || 0
|
||||
}
|
||||
},
|
||||
memory: {
|
||||
|
||||
+26
-1
@@ -14,6 +14,8 @@ export const EVENT_RATE_LIMIT = 50; // max relayed events per soc
|
||||
export const EVENT_RATE_WINDOW_MS = 10000; // 10 seconds
|
||||
export const HEALTH_RATE_WINDOW_MS = 60000; // 1 minute
|
||||
export const ADMIN_METRICS_AUTH_WINDOW_MS = 60000; // 1 minute
|
||||
export const LEAVE_ROOM_RATE_LIMIT = 10; // max LEAVE_ROOM events per socket per window
|
||||
export const LEAVE_ROOM_RATE_WINDOW_MS = 60000; // 1 minute
|
||||
|
||||
export const connectionCounts = new Map(); // ip -> { count, resetTime }
|
||||
export const failedAuthAttempts = new Map(); // Map<IP+RoomID, {count, lastAttempt}>
|
||||
@@ -21,13 +23,15 @@ export const eventCounts = new Map(); // socketId -> { count, resetTime }
|
||||
export const healthCounts = new Map(); // ip -> { count, resetTime }
|
||||
export const adminMetricsAuthCounts = new Map(); // ip -> { count, resetTime }
|
||||
export const roomListCooldowns = new Map(); // socketId -> last allowed timestamp
|
||||
export const leaveRoomCounts = new Map(); // socketId -> { count, resetTime }
|
||||
|
||||
export const rateLimitDenied = {
|
||||
connections: 0,
|
||||
events: 0,
|
||||
health: 0,
|
||||
adminMetricsAuth: 0,
|
||||
roomList: 0
|
||||
roomList: 0,
|
||||
leaveRoom: 0
|
||||
};
|
||||
|
||||
let authCleanupId = null;
|
||||
@@ -122,6 +126,20 @@ export function checkAdminMetricsAuthRate(ip) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function checkLeaveRoomRate(socketId) {
|
||||
const now = Date.now();
|
||||
const entry = leaveRoomCounts.get(socketId) || { count: 0, resetTime: now + LEAVE_ROOM_RATE_WINDOW_MS };
|
||||
if (now > entry.resetTime) {
|
||||
entry.count = 0;
|
||||
entry.resetTime = now + LEAVE_ROOM_RATE_WINDOW_MS;
|
||||
}
|
||||
entry.count++;
|
||||
leaveRoomCounts.set(socketId, entry);
|
||||
if (entry.count <= LEAVE_ROOM_RATE_LIMIT) return true;
|
||||
rateLimitDenied.leaveRoom++;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function startRateLimitCleanup(io) {
|
||||
if (authCleanupId !== null || rateLimitCleanupId !== null) return; // guard double-start
|
||||
// Clean up old auth failure records (every 15 minutes)
|
||||
@@ -145,6 +163,11 @@ export function startRateLimitCleanup(io) {
|
||||
eventCounts.delete(socketId);
|
||||
}
|
||||
}
|
||||
for (const [socketId, entry] of leaveRoomCounts.entries()) {
|
||||
if (now > entry.resetTime || !io.sockets.sockets.has(socketId)) {
|
||||
leaveRoomCounts.delete(socketId);
|
||||
}
|
||||
}
|
||||
for (const [ip, entry] of healthCounts.entries()) {
|
||||
if (now > entry.resetTime) healthCounts.delete(ip);
|
||||
}
|
||||
@@ -166,7 +189,9 @@ export function clearRateLimitMaps() {
|
||||
connectionCounts.clear();
|
||||
failedAuthAttempts.clear();
|
||||
eventCounts.clear();
|
||||
|
||||
healthCounts.clear();
|
||||
adminMetricsAuthCounts.clear();
|
||||
roomListCooldowns.clear();
|
||||
leaveRoomCounts.clear();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import {
|
||||
checkLeaveRoomRate,
|
||||
LEAVE_ROOM_RATE_LIMIT,
|
||||
LEAVE_ROOM_RATE_WINDOW_MS,
|
||||
rateLimitDenied,
|
||||
leaveRoomCounts,
|
||||
clearRateLimitMaps
|
||||
} from './rate-limiter.js';
|
||||
|
||||
describe('LEAVE_ROOM Rate Limiter', () => {
|
||||
const testSocketId = 'test-socket-123';
|
||||
|
||||
beforeEach(() => {
|
||||
clearRateLimitMaps();
|
||||
rateLimitDenied.leaveRoom = 0;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearRateLimitMaps();
|
||||
});
|
||||
|
||||
it('should allow LEAVE_ROOM within limit', () => {
|
||||
// Test within the rate limit
|
||||
for (let i = 0; i < LEAVE_ROOM_RATE_LIMIT; i++) {
|
||||
const result = checkLeaveRoomRate(testSocketId);
|
||||
expect(result).toBe(true);
|
||||
}
|
||||
expect(rateLimitDenied.leaveRoom).toBe(0);
|
||||
});
|
||||
|
||||
it('should block LEAVE_ROOM when exceeding limit', () => {
|
||||
// Fill up to the limit
|
||||
for (let i = 0; i < LEAVE_ROOM_RATE_LIMIT; i++) {
|
||||
checkLeaveRoomRate(testSocketId);
|
||||
}
|
||||
|
||||
// Next request should be blocked
|
||||
const result = checkLeaveRoomRate(testSocketId);
|
||||
expect(result).toBe(false);
|
||||
expect(rateLimitDenied.leaveRoom).toBe(1);
|
||||
});
|
||||
|
||||
it('should reset count after window expires', () => {
|
||||
// Fill up to the limit
|
||||
for (let i = 0; i < LEAVE_ROOM_RATE_LIMIT; i++) {
|
||||
checkLeaveRoomRate(testSocketId);
|
||||
}
|
||||
|
||||
// Verify we're at the limit
|
||||
let result = checkLeaveRoomRate(testSocketId);
|
||||
expect(result).toBe(false);
|
||||
|
||||
// Fast-forward time beyond the rate limit window
|
||||
const entry = leaveRoomCounts.get(testSocketId);
|
||||
entry.resetTime = Date.now() - LEAVE_ROOM_RATE_WINDOW_MS - 1000;
|
||||
leaveRoomCounts.set(testSocketId, entry);
|
||||
|
||||
// Next request should be allowed again
|
||||
result = checkLeaveRoomRate(testSocketId);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle multiple sockets independently', () => {
|
||||
const socketId2 = 'test-socket-456';
|
||||
|
||||
// Fill up first socket
|
||||
for (let i = 0; i < LEAVE_ROOM_RATE_LIMIT; i++) {
|
||||
checkLeaveRoomRate(testSocketId);
|
||||
}
|
||||
|
||||
// Second socket should still be allowed
|
||||
const result = checkLeaveRoomRate(socketId2);
|
||||
expect(result).toBe(true);
|
||||
|
||||
// First socket should be blocked
|
||||
const result2 = checkLeaveRoomRate(testSocketId);
|
||||
expect(result2).toBe(false);
|
||||
});
|
||||
|
||||
it('should increment rateLimitDenied counter on block', () => {
|
||||
for (let i = 0; i < LEAVE_ROOM_RATE_LIMIT; i++) {
|
||||
checkLeaveRoomRate(testSocketId);
|
||||
}
|
||||
|
||||
checkLeaveRoomRate(testSocketId);
|
||||
expect(rateLimitDenied.leaveRoom).toBe(1);
|
||||
|
||||
checkLeaveRoomRate(testSocketId);
|
||||
expect(rateLimitDenied.leaveRoom).toBe(2);
|
||||
});
|
||||
|
||||
it('should be cleared by the shared reset helper', () => {
|
||||
checkLeaveRoomRate(testSocketId);
|
||||
expect(leaveRoomCounts.size).toBe(1);
|
||||
|
||||
clearRateLimitMaps();
|
||||
expect(leaveRoomCounts.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rate Limit Constants', () => {
|
||||
it('should have correct rate limit values', () => {
|
||||
expect(LEAVE_ROOM_RATE_LIMIT).toBe(10);
|
||||
expect(LEAVE_ROOM_RATE_WINDOW_MS).toBe(60000); // 1 minute
|
||||
});
|
||||
});
|
||||
+61
-21
@@ -1,33 +1,73 @@
|
||||
# KoalaSync Shared Constants
|
||||
|
||||
This directory contains constants and protocol definitions used by both the extension and the server.
|
||||
This directory is the source of truth for protocol constants and shared browser/server data.
|
||||
|
||||
## Where You Are
|
||||
|
||||
- `constants.js`: protocol version, app version, official URLs/token, event names, control modes, capabilities, and timing constants.
|
||||
- `blacklist.js`: domains hidden by the popup's clutter filter.
|
||||
- `names.js`: generated username parts and helper used by the extension.
|
||||
- `extension/shared/`: generated mirror created by `npm run build:extension`; do not edit that mirror directly.
|
||||
|
||||
## Syncing with the Extension
|
||||
> [!IMPORTANT]
|
||||
> Every time this directory is modified, you must run `node scripts/build-extension.cjs` to keep the extension's copy up to date.
|
||||
|
||||
Because Browser Extensions (Manifest V3) cannot load files outside their root directory, all files in this directory must be copied to `extension/shared/` whenever they are modified. The build script handles this automatically.
|
||||
> [!IMPORTANT]
|
||||
> After modifying any file in this directory, run `npm run build:extension` from the repository root.
|
||||
|
||||
Browser extensions cannot import files outside their own root directory, so the build script copies shared files into `extension/shared/` and injects selected constants into `content.js`.
|
||||
|
||||
## Security & Versioning Constants
|
||||
- `OFFICIAL_SERVER_TOKEN`: A 32-byte hex token required to connect to the official relay server.
|
||||
- `APP_VERSION`: The current version of the extension. Automatically injected from the git tag during CI release builds.
|
||||
- `OFFICIAL_SERVER_URL`: The default endpoint for the official KoalaSync relay.
|
||||
|
||||
- `PROTOCOL_VERSION`: exact protocol version required during `join_room`.
|
||||
- `APP_VERSION`: extension/app version, injected from release tags by CI.
|
||||
- `OFFICIAL_SERVER_URL`: default public relay endpoint.
|
||||
- `OFFICIAL_LANDING_PAGE_URL`: public website and invitation bridge origin.
|
||||
- `OFFICIAL_SERVER_TOKEN`: public client token used to reject non-KoalaSync Socket.IO clients. It is not a secret; see `docs/KNOWN_LIMITATIONS.md`.
|
||||
- `SUPPORT_URL` and `GITHUB_URL`: public project links used by the extension UI.
|
||||
|
||||
## Control Modes & Capabilities
|
||||
|
||||
- `CONTROL_MODES.EVERYONE`: default room mode; any peer may control playback.
|
||||
- `CONTROL_MODES.HOST_ONLY`: only the host and promoted controllers may move playback.
|
||||
- `CAPABILITIES.HOST_CONTROL`: relay supports host-only room authority.
|
||||
- `CAPABILITIES.CO_HOST`: relay supports promoted controller peers.
|
||||
|
||||
Clients should enable capability-gated UI only when the relay advertises the matching flag in `room_data.capabilities`.
|
||||
|
||||
## Protocol Events
|
||||
For the complete and current event list, see the `EVENTS` object in [`constants.js`](constants.js). Key events include:
|
||||
|
||||
For the complete event list, read the `EVENTS` object in [`constants.js`](constants.js). Current event groups:
|
||||
|
||||
| Event | Direction | Purpose |
|
||||
|:------|:----------|:--------|
|
||||
| `JOIN_ROOM` | Client → Server | Request to join a room with credentials |
|
||||
| `LEAVE_ROOM` | Client → Server | Leave the current room |
|
||||
| `ROOM_DATA` | Server → Client | Current room state (peers list) |
|
||||
|:---|:---|:---|
|
||||
| `JOIN_ROOM` | Client -> Server | Join or create a room with credentials and protocol version |
|
||||
| `LEAVE_ROOM` | Client -> Server | Leave the current room |
|
||||
| `ROOM_DATA` | Server -> Client | Initial room snapshot, peers, host state, capabilities |
|
||||
| `ERROR` | Server -> Client | Connection, auth, capacity, or protocol error |
|
||||
| `SET_CONTROL_MODE` | Client -> Server | Host switches between `everyone` and `host-only` |
|
||||
| `CONTROL_MODE` | Server -> Client | Host/control-mode/controller snapshot changed |
|
||||
| `SET_PEER_ROLE` | Client -> Server | Host promotes or demotes a co-host/controller |
|
||||
| `PLAY` / `PAUSE` / `SEEK` | Bidirectional relay | Media control commands |
|
||||
| `PEER_STATUS` | Bidirectional relay | Heartbeat or join/leave notification |
|
||||
| `FORCE_SYNC_PREPARE` | Bidirectional relay | Phase 1: Pause & seek to target time |
|
||||
| `FORCE_SYNC_ACK` | Bidirectional relay | Phase 1 confirmation: peer is buffered |
|
||||
| `FORCE_SYNC_EXECUTE` | Bidirectional relay | Phase 2: Resume playback simultaneously |
|
||||
| `EVENT_ACK` | Server → Client | Delivery confirmation for UI feedback |
|
||||
| `EPISODE_LOBBY` | Bidirectional relay | Episode transition: waiting for all peers |
|
||||
| `EPISODE_READY` | Bidirectional relay | Episode confirmation: peer has loaded |
|
||||
| `GET_ROOMS` / `ROOM_LIST` | Client ↔ Server | Room discovery |
|
||||
| `ERROR` | Server → Client | Error message |
|
||||
| `PEER_STATUS` | Bidirectional relay | Heartbeat and current media state |
|
||||
| `FORCE_SYNC_PREPARE` | Bidirectional relay | Phase 1: pause and seek peers to a target time |
|
||||
| `FORCE_SYNC_ACK` | Bidirectional relay | Phase 1 confirmation that a peer is ready |
|
||||
| `FORCE_SYNC_EXECUTE` | Bidirectional relay | Phase 2: resume playback together |
|
||||
| `EVENT_ACK` | Server -> Client | Delivery confirmation for UI feedback |
|
||||
| `EPISODE_LOBBY` | Bidirectional relay | Episode transition lobby started |
|
||||
| `EPISODE_READY` | Bidirectional relay | Peer has loaded the episode and is ready |
|
||||
| `EPISODE_LOBBY_CANCEL` | Bidirectional relay | Active episode lobby cancelled |
|
||||
| `GET_ROOMS` / `ROOM_LIST` | Client <-> Server | Room discovery with server-side cooldown |
|
||||
| `PING` / `PONG` | Client <-> Server/Peer | Server RTT and peer latency checks |
|
||||
|
||||
## Timing Constants
|
||||
|
||||
- `HEARTBEAT_INTERVAL`: content heartbeat interval in milliseconds.
|
||||
- `FORCE_SYNC_TIMEOUT`: max wait for force-sync ACKs.
|
||||
- `EPISODE_LOBBY_TIMEOUT`: max wait for episode-lobby readiness.
|
||||
|
||||
## Do Not Break
|
||||
|
||||
- Do not rename events without updating `docs/PROTOCOL.md`, `server/index.js`, `extension/background.js`, and the build-injected `content.js` path.
|
||||
- Do not edit `extension/shared/` directly.
|
||||
- Keep new server-gated features behind `CAPABILITIES` so old clients and old relays degrade cleanly.
|
||||
- Run `npm run build:extension` and `npm run verify` after protocol changes.
|
||||
|
||||
@@ -53,10 +53,6 @@ export const BLACKLIST_DOMAINS = [
|
||||
'docs.google.com',
|
||||
'sheets.google.com',
|
||||
'slides.google.com',
|
||||
'drive.google.com',
|
||||
'dropbox.com',
|
||||
'onedrive.live.com',
|
||||
'icloud.com',
|
||||
|
||||
// Messengers
|
||||
'web.whatsapp.com',
|
||||
@@ -117,7 +113,6 @@ export const BLACKLIST_DOMAINS = [
|
||||
'status.koalastuff.net',
|
||||
'sync.koalastuff.net',
|
||||
'timer.koalastuff.net',
|
||||
'localhost',
|
||||
'zoom.us',
|
||||
'teams.microsoft.com',
|
||||
'meet.google.com',
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
export const PROTOCOL_VERSION = "1.0.0";
|
||||
export const APP_VERSION = "2.5.0";
|
||||
export const APP_VERSION = "2.5.3";
|
||||
|
||||
export const OFFICIAL_SERVER_URL = 'wss://syncserver.koalastuff.net';
|
||||
export const OFFICIAL_LANDING_PAGE_URL = 'https://sync.koalastuff.net';
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: false,
|
||||
environment: 'node',
|
||||
include: [
|
||||
'server/**/*.test.js',
|
||||
'server/**/*.test.mjs',
|
||||
'shared/**/*.test.js',
|
||||
'shared/**/*.test.mjs'
|
||||
],
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'lcov'],
|
||||
include: ['server/**/*.js', 'shared/**/*.js'],
|
||||
exclude: [
|
||||
'**/node_modules/**',
|
||||
'**/scripts/**',
|
||||
'**/extension/**'
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
4650678bb36b488d8f64343a67f1b931
|
||||
+68
-41
@@ -1,74 +1,101 @@
|
||||
# KoalaSync Website & Invitation Bridge
|
||||
|
||||
This directory contains the KoalaSync website. It serves a dual purpose: it is both the **marketing landing page** and the **technical bridge** for joining synchronized rooms.
|
||||
This directory contains the static KoalaSync website. It is both the public marketing/onboarding site and the browser-side invitation bridge for joining sync rooms.
|
||||
|
||||
## Where You Are
|
||||
|
||||
- `template.html`, `style.css`, `app.js`, `lang-init.js`, and `locales/*.json` are source files.
|
||||
- `build.cjs` compiles the static site into `website/www/`.
|
||||
- `website/www/` is generated output. Do not edit files there directly.
|
||||
- `join.html` handles room-invite links and communicates with the extension through `bridge.js`.
|
||||
- `llms.txt` gives crawlers and AI tools a compact project overview.
|
||||
- `alternatives/` contains comparison pages for users evaluating KoalaSync against other watch-party approaches.
|
||||
|
||||
## Core Roles
|
||||
|
||||
### 1. Marketing & Onboarding
|
||||
Provides a premium, multi-language (EN/DE/FR/ES/PT-BR/RU/IT/PL/TR/NL/JA/KO/PT) overview of features, setup instructions, and direct links to the extension stores.
|
||||
|
||||
### 2. The Invitation Bridge (`join.html`)
|
||||
The website handles incoming invitation links. When a user clicks a link like `sync.koalastuff.net/join.html#join:roomID:pass`, the website:
|
||||
- **Detects the Extension**: Verifies if KoalaSync is installed via the `bridge.js` content script.
|
||||
- **Privacy-First Handshake**: The room credentials (ID/Password) are stored in the **URL Hash (#)**. This ensures the sensitive credentials **never reach the web server** and are processed entirely within the user's browser.
|
||||
- **Auto-Join**: If the extension is detected, it automatically triggers the join flow without requiring user input.
|
||||
The site explains KoalaSync, links to browser stores, documents self-hosting, and provides localized user-facing copy in 15 languages:
|
||||
|
||||
`en`, `de`, `fr`, `es`, `it`, `nl`, `pl`, `pt`, `pt-BR`, `tr`, `ru`, `ja`, `ko`, `zh`, and `uk`.
|
||||
|
||||
### 2. Invitation Bridge (`join.html`)
|
||||
|
||||
When a user opens an invitation such as `https://sync.koalastuff.net/join.html#join:roomID:pass`, the page:
|
||||
|
||||
- detects whether the KoalaSync extension is installed via the extension's `bridge.js` content script,
|
||||
- keeps room credentials inside the URL hash so they are not sent to the web server,
|
||||
- asks the extension to join the target room automatically when possible.
|
||||
|
||||
## Architecture
|
||||
|
||||
The website is 100% **Static HTML, CSS, and JS**.
|
||||
- **Static i18n Compiler**: The site uses a lightweight, zero-dependency Node.js compiler (`build.cjs`) to parse dictionary files inside `/locales/` against a single source-of-truth template (`template.html`), outputting the fully deployable static folder to `/www/`.
|
||||
- **Build-time Minification**: `build.cjs` automatically minifies CSS and JS during compilation using a built-in state-machine tokenizer (no npm dependencies). Source files are written unminified (`style.css`, `app.js`, `lang-init.js`) — always edit source files, never the generated `.min.*` files in `www/`.
|
||||
- **Zero Backend**: No Node.js, PHP, or databases are required to host the compiled website.
|
||||
- **Zero Tracking**: All assets (fonts, icons) are self-hosted to prevent third-party tracking.
|
||||
- **Responsive**: Fully optimized for mobile with a native-feel hamburger menu.
|
||||
The website is 100% static HTML, CSS, and JavaScript.
|
||||
|
||||
- **Static i18n compiler**: `build.cjs` combines `template.html` with dictionaries in `locales/`.
|
||||
- **Build-time minification**: Source CSS/JS stays readable; generated output uses `.min.css` and `.min.js`.
|
||||
- **Zero backend**: The compiled site can be hosted by any static file server.
|
||||
- **Zero external assets**: Fonts, icons, scripts, and images must remain self-hosted.
|
||||
- **Generated SEO/runtime files**: `version.json`, sitemap, robots, clean URLs, localized pages, and minified assets are copied or generated into `www/`.
|
||||
|
||||
## Local Development & Compilation
|
||||
|
||||
From the repository root:
|
||||
```bash
|
||||
node website/build.cjs
|
||||
npx serve -l 5000 website/www
|
||||
```
|
||||
|
||||
Then open:
|
||||
|
||||
- `http://localhost:5000/` for the default page.
|
||||
- `http://localhost:5000/de/` for a localized page.
|
||||
- `http://localhost:5000/join.html#join:test-room:test-pass` to test the invitation bridge path.
|
||||
|
||||
Focused verification:
|
||||
```bash
|
||||
node scripts/test-website-locales.mjs
|
||||
node --check website/www/app.min.js
|
||||
node --check website/www/lang-init.min.js
|
||||
```
|
||||
|
||||
Full verification:
|
||||
```bash
|
||||
npm run verify
|
||||
```
|
||||
|
||||
## Hosting with Caddy
|
||||
|
||||
Caddy is the recommended web server. It provides automatic HTTPS and high-performance static file serving.
|
||||
|
||||
### Recommended Caddyfile
|
||||
|
||||
For a more comprehensive configuration that includes the Relay Server reverse proxy, see the root [Caddyfile.example](../examples/Caddyfile.example).
|
||||
Caddy is the recommended production server because it handles HTTPS and static file serving cleanly. For a complete website plus relay reverse-proxy setup, use the root [Caddyfile.example](../examples/Caddyfile.example).
|
||||
|
||||
Minimal static-site block:
|
||||
```caddy
|
||||
sync.koalastuff.net {
|
||||
root * /var/www/koalasync/website/www
|
||||
try_files {path} {path}.html {path}/
|
||||
file_server
|
||||
encode zstd gzip
|
||||
|
||||
# Static Caching for high-performance PageSpeed (1 year with validation)
|
||||
@static {
|
||||
file
|
||||
path *.ico *.css *.js *.png *.svg *.webp
|
||||
path *.ico *.css *.js *.png *.svg *.webp *.avif
|
||||
}
|
||||
header @static Cache-Control "public, max-age=31536000, must-revalidate"
|
||||
|
||||
# Security Headers & Content Security Policy (CSP)
|
||||
header {
|
||||
# Strict Content Security Policy (restricts scripts and connections to self, forbids frames)
|
||||
Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; object-src 'none'; frame-ancestors 'none';"
|
||||
# Prevent FLoC tracking
|
||||
Permissions-Policy interest-cohort=()
|
||||
# Security best practices
|
||||
Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
|
||||
Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()"
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||
X-Content-Type-Options nosniff
|
||||
X-Frame-Options DENY
|
||||
Referrer-Policy no-referrer-when-downgrade
|
||||
X-Frame-Options SAMEORIGIN
|
||||
Referrer-Policy strict-origin-when-cross-origin
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Local Development & Compilation
|
||||
## Do Not Break
|
||||
|
||||
1. Run the compilation script from the repository root to generate the `/website/www` folder:
|
||||
```bash
|
||||
node website/build.cjs
|
||||
```
|
||||
2. Serve the compiled `/www` directory using any local development server:
|
||||
```bash
|
||||
npx serve website/www
|
||||
```
|
||||
3. To test the invitation flow locally, navigate to `http://localhost:5000/join.html#join:test-room:test-pass`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Never edit files inside `website/www/` directly.** This directory is fully auto-generated by `build.cjs`. Always edit source files (`template.html`, `style.css`, `app.js`, `lang-init.js`, locale files in `locales/`) and re-run `node website/build.cjs` to apply changes. CSS and JS are output as `style.min.css`, `app.min.js`, and `lang-init.min.js` — the `.min.*` naming makes it visually obvious these are build artifacts. Editing minified files in `www/` will result in lost changes on the next build.
|
||||
- Do not edit `website/www/` manually; rebuild it from sources.
|
||||
- Do not add external CDNs, fonts, analytics, or third-party scripts.
|
||||
- Keep invite credentials in the URL hash, not query parameters.
|
||||
- Keep locale files synchronized with `website/build.cjs` and `scripts/test-website-locales.mjs`.
|
||||
- Commit source changes and regenerated `www/` output together when website output changes.
|
||||
|
||||
@@ -142,17 +142,13 @@
|
||||
<img src="{{ASSET_PATH}}assets/RemoteProf.webp" alt="Koala teaching and explaining concepts" class="legal-mascot" width="180" height="180">
|
||||
</div>
|
||||
|
||||
<h1>
|
||||
<span lang="en">Guides & Comparisons</span>
|
||||
<span lang="de">Vergleiche & Anleitungen</span>
|
||||
</h1>
|
||||
<h1>{{ALT_INDEX_HEADING}}</h1>
|
||||
<p style="text-align: center; font-size: 0.95rem; line-height: 1.6; max-width: 600px; margin: 0 auto 2rem auto; color: var(--text-muted);">
|
||||
<span lang="en">How does KoalaSync stack up against other watch party tools and streaming solutions? Read our honest, technical comparisons to find the best fit for your remote movie nights.</span>
|
||||
<span lang="de">Wie schlägt sich KoalaSync im Vergleich zu anderen Watch-Party-Tools und Streaming-Lösungen? Lies unsere ehrlichen, technischen Vergleiche, um die passende Lösung für deine Filmabende zu finden.</span>
|
||||
{{ALT_INDEX_INTRO}}
|
||||
</p>
|
||||
|
||||
<!-- Cards Grid Layout -->
|
||||
<div class="guides-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-top: 2rem; margin-bottom: 2rem;">
|
||||
<div class="guides-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1.5rem; margin-top: 2rem; margin-bottom: 2rem;">
|
||||
<!-- Teleparty Card -->
|
||||
<a href="{{ASSET_PATH}}{{LANG_PREFIX}}alternatives/teleparty" class="guide-card" style="text-decoration: none; background: rgba(255, 255, 255, 0.03); border: 1px solid var(--glass-border); border-radius: 12px; padding: 1.5rem; display: flex; flex-direction: column; justify-content: space-between;">
|
||||
<div>
|
||||
@@ -160,8 +156,18 @@
|
||||
<p style="font-size: 0.9rem; line-height: 1.5; color: var(--text-muted); margin-bottom: 1.5rem;">{{ALT_TELEPARTY_META_DESC}}</p>
|
||||
</div>
|
||||
<div class="btn btn-primary" style="align-self: flex-start; padding: 0.5rem 1rem; font-size: 0.85rem; border-radius: 6px;">
|
||||
<span lang="en">Read Comparison →</span>
|
||||
<span lang="de">Vergleich lesen →</span>
|
||||
{{ALT_INDEX_READ_COMPARISON}}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Watch2Gether Card -->
|
||||
<a href="{{ASSET_PATH}}{{LANG_PREFIX}}alternatives/watch2gether" class="guide-card" style="text-decoration: none; background: rgba(255, 255, 255, 0.03); border: 1px solid var(--glass-border); border-radius: 12px; padding: 1.5rem; display: flex; flex-direction: column; justify-content: space-between;">
|
||||
<div>
|
||||
<h3 style="margin-top: 0; margin-bottom: 0.75rem; color: #fff; font-size: 1.25rem;">{{ALT_W2G_TITLE}}</h3>
|
||||
<p style="font-size: 0.9rem; line-height: 1.5; color: var(--text-muted); margin-bottom: 1.5rem;">{{ALT_W2G_META_DESC}}</p>
|
||||
</div>
|
||||
<div class="btn btn-primary" style="align-self: flex-start; padding: 0.5rem 1rem; font-size: 0.85rem; border-radius: 6px;">
|
||||
{{ALT_INDEX_READ_COMPARISON}}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -172,8 +178,7 @@
|
||||
<p style="font-size: 0.9rem; line-height: 1.5; color: var(--text-muted); margin-bottom: 1.5rem;">{{ALT_SCREEN_META_DESC}}</p>
|
||||
</div>
|
||||
<div class="btn btn-primary" style="align-self: flex-start; padding: 0.5rem 1rem; font-size: 0.85rem; border-radius: 6px;">
|
||||
<span lang="en">Read Comparison →</span>
|
||||
<span lang="de">Vergleich lesen →</span>
|
||||
{{ALT_INDEX_READ_GUIDE}}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -193,7 +198,7 @@
|
||||
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
|
||||
<a href="{{ASSET_PATH}}{{FOOTER_LEGAL_LINK}}">{{FOOTER_LEGAL}}</a>
|
||||
<a href="{{ASSET_PATH}}{{FOOTER_PRIVACY_LINK}}">{{FOOTER_PRIVACY}}</a>
|
||||
<a href="{{ASSET_PATH}}{{LANG_PREFIX}}alternatives"><span><span lang="en">Guides & comparisons</span><span lang="de">Vergleiche & Anleitungen</span></span></a>
|
||||
<a href="{{ASSET_PATH}}{{LANG_PREFIX}}alternatives">{{ALT_INDEX_FOOTER_LINK}}</a>
|
||||
<a href="https://mastodon.social/@koalastuff" rel="me noopener" target="_blank" style="display: inline-flex; align-items: center; gap: 4px;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="14" height="14" aria-hidden="true" style="display: block;"><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/></svg>
|
||||
Mastodon
|
||||
|
||||
@@ -188,7 +188,8 @@
|
||||
<!-- CTA block -->
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--glass-border);">
|
||||
<a href="{{ASSET_PATH}}" class="btn btn-primary" style="margin-bottom: 1.5rem;">{{ALT_TRY_KOALASYNC}}</a>
|
||||
<p style="font-size: 0.9rem; text-align: center;">
|
||||
<p style="font-size: 0.9rem; text-align: center; display: flex; flex-direction: column; gap: 0.5rem;">
|
||||
<a href="{{ASSET_PATH}}{{LANG_PREFIX}}alternatives/watch2gether" style="color: var(--accent); text-decoration: none;">{{ALT_W2G_TITLE}}</a>
|
||||
<a href="{{ASSET_PATH}}{{LANG_PREFIX}}alternatives/teleparty" style="color: var(--accent); text-decoration: none;">{{ALT_TELEPARTY_TITLE}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -188,7 +188,8 @@
|
||||
<!-- CTA block -->
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--glass-border);">
|
||||
<a href="{{ASSET_PATH}}" class="btn btn-primary" style="margin-bottom: 1.5rem;">{{ALT_TRY_KOALASYNC}}</a>
|
||||
<p style="font-size: 0.9rem; text-align: center;">
|
||||
<p style="font-size: 0.9rem; text-align: center; display: flex; flex-direction: column; gap: 0.5rem;">
|
||||
<a href="{{ASSET_PATH}}{{LANG_PREFIX}}alternatives/watch2gether" style="color: var(--accent); text-decoration: none;">{{ALT_W2G_TITLE}}</a>
|
||||
<a href="{{ASSET_PATH}}{{LANG_PREFIX}}alternatives/screen-sharing" style="color: var(--accent); text-decoration: none;">{{ALT_SCREEN_TITLE}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{LANG_CODE}}" class="lang-{{LANG_CODE}}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ALT_W2G_TITLE}}</title>
|
||||
<link rel="preload" href="{{ASSET_PATH}}style.min.css" as="style">
|
||||
<link rel="stylesheet" href="{{ASSET_PATH}}style.min.css">
|
||||
<link rel="icon" type="image/webp" href="{{ASSET_PATH}}assets/NewLogoIcon_64.webp">
|
||||
<link rel="canonical" href="https://sync.koalastuff.net/{{LANG_PREFIX}}alternatives/watch2gether">
|
||||
|
||||
<link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="de" href="https://sync.koalastuff.net/de/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="fr" href="https://sync.koalastuff.net/fr/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="es" href="https://sync.koalastuff.net/es/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="pt-br" href="https://sync.koalastuff.net/pt-BR/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="ru" href="https://sync.koalastuff.net/ru/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="it" href="https://sync.koalastuff.net/it/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="pl" href="https://sync.koalastuff.net/pl/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="tr" href="https://sync.koalastuff.net/tr/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/alternatives/watch2gether">
|
||||
<link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/alternatives/watch2gether">
|
||||
|
||||
<meta name="description" content="{{ALT_W2G_META_DESC}}">
|
||||
<meta name="robots" content="index, follow">
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="https://sync.koalastuff.net/{{LANG_PREFIX}}alternatives/watch2gether">
|
||||
<meta property="og:title" content="{{ALT_W2G_TITLE}}">
|
||||
<meta property="og:description" content="{{ALT_W2G_META_DESC}}">
|
||||
<meta property="og:image" content="https://sync.koalastuff.net/assets/ProContraKoala.webp">
|
||||
<meta property="og:locale" content="{{OG_LOCALE}}">
|
||||
|
||||
<!-- Twitter / X -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="{{ALT_W2G_TITLE}}">
|
||||
<meta name="twitter:description" content="{{ALT_W2G_META_DESC}}">
|
||||
<meta name="twitter:image" content="https://sync.koalastuff.net/assets/ProContraKoala.webp">
|
||||
|
||||
<!-- Structured Data (Schema.org) for Breadcrumbs -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "Home",
|
||||
"item": "https://sync.koalastuff.net/{{LANG_PREFIX}}"
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 2,
|
||||
"name": "Alternatives",
|
||||
"item": "https://sync.koalastuff.net/{{LANG_PREFIX}}alternatives"
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 3,
|
||||
"name": "KoalaSync vs Watch2Gether",
|
||||
"item": "https://sync.koalastuff.net/{{LANG_PREFIX}}alternatives/watch2gether"
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Organization Schema -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
"name": "KoalaSync",
|
||||
"url": "https://sync.koalastuff.net/",
|
||||
"logo": "https://sync.koalastuff.net/assets/NewLogoIcon_128.webp",
|
||||
"sameAs": [
|
||||
"https://github.com/Shik3i/KoalaSync",
|
||||
"https://mastodon.social/@koalastuff"
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Mobile Browser Theme Styling -->
|
||||
<meta name="theme-color" content="#0f172a">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
|
||||
<script src="{{ASSET_PATH}}lang-init.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-blobs">
|
||||
<div class="blob blob-1"></div>
|
||||
<div class="blob blob-2"></div>
|
||||
<div class="blob blob-3"></div>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<div class="container nav-content">
|
||||
<a href="{{ASSET_PATH}}" class="logo-area" style="text-decoration: none;">
|
||||
<img src="{{ASSET_PATH}}assets/NewLogoIcon.webp" alt="KoalaSync Logo" width="40" height="40">
|
||||
<span>KoalaSync</span>
|
||||
</a>
|
||||
<div class="nav-links">
|
||||
<a href="{{ASSET_PATH}}" style="display: inline-flex; align-items: center; gap: 6px;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="width: 16px; height: 16px; display: block;"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
|
||||
<span>Home</span>
|
||||
</a>
|
||||
<a href="https://github.com/Shik3i/KoalaSync" target="_blank" rel="noopener" style="display: inline-flex; align-items: center; gap: 6px;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" aria-hidden="true" style="display: block;"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
||||
GitHub
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-right">
|
||||
<div class="lang-select-container">
|
||||
<select class="lang-dropdown" aria-label="Select Language">
|
||||
<option value="en">🇬🇧 English</option>
|
||||
<option value="de">🇩🇪 Deutsch</option>
|
||||
<option value="fr">🇫🇷 Français</option>
|
||||
<option value="es">🇪🇸 Español</option>
|
||||
<option value="pt-BR">🇧🇷 Português (Brasil)</option>
|
||||
<option value="ru">🇷🇺 Русский</option>
|
||||
<option value="it">🇮🇹 Italiano</option>
|
||||
<option value="pl">🇵🇱 Polski</option>
|
||||
<option value="tr">🇹🇷 Türkçe</option>
|
||||
<option value="nl">🇳🇱 Nederlands</option>
|
||||
<option value="ja">🇯🇵 日本語</option>
|
||||
<option value="ko">🇰🇷 한국어</option>
|
||||
<option value="zh">🇨🇳 中文</option>
|
||||
<option value="uk">🇺🇦 Українська</option>
|
||||
<option value="pt">🇵🇹 Português (Portugal)</option>
|
||||
</select>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="chevron-icon"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</div>
|
||||
<button class="hamburger" aria-label="Menu" aria-expanded="false">☰</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="legal-content">
|
||||
<div class="legal-card" data-reveal style="padding: 2rem;">
|
||||
<div style="display: flex; justify-content: center;">
|
||||
<img src="{{ASSET_PATH}}assets/ProContraKoala.webp" alt="Koala weighing pros and cons" class="legal-mascot" width="180" height="180">
|
||||
</div>
|
||||
|
||||
<h1>{{ALT_W2G_TITLE}}</h1>
|
||||
|
||||
<!-- Intro -->
|
||||
<section>
|
||||
<p><em>{{ALT_W2G_INTRO}}</em></p>
|
||||
</section>
|
||||
|
||||
<!-- When Watch2Gether may still make sense -->
|
||||
<section>
|
||||
<h2>{{ALT_W2G_WHEN_W2G_TITLE}}</h2>
|
||||
<p>{{ALT_W2G_WHEN_W2G_BODY}}</p>
|
||||
</section>
|
||||
|
||||
<!-- When KoalaSync may fit better -->
|
||||
<section>
|
||||
<h2>{{ALT_W2G_WHEN_KOALA_TITLE}}</h2>
|
||||
<p>{{ALT_W2G_WHEN_KOALA_BODY}}</p>
|
||||
</section>
|
||||
|
||||
<!-- The main difference -->
|
||||
<section>
|
||||
<h2>{{ALT_W2G_MAIN_DIFF_TITLE}}</h2>
|
||||
<p>{{ALT_W2G_MAIN_DIFF_BODY}}</p>
|
||||
</section>
|
||||
|
||||
<!-- Honest limitations -->
|
||||
<section>
|
||||
<h2>{{ALT_W2G_LIMITS_TITLE}}</h2>
|
||||
<p>{{ALT_W2G_LIMITS_BODY}}</p>
|
||||
</section>
|
||||
|
||||
<!-- Summary -->
|
||||
<section>
|
||||
<h2>{{ALT_W2G_SUMMARY_TITLE}}</h2>
|
||||
<p>{{ALT_W2G_SUMMARY_BODY}}</p>
|
||||
</section>
|
||||
|
||||
<!-- CTA block -->
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--glass-border);">
|
||||
<a href="{{ASSET_PATH}}" class="btn btn-primary" style="margin-bottom: 1.5rem;">{{ALT_TRY_KOALASYNC}}</a>
|
||||
<p style="font-size: 0.9rem; text-align: center; display: flex; flex-direction: column; gap: 0.5rem;">
|
||||
<a href="{{ASSET_PATH}}{{LANG_PREFIX}}alternatives/teleparty" style="color: var(--accent); text-decoration: none;">{{ALT_TELEPARTY_TITLE}}</a>
|
||||
<a href="{{ASSET_PATH}}{{LANG_PREFIX}}alternatives/screen-sharing" style="color: var(--accent); text-decoration: none;">{{ALT_SCREEN_TITLE}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2026 KoalaSync. {{FOOTER_MIT}}</p>
|
||||
<p style="font-size: 0.8rem; margin-top: 0.5rem;">{{FOOTER_RAM}}</p>
|
||||
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
|
||||
<a href="{{ASSET_PATH}}{{FOOTER_LEGAL_LINK}}">{{FOOTER_LEGAL}}</a>
|
||||
<a href="{{ASSET_PATH}}{{FOOTER_PRIVACY_LINK}}">{{FOOTER_PRIVACY}}</a>
|
||||
<a href="{{ASSET_PATH}}{{LANG_PREFIX}}alternatives"><span><span lang="en">Guides & comparisons</span><span lang="de">Vergleiche & Anleitungen</span></span></a>
|
||||
<a href="https://mastodon.social/@koalastuff" rel="me noopener" target="_blank" style="display: inline-flex; align-items: center; gap: 4px;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="14" height="14" aria-hidden="true" style="display: block;"><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/></svg>
|
||||
Mastodon
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="{{ASSET_PATH}}app.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+852
-68
@@ -1,6 +1,49 @@
|
||||
// KoalaSync Landing Page Logic
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Mockup Video Title Randomization on Load
|
||||
const SERIES_NAMES = [
|
||||
'Stranger Things',
|
||||
'Wednesday',
|
||||
'The Boys',
|
||||
'Loki',
|
||||
'Arcane',
|
||||
'Dark',
|
||||
'One Piece',
|
||||
'Lupin'
|
||||
];
|
||||
|
||||
try {
|
||||
const chosenSeries = SERIES_NAMES[Math.floor(Math.random() * SERIES_NAMES.length)];
|
||||
const startEp = Math.floor(Math.random() * 7) + 1; // Random episode between 1 and 7
|
||||
const ep1 = `${chosenSeries} - S1E${startEp}`;
|
||||
const ep2 = `${chosenSeries} - S1E${startEp + 1}`;
|
||||
|
||||
document.querySelectorAll('.demo-title-text-ep1').forEach(el => {
|
||||
el.textContent = ep1;
|
||||
});
|
||||
document.querySelectorAll('.demo-title-text-ep2').forEach(el => {
|
||||
el.textContent = ep2;
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
|
||||
// Scroll Progress Indicator
|
||||
try {
|
||||
const progressBar = document.querySelector('.scroll-progress-bar');
|
||||
if (progressBar) {
|
||||
window.addEventListener('scroll', () => {
|
||||
const winScroll = document.body.scrollTop || document.documentElement.scrollTop;
|
||||
const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
||||
const scrolled = height > 0 ? (winScroll / height) * 100 : 0;
|
||||
progressBar.style.width = scrolled + '%';
|
||||
}, { passive: true });
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
|
||||
const safeGetLocalStorage = (key) => {
|
||||
try {
|
||||
return localStorage.getItem(key);
|
||||
@@ -17,6 +60,95 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Theme toggle (dark is default; light mode via html.theme-light,
|
||||
// pre-paint class is applied by lang-init.js to avoid a flash)
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const themeMeta = document.querySelector('meta[name="theme-color"]');
|
||||
const themeStorageKey = 'koala_theme';
|
||||
const getCurrentTheme = () => document.documentElement.classList.contains('theme-light') ? 'light' : 'dark';
|
||||
const savedTheme = safeGetLocalStorage(themeStorageKey);
|
||||
|
||||
if (savedTheme === 'light') {
|
||||
document.documentElement.classList.add('theme-light');
|
||||
} else if (savedTheme === 'dark') {
|
||||
document.documentElement.classList.remove('theme-light');
|
||||
}
|
||||
|
||||
const syncThemeMeta = () => {
|
||||
if (themeMeta) {
|
||||
themeMeta.setAttribute('content', document.documentElement.classList.contains('theme-light') ? '#f1f5f9' : '#0f172a');
|
||||
}
|
||||
};
|
||||
syncThemeMeta();
|
||||
safeSetLocalStorage(themeStorageKey, getCurrentTheme());
|
||||
if (themeToggle) {
|
||||
themeToggle.addEventListener('click', () => {
|
||||
const isLight = document.documentElement.classList.toggle('theme-light');
|
||||
safeSetLocalStorage(themeStorageKey, isLight ? 'light' : 'dark');
|
||||
syncThemeMeta();
|
||||
});
|
||||
}
|
||||
|
||||
// Highlight the CTA for the visitor's browser: it expands to the full
|
||||
// "Add to X" label while the others stay compact. Unknown browsers keep
|
||||
// all three compact.
|
||||
try {
|
||||
const ua = navigator.userAgent;
|
||||
let detected = null;
|
||||
if (/Firefox\//.test(ua) && !/Seamonkey\//.test(ua)) {
|
||||
detected = 'firefox';
|
||||
} else if (/Chrome\//.test(ua) || /Chromium\//.test(ua) || /Edg\//.test(ua) || /OPR\//.test(ua)) {
|
||||
detected = 'chrome';
|
||||
}
|
||||
if (detected) {
|
||||
const btn = document.querySelector('.btn-install[data-browser="' + detected + '"]');
|
||||
if (btn) btn.classList.add('is-detected');
|
||||
}
|
||||
} catch (_) { /* leave all buttons compact */ }
|
||||
|
||||
// Keep the look-down koala anchored above the highlighted install button
|
||||
// when the CTA row wraps on tablet/mobile.
|
||||
const syncHeroMascotAnchor = () => {
|
||||
const heroText = document.querySelector('.hero-text');
|
||||
const mascot = heroText ? heroText.querySelector('.hero-mascot-container') : null;
|
||||
const ctaGroup = heroText ? heroText.querySelector('.cta-group') : null;
|
||||
if (!heroText || !mascot || !ctaGroup) return;
|
||||
|
||||
const target = ctaGroup.querySelector('.btn-install.is-detected') ||
|
||||
ctaGroup.querySelector('.btn-install[data-browser="chrome"]') ||
|
||||
ctaGroup.querySelector('.btn-install');
|
||||
if (!target) return;
|
||||
|
||||
const mascotRect = mascot.getBoundingClientRect();
|
||||
const targetRect = target.getBoundingClientRect();
|
||||
if (!mascotRect.width || !targetRect.width) return;
|
||||
|
||||
const mascotCenter = mascotRect.left + mascotRect.width / 2;
|
||||
const targetCenter = targetRect.left + targetRect.width / 2;
|
||||
mascot.style.setProperty('--hero-mascot-offset', Math.round(targetCenter - mascotCenter) + 'px');
|
||||
};
|
||||
|
||||
const scheduleHeroMascotAnchor = () => requestAnimationFrame(syncHeroMascotAnchor);
|
||||
scheduleHeroMascotAnchor();
|
||||
window.addEventListener('load', scheduleHeroMascotAnchor, { once: true });
|
||||
window.addEventListener('resize', scheduleHeroMascotAnchor, { passive: true });
|
||||
if ('ResizeObserver' in window) {
|
||||
const heroText = document.querySelector('.hero-text');
|
||||
const ctaGroup = heroText ? heroText.querySelector('.cta-group') : null;
|
||||
const mascot = heroText ? heroText.querySelector('.hero-mascot-container') : null;
|
||||
const mascotObserver = new window.ResizeObserver(scheduleHeroMascotAnchor);
|
||||
if (ctaGroup) mascotObserver.observe(ctaGroup);
|
||||
if (mascot) mascotObserver.observe(mascot);
|
||||
}
|
||||
|
||||
// Open collapsed section blocks when the URL directly targets them
|
||||
const hashCollapseMap = { '#faq': 'faq-collapse', '#self-hosting': 'selfhost-collapse' };
|
||||
const collapseId = hashCollapseMap[window.location.hash];
|
||||
if (collapseId) {
|
||||
const collapseEl = document.getElementById(collapseId);
|
||||
if (collapseEl) collapseEl.open = true;
|
||||
}
|
||||
|
||||
// Scroll Reveal Logic (IntersectionObserver for performance)
|
||||
const revealElements = document.querySelectorAll('[data-reveal]');
|
||||
|
||||
@@ -29,8 +161,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
}, {
|
||||
rootMargin: '0px 0px -150px 0px',
|
||||
threshold: 0.1
|
||||
rootMargin: '0px 0px -30px 0px',
|
||||
threshold: 0.05
|
||||
});
|
||||
|
||||
revealElements.forEach(el => revealObserver.observe(el));
|
||||
@@ -53,17 +185,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('section[id], header[id]').forEach(el => sectionObserver.observe(el));
|
||||
}
|
||||
|
||||
// Navbar scroll effect
|
||||
// Navbar scroll effect (class-based so it follows the active theme)
|
||||
const nav = document.querySelector('nav');
|
||||
window.addEventListener('scroll', () => {
|
||||
if (window.scrollY > 50) {
|
||||
nav.style.padding = '0.75rem 0';
|
||||
nav.style.background = 'rgba(15, 23, 42, 0.9)';
|
||||
} else {
|
||||
nav.style.padding = '1rem 0';
|
||||
nav.style.background = 'rgba(30, 41, 59, 0.7)';
|
||||
}
|
||||
});
|
||||
nav.classList.toggle('nav-scrolled', window.scrollY > 50);
|
||||
}, { passive: true });
|
||||
|
||||
// Invite Detection & Bridge
|
||||
const checkInvite = () => {
|
||||
@@ -329,63 +455,42 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const updateDynamicVersion = async () => {
|
||||
try {
|
||||
const versionPath = '/version.json';
|
||||
const response = await fetch(versionPath);
|
||||
if (!response.ok) return;
|
||||
const data = await response.json();
|
||||
const { version, date } = data;
|
||||
if (!version || !date) return;
|
||||
// Version and release date are baked in at build time; only the relative
|
||||
// "x days ago" needs to be computed at view time so it never goes stale.
|
||||
const updateDynamicVersion = () => {
|
||||
const bubble = document.querySelector('.koala-speech-bubble');
|
||||
if (!bubble || !bubble.dataset.release) return;
|
||||
const releaseDate = new Date(bubble.dataset.release);
|
||||
if (isNaN(releaseDate)) return;
|
||||
|
||||
const releaseDate = new Date(date);
|
||||
const now = new Date();
|
||||
const diffMs = now - releaseDate;
|
||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||
const diffMins = Math.floor(diffMs / (1000 * 60));
|
||||
const diffMs = Date.now() - releaseDate.getTime();
|
||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||
const diffMins = Math.floor(diffMs / (1000 * 60));
|
||||
|
||||
let relativeTimeEn = '';
|
||||
let relativeTimeDe = '';
|
||||
|
||||
if (diffDays > 0) {
|
||||
relativeTimeEn = `${diffDays} ${diffDays === 1 ? 'day' : 'days'} ago`;
|
||||
relativeTimeDe = `vor ${diffDays} ${diffDays === 1 ? 'Tag' : 'Tagen'}`;
|
||||
} else if (diffHours > 0) {
|
||||
relativeTimeEn = `${diffHours} ${diffHours === 1 ? 'hour' : 'hours'} ago`;
|
||||
relativeTimeDe = `vor ${diffHours} ${diffHours === 1 ? 'Stunde' : 'Stunden'}`;
|
||||
} else if (diffMins > 0) {
|
||||
relativeTimeEn = `${diffMins} ${diffMins === 1 ? 'minute' : 'minutes'} ago`;
|
||||
relativeTimeDe = `vor ${diffMins} ${diffMins === 1 ? 'Minute' : 'Minuten'}`;
|
||||
} else {
|
||||
relativeTimeEn = 'just now';
|
||||
relativeTimeDe = 'gerade eben';
|
||||
}
|
||||
|
||||
const badgeEn = document.querySelector('.version-text-en');
|
||||
const badgeDe = document.querySelector('.version-text-de');
|
||||
|
||||
if (badgeEn) {
|
||||
badgeEn.textContent = `v${version} OUT NOW • ${relativeTimeEn}`;
|
||||
}
|
||||
if (badgeDe) {
|
||||
badgeDe.textContent = `v${version} JETZT VERFÜGBAR • ${relativeTimeDe}`;
|
||||
}
|
||||
|
||||
// Update Schema.org structured data dynamically
|
||||
const schemaScript = document.getElementById('schema-software');
|
||||
if (schemaScript) {
|
||||
try {
|
||||
const schema = JSON.parse(schemaScript.textContent);
|
||||
schema.softwareVersion = version;
|
||||
schemaScript.textContent = JSON.stringify(schema, null, 2);
|
||||
} catch (err) {
|
||||
console.warn('Failed to dynamically update schema version:', err);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to fetch dynamic version info:', e);
|
||||
let relativeTimeEn;
|
||||
let relativeTimeDe;
|
||||
if (diffDays > 0) {
|
||||
relativeTimeEn = `${diffDays} ${diffDays === 1 ? 'day' : 'days'} ago`;
|
||||
relativeTimeDe = `vor ${diffDays} ${diffDays === 1 ? 'Tag' : 'Tagen'}`;
|
||||
} else if (diffHours > 0) {
|
||||
relativeTimeEn = `${diffHours} ${diffHours === 1 ? 'hour' : 'hours'} ago`;
|
||||
relativeTimeDe = `vor ${diffHours} ${diffHours === 1 ? 'Stunde' : 'Stunden'}`;
|
||||
} else if (diffMins > 0) {
|
||||
relativeTimeEn = `${diffMins} ${diffMins === 1 ? 'minute' : 'minutes'} ago`;
|
||||
relativeTimeDe = `vor ${diffMins} ${diffMins === 1 ? 'Minute' : 'Minuten'}`;
|
||||
} else {
|
||||
relativeTimeEn = 'just now';
|
||||
relativeTimeDe = 'gerade eben';
|
||||
}
|
||||
|
||||
bubble.querySelectorAll('.bubble-ago').forEach(el => {
|
||||
// Nearest [lang] ancestor is the bubble's own language wrapper span
|
||||
// (not the <html> element, which also carries a lang attribute).
|
||||
const langWrap = el.closest('[lang]');
|
||||
const isDe = !!langWrap && langWrap.getAttribute('lang') === 'de';
|
||||
el.textContent = (isDe ? relativeTimeDe : relativeTimeEn) + ' \u00b7 ';
|
||||
});
|
||||
};
|
||||
|
||||
// Extension Mockup Tab Switcher
|
||||
@@ -406,6 +511,625 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// The hero demo is a decorative, self-playing illustration; the real,
|
||||
// accessible walkthrough is the #how-it-works section. Hide the fake
|
||||
// extension UI from the accessibility tree and drop its controls from the
|
||||
// tab order so screen readers are not confused and it does not trip
|
||||
// contrast/label audits. Mouse interaction is unaffected.
|
||||
const heroDemoScene = document.getElementById('hero-demo');
|
||||
if (heroDemoScene) {
|
||||
heroDemoScene.setAttribute('aria-hidden', 'true');
|
||||
heroDemoScene.querySelectorAll('a[href], button, input, select, textarea, [tabindex]')
|
||||
.forEach(el => el.setAttribute('tabindex', '-1'));
|
||||
}
|
||||
|
||||
// --- Hero Live Demo (two synced video tabs + extension popup) ---
|
||||
// The same scene is used inline on desktop and mounted into a modal on
|
||||
// mobile, so the demo behavior stays in one place.
|
||||
const initHeroDemo = () => {
|
||||
const scene = document.getElementById('hero-demo');
|
||||
if (!scene) return;
|
||||
|
||||
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
const launcher = scene.querySelector('#demo-launcher');
|
||||
const chip = scene.querySelector('#demo-sync-chip');
|
||||
const cursor = scene.querySelector('#demo-cursor');
|
||||
const hint = document.getElementById('demo-hint');
|
||||
const playBtn = scene.querySelector('#demo-play-btn');
|
||||
const pauseBtn = scene.querySelector('#demo-pause-btn');
|
||||
const forceSyncBtn = scene.querySelector('#demo-force-sync');
|
||||
const syncTabBtn = scene.querySelector('.mock-tab[data-target="mock-sync"]');
|
||||
const roomEmpty = scene.querySelector('#demo-room-empty');
|
||||
const roomJoined = scene.querySelector('#demo-room-joined');
|
||||
const createRoomBtn = scene.querySelector('#demo-create-room');
|
||||
const inviteCopyBtn = scene.querySelector('#demo-invite-copy');
|
||||
const videoSelect = scene.querySelector('#demo-video-select');
|
||||
const peerBs = scene.querySelectorAll('.demo-peer-b');
|
||||
const inviteFly = scene.querySelector('#demo-invite-fly');
|
||||
const toastB = scene.querySelector('#demo-toast-b');
|
||||
if (!launcher || !chip || !cursor || !playBtn || !pauseBtn) return;
|
||||
|
||||
const EP_LEN = 2537; // fake 42:17 episode
|
||||
const RATE = 1; // realtime: 1 wall-clock second = 1 video second
|
||||
const START_T = 754; // 12:34
|
||||
|
||||
const tabs = {};
|
||||
['a', 'b'].forEach(k => {
|
||||
const root = scene.querySelector('#demo-tab-' + k);
|
||||
tabs[k] = {
|
||||
root,
|
||||
fill: root.querySelector('.demo-progress-fill'),
|
||||
time: root.querySelector('.demo-time'),
|
||||
t: START_T,
|
||||
playing: false
|
||||
};
|
||||
});
|
||||
|
||||
const fmt = (s) => {
|
||||
s = Math.floor(s);
|
||||
return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0');
|
||||
};
|
||||
|
||||
const renderTab = (tab) => {
|
||||
tab.fill.style.width = ((tab.t / EP_LEN) * 100).toFixed(2) + '%';
|
||||
tab.time.textContent = fmt(tab.t);
|
||||
|
||||
// Frame-locked parallax offsets
|
||||
const t = tab.t;
|
||||
const backOffset = (t * -8) % 160;
|
||||
const midOffset = (t * -24) % 160;
|
||||
const foreOffset = (t * -48) % 160;
|
||||
|
||||
// Bouncing ball logic (bounce Y is computed as parabolic arc)
|
||||
const bouncePeriod = 0.8;
|
||||
const bounceProgress = (t % bouncePeriod) / bouncePeriod;
|
||||
const bounceHeight = 16;
|
||||
const bounceY = 4 * bounceProgress * (1 - bounceProgress) * -bounceHeight;
|
||||
|
||||
tab.root.style.setProperty('--scroll-back', backOffset.toFixed(2) + 'px');
|
||||
tab.root.style.setProperty('--scroll-mid', midOffset.toFixed(2) + 'px');
|
||||
tab.root.style.setProperty('--scroll-fore', foreOffset.toFixed(2) + 'px');
|
||||
tab.root.style.setProperty('--bounce-y', bounceY.toFixed(2) + 'px');
|
||||
};
|
||||
|
||||
const updateSyncUI = () => {
|
||||
scene.classList.toggle('streaming', tabs.a.playing && tabs.b.playing);
|
||||
};
|
||||
|
||||
// Playback clock (runs only while at least one tab is playing)
|
||||
let rafId = null;
|
||||
let lastTs = null;
|
||||
|
||||
const tick = (ts) => {
|
||||
rafId = null;
|
||||
const dt = lastTs === null ? 0 : Math.min((ts - lastTs) / 1000, 0.5);
|
||||
lastTs = ts;
|
||||
let anyPlaying = false;
|
||||
['a', 'b'].forEach(k => {
|
||||
const tab = tabs[k];
|
||||
if (!tab.playing) return;
|
||||
anyPlaying = true;
|
||||
tab.t = (tab.t + dt * RATE) % EP_LEN;
|
||||
renderTab(tab);
|
||||
});
|
||||
updateSyncUI();
|
||||
if (anyPlaying) rafId = requestAnimationFrame(tick);
|
||||
else lastTs = null;
|
||||
};
|
||||
|
||||
const ensureLoop = () => {
|
||||
if (rafId === null && (tabs.a.playing || tabs.b.playing)) {
|
||||
lastTs = null;
|
||||
rafId = requestAnimationFrame(tick);
|
||||
}
|
||||
};
|
||||
|
||||
const setPlaying = (k, playing) => {
|
||||
const tab = tabs[k];
|
||||
tab.playing = playing;
|
||||
tab.root.classList.toggle('playing', playing);
|
||||
tab.root.setAttribute('aria-pressed', playing ? 'true' : 'false');
|
||||
ensureLoop();
|
||||
updateSyncUI();
|
||||
};
|
||||
|
||||
const sceneLocalPoint = (rect, fx, fy) => {
|
||||
const sceneRect = scene.getBoundingClientRect();
|
||||
const scaleX = scene.offsetWidth ? sceneRect.width / scene.offsetWidth : 1;
|
||||
const scaleY = scene.offsetHeight ? sceneRect.height / scene.offsetHeight : scaleX;
|
||||
const safeScaleX = scaleX || 1;
|
||||
const safeScaleY = scaleY || safeScaleX;
|
||||
const xFactor = typeof fx === 'number' ? fx : 0.5;
|
||||
const yFactor = typeof fy === 'number' ? fy : 0.5;
|
||||
|
||||
return {
|
||||
x: (rect.left - sceneRect.left) / safeScaleX + (rect.width / safeScaleX) * xFactor,
|
||||
y: (rect.top - sceneRect.top) / safeScaleY + (rect.height / safeScaleY) * yFactor
|
||||
};
|
||||
};
|
||||
|
||||
const pulse = (key) => {
|
||||
const el = tabs[key].root;
|
||||
el.classList.remove('sync-pulse');
|
||||
void el.offsetWidth; // restart the CSS animation
|
||||
el.classList.add('sync-pulse');
|
||||
};
|
||||
|
||||
const NAMES = { a: '🐱 ChillCat', b: '🐶 HappyDog' };
|
||||
const chipEvent = chip.querySelector('.demo-chip-event');
|
||||
|
||||
let eventTimer = null;
|
||||
const showEvent = (text) => {
|
||||
if (!chipEvent) return;
|
||||
chipEvent.textContent = text;
|
||||
chip.classList.add('event');
|
||||
clearTimeout(eventTimer);
|
||||
eventTimer = setTimeout(() => chip.classList.remove('event'), 1600);
|
||||
};
|
||||
|
||||
// Core of the demo: play/pause on EITHER side is broadcast to the peer
|
||||
// tab — exactly what the extension does. The popup is just a remote
|
||||
// control on top, never a requirement.
|
||||
let broadcasting = false;
|
||||
const broadcast = (sourceKey, playing) => {
|
||||
if (broadcasting) return;
|
||||
broadcasting = true;
|
||||
const peerKey = sourceKey === 'a' ? 'b' : 'a';
|
||||
setPlaying(sourceKey, playing);
|
||||
showEvent((playing ? '▶ ' : '❚❚ ') + NAMES[sourceKey]);
|
||||
// the peer follows near-instantly — that is the whole point
|
||||
setTimeout(() => {
|
||||
tabs[peerKey].t = tabs[sourceKey].t;
|
||||
setPlaying(peerKey, playing);
|
||||
renderTab(tabs[peerKey]);
|
||||
pulse(peerKey);
|
||||
broadcasting = false;
|
||||
}, 90);
|
||||
};
|
||||
|
||||
// Seeking works the same way: scrub one tab, the peer jumps along
|
||||
const seekTo = (sourceKey, fraction) => {
|
||||
if (broadcasting) return;
|
||||
broadcasting = true;
|
||||
const peerKey = sourceKey === 'a' ? 'b' : 'a';
|
||||
tabs[sourceKey].t = fraction * EP_LEN;
|
||||
renderTab(tabs[sourceKey]);
|
||||
showEvent('» ' + NAMES[sourceKey]);
|
||||
// visible "cut" so the jump reads as a real seek, not a silent update
|
||||
flashSeek(tabs[sourceKey].root);
|
||||
setTimeout(() => {
|
||||
tabs[peerKey].t = tabs[sourceKey].t;
|
||||
renderTab(tabs[peerKey]);
|
||||
flashSeek(tabs[peerKey].root);
|
||||
pulse(peerKey);
|
||||
broadcasting = false;
|
||||
}, 90);
|
||||
};
|
||||
|
||||
// Restart the film animations mid-stride + trigger the sweep overlay
|
||||
let seekFlashTimers = [];
|
||||
const flashSeek = (root) => {
|
||||
if (!root) return;
|
||||
root.classList.remove('demo-seeking');
|
||||
// Restart every animated layer from frame zero so the jump is visible
|
||||
const film = root.querySelector('.demo-film');
|
||||
if (film) {
|
||||
film.classList.add('demo-reset');
|
||||
void film.offsetWidth; // force reflow so the browser commits the reset
|
||||
film.classList.remove('demo-reset');
|
||||
}
|
||||
void root.offsetWidth;
|
||||
root.classList.add('demo-seeking');
|
||||
const t = setTimeout(() => root.classList.remove('demo-seeking'), 360);
|
||||
seekFlashTimers.push(t);
|
||||
};
|
||||
|
||||
const setPopupOpen = (open) => {
|
||||
scene.classList.toggle('popup-open', open);
|
||||
launcher.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
};
|
||||
|
||||
// Manual controls (the automated walkthrough drives these same handlers)
|
||||
launcher.addEventListener('click', () => setPopupOpen(!scene.classList.contains('popup-open')));
|
||||
|
||||
// The popup remote control acts as 🐱 ChillCat (tab A's user)
|
||||
playBtn.addEventListener('click', () => {
|
||||
if (tabs.a.playing && tabs.b.playing) { pulse('b'); return; }
|
||||
broadcast('a', true);
|
||||
});
|
||||
pauseBtn.addEventListener('click', () => {
|
||||
if (!tabs.a.playing && !tabs.b.playing) { pulse('b'); return; }
|
||||
broadcast('a', false);
|
||||
});
|
||||
if (forceSyncBtn) forceSyncBtn.addEventListener('click', () => {
|
||||
tabs.b.t = tabs.a.t = Math.max(tabs.a.t, tabs.b.t);
|
||||
renderTab(tabs.a);
|
||||
renderTab(tabs.b);
|
||||
pulse('a');
|
||||
pulse('b');
|
||||
});
|
||||
|
||||
['a', 'b'].forEach(k => {
|
||||
const root = tabs[k].root;
|
||||
const toggle = () => broadcast(k, !tabs[k].playing);
|
||||
// the toolbar extension icon lives inside card A — its clicks
|
||||
// toggle the popup, not playback
|
||||
root.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.demo-ext-launcher')) return;
|
||||
toggle();
|
||||
});
|
||||
root.addEventListener('keydown', (e) => {
|
||||
if (e.target.closest('.demo-ext-launcher')) return;
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
toggle();
|
||||
}
|
||||
});
|
||||
|
||||
const progress = root.querySelector('.demo-progress');
|
||||
if (progress) {
|
||||
progress.addEventListener('click', (e) => {
|
||||
e.stopPropagation(); // a scrub must not toggle play/pause
|
||||
const r = progress.getBoundingClientRect();
|
||||
seekTo(k, Math.min(Math.max((e.clientX - r.left) / r.width, 0), 1));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
chip.addEventListener('click', () => {
|
||||
pulse('a');
|
||||
pulse('b');
|
||||
});
|
||||
|
||||
// --- Story state (room creation -> invite -> connected) ---
|
||||
const setRoomJoined = (joined) => {
|
||||
if (roomEmpty) roomEmpty.style.display = joined ? 'none' : 'flex';
|
||||
if (roomJoined) roomJoined.style.display = joined ? '' : 'none';
|
||||
};
|
||||
|
||||
const setConnected = (connected) => {
|
||||
scene.classList.toggle('connected', connected);
|
||||
peerBs.forEach(el => { el.style.display = connected ? '' : 'none'; });
|
||||
};
|
||||
|
||||
let toastTimer = null;
|
||||
const showToastB = (text) => {
|
||||
if (!toastB || !text) return;
|
||||
toastB.textContent = '✓ ' + text;
|
||||
toastB.classList.add('show');
|
||||
clearTimeout(toastTimer);
|
||||
toastTimer = setTimeout(() => toastB.classList.remove('show'), 1700);
|
||||
};
|
||||
|
||||
// The invite link visibly travels from the popup to the friend's window
|
||||
const flyInvite = () => new Promise((resolve) => {
|
||||
if (!inviteFly || !inviteCopyBtn) { resolve(); return; }
|
||||
const from = inviteCopyBtn.getBoundingClientRect();
|
||||
const to = tabs.b.root.querySelector('.demo-tab-titlebar').getBoundingClientRect();
|
||||
const fromPoint = sceneLocalPoint(from, 0, 0);
|
||||
const toPoint = sceneLocalPoint(to, 0.5, 0);
|
||||
inviteFly.style.transition = 'none';
|
||||
inviteFly.style.left = fromPoint.x + 'px';
|
||||
inviteFly.style.top = fromPoint.y + 'px';
|
||||
inviteFly.style.opacity = '1';
|
||||
void inviteFly.offsetWidth;
|
||||
inviteFly.style.transition = '';
|
||||
inviteFly.style.left = (toPoint.x - 40) + 'px';
|
||||
inviteFly.style.top = (toPoint.y + 2) + 'px';
|
||||
setTimeout(() => {
|
||||
inviteFly.style.opacity = '0';
|
||||
resolve();
|
||||
}, 800);
|
||||
});
|
||||
|
||||
const flashSelect = () => {
|
||||
if (!videoSelect) return;
|
||||
// Actually pick the Stranger Things tab so the placeholder reads as chosen
|
||||
if (videoSelect.options.length > 1) {
|
||||
videoSelect.selectedIndex = 1;
|
||||
}
|
||||
videoSelect.classList.remove('demo-attn');
|
||||
void videoSelect.offsetWidth;
|
||||
videoSelect.classList.add('demo-attn');
|
||||
};
|
||||
|
||||
if (createRoomBtn) createRoomBtn.addEventListener('click', () => setRoomJoined(true));
|
||||
|
||||
renderTab(tabs.a);
|
||||
renderTab(tabs.b);
|
||||
updateSyncUI();
|
||||
|
||||
// JS is active: start with the popup tucked away and, if the scripted
|
||||
// story is going to run, rewind to the "before" state (no transitions
|
||||
// on load).
|
||||
scene.classList.add('demo-no-anim');
|
||||
if (!reduceMotion) {
|
||||
setPopupOpen(false);
|
||||
setConnected(false);
|
||||
setRoomJoined(false);
|
||||
}
|
||||
void scene.offsetWidth;
|
||||
scene.classList.remove('demo-no-anim');
|
||||
|
||||
const showHint = () => {
|
||||
if (hint) hint.classList.add('show');
|
||||
};
|
||||
|
||||
// Automated walkthrough; any interaction aborts it and jumps
|
||||
// straight to the finished end state, then the user's click applies.
|
||||
let userTookOver = false;
|
||||
let demoStarted = false;
|
||||
let demoFinished = false;
|
||||
let demoRunId = 0;
|
||||
|
||||
const resetDemo = () => {
|
||||
demoStarted = false;
|
||||
demoFinished = false;
|
||||
userTookOver = false;
|
||||
demoRunId++;
|
||||
|
||||
// Reset DOM states:
|
||||
scene.classList.add('demo-no-anim');
|
||||
setPopupOpen(false);
|
||||
setConnected(false);
|
||||
setRoomJoined(false);
|
||||
cursor.classList.remove('visible', 'clicking');
|
||||
scene.classList.remove('demo-complete', 'streaming');
|
||||
if (hint) hint.classList.remove('show');
|
||||
|
||||
// Rewind tab playback times to START_T, paused:
|
||||
['a', 'b'].forEach(k => {
|
||||
const tab = tabs[k];
|
||||
tab.t = START_T;
|
||||
setPlaying(k, false);
|
||||
renderTab(tab);
|
||||
});
|
||||
|
||||
// Clear any sync-pulse or seeking classes:
|
||||
['a', 'b'].forEach(k => {
|
||||
tabs[k].root.classList.remove('sync-pulse', 'demo-seeking');
|
||||
});
|
||||
|
||||
// Clean up any pending flash timers:
|
||||
seekFlashTimers.forEach(clearTimeout);
|
||||
seekFlashTimers = [];
|
||||
|
||||
// Force reflow and remove demo-no-anim:
|
||||
void scene.offsetWidth;
|
||||
scene.classList.remove('demo-no-anim');
|
||||
};
|
||||
const finishDemo = () => {
|
||||
if (demoFinished) return;
|
||||
demoFinished = true;
|
||||
setRoomJoined(true);
|
||||
setConnected(true);
|
||||
cursor.classList.remove('visible');
|
||||
scene.classList.add('demo-complete');
|
||||
showHint();
|
||||
};
|
||||
|
||||
const takeOver = (e) => {
|
||||
userTookOver = true;
|
||||
finishDemo();
|
||||
// Clicks outside the popup (and not on its launcher icon) always collapse the popup
|
||||
if (e && e.target &&
|
||||
!e.target.closest('.extension-mockup') &&
|
||||
!e.target.closest('.demo-ext-launcher')) {
|
||||
setPopupOpen(false);
|
||||
}
|
||||
};
|
||||
scene.addEventListener('pointerdown', takeOver, true);
|
||||
scene.addEventListener('keydown', takeOver, true);
|
||||
|
||||
// Clicking anywhere else on the page also closes the popup, like a
|
||||
// real browser extension popup would.
|
||||
document.addEventListener('pointerdown', (e) => {
|
||||
if (!scene.classList.contains('popup-open')) return;
|
||||
if (scene.contains(e.target)) return; // in-scene clicks are handled by takeOver
|
||||
userTookOver = true;
|
||||
finishDemo();
|
||||
setPopupOpen(false);
|
||||
}, true);
|
||||
|
||||
// Explicit close button in the popup header
|
||||
const popupCloseBtn = scene.querySelector('#demo-popup-close');
|
||||
if (popupCloseBtn) {
|
||||
popupCloseBtn.addEventListener('click', () => {
|
||||
userTookOver = true;
|
||||
finishDemo();
|
||||
setPopupOpen(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Escape closes the popup as well
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key !== 'Escape') return;
|
||||
if (!scene.classList.contains('popup-open')) return;
|
||||
userTookOver = true;
|
||||
finishDemo();
|
||||
setPopupOpen(false);
|
||||
});
|
||||
|
||||
const runDemo = async () => {
|
||||
if (demoStarted || userTookOver || reduceMotion) return;
|
||||
// Prevent inline demo animations from running on tablet/mobile if windows are hidden:
|
||||
if (window.innerWidth < 1024 && !scene.closest('.mobile-demo-stage')) {
|
||||
finishDemo();
|
||||
return;
|
||||
}
|
||||
demoRunId++;
|
||||
const myRunId = demoRunId;
|
||||
demoStarted = true;
|
||||
|
||||
const wait = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
const cursorHotspot = { x: 5, y: 2 };
|
||||
const moveTo = async (el, fx) => {
|
||||
if (demoRunId !== myRunId) return;
|
||||
const er = el.getBoundingClientRect();
|
||||
const point = sceneLocalPoint(er, fx, 0.5);
|
||||
cursor.style.left = (point.x - cursorHotspot.x) + 'px';
|
||||
cursor.style.top = (point.y - cursorHotspot.y) + 'px';
|
||||
await wait(700);
|
||||
};
|
||||
const step = async (el, pause, action, fx) => {
|
||||
if (userTookOver || demoRunId !== myRunId || !el) return false;
|
||||
await moveTo(el, fx);
|
||||
if (userTookOver || demoRunId !== myRunId) return false;
|
||||
cursor.classList.remove('clicking');
|
||||
void cursor.offsetWidth;
|
||||
cursor.classList.add('clicking');
|
||||
await wait(180);
|
||||
if (userTookOver || demoRunId !== myRunId) return false;
|
||||
if (action) action(); else el.click();
|
||||
await wait(320 + (pause || 0));
|
||||
return !userTookOver && demoRunId === myRunId;
|
||||
};
|
||||
|
||||
await wait(500);
|
||||
if (userTookOver || demoRunId !== myRunId) return;
|
||||
cursor.classList.add('visible');
|
||||
await wait(400);
|
||||
if (userTookOver || demoRunId !== myRunId) return;
|
||||
|
||||
const progressA = tabs.a.root.querySelector('.demo-progress');
|
||||
|
||||
// Act 1: open the extension and create a room
|
||||
if (!await step(launcher, 300)) return;
|
||||
if (demoRunId !== myRunId) return;
|
||||
if (!await step(createRoomBtn, 500)) return;
|
||||
if (demoRunId !== myRunId) return;
|
||||
|
||||
// Act 2: the invite link travels to the friend's browser
|
||||
if (!await step(inviteCopyBtn, 0)) return;
|
||||
if (demoRunId !== myRunId) return;
|
||||
await flyInvite();
|
||||
if (userTookOver || demoRunId !== myRunId) return;
|
||||
setConnected(true);
|
||||
showToastB(toastB ? toastB.dataset.joined : '');
|
||||
pulse('b');
|
||||
await wait(900);
|
||||
if (userTookOver || demoRunId !== myRunId) return;
|
||||
|
||||
// Act 3: both sides pick the video tab to sync
|
||||
if (!await step(syncTabBtn, 250)) return;
|
||||
if (demoRunId !== myRunId) return;
|
||||
if (!await step(videoSelect, 150, flashSelect)) return;
|
||||
if (demoRunId !== myRunId) return;
|
||||
showToastB(toastB ? toastB.dataset.selected : '');
|
||||
await wait(700);
|
||||
if (userTookOver || demoRunId !== myRunId) return;
|
||||
|
||||
// Act 4: play for everyone, then tuck the popup away
|
||||
if (!await step(playBtn, 1800)) return;
|
||||
if (demoRunId !== myRunId) return;
|
||||
if (!await step(launcher, 500)) return;
|
||||
if (demoRunId !== myRunId) return;
|
||||
|
||||
// Act 5: any side can control — pause there, seek here, play again
|
||||
if (!await step(tabs.b.root, 1000)) return;
|
||||
if (demoRunId !== myRunId) return;
|
||||
if (!await step(progressA, 1300, () => seekTo('a', 0.62), 0.62)) return;
|
||||
if (demoRunId !== myRunId) return;
|
||||
if (!await step(tabs.a.root, 700)) return;
|
||||
if (demoRunId !== myRunId) return;
|
||||
|
||||
finishDemo();
|
||||
};
|
||||
|
||||
scene.__koalaStartDemo = reduceMotion ? finishDemo : runDemo;
|
||||
scene.__koalaFinishDemo = finishDemo;
|
||||
scene.__koalaResetDemo = resetDemo;
|
||||
|
||||
if ('IntersectionObserver' in window && !reduceMotion) {
|
||||
const demoObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
demoObserver.disconnect();
|
||||
setTimeout(runDemo, 900);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.45 });
|
||||
demoObserver.observe(scene);
|
||||
} else {
|
||||
finishDemo();
|
||||
}
|
||||
};
|
||||
|
||||
initHeroDemo();
|
||||
|
||||
const initMobileDemoModal = () => {
|
||||
const openBtn = document.getElementById('mobile-demo-open');
|
||||
const modal = document.getElementById('mobile-demo-modal');
|
||||
const stage = document.getElementById('mobile-demo-stage');
|
||||
const closeBtn = document.getElementById('mobile-demo-close');
|
||||
const wrapper = document.querySelector('.hero-grid > .hero-mockup-wrapper');
|
||||
if (!openBtn || !modal || !stage || !closeBtn || !wrapper) return;
|
||||
|
||||
const originalParent = wrapper.parentNode;
|
||||
const originalNext = wrapper.nextSibling;
|
||||
let previousFocus = null;
|
||||
|
||||
const openModal = () => {
|
||||
previousFocus = document.activeElement && typeof document.activeElement.focus === 'function'
|
||||
? document.activeElement
|
||||
: null;
|
||||
modal.hidden = false;
|
||||
document.body.classList.add('mobile-demo-open');
|
||||
openBtn.setAttribute('aria-expanded', 'true');
|
||||
stage.appendChild(wrapper);
|
||||
requestAnimationFrame(() => {
|
||||
closeBtn.focus({ preventScroll: true });
|
||||
const scene = wrapper.querySelector('#hero-demo');
|
||||
if (scene) {
|
||||
if (typeof scene.__koalaResetDemo === 'function') {
|
||||
scene.__koalaResetDemo();
|
||||
}
|
||||
if (typeof scene.__koalaStartDemo === 'function') {
|
||||
scene.__koalaStartDemo();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
if (modal.hidden) return;
|
||||
modal.hidden = true;
|
||||
document.body.classList.remove('mobile-demo-open');
|
||||
openBtn.setAttribute('aria-expanded', 'false');
|
||||
|
||||
const scene = wrapper.querySelector('#hero-demo');
|
||||
if (scene && typeof scene.__koalaResetDemo === 'function') {
|
||||
scene.__koalaResetDemo();
|
||||
}
|
||||
if (originalNext && originalNext.parentNode === originalParent) {
|
||||
originalParent.insertBefore(wrapper, originalNext);
|
||||
} else {
|
||||
originalParent.appendChild(wrapper);
|
||||
}
|
||||
if (previousFocus && typeof previousFocus.focus === 'function') {
|
||||
previousFocus.focus({ preventScroll: true });
|
||||
}
|
||||
};
|
||||
|
||||
openBtn.setAttribute('aria-expanded', 'false');
|
||||
openBtn.addEventListener('click', openModal);
|
||||
closeBtn.addEventListener('click', closeModal);
|
||||
modal.querySelectorAll('[data-mobile-demo-close]').forEach(el => {
|
||||
el.addEventListener('click', closeModal);
|
||||
});
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (modal.hidden) return;
|
||||
if (e.key === 'Escape') closeModal();
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
closeBtn.focus({ preventScroll: true });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
initMobileDemoModal();
|
||||
|
||||
// Terminal Tab Switcher
|
||||
const termTabBtns = document.querySelectorAll('.terminal-tab-btn');
|
||||
const termPanes = document.querySelectorAll('.terminal-pane');
|
||||
@@ -492,8 +1216,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const altLinks = document.querySelectorAll('a[href*="alternatives"]');
|
||||
altLinks.forEach(link => {
|
||||
const attr = link.getAttribute('href');
|
||||
if (attr === 'alternatives' || attr === 'alternatives/teleparty' || attr.endsWith('/alternatives')) {
|
||||
link.href = (activeLang === 'en') ? 'alternatives' : `${activeLang}/alternatives`;
|
||||
if (attr === 'alternatives' || attr.startsWith('alternatives/') || attr.endsWith('/alternatives')) {
|
||||
link.href = (activeLang === 'en') ? attr : `${activeLang}/${attr}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -698,7 +1422,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// User is on Chrome: Make Firefox secondary
|
||||
firefoxBtns.forEach(btn => {
|
||||
btn.classList.remove('btn-firefox');
|
||||
btn.classList.add('btn-secondary');
|
||||
btn.classList.add('btn-secondary', 'btn-firefox-secondary');
|
||||
btn.style.color = 'var(--text)';
|
||||
btn.style.background = 'var(--card)';
|
||||
btn.style.border = '1px solid var(--glass-border)';
|
||||
@@ -775,4 +1499,64 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
updateDynamicVersion();
|
||||
localizeHomeLinks();
|
||||
initLanguageSelectorValue();
|
||||
// FAQ Accordion Transition (Web Animations API for smooth vertical spring height collapse/expand)
|
||||
try {
|
||||
document.querySelectorAll('.faq-item').forEach(details => {
|
||||
const summary = details.querySelector('summary');
|
||||
let isAnimating = false;
|
||||
|
||||
summary.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
if (isAnimating) return;
|
||||
|
||||
const startHeight = details.offsetHeight;
|
||||
|
||||
if (details.hasAttribute('open')) {
|
||||
isAnimating = true;
|
||||
const computed = window.getComputedStyle(details);
|
||||
const padY = parseInt(computed.paddingTop) + parseInt(computed.paddingBottom);
|
||||
const borderY = parseInt(computed.borderTopWidth) + parseInt(computed.borderBottomWidth);
|
||||
const closedHeight = summary.offsetHeight + padY + (isNaN(borderY) ? 0 : borderY);
|
||||
|
||||
const animation = details.animate([
|
||||
{ height: startHeight + 'px' },
|
||||
{ height: closedHeight + 'px' }
|
||||
], {
|
||||
duration: 250,
|
||||
easing: 'cubic-bezier(0.16, 1, 0.3, 1)'
|
||||
});
|
||||
|
||||
animation.onfinish = () => {
|
||||
details.removeAttribute('open');
|
||||
details.style.height = '';
|
||||
isAnimating = false;
|
||||
};
|
||||
} else {
|
||||
isAnimating = true;
|
||||
details.setAttribute('open', '');
|
||||
const openHeight = details.offsetHeight;
|
||||
|
||||
const computed = window.getComputedStyle(details);
|
||||
const padY = parseInt(computed.paddingTop) + parseInt(computed.paddingBottom);
|
||||
const borderY = parseInt(computed.borderTopWidth) + parseInt(computed.borderBottomWidth);
|
||||
const closedHeight = summary.offsetHeight + padY + (isNaN(borderY) ? 0 : borderY);
|
||||
|
||||
const animation = details.animate([
|
||||
{ height: closedHeight + 'px' },
|
||||
{ height: openHeight + 'px' }
|
||||
], {
|
||||
duration: 250,
|
||||
easing: 'cubic-bezier(0.16, 1, 0.3, 1)'
|
||||
});
|
||||
|
||||
animation.onfinish = () => {
|
||||
details.style.height = '';
|
||||
isAnimating = false;
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1041px" height="565px" viewBox="0 0 1041 565" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
|
||||
<!-- Generated by Pixelmator Pro 2.2 -->
|
||||
<g id="Layer_1">
|
||||
<g id="group">
|
||||
<g id="group-1">
|
||||
<g id="group-2">
|
||||
<path id="Path" d="M735.8 365.7 C721.4 369 683.5 370.9 683.5 370.9 L678.7 385.9 C678.7 385.9 697.6 384.3 711.4 385.7 711.4 385.7 715.9 385.2 716.4 390.8 716.6 396 716 401.6 716 401.6 716 401.6 715.7 405 710.9 405.8 705.7 406.7 670.1 408 670.1 408 L664.3 427.5 C664.3 427.5 662.2 432 667 430.7 671.5 429.5 708.8 422.5 713.7 423.5 718.9 424.8 724.7 431.7 723 438.1 721 445.9 683.8 469.7 661.1 468 661.1 468 649.2 468.8 639.1 452.7 629.7 437.4 642.7 408.3 642.7 408.3 642.7 408.3 636.8 394.7 641.1 390.2 641.1 390.2 643.7 387.9 651.1 387.3 L660.2 368.4 C660.2 368.4 649.8 369.1 643.6 361.5 637.8 354.2 637.4 350.9 641.8 348.9 646.5 346.6 689.8 338.7 719.6 339.7 719.6 339.7 730 338.7 738.9 356.7 738.8 356.7 743.2 364 735.8 365.7 Z M623.7 438.3 C619.9 447.3 609.8 456.9 597.3 450.9 584.9 444.9 565.2 404.6 565.2 404.6 565.2 404.6 557.7 389.6 556.3 389.9 556.3 389.9 554.7 387 553.7 403.4 552.7 419.8 553.9 451.7 547.4 456.7 541.2 461.7 533.7 459.7 529.8 453.8 526.3 448 524.8 434.2 526.7 410 529 385.8 534.6 360 541.8 351.9 549 343.9 554.8 349.7 557 351.8 557 351.8 566.6 360.5 582.5 386.1 L585.3 390.8 C585.3 390.8 599.7 415 601.2 414.9 601.2 414.9 602.4 416 603.4 415.2 604.9 414.8 604.3 407 604.3 407 604.3 407 601.3 380.7 588.2 336.1 588.2 336.1 586.2 330.5 587.6 325.3 588.9 320 594.2 322.5 594.2 322.5 594.2 322.5 614.6 332.7 624.4 365.9 634.1 399.4 627.5 429.3 623.7 438.3 Z M523.5 353 C521.8 356.4 520.8 361.3 512.2 362.6 512.2 362.6 429.9 368.2 426 374 426 374 423.1 377.4 427.6 378.4 432.1 379.3 450.7 381.8 459.7 382.3 469.3 382.4 501.7 382.7 513.3 397.2 513.3 397.2 520.2 404.1 519.9 419.7 519.6 435.7 516.8 441.3 510.6 447.1 504.1 452.5 448.3 477.5 412.3 439.1 412.3 439.1 395.7 420.6 418 406.6 418 406.6 434.1 396.9 475 408.3 475 408.3 487.4 412.8 486.8 417.3 486.1 422.1 476.6 427.2 462.8 426.9 449.4 426.5 439.6 420.1 441.5 421.1 443.3 421.8 427.1 413.3 422.1 419.1 417.1 424.4 418.3 427.7 423.2 431 435.7 438.1 484 435.6 498.4 419.6 498.4 419.6 504.1 413.1 495.4 407.8 486.7 402.8 461.8 399.8 452.1 399.3 442.8 398.8 408.2 399.4 403.2 390.2 403.2 390.2 398.2 384 403.7 366.4 409.5 348 449.8 340.9 467.2 339.3 467.2 339.3 515.1 337.6 523.9 347.4 523.8 347.4 525 349.7 523.5 353 Z M387.5 460.9 C381.7 465.2 369.4 463.3 365.9 458.5 362.4 454.2 361.2 437.1 361.9 410.3 362.6 383.2 363.2 349.6 369 344.3 375.2 338.9 379 343.6 381.4 347.3 384 350.9 387.1 354.9 387.8 363.4 388.4 371.9 390.4 416.5 390.4 416.5 390.4 416.5 393 456.7 387.5 460.9 Z M400 317.1 C383.1 322.7 371.5 320.8 361.7 316.6 357.4 324.1 354.9 326.4 351.6 326.9 346.8 327.4 342.5 319.7 341.7 317.2 340.9 315.3 338.6 312.1 341.4 304.5 331.8 295.9 331.1 284.3 332.7 276.5 335.1 267.5 351.3 233.3 400.6 229.3 400.6 229.3 424.7 227.5 428.8 240.4 L429.5 240.4 C429.5 240.4 452.9 240.5 452.4 261.3 452.1 282.2 426.4 308.2 400 317.1 Z M354 270.8 C349 278.8 348.8 283.6 351.1 286.9 356.8 278.2 367.2 264.5 382.5 254.1 370.7 255.1 360.8 260.2 354 270.8 Z M422.1 257.4 C406.6 259.7 382.6 280.5 371.2 297.5 388.7 300.7 419.6 299.5 433.3 271.6 433.2 271.6 439.8 254.3 422.1 257.4 Z M842.9 418.5 C833.6 434.7 807.5 468.5 772.7 460.6 761.2 488.5 751.6 516.6 746.1 558.8 746.1 558.8 744.9 567 738.1 564.1 731.4 561.7 720.2 550.5 718 535 715.6 514.6 724.7 480.1 743.2 440.6 737.8 431.8 734.1 419.2 737.3 401.3 737.3 401.3 742 368.1 775.3 338.1 775.3 338.1 779.3 334.6 781.6 335.7 784.2 336.8 783 347.6 780.9 352.8 778.8 358 763.9 383.8 763.9 383.8 763.9 383.8 754.6 401.2 757.2 414.9 774.7 388 814.5 333.7 839.2 350.8 847.5 356.7 851.3 369.6 851.3 383.5 851.2 395.8 848.3 408.8 842.9 418.5 Z M835.7 375.9 C835.7 375.9 834.3 365.2 823.9 377 814.9 386.9 798.7 405.6 785.6 430.9 799.3 429.4 812.5 421.9 816.5 418.1 823 412.3 838.1 396.7 835.7 375.9 Z M350.2 389.5 C348.3 413.7 339 454.4 273.1 474.5 229.6 487.6 188.5 481.3 166.1 475.6 165.6 484.5 164.6 488.3 163.2 489.8 161.3 491.7 147.1 499.9 139.3 488.3 135.8 482.8 134 472.8 133 463.9 82.6 440.7 59.4 407.3 58.5 405.8 57.4 404.7 45.9 392.7 57.4 378 68.2 364.7 103.5 351.4 135.3 346 136.4 318.8 139.6 298.3 143.4 288.9 148 278 153.8 287.8 158.8 295.2 163 300.7 165.5 324.4 165.7 343.3 186.5 342.3 198.8 343.8 222 348 252.2 353.5 272.4 368.9 270.6 386.4 269.3 403.6 253.5 410.7 247.5 411.2 241.2 411.7 231.4 407.2 231.4 407.2 224.7 404 230.9 401.2 239 397.7 247.8 393.4 245.8 389 245.8 389 242.5 379.4 203.3 372.7 164.3 372.7 164.1 394.2 165.2 429.9 165.7 450.7 193 455.9 213.4 454.9 213.4 454.9 213.4 454.9 313 452.1 316 388.5 319.1 324.8 216.7 263.7 141 244.3 65.4 224.5 22.6 238.3 18.9 240.2 14.9 242.2 18.6 242.8 18.6 242.8 18.6 242.8 22.7 243.4 29.8 245.8 37.3 248.2 31.5 252.1 31.5 252.1 18.6 256.2 4.1 253.6 1.3 247.7 -1.5 241.8 3.2 236.5 8.6 228.9 14 220.9 19.9 221.2 19.9 221.2 113.4 188.8 227.3 247.4 227.3 247.4 334 301.5 352.2 364.9 350.2 389.5 Z M68 386.2 C57.4 391.4 64.7 398.9 64.7 398.9 84.6 420.3 109.1 433.7 132.4 442 135.1 405.1 134.7 392.1 135 373.5 98.6 376 77.6 381.8 68 386.2 Z" fill="#01147c" fill-opacity="1" stroke="none"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="group-3">
|
||||
<g id="group-4">
|
||||
<g id="group-5">
|
||||
<path id="Path-1" d="M1040.9 378.6 L1040.9 391.8 C1040.9 394.7 1038.6 397 1035.7 397 L972.8 397 C972.8 400.3 972.9 403.2 972.9 405.9 972.9 425.4 972.1 441.3 970.2 459.2 969.9 461.9 967.7 463.9 965.1 463.9 L951.5 463.9 C950.1 463.9 948.8 463.3 947.9 462.3 947 461.3 946.5 459.9 946.7 458.5 948.6 440.7 949.5 425 949.5 405.9 949.5 403.1 949.5 400.2 949.4 397 L887.2 397 C884.3 397 882 394.7 882 391.8 L882 378.6 C882 375.7 884.3 373.4 887.2 373.4 L948.5 373.4 C947.2 351.9 944.6 331.2 940.4 310.2 940.2 308.9 940.5 307.6 941.3 306.6 942.1 305.6 943.3 305 944.6 305 L959.3 305 C961.6 305 963.5 306.6 964 308.9 968.1 330.6 970.7 351.7 972 373.4 L1035.7 373.4 C1038.5 373.4 1040.9 375.8 1040.9 378.6 Z" fill="#01147c" fill-opacity="1" stroke="none"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="group-6">
|
||||
<g id="group-7">
|
||||
<path id="Path-2" d="M200.2 204.3 L200.1 204.3 M199.4 204.4 C199.1 204.4 198.8 204.3 198.5 204.3 198.8 204.4 199.1 204.4 199.4 204.4 L199.7 204.4 C199.6 204.4 199.5 204.4 199.4 204.4 Z M199.4 204.4 C199.1 204.4 198.8 204.3 198.5 204.3 198.8 204.4 199.1 204.4 199.4 204.4 L199.7 204.4 C199.6 204.4 199.5 204.4 199.4 204.4 Z" fill="none" stroke="none"/>
|
||||
<defs>
|
||||
<radialGradient id="radial-gradient" gradientUnits="userSpaceOnUse" cx="942.524" cy="279.896" r="760.124" fx="942.524" fy="279.896">
|
||||
<stop offset="0.007" stop-color="#021192" stop-opacity="1"/>
|
||||
<stop offset="0.03" stop-color="#021096" stop-opacity="1"/>
|
||||
<stop offset="0.057" stop-color="#010cb4" stop-opacity="1"/>
|
||||
<stop offset="0.084" stop-color="#0008ce" stop-opacity="1"/>
|
||||
<stop offset="0.111" stop-color="#0006d7" stop-opacity="1"/>
|
||||
<stop offset="0.138" stop-color="#0004e1" stop-opacity="1"/>
|
||||
<stop offset="0.165" stop-color="#0001fa" stop-opacity="1"/>
|
||||
<stop offset="0.191" stop-color="#0000fe" stop-opacity="1"/>
|
||||
<stop offset="0.216" stop-color="#0f1eff" stop-opacity="1"/>
|
||||
<stop offset="1" stop-color="#00ffff" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<path id="Path-3" d="M955.3 273.9 C922.8 194 867.9 125.9 796.5 76.9 723.4 26.8 637.7 0.3 548.7 0.3 401.5 0.3 264.9 73.4 183.4 195.9 182.5 197.2 182.3 198.9 182.8 200.4 183.3 202 184.5 203.1 186 203.6 L197.4 207.5 C198.1 207.7 198.8 207.8 199.4 207.8 201.5 207.8 203.5 206.7 204.7 205 242.1 150 292.7 104.3 351.1 72.7 411.4 40.1 479.7 22.8 548.6 22.8 631.9 22.8 712.2 47.4 781 93.8 848.1 139.1 900.2 202.4 931.7 276.7 932.6 278.9 934.8 280.4 937.2 280.4 L950.8 280.4 C952.4 280.4 953.9 279.6 954.7 278.3 955.7 277 955.9 275.4 955.3 273.9 Z M199.4 204.4 C199.1 204.4 198.8 204.3 198.5 204.2 198.8 204.3 199.1 204.4 199.4 204.4 L199.6 204.4 C199.6 204.4 199.5 204.4 199.4 204.4 Z M934.4 278.6 C934.7 278.8 935 279 935.3 279.1 935 278.9 934.7 278.8 934.4 278.6 Z" fill-opacity="1" fill="url(#radial-gradient)" stroke="none"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
+47
-10
@@ -117,6 +117,18 @@ async function compile() {
|
||||
}
|
||||
console.log(' ✓ Favicons/touch icons successfully synced to website/assets/');
|
||||
|
||||
// Social link preview image (og:image / twitter:image), same artwork as the
|
||||
// GitHub repository OpenGraph card. PNG kept for maximum scraper support.
|
||||
const ogSrc = path.join(websiteDir, '..', 'assets', 'StoreAssets', 'RepositoryOpenGraph.png');
|
||||
if (fs.existsSync(ogSrc)) {
|
||||
await sharp(ogSrc)
|
||||
.png({ quality: 80, palette: true })
|
||||
.toFile(path.join(targetAssetsDir, 'og-image.png'));
|
||||
console.log(' ✓ og-image.png generated from RepositoryOpenGraph.png');
|
||||
} else {
|
||||
console.warn(` ⚠️ Warning: ${ogSrc} not found. Skipping og-image generation.`);
|
||||
}
|
||||
|
||||
// ── 1. Minify CSS/JS (must happen first so hashes go into HTML) ──
|
||||
console.log('Minifying CSS/JS...');
|
||||
const styleRaw = fs.readFileSync(path.join(websiteDir, 'style.css'), 'utf8');
|
||||
@@ -174,6 +186,7 @@ async function compile() {
|
||||
let compiled = templateContent;
|
||||
compiled = compiled.replace(/\{\{ASSET_PATH\}\}/g, assetPath);
|
||||
compiled = compiled.replace(/\{\{VERSION\}\}/g, buildVersion);
|
||||
compiled = compiled.replace(/\{\{VERSION_DATE\}\}/g, versionJson.date || '');
|
||||
const langPrefix = lang === 'en' ? '' : `${lang}/`;
|
||||
compiled = compiled.replace(/\{\{LANG_PREFIX\}\}/g, langPrefix);
|
||||
languages.forEach(l => {
|
||||
@@ -188,12 +201,15 @@ async function compile() {
|
||||
// Read alternative page templates
|
||||
const telepartyTemplatePath = path.join(websiteDir, 'alternatives/teleparty.html');
|
||||
const screenSharingTemplatePath = path.join(websiteDir, 'alternatives/screen-sharing.html');
|
||||
const watch2getherTemplatePath = path.join(websiteDir, 'alternatives/watch2gether.html');
|
||||
const overviewTemplatePath = path.join(websiteDir, 'alternatives/index.html');
|
||||
const hasTelepartyTemplate = fs.existsSync(telepartyTemplatePath);
|
||||
const hasScreenSharingTemplate = fs.existsSync(screenSharingTemplatePath);
|
||||
const hasWatch2getherTemplate = fs.existsSync(watch2getherTemplatePath);
|
||||
const hasOverviewTemplate = fs.existsSync(overviewTemplatePath);
|
||||
const telepartyTemplate = hasTelepartyTemplate ? fs.readFileSync(telepartyTemplatePath, 'utf8') : '';
|
||||
const screenSharingTemplate = hasScreenSharingTemplate ? fs.readFileSync(screenSharingTemplatePath, 'utf8') : '';
|
||||
const watch2getherTemplate = hasWatch2getherTemplate ? fs.readFileSync(watch2getherTemplatePath, 'utf8') : '';
|
||||
const overviewTemplate = hasOverviewTemplate ? fs.readFileSync(overviewTemplatePath, 'utf8') : '';
|
||||
|
||||
// Preload English locale for fallback
|
||||
@@ -207,14 +223,6 @@ async function compile() {
|
||||
compiled = compiled.replace(/\{\{LANG_PREFIX\}\}/g, langPrefix);
|
||||
compiled = compiled.replace(/\{\{VERSION\}\}/g, buildVersion);
|
||||
|
||||
const indexTitle = lang === 'de' ? 'KoalaSync - Vergleiche & Anleitungen' : 'KoalaSync - Alternatives & Comparisons';
|
||||
const indexDesc = lang === 'de'
|
||||
? 'Entdecke ehrliche Vergleiche und detaillierte Leitfäden zwischen KoalaSync und anderen Watch-Party-Erweiterungen.'
|
||||
: 'Explore honest comparisons and detailed guides comparing KoalaSync with other watch party extensions and streaming solutions.';
|
||||
|
||||
compiled = compiled.replace(/\{\{ALT_INDEX_TITLE\}\}/g, indexTitle);
|
||||
compiled = compiled.replace(/\{\{ALT_INDEX_META_DESC\}\}/g, indexDesc);
|
||||
|
||||
// og:locale mapping for Facebook
|
||||
const ogLocales = {
|
||||
en: 'en_US', de: 'de_DE', fr: 'fr_FR', es: 'es_ES',
|
||||
@@ -256,6 +264,12 @@ async function compile() {
|
||||
const ssCompiled = compileAlternativePage(screenSharingTemplate, locale, lang, '../', '');
|
||||
fs.writeFileSync(path.join(altDir, 'screen-sharing.html'), ssCompiled);
|
||||
}
|
||||
if (hasWatch2getherTemplate) {
|
||||
const altDir = path.join(wwwDir, 'alternatives');
|
||||
fs.mkdirSync(altDir, { recursive: true });
|
||||
const w2gCompiled = compileAlternativePage(watch2getherTemplate, locale, lang, '../', '');
|
||||
fs.writeFileSync(path.join(altDir, 'watch2gether.html'), w2gCompiled);
|
||||
}
|
||||
if (hasOverviewTemplate) {
|
||||
const altDir = path.join(wwwDir, 'alternatives');
|
||||
fs.mkdirSync(altDir, { recursive: true });
|
||||
@@ -283,6 +297,12 @@ async function compile() {
|
||||
const ssCompiled = compileAlternativePage(screenSharingTemplate, locale, lang, '../../', lang + '/');
|
||||
fs.writeFileSync(path.join(langAltDir, 'screen-sharing.html'), ssCompiled);
|
||||
}
|
||||
if (hasWatch2getherTemplate) {
|
||||
const langAltDir = path.join(langDir, 'alternatives');
|
||||
fs.mkdirSync(langAltDir, { recursive: true });
|
||||
const w2gCompiled = compileAlternativePage(watch2getherTemplate, locale, lang, '../../', lang + '/');
|
||||
fs.writeFileSync(path.join(langAltDir, 'watch2gether.html'), w2gCompiled);
|
||||
}
|
||||
if (hasOverviewTemplate) {
|
||||
const langAltDir = path.join(langDir, 'alternatives');
|
||||
fs.mkdirSync(langAltDir, { recursive: true });
|
||||
@@ -314,14 +334,31 @@ async function compile() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── 5. Copy generic static files ──
|
||||
const genericFiles = ['robots.txt', 'sitemap.xml', 'site.webmanifest', 'version.json'];
|
||||
// ── 5. Copy generic static files and verification files ──
|
||||
const genericFiles = ['robots.txt', 'sitemap.xml', 'site.webmanifest', 'version.json', 'llms.txt'];
|
||||
for (const file of genericFiles) {
|
||||
const src = path.join(websiteDir, file);
|
||||
const dest = path.join(wwwDir, file);
|
||||
if (fs.existsSync(src)) { fs.copyFileSync(src, dest); }
|
||||
}
|
||||
|
||||
// Auto-copy Google verification files and IndexNow/txt key files from website source to www root
|
||||
const websiteFiles = fs.readdirSync(websiteDir);
|
||||
for (const file of websiteFiles) {
|
||||
const filePath = path.join(websiteDir, file);
|
||||
if (fs.statSync(filePath).isFile()) {
|
||||
// Match google[hex/alphanumeric].html or any hex/uuid txt file (e.g. 4650678bb36b488d8f64343a67f1b931.txt)
|
||||
const isGoogleVerification = /^google[a-zA-Z0-9_-]+\.html$/i.test(file);
|
||||
const isIndexNowVerification = (/^[a-zA-Z0-9_-]{8,}\.txt$/i.test(file) || /^[a-f0-9-]{32,36}$/i.test(file)) && file !== 'robots.txt';
|
||||
|
||||
if (isGoogleVerification || isIndexNowVerification) {
|
||||
const dest = path.join(wwwDir, file);
|
||||
fs.copyFileSync(filePath, dest);
|
||||
console.log(` Copied verification file: ${file} → ${file}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 6. Copy assets ──
|
||||
console.log('Copying assets...');
|
||||
const srcAssets = path.join(websiteDir, 'assets');
|
||||
|
||||
+76
-16
@@ -90,14 +90,28 @@
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<h2>1. Hosting & Logfiles</h2>
|
||||
<h2>1. Kontakt</h2>
|
||||
<p>
|
||||
Diese Seite wird auf einem privaten Server gehostet. Zur Gewährleistung der Stabilität werden standardmäßige Server-Logs (IP, Browser, Zeit) erhoben, aber nicht mit Personen verknüpft und nach 7 Tagen automatisch gelöscht.
|
||||
KoalaSync wird von Timo Schmidt betrieben.<br>
|
||||
Für datenschutzbezogene Fragen: <span class="email-reveal" data-user="koalasync" data-domain="koalastuff.net" style="color: var(--accent); cursor: pointer; text-decoration: underline;">[E-Mail anzeigen]</span>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>2. Keine Drittanbieter & Open Source</h2>
|
||||
<h2>2. Website-Hosting & Access Logs</h2>
|
||||
<p>
|
||||
KoalaSync wird auf Infrastruktur der Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Deutschland, gehostet. Der Serverstandort ist Finnland und damit innerhalb der EU/des EWR. Mit Hetzner besteht ein Auftragsverarbeitungsvertrag gemäß Art. 28 DSGVO.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Beim Aufruf dieser Website können Standard-Access-Logs Ihre IP-Adresse, Browser/User-Agent und Zeitpunkt enthalten. Diese Logs werden ausschließlich für Sicherheit, Stabilität und Missbrauchsprävention verwendet und nach 7 Tagen automatisch gelöscht.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Rechtsgrundlage: Art. 6 Abs. 1 lit. f DSGVO, berechtigtes Interesse am sicheren Betrieb der Website, an Stabilität und Missbrauchsprävention.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>3. Keine Drittanbieter & Open Source</h2>
|
||||
<p>
|
||||
KoalaSync verzichtet bewusst auf Analyse-Tools, Tracking-Cookies oder Werbenetzwerke. Wir laden keine Ressourcen von Drittanbietern (wie Google Fonts) nach, um Ihre Privatsphäre maximal zu schützen.
|
||||
</p>
|
||||
@@ -107,26 +121,41 @@
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>3. Relay-Server Architektur</h2>
|
||||
<h2>4. Offizieller Relay-Server & Raumdaten</h2>
|
||||
<p>
|
||||
Unser Relay-Server arbeitet ausschließlich im Arbeitsspeicher (RAM). Nachrichten zwischen Teilnehmern werden nicht auf Festplatten gespeichert und sind flüchtig. Sobald ein Raum geschlossen wird, werden alle zugehörigen Metadaten sofort gelöscht. Wir tracken Sie nicht. Wir überwachen lediglich unseren Server auf Basis von aggregierten, anonymen, nicht-personenbezogenen System-Leistungsmetriken.
|
||||
Der offizielle KoalaSync-Relay-Server führt keine persistenten Logs. Er speichert keine Raumhistorie, keinen Sync-Verlauf, keinen Wiedergabeverlauf, keine Raumnachrichten, keine Playback-Events, keine Tab-Titel und keine Medientitel auf Festplatte.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Zur Bereitstellung der Raumfunktion verarbeitet der Relay Raum- und Synchronisationsdaten temporär im Arbeitsspeicher, solange ein Raum aktiv ist. Dazu können Room-ID, Peer-ID, Anzeigename, Wiedergabestatus, Sync-Events und – abhängig von den Titel-Privatsphäre-Einstellungen – Tab-/Medientitel gehören. Diese Daten werden nur an Teilnehmer desselben Raums weitergeleitet und aus dem Arbeitsspeicher entfernt, wenn der Raum abläuft oder geschlossen wird.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Rechtsgrundlage: Art. 6 Abs. 1 lit. f DSGVO, berechtigtes Interesse an der Bereitstellung der gewünschten Synchronisationsfunktion, Sicherheit und Missbrauchsprävention.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Raum- und Peer-Zahlen sind aggregierte Betriebsmetriken zur Überwachung des Serverstatus.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>4. Browser-Erweiterung (Extension)</h2>
|
||||
<h2>5. Browser-Erweiterung & lokaler Speicher</h2>
|
||||
<p>
|
||||
Um die geräteübergreifende Synchronisation zu ermöglichen, erfasst die KoalaSync Browser-Erweiterung temporär Daten des aktuell aktiven Video-Tabs (z. B. Tab-Titel, Medien-Metadaten wie den Videotitel sowie den Wiedergabestatus). Diese Daten werden ausschließlich zur Synchronisation an die anderen Teilnehmer in Ihrem Raum gesendet. Es wird ausdrücklich <strong>kein allgemeiner Browserverlauf (Browsing History)</strong> ausgelesen, gespeichert oder übermittelt.
|
||||
Zur Synchronisierung der Wiedergabe zwischen Teilnehmern desselben Raums erfasst die KoalaSync Browser-Erweiterung temporär Daten des aktuell aktiven Video-Tabs (z. B. Tab-Titel, Medien-Metadaten wie den Videotitel sowie den Wiedergabestatus). Tab- oder Medientitel können je nach Inhalt potenziell personenbezogen sein. Sie werden nur für die Synchronisationsfunktion verarbeitet, können über die Privatsphäre-Einstellungen der Erweiterung eingeschränkt oder deaktiviert werden, werden nur an Teilnehmer desselben Raums weitergeleitet und vom Relay nicht protokolliert oder dauerhaft gespeichert.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Die Erweiterung speichert nur für den Betrieb erforderliche Einstellungen lokal im Browser, z. B. Anzeigename, Server-URL, aktuelle Raumdaten, Sprache, Titel-Privatsphäre-Einstellungen, Benachrichtigungseinstellungen und Audio-Processing-Einstellungen. KoalaSync speichert keinen allgemeinen Browserverlauf und keinen persistenten Wiedergabeverlauf.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Die Erweiterung verbindet sich nur mit dem Relay-Server, während Sie sich aktiv in einem Raum befinden. Es wird keine dauerhafte Hintergrundverbindung aufrechterhalten, sodass Ihre IP-Adresse dem Server nicht offengelegt wird, wenn Sie die Erweiterung nicht nutzen.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>5. Berechtigungen der Erweiterung</h2>
|
||||
<h2>6. Berechtigungen der Erweiterung</h2>
|
||||
<p>
|
||||
Um ihre technischen Zweck zu erfüllen, benötigt die Browser-Erweiterung bestimmte Berechtigungen. Jede Berechtigung wird ausschließlich für die Kernfunktionalität genutzt:
|
||||
Um ihren technischen Zweck zu erfüllen, benötigt die Browser-Erweiterung bestimmte Berechtigungen. Jede Berechtigung wird ausschließlich für die Kernfunktionalität genutzt:
|
||||
</p>
|
||||
<ul style="margin-left: 1.5rem; margin-top: 0.5rem; color: var(--text-muted); font-size: 0.9rem; list-style-type: disc; display: flex; flex-direction: column; gap: 0.35rem;">
|
||||
<li><strong>storage</strong>: Erlaubt das lokale Speichern von Benutzernamen, Server-URLs und Raum-Zugangsdaten im Browser, damit Sie sich nicht jedes Mal neu anmelden müssen.</li>
|
||||
<li><strong>storage</strong>: Ermöglicht die lokale Speicherung von Anzeigename, Server-URL, Raumdaten und Einstellungen im Browser, damit diese zwischen Sitzungen erhalten bleiben.</li>
|
||||
<li><strong>tabs</strong>: Wird benötigt, um geöffnete Tabs im Dropdown der Erweiterung aufzulisten und deren Titel auszulesen, damit Sie bequem den richtigen Video-Tab auswählen können.</li>
|
||||
<li><strong>scripting</strong>: Erforderlich, um das Synchronisationsskript (content.js) sicher in den von Ihnen ausgewählten Videotab zu injizieren.</li>
|
||||
<li><strong>alarms</strong>: Verhindert, dass der Hintergrunddienst der Erweiterung (Service Worker) während einer aktiven Synchronisationssitzung vom Browser in den Ruhezustand versetzt wird.</li>
|
||||
@@ -137,18 +166,49 @@
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>6. Brute-Force Schutz</h2>
|
||||
<h2>7. Brute-Force & Rate-Limit-Schutz</h2>
|
||||
<p>
|
||||
Zur Sicherheit unserer Nutzer speichern wir fehlgeschlagene Login-Versuche (IP-Adresse und Raum-ID) für maximal 15 Minuten im RAM, um automatisierte Angriffe zu verhindern. Diese Daten werden danach rückstandslos gelöscht.
|
||||
Zur Missbrauchsprävention können fehlgeschlagene Beitritts-/Login-Versuche die IP-Adresse und Room-ID für bis zu 15 Minuten im Arbeitsspeicher halten. Diese Daten werden nicht auf Festplatte geschrieben, nicht öffentlich zugänglich gemacht und automatisch gelöscht. Rechtsgrundlage: Art. 6 Abs. 1 lit. f DSGVO, berechtigtes Interesse an der Verhinderung von Brute-Force-Angriffen und Missbrauch.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>7. Ihre Rechte</h2>
|
||||
<h2>8. Empfänger & Dritte</h2>
|
||||
<p>
|
||||
Sie haben das Recht auf Auskunft, Berichtigung oder Löschung Ihrer Daten. Da wir jedoch keine personenbezogenen Daten dauerhaft speichern, ist eine Zuordnung zu Ihrer Person in der Regel technisch nicht möglich.
|
||||
Technische Hosting- und Verbindungsdaten können durch Hetzner als Hosting-Anbieter im Auftrag des Betreibers verarbeitet werden. Raum-Synchronisationsdaten werden nur an Teilnehmer desselben Raums weitergeleitet. KoalaSync verwendet keine Analytics-Anbieter, Werbenetzwerke oder Tracking-Dienste.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>9. Erforderliche & optionale Daten</h2>
|
||||
<p>
|
||||
Die Bereitstellung technischer Verbindungsdaten ist erforderlich, um die Website aufzurufen und den Relay zu nutzen. Raumdaten und Sync-Events sind erforderlich, um die Raumsynchronisation zu verwenden. Das Teilen von Tab-/Medientitel ist optional und kann über die Privatsphäre-Einstellungen der Erweiterung eingeschränkt werden.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>10. Rechte der betroffenen Person</h2>
|
||||
<p>
|
||||
Nach der DSGVO können Ihnen Rechte auf Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung, Datenübertragbarkeit, Widerspruch sowie das Recht auf Beschwerde bei einer Aufsichtsbehörde zustehen.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Da KoalaSync keine Nutzerkonten verwendet und keine persistenten Raumlogs oder Wiedergabeverläufe speichert, sind viele Datenpunkte entweder kurzlebig oder nach ihrer Löschung keiner bestimmten Person mehr zuordenbar. KoalaSync erhebt oder speichert außerdem keine zusätzlichen Identifikationsdaten nur um Nutzer für Betroffenenanfragen zu identifizieren.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">Kontakt für Datenschutzanfragen: <span class="email-reveal" data-user="koalasync" data-domain="koalastuff.net" style="color: var(--accent); cursor: pointer; text-decoration: underline;">[E-Mail anzeigen]</span></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>11. Kein Profiling & automatisierte Entscheidungsfindung</h2>
|
||||
<p>
|
||||
KoalaSync verwendet kein Profiling und keine automatisierte Entscheidungsfindung im Sinne von Art. 22 DSGVO.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>12. Drittlandstransfers</h2>
|
||||
<p>
|
||||
Das reguläre Hosting befindet sich innerhalb der EU/des EWR. Eine regelmäßige Übermittlung personenbezogener Daten in Drittländer findet nicht statt.
|
||||
</p>
|
||||
<p>Kontakt bei Fragen: <span class="email-reveal" data-user="koalasync_datenschutz" data-domain="koalamail.rocks" style="color: var(--accent); cursor: pointer; text-decoration: underline;">[E-Mail anzeigen]</span></p>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
@@ -156,7 +216,7 @@
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2026 KoalaSync. Open Source unter der MIT-Lizenz.</p>
|
||||
<p style="font-size: 0.8rem; margin-top: 0.5rem;">Keine Daten werden auf unseren Servern gespeichert. Reines RAM-basiertes Relay.</p>
|
||||
<p style="font-size: 0.8rem; margin-top: 0.5rem;">Keine Relay-Logs. Kein Tracking. Website-Access-Logs werden nach 7 Tagen gelöscht.</p>
|
||||
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
|
||||
<a href="impressum" style="color: var(--text-muted); text-decoration: none;">Impressum</a>
|
||||
<a href="datenschutz" style="color: var(--text-muted); text-decoration: none;">Datenschutz</a>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex, follow">
|
||||
<title>Redirecting...</title>
|
||||
<script>
|
||||
(function() {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
google-site-verification: google157cb8453d13ccdc.html
|
||||
+28
-18
@@ -84,57 +84,67 @@
|
||||
<div style="display: flex; justify-content: center;">
|
||||
<img src="../assets/KoalaImprintl.webp" alt="Niedlicher Koala, der das Impressum repräsentiert" class="legal-mascot" width="180" height="180">
|
||||
</div>
|
||||
<h1>Impressum</h1>
|
||||
<h1>Impressum / Anbieterinformationen</h1>
|
||||
<p style="text-align: center; text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.8rem; border-bottom: 1px solid var(--glass-border); padding-bottom: 1.5rem; margin-bottom: 2rem;">
|
||||
Transparenz & Identifikation
|
||||
Kontakt & Projektinformationen
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<h2>Betreiber & Kontakt</h2>
|
||||
<p>Timo (KoalaDev) – Privatperson</p>
|
||||
<p>Timo Schmidt</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
E-Mail: <span class="email-reveal" data-user="koalasync_admin" data-domain="koalamail.rocks" style="color: var(--accent); cursor: pointer; text-decoration: underline;">[E-Mail anzeigen]</span>
|
||||
E-Mail: <span class="email-reveal" data-user="koalasync" data-domain="koalastuff.net" style="color: var(--accent); cursor: pointer; text-decoration: underline;">[E-Mail anzeigen]</span>
|
||||
</p>
|
||||
<p style="margin-top: 0.75rem;">
|
||||
<span style="opacity: 0.6;">🔒 Private Nachricht:</span>
|
||||
<a href="https://mastodon.social/@koalastuff" rel="me noopener" target="_blank" style="color: var(--accent); text-decoration: none;">@koalastuff auf Mastodon</a>
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
<span style="opacity: 0.6;">🐛 Active Bedenken / Bug Reports:</span>
|
||||
<span style="opacity: 0.6;">🐛 Fehlerberichte und Sicherheitsmeldungen:</span>
|
||||
<a href="https://github.com/Shik3i/KoalaSync/issues" target="_blank" rel="noopener" style="color: var(--accent); text-decoration: none;">GitHub Issues</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section style="opacity: 0.8;">
|
||||
<h2>Privatprojekt-Hinweis</h2>
|
||||
<section>
|
||||
<h2>Projektinformationen</h2>
|
||||
<p>
|
||||
Diese Website ist ein rein privates Hobby-Projekt und dient keinen geschäftsmäßigen Zwecken.
|
||||
Eine Impressumspflicht nach § 5 DDG (ehemals TMG) besteht daher nicht.
|
||||
Diese Angaben erfolgen rein freiwillig zur Transparenz gegenüber der Community.
|
||||
KoalaSync ist eine kostenlose und quelloffene Browser-Erweiterung. Der Quellcode ist auf GitHub unter der MIT-Lizenz verfügbar.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Informationen zum Datenschutz, einschließlich Hosting, Access Logs, Relay-Verhalten und Betroffenenrechten, finden Sie in der <a href="datenschutz" style="color: var(--accent); text-decoration: none;">Datenschutzerklärung</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Haftung für Inhalte</h2>
|
||||
<p>
|
||||
Gemäß § 7 Abs.1 DDG sind wir für eigene Inhalte verantwortlich. Nach §§ 8 bis 10 DDG sind wir jedoch nicht verpflichtet,
|
||||
übermittelte oder gespeicherte fremde Informationen zu überwachen.
|
||||
Die Inhalte dieser Website wurden mit Sorgfalt erstellt. Ich kann jedoch nicht gewährleisten, dass alle Informationen jederzeit vollständig, richtig oder aktuell sind.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Als Diensteanbieter bin ich nach den allgemeinen Gesetzen für eigene Inhalte auf diesen Seiten verantwortlich.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Haftung für Links</h2>
|
||||
<p>
|
||||
Unser Angebot enthält Links zu externen Websites Dritter. Auf deren Inhalte haben wir keinen Einfluss und
|
||||
können daher keine Gewähr für diese fremden Inhalte übernehmen.
|
||||
Diese Website enthält Links zu externen Websites Dritter. Auf deren Inhalte habe ich keinen Einfluss. Für die Inhalte verlinkter Seiten ist der jeweilige Anbieter oder Betreiber verantwortlich.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Wenn mir rechtswidrige Inhalte auf verlinkten Seiten bekannt werden, entferne ich die entsprechenden Links.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Urheberrecht</h2>
|
||||
<h2>Lizenz & Urheberrecht</h2>
|
||||
<p>
|
||||
Die durch die Seitenbetreiber erstellten Inhalte auf diesen Seiten unterliegen dem deutschen Urheberrecht.
|
||||
Vervielfältigung, Bearbeitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung.
|
||||
KoalaSync ist freie und quelloffene Software. Sofern nicht anders angegeben, steht der Quellcode von KoalaSync unter der MIT-Lizenz.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Die MIT-Lizenz erlaubt Nutzung, Vervielfältigung, Bearbeitung, Verbreitung, Unterlizenzierung und Verkauf der Software, sofern der Copyright-Hinweis und der Lizenztext entsprechend den Lizenzbedingungen erhalten bleiben.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Projektname, Logos, Grafiken, Website-Texte und andere Nicht-Code-Assets können separaten Rechten unterliegen, sofern sie nicht ausdrücklich von der Repository-Lizenz umfasst sind.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
@@ -143,7 +153,7 @@
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2026 KoalaSync. Open Source unter der MIT-Lizenz.</p>
|
||||
<p style="font-size: 0.8rem; margin-top: 0.5rem;">Keine Daten werden auf unseren Servern gespeichert. Reines RAM-basiertes Relay.</p>
|
||||
<p style="font-size: 0.8rem; margin-top: 0.5rem;">Keine Relay-Logs. Kein Tracking. Website-Access-Logs werden nach 7 Tagen gelöscht.</p>
|
||||
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
|
||||
<a href="impressum" style="color: var(--text-muted); text-decoration: none;">Impressum</a>
|
||||
<a href="datenschutz" style="color: var(--text-muted); text-decoration: none;">Datenschutz</a>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex, follow">
|
||||
<title>Redirecting...</title>
|
||||
<script>
|
||||
(function() {
|
||||
|
||||
+27
-15
@@ -86,53 +86,65 @@
|
||||
</div>
|
||||
<h1>Legal Notice</h1>
|
||||
<p style="text-align: center; text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.8rem; border-bottom: 1px solid var(--glass-border); padding-bottom: 1.5rem; margin-bottom: 2rem;">
|
||||
Transparency & Identification
|
||||
Contact & Project Information
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<h2>Operator & Contact</h2>
|
||||
<p>Timo (KoalaDev) – Private Individual</p>
|
||||
<p>Timo Schmidt</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
E-Mail: <span class="email-reveal" data-user="koalasync_admin" data-domain="koalamail.rocks" style="color: var(--accent); cursor: pointer; text-decoration: underline;">[Show Email]</span>
|
||||
E-Mail: <span class="email-reveal" data-user="koalasync" data-domain="koalastuff.net" style="color: var(--accent); cursor: pointer; text-decoration: underline;">[Show Email]</span>
|
||||
</p>
|
||||
<p style="margin-top: 0.75rem;">
|
||||
<span style="opacity: 0.6;">🔒 Private message:</span>
|
||||
<a href="https://mastodon.social/@koalastuff" rel="me noopener" target="_blank" style="color: var(--accent); text-decoration: none;">@koalastuff on Mastodon</a>
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
<span style="opacity: 0.6;">🐛 Active concerns / Bug reports:</span>
|
||||
<span style="opacity: 0.6;">🐛 Bug reports and security concerns:</span>
|
||||
<a href="https://github.com/Shik3i/KoalaSync/issues" target="_blank" rel="noopener" style="color: var(--accent); text-decoration: none;">GitHub Issues</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section style="opacity: 0.8;">
|
||||
<h2>Private Project Notice</h2>
|
||||
<section>
|
||||
<h2>Project Information</h2>
|
||||
<p>
|
||||
This website is a purely private hobby project and does not serve any commercial purposes.
|
||||
Therefore, there is no obligation to provide a legal notice according to § 5 DDG (formerly TMG).
|
||||
This information is provided voluntarily for transparency towards the community.
|
||||
KoalaSync is a free and open-source browser extension. The source code is available on GitHub under the MIT License.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
For privacy-related information, including hosting, access logs, relay behavior, and data subject rights, please see the <a href="privacy" style="color: var(--accent); text-decoration: none;">Privacy Policy</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Liability for Content</h2>
|
||||
<p>
|
||||
According to § 7 Abs.1 DDG we are responsible for our own content. According to §§ 8 to 10 DDG, however, we are not obligated to monitor transmitted or stored third-party information.
|
||||
The contents of this website were created with care. However, I cannot guarantee that all information is always complete, accurate, or up to date.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
As a service provider, I am responsible for my own content on these pages in accordance with the general laws.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Liability for Links</h2>
|
||||
<p>
|
||||
Our offer contains links to external third-party websites. We have no influence on their content and therefore cannot assume any liability for these external contents.
|
||||
This website contains links to external third-party websites. I have no influence over the content of those websites. The respective provider or operator is responsible for the content of linked pages.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
If I become aware of unlawful content on linked pages, I will remove the corresponding links.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Copyright</h2>
|
||||
<h2>License & Copyright</h2>
|
||||
<p>
|
||||
The content and works created by the site operators on these pages are subject to German copyright law.
|
||||
Duplication, processing, and any kind of exploitation outside the limits of copyright require written consent.
|
||||
KoalaSync is free and open-source software. Unless otherwise stated, the KoalaSync source code is licensed under the MIT License.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
The MIT License allows use, copying, modification, distribution, sublicensing, and sale of the software, provided that the copyright notice and license text are included where required by the license.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Project name, logos, graphics, website text, and other non-code assets may be subject to separate rights unless they are explicitly included under the repository license.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
@@ -141,7 +153,7 @@
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2026 KoalaSync. Open source under the MIT License.</p>
|
||||
<p style="font-size: 0.8rem; margin-top: 0.5rem;">No data is stored on our servers. Pure RAM-based relay.</p>
|
||||
<p style="font-size: 0.8rem; margin-top: 0.5rem;">No relay logs. No tracking. Website access logs are deleted after 7 days.</p>
|
||||
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
|
||||
<a href="imprint" style="color: var(--text-muted); text-decoration: none;">Legal Notice</a>
|
||||
<a href="privacy" style="color: var(--text-muted); text-decoration: none;">Privacy Policy</a>
|
||||
|
||||
+6
-4
@@ -9,6 +9,8 @@
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon-32x32.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/icon-192x192.png">
|
||||
<meta name="description" content="Join a KoalaSync room invitation and start watching videos in sync with friends. No account needed.">
|
||||
<link rel="canonical" href="https://sync.koalastuff.net/join">
|
||||
<meta name="robots" content="noindex">
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
@@ -16,13 +18,13 @@
|
||||
<meta property="og:url" content="https://sync.koalastuff.net/join">
|
||||
<meta property="og:title" content="Join Room | KoalaSync">
|
||||
<meta property="og:description" content="You've been invited to watch videos together in perfect sync with your friends on KoalaSync.">
|
||||
<meta property="og:image" content="https://sync.koalastuff.net/assets/PlatformJuggler_New.webp">
|
||||
<meta property="og:image" content="https://sync.koalastuff.net/assets/og-image.png">
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="Join Room | KoalaSync">
|
||||
<meta name="twitter:description" content="You've been invited to watch videos together in perfect sync with your friends on KoalaSync.">
|
||||
<meta name="twitter:image" content="https://sync.koalastuff.net/assets/PlatformJuggler_New.webp">
|
||||
<meta name="twitter:image" content="https://sync.koalastuff.net/assets/og-image.png">
|
||||
|
||||
<!-- Mobile Browser Theme Styling -->
|
||||
<meta name="theme-color" content="#0f172a">
|
||||
@@ -114,8 +116,8 @@
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2026 KoalaSync. Open source under the MIT License.</p>
|
||||
<p lang="en" style="font-size: 0.8rem; margin-top: 0.5rem;">No data is stored on our servers. Pure RAM-based relay.</p>
|
||||
<p lang="de" style="font-size: 0.8rem; margin-top: 0.5rem;">Keine Daten werden auf unseren Servern gespeichert. Reines RAM-basiertes Relay.</p>
|
||||
<p lang="en" style="font-size: 0.8rem; margin-top: 0.5rem;">No relay logs. No tracking. Website access logs are deleted after 7 days.</p>
|
||||
<p lang="de" style="font-size: 0.8rem; margin-top: 0.5rem;">Keine Relay-Logs. Kein Tracking. Website-Access-Logs werden nach 7 Tagen gelöscht.</p>
|
||||
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
|
||||
<a href="impressum" style="color: var(--text-muted); text-decoration: none;"><span lang="en">Legal Notice</span><span lang="de">Impressum</span></a>
|
||||
<a href="datenschutz" style="color: var(--text-muted); text-decoration: none;"><span lang="en">Privacy Policy</span><span lang="de">Datenschutz</span></a>
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Apply saved theme before first paint (dark is the default, no class needed)
|
||||
if (safeGetLocalStorage('koala_theme') === 'light') {
|
||||
html.classList.add('theme-light');
|
||||
}
|
||||
|
||||
// Mapping of browser language codes to KoalaSync locale directories
|
||||
var langMap = {
|
||||
'de': 'de',
|
||||
@@ -97,8 +102,8 @@
|
||||
|
||||
if (isIndex) {
|
||||
var titles = {
|
||||
en: 'KoalaSync | Real-time Video Synchronization for Friends',
|
||||
de: 'KoalaSync | Echtzeit-Video-Synchronisation für Freunde'
|
||||
en: 'KoalaSync | Watch Party for Netflix, YouTube & Any Video',
|
||||
de: 'KoalaSync | Watch Party für Netflix, YouTube & jedes Video'
|
||||
};
|
||||
document.title = titles[activeLang] || titles.en;
|
||||
} else if (isJoin) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# KoalaSync
|
||||
|
||||
> Watch Netflix, YouTube, Twitch & any HTML5 video in perfect sync with friends. Free, open-source browser extension for Chrome and Firefox. No sign-up needed.
|
||||
|
||||
KoalaSync is a privacy-first peer-to-peer video synchronization tool. It uses WebRTC for direct connections between peers — no video data ever passes through a server. The relay server is only used for signaling and optional room-based coordination. Self-hostable, MIT licensed.
|
||||
|
||||
## Features
|
||||
|
||||
- [Sync any HTML5 video](https://sync.koalastuff.net/): Works with Netflix, Emby, Jellyfin, YouTube, Twitch, Amazon Prime Video, Disney+, Crunchyroll, HBO Max and more
|
||||
- [Host & co-host controls](https://sync.koalastuff.net/#features): Synchronized play, pause, seek, host control mode, co-host roles
|
||||
- [Privacy-first](https://sync.koalastuff.net/#features): No accounts, no tracking, no data collection, peer-to-peer by design
|
||||
- [Available in 15 languages](https://sync.koalastuff.net/): English, Deutsch, Français, Español, Português, Italiano, Nederlands, Polski, Türkçe, Русский, 日本語, 한국어, 中文, Українська
|
||||
|
||||
## Download
|
||||
|
||||
- [Chrome Web Store](https://chromewebstore.google.com/detail/koalasync/obbnmkmlaaddodakcbdljknjpagklifc): Install KoalaSync for Chrome, Edge, and other Chromium-based browsers
|
||||
- [Firefox Add-ons](https://addons.mozilla.org/de/firefox/addon/koalasync/): Install KoalaSync for Firefox
|
||||
- [GitHub Releases](https://github.com/Shik3i/KoalaSync/releases): Manual download and release notes
|
||||
|
||||
## Technical
|
||||
|
||||
- [Source code](https://github.com/Shik3i/KoalaSync): Full MIT-licensed source on GitHub (extension, server, website)
|
||||
- [Changelog](https://github.com/Shik3i/KoalaSync/blob/main/docs/CHANGELOG.md): Version history and release notes
|
||||
- [Self-hosting guide](https://sync.koalastuff.net/#self-hosting): Run your own relay server
|
||||
- [Server](https://sync.koalastuff.net/): Node.js WebSocket relay, port 54000
|
||||
|
||||
## Optional
|
||||
|
||||
- [Alternatives hub](https://sync.koalastuff.net/alternatives/): Comparison with Teleparty, Scener, other sync tools
|
||||
- [Privacy policy](https://sync.koalastuff.net/privacy.html): Data handling and privacy information
|
||||
- [Imprint](https://sync.koalastuff.net/imprint.html): Legal notice
|
||||
- [Sitemap](https://sync.koalastuff.net/sitemap.xml): Full site index
|
||||
- [Join a room](https://sync.koalastuff.net/join.html): Direct room join page
|
||||
+75
-34
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "de_DE",
|
||||
"META_TITLE": "KoalaSync | Netflix, YouTube & jedes Video mit Freunden synchronisieren",
|
||||
"META_DESCRIPTION": "Schaue Netflix, YouTube, Twitch und jedes HTML5-Video synchron mit Freunden. Kostenlose, quelloffene Browser-Erweiterung. Keine Anmeldung erforderlich.",
|
||||
"SCHEMA_APP_DESC": "Schaue Netflix, YouTube, Twitch und jedes HTML5-Video synchron mit Freunden. Kostenlose, quelloffene Browser-Erweiterung. Keine Anmeldung erforderlich.",
|
||||
"OG_TITLE": "KoalaSync | Netflix, Emby, Jellyfin & fast jedes Video mit Freunden synchronisieren",
|
||||
"OG_DESCRIPTION": "Schaue Netflix, Emby, Jellyfin, YouTube, Twitch und fast jedes HTML5-Video perfekt synchronisiert. Quelloffene, datenschutzfreundliche Browser-Erweiterung für Chrome und Firefox.",
|
||||
"TWITTER_TITLE": "KoalaSync | Netflix, Emby, Jellyfin & fast jedes Video mit Freunden synchronisieren – Browser-Erweiterung",
|
||||
"TWITTER_DESCRIPTION": "Schaue Netflix, Emby, Jellyfin, YouTube, Twitch und fast jedes HTML5-Video perfekt synchronisiert mit Freunden. Datenschutzfreundliche Browser-Erweiterung für Chrome und Firefox.",
|
||||
"META_TITLE": "KoalaSync | Watch Party für Netflix, YouTube & jedes Video",
|
||||
"META_DESCRIPTION": "Watch Party auf Netflix, YouTube, Twitch & HTML5-Videos. Open-Source-Erweiterung für Chrome & Firefox. Kein Konto, kein Tracking, keine Werbung.",
|
||||
"SCHEMA_APP_DESC": "Watch Party auf Netflix, YouTube, Twitch & HTML5-Videos. Open-Source-Erweiterung für Chrome & Firefox. Kein Konto, kein Tracking, keine Werbung.",
|
||||
"OG_TITLE": "KoalaSync | Watch Party für Netflix, Emby, Jellyfin & fast jedes Video",
|
||||
"OG_DESCRIPTION": "Starte eine Watch Party auf Netflix, Emby, Jellyfin, YouTube, Twitch und fast jedem HTML5-Video. Open Source, datenschutzfreundlich und ohne Konto.",
|
||||
"TWITTER_TITLE": "KoalaSync | Watch Party für Netflix, Emby, Jellyfin & fast jedes Video – Browser-Erweiterung",
|
||||
"TWITTER_DESCRIPTION": "Starte eine Watch Party auf Netflix, Emby, Jellyfin, YouTube, Twitch und fast jedem HTML5-Video. Open Source, datenschutzfreundlich und ohne Konto.",
|
||||
"NAV_FEATURES": "Funktionen",
|
||||
"NAV_HOW_IT_WORKS": "So funktioniert's",
|
||||
"HERO_TITLE": "Gemeinsam schauen.<span class='hero-line2'>Perfekt synchron.</span>",
|
||||
"HERO_SUBTITLE": "Dein Kino-Abend auf Distanz. Keine Lags, keine Anmeldung. Einfach Link teilen und zusammen schauen.",
|
||||
"HERO_TITLE": "Mach jedes Video zur <span class='hero-highlight'>Watch Party</span>.",
|
||||
"HERO_SUBTITLE": "Dein Kino-Abend auf Distanz, ohne Lags. Kein Account, kein Tracking, keine Werbung. Einfach Link teilen und gemeinsam Play drücken.",
|
||||
"HERO_MASCOT_ALT": "Ein niedlicher Koala steht da und schaut nach unten auf die Download-Buttons",
|
||||
"DEMO_SYNC": "Synchron",
|
||||
"DEMO_HINT": "Live-Demo: Drück Play in einem der Tabs, KoalaSync hält beide synchron.",
|
||||
"DEMO_NO_ROOM": "Noch nicht verbunden",
|
||||
"DEMO_CREATE_ROOM": "Raum erstellen",
|
||||
"DEMO_JOINED": "Raum beigetreten",
|
||||
"DEMO_TAB_SELECTED": "Tab ausgewählt",
|
||||
"DEMO_SELECT_PLACEHOLDER": "Video-Tab wählen…",
|
||||
"MOBILE_DEMO_CTA": "So funktioniert's ansehen",
|
||||
"MOBILE_DEMO_TITLE": "KoalaSync Live-Demo",
|
||||
"MOBILE_DEMO_CLOSE": "Demo schließen",
|
||||
"ADD_TO_CHROME": "Zu Chrome hinzufügen",
|
||||
"ADD_TO_FIREFOX": "Zu Firefox hinzufügen",
|
||||
"COMPAT_HEADING": "Funktioniert auf deinen Lieblingsplattformen",
|
||||
@@ -35,22 +45,22 @@
|
||||
"USE_CASE_3_DESC": "Überbrückt die Distanz und genießt gemeinsame Date-Nächte. Erlebt jeden Plot-Twist, lacht über dieselben Witze und teilt emotionale Momente zur selben Millisekunde.",
|
||||
"WHY_TITLE": "Warum KoalaSync?",
|
||||
"WHY_SUBTITLE": "Entwickelt für zuverlässigen Sync, Datenschutz und einfache Einrichtung.",
|
||||
"FEATURE_1_TITLE": "Volle Kontrolle, in Echtzeit",
|
||||
"FEATURE_1_DESC": "Einer pausiert, alle pausieren. Einer spult, alle folgen. Unser Zwei-Phasen-Synchronisationsprotokoll koordiniert die Wiedergabe aller Teilnehmer in Echtzeit.",
|
||||
"FEATURE_1_TITLE": "Raum-Kontrolle & Sync",
|
||||
"FEATURE_1_DESC": "Du bestimmst, wer die Wiedergabe steuert. Aktiviere die Host-Steuerung, damit nur du pausieren und spulen darfst, oder ernenne Co-Hosts in der Teilnehmerliste.",
|
||||
"FEATURE_2_TITLE": "Grenzenloses Bingen",
|
||||
"FEATURE_2_DESC": "Nächste Episode startet für jeden zeitgleich. KoalaSync erkennt den Episodenwechsel und pausiert, bis jeder Teilnehmer das neue Video fertig geladen hat.",
|
||||
"FEATURE_3_TITLE": "Keine Accounts / Datenschutz",
|
||||
"FEATURE_3_DESC": "Kein Login. Keine Daten. Einfach Link teilen und schauen. Der Server läuft flüchtig im RAM und löscht deinen Raum komplett nach dem Verlassen.",
|
||||
"FEATURE_4_TITLE": "Universeller HTML5-Support",
|
||||
"FEATURE_4_DESC": "Unterstützt YouTube, Twitch, Netflix, Jellyfin, Emby und fast jede beliebige Webseite mit einem HTML5-Video-Tag. Ideal auch für eigene Medienbibliotheken. Es werden keinerlei Videodaten an unsere Server gesendet — nur Synchronisations-Metadaten.",
|
||||
"FEATURE_4_DESC": "Netflix, YouTube, Twitch, Jellyfin, Emby und fast jede Seite mit HTML5-Video. Nur Sync-Signale verlassen deinen Browser, nie das Video.",
|
||||
"FEATURE_5_TITLE": "Self-Hostable & Docker-Ready",
|
||||
"FEATURE_5_DESC": "Unsere Server sind kostenlos und schnell, aber du kannst auch die volle Kontrolle übernehmen, wenn du willst. Starte dein eigenes privates Relay in Sekunden via Docker.",
|
||||
"FEATURE_6_TITLE": "Direkte Einladungen & 1-Klick Beitritt",
|
||||
"FEATURE_6_DESC": "Keine lästigen IPs oder Passwörter austauschen. Teile einfach einen Einladungslink mit deinen Freunden, um sie mit einem Klick in den Raum zu holen.",
|
||||
"FEATURE_7_TITLE": "Lautstärke Angleichen / Audio Compressor",
|
||||
"FEATURE_7_DESC": "Nie wieder am Lautstärkeregler spielen. Der eingebaute Audio Compressor verstärkt leise Dialoge und zähmt laute Explosionen. Funktioniert auf Netflix, Prime Video, Disney+, YouTube und jedem HTML5-Video. Komplett lokal — keine Audiodaten verlassen deinen Computer.",
|
||||
"FEATURE_7_DESC": "Nie wieder am Lautstärkeregler spielen. Ein eingebauter Kompressor hebt leise Dialoge an und dämpft laute Explosionen, alles lokal im Browser verarbeitet.",
|
||||
"FEATURE_8_TITLE": "Open Source (MIT-Lizenz)",
|
||||
"FEATURE_8_DESC": "Der gesamte Quellcode ist auf GitHub öffentlich einsehbar. Keine Black Boxes, keine proprietären Teile — 100 % Transparenz vom Relay-Server bis zur Browser-Erweiterung.",
|
||||
"FEATURE_8_DESC": "Der gesamte Quellcode ist auf GitHub öffentlich einsehbar. Keine Black Boxes, keine proprietären Teile. 100 % Transparenz vom Relay-Server bis zur Browser-Erweiterung.",
|
||||
"COMP_TITLE": "KoalaSync vs. Teleparty",
|
||||
"COMP_SUBTITLE": "Erfahre, warum Open Source, Werbefreiheit und echter Datenschutz die bessere Wahl für gemeinsames Schauen sind.",
|
||||
"COMP_COL_FEATURE": "Funktion",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Datenschutzerklärung und Tracker-Erfassung von Teleparty:",
|
||||
"COMP_FOOTNOTE_4": "Funktioniert auf allen Seiten, die Skript-Injektionen in Standard-HTML5-Videoplayer erlauben. Seiten mit extrem strengen Content Security Policies (CSP), DRM-Kopierschutz oder stark verschachtelten Player-Wrappern (z. B. komplexe Shadow-DOMs) können die automatische Steuerung blockieren.",
|
||||
"STEPS_TITLE": "Erste Schritte",
|
||||
"STEP_1_TITLE": "Erweiterung installieren",
|
||||
"STEP_1_DESC": "Füge KoalaSync aus dem Chrome Web Store, den Firefox Add-ons oder über die Entwickler-ZIP von GitHub zu deinem Browser hinzu.",
|
||||
"STEP_1_ILLUS_DESC": "Privatsphäre-fokussierte Video-Synchronisation",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Download für Chrome",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Download für Firefox",
|
||||
"STEP_1_ILLUS_ACTIVE": "Erweiterung aktiv",
|
||||
"STEP_1_ILLUS_READY": "Bereit zum Mitschauen!",
|
||||
"STEP_2_TITLE": "Raum erstellen",
|
||||
"STEP_2_DESC": "Öffne die Erweiterung und klicke auf „+ Neuer Raum“. KoalaSync generiert automatisch eine sichere Raum-ID samt Passwort, tritt dem Raum bei und kopiert den Einladungslink in deine Zwischenablage.",
|
||||
"STEP_1_TITLE": "Raum erstellen & teilen",
|
||||
"STEP_1_DESC": "Öffne das Popup der Erweiterung, erstelle einen Raum mit einem Klick und teile den generierten Link mit deinen Freunden. Sie treten über die Einladungsbrücke sofort bei.",
|
||||
"STEP_1_ILLUS_DESC": "Wähle einen Video-Tab",
|
||||
"STEP_1_ILLUS_DL_CHROME": "YouTube-Tab verbinden",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Netflix-Tab verbinden",
|
||||
"STEP_1_ILLUS_ACTIVE": "Tab verbunden",
|
||||
"STEP_1_ILLUS_READY": "Bereit für synchrones Schauen!",
|
||||
"STEP_2_TITLE": "Video-Tab auswählen",
|
||||
"STEP_2_DESC": "Klicke auf den Tab deines Videos und wähle im Popup den Tab aus, den du synchronisieren möchtest. KoalaSync übernimmt ab jetzt die automatische Verbindung.",
|
||||
"STEP_2_ILLUS_ROOM": "Raum",
|
||||
"STEP_2_ILLUS_SYNC": "Sync",
|
||||
"STEP_2_ILLUS_SETTINGS": "Optionen",
|
||||
"STEP_2_ILLUS_CREATE": "+ Neuer Raum",
|
||||
"STEP_2_ILLUS_MANUAL": "Manuell verbinden / Erweitert",
|
||||
"STEP_2_ILLUS_COPIED": "Einladungslink kopiert!",
|
||||
"STEP_3_TITLE": "Teilen & Synchronisieren",
|
||||
"STEP_3_DESC": "Sende den Einladungslink an deine Freunde. Sobald sie beitreten, wähle deinen Video-Tab aus und genieße die synchronisierte Wiedergabe.",
|
||||
"STEP_3_TITLE": "Synchron genießen",
|
||||
"STEP_3_DESC": "Drücke auf Wiedergabe. Egal wer pausiert, spult oder startet: Alles läuft bei allen Beteiligten absolut zeitgleich und verzögerungsfrei ab.",
|
||||
"STEP_3_ILLUS_IN_SYNC": "SYNCHRON",
|
||||
"SELF_TITLE": "Für Self-Hoster",
|
||||
"SELF_SUBTITLE": "Du traust dem Server nicht? Behalte die volle Datenhoheit. Richte deinen eigenen privaten Relay-Server in wenigen Minuten ein.",
|
||||
@@ -116,21 +126,21 @@
|
||||
"FAQ_TITLE": "Häufig gestellte Fragen",
|
||||
"FAQ_SUBTITLE": "Alles, was du über synchrones Videoschauen mit Freunden wissen musst.",
|
||||
"FAQ_Q1": "Kann ich Netflix, Emby oder Jellyfin synchron mit Freunden schauen?",
|
||||
"FAQ_A1": "Ja! KoalaSync funktioniert mit Netflix, YouTube, Twitch und jeder Website mit einem HTML5-Videoplayer — einschließlich Emby, Jellyfin und eigenen Medienservern. Installiere die Erweiterung, erstelle einen Raum, teile den Einladungslink und alle schauen perfekt synchron.",
|
||||
"FAQ_A1": "Ja! KoalaSync funktioniert mit Netflix, YouTube, Twitch und jeder Website mit einem HTML5-Videoplayer, einschließlich Emby, Jellyfin und eigenen Medienservern. Installiere die Erweiterung, erstelle einen Raum, teile den Einladungslink und alle schauen perfekt synchron.",
|
||||
"FAQ_Q_VIDEO_ACCESS": "Streamt KoalaSync das Video an meine Freunde?",
|
||||
"FAQ_A_VIDEO_ACCESS": "Nein. KoalaSync streamt, hostet oder teilt keine Videoinhalte und umgeht keinen Zugang. Jede Person schaut das Video lokal im eigenen Browser und braucht selbst Zugriff auf die jeweilige Website oder den Streamingdienst, zum Beispiel ein eigenes Netflix-Konto. KoalaSync synchronisiert nur Aktionen wie Play, Pause und Spulen.",
|
||||
"FAQ_Q2": "Ist KoalaSync eine kostenlose Alternative zu Teleparty?",
|
||||
"FAQ_A2": "100 % kostenlos und Open Source unter der MIT-Lizenz. Keine Premium-Stufen, keine gesperrten Funktionen, keine Werbung. Anders als Teleparty läuft KoalaSyncs Relay-Server ausschließlich im RAM — ohne Datensammlung und ohne Benutzerkonten.",
|
||||
"FAQ_A2": "100 % kostenlos und Open Source unter der MIT-Lizenz. Keine Premium-Stufen, keine gesperrten Funktionen, keine Werbung. Anders als Teleparty läuft KoalaSyncs Relay-Server ausschließlich im RAM, ohne Datensammlung und ohne Benutzerkonten.",
|
||||
"FAQ_Q3": "Funktioniert KoalaSync mit Emby, Jellyfin und eigenen Medienservern?",
|
||||
"FAQ_A3": "Ja. KoalaSync erkennt jedes HTML5-Videoelement und funktioniert perfekt mit Emby, Jellyfin, Plex und beliebigen eigenen Streaming-Setups. Du kannst den KoalaSync Relay-Server auch via Docker selbst hosten.",
|
||||
"FAQ_Q4": "Welche Browser und Plattformen unterstützen KoalaSync?",
|
||||
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave und alle Chromium-basierten Browser. Direkt aus dem Chrome Web Store oder den Firefox Add-ons installieren — keine manuelle Konfiguration nötig.",
|
||||
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave und alle Chromium-basierten Browser. Direkt aus dem Chrome Web Store oder den Firefox Add-ons installieren, keine manuelle Konfiguration nötig.",
|
||||
"FAQ_Q5": "Müssen alle meine Freunde die Erweiterung installieren?",
|
||||
"FAQ_A5": "Ja, jeder Teilnehmer braucht KoalaSync — aber ohne Anmeldung oder Kontoerstellung. Einfach auf den Einladungslink klicken, die Erweiterung installieren (falls nicht vorhanden) und du bist automatisch im Raum.",
|
||||
"FAQ_A5": "Ja, jeder Teilnehmer braucht KoalaSync, aber ohne Anmeldung oder Kontoerstellung. Einfach auf den Einladungslink klicken, die Erweiterung installieren (falls nicht vorhanden) und du bist automatisch im Raum.",
|
||||
"FAQ_Q6": "Sind meine Daten sicher? Was, wenn ich dem offiziellen Server nicht traue?",
|
||||
"FAQ_A6": "Absolut sicher. Der KoalaSync Relay-Server läuft ausschließlich im RAM — alle Sitzungsdaten werden sofort gelöscht, sobald du den Raum verlässt. Keine Datenbank, keine Protokolle, keine Analysen, keine Benutzerkonten. Wenn du volle Kontrolle möchtest: hoste deinen eigenen privaten Relay-Server via Docker in wenigen Minuten. Der gesamte Quellcode ist Open Source und auf GitHub einsehbar.",
|
||||
"FAQ_A6": "Absolut sicher. Der KoalaSync Relay-Server läuft ausschließlich im RAM, alle Sitzungsdaten werden sofort gelöscht, sobald du den Raum verlässt. Keine Datenbank, keine Protokolle, keine Analysen, keine Benutzerkonten. Wenn du volle Kontrolle möchtest: hoste deinen eigenen privaten Relay-Server via Docker in wenigen Minuten. Der gesamte Quellcode ist Open Source und auf GitHub einsehbar.",
|
||||
"FAQ_Q7": "Kann ich leise Dialoge und laute Explosionen beim Videoschauen im Browser ausgleichen?",
|
||||
"FAQ_A7": "Ja. KoalaSync 2.2.0 enthält einen eingebauten Audio Compressor, der den Unterschied zwischen lauten und leisen Passagen reduziert. Er läuft lokal über die Web Audio API — keine Audiodaten verlassen deinen Computer. Öffne das Popup, erstelle einen Raum, gehe zu Einstellungen → Audio Processing, schalte den Compressor ein und wähle ein Preset. Funktioniert auf Netflix, Prime Video, Disney+, YouTube und allen HTML5-Video-Seiten.",
|
||||
"FAQ_A7": "Ja. KoalaSync 2.2.0 enthält einen eingebauten Audio Compressor, der den Unterschied zwischen lauten und leisen Passagen reduziert. Er läuft lokal über die Web Audio API, keine Audiodaten verlassen deinen Computer. Öffne das Popup, erstelle einen Raum, gehe zu Einstellungen → Audio Processing, schalte den Compressor ein und wähle ein Preset. Funktioniert auf Netflix, Prime Video, Disney+, YouTube und allen HTML5-Video-Seiten.",
|
||||
"HOWTO_TITLE": "So synchronisierst du Videos mit Freunden mit KoalaSync",
|
||||
"HOWTO_DESC": "Schaue Videos perfekt synchron mit Freunden auf YouTube, Netflix, Twitch und mehr. Keine Anmeldung, kein Tracking.",
|
||||
"HOWTO_STEP_1_NAME": "Browser-Erweiterung installieren",
|
||||
@@ -140,7 +150,7 @@
|
||||
"HOWTO_STEP_3_NAME": "Teilen und synchronisieren",
|
||||
"HOWTO_STEP_3_TEXT": "Sende den Einladungslink an deine Freunde. Wähle einen Video-Tab aus und genieße synchronisierte Wiedergabe in Echtzeit.",
|
||||
"FOOTER_MIT": "Open Source unter der MIT-Lizenz.",
|
||||
"FOOTER_RAM": "Keine Daten werden auf unseren Servern gespeichert. Reines RAM-basiertes Relay.",
|
||||
"FOOTER_RAM": "Keine Relay-Logs. Kein Tracking. Website-Access-Logs werden nach 7 Tagen gelöscht.",
|
||||
"FOOTER_LEGAL": "Impressum",
|
||||
"FOOTER_PRIVACY": "Datenschutz",
|
||||
"MOCK_01": "Aktiver Raum",
|
||||
@@ -158,7 +168,7 @@
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> SYNC",
|
||||
"MOCK_14": "Zu anderen springen",
|
||||
"MOCK_15": "Letzte Aktivität",
|
||||
"MOCK_16": "Warten auf 1 Partner (KoalaPC)...",
|
||||
"MOCK_16": "Warten auf 1 Partner (🐶 HappyDog)...",
|
||||
"MOCK_17": "Überspringen & abspielen",
|
||||
"MOCK_18": "Dein Benutzername",
|
||||
"MOCK_19": "Noise-Filterung",
|
||||
@@ -179,6 +189,13 @@
|
||||
"FOOTER_PRIVACY_LINK": "de/datenschutz",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync ist nicht mit Netflix, Disney+, Amazon, YouTube, Twitch oder anderen Streaming-Plattformen verbunden, wird von diesen nicht unterstützt oder steht in keiner Beziehung zu diesen. Alle Markenrechte liegen bei ihren jeweiligen Inhabern.",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_INDEX_TITLE": "KoalaSync - Vergleiche & Anleitungen",
|
||||
"ALT_INDEX_META_DESC": "Entdecke ehrliche Vergleiche und detaillierte Leitfäden zwischen KoalaSync und anderen Watch-Party-Erweiterungen.",
|
||||
"ALT_INDEX_HEADING": "Vergleiche & Anleitungen",
|
||||
"ALT_INDEX_INTRO": "Wie schlägt sich KoalaSync im Vergleich zu anderen Watch-Party-Tools und Streaming-Lösungen? Lies unsere ehrlichen, technischen Vergleiche, um die passende Lösung für deine Filmabende zu finden.",
|
||||
"ALT_INDEX_READ_COMPARISON": "Vergleich lesen →",
|
||||
"ALT_INDEX_READ_GUIDE": "Anleitung lesen →",
|
||||
"ALT_INDEX_FOOTER_LINK": "Vergleiche & Anleitungen",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync vs Teleparty: Welche Watch-Party-Lösung passt besser?",
|
||||
"ALT_TELEPARTY_META_DESC": "Ein ehrlicher Vergleich von KoalaSync und Teleparty für Watch-Partys, eingebauten Chat, Mobile-Support, Browser-Video-Sync, selbst gehostete Medienserver und Open Source.",
|
||||
"ALT_TELEPARTY_INTRO": "KoalaSync und Teleparty lösen beide dasselbe nervige Problem: Du willst mit anderen Leuten online etwas schauen, ohne ständig „3, 2, 1, Play“ sagen zu müssen. Beide synchronisieren Videowiedergabe, aber sie gehen ziemlich unterschiedlich an die Sache ran.<br><br>Teleparty ist sinnvoll, wenn du einen eingebauten Chat möchtest oder am Handy schauen willst. KoalaSync ist eher für Desktop-Browser-Watch-Partys gedacht, bei denen du sowieso schon mit Freunden in Discord, Teamspeak oder einem anderen Voice-Call bist und einfach nur die Wiedergabe synchron halten möchtest.<br><br>Der Hauptgrund, warum ich KoalaSync gebaut habe, war Flexibilität. Es soll mit fast jeder Website funktionieren, die einen normalen Browser-Videoplayer nutzt, nicht nur mit einer festen Liste an Plattformen. Dazu gehören auch selbst gehostete Setups wie Emby, Jellyfin und Plex, lokale oder private Domains und viele normale Videoseiten im Browser.",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "KoalaSync ersetzt keinen Voice-Call. Wenn ihr beim Schauen reden wollt, nutzt ihr weiterhin Discord, Teamspeak, Signal, WhatsApp oder was auch immer ihr normalerweise verwendet.<br><br>Außerdem braucht jede Person selbst Zugriff auf das Video oder die jeweilige Seite. KoalaSync umgeht keine Abos, Logins, DRM, regionale Einschränkungen oder Plattformregeln.<br><br>KoalaSync ist aktuell Desktop-Browser-only, hat keinen eingebauten Chat oder Voice-Chat und die Funktion hängt immer vom jeweiligen Browser und Videoplayer ab.",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "Kurz gesagt",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "Bildschirmübertragung ist die einfachste Lösung, wenn es schnell gehen soll und Qualität, Privatsphäre oder individuelle Kontrolle nicht so wichtig sind. KoalaSync passt besser, wenn jeder lokal im eigenen Browser schauen soll und sich der Filmabend mehr wie wirklich gemeinsames Schauen anfühlen soll.",
|
||||
"ALT_TRY_KOALASYNC": "KoalaSync ausprobieren"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Welche Watch-Party-Lösung passt besser?",
|
||||
"ALT_W2G_META_DESC": "Ein ehrlicher Vergleich von KoalaSync und Watch2Gether für Watch-Partys. Erfahre, wann KoalaSyncs Flexibilität unschlagbar ist und warum Watch2Gether für YouTube die Nase vorn hat.",
|
||||
"ALT_W2G_INTRO": "KoalaSync und Watch2Gether lösen beide dasselbe Problem: Du willst mit Freunden online Videos schauen, ohne ständig manuell abgleichen zu müssen. Während Watch2Gether einer der ältesten und bekanntesten Dienste für gemeinsame Videoabende ist, geht KoalaSync einen ganz anderen, minimalistischen Weg. Welches Tool das richtige für dich ist, hängt stark davon ab, was und wo du schauen möchtest.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "Wann Watch2Gether die bessere Wahl ist",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether ist unschlagbar für <strong>YouTube-Watch-Partys</strong>. Dank integrierter Playlist und Suche kann jeder in der Lobby sofort neue Videos suchen, hinzufügen und abspielen. Alle schauen nahtlos das nächste Video zusammen.<br><br>Bei KoalaSync greift auf YouTube die Autoplay-Logik der Website: Nach Videoende wird jeder auf ein anderes Empfehlungsvideo geleitet. Unsere automatische Folgen-Fortsetzung („Episode Lobby“), die auf Netflix oder Disney+ super funktioniert, klappt auf YouTube wegen dieser dynamischen Vorschläge leider nicht. Für reine YouTube-Abende ist Watch2Gether daher die bessere Wahl.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "Wann KoalaSync besser passt",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync glänzt bei <strong>echten Streaming-Diensten</strong> (Netflix, Prime Video, Disney+) und selbstgehosteten Medienservern (Jellyfin, Plex, Emby). Im Gegensatz zu Watch2Gether, das oft keine Premium-Inhalte abspielen kann, synchronisiert KoalaSync den echten Original-Player direkt auf der jeweiligen Website.<br><br>Es funktioniert auf fast jeder Seite mit einem normalen HTML5-Videoplayer. Du musst keinen virtuellen Raum auf einer Drittseite erstellen: Einfach die Extension aktivieren, mit Freunden verbinden und losschauen.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "Der wichtigste Unterschied",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether ist eine <strong>eigenständige Website</strong> mit integriertem Chat, Playlist und Webcam-Support. KoalaSync ist eine <strong>minimalistische Erweiterung</strong>, die sich direkt in deine normalen Browser-Tabs einklinkt.<br><br>Suchst du ein All-in-One-Paket für YouTube mit Chat? Nimm Watch2Gether. Willst du Filme auf Netflix oder deinem eigenen Server ohne Ablenkung, ohne Umwege und in voller nativer Bildqualität (ohne Server-Komprimierung) genießen? Dann ist KoalaSync die sauberere Lösung.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Ehrliche Grenzen",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync hat <strong>keine eigene Playlist oder Suche</strong> wie Watch2Gether. Auf YouTube führt das dazu, dass das automatische Abspielen der nächsten Videos nicht synchron läuft.<br><br>Zudem gibt es <strong>keinen integrierten Chat</strong> und KoalaSync läuft aktuell nur auf Desktop-Browsern. Es ist so gedacht, dass ihr ohnehin über Discord, Teamspeak oder andere Voice-Tools miteinander sprecht.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "Kurz gesagt",
|
||||
"ALT_W2G_SUMMARY_BODY": "Für spontane YouTube-Sessions mit Playlists und integriertem Chat ist Watch2Gether die beste Wahl. Für echte Premium-Streaming-Plattformen (wie Netflix, Prime Video, Disney+), selbstgehostete Server (Jellyfin, Plex, Emby) und maximale Flexibilität im Browser ist KoalaSync das passendere und schlankere Tool.",
|
||||
"ALT_TRY_KOALASYNC": "KoalaSync ausprobieren",
|
||||
"MOCK_HOST_CONTROL_LABEL": "Host-Steuerung",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "Nur ich darf die Wiedergabe steuern",
|
||||
"LABEL_SELECT_VIDEO": "Video auswählen",
|
||||
"OPTION_SELECT_TAB": "-- Wähle einen Tab --",
|
||||
"THEME_TOGGLE_LABEL": "Hell-/Dunkelmodus umschalten",
|
||||
"FAQ_SHOW_ALL": "Alle Fragen anzeigen",
|
||||
"FAQ_HIDE_ALL": "Fragen ausblenden",
|
||||
"SELF_SHOW_SETUP": "Setup-Anleitung anzeigen",
|
||||
"SELF_HIDE_SETUP": "Setup-Anleitung ausblenden",
|
||||
"HERO_TRUST": "Kostenlos · Open Source · Kein Account nötig",
|
||||
"COMP_MORE_GUIDES": "Ausführlicher Vergleich und weitere Alternativen"
|
||||
}
|
||||
|
||||
+76
-35
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "de/",
|
||||
"LANG_TOGGLE_TEXT": "DE",
|
||||
"OG_LOCALE": "en_US",
|
||||
"META_TITLE": "KoalaSync | Sync Netflix, YouTube & Any Video with Friends",
|
||||
"META_DESCRIPTION": "Watch Netflix, YouTube, Twitch & any HTML5 video in perfect sync with friends. Free, open-source browser extension for Chrome and Firefox. No sign-up needed.",
|
||||
"SCHEMA_APP_DESC": "Watch Netflix, YouTube, Twitch & any HTML5 video in perfect sync with friends. Free, open-source browser extension for Chrome and Firefox. No sign-up needed.",
|
||||
"OG_TITLE": "KoalaSync | Sync Netflix, Emby, Jellyfin & Almost Any Video with Friends",
|
||||
"OG_DESCRIPTION": "Watch Netflix, Emby, Jellyfin, YouTube, Twitch and almost any HTML5 video in perfect sync. Privacy-first, open-source browser extension for Chrome & Firefox.",
|
||||
"TWITTER_TITLE": "KoalaSync | Sync Netflix, Emby, Jellyfin & Almost Any Video with Friends – Browser Extension",
|
||||
"TWITTER_DESCRIPTION": "Watch Netflix, Emby, Jellyfin, YouTube, Twitch and almost any HTML5 video in perfect sync with friends. Privacy-first, open-source browser extension for Chrome and Firefox.",
|
||||
"META_TITLE": "KoalaSync | Watch Party for Netflix, YouTube & Any Video",
|
||||
"META_DESCRIPTION": "Host a watch party on Netflix, YouTube, Twitch & HTML5 video. Free open-source extension for Chrome & Firefox. No account, tracking, or ads.",
|
||||
"SCHEMA_APP_DESC": "Host a watch party on Netflix, YouTube, Twitch & HTML5 video. Free open-source extension for Chrome & Firefox. No account, tracking, or ads.",
|
||||
"OG_TITLE": "KoalaSync | Watch Party for Netflix, Emby, Jellyfin & Almost Any Video",
|
||||
"OG_DESCRIPTION": "Host a watch party on Netflix, Emby, Jellyfin, YouTube, Twitch and almost any HTML5 video. Open source, privacy-first, and account-free.",
|
||||
"TWITTER_TITLE": "KoalaSync | Watch Party for Netflix, Emby, Jellyfin & Almost Any Video – Browser Extension",
|
||||
"TWITTER_DESCRIPTION": "Host a watch party on Netflix, Emby, Jellyfin, YouTube, Twitch and almost any HTML5 video. Open source, privacy-first, and account-free.",
|
||||
"NAV_FEATURES": "Features",
|
||||
"NAV_HOW_IT_WORKS": "How it works",
|
||||
"HERO_TITLE": "Watch Together.<span class='hero-line2'>Sync Perfectly.</span>",
|
||||
"HERO_SUBTITLE": "Your remote movie night without lags. No registration, no data collection. Just share a link and watch together.",
|
||||
"HERO_TITLE": "Turn any video into a <span class='hero-highlight'>watch party</span>.",
|
||||
"HERO_SUBTITLE": "Your remote movie night without lags. No account, no tracking, no ads. Just share a link and press play together.",
|
||||
"HERO_MASCOT_ALT": "A cute koala standing and looking down at the download buttons",
|
||||
"DEMO_SYNC": "In sync",
|
||||
"DEMO_HINT": "Live demo: press play in either tab and KoalaSync keeps both in sync.",
|
||||
"DEMO_NO_ROOM": "Not connected yet",
|
||||
"DEMO_CREATE_ROOM": "Create Room",
|
||||
"DEMO_JOINED": "Room joined",
|
||||
"DEMO_TAB_SELECTED": "Tab selected",
|
||||
"DEMO_SELECT_PLACEHOLDER": "Choose a video tab…",
|
||||
"MOBILE_DEMO_CTA": "Watch how it works",
|
||||
"MOBILE_DEMO_TITLE": "KoalaSync Live Demo",
|
||||
"MOBILE_DEMO_CLOSE": "Close demo",
|
||||
"ADD_TO_CHROME": "Add to Chrome",
|
||||
"ADD_TO_FIREFOX": "Add to Firefox",
|
||||
"COMPAT_HEADING": "Works on your favorite platforms",
|
||||
@@ -35,22 +45,22 @@
|
||||
"USE_CASE_3_DESC": "Bridge the distance and enjoy date nights. Experience every plot twist, laugh at the same jokes, and share emotional moments at the exact same millisecond.",
|
||||
"WHY_TITLE": "Why KoalaSync?",
|
||||
"WHY_SUBTITLE": "Built for reliable sync, privacy-first design, and easy setup.",
|
||||
"FEATURE_1_TITLE": "Full Control / Instant Sync",
|
||||
"FEATURE_1_DESC": "One pauses, everyone pauses. One seeks, everyone follows. Our two-phase sync protocol coordinates playback in real time across all participants.",
|
||||
"FEATURE_1_TITLE": "Host Control & Sync",
|
||||
"FEATURE_1_DESC": "You decide who controls playback. Turn on Host Control to lock playback actions to yourself, or promote individual peers to co-hosts in the participant list.",
|
||||
"FEATURE_2_TITLE": "Endless Binge-Watching",
|
||||
"FEATURE_2_DESC": "Autoplay in sync. KoalaSync automatically detects episode transitions and holds playback until all peers have successfully loaded the next video.",
|
||||
"FEATURE_3_TITLE": "Zero Accounts / Pure Privacy",
|
||||
"FEATURE_3_DESC": "No registration, no tracking, no persistency footprints. The server runs entirely in ephemeral RAM and purges your room when you leave.",
|
||||
"FEATURE_4_TITLE": "Universal HTML5 Support",
|
||||
"FEATURE_4_DESC": "Works on YouTube, Twitch, Netflix, Jellyfin, Emby, and almost any standard webpage containing a HTML5 video element. Also compatible with custom self-hosted setups. No video data is ever sent to our servers — only synchronization metadata.",
|
||||
"FEATURE_4_DESC": "Netflix, YouTube, Twitch, Jellyfin, Emby and almost any page with an HTML5 video. Only sync signals leave your browser, never the video.",
|
||||
"FEATURE_5_TITLE": "Self-Hostable & Docker-Ready",
|
||||
"FEATURE_5_DESC": "Our official servers are free and fast, but you can take full ownership if you want to. Launch your own private relay server in seconds via Docker.",
|
||||
"FEATURE_6_TITLE": "Instant Invites / 1-Click Join",
|
||||
"FEATURE_6_DESC": "No IP addresses or passwords to exchange. Share a generated invite link with your friends to let them join your room automatically with a single click.",
|
||||
"FEATURE_7_TITLE": "Volume Leveling / Audio Compressor",
|
||||
"FEATURE_7_DESC": "No more riding the volume knob. Built-in audio compressor boosts quiet dialogue and tames loud explosions. Works on Netflix, Prime Video, Disney+, YouTube, and any HTML5 video. Completely local — no audio data leaves your computer.",
|
||||
"FEATURE_7_DESC": "No more riding the volume knob. A built-in compressor lifts quiet dialogue and tames loud explosions, all processed locally in your browser.",
|
||||
"FEATURE_8_TITLE": "Open Source (MIT License)",
|
||||
"FEATURE_8_DESC": "The entire codebase is publicly auditable on GitHub. No black boxes, no proprietary blobs — 100% transparency from the relay server to the browser extension.",
|
||||
"FEATURE_8_DESC": "The entire codebase is publicly auditable on GitHub. No black boxes, no proprietary blobs. 100% transparency from the relay server to the browser extension.",
|
||||
"COMP_TITLE": "KoalaSync vs. Teleparty",
|
||||
"COMP_SUBTITLE": "See why open source, ad-free and privacy-first is the superior way to watch together.",
|
||||
"COMP_COL_FEATURE": "Feature",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Teleparty privacy policies and tracking data collection:",
|
||||
"COMP_FOOTNOTE_4": "Works on websites that allow script injections into standard HTML5 video tags. Websites with highly strict Content Security Policies (CSP), DRM copy protection, or heavily obfuscated player wrappers (like complex shadow DOMs) might restrict automated control or injection.",
|
||||
"STEPS_TITLE": "Getting Started",
|
||||
"STEP_1_TITLE": "Install Extension",
|
||||
"STEP_1_DESC": "Add KoalaSync to your browser from the Chrome Web Store, Firefox Add-ons, or download the latest developer ZIP from GitHub.",
|
||||
"STEP_1_ILLUS_DESC": "Privacy-first video synchronizer",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Download for Chrome",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Download for Firefox",
|
||||
"STEP_1_ILLUS_ACTIVE": "Extension Active",
|
||||
"STEP_1_ILLUS_READY": "Ready to watch together!",
|
||||
"STEP_2_TITLE": "Create a Room",
|
||||
"STEP_2_DESC": "Open the extension popup and click '+ Create New Room'. KoalaSync automatically generates a secure Room ID and Password, joins it, and copies the invite link to your clipboard.",
|
||||
"STEP_1_TITLE": "Create & Share Room",
|
||||
"STEP_1_DESC": "Open the extension popup, create a room with one click, and share the link with your friends. They join instantly via the invitation bridge.",
|
||||
"STEP_1_ILLUS_DESC": "Select a tab to link",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Link YouTube Tab",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Link Netflix Tab",
|
||||
"STEP_1_ILLUS_ACTIVE": "Tab Linked",
|
||||
"STEP_1_ILLUS_READY": "Ready to sync playback!",
|
||||
"STEP_2_TITLE": "Select Video Tab",
|
||||
"STEP_2_DESC": "Go to your video tab and select the tab you want to synchronize in the popup. KoalaSync automatically links your playheads from here.",
|
||||
"STEP_2_ILLUS_ROOM": "Room",
|
||||
"STEP_2_ILLUS_SYNC": "Sync",
|
||||
"STEP_2_ILLUS_SETTINGS": "Settings",
|
||||
"STEP_2_ILLUS_CREATE": "+ Create New Room",
|
||||
"STEP_2_ILLUS_MANUAL": "Manual Connect / Advanced",
|
||||
"STEP_2_ILLUS_COPIED": "Invite link copied!",
|
||||
"STEP_3_TITLE": "Share & Sync",
|
||||
"STEP_3_DESC": "Send the invite link to your friends. Once they join, select your video tab and enjoy synchronized playback.",
|
||||
"STEP_3_TITLE": "Enjoy Perfect Sync",
|
||||
"STEP_3_DESC": "Press play. Whether someone pauses, seeks, or plays, everyone's playback stays perfectly in sync without any lag.",
|
||||
"STEP_3_ILLUS_IN_SYNC": "IN SYNC",
|
||||
"SELF_TITLE": "For Self-Hosters",
|
||||
"SELF_SUBTITLE": "Don't trust our server? Maintain full data sovereignty. Deploy your own private relay server in minutes.",
|
||||
@@ -116,21 +126,21 @@
|
||||
"FAQ_TITLE": "Frequently Asked Questions",
|
||||
"FAQ_SUBTITLE": "Everything you need to know about watching videos together in perfect sync.",
|
||||
"FAQ_Q1": "Can I watch Netflix, Emby or Jellyfin together in sync with friends?",
|
||||
"FAQ_A1": "Yes! KoalaSync works on Netflix, YouTube, Twitch and any website with an HTML5 video player — including Emby, Jellyfin and self-hosted media servers. Install the extension, create a room, share the invite link, and everyone watches in perfect sync.",
|
||||
"FAQ_A1": "Yes! KoalaSync works on Netflix, YouTube, Twitch and any website with an HTML5 video player, including Emby, Jellyfin and self-hosted media servers. Install the extension, create a room, share the invite link, and everyone watches in perfect sync.",
|
||||
"FAQ_Q_VIDEO_ACCESS": "Does KoalaSync stream the video to my friends?",
|
||||
"FAQ_A_VIDEO_ACCESS": "No. KoalaSync does not stream, host, share, or bypass access to video content. Everyone watches the video locally in their own browser and needs their own access to the website or streaming service, such as their own Netflix account. KoalaSync only synchronizes playback actions like play, pause, and seeking.",
|
||||
"FAQ_Q2": "Is KoalaSync a free alternative to Teleparty?",
|
||||
"FAQ_A2": "100% free and open source under the MIT License. No premium tiers, no locked features, no ads — everything is included. Unlike Teleparty, KoalaSync's relay server is RAM-only with zero data collection or user accounts required.",
|
||||
"FAQ_A2": "100% free and open source under the MIT License. No premium tiers, no locked features, no ads, everything is included. Unlike Teleparty, KoalaSync's relay server is RAM-only with zero data collection or user accounts required.",
|
||||
"FAQ_Q3": "Does KoalaSync work with Emby, Jellyfin and self-hosted media servers?",
|
||||
"FAQ_A3": "Yes. KoalaSync detects any standard HTML5 video element, which means it works perfectly with Emby, Jellyfin, Plex, and any custom self-hosted streaming setup. You can also self-host the KoalaSync relay server via Docker for complete data sovereignty.",
|
||||
"FAQ_Q4": "Which browsers and platforms support KoalaSync?",
|
||||
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave, and all Chromium-based browsers. Install it directly from the Chrome Web Store or Firefox Add-ons — no manual configuration needed.",
|
||||
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave, and all Chromium-based browsers. Install it directly from the Chrome Web Store or Firefox Add-ons, no manual configuration needed.",
|
||||
"FAQ_Q5": "Do all my friends need the extension installed?",
|
||||
"FAQ_A5": "Yes, each participant needs KoalaSync installed — but there's no sign-up or account creation. Simply click the invitation link, install the extension if you haven't already, and you'll join the room automatically in seconds.",
|
||||
"FAQ_A5": "Yes, each participant needs KoalaSync installed, but there's no sign-up or account creation. Simply click the invitation link, install the extension if you haven't already, and you'll join the room automatically in seconds.",
|
||||
"FAQ_Q6": "Is my data safe? What if I don't trust the official server?",
|
||||
"FAQ_A6": "Completely safe. KoalaSync's relay server is RAM-only — all session data is permanently deleted the moment you leave the room. No database, no logging, no analytics, no accounts. And if you prefer full control: you can self-host your own private relay server via Docker in minutes. The entire codebase is open source and auditable on GitHub.",
|
||||
"FAQ_A6": "Completely safe. KoalaSync's relay server is RAM-only, all session data is permanently deleted the moment you leave the room. No database, no logging, no analytics, no accounts. And if you prefer full control: you can self-host your own private relay server via Docker in minutes. The entire codebase is open source and auditable on GitHub.",
|
||||
"FAQ_Q7": "Can I fix quiet dialogue and loud explosions when watching videos in the browser?",
|
||||
"FAQ_A7": "Yes. KoalaSync 2.2.0 includes a built-in audio compressor that reduces the gap between quiet and loud parts of your video. It runs locally via the Web Audio API — no audio data ever leaves your computer. Open the popup, create a room, go to Settings → Audio Processing, toggle the compressor on, and pick a preset. Works on Netflix, Prime Video, Disney+, YouTube, and any HTML5 video site.",
|
||||
"FAQ_A7": "Yes. KoalaSync 2.2.0 includes a built-in audio compressor that reduces the gap between quiet and loud parts of your video. It runs locally via the Web Audio API, no audio data ever leaves your computer. Open the popup, create a room, go to Settings → Audio Processing, toggle the compressor on, and pick a preset. Works on Netflix, Prime Video, Disney+, YouTube, and any HTML5 video site.",
|
||||
"HOWTO_TITLE": "How to synchronize videos with friends using KoalaSync",
|
||||
"HOWTO_DESC": "Watch any video in perfect sync with friends across YouTube, Netflix, Twitch and more. No sign-up, no tracking.",
|
||||
"HOWTO_STEP_1_NAME": "Install the browser extension",
|
||||
@@ -140,7 +150,7 @@
|
||||
"HOWTO_STEP_3_NAME": "Share and sync",
|
||||
"HOWTO_STEP_3_TEXT": "Send the invite link to your friends. Pick a video tab and enjoy synchronized playback in real time.",
|
||||
"FOOTER_MIT": "Open source under the MIT License.",
|
||||
"FOOTER_RAM": "No data is stored on our servers. Pure RAM-based relay.",
|
||||
"FOOTER_RAM": "No relay logs. No tracking. Website access logs are deleted after 7 days.",
|
||||
"FOOTER_LEGAL": "Legal Notice",
|
||||
"FOOTER_PRIVACY": "Privacy Policy",
|
||||
"MOCK_01": "Active Room",
|
||||
@@ -158,7 +168,7 @@
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> SYNC",
|
||||
"MOCK_14": "Jump to Others",
|
||||
"MOCK_15": "Last Activity Status",
|
||||
"MOCK_16": "Waiting for 1 peer (KoalaPC)...",
|
||||
"MOCK_16": "Waiting for 1 peer (🐶 HappyDog)...",
|
||||
"MOCK_17": "Skip & Play anyway",
|
||||
"MOCK_18": "Your Username",
|
||||
"MOCK_19": "Hide Clutter Tabs",
|
||||
@@ -179,6 +189,13 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync is not affiliated with, endorsed by, or associated with Netflix, Disney+, Amazon, YouTube, Twitch, or any other streaming platform. All trademarks are property of their respective owners.",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_INDEX_TITLE": "KoalaSync - Alternatives & Comparisons",
|
||||
"ALT_INDEX_META_DESC": "Explore honest comparisons and detailed guides comparing KoalaSync with other watch party extensions and streaming solutions.",
|
||||
"ALT_INDEX_HEADING": "Guides & Comparisons",
|
||||
"ALT_INDEX_INTRO": "How does KoalaSync stack up against other watch party tools and streaming solutions? Read our honest, technical comparisons to find the best fit for your remote movie nights.",
|
||||
"ALT_INDEX_READ_COMPARISON": "Read comparison →",
|
||||
"ALT_INDEX_READ_GUIDE": "Read guide →",
|
||||
"ALT_INDEX_FOOTER_LINK": "Guides & comparisons",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync vs Teleparty: which watch party tool should you use?",
|
||||
"ALT_TELEPARTY_META_DESC": "A practical comparison of KoalaSync and Teleparty for watch parties, built-in chat, mobile support, browser video sync, self-hosted media servers and open-source use.",
|
||||
"ALT_TELEPARTY_INTRO": "KoalaSync and Teleparty both help with the same annoying problem: watching something together without doing the whole \"3, 2, 1, play\" routine. They just take a different approach.<br><br>Teleparty makes the most sense if you want a built-in chat or if you want to watch on mobile. KoalaSync is more for desktop browser watch parties where you are already talking on Discord, Teamspeak or another voice call and just want playback to stay in sync.<br><br>The main reason I built KoalaSync was flexibility. It is meant to work with almost any website that uses a normal browser video player, not just a fixed list of platforms. That includes self-hosted setups like Emby, Jellyfin and Plex, local/private domains, and a lot of normal video pages in the browser.",
|
||||
@@ -194,7 +211,7 @@
|
||||
"ALT_TELEPARTY_SUMMARY_BODY": "If you want built-in chat or mobile support, Teleparty may be the better fit. If you want free, open-source desktop browser sync that works with almost any normal video player, including self-hosted media servers like Emby, Jellyfin and Plex, KoalaSync is probably the better match.",
|
||||
"ALT_SCREEN_TITLE": "KoalaSync vs screen sharing: when is video sync better?",
|
||||
"ALT_SCREEN_META_DESC": "A practical comparison of KoalaSync and screen sharing for remote movie nights, playback control, video quality, privacy, subtitles and browser-based watching.",
|
||||
"ALT_SCREEN_INTRO": "Screen sharing works, and sometimes it is honestly enough. One person shares their screen, everyone else watches, and you are done.<br><br>But for longer movie nights or watching episodes together, it can get annoying. The video quality depends on the sharer\u2019s upload, CPU, browser, and whatever compression Discord, Teamspeak, Zoom or another call app applies. Also, only one person really controls the playback.",
|
||||
"ALT_SCREEN_INTRO": "Screen sharing works, and sometimes it is honestly enough. One person shares their screen, everyone else watches, and you are done.<br><br>But for longer movie nights or watching episodes together, it can get annoying. The video quality depends on the sharer’s upload, CPU, browser, and whatever compression Discord, Teamspeak, Zoom or another call app applies. Also, only one person really controls the playback.",
|
||||
"ALT_SCREEN_WHEN_ENOUGH_TITLE": "When screen sharing is enough",
|
||||
"ALT_SCREEN_WHEN_ENOUGH_BODY": "Screen sharing is fine when you want the fastest possible setup. If only one person has access to the video, or everyone is okay with one person controlling play, pause and seeking, it may be the simplest option.<br><br>It also makes sense when quality is not a big deal, when nobody needs their own subtitles or audio settings, and when the person sharing is comfortable showing that browser window or screen.",
|
||||
"ALT_SCREEN_WHEN_ANNOYING_TITLE": "Where screen sharing gets annoying",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "KoalaSync is not a replacement for a voice call. If you want to talk while watching, you still use Discord, Teamspeak, Signal, WhatsApp or whatever you normally use.<br><br>Everyone also needs access to the video or page on their own device. KoalaSync does not bypass subscriptions, logins, DRM, regional restrictions or platform rules.<br><br>It is currently desktop-browser only, has no built-in chat or voice feature, and compatibility always depends on the browser and the video player.",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "Summary",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "Screen sharing is the simplest option when you just want something quick and do not care much about quality, privacy or individual control. KoalaSync is a better fit when everyone should watch locally in their own browser and the session should feel more like actually watching together.",
|
||||
"ALT_TRY_KOALASYNC": "Try KoalaSync"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "Try KoalaSync",
|
||||
"MOCK_HOST_CONTROL_LABEL": "Host Control",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "Only I can control playback",
|
||||
"LABEL_SELECT_VIDEO": "Select Video",
|
||||
"OPTION_SELECT_TAB": "-- Select a Tab --",
|
||||
"THEME_TOGGLE_LABEL": "Toggle light/dark mode",
|
||||
"FAQ_SHOW_ALL": "Show all questions",
|
||||
"FAQ_HIDE_ALL": "Hide questions",
|
||||
"SELF_SHOW_SETUP": "Show setup guide",
|
||||
"SELF_HIDE_SETUP": "Hide setup guide",
|
||||
"HERO_TRUST": "Free · Open source · No account needed",
|
||||
"COMP_MORE_GUIDES": "Full comparison and more alternatives"
|
||||
}
|
||||
|
||||
+68
-27
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "es_ES",
|
||||
"META_TITLE": "KoalaSync | Sincroniza Netflix, YouTube y cualquier video con amigos",
|
||||
"META_DESCRIPTION": "Mira Netflix, YouTube, Twitch y cualquier video HTML5 en perfecta sincronización con amigos. Extensión de navegador gratuita, de código abierto y sin necesidad de registro.",
|
||||
"SCHEMA_APP_DESC": "Mira Netflix, YouTube, Twitch y cualquier video HTML5 en perfecta sincronización con amigos. Extensión de navegador gratuita, de código abierto y sin necesidad de registro.",
|
||||
"OG_TITLE": "KoalaSync | Sincroniza Netflix, Emby, Jellyfin y casi cualquier video con amigos",
|
||||
"OG_DESCRIPTION": "Mira Netflix, Emby, Jellyfin, YouTube, Twitch y casi cualquier video HTML5 en perfecta sincronización. Extensión de navegador de código abierto y respetuosa con la privacidad para Chrome y Firefox.",
|
||||
"TWITTER_TITLE": "KoalaSync | Sincroniza Netflix, Emby, Jellyfin y casi cualquier video con amigos – Extensión de navegador",
|
||||
"TWITTER_DESCRIPTION": "Mira Netflix, Emby, Jellyfin, YouTube, Twitch y casi cualquier video HTML5 en perfecta sincronización con amigos. Extensión de navegador de código abierto y respetuosa con la privacidad para Chrome y Firefox.",
|
||||
"META_TITLE": "KoalaSync | Watch party para Netflix, YouTube y más",
|
||||
"META_DESCRIPTION": "Watch party en Netflix, YouTube, Twitch y cualquier vídeo HTML5. Extensión gratuita y open source para Chrome y Firefox. Sin cuenta, sin rastreo, sin anuncios.",
|
||||
"SCHEMA_APP_DESC": "Watch party en Netflix, YouTube, Twitch y cualquier vídeo HTML5. Extensión gratuita y open source para Chrome y Firefox. Sin cuenta, sin rastreo, sin anuncios.",
|
||||
"OG_TITLE": "KoalaSync | Watch party para Netflix, Emby, Jellyfin y casi cualquier vídeo",
|
||||
"OG_DESCRIPTION": "Organiza una watch party en Netflix, Emby, Jellyfin, YouTube, Twitch y casi cualquier vídeo HTML5. Open source, privada y sin cuenta.",
|
||||
"TWITTER_TITLE": "KoalaSync | Watch party para Netflix, Emby, Jellyfin y casi cualquier vídeo – Extensión",
|
||||
"TWITTER_DESCRIPTION": "Organiza una watch party en Netflix, Emby, Jellyfin, YouTube, Twitch y casi cualquier vídeo HTML5. Open source, privada y sin cuenta.",
|
||||
"NAV_FEATURES": "Características",
|
||||
"NAV_HOW_IT_WORKS": "Cómo funciona",
|
||||
"HERO_TITLE": "Miren juntos.<span class='hero-line2'>Sincronización perfecta.</span>",
|
||||
"HERO_SUBTITLE": "Tu noche de películas a distancia sin retrasos. Sin registros ni recopilación de datos. Solo comparte un enlace y miren juntos.",
|
||||
"HERO_TITLE": "Convierte cualquier vídeo en una <span class='hero-highlight'>watch party</span>.",
|
||||
"HERO_SUBTITLE": "Tu noche de cine a distancia, sin retrasos. Sin cuenta, sin rastreo, sin anuncios. Comparte un enlace y dale al play juntos.",
|
||||
"HERO_MASCOT_ALT": "Un lindo koala de pie mirando hacia abajo a los botones de descarga",
|
||||
"DEMO_SYNC": "Sincronizado",
|
||||
"DEMO_HINT": "Demo en vivo: pulsa play en cualquiera de las pestañas y KoalaSync mantiene ambas sincronizadas.",
|
||||
"DEMO_NO_ROOM": "Aún sin conexión",
|
||||
"DEMO_CREATE_ROOM": "Crear sala",
|
||||
"DEMO_JOINED": "Conectado a la sala",
|
||||
"DEMO_TAB_SELECTED": "Pestaña seleccionada",
|
||||
"DEMO_SELECT_PLACEHOLDER": "Elige una pestaña de video…",
|
||||
"MOBILE_DEMO_CTA": "Ver cómo funciona",
|
||||
"MOBILE_DEMO_TITLE": "Demo en vivo de KoalaSync",
|
||||
"MOBILE_DEMO_CLOSE": "Cerrar demo",
|
||||
"ADD_TO_CHROME": "Añadir a Chrome",
|
||||
"ADD_TO_FIREFOX": "Añadir a Firefox",
|
||||
"COMPAT_HEADING": "Funciona en tus plataformas favoritas",
|
||||
@@ -35,20 +45,20 @@
|
||||
"USE_CASE_3_DESC": "Reduce la distancia y disfruta de noches de citas. Vive cada giro de la trama, ríete de los mismos chistes y comparte momentos emotivos en el mismo milisegundo exacto.",
|
||||
"WHY_TITLE": "¿Por qué KoalaSync?",
|
||||
"WHY_SUBTITLE": "Diseñado para una sincronización confiable, con la privacidad como prioridad y una configuración sencilla.",
|
||||
"FEATURE_1_TITLE": "Control total / Sincronización instantánea",
|
||||
"FEATURE_1_DESC": "Si uno pausa, todos pausan. Si uno avanza, todos le siguen. Nuestro protocolo de sincronización en dos fases coordina la reproducción en tiempo real para todos los participantes.",
|
||||
"FEATURE_1_TITLE": "Control del host y sincronización",
|
||||
"FEATURE_1_DESC": "Tú decides quién controla la reproducción. Activa el control del anfitrión para que solo tú puedas pausar y avanzar, o asigna coanfitriones.",
|
||||
"FEATURE_2_TITLE": "Maratones sin fin",
|
||||
"FEATURE_2_DESC": "Reproducción automática sincronizada. KoalaSync detecta automáticamente los cambios de episodio y detiene la reproducción hasta que todos los participantes hayan cargado el siguiente video.",
|
||||
"FEATURE_3_TITLE": "Sin cuentas / Privacidad absoluta",
|
||||
"FEATURE_3_DESC": "Sin registros, sin seguimiento y sin dejar huella. El servidor funciona completamente en RAM efímera y elimina tu sala en cuanto te vas.",
|
||||
"FEATURE_4_TITLE": "Soporte universal HTML5",
|
||||
"FEATURE_4_DESC": "Funciona en YouTube, Twitch, Netflix, Jellyfin, Emby y casi cualquier página web estándar con un reproductor HTML5. También es compatible con configuraciones propias. Nunca enviamos datos de vídeo a nuestros servidores, solo metadatos de sincronización.",
|
||||
"FEATURE_4_DESC": "Netflix, YouTube, Twitch, Jellyfin, Emby y casi cualquier página con vídeo HTML5. Solo salen las señales de sincronización, nunca el vídeo.",
|
||||
"FEATURE_5_TITLE": "Auto-alojable y listo para Docker",
|
||||
"FEATURE_5_DESC": "Nuestros servidores oficiales son gratuitos y rápidos, pero puedes tomar el control total si lo deseas. Inicia tu propio servidor de retransmisión privado en segundos mediante Docker.",
|
||||
"FEATURE_6_TITLE": "Invitaciones al instante / Unirse con un clic",
|
||||
"FEATURE_6_DESC": "Sin intercambio de direcciones IP o contraseñas. Comparte un enlace de invitación generado para que tus amigos se unan automáticamente a tu sala con un solo clic.",
|
||||
"FEATURE_7_TITLE": "Nivelación de volumen / Compresor de audio",
|
||||
"FEATURE_7_DESC": "Se acabó el tener que ajustar el volumen constantemente. El compresor de audio integrado realza los diálogos bajos y suaviza las explosiones fuertes. Funciona en Netflix, Prime Video, Disney+, YouTube y cualquier video HTML5. Funciona de forma local: ningún dato de audio sale de tu equipo.",
|
||||
"FEATURE_7_DESC": "Se acabó ajustar el volumen. Un compresor integrado sube los diálogos bajos y suaviza las explosiones fuertes, todo procesado localmente en tu navegador.",
|
||||
"FEATURE_8_TITLE": "Código Abierto (Licencia MIT)",
|
||||
"FEATURE_8_DESC": "Todo el código fuente es público y auditable en GitHub. Sin cajas negras ni software propietario: transparencia total desde el servidor de retransmisión hasta la extensión del navegador.",
|
||||
"COMP_TITLE": "KoalaSync vs Teleparty",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Políticas de privacidad y recopilación de datos de Teleparty:",
|
||||
"COMP_FOOTNOTE_4": "Funciona en sitios web que permiten inyecciones de scripts en etiquetas de video HTML5 estándar. Los sitios con políticas de seguridad (CSP) muy estrictas, protección DRM o reproductores ofuscados (como DOMs de sombra complejos) pueden restringir el control automático.",
|
||||
"STEPS_TITLE": "Cómo empezar",
|
||||
"STEP_1_TITLE": "Instalar la extensión",
|
||||
"STEP_1_DESC": "Añade KoalaSync a tu navegador desde la Chrome Web Store, complementos de Firefox o descarga el archivo ZIP para desarrolladores desde GitHub.",
|
||||
"STEP_1_ILLUS_DESC": "Sincronizador de video privado",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Descargar para Chrome",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Descargar para Firefox",
|
||||
"STEP_1_ILLUS_ACTIVE": "Extensión activa",
|
||||
"STEP_1_ILLUS_READY": "¡Listo para ver juntos!",
|
||||
"STEP_2_TITLE": "Crear una sala",
|
||||
"STEP_2_DESC": "Abre el menú de la extensión y haz clic en '+ Crear nueva sala'. KoalaSync generará automáticamente un ID de sala y una contraseña seguros, se conectará y copiará el enlace al portapapeles.",
|
||||
"STEP_1_TITLE": "Crear y compartir sala",
|
||||
"STEP_1_DESC": "Abre la extensión, crea una sala con un clic y comparte el enlace con tus amigos. Se unirán al instante a través del enlace de invitación.",
|
||||
"STEP_1_ILLUS_DESC": "Elige una pestaña para vincular",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Vincular pestaña de YouTube",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Vincular pestaña de Netflix",
|
||||
"STEP_1_ILLUS_ACTIVE": "Pestaña vinculada",
|
||||
"STEP_1_ILLUS_READY": "Listo para ver en sincronía!",
|
||||
"STEP_2_TITLE": "Seleccionar pestaña de vídeo",
|
||||
"STEP_2_DESC": "Ve a la pestaña de tu vídeo y selecciónala en el menú de la extensión. KoalaSync vinculará vuestros reproductores automáticamente.",
|
||||
"STEP_2_ILLUS_ROOM": "Sala",
|
||||
"STEP_2_ILLUS_SYNC": "Sincro",
|
||||
"STEP_2_ILLUS_SETTINGS": "Ajustes",
|
||||
"STEP_2_ILLUS_CREATE": "+ Crear nueva sala",
|
||||
"STEP_2_ILLUS_MANUAL": "Conexión manual / Avanzado",
|
||||
"STEP_2_ILLUS_COPIED": "¡Enlace de invitación copiado!",
|
||||
"STEP_3_TITLE": "Compartir y Sincronizar",
|
||||
"STEP_3_DESC": "Envía el enlace de invitación a tus amigos. Una vez que se unan, selecciona tu pestaña de video y disfrutad de la reproducción sincronizada.",
|
||||
"STEP_3_TITLE": "Disfrutar de la sincronización",
|
||||
"STEP_3_DESC": "Pulsa reproducir. Cuando alguien pause, avance o reproduzca, el vídeo de todos se mantendrá en sincronía en tiempo real y sin retrasos.",
|
||||
"STEP_3_ILLUS_IN_SYNC": "SINCRONIZADO",
|
||||
"SELF_TITLE": "Para auto-alojadores",
|
||||
"SELF_SUBTITLE": "¿No te fías de nuestro servidor? Mantén la soberanía total de tus datos. Despliega tu propio servidor de retransmisión en minutos.",
|
||||
@@ -158,7 +168,7 @@
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> SINCRO",
|
||||
"MOCK_14": "Ir a la posición de otros",
|
||||
"MOCK_15": "Estado de la última actividad",
|
||||
"MOCK_16": "Esperando a 1 participante (KoalaPC)...",
|
||||
"MOCK_16": "Esperando a 1 participante (🐶 HappyDog)...",
|
||||
"MOCK_17": "Omitir y reproducir ya",
|
||||
"MOCK_18": "Tu nombre de usuario",
|
||||
"MOCK_19": "Ocultar pestañas sin video",
|
||||
@@ -179,6 +189,13 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync no está afiliado, respaldado ni asociado con Netflix, Disney+, Amazon, YouTube, Twitch ni ninguna otra plataforma de streaming. Todas las marcas comerciales pertenecen a sus respectivos dueños.",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_INDEX_TITLE": "KoalaSync - Alternativas y comparativas",
|
||||
"ALT_INDEX_META_DESC": "Explora comparativas honestas y guías detalladas entre KoalaSync y otras extensiones de watch party y soluciones de streaming.",
|
||||
"ALT_INDEX_HEADING": "Guías y comparativas",
|
||||
"ALT_INDEX_INTRO": "¿Cómo se compara KoalaSync con otras herramientas de watch party y soluciones de streaming? Lee nuestras comparativas honestas y técnicas para encontrar la mejor opción para tus noches de cine a distancia.",
|
||||
"ALT_INDEX_READ_COMPARISON": "Leer comparativa →",
|
||||
"ALT_INDEX_READ_GUIDE": "Leer guía →",
|
||||
"ALT_INDEX_FOOTER_LINK": "Guías y comparativas",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync frente a Teleparty: ¿qué herramienta de visionado conjunto deberías usar?",
|
||||
"ALT_TELEPARTY_META_DESC": "Una comparativa práctica entre KoalaSync y Teleparty para ver contenido en grupo, analizando aspectos como el chat integrado, la compatibilidad móvil, la sincronización de vídeo en el navegador, los servidores multimedia autohospedados y el uso de código abierto.",
|
||||
"ALT_TELEPARTY_INTRO": "Tanto KoalaSync como Teleparty ayudan a resolver el mismo problema molesto: ver algo juntos sin tener que recurrir a la típica rutina de «3, 2, 1, ¡reproducir!». Simplemente adoptan enfoques diferentes.<br><br>Teleparty es la opción más lógica si buscas un chat integrado o si quieres ver contenido desde un dispositivo móvil. KoalaSync está más orientado a sesiones de visionado en navegadores de escritorio, donde ya te comunicas a través de Discord, Teamspeak u otra aplicación de voz y solo necesitas que la reproducción se mantenga sincronizada.<br><br>La razón principal por la que creé KoalaSync fue la flexibilidad. Está diseñado para funcionar con casi cualquier sitio web que utilice un reproductor de vídeo estándar de navegador, no solo con una lista fija de plataformas. Esto incluye configuraciones autohospedadas como Emby, Jellyfin y Plex, dominios locales o privados y muchas páginas de vídeo convencionales.",
|
||||
@@ -191,7 +208,7 @@
|
||||
"ALT_TELEPARTY_LIMITS_TITLE": "Limitaciones reales",
|
||||
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync no hace milagros ni pretende ocultar sus limitaciones. Actualmente, solo funciona en navegadores de escritorio; no cuenta con chat integrado ni aplicación móvil.<br><br>La compatibilidad también depende del navegador y del reproductor de vídeo utilizado. Aunque KoalaSync debería funcionar con casi cualquier reproductor estándar de navegador, los sitios web pueden modificar sus reproductores, el comportamiento del DRM o las restricciones del navegador en cualquier momento.<br><br>KoalaSync no tiene ninguna relación con Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex ni ninguna otra marca mencionada aquí. Dichos nombres se utilizan únicamente para ilustrar casos de uso habituales.",
|
||||
"ALT_TELEPARTY_SUMMARY_TITLE": "Resumen",
|
||||
"ALT_TELEPARTY_SUMMARY_BODY": "Si buscas chat integrado o compatibilidad con dispositivos móviles, Teleparty podría ser la mejor opción. Si prefieres una solución gratuita y de código abierto para sincronizar navegadores de escritorio que funcione con casi cualquier reproductor de vídeo convencional —incluidos servidores multimedia de alojamiento propio como Emby, Jellyfin y Plex—, KoalaSync es probablemente la alternativa más adecuada.",
|
||||
"ALT_TELEPARTY_SUMMARY_BODY": "Si buscas chat integrado o compatibilidad con dispositivos móviles, Teleparty podría ser la mejor opción. Si prefieres una solución gratuita y de código abierto para sincronizar navegadores de escritorio que funcione con casi cualquier reproductor de vídeo convencional, incluidos servidores multimedia de alojamiento propio como Emby, Jellyfin y Plex, KoalaSync es probablemente la alternativa más adecuada.",
|
||||
"ALT_SCREEN_TITLE": "KoalaSync frente a compartir pantalla: ¿cuándo es mejor la sincronización de vídeo?",
|
||||
"ALT_SCREEN_META_DESC": "Una comparativa práctica entre KoalaSync y la función de compartir pantalla para noches de cine a distancia, analizando aspectos como el control de reproducción, la calidad de vídeo, la privacidad, los subtítulos y la visualización desde el navegador.",
|
||||
"ALT_SCREEN_INTRO": "Compartir pantalla funciona y, sinceramente, a veces es suficiente. Una persona comparte su pantalla, las demás la ven y listo.<br><br>Sin embargo, para sesiones de cine más largas o para ver episodios en grupo, puede resultar molesto. La calidad del vídeo depende de la velocidad de subida, la CPU y el navegador de quien comparte, así como de la compresión que apliquen aplicaciones como Discord, Teamspeak, Zoom u otras plataformas de videollamada. Además, solo una persona tiene el control real de la reproducción.",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "KoalaSync no sustituye a una llamada de voz. Si quieres hablar mientras ves el contenido, puedes seguir usando Discord, Teamspeak, Signal, WhatsApp o cualquier otra herramienta que utilices habitualmente.<br><br>Además, cada participante necesita acceder al vídeo o a la página web desde su propio dispositivo. KoalaSync no elude suscripciones, inicios de sesión, DRM, restricciones regionales ni las normas de las plataformas.<br><br>Actualmente solo funciona en navegadores de escritorio, no incluye funciones de chat o voz, y la compatibilidad depende siempre del navegador y del reproductor de vídeo.",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "Resumen",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "Compartir pantalla es la opción más sencilla cuando buscas algo rápido y no te preocupa demasiado la calidad, la privacidad o el control individual. KoalaSync es una mejor alternativa cuando cada persona debe ver el contenido localmente en su propio navegador y se busca una experiencia que realmente se asemeje a verlo juntos.",
|
||||
"ALT_TRY_KOALASYNC": "Prueba KoalaSync"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "Prueba KoalaSync",
|
||||
"MOCK_HOST_CONTROL_LABEL": "Control del anfitrión",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "Solo yo puedo controlar la reproducción",
|
||||
"LABEL_SELECT_VIDEO": "Seleccionar video",
|
||||
"OPTION_SELECT_TAB": "-- Selecciona una pestaña --",
|
||||
"THEME_TOGGLE_LABEL": "Cambiar modo claro/oscuro",
|
||||
"FAQ_SHOW_ALL": "Mostrar todas las preguntas",
|
||||
"FAQ_HIDE_ALL": "Ocultar preguntas",
|
||||
"SELF_SHOW_SETUP": "Mostrar guía de instalación",
|
||||
"SELF_HIDE_SETUP": "Ocultar guía de instalación",
|
||||
"HERO_TRUST": "Gratis · Código abierto · Sin cuenta",
|
||||
"COMP_MORE_GUIDES": "Comparativa completa y más alternativas"
|
||||
}
|
||||
|
||||
+73
-32
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "fr_FR",
|
||||
"META_TITLE": "KoalaSync | Synchronisez Netflix, YouTube et n'importe quelle vidéo avec vos amis",
|
||||
"META_DESCRIPTION": "Regardez Netflix, YouTube, Twitch et des vidéos HTML5 en synchro avec vos amis. Extension de navigateur gratuite et open-source. Aucune inscription requise.",
|
||||
"SCHEMA_APP_DESC": "Regardez Netflix, YouTube, Twitch et des vidéos HTML5 en synchro avec vos amis. Extension de navigateur gratuite et open-source. Aucune inscription requise.",
|
||||
"OG_TITLE": "KoalaSync | Synchronisez Netflix, Emby, Jellyfin et presque toutes les vidéos avec vos amis",
|
||||
"OG_DESCRIPTION": "Regardez Netflix, Emby, Jellyfin, YouTube, Twitch et presque n'importe quelle vidéo HTML5 en parfaite synchronisation. Extension de navigateur open-source et respectueuse de la vie privée pour Chrome et Firefox.",
|
||||
"TWITTER_TITLE": "KoalaSync | Synchronisez Netflix, Emby, Jellyfin et presque toutes les vidéos avec vos amis – Extension de navigateur",
|
||||
"TWITTER_DESCRIPTION": "Regardez Netflix, Emby, Jellyfin, YouTube, Twitch et presque n'importe quelle vidéo HTML5 en parfaite synchronisation avec vos amis. Extension de navigateur open-source et respectueuse de la vie privée pour Chrome et Firefox.",
|
||||
"META_TITLE": "KoalaSync | Watch party pour Netflix, YouTube et plus",
|
||||
"META_DESCRIPTION": "Organisez une watch party sur Netflix, YouTube, Twitch et toute vidéo HTML5. Extension libre et gratuite pour Chrome et Firefox. Sans compte, sans pistage, sans pub.",
|
||||
"SCHEMA_APP_DESC": "Organisez une watch party sur Netflix, YouTube, Twitch et toute vidéo HTML5. Extension libre et gratuite pour Chrome et Firefox. Sans compte, sans pistage, sans pub.",
|
||||
"OG_TITLE": "KoalaSync | Watch party pour Netflix, Emby, Jellyfin et presque toute vidéo",
|
||||
"OG_DESCRIPTION": "Organisez une watch party sur Netflix, Emby, Jellyfin, YouTube, Twitch et presque toute vidéo HTML5. Open source, privé et sans compte.",
|
||||
"TWITTER_TITLE": "KoalaSync | Watch party pour Netflix, Emby, Jellyfin et presque toute vidéo – Extension",
|
||||
"TWITTER_DESCRIPTION": "Organisez une watch party sur Netflix, Emby, Jellyfin, YouTube, Twitch et presque toute vidéo HTML5. Open source, privé et sans compte.",
|
||||
"NAV_FEATURES": "Fonctionnalités",
|
||||
"NAV_HOW_IT_WORKS": "Comment ça marche",
|
||||
"HERO_TITLE": "Regardez ensemble.<span class='hero-line2'>Synchronisation parfaite.</span>",
|
||||
"HERO_SUBTITLE": "Votre soirée cinéma à distance sans décalage. Pas d'inscription, pas de collecte de données. Partagez simplement un lien et regardez ensemble.",
|
||||
"HERO_TITLE": "Transformez n'importe quelle vidéo en <span class='hero-highlight'>watch party</span>.",
|
||||
"HERO_SUBTITLE": "Votre soirée ciné à distance, sans décalage. Pas de compte, pas de tracking, pas de pub. Partagez un lien et lancez la lecture ensemble.",
|
||||
"HERO_MASCOT_ALT": "Un koala mignon debout et regardant vers le bas les boutons de téléchargement",
|
||||
"DEMO_SYNC": "Synchronisé",
|
||||
"DEMO_HINT": "Démo interactive : lancez la lecture dans un onglet, KoalaSync garde les deux synchronisés.",
|
||||
"DEMO_NO_ROOM": "Pas encore connecté",
|
||||
"DEMO_CREATE_ROOM": "Créer un salon",
|
||||
"DEMO_JOINED": "Salon rejoint",
|
||||
"DEMO_TAB_SELECTED": "Onglet sélectionné",
|
||||
"DEMO_SELECT_PLACEHOLDER": "Choisir un onglet vidéo…",
|
||||
"MOBILE_DEMO_CTA": "Voir comment ça marche",
|
||||
"MOBILE_DEMO_TITLE": "Démo live KoalaSync",
|
||||
"MOBILE_DEMO_CLOSE": "Fermer la démo",
|
||||
"ADD_TO_CHROME": "Ajouter à Chrome",
|
||||
"ADD_TO_FIREFOX": "Ajouter à Firefox",
|
||||
"COMPAT_HEADING": "Fonctionne sur vos plateformes préférées",
|
||||
@@ -35,20 +45,20 @@
|
||||
"USE_CASE_3_DESC": "Comblez la distance et profitez de soirées en tête-à-tête. Vivez chaque rebondissement, riez des mêmes blagues et partagez des moments émotionnels à la milliseconde près.",
|
||||
"WHY_TITLE": "Pourquoi KoalaSync ?",
|
||||
"WHY_SUBTITLE": "Conçu pour une synchronisation fiable, le respect de la vie privée et une configuration facile.",
|
||||
"FEATURE_1_TITLE": "Contrôle total / Synchro instantanée",
|
||||
"FEATURE_1_DESC": "Un joueur met en pause, tout le monde met en pause. Un joueur avance, tout le monde suit. Notre protocole de synchronisation en deux phases coordonne la lecture en temps réel pour tous les participants.",
|
||||
"FEATURE_1_TITLE": "Contrôle de l'hôte et synchronisation",
|
||||
"FEATURE_1_DESC": "Vous décidez qui contrôle la lecture. Activez le contrôle de l'hôte pour être le seul à pouvoir mettre en pause et avancer, ou nommez des co-hôtes.",
|
||||
"FEATURE_2_TITLE": "Binge-watching sans fin",
|
||||
"FEATURE_2_DESC": "Lecture automatique synchronisée. KoalaSync détecte automatiquement les transitions d'épisodes et suspend la lecture jusqu'à ce que tous les participants aient chargé la vidéo suivante.",
|
||||
"FEATURE_3_TITLE": "Aucun compte / Vie privée garantie",
|
||||
"FEATURE_3_DESC": "Pas d'inscription, pas de suivi, pas de stockage de données. Le serveur fonctionne entièrement dans une RAM éphémère et supprime votre salon dès votre départ.",
|
||||
"FEATURE_4_TITLE": "Support HTML5 universel",
|
||||
"FEATURE_4_DESC": "Fonctionne sur YouTube, Twitch, Netflix, Jellyfin, Emby et presque n'importe quelle page web standard contenant un élément vidéo HTML5. Également compatible avec les installations personnalisées. Aucune donnée vidéo n'est envoyée à nos serveurs — seules les métadonnées de synchronisation.",
|
||||
"FEATURE_4_DESC": "Netflix, YouTube, Twitch, Jellyfin, Emby et presque toute page avec une vidéo HTML5. Seuls les signaux de sync quittent le navigateur, jamais la vidéo.",
|
||||
"FEATURE_5_TITLE": "Hébergeable soi-même & prêt pour Docker",
|
||||
"FEATURE_5_DESC": "Nos serveurs officiels sont gratuits et rapides, mais vous pouvez prendre le contrôle total si vous le souhaitez. Lancez votre propre serveur relais privé en quelques secondes via Docker.",
|
||||
"FEATURE_6_TITLE": "Invitations instantanées / Rejoindre en 1 clic",
|
||||
"FEATURE_6_DESC": "Pas d'adresses IP ou de mots de passe à échanger. Partagez un lien d'invitation généré pour permettre à vos amis de rejoindre automatiquement votre salon en un seul clic.",
|
||||
"FEATURE_7_TITLE": "Égalisation du volume / Compresseur audio",
|
||||
"FEATURE_7_DESC": "Fini de jouer avec le bouton du volume. Le compresseur audio intégré booste les dialogues discrets et adoucit les explosions. Fonctionne sur Netflix, Prime Video, Disney+, YouTube et toute vidéo HTML5. Entièrement local — aucune donnée audio ne quitte votre ordinateur.",
|
||||
"FEATURE_7_DESC": "Fini de jouer avec le bouton du volume. Un compresseur intégré rehausse les dialogues discrets et adoucit les explosions, le tout traité localement.",
|
||||
"FEATURE_8_TITLE": "Open Source (MIT License)",
|
||||
"FEATURE_8_DESC": "Tout le code source est publiquement auditable sur GitHub. Pas de boite noire, pas de blob proprietaire - transparence totale du serveur relais a l'extension de navigateur.",
|
||||
"COMP_TITLE": "KoalaSync vs Teleparty",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Politiques de confidentialité et collecte de données de suivi de Teleparty :",
|
||||
"COMP_FOOTNOTE_4": "Fonctionne sur les sites web qui autorisent les injections de scripts dans les balises vidéo HTML5 standard. Les sites avec des politiques de sécurité du contenu (CSP) très strictes, une protection contre la copie DRM ou des conteneurs de lecteurs fortement obscurcis (comme des DOM fantômes complexes) peuvent restreindre le contrôle automatisé ou l'injection.",
|
||||
"STEPS_TITLE": "Pour commencer",
|
||||
"STEP_1_TITLE": "Installer l'extension",
|
||||
"STEP_1_DESC": "Ajoutez KoalaSync à votre navigateur depuis le Chrome Web Store, les modules complémentaires de Firefox, ou téléchargez le dernier fichier ZIP développeur depuis GitHub.",
|
||||
"STEP_1_ILLUS_DESC": "Synchronisateur vidéo respectueux de la vie privée",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Télécharger pour Chrome",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Télécharger pour Firefox",
|
||||
"STEP_1_ILLUS_ACTIVE": "Extension active",
|
||||
"STEP_1_ILLUS_READY": "Prêt à regarder ensemble !",
|
||||
"STEP_2_TITLE": "Créer un salon",
|
||||
"STEP_2_DESC": "Ouvrez la fenêtre de l'extension et cliquez sur '+ Créer un nouveau salon'. KoalaSync génère automatiquement un identifiant de salon et un mot de passe sécurisés, s'y connecte et copie le lien d'invitation dans votre presse-papiers.",
|
||||
"STEP_1_TITLE": "Créer et partager un salon",
|
||||
"STEP_1_DESC": "Ouvrez l'extension, créez un salon en un clic et partagez le lien avec vos amis. Ils rejoindront instantanément via le lien d'invitation.",
|
||||
"STEP_1_ILLUS_DESC": "Choisir un onglet à lier",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Lier l'onglet YouTube",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Lier l'onglet Netflix",
|
||||
"STEP_1_ILLUS_ACTIVE": "Onglet lié",
|
||||
"STEP_1_ILLUS_READY": "Prêt à regarder en synchronisation !",
|
||||
"STEP_2_TITLE": "Sélectionner l'onglet vidéo",
|
||||
"STEP_2_DESC": "Allez sur l'onglet de votre vidéo et sélectionnez-le dans le menu de l'extension. KoalaSync liera vos lecteurs automatiquement.",
|
||||
"STEP_2_ILLUS_ROOM": "Salon",
|
||||
"STEP_2_ILLUS_SYNC": "Synchro",
|
||||
"STEP_2_ILLUS_SETTINGS": "Options",
|
||||
"STEP_2_ILLUS_CREATE": "+ Créer un nouveau salon",
|
||||
"STEP_2_ILLUS_MANUAL": "Connexion manuelle / Avancé",
|
||||
"STEP_2_ILLUS_COPIED": "Lien d'invitation copié !",
|
||||
"STEP_3_TITLE": "Partager & Synchroniser",
|
||||
"STEP_3_DESC": "Envoyez le lien d'invitation à vos amis. Dès qu'ils rejoignent, sélectionnez votre onglet vidéo et profitez de la lecture synchronisée.",
|
||||
"STEP_3_TITLE": "Profiter de la synchronisation",
|
||||
"STEP_3_DESC": "Lancez la lecture. Si quelqu'un met en pause, avance ou joue, la vidéo de tout le monde reste synchronisée en temps réel sans décalage.",
|
||||
"STEP_3_ILLUS_IN_SYNC": "SYNCHRONISÉ",
|
||||
"SELF_TITLE": "Pour l'auto-hébergement",
|
||||
"SELF_SUBTITLE": "Vous ne faites pas confiance à notre serveur ? Conservez votre souveraineté totale sur les données. Déployez votre propre serveur relais en quelques minutes.",
|
||||
@@ -116,21 +126,21 @@
|
||||
"FAQ_TITLE": "Foire aux questions",
|
||||
"FAQ_SUBTITLE": "Tout ce que vous devez savoir pour regarder des vidéos en parfaite synchronisation.",
|
||||
"FAQ_Q1": "Puis-je regarder Netflix, Emby ou Jellyfin en synchronisation avec des amis ?",
|
||||
"FAQ_A1": "Oui ! KoalaSync fonctionne avec Netflix, YouTube, Twitch et tout site avec un lecteur vidéo HTML5 — y compris Emby, Jellyfin et les serveurs multimédia auto-hébergés. Installez l'extension, créez un salon, partagez le lien et regardez en parfaite synchronisation.",
|
||||
"FAQ_A1": "Oui ! KoalaSync fonctionne avec Netflix, YouTube, Twitch et tout site avec un lecteur vidéo HTML5, y compris Emby, Jellyfin et les serveurs multimédia auto-hébergés. Installez l'extension, créez un salon, partagez le lien et regardez en parfaite synchronisation.",
|
||||
"FAQ_Q_VIDEO_ACCESS": "KoalaSync diffuse-t-il la vidéo à mes amis ?",
|
||||
"FAQ_A_VIDEO_ACCESS": "Non. KoalaSync ne diffuse, n’héberge, ne partage et ne contourne aucun accès aux contenus vidéo. Chaque personne regarde la vidéo localement dans son propre navigateur et doit disposer de son propre accès au site ou au service de streaming, par exemple son propre compte Netflix. KoalaSync synchronise uniquement les actions de lecture comme lecture, pause et recherche dans la vidéo.",
|
||||
"FAQ_Q2": "KoalaSync est-il une alternative gratuite à Teleparty ?",
|
||||
"FAQ_A2": "100 % gratuit et open source sous licence MIT. Aucun niveau premium, aucune fonctionnalité verrouillée, aucune publicité. Contrairement à Teleparty, le serveur relay de KoalaSync fonctionne uniquement en RAM — sans collecte de données ni comptes utilisateur.",
|
||||
"FAQ_A2": "100 % gratuit et open source sous licence MIT. Aucun niveau premium, aucune fonctionnalité verrouillée, aucune publicité. Contrairement à Teleparty, le serveur relay de KoalaSync fonctionne uniquement en RAM, sans collecte de données ni comptes utilisateur.",
|
||||
"FAQ_Q3": "KoalaSync fonctionne-t-il avec Emby, Jellyfin et les serveurs multimédia auto-hébergés ?",
|
||||
"FAQ_A3": "Oui. KoalaSync détecte tout élément vidéo HTML5 standard et fonctionne parfaitement avec Emby, Jellyfin, Plex et toute configuration de streaming personnalisée. Vous pouvez également auto-héberger le serveur relay via Docker.",
|
||||
"FAQ_Q4": "Quels navigateurs et plateformes prennent en charge KoalaSync ?",
|
||||
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave et tous les navigateurs basés sur Chromium. Installez-le depuis le Chrome Web Store ou les modules complémentaires Firefox.",
|
||||
"FAQ_Q5": "Tous mes amis doivent-ils installer l'extension ?",
|
||||
"FAQ_A5": "Oui, chaque participant a besoin de KoalaSync — mais sans inscription ni création de compte. Cliquez simplement sur le lien d'invitation, installez l'extension si nécessaire et vous rejoignez automatiquement le salon.",
|
||||
"FAQ_A5": "Oui, chaque participant a besoin de KoalaSync, mais sans inscription ni création de compte. Cliquez simplement sur le lien d'invitation, installez l'extension si nécessaire et vous rejoignez automatiquement le salon.",
|
||||
"FAQ_Q6": "Mes données sont-elles en sécurité ? Et si je ne fais pas confiance au serveur officiel ?",
|
||||
"FAQ_A6": "Totalement sûr. Le serveur relay de KoalaSync fonctionne uniquement en RAM — toutes les données de session sont supprimées dès que vous quittez le salon. Aucune base de données, aucun journal, aucune analyse, aucun compte. Si vous préférez un contrôle total : hébergez votre propre serveur relay privé via Docker en quelques minutes. Le code source est entièrement open source et vérifiable sur GitHub.",
|
||||
"FAQ_A6": "Totalement sûr. Le serveur relay de KoalaSync fonctionne uniquement en RAM, toutes les données de session sont supprimées dès que vous quittez le salon. Aucune base de données, aucun journal, aucune analyse, aucun compte. Si vous préférez un contrôle total : hébergez votre propre serveur relay privé via Docker en quelques minutes. Le code source est entièrement open source et vérifiable sur GitHub.",
|
||||
"FAQ_Q7": "Puis-je atténuer les dialogues discrets et les explosions fortes en regardant des vidéos dans le navigateur ?",
|
||||
"FAQ_A7": "Oui. KoalaSync 2.2.0 inclut un compresseur audio intégré qui réduit l'écart entre les parties silencieuses et bruyantes de votre vidéo. Il fonctionne localement via l'API Web Audio — aucune donnée audio ne quitte votre ordinateur. Ouvrez le popup, créez un salon, allez dans Paramètres → Audio Processing, activez le compresseur et choisissez un préréglage. Fonctionne sur Netflix, Prime Video, Disney+, YouTube et tout site vidéo HTML5.",
|
||||
"FAQ_A7": "Oui. KoalaSync 2.2.0 inclut un compresseur audio intégré qui réduit l'écart entre les parties silencieuses et bruyantes de votre vidéo. Il fonctionne localement via l'API Web Audio, aucune donnée audio ne quitte votre ordinateur. Ouvrez le popup, créez un salon, allez dans Paramètres → Audio Processing, activez le compresseur et choisissez un préréglage. Fonctionne sur Netflix, Prime Video, Disney+, YouTube et tout site vidéo HTML5.",
|
||||
"HOWTO_TITLE": "Comment synchroniser des vidéos avec des amis avec KoalaSync",
|
||||
"HOWTO_DESC": "Regardez des vidéos en parfaite synchronisation avec vos amis sur YouTube, Netflix, Twitch et plus. Sans inscription, sans pistage.",
|
||||
"HOWTO_STEP_1_NAME": "Installer l'extension",
|
||||
@@ -157,7 +167,7 @@
|
||||
"MOCK_12": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"14\" height=\"14\" aria-hidden=\"true\"><rect x=\"6\" y=\"4\" width=\"4\" height=\"16\"/><rect x=\"14\" y=\"4\" width=\"4\" height=\"16\"/></svg> Pause",
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> SYNC",
|
||||
"MOCK_14": "Dernière activité",
|
||||
"MOCK_15": "En attente de 1 participant (KoalaPC)...",
|
||||
"MOCK_15": "En attente de 1 participant (🐶 HappyDog)...",
|
||||
"MOCK_16": "Passer & lire",
|
||||
"MOCK_17": "Votre pseudo",
|
||||
"MOCK_18": "Masquer les onglets inutiles",
|
||||
@@ -179,6 +189,13 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync n'est pas affilié à Netflix, Disney+, Amazon, YouTube, Twitch ou toute autre plateforme de streaming. Toutes les marques déposées appartiennent à leurs détenteurs respectifs.",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_INDEX_TITLE": "KoalaSync - Alternatives et comparatifs",
|
||||
"ALT_INDEX_META_DESC": "Explorez des comparatifs honnêtes et des guides détaillés entre KoalaSync et d’autres extensions de watch party et solutions de streaming.",
|
||||
"ALT_INDEX_HEADING": "Guides et comparatifs",
|
||||
"ALT_INDEX_INTRO": "Comment KoalaSync se compare-t-il aux autres outils de watch party et solutions de streaming ? Lisez nos comparatifs honnêtes et techniques pour trouver la meilleure option pour vos soirées cinéma à distance.",
|
||||
"ALT_INDEX_READ_COMPARISON": "Lire le comparatif →",
|
||||
"ALT_INDEX_READ_GUIDE": "Lire le guide →",
|
||||
"ALT_INDEX_FOOTER_LINK": "Guides et comparatifs",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync vs Teleparty : quel outil de visionnage en groupe choisir ?",
|
||||
"ALT_TELEPARTY_META_DESC": "Une comparaison pratique entre KoalaSync et Teleparty pour le visionnage en groupe : chat intégré, compatibilité mobile, synchronisation vidéo via navigateur, serveurs multimédias auto-hébergés et nature open-source.",
|
||||
"ALT_TELEPARTY_INTRO": "KoalaSync et Teleparty répondent tous deux au même problème agaçant : regarder quelque chose ensemble sans avoir à passer par le rituel du « 3, 2, 1, lecture ». Ils adoptent simplement des approches différentes.<br><br>Teleparty est la solution idéale si vous souhaitez un chat intégré ou si vous voulez regarder du contenu sur mobile. KoalaSync est davantage conçu pour le visionnage sur navigateur (PC/Mac) lorsque vous communiquez déjà via Discord, Teamspeak ou un autre service d'appel vocal, et que vous voulez simplement que la lecture reste synchronisée.<br><br>La raison principale pour laquelle j'ai créé KoalaSync est la flexibilité. L'outil est conçu pour fonctionner avec presque tous les sites web utilisant un lecteur vidéo standard, et non avec une liste fixe de plateformes. Cela inclut les installations auto-hébergées comme Emby, Jellyfin et Plex, les domaines locaux ou privés, ainsi que de nombreuses pages vidéo classiques sur navigateur.",
|
||||
@@ -191,7 +208,7 @@
|
||||
"ALT_TELEPARTY_LIMITS_TITLE": "Limites en toute transparence",
|
||||
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync n'est pas magique et ne cherche pas à dissimuler ses limites. Actuellement, il ne fonctionne que sur les navigateurs de bureau. Il ne dispose ni de messagerie intégrée ni d'application mobile.<br><br>La compatibilité dépend également du navigateur et du lecteur vidéo. KoalaSync devrait fonctionner avec presque tous les lecteurs vidéo standard, mais les sites web peuvent modifier leurs lecteurs, la gestion des DRM ou les restrictions du navigateur à tout moment.<br><br>KoalaSync n'est affilié ni à Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex, ni à aucune autre marque mentionnée ici. Ces noms sont utilisés uniquement pour illustrer des cas d'utilisation courants.",
|
||||
"ALT_TELEPARTY_SUMMARY_TITLE": "Résumé",
|
||||
"ALT_TELEPARTY_SUMMARY_BODY": "Si vous recherchez une fonction de chat intégrée ou une prise en charge mobile, Teleparty pourrait être la meilleure option. Si vous préférez une solution gratuite et open source de synchronisation via navigateur (sur ordinateur) compatible avec presque tous les lecteurs vidéo classiques — y compris les serveurs multimédias auto-hébergés comme Emby, Jellyfin et Plex —, alors KoalaSync est probablement le choix idéal.",
|
||||
"ALT_TELEPARTY_SUMMARY_BODY": "Si vous recherchez une fonction de chat intégrée ou une prise en charge mobile, Teleparty pourrait être la meilleure option. Si vous préférez une solution gratuite et open source de synchronisation via navigateur (sur ordinateur) compatible avec presque tous les lecteurs vidéo classiques, y compris les serveurs multimédias auto-hébergés comme Emby, Jellyfin et Plex, alors KoalaSync est probablement le choix idéal.",
|
||||
"ALT_SCREEN_TITLE": "KoalaSync vs partage d'écran : quand la synchronisation vidéo est-elle préférable ?",
|
||||
"ALT_SCREEN_META_DESC": "Une comparaison pratique entre KoalaSync et le partage d'écran pour les soirées cinéma à distance : contrôle de la lecture, qualité vidéo, confidentialité, sous-titres et visionnage via navigateur.",
|
||||
"ALT_SCREEN_INTRO": "Le partage d'écran fonctionne et, honnêtement, cela suffit parfois. Une personne partage son écran, les autres regardent, et le tour est joué.<br><br>Cependant, pour des soirées cinéma plus longues ou pour regarder des épisodes ensemble, cela peut devenir contraignant. La qualité vidéo dépend de la vitesse d'envoi (upload), du processeur et du navigateur de la personne qui partage, ainsi que de la compression appliquée par Discord, Teamspeak, Zoom ou toute autre application d'appel. De plus, une seule personne contrôle réellement la lecture.",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "KoalaSync ne remplace pas un appel vocal. Si vous souhaitez discuter tout en regardant, vous continuez à utiliser Discord, Teamspeak, Signal, WhatsApp ou tout autre outil habituel.<br><br>Chacun doit également pouvoir accéder à la vidéo ou à la page depuis son propre appareil. KoalaSync ne contourne ni les abonnements, ni les identifiants, ni les DRM, ni les restrictions géographiques ou les règles des plateformes.<br><br>L'outil fonctionne actuellement uniquement sur les navigateurs de bureau, ne dispose pas de fonction de chat ou de voix intégrée, et la compatibilité dépend toujours du navigateur et du lecteur vidéo utilisé.",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "Résumé",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "Le partage d'écran est la solution la plus simple si vous cherchez une méthode rapide sans accorder une grande importance à la qualité, à la confidentialité ou au contrôle individuel. KoalaSync est plus adapté lorsque chaque participant doit visionner le contenu localement dans son propre navigateur, pour une expérience se rapprochant davantage d'un véritable visionnage en commun.",
|
||||
"ALT_TRY_KOALASYNC": "Essayer KoalaSync"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "Essayer KoalaSync",
|
||||
"MOCK_HOST_CONTROL_LABEL": "Contrôle de l'hôte",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "Seul moi peut contrôler la lecture",
|
||||
"LABEL_SELECT_VIDEO": "Choisir une vidéo",
|
||||
"OPTION_SELECT_TAB": "-- Choisir un onglet --",
|
||||
"THEME_TOGGLE_LABEL": "Basculer mode clair/sombre",
|
||||
"FAQ_SHOW_ALL": "Afficher toutes les questions",
|
||||
"FAQ_HIDE_ALL": "Masquer les questions",
|
||||
"SELF_SHOW_SETUP": "Afficher le guide d'installation",
|
||||
"SELF_HIDE_SETUP": "Masquer le guide d'installation",
|
||||
"HERO_TRUST": "Gratuit · Open source · Sans compte",
|
||||
"COMP_MORE_GUIDES": "Comparatif complet et autres alternatives"
|
||||
}
|
||||
|
||||
+68
-27
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "it_IT",
|
||||
"META_TITLE": "KoalaSync | Sincronizza Netflix, YouTube e qualsiasi video con i tuoi amici",
|
||||
"META_DESCRIPTION": "Guarda Netflix, YouTube, Twitch e qualsiasi video HTML5 in perfetta sincronia. Estensione gratuita, open source e senza registrazione.",
|
||||
"SCHEMA_APP_DESC": "Guarda Netflix, YouTube, Twitch e qualsiasi video HTML5 in perfetta sincronia. Estensione gratuita, open source e senza registrazione.",
|
||||
"OG_TITLE": "KoalaSync | Sincronizza Netflix, Emby, Jellyfin e video web con i tuoi amici",
|
||||
"OG_DESCRIPTION": "Guarda video in perfetta sincronia con i tuoi amici. Estensione open source per Chrome e Firefox incentrata sulla privacy.",
|
||||
"TWITTER_TITLE": "KoalaSync | Sincronizza video in streaming con i tuoi amici",
|
||||
"TWITTER_DESCRIPTION": "Guarda video insieme ai tuoi amici in perfetta sincronia. Open source e rispettoso della privacy.",
|
||||
"META_TITLE": "KoalaSync | Watch party per Netflix, YouTube e ogni video",
|
||||
"META_DESCRIPTION": "Watch party su Netflix, YouTube, Twitch e qualsiasi video HTML5. Estensione gratuita e open source per Chrome e Firefox. Senza account, tracking né pubblicità.",
|
||||
"SCHEMA_APP_DESC": "Watch party su Netflix, YouTube, Twitch e qualsiasi video HTML5. Estensione gratuita e open source per Chrome e Firefox. Senza account, tracking né pubblicità.",
|
||||
"OG_TITLE": "KoalaSync | Watch party per Netflix, Emby, Jellyfin e quasi ogni video",
|
||||
"OG_DESCRIPTION": "Avvia una watch party su Netflix, Emby, Jellyfin, YouTube, Twitch e quasi ogni video HTML5. Open source, privata e senza account.",
|
||||
"TWITTER_TITLE": "KoalaSync | Watch party per Netflix, Emby, Jellyfin e quasi ogni video – Estensione",
|
||||
"TWITTER_DESCRIPTION": "Avvia una watch party su Netflix, Emby, Jellyfin, YouTube, Twitch e quasi ogni video HTML5. Open source, privata e senza account.",
|
||||
"NAV_FEATURES": "Funzionalità",
|
||||
"NAV_HOW_IT_WORKS": "Come Funziona",
|
||||
"HERO_TITLE": "Guardate Insieme.<span class='hero-line2'>Sincronia Perfetta.</span>",
|
||||
"HERO_SUBTITLE": "La tua serata cinema a distanza senza ritardi. Nessun account, nessun dato raccolto. Condividi un link e guardate insieme.",
|
||||
"HERO_TITLE": "Trasforma qualsiasi video in una <span class='hero-highlight'>watch party</span>.",
|
||||
"HERO_SUBTITLE": "La tua serata film a distanza, senza lag. Niente account, niente tracciamento, niente pubblicità. Condividi un link e premete play insieme.",
|
||||
"HERO_MASCOT_ALT": "Un simpatico koala che guarda i pulsanti di download",
|
||||
"DEMO_SYNC": "Sincronizzato",
|
||||
"DEMO_HINT": "Demo dal vivo: premi play in una delle schede e KoalaSync le mantiene sincronizzate.",
|
||||
"DEMO_NO_ROOM": "Non ancora connesso",
|
||||
"DEMO_CREATE_ROOM": "Crea stanza",
|
||||
"DEMO_JOINED": "Entrato nella stanza",
|
||||
"DEMO_TAB_SELECTED": "Scheda selezionata",
|
||||
"DEMO_SELECT_PLACEHOLDER": "Scegli una scheda video…",
|
||||
"MOBILE_DEMO_CTA": "Guarda come funziona",
|
||||
"MOBILE_DEMO_TITLE": "Demo live di KoalaSync",
|
||||
"MOBILE_DEMO_CLOSE": "Chiudi demo",
|
||||
"ADD_TO_CHROME": "Aggiungi a Chrome",
|
||||
"ADD_TO_FIREFOX": "Aggiungi a Firefox",
|
||||
"COMPAT_HEADING": "Compatibile con le tue piattaforme preferite",
|
||||
@@ -35,20 +45,20 @@
|
||||
"USE_CASE_3_DESC": "Accorcia le distanze e goditi serate romantiche. Vivi ogni emozione e ridi alle stesse battute nello stesso identico istante.",
|
||||
"WHY_TITLE": "Perché KoalaSync?",
|
||||
"WHY_SUBTITLE": "Sincronizzazione affidabile, massima privacy e configurazione immediata.",
|
||||
"FEATURE_1_TITLE": "Controllo Totale / Sincro Istantanea",
|
||||
"FEATURE_1_DESC": "Se uno mette in pausa, tutti si fermano. Se uno salta in avanti, gli altri lo seguono. Tutto in tempo reale.",
|
||||
"FEATURE_1_TITLE": "Controllo dell'host e sincronizzazione",
|
||||
"FEATURE_1_DESC": "Decidi tu chi controlla la riproduzione. Attiva il controllo host per consentire solo a te di mettere in pausa e avanzare, o nomina co-host.",
|
||||
"FEATURE_2_TITLE": "Maratone senza interruzioni",
|
||||
"FEATURE_2_DESC": "Riproduzione automatica sincronizzata. KoalaSync attende che tutti abbiano caricato l'episodio successivo prima di farlo partire.",
|
||||
"FEATURE_3_TITLE": "Senza Account / Privacy Totale",
|
||||
"FEATURE_3_DESC": "Niente registrazioni o tracciamenti. Il server non salva nulla e cancella la tua stanza appena te ne vai.",
|
||||
"FEATURE_4_TITLE": "Supporto HTML5 Universale",
|
||||
"FEATURE_4_DESC": "Funziona su YouTube, Twitch, Netflix, Jellyfin, Emby e quasi ogni pagina web. Nessun dato video viene inviato ai nostri server.",
|
||||
"FEATURE_4_DESC": "Netflix, YouTube, Twitch, Jellyfin, Emby e quasi ogni pagina con video HTML5. Escono solo i segnali di sync, mai il video.",
|
||||
"FEATURE_5_TITLE": "Ospitabile in Proprio (Docker)",
|
||||
"FEATURE_5_DESC": "I nostri server sono veloci, ma puoi gestire il tuo server privato in pochi secondi tramite Docker.",
|
||||
"FEATURE_6_TITLE": "Inviti Rapidi / Un clic per entrare",
|
||||
"FEATURE_6_DESC": "Niente password complicate. Condividi il link generato e i tuoi amici entreranno automaticamente con un solo clic.",
|
||||
"FEATURE_7_TITLE": "Volume Bilanciato / Compressore Audio",
|
||||
"FEATURE_7_DESC": "Basta regolare il volume ogni minuto. Il compressore integrato esalta le voci e attenua i rumori forti. Tutto in locale.",
|
||||
"FEATURE_7_DESC": "Basta armeggiare col volume. Un compressore integrato alza i dialoghi sommessi e attenua le esplosioni forti, tutto elaborato localmente nel browser.",
|
||||
"FEATURE_8_TITLE": "Open Source (Licenza MIT)",
|
||||
"FEATURE_8_DESC": "Tutto il codice è pubblico su GitHub. Trasparenza totale, senza scatole nere o software proprietario.",
|
||||
"COMP_TITLE": "KoalaSync vs Teleparty",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Politiche sulla privacy di Teleparty:",
|
||||
"COMP_FOOTNOTE_4": "Funziona su siti che permettono l'uso di script nei tag video HTML5. Alcuni siti con protezioni DRM molto rigide potrebbero limitare il controllo automatico.",
|
||||
"STEPS_TITLE": "Inizia ora",
|
||||
"STEP_1_TITLE": "Installa l'Estensione",
|
||||
"STEP_1_DESC": "Aggiungi KoalaSync al tuo browser dal Chrome Web Store o dai componenti aggiuntivi di Firefox.",
|
||||
"STEP_1_ILLUS_DESC": "Sincronizzatore video privato",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Scarica per Chrome",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Scarica per Firefox",
|
||||
"STEP_1_ILLUS_ACTIVE": "Estensione Attiva",
|
||||
"STEP_1_ILLUS_READY": "Pronti a guardare!",
|
||||
"STEP_2_TITLE": "Crea una Stanza",
|
||||
"STEP_2_DESC": "Apri l'estensione e clicca su '+ Crea Nuova Stanza'. Verrà generato un link di invito automaticamente.",
|
||||
"STEP_1_TITLE": "Creare e condividere la stanza",
|
||||
"STEP_1_DESC": "Apri l'estensione, crea una stanza con un clic e condividi il link con i tuoi amici. Si uniranno all'istante tramite il link di invito.",
|
||||
"STEP_1_ILLUS_DESC": "Scegli una scheda da collegare",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Collega scheda YouTube",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Collega scheda Netflix",
|
||||
"STEP_1_ILLUS_ACTIVE": "Scheda collegata",
|
||||
"STEP_1_ILLUS_READY": "Pronto per guardare in sincro !",
|
||||
"STEP_2_TITLE": "Selezionare la scheda del video",
|
||||
"STEP_2_DESC": "Vai alla scheda del tuo video e selezionala nel menu dell'estensione. KoalaSync collegherà i vostri lettori in modo automatico.",
|
||||
"STEP_2_ILLUS_ROOM": "Stanza",
|
||||
"STEP_2_ILLUS_SYNC": "Sincro",
|
||||
"STEP_2_ILLUS_SETTINGS": "Ajustes",
|
||||
"STEP_2_ILLUS_CREATE": "+ Crea Nuova Stanza",
|
||||
"STEP_2_ILLUS_MANUAL": "Manuale / Avanzato",
|
||||
"STEP_2_ILLUS_COPIED": "Link copiato!",
|
||||
"STEP_3_TITLE": "Condividi e Guarda",
|
||||
"STEP_3_DESC": "Invia il link ai tuoi amici. Una volta connessi, scegli il video e goditi la riproduzione sincronizzata.",
|
||||
"STEP_3_TITLE": "Godersi la sincronizzazione",
|
||||
"STEP_3_DESC": "Avvia la riproduzione. Quando qualcuno mette in pausa, avanza o riproduce, il video di tutti rimarrà sincronizzato in tempo reale senza ritardi.",
|
||||
"STEP_3_ILLUS_IN_SYNC": "SINCRONIZZATI",
|
||||
"SELF_TITLE": "Per utenti esperti",
|
||||
"SELF_SUBTITLE": "Preferisci gestire i tuoi dati? Avvia il tuo server privato in pochi minuti.",
|
||||
@@ -158,7 +168,7 @@
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> SYNC",
|
||||
"MOCK_14": "Vai dagli altri",
|
||||
"MOCK_15": "Ultima attività",
|
||||
"MOCK_16": "In attesa di un amico (KoalaPC)...",
|
||||
"MOCK_16": "In attesa di un amico (🐶 HappyDog)...",
|
||||
"MOCK_17": "Salta l'attesa",
|
||||
"MOCK_18": "Tuo Nome",
|
||||
"MOCK_19": "Nascondi schede",
|
||||
@@ -179,6 +189,13 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync non è affiliato a Netflix, YouTube o altre piattaforme. I marchi appartengono ai rispettivi proprietari.",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_INDEX_TITLE": "KoalaSync - Alternative e confronti",
|
||||
"ALT_INDEX_META_DESC": "Scopri confronti onesti e guide dettagliate tra KoalaSync e altre estensioni per watch party e soluzioni di streaming.",
|
||||
"ALT_INDEX_HEADING": "Guide e confronti",
|
||||
"ALT_INDEX_INTRO": "Come si confronta KoalaSync con altri strumenti per watch party e soluzioni di streaming? Leggi i nostri confronti onesti e tecnici per trovare l’opzione migliore per le tue serate film a distanza.",
|
||||
"ALT_INDEX_READ_COMPARISON": "Leggi il confronto →",
|
||||
"ALT_INDEX_READ_GUIDE": "Leggi la guida →",
|
||||
"ALT_INDEX_FOOTER_LINK": "Guide e confronti",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync vs Teleparty: quale strumento scegliere per le watch party?",
|
||||
"ALT_TELEPARTY_META_DESC": "Un confronto pratico tra KoalaSync e Teleparty per le watch party: chat integrata, supporto mobile, sincronizzazione video nel browser, server multimediali self-hosted e natura open-source.",
|
||||
"ALT_TELEPARTY_INTRO": "Sia KoalaSync che Teleparty risolvono lo stesso fastidioso problema: guardare qualcosa insieme senza dover ricorrere alla classica procedura del '3, 2, 1, via'. Semplicemente, adottano approcci diversi.<br><br>Teleparty è la scelta ideale se cerchi una chat integrata o se vuoi guardare i contenuti da dispositivo mobile. KoalaSync è più indicato per le watch party su browser desktop, quando stai già comunicando tramite Discord, Teamspeak o un'altra chiamata vocale e vuoi semplicemente mantenere sincronizzata la riproduzione.<br><br>Il motivo principale per cui ho creato KoalaSync è la flessibilità. È progettato per funzionare con quasi tutti i siti web che utilizzano un normale lettore video del browser, non solo con una lista predefinita di piattaforme. Ciò include configurazioni self-hosted come Emby, Jellyfin e Plex, domini locali o privati e molte pagine web che ospitano video.",
|
||||
@@ -191,7 +208,7 @@
|
||||
"ALT_TELEPARTY_LIMITS_TITLE": "Limiti dichiarati apertamente",
|
||||
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync non fa miracoli e non cerca di nascondere i propri limiti. Attualmente funziona solo su browser desktop; non dispone di chat integrata né di un'app per dispositivi mobili.<br><br>La compatibilità dipende anche dal browser e dal lettore video utilizzato. KoalaSync dovrebbe funzionare con quasi tutti i normali lettori video dei browser, ma i siti web possono modificare in qualsiasi momento i propri lettori, la gestione dei DRM o le restrizioni del browser.<br><br>KoalaSync non è affiliato a Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex o qualsiasi altro marchio qui menzionato. I nomi sono utilizzati esclusivamente per illustrare i tipici scenari di utilizzo.",
|
||||
"ALT_TELEPARTY_SUMMARY_TITLE": "Riepilogo",
|
||||
"ALT_TELEPARTY_SUMMARY_BODY": "Se cerchi una chat integrata o il supporto per dispositivi mobili, Teleparty potrebbe essere la scelta più adatta. Se invece preferisci una soluzione gratuita e open-source per la sincronizzazione tra browser desktop, compatibile con quasi tutti i comuni lettori video — inclusi i media server self-hosted come Emby, Jellyfin e Plex — KoalaSync è probabilmente l'opzione migliore.",
|
||||
"ALT_TELEPARTY_SUMMARY_BODY": "Se cerchi una chat integrata o il supporto per dispositivi mobili, Teleparty potrebbe essere la scelta più adatta. Se invece preferisci una soluzione gratuita e open-source per la sincronizzazione tra browser desktop, compatibile con quasi tutti i comuni lettori video, inclusi i media server self-hosted come Emby, Jellyfin e Plex. KoalaSync è probabilmente l'opzione migliore.",
|
||||
"ALT_SCREEN_TITLE": "KoalaSync vs condivisione schermo: quando è meglio la sincronizzazione video?",
|
||||
"ALT_SCREEN_META_DESC": "Un confronto pratico tra KoalaSync e la condivisione dello schermo per serate film a distanza, analizzando controllo della riproduzione, qualità video, privacy, sottotitoli e visione tramite browser.",
|
||||
"ALT_SCREEN_INTRO": "La condivisione dello schermo funziona e, a dire il vero, a volte è sufficiente. Una persona condivide il proprio schermo, le altre guardano e il gioco è fatto.<br><br>Tuttavia, per serate film più lunghe o per guardare insieme degli episodi, può diventare scomoda. La qualità video dipende dalla velocità di upload, dalla CPU e dal browser di chi condivide, oltre che dalla compressione applicata da app come Discord, Teamspeak, Zoom o altre piattaforme di chiamata. Inoltre, solo una persona ha il controllo effettivo della riproduzione.",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "KoalaSync non sostituisce una chiamata vocale. Se volete parlare mentre guardate il contenuto, potete continuare a usare Discord, Teamspeak, Signal, WhatsApp o qualsiasi altro strumento abituale.<br><br>Ciascun partecipante deve inoltre poter accedere al video o alla pagina dal proprio dispositivo. KoalaSync non aggira abbonamenti, accessi, DRM, restrizioni geografiche o regole delle piattaforme.<br><br>Al momento è utilizzabile solo tramite browser desktop, non dispone di funzioni di chat o voce integrate e la compatibilità dipende sempre dal browser e dal lettore video.",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "Riepilogo",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "La condivisione dello schermo è l'opzione più semplice quando si cerca una soluzione rapida e non si dà molta importanza alla qualità, alla privacy o al controllo individuale. KoalaSync è la scelta ideale quando tutti devono guardare il contenuto localmente sul proprio browser, offrendo un'esperienza che ricorda più da vicino la visione condivisa reale.",
|
||||
"ALT_TRY_KOALASYNC": "Prova KoalaSync"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "Prova KoalaSync",
|
||||
"MOCK_HOST_CONTROL_LABEL": "Controllo host",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "Solo io posso controllare la riproduzione",
|
||||
"LABEL_SELECT_VIDEO": "Seleziona Video",
|
||||
"OPTION_SELECT_TAB": "-- Seleziona una Scheda --",
|
||||
"THEME_TOGGLE_LABEL": "Attiva/disattiva tema chiaro/scuro",
|
||||
"FAQ_SHOW_ALL": "Mostra tutte le domande",
|
||||
"FAQ_HIDE_ALL": "Nascondi le domande",
|
||||
"SELF_SHOW_SETUP": "Mostra la guida all'installazione",
|
||||
"SELF_HIDE_SETUP": "Nascondi la guida",
|
||||
"HERO_TRUST": "Gratuito · Open source · Senza account",
|
||||
"COMP_MORE_GUIDES": "Confronto completo e altre alternative"
|
||||
}
|
||||
|
||||
+68
-27
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "ja_JP",
|
||||
"META_TITLE": "KoalaSync | Netflix、YouTube、あらゆる動画を友達と同期視聴",
|
||||
"META_DESCRIPTION": "Netflix、YouTube、Twitch、およびあらゆるHTML5動画を友達と完全に同期して視聴。ChromeおよびFirefox用の無料のオープンソースブラウザ拡張機能。登録不要。",
|
||||
"SCHEMA_APP_DESC": "Netflix、YouTube、Twitch、およびあらゆるHTML5動画を友達と完全に同期して視聴。ChromeおよびFirefox用の無料のオープンソースブラウザ拡張機能。登録不要。",
|
||||
"OG_TITLE": "KoalaSync | Netflix、Emby、Jellyfin、ほぼすべての動画を友達と同期",
|
||||
"OG_DESCRIPTION": "Netflix、Emby、Jellyfin、YouTube、Twitch、ほぼすべてのHTML5動画を完全に同期して視聴。ChromeおよびFirefox用のプライバシー優先のオープンソースブラウザ拡張機能。",
|
||||
"TWITTER_TITLE": "KoalaSync | Netflix、Emby、Jellyfin、ほぼすべての動画を友達と同期 – ブラウザ拡張機能",
|
||||
"TWITTER_DESCRIPTION": "Netflix、Emby、Jellyfin、YouTube、Twitch、ほぼすべてのHTML5動画を友達と完全に同期して視聴。ChromeおよびFirefox用のプライバシー優先のオープンソースブラウザ拡張機能。",
|
||||
"META_TITLE": "KoalaSync | Netflix・YouTube対応ウォッチパーティー拡張機能",
|
||||
"META_DESCRIPTION": "Netflix、YouTube、TwitchなどあらゆるHTML5動画でウォッチパーティー。Chrome・Firefox向けの無料オープンソース拡張機能。アカウント不要、トラッキングなし、広告なし。",
|
||||
"SCHEMA_APP_DESC": "Netflix、YouTube、TwitchなどあらゆるHTML5動画でウォッチパーティー。Chrome・Firefox向けの無料オープンソース拡張機能。アカウント不要、トラッキングなし、広告なし。",
|
||||
"OG_TITLE": "KoalaSync | Netflix、Emby、Jellyfin、ほぼすべての動画でウォッチパーティー",
|
||||
"OG_DESCRIPTION": "Netflix、Emby、Jellyfin、YouTube、Twitch、ほぼすべてのHTML5動画でウォッチパーティーを開催。オープンソース、プライバシー重視、アカウント不要。",
|
||||
"TWITTER_TITLE": "KoalaSync | Netflix、Emby、Jellyfin、ほぼすべての動画でウォッチパーティー – 拡張機能",
|
||||
"TWITTER_DESCRIPTION": "Netflix、Emby、Jellyfin、YouTube、Twitch、ほぼすべてのHTML5動画でウォッチパーティーを開催。オープンソース、プライバシー重視、アカウント不要。",
|
||||
"NAV_FEATURES": "機能",
|
||||
"NAV_HOW_IT_WORKS": "仕組み",
|
||||
"HERO_TITLE": "一緒に観よう。<span class='hero-line2'>完璧に同期。</span>",
|
||||
"HERO_SUBTITLE": "遅延のないリモート映画鑑賞。登録不要、データ収集なし。リンクを共有するだけで、一緒に視聴できます。",
|
||||
"HERO_TITLE": "どんな動画も<span class='hero-highlight'>ウォッチパーティー</span>に。",
|
||||
"HERO_SUBTITLE": "離れていても一緒に映画ナイト。アカウント不要、トラッキングなし、広告なし。リンクを共有して、一緒に再生するだけ。",
|
||||
"HERO_MASCOT_ALT": "ダウンロードボタンを見下ろしているかわいいコアラ",
|
||||
"DEMO_SYNC": "同期中",
|
||||
"DEMO_HINT": "ライブデモ:どちらかのタブで再生すると、KoalaSyncが両方を同期させます。",
|
||||
"DEMO_NO_ROOM": "未接続",
|
||||
"DEMO_CREATE_ROOM": "ルームを作成",
|
||||
"DEMO_JOINED": "ルームに参加",
|
||||
"DEMO_TAB_SELECTED": "タブを選択済み",
|
||||
"DEMO_SELECT_PLACEHOLDER": "動画タブを選ぶ…",
|
||||
"MOBILE_DEMO_CTA": "仕組みを見る",
|
||||
"MOBILE_DEMO_TITLE": "KoalaSync ライブデモ",
|
||||
"MOBILE_DEMO_CLOSE": "デモを閉じる",
|
||||
"ADD_TO_CHROME": "Chromeに追加",
|
||||
"ADD_TO_FIREFOX": "Firefoxに追加",
|
||||
"COMPAT_HEADING": "お気に入りのプラットフォームで動作",
|
||||
@@ -35,20 +45,20 @@
|
||||
"USE_CASE_3_DESC": "距離を縮めてデートナイトを楽しみましょう。すべてのストーリー展開を体験し、同じジョックで笑い、感情的な瞬間をまったく同じミリ秒で共有します。",
|
||||
"WHY_TITLE": "KoalaSyncを選ぶ理由",
|
||||
"WHY_SUBTITLE": "信頼性の高い同期、プライバシー優先の設計、簡単なセットアップのために構築。",
|
||||
"FEATURE_1_TITLE": "完全なコントロール / 即時同期",
|
||||
"FEATURE_1_DESC": "一人が一時停止すれば、全員が一時停止。一人がシークすれば、全員が追従。2フェーズ同期プロトコルが、リアルタイムで全員の再生状況を連動させます。",
|
||||
"FEATURE_1_TITLE": "ホスト制御と同期",
|
||||
"FEATURE_1_DESC": "再生をコントロールする人を決定できます。ホスト操作を有効にして自分だけが一時停止やスキップを行えるようにするか、共同ホストを任命します。",
|
||||
"FEATURE_2_TITLE": "終わらない連続視聴",
|
||||
"FEATURE_2_DESC": "同期した自動再生。KoalaSyncはエピソードの切り替えを自動検知し、全員が次の動画を読み込むまで再生を待機します。",
|
||||
"FEATURE_3_TITLE": "アカウント不要 / 純粋なプライバシー",
|
||||
"FEATURE_3_DESC": "登録不要、追跡なし、履歴も残りません。サーバーは一時的なメモリ(RAM)上でのみ動作し、退出時にルームを完全に消去します。",
|
||||
"FEATURE_4_TITLE": "ユニバーサルHTML5サポート",
|
||||
"FEATURE_4_DESC": "YouTube、Twitch、Netflix、Jellyfin、Emby、およびHTML5ビデオ要素を含むほぼすべての標準的なWebページで動作します。独自のセルフホスト設定とも互換性があります。No video data is ever sent to our servers — only synchronization metadata.",
|
||||
"FEATURE_4_DESC": "Netflix、YouTube、Twitch、Jellyfin、Emby、そしてHTML5動画があるほぼすべてのページに対応。ブラウザから出るのは同期信号だけで、動画自体は送信されません。",
|
||||
"FEATURE_5_TITLE": "セルフホスト可能&Docker対応",
|
||||
"FEATURE_5_DESC": "公式サーバーは無料で高速ですが、必要に応じて独自のサーバーを構築できます。Dockerを使用して、数秒でプライベートリレーサーバーを起動できます。",
|
||||
"FEATURE_6_TITLE": "インスタント招待 / 1クリック参加",
|
||||
"FEATURE_6_DESC": "IPアドレスやパスワードをやり取りする必要はありません。生成された招待リンクを友達に共有するだけで、1クリックで自動的にルームに参加できます。",
|
||||
"FEATURE_7_TITLE": "音量均等化 / オーディオコンプレッサー",
|
||||
"FEATURE_7_DESC": "音量つまみをいじる必要はもうありません。内蔵オーディオコンプレッサーが小さな会話を強調し、大きな爆発音を抑えます。Netflix、Prime Video、Disney+、YouTube、あらゆるHTML5ビデオで動作。完全にローカル処理 — オーディオデータがコンピュータから送信されることはありません。",
|
||||
"FEATURE_7_DESC": "音量つまみをいじる必要はもうありません。内蔵コンプレッサーが小さな会話を持ち上げ、大きな爆発音を抑えます。すべてブラウザ内でローカル処理されます。",
|
||||
"FEATURE_8_TITLE": "オープンソース(MITライセンス)",
|
||||
"FEATURE_8_DESC": "すべてのコードベースはGitHubで公開されており、誰でも監査可能です。ブラックボックスや独自のバイナリデータ(blob)は一切なく、中継サーバーからブラウザ拡張機能まで100%の透明性を確保しています。",
|
||||
"COMP_TITLE": "KoalaSync と Teleparty の比較",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Telepartyのプライバシーポリシーと追跡データの収集について:",
|
||||
"COMP_FOOTNOTE_4": "標準的なHTML5ビデオタグへのスクリプトインジェクションを許可するウェブサイトで動作します。非常に厳格なコンテンツセキュリティポリシー(CSP)、DRMコピー保護、または高度に難読化されたプレーヤーラッパー(複雑なシャドウDOMなど)を持つウェブサイトでは、自動制御やインジェクションが制限される場合があります。",
|
||||
"STEPS_TITLE": "はじめに",
|
||||
"STEP_1_TITLE": "拡張機能をインストール",
|
||||
"STEP_1_DESC": "Chrome ウェブストアや Firefox Add-ons から KoalaSync をブラウザに追加するか、GitHub から最新の開発者向け ZIP をダウンロードしてください。",
|
||||
"STEP_1_ILLUS_DESC": "プライバシー優先のビデオ同期ツール",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Chrome版をダウンロード",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Firefox版をダウンロード",
|
||||
"STEP_1_ILLUS_ACTIVE": "拡張機能はアクティブです",
|
||||
"STEP_1_ILLUS_READY": "一緒に観る準備は完了!",
|
||||
"STEP_2_TITLE": "ルームを作成",
|
||||
"STEP_2_DESC": "拡張機能のポップアップを開き、「+ 新規ルーム作成」をクリックします。KoalaSyncは自動的に安全なルームIDとパスワードを生成し、接続し、招待リンクをクリップボードにコピーします。",
|
||||
"STEP_1_TITLE": "ルーム作成と共有",
|
||||
"STEP_1_DESC": "拡張機能を開き、ワンクリックでルームを作成してリンクを共有します。友達は招待リンクからすぐに参加できます。",
|
||||
"STEP_1_ILLUS_DESC": "連携するタブを選択",
|
||||
"STEP_1_ILLUS_DL_CHROME": "YouTubeタブを連携",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Netflixタブを連携",
|
||||
"STEP_1_ILLUS_ACTIVE": "タブ連携完了",
|
||||
"STEP_1_ILLUS_READY": "同期再生の準備完了 !",
|
||||
"STEP_2_TITLE": "動画タブの選択",
|
||||
"STEP_2_DESC": "動画のあるタブを開き、拡張機能のメニューでそのタブを選択します。KoalaSyncが自動的にお互いのプレイヤーを連携させます。",
|
||||
"STEP_2_ILLUS_ROOM": "ルーム",
|
||||
"STEP_2_ILLUS_SYNC": "同期",
|
||||
"STEP_2_ILLUS_SETTINGS": "設定",
|
||||
"STEP_2_ILLUS_CREATE": "+ 新規ルーム作成",
|
||||
"STEP_2_ILLUS_MANUAL": "手動接続 / 詳細設定",
|
||||
"STEP_2_ILLUS_COPIED": "招待リンクをコピーしました!",
|
||||
"STEP_3_TITLE": "共有と同期",
|
||||
"STEP_3_DESC": "招待リンクを友達に送信します。友達が参加したら、ビデオのタブを選択して、同期された再生を楽しんでください。",
|
||||
"STEP_3_TITLE": "同期を楽しむ",
|
||||
"STEP_3_DESC": "再生ボタンを押します。誰かが一時停止、早送り、または再生をしても、全員の動画がタイムラグなしで完璧に同期します。",
|
||||
"STEP_3_ILLUS_IN_SYNC": "同期中",
|
||||
"SELF_TITLE": "セルフホストする方向け",
|
||||
"SELF_SUBTITLE": "公式サーバーを信頼しませんか?データの完全な所有権を維持しましょう。数分で独自のプライベートリレーサーバーを構築できます。",
|
||||
@@ -158,7 +168,7 @@
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> SYNC",
|
||||
"MOCK_14": "他の人に合わせる",
|
||||
"MOCK_15": "最新のアクティビティ状況",
|
||||
"MOCK_16": "1人のメンバー (KoalaPC) を待機中...",
|
||||
"MOCK_16": "1人のメンバー (🐶 HappyDog) を待機中...",
|
||||
"MOCK_17": "スキップして再生",
|
||||
"MOCK_18": "ユーザー名",
|
||||
"MOCK_19": "不要なタブを非表示",
|
||||
@@ -179,7 +189,14 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSyncは、Netflix、Disney+、Amazon、YouTube、Twitch、またはその他のストリーミングプラットフォームと提携、承認、または関連付けられているものではありません。すべての商標はそれぞれの所有者に帰属します。",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync vs Teleparty:どちらのウォッチパーティツールを使うべき?",
|
||||
"ALT_INDEX_TITLE": "KoalaSync - 代替サービスと比較",
|
||||
"ALT_INDEX_META_DESC": "KoalaSync と他のウォッチパーティー拡張機能やストリーミング向けソリューションを、公平な比較と詳しいガイドで確認できます。",
|
||||
"ALT_INDEX_HEADING": "ガイドと比較",
|
||||
"ALT_INDEX_INTRO": "KoalaSync は他のウォッチパーティーツールやストリーミング向けソリューションと比べてどう違うのでしょうか。リモート映画鑑賞に合う選択肢を見つけるために、公平で技術的な比較をご覧ください。",
|
||||
"ALT_INDEX_READ_COMPARISON": "比較を読む →",
|
||||
"ALT_INDEX_READ_GUIDE": "ガイドを読む →",
|
||||
"ALT_INDEX_FOOTER_LINK": "ガイドと比較",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync vs Teleparty:どちらのウォッチパーティツールを使うべき?",
|
||||
"ALT_TELEPARTY_META_DESC": "ウォッチパーティ向けツール、KoalaSyncとTelepartyの実践的比較。チャット機能、モバイル対応、ブラウザでの動画同期、セルフホスト型メディアサーバー、オープンソース利用といった観点から解説します。",
|
||||
"ALT_TELEPARTY_INTRO": "KoalaSyncとTelepartyは、どちらも「3、2、1、再生!」と声を掛け合う手間をかけずに、一緒に動画を楽しむという共通の課題を解決してくれます。ただ、そのアプローチは異なります。<br><br>チャット機能が組み込まれていることや、モバイル端末で視聴できることを重視するなら、Telepartyが最適です。一方、KoalaSyncは、DiscordやTeamSpeakなどで既に通話しながら、ブラウザ上で動画の再生タイミングだけを同期させたいという、デスクトップブラウザでの利用に適しています。<br><br>私がKoalaSyncを開発した主な理由は「柔軟性」です。特定のプラットフォームだけでなく、一般的なブラウザ用動画プレーヤーを使用するほぼすべてのウェブサイトで動作するように設計されています。これには、Emby、Jellyfin、Plexといったセルフホスト環境や、ローカル/プライベートドメイン、そしてブラウザ上の一般的な動画ページなどが含まれます。",
|
||||
"ALT_TELEPARTY_WHEN_TELE_TITLE": "Telepartyが適しているケース",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "KoalaSyncは音声通話の代わりになるものではありません。視聴しながら会話をする場合は、Discord、TeamSpeak、Signal、WhatsAppなど、普段お使いのツールをそのまま利用します。<br><br>また、参加者全員がそれぞれのデバイスでその動画やページにアクセスできる必要があります。KoalaSyncは、サブスクリプション、ログイン、DRM(デジタル著作権管理)、地域制限、プラットフォームの利用規約などを回避するものではありません。<br><br>現在はデスクトップ版ブラウザのみに対応しており、チャットや音声通話機能は内蔵されていません。また、動作の可否は使用するブラウザや動画プレーヤーに依存します。",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "概要",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "画質やプライバシー、個別の操作性をあまり気にせず、手軽に済ませたい場合は「画面共有」が最も簡単な選択肢です。一方、参加者それぞれが自身のブラウザで動画を再生し、実際に同じ空間で一緒に視聴しているような感覚を重視するなら、KoalaSyncの方が適しています。",
|
||||
"ALT_TRY_KOALASYNC": "KoalaSyncを試す"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "KoalaSyncを試す",
|
||||
"MOCK_HOST_CONTROL_LABEL": "ホスト操作",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "自分だけが再生を操作できる",
|
||||
"LABEL_SELECT_VIDEO": "ビデオを選択",
|
||||
"OPTION_SELECT_TAB": "-- タブを選択してください --",
|
||||
"THEME_TOGGLE_LABEL": "ライト/ダークモード切り替え",
|
||||
"FAQ_SHOW_ALL": "すべての質問を表示",
|
||||
"FAQ_HIDE_ALL": "質問を隠す",
|
||||
"SELF_SHOW_SETUP": "セットアップガイドを表示",
|
||||
"SELF_HIDE_SETUP": "ガイドを隠す",
|
||||
"HERO_TRUST": "無料 · オープンソース · アカウント不要",
|
||||
"COMP_MORE_GUIDES": "詳しい比較とその他の選択肢"
|
||||
}
|
||||
|
||||
+68
-27
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "ko_KR",
|
||||
"META_TITLE": "KoalaSync | Netflix, YouTube 및 모든 비디오를 친구와 실시간 동기화 시청",
|
||||
"META_DESCRIPTION": "Netflix, YouTube, Twitch 및 모든 HTML5 비디오를 친구들과 완벽하게 동기화하여 시청하세요. Chrome 및 Firefox용 무료 오픈 소스 브라우저 확장 프로그램. 가입 필요 없음.",
|
||||
"SCHEMA_APP_DESC": "Netflix, YouTube, Twitch 및 모든 HTML5 비디오를 친구들과 완벽하게 동기화하여 시청하세요. Chrome 및 Firefox용 무료 오픈 소스 브라우저 확장 프로그램. 가입 필요 없음.",
|
||||
"OG_TITLE": "KoalaSync | Netflix, Emby, Jellyfin 및 거의 모든 비디오를 친구와 동기화 시청",
|
||||
"OG_DESCRIPTION": "Netflix, Emby, Jellyfin, YouTube, Twitch 및 거의 모든 HTML5 비디오를 완벽하게 동기화하여 시청하세요. Chrome 및 Firefox용 개인정보 보호 중심 오픈 소스 브라우저 확장 프로그램.",
|
||||
"TWITTER_TITLE": "KoalaSync | Netflix, Emby, Jellyfin 및 거의 모든 비디오를 친구와 동기화 시청 – 브라우저 확장 프로그램",
|
||||
"TWITTER_DESCRIPTION": "Netflix, Emby, Jellyfin, YouTube, Twitch 및 거의 모든 HTML5 비디오를 친구들과 완벽하게 동기화하여 시청하세요. Chrome 및 Firefox용 개인정보 보호 중심 오픈 소스 브라우저 확장 프로그램.",
|
||||
"META_TITLE": "KoalaSync | 넷플릭스·유튜브 워치 파티 확장 프로그램",
|
||||
"META_DESCRIPTION": "넷플릭스, 유튜브, 트위치 등 모든 HTML5 영상에서 워치 파티. 크롬·파이어폭스용 무료 오픈소스 확장 프로그램. 계정 없음, 추적 없음, 광고 없음.",
|
||||
"SCHEMA_APP_DESC": "넷플릭스, 유튜브, 트위치 등 모든 HTML5 영상에서 워치 파티. 크롬·파이어폭스용 무료 오픈소스 확장 프로그램. 계정 없음, 추적 없음, 광고 없음.",
|
||||
"OG_TITLE": "KoalaSync | Netflix, Emby, Jellyfin 및 거의 모든 영상 워치 파티",
|
||||
"OG_DESCRIPTION": "Netflix, Emby, Jellyfin, YouTube, Twitch 및 거의 모든 HTML5 영상에서 워치 파티를 열어보세요. 오픈소스, 개인정보 보호 중심, 계정 불필요.",
|
||||
"TWITTER_TITLE": "KoalaSync | Netflix, Emby, Jellyfin 및 거의 모든 영상 워치 파티 – 브라우저 확장 프로그램",
|
||||
"TWITTER_DESCRIPTION": "Netflix, Emby, Jellyfin, YouTube, Twitch 및 거의 모든 HTML5 영상에서 워치 파티를 열어보세요. 오픈소스, 개인정보 보호 중심, 계정 불필요.",
|
||||
"NAV_FEATURES": "특징",
|
||||
"NAV_HOW_IT_WORKS": "작동 방식",
|
||||
"HERO_TITLE": "함께 시청하세요.<span class='hero-line2'>완벽하게 동기화됩니다.</span>",
|
||||
"HERO_SUBTITLE": "지연 없는 원격 영화의 밤. 회원가입도, 데이터 수집도 없습니다. 링크를 공유하고 함께 시청하세요.",
|
||||
"HERO_TITLE": "어떤 영상이든 <span class='hero-highlight'>워치 파티</span>로.",
|
||||
"HERO_SUBTITLE": "지연 없는 원격 영화의 밤. 계정 없음, 추적 없음, 광고 없음. 링크만 공유하고 함께 재생하세요.",
|
||||
"HERO_MASCOT_ALT": "다운로드 버튼을 내려다보고 서 있는 귀여운 코알라",
|
||||
"DEMO_SYNC": "동기화됨",
|
||||
"DEMO_HINT": "라이브 데모: 아무 탭에서나 재생을 누르면 KoalaSync가 두 탭을 동기화합니다.",
|
||||
"DEMO_NO_ROOM": "아직 연결되지 않음",
|
||||
"DEMO_CREATE_ROOM": "방 만들기",
|
||||
"DEMO_JOINED": "방 참여됨",
|
||||
"DEMO_TAB_SELECTED": "탭 선택됨",
|
||||
"DEMO_SELECT_PLACEHOLDER": "동영상 탭 선택…",
|
||||
"MOBILE_DEMO_CTA": "작동 방식 보기",
|
||||
"MOBILE_DEMO_TITLE": "KoalaSync 라이브 데모",
|
||||
"MOBILE_DEMO_CLOSE": "데모 닫기",
|
||||
"ADD_TO_CHROME": "Chrome에 추가",
|
||||
"ADD_TO_FIREFOX": "Firefox에 추가",
|
||||
"COMPAT_HEADING": "가장 좋아하는 플랫폼에서 작동합니다",
|
||||
@@ -35,20 +45,20 @@
|
||||
"USE_CASE_3_DESC": "거리의 한계를 넘어 데이트를 즐겨보세요. 모든 줄거리의 반전을 경험하고, 같은 농담에 웃으며, 감동적인 순간을 정확히 같은 밀리초에 공유하세요.",
|
||||
"WHY_TITLE": "왜 KoalaSync인가요?",
|
||||
"WHY_SUBTITLE": "신뢰할 수 있는 동기화, 개인정보 보호 중심 설계, 쉬운 설정을 위해 구축되었습니다.",
|
||||
"FEATURE_1_TITLE": "완벽한 제어 / 즉시 동기화",
|
||||
"FEATURE_1_DESC": "한 명이 일시정지하면 모두가 일시정지됩니다. 한 명이 탐색하면 모두가 따라갑니다. 당사의 2단계 동기화 프로토콜은 실시간으로 모든 참여자의 비디오 재생을 조율합니다.",
|
||||
"FEATURE_1_TITLE": "방장 제어 및 동기화",
|
||||
"FEATURE_1_DESC": "누가 재생을 제어할지 직접 결정하세요. 호스트 제어를 활성화하여 자신만 일시중지 및 탐색을 수행하거나 공동 호스트를 지정합니다.",
|
||||
"FEATURE_2_TITLE": "끊임없는 몰아보기",
|
||||
"FEATURE_2_DESC": "동기화된 자동 재생. KoalaSync는 에피소드 전환을 자동으로 감지하고 모든 참여자가 다음 비디오를 로드할 때까지 재생을 대기시킵니다.",
|
||||
"FEATURE_3_TITLE": "계정 없음 / 완전한 개인정보 보호",
|
||||
"FEATURE_3_DESC": "회원가입 없음, 추적 없음, 흔적을 남기지 않습니다. 서버는 휘발성 RAM에서만 완전히 실행되며 방을 나가면 영구히 삭제됩니다.",
|
||||
"FEATURE_4_TITLE": "범용 HTML5 지원",
|
||||
"FEATURE_4_DESC": "YouTube, Twitch, Netflix, Jellyfin, Emby 및 HTML5 비디오 요소가 포함된 거의 모든 웹 페이지에서 작동합니다. 사용자 정의 자체 호스팅 설정과도 호환됩니다. 비디오 데이터는 당사 서버로 절대 전송되지 않으며, 동기화 메타데이터만 전송됩니다.",
|
||||
"FEATURE_4_DESC": "Netflix, YouTube, Twitch, Jellyfin, Emby 등 HTML5 영상이 있는 거의 모든 페이지에서 작동합니다. 브라우저를 떠나는 것은 동기화 신호뿐, 영상은 아닙니다.",
|
||||
"FEATURE_5_TITLE": "자체 호스팅 가능 및 Docker 지원",
|
||||
"FEATURE_5_DESC": "공식 서버는 무료이며 빠르지만, 원하는 경우 직접 호스팅할 수도 있습니다. Docker를 통해 몇 초 만에 나만의 개인 릴레이 서버를 구축해 보세요.",
|
||||
"FEATURE_6_TITLE": "즉시 초대 / 클릭 한 번으로 참여",
|
||||
"FEATURE_6_DESC": "IP 주소나 비밀번호를 교환할 필요가 없습니다. 생성된 초대 링크를 친구들과 공유하면 클릭 한 번으로 방에 자동으로 참여할 수 있습니다.",
|
||||
"FEATURE_7_TITLE": "볼륨 레벨링 / 오디오 압축기",
|
||||
"FEATURE_7_DESC": "더 이상 볼륨 노브를 계속 조정하지 마세요. 내장 오디오 압축기가 조용한 대화를 증폭하고 큰 폭발음을 완화합니다. Netflix, Prime Video, Disney+, YouTube 및 모든 HTML5 비디오에서 작동합니다. 완전히 로컬에서 처리 — 오디오 데이터가 컴퓨터를 떠나지 않습니다.",
|
||||
"FEATURE_7_DESC": "더 이상 볼륨을 조절할 필요 없습니다. 내장 압축기가 조용한 대사를 키우고 큰 폭발음을 완화하며, 모든 처리는 브라우저에서 로컬로 이루어집니다.",
|
||||
"FEATURE_8_TITLE": "오픈 소스 (MIT 라이선스)",
|
||||
"FEATURE_8_DESC": "모든 코드베이스는 GitHub에서 공개적으로 검토할 수 있습니다. 블랙박스나 독점적인 블롭(blob)이 전혀 없으며, 릴레이 서버부터 브라우저 확장 프로그램까지 100% 투명성을 보장합니다.",
|
||||
"COMP_TITLE": "KoalaSync 대 Teleparty",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Teleparty 개인정보 처리방침 및 추적 데이터 수집 정보:",
|
||||
"COMP_FOOTNOTE_4": "표준 HTML5 비디오 태그에 스크립트 삽입을 허용하는 웹사이트에서 작동합니다. 콘텐츠 보안 정책(CSP)이 매우 엄격하거나, DRM 복사 방지가 적용되었거나, 플레이어 래퍼가 복잡하게 난독화된 웹사이트(예: 복잡한 섀도 DOM)에서는 자동 제어 또는 스크립트 삽입이 제한될 수 있습니다.",
|
||||
"STEPS_TITLE": "시작하기",
|
||||
"STEP_1_TITLE": "확장 프로그램 설치",
|
||||
"STEP_1_DESC": "Chrome 웹 스토어, Firefox 부가 기능에서 KoalaSync를 브라우저에 추가하거나, GitHub에서 최신 개발자용 ZIP 파일을 다운로드하세요.",
|
||||
"STEP_1_ILLUS_DESC": "개인정보 보호 중심의 비디오 동기화 프로그램",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Chrome용 다운로드",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Firefox용 다운로드",
|
||||
"STEP_1_ILLUS_ACTIVE": "확장 프로그램 활성화됨",
|
||||
"STEP_1_ILLUS_READY": "함께 시청할 준비가 되었습니다!",
|
||||
"STEP_2_TITLE": "방 만들기",
|
||||
"STEP_2_DESC": "확장 프로그램 팝업을 열고 '+ 새 방 만들기'를 클릭하세요. KoalaSync가 안전한 방 ID와 비밀번호를 자동으로 생성하고, 참여한 후 초대 링크를 클립보드에 복사합니다.",
|
||||
"STEP_1_TITLE": "방 만들기 및 공유",
|
||||
"STEP_1_DESC": "확장 프로그램을 열고 클릭 한 번으로 방을 만든 후 링크를 공유하세요. 친구들이 초대 링크를 통해 바로 참여합니다.",
|
||||
"STEP_1_ILLUS_DESC": "연결할 탭 선택",
|
||||
"STEP_1_ILLUS_DL_CHROME": "YouTube 탭 연결",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Netflix 탭 연결",
|
||||
"STEP_1_ILLUS_ACTIVE": "탭 연결됨",
|
||||
"STEP_1_ILLUS_READY": "동기화 시청 준비 완료 !",
|
||||
"STEP_2_TITLE": "동영상 탭 선택",
|
||||
"STEP_2_DESC": "동영상이 열려 있는 탭으로 이동하여 확장 프로그램 메뉴에서 해당 탭을 선택하세요. KoalaSync가 플레이어를 자동으로 연결합니다.",
|
||||
"STEP_2_ILLUS_ROOM": "방",
|
||||
"STEP_2_ILLUS_SYNC": "동기화",
|
||||
"STEP_2_ILLUS_SETTINGS": "설정",
|
||||
"STEP_2_ILLUS_CREATE": "+ 새 방 만들기",
|
||||
"STEP_2_ILLUS_MANUAL": "수동 연결 / 고급",
|
||||
"STEP_2_ILLUS_COPIED": "초대 링크가 복사되었습니다!",
|
||||
"STEP_3_TITLE": "공유 및 동기화",
|
||||
"STEP_3_DESC": "친구들에게 초대 링크를 보내세요. 친구들이 참여하면 비디오 탭을 선택하고 동기화된 재생을 즐기세요.",
|
||||
"STEP_3_TITLE": "동기화 즐기기",
|
||||
"STEP_3_DESC": "재생을 누르세요. 누군가 일시중지, 탐색 또는 재생을 하더라도 모든 사람의 영상이 지연 없이 실시간으로 동기화됩니다.",
|
||||
"STEP_3_ILLUS_IN_SYNC": "동기화됨",
|
||||
"SELF_TITLE": "자체 호스팅 사용자를 위한 안내",
|
||||
"SELF_SUBTITLE": "공식 서버를 신뢰하기 어려우신가요? 완벽한 데이터 주권을 유지하세요. 몇 분 만에 나만의 개인 릴레이 서버를 구축할 수 있습니다.",
|
||||
@@ -158,7 +168,7 @@
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> SYNC",
|
||||
"MOCK_14": "다른 사람에게 이동",
|
||||
"MOCK_15": "최근 활동 상태",
|
||||
"MOCK_16": "참여자 1명 (KoalaPC) 대기 중...",
|
||||
"MOCK_16": "참여자 1명 (🐶 HappyDog) 대기 중...",
|
||||
"MOCK_17": "건너뛰고 재생",
|
||||
"MOCK_18": "사용자 이름",
|
||||
"MOCK_19": "복잡한 탭 숨기기",
|
||||
@@ -179,7 +189,14 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync는 Netflix, Disney+, Amazon, YouTube, Twitch 또는 기타 스트리밍 플랫폼과 제휴, 보증 또는 연관되어 있지 않습니다. 모든 상표는 해당 소유자의 재산입니다.",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync 대 Teleparty: 어떤 '워치 파티(watch party)' 도구를 선택해야 할까요?",
|
||||
"ALT_INDEX_TITLE": "KoalaSync - 대안 및 비교",
|
||||
"ALT_INDEX_META_DESC": "KoalaSync와 다른 워치 파티 확장 프로그램 및 스트리밍 솔루션을 솔직한 비교와 자세한 가이드로 살펴보세요.",
|
||||
"ALT_INDEX_HEADING": "가이드 및 비교",
|
||||
"ALT_INDEX_INTRO": "KoalaSync는 다른 워치 파티 도구와 스트리밍 솔루션에 비해 어떤가요? 원격 영화 감상에 가장 잘 맞는 선택지를 찾을 수 있도록 솔직하고 기술적인 비교를 읽어보세요.",
|
||||
"ALT_INDEX_READ_COMPARISON": "비교 읽기 →",
|
||||
"ALT_INDEX_READ_GUIDE": "가이드 읽기 →",
|
||||
"ALT_INDEX_FOOTER_LINK": "가이드 및 비교",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync 대 Teleparty: 어떤 '워치 파티(watch party)' 도구를 선택해야 할까요?",
|
||||
"ALT_TELEPARTY_META_DESC": "워치 파티를 위한 KoalaSync와 Teleparty의 실용적 비교: 내장 채팅, 모바일 지원, 브라우저 영상 동기화, 자체 호스팅 미디어 서버 및 오픈 소스 활용 여부 등을 다룹니다.",
|
||||
"ALT_TELEPARTY_INTRO": "KoalaSync와 Teleparty는 모두 '3, 2, 1, 재생' 같은 번거로운 절차 없이 함께 영상을 시청할 수 있게 해준다는 점에서 같은 문제를 해결해 줍니다. 다만 접근 방식에는 차이가 있습니다.<br><br>내장 채팅 기능이 필요하거나 모바일에서 시청하고 싶다면 Teleparty가 가장 합리적인 선택입니다. 반면 KoalaSync는 Discord, Teamspeak 또는 기타 음성 통화 서비스를 통해 이미 대화를 나누고 있으면서, 단순히 영상 재생만 동기화하고 싶은 데스크톱 브라우저 기반의 워치 파티에 더 적합합니다.<br><br>제가 KoalaSync를 개발한 주된 이유는 유연성 때문입니다. 특정 플랫폼 목록에만 국한되지 않고, 일반적인 브라우저 비디오 플레이어를 사용하는 거의 모든 웹사이트에서 작동하도록 설계되었습니다. 여기에는 Emby, Jellyfin, Plex와 같은 자체 호스팅 환경, 로컬/비공개 도메인, 그리고 브라우저상의 수많은 일반 동영상 페이지가 포함됩니다.",
|
||||
"ALT_TELEPARTY_WHEN_TELE_TITLE": "Teleparty가 여전히 좋은 선택이 될 수 있는 경우",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "KoalaSync는 음성 통화를 대체하는 서비스가 아닙니다. 영상을 보면서 대화하고 싶다면 평소처럼 Discord, Teamspeak, Signal, WhatsApp 등을 그대로 사용하면 됩니다.<br><br>또한 모든 참가자가 각자의 기기에서 해당 영상이나 페이지에 접속할 수 있어야 합니다. KoalaSync는 구독, 로그인, DRM, 지역 제한 또는 플랫폼 규정을 우회하지 않습니다.<br><br>현재 데스크톱 브라우저에서만 지원되며, 자체 채팅이나 음성 기능은 없습니다. 호환성은 항상 브라우저 및 동영상 플레이어의 영향을 받습니다.",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "요약",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "화질, 개인정보 보호, 개별 제어 기능 등을 크게 신경 쓰지 않고 간편하게 진행하고 싶을 때는 화면 공유가 가장 간단한 방법입니다. 반면, 모든 참가자가 각자의 브라우저에서 직접 영상을 시청하며 실제로 함께 보는 듯한 느낌을 원한다면 KoalaSync가 더 적합합니다.",
|
||||
"ALT_TRY_KOALASYNC": "KoalaSync 사용해 보기"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "KoalaSync 사용해 보기",
|
||||
"MOCK_HOST_CONTROL_LABEL": "호스트 제어",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "나만 재생을 제어할 수 있음",
|
||||
"LABEL_SELECT_VIDEO": "비디오 선택",
|
||||
"OPTION_SELECT_TAB": "-- 탭 선택 --",
|
||||
"THEME_TOGGLE_LABEL": "라이트/다크 모드 전환",
|
||||
"FAQ_SHOW_ALL": "모든 질문 보기",
|
||||
"FAQ_HIDE_ALL": "질문 숨기기",
|
||||
"SELF_SHOW_SETUP": "설치 가이드 보기",
|
||||
"SELF_HIDE_SETUP": "가이드 숨기기",
|
||||
"HERO_TRUST": "무료 · 오픈소스 · 계정 불필요",
|
||||
"COMP_MORE_GUIDES": "전체 비교 및 다른 대안 보기"
|
||||
}
|
||||
|
||||
+73
-32
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "nl_NL",
|
||||
"META_TITLE": "KoalaSync | Synchroniseer Netflix, YouTube & elke video met vrienden",
|
||||
"META_DESCRIPTION": "Bekijk Netflix, YouTube, Twitch en elke HTML5-video in perfecte synchronisatie met vrienden. Gratis, open-source browserextensie voor Chrome en Firefox. Geen registratie nodig.",
|
||||
"SCHEMA_APP_DESC": "Bekijk Netflix, YouTube, Twitch en elke HTML5-video in perfecte synchronisatie met vrienden. Gratis, open-source browserextensie voor Chrome en Firefox. Geen registratie nodig.",
|
||||
"OG_TITLE": "KoalaSync | Synchroniseer Netflix, Emby, Jellyfin & bijna elke video met vrienden",
|
||||
"OG_DESCRIPTION": "Bekijk Netflix, Emby, Jellyfin, YouTube, Twitch en bijna elke HTML5-video in perfecte synchronisatie. Privacy-first, open-source browserextensie voor Chrome & Firefox.",
|
||||
"TWITTER_TITLE": "KoalaSync | Synchroniseer Netflix, Emby, Jellyfin & bijna elke video met vrienden – Browserextensie",
|
||||
"TWITTER_DESCRIPTION": "Bekijk Netflix, Emby, Jellyfin, YouTube, Twitch en bijna elke HTML5-video in perfecte synchronisatie met vrienden. Privacy-first, open-source browserextensie voor Chrome en Firefox.",
|
||||
"META_TITLE": "KoalaSync | Watch party voor Netflix, YouTube & elke video",
|
||||
"META_DESCRIPTION": "Watch party op Netflix, YouTube, Twitch en elke HTML5-video. Gratis open-source extensie voor Chrome en Firefox. Geen account, geen tracking, geen reclame.",
|
||||
"SCHEMA_APP_DESC": "Watch party op Netflix, YouTube, Twitch en elke HTML5-video. Gratis open-source extensie voor Chrome en Firefox. Geen account, geen tracking, geen reclame.",
|
||||
"OG_TITLE": "KoalaSync | Watch party voor Netflix, Emby, Jellyfin en bijna elke video",
|
||||
"OG_DESCRIPTION": "Start een watch party op Netflix, Emby, Jellyfin, YouTube, Twitch en bijna elke HTML5-video. Open source, privacyvriendelijk en zonder account.",
|
||||
"TWITTER_TITLE": "KoalaSync | Watch party voor Netflix, Emby, Jellyfin en bijna elke video – Browserextensie",
|
||||
"TWITTER_DESCRIPTION": "Start een watch party op Netflix, Emby, Jellyfin, YouTube, Twitch en bijna elke HTML5-video. Open source, privacyvriendelijk en zonder account.",
|
||||
"NAV_FEATURES": "Functies",
|
||||
"NAV_HOW_IT_WORKS": "Hoe het werkt",
|
||||
"HERO_TITLE": "Samen Kijken.<span class='hero-line2'>Perfect Synchroniseren.</span>",
|
||||
"HERO_SUBTITLE": "Uw filmavond op afstand zonder vertraging. Geen registratie, geen gegevensverzameling. Deel gewoon een link en kijk samen.",
|
||||
"HERO_TITLE": "Maak van elke video een <span class='hero-highlight'>watch party</span>.",
|
||||
"HERO_SUBTITLE": "Jouw filmavond op afstand, zonder vertraging. Geen account, geen tracking, geen advertenties. Deel een link en druk samen op play.",
|
||||
"HERO_MASCOT_ALT": "Een schattige koala die staat en naar beneden kijkt naar de downloadknoppen",
|
||||
"DEMO_SYNC": "Synchroon",
|
||||
"DEMO_HINT": "Live demo: druk op play in een van de tabbladen en KoalaSync houdt beide synchroon.",
|
||||
"DEMO_NO_ROOM": "Nog niet verbonden",
|
||||
"DEMO_CREATE_ROOM": "Kamer aanmaken",
|
||||
"DEMO_JOINED": "In de kamer",
|
||||
"DEMO_TAB_SELECTED": "Tabblad geselecteerd",
|
||||
"DEMO_SELECT_PLACEHOLDER": "Kies een videotabblad…",
|
||||
"MOBILE_DEMO_CTA": "Bekijk hoe het werkt",
|
||||
"MOBILE_DEMO_TITLE": "KoalaSync live demo",
|
||||
"MOBILE_DEMO_CLOSE": "Demo sluiten",
|
||||
"ADD_TO_CHROME": "Toevoegen aan Chrome",
|
||||
"ADD_TO_FIREFOX": "Toevoegen aan Firefox",
|
||||
"COMPAT_HEADING": "Werkt op uw favoriete platforms",
|
||||
@@ -35,20 +45,20 @@
|
||||
"USE_CASE_3_DESC": "Overbrug de afstand en geniet van date nights. Ervaar elke plotwending, lach om dezelfde grappen en deel emotionele momenten op exact dezelfde milliseconde.",
|
||||
"WHY_TITLE": "Waarom KoalaSync?",
|
||||
"WHY_SUBTITLE": "Gebouwd voor betrouwbare synchronisatie, privacy-first ontwerp en eenvoudige installatie.",
|
||||
"FEATURE_1_TITLE": "Volledige controle / Directe synchronisatie",
|
||||
"FEATURE_1_DESC": "Eén pauzeert, iedereen pauzeert. Eén spoelt, iedereen volgt. Ons tweefasige synchronisatieprotocol coördineert het afspelen in realtime voor alle deelnemers.",
|
||||
"FEATURE_1_TITLE": "Host-controle & synchronisatie",
|
||||
"FEATURE_1_DESC": "Jij bepaalt wie het afspelen bedient. Schakel hostbeheer in zodat alleen jij kunt pauzeren en spoelen, of wijs co-hosts aan.",
|
||||
"FEATURE_2_TITLE": "Eindeloos binge-watchen",
|
||||
"FEATURE_2_DESC": "Automatisch afspelen in sync. KoalaSync detecteert automatisch afleveringsovergangen en houdt het afspelen vast totdat alle deelnemers de volgende video succesvol hebben geladen.",
|
||||
"FEATURE_3_TITLE": "Geen accounts / Pure privacy",
|
||||
"FEATURE_3_DESC": "Geen registratie, geen tracking, geen permanente voetafdruk. De server draait volledig in het tijdelijke RAM en wist uw kamer wanneer u vertrekt.",
|
||||
"FEATURE_4_TITLE": "Universele HTML5-ondersteuning",
|
||||
"FEATURE_4_DESC": "Werkt op YouTube, Twitch, Netflix, Jellyfin, Emby en bijna elke standaard webpagina met een HTML5-video-element. Ook compatibel met aangepaste zelfgehoste installaties. Er worden nooit videogegevens naar onze servers verzonden — alleen synchronisatiemetagegevens.",
|
||||
"FEATURE_4_DESC": "Netflix, YouTube, Twitch, Jellyfin, Emby en vrijwel elke pagina met HTML5-video. Alleen sync-signalen verlaten je browser, nooit de video.",
|
||||
"FEATURE_5_TITLE": "Zelf te hosten & Docker-ready",
|
||||
"FEATURE_5_DESC": "Onze officiële servers zijn gratis en snel, maar u kunt de volledige controle nemen als u dat wilt. Start uw eigen privé-relayserver in seconden via Docker.",
|
||||
"FEATURE_6_TITLE": "Directe uitnodigingen / 1-klik deelname",
|
||||
"FEATURE_6_DESC": "Geen IP-adressen of wachtwoorden om uit te wisselen. Deel een gegenereerde uitnodigingslink met uw vrienden om ze met een enkele klik automatisch deel te laten nemen.",
|
||||
"FEATURE_7_TITLE": "Volume egaliseren / Audiocompressor",
|
||||
"FEATURE_7_DESC": "Niet langer aan de volumeknop zitten. De ingebouwde audiocompressor versterkt stille dialogen en dempt harde explosies. Werkt op Netflix, Prime Video, Disney+, YouTube en elke HTML5-video. Volledig lokaal — geen audiogegevens verlaten uw computer.",
|
||||
"FEATURE_7_DESC": "Nooit meer aan de volumeknop draaien. Een ingebouwde compressor tilt stille dialogen op en temt luide explosies, allemaal lokaal in je browser verwerkt.",
|
||||
"FEATURE_8_TITLE": "Open Source (MIT License)",
|
||||
"FEATURE_8_DESC": "De volledige codebase is openbaar controleerbaar op GitHub. Geen zwarte dozen, geen propri?taire blobs - 100% transparantie van de relayserver tot de browserextensie.",
|
||||
"COMP_TITLE": "KoalaSync vs. Teleparty",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Teleparty privacybeleid en trackinggegevensverzameling:",
|
||||
"COMP_FOOTNOTE_4": "Werkt op websites die scriptinjecties in standaard HTML5-videotags toestaan. Websites met een zeer strikt Content Security Policy (CSP), DRM-kopieerbeveiliging of sterk versluierde spelers (zoals complexe shadow-DOM's) kunnen automatische controle of injectie beperken.",
|
||||
"STEPS_TITLE": "Aan de slag",
|
||||
"STEP_1_TITLE": "Extensie installeren",
|
||||
"STEP_1_DESC": "Voeg KoalaSync toe aan uw browser vanuit de Chrome Web Store, Firefox-add-ons, of download de nieuwste ontwikkelaars-ZIP van GitHub.",
|
||||
"STEP_1_ILLUS_DESC": "Privacy-first videosynchronisator",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Downloaden voor Chrome",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Downloaden voor Firefox",
|
||||
"STEP_1_ILLUS_ACTIVE": "Extensie actief",
|
||||
"STEP_1_ILLUS_READY": "Klaar om samen te kijken!",
|
||||
"STEP_2_TITLE": "Kamer maken",
|
||||
"STEP_2_DESC": "Open de extensie-popup en klik op '+ Nieuwe kamer maken'. KoalaSync genereert automatisch een beveiligde Kamer-ID en Wachtwoord, neemt deel en kopieert de uitnodigingslink naar uw klembord.",
|
||||
"STEP_1_TITLE": "Kamer maken en delen",
|
||||
"STEP_1_DESC": "Open de extensie, maak een kamer met één klik en deel de link met je vrienden. Ze nemen direct deel via de uitnodigingslink.",
|
||||
"STEP_1_ILLUS_DESC": "Kies een tabblad om te koppelen",
|
||||
"STEP_1_ILLUS_DL_CHROME": "YouTube-tabblad koppelen",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Netflix-tabblad koppelen",
|
||||
"STEP_1_ILLUS_ACTIVE": "Tabblad gekoppeld",
|
||||
"STEP_1_ILLUS_READY": "Klaar voor synchroon kijken !",
|
||||
"STEP_2_TITLE": "Videotabblad selecteren",
|
||||
"STEP_2_DESC": "Ga naar je videotabblad en selecteer dit in het menu van de extensie. KoalaSync koppelt vanaf nu automatisch jullie spelers.",
|
||||
"STEP_2_ILLUS_ROOM": "Kamer",
|
||||
"STEP_2_ILLUS_SYNC": "Sync",
|
||||
"STEP_2_ILLUS_SETTINGS": "Instellingen",
|
||||
"STEP_2_ILLUS_CREATE": "+ Nieuwe kamer maken",
|
||||
"STEP_2_ILLUS_MANUAL": "Handmatig verbinden / Geavanceerd",
|
||||
"STEP_2_ILLUS_COPIED": "Uitnodigingslink gekopieerd!",
|
||||
"STEP_3_TITLE": "Delen & Synchroniseren",
|
||||
"STEP_3_DESC": "Stuur de uitnodigingslink naar uw vrienden. Zodra ze deelnemen, selecteert u uw videotabblad en geniet u van gesynchroniseerd afspelen.",
|
||||
"STEP_3_TITLE": "Genieten van de synchronisatie",
|
||||
"STEP_3_DESC": "Druk op afspelen. Of iemand nu pauzeert, spoelt of afspeelt, de video van iedereen blijft perfect en zonder vertraging synchroon.",
|
||||
"STEP_3_ILLUS_IN_SYNC": "IN SYNC",
|
||||
"SELF_TITLE": "Voor zelf-hosters",
|
||||
"SELF_SUBTITLE": "Vertrouwt u onze server niet? Behoud de volledige soevereiniteit over uw gegevens. Implementeer uw eigen privé-relayserver in enkele minuten.",
|
||||
@@ -116,21 +126,21 @@
|
||||
"FAQ_TITLE": "Veelgestelde vragen",
|
||||
"FAQ_SUBTITLE": "Alles wat u moet weten over het samen bekijken van video's in perfecte synchronisatie.",
|
||||
"FAQ_Q1": "Kan ik Netflix, Emby of Jellyfin samen in sync kijken met vrienden?",
|
||||
"FAQ_A1": "Ja! KoalaSync werkt op Netflix, YouTube, Twitch en elke website met een HTML5-videospeler — inclusief Emby, Jellyfin en zelfgehoste mediaservers. Installeer de extensie, maak een kamer, deel de uitnodigingslink en iedereen kijkt in perfecte sync.",
|
||||
"FAQ_A1": "Ja! KoalaSync werkt op Netflix, YouTube, Twitch en elke website met een HTML5-videospeler, inclusief Emby, Jellyfin en zelfgehoste mediaservers. Installeer de extensie, maak een kamer, deel de uitnodigingslink en iedereen kijkt in perfecte sync.",
|
||||
"FAQ_Q_VIDEO_ACCESS": "Streamt KoalaSync de video naar mijn vrienden?",
|
||||
"FAQ_A_VIDEO_ACCESS": "Nee. KoalaSync streamt, host of deelt geen videocontent en omzeilt geen toegang tot video’s. Iedereen kijkt de video lokaal in de eigen browser en heeft zelf toegang nodig tot de website of streamingdienst, bijvoorbeeld een eigen Netflix-account. KoalaSync synchroniseert alleen afspeelacties zoals afspelen, pauzeren en spoelen.",
|
||||
"FAQ_Q2": "Is KoalaSync een gratis alternatief voor Teleparty?",
|
||||
"FAQ_A2": "100% gratis en open-source onder de MIT-licentie. Geen premium-niveaus, geen vergrendelde functies, geen advertenties — alles is inbegrepen. In tegenstelling tot Teleparty is de relayserver van KoalaSync alleen in het RAM-geheugen met nul gegevensverzameling of gebruikersaccounts vereist.",
|
||||
"FAQ_A2": "100% gratis en open-source onder de MIT-licentie. Geen premium-niveaus, geen vergrendelde functies, geen advertenties, alles is inbegrepen. In tegenstelling tot Teleparty is de relayserver van KoalaSync alleen in het RAM-geheugen met nul gegevensverzameling of gebruikersaccounts vereist.",
|
||||
"FAQ_Q3": "Werkt KoalaSync met Emby, Jellyfin en zelfgehoste mediaservers?",
|
||||
"FAQ_A3": "Ja. KoalaSync detecteert elk standaard HTML5-video-element, wat betekent dat het perfect werkt met Emby, Jellyfin, Plex en elke aangepaste zelfgehoste streaminginstallatie. U kunt de KoalaSync-relayserver ook zelf hosten via Docker voor volledige soevereiniteit over uw gegevens.",
|
||||
"FAQ_Q4": "Welke browsers en platforms ondersteunen KoalaSync?",
|
||||
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave en alle op Chromium gebaseerde browsers. Installeer het rechtstreeks vanuit de Chrome Web Store of Firefox Add-ons — geen handmatige configuratie nodig.",
|
||||
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave en alle op Chromium gebaseerde browsers. Installeer het rechtstreeks vanuit de Chrome Web Store of Firefox Add-ons, geen handmatige configuratie nodig.",
|
||||
"FAQ_Q5": "Moeten al mijn vrienden de extensie geïnstalleerd hebben?",
|
||||
"FAQ_A5": "Ja, elke deelnemer moet KoalaSync geïnstalleerd hebben — maar er is geen aanmelding of accountcreatie. Klik gewoon op de uitnodigingslink, installeer de extensie als u dat nog niet hebt gedaan, en u neemt binnen enkele seconden automatisch deel aan de kamer.",
|
||||
"FAQ_A5": "Ja, elke deelnemer moet KoalaSync geïnstalleerd hebben, maar er is geen aanmelding of accountcreatie. Klik gewoon op de uitnodigingslink, installeer de extensie als u dat nog niet hebt gedaan, en u neemt binnen enkele seconden automatisch deel aan de kamer.",
|
||||
"FAQ_Q6": "Zijn mijn gegevens veilig? Wat als ik de officiële server niet vertrouw?",
|
||||
"FAQ_A6": "Volledig veilig. De relayserver van KoalaSync is alleen in het RAM-geheugen — alle sessiegegevens worden permanent verwijderd zodra u de kamer verlaat. Geen database, geen logboeken, geen analyses, geen accounts. En als u de voorkeur geeft aan volledige controle: u kunt uw eigen privé-relayserver binnen enkele minuten zelf hosten via Docker. De volledige codebase is open-source en auditeerbaar op GitHub.",
|
||||
"FAQ_A6": "Volledig veilig. De relayserver van KoalaSync is alleen in het RAM-geheugen, alle sessiegegevens worden permanent verwijderd zodra u de kamer verlaat. Geen database, geen logboeken, geen analyses, geen accounts. En als u de voorkeur geeft aan volledige controle: u kunt uw eigen privé-relayserver binnen enkele minuten zelf hosten via Docker. De volledige codebase is open-source en auditeerbaar op GitHub.",
|
||||
"FAQ_Q7": "Kan ik stille dialogen en harde explosies oplossen bij het kijken van video's in de browser?",
|
||||
"FAQ_A7": "Ja. KoalaSync 2.2.0 bevat een ingebouwde audiocompressor die het verschil tussen stille en luidruchtige delen van uw video verkleint. Het werkt lokaal via de Web Audio API — geen audiogegevens verlaten ooit uw computer. Open de popup, maak een kamer, ga naar Instellingen → Audio Processing, schakel de compressor in en kies een preset. Werkt op Netflix, Prime Video, Disney+, YouTube en elke HTML5-videosite.",
|
||||
"FAQ_A7": "Ja. KoalaSync 2.2.0 bevat een ingebouwde audiocompressor die het verschil tussen stille en luidruchtige delen van uw video verkleint. Het werkt lokaal via de Web Audio API, geen audiogegevens verlaten ooit uw computer. Open de popup, maak een kamer, ga naar Instellingen → Audio Processing, schakel de compressor in en kies een preset. Werkt op Netflix, Prime Video, Disney+, YouTube en elke HTML5-videosite.",
|
||||
"HOWTO_TITLE": "Hoe video's te synchroniseren met vrienden via KoalaSync",
|
||||
"HOWTO_DESC": "Bekijk elke video in perfecte synchronisatie met vrienden op YouTube, Netflix, Twitch en meer. Geen aanmelding, geen tracking.",
|
||||
"HOWTO_STEP_1_NAME": "Installeer de browserextensie",
|
||||
@@ -158,7 +168,7 @@
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> SYNC",
|
||||
"MOCK_14": "Spring naar anderen",
|
||||
"MOCK_15": "Laatste activiteitsstatus",
|
||||
"MOCK_16": "Wachten op 1 deelnemer (KoalaPC)...",
|
||||
"MOCK_16": "Wachten op 1 deelnemer (🐶 HappyDog)...",
|
||||
"MOCK_17": "Overslaan & toch afspelen",
|
||||
"MOCK_18": "Uw gebruikersnaam",
|
||||
"MOCK_19": "Overbodige tabbladen verbergen",
|
||||
@@ -179,6 +189,13 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync is niet verbonden aan, goedgekeurd door of geassocieerd met Netflix, Disney+, Amazon, YouTube, Twitch of enig ander streamingplatform. Alle handelsmerken zijn eigendom van hun respectieve eigenaren.",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_INDEX_TITLE": "KoalaSync - Alternatieven en vergelijkingen",
|
||||
"ALT_INDEX_META_DESC": "Ontdek eerlijke vergelijkingen en gedetailleerde gidsen tussen KoalaSync en andere watchparty-extensies en streamingoplossingen.",
|
||||
"ALT_INDEX_HEADING": "Gidsen en vergelijkingen",
|
||||
"ALT_INDEX_INTRO": "Hoe verhoudt KoalaSync zich tot andere watchparty-tools en streamingoplossingen? Lees onze eerlijke, technische vergelijkingen en vind de beste keuze voor je filmavonden op afstand.",
|
||||
"ALT_INDEX_READ_COMPARISON": "Vergelijking lezen →",
|
||||
"ALT_INDEX_READ_GUIDE": "Gids lezen →",
|
||||
"ALT_INDEX_FOOTER_LINK": "Gidsen en vergelijkingen",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync vs. Teleparty: welke tool voor 'watch parties' moet je kiezen?",
|
||||
"ALT_TELEPARTY_META_DESC": "Een praktische vergelijking tussen KoalaSync en Teleparty voor watch parties, met aandacht voor ingebouwde chat, mobiele ondersteuning, videosynchronisatie in de browser, self-hosted mediaservers en open-sourcegebruik.",
|
||||
"ALT_TELEPARTY_INTRO": "KoalaSync en Teleparty bieden allebei een oplossing voor hetzelfde vervelende probleem: samen iets kijken zonder dat gedoe met '3, 2, 1, afspelen'. Ze pakken het alleen anders aan.<br><br>Teleparty is de logische keuze als je een ingebouwde chatfunctie wilt of op mobiel wilt kijken. KoalaSync is meer geschikt voor watch parties via de desktopbrowser, waarbij je al met elkaar praat via Discord, Teamspeak of een andere spraakverbinding en alleen wilt dat het afspelen synchroon loopt.<br><br>De belangrijkste reden waarom ik KoalaSync heb ontwikkeld, is flexibiliteit. Het is bedoeld om te werken met vrijwel elke website die een standaard videospeler in de browser gebruikt, en niet slechts met een vaste lijst platforms. Dit omvat ook self-hosted systemen zoals Emby, Jellyfin en Plex, lokale of privé-domeinen en talloze reguliere videopagina's in de browser.",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "KoalaSync is geen vervanging voor een spraakgesprek. Als je tijdens het kijken wilt praten, gebruik je gewoon Discord, Teamspeak, Signal, WhatsApp of wat je normaal ook gebruikt.<br><br>Iedereen heeft ook toegang tot de video of pagina op het eigen apparaat nodig. KoalaSync omzeilt geen abonnementen, inloggegevens, DRM, regionale beperkingen of platformregels.<br><br>Het werkt momenteel alleen in desktopbrowsers, heeft geen ingebouwde chat- of spraakfunctie en de compatibiliteit hangt altijd af van de browser en de videospeler.",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "Samenvatting",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "Scherm delen is de eenvoudigste optie als je snel iets wilt doen en niet veel waarde hecht aan kwaliteit, privacy of individuele controle. KoalaSync is een betere keuze wanneer iedereen lokaal in de eigen browser moet kijken en de sessie meer moet aanvoelen als echt samen kijken.",
|
||||
"ALT_TRY_KOALASYNC": "Probeer KoalaSync"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "Probeer KoalaSync",
|
||||
"MOCK_HOST_CONTROL_LABEL": "Hostbeheer",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "Alleen ik mag de weergave bedienen",
|
||||
"LABEL_SELECT_VIDEO": "Selecteer video",
|
||||
"OPTION_SELECT_TAB": "-- Selecteer een tabblad --",
|
||||
"THEME_TOGGLE_LABEL": "Licht/donker thema wisselen",
|
||||
"FAQ_SHOW_ALL": "Alle vragen tonen",
|
||||
"FAQ_HIDE_ALL": "Vragen verbergen",
|
||||
"SELF_SHOW_SETUP": "Installatiehandleiding tonen",
|
||||
"SELF_HIDE_SETUP": "Handleiding verbergen",
|
||||
"HERO_TRUST": "Gratis · Open source · Geen account nodig",
|
||||
"COMP_MORE_GUIDES": "Volledige vergelijking en meer alternatieven"
|
||||
}
|
||||
|
||||
+74
-33
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "pl_PL",
|
||||
"META_TITLE": "KoalaSync | Synchronizuj Netflix, YouTube i dowolne wideo ze znajomymi",
|
||||
"META_DESCRIPTION": "Oglądaj Netflix, YouTube, Twitch i dowolne wideo HTML5 w idealnej synchronizacji ze znajomymi. Darmowe rozszerzenie o otwartym kodzie dla Chrome i Firefox. Bez rejestracji.",
|
||||
"SCHEMA_APP_DESC": "Oglądaj Netflix, YouTube, Twitch i dowolne wideo HTML5 w idealnej synchronizacji ze znajomymi. Darmowe rozszerzenie o otwartym kodzie dla Chrome i Firefox. Bez rejestracji.",
|
||||
"OG_TITLE": "KoalaSync | Synchronizuj Netflix, Emby, Jellyfin i prawie każde wideo ze znajomymi",
|
||||
"OG_DESCRIPTION": "Oglądaj Netflix, Emby, Jellyfin, YouTube, Twitch i prawie każde wideo HTML5 w idealnej synchronizacji. Zorientowane na prywatność rozszerzenie o otwartym kodzie dla Chrome i Firefox.",
|
||||
"TWITTER_TITLE": "KoalaSync | Synchronizuj Netflix, Emby, Jellyfin i prawie każde wideo ze znajomymi – Rozszerzenie przeglądarki",
|
||||
"TWITTER_DESCRIPTION": "Oglądaj Netflix, Emby, Jellyfin, YouTube, Twitch i prawie każde wideo HTML5 w idealnej synchronizacji ze znajomymi. Zorientowane na prywatność rozszerzenie o otwartym kodzie dla Chrome i Firefox.",
|
||||
"META_TITLE": "KoalaSync | Watch party dla Netflix, YouTube i innych",
|
||||
"META_DESCRIPTION": "Watch party na Netflix, YouTube, Twitch i dowolnym wideo HTML5. Darmowe, otwarte rozszerzenie do Chrome i Firefox. Bez konta, bez śledzenia, bez reklam.",
|
||||
"SCHEMA_APP_DESC": "Watch party na Netflix, YouTube, Twitch i dowolnym wideo HTML5. Darmowe, otwarte rozszerzenie do Chrome i Firefox. Bez konta, bez śledzenia, bez reklam.",
|
||||
"OG_TITLE": "KoalaSync | Watch party dla Netflix, Emby, Jellyfin i prawie każdego wideo",
|
||||
"OG_DESCRIPTION": "Zorganizuj watch party na Netflix, Emby, Jellyfin, YouTube, Twitch i prawie każdym wideo HTML5. Open source, prywatnie i bez konta.",
|
||||
"TWITTER_TITLE": "KoalaSync | Watch party dla Netflix, Emby, Jellyfin i prawie każdego wideo – Rozszerzenie",
|
||||
"TWITTER_DESCRIPTION": "Zorganizuj watch party na Netflix, Emby, Jellyfin, YouTube, Twitch i prawie każdym wideo HTML5. Open source, prywatnie i bez konta.",
|
||||
"NAV_FEATURES": "Funkcje",
|
||||
"NAV_HOW_IT_WORKS": "Jak to działa",
|
||||
"HERO_TITLE": "Oglądajcie Razem.<span class='hero-line2'>Synchronizujcie Idealnie.</span>",
|
||||
"HERO_SUBTITLE": "Twój zdalny wieczór filmowy bez opóźnień. Bez rejestracji, bez zbierania danych. Po prostu udostępnij link i oglądajcie razem.",
|
||||
"HERO_TITLE": "Zamień dowolne wideo w <span class='hero-highlight'>watch party</span>.",
|
||||
"HERO_SUBTITLE": "Twój zdalny wieczór filmowy bez opóźnień. Bez konta, bez śledzenia, bez reklam. Udostępnij link i naciśnijcie play razem.",
|
||||
"HERO_MASCOT_ALT": "Uroczy koala stojący i patrzący w dół na przyciski pobierania",
|
||||
"DEMO_SYNC": "Zsynchronizowane",
|
||||
"DEMO_HINT": "Demo na żywo: naciśnij play w dowolnej karcie, a KoalaSync utrzyma obie w synchronizacji.",
|
||||
"DEMO_NO_ROOM": "Jeszcze nie połączono",
|
||||
"DEMO_CREATE_ROOM": "Utwórz pokój",
|
||||
"DEMO_JOINED": "Dołączono do pokoju",
|
||||
"DEMO_TAB_SELECTED": "Karta wybrana",
|
||||
"DEMO_SELECT_PLACEHOLDER": "Wybierz kartę wideo…",
|
||||
"MOBILE_DEMO_CTA": "Zobacz, jak działa",
|
||||
"MOBILE_DEMO_TITLE": "Demo live KoalaSync",
|
||||
"MOBILE_DEMO_CLOSE": "Zamknij demo",
|
||||
"ADD_TO_CHROME": "Dodaj do Chrome",
|
||||
"ADD_TO_FIREFOX": "Dodaj do Firefox",
|
||||
"COMPAT_HEADING": "Działa na Twoich ulubionych platformach",
|
||||
@@ -35,22 +45,22 @@
|
||||
"USE_CASE_3_DESC": "Pokonaj odległość i ciesz się randkami. Przeżywajcie każdy zwrot akcji, śmiejcie się z tych samych żartów i dzielcie emocje w tej samej milisekundzie.",
|
||||
"WHY_TITLE": "Dlaczego KoalaSync?",
|
||||
"WHY_SUBTITLE": "Stworzony z myślą o niezawodnej synchronizacji, prywatności i łatwej konfiguracji.",
|
||||
"FEATURE_1_TITLE": "Pełna kontrola / Natychmiastowa synchronizacja",
|
||||
"FEATURE_1_DESC": "Jeden zatrzymuje, wszyscy zatrzymują. Jeden przewija, wszyscy podążają. Nasz dwufazowy protokół synchronizacji koordynuje odtwarzanie w czasie rzeczywistym u wszystkich uczestników.",
|
||||
"FEATURE_1_TITLE": "Kontrola hosta i synchronizacja",
|
||||
"FEATURE_1_DESC": "Decydujesz, kto steruje odtwarzaniem. Włącz kontrolę hosta, aby tylko Ty mógł wstrzymywać i przewijać, lub mianuj współhostów.",
|
||||
"FEATURE_2_TITLE": "Niekończące się oglądanie",
|
||||
"FEATURE_2_DESC": "Autoodtwarzanie w synchronizacji. KoalaSync automatycznie wykrywa zmiany odcinków i wstrzymuje odtwarzanie, dopóki wszyscy uczestnicy nie załadują następnego filmu.",
|
||||
"FEATURE_3_TITLE": "Zero kont / Czysta prywatność",
|
||||
"FEATURE_3_DESC": "Bez rejestracji, bez śledzenia, bez trwałych śladów. Serwer działa w całości w pamięci RAM i czyści Twój pokój, gdy go opuszczasz.",
|
||||
"FEATURE_4_TITLE": "Uniwersalne wsparcie HTML5",
|
||||
"FEATURE_4_DESC": "Działa na YouTube, Twitch, Netflix, Jellyfin, Emby i prawie każdej standardowej stronie z elementem wideo HTML5. Kompatybilny również z własnymi serwerami. Żadne dane wideo nigdy nie są wysyłane na nasze serwery — jedynie metadane synchronizacji.",
|
||||
"FEATURE_4_DESC": "Netflix, YouTube, Twitch, Jellyfin, Emby i niemal każda strona z wideo HTML5. Przeglądarkę opuszczają tylko sygnały synchronizacji, nigdy wideo.",
|
||||
"FEATURE_5_TITLE": "Możliwość własnego hostowania i wsparcie Docker",
|
||||
"FEATURE_5_DESC": "Nasze oficjalne serwery są darmowe i szybkie, ale możesz przejąć pełną kontrolę, jeśli chcesz. Uruchom własny serwer w kilka sekund za pomocą Dockera.",
|
||||
"FEATURE_6_TITLE": "Natychmiastowe zaproszenia / Dołączanie 1 kliknięciem",
|
||||
"FEATURE_6_DESC": "Bez wymiany adresów IP czy haseł. Udostępnij wygenerowany link zaproszenia znajomym, aby mogli automatycznie dołączyć do pokoju jednym kliknięciem.",
|
||||
"FEATURE_7_TITLE": "Wyrównywanie głośności / Kompresor audio",
|
||||
"FEATURE_7_DESC": "Koniec z ciągłym regulowaniem głośności. Wbudowany kompresor audio wzmacnia ciche dialogi i łagodzi głośne eksplozje. Działa na Netflix, Prime Video, Disney+, YouTube i każdym filmie HTML5. W pełni lokalny — żadne dane audio nie opuszczają twojego komputera.",
|
||||
"FEATURE_7_DESC": "Koniec z ciągłym regulowaniem głośności. Wbudowany kompresor podbija ciche dialogi i wycisza głośne wybuchy, a wszystko przetwarzane lokalnie w przeglądarce.",
|
||||
"FEATURE_8_TITLE": "Open Source (Licencja MIT)",
|
||||
"FEATURE_8_DESC": "Cały kod źródłowy jest publicznie dostępny i możliwy do zweryfikowania na GitHubie. Żadnych czarnych skrzynek, żadnych własnościowych obiektów blob — 100% przejrzystości od serwera przekaźnikowego (relay server) po rozszerzenie przeglądarki.",
|
||||
"FEATURE_8_DESC": "Cały kod źródłowy jest publicznie dostępny i możliwy do zweryfikowania na GitHubie. Żadnych czarnych skrzynek, żadnych własnościowych obiektów blob. 100% przejrzystości od serwera przekaźnikowego (relay server) po rozszerzenie przeglądarki.",
|
||||
"COMP_TITLE": "KoalaSync vs. Teleparty",
|
||||
"COMP_SUBTITLE": "Zobacz, dlaczego otwarty kod, brak reklam i ochrona prywatności to lepszy sposób na wspólne oglądanie.",
|
||||
"COMP_COL_FEATURE": "Funkcja",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Polityka prywatności Teleparty i zbieranie danych śledzenia:",
|
||||
"COMP_FOOTNOTE_4": "Działa na stronach zezwalających na wstrzykiwanie skryptów do standardowych tagów wideo HTML5. Strony z bardzo rygorystycznymi zasadami bezpieczeństwa (CSP), zabezpieczeniami DRM (kopiowanie) lub mocno zaciemnionymi odtwarzaczami (jak złożone systemy shadow DOM) mogą ograniczać zdalne sterowanie.",
|
||||
"STEPS_TITLE": "Jak zacząć",
|
||||
"STEP_1_TITLE": "Zainstaluj rozszerzenie",
|
||||
"STEP_1_DESC": "Dodaj KoalaSync do swojej przeglądarki z Chrome Web Store, Firefox Add-ons lub pobierz najnowsze archiwum ZIP dla deweloperów z GitHub.",
|
||||
"STEP_1_ILLUS_DESC": "Synchronizator wideo dbający o prywatność",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Pobierz dla Chrome",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Pobierz dla Firefox",
|
||||
"STEP_1_ILLUS_ACTIVE": "Rozszerzenie aktywne",
|
||||
"STEP_1_ILLUS_READY": "Gotowe do wspólnego oglądania!",
|
||||
"STEP_2_TITLE": "Utwórz pokój",
|
||||
"STEP_2_DESC": "Otwórz okienko rozszerzenia i kliknij '+ Utwórz nowy pokój'. KoalaSync automatycznie wygeneruje bezpieczny ID pokoju oraz hasło, dołączy do niego i skopiuje link zaproszenia do schowka.",
|
||||
"STEP_1_TITLE": "Utwórz i udostępnij pokój",
|
||||
"STEP_1_DESC": "Otwórz rozszerzenie, utwórz pokój jednym kliknięciem i udostępnij link znajomym. Dołączą natychmiast przez link z zaproszeniem.",
|
||||
"STEP_1_ILLUS_DESC": "Wybierz kartę do połączenia",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Połącz kartę YouTube",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Połącz kartę Netflix",
|
||||
"STEP_1_ILLUS_ACTIVE": "Karta połączona",
|
||||
"STEP_1_ILLUS_READY": "Gotowe do synchronizacji !",
|
||||
"STEP_2_TITLE": "Wybierz kartę z wideo",
|
||||
"STEP_2_DESC": "Przejdź do karty ze swoim wideo i wybierz ją w menu rozszerzenia. KoalaSync automatycznie połączy Wasze odtwarzacze.",
|
||||
"STEP_2_ILLUS_ROOM": "Pokój",
|
||||
"STEP_2_ILLUS_SYNC": "Sync",
|
||||
"STEP_2_ILLUS_SETTINGS": "Ustawienia",
|
||||
"STEP_2_ILLUS_CREATE": "+ Utwórz nowy pokój",
|
||||
"STEP_2_ILLUS_MANUAL": "Połączenie ręczne / Zaawansowane",
|
||||
"STEP_2_ILLUS_COPIED": "Link zaproszenia skopiowany!",
|
||||
"STEP_3_TITLE": "Udostępnij i synchronizuj",
|
||||
"STEP_3_DESC": "Wyślij link zaproszenia znajomym. Gdy dołączą, wybierz kartę z filmem i ciesz się zsynchronizowanym odtwarzaniem.",
|
||||
"STEP_3_TITLE": "Ciesz się synchronizacją",
|
||||
"STEP_3_DESC": "Naciśnij odtwarzaj. Gdy ktoś wstrzyma, przewinie lub odtworzy wideo, odtwarzanie u wszystkich pozostanie idealnie zsynchronizowane.",
|
||||
"STEP_3_ILLUS_IN_SYNC": "ZSYNCHRONIZOWANO",
|
||||
"SELF_TITLE": "Dla hostujących samodzielnie",
|
||||
"SELF_SUBTITLE": "Nie ufasz naszemu serwerowi? Zachowaj pełną suwerenność danych. Wdróż własny prywatny serwer w kilka minut.",
|
||||
@@ -116,21 +126,21 @@
|
||||
"FAQ_TITLE": "Często zadawane pytania",
|
||||
"FAQ_SUBTITLE": "Wszystko, co musisz wiedzieć o wspólnym oglądaniu filmów w idealnej synchronizacji.",
|
||||
"FAQ_Q1": "Czy mogę oglądać Netflix, Emby lub Jellyfin w synchronizacji ze znajomymi?",
|
||||
"FAQ_A1": "Tak! KoalaSync działa na Netflix, YouTube, Twitch i każdej stronie z odtwarzaczem wideo HTML5 — w tym Emby, Jellyfin i własnych serwerach mediów. Zainstaluj rozszerzenie, utwórz pokój, udostępnij link i wszyscy oglądają w idealnej synchronizacji.",
|
||||
"FAQ_A1": "Tak! KoalaSync działa na Netflix, YouTube, Twitch i każdej stronie z odtwarzaczem wideo HTML5, w tym Emby, Jellyfin i własnych serwerach mediów. Zainstaluj rozszerzenie, utwórz pokój, udostępnij link i wszyscy oglądają w idealnej synchronizacji.",
|
||||
"FAQ_Q_VIDEO_ACCESS": "Czy KoalaSync przesyła wideo do moich znajomych?",
|
||||
"FAQ_A_VIDEO_ACCESS": "Nie. KoalaSync nie przesyła, nie hostuje, nie udostępnia ani nie omija dostępu do treści wideo. Każda osoba ogląda wideo lokalnie we własnej przeglądarce i potrzebuje własnego dostępu do danej strony lub usługi streamingowej, na przykład własnego konta Netflix. KoalaSync synchronizuje tylko akcje odtwarzania, takie jak odtwarzanie, pauza i przewijanie.",
|
||||
"FAQ_Q2": "Czy KoalaSync to darmowa alternatywa dla Teleparty?",
|
||||
"FAQ_A2": "W 100% darmowy i o otwartym kodzie na licencji MIT. Brak planów premium, brak zablokowanych funkcji, brak reklam — wszystko jest w cenie. W przeciwieństwie do Teleparty, serwer KoalaSync działa tylko w pamięci RAM, bez zbierania danych i bez kont użytkowników.",
|
||||
"FAQ_A2": "W 100% darmowy i o otwartym kodzie na licencji MIT. Brak planów premium, brak zablokowanych funkcji, brak reklam, wszystko jest w cenie. W przeciwieństwie do Teleparty, serwer KoalaSync działa tylko w pamięci RAM, bez zbierania danych i bez kont użytkowników.",
|
||||
"FAQ_Q3": "Czy KoalaSync działa z Emby, Jellyfin i własnymi serwerami mediów?",
|
||||
"FAQ_A3": "Tak. KoalaSync wykrywa każdy standardowy element wideo HTML5, co oznacza, że działa idealnie z Emby, Jellyfin, Plexem i dowolnymi własnymi rozwiązaniami streamingowymi. Możesz również samodzielnie hostować serwer przez Dockera.",
|
||||
"FAQ_Q4": "Które przeglądarki i platformy obsługują KoalaSync?",
|
||||
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave i wszystkie przeglądarki oparte na Chromium. Zainstaluj je bezpośrednio z Chrome Web Store lub Firefox Add-ons — nie jest wymagana ręczna konfiguracja.",
|
||||
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave i wszystkie przeglądarki oparte na Chromium. Zainstaluj je bezpośrednio z Chrome Web Store lub Firefox Add-ons, nie jest wymagana ręczna konfiguracja.",
|
||||
"FAQ_Q5": "Czy wszyscy moi znajomi muszą mieć zainstalowane rozszerzenie?",
|
||||
"FAQ_A5": "Tak, każdy uczestnik musi mieć zainstalowane KoalaSync — ale nie ma rejestracji ani tworzenia kont. Po prostu kliknij link zaproszenia, zainstaluj rozszerzenie, jeśli jeszcze tego nie zrobiłeś, a dołączysz do pokoju automatycznie w kilka sekund.",
|
||||
"FAQ_A5": "Tak, każdy uczestnik musi mieć zainstalowane KoalaSync, ale nie ma rejestracji ani tworzenia kont. Po prostu kliknij link zaproszenia, zainstaluj rozszerzenie, jeśli jeszcze tego nie zrobiłeś, a dołączysz do pokoju automatycznie w kilka sekund.",
|
||||
"FAQ_Q6": "Czy moje dane są bezpieczne? Co jeśli nie ufam oficjalnemu serwerowi?",
|
||||
"FAQ_A6": "Całkowicie bezpieczne. Serwer KoalaSync działa tylko w pamięci RAM — wszystkie dane sesji są trwale usuwane w momencie opuszczenia pokoju. Brak bazy danych, brak logowania, brak analityki, brak kont. A jeśli wolisz pełną kontrolę: możesz samodzielmente uruchomić własny serwer przez Dockera w kilka minut. Cały kod źródłowy jest otwarty i dostępny na GitHubie.",
|
||||
"FAQ_A6": "Całkowicie bezpieczne. Serwer KoalaSync działa tylko w pamięci RAM, wszystkie dane sesji są trwale usuwane w momencie opuszczenia pokoju. Brak bazy danych, brak logowania, brak analityki, brak kont. A jeśli wolisz pełną kontrolę: możesz samodzielmente uruchomić własny serwer przez Dockera w kilka minut. Cały kod źródłowy jest otwarty i dostępny na GitHubie.",
|
||||
"FAQ_Q7": "Czy mogę naprawić ciche dialogi i głośne eksplozje podczas oglądania filmów w przeglądarce?",
|
||||
"FAQ_A7": "Tak. KoalaSync 2.2.0 zawiera wbudowany kompresor audio, który zmniejsza różnicę między cichymi a głośnymi częściami filmu. Działa lokalnie przez Web Audio API — żadne dane audio nie opuszczają twojego komputera. Otwórz popup, utwórz pokój, przejdź do Ustawienia → Audio Processing, włącz kompresor i wybierz preset. Działa na Netflix, Prime Video, Disney+, YouTube i każdej stronie z wideo HTML5.",
|
||||
"FAQ_A7": "Tak. KoalaSync 2.2.0 zawiera wbudowany kompresor audio, który zmniejsza różnicę między cichymi a głośnymi częściami filmu. Działa lokalnie przez Web Audio API, żadne dane audio nie opuszczają twojego komputera. Otwórz popup, utwórz pokój, przejdź do Ustawienia → Audio Processing, włącz kompresor i wybierz preset. Działa na Netflix, Prime Video, Disney+, YouTube i każdej stronie z wideo HTML5.",
|
||||
"HOWTO_TITLE": "Jak synchronizować wideo ze znajomymi za pomocą KoalaSync",
|
||||
"HOWTO_DESC": "Oglądaj dowolne wideo w idealnej synchronizacji ze znajomymi na YouTube, Netflix, Twitch i innych. Bez rejestracji, bez śledzenia.",
|
||||
"HOWTO_STEP_1_NAME": "Zainstaluj rozszerzenie przeglądarki",
|
||||
@@ -158,7 +168,7 @@
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> SYNC",
|
||||
"MOCK_14": "Skocz do innych",
|
||||
"MOCK_15": "Status ostatniej aktywności",
|
||||
"MOCK_16": "Oczekiwanie na 1 uczestnika (KoalaPC)...",
|
||||
"MOCK_16": "Oczekiwanie na 1 uczestnika (🐶 HappyDog)...",
|
||||
"MOCK_17": "Pomiń i odtwórz mimo to",
|
||||
"MOCK_18": "Twoja nazwa użytkownika",
|
||||
"MOCK_19": "Ukryj zbędne karty",
|
||||
@@ -179,6 +189,13 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync nie jest powiązany, wspierany ani stowarzyszony z Netflix, Disney+, Amazon, YouTube, Twitch ani żadną inną platformą streamingową. Wszystkie znaki towarowe są własnością ich odpowiednich właścicieli.",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_INDEX_TITLE": "KoalaSync - Alternatywy i porównania",
|
||||
"ALT_INDEX_META_DESC": "Poznaj uczciwe porównania i szczegółowe poradniki zestawiające KoalaSync z innymi rozszerzeniami do watch party oraz rozwiązaniami streamingowymi.",
|
||||
"ALT_INDEX_HEADING": "Poradniki i porównania",
|
||||
"ALT_INDEX_INTRO": "Jak KoalaSync wypada na tle innych narzędzi do watch party i rozwiązań streamingowych? Przeczytaj nasze uczciwe, techniczne porównania i wybierz najlepszą opcję na zdalne wieczory filmowe.",
|
||||
"ALT_INDEX_READ_COMPARISON": "Przeczytaj porównanie →",
|
||||
"ALT_INDEX_READ_GUIDE": "Przeczytaj poradnik →",
|
||||
"ALT_INDEX_FOOTER_LINK": "Poradniki i porównania",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync czy Teleparty: którego narzędzia do wspólnego oglądania użyć?",
|
||||
"ALT_TELEPARTY_META_DESC": "Praktyczne porównanie narzędzi KoalaSync i Teleparty pod kątem wspólnego oglądania: wbudowany czat, obsługa urządzeń mobilnych, synchronizacja wideo w przeglądarce, własne serwery multimediów i charakter open-source.",
|
||||
"ALT_TELEPARTY_INTRO": "Zarówno KoalaSync, jak i Teleparty rozwiązują ten sam uciążliwy problem: jak oglądać coś wspólnie bez konieczności odliczania „3, 2, 1, start”. Różnią się jednak podejściem do tego zadania.<br><br>Teleparty sprawdza się najlepiej, jeśli zależy Ci na wbudowanym czacie lub oglądaniu na urządzeniach mobilnych. KoalaSync jest raczej rozwiązaniem do wspólnego oglądania w przeglądarce na komputerze – gdy rozmawiacie już przez Discorda, Teamspeaka lub inny komunikator głosowy i chcecie jedynie zsynchronizować odtwarzanie.<br><br>Głównym powodem stworzenia KoalaSync była elastyczność. Narzędzie to ma współpracować z niemal każdą stroną wykorzystującą standardowy odtwarzacz wideo w przeglądarce, a nie tylko z wybraną listą platform. Obejmuje to własne serwery multimediów, takie jak Emby, Jellyfin czy Plex, domeny lokalne lub prywatne oraz wiele typowych stron z materiałami wideo.",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "KoalaSync nie zastępuje rozmowy głosowej. Jeśli chcesz rozmawiać podczas oglądania, nadal możesz korzystać z Discorda, TeamSpeaka, Signala, WhatsAppa lub innych narzędzi, których używasz na co dzień.<br><br>Każda osoba musi również mieć dostęp do filmu lub strony na własnym urządzeniu. KoalaSync nie omija subskrypcji, konieczności logowania, zabezpieczeń DRM, ograniczeń regionalnych ani regulaminów platform.<br><br>Obecnie rozwiązanie to działa tylko w przeglądarkach na komputerach stacjonarnych i nie posiada wbudowanego czatu ani funkcji komunikacji głosowej, a kompatybilność zależy od używanej przeglądarki oraz odtwarzacza wideo.",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "Podsumowanie",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "Udostępnianie ekranu to najprostsze rozwiązanie, gdy zależy Ci na szybkości, a kwestie jakości, prywatności czy indywidualnej kontroli nad odtwarzaniem nie są priorytetem. KoalaSync sprawdza się lepiej, gdy uczestnicy oglądają materiał lokalnie we własnych przeglądarkach, a sesja ma bardziej przypominać wspólny seans.",
|
||||
"ALT_TRY_KOALASYNC": "Wypróbuj KoalaSync"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "Wypróbuj KoalaSync",
|
||||
"MOCK_HOST_CONTROL_LABEL": "Kontrola hosta",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "Tylko ja mogę sterować odtwarzaniem",
|
||||
"LABEL_SELECT_VIDEO": "Wybierz wideo",
|
||||
"OPTION_SELECT_TAB": "-- Wybierz kartę --",
|
||||
"THEME_TOGGLE_LABEL": "Przełącz tryb jasny/ciemny",
|
||||
"FAQ_SHOW_ALL": "Pokaż wszystkie pytania",
|
||||
"FAQ_HIDE_ALL": "Ukryj pytania",
|
||||
"SELF_SHOW_SETUP": "Pokaż instrukcję instalacji",
|
||||
"SELF_HIDE_SETUP": "Ukryj instrukcję",
|
||||
"HERO_TRUST": "Za darmo · Open source · Bez konta",
|
||||
"COMP_MORE_GUIDES": "Pełne porównanie i więcej alternatyw"
|
||||
}
|
||||
|
||||
+68
-27
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "pt_BR",
|
||||
"META_TITLE": "KoalaSync | Sincronize Netflix, YouTube e qualquer vídeo com amigos",
|
||||
"META_DESCRIPTION": "Assista Netflix, YouTube, Twitch e qualquer vídeo HTML5 em perfeita sincronia. Extensão de navegador gratuita e open source. Sem cadastro.",
|
||||
"SCHEMA_APP_DESC": "Assista Netflix, YouTube, Twitch e qualquer vídeo HTML5 em perfeita sincronia. Extensão de navegador gratuita e open source. Sem cadastro.",
|
||||
"OG_TITLE": "KoalaSync | Sincronize Netflix, Emby, Jellyfin e vídeos com amigos",
|
||||
"OG_DESCRIPTION": "Assista a vídeos em perfeita sincronia com seus amigos. Extensão open source e focada em privacidade.",
|
||||
"TWITTER_TITLE": "KoalaSync | Sincronize vídeos online com seus amigos",
|
||||
"TWITTER_DESCRIPTION": "Assista a vídeos junto com seus amigos em sincronia perfeita. Open source e seguro.",
|
||||
"META_TITLE": "KoalaSync | Watch party para Netflix, YouTube e mais",
|
||||
"META_DESCRIPTION": "Watch party na Netflix, YouTube, Twitch e qualquer vídeo HTML5. Extensão gratuita e open source para Chrome e Firefox. Sem conta, sem rastreamento, sem anúncios.",
|
||||
"SCHEMA_APP_DESC": "Watch party na Netflix, YouTube, Twitch e qualquer vídeo HTML5. Extensão gratuita e open source para Chrome e Firefox. Sem conta, sem rastreamento, sem anúncios.",
|
||||
"OG_TITLE": "KoalaSync | Watch party para Netflix, Emby, Jellyfin e quase qualquer vídeo",
|
||||
"OG_DESCRIPTION": "Crie uma watch party na Netflix, Emby, Jellyfin, YouTube, Twitch e quase qualquer vídeo HTML5. Open source, privado e sem conta.",
|
||||
"TWITTER_TITLE": "KoalaSync | Watch party para Netflix, Emby, Jellyfin e quase qualquer vídeo – Extensão",
|
||||
"TWITTER_DESCRIPTION": "Crie uma watch party na Netflix, Emby, Jellyfin, YouTube, Twitch e quase qualquer vídeo HTML5. Open source, privado e sem conta.",
|
||||
"NAV_FEATURES": "Recursos",
|
||||
"NAV_HOW_IT_WORKS": "Como Funciona",
|
||||
"HERO_TITLE": "Assistam Juntos.<span class='hero-line2'>Sincronia Perfeita.</span>",
|
||||
"HERO_SUBTITLE": "Sua noite de cinema à distância sem atrasos. Sem contas, sem coleta de dados. Apenas compartilhe o link e assistam juntos.",
|
||||
"HERO_TITLE": "Transforme qualquer vídeo em uma <span class='hero-highlight'>watch party</span>.",
|
||||
"HERO_SUBTITLE": "Sua sessão de cinema à distância, sem atrasos. Sem conta, sem rastreamento, sem anúncios. Compartilhe um link e deem play juntos.",
|
||||
"HERO_MASCOT_ALT": "Um coala fofo olhando para os botões de download",
|
||||
"DEMO_SYNC": "Sincronizado",
|
||||
"DEMO_HINT": "Demo ao vivo: dê play em qualquer aba e o KoalaSync mantém as duas em sincronia.",
|
||||
"DEMO_NO_ROOM": "Ainda não conectado",
|
||||
"DEMO_CREATE_ROOM": "Criar sala",
|
||||
"DEMO_JOINED": "Entrou na sala",
|
||||
"DEMO_TAB_SELECTED": "Aba selecionada",
|
||||
"DEMO_SELECT_PLACEHOLDER": "Escolha uma aba de vídeo…",
|
||||
"MOBILE_DEMO_CTA": "Ver como funciona",
|
||||
"MOBILE_DEMO_TITLE": "Demo ao vivo do KoalaSync",
|
||||
"MOBILE_DEMO_CLOSE": "Fechar demo",
|
||||
"ADD_TO_CHROME": "Adicionar ao Chrome",
|
||||
"ADD_TO_FIREFOX": "Adicionar ao Firefox",
|
||||
"COMPAT_HEADING": "Funciona nas suas plataformas favoritas",
|
||||
@@ -35,20 +45,20 @@
|
||||
"USE_CASE_3_DESC": "Aproveite as noites a dois. Ria das mesmas piadas e sinta as mesmas emoções exatamente no mesmo segundo.",
|
||||
"WHY_TITLE": "Por que o KoalaSync?",
|
||||
"WHY_SUBTITLE": "Sincronização confiável, máxima privacidade e uso simples.",
|
||||
"FEATURE_1_TITLE": "Controle Total / Sincronia Instantânea",
|
||||
"FEATURE_1_DESC": "Se um pausa, todos pausam. Se um avança, todos seguem. Sincronização em tempo real para todos os participantes.",
|
||||
"FEATURE_1_TITLE": "Controle do host e sincronização",
|
||||
"FEATURE_1_DESC": "Você decide quem controla a reprodução. Ative o controle do anfitrião para que apenas você possa pausar e avançar, ou nomeie coanfitriões.",
|
||||
"FEATURE_2_TITLE": "Maratonas Sem Interrupções",
|
||||
"FEATURE_2_DESC": "O KoalaSync detecta a mudança de episódios e pausa automaticamente até que todos tenham carregado o vídeo seguinte.",
|
||||
"FEATURE_3_TITLE": "Sem Contas / Privacidade Absoluta",
|
||||
"FEATURE_3_DESC": "Sem cadastros ou rastreio. O servidor roda apenas na RAM e apaga sua sala assim que você sai.",
|
||||
"FEATURE_4_TITLE": "Suporte HTML5 Universal",
|
||||
"FEATURE_4_DESC": "Funciona no YouTube, Twitch, Netflix, Jellyfin, Emby e em qualquer site web. Nenhum dado de vídeo é enviado a nós.",
|
||||
"FEATURE_4_DESC": "Netflix, YouTube, Twitch, Jellyfin, Emby e quase qualquer página com vídeo HTML5. Só saem os sinais de sincronização, nunca o vídeo.",
|
||||
"FEATURE_5_TITLE": "Servidores Próprios (Docker)",
|
||||
"FEATURE_5_DESC": "Nossos servidores são gratuitos, mas você pode assumir o controle total e iniciar seu próprio servidor privado em segundos com Docker.",
|
||||
"FEATURE_6_TITLE": "Convites Rápidos / Entrar com 1 Clique",
|
||||
"FEATURE_6_DESC": "Chega de senhas complicadas. Compartilhe o link e seus amigos entram na sala automaticamente.",
|
||||
"FEATURE_7_TITLE": "Compressor de Áudio Integrado",
|
||||
"FEATURE_7_DESC": "O compressor de áudio reforça diálogos baixos e diminui o volume de explosões. Funciona localmente, nenhum áudio sai do seu computador.",
|
||||
"FEATURE_7_DESC": "Nunca mais mexer no volume. Um compressor integrado realça diálogos baixos e suaviza explosões altas, tudo processado localmente no seu navegador.",
|
||||
"FEATURE_8_TITLE": "Open Source (Licença MIT)",
|
||||
"FEATURE_8_DESC": "Todo o código é público no GitHub. 100% de transparência, sem softwares ocultos.",
|
||||
"COMP_TITLE": "KoalaSync vs Teleparty",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Políticas de privacidade do Teleparty:",
|
||||
"COMP_FOOTNOTE_4": "Funciona em sites que permitem injeção de scripts. Plataformas com DRM estrito podem ter limitações.",
|
||||
"STEPS_TITLE": "Como Começar",
|
||||
"STEP_1_TITLE": "Instalar a Extensão",
|
||||
"STEP_1_DESC": "Adicione o KoalaSync ao seu navegador (Chrome ou Firefox).",
|
||||
"STEP_1_ILLUS_DESC": "Extensão segura para ver vídeos",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Baixar para Chrome",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Baixar para Firefox",
|
||||
"STEP_1_ILLUS_ACTIVE": "Extensão Ativa",
|
||||
"STEP_1_ILLUS_READY": "Pronto!",
|
||||
"STEP_2_TITLE": "Criar uma Sala",
|
||||
"STEP_2_DESC": "Abra a extensão e clique em '+ Criar nova sala'. O link de convite é copiado automaticamente.",
|
||||
"STEP_1_TITLE": "Criar e compartilhar a sala",
|
||||
"STEP_1_DESC": "Abra a extensão, crie uma sala com um clique e compartilhe o link com seus amigos. Eles entram instantaneamente pelo link de convite.",
|
||||
"STEP_1_ILLUS_DESC": "Escolha uma guia para vincular",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Vincular guia do YouTube",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Vincular guia do Netflix",
|
||||
"STEP_1_ILLUS_ACTIVE": "Guia vinculada",
|
||||
"STEP_1_ILLUS_READY": "Pronto para ver em sincronia !",
|
||||
"STEP_2_TITLE": "Selecionar a guia do vídeo",
|
||||
"STEP_2_DESC": "Acesse a guia do seu vídeo e selecione-a no menu da extensão. O KoalaSync vincula seus reprodutores automaticamente.",
|
||||
"STEP_2_ILLUS_ROOM": "Sala",
|
||||
"STEP_2_ILLUS_SYNC": "Sincronia",
|
||||
"STEP_2_ILLUS_SETTINGS": "Configurações",
|
||||
"STEP_2_ILLUS_CREATE": "+ Criar nova sala",
|
||||
"STEP_2_ILLUS_MANUAL": "Manual / Avançado",
|
||||
"STEP_2_ILLUS_COPIED": "Link copiado!",
|
||||
"STEP_3_TITLE": "Compartilhar e Assistir",
|
||||
"STEP_3_DESC": "Envie o link aos amigos. Escolha um vídeo e aproveitem juntos.",
|
||||
"STEP_3_TITLE": "Aproveitar a sincronização",
|
||||
"STEP_3_DESC": "Inicie a reprodução. Se alguém pausar, avançar ou reproduzir, o vídeo de todos se mantém sincronizado em tempo real e sem atrasos.",
|
||||
"STEP_3_ILLUS_IN_SYNC": "SINCRONIZADOS",
|
||||
"SELF_TITLE": "Para usuários avançados",
|
||||
"SELF_SUBTITLE": "Prefere gerenciar seus próprios dados? Inicie seu próprio servidor em minutos com Docker.",
|
||||
@@ -158,7 +168,7 @@
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> SINCRO",
|
||||
"MOCK_14": "Ir para a posição dos outros",
|
||||
"MOCK_15": "Última Atividade",
|
||||
"MOCK_16": "Aguardando 1 amigo (KoalaPC)...",
|
||||
"MOCK_16": "Aguardando 1 amigo (🐶 HappyDog)...",
|
||||
"MOCK_17": "Pular e reproduzir agora",
|
||||
"MOCK_18": "Seu Nome",
|
||||
"MOCK_19": "Ocultar abas sem vídeo",
|
||||
@@ -179,6 +189,13 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync não é afiliado à Netflix, Disney+, YouTube, Twitch, etc. Todas as marcas registradas são de propriedade de seus respectivos donos.",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_INDEX_TITLE": "KoalaSync - Alternativas e comparações",
|
||||
"ALT_INDEX_META_DESC": "Explore comparações honestas e guias detalhados entre o KoalaSync e outras extensões de watch party e soluções de streaming.",
|
||||
"ALT_INDEX_HEADING": "Guias e comparações",
|
||||
"ALT_INDEX_INTRO": "Como o KoalaSync se compara a outras ferramentas de watch party e soluções de streaming? Leia nossas comparações honestas e técnicas para encontrar a melhor opção para suas noites de cinema à distância.",
|
||||
"ALT_INDEX_READ_COMPARISON": "Ler comparação →",
|
||||
"ALT_INDEX_READ_GUIDE": "Ler guia →",
|
||||
"ALT_INDEX_FOOTER_LINK": "Guias e comparações",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync vs. Teleparty: qual ferramenta de watch party você deve usar?",
|
||||
"ALT_TELEPARTY_META_DESC": "Uma comparação prática entre KoalaSync e Teleparty para watch parties, abordando chat integrado, suporte para dispositivos móveis, sincronização de vídeo no navegador, servidores de mídia hospedados pelo próprio usuário e uso de código aberto.",
|
||||
"ALT_TELEPARTY_INTRO": "Tanto o KoalaSync quanto o Teleparty ajudam a resolver o mesmo problema incômodo: assistir a algo em conjunto sem precisar daquela rotina de \"3, 2, 1, play\". A diferença é a abordagem que cada um utiliza.<br><br>O Teleparty é a opção mais indicada se você deseja um chat integrado ou quer assistir pelo celular. O KoalaSync é mais voltado para watch parties em navegadores de desktop, onde você já conversa via Discord, Teamspeak ou outra chamada de voz e só precisa que a reprodução do vídeo permaneça sincronizada.<br><br>O principal motivo pelo qual criei o KoalaSync foi a flexibilidade. Ele foi projetado para funcionar com praticamente qualquer site que utilize um reprodutor de vídeo padrão de navegador, e não apenas com uma lista fixa de plataformas. Isso inclui sistemas hospedados pelo próprio usuário, como Emby, Jellyfin e Plex, domínios locais ou privados e muitas páginas de vídeo comuns na web.",
|
||||
@@ -191,7 +208,7 @@
|
||||
"ALT_TELEPARTY_LIMITS_TITLE": "Limitações reais",
|
||||
"ALT_TELEPARTY_LIMITS_BODY": "O KoalaSync não faz mágica e não tenta esconder suas limitações. Atualmente, ele funciona apenas em navegadores para desktop. Não possui chat integrado nem aplicativo para dispositivos móveis.<br><br>A compatibilidade também depende do navegador e do reprodutor de vídeo utilizado. O KoalaSync deve funcionar com praticamente qualquer reprodutor de vídeo padrão de navegador, mas os sites podem alterar seus reprodutores, o comportamento de DRM ou as restrições do navegador a qualquer momento.<br><br>O KoalaSync não tem vínculo com Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex ou qualquer outra marca mencionada aqui. Os nomes são utilizados apenas para ilustrar casos de uso comuns.",
|
||||
"ALT_TELEPARTY_SUMMARY_TITLE": "Resumo",
|
||||
"ALT_TELEPARTY_SUMMARY_BODY": "Se você busca chat integrado ou suporte para dispositivos móveis, o Teleparty pode ser a melhor opção. Se você prefere uma solução gratuita e de código aberto para sincronização em navegadores de desktop — que funcione com praticamente qualquer reprodutor de vídeo comum, incluindo servidores de mídia auto-hospedados como Emby, Jellyfin e Plex —, o KoalaSync é provavelmente a escolha ideal.",
|
||||
"ALT_TELEPARTY_SUMMARY_BODY": "Se você busca chat integrado ou suporte para dispositivos móveis, o Teleparty pode ser a melhor opção. Se você prefere uma solução gratuita e de código aberto para sincronização em navegadores de desktop, que funcione com praticamente qualquer reprodutor de vídeo comum, incluindo servidores de mídia auto-hospedados como Emby, Jellyfin e Plex, o KoalaSync é provavelmente a escolha ideal.",
|
||||
"ALT_SCREEN_TITLE": "KoalaSync vs. compartilhamento de tela: quando a sincronização de vídeo é melhor?",
|
||||
"ALT_SCREEN_META_DESC": "Uma comparação prática entre o KoalaSync e o compartilhamento de tela para sessões de cinema remotas, abordando controle de reprodução, qualidade de vídeo, privacidade, legendas e visualização via navegador.",
|
||||
"ALT_SCREEN_INTRO": "O compartilhamento de tela funciona e, honestamente, às vezes é o suficiente. Uma pessoa compartilha a tela, as outras assistem e pronto.<br><br>Mas, para sessões de cinema mais longas ou para assistir a episódios em conjunto, isso pode se tornar incômodo. A qualidade do vídeo depende da velocidade de upload, da CPU e do navegador de quem está compartilhando, além da compressão aplicada por aplicativos como Discord, Teamspeak, Zoom ou outros. Além disso, apenas uma pessoa realmente controla a reprodução.",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "O KoalaSync não substitui uma chamada de voz. Se você quiser conversar enquanto assiste, continue usando Discord, Teamspeak, Signal, WhatsApp ou qualquer ferramenta que utilize normalmente.<br><br>Todos também precisam ter acesso ao vídeo ou à página em seus próprios dispositivos. O KoalaSync não contorna assinaturas, logins, DRM, restrições regionais ou regras da plataforma.<br><br>Atualmente, funciona apenas em navegadores para desktop, não possui recursos integrados de chat ou voz, e a compatibilidade depende sempre do navegador e do reprodutor de vídeo.",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "Resumo",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "O compartilhamento de tela é a opção mais simples quando você quer algo rápido e não se preocupa muito com qualidade, privacidade ou controle individual. O KoalaSync é mais adequado quando todos devem assistir localmente em seus próprios navegadores e a sessão deve proporcionar uma experiência mais próxima de realmente assistir juntos.",
|
||||
"ALT_TRY_KOALASYNC": "Experimente o KoalaSync"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "Experimente o KoalaSync",
|
||||
"MOCK_HOST_CONTROL_LABEL": "Controle do anfitrião",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "Apenas eu posso controlar a reprodução",
|
||||
"LABEL_SELECT_VIDEO": "Selecionar vídeo",
|
||||
"OPTION_SELECT_TAB": "-- Selecione uma aba --",
|
||||
"THEME_TOGGLE_LABEL": "Alternar modo claro/escuro",
|
||||
"FAQ_SHOW_ALL": "Mostrar todas as perguntas",
|
||||
"FAQ_HIDE_ALL": "Ocultar perguntas",
|
||||
"SELF_SHOW_SETUP": "Mostrar guia de configuração",
|
||||
"SELF_HIDE_SETUP": "Ocultar guia",
|
||||
"HERO_TRUST": "Grátis · Código aberto · Sem conta",
|
||||
"COMP_MORE_GUIDES": "Comparação completa e mais alternativas"
|
||||
}
|
||||
|
||||
+67
-26
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "pt_PT",
|
||||
"META_TITLE": "KoalaSync | Sincronize Netflix, YouTube e vídeos com os amigos",
|
||||
"META_DESCRIPTION": "Veja Netflix, YouTube, Twitch e qualquer vídeo HTML5 em sincronia perfeita. Extensão gratuita e de código aberto. Sem registo.",
|
||||
"SCHEMA_APP_DESC": "Veja Netflix, YouTube, Twitch e qualquer vídeo HTML5 em sincronia perfeita. Extensão gratuita e de código aberto. Sem registo.",
|
||||
"OG_TITLE": "KoalaSync | Sincronize Netflix, Emby, Jellyfin e vídeos com os amigos",
|
||||
"OG_DESCRIPTION": "Veja vídeos em sincronia perfeita com os amigos. Extensão de código aberto e focada na privacidade.",
|
||||
"TWITTER_TITLE": "KoalaSync | Sincronize vídeos em streaming com os amigos",
|
||||
"TWITTER_DESCRIPTION": "Veja vídeos com os amigos em sincronia perfeita. Extensão de código aberto focada na privacidade.",
|
||||
"META_TITLE": "KoalaSync | Watch party para Netflix, YouTube e mais",
|
||||
"META_DESCRIPTION": "Watch party na Netflix, YouTube, Twitch e qualquer vídeo HTML5. Extensão gratuita e open source para Chrome e Firefox. Sem conta, sem rastreio, sem anúncios.",
|
||||
"SCHEMA_APP_DESC": "Watch party na Netflix, YouTube, Twitch e qualquer vídeo HTML5. Extensão gratuita e open source para Chrome e Firefox. Sem conta, sem rastreio, sem anúncios.",
|
||||
"OG_TITLE": "KoalaSync | Watch party para Netflix, Emby, Jellyfin e quase qualquer vídeo",
|
||||
"OG_DESCRIPTION": "Cria uma watch party na Netflix, Emby, Jellyfin, YouTube, Twitch e quase qualquer vídeo HTML5. Open source, privado e sem conta.",
|
||||
"TWITTER_TITLE": "KoalaSync | Watch party para Netflix, Emby, Jellyfin e quase qualquer vídeo – Extensão",
|
||||
"TWITTER_DESCRIPTION": "Cria uma watch party na Netflix, Emby, Jellyfin, YouTube, Twitch e quase qualquer vídeo HTML5. Open source, privado e sem conta.",
|
||||
"NAV_FEATURES": "Funcionalidades",
|
||||
"NAV_HOW_IT_WORKS": "Como Funciona",
|
||||
"HERO_TITLE": "Vejam Juntos.<span class='hero-line2'>Sincronia Perfeita.</span>",
|
||||
"HERO_SUBTITLE": "A sua noite de cinema à distância e sem atrasos. Sem contas, sem recolha de dados. Partilhe um link e vejam juntos.",
|
||||
"HERO_TITLE": "Transforma qualquer vídeo numa <span class='hero-highlight'>watch party</span>.",
|
||||
"HERO_SUBTITLE": "A tua noite de cinema à distância, sem atrasos. Sem conta, sem rastreio, sem anúncios. Partilha um link e carreguem no play juntos.",
|
||||
"HERO_MASCOT_ALT": "Um coala fofo a olhar para os botões de download",
|
||||
"DEMO_SYNC": "Sincronizado",
|
||||
"DEMO_HINT": "Demo ao vivo: dê play em qualquer aba e o KoalaSync mantém as duas em sincronia.",
|
||||
"DEMO_NO_ROOM": "Ainda não conectado",
|
||||
"DEMO_CREATE_ROOM": "Criar sala",
|
||||
"DEMO_JOINED": "Entrou na sala",
|
||||
"DEMO_TAB_SELECTED": "Separador selecionado",
|
||||
"DEMO_SELECT_PLACEHOLDER": "Escolha um separador de vídeo…",
|
||||
"MOBILE_DEMO_CTA": "Ver como funciona",
|
||||
"MOBILE_DEMO_TITLE": "Demo ao vivo do KoalaSync",
|
||||
"MOBILE_DEMO_CLOSE": "Fechar demo",
|
||||
"ADD_TO_CHROME": "Adicionar ao Chrome",
|
||||
"ADD_TO_FIREFOX": "Adicionar ao Firefox",
|
||||
"COMPAT_HEADING": "Funciona nas suas plataformas favoritas",
|
||||
@@ -35,20 +45,20 @@
|
||||
"USE_CASE_3_DESC": "Aproveite as noites a dois. Ria das mesmas piadas e sinta as mesmas emoções exatamente no mesmo milissegundo.",
|
||||
"WHY_TITLE": "Porquê o KoalaSync?",
|
||||
"WHY_SUBTITLE": "Sincronização fiável, máxima privacidade e uso simples.",
|
||||
"FEATURE_1_TITLE": "Controlo Total / Sincronia Instantânea",
|
||||
"FEATURE_1_DESC": "Se um pausa, todos pausam. Se um avança, todos seguem. Sincronização em tempo real para todos os participantes.",
|
||||
"FEATURE_1_TITLE": "Controlo do host e sincronização",
|
||||
"FEATURE_1_DESC": "Tu decides quem controla a reprodução. Ativa o controlo do anfitrião para que apenas tu possas pausar e avançar, ou nomeia co-anfitriões.",
|
||||
"FEATURE_2_TITLE": "Maratonas sem interrupções",
|
||||
"FEATURE_2_DESC": "O KoalaSync deteta a mudança de episódios e pausa automaticamente até que todos tenham carregado o vídeo seguinte.",
|
||||
"FEATURE_3_TITLE": "Sem Contas / Privacidade Absoluta",
|
||||
"FEATURE_3_DESC": "Sem registos ou rastreios. O servidor corre apenas na RAM e apaga a sua sala assim que sair.",
|
||||
"FEATURE_4_TITLE": "Suporte HTML5 Universal",
|
||||
"FEATURE_4_DESC": "Funciona no YouTube, Twitch, Netflix, Jellyfin, Emby e qualquer site web. Nenhum dado de vídeo é enviado para nós.",
|
||||
"FEATURE_4_DESC": "Netflix, YouTube, Twitch, Jellyfin, Emby e quase qualquer página com vídeo HTML5. Só saem os sinais de sincronização, nunca o vídeo.",
|
||||
"FEATURE_5_TITLE": "Servidores Próprios (Docker)",
|
||||
"FEATURE_5_DESC": "Os nossos servidores são gratuitos, mas pode assumir o controlo total e iniciar o seu próprio servidor privado em segundos com Docker.",
|
||||
"FEATURE_6_TITLE": "Convites Rápidos / Entrar com 1 Clique",
|
||||
"FEATURE_6_DESC": "Nada de senhas complicadas. Partilhe o link e os seus amigos entram na sala automaticamente.",
|
||||
"FEATURE_7_TITLE": "Compressor de Áudio Integrado",
|
||||
"FEATURE_7_DESC": "O compressor de áudio reforça os diálogos suaves e baixa o volume das explosões. Funciona localmente, nenhum áudio sai do seu computador.",
|
||||
"FEATURE_7_DESC": "Nunca mais mexer no volume. Um compressor integrado realça diálogos baixos e suaviza explosões altas, tudo processado localmente no teu navegador.",
|
||||
"FEATURE_8_TITLE": "Código Aberto (Licença MIT)",
|
||||
"FEATURE_8_DESC": "Todo o código é público no GitHub. 100% de transparência, sem software oculto.",
|
||||
"COMP_TITLE": "KoalaSync vs Teleparty",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Políticas de privacidade do Teleparty:",
|
||||
"COMP_FOOTNOTE_4": "Funciona em websites que permitem a injeção de scripts. Plataformas com DRM estrito podem ter limitações.",
|
||||
"STEPS_TITLE": "Como Começar",
|
||||
"STEP_1_TITLE": "Instalar a Extensão",
|
||||
"STEP_1_DESC": "Adicione o KoalaSync ao seu navegador (Chrome ou Firefox).",
|
||||
"STEP_1_ILLUS_DESC": "Extensão segura para ver vídeos",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Descarregar para Chrome",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Descarregar para Firefox",
|
||||
"STEP_1_ILLUS_ACTIVE": "Extensão Ativa",
|
||||
"STEP_1_ILLUS_READY": "Pronto!",
|
||||
"STEP_2_TITLE": "Criar uma Sala",
|
||||
"STEP_2_DESC": "Abra a extensão e clique em '+ Criar Nova Sala'. O link de convite é copiado automaticamente.",
|
||||
"STEP_1_TITLE": "Criar e partilhar a sala",
|
||||
"STEP_1_DESC": "Abra a extensão, crie uma sala com um clique e partilhe o link com os seus amigos. Eles entram instantaneamente pelo link de convite.",
|
||||
"STEP_1_ILLUS_DESC": "Escolha um separador para ligar",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Ligar separador do YouTube",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Ligar separador do Netflix",
|
||||
"STEP_1_ILLUS_ACTIVE": "Separador ligado",
|
||||
"STEP_1_ILLUS_READY": "Pronto para ver em sincronia !",
|
||||
"STEP_2_TITLE": "Selecionar o separador do vídeo",
|
||||
"STEP_2_DESC": "Aceda ao separador do seu vídeo e selecione-o no menu da extensão. O KoalaSync liga os vossos leitores automaticamente.",
|
||||
"STEP_2_ILLUS_ROOM": "Sala",
|
||||
"STEP_2_ILLUS_SYNC": "Sincronia",
|
||||
"STEP_2_ILLUS_SETTINGS": "Definições",
|
||||
"STEP_2_ILLUS_CREATE": "+ Criar Nova Sala",
|
||||
"STEP_2_ILLUS_MANUAL": "Manual / Avançado",
|
||||
"STEP_2_ILLUS_COPIED": "Link copiado!",
|
||||
"STEP_3_TITLE": "Partilhar e Sincronizar",
|
||||
"STEP_3_DESC": "Envie o link aos amigos. Escolha um vídeo e desfrutem da reprodução juntos.",
|
||||
"STEP_3_TITLE": "Desfrutar da sincronização",
|
||||
"STEP_3_DESC": "Inicie a reprodução. Se alguém pausar, avançar ou reproduzir, o vídeo de todos mantém-se sincronizado em tempo real e sem atrasos.",
|
||||
"STEP_3_ILLUS_IN_SYNC": "SINCRONIZADOS",
|
||||
"SELF_TITLE": "Para utilizadores avançados",
|
||||
"SELF_SUBTITLE": "Prefere gerir os seus dados? Inicie o seu próprio servidor em minutos com Docker.",
|
||||
@@ -158,7 +168,7 @@
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> SINCRO",
|
||||
"MOCK_14": "Ir para a posição dos outros",
|
||||
"MOCK_15": "Última Atividade",
|
||||
"MOCK_16": "À espera de 1 amigo (KoalaPC)...",
|
||||
"MOCK_16": "À espera de 1 amigo (🐶 HappyDog)...",
|
||||
"MOCK_17": "Ignorar e reproduzir agora",
|
||||
"MOCK_18": "O seu Nome",
|
||||
"MOCK_19": "Ocultar separadores sem vídeo",
|
||||
@@ -179,6 +189,13 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync não é afiliado à Netflix, Disney+, YouTube, Twitch, etc. Todas as marcas são propriedade dos seus titulares.",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_INDEX_TITLE": "KoalaSync - Alternativas e comparações",
|
||||
"ALT_INDEX_META_DESC": "Explore comparações honestas e guias detalhados entre o KoalaSync e outras extensões de watch party e soluções de streaming.",
|
||||
"ALT_INDEX_HEADING": "Guias e comparações",
|
||||
"ALT_INDEX_INTRO": "Como o KoalaSync se compara a outras ferramentas de watch party e soluções de streaming? Leia as nossas comparações honestas e técnicas para encontrar a melhor opção para as suas noites de cinema à distância.",
|
||||
"ALT_INDEX_READ_COMPARISON": "Ler comparação →",
|
||||
"ALT_INDEX_READ_GUIDE": "Ler guia →",
|
||||
"ALT_INDEX_FOOTER_LINK": "Guias e comparações",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync vs Teleparty: que ferramenta de festa de exibição deve utilizar?",
|
||||
"ALT_TELEPARTY_META_DESC": "Uma comparação prática entre o KoalaSync e o Teleparty para festas de exibição, chat integrado, suporte para dispositivos móveis, sincronização de vídeo no browser, servidores de media auto-hospedados e utilização de código aberto.",
|
||||
"ALT_TELEPARTY_INTRO": "O KoalaSync e o Teleparty resolvem o mesmo problema irritante: ver algo em conjunto sem ter de fazer toda aquela rotina de \"3, 2, 1, reproduzir\". Apenas usam abordagens diferentes.<br><br>O Teleparty é mais indicado se pretende um chat integrado ou se pretende ver em dispositivos móveis. O KoalaSync está mais virado para sessões de vídeo em browsers de desktop, onde já estão a conversar no Discord, Teamspeak ou noutra chamada de voz e apenas querem que a reprodução permaneça sincronizada.<br><br>A principal razão para eu ter criado o KoalaSync foi a flexibilidade. Foi concebido para funcionar com praticamente qualquer site que utilize um leitor de vídeo comum de browser, e não apenas com uma lista fixa de plataformas.",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "O KoalaSync não substitui uma chamada de voz. Se quiser conversar enquanto assiste, utilize ainda o Discord, Teamspeak, Signal, WhatsApp ou qualquer outra aplicação que utilize normalmente.<br><br>Todos também precisam de ter acesso ao vídeo ou à página nos seus próprios dispositivos. O KoalaSync não ignora as subscrições, logins, DRM, restrições regionais ou regras de plataforma.<br><br>Atualmente, funciona apenas em navegadores de desktop, não tem chat ou recurso de voz integrados e a compatibilidade depende sempre do navegador e do leitor de vídeo.",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "Resumo",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "Partilhar o ecrã é a opção mais simples quando se quer algo rápido e não se liga muito à qualidade, privacidade ou controlo individual. O KoalaSync é mais adequado quando todos devem assistir localmente nos seus próprios browsers e a sessão deve parecer mais como assistir em conjunto.",
|
||||
"ALT_TRY_KOALASYNC": "Experimente o KoalaSync"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "Experimente o KoalaSync",
|
||||
"MOCK_HOST_CONTROL_LABEL": "Controlo do anfitrião",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "Apenas eu posso controlar a reprodução",
|
||||
"LABEL_SELECT_VIDEO": "Selecionar Vídeo",
|
||||
"OPTION_SELECT_TAB": "-- Selecionar um Separador --",
|
||||
"THEME_TOGGLE_LABEL": "Alternar modo claro/escuro",
|
||||
"FAQ_SHOW_ALL": "Mostrar todas as perguntas",
|
||||
"FAQ_HIDE_ALL": "Ocultar perguntas",
|
||||
"SELF_SHOW_SETUP": "Mostrar guia de configuração",
|
||||
"SELF_HIDE_SETUP": "Ocultar guia",
|
||||
"HERO_TRUST": "Grátis · Open source · Sem conta",
|
||||
"COMP_MORE_GUIDES": "Comparação completa e mais alternativas"
|
||||
}
|
||||
|
||||
+69
-28
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "ru_RU",
|
||||
"META_TITLE": "KoalaSync | Синхронизация Netflix, YouTube и любого видео с друзьями",
|
||||
"META_DESCRIPTION": "Смотрите Netflix, YouTube, Twitch и любые HTML5-видео в синхронизации с друзьями. Бесплатное расширение браузера с открытым кодом. Регистрация не требуется.",
|
||||
"SCHEMA_APP_DESC": "Смотрите Netflix, YouTube, Twitch и любые HTML5-видео в синхронизации с друзьями. Бесплатное расширение браузера с открытым кодом. Регистрация не требуется.",
|
||||
"OG_TITLE": "KoalaSync | Синхронизация Netflix, Emby, Jellyfin и почти любого видео с друзьями",
|
||||
"OG_DESCRIPTION": "Смотрите Netflix, Emby, Jellyfin, YouTube, Twitch и почти любое HTML5-видео в идеальной синхронизации. Открытое и ориентированное на конфиденциальность расширение для Chrome и Firefox.",
|
||||
"TWITTER_TITLE": "KoalaSync | Синхронизация Netflix, Emby, Jellyfin и почти любого видео с друзьями – Расширение",
|
||||
"TWITTER_DESCRIPTION": "Смотрите Netflix, Emby, Jellyfin, YouTube, Twitch и почти любое HTML5-видео в идеальной синхронизации с друзьями. Открытое и конфиденциальное расширение для Chrome и Firefox.",
|
||||
"META_TITLE": "KoalaSync | Watch party для Netflix, YouTube и любых видео",
|
||||
"META_DESCRIPTION": "Watch party в Netflix, YouTube, Twitch и любом HTML5-видео. Бесплатное расширение с открытым кодом для Chrome и Firefox. Без аккаунта, слежки и рекламы.",
|
||||
"SCHEMA_APP_DESC": "Watch party в Netflix, YouTube, Twitch и любом HTML5-видео. Бесплатное расширение с открытым кодом для Chrome и Firefox. Без аккаунта, слежки и рекламы.",
|
||||
"OG_TITLE": "KoalaSync | Watch party для Netflix, Emby, Jellyfin и почти любого видео",
|
||||
"OG_DESCRIPTION": "Запускайте watch party в Netflix, Emby, Jellyfin, YouTube, Twitch и почти любом HTML5-видео. Open source, приватно и без аккаунта.",
|
||||
"TWITTER_TITLE": "KoalaSync | Watch party для Netflix, Emby, Jellyfin и почти любого видео – Расширение",
|
||||
"TWITTER_DESCRIPTION": "Запускайте watch party в Netflix, Emby, Jellyfin, YouTube, Twitch и почти любом HTML5-видео. Open source, приватно и без аккаунта.",
|
||||
"NAV_FEATURES": "Функции",
|
||||
"NAV_HOW_IT_WORKS": "Как это работает",
|
||||
"HERO_TITLE": "Смотрите вместе.<span class='hero-line2'>Синхронно на 100%.</span>",
|
||||
"HERO_SUBTITLE": "Ваш киновечер на расстоянии без задержек. Без регистрации и сбора данных. Просто поделитесь ссылкой и смотрите вместе.",
|
||||
"HERO_TITLE": "Преврати любое видео в <span class='hero-highlight'>watch party</span>.",
|
||||
"HERO_SUBTITLE": "Ваш киновечер на расстоянии, без задержек. Без аккаунта, без слежки, без рекламы. Просто поделитесь ссылкой и нажмите play вместе.",
|
||||
"HERO_MASCOT_ALT": "Милый коала стоит и смотрит вниз на кнопки загрузки",
|
||||
"DEMO_SYNC": "Синхронно",
|
||||
"DEMO_HINT": "Живое демо: нажмите play в любой вкладке, и KoalaSync синхронизирует обе.",
|
||||
"DEMO_NO_ROOM": "Ещё не подключено",
|
||||
"DEMO_CREATE_ROOM": "Создать комнату",
|
||||
"DEMO_JOINED": "В комнате",
|
||||
"DEMO_TAB_SELECTED": "Вкладка выбрана",
|
||||
"DEMO_SELECT_PLACEHOLDER": "Выберите вкладку с видео…",
|
||||
"MOBILE_DEMO_CTA": "Посмотреть, как работает",
|
||||
"MOBILE_DEMO_TITLE": "Живое демо KoalaSync",
|
||||
"MOBILE_DEMO_CLOSE": "Закрыть демо",
|
||||
"ADD_TO_CHROME": "Установить в Chrome",
|
||||
"ADD_TO_FIREFOX": "Установить в Firefox",
|
||||
"COMPAT_HEADING": "Работает на ваших любимых платформах",
|
||||
@@ -35,20 +45,20 @@
|
||||
"USE_CASE_3_DESC": "Сократите расстояние и наслаждайтесь свиданиями. Переживайте каждый поворот сюжета, смейтесь над шутками и делитесь эмоциями в одну и ту же миллисекунду.",
|
||||
"WHY_TITLE": "Почему KoalaSync?",
|
||||
"WHY_SUBTITLE": "Создан для надежной синхронизации, конфиденциальности и простой настройки.",
|
||||
"FEATURE_1_TITLE": "Полный контроль и мгновенный синхро",
|
||||
"FEATURE_1_DESC": "Один ставит на паузу — пауза у всех. Один перематывает — все перематывают. Наш двухфазный протокол координирует воспроизведение в реальном времени у всех участников.",
|
||||
"FEATURE_1_TITLE": "Управление хоста и синхронизация",
|
||||
"FEATURE_1_DESC": "Вы решаете, кто управляет воспроизведением. Включите управление ведущего, чтобы только вы могли ставить на паузу и перемотку, или назначайте со-ведущих.",
|
||||
"FEATURE_2_TITLE": "Бесконечные марафоны",
|
||||
"FEATURE_2_DESC": "Синхронное автовоспроизведение. KoalaSync автоматически определяет переход на следующую серию и ждет, пока она загрузится у всех участников.",
|
||||
"FEATURE_3_TITLE": "Без аккаунтов и хранения данных",
|
||||
"FEATURE_3_DESC": "Без регистрации, без отслеживания, без следов на диске. Сервер работает исключительно во временной RAM и полностью стирает вашу комнату при выходе.",
|
||||
"FEATURE_4_TITLE": "Универсальная поддержка HTML5",
|
||||
"FEATURE_4_DESC": "Работает на YouTube, Twitch, Netflix, Jellyfin, Emby и почти на любой веб-странице со стандартным HTML5-видеотегом. Подходит для локальных медиатек. Никакие видеоданные никогда не отправляются на наши серверы — только метаданные синхронизации.",
|
||||
"FEATURE_4_DESC": "Netflix, YouTube, Twitch, Jellyfin, Emby и почти любая страница с HTML5-видео. Из браузера уходят только сигналы синхронизации, но не само видео.",
|
||||
"FEATURE_5_TITLE": "Self-Hosted и готов к Docker",
|
||||
"FEATURE_5_DESC": "Наши официальные серверы бесплатны и быстры, но вы можете получить полный контроль. Запустите свой собственный приватный ретранслятор в Docker за секунды.",
|
||||
"FEATURE_6_TITLE": "Быстрые приглашения в 1 клик",
|
||||
"FEATURE_6_DESC": "Не нужно обмениваться IP-адресами или паролями. Отправьте сгенерированную ссылку друзьям, чтобы они автоматически вошли в комнату в один клик.",
|
||||
"FEATURE_7_TITLE": "Выравнивание громкости / Аудио компрессор",
|
||||
"FEATURE_7_DESC": "Больше никакой постоянной регулировки громкости. Встроенный аудио компрессор усиливает тихие диалоги и приглушает громкие взрывы. Работает на Netflix, Prime Video, Disney+, YouTube и любом HTML5-видео. Полностью локально — никакие аудиоданные не покидают ваш компьютер.",
|
||||
"FEATURE_7_DESC": "Больше не нужно крутить громкость. Встроенный компрессор поднимает тихие диалоги и смягчает громкие взрывы, обрабатывая всё локально в браузере.",
|
||||
"FEATURE_8_TITLE": "Открытый исходный код (Лицензия MIT)",
|
||||
"FEATURE_8_DESC": "Весь исходный код публично доступен для проверки на GitHub. Никаких «черных ящиков», никаких проприетарных бинарных файлов (blobs) — 100% прозрачность от relay-сервера до расширения браузера.",
|
||||
"COMP_TITLE": "KoalaSync против Teleparty",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Политика конфиденциальности Teleparty и сбор трекеров:",
|
||||
"COMP_FOOTNOTE_4": "Работает на сайтах, разрешающих инъекцию скриптов в стандартные видеотеги HTML5. Сайты с очень строгой политикой безопасности контента (CSP), защитой от копирования DRM или сильно обфусцированными оболочками плееров (например, сложные теневые DOM) могут блокировать автоматическое управление.",
|
||||
"STEPS_TITLE": "С чего начать",
|
||||
"STEP_1_TITLE": "Установить расширение",
|
||||
"STEP_1_DESC": "Добавьте KoalaSync в браузер из Chrome Web Store, Firefox Add-ons or скачайте последний ZIP-архив разработчика с GitHub.",
|
||||
"STEP_1_ILLUS_DESC": "Ориентированный на конфиденциальность синхронизатор видео",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Скачать для Chrome",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Скачать для Firefox",
|
||||
"STEP_1_ILLUS_ACTIVE": "Расширение активно",
|
||||
"STEP_1_ILLUS_READY": "Готовы смотреть вместе!",
|
||||
"STEP_2_TITLE": "Создать комнату",
|
||||
"STEP_2_DESC": "Откройте меню расширения и нажмите «+ Создать новую комнату». KoalaSync автоматически сгенерирует ID и пароль, войдет в нее и скопирует ссылку-приглашение.",
|
||||
"STEP_1_TITLE": "Создать комнату и поделиться",
|
||||
"STEP_1_DESC": "Откройте расширение, создайте комнату одним кликом и отправьте ссылку друзьям. Они мгновенно подключатся по приглашению.",
|
||||
"STEP_1_ILLUS_DESC": "Выберите вкладку для подключения",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Подключить вкладку YouTube",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Подключить вкладку Netflix",
|
||||
"STEP_1_ILLUS_ACTIVE": "Вкладка подключена",
|
||||
"STEP_1_ILLUS_READY": "Готово к синхронному просмотру !",
|
||||
"STEP_2_TITLE": "Выбрать вкладку с видео",
|
||||
"STEP_2_DESC": "Перейдите на вкладку с видео и выберите её в меню расширения. KoalaSync автоматически свяжет ваши плееры.",
|
||||
"STEP_2_ILLUS_ROOM": "Комната",
|
||||
"STEP_2_ILLUS_SYNC": "Синхро",
|
||||
"STEP_2_ILLUS_SETTINGS": "Опции",
|
||||
"STEP_2_ILLUS_CREATE": "+ Создать комнату",
|
||||
"STEP_2_ILLUS_MANUAL": "Ручное подключение / Дополнительно",
|
||||
"STEP_2_ILLUS_COPIED": "Ссылка скопирована!",
|
||||
"STEP_3_TITLE": "Поделиться и смотреть",
|
||||
"STEP_3_DESC": "Отправьте ссылку друзьям. Как только они присоединятся, выберите вкладку с видео и наслаждайтесь синхронным просмотром.",
|
||||
"STEP_3_TITLE": "Наслаждаться синхронизацией",
|
||||
"STEP_3_DESC": "Нажмите кнопку воспроизведения. Когда кто-то ставит на паузу, перематывает или запускает видео, просмотр синхронизируется у всех без задержек.",
|
||||
"STEP_3_ILLUS_IN_SYNC": "В СИНХРОНЕ",
|
||||
"SELF_TITLE": "Для селф-хостеров",
|
||||
"SELF_SUBTITLE": "Не доверяете нашему серверу? Сохраняйте полный контроль над данными. Разверните собственный приватный сервер за пару минут.",
|
||||
@@ -126,9 +136,9 @@
|
||||
"FAQ_Q4": "Какие браузеры и платформы поддерживают KoalaSync?",
|
||||
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave и все браузеры на базе Chromium. Установите из Chrome Web Store или Firefox Add-ons.",
|
||||
"FAQ_Q5": "Всем ли моим друзьям нужно устанавливать расширение?",
|
||||
"FAQ_A5": "Да, каждому участнику нужен KoalaSync — но без регистрации и создания учётной записи. Просто перейдите по ссылке-приглашению, установите расширение (если ещё нет) и вы автоматически в комнате.",
|
||||
"FAQ_A5": "Да, каждому участнику нужен KoalaSync, но без регистрации и создания учётной записи. Просто перейдите по ссылке-приглашению, установите расширение (если ещё нет) и вы автоматически в комнате.",
|
||||
"FAQ_Q6": "Мои данные в безопасности? Что, если я не доверяю официальному серверу?",
|
||||
"FAQ_A6": "Полностью безопасно. Relay-сервер KoalaSync работает только в ОЗУ — все данные сессии удаляются при выходе из комнаты. Никаких баз данных, логов, аналитики и учётных записей. Если хотите полный контроль: разверните свой приватный relay-сервер через Docker за пару минут. Исходный код полностью открыт и доступен для аудита на GitHub.",
|
||||
"FAQ_A6": "Полностью безопасно. Relay-сервер KoalaSync работает только в ОЗУ, все данные сессии удаляются при выходе из комнаты. Никаких баз данных, логов, аналитики и учётных записей. Если хотите полный контроль: разверните свой приватный relay-сервер через Docker за пару минут. Исходный код полностью открыт и доступен для аудита на GitHub.",
|
||||
"FAQ_Q7": "Можно ли исправить тихие диалоги и громкие взрывы при просмотре видео в браузере?",
|
||||
"FAQ_A7": "Да. KoalaSync 2.2.0 включает встроенный аудио компрессор, который уменьшает разницу между тихими и громкими частями видео. Он работает локально через Web Audio API — аудиоданные никогда не покидают ваш компьютер. Откройте попап, создайте комнату, перейдите в Настройки → Audio Processing, включите компрессор и выберите пресет. Работает на Netflix, Prime Video, Disney+, YouTube и любом сайте с HTML5-видео.",
|
||||
"HOWTO_TITLE": "Как синхронизировать видео с друзьями с помощью KoalaSync",
|
||||
@@ -157,7 +167,7 @@
|
||||
"MOCK_12": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"14\" height=\"14\" aria-hidden=\"true\"><rect x=\"6\" y=\"4\" width=\"4\" height=\"16\"/><rect x=\"14\" y=\"4\" width=\"4\" height=\"16\"/></svg> Пауза",
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> SYNC",
|
||||
"MOCK_14": "Последняя активность",
|
||||
"MOCK_15": "Ожидание 1 участника (KoalaPC)...",
|
||||
"MOCK_15": "Ожидание 1 участника (🐶 HappyDog)...",
|
||||
"MOCK_16": "Пропустить и играть",
|
||||
"MOCK_17": "Ваше имя",
|
||||
"MOCK_18": "Скрыть лишние вкладки",
|
||||
@@ -179,6 +189,13 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync не связан, не спонсируется и не аффилирован с Netflix, Disney+, Amazon, YouTube, Twitch или любой другой стриминговой платформой. Все товарные знаки являются собственностью их соответствующих владельцев.",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_INDEX_TITLE": "KoalaSync — альтернативы и сравнения",
|
||||
"ALT_INDEX_META_DESC": "Изучите честные сравнения и подробные руководства о KoalaSync, других расширениях для совместного просмотра и стриминговых решениях.",
|
||||
"ALT_INDEX_HEADING": "Руководства и сравнения",
|
||||
"ALT_INDEX_INTRO": "Как KoalaSync выглядит на фоне других инструментов для совместного просмотра и стриминговых решений? Прочитайте наши честные технические сравнения, чтобы выбрать лучший вариант для удалённых киновечеров.",
|
||||
"ALT_INDEX_READ_COMPARISON": "Читать сравнение →",
|
||||
"ALT_INDEX_READ_GUIDE": "Читать руководство →",
|
||||
"ALT_INDEX_FOOTER_LINK": "Руководства и сравнения",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync или Teleparty: какой инструмент выбрать для совместного просмотра?",
|
||||
"ALT_TELEPARTY_META_DESC": "Практическое сравнение KoalaSync и Teleparty: встроенный чат, поддержка мобильных устройств, синхронизация видео в браузере, использование собственных медиасерверов и открытый исходный код.",
|
||||
"ALT_TELEPARTY_INTRO": "И KoalaSync, и Teleparty решают одну и ту же надоедливую проблему: как смотреть что-либо вместе, не прибегая к команде «3, 2, 1, начали!». Просто они используют разные подходы.<br><br>Teleparty — отличный выбор, если вам нужен встроенный чат или возможность просмотра на мобильном устройстве. KoalaSync больше подходит для совместного просмотра через браузер на компьютере: вы общаетесь, например, в Discord, TeamSpeak или другом голосовом чате, а программа просто обеспечивает синхронизацию воспроизведения.<br><br>Главной причиной создания KoalaSync стала гибкость. Инструмент задуман так, чтобы работать практически с любым сайтом, использующим стандартный браузерный видеоплеер, а не только с фиксированным списком платформ. Это касается и решений для самостоятельного хостинга (например, Emby, Jellyfin и Plex), и локальных или частных доменов, и множества обычных веб-страниц с видео.",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "KoalaSync не заменяет голосовую связь. Если во время просмотра вы хотите общаться, используйте Discord, TeamSpeak, Signal, WhatsApp или любой другой привычный вам сервис.<br><br>Каждому участнику также необходим доступ к видео или веб-странице на собственном устройстве. KoalaSync не обходит требования к подписке, авторизации, DRM-защите, региональные ограничения или правила платформ.<br><br>На данный момент сервис работает только в браузерах на ПК, не имеет встроенного чата или функции голосовой связи, а совместимость зависит от конкретного браузера и видеоплеера.",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "Краткий обзор",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "Демонстрация экрана — самый простой вариант, если нужно быстро всё организовать и вас не особо волнуют качество, конфиденциальность или возможность индивидуального управления. KoalaSync лучше подходит для ситуаций, когда каждый участник смотрит видео локально в своем браузере, а сам сеанс больше напоминает реальный совместный просмотр.",
|
||||
"ALT_TRY_KOALASYNC": "Попробовать KoalaSync"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "Попробовать KoalaSync",
|
||||
"MOCK_HOST_CONTROL_LABEL": "Управление ведущим",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "Только я могу управлять воспроизведением",
|
||||
"LABEL_SELECT_VIDEO": "Выбрать видео",
|
||||
"OPTION_SELECT_TAB": "-- Выберите вкладку --",
|
||||
"THEME_TOGGLE_LABEL": "Переключить светлую/тёмную тему",
|
||||
"FAQ_SHOW_ALL": "Показать все вопросы",
|
||||
"FAQ_HIDE_ALL": "Скрыть вопросы",
|
||||
"SELF_SHOW_SETUP": "Показать инструкцию по установке",
|
||||
"SELF_HIDE_SETUP": "Скрыть инструкцию",
|
||||
"HERO_TRUST": "Бесплатно · Открытый код · Без аккаунта",
|
||||
"COMP_MORE_GUIDES": "Полное сравнение и другие альтернативы"
|
||||
}
|
||||
|
||||
+73
-32
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "tr_TR",
|
||||
"META_TITLE": "KoalaSync | Netflix, YouTube ve Herhangi Bir Videoyu Arkadaşlarınızla Eşitleyin",
|
||||
"META_DESCRIPTION": "Netflix, YouTube, Twitch ve herhangi bir HTML5 videoyu arkadaşlarınızla mükemmel uyum içinde izleyin. Chrome ve Firefox için ücretsiz, açık kaynaklı tarayıcı eklentisi. Kayıt gerekmez.",
|
||||
"SCHEMA_APP_DESC": "Netflix, YouTube, Twitch ve herhangi bir HTML5 videoyu arkadaşlarınızla mükemmel uyum içinde izleyin. Chrome ve Firefox için ücretsiz, açık kaynaklı tarayıcı eklentisi. Kayıt gerekmez.",
|
||||
"OG_TITLE": "KoalaSync | Netflix, Emby, Jellyfin ve Neredeyse Her Videoyu Arkadaşlarınızla Eşitleyin",
|
||||
"OG_DESCRIPTION": "Netflix, Emby, Jellyfin, YouTube, Twitch ve neredeyse tüm HTML5 videolarını mükemmel bir senkronizasyonla izleyin. Chrome ve Firefox için gizlilik öncelikli, açık kaynaklı tarayıcı eklentisi.",
|
||||
"TWITTER_TITLE": "KoalaSync | Netflix, Emby, Jellyfin ve Neredeyse Her Videoyu Arkadaşlarla Eşitle – Tarayıcı Eklentisi",
|
||||
"TWITTER_DESCRIPTION": "Netflix, Emby, Jellyfin, YouTube, Twitch ve neredeyse tüm HTML5 videolarını arkadaşlarınızla mükemmel bir senkronizasyonla izleyin. Chrome ve Firefox için gizlilik öncelikli, açık kaynaklı tarayıcı eklentisi.",
|
||||
"META_TITLE": "KoalaSync | Netflix, YouTube ve Her Video için Watch Party",
|
||||
"META_DESCRIPTION": "Netflix, YouTube, Twitch ve her HTML5 videoda watch party. Chrome ve Firefox için ücretsiz, açık kaynak uzantı. Hesap yok, takip yok, reklam yok.",
|
||||
"SCHEMA_APP_DESC": "Netflix, YouTube, Twitch ve her HTML5 videoda watch party. Chrome ve Firefox için ücretsiz, açık kaynak uzantı. Hesap yok, takip yok, reklam yok.",
|
||||
"OG_TITLE": "KoalaSync | Netflix, Emby, Jellyfin ve neredeyse her video için watch party",
|
||||
"OG_DESCRIPTION": "Netflix, Emby, Jellyfin, YouTube, Twitch ve neredeyse tüm HTML5 videolarında watch party başlatın. Açık kaynak, gizlilik odaklı ve hesapsız.",
|
||||
"TWITTER_TITLE": "KoalaSync | Netflix, Emby, Jellyfin ve neredeyse her video için watch party – Eklenti",
|
||||
"TWITTER_DESCRIPTION": "Netflix, Emby, Jellyfin, YouTube, Twitch ve neredeyse tüm HTML5 videolarında watch party başlatın. Açık kaynak, gizlilik odaklı ve hesapsız.",
|
||||
"NAV_FEATURES": "Özellikler",
|
||||
"NAV_HOW_IT_WORKS": "Nasıl Çalışır",
|
||||
"HERO_TITLE": "Birlikte İzleyin.<span class='hero-line2'>Mükemmel Eşitleyin.</span>",
|
||||
"HERO_SUBTITLE": "Gecikmesiz uzaktan film geceniz. Kayıt yok, veri toplama yok. Sadece bir bağlantı paylaşın ve birlikte izleyin.",
|
||||
"HERO_TITLE": "Herhangi bir videoyu <span class='hero-highlight'>watch party</span>'ye dönüştür.",
|
||||
"HERO_SUBTITLE": "Gecikmesiz uzaktan film gecen. Hesap yok, takip yok, reklam yok. Sadece bir bağlantı paylaş ve birlikte oynatın.",
|
||||
"HERO_MASCOT_ALT": "İndirme düğmelerine bakan sevimli bir koala",
|
||||
"DEMO_SYNC": "Senkronize",
|
||||
"DEMO_HINT": "Canlı demo: sekmelerden birinde oynat'a basın, KoalaSync ikisini de senkronize tutar.",
|
||||
"DEMO_NO_ROOM": "Henüz bağlı değil",
|
||||
"DEMO_CREATE_ROOM": "Oda oluştur",
|
||||
"DEMO_JOINED": "Odaya katıldı",
|
||||
"DEMO_TAB_SELECTED": "Sekme seçildi",
|
||||
"DEMO_SELECT_PLACEHOLDER": "Bir video sekmesi seçin…",
|
||||
"MOBILE_DEMO_CTA": "Nasıl çalıştığını izle",
|
||||
"MOBILE_DEMO_TITLE": "KoalaSync canlı demo",
|
||||
"MOBILE_DEMO_CLOSE": "Demoyu kapat",
|
||||
"ADD_TO_CHROME": "Chrome'a Ekle",
|
||||
"ADD_TO_FIREFOX": "Firefox'a Ekle",
|
||||
"COMPAT_HEADING": "En sevdiğiniz platformlarda çalışır",
|
||||
@@ -35,22 +45,22 @@
|
||||
"USE_CASE_3_DESC": "Mesafeleri köprüleyin ve buluşma gecelerinin tadını çıkarın. Her olay örgüsünü deneyimleyin, aynı şakalara gülün ve duygusal anları tam olarak aynı milisaniyede paylaşın.",
|
||||
"WHY_TITLE": "Neden KoalaSync?",
|
||||
"WHY_SUBTITLE": "Güvenilir eşitleme, önce gizlilik ilkesi ve kolay kurulum için tasarlandı.",
|
||||
"FEATURE_1_TITLE": "Tam Kontrol / Anında Eşitleme",
|
||||
"FEATURE_1_DESC": "Biri duraklatır, herkes duraklatır. Biri arar, herkes takip eder. İki aşamalı eşitleme protokolümüz, oynatmayı tüm katılımcılar arasında gerçek zamanlı olarak koordine eder.",
|
||||
"FEATURE_1_TITLE": "Oturum Sahibi Kontrolü ve Senkronizasyon",
|
||||
"FEATURE_1_DESC": "Oynatmayı kimin kontrol edeceğine siz karar verin. Yalnızca sizin durdurup sarabilmeniz için sunucu kontrolünü etkinleştirin veya ortak sunucular atayın.",
|
||||
"FEATURE_2_TITLE": "Kesintisiz İzleme Keyfi",
|
||||
"FEATURE_2_DESC": "Senkronize otomatik oynatma. KoalaSync, bölüm geçişlerini otomatik olarak algılar ve tüm bağlantılar bir sonraki videoyu başarıyla yükleyene kadar oynatmayı bekletir.",
|
||||
"FEATURE_3_TITLE": "Sıfır Hesap / Saf Gizlilik",
|
||||
"FEATURE_3_DESC": "Kayıt yok, takip yok, kalıcı iz yok. Sunucu tamamen geçici RAM üzerinde çalışır ve ayrıldığınızda odanızı temizler.",
|
||||
"FEATURE_4_TITLE": "Evrensel HTML5 Desteği",
|
||||
"FEATURE_4_DESC": "YouTube, Twitch, Netflix, Jellyfin, Emby ve HTML5 video öğesi içeren neredeyse tüm standart web sayfalarında çalışır. Ayrıca özel barındırılan kurulumlarla da uyumludur. Sunucularımıza hiçbir video verisi asla gönderilmez — yalnızca senkronizasyon meta verileri gönderilir.",
|
||||
"FEATURE_4_DESC": "Netflix, YouTube, Twitch, Jellyfin, Emby ve HTML5 videosu olan neredeyse her sayfa. Tarayıcından yalnızca sync sinyalleri çıkar, video asla.",
|
||||
"FEATURE_5_TITLE": "Kendi Kendine Barındırılabilir ve Docker Uyumlu",
|
||||
"FEATURE_5_DESC": "Resmi sunucularımız ücretsiz ve hızlıdır, ancak isterseniz tüm kontrolü elinize alabilirsiniz. Docker aracılığıyla saniyeler içinde kendi özel aktarım sunucunuzu başlatın.",
|
||||
"FEATURE_6_TITLE": "Anında Davetler / Tek Tıkla Katılım",
|
||||
"FEATURE_6_DESC": "Takas edilecek IP adresi veya şifre yok. Arkadaşlarınızın tek bir tıklamayla odanıza otomatik olarak katılması için oluşturulan bir davet bağlantısını paylaşın.",
|
||||
"FEATURE_7_TITLE": "Ses Seviyesi Dengeleme / Ses Sıkıştırıcı",
|
||||
"FEATURE_7_DESC": "Ses düğmesiyle sürekli oynamak yok. Dahili ses sıkıştırıcı, sessiz diyalogları güçlendirir ve yüksek patlamaları yumuşatır. Netflix, Prime Video, Disney+, YouTube ve herhangi bir HTML5 videosunda çalışır. Tamamen yerel — hiçbir ses verisi bilgisayarınızı terk etmez.",
|
||||
"FEATURE_7_DESC": "Ses düğmesiyle uğraşmak yok. Yerleşik bir kompresör kısık diyalogları yükseltir, gürültülü patlamaları yumuşatır; hepsi tarayıcında yerel olarak işlenir.",
|
||||
"FEATURE_8_TITLE": "Açık Kaynak (MIT Lisansı)",
|
||||
"FEATURE_8_DESC": "Tüm kod tabanı GitHub'da herkese açık olarak incelenebilir. Kapalı kutular yok, tescilli blob'lar yok — aktarma sunucusundan (relay server) tarayıcı eklentisine kadar %100 şeffaflık.",
|
||||
"FEATURE_8_DESC": "Tüm kod tabanı GitHub'da herkese açık olarak incelenebilir. Kapalı kutular yok, tescilli blob'lar yok, aktarma sunucusundan (relay server) tarayıcı eklentisine kadar %100 şeffaflık.",
|
||||
"COMP_TITLE": "KoalaSync vs. Teleparty",
|
||||
"COMP_SUBTITLE": "Açık kaynaklı, reklamsız ve önce gizlilik diyen yöntemin neden birlikte izlemenin en iyi yolu olduğunu görün.",
|
||||
"COMP_COL_FEATURE": "Özellik",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Teleparty gizlilik politikaları ve takip verisi toplama:",
|
||||
"COMP_FOOTNOTE_4": "Standart HTML5 video etiketlerine komut dosyası eklenmesine izin veren web sitelerinde çalışır. Çok katı İçerik Güvenliği Politikalarına (CSP) sahip, DRM kopya korumalı veya yoğun şekilde gizlenmiş oynatıcı sarmalayıcıları (karmaşık gölge DOM'lar gibi) olan web siteleri otomatik kontrolü veya enjeksiyonu kısıtlayabilir.",
|
||||
"STEPS_TITLE": "Başlarken",
|
||||
"STEP_1_TITLE": "Eklentiyi Kur",
|
||||
"STEP_1_DESC": "KoalaSync'i Chrome Web Mağazası, Firefox Eklentileri aracılığıyla tarayıcınıza ekleyin veya en son geliştirici ZIP dosyasını GitHub'dan indirin.",
|
||||
"STEP_1_ILLUS_DESC": "Gizlilik öncelikli video senkronize edici",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Chrome için İndir",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Firefox için İndir",
|
||||
"STEP_1_ILLUS_ACTIVE": "Eklenti Etkin",
|
||||
"STEP_1_ILLUS_READY": "Birlikte izlemeye hazır!",
|
||||
"STEP_2_TITLE": "Oda Oluştur",
|
||||
"STEP_2_DESC": "Eklenti açılır penceresini açın ve '+ Yeni Oda Oluştur' düğmesine tıklayın. KoalaSync otomatik olarak güvenli bir Oda Kimliği ve Şifresi oluşturur, odaya katılır ve davet bağlantısını panonuza kopyalar.",
|
||||
"STEP_1_TITLE": "Oda Oluştur ve Paylaş",
|
||||
"STEP_1_DESC": "Uanzıyı açın, tek tıkla oda oluşturun ve bağlantıyı arkadaşlarınızla paylaşın. Davet köprüsü ile anında katılırlar.",
|
||||
"STEP_1_ILLUS_DESC": "Bağlanacak bir sekme seçin",
|
||||
"STEP_1_ILLUS_DL_CHROME": "YouTube Sekmesini Bağla",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Netflix Sekmesini Bağla",
|
||||
"STEP_1_ILLUS_ACTIVE": "Sekme Bağlandı",
|
||||
"STEP_1_ILLUS_READY": "Senkronize izleme için hazır !",
|
||||
"STEP_2_TITLE": "Video Sekmesini Seç",
|
||||
"STEP_2_DESC": "Videonuzun bulunduğu sekmeye gidin ve uzantı menüsünden bu sekmeyi seçin. KoalaSync oynatıcılarınızı otomatik olarak bağlar.",
|
||||
"STEP_2_ILLUS_ROOM": "Oda",
|
||||
"STEP_2_ILLUS_SYNC": "Senkronizasyon",
|
||||
"STEP_2_ILLUS_SETTINGS": "Ayarlar",
|
||||
"STEP_2_ILLUS_CREATE": "+ Yeni Oda Oluştur",
|
||||
"STEP_2_ILLUS_MANUAL": "Manuel Bağlantı / Gelişmiş",
|
||||
"STEP_2_ILLUS_COPIED": "Davet bağlantısı kopyalandı!",
|
||||
"STEP_3_TITLE": "Paylaş ve Eşitle",
|
||||
"STEP_3_DESC": "Davet bağlantısını arkadaşlarınıza gönderin. Katıldıklarında video sekmenizi seçin ve senkronize oynatmanın keyfini çıkarın.",
|
||||
"STEP_3_TITLE": "Senkronizasyonun Tadını Çıkar",
|
||||
"STEP_3_DESC": "Oynat tuşuna basın. Birisi durdurduğunda, sardığında veya oynattığında herkesin videosu gecikmesiz olarak senkronize kalır.",
|
||||
"STEP_3_ILLUS_IN_SYNC": "EŞİTLENDİ",
|
||||
"SELF_TITLE": "Kendi Kendine Barındıranlar İçin",
|
||||
"SELF_SUBTITLE": "Sunucumuza güvenmiyor musunuz? Veri egemenliğinizi koruyun. Kendi özel aktarım sunucunuzu dakikalar içinde devreye alın.",
|
||||
@@ -120,17 +130,17 @@
|
||||
"FAQ_Q_VIDEO_ACCESS": "KoalaSync videoyu arkadaşlarıma aktarır mı?",
|
||||
"FAQ_A_VIDEO_ACCESS": "Hayır. KoalaSync video içeriğini yayınlamaz, barındırmaz, paylaşmaz veya erişim kısıtlamalarını aşmaz. Herkes videoyu kendi tarayıcısında yerel olarak izler ve ilgili web sitesine ya da yayın hizmetine kendi erişimine, örneğin kendi Netflix hesabına, ihtiyaç duyar. KoalaSync yalnızca oynat, duraklat ve ileri geri sarma gibi oynatma eylemlerini senkronize eder.",
|
||||
"FAQ_Q2": "KoalaSync, Teleparty'ye ücretsiz bir alternatif mi?",
|
||||
"FAQ_A2": "MIT Lisansı kapsamında %100 ücretsiz ve açık kaynaklıdır. Premium katmanlar yok, kilitli özellikler yok, reklam yok — her şey dahil. Teleparty'nin aksine, KoalaSync'in aktarım sunucusu yalnızca RAM üzerindedir; sıfır veri toplama ve kullanıcı hesabı gereksinimi vardır.",
|
||||
"FAQ_A2": "MIT Lisansı kapsamında %100 ücretsiz ve açık kaynaklıdır. Premium katmanlar yok, kilitli özellikler yok, reklam yok, her şey dahil. Teleparty'nin aksine, KoalaSync'in aktarım sunucusu yalnızca RAM üzerindedir; sıfır veri toplama ve kullanıcı hesabı gereksinimi vardır.",
|
||||
"FAQ_Q3": "KoalaSync; Emby, Jellyfin ve kendi barındırdığınız medya sunucularıyla çalışır mı?",
|
||||
"FAQ_A3": "Evet. KoalaSync, herhangi bir standart HTML5 video öğesini algılar; bu da Emby, Jellyfin, Plex ve her türlü özel barındırılan akış kurulumuyla mükemmel çalıştığı anlamına gelir. Tam veri egemenliği için Docker aracılığıyla KoalaSync aktarım sunucusunu kendiniz de barındırabilirsiniz.",
|
||||
"FAQ_Q4": "Hangi tarayıcılar ve platformlar KoalaSync'i destekliyor?",
|
||||
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave ve tüm Chromium tabanlı tarayıcılar. Doğrudan Chrome Web Mağazası veya Firefox Eklentileri üzerinden kurun — manuel yapılandırmaya gerek yoktur.",
|
||||
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave ve tüm Chromium tabanlı tarayıcılar. Doğrudan Chrome Web Mağazası veya Firefox Eklentileri üzerinden kurun, manuel yapılandırmaya gerek yoktur.",
|
||||
"FAQ_Q5": "Tüm arkadaşlarımın eklentiyi kurması gerekiyor mu?",
|
||||
"FAQ_A5": "Evet, her katılımcının KoalaSync'i kurması gerekir — ancak kayıt veya hesap oluşturma yoktur. Sadece davet bağlantısına tıklayın, henüz yapmadıysanız eklentiyi kurun, saniyeler içinde odaya otomatik olarak katılacaksınız.",
|
||||
"FAQ_A5": "Evet, her katılımcının KoalaSync'i kurması gerekir, ancak kayıt veya hesap oluşturma yoktur. Sadece davet bağlantısına tıklayın, henüz yapmadıysanız eklentiyi kurun, saniyeler içinde odaya otomatik olarak katılacaksınız.",
|
||||
"FAQ_Q6": "Verilerim güvende mi? Ya resmi sunucuya güvenmiyorsam?",
|
||||
"FAQ_A6": "Tamamen güvende. KoalaSync'in aktarım sunucusu yalnızca RAM üzerindedir — odadan ayrıldığınız andan itibaren tüm oturum verileri kalıcı olarak silinir. Veritabanı yok, günlük kaydı yok, analitik yok, hesap yok. Ve tam kontrolü tercih ediyorsanız: Docker aracılığıyla kendi özel aktarım sunucunuzu dakikalar içinde barındırabilirsiniz. Tüm kod tabanı açık kaynaklıdır ve GitHub'da denetlenebilir.",
|
||||
"FAQ_A6": "Tamamen güvende. KoalaSync'in aktarım sunucusu yalnızca RAM üzerindedir, odadan ayrıldığınız andan itibaren tüm oturum verileri kalıcı olarak silinir. Veritabanı yok, günlük kaydı yok, analitik yok, hesap yok. Ve tam kontrolü tercih ediyorsanız: Docker aracılığıyla kendi özel aktarım sunucunuzu dakikalar içinde barındırabilirsiniz. Tüm kod tabanı açık kaynaklıdır ve GitHub'da denetlenebilir.",
|
||||
"FAQ_Q7": "Tarayıcıda video izlerken sessiz diyalogları ve yüksek patlamaları düzeltebilir miyim?",
|
||||
"FAQ_A7": "Evet. KoalaSync 2.2.0, videonuzun sessiz ve yüksek kısımları arasındaki farkı azaltan yerleşik bir ses sıkıştırıcı içerir. Web Audio API aracılığıyla yerel olarak çalışır — hiçbir ses verisi bilgisayarınızdan çıkmaz. Popup'ı açın, bir oda oluşturun, Ayarlar → Audio Processing'e gidin, sıkıştırıcıyı açın ve bir ön ayar seçin. Netflix, Prime Video, Disney+, YouTube ve herhangi bir HTML5 video sitesinde çalışır.",
|
||||
"FAQ_A7": "Evet. KoalaSync 2.2.0, videonuzun sessiz ve yüksek kısımları arasındaki farkı azaltan yerleşik bir ses sıkıştırıcı içerir. Web Audio API aracılığıyla yerel olarak çalışır, hiçbir ses verisi bilgisayarınızdan çıkmaz. Popup'ı açın, bir oda oluşturun, Ayarlar → Audio Processing'e gidin, sıkıştırıcıyı açın ve bir ön ayar seçin. Netflix, Prime Video, Disney+, YouTube ve herhangi bir HTML5 video sitesinde çalışır.",
|
||||
"HOWTO_TITLE": "KoalaSync kullanarak arkadaşlarınızla videoları senkronize etme",
|
||||
"HOWTO_DESC": "YouTube, Netflix, Twitch ve daha fazlasında arkadaşlarınızla mükemmel uyum içinde herhangi bir videoyu izleyin. Kayıt yok, takip yok.",
|
||||
"HOWTO_STEP_1_NAME": "Tarayıcı eklentisini kurun",
|
||||
@@ -158,7 +168,7 @@
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> SYNC",
|
||||
"MOCK_14": "Diğerlerine Atla",
|
||||
"MOCK_15": "Son Etkinlik Durumu",
|
||||
"MOCK_16": "1 bağlantı bekleniyor (KoalaPC)...",
|
||||
"MOCK_16": "1 bağlantı bekleniyor (🐶 HappyDog)...",
|
||||
"MOCK_17": "Atla ve Yine de Oynat",
|
||||
"MOCK_18": "Kullanıcı Adınız",
|
||||
"MOCK_19": "Gereksiz Sekmeleri Gizle",
|
||||
@@ -179,6 +189,13 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync; Netflix, Disney+, Amazon, YouTube, Twitch veya başka herhangi bir yayın platformuna bağlı değildir, bunlar tarafından desteklenmemektedir ve bunlarla ilişkili değildir. Tüm ticari markalar ilgili sahiplerinin mülkiyetindedir.",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_INDEX_TITLE": "KoalaSync - Alternatifler ve karşılaştırmalar",
|
||||
"ALT_INDEX_META_DESC": "KoalaSync ile diğer watch party eklentileri ve yayın çözümleri arasındaki dürüst karşılaştırmaları ve ayrıntılı rehberleri keşfedin.",
|
||||
"ALT_INDEX_HEADING": "Rehberler ve karşılaştırmalar",
|
||||
"ALT_INDEX_INTRO": "KoalaSync, diğer watch party araçları ve yayın çözümleriyle karşılaştırıldığında nasıl duruyor? Uzaktan film geceleriniz için en iyi seçeneği bulmak üzere dürüst ve teknik karşılaştırmalarımızı okuyun.",
|
||||
"ALT_INDEX_READ_COMPARISON": "Karşılaştırmayı oku →",
|
||||
"ALT_INDEX_READ_GUIDE": "Rehberi oku →",
|
||||
"ALT_INDEX_FOOTER_LINK": "Rehberler ve karşılaştırmalar",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync ve Teleparty: Hangi ortak izleme aracını kullanmalısınız?",
|
||||
"ALT_TELEPARTY_META_DESC": "Ortak izleme etkinlikleri (watch party) için KoalaSync ve Teleparty'nin; yerleşik sohbet, mobil desteği, tarayıcı tabanlı video senkronizasyonu, kendi kendine barındırılan (self-hosted) medya sunucuları ve açık kaynak kullanımı açısından pratik bir karşılaştırması.",
|
||||
"ALT_TELEPARTY_INTRO": "KoalaSync ve Teleparty, aynı can sıkıcı sorunu çözmeye yardımcı oluyor: O meşhur \"3, 2, 1, başlat\" ritüeliyle uğraşmadan birlikte bir şeyler izlemek. Ancak bunu yaparken farklı yaklaşımlar benimsiyorlar.<br><br>Yerleşik bir sohbet özelliği istiyorsanız veya mobilde izlemek istiyorsanız Teleparty en mantıklı seçenek olabilir. KoalaSync ise daha çok; Discord, Teamspeak veya başka bir sesli görüşme üzerinden zaten konuştuğunuz ve sadece oynatmanın senkronize kalmasını istediğiniz, masaüstü tarayıcı tabanlı izleme etkinlikleri için uygundur.<br><br>KoalaSync'i geliştirmemin temel nedeni esneklikti. Sadece belirli platformlar listesiyle sınırlı kalmayıp, standart bir tarayıcı video oynatıcısı kullanan hemen hemen her web sitesiyle çalışacak şekilde tasarlandı. Buna Emby, Jellyfin ve Plex gibi kendi kendine barındırılan sistemler, yerel/özel alan adları ve tarayıcıdaki pek çok sıradan video sayfası da dahildir.",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "KoalaSync, sesli görüşmenin yerini tutmaz. İzleme sırasında konuşmak isterseniz, yine Discord, Teamspeak, Signal, WhatsApp veya normalde kullandığınız herhangi bir aracı kullanmaya devam edebilirsiniz.<br><br>Ayrıca herkesin ilgili videoya veya sayfaya kendi cihazından erişebilmesi gerekir. KoalaSync; abonelikleri, oturum açma işlemlerini, DRM korumalarını, bölgesel kısıtlamaları veya platform kurallarını devre dışı bırakmaz.<br><br>Şu anda yalnızca masaüstü tarayıcılarında çalışır; yerleşik bir sohbet veya sesli görüşme özelliği yoktur ve uyumluluk her zaman kullanılan tarayıcıya ve video oynatıcıya bağlıdır.",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "Özet",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "Hızlı bir çözüm aradığınızda ve kalite, gizlilik ya da bireysel kontrol konularına çok önem vermediğinizde, ekran paylaşımı en basit seçenektir. Herkesin içeriği kendi tarayıcısında yerel olarak izlemesi gereken ve oturumun gerçekten birlikte izliyormuş hissi verdiği durumlarda ise KoalaSync daha uygun bir tercihtir.",
|
||||
"ALT_TRY_KOALASYNC": "KoalaSync'i Deneyin"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "KoalaSync'i Deneyin",
|
||||
"MOCK_HOST_CONTROL_LABEL": "Sunucu Kontrolü",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "Oynatmayı yalnızca ben kontrol edebilirim",
|
||||
"LABEL_SELECT_VIDEO": "Video Seç",
|
||||
"OPTION_SELECT_TAB": "-- Bir Sekme Seçin --",
|
||||
"THEME_TOGGLE_LABEL": "Açık/koyu modu değiştir",
|
||||
"FAQ_SHOW_ALL": "Tüm soruları göster",
|
||||
"FAQ_HIDE_ALL": "Soruları gizle",
|
||||
"SELF_SHOW_SETUP": "Kurulum rehberini göster",
|
||||
"SELF_HIDE_SETUP": "Rehberi gizle",
|
||||
"HERO_TRUST": "Ücretsiz · Açık kaynak · Hesap gerekmez",
|
||||
"COMP_MORE_GUIDES": "Tam karşılaştırma ve diğer alternatifler"
|
||||
}
|
||||
|
||||
+69
-28
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "uk_UA",
|
||||
"META_TITLE": "KoalaSync | Синхронізуйте Netflix, YouTube і будь-яке відео з друзями",
|
||||
"META_DESCRIPTION": "Дивіться Netflix, YouTube, Twitch і будь-яке HTML5 відео в ідеальній синхронізації з друзями. Безкоштовне розширення для браузера з відкритим кодом для Chrome і Firefox. Реєстрація не потрібна.",
|
||||
"SCHEMA_APP_DESC": "Дивіться Netflix, YouTube, Twitch і будь-яке HTML5 відео в ідеальній синхронізації з друзями. Безкоштовне розширення для браузера з відкритим кодом для Chrome і Firefox. Реєстрація не потрібна.",
|
||||
"OG_TITLE": "KoalaSync | Синхронізація Netflix, Emby, Jellyfin і майже будь-якого відео з друзями",
|
||||
"OG_DESCRIPTION": "Дивіться Netflix, Emby, Jellyfin, YouTube, Twitch і майже будь-яке відео HTML5 в ідеальній синхронізації. Розширення веб-переглядача з відкритим вихідним кодом для Chrome і Firefox, перш за все конфіденційність.",
|
||||
"TWITTER_TITLE": "KoalaSync | Синхронізація Netflix, Emby, Jellyfin і майже будь-якого відео з друзями – розширення браузера",
|
||||
"TWITTER_DESCRIPTION": "Дивіться Netflix, Emby, Jellyfin, YouTube, Twitch і майже будь-яке відео HTML5 в ідеальній синхронізації з друзями. Розширення веб-переглядача з відкритим вихідним кодом для Chrome і Firefox, перш за все конфіденційність.",
|
||||
"META_TITLE": "KoalaSync | Watch party для Netflix, YouTube та інших",
|
||||
"META_DESCRIPTION": "Watch party у Netflix, YouTube, Twitch і будь-якому HTML5-відео. Безкоштовне розширення з відкритим кодом для Chrome і Firefox. Без акаунта, стеження та реклами.",
|
||||
"SCHEMA_APP_DESC": "Watch party у Netflix, YouTube, Twitch і будь-якому HTML5-відео. Безкоштовне розширення з відкритим кодом для Chrome і Firefox. Без акаунта, стеження та реклами.",
|
||||
"OG_TITLE": "KoalaSync | Watch party для Netflix, Emby, Jellyfin і майже будь-якого відео",
|
||||
"OG_DESCRIPTION": "Запускайте watch party у Netflix, Emby, Jellyfin, YouTube, Twitch і майже будь-якому HTML5-відео. Open source, приватно й без акаунта.",
|
||||
"TWITTER_TITLE": "KoalaSync | Watch party для Netflix, Emby, Jellyfin і майже будь-якого відео – Розширення",
|
||||
"TWITTER_DESCRIPTION": "Запускайте watch party у Netflix, Emby, Jellyfin, YouTube, Twitch і майже будь-якому HTML5-відео. Open source, приватно й без акаунта.",
|
||||
"NAV_FEATURES": "особливості",
|
||||
"NAV_HOW_IT_WORKS": "Як це працює",
|
||||
"HERO_TITLE": "Дивіться разом.<span class='hero-line2'>Ідеальна синхронізація.</span>",
|
||||
"HERO_SUBTITLE": "Ваш віддалений кіновечір без лагів. Ні реєстрації, ні збору даних. Просто поділіться посиланням і дивіться разом.",
|
||||
"HERO_TITLE": "Перетвори будь-яке відео на <span class='hero-highlight'>watch party</span>.",
|
||||
"HERO_SUBTITLE": "Ваш кіновечір на відстані, без затримок. Без акаунта, без стеження, без реклами. Просто поділіться посиланням і натисніть play разом.",
|
||||
"HERO_MASCOT_ALT": "Мила коала стоїть і дивиться вниз на кнопки завантаження",
|
||||
"DEMO_SYNC": "Синхронно",
|
||||
"DEMO_HINT": "Жива демонстрація: натисніть play у будь-якій вкладці, і KoalaSync синхронізує обидві.",
|
||||
"DEMO_NO_ROOM": "Ще не підключено",
|
||||
"DEMO_CREATE_ROOM": "Створити кімнату",
|
||||
"DEMO_JOINED": "У кімнаті",
|
||||
"DEMO_TAB_SELECTED": "Вкладку вибрано",
|
||||
"DEMO_SELECT_PLACEHOLDER": "Оберіть вкладку з відео…",
|
||||
"MOBILE_DEMO_CTA": "Подивитися, як працює",
|
||||
"MOBILE_DEMO_TITLE": "Жива демонстрація KoalaSync",
|
||||
"MOBILE_DEMO_CLOSE": "Закрити демонстрацію",
|
||||
"ADD_TO_CHROME": "Додати до Chrome",
|
||||
"ADD_TO_FIREFOX": "Додати до Firefox",
|
||||
"COMPAT_HEADING": "Працює на ваших улюблених платформах",
|
||||
@@ -35,20 +45,20 @@
|
||||
"USE_CASE_3_DESC": "Подолайте відстань і насолоджуйтесь побаченнями. Переживайте кожен поворот сюжету, смійтеся над тими самими жартами та діліться емоційними моментами в ту саму мілісекунду.",
|
||||
"WHY_TITLE": "Чому KoalaSync?",
|
||||
"WHY_SUBTITLE": "Створено для надійної синхронізації, конфіденційності та простого налаштування.",
|
||||
"FEATURE_1_TITLE": "Повний контроль/миттєва синхронізація",
|
||||
"FEATURE_1_DESC": "Один робить паузу, всі зупиняються. Один шукає, всі йдуть. Наш двофазний протокол синхронізації координує відтворення в режимі реального часу для всіх учасників.",
|
||||
"FEATURE_1_TITLE": "Керування хоста та синхронізація",
|
||||
"FEATURE_1_DESC": "Ви вирішуєте, хто керує відтворенням. Увімкніть керування ведучого, щоб тільки ви могли призупиняти та перемотувати відео, або призначайте співведучих.",
|
||||
"FEATURE_2_TITLE": "Нескінченне споглядання",
|
||||
"FEATURE_2_DESC": "Автоматичне відтворення синхронізовано. KoalaSync автоматично визначає переходи епізодів і утримує відтворення, доки всі однорангові пристрої успішно не завантажать наступне відео.",
|
||||
"FEATURE_3_TITLE": "Нуль облікових записів / чиста конфіденційність",
|
||||
"FEATURE_3_DESC": "Ні реєстрації, ні відстеження, ні слідів постійності. Сервер повністю працює в ефемерному RAM і очищає вашу кімнату, коли ви залишаєте її.",
|
||||
"FEATURE_4_TITLE": "Універсальна підтримка HTML5",
|
||||
"FEATURE_4_DESC": "Працює на YouTube, Twitch, Netflix, Jellyfin, Emby і майже будь-якій стандартній веб-сторінці, що містить відеоелемент HTML5. Також сумісний із користувальницькими налаштуваннями, розміщеними на власному хості. На наші сервери ніколи не надсилаються відеодані — лише метадані синхронізації.",
|
||||
"FEATURE_4_DESC": "Netflix, YouTube, Twitch, Jellyfin, Emby і майже будь-яка сторінка з HTML5-відео. З браузера виходять лише сигнали синхронізації, але не саме відео.",
|
||||
"FEATURE_5_TITLE": "Саморозміщення та готовність до Docker",
|
||||
"FEATURE_5_DESC": "Наші офіційні сервери безкоштовні та швидкі, але ви можете отримати повне право власності, якщо хочете. Запустіть власний приватний сервер ретрансляції за лічені секунди через Docker.",
|
||||
"FEATURE_6_TITLE": "Миттєві запрошення / Приєднання в 1 клік",
|
||||
"FEATURE_6_DESC": "Немає IP-адрес або паролів для обміну. Поділіться створеним посиланням із запрошенням із друзями, щоб вони могли автоматично приєднатися до вашої кімнати одним клацанням миші.",
|
||||
"FEATURE_7_TITLE": "Вирівнювання гучності / Аудіокомпресор",
|
||||
"FEATURE_7_DESC": "Більше не потрібно натискати на ручку гучності. Вбудований аудіокомпресор посилює тихий діалог і приборкує гучні вибухи. Працює на Netflix, Prime Video, Disney+, YouTube і будь-якому відео HTML5. Повністю локально — аудіодані не залишають ваш комп’ютер.",
|
||||
"FEATURE_7_DESC": "Більше не треба крутити гучність. Вбудований компресор підсилює тихі діалоги та пом'якшує гучні вибухи, обробляючи все локально в браузері.",
|
||||
"FEATURE_8_TITLE": "Відкритий код (ліцензія MIT)",
|
||||
"FEATURE_8_DESC": "Вся кодова база доступна для публічного аудиту на GitHub. Жодних чорних скриньок, жодних власних блобів — 100% прозорість від сервера ретрансляції до розширення браузера.",
|
||||
"COMP_TITLE": "KoalaSync проти Teleparty",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Політика конфіденційності Teleparty і збір даних відстеження:",
|
||||
"COMP_FOOTNOTE_4": "Працює на веб-сайтах, які дозволяють ін’єкції сценаріїв у стандартні відеотеги HTML5. Веб-сайти з дуже суворими політиками безпеки вмісту (CSP), захистом від копіювання DRM або сильно затуманеними оболонками програвачів (як-от складні тіньові DOM) можуть обмежувати автоматизований контроль або впровадження.",
|
||||
"STEPS_TITLE": "Початок роботи",
|
||||
"STEP_1_TITLE": "Встановити розширення",
|
||||
"STEP_1_DESC": "Додайте KoalaSync до свого браузера з веб-магазину Chrome, доповнень Firefox або завантажте найновіший розробник ZIP із GitHub.",
|
||||
"STEP_1_ILLUS_DESC": "Синхронізатор відео насамперед із конфіденційністю",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Завантажити для Chrome",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Завантажити для Firefox",
|
||||
"STEP_1_ILLUS_ACTIVE": "Розширення активне",
|
||||
"STEP_1_ILLUS_READY": "Готові дивитися разом!",
|
||||
"STEP_2_TITLE": "Створіть кімнату",
|
||||
"STEP_2_DESC": "Відкрийте спливаюче вікно розширення та натисніть «+ Створити нову кімнату». KoalaSync автоматично генерує безпечний Room ID і пароль, приєднується до нього та копіює посилання для запрошення в буфер обміну.",
|
||||
"STEP_1_TITLE": "Створити кімнату та поділитися",
|
||||
"STEP_1_DESC": "Відкрийте розширення, створити кімнату одним кліком та надішліть посилання друзям. Вони миттєво приєднаються за запрошенням.",
|
||||
"STEP_1_ILLUS_DESC": "Виберіть вкладку для підключення",
|
||||
"STEP_1_ILLUS_DL_CHROME": "Підключити вкладку YouTube",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "Підключити вкладку Netflix",
|
||||
"STEP_1_ILLUS_ACTIVE": "Вкладку підключено",
|
||||
"STEP_1_ILLUS_READY": "Готово до синхронного перегляду !",
|
||||
"STEP_2_TITLE": "Вибрати вкладку з відео",
|
||||
"STEP_2_DESC": "Перейдіть на вкладку з відео та виберіть її в меню розширення. KoalaSync автоматично зв'яже ваші плеєри.",
|
||||
"STEP_2_ILLUS_ROOM": "Кімната",
|
||||
"STEP_2_ILLUS_SYNC": "Синхронізувати",
|
||||
"STEP_2_ILLUS_SETTINGS": "Налаштування",
|
||||
"STEP_2_ILLUS_CREATE": "+ Створити нову кімнату",
|
||||
"STEP_2_ILLUS_MANUAL": "Ручне підключення / Додатково",
|
||||
"STEP_2_ILLUS_COPIED": "Посилання на запрошення скопійовано!",
|
||||
"STEP_3_TITLE": "Поділитися та синхронізувати",
|
||||
"STEP_3_DESC": "Надішліть запрошення своїм друзям. Коли вони приєднаються, виберіть вкладку відео та насолоджуйтеся синхронізованим відтворенням.",
|
||||
"STEP_3_TITLE": "Насолоджуватися синхронізацією",
|
||||
"STEP_3_DESC": "Натисніть відтворення. Коли хтось зупиняє, перемотує або запускає відео, відтворення у всіх синхронізується без затримок.",
|
||||
"STEP_3_ILLUS_IN_SYNC": "СИНХРОНІЗОВАНО",
|
||||
"SELF_TITLE": "Для самостійних господарів",
|
||||
"SELF_SUBTITLE": "Не довіряєте нашому серверу? Підтримуйте повний суверенітет даних. Розгорніть власний приватний сервер ретрансляції за лічені хвилини.",
|
||||
@@ -120,13 +130,13 @@
|
||||
"FAQ_Q_VIDEO_ACCESS": "Чи транслює KoalaSync відео моїм друзям?",
|
||||
"FAQ_A_VIDEO_ACCESS": "Ні. KoalaSync не транслює, не розміщує, не поширює відеоконтент і не обходить доступ до нього. Кожен учасник дивиться відео локально у власному браузері та повинен мати власний доступ до сайту або стримінгового сервісу, наприклад власний акаунт Netflix. KoalaSync синхронізує лише дії відтворення, як-от запуск, паузу та перемотування.",
|
||||
"FAQ_Q2": "Чи є KoalaSync безкоштовною альтернативою Teleparty?",
|
||||
"FAQ_A2": "100% безкоштовне та з відкритим кодом за ліцензією MIT. Без преміум-рівнів, без заблокованих функцій, без реклами — все включено. На відміну від Teleparty, ретрансляційний сервер KoalaSync призначений лише для RAM і не вимагає збору даних або облікових записів користувачів.",
|
||||
"FAQ_A2": "100% безкоштовне та з відкритим кодом за ліцензією MIT. Без преміум-рівнів, без заблокованих функцій, без реклами, все включено. На відміну від Teleparty, ретрансляційний сервер KoalaSync призначений лише для RAM і не вимагає збору даних або облікових записів користувачів.",
|
||||
"FAQ_Q3": "Чи працює KoalaSync із Emby, Jellyfin і власними медіа-серверами?",
|
||||
"FAQ_A3": "так KoalaSync виявляє будь-який стандартний відеоелемент HTML5, що означає, що він ідеально працює з Emby, Jellyfin, Plex та будь-якими спеціальними налаштуваннями потокового передавання. Ви також можете самостійно розмістити сервер ретрансляції KoalaSync через Docker для повного суверенітету даних.",
|
||||
"FAQ_Q4": "Які браузери та платформи підтримують KoalaSync?",
|
||||
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave та всі браузери на основі Chromium. Встановіть його безпосередньо з веб-магазину Chrome або додатків Firefox — ручне налаштування не потрібне.",
|
||||
"FAQ_Q5": "Чи всім моїм друзям потрібно встановити розширення?",
|
||||
"FAQ_A5": "Так, кожному учаснику потрібно встановити KoalaSync — але немає реєстрації чи створення облікового запису. Просто клацніть посилання запрошення, установіть розширення, якщо ви ще цього не зробили, і ви автоматично приєднаєтесь до кімнати за лічені секунди.",
|
||||
"FAQ_A5": "Так, кожному учаснику потрібно встановити KoalaSync, але немає реєстрації чи створення облікового запису. Просто клацніть посилання запрошення, установіть розширення, якщо ви ще цього не зробили, і ви автоматично приєднаєтесь до кімнати за лічені секунди.",
|
||||
"FAQ_Q6": "Чи мої дані в безпеці? Що робити, якщо я не довіряю офіційному серверу?",
|
||||
"FAQ_A6": "Повністю безпечний. Сервер ретрансляції KoalaSync призначений лише для RAM — усі дані сеансу назавжди видаляються, коли ви залишаєте кімнату. Ні бази даних, ні реєстрації, ні аналітики, ні облікових записів. А якщо ви віддаєте перевагу повному контролю: ви можете самостійно розмістити свій приватний сервер ретрансляції через Docker за лічені хвилини. Уся кодова база є відкритим кодом і доступна д��я перевірки на GitHub.",
|
||||
"FAQ_Q7": "Чи можу я виправити тихий діалог і гучні вибухи під час перегляду відео в браузері?",
|
||||
@@ -158,7 +168,7 @@
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> СИНХ",
|
||||
"MOCK_14": "Перейти до інших",
|
||||
"MOCK_15": "Статус останньої дії",
|
||||
"MOCK_16": "Очікування 1 партнера (KoalaPC)...",
|
||||
"MOCK_16": "Очікування 1 партнера (🐶 HappyDog)...",
|
||||
"MOCK_17": "Все одно пропустіть і грайте",
|
||||
"MOCK_18": "Ваше ім'я користувача",
|
||||
"MOCK_19": "Приховати безладні вкладки",
|
||||
@@ -179,6 +189,13 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync не пов’язаний із Netflix, Disney+, Amazon, YouTube, Twitch або будь-якою іншою потоковою платформою, не схвалений і не пов’язаний з ними. Усі торгові марки є власністю відповідних власників.",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_INDEX_TITLE": "KoalaSync — альтернативи та порівняння",
|
||||
"ALT_INDEX_META_DESC": "Перегляньте чесні порівняння й докладні посібники про KoalaSync, інші розширення для спільного перегляду та стримінгові рішення.",
|
||||
"ALT_INDEX_HEADING": "Посібники та порівняння",
|
||||
"ALT_INDEX_INTRO": "Як KoalaSync виглядає порівняно з іншими інструментами для спільного перегляду та стримінговими рішеннями? Прочитайте наші чесні технічні порівняння, щоб знайти найкращий варіант для дистанційних кіновечорів.",
|
||||
"ALT_INDEX_READ_COMPARISON": "Читати порівняння →",
|
||||
"ALT_INDEX_READ_GUIDE": "Читати посібник →",
|
||||
"ALT_INDEX_FOOTER_LINK": "Посібники та порівняння",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync проти Teleparty: який інструмент для спільного перегляду відео слід використовувати?",
|
||||
"ALT_TELEPARTY_META_DESC": "Практичне порівняння KoalaSync та Teleparty для спільного перегляду відео, вбудованого чату, підтримки мобільних пристроїв, синхронізації відео в браузері, власних медіасерверів та використання програмного забезпечення з відкритим вихідним кодом.",
|
||||
"ALT_TELEPARTY_INTRO": "KoalaSync та Teleparty допомагають вирішити ту саму надокучливу проблему: перегляд чогось разом без виконання всієї процедури «3, 2, 1, грати». Вони просто використовують інший підхід.<br><br>Teleparty має найбільший сенс, якщо вам потрібен вбудований чат або якщо ви хочете дивитися на мобільному пристрої. KoalaSync більше підходить для вечірок у браузері на комп'ютері, де ви вже спілкуєтеся через Discord, Teamspeak або інший голосовий дзвінок і просто хочете, щоб відтворення залишалося синхронізованим.<br><br>Головною причиною, чому я створив KoalaSync, була гнучкість. Він призначений для роботи майже з будь-яким веб-сайтом, який використовує звичайний відеоплеєр браузера, а не лише з фіксованим списком платформ. Це включає самостійно розміщені налаштування, такі як Emby, Jellyfin та Plex, локальні/приватні домени та багато звичайних відеосторінок у браузері.",
|
||||
@@ -205,5 +222,29 @@
|
||||
"ALT_SCREEN_LIMITS_BODY": "KoalaSync не замінює голосовий дзвінок. Якщо ви хочете розмовляти під час перегляду, ви все одно використовуєте Discord, Teamspeak, Signal, WhatsApp або будь-який інший додаток, який ви зазвичай використовуєте.<br><br>Кожному також потрібен доступ до відео або сторінки на власному пристрої. KoalaSync не обходить підписки, вхід, DRM, регіональні обмеження чи правила платформи.<br><br>Наразі він доступний лише для браузера на комп'ютері, не має вбудованої функції чату чи голосового зв'язку, а сумісність завжди залежить від браузера та відеоплеєра.",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "Підсумок",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "Спільний доступ до екрана – це найпростіший варіант, коли ви просто хочете чогось швидкого і не дуже переймаєтеся якістю, конфіденційністю чи індивідуальним контролем. KoalaSync краще підходить, коли кожен має дивитися локально у своєму власному браузері, і сеанс має більше нагадувати спільний перегляд.",
|
||||
"ALT_TRY_KOALASYNC": "Спробуйте KoalaSync"
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "Спробуйте KoalaSync",
|
||||
"MOCK_HOST_CONTROL_LABEL": "Керування ведучим",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "Тільки я можу керувати відтворенням",
|
||||
"LABEL_SELECT_VIDEO": "Виберіть Відео",
|
||||
"OPTION_SELECT_TAB": "-- Виберіть вкладку --",
|
||||
"THEME_TOGGLE_LABEL": "Перемкнути світлу/темну тему",
|
||||
"FAQ_SHOW_ALL": "Показати всі запитання",
|
||||
"FAQ_HIDE_ALL": "Приховати запитання",
|
||||
"SELF_SHOW_SETUP": "Показати інструкцію з налаштування",
|
||||
"SELF_HIDE_SETUP": "Приховати інструкцію",
|
||||
"HERO_TRUST": "Безкоштовно · Відкритий код · Без акаунта",
|
||||
"COMP_MORE_GUIDES": "Повне порівняння та інші альтернативи"
|
||||
}
|
||||
|
||||
+77
-36
@@ -5,18 +5,28 @@
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN",
|
||||
"OG_LOCALE": "zh_CN",
|
||||
"META_TITLE": "KoalaSync | 与朋友同步 Netflix、YouTube 和任何视频",
|
||||
"META_DESCRIPTION": "与朋友完美同步观看 Netflix、YouTube、Twitch 和任何 HTML5 视频。适用于 Chrome 和 Firefox 的免费开源浏览器扩展。无需注册。",
|
||||
"SCHEMA_APP_DESC": "与朋友完美同步观看 Netflix、YouTube、Twitch 和任何 HTML5 视频。适用于 Chrome 和 Firefox 的免费开源浏览器扩展。无需注册。",
|
||||
"OG_TITLE": "KoalaSync |与朋友同步 Netflix、Emby、Jellyfin 和几乎任何视频",
|
||||
"OG_DESCRIPTION": "完美同步观看 Netflix、Emby、Jellyfin、YouTube、Twitch 和几乎任何 HTML5 视频。适用于 Chrome 和 Firefox 的隐私第一开源浏览器扩展。",
|
||||
"TWITTER_TITLE": "KoalaSync |与朋友同步 Netflix、Emby、Jellyfin 和几乎所有视频 – 浏览器扩展",
|
||||
"TWITTER_DESCRIPTION": "与朋友完美同步观看 Netflix、Emby、Jellyfin、YouTube、Twitch 和几乎任何 HTML5 视频。适用于 Chrome 和 Firefox 的隐私第一开源浏览器扩展。",
|
||||
"META_TITLE": "KoalaSync | Netflix、YouTube 一起看派对浏览器扩展",
|
||||
"META_DESCRIPTION": "在 Netflix、YouTube、Twitch 及任意 HTML5 视频上发起一起看派对。适用于 Chrome 和 Firefox 的免费开源扩展。无需账号、无跟踪、无广告。",
|
||||
"SCHEMA_APP_DESC": "在 Netflix、YouTube、Twitch 及任意 HTML5 视频上发起一起看派对。适用于 Chrome 和 Firefox 的免费开源扩展。无需账号、无跟踪、无广告。",
|
||||
"OG_TITLE": "KoalaSync | 在 Netflix、Emby、Jellyfin 和几乎任意视频上一起看",
|
||||
"OG_DESCRIPTION": "在 Netflix、Emby、Jellyfin、YouTube、Twitch 和几乎任意 HTML5 视频上发起一起看派对。开源、重视隐私、无需账号。",
|
||||
"TWITTER_TITLE": "KoalaSync | 在 Netflix、Emby、Jellyfin 和几乎任意视频上一起看 – 浏览器扩展",
|
||||
"TWITTER_DESCRIPTION": "在 Netflix、Emby、Jellyfin、YouTube、Twitch 和几乎任意 HTML5 视频上发起一起看派对。开源、重视隐私、无需账号。",
|
||||
"NAV_FEATURES": "特征",
|
||||
"NAV_HOW_IT_WORKS": "它是如何运作的",
|
||||
"HERO_TITLE": "一起观看.<span class='hero-line2'> 完美同步.</span>",
|
||||
"HERO_SUBTITLE": "您的远程电影之夜没有延迟。无需注册,无需收集数据。只需分享链接即可一起观看。",
|
||||
"HERO_TITLE": "把任意视频变成<span class='hero-highlight'>一起看派对</span>。",
|
||||
"HERO_SUBTITLE": "远程电影之夜,毫无延迟。无需账号、无跟踪、无广告。分享链接,一起播放。",
|
||||
"HERO_MASCOT_ALT": "一只可爱的考拉站着,低头看着下载按钮",
|
||||
"DEMO_SYNC": "同步中",
|
||||
"DEMO_HINT": "在线演示:在任一标签页点击播放,KoalaSync 会让两边保持同步。",
|
||||
"DEMO_NO_ROOM": "尚未连接",
|
||||
"DEMO_CREATE_ROOM": "创建房间",
|
||||
"DEMO_JOINED": "已加入房间",
|
||||
"DEMO_TAB_SELECTED": "已选择标签页",
|
||||
"DEMO_SELECT_PLACEHOLDER": "选择视频标签页…",
|
||||
"MOBILE_DEMO_CTA": "查看工作方式",
|
||||
"MOBILE_DEMO_TITLE": "KoalaSync 在线演示",
|
||||
"MOBILE_DEMO_CLOSE": "关闭演示",
|
||||
"ADD_TO_CHROME": "添加到Chrome",
|
||||
"ADD_TO_FIREFOX": "添加到Firefox",
|
||||
"COMPAT_HEADING": "适用于您最喜欢的平台",
|
||||
@@ -35,22 +45,22 @@
|
||||
"USE_CASE_3_DESC": "拉近距离,享受约会之夜。体验每一个情节转折,嘲笑同样的笑话,并在同一毫秒内分享情感时刻。",
|
||||
"WHY_TITLE": "为什么是KoalaSync?",
|
||||
"WHY_SUBTITLE": "专为可靠的同步、隐私优先的设计和轻松的设置而打造。",
|
||||
"FEATURE_1_TITLE": "完全控制/即时同步",
|
||||
"FEATURE_1_DESC": "一个人停顿,所有人都停顿。一追寻,众人追随。我们的两阶段同步协议协调所有参与者的实时播放。",
|
||||
"FEATURE_1_TITLE": "房主控制与同步",
|
||||
"FEATURE_1_DESC": "您可以自主决定谁来控制播放。启用主持人控制以仅允许您进行暂停和快进,或在成员列表中指定共同主持人。",
|
||||
"FEATURE_2_TITLE": "无尽的狂欢",
|
||||
"FEATURE_2_DESC": "自动同步播放。 KoalaSync 自动检测剧集转换并保持播放,直到所有对等方都成功加载下一个视频。",
|
||||
"FEATURE_3_TITLE": "零账户/纯粹隐私",
|
||||
"FEATURE_3_DESC": "没有注册,没有跟踪,没有持久性足迹。服务器完全在临时 RAM 中运行,并在您离开时清理您的房间。",
|
||||
"FEATURE_4_TITLE": "通用 HTML5 支持",
|
||||
"FEATURE_4_DESC": "适用于 YouTube、Twitch、Netflix、Jellyfin、Emby 以及几乎任何包含 HTML5 视频元素的标准网页。还与自定义自托管设置兼容。不会将任何视频数据发送到我们的服务器,仅发送同步元数据。",
|
||||
"FEATURE_4_DESC": "支持 Netflix、YouTube、Twitch、Jellyfin、Emby 以及几乎任何含 HTML5 视频的页面。离开浏览器的只有同步信号,绝不包含视频本身。",
|
||||
"FEATURE_5_TITLE": "自托管和 Docker 就绪",
|
||||
"FEATURE_5_DESC": "我们的官方服务器是免费且快速的,但如果您愿意,您可以完全拥有所有权。通过 Docker 在几秒钟内启动您自己的私有中继服务器。",
|
||||
"FEATURE_6_TITLE": "即时邀请/一键加入",
|
||||
"FEATURE_6_DESC": "无需交换 IP 地址或密码。与您的朋友分享生成的邀请链接,让他们只需单击一下即可自动加入您的房间。",
|
||||
"FEATURE_7_TITLE": "音量调节/音频压缩器",
|
||||
"FEATURE_7_DESC": "不再需要调节音量旋钮。内置音频压缩器可增强安静的对话并抑制大声的爆炸声。适用于 Netflix、Prime Video、Disney+、YouTube 和任何 HTML5 视频。完全本地化——音频数据不会离开您的计算机。",
|
||||
"FEATURE_7_DESC": "不用再调音量了。内置压缩器会提升安静的对话、抑制响亮的爆炸声,全部在浏览器本地处理。",
|
||||
"FEATURE_8_TITLE": "开源(MIT 许可证)",
|
||||
"FEATURE_8_DESC": "整个代码库可在 GitHub 上公开审核。没有黑匣子,没有专有 blob——从中继服务器到浏览器扩展 100% 透明。",
|
||||
"FEATURE_8_DESC": "整个代码库可在 GitHub 上公开审核。没有黑匣子,没有专有 blob,从中继服务器到浏览器扩展 100% 透明。",
|
||||
"COMP_TITLE": "KoalaSync 与 Teleparty",
|
||||
"COMP_SUBTITLE": "了解为什么开源、无广告和隐私优先是一起观看的最佳方式。",
|
||||
"COMP_COL_FEATURE": "特征",
|
||||
@@ -87,23 +97,23 @@
|
||||
"COMP_FOOTNOTE_3": "Teleparty 隐私政策和跟踪数据收集:",
|
||||
"COMP_FOOTNOTE_4": "适用于允许将脚本注入标准 HTML5 视频标签的网站。具有高度严格的内容安全策略 (CSP)、DRM 复制保护或严重混淆的播放器包装器(如复杂的影子 DOM)的网站可能会限制自动控制或注入。",
|
||||
"STEPS_TITLE": "入门",
|
||||
"STEP_1_TITLE": "安装扩展",
|
||||
"STEP_1_DESC": "从 Chrome Web Store、Firefox 附加组件将 KoalaSync 添加到您的浏览器,或从 GitHub 下载最新的开发人员 ZIP。",
|
||||
"STEP_1_ILLUS_DESC": "隐私第一的视频同步器",
|
||||
"STEP_1_ILLUS_DL_CHROME": "下载 Chrome",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "下载 Firefox",
|
||||
"STEP_1_ILLUS_ACTIVE": "分机激活",
|
||||
"STEP_1_ILLUS_READY": "准备好一起观看吧!",
|
||||
"STEP_2_TITLE": "创建房间",
|
||||
"STEP_2_DESC": "打开扩展弹出窗口并单击“+ 创建新房间”。 KoalaSync 自动生成安全的 Room ID 和密码,加入它,并将邀请链接复制到剪贴板。",
|
||||
"STEP_1_TITLE": "创建并分享房间",
|
||||
"STEP_1_DESC": "打开插件,一键创建房间并将链接分享给好友。他们可以通过邀请链接立即加入。",
|
||||
"STEP_1_ILLUS_DESC": "选择一个标签页进行绑定",
|
||||
"STEP_1_ILLUS_DL_CHROME": "绑定 YouTube 标签页",
|
||||
"STEP_1_ILLUS_DL_FIREFOX": "绑定 Netflix 标签页",
|
||||
"STEP_1_ILLUS_ACTIVE": "标签页已绑定",
|
||||
"STEP_1_ILLUS_READY": "准备好同步观看 !",
|
||||
"STEP_2_TITLE": "选择视频标签页",
|
||||
"STEP_2_DESC": "转到您的视频标签页,并在插件菜单中选择该页面。KoalaSync 会自动连接你们的播放器。",
|
||||
"STEP_2_ILLUS_ROOM": "房间",
|
||||
"STEP_2_ILLUS_SYNC": "同步",
|
||||
"STEP_2_ILLUS_SETTINGS": "设置",
|
||||
"STEP_2_ILLUS_CREATE": "+ 创建新房间",
|
||||
"STEP_2_ILLUS_MANUAL": "手动连接/高级",
|
||||
"STEP_2_ILLUS_COPIED": "邀请链接已复制!",
|
||||
"STEP_3_TITLE": "分享与同步",
|
||||
"STEP_3_DESC": "将邀请链接发送给您的朋友。他们加入后,选择您的视频选项卡并享受同步播放。",
|
||||
"STEP_3_TITLE": "享受同步播放",
|
||||
"STEP_3_DESC": "点击播放。无论是谁暂停、快进还是播放,所有人的视频都会保持完美实时同步,毫无延迟。",
|
||||
"STEP_3_ILLUS_IN_SYNC": "同步",
|
||||
"SELF_TITLE": "对于自托管者",
|
||||
"SELF_SUBTITLE": "不信任我们的服务器?维护完整的数据主权。只需几分钟即可部署您自己的私有中继服务器。",
|
||||
@@ -120,7 +130,7 @@
|
||||
"FAQ_Q_VIDEO_ACCESS": "KoalaSync 会把视频串流给我的朋友吗?",
|
||||
"FAQ_A_VIDEO_ACCESS": "不会。KoalaSync 不会串流、托管、共享视频内容,也不会绕过任何访问权限。每个人都在自己的浏览器中本地观看视频,并且需要自己拥有该网站或流媒体服务的访问权限,例如自己的 Netflix 账号。KoalaSync 只同步播放、暂停和拖动进度等播放操作。",
|
||||
"FAQ_Q2": "KoalaSync 是 Teleparty 的免费替代品吗?",
|
||||
"FAQ_A2": "在 MIT 许可证下 100% 免费和开源。没有高级套餐、没有锁定功能、没有广告——一切都包含在内。与 Teleparty 不同,KoalaSync 的中继服务器仅是 RAM,需要零数据收集或用户帐户。",
|
||||
"FAQ_A2": "在 MIT 许可证下 100% 免费和开源。没有高级套餐、没有锁定功能、没有广告,一切都包含在内。与 Teleparty 不同,KoalaSync 的中继服务器仅是 RAM,需要零数据收集或用户帐户。",
|
||||
"FAQ_Q3": "KoalaSync 是否可以与 Emby、Jellyfin 和自托管媒体服务器配合使用?",
|
||||
"FAQ_A3": "是的。 KoalaSync 可检测任何标准 HTML5 视频元素,这意味着它可以与 Emby、Jellyfin、Plex 和任何自定义自托管流设置完美配合。您还可以通过 Docker 自托管 KoalaSync 中继服务器,以实现完整的数据主权。",
|
||||
"FAQ_Q4": "哪些浏览器和平台支持KoalaSync?",
|
||||
@@ -128,9 +138,9 @@
|
||||
"FAQ_Q5": "我所有的朋友都需要安装该扩展吗?",
|
||||
"FAQ_A5": "是的,每个参与者都需要安装 KoalaSync - 但无需注册或创建帐户。只需单击邀请链接,安装扩展程序(如果尚未安装),您将在几秒钟内自动加入房间。",
|
||||
"FAQ_Q6": "我的数据安全吗?如果我不信任官方服务器怎么办?",
|
||||
"FAQ_A6": "完全安全。 KoalaSync 的中继服务器仅限 RAM — 所有会话数据在您离开房间时都会永久删除。没有数据库,没有日志记录,没有分析,没有帐户。如果您喜欢完全控制:您可以在几分钟内通过 Docker 自行托管您自己的私人中继服务器。整个代码库是开源的,并且可以在 GitHub 上进行审核。",
|
||||
"FAQ_A6": "完全安全。 KoalaSync 的中继服务器仅限 RAM,所有会话数据在您离开房间时都会永久删除。没有数据库,没有日志记录,没有分析,没有帐户。如果您喜欢完全控制:您可以在几分钟内通过 Docker 自行托管您自己的私人中继服务器。整个代码库是开源的,并且可以在 GitHub 上进行审核。",
|
||||
"FAQ_Q7": "我可以修复在浏览器中观看视频时安静的对话和大声的爆炸声吗?",
|
||||
"FAQ_A7": "是的。 KoalaSync 2.2.0 包含一个内置音频压缩器,可减少视频安静部分和大声部分之间的差距。它通过 Web Audio API 在本地运行 — 任何音频数据都不会离开您的计算机。打开弹出窗口,创建一个房间,转到“设置”→“音频处理”,打开压缩器,然后选择一个预设。适用于 Netflix、Prime Video、Disney+、YouTube 和任何 HTML5 视频网站。",
|
||||
"FAQ_A7": "是的。 KoalaSync 2.2.0 包含一个内置音频压缩器,可减少视频安静部分和大声部分之间的差距。它通过 Web Audio API 在本地运行,任何音频数据都不会离开您的计算机。打开弹出窗口,创建一个房间,转到“设置”→“音频处理”,打开压缩器,然后选择一个预设。适用于 Netflix、Prime Video、Disney+、YouTube 和任何 HTML5 视频网站。",
|
||||
"HOWTO_TITLE": "如何使用KoalaSync与好友同步视频",
|
||||
"HOWTO_DESC": "与 YouTube、Netflix、Twitch 等平台上的朋友完美同步观看任何视频。无需注册,无需跟踪。",
|
||||
"HOWTO_STEP_1_NAME": "安装浏览器扩展",
|
||||
@@ -158,7 +168,7 @@
|
||||
"MOCK_13": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg> 同步",
|
||||
"MOCK_14": "跳转到其他",
|
||||
"MOCK_15": "上次活动状态",
|
||||
"MOCK_16": "正在等待 1 个同伴 (KoalaPC)...",
|
||||
"MOCK_16": "正在等待 1 个同伴 (🐶 HappyDog)...",
|
||||
"MOCK_17": "仍然跳过并播放",
|
||||
"MOCK_18": "您的用户名",
|
||||
"MOCK_19": "隐藏杂乱标签",
|
||||
@@ -179,15 +189,22 @@
|
||||
"FOOTER_PRIVACY_LINK": "privacy",
|
||||
"FOOTER_DISCLAIMER": "KoalaSync 不隶属于 Netflix、Disney+、Amazon、YouTube、Twitch 或任何其他流媒体平台,也不受其认可或关联。所有商标均为其各自所有者的财产。",
|
||||
"FOOTER_SUPPORT": "Support KoalaSync",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync 与 Teleparty:你应该选择哪款在线同步观影工具?",
|
||||
"ALT_INDEX_TITLE": "KoalaSync - 替代方案与对比",
|
||||
"ALT_INDEX_META_DESC": "查看 KoalaSync 与其他 Watch Party 扩展和流媒体同步方案的真实对比与详细指南。",
|
||||
"ALT_INDEX_HEADING": "指南与对比",
|
||||
"ALT_INDEX_INTRO": "KoalaSync 与其他 Watch Party 工具和流媒体同步方案相比如何?阅读我们真实、偏技术的对比,找到最适合远程电影之夜的选择。",
|
||||
"ALT_INDEX_READ_COMPARISON": "阅读对比 →",
|
||||
"ALT_INDEX_READ_GUIDE": "阅读指南 →",
|
||||
"ALT_INDEX_FOOTER_LINK": "指南与对比",
|
||||
"ALT_TELEPARTY_TITLE": "KoalaSync 与 Teleparty:你应该选择哪款在线同步观影工具?",
|
||||
"ALT_TELEPARTY_META_DESC": "针对 KoalaSync 和 Teleparty 的实用对比,涵盖内置聊天功能、移动端支持、浏览器视频同步、自建媒体服务器以及开源特性等方面。",
|
||||
"ALT_TELEPARTY_INTRO": "KoalaSync 和 Teleparty 都能解决同一个令人头疼的问题:如何一起观看视频,而无需经历\u201c3、2、1,开始播放\u201d这种繁琐的同步步骤。只不过,它们采取了不同的实现方式。<br><br>如果你需要内置聊天功能或希望在移动设备上观看,Teleparty 是更合理的选择。KoalaSync 则更适合桌面浏览器观影场景——即你已经通过 Discord、TeamSpeak 或其他语音通话软件进行交流,只需确保视频播放保持同步即可。<br><br>我开发 KoalaSync 的主要初衷是为了灵活性。它旨在支持几乎所有使用标准浏览器视频播放器的网站,而不仅仅局限于特定的平台列表。这包括 Emby、Jellyfin 和 Plex 等自建媒体服务,本地/私有域名,以及浏览器中的各类常规视频页面。",
|
||||
"ALT_TELEPARTY_INTRO": "KoalaSync 和 Teleparty 都能解决同一个令人头疼的问题:如何一起观看视频,而无需经历“3、2、1,开始播放”这种繁琐的同步步骤。只不过,它们采取了不同的实现方式。<br><br>如果你需要内置聊天功能或希望在移动设备上观看,Teleparty 是更合理的选择。KoalaSync 则更适合桌面浏览器观影场景,即你已经通过 Discord、TeamSpeak 或其他语音通话软件进行交流,只需确保视频播放保持同步即可。<br><br>我开发 KoalaSync 的主要初衷是为了灵活性。它旨在支持几乎所有使用标准浏览器视频播放器的网站,而不仅仅局限于特定的平台列表。这包括 Emby、Jellyfin 和 Plex 等自建媒体服务,本地/私有域名,以及浏览器中的各类常规视频页面。",
|
||||
"ALT_TELEPARTY_WHEN_TELE_TITLE": "Teleparty 依然适用的场景",
|
||||
"ALT_TELEPARTY_WHEN_TELE_BODY": "如果你希望聊天功能集成在观影工具本身之中,Teleparty 仍然是更好的选择。KoalaSync 目前没有内置聊天功能;它默认你已经在其他平台(如 Discord、TeamSpeak、WhatsApp、Signal 或常规通话)上进行交流。<br><br>移动端支持是另一个关键因素。Teleparty 拥有专属应用程序,而 KoalaSync 目前仅支持桌面浏览器。如果你想在手机或平板电脑上同步观看 Netflix,KoalaSync 暂时并不适用。<br><br>因此,如果你最看重聊天功能和移动端支持,Teleparty 可能会更适合你的使用需求。",
|
||||
"ALT_TELEPARTY_WHEN_KOALA_TITLE": "KoalaSync 适用场景",
|
||||
"ALT_TELEPARTY_WHEN_KOALA_BODY": "如果您想要一款灵活的桌面浏览器同步工具,而不是受限于特定平台列表的软件,那么 KoalaSync 是更合适的选择。<br><br>它适用于几乎所有使用标准浏览器视频播放器的网站。对于 Emby、Jellyfin、Plex、私有媒体服务器、本地域或传统\u201c在线观影派对\u201d工具通常忽略的小型视频网站来说,这一点尤为实用。<br><br>此外,它完全免费且开源,无需注册账号;如果您希望运行自己的中继服务器,还可以选择自托管。我开发它的初衷是寻找一款既注重隐私又能与我自己的 Emby 系统兼容的工具,而不是那种仅针对少数几家大型流媒体平台的封闭式平台。",
|
||||
"ALT_TELEPARTY_WHEN_KOALA_BODY": "如果您想要一款灵活的桌面浏览器同步工具,而不是受限于特定平台列表的软件,那么 KoalaSync 是更合适的选择。<br><br>它适用于几乎所有使用标准浏览器视频播放器的网站。对于 Emby、Jellyfin、Plex、私有媒体服务器、本地域或传统“在线观影派对”工具通常忽略的小型视频网站来说,这一点尤为实用。<br><br>此外,它完全免费且开源,无需注册账号;如果您希望运行自己的中继服务器,还可以选择自托管。我开发它的初衷是寻找一款既注重隐私又能与我自己的 Emby 系统兼容的工具,而不是那种仅针对少数几家大型流媒体平台的封闭式平台。",
|
||||
"ALT_TELEPARTY_MAIN_DIFF_TITLE": "主要区别",
|
||||
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty 更侧重于提供包含聊天和移动端支持的\u201c观影派对\u201d体验;而 KoalaSync 则更像是一个针对浏览器视频的灵活同步层。<br><br>这听起来似乎差别不大,但实际上极大地改变了使用场景。如果您需要一个内置聊天功能的完整观影派对应用,Teleparty 是合理的选择;如果您已有语音聊天渠道,仅需保持各浏览器间的视频播放控制同步,那么 KoalaSync 则是更简洁适用的方案。",
|
||||
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty 更侧重于提供包含聊天和移动端支持的“观影派对”体验;而 KoalaSync 则更像是一个针对浏览器视频的灵活同步层。<br><br>这听起来似乎差别不大,但实际上极大地改变了使用场景。如果您需要一个内置聊天功能的完整观影派对应用,Teleparty 是合理的选择;如果您已有语音聊天渠道,仅需保持各浏览器间的视频播放控制同步,那么 KoalaSync 则是更简洁适用的方案。",
|
||||
"ALT_TELEPARTY_LIMITS_TITLE": "局限性说明",
|
||||
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync 并非万能,我们也坦诚其局限性。目前它仅支持桌面端浏览器,没有内置聊天功能,也没有移动端应用。<br><br>兼容性也取决于浏览器和视频播放器。虽然 KoalaSync 理论上支持几乎所有标准浏览器视频播放器,但网站随时可能更改其播放器、DRM(数字版权管理)机制或浏览器限制策略。<br><br>KoalaSync 与 Netflix、Disney+、Prime Video、YouTube、Teleparty、Emby、Jellyfin、Plex 或本文提及的任何其他品牌均无关联。提及这些名称仅为说明典型的使用场景。",
|
||||
"ALT_TELEPARTY_SUMMARY_TITLE": "总结",
|
||||
@@ -196,7 +213,7 @@
|
||||
"ALT_SCREEN_META_DESC": "针对远程观影之夜,从播放控制、视频质量、隐私保护、字幕支持及浏览器观看体验等方面,对 KoalaSync 与屏幕共享进行实用对比。",
|
||||
"ALT_SCREEN_INTRO": "屏幕共享确实可行,而且老实说,有时这就足够了。一人共享屏幕,其他人观看,搞定。<br><br>但在长时间观影或一起追剧时,它可能会让人感到烦恼。视频质量取决于共享者的上传带宽、CPU、浏览器,以及 Discord、Teamspeak、Zoom 或其他通话应用所采用的压缩算法。此外,实际上只有一个人能控制播放。",
|
||||
"ALT_SCREEN_WHEN_ENOUGH_TITLE": "何时使用屏幕共享就足够了",
|
||||
"ALT_SCREEN_WHEN_ENOUGH_BODY": "如果您追求最快的设置速度,屏幕共享是个不错的选择。如果只有一个人能访问该视频……\u201c或者,如果大家都能接受由一个人来控制播放、暂停和进度跳转,那这可能是最简单的方案。<br><br>此外,如果画质要求不高,大家不需要各自独立的字幕或音频设置,且分享者也不介意展示自己的浏览器窗口或屏幕,那么这种方式也很合理。",
|
||||
"ALT_SCREEN_WHEN_ENOUGH_BODY": "如果您追求最快的设置速度,屏幕共享是个不错的选择。如果只有一个人能访问该视频……“或者,如果大家都能接受由一个人来控制播放、暂停和进度跳转,那这可能是最简单的方案。<br><br>此外,如果画质要求不高,大家不需要各自独立的字幕或音频设置,且分享者也不介意展示自己的浏览器窗口或屏幕,那么这种方式也很合理。",
|
||||
"ALT_SCREEN_WHEN_ANNOYING_TITLE": "屏幕共享可能带来的不便",
|
||||
"ALT_SCREEN_WHEN_ANNOYING_BODY": "最大的弊端在于,并非每个人都在自己的播放器中观看。由一人流式传输屏幕画面,其他人只能观看该画面。<br><br>这意味着视频画质可能较差、音频经过压缩、播放存在延迟,且其他人缺乏控制权。字幕和音轨也完全取决于分享者的选择。<br><br>此外还涉及隐私问题。如果你切换窗口或标签页,其他人可能会短暂看到你的浏览器标签页、电子邮件、Reddit 页面、聊天记录或其他私密内容。虽然可以通过使用独立窗口来规避这一问题,但这仍是需要考虑的因素。",
|
||||
"ALT_SCREEN_WHY_CLEANER_TITLE": "为何 KoalaSync 体验更清爽",
|
||||
@@ -204,6 +221,30 @@
|
||||
"ALT_SCREEN_LIMITS_TITLE": "客观存在的局限性",
|
||||
"ALT_SCREEN_LIMITS_BODY": "KoalaSync 无法替代语音通话。如果您想在观看的同时进行交流,仍可使用 Discord、Teamspeak、Signal、WhatsApp 或您常用的任何通讯工具。<br><br>此外,每位参与者都需要在各自的设备上访问相应的视频或网页。KoalaSync 无法绕过订阅、登录验证、DRM(数字版权管理)、地域限制或平台规则。<br><br>该工具目前仅支持桌面端浏览器,不包含内置聊天或语音功能;其兼容性取决于具体的浏览器和视频播放器。",
|
||||
"ALT_SCREEN_SUMMARY_TITLE": "摘要",
|
||||
"ALT_SCREEN_SUMMARY_BODY": "如果您追求快速便捷,且不太在意画质、隐私或个性化控制,那么屏幕共享是最简单的选择。而当您希望每位参与者都在各自的浏览器中观看,并营造出一种真正\u201c共同观看\u201d的氛围时,KoalaSync 则是更理想的方案。",
|
||||
"ALT_TRY_KOALASYNC": "试用 KoalaSync"
|
||||
"ALT_SCREEN_SUMMARY_BODY": "如果您追求快速便捷,且不太在意画质、隐私或个性化控制,那么屏幕共享是最简单的选择。而当您希望每位参与者都在各自的浏览器中观看,并营造出一种真正“共同观看”的氛围时,KoalaSync 则是更理想的方案。",
|
||||
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
|
||||
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
|
||||
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
|
||||
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
|
||||
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
|
||||
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
|
||||
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
|
||||
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
|
||||
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
|
||||
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
|
||||
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
|
||||
"ALT_W2G_SUMMARY_TITLE": "In short",
|
||||
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
|
||||
"ALT_TRY_KOALASYNC": "试用 KoalaSync",
|
||||
"MOCK_HOST_CONTROL_LABEL": "主持人控制",
|
||||
"MOCK_HOST_CONTROL_TOGGLE": "只有我可以控制播放",
|
||||
"LABEL_SELECT_VIDEO": "选择视频",
|
||||
"OPTION_SELECT_TAB": "-- 选择一个选项卡 --",
|
||||
"THEME_TOGGLE_LABEL": "切换浅色/深色模式",
|
||||
"FAQ_SHOW_ALL": "显示全部问题",
|
||||
"FAQ_HIDE_ALL": "收起问题",
|
||||
"SELF_SHOW_SETUP": "显示部署指南",
|
||||
"SELF_HIDE_SETUP": "收起指南",
|
||||
"HERO_TRUST": "免费 · 开源 · 无需账号",
|
||||
"COMP_MORE_GUIDES": "完整对比与更多替代方案"
|
||||
}
|
||||
|
||||
+78
-21
@@ -90,14 +90,28 @@
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<h2>1. Hosting & Logfiles</h2>
|
||||
<h2>1. Contact</h2>
|
||||
<p>
|
||||
This site is hosted on a private server. To ensure stability, standard server logs (IP, browser, time) are collected, but not linked to individuals and are automatically deleted after 7 days.
|
||||
KoalaSync is operated by Timo Schmidt.<br>
|
||||
For privacy-related questions, contact: <span class="email-reveal" data-user="koalasync" data-domain="koalastuff.net" style="color: var(--accent); cursor: pointer; text-decoration: underline;">[Show Email]</span>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>2. No Third Parties & Open Source</h2>
|
||||
<h2>2. Website Hosting & Access Logs</h2>
|
||||
<p>
|
||||
KoalaSync is hosted on infrastructure provided by Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Germany. The server is located in Finland, within the EU/EEA. A data processing agreement pursuant to Art. 28 GDPR has been concluded with Hetzner.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
When you visit this website, standard access logs may include your IP address, browser/user agent and timestamp. These logs are used only for security, stability and abuse prevention and are automatically deleted after 7 days.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Legal basis: Art. 6(1)(f) GDPR, legitimate interest in operating the website securely, maintaining stability, and preventing abuse.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>3. No Third Parties & Open Source</h2>
|
||||
<p>
|
||||
KoalaSync deliberately avoids analytics tools, tracking cookies, or advertising networks. We do not load any third-party resources (such as Google Fonts) to maximize the protection of your privacy.
|
||||
</p>
|
||||
@@ -107,29 +121,41 @@
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>3. Relay Server Architecture</h2>
|
||||
<h2>4. Official Relay Server & Room Data</h2>
|
||||
<p>
|
||||
Our relay server operates exclusively in memory (RAM). Messages between participants are not stored on hard drives and are volatile. As soon as a room is closed, all associated metadata is immediately deleted. We don't track you. We only track our server, relying on aggregated, anonymous, and non-personal system performance metrics to monitor server health.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>4. Browser Extension</h2>
|
||||
<p>
|
||||
To enable cross-device synchronization, the KoalaSync browser extension temporarily captures data from the currently active video tab (e.g., tab title, media metadata like the video title, and playback state). This data is exclusively sent to other participants in your room for synchronization. We explicitly <strong>do not read, store, or transmit your general browsing history</strong>.
|
||||
The official KoalaSync relay server does not keep persistent logs. It does not store room history, sync history, watch history, room messages, playback events, tab titles, or media titles on disk.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
The extension only connects to the relay server while you are actively in a room. No persistent background connection is maintained — your IP address is not exposed to the server when you are not using the extension.
|
||||
To provide the room feature, the relay temporarily processes room and synchronization data in memory while a room is active. This can include room ID, peer ID, display name, playback state, sync events, and, depending on title privacy settings, tab/media titles. This data is only forwarded to participants in the same room and is removed from memory when the room expires or is closed.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Legal basis: Art. 6(1)(f) GDPR, legitimate interest in providing the requested synchronization functionality, maintaining security, and preventing abuse.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Room and peer counts are aggregated operational metrics used to monitor server status.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>5. Extension Permissions</h2>
|
||||
<h2>5. Browser Extension & Local Storage</h2>
|
||||
<p>
|
||||
To synchronize playback between participants in the same room, the KoalaSync browser extension temporarily captures data from the currently active video tab (e.g., tab title, media metadata like the video title, and playback state). Tab or media titles can potentially be personal depending on content. They are only processed for the synchronization feature, can be limited or disabled through title privacy settings in the extension, are forwarded only to room participants, and are not logged or stored persistently by the relay.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
The extension stores only settings required for operation locally in the browser, such as username, server URL, current room credentials, language, title privacy settings, notification preferences and audio-processing settings. KoalaSync does not store general browsing history or a persistent watch history.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
The extension only connects to the relay server while you are actively in a room. No persistent background connection is maintained, so your IP address is not exposed to the server when you are not using the extension.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>6. Extension Permissions</h2>
|
||||
<p>
|
||||
To fulfill its technical purpose, the browser extension requires certain permissions. Each permission is used exclusively for core functionality:
|
||||
</p>
|
||||
<ul style="margin-left: 1.5rem; margin-top: 0.5rem; color: var(--text-muted); font-size: 0.9rem; list-style-type: disc; display: flex; flex-direction: column; gap: 0.35rem;">
|
||||
<li><strong>storage</strong>: Allows local storage of your username, server URL, and room credentials in your browser so you don't have to log in every time.</li>
|
||||
<li><strong>storage</strong>: Allows local storage of your username, server URL, room credentials, and settings in your browser so they persist between sessions.</li>
|
||||
<li><strong>tabs</strong>: Required to list open tabs in the extension's dropdown and read their titles, making it easy for you to select the correct video tab.</li>
|
||||
<li><strong>scripting</strong>: Required to securely inject the synchronization script (content.js) into your selected video tab.</li>
|
||||
<li><strong>alarms</strong>: Prevents the extension's background service worker from being suspended by the browser during an active synchronization session.</li>
|
||||
@@ -140,18 +166,49 @@
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>6. Brute-Force Protection</h2>
|
||||
<h2>7. Brute-Force & Rate-Limit Protection</h2>
|
||||
<p>
|
||||
For the security of our users, we store failed login attempts (IP address and room ID) for a maximum of 15 minutes in RAM to prevent automated attacks. This data is deleted without a trace afterwards.
|
||||
For abuse prevention, failed join/login attempts may temporarily keep IP address and room ID in memory for up to 15 minutes. This data is not written to disk, is not exposed publicly, and is deleted automatically. Legal basis: Art. 6(1)(f) GDPR, legitimate interest in preventing brute-force attacks and abuse.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>7. Your Rights</h2>
|
||||
<h2>8. Recipients & Third Parties</h2>
|
||||
<p>
|
||||
You have the right to information, correction, or deletion of your data. However, since we do not store any personal data permanently, linking data to your person is technically impossible in most cases.
|
||||
Technical hosting and connection data may be processed by Hetzner as hosting provider on behalf of the operator. Room sync data is forwarded only to participants in the same room. KoalaSync does not use analytics providers, advertising networks, or tracking services.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>9. Required & Optional Data</h2>
|
||||
<p>
|
||||
Providing technical connection data is necessary to access the website and use the relay. Room credentials and sync events are necessary to use room synchronization. Sharing tab/media titles is optional and can be limited through the extension’s privacy settings.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>10. Data Subject Rights</h2>
|
||||
<p>
|
||||
Under the GDPR, you may have the right to access, rectification, erasure, restriction of processing, data portability, objection, and the right to lodge a complaint with a supervisory authority.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
Because KoalaSync does not use accounts and does not keep persistent room logs or watch history, many data points are either short-lived or cannot be linked back to a specific person after deletion. KoalaSync does not collect or retain additional identification data solely to identify users for rights requests.
|
||||
</p>
|
||||
<p style="margin-top: 0.5rem;">Contact for privacy requests: <span class="email-reveal" data-user="koalasync" data-domain="koalastuff.net" style="color: var(--accent); cursor: pointer; text-decoration: underline;">[Show Email]</span></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>11. No Profiling & Automated Decision-Making</h2>
|
||||
<p>
|
||||
KoalaSync does not use profiling or automated decision-making within the meaning of Art. 22 GDPR.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>12. Third-Country Transfers</h2>
|
||||
<p>
|
||||
The regular hosting setup is located within the EU/EEA. No regular transfer of personal data to third countries takes place.
|
||||
</p>
|
||||
<p>Contact for questions: <span class="email-reveal" data-user="koalasync_datenschutz" data-domain="koalamail.rocks" style="color: var(--accent); cursor: pointer; text-decoration: underline;">[Show Email]</span></p>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
@@ -159,7 +216,7 @@
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2026 KoalaSync. Open source under the MIT License.</p>
|
||||
<p style="font-size: 0.8rem; margin-top: 0.5rem;">No data is stored on our servers. Pure RAM-based relay.</p>
|
||||
<p style="font-size: 0.8rem; margin-top: 0.5rem;">No relay logs. No tracking. Website access logs are deleted after 7 days.</p>
|
||||
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
|
||||
<a href="imprint" style="color: var(--text-muted); text-decoration: none;">Legal Notice</a>
|
||||
<a href="privacy" style="color: var(--text-muted); text-decoration: none;">Privacy Policy</a>
|
||||
|
||||
+132
-62
@@ -3,31 +3,31 @@
|
||||
xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/imprint</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/privacy</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/de/impressum</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/de/datenschutz</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -49,7 +49,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/de/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -71,7 +71,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -93,7 +93,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/de/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -115,7 +115,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -137,7 +137,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/de/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -159,7 +159,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/fr/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -181,7 +181,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/fr/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -203,7 +203,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/fr/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -225,7 +225,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/es/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -247,7 +247,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/es/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -269,7 +269,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/es/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -291,7 +291,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/it/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -313,7 +313,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/it/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -335,7 +335,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/it/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -357,7 +357,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/nl/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -379,7 +379,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/nl/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -401,7 +401,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/nl/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -423,7 +423,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/pl/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -445,7 +445,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/pl/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -467,7 +467,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/pl/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -489,7 +489,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/pt/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -511,7 +511,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/pt/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -533,7 +533,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/pt/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -555,7 +555,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/pt-BR/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -577,7 +577,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/pt-BR/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -599,7 +599,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/pt-BR/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -621,7 +621,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/ru/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -643,7 +643,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/ru/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -665,7 +665,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/ru/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -687,7 +687,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/tr/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -709,7 +709,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/tr/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -731,7 +731,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/tr/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -753,7 +753,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/ja/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -775,7 +775,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/ja/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -797,7 +797,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/ja/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -819,7 +819,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/ko/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -841,7 +841,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/ko/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -863,7 +863,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/ko/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -885,7 +885,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/zh/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -907,7 +907,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/zh/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -929,7 +929,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/zh/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -951,7 +951,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/uk/alternatives</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
|
||||
@@ -973,7 +973,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/uk/alternatives/teleparty</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
|
||||
@@ -995,7 +995,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/uk/alternatives/screen-sharing</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
|
||||
@@ -1017,7 +1017,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
@@ -1032,12 +1032,14 @@
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/de/</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
@@ -1052,12 +1054,14 @@
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/fr/</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
@@ -1072,12 +1076,14 @@
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/es/</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
@@ -1092,12 +1098,14 @@
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/pt-BR/</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
@@ -1112,12 +1120,14 @@
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/ru/</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
@@ -1132,12 +1142,14 @@
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/it/</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
@@ -1152,12 +1164,14 @@
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/pl/</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
@@ -1172,12 +1186,14 @@
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/tr/</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
@@ -1192,12 +1208,14 @@
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/nl/</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
@@ -1212,12 +1230,14 @@
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/ja/</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
@@ -1232,12 +1252,14 @@
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/ko/</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
@@ -1252,12 +1274,58 @@
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/zh/</loc>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
<xhtml:link rel="alternate" hreflang="de" href="https://sync.koalastuff.net/de/"/>
|
||||
<xhtml:link rel="alternate" hreflang="fr" href="https://sync.koalastuff.net/fr/"/>
|
||||
<xhtml:link rel="alternate" hreflang="es" href="https://sync.koalastuff.net/es/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt-br" href="https://sync.koalastuff.net/pt-BR/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ru" href="https://sync.koalastuff.net/ru/"/>
|
||||
<xhtml:link rel="alternate" hreflang="it" href="https://sync.koalastuff.net/it/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pl" href="https://sync.koalastuff.net/pl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="tr" href="https://sync.koalastuff.net/tr/"/>
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/uk/</loc>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
<xhtml:link rel="alternate" hreflang="de" href="https://sync.koalastuff.net/de/"/>
|
||||
<xhtml:link rel="alternate" hreflang="fr" href="https://sync.koalastuff.net/fr/"/>
|
||||
<xhtml:link rel="alternate" hreflang="es" href="https://sync.koalastuff.net/es/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt-br" href="https://sync.koalastuff.net/pt-BR/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ru" href="https://sync.koalastuff.net/ru/"/>
|
||||
<xhtml:link rel="alternate" hreflang="it" href="https://sync.koalastuff.net/it/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pl" href="https://sync.koalastuff.net/pl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="tr" href="https://sync.koalastuff.net/tr/"/>
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sync.koalastuff.net/pt/</loc>
|
||||
<lastmod>2026-06-29</lastmod>
|
||||
<lastmod>2026-07-02</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
|
||||
@@ -1272,6 +1340,8 @@
|
||||
<xhtml:link rel="alternate" hreflang="nl" href="https://sync.koalastuff.net/nl/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://sync.koalastuff.net/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://sync.koalastuff.net/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh" href="https://sync.koalastuff.net/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="uk" href="https://sync.koalastuff.net/uk/"/>
|
||||
<xhtml:link rel="alternate" hreflang="pt" href="https://sync.koalastuff.net/pt/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/"/>
|
||||
</url>
|
||||
|
||||
+2352
-88
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,105 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
|
||||
const websiteDir = __dirname;
|
||||
const sitemapPath = path.join(websiteDir, 'sitemap.xml');
|
||||
|
||||
// 1. Find the IndexNow key file in the website directory
|
||||
const files = fs.readdirSync(websiteDir);
|
||||
let keyFile = null;
|
||||
|
||||
for (const file of files) {
|
||||
const isIndexNowFile = (/^[a-zA-Z0-9_-]{8,}\.txt$/i.test(file) || /^[a-f0-9-]{32,36}$/i.test(file)) && file !== 'robots.txt';
|
||||
if (isIndexNowFile) {
|
||||
keyFile = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!keyFile) {
|
||||
console.error('Error: Could not find any IndexNow key file in the website/ directory (e.g. <key>.txt or UUID file).');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const keyFilePath = path.join(websiteDir, keyFile);
|
||||
const keyContent = fs.readFileSync(keyFilePath, 'utf8').trim();
|
||||
|
||||
// The key is either the file contents or the filename itself (excluding .txt)
|
||||
const key = keyContent || keyFile.replace(/\.txt$/i, '');
|
||||
const keyLocation = `https://sync.koalastuff.net/${keyFile}`;
|
||||
|
||||
console.log(`Found IndexNow Key: ${key}`);
|
||||
console.log(`Key Location: ${keyLocation}`);
|
||||
|
||||
// 2. Parse URLs from sitemap.xml
|
||||
if (!fs.existsSync(sitemapPath)) {
|
||||
console.error(`Error: sitemap.xml not found at ${sitemapPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const sitemapContent = fs.readFileSync(sitemapPath, 'utf8');
|
||||
const urlRegex = /<loc>(https:\/\/sync\.koalastuff\.net[^<]+)<\/loc>/g;
|
||||
const urls = [];
|
||||
let match;
|
||||
|
||||
while ((match = urlRegex.exec(sitemapContent)) !== null) {
|
||||
urls.push(match[1]);
|
||||
}
|
||||
|
||||
if (urls.length === 0) {
|
||||
console.error('Error: No URLs found in sitemap.xml matching https://sync.koalastuff.net');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Parsed ${urls.length} URLs from sitemap.xml.`);
|
||||
|
||||
// 3. Prepare IndexNow payload
|
||||
const payload = JSON.stringify({
|
||||
host: 'sync.koalastuff.net',
|
||||
key: key,
|
||||
keyLocation: keyLocation,
|
||||
urlList: urls
|
||||
});
|
||||
|
||||
// 4. Send request to IndexNow API
|
||||
function submitIndexNow() {
|
||||
const options = {
|
||||
hostname: 'api.indexnow.org',
|
||||
port: 443,
|
||||
path: '/indexnow',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'Content-Length': Buffer.byteLength(payload)
|
||||
}
|
||||
};
|
||||
|
||||
console.log('Sending request to https://api.indexnow.org/indexnow...');
|
||||
const req = https.request(options, (res) => {
|
||||
let responseBody = '';
|
||||
res.on('data', (chunk) => {
|
||||
responseBody += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
console.log(`Response Status: ${res.statusCode} ${res.statusMessage}`);
|
||||
if (res.statusCode === 200 || res.statusCode === 202) {
|
||||
console.log('Success! IndexNow has accepted the URLs for re-indexing.');
|
||||
} else {
|
||||
console.error(`IndexNow submission failed. Response body: ${responseBody}`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (e) => {
|
||||
console.error(`Request error: ${e.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
req.write(payload);
|
||||
req.end();
|
||||
}
|
||||
|
||||
submitIndexNow();
|
||||
+405
-270
@@ -36,7 +36,10 @@
|
||||
<meta property="og:url" content="https://sync.koalastuff.net/{{CANONICAL_PATH}}">
|
||||
<meta property="og:title" content="{{OG_TITLE}}">
|
||||
<meta property="og:description" content="{{OG_DESCRIPTION}}">
|
||||
<meta property="og:image" content="https://sync.koalastuff.net/assets/PlatformJuggler_New.webp">
|
||||
<meta property="og:image" content="https://sync.koalastuff.net/assets/og-image.png">
|
||||
<meta property="og:image:width" content="1280">
|
||||
<meta property="og:image:height" content="640">
|
||||
<meta property="og:image:type" content="image/png">
|
||||
<meta property="og:locale" content="{{OG_LOCALE}}">
|
||||
<meta property="og:locale:alternate" content="en_US">
|
||||
<meta property="og:locale:alternate" content="de_DE">
|
||||
@@ -59,7 +62,7 @@
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="{{TWITTER_TITLE}}">
|
||||
<meta name="twitter:description" content="{{TWITTER_DESCRIPTION}}">
|
||||
<meta name="twitter:image" content="https://sync.koalastuff.net/assets/PlatformJuggler_New.webp">
|
||||
<meta name="twitter:image" content="https://sync.koalastuff.net/assets/og-image.png">
|
||||
|
||||
<!-- Mobile Browser Theme Styling -->
|
||||
<meta name="theme-color" content="#0f172a">
|
||||
@@ -106,7 +109,7 @@
|
||||
"priceCurrency": "EUR"
|
||||
},
|
||||
"description": "{{SCHEMA_APP_DESC}}",
|
||||
"softwareVersion": "2.5.0",
|
||||
"softwareVersion": "{{VERSION}}",
|
||||
"license": "https://opensource.org/licenses/MIT",
|
||||
"sameAs": "https://github.com/Shik3i/KoalaSync",
|
||||
"image": "https://sync.koalastuff.net/assets/NewLogoIcon.webp",
|
||||
@@ -201,6 +204,7 @@
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="scroll-progress-bar"></div>
|
||||
|
||||
<a class="skip-link" href="#main-content">Skip to main content</a>
|
||||
|
||||
@@ -213,7 +217,7 @@
|
||||
<nav>
|
||||
<div class="container nav-content">
|
||||
<div class="logo-area">
|
||||
<img src="{{ASSET_PATH}}assets/NewLogoIcon_128.webp" srcset="{{ASSET_PATH}}assets/NewLogoIcon_64.webp 64w, {{ASSET_PATH}}assets/NewLogoIcon_128.webp 128w, {{ASSET_PATH}}assets/NewLogoIcon.webp 256w" sizes="64px" alt="KoalaSync Logo" width="64" height="64">
|
||||
<img class="nav-logo-img" src="{{ASSET_PATH}}assets/NewLogoIcon_128.webp" srcset="{{ASSET_PATH}}assets/NewLogoIcon_64.webp 64w, {{ASSET_PATH}}assets/NewLogoIcon_128.webp 128w, {{ASSET_PATH}}assets/NewLogoIcon.webp 256w" sizes="64px" alt="KoalaSync Logo" width="64" height="64">
|
||||
<span>KoalaSync</span>
|
||||
<div id="nav-extension-status" class="nav-ext-status" style="display: none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" width="12" height="12"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
@@ -246,6 +250,10 @@
|
||||
</nav>
|
||||
</div>
|
||||
<div class="nav-right">
|
||||
<button class="theme-toggle" id="theme-toggle" type="button" aria-label="{{THEME_TOGGLE_LABEL}}" title="{{THEME_TOGGLE_LABEL}}">
|
||||
<svg class="theme-icon-sun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="18" height="18" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></svg>
|
||||
<svg class="theme-icon-moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="18" height="18" aria-hidden="true"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
|
||||
</button>
|
||||
<div class="lang-select-container">
|
||||
<select class="lang-dropdown" aria-label="Select Language">
|
||||
<option value="en" {{SELECTED_EN}}>🇬🇧 English</option>
|
||||
@@ -276,33 +284,168 @@
|
||||
<header class="hero" id="main-content">
|
||||
<div class="container hero-grid">
|
||||
<div class="hero-text">
|
||||
<a href="https://github.com/Shik3i/KoalaSync/blob/main/docs/CHANGELOG.md" target="_blank" rel="noopener" class="version-badge" data-reveal style="text-decoration: none; display: inline-block; transition: transform 0.2s, box-shadow 0.2s;" title="View Changelog">
|
||||
<span class="version-text-en" lang="en">v2.2.1 OUT NOW</span>
|
||||
<span class="version-text-de" lang="de">v2.2.1 JETZT VERFÜGBAR</span>
|
||||
</a>
|
||||
<h1 data-reveal>{{HERO_TITLE}}</h1>
|
||||
<p class="hero-subtitle" data-reveal>{{HERO_SUBTITLE}}</p>
|
||||
<div class="hero-mascot-container" data-reveal>
|
||||
<h1>{{HERO_TITLE}}</h1>
|
||||
<p class="hero-subtitle">{{HERO_SUBTITLE}}</p>
|
||||
<div class="hero-mascot-container">
|
||||
<img src="{{ASSET_PATH}}assets/LookDownKoala-1x.webp" srcset="{{ASSET_PATH}}assets/LookDownKoala-1x.webp 90w, {{ASSET_PATH}}assets/LookDownKoala.webp 205w" sizes="90px" alt="{{HERO_MASCOT_ALT}}" class="hero-lookdown-mascot" width="90" height="132" loading="eager" fetchpriority="high" decoding="async">
|
||||
<a href="https://github.com/Shik3i/KoalaSync/blob/main/docs/CHANGELOG.md" target="_blank" rel="noopener" class="koala-speech-bubble" data-release="{{VERSION_DATE}}" title="View Changelog">
|
||||
<span class="version-text-en" lang="en">
|
||||
<strong class="bubble-title">v{{VERSION}} is out! 🎉</strong>
|
||||
<span class="bubble-sub"><span class="bubble-ago"></span>See what's new →</span>
|
||||
</span>
|
||||
<span class="version-text-de" lang="de">
|
||||
<strong class="bubble-title">v{{VERSION}} ist da! 🎉</strong>
|
||||
<span class="bubble-sub"><span class="bubble-ago"></span>Was ist neu? →</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="cta-group" data-reveal>
|
||||
<a href="https://chromewebstore.google.com/detail/koalasync/obbnmkmlaaddodakcbdljknjpagklifc" class="btn btn-primary">
|
||||
<img src="{{ASSET_PATH}}assets/chrome.svg" alt="Chrome" width="20" height="20">
|
||||
<span>{{ADD_TO_CHROME}}</span>
|
||||
<div class="cta-group">
|
||||
<a href="https://chromewebstore.google.com/detail/koalasync/obbnmkmlaaddodakcbdljknjpagklifc" class="btn btn-primary btn-install" data-browser="chrome">
|
||||
<img src="{{ASSET_PATH}}assets/chrome.svg" alt="" width="20" height="20">
|
||||
<span class="btn-label-short">Chrome</span>
|
||||
<span class="btn-label-full">{{ADD_TO_CHROME}}</span>
|
||||
</a>
|
||||
<a href="https://addons.mozilla.org/de/firefox/addon/koalasync/" class="btn btn-firefox">
|
||||
<img src="{{ASSET_PATH}}assets/firefox.svg" alt="Firefox" width="20" height="20">
|
||||
<span>{{ADD_TO_FIREFOX}}</span>
|
||||
<a href="https://addons.mozilla.org/de/firefox/addon/koalasync/" class="btn btn-firefox btn-install" data-browser="firefox">
|
||||
<img src="{{ASSET_PATH}}assets/firefox.svg" alt="" width="20" height="20">
|
||||
<span class="btn-label-short">Firefox</span>
|
||||
<span class="btn-label-full">{{ADD_TO_FIREFOX}}</span>
|
||||
</a>
|
||||
<a href="https://github.com/Shik3i/KoalaSync/releases" target="_blank" rel="noopener" class="btn btn-secondary" style="display: inline-flex; align-items: center; gap: 8px;">
|
||||
<a href="https://github.com/Shik3i/KoalaSync/releases" target="_blank" rel="noopener" class="btn btn-secondary btn-compact hero-github-cta">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="20" height="20" aria-hidden="true" style="display: block;"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
||||
GitHub Releases
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
<button class="btn btn-secondary btn-demo-mobile" id="mobile-demo-open" type="button" aria-haspopup="dialog" aria-controls="mobile-demo-modal">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="18" height="18" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg>
|
||||
<span>{{MOBILE_DEMO_CTA}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="cta-trust">{{HERO_TRUST}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Interactive CSS Extension Mockup -->
|
||||
<div class="hero-mockup-wrapper" data-reveal>
|
||||
<!-- Interactive Live Demo: two synced video tabs + the extension popup -->
|
||||
<div class="hero-mockup-wrapper">
|
||||
<div class="hero-demo-scene popup-open connected" id="hero-demo">
|
||||
<button class="demo-sync-chip" id="demo-sync-chip" type="button" title="Live sync status of the two demo tabs">
|
||||
<span class="demo-chip-dot" aria-hidden="true"></span>
|
||||
<span class="demo-chip-label-sync">{{DEMO_SYNC}}</span>
|
||||
<span class="demo-chip-event"></span>
|
||||
</button>
|
||||
|
||||
<div class="demo-tab-card demo-tab-a" id="demo-tab-a" role="button" tabindex="0" aria-pressed="false" aria-label="Press play or pause as 🐱 ChillCat. KoalaSync mirrors it to the other tab" title="Press play/pause here. KoalaSync mirrors it to the other tab">
|
||||
<div class="demo-tab-titlebar">
|
||||
<span class="demo-tab-dots" aria-hidden="true"><i></i><i></i><i></i></span>
|
||||
<span class="demo-tab-title"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="11" height="11" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg> <span class="demo-title-text-ep1">Stranger Things - S4E1</span></span>
|
||||
<span class="demo-tab-user demo-user-a">🐱 ChillCat</span>
|
||||
<button class="demo-ext-launcher" id="demo-launcher" type="button" aria-expanded="true" title="Open or close the KoalaSync popup">
|
||||
<img src="{{ASSET_PATH}}assets/NewLogoIcon.webp" alt="" width="16" height="16" loading="eager" decoding="async">
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-video">
|
||||
<div class="demo-video-art" aria-hidden="true"></div>
|
||||
<div class="demo-seek-sweep" aria-hidden="true"></div>
|
||||
<svg class="demo-film" viewBox="0 0 160 90" preserveAspectRatio="xMidYMid slice" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
|
||||
<g class="film-stars" fill="#e2e8f0">
|
||||
<circle class="film-star" cx="18" cy="14" r="0.8"/>
|
||||
<circle class="film-star" cx="42" cy="24" r="0.6"/>
|
||||
<circle class="film-star" cx="64" cy="10" r="0.9"/>
|
||||
<circle class="film-star" cx="90" cy="28" r="0.6"/>
|
||||
<circle class="film-star" cx="108" cy="16" r="0.5"/>
|
||||
<circle class="film-star" cx="134" cy="22" r="0.8"/>
|
||||
<circle class="film-star" cx="150" cy="12" r="0.5"/>
|
||||
</g>
|
||||
<circle class="film-moon" cx="122" cy="26" r="7.5" fill="#f1f5f9" opacity="0.92"/>
|
||||
<path class="film-mtn-scroll" d="M0,60 L30,45 L70,55 L110,40 L160,60 L190,45 L230,55 L270,40 L320,60 L320,92 L0,92 Z" fill="rgba(30,27,75,0.7)"/>
|
||||
<g class="film-mid-scroll">
|
||||
<polygon points="20,76 26,60 32,76" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="55,78 63,52 71,78" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="95,76 101,62 107,76" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="135,77 144,55 153,77" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="180,76 186,60 192,76" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="215,78 223,52 231,78" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="255,76 261,62 267,76" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="295,77 304,55 313,77" fill="rgba(49,46,129,0.55)"/>
|
||||
<path d="M0,76 Q40,73 80,76 T160,76 Q200,73 240,76 T320,76 L320,92 L0,92 Z" fill="rgba(49,46,129,0.55)"/>
|
||||
</g>
|
||||
<g class="film-fore-scroll">
|
||||
<polygon points="40,84 52,48 64,84" fill="#090d16"/>
|
||||
<polygon points="115,84 128,42 141,84" fill="#090d16"/>
|
||||
<polygon points="200,84 212,48 224,84" fill="#090d16"/>
|
||||
<polygon points="275,84 288,42 301,84" fill="#090d16"/>
|
||||
<path d="M0,84 Q80,82 160,84 T320,84 L320,92 L0,92 Z" fill="#090d16"/>
|
||||
</g>
|
||||
<circle class="film-bounce-ball" cx="80" cy="46" r="3.5" fill="#fde047"/>
|
||||
</svg>
|
||||
<div class="demo-play-overlay" aria-hidden="true">
|
||||
<span class="demo-icon-wrap">
|
||||
<svg class="icon-play" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M8 5v14l11-7z"/></svg>
|
||||
<svg class="icon-pause" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M6 5h4v14H6zM14 5h4v14h-4z"/></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="demo-video-controls">
|
||||
<span class="demo-time">12:34</span>
|
||||
<div class="demo-progress" title="Seek: KoalaSync jumps the other tab to the same spot"><div class="demo-progress-fill"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-tab-card demo-tab-b" id="demo-tab-b" role="button" tabindex="0" aria-pressed="false" aria-label="Press play or pause as 🐶 HappyDog. KoalaSync mirrors it to the other tab" title="Press play/pause here. KoalaSync mirrors it to the other tab">
|
||||
<div class="demo-tab-titlebar">
|
||||
<span class="demo-tab-dots" aria-hidden="true"><i></i><i></i><i></i></span>
|
||||
<span class="demo-tab-title"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="11" height="11" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg> <span class="demo-title-text-ep1">Stranger Things - S4E1</span></span>
|
||||
<span class="demo-tab-user demo-user-b">🐶 HappyDog</span>
|
||||
<span class="demo-ext-launcher demo-ext-static" aria-hidden="true" title="KoalaSync is installed here too">
|
||||
<img src="{{ASSET_PATH}}assets/NewLogoIcon.webp" alt="" width="16" height="16" loading="eager" decoding="async">
|
||||
</span>
|
||||
</div>
|
||||
<div class="demo-video">
|
||||
<div class="demo-video-art" aria-hidden="true"></div>
|
||||
<div class="demo-seek-sweep" aria-hidden="true"></div>
|
||||
<svg class="demo-film" viewBox="0 0 160 90" preserveAspectRatio="xMidYMid slice" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
|
||||
<g class="film-stars" fill="#e2e8f0">
|
||||
<circle class="film-star" cx="18" cy="14" r="0.8"/>
|
||||
<circle class="film-star" cx="42" cy="24" r="0.6"/>
|
||||
<circle class="film-star" cx="64" cy="10" r="0.9"/>
|
||||
<circle class="film-star" cx="90" cy="28" r="0.6"/>
|
||||
<circle class="film-star" cx="108" cy="16" r="0.5"/>
|
||||
<circle class="film-star" cx="134" cy="22" r="0.8"/>
|
||||
<circle class="film-star" cx="150" cy="12" r="0.5"/>
|
||||
</g>
|
||||
<circle class="film-moon" cx="122" cy="26" r="7.5" fill="#f1f5f9" opacity="0.92"/>
|
||||
<path class="film-mtn-scroll" d="M0,60 L30,45 L70,55 L110,40 L160,60 L190,45 L230,55 L270,40 L320,60 L320,92 L0,92 Z" fill="rgba(30,27,75,0.7)"/>
|
||||
<g class="film-mid-scroll">
|
||||
<polygon points="20,76 26,60 32,76" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="55,78 63,52 71,78" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="95,76 101,62 107,76" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="135,77 144,55 153,77" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="180,76 186,60 192,76" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="215,78 223,52 231,78" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="255,76 261,62 267,76" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="295,77 304,55 313,77" fill="rgba(49,46,129,0.55)"/>
|
||||
<path d="M0,76 Q40,73 80,76 T160,76 Q200,73 240,76 T320,76 L320,92 L0,92 Z" fill="rgba(49,46,129,0.55)"/>
|
||||
</g>
|
||||
<g class="film-fore-scroll">
|
||||
<polygon points="40,84 52,48 64,84" fill="#090d16"/>
|
||||
<polygon points="115,84 128,42 141,84" fill="#090d16"/>
|
||||
<polygon points="200,84 212,48 224,84" fill="#090d16"/>
|
||||
<polygon points="275,84 288,42 301,84" fill="#090d16"/>
|
||||
<path d="M0,84 Q80,82 160,84 T320,84 L320,92 L0,92 Z" fill="#090d16"/>
|
||||
</g>
|
||||
<circle class="film-bounce-ball" cx="80" cy="46" r="3.5" fill="#fde047"/>
|
||||
</svg>
|
||||
<div class="demo-play-overlay" aria-hidden="true">
|
||||
<span class="demo-icon-wrap">
|
||||
<svg class="icon-play" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M8 5v14l11-7z"/></svg>
|
||||
<svg class="icon-pause" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M6 5h4v14H6zM14 5h4v14h-4z"/></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="demo-video-controls">
|
||||
<span class="demo-time">12:34</span>
|
||||
<div class="demo-progress" title="Seek: KoalaSync jumps the other tab to the same spot"><div class="demo-progress-fill"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="demo-tab-toast" id="demo-toast-b" data-joined="{{DEMO_JOINED}}" data-selected="{{DEMO_TAB_SELECTED}}" aria-hidden="true"></div>
|
||||
</div>
|
||||
|
||||
<div class="extension-mockup">
|
||||
<div class="mock-header-row">
|
||||
<div class="mock-header-title"><img src="{{ASSET_PATH}}assets/NewLogoIcon.webp" alt="KoalaSync Logo">KoalaSync</div>
|
||||
@@ -310,6 +453,9 @@
|
||||
<svg viewBox="0 0 16 16" fill="currentColor" style="width: 12px; height: 12px; display: block;"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
|
||||
<span class="mockup-version">{{VERSION}}</span>
|
||||
</a>
|
||||
<button class="mock-close-btn" id="demo-popup-close" type="button" aria-label="Close popup" title="Close popup">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" width="13" height="13" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mock-tabs">
|
||||
<button class="mock-tab active" data-target="mock-room" title="Room settings and connection">Room</button>
|
||||
@@ -320,6 +466,13 @@
|
||||
<div class="mock-body">
|
||||
<!-- ROOM TAB -->
|
||||
<div id="mock-room" class="mock-screen active">
|
||||
<!-- Pre-room state, shown only during the scripted walkthrough -->
|
||||
<div id="demo-room-empty" class="demo-room-empty">
|
||||
<img src="{{ASSET_PATH}}assets/NewLogoIcon.webp" alt="" width="42" height="42" loading="eager" decoding="async">
|
||||
<div class="demo-room-empty-text">{{DEMO_NO_ROOM}}</div>
|
||||
<button id="demo-create-room" class="demo-create-room-btn" type="button">{{DEMO_CREATE_ROOM}}</button>
|
||||
</div>
|
||||
<div id="demo-room-joined">
|
||||
<div class="mock-joined-room" style="display: flex; justify-content: space-between; align-items: center; border-left: 4px solid var(--accent); padding: 8px 10px; background: var(--card); border-radius: 8px; margin-bottom: 12px;" title="The room you are currently connected to">
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<span style="font-size: 0.6rem; text-transform: uppercase; color: var(--text-muted); font-weight: 700; line-height: 1;">{{MOCK_01}}</span>
|
||||
@@ -331,7 +484,7 @@
|
||||
<div class="mock-label" title="Share this link with friends so they can join">{{MOCK_03}}</div>
|
||||
<div class="mock-invite-box">
|
||||
<input type="text" class="mock-input" value="https://sync.koalastuff.net/join#join:brave-eagle-80:pass" readonly style="flex: 1;" aria-label="Invite link">
|
||||
<button class="mock-btn" style="width: 40px; padding: 0.45rem 0; flex-shrink: 0; background: #334155;" title="Copy Invite Link"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
|
||||
<button class="mock-btn" id="demo-invite-copy" style="width: 40px; padding: 0.45rem 0; flex-shrink: 0; background: #334155;" title="Copy Invite Link"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mock-card" style="margin-bottom: 12px;">
|
||||
@@ -339,33 +492,35 @@
|
||||
<div class="mock-peer-list">
|
||||
<div class="mock-peer-item">
|
||||
<div class="mock-peer-header">
|
||||
<span class="mock-peer-name">CoolUsername</span>
|
||||
<span class="mock-peer-name">🐱 ChillCat</span>
|
||||
<span class="mock-peer-meta">{{MOCK_05}}</span>
|
||||
</div>
|
||||
<div class="mock-peer-tab"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/><line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/><line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/><line x1="17" y1="7" x2="22" y2="7"/></svg> Stranger Things - S4E1</div>
|
||||
<div class="mock-peer-tab"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/><line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/><line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/><line x1="17" y1="7" x2="22" y2="7"/></svg> <span class="demo-title-text-ep1">Stranger Things - S4E1</span></div>
|
||||
<div class="mock-peer-status"><span style="color:#22c55e;">●</span> 0:55 • {{MOCK_06}}</div>
|
||||
</div>
|
||||
<div class="mock-peer-item">
|
||||
<div class="mock-peer-item demo-peer-b">
|
||||
<div class="mock-peer-header">
|
||||
<span class="mock-peer-name">KoalaPC</span>
|
||||
<span class="mock-peer-name">🐶 HappyDog</span>
|
||||
<span class="mock-peer-meta">{{MOCK_07}}</span>
|
||||
</div>
|
||||
<div class="mock-peer-tab"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/><line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/><line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/><line x1="17" y1="7" x2="22" y2="7"/></svg> Stranger Things - S4E1</div>
|
||||
<div class="mock-peer-tab"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/><line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/><line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/><line x1="17" y1="7" x2="22" y2="7"/></svg> <span class="demo-title-text-ep1">Stranger Things - S4E1</span></div>
|
||||
<div class="mock-peer-status"><span style="color:#22c55e;">●</span> 0:55 • {{MOCK_06}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="mock-btn" style="background:#ef4444; width:100%; border: none; padding: 10px; border-radius: 8px; color: white; font-weight: 700; cursor: pointer; border: 1px solid #ef4444;">{{MOCK_08}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SYNC TAB -->
|
||||
<div id="mock-sync" class="mock-screen">
|
||||
<div class="mock-form-group" style="margin-bottom: 12px;">
|
||||
<label class="mock-section-label" title="Choose the browser tab containing the video to sync">
|
||||
<label class="mock-section-label" for="demo-video-select" title="Choose the browser tab containing the video to sync">
|
||||
{{MOCK_09}}
|
||||
</label>
|
||||
<select class="mock-input" style="width: 100%; box-sizing: border-box; background: var(--card); border: 1px solid #334155; color: white; padding: 8px; border-radius: 8px; font-family: inherit; font-size: 0.75rem; outline: none; cursor: default;">
|
||||
<option><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/><line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/><line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/><line x1="17" y1="7" x2="22" y2="7"/></svg> Stranger Things - S4E1</option>
|
||||
<select class="mock-input" id="demo-video-select" style="width: 100%; box-sizing: border-box; background: var(--card); border: 1px solid #334155; color: white; padding: 8px; border-radius: 8px; font-family: inherit; font-size: 0.75rem; outline: none; cursor: default;">
|
||||
<option value="" class="demo-select-placeholder" selected hidden>{{DEMO_SELECT_PLACEHOLDER}}</option>
|
||||
<option><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/><line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/><line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/><line x1="17" y1="7" x2="22" y2="7"/></svg> <span class="demo-title-text-ep1">Stranger Things - S4E1</span></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -379,12 +534,12 @@
|
||||
</button>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; margin-bottom: 12px;">
|
||||
<button class="mock-btn mock-btn-play" style="flex: 1; background: #22c55e; color: white; padding: 8px; border-radius: 8px; font-weight: 700; border: none; cursor: pointer;" title="Send a Play command to everyone">{{MOCK_11}}</button>
|
||||
<button class="mock-btn mock-btn-pause" style="flex: 1; background: #ef4444; color: white; padding: 8px; border-radius: 8px; font-weight: 700; border: none; cursor: pointer;" title="Send a Pause command to everyone">{{MOCK_12}}</button>
|
||||
<button class="mock-btn mock-btn-play" id="demo-play-btn" style="flex: 1; background: #22c55e; color: white; padding: 8px; border-radius: 8px; font-weight: 700; border: none; cursor: pointer;" title="Send a Play command to everyone">{{MOCK_11}}</button>
|
||||
<button class="mock-btn mock-btn-pause" id="demo-pause-btn" style="flex: 1; background: #ef4444; color: white; padding: 8px; border-radius: 8px; font-weight: 700; border: none; cursor: pointer;" title="Send a Pause command to everyone">{{MOCK_12}}</button>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 8px; margin-bottom: 15px; align-items: stretch;">
|
||||
<button class="mock-btn" style="background: linear-gradient(135deg, #6366f1, #a855f7); flex: 1; color: white; border: none; font-weight: 700; border-radius: 8px; padding: 8px; cursor: pointer;" title="Force all users to sync up">
|
||||
<button class="mock-btn" id="demo-force-sync" style="background: linear-gradient(135deg, #6366f1, #a855f7); flex: 1; color: white; border: none; font-weight: 700; border-radius: 8px; padding: 8px; cursor: pointer;" title="Force all users to sync up">
|
||||
{{MOCK_13}}
|
||||
</button>
|
||||
<div class="mock-input" style="flex: 1; background: var(--card); border: 1px solid #334155; color: white; padding: 6px; border-radius: 8px; font-size: 0.75rem; display: flex; align-items: center; justify-content: space-between;" title="Choose sync target">
|
||||
@@ -401,33 +556,16 @@
|
||||
<div class="mock-card" style="margin-bottom: 15px; display: flex; flex-direction: column; gap: 6px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: baseline;">
|
||||
<span style="font-weight: 700; color: #ef4444; font-size: 11px;">PAUSE</span>
|
||||
<span style="font-size: 9px; color: var(--text-muted);">CoolUsername @ 12:44:32</span>
|
||||
<span style="font-size: 9px; color: var(--text-muted);">🐱 ChillCat @ 12:44:32</span>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(36px, 1fr)); gap: 5px; margin-top: 4px;">
|
||||
<div style="display: flex; flex-direction: column; align-items: center;">
|
||||
<div style="width: 18px; height: 18px; border-radius: 50%; background: var(--success); color: white; display: flex; align-items: center; justify-content: center; font-size: 9px; font-weight: bold; margin-bottom: 1px;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" width="10" height="10" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg></div>
|
||||
<span style="font-size: 7px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 36px;">🐨 KoalaPC</span>
|
||||
<span style="font-size: 7px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 36px;">🐶 HappyDog</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Episode Auto-Sync Lobby Status Mockup -->
|
||||
<div class="mock-card" style="margin-bottom: 15px; border-left: 4px solid #fbbf24; background: var(--card); padding: 8px 10px; border-radius: 8px;">
|
||||
<div style="display:flex; align-items:center; gap: 6px; margin-bottom: 4px;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14" aria-hidden="true" style="display:block; flex-shrink:0;"><path d="M5 22h14"/><path d="M5 2h14"/><path d="M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22"/><path d="M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2"/></svg>
|
||||
<span style="font-weight: 700; color: #fbbf24; font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em;">Episode Lobby</span>
|
||||
</div>
|
||||
<div style="font-size: 0.7rem; color: white; margin-bottom: 4px; font-weight: 600;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/><line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/><line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/><line x1="17" y1="7" x2="22" y2="7"/></svg> Stranger Things - S4E2
|
||||
</div>
|
||||
<div style="font-size: 0.65rem; color: var(--text-muted); margin-bottom: 6px;">
|
||||
{{MOCK_16}}
|
||||
</div>
|
||||
<button class="mock-btn" style="background: #334155; border: none; padding: 4px 8px; border-radius: 6px; color: white; font-weight: 600; font-size: 0.65rem; cursor: pointer; width: auto; display: block;" title="Cancel lobby and play anyway">
|
||||
{{MOCK_17}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<label class="mock-section-label" title="Other users currently connected to this room">
|
||||
{{MOCK_04}}
|
||||
</label>
|
||||
@@ -435,18 +573,18 @@
|
||||
<div class="mock-peer-list">
|
||||
<div class="mock-peer-item">
|
||||
<div class="mock-peer-header">
|
||||
<span class="mock-peer-name">CoolUsername</span>
|
||||
<span class="mock-peer-name">🐱 ChillCat</span>
|
||||
<span class="mock-peer-meta">{{MOCK_05}}</span>
|
||||
</div>
|
||||
<div class="mock-peer-tab"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/><line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/><line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/><line x1="17" y1="7" x2="22" y2="7"/></svg> Stranger Things - S4E1</div>
|
||||
<div class="mock-peer-tab"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/><line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/><line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/><line x1="17" y1="7" x2="22" y2="7"/></svg> <span class="demo-title-text-ep1">Stranger Things - S4E1</span></div>
|
||||
<div class="mock-peer-status"><span style="color:#22c55e;">●</span> 0:55 • {{MOCK_06}}</div>
|
||||
</div>
|
||||
<div class="mock-peer-item">
|
||||
<div class="mock-peer-item demo-peer-b">
|
||||
<div class="mock-peer-header">
|
||||
<span class="mock-peer-name">KoalaPC</span>
|
||||
<span class="mock-peer-name">🐶 HappyDog</span>
|
||||
<span class="mock-peer-meta">{{MOCK_07}}</span>
|
||||
</div>
|
||||
<div class="mock-peer-tab"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/><line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/><line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/><line x1="17" y1="7" x2="22" y2="7"/></svg> Stranger Things - S4E1</div>
|
||||
<div class="mock-peer-tab"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/><line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/><line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/><line x1="17" y1="7" x2="22" y2="7"/></svg> <span class="demo-title-text-ep1">Stranger Things - S4E1</span></div>
|
||||
<div class="mock-peer-status"><span style="color:#22c55e;">●</span> 0:55 • {{MOCK_06}}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -459,7 +597,7 @@
|
||||
<label style="margin: 0; font-size: 11px; text-transform: uppercase; color: var(--text-muted); font-weight: 700;" title="Username helps others identify you.">
|
||||
{{MOCK_18}}
|
||||
</label>
|
||||
<input type="text" class="mock-input" value="CoolUsername" readonly style="width: 110px; padding: 4px; text-align: right; background: transparent; border: none; color: var(--accent); font-weight: 700;">
|
||||
<input type="text" class="mock-input" value="🐱 ChillCat" readonly style="width: 110px; padding: 4px; text-align: right; background: transparent; border: none; color: var(--accent); font-weight: 700;">
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; background: var(--card); padding: 8px 10px; border-radius: 8px; margin-bottom: 8px; border: 1px solid #334155;">
|
||||
@@ -545,11 +683,11 @@
|
||||
</label>
|
||||
<div class="mock-card" style="font-size: 0.65rem; color: var(--text-muted); line-height: 1.4; margin-bottom: 12px; max-height: 100px; overflow-y: auto; display: flex; flex-direction: column; gap: 4px;">
|
||||
<div style="display: flex; justify-content: space-between; border-bottom: 1px solid #334155; padding-bottom: 4px;">
|
||||
<span>CoolUsername: PAUSE</span>
|
||||
<span>🐱 ChillCat: PAUSE</span>
|
||||
<span style="opacity: 0.5;">0:55</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding-top: 2px;">
|
||||
<span>KoalaPC: PLAY</span>
|
||||
<span>🐶 HappyDog: PLAY</span>
|
||||
<span style="opacity: 0.5;">0:00</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -576,10 +714,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-invite-fly" id="demo-invite-fly" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" width="11" height="11" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
|
||||
sync.koalastuff.net/join…
|
||||
</div>
|
||||
<div class="demo-cursor" id="demo-cursor" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 2l13 11.5-6 .7 3.4 6.3-2.6 1.4-3.4-6.4L5 19.5z" fill="#f8fafc" stroke="#0f172a" stroke-width="1.4" stroke-linejoin="round"/></svg>
|
||||
</div>
|
||||
<p class="demo-hint" id="demo-hint">{{DEMO_HINT}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="mobile-demo-modal" id="mobile-demo-modal" hidden>
|
||||
<div class="mobile-demo-backdrop" data-mobile-demo-close></div>
|
||||
<div class="mobile-demo-panel" role="dialog" aria-modal="true" aria-labelledby="mobile-demo-title">
|
||||
<div class="mobile-demo-header">
|
||||
<h2 id="mobile-demo-title">{{MOBILE_DEMO_TITLE}}</h2>
|
||||
<button class="mobile-demo-close" id="mobile-demo-close" type="button" aria-label="{{MOBILE_DEMO_CLOSE}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" width="20" height="20" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mobile-demo-stage" id="mobile-demo-stage"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="compat-section">
|
||||
<div class="container">
|
||||
<img src="{{ASSET_PATH}}assets/PlatformJuggler_New-1x.webp" srcset="{{ASSET_PATH}}assets/PlatformJuggler_New-1x.webp 368w, {{ASSET_PATH}}assets/PlatformJuggler_New.webp 643w" sizes="(max-width: 768px) calc(100vw - 2rem), 368px" alt="A cute koala juggling multiple browser tabs showing streaming platforms" class="compat-mascot" width="300" height="233" loading="lazy" fetchpriority="low" data-reveal>
|
||||
@@ -603,7 +764,11 @@
|
||||
<img src="{{ASSET_PATH}}assets/primevideo.svg" alt="Prime Video" width="24" height="24">
|
||||
<span>Prime Video</span>
|
||||
</div>
|
||||
<div class="compat-logo">
|
||||
<div class="compat-logo compat-logo-disneyplus">
|
||||
<img class="compat-logo-wide compat-logo-disneyplus-mark" src="{{ASSET_PATH}}assets/disneyplus.svg" alt="Disney+" width="76" height="36">
|
||||
<span>Disney+</span>
|
||||
</div>
|
||||
<div class="compat-logo compat-logo-jellyfin">
|
||||
<img src="{{ASSET_PATH}}assets/jellyfin.svg" alt="Jellyfin" width="24" height="24">
|
||||
<span>Jellyfin</span>
|
||||
</div>
|
||||
@@ -625,12 +790,12 @@
|
||||
<section id="use-cases" class="use-cases-section">
|
||||
<div class="container">
|
||||
<h2 class="section-title" data-reveal style="text-align: center; font-size: 2.2rem; margin-bottom: 1rem;">
|
||||
<span>{{USE_CASES_TITLE}}</span>
|
||||
<span>{{WHY_TITLE}}</span>
|
||||
</h2>
|
||||
<p class="section-subtitle" data-reveal style="text-align: center; color: var(--text-muted); margin-bottom: 3.5rem; max-width: 600px; margin-left: auto; margin-right: auto;">
|
||||
<span>{{USE_CASES_SUBTITLE}}</span>
|
||||
<span>{{WHY_SUBTITLE}}</span>
|
||||
</p>
|
||||
<div class="use-cases-grid">
|
||||
<div class="use-cases-grid use-cases-feature-grid">
|
||||
<div class="use-case-card" data-reveal>
|
||||
<img src="{{ASSET_PATH}}assets/PopcornFriends-1x.webp" srcset="{{ASSET_PATH}}assets/PopcornFriends-1x.webp 272w, {{ASSET_PATH}}assets/PopcornFriends.webp 543w" sizes="(max-width: 768px) calc(100vw - 2rem), 272px" alt="{{USE_CASE_1_ALT}}" class="use-case-image" width="272" height="150" loading="lazy" fetchpriority="low">
|
||||
<h3>
|
||||
@@ -652,71 +817,8 @@
|
||||
</h3>
|
||||
<p>{{USE_CASE_3_DESC}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="features">
|
||||
<div class="container">
|
||||
<img src="{{ASSET_PATH}}assets/KoalaQuestions-1x.webp" srcset="{{ASSET_PATH}}assets/KoalaQuestions-1x.webp 250w, {{ASSET_PATH}}assets/KoalaQuestions.webp 355w" sizes="(max-width: 768px) calc(100vw - 2rem), 250px" alt="A cute koala with a question mark above its head wondering why to choose KoalaSync" class="features-questions-mascot" width="250" height="211" loading="lazy" fetchpriority="low" data-reveal>
|
||||
<h2 style="font-size: 2.5rem; text-align: center; margin-bottom: 1rem;">
|
||||
<span>{{WHY_TITLE}}</span>
|
||||
</h2>
|
||||
<p style="text-align: center; color: var(--text-muted); margin-bottom: 4rem;">
|
||||
<span>{{WHY_SUBTITLE}}</span>
|
||||
</p>
|
||||
|
||||
<div class="features-grid">
|
||||
<!-- Card 1: Full Control / Instant Sync (Bento Large) -->
|
||||
<div class="feature-card bento-large" data-reveal>
|
||||
<h3>
|
||||
<span class="feature-icon-svg">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="bento-icon"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||
</span>
|
||||
<span>{{FEATURE_1_TITLE}}</span>
|
||||
</h3>
|
||||
<p>{{FEATURE_1_DESC}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card 2: Endless Binge-Watching (Bento Standard) -->
|
||||
<div class="feature-card" data-reveal>
|
||||
<h3>
|
||||
<span class="feature-icon-svg">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="bento-icon"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/><line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/><line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/><line x1="17" y1="7" x2="22" y2="7"/></svg>
|
||||
</span>
|
||||
<span>{{FEATURE_2_TITLE}}</span>
|
||||
</h3>
|
||||
<p>{{FEATURE_2_DESC}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card 3: Zero Accounts / Pure Privacy (Bento Standard) -->
|
||||
<div class="feature-card" data-reveal>
|
||||
<h3>
|
||||
<span class="feature-icon-svg">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="bento-icon"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||||
</span>
|
||||
<span>{{FEATURE_3_TITLE}}</span>
|
||||
</h3>
|
||||
<p>{{FEATURE_3_DESC}}</p>
|
||||
<div class="bento-visual-architecture" style="margin-top: 15px; padding-top: 10px; border-top: 1px solid rgba(255,255,255,0.05); font-size: 0.7rem; font-family: monospace; display: flex; align-items: center; justify-content: space-between; color: var(--accent);">
|
||||
<span>STATE: VOLATILE</span>
|
||||
<span style="background: rgba(99, 102, 241, 0.15); padding: 2px 6px; border-radius: 4px; font-weight: 700; font-size: 0.6rem;">RAM-ONLY</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 4: Universal HTML5 Support (Bento Large) -->
|
||||
<div class="feature-card bento-large" data-reveal>
|
||||
<h3>
|
||||
<span class="feature-icon-svg">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="bento-icon"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
|
||||
</span>
|
||||
<span>{{FEATURE_4_TITLE}}</span>
|
||||
</h3>
|
||||
<p>{{FEATURE_4_DESC}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card 5: Instant Invites / 1-Click Join (Bento Large) -->
|
||||
<div class="feature-card bento-large" data-reveal>
|
||||
<!-- Tile 1: Instant Invites / 1-Click Join -->
|
||||
<div id="features" class="feature-card feature-card-compact" data-reveal>
|
||||
<h3>
|
||||
<span class="feature-icon-svg">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="bento-icon"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
|
||||
@@ -726,30 +828,8 @@
|
||||
<p>{{FEATURE_6_DESC}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card 6: Self-Hostable & Docker-Ready (Bento Standard) -->
|
||||
<div class="feature-card" data-reveal>
|
||||
<h3>
|
||||
<span class="feature-icon-svg">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="bento-icon"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
|
||||
</span>
|
||||
<span>{{FEATURE_5_TITLE}}</span>
|
||||
</h3>
|
||||
<p>{{FEATURE_5_DESC}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card 7: Open Source / MIT License (Bento Standard) -->
|
||||
<div class="feature-card" data-reveal>
|
||||
<h3>
|
||||
<span class="feature-icon-svg">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="bento-icon"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
||||
</span>
|
||||
<span>{{FEATURE_8_TITLE}}</span>
|
||||
</h3>
|
||||
<p>{{FEATURE_8_DESC}}</p>
|
||||
</div>
|
||||
|
||||
<!-- Card 8: Volume Leveling / Audio Compressor (Bento Large) -->
|
||||
<div class="feature-card bento-large" data-reveal>
|
||||
<!-- Tile 2: Volume Leveling / Audio Compressor -->
|
||||
<div class="feature-card feature-card-compact" data-reveal>
|
||||
<h3>
|
||||
<span class="feature-icon-svg">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="bento-icon"><path d="M11 5 6 9H2v6h4l5 4V5z"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>
|
||||
@@ -758,8 +838,18 @@
|
||||
</h3>
|
||||
<p>{{FEATURE_7_DESC}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tile 3: Universal HTML5 Support -->
|
||||
<div class="feature-card feature-card-compact" data-reveal>
|
||||
<h3>
|
||||
<span class="feature-icon-svg">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="bento-icon"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
|
||||
</span>
|
||||
<span>{{FEATURE_4_TITLE}}</span>
|
||||
</h3>
|
||||
<p>{{FEATURE_4_DESC}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -875,17 +965,23 @@
|
||||
<a href="https://www.teleparty.com/privacy" target="_blank" rel="noopener noreferrer">teleparty.com/privacy</a>
|
||||
</p>
|
||||
<p id="footnote-3">
|
||||
<sup>[3]</sup>
|
||||
<sup>[3]</sup>
|
||||
<span>{{COMP_FOOTNOTE_4}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comparison-guides-link" data-reveal>
|
||||
<a href="{{ASSET_PATH}}{{LANG_PREFIX}}alternatives">
|
||||
<span>{{COMP_MORE_GUIDES}}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="15" height="15" aria-hidden="true"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="how-it-works" style="background: rgba(255,255,255,0.02)">
|
||||
<section id="how-it-works" style="background: var(--section-tint)">
|
||||
<div class="container">
|
||||
<h2 style="font-size: 2.5rem; text-align: center; margin-bottom: 4rem;">
|
||||
<h2 style="font-size: 2.2rem; text-align: center; margin-bottom: 2.25rem;">
|
||||
<span>{{STEPS_TITLE}}</span>
|
||||
</h2>
|
||||
|
||||
@@ -897,73 +993,6 @@
|
||||
<p>{{STEP_1_DESC}}</p>
|
||||
</div>
|
||||
<!-- Custom Step 1 Illustration -->
|
||||
<div class="step-illustration-1" data-reveal>
|
||||
<div class="illus-browser-container">
|
||||
<div class="illus-browser-header">
|
||||
<div class="illus-browser-dots">
|
||||
<span class="illus-browser-dot"></span>
|
||||
<span class="illus-browser-dot"></span>
|
||||
<span class="illus-browser-dot"></span>
|
||||
</div>
|
||||
<div class="illus-browser-address-bar">
|
||||
<span class="url-protocol">https://</span>sync.koalastuff.net
|
||||
</div>
|
||||
<div class="illus-browser-toolbar">
|
||||
<div class="illus-extension-btn active">
|
||||
<img src="{{ASSET_PATH}}assets/NewLogoIcon.webp" alt="KoalaSync Extension" width="14" height="14">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="illus-browser-content">
|
||||
<div class="illus-store-card">
|
||||
<div class="illus-store-card-header">
|
||||
<img src="{{ASSET_PATH}}assets/NewLogoIcon.webp" alt="KoalaSync Logo" class="illus-large-logo" width="30" height="30">
|
||||
<div class="illus-store-info">
|
||||
<div class="illus-store-title">KoalaSync</div>
|
||||
<div class="illus-store-desc">
|
||||
<span>{{STEP_1_ILLUS_DESC}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="illus-store-buttons">
|
||||
<div class="illus-store-btn chrome">
|
||||
<img src="{{ASSET_PATH}}assets/chrome.svg" alt="Chrome Logo" width="12" height="12">
|
||||
<span>{{STEP_1_ILLUS_DL_CHROME}}</span>
|
||||
</div>
|
||||
<div class="illus-store-btn firefox">
|
||||
<img src="{{ASSET_PATH}}assets/firefox.svg" alt="Firefox Logo" width="12" height="12">
|
||||
<span>{{STEP_1_ILLUS_DL_FIREFOX}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Floating Extension Success Popup -->
|
||||
<div class="illus-extension-popup">
|
||||
<div class="popup-title">
|
||||
<img src="{{ASSET_PATH}}assets/NewLogoIcon.webp" alt="KoalaSync Logo" width="12" height="12">
|
||||
<span>KoalaSync</span>
|
||||
</div>
|
||||
<div class="popup-status">
|
||||
<div class="status-badge-success">
|
||||
<span class="status-check"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg></span>
|
||||
<span>{{STEP_1_ILLUS_ACTIVE}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popup-quick-start">
|
||||
<span>{{STEP_1_ILLUS_READY}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-text" data-reveal>
|
||||
<div class="step-num" aria-hidden="true">02</div>
|
||||
<h3><span>{{STEP_2_TITLE}}</span></h3>
|
||||
<p>{{STEP_2_DESC}}</p>
|
||||
</div>
|
||||
<!-- Custom Step 2 Illustration -->
|
||||
<div class="step-illustration-2" data-reveal>
|
||||
<div class="illus-popup-card">
|
||||
<div class="illus-popup-header">
|
||||
@@ -1003,6 +1032,51 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-text" data-reveal>
|
||||
<div class="step-num" aria-hidden="true">02</div>
|
||||
<h3><span>{{STEP_2_TITLE}}</span></h3>
|
||||
<p>{{STEP_2_DESC}}</p>
|
||||
</div>
|
||||
<!-- Custom Step 2 Illustration -->
|
||||
<div class="step-illustration-1" data-reveal>
|
||||
<div class="illus-popup-card">
|
||||
<div class="illus-popup-header">
|
||||
<div class="illus-popup-brand">
|
||||
<img src="{{ASSET_PATH}}assets/NewLogoIcon.webp" alt="KoalaSync Logo" width="14" height="14">
|
||||
<span>KoalaSync</span>
|
||||
</div>
|
||||
<div class="illus-popup-version mockup-version">{{VERSION}}</div>
|
||||
</div>
|
||||
<div class="illus-popup-tabs">
|
||||
<div class="illus-popup-tab">
|
||||
<span>{{STEP_2_ILLUS_ROOM}}</span>
|
||||
</div>
|
||||
<div class="illus-popup-tab active">
|
||||
<span>{{STEP_2_ILLUS_SYNC}}</span>
|
||||
</div>
|
||||
<div class="illus-popup-tab">
|
||||
<span>{{STEP_2_ILLUS_SETTINGS}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="illus-popup-body">
|
||||
<div class="popup-form-group">
|
||||
<label class="popup-form-label" for="popup-select-mock-el">{{LABEL_SELECT_VIDEO}}</label>
|
||||
<div class="popup-select-wrapper">
|
||||
<select class="popup-select-mock" id="popup-select-mock-el" disabled>
|
||||
<option class="demo-title-text-ep1">Stranger Things - S4E1</option>
|
||||
</select>
|
||||
<div class="popup-cursor-pointer">👆</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popup-mock-controls">
|
||||
<div class="popup-mock-btn play">▶ Play</div>
|
||||
<div class="popup-mock-btn pause">⏸ Pause</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-text" data-reveal>
|
||||
<div class="step-num" aria-hidden="true">03</div>
|
||||
@@ -1012,15 +1086,38 @@
|
||||
<!-- Custom Step 3 Illustration -->
|
||||
<div class="step-illustration-3" data-reveal>
|
||||
<div class="illus-sync-theater">
|
||||
<!-- Player A (KoalaPC) -->
|
||||
<!-- Player A (🐱 ChillCat) -->
|
||||
<div class="illus-player-card player-a">
|
||||
<div class="player-header">
|
||||
<span class="player-user">🐨 KoalaPC</span>
|
||||
<span class="player-user">🐱 ChillCat</span>
|
||||
<span class="player-badge active">PLAYING</span>
|
||||
</div>
|
||||
<div class="player-video-canvas">
|
||||
<div class="player-play-pulse">▶</div>
|
||||
<div class="player-video-overlay"></div>
|
||||
<svg class="demo-film" viewBox="0 0 160 90" preserveAspectRatio="xMidYMid slice" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
|
||||
<g class="film-stars" fill="#e2e8f0">
|
||||
<circle class="film-star" cx="18" cy="14" r="0.8"/>
|
||||
<circle class="film-star" cx="42" cy="24" r="0.6"/>
|
||||
<circle class="film-star" cx="64" cy="10" r="0.9"/>
|
||||
<circle class="film-star" cx="90" cy="28" r="0.6"/>
|
||||
<circle class="film-star" cx="134" cy="22" r="0.8"/>
|
||||
</g>
|
||||
<circle class="film-moon" cx="122" cy="26" r="7.5" fill="#f1f5f9" opacity="0.92"/>
|
||||
<path class="film-mtn-scroll" d="M0,60 L30,45 L70,55 L110,40 L160,60 L190,45 L230,55 L270,40 L320,60 L320,92 L0,92 Z" fill="rgba(30,27,75,0.7)"/>
|
||||
<g class="film-mid-scroll">
|
||||
<polygon points="20,76 26,60 32,76" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="55,78 63,52 71,78" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="95,76 101,62 107,76" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="135,77 144,55 153,77" fill="rgba(49,46,129,0.55)"/>
|
||||
<path d="M0,76 Q40,73 80,76 T160,76 Q200,73 240,76 T320,76 L320,92 L0,92 Z" fill="rgba(49,46,129,0.55)"/>
|
||||
</g>
|
||||
<g class="film-fore-scroll">
|
||||
<polygon points="40,84 52,48 64,84" fill="#090d16"/>
|
||||
<polygon points="115,84 128,42 141,84" fill="#090d16"/>
|
||||
<path d="M0,84 Q40,82 80,84 T160,84 L160,92 L0,92 Z" fill="#090d16"/>
|
||||
</g>
|
||||
<circle class="film-bounce-ball" cx="80" cy="80" r="4.5" fill="#fde047"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="player-controls">
|
||||
<span class="control-btn">⏸</span>
|
||||
@@ -1042,15 +1139,38 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Player B (LaptopKoala) -->
|
||||
<!-- Player B (🐶 HappyDog) -->
|
||||
<div class="illus-player-card player-b">
|
||||
<div class="player-header">
|
||||
<span class="player-user">💻 LaptopKoala</span>
|
||||
<span class="player-user">🐶 HappyDog</span>
|
||||
<span class="player-badge active">PLAYING</span>
|
||||
</div>
|
||||
<div class="player-video-canvas">
|
||||
<div class="player-play-pulse">▶</div>
|
||||
<div class="player-video-overlay"></div>
|
||||
<svg class="demo-film" viewBox="0 0 160 90" preserveAspectRatio="xMidYMid slice" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
|
||||
<g class="film-stars" fill="#e2e8f0">
|
||||
<circle class="film-star" cx="18" cy="14" r="0.8"/>
|
||||
<circle class="film-star" cx="42" cy="24" r="0.6"/>
|
||||
<circle class="film-star" cx="64" cy="10" r="0.9"/>
|
||||
<circle class="film-star" cx="90" cy="28" r="0.6"/>
|
||||
<circle class="film-star" cx="134" cy="22" r="0.8"/>
|
||||
</g>
|
||||
<circle class="film-moon" cx="122" cy="26" r="7.5" fill="#f1f5f9" opacity="0.92"/>
|
||||
<path class="film-mtn-scroll" d="M0,60 L30,45 L70,55 L110,40 L160,60 L190,45 L230,55 L270,40 L320,60 L320,92 L0,92 Z" fill="rgba(30,27,75,0.7)"/>
|
||||
<g class="film-mid-scroll">
|
||||
<polygon points="20,76 26,60 32,76" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="55,78 63,52 71,78" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="95,76 101,62 107,76" fill="rgba(49,46,129,0.55)"/>
|
||||
<polygon points="135,77 144,55 153,77" fill="rgba(49,46,129,0.55)"/>
|
||||
<path d="M0,76 Q40,73 80,76 T160,76 Q200,73 240,76 T320,76 L320,92 L0,92 Z" fill="rgba(49,46,129,0.55)"/>
|
||||
</g>
|
||||
<g class="film-fore-scroll">
|
||||
<polygon points="40,84 52,48 64,84" fill="#090d16"/>
|
||||
<polygon points="115,84 128,42 141,84" fill="#090d16"/>
|
||||
<path d="M0,84 Q40,82 80,84 T160,84 L160,92 L0,92 Z" fill="#090d16"/>
|
||||
</g>
|
||||
<circle class="film-bounce-ball" cx="80" cy="80" r="4.5" fill="#fde047"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="player-controls">
|
||||
<span class="control-btn">⏸</span>
|
||||
@@ -1069,7 +1189,7 @@
|
||||
</section>
|
||||
|
||||
<!-- Self Hosters Paradise Section -->
|
||||
<section id="self-hosting" style="background: rgba(15, 23, 42, 0.4); border-top: 1px solid var(--glass-border); border-bottom: 1px solid var(--glass-border);">
|
||||
<section id="self-hosting" style="background: var(--section-tint-strong); border-top: 1px solid var(--glass-border); border-bottom: 1px solid var(--glass-border);">
|
||||
<div class="container">
|
||||
<h2 style="font-size: 2.5rem; text-align: center; margin-bottom: 1rem;">
|
||||
<span>{{SELF_TITLE}}</span>
|
||||
@@ -1083,7 +1203,13 @@
|
||||
<img src="{{ASSET_PATH}}assets/KoalaDeploy-1x.webp" srcset="{{ASSET_PATH}}assets/KoalaDeploy-1x.webp 300w, {{ASSET_PATH}}assets/KoalaDeploy.webp 600w" sizes="(max-width: 768px) calc(100vw - 2rem), 300px" alt="{{SELF_MASCOT_ALT}}" class="selfhost-mascot" width="300" height="201" loading="lazy" fetchpriority="low">
|
||||
</div>
|
||||
|
||||
<div class="terminal-container" data-reveal>
|
||||
<details class="section-collapse" id="selfhost-collapse">
|
||||
<summary class="collapse-summary" data-reveal>
|
||||
<span class="collapse-label-show"><span>{{SELF_SHOW_SETUP}}</span></span>
|
||||
<span class="collapse-label-hide"><span>{{SELF_HIDE_SETUP}}</span></span>
|
||||
<svg class="collapse-chevron" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="18" height="18" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</summary>
|
||||
<div class="terminal-container collapse-content">
|
||||
<div class="terminal-header">
|
||||
<div class="terminal-dots">
|
||||
<span class="terminal-dot red"></span>
|
||||
@@ -1185,52 +1311,61 @@
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="faq" style="border-top: 1px solid var(--glass-border);">
|
||||
<div class="container">
|
||||
<img src="{{ASSET_PATH}}assets/KoalaQuestions-1x.webp" srcset="{{ASSET_PATH}}assets/KoalaQuestions-1x.webp 250w, {{ASSET_PATH}}assets/KoalaQuestions.webp 355w" sizes="(max-width: 768px) calc(100vw - 2rem), 250px" alt="A cute koala holding a question mark wondering about KoalaSync" class="features-questions-mascot" width="250" height="211" loading="lazy" fetchpriority="low" data-reveal>
|
||||
<h2 data-reveal style="font-size: 2.2rem; text-align: center; margin-bottom: 1rem;">
|
||||
<span>{{FAQ_TITLE}}</span>
|
||||
</h2>
|
||||
<p data-reveal style="text-align: center; color: var(--text-muted); margin-bottom: 3rem; max-width: 600px; margin-left: auto; margin-right: auto;">
|
||||
<p data-reveal style="text-align: center; color: var(--text-muted); margin-bottom: 2rem; max-width: 600px; margin-left: auto; margin-right: auto;">
|
||||
<span>{{FAQ_SUBTITLE}}</span>
|
||||
</p>
|
||||
<div class="faq-grid">
|
||||
<details class="faq-item" data-reveal>
|
||||
<summary><span>{{FAQ_Q1}}</span></summary>
|
||||
<p><span>{{FAQ_A1}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item" data-reveal>
|
||||
<summary><span>{{FAQ_Q_VIDEO_ACCESS}}</span></summary>
|
||||
<p><span>{{FAQ_A_VIDEO_ACCESS}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item" data-reveal>
|
||||
<summary><span>{{FAQ_Q2}}</span></summary>
|
||||
<p><span>{{FAQ_A2}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item" data-reveal>
|
||||
<summary><span>{{FAQ_Q3}}</span></summary>
|
||||
<p><span>{{FAQ_A3}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item" data-reveal>
|
||||
<summary><span>{{FAQ_Q4}}</span></summary>
|
||||
<p><span>{{FAQ_A4}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item" data-reveal>
|
||||
<summary><span>{{FAQ_Q5}}</span></summary>
|
||||
<p><span>{{FAQ_A5}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item" data-reveal>
|
||||
<summary><span>{{FAQ_Q6}}</span></summary>
|
||||
<p><span>{{FAQ_A6}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item" data-reveal>
|
||||
<summary><span>{{FAQ_Q7}}</span></summary>
|
||||
<p><span>{{FAQ_A7}}</span></p>
|
||||
</details>
|
||||
</div>
|
||||
<details class="section-collapse faq-section-collapse" id="faq-collapse">
|
||||
<summary class="collapse-summary" data-reveal>
|
||||
<span class="collapse-label-show"><span>{{FAQ_SHOW_ALL}}</span></span>
|
||||
<span class="collapse-label-hide"><span>{{FAQ_HIDE_ALL}}</span></span>
|
||||
<svg class="collapse-chevron" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="18" height="18" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</summary>
|
||||
<div class="faq-grid collapse-content">
|
||||
<details class="faq-item">
|
||||
<summary><span>{{FAQ_Q1}}</span></summary>
|
||||
<p><span>{{FAQ_A1}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary><span>{{FAQ_Q_VIDEO_ACCESS}}</span></summary>
|
||||
<p><span>{{FAQ_A_VIDEO_ACCESS}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary><span>{{FAQ_Q2}}</span></summary>
|
||||
<p><span>{{FAQ_A2}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary><span>{{FAQ_Q3}}</span></summary>
|
||||
<p><span>{{FAQ_A3}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary><span>{{FAQ_Q4}}</span></summary>
|
||||
<p><span>{{FAQ_A4}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary><span>{{FAQ_Q5}}</span></summary>
|
||||
<p><span>{{FAQ_A5}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary><span>{{FAQ_Q6}}</span></summary>
|
||||
<p><span>{{FAQ_A6}}</span></p>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary><span>{{FAQ_Q7}}</span></summary>
|
||||
<p><span>{{FAQ_A7}}</span></p>
|
||||
</details>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1275,7 +1410,7 @@
|
||||
<a href="{{ASSET_PATH}}{{FOOTER_LEGAL_LINK}}" style="color: var(--text-muted); text-decoration: none;"><span>{{FOOTER_LEGAL}}</span></a>
|
||||
<a href="{{ASSET_PATH}}{{FOOTER_PRIVACY_LINK}}" style="color: var(--text-muted); text-decoration: none;"><span>{{FOOTER_PRIVACY}}</span></a>
|
||||
<a href="{{ASSET_PATH}}{{LANG_PREFIX}}alternatives"><span><span lang="en">Guides & comparisons</span><span lang="de">Vergleiche & Anleitungen</span></span></a>
|
||||
<a href="https://support.koalastuff.net" target="_blank" rel="noopener" title="{{FOOTER_SUPPORT}}" style="color: #fff; text-decoration: none; display: inline-flex; align-items: center; gap: 6px; background: #ff5e5b; border: 1px solid rgba(255,255,255,0.18); border-radius: 999px; padding: 3px 9px; font-size: 0.72rem; font-weight: 700; line-height: 1;">
|
||||
<a href="https://support.koalastuff.net" target="_blank" rel="noopener" title="{{FOOTER_SUPPORT}}" style="color: #fff; text-decoration: none; display: inline-flex; align-items: center; gap: 6px; background: #c9302c; border: 1px solid rgba(255,255,255,0.18); border-radius: 999px; padding: 3px 9px; font-size: 0.72rem; font-weight: 700; line-height: 1;">
|
||||
<span aria-hidden="true">♥</span>
|
||||
<span>{{FOOTER_SUPPORT}}</span>
|
||||
</a>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "2.5.0",
|
||||
"date": "2026-06-29T10:58:06Z"
|
||||
"version": "2.5.3",
|
||||
"date": "2026-07-02T14:59:12Z"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user