Cannabis Freedom Index

Changelog 3792167

first commit

19 files of code, design or docs +10,314 −0 lines

Code, design and documents 19 files

.gitignore +4 −0
@@ -0,0 +1,4 @@
+node_modules/
+dist/
+.astro/
+.claude/

astro.config.mjs +4 −0
@@ -0,0 +1,4 @@
+import { defineConfig } from "astro/config";
+
+// Fully static output: one HTML file per page, including one per country.
+export default defineConfig({});

package-lock.json +4293 −0

Not shown: a picture, a generated file, or past the length shown per change.

package.json +20 −0
@@ -0,0 +1,20 @@
+{
+  "name": "cannabis-freedom-index",
+  "type": "module",
+  "version": "1.0.0",
+  "private": true,
+  "scripts": {
+    "dev": "astro dev",
+    "build": "astro build",
+    "preview": "astro preview"
+  },
+  "dependencies": {
+    "astro": "^7.3.5",
+    "d3-geo": "^3.1.1",
+    "d3-selection": "^3.0.0",
+    "d3-transition": "^3.0.1",
+    "d3-zoom": "^3.0.0",
+    "topojson-client": "^3.1.0",
+    "world-atlas": "^2.0.2"
+  }
+}

public/favicon.svg +15 −0

Not shown: a picture, a generated file, or past the length shown per change.

src/components/CountryArticle.astro +100 −0
@@ -0,0 +1,100 @@
+---
+// Full write-up for one country. Rendered on its own page, and fetched into the
+// drawer on the home page — so there is a single template for both views.
+import { SEGMENTS, CONTEXT, REC_LABEL, MED_LABEL } from "../data/data.js";
+import { DETAILS } from "../data/details.js";
+import { countries, segAvg, segmentPoints, segmentMax } from "../lib/countries.js";
+import { tierOf, tierColor } from "../lib/tiers.js";
+
+const { c } = Astro.props;
+const d = DETAILS[c.id];
+const r = 42;
+const circ = 2 * Math.PI * r;
+---
+<article class="country" data-id={c.id}>
+  <div class="c-side">
+    <div class="d-head">
+      <span class="d-flag">{c.flag}</span>
+      <div>
+        <h1>{c.name}</h1>
+        <p class="sub">{c.region}</p>
+      </div>
+    </div>
+    <div class="d-score">
+      <svg class="ring" width="104" height="104" viewBox="0 0 104 104" aria-hidden="true">
+        <circle cx="52" cy="52" r={r} fill="none" stroke="var(--border)" stroke-width="9" />
+        <circle cx="52" cy="52" r={r} fill="none" stroke={tierColor(c.raw)} stroke-width="9" stroke-linecap="round"
+          stroke-dasharray={`${(c.raw / 100) * circ} ${circ}`} transform="rotate(-90 52 52)" />
+        <text x="52" y="62" text-anchor="middle">{c.score}</text>
+      </svg>
+      <div>
+        <div class="tier">{tierOf(c.raw).name}</div>
+        <p class="sub">#{c.rank} of {countries.length} worldwide<br>#{c.regionRank} of {c.regionCount} in {c.region}</p>
+      </div>
+    </div>
+    <div class="d-chips">
+      <span class={`chip s-${c.rec}`}>Recreational: {REC_LABEL[c.rec]}</span>
+      <span class={`chip s-${c.med}`}>Medical: {MED_LABEL[c.med]}</span>
+    </div>
+    <p class="d-note">{c.note}</p>
+  </div>
+
+  <div class="c-main">
+    <h2 class="d-h">Score breakdown <span>▏world average</span></h2>
+    <p class="d-hint">Select a segment to show or hide the exact rules and sources.{d && ` Reviewed ${d.reviewed}.`}</p>
+    {SEGMENTS.map((s, i) => {
+      const seg = d?.seg[s.detail];
+      return (
+        <details class="segrow" open>
+          <summary>
+            <div class="segrow-top">
+              <span>{s.name} <small>· {s.weight}%</small></span>
+              <b>{c.s[i]}/10<i class="chev" aria-hidden="true"></i></b>
+            </div>
+            <div class="bar" title={`World average ${segAvg[i].toFixed(1)}/10`}>
+              <i style={`width:${c.s[i] * 10}%;background:${tierColor(c.s[i] * 10)}`}></i>
+              <span class="avg-tick" style={`left:calc(${segAvg[i] * 10}% - 1px)`}></span>
+            </div>
+            <div class="pts">adds {segmentPoints(i, c.s[i]).toFixed(1)} of {segmentMax(i).toFixed(0)} points</div>
+          </summary>
+          {seg && (
+            <div class="why">
+              <p>{seg.t}</p>
+              <ul class="srcs">
+                {seg.src.map((k) => (
+                  <li><a href={d.sources[k][1]} target="_blank" rel="noopener noreferrer">{d.sources[k][0]}</a></li>
+                ))}
+              </ul>
+            </div>
+          )}
+        </details>
+      );
+    })}
+
+    {d && (
+      <>
+        <h2 class="d-h">More context <span>not part of the score</span></h2>
+        {CONTEXT.map(([name, k]) => (
+          <div class="segrow">
+            <div class="segrow-top"><span>{name}</span></div>
+            <p class="ctx">{d.seg[k].t}</p>
+            <ul class="srcs">
+              {d.seg[k].src.map((x) => (
+                <li><a href={d.sources[x][1]} target="_blank" rel="noopener noreferrer">{d.sources[x][0]}</a></li>
+              ))}
+            </ul>
+          </div>
+        ))}
+
+        <section class="c-sources">
+          <h2 class="d-h">All sources</h2>
+          <ol>
+            {Object.values(d.sources).map(([label, url]) => (
+              <li><a href={url} target="_blank" rel="noopener noreferrer">{label}</a></li>
+            ))}
+          </ol>
+        </section>
+      </>
+    )}
+  </div>
+</article>

