Build a website from scratch that looks designed rather than generated. Reads the brief, picks a visual direction, and builds the real thing while enforcing thirty patterns that give away AI-made pages. Use for any new landing page, marketing site, portfolio, or app shell, and whenever someone asks for a site that should not look AI-generated or templated.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add whatsuppiyush/make-my-website-non-ai-looking-claude-skill --skill make-website --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Make Website?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/whatsuppiyush-make-website)More formats (shields.io, HTML) on the badges page.
---
name: make-website
description: Build a website from scratch that looks designed rather than generated. Reads the brief, picks a visual direction, and builds the real thing while enforcing thirty patterns that give away AI-made pages. Use for any new landing page, marketing site, portfolio, or app shell, and whenever someone asks for a site that should not look AI-generated or templated.
---
# Make a website that does not look AI-made
> Everything needed to take a site from a one-line brief to something shippable: how to read
> the brief, how to choose a direction, how to build each layer, and the thirty patterns that
> will give the whole thing away if you let them through.
>
> Two halves. **Taste** decides what the page looks like. **Tells** decide what it must never
> look like and what it must contain. Neither works alone. Taste without tells produces a
> handsome page with a fake testimonial and no privacy policy. Tells without taste produces a
> page that broke no rules and moves nobody.
---
## 0. The loop
Follow this order. Most bad output comes from skipping straight to step 5.
1. **Read the brief.** One line: what, for whom, what feeling. §1
2. **Set the dials.** Variance, motion, density. §2
3. **Pick the foundation.** Stack, and whether a real design system applies. §3
4. **Pick the direction.** One of five aesthetic families, chosen not defaulted. §4
5. **Build**, in this order: type → colour → layout → images → states → motion. §5 to §10
6. **Build the trust surface.** Demo, proof, legal. §11
7. **Spend the tell budget deliberately.** §12
8. **Pre-flight.** §14
**If the user said only "build me a website"**, do not ask five questions. Infer, state your
read in one line, name your assumptions, and build. Ask **one** question only when the read
genuinely forks, for example "closer to Linear-clean or Awwwards-experimental?"
**If the user says "make it not look AI-generated"** and nothing else, that is a brief. It
means: real images, real copy, a real trust surface, one committed aesthetic, and none of §12
Class A. Build to that and say what you assumed.
---
## 1. Read the brief
Before any code, work out what is actually wanted. The most common failure is jumping to a
default aesthetic instead of reading the room.
**Signals to read.**
1. **Page kind.** Landing (SaaS, consumer, agency, event), portfolio (developer, designer,
studio), editorial, app shell, docs.
2. **Vibe words they used.** Minimalist, calm, Linear-style, Awwwards, brutalist, premium,
Apple-y, playful, serious B2B, editorial, glassy, dark tech.
3. **References.** URLs, screenshots, competitors, products they named. These outrank your
taste.
4. **Audience.** A procurement panel, a design-conscious consumer, a recruiter scanning for
fifteen seconds. The audience picks the aesthetic.
5. **Existing brand.** Logo, colour, type, photography. If any exists it is starting material,
not optional input.
6. **Quiet constraints.** Accessibility-critical, public sector, regulated, children,
trust-first commerce. These override aesthetic preference entirely.
**Then state the read in one line**, before writing anything:
> Reading this as: **\<page kind>** for **\<audience>**, in a **\<vibe>** language, leaning
> toward **\<direction>**.
Examples.
- *B2B SaaS landing for technical buyers, restrained editorial language, leaning toward a
neutral base with one saturated accent and near-zero motion.*
- *Solo designer portfolio for hiring managers, kinetic-editorial language, leaning toward
large type, real project photography, scroll-driven reveals.*
- *Redesign of a clinic site, trust-first language, leaning toward high contrast, real
photography of the actual practice, no decoration whatsoever.*
**Anti-default discipline.** Do not reach for: purple gradients, a centred hero over a dark
mesh, three equal feature cards, glass on everything, Inter at 600. Those are what the model
produces when it has not read the brief. If your first instinct matches that list, you have
not read the brief.
---
## 2. Set the dials
Three values, 1 to 10. Every later decision is gated by them. State them explicitly with a
reason. Do not silently use the baseline.
- **VARIANCE** how experimental the layout is. 1 is perfectly symmetrical, 10 is asymmetric
and editorial.
- **MOTION** how alive it feels. 1 is hover states only, 10 is scroll-driven choreography.
- **DENSITY** how much is on screen. 1 is gallery, 10 is cockpit.
**Baseline 8 / 6 / 4.** Override from the read.
| The brief reads as | VARIANCE | MOTION | DENSITY |
|---|---|---|---|
| Minimalist, calm, editorial, Linear-style | 5-6 | 3-4 | 2-3 |
| Premium consumer, luxury, brand-led | 7-8 | 5-7 | 3-4 |
| Agency, experimental, Awwwards, playful | 9-10 | 8-10 | 3-4 |
| SaaS landing, mainstream marketing | 7 | 6 | 4 |
| Developer portfolio | 6 | 5 | 4 |
| Docs, dashboard, tool | 3-5 | 2-3 | 7-9 |
| Trust-first, public sector, regulated | 3-4 | 2-3 | 4-5 |
**What the dials actually mean in code.**
- **VARIANCE 1-3.** Symmetric grid, equal padding, centred. **4-7.** Negative-margin overlaps,
mixed aspect ratios, left-aligned headers over centred content. **8-10.** Masonry,
fractional grid columns (`2fr 1fr 1fr`), deliberate empty zones (`pl-[20vw]`).
Above 4, asymmetric layouts must collapse to strict single column below 768px.
- **MOTION 1-3.** Hover and active only. **4-7.** Transitions on transform and opacity with a
custom cubic-bezier, staggered load-ins. **8-10.** Scroll-driven reveals, parallax, pinning.
- **DENSITY 1-3.** `py-32` to `py-48` section gaps. **4-7.** `py-16` to `py-24`. **8-10.**
Tight padding, hairlines instead of cards, mono for all numbers.
**Motion claimed is motion shown.** If MOTION is above 4 the page must actually move: entry
transitions, scroll reveals, real hover physics. If you cannot ship working motion in scope,
drop the dial to 3 and ship a clean static page. Never half-build motion that breaks.
---
## 3. Pick the foundation
### 3.A When a real design system applies
Do not hand-roll CSS for something that has an official package.
| The brief reads as | Use | Why |
|---|---|---|
| Microsoft or enterprise SaaS | `@fluentui/react-components` | Official Fluent, tokens, a11y done |
| Google-flavoured product | `@material/web` + Material 3 tokens | Official, themeable |
| IBM-style enterprise analytics | `@carbon/react` + `@carbon/styles` | Mature data-density patterns |
| Shopify app surface | Polaris | Required for Shopify admin |
| Atlassian-style product | `@atlaskit/*` + `@atlaskit/tokens` | Official Atlassian |
| GitHub-style devtool | `@primer/css`, or `@primer/react-brand` for marketing | Official Primer |
| UK public sector | `govuk-frontend` | Regulatorily expected |
| US public sector, trust-first | `uswds` | Same |
| Accessible React foundation | `@radix-ui/themes` | Primitives plus a polished theme |
| Modern SaaS, you own the components | shadcn/ui | You own the code. Never ship default state |
| Indie and small-team default | Tailwind v4 utilities | The default here |
**One system per project.** Never mix Fluent with Carbon, or shadcn into a Material app. If
you import a system, use its tokens rather than overriding ninety percent of them.
### 3.B Default stack when no system applies
- **Framework.** React or Next. Default to Server Components. Anything using motion, scroll
listeners or pointer physics is an isolated leaf with `"use client"` at the top. Providers
and global state live inside a client component.
- **Styling.** Tailwind v4. Use `@tailwindcss/postcss` or the Vite plugin, not the old
`tailwindcss` PostCSS plugin.
- **Animation.** Motion, imported from `motion/react`. GSAP with ScrollTrigger only for real
pin and scrub work. Never mix GSAP and Motion in one component tree.
- **Fonts.** `next/font`, or self-host with `@font-face` and `font-display: swap`. Never a
`<link>` to Google Fonts in production.
- **Icons.** One family, one stroke width, project-wide. Prefer `@phosphor-icons/react`,
`hugeicons-react`, `@radix-ui/react-icons`, `@tabler/icons-react`. Lucide only if the project
already uses it. **Never hand-draw SVG icon paths.**
- **State.** `useState` for isolated UI. **Never** `useState` for continuous values driven by
input (mouse position, scroll progress, magnetic hover). Use `useMotionValue` and
`useTransform`, which live outside the React render cycle. `useState` re-renders on every
frame and collapses on mobile.
- **Check `package.json` before importing anything.** If a package is missing, output the
install command first.
### 3.C Layout mechanics
- Breakpoints `sm 640 · md 768 · lg 1024 · xl 1280 · 2xl 1536`.
- Contain with `max-w-7xl mx-auto` or a declared `max-w-[1400px]`.
- **Never `h-screen` for a full-height hero. Always `min-h-[100dvh]`**, or the iOS address bar
makes the layout jump.
- **Grid, not flex percentage maths.** Never `w-[calc(33%-1rem)]`. Use
`grid grid-cols-1 md:grid-cols-3 gap-6`.
- Declare the sub-768px collapse for every multi-column layout, in the same component. No
"Tailwind will handle it."
---
## 4. Pick the direction
Choose one aesthetic family deliberately and commit. The failure mode is not picking an ugly
one, it is picking none and landing on the default.
| Family | What it is | Reach for it when |
|---|---|---|
| **Editorial** | Type-led, asymmetric grid, generous whitespace, real photography, restrained motion | Content-first products, portfolios, brands with something to say |
| **Utilitarian** | Dense, hairlines instead of cards, mono for data, near-zero decoration | Tools, dashboards, developer products, docs |
| **Premium consumer** | Big imagery, considered motion, material depth, tight type | DTC, hardware, wellness, anything with a physical product |
| **Structural** | Rigid grid made visible, extreme type-scale contrast, utilitarian colour | Bold portfolios, agencies, editorial sites that should feel engineered |
| **Quiet corporate** | High contrast, conventional layout, zero decoration, trust signals forward | Regulated, public sector, healthcare, finance, B2B with a long sales cycle |
**When exploring, build several.** If the user wants options, produce **4 to 6 genuinely
different** directions on throwaway routes (`/v1`, `/v2`, …) with a small floating switcher,
compare, promote the winner, delete the rest. Different means different layout skeleton, not
the same page in another colour.
**Variance mandate.** No two directions may share a layout family. If `/v2` and `/v4` are both
"centred hero, three cards, testimonial row," you produced one direction twice.
---
## 5. Build: typography
**Scale.** Display `text-4xl md:text-6xl tracking-tighter leading-none`. Body
`text-base leading-relaxed max-w-[65ch]`. Hierarchy comes from **weight and colour first**,
size second. Load 400, 500 and 600 and use all three rather than solving everything with a
size jump.
**Font choice.** Inter, Geist and Space Grotesk are **Class B tells** (§12, B7). They are
excellent, which is the problem: they are the default good answer, so they carry no signal.
Note the trap, Geist got popular *as the cure for Inter*, so the anti-Inter advice grew its
own uniform. Satoshi, Cabinet Grotesk and Plus Jakarta Sans are on the same path.
*Burned as defaults.* Inter, Geist, Space Grotesk, Poppins, Montserrat, Roboto, Open Sans,
Lato, DM Sans, Manrope, Satoshi.
*Burning now.* Sora, Outfit, Plus Jakarta Sans, Cabinet Grotesk, Instrument Serif, Fraunces,
Bricolage Grotesque.
*Free and still clean.*
- **Google Fonts.** Instrument Sans, Schibsted Grotesk, Onest, Host Grotesk, Funnel Display,
Gabarito, Familjen Grotesk, Archivo and Archivo Expanded, Libre Franklin, Public Sans,
Newsreader, Literata, Source Serif 4, Bodoni Moda.
- **Fontshare**, free for commercial use. General Sans, Switzer, Chillax, Ranade, Supreme,
Author, Nippo, Amulya, Pally, Excon, Bespoke Sans, Gambetta, Zodiak, Erode, Sentient, Boska,
Melodrama, Stardom, Panchang.
- **Independent and open.** Uncut Sans, Redaction, Departure Mono, Commit Mono, Martian Mono,
Fragment Mono. Velvetyne: Basteleur, Karrik, Lack, Terminal Grotesque. Collletttivo: Apfel
Grotezk, Mattone, Sligoil.
*Paid, worth it on a flagship.* ABC Diatype, Söhne, GT America, Neue Haas Grotesk, Untitled
Sans, Founders Grotesk, Suisse Int'l, Graphik, Aeonik, PP Neue Montreal, Editorial New.
**Rules.**
1. **Rotate.** Never the same display face on two consecutive projects.
2. **Two families maximum**, three only if the third is a mono used strictly for code and data.
3. **Never mix families for emphasis.** To stress a word inside a headline use italic or a
heavier weight *of the same face*. Dropping a serif word into a sans headline is the single
most common amateur move in generated type.
4. **Serif is not the default for "creative."** "It feels premium" is not a reason. Serif is
right for genuinely editorial, luxury, publication or heritage work where you can say why
*this* serif fits *this* brand. Otherwise use a sans display face. Sans is not boring for
the same reason black is not boring in fashion.
5. **Italic descenders clip.** Any italic word containing `y g j p q` needs `leading-[1.1]`
minimum plus `pb-1` reserve, or the tail gets cut.
---
## 6. Build: colour
**One accent.** Saturation under 80% by default. Get variety from tint, shade and opacity of
that accent plus your neutrals, never from a second hue.
**Colour consistency lock.** Once the accent is chosen it is used on the whole page. A
warm-grey site does not get a blue CTA in section seven. A rose-accented site does not get a
teal badge in the footer. Audit every component before shipping.
**Theme lock.** The page has one theme. If it is dark, every section is dark. No warm-paper
section sandwiched between dark ones. The single exception is a deliberate one-time theme
switch used as a compositional device, not random alternation. Section-level tints within one
family (`bg-zinc-950` beside `bg-zinc-900`) are fine.
**Never pure `#000000` or `#ffffff`.** Off-black and off-white preserve depth and give you an
elevation scale. Pure white as the page ground is B3.
**Purple on near-black is banned** (§12, A8). So is rainbow (A5), neon glow (A9) and pastel
palettes as the accent system (B15).
**For premium-consumer briefs specifically**, avoid the default warm-craft palette: cream and
beige grounds (`#f5f1ea`, `#faf7f1`, `#efeae0`), brass and clay and oxblood accents
(`#b08947`, `#b6553a`, `#9a2436`), espresso text (`#1a1714`). Every generated cookware,
wellness and artisan site uses it. Rotate instead: cold luxury (silver, chrome, smoke), forest
(deep green, bone, amber), black and tan, cobalt and cream, terracotta and slate, or pure
monochrome with one saturated pop.
**Dark mode.** Design both from the start unless told otherwise. Pick one strategy, Tailwind
`dark:` variants or CSS custom properties, and keep it. Maintain WCAG AA on body text, hold
hierarchy parity so anything that pops in light pops in dark, and keep the brand colour
recognisable rather than desaturating it into the background. Test both before finishing.
---
## 7. Build: layout and composition
### 7.A The hero
- **It fits in the viewport.** Headline max 2 lines desktop. Subtext max 20 words and max 4
lines. CTA visible without scrolling. If the copy will not fit, the copy is the problem.
- **Plan font size and image size together.** Default range `text-4xl md:text-5xl lg:text-6xl`.
Reserve `text-6xl md:text-7xl` for headlines of 3 to 5 words. A 4-line hero headline is
always a font-size error, never a copy-length error.
- **Top padding caps at `pt-24` desktop.** More and the content floats halfway down and reads
as a bug.
- **Max 4 text elements.** Pick zero or one of {eyebrow, brand strip}, then headline, subtext,
CTAs (1 primary, at most 1 secondary). **Banned inside the hero:** a tiny tagline under the
CTAs, a trust micro-strip, a pricing teaser, a feature bullet list, an avatar row. All of
those become their own section directly below.
- **The logo wall goes under the hero, never inside it.**
- **Avoid the centred hero when VARIANCE is above 4.** Use an asymmetric split, left content
with a right asset, or a scroll-pinned structure. Centred is fine for a manifesto or a launch
announcement where the message is the design.
### 7.B Sections
- **Navigation on one line at desktop, height 64-80px.** If items do not fit at 1024px,
shorten labels or drop secondary items. A two-line desktop nav is broken.
- **Layout repetition ban.** Once a layout family is used (three-column cards, full-width
quote, split text-image), it appears at most once more. Eight sections need at least four
different families.
- **Zigzag cap.** Alternating left-image/right-text is fine twice. A third consecutive
image-text split fails.
- **Eyebrow restraint.** An eyebrow is the small uppercase wide-tracking label above a section
headline. **Maximum one per three sections**, hero counts as one. This is mechanically
checkable: count `uppercase tracking` labels, the count must be at or under
`ceil(sections / 3)`. Usually the right answer is to delete it, since a section's position on
the page already categorises it.
- **No section-number eyebrows.** `00 / INDEX`, `001 · Capabilities`, `Phase 02`. Enumerate
nothing.
- **Split-header ban.** "Left big headline, right small explainer paragraph" as a section
header is banned by default. Stack them, headline then body at `max-w-[65ch]`. Use a
two-column header only when the right column carries a real visual, not filler text.
- **Bento cell count.** A bento grid has exactly as many cells as you have content for. Three
items means three cells. If a cell is empty, reshape the grid, never paste a blank tile. At
least two cells must carry something other than text.
- **Long lists need a different component, not a longer list.** Beyond five items, reach for
grouped two-column, a card grid, tabs or accordion, scroll-snap pills, or a marquee. A
ten-row table with a hairline under every row is the laziest available layout.
- **Marquee maximum one per page.**
### 7.C Cards, shape and depth
- **Use a card only when elevation communicates real hierarchy.** Otherwise group with
`border-t`, `divide-y`, a background shift, or space. Above DENSITY 7, card containers are
banned outright, data breathes in plain layout.
- **Shape consistency lock.** One radius system, documented, applied everywhere. Options:
all-sharp, all-soft, all-pill for interactive, or a declared mixed scale ("pill buttons, 4px
inputs, 12px cards, 0 on full-bleed media"). Round buttons in a square layout is broken.
- **Shadows tint toward the background hue**, tight radius, under 10% opacity, consistent
direction. Never pure black on a light ground. See B4.
---
## 8. Build: images and real assets
**A landing page is a visual product. A text-only page with div-drawn previews is not
finished work.**
Priority order.
1. **Generate them.** If any image tool is available in the environment, use it for
section-specific assets at the right aspect ratio: hero photography, product shots,
textures, mood images.
2. **Real photography second.** `https://picsum.photos/seed/{descriptive-seed}/{w}/{h}` with a
seed that describes the section. Brand or stock URLs when provided.
3. **Last resort, say so.** Leave clearly labelled placeholder slots
(`{/* TODO: hero product photo, 1600x1200 */}`) and end your response with the list of
images the page needs. Do **not** fill the gap with hand-rolled decorative SVG or div-based
fake screenshots.
**Even minimalist sites need real images.** A pure-text page is not minimalism, it is
incomplete. Generate restrained black-and-white photography if the brief is quiet.
**Logo walls use real marks.** Simple Icons (`https://cdn.simpleicons.org/{slug}/{hex}`) or
devicon for tech logos. For invented brands, generate a simple monogram as inline SVG matching
the page style. Plain text wordmarks in a row look generic. **Logos only**, no category label
under each one. Ensure they work in both themes.
**Banned:** div-based fake product UI, fake dashboards, fake terminals (A6), pills and tags
overlaid on photos, decorative photo-credit captions (`Frame XII · 35mm`), version footers
(`v1.4.2`, `Build 0048`) on a marketing page.
---
## 9. Build: states
Generated pages ship exactly one state: finished, populated, successful. Real software spends
much of its life loading and some of it failing. **This is C2 and it is required, not
optional.**
- **Skeletons shaped like the content they replace.** Not a centred spinner, not three grey
bars regardless of layout. If a card has an avatar, a title and two lines, the skeleton has
an avatar, a title and two lines, occupying the same box so nothing shifts.
- **Empty states that say how to leave empty.** An empty list is an onboarding surface.
- **Errors inline at the thing that failed.** Toasts are for transient notices, not validation.
- **In-flight buttons** disable and say what is happening.
- **Tactile feedback.** On `:active`, `-translate-y-[1px]` or `scale-[0.98]`.
**Contrast checks before shipping, both mandatory.**
- **Buttons.** Every CTA's text against its own background, WCAG AA (4.5:1 body, 3:1 for 18px
and above). White button with white text, or a transparent button on the page ground with no
border, are the common failures. Ghost buttons over photography need a scrim or a stroke.
- **Forms.** Inputs, placeholders, focus rings, helper text and error text all pass AA against
the section background. Labels **above** inputs, error text below, `gap-2`. Never
placeholder-as-label.
**CTA discipline.** Labels fit on one line at desktop, three words maximum for a primary,
ideally one or two. **No two CTAs with the same intent on one page:** "Get in touch" plus
"Let's talk" plus "Start a project" is one intent and needs one label used everywhere.
---
## 10. Build: motion
**Every animation must be justifiable in one sentence.** Valid reasons: hierarchy, storytelling
sequence, feedback on an action, state transition. Invalid: it looked cool. If you cannot
articulate the reason, delete it.
**Hard bans.**
- `window.addEventListener("scroll", ...)`. Runs every frame, jank-prone, unbatched. Use
Motion's `useScroll()`, GSAP ScrollTrigger, IntersectionObserver, or CSS
`animation-timeline: view()`.
- Scroll progress computed into React state via `window.scrollY`. Same reason.
- `requestAnimationFrame` loops touching React state.
- Custom mouse cursors.
**Animate only `transform` and `opacity`.** Never `top`, `left`, `width`, `height`.
`will-change` only on elements that genuinely animate.
**Reduced motion is mandatory above MOTION 3.** Wrap with `useReducedMotion()` and degrade to
static, or gate CSS behind `@media (prefers-reduced-motion: no-preference)`. Infinite loops,
parallax, scroll hijack and magnetic physics all collapse to static.
**Grain and noise filters** go on a fixed, `pointer-events-none` pseudo-element only, never on
a scrolling container, or mobile framerate dies.
### 10.A Scroll reveal, the light default
Prefer Motion's `whileInView` over GSAP whenever there is no pinning.
```tsx
"use client";
import { motion, useReducedMotion } from "motion/react";
export function Reveal({ items }: { items: string[] }) {
const reduce = useReducedMotion();
return (
<ul className="grid gap-6">
{items.map((item, i) => (
<motion.li
key={item}
initial={reduce ? false : { opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.3 }}
transition={{ duration: 0.6, delay: i * 0.06, ease: [0.16, 1, 0.3, 1] }}
>
{item}
</motion.li>
))}
</ul>
);
}
```
### 10.B Sticky stack, when cards should physically stack
The usual failure is the trigger firing mid-scroll instead of pinning at the top. The fix is
`start: "top top"`.
```tsx
"use client";
import { useRef, useEffect } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { useReducedMotion } from "motion/react";
gsap.registerPlugin(ScrollTrigger);
export function StickyStack({ cards }: { cards: React.ReactNode[] }) {
const ref = useRef<HTMLDivElement>(null);
const reduce = useReducedMotion();
useEffect(() => {
if (reduce || !ref.current) return;
const ctx = gsap.context(() => {
const els = gsap.utils.toArray<HTMLElement>(".stack-card");
els.forEach((card, i) => {
if (i === els.length - 1) return;
ScrollTrigger.create({
trigger: card,
start: "top top",
endTrigger: els[els.length - 1],
end: "top top",
pin: true,
pinSpacing: false,
});
gsap.to(card, {
scale: 0.92,
opacity: 0.55,
ease: "none",
scrollTrigger: { trigger: els[i + 1], start: "top bottom", end: "top top", scrub: true },
});
});
}, ref);
return () => ctx.revert();
}, [reduce]);
return (
<div ref={ref} className="relative">
{cards.map((card, i) => (
<div key={i} className="stack-card sticky top-0 min-h-[100dvh] flex items-center justify-center">
{card}
</div>
))}
</div>
);
}
```
### 10.C Horizontal pan, when vertical scroll drives horizontal travel
```tsx
"use client";
import { useRef, useEffect } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { useReducedMotion } from "motion/react";
gsap.registerPlugin(ScrollTrigger);
export function HorizontalPan({ children }: { children: React.ReactNode }) {
const wrap = useRef<HTMLDivElement>(null);
const track = useRef<HTMLDivElement>(null);
const reduce = useReducedMotion();
useEffect(() => {
if (reduce || !wrap.current || !track.current) return;
const ctx = gsap.context(() => {
const distance = track.current!.scrollWidth - window.innerWidth;
gsap.to(track.current, {
x: -distance,
ease: "none",
scrollTrigger: {
trigger: wrap.current,
start: "top top",
end: () => `+=${distance}`,
pin: true,
scrub: 1,
invalidateOnRefresh: true,
},
});
}, wrap);
return () => ctx.revert();
}, [reduce]);
return (
<section ref={wrap} className="relative overflow-hidden">
<div ref={track} className="flex h-[100dvh] items-center">{children}</div>
</section>
);
}
```
Every `useEffect` animation needs a cleanup. `gsap.context()` plus `ctx.revert()` is the
pattern.
---
## 11. Build: the trust surface
**This is the part generated sites always skip, and it is what actually makes a page read as
machine-made.**
A visitor cannot articulate "this uses Geist at 600 weight." They can absolutely feel *nobody
actually runs this*. That feeling comes from here, not from the font.
**Four things, all required (§12 Class C).**
1. **A real product demo.** A real screenshot, a real screen recording, a live embedded
component, or a real playground. Not a div reconstruction, not a Figma mockup passed off as
a screenshot, not a stock photo of someone at a laptop. If you are pre-launch, say so: "In
private beta, here is a 40-second walkthrough" is honest and converts. Faking the product
surface poisons every other claim on the page.
2. **Loading, empty and error states.** §9.
3. **A privacy policy** at `/privacy`, linked in the footer of every page, naming the real
entity, what is collected, why, who it is shared with, and how to delete it.
4. **Terms of service** at `/terms`, linked in the footer.
**Proof, done honestly.** Never invent a testimonial (A3). One real quote with a full name, a
real company and a findable profile beats six invented ones. With zero real quotes, use other
proof: a number you can defend, a public changelog, a demo recording, a repository, a named
logo you have permission for. Quotes cap at three lines, attribution is name plus role, never
a name alone.
**Copy self-audit before shipping.** Re-read every visible string: headlines, subheads, button
labels, body, captions, alt text, footer, error messages. Flag and rewrite anything that is
grammatically broken, has an unclear referent, is cute-but-wrong wordplay, or reads like a
model trying to sound thoughtful. Plain functional copy beats clever AI copy every time.
**Numbers are real or labelled.** `92%`, `4.1x`, `48k`, `5.8mm` either come from real data, or
are explicitly marked as sample, or they do not appear. Do not fake engineering precision the
brand does not claim.
**Banned copy patterns.** Negation positioning (A7). Filler verbs: elevate, seamless, unleash,
supercharge, transform, unlock, next-gen, revolutionise, game-changing. Startup-slop names:
Acme, Nexus, SmartFlow, Cloudly. Generic people: John Doe, Sarah Chan. Fake-perfect numbers:
99.99%, exactly 50%. Poetic section labels: "From the field", "On our desks", "Currently on the
bench". Locale and weather strips (`LIS 14:23 · 18°C`) unless the brief is genuinely about a
place. Scroll cues of any kind, the user knows what scroll is.
---
## 12. The thirty tells
The constraint layer. Three classes.
| Class | Rule | Count |
|---|---|---|
| **A. Banned** | Never appears. One named override each at most. | 11 |
| **B. Budgeted** | May appear. Costs budget. Must be justified. | 15 |
| **C. Required** | Must be present. Absence is the tell. | 4 |
### 12.A Why tiered instead of banned outright
A blanket ban produces contrarian design, which is its own tell. A page with sharp corners, no
shadows, no hover states, an unfashionable font and a two-tier pricing table does not read as
considered. It reads as built by someone avoiding things, which is exactly as legible as built
by someone copying things.
None of these patterns is ugly. Each was a good decision the first time somebody made it, for
a reason. They became tells when they got copied without the reason. **So the test is never "is
this present," it is "can you name the reason."**
### 12.B Class A, banned (11)
| | Tell | Signature | Instead | Override |
|---|---|---|---|---|
| **A1** | Gradient stripe | `h-1 bg-gradient-to-r from-… via-… to-…` under the nav or atop a card | Nothing. If separation is needed, a 1px hairline at 8-12% opacity, or a shadow that appears on scroll | None |
| **A2** | Sparkle icons | `<Sparkles />`, `<WandSparkles />`, `✨` near AI, generate, magic, smart | Name the action. "Generate 12 variants" beats "✨ AI Magic" | None |
| **A3** | Fabricated testimonials | Quotes nobody said, invented names, stock faces, "Sarah K., Marketing Director" | Ship no testimonial section until one real quote exists. Use other proof meanwhile | None |
| **A4** | Em-dashes | `—` and `–` as separators anywhere visible | A period splitting the sentence, a comma, parentheses, a colon | None |
| **A5** | Rainbow colouring | 3+ hues in one element or palette, `from-blue via-purple to-pink` | One accent, varied by tint, shade and opacity | Genuine spectrum product, or a category system users learn |
| **A6** | Fake terminal window | Three coloured circles, mono text, `$ npm install our-product` | Real asciinema or recording. Or a copy-button code line with no chrome. Or delete it | You ship a real CLI and the output is real |
| **A7** | "It's not X, it's Y" | Positioning by negation. Also "X but for Y", "the last X you'll need", "X, reimagined" | Say what it does in a sentence a user could repeat | None |
| **A8** | Purple on black | Near-black ground, `#8B5CF6`/`#A855F7`/violet-600, usually plus a violet glow | Anywhere else on the wheel. If the brand is purple, drop the black and the glow | Purple is in a brand guideline you did not invent this week |
| **A9** | Neon and outer glow | `shadow-[0_0_40px_rgba(...)]`, haloed text, glowing borders | Depth from layering and contrast. Shadows that behave like shadows | Brand genuinely about light, night, gaming or music, on one element only |
| **A10** | Radial glow orbs | `rounded-full bg-purple-500/30 blur-[120px]` behind the hero, usually two | Put something real there: the product, a photograph, grain, or nothing | None for the two-blob hero |
| **A11** | Emoji as interface | 🚀 in the hero, ✅ in lists, emoji as feature icons or in buttons | One icon family, one stroke width. Often no mark at all | The emoji is product content: chat, reactions, a status picker |
### 12.C Class B, budgeted (15)
**Spend 4 maximum. Never two from one family. Each spend gets a one-line reason in a comment
at the site of the spend.** Zero is the target, four is the ceiling.
| Family | | Tell | Earned when |
|---|---|---|---|
| **Background filler** | B1 | Harsh gradients | It separates a section or encodes depth. Two stops, one hue family, low contrast |
| | B3 | Pure white ground | Print-emulating, gallery, document surface. Otherwise off-white buys you an elevation scale |
| | B12 | Dot grids | The dots are functional: a canvas, an editor, a snap grid, a diagramming tool |
| | B15 | Pastel palettes | Supported by a real dark value so hierarchy exists. One pastel as ground under a saturated accent |
| **Container styling** | B4 | Drop shadows everywhere | Elevation is real: a menu over the page, a modal, a sticky bar. Tinted, tight, under 10% |
| | B6 | Liquid glass | It genuinely overlays moving or photographic content. Add an inner highlight and a solid `prefers-reduced-transparency` fallback |
| | B11 | Soft corner radius | It is a declared system with roles, applied everywhere, not `rounded-xl` on all of it |
| **Grid layout** | B5 | Three feature cards | You have exactly three peers of the same kind. Otherwise give the strongest one more room |
| | B8 | Bento grid | Cells hold different content *types* and sizes reflect real importance. Exact cell count, 2+ non-text cells |
| **Motion** | B13 | Animated arrows | It marks a real directional relationship, on one control class. Never as a scroll cue |
| | B14 | Hover on everything | Hover goes on things that do something. Named properties, custom easing, reduced-motion gated |
| **Typography** | B7 | Inter / Geist / Space Grotesk | Inter for accessibility-first or deliberate neutral. Geist inside the Vercel ecosystem. Otherwise rotate, §5 |
| **Conversion** | B9 | Checkmark bullets | A real binary exists: checks *and* crosses, or genuine completion state |
| | B10 | Three pricing tiers | Tiers map to nameable segments, splits reflect real boundaries, someone answers the enterprise form |
| **Iconography** | B2 | Loose side icons | One family, one weight, one optical size per role, aligned to a grid, each icon meaning something |
### 12.D Class C, required (4)
C1 real product demo · C2 loading, empty and error states · C3 privacy policy · C4 terms of
service. Full treatment in §11.
---
## 13. Performance and accessibility
- **LCP under 2.5s.** The hero image is `priority` or preloaded.
- **INP under 200ms.** Heavy work off the main thread.
- **CLS under 0.1.** Reserve space for images, fonts and embeds. This is what skeletons buy you.
- **Bundle awareness.** Motion is not small, Three.js is large. Lazy-load anything below the
fold.
- **Z-index discipline.** Never spam `z-50`. Use it only for real layer contexts (sticky nav,
modal, overlay, grain) and document the scale in one constants file.
- **Keyboard and focus.** Visible focus rings that pass contrast. Do not remove outlines
without replacing them.
- Run Lighthouse before declaring the page done.
---
## 14. Pre-flight
Run every box. If one cannot be honestly ticked, the page is not finished.
**Foundations**
- [ ] Design read stated in one line, dials explicit and reasoned
- [ ] One design system or one honestly-labelled aesthetic, not a mix
- [ ] Dependencies verified against `package.json` before import
**The thirty**
- [ ] **Class A: zero occurrences** of all 11, or a stated named override
- [ ] **Class C: all 4 present**, real demo, states, `/privacy`, `/terms` linked in the footer
- [ ] **Class B: 4 or fewer spent, no two from one family**, each with a written reason
**Type and colour**
- [ ] One accent, used identically in every section
- [ ] One theme, no section inverts mid-page
- [ ] One radius system, applied consistently
- [ ] Two font families maximum, no mixed-family emphasis, italic descenders cleared
- [ ] No pure `#000` or `#fff` as ground
**Layout**
- [ ] Hero fits the viewport: headline ≤2 lines, subtext ≤20 words, CTA visible, `pt-24` cap
- [ ] Hero has ≤4 text elements, no tagline under the CTAs, no trust strip inside it
- [ ] Nav on one line at desktop, ≤80px tall
- [ ] Eyebrow count ≤ `ceil(sections / 3)`
- [ ] No layout family used more than twice, ≥4 families across 8 sections
- [ ] No 3 consecutive image-text splits
- [ ] Bento cell count matches content exactly, ≥2 non-text cells
- [ ] Lists over 5 items use a real component, not `divide-y`
- [ ] Mobile collapse declared per section, `min-h-[100dvh]` never `h-screen`
**Content and states**
- [ ] Real images, generated or sourced. No div-based fake product UI, no hand-rolled
decorative SVG, no text-only "minimalism"
- [ ] Logo wall uses real marks, logos only, works in both themes
- [ ] Every CTA passes AA contrast, fits one line, no two CTAs share an intent
- [ ] Forms pass AA on inputs, placeholders, focus rings, labels and errors
- [ ] Loading, empty and error states exist
- [ ] Copy self-audit done, no broken or hallucinated strings, no filler verbs
- [ ] Numbers are real or labelled as sample
**Motion and performance**
- [ ] Every animation justifiable in one sentence
- [ ] Motion claimed equals motion shown
- [ ] No `window.addEventListener('scroll')`, no `useState` for continuous values
- [ ] Reduced motion honoured above MOTION 3, `useEffect` animations cleaned up
- [ ] Dark mode tested in both modes
- [ ] Lighthouse run, LCP / INP / CLS plausibly met
**The stranger test.** Read the finished page as someone who has never heard of it:
- Can I *see* the product, not just read about it?
- Is there one thing here only a real operator could have written? A date, a named customer, a
changelog entry, an admitted limitation, a support address.
- What is the single thing this page wants me to do?
If the second question has no answer, fix that before anything else on this list.
---
## 15. Scope
**Good for:** landing pages, marketing sites, portfolios, editorial sites, app shells,
pricing and about pages, redesigns.
**Not the right tool for:** dense data tables (use TanStack Table or AG Grid), code editors
(Monaco or CodeMirror with official theming), native mobile (Apple HIG or Material directly),
realtime collaborative UI. For those, say so, point at the right tool, and apply this skill
only to the marketing surfaces around them.
**On a redesign**, audit before touching anything. Extract the existing brand tokens, IA and
SEO baseline first. Never silently change URL structure, nav labels, form field names, the
logo, or legal copy. Preserve the copy voice unless a rewrite was asked for. A brand that is
already purple stays purple, that is A8's override.
**To check finished work**, use the `fix-website` skill, which audits against everything here
and returns a prioritised improvement plan.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!