Thailand's cabinet approves a medical-only cannabis bill
tightening
Changelog 215a088
9 history entries added538 history entries edited1 history entry removed1 score changed11 files of code, design or docs +832 −67 lines
tightening
medical
death
death
medical
legalisation
legalisation
medical
medical
legalisation
medical
tightening
medical
tightening
other
death
tightening
medical
easing
medical
legalisation
easing
easing
debate
legalisation
tightening
legalisation
medical
legalisation
legalisation
debate
legalisation
tightening
legalisation
easing
medical
medical
death
debate
debate
tightening
other
death
debate
easing
other
death
easing
easing
medical
other
medical
legalisation
easing
medical
debate
other
legalisation
debate
medical
legalisation
debate
medical
debate
easing
medical
legalisation
other
medical
easing
debate
death
easing
international
medical
debate
legalisation
easing
medical
medical
easing
easing
easing
medical
legalisation
medical
legalisation
easing
medical
medical
medical
medical
medical
legalisation
easing
easing
debate
medical
medical
easing
medical
medical
other
easing
medical
debate
medical
medical
easing
legalisation
tightening
legalisation
easing
easing
international
medical
medical
medical
medical
easing
legalisation
medical
medical
medical
easing
easing
medical
medical
easing
easing
tightening
legalisation
legalisation
medical
medical
easing
medical
medical
medical
tightening
legalisation
medical
tightening
medical
medical
medical
easing
medical
easing
easing
medical
debate
easing
tightening
tightening
easing
easing
easing
tightening
easing
easing
legalisation
easing
easing
other
easing
tightening
medical
medical
easing
medical
medical
death
easing
legalisation
tightening
easing
other
other
easing
easing
tightening
tightening
easing
easing
tightening
debate
medical
other
medical
easing
tightening
easing
death
tightening
tightening
other
other
easing
tightening
and 338 more
possession 1 → 0
A 2021 amendment raised the maximum for possession to 40Cannabis is a strictly prohibited Schedule I substance. Ten grams or less can bring two years in prison; districtmore than two kilograms falls in a 16-years-to-life tier, and courts impose far less for small amountsdo jail users and transporters under the law.
Until 2022 possession of a dangerous drug carried three monthsCannabis, cannabis resin and cannabis oil are Schedule I substances under the Controlled Substance Act 2021. Schedule VII sets up to two years in prison. The Dangerous Drugs (Amendment) Act 2021, passed in response to methamphetamine trafficking, raised the maximum for knowingly possessing any dangerous drug to or a K15,000 fine of up to one million kinafor 10 g or less, up to 4015 years in prisonor K25,000 for more than 10 g but less than 2 kg, and 16 years to life or bothK26,000–2 million for more than 2 kg. District courts dealing withCourts apply the Act to small amounts of marijuana impose far lower sentencespossession: one man received 18 months for nine packets in 2023.
actcontrolled, casesmall_case
Cultivation is an offence under the same Act. Highlands-grown cannabis isSection 34 expressly prohibits cultivating a major informal cropSchedule I plant. Schedule VII grades fewer than five plants at up to two years or K15,000, more than five at up to 15 years or K25,000, and more than 20 at 16 years to life or K26,000–2 million.
actcontrolled
Short prison terms and fines are typicalPrison is used at both ends of the scale: a court imposed 18 months for small marijuana casesnine packets in the district courts; the new maximums give courts enormous discretion2023, while two men with 14–20 kg received 25 years in 2025. In August 2026, a court ordered a man caught with 4.7 kg to pay K30,000 or serve 20 years.
casesmall_case, actlong_case, police
Supply fallsFacilitating another person's use, whether free or for consideration, carries up to 12 months or K50,000. Selling or distributing for financial or material benefit is trafficking and is punished under the same greatly increased maximumsquantity-based Schedule VII tiers.
actcontrolled
Use is covered by the possessionThe Act does not set out a separate simple own-use offence, but consuming cannabis ordinarily requires illegal possession. Possessing and selling consumption implements, facilitating another person's use and inciting use are separate offences.
casecontrolled
None. Schedule I cannabis has no adult-use or patient-access channel.
actcontrolled
None. The 2023–2027 development plan calls for an industrial-hemp policy framework, but that policy intention did not legalise cultivation. A 2024 National Research Institute study said Papua New Guinea was not yet ready to legalise hemp production.
actcontrolled, development_plan, hemp_study
NoneCannabis flower, resin, oil and Cannabis sativa plants are all in Schedule I. Cannabis fibre is excluded from the Act's definition of cannabis, but the plant needed to produce it remains prohibited; there is no general CBD or low-THC exemption.
actcontrolled
No medical-cannabis programme. Schedule I is described as having no medical value, and the Act's prescribed-medication defence applies only to Schedules II–IV.
actcontrolled
A conviction isproduces a criminal record and may bring a fine, imprisonment or both. Courts sometimes make a long default prison term the alternative to a fine, as in the 2026 order of K30,000 or 20 years for 4.7 kg.
casepolice
National Parliament — Controlled Substance Act 2021
The National — 18 months for drug possession
NBC PNG — Duo jailed for 25 years for drug offences
Royal Papua New Guinea Constabulary — man fined K30,000 or 20 years over marijuana
Government of Papua New Guinea — Medium Term Development Plan IV 2023–2027
PNG National Research Institute — Papua New Guinea's drive to legalise industrial hemp
PacLII — Dangerous Drugs (Amendment) Act 2021
PacLII — Papua New Guinea District Court judgment on drug possession (2021)
src/components/Changes.astro +1 @@ -25,5 +25,5 @@ const shortDate = (iso) => <span class="mini-tl-body"> <span class="mini-tl-country">{ch.country.flag} {ch.country.name}</span> - <b>{ch.title}</b> + <b><span class="kind" title={ch.kind.label} aria-hidden="true">{ch.kind.emoji}</span><span class="visually-hidden">{ch.kind.label}: </span>{ch.title}</b> <ScoreChange score={ch.score} /> </span>
src/components/CountryArticle.astro +1 @@ -69,4 +69,5 @@ const circ = 2 * Math.PI * r; ? `An estimate: there is no national survey, so the median rate of surveyed countries in ${c.region} is assumed.` : `People who used cannabis in the past year, from a ${use.year} survey applied to today's population.`}</p> + <p class="c-use-src">Source: <a href="https://www.unodc.org/unodc/en/data-and-analysis/wdr2022_annex.html" target="_blank" rel="noopener noreferrer">UNODC World Drug Report</a> prevalence figures, as collected in <a href="https://en.wikipedia.org/wiki/List_of_countries_by_annual_cannabis_use" target="_blank" rel="noopener noreferrer">Wikipedia's list of annual cannabis use by country</a>; population aged 15–64 from the <a href="https://data.worldbank.org/indicator/SP.POP.1564.TO" target="_blank" rel="noopener noreferrer">World Bank</a>.</p> </dl> )}
src/components/CountryChanges.astro +1 @@ -14,5 +14,5 @@ const base = import.meta.env.BASE_URL; <a href={`${base}history/${ch.id}/`}> <time datetime={ch.date}>{changeDate(ch)}</time> - <b>{ch.title}</b> + <b><span class="kind" title={ch.kind.label} aria-hidden="true">{ch.kind.emoji}</span><span class="visually-hidden">{ch.kind.label}: </span>{ch.title}</b> {ch.score && <span class="c-change-score"> {ch.score.before} <span aria-hidden="true">→</span><span class="visually-hidden">to</span> {ch.score.after}
src/components/HistoryList.astro +23 @@ -1,10 +1,11 @@ --- -// One page of the History list: a period of entries, newest first, grouped by year, with links -// to the other periods above and below. +// One page of the History list, newest first and grouped by year. It shows either a period +// (`period`) or every entry of one kind (`kind`, a key of CHANGE_CATEGORIES). The period links +// and the kind filters above the list lead to the other pages. import ScoreChange from "./ScoreChange.astro"; -import { historyPeriods, formatDate } from "../lib/countries.js"; +import { changes, historyPeriods, formatDate, CHANGE_CATEGORIES } from "../lib/countries.js"; import { tierColor } from "../lib/tiers.js"; -const { period } = Astro.props; +const { period, kind } = Astro.props; const base = import.meta.env.BASE_URL; // the newest period lives at /history/ itself @@ -13,6 +14,10 @@ const index = historyPeriods.indexOf(period); const newer = historyPeriods[index - 1]; const older = historyPeriods[index + 1]; -const years = [...new Set(period.items.map((ch) => ch.date.slice(0, 4)))] - .map((year) => ({ year, items: period.items.filter((ch) => ch.date.startsWith(year)) })); +// estimated entries have no real date, so they are not listed +const dated = changes.filter((ch) => !ch.estimate); +const items = kind ? dated.filter((ch) => ch.category === kind) : period.items; +const kindCount = (key) => dated.filter((ch) => ch.category === key).length; +const years = [...new Set(items.map((ch) => ch.date.slice(0, 4)))] + .map((year) => ({ year, items: items.filter((ch) => ch.date.startsWith(year)) })); --- <nav class="periods" aria-label="Periods"> @@ -22,4 +27,13 @@ const years = [...new Set(period.items.map((ch) => ch.date.slice(0, 4)))] </nav> +<nav class="kinds" aria-label="Filter by kind of event"> + <a href={`${base}history/`} aria-current={kind ? undefined : "true"}>All kinds</a> + {Object.entries(CHANGE_CATEGORIES).map(([key, k]) => ( + <a href={`${base}history/kind/${key}/`} title={k.help} aria-current={key === kind ? "page" : undefined}> + <span aria-hidden="true">{k.emoji}</span> {k.label} <small>{kindCount(key)}</small> + </a> + ))} +</nav> + <div class="chlog"> {years.map(({ year, items }) => ( @@ -34,5 +48,5 @@ const years = [...new Set(period.items.map((ch) => ch.date.slice(0, 4)))] <span>{ch.place.flag} {ch.place.name}</span> </p> - <h3>{ch.title}</h3> + <h3><span class="kind" title={ch.kind.label} aria-hidden="true">{ch.kind.emoji}</span><span class="visually-hidden">{ch.kind.label}: </span>{ch.title}</h3> <p class="chl-text">{ch.description}</p> {ch.score && <ScoreChange score={ch.score} />} @@ -45,5 +59,5 @@ const years = [...new Set(period.items.map((ch) => ch.date.slice(0, 4)))] </div> -<nav class="pager chl-pager" aria-label="Other periods"> +{period && <nav class="pager chl-pager" aria-label="Other periods"> {newer ? <a href={href(newer)}><small>Newer</small><span>← {newer.label}</span></a> @@ -52,3 +66,3 @@ const years = [...new Set(period.items.map((ch) => ch.date.slice(0, 4)))] ? <a class="next" href={href(older)}><small>Older</small><span>{older.label} →</span></a> : <span></span>} -</nav> +</nav>}
src/content.config.ts +2 @@ -4,5 +4,5 @@ import { defineCollection } from "astro:content"; import { glob, file } from "astro/loaders"; import { z } from "astro/zod"; -import { SEGMENT_IDS, CONTEXT_IDS } from "./lib/segment-ids.js"; +import { SEGMENT_IDS, CONTEXT_IDS, CHANGE_CATEGORIES } from "./lib/segment-ids.js"; const score = z.number().int().min(0).max(10); @@ -116,4 +116,5 @@ const changes = defineCollection({ id: z.string(), date: z.string().regex(/^\d{4}(-\d{2}(-\d{2})?)?$/, "use YYYY-MM-DD, YYYY-MM or YYYY"), + category: z.enum(Object.keys(CHANGE_CATEGORIES) as [string, ...string[]]), // what kind of event it is; see segment-ids.js // A change has `country` and `before`. Without `before` the entry is context that moves no // score: a report or ruling in that country, or with no `country` a treaty or UN decision.
src/lib/countries.js +5 @@ -2,5 +2,6 @@ // pages need: index scores, ranks, averages. Runs once at build time. import { getCollection } from "astro:content"; -import { SEGMENT_IDS } from "./segment-ids.js"; +import { CHANGE_CATEGORIES, SEGMENT_IDS } from "./segment-ids.js"; +export { CHANGE_CATEGORIES }; export const DATA_SNAPSHOT = "2026"; @@ -130,5 +131,6 @@ export const changes = changeEntries if (data.country && !country) throw new Error(`changes.yaml: "${data.id}" refers to unknown country "${data.country}"`); // context only (a treaty, report or ruling): part of the history, but it moves no score - if (!data.before) return { ...data, country, place: country ?? { flag: "🌐", name: "International" }, source: pair(data.source), score: null, segments: [] }; + const kind = CHANGE_CATEGORIES[data.category]; // { emoji, label, help } + if (!data.before) return { ...data, kind, country, place: country ?? { flag: "🌐", name: "International" }, source: pair(data.source), score: null, segments: [] }; const next = changeEntries.slice(i + 1).find((x) => x.country === data.country && x.before); const before = scoreList(data.before); @@ -137,4 +139,5 @@ export const changes = changeEntries return { ...data, + kind, country, place: country,
src/lib/segment-ids.js +12 @@ -3,2 +3,14 @@ export const SEGMENT_IDS = ["possession", "cultivation", "enforcement", "sharing", "consumption", "access"]; export const CONTEXT_IDS = ["production", "products", "medical", "consequences"]; +// What kind of event a History entry is, in the order the legend lists them. +export const CHANGE_CATEGORIES = { + ban: { emoji: "⛔", label: "Ban", help: "Cannabis is prohibited or restricted for the first time, or banned again" }, + tightening: { emoji: "👎", label: "Tightening", help: "Harsher penalties, new offences or a crackdown" }, + death: { emoji: "☠️", label: "Death penalty", help: "The death penalty is introduced, used, limited or removed" }, + easing: { emoji: "👍", label: "Easing", help: "Decriminalisation, lighter penalties or a ruling that protects users" }, + legalisation: { emoji: "✅", label: "Legalisation", help: "Possession, growing or sales become legal for adults" }, + medical: { emoji: "💊", label: "Medical", help: "Medical cannabis is allowed, widened or refused" }, + debate: { emoji: "🗳️", label: "Debate", help: "Reports, commissions, votes and plans that changed no rule by themselves" }, + international: { emoji: "🌐", label: "International", help: "Treaties and decisions of international bodies" }, + other: { emoji: "📜", label: "Other law", help: "Other laws and rulings, including hemp and CBD rules" }, +};
src/pages/history/[id].astro +1 @@ -22,5 +22,5 @@ const diff = (seg) => seg.after - seg.before; <article class="change-detail"> - <p class="eyebrow"><time datetime={ch.date}>{changeDate(ch)}</time> · {ch.place.name}</p> + <p class="eyebrow"><span title={ch.kind.help}>{ch.kind.emoji} {ch.kind.label}</span> · <time datetime={ch.date}>{changeDate(ch)}</time> · {ch.place.name}</p> <h1>{ch.title}</h1> <p class="lede">{ch.description}</p>
src/pages/history/kind/[kind].astro +23 @@ -0,0 +1,23 @@ +--- +// Every History entry of one kind, across all periods. +import Base from "../../../layouts/Base.astro"; +import HistoryList from "../../../components/HistoryList.astro"; +import { CHANGE_CATEGORIES } from "../../../lib/countries.js"; + +export function getStaticPaths() { + return Object.keys(CHANGE_CATEGORIES).map((kind) => ({ params: { kind } })); +} + +const { kind } = Astro.params; +const k = CHANGE_CATEGORIES[kind]; +--- +<Base title={`History: ${k.label}`} description={`${k.help}. Every such entry in the history of cannabis law, newest first.`}> + <main class="changes-page"> + <section class="hero hero-sm"> + <p class="eyebrow">History</p> + <h1>{k.emoji} {k.label}</h1> + <p class="lede">{k.help}.</p> + </section> + <HistoryList kind={kind} /> + </main> +</Base>
src/pages/index.astro +20 @@ -14,5 +14,5 @@ const useNote = (c) => c.use.estimate : `${c.use.prevalence}% of people aged 15–64 used cannabis in the past year (${c.use.year} survey)`; const stats = [ - [worldAvg.toFixed(1), "world average, out of 100"], + [countries.length, "countries scored"], [count((c) => c.raw >= 60), "reach the “Free” tier (60+)"], [count((c) => c.raw >= 40), "score 40+: legal to possess and grow, with limits"], @@ -25,9 +25,17 @@ const legend = [...TIERS].reverse().map((t, i, arr) => ({ const MED_ORDER = ["yes", "limited", "no"]; // What the browser needs for the map: scores only, no prose. -const mapData = countries.map(({ id, num, name, flag, raw, score, rank, s, use }) => ({ id, num, name, flag, raw, score, rank, s, use })); +// `ratio` is the share of people who use cannabis divided by the index score: how far use runs +// ahead of freedom. A score below 1 counts as 1, so that a zero does not divide. +const mapData = countries.map(({ id, num, name, flag, raw, score, rank, s, use }) => + ({ id, num, name, flag, raw, score, rank, s, use: { ...use, ratio: +(use.prevalence / Math.max(raw, 1)).toFixed(2) } })); // Cannabis use on the map: one hue from light to dark, in five steps. Each step starts at `min`. const USE_SCALES = { users: { label: "Cannabis users", steps: [[0, "Under 10k"], [1e4, "10k–100k"], [1e5, "100k–1M"], [1e6, "1M–10M"], [1e7, "10M or more"]] }, prevalence: { label: "Share who use cannabis", steps: [[0, "Under 1%"], [1, "1–3%"], [3, "3–7%"], [7, "7–15%"], [15, "15% or more"]] }, + ratio: { + label: "Use relative to freedom", + steps: [[0, "Under 0.15"], [0.15, "0.15–0.3"], [0.3, "0.3–0.5"], [0.5, "0.5–1"], [1, "1 or more"]], + note: "Share of people aged 15–64 who use cannabis, divided by the index score. A high value means many people use cannabis where the law gives them little freedom to.", + }, }; --- @@ -48,5 +56,5 @@ const USE_SCALES = { <div> <h2>World map</h2> - <p class="sub" id="mapSub" data-score="Red means prison risk, green means free. Select a country for its full breakdown." data-use="The stronger the blue, the more people use cannabis. Select a country for its full breakdown.">Red means prison risk, green means free. Select a country for its full breakdown.</p> + <p class="sub" id="mapSub" data-score="Red means prison risk, green means free. Select a country for its full breakdown." data-use="The stronger the blue, the more people use cannabis. Select a country for its full breakdown." data-ratio="The stronger the blue, the further cannabis use runs ahead of the freedom to use it. Select a country for its full breakdown.">Red means prison risk, green means free. Select a country for its full breakdown.</p> </div> <label class="field"> @@ -57,4 +65,5 @@ const USE_SCALES = { <option value="users">Cannabis users (number)</option> <option value="prevalence">Cannabis users (share of adults)</option> + <option value="ratio">Share of users ÷ index</option> </select> </label> @@ -79,5 +88,5 @@ const USE_SCALES = { {scale.steps.map(([, text], i) => <span><i class="sw" style={`background:var(--u${i})`}></i>{text}</span>)} <span><i class="sw sw-nodata"></i>No data</span> - <span class="legend-note">People aged 15–64 who used cannabis in the past year. Figures marked ~ are regional estimates.</span> + <span class="legend-note">{scale.note ?? "People aged 15–64 who used cannabis in the past year. Figures marked ~ are regional estimates."}</span> </div> ))} @@ -235,5 +244,5 @@ const USE_SCALES = { document.querySelectorAll("[data-legend]").forEach((el) => { el.hidden = el.dataset.legend !== shown; }); const sub = $("#mapSub"); - sub.textContent = isUse() ? sub.dataset.use : sub.dataset.score; + sub.textContent = metric === "ratio" ? sub.dataset.ratio : isUse() ? sub.dataset.use : sub.dataset.score; } $("#metric").addEventListener("change", (e) => { metric = e.target.value; paintMap(); }); @@ -275,5 +284,10 @@ const USE_SCALES = { .on("pointermove", (e, d) => { const c = d.c; - tip.innerHTML = c && isUse() + tip.innerHTML = c && metric === "ratio" + ? `<strong>${c.flag} ${c.name}</strong> + <div class="row"><span>Share ÷ index</span><b>${c.use.estimate ? "~" : ""}${c.use.ratio}</b></div> + <div class="row"><span>Share aged 15–64</span><b>${c.use.estimate ? "~" : ""}${c.use.prevalence}%</b></div> + <div class="row"><span>Index</span><b>${c.score} / 100</b></div>` + : c && isUse() ? `<strong>${c.flag} ${c.name}</strong> <div class="row"><span>Users</span><b>${c.use.estimate ? "~" : ""}${compact(c.use.users)}</b></div>
src/styles/global.css +12 @@ -576,2 +576,14 @@ th.num-col { text-align: right; } .c-use dd { margin: 0; font-family: "Space Grotesk", Inter, system-ui, sans-serif; font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.2; } .c-use p { grid-column: 1 / -1; margin: 6px 0 0; color: var(--muted); font-size: 12.5px; } +.c-use .c-use-src { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); font-size: 11.5px; line-height: 1.45; } +.c-use-src a { color: var(--text-2); text-decoration: underline; text-decoration-color: var(--border); text-underline-offset: 2px; } +.c-use-src a:hover { color: var(--text); text-decoration-color: currentColor; } + +/* ── History entry kinds ────────────────────────────── */ +.kind { margin-right: 6px; font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif; letter-spacing: 0; } +/* the kinds double as filters: each leads to a page listing only that kind */ +.kinds { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 0; max-width: 760px; } +.kinds a { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border: 1px solid var(--border); border-radius: 999px; color: var(--text-2); font-size: 12.5px; white-space: nowrap; } +.kinds a:hover { border-color: var(--muted); color: var(--text); } +.kinds a[aria-current] { background: var(--surface-2); border-color: var(--muted); color: var(--text); font-weight: 600; } +.kinds small { opacity: .7; font-weight: 400; font-variant-numeric: tabular-nums; }