src/components/DidYouKnow.astro +58 −0
@@ -0,0 +1,58 @@
+---
+// A rotating "Did you know?" card. Every fact is in the HTML; the script only
+// decides which one is visible, so the first fact still shows without JavaScript.
+import { getFacts } from "../lib/facts.js";
+
+const base = import.meta.env.BASE_URL;
+const facts = getFacts();
+---
+<section class="dyk card" id="dyk" aria-roledescription="carousel" aria-label="Did you know?">
+  <div class="dyk-top">
+    <p class="dyk-label"><span aria-hidden="true">💡</span> Did you know?</p>
+    <div class="dyk-nav">
+      <span class="dyk-count" id="dykCount">1 / {facts.length}</span>
+      <button class="icon-btn" id="dykPrev" aria-label="Previous fact">‹</button>
+      <button class="icon-btn" id="dykNext" aria-label="Next fact">›</button>
+    </div>
+  </div>
+  <div class="dyk-facts" aria-live="polite">
+    {facts.map((f, i) => (
+      <div class="dyk-fact" hidden={i !== 0}>
+        <p>{f.text}</p>
+        {f.country && (
+          <a href={`${base}country/${f.country.slug}/`} data-country={f.country.id}>
+            {f.country.flag} See {f.country.name} →
+          </a>
+        )}
+      </div>
+    ))}
+  </div>
+</section>
+
+<script>
+  const root = document.getElementById("dyk");
+  const facts = [...root.querySelectorAll(".dyk-fact")];
+  const count = document.getElementById("dykCount");
+  let i = 0;
+  let timer = null;
+
+  function show(n) {
+    i = (n + facts.length) % facts.length;
+    facts.forEach((el, k) => (el.hidden = k !== i));
+    count.textContent = `${i + 1} / ${facts.length}`;
+  }
+  // Rotate slowly, but never while someone is reading or interacting, and not at all
+  // for people who have asked for reduced motion.
+  const still = matchMedia("(prefers-reduced-motion: reduce)").matches;
+  function start() { if (!still && !timer) timer = setInterval(() => show(i + 1), 9000); }
+  function stop() { clearInterval(timer); timer = null; }
+
+  document.getElementById("dykPrev").addEventListener("click", () => { stop(); show(i - 1); });
+  document.getElementById("dykNext").addEventListener("click", () => { stop(); show(i + 1); });
+  root.addEventListener("pointerenter", stop);
+  root.addEventListener("pointerleave", start);
+  root.addEventListener("focusin", stop);
+
+  show(Math.floor(Math.random() * facts.length)); // a different fact on each visit
+  start();
+</script>

src/components/Leaf.astro +14 −0
@@ -0,0 +1,14 @@
+---
+// Seven-leaflet cannabis leaf, drawn from one leaflet shape rotated and scaled
+// around the point where the leaflets meet the stem.
+const { size = 22 } = Astro.props;
+const leaflets = [[0, 1], [-31, 0.9], [31, 0.9], [-63, 0.7], [63, 0.7], [-97, 0.43], [97, 0.43]];
+---
+<svg viewBox="0 0 24 24" width={size} height={size} aria-hidden="true">
+  <g transform="translate(12 16.4) scale(1.28)" fill="currentColor">
+    {leaflets.map(([angle, k]) => (
+      <path d="M0 0C1.9-2.6 1.5-7 0-10C-1.5-7-1.9-2.6 0 0Z" transform={`rotate(${angle}) scale(${k})`} />
+    ))}
+  </g>
+  <path d="M12 16.4V21.6" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
+</svg>

