Cannabis Freedom Index

Changelog 051549etree, with Claude Opus 5.5

Footer: build commit (linked to GitHub) and date on the right; npm run build:quick

The stamp uses GITHUB_SHA in the deploy and git locally. build:quick sets SKIP_OG=1 so small frontend changes can be checked without drawing the preview images; the deploy still runs the full build.

6 files of code, design or docs +28 −2 lines

Code, design and documents 6 files

AGENTS.md +1 −1
@@ -15,3 +15,3 @@ cannabis law (`changes.yaml`, `articles/`) and a cultural atlas (`culture/`), bu
   else's uncommitted changes, and commit only your own files.
 - Don't start dev or preview servers; check with `npm run build` (one build at a time) and the
-  HTML in `dist/`.
+  HTML in `dist/`. For a small frontend change, `npm run build:quick` skips the preview images.

package.json +1 −0
@@ -7,4 +7,5 @@
     "dev": "astro dev",
     "build": "astro build",
+    "build:quick": "SKIP_OG=1 astro build",
     "preview": "astro preview",
     "books:status": "node scripts/books-status.cjs",

src/layouts/Base.astro +4 −0
@@ -9,4 +9,5 @@ import Leaf from "../components/Leaf.astro";
 import { Icon } from "astro-icon/components";
 import { SEARCH_ICONS, RESULT_KIND_ICONS } from "../lib/search-icons.js";
+import { build } from "../lib/build-info.js";
 
 // `seoTitle` replaces the whole <title> (search-result wording); `jsonld` is one schema.org
@@ -127,7 +128,10 @@ const schemas = jsonld ? [jsonld].flat() : [];
 
   <footer class="footer">
+    <div class="footer-main">
     <p>Cannabis Freedom Index · Informational only, not legal advice · Map data © Natural Earth</p>
     <p class="footer-links"><a href={`${base}about/`}>About</a> · <a href={`${base}sources/`}>Sources</a> · <a href={`${base}glossary/`}>Glossary</a> · <a href={`${base}changelog/`}>Changelog</a> · <a href={`${base}rss.xml`}>RSS</a> · <a href={`${base}data/`}>Download the data</a> · <a href={`${base}llms.txt`} title="A plain-text guide to this site for language models">For AI: llms.txt</a> · <a href={`${base}data/#licence`}>Licence: data CC BY 4.0, code MIT</a></p>
     <p>Open source — the scores, explanations and code are on <a href={repo} target="_blank" rel="noopener noreferrer">GitHub</a>. Spotted a mistake or a law that changed? <a href={`${repo}/issues`} target="_blank" rel="noopener noreferrer">Open an issue</a> or send a pull request.</p>
+    </div>
+    <p class="footer-build">{build.short && <>Build <a href={`${repo}/commit/${build.sha}`} target="_blank" rel="noopener noreferrer" title="The commit this site was built from">{build.short}</a> · </>}<time datetime={build.date}>{build.label}</time></p>
   </footer>
 

src/lib/build-info.js +15 −0
@@ -0,0 +1,15 @@
+// What the footer says about this build: the commit it was built from and the day. Read once per
+// build. The deploy workflow sets GITHUB_SHA; locally git is asked; without either, no commit.
+import { execFileSync } from "node:child_process";
+
+const sha = process.env.GITHUB_SHA || (() => {
+  try { return execFileSync("git", ["rev-parse", "HEAD"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim(); } catch { return ""; }
+})();
+
+const built = new Date();
+export const build = {
+  sha,
+  short: sha.slice(0, 7),
+  date: built.toISOString().slice(0, 10),
+  label: built.toLocaleDateString("en-GB", { day: "numeric", month: "long", year: "numeric", timeZone: "UTC" }),
+};

src/pages/og/[...path].png.js +2 −1
@@ -98,5 +98,6 @@ for (const c of places) {
 }
 
-export const getStaticPaths = () => Object.entries(cards).map(([path, card]) => ({ params: { path }, props: { card } }));
+// `npm run build:quick` (SKIP_OG=1) draws no cards, to check a small change fast; the deploy always draws them
+export const getStaticPaths = () => process.env.SKIP_OG ? [] : Object.entries(cards).map(([path, card]) => ({ params: { path }, props: { card } }));
 
 const draw = async ([kind, props]) => kind === "culture" ? renderOg(kind, { ...props, photo: await photoOf(props.photo.file, props.photo.fit) })

src/styles/global.css +5 −0
@@ -1148,4 +1148,9 @@ tbody tr:hover .cname { text-decoration: underline; text-underline-offset: 4px;
 
 .footer { text-align: left; font-size: 10.5px; padding: 28px clamp(16px, 4vw, 40px) 48px; margin-top: clamp(64px, 9vw, 120px); border-top: 1px solid var(--text); }
+/* the build stamp (commit and date) sits on the right, under the rest on a phone */
+.footer { display: flex; justify-content: space-between; align-items: flex-start; gap: 6px 32px; flex-wrap: wrap; }
+.footer-main { flex: 1 1 520px; min-width: 0; }
+.footer .footer-build { margin: 0; white-space: nowrap; font-variant-numeric: tabular-nums; }
+.footer-build a { font-family: var(--mono); }
 
 /* ══ Country profile ═════════════════════════════════════════════════════════

    ↑↓ to move, ↵ to open.