Template
Ashlight
A search-first web app that lets people find films in a large catalogue even when they misspell titles, with facets for genre, year and rating. It demonstrates server-rendered search, aggregations for facet counts and modal detail routes, and is a starter for any 'search a big catalogue' product.
Typo-tolerant full-text search app over a film catalogue · App · Small tools and apps · full-stack app (auth + DB)
A mock-up of the homepage, drawn from this design’s layout, palette and typefaces. A build follows the full prompt below.
Add app screens
Pick up to 6 screens, such as a dashboard, settings or an empty state. Each is built in this design’s own palette and typefaces, with its states and guardrails.
Start from this templateRead the build prompt
Typefaces
Mozilla Text keeps the poster grid text clean and compact on dark navy, and DM Mono gives the optional record-count digits a soft, cinematic mono.
- Mozilla TextHeadings: hero headline 40px 700 at -0.02em, card titles 16px 700
- Mozilla TextBody: synopsis 16px/1.5 400, chips and meta 12-14px 500, highlighted matches 600
- DM MonoFigures and code: record-count caption digits
Patterns
- centred hero search with record-count caption
- debounced search-as-you-type with typo tolerance
- highlighted match fragments in results
- poster card grid with genre chips and star rating
- faceted filters (genre, year, rating) with counts
- detail view as intercepted modal over the grid, full page on direct load
- dark navy canvas with faint repeating icon pattern
- pill wordmark badge in accent colour
States it is designed for
- Initial: popular titles grid
- Searching: skeleton cards while keeping previous results dimmed
- No results: message with the query, typo suggestions and a 'Clear filters' button
- Search service error: inline banner with retry
- Poster image missing: navy placeholder with title initials
- Very long synopsis: clamp to 3 lines in cards, full in detail
- Direct load of a detail URL: renders as full page
Who it is for
- developers building catalogue or knowledge-base search
- product teams prototyping discovery UIs
- film enthusiasts (end users of the demo)
Layout
- Page canvas: deep navy with a very faint repeating pattern of small circular glyphs
- Top-left: small pink pill wordmark badge; right: source link
- Hero: 40px bold white headline, muted caption 'Search across N titles', full-width rounded search input with leading magnifier icon
- Below search: facet bar (genre chips with counts, year range, minimum rating) and result count with sort select
- Results: responsive grid of cards: 2:3 poster with dark gradient overlay and title at the bottom, genre chips, pink star rating, three-line synopsis with highlighted matches
- Detail: modal (desktop) or full page (direct URL/mobile) with large poster, title, year, runtime, genres, rating, synopsis, cast list
- Footer: small credit line and data disclaimer
- Breakpoints: 5 columns at 1280px, 3 at 768px, 2 on phones; facet bar scrolls horizontally on mobile
Palette
Cinematic and nocturnal. Dark navy lets posters glow; pink accents feel playful.
- canvas navy
#0b1120 - pattern / panel navy
#121b34 - card surface
#111827 - input surface
#1e293b - primary text
#f8fafc - secondary text
#94a3b8 - accent pink (badge fill)
#db2777 - accent pink text / stars
#f472b6 - chip / input border
#60738e - chip text
#cbd5e1
Every checked pair, measured again
| Sample | Where | Ratio | Needs |
|---|---|---|---|
| Aa | primary text on canvas | 18.00:1 | 4.5:1 |
| Aa | secondary text on card | 6.92:1 | 4.5:1 |
| Aa | badge label on pink | 4.60:1 | 4.5:1 |
| Aa | pink stars / accent text on card | 6.70:1 | 4.5:1 |
| Aa | chip text on input surface | 9.85:1 | 4.5:1 |
| chip border on card | 3.67:1 | 3:1 | |
| Aa | white title on poster scrim (panel navy worst case) | 17.04:1 | 4.5:1 |
As vibld’s tokens
The palette on the fifteen colour tokens vibld styles a project with, each text colour on the fill it is read on. Marked tokens are solved from the palette, because no swatch held that role at 4.5:1.
- background
- card
- muted
- primary
- secondary
- accent
- destructive *
Type scale
- Display
- Mozilla Text 700, 40px hero headline with -0.02em tracking; 16px 700 card titles
- Body
- Mozilla Text 400 16px/1.5 synopsis; chips and meta 12-14px 500
Observed system sans maps to Mozilla Text. Use DM Mono for the record-count caption digits if desired. Highlighted matches: 600 weight + pink underline, not colour alone.
Spacing and imagery
Medium density; container 1280px; grid gap 16px; card padding 12px; hero margin 64px. Radius 8px cards and posters, 999px chips and search input. Shadows: subtle `0 8px 24px rgb(0 0 0 / 0.35)` on modal only.
Film posters (use royalty-free or generated placeholder posters for demos) with bottom gradient scrim; tiny line icons for search and meta.
Components
- WordmarkBadge
- HeroSearch (input + count caption)
- FacetBar (chip toggles with counts, year range, rating select)
- SortSelect
- ResultCard (poster, title, chips, stars, highlighted synopsis)
- StarRating (display only)
- DetailModal / DetailPage
- ResultSkeleton
- Pagination or infinite loader
- EmptyResults with suggestions
Interactions
- Typing debounces 250ms and updates the URL (?q=) so results are shareable
- Fuzzy matching tolerates 1-2 character typos; the 'Did you mean' line appears when a correction was applied
- Facet chips toggle with aria-pressed and update counts
- Clicking a card opens the detail modal via an intercepted route; Escape or back closes it and restores scroll and focus
- Keyboard: '/' focuses search; arrow keys move across cards; Enter opens
- Poster hover scales 1.02 with 150ms ease
Data
Title{id, name, year, runtime_min, genres[], rating (0-10), synopsis, poster_url, cast[]}SearchResult{title_id, score, highlights{field, fragments[]}}FacetCount{facet (genre|year|rating), value, count}
Guardrails
Experience
- Keep the search box visible at the top on scroll (sticky compact version)
- Show the result count and applied filters as removable chips
- Always show why a result matched (highlighted fragment)
- Preserve query, filters and scroll position when closing a detail
- Never show an empty grid without guidance
Accessibility
- Search input has a visible label (can be visually hidden) and role=search landmark
- Result count changes announced via aria-live polite
- Highlights use weight and underline, not colour only
- Detail modal traps focus and returns it to the card
- Poster images carry alt text 'Poster for {title}'; overlaid titles sit on a scrim tested against white at 4.5:1
- Chip borders #60738e reach 3:1 on both card and input surfaces
Security
- Search queries validated (length <= 100, trimmed) and parameterised; never interpolate into SQL
- Catalogue tables are read-only to the public via RLS select policies; writes only by service role in seed scripts
- Rate-limit the search endpoint per IP
- Search service keys stay on the server; client calls a route handler
Build prompt
The baseline every prompt in the catalog assumes, then this design’s own ten sections, from goal to guardrails.
The baseline
### How to use these prompts Paste an entry's build prompt into your coding agent as the first message. Each prompt names its own stack, tokens and acceptance criteria; the rules below apply to all of them and can be prepended once per project. ### Engineering baseline - TypeScript strict mode, no `any`, small typed components, feature folders, and one source of truth for design tokens (CSS variables consumed by Tailwind). - Validate every input with a shared zod schema on the client and again on the server or edge function. Never trust client-side checks alone. - Show loading, empty and error states for every async view. Surface errors in plain language with a retry, and log details to the console in development only. - Keep secrets out of the bundle. Only publishable keys (for example a Supabase anon key) belong in client code; service-role keys, API keys and webhooks live in server or edge-function environment variables. ### Data and auth baseline (full-stack entries) - Enable Row Level Security on every table before inserting data. Default-deny, then add owner-scoped policies (`auth.uid() = user_id`) and explicit role checks for admin views. - Store roles in a separate table checked by a security-definer function, never in a user-editable profile field. - Upload files to private storage buckets with size and MIME limits, and serve them through signed URLs. - Rate-limit public endpoints (forms, auth, AI calls) and add a honeypot field or captcha to anonymous forms. - Take payments through a hosted checkout and verify webhooks by signature. Never handle raw card data. ### Accessibility and UX baseline - Target WCAG 2.2 AA: 4.5:1 contrast for normal text and 3:1 for large text, input borders, focus rings and meaningful icons or chart lines. Every palette in this catalog lists its verified pairs; re-check with a contrast tool after any colour change. - Keep body text at 16px or larger with 1.5 line height, nothing below 12px, no light weights under 24px, and uppercase only for short labels. - Give every interactive element a visible focus ring, full keyboard support, semantic landmarks, labelled form fields, and alt text on meaningful images. - Respect `prefers-reduced-motion` for every animation. Give drag-and-drop and carousels keyboard and button alternatives. - Build mobile-first and test at 375px, 768px and 1280px. ### Content guardrails - Use original copy, fictional sample data and placeholder or licensed imagery. Do not reuse another product's name, logo, screenshots or marketing text. - Label demo testimonials and metrics as samples. Collect the minimum personal data the feature needs. ### SaaS screen baseline - Design every screen for its full set of states: first-run empty, loading skeleton, partial data, error with retry, permission-denied, and success feedback. Each entry lists the states its screen needs. - Keep destructive actions (delete, revoke, downgrade, remove member) behind a confirmation that names the object, and prefer undo over a second dialog where the action is reversible. - Enforce authorisation on the server for every action a screen exposes. Hiding a button is not access control; check the role again in the API or RLS policy. - Never show secrets (API keys, tokens) in full after creation. Show them once, then mask them, and offer rotate and revoke. - Keep the app shell (navigation, workspace switcher, account menu) consistent across screens, and preserve filters, sort and scroll position when the user navigates back.
### Goal
Build **Ashlight**, a search app over a large film catalogue that forgives typos. People type a partial or misspelled title, see matches with highlighted fragments, narrow by genre, year and rating, and open a detail view without losing their place. It is also a starter for any catalogue-search product that needs full-text search, facets and modal detail routes.
### Stack
Next.js (App Router, React Server Components, parallel and intercepting routes) + TypeScript, Tailwind CSS, lucide-react, zod. Supabase Postgres with the `pg_trgm` extension for trigram similarity and a `tsvector` column for full-text ranking; an RPC returns results, highlights and facet counts. Use a dynamic OG image route for shareable searches.
### Pages & layout
1. **Search (`/`)**: navy canvas with a faint repeating glyph pattern. Top-left, a pink pill wordmark badge. Hero: a bold headline ("Find any film, even misspelled"), a caption with the live title count, and a wide pill search input with a magnifier. Under it sit the facet bar (genre chips with counts, a year range, minimum rating) and a result count with a sort select (relevance, rating, newest). The results grid uses cards with a 2:3 poster, gradient scrim and title, genre chips, pink star rating and a three-line synopsis with highlighted matches.
2. **Detail (`/title/[id]`)**: when navigated from the grid it opens as a modal via `@modal/(.)title/[id]`; on direct load it renders as a full page. It shows the large poster, title, year, runtime, genres, rating, synopsis and cast.
3. **Responsive**: five columns at 1280px, three at 768px and two on phones. The facet bar scrolls horizontally and the search becomes sticky and compact on scroll.
### Design system
- Colors: `--canvas: #0b1120`, `--panel: #121b34`, `--card: #111827`, `--input: #1e293b`, `--fg: #f8fafc`, `--fg-2: #94a3b8`, `--accent: #db2777` (fills), `--accent-text: #f472b6` (stars, highlights), `--border: #60738e`, `--chip-text: #cbd5e1`.
- Fonts: Mozilla Text; headline 700 at 40px (-0.02em); card titles 700 at 16px; body 400 at 16px/1.5; chips and meta 500 at 12-14px.
- Spacing: 4px base; grid gap 16px; card padding 12px; container 1280px.
- Radius: 8px cards and posters; 999px chips and search.
- Shadows: modal only, `0 8px 24px rgb(0 0 0 / 0.35)`.
- Motion: 150ms hover scale 1.02; modal fades and rises 8px over 200ms; none under reduced motion.
- Typeface: DM Mono (Google Fonts) for record-count caption digits.
### Components & interactions
WordmarkBadge, HeroSearch, FacetBar, ActiveFilterChips, SortSelect, ResultCard, StarRating, DetailModal, DetailPage, ResultSkeleton, EmptyResults, ErrorBanner. Search debounces 250ms and writes `?q=&genre=&year=&rating=&sort=`. When the best match came from a fuzzy correction, show "Showing results for …" with a link to search the exact text. The `/` key focuses search; arrow keys move between cards; Enter opens; Escape closes the modal and restores focus and scroll.
### Data & state
`titles(id, name, year, runtime_min, genres text[], rating numeric, synopsis, poster_url, cast text[], search tsvector generated)`, with GIN indexes on `search` and trigram indexes on `name`. RPC `search_titles(q, genres, year_from, year_to, min_rating, sort, page)` returns rows, a `ts_headline` fragment and facet counts. Seed 5,000 invented titles with generated placeholder posters. URL params are the single source of truth for search state.
### Accessibility
Wrap search in `role="search"` with a labelled input. Announce result counts politely. Highlights use `<mark>` styled with weight and underline. Cards are links with the title as their accessible name. The modal is a Radix Dialog with a focus trap and restore. Poster alt text is "Poster for {title}". Titles over posters sit on a gradient scrim whose darkest stop is at least `--panel`.
Verified contrast: primary text on canvas: #f8fafc on #0b1120 = 18.0:1; secondary text on card: #94a3b8 on #111827 = 6.92:1; badge label on pink: #ffffff on #db2777 = 4.6:1; pink stars / accent text on card: #f472b6 on #111827 = 6.7:1; chip text on input surface: #cbd5e1 on #1e293b = 9.85:1; chip border on card: #60738e on #111827 = 3.67:1; white title on poster scrim (panel navy worst case): #ffffff on #121b34 = 17.04:1.
### Security
Enable RLS on `titles` with a public select policy only; inserts and updates come only from the service role in seed scripts. Validate every query param with zod (query at most 100 characters, numeric ranges bounded) and call the RPC with parameters, never string-built SQL. Rate-limit the search route per IP. Service keys stay server-side.
### Performance & SEO
Server-render the first page of results for shareable URLs. Use `next/image` with blur placeholders and responsive sizes. Stream the facet bar separately from results. Return a dynamic OG image with the query text. Index title detail pages with structured data for creative works; keep search pages canonicalised.
### Guardrails
- Use invented titles, synopses and cast names; no real films or studios.
- Placeholder posters must be original or generated.
- Keep ranking explainable: relevance first, then rating.
- Acceptance criteria:
- [ ] A query with two typos still returns the intended title first
- [ ] Facet counts update with the query
- [ ] Detail opens as a modal from the grid and as a page on reload
- [ ] Empty, loading and error states are reachable and styled
- [ ] Keyboard-only users can search, filter, open and close details