From 7fea11de0a3778062f493b83cf6bced075ac34c1 Mon Sep 17 00:00:00 2001 From: KoalaDev <6156589+Shik3i@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:21:47 +0200 Subject: [PATCH] Polish extension and landing page UI --- extension/i18n.js | 1 + extension/popup.html | 88 ++++++++-- scripts/test-website-theme.mjs | 31 ++++ scripts/verify-release.mjs | 1 + website/style.css | 285 ++++++++++++++++++++++++++++++--- website/template.html | 39 +++-- 6 files changed, 394 insertions(+), 51 deletions(-) create mode 100644 scripts/test-website-theme.mjs diff --git a/extension/i18n.js b/extension/i18n.js index 026de47..1e65eac 100644 --- a/extension/i18n.js +++ b/extension/i18n.js @@ -12,6 +12,7 @@ let currentLanguage = null; */ export async function loadLocale(langCode) { const resolvedLang = SUPPORTED_LANGUAGES.includes(langCode) ? langCode : DEFAULT_LANGUAGE; + document.documentElement.lang = resolvedLang; if (currentLanguage === resolvedLang && Object.keys(activeDictionary).length > 0) { return; diff --git a/extension/popup.html b/extension/popup.html index bbd7f4f..576e358 100644 --- a/extension/popup.html +++ b/extension/popup.html @@ -1,5 +1,5 @@ - + KoalaSync @@ -61,9 +61,10 @@ } body { - width: 320px; + width: 360px; margin: 0; - padding: 16px; + padding: 18px; + box-sizing: border-box; background: var(--bg); color: var(--text); font-family: 'Twemoji Country Flags', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; @@ -83,7 +84,8 @@ .header-row { display: flex; align-items: center; - margin-bottom: 16px; + margin-bottom: 14px; + padding: 2px 2px 0; } h1 { @@ -98,8 +100,8 @@ } h1 img { - width: 48px; - height: 48px; + width: 42px; + height: 42px; object-fit: contain; border-radius: 4px; } @@ -132,7 +134,8 @@ background: var(--card); padding: 4px; border-radius: var(--radius); - margin-bottom: 20px; + margin-bottom: 16px; + border: 1px solid var(--border-soft); } .tab-btn { @@ -146,11 +149,13 @@ border-radius: 8px; min-height: 36px; transition: background-color 0.2s, color 0.2s, box-shadow 0.2s; + white-space: nowrap; } .tab-btn.active { background: var(--accent); color: var(--text-on-green); + box-shadow: 0 5px 14px oklch(0.38 0.10 150 / 0.22); } .tab-content { @@ -191,6 +196,8 @@ border-radius: 8px; outline: none; font-family: inherit; + min-height: 40px; + transition: border-color 0.2s, box-shadow 0.2s, background-color 0.2s; } @supports (appearance: base-select) { @@ -207,9 +214,10 @@ } } - input:focus { + input:focus, + select:focus { border-color: var(--accent); - box-shadow: 0 0 0 2px rgba(86, 174, 108, 0.2); + box-shadow: 0 0 0 3px oklch(0.68 0.13 150 / 0.18); } button.primary { @@ -277,13 +285,14 @@ .info-card { background: var(--card); padding: 12px; - border-radius: 8px; + border-radius: 10px; margin-bottom: 12px; border: 1px solid var(--border-strong); + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.08); } .peer-item { - padding: 8px 0; + padding: 10px 0; border-bottom: 1px solid var(--border-strong); } @@ -471,6 +480,51 @@ font-size: 10px; opacity: 0.78; } + + /* Unified interaction layer: the popup is a compact control surface, + so every actionable target gets the same tactile and keyboard cues. */ + button, + select, + summary, + a { + -webkit-tap-highlight-color: transparent; + } + button:not(:disabled):active, + .tab-btn:not(:disabled):active { + transform: translateY(1px) scale(0.985); + } + details.form-group > summary { + min-height: 30px; + display: flex; + align-items: center; + cursor: pointer; + } + #section-join > .info-card, + #section-active > .info-card, + #tab-sync .info-card { + position: relative; + overflow: hidden; + } + #section-active > .info-card:first-child::before, + #connStatus::before { + content: ""; + position: absolute; + inset: 0 auto 0 0; + width: 3px; + background: var(--accent); + } + #playBtn, + #pauseBtn, + #forceSyncBtn { + min-height: 42px; + } + #leaveBtn { + background: transparent; + border: 1px solid color-mix(in oklch, var(--error), transparent 35%); + } + #leaveBtn:hover { + background: color-mix(in oklch, var(--error), transparent 88%); + } .popup-footer a { color: var(--text-muted); text-decoration: none; @@ -516,6 +570,9 @@ html.theme-light .popup-footer { border-top-color: var(--border-soft); } + html.theme-light .tab-btn.active { + color: #fff; + } .feature-hint { display: inline-block; width: 8px; @@ -543,6 +600,7 @@
+

