This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Mater is a static publication exploring materials through the eyes of artists and researchers. It is a deliberately low-carbon website — every technical decision should support that goal.
npm run serve # Start local dev server with hot-reload
npm run build # Build static site to _site/
No test suite is configured.
No environment variables required. Content is stored as Markdown files in content/ and read at build time — no external CMS credentials needed.
This project is built to minimise its environmental impact. All changes must preserve or improve on these constraints:
@font-face rules<style> at build time via the cssmin filter; no separate stylesheet requests<img> srcs to Cloudflare Image Transformation URLs (/cdn-cgi/image/, AVIF/WebP via format=auto); use loading="lazy" and set data-img-width per usage; do not add unoptimised <img> tagsdefer and is the only exception)Eleventy (11ty) static site with content managed via PagesCMS. Content lives as Markdown files with YAML frontmatter in content/ and is read at build time using gray-matter + fast-glob.
content/**/*.md files (YAML frontmatter) → _data/*.js reads them with gray-matter → Liquid templates consume the data → Eleventy outputs _site/.
_data/stories.js → reads content/stories/*.md, sorted by date_data/issueonecontributors.js → reads content/contributors/*.md, sorted by date_data/references.js → reads content/references/*.md, sorted by date_data/categoriesList.js → static array of material category labels/values_data/about.js → reads content/about/*.md (one file per page on /about/), sorted by order frontmatter; each page gets a slug used as a linkable #hash id_data/announcement.js → reads content/announcement/index.md; optional homepage banner shown when enabled: trueContent pages (content/glossary/, etc.) are processed directly by Eleventy — their .md files carry their own layout and permalink frontmatter. content/about/ and content/announcement/ produce no pages of their own; they render into the About page (/about/) and the home page respectively.
content/glossary-words/ and content/reading-list/ items are tagged as Eleventy collections (glossaryWords, readingList) and rendered into the glossary page — they don’t produce their own output pages.
.eleventy.jsThe main Eleventy config (~240 lines) contains:
cssmin (CSS minification), hexToRgb, lookUpColour, lookUpColourBackground (14-colour palette lookups), markdownify (renders Markdown strings to HTML), hasCategory (filters items by category value)contributorHero, contributorHeroBig, audio (with optional transcript), nav, footereleventyComputed: Global computed data that assigns layout, permalink, tags, title, and meta to content directory files — this replaces .11tydata.js files so PagesCMS only sees .md files in collection directoriespage) with prev/next post navigationcontent/
about/ # One .md per page on /about/ (title/order/body; no output pages of their own)
announcement/ # Optional homepage announcement banner (no output page)
contributors/ # Issue One contributor pages
glossary/ # Glossary page content
glossary-words/ # Individual glossary entries (no output pages)
reading-list/ # Reading list entries (no output pages)
references/ # References/materials entries
stories/ # Story pages
New pages are created as About pages in this section (managed in PagesCMS); there is no separate footer-pages collection. The footer is a fixed set of links matching the nav (Mater, About, Stories, Texts, Podcast) plus a back-to-top arrow and the carbon badge, and is not rendered on the home page. The central nav is Material Archive, Stories, Texts, Podcast, About; Search sits alone in the top-right corner (both desktop and mobile).
Root-level .liquid files are pages; _includes/ contains layout and shared components:
| File | Route | Notes |
|---|---|---|
index.liquid |
/ |
Home — contributor cards + story grid |
about.liquid |
/about/ |
About page — white, :target-switched panels from content/about/ |
stories.liquid |
/stories/ |
Story listing grid |
categories.liquid |
/categories/ |
Categories index |
categories-category.liquid |
/categories/{slug}/ |
Per-category story listing |
chapterone.liquid |
/chapterone/ |
Issue One contributor archive |
landing-page.liquid |
— | Deprecated landing page |
robots.liquid |
/robots.txt |
Allows all crawlers |
sitemap.liquid |
Sitemap | Auto-generated |
_includes/layout.liquid |
— | Main HTML wrapper; inlines all CSS |
_includes/stories-story.liquid |
/stories/{slug}/ |
Individual story; scroll colour animation |
_includes/chapterone-stories.liquid |
/chapterone/{slug}/ |
Individual contributor page; scroll colour animation |
Layout assignment for content/ files is handled by eleventyComputed in .eleventy.js, not by template frontmatter in the files themselves.
14 named colours are defined in .eleventy.js (COLOURS / COLOURS_BG objects) and exposed via lookUpColour / lookUpColourBackground filters. Colour names come from content frontmatter fields:
purple, blue, rose, red, mud, ochre, moss, grass, jade, sea, midnight, amythyst, forest, grey
The CSS :has() pseudo-class in storycolors.css changes section backgrounds on hover — no JavaScript required.
Plain CSS, no framework. All files live in _includes/ and are inlined at build time:
main.css — primary styles; uses CSS custom properties; Times New Roman throughoutreset.css — Meyer reset with custom focus styles (cyan #b8fbff)storycolors.css — per-story/contributor colour theming via :has()src/assets/ is copied verbatim to build output. Contains logo, favicon, background images, uploaded content images, and the Arts Council England logo. src/_redirects is a Netlify redirects file.
Images are resized on Cloudflare’s edge at request time via Image Transformations, not during the build. An Eleventy transform (imageCdn in .eleventy.js) rewrites every <img src="/src/assets/uploads/..."> in the output HTML to a /cdn-cgi/image/width=…,fit=scale-down,quality=82,format=auto/... URL. Templates choose a size with a data-img-width attribute on the <img> (thumbs 240, cards 600, heroes/lightboxes 1200; unmarked body images default to 1000); the attribute is stripped from the output.
The same transform also (in every environment, not just production):
width/height attributes with each upload’s intrinsic pixel size (read at build time via image-size, cached per build) so browsers reserve the aspect-ratio box before images load — no pop-in/layout shift. A global img { height: auto } rule in main.css keeps rendered height ratio-derived wherever CSS constrains only the width; any CSS rule that sets a fixed height on upload images must also set width: auto.src paths — files in the repo are NFC; frontmatter written in decomposed Unicode (NFD, e.g. from macOS filename pasting) would otherwise 404 on the normalisation-sensitive host..svg and .avif files (serves the originals): SVGs need no resizing, and AVIF is not a supported input format for Cloudflare Image Transformations (returns 415, err 9520). Keep AVIF uploads under ~1MB or convert them to WebP/JPEG.data-img-inline images as data URIs (≤100KB, else falls back to the CDN path): used for the text-page heroes — tiny pixelated PNGs (~8KB) that must be painted on first render. Inlining puts them in the HTML itself, so they appear in the cross-document view-transition frame instead of flashing in after the crossfade (images are never render-blocking, so a fetched hero always arrives after first paint).The rewrite only runs when CF_PAGES_BRANCH=main (Cloudflare Pages production builds), because /cdn-cgi/image/ only resolves on the custom domain (mater.digital) — *.pages.dev previews and local dev serve the raw originals. Transformations must be enabled once per zone in the Cloudflare dashboard (Images → Transformations). format=auto delivers AVIF or WebP per browser and counts as one unique transformation; the free tier allows 5,000 unique transformations per month and the site currently uses ~600. Builds consume none — a build only writes URLs.
Hosted on Cloudflare Pages. Build output is _site/. Redirects are in src/_redirects.