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 版为实验版。