// Category-page data model — drives the generic, data-driven CategoryPage.
// Architecture reuses window.ARCH_PROJECTS (defined in content.jsx); the three
// other categories define their rows here. All four share one shape so the same
// tabular page renders each, just with different columns + content.
//
// Per-category config:
//   section : the "§ NN" index number shown in the breadcrumb/eyebrow
//   qual    : bracketed qualifier next to the title (mirrors CATEGORIES[].qual)
//   gate    : optional gated CTA in the hero (architecture work samples, etc.)
//   grid    : CSS grid-template-columns for the table
//   columns : ordered column defs { key, label, kind, align }
//             kinds → 'tnum' (year), 'title' (chevron + name), 'text', 'mono',
//                     'link' (anchor w/ ↗), 'status' (dot + label)
//   facts   : (row) => [[label, value], …]  — quick-facts list in the detail
//   images  : (row) => [{ tag, title, src? }]  — selected-image strip in detail
//   brief   : (row) => string                 — short paragraph in detail
//
// Swap any striped placeholder for a real photo by giving its image a `src`.

// Shared project — referenced from BOTH VISUAL_PROJECTS (visual → graphics) and
// MATERIAL_PROJECTS (material → fabrication). One object, one set of hosted
// images, no duplication: it carries the keys for both column schemas
// (discipline/client/format for visual; process/material/sub for material) and
// the graphics + fabrication filters pick it up via discipline:'print' and
// sub:'fabrication'. Edit it here once and both pages update.
const PLOTTER_PRINTS = {
  yr: '2021', title: 'Pen Plotter Prints',
  discipline: 'print', client: 'futuremagic', format: 'pen plot',
  sub: 'fabrication', process: 'pen plotter', material: 'archival ink on colored stock',
  status: 'complete',
  brief: 'A series of generative drawings output on an AxiDraw pen plotter. Each composition is grown digitally \u2014 topographic contour fields and warped wireframe meshes built in Cinema 4D and Rhino / Grasshopper, then cleaned and pathed in Illustrator \u2014 and drawn by the machine as a single continuous ink line onto colored stock, so the plot is a genuine one-pass pen drawing rather than a print. Released as an open edition through my futuremagic shop. Each sheet signed SR 2021.',
  facts: [['Role', 'Designer'], ['Year', '2021'], ['Generated in', 'Cinema 4D \u00b7 Rhino / Grasshopper'], ['Pathed in', 'Illustrator'], ['Plotted on', 'AxiDraw pen plotter'], ['Shop', 'futuremagic / Etsy']],
  images: [
    { tag: 'topo', title: 'Canyon contour field, black on yellow', src: 'uploads/work/plotter-topo-yellow.png', aspect: '4 / 5', feature: true },
    { tag: 'mesh', title: 'Warped wireframe grid, black on blue', src: 'uploads/work/plotter-grid-blue.png', aspect: '4 / 5' },
    { tag: 'wave', title: 'Moir\u00e9 line wave, black on pink', src: 'uploads/work/plotter-wave-pink.png', aspect: '4 / 5' },
  ],
};

const YUPO_PLOTS = {
  yr: '2023', title: 'Yupo Plotter Drawings',
  discipline: 'print', client: 'Self-initiated', format: 'pen plot',
  sub: 'fabrication', process: 'pen plotter', material: 'black ink on translucent Yupo',
  status: 'complete',
  brief: 'A series of single-line pen-plotter drawings output on an AxiDraw. Each composition is drawn as vector paths in Adobe Illustrator — contour fields, maze-like offset bands, and organic islands left as negative space — then pen-plotted in black ink onto sheets of translucent white Yupo, a synthetic waterproof paper whose milky, non-absorbent surface lets the ink sit crisp and glossy on top. Hung pinned at the corners off the wall so light passes through the stock and the linework floats. Every drawing is one continuous machine pass, signed in the corner.',
  facts: [['Role', 'Designer'], ['Year', '2023'], ['Process', 'Illustrator \u2192 AxiDraw pen plot'], ['Ink', 'Black, single pass'], ['Substrate', 'Translucent Yupo paper'], ['Series', 'Seven drawings']],
  images: [
    { tag: 'topographic', title: 'Dense diagonal contour field', src: 'uploads/work/yupo-plot-4.jpg', aspect: '3 / 4', feature: true },
    { tag: 'maze', title: 'Offset bands around organic islands', src: 'uploads/work/yupo-plot-1.jpg', aspect: '3 / 4' },
    { tag: 'ripple', title: 'Concentric ripple with cloud forms', src: 'uploads/work/yupo-plot-2.jpg', aspect: '3 / 4' },
    { tag: 'coastline', title: 'Contour islands and channels', src: 'uploads/work/yupo-plot-3.jpg', aspect: '3 / 4' },
    { tag: 'radial', title: 'Radial burst with grey fill', src: 'uploads/work/yupo-plot-5.jpg', aspect: '3 / 4' },
    { tag: 'foliage', title: 'Branch and undergrowth contours', src: 'uploads/work/yupo-plot-6.jpg', aspect: '3 / 4' },
    { tag: 'scatter', title: 'Loose organic line scatter', src: 'uploads/work/yupo-plot-7.jpg', aspect: '3 / 4' },
  ],
};

