Changelog aa76127
licence, download, rss
14 files of code, design or docs +665 −0 lines
Code, design and documents 14 files
LICENSE +21 −0
@@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Cannabis Freedom Index contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
LICENSE-DATA +396 −0
@@ -0,0 +1,396 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or … 238 more lines
README.md +8 −0
@@ -67,2 +67,10 @@ Please include a source link. Or just [open an issue](https://github.com/burning Scores are editorial estimates, not legal advice. Check current local law before travelling with, buying or growing cannabis. + +## Licence + +- **Code:** [MIT](LICENSE). +- **Data** (everything in `countries/`, `changes.yaml` and `segments.yaml`): [CC BY 4.0](LICENSE-DATA). Credit "Cannabis Freedom Index" and link to https://cannabisfreedom.fyi/. +- Data from others keeps its own terms: population from the World Bank and Our World in Data (CC BY 4.0), cannabis-use rates from the UN World Drug Report as collected on Wikipedia, map shapes from Natural Earth (public domain). + +The scores and history can be downloaded as CSV or JSON at https://cannabisfreedom.fyi/data/, and score changes are listed in the [changelog](https://cannabisfreedom.fyi/changelog/) and its RSS feed.
src/components/CountryArticle.astro +21 −0
@@ -25,4 +25,15 @@ const scored = history.filter((ch) => ch.score); // the ones that moved the scor const people = (n) => (n >= 1e6 ? `${+(n / 1e6).toPrecision(2)} million` : n.toLocaleString("en-GB")); const use = sub ? null : c.use; +// Crossing a border with cannabis. This is general guidance, the same rule almost everywhere, +// worded for the country's status; it is not country-specific research. +const SCHENGEN = new Set("AT BE BG HR CZ DK EE FI FR DE GR HU IS IT LV LI LT LU MT NL NO PL PT RO SK SI ES SE CH".split(" ")); +const travel = sub ? null : [ + `Taking cannabis into or out of ${c.name} is a separate offence from possessing it, normally charged as smuggling or trafficking, with far heavier penalties. That holds almost everywhere, including between two places where cannabis is legal.`, + ["legal", "partial", "decrim"].includes(c.rec) && `The rules that allow or tolerate small amounts inside ${c.name} do not cover the border, an airport or the post.`, + c.rec === "death" && `In ${c.name} the heaviest penalties, up to the death penalty, are aimed at trafficking, and bringing drugs across the border is treated as trafficking.`, + SCHENGEN.has(c.id) + ? `A prescription from another country is not recognised by itself. Within the Schengen area a patient can carry prescribed medicine for up to 30 days with a certificate issued by their home health authority; whether cannabis qualifies depends on both countries.` + : `A prescription or medical card from another country is generally not recognised. Travellers who need a cannabis-based medicine should ask ${c.name}'s embassy before travelling.`, +].filter(Boolean); const r = 42; const circ = 2 * Math.PI * r; @@ -187,4 +198,14 @@ const circ = 2 * Math.PI * r; </div> ))} + {travel && ( + <div class="segrow"> + <div class="segrow-top"><span>🛂 Crossing the border</span></div> + {travel.map((line) => <p class="ctx">{line}</p>)} + <p class="ctx ctx-general">General guidance that applies to almost every country, not a check of {c.name}'s customs rules.</p> + <ul class="srcs"> + <li><a href="https://www.incb.org/incb/en/travellers/index.html" target="_blank" rel="noopener noreferrer">International Narcotics Control Board — Travellers carrying medicines containing controlled substances</a></li> + </ul> + </div> + )} <section class="c-sources">
src/layouts/Base.astro +2 −0
@@ -16,4 +16,5 @@ const fullTitle = title ? `${title} — Cannabis Freedom Index` : "Cannabis Free <meta name="description" content={description}> <link rel="icon" type="image/svg+xml" href={`${base}favicon.svg`}> + <link rel="alternate" type="application/rss+xml" title="Cannabis Freedom Index: score changes" href={`${base}rss.xml`}> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> @@ -55,4 +56,5 @@ const fullTitle = title ? `${title} — Cannabis Freedom Index` : "Cannabis Free <footer class="footer"> <p>Cannabis Freedom Index · Informational only, not legal advice · Map data © Natural Earth</p> + <p class="footer-links"><a href={`${base}changelog/`}>Changelog</a> · <a href={`${base}rss.xml`}>RSS</a> · <a href={`${base}data/`}>Download the data</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> </footer>
src/lib/exports.js +52 −0
@@ -0,0 +1,52 @@ +// The data offered for download and in the feed, built from the same objects the pages use. +import { SEGMENTS, REC_LABEL, MED_LABEL, places, changes, formatDate, dateTime } from "./countries.js"; +import { tierOf } from "./tiers.js"; + +export const SITE = "https://cannabisfreedom.fyi"; + +// One row per country or disputed territory. +export const scoreRows = places.map((c) => ({ + id: c.id, + name: c.name, + region: c.region, + disputed: Boolean(c.disputed), + rank: c.rank ?? "", + score: c.score, + score_exact: c.raw, + tier: tierOf(c.raw).name, + ...Object.fromEntries(SEGMENTS.map((seg, i) => [seg.id, c.s[i]])), + recreational: REC_LABEL[c.rec], + medical: MED_LABEL[c.med], + population: c.population, + users: c.use.users, + users_share_percent: c.use.prevalence, + users_estimated: Boolean(c.use.estimate), + reviewed: c.details.reviewed, +})); + +// One row per History entry, newest first. Entries with no real date (estimates) are marked. +export const historyRows = changes.map((ch) => ({ + id: ch.id, + date: ch.date, + country: ch.country?.id ?? "", + place: ch.place.name, + category: ch.category, + title: ch.title, + description: ch.description, + score_before: ch.score?.before ?? "", + score_after: ch.score?.after ?? "", + date_estimated: Boolean(ch.estimate), + source_title: ch.source[0], + source_url: ch.source[1], +})); + +// Changes that moved a score and have a real date: the changelog and the feed. +export const scoreChanges = changes.filter((ch) => ch.score && !ch.estimate); +export { formatDate, dateTime }; + +const cell = (v) => { + const s = String(v ?? ""); + return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; +}; +export const toCsv = (rows) => [Object.keys(rows[0]).join(","), ...rows.map((r) => Object.values(r).map(cell).join(","))].join("\n") + "\n"; +export const file = (body, type) => new Response(body, { headers: { "Content-Type": `${type}; charset=utf-8` } });
src/pages/changelog.astro +38 −0
@@ -0,0 +1,38 @@ +--- +// Every change that moved a country's score, newest first, as one compact table. +import Base from "../layouts/Base.astro"; +import { scoreChanges, formatDate } from "../lib/exports.js"; + +const base = import.meta.env.BASE_URL; +--- +<Base title="Changelog" description="Every change to a country's score on the Cannabis Freedom Index, newest first, with an RSS feed."> + <main class="changes-page"> + <section class="hero hero-sm"> + <p class="eyebrow">Changelog</p> + <h1>Score changes</h1> + <p class="lede">Every law or ruling that moved a country's score, newest first: {scoreChanges.length} in all. For the full story of each, with context and treaties, see the <a href={`${base}history/`}>History</a>.</p> + <p class="page-links"><a class="btn" href={`${base}rss.xml`}>RSS feed</a> <a class="btn" href={`${base}data/`}>Download the data</a></p> + </section> + + <div class="card table-card log-card"> + <table class="log"> + <thead> + <tr><th scope="col">Date</th><th scope="col">Country</th><th scope="col">Change</th><th scope="col" class="num-col">Score</th></tr> + </thead> + <tbody> + {scoreChanges.map((ch) => { + const delta = ch.score.after - ch.score.before; + return ( + <tr> + <td class="log-date"><time datetime={ch.date}>{formatDate(ch.date)}</time></td> + <td class="log-place">{ch.country.flag} {ch.country.name}</td> + <td><a href={`${base}history/${ch.id}/`}><span class="kind" aria-hidden="true">{ch.kind.emoji}</span>{ch.title}</a></td> + <td class="num-col">{ch.score.before} → {ch.score.after} <span class={`delta ${delta >= 0 ? "up" : "down"}`}>{delta >= 0 ? "+" : "−"}{Math.abs(delta)}</span></td> + </tr> + ); + })} + </tbody> + </table> + </div> + </main> +</Base>
src/pages/data/history.csv.js +4 −0
@@ -0,0 +1,4 @@ +// Download: history.csv +import { historyRows, toCsv, file } from "../../lib/exports.js"; + +export const GET = () => file(toCsv(historyRows), "text/csv");
src/pages/data/history.json.js +4 −0
@@ -0,0 +1,4 @@ +// Download: history.json +import { historyRows, file } from "../../lib/exports.js"; + +export const GET = () => file(JSON.stringify(historyRows, null, 1), "application/json");
src/pages/data/index.astro +50 −0
@@ -0,0 +1,50 @@ +--- +// Downloads: the scores and the history as CSV and JSON, with the licence. +import Base from "../../layouts/Base.astro"; +import { scoreRows, historyRows } from "../../lib/exports.js"; +import { lastUpdated, formatDate } from "../../lib/countries.js"; + +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, population and estimated cannabis users." }, + { name: "history", title: "History", rows: historyRows, about: "One row per history entry: date, country, category, title, description, the score before and after, and the source." }, +]; +--- +<Base title="Data and licence" description="Download the Cannabis Freedom Index scores and history as CSV or JSON. Data is CC BY 4.0, code is MIT."> + <main class="changes-page"> + <section class="hero hero-sm"> + <p class="eyebrow">Data</p> + <h1>Download the data</h1> + <p class="lede">Everything on this site is built from open files. Take the scores and the history as a spreadsheet or as JSON, or read the source files on GitHub. Last updated {formatDate(lastUpdated)}.</p> + </section> + + <div class="downloads"> + {files.map((f) => ( + <section class="card download"> + <h2>{f.title}</h2> + <p>{f.about}</p> + <p class="download-meta">{f.rows.length} rows · {Object.keys(f.rows[0]).length} columns</p> + <p class="page-links"> + <a class="btn" href={`${base}data/${f.name}.csv`} download>{f.name}.csv</a> + <a class="btn" href={`${base}data/${f.name}.json`} download>{f.name}.json</a> + </p> + <details> + <summary>Columns</summary> + <p class="download-cols">{Object.keys(f.rows[0]).join(", ")}</p> + </details> + </section> + ))} + </div> + + <section class="prose" id="licence"> + <h2>Licence</h2> + <p><strong>The data</strong> (scores, explanations, history) is published under <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener noreferrer">Creative Commons Attribution 4.0</a>. You may copy, adapt and republish it, including commercially, as long as you credit "Cannabis Freedom Index" and link to this site.</p> + <p><strong>The code</strong> is under the <a href={`${repo}/blob/main/LICENSE`} target="_blank" rel="noopener noreferrer">MIT licence</a>.</p> + <p><strong>Data from others</strong> keeps its own terms: population figures come from the World Bank and Our World in Data (both CC BY 4.0), cannabis-use rates from the UN's World Drug Report as collected on Wikipedia, and map shapes from Natural Earth (public domain).</p> + <p>Scores are editorial estimates and many entries rest on secondary sources. This is not legal advice.</p> + <h2>Stay up to date</h2> + <p>The <a href={`${base}changelog/`}>changelog</a> lists every score change, and the <a href={`${base}rss.xml`}>RSS feed</a> carries the latest fifty. The source files, one per country, are on <a href={repo} target="_blank" rel="noopener noreferrer">GitHub</a>.</p> + </section> + </main> +</Base>
src/pages/data/scores.csv.js +4 −0
@@ -0,0 +1,4 @@ +// Download: scores.csv +import { scoreRows, toCsv, file } from "../../lib/exports.js"; + +export const GET = () => file(toCsv(scoreRows), "text/csv");
src/pages/data/scores.json.js +4 −0
@@ -0,0 +1,4 @@ +// Download: scores.json +import { scoreRows, file } from "../../lib/exports.js"; + +export const GET = () => file(JSON.stringify(scoreRows, null, 1), "application/json");
src/pages/rss.xml.js +33 −0
@@ -0,0 +1,33 @@ +// RSS feed of changes that moved a country's score, newest first. +import { SITE, scoreChanges, dateTime } from "../lib/exports.js"; + +const esc = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); + +export function GET({ site }) { + const root = (site?.href ?? SITE + "/").replace(/\/$/, "") + import.meta.env.BASE_URL; + const items = scoreChanges.slice(0, 50).map((ch) => { + const delta = ch.score.after - ch.score.before; + const link = `${root}history/${ch.id}/`; + return ` <item> + <title>${esc(`${ch.title} (${ch.score.before} → ${ch.score.after})`)}</title> + <link>${link}</link> + <guid isPermaLink="true">${link}</guid> + <pubDate>${new Date(dateTime(ch.date)).toUTCString()}</pubDate> + <category>${esc(ch.kind.label)}</category> + <description>${esc(`${ch.description} Index score ${ch.score.before} → ${ch.score.after} (${delta >= 0 ? "+" : "−"}${Math.abs(delta)}). Source: ${ch.source[0]}.`)}</description> + </item>`; + }); + const xml = `<?xml version="1.0" encoding="UTF-8"?> +<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> + <channel> + <title>Cannabis Freedom Index: score changes</title> + <link>${root}changelog/</link> + <atom:link href="${root}rss.xml" rel="self" type="application/rss+xml"/> + <description>Laws and rulings that moved a country's score on the Cannabis Freedom Index.</description> + <language>en</language> +${items.join("\n")} + </channel> +</rss> +`; + return new Response(xml, { headers: { "Content-Type": "application/rss+xml; charset=utf-8" } }); +}
src/styles/global.css +28 −0
@@ -600,2 +600,30 @@ th.num-col { text-align: right; } .c-disputed strong { color: var(--text); } .c-disputed a { color: var(--text); text-decoration: underline; text-decoration-color: var(--border); text-underline-offset: 3px; } + +/* general guidance inside a context section */ +.ctx + .ctx { margin-top: 8px; } +.ctx-general { color: var(--muted); font-size: 12.5px; } + +/* ── Changelog, downloads, licence ──────────────────── */ +.page-links { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0 0; } +.lede a, .prose a { color: var(--text); text-decoration: underline; text-decoration-color: var(--border); text-underline-offset: 3px; } +.log-card { margin-top: 24px; overflow-x: auto; } +.log td { font-size: 14px; } +.log th { cursor: default; } +.log-date, .log-place { white-space: nowrap; color: var(--text-2); } +.log a { color: var(--text); } +.log a:hover { text-decoration: underline; text-underline-offset: 3px; } +.downloads { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 12px; margin-top: 24px; } +.download { padding: 20px 22px; } +.download h2 { font-size: 20px; margin: 0 0 6px; } +.download p { margin: 0; color: var(--text-2); font-size: 14.5px; } +.download .download-meta { margin-top: 8px; color: var(--muted); font-size: 13px; } +.download details { margin-top: 12px; font-size: 13px; color: var(--muted); } +.download summary { cursor: pointer; } +.download .download-cols { margin-top: 6px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; overflow-wrap: anywhere; } +.prose { max-width: 720px; margin-top: 40px; } +.prose h2 { font-size: 22px; margin: 28px 0 8px; } +.prose p { color: var(--text-2); margin: 0 0 10px; } +.prose strong { color: var(--text); } +.footer-links a { color: var(--text-2); } +@media (max-width: 640px) { .log-place { white-space: normal; } }