mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 36f494fa55 | |||
| b5b3885d19 | |||
| 373f2d127a | |||
| 3ebe80aab2 | |||
| e7b59d61ac | |||
| 71f7ac425a |
@@ -6,15 +6,15 @@
|
||||
|
||||
<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/github/v/release/Shik3i/KoalaSync" alt="GitHub release"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/github/license/Shik3i/KoalaSync?color=blue" alt="License"></a>
|
||||
<a href="https://github.com/Shik3i/KoalaSync/releases"><img src="https://img.shields.io/badge/Release-v2.4.0-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>
|
||||
</p>
|
||||
|
||||
<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.3.2 Release!</b> — See what's changed</a></p>
|
||||
<p align="center"><a href="docs/CHANGELOG.md"><b>New v2.4.0 Release!</b> — See what's changed</a></p>
|
||||
|
||||
### 🌟 Why KoalaSync?
|
||||
|
||||
@@ -81,7 +81,7 @@ Both the official KoalaSync website and the **v2.0 Browser Extension** feature f
|
||||
To build the extension from source and synchronize protocol constants:
|
||||
```bash
|
||||
npm install
|
||||
node scripts/build-extension.js
|
||||
npm run build:extension
|
||||
```
|
||||
The compiled artifacts will be available in the `dist/` directory.
|
||||
|
||||
@@ -103,7 +103,7 @@ To connect your extension to a self-hosted server, open the popup → **Room** t
|
||||
|
||||
To verify your relay is reachable from outside, visit `https://your-domain.com` in a browser — it should return `{"status":"online","service":"KoalaSync Relay"}`.
|
||||
|
||||
#### Supply Chain Security (v2.3.2+)
|
||||
#### Supply Chain Security (v2.4.0+)
|
||||
|
||||
All official release artifacts (Docker images and extension binaries) are published with signed [artifact attestations](https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations) to prove they were built from this repository's source code.
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ KoalaSync is built for private watch parties without unnecessary data collection
|
||||
⚙️ UNDER THE HOOD
|
||||
KoalaSync is lightweight, transparent, and built with privacy in mind.
|
||||
|
||||
• Real-Time Relay: Playback state is synchronized through a custom WebSocket-based relay server for fast room updates.
|
||||
• On-Demand Relay: Playback state is synchronized through a custom WebSocket-based relay server. No persistent connection — the relay is only active while you're in a room. No background traffic, no idle connections.
|
||||
• No Media Streaming: KoalaSync does not stream, proxy, upload, download, or redistribute any video content. Everyone watches from their own browser on the original website.
|
||||
• Temporary Room State Only: The relay server only coordinates room state such as play, pause, seek position, active target, nickname, and readiness status.
|
||||
• Docker Self-Hosting: The relay server can be self-hosted with Docker if you prefer to run your own private instance.
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
This document describes the communication flows and internal logic of the KoalaSync system.
|
||||
|
||||
## 1. Extension Startup & Connection
|
||||
- **Initialization**: On startup, `background.js` reads settings (Server URL, Username, Last Room) from `chrome.storage.sync`.
|
||||
- **WebSocket Handshake**:
|
||||
## 1. Extension Connection (Lazy Connect)
|
||||
- **Initialization**: On startup, `background.js` reads settings (Server URL, Username, Last Room) from `chrome.storage.sync`. No WebSocket connection is established at this point.
|
||||
- **On-Demand Connection**: The extension only connects when needed — either the user opens the popup with saved room credentials, or when actively in a room. When not in a room, no connection exists. This improves privacy (IP not exposed while idle) and reduces battery/network usage.
|
||||
- **WebSocket Handshake (when connecting)**:
|
||||
1. Background creates a `new WebSocket` to `/socket.io/?EIO=4&transport=websocket&version=1.0.0`.
|
||||
2. Server performs security checks:
|
||||
- **IP Rate Limit**: Checks if the IP has exceeded connection limits.
|
||||
@@ -45,7 +46,7 @@ To maintain a clean room state and eliminate "Ghost Peers":
|
||||
- **Video Heartbeat (Content)**: Every 15 seconds, `content.js` sends current playback metadata (time, title, state) if a video is found.
|
||||
- **Server Pruning**: The server runs a "Reaper" every 2 minutes. If a peer has sent **zero** activity (no events and no heartbeats) for 5 minutes, they are forcefully disconnected.
|
||||
- **Immediate Cleanup**: Rooms are deleted instantly when the last peer leaves or disconnects.
|
||||
- **Reconnect Strategy**: Aggressive backoff — 500ms base, 1.5x multiplier, capped at 5s. Max 20 attempts before marking as failed. Events are queued during disconnect and flushed after namespace rejoin.
|
||||
- **Reconnect Strategy (while in room)**: Aggressive backoff — 500ms base, 1.5x multiplier, capped at 5s. Max 20 attempts before marking as failed. Events are queued during disconnect and flushed after namespace rejoin. When not in a room, no reconnection occurs.
|
||||
|
||||
> [!CAUTION]
|
||||
> **Identity Rule**: Differentiate between `peerId` and `socket.id`. Use `socket.id` exclusively for ephemeral transport routing on the server. Use `peerId` exclusively for identity, state management, and room tracking across the stack.
|
||||
@@ -70,5 +71,5 @@ KoalaSync uses a megaphone routing approach to minimize server logic:
|
||||
To maintain a "Single Source of Truth" across the server and extension without using a bundler:
|
||||
- **Relay Server & Extension Modules**: `background.js` and `popup.js` import constants directly from `shared/constants.js`.
|
||||
- **Content Scripts**: To ensure zero-latency execution, `content.js` uses a synchronized copy of `EVENTS` and constants.
|
||||
- **Automation**: The `node scripts/build-extension.js` script automatically injects these constants into `content.js` during the build process, eliminating the risk of manual mirror mismatch.
|
||||
- **Automation**: The `npm run build:extension` script automatically injects `EVENTS`, `HEARTBEAT_INTERVAL`, and `episode-utils.js` functions into `content.js` during the build process, eliminating the risk of manual mirror mismatch.
|
||||
- **Verification**: Any protocol change is automatically propagated across the stack by running the build script.
|
||||
|
||||
@@ -4,6 +4,22 @@ All notable changes to the KoalaSync browser extension and relay server.
|
||||
|
||||
---
|
||||
|
||||
## [v2.4.1] — 2026-06-19
|
||||
|
||||
### Added
|
||||
- **Extension: Onboarding tour now has a closing step** — The first-run tour ends on a dedicated "You're all set!" card (the `ONBOARDING_5` copy that already existed in all 13 locales but was never shown). The tour no longer stops abruptly on the username step.
|
||||
- **Extension: One-click invite from the empty peer list** — The "No peers yet" state now shows a **📋 Invite Link** button that copies the invite link to the clipboard, so users can share it without hunting for the field.
|
||||
|
||||
### Changed
|
||||
- **Extension: Cleaner onboarding welcome** — Step 1 is now a centered welcome card instead of spotlighting the logo title. Added a guard so target-less tour steps center cleanly.
|
||||
- **Website: Mobile comparison table** — The KoalaSync vs Teleparty table stacks into per-feature cards on phones instead of forcing horizontal scrolling; feature descriptions are shown again on mobile.
|
||||
|
||||
### Fixed
|
||||
- **Extension: Onboarding step counter/progress placeholders** — Static `Step 1 of 3` / 33% fallbacks in `popup.html` corrected to match the actual 5-step tour (`Step 1 of 5` / 20%).
|
||||
- **Website: Mobile navigation restored** — The header hamburger menu was hidden by a `display:none !important` rule, leaving the nav links unreachable on phones. Re-enabled, with spacing kept comfortable down to ~320px.
|
||||
- **Website: Hero alignment on mobile** — A fixed-width extension mockup forced the hero grid column wider than the container, shifting all hero content off-center (larger left margin than right). The mockup is now responsive (`width:100%/max-width` + `minmax(0,1fr)` grid track).
|
||||
- **Website: Reveal-animation fallback** — Added a `<noscript>` style fallback and `IntersectionObserver` feature guards so scroll-revealed content can never stay invisible if JavaScript is disabled or unsupported.
|
||||
|
||||
## [v2.4.0] — 2026-06-16
|
||||
|
||||
### Added
|
||||
|
||||
@@ -16,9 +16,9 @@ This guide walks through the complete user flow of KoalaSync, from creating a ro
|
||||
|
||||
## Step 2: Connecting to the Relay Server
|
||||
|
||||
When you open the extension popup, the background service worker connects to the relay server:
|
||||
When you open the extension popup (with saved room credentials) or when a saved room configuration exists from a previous session, the background service worker connects to the relay server:
|
||||
|
||||
1. **WebSocket Handshake**: `background.js` opens a WebSocket to `wss://syncserver.koalastuff.net/socket.io/?EIO=4&transport=websocket`.
|
||||
1. **WebSocket Handshake** (on demand): `background.js` opens a WebSocket to `wss://syncserver.koalastuff.net/socket.io/?EIO=4&transport=websocket` only when needed (popup opened or active room).
|
||||
2. **Security Checks** (server-side):
|
||||
- The server checks the client's **IP rate limit** (max 10 connections per 60 seconds).
|
||||
- The server validates the **authentication token** (hardcoded in `shared/constants.js`) to verify this is a legitimate KoalaSync client.
|
||||
@@ -155,7 +155,7 @@ While in a room, two heartbeats keep the session alive:
|
||||
|
||||
| Heartbeat | Interval | Source | Purpose |
|
||||
|:----------|:---------|:-------|:--------|
|
||||
| **Background** | 30 seconds | `background.js` | Signals "I'm still connected" and triggers aggressive reconnect (500ms base, max 5s) |
|
||||
| **Background** | 30 seconds | `background.js` | While connected, signals "I'm still connected" and triggers automatic reconnect (500ms base, max 5s). No heartbeats fire when idle (lazy connect). |
|
||||
| **Content** | 15 seconds | `content.js` | Sends video metadata: `currentTime`, `mediaTitle`, `playbackState`, `volume`, `muted` |
|
||||
|
||||
- **Server Reaper**: Every 2 minutes, the server checks for peers with no activity for 5+ minutes and disconnects them ("dead peer pruning").
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ The browser extension requires the following permissions:
|
||||
- `storage`: To remember your local preferences (username, server URL, room settings).
|
||||
- `tabs` & `scripting`: To detect and control video elements on the pages you choose to sync.
|
||||
- `<all_urls>` (host permission): Required to detect `<video>` elements on any website the user chooses to synchronize. The extension only activates on the specific tab the user has actively selected — it does not scan, monitor, or interact with any other tabs or pages.
|
||||
- `alarms`: To keep the background service worker alive during active sync sessions.
|
||||
- `alarms`: To keep the background service worker alive during active sync sessions (the extension only stays connected while you are in a room).
|
||||
- `notifications`: To display sync status updates (e.g., "Peer joined", "Force Sync initiated").
|
||||
- **No History Access**: We do not read, store, or transmit your browsing history. We only interact with the specific tab you have actively selected for synchronization.
|
||||
|
||||
|
||||
+4
-2
@@ -17,13 +17,15 @@ You MUST run the build script in any of the following scenarios:
|
||||
|
||||
Run the Node.js build script from the repository root:
|
||||
```bash
|
||||
node scripts/build-extension.js
|
||||
node scripts/build-extension.cjs
|
||||
# or simply:
|
||||
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/`.
|
||||
2. Injects `EVENTS` and `HEARTBEAT_INTERVAL` into `content.js` via marker-based replacement.
|
||||
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
-3
@@ -6,7 +6,7 @@ A Manifest V3 Browser Extension (Chrome & Firefox) for synchronized video playba
|
||||
- **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.
|
||||
- **Dual Heartbeat**: Advanced session tracking (Background) and video synchronization (Content) to prevent ghost sessions.
|
||||
- **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 6 languages (`en`, `de`, `fr`, `es`, `pt-BR`, `ru`) with auto-detected fallback and dynamic on-the-fly language selectors.
|
||||
|
||||
@@ -26,7 +26,7 @@ KoalaSync requires `<all_urls>` permission to detect and interact with video ele
|
||||
## Installation
|
||||
1. **Prepare Extension**: From the repository root, run:
|
||||
```bash
|
||||
node scripts/build-extension.js
|
||||
npm run build:extension
|
||||
```
|
||||
2. Open Chrome and go to `chrome://extensions/`.
|
||||
3. Enable **Developer mode** (top right).
|
||||
@@ -35,7 +35,7 @@ KoalaSync requires `<all_urls>` permission to detect and interact with video ele
|
||||
## Development
|
||||
If you modify `shared/constants.js`, you must synchronize the changes by running the build script from the root:
|
||||
```bash
|
||||
node scripts/build-extension.js
|
||||
npm run build:extension
|
||||
```
|
||||
This ensures that the `extension/shared` folder is updated with the latest protocol constants.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"default_locale": "en",
|
||||
"name": "KoalaSync",
|
||||
"version": "2.3.2",
|
||||
"version": "2.4.0",
|
||||
"description": "Synchronize video playback on YouTube, Netflix, Emby, Jellyfin, and any HTML5 site in real-time with friends.",
|
||||
"permissions": [
|
||||
"storage",
|
||||
|
||||
@@ -659,14 +659,14 @@
|
||||
<div id="onboarding-card" style="position:fixed; background:var(--card); padding:16px 20px; border-radius:12px; width:280px; box-shadow:0 8px 32px rgba(0,0,0,0.5); z-index:1002; transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-sizing:border-box; border:1px solid #334155; opacity:0; transform: scale(0.9); display:none;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
|
||||
<span id="onboarding-icon" style="font-size:24px;">👋</span>
|
||||
<span id="onboarding-step-indicator" style="font-size:10px; font-weight:700; color:var(--accent); text-transform:uppercase; letter-spacing:0.5px;">Step 1 of 3</span>
|
||||
<span id="onboarding-step-indicator" style="font-size:10px; font-weight:700; color:var(--accent); text-transform:uppercase; letter-spacing:0.5px;">Step 1 of 5</span>
|
||||
</div>
|
||||
<h2 id="onboarding-title" style="color:var(--accent); margin:0 0 6px; font-size:14px; font-weight:700; text-align:left;">Welcome to KoalaSync!</h2>
|
||||
<p id="onboarding-text" style="color:var(--text-muted); font-size:12px; margin:0 0 16px; line-height:1.4; text-align:left;">Let's get you started.</p>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div id="onboarding-progress-container" style="height:4px; background:#334155; border-radius:2px; margin-bottom:16px; overflow:hidden;">
|
||||
<div id="onboarding-progress-bar" style="height:100%; width:33%; background:var(--accent); transition:width 0.3s ease;"></div>
|
||||
<div id="onboarding-progress-bar" style="height:100%; width:20%; background:var(--accent); transition:width 0.3s ease;"></div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:8px; justify-content:space-between; align-items:center;">
|
||||
|
||||
+29
-4
@@ -503,6 +503,24 @@ function renderEmpty(container, type) {
|
||||
wrapper.appendChild(iconDiv);
|
||||
wrapper.appendChild(titleDiv);
|
||||
wrapper.appendChild(hintDiv);
|
||||
|
||||
// "No peers yet" — turn the hint into a one-click action so the user can
|
||||
// immediately copy and share the invite link instead of hunting for it.
|
||||
if (type === 'peers' && elements.inviteLink && elements.inviteLink.value) {
|
||||
const copyBtn = document.createElement('button');
|
||||
copyBtn.type = 'button';
|
||||
copyBtn.className = 'primary';
|
||||
copyBtn.style.cssText = 'display:inline-block; width:auto; margin-top:12px; padding:6px 14px; font-size:11px;';
|
||||
copyBtn.textContent = getMessage('BTN_COPY_INVITE');
|
||||
copyBtn.title = getMessage('BTN_COPY_INVITE_TOOLTIP');
|
||||
copyBtn.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(elements.inviteLink.value)
|
||||
.then(() => showToast(getMessage('TOAST_INVITE_COPIED'), 'success', 2000))
|
||||
.catch(() => showToast(getMessage('TOAST_COPY_FAILED'), 'error'));
|
||||
});
|
||||
wrapper.appendChild(copyBtn);
|
||||
}
|
||||
|
||||
container.replaceChildren(wrapper);
|
||||
}
|
||||
|
||||
@@ -2119,9 +2137,9 @@ const onboardingSteps = [
|
||||
{
|
||||
icon: '👋',
|
||||
get title() { return getMessage('ONBOARDING_1_TITLE'); },
|
||||
get text() { return getMessage('ONBOARDING_1_TEXT'); },
|
||||
get text() { return getMessage('ONBOARDING_1_TEXT'); },
|
||||
targetTab: 'tab-room',
|
||||
targetSelector: 'h1'
|
||||
targetSelector: null
|
||||
},
|
||||
{
|
||||
icon: '🏠',
|
||||
@@ -2140,9 +2158,16 @@ const onboardingSteps = [
|
||||
{
|
||||
icon: '⚙️',
|
||||
get title() { return getMessage('ONBOARDING_4_TITLE'); },
|
||||
get text() { return getMessage('ONBOARDING_4_TEXT'); },
|
||||
get text() { return getMessage('ONBOARDING_4_TEXT'); },
|
||||
targetTab: 'tab-settings',
|
||||
targetSelector: '#username'
|
||||
},
|
||||
{
|
||||
icon: '🍿',
|
||||
get title() { return getMessage('ONBOARDING_5_TITLE'); },
|
||||
get text() { return getMessage('ONBOARDING_5_TEXT'); },
|
||||
targetTab: 'tab-room',
|
||||
targetSelector: null
|
||||
}
|
||||
];
|
||||
|
||||
@@ -2316,7 +2341,7 @@ function renderOnboardingStep() {
|
||||
}
|
||||
|
||||
onboardingTimeout = setTimeout(() => {
|
||||
const targetEl = document.querySelector(step.targetSelector);
|
||||
const targetEl = step.targetSelector ? document.querySelector(step.targetSelector) : null;
|
||||
if (targetEl && targetEl.offsetParent !== null) {
|
||||
positionSpotlightAndCard(targetEl);
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "koalasync",
|
||||
"version": "2.3.2",
|
||||
"version": "2.4.0",
|
||||
"description": "KoalaSync Build Scripts",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
+29
-21
@@ -19,31 +19,39 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Scroll Reveal Logic (IntersectionObserver for performance)
|
||||
const revealElements = document.querySelectorAll('[data-reveal]');
|
||||
|
||||
const revealObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('revealed');
|
||||
revealObserver.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
rootMargin: '0px 0px -150px 0px',
|
||||
threshold: 0.1
|
||||
});
|
||||
|
||||
revealElements.forEach(el => revealObserver.observe(el));
|
||||
if ('IntersectionObserver' in window) {
|
||||
const revealObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('revealed');
|
||||
revealObserver.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
rootMargin: '0px 0px -150px 0px',
|
||||
threshold: 0.1
|
||||
});
|
||||
|
||||
revealElements.forEach(el => revealObserver.observe(el));
|
||||
} else {
|
||||
// Fallback: without IntersectionObserver support, reveal everything
|
||||
// immediately so no content can ever stay hidden.
|
||||
revealElements.forEach(el => el.classList.add('revealed'));
|
||||
}
|
||||
|
||||
// Auto-update URL hash as user scrolls through sections
|
||||
// (preserves position across language switches)
|
||||
const sectionObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
history.replaceState(null, null, '#' + entry.target.id);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.3 });
|
||||
document.querySelectorAll('section[id], header[id]').forEach(el => sectionObserver.observe(el));
|
||||
if ('IntersectionObserver' in window) {
|
||||
const sectionObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
history.replaceState(null, null, '#' + entry.target.id);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.3 });
|
||||
document.querySelectorAll('section[id], header[id]').forEach(el => sectionObserver.observe(el));
|
||||
}
|
||||
|
||||
// Navbar scroll effect
|
||||
const nav = document.querySelector('nav');
|
||||
|
||||
@@ -113,6 +113,9 @@
|
||||
<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>.
|
||||
</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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
||||
+123
-5
@@ -358,7 +358,8 @@ nav {
|
||||
}
|
||||
|
||||
.extension-mockup {
|
||||
width: 320px;
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
height: 520px;
|
||||
background: #0f172a;
|
||||
border-radius: 20px;
|
||||
@@ -1900,7 +1901,7 @@ footer {
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero-grid, .step {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
text-align: center;
|
||||
}
|
||||
.hero-text h1 {
|
||||
@@ -1931,7 +1932,7 @@ footer {
|
||||
margin-left: auto;
|
||||
}
|
||||
.hamburger {
|
||||
display: none !important;
|
||||
display: inline-flex !important;
|
||||
}
|
||||
nav .container {
|
||||
padding: 0 0.5rem;
|
||||
@@ -1966,6 +1967,21 @@ footer {
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra-narrow phones: make sure logo, language picker and the menu button
|
||||
never crowd each other in the header (kept comfortable down to ~320px). */
|
||||
@media (max-width: 380px) {
|
||||
nav .container {
|
||||
padding: 0 0.4rem;
|
||||
}
|
||||
.nav-right {
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.logo-area {
|
||||
font-size: 1rem;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Language Toggle --- */
|
||||
html.lang-de [lang="en"] {
|
||||
display: none !important;
|
||||
@@ -2450,13 +2466,115 @@ html:not(.lang-de) [lang="de"] {
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.comparison-table th,
|
||||
.comparison-table th,
|
||||
.comparison-table td {
|
||||
padding: 1rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* On phones the 3-column table is turned into stacked cards so there is
|
||||
no horizontal scrolling — one card per feature, KoalaSync above Teleparty. */
|
||||
@media (max-width: 600px) {
|
||||
.comparison-table-wrapper {
|
||||
overflow-x: visible;
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.comparison-table {
|
||||
display: block;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* Visually hide the header row but keep it for screen readers. */
|
||||
.comparison-table thead {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.comparison-table tbody,
|
||||
.comparison-table tr,
|
||||
.comparison-table td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.comparison-table tbody tr {
|
||||
background: rgba(30, 41, 59, 0.35);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
|
||||
margin-bottom: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.comparison-table tbody tr:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.comparison-table td {
|
||||
padding: 0.85rem 1.1rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.comparison-table tbody tr td:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Feature name acts as the card header. */
|
||||
.comparison-table td.feat-name {
|
||||
background: rgba(15, 23, 42, 0.55);
|
||||
padding: 0.9rem 1.1rem;
|
||||
}
|
||||
|
||||
/* Room is back on the card, so show the description again. */
|
||||
.feat-desc {
|
||||
display: none;
|
||||
display: block;
|
||||
margin-top: 0.15rem;
|
||||
}
|
||||
|
||||
/* Label each value cell with the product it belongs to. */
|
||||
.comparison-table td.check,
|
||||
.comparison-table td.cross {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.comparison-table td.check::before,
|
||||
.comparison-table td.cross::before {
|
||||
flex: 0 0 5.5rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-muted);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.comparison-table td.check::before {
|
||||
content: "KoalaSync";
|
||||
}
|
||||
|
||||
.comparison-table td.cross::before {
|
||||
content: "Teleparty";
|
||||
}
|
||||
|
||||
/* Keep the KoalaSync row subtly highlighted, like on desktop. */
|
||||
.comparison-table td.check.highlight {
|
||||
background: rgba(99, 102, 241, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"priceCurrency": "EUR"
|
||||
},
|
||||
"description": "Watch Netflix, YouTube, Twitch, Jellyfin, Emby and almost any HTML5 video in perfect sync with friends. Open source, privacy-first, free.",
|
||||
"softwareVersion": "2.3.2",
|
||||
"softwareVersion": "2.4.0",
|
||||
"license": "https://opensource.org/licenses/MIT",
|
||||
"sameAs": "https://github.com/Shik3i/KoalaSync",
|
||||
"image": "https://sync.koalastuff.net/assets/NewLogoIcon.webp",
|
||||
@@ -183,6 +183,13 @@
|
||||
]
|
||||
}
|
||||
</script>
|
||||
<noscript>
|
||||
<!-- Without JS the scroll-reveal observer never runs, so make all
|
||||
reveal-animated content visible by default. -->
|
||||
<style>
|
||||
[data-reveal] { opacity: 1 !important; transform: none !important; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "2.3.2",
|
||||
"date": "2026-06-16T02:29:41Z"
|
||||
"version": "2.4.0",
|
||||
"date": "2026-06-16T12:00:03Z"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user