fix(website): smooth anchor scrolling, language-switch scroll position, lang select UX

- Replace JS scrollIntoView handler with CSS scroll-behavior:smooth for
  native anchor navigation. Fixes #hash-based scroll position surviving
  language switches since the hash stays in the URL.
- Auto-update URL hash via IntersectionObserver as user scrolls through
  sections, so manual scrolling also sets the anchor for language switch.
- Preserve window.location.hash across all language-switch navigations.
- Fix Chrome double-arrow on language select by limiting appearance:base-select
  to ::picker(select) only, keeping the custom SVG chevron.
- Make chevron absolutely positioned over the select with pointer-events:none
  so clicks anywhere on the container open the dropdown.
- Remove max-width constraint on language select so longer names fit.
- Add scroll-margin-top to section[id] and header[id] so anchored sections
  clear the fixed navbar (100px offset).
- Add header[id] to scroll-margin-top for keyboard skip-link accessibility.

Thanks to Taradal from Reddit
This commit is contained in:
KoalaDev
2026-06-16 06:05:42 +02:00
parent b7a7a14a35
commit 884feb982d
2 changed files with 27 additions and 27 deletions
+14 -17
View File
@@ -34,6 +34,17 @@ document.addEventListener('DOMContentLoaded', () => {
revealElements.forEach(el => revealObserver.observe(el));
// Auto-update URL hash as user scrolls through sections
// (preserves position across language switches)
const sectionObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
history.replaceState(null, null, '#' + entry.target.id);
}
});
}, { threshold: 0.3 });
document.querySelectorAll('section[id], header[id]').forEach(el => sectionObserver.observe(el));
// Navbar scroll effect
const nav = document.querySelector('nav');
window.addEventListener('scroll', () => {
@@ -46,20 +57,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
// Smooth scroll for anchors
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// Invite Detection & Bridge
const checkInvite = () => {
const isJoinPage = window.location.pathname.includes('join');
@@ -508,7 +505,7 @@ document.addEventListener('DOMContentLoaded', () => {
target = hasHtml ? 'imprint.html' : 'imprint';
if (path.includes('/de/')) target = hasHtml ? '../imprint.html' : '../imprint';
}
window.location.href = target;
window.location.href = target + window.location.hash;
return;
} else if (isLegalPrivacy) {
let target;
@@ -520,7 +517,7 @@ document.addEventListener('DOMContentLoaded', () => {
target = hasHtml ? 'privacy.html' : 'privacy';
if (path.includes('/de/')) target = hasHtml ? '../privacy.html' : '../privacy';
}
window.location.href = target;
window.location.href = target + window.location.hash;
return;
}
@@ -549,7 +546,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
window.location.href = targetPath;
window.location.href = targetPath + window.location.hash;
} else {
// Dynamic page: Toggle classes and update elements dynamically without navigating away
const html = document.documentElement;
+13 -10
View File
@@ -61,6 +61,10 @@
border-radius: 6px;
}
html {
scroll-behavior: smooth;
}
body {
font-family: 'Twemoji Country Flags', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
background-color: var(--bg);
@@ -2007,12 +2011,14 @@ html:not(.lang-de) [lang="de"] {
}
.lang-select-container .chevron-icon {
position: absolute;
right: 14px;
top: 50%;
transform: translateY(-50%);
width: 12px;
height: 12px;
flex-shrink: 0;
pointer-events: none;
opacity: 0.6;
margin-left: -2px;
}
.lang-select-container:hover .chevron-icon {
@@ -2031,21 +2037,16 @@ html:not(.lang-de) [lang="de"] {
font-weight: 600;
color: currentColor;
cursor: pointer;
padding: 0 12px 0 0;
padding: 0 28px 0 0;
margin: 0;
width: auto;
max-width: 150px;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 110px;
overflow: hidden;
}
@supports (appearance: base-select) {
.lang-dropdown,
.lang-dropdown::picker(select) {
appearance: base-select;
}
.lang-dropdown::picker(select) {
background-color: #0f172a;
border: 1px solid #334155;
border-radius: 8px;
@@ -2748,7 +2749,9 @@ html:not(.lang-de) [lang="de"] {
}
.feature-card,
.use-case-card,
.compat-logo {
.compat-logo,
section[id],
header[id] {
scroll-margin-top: 100px;
}