feat(web): pin curated male cards to the front + row-first grid layout

Two coupled changes so the user's preferred male cards (复古未来梦,
社团存亡日, 黄昏归途, 极简杀机, 辐射新娘, 霓虹义体, 月光下的约定,
花魁的刀) actually appear in the visual front row:

1. Add a DISPLAY_ORDER indirection. STORIES, covers (m{i}.webp),
   prebaked first-acts (firstact/m{i}.json) and prompts.json are all
   keyed on the original array index — renaming them would touch
   dozens of static assets. DISPLAY_ORDER instead lets the homepage
   iterate cards in a curated order while still resolving each card's
   assets via its original index. Editing one line re-shuffles the
   gallery.

2. Switch the gallery wrapper from CSS multi-column (columns-N) to
   grid (grid-cols-N). columns fills column-first (top-of-col-1, then
   bottom-of-col-1, then top-of-col-2...) so the first eight entries
   of DISPLAY_ORDER ended up stacked down the leftmost column instead
   of across the top row. Grid fills row-first, which is what "visual
   front row" actually means. Cards are already fixed at aspect-ratio
   4/5 so row heights stay uniform — no masonry effect lost.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
DESKTOP-I1T6TF3\Q
2026-06-03 04:11:26 +08:00
parent cd7619265c
commit c90929ce4b
+35 -11
View File
@@ -731,6 +731,26 @@ const STORIES: Record<Gender, StoryContent[]> = {
]
};
/* 显示顺序映射:STORIES 数组本身不动(封面 /home/{m|f}{i}.webp、首幕
/home/firstact/{m|f}{i}.json、prompts.json 都按其索引固定关联,重排会牵动
几十个静态资源)。这里只决定首页瀑布流的「呈现顺序」,每一位填入对应
STORIES 里的原始索引;渲染时仍用原始索引拼资源 URL。改这一行就能再调顺序。 */
const DISPLAY_ORDER: Record<Gender, number[]> = {
: [
13, // 复古未来梦
8, // 社团存亡日
9, // 黄昏归途
14, // 极简杀机
27, // 辐射新娘
10, // 霓虹义体
11, // 月光下的约定
2, // 花魁的刀
// 其余按原顺序填补
0, 1, 3, 4, 5, 6, 7, 12, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 28, 29,
],
女性向: Array.from({ length: 30 }, (_, i) => i),
};
/* ---------- typewriter ---------- */
// 父组件持有当前 phrase 的索引(这样 start() 不输入时能用当前闪动的那句
@@ -804,7 +824,7 @@ function StoryCard({
type="button"
onClick={onClick}
style={{ aspectRatio: "4 / 5" }}
className="group relative block w-full mb-4 md:mb-5 break-inside-avoid overflow-hidden rounded-sm border border-clay-900/10 bg-cream-100 text-left transition-transform duration-300 ease-out hover:-translate-y-1 hover:shadow-md hover:shadow-clay-900/5"
className="group relative block w-full overflow-hidden rounded-sm border border-clay-900/10 bg-cream-100 text-left transition-transform duration-300 ease-out hover:-translate-y-1 hover:shadow-md hover:shadow-clay-900/5"
>
<img
src={image}
@@ -1255,16 +1275,20 @@ export default function HomePage() {
(fading ? "opacity-0 blur-[3px]" : "opacity-100 blur-0")
}
>
<div className="columns-2 sm:columns-3 md:columns-4 lg:columns-5 gap-4 md:gap-5">
{stories.map((c, i) => (
<StoryCard
key={`${imgPrefix}-${i}`}
title={c.title}
outline={c.outline}
image={`/home/${imgPrefix}${i}.webp`}
onClick={() => onCardClick(i, c)}
/>
))}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 md:gap-5">
{DISPLAY_ORDER[galleryGender].map((origIdx) => {
const c = stories[origIdx];
if (!c) return null;
return (
<StoryCard
key={`${imgPrefix}-${origIdx}`}
title={c.title}
outline={c.outline}
image={`/home/${imgPrefix}${origIdx}.webp`}
onClick={() => onCardClick(origIdx, c)}
/>
);
})}
</div>
</div>
</section>