Template
Dani Okafor
A bold portrait photographer's portfolio whose home page is an accordion-style carousel of tall rounded portrait panels: one expanded with a caption, the rest as narrow slivers. Navigation leads to themed collections (portraits, candid life, people and passions) and an about page, with contact details always visible.
Portrait photographer portfolio · Website · Portfolio · static site
A mock-up of the homepage, drawn from this design’s layout, palette and typefaces. A build follows the full prompt below.
Start from this templateRead the build prompt
Typefaces
The warm, friendly single geometric family is the design, and Poppins stays in only a few templates.
- PoppinsHeadings: name ~38px bold at -0.96px
- PoppinsBody: nav and contact 16-18px
Who it is for
- Portrait photographers with a distinct voice
- Photographers seeking brand collaborations
- Artists submitting thematic collections
- Clients booking portrait sessions
Layout
- Header: bold name top-left, collection links below (Portraits, Everyday, People & Craft, About); email and phone right-aligned
- Horizontal row of tall rounded portrait panels; the active one expands wide with a caption (name - role - year), others compressed into slivers
- Collection pages with gallery layouts and lightbox
- About page
Palette
Warm, human, vibrant, friendly, dynamic. It celebrates faces and personalities.
- background
#fcfaf8 - text
#2e251f - primary
#3d3129 - accent terracotta (fills, large text)
#dd713c - terracotta text / links
#b2501f - secondary
#f2f0ed - decorative border
#e5e0dc - nav gray / muted text
#6f6760 - caption scrim (black 60%)
#00000099
Every checked pair, measured again
| Sample | Where | Ratio | Needs |
|---|---|---|---|
| Aa | body text on background | 14.40:1 | 4.5:1 |
| Aa | text on secondary | 13.18:1 | 4.5:1 |
| Aa | nav gray on background | 5.33:1 | 4.5:1 |
| Aa | nav gray on secondary | 4.88:1 | 4.5:1 |
| Aa | terracotta link text on background | 4.97:1 | 4.5:1 |
| Aa | terracotta link text on secondary | 4.55:1 | 4.5:1 |
| terracotta 24px+ accent / indicator on background | 3.10:1 | 3:1 | |
| Aa | white text on primary button | 12.58:1 | 4.5:1 |
| Aa | caption over 60% black scrim on white-worst-case photo | 5.74:1 | 4.5:1 |
| focus ring (primary) on background | 12.08:1 | 3: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
- Poppins bold, ~38px with -0.96px tracking for the name
- Body
- Poppins 16-18px regular for nav and contact
Single geometric family, friendly and approachable; hierarchy by weight
Spacing and imagery
40px margins; portrait panels ~480px tall with 20px gaps and ~24px corner radius; single-screen home
Close-up color and black-and-white studio portraits of diverse people with soft backgrounds, cropped into tall rounded panels
Components
- Name + collection nav header
- Contact info block
- Expanding portrait panel carousel
- Caption overlay
- Collection gallery
- Lightbox (LightGallery)
- Swiper slider
- About section
- Scroll-triggered animations
Interactions
- Hover/click expands a panel while others compress (flex-grow animation)
- Auto-advance with pause on hover
- Swipe on touch
- Caption fades in on active panel
- GSAP scroll-triggered reveals on collection pages
- Lightbox zoom and navigation
Data
Portrait{src, alt, subject, role, year, collection}Collection{slug, title, description, cover}Contact{email, phone}
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 a template'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 templates) - 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.
### Goal
Build a portfolio for a fictional portrait photographer called **Dani Okafor**. The audience is people booking portrait sessions, brands looking for a photographer with a warm human style, and galleries reviewing themed series. The home page is a single, lively screen: a row of tall rounded portrait panels where one expands to show a caption while the others shrink to slivers.
### Stack
React 18 + TypeScript + Vite, Tailwind CSS, shadcn/ui (Radix Dialog), lucide-react, React Router, Framer Motion for the panel expansion (`layout` animations), Swiper for touch galleries on collection pages, and a lightbox built from Radix Dialog (or yet-another-react-lightbox). GSAP is optional for scroll reveals. No backend.
### Pages & layout
1. Header: the photographer's name in bold at top left, with collection links underneath (Portraits, Everyday, People & Craft, About); email and phone right-aligned as plain links.
2. Home: a full-width row of 6–8 tall panels (about 480px) with 20px gaps and large corner radius. The active panel grows to about 3× width and shows a caption (subject – role – year) bottom-right; the others compress. It advances every 5s and pauses on hover or focus.
3. Collection page: intro line and a responsive gallery (masonry or justified) opening a lightbox with captions.
4. About: portrait of the photographer, bio, services (sessions, editorial, brand), and booking info.
### Design system
Tokens: `--background: #fcfaf8; --foreground: #2e251f; --primary: #3d3129; --accent: #dd713c; --secondary: #f2f0ed; --border: #e5e0dc; --muted-foreground: #6f6760; --accent-text: #b2501f; --border: decorative only`. Font: Poppins 400/500/700. Scale: 38px name with -2.5% tracking; 18px nav; 16px body at line-height 1.6, max 70ch; 13px captions at weight 500. Radius: 24px panels, 12px gallery images. Spacing: 40px margins, 20px panel gap. Motion: 500ms `cubic-bezier(.22,1,.36,1)` flex-grow transitions; 300ms caption fade. Light only.
### Components & interactions
- Panel strip: each panel is a button; hover, focus or tap expands it; left/right arrow keys move the active panel; autoplay has a visible pause/play button.
- Caption: white text that fades in on the active panel over a bottom gradient reaching 60% black.
- Gallery: lazy images with blurred placeholders; the lightbox supports arrows, swipe, Esc, and returns focus.
- Mobile: the strip becomes a horizontal snap carousel with one active card and peeking neighbors.
- Error fallback tile for failed images; empty collection message.
### Data & state
`Portrait{src,alt,subject,role,year,collection}`, `Collection{slug,title,description,cover}`, `Contact{email,phone}`. Active index state lives in the strip component; the lightbox index is in the URL. Use fictional subject names and roles, with consent-friendly stock or generated faces.
### Accessibility
The nav gray is #6f6760 on #fcfaf8 (5.3:1). Caption text sits on a bottom gradient reaching 60% black, which holds white captions at 5.7:1 over any portrait. The terracotta #dd713c is only 3.1:1 on the background, so it is used for fills and 24px+ text; small terracotta text and links use #b2501f. Autoplay must be pausable (WCAG 2.2.2) and off under `prefers-reduced-motion`. Panels need accessible names ("Portrait of Alex, music teacher, 2024") and `aria-expanded` or `aria-current` on the active one. Phone and email should be real `tel:` and `mailto:` links. Verified contrast: body text on background: #2e251f on #fcfaf8 = 14.4:1; nav gray on background: #6f6760 on #fcfaf8 = 5.33:1; terracotta link text on background: #b2501f on #fcfaf8 = 4.97:1; caption over 60% black scrim on white-worst-case photo: #ffffff on #666666 = 5.74:1.
### Security
Static: no forms required; if booking is added, validate with zod on the client and server with a honeypot. Strip EXIF metadata from images. External links use `rel="noopener noreferrer"`. Don't publish a personal mobile number in demo data; use a clearly fake placeholder.
### Performance & SEO
Only the visible panels load at full size; others use smaller crops (they're slivers). Serve AVIF/WebP with `srcset`; lazy-load collection images; code-split the lightbox. Person and ImageGallery JSON-LD; per-collection meta and OG images.
### Guardrails
- Use licensed or generated portraits with fictional names and roles; never caption real people with made-up biographies.
- No fake testimonials.
- Collect no personal data.
- Keep components small and typed, never use `any`, and show failed images visibly.
- Done when: the strip expands smoothly by mouse, keyboard and touch; autoplay pauses via control and reduced motion; captions meet contrast; collections open a working lightbox; the home page has no layout shift during expansion.