feat: settings panel becomes native-style bottom sheet on mobile with drag-to-expand/dismiss
This commit is contained in:
@@ -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) &&
|
||||
|
||||
Reference in New Issue
Block a user