Enhance forest scene with mid bamboo layer, butterflies, click confetti, and demo scene polish

This commit is contained in:
Timo
2026-07-11 06:08:12 +02:00
parent 21383b8b9c
commit 04df35bbe6
12 changed files with 1060 additions and 182 deletions
+16
View File
@@ -18,6 +18,14 @@
# # Clean URLs support (resolves /join to join.html, etc.)
# try_files {path} {path}.html {path}/
# file_server
#
# # Serve the themed 404 page for unknown URLs (without this block,
# # Caddy returns an empty 404 response)
# handle_errors {
# @notfound expression {err.status_code} == 404
# rewrite @notfound /404.html
# file_server
# }
# }
#
# syncserver.koalastuff.net {
@@ -57,6 +65,14 @@ sync.koalastuff.net {
try_files {path} {path}.html {path}/
file_server
# Serve the themed 404 page for unknown URLs (without this block,
# Caddy returns an empty 404 response)
handle_errors {
@notfound expression {err.status_code} == 404
rewrite @notfound /404.html
file_server
}
# Static Caching for high-performance PageSpeed (1 year with validation)
@static {
file
+169 -8
View File
@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<title>KoalaSync</title>
<script src="theme-init.js"></script>
<style>
@font-face {
font-family: 'Twemoji Country Flags';
@@ -26,6 +27,7 @@
--accent-terracotta: oklch(0.62 0.14 45);
--danger: oklch(0.55 0.15 25);
--text-on-green: oklch(0.14 0.02 90);
--text-on-warm: oklch(0.16 0.025 45);
--bg: var(--surface-deep);
--card: var(--surface);
@@ -37,6 +39,25 @@
--error: var(--danger);
--radius: 12px;
--star: oklch(0.68 0.14 45);
color-scheme: dark;
}
html.theme-light {
--bg-base: oklch(0.965 0.012 105);
--surface: oklch(0.995 0.006 100);
--surface-deep: oklch(0.935 0.018 110);
--surface-alt: oklch(0.91 0.022 115);
--border-soft: oklch(0.28 0.035 140 / 0.10);
--border-strong: oklch(0.73 0.035 125);
--text-primary: oklch(0.21 0.03 140);
--text-secondary: oklch(0.43 0.03 135);
--accent-green: oklch(0.56 0.13 150);
--accent-green-hover: oklch(0.49 0.13 150);
--accent-terracotta: oklch(0.64 0.14 45);
--danger: oklch(0.57 0.17 25);
--text-on-green: oklch(0.16 0.03 140);
--text-on-warm: oklch(0.18 0.03 45);
color-scheme: light;
}
body {
@@ -47,6 +68,16 @@
color: var(--text);
font-family: 'Twemoji Country Flags', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 14px;
background-image:
radial-gradient(circle at 12% -8%, oklch(0.68 0.13 150 / 0.12), transparent 42%),
radial-gradient(circle at 105% 12%, oklch(0.62 0.14 45 / 0.08), transparent 34%);
transition: background-color 0.25s ease, color 0.25s ease;
}
html.theme-light body {
background-image:
radial-gradient(circle at 8% -5%, oklch(0.72 0.11 145 / 0.24), transparent 40%),
radial-gradient(circle at 105% 5%, oklch(0.82 0.12 65 / 0.2), transparent 38%);
}
.header-row {
@@ -81,7 +112,7 @@
font-size: 10px;
color: var(--text-muted);
text-decoration: none;
opacity: 0.5;
opacity: 0.72;
transition: opacity 0.2s, color 0.2s;
}
.popup-version:hover {
@@ -113,7 +144,8 @@
cursor: pointer;
font-weight: 600;
border-radius: 8px;
transition: all 0.2s;
min-height: 36px;
transition: background-color 0.2s, color 0.2s, box-shadow 0.2s;
}
.tab-btn.active {
@@ -154,7 +186,7 @@
box-sizing: border-box;
background: var(--card);
border: 1px solid var(--border-strong);
color: white;
color: var(--text);
padding: 10px;
border-radius: 8px;
outline: none;
@@ -189,7 +221,7 @@
font-weight: 700;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
transition: background-color 0.2s, box-shadow 0.2s, transform 0.2s;
margin-top: 8px;
}
@@ -203,13 +235,44 @@
padding: 10px;
background: var(--border-strong);
border: none;
color: white;
color: var(--text);
font-weight: 600;
border-radius: 8px;
cursor: pointer;
margin-top: 8px;
}
button.secondary:hover {
background: oklch(0.38 0.035 125);
}
details > summary:focus-visible {
outline: 2px solid var(--accent-hover) !important;
outline-offset: 3px;
border-radius: 4px;
}
button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible {
outline: 2px solid var(--accent-hover);
outline-offset: 2px;
}
button:disabled {
cursor: not-allowed;
opacity: 0.48;
transform: none !important;
}
.copy-success {
background: var(--success) !important;
border-color: var(--success) !important;
color: var(--text-on-green) !important;
opacity: 1 !important;
}
/* Info Cards */
.info-card {
background: var(--card);
@@ -289,7 +352,7 @@
.toast-success { background: var(--success); color: var(--text-on-green); }
.toast-error { background: var(--error); color: white; }
.toast-info { background: var(--accent); color: var(--text-on-green); }
.toast-warning { background: var(--accent-terracotta); color: white; }
.toast-warning { background: var(--accent-terracotta); color: var(--text-on-warm); }
@keyframes toastSlideIn {
from { opacity: 0; transform: translateY(-20px); }
@@ -300,6 +363,59 @@
to { opacity: 0; transform: translateY(-10px); }
}
/* Eucalyptus confetti: tiny leaves bursting from the copy-invite
button (spawned by popup.js, mirrors the website's effect). The
outer span owns the flight path — fast burst that brakes at its
peak, then sinks — while the inner leaf rocks around its stem. */
.click-leaf {
position: fixed;
z-index: 3000;
margin: -5px 0 0 -4px;
pointer-events: none;
opacity: 0;
animation: leafBurst var(--leaf-dur, 1s) forwards;
will-change: transform, opacity;
}
.click-leaf i {
display: block;
width: 7px;
height: 10px;
border-radius: 0 70% 0 70%;
background: linear-gradient(135deg, oklch(0.68 0.13 150 / 0.95), oklch(0.5 0.1 145 / 0.85));
transform: rotate(var(--leaf-rot, 0deg));
animation: leafRock var(--flutter-dur, 0.55s) ease-in-out infinite alternate;
}
.click-leaf-sage i {
background: linear-gradient(135deg, oklch(0.75 0.09 140 / 0.9), oklch(0.58 0.08 145 / 0.8));
}
.click-leaf-amber i {
background: linear-gradient(135deg, oklch(0.68 0.14 55 / 0.95), oklch(0.55 0.13 40 / 0.85));
}
@keyframes leafBurst {
0% {
opacity: 0;
transform: translate(0, 0) scale(var(--leaf-scale, 1));
animation-timing-function: cubic-bezier(0.16, 0.84, 0.44, 1);
}
10% { opacity: 1; }
45% {
opacity: 1;
transform: translate(var(--leaf-x, 0), var(--leaf-y, -14px)) scale(var(--leaf-scale, 1));
animation-timing-function: cubic-bezier(0.55, 0.06, 0.68, 0.19);
}
100% {
opacity: 0;
transform: translate(calc(var(--leaf-x, 0) * 1.4), calc(var(--leaf-y, -14px) + 30px)) scale(calc(var(--leaf-scale, 1) * 0.92));
}
}
@keyframes leafRock {
from { transform: rotate(calc(var(--leaf-rot, 0deg) - 24deg)); }
to { transform: rotate(calc(var(--leaf-rot, 0deg) + 24deg)); }
}
@media (prefers-reduced-motion: reduce) {
.click-leaf { display: none; }
}
@keyframes fadeSlideIn {
from { opacity: 0; transform: translateX(5px); }
to { opacity: 1; transform: translateX(0); }
@@ -353,7 +469,7 @@
padding-top: 10px;
color: var(--text-muted);
font-size: 10px;
opacity: 0.6;
opacity: 0.78;
}
.popup-footer a {
color: var(--text-muted);
@@ -364,6 +480,42 @@
color: var(--accent);
opacity: 1;
}
/* Light theme polish: preserve the same depth hierarchy without
carrying dark-only inline colors into form controls and cards. */
html.theme-light .tabs,
html.theme-light .info-card,
html.theme-light details.form-group,
html.theme-light .form-group {
box-shadow: 0 8px 24px oklch(0.22 0.035 140 / 0.07);
}
html.theme-light input,
html.theme-light select,
html.theme-light option {
color: var(--text) !important;
background-color: var(--surface) !important;
}
html.theme-light button.secondary {
background: var(--surface-alt);
color: var(--text);
border: 1px solid var(--border-strong);
}
html.theme-light button.secondary:hover {
background: oklch(0.86 0.035 125);
}
html.theme-light .mock-card,
html.theme-light .peer-item {
border-color: var(--border-strong);
}
html.theme-light .slider:before {
background-color: oklch(0.62 0.025 125);
}
html.theme-light input:checked + .slider:before {
background-color: white;
}
html.theme-light .popup-footer {
border-top-color: var(--border-soft);
}
.feature-hint {
display: inline-block;
width: 8px;
@@ -466,7 +618,7 @@
<label title="Share this link with friends so they can join" data-i18n="LABEL_INVITE_LINK" data-i18n-title="LABEL_INVITE_LINK_TOOLTIP">Invite Link</label>
<div class="invite-box">
<input type="text" id="inviteLink" readonly>
<button id="copyInvite" class="secondary">📋</button>
<button id="copyInvite" class="secondary" title="Copy invite link" aria-label="Copy invite link">📋</button>
</div>
</div>
@@ -561,6 +713,15 @@
<!-- Settings Tab -->
<div id="tab-settings" class="tab-content">
<div class="form-group" style="display: flex; align-items: center; justify-content: space-between; background: var(--card); padding: 10px; border-radius: 8px; margin-bottom: 12px; border: 1px solid var(--border-strong);">
<label id="themeLabel" style="margin-bottom: 0;" title="Follow the system theme or choose a fixed appearance">Appearance</label>
<select id="themeSelector" aria-label="Appearance" style="width: 165px; background: var(--bg); border: 1px solid var(--border-strong); color: var(--text); padding: 6px 10px; border-radius: 8px; font-size: 13px; cursor: pointer; outline: none; font-family: inherit;">
<option id="themeOptionSystem" value="system">💻 System</option>
<option id="themeOptionDark" value="dark">🌙 Dark</option>
<option id="themeOptionLight" value="light">☀️ Light</option>
</select>
</div>
<div class="form-group" style="display: flex; align-items: center; justify-content: space-between; background: var(--card); padding: 10px; border-radius: 8px; margin-bottom: 12px; border: 1px solid var(--border-strong);">
<label style="margin-bottom: 0;" title="Username helps others identify you." data-i18n="LABEL_USERNAME" data-i18n-title="LABEL_USERNAME_TOOLTIP">Your Username</label>
<input type="text" id="username" data-i18n-placeholder="PLACEHOLDER_USERNAME" placeholder="Anonymous Koala" maxlength="20" style="width: 150px;">
+83 -12
View File
@@ -64,6 +64,7 @@ const elements = {
autoCopyInvite: document.getElementById('autoCopyInvite'),
cancelLobbyBtn: document.getElementById('cancelLobbyBtn'),
langSelector: document.getElementById('langSelector'),
themeSelector: document.getElementById('themeSelector'),
audioSettingsLink: document.getElementById('audioSettingsLink'),
settingsSupportLink: document.getElementById('settingsSupportLink'),
@@ -132,6 +133,29 @@ function openAudioSettingsPage() {
chrome.tabs.create({ url: chrome.runtime.getURL('audio-options.html') });
}
function localizeThemeSelector(locale) {
const labels = {
de: ['Darstellung', 'System', 'Dunkel', 'Hell'], fr: ['Apparence', 'Système', 'Sombre', 'Clair'],
es: ['Apariencia', 'Sistema', 'Oscuro', 'Claro'], it: ['Aspetto', 'Sistema', 'Scuro', 'Chiaro'],
nl: ['Weergave', 'Systeem', 'Donker', 'Licht'], pl: ['Wygląd', 'System', 'Ciemny', 'Jasny'],
pt: ['Aparência', 'Sistema', 'Escuro', 'Claro'], 'pt-BR': ['Aparência', 'Sistema', 'Escuro', 'Claro'],
tr: ['Görünüm', 'Sistem', 'Koyu', 'Açık'], ru: ['Оформление', 'Системная', 'Тёмная', 'Светлая'],
ja: ['外観', 'システム', 'ダーク', 'ライト'], ko: ['테마', '시스템', '다크', '라이트'],
zh: ['外观', '跟随系统', '深色', '浅色'], uk: ['Вигляд', 'Системна', 'Темна', 'Світла'],
en: ['Appearance', 'System', 'Dark', 'Light']
};
const [label, system, dark, light] = labels[locale] || labels.en;
const labelEl = document.getElementById('themeLabel');
const systemEl = document.getElementById('themeOptionSystem');
const darkEl = document.getElementById('themeOptionDark');
const lightEl = document.getElementById('themeOptionLight');
if (labelEl) labelEl.textContent = label;
if (systemEl) systemEl.textContent = `💻 ${system}`;
if (darkEl) darkEl.textContent = `🌙 ${dark}`;
if (lightEl) lightEl.textContent = `☀️ ${light}`;
if (elements.themeSelector) elements.themeSelector.setAttribute('aria-label', label);
}
async function updateFeatureHints() {
const data = await chrome.storage.sync.get(['dismissedHints']);
const dismissed = Array.isArray(data.dismissedHints) ? data.dismissedHints : [];
@@ -204,7 +228,7 @@ function setRoomRefreshCooldown() {
async function init() {
// Local-only by design — settings and room credentials never come from
// storage.sync (only onboardingComplete + dismissedHints live there).
const localData = await chrome.storage.local.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'username', 'filterNoise', 'autoSyncNextEpisode', 'sendTabTitle', 'mediaTitlePrivacyMode', 'titlePrivacyMode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite', 'locale', 'audioSettings', 'activeTab']);
const localData = await chrome.storage.local.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'username', 'filterNoise', 'autoSyncNextEpisode', 'sendTabTitle', 'mediaTitlePrivacyMode', 'titlePrivacyMode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite', 'locale', 'audioSettings', 'activeTab', 'themeMode']);
let activeLang = localData.locale;
if (!activeLang) {
@@ -214,8 +238,10 @@ async function init() {
await loadLocale(activeLang);
translateDOM();
localizeThemeSelector(activeLang);
if (elements.langSelector) elements.langSelector.value = activeLang;
if (elements.themeSelector) elements.themeSelector.value = ['dark', 'light'].includes(localData.themeMode) ? localData.themeMode : 'system';
let username = localData.username;
if (!username) {
@@ -538,7 +564,7 @@ function updateLastActionUI(state, peers) {
peerItem.style.cssText = `display:flex; flex-direction:column; align-items:center; opacity: ${isAcked ? 1 : 0.6};`;
const dot = document.createElement('div');
dot.style.cssText = `width:18px; height:18px; border-radius:50%; background:${color}; color:white; display:flex; align-items:center; justify-content:center; font-size:9px; font-weight:bold; margin-bottom:1px;`;
dot.style.cssText = `width:18px; height:18px; border-radius:50%; background:${color}; color:${isAcked ? 'var(--text-on-green)' : 'var(--text-primary)'}; display:flex; align-items:center; justify-content:center; font-size:9px; font-weight:bold; margin-bottom:1px;`;
dot.textContent = icon;
const nameSpan = document.createElement('span');
@@ -630,7 +656,10 @@ function renderEmpty(container, type) {
copyBtn.title = getMessage('BTN_COPY_INVITE_TOOLTIP');
copyBtn.addEventListener('click', () => {
navigator.clipboard.writeText(elements.inviteLink.value)
.then(() => showToast(getMessage('TOAST_INVITE_COPIED'), 'success', 2000))
.then(() => {
showToast(getMessage('TOAST_INVITE_COPIED'), 'success', 2000);
spawnLeafBurst(copyBtn);
})
.catch(() => showToast(getMessage('TOAST_COPY_FAILED'), 'error'));
});
wrapper.appendChild(copyBtn);
@@ -725,7 +754,7 @@ function updatePeerList(peers) {
// Host Control Mode: show "Solo" badge for peers watching on their own.
if (typeof p === 'object' && p.desynced) {
const solo = document.createElement('span');
solo.style.cssText = 'font-size:10px; color:#fff; background:#c96736; padding:2px 6px; border-radius:6px; font-weight:600;';
solo.style.cssText = 'font-size:10px; color:var(--text-on-warm); background:var(--accent-terracotta); padding:2px 6px; border-radius:6px; font-weight:700;';
const soloText = getMessage('BADGE_DESYNCED') || 'Solo';
solo.textContent = soloText;
solo.title = getMessage('TOOLTIP_PEER_DESYNCED') || soloText;
@@ -742,7 +771,7 @@ function updatePeerList(peers) {
const isController = !isOwner && hcmControllers.includes(pId);
if (isOwner || isController) {
const roleBadge = document.createElement('span');
roleBadge.style.cssText = 'font-size:10px; color:#fff; background:var(--accent); padding:2px 6px; border-radius:6px; font-weight:600;';
roleBadge.style.cssText = 'font-size:10px; color:var(--text-on-green); background:var(--accent); padding:2px 6px; border-radius:6px; font-weight:700;';
roleBadge.textContent = isOwner ? (getMessage('BADGE_HOST') || 'Host') : (getMessage('BADGE_CONTROLLER') || 'Controller');
rightGroup.appendChild(roleBadge);
}
@@ -755,7 +784,7 @@ function updatePeerList(peers) {
btn.title = revoke ? (getMessage('BTN_REVOKE_CONTROL') || 'Revoke') : (getMessage('BTN_GIVE_CONTROL') || 'Give control');
btn.addEventListener('mouseenter', () => {
btn.style.background = revoke ? 'var(--text-muted)' : 'var(--accent)';
btn.style.color = '#fff';
btn.style.color = 'var(--text-on-green)';
});
btn.addEventListener('mouseleave', () => {
btn.style.background = 'transparent';
@@ -1273,6 +1302,14 @@ if (elements.autoCopyInvite) {
});
}
if (elements.themeSelector) {
elements.themeSelector.addEventListener('change', () => {
const themeMode = elements.themeSelector.value;
window.koalaTheme?.setMode(themeMode);
chrome.storage.local.set({ themeMode });
});
}
if (elements.audioSettingsLink) {
elements.audioSettingsLink.addEventListener('click', async (event) => {
event.preventDefault();
@@ -1305,6 +1342,7 @@ if (elements.langSelector) {
await chrome.storage.local.set({ locale: selectedLang });
await loadLocale(selectedLang);
translateDOM();
localizeThemeSelector(selectedLang);
configureFooterLinks();
await updateFeatureHints();
@@ -1389,6 +1427,39 @@ function showToast(message, type = 'info', duration = 3000) {
setTimeout(() => toast.remove(), duration);
}
// Eucalyptus confetti: a few tiny leaves burst from the given element
// (matches the website's copy-invite moment). Subtle by design — few,
// tiny, short-lived — and skipped entirely under reduced motion.
function spawnLeafBurst(sourceEl) {
if (!sourceEl || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const rect = sourceEl.getBoundingClientRect();
if (!rect.width) return;
const originY = rect.top + rect.height / 2;
const count = 4 + Math.floor(Math.random() * 3);
for (let i = 0; i < count; i++) {
const leaf = document.createElement('span');
// Three tones of the palette: deep green, sage, a dash of amber
const tone = Math.random();
leaf.className = 'click-leaf' + (tone < 0.25 ? ' click-leaf-amber' : tone < 0.55 ? ' click-leaf-sage' : '');
// Fan out upward to a burst peak spread across the button's width;
// the keyframes brake there and let each leaf sink while it rocks.
const angle = (-90 + (Math.random() - 0.5) * 150) * Math.PI / 180;
const dist = 18 + Math.random() * 24;
const dur = 0.85 + Math.random() * 0.4;
leaf.style.left = (rect.left + 4 + Math.random() * Math.max(1, rect.width - 8)) + 'px';
leaf.style.top = (originY - rect.height * 0.2) + 'px';
leaf.style.setProperty('--leaf-x', (Math.cos(angle) * dist).toFixed(1) + 'px');
leaf.style.setProperty('--leaf-y', (Math.sin(angle) * dist).toFixed(1) + 'px');
leaf.style.setProperty('--leaf-rot', ((Math.random() - 0.5) * 160).toFixed(0) + 'deg');
leaf.style.setProperty('--leaf-scale', (0.55 + Math.random() * 0.55).toFixed(2));
leaf.style.setProperty('--leaf-dur', dur.toFixed(2) + 's');
leaf.style.setProperty('--flutter-dur', (0.35 + Math.random() * 0.3).toFixed(2) + 's');
leaf.appendChild(document.createElement('i'));
document.body.appendChild(leaf);
setTimeout(() => leaf.remove(), dur * 1000 + 150);
}
}
function showError(msg) {
if (!elements.roomError) return;
const currentToken = ++errorToken;
@@ -1775,13 +1846,12 @@ elements.copyInvite.addEventListener('click', () => {
navigator.clipboard.writeText(elements.inviteLink.value).then(() => {
const original = elements.copyInvite.textContent;
elements.copyInvite.textContent = '✓';
elements.copyInvite.style.background = 'var(--success)';
elements.copyInvite.style.color = 'white';
elements.copyInvite.classList.add('copy-success');
showToast(getMessage('TOAST_INVITE_COPIED'), 'success', 2000);
spawnLeafBurst(elements.copyInvite);
setTimeout(() => {
elements.copyInvite.textContent = original;
elements.copyInvite.style.background = '';
elements.copyInvite.style.color = '';
elements.copyInvite.classList.remove('copy-success');
}, 2000);
}).catch(() => {
showToast(getMessage('TOAST_COPY_FAILED'), 'error');
@@ -1793,11 +1863,12 @@ if (elements.syncTabCopyInvite) {
navigator.clipboard.writeText(elements.inviteLink.value).then(() => {
const original = elements.syncTabCopyInvite.textContent;
elements.syncTabCopyInvite.textContent = '✓';
elements.syncTabCopyInvite.style.color = 'var(--success)';
elements.syncTabCopyInvite.classList.add('copy-success');
showToast(getMessage('TOAST_INVITE_COPIED'), 'success', 2000);
spawnLeafBurst(elements.syncTabCopyInvite);
setTimeout(() => {
elements.syncTabCopyInvite.textContent = original;
elements.syncTabCopyInvite.style.color = '';
elements.syncTabCopyInvite.classList.remove('copy-success');
}, 2000);
});
});
+29
View File
@@ -0,0 +1,29 @@
(() => {
const systemTheme = window.matchMedia('(prefers-color-scheme: light)');
let mode = 'system';
const normalize = value => ['system', 'dark', 'light'].includes(value) ? value : 'system';
const apply = value => {
mode = normalize(value);
const light = mode === 'light' || (mode === 'system' && systemTheme.matches);
document.documentElement.classList.toggle('theme-light', light);
document.documentElement.dataset.theme = light ? 'light' : 'dark';
document.documentElement.style.colorScheme = light ? 'light' : 'dark';
};
apply('system');
window.koalaTheme = {
getMode: () => mode,
setMode: apply
};
systemTheme.addEventListener('change', () => {
if (mode === 'system') apply('system');
});
if (!globalThis.chrome?.storage?.local) return;
chrome.storage.local.get(['themeMode'], data => apply(data.themeMode));
chrome.storage.onChanged.addListener((changes, area) => {
if (area === 'local' && changes.themeMode) apply(changes.themeMode.newValue);
});
})();
+146
View File
@@ -0,0 +1,146 @@
<!DOCTYPE html>
<html lang="en" class="lang-en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 Page Not Found | KoalaSync</title>
<!-- Absolute asset paths: Caddy rewrites any missing URL (also nested ones
like /foo/bar) to this file, so relative paths would break. -->
<link rel="preload" href="/style.min.css" as="style">
<link rel="stylesheet" href="/style.min.css">
<link rel="icon" type="image/webp" href="/assets/NewLogoIcon_64.webp">
<meta name="description" content="This page could not be found.">
<meta name="robots" content="noindex, follow">
<!-- Mobile Browser Theme Styling -->
<meta name="theme-color" content="#10190e">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<script src="/lang-init.min.js"></script>
</head>
<body>
<div class="bg-nature" aria-hidden="true">
<div class="bg-dusk-tint"></div>
<div class="bg-depth-day">
<div class="bg-godrays">
<span class="godray godray-1"></span>
<span class="godray godray-2"></span>
<span class="godray godray-3"></span>
</div>
<div class="bg-canopy"></div>
<span class="canopy-arc canopy-arc-1"></span>
<span class="canopy-arc canopy-arc-2"></span>
<span class="canopy-arc canopy-arc-3"></span>
<span class="canopy-arc canopy-arc-4"></span>
</div>
<div id="bamboo-far">
<span class="bamboo-stalk far-1"></span>
<span class="bamboo-stalk far-2"></span>
<span class="bamboo-stalk far-3"></span>
<span class="bamboo-stalk far-4"></span>
<span class="bamboo-stalk far-5"></span>
</div>
<div id="bamboo-near">
<div class="bamboo-stalk near-1">
<i class="bamboo-node node-1"></i>
<i class="bamboo-node node-2"></i>
<i class="bamboo-node node-3"></i>
<i class="bamboo-leaf leaf-1"></i>
<i class="bamboo-leaf leaf-2"></i>
</div>
<div class="bamboo-stalk near-2">
<i class="bamboo-node node-1"></i>
<i class="bamboo-node node-2"></i>
<i class="bamboo-node node-3"></i>
<i class="bamboo-leaf leaf-1"></i>
</div>
<div class="bamboo-stalk near-3">
<i class="bamboo-node node-1"></i>
<i class="bamboo-node node-2"></i>
<i class="bamboo-node node-3"></i>
<i class="bamboo-leaf leaf-1"></i>
</div>
</div>
<span class="fall-leaf fall-leaf-1"><i></i></span>
<span class="fall-leaf fall-leaf-2"><i></i></span>
<span class="fall-leaf fall-leaf-3"><i></i></span>
<span class="fall-leaf fall-leaf-4"><i></i></span>
<span class="fall-leaf fall-leaf-5"><i></i></span>
<span class="fall-leaf fall-leaf-6"><i></i></span>
<span class="fall-leaf fall-leaf-7"><i></i></span>
<span class="fall-leaf fall-leaf-8"><i></i></span>
<span class="bg-grass bg-grass-1"></span>
<span class="bg-grass bg-grass-2"></span>
<span class="bg-grass bg-grass-3"></span>
<span class="bg-grass bg-grass-4"></span>
<div class="bg-depth-dusk">
<div class="bg-mist bg-mist-1"></div>
<div class="bg-mist bg-mist-2"></div>
<div class="bg-horizon"></div>
<span class="firefly-wrap fw-1"><span class="firefly"></span></span>
<span class="firefly-wrap fw-2"><span class="firefly"></span></span>
<span class="firefly-wrap fw-3"><span class="firefly"></span></span>
<span class="firefly-wrap fw-4"><span class="firefly"></span></span>
<span class="firefly-wrap fw-5"><span class="firefly"></span></span>
<span class="firefly-wrap fw-6"><span class="firefly"></span></span>
</div>
<div class="bg-light-sweep"></div>
<div class="bg-grain"></div>
</div>
<nav>
<div class="container nav-content">
<a href="/" class="logo-area" style="text-decoration: none;">
<img src="/assets/NewLogoIcon.webp" alt="KoalaSync Logo" width="40" height="40">
<span>KoalaSync</span>
</a>
<div class="nav-links">
<a href="/" 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>
</nav>
<main class="legal-content">
<div class="legal-card" data-reveal style="padding: 2.5rem 2rem; text-align: center;">
<div style="display: flex; justify-content: center;">
<img src="/assets/KoalaSearching.webp" alt="A cute koala looking through a telescope, searching for the missing page" class="legal-mascot" width="180" height="180">
</div>
<h1 style="margin-bottom: 0.5rem;">404</h1>
<p style="text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.8rem; border-bottom: 1px solid var(--glass-border); padding-bottom: 1.5rem; margin-bottom: 1.5rem;">
Page not found
</p>
<p style="max-width: 34rem; margin: 0 auto;">
This page wandered off into the bamboo forest. Our koala is still looking for it — in the meantime, head back to safety.
</p>
<div style="display: flex; justify-content: center; gap: 1rem; margin-top: 2rem; flex-wrap: wrap;">
<a href="/" class="btn btn-primary">
Back to home
</a>
<a href="https://github.com/Shik3i/KoalaSync/issues" target="_blank" rel="noopener" class="btn btn-secondary">
Report a broken link
</a>
</div>
</div>
</main>
<footer>
<div class="container">
<p>&copy; 2026 KoalaSync. Open source under the MIT License.</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>
</div>
</div>
</footer>
<script src="/app.min.js"></script>
</body>
</html>
+7
View File
@@ -75,6 +75,13 @@ sync.koalastuff.net {
file_server
encode zstd gzip
# Themed 404 page (Caddy returns an empty 404 without this)
handle_errors {
@notfound expression {err.status_code} == 404
rewrite @notfound /404.html
file_server
}
@static {
file
path *.ico *.css *.js *.png *.svg *.webp *.avif
+91 -4
View File
@@ -184,6 +184,19 @@ document.addEventListener('DOMContentLoaded', () => {
// Scroll Reveal Logic (IntersectionObserver for performance)
const revealElements = document.querySelectorAll('[data-reveal]');
// Hash navigation must never land on an intentionally transparent state.
// Reveal the destination before the smooth scroll begins; the observer
// continues to animate sections reached through ordinary scrolling.
const revealHashTarget = () => {
const id = decodeURIComponent(window.location.hash.slice(1));
const target = id ? document.getElementById(id) : null;
if (!target) return;
if (target.matches('[data-reveal]')) target.classList.add('revealed');
target.querySelectorAll('[data-reveal]').forEach(el => el.classList.add('revealed'));
};
revealHashTarget();
window.addEventListener('hashchange', revealHashTarget);
if ('IntersectionObserver' in window) {
const revealObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
@@ -193,7 +206,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
}, {
rootMargin: '0px 0px -30px 0px',
rootMargin: '0px 0px 12% 0px',
threshold: 0.05
});
@@ -231,6 +244,7 @@ document.addEventListener('DOMContentLoaded', () => {
// value crossfades the scene from sunlit canopy into misty dusk, and on
// pointer devices the layers tilt slightly toward the cursor.
const bambooFar = document.getElementById('bamboo-far');
const bambooMid = document.getElementById('bamboo-mid');
const bambooNear = document.getElementById('bamboo-near');
const forestScene = document.querySelector('.bg-nature');
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
@@ -241,6 +255,7 @@ document.addEventListener('DOMContentLoaded', () => {
const fireflies = Array.from(document.querySelectorAll('.firefly-wrap'), (el) => ({ el, x: 0, y: 0 }));
const canHover = window.matchMedia('(hover: hover)').matches;
let mouseX = 0;
let mouseY = 0;
let pointerX = -9999;
let pointerY = -9999;
let depthStep = '';
@@ -251,9 +266,16 @@ document.addEventListener('DOMContentLoaded', () => {
const doc = document.documentElement;
const maxScroll = Math.max(1, doc.scrollHeight - window.innerHeight);
const depth = Math.min(1, window.scrollY / maxScroll);
const shift = window.innerHeight * 0.14;
bambooFar.style.transform = `translate(${(-mouseX * 5).toFixed(1)}px, ${(depth * shift).toFixed(1)}px)`;
bambooNear.style.transform = `translate(${(mouseX * 11).toFixed(1)}px, ${(-depth * shift).toFixed(1)}px)`;
// Couple the forest directly to the scroll distance so it visibly
// travels down instead of being overpowered by upward-moving content.
const shift = Math.min(window.innerHeight * 0.55, window.scrollY * 0.14);
// All bamboo layers travel in the same vertical direction. Depth
// comes from their different speeds; reversing one layer makes
// the forest look mechanically disconnected while scrolling.
bambooFar.style.transform = `translate(${(-mouseX * 5).toFixed(1)}px, ${(shift * 0.42).toFixed(1)}px)`;
bambooNear.style.transform = `translate(${(mouseX * 11).toFixed(1)}px, ${shift.toFixed(1)}px)`;
if (bambooMid) bambooMid.style.transform = `translate(${(mouseX * 4).toFixed(1)}px, ${(shift * 0.7).toFixed(1)}px)`;
if (depthDay) depthDay.style.transform = `translate(${(-mouseX * 2.5).toFixed(1)}px, ${(mouseY * 1.5).toFixed(1)}px)`;
if (depthDay) depthDay.style.opacity = (1 - depth * 0.75).toFixed(3);
if (depthDusk) depthDusk.style.opacity = (0.35 + depth * 0.65).toFixed(3);
if (duskTint) duskTint.style.opacity = (depth * 0.85).toFixed(3);
@@ -316,6 +338,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (canHover) {
window.addEventListener('pointermove', (e) => {
mouseX = (e.clientX / window.innerWidth - 0.5) * 2;
mouseY = (e.clientY / window.innerHeight - 0.5) * 2;
pointerX = e.clientX;
pointerY = e.clientY;
requestForestFrame();
@@ -345,6 +368,68 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
// Eucalyptus click confetti: leaves burst from wherever the visitor
// presses. Buttons and other controls shed leaves along their whole
// width; anywhere else a small fan rises from the pointer itself. The
// hero walkthrough's scripted cursor calls burstFromElement directly so
// its fake clicks scatter leaves too. Rate-capped, pointer-events:none —
// it can never block a click or flood the DOM.
let burstFromElement = () => {};
if (!prefersReducedMotion) {
const spawnLeaf = (x, y) => {
const leaf = document.createElement('span');
// Three tones of the palette: deep green, sage, a dash of amber
const tone = Math.random();
leaf.className = 'click-leaf' + (tone < 0.25 ? ' click-leaf-amber' : tone < 0.55 ? ' click-leaf-sage' : '');
// Fan out upward to a burst peak; the keyframes brake there and
// let the leaf sink past it while the inner element rocks.
const angle = (-90 + (Math.random() - 0.5) * 150) * Math.PI / 180;
const dist = 26 + Math.random() * 38;
const dur = 0.9 + Math.random() * 0.5;
leaf.style.left = x + 'px';
leaf.style.top = y + 'px';
leaf.style.setProperty('--leaf-x', (Math.cos(angle) * dist).toFixed(1) + 'px');
leaf.style.setProperty('--leaf-y', (Math.sin(angle) * dist).toFixed(1) + 'px');
leaf.style.setProperty('--leaf-rot', ((Math.random() - 0.5) * 160).toFixed(0) + 'deg');
leaf.style.setProperty('--leaf-scale', (0.55 + Math.random() * 0.6).toFixed(2));
leaf.style.setProperty('--leaf-dur', dur.toFixed(2) + 's');
leaf.style.setProperty('--flutter-dur', (0.4 + Math.random() * 0.35).toFixed(2) + 's');
leaf.appendChild(document.createElement('i'));
document.body.appendChild(leaf);
setTimeout(() => leaf.remove(), dur * 1000 + 150);
};
burstFromElement = (el) => {
if (!el) return;
const r = el.getBoundingClientRect();
if (!r.width || r.bottom < 0 || r.top > window.innerHeight) return;
// Leaf count scales with the control's width so a wide demo card
// sheds a few more than a compact icon button, capped for subtlety.
const count = Math.max(4, Math.min(9, Math.round(r.width / 26)));
for (let i = 0; i < count; i++) {
const x = r.left + 4 + Math.random() * Math.max(1, r.width - 8);
// Spawn along the upper edge, like leaves knocked off the top
const y = r.top + r.height * (0.1 + Math.random() * 0.4);
spawnLeaf(x, y);
}
};
let lastBurst = 0;
document.addEventListener('pointerdown', (e) => {
if (e.button !== 0 || !e.isPrimary) return;
const now = Date.now();
if (now - lastBurst < 180) return;
lastBurst = now;
const control = e.target.closest('button, [role="button"], .btn, .mock-tab, select, .demo-progress');
if (control) {
burstFromElement(control);
} else {
const count = 4 + Math.floor(Math.random() * 3);
for (let i = 0; i < count; i++) spawnLeaf(e.clientX, e.clientY);
}
}, { passive: true });
}
// Invite Detection & Bridge
const checkInvite = () => {
const isJoinPage = window.location.pathname.includes('join');
@@ -1136,6 +1221,8 @@ document.addEventListener('DOMContentLoaded', () => {
await wait(180);
if (userTookOver || demoRunId !== myRunId) return false;
if (action) action(); else el.click();
// The scripted cursor's clicks scatter leaves like real ones
burstFromElement(el);
await wait(320 + (pause || 0));
return !userTookOver && demoRunId === myRunId;
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

+7 -4
View File
@@ -371,7 +371,8 @@ async function compile() {
{ src: 'impressum-de.html', dest: 'de/impressum.html' },
{ src: 'datenschutz-de.html', dest: 'de/datenschutz.html' },
{ src: 'impressum.html', dest: 'impressum.html' },
{ src: 'datenschutz.html', dest: 'datenschutz.html' }
{ src: 'datenschutz.html', dest: 'datenschutz.html' },
{ src: '404.html', dest: '404.html' }
];
for (const mapping of staticMappings) {
const src = path.join(websiteDir, mapping.src);
@@ -457,13 +458,15 @@ async function compile() {
let html = fs.readFileSync(filePath, 'utf8');
// 8a. Replace hashed asset refs and inject SRI (Subresource Integrity)
html = html.replace(/(<link\b[^>]*?\bhref=")((?:\.\.\/)*)style\.min\.css"/g, (m, before, prefix) => {
// The prefix is either any number of "../" segments or a single "/"
// (404.html must use absolute paths: it is served for arbitrary URLs).
html = html.replace(/(<link\b[^>]*?\bhref=")((?:\.\.\/)*\/?)style\.min\.css"/g, (m, before, prefix) => {
return `${before}${prefix}${styleName}" integrity="${styleSRI}" crossorigin="anonymous"`;
});
html = html.replace(/(<script\b[^>]*?\bsrc=")((?:\.\.\/)*)app\.min\.js"/g, (m, before, prefix) => {
html = html.replace(/(<script\b[^>]*?\bsrc=")((?:\.\.\/)*\/?)app\.min\.js"/g, (m, before, prefix) => {
return `${before}${prefix}${appName}" integrity="${appSRI}" crossorigin="anonymous"`;
});
html = html.replace(/(<script\b[^>]*?\bsrc=")((?:\.\.\/)*)lang-init\.min\.js"/g, (m, before, prefix) => {
html = html.replace(/(<script\b[^>]*?\bsrc=")((?:\.\.\/)*\/?)lang-init\.min\.js"/g, (m, before, prefix) => {
return `${before}${prefix}${langName}" integrity="${langSRI}" crossorigin="anonymous"`;
});
+124 -124
View File
@@ -3,31 +3,31 @@
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://sync.koalastuff.net/imprint</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>monthly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://sync.koalastuff.net/privacy</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>monthly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://sync.koalastuff.net/de/impressum</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>monthly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://sync.koalastuff.net/de/datenschutz</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>monthly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://sync.koalastuff.net/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -49,7 +49,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/de/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -71,7 +71,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/fr/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -93,7 +93,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/es/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -115,7 +115,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt-BR/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -137,7 +137,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ru/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -159,7 +159,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/it/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -181,7 +181,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pl/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -203,7 +203,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/tr/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -225,7 +225,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/nl/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -247,7 +247,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ja/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -269,7 +269,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ko/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -291,7 +291,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/zh/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -313,7 +313,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/uk/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -335,7 +335,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt/</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/"/>
@@ -357,7 +357,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</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/de/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
@@ -401,7 +401,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/fr/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
@@ -423,7 +423,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/es/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</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/pt-BR/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
@@ -467,7 +467,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ru/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
@@ -489,7 +489,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/it/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</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/pl/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
@@ -533,7 +533,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/tr/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
@@ -555,7 +555,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/nl/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</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/ja/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
@@ -599,7 +599,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ko/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
@@ -621,7 +621,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/zh/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</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/uk/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
@@ -665,7 +665,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt/alternatives</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives"/>
@@ -687,7 +687,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
@@ -709,7 +709,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/de/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</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/fr/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
@@ -753,7 +753,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/es/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
@@ -775,7 +775,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt-BR/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</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/ru/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
@@ -819,7 +819,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/it/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
@@ -841,7 +841,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pl/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</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/tr/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
@@ -885,7 +885,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/nl/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
@@ -907,7 +907,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ja/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</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/ko/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
@@ -951,7 +951,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/zh/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
@@ -973,7 +973,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/uk/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</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/pt/alternatives/teleparty</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/teleparty"/>
@@ -1017,7 +1017,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1039,7 +1039,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/de/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1061,7 +1061,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/fr/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1083,7 +1083,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/es/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1105,7 +1105,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt-BR/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1127,7 +1127,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ru/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1149,7 +1149,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/it/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1171,7 +1171,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pl/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1193,7 +1193,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/tr/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1215,7 +1215,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/nl/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1237,7 +1237,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ja/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1259,7 +1259,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ko/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1281,7 +1281,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/zh/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1303,7 +1303,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/uk/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1325,7 +1325,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt/alternatives/screen-sharing</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/screen-sharing"/>
@@ -1347,7 +1347,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1369,7 +1369,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/de/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1391,7 +1391,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/fr/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1413,7 +1413,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/es/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1435,7 +1435,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt-BR/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1457,7 +1457,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ru/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1479,7 +1479,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/it/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1501,7 +1501,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pl/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1523,7 +1523,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/tr/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1545,7 +1545,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/nl/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1567,7 +1567,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ja/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1589,7 +1589,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ko/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1611,7 +1611,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/zh/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1633,7 +1633,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/uk/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1655,7 +1655,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt/alternatives/watch2gether</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/watch2gether"/>
@@ -1677,7 +1677,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1699,7 +1699,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/de/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1721,7 +1721,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/fr/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1743,7 +1743,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/es/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1765,7 +1765,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt-BR/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1787,7 +1787,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ru/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1809,7 +1809,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/it/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1831,7 +1831,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pl/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1853,7 +1853,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/tr/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1875,7 +1875,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/nl/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1897,7 +1897,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ja/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1919,7 +1919,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ko/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1941,7 +1941,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/zh/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1963,7 +1963,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/uk/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -1985,7 +1985,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt/alternatives/scener</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/scener"/>
@@ -2007,7 +2007,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2029,7 +2029,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/de/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2051,7 +2051,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/fr/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2073,7 +2073,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/es/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2095,7 +2095,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt-BR/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2117,7 +2117,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ru/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2139,7 +2139,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/it/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2161,7 +2161,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pl/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2183,7 +2183,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/tr/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2205,7 +2205,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/nl/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2227,7 +2227,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ja/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2249,7 +2249,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ko/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2271,7 +2271,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/zh/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2293,7 +2293,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/uk/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2315,7 +2315,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt/alternatives/kosmi</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/kosmi"/>
@@ -2337,7 +2337,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2359,7 +2359,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/de/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2381,7 +2381,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/fr/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2403,7 +2403,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/es/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2425,7 +2425,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt-BR/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2447,7 +2447,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ru/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2469,7 +2469,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/it/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2491,7 +2491,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pl/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2513,7 +2513,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/tr/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2535,7 +2535,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/nl/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2557,7 +2557,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ja/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2579,7 +2579,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/ko/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2601,7 +2601,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/zh/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2623,7 +2623,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/uk/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
@@ -2645,7 +2645,7 @@
</url>
<url>
<loc>https://sync.koalastuff.net/pt/alternatives/twoseven</loc>
<lastmod>2026-07-10</lastmod>
<lastmod>2026-07-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/alternatives/twoseven"/>
+382 -30
View File
@@ -163,8 +163,8 @@ body {
#bamboo-far .bamboo-stalk {
position: absolute;
top: -20%;
height: 140%;
top: -70%;
height: 240%;
border-radius: 999px;
filter: blur(5.5px);
transition: filter 1.2s ease;
@@ -183,8 +183,8 @@ body {
#bamboo-far::after {
content: '';
position: absolute;
top: -20%;
height: 140%;
top: -70%;
height: 240%;
border-radius: 999px;
filter: blur(8px);
opacity: 0.28;
@@ -199,6 +199,71 @@ body {
.bg-nature.depth-mid #bamboo-far .bamboo-stalk { filter: blur(6.5px); }
.bg-nature.depth-deep #bamboo-far .bamboo-stalk { filter: blur(8px); }
/* (a2) Mid bamboo layer: sits between far and near with its own parallax
rate (app.js) and half the near layer's sway, so the three layers drift
apart subtly instead of moving as one sheet. Landing page only — the
static pages keep the lighter two-layer scene. */
#bamboo-mid {
position: absolute;
inset: 0;
will-change: transform;
}
#bamboo-mid .bamboo-stalk {
position: absolute;
top: -70%;
height: 240%;
border-radius: 999px;
filter: blur(2.5px);
transition: filter 1.2s ease;
/* Segment rings + a soft left-of-centre sheen give the stalks culm
structure without any extra elements; the blur keeps both faint. */
background:
repeating-linear-gradient(180deg,
transparent 0, transparent 84px,
oklch(0.46 0.075 145 / 0.28) 84px, oklch(0.46 0.075 145 / 0.28) 87px),
linear-gradient(90deg, transparent 14%, oklch(0.58 0.07 145 / 0.14) 38%, transparent 68%),
linear-gradient(180deg, oklch(0.36 0.06 145 / 0.45), oklch(0.25 0.045 140 / 0.2));
transform-origin: bottom center;
animation: bambooSwayHalf 13s ease-in-out infinite;
}
#bamboo-mid .mid-1 { left: 17%; width: 15px; animation-duration: 12s; }
#bamboo-mid .mid-2 { left: 31%; width: 9px; animation-delay: -5s; opacity: 0.55; }
#bamboo-mid .mid-3 { right: 15.5%; width: 17px; animation-duration: 14.5s; animation-delay: -8s; }
#bamboo-mid .mid-4 { right: 30%; width: 8px; animation-duration: 11.5s; animation-delay: -2.5s; opacity: 0.5; }
/* A few leaves sprout from the mid stalks (pseudo-elements, so they ride
the stalk's sway for free). The slim centre stalks stay bare. */
#bamboo-mid .bamboo-stalk::before,
#bamboo-mid .bamboo-stalk::after {
content: '';
position: absolute;
width: 24px;
height: 11px;
border-radius: 0 100% 0 100%;
background: oklch(0.5 0.1 145 / 0.32);
}
#bamboo-mid .mid-1::before { top: 31%; left: 13px; transform: rotate(20deg); }
#bamboo-mid .mid-1::after { top: 55%; left: -26px; transform: rotate(-158deg); width: 20px; height: 9px; }
#bamboo-mid .mid-3::before { top: 26%; left: -27px; transform: rotate(-156deg); }
#bamboo-mid .mid-3::after { top: 49%; left: 15px; transform: rotate(24deg); width: 21px; height: 10px; }
#bamboo-mid .mid-2::before,
#bamboo-mid .mid-2::after,
#bamboo-mid .mid-4::before,
#bamboo-mid .mid-4::after {
display: none;
}
.bg-nature.depth-mid #bamboo-mid .bamboo-stalk { filter: blur(3.5px); }
.bg-nature.depth-deep #bamboo-mid .bamboo-stalk { filter: blur(4.5px); }
@keyframes bambooSwayHalf {
0%, 100% { transform: rotate(0); }
50% { transform: rotate(calc(var(--sway, 1.2deg) * 0.5)); }
}
/* (b) Near bamboo layer: sharp stalks with nodes and leaves, gentle sway */
#bamboo-near {
position: absolute;
@@ -208,10 +273,14 @@ body {
#bamboo-near .bamboo-stalk {
position: absolute;
top: -20%;
height: 140%;
top: -70%;
height: 240%;
border-radius: 999px;
background: linear-gradient(180deg, oklch(0.4 0.07 145 / 0.55), oklch(0.26 0.05 140 / 0.25));
/* Vertical sheen over the culm gradient: reads as light from the
canopy catching the rounded stalk instead of a flat strip. */
background:
linear-gradient(90deg, transparent 12%, oklch(0.62 0.08 145 / 0.18) 36%, transparent 64%),
linear-gradient(180deg, oklch(0.4 0.07 145 / 0.55), oklch(0.26 0.05 140 / 0.25));
transform-origin: bottom center;
animation: bambooSway 10s ease-in-out infinite;
}
@@ -375,13 +444,31 @@ body {
.canopy-arc-3 { top: -4px; left: 61%; width: 46px; height: 14px; transform: rotate(-3deg); }
.canopy-arc-4 { top: -2px; left: 84%; width: 30px; height: 10px; transform: rotate(7deg); background: oklch(0.33 0.06 145 / 0.4); }
/* (f) Undergrowth: grass blades in the lower corners */
/* (f) Undergrowth: grass blades in the lower corners. Each blade carries a
shorter companion blade (::before) so the corners read as small tufts. */
.bg-grass {
position: absolute;
bottom: -4px;
transform-origin: bottom;
}
.bg-grass::before {
content: '';
position: absolute;
bottom: 0;
width: 72%;
height: 55%;
border-radius: inherit;
background: inherit;
opacity: 0.7;
transform-origin: bottom;
}
.bg-grass-1::before { left: -5px; transform: rotate(-13deg); }
.bg-grass-2::before { left: 4px; transform: rotate(11deg); }
.bg-grass-3::before { right: -5px; transform: rotate(12deg); }
.bg-grass-4::before { right: 4px; transform: rotate(-9deg); }
.bg-grass-1 { left: 1.5%; width: 6px; height: 52px; border-radius: 100% 0 0 0; background: oklch(0.38 0.07 145 / 0.4); transform: rotate(-9deg); }
.bg-grass-2 { left: 3.2%; width: 5px; height: 38px; border-radius: 100% 0 0 0; background: oklch(0.35 0.07 145 / 0.3); transform: rotate(-12deg); }
.bg-grass-3 { right: 2%; width: 6px; height: 56px; border-radius: 0 100% 0 0; background: oklch(0.4 0.07 145 / 0.4); transform: rotate(8deg); }
@@ -508,6 +595,83 @@ body {
100% { translate: -14px -18px; }
}
/* Day/night: fireflies belong to the dark theme. By daylight the same six
spots are visited by small butterflies instead — the wrapper (and with it
the cursor-shy push and the section greeting) is shared, only the inner
dot is redressed. Only animation-name is swapped so the per-firefly
duration/delay variety above keeps applying to the butterflies. */
html.theme-light .firefly {
position: relative;
width: 2px;
height: 9px;
border-radius: 1px;
background: oklch(0.35 0.05 140 / 0.8);
box-shadow: none;
opacity: 0.85;
animation-name: butterflyBob, fireflyWander;
}
html.theme-light .firefly::before,
html.theme-light .firefly::after {
content: '';
position: absolute;
top: -2px;
width: 7px;
height: 10px;
background: oklch(0.62 0.14 45 / 0.7);
animation: wingFlap 0.5s ease-in-out infinite alternate;
}
html.theme-light .firefly::before {
left: -6px;
border-radius: 90% 10% 60% 40%;
transform-origin: 100% 55%;
}
html.theme-light .firefly::after {
right: -6px;
border-radius: 10% 90% 40% 60%;
transform-origin: 0% 55%;
animation-name: wingFlapR;
}
/* Every other butterfly wears the sage of the canopy and flaps lazier */
html.theme-light .fw-2 .firefly::before,
html.theme-light .fw-2 .firefly::after,
html.theme-light .fw-4 .firefly::before,
html.theme-light .fw-4 .firefly::after,
html.theme-light .fw-6 .firefly::before,
html.theme-light .fw-6 .firefly::after {
background: oklch(0.52 0.12 150 / 0.55);
animation-duration: 0.62s;
}
@keyframes butterflyBob {
0%, 100% { transform: translateY(0) rotate(-4deg); }
50% { transform: translateY(-8px) rotate(5deg); }
}
@keyframes wingFlap {
from { transform: scaleX(1) rotate(-6deg); }
to { transform: scaleX(0.3) rotate(8deg); }
}
@keyframes wingFlapR {
from { transform: scaleX(1) rotate(6deg); }
to { transform: scaleX(0.3) rotate(-8deg); }
}
/* By day the dusk layer must not dim the butterflies with scroll depth
(the JS crossfade keeps writing inline opacity for the dark theme), and
the warm horizon glow steps back so it reads as haze, not dusk. */
html.theme-light .bg-depth-dusk {
opacity: 1 !important;
}
html.theme-light .bg-horizon {
background: radial-gradient(ellipse 60% 100% at 50% 100%, oklch(0.8 0.08 60 / 0.1), transparent 70%);
}
/* (j) Wandering light sweep */
.bg-light-sweep {
position: absolute;
@@ -525,6 +689,20 @@ body {
100% { transform: translate(20%, 20%) rotate(8deg); }
}
/* (j2) Corner vignette: pulls the eye toward the content and grounds the
scene — the forest darkens gently toward the edges like real depth. */
.bg-nature::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(ellipse 120% 90% at 50% 38%, transparent 55%, oklch(0.10 0.025 142 / 0.4) 100%);
}
html.theme-light .bg-nature::after {
background: radial-gradient(ellipse 120% 90% at 50% 38%, transparent 62%, oklch(0.78 0.03 120 / 0.3) 100%);
}
/* (k) Film grain (SVG feTurbulence, no external assets) */
.bg-grain {
position: absolute;
@@ -543,6 +721,7 @@ body {
.fall-leaf-8,
.bg-godrays,
.bg-light-sweep,
#bamboo-mid,
#bamboo-far .far-4,
#bamboo-far .far-5,
#bamboo-near .near-3 {
@@ -550,7 +729,67 @@ body {
}
}
/* (l) Click confetti: a small handful of eucalyptus leaves bursts from
wherever the visitor presses (spawned by app.js). Built like the falling
leaves above — the outer span owns the flight path (fast burst that
brakes at its peak, then sinks), the inner leaf rocks around its stem
the whole way down. Deliberately subtle: few, tiny, short-lived. */
.click-leaf {
position: fixed;
z-index: 3000;
margin: -6px 0 0 -4px;
pointer-events: none;
opacity: 0;
animation: leafBurst var(--leaf-dur, 1.1s) forwards;
will-change: transform, opacity;
}
.click-leaf i {
display: block;
width: 8px;
height: 12px;
border-radius: 0 70% 0 70%;
background: linear-gradient(135deg, oklch(0.62 0.12 150 / 0.95), oklch(0.45 0.09 145 / 0.85));
transform: rotate(var(--leaf-rot, 0deg));
animation: leafRock var(--flutter-dur, 0.6s) ease-in-out infinite alternate;
}
.click-leaf-sage i {
background: linear-gradient(135deg, oklch(0.72 0.09 140 / 0.9), oklch(0.55 0.08 145 / 0.8));
}
.click-leaf-amber i {
background: linear-gradient(135deg, oklch(0.68 0.14 55 / 0.95), oklch(0.55 0.13 40 / 0.85));
}
/* Two-phase flight: --leaf-x/--leaf-y is the burst PEAK; from there each
leaf loses momentum and sinks past it while fading. Per-segment timing
functions give the fast pop and the gentle fall. */
@keyframes leafBurst {
0% {
opacity: 0;
transform: translate(0, 0) scale(var(--leaf-scale, 1));
animation-timing-function: cubic-bezier(0.16, 0.84, 0.44, 1);
}
10% { opacity: 1; }
45% {
opacity: 1;
transform: translate(var(--leaf-x, 0), var(--leaf-y, -20px)) scale(var(--leaf-scale, 1));
animation-timing-function: cubic-bezier(0.55, 0.06, 0.68, 0.19);
}
100% {
opacity: 0;
transform: translate(calc(var(--leaf-x, 0) * 1.4), calc(var(--leaf-y, -20px) + 44px)) scale(calc(var(--leaf-scale, 1) * 0.92));
}
}
@keyframes leafRock {
from { transform: rotate(calc(var(--leaf-rot, 0deg) - 24deg)); }
to { transform: rotate(calc(var(--leaf-rot, 0deg) + 24deg)); }
}
@media (prefers-reduced-motion: reduce) {
.click-leaf { display: none; }
.bg-nature *,
.bg-nature *::before,
.bg-nature *::after {
@@ -771,6 +1010,7 @@ html.theme-light .bg-nature {
html.theme-light .bg-canopy,
html.theme-light .canopy-arc,
html.theme-light #bamboo-far .bamboo-stalk,
html.theme-light #bamboo-mid .bamboo-stalk,
html.theme-light #bamboo-near .bamboo-stalk,
html.theme-light .bg-grass {
opacity: 0.45;
@@ -1158,14 +1398,16 @@ html.theme-light .koala-speech-bubble::after {
font-size: 0.95rem;
}
.btn-primary {
background: var(--accent-green);
color: var(--text-on-green);
box-shadow: 0 10px 15px -3px var(--accent-glow);
background: linear-gradient(135deg, oklch(0.48 0.13 150), oklch(0.39 0.10 145));
color: #fff;
border: 1px solid oklch(0.72 0.11 95 / 0.24);
box-shadow: 0 12px 24px -8px oklch(0.38 0.11 148 / 0.65), 0 0 0 1px oklch(0.80 0.08 90 / 0.06) inset;
}
.btn-primary:hover {
background: linear-gradient(135deg, oklch(0.54 0.14 150), oklch(0.44 0.11 145));
transform: translateY(-2px);
box-shadow: 0 20px 25px -5px var(--accent-glow);
box-shadow: 0 18px 32px -8px oklch(0.38 0.11 148 / 0.75), 0 0 18px oklch(0.68 0.13 150 / 0.18);
}
.btn-firefox {
@@ -1615,6 +1857,19 @@ html.theme-light .koala-speech-bubble::after {
put white text on a colour get a darker shade (these beat inline styles). */
.hero-demo-scene {
--accent: oklch(0.80 0.10 150);
--demo-sky-top: oklch(0.22 0.055 152);
--demo-sky-mid: oklch(0.145 0.035 150);
--demo-sky-bottom: oklch(0.075 0.018 145);
--demo-glow: oklch(0.76 0.13 150 / 0.34);
--demo-stars: oklch(0.94 0.025 95);
--demo-moon: oklch(0.97 0.025 92);
--demo-mountain: oklch(0.18 0.055 148 / 0.9);
--demo-midground: oklch(0.22 0.065 145 / 0.88);
--demo-foreground: oklch(0.065 0.025 145);
--demo-card: oklch(0.18 0.032 142);
--demo-toolbar: oklch(0.12 0.025 142);
--demo-control-fade: oklch(0.055 0.015 145 / 0.94);
--demo-track: oklch(0.92 0.02 95 / 0.2);
}
.hero-demo-scene .mock-tab.active,
.hero-demo-scene .mock-btn-force,
@@ -1636,17 +1891,33 @@ html.theme-light .koala-speech-bubble::after {
html.theme-light .hero-demo-scene {
--accent: oklch(0.60 0.12 150);
--demo-sky-top: oklch(0.86 0.085 78);
--demo-sky-mid: oklch(0.76 0.095 112);
--demo-sky-bottom: oklch(0.54 0.10 150);
--demo-glow: oklch(0.92 0.13 72 / 0.44);
--demo-stars: oklch(0.98 0.025 90);
--demo-moon: oklch(0.99 0.025 88);
--demo-mountain: oklch(0.48 0.095 150 / 0.82);
--demo-midground: oklch(0.40 0.10 148 / 0.8);
--demo-foreground: oklch(0.24 0.075 145);
--demo-card: oklch(0.985 0.008 95);
--demo-toolbar: oklch(0.955 0.014 100);
--demo-control-fade: oklch(0.16 0.035 145 / 0.88);
--demo-track: oklch(0.98 0.01 95 / 0.34);
}
html.theme-light .hero-demo-scene .demo-tab-card {
background: #ffffff;
background: var(--demo-card);
border-color: oklch(0.20 0.025 140 / 0.1);
box-shadow: 0 18px 38px oklch(0.20 0.025 140 / 0.14);
box-shadow:
0 18px 38px oklch(0.20 0.025 140 / 0.16),
0 0 28px oklch(0.82 0.10 85 / 0.14),
inset 0 1px rgba(255, 255, 255, 0.9);
}
html.theme-light .hero-demo-scene .demo-tab-titlebar {
background: oklch(0.96 0.01 90);
background: var(--demo-toolbar);
border-bottom-color: oklch(0.88 0.015 90);
color: oklch(0.45 0.02 110);
}
@@ -2941,6 +3212,61 @@ footer a:hover {
transform: translateY(0);
}
html.theme-light .hero-demo-scene .demo-user-a,
html.theme-light .hero-demo-scene .demo-user-b {
color: oklch(0.36 0.105 150);
background: oklch(0.60 0.12 150 / 0.13);
}
html.theme-light .hero-demo-scene .extension-mockup {
--bg: oklch(0.935 0.018 110) !important;
--card: oklch(0.995 0.006 100) !important;
--accent: oklch(0.56 0.13 150) !important;
--accent-glow: oklch(0.56 0.13 150 / 0.22) !important;
--text: oklch(0.21 0.03 140) !important;
--text-muted: oklch(0.43 0.03 135) !important;
--success: oklch(0.56 0.13 150) !important;
--error: oklch(0.57 0.17 25) !important;
--glass-border: oklch(0.28 0.035 140 / 0.10) !important;
background: oklch(0.935 0.018 110) !important;
border-color: oklch(0.28 0.035 140 / 0.12);
box-shadow:
0 20px 42px oklch(0.20 0.025 140 / 0.16),
0 0 26px oklch(0.82 0.10 85 / 0.12);
}
html.theme-light .hero-demo-scene .mock-tabs,
html.theme-light .hero-demo-scene .mock-card,
html.theme-light .hero-demo-scene .mock-input {
background: oklch(0.995 0.006 100);
border-color: oklch(0.28 0.035 140 / 0.14);
color: oklch(0.21 0.03 140);
}
html.theme-light .hero-demo-scene .mock-peer-item {
background: oklch(0.28 0.035 140 / 0.035);
border-color: oklch(0.28 0.035 140 / 0.09);
}
html.theme-light .hero-demo-scene .mock-joined-room,
html.theme-light .hero-demo-scene .mock-status-pill {
background: oklch(0.56 0.13 150 / 0.10);
border-color: oklch(0.56 0.13 150 / 0.24);
}
html.theme-light .hero-demo-scene .mock-close-btn:hover {
color: oklch(0.21 0.03 140);
background: oklch(0.28 0.035 140 / 0.08);
}
/* A direct hash destination is content, not an entrance animation. Keep it
readable from the first paint even before app.js has initialized. */
section:target [data-reveal],
section:target[data-reveal] {
opacity: 1;
transform: translateY(0);
}
/* --- Legal Pages --- */
.legal-content {
@@ -3685,9 +4011,9 @@ footer {
}
.cross {
color: oklch(0.55 0.15 25);
font-weight: 500;
opacity: 0.8;
color: oklch(0.68 0.17 25);
font-weight: 600;
opacity: 1;
}
@media (max-width: 768px) {
@@ -3838,7 +4164,7 @@ footer {
display: inline-flex;
align-items: center;
vertical-align: super;
opacity: 0.6;
opacity: 0.82;
transition: opacity 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
@@ -4396,10 +4722,10 @@ header[id] {
position: absolute;
width: 310px;
width: 70cqw;
background: oklch(0.21 0.025 138);
background: var(--demo-card);
border: 1px solid rgba(255, 255, 255, 0.09);
border-radius: 14px;
box-shadow: 0 18px 36px rgba(0, 0, 0, 0.45);
box-shadow: 0 18px 36px rgba(0, 0, 0, 0.45), inset 0 1px rgba(255, 255, 255, 0.05);
overflow: hidden;
cursor: pointer;
user-select: none;
@@ -4471,7 +4797,7 @@ header[id] {
align-items: center;
gap: 8px;
padding: 6px 8px 6px 10px;
background: oklch(0.15 0.02 138);
background: var(--demo-toolbar);
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
font-size: 10.5px;
color: var(--text-muted);
@@ -4596,18 +4922,35 @@ header[id] {
.demo-video {
position: relative;
aspect-ratio: 16 / 9;
background: #020617;
background: var(--demo-sky-bottom);
overflow: hidden;
}
.demo-video::after {
content: '';
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.05),
inset 0 -24px 36px oklch(0.04 0.015 145 / 0.2);
}
html.theme-light .hero-demo-scene .demo-video::after {
box-shadow:
inset 0 0 0 1px oklch(0.30 0.04 145 / 0.13),
inset 0 18px 32px oklch(0.98 0.03 90 / 0.1),
inset 0 -22px 34px oklch(0.18 0.05 145 / 0.16);
}
.demo-video-art {
position: absolute;
inset: 0;
background:
radial-gradient(circle at 78% 22%, oklch(0.96 0.01 90 / 0.28) 0 4%, transparent 5.5%),
radial-gradient(120% 90% at 70% 115%, oklch(0.26 0.05 140) 0%, transparent 60%),
radial-gradient(100% 80% at 12% 112%, oklch(0.20 0.04 145) 0%, transparent 55%),
linear-gradient(160deg, oklch(0.27 0.035 130) 0%, oklch(0.15 0.02 138) 55%, #020617 100%);
radial-gradient(30% 42% at 76% 29%, var(--demo-glow), transparent 64%),
radial-gradient(90% 85% at 18% 5%, var(--demo-glow), transparent 66%),
linear-gradient(160deg, var(--demo-sky-top) 0%, var(--demo-sky-mid) 52%, var(--demo-sky-bottom) 100%);
transform-origin: 60% 40%;
animation:
demoKenBurns 9s ease-in-out infinite alternate,
@@ -4624,7 +4967,7 @@ header[id] {
width: 70%;
aspect-ratio: 1;
border-radius: 50%;
background: radial-gradient(circle, oklch(0.75 0.13 150 / 0.32), transparent 65%);
background: radial-gradient(circle, var(--demo-glow), transparent 65%);
animation: demoGlowDrift 7s ease-in-out infinite alternate;
animation-play-state: paused;
}
@@ -4718,7 +5061,7 @@ header[id] {
align-items: center;
gap: 8px;
padding: 14px 10px 8px;
background: linear-gradient(transparent, oklch(0.10 0.01 135 / 0.85));
background: linear-gradient(transparent, var(--demo-control-fade));
font-size: 9.5px;
color: oklch(0.88 0.015 90);
z-index: 1;
@@ -4737,7 +5080,7 @@ header[id] {
flex: 1;
height: 5px;
border-radius: 99px;
background: rgba(255, 255, 255, 0.18);
background: var(--demo-track);
cursor: pointer;
}
@@ -4925,6 +5268,15 @@ header[id] {
height: 100%;
}
.hero-demo-scene .film-stars { fill: var(--demo-stars) !important; }
.hero-demo-scene .film-moon {
fill: var(--demo-moon) !important;
filter: drop-shadow(0 0 6px var(--demo-moon));
}
.hero-demo-scene .film-mtn-scroll { fill: var(--demo-mountain) !important; }
.hero-demo-scene .film-mid-scroll > * { fill: var(--demo-midground) !important; }
.hero-demo-scene .film-fore-scroll > * { fill: var(--demo-foreground) !important; }
/* Stars: staggered twinkle so the sky feels alive */
.film-star {
transform-box: fill-box;
+6
View File
@@ -235,6 +235,12 @@
<span class="bamboo-stalk far-4"></span>
<span class="bamboo-stalk far-5"></span>
</div>
<div id="bamboo-mid">
<span class="bamboo-stalk mid-1"></span>
<span class="bamboo-stalk mid-2"></span>
<span class="bamboo-stalk mid-3"></span>
<span class="bamboo-stalk mid-4"></span>
</div>
<div id="bamboo-near">
<div class="bamboo-stalk near-1">
<i class="bamboo-node node-1"></i>