From c2766922b56884ef77cfd4cd34e32ab3e3828c0d Mon Sep 17 00:00:00 2001 From: Anas Rashid Date: Fri, 9 Oct 2026 23:59:46 +0200 Subject: [PATCH] Phones: couplet tools on tap, e-book tabs on one line, a header that steps aside - Touch screens: a couplet's tools show when it is tapped (the couplet lightly marked; tapping again or another hides them); a set bookmark or note stays visible; couplets without either keep no room for the row (from a phone screenshot: every line had four icons under it) - E-book format tabs stay on one line each; on phones without the English in brackets, scrolling sideways if needed - Phones: the sticky header slides away while scrolling down and returns on the way up (a transform, so nothing jumps; not while the search box, keyboard or display menu is in use) Co-Authored-By: Claude Opus 5.5 --- web/src/layouts/Base.astro | 12 +++++++++++- web/src/pages/[...path].astro | 9 +++++++++ web/src/styles/global.css | 22 ++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/web/src/layouts/Base.astro b/web/src/layouts/Base.astro index 6caeb041..b9c395eb 100644 --- a/web/src/layouts/Base.astro +++ b/web/src/layouts/Base.astro @@ -566,7 +566,17 @@ import { brand } from '../lib/brand'; (() => { const root = document.documentElement, header = document.querySelector('.site-header'); new ResizeObserver(() => root.style.setProperty('--header-h', header.offsetHeight + 'px')).observe(header); - const mark = () => (scrollY > 8 ? (root.dataset.scrolled = '') : delete root.dataset.scrolled); + // phones: the header steps aside while scrolling down and returns on the way up (not while it is in use) + const phone = matchMedia('(max-width: 640px)'); + let last = scrollY; + const mark = () => { + scrollY > 8 ? (root.dataset.scrolled = '') : delete root.dataset.scrolled; + const busy = header.contains(document.activeElement) || header.querySelector('details[open]') || !document.getElementById('kb').hidden; + if (!phone.matches || busy || scrollY < header.offsetHeight) delete root.dataset.headerHidden; + else if (scrollY > last + 6) root.dataset.headerHidden = ''; + else if (scrollY < last - 6) delete root.dataset.headerHidden; + if (Math.abs(scrollY - last) > 6) last = scrollY; + }; addEventListener('scroll', mark, { passive: true }); mark(); })(); diff --git a/web/src/pages/[...path].astro b/web/src/pages/[...path].astro index 6cd69522..75388ced 100644 --- a/web/src/pages/[...path].astro +++ b/web/src/pages/[...path].astro @@ -305,6 +305,15 @@ import Icon from '../components/Icon.astro'; else { try { await navigator.clipboard.writeText(`${text}\n${url}`); (window as any).toast('یہ براؤزر شیئر نہیں کر سکتا: شعر اور لنک کاپی ہو گئے'); } catch { (window as any).toast('شیئر نہیں ہو سکا', 'error'); } } }; document.addEventListener('click', (e) => { const b = (e.target as HTMLElement).closest('.sh, .dl') as HTMLElement | null; if (b) share(Number(b.dataset.for), b.classList.contains('dl')); }); + // touch screens: tapping a couplet shows its tools (and hides the last one's); links, buttons and selections are left alone + if (matchMedia('(hover: none)').matches) document.querySelector('.poem')?.addEventListener('click', (e) => { + const t = e.target as HTMLElement; + if (t.closest('a, button, .bmk') || getSelection()?.toString().trim()) return; + const block = t.closest('.couplet, .para, .single'); if (!block) return; + const was = block.classList.contains('is-active'); + document.querySelectorAll('.poem .is-active').forEach((x) => x.classList.remove('is-active')); + if (!was) block.classList.add('is-active'); + }); document.addEventListener('divan:note', (e) => { const { couplet, phrase } = (e as CustomEvent).detail; open(couplet, phrase); }); } diff --git a/web/src/styles/global.css b/web/src/styles/global.css index ae5ab322..90ec1d8c 100644 --- a/web/src/styles/global.css +++ b/web/src/styles/global.css @@ -862,3 +862,25 @@ p, li { text-wrap: pretty; } .site-footer .wrap { grid-template-columns: 1fr; } .footer-bar { justify-content: center; text-align: center; } } + +/* ── phones ── */ +/* e-book format tabs: one line each; the English in brackets only where there is room */ +.ebook-tabs a { white-space: nowrap; } +@media (max-width: 560px) { + .ebook-tabs { flex-wrap: nowrap; justify-content: flex-start; overflow-x: auto; scrollbar-width: none; padding-bottom: 2px; } + .ebook-tabs a span[lang] { display: none; } +} +/* touch screens have no hover: a couplet's tools show when it is tapped (the couplet is lightly marked); a set bookmark + or note stays visible; couplets without either do not keep room for the row */ +@media (hover: none) { + .c-tools > button { opacity: 0; pointer-events: none; } + .c-tools > button[aria-pressed='true'], .is-active > .c-tools > button { opacity: 1; pointer-events: auto; } + .poem .couplet.is-active, .poem .para.is-active, .poem .single.is-active { background: color-mix(in srgb, var(--accent) 14%, transparent); border-radius: var(--r-sm); } + .poem .couplet:not(.is-active):not(:has(.c-tools [aria-pressed='true'])) { padding-bottom: 6px; } + .poem .para:not(.is-active):not(:has(.c-tools [aria-pressed='true'])), .poem .single:not(.is-active):not(:has(.c-tools [aria-pressed='true'])) { margin-bottom: 10px; } +} +/* phones: the header slides away while scrolling down and comes back on the way up (a transform: nothing below jumps) */ +@media (max-width: 640px) { + .site-header { transition: transform .25s ease, box-shadow .2s, background .2s; } + :root[data-header-hidden] .site-header { transform: translateY(-100%); box-shadow: none; } +}