Cannabis Freedom Index

Changelog dbc050dtree, with Claude Opus 5.5

Glossary page, 29 new terms, terms in Did you know texts, missing pages in search

- /glossary/: every glossary term and every word for cannabis with an atlas entry, A to Z, with a letter bar and an anchor per term; atlas words carry an "Atlas entry" badge and link to it. Linked from the footer, with a share image and a sitemap entry. - glossary.yaml gains 29 terms: the UN treaties and bodies, sentencing words, treatment and registers, medicines and products, excise and eradication. - Did you know texts mark words for cannabis and jargon too, once per case, rendered through one shared component (FactText.astro) on the home page, /did-you-know/ and country pages. - Search finds the glossary, every glossary term (by its other forms too), Charts, the world map page and the map poster, which were missing.

14 files of code, design or docs +203 −18 lines

Code, design and documents 14 files

glossary.yaml +91 −0
@@ -98,2 +98,93 @@
   also: [telemedicine platforms, online clinics]
   text: Doctors' consultations held online, through which some countries' patients get cannabis prescriptions without a visit.
+
+# Treaties and international bodies
+- id: Convention on Psychotropic Substances
+  also: [1971 Convention, Psychotropic Substances Convention]
+  text: The United Nations treaty of 1971 that controls psychotropic substances, among them THC itself; the cannabis plant is controlled by the treaty of 1961.
+- id: 1988 Convention
+  also: [Convention against Illicit Traffic]
+  text: The United Nations treaty of 1988 against drug trafficking. It also asks countries to make possession for personal use a criminal offence, within the limits of their constitutions.
+- id: UNODC
+  also: [UN Office on Drugs and Crime, United Nations Office on Drugs and Crime]
+  text: The United Nations Office on Drugs and Crime, which collects countries' drug figures and publishes the yearly World Drug Report.
+- id: International Narcotics Control Board
+  also: [INCB]
+  text: The UN body that watches whether countries keep the drug treaties, and criticises those that legalise cannabis.
+- id: Commission on Narcotic Drugs
+  also: [CND]
+  text: The UN commission where governments vote on which substances the drug treaties control. In 2020 it took cannabis out of the treaty's strictest list.
+- id: EUDA
+  also: [EMCDDA, European Union Drugs Agency]
+  text: The European Union Drugs Agency in Lisbon, called the EMCDDA until 2024, which gathers drug figures from the EU countries.
+- id: rescheduling
+  also: [rescheduled, reschedule]
+  text: Moving a drug to another list of a drug law or treaty, which changes how strictly it is controlled.
+
+# The law and the courts
+- id: personal use
+  also: [for personal use, own use]
+  text: Holding or using drugs for oneself rather than to supply anyone else. Many laws punish it far more lightly than dealing.
+- id: aggravated
+  also: [aggravating circumstances, aggravated offence, aggravated offense]
+  text: An offence treated as more serious because of its circumstances, such as selling to children, near a school or as part of a gang, which raises the punishment.
+- id: suspended sentence
+  also: [suspended sentences, suspended prison sentence]
+  text: A prison term the court imposes but does not enforce, unless the person offends again within a set period.
+- id: probation
+  text: A sentence served in the community under supervision instead of in prison, with conditions such as regular drug tests.
+- id: day fine
+  also: [day fines, day-fine]
+  text: A fine counted in days, each day worth a share of the person's income, so the same offence costs the rich more. Used in Germany and the Nordic countries.
+- id: amnesty
+  also: [amnesties]
+  text: A decision that frees people from punishment for past offences or clears their convictions, usually after a law has changed.
+- id: zero tolerance
+  text: A driving rule under which any trace of THC in the body is an offence, however long ago the cannabis was used.
+- id: paraphernalia
+  also: [drug paraphernalia]
+  text: Equipment for using drugs, such as pipes, bongs, papers and grinders. Some laws make selling or holding it an offence.
+
+# Treatment and registers
+- id: compulsory treatment
+  also: [compulsory rehabilitation, drug rehabilitation centre, rehabilitation centre]
+  text: Drug treatment ordered by a court or the authorities, sometimes in a closed rehabilitation centre, that the person cannot refuse.
+- id: dissuasion commission
+  also: [dissuasion commissions]
+  text: In Portugal, a panel of health and social workers to which the police send people caught with drugs for their own use, instead of to a court.
+- id: narcological register
+  also: [drug users' register]
+  text: In Russia and other former Soviet states, an official list of people known to use drugs, which can cost them a driving licence and some jobs.
+- id: harm reduction
+  text: Measures that reduce the damage drug use does, such as drug checking or advice on safer use, without first requiring people to stop.
+
+# Medicines and products
+- id: Sativex
+  also: [nabiximols]
+  text: A mouth spray made from cannabis extracts, with THC and CBD in roughly equal parts, approved in many countries for muscle stiffness in multiple sclerosis. Its generic name is nabiximols.
+- id: dronabinol
+  text: THC made as a medicine, mainly against nausea and loss of appetite.
+- id: nabilone
+  text: A man-made substance similar to THC, prescribed against nausea during chemotherapy.
+- id: magistral preparation
+  also: [magistral preparations, magistral]
+  text: A medicine made up by a pharmacist for one patient from a doctor's prescription, such as a cannabis oil dosed for that person.
+- id: THCA
+  also: [THC-A, tetrahydrocannabinolic acid]
+  text: The acid form of THC found in the raw plant. It does not make people high until it is heated, which some sellers use to sell strong flower as "hemp".
+- id: synthetic cannabinoids
+  also: [synthetic cannabinoid]
+  text: Laboratory chemicals that act like THC, often far stronger and more dangerous, sold under names such as Spice or K2.
+- id: hash oil
+  also: [honey oil]
+  text: A strong liquid extract of cannabis, with much more THC than the flower or hashish.
+- id: potency
+  text: How strong a cannabis product is, usually given as its share of THC.
+
+# The market and enforcement
+- id: excise
+  also: [excise tax, excise duty, excise duties]
+  text: A tax on a particular product, such as alcohol, tobacco or legal cannabis, charged on top of ordinary sales tax.
+- id: eradication
+  also: [eradication campaign, eradication campaigns]
+  text: Destroying cannabis crops, by hand, by burning or by spraying from the air, usually by police or soldiers.

src/components/CountryFacts.astro +2 −4
@@ -7,4 +7,5 @@ const { c } = Astro.props;
 const base = import.meta.env.BASE_URL;
 const facts = factsOf(c);
+import FactText from "./FactText.astro";
 ---
 {facts.length > 0 && (
@@ -19,8 +20,5 @@ const facts = factsOf(c);
           <p class="dyk-kicker">{f.kicker}</p>
           <p class="dyk-text">
-            {f.parts.map((part) => typeof part === "string" ? part
-              : part.country ? (part.country.id === c.id ? <b>{part.label}</b> : <a href={`${base}country/${part.country.slug}/`}>{part.label}</a>)
-              : part.entry ? <a href={`${base}${part.entry.url}`}>{part.label}</a>
-              : <a href={`${base}history/${part.change.id}/`}>{part.label}</a>)}
+            <FactText parts={f.parts} here={c.id} />
           </p>
           {f.culture ? <a class="dyk-more" href={`${base}${f.culture.url}`}>Read the entry →</a> : f.history && <a class="dyk-more" href={`${base}history/${f.history.id}/`}>The full story →</a>}

src/components/DidYouKnow.astro +2 −5
@@ -11,4 +11,5 @@ const { group } = Astro.props;
 const facts = getFacts().filter((f) => !group || f.group === group);
 const PER_PAGE = 3;
+import FactText from "./FactText.astro";
 ---
 <section class="dyk" id="dyk" aria-label="Did you know?" data-per-page={PER_PAGE}>
@@ -27,9 +28,5 @@ const PER_PAGE = 3;
         <p class="dyk-kicker">{f.kicker}</p>
         <p class="dyk-text">
-          {f.parts.map((part) => typeof part === "string" ? part
-            : part.country ? <a href={`${base}country/${part.country.slug}/`} data-country={part.country.id}>{part.label}</a>
-            : part.entry ? <a href={`${base}${part.entry.url}`}>{part.label}</a>
-            : part.entry ? <a href={`${base}${part.entry.url}`}>{part.label}</a>
-          : <a href={`${base}history/${part.change.id}/`}>{part.label}</a>)}
+          <FactText parts={f.parts} />
         </p>
         {f.culture ? <a class="dyk-more" href={`${base}${f.culture.url}`}>Read the entry →</a> : f.history && <a class="dyk-more" href={`${base}history/${f.history.id}/`}>The full story →</a>}

src/components/FactText.astro +15 −0
@@ -0,0 +1,15 @@
+---
+// The text of one "Did you know?" case (src/lib/facts.js): plain text, links to countries, history and
+// atlas entries, and marked words (src/lib/terms.js). `here` is the country whose page this is, named
+// in bold instead of linked. Marked words carry the class term-f, so the page-wide rule that marks a
+// word only once (src/middleware.js) leaves them alone: the home page shows its cases a few at a time.
+const { parts, here = null } = Astro.props;
+const base = import.meta.env.BASE_URL;
+---
+{parts.map((part) => typeof part === "string" ? part
+  : part.term ? (part.term.kind === "language"
+    ? <a class="term term-l term-f" href={`${base}${part.term.url}`}>{part.label}</a>
+    : <span class="term term-g term-f" tabindex="0" role="button" data-title={part.term.title} data-text={part.term.text}>{part.label}</span>)
+  : part.country ? (here && part.country.id === here ? <b>{part.label}</b> : <a href={`${base}country/${part.country.slug}/`} data-country={part.country.id}>{part.label}</a>)
+  : part.entry ? <a href={`${base}${part.entry.url}`}>{part.label}</a>
+  : <a href={`${base}history/${part.change.id}/`}>{part.label}</a>)}

src/layouts/Base.astro +1 −1
@@ -128,5 +128,5 @@ const schemas = jsonld ? [jsonld].flat() : [];
   <footer class="footer">
     <p>Cannabis Freedom Index · Informational only, not legal advice · Map data © Natural Earth</p>
-    <p class="footer-links"><a href={`${base}about/`}>About</a> · <a href={`${base}can-i/`}>Can I?</a> · <a href={`${base}compare/`}>Compare</a> · <a href={`${base}score-changes/`}>Score changes</a> · <a href={`${base}changelog/`}>Changelog</a> · <a href={`${base}rss.xml`}>RSS</a> · <a href={`${base}data/`}>Download the data</a> · <a href={`${base}llms.txt`} title="A plain-text guide to this site for language models">For AI: llms.txt</a> · <a href={`${base}data/#licence`}>Licence: data CC BY 4.0, code MIT</a></p>
+    <p class="footer-links"><a href={`${base}about/`}>About</a> · <a href={`${base}can-i/`}>Can I?</a> · <a href={`${base}compare/`}>Compare</a> · <a href={`${base}score-changes/`}>Score changes</a> · <a href={`${base}glossary/`}>Glossary</a> · <a href={`${base}changelog/`}>Changelog</a> · <a href={`${base}rss.xml`}>RSS</a> · <a href={`${base}data/`}>Download the data</a> · <a href={`${base}llms.txt`} title="A plain-text guide to this site for language models">For AI: llms.txt</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/facts.js +8 −2
@@ -12,4 +12,5 @@
 import { SEGMENTS, countries, changes, regions, segAvg, worldAvg } from "./countries.js";
 import { cultureEntries } from "./culture.js";
+import { markTerms } from "./terms.js";
 
 const seg = (id) => SEGMENTS.findIndex((s) => s.id === id);
@@ -193,7 +194,9 @@ const fromCulture = [
 ];
 
-// Turn the inline [[…|…]] links into parts the component can render, checking each target.
+// Turn the inline [[…|…]] links into parts the component can render, checking each target. Words
+// for cannabis and jargon in the plain text become { label, term } parts (src/lib/terms.js), once
+// per fact; a word whose atlas entry the fact already links is not marked again.
 function parse(text) {
-  return text.split(/(\[\[[^\]]+\]\])/).filter(Boolean).map((part) => {
+  const linked = text.split(/(\[\[[^\]]+\]\])/).filter(Boolean).map((part) => {
     const m = part.match(/^\[\[([^|]+)\|([^\]]+)\]\]$/);
     if (!m) return part;
@@ -212,4 +215,7 @@ function parse(text) {
     return { label: m[2], country };
   });
+  const seen = new Set(linked.filter((p) => p.entry?.section === "language").map((p) => `l:${p.entry.slug}`));
+  return linked.flatMap((part) => (typeof part !== "string" ? [part]
+    : markTerms(part, seen).map((w) => (w.term ? { label: w.t, term: w.term } : w.t))));
 }
 

src/lib/search-icons.js +1 −0
@@ -13,4 +13,5 @@ export const PAGE_ICONS = {
   "did-you-know/": "iconoir:light-bulb", "survey/": "iconoir:graph-up", "survey/take/": "iconoir:edit-pencil",
   "culture/": "iconoir:palette", "culture/people/": "iconoir:community", "culture/stories/": "iconoir:book", "sources/": "iconoir:bookmark-book",
+  "glossary/": "iconoir:book-stack", "charts/": "iconoir:stats-report", "map/": "iconoir:map", "map/poster/": "iconoir:media-image",
 };
 // pages that come in families: a part of the cultural atlas, a map view, a history period, a history tag, an exhibition

src/lib/terms.js +3 −0
@@ -53,4 +53,7 @@ let cache = null;
 const terms = () => (cache ??= load());
 
+// a term's anchor on the glossary page: "Per se limit" → "per-se-limit" (/glossary/#per-se-limit)
+export const termAnchor = (t) => String(t).normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
+
 // A plain text split into parts: { t } plain, { t, term } a marked word, where term is
 // { kind: "language", url, title } or { kind: "glossary", title, text }. `seen` holds the keys of

src/pages/did-you-know.astro +2 −5
@@ -13,4 +13,5 @@ const groups = [
   ["culture", "From the culture", "Words, customs, faiths and people from the cultural atlas."],
 ].map(([id, name, sub]) => ({ id, name, sub, facts: facts.filter((f) => f.group === id) }));
+import FactText from "../components/FactText.astro";
 ---
 <Base title="Did you know?" description={`${facts.length} curious cases from the Cannabis Freedom Index: strange rules, forgotten laws and what the scores show, each linked to its source page.`}>
@@ -32,9 +33,5 @@ const groups = [
               <p class="dyk-kicker">{f.kicker}</p>
               <p class="dyk-text">
-                {f.parts.map((part) => typeof part === "string" ? part
-                  : part.country ? <a href={`${base}country/${part.country.slug}/`}>{part.label}</a>
-                  : part.entry ? <a href={`${base}${part.entry.url}`}>{part.label}</a>
-            : part.entry ? <a href={`${base}${part.entry.url}`}>{part.label}</a>
-          : <a href={`${base}history/${part.change.id}/`}>{part.label}</a>)}
+                <FactText parts={f.parts} />
               </p>
               {f.culture ? <a class="dyk-more" href={`${base}${f.culture.url}`}>Read the entry →</a> : f.history && <a class="dyk-more" href={`${base}history/${f.history.id}/`}>The full story →</a>}

src/pages/glossary.astro +56 −0
@@ -0,0 +1,56 @@
+---
+// The glossary: every word the term layer explains (glossary.yaml) and every word for cannabis that
+// has an entry in Culture/Language, in one list from A to Z. The words with an article of their own
+// are marked "Atlas entry" and lead to it. The same words are marked in running text across the site
+// (src/lib/terms.js); each term here has an anchor, /glossary/#decriminalised.
+import { getCollection } from "astro:content";
+import Base from "../layouts/Base.astro";
+import { cultureEntries } from "../lib/culture.js";
+import { termAnchor as anchor } from "../lib/terms.js";
+
+const base = import.meta.env.BASE_URL;
+const plain = (t) => t.normalize("NFD").replace(/[̀-ͯ]/g, "");
+const sortKey = (t) => plain(t).replace(/^[^A-Za-z0-9]+/, "").toLowerCase();
+const letterOf = (t) => (/^[a-z]/.test(sortKey(t)) ? sortKey(t)[0].toUpperCase() : "#");
+
+const explained = (await getCollection("glossary")).map(({ data }) => ({ id: anchor(data.id), title: data.id, also: data.also ?? [], text: data.text, url: null }));
+const withArticle = cultureEntries.filter((e) => e.section === "language")
+  .map((e) => ({ id: anchor(e.title), title: e.title, also: e.also ?? [], text: e.summary, url: `${base}${e.url}` }));
+const all = [...explained, ...withArticle].sort((a, b) => sortKey(a.title).localeCompare(sortKey(b.title)));
+// grouped by first letter, for the letter bar and the headings
+const groups = [];
+for (const t of all) { const l = letterOf(t.title); if (groups.at(-1)?.l !== l) groups.push({ l, items: [] }); groups.at(-1).items.push(t); }
+---
+<Base title="Glossary" description={`${all.length} words explained in plain language: the terms of cannabis law, from decriminalised to per se limit, and words for cannabis from around the world, ${withArticle.length} of them with an article of their own.`}>
+  <main class="changes-page glossary-page">
+    <section class="hero hero-sm">
+      <p class="eyebrow">Cannabis Freedom Index</p>
+      <h1>Glossary</h1>
+      <p class="lede">The words of drug law and of the cannabis trade that this site uses, and the words people use for cannabis itself, each explained in a sentence or two. The {withArticle.length} marked "Atlas entry" have a whole article in the <a href={`${base}culture/language/`}>cultural atlas</a>. Across the site, the first time one of these words appears in a text it has a dashed underline: hover or tap it for the explanation.</p>
+      <nav class="gls-letters" aria-label="Terms by letter">
+        {groups.map((g) => <a href={`#letter-${g.l === "#" ? "num" : g.l}`}>{g.l}</a>)}
+      </nav>
+    </section>
+
+    <section class="section gls-body">
+      {groups.map((g) => (
+        <div class="gls-group" id={`letter-${g.l === "#" ? "num" : g.l}`}>
+          <h2 class="gls-letter">{g.l}</h2>
+          <dl class="gls-list">
+            {g.items.map((t) => (
+              <div class="gls-item" id={t.id}>
+                <dt>
+                  {t.url ? <a href={t.url}>{t.title}</a> : <a href={`#${t.id}`}>{t.title}</a>}
+                  {t.url && <span class="gls-badge">Atlas entry</span>}
+                  {t.also.length > 0 && <small>also {t.also.slice(0, 6).join(", ")}{t.also.length > 6 ? "…" : ""}</small>}
+                </dt>
+                <dd>{t.text}</dd>
+              </div>
+            ))}
+          </dl>
+        </div>
+      ))}
+    </section>
+  </main>
+</Base>
+

src/pages/og/[...path].png.js +1 −0
@@ -57,4 +57,5 @@ const cards = {
   changelog: page("Changelog", "Every change to the site, its data and its text, and exactly what changed.", [], `CHANGELOG · ${BRAND}`),
   data: page("Download the data", "Scores, history and a yearly panel since 1900 as CSV and JSON, free to reuse with attribution."),
+  glossary: page("Glossary", "The words of cannabis law explained in plain language, and the words for cannabis from around the world."),
   "did-you-know": page("Did you know?", "Strange rules, forgotten laws and what the scores show.", [[String(getFacts().length), "CURIOUS CASES"]]),
   articles: page("Articles", "Moments from the history of cannabis law told in full.", [[num(articles.size), "ARTICLES"]]),

src/pages/search.json.js +5 −1
@@ -6,4 +6,6 @@
 //   n: what is shown and matched   s: the smaller second line, matched too   u: address under the site root
 //   g: words matched but not shown (a history entry's tags)
+import { getCollection } from "astro:content";
+import { termAnchor } from "../lib/terms.js";
 import { places, countries, subunitsOf, changes, articles, historyPeriods, CHANGE_CATEGORIES, CHANGE_TAGS, estimateNote } from "../lib/countries.js";
 import { MAP_VIEWS } from "../lib/map-views.js";
@@ -23,5 +25,5 @@ const rows = [
     ["History", "Laws, rulings and treaties", "history/"], ["Can I?", "Plain answers per country", "can-i/"], ["Compare", "Countries side by side", "compare/"],
     ["Download the data", "Scores, history and yearly panel", "data/"], ["Score changes", "Every score change", "score-changes/"], ["Changelog", "Every change to the site, data and text", "changelog/"],
-    ["Articles", "The longer reads on single events", "articles/"], ["About", "What the project is, its aims and commitments", "about/"], ["Did you know?", "Curious cases from the index", "did-you-know/"], ["State of Cannabis", "The yearly survey and its results", "survey/"], ["Take the survey", "Anonymous, about ten minutes", "survey/take/"], ["Culture", "A cultural atlas of cannabis", "culture/"], ["Culture: People", "Officials, preachers, scientists and presidents", "culture/people/"], ["Culture: Stories", "The people behind the laws", "culture/stories/"], ["Sources", "Every source the index cites", "sources/"],
+    ["Articles", "The longer reads on single events", "articles/"], ["About", "What the project is, its aims and commitments", "about/"], ["Did you know?", "Curious cases from the index", "did-you-know/"], ["State of Cannabis", "The yearly survey and its results", "survey/"], ["Take the survey", "Anonymous, about ten minutes", "survey/take/"], ["Culture", "A cultural atlas of cannabis", "culture/"], ["Culture: People", "Officials, preachers, scientists and presidents", "culture/people/"], ["Culture: Stories", "The people behind the laws", "culture/stories/"], ["Sources", "Every source the index cites", "sources/"], ["Glossary", "The words of cannabis law, explained", "glossary/"], ["Charts", "The history and the scores in charts", "charts/"], ["World map", "Every country coloured by its score, full screen", "map/"], ["Map poster", "The world map as a printable poster", "map/poster/"],
   ].map(([n, s, u]) => ({ t: "p", n, s, u, ic: PAGE_ICONS[u] })),
   // the exhibitions: one row each, its rooms' names matched but not listed
@@ -35,4 +37,6 @@ const rows = [
     ...(e.image && { img: (await getImage({ src: e.image.src, width: 96, height: 96, fit: "cover", format: "webp" })).src }),
   })))),
+  // every term of the glossary, found by its other forms too, opens its place on the glossary page
+  ...(await getCollection("glossary")).map(({ data }) => ({ t: "p", n: data.id, s: `Glossary · ${data.text}`, u: `glossary/#${termAnchor(data.id)}`, ic: "iconoir:book-stack", ...(data.also?.length && { g: data.also.join(" ") }) })),
   // every map view, history kind, history tag and history period has its own page
   ...MAP_VIEWS.filter((v) => v.slug).map((v) => ({ t: "p", n: v.title, s: "Map", u: `map/${v.slug}/`, ic: MAP_VIEW_ICON })),

src/pages/sitemap.xml.js +1 −0
@@ -51,4 +51,5 @@ export function GET({ site }) {
     ["culture/", null],
     ["did-you-know/", null],
+    ["glossary/", null],
     // stories is a section too, listed above with its own file's date
     ...CULTURE_SECTIONS.filter((x) => !x.soon && x.id !== "stories").map((x) => [`culture/${x.id}/`, null]),

src/styles/global.css +15 −0
@@ -765,4 +765,19 @@ a.chip:hover { border-color: var(--muted); color: var(--text); }
 /* stronger than the solid underline of ordinary links in these texts */
 :is(.prose, .lede, .ctx, .why p) a.term:not(.cite a) { color: inherit; text-decoration: underline dashed; text-decoration-color: var(--muted); text-decoration-thickness: 1px; }
+/* the glossary (src/pages/glossary.astro): a letter bar, then terms with their explanations */
+.gls-letters { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 18px; }
+.gls-letters a { padding: 4px 9px; border: 1px solid var(--border); border-radius: var(--r); font: 400 13px var(--mono); color: var(--text-2); text-decoration: none; }
+.gls-letters a:hover { color: var(--text); border-color: var(--muted); }
+.gls-letter { margin: 26px 0 6px; font: 400 28px/1 var(--display); color: var(--muted); }
+.gls-list { margin: 0; display: grid; gap: 0; }
+.gls-item { display: grid; grid-template-columns: minmax(160px, 260px) minmax(0, 1fr); gap: 6px 24px; padding: 12px 0; border-top: 1px solid var(--border); scroll-margin-top: 90px; }
+.gls-item:target { background: color-mix(in srgb, var(--text) 5%, transparent); }
+.gls-item dt a { font: 400 20px/1.2 var(--display); color: var(--text); text-decoration: none; }
+.gls-item dt a:hover { text-decoration: underline; text-underline-offset: 4px; }
+.gls-item dt small { display: block; margin-top: 4px; color: var(--muted); font-size: 12.5px; }
+.gls-item dd { margin: 0; color: var(--text-2); font-size: 15px; line-height: 1.55; max-width: 72ch; }
+/* a word with an article of its own in the atlas */
+.gls-badge { display: inline-block; margin-left: 8px; padding: 1px 6px; border: 1px solid var(--border); border-radius: 2px; font: 400 10px var(--mono); text-transform: uppercase; letter-spacing: .08em; color: var(--text-2); vertical-align: 3px; }
+@media (max-width: 640px) { .gls-item { grid-template-columns: 1fr; } }
 /* one country spotlight inside its section */
 .ctx-spot + .ctx-spot { margin-top: 14px; }

    ↑↓ to move, ↵ to open.