Frame 44 — Marketing Site
Eleventy (11ty v3) static site. Nunjucks templates, plain CSS, no client-side framework, no build tooling beyond Eleventy itself.
Build & run
npm install
npm run serve # http://localhost:8080, live reload
npm run build # writes static output to _site/
_site/ is pure static HTML/CSS/JS/SVG — no server-side runtime required.
Deploying (self-hosted)
Copy the contents of _site/ to your web server's document root after running npm run build. Any static file server works. Example nginx server block:
server {
listen 80;
server_name frame44.com www.frame44.com;
root /var/www/frame44-landing-page/_site;
index index.html;
location / {
try_files $uri $uri/index.html =404;
}
}
No Netlify/Cloudflare Pages config is included — this build targets self-hosting. If that changes later, add netlify.toml or a Cloudflare Pages project pointing npm run build → _site/.
The contact page uses a plain mailto: link (see src/contact/index.njk), so there's no form backend to wire up. When email delivery is set up (sendmail or otherwise), the address that link points to still needs to be a real, monitored inbox — see the TODO list below.
Logo & favicon
The real brand mark lives in src/assets/logos/ as huge (8000×4500px) design-export PNGs — 11 variants (colour/mono/white × horizontal/stacked lockups, plus two duplicate favicon-mark exports). That folder is not shipped: eleventy.config.js only passes through src/assets/{css,js,images,fonts}, deliberately excluding src/assets/logos, so those multi-megabyte source files never reach _site/.
What's actually served, all cropped/resized from those sources and checked into src/assets/images/:
logo-header.png— white + red horizontal lockup, for the dark header bandlogo-footer.png— full-colour (black + red) horizontal lockup, for the light footer and the JSON-LDlogofield (schema.org recommends a logo on a light/transparent background, not an inverted one)favicon-16x16.png/favicon-32x32.png/apple-touch-icon.png, andsrc/favicon.ico(multi-size 16/32/48) — the square icon mark composited onto a solid--color-inktile, since the source mark is white + red and needs a dark background to read
If the brand mark changes, re-crop from src/assets/logos/ (or a new export) rather than hand-editing the PNGs in src/assets/images/.
Where prices live
All pricing content is in src/_data/*.json — nothing is hardcoded in templates:
packages.json— the four core packages (Exhibit One, Gallery, Flagship, Multi-Site)historicalCollections.json— licensed historical data collectionsaitools.json— optional AI add-ons + the bundleextras.json— extras, prepay terms, hardware range, grant support note, and the worked-example inputs
Changing a number in one of these files updates every page that shows it. The pricing page's "worked example" and the multi-year prepay example are computed in the template from these JSON values (via the cad, prepay, and findByKey Nunjucks filters in eleventy.config.js) — they are not separately typed-in numbers, so they can't drift out of sync with the package/collection/AI-tool prices.
Adding a package
Add an object to src/_data/packages.json:
{
"key": "unique-key",
"name": "Package Name",
"badge": null,
"buildPrice": 10000,
"annualPrice": 3000,
"summary": "One-line summary.",
"features": ["Feature one", "Feature two"]
}
It will automatically appear in the home page pricing teaser and the /pricing/ packages grid.
Adding a historical collection
Add an object to src/_data/historicalCollections.json with key, name, price, priceType ("annual" or "one-time-per-project"), description, whatsInside, and suits. It will automatically appear on both /pricing/ and /collections/.
Planned /fr/ locale
The site builds English-only today, but the data/folder layout is already split so French can be added without restructuring:
- UI strings (nav labels, buttons, footer copy) live in
src/_data/i18n/en.json, exposed to templates asi18nviasrc/_data/i18n.js. - To add French: create
src/_data/i18n/fr.json, then changei18n.jsfrom always returningento selecting bydata.lang(the comment at the top of that file spells out the exact change). - Duplicate the page templates under
src/fr/(e.g.src/fr/index.njk,src/fr/pricing/index.njk) with front matterlang: frandpermalink: /fr/.... They can{% extends %}/reuse the same layout and macros — only the copy andlangdiffer. - Prices themselves (
packages.json,historicalCollections.json, etc.) don't need duplicating unless product names/descriptions need translation — in that case, add locale-suffixed data files (e.g.packages.fr.json) and pick the right one the same wayi18n.jsdoes.
What's not real yet (TODO before launch)
- Contact email —
contact@frame44.exampleis a placeholder (site.contactEmailConfirmed: falseinsrc/_data/site.json). Flagged inline on the Contact page. - Hardware price range ($3,500–$9,000/screen) — unconfirmed, flagged on the Pricing page (
extras.hardware.confirmed: false). - Installation timeline (8–16 weeks) — unconfirmed, flagged on the Pricing page (
extras.timeline.confirmed: false). - Self-hosted webfonts —
src/assets/css/style.cssreferences/assets/fonts/schibsted-grotesk/*.woff2and/assets/fonts/inter/*.woff2, but no font files are checked in (this environment has no network access to fetch them). The site falls back to system fonts until real woff2 files are added at those paths. - Museum case-study copy — Grootegast and Holten summaries in
src/_data/museums.jsonare<!-- TODO -->placeholders; real photos also needed atsrc/assets/images/placeholders/grootegast.svg/holten.svg(currently generated placeholder graphics). - Team bios — all three entries in
src/_data/team.jsonare placeholders (name, role, bio, photo). - Open Graph share image — no
og:imagetag is set. Add a real image and anog:imagemeta tag insrc/_includes/layouts/base.njkonce one exists. - Lighthouse scores — not run in this environment (no browser available here). Run
npm run build && npx serve _siteand check Performance/Accessibility/Best Practices/SEO locally before launch, particularly once the real webfonts and images are in place (placeholders are lightweight SVG, so real photos may change the Performance number).