Cannabis Freedom Index

Changelog 2125f61

add more territories

41 history entries added2 history entries edited3 country profiles edited19 places added16 files of code, design or docs +3,883 −107 lines

Score changes 19 places

  1. Northern Mariana Islands new53
  2. Guam new43
  3. US Virgin Islands new33
  4. Curaçao new25
  5. Sint Maarten new24
  6. Aruba new21
  7. Bermuda new19
  8. Jersey new19
  9. Puerto Rico new14
  10. Guernsey new13
  11. Isle of Man new13
  12. Faroe Islands new12
  13. Gibraltar new12
  14. Greenland new12
  15. Cayman Islands new11
  16. French Polynesia new11
  17. British Virgin Islands new11
  18. Macau new9
  19. Hong Kong new7

History changes.yaml

Added 41

Edited 2

Countries and states 22 files

Canada countries/ca/index.yaml

context › products › text

Flower, pre-rolls, oils, edibles, drinks, vapes, concentrates and topicals are all legal. … g per container, with plain packaging, health warnings and a near-total ban on promotion. Health Canada's classification guidance treats ingestible products marketed for mixing with food or drinks as edibles, subject to the 10 mg limit.

context › products › sources

hc, justice, classification

context › medical › text

Any physician or nurse practitioner can authorise medical use with no list of qualifying c … to grow their own or designate a grower. Still a registration system, and not covered by ordinary public health insurance. A separate Veterans Affairs benefit reimburses eligible patients, with its ceiling reduced from C$8.50 to C$6 per dried gram or equivalent for orders from 1 April 2026.

context › medical › sources

hc, vac26

sources

Health Canada — Classification of ingestible cannabis products, edible cannabis and cannabis extracts Veterans Affairs Canada — Cannabis for Medical Purposes, Revised Reimbursement Policy 11 unchanged

Nova Scotia countries/ca/ns.yaml

segments › enforcement

Nothing within the limits. Provincial offences, such as consuming in the wrong place, are ticketed; unlicensed selling is a federal crime with up to 14 years. Provincial fine increases took effect on 25 June 2026: buying outside the NSLC brings C$600–5,000, illegal individual sales C$5,000–25,000 and business sales C$15,000–50,000. New offences also target promotion of illegal sales and landlords allowing sales on their property.

sources

Government of Nova Scotia — Higher Fines, New Offences Support Safe, Legal Cannabis Market (25 June 2026) 1 unchanged

Virginia countries/us/va.yaml

summary

Possession and four plants per household have been legal since 2021. After years of vetoes, a retail law was enacted in 2026; adult-use shopssales are due to open inscheduled for 1 July 2027. Until then medical dispensaries are the only legal place to buy.

segments › access

No adult-use shops yet. Earlier bills were vetoed, butThe Cannabis Control Authority schedules retail sales for 1 July 2027; its timeline records an initial regulation draft in September 2026 the legislature passed a retail framework and the new governor signed sales into law, with shops due to open duringschedules effective rules for January 2027. Medical dispensaries areremain the only legal placeroute to buy for nowcannabis. Separately, from 15 August 2026 hemp products may contain no more than 2 mg total THC per package; the former 25-to-1 CBD-to-THC exception was removed.

sources

Virginia Cannabis Control Authority — Retail Marijuana Market, implementation timeline Virginia Cannabis Control Authority — Virginia's New Marijuana and Hemp Laws: What You Need to Know (1 July 2026) 2 unchanged

Code, design and documents 16 files

README.md +36 −1
@@ -5,4 +5,12 @@ A 0–100 score of personal cannabis freedom for every country — what you may
 **Live site:** https://cannabisfreedom.fyi/
 
+## What's on the site
+
+- **The index:** 214 countries, territories and dependencies scored, with state-by-state scores for the United States, Canada and Australia, a world map that can be coloured by any segment or context section (seeds and clones, clubs, driving, visitors, cannabis use), rankings and side-by-side comparison.
+- **History:** the laws, rulings and treaties that moved each score since 1900, with a yearly score for every place rebuilt from them. More than 500 events have a full article.
+- **Culture:** a cultural atlas of words for the plant, traditions and the people who changed the law.
+- **State of Cannabis:** a yearly, pseudonymous community survey of how people use, grow and think about cannabis.
+- **Sources:** every source the index cites, including the books it draws on.
+
 ## How the score works
 
