Template
Transom
A horizontally scrolling, monospace-typed photography portfolio. A compact three-column text header holds the name and project links, a short sentence-case artist statement, and about/email/social links. Below it, a snap-scrolling row of large photographs carries a floating client caption that updates per slide. It targets photographers who want a fast, editorial, premium-feeling site.
Horizontal-scroll monospace photo portfolio · Website · Portfolio · static site
Who it is for
- Architectural and lifestyle photographers
- Photographers pitching for premium bookings
- Agencies customizing sites for several shooters
- Creatives who favor typewriter-style minimalism
Layout
- Three-column text header: name + Project 1 / Project 2 links left; sentence-case artist statement center; About, Email, Instagram right
- Full-width horizontal snap gallery of tall images at varied widths with a small gap
- Floating label on the current slide ('For [client]') with a white/glass background
- Infinite client marquee
- About page with bio and contact
Palette
Crisp, editorial, typewritten, sunny, efficient. It feels like an index card taped next to large prints.
- background
#ffffff - text
#09090b - primary
#18181b - muted
#f4f4f5 - border
#e4e4e7 - photo accent (turquoise)
#5ec8d8 - meta text
#71717a
Every checked pair, measured again
| Sample | Where | Ratio | Needs |
|---|---|---|---|
| Aa | text on white | 19.90:1 | 4.5:1 |
| Aa | slide caption on white/85 glass over darkest photo | 14.09:1 | 4.5:1 |
| Aa | primary button text | 17.72:1 | 4.5:1 |
| Aa | text on muted surface | 18.10:1 | 4.5:1 |
| Aa | secondary/meta text on white | 4.83:1 | 4.5:1 |
| prev/next button outline and focus ring | 17.72:1 | 3:1 | |
| focus ring over caption glass | 12.55: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
- Display
- Monospace (IBM Plex Mono-like) 16-18px regular; uppercase with 0.06em tracking only for short labels (Monospace is not on Google Fonts; a vibld project loads JetBrains Mono)
- Body
- Same monospace in sentence case at 1.5 line-height (Monospace is not on Google Fonts; a vibld project loads JetBrains Mono)
One monospaced face with small capitalised labels keeps the page like a typed contact sheet; photos provide all the scale.
Spacing and imagery
24px page margin, 12px gap between slides, images ~670px tall filling the viewport below the header; no radius.
Bright, saturated architectural details (colored doors, balconies, facades) as tall, uncropped verticals and wide horizontals.
Components
- Three-column text header
- Project links
- Artist statement block
- Horizontal snap gallery
- Floating slide caption with glass backdrop
- Client marquee
- About page
- Email and Instagram links
- Toast on copy-email
Interactions
- Horizontal scroll with snap; vertical wheel mapped to horizontal
- Caption text updates to the client of the slide in view
- Staggered slide-in entrance animation (React Spring)
- Marquee scrolls infinitely, pauses on hover
- Arrow keys move one slide
- Copy-email button shows a toast
Data
Project{slug, title, slides: Slide[]}Slide{src, alt, width, height, client}Client{name}site.json for all content
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 lightweight, typewriter-style photography portfolio for a fictional architecture and lifestyle photographer, "Transom". Its audience is art directors and brands booking shoots. The home view is a single horizontal strip of big images that snaps slide by slide, with a floating caption naming the (fictional) client for each frame. A dense monospace header holds all navigation and a short statement. Keep the JavaScript payload tiny.
### Stack
React 18 + TypeScript + Vite, Tailwind CSS, shadcn/ui (only Button and Toast via Sonner) and lucide-react. Use React Spring for staggered entrances and caption transitions and React Router for `/`, `/project/:slug` and `/about`. Content comes from a typed `site.json`. No backend. Keep the JavaScript payload under 250KB gzipped.
### Pages & layout
1. Header: a 3-column grid (3/6/3 of 12) set in mono at 16px (labels uppercase, statement sentence case). Left: name, then project links. Center: a 2-3 line statement. Right: About, Email and Instagram.
2. Gallery: a `scroll-snap-type: x mandatory` row filling the remaining viewport height with 12px gaps. Each slide keeps its image ratio at fixed height.
3. Caption: pinned top-left over the active slide, "FOR [CLIENT]", on a white/85 background with `backdrop-blur` (effective #d9d9d9 or lighter even over black imagery).
4. Client marquee near the bottom or on the About page.
5. About: a portrait, a short bio, services and contact.
6. Mobile: the header stacks and the gallery becomes a vertical list, since horizontal swipe is optional.
### Design system
- `--background: #ffffff`, `--foreground: #09090b`, `--primary: #18181b`, `--muted: #f4f4f5`, `--border: #e4e4e7`.
- Font: IBM Plex Mono (Courier Prime as an alternative) from Google Fonts, 400 only, 16px/1.5. Uppercase with 0.06em tracking is reserved for short labels (nav links, the slide caption, marquee names); the statement and all paragraphs are sentence case. The only other size is 12px for the marquee.
- Spacing: 4px base; 24px page margin; 12px gallery gap. Radius 0.
- Motion: spring (tension 210, friction 20) with an 80ms stagger per slide; 200ms caption crossfade. Light only.
### Components & interactions
- HorizontalGallery: converts vertical wheel to horizontal scroll (not on trackpads that already scroll horizontally) and supports ←/→ keys. Uses IntersectionObserver to set the active slide.
- SlideCaption: animates text changes. Show a gray shimmer while an image loads, and a caption-only tile on error.
- EmailLink: a `mailto:` plus a copy button that shows a Sonner toast ("Email copied").
- Marquee: pauses on hover or focus and is static under reduced motion.
### Data & state
`site.json` has `{ name, statement, links, projects: Project{slug, title, slides: Slide{src, alt, w, h, client}[]}[], clients: string[] }`. Parse it with zod and render a readable error panel if it is invalid. State is the active slide index. Seed two projects of 8-10 placeholder images with invented client names.
### Accessibility
- Build the gallery as a labelled region (`role="region" aria-label="Project 1 gallery"`) that is focusable and keyboard-scrollable, with visible prev/next buttons for pointer users who cannot scroll sideways.
- The caption is text, not baked into the image. Every image has alt text.
- Uppercase is limited to labels of four words or fewer with 0.06em tracking; the statement and About copy stay in sentence case.
- Respect reduced motion.
- Verified contrast: text #09090b on #ffffff = 19.9:1; caption #09090b on #d9d9d9 = 14.09:1; button #ffffff on #18181b = 17.72:1; meta #71717a on #ffffff = 4.83:1.
### Security
- No forms. `mailto:` only.
- External links use `rel="noopener noreferrer"`.
- JSON validated at build time. No secrets in client-side code.
### Performance & SEO
Eager-load only the first two slides and lazy-load the rest with explicit dimensions. Use AVIF/WebP with `srcset` keyed to the viewport height. Add per-project titles and OG images and a meaningful H1 (visually hidden if needed).
### 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:
- [ ] Gallery snaps per slide with wheel, keys and buttons
- [ ] Caption updates to the active slide's client
- [ ] JavaScript payload stays under 250KB gzipped
- [ ] Mobile layout falls back to vertical list
- [ ] Invalid site.json shows an error panel