const VISUAL_PROJECTS = [
  PLOTTER_PRINTS,
  YUPO_PLOTS,
  { yr: '2025', title: 'Your Sunday Best', discipline: 'poster', client: 'White Owl Social Club', format: 'event poster', status: 'complete',
    brief: 'A flyer for Your Sunday Best at White Owl Social Club — a daytime-into-evening session with Eddie C, alexi, and residents. The whole sheet is a primary-bright grid of hand-drawn marks: lumpy suns, ribbon waves, and capsule glyphs filled flat in red, blue, green, yellow, and violet with heavy black keylines, printed risograph-rough so the inks sit textured and slightly off-register on a creased paper ground. A wobbly puffed wordmark anchors the top; a starburst "9.14" sets the date. Sunday, September 14th, 2025, 4pm til 11pm.',
    facts: [['Role', 'Designer'], ['Year', '2025'], ['Format', 'Event poster'], ['Venue', 'White Owl Social Club'], ['Bill', 'Eddie C · alexi · residents'], ['Date', 'Sun Sep 14 · 4–11pm']],
    images: [
      { tag: 'poster', title: 'Your Sunday Best — Sun Sep 14', src: 'uploads/work/your-sunday-best-poster.jpg', aspect: '4 / 5' },
      { tag: 'poster', title: 'Your Sunday Best — Sun Jul 27', src: 'uploads/work/your-sunday-best-poster-2.jpg', aspect: '4 / 5' },
    ] },
  { yr: '2024', title: 'Process Molecule', discipline: 'concept art', client: 'futureform', format: 'concept render', status: 'complete',
    brief: 'The concept study for the molecule wall relief at Process. A soft, organic metaball form — overlapping blobs blended into one continuous skin with a hand-worked, clay-like surface — was modeled in Cinema 4D and rendered with Redshift, then composited into the Watershed industrial space to test how the form would read against the venue\u2019s concrete columns and clerestory light before it was scaled up into the dancefloor relief.',
    facts: [['Role', 'Designer'], ['Year', '2024'], ['Context', 'Process Nightclub'], ['Modeled in', 'Cinema 4D — metaballs'], ['Render', 'Redshift']],
    images: [
      { tag: 'render', title: 'Metaball form, C4D + Redshift', src: 'uploads/work/process-molecule-render.jpg', aspect: '108 / 70' },
      { tag: 'in situ', title: 'Composited into the Watershed space', src: 'uploads/work/process-molecule-maquette.jpg', aspect: '1 / 1', feature: true },
    ] },
  { yr: '2025', title: 'Zigzag', discipline: 'concept art', client: 'Self-initiated', format: 'digital illustration', status: 'complete',
    brief: 'A digital concept piece built from a field of soft, organic shapes — a blue ribbon weaving a loose zigzag through scattered vermilion, green, and amber forms with brown pebbles strewn between. Drawn flat in Procreate, then rebuilt as inflated, candy-skinned geometry and rendered in Cinema 4D, finished with a risograph-style halftone grain on a paper-grey ground.',
    facts: [['Role', 'Designer'], ['Year', '2025'], ['Process', 'Procreate → Cinema 4D'], ['Render', 'Inflated geometry'], ['Finish', 'Halftone grain']],
    images: [
      { tag: 'render', title: 'Inflated forms, rendered in C4D', src: 'uploads/work/zigzag-c4d.jpg', aspect: '3 / 4' },
      { tag: 'source', title: 'Original Procreate drawing', src: 'uploads/work/zigzag-procreate.jpg', aspect: '3 / 4' },
    ] },
  { yr: '2026', title: 'Cristi Cons', discipline: 'poster', client: 'Thread the Needle', format: 'event poster', status: 'complete',
    brief: 'A flyer for "Thread the Needle presents cristi cons [amphia | romania]" with Andrew Paris at Process. A churning blue-and-green metaball pour — liquid blobs blended into one continuous skin around a glowing pale-yellow core — floats over a scanned, hand-creased sheet, with a faint draped mesh and thin registration brackets framing the form. Helvetica-set credits in cobalt and black ring the artwork at the corners, and an oversized ghosted "3.21" bleeds off the bottom edge. Friday, March 21, 10pm til late.',
    facts: [['Role', 'Designer'], ['Year', '2026'], ['Format', 'Event flyer'], ['Venue', 'Process'], ['Process', 'Scan + digital'], ['Bill', 'Cristi Cons · Andrew Paris']],
    images: [
      { tag: 'poster', title: 'Cristi Cons — Fri Mar 21', src: 'uploads/work/cristi-cons-poster.jpg', aspect: '4 / 5' },
    ] },
  { yr: '2026', title: 'Adi', discipline: 'poster', client: 'Thread the Needle', format: 'event poster', status: 'complete',
    brief: 'A flyer for "Thread the Needle presents adi [colombia]" with Andrew Paris. Pill- and capsule-shaped windows are cut out of a scanned, hand-creased sheet to reveal a churning blue-resin pour underneath; the type sits on raised, rounded paper keys like a worn keyboard. Friday, March 20, 10p–4a.',
    facts: [['Role', 'Designer'], ['Year', '2026'], ['Format', 'Event flyer'], ['Process', 'Scan + digital'], ['Bill', 'adi · Andrew Paris']],
    images: [
      { tag: 'poster', title: 'adi — Fri Mar 20', src: 'uploads/work/adi-poster.jpg', aspect: '3 / 4' },
    ] },
  { yr: '2025', title: 'Confidant', discipline: 'poster', client: 'Self-initiated', format: 'event poster', status: 'complete',
    brief: 'A flyer for Confidant — a late-night warehouse party with a Funktion-One rig and an undisclosed location revealed day-of. Built on a scan of a folded, creased sheet so the artwork reads as a physical object: typewriter body copy, a hand-scrawled signature, and a sprayed fluorescent-red gesture cutting across a faint wireframe globe. A single olive band locks the title across the middle. 21+, BYOB.',
    facts: [['Role', 'Designer'], ['Year', '2025'], ['Format', 'Event flyer'], ['Process', 'Scan + digital'], ['Edition', 'Web + print']],
    images: [
      { tag: 'poster', title: 'Confidant — Sat Nov 29', src: 'uploads/work/confidant-poster.jpg', aspect: '3 / 4' },
    ] },
  { yr: '2024', title: 'Shawn Rudiman', discipline: 'poster', client: 'SubSensory', format: 'event poster', status: 'complete',
    brief: 'A flyer for "SubSensory + Nu Habitat present: Shawn Rudiman (live) [Detroit Techno Militia]" with DJ Dissolve, JAK b2b Centrikal, and Tracy Why at Process. Built on the acid-green-and-black house style: overlapping blocks of fluorescent green knock the bill out in heavy black Helvetica, while halftone-screened discs — coarse line-rastered circles — anchor the top and lower-left corners. The date slams across the bottom in oversized numerals. Friday, November 1st, $20 presale / $25 door.',
    facts: [['Role', 'Designer'], ['Year', '2024'], ['Format', 'Event flyer'], ['Venue', 'Process — 5040 SE Milwaukie Ave'], ['Presented by', 'SubSensory + Nu Habitat'], ['Bill', 'Shawn Rudiman · DJ Dissolve · JAK b2b Centrikal · Tracy Why']],
    images: [
      { tag: 'poster', title: 'Shawn Rudiman — Fri Nov 1', src: 'uploads/work/shawn-rudiman-poster.jpg', aspect: '1 / 1' },
    ] },
  { yr: '2024', title: 'Shvili', discipline: 'poster', client: 'Thread the Needle', format: 'event poster', status: 'complete',
    brief: 'A flyer for "Thread the Needle presents shvili [resolute | nyc]" with Ricardo Roessel [alternate time movements | chi] and 8maos, at Process. The bill is knocked out of a creased, scan-textured panel so the type takes its color from what shows through — a watery, iridescent field of CRT-scanline distortion, a motion-blurred figure, and a glowing lime wireframe cube floating in the lower right. Saturday, August 17, 10p–5a.',
    facts: [['Role', 'Designer'], ['Year', '2024'], ['Format', 'Event flyer'], ['Venue', 'Process'], ['Bill', 'Shvili · Ricardo Roessel · 8maos']],
    images: [
      { tag: 'poster', title: 'Shvili — Sat Aug 17', src: 'uploads/work/shvili-poster.jpg', aspect: '4 / 5' },
    ] },
  { yr: '2023', title: 'Andrey Pushkarev', discipline: 'poster', client: 'Thread the Needle', format: 'event poster', status: 'complete',
    brief: 'A flyer for "Thread the Needle presents andrey pushkarev [luck of access]" with Andrew Paris at Watershed. Glossy, candy-like inflated "Z" glyphs and capsule shapes — rendered in green, lime, and amber — tumble across a creased grey sheet, set against a clean Helvetica bill. December 16, 10p–late.',
    facts: [['Role', 'Designer'], ['Year', '2023'], ['Format', 'Event flyer'], ['Venue', 'Watershed'], ['Bill', 'Andrey Pushkarev · Andrew Paris']],
    images: [
      { tag: 'poster', title: 'Andrey Pushkarev — Sat Dec 16', src: 'uploads/work/andrey-pushkarev-poster.jpg', aspect: '1 / 1' },
    ] },
  { yr: '2023', title: 'Canyon Vibration 2023', discipline: 'poster', client: 'Occasion Vibration', format: 'event identity', status: 'complete',
    brief: 'The promo suite for the first Canyon Vibration — an Occasion Vibration campout in Tygh Valley, Oregon headlined by Avalon Emerson [Another Dove · USA], Shanti Celeste [Hessle Audio · UK], and Mike Servito [The Bunker · NY]. A flat, sun-warmed system on a marigold paper ground: a halftone-screened canyon wordmark, a bold cobalt switchback river zigzagging down the sheet past pale dune triangles, scattered fir-tree clusters, and a vermilion sun. Built to flex between a square poster and a wide landscape banner. September 8–10, 2023.',
    facts: [['Role', 'Designer'], ['Year', '2023'], ['Format', 'Poster + banner'], ['Headliners', 'Avalon Emerson · Shanti Celeste · Mike Servito'], ['Location', 'Tygh Valley, OR'], ['Time', 'Sep 8–10']],
    images: [
      { tag: 'poster', title: 'Canyon Vibration — Sep 8–10', src: 'uploads/work/canyon-2023-poster.jpg', aspect: '4 / 5', feature: true },
      { tag: 'banner', title: 'Landscape banner cut of the same system', src: 'uploads/work/canyon-2023-banner.jpg', aspect: '2 / 1' },
    ] },
  { yr: '2024', title: 'Canyon Vibration', discipline: 'poster', client: 'Occasion Vibration', format: 'event poster', status: 'complete',
    brief: 'A flyer for Canyon Vibration — an Occasion Vibration three-day campout in Tygh Valley, Oregon headlined by ANZ [Hessle Audio · UK], DJ Voices [Nowadays · NY], and Soichi Terada (live) [Rush Hour · Japan], over a deep local bill. The whole sheet is a sculpted relief: hand-shaped polymer-clay tiles — zigzag teeth, sunbursts, pine triangles, and glossy black nameplates — modeled and rendered in Cinema 4D so the artwork reads as a tactile mosaic pressed into a putty-grey wall, lit to catch the soft clay edges. August 2–4, 2024.',
    facts: [['Role', 'Designer'], ['Year', '2024'], ['Format', 'Event flyer'], ['Process', 'Cinema 4D — sculpted clay relief'], ['Headliners', 'ANZ · DJ Voices · Soichi Terada'], ['Location', 'Tygh Valley, OR · Aug 2–4']],
    images: [
      { tag: 'poster', title: 'Canyon Vibration — Aug 2–4', src: 'uploads/work/canyon-vibration-poster.jpg', aspect: '4 / 5', feature: true },
    ] },
  { yr: '2024', title: 'Orchard Vibration 2024', discipline: 'poster', client: 'Occasion Vibration', format: 'event poster', status: 'complete',
    brief: 'The second Orchard Vibration — an Occasion Vibration day-to-sunrise party in Newberg, Oregon headlined by Ash Lauryn [Underground & Black · Atlanta/Detroit] and bmg [Interdimensional Transmissions · Detroit], with kud, nite bite, Peter Marks b2b Bobby Souers, and Spencer Miles. An oversized outlined "8.24" runs off the top edge over a creased off-white sheet, the bill set in a tight pink-to-aubergine Helvetica column beside a saturated psychedelic photo window of a sun-flared orchard canopy melting into silhouetted dancers. Saturday, August 24, 2024, 5pm til sunrise.',
    facts: [['Role', 'Designer'], ['Year', '2024'], ['Format', 'Event flyer'], ['Headliners', 'Ash Lauryn · bmg'], ['Location', 'Newberg, OR'], ['Time', 'Sat Aug 24 · 5pm–sunrise']],
    images: [
      { tag: 'poster', title: 'Orchard Vibration — Ash Lauryn · bmg', src: 'uploads/work/orchard-vibration-2024-flat.jpg', aspect: '4 / 5', feature: true },
      { tag: 'mockup', title: 'Printed on a creased sheet', src: 'uploads/work/orchard-vibration-2024-poster.jpg', aspect: '1 / 1' },
      { tag: 'in situ', title: 'Posted up under the orchard canopy', src: 'uploads/work/orchard-vibration-2024-situ.jpg', aspect: '2 / 1' },
    ] },
  { yr: '2023', title: 'Echoes from the Future', discipline: 'poster', client: 'Occasion Vibration', format: 'event poster', status: 'complete',
    brief: 'A New Year\u2019s Eve flyer for "Echoes from the Future" — an Occasion Vibration · Night Service & Uplift party at Watershed with Ayesha [NYC], cay horiuchi, and leeonn b2b Peter Marks. A holographic, iridescent composition: a spiraling cluster of oil-slick soap bubbles tumbles across a soft cyan-to-coral gradient, overprinted with the title knocked through in chunky letters, faint axis tick-marks, a wireframe-globe panel, and a scribbled fortune-teller margin. 12.31 NYE, 9pm til late.',
    facts: [['Role', 'Designer'], ['Year', '2023'], ['Format', 'Event flyer'], ['Venue', 'Watershed'], ['Bill', 'Ayesha · cay horiuchi · leeonn b2b Peter Marks'], ['Occasion', 'NYE · 12.31']],
    images: [
      { tag: 'poster', title: 'Echoes from the Future — NYE 12.31', src: 'uploads/work/echoes-future-poster.jpg', aspect: '3 / 4' },
    ] },
  { yr: '2023', title: 'Midland', discipline: 'poster', client: 'Occasion Vibration', format: 'event poster', status: 'complete',
    brief: 'A flyer for Occasion Vibration presents Midland [Graded · UK] with resident Peter Marks at The Den. A flat coral sheet with an outlined "2.24" running off the top, the headliner set huge in stacked black Helvetica, and a teal photo window holding a loose, Matisse-like continuous-line figure hunched into the frame. Built to flex between a square poster and a wide landscape banner cut. February 24, 2023, 10pm to 4am.',
    facts: [['Role', 'Designer'], ['Year', '2023'], ['Format', 'Poster + banner'], ['Venue', 'The Den'], ['Bill', 'Midland · Peter Marks'], ['Time', 'Feb 24 · 10pm–4am']],
    images: [
      { tag: 'poster', title: 'Midland — Fri Feb 24', src: 'uploads/work/midland-poster.jpg', aspect: '1 / 1', feature: true },
      { tag: 'banner', title: 'Landscape banner cut', src: 'uploads/work/midland-banner.jpg', aspect: '1 / 1' },
    ] },
  { yr: '2023', title: 'Orchard Vibration', discipline: 'poster', client: 'Occasion Vibration', format: 'event poster', status: 'complete',
    brief: 'Identity and flyer for Orchard Vibration — an Occasion Vibration day-to-night party in Newberg, Oregon headlined by Yu Su [Vancouver, BC], with Carly Barton, DJ EFT, Garon, Peter Marks, and Spencer Miles. An all-green wordmark, an oversized "7.8" date, and a dappled-canopy photo window printed on a creased, off-white sheet. Saturday, July 8, 2023, 2pm til morning.',
    facts: [['Role', 'Designer'], ['Year', '2023'], ['Format', 'Flyer + banner'], ['Headliner', 'Yu Su'], ['Location', 'Newberg, OR']],
    images: [
      { tag: 'poster', title: 'Orchard Vibration — Yu Su', src: 'uploads/work/orchard-vibration-poster.jpg', aspect: '1 / 1' },
      { tag: 'banner', title: 'Foldout / event banner', src: 'uploads/work/orchard-vibration-banner.jpg', aspect: '1 / 1' },
    ] },
  { yr: '2023', title: '36 Days of Type', discipline: 'type', client: 'Competition entry', format: '3d lettering', status: 'complete',
    brief: 'A submission for the 2023 36 Days of Type challenge — a complete A–Z and 0–9 set of soft, inflated glyphs modeled and rendered in Cinema 4D, hand-dyed in a blue / amber / vermilion palette and finished with a risograph-style halftone grain on a paper-grey ground.',
    facts: [['Role', 'Designer'], ['Year', '2023'], ['Set', 'A–Z · 0–9'], ['Tool', 'Cinema 4D'], ['Finish', 'Halftone grain']],
    images: [
      { tag: 'full set', title: 'All 36 glyphs', src: 'uploads/work/36-days-all.jpg', aspect: '1 / 1' },
      { tag: 'A', title: 'A', src: 'uploads/work/36-days-a.jpg', aspect: '1 / 1' },
      { tag: 'B', title: 'B', src: 'uploads/work/36-days-b.jpg', aspect: '1 / 1' },
      { tag: 'C', title: 'C', src: 'uploads/work/36-days-c.jpg', aspect: '1 / 1' },
      { tag: '1', title: '1', src: 'uploads/work/36-days-1.jpg', aspect: '1 / 1' },
      { tag: '2', title: '2', src: 'uploads/work/36-days-2.jpg', aspect: '1 / 1' },
      { tag: '3', title: '3', src: 'uploads/work/36-days-3.jpg', aspect: '1 / 1' },
    ] },
  // NOTE: visual → motion is NOT card-driven. The 31 real clips live in
  // motion-clips.jsx and render as a hover-to-play grid (motion-grid.jsx) wired
  // up in subcategory-page.jsx — so there are intentionally no discipline:'motion'
  // rows here. To change what shows under motion, edit MOTION_REAL in
  // motion-clips.jsx, not this array.
];

