1. Design read and dials
Reading this as: a consumer subscription landing page for design-conscious customers in their 30s, with a calm, warm, premium-but-not-stuffy language. It leans toward Tailwind v4 utilities and a sans display face (Geist), with a green palette. There's no official design system for this brief, so the aesthetic is custom and I'm not recreating any system's CSS.
Palette is "Forest": deep tea-green accent on a cool off-white, with a green-tinted off-black for dark mode. I avoided the beige, brass and espresso default, and the accent is locked to one green across the page.
| Dial | Value | Reason |
|---|---|---|
DESIGN_VARIANCE |
7 | Asymmetric, offset compositions signal design literacy. I'm not going to 9-10 because that tips into agency-experimental, which feels precious rather than approachable. |
MOTION_INTENSITY |
5 | A tea brand should feel unhurried. Spring entrances, hover and press feedback, and scroll reveals are enough, with no pinning or hijacks. |
VISUAL_DENSITY |
3 | Generous space reads as premium, and each section carries one message. |
2. First five sections
| # | Section | Layout family | Mobile (<768px) |
|---|---|---|---|
| 1 | Hero: promise, subscribe CTA, and a link to the teas | Asymmetric split with an overlapping inset image | Single column, text first, then the image |
| 2 | This month's three teas | Horizontal scroll-snap rail with mixed image aspect ratios (not three equal cards) | The rail is the mobile layout, with the next card peeking in |
| 3 | How a box works (verb-noun labels such as "Choose", "Brew", "Adjust", not "Step 1") | Sticky split: headline pinned left, items scroll right | Plain vertical stack, no sticky |
| 4 | Where the leaf comes from | Full-bleed photo band with one short statement beneath it, not on top of it | Same, with a shorter image height |
| 5 | What's in the box | Bento with exactly 5 cells (a 4+2 row, then 2+2+2), mixing photo, tinted green and plain cells | Single column, cells in the same order |
- Layout families: five different families, and no three consecutive image+text splits.
- Eyebrows: the hero uses none and I'd allow at most one across sections 2-5, which keeps a roughly 9-section page under its cap.
- Marquee: none planned here. If the page gets one, it belongs in the later logo section.
3. Hero code
Install:
npm install motion @phosphor-icons/react
next.config.ts: needed for the placeholder images.
images: {
remotePatterns: [
{ protocol: "https", hostname: "picsum.photos" },
{ protocol: "https", hostname: "fastly.picsum.photos" },
],
},
app/globals.css: one token strategy, CSS variables, light and dark.
@import "tailwindcss";
:root {
--surface: #f3f5f1;
--ink: #151a17;
--ink-muted: #4b564f;
--accent: #2c5b43;
--accent-ink: #f3f5f1;
--line: #d5dad3;
}
@media (prefers-color-scheme: dark) {
:root {
--surface: #0e1310;
--ink: #e9ede7;
--ink-muted: #a3aea6;
--accent: #9ccaa9;
--accent-ink: #0e1310;
--line: #26302a;
}
}
@theme inline {
--color-surface: var(--surface);
--color-ink: var(--ink);
--color-ink-muted: var(--ink-muted);
--color-accent: var(--accent);
--color-accent-ink: var(--accent-ink);
--color-line: var(--line);
--font-sans: var(--font-geist), ui-sans-serif, system-ui, sans-serif;
}
app/layout.tsx: font wiring only.
import { Geist } from "next/font/google";
const geist = Geist({ subsets: ["latin"], variable: "--font-geist", display: "swap" });
// <html lang="en" className={geist.variable}><body className="bg-surface font-sans text-ink antialiased">
components/reveal.tsx: the only client leaf, which handles entrance motion.
"use client";
import { motion, useReducedMotion } from "motion/react";
import type { ReactNode } from "react";
type RevealProps = {
children: ReactNode;
delay?: number;
y?: number;
scale?: number;
className?: string;
};
// Entrance only: sequences the hero so the eye lands on headline, then CTAs, then image.
export function Reveal({ children, delay = 0, y = 16, scale = 1, className }: RevealProps) {
const reduce = useReducedMotion();
return (
<motion.div
className={className}
initial={reduce ? false : { opacity: 0, y, scale }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ type: "spring", stiffness: 100, damping: 20, delay }}
>
{children}
</motion.div>
);
}
components/hero.tsx: a Server Component.
import Image from "next/image";
import { ArrowRight } from "@phosphor-icons/react/dist/ssr";
import { Reveal } from "./reveal";
// Shape rule: one radius (rounded-2xl) for buttons and images.
const button =
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-2xl px-6 py-3.5 text-base font-medium " +
"transition-transform duration-300 ease-[cubic-bezier(0.16,1,0.3,1)] " +
"hover:-translate-y-0.5 active:translate-y-px active:scale-[0.98] " +
"motion-reduce:transition-none motion-reduce:hover:translate-y-0 motion-reduce:active:scale-100 " +
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent";
export function Hero() {
return (
<section className="bg-surface text-ink">
<div className="mx-auto grid min-h-[100dvh] w-full max-w-7xl grid-cols-1 items-center gap-12 px-4 pb-16 pt-24 sm:px-6 lg:grid-cols-12 lg:gap-8 lg:px-8 lg:pb-12">
<div className="lg:col-span-6">
<Reveal>
<h1 className="text-balance text-5xl font-semibold leading-[1.05] tracking-tight sm:text-6xl xl:text-7xl">
Good tea, in small batches.
</h1>
</Reveal>
<Reveal delay={0.1}>
<p className="mt-6 max-w-[44ch] text-lg leading-relaxed text-ink-muted">
Three single-estate loose-leaf teas, packed in small batches and sent to your door
every month.
</p>
</Reveal>
<Reveal delay={0.2} className="mt-10 flex flex-wrap items-center gap-3">
<a href="#plans" className={`${button} bg-accent text-accent-ink`}>
Start my box
<ArrowRight size={18} weight="regular" aria-hidden />
</a>
<a href="#teas" className={`${button} border border-ink/30 text-ink hover:border-ink`}>
See the teas
</a>
</Reveal>
</div>
<div className="relative lg:col-span-5 lg:col-start-8">
<Reveal y={0} scale={1.04}>
{/* bg-line reserves the box so there is no layout shift while the image loads */}
<div className="relative h-[56dvh] w-full overflow-hidden rounded-2xl bg-line lg:h-[min(72dvh,680px)]">
{/* TODO: replace with real photo, 1200x1500, tea pouring into a ceramic cup */}
<Image
src="https://picsum.photos/seed/stillwater-tea-pour/1200/1500"
alt="Loose-leaf tea being poured into a handmade ceramic cup"
fill
priority
sizes="(min-width: 1024px) 40vw, 100vw"
className="object-cover"
/>
</div>
</Reveal>
<Reveal delay={0.35} className="absolute -bottom-8 -left-6 hidden sm:block lg:-left-16">
<div className="relative h-40 w-32 overflow-hidden rounded-2xl border-4 border-surface bg-line lg:h-48 lg:w-40">
{/* TODO: replace with real photo, 400x520, tin of dried leaf */}
<Image
src="https://picsum.photos/seed/stillwater-tea-tin/400/520"
alt="A tin of dried loose-leaf tea"
fill
sizes="160px"
className="object-cover"
/>
</div>
</Reveal>
</div>
</div>
</section>
);
}
The nav is a separate fixed component. The hero's pt-24 leaves room for a 64px bar.
4. Pre-flight: what I could not tick
- Real images: no image-generation tool is available here. I used Picsum seeds, which return random photos, not tea. The alt text describes the intended photos, so it will be wrong until real images replace them (
TODOcomments mark both slots). - Both modes tested: not done. I haven't run the app or viewed either theme. The contrast figures are my hand estimates (all above 7:1 for text on surface and for the button labels), not measured.
- Hero fits the viewport: I reasoned this from the sizing (a 5-word headline that wraps to at most 2 lines, a 15-word subtext,
pt-24, image height capped at 72dvh). I haven't checked it in a browser at real viewport sizes. - Core Web Vitals and Lighthouse: not run. The hero image uses
priorityand a reserved box, so LCP and CLS should be fine, but that's unmeasured. - Dependency verification: I didn't scaffold a project or inspect a
package.json. The install line above is the assumption to check. - Navigation (one line, height 80px or less): not built, so not checked.
- Page-level checks: I only built the hero, so these are planned, not verified:
- section-layout repetition
- eyebrow count
- bento cell count
- duplicate CTA intent (use "Start my box" everywhere for subscribe intent)
- logo wall
- quotes
- empty, loading and error states beyond the image placeholder
Everything else applicable to the hero ticks. That covers:
- zero em-dashes
- theme, accent and radius locks
- button contrast and no CTA wrap
- palette and font rules
- hero stack (headline, subtext, two CTAs, no eyebrow)
min-h-[100dvh]- reduced-motion handling
- icons from Phosphor only
- motion confined to one client leaf
Test run: a fictional tea-subscription brief, asking for the design read, dials, a section plan and only the hero code. Nothing was built or opened in a browser; the model itself listed what it could not check (real images, both themes, Lighthouse). Hero uses placeholder Picsum images, so the alt text will not match until real photos are added.
它做什麼
一個專治「AI 味」的前端 Skill,適用於登陸頁、作品集與改版。寫程式之前,智能體先讀需求(頁面類型、風格詞、受眾、品牌資產、隱含限制),用一句話說出「設計判斷」,並設定三個刻度:版式變化度、動態強度、資訊密度。接著選對基礎:需求對應某個官方設計系統(Material、Fluent、Carbon、Primer、GOV.UK、USWDS、shadcn/ui 等)就用官方套件;如果是玻璃擬態、便當格、粗獷主義這類美學,就用原生 CSS 並如實說明。規則專門對抗常見的 AI 預設寫法:紫色漸層、三張等寬卡片、到處都是 Inter、用 div 拼出來的假儀表板、破折號與裝飾性小標籤。它也涵蓋深色模式、減少動態、改版模式(保留或重做)以及很長的發布前檢查清單。
輸出
設計判斷、附理由的刻度值、標明版式類型的章節規劃、程式碼(預設 React、Next.js 與 Tailwind),以及它未能完成的檢查項目。
適合什麼場景
行銷網站、作品集與改版。它自己也寫明不適合儀表板、資料表與多步驟產品介面。第 2 版被作者標為實驗版。
中風險:純指令檔,本身沒有腳本,但它會讓智能體安裝套件(npm install、npx shadcn init)並把程式碼寫進你的專案。請先看清安裝指令再讓它執行,並在分支上操作。篇幅很長(約 1200 行),佔用較多上下文。已用一個虛構的茶葉訂閱品牌試用一次:給出設計判斷、刻度、規劃與首屏程式碼,並列出未能檢查的內容(沒有真實圖片,沒有在瀏覽器中執行)。沒有安裝或執行任何東西。作者 Leonxlnx,MIT 授權,第 2 版為實驗版。