Code, design and documents 15 files
docs/CULTURE.md +11 −5
@@ -123,4 +123,5 @@ also: [bangue, bang] # other spellings, related words, names to be
countries: [in, ir] # file names in /countries, the most closely tied first
history: [india-bhang-exemption-1961, india-ndps-act-1985] # ids in changes.yaml
+history_cards: [india-bhang-exemption-1961] # only events directly about this atlas subject
related: [traditions/bhang-at-holi, language/ganja] # optional: closest atlas entries first
image: # the lead picture, optional
@@ -212,9 +213,14 @@ The site turns that into a figure with the caption and credit underneath. Rules:
- A country may be listed for a law about the custom (Italy for its Rastafari ruling) when the
text says so.
-- `history` lists the entries the text mentions or relies on, and every history entry that is
- about the word or the custom itself (a coffeeshop law belongs to The coffeeshop even when the
- text does not mention it). Each one listed shows the atlas entry beside it on its history page,
- so a reader who opens the law finds the culture behind it. If the text names a law that has no
- history entry, add the entry to `changes.yaml` first.
+- `history` lists the entries the text mentions or relies on, including legal background. These
+ appear in the atlas entry's history list. If the text names a law that has no history entry,
+ add the entry to `changes.yaml` first.
+- `history_cards` selects which of those events should show this entry under **In the cultural
+ atlas** on the history page. Use it only when the event is specifically about the word,
+ preparation, community, place or institution. Each selected id must also be in `history`.
+ A background citation, a shared country or an incidental link in a history article is not
+ enough. Omission means no reverse cards. The first Cannabis Cup shows The Cannabis Cup,
+ not Indica and sativa or Kush; a general national cannabis ban should not show every local
+ dish, slang word or tradition that mentions it.
- Beside each entry the page lists up to eight related words and traditions. They are worked out
on their own: entries the text links to or that link to it, entries sharing history entries,
docs/research/brick-weed-2026-10-14.md +17 −0
@@ -0,0 +1,17 @@
+# Brick weed
+
+Added `/culture/traditions/brick-weed/` as an entry about the physical form, supply-chain practices and social context. Related terms are searchable through `also`; Mota and Hashish link to it. No separate Language entry duplicates the subject.
+
+Sources read: DEA Museum's description of bulk cannabis bricks; Garat's 2016 TNI briefing on Paraguay's production and labour; Macedo, Kosmann and Pujol-Luz's 2013 forensic study; Castro and Fraga's 2023 observational/interview study of Brazilian home-growers; Wikipedia for regional terminology only. The dated studies support the account rather than constituting new events in cannabis policy. Existing history links supply the legal context.
+
+The article distinguishes plant-material bricks from resin and treats contamination as evidence about particular samples. It does not repeat unsupported assertions that every brick contains glue, faeces or petroleum products, assign a universal potency, or claim every pressed sample originated in Paraguay. The comparison with growers' knowledge is interpretive, based on the documented contrast between bulk supply and cultivation networks.
+
+The DEA exhibit photograph could not be downloaded (403). Used the original 492 × 492 Wikimedia Commons image `Marihuana_prensada.jpg` by PudasSAL, dated 20 August 2021, under CC BY-SA 4.0. Downloaded and visually inspected; source and licence links are rendered. Its location, contents beyond visible plant fragments, and origin are not inferred from the Commons category. `fit: contain` preserves the whole object; no retouching or synthetic substitute was used.
+
+Verification: 515-word body; build passed (2,764 pages); browser confirmed the entry, full image and licence link; generated Traditions index, country profiles and related Language pages link to it; internal links and `git diff --check` passed.
+
+## Documentation review
+
+Re-read docs/CULTURE.md and the history-article rules in docs/DATA-MAINTENANCE.md. The subject fits the documented Traditions category for a preparation whose interest is the thing itself. Added first-mention links to named sources, explicit US law context and the checked Brazilian 2024 ruling; replaced the concluding summary with a law section. Removed Mexico from `countries`: the consulted museum source names Mexican cannabis generally but does not specifically establish the brick practice there. The remaining countries and all listed history IDs are mentioned in the body.
+
+Documentation-review verification: revised body 574 words with five sections; build and diff checks passed; browser checked the revised page, Culture card and all three listed country profiles; Mexico no longer lists the entry; internal links resolve.
docs/research/history-articles-batch-2026-10-14.md +16 −0
@@ -0,0 +1,16 @@
+# Six additional history articles
+
+Added articles for existing entries, without changing the score timelines:
+
+- `denmark-christiania-junk-blockade-1979`: Christiania's own history dates the wider campaign to 1979–80; Jenny Künkel's CILIP research account dates the forty-day blockade to autumn 1979. The article preserves that distinction and includes coercion/exclusion as well as support for treatment. It does not imply national legalisation of cannabis.
+- `netherlands-ruigoord-saved-1973`: regional heritage source confirms preservation in 1973. Community programmes and Christiania's cultural association document the setting and later connections. The article links the Highlife Cup as a separate event and does not reproduce the delegation report's inaccurate claims about Dutch cannabis law.
+- `uk-burns-halloween-1786`: Glasgow's first-edition digitisation establishes publication in July 1786; Burns's poem and explanatory notes support the charm and comic episode. Publication is not dated as the origin of the practice, and no intoxication is inferred.
+- `france-paradis-artificiels-1860`: BnF's record confirms the 1860 Poulet-Malassis et de Broise edition. The literary analysis uses Baudelaire's public-domain text, accessed through the later collected edition on Wikisource, especially the opening, Morale and the introduction to his treatment of De Quincey. The source edition is distinguished from first publication; moral judgements are not presented as clinical findings.
+- `italy-hemp-law-2016`: original Gazzetta Ufficiale text, particularly articles 1, 2 and 4. Farmer protection for crops testing above 0.2% and within 0.6% is distinguished from retail permission; later flower restrictions are linked separately. This is historical analysis of the original framework, not current selling advice.
+- `france-cbd-flower-2022`: Conseil d'État's official explanation and judgment 444887. The article describes annulment of a specific blanket restriction, proportionality and the limits of the ruling, rather than general recreational legalisation or a claim that all CBD products are harmless.
+
+Each article is between 350 and 500 words, carries model/date and source metadata, and links to the relevant traditions or related history. Four cultural items were added to the curated Culture History list. Only the Ruigoord article uses a photograph, reusing the already inspected and credited Arthena CC BY-SA 4.0 village image. No substitute institutional stock photographs were added.
+
+## Verification
+
+Final build passed (2,763 pages). All six articles render in the browser with their sections and author/date metadata; generated HTML contains every added citation and all internal links resolve. The four curated items appear on Culture History. Article bodies are 391–415 words. `git diff --check` passed.
docs/research/history-articles-second-batch-2026-10-14.md +13 −0
@@ -0,0 +1,13 @@
+# Five more history articles
+
+Added 391–400 word articles for existing history entries:
+
+- **Paris hashish circle:** Gautier's public-domain narrative, Paris 1's research notice for Pointeau-Lagadec and the BnF record. Conventional 1844 beginning is distinguished from documented 1845–49 meetings. The timeline description was qualified and the research notice added; the famous-members list was removed because it encouraged an unsupported attendance-register reading.
+- **First official Cannabis Cup:** organiser's 2018 retrospective, Sensi Seeds' anniversary account and the World of Cannabis Museum's Rosenthal profile. The organiser uses 1988; Dronkers's recollection and the museum describe 1987. The Sensi article text was read from downloaded HTML because the web reader returned navigation rather than the relevant section. Retained 1988 as the conventional official entry, disclosed the earlier gathering, and updated the timeline description and corresponding Traditions paragraph. No exact day was inferred. No separate 1987 event was added because its timing remains unresolved.
+- **Ruigoord Highlife Cup:** Soft Secrets' contemporary report establishes Friday 24 June 2011, the third time hosted at Ruigoord. Its competition details are distinguished from its promotional and Orientalist presentation. Community programme and regional heritage sources establish the venue's wider context. This is not a High Times edition or a statutory exemption.
+- **Metelkova occupation:** Culture of Slovenia establishes the night of 10–11 September 1993. The article separates that event from Harmony's dated September 2017 observation of cannabis sales; neither establishes a legal exception or continuous market to the present.
+- **Amambay cultivation:** Garat's 2016 TNI briefing describes late-1960s establishment and explicitly leaves exact origins uncertain. The 1969 marker is qualified in the article, rather than presented as a first planting date. The separate TNI legal overview and existing 1988 history item supply the personal-possession distinction. No new national production estimate is inferred.
+
+Sources were opened and read, with supporting sources carried in article metadata. First mentions link to the relevant source or atlas/history entry. No stock images were added. The new narratives concern existing context events and do not change scores.
+
+Verification: build passed (2,764 pages); all five article pages checked in the browser; four sections each, 391–400 words. Generated citations and internal links verified. `git diff --check` passed.
docs/research/history-atlas-cards-2026-10-14.md +20 −0
@@ -0,0 +1,20 @@
+# Focused atlas cards on history pages
+
+The user requested event-specific cards, using the first Cannabis Cup as the example: The Cannabis Cup belongs there; Indica and sativa and Kush are background references.
+
+Added optional `history_cards` to Culture metadata, defaulting to no cards. It is a curated subset of `history`, validated against known history entries and the entry's own history list. `history` and prose links continue to serve the atlas reader, while only `history_cards` provides reverse cards on history pages. Removed automatic promotion of incidental culture links inside history article prose.
+
+Reviewed the 379 existing metadata associations and selected 95 direct subject associations across 36 atlas entries. Examples:
+
+- First Cannabis Cup → The Cannabis Cup only.
+- Ruigoord's preservation and Highlife event → Ruigoord.
+- Metelkova occupation → Metelkova; Slovenia's general referendum supplies no Metelkova card.
+- Paris club → Club des Hashischins; the generic Hashish word no longer appears there.
+- Chinese burial studies → Cannabis in the graves of Xinjiang, rather than the Daoist censer or Scythian custom.
+- Christiania's blockade, gang takeover and closure → Pusher Street; Denmark's general ban and medical pilot do not.
+- Religious rulings and exemptions → Rastafari; general cultivation or medical changes do not automatically qualify.
+- Generic US drug acts no longer collect Pot, Dope, Grass, Pakalōlō or Brick weed cards merely because those entries cite the law.
+
+Atlas entries without a directly corresponding event retain their ordinary history citations, country placement, related links and pictures. No events, scores or article text were removed.
+
+Verification: final build passed (2,764 pages). Every generated history page's atlas-card set was compared with the curated metadata; no incidental cards remain. Browser checked first Cannabis Cup (one card), Ruigoord, Metelkova, Paris circle and Jirzankal (one specific card each), plus the general US Controlled Substances Act and Danish medical pilot (no cards). `git diff --check` passed. History-to-history related suggestions also now use the focused card associations.
src/assets/culture/brick-weed-prensada.jpg +0 −0
Not shown: a picture, a generated file, or past the length shown per change.
src/assets/culture/harry-anslinger-1938.jpg +0 −0
Not shown: a picture, a generated file, or past the length shown per change.
src/assets/culture/jacques-joseph-moreau.jpg +0 −0
Not shown: a picture, a generated file, or past the length shown per change.
src/assets/culture/jose-mujica-2014.jpg +0 −0
Not shown: a picture, a generated file, or past the length shown per change.
src/assets/culture/moreau-by-gautier-1845.jpg +0 −0
Not shown: a picture, a generated file, or past the length shown per change.
src/assets/culture/raphael-mechoulam-2017.jpg +0 −0
Not shown: a picture, a generated file, or past the length shown per change.
src/content.config.ts +1 −0
@@ -229,4 +229,5 @@ const culture = defineCollection({
countries: z.array(z.string().regex(/^[a-z]{2}$/)).min(1), // file names in /countries, the most closely tied first
history: z.array(z.string()).default([]), // ids in changes.yaml
+ history_cards: z.array(z.string()).default([]), // events specifically about this subject; reverse cards on history pages
// other atlas entries to show beside this one, as "<section>/<slug>", closest first.
// Links in the text and shared history entries add more on their own; see docs/CULTURE.md.
src/lib/culture.js +10 −3
@@ -34,4 +34,10 @@ export const cultureEntries = (await getCollection("culture"))
return ch;
}).sort((a, b) => a.date.localeCompare(b.date));
+ const historyCards = d.history_cards.map((id) => {
+ const ch = changes.find((x) => x.id === id);
+ if (!ch) throw new Error(`${file}: history_cards names an unknown history entry "${id}"`);
+ if (!d.history.includes(id)) throw new Error(`${file}: history_cards entry "${id}" must also be in history`);
+ return ch;
+ });
// links in the text to pages of this site must lead somewhere
for (const [, kind, id] of (entry.body ?? "").matchAll(/\]\(\/(history|country)\/([^/)#]+)\/?[^)]*\)/g)) {
@@ -39,5 +45,5 @@ export const cultureEntries = (await getCollection("culture"))
if (!found) throw new Error(`${file}: the text links to /${kind}/${id}/, which does not exist`);
}
- return { entry, section, slug, url: `culture/${section}/${slug}/`, ...d, countries, history, sources: d.sources.map(sourceTuple) };
+ return { entry, section, slug, url: `culture/${section}/${slug}/`, ...d, countries, history, historyCards, sources: d.sources.map(sourceTuple) };
})
// featured entries first, by rank; then the rest alphabetically
@@ -73,6 +79,7 @@ for (const e of cultureEntries) {
export const cultureIn = (section) => cultureEntries.filter((e) => e.section === section);
-// the atlas entries a history entry belongs to: named in their `history`, or linked from their text
-export const cultureOfChange = (ch) => cultureEntries.filter((e) => e.history.includes(ch) || (e.entry.body ?? "").includes(`](/history/${ch.id}/`));
+// Reverse cards are editorial: background citations and incidental body links do not make
+// an atlas entry the subject of an event. Keep those links on the atlas page itself.
+export const cultureOfChange = (ch) => cultureEntries.filter((e) => e.historyCards.includes(ch));
// what the atlas has on one country, for its page
export const cultureOf = (c) => cultureEntries.filter((e) => e.countries.includes(c));
src/pages/culture/history.astro +4 −0
@@ -8,4 +8,8 @@ const base = import.meta.env.BASE_URL;
// Places where the history on this site already records culture meeting law. Each must exist.
const threads = [
+ ["uk-burns-halloween-1786", "Burns preserved a hemp-seed courtship charm in print. Cannabis appears as a familiar crop in seasonal folklore, with no intoxicating preparation involved."],
+ ["france-paradis-artificiels-1860", "Baudelaire made hashish a subject for an argument about imagination and the will. The artificial paradise attracted him as a literary problem, not a simple promise of creative freedom."],
+ ["netherlands-ruigoord-saved-1973", "Artists preserved a village threatened by harbour expansion. Ruigoord became a cultural enclave connected to Christiania and to documented cannabis gatherings."],
+ ["denmark-christiania-junk-blockade-1979", "Christiania excluded heroin while keeping cannabis. Its blockade combined community care with coercive rules about who could remain in the freetown."],
["us-assassin-of-youth-1937", "A narcotics commissioner presented cannabis as an “assassin of youth”. The surviving magazine page shows how official authority and frightening anecdotes were turned into propaganda."],
["us-reefer-madness-1938", "Tell Your Children, later known as Reefer Madness, made cannabis a screen menace. Later audiences turned its warnings into an underground joke."],
src/pages/history/[id].astro +2 −6
@@ -8,5 +8,5 @@ import { sourceTuple } from "../../lib/sources.js";
import SourceCitation from "../../components/SourceCitation.astro";
import { Image } from "astro:assets";
-import { cultureEntries, cultureOfChange, cultureSection } from "../../lib/culture.js";
+import { cultureOfChange, cultureSection } from "../../lib/culture.js";
import { relatedTo } from "../../lib/related.js";
@@ -32,10 +32,6 @@ const sources = [...ch.sources, ...(article?.data.sources ?? []).map(sourceTuple
const own = changes.filter((x) => x.country === c && !x.estimate).reverse();
const at = own.indexOf(ch);
-// The atlas entries this one belongs to, and those its article links to.
+// Only atlas subjects explicitly selected for this event. Body links remain ordinary links.
const culture = cultureOfChange(ch);
-for (const [, path] of (article?.body ?? "").matchAll(/\]\(\/(culture\/(?:language|traditions)\/[^/)#]+)/g)) {
- const e = cultureEntries.find((x) => x.url === path + "/");
- if (e && !culture.includes(e)) culture.push(e);
-}
const span = c ? 3 : 5;
const nearby = at < 0 ? [] : own.slice(Math.max(0, Math.min(at - span, own.length - 2 * span - 1)), Math.max(at + span + 1, 2 * span + 1));