KoalaSync LogoKoalaSync

@@ -676,7 +734,7 @@
- @@ -738,7 +796,7 @@
- @@ -787,7 +845,7 @@
- @@ -881,6 +939,8 @@
+
+ diff --git a/scripts/test-website-theme.mjs b/scripts/test-website-theme.mjs new file mode 100644 index 0000000..f94f88f --- /dev/null +++ b/scripts/test-website-theme.mjs @@ -0,0 +1,31 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const template = fs.readFileSync(path.join(repoRoot, 'website', 'template.html'), 'utf8'); +const mockupStart = template.indexOf('
'); +const mockupEnd = template.indexOf('
]+id="demo-video-select"[^>]+>/], + ['sync target', /]+class="mock-input"[^>]+title="Choose sync target"[^>]*>/], + ['WebSocket status', /\s*\{\{MOCK_27\}\}/] +]; + +for (const [label, pattern] of themeSensitiveControls) { + const match = mockup.match(pattern); + if (!match) throw new Error(`Could not locate ${label} in the extension mockup`); + if (!/color:\s*var\(--text\)/.test(match[0])) { + throw new Error(`${label} must use var(--text) so it remains readable in both themes`); + } +} + +console.log('Extension mockup theme-sensitive text uses theme-aware colors'); diff --git a/scripts/verify-release.mjs b/scripts/verify-release.mjs index b8545c2..e83ee6e 100644 --- a/scripts/verify-release.mjs +++ b/scripts/verify-release.mjs @@ -28,6 +28,7 @@ const checks = [ ['background syntax', 'node', ['-c', 'extension/background.js']], ['locale coverage', 'node', ['scripts/test-locales.cjs']], ['website locale coverage', 'node', ['scripts/test-website-locales.mjs']], + ['website theme coverage', 'node', ['scripts/test-website-theme.mjs']], ['lint', 'npm', ['run', 'lint']], ['root production audit', 'npm', ['audit', '--omit=dev']], ['server production audit', 'npm', ['audit', '--omit=dev'], { cwd: path.join(repoRoot, 'server') }], diff --git a/website/style.css b/website/style.css index 1ddbaf9..ad55856 100644 --- a/website/style.css +++ b/website/style.css @@ -743,6 +743,215 @@ html.theme-light .bg-nature::after { } } +/* --- UI/UX cohesion pass ------------------------------------------------- + Shared rhythm and interaction rules for the landing page. Component-specific + responsive refinements are repeated at the end of the cascade. */ +html { + scroll-padding-top: 96px; +} + +body { + text-rendering: optimizeLegibility; +} + +.footer-disclaimer { + color: oklch(0.72 0.015 90); +} + +html.theme-light .footer-disclaimer { + color: var(--text-muted); +} + +.logo-area { + text-decoration: none; + border-radius: 12px; +} + +.logo-area:focus-visible, +.nav-links a:focus-visible, +.theme-toggle:focus-visible, +.lang-dropdown:focus-visible, +.hamburger:focus-visible, +.btn:focus-visible, +.faq-item summary:focus-visible { + outline: 3px solid var(--accent); + outline-offset: 4px; +} + +.nav-links a { + position: relative; + padding: 0.55rem 0; +} + +.nav-links a::after { + content: ""; + position: absolute; + right: 0; + bottom: 0.25rem; + left: 0; + height: 2px; + border-radius: 999px; + background: var(--accent); + transform: scaleX(0); + transform-origin: right; + transition: transform 0.25s ease; +} + +.nav-links a:hover::after, +.nav-links a:focus-visible::after { + transform: scaleX(1); + transform-origin: left; +} + +.hero-text { + max-width: 42rem; +} + +.hero-text h1 { + text-wrap: balance; +} + +.hero-subtitle, +.section-subtitle, +.use-cases-subtitle { + text-wrap: pretty; +} + +.cta-group { + align-items: stretch; +} + +.cta-group .btn { + min-height: 48px; + box-sizing: border-box; +} + +.cta-trust { + max-width: 44rem; + line-height: 1.55; +} + +section[id] { + scroll-margin-top: 96px; +} + +.section-title, +#how-it-works h2, +#self-hosting h2, +#faq h2 { + text-wrap: balance; + letter-spacing: -0.035em; +} + +.use-case-card, +.feature-card, +.faq-item, +.comparison-table-wrapper { + box-shadow: 0 18px 46px rgba(0, 0, 0, 0.12); +} + +html.theme-light :is(.use-case-card, .feature-card, .faq-item, .comparison-table-wrapper) { + box-shadow: 0 18px 46px oklch(0.22 0.035 140 / 0.09); +} + +.use-case-card, +.feature-card { + height: 100%; +} + +.use-case-card p, +.feature-card p, +.faq-item p { + line-height: 1.7; +} + +.faq-item summary { + min-height: 52px; + display: flex; + align-items: center; +} + +.comparison-table tbody tr { + transition: background-color 0.2s ease; +} + +@media (max-width: 768px) { + html { + scroll-padding-top: 76px; + } + + .container { + padding-inline: 1.15rem; + } + + section { + padding-block: 4.25rem; + } + + .hero-text { + max-width: none; + } + + .hero-text h1, + .section-title, + #how-it-works h2, + #self-hosting h2, + #faq h2 { + font-size: clamp(2rem, 9vw, 2.65rem) !important; + } + + .cta-group { + display: grid; + grid-template-columns: 1fr 1fr; + width: 100%; + } + + .cta-group .btn { + width: 100%; + justify-content: center; + } + + .cta-group .btn-primary, + .cta-group .btn-firefox { + grid-column: span 1; + } + + .hero-github-cta, + .btn-demo-mobile { + grid-column: span 1; + } + + .use-case-card, + .feature-card, + .faq-item, + .comparison-table-wrapper { + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.1); + } +} + +@media (max-width: 430px) { + .cta-group { + grid-template-columns: 1fr; + } + + .cta-group .btn-primary, + .cta-group .btn-firefox, + .hero-github-cta, + .btn-demo-mobile { + grid-column: 1; + } +} + +@media (prefers-reduced-motion: reduce) { + .nav-links a::after, + .use-case-card, + .feature-card, + .faq-item, + .btn { + transition-duration: 0.01ms !important; + } +} + /* (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 @@ -5414,8 +5623,9 @@ html.theme-light .hero-demo-scene .demo-video::after { .hero-demo-scene .film-mid-scroll > * { fill: var(--demo-midground) !important; } .hero-demo-scene .film-fore-scroll > * { fill: var(--demo-foreground) !important; } -/* By day the flock reads as silhouettes, not chalk marks */ +/* By day the birds read as silhouettes, not chalk marks */ html.theme-light .hero-demo-scene .film-birds { stroke: oklch(0.32 0.06 145 / 0.65); } +html.theme-light :is(.hero-demo-scene, .step-illustration-3) .film-hero-bird { stroke: oklch(0.32 0.06 145 / 0.75); } /* Stars: staggered twinkle so the sky feels alive */ .film-star { @@ -5480,27 +5690,22 @@ html.theme-light .hero-demo-scene .film-birds { stroke: oklch(0.32 0.06 145 / 0. .film-fore-scroll { transform: translateX(var(--scroll-fore, 0px)); } -/* Floating lantern: the group rides the JS-driven drift vars so halo and - light travel together; the glow stays cheap via a single drop-shadow. */ -.film-lantern-g { +/* A single gliding bird carries the scene: the outer group rides the + JS-driven drift vars, the inner group flaps its wings via scaleY. */ +.film-hero-bird-drift { transform: translate(var(--lantern-x, 0px), var(--bounce-y, 0px)); } -.film-bounce-ball { - transform-origin: bottom center; - filter: drop-shadow(0 0 4px oklch(0.72 0.13 50)); -} - -.film-lantern-halo { +.film-hero-bird { transform-box: fill-box; transform-origin: center; - animation: filmHaloPulse 2.6s ease-in-out infinite alternate; + animation: filmWingFlap 0.85s ease-in-out infinite alternate; animation-play-state: paused; } -@keyframes filmHaloPulse { - from { transform: scale(0.85); opacity: 0.12; } - to { transform: scale(1.18); opacity: 0.22; } +@keyframes filmWingFlap { + from { transform: scaleY(1); } + to { transform: scaleY(0.5); } } /* Twinkle & Glow play state bindings */ @@ -5510,7 +5715,7 @@ html.theme-light .hero-demo-scene .film-birds { stroke: oklch(0.32 0.06 145 / 0. animation: filmTwinkle 2.8s ease-in-out infinite; animation-play-state: paused; } -.demo-tab-card.playing :is(.film-star, .film-moon, .film-moon-g, .film-cloud, .film-shooting-star, .film-birds, .film-lantern-halo) { +.demo-tab-card.playing :is(.film-star, .film-moon, .film-moon-g, .film-cloud, .film-shooting-star, .film-birds, .film-hero-bird) { animation-play-state: running; } @@ -5914,9 +6119,9 @@ html.theme-light .hero-demo-scene .film-birds { stroke: oklch(0.32 0.06 145 / 0. .step-illustration-3 .film-fore-scroll { animation: scrollFore 3s linear infinite !important; } -.step-illustration-3 .film-bounce-ball { - animation: ballBounce 0.8s ease-in-out infinite !important; - transform-origin: bottom center; +.step-illustration-3 .film-hero-bird { + animation: filmWingFlap 0.85s ease-in-out infinite !important; + animation-direction: alternate !important; } @keyframes scrollBack { @@ -5931,10 +6136,6 @@ html.theme-light .hero-demo-scene .film-birds { stroke: oklch(0.32 0.06 145 / 0. 0% { transform: translateX(0); } 100% { transform: translateX(-160px); } } -@keyframes ballBounce { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-16px); } -} /* --- High Fidelity Micro-Animations & Identity additions --- */ @@ -6333,3 +6534,43 @@ html.theme-light .mobile-demo-close:hover { contain-intrinsic-block-size: auto 700px; } } + +/* Final responsive hierarchy overrides. Keep these last: the landing page has + several component-level media blocks above that intentionally own geometry. */ +@media (max-width: 768px) { + .container { + padding-inline: 1.15rem; + } + + section { + padding-block: 4.25rem; + } + + .hero-text h1, + .section-title, + #how-it-works h2, + #self-hosting h2, + #faq h2 { + font-size: clamp(2rem, 9vw, 2.65rem) !important; + text-wrap: balance; + } + + .cta-group { + display: grid; + grid-template-columns: 1fr 1fr; + align-items: stretch; + width: 100%; + } + + .cta-group .btn { + width: 100%; + min-height: 48px; + justify-content: center; + } +} + +@media (max-width: 430px) { + .cta-group { + grid-template-columns: 1fr; + } +} diff --git a/website/template.html b/website/template.html index 84fda59..bc24506 100644 --- a/website/template.html +++ b/website/template.html @@ -294,14 +294,14 @@ -
+
+

{{HERO_TITLE}}

@@ -481,9 +482,10 @@ - - - + + + +
+