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.
What it does
An anti-slop frontend Skill for landing pages, portfolios and redesigns. Before any code the agent reads the brief (page kind, vibe words, audience, brand assets, quiet constraints), declares a one-line "design read" and sets three dials: design variance, motion intensity and visual density. It then picks the right foundation: an official design system (Material, Fluent, Carbon, Primer, GOV.UK, USWDS, shadcn/ui and others) when the brief matches one, or honest native CSS for aesthetics such as glassmorphism, bento or brutalism. The rules push against common AI defaults: purple gradients, three equal cards, Inter everywhere, fake dashboards built from divs, em-dashes and decorative eyebrows. It covers dark mode, reduced motion, redesign mode (preserve or overhaul) and a long pre-flight checklist.
Output
A design read, dial values with reasons, a section plan with layout families, the code (React, Next.js and Tailwind by default) and a list of checks it could not complete.
Good for
Marketing sites, portfolios and redesigns. It says itself that it is not for dashboards, data tables or multi-step product UI. Version 2 is marked experimental by its author.
Medium risk: instructions only, with no scripts of its own, but it tells the agent to install packages (npm install, npx shadcn init) and write code into your project. Read the install command before it runs and work on a branch. It is long (about 1,200 lines), so it uses a lot of context. Test-run once with a fictional tea brand: it gave a design read, dials, a plan and hero code, and listed what it could not check (no real images, no browser run). Nothing was installed or run. By Leonxlnx, MIT; v2 is experimental.