@@ -40,8 +48,27 @@ Built with [Astro](https://astro.build). Pushing to `main` deploys to GitHub Pag
 | `segments.yaml` | The six segments, their weights and level descriptions |
 | `changes.yaml` | The History page: laws and events that moved a country's score, with the scores before, plus treaties; the home page shows the latest four |
+| `articles/` | Longer articles on single history entries, one Markdown file each, named after the entry |
+| `culture/` | The cultural atlas: `language/`, `traditions/` and `people/`, one Markdown file per entry |
 | `prices.yaml` | Sourced flower prices by country, date and market (street or medical) |
+| `survey/` | The State of Cannabis questionnaire (`2026.yaml`) and its published, counted results |
 | `src/` | The website: pages, components, styles |
 | `docs/DATA-MAINTENANCE.md` | How the country profiles and the history are checked and extended, and what is still open |
-| `scripts/` | Helpers for that work: apply a batch of history edits, list what still needs a source |
+| `docs/CULTURE.md` | What belongs in each part of the cultural atlas and how to write an entry |
+| `docs/BOOKS.md`, `docs/books.yaml` | How the research books are read and cited, and the catalogue of what has been read; the books themselves sit in `books/`, which is not in Git |
+| `docs/research/` | Dated research notes behind new entries and changes |
+| `source-archive.json` | Internet Archive copies of the cited sources |
+| `scripts/` | Helpers for that work: see below |
+
+Helper scripts, run from the project root:
+
+| Command | What |
+|---|---|
+| `node scripts/data-status.cjs` | Where the data stands: unchecked history entries, estimates, thin profiles, sections not yet researched |
+| `node scripts/history-batch.cjs` | Apply a batch of history edits from a JSON file |
+| `npm run articles:backlog` | Which history entries most deserve an article next |
+| `npm run books:status` / `books:check` | Reading status of the research books; `books:report` rewrites `docs/BOOK-RESEARCH.md` |
+| `node scripts/archive-sources.cjs` | Save new sources to the Internet Archive and update `source-archive.json` |
+| `node scripts/book-covers.cjs` | Fetch covers for the books listed on the Sources page |
+| `node scripts/survey-collect.cjs` | Decrypt and count submitted survey answers into `survey/results-<year>.json` (needs the project's secret key) |
 
 The build checks every data file against a schema (`src/content.config.ts`) and fails with a clear message if, say, a score is out of range or a segment cites a source that isn't defined.
@@ -69,4 +96,6 @@ Please include a source link or a complete bibliographic citation. Or just [open
 ### Contributing flower prices
 
+Prices are still incomplete, so the site hides them unless it is built with `PUBLIC_SHOW_PRICES=true` (see `.env.example`).
+
 Add sourced per-gram observations to `prices.yaml`. Only two markets are used: `street` for
 illicit or non-prescribed flower, and `medical` for prescribed flower. Keep the original currency,
@@ -80,8 +109,14 @@ example, source-quality rules, currency conversion and validation checklist.
 Scores are editorial estimates, not legal advice. Check current local law before travelling with, buying or growing cannabis.
 
+## Privacy
+
+The site uses [Plausible](https://plausible.io/) for visitor statistics: no cookies and no personal data. Survey answers are signed with a key made in the respondent's browser and encrypted so that only the respondent and the project can read them; only counts are published.
+
 ## Data for research
 
 - `/data/panel.csv`: a score for every place in every year since 1900, with the six segment scores, rebuilt from the history. The `basis` column separates recorded years from assumptions.
 - `/data/history.csv`, `/data/scores.csv`: every history entry with its sources, and today's scores with ISO numeric codes for joining.
+- `/data/prices.csv` and `/data/survey.json`: the sourced flower prices, and the counted survey results.
+- Every file is also available as JSON (`/data/panel.json`, `/data/scores.json`, …).
 - `source-archive.json`: copies of the sources in the Internet Archive, kept up to date with `node scripts/archive-sources.cjs`.
 

docs/DATA-MAINTENANCE.md +28 −0
@@ -368,4 +368,19 @@ still require a URL.
 - **`country`** may also be a disputed territory (`xa`, `xn`, `xs`, `xt`). Its entries show on its
   page and in the history list, but its scored changes never move the world average.
+- **Dependencies with laws of their own** have their own profiles where they matter enough:
+  Jersey (`je`), Guernsey (`gg`), the Isle of Man (`im`), Bermuda (`bm`), the Cayman Islands
+  (`ky`), Gibraltar (`gi`) and the British Virgin Islands (`vg`) under the UK, and Greenland (`gl`)
+  and the Faroe Islands (`fo`) under Denmark, French Polynesia (`pf`) under France, and Puerto Rico
+  (`pr`), Guam (`gu`), the Northern Mariana Islands (`mp`) and the US Virgin Islands (`vi`) under
+  the United States. A profile marks this with
+  `dependency: {of: gb, kind: "Crown Dependency", note: …}`;
+  like a disputed territory it is scored and drawn on the map but takes no rank and stays out of
+  the averages. Their history entries use their own code.
+- **A place without a profile of its own** goes under the country that answers for it
+  internationally: the Falklands and the other British Overseas Territories under `gb`; French
+  overseas territories such as New Caledonia under `fr`; American Samoa under `us`. Leaving `country` out files the entry as
+  International, which is only for treaties, UN decisions and other events that belong to no one
+  country. Such entries are normally context entries: they must not move the profile's scores.
+  When a place has gathered enough to deserve a profile, give it one as above and move its entries.
 - **`checked: true`** means: the date and the event were compared with a source that was read.
   A search-result summary is not a read source (see "What went wrong").
@@ -924,4 +939,17 @@ drugs. No articles were changed; see the research note for source limits.
 ## Recent law-news scan (12 October 2026)
 
+A [17 October follow-up](research/law-news-2026-10-17.md) adds four official-source
+context entries for Virginia's hemp cap and retail-rule draft, Jersey's planned
+prescriber oversight, and Bermuda's medical/hemp timetable. Virginia's profile
+now gives the scheduled retail start date and hemp restriction. Announcements
+and drafts remain distinguished from operating rules; scores were preserved.
+The build passed with 3,102 pages.
+
+The Canada follow-up in the same note adds Nova Scotia's June fine increases,
+the April veterans' reimbursement reduction, and the earlier commercial-edible
+classification guidance raised by a cooking-policy lead. Canada and Nova Scotia
+profiles were updated together. The guidance is dated to its verified September
+2024 announcement, not presented as a new 2026 cooking ban. Scores were preserved.
+
 Added five primary-sourced context events: Ireland's October medical-access
 review timetable, Canada's October regulatory commencement and September

docs/research/law-news-2026-10-17.md +87 −0
@@ -0,0 +1,87 @@
+# Cannabis policy scan — 17 October 2026
+
+Four gaps were added to `changes.yaml` after reading official sources. The scan
+looked for recent developments and checked existing coverage before adding records;
+it is not an exhaustive worldwide audit. The repository's database is its YAML
+data, rather than a separate SQL store.
+
+| Event date | Jurisdiction | Addition and legal stage |
+| --- | --- | --- |
+| 12 October 2026 | Jersey | Ministerial answer plans legislation overseeing medical cannabis prescribers, expected in early 2027; not enacted rules. |
+| September 2026 | Virginia, US | Regulator timeline records initial retail-rule draft; sales scheduled for 1 July 2027. |
+| 15 August 2026 | Virginia, US | Regulator confirms removal of the CBD-ratio exception to its 2 mg total-THC hemp-package cap. |
+| 23 July 2026 | Bermuda | Minister sets Q4 target for initial medical/hemp reforms; wider adult-use work takes longer. |
+
+## Sources read and limits
+
+- [Jersey WQ.201/2026, original one-page PDF](https://www.statesassembly.je/getattachment/0ca87632-b6c1-4e9c-9d99-80e90eaef827/WQ-201-2026.pdf?ext=.pdf&lang=en-GB).
+  The answer date, rather than the question's 5 October submission, is used.
+  It does not establish passage, final prescribing requirements or commencement.
+- [Virginia regulator timeline](https://cca.virginia.gov/retailmarijuanamarket).
+  September draft release is recorded only to month precision. Later milestones
+  remain scheduled. The linked SharePoint draft required authentication and was
+  not read; no individual draft provisions are asserted.
+- [Virginia regulator implementation notice](https://cca.virginia.gov/news/virginias-new-marijuana-and-hemp-laws-what-you-need-to-know).
+  Uses the stated August effective date, not the July publication date. The HB30
+  legislative link returned a JavaScript shell; this addition relies on the
+  regulator's express implementation guidance, not a claimed full statute review.
+- [Bermuda Senate statement](https://www.gov.bm/news/advancing-public-safety-across-the-ministry-of-national-security).
+  The Q4 target is an announcement, not evidence that implementation has occurred.
+  No subsequent commencement was established in this scan.
+
+Jersey and Bermuda have no standalone country profile here. Their entries have
+no `country`, following the existing Cayman convention; neither is assigned to
+the UK's domestic score. Virginia's entries belong to the US context timeline,
+with its state profile updated together. All four entries omit `before`.
+Scores remain unchanged: the new material establishes a hemp-market restriction,
+draft rules and planned reforms, not a change to the scored adult flower rights.
+
+Already-covered Canada, Ireland, NSW and Cayman developments were not duplicated.
+A New Zealand controlled-drug administration amendment was screened but not added:
+the material retrieved did not establish a sufficiently specific cannabis change.
+Virginia's September medical-dispensary opening was read as an access lead but
+not added as a separate policy change.
+
+## Validation
+
+`npm run build` passed (3,102 pages). History IDs are unique, all four additions
+are checked context entries without score baselines, and Virginia's six scores
+match their pre-edit values. The US score-chain check reports zero flagged steps.
+Generated HTML contains all four new history pages and their source links;
+Virginia's page contains the new sales date, hemp cap and regulator citation.
+`git diff --check` passed. Pre-existing README and source-archive edits were
+preserved. No articles were changed.
+
+## Canada follow-up: commercial edibles and two 2026 changes
+
+The user recalled a cooking-related story and narrowed it to commercial edibles.
+The closest verified match is Health Canada's ingestible-product classification
+guidance. This remains a possible match, not a confirmed identification of the
+story. No new home-cooking ban was established.
+
+Three additional records were saved:
+
+| Event date | Record | Source and dating |
+| --- | --- | --- |
+| 3 September 2024 | Expanded public classification guidance | [Health Canada industry announcement](https://www.canada.ca/en/health-canada/services/drugs-medication/cannabis/industry-licensees-applicants/updates-cannabis-industrial-hemp.html) dates inclusion of the policy; it was shared with licence holders in December 2023. The [current guidance](https://www.canada.ca/en/health-canada/services/cannabis-regulations-licensed-producers/classification-edible-cannabis.html) carries 18 November 2025 page metadata, which is not treated as first commencement. |
+| 25 June 2026 | Nova Scotia fine increases and new offences | [Provincial announcement](https://news.novascotia.ca/en/2026/06/25/higher-fines-new-offences-support-safe-legal-cannabis-market) explicitly confirms proclamation that day. Its linked statute PDF returned a rejection page; penalty details rely on the government's implementation notice. |
+| 1 April 2026 | Veterans Affairs reimbursement reduction | [Official FAQ](https://www.veterans.gc.ca/en/contact-us/help/faqs/cannabis-medical-purposes-revised-reimbursement-policy), especially questions 8 and 13–15, separates reimbursement from retail prices, authorised quantities and eligible product forms. |
+
+Canada's product context now explains the food-mixing classification, and its
+medical context acknowledges the separate veterans' benefit. Nova Scotia's
+enforcement section now records the provincial fines. All three additions omit
+`before`: classification guidance clarifies existing product rules, the benefit
+does not alter adult-use rights, and the provincial entry follows the federation
+context convention. Existing national and provincial scores were preserved.
+
+The commercial guidance is recorded under its verified earlier announcement,
+not presented as a newly adopted 2026 rule. The October industry notice about
+administrative monetary penalties links to guidance carrying July metadata;
+no fresh penalty law was inferred from that notice. The previously recorded
+March 2025 streamlining reform was not duplicated.
+
+Follow-up validation: `npm run build` passed (3,105 pages). History IDs are unique,
+all three additions are checked context entries, and Canada/Nova Scotia scores
+match their pre-edit values. Canada's score-chain check reports zero flagged
+steps. Generated HTML contains the three new history pages and the updated
+profiles with citations. `git diff --check` passed. No articles were changed.

source-archive.json +466 −70

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

src/components/CountryArticle.astro +4 −4
@@ -91,6 +91,6 @@ const culture = sub ? [] : cultureOf(c);
         {sub
           ? <p class="sub">#{sub.rank} of {group.units.length} in {c.name}<br>would rank #{sub.worldRank} among countries</p>
-          : c.disputed
-            ? <p class="sub">Not ranked: disputed territory<br>would rank #{c.worldRank} among countries</p>
+          : c.territory
+            ? <p class="sub">Not ranked: {c.territory.label}<br>would rank #{c.worldRank} among countries</p>
             : <p class="sub">#{c.rank} of {countries.length} worldwide<br>#{c.regionRank} of {c.regionCount} in {c.region}</p>}
       </div>
@@ -113,6 +113,6 @@ const culture = sub ? [] : cultureOf(c);
       </div>
     )}
-    {c.disputed && !sub && (
-      <p class="c-disputed"><strong>Disputed territory.</strong> {c.disputed.note} See also <a href={`${base}country/${c.disputed.country.slug}/`} data-country={c.disputed.country.id}>{c.disputed.country.flag} {c.disputed.country.name}</a>.</p>
+    {c.territory && !sub && (
+      <p class="c-disputed"><strong>{c.territory.kind}.</strong> {c.territory.note} See also <a href={`${base}country/${c.territory.country.slug}/`} data-country={c.territory.country.id}>{c.territory.country.flag} {c.territory.country.name}</a>.</p>
     )}
     <h2 class="d-h">In short <span><a href={`${base}can-i/`}>all countries →</a></span></h2>

src/components/HomePage.astro +2 −2
@@ -367,5 +367,5 @@ const columnGroups = [["Shown by default", BASE_COLUMNS], ...["Score segments",
                   {...Object.fromEntries(EXTRA_COLUMNS.map((col) => [`data-${col.key}`, col.sort(c)]))}>
                 <td class="num">{c.rank ?? "–"}</td>
-                <td><a class="cname" href={`${base}country/${c.slug}/`}><span class="flag">{c.flag}</span>{c.name}{trendOf(c) && ((t) => <span class={`trend ${t.delta > 0 ? "up" : "down"}`} title={`${t.delta > 0 ? "+" : "−"}${Math.abs(t.delta)} ${Math.abs(t.delta) === 1 ? "point" : "points"} since ${t.since}. Latest change: ${t.last.title} (${changeDate(t.last)})`}>{t.delta > 0 ? "▲" : "▼"}{Math.abs(t.delta)}</span>)(trendOf(c))}</a>{c.disputed && <span class="tag-disputed" title={c.disputed.note}>disputed</span>}</td>
+                <td><a class="cname" href={`${base}country/${c.slug}/`}><span class="flag">{c.flag}</span>{c.name}{trendOf(c) && ((t) => <span class={`trend ${t.delta > 0 ? "up" : "down"}`} title={`${t.delta > 0 ? "+" : "−"}${Math.abs(t.delta)} ${Math.abs(t.delta) === 1 ? "point" : "points"} since ${t.since}. Latest change: ${t.last.title} (${changeDate(t.last)})`}>{t.delta > 0 ? "▲" : "▼"}{Math.abs(t.delta)}</span>)(trendOf(c))}</a>{c.territory && <span class="tag-disputed" title={`${c.territory.kind}. ${c.territory.note}`}>{c.territory.tag}</span>}</td>
                 <td class="hide-sm" data-col="region">{c.region}</td>
                 <td class="num-col hide-md" data-col="users" title={useNote(c)}>{c.use.estimate && <span class="approx">~</span>}{compact(c.use.users)}</td>
@@ -503,5 +503,5 @@ const columnGroups = [["Shown by default", BASE_COLUMNS], ...["Score segments",
     const byNum = new Map(COUNTRIES.filter((c) => c.num != null).map((c) => [c.num, c]));
     // Map features that inherit another entry's score (atlas name or numeric id → country id).
-    const ALIASES = { Kosovo: "XK", Somaliland: "XS", "N. Cyprus": "XN", 304: "DK" };
+    const ALIASES = { Kosovo: "XK", Somaliland: "XS", "N. Cyprus": "XN" };
 
     // ── Map ───────────────────────────────────────────────

src/content.config.ts +5 −0
@@ -59,4 +59,9 @@ const countries = defineCollection({
       // name of the country it is internationally regarded as part of.
       disputed: z.object({ part_of: z.string().regex(/^[a-z]{2}$/), note: z.string().min(1) }).strict().optional(),
+      // A dependency with its own laws, outside the country that answers for it internationally
+      // (a Crown Dependency or an overseas territory). Shown and scored like a disputed territory:
+      // a page, no rank, no part in the averages. `of` is that country's file name; `kind` is how
+      // the place is described ("Crown Dependency", "British Overseas Territory").
+      dependency: z.object({ of: z.string().regex(/^[a-z]{2}$/), kind: z.string().min(1), note: z.string().min(1) }).strict().optional(),
       flag: z.string().optional(), // an emoji to show when the two-letter code has no flag of its own
       // Population by year: a snapshot every ten years from 1900 (Our World in Data) and the

src/lib/countries.js +14 −7
@@ -65,4 +65,9 @@ const everyPlace = (await getCollection("countries"))
       flag: data.flag ?? flagOf(id),
       disputed: data.disputed ?? null, // { part_of, note } for a territory that is not a recognised state
+      dependency: data.dependency ?? null, // { of, kind, note } for a Crown Dependency or overseas territory
+      // what an unranked place is, for both kinds: { kind (to start a sentence), label (inside one),
+      // tag, note, of (file name), country (set below) }
+      territory: data.disputed ? { kind: "Disputed territory", label: "disputed territory", tag: "disputed", note: data.disputed.note, of: data.disputed.part_of }
+        : data.dependency ? { kind: data.dependency.kind, label: data.dependency.kind, tag: "dependency", note: data.dependency.note, of: data.dependency.of } : null,
       num: data.iso_numeric ?? null,
       populationByYear: Object.entries(data.population).map(([y, v]) => [+y, v]).sort((a, b) => a[0] - b[0]),
@@ -88,8 +93,9 @@ const everyPlace = (await getCollection("countries"))
   .sort((a, b) => b.raw - a.raw || a.name.localeCompare(b.name));
 
-// The index ranks recognised countries. Disputed territories are scored the same way and shown
-// alongside, but they take no rank and do not count towards any average.
-export const countries = everyPlace.filter((c) => !c.disputed);
-export const territories = everyPlace.filter((c) => c.disputed);
+// The index ranks recognised countries. Disputed territories and dependencies with laws of their
+// own are scored the same way and shown alongside, but they take no rank and do not count towards
+// any average.
+export const countries = everyPlace.filter((c) => !c.territory);
+export const territories = everyPlace.filter((c) => c.territory);
 export const places = everyPlace; // both, by score: for pages, the map and the table
 countries.forEach((c, i) => (c.rank = i + 1));
@@ -97,6 +103,7 @@ territories.forEach((t) => {
   t.rank = null;
   t.worldRank = 1 + countries.filter((c) => c.raw > t.raw).length; // where it would sit among countries
-  t.disputed.country = countries.find((c) => c.slug === t.disputed.part_of);
-  if (!t.disputed.country) throw new Error(`countries/${t.slug}.yaml: disputed.part_of "${t.disputed.part_of}" is not a country`);
+  t.territory.country = countries.find((c) => c.slug === t.territory.of);
+  if (!t.territory.country) throw new Error(`countries/${t.slug}.yaml: "${t.territory.of}" (disputed.part_of or dependency.of) is not a country`);
+  if (t.disputed) t.disputed.country = t.territory.country;
 });
 countries.forEach((c) => {
@@ -240,5 +247,5 @@ function averageOverTime(weight) {
     return sum / total;
   };
-  const events = changes.filter((ch) => ch.score && !ch.estimate && !ch.country.disputed).map((ch) => ({ change: ch, date: ch.date, t: dateTime(ch.date) }));
+  const events = changes.filter((ch) => ch.score && !ch.estimate && !ch.country.territory).map((ch) => ({ change: ch, date: ch.date, t: dateTime(ch.date) }));
   for (let year = CHART_FROM; year <= +DATA_SNAPSHOT; year++) events.push({ change: null, date: `${year}-01-01`, t: Date.UTC(year, 0, 1) });
   events.sort((a, b) => a.t - b.t);

src/lib/exports.js +3 −1
@@ -7,5 +7,5 @@ import { priceObservations } from "./prices.js";
 export const SITE = "https://cannabisfreedom.fyi";
 
-// One row per country or disputed territory.
+// One row per country, disputed territory or dependency.
 export const scoreRows = places.map((c) => ({
   id: c.id,
@@ -14,4 +14,5 @@ export const scoreRows = places.map((c) => ({
   region: c.region,
   disputed: Boolean(c.disputed),
+  dependency_of: c.dependency?.of ?? "", // a Crown Dependency or overseas territory: the country that answers for it
   rank: c.rank ?? "",
   score: c.score,
@@ -96,4 +97,5 @@ export const panelRows = places.flatMap((c) => {
       basis,
       disputed: Boolean(c.disputed),
+      dependency_of: c.dependency?.of ?? "",
     });
   }

src/lib/llms.js +2 −2
@@ -11,5 +11,5 @@ const scored = changes.filter((ch) => ch.score && !ch.estimate);
 const intro = (root) => `# Cannabis Freedom Index
 
-> A score from 0 to 100 for every country, for how free a person is to possess, grow, share and use cannabis. ${countries.length} countries and ${territories.length} disputed territories are scored, each with a written explanation, sources and a dated history of how its law changed. ${DATA_SNAPSHOT} edition, last updated ${formatDate(lastUpdated)}.
+> A score from 0 to 100 for every country, for how free a person is to possess, grow, share and use cannabis. ${countries.length} countries and ${territories.length} disputed territories and dependencies are scored, each with a written explanation, sources and a dated history of how its law changed. ${DATA_SNAPSHOT} edition, last updated ${formatDate(lastUpdated)}.
 
 Important when using this data:
@@ -65,5 +65,5 @@ export function llmsFull(root) {
   const profile = (c) => `### ${c.name}
 
-Score ${c.score}/100 (${tierOf(c.raw).name})${c.disputed ? ", disputed territory, not ranked" : `, rank ${c.rank} of ${countries.length}`}. Recreational: ${REC_LABEL[c.rec]}. Medical: ${MED_LABEL[c.med]}. Reviewed ${c.details.reviewed}. Page: ${root}country/${c.slug}/
+Score ${c.score}/100 (${tierOf(c.raw).name})${c.territory ? `, ${c.territory.label}, not ranked` : `, rank ${c.rank} of ${countries.length}`}. Recreational: ${REC_LABEL[c.rec]}. Medical: ${MED_LABEL[c.med]}. Reviewed ${c.details.reviewed}. Page: ${root}country/${c.slug}/
 
 ${one(c.note)}

src/lib/research.js +1 −1
@@ -79,5 +79,5 @@ export const research = (await getCollection("countries")).map(({ id: slug, data
   const gaps = years.slice(1).map((y, i) => y - years[i]);
   return {
-    slug, name: data.name, flag: data.flag ?? flagOf(slug), region: data.region, disputed: Boolean(data.disputed),
+    slug, name: data.name, flag: data.flag ?? flagOf(slug), region: data.region, disputed: Boolean(data.disputed || data.dependency),
     population: data.population[Object.keys(data.population).sort().at(-1)],
     pct, profile: Math.floor(share("profile") * 100), history: Math.floor(share("history") * 100), signed,

src/lib/world-map.js +1 −1
@@ -17,5 +17,5 @@ const byId = new Map(places.map((c) => [c.id, c]));
 const byNum = new Map(places.filter((c) => c.num != null).map((c) => [c.num, c]));
 // atlas shapes that take another entry's score, as on the home page
-const ALIASES = { Kosovo: "XK", Somaliland: "XS", "N. Cyprus": "XN", 304: "DK" };
+const ALIASES = { Kosovo: "XK", Somaliland: "XS", "N. Cyprus": "XN" };
 const ANTARCTICA = "010";
 

src/pages/compare.astro +3 −3
@@ -6,6 +6,6 @@ import { SHOW_PRICES } from "../lib/config.js";
 import { PLANTING_STATUS, STATUS_SECTIONS, PLANTING_TRADE } from "../lib/segment-ids.js";
 
-const light = places.map(({ id, slug, name, flag, region, raw, score, rank, s, rec, med }) =>
-  ({ id, slug, name, flag, region, raw, score, rank, s, rec, med, prices: SHOW_PRICES ? flatLatestPricesFor(id) : {} }));
+const light = places.map(({ id, slug, name, flag, region, raw, score, rank, s, rec, med, territory }) =>
+  ({ id, slug, name, flag, region, raw, score, rank, s, rec, med, kind: territory?.label, prices: SHOW_PRICES ? flatLatestPricesFor(id) : {} }));
 const meta = {
   segments: SEGMENTS.map((s) => ({ name: s.name, weight: s.weight })),
@@ -149,5 +149,5 @@ const presets = [
           <div class="bar"><i style="width:${c.raw}%;background:${tierColor(c.raw)}"></i></div>
           <div class="cmp-sub">${tierOf(c.raw).name}</div>`), "cmp-index"),
-        row("World rank", cs.map((c) => (c.rank ? `#${c.rank} <span class="cmp-sub">of ${META.total}</span>` : `<span class="cmp-sub">not ranked: disputed territory</span>`))),
+        row("World rank", cs.map((c) => (c.rank ? `#${c.rank} <span class="cmp-sub">of ${META.total}</span>` : `<span class="cmp-sub">not ranked: ${c.kind}</span>`))),
         row("Recreational", cs.map((c) => `<span class="chip s-${c.rec}">${META.rec[c.rec]}</span>`)),
         row("Medical", cs.map((c) => `<span class="chip s-${c.med}">${META.med[c.med]}</span>`)),

src/pages/data/index.astro +2 −2
@@ -10,5 +10,5 @@ const base = import.meta.env.BASE_URL;
 const repo = "https://github.com/burningtree/cannabis-freedom-index";
 const files = [
-  { name: "scores", title: "Scores", rows: scoreRows, about: "One row per country or disputed territory: index score, the six segment scores, rank, tier, legal status, seed and clone status where researched, population and estimated cannabis users." },
+  { name: "scores", title: "Scores", rows: scoreRows, about: "One row per country, disputed territory or dependency (Crown Dependencies and overseas territories, with dependency_of naming the country that answers for them): index score, the six segment scores, rank, tier, legal status, seed and clone status where researched, population and estimated cannabis users." },
   { name: "panel", title: "Scores by year", rows: panelRows, about: `One row per place and year from ${PANEL_FROM} to ${DATA_SNAPSHOT}: the index score and the six segment scores as they stood at the end of each year, rebuilt from the history. Made for joining with other country-year data.` },
   { name: "history", title: "History", rows: historyRows, about: "One row per history entry: date, country, category, title, description, the score before and after, how far it has been checked, and its sources." },
@@ -51,5 +51,5 @@ const files = [
         <li><code>baseline</code>: years before the place's first recorded change. The scores are its starting point, usually no cannabis-specific rules at all.</li>
       </ul>
-      <p>Scores before each change are editorial estimates in every case. Places are identified by a two-letter code (<code>id</code>) and by the ISO 3166-1 numeric code (<code>iso_numeric</code>), which is empty for disputed territories. Borders are today's: a country that was part of another state carries the law that applied on its territory.</p>
+      <p>Scores before each change are editorial estimates in every case. Places are identified by a two-letter code (<code>id</code>) and by the ISO 3166-1 numeric code (<code>iso_numeric</code>), which is empty for disputed territories. Disputed territories and dependencies are scored but not ranked, and are left out of every average. Borders are today's: a country that was part of another state carries the law that applied on its territory.</p>
       <h2>How to cite</h2>
       <p>Cannabis Freedom Index, {DATA_SNAPSHOT} edition, cannabisfreedom.fyi, retrieved on the date of download. The data changes as laws do, so keep the file you used; the <a href={`${base}changelog/`}>changelog</a> lists every score change since.</p>

src/pages/history/[id].astro +1 −1
@@ -147,5 +147,5 @@ const related = relatedTo(ch, nearby);
           <p class="d-num"><b>{c.score}</b><span>/100</span></p>
           <div class="d-meter" aria-hidden="true"><i style={`width:${c.raw}%`}></i></div>
-          <p class="ev-tier">{tierOf(c.raw).name} <small>{c.disputed ? "Not ranked: disputed territory" : `#${c.rank} of ${countries.length}`}</small></p>
+          <p class="ev-tier">{tierOf(c.raw).name} <small>{c.territory ? `Not ranked: ${c.territory.label}` : `#${c.rank} of ${countries.length}`}</small></p>
           <div class="d-chips">
             <span class={`chip s-${c.rec}`} title={REC_HELP[c.rec]}>Recreational: {REC_LABEL[c.rec]}</span>

src/pages/previews/[kind].json.js +1 −1
@@ -19,5 +19,5 @@ async function build(kind) {
     return Object.fromEntries(places.map((c) => [`country/${c.slug}/`, {
       f: c.flag, n: c.name, r: c.region, s: c.score, c: tierColor(c.raw), t: tierOf(c.raw).name,
-      k: c.disputed ? null : c.rank, of: places.filter((x) => !x.disputed).length,
+      k: c.territory ? null : c.rank, of: places.filter((x) => !x.territory).length,
       rec: [REC_LABEL[c.rec], c.rec], med: [MED_LABEL[c.med], c.med], x: cut(c.note, 230), h: entries.get(c.slug) ?? 0,
     }]));

    ↑↓ to move, ↵ to open.