creating layout
29
.claude/settings.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"defaultMode": "acceptEdits",
|
||||||
|
"allow": [
|
||||||
|
"Bash(npm install *)",
|
||||||
|
"Bash(npm run *)",
|
||||||
|
"Bash(npx @11ty/eleventy *)",
|
||||||
|
"Bash(npx eleventy *)",
|
||||||
|
"Bash(git add *)",
|
||||||
|
"Bash(git commit *)",
|
||||||
|
"Bash(git status)",
|
||||||
|
"Bash(git diff *)",
|
||||||
|
"Bash(git log *)",
|
||||||
|
"Bash(mkdir *)",
|
||||||
|
"Bash(ls *)"
|
||||||
|
],
|
||||||
|
"ask": [
|
||||||
|
"Bash(git push *)",
|
||||||
|
"Bash(npm publish *)"
|
||||||
|
],
|
||||||
|
"deny": [
|
||||||
|
"Read(.env)",
|
||||||
|
"Read(**/.env*)",
|
||||||
|
"Bash(rm -rf *)",
|
||||||
|
"Bash(curl *)",
|
||||||
|
"Bash(wget *)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
53
CLAUDE.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## What this is
|
||||||
|
|
||||||
|
Frame 44 marketing site — an Eleventy (11ty v3) static site. Nunjucks templates, plain CSS, no client-side framework, no build tooling beyond Eleventy itself. Sells map-native interactive exhibit software to museums, historical societies, and heritage sites.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
npm run serve # http://localhost:8080, live reload
|
||||||
|
npm run build # writes static output to _site/
|
||||||
|
```
|
||||||
|
|
||||||
|
There is no test suite (`npm test` is a stub) and no linter configured.
|
||||||
|
|
||||||
|
`_site/` is pure static HTML/CSS/JS/SVG — no server-side runtime required. Deployment is self-hosted (copy `_site/` to a web server document root); no Netlify/Cloudflare Pages config exists.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**Data-driven pricing.** All pricing content lives in `src/_data/*.json`, never hardcoded in templates:
|
||||||
|
- `packages.json` — the four core packages (Exhibit One, Gallery, Flagship, Multi-Site)
|
||||||
|
- `historicalCollections.json` — licensed historical data collections
|
||||||
|
- `aitools.json` — optional AI add-ons + the bundle
|
||||||
|
- `extras.json` — extras, prepay terms, hardware range, grant support note, worked-example inputs
|
||||||
|
|
||||||
|
Adding an object to `packages.json` or `historicalCollections.json` automatically surfaces it on the relevant pages (home pricing teaser, `/pricing/`, `/collections/`) — no template changes needed. The pricing page's worked examples are *computed* in templates from this JSON via the `cad`, `prepay`, and `findByKey` Nunjucks filters defined in `eleventy.config.js`, so they can't drift out of sync with the underlying prices.
|
||||||
|
|
||||||
|
Key filters/shortcodes in `eleventy.config.js`:
|
||||||
|
- `cad` — formats a number as CAD currency (whole dollars)
|
||||||
|
- `prepay` — multi-year prepay pricing: 3yr = 2.5x annual, 5yr = 4x annual
|
||||||
|
- `sum`, `findByKey` — array helpers used in templates
|
||||||
|
- `year` shortcode — current year for footer copyright
|
||||||
|
|
||||||
|
**i18n scaffolding (English-only today).** UI strings live in `src/_data/i18n/en.json`, exposed to templates as `i18n` via `src/_data/i18n.js` (currently always returns `en`). The folder layout anticipates a `/fr/` locale: adding French means creating `src/_data/i18n/fr.json`, switching `i18n.js` to select by `data.lang`, and duplicating page templates under `src/fr/` with `lang: fr` front matter (they can extend/reuse the same layouts and macros). Product pricing data (`packages.json` etc.) only needs duplicating if names/descriptions require translation — see `i18n.js` for the pattern.
|
||||||
|
|
||||||
|
**Logos vs. served images.** `src/assets/logos/` holds huge (8000×4500px) raw design-export PNGs (11 variants). `eleventy.config.js` passthrough copy deliberately excludes this folder — only `src/assets/{css,js,images,fonts}` ship to `_site/`. What's actually served (checked into `src/assets/images/`, cropped/resized from the raw sources) is `logo-header.png` (white+red, dark header), `logo-footer.png` (full-colour, light footer + JSON-LD), and the favicon set. If the brand mark changes, re-crop from `src/assets/logos/` rather than hand-editing the served images.
|
||||||
|
|
||||||
|
**Templates.** `src/_includes/layouts/base.njk` is the shared layout; `src/_includes/macros.njk` holds shared Nunjucks macros; `src/_includes/partials/` holds header/footer. Page routes are folders under `src/` (`about/`, `contact/`, `museums/`, `platform/`, `pricing/`, `collections/` via `src/collections/index.njk`) each with an `index.njk`.
|
||||||
|
|
||||||
|
**Contact.** The contact page uses a plain `mailto:` link (`src/contact/index.njk`) — there is no form backend.
|
||||||
|
|
||||||
|
## Known placeholders / unconfirmed data (see `site.json` / `extras.json` flags)
|
||||||
|
|
||||||
|
- `contactEmail` in `src/_data/site.json` is a placeholder (`contactEmailConfirmed: false`).
|
||||||
|
- Hardware price range and installation timeline in `extras.json` are unconfirmed (`extras.hardware.confirmed` / `extras.timeline.confirmed`).
|
||||||
|
- `src/assets/css/style.css` references self-hosted webfonts (Schibsted Grotesk, Inter) at `/assets/fonts/...` that are not checked in; the site falls back to system fonts until real `.woff2` files are added.
|
||||||
|
- Museum case-study copy in `src/_data/museums.json` (Grootegast, Holten) and all entries in `src/_data/team.json` are placeholders.
|
||||||
|
- No `og:image` is set in `src/_includes/layouts/base.njk`.
|
||||||
|
|
||||||
|
Treat these as real gaps, not stale notes — check the `confirmed` flags in the JSON before assuming a number is final.
|
||||||
99
README.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# 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:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
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 band
|
||||||
|
- `logo-footer.png` — full-colour (black + red) horizontal lockup, for the light footer and the JSON-LD `logo` field (schema.org recommends a logo on a light/transparent background, not an inverted one)
|
||||||
|
- `favicon-16x16.png` / `favicon-32x32.png` / `apple-touch-icon.png`, and `src/favicon.ico` (multi-size 16/32/48) — the square icon mark composited onto a solid `--color-ink` tile, 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 collections
|
||||||
|
- `aitools.json` — optional AI add-ons + the bundle
|
||||||
|
- `extras.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`:
|
||||||
|
|
||||||
|
```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 as `i18n` via `src/_data/i18n.js`.
|
||||||
|
- To add French: create `src/_data/i18n/fr.json`, then change `i18n.js` from always returning `en` to selecting by `data.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 matter `lang: fr` and `permalink: /fr/...`. They can `{% extends %}`/reuse the same layout and macros — only the copy and `lang` differ.
|
||||||
|
- 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 way `i18n.js` does.
|
||||||
|
|
||||||
|
## What's not real yet (TODO before launch)
|
||||||
|
|
||||||
|
- **Contact email** — `contact@frame44.example` is a placeholder (`site.contactEmailConfirmed: false` in `src/_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.css` references `/assets/fonts/schibsted-grotesk/*.woff2` and `/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.json` are `<!-- TODO -->` placeholders; real photos also needed at `src/assets/images/placeholders/grootegast.svg` / `holten.svg` (currently generated placeholder graphics).
|
||||||
|
- **Team bios** — all three entries in `src/_data/team.json` are placeholders (name, role, bio, photo).
|
||||||
|
- **Open Graph share image** — no `og:image` tag is set. Add a real image and an `og:image` meta tag in `src/_includes/layouts/base.njk` once one exists.
|
||||||
|
- **Lighthouse scores** — not run in this environment (no browser available here). Run `npm run build && npx serve _site` and 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).
|
||||||
95
docs/frame44-site-update-prompt.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# Frame 44 site update — realign pricing model, gate prices, add data catalogue
|
||||||
|
|
||||||
|
Three tasks in one pass, on the existing Eleventy (11ty v3) site. Keep the current architecture exactly: all pricing content in `src/_data/*.json`, Nunjucks templates, plain CSS, the i18n split, no hardcoded prices in templates, no new build tooling.
|
||||||
|
|
||||||
|
**Read `CLAUDE.md` and `README.md` first** — follow the data-driven pattern, filter/shortcode conventions, logo/passthrough rules, and i18n scaffolding described there.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## HARD RULE — no prices anywhere on the public site
|
||||||
|
|
||||||
|
**No dollar figures, no "starting at," no ranges, anywhere in the built site.** Pricing lives only in a PDF guide sent on request. Price fields still exist in the JSON data files (for internal use and future PDF generation) but **must never be rendered in a public template**. Add a build-time check: grep the rendered `_site/` output for `$` in page content and confirm none appears in pricing context. If a price would surface on a page, that's a bug.
|
||||||
|
|
||||||
|
The public site's job: explain *how* pricing works and *what* exists, then capture the lead via two CTAs — **"Request the pricing guide"** and **"Inquire"**. Every interested museum should identify itself before it sees a number.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TASK A — Replace the outdated pricing model
|
||||||
|
|
||||||
|
The current `packages.json` (Exhibit One / Gallery / Flagship / Multi-Site, build+annual prices) is from an older model. Replace the data model with the current one. Update the JSON so the data is correct and internally consistent; the public templates render names and descriptions only (never prices — see hard rule).
|
||||||
|
|
||||||
|
1. **`packages.json` → Studio tiers, one-time.** Three objects:
|
||||||
|
- Studio 1 — `oneTimePrice` 2500, features: "1 published exhibit", "1 theme"
|
||||||
|
- Studio 3 — `oneTimePrice` 7000, `badge` "Most chosen", features: "3 published exhibits", "All themes"
|
||||||
|
- Studio 5 — `oneTimePrice` 10000, features: "5 published exhibits", "All themes + custom branding"
|
||||||
|
- Add a shared-features string rendered once beneath the three cards: "Every Studio includes the map builder, timeline, English/French translation, web + touchscreen output, and hosting."
|
||||||
|
|
||||||
|
2. **New `maintenance.json`** — M&S annual tiers: Studio 1 (or a ready-to-go exhibit) 2500, Studio 3 3500, Studio 5 4500. Plus a coverage string ("Covers hosting & uptime (or on-premise server care), software updates & new features, screen monitoring & content sync, staff support by email & phone, security patching & backups.") and the footnote "Screens and on-premise add-ons include their own yearly maintenance, shown under Deployment."
|
||||||
|
|
||||||
|
3. **New `deployment.json`** — Cloud (included), On-Premise Server (+30% one-time, then +30%/yr), Gallery Screen (2500 one-time, then +500/yr), Custom Web Address (250 one-time; subdomain free).
|
||||||
|
|
||||||
|
4. **`aitools.json` → "AI & community tools."** Four items with the fuller descriptions:
|
||||||
|
- Visitor Chatbot — "A conversational guide on your gallery screens and website, answering visitor questions using only your approved exhibit content — so answers always stay accurate and on-brand."
|
||||||
|
- Research Chatbot — "A staff-only research assistant that searches across your full licensed archive in plain English, surfacing sources fast for exhibit development and visitor inquiries."
|
||||||
|
- Document Transcription — "Turns scanned letters, logs and documents into searchable, quotable text — so your archive becomes something visitors and staff can actually find things in."
|
||||||
|
- Community Contributions — "Community photo & story uploads with built-in moderation — nothing publishes without approval. Includes AI pre-screening & audit log."
|
||||||
|
|
||||||
|
5. **New `readymade.json`** — ready-to-go exhibit packages ("a finished, researched guided exhibit with its animated map — Studio included, ready to publish in weeks; growing library"). Entries: Dieppe, 419 Bomber Squadron.
|
||||||
|
|
||||||
|
6. **`historicalCollections.json` → keep per-collection objects** (the catalogue uses them) but they carry names/descriptions/coverage, not public prices. Reframe any pricing-page reference as "we'll recommend the right fit — request the pricing guide," never a printed grid.
|
||||||
|
|
||||||
|
7. **`extras.json`** — keep worked-example *inputs* in JSON for future PDF use, but the worked examples **do not render on the public site**. Remove the hardware-range and installation-timeline references from public pricing templates (they no longer appear under the new model).
|
||||||
|
|
||||||
|
8. **Rework `/pricing/`, `/platform/`, and the home pricing teaser** to the new model with **zero prices**:
|
||||||
|
- Intro line: "Interactive map exhibits for museums and heritage sites — any story that unfolds across a place. You buy the software once, then pay a yearly plan to keep it running."
|
||||||
|
- **Three ways to start** cards: *License a ready-made exhibit* (Fastest) — "A finished exhibit — Dieppe, 419 Bomber Squadron — live in weeks." · *We build it for you* (Most popular) — "Our team researches, writes and builds a guided map-story that carries visitors through your history, chapter by chapter." · *Build it yourself* (Most control) — "Your staff create exhibits in Studio, our builder."
|
||||||
|
- **What a Studio is** (no prices): the builder, running on the Frame 44 Atlas platform; exhibits run on your website and on gallery touchscreens; every Studio includes the map builder, timeline, EN/FR translation, web + touchscreen output, and hosting.
|
||||||
|
- **Custom builds:** "We create guided map-stories for any history rooted in place — a regiment's war, a ship's voyages, a town's growth, an expedition's route. Full archival research, writing, map animation and Studio build, shaped to your collection." (No price.)
|
||||||
|
- **Deployment options** (Cloud / On-Premise / gallery screens / custom web address) described by name only.
|
||||||
|
- **AI & community tools** described by name + description only.
|
||||||
|
- **Historical data:** described, with a link to `/catalogue/`.
|
||||||
|
- **Trust strip:** "Bilingual by design (English/French) · Runs offline on secure networks · Your content stays yours · Grant-ready paperwork included · Built for museums of every kind."
|
||||||
|
- **Dominant CTA, top and bottom of `/pricing/`: "Request the pricing guide"** — a short form (name, institution, email, optional message) that emails info@frame44.com; the reply sends the current pricing guide PDF and books a call. Use the site's existing contact/mailto pattern if no form backend exists (a structured `mailto:` with subject "Pricing guide request" is acceptable until a form backend is added — flag as TODO).
|
||||||
|
- Retire old package names (Exhibit One / Gallery / Flagship / Multi-Site) everywhere they appear.
|
||||||
|
|
||||||
|
9. **Confirmed values:** set `contactEmail` to **info@frame44.com** and `contactEmailConfirmed: true` in `site.json`. Update the `mailto:` on the contact page accordingly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TASK B — New `/catalogue/` page
|
||||||
|
|
||||||
|
A browsable catalogue of licensable data and exhibits at `src/catalogue/index.njk`. **No prices** — every tile ends in an **"Inquire"** CTA (structured `mailto:` to info@frame44.com with a subject naming the item, or a link to the contact page). Data-driven from JSON like the rest of the site.
|
||||||
|
|
||||||
|
Make both `/catalogue/` and `/data/` resolve here (alias or redirect), because the pricing guide PDF links to `frame44.com/data`. Add `/catalogue/` to the main nav.
|
||||||
|
|
||||||
|
Three groups:
|
||||||
|
|
||||||
|
1. **Ready-to-go exhibits** (from `readymade.json`) — image tile each (Dieppe, 419 Bomber Squadron): thumbnail, title, one-line description, "guided map-story" framing, "Inquire" button, plus a "new exhibits added regularly" note.
|
||||||
|
|
||||||
|
2. **Data collections** (from `historicalCollections.json`) — tiles for: Project '44 WWII mapped data (with a coverage tag, e.g. "First Canadian Army · NW Europe & Italy · 1943–45"), War diaries (digitized, searchable primary sources), Story maps (guided narratives), Project Athena (Afghanistan photo archive), Project Friction (Gulf War). Each tile: thumbnail, description, a **coverage/scope tag**, "Inquire" CTA. The coverage tag answers a regimental museum's first question — "do you have my unit?" — at a glance.
|
||||||
|
|
||||||
|
3. **Maps & imagery** (new `cartography.json`) — the differentiator: Historical basemaps (GSGS series, army-group situation maps, period projections), 1944–46 aerial imagery, Historical site layers ("a site mapped through time"). Each tile: thumbnail, description, "Inquire."
|
||||||
|
|
||||||
|
Plus a closing **"Don't see your subject?"** tile → custom research build → contact CTA, so non-military museums (maritime, civic, settlement) see themselves in the catalogue.
|
||||||
|
|
||||||
|
**Images:** screenshots will be supplied at `src/assets/images/catalogue/{dieppe,419sqn,aerial,wardiary,basemap}.jpg`. For any tile whose image isn't present yet, use the existing SVG-placeholder approach and flag it as a TODO. Follow the README's logo/passthrough conventions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Guardrails
|
||||||
|
|
||||||
|
- **No prices in any rendered public page** (the hard rule above). Price fields live in JSON for internal/PDF use only.
|
||||||
|
- **Never describe Frame 44 as owning Project '44's research.** Correct framing: Frame 44 licenses data from Project '44 (CRMA). Footer/data lines read "Data licensed from Project '44 (CRMA)."
|
||||||
|
- **Two product names only: Atlas** (the platform) and **Studio** (the builder). Never "Enhanced Content Builder" or "ECB"; never coin new product names. The kiosk output is "gallery screens"; the web output is "web exhibits."
|
||||||
|
- **Don't invent** prices, coverage claims, collection names, testimonials, or team bios beyond what's in the data files. Anything unknown → `<!-- TODO -->` and list it at the end.
|
||||||
|
- Follow the existing i18n pattern (strings in `src/_data/i18n/en.json`), keep the `/fr/`-ready structure intact.
|
||||||
|
|
||||||
|
## Definition of done
|
||||||
|
|
||||||
|
- `npm run build` succeeds; `npm run serve` renders cleanly, responsive 360–1440px.
|
||||||
|
- **Zero dollar figures anywhere in `_site/` page content** (grep to verify).
|
||||||
|
- `/pricing/` sells the model and drives "Request the pricing guide"; `/catalogue/` (and `/data/`) browse-only with "Inquire" CTAs.
|
||||||
|
- Old package names (Exhibit One / Gallery / Flagship / Multi-Site) appear nowhere in the built site.
|
||||||
|
- `contactEmail` is info@frame44.com and confirmed.
|
||||||
|
- A README/CLAUDE.md note added documenting the new data files (`maintenance.json`, `deployment.json`, `readymade.json`, `cartography.json`) and the no-public-prices rule.
|
||||||
|
- Final message: list every `<!-- TODO -->` placeholder still needing input (screenshots, form backend, fonts, museum copy, team bios, OG image).
|
||||||
115
docs/initial-prompt.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# GOAL
|
||||||
|
|
||||||
|
Build a complete, production-ready marketing website for **Frame 44 Technology Inc.** using **Eleventy (11ty v3)**, to replace the current frame44.com. The current site is too small for what the company now sells; this build needs room for the full product line, pricing, and historical data collections.
|
||||||
|
|
||||||
|
Work iteratively: scaffold the project first, then build page by page, showing me progress as you go. Ask before making assumptions about content I haven't provided.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# CONTEXT — WHO FRAME 44 IS
|
||||||
|
|
||||||
|
Frame 44 Technology is a Canadian software company. We build **map-native interactive exhibit software for museums, historical societies, and heritage sites** — touchscreen kiosks and web exhibits where visitors explore historical events on live maps with timelines, story panels, and imagery.
|
||||||
|
|
||||||
|
Critical positioning rule (never violate this):
|
||||||
|
- **Project '44 is NOT Frame 44.** Project '44 (run by the nonprofit CRMA) is Frame 44's first and biggest customer, and Frame 44 **licenses historical data from** Project '44 and its sister archives. Never write copy implying Frame 44 owns Project '44's research or that the product "ships with" it. Correct framing: "The platform behind Project '44, available for your story."
|
||||||
|
- Reference installations that ARE fair game to showcase: permanent kiosk installations at the **Victory Museum in Grootegast (Netherlands)** and the **Holten Canadian War Cemetery visitor centre (Netherlands)**.
|
||||||
|
|
||||||
|
Audience: museum directors and curators — often non-technical, often at small institutions, often writing grant applications. Plain language everywhere. Explain jargon or don't use it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TECH REQUIREMENTS
|
||||||
|
|
||||||
|
- **Eleventy v3**, Nunjucks templates, no client-side framework. Minimal vanilla JS only where needed (mobile nav, pricing toggle if any).
|
||||||
|
- All pricing/product content lives in **`_data/` JSON files** (e.g. `_data/packages.json`, `_data/collections.json`, `_data/aitools.json`, `_data/extras.json`) so prices update in one place and templates render from data. No prices hardcoded in templates.
|
||||||
|
- Plain modern CSS (custom properties, grid/flex). No Tailwind, no CSS framework.
|
||||||
|
- Fully responsive, mobile-first.
|
||||||
|
- Accessibility: WCAG 2.1 AA. Semantic HTML, skip link, focus states, alt text, color contrast ≥ 4.5:1 for body text.
|
||||||
|
- Performance: no web-font bloat (2 families max, self-hosted woff2), lazy-loaded images, target Lighthouse 95+ across the board.
|
||||||
|
- SEO: per-page titles/descriptions, Open Graph tags, sitemap.xml, robots.txt, JSON-LD Organization schema.
|
||||||
|
- Build output to `_site/`, deployable as pure static files (will likely be hosted on Netlify/Cloudflare Pages — include config for both, ask me which).
|
||||||
|
- Structure the site so an **/fr/ French locale can be added later** without restructuring (i18n-ready folder + data layout, but build English-only for now).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# DESIGN SYSTEM
|
||||||
|
|
||||||
|
- Brand red: **#DF272F**. Dark ink: **#1B1D21**. Body text: #24262B. Muted: #6A6E76. Lines: #E3E5E8. Light bg: #F6F7F8.
|
||||||
|
- Logo: white + red mark (I will drop the file into `assets/` — use a placeholder reference `assets/logo.png` for now). Logo needs a dark background to be visible → dark header band with red underline accent, matching our print materials.
|
||||||
|
- Overall aesthetic: clean, modern, cartographic-adjacent — subtle map/topographic texture is welcome as a background accent, but do NOT theme the whole site like a vintage map. Professional first.
|
||||||
|
- Typography: one strong grotesque for headings (e.g. Schibsted Grotesk), one clean sans for body (e.g. Inter). Self-host both.
|
||||||
|
- Red is reserved for: prices, primary CTAs, and one highlight element per view. Don't spray it everywhere.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# SITE ARCHITECTURE (pages to build)
|
||||||
|
|
||||||
|
1. **Home (`/`)**
|
||||||
|
- Hero: "Map-native interactive exhibits for museums & heritage sites" + subline + primary CTA ("Book a conversation").
|
||||||
|
- Three-step plain-language explainer (reuse this copy):
|
||||||
|
- *An exhibit* is one interactive story — a map your visitors can touch and explore, with timelines, photos and text panels.
|
||||||
|
- *A gallery screen* is the physical touchscreen on your museum floor. The same exhibits also run on your website.
|
||||||
|
- *The builder* is the tool your own staff use to edit text, photos and maps — no technical skills needed.
|
||||||
|
- Reference installations section (Grootegast, Holten) with image placeholders.
|
||||||
|
- Pricing teaser (two-number model) linking to /pricing.
|
||||||
|
- Closing CTA band.
|
||||||
|
|
||||||
|
2. **Platform (`/platform/`)**
|
||||||
|
- The Enhanced Content Builder: block-based builder (headings, fact panels, timeline cards, galleries, map buttons, overlay toggles, scenario buttons), live MapLibre map integration, themes, publish/sync workflow.
|
||||||
|
- Gallery screens / kiosk software: runs offline on secure networks, syncs when connected, monitored and supported.
|
||||||
|
- Web exhibits: same content, on the museum's website.
|
||||||
|
- "Your team can edit everything, forever" as a section, plus "Your content stays yours — exports in standard formats if you ever leave."
|
||||||
|
|
||||||
|
3. **Pricing (`/pricing/`)** — render everything from `_data/`. Reproduce the two-number model prominently:
|
||||||
|
- Explainer: "Every package has two numbers: what it costs to build, and what it costs to keep running. That's it." Build year = one-time, fits project & development funding. Every year after = annual, sized for operating budgets.
|
||||||
|
- **Packages** (build year / every year after):
|
||||||
|
- Exhibit One — $7,500 / $2,900 — 1 interactive map exhibit built with our team; web-based & touchscreen-ready; full builder access; hosting, support & updates.
|
||||||
|
- Gallery — $19,500 / $6,500 — up to 5 published exhibits; first exhibit built by our team; touchscreen software for 1 gallery screen; all themes, 5 staff accounts.
|
||||||
|
- Flagship (badge: "Most installed") — $45,000 / $9,500 — bespoke build by our historian team; unlimited published exhibits; touchscreen software for 2 gallery screens; one new or refreshed exhibit every year; custom branding & priority support.
|
||||||
|
- Multi-Site — from $75,000 / custom — multiple locations, synced content; shared collections across sites; dedicated account historian; scoped & quoted per institution.
|
||||||
|
- Worked example: Flagship $45,000 + Campaign data licence $3,900 + Visitor chatbot $4,800 = **$53,700 build year · $18,200 every year after**, vs. comparable one-off custom installs typically starting near $67,000 with no updates, no builder, nothing new next year. Most installations live **8–16 weeks after signing** (TODO: confirm timeline).
|
||||||
|
- **Historical Collections** (licensed from Project '44 and its sister archives, CRMA — scoped to the story your museum tells):
|
||||||
|
- Story Packs (Dieppe, 419 Squadron & growing) — $1,900/yr each — pre-built, ready-to-publish exhibits from completed research projects.
|
||||||
|
- Gulf War Collection (Project Friction) — $2,500/yr — complete and museum-ready.
|
||||||
|
- Afghanistan Collection (Project Athena) — $2,900/yr — veteran-contributed, geotagged photos of your regiment's tours; pairs with Crowdsourcing.
|
||||||
|
- Second World War — Campaign/Regional (Project '44) — $3,900/yr — mapped unit positions, frontlines and war diary events for one campaign, operation or regiment.
|
||||||
|
- Second World War — Full Collection (Project '44) — $8,500/yr — includes the staff Research Assistant: plain-English questions, sourced answers from the full archive.
|
||||||
|
- Complete Archive — $12,500/yr — every collection, Research Assistant across all of it. Highlighted; "best value for Flagship & Multi-Site."
|
||||||
|
- Custom Research Build — from $12,000 **one-time · per project**.
|
||||||
|
- **AI Tools** ("Optional. Add to any package — you control what they can say."):
|
||||||
|
- Visitor Chatbot — $4,800/yr — answers visitor questions using only approved exhibit content and licensed data.
|
||||||
|
- Document Transcription — $3,000/yr — scanned archival documents into searchable text; 5,000 pages/yr included.
|
||||||
|
- Crowdsourcing — $2,400/yr — moderated community photo & story contributions.
|
||||||
|
- Complete AI Package — $8,000/yr (save $2,200/yr).
|
||||||
|
- **Extras**: Additional gallery screen $2,400/yr · Additional published exhibit $1,000/yr · Curator Care $6,000/yr (we maintain, refresh and update all content) · Done-For-You Exhibit $5,000–15,000 **one-time · per exhibit**.
|
||||||
|
- **Terms**: 3 years prepaid = 2.5× annual, 5 years prepaid = 4× annual, applied to "every year after" fees (worked example: Flagship 3-yr prepaid = $23,750 vs $28,500 annually). Hardware: touchscreens typically $3,500–$9,000 per screen installed (TODO: confirm range), supplied at cost + 15% or bring your own. Grant support: we provide statements of work, budgets and letters of support formatted for museum development and heritage funding applications.
|
||||||
|
- Reassurance band: ✓ Bilingual by design (full EN/FR visitor experience on every package) · ✓ Runs on secure networks (screens run offline, sync when connected) · ✓ Your content stays yours (exports in standard formats).
|
||||||
|
- All prices CAD, exclusive of taxes, hardware quoted separately.
|
||||||
|
|
||||||
|
4. **Collections (`/collections/`)** — expanded page for the historical collections above: one section per collection with a short description, what's inside, and which museums it suits. Link back to pricing.
|
||||||
|
|
||||||
|
5. **Museums (`/museums/`)** — case-study page: Grootegast and Holten installations with image placeholders and short write-ups (I'll supply final text and photos; scaffold with clearly marked placeholder copy).
|
||||||
|
|
||||||
|
6. **About (`/about/`)** — company story: Canadian company, veteran-founded, the relationship with Project '44/CRMA stated correctly, the team (placeholder bios).
|
||||||
|
|
||||||
|
7. **Contact (`/contact/`)** — "Every installation starts with a conversation. We'll scope your exhibit, prepare a grant-ready proposal, and price it to your funding — no obligation." Simple contact form (Netlify Forms or static mailto — ask me) + email. **Email is TODO — use `contact@frame44.example` placeholder and flag it.**
|
||||||
|
|
||||||
|
Global: header (logo, nav, CTA button), footer (Project '44 licensing attribution line: "Historical data licensed from Project '44 (CRMA)", copyright, prices-CAD note).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# HARD RULES
|
||||||
|
|
||||||
|
1. Never invent prices, statistics, testimonials, client names, or team bios. Anything you don't have: insert `<!-- TODO -->` placeholder and list it in a final TODO summary.
|
||||||
|
2. Never describe Frame 44 as owning Project '44's research (see positioning rule above).
|
||||||
|
3. All money renders from `_data/` files. Changing one JSON value must update every page that shows it.
|
||||||
|
4. Mark the three unconfirmed values wherever they appear: contact email, hardware price range, 8–16 week timeline.
|
||||||
|
5. Keep every page's copy in plain language a non-technical museum director understands on first read.
|
||||||
|
|
||||||
|
# DEFINITION OF DONE
|
||||||
|
|
||||||
|
- `npx @11ty/eleventy --serve` runs clean; all 7 pages render, responsive from 360px to 1440px.
|
||||||
|
- Pricing page numbers match this document exactly.
|
||||||
|
- Lighthouse: Performance/Accessibility/Best Practices/SEO ≥ 95 on Home and Pricing.
|
||||||
|
- A README covering: build/deploy, where to edit prices (`_data/`), how to add a collection or package, and the planned /fr/ locale approach.
|
||||||
|
- Final message: list of every TODO placeholder needing my input.
|
||||||
51
eleventy.config.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
const { EleventyHtmlBasePlugin } = require("@11ty/eleventy");
|
||||||
|
|
||||||
|
module.exports = function (eleventyConfig) {
|
||||||
|
// Static passthrough — note src/assets/logos/ (raw design source files) is
|
||||||
|
// intentionally excluded; only the cropped, web-sized exports in
|
||||||
|
// src/assets/images/ ship to the built site.
|
||||||
|
eleventyConfig.addPassthroughCopy({ "src/assets/css": "assets/css" });
|
||||||
|
eleventyConfig.addPassthroughCopy({ "src/assets/js": "assets/js" });
|
||||||
|
eleventyConfig.addPassthroughCopy({ "src/assets/images": "assets/images" });
|
||||||
|
eleventyConfig.addPassthroughCopy({ "src/assets/fonts": "assets/fonts" });
|
||||||
|
eleventyConfig.addPassthroughCopy({ "src/robots.txt": "robots.txt" });
|
||||||
|
eleventyConfig.addPassthroughCopy({ "src/favicon.ico": "favicon.ico" });
|
||||||
|
|
||||||
|
eleventyConfig.addPlugin(EleventyHtmlBasePlugin);
|
||||||
|
|
||||||
|
eleventyConfig.addWatchTarget("src/assets/css/");
|
||||||
|
|
||||||
|
// Currency formatter — all prices are CAD, whole dollars.
|
||||||
|
eleventyConfig.addFilter("cad", (value) => {
|
||||||
|
if (value === null || value === undefined || value === "") return "";
|
||||||
|
return new Intl.NumberFormat("en-CA", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "CAD",
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
}).format(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prepay pricing: 3yr = 2.5x annual, 5yr = 4x annual (applied to "every year after" fees).
|
||||||
|
eleventyConfig.addFilter("prepay", (annual, years) => {
|
||||||
|
const multiplier = years === 5 ? 4 : years === 3 ? 2.5 : 1;
|
||||||
|
return Math.round(annual * multiplier);
|
||||||
|
});
|
||||||
|
|
||||||
|
eleventyConfig.addFilter("sum", (values) => values.reduce((a, b) => a + b, 0));
|
||||||
|
|
||||||
|
eleventyConfig.addFilter("findByKey", (arr, value) => (arr || []).find((item) => item.key === value));
|
||||||
|
|
||||||
|
eleventyConfig.addShortcode("year", () => `${new Date().getFullYear()}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
dir: {
|
||||||
|
input: "src",
|
||||||
|
output: "_site",
|
||||||
|
includes: "_includes",
|
||||||
|
data: "_data",
|
||||||
|
},
|
||||||
|
templateFormats: ["njk", "md", "11ty.js"],
|
||||||
|
htmlTemplateEngine: "njk",
|
||||||
|
markdownTemplateEngine: "njk",
|
||||||
|
};
|
||||||
|
};
|
||||||
1687
package-lock.json
generated
Normal file
24
package.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "frame44-landing-page",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"directories": {
|
||||||
|
"doc": "docs"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "eleventy",
|
||||||
|
"serve": "eleventy --serve",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.frame44.com/nathan/frame44-landing-page.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"devDependencies": {
|
||||||
|
"@11ty/eleventy": "^3.1.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/_data/aitools.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"intro": "Optional. Add to any package — you control what they can say.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"key": "visitor-chatbot",
|
||||||
|
"name": "Visitor Chatbot",
|
||||||
|
"price": 4800,
|
||||||
|
"priceType": "annual",
|
||||||
|
"description": "Answers visitor questions using only approved exhibit content and licensed data."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "document-transcription",
|
||||||
|
"name": "Document Transcription",
|
||||||
|
"price": 3000,
|
||||||
|
"priceType": "annual",
|
||||||
|
"description": "Scanned archival documents into searchable text; 5,000 pages/yr included."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "crowdsourcing",
|
||||||
|
"name": "Crowdsourcing",
|
||||||
|
"price": 2400,
|
||||||
|
"priceType": "annual",
|
||||||
|
"description": "Moderated community photo & story contributions."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bundle": {
|
||||||
|
"key": "complete-ai-package",
|
||||||
|
"name": "Complete AI Package",
|
||||||
|
"price": 8000,
|
||||||
|
"priceType": "annual",
|
||||||
|
"savings": 2200,
|
||||||
|
"description": "All three AI tools together, for less than buying them separately."
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/_data/extras.json
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"key": "additional-gallery-screen",
|
||||||
|
"name": "Additional gallery screen",
|
||||||
|
"price": 2400,
|
||||||
|
"priceType": "annual"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "additional-exhibit",
|
||||||
|
"name": "Additional published exhibit",
|
||||||
|
"price": 1000,
|
||||||
|
"priceType": "annual",
|
||||||
|
"description": "Built by your team in Studio."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "curator-care",
|
||||||
|
"name": "Curator Care",
|
||||||
|
"price": 6000,
|
||||||
|
"priceType": "annual",
|
||||||
|
"description": "We maintain, refresh and update all content."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "done-for-you-exhibit",
|
||||||
|
"name": "Done-For-You Exhibit",
|
||||||
|
"priceMin": 5000,
|
||||||
|
"priceMax": 15000,
|
||||||
|
"priceType": "one-time-per-exhibit"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terms": {
|
||||||
|
"prepay": [
|
||||||
|
{ "years": 3, "multiplier": 2.5 },
|
||||||
|
{ "years": 5, "multiplier": 4 }
|
||||||
|
],
|
||||||
|
"appliesTo": "Every year after fees only."
|
||||||
|
},
|
||||||
|
"hardware": {
|
||||||
|
"priceMin": 3500,
|
||||||
|
"priceMax": 9000,
|
||||||
|
"unit": "per screen installed",
|
||||||
|
"note": "Supplied at cost + 15%, or bring your own.",
|
||||||
|
"confirmed": false
|
||||||
|
},
|
||||||
|
"grantSupport": "We provide statements of work, budgets and letters of support formatted for museum development and heritage funding applications.",
|
||||||
|
"timeline": {
|
||||||
|
"weeksMin": 8,
|
||||||
|
"weeksMax": 16,
|
||||||
|
"note": "Most installations live 8–16 weeks after signing.",
|
||||||
|
"confirmed": false
|
||||||
|
},
|
||||||
|
"workedExample": {
|
||||||
|
"packageKey": "flagship",
|
||||||
|
"collectionKey": "second-world-war-campaign",
|
||||||
|
"aitoolKey": "visitor-chatbot",
|
||||||
|
"comparisonOneOff": 67000
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/_data/historicalCollections.json
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "story-packs",
|
||||||
|
"name": "Story Packs",
|
||||||
|
"subtitle": "Dieppe, 419 Squadron & growing",
|
||||||
|
"price": 1900,
|
||||||
|
"priceType": "annual",
|
||||||
|
"description": "Pre-built, ready-to-publish exhibits from completed research projects.",
|
||||||
|
"whatsInside": "Finished, ready-to-publish exhibits drawn from completed research projects — no commissioning or writing required on your end.",
|
||||||
|
"suits": "Museums that want a proven exhibit fast, without commissioning original research."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "gulf-war",
|
||||||
|
"name": "Gulf War Collection",
|
||||||
|
"subtitle": "Project Friction",
|
||||||
|
"price": 2500,
|
||||||
|
"priceType": "annual",
|
||||||
|
"description": "Complete and museum-ready.",
|
||||||
|
"whatsInside": "A complete, museum-ready exhibit covering the Gulf War, sourced and mapped by our research partners.",
|
||||||
|
"suits": "Regimental museums and heritage sites with a Gulf War service connection."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "afghanistan",
|
||||||
|
"name": "Afghanistan Collection",
|
||||||
|
"subtitle": "Project Athena",
|
||||||
|
"price": 2900,
|
||||||
|
"priceType": "annual",
|
||||||
|
"description": "Veteran-contributed, geotagged photos of your regiment's tours.",
|
||||||
|
"whatsInside": "Veteran-contributed, geotagged photographs from your regiment's tours in Afghanistan. Pairs well with the Crowdsourcing AI tool for ongoing contributions.",
|
||||||
|
"suits": "Regimental museums building an Afghanistan-era exhibit with community contributions."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "second-world-war-campaign",
|
||||||
|
"name": "Second World War — Campaign / Regional",
|
||||||
|
"subtitle": "Project '44",
|
||||||
|
"price": 3900,
|
||||||
|
"priceType": "annual",
|
||||||
|
"description": "Mapped unit positions, frontlines and war diary events for one campaign, operation or regiment.",
|
||||||
|
"whatsInside": "Mapped unit positions, frontlines and war diary events for one campaign, operation or regiment, drawn from Project '44's research.",
|
||||||
|
"suits": "Museums telling one specific Second World War campaign, operation or regimental story."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "second-world-war-full",
|
||||||
|
"name": "Second World War — Full Collection",
|
||||||
|
"subtitle": "Project '44",
|
||||||
|
"price": 8500,
|
||||||
|
"priceType": "annual",
|
||||||
|
"description": "Includes the staff Research Assistant: plain-English questions, sourced answers from the full archive.",
|
||||||
|
"whatsInside": "The complete Second World War archive, plus the staff Research Assistant — plain-English questions, sourced answers from the full archive.",
|
||||||
|
"suits": "Museums covering the full Second World War story, or that want research help on tap for staff."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "complete-archive",
|
||||||
|
"name": "Complete Archive",
|
||||||
|
"subtitle": null,
|
||||||
|
"price": 12500,
|
||||||
|
"priceType": "annual",
|
||||||
|
"highlight": "Best value for Flagship & Multi-Site",
|
||||||
|
"description": "Every collection, Research Assistant across all of it.",
|
||||||
|
"whatsInside": "Every historical collection Frame 44 licenses, with the Research Assistant working across the full archive.",
|
||||||
|
"suits": "Flagship and Multi-Site museums that want the whole archive available to draw from."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "custom-research-build",
|
||||||
|
"name": "Custom Research Build",
|
||||||
|
"subtitle": null,
|
||||||
|
"price": 12000,
|
||||||
|
"priceType": "one-time-per-project",
|
||||||
|
"priceFrom": true,
|
||||||
|
"description": "Commissioned research scoped to the story your museum tells.",
|
||||||
|
"whatsInside": "A commissioned research project scoped to the specific story your museum tells, built by our historian team.",
|
||||||
|
"suits": "Museums with a story not yet covered by an existing collection."
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/_data/i18n.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const en = require("./i18n/en.json");
|
||||||
|
|
||||||
|
// English-only today. Adding /fr/: create ./i18n/fr.json, then switch this
|
||||||
|
// to `(data) => ({ en, fr })[data.lang || "en"]` and set `lang: fr` in front
|
||||||
|
// matter on the src/fr/ page tree.
|
||||||
|
module.exports = function () {
|
||||||
|
return en;
|
||||||
|
};
|
||||||
38
src/_data/i18n/en.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"skipLink": "Skip to main content",
|
||||||
|
"nav": {
|
||||||
|
"platform": "Platform",
|
||||||
|
"pricing": "Pricing",
|
||||||
|
"collections": "Collections",
|
||||||
|
"museums": "Museums",
|
||||||
|
"about": "About",
|
||||||
|
"contact": "Contact"
|
||||||
|
},
|
||||||
|
"cta": {
|
||||||
|
"bookConversation": "Book a conversation",
|
||||||
|
"seePricing": "See pricing",
|
||||||
|
"explorePlatform": "Explore the platform",
|
||||||
|
"emailUs": "Email us",
|
||||||
|
"viewCollections": "View all collections",
|
||||||
|
"readMore": "Read more"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"attribution": "Historical data licensed from Project '44 (CRMA).",
|
||||||
|
"pricesNote": "All prices in Canadian dollars (CAD), exclusive of taxes.",
|
||||||
|
"copyrightSuffix": "Frame 44 Technology Inc. All rights reserved."
|
||||||
|
},
|
||||||
|
"pricing": {
|
||||||
|
"eyebrow": "How pricing works",
|
||||||
|
"twoNumberExplainer": "Every package has two numbers: what it costs to build, and what it costs to keep running. That's it.",
|
||||||
|
"buildYearLabel": "Build year",
|
||||||
|
"buildYearNote": "One-time. Fits project & development funding.",
|
||||||
|
"everyYearLabel": "Every year after",
|
||||||
|
"everyYearNote": "Annual. Sized for operating budgets.",
|
||||||
|
"currencyNote": "All prices CAD, exclusive of taxes. Hardware quoted separately.",
|
||||||
|
"reassurance": [
|
||||||
|
"Bilingual by design (full EN/FR visitor experience on every package)",
|
||||||
|
"Runs on secure networks (screens run offline, sync when connected)",
|
||||||
|
"Your content stays yours (exports in standard formats)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/_data/museums.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "grootegast",
|
||||||
|
"name": "Victory Museum",
|
||||||
|
"location": "Grootegast, Netherlands",
|
||||||
|
"type": "Permanent kiosk installation",
|
||||||
|
"summary": "<!-- TODO: confirm case-study summary copy for Victory Museum, Grootegast -->",
|
||||||
|
"image": "/assets/images/placeholders/grootegast.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "holten",
|
||||||
|
"name": "Holten Canadian War Cemetery Visitor Centre",
|
||||||
|
"location": "Holten, Netherlands",
|
||||||
|
"type": "Permanent kiosk installation",
|
||||||
|
"summary": "<!-- TODO: confirm case-study summary copy for Holten Canadian War Cemetery Visitor Centre -->",
|
||||||
|
"image": "/assets/images/placeholders/holten.svg"
|
||||||
|
}
|
||||||
|
]
|
||||||
62
src/_data/packages.json
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "exhibit-one",
|
||||||
|
"name": "Exhibit One",
|
||||||
|
"badge": null,
|
||||||
|
"buildPrice": 7500,
|
||||||
|
"annualPrice": 2900,
|
||||||
|
"summary": "One interactive map exhibit, built with our team.",
|
||||||
|
"features": [
|
||||||
|
"1 interactive map exhibit built with our team",
|
||||||
|
"Web-based & touchscreen-ready",
|
||||||
|
"Full Frame 44 Studio access",
|
||||||
|
"Hosting, support & updates"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "gallery",
|
||||||
|
"name": "Gallery",
|
||||||
|
"badge": null,
|
||||||
|
"buildPrice": 19500,
|
||||||
|
"annualPrice": 6500,
|
||||||
|
"summary": "A full gallery screen with room to grow to five exhibits.",
|
||||||
|
"features": [
|
||||||
|
"Up to 5 published exhibits",
|
||||||
|
"First exhibit built by our team",
|
||||||
|
"Touchscreen software for 1 gallery screen",
|
||||||
|
"All themes",
|
||||||
|
"5 staff accounts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "flagship",
|
||||||
|
"name": "Flagship",
|
||||||
|
"badge": "Most installed",
|
||||||
|
"buildPrice": 45000,
|
||||||
|
"annualPrice": 9500,
|
||||||
|
"summary": "A bespoke build from our historian team, with something new every year.",
|
||||||
|
"features": [
|
||||||
|
"Bespoke build by our historian team",
|
||||||
|
"Unlimited published exhibits",
|
||||||
|
"Touchscreen software for 2 gallery screens",
|
||||||
|
"One new or refreshed exhibit every year",
|
||||||
|
"Custom branding & priority support"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "multi-site",
|
||||||
|
"name": "Multi-Site",
|
||||||
|
"badge": null,
|
||||||
|
"buildPrice": 75000,
|
||||||
|
"buildPriceFrom": true,
|
||||||
|
"annualPrice": null,
|
||||||
|
"annualPriceLabel": "Custom",
|
||||||
|
"summary": "Multiple locations on one synced platform, scoped to your institution.",
|
||||||
|
"features": [
|
||||||
|
"Multiple locations, synced content",
|
||||||
|
"Shared collections across sites",
|
||||||
|
"Dedicated account historian",
|
||||||
|
"Scoped & quoted per institution"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
24
src/_data/site.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "Frame 44 Technology Inc.",
|
||||||
|
"shortName": "Frame 44",
|
||||||
|
"url": "https://www.frame44.com",
|
||||||
|
"lang": "en",
|
||||||
|
"locales": ["en"],
|
||||||
|
"description": "Map-native interactive exhibit software for museums, historical societies and heritage sites.",
|
||||||
|
"contactEmail": "contact@frame44.example",
|
||||||
|
"contactEmailConfirmed": false,
|
||||||
|
"logo": "/assets/images/logo-footer.png",
|
||||||
|
"logoWidth": 457,
|
||||||
|
"logoHeight": 160,
|
||||||
|
"logoHeader": "/assets/images/logo-header.png",
|
||||||
|
"logoFooter": "/assets/images/logo-footer.png",
|
||||||
|
"ctaUrl": "/contact/",
|
||||||
|
"nav": [
|
||||||
|
{ "key": "platform", "url": "/platform/" },
|
||||||
|
{ "key": "pricing", "url": "/pricing/" },
|
||||||
|
{ "key": "collections", "url": "/collections/" },
|
||||||
|
{ "key": "museums", "url": "/museums/" },
|
||||||
|
{ "key": "about", "url": "/about/" },
|
||||||
|
{ "key": "contact", "url": "/contact/" }
|
||||||
|
]
|
||||||
|
}
|
||||||
23
src/_data/team.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "placeholder-1",
|
||||||
|
"name": "<!-- TODO: name -->",
|
||||||
|
"role": "<!-- TODO: role -->",
|
||||||
|
"bio": "<!-- TODO: bio -->",
|
||||||
|
"photo": "/assets/images/placeholders/team-1.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "placeholder-2",
|
||||||
|
"name": "<!-- TODO: name -->",
|
||||||
|
"role": "<!-- TODO: role -->",
|
||||||
|
"bio": "<!-- TODO: bio -->",
|
||||||
|
"photo": "/assets/images/placeholders/team-2.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "placeholder-3",
|
||||||
|
"name": "<!-- TODO: name -->",
|
||||||
|
"role": "<!-- TODO: role -->",
|
||||||
|
"bio": "<!-- TODO: bio -->",
|
||||||
|
"photo": "/assets/images/placeholders/team-3.svg"
|
||||||
|
}
|
||||||
|
]
|
||||||
43
src/_includes/layouts/base.njk
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="{{ site.lang }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{{ title }} | {{ site.shortName }}</title>
|
||||||
|
<meta name="description" content="{{ description }}">
|
||||||
|
<link rel="canonical" href="{{ site.url }}{{ page.url }}">
|
||||||
|
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:site_name" content="{{ site.shortName }}">
|
||||||
|
<meta property="og:title" content="{{ title }}">
|
||||||
|
<meta property="og:description" content="{{ description }}">
|
||||||
|
<meta property="og:url" content="{{ site.url }}{{ page.url }}">
|
||||||
|
<meta property="og:locale" content="en_CA">
|
||||||
|
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="any">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||||
|
<link rel="apple-touch-icon" href="/assets/images/apple-touch-icon.png">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/assets/css/style.css">
|
||||||
|
|
||||||
|
{% set orgSchema = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": site.name,
|
||||||
|
"url": site.url,
|
||||||
|
"logo": site.url + site.logo,
|
||||||
|
"description": site.description
|
||||||
|
} %}
|
||||||
|
<script type="application/ld+json">{{ orgSchema | dump | safe }}</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a class="skip-link" href="#main">{{ i18n.skipLink }}</a>
|
||||||
|
{% include "partials/header.njk" %}
|
||||||
|
<main id="main">
|
||||||
|
{{ content | safe }}
|
||||||
|
</main>
|
||||||
|
{% include "partials/footer.njk" %}
|
||||||
|
<script src="/assets/js/main.js" defer></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
50
src/_includes/macros.njk
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{% macro ctaBand(heading, body, ctaLabel, ctaUrl) %}
|
||||||
|
<section class="section cta-band">
|
||||||
|
<div class="container">
|
||||||
|
<h2>{{ heading }}</h2>
|
||||||
|
<p>{{ body }}</p>
|
||||||
|
<a class="button button--primary" href="{{ ctaUrl }}">{{ ctaLabel }}</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro pricingCard(pkg) %}
|
||||||
|
<article class="card pricing-card{% if pkg.badge %} pricing-card--featured{% endif %}">
|
||||||
|
{% if pkg.badge %}<span class="pricing-card__badge">{{ pkg.badge }}</span>{% endif %}
|
||||||
|
<h3>{{ pkg.name }}</h3>
|
||||||
|
<p>{{ pkg.summary }}</p>
|
||||||
|
<div class="pricing-card__prices">
|
||||||
|
<div>
|
||||||
|
<span class="price-figure">{% if pkg.buildPriceFrom %}from {% endif %}{{ pkg.buildPrice | cad }}</span>
|
||||||
|
<span class="price-figure__label">Build year</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="price-figure">{% if pkg.annualPrice %}{{ pkg.annualPrice | cad }}{% else %}{{ pkg.annualPriceLabel }}{% endif %}</span>
|
||||||
|
<span class="price-figure__label">Every year after</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="pricing-card__features">
|
||||||
|
{% for feature in pkg.features %}
|
||||||
|
<li>{{ feature }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro collectionCard(item) %}
|
||||||
|
<article class="card collection-card">
|
||||||
|
<div class="collection-card__meta">
|
||||||
|
<h3>{{ item.name }}{% if item.subtitle %} <span class="tag">{{ item.subtitle }}</span>{% endif %}</h3>
|
||||||
|
</div>
|
||||||
|
{% if item.highlight %}<p class="tag">{{ item.highlight }}</p>{% endif %}
|
||||||
|
<p>{{ item.description }}</p>
|
||||||
|
<p><strong>What's inside:</strong> {{ item.whatsInside }}</p>
|
||||||
|
<p><strong>Suits:</strong> {{ item.suits }}</p>
|
||||||
|
<p class="price-figure">
|
||||||
|
{% if item.priceFrom %}from {% endif %}{{ item.price | cad }}
|
||||||
|
<span class="price-figure__label">
|
||||||
|
{% if item.priceType == "annual" %}per year{% else %}one-time, per project{% endif %}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
{% endmacro %}
|
||||||
15
src/_includes/partials/footer.njk
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<footer class="site-footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="site-footer__row">
|
||||||
|
<img class="site-footer__logo" src="{{ site.logoFooter }}" alt="{{ site.name }}" width="{{ site.logoWidth }}" height="{{ site.logoHeight }}">
|
||||||
|
<p>© {% year %} {{ i18n.footer.copyrightSuffix }}</p>
|
||||||
|
</div>
|
||||||
|
<p>{{ i18n.footer.attribution }}</p>
|
||||||
|
<p>{{ i18n.footer.pricesNote }}</p>
|
||||||
|
<ul class="site-footer__nav">
|
||||||
|
{% for item in site.nav %}
|
||||||
|
<li><a href="{{ item.url }}">{{ i18n.nav[item.key] }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
19
src/_includes/partials/header.njk
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<header class="site-header">
|
||||||
|
<div class="container site-header__bar">
|
||||||
|
<a class="site-header__logo" href="/" aria-label="{{ site.name }} — home">
|
||||||
|
<img src="{{ site.logoHeader }}" alt="{{ site.name }}" width="{{ site.logoWidth }}" height="{{ site.logoHeight }}">
|
||||||
|
</a>
|
||||||
|
<nav class="site-nav" aria-label="Primary">
|
||||||
|
<ul class="site-nav__list" id="primary-nav-list">
|
||||||
|
{% for item in site.nav %}
|
||||||
|
<li><a href="{{ item.url }}"{% if page.url == item.url %} aria-current="page"{% endif %}>{{ i18n.nav[item.key] }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<a class="button button--primary header-cta" href="{{ site.ctaUrl }}">{{ i18n.cta.bookConversation }}</a>
|
||||||
|
<button class="site-nav__toggle" type="button" aria-expanded="false" aria-controls="primary-nav-list">
|
||||||
|
<span class="visually-hidden">Menu</span>
|
||||||
|
<span aria-hidden="true">☰</span>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
40
src/about/index.njk
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
layout: layouts/base.njk
|
||||||
|
title: About
|
||||||
|
description: Frame 44 is a Canadian, veteran-founded software company building map-native interactive exhibits for museums and heritage sites.
|
||||||
|
permalink: /about/
|
||||||
|
---
|
||||||
|
{% from "macros.njk" import ctaBand %}
|
||||||
|
|
||||||
|
<section class="hero">
|
||||||
|
<div class="container hero__inner">
|
||||||
|
<p class="hero__eyebrow">About</p>
|
||||||
|
<h1>A Canadian company, built by people who've served</h1>
|
||||||
|
<p class="hero__sub">Frame 44 Technology is a Canadian software company. We build map-native interactive exhibit software for museums, historical societies and heritage sites — veteran-founded, and built to make real, sourced history easy for visitors to explore.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Our relationship with Project '44</h2>
|
||||||
|
<p>Project '44, run by the nonprofit CRMA, is Frame 44's first and biggest customer. Frame 44 licenses historical data from Project '44 and its sister archives — we don't own that research. Think of it this way: the platform behind Project '44, available for your story.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section section--muted">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Our team</h2>
|
||||||
|
<div class="grid grid--3">
|
||||||
|
{% for person in team %}
|
||||||
|
<article class="card team-card">
|
||||||
|
<img src="{{ person.photo }}" alt="Team headshot placeholder" width="400" height="400" loading="lazy">
|
||||||
|
<h3>{{ person.name | safe }}</h3>
|
||||||
|
<p class="tag">{{ person.role | safe }}</p>
|
||||||
|
<p>{{ person.bio | safe }}</p>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{ ctaBand("Let's talk about your museum.", "Every installation starts with a conversation.", i18n.cta.bookConversation, site.ctaUrl) }}
|
||||||
665
src/assets/css/style.css
Normal file
@@ -0,0 +1,665 @@
|
|||||||
|
/* ---- Fonts ---- */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Schibsted Grotesk";
|
||||||
|
src: url("/assets/fonts/schibsted-grotesk/schibsted-grotesk-600.woff2") format("woff2");
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Schibsted Grotesk";
|
||||||
|
src: url("/assets/fonts/schibsted-grotesk/schibsted-grotesk-700.woff2") format("woff2");
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Inter";
|
||||||
|
src: url("/assets/fonts/inter/inter-400.woff2") format("woff2");
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Inter";
|
||||||
|
src: url("/assets/fonts/inter/inter-600.woff2") format("woff2");
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Tokens ---- */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-red: #df272f;
|
||||||
|
--color-ink: #1b1d21;
|
||||||
|
--color-body: #24262b;
|
||||||
|
--color-muted: #6a6e76;
|
||||||
|
--color-line: #e3e5e8;
|
||||||
|
--color-bg-light: #f6f7f8;
|
||||||
|
--color-bg: #ffffff;
|
||||||
|
|
||||||
|
--font-heading: "Schibsted Grotesk", ui-sans-serif, system-ui, "Segoe UI", Arial, sans-serif;
|
||||||
|
--font-body: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
|
||||||
|
|
||||||
|
--space-1: 0.25rem;
|
||||||
|
--space-2: 0.5rem;
|
||||||
|
--space-3: 1rem;
|
||||||
|
--space-4: 1.5rem;
|
||||||
|
--space-5: 2rem;
|
||||||
|
--space-6: 3rem;
|
||||||
|
--space-7: 4rem;
|
||||||
|
--space-8: 6rem;
|
||||||
|
|
||||||
|
--radius: 10px;
|
||||||
|
--radius-lg: 16px;
|
||||||
|
--container-width: 1200px;
|
||||||
|
--shadow-card: 0 1px 2px rgba(27, 29, 33, 0.06), 0 8px 24px rgba(27, 29, 33, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Reset ---- */
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
color: var(--color-body);
|
||||||
|
background: var(--color-bg);
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
display: block;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
color: var(--color-ink);
|
||||||
|
line-height: 1.15;
|
||||||
|
margin: 0 0 var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(2.25rem, 1.7rem + 2.2vw, 3.5rem);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: clamp(1.6rem, 1.35rem + 1vw, 2.25rem);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: clamp(1.2rem, 1.1rem + 0.4vw, 1.4rem);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
padding-left: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Accessibility ---- */
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
outline: 3px solid var(--color-ink);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skip-link {
|
||||||
|
position: absolute;
|
||||||
|
top: -100%;
|
||||||
|
left: var(--space-3);
|
||||||
|
background: var(--color-ink);
|
||||||
|
color: #fff;
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
z-index: 100;
|
||||||
|
border-radius: 0 0 var(--radius) var(--radius);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skip-link:focus {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visually-hidden {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Layout ---- */
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--container-width);
|
||||||
|
margin-inline: auto;
|
||||||
|
padding-inline: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
padding-block: var(--space-7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section--tight {
|
||||||
|
padding-block: var(--space-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section--muted {
|
||||||
|
background: var(--color-bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.grid--2 {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 900px) {
|
||||||
|
.grid--3 {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
.grid--4 {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Site header ---- */
|
||||||
|
|
||||||
|
.site-header {
|
||||||
|
background: var(--color-ink);
|
||||||
|
border-bottom: 3px solid var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header__bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-4);
|
||||||
|
padding-block: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header__logo img {
|
||||||
|
height: 28px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-nav__list {
|
||||||
|
display: none;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
gap: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-nav__list a {
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-nav__list a:hover,
|
||||||
|
.site-nav__list a:focus-visible {
|
||||||
|
color: var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-nav__toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
background: none;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 900px) {
|
||||||
|
.site-nav__list {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.site-nav__toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-nav__list--open {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
inset-inline: 0;
|
||||||
|
top: 100%;
|
||||||
|
background: var(--color-ink);
|
||||||
|
padding: var(--space-4);
|
||||||
|
gap: var(--space-3);
|
||||||
|
border-bottom: 3px solid var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cta {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Buttons ---- */
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.75rem 1.375rem;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--primary {
|
||||||
|
background: var(--color-red);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--primary:hover,
|
||||||
|
.button--primary:focus-visible {
|
||||||
|
background: #c11f26;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--ghost {
|
||||||
|
background: transparent;
|
||||||
|
border-color: currentColor;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--ghost:hover,
|
||||||
|
.button--ghost:focus-visible {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--outline {
|
||||||
|
background: transparent;
|
||||||
|
border-color: var(--color-ink);
|
||||||
|
color: var(--color-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--outline:hover,
|
||||||
|
.button--outline:focus-visible {
|
||||||
|
background: var(--color-ink);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Hero ---- */
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
background: var(--color-bg-light);
|
||||||
|
background-image: radial-gradient(circle at 15% 20%, rgba(223, 39, 47, 0.06), transparent 40%),
|
||||||
|
repeating-linear-gradient(120deg, rgba(27, 29, 33, 0.035) 0 1px, transparent 1px 90px);
|
||||||
|
padding-block: var(--space-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero__inner {
|
||||||
|
max-width: 760px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero__eyebrow {
|
||||||
|
color: var(--color-red);
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero__sub {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
color: var(--color-muted);
|
||||||
|
max-width: 60ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero__actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-3);
|
||||||
|
margin-top: var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Cards ---- */
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--color-bg);
|
||||||
|
border: 1px solid var(--color-line);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: var(--space-5);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h3 {
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Steps ---- */
|
||||||
|
|
||||||
|
.steps {
|
||||||
|
counter-reset: step;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step::before {
|
||||||
|
counter-increment: step;
|
||||||
|
content: counter(step);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 2.25rem;
|
||||||
|
height: 2.25rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--color-ink);
|
||||||
|
color: #fff;
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-weight: 700;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Pricing ---- */
|
||||||
|
|
||||||
|
.price-figure {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-red);
|
||||||
|
font-size: 2rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-figure__label {
|
||||||
|
display: block;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-muted);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin-top: var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card--featured {
|
||||||
|
border-color: var(--color-red);
|
||||||
|
box-shadow: 0 0 0 2px var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card__badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -0.75rem;
|
||||||
|
left: var(--space-4);
|
||||||
|
background: var(--color-red);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card__prices {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-5);
|
||||||
|
padding-block: var(--space-3);
|
||||||
|
border-block: 1px solid var(--color-line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card__features {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card__features li {
|
||||||
|
padding-left: 1.5em;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card__features li::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0.5em;
|
||||||
|
width: 0.6em;
|
||||||
|
height: 0.6em;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-table-note {
|
||||||
|
color: var(--color-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worked-example {
|
||||||
|
border-left: 4px solid var(--color-red);
|
||||||
|
padding-left: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.worked-example dl {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
gap: var(--space-1) var(--space-3);
|
||||||
|
margin: var(--space-3) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worked-example dt {
|
||||||
|
color: var(--color-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.worked-example dd {
|
||||||
|
margin: 0;
|
||||||
|
text-align: right;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worked-example__total {
|
||||||
|
border-top: 1px solid var(--color-line);
|
||||||
|
padding-top: var(--space-2);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reassurance-band {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 700px) {
|
||||||
|
.reassurance-band {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reassurance-band li {
|
||||||
|
padding-left: 1.75em;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reassurance-band li::before {
|
||||||
|
content: "✓";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: var(--color-red);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Collections & museums ---- */
|
||||||
|
|
||||||
|
.collection-card,
|
||||||
|
.museum-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card__meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
background: var(--color-bg-light);
|
||||||
|
border: 1px solid var(--color-line);
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.15rem 0.6rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--color-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.museum-card img,
|
||||||
|
.team-card img {
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- CTA band ---- */
|
||||||
|
|
||||||
|
.cta-band {
|
||||||
|
background: var(--color-ink);
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-band h2 {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-band p {
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
max-width: 55ch;
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Contact ---- */
|
||||||
|
|
||||||
|
.contact-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-3);
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Footer ---- */
|
||||||
|
|
||||||
|
.site-footer {
|
||||||
|
background: var(--color-bg-light);
|
||||||
|
border-top: 1px solid var(--color-line);
|
||||||
|
padding-block: var(--space-6);
|
||||||
|
color: var(--color-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer a {
|
||||||
|
color: var(--color-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__logo {
|
||||||
|
height: 24px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-2) var(--space-4);
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__nav {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-3);
|
||||||
|
padding: 0;
|
||||||
|
margin: var(--space-4) 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer__nav a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Utilities ---- */
|
||||||
|
|
||||||
|
.mt-4 {
|
||||||
|
margin-top: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-5 {
|
||||||
|
margin-top: var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card--accent {
|
||||||
|
border-color: var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- TODO markers ---- */
|
||||||
|
|
||||||
|
.todo-flag {
|
||||||
|
display: inline-block;
|
||||||
|
background: #fff4e5;
|
||||||
|
border: 1px dashed #c77700;
|
||||||
|
color: #8a5600;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.15rem 0.5rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
BIN
src/assets/images/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src/assets/images/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 582 B |
BIN
src/assets/images/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/assets/images/logo-footer.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
src/assets/images/logo-header.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
7
src/assets/images/placeholders/grootegast.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450" role="img" aria-label="Placeholder photo — Victory Museum, Grootegast">
|
||||||
|
<rect width="800" height="450" fill="#F6F7F8"/>
|
||||||
|
<path d="M0 375 L800 325 M0 300 L800 250 M0 225 L800 175" stroke="#E3E5E8" stroke-width="2" fill="none"/>
|
||||||
|
<rect x="0" y="0" width="6" height="450" fill="#DF272F"/>
|
||||||
|
<text x="40" y="225" font-family="Arial, sans-serif" font-size="24" fill="#6A6E76">Photo placeholder</text>
|
||||||
|
<text x="40" y="255" font-family="Arial, sans-serif" font-size="18" fill="#6A6E76">Victory Museum, Grootegast</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 590 B |
7
src/assets/images/placeholders/holten.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450" role="img" aria-label="Placeholder photo — Holten Canadian War Cemetery Visitor Centre">
|
||||||
|
<rect width="800" height="450" fill="#F6F7F8"/>
|
||||||
|
<path d="M0 375 L800 325 M0 300 L800 250 M0 225 L800 175" stroke="#E3E5E8" stroke-width="2" fill="none"/>
|
||||||
|
<rect x="0" y="0" width="6" height="450" fill="#DF272F"/>
|
||||||
|
<text x="40" y="225" font-family="Arial, sans-serif" font-size="24" fill="#6A6E76">Photo placeholder</text>
|
||||||
|
<text x="40" y="255" font-family="Arial, sans-serif" font-size="18" fill="#6A6E76">Holten Canadian War Cemetery</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 609 B |
9
src/assets/images/placeholders/studio-screenshot.svg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600" role="img" aria-label="Placeholder screenshot of Frame 44 Studio">
|
||||||
|
<rect width="800" height="600" fill="#F6F7F8"/>
|
||||||
|
<path d="M0 500 L800 440 M0 400 L800 340 M0 300 L800 240 M0 200 L800 140" stroke="#E3E5E8" stroke-width="2" fill="none"/>
|
||||||
|
<circle cx="260" cy="260" r="10" fill="#DF272F"/>
|
||||||
|
<circle cx="480" cy="360" r="10" fill="#DF272F"/>
|
||||||
|
<circle cx="380" cy="180" r="10" fill="#DF272F"/>
|
||||||
|
<rect x="0" y="0" width="800" height="6" fill="#1B1D21"/>
|
||||||
|
<text x="40" y="560" font-family="Arial, sans-serif" font-size="22" fill="#6A6E76">Studio screenshot placeholder</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 648 B |
6
src/assets/images/placeholders/team-1.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" role="img" aria-label="Placeholder headshot">
|
||||||
|
<rect width="400" height="400" fill="#F6F7F8"/>
|
||||||
|
<circle cx="200" cy="160" r="70" fill="#E3E5E8"/>
|
||||||
|
<path d="M70 380 C70 280 330 280 330 380 Z" fill="#E3E5E8"/>
|
||||||
|
<text x="200" y="360" font-family="Arial, sans-serif" font-size="16" fill="#6A6E76" text-anchor="middle">Photo placeholder</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 412 B |
6
src/assets/images/placeholders/team-2.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" role="img" aria-label="Placeholder headshot">
|
||||||
|
<rect width="400" height="400" fill="#F6F7F8"/>
|
||||||
|
<circle cx="200" cy="160" r="70" fill="#E3E5E8"/>
|
||||||
|
<path d="M70 380 C70 280 330 280 330 380 Z" fill="#E3E5E8"/>
|
||||||
|
<text x="200" y="360" font-family="Arial, sans-serif" font-size="16" fill="#6A6E76" text-anchor="middle">Photo placeholder</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 412 B |
6
src/assets/images/placeholders/team-3.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" role="img" aria-label="Placeholder headshot">
|
||||||
|
<rect width="400" height="400" fill="#F6F7F8"/>
|
||||||
|
<circle cx="200" cy="160" r="70" fill="#E3E5E8"/>
|
||||||
|
<path d="M70 380 C70 280 330 280 330 380 Z" fill="#E3E5E8"/>
|
||||||
|
<text x="200" y="360" font-family="Arial, sans-serif" font-size="16" fill="#6A6E76" text-anchor="middle">Photo placeholder</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 412 B |
10
src/assets/js/main.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
(function () {
|
||||||
|
const toggle = document.querySelector(".site-nav__toggle");
|
||||||
|
const list = document.querySelector(".site-nav__list");
|
||||||
|
if (!toggle || !list) return;
|
||||||
|
|
||||||
|
toggle.addEventListener("click", () => {
|
||||||
|
const isOpen = list.classList.toggle("site-nav__list--open");
|
||||||
|
toggle.setAttribute("aria-expanded", String(isOpen));
|
||||||
|
});
|
||||||
|
})();
|
||||||
BIN
src/assets/logos/Favicon.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
src/assets/logos/Favicon2.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
src/assets/logos/Frame 44 Logo PNG-02.png
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
src/assets/logos/Frame 44 Logo PNG-03.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
src/assets/logos/Frame 44 Logo PNG-04.png
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
src/assets/logos/Frame 44 Logo PNG-05 - Copy (2).png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
src/assets/logos/Frame 44 Logo PNG-05.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
src/assets/logos/Frame 44 Logo PNG-06.png
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
src/assets/logos/Frame 44 Logo PNG-07.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
src/assets/logos/Frame 44 Logo PNG-08.png
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
src/assets/logos/Frame 44 Logo PNG-09.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
40
src/collections/index.njk
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
layout: layouts/base.njk
|
||||||
|
title: Historical Collections
|
||||||
|
description: Historical data collections licensed from Project '44 and its sister archives — scoped to the story your museum tells.
|
||||||
|
permalink: /collections/
|
||||||
|
---
|
||||||
|
{% from "macros.njk" import ctaBand %}
|
||||||
|
|
||||||
|
<section class="hero">
|
||||||
|
<div class="container hero__inner">
|
||||||
|
<p class="hero__eyebrow">Historical Collections</p>
|
||||||
|
<h1>Research you don't have to commission yourself</h1>
|
||||||
|
<p class="hero__sub">The platform behind Project '44, available for your story. Frame 44 licenses historical data from Project '44 and its sister archives (CRMA) so your exhibits launch with real, sourced content.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="grid">
|
||||||
|
{% for item in historicalCollections %}
|
||||||
|
<article class="card collection-card">
|
||||||
|
<div class="collection-card__meta">
|
||||||
|
<h2>{{ item.name }}{% if item.subtitle %} <span class="tag">{{ item.subtitle }}</span>{% endif %}</h2>
|
||||||
|
<p class="price-figure">
|
||||||
|
{% if item.priceFrom %}from {% endif %}{{ item.price | cad }}
|
||||||
|
<span class="price-figure__label">{% if item.priceType == "annual" %}per year{% else %}one-time, per project{% endif %}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% if item.highlight %}<p class="tag">{{ item.highlight }}</p>{% endif %}
|
||||||
|
<p>{{ item.description }}</p>
|
||||||
|
<p><strong>What's inside:</strong> {{ item.whatsInside }}</p>
|
||||||
|
<p><strong>Suits:</strong> {{ item.suits }}</p>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<p class="mt-4"><a href="/pricing/#collections">See collection pricing alongside packages and AI tools →</a></p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{ ctaBand("Not sure which collection fits your museum?", "Tell us the story you're telling and we'll point you to the right collection — or scope a custom research build.", i18n.cta.bookConversation, site.ctaUrl) }}
|
||||||
31
src/contact/index.njk
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
layout: layouts/base.njk
|
||||||
|
title: Contact
|
||||||
|
description: Every installation starts with a conversation. Get in touch with Frame 44 to scope your exhibit and price it to your funding.
|
||||||
|
permalink: /contact/
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="hero">
|
||||||
|
<div class="container hero__inner">
|
||||||
|
<p class="hero__eyebrow">Contact</p>
|
||||||
|
<h1>Every installation starts with a conversation.</h1>
|
||||||
|
<p class="hero__sub">We'll scope your exhibit, prepare a grant-ready proposal, and price it to your funding — no obligation.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container contact-panel">
|
||||||
|
<p>
|
||||||
|
<a class="button button--primary" href="mailto:{{ site.contactEmail }}?subject=Exhibit%20inquiry">
|
||||||
|
{{ i18n.cta.emailUs }}: {{ site.contactEmail }}
|
||||||
|
</a>
|
||||||
|
{% if not site.contactEmailConfirmed %}<span class="todo-flag">TODO: confirm real contact email</span>{% endif %}
|
||||||
|
</p>
|
||||||
|
<p>Prefer to write ahead of a call? Tell us:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Your museum or heritage site</li>
|
||||||
|
<li>The story or collection you have in mind</li>
|
||||||
|
<li>Any funding deadline we should be scoping around</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
BIN
src/favicon.ico
Normal file
|
After Width: | Height: | Size: 604 B |
76
src/index.njk
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
layout: layouts/base.njk
|
||||||
|
title: Map-native interactive exhibits for museums & heritage sites
|
||||||
|
description: Frame 44 builds map-native interactive exhibits for museums and heritage sites — touchscreen kiosks and web exhibits your own staff can edit, forever.
|
||||||
|
---
|
||||||
|
{% from "macros.njk" import ctaBand %}
|
||||||
|
|
||||||
|
<section class="hero">
|
||||||
|
<div class="container hero__inner">
|
||||||
|
<p class="hero__eyebrow">Frame 44 Technology</p>
|
||||||
|
<h1>Map-native interactive exhibits for museums & heritage sites</h1>
|
||||||
|
<p class="hero__sub">Give visitors a map they can touch and explore — timelines, story panels and imagery, on your museum floor and on your website. Built and supported by our team; edited by yours.</p>
|
||||||
|
<div class="hero__actions">
|
||||||
|
<a class="button button--primary" href="{{ site.ctaUrl }}">{{ i18n.cta.bookConversation }}</a>
|
||||||
|
<a class="button button--outline" href="/platform/">{{ i18n.cta.explorePlatform }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Three things to understand</h2>
|
||||||
|
<div class="grid grid--3 steps">
|
||||||
|
<div class="step card">
|
||||||
|
<h3>An exhibit</h3>
|
||||||
|
<p>One interactive story — a map your visitors can touch and explore, with timelines, photos and text panels.</p>
|
||||||
|
</div>
|
||||||
|
<div class="step card">
|
||||||
|
<h3>A gallery screen</h3>
|
||||||
|
<p>The physical touchscreen on your museum floor. The same exhibits also run on your website.</p>
|
||||||
|
</div>
|
||||||
|
<div class="step card">
|
||||||
|
<h3>Frame 44 Studio</h3>
|
||||||
|
<p>The tool your own staff use to edit text, photos and maps — no technical skills needed.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section section--muted">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Permanent installations</h2>
|
||||||
|
<p>Frame 44 exhibits are running in museums and heritage sites today.</p>
|
||||||
|
<div class="grid grid--2">
|
||||||
|
{% for museum in museums %}
|
||||||
|
<article class="card museum-card">
|
||||||
|
<img src="{{ museum.image }}" alt="{{ museum.name }}" width="800" height="450" loading="lazy">
|
||||||
|
<h3>{{ museum.name }}</h3>
|
||||||
|
<p class="tag">{{ museum.location }} · {{ museum.type }}</p>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<p><a href="/museums/">See the full case studies →</a></p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>{{ i18n.pricing.eyebrow }}</h2>
|
||||||
|
<p>{{ i18n.pricing.twoNumberExplainer }}</p>
|
||||||
|
<div class="grid grid--4">
|
||||||
|
{% for pkg in packages %}
|
||||||
|
<div class="card">
|
||||||
|
<h3>{{ pkg.name }}</h3>
|
||||||
|
<p class="price-figure">{% if pkg.buildPriceFrom %}from {% endif %}{{ pkg.buildPrice | cad }}</p>
|
||||||
|
<p class="price-figure__label">{{ i18n.pricing.buildYearLabel }}</p>
|
||||||
|
<p class="price-figure">{% if pkg.annualPrice %}{{ pkg.annualPrice | cad }}{% else %}{{ pkg.annualPriceLabel }}{% endif %}</p>
|
||||||
|
<p class="price-figure__label">{{ i18n.pricing.everyYearLabel }}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<p><a class="button button--outline" href="/pricing/">{{ i18n.cta.seePricing }}</a></p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{ ctaBand("Every installation starts with a conversation.", "We'll scope your exhibit, prepare a grant-ready proposal, and price it to your funding — no obligation.", i18n.cta.bookConversation, site.ctaUrl) }}
|
||||||
32
src/museums/index.njk
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
layout: layouts/base.njk
|
||||||
|
title: Museums
|
||||||
|
description: Case studies from Frame 44 installations — the Victory Museum in Grootegast and the Holten Canadian War Cemetery Visitor Centre.
|
||||||
|
permalink: /museums/
|
||||||
|
---
|
||||||
|
{% from "macros.njk" import ctaBand %}
|
||||||
|
|
||||||
|
<section class="hero">
|
||||||
|
<div class="container hero__inner">
|
||||||
|
<p class="hero__eyebrow">Museums</p>
|
||||||
|
<h1>Where Frame 44 is installed today</h1>
|
||||||
|
<p class="hero__sub">Two permanent kiosk installations, in the Netherlands.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="grid grid--2">
|
||||||
|
{% for museum in museums %}
|
||||||
|
<article class="card museum-card">
|
||||||
|
<img src="{{ museum.image }}" alt="{{ museum.name }}" width="800" height="450" loading="lazy">
|
||||||
|
<h2>{{ museum.name }}</h2>
|
||||||
|
<p class="tag">{{ museum.location }} · {{ museum.type }}</p>
|
||||||
|
<p>{{ museum.summary | safe }}</p>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{ ctaBand("Want your museum here next?", "We'll scope your exhibit, prepare a grant-ready proposal, and price it to your funding — no obligation.", i18n.cta.bookConversation, site.ctaUrl) }}
|
||||||
69
src/platform/index.njk
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
layout: layouts/base.njk
|
||||||
|
title: The Platform
|
||||||
|
description: Frame 44 Atlas, the map platform, and Frame 44 Studio, the exhibit builder — the two pieces behind every Frame 44 exhibit.
|
||||||
|
permalink: /platform/
|
||||||
|
---
|
||||||
|
{% from "macros.njk" import ctaBand %}
|
||||||
|
|
||||||
|
<section class="hero">
|
||||||
|
<div class="container hero__inner">
|
||||||
|
<p class="hero__eyebrow">Platform</p>
|
||||||
|
<h1>Your team builds in Studio. Everything runs on Atlas.</h1>
|
||||||
|
<p class="hero__sub">Frame 44 Atlas is the map platform behind every exhibit — the map engine, your historical data, hosting and sync. Frame 44 Studio is where your staff build and edit exhibits — no developer required.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Frame 44 Atlas</h2>
|
||||||
|
<p>The platform underneath every Frame 44 exhibit — the map engine, your historical data, and the infrastructure that keeps it all running.</p>
|
||||||
|
<ul>
|
||||||
|
<li>Live MapLibre map integration</li>
|
||||||
|
<li>Historical map layers and projections</li>
|
||||||
|
<li>Hosting and content sync</li>
|
||||||
|
<li>Offline kiosk runtime for gallery screens</li>
|
||||||
|
<li>Monitoring and support</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section section--muted">
|
||||||
|
<div class="container grid grid--2">
|
||||||
|
<div>
|
||||||
|
<h2>Frame 44 Studio</h2>
|
||||||
|
<p>The block-based builder your own staff use to assemble each exhibit — no coding, no developer on standby.</p>
|
||||||
|
<ul>
|
||||||
|
<li>Headings, fact panels and timeline cards</li>
|
||||||
|
<li>Photo and document galleries</li>
|
||||||
|
<li>Map buttons, overlay toggles and scenario buttons</li>
|
||||||
|
<li>Themes to match your institution's look</li>
|
||||||
|
<li>One-click publish and sync workflow</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<img src="/assets/images/placeholders/studio-screenshot.svg" alt="Screenshot placeholder of Frame 44 Studio" width="800" height="600" loading="lazy">
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container grid grid--2">
|
||||||
|
<div class="card">
|
||||||
|
<h3>Gallery screens</h3>
|
||||||
|
<p>Kiosk software built to run in a museum, not an office. It works offline on secure networks and syncs new content automatically when it's connected. We monitor and support every screen we install.</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>Web exhibits</h3>
|
||||||
|
<p>The same exhibits your visitors touch in the gallery also run on your museum's website — built once, published everywhere, at no extra authoring effort.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section section--muted">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Your team can edit everything, forever</h2>
|
||||||
|
<p>Once an exhibit is built, it's yours to maintain. Your staff can update text, swap photos, adjust map overlays and publish changes themselves — no ticket, no developer, no waiting.</p>
|
||||||
|
<p>Your content stays yours — exports in standard formats if you ever leave.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{ ctaBand("See Studio in a live walkthrough.", "We'll show you Frame 44 Studio and answer questions about your specific exhibit.", i18n.cta.bookConversation, site.ctaUrl) }}
|
||||||
151
src/pricing/index.njk
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
---
|
||||||
|
layout: layouts/base.njk
|
||||||
|
title: Pricing
|
||||||
|
description: Frame 44 pricing — packages, historical collections and AI tools. Every price shown here is CAD, exclusive of taxes.
|
||||||
|
permalink: /pricing/
|
||||||
|
---
|
||||||
|
{% from "macros.njk" import ctaBand, pricingCard, collectionCard %}
|
||||||
|
{% set flagship = packages | findByKey(extras.workedExample.packageKey) %}
|
||||||
|
{% set campaign = historicalCollections | findByKey(extras.workedExample.collectionKey) %}
|
||||||
|
{% set chatbot = aitools.items | findByKey(extras.workedExample.aitoolKey) %}
|
||||||
|
{% set buildYearTotal = flagship.buildPrice + campaign.price + chatbot.price %}
|
||||||
|
{% set everyYearTotal = flagship.annualPrice + campaign.price + chatbot.price %}
|
||||||
|
|
||||||
|
<section class="hero">
|
||||||
|
<div class="container hero__inner">
|
||||||
|
<p class="hero__eyebrow">{{ i18n.pricing.eyebrow }}</p>
|
||||||
|
<h1>Pricing</h1>
|
||||||
|
<p class="hero__sub">{{ i18n.pricing.twoNumberExplainer }}</p>
|
||||||
|
<div class="grid grid--2 mt-5">
|
||||||
|
<div class="card">
|
||||||
|
<h3>{{ i18n.pricing.buildYearLabel }}</h3>
|
||||||
|
<p>{{ i18n.pricing.buildYearNote }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>{{ i18n.pricing.everyYearLabel }}</h3>
|
||||||
|
<p>{{ i18n.pricing.everyYearNote }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section" id="packages">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Packages</h2>
|
||||||
|
<div class="grid grid--2">
|
||||||
|
{% for pkg in packages %}
|
||||||
|
{{ pricingCard(pkg) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section section--muted">
|
||||||
|
<div class="container">
|
||||||
|
<h2>A worked example</h2>
|
||||||
|
<div class="card worked-example">
|
||||||
|
<dl>
|
||||||
|
<dt>{{ flagship.name }}</dt>
|
||||||
|
<dd>{{ flagship.buildPrice | cad }} build · {{ flagship.annualPrice | cad }}/yr</dd>
|
||||||
|
<dt>{{ campaign.name }} data licence</dt>
|
||||||
|
<dd>{{ campaign.price | cad }}/yr</dd>
|
||||||
|
<dt>{{ chatbot.name }}</dt>
|
||||||
|
<dd>{{ chatbot.price | cad }}/yr</dd>
|
||||||
|
<dt class="worked-example__total">Total</dt>
|
||||||
|
<dd class="worked-example__total">{{ buildYearTotal | cad }} build year · {{ everyYearTotal | cad }} every year after</dd>
|
||||||
|
</dl>
|
||||||
|
<p class="price-table-note">Compare that with comparable one-off custom installs, typically starting near {{ extras.workedExample.comparisonOneOff | cad }} — with no updates, no builder, and nothing new next year.</p>
|
||||||
|
<p class="price-table-note">Most installations live <span class="todo-flag">TODO: confirm timeline</span> {{ extras.timeline.note }} after signing.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section" id="collections">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Historical Collections</h2>
|
||||||
|
<p>Licensed from Project '44 and its sister archives (CRMA) — scoped to the story your museum tells.</p>
|
||||||
|
<div class="grid grid--3">
|
||||||
|
{% for item in historicalCollections %}
|
||||||
|
{{ collectionCard(item) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section section--muted" id="ai-tools">
|
||||||
|
<div class="container">
|
||||||
|
<h2>AI Tools</h2>
|
||||||
|
<p>{{ aitools.intro }}</p>
|
||||||
|
<div class="grid grid--3">
|
||||||
|
{% for tool in aitools.items %}
|
||||||
|
<article class="card">
|
||||||
|
<h3>{{ tool.name }}</h3>
|
||||||
|
<p>{{ tool.description }}</p>
|
||||||
|
<p class="price-figure">{{ tool.price | cad }}<span class="price-figure__label">per year</span></p>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="card card--accent mt-4">
|
||||||
|
<h3>{{ aitools.bundle.name }}</h3>
|
||||||
|
<p>{{ aitools.bundle.description }}</p>
|
||||||
|
<p class="price-figure">{{ aitools.bundle.price | cad }}<span class="price-figure__label">per year — save {{ aitools.bundle.savings | cad }}/yr</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section" id="extras">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Extras</h2>
|
||||||
|
<div class="grid grid--2">
|
||||||
|
{% for item in extras.items %}
|
||||||
|
<article class="card">
|
||||||
|
<h3>{{ item.name }}</h3>
|
||||||
|
{% if item.description %}<p>{{ item.description }}</p>{% endif %}
|
||||||
|
<p class="price-figure">
|
||||||
|
{% if item.priceMin %}{{ item.priceMin | cad }}–{{ item.priceMax | cad }}{% else %}{{ item.price | cad }}{% endif %}
|
||||||
|
<span class="price-figure__label">{% if item.priceType == "annual" %}per year{% else %}one-time, per exhibit{% endif %}</span>
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section section--muted" id="terms">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Terms</h2>
|
||||||
|
<div class="grid grid--2">
|
||||||
|
<div class="card">
|
||||||
|
<h3>Multi-year prepay</h3>
|
||||||
|
<p>Prepay your "every year after" fees and save:</p>
|
||||||
|
<ul>
|
||||||
|
{% for term in extras.terms.prepay %}
|
||||||
|
<li>{{ term.years }}-year prepaid = {{ term.multiplier }}× annual</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<p class="price-table-note">Worked example: {{ flagship.name }} 3-year prepaid = {{ flagship.annualPrice | prepay(3) | cad }}, vs {{ (flagship.annualPrice * 3) | cad }} paid annually.</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>Hardware</h3>
|
||||||
|
<p>Touchscreens typically <span class="todo-flag">TODO: confirm range</span> {{ extras.hardware.priceMin | cad }}–{{ extras.hardware.priceMax | cad }} {{ extras.hardware.unit }}. {{ extras.hardware.note }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card mt-4">
|
||||||
|
<h3>Grant support</h3>
|
||||||
|
<p>{{ extras.grantSupport }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<ul class="reassurance-band">
|
||||||
|
{% for line in i18n.pricing.reassurance %}
|
||||||
|
<li>{{ line }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<p class="price-table-note mt-4">{{ i18n.pricing.currencyNote }}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{ ctaBand("Ready to see this priced for your museum?", "Tell us what you're building and we'll put together a grant-ready proposal.", i18n.cta.bookConversation, site.ctaUrl) }}
|
||||||
4
src/robots.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
Sitemap: https://www.frame44.com/sitemap.xml
|
||||||
12
src/sitemap.njk
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
permalink: /sitemap.xml
|
||||||
|
eleventyExcludeFromCollections: true
|
||||||
|
---
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
{%- for entry in collections.all -%}
|
||||||
|
<url>
|
||||||
|
<loc>{{ site.url }}{{ entry.url }}</loc>
|
||||||
|
</url>
|
||||||
|
{%- endfor -%}
|
||||||
|
</urlset>
|
||||||