A modern Renaissance portfolio template. Single oil-painting world that color-grades through the day as you scroll. React + Vite + framer-motion + Lenis.
Marginalia is an open-source portfolio template for designers, design engineers, and indie builders who want a folio that feels like a painting and ships like software. One painted scene becomes the backdrop for the whole site, shifting from dawn through midday, dusk, and night as the reader scrolls.
- One-world design: a single Renaissance-style background painting, color-graded section by section as you scroll
- Persistent protagonist: a figure anchored to the bottom of the page that scales and shifts with scroll progress
- Seven chapters: Hero · Manifesto · Work · Writing · Workshop · Press · Contact
- Built-in animations: scroll-driven, intersection-observer reveals, cursor parallax, floating embers
- Smooth scroll via Lenis
- Single-file config: all copy + identity in
src/marginalia/config.js. List data insrc/marginalia/data/* - Theming: design tokens exported as CSS variables — palette, type, motion easing
- Accessible: skip link, reduced-motion respected, semantic landmarks, scroll index navigable by keyboard
- No backend required: ships as a static SPA, host on Vercel / Netlify / GitHub Pages
- React 19 + Vite
- framer-motion
- lenis (smooth scroll)
- Google Fonts: Instrument Serif, Inter, Geist Mono
- Pure CSS — no Tailwind, no CSS-in-JS
git clone https://github.com/uxderrick/marginalia.git my-folio
cd my-folio
npm install
npm run devVisit http://localhost:5173.
Marginalia is designed to be forked. Three places to edit:
src/marginalia/config.js— your identity, all section titles + copysrc/marginalia/data/*.js— list content (works, writings, stack, press, socials)public/assets/marginalia/— drop your background painting + figure here
Marginalia ships with a placeholder painting and figure. Replace them:
public/assets/marginalia/bg-01-dawn.png— landscape painting, ~1600×900, used as the world bgpublic/assets/marginalia/figure-01-standing.png— protagonist on transparent background, vertical 9:16
Generate them in any image tool (Gemini, Midjourney, DALL·E, Imagen, Flux). Two starter prompts below — full asset guide + extra poses in docs/asset-prompts.md.
Background prompt (16:9 PNG, no transparency):
Wide cinematic landscape painting in Italian High Renaissance oil-painting
style (think Giorgione, early Raphael). Dawn lighting: soft pink and gold sky
with low pearlescent mist hanging over rolling Tuscan hills. Cluster of slender
cypress trees mid-frame left. Distant blue mountains fading into atmospheric
haze. Sparse pink and white blossoming branches in foreground corners. Two
small birds in mid-flight upper right. No people, no text, no UI elements.
Empty center-frame composition leaving negative space for a figure. Dramatic
volumetric clouds, god-rays piercing through. Color palette: ivory #F4EBD9,
deep umber #3B2A1A, venetian red #8B2E1F, old gold #C9A227, sky teal #6B8E9E.
Painterly brushstrokes visible. 16:9 cinematic aspect ratio. Ultra-detailed,
museum-quality, soft volumetric light, cinematic depth.
Figure prompt (9:16 PNG, true alpha — generate against solid magenta if your tool fakes transparency, then chroma-key):
Single full-body figure on a SOLID FLAT MAGENTA BACKGROUND (#FF00FF). NOT
transparent, NOT checkered — pure solid magenta fill behind figure for chroma
keying. Italian High Renaissance oil-painting style matching Giorgione and
early Raphael. [DESCRIBE YOURSELF: age range, ethnicity, hair, expression].
Standing pose, slight contrapposto, weight on one leg, arms relaxed, one hand
holding a closed gilded illuminated manuscript at hip level. Wearing flowing
ivory and pale-gold classical robe with painterly fabric folds, venetian red
sash. Lit from upper-left by warm golden dawn light, soft pink rim light on
right shoulder, deep umber shadows on left side. Visible oil brushstrokes,
museum-quality. Color palette: ivory #F4EBD9, deep umber #3B2A1A, venetian
red #8B2E1F, old gold #C9A227. Vertical 9:16 frame, full body head to feet.
⚠️ Transparency gotcha: many image generators export a checkerboard pattern as the "background" instead of true alpha. Generate against solid magenta#FF00FF, then key it out:magick figure.png -fuzz 8% -transparent "#FF00FF" figure-cut.png
After generating, compress before committing (Squoosh, ImageOptim, pngquant). Target under 500KB per asset.
Want the figure to actually look like you (or your subject)? Most modern image models accept reference images alongside the prompt — Gemini, Midjourney (--cref), DALL·E, Flux Kontext, ChatGPT image, Nano Banana. Workflow:
-
Attach 1–3 clear photos of your face / full body. Front-facing, even lighting, neutral expression works best. No sunglasses, no heavy filters.
-
Reference them inside the prompt so the model knows what role they play. Add a line like:
Use the attached photo(s) as the facial reference for the figure — match the face, hair, skin tone, and approximate age. Keep the Renaissance oil-painting style; do NOT photorealistically copy the photo, paint it. -
Replace the
[DESCRIBE YOURSELF: ...]placeholder with a one-line description that matches your photo (e.g.mid-30s, West African, short coiled hair, calm gaze). Models lock onto descriptors faster than pixels alone. -
Iterate. Likeness is the hardest part — regenerate 3–6 times, pick the closest, optionally inpaint the face.
Tool-specific notes:
- Midjourney: upload photo, copy URL, prepend to prompt:
<url> <prompt> --cref <url> --cw 80 - Gemini / ChatGPT: drop the image into chat, then paste the prompt — they pick it up automatically
- Flux Kontext / Nano Banana: use the dedicated reference-image slot in the UI
Open src/marginalia/config.js. Edit the SITE object — your name, role, hero copy, manifesto, section titles, footer, etc.
export const SITE = {
owner: {
name: "Your Name",
initials: "YN",
role: "Design Engineer",
location: "Somewhere · Earth",
email: "you@example.com",
},
hero: {
titleLine1: "Building",
titleLine2Italic: "Beautifully",
// ...
},
// ...
};src/marginalia/data/works.js— projects (frame grid)src/marginalia/data/writings.js— essays / talkssrc/marginalia/data/stack.js— tools by categorysrc/marginalia/data/press.js— mentions, downloads, factssrc/marginalia/data/socials.js— contact links
Open src/marginalia/tokens.css. CSS custom properties under :root control palette, type, motion. Defaults:
--r-ivory: #f4ebd9;
--r-umber: #3b2a1a;
--r-venetian: #8b2e1f;
--r-gold: #c9a227;
--r-chartreuse: #d4ff3a; /* accent — change to taste */src/marginalia/components/SkyCanvas.jsx—STAGESarray drives sky color grade per scroll positionsrc/marginalia/components/PageFigure.jsx—SCALE_VALSandX_VALSarrays drive figure size + positionsrc/marginalia/components/PageEmbers.jsx— particle count
The contact form is a stub — submitting flips to a "sealed" state but does nothing. To wire it up, pass an onSubmit handler to <Contact />, or post directly to a service like:
Example:
<Contact
onSubmit={async (formData) => {
await fetch("/api/contact", {
method: "POST",
body: formData,
});
}}
/>The default display font is Instrument Serif. Marginalia bundles two alternates loaded from Google Fonts:
?font=instrument(default)?font=fraunces?font=erode(requires self-hosting, see below)
Set the default in Marginalia.jsx:
const fontKey = (params.get("font") || "instrument").toLowerCase();To use Erode (Indian Type Foundry), self-host the variable woff2 files at /public/fonts/erode/ and add a @font-face rule.
npm run build # → ./dist
npm run preview # serve the build locallyDrop dist/ on any static host. Vercel, Netlify, GitHub Pages all work without config.
- The background painting is a single PNG, served as-is. Compress it before deploy (Squoosh, ImageOptim) — under 500KB is reasonable.
- Consider serving WebP/AVIF for ~40% smaller payloads.
- Lenis adds ~6KB gz; framer-motion ~30KB gz.
MIT — fork it, ship your folio, keep the credit if you'd like (it helps others find the template).
- Built by Derrick Tsorme
- Inspired by Shopify Editions Winter '26 — "The Renaissance Edition"
- Type: Instrument Serif (Rodrigo Fuenzalida + Franco Jonas Henríquez), Inter (Rasmus Andersson), Geist Mono (Vercel)
Issues, ideas, and pull requests welcome. See CONTRIBUTING.md.