feat: production-ready release v1.0.0

- Integrated 'Koala-Bridge' for seamless web-to-extension communication.
- Implemented dedicated, minimalist invitation page (join.html).
- Added brute-force protection and RAM-cleanup to the relay server.
- Removed external fonts and trackers from the landing page (Privacy First).
- Unified header/footer design across all website pages.
- Added MIT License and comprehensive legal documentation (Impressum/Datenschutz).
This commit is contained in:
MacBook
2026-04-21 08:46:28 +02:00
parent 8b02593f40
commit 3e2e9ed7a5
15 changed files with 757 additions and 12 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Shik3i
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+31
View File
@@ -191,9 +191,30 @@ function handleServerEvent(event, data) {
currentRoom = data; currentRoom = data;
addLog(`Joined Room: ${data.roomId}`, 'success'); addLog(`Joined Room: ${data.roomId}`, 'success');
chrome.runtime.sendMessage({ type: 'PEER_UPDATE', peers: data.peers }).catch(() => {}); chrome.runtime.sendMessage({ type: 'PEER_UPDATE', peers: data.peers }).catch(() => {});
// Inform Website Bridge
chrome.tabs.query({}, (tabs) => {
tabs.forEach(tab => {
chrome.tabs.sendMessage(tab.id, { type: 'JOIN_STATUS', success: true, message: 'Joined' }).catch(() => {});
});
});
break;
case EVENTS.ROOM_LIST:
chrome.runtime.sendMessage({ type: 'ROOM_LIST', rooms: data.rooms }).catch(() => {});
break; break;
case EVENTS.ERROR: case EVENTS.ERROR:
addLog(`Server Error: ${data.message}`, 'error'); addLog(`Server Error: ${data.message}`, 'error');
chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon128.png',
title: 'KoalaSync Error',
message: data.message
});
// Inform Website Bridge
chrome.tabs.query({}, (tabs) => {
tabs.forEach(tab => {
chrome.tabs.sendMessage(tab.id, { type: 'JOIN_STATUS', success: false, message: data.message }).catch(() => {});
});
});
break; break;
case EVENTS.PLAY: case EVENTS.PLAY:
case EVENTS.PAUSE: case EVENTS.PAUSE:
@@ -327,6 +348,16 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
sendResponse(logs); sendResponse(logs);
} else if (message.type === 'GET_HISTORY') { } else if (message.type === 'GET_HISTORY') {
sendResponse(history); sendResponse(history);
} else if (message.type === 'GET_ROOM_LIST') {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(`42${JSON.stringify([EVENTS.GET_ROOMS])}`);
}
} else if (message.type === 'WEB_JOIN_REQUEST') {
const { roomId, password } = message;
chrome.storage.sync.set({ roomId, password }, () => {
connect();
// We wait for status update in handleServerEvent
});
} else if (message.type === 'CONTENT_EVENT') { } else if (message.type === 'CONTENT_EVENT') {
if (sender.tab) { if (sender.tab) {
currentTabId = sender.tab.id; currentTabId = sender.tab.id;
+31
View File
@@ -0,0 +1,31 @@
/**
* KoalaSync Bridge Script
* Injected into koalasync.shik3i.net to facilitate communication between
* the landing page and the extension.
*/
// 1. Signal presence to the website
document.documentElement.dataset.koalasyncInstalled = 'true';
// 2. Listen for Join Requests from the Website
window.addEventListener('KOALASYNC_JOIN_REQUEST', (e) => {
const { roomId, password } = e.detail;
chrome.runtime.sendMessage({
type: 'WEB_JOIN_REQUEST',
roomId,
password
});
});
// 3. Listen for Status Updates from the Extension and relay to Website
chrome.runtime.onMessage.addListener((msg) => {
if (msg.type === 'JOIN_STATUS') {
const event = new CustomEvent('KOALASYNC_STATUS', {
detail: {
success: msg.success,
message: msg.message
}
});
window.dispatchEvent(event);
}
});
+5
View File
@@ -35,6 +35,11 @@
], ],
"run_at": "document_idle", "run_at": "document_idle",
"all_frames": false "all_frames": false
},
{
"matches": ["https://koalasync.shik3i.net/*"],
"js": ["bridge.js"],
"run_at": "document_start"
} }
], ],
"icons": { "icons": {
+11 -1
View File
@@ -222,7 +222,17 @@
<label>Password (Optional)</label> <label>Password (Optional)</label>
<input type="password" id="password" placeholder="Room password"> <input type="password" id="password" placeholder="Room password">
</div> </div>
<button id="joinBtn" class="primary">Join / Create Room</button> <div id="roomError" style="display:none; color:var(--error); font-size:11px; margin-bottom:8px; text-align:center;"></div>
<button id="joinBtn" class="primary">Join Room</button>
<button id="createRoomBtn" class="secondary" style="border: 1px solid var(--accent); color: var(--accent);">Create Random Room</button>
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1.5rem; margin-bottom: 8px;">
<label style="margin:0;">Public Rooms</label>
<button id="refreshRooms" style="background:transparent; border:none; color:var(--accent); font-size:10px; cursor:pointer;">REFRESH</button>
</div>
<div id="publicRooms" class="info-card" style="max-height: 120px; overflow-y: auto; padding: 4px;">
<div style="text-align:center; color: var(--text-muted); font-size: 11px; padding: 10px;">Refreshing...</div>
</div>
<div id="roomInfo" style="display:none; margin-top: 20px;"> <div id="roomInfo" style="display:none; margin-top: 20px;">
<label>Invite Link</label> <label>Invite Link</label>
+93 -3
View File
@@ -1,4 +1,4 @@
import { EVENTS } from './shared/constants.js'; import { EVENTS, OFFICIAL_LANDING_PAGE_URL } from './shared/constants.js';
import { BLACKLIST_DOMAINS } from './shared/blacklist.js'; import { BLACKLIST_DOMAINS } from './shared/blacklist.js';
const elements = { const elements = {
@@ -23,7 +23,11 @@ const elements = {
inviteLink: document.getElementById('inviteLink'), inviteLink: document.getElementById('inviteLink'),
filterNoise: document.getElementById('filterNoise'), filterNoise: document.getElementById('filterNoise'),
historyList: document.getElementById('historyList'), historyList: document.getElementById('historyList'),
copyLogs: document.getElementById('copyLogs') copyLogs: document.getElementById('copyLogs'),
createRoomBtn: document.getElementById('createRoomBtn'),
publicRooms: document.getElementById('publicRooms'),
refreshRooms: document.getElementById('refreshRooms'),
roomError: document.getElementById('roomError')
}; };
// --- Initialization --- // --- Initialization ---
@@ -55,6 +59,12 @@ async function init() {
updatePeerList(res.peers); updatePeerList(res.peers);
} }
}); });
// Check for invite link on landing page
checkInviteLink();
// Initial room list fetch
chrome.runtime.sendMessage({ type: 'GET_ROOM_LIST' });
} }
// --- UI Logic --- // --- UI Logic ---
@@ -62,7 +72,8 @@ function updateUI(roomId, password) {
const inRoom = !!roomId; const inRoom = !!roomId;
elements.roomInfo.style.display = inRoom ? 'block' : 'none'; elements.roomInfo.style.display = inRoom ? 'block' : 'none';
if (inRoom) { if (inRoom) {
elements.inviteLink.value = `${roomId}${password ? '#' + password : ''}`; const invite = `${OFFICIAL_LANDING_PAGE_URL}/join.html#join:${roomId}:${password}`;
elements.inviteLink.value = invite;
} }
} }
@@ -168,6 +179,43 @@ function refreshHistory() {
}); });
} }
function updateRoomList(rooms) {
if (!rooms || rooms.length === 0) {
elements.publicRooms.innerHTML = '<div style="text-align:center; padding: 10px; color:var(--text-muted);">No active rooms</div>';
return;
}
elements.publicRooms.innerHTML = rooms.map(r => `
<div class="room-item" style="display:flex; justify-content:space-between; align-items:center; padding: 8px; border-bottom: 1px solid rgba(255,255,255,0.05); cursor:pointer;" data-id="${r.id}">
<span style="font-weight:600;">${r.id}</span>
<span style="font-size:11px; color:var(--accent)">${r.peerCount} peers</span>
</div>
`).join('');
elements.publicRooms.querySelectorAll('.room-item').forEach(item => {
item.addEventListener('click', () => {
elements.roomId.value = item.dataset.id;
elements.password.value = '';
elements.password.focus();
});
});
}
function checkInviteLink() {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const tab = tabs[0];
if (tab && tab.url && tab.url.includes(OFFICIAL_LANDING_PAGE_URL) && tab.url.includes('#join:')) {
const parts = tab.url.split('#join:')[1].split(':');
if (parts.length >= 2) {
elements.roomId.value = parts[0];
elements.password.value = parts[1];
// Visual feedback
elements.joinBtn.style.boxShadow = '0 0 15px var(--accent)';
setTimeout(() => elements.joinBtn.style.boxShadow = '', 2000);
}
}
});
}
function setServerMode(custom) { function setServerMode(custom) {
elements.serverOfficial.classList.toggle('active', !custom); elements.serverOfficial.classList.toggle('active', !custom);
elements.serverCustom.classList.toggle('active', custom); elements.serverCustom.classList.toggle('active', custom);
@@ -194,6 +242,28 @@ elements.tabs.forEach(btn => {
}); });
}); });
function showError(msg) {
if (!elements.roomError) return;
elements.roomError.textContent = msg;
elements.roomError.style.display = 'block';
elements.roomId.style.borderColor = 'var(--error)';
elements.password.style.borderColor = 'var(--error)';
// Shake effect
document.getElementById('tab-room').animate([
{ transform: 'translateX(0)' },
{ transform: 'translateX(-5px)' },
{ transform: 'translateX(5px)' },
{ transform: 'translateX(0)' }
], { duration: 200, iterations: 2 });
setTimeout(() => {
if (elements.roomError) elements.roomError.style.display = 'none';
elements.roomId.style.borderColor = '';
elements.password.style.borderColor = '';
}, 5000);
}
// --- Action Handlers --- // --- Action Handlers ---
elements.joinBtn.addEventListener('click', async () => { elements.joinBtn.addEventListener('click', async () => {
const serverUrl = elements.serverUrl.value; const serverUrl = elements.serverUrl.value;
@@ -216,6 +286,22 @@ elements.leaveBtn.addEventListener('click', async () => {
updateUI(null, null); updateUI(null, null);
}); });
elements.createRoomBtn.addEventListener('click', () => {
const animals = ['koala', 'panda', 'tiger', 'eagle', 'fox', 'bear'];
const adj = ['happy', 'cool', 'fast', 'smart', 'brave', 'calm'];
const id = `${adj[Math.floor(Math.random() * adj.length)]}-${animals[Math.floor(Math.random() * animals.length)]}-${Math.floor(Math.random() * 100)}`;
const pass = Math.random().toString(36).substring(2, 8);
elements.roomId.value = id;
elements.password.value = pass;
elements.joinBtn.click();
});
elements.refreshRooms.addEventListener('click', () => {
elements.publicRooms.innerHTML = '<div style="text-align:center; padding: 10px; color:var(--text-muted);">Refreshing...</div>';
chrome.runtime.sendMessage({ type: 'GET_ROOM_LIST' });
});
elements.targetTab.addEventListener('change', async () => { elements.targetTab.addEventListener('change', async () => {
await chrome.storage.sync.set({ targetTabId: elements.targetTab.value }); await chrome.storage.sync.set({ targetTabId: elements.targetTab.value });
}); });
@@ -270,6 +356,10 @@ chrome.runtime.onMessage.addListener((msg) => {
applyConnectionStatus(msg.status); applyConnectionStatus(msg.status);
} else if (msg.type === 'HISTORY_UPDATE') { } else if (msg.type === 'HISTORY_UPDATE') {
updateHistory(msg.history); updateHistory(msg.history);
} else if (msg.type === 'ROOM_LIST') {
updateRoomList(msg.rooms);
} else if (msg.type === 'LOG_UPDATE' && msg.log && msg.log.type === 'error') {
showError(msg.log.message);
} }
}); });
+54 -1
View File
@@ -42,8 +42,46 @@ function log(type, message, details = '') {
console.log(`[${timestamp}] [${type}] ${message}`, details); console.log(`[${timestamp}] [${type}] ${message}`, details);
} }
// Rate Limiting Storage // Rate Limiting & Security
const connectionCounts = new Map(); // ip -> { count, resetTime } const connectionCounts = new Map(); // ip -> { count, resetTime }
const failedAuthAttempts = new Map(); // Map<IP+RoomID, {count, lastAttempt}>
function checkAuthRate(ip, roomId) {
const key = `${ip}:${roomId}`;
const now = Date.now();
const record = failedAuthAttempts.get(key) || { count: 0, lastAttempt: 0 };
// Block for 15 mins if 5 fails in 2 mins
if (record.count >= 5 && (now - record.lastAttempt) < 15 * 60 * 1000) {
return false;
}
// Reset if last attempt was long ago
if ((now - record.lastAttempt) > 2 * 60 * 1000) {
record.count = 0;
}
return true;
}
function recordAuthFailure(ip, roomId) {
const key = `${ip}:${roomId}`;
const record = failedAuthAttempts.get(key) || { count: 0, lastAttempt: 0 };
record.count++;
record.lastAttempt = Date.now();
failedAuthAttempts.set(key, record);
}
// Periodically clean up old auth failure records (every hour)
setInterval(() => {
const now = Date.now();
for (const [key, record] of failedAuthAttempts.entries()) {
if (now - record.lastAttempt > 60 * 60 * 1000) {
failedAuthAttempts.delete(key);
}
}
}, 60 * 60 * 1000);
const eventCounts = new Map(); // socketId -> { count, resetTime } const eventCounts = new Map(); // socketId -> { count, resetTime }
function checkConnectionRate(ip) { function checkConnectionRate(ip) {
@@ -121,6 +159,12 @@ io.on('connection', (socket) => {
} }
} }
const ip = socket.handshake.address;
if (!checkAuthRate(ip, roomId)) {
socket.emit(EVENTS.ERROR, { message: "Too many failed attempts. Try again later." });
return;
}
let room = rooms.get(roomId); let room = rooms.get(roomId);
if (!room) { if (!room) {
@@ -141,6 +185,7 @@ io.on('connection', (socket) => {
} else { } else {
if (room.passwordHash) { if (room.passwordHash) {
if (!password || !(await bcrypt.compare(password, room.passwordHash))) { if (!password || !(await bcrypt.compare(password, room.passwordHash))) {
recordAuthFailure(ip, roomId);
socket.emit(EVENTS.ERROR, { message: "Invalid password" }); socket.emit(EVENTS.ERROR, { message: "Invalid password" });
return; return;
} }
@@ -192,6 +237,14 @@ io.on('connection', (socket) => {
}); });
}); });
socket.on(EVENTS.GET_ROOMS, () => {
const list = Array.from(rooms.entries()).map(([id, r]) => ({
id,
peerCount: r.peers.size
}));
socket.emit(EVENTS.ROOM_LIST, { rooms: list });
});
socket.on(EVENTS.LEAVE_ROOM, () => { socket.on(EVENTS.LEAVE_ROOM, () => {
const mapping = socketToRoom.get(socket.id); const mapping = socketToRoom.get(socket.id);
if (mapping) { if (mapping) {
+5 -2
View File
@@ -6,7 +6,8 @@ export const PROTOCOL_VERSION = "1.0.0";
export const APP_VERSION = "1.0.0"; export const APP_VERSION = "1.0.0";
export const OFFICIAL_SERVER_URL = 'wss://sync.shik3i.net'; export const OFFICIAL_SERVER_URL = 'wss://sync.shik3i.net';
export const OFFICIAL_SERVER_TOKEN = '39ce48b42aa442a34b80cfe2d7314177d4c725c4316b1f83aa3a1e2f0f5c5bfd'; export const OFFICIAL_LANDING_PAGE_URL = 'https://koalasync.shik3i.net';
export const OFFICIAL_SERVER_TOKEN = 'koala_secure_access_2026';
export const EVENTS = { export const EVENTS = {
// Connection & Room // Connection & Room
@@ -24,7 +25,9 @@ export const EVENTS = {
PEER_STATUS: "peer_status", // Heartbeat from peers PEER_STATUS: "peer_status", // Heartbeat from peers
FORCE_SYNC_PREPARE: "force_sync_prepare", FORCE_SYNC_PREPARE: "force_sync_prepare",
FORCE_SYNC_ACK: "force_sync_ack", FORCE_SYNC_ACK: "force_sync_ack",
FORCE_SYNC_EXECUTE: "force_sync_execute" FORCE_SYNC_EXECUTE: "force_sync_execute",
GET_ROOMS: "get_rooms",
ROOM_LIST: "room_list"
}; };
export const HEARTBEAT_INTERVAL = 15000; // 15s export const HEARTBEAT_INTERVAL = 15000; // 15s
+51
View File
@@ -0,0 +1,51 @@
# KoalaSync Landing Page
This directory contains the static marketing website for KoalaSync. It is built using vanilla HTML, CSS, and JavaScript to ensure maximum performance, zero tracking, and easy hosting.
## Features
- **Privacy First**: No external fonts, scripts, or trackers.
- **Modern Tech Aesthetic**: Pure CSS animated gradients and glassmorphism.
- **Smart Join**: Integrated bridge for communication with the KoalaSync browser extension.
## Hosting with Caddy
Caddy is the recommended web server for KoalaSync due to its automatic HTTPS and simple configuration.
### Example Caddyfile
To host the website on `koalasync.shik3i.net`, you can use the following configuration:
```caddy
koalasync.shik3i.net {
# Path to the website directory
root * /var/www/koalasync/website
# Enable static file serving
file_server
# Enable Gzip/Zstd compression
encode zstd gzip
# Security Headers
header {
# Prevent FLoC tracking
Permissions-Policy interest-cohort=()
# Security best practices
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy no-referrer-when-downgrade
}
# Custom 404 page
handle_errors {
rewrite * /{err.status_code}.html
file_server
}
}
```
### Deployment Steps
1. Copy the contents of this folder to `/var/www/koalasync/website` on your server.
2. Update the path in your `Caddyfile`.
3. Reload Caddy: `caddy reload`.
+116
View File
@@ -47,4 +47,120 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
}); });
// Invite Detection & Bridge
const checkInvite = () => {
const isJoinPage = window.location.pathname.includes('join.html');
// Use a short timeout to let the bridge script initialize its dataset attribute
setTimeout(() => {
const isInstalled = document.documentElement.dataset.koalasyncInstalled === 'true';
if (window.location.hash.startsWith('#join:')) {
const parts = window.location.hash.split(':');
if (parts.length >= 3) {
const roomId = parts[1];
const password = parts[2];
if (isJoinPage) {
const displayRoom = document.getElementById('display-room-id');
const actions = document.getElementById('join-actions');
if (displayRoom) displayRoom.textContent = roomId;
if (actions) {
if (!isInstalled) {
actions.innerHTML = `
<a href="#" class="primary" style="text-align:center; text-decoration:none; display:block; padding: 1.2rem; background: var(--accent); color: white; border-radius: 12px; font-weight: 700;">GET IT ON CHROME WEBSTORE</a>
<a href="https://github.com/shik3i/KoalaSync" style="text-align:center; color:var(--accent); text-decoration:underline; font-size:0.85rem; margin-top:0.8rem; display:block; font-weight: 600;">Download via GitHub</a>
<p style="text-align:center; font-size:0.8rem; opacity:0.7; margin-top: 1.2rem; color: var(--text-muted);">The extension is required to join and sync videos.</p>
`;
} else {
actions.innerHTML = `
<button class="primary" id="webJoinBtn" style="padding: 1.2rem; width:100%; cursor: pointer;">JOIN ROOM NOW</button>
<p style="text-align:center; font-size:0.8rem; color:var(--success); margin-top: 0.8rem; font-weight: 600;">✅ Extension detected and ready.</p>
`;
}
}
} else {
// Fallback banner for index.html
if (!document.getElementById('koala-banner')) {
const banner = document.createElement('div');
banner.className = 'invite-banner';
banner.id = 'koala-banner';
banner.innerHTML = `
<div class="container" style="display:flex; justify-content:space-between; align-items:center;">
<span>🎫 Invitation for <b>${roomId}</b> detected!</span>
<a href="join.html${window.location.hash}" class="btn-banner">OPEN JOIN PAGE</a>
</div>
`;
document.body.prepend(banner);
}
}
// Global listener for Join Button
document.addEventListener('click', (e) => {
if (e.target && e.target.id === 'webJoinBtn') {
e.target.textContent = 'JOINING...';
e.target.disabled = true;
window.dispatchEvent(new CustomEvent('KOALASYNC_JOIN_REQUEST', {
detail: { roomId, password }
}));
}
});
}
}
}, 600); // 600ms delay to ensure bridge.js has set the dataset
};
// Listen for status from Extension
window.addEventListener('KOALASYNC_STATUS', (e) => {
const { success, message } = e.detail;
const isJoinPage = window.location.pathname.includes('join.html');
if (isJoinPage) {
const icon = document.getElementById('join-status-icon');
const title = document.getElementById('join-title');
const actions = document.getElementById('join-actions');
const desc = document.getElementById('join-desc');
if (success) {
if (icon) icon.textContent = '✅';
title.textContent = 'Erfolgreich!';
let count = 5;
const updateCountdown = () => {
desc.textContent = `Du bist dem Raum beigetreten. Dieser Tab schließt sich in ${count} Sekunden...`;
if (count <= 0) {
window.close();
desc.textContent = 'Beitritt erfolgreich! Du kannst diesen Tab jetzt manuell schließen.';
} else {
count--;
setTimeout(updateCountdown, 1000);
}
};
updateCountdown();
actions.innerHTML = '<button class="primary" onclick="window.close()" style="background:var(--success); width: 100%;">TAB JETZT SCHLIESSEN</button>';
} else {
if (icon) icon.textContent = '❌';
title.textContent = 'Fehler';
desc.textContent = `Beitritt fehlgeschlagen: ${message}`;
actions.innerHTML = '<button class="primary" onclick="location.reload()" style="width: 100%;">ERNEUT VERSUCHEN</button>';
}
} else {
const banner = document.getElementById('koala-banner');
if (banner) {
if (success) {
banner.style.background = 'var(--success)';
banner.innerHTML = '<div class="container">✅ Joined! This tab will close in 3s...</div>';
setTimeout(() => window.close(), 3000);
} else {
banner.style.background = 'var(--error)';
banner.innerHTML = `<div class="container">❌ Error: ${message}</div>`;
}
}
}
});
checkInvite();
}); });
+88
View File
@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Datenschutz | KoalaSync</title>
<link rel="stylesheet" href="style.css">
<link rel="icon" type="image/png" href="assets/logo.png">
</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="index.html" class="logo-area" style="text-decoration: none;">
<img src="assets/logo.png" alt="KoalaSync Logo">
<span>KoalaSync</span>
</a>
<div class="nav-links">
<a href="index.html">Home</a>
<a href="https://github.com/shik3i/KoalaSync" target="_blank">GitHub</a>
</div>
</div>
</nav>
<main class="legal-content">
<div class="legal-card" data-reveal style="padding: 2rem;">
<h1>Datenschutz</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;">
Sicherheit & Privatsphäre
</p>
<section>
<h2>1. Hosting & Logfiles</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.
</p>
</section>
<section>
<h2>2. Keine Drittanbieter & Tracking</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>
</section>
<section>
<h2>3. Relay-Server Architektur</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.
</p>
</section>
<section>
<h2>4. Brute-Force 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.
</p>
</section>
<section>
<h2>5. Ihre Rechte</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.
</p>
<p>Kontakt bei Fragen: <span style="color: var(--accent); cursor: pointer; text-decoration: underline;" onclick="this.innerHTML='koalasync_datenschutz' + '@' + 'koalamail.rocks'">[E-Mail anzeigen]</span></p>
</section>
</div>
</main>
<footer>
<div class="container">
<p>&copy; 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>
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; gap: 1.5rem;">
<a href="impressum.html" style="color: var(--text-muted); text-decoration: none;">Impressum</a>
<a href="datenschutz.html" style="color: var(--text-muted); text-decoration: none;">Datenschutz</a>
</div>
</div>
</footer>
<script src="app.js"></script>
</body>
</html>
+91
View File
@@ -0,0 +1,91 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Impressum | KoalaSync</title>
<link rel="stylesheet" href="style.css">
<link rel="icon" type="image/png" href="assets/logo.png">
</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="index.html" class="logo-area" style="text-decoration: none;">
<img src="assets/logo.png" alt="KoalaSync Logo">
<span>KoalaSync</span>
</a>
<div class="nav-links">
<a href="index.html">Home</a>
<a href="https://github.com/shik3i/KoalaSync" target="_blank">GitHub</a>
</div>
</div>
</nav>
<main class="legal-content">
<div class="legal-card" data-reveal style="padding: 2rem;">
<h1>Impressum</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
</p>
<section>
<h2>Betreiber & Kontakt</h2>
<p>Administrator KoalaSync (Privatperson)</p>
<p>E-Mail: <span style="color: var(--accent); cursor: pointer; text-decoration: underline;" onclick="this.innerHTML='koalasync_admin' + '@' + 'koalamail.rocks'">[E-Mail anzeigen]</span></p>
</section>
<section style="opacity: 0.8;">
<h2>Privatprojekt-Hinweis</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.
</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.
</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.
</p>
</section>
<section>
<h2>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.
</p>
</section>
</div>
</main>
<footer>
<div class="container">
<p>&copy; 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>
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; gap: 1.5rem;">
<a href="impressum.html" style="color: var(--text-muted); text-decoration: none;">Impressum</a>
<a href="datenschutz.html" style="color: var(--text-muted); text-decoration: none;">Datenschutz</a>
</div>
</div>
</footer>
<script src="app.js"></script>
</body>
</html>
+5 -3
View File
@@ -8,8 +8,6 @@
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<link rel="icon" type="image/png" href="assets/logo.png"> <link rel="icon" type="image/png" href="assets/logo.png">
<!-- Open Graph -->
<meta property="og:title" content="KoalaSync | Sync your videos"> <meta property="og:title" content="KoalaSync | Sync your videos">
<meta property="og:description" content="Watch together, stay in sync. Privacy-first video synchronization."> <meta property="og:description" content="Watch together, stay in sync. Privacy-first video synchronization.">
<meta property="og:image" content="assets/hero.png"> <meta property="og:image" content="assets/hero.png">
@@ -124,7 +122,11 @@
<footer> <footer>
<div class="container"> <div class="container">
<p>&copy; 2026 KoalaSync. Open source under the MIT License.</p> <p>&copy; 2026 KoalaSync. Open source under the MIT License.</p>
<p style="font-size: 0.8rem; margin-top: 1rem;">No data is stored on our servers. Pure RAM-based relay.</p> <p style="font-size: 0.8rem; margin-top: 0.5rem;">No data is stored on our servers. Pure RAM-based relay.</p>
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; gap: 1.5rem;">
<a href="impressum.html" style="color: var(--text-muted); text-decoration: none;">Impressum</a>
<a href="datenschutz.html" style="color: var(--text-muted); text-decoration: none;">Datenschutz</a>
</div>
</div> </div>
</footer> </footer>
+62
View File
@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Beitreten | KoalaSync</title>
<link rel="stylesheet" href="style.css">
<link rel="icon" type="image/png" href="assets/logo.png">
</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="index.html" class="logo-area" style="text-decoration: none;">
<img src="assets/logo.png" alt="KoalaSync Logo">
<span>KoalaSync</span>
</a>
<div class="nav-links">
<a href="index.html">Home</a>
<a href="https://github.com/shik3i/KoalaSync" target="_blank">GitHub</a>
</div>
</div>
</nav>
<main class="legal-content join-card">
<div class="legal-card" id="join-container" data-reveal style="padding: 2.5rem;">
<div class="room-badge">INVITATION DETECTED</div>
<h1 id="join-title" style="font-size: 2rem; margin-bottom: 1rem;">Ready to sync?</h1>
<p id="join-desc" style="text-align: center; color: var(--text-muted); margin-bottom: 2rem; font-size: 0.9rem;">
You've been invited to join a synchronized session.
</p>
<div id="room-info-box" style="background: rgba(255,255,255,0.03); padding: 2rem; border-radius: 20px; margin-bottom: 2rem; border: 1px solid var(--glass-border); text-align: center; position: relative; overflow: hidden;">
<div style="font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.2em; color: var(--accent); margin-bottom: 0.75rem; font-weight: 700;">Room ID</div>
<div id="display-room-id" style="font-size: 1.75rem; font-weight: 800; letter-spacing: 1px; color: white;">-------</div>
</div>
<div id="join-actions" style="display: flex; flex-direction: column; gap: 1rem;">
<div style="text-align: center; color: var(--text-muted); font-size: 0.8rem;">Detecting extension...</div>
</div>
</div>
</main>
<footer>
<div class="container">
<p>&copy; 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>
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; gap: 1.5rem;">
<a href="impressum.html" style="color: var(--text-muted); text-decoration: none;">Impressum</a>
<a href="datenschutz.html" style="color: var(--text-muted); text-decoration: none;">Datenschutz</a>
</div>
</div>
</footer>
<script src="app.js"></script>
</body>
</html>
+93 -2
View File
@@ -1,4 +1,4 @@
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&display=swap'); /* KoalaSync Design System - Privacy-Focused (No External Assets) */
:root { :root {
--bg: #0f172a; --bg: #0f172a;
@@ -12,6 +12,36 @@
--glass-border: rgba(255, 255, 255, 0.05); --glass-border: rgba(255, 255, 255, 0.05);
} }
.invite-banner {
background: var(--accent);
color: white;
padding: 0.75rem 0;
text-align: center;
font-weight: 600;
font-size: 0.9rem;
position: relative;
z-index: 2000;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.btn-banner {
background: white;
color: var(--accent);
padding: 4px 12px;
border-radius: 6px;
text-decoration: none;
font-size: 0.8rem;
font-weight: 700;
margin-left: 12px;
cursor: pointer;
border: none;
transition: transform 0.2s;
}
.btn-banner:hover {
transform: scale(1.05);
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -19,7 +49,7 @@
} }
body { body {
font-family: 'Outfit', sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
background-color: var(--bg); background-color: var(--bg);
color: var(--text); color: var(--text);
line-height: 1.6; line-height: 1.6;
@@ -285,6 +315,64 @@ footer {
transform: translateY(0); transform: translateY(0);
} }
/* --- Legal Pages --- */
.legal-content {
max-width: 800px;
margin: 8rem auto 4rem auto;
padding: 0 2rem;
}
.legal-card {
background: var(--card);
padding: 3rem;
border-radius: 24px;
border: 1px solid var(--glass-border);
}
.legal-card h1 {
font-size: 2.5rem;
margin-bottom: 2rem;
text-align: center;
}
.legal-card h2 {
font-size: 1.25rem;
margin-top: 1rem;
margin-bottom: 0.5rem;
color: var(--accent);
}
.legal-card p {
color: var(--text-muted);
margin-bottom: 0.5rem;
font-size: 0.95rem;
}
/* Reset global section padding for legal sections specifically */
.legal-card section {
padding: 0 !important;
margin-bottom: 0.75rem;
}
/* --- Join Page Specifics --- */
.join-card {
max-width: 450px;
margin: 6rem auto;
text-align: center;
}
.room-badge {
display: inline-block;
background: rgba(99, 102, 241, 0.1);
color: var(--accent);
padding: 0.5rem 1rem;
border-radius: 99px;
font-size: 0.8rem;
font-weight: 700;
margin-bottom: 1rem;
border: 1px solid rgba(99, 102, 241, 0.2);
}
@media (max-width: 768px) { @media (max-width: 768px) {
.hero-grid, .step { .hero-grid, .step {
grid-template-columns: 1fr; grid-template-columns: 1fr;
@@ -299,4 +387,7 @@ footer {
.nav-links { .nav-links {
display: none; display: none;
} }
.legal-card {
padding: 1.5rem;
}
} }