Skip to main content
vibld

Template

Marisol Duarte

An elegant, warm portfolio for a food photographer. A full-bleed overhead food photo sits under a translucent header and a centered serif headline, followed by an asymmetric editorial grid of dishes with ingredient-style captions and lighting notes, an infinite carousel and a validated contact form. It is built for food photographers who shoot for restaurants, brands and editors.

Editorial food 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

Bodoni Moda is the high-contrast display serif the design wants in place of Boska, Cormorant's italic suits the recipe-card notes, and DM Sans stays as the friendly body.

  • Bodoni ModaHeadings: hero 72px 400, section heads 48px 500 at -1.2px
  • DM SansBody: body 16px, 14px ingredient captions
  • CormorantAccent: italic serif lighting notes at 16px

Who it is for

  • Food and still-life photographers
  • Photographers pitching restaurants and food brands
  • Creatives who tell stories with each image
  • Editorial food stylists

Layout

  1. Translucent sticky header: serif name left; Home (active), Work, About, Styleguide, Contact right
  2. Full-bleed hero photo with soft overlay: tracked uppercase eyebrow, two-line light serif headline, one-line subtitle, thin vertical scroll line
  3. Intro statement
  4. Asymmetric 12-column editorial grid: offset images of different sizes, each with a sentence-case ingredient caption and an italic lighting note
  5. Full-width image breakouts
  6. Infinite carousel of work
  7. About with portrait
  8. Contact: 'Let's create something beautiful' with validated form
  9. Footer; living Styleguide page

Palette

Warm, appetizing, editorial, elegant, calm. It reads like a food magazine spread with natural light and generous margins.

  • background (warm off-white)#faf8f5
  • text#2b2b2b
  • accent (terracotta)#9f563c
  • secondary surface#efebe7
  • muted#e9e6e2
  • border#dedad3
  • caption text#5f5a55
  • input border#9a8e79

Every checked pair, measured again

SampleWhereRatioNeeds
Aabody text on off-white13.36:14.5:1
Aacaption text on off-white6.43:14.5:1
Aacaption text on secondary surface5.75:14.5:1
Aaaccent links on off-white5.12:14.5:1
Aaaccent on secondary surface4.57:14.5:1
Aawhite text on accent button5.43:14.5:1
Aawhite hero text on 65% overlay (worst case)5.40:14.5:1
Aaheader text on white/80 over darkest photo8.82:14.5:1
input border on off-white3.04:13:1
Aadark mode body text15.02:14.5:1
Aadark mode accent text4.86:14.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
Boska serif 400, 72px hero (light, airy), 48px section headings at 500 with -1.2px tracking (Boska is not on Google Fonts; a vibld project loads Bodoni Moda)
Body
DM Sans 16px at 1.6 line-height; 14px sentence-case ingredient captions in #5f5a55; italic serif at 16px for lighting notes

High-contrast display serif with a friendly geometric sans; captions mimic a menu or recipe card.

Spacing and imagery

1120px container with 80px gutters, intentionally offset columns, big vertical gaps (~200px) between image rows, small 4px radius.

Bright overhead and 3/4-angle food photography (salads, pizza, burgers, pastries) on textured surfaces; hero washed with a dark warm overlay for legible text.

Components

  • Translucent sticky header
  • Full-bleed hero with overlay
  • Tracked eyebrow label
  • Serif headline
  • Scroll line indicator
  • Asymmetric editorial grid
  • Ingredient caption + italic lighting note
  • Full-width breakout image
  • Infinite carousel
  • About portrait section
  • Validated contact form
  • Styleguide page
  • Mobile drawer nav

Interactions

  • Images fade and rise into view on scroll with slight offsets
  • Hover gently scales images and darkens caption
  • Infinite carousel with drag and pause on hover
  • Header gains a white/80 blurred background on scroll
  • Contact form validation with inline errors and toast
  • Mobile drawer navigation

Data

  • Shot{id, src, alt, ingredients, lighting, client?, layoutSpan}
  • Series{slug, title, shots[]}
  • Profile{name, city, bio, portrait}
  • Inquiry{name, email, company, projectType, budget, message}

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 warm, editorial portfolio for a fictional food photographer, "Marisol Duarte". It targets restaurant owners, food brands and magazine editors. Each image should feel like a story: captions read like menu lines (the ingredients) and short italic notes describe the light. The site opens on a full-bleed hero, moves through an intentionally uneven grid, and ends with a friendly inquiry form.

