Workshops
Small groups, out shooting. I teach camera movement, night skies and getting more from Fujifilm cameras, on my own and through FUJIFILM events.
| Date | Where | Workshop | Format | Status | |
|---|---|---|---|---|---|
| Nov 14 2026 | Indianapolis, IN | Moving the camera | Walk · 3 h | 3 seats left | Reserve |
| Dec 05 2026 | Chicago, IL | Film simulations in practice | FUJIFILM event | Open | Register ↗ |
| Jan 23 2027 | Southern Indiana | Night sky, start to finish | Field · 6 h | Open | Reserve |
| Jun 10 2026 | Theme park | Rides at an eighth of a second | Field · 4 h | Past |
Moving the camera
Three hours on foot, from golden hour to dark. We slow the shutter to an eighth of a second and use the camera's motion as the brush: pans, vertical drags and zoom bursts. Bring any camera with manual shutter control. Fujifilm loaners available.
- Shutter speeds from 1/30 to 1 second, and when each one works
- Panning a walker so the face stays sharp
- Building a sequence instead of a single frame
- Exporting to the 3:4 sheet for print and Instagram
One-on-one sessions
Just you and me, out shooting. Tell me when you're free, what camera you have and what you want to get better at.
Email me to book a session→goldsworthymedia@gmail.comimport * as React from "react"
import { addPropertyControls, ControlType } from "framer"
// Hayden Goldsworthy site header: menu button, tracked wordmark, page links,
// and a drawer that holds navigation plus the About section.
const T = {
ink: "#0F0E0C",
carbon: "#171513",
graphite: "#2B2824",
ash: "#8D877D",
bone: "#E9E4DA",
paper: "#FFFFFF",
}
const SERIF = '"Instrument Serif", "Iowan Old Style", Georgia, serif'
const SANS = '"Geist", ui-sans-serif, system-ui, -apple-system, sans-serif'
const MONO = '"Geist Mono", ui-monospace, Menlo, monospace'
function useFonts() {
React.useEffect(() => {
if (typeof document === "undefined" || document.getElementById("hg-fonts")) return
const l = document.createElement("link")
l.id = "hg-fonts"
l.rel = "stylesheet"
l.href =
"https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500&family=Geist+Mono:wght@400;500&family=Doto:wght@800&display=swap"
document.head.appendChild(l)
}, [])
}
function useWidth(ref: React.RefObject<HTMLElement>) {
const [w, setW] = React.useState(1200)
React.useLayoutEffect(() => {
if (!ref.current) return
const ro = new ResizeObserver((e) => setW(e[0].contentRect.width))
ro.observe(ref.current)
return () => ro.disconnect()
}, [])
return w
}
function isCurrent(link?: string) {
if (!link || typeof window === "undefined") return false
try {
const p = new URL(link, window.location.href).pathname.replace(/\/$/, "")
const here = window.location.pathname.replace(/\/$/, "")
return p === here && p !== ""
} catch {
return false
}
}
type NavLink = { label: string; link?: string }
/**
* @framerSupportedLayoutWidth any-prefer-fixed
* @framerSupportedLayoutHeight auto
*/
export default function SiteHeader(props) {
useFonts()
const {
name,
homeLink,
links = [],
aboutTitle,
aboutText,
portrait,
cameras,
lenses,
socialLabel,
socialLink,
contactNote,
email,
} = props
const ref = React.useRef<HTMLDivElement>(null)
const width = useWidth(ref)
const phone = width < 700
const [open, setOpen] = React.useState(false)
React.useEffect(() => {
if (!open) return
const onKey = (e: KeyboardEvent) => e.key === "Escape" && setOpen(false)
window.addEventListener("keydown", onKey)
return () => window.removeEventListener("keydown", onKey)
}, [open])
const list = (s?: string) =>
(s || "")
.split("\n")
.map((x) => x.trim())
.filter(Boolean)
return (
<div ref={ref} style={{ ...props.style, width: "100%", background: T.ink, color: T.bone }}>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr auto 1fr",
alignItems: "center",
height: 48,
padding: "0 14px",
}}
>
<button
aria-label="Open menu"
onClick={() => setOpen(true)}
style={{
justifySelf: "start",
width: 36,
height: 36,
background: "none",
border: 0,
cursor: "pointer",
display: "grid",
alignContent: "center",
gap: 5,
padding: 0,
}}
>
<i style={{ display: "block", width: 18, height: 1, background: T.bone }} />
<i style={{ display: "block", width: 18, height: 1, background: T.bone }} />
</button>
<a
href={homeLink || "/"}
style={{
fontFamily: SERIF,
fontSize: phone ? 13 : 15,
letterSpacing: phone ? "0.32em" : "0.42em",
paddingLeft: phone ? "0.32em" : "0.42em",
textTransform: "uppercase",
color: T.bone,
textDecoration: "none",
whiteSpace: "nowrap",
}}
>
{name}
</a>
<nav
aria-label="Main"
style={{
justifySelf: "end",
display: phone ? "none" : "flex",
gap: 20,
fontFamily: SANS,
fontSize: 13,
}}
>
{links.map((l: NavLink, i: number) => (
<a
key={i}
href={l.link}
style={{ color: isCurrent(l.link) ? T.bone : T.ash, textDecoration: "none" }}
>
{l.label}
</a>
))}
</nav>
</div>
{/* drawer */}
<div
onClick={() => setOpen(false)}
style={{
position: "fixed",
inset: 0,
zIndex: 900,
background: "rgba(15,14,12,.6)",
opacity: open ? 1 : 0,
pointerEvents: open ? "auto" : "none",
transition: "opacity .3s",
}}
/>
<aside
aria-hidden={!open}
style={{
position: "fixed",
top: 0,
bottom: 0,
left: 0,
zIndex: 901,
width: "min(100%, 560px)",
background: T.ink,
borderRight: `1px solid ${T.graphite}`,
transform: open ? "none" : "translateX(-102%)",
// hidden when closed so it never shows beside the page on the canvas
visibility: open ? "visible" : "hidden",
transition: `transform .35s cubic-bezier(.2,.7,.2,1), visibility 0s linear ${open ? "0s" : ".35s"}`,
overflowY: "auto",
padding: "14px 28px 28px",
display: "flex",
flexDirection: "column",
gap: 36,
color: T.bone,
fontFamily: SANS,
}}
>
<button
aria-label="Close menu"
onClick={() => setOpen(false)}
style={{ alignSelf: "flex-start", background: "none", border: 0, color: T.bone, fontSize: 22, cursor: "pointer", padding: "6px 0" }}
>
×
</button>
<nav style={{ display: "flex", flexDirection: "column", gap: 2 }}>
{[{ label: "Home", link: homeLink || "/" }, ...links].map((l: NavLink, i: number) => (
<a
key={i}
href={l.link}
onClick={() => setOpen(false)}
style={{
fontFamily: SERIF,
fontSize: 40,
lineHeight: 1.1,
color: isCurrent(l.link) ? T.bone : T.ash,
textDecoration: "none",
}}
>
{l.label}
</a>
))}
</nav>
<div style={{ display: "grid", gridTemplateColumns: "120px 1fr", gap: 20, alignItems: "start" }}>
<div style={{ background: T.paper, padding: 2, borderRadius: 12, aspectRatio: "3 / 4" }}>
{portrait?.src ? (
<img
src={portrait.src}
srcSet={portrait.srcSet}
alt={portrait.alt || "Portrait"}
style={{ width: "100%", height: "100%", objectFit: "cover", borderRadius: 10, display: "block" }}
/>
) : (
<div
style={{
width: "100%",
height: "100%",
borderRadius: 10,
background: T.carbon,
display: "grid",
placeItems: "center",
fontFamily: MONO,
fontSize: 10,
color: T.ash,
}}
>
Portrait
</div>
)}
</div>
<div>
<div style={{ fontFamily: SERIF, fontSize: 40, lineHeight: 1 }}>{aboutTitle}</div>
<p style={{ fontSize: 13.5, color: T.ash, marginTop: 10, lineHeight: 1.55 }}>{aboutText}</p>
</div>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24 }}>
{[
["Cameras", list(cameras)],
["Lenses", list(lenses)],
].map(([label, items]: any) => (
<div key={label}>
<span style={{ fontFamily: MONO, fontSize: 10.5, letterSpacing: ".12em", textTransform: "uppercase", color: T.ash }}>
{label}
</span>
<ul style={{ listStyle: "none", margin: "8px 0 0", padding: 0, fontFamily: SERIF, fontSize: 19, lineHeight: 1.45 }}>
{items.map((x: string) => (
<li key={x}>{x}</li>
))}
</ul>
</div>
))}
</div>
<div style={{ marginTop: "auto", display: "flex", flexDirection: "column", gap: 6, fontSize: 13, color: T.ash }}>
{contactNote && <span>{contactNote}</span>}
{email && (
<a href={`mailto:${email}`} style={{ color: T.bone }}>
{email}
</a>
)}
{socialLink && (
<a href={socialLink} target="_blank" rel="noopener" style={{ color: T.bone }}>
{socialLabel} ↗
</a>
)}
</div>
</aside>
</div>
)
}
SiteHeader.defaultProps = {
name: "Hayden Goldsworthy",
homeLink: "/",
links: [
{ label: "Portfolio", link: "/portfolio" },
{ label: "Prints", link: "/prints" },
{ label: "Workshops", link: "/workshops" },
{ label: "Film Sims", link: "/film-sims" },
],
aboutTitle: "Hi, I'm Hayden.",
aboutText:
"I'm a photographer and filmmaker in Indiana, and a FUJIFILM Product Technical Expert. I've spent more than a decade telling stories with cameras, from documentaries and commercial films to teaching. Most of my work slows the shutter down: moving the camera, stacking exposures, waiting for the sky.",
cameras: "X-E5\nX-T5\nGFX100RF\nGFX100 II",
lenses: "XF35mm F1.4\nXF90mm F2\nXF70-300mm\nXF500mm F5.6\nXF8mm F3.5",
socialLabel: "@fujiworthy",
socialLink: "https://www.instagram.com/fujiworthy/",
contactNote: "For prints, workshops and commissions, email me.",
email: "goldsworthymedia@gmail.com",
}
addPropertyControls(SiteHeader, {
name: { type: ControlType.String, title: "Name" },
homeLink: { type: ControlType.Link, title: "Home link" },
links: {
type: ControlType.Array,
title: "Links",
control: {
type: ControlType.Object,
controls: {
label: { type: ControlType.String, title: "Label" },
link: { type: ControlType.Link, title: "Link" },
},
},
},
aboutTitle: { type: ControlType.String, title: "About title" },
aboutText: { type: ControlType.String, title: "About text", displayTextArea: true },
portrait: { type: ControlType.ResponsiveImage, title: "Portrait" },
cameras: { type: ControlType.String, title: "Cameras", displayTextArea: true },
lenses: { type: ControlType.String, title: "Lenses", displayTextArea: true },
contactNote: { type: ControlType.String, title: "Footnote", displayTextArea: true },
email: { type: ControlType.String, title: "Email" },
socialLabel: { type: ControlType.String, title: "Social label" },
socialLink: { type: ControlType.Link, title: "Social link" },
})