const CODING_PROJECTS = [
  { yr: '2026', title: 'Vaseworks', stack: 'React · Vite', status: 'live', link: { label: 'vaseworks.spencer-russell.com', url: 'https://vaseworks.spencer-russell.com' },
    brief: 'A browser tool for designing parametric ceramic forms by sketching a profile curve and revolving it, then exporting a watertight, single-wall STL ready to print in vase mode. Built to bridge the gap between a CAD model and what actually survives the kiln.',
    facts: [['Role', 'Solo build'], ['Stack', 'React, Vite'], ['Status', 'Live']],
    images: [
      { tag: 'app', title: 'Profile editor + 3D preview', src: 'uploads/work/vaseworks-app.png' },
      { tag: 'irl', title: 'Vase-mode print off the bed', src: 'uploads/work/vaseworks-print.jpg' },
    ] },
  { yr: '2026', title: 'SVG Plotter Generator', stack: 'Vue 3 · d3-contour', status: 'live', link: { label: 'svg-plotter.spencer-russell.com', url: 'https://svg-plotter.spencer-russell.com' },
    brief: 'A browser tool for generating pen-plotter-ready SVGs for the AxiDraw — topographic contour maps from any searched location, or moiré interference patterns from layered geometry. Paths are reordered nearest-neighbor to minimize pen-up travel, and exported with real physical dimensions.',
    facts: [['Role', 'Solo build'], ['Stack', 'Vue 3, Vite'], ['License', 'MIT']],
    images: [
      { tag: 'ui', title: 'Topo + moiré controls', src: 'uploads/work/svg-plotter-app.png' },
      { tag: 'output', title: 'Plotted sheet' },
      { tag: 'detail', title: 'Hatch fill close-up' },
    ] },
];

