creating layout

This commit is contained in:
nathan
2026-07-16 16:36:46 -04:00
parent 8fc2cae0b5
commit 949a50f741
55 changed files with 3790 additions and 0 deletions

34
src/_data/aitools.json Normal file
View 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
View 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 816 weeks after signing.",
"confirmed": false
},
"workedExample": {
"packageKey": "flagship",
"collectionKey": "second-world-war-campaign",
"aitoolKey": "visitor-chatbot",
"comparisonOneOff": 67000
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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"
}
]

View 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
View 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 %}

View 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>&copy; {% 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>

View 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">&#9776;</span>
</button>
</nav>
</div>
</header>

40
src/about/index.njk Normal file
View 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
View 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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View 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

View 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

View 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

View 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

View 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

View 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
View 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));
});
})();

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

40
src/collections/index.njk Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

76
src/index.njk Normal file
View 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 &amp; 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 }} &middot; {{ 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
View 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 }} &middot; {{ 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
View 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
View 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 &middot; {{ 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 &middot; {{ 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
View File

@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://www.frame44.com/sitemap.xml

12
src/sitemap.njk Normal file
View 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>