feat(home): align desktop cards with Figma actions

- Update desktop header actions to match Figma: remove the standalone desktop favorites button and style the wallet connect pill with the wallet icon while allowing localized labels to expand.
- Replace favorite action with the Figma bookmark SVG and hover state; replace download cloud with the provided Figma SVG.
- Align official recommendation cards with the Figma card structure, colors, and bottom action row.
- Rework popular rows to the Figma desktop rhythm with 90px rows, wide thumbnails, rank area, and right-side action buttons.
- Add a dedicated desktop LatestUpdateCard for Figma-style latest-update masonry cards with flexible text-driven heights instead of fixed card heights.
This commit is contained in:
TerryM
2026-06-03 08:18:05 +08:00
parent 4f0d8925a4
commit be638e32c9
8 changed files with 464 additions and 65 deletions

View File

@@ -7,6 +7,7 @@ import { FigmaBanner } from "../../components/FigmaBanner";
import { PopularRankList } from "../../components/PopularRankList";
import { RecommendedCard } from "../../components/RecommendedCard";
import { SectionHeader } from "../../components/SectionHeader";
import { LatestUpdateCard } from "../../components/LatestUpdateCard";
import { MessageBubble } from "../../components/messageStream/MessageBubble";
import { langQuery, useI18n } from "../../i18n";
import { useLocalizedPath } from "../../useLocalizedPath";
@@ -47,28 +48,27 @@ type LatestPostColumnItem = {
};
function estimateLatestPostHeight(post: Post): number {
const textLength = (post.text ?? post.title ?? "").length;
const textRows = Math.ceil(textLength / 72);
const textHeight = Math.min(180, Math.max(0, textRows * 22));
const previewHeight = post.linkPreview ? 132 : 0;
const [firstAttachment] = post.attachments;
const textLength = (post.text ?? post.title ?? "").length;
const textRows = Math.max(1, Math.ceil(textLength / 26));
const textHeight = textRows * 24;
const footerHeight = 64;
if (!firstAttachment) return 72 + textHeight + previewHeight;
if (!firstAttachment) return textHeight + footerHeight + 24;
if (post.attachments.length >= 2) {
const mediaHeight = firstAttachment.kind === "video" ? 340 : 300;
return mediaHeight + textHeight + previewHeight + 42;
}
const isVisual =
firstAttachment.kind === "image" ||
firstAttachment.kind === "video" ||
firstAttachment.mime.startsWith("image/") ||
firstAttachment.mime.startsWith("video/");
if (!isVisual) return 52 + textHeight + footerHeight + 28;
if (firstAttachment.kind === "video") {
return 300 + textHeight + previewHeight + 42;
}
if (firstAttachment.kind === "image") {
return (post.text ? 300 : 260) + textHeight + previewHeight + 42;
}
return 96 + post.attachments.length * 72 + textHeight + previewHeight;
const mediaHeight =
firstAttachment.kind === "video" ||
firstAttachment.mime.startsWith("video/")
? 180
: 230;
return mediaHeight + textHeight + footerHeight + 12;
}
function splitLatestPostsIntoColumns(
@@ -545,7 +545,7 @@ export function Home() {
<Reveal>
<section id="latest" className="scroll-mt-16 md:scroll-mt-24">
<div className="mx-auto max-w-full md:max-w-[820px] lg:max-w-[1080px] xl:max-w-[1180px]">
<div className="mx-auto max-w-full md:max-w-[820px] lg:max-w-[1080px] xl:max-w-[1280px]">
<div className="px-4 md:px-0">
<SectionHeader
title={t("latestSection")}
@@ -562,7 +562,7 @@ export function Home() {
</div>
{/* Desktop: explicit balanced columns avoid the uneven gaps that
CSS multi-column masonry can create with variable-height cards. */}
<div className="mt-7 hidden gap-4 px-4 md:grid md:grid-cols-2 lg:grid-cols-3 lg:px-0">
<div className="mt-7 hidden gap-4 px-4 md:grid md:grid-cols-2 lg:grid-cols-3 lg:px-0 xl:grid-cols-[repeat(3,416px)] xl:justify-center">
{latestDesktopColumns.map((column, columnIndex) => (
<div
key={`latest-desktop-column-${columnIndex}`}
@@ -573,7 +573,7 @@ export function Home() {
key={post.id}
delay={Math.min(originalIndex, 8) * 0.05}
>
<MessageBubble post={post} fluid />
<LatestUpdateCard post={post} />
</Reveal>
))}
</div>