const MATERIAL_PROJECTS = [
  PLOTTER_PRINTS,
  YUPO_PLOTS,
  { yr: '2025', title: 'Acrylic Paint Pour', sub: 'handmade', process: 'acrylic pouring', material: 'Golden acrylics + pouring medium', status: 'complete',
    brief: 'A fluid-art pour on a square panel using Golden acrylics cut with Golden\u2019s Color Pouring Medium (Gloss) to a self-leveling consistency. Red, blue, and yellow are layered and tilted across the surface so they marble into one another \u2014 the medium lowers the surface tension just enough to keep the colors crisp while letting them braid into ribbons and cells, with the runoff catching its own secondary patterns off the edge of the board.',
    facts: [['Process', 'Fluid acrylic pour'], ['Paint', 'Golden acrylics'], ['Medium', 'Golden Color Pouring Medium (Gloss)'], ['Palette', 'Primary red / blue / yellow'], ['Year', '2025']],
    images: [
      { tag: 'panel', title: 'The poured panel, primaries marbled', src: 'uploads/work/paint-pour-canvas.jpg', aspect: '3 / 4' },
      { tag: 'detail', title: 'Ribbons braiding across the surface', src: 'uploads/work/paint-pour-detail.jpg', aspect: '3 / 4' },
      { tag: 'cells', title: 'Runoff cells off the edge', src: 'uploads/work/paint-pour-cells.jpg', aspect: '3 / 4' },
      { tag: 'bench', title: 'On the pour station', src: 'uploads/work/paint-pour-bench.jpg', aspect: '3 / 4', feature: true },
    ] },
  { yr: '2026', title: 'Twisted Vase-Mode Prints', sub: 'fabrication', process: 'spiral vase-mode FDM', material: 'PLA', status: 'complete',
    related: { label: 'Vaseworks', to: { page: 'category', cat: 'coding', open: 'Vaseworks' } },
    brief: 'A set of single-wall vessels — a tall faceted vase and a pair of twisted desk cups — printed in spiral vase mode, where the nozzle climbs in one continuous seamless wall instead of stacking discrete layers. The profiles were designed in Vaseworks, my browser tool for sketching a revolve profile and exporting a watertight single-wall STL; a per-height rotation applied to the profile gives the slow twist that catches light down the faceted ridges. Printed in cobalt PLA at a coarse line width so the ribbed wall reads as deliberate texture rather than layer artifact.',
    facts: [['Process', 'Spiral vase mode'], ['Design', 'Vaseworks STL'], ['Wall', 'Single-wall, seamless'], ['Material', 'Cobalt PLA'], ['Feature', 'Per-height twist'], ['Year', '2026']],
    images: [
      { tag: 'vase', title: 'Tall faceted vase, twist catching the light', src: 'uploads/work/vaseworks-twist-vase.jpg', aspect: '3 / 4', feature: true },
      { tag: 'pair', title: 'Twisted desk cups — one as a pen pot', src: 'uploads/work/vaseworks-twist-pair.jpg', aspect: '3 / 4' },
    ] },
  { yr: '2025', title: 'Cast Candle Holder', sub: ['fabrication', 'handmade'], process: 'print → mold → cast', material: 'pigmented gypsum resin, beeswax/soy wax', status: 'complete',
    brief: 'A small wicked candle vessel made through the full toolchain: a master is modeled and 3D printed, then used to cast a two-part silicone mold. Modified gypsum resin — pigmented in the bucket for solid color — is poured into the mold, with a raised feature forming a recess that seats the candle\u2019s wick clip. Once cured, a melted beeswax-and-soy blend is poured into the finished holder around a wooden wick, propped level on a stick while it sets.',
    facts: [['Master', '3D-printed pattern'], ['Mold', 'Two-part silicone'], ['Cast', 'Pigmented gypsum resin'], ['Wax', 'Beeswax + soy blend'], ['Feature', 'Wick-clip recess'], ['Year', '2025']],
    images: [
      { tag: 'finished', title: 'Finished holder, poured candle + wooden wick', src: 'uploads/work/candle-finished.jpg', aspect: '3 / 4' },
      { tag: 'molds', title: 'Silicone molds + pigmented resin on the bench', src: 'uploads/work/candle-mold-bench.jpg', aspect: '3 / 4' },
      { tag: 'mold', title: 'Silicone negative — raised wick-recess feature', src: 'uploads/work/candle-mold-empty.jpg', aspect: '3 / 4' },
      { tag: 'wick', title: 'Wick clip seated in the mold recess', src: 'uploads/work/candle-mold-wick.jpg', aspect: '3 / 4' },
      { tag: 'pour', title: 'Pouring the beeswax/soy melt around the wick', src: 'uploads/work/candle-pour.jpg', aspect: '3 / 4' },
    ] },
  { yr: '2025', title: 'Geo Candle', sub: 'fabrication', process: '3d printing', material: 'translucent white PLA', status: 'complete',
    brief: 'A geometric candle diffuser designed in Rhino and 3D printed in translucent white filament \u2014 a study in how the printer\u2019s internal infill geometry reads as light. With the outer walls kept thin and translucent, the lattice that normally hides inside the part becomes the whole point: a candle set inside casts the structure outward, so the wall glows with the pattern of its own scaffolding. Printed in two infill schemes \u2014 a crisp orthogonal cubic lattice and a soft, continuous gyroid \u2014 to compare how each diffuses and patterns the light.',
    facts: [['Modeled in', 'Rhino'], ['Process', 'FDM 3D print'], ['Material', 'Translucent white PLA'], ['Study', 'Infill light diffusion'], ['Infill', 'Cubic · gyroid'], ['Year', '2025']],
    images: [
      { tag: 'cubic', title: 'Cubic infill, lit \u2014 orthogonal grid through the facets', src: 'uploads/work/geocandle-cubic.png', aspect: '1 / 1', feature: true },
      { tag: 'gyroid', title: 'Gyroid infill, honeycomb cells diffusing the light', src: 'uploads/work/geocandle-gyroid.png', aspect: '3 / 2' },
      { tag: 'gyroid', title: 'Gyroid form, head-on glow', src: 'uploads/work/geocandle-gyroid-2.png', aspect: '3 / 2' },
      { tag: 'object', title: 'The printed diffuser, unlit', src: 'uploads/work/geocandle-unlit.png', aspect: '3 / 2' },
      { tag: 'scale', title: 'For scale, beside the tea light', src: 'uploads/work/geocandle-scale.png', aspect: '3 / 2' },
    ] },
  { yr: '2025', title: 'UV-Drawn Glow Castings', sub: 'fabrication', process: 'cast silicone + UV plotter', material: 'glow-powder silicone composite', status: 'complete',
    brief: 'Square tiles cast from a platinum-cure silicone loaded with strontium-aluminate glow powder, poured into shallow 3D-printed frame molds so each pour sets as a flat, translucent slab that holds a charge. Once cured, a UV laser diode is mounted in the pen carriage of an AxiDraw plotter in place of a pen — so instead of inking, the machine traces vector paths in light, sweeping the beam across the phosphorescent surface to "draw" with charge. Wherever the beam dwells the powder excites and holds, so the plotted linework — grids, contour fields, warped meshes — blooms back out as a glowing afterimage in orange, teal, and blue once the room goes dark, fading slowly as the charge decays.',
    facts: [['Process', 'Cast silicone \u2192 UV plot'], ['Cast', 'Platinum silicone + glow powder'], ['Pigment', 'Strontium-aluminate phosphor'], ['Mold', '3D-printed frame'], ['Charge', 'UV laser on AxiDraw'], ['Source', 'Vector paths'], ['Year', '2025']],
    images: [
      { tag: 'glowing', title: 'Plotted vector paths blooming back in the dark', src: 'uploads/work/glow-silicone-tiles.jpg', aspect: '3 / 4', feature: true },
      { tag: 'plotting', title: 'UV laser tracing paths across a tile on the AxiDraw', src: 'uploads/work/glow-silicone-laser.jpg', aspect: '3 / 4' },
      { tag: 'casting', title: 'Mixing the glow-powder silicone, tiles curing in 3D-printed frames', src: 'uploads/work/glow-silicone-bench.jpg', aspect: '3 / 4' },
    ] },
  { yr: '2024', title: 'Canyon Vibration Mobiles', sub: 'handmade', process: 'cut + painted lumber', material: 'dimensional lumber, acrylic, cord', status: 'complete',
    brief: 'Hanging mobiles made as site decor for Canyon Vibration. Off-cuts of dimensional lumber are sawn into simple geometric shapes — squares, triangles, leaning trapezoids — sanded, then painted in flat blocks of red, blue, yellow, grey, and black so the saw-cut end grain and growth rings still read through the color. Strung in vertical pairs and threes on cord, they were hung from a strung line across the meadow and tucked into the orchard trees, turning and clacking in the canyon wind over the festival grounds in Tygh Valley.',
    facts: [['Process', 'Cut + hand-painted lumber'], ['Material', 'Dimensional lumber, acrylic'], ['Form', 'Hanging mobiles'], ['Context', 'Canyon Vibration decor'], ['Location', 'Tygh Valley, OR'], ['Year', '2024']],
    images: [
      { tag: 'strung', title: 'Painted shapes strung across the meadow', src: 'uploads/work/canyon-mobiles-line.jpg', aspect: '1 / 1', feature: true },
      { tag: 'cutting', title: 'Sawing shapes from dimensional lumber on the miter saw', src: 'uploads/work/canyon-mobiles-cut.jpg', aspect: '3 / 4' },
      { tag: 'batch', title: 'The cut batch stacking up at the saw station', src: 'uploads/work/canyon-mobiles-saw.jpg', aspect: '3 / 4' },
      { tag: 'in trees', title: 'Tucked into the orchard canopy', src: 'uploads/work/canyon-mobiles-tree.jpg', aspect: '4 / 5' },
    ] },
  { yr: '2025', title: 'Discharge-Bleached Bolt', sub: 'handmade', process: 'dye + discharge', material: 'natural-fiber fabric, Procion MX', status: 'complete',
    brief: 'A continuous bolt of natural-fiber cloth — several yards long — immersion-dyed solid black with Procion MX, then accordion-folded and clamped into a repeating shibori grid and dipped in a bleach bath to discharge the color back out. The dye lifts unevenly along the fold lines, leaving a ladder of soft-edged ecru and warm-grey bursts on a near-black ground that runs the full length of the fabric.',
    facts: [['Process', 'Immersion dye \u2192 fold \u2192 discharge'], ['Dye', 'Procion MX (black)'], ['Resist', 'Accordion fold + clamp'], ['Discharge', 'Bleach bath'], ['Format', 'Continuous bolt'], ['Year', '2025']],
    images: [
      { tag: 'full bolt', title: 'The full length, laid out on the floor', src: 'uploads/work/shibori-bolt-full.jpg', aspect: '3 / 4' },
      { tag: 'run', title: 'Fold-grid repeat down the length', src: 'uploads/work/shibori-bolt-run.jpg', aspect: '3 / 4' },
      { tag: 'detail', title: 'Discharged bursts along the fold lines', src: 'uploads/work/shibori-bolt-detail.jpg', aspect: '3 / 4' },
      { tag: 'scale', title: 'For scale', src: 'uploads/work/shibori-bolt-feet.jpg', aspect: '3 / 4', feature: true },
    ] },
  { yr: '2025', title: 'Procion Garment Dyeing', sub: 'handmade', process: 'fiber-reactive dyeing', material: 'natural-fiber fabric, Procion MX', status: 'complete',
    brief: 'A run of solid-shade garment dyeing — thrifted and blank natural-fiber pieces (100% cotton and rayon) immersion-dyed with Procion MX fiber-reactive dyes in a soda-ash bath. Each color is built in its own vat: a cool turquoise-to-teal blend, a deep golden amber, and a saturated royal violet, agitated by hand and left to cure so the dye chemically bonds to the cellulose for a wash-fast finish.',
    facts: [['Process', 'Immersion / vat dyeing'], ['Dye', 'Procion MX fiber-reactive'], ['Fixative', 'Soda ash'], ['Fiber', 'Cotton · rayon (cellulose)'], ['Year', '2025']],
    images: [
      { tag: 'teal', title: 'Turquoise-to-teal vat, cotton', src: 'uploads/work/garment-dye-teal.jpg', aspect: '1 / 1' },
      { tag: 'amber', title: 'Golden amber bath', src: 'uploads/work/garment-dye-amber.jpg', aspect: '1 / 1' },
      { tag: 'violet', title: 'Royal violet vat', src: 'uploads/work/garment-dye-violet.jpg', aspect: '1 / 1' },
    ] },
  { yr: '2025', title: 'Slip-Cast Porcelain Tumbler', sub: 'fabrication', process: 'print \u2192 plaster mold \u2192 slip cast', material: '3D-printed master, plaster, porcelain slip', status: 'complete',
    brief: 'A faceted, half-twisted hexagonal tumbler made through the full ceramic-production toolchain. A master is modeled and 3D printed, then blocked into a two-part plaster mold so the geometry — flat triangulated facets spiraling a quarter turn up the body — transfers cleanly. Liquid porcelain slip is poured into the closed plaster mold; the gypsum wicks water out of the slip to build an even wall against the cavity, the excess is drained, and the leather-hard casting is freed when the halves are pulled apart. Cleaned up and fired to a soft bone-white bisque that holds every crisp facet edge.',
    facts: [['Master', '3D-printed pattern'], ['Mold', 'Two-part plaster'], ['Cast', 'Porcelain slip'], ['Form', 'Twisted faceted hexagon'], ['Finish', 'Bone-white bisque'], ['Year', '2025']],
    images: [
      { tag: 'finished', title: 'The fired porcelain tumbler, facets and quarter twist', src: 'uploads/work/porcelain-cup-finished.jpg', aspect: '3 / 4', feature: true },
      { tag: 'master', title: '3D-printed master seated in the plaster block', src: 'uploads/work/porcelain-cup-master.jpg', aspect: '3 / 4' },
      { tag: 'mold', title: 'Master in the open mold cavity', src: 'uploads/work/porcelain-cup-mold.jpg', aspect: '3 / 4' },
      { tag: 'demold', title: 'Greenware casting freed from the plaster halves', src: 'uploads/work/porcelain-cup-demold.jpg', aspect: '3 / 4' },
      { tag: 'halves', title: 'The two-part plaster mold, faceted negatives', src: 'uploads/work/porcelain-cup-halves.jpg', aspect: '3 / 4' },
    ] },
  { yr: '2025', title: 'Voronoi Sphere', sub: 'fabrication', process: 'C4D \u2192 3d print \u2192 resin cast', material: '3D-printed PLA, cast resin', status: 'complete',
    brief: 'A spherical voronoi cage modeled in Cinema 4D — a closed shell scored into irregular polygonal cells, then thickened so the cell walls become rounded struts around open windows. Printed on an Ender FDM machine in white filament, then flooded with a pourable casting resin: the resin sheets over the strut network and pools into the open cells, drying to a glossy skin that webs across the openings. Lit from inside, the finished object throws the voronoi pattern outward as a soft net of light and shadow across the wall.',
    facts: [['Modeled in', 'Cinema 4D \u2014 voronoi'], ['Print', 'Ender FDM \u2014 white PLA'], ['Finish', 'Flood-cast resin skin'], ['Study', 'Cast light + shadow'], ['Year', '2025']],
    images: [
      { tag: 'shadow', title: 'Lit from inside, voronoi shadows across the wall', src: 'uploads/work/voronoi-sphere-shadow.jpg', aspect: '3 / 4', feature: true },
      { tag: 'model', title: 'The voronoi cage modeled in Cinema 4D', src: 'uploads/work/voronoi-sphere-model.jpg', aspect: '3 / 4' },
      { tag: 'print', title: 'Printing the cage on the Ender', src: 'uploads/work/voronoi-sphere-print.jpg', aspect: '3 / 4' },
      { tag: 'cast', title: 'Resin sheeting over the strut network', src: 'uploads/work/voronoi-sphere-cast.jpg', aspect: '3 / 4' },
      { tag: 'drip', title: 'Resin pooling and dripping off the cells', src: 'uploads/work/voronoi-sphere-drip.jpg', aspect: '3 / 4' },
      { tag: 'lit', title: 'Finished sphere glowing on the counter', src: 'uploads/work/voronoi-sphere-lit.jpg', aspect: '3 / 4' },
    ] },
  { yr: '2025', title: 'Rusted Metaball', sub: 'fabrication', process: '3d printing + patina', material: 'iron-filled PLA (Protopasta)', status: 'complete',
    brief: 'A sibling to the Metaball Stars, printed in Protopasta\u2019s iron-filled filament so the part carries real iron powder in its skin. Sanded and polished to open the metal up, then brushed with a rust-activating solution that oxidizes the surface in place \u2014 turning the embedded iron to a mottled orange-and-grey patina that settles into the contour layer lines and the recessed metaball pockets.',
    facts: [['Process', 'FDM + chemical patina'], ['Modeled in', 'Cinema 4D — metaballs'], ['Material', 'Protopasta iron-filled PLA'], ['Finish', 'Sanded, polished, rust patina'], ['Year', '2025']],
    images: [
      { tag: 'patina', title: 'Rust patina settled into the metaball pockets', src: 'uploads/work/metaball-iron-hand.jpg', aspect: '3 / 4' },
      { tag: 'oxidized', title: 'Mottled orange-and-grey oxidation', src: 'uploads/work/metaball-iron-bench.jpg', aspect: '3 / 4' },
    ] },
  { yr: '2025', title: 'Metaball Stars', sub: 'fabrication', process: '3d printing', material: 'metal-filled PLA (copper, bronze, brass)', status: 'complete',
    brief: 'A radial, urchin-like form grown from overlapping metaballs in Cinema 4D — soft blobs blended into a single skin so the spikes, satellite beads, and central bore all read as one continuous surface. Printed three times in Protopasta\u2019s metal-filled filaments — one each in copper, bronze, and brass — then sanded back through grits and polished so the embedded metal powder burnishes to a real patina. The fused-layer contour lines wrap the body like topographic rings.',
    facts: [['Process', 'FDM, metal-filled filament'], ['Modeled in', 'Cinema 4D — metaballs'], ['Material', 'Protopasta copper · bronze · brass'], ['Finish', 'Sanded + polished'], ['Edition', 'One of each metal'], ['Year', '2025']],
    images: [
      { tag: 'tips', title: 'Spike tips and contour layer lines, close', src: 'uploads/work/metaball-star-detail.png', aspect: '3 / 2', feature: true },
      { tag: 'front', title: 'Radial form head-on, central bore', src: 'uploads/work/metaball-star-front.png', aspect: '1 / 1' },
      { tag: 'raking', title: 'Raking light across the burnished skin', src: 'uploads/work/metaball-star-angle.png', aspect: '3 / 2' },
      { tag: 'set', title: 'One of each — copper, bronze, brass', src: 'uploads/work/metaball-set.jpg', aspect: '3 / 4' },
      { tag: 'brass', title: 'Brass, polished to a burnished shine', src: 'uploads/work/metaball-brass.jpg', aspect: '3 / 4' },
      { tag: 'detail', title: 'Metaball skin and contour layer lines', src: 'uploads/work/metaball-copper.jpg', aspect: '3 / 4' },
    ] },
  { yr: '2022', title: 'Diffraction Earrings', sub: 'handmade', process: 'resin casting', material: 'resin, diffraction film', status: 'complete',
    brief: 'A pair of statement earrings made by pouring clear casting resin freehand over diffraction grating film and peeling the cured forms off the backing, so the microscopic grating transfers to the resin face. The flat surfaces split light into a full spectrum that slides as the earring turns; hung on anodized niobium ear wires and jump rings.',
    facts: [['Process', 'Hand-poured resin'], ['Material', 'Casting resin, diffraction film'], ['Findings', 'Anodized niobium wires'], ['Year', '2022']],
    images: [
      { tag: 'in light', title: 'The pair in sunlight, full-spectrum diffraction', src: 'uploads/work/earrings-grass.jpg', aspect: '3 / 4', feature: true },
      { tag: 'object', title: 'The pair, diffraction in raking light', src: 'uploads/work/earrings-bench.jpg' },
      { tag: 'detail', title: 'Full-spectrum split across the faces', src: 'uploads/work/earrings-detail.jpg' },
      { tag: 'bench', title: 'On the bench with the findings', src: 'uploads/work/earrings-pair.jpg' },
    ] },
];

