mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-03-31 07:35:55 +00:00
web: mobile layout / safe area (#6408)
* improve status bar / safe area coloring * improve status coloring
This commit is contained in:
@@ -274,11 +274,14 @@ window.addEventListener('click',(e)=>{
|
||||
}
|
||||
}
|
||||
else if(e.target.closest('.nav-toggle-btn')){
|
||||
document.body.classList.toggle('lock-scroll');
|
||||
if(nav.classList.contains('open')){
|
||||
nav.classList.remove('open');
|
||||
document.getElementById('mobile-header').classList.remove('nav-open');
|
||||
document.documentElement.classList.remove('lock-scroll');
|
||||
}
|
||||
else{
|
||||
document.documentElement.classList.add('lock-scroll');
|
||||
document.getElementById('mobile-header').classList.add('nav-open');
|
||||
nav.classList.add('open');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,4 +538,37 @@ button#cross-btn {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
#mobile-header.cover {
|
||||
background: #d7fbfe09;
|
||||
}
|
||||
|
||||
.dark #mobile-header.cover {
|
||||
background: #283b630a;
|
||||
}
|
||||
|
||||
#mobile-header.main {
|
||||
background: #ffefd60e;
|
||||
}
|
||||
|
||||
.dark #mobile-header.main {
|
||||
background: #fff6e00a;
|
||||
}
|
||||
|
||||
#mobile-header.nav-open {
|
||||
background: #ffffff0c;
|
||||
}
|
||||
|
||||
.dark #mobile-header.nav-open {
|
||||
background: #0a0f2b0f;
|
||||
}
|
||||
|
||||
#mobile-header.footer {
|
||||
background: #d3e9ff11;
|
||||
}
|
||||
|
||||
.dark #mobile-header.footer {
|
||||
background: #080d250e;
|
||||
--nav-color: #ffffff;
|
||||
}
|
||||
@@ -218,12 +218,12 @@ html {
|
||||
|
||||
html,
|
||||
body {
|
||||
background: #ccf9fc;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.dark html,
|
||||
.dark body {
|
||||
background: #2d3f64;
|
||||
background: #0a0f2b;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
@@ -301,13 +301,21 @@ p {
|
||||
}
|
||||
|
||||
/* Make the viewport the scroll snap container (works in iOS Safari) */
|
||||
html,
|
||||
body {
|
||||
html {
|
||||
scroll-snap-type: y mandatory;
|
||||
scroll-behavior: smooth;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
html.lock-scroll {
|
||||
scroll-snap-type: none !important;
|
||||
}
|
||||
|
||||
.lock-scroll body {
|
||||
overflow: hidden !important;
|
||||
height: 100svh;
|
||||
}
|
||||
|
||||
section.page {
|
||||
min-height: 100svh;
|
||||
/* fallback */
|
||||
|
||||
@@ -250,6 +250,105 @@ active_home: true
|
||||
io.observe(cover);
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function sectionHeaderState(){
|
||||
const mobileHeader = document.getElementById('mobile-header');
|
||||
if (!mobileHeader) return;
|
||||
|
||||
const cover = document.querySelector('.page-1.cover');
|
||||
const messaging = document.getElementById('messaging');
|
||||
const nextweb = document.getElementById('nextweb');
|
||||
const token = document.getElementById('token');
|
||||
const roadmap = document.getElementById('roadmap');
|
||||
const directory = document.getElementById('directory');
|
||||
const footer = document.querySelector('footer, .footer, #footer');
|
||||
|
||||
const observed = [
|
||||
{ key: 'cover', el: cover },
|
||||
{ key: 'main', el: messaging },
|
||||
{ key: 'main', el: nextweb },
|
||||
{ key: 'main', el: token },
|
||||
{ key: 'main', el: roadmap },
|
||||
{ key: 'main', el: directory },
|
||||
{ key: 'footer', el: footer }
|
||||
].filter(t => t.el);
|
||||
|
||||
if (!observed.length) return;
|
||||
|
||||
const MIN_COVER = 0.02;
|
||||
|
||||
function clear(){
|
||||
mobileHeader.classList.remove('cover','main','footer');
|
||||
}
|
||||
|
||||
function apply(key){
|
||||
clear();
|
||||
if (key) mobileHeader.classList.add(key);
|
||||
}
|
||||
|
||||
function viewportCoverageY(el){
|
||||
const r = el.getBoundingClientRect();
|
||||
const vh = window.innerHeight || document.documentElement.clientHeight;
|
||||
const visibleY = Math.max(0, Math.min(vh, r.bottom) - Math.max(0, r.top));
|
||||
return visibleY / Math.max(1, vh);
|
||||
}
|
||||
|
||||
function computeKeyMax(){
|
||||
const keyMax = { cover: 0, main: 0, footer: 0 };
|
||||
for (const t of observed) {
|
||||
const ratio = viewportCoverageY(t.el);
|
||||
if (ratio > keyMax[t.key]) keyMax[t.key] = ratio;
|
||||
}
|
||||
return keyMax;
|
||||
}
|
||||
|
||||
let lastKey = null;
|
||||
let rafId = null;
|
||||
|
||||
function update(){
|
||||
rafId = null; // Reset RAF flag
|
||||
|
||||
const keyMax = computeKeyMax();
|
||||
|
||||
let bestKey = null, best = MIN_COVER;
|
||||
for (const [k, r] of Object.entries(keyMax)) {
|
||||
if (r > best) { best = r; bestKey = k; }
|
||||
}
|
||||
|
||||
// Only update DOM if state actually changed
|
||||
if (bestKey !== lastKey) {
|
||||
if (bestKey) {
|
||||
apply(bestKey);
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
lastKey = bestKey;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleUpdate(){
|
||||
if (rafId) return; // Already scheduled
|
||||
rafId = requestAnimationFrame(update);
|
||||
}
|
||||
|
||||
let observer = null;
|
||||
|
||||
if ('IntersectionObserver' in window) {
|
||||
observer = new IntersectionObserver(scheduleUpdate, {
|
||||
threshold: [0, 0.02, 0.1, 0.25, 0.5, 0.75, 1],
|
||||
rootMargin: '0px 0px -35% 0px'
|
||||
});
|
||||
observed.forEach(t => observer.observe(t.el));
|
||||
scheduleUpdate(); // Initial update
|
||||
} else {
|
||||
// Fallback for older browsers
|
||||
['scroll','resize','load'].forEach(ev =>
|
||||
window.addEventListener(ev, scheduleUpdate, { passive: true })
|
||||
);
|
||||
scheduleUpdate();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user