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 <noreply@anthropic.com>
This commit is contained in:
Anas Rashid 2026-10-09 23:59:46 +02:00
parent b7facedf95
commit c2766922b5
3 changed files with 42 additions and 1 deletions

View File

@ -566,7 +566,17 @@ import { brand } from '../lib/brand';
(() => { (() => {
const root = document.documentElement, header = document.querySelector('.site-header'); const root = document.documentElement, header = document.querySelector('.site-header');
new ResizeObserver(() => root.style.setProperty('--header-h', header.offsetHeight + 'px')).observe(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(); addEventListener('scroll', mark, { passive: true }); mark();
})(); })();
</script> </script>

View File

@ -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'); } } 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')); }); 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); }); document.addEventListener('divan:note', (e) => { const { couplet, phrase } = (e as CustomEvent).detail; open(couplet, phrase); });
} }
</script> </script>

View File

@ -862,3 +862,25 @@ p, li { text-wrap: pretty; }
.site-footer .wrap { grid-template-columns: 1fr; } .site-footer .wrap { grid-template-columns: 1fr; }
.footer-bar { justify-content: center; text-align: center; } .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; }
}