// Public photography gallery (visual → photo). Organized into named sections;
// each section is a grid of image slots. Same image convention as everywhere:
// { tag, title, src? } — omit `src` for a striped placeholder, drop real photos
// in uploads/work/ and add `src` to fill them. This is the PUBLIC tier; the
// private, password-protected albums live off-site (META.photoArchiveUrl).
const PHOTO_SECTIONS = [
  { id: 'nature', label: 'nature', meta: 'landscape · sea', images: [
    { tag: 'dusk', title: 'Owyhee, pink light over the valley floor', src: 'uploads/work/nature/Owyhee_2026-1.jpg' },
    { tag: 'blue hour', title: 'Contrail through the ponderosas', src: 'uploads/work/nature/Owyhee_2026-43.jpg' },
    { tag: 'rock', title: 'Rhyolite crag, midday', src: 'uploads/work/nature/Owyhee_2026-16.jpg' },
    { tag: 'moon', title: 'Moonrise over the rimrock', src: 'uploads/work/nature/Owyhee_2026-19.jpg' },
    { tag: 'spires', title: 'Hoodoos on the sage slope', src: 'uploads/work/nature/Owyhee_2026-26.jpg' },
    { tag: 'landscape', title: 'Layered hills, high desert', src: 'uploads/work/nature/Owyhee_2026-37.jpg' },
    { tag: 'coast', title: 'Sea stack at low tide', src: 'uploads/work/nature/Coast_2026-1680.jpg' },
    { tag: 'tide pools', title: 'Sea stacks beyond the tide flat', src: 'uploads/work/nature/Coast_2026-1731.jpg' },
    { tag: 'river', title: 'Old pilings, cargo ships on the Columbia', src: 'uploads/work/nature/Coast_2026-1893.jpg' },
    { tag: 'island', title: "Ireland's Eye across the sound", src: 'uploads/work/nature/Ireland_2026-0356.jpg' },
    { tag: 'alpine', title: 'Mt. Adams, glacier and meltwater stream', src: 'uploads/work/nature/Rainier_2026-4094.jpg' },
  ] },
  { id: 'architecture', label: 'architecture', meta: 'built · structure', images: [
    { tag: 'night', title: 'Slatted timber cantilevers, uplit', src: 'uploads/work/architecture/Slatted_8197.jpg' },
    { tag: 'bridge', title: 'St. Johns Bridge, Gothic arches from below', src: 'uploads/work/architecture/StJohns_6341.jpg' },
  ] },
  { id: 'events', label: 'events', meta: 'live · gatherings', images: [
    { tag: 'venue', title: 'Light shafts and paper lanterns, the hall', src: 'uploads/work/events/Venue_2022-0133.jpg' },
    { tag: 'projection', title: 'Canyon Vibration, checkerboard on the cliff', src: 'uploads/work/events/CanyonVibration_2022-0155.jpg' },
    { tag: 'lasers', title: 'Canyon Vibration, lasers over the crowd', src: 'uploads/work/events/CanyonVibration_2022-0158.jpg' },
    { tag: 'projection', title: 'Canyon Vibration, spirograph projection on the cliff face under the stars', src: 'uploads/work/events/CanyonVibration_2022-0161.jpg' },
  ] },
  { id: 'europe', label: 'europe', meta: 'ireland', images: [
    { tag: 'ruin', title: 'Yellow Steeple, Trim, evening light', src: 'uploads/work/europe/Ireland_2026-0501.jpg' },
    { tag: 'ruin', title: 'Tracery window against the sky', src: 'uploads/work/europe/Ireland_2026-0515.jpg' },
    { tag: 'pasture', title: 'Dairy cow, Galtee foothills', src: 'uploads/work/europe/Ireland_2026-0678.jpg' },
    { tag: 'coast', title: "Ireland's Eye from Howth", src: 'uploads/work/europe/Ireland_2026-0449.jpg' },
  ] },
  { id: 'macro', label: 'macro', meta: 'close · texture', images: [
    { tag: 'flora', title: 'Chamomile, shallow focus', src: 'uploads/work/macro/Chamomile_2026-0906.jpg' },
    { tag: 'bloom', title: 'Coral peonies in afternoon light', src: 'uploads/work/macro/Peony_2022-0050.jpg' },
    { tag: 'stamen', title: 'Into the peony, stamens and carpels', src: 'uploads/work/macro/Peony_2022-0060.jpg' },
    { tag: 'conifer', title: 'Pine candles and new cones', src: 'uploads/work/macro/Pine_2266.jpg' },
  ] },
  { id: 'street', label: 'street', meta: 'found · color', images: [
    { tag: 'facade', title: 'Blue doors, No. 26', src: 'uploads/work/street/Street_2026-0378.jpg' },
  ] },
];

