feat: settings panel becomes native-style bottom sheet on mobile with drag-to-expand/dismiss
Some checks failed
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled

This commit is contained in:
Admin
2026-03-02 13:57:29 +05:00
parent 3e74019a5e
commit 6e0041ea01

View File

@@ -2161,6 +2161,56 @@ const chapterTmpl = `
/* ── Safe-area inset ────────────────────────────────────────────────────── */
#mini-player { padding-bottom: env(safe-area-inset-bottom, 0); }
/* ── Settings bottom sheet (mobile) ────────────────────────────────────── */
#settings-backdrop {
display: none;
position: fixed; inset: 0; z-index: 90;
background: rgba(0,0,0,0.5);
}
#settings-panel {
/* mobile: bottom sheet */
position: fixed;
left: 0; right: 0; bottom: 0;
z-index: 100;
max-height: 92dvh;
overflow-y: auto;
overscroll-behavior: contain;
background: #18181b;
border-top: 1px solid #27272a;
border-radius: 1rem 1rem 0 0;
box-shadow: 0 -4px 32px rgba(0,0,0,.5);
transform: translateY(100%);
transition: transform 0.32s cubic-bezier(0.32,0.72,0,1);
will-change: transform;
padding-bottom: env(safe-area-inset-bottom, 0);
}
#settings-panel.sheet-open {
transform: translateY(0);
}
.sheet-handle {
width: 2.5rem; height: 4px;
background: #3f3f46; border-radius: 9999px;
margin: 0.75rem auto 0;
flex-shrink: 0;
}
@media (min-width: 640px) {
#settings-backdrop { display: none !important; }
#settings-panel {
position: absolute;
left: auto; right: max(0.5rem, calc(50% - 32rem + 0.5rem));
bottom: calc(100% + 0.25rem);
top: auto;
width: 20rem;
max-height: 80vh;
border-radius: 0.75rem;
border: 1px solid #27272a;
transform: none !important;
transition: none !important;
}
#settings-panel.sheet-open { transform: none !important; }
.sheet-handle { display: none; }
}
/* ── Toggle switch ──────────────────────────────────────────────────────── */
.toggle-switch { position: relative; display: inline-block; width: 2.25rem; height: 1.25rem; flex-shrink: 0; }
.toggle-switch input { opacity: 0; width: 0; height: 0; position: absolute; }
@@ -2326,12 +2376,17 @@ const chapterTmpl = `
</div>
</div>
<!-- Settings panel (opens upward) -->
<!-- Backdrop (mobile only) -->
<div id="settings-backdrop" onclick="closeSettings()"></div>
<!-- Settings panel — bottom sheet on mobile, popover on sm+ -->
<div id="settings-panel"
role="dialog"
aria-label="Reader settings"
hidden
class="fixed inset-x-4 top-1/2 -translate-y-1/2 max-h-[75vh] overflow-y-auto sm:absolute sm:inset-x-auto sm:top-auto sm:translate-y-0 sm:bottom-[calc(100%+0.25rem)] sm:right-[max(0.5rem,calc(50%-32rem+0.5rem))] sm:w-80 sm:max-h-[80vh] bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl z-[100]">
aria-modal="true">
<!-- Drag handle -->
<div class="sheet-handle" id="sheet-handle"></div>
<div class="p-4 space-y-4">
@@ -2496,7 +2551,7 @@ const chapterTmpl = `
window.closeChapterList = function () { chapterListPanel.style.display = 'none'; };
window.toggleChapterList = function () {
var open = chapterListPanel.style.display !== 'none';
settingsPanel.style.display = 'none';
closeSettings();
if (open) { closeChapterList(); }
else {
chapterListPanel.style.display = 'block';
@@ -2504,19 +2559,82 @@ const chapterTmpl = `
if (cur) cur.scrollIntoView({ block: 'center' });
}
};
window.toggleSettings = function () {
var open = settingsPanel.style.display !== 'none';
var settingsBackdrop = document.getElementById('settings-backdrop');
function openSettings() {
closeChapterList();
settingsPanel.style.display = open ? 'none' : 'block';
settingsPanel.classList.add('sheet-open');
settingsBackdrop.style.display = 'block';
}
function closeSettings() {
settingsPanel.classList.remove('sheet-open');
settingsBackdrop.style.display = 'none';
}
window.closeSettings = closeSettings;
window.toggleSettings = function () {
if (settingsPanel.classList.contains('sheet-open')) closeSettings();
else openSettings();
};
// ── Bottom sheet drag-to-dismiss / drag-to-expand ─────────────────────────
(function sheetDrag() {
var handle = document.getElementById('sheet-handle');
if (!handle) return;
var startY = 0, startH = 0, dragging = false;
var DISMISS_THRESHOLD = 80; // px swipe down to dismiss
function onStart(clientY) {
if (window.innerWidth >= 640) return; // desktop: no drag
startY = clientY;
startH = settingsPanel.getBoundingClientRect().height;
dragging = true;
settingsPanel.style.transition = 'none';
}
function onMove(clientY) {
if (!dragging) return;
var dy = clientY - startY; // positive = dragging down
if (dy < 0) {
// stretching up — increase height, clamped to 92dvh
var newH = Math.min(startH - dy, window.innerHeight * 0.92);
settingsPanel.style.maxHeight = newH + 'px';
settingsPanel.style.transform = 'translateY(0)';
} else {
// dragging down — translate panel down
settingsPanel.style.transform = 'translateY(' + dy + 'px)';
settingsPanel.style.maxHeight = '';
}
}
function onEnd(clientY) {
if (!dragging) return;
dragging = false;
settingsPanel.style.transition = '';
var dy = clientY - startY;
if (dy > DISMISS_THRESHOLD) {
closeSettings();
settingsPanel.style.maxHeight = '';
} else {
settingsPanel.style.transform = '';
// snap: if we dragged up, keep the new height
if (dy < 0) {
var snapped = Math.min(startH - dy, window.innerHeight * 0.92);
settingsPanel.style.maxHeight = snapped + 'px';
}
}
}
handle.addEventListener('touchstart', function (e) { onStart(e.touches[0].clientY); }, { passive: true });
handle.addEventListener('touchmove', function (e) { onMove(e.touches[0].clientY); }, { passive: true });
handle.addEventListener('touchend', function (e) { onEnd(e.changedTouches[0].clientY); }, { passive: true });
}());
if (window.__ttsClickOutside) document.removeEventListener('click', window.__ttsClickOutside);
window.__ttsClickOutside = function (e) {
if (stale()) { document.removeEventListener('click', window.__ttsClickOutside); return; }
var sb = document.getElementById('settings-btn');
if (settingsPanel.style.display !== 'none' &&
!settingsPanel.contains(e.target) && e.target !== sb && !sb.contains(e.target)) {
settingsPanel.style.display = 'none';
if (settingsPanel.classList.contains('sheet-open') &&
!settingsPanel.contains(e.target) && e.target !== sb && !sb.contains(e.target) &&
e.target !== settingsBackdrop) {
closeSettings();
}
if (chapterListPanel.style.display !== 'none' &&
!chapterListPanel.contains(e.target) &&