feat: scroll to post bubble from recommended card + back-to-top button
Some checks failed
Deploy to Frontend Servers / deploy (push) Failing after 14s

Recommended cards already routed to /browse#post-<id>, but the stream had
no logic to scroll to the target bubble — and the post might not be paged
in yet. MessageStream now resolves the #post-<id> hash, auto-loads more
pages until the bubble renders, scrolls to it, and gives it a brief gold
highlight. Bubbles get scroll-mt so they clear the sticky header.

Also adds a global floating back-to-top button (BackToTop) mounted in
PublicLayout, shown after scrolling past 400px.

Bundles related staging UI work already present in the working tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
TerryM
2026-05-29 11:50:27 +08:00
parent 8e36894851
commit 88a25b6ad4
27 changed files with 748 additions and 139 deletions

View File

@@ -20,9 +20,6 @@ body {
/* Match theme: avoid default blue accent on controls */
:root {
accent-color: #eeb726;
--animate-duration: 720ms;
--animate-delay: 0s;
--animate-repeat: 1;
}
header a,
@@ -49,26 +46,6 @@ header button {
}
}
.ark-page-fade-in {
animation: ark-page-fade-in 240ms ease-out both;
}
@keyframes ark-page-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.ark-page-fade-in {
animation: none;
}
}
/* Desktop header nav: thin scrollbar when links overflow (still 單列) */
.header-nav-scroll {
scrollbar-width: thin;
@@ -89,6 +66,73 @@ header button {
box-shadow: inset 0 -2px 0 #eeb726;
}
/* Reusable skeleton placeholder with a soft shimmer sweep. */
.ark-skeleton {
position: relative;
overflow: hidden;
background-color: #1d1e23;
}
.ark-skeleton::after {
content: "";
position: absolute;
inset: 0;
transform: translateX(-100%);
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.05),
transparent
);
animation: ark-shimmer 1.5s infinite;
}
@keyframes ark-shimmer {
100% {
transform: translateX(100%);
}
}
/* Image fade-in: add .is-loaded on the img's onLoad handler. */
.ark-img-fade {
opacity: 0;
transition: opacity 0.4s ease-out;
}
.ark-img-fade.is-loaded {
opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
.ark-skeleton::after {
animation: none;
}
.ark-img-fade {
opacity: 1;
transition: none;
}
}
/* Brief gold ring pulse when a bubble is targeted via a #post-<id> deep link. */
.ark-bubble-highlight {
border-radius: 1rem;
animation: ark-bubble-highlight 2s ease-out both;
}
@keyframes ark-bubble-highlight {
0%,
35% {
box-shadow: 0 0 0 2px rgba(238, 183, 38, 0.9);
}
100% {
box-shadow: 0 0 0 2px rgba(238, 183, 38, 0);
}
}
@media (prefers-reduced-motion: reduce) {
.ark-bubble-highlight {
animation: none;
}
}
.message-stream-copyable-text,
.message-stream-copyable-text * {
-webkit-user-select: text;