// Unified per-category page config. Architecture pulls rows from ARCH_PROJECTS;
// its facts are derived from the existing fields so content.jsx stays untouched.
const CATEGORY_PAGES = {
  architecture: {
    section: '01', qual: 'spatial',
    gallery: true,  // enables the index ⇄ gallery (photo-card) view toggle on the page
    gate: {
      inPage: true,
      // Public view shows no gate UI at all; these strings appear only after
      // unlocking, via the hidden access slug (see photo-gate.jsx PHOTO_GATE_SLUG).
      unlockedLabel: 'photography unlocked',
      unlockedSub: 'Firm project photos are visible on this device. Re-lock to hide them again.',
    },
    grid: '90px 1.6fr 110px 1fr 0.8fr 0.7fr',
    columns: [
      { key: 'yr', label: 'Year', kind: 'tnum' },
      { key: 'title', label: 'Project', kind: 'title' },
      { key: 'firm', label: 'Firm', kind: 'mono' },
      { key: 'loc', label: 'Location', kind: 'text' },
      { key: 'sector', label: 'Sector', kind: 'mono' },
      { key: 'status', label: 'Status', kind: 'status', align: 'right' },
    ],
    rows: window.ARCH_PROJECTS,
    facts: (r) => [['Tags', r.tag], ['Area', r.area], ['Client', r.client], ['Role', r.role], ['Location', r.loc], ['GC', r.gc], ['Awards', r.awards]],
    images: (r) => r.images || [],
    brief: (r) => r.brief,
  },
  visual: {
    section: '02', qual: 'design',
    grid: '90px 1.6fr 1fr 1fr 0.9fr 0.7fr',
    columns: [
      { key: 'yr', label: 'Year', kind: 'tnum' },
      { key: 'title', label: 'Project', kind: 'title' },
      { key: 'discipline', label: 'Discipline', kind: 'mono' },
      { key: 'client', label: 'Client', kind: 'text' },
      { key: 'format', label: 'Format', kind: 'mono' },
      { key: 'status', label: 'Status', kind: 'status', align: 'right' },
    ],
    rows: VISUAL_PROJECTS,
    facts: (r) => r.facts || [],
    images: (r) => r.images || [],
    brief: (r) => r.brief,
  },
  coding: {
    section: '03', qual: 'tools',
    gallery: true,   // index ⇄ gallery (project-card) view toggle, like architecture
    hideSubs: true,  // hide the subcategory chips in the hero — the gallery cards cover the same projects
    grid: '90px 1.7fr 1.2fr 0.8fr 1fr',
    columns: [
      { key: 'yr', label: 'Year', kind: 'tnum' },
      { key: 'title', label: 'Project', kind: 'title' },
      { key: 'stack', label: 'Stack', kind: 'mono' },
      { key: 'status', label: 'Status', kind: 'status' },
      { key: 'link', label: 'Link', kind: 'link', align: 'right' },
    ],
    rows: CODING_PROJECTS,
    facts: (r) => r.facts || [],
    images: (r) => r.images || [],
    brief: (r) => r.brief,
  },
  material: {
    section: '04', qual: 'experiments',
    gallery: true,  // index ⇄ gallery (project-card) view toggle, like architecture + coding
    grid: '90px 1.6fr 1fr 1fr 0.7fr',
    columns: [
      { key: 'yr', label: 'Year', kind: 'tnum' },
      { key: 'title', label: 'Object', kind: 'title' },
      { key: 'process', label: 'Process', kind: 'mono' },
      { key: 'material', label: 'Material', kind: 'text' },
      { key: 'status', label: 'Status', kind: 'status', align: 'right' },
    ],
    rows: MATERIAL_PROJECTS,
    facts: (r) => r.facts || [],
    images: (r) => r.images || [],
    brief: (r) => r.brief,
  },
};

// Statuses that read as "active" get the warm accent dot; everything else inks.
const ACTIVE_STATUSES = ['in construction', 'ongoing', 'live', 'wip', 'in progress', 'conceptual', 'permitted'];

window.CATEGORY_PAGES = CATEGORY_PAGES;
window.ACTIVE_STATUSES = ACTIVE_STATUSES;
window.PHOTO_SECTIONS = PHOTO_SECTIONS;