src/data/data.js +472 −0
@@ -0,0 +1,472 @@
+/* Cannabis Freedom Index — dataset
+ *
+ * The index measures PERSONAL cannabis freedom: what an ordinary adult may do
+ * and what happens to them if they do it. Industry, product and medical rules
+ * are deliberately left out of the score.
+ *
+ * The yardstick is the "tomato standard": 10 means no cannabis-specific rule at all —
+ * unlimited, unlicensed, unregistered. Every cap, licence and registry costs points.
+ *
+ * Every country is scored 0–10 in six segments using the fixed levels in SEGMENTS.
+ * The index is the weighted sum, scaled to 0–100.
+ *
+ * Score order in each row:
+ * [possession, cultivation, enforcement, sharing, consumption, access]
+ *
+ * Scores are editorial estimates of law *and* practice, not legal advice.
+ * Per-segment explanations and sources live in js/details.js.
+ */
+
+export const DATA_SNAPSHOT = "2026";
+
+// `detail` is the index of this segment's explanation in DETAILS[id].seg.
+export const SEGMENTS = [
+  {
+    id: "possession", name: "Possession", weight: 25, detail: 0,
+    desc: "Can an adult hold a small amount for personal use?",
+    levels: [[0, "Prison is a realistic outcome for a small amount"], [1, "A crime, but usually ends in a fine or diversion"], [3, "Decriminalised: at most an administrative fine"], [5, "Legal up to a small cap (around 30 g)"], [7, "Legal with a generous cap or none at home"], [10, "Unlimited — like any vegetable"]],
+  },
+  {
+    id: "cultivation", name: "Personal cultivation", weight: 25, detail: 1,
+    desc: "Can you grow your own plants at home?",
+    levels: [[0, "Treated as drug production, prison"], [1, "A crime, lesser penalties for a few plants"], [2, "A few plants tolerated or decriminalised"], [4, "Legal for a handful of plants (3–6)"], [6, "Legal with no fixed plant cap, private only"], [10, "Unlimited, indoors or out, no registration"]],
+  },
+  {
+    id: "enforcement", name: "Punishment & enforcement", weight: 20, detail: 8,
+    desc: "How hard does the law come down on personal users in practice?",
+    levels: [[0, "Death penalty, corporal punishment or long sentences"], [1, "Users are routinely jailed"], [2, "Prison possible, fines typical"], [4, "Fines or diversion only"], [6, "Nothing inside the limits, prison for going beyond them"], [10, "No cannabis offences at all"]],
+  },
+  {
+    id: "sharing", name: "Sharing & gifting", weight: 10, detail: 2,
+    desc: "Is passing cannabis to another adult, unpaid, treated differently from dealing?",
+    levels: [[0, "Sharing equals trafficking"], [2, "Illegal, but not pursued in practice"], [3, "Legal grey area"], [5, "Gifts between adults legal up to a cap"], [10, "Give away any amount"]],
+  },
+  {
+    id: "consumption", name: "Consumption", weight: 10, detail: 3,
+    desc: "Is using itself an offence, and where may you do it?",
+    levels: [[0, "Use itself is a crime"], [1, "Illegal, lightly enforced"], [3, "Legal or tolerated in private only"], [5, "Legal in public with many restrictions"], [7, "Allowed wherever tobacco is"], [10, "No cannabis-specific rules"]],
+  },
+  {
+    id: "access", name: "Legal access", weight: 10, detail: 4,
+    desc: "Is there a lawful way to obtain cannabis without growing it yourself?",
+    levels: [[0, "No lawful source"], [1, "Medical route only, hard to use"], [3, "Tolerated shops or clubs, or easy medical access"], [4, "Legal clubs or state outlets with registration and caps"], [6, "Licensed shops with purchase caps"], [10, "Sold by anyone, no licence — like produce"]],
+  },
+];
+
+// Explanations that exist in DETAILS but are not part of the score (index into DETAILS[id].seg).
+export const CONTEXT = [["Commercial production", 5], ["Products", 6], ["Medical use", 7], ["Other consequences", 9]];
+
+export const REC_LABEL = { legal: "Legal", partial: "Partly legal / tolerated", decrim: "Decriminalised", illegal: "Illegal" };
+export const MED_LABEL = { yes: "Legal", limited: "Limited", no: "None" };
+
+const AF = "Africa", AS = "Asia", EU = "Europe", NA = "North America", SA = "South America", OC = "Oceania";
+
+function c(id, num, name, region, s, rec = "illegal", med = "no", note) {
+  return { id, num, name, region, s, rec, med, note };
+}
+
+export const COUNTRIES = [
+  // ───────────── Africa ─────────────
+  c("DZ", 12, "Algeria", AF, [0, 0, 1, 0, 1, 0], "illegal", "no",
+    "Prison and a fine for personal use, with court-ordered treatment as an alternative; penalties for trafficking were raised in 2023."),
+  c("AO", 24, "Angola", AF, [1, 1, 2, 1, 1, 0], "illegal", "no",
+    "Possessing a small amount of liamba for personal use is a crime with up to three months in prison, which courts can suspend in favour of treatment."),
+  c("BJ", 204, "Benin", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Illegal for all purposes; possession is reported to carry two to five years in prison, with some informal tolerance outside the cities."),
+  c("BW", 72, "Botswana", AF, [1, 1, 2, 1, 1, 0], "illegal", "no",
+    "Under 60 g carries a fine of up to 20,000 pula or up to three years; most small cases end in fines."),
+  c("BF", 854, "Burkina Faso", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Possession for personal use is punishable by two months to two years in prison under the 1999 drug code."),
+  c("BI", 108, "Burundi", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Holding or consuming cannabis carries one to five years in prison under the Penal Code."),
+  c("CV", 132, "Cabo Verde", AF, [1, 1, 2, 1, 1, 0], "illegal", "no",
+    "Prohibited under a 1993 law; small personal amounts are reportedly handled leniently, but this entry rests on a single source."),
+  c("CM", 120, "Cameroon", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "A 'high-risk drug' under the 1997 law: possession is a crime and cultivation carries 10 to 20 years, though banga is widely grown."),
+  c("CF", 140, "Central African Republic", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Possession for personal use carries one to five years in prison under a 1997 law; enforcement is patchy outside Bangui."),
+  c("TD", 148, "Chad", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Possession carries one to five years in prison under a 2017 drug law."),
+  c("KM", 174, "Comoros", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Illegal, with long sentences reported. Cannabis was briefly legal here from 1975 to 1978."),
+  c("CG", 178, "Congo", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Illegal for all purposes; penalties for small amounts are not published in accessible sources."),
+  c("CD", 180, "DR Congo", AF, [1, 1, 1, 1, 1, 0], "illegal", "limited",
+    "Recreational use is illegal; a 2021 measure authorised cannabis for medical and scientific purposes under licence."),
+  c("CI", 384, "Côte d'Ivoire", AF, [1, 1, 2, 1, 1, 0], "illegal", "no",
+    "Possession is a crime; a 2022 law reframed drug use as a health matter and lets courts order treatment instead of prison."),
+  c("DJ", 262, "Djibouti", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Possession can bring a fine or up to about five years in prison; cannabis use is rare, khat being the local drug."),
+  c("EG", 818, "Egypt", AF, [0, 0, 1, 0, 1, 0], "illegal", "no",
+    "Hashish and bango are widely used, but the law is harsh: prison for possession, and large-scale trafficking can carry the death penalty."),
+  c("GQ", 226, "Equatorial Guinea", AF, [1, 1, 2, 1, 1, 0], "illegal", "no",
+    "Illegal on paper with long sentences, but smoked openly and rarely prosecuted among locals."),
+  c("ER", 232, "Eritrea", AF, [1, 1, 2, 1, 1, 0], "illegal", "no",
+    "Personal possession is reported to carry 6 to 12 months or a fine; little is known about practice."),
+  c("SZ", 748, "Eswatini", AF, [1, 1, 2, 1, 1, 0], "illegal", "no",
+    "Illegal, though 'Swazi Gold' is a major smallholder cash crop. Bills to license medical and hemp production have been debated for years."),
+  c("ET", 231, "Ethiopia", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Possession for personal use is reported to bring six months to five years; cultivation and sale up to fifteen."),
+  c("GA", 266, "Gabon", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Illegal; trafficking carries up to twenty years. No reliable figure for simple possession was found."),
+  c("GM", 270, "Gambia", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "First-time possession carries a fine of 50,000–100,000 dalasi or six months to a year."),
+  c("GH", 288, "Ghana", AF, [2, 1, 3, 1, 1, 0], "illegal", "limited",
+    "The 2020 Narcotics Control Commission Act treats personal use as a health matter (fine rather than prison). A 2023 amendment licenses low-THC cultivation for industrial and medicinal purposes."),
+  c("GN", 324, "Guinea", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Possession and use are offences; reported cases end in short or suspended sentences and fines."),
+  c("GW", 624, "Guinea-Bissau", AF, [1, 1, 2, 1, 1, 0], "illegal", "no",
+    "Personal possession carries up to three months or a fine; trafficking 2 to 12 years."),
+  c("KE", 404, "Kenya", AF, [0, 0, 1, 0, 1, 0], "illegal", "no",
+    "A 2022 amendment set extremely heavy penalties above a tiny personal-use amount; courts in practice impose far less."),
+  c("LS", 426, "Lesotho", AF, [1, 1, 2, 1, 1, 0], "illegal", "limited",
+    "First African country to license medical cannabis cultivation (2017), mostly for export. Personal use remains illegal although matekoane is widely grown."),
+  c("LR", 430, "Liberia", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Since 2023, personal use carries three to eighteen months and is bailable; dealing carries ten to twenty years."),
+  c("LY", 434, "Libya", AF, [0, 0, 1, 0, 1, 0], "illegal", "no",
+    "Strict prohibition with long prison sentences; a major transit route for Moroccan hashish."),
+  c("MG", 450, "Madagascar", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "A small quantity for personal use carries two to six months; rongony is nonetheless grown on a large scale."),
+  c("MW", 454, "Malawi", AF, [1, 1, 2, 1, 1, 0], "illegal", "limited",
+    "The 2020 Cannabis Regulation Act legalised cultivation for medical and industrial use. Recreational use of the famous 'Malawi Gold' remains illegal."),
+  c("ML", 466, "Mali", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Illegal under a 2001 law; penalties for small amounts are not clearly documented."),
+  c("MR", 478, "Mauritania", AF, [0, 0, 1, 0, 1, 0], "illegal", "no",
+    "Prison even for simple use, up to two years; a large share of prisoners are held for possession."),
+  c("MU", 480, "Mauritius", AF, [1, 0, 2, 0, 1, 1], "illegal", "limited",
+    "Strict prohibition, but since 2022 medical cannabis can be approved by specialist committees in public hospitals."),
+  c("MA", 504, "Morocco", AF, [1, 1, 2, 1, 2, 1], "illegal", "limited",
+    "Law 13-21 (2021) legalised cultivation for medical, cosmetic and industrial use in the Rif provinces. Recreational kif remains illegal but is widely tolerated; thousands of growers were pardoned in 2024."),
+  c("MZ", 508, "Mozambique", AF, [1, 1, 2, 1, 1, 0], "illegal", "no",
+    "Use and possession carry up to two years, with prison waivable for occasional users who accept treatment."),
+  c("NA", 516, "Namibia", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Prohibited under a 1971 Act; over 115 g is presumed dealing. Court challenges by cannabis activists have failed so far."),
+  c("NE", 562, "Niger", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Personal possession carries two months to two years and a fine."),
+  c("NG", 566, "Nigeria", AF, [0, 0, 1, 0, 1, 0], "illegal", "no",
+    "Possession carries long mandatory prison terms under the NDLEA Act, despite Nigeria being one of the world's largest consumer populations."),
+  c("RW", 646, "Rwanda", AF, [0, 0, 1, 0, 0, 0], "illegal", "limited",
+    "Since 2021 licensed producers can grow medical cannabis for export. Domestic use is heavily penalised."),
+  c("ST", 678, "São Tomé and Príncipe", AF, [1, 1, 2, 1, 1, 0], "illegal", "no",
+    "Personal possession is a minor offence; trafficking carries four to twelve years."),
+  c("SN", 686, "Senegal", AF, [1, 1, 1, 1, 1, 0], "illegal", "no",
+    "Personal possession carries two months to one year in prison; trafficking ten to twenty years since 2007."),
+  c("SC", 690, "Seychelles", AF, [1, 1, 2, 1, 1, 1], "illegal", "limited",
+    "Medical cannabis has been available by specialist prescription since 2020; otherwise illegal."),
+  c("SL", 694, "Sierra Leone", AF, [0, 0, 1, 0, 1, 0], "illegal", "no",
+    "Multi-year prison terms for possession under the 2008 Act, with exemplary sentences in recent drug cases."),
+  c("SO", 706, "Somalia", AF, [0, 0, 1, 0, 1, 0], "illegal", "no",
+    "Illegal under the penal code and sharia; outcomes depend on who controls the territory."),
… 314 more lines
src/data/details.js +4124 −0
@@ -0,0 +1,4124 @@
+/* Cannabis Freedom Index — per-segment explanations and sources
+ *
+ * DETAILS[countryId] = {
+ *   reviewed: "Month YYYY",
+ *   sources:  { key: [label, url] },
+ *   seg:      ten entries in SEGMENTS order, each { t: explanation, src: [source keys] }
+ * }
+ *
+ * Countries without an entry show only their summary note.
+ */
+
+export const DETAILS = {
+  CA: {
+    reviewed: "October 2026",
+    sources: {
+      justice: ["Department of Justice Canada — Cannabis legalization and regulation", "https://www.justice.gc.ca/eng/cj-jp/cannabis/"],
+      act: ["Cannabis Act (S.C. 2018, c. 16)", "https://laws-lois.justice.gc.ca/eng/acts/c-24.5/"],
+      hc: ["Health Canada — Cannabis laws and regulations", "https://www.canada.ca/en/health-canada/services/drugs-medication/cannabis/laws-regulations.html"],
+      bc: ["Government of British Columbia — Possession and purchasing limits", "https://www2.gov.bc.ca/gov/content/safety/public-safety/cannabis/possession-and-purchasing-limits"],
+    },
+    seg: [
+      { t: "Adults (18+, 19 or 21 in most provinces) may carry up to 30 g of dried cannabis in public, or its equivalent in other forms (for example 150 g of fresh cannabis or 7.5 g of concentrate). Federal law sets no cap at home, though some provinces add one. It must be 'legal cannabis' from a licensed source. Carrying more than 30 g in public is an offence punishable by up to five years less a day.", src: ["justice", "bc"] },
+      { t: "Up to four plants per residence — not per adult — grown from licensed seeds or seedlings. Quebec prohibits home growing entirely. Any fifth plant is an offence. A hard household cap and a required legal seed source keep this well short of gardening freedom.", src: ["justice", "act"] },
+      { t: "Adults may give up to 30 g of legal cannabis to another adult without payment. Any payment turns it into unlicensed sale, and giving to anyone under 18 carries up to 14 years.", src: ["justice"] },
+      { t: "Using is legal, but where is decided by provinces and cities. Some treat it like tobacco; others restrict it to private residences. Landlords and condo boards can ban smoking, and licensed consumption lounges remain rare.", src: ["hc", "bc"] },
+      { t: "Licensed shops and online ordering exist in every province and territory, privately run in some and government monopolies in others. Purchases are capped at 30 g per transaction. Only provincially authorised retailers may sell; everything else is a criminal market.", src: ["justice", "hc"] },
+      { t: "Anyone can apply for a federal cultivation or processing licence, including small 'micro' classes, and there is no cap on licence numbers. But security clearances, site requirements, excise stamps and compliance costs are heavy, and unlicensed commercial production carries up to 14 years.", src: ["act", "hc"] },
+      { t: "Flower, pre-rolls, oils, edibles, drinks, vapes, concentrates and topicals are all legal. Limits: edibles are capped at 10 mg THC per package, extracts at 1,000 mg per container, with plain packaging, health warnings and a near-total ban on promotion.", src: ["hc", "justice"] },
+      { t: "Any physician or nurse practitioner can authorise medical use with no list of qualifying conditions. Patients buy from federal licence holders by mail, or register with Health Canada to grow their own or designate a grower. Still a registration system, and not covered by public insurance.", src: ["hc"] },
+      { t: "Personal conduct inside the limits is not punished. Outside them the Act is severe: up to 14 years for unlicensed sale, production, export or supplying a minor, with ticketing for small overages.", src: ["justice", "act"] },
+      { t: "Drug-impaired driving law uses per-se THC blood limits, so a regular user can be over the limit without being impaired. Past simple-possession convictions can be cleared through a free, expedited record suspension — on application, not automatically. Admitting to cannabis use can still get a traveller refused at the US border.", src: ["justice", "hc"] },
+    ],
+  },
+
+  UY: {
+    reviewed: "October 2026",
+    sources: {
+      law: ["Ley N° 19.172 — Regulación y control del cannabis (IMPO)", "https://www.impo.com.uy/bases/leyes/19172-2013"],
+      ircca: ["IRCCA — Instituto de Regulación y Control del Cannabis", "https://www.ircca.gub.uy/"],
+      wiki: ["Wikipedia — Cannabis in Uruguay", "https://en.wikipedia.org/wiki/Cannabis_in_Uruguay"],
+    },
+    seg: [
+      { t: "Possession for personal use has not been a crime since 1974, and the 2013 law presumes up to 40 g is personal. There is no separate home limit. The cannabis is only 'legal' if it came through one of the three registered routes.", src: ["law", "wiki"] },
+      { t: "Registered residents may grow up to six flowering plants per household with an annual harvest of at most 480 g. Registration with the state regulator IRCCA is mandatory, and choosing home growing excludes you from pharmacies and clubs. Unregistered growing is an offence.", src: ["law", "ircca"] },
+      { t: "The law is built around personal or household use. Club harvests are shared among members, but there is no general right to give cannabis to other adults, and any supply outside the registered channels can be prosecuted as dealing.", src: ["law"] },
+      { t: "Smoking is allowed wherever tobacco is: not in enclosed public spaces, workplaces, schools or health facilities. Using in the street or a park is lawful, one of the most permissive rules anywhere. Working or driving under the influence is prohibited.", src: ["law", "wiki"] },
+      { t: "Only registered Uruguayan citizens and permanent residents aged 18+ can buy, at participating pharmacies, verified by fingerprint, up to 10 g a week (40 g a month) at a state-set price. Tourists are excluded. Each person must pick one route: pharmacy, club or home grow.", src: ["ircca", "wiki"] },
+      { t: "Cannabis for pharmacies is grown by a handful of companies chosen by state tender, on state-controlled land, with the state setting price and genetics. Membership clubs (15–45 members, up to 99 plants) are the only other producers. There is no open commercial licensing.", src: ["law", "wiki"] },
+      { t: "Pharmacies sell only a few state-specified flower varieties with capped THC — launched at very low potency and since raised, with the newest variety at up to about 20%. No edibles, concentrates or vapes in the legal market.", src: ["wiki", "ircca"] },
+      { t: "Medical cannabis is legal and a 2019 law aimed to widen access, but few registered products exist, they are expensive, and most patients rely on the adult-use channels or home-made preparations instead.", src: ["ircca", "wiki"] },
+      { t: "Registered users are left alone. Growing or selling outside the registry remains a criminal offence under the drug law, with prison sentences measured in years.", src: ["law"] },
+      { t: "Three state registries hold the identity of every legal buyer, grower and club member. Driving has a zero-tolerance THC rule, and employers may test for use at work. No criminal record arises from personal use.", src: ["law", "ircca"] },
+    ],
+  },
+
+  DE: {
+    reviewed: "October 2026",
+    sources: {
+      bmg: ["Bundesgesundheitsministerium — FAQ zum Cannabisgesetz", "https://www.bundesgesundheitsministerium.de/themen/cannabis/faq-cannabisgesetz"],
+      kcang: ["Konsumcannabisgesetz (KCanG) — full text", "https://www.gesetze-im-internet.de/kcang/"],
+      medcang: ["Medizinal-Cannabisgesetz (MedCanG) — full text", "https://www.gesetze-im-internet.de/medcang/"],
+    },
+    seg: [
+      { t: "Adults may carry up to 25 g in public and keep up to 50 g of dried cannabis at home. Holding 25–30 g in public (or 50–60 g at home) is an administrative offence; above that it is a crime. Applies to people who have lived in Germany for at least six months.", src: ["bmg", "kcang"] },
+      { t: "Up to three plants at a time per adult, at your own residence, protected from access by children. Yield above the 50 g home limit must be destroyed. A fourth plant is a criminal offence.", src: ["bmg", "kcang"] },
+      { t: "Not allowed. Home-grown cannabis may not be passed on to anyone, even for free, and club cannabis is for the member's own use only. Giving cannabis to another adult remains punishable — the main reason this segment scores so low.", src: ["bmg"] },
+      { t: "Public consumption is legal, with exceptions: not near minors, not in or within sight of schools, playgrounds, youth facilities and sports grounds, not in pedestrian zones between 7:00 and 20:00, and not inside or near cultivation associations.", src: ["bmg", "kcang"] },
+      { t: "There are no shops. The only non-medical source besides home growing is a non-profit cultivation association: up to 500 members, minimum three-month membership, at most 25 g a day and 50 g a month (30 g and max 10% THC for ages 18–21). Many regions still have few licensed associations.", src: ["bmg", "kcang"] },
+      { t: "Commercial recreational production is not permitted at all; associations must grow for their own members with member labour. Medical cultivation and import need a licence from the federal medicines agency.", src: ["kcang", "medcang"] },
+      { t: "Associations may hand out only flower and hashish. Edibles such as THC gummies or cookies remain banned, as do extracts for the adult-use channel.", src: ["bmg"] },
+      { t: "Cannabis is no longer a narcotic in law: any doctor can prescribe it on an ordinary prescription, dispensed by pharmacies, and statutory insurers cover it for serious illness after approval. Still prescription-only, and rules on remote prescribing have been tightened.", src: ["medcang", "bmg"] },
+      { t: "Within the limits nothing is punished; small overages are fines. Selling, giving away, or growing beyond three plants are crimes with prison of up to three years, more in aggravated cases.", src: ["kcang", "bmg"] },
+      { t: "Old convictions for conduct that is now legal can be deleted from the federal register on application. The driving limit is 3.5 ng/ml THC in blood serum, with zero tolerance for novice drivers and under-21s.", src: ["bmg"] },
+    ],
+  },
+
+  US: {
+    reviewed: "October 2026",
+    sources: {
+      ncsl: ["National Conference of State Legislatures — State cannabis laws", "https://www.ncsl.org/health/state-medical-cannabis-laws"],
+      norml: ["NORML — State-by-state laws", "https://norml.org/laws/"],
+      hh: ["Holland & Hart — Cannabis newsletter, April 2026 (federal rescheduling order)", "https://www.hollandhart.com/webfiles/BUZZ_Cannabis_Newsletter_April_Edition_2026.pdf"],
+      mm: ["Marijuana Moment — Which states could legalize in 2026", "https://www.marijuanamoment.net/which-states-most-likely-to-legalize-marijuana-in-2026/"],
+    },
+    seg: [
+      { t: "It depends entirely on the state. In roughly half the states adults 21+ may carry a capped amount, typically 1 to 2.5 oz (28–70 g). In prohibition states the same amount can mean arrest and jail. Under federal law, non-medical possession is still a Schedule I offence everywhere.", src: ["norml", "ncsl"] },
+      { t: "Most legal states allow home growing, usually six plants per adult with a household cap, but several — including Washington, New Jersey and Illinois for non-patients — ban it. In prohibition states growing a single plant can be a felony.", src: ["norml"] },
+      { t: "Legal states generally allow adults to give up to the possession limit (commonly one ounce) to another adult 21+ with no payment. Elsewhere any transfer counts as distribution.", src: ["norml"] },
+      { t: "Even in legal states use is almost always confined to private property; public consumption is a ticketable offence, landlords and hotels can ban it, and it is prohibited on all federal land. Only a few states license consumption lounges.", src: ["norml", "ncsl"] },
+      { t: "Legal states have licensed dispensaries, many with delivery, open to any adult 21+ including visitors, with per-transaction limits. Licence caps and local opt-outs leave large areas without shops, and nothing can cross state lines. About half the population has no legal adult-use store.", src: ["ncsl", "mm"] },
+      { t: "States issue commercial licences, some uncapped (Oklahoma, Oregon), many tightly capped and very expensive. In April 2026 state-licensed medical cannabis was moved to federal Schedule III; adult-use businesses remain Schedule I, with punitive federal tax treatment and limited banking.", src: ["hh", "ncsl"] },
+      { t: "The widest product range in the world in legal states: flower, edibles (typically capped at 100 mg THC per package), vapes, concentrates and beverages. Hemp-derived products are sold in most states, though federal and state rules on intoxicating hemp have been tightening.", src: ["norml", "hh"] },
+      { t: "40 states have medical programmes. Most require a doctor's certification for a listed condition, a state patient card and an annual fee. Federal rescheduling of state-licensed medical cannabis in 2026 acknowledged medical use, but it is still not prescribed or insured like ordinary medicine.", src: ["ncsl", "hh"] },
+      { t: "Hundreds of thousands of cannabis arrests still happen every year, concentrated in prohibition states where possession can bring jail and sale or cultivation long felony sentences. Federal trafficking penalties include mandatory minimums.", src: ["norml"] },
+      { t: "The heaviest collateral consequences of any country with legal markets: cannabis users are barred from buying firearms under federal law, non-citizens can be deported or refused entry, federally assisted housing can evict, and most employers may still test and fire. Expungement exists in some states only.", src: ["norml", "ncsl"] },
+    ],
+  },
+
+  MT: {
+    reviewed: "October 2026",
+    sources: {
+      cap628: ["Authority on the Responsible Use of Cannabis Act (Cap. 628)", "https://legislation.mt/eli/cap/628/eng"],
+      aruc: ["ARUC — Authority for the Responsible Use of Cannabis", "https://aruc.mt/"],
+      euda: ["EU Drugs Agency — Malta and Luxembourg plans for limited cultivation and use", "https://www.euda.europa.eu/news/2021/malta-and-luxembourg-announce-plans-limited-cultivation-and-use-cannabis_en"],
+      mt: ["MaltaToday — 39 fined for smoking cannabis in public", "https://www.maltatoday.com.mt/news/national/132682/39_fined_for_smoking_cannabis_in_public"],
+    },
+    seg: [
+      { t: "Adults 18+ may carry up to 7 g in public and keep up to 50 g of dried cannabis at home. Between 7 g and 28 g in public is not a crime but goes to a tribunal with a fine of €50–100. Above 28 g, ordinary drug law applies.", src: ["cap628", "euda"] },
+      { t: "Up to four plants per household (not per adult), which must not be visible from outside. The dried harvest kept at home may not exceed 50 g.", src: ["cap628", "euda"] },
+      { t: "The law provides for personal use and for distribution by licensed associations to their own members. There is no explicit right to give cannabis to another adult, and supply outside an association remains trafficking.", src: ["cap628"] },
+      { t: "Private only. Consuming in public is prohibited and fined €235; consuming in front of a minor is fined €300–500. Associations may not allow consumption on their premises.", src: ["mt", "cap628"] },
+      { t: "No shops. The only legal source besides home growing is a licensed non-profit Cannabis Harm Reduction Association: maximum 500 members, residents only, one association per person, at most 7 g a day and 50 g a month. Tourists have no legal source.", src: ["aruc", "cap628"] },
+      { t: "Only licensed non-profit associations may grow, for their own members, under ARUC supervision and away from schools and youth centres. For-profit recreational production is not allowed. Medical production is separately licensed.", src: ["aruc"] },
+      { t: "Associations distribute dried flower (and seeds). Edibles and concentrates are not part of the regulated adult-use channel. Advertising is banned.", src: ["aruc", "cap628"] },
+      { t: "Legal since 2018 on prescription from a registered doctor, with an approval card for the patient, dispensed in pharmacies. Products are limited and paid out of pocket.", src: ["euda"] },
+      { t: "Among Europe's mildest for users: conduct inside the limits is unpunished, and small overages or public use are handled by a tribunal with fines rather than courts. Trafficking still carries prison.", src: ["cap628", "mt"] },
+      { t: "People convicted of simple possession before the reform can have the conviction struck from their record. Association membership is recorded, though privacy rules limit access. Driving under the influence remains an offence.", src: ["cap628", "aruc"] },
+    ],
+  },
+
+  JM: {
+    reviewed: "October 2026",
+    sources: {
+      fact: ["Ministry of Justice — Dangerous Drugs (Amendment) Act 2015 fact sheet", "https://ncst.gov.jm/wp-content/uploads/2015/09/Ganja-FACT-SHEET-Amendment-to-Dangerous-Drug-ACT.pdf"],
+      cla: ["Cannabis Licensing Authority", "https://www.cla.org.jm/"],
+      facts2: ["The facts about ganja in Jamaica (Jamaica Observer, via TNI)", "https://ungassondrugs.org/en/site_content/item/8143-the-facts-about-ganja-in-jamaica.html"],
+    },
+    seg: [
+      { t: "Not actually legal: possessing up to 2 oz (56 g) is a ticketable infraction with a fixed J$500 penalty — no arrest, no court, no criminal record. Above 2 oz it is still a criminal offence. Rastafari adherents and holders of a medical permit may possess lawfully.", src: ["fact"] },
+      { t: "Each household may legally grow up to five ganja plants on its premises; where several households share a property, each gets five. More than that is an offence unless licensed.", src: ["fact"] },
+      { t: "There is no provision for gifting. Supplying ganja to another person outside the licensed medical system or sacramental use remains dealing.", src: ["fact", "facts2"] },
+      { t: "Smoking in public places is prohibited on the same terms as tobacco, with a J$500 ticket. It is lawful at a private residence that is not a workplace, in licensed premises, and at places registered for Rastafari worship or exempt events.", src: ["fact"] },
+      { t: "Licensed 'herb houses' sell to people with a medical recommendation — obtainable from a local practitioner. Visitors with a prescription from home can buy a permit to purchase up to 2 oz at a time. Formally a medical market, in practice accessible; the informal market remains far larger.", src: ["fact", "cla"] },
+      { t: "The Cannabis Licensing Authority issues cultivator, processor, transporter, retail and research licences in tiers by size. Fees, security rules and banking problems have kept many traditional small growers outside the legal industry.", src: ["cla"] },
+      { t: "Licensed retailers offer flower, oils, edibles and other preparations for medical or therapeutic purposes. Hemp is licensed separately.", src: ["cla", "fact"] },
+      { t: "Any registered medical practitioner can recommend ganja for medical or therapeutic purposes, with no fixed list of conditions. Patients buy from licensed herb houses.", src: ["fact", "cla"] },
+      { t: "Small-scale possession and public smoking mean a ticket. Possession over 2 oz, unlicensed cultivation beyond five plants, dealing and export remain criminal with fines and prison. Under-18s found with ganja are referred to counselling.", src: ["fact"] },
+      { t: "Tickets leave no criminal record, and earlier convictions for small quantities can be expunged. Unpaid tickets can lead to a court order for community service.", src: ["fact", "facts2"] },
+    ],
+  },
+
+  ZA: {
+    reviewed: "October 2026",
+    sources: {
+      act: ["Cannabis for Private Purposes Act 7 of 2024 — full text", "https://lawlibrary.org.za/akn/za/act/2024/7/eng@2024-06-03"],
+      idpc: ["IDPC — South African president assents to the Cannabis for Private Purposes Bill", "https://idpc.net/news/2024/06/south-african-president-assents-to-the-cannabis-private-purposes-bill"],
+      adams: ["Adams & Adams — Cannabis for Private Purposes Act signed into law", "https://www.adams.africa/adams-news/sa-cannabis-for-private-purposes-act-signed-into-law/"],
+      sahpra: ["SAHPRA — South African Health Products Regulatory Authority", "https://www.sahpra.org.za/"],
+    },
+    seg: [
+      { t: "Adults 18+ may possess cannabis for private purposes, kept out of public view — at home, or concealed while carrying it. The Act itself sets no gram limit; maximum quantities are left to regulations, so for now there is no fixed number. The right rests on the Constitutional Court's 2018 privacy ruling.", src: ["act", "idpc"] },
+      { t: "Adults may grow cannabis in a private place for their own use, with no licence or registration. As with possession, the number of plants is left to regulations rather than fixed in the Act. Plants must not be accessible to children or visible to the public.", src: ["act", "adams"] },
+      { t: "Any exchange for payment or other reward is 'dealing' and remains a crime. Sharing between adults in private without payment is not clearly protected by the Act, leaving it a grey area.", src: ["act", "adams"] },
+      { t: "Legal in a private place only. Smoking or consuming in public is an offence, as is use in the presence of a child or of a non-consenting adult.", src: ["act"] },
+      { t: "There is no legal way to buy cannabis for adult use — the Act deliberately excludes trade. Private 'grow clubs' and dispensaries operate in a legal grey zone and are periodically raided.", src: ["adams", "idpc"] },
+      { t: "No commercial recreational licensing exists. The health regulator licenses cultivation of medical cannabis, largely for export, and hemp permits are issued separately. A commercial framework has been promised but not enacted.", src: ["sahpra", "adams"] },
+      { t: "Whatever you make at home for yourself is lawful; nothing may be sold. Low-dose CBD products are available over the counter; THC products are not legally sold outside the medical route.", src: ["sahpra", "act"] },
+      { t: "Medical access is possible but narrow: a doctor must apply to the regulator for permission to prescribe an unregistered cannabis medicine for a named patient. The Act also provides for cannabis prescribed to a child.", src: ["sahpra", "act"] },
+      { t: "Private adult conduct is not punished. Dealing remains a criminal offence with prison terms, and smoking in public or around children is an offence. Children caught with cannabis are diverted rather than prosecuted.", src: ["act", "idpc"] },
… 3966 more lines
src/layouts/Base.astro +62 −0
@@ -0,0 +1,62 @@
+---
+import "../styles/global.css";
+import Leaf from "../components/Leaf.astro";
+
+const { title, description } = Astro.props;
+const base = import.meta.env.BASE_URL;
+const fullTitle = title ? `${title} — Cannabis Freedom Index` : "Cannabis Freedom Index";
+---
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>{fullTitle}</title>
+  <meta name="description" content={description}>
+  <link rel="icon" type="image/svg+xml" href={`${base}favicon.svg`}>
+  <link rel="preconnect" href="https://fonts.googleapis.com">
+  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
+  <script is:inline>
+    // Set the theme before first paint to avoid a flash.
+    (function () {
+      var saved = null;
+      try { saved = localStorage.getItem("cfi-theme"); } catch (e) {}
+      document.documentElement.dataset.theme = saved || (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark");
+    })();
+  </script>
+</head>
+<body>
+  <header class="topbar">
+    <a class="brand" href={base}>
+      <span class="brand-mark" aria-hidden="true">
+        <Leaf size={24} />
+      </span>
+      Cannabis Freedom Index
+    </a>
+    <nav class="nav">
+      <a href={`${base}#map`}>Map</a>
+      <a href={`${base}#rankings`}>Rankings</a>
+      <a href={`${base}#methodology`}>Methodology</a>
+      <a href={`${base}compare/`}>Compare</a>
+    </nav>
+    <button class="icon-btn" id="themeToggle" aria-label="Switch colour theme" title="Switch colour theme">
+      <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M12 3a9 9 0 1 0 9 9c0-.5 0-.9-.1-1.4A5.5 5.5 0 0 1 13.4 3.1 9 9 0 0 0 12 3z" fill="currentColor"/></svg>
+    </button>
+  </header>
+
+  <slot />
+
+  <footer class="footer">
+    <p>Cannabis Freedom Index · Informational only, not legal advice · Map data © Natural Earth</p>
+  </footer>
+
+  <script is:inline>
+    document.getElementById("themeToggle").addEventListener("click", function () {
+      var root = document.documentElement;
+      root.dataset.theme = root.dataset.theme === "light" ? "dark" : "light";
+      try { localStorage.setItem("cfi-theme", root.dataset.theme); } catch (e) {}
+    });
+  </script>
+</body>
+</html>

src/lib/countries.js +25 −0
@@ -0,0 +1,25 @@
+// Derived data: index scores, ranks and averages computed once at build time.
+import { SEGMENTS, COUNTRIES } from "../data/data.js";
+
… 23 more lines
src/lib/facts.js +58 −0

Not shown: a picture, a generated file, or past the length shown per change.

src/lib/tiers.js +13 −0

Not shown: a picture, a generated file, or past the length shown per change.

src/pages/api/[id].json.js +23 −0

Not shown: a picture, a generated file, or past the length shown per change.

src/pages/compare.astro +227 −0

Not shown: a picture, a generated file, or past the length shown per change.

src/pages/country/[id].astro +33 −0

Not shown: a picture, a generated file, or past the length shown per change.

src/pages/index.astro +380 −0

Not shown: a picture, a generated file, or past the length shown per change.

src/styles/global.css +389 −0

Not shown: a picture, a generated file, or past the length shown per change.

    ↑↓ to move, ↵ to open.