diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.js | 7 | ||||
| -rw-r--r-- | src/features/messages.js | 38 | ||||
| -rw-r--r-- | src/style.css | 20 |
3 files changed, 40 insertions, 25 deletions
@@ -885,7 +885,12 @@ function renderInner() { const side = bottomNav && featScreen && !restorePending() && WIDE_MQ && WIDE_MQ.matches ? walletSide() : null; // a call, over whatever page is up (the messages feature's) const callOv = activeAccount() ? featureHook('callOverlay') : null; - morphChildren(root, [screen, footer(), ...(bottomNav ? [bottomNav] : []), ...(side ? [side] : []), ...(ui.lightbox ? [imageViewer()] : []), ...(callOv ? [callOv] : [])]); + // What a feature floats over its page (the feed's post button and + // new-posts pill): fixed-position nodes, so they live beside the page, + // not inside it — the page's entrance animation transforms it, and a + // transform makes fixed descendants position within the page. + const floaters = h('div', { class: 'page-floaters', style: 'display:contents' }, ...featureAll('pageFloaters')); + morphChildren(root, [screen, floaters, footer(), ...(bottomNav ? [bottomNav] : []), ...(side ? [side] : []), ...(ui.lightbox ? [imageViewer()] : []), ...(callOv ? [callOv] : [])]); promoteLazySrc(root); // sources on the nodes that actually made it into the page try { document.documentElement.classList.toggle('no-scroll', !!(ui.lightbox || ui.mediaPager)); } catch {} if (fpath) { diff --git a/src/features/messages.js b/src/features/messages.js index fb9a9d6..90bfaaf 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -16477,6 +16477,9 @@ export function messagesFeature(ctx) { window.addEventListener('popstate', () => setTimeout(syncMediaPager, 0)); } + // what the feed floats over the page (post button, new-posts pill): the + // shell takes them each render, see pageFloaters in the hooks + let pageFloaters = []; function feedView() { syncFollowSets().catch(() => {}); // throttled inside syncReports().catch(() => {}); // likewise @@ -16541,9 +16544,28 @@ export function messagesFeature(ctx) { }, '↑ ', h('span', { class: 'n', 'data-key': 'n:' + waiting }, String(waiting)), ' ', waiting === 1 ? t('feedOneNewWord') : t('feedNNewWord')) : null; + // The post button floats bottom right wherever the reader is in the + // feed; the composer opens at the top and takes the focus. It and the + // pill are position: fixed — and a page sliding in has a transform on + // it, which makes fixed descendants position within the page instead: + // the button rode at the foot of the feed card, below the screen, until + // the entrance animation ended ("not seeing the Post button right away", + // Adam, 2026-11-02; measured on his phone: 4109 px down at 87 ms, in + // place at 562 ms). So they're handed to the shell, which paints them + // beside the page, outside anything that animates. + const fab = visitor || ui.profCompose != null ? null : h('button', { + class: 'btn-primary feed-post-fab', 'data-key': 'feed-post-fab', type: 'button', title: t('profNewPost'), 'aria-label': t('profNewPost'), + onClick: () => { + ui.profCompose = draftFor(POST_DRAFT) || ''; + render(); + try { window.scrollTo({ top: 0, behavior: 'smooth' }); } catch { window.scrollTo(0, 0); } + setTimeout(() => document.querySelector('.post-input')?.focus(), 60); + }, + }, h('span', { class: 'feed-compose-icon', 'aria-hidden': 'true', html: + '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path stroke="currentColor" d="M12 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-6"/><path stroke="currentColor" opacity=".5" d="M8 17h5"/><path class="compose-pen-fill" d="m16.5 3.5 3 3-8 8-4 1 1-4Z"/><path class="compose-pen" d="M16.5 3.5a2.1 2.1 0 0 1 3 3l-8 8-4 1 1-4Z"/><path class="compose-pen" d="m14.5 5.5 3 3"/></svg>' }), h('span', {}, t('feedPostFab'))); + pageFloaters = [pill, fab].filter(Boolean); // the chat shell draws the brand header; this is just the page under it return h('div', { class: 'card col chat-page feed-page', style: 'gap:10px' }, - h('div', { 'data-key': 'feed-notice', style: 'display:contents' }, pill), h('div', { class: 'row gap6', style: 'align-items:center' }, backBtn(() => { stopFeedWatch(); @@ -16566,18 +16588,6 @@ export function messagesFeature(ctx) { ui.feedRelayEdit && ui.feedRelayEdit.id === def.id ? relayPanel(def) : null, visitor ? null : feedChips(), (refreshLive(), mediaModes()), - // floating bottom right, wherever the reader is in the feed: the - // composer opens at the top and takes the focus - visitor || ui.profCompose != null ? null : h('button', { - class: 'btn-primary feed-post-fab', 'data-key': 'feed-post-fab', type: 'button', title: t('profNewPost'), 'aria-label': t('profNewPost'), - onClick: () => { - ui.profCompose = draftFor(POST_DRAFT) || ''; - render(); - try { window.scrollTo({ top: 0, behavior: 'smooth' }); } catch { window.scrollTo(0, 0); } - setTimeout(() => document.querySelector('.post-input')?.focus(), 60); - }, - }, h('span', { class: 'feed-compose-icon', 'aria-hidden': 'true', html: - '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path stroke="currentColor" d="M12 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-6"/><path stroke="currentColor" opacity=".5" d="M8 17h5"/><path class="compose-pen-fill" d="m16.5 3.5 3 3-8 8-4 1 1-4Z"/><path class="compose-pen" d="M16.5 3.5a2.1 2.1 0 0 1 3 3l-8 8-4 1 1-4Z"/><path class="compose-pen" d="m14.5 5.5 3 3"/></svg>' }), h('span', {}, t('feedPostFab'))), visitor ? null : postComposer(), def.of && !ofLoaded(def) ? h('div', { class: 'row gap6', style: 'justify-content:center;align-items:center;padding:12px 0' }, @@ -18743,6 +18753,8 @@ export function messagesFeature(ctx) { return unreadCount(); }, bottomNav: appBottomNav, + // taken once per render: a page that isn't the feed leaves none + pageFloaters() { const f = pageFloaters; pageFloaters = []; return f; }, notifySettingsCards() { return [notifyCard()]; }, screenView() { liveShown = false; diff --git a/src/style.css b/src/style.css index b65f632..8a28377 100644 --- a/src/style.css +++ b/src/style.css @@ -1035,17 +1035,11 @@ html.no-scroll { overflow: hidden; scrollbar-gutter: auto; } .feed-compose-icon { display: flex; flex-shrink: 0; } .feed-compose-icon .compose-pen { stroke: #14935a; } .feed-compose-icon .compose-pen-fill { fill: #14935a; opacity: .18; } -.feed-page .feed-new-pill { - bottom: calc(env(safe-area-inset-bottom, 0px) + 82px); -} -/* a phone is too narrow for it beside the Post button: it rides above */ -@media (max-width: 480px) { - .feed-page .feed-new-pill { bottom: calc(env(safe-area-inset-bottom, 0px) + 142px); } -} .feed-new-pill { - /* Rides at the bottom of the window, where a thumb already is; the tap - takes the reader up to the new posts. */ - position: fixed; bottom: calc(env(safe-area-inset-bottom, 0px) + 18px); + /* Rides at the bottom of the window, where a thumb already is, above the + nav; the tap takes the reader up to the new posts. (Painted beside the + page, not inside it: see page-floaters in app.js.) */ + position: fixed; bottom: calc(env(safe-area-inset-bottom, 0px) + 82px); left: 50%; transform: translateX(-50%); z-index: 30; width: auto; padding: 12px 24px; border-radius: 999px; background: var(--fill); color: var(--fill-fg); border: none; @@ -1054,6 +1048,10 @@ html.no-scroll { overflow: hidden; scrollbar-gutter: auto; } box-shadow: 0 3px 12px rgba(0, 0, 0, .28); animation: pill-in .18s ease-out; } +/* a phone is too narrow for it beside the Post button: it rides above */ +@media (max-width: 480px) { + .feed-new-pill { bottom: calc(env(safe-area-inset-bottom, 0px) + 142px); } +} /* the generic button hover (a light surface) and press (a 1px drop, which REPLACED the centring transform and threw the pill half its width to the right, out from under the pointer, so the tap never landed) stay off it */ @@ -1894,7 +1892,7 @@ body::after { min-height: 48px; padding: 8px 14px; font-size: 16px; } .feed-post-fab { right: calc(50% - var(--col-edge) + 16px); bottom: 24px; } - .feed-new-pill, .feed-page .feed-new-pill { left: calc(50% + var(--col-mid)); bottom: 24px; } + .feed-new-pill { left: calc(50% + var(--col-mid)); bottom: 24px; } .toast { left: calc(50% + var(--col-mid)); } } @media (min-width: 1220px) { |
