// Gallery, artwork detail (with pricing breakdown), commission builder
const { useEffect, useMemo, useRef, useState } = React;
// --- pricing engine
function computePrice(piece, opts) {
const surfaceMul = { canvas: 1.0, wall: 1.6, wearable: 0.7, furniture: 1.4, shoes: 0.55, helmet: 0.6, skate: 0.5, interior: 2.4, glass: 0.9, metal: 1.1, digital: 0.8 };
const sizeMul = { S: 0.7, M: 1.0, L: 1.4, XL: 1.9 };
const frameAdd = { none: 0, wood: 320, oak: 540, brass: 880, museum: 1400 };
const textureMul= { Subtle: 1.0, Layered: 1.18, Heavy: 1.36, Sculptural: 1.6 };
const realismMul= { Atmospheric: 0.95, Figurative: 1.1, Hyperreal: 1.3 };
const rushAdd = { Standard: 0, Priority: 880, Rush: 2200 };
const base = piece.basePrice;
const sub =
base
* (sizeMul[opts.size] ?? 1)
* (textureMul[opts.texture] ?? 1)
* (realismMul[opts.realism] ?? 1)
* (surfaceMul[opts.surface] ?? 1);
const frame = frameAdd[opts.frame] ?? 0;
const rush = rushAdd[opts.rush] ?? 0;
const custom = opts.customDepth * 180; // 0..5
return {
base: Math.round(sub),
frame, rush, custom,
total: Math.round(sub + frame + rush + custom),
hours: Math.round(piece.hours * (sizeMul[opts.size] ?? 1) * (textureMul[opts.texture] ?? 1)),
};
}
const fmt = (n) => "$" + n.toLocaleString("en-US");
// --- gallery (editorial mosaic)
function Gallery({ artworks, onOpen, accentTo }) {
// editorial layout — six tiles with intentional asymmetry
const layout = [
{ col: "1 / span 7", row: "span 14", scale: "lg" },
{ col: "8 / span 5", row: "span 9", scale: "md" },
{ col: "8 / span 5", row: "span 5", scale: "sm" },
{ col: "1 / span 4", row: "span 8", scale: "md" },
{ col: "5 / span 4", row: "span 8", scale: "md" },
{ col: "9 / span 4", row: "span 8", scale: "md" },
{ col: "1 / span 6", row: "span 9", scale: "lg" },
{ col: "7 / span 6", row: "span 9", scale: "lg" },
];
return (
{artworks.map((a, i) => {
const cell = layout[i % layout.length];
return (
accentTo(a.accent)}
onClick={() => onOpen(a)}
>
{String(i + 1).padStart(2, "0")}
{a.category}
{a.title}
{a.dimensions}
{fmt(a.basePrice)}
);
})}
);
}
// --- detail modal
function ArtworkDetail({ piece, onClose, accentTo }) {
const [opts, setOpts] = useState({
size: "M", surface: piece.surfaces[0] || "canvas",
frame: "none", texture: piece.complexity === "Heavy texture" ? "Heavy" : "Layered",
realism: "Figurative", rush: "Standard", customDepth: 1,
});
useEffect(() => { accentTo(piece.accent); }, [piece, accentTo]);
const price = useMemo(() => computePrice(piece, opts), [piece, opts]);
const set = (k, v) => setOpts(o => ({ ...o, [k]: v }));
// ESC close
useEffect(() => {
const onKey = (e) => { if (e.key === "Escape") onClose(); };
window.addEventListener("keydown", onKey);
document.body.style.overflow = "hidden";
return () => {
window.removeEventListener("keydown", onKey);
document.body.style.overflow = "";
};
}, [onClose]);
return (
e.stopPropagation()}>
Medium {piece.medium}
Dimensions {piece.dimensions}
Hours invested {piece.hours} h
Availability {piece.availability}
II. Configure the work
{["S","M","L","XL"].map(v =>
set("size", v)}>{v}
)}
{["canvas","wall","wearable","furniture","glass","metal"].map(v =>
set("surface", v)}>{v}
)}
{[["none","Unframed"],["wood","Walnut"],["oak","Oak"],["brass","Brass"],["museum","Museum"]].map(([k,l]) =>
set("frame", k)}>{l}
)}
{["Subtle","Layered","Heavy","Sculptural"].map(v =>
set("texture", v)}>{v}
)}
{["Atmospheric","Figurative","Hyperreal"].map(v =>
set("realism", v)}>{v}
)}
set("customDepth", parseInt(e.target.value))}
className="ranger"
style={{ accentColor: piece.accent }}
/>
{[["Standard","6–10 wk"],["Priority","4–6 wk"],["Rush","≤ 3 wk"]].map(([k,l]) =>
set("rush", k)}>{l}
)}
Base · sized {fmt(price.base)}
{price.frame > 0 &&
Framing {fmt(price.frame)}
}
{price.custom > 0 &&
Customization {fmt(price.custom)}
}
{price.rush > 0 &&
Priority {fmt(price.rush)}
}
Total · all in
{fmt(price.total)}
≈ {price.hours} h studio time
·
signed · authenticity card
Reserve ↗
View in your space
Pricing scales with detailing, scale, texture, materials, and creative complexity.
Final number confirmed after a 20-minute studio call.
);
}
function Field({ label, hint, children }) {
return (
{label}
{hint && {hint} }
{children}
);
}
function Chip({ active, onClick, children }) {
return (
{children}
);
}
// --- commission builder (multi-step)
function CommissionBuilder({ surfaces }) {
const STEPS = ["Surface", "Style", "Scale", "Story", "Send"];
const [step, setStep] = useState(0);
const [data, setData] = useState({
surface: "wall",
style: "Atmospheric",
palette: "Warm",
scale: "Mid (4-6 ft)",
timeline: "8 weeks",
budget: "5–10k",
note: "",
name: "",
email: "",
});
const [sending, setSending] = useState(false);
const [sent, setSent] = useState(false);
const set = (k, v) => setData(d => ({ ...d, [k]: v }));
// Replace with your Google Apps Script Web App deployment URL
const FORM_ENDPOINT = "PASTE_YOUR_WEB_APP_URL_HERE";
const sendInquiry = async () => {
if (!data.email) { alert("Please add an email so I can reply."); return; }
setSending(true);
try {
await fetch(FORM_ENDPOINT, {
method: "POST",
mode: "no-cors",
body: JSON.stringify({ ...data, sentAt: new Date().toISOString() }),
});
setSent(true);
} catch (err) {
alert("Send failed — please email studio@snigdha.art directly.");
} finally {
setSending(false);
}
};
const goto = (i) => setStep(Math.max(0, Math.min(STEPS.length - 1, i)));
return (
Commission · 04
Bring me
your surface,
and a feeling.
A jacket, a ceiling, a console, a wall that feels too quiet.
The brief begins here. We'll meet in five short steps —
then on a call, in the studio, or in your room.
{surfaces.slice(0, 7).map(s => (
{s.label}
{s.note}
))}
{STEPS.map((s, i) => (
goto(i)} data-cursor="">
{String(i+1).padStart(2,"0")}
{s}
))}
{step === 0 && (
{surfaces.map(s => (
set("surface", s.id)} />
{s.label}
{s.note}
×{s.scale.toFixed(2)}
))}
)}
{step === 1 && (
{[
["Atmospheric", "Mood, weather, low contrast."],
["Figurative", "Bodies, objects, recognisable forms."],
["Abstract", "Gesture, mark, surface."],
["Hyperreal", "Detail at the limits."],
].map(([k, sub]) => (
set("style", k)} />
{k} {sub}
))}
{["Warm","Cool","Earthen","Monochrome","Jewel","Pastel"].map(p => (
set("palette", p)} data-cursor="">
{p}
))}
)}
{step === 2 && (
{["Intimate (≤ 2 ft)","Mid (4-6 ft)","Statement (8-12 ft)","Architectural (12 ft+)"].map(v =>
set("scale", v)}>{v}
)}
{["4 weeks","8 weeks","12 weeks","Open"].map(v =>
set("timeline", v)}>{v}
)}
{["≤ 2k","2–5k","5–10k","10–25k","25k+"].map(v =>
set("budget", v)}>{v}
)}
)}
{step === 3 && (
)}
{step === 4 && (
{sent ? (
Thank you, {data.name || "friend"}. Your brief has reached the studio. I'll write back to {data.email} within 48 hours.
) : (
set("name", e.target.value)}
/>
set("email", e.target.value)}
/>
s.id===data.surface)||{}).label} />
140 ? "…" : "") : "—"} />
{sending ? "Sending…" : "Send to studio"} ↗
Replies within 48 h · No-cost first call
)}
)}
goto(step-1)} data-cursor="">
← Back
goto(step+1)} data-cursor="">
Continue →
);
}
function Step({ title, children }) {
return (
);
}
function Row({ k, v }) {
return {k} {v || "—"} ;
}
Object.assign(window, { Gallery, ArtworkDetail, CommissionBuilder });