Ship the referral loop
shippedfor Northline
Turn happy customers into a growth channel.
difficulty
Search components and documentation
Pinned paper cards on a wood panel, texture from inset shadows alone
npx shadcn@latest add https://ethankd.com/r/quest-board.jsonShip the referral loop
shippedfor Northline
Turn happy customers into a growth channel.
Cut onboarding to one screen
shippedfor Reyes Co.
Five steps down to one, no drop in signups.
Untangle the pricing page
open questNobody can tell what plan they're on.
Your problem here
The board has room. Difficult submissions get reviewed first.
const QUESTS = [
{ id: 1, title: "Ship the referral loop", client: "Northline", desc: "Turn happy customers into a growth channel.", difficulty: 3, status: "shipped" as const },
{ id: 2, title: "Cut onboarding to one screen", client: "Reyes Co.", desc: "Five steps down to one, no drop in signups.", difficulty: 4, status: "shipped" as const },
{ id: 3, title: "Untangle the pricing page", desc: "Nobody can tell what plan they're on.", difficulty: 2, status: "open" as const },
];
const TILT = ["-1.2deg", "0.8deg", "-0.6deg", "1.4deg", "-1deg"];
/**
* A wooden notice board with pinned paper quest cards — texture entirely
* from inset box-shadows (a lighter bevel top edge, a darker inset border),
* each card tilted a few degrees and straightening on hover. Difficulty
* reads as filled squares rather than the original's pixel-art wheat icon.
*/
export function QuestBoard() {
return (
<div
className="rounded-2xl px-4 py-6 md:px-8 md:py-8"
style={{
background: "#dcb98a",
boxShadow: "inset 0 0 0 4px #754c60, inset 0 8px 0 #e8cfa6",
}}
>
<div className="grid gap-4 sm:grid-cols-2 md:gap-5">
{QUESTS.map((q, i) => (
<article
key={q.id}
className="relative rounded-sm bg-[#f3f4e7] px-4 pb-4 pt-5 transition-transform duration-150 hover:scale-[1.02] hover:rotate-0"
style={{ rotate: TILT[i % TILT.length], boxShadow: "inset 0 0 0 2px #c49a6c, 3px 4px 0 rgba(53,55,56,0.25)" }}
>
<span
aria-hidden
className="absolute -top-1.5 left-1/2 h-2 w-2 -translate-x-1/2"
style={{ background: "#a35b70", boxShadow: "inset -2px -2px 0 rgba(53,55,56,0.4)" }}
/>
<div className="mb-2 flex items-start justify-between gap-2">
<p className="text-[15px] leading-snug text-[#353738]">{q.title}</p>
{q.status === "shipped" ? (
<span
className="shrink-0 px-1.5 py-1 text-[11px] uppercase tracking-[0.12em]"
style={{ color: "#f3f4e7", background: "#67835c", rotate: "6deg", boxShadow: "inset 0 0 0 1px #545959" }}
>
shipped
</span>
) : (
<span
className="shrink-0 px-1.5 py-1 text-[11px] uppercase tracking-[0.12em]"
style={{ color: "#353738", background: "#f2cf8c", rotate: "-5deg", boxShadow: "inset 0 0 0 1px #ba7c54" }}
>
open quest
</span>
)}
</div>
{q.client && <p className="mb-1.5 text-[11px] uppercase tracking-[0.14em] text-[#545959]">for {q.client}</p>}
<p className="mb-3 text-[14px] leading-relaxed text-[#474a4b]">{q.desc}</p>
<div className="flex items-center gap-1" aria-label={`Difficulty ${q.difficulty} out of 5`}>
<span className="mr-1 text-[11px] uppercase tracking-wide text-[#545959]">difficulty</span>
{Array.from({ length: 5 }, (_, s) => (
<span key={s} aria-hidden className="h-2 w-2" style={{ background: "#353738", opacity: s < q.difficulty ? 1 : 0.22 }} />
))}
</div>
</article>
))}
<article
className="relative flex flex-col items-center justify-center gap-3 rounded-sm bg-[#f3f4e7] px-4 pb-4 pt-5 text-center"
style={{ rotate: "0.9deg", outline: "2px dashed #c49a6c", outlineOffset: "-6px" }}
>
<p className="text-[15px] text-[#353738]">Your problem here</p>
<p className="text-[14px] leading-relaxed text-[#474a4b]">The board has room. Difficult submissions get reviewed first.</p>
<button type="button" className="rounded-sm bg-[#353738] px-3 py-1.5 text-[12px] text-[#f3f4e7]">
Post a quest
</button>
</article>
</div>
</div>
);
}
Ship the referral loop
shippedfor Northline
Turn happy customers into a growth channel.
Cut onboarding to one screen
shippedfor Reyes Co.
Five steps down to one, no drop in signups.
Untangle the pricing page
open questNobody can tell what plan they're on.
Your problem here
The board has room. Difficult submissions get reviewed first.
On This Page