// sections.jsx — Hero, Gallery, Detail overlay, Process spread, Commission, Contact
const { useState, useMemo, useEffect } = React;
// ── Hero ──────────────────────────────────────────────────────────────
function Masthead() {
return (
Vol. VII · Spring/Summer 2026
);
}
function Hero({ onNav }) {
return (
Olena
& the graphite
portrait.
Pencil portraits drawn from the photographs you send me. Of someone you love. Of someone you have lost. Made slowly, by hand, on paper. Currently open for commissions.
In this issue ↓
Fig. 01 — Featured
Solenne
Year 2025
Medium Graphite, Paper
Size 40 × 50 cm
From A single photograph
Note Past commission, not for sale
onNav('detail', 'w01')} style={{ alignSelf: 'flex-start' }}>See the drawing →
onNav('commission')} style={{ alignSelf: 'flex-start', background: 'var(--ink)', color: 'var(--paper)', borderColor: 'var(--ink)' }}>Commission a portrait →
);
}
// ── Gallery ───────────────────────────────────────────────────────────
function FilterBar({ filters, set, count }) {
return (
{Object.keys(window.FILTERS).map((k) => (
{k}
{window.FILTERS[k].map((opt) => (
set(k, opt)}>
{opt}
))}
))}
{count} {count === 1 ? 'work' : 'works'}
);
}
function WorkCard({ w, onOpen }) {
return (
onOpen(w.id)}>
Open ↗
Fig. {w.no}
{w.titleIt ? {w.title} : w.title}
{w.year} · {w.size}
);
}
function Gallery({ layout, onOpen }) {
const [filters, setFilters] = useState({ subject: 'All', size: 'All', year: 'All' });
const set = (k, v) => setFilters((f) => ({ ...f, [k]: v }));
const works = useMemo(() => window.WORKS.filter((w) => (
(filters.size === 'All' || w.size === filters.size) &&
(filters.subject === 'All' || w.subject === filters.subject) &&
(filters.year === 'All' || String(w.year) === filters.year)
)), [filters]);
const cls = layout === 'masonry' ? 'gallery-masonry' : layout === 'strip' ? 'gallery-strip' : 'gallery-grid';
return (
§ 02
Past commissions
{window.WORKS.length} drawings · 2023–2025 · archive only
{works.map((w) => )}
{works.length === 0 && (
No works match these filters.
)}
);
}
// ── Spread (removed — quote treatment was too heavy) ──────────────────
function Spread() { return null; }
// ── Process ───────────────────────────────────────────────────────────
function Process() {
return (
§ 03
The studio
A portrait, end to end
From a photograph. Drawn the old way.
Every portrait begins with a conversation and the photographs you send me. A wedding, a christening, a last birthday, a quiet morning at the kitchen table. The image you choose matters as much as the drawing that follows.
From there, pencils. Grades 2H through 6B, kneaded eraser, cotton paper, hours. I work between four and eight weeks depending on scale, sending one progress photograph at the halfway mark. Most drawings take six to ten weeks in total.
01 · Brief
The first letter
You write to me. Who is this for, who is in the photograph, what should I know.
02 · Photographs
Choosing the reference
You send me what you have. Together we choose the image, or piece one together from several.
03 · Drawing
Long, quiet hours
Worked in the studio over four to twelve weeks. One progress photograph by post or email.
04 · Delivery
Signed and shipped
Archival framing on request, otherwise rolled or boxed flat. Insured worldwide.
);
}
// ── Commission ────────────────────────────────────────────────────────
function CommissionForm({ preselected, onPick }) {
const [sent, setSent] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [form, setForm] = useState({
name: '', email: '',
subject: 'One person', tier: preselected || 'M',
timeline: 'Within 3 months', notes: '',
});
useEffect(() => { if (preselected) setForm((f) => ({ ...f, tier: preselected })); }, [preselected]);
const update = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value }));
const submit = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const res = await fetch('/api/commission', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form),
});
if (!res.ok) {
const json = await res.json().catch(() => ({}));
throw new Error(json.detail || 'Something went wrong — please try again.');
}
setSent(true);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
if (sent) {
return (
✓ Letter received
Thank you, {form.name || 'friend'}.
I read every commission inquiry myself. You will hear from me within a week, usually sooner. If it is urgent, write again — sometimes the post sleeps.
setSent(false)}>Write another
);
}
return (
);
}
function Commission({ onNav }) {
const [picked, setPicked] = useState(null);
return (
§ 04
Commission a portrait
Three tiers · open quarterly
{window.TIERS.map((t) => (
Tier {t.key} · {t.name}
{t.name}
Size {t.size}
From {t.sittings}
Timeline {t.timeline}
{t.includes.map((inc, i) => {inc} )}
{ setPicked(t.key); document.getElementById('commission-form-anchor')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }}>
Begin with {t.name} →
))}
);
}
// ── Detail overlay ────────────────────────────────────────────────────
function Detail({ id, onClose, onOpen }) {
const w = window.WORKS.find((x) => x.id === id);
const idx = window.WORKS.findIndex((x) => x.id === id);
const prev = window.WORKS[(idx - 1 + window.WORKS.length) % window.WORKS.length];
const next = window.WORKS[(idx + 1) % window.WORKS.length];
useEffect(() => {
const onKey = (e) => {
if (e.key === 'Escape') onClose();
if (e.key === 'ArrowLeft') onOpen(prev.id);
if (e.key === 'ArrowRight') onOpen(next.id);
};
document.addEventListener('keydown', onKey);
return () => document.removeEventListener('keydown', onKey);
}, [prev, next, onOpen, onClose]);
if (!w) return null;
return (
Fig. {w.no} / {String(window.WORKS.length).padStart(2, '0')} · {w.title}
onOpen(prev.id)} aria-label="Previous">←
onOpen(next.id)} aria-label="Next">→
✕
Fig. {w.no} — {w.subject}
{w.titleIt ? {w.title} : w.title}
{w.lede}
Year {w.year}
Medium {w.medium}
Dimensions {w.dim}
Size class {w.size === 'S' ? 'Small' : w.size === 'M' ? 'Medium' : 'Large'}
Subject {w.subject}
Note Past commission · archive only
{w.notes && (
Studio note
{w.notes}
)}
{ onClose(); document.getElementById('commission')?.scrollIntoView({ behavior: 'smooth' }); }}>Commission a portrait like this →
onOpen(next.id)}>Next drawing →
);
}
// ── Footer / Contact ──────────────────────────────────────────────────
function Foot() {
return (
);
}
Object.assign(window, { Masthead, Hero, Gallery, Spread, Process, Commission, Detail, Foot });