The banners API can return hundreds of records; show at most 10 so the carousel
and its dot indicator stay on one row within the phone width, regardless of how
many exist on the backend.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Banner linkUrls come back from the API as unprefixed paths
(e.g. /browse?post=123). Navigating to them directly dropped non-English
viewers into the English version of the post. Localize both the rendered
href and the SPA navigate target via stripLangPrefix + localizePath.
With many banners the pagination dots overflowed the phone width. Show at most
10 dots in a window that follows the active slide; each dot still maps to its
real slide index.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Measure the image's rendered bottom edge with refs + ResizeObserver and
position the long-press save hint relative to it instead of pinning to
screen center or stage bottom. Enlarges the toast for mobile legibility
and clamps the offset so tall portrait images don't push it offscreen.
The app ships its own 7-language i18n and serves localized content, but mobile
browsers (Google Translate) were auto-translating the Chinese UI into broken
English (brand, nav, language dropdown). Add translate="no" + the Google
notranslate meta, and keep the attribute set on language changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Extract zhDict/enDict from i18n.tsx into src/locales/{zh-CN,en}.ts
- Add full Korean dictionary (src/locales/ko.ts) covering all 115 UI keys
- Update formatBytes test/impl boundary for 1000-based units
- Matches Figma design (file uHDZkVHjAp7BXDKQKB0PM4, node 4367-11405).
- Mobile keeps the existing 5-post single column unchanged.
- Desktop (md+) renders all 12 latest posts in a CSS-columns masonry
with break-inside-avoid so each card's height stays content-driven.
- Adds an optional 'fluid' prop to MessageBubble that drops the
standalone-feed max-widths so bubbles fill the masonry column. The
/browse stream keeps the default non-fluid widths.
Match Figma node 4366-11092 desktop banner design:
- Slides shrink to 78%/72%/60% width on md/lg/xl with snap-center,
first/last get matching left/right margin so the edges still center.
- Each slide is wrapped in a framer-motion m.div that animates filter,
opacity, and scale between active and idle states.
- goTo and scroll/drag handlers use the slide's real offsetWidth so
centering math holds at every breakpoint; mobile (full-width, snap-start
visual) is unchanged.
Match the official-recommendation and popular cards to the message bubble
surface color (#272632) used on the browse/all page and the latest section,
so homepage content cards and the browse page share one background. Also
align the popular card border with the official card (#27292E).
Categories tiles are intentionally left unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Size the banner and the popular rank list to the same responsive widths as
the latest section's message bubbles (680/900/1120), so the banner, latest
cards and popular cards line up at one consistent content width on desktop.
Desktop-scoped; mobile stays full-width.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Wrap the Categories and Popular sections in the same responsive
max-width container (820/1080/1180) used by Official and Latest, so all
four section titles line up vertically on desktop.
- Official carousel arrows: hide the arrow at the edge already reached, and
snap one card per click (reveal the next/previous card fully, keeping a
small peek) instead of a fixed-pixel scroll.
- Show the pagination dots on desktop too (was mobile-only).
- RecommendedCard download button icon: text-ark-gold -> text-white to match
the file bubble / popular / video download buttons.
Desktop-scoped; mobile layout unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Banner: scale down and center on desktop (md:max-w-[680px] lg:max-w-[800px])
so it no longer fills the whole screen.
- Align section widths to one responsive container (820/1080/1180): wrap the
Official row and match Popular to Latest, fixing left-edge/width mismatches.
- RecommendedCard: stop the carousel card from shrinking back to 246.4px at xl.
- Popular download button now matches the file bubble's filled round
DownloadCloud button for visual consistency.
- Header nav vertical padding py-1 -> py-0.5; swap Favorites before Popular
across desktop nav and mobile menu to match the bottom tab order.
- Official carousel: hide the left arrow at the start and the right arrow at
the end instead of always showing both.
All changes are md/lg/xl-scoped; mobile layout is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entering /browse via a popular-section card carried ?post=, which made
ScrollToTop skip the reset entirely. The window stayed at the previous
page's bottom scroll, so the deep-link animation visibly scrolled UP from
the bottom to the target post.
Now any pathname change jumps to the top first (even with ?post=), letting
the destination align to the post by scrolling down from the top. Hash
anchors and same-page ?post= changes are still left alone.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `select-none` to the sticky header, the type filter chips row, and the
mobile bottom nav so their labels and icons can't be highlighted/selected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- FigmaBanner: route same-app linkUrl through SPA navigation so the stream's
scroll-to-post runs without a full reload; defer pointer capture until a real
drag starts, fixing plain clicks being swallowed by setPointerCapture
- PopularRankList: rank rows navigate straight to /browse?sort=popular&post=<id>
- MessageStream: ?post= deep links jump directly to the target instead of
resetting to the top and animating through the stream
- ScrollToTop: skip the top-reset for ?post= navigations so the target page
handles its own alignment
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously the file bubble wrapped JS middle-ellipsis in CSS truncate,
so narrow containers silently clipped the tail+extension, leaving
e.g. "25cb264a-e06…." instead of "25cb264a-e06…811a.jpg".
Split the displayed name into a shrinking head (with CSS truncate)
and a non-shrinking tail (last 4 base chars + extension). The browser
now decides how much head to clip while the suffix is always visible.
Route resource card clicks to /browse?post=<id> instead of relying on hash
anchors, then manually calculate the sticky filter offset when scrolling to
the target bubble. Start from the top and smooth-scroll to the card for a
clear transition, with delayed auto realignments after media above the target
settles.
Add optional post-level and localized title fields, and use a new
postTitleText helper for Resource.title so card/list surfaces prefer short
backend-provided titles instead of full body text. When title is missing,
fallback to the first non-empty body line, then filename, then post id.
Document the backend handoff in docs/posts-title-api.md alongside the other
backend task docs.
Use object-fill for compact preview thumbnails so rank-list covers and
file previews fill their fixed boxes without cropping or black bars. This
keeps the list layout stable while matching the desired compressed-ratio
thumbnail treatment.
Clamp goPrev / goNext instead of wrapping the index, and hide the
left chevron on the first image and the right chevron on the last
one. Arrow keys, swipe gestures, and the on-screen buttons all
behave like a linear gallery now, so users get a clear cue that
they have reached the end instead of unexpectedly looping back.