### Stack
React 18 + TypeScript + Vite, Tailwind CSS, shadcn/ui (Radix Form, Select, Toast via Sonner) and lucide-react. Use Framer Motion for scroll reveals, Embla Carousel with loop and drag for the infinite strip, Vaul for the mobile drawer, react-hook-form + zod and React Router for Home, Work, About, Styleguide and Contact. One serverless function relays inquiries.

### Pages & layout
1. Header, 80px, sticky: white/80 with `backdrop-blur`. A serif name on the left and five links on the right; the active link is darker.
2. Hero at 100vh: a photo with a 65% #1c1a18 overlay (effective #6b6a69 or darker behind white text), an eyebrow "FOOD PHOTOGRAPHY" in 14px with 0.3em tracking, a two-line headline at clamp(40px, 6vw, 72px) in light serif, a subtitle and a 64px vertical hairline scroll cue.
3. Intro: a centered 2-sentence statement in serif.
4. Editorial grid: 12 columns. Rows pair images with different spans and offsets (for example 7 plus 5 columns, then 4 columns starting at column 2 plus 6 columns starting at column 7, dropped 64px). Below each image: a 14px sentence-case ingredients line and an italic lighting note.
5. Breakout: one full-bleed image between grid rows.
6. Carousel: a looping strip of 6-10 square thumbnails.
7. Contact: a serif heading, the form and details. Footer with socials.

### Design system
- `--background: #faf8f5`, `--foreground: #2b2b2b`, `--accent: #9f563c`, `--secondary: #efebe7`, `--muted: #e9e6e2`, `--border: #dedad3` (decorative), `--muted-foreground: #5f5a55`, `--input-border: #9a8e79`, `--radius: 4px`.
- Dark tokens: background #1c1a18, foreground #f2eee9, accent #c07459.
- Fonts: Boska (Fontshare), or Bodoni Moda or Cormorant from Google Fonts, for display. DM Sans for body. Scale: 12 / 14 / 16 / 20 / 32 / 48 / 72px.
- Spacing: 8px base, 80px gutters, 160-200px between rows.
- Motion: 700ms cubic-bezier(0.22,1,0.36,1) fade-up with a 20px offset and 400ms image hover scale (1.03).

### Components & interactions
- Shot: skeleton (warm gray), then fade in. Hover or focus scales the image. The caption is always visible.
- Carousel: drag, arrows and a pause button; it pauses on hover and focus.
- InquiryForm: name, email, company, project type select, budget select and message. States for invalid, submitting, success and error.
- Styleguide page: swatches, type ramp and buttons in all states.

### Data & state
`Shot{id, src, alt, ingredients, lighting, client?, span}` and `Series{slug, title, shots}` are typed. The grid layout comes from the `span` and `offset` fields. Seed 10-14 placeholder food photos with invented dish descriptions and no real restaurant names.

### Accessibility
- The terracotta accent is deepened to #9f563c (5.12:1 on off-white, 4.57:1 on the secondary surface) so it works for links and small text; dark mode keeps #c07459 (4.86:1). Captions use #5f5a55.
- The 65% hero overlay keeps the white eyebrow, headline and subtitle at 5.4:1 even over a white plate.
- Asymmetric layout must not scramble reading order. Keep DOM order row by row.
- Alt text should describe the dish, not the ingredients list verbatim. The carousel is pausable and honors reduced motion.
- Verified contrast: body #2b2b2b on #faf8f5 = 13.36:1; captions #5f5a55 on #faf8f5 = 6.43:1; accent #9f563c on #faf8f5 = 5.12:1; hero text #ffffff on #6b6a69 = 5.4:1.

### Security
- zod on the client and in the function, with a honeypot and rate limit.
- Store only what the inquiry needs; don't collect phone numbers unless required.
- No secrets in client-side code. External links use `rel="noopener noreferrer"`.

### Performance & SEO
The hero image is the LCP, so preload it with AVIF `srcset`. Lazy-load the grid. Add Photographer/LocalBusiness JSON-LD and OG images per series.

### Guardrails
- Do write all copy yourself and use placeholder imagery (Unsplash-style stock or generated); no trademarked names, client logos or real people.
- Don't present invented testimonials or awards as real; label sample content as sample.
- Keep components small and fully typed; no `any`. Surface errors visibly instead of failing silently.
- Don't collect or store more personal data than the contact form needs.
- Done when:
  - [ ] Asymmetric grid renders intentionally offset rows and collapses cleanly on mobile
  - [ ] Every image has ingredient caption and lighting note
  - [ ] Carousel loops, drags, and pauses
  - [ ] Inquiry form covers all states
  - [ ] Accent color used only where contrast allows

Open the builderAll templatesThis palette on its own