Skip to content

Commit 4421aa3

Browse files
committed
Refine homepage featured tasks and card icons
1 parent 2168a2a commit 4421aa3

5 files changed

Lines changed: 522 additions & 89 deletions

File tree

src/app/icon.svg

Lines changed: 48 additions & 0 deletions
Loading

src/app/page.tsx

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Link from "next/link";
22
import { FeaturedTaskCarousel } from "@/components/featured-task-carousel";
33
import { HomeHeroStats } from "@/components/home-hero-stats";
44
import { ResourceCard } from "@/components/resource-card";
5-
import { getIndex } from "@/lib/task-index";
5+
import { getIndex, type TaskIndexItem } from "@/lib/task-index";
66
import {
77
contributeResources,
88
frameworkHighlights,
@@ -11,7 +11,41 @@ import {
1111
tutorialResources
1212
} from "@/lib/site-content";
1313

14-
const FEATURED_REPOS = ["T000002-bart", "T000012-sst", "T000006-mid"] as const;
14+
const FIXED_FEATURED_REPOS = ["T000002-bart", "T000012-sst", "T000006-mid"] as const;
15+
const FIXED_FEATURED_REPO_SET = new Set<string>(FIXED_FEATURED_REPOS);
16+
const RANDOM_FEATURED_COUNT = 2;
17+
18+
function buildSeed(seedSource: string): number {
19+
return Array.from(seedSource).reduce(
20+
(seed, character) => ((seed * 33) ^ character.charCodeAt(0)) >>> 0,
21+
5381
22+
);
23+
}
24+
25+
function seededShuffle<T>(items: T[], seedSource: string): T[] {
26+
const shuffled = [...items];
27+
let seed = buildSeed(seedSource);
28+
29+
for (let index = shuffled.length - 1; index > 0; index -= 1) {
30+
seed = (seed * 1664525 + 1013904223) >>> 0;
31+
const swapIndex = seed % (index + 1);
32+
[shuffled[index], shuffled[swapIndex]] = [shuffled[swapIndex], shuffled[index]];
33+
}
34+
35+
return shuffled;
36+
}
37+
38+
function uniqueTasks(tasks: TaskIndexItem[]): TaskIndexItem[] {
39+
const seen = new Set<string>();
40+
41+
return tasks.filter((task) => {
42+
if (!task.repo || seen.has(task.repo)) {
43+
return false;
44+
}
45+
seen.add(task.repo);
46+
return true;
47+
});
48+
}
1549

1650
function TapsDiagram() {
1751
return (
@@ -96,14 +130,22 @@ function TapsDiagram() {
96130
export default function Page() {
97131
const index = getIndex();
98132
const tasks = index.tasks ?? [];
99-
const featuredTasks = FEATURED_REPOS.map((repo) => tasks.find((task) => task.repo === repo)).filter(
100-
(task): task is (typeof tasks)[number] => Boolean(task)
101-
);
102-
const curatedFeaturedTasks = featuredTasks.length > 0 ? featuredTasks : tasks.slice(0, 3);
133+
const fixedFeaturedTasks = FIXED_FEATURED_REPOS.map((repo) =>
134+
tasks.find((task) => task.repo === repo)
135+
).filter((task): task is TaskIndexItem => Boolean(task));
136+
const randomFeaturedTasks = seededShuffle(
137+
tasks.filter((task) => !FIXED_FEATURED_REPO_SET.has(task.repo)),
138+
index.generated_at
139+
).slice(0, RANDOM_FEATURED_COUNT);
140+
const curatedFeaturedTasks = uniqueTasks([
141+
...fixedFeaturedTasks,
142+
...randomFeaturedTasks,
143+
...tasks
144+
]).slice(0, FIXED_FEATURED_REPOS.length + RANDOM_FEATURED_COUNT);
103145

104146
return (
105147
<div className="space-y-20 pb-8">
106-
<section className="grid gap-12 lg:grid-cols-[minmax(0,1fr)_590px] lg:items-center xl:gap-16">
148+
<section className="grid gap-12 pt-4 sm:pt-6 lg:grid-cols-[minmax(0,1fr)_620px] lg:items-center xl:gap-16">
107149
<div>
108150
<div className="tb-badge mx-auto w-fit">New: canonical tasks, previews, docs, and skills</div>
109151
<h1 className="mt-6 max-w-3xl font-heading text-5xl font-bold leading-[0.92] text-[#25314d] sm:text-6xl">

src/components/featured-task-carousel.tsx

Lines changed: 74 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ export function FeaturedTaskCarousel({
3838
);
3939
}
4040

41-
const activeTask = mergedTasks[activeIndex] ?? mergedTasks[0];
41+
const normalizedActiveIndex = activeIndex % mergedTasks.length;
42+
const activeTask = mergedTasks[normalizedActiveIndex] ?? mergedTasks[0];
4243
const preview = activeTask.web_variant;
4344
const drawerTask = mergedTasks.find((task) => task.repo === drawerRepo) ?? null;
4445

@@ -49,32 +50,45 @@ export function FeaturedTaskCarousel({
4950

5051
return (
5152
<>
52-
<div className="relative mx-auto w-full max-w-[590px]">
53-
<button
54-
type="button"
55-
className="tb-focus-ring absolute left-3 top-1/2 z-10 -translate-y-1/2 rounded-full border-2 border-[#25314d] bg-white p-3 shadow-[0_4px_0_#25314d] sm:left-0 sm:-translate-x-1/2"
56-
onClick={() => goTo(activeIndex - 1)}
57-
aria-label="Show previous featured task"
58-
>
59-
<IconChevronLeft className="size-4" />
60-
</button>
61-
<button
62-
type="button"
63-
className="tb-focus-ring absolute right-3 top-1/2 z-10 -translate-y-1/2 rounded-full border-2 border-[#25314d] bg-white p-3 shadow-[0_4px_0_#25314d] sm:right-0 sm:translate-x-1/2"
64-
onClick={() => goTo(activeIndex + 1)}
65-
aria-label="Show next featured task"
66-
>
67-
<IconChevronRight className="size-4" />
68-
</button>
69-
<div className="tb-frame h-[410px] overflow-visible bg-[#fffdf9] px-6 py-7 sm:h-[450px] sm:px-8">
70-
<div className="flex h-full flex-col">
71-
<div className="flex flex-wrap justify-center gap-2">
53+
<div className="mx-auto w-full max-w-[620px]">
54+
<div className="tb-frame overflow-hidden bg-[#fffdf9] p-4 sm:p-6">
55+
<div className="flex flex-col gap-5">
56+
<div className="flex items-start justify-between gap-4">
57+
<div className="min-w-0">
58+
<div className="text-[11px] font-bold uppercase tracking-[0.24em] text-slate-500">
59+
Featured Task
60+
</div>
61+
</div>
62+
63+
<div className="flex shrink-0 items-center gap-2">
64+
<button
65+
type="button"
66+
className="tb-focus-ring rounded-full border-2 border-[#25314d] bg-white p-2.5 shadow-[0_4px_0_#25314d] transition-transform hover:-translate-y-px disabled:translate-y-0 disabled:cursor-default disabled:opacity-50"
67+
onClick={() => goTo(activeIndex - 1)}
68+
aria-label="Show previous featured task"
69+
disabled={mergedTasks.length < 2}
70+
>
71+
<IconChevronLeft className="size-4" />
72+
</button>
73+
<button
74+
type="button"
75+
className="tb-focus-ring rounded-full border-2 border-[#25314d] bg-white p-2.5 shadow-[0_4px_0_#25314d] transition-transform hover:-translate-y-px disabled:translate-y-0 disabled:cursor-default disabled:opacity-50"
76+
onClick={() => goTo(activeIndex + 1)}
77+
aria-label="Show next featured task"
78+
disabled={mergedTasks.length < 2}
79+
>
80+
<IconChevronRight className="size-4" />
81+
</button>
82+
</div>
83+
</div>
84+
85+
<div className="flex flex-wrap gap-2">
7286
{mergedTasks.map((task, index) => (
7387
<button
7488
key={task.repo}
7589
type="button"
7690
className={
77-
index === activeIndex
91+
index === normalizedActiveIndex
7892
? "tb-focus-ring rounded-full border-2 border-[#25314d] bg-[#d7ebf6] px-3 py-1.5 text-sm font-bold text-[#25314d] shadow-[0_4px_0_#25314d]"
7993
: "tb-focus-ring rounded-full border-2 border-[#25314d] bg-white px-3 py-1.5 text-sm font-bold text-[#25314d]"
8094
}
@@ -85,68 +99,49 @@ export function FeaturedTaskCarousel({
8599
))}
86100
</div>
87101

88-
<div className="mt-6 flex-1">
89-
<div className="tb-frame-soft flex h-full flex-col bg-[#f8fcff] p-5 sm:p-6">
90-
<div className="flex flex-wrap items-center gap-2 text-xs text-slate-600">
91-
<code className="rounded-full border-2 border-[#25314d] bg-white px-2.5 py-1 font-mono text-[11px] font-semibold text-[#25314d]">
92-
{taskHandle(activeTask)}
93-
</code>
94-
{activeTask.maturity ? <MaturityBadge maturity={activeTask.maturity} /> : null}
95-
{preview ? (
96-
<span className="rounded-full bg-[#ecffe5] px-3 py-1 text-[11px] font-bold text-[#25314d]">
97-
Preview ready
98-
</span>
99-
) : null}
100-
<span className="rounded-full bg-[#e2f3fb] px-3 py-1 text-[11px] font-bold text-[#25314d]">
101-
{formatShortDate(activeTask.last_updated)}
102+
<div className="tb-frame-soft flex min-h-[320px] flex-col bg-[#f8fcff] p-5 sm:min-h-[360px] sm:p-6">
103+
<div className="flex flex-wrap items-center gap-2 text-xs text-slate-600">
104+
<code className="rounded-full border-2 border-[#25314d] bg-white px-2.5 py-1 font-mono text-[11px] font-semibold text-[#25314d]">
105+
{taskHandle(activeTask)}
106+
</code>
107+
{activeTask.maturity ? <MaturityBadge maturity={activeTask.maturity} /> : null}
108+
{preview ? (
109+
<span className="rounded-full bg-[#ecffe5] px-3 py-1 text-[11px] font-bold text-[#25314d]">
110+
Preview ready
102111
</span>
103-
</div>
112+
) : null}
113+
<span className="rounded-full bg-[#e2f3fb] px-3 py-1 text-[11px] font-bold text-[#25314d]">
114+
{formatShortDate(activeTask.last_updated)}
115+
</span>
116+
</div>
104117

105-
<div className="mt-4 min-h-[6.1rem] font-heading text-3xl font-bold leading-tight text-[#25314d] sm:text-[2.65rem]">
106-
<span
107-
style={{
108-
display: "-webkit-box",
109-
WebkitLineClamp: 2,
110-
WebkitBoxOrient: "vertical",
111-
overflow: "hidden"
112-
}}
113-
>
114-
{taskTitle(activeTask)}
115-
</span>
116-
</div>
118+
<div className="mt-4 font-heading text-[2rem] font-bold leading-[1.02] text-[#25314d] sm:text-[2.45rem]">
119+
{taskTitle(activeTask)}
120+
</div>
121+
122+
<p className="mt-3 max-w-[38ch] text-sm leading-7 text-slate-700 sm:text-base">
123+
{activeTask.short_description}
124+
</p>
117125

118-
<p
119-
className="mt-3 min-h-[4.8rem] max-w-[36ch] text-sm leading-7 text-slate-700 sm:text-base"
120-
style={{
121-
display: "-webkit-box",
122-
WebkitLineClamp: 3,
123-
WebkitBoxOrient: "vertical",
124-
overflow: "hidden"
125-
}}
126+
<div className="mt-6 flex flex-wrap gap-3 sm:mt-auto">
127+
<button
128+
type="button"
129+
className="tb-focus-ring tb-button-primary"
130+
onClick={() => setDrawerRepo(activeTask.repo)}
126131
>
127-
{activeTask.short_description}
128-
</p>
129-
130-
<div className="mt-auto flex flex-wrap gap-3">
131-
<button
132-
type="button"
133-
className="tb-focus-ring tb-button-primary"
134-
onClick={() => setDrawerRepo(activeTask.repo)}
132+
Expand details
133+
</button>
134+
{preview ? (
135+
<a
136+
className="tb-focus-ring tb-button-secondary bg-[#d7ebf6]"
137+
href={preview.run_url}
138+
target="_blank"
139+
rel="noreferrer"
135140
>
136-
Expand details
137-
</button>
138-
{preview ? (
139-
<a
140-
className="tb-focus-ring tb-button-secondary bg-[#d7ebf6]"
141-
href={preview.run_url}
142-
target="_blank"
143-
rel="noreferrer"
144-
>
145-
<IconPlay className="size-4" />
146-
Run Preview
147-
</a>
148-
) : null}
149-
</div>
141+
<IconPlay className="size-4" />
142+
Run Preview
143+
</a>
144+
) : null}
150145
</div>
151146
</div>
152147
</div>

0 commit comments

Comments
 (0)