/* Godebønner: the reading pages (/om/ and /risterier/).
 *
 * A separate sheet from styles.css on purpose. styles.css is 24 KB of index chrome: cards,
 * filters, a sticky masthead, a dialog. None of it applies to a page you read top to bottom,
 * and inlining it into two content pages would ship that weight three times.
 *
 * The tokens are the SAME values as styles.css and BRAND.md section 4, duplicated rather than
 * imported because every served page is self-contained (no second request, works when the file
 * is opened directly). If a token changes, it changes in all three.
 *
 * Dark mode used to live only here, and the note that stood in this spot explained why: these
 * pages are text, and text on a lit paper ground at night is the one place the warm palette
 * actually hurts. That reasoning was right about the need and wrong about the boundary. As of
 * 2026-08-07 the index carries the same "ristet" set, ported from this file rather than
 * invented, so the two surfaces answer the same system question the same way.
 *
 * WATCH THE TWO NAMES. This sheet and styles.css use --line and --line-soft for colours that
 * styles.css calls --line-2 and --line. They are NOT interchangeable across the two files:
 *
 *     pages.css --line       #e0d6c7  ==  styles.css --line-2   (BRAND.md "streg")
 *     pages.css --line-soft  #ece4d8  ==  styles.css --line
 *
 * Copying a rule between the sheets by name swaps one hairline weight for the other, and it
 * looks plausible either way. Q12 has the full inventory of where the two files disagree.
 */

:root {
  --paper: #faf7f2;
  --ink: #221d1a;
  --ink-2: #57504a;
  /* Was #8a8078: 3,61:1 on --paper against a 4,5:1 floor. Changed in the same commit as
     styles.css and brand/BRAND.md, because the three carry the same value and a fix in one
     of them is a fix the next session undoes by following either of the other two.
     ONLY this light :root. The dark block below remaps --ink-3 to #a89c8e, which measures
     6,29:1 on the ristet ground and already passes; darkening that one would break the
     half of the site that got this right first. */
  --ink-3: #6f6760;
  /* This sheet used `var(--card, #fff)` with no --card anywhere in it, so the fallback was
     the real value on every light render, and it was the pure white BRAND.md hard rule 7
     forbids. Named here, with the same hex styles.css uses, so the two sheets stop
     disagreeing about what a card is. */
  --card: #fffdfa;
  /* A roaster's own wordmark is a dark-ink PNG that we mirror and do not recolour, so its
     tile keeps a light ground in BOTH schemes. It is the one surface here that must not
     follow the page, and it is a token rather than a literal so that the exception is
     stated instead of looking like an oversight. */
  --mark-ground: #fffdfa;
  --line: #e0d6c7;
  --line-soft: #ece4d8;
  --accent: #a8451e;
  --warn: #8a6a30;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  /* The abe-marks' three colours. pipeline/inline.mjs swaps the baked hex in the mark
     files for these on the way in, so a silhouette mark drawn straight onto the page
     ground flips with the ground instead of needing a second file. The seal does NOT use
     these: its monkey is a hole in the disc, and flipping a badge's inside would invert
     the mark, so only its disc follows the page, through currentColor. */
  --gb-papir: #faf7f2;
  --gb-blaek: #221d1a;
  --gb-terracotta: #a8451e;
}
@media (prefers-color-scheme: dark) {
  :root {
    --paper: #241b15;
    --ink: #f0e7d8;
    --ink-2: #d8cbb8;
    --ink-3: #a89c8e;
    --line: #4d4138;
    --line-soft: #3a3028;
    --accent: #d06a50;
    --warn: #c2a262;
    /* Baked ink monkeys on the ristet ground would be a dark shape on a dark page. The
       mark inverts with the paper instead: light figures, dark muzzle line, and the
       accent follows the palette's own terracotta-lys. */
    --gb-papir: #241b15;
    --gb-blaek: #f0e7d8;
    --gb-terracotta: #d06a50;
    /* the same card ground styles.css uses at night: dark enough that terracotta stays
       legible as a link on top of it (4,6:1), which #2e231c was not (4,3:1) */
    --card: #281e18;
  }
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--accent); text-decoration: none; border-bottom: 1px solid currentColor; padding-bottom: 1px; }
a:hover { color: var(--ink); }

/* ---- the thin top bar -------------------------------------------------
 * Not a header menu. One link, back to the index, carrying the mark. The index's own
 * masthead is already dense (brand, search, taste wedge, stat line) and D20 spent real
 * effort cutting 351 px of chrome to 277 px; a nav bar would hand that back for two links
 * that almost nobody needs on the page where the coffee is. */
.topbar { border-bottom: 1px solid var(--line-soft); }
.topbar .in {
  max-width: 62rem; margin: 0 auto; padding: 15px 26px;
  display: flex; align-items: center; gap: 9px;
}
.topbar a { color: var(--ink); border: 0; display: flex; align-items: center; gap: 9px; font-size: 14px; }
.topbar a:hover .home { color: var(--accent); }
.topbar svg { display: block; color: currentColor; }
.home { color: var(--ink-2); }
/* Seglet in its own colour, which the index masthead cannot afford and this page can:
   nothing here uses terracotta to mean a price or a match, so there is no second meaning
   to collide with. The disc is drawn in currentColor, so this one line is the whole
   recolouring, and --accent already lightens to terracotta-lys at night. The mark carries
   no width of its own (the file is 240 px); 27 px sits just above the 14 px link text. */
.topbar .seglet { display: flex; color: var(--accent); flex-shrink: 0; }
.topbar .seglet svg { width: 27px; height: 27px; }

.page { max-width: 37rem; margin: 0 auto; padding: 76px 26px 56px; }
.page.wide { max-width: 62rem; }

.eyebrow {
  font-size: 11px; letter-spacing: 0.34em; text-transform: uppercase;
  color: var(--ink-3); margin: 0 0 24px;
}
h1 {
  font-family: var(--serif); font-size: 40px; line-height: 1.12; font-weight: 600;
  letter-spacing: -0.015em; margin: 0 0 30px; text-wrap: balance;
}
h2 {
  font-family: var(--serif); font-size: 23px; font-weight: 600; line-height: 1.25;
  margin: 0 0 18px; letter-spacing: -0.01em;
}
p { font-size: 17.5px; line-height: 1.75; margin: 0 0 24px; max-width: 33rem; }
p.lede { font-size: 19px; line-height: 1.7; }

/* the one pull-quote weight, from the origin story */
.turn {
  font-family: var(--serif); font-size: 25px; line-height: 1.4; color: var(--ink);
  margin: 44px 0 42px; max-width: 32rem; text-wrap: balance;
}

/* The closing line, centred in the band. Only this one: `.turn` is also the story's pivot
   paragraph, which stays left-aligned with the prose around it. `text-wrap: balance` matters more
   here than anywhere else on the page, because a centred two-line sentence with a one-word second
   line looks like a mistake rather than a cadence. */
.slutlinje {
  text-align: center; margin-left: auto; margin-right: auto;
  margin-top: 56px; margin-bottom: 48px; text-wrap: balance;
}

.sign {
  margin: 50px 0 0; padding-top: 24px; border-top: 1px solid var(--line);
  font-size: 15px; line-height: 1.7; color: var(--ink-2);
}
.sign .navn { color: var(--ink); }

/* ---- the second movement: after the story, the facts -------------------
 * A full-width rule marks where the essay ends and the mechanics begin, so the practical
 * sections do not read as more of the story and the story's last line keeps its landing. */
.turnover { border: 0; border-top: 1px solid var(--line); margin: 62px 0 54px; }

section { margin: 0 0 54px; }
section:last-of-type { margin-bottom: 0; }

/* The practical half of /om/ breaks out of the reading column, 2026-08-07.
 * 37 rem inside a 1280 px window leaves 54 % of the screen empty, and the sections below the
 * rule are the ones with parts to compare: overview means seeing things BESIDE each other, not
 * under each other. The essay above keeps the reading measure, because prose read top to bottom
 * wants a short line. The band moves as ONE block; splitting it would put the three h2s in two
 * different left edges, and a jump in the left edge halfway down a page reads as a bug. */
.vk-ud { width: min(58rem, calc(100vw - 52px)); margin-left: 50%; transform: translateX(-50%); }
/* headings keep the prose measure so a long one still breaks like a sentence, not like a banner */
.vk-ud > section > h2 { max-width: 33rem; }

/* Label left, sentence right: what "Hvad du kan bruge den til" became after Pascal read the live
 * page and said the three-part form gave no overview. The old form put the label in 11 px caps,
 * letterspaced to 0.28em, in --ink-3, so the element carrying the section's overview was the
 * least legible thing on the page (and --ink-3 is the token still failing contrast at 3.61 of a
 * required 4.5, Q11). Now the label has its own column in the serif and reads at full size, and
 * the three can be scanned in one pass. The table idiom is borrowed from /risterier/ rather than
 * invented, so the page that explains the site looks like the site. */
.qtbl { list-style: none; margin: 0; padding: 0; }
/* position:relative so the row is the containing block the stretched link resolves against */
.qtbl li { position: relative; display: grid; grid-template-columns: minmax(13rem, 20rem) 1fr;
  gap: 8px 32px; padding: 19px 14px 19px 0; margin: 0;
  border-top: 1px solid var(--line-soft); align-items: baseline;
  border-radius: 4px; transition: background-color .12s ease; }
.qtbl li:first-child { border-top: 1px solid var(--line); }
.qtbl li:last-child { border-bottom: 1px solid var(--line-soft); }
/* The WHOLE row is the link, the same trick /risterier/ uses on its table rows: a value
   proposition you can click is shown, one you can only read is told, and a row that lights up
   under the cursor tells you it goes somewhere before you have read a word of it. The tint is
   terracotta at 4 %, which is the exact value the roaster table already uses, so the two
   surfaces behave identically rather than merely similarly. */
.qtbl li:hover { background: rgba(168,69,30,.045); }
@media (prefers-color-scheme: dark) { .qtbl li:hover { background: rgba(208,106,80,.09); } }
.qtbl .s a::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.qtbl li:hover .q { color: var(--accent); }
.qtbl li:hover .s a { color: var(--ink); }
/* The ring is ADDED to the row, never moved to it. Suppressing the link's own outline and
   relying on :has() to put one back would leave keyboard focus invisible in any browser that
   does not support :has(), which is a silent accessibility regression of exactly the kind
   nothing in this repo would catch. The link keeps its native ring; the row lights up too where
   the selector is understood. */
.qtbl li:has(a:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.qtbl .q { font-family: var(--serif); font-size: 19px; font-weight: 600; letter-spacing: -.01em;
  color: var(--ink); text-wrap: balance; transition: color .12s ease; }
/* --ink-2, never --ink-3: Q11 is still open and --ink-3 measures 3.61 against a required 4.5.
   Nothing in this redesign that carries meaning is set in that token, so a later contrast fix
   can raise --ink-3 without any of these choices needing to be revisited. */
.qtbl .s { font-size: 16px; line-height: 1.6; color: var(--ink-2); }

/* The sum, shown, on the section that explains the sum. Not the full band width: it is three
 * short lines, and stretched across 58 rem the word "bliver" drifts into the middle of an empty
 * field and the two numbers stop looking related. A worked example has to read left to right in
 * one movement. Tabular figures so the three kilo prices line up under each other, which is what
 * makes the point land: the biggest bag is the cheapest coffee. */
/* Shrunk 2026-08-07 on Pascal's read of the live page: "ALT for stor". It was, and the reason
   is worth keeping. Sized for a section that led, it now sits inside a fold the reader has
   chosen to open, so it no longer has to announce itself, only to be legible. The kilo price
   went 23px to 17px and the whole block from 34rem to 25rem, which is about as wide as its
   longest line needs and no wider: an example stretched past its own content reads as a table
   of data rather than as one worked sum. */
.regnestykke { border-top: 1px solid var(--line-soft); margin: 0 0 22px; max-width: 30rem; }
.rk { display: grid; grid-template-columns: 10.5rem 1fr auto; gap: 16px; align-items: center;
  padding: 9px 0; border-bottom: 1px solid var(--line-soft); font-variant-numeric: tabular-nums; }
.rk .pose { font-size: 14.5px; color: var(--ink-2); }
.rk b { font-family: var(--serif); font-size: 17px; font-weight: 600; color: var(--ink);
  text-align: right; min-width: 5.5rem; }
/* The bar carries the argument: it is drawn from the KILO price, so as the bag on the left gets
   bigger the bar on the right gets shorter, and the page's whole thesis is a shape before it is
   a sentence. Terracotta because this is the one place on these two pages where a colour means
   a price, which is exactly what it means on the front page and nowhere else here. */
.rk-bar { display: block; position: relative; height: 6px; border-radius: 3px;
  background: var(--line-soft); overflow: hidden; }
.rk-bar i { position: absolute; left: 0; top: 0; bottom: 0; display: block;
  background: var(--accent); opacity: .75; border-radius: 3px; }

/* ---- the two folded sections -------------------------------------------------------------
 * `<details>` and no script, because these pages ship no JavaScript at all. Closed they are two
 * quiet rows; open they are the full reference. The contents stay in the DOM either way, so a
 * crawler still reads the honesty section and both roaster domains inside it.
 *
 * The heading keeps its own weight but drops to --ink-2 while closed, so a shut row reads as an
 * offer rather than as a heading with its section missing. */
.fold { border-top: 1px solid var(--line); }
.fold + .fold { border-top: 1px solid var(--line-soft); }
.fold:last-of-type { border-bottom: 1px solid var(--line); margin-bottom: 44px; }
.fold > summary {
  list-style: none; cursor: pointer; padding: 17px 14px 17px 0;
  display: flex; align-items: center; gap: 13px;
  border-radius: 4px; transition: background-color .12s ease;
}
/* same tint as the rows above, so everything clickable on this page behaves the same way */
.fold > summary:hover { background: rgba(168,69,30,.045); }
@media (prefers-color-scheme: dark) { .fold > summary:hover { background: rgba(208,106,80,.09); } }
/* Safari draws its own triangle from a pseudo-element that `list-style` does not reach */
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary h2 { margin: 0; font-size: 18px; color: var(--ink-2); letter-spacing: 0; }
.fold > summary:hover h2, .fold[open] > summary h2 { color: var(--ink); }
/* the chevron: two borders of an empty box, rotated. Right when shut, down when open. */
.fold > summary::before {
  content: ""; flex: 0 0 auto; width: 6px; height: 6px; margin-left: 2px;
  border-right: 1.5px solid var(--ink-3); border-bottom: 1.5px solid var(--ink-3);
  transform: rotate(-45deg); transition: transform .15s ease;
}
.fold[open] > summary::before { transform: rotate(45deg); }
.fold > summary:hover::before { border-color: var(--ink); }
/* the ring belongs on the row the keyboard is actually on */
.fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }
.fold-krop { padding: 4px 0 30px; }
@media (prefers-reduced-motion: reduce) { .fold > summary::before { transition: none; } }

/* the three-part value list: kept because /404/ still uses it (.fejl-veje), and it is the right
   shape there: three ways out of a dead end are choices, not rows to compare */
.uses { list-style: none; margin: 0; padding: 0; }
.uses li { margin: 0 0 26px; padding: 0; }
.uses .lbl {
  display: block; font-size: 11px; letter-spacing: 0.28em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 6px;
}
.uses p { margin: 0; font-size: 17px; max-width: 32rem; }

/* plain fact lists: the arithmetic, and the gaps */
.facts { list-style: none; margin: 0; padding: 0; }
.facts li {
  position: relative; padding-left: 20px; margin-bottom: 15px;
  font-size: 16.5px; line-height: 1.65; color: var(--ink); max-width: 33rem;
}
.facts li::before {
  content: ""; position: absolute; left: 0; top: 11px;
  width: 7px; height: 7px; border: 1px solid var(--ink-3); border-radius: 50%;
}

/* The three monkeys, closing the section that names the site's own gaps. Left-aligned with
   the text rather than centred: this is a drawing in a document, not a divider across it,
   and a centred figure in a 37 rem reading column reads as decoration. The file's own
   canvas has empty space above the figures, so the top margin is small and the negative
   room does the spacing. Sized in one place, here, because the SVG carries no width. */
/* Room UNDER the drawing since it moved above the folds: its terracotta baseline would otherwise
   sit a few pixels off the first fold's hairline, and two horizontal lines that nearly touch read
   as a collision rather than as two elements. The file's own canvas carries empty space above the
   figures, so the top margin stays small and the negative room does that half. */
.tre-maerke { margin: 34px 0 42px; }
.tre-maerke svg { display: block; width: 280px; height: auto; }

/* ---- 404 ----------------------------------------------------------------
 * The mark leads and the heading follows, which is the opposite of every other page here
 * and is the point: on this one surface the drawing IS the message (see nothing, hear
 * nothing, say nothing about an address we have never heard of) and the sentence under it
 * is the caption. 132 px, big enough to be read as a drawing and not as an icon.
 * The badge inverts whole with the palette, because tre-rund fills its monkeys as shapes
 * rather than punching them out of the disc the way the seal does. */
.fejl { padding-top: 64px; }
.fejl-mark { margin: 0 0 30px; }
.fejl-mark svg { display: block; width: 132px; height: auto; }
.fejl h1 { margin-bottom: 18px; }
/* The three ways out reuse the .uses list from /om/ rather than inventing a second list
   idiom: same label-over-sentence shape, so the site has one way of offering a choice. */
.fejl-veje { margin-top: 40px; }

/* ---- the roaster catalogue: three views of one dataset ------------------
 * Switched with radios and a sibling selector, no script, because these pages ship no JS at
 * all. All three panels stay in the served markup, so a crawler reads every roaster name and
 * every description whichever view is showing; only the paint changes. */
.vradio { position: absolute; opacity: 0; pointer-events: none; }
.viewbar {
  display: inline-flex; gap: 2px; padding: 3px;
  background: var(--line-soft); border-radius: 999px; margin: 0 0 26px;
}
.viewbar label {
  font-size: 13.5px; padding: 6px 17px; border-radius: 999px; cursor: pointer;
  color: var(--ink-2); white-space: nowrap; user-select: none;
}
.viewbar label:hover { color: var(--ink); }
.view { display: none; }
/* one pair per view; the radio precedes .viewbar and the panels, so ~ reaches both */
#v-liste:checked   ~ .viewbar label[for="v-liste"],
#v-maerker:checked ~ .viewbar label[for="v-maerker"],
#v-kort:checked    ~ .viewbar label[for="v-kort"] {
  background: var(--paper); color: var(--ink); font-weight: 600;
  box-shadow: 0 1px 2px rgba(34,29,26,.10);
}
#v-liste:checked   ~ .w-liste,
#v-maerker:checked ~ .w-maerker,
#v-kort:checked    ~ .w-kort { display: block; }
/* the focus ring belongs on the visible control, not the hidden input */
.vradio:focus-visible ~ .viewbar label[for="v-liste"] { outline: 2px solid var(--accent); outline-offset: 2px; }
#v-maerker.vradio:focus-visible ~ .viewbar label[for="v-maerker"],
#v-kort.vradio:focus-visible ~ .viewbar label[for="v-kort"] { outline: 2px solid var(--accent); outline-offset: 2px; }

/* every roaster's mark, mirrored locally; the 7 without one get their initials in the serif */
.mk, .tile {
  display: flex; align-items: center; justify-content: center;
  background: var(--mark-ground); border: 1px solid var(--line-soft); border-radius: 6px; flex: 0 0 auto;
}
.mk { width: 30px; height: 30px; overflow: hidden; padding: 3px; }
/* A ROASTER'S MARK IS WIDER THAN IT IS TALL, and a square slot is what made this page show a
   roaster's own mark worse than the two letters we substitute for the ones we do not have.
   Measured on the eleven mirrored marks: aspect ratios run 0,88 to 3,05, and four of the
   eleven are wordmarks rather than favicons. Inside the 30 px square (22 px of content after
   the border and padding) Prolog's 320x105 wordmark renders 22 x 7,2 px, letterforms about
   4 px tall, against a monogram whose cap height is about 8 px. Nothing here is broken; the
   slot simply asks a wordmark to be square.

   52 of 63 roasters have no mirrored mark today, so this rule is the rarer half of the
   column, which is the other reason the square went unnoticed.

   FROM 1060 px ONLY, and the threshold is measured rather than chosen. The name column sits
   at its own min-content width (206 px at 1280, driven by the longest domain,
   copenhagencoffeelab.com, which must not break), and so does every other column: the table
   is 966,9 px inside a 940 px page column, so nothing in it can grow by taking room from a
   neighbour. The 22 px this costs therefore comes out of the page margin, and below about
   1040 px there is no margin left to take it from. Measured at 1024: 15,1 px of slack to the
   viewport edge, and a horizontal scrollbar appears at 18. Under the threshold the square
   stays, which is the honest trade: the Maerker tab shows every mark at up to 122 x 56 px at
   any width, so nothing is unreachable on a phone, it is one tap away. */
@media (min-width: 1060px) {
  .mk { width: 52px; height: 32px; }
  .mk.mono { font-size: 13px; letter-spacing: .04em; }
}
/* width/height + object-fit rather than max-width alone: these are flex items, and a flex item
   keeps min-width:auto, so a 320 px-wide logo ignores max-width:82% and overflows a 30 px box.
   The visible result was a white square, because what survived the clip was the transparent
   top-left corner of a wide mark. Same trap that broke the masthead on phones. */
.mk img { width: 100%; height: 100%; min-width: 0; object-fit: contain; }
.mk.mono, .tile.mono {
  font-family: var(--serif); font-weight: 600; color: var(--ink-3);
  background: var(--paper); border-style: dashed;
}
.mk.mono { font-size: 11.5px; }

/* --- view 1: liste --- */
.tbl { width: 100%; border-collapse: collapse; }
.tbl th {
  text-align: left; font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 600; padding: 0 12px 9px; border-bottom: 1px solid var(--line);
}
.tbl td { padding: 9px 12px; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
/* the whole row is the link: position:relative makes the <tr> the containing block that the
   stretched ::after resolves against, so the target is the row and not just the name */
.tbl tbody tr { position: relative; }
.tbl tbody tr:hover { background: rgba(168,69,30,.045); }
.nm { display: flex; align-items: center; gap: 11px; }
.nm-t { display: flex; flex-direction: column; }
.rl { color: var(--ink); border: 0; font-weight: 600; font-size: 14.5px; }
.rl::after { content: ""; position: absolute; inset: 0; }
.tbl tbody tr:hover .rl { color: var(--accent); }
/* The roaster's own address. It was --ink-3 dead text; it is now a link and wears the sheet's
   own link idiom (accent, hairline underline), because a link that does not look like one is
   the defect this replaced, one step quieter.
   AA in both palettes, measured on all three grounds this line can sit on: --accent is
   5,56:1 on light paper, 5,21:1 on a hovered row, and 4,72:1 on the dark ground. The
   underline is currentColor, so it clears the 3:1 a non-text mark needs by the same margin
   the text clears 4,5.
   position/z-index is load bearing and invisible: .rl::after stretches over the entire row to
   make the row a link, and it would otherwise sit on top of this one and swallow the click.
   The negative margin against the padding buys a taller tap target on an 11 px line without
   moving anything.
   align-self, because .nm-t is a column flex: a stretched link would carry its underline the
   full width of the cell, well past the end of the word. */
.nm-t .h {
  font-size: 11px; align-self: flex-start;
  position: relative; z-index: 1;
  padding: 3px 4px; margin: -3px -4px;
}
.r-stale { font-size: 11px; color: var(--warn); }
.c-meta { font-size: 12.5px; color: var(--ink-2); white-space: nowrap; }
.c-meta span:not(:last-child)::after { content: " ·"; padding: 0 5px; color: var(--ink-3); }
/* The separator is a NO-BREAK SPACE, a dot, and then an ordinary space, and all three are
   doing something. idLine joins its facts with no whitespace between the elements, so the
   space inside this pseudo-element is the ONLY place the line can break; with the old " ·"
   the break fell before the dot, and the card printed "Hvidovre · grundlagt 2005" over
   "· kontracoffee.com". Latent until the roaster's own domain became a third fact and the
   line started wrapping. The no-break space glues the dot to the fact it follows, the
   ordinary one after it is where the line is allowed to turn. */
.rcard .meta span:not(:last-child)::after { content: "\00a0· "; padding: 0 1px; color: var(--ink-3); }
.c-meta .soft { color: var(--ink-3); }
.c-n { font-size: 13px; color: var(--ink-2); text-align: right; font-variant-numeric: tabular-nums; }
/* Økologi-kolonnen, 2026-08-22. Tallet i tekstfarve og nævneren i den stille grå, så
   "1 af 12" læses som ét udtryk hvor kun tælleren er påstanden. Højrestillet som antallet
   ved siden af, så de to tal-kolonner deler kant. */
.c-oe { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.c-oe .oe-n { color: var(--ink); font-weight: 600; }
.c-oe .oe-af { color: var(--ink-3); font-size: .92em; }
.kg { font-size: 12.5px; color: var(--ink-2); font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; }
.kg.soft { color: var(--ink-3); }
/* The fragt column. LEFT-aligned and not right, unlike every other cell that carries a
   number here, and the difference is the point: a right-aligned column of figures invites
   the eye to compare down it, and these values are not comparable with each other (a
   pakkeshop price, a threshold, and four kinds of not-knowing). Left-aligned, it reads as
   a statement per roaster, which is what it is. It also must never line up with the kilo
   price beside it: the whole design decision was that fragt is NOT part of that number. */
/* Wide enough that the longest value, "fri over 1.000 kr", stays on ONE line. Measured
   first, and the first version was not: at the default width "står i kassen" wrapped on
   most rows and every wrapped row grew, which breaks the same scanning alignment the kilo
   column's fixed row height exists to protect. The width comes off .c-bar, which had 34 %
   for a bar that reads the same at 26 %. */
.tbl td.fr { text-align: left; color: var(--ink-2); width: 10.5rem; }
/* nowrap on the VALUE, not the cell: 'fri over 1.000 kr' must stay whole, but the one
   contested row is longer than any width worth spending and is allowed to wrap. */
.tbl td.fr { overflow-wrap: normal; }
.tbl td.fr.soft { color: var(--ink-3); }
.tbl th.fr { text-align: left; }
/* display:block is load bearing: .bar is a <span>, and height does nothing on an inline box,
   so its percentage-positioned children would collapse against a zero-height parent */
.bar { display: block; position: relative; height: 15px; width: 100%; }
/* pages.js derives the axis step from this width (AKSE_BREDDE = 150 less 2 x 12 px of cell
   padding), so the two files have to be read together. Widening it to 200 was tried on
   2026-08-09 and reverted, and the measurement is worth keeping because the obvious repair
   does not work: this table's columns are already at their min-content widths at 1280 (name
   206, sted 225, fragt 223 of a 940 px page column), so the 50 px does not come out of them.
   It comes out of the page, and the table went from overflowing its own column by 26,9 px to
   overflowing it by 76,9 px. Moving sted's nowrap onto its individual facts, which should
   have freed 100 px, wrapped all 63 rows to two lines and freed nothing. Making this column
   wider means giving the desktop table declared widths and table-layout:fixed, the way the
   phone rule below already does. That is a bigger change than an axis needs. */
.tbl td.c-bar { width: 26%; min-width: 150px; }
.bar .track { position: absolute; left: 0; right: 0; top: 7px; height: 1px; background: var(--line-soft); }
.bar .span { position: absolute; top: 4px; height: 7px; border-radius: 4px; background: var(--ink); opacity: .82; }
/* The median, as a tick TALLER than the span rather than a gap cut into it, which is what
   the first version was and why it had to change. A paper-coloured notch is only legible if
   there is a bar left either side of it, and most bars here are shorter than the notch: this
   axis spans 150 to 1.900 kr/kg, so a roaster priced 290 to 310 draws 1,14 % of it, which is
   1,4 px at 1280 and 0,9 px at 375. The 2 px notch did not mark that bar, it deleted it.
   Seen at 375 on Den Braendte Boenne, Kama Kaffe and Fano MikroRisteri, all three drawing an
   empty track.

   So the mark adds ink instead of removing it: same colour as the span, 4 px proud at top and
   bottom, and what a reader sees is the pair of stubs standing clear of the bar. That works
   at every span width, including one narrower than the tick itself, and it cannot subtract
   anything. It is also the box-plot idiom, which is what this actually is.
   Contrast is not a question at 15,61:1 on light paper and 13,78:1 on the dark ground: it is
   the page's own ink either way. */
.bar .med { position: absolute; top: 0; height: 15px; width: 2px; margin-left: -1px; border-radius: 1px; background: var(--ink); }
.axis { position: relative; display: block; height: 15px; font-size: 10px; color: var(--ink-3); }
.axis span { position: absolute; transform: translateX(-50%); white-space: nowrap; }
.tbl tfoot td { border-bottom: 0; padding-top: 4px; }

/* --- view 2: mærker --- */
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); gap: 16px; }
/* position:relative is what makes the card a link target again after it stopped being one
   anchor. The heading's stretched ::after resolves against this box, so the whole card is
   still clickable, and the domain link inside it is now legal markup rather than an anchor
   nested in an anchor. */
.rcard {
  position: relative;
  display: flex; flex-direction: column; border: 1px solid var(--line-soft); border-radius: 14px;
  padding: 18px 18px 15px; background: var(--card); color: inherit;
}
.rcard:hover { border-color: var(--accent); color: inherit; }
.rcard h3 a { color: inherit; border: 0; padding: 0; }
.rcard h3 a::after { content: ""; position: absolute; inset: 0; }
.rcard:hover h3 a { color: var(--accent); }
/* the domain, above the card-wide overlay for the same reason the table's copy is */
.rcard .meta .sh { position: relative; z-index: 1; padding: 3px 4px; margin: -3px -4px; }
.tile { height: 56px; width: 100%; justify-content: flex-start; border: 0; background: none; margin-bottom: 13px; }
.tile img { width: auto; height: 100%; max-width: 122px; min-width: 0; object-fit: contain; object-position: left center; }
.tile.mono { font-size: 30px; border: 0; background: none; justify-content: flex-start; }
.rcard h3 { margin: 0 0 3px; font-family: var(--serif); font-size: 18.5px; font-weight: 600; letter-spacing: -.01em; }
.rcard .meta { font-size: 12px; color: var(--ink-3); margin: 0 0 11px; }
.rcard .meta .soft { color: var(--ink-3); }
.rdesc { font-size: 13px; line-height: 1.55; color: var(--ink-2); margin: 0 0 15px; flex: 1; }
.rprice { display: flex; align-items: baseline; gap: 7px; border-top: 1px solid var(--line-soft); padding-top: 11px; }
.rprice b { font-family: var(--serif); font-size: 25px; font-weight: 600; color: var(--ink); }
.rprice .u { font-size: 12px; color: var(--ink-2); }
.rprice .n { margin-left: auto; font-size: 12px; color: var(--ink-3); }
.rprice .soft { font-size: 13px; color: var(--ink-3); }

/* --- view 3: kort --- */
.land { fill: var(--line-soft); }

/* ---- the roaster catalogue -------------------------------------------- */
.cat { list-style: none; margin: 0; padding: 0; }
.cat > li { border-top: 1px solid var(--line-soft); padding: 26px 0; }
.cat > li:first-child { border-top: 1px solid var(--line); }

.r-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 12px; margin-bottom: 5px; }
.r-name { font-family: var(--serif); font-size: 22px; font-weight: 600; letter-spacing: -0.01em; }
.r-name a { color: var(--ink); border-bottom: 1px solid var(--line); }
.r-name a:hover { color: var(--accent); border-bottom-color: currentColor; }
.r-host { font-size: 12.5px; color: var(--ink-3); }

/* by · år, the identity line. Kept in one nowrap-per-atom row for the same reason the stat
   line is: a year must never be parted from the word that says which year it is. */
.r-id { font-size: 13.5px; color: var(--ink-2); margin: 0 0 9px; display: flex; flex-wrap: wrap; }
.r-id span { white-space: nowrap; }
.r-id span:not(:last-child)::after { content: " ·"; padding: 0 6px; color: var(--ink-3); }
.r-id .soft { color: var(--ink-3); }

.r-desc { font-size: 16px; line-height: 1.6; color: var(--ink); margin: 0 0 12px; max-width: 44rem; }
.r-desc.none { color: var(--ink-3); font-size: 14.5px; }

/* the numbers row: the reason the catalogue is a buying instrument and not a directory */
.r-nums { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 20px; font-size: 14px; }
.r-kg { font-family: var(--serif); font-size: 19px; color: var(--ink); }
.r-kg .u { font-size: 13px; color: var(--ink-2); padding-left: 3px; }
.r-n { color: var(--ink-2); }
.r-seen { color: var(--ink-3); font-size: 13px; }
.r-seen.is-old { color: var(--warn); }
.r-go { margin-left: auto; font-size: 14px; }

/* ==========================================================================
   /risterier/<slug>/, siden om ET risteri

   Den genbruger arket frem for at faa sit eget, og det er en beslutning og ikke en
   genvej. Kaffesiderne har deres eget lille ark af den modsatte grund: de laeses een
   gang fra et delt link paa en telefon, de er 617 stykker, og de deler ingen komponent
   med laesesiderne ud over farverne. Risteri-siden deler topbaren, kolofonen, .tbl,
   .kg, .mk og .note med /risterier/, og den er den SAMME side een risteri ad gangen.
   To ark ville betyde at et restyle af tabellen ramte den ene og ikke den anden, og
   det er praecis den uenighed oekologi-kolonnen kostede os i faa timer den 22. august.

   Arket LINKES ind paa de 63 sider i stedet for at blive skrevet ind i hver af dem, og
   det tal er hele grunden: pages.css er 68 KB, saa 63 indlejrede kopier er 4,3 MB i
   repoet og en aendring paa een linje bliver en diff paa 63 filer hver nat. Det er
   ordret den maaling build-kaffe-sider.mjs' hoved allerede har gjort for 610 sider.
   pipeline/inline.mjs skriver arket som /assets/laesesider.<hash>.css, hvor hashen er
   af arkets egne bytes, saa en aendret CSS faar en ny adresse og der findes ingen
   cachet kopi der kan serveres i stedet. /om/ og /risterier/ beholder deres indlejrede
   kopi: de er tre sider, og en indlejret <style> sparer dem en rundtur.
   ========================================================================== */

/* Maerket og adressen paa een linje under navnet. Samme .mk som tabellen, saa et
   risteri der ser sit eget maerke her ser noejagtig det der staar i kataloget. */
.rs-adresse { display: flex; align-items: center; gap: 12px; margin: -18px 0 26px; font-size: 14px; }
/* .h uden .nm-t foran sig: butiksLink deler klasse med tabellens, men tabellens regel
   er scopet til flex-kolonnen inde i en raekke, saa den naar ikke herud. */
.rs-adresse .h { font-size: 14px; }
.rs-desc { font-size: 17px; line-height: 1.7; color: var(--ink-2); margin: 0 0 30px; }

/* Fakta-listen. Ikke en tabel: raekkerne er ikke sammenlignelige med hinanden (en by, et
   aarstal, en fragtregel, et tal med naevner), saa en kolonne ville invitere til at laese
   ned ad dem, praecis den fejl fragt-kolonnen paa /risterier/ er venstrestillet for at
   undgaa.

   OG IKKE ET GITTER, hvilket den var i foerste omgang og blev maalt ned. Antallet af felter
   svinger med hvad risteriet HAR oplyst: syv paa nogle, fire paa andre, fordi et ukendt
   aarstal eller et ukendt sted udelader sit felt frem for at trykke en streg. Et
   auto-fill-gitter fylder saa den sidste raekke op med tomme celler, og med hairline-gitteret
   tegnet som en baggrund bag 1px mellemrum fik de tomme celler en synlig flade. Set paa April
   Coffee: en beige blok til hoejre for "Priserne bekraeftet", som laeser som en oplysning der
   mangler. Det er noejagtig den laesning hele siden er skrevet for at undgaa (GOALS.md 58: det
   vi ikke ved, siger vi, eller ogsaa staar der ingenting).

   Flex med ombrydning kan ikke danne en tom celle: der er kun de felter der findes. De to
   haarstreger over og under rammer blokken, og feltbredden er en min-width og ikke en
   kolonne, saa "Solroed Strand" og "12 af 12" hver faar den plads de har brug for. */
.rs-tal {
  display: flex; flex-wrap: wrap;
  border-top: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft);
  margin: 0 0 44px; padding: 6px 0;
}
.rs-tal > div { min-width: 8.5rem; padding: 11px 30px 11px 0; }
.rs-tal dt {
  font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 600; margin: 0 0 6px;
}
/* Serif og ikke sans: tallene er sidens indhold og ikke dens etiketter, samme greb som
   .kolofon-dt bruger paa datoen. tabular-nums, saa to sider ved siden af hinanden i to
   faneblade ikke rykker paa cifrene. */
.rs-tal dd {
  margin: 0; font-family: var(--serif); font-size: 17px; line-height: 1.35;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.rs-tal dd.soft { color: var(--ink-3); }

/* Kaffelisten. .tbl giver raekkerne, kanterne og hele-raekken-er-et-link; her staar kun
   det der er anderledes: navnet skal kunne bryde, fordi et produktnavn kan vaere langt
   ("Fazenda Esperanca Natural Ruby (drip pack)") og en telefon er 375 px bred.

   INGEN BREDDER PAA KOLONNERNE, og det er en rettelse frem for en udeladelse. Foerste
   version skrev `td:first-child { width: 100% }` og `.kg { width: 1% }`, som er det
   saedvanlige greb til "navnet tager resten, tallene fylder deres eget". Maalt paa 375 px:
   de to talkolonner faldt til 3 px klientbredde med 62 og 31 px indhold i sig, tabellen
   blev 408 px inde i en 331 px celle, og HELE dokumentet fik scrollWidth 430 mod
   clientWidth 375. Ingen enkelt elements kant laa uden for skaermen, saa en scanning efter
   "hvad stikker ud" fandt ingenting: det var kolonnerne der var klemt, ikke siden der var
   for bred. white-space: nowrap paa .kg er det der goer klemningen til overloeb frem for
   til ombrydning.

   Uden bredderne fordeler browserens egen auto-layout efter indhold, hvilket er den
   fordeling greb ovenfor forsoegte at efterligne. /risterier/ maaler 375/375/375 paa samme
   skaerm, og det er den maalestok denne side skal ligne. */
.rs-kaffer { margin: 0 0 40px; }
.rs-kaffer td:first-child { overflow-wrap: anywhere; }

/* "Set fra de andre 66". Listen er sidens eneste sted hvor sitet siger noget der IKKE
   staar i risteriets egen butik, og den skal laeses som en oplysning og ikke som en dom.
   Derfor: ingen ikoner, ingen farvekodning efter slags, ingen rangtal. Een streg i venstre
   kant og ellers samme serif som tallene ovenfor, saa afsnittet hoerer til siden.

   INGEN FARVE SKILLER "overraskende" FRA "unikhed". Klasserne staar i markup fordi de er
   sande og fordi en senere flade (den udgave der sendes) skal kunne vaelge paa dem, men at
   male "eneste af 67" roedt ville goere en optaelling til en udmaerkelse. BRAND 6.5: sitet
   viser, det udraaber ikke.

   KONTRASTEN ER REGNET, IKKE SKOENNET (haard regel 10, og den kraever maalingen mod den
   flade elementet FAKTISK sidder paa, som her er --paper i begge skemaer):
     --ink   paa --paper   15,61:1 lys   13,78:1 moerk   (selve paastanden)
     --ink-3 paa --paper    5,19:1 lys    6,29:1 moerk   (kaffenavnene)
   Begge over 4,5:1. Foerste udkast af denne kommentar PAASTOD 5,4:1 og 5,9:1 uden at have
   regnet; de rigtige tal staar ovenfor. En kommentar er ogsaa en paastand.

   Stregen i venstre kant maaler 1,18:1 og bestaar ALTSAA IKKE de 3:1 regel 10 kraever af en
   grafik der baerer betydning. Den er derfor ren dekoration og skal blive ved med at vaere
   det: hele paastanden staar i teksten, og listen laeses uaendret hvis stregen forsvinder.
   Skal stregen nogensinde skelne to slags linjer fra hinanden, skal den op paa et token der
   maaler 3:1 foerst. */
.rs-indsigt { list-style: none; margin: 0 0 14px; padding: 0; }
.rs-indsigt li {
  font-family: var(--serif); font-size: 16.5px; line-height: 1.5;
  color: var(--ink); margin: 0 0 14px; padding: 0 0 0 16px;
  border-left: 2px solid var(--line-soft);
}
/* Kaffenavnene bag paastanden. Mindre og i sans, saa de laeses som en henvisning ind i
   tabellen nedenfor og ikke som en fortsaettelse af saetningen. */
.rs-ind-kaffer {
  display: block; font-family: var(--sans); font-size: 13.5px; color: var(--ink-3);
  margin-top: 3px;
}

/* De to veje videre. Butikken foerst og med vaegt, fordi siden handler om dem og fordi
   et risteri der aabner sin egen side skal kunne komme hjem med et blik. */
.rs-videre { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin: 0 0 8px; }
.rs-koeb {
  background: var(--accent); color: var(--paper); border: 0;
  padding: 11px 18px; border-radius: 7px; font-size: 14.5px; font-weight: 600;
}
.rs-koeb:hover { color: var(--paper); filter: brightness(1.08); }
.rs-blandt { font-size: 14.5px; }
.rs-tilbage { margin: 26px 0 0; font-size: 14.5px; }

.note {
  font-size: 13.5px; line-height: 1.7; color: var(--ink-3);
  border-top: 1px solid var(--line); margin-top: 40px; padding-top: 22px; max-width: 44rem;
}
.note p { font-size: 13.5px; line-height: 1.7; color: var(--ink-3); margin: 0 0 12px; max-width: none; }
.note p:last-child { margin-bottom: 0; }

/* ---- foot ----
   The SAME four type steps as .foot in styles.css, so the two footers stop being two
   designs: 12 px label, 14 px body, the date as its own 14 px object, 14,5 px links.
   Q12 counted 32 distinct font sizes across the three pages and, above 21 px, not one
   shared step between the two stylesheets; a footer that exists on both surfaces is the
   one component that has no excuse for disagreeing with itself.

   Colour moved from --ink-3 to --ink-2 for the same reason it did on the front page:
   --ink-3 is 3,61:1 on --paper against a floor of 4,5 (Q11), and this colophon printed
   every one of its lines in it. --ink-2 is 7,41:1. In dark mode the tokens flip with the
   palette these pages already ship, so no value here is a hex. */
.kolofon { margin-top: 72px; color: var(--ink-2); font-size: 14px; line-height: 1.7; }
/* The wordmark is a link home and must not look like one; only focus reveals it, in the
   same accent idiom the view switcher already uses on these pages. No colour is set here
   on purpose: `.kolofon svg` below owns the wordmark's ink. */
.kolofon-home { display: inline-block; text-decoration: none; }
.kolofon-home:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 6px; }
.kolofon svg { display: block; width: 152px; height: auto; color: var(--ink); margin-bottom: 13px; }

/* The base for every line in the colophon. The three modifiers below are written as
   `.kolofon p.x` and not `.x` ON PURPOSE: `.kolofon p` is (0,1,1) and a bare `.x` is
   (0,1,0), so a class selector would LOSE to this rule and the colophon would silently
   keep its old 12 px --ink-3 while the stylesheet read as though it had been changed.
   That is the whole failure mode of a specificity bug: it looks done. */
.kolofon p { font-size: 14px; line-height: 1.7; color: var(--ink-2); margin: 0; max-width: none; }
.kolofon p.kolofon-lab { font-size: 12px; font-weight: 600; margin: 0 0 7px; }
.kolofon p.kolofon-tal { margin: 0 0 10px; }
.kolofon p.kolofon-dt { font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
/* the two doors out. This colophon had none until 2026-08-07, so the only way from /om/
   to /risterier/ was the back button. Same size, weight and underline idiom as .foot-end.
   Also `.kolofon p.x`, for the same specificity reason, and the anchor rule sits AFTER
   the generic `.kolofon a` below so it wins on source order at equal specificity. */
.kolofon p.kolofon-links { display: flex; flex-wrap: wrap; gap: 8px 22px; margin: 20px 0 0; padding-top: 16px; border-top: 1px solid var(--line-soft); }
.kolofon a { color: var(--ink-2); }
/* Stregen er --ink-3 og ikke --line, af samme grund som .mast-om og forsidens .foot-end
   fik den: `text-decoration: none` betyder at stregen er det ENESTE der siger at ordet er
   et link, altsaa "grafik der baerer betydning" under SC 1.4.11, som BRAND regel 10 giver
   3:1. Paa --line maalte den 1,34:1 i lys og 1,71:1 i moerk paa de byggede laesesider;
   paa --ink-3 maaler den 5,19:1 og 6,29:1.

   NAVNEKRYDSET, og det er derfor denne linje ikke saa ud som forsidens: den regel den
   kopierer (styles.css .foot-end a) skrev --line-2, og pages.css --line ER den farve.
   De to filer var altsaa enige om pixlen og uenige om navnet, hvilket er praecis den
   faelde advarslen oeverst i begge ark beskriver. --ink-3 hedder det samme i begge ark og
   i begge tilstande, saa efter denne rettelse er de to linjer ogsaa ens at laese. */
.kolofon-links a { color: var(--ink); text-decoration: none; font-size: 14.5px; font-weight: 600; border-bottom: 1px solid var(--ink-3); padding-bottom: 1px; }
.kolofon-links a:hover, .kolofon-links a:focus-visible { color: var(--accent); border-bottom-color: currentColor; }

/* The band folds back at 900, not at 620. Its own two-column rows run out of room long before
   the rest of the page does: a 13 rem label column beside a sentence needs about 58 rem to stay
   readable, so waiting for the phone breakpoint would leave a stretch of widths where the label
   column is squeezed to two words per line. Measured at a real 375 px viewport with device
   metrics (not a cropped headless screenshot, which is how this repo has been fooled before):
   the band is 331 px, every row is one column, and nothing overflows. */
@media (max-width: 900px) {
  .vk-ud { width: 100%; margin-left: 0; transform: none; }
  .qtbl li { grid-template-columns: 1fr; gap: 6px; }
  /* "bliver" goes rather than the numbers: the two figures ARE the sentence, and the word is
     scaffolding that a one-line row no longer needs */
  .rk { grid-template-columns: 1fr auto; }
  .rk .bliver { display: none; }
}

@media (max-width: 620px) {
  .page { padding: 46px 22px 44px; }
  h1 { font-size: 31px; }
  h2 { font-size: 21px; }
  p { font-size: 17px; }
  p.lede { font-size: 18px; }
  .turn { font-size: 22px; margin: 36px 0 34px; }
  .turnover { margin: 48px 0 42px; }
  section { margin-bottom: 44px; }
  .r-name { font-size: 20px; }
  /* the shop link drops under the numbers rather than squeezing them */
  .r-go { margin-left: 0; flex-basis: 100%; }

  /* Three views on a phone. The tab bar becomes full width so the three targets are big
     enough to hit; the table sheds its two least decisive columns (place and count) rather
     than shrinking every column until the price bar is a stub, which is what happened when
     the whole table was allowed to squeeze; the cards go to one per row. */
  .viewbar { display: flex; width: 100%; }
  .viewbar label { flex: 1; text-align: center; padding: 8px 6px; }
  /* By CLASS, never by position. These two selectors used to be nth-child(2) and (5), and
     when the fragt column was inserted between kilopris and antal, (5) stopped meaning the
     count and started meaning fragt, with no test and no visible error. Three columns go on
     a phone rather than two: place, fragt and count. Fragt is the deliberate one, and it is
     Pascal's standing desktop-first constraint (2026-08-01) rather than a judgement that
     fragt does not matter: a fourth column at 390 px squeezes the price bar to a stub, which
     is the exact failure the note under table-layout:fixed below records. The fact stays one
     tap away on any wider screen, and the note under the table still explains the column.

     Økologi-kolonnen kom til 2026-08-22 og går samme vej, af præcis den grund afsnittet
     ovenfor giver: en femte kolonne ved 390 px presser prisbjælken til en stump, og bjælken
     er det denne tabel findes for. Den er stadig ét tap væk på enhver bredere skærm, og
     kortvisningen på telefon er den flade den slags detalje hører hjemme på. */
  .tbl .c-meta, .tbl .fr, .tbl .c-n, .tbl .c-oe { display: none; }
  .tbl th, .tbl td { padding: 9px 6px; }
  /* table-layout:fixed, because an auto table sizes to its widest cell and refuses to shrink:
     measured 449 px inside a 390 px viewport, driven by the longest domain
     (copenhagencoffeelab.com) plus the longest name. Fixed makes the declared widths bind, and
     the domain goes: it is the one line here that names a place you are not being sent to. */
  .tbl { table-layout: fixed; }
  /* declared widths, because a fixed table divides the row by them. Measured against the
     longest real name (Copenhagen Coffee Lab) and the longest real range (260-1.000): the name
     needs room to break at its SPACES. An earlier pass used overflow-wrap:anywhere and got
     "Kom / ma / Kaff / e", which is worse than any overflow. */
  /* The three visible columns must sum to 100, or table-layout:fixed leaves the remainder
     as dead paper. Measured at 390 px: main renders the table 346 px wide (159 + 111 + 76),
     and the first class-based version of this rule came to 159 + 75 with 112 px of nothing,
     because the hidden count column's 22 % went to no one. Declared by class, and the
     header's colspan cell gets the 54 % its two body cells add up to. */
  .tbl th.h-navn, .tbl td:first-child { width: 46%; }
  .tbl th.h-kg { width: 54%; }
  .tbl td.c-bar { width: 32%; min-width: 0; }
  /* :not(.fr) matters: the fragt cell also carries .kg, so a bare .tbl td.kg sets the
     width of two different columns and the narrower one wins. */
  .tbl td.kg:not(.fr) { width: 22%; }
  /* The axis sheds every second label. pages.js sizes the step against the DESKTOP axis
     (126 px, three labels); the same three inside 77 px would overlap again, and a phone has
     no second place to put a number. Dropping alternates is the one thinning that keeps the
     series regular: what survives is exactly twice the step that was printed, so the numbers
     stay as round as they were, and nothing has to be re-derived per breakpoint. */
  /* ---- /risterier/<slug>/ paa en telefon ----
     RISTERI-SIDENS KAFFELISTE BAERER `.tbl` OG BLEV DERFOR RAMT AF KATALOGETS EGNE BREDDER,
     som er skrevet til fem kolonner den ikke har. Maalt paa 375 px foer disse linjer: navnet
     fik 46 % (152 px) og de to talkolonner 22 % hver, saa 10 % af tabellen stod som doedt
     papir mens "Sustainable Profile Coffee - Decaf" broed over fire linjer ved siden af en
     halvtom kilopris-kolonne. `table-layout: fixed` ovenfor er det der binder dem: uden den
     ville browserens auto-layout have fordelt efter indhold af sig selv.

     POSE-KOLONNEN GAAR, og det er den rigtige af de tre at fjerne. Kaffens EGEN side har hele
     posestigen med kilopris pr. stoerrelse, som er den flade det hoerer hjemme paa, og hvert
     navn i denne tabel er et link dertil. Kiloprisen bliver, fordi den er grunden til at
     listen findes. Samme afvejning som kataloget lige ovenfor traeffer, og af samme grund.

     Bredderne SUMMER TIL 100, hvilket er den fejl afsnittet over dette fangede i katalogets
     egen regel: en skjult kolonnes procenter gaar ikke til nogen, de bliver til papir. */
  .rs-kaffer .k-pose { display: none; }
  .tbl.rs-kaffer th:first-child, .tbl.rs-kaffer td:first-child { width: 66%; }
  .tbl.rs-kaffer th.k-pris, .tbl.rs-kaffer td.k-pris { width: 34%; }
  /* Fakta-felterne foelger .page's smallere polstring ned, saa der stadig er to pr. raekke
     paa 375 px frem for een lang soejle. */
  .rs-tal > div { min-width: 7.5rem; padding: 10px 18px 10px 0; }
  .rs-adresse { margin: -12px 0 22px; }

  .axis span:nth-child(even) { display: none; }
  /* The domain still goes on a phone, and the REASON changed on 2026-08-09 even though the
     rule did not. It used to go because it was dead text: "the one line here that names a
     place you are not being sent to". It is a link now, so that argument is spent, and what
     is left is only room. Measured at 375: the name column gives its text 79,5 px, and 40 of
     the 63 domains are wider than that (copenhagencoffeelab.com wants 139). Truncating two
     thirds of the addresses to an ellipsis would print 40 domains that are not the domain.
     Nothing is unreachable: the Maerker tab is one tap away and carries all 63 links at full
     card width, measured at 375 with zero overflowing. If this column ever gets real width on
     a phone, delete this rule rather than working around it. */
  .nm-t .h { display: none; }
  .nm-t { min-width: 0; }
  .nm { gap: 8px; }
  .mk { width: 26px; height: 26px; }
  .rl { overflow-wrap: break-word; }
  .cards { grid-template-columns: 1fr; }
}

/* Invitationen i laesesidernes kolofon. Samme tekst og samme adresse som forsiden, fra
   samme noegler; kun indpakningen er denne flades egen. */
.kolofon-forslag { margin: 14px 0 0; font-size: 13.5px; line-height: 1.6; }
/* Denne linje stod paa `var(--line-2)`, og pages.css definerer ikke --line-2 NOGET STED:
   navnet findes kun i advarslen oeverst i filen om at styles.css bruger det om en anden
   farve. En var() uden reserve, der peger paa et token som ikke findes, goer hele
   shorthanden ugyldig ved beregningstid, saa border-bottom-style falder til sin
   initialvaerdi `none` og bredden bliver 0. Maalt paa de byggede sider, begge tilstande:
   borderBottomStyle "none", borderBottomWidth "0px", --line-2 slaar op som "".

   Adressen havde altsaa `text-decoration: none` OG ingen streg. Det er ikke et link med
   for lav kontrast som de fem andre i denne runde: det er et link uden nogen markering
   overhovedet, og det er den vaerste af de seks, fordi der ikke er noget at maale. En
   regel der greper som til stede og erklaerer intet.

   Rettet til --ink-3, som findes i begge ark og begge tilstande, saa adressen faar den
   samme streg som forsidens .foot-mail (5,19:1 lys, 6,29:1 moerk). Det er ogsaa den
   eneste af de fire rettelser i denne runde der aendrer noget MAALBART i layoutet: en
   streg der ikke blev tegnet, tegnes nu, saa elementets boks bliver 1 px hoejere. Maalt
   ved 375 px, hvor kolofonen ombryder, foer og efter. */
.kolofon-forslag a { color: var(--ink); font-weight: 600; text-decoration: none; border-bottom: 1px solid var(--ink-3); white-space: nowrap; }
.kolofon-forslag a:hover { color: var(--accent); border-bottom-color: currentColor; }

/* ======================================================================
   Aberne, i bevægelse: læsesiderne
   ======================================================================
   Søsteren til blokken i styles.css, og den bærer resten af sitets grafik.
   Ejerens ord 2026-08-07: "jeg vil gerne have over det hele". Fire flader, og
   de får ikke det samme, fordi de tre mærker betyder tre forskellige ting.

   Nul JavaScript, som på forsiden. To udløsere findes i forvejen i markuppen:
   :hover, og at et element går fra display:none til synligt, hvilket er præcis
   hvad visningsskifteren på /risterier/ gør (.view{display:none} plus
   #v-kort:checked ~ .w-kort{display:block}). En animation på noget der bliver
   vist starter dér. Målt 2026-08-07 på den serverede side.

   Koreografien selv, med begrundelserne, ligger i brand/monkeys/bevaegelse/
   og er vist rendret i brand/monkeys/BEVAEGELSE.html. Ét led = én <g> der
   drejer om et punkt FIGUREN udpeger som --x/--y, så der står ikke ét
   koordinat her.
   ====================================================================== */
.gb-led {
  --ind: translate(var(--x), var(--y));
  --ud: translate(calc(-1 * var(--x)), calc(-1 * var(--y)));
  transform: var(--ind) rotate(0deg) var(--ud);
}

/* ---- topbjælkens segl, alle fire læsesider ----------------------------
   Samme regel som forsidens masthead, og af samme grund: en bjælke man læser
   forbi må ikke bevæge sig af sig selv. Her er seglet 27 px, altså endnu
   mindre end forsidens 52, så løftet på 2,5 brugerenheder er under en halv
   pixel og det man ser er lysskiftet alene. Det er nok: mærket svarer, når man
   peger på det, og ellers står det stille. */
.topbar a:hover .seglet .gb-damp path,
.topbar a:focus-visible .seglet .gb-damp path { animation: gb-damp-drift 2.8s ease-in-out both; }
.topbar a:hover .seglet .gb-damp path:nth-child(2),
.topbar a:focus-visible .seglet .gb-damp path:nth-child(2) { animation-delay: .16s; }
.topbar a:hover .seglet .gb-damp path:nth-child(3),
.topbar a:focus-visible .seglet .gb-damp path:nth-child(3) { animation-delay: .32s; }

@keyframes gb-damp-drift {
  0%, 100% { opacity: .5; transform: none; }
  50% { opacity: 1; transform: translateY(-2.5px); }
}

/* ---- /om/: vendingen, 280 px ------------------------------------------
   Sidens største grafik og den ENESTE flade hvor de tre aber har plads til at
   spille en scene. Ordsproget handler om ikke at se; sitet handler om at se.
   BESLUTNING.html siger det med rene ord, og /om/ er siden der forklarer netop
   hvorfor sitet findes, så vendingen er sidens egen tekst, tegnet.

   Den tredje DRIKKER IKKE. Tre aber på en linje hvor den tredje drikker af en
   kop er Terres de Cafés logo, og runde 3 blev brugt på at komme væk fra netop
   den komposition. Den løfter koppen ti grader. Det er en skål, ikke en synk.

   To led pr. arm. En arm der kun drejer om skulderen svinger hånden udad i en
   cirkel som en vinkende dukke, hvilket er untemplateds D3-indvending mod at
   dreje lemmer overhovedet. Med underarmen som sit eget led inde i overarmen
   folder albuen sig, og hånden kommer NED forbi ansigtet. Vinklerne er valgt
   ved at rendre fire varianter og se på dem, ikke ved at regne: 52 og 62 grader
   lader ansigtet blive helt frit, mens 40 og 34 efterlod armen som en bjælke
   tværs over brystet.

   Udløseren er :hover på figuren, ikke sidens indlæsning. /om/ er en tekst man
   læser, og en animation der starter af sig selv i første skærmbillede er
   nøjagtig den afbrydelse GOALS' tredje søjle forbyder. */
.tre-maerke:hover .gb-abe1 .gb-overarm { animation: gb-ned-over 2.6s cubic-bezier(.35, 0, .25, 1) both; }
.tre-maerke:hover .gb-abe1 .gb-underarm { animation: gb-ned-under 2.6s cubic-bezier(.35, 0, .25, 1) both; }
.tre-maerke:hover .gb-abe1 .gb-hoved { animation: gb-kig 2.6s cubic-bezier(.35, 0, .25, 1) .12s both; }
.tre-maerke:hover .gb-abe2 .gb-overarm { animation: gb-ned-over-v 2.6s cubic-bezier(.35, 0, .25, 1) .45s both; }
.tre-maerke:hover .gb-abe2 .gb-underarm { animation: gb-ned-under-v 2.6s cubic-bezier(.35, 0, .25, 1) .45s both; }
.tre-maerke:hover .gb-abe2 .gb-hoved { animation: gb-kig 2.6s cubic-bezier(.35, 0, .25, 1) .57s both; }
.tre-maerke:hover .gb-abe3 .gb-arm { animation: gb-arm-loeft 2.4s cubic-bezier(.35, 0, .3, 1) .9s both; }
.tre-maerke:hover .gb-abe3 .gb-damp { animation: gb-damp-loeft 2.4s cubic-bezier(.35, 0, .3, 1) .9s both; }

/* Hænderne ender inde i kroppens egen silhuet, og det er meningen: figuren er
   ét fladt blækfelt, så armen smelter tilbage i kroppen i stedet for at stikke
   ud som en pind. */
@keyframes gb-ned-over {
  0%, 10% { transform: var(--ind) rotate(0deg) var(--ud); }
  100% { transform: var(--ind) rotate(52deg) var(--ud); }
}
@keyframes gb-ned-under {
  0%, 10% { transform: var(--ind) rotate(0deg) var(--ud); }
  100% { transform: var(--ind) rotate(-62deg) var(--ud); }
}
@keyframes gb-ned-over-v {
  0%, 10% { transform: var(--ind) rotate(0deg) var(--ud); }
  100% { transform: var(--ind) rotate(-52deg) var(--ud); }
}
@keyframes gb-ned-under-v {
  0%, 10% { transform: var(--ind) rotate(0deg) var(--ud); }
  100% { transform: var(--ind) rotate(62deg) var(--ud); }
}
/* Et lille nik nedad. De kigger på tallene, ikke ud i luften. */
@keyframes gb-kig {
  0%, 18% { transform: var(--ind) rotate(0deg) var(--ud); }
  100% { transform: var(--ind) rotate(5deg) var(--ud); }
}
@keyframes gb-arm-loeft {
  0%, 10% { transform: var(--ind) rotate(0deg) var(--ud); }
  44%, 70% { transform: var(--ind) rotate(-10deg) var(--ud); }
  100% { transform: var(--ind) rotate(0deg) var(--ud); }
}
@keyframes gb-damp-loeft {
  0%, 10% { transform: var(--ind) rotate(0deg) var(--ud); }
  44%, 70% { transform: var(--ind) rotate(10deg) var(--ud); }
  100% { transform: var(--ind) rotate(0deg) var(--ud); }
}

/* ---- /404/: kun dampen, og det er en indholdsbeslutning ----------------
   Her er mærket ikke pynt, det ER beskeden: hverken se, høre eller sige noget
   om en adresse vi aldrig har hørt om. Tager de to hænderne ned på DEN side,
   modsiger tegningen sætningen under sig. Så hænderne bliver oppe, og kun
   koppen damper. Det er den ene flade på sitet hvor det rigtige svar var
   mindre bevægelse, ikke mere. */
.fejl-mark:hover .gb-abe3 .gb-damp path { animation: gb-damp-drift 3s ease-in-out both; }
.fejl-mark:hover .gb-abe3 .gb-damp path:nth-child(2) { animation-delay: .18s; }
.fejl-mark:hover .gb-abe3 .gb-damp path:nth-child(3) { animation-delay: .36s; }

/* ---- /stats/: hele vendingen, uden tilbageholdenhed --------------------
   Siden handler bogstaveligt om hvem der kigger, så de tre aber der holder op
   med ikke at se er sidens joke og ikke en dekoration. Den er ulistet, har
   ingen priser at læse forbi og ingen fremmede læsere, så den spiller af sig
   selv ved ankomst i stedet for at vente på en mus. Det ene sted på sitet hvor
   det er rigtigt. */
.trafik-mark .gb-abe1 .gb-overarm { animation: gb-ned-over 2.6s cubic-bezier(.35, 0, .25, 1) .3s both; }
.trafik-mark .gb-abe1 .gb-underarm { animation: gb-ned-under 2.6s cubic-bezier(.35, 0, .25, 1) .3s both; }
.trafik-mark .gb-abe1 .gb-hoved { animation: gb-kig 2.6s cubic-bezier(.35, 0, .25, 1) .42s both; }
.trafik-mark .gb-abe2 .gb-overarm { animation: gb-ned-over-v 2.6s cubic-bezier(.35, 0, .25, 1) .75s both; }
.trafik-mark .gb-abe2 .gb-underarm { animation: gb-ned-under-v 2.6s cubic-bezier(.35, 0, .25, 1) .75s both; }
.trafik-mark .gb-abe2 .gb-hoved { animation: gb-kig 2.6s cubic-bezier(.35, 0, .25, 1) .87s both; }
.trafik-mark .gb-abe3 .gb-arm { animation: gb-arm-loeft 2.4s cubic-bezier(.35, 0, .3, 1) 1.2s both; }
.trafik-mark .gb-abe3 .gb-damp { animation: gb-damp-loeft 2.4s cubic-bezier(.35, 0, .3, 1) 1.2s both; }

/* ---- /risterier/: kortet ----------------------------------------------
   Den ene animation på sitet der FORTÆLLER noget frem for at pynte: den siger
   "her er de, spredt ud over landet". Udløseren er gratis og allerede bygget:
   kortvisningen ligger i display:none indtil man klikker på "Kort", så
   prikkerne lander netop i det sekund man har bedt om at se dem.

   Kysten tegner IKKE sig selv. Den oplagte idé var stroke-dashoffset på
   landomridset, men .land er én fyldt path på 37 kB der indeholder hver eneste
   ø: at strege den ville tegne Danmark som en tråd og lade Fyn dukke op efter
   Jylland i en tilfældig rækkefølge. Landet toner op som ét, og prikkerne er
   det der bevæger sig.

   --i sættes i pages.js i rækkefølge fra vest mod øst, så kortet fyldes fra
   Jylland ind mod København. Rækkefølgen er geografi og ikke datarækkefølge,
   fordi det er geografi kortet handler om. */
#v-kort:checked ~ .w-kort .land { animation: gb-land 1s ease-out both; }
/* Forsinkelsen er en BRØKDEL af et fast vindue, ikke et fast tal pr. prik, og det er en
   rettelse frem for en finesse. Den gamle regel var .055s pr. prik, tunet da kortet havde 18:
   det gav 1,7 sekund. Med 46 prikker gav den samme regel 3,3 sekund, og fordi rækkefølgen er
   vest mod øst var den SIDSTE prik Bornholm, altså præcis den ene prik hele funktionen findes
   for. En animation der skalerer med datamængden bliver langsommere hver gang arbejdet lykkes.
   --n sættes af pages.js på .finder og er antallet af tegnede prikker. */
#v-kort:checked ~ .w-kort .dot { animation: gb-prik-ned .5s cubic-bezier(.2, 1.5, .4, 1) both;
  animation-delay: calc(.2s + 1.1s * var(--i, 0) / var(--n, 46)); }

@keyframes gb-land { from { opacity: 0; } to { opacity: 1; } }
/* Prikken lander med et lille overskud (kurven går over 1) og sætter sig.
   transform-box: fill-box gør elementets eget centrum til omdrejningspunkt, så
   en <circle> kan skaleres uden at flytte sig ud af landet. */
@keyframes gb-prik-ned {
  from { opacity: 0; transform: scale(.2); }
  to { opacity: 1; transform: scale(1); }
}
.w-kort .dot { transform-box: fill-box; transform-origin: 50% 50%; }

/* ---- kolofonens ordmærke ----------------------------------------------
   Et ordmærke der skriver sig selv er den mest udbredte af alle web-animationer
   og ville få mærket til at ligne en skabelon, hvilket er den ene ting det ikke
   må. Så bogstaverne rører sig ikke. Kun ø'ets skråstreg tegner sig selv, og
   den er ikke tilfældigt valgt: det er bønnens revne, brandets eget tegn, og
   det er den ene streg i ordet der ER en streg og ikke et bogstav.

   Kun på hover af linket, altså når man alligevel er på vej hjem.

   Tallet 1 og ikke stregens rigtige længde: pages.js giver pathen pathLength="1",
   så SVG regner længden som 1 uanset hvordan kurven ser ud. Målt i browseren er
   den i dag 84,9 enheder lang, og det tal ville have stået her og været forkert
   den dag nogen retter kurven, uden at nogen opdagede det. */
.kolofon-home svg path:last-of-type { stroke-dasharray: 1; }
.kolofon-home:hover svg path:last-of-type,
.kolofon-home:focus-visible svg path:last-of-type { animation: gb-revne 1.1s cubic-bezier(.4, 0, .2, 1) both; }
@keyframes gb-revne {
  from { stroke-dashoffset: 1; }
  to { stroke-dashoffset: 0; }
}

/* ---- reduceret bevægelse ----------------------------------------------
   pages.css havde kun én regel her (foldepilens transition), fordi kun den
   bevægede sig. Nu bevæger fire flader sig. Hvilestillingerne er alle steder
   mærket som det er tegnet, så det at slå animationerne fra lander rigtigt:
   ingen af retningerne her har en engageret hvilestilling, i modsætning til
   forsidens tomme tilstand. */
@media (prefers-reduced-motion: reduce) {
  .gb-led,
  .gb-damp,
  .gb-damp path,
  .land, .dot,
  .kolofon-home svg path:last-of-type { animation: none !important; }
  .kolofon-home svg path:last-of-type { stroke-dasharray: none; }
}

/* ---------------------------------------------------------------------------
   "Find et risteri": kortet, søgningen og listen som én visning (2026-08-09).
   Erstatter det gamle to-figurers kort med udsnit. Formen er en butiksfinder:
   rulleliste til venstre, kort til højre, og de to peger på hinanden.

   ÉN REGEL STYRER UDSEENDET, og den er ikke kosmetisk. Når et filter er sat,
   FORSVINDER de fravalgte prikker ikke: de fader til en hvisken. Sitets egen
   regel er at en besøgende aldrig må få vist et mindre katalog end der findes
   uden at få det at vide, og et kort kan overholde den bogstaveligt, hvor en
   liste kun kan sige det med ord. Man vælger Bornholm og ser stadig hele
   landet ligge der, nedtonet. Det er samtidig det pæneste: figuren beholder
   sin form i stedet for at gå i stykker ved hvert klik.
   --------------------------------------------------------------------------- */

.finder { display: grid; gap: 26px; margin: 8px 0 4px; align-items: start; }
@media (min-width: 860px) {
  .finder { grid-template-columns: minmax(310px, 400px) 1fr; }
  /* Kortet bliver stående mens listen ruller: det er hele pointen i to ruder. */
  .fmap { position: sticky; top: 16px; }
}
/* Under 860 px kommer kortet FØRST, fordi det er det man genkender siden på,
   og listen fylder alligevel resten af skærmen uanset rækkefølgen. */
@media (max-width: 859px) { .fmap { order: -1; } }

.fsearch { display: flex; gap: 8px; flex-wrap: wrap; }
.fsr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.finput {
  flex: 1 1 190px; min-width: 0; font: inherit; font-size: 15px;
  padding: 9px 12px; border: 1px solid var(--line); border-radius: 3px;
  background: var(--card); color: var(--ink); -webkit-appearance: none; appearance: none;
}
.finput:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
/* Den er terracotta og ikke en kant om ingenting, fordi den ER rudens hovedhandling: alt
   andet i panelet kan man naa ved at rulle, og det her er den ene ting siden kan goere som
   den besoegende ikke kan goere selv. Ikonet er en papirflyver frem for en nael: en nael
   siger "her er du", og knappen handler om at flytte listen, ikke om at markere et sted. */
.fnaer {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--sans); font-size: 13.5px; font-weight: 600; white-space: nowrap;
  padding: 9px 15px; border: 1px solid var(--accent); border-radius: 3px;
  background: var(--accent); color: var(--card); cursor: pointer;
  transition: filter .15s ease;
}
.fnaer:hover { filter: brightness(1.12); }
.fnaer:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.fnaer-ikon { width: 14px; height: 14px; fill: currentColor; flex: none; }
/* Slaaet til: knappen er nu en SLUK-knap, saa den skal se ud som en kontrol der staar i en
   tilstand frem for som en invitation. */
.fnaer[aria-pressed="true"] {
  background: transparent; color: var(--accent);
}
.fnaer[aria-pressed="true"] .fnaer-ikon { transform: rotate(-45deg); }
.fnaer:disabled { cursor: progress; opacity: .75; }
.fnaer.venter .fnaer-ikon { animation: fpuls 1s ease-in-out infinite; }
@keyframes fpuls { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .fnaer.venter .fnaer-ikon { animation: none; } }

/* Gul og ikke roed, og det er en paastand om hvad der skete. At en browser ikke deler en
   position er ikke en fejl paa siden og ofte ikke engang et nej: det kan vaere en
   indstilling, en blokeret sensor eller et timeout. Roed ville sige "der gik noget i
   stykker" om noget der virker praecis som det skal. */
.fbesked {
  font-family: var(--sans); font-size: 13px; line-height: 1.5;
  margin: 10px 0 0; padding: 9px 12px;
  border-left: 3px solid var(--warn); background: var(--line-soft);
  color: var(--ink-2); border-radius: 0 3px 3px 0;
}

.fchips { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 0; }
.fchip {
  font-family: var(--sans); font-size: 12.5px; line-height: 1.1;
  padding: 6px 11px; border: 1px solid var(--line); border-radius: 999px;
  background: transparent; color: var(--ink-2); cursor: pointer;
}
.fchip:hover { border-color: var(--accent); color: var(--accent); }
.fchip.on { background: var(--accent); border-color: var(--accent); color: var(--card); font-weight: 600; }
.fchip .fn { font-variant-numeric: tabular-nums; opacity: .65; margin-left: 3px; }
.fchip.on .fn { opacity: .8; }
/* De 17 uden sted er en gruppe som alle andre, ikke en fejlmeddelelse. Den
   stiplede kant siger "ikke placeret" uden at sige "gik galt". */
.fchip-ukendt { border-style: dashed; }

.fstatus { font-family: var(--sans); font-size: 13px; color: var(--ink-3); margin: 12px 0 10px; }

.fliste {
  list-style: none; margin: 0; padding: 0;
  max-height: 62vh; overflow-y: auto; overscroll-behavior: contain;
  border-top: 1px solid var(--line);
}
@media (max-width: 859px) { .fliste { max-height: none; overflow: visible; } }
.fgruppe { margin: 0; }
.fgruppe > ul { list-style: none; margin: 0; padding: 0; }
.fgh {
  font-family: var(--sans); font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 700; margin: 0; padding: 14px 2px 5px;
  position: sticky; top: 0; background: var(--paper); z-index: 1;
  border-bottom: 1px solid var(--line-soft);
}
.fgh .fn { font-variant-numeric: tabular-nums; font-weight: 400; opacity: .7; }
.frow { border-bottom: 1px solid var(--line-soft); }
.frow > a {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; align-items: baseline;
  padding: 9px 2px; text-decoration: none; color: inherit;
}
.frow > a:hover { background: var(--card); }
.frow > a:hover .fnavn { color: var(--accent); text-decoration: underline; }
.fnavn { font-family: var(--serif); font-size: 16px; }
.fmeta { grid-column: 1; font-family: var(--sans); font-size: 12.5px; color: var(--ink-3); }
.fsep { opacity: .5; }
.fkm {
  grid-row: 1 / span 2; grid-column: 2; align-self: center;
  font-family: var(--sans); font-size: 12.5px; font-variant-numeric: tabular-nums;
  color: var(--accent); font-weight: 600; white-space: nowrap;
}
/* Fremhævet fra kortet: en terracotta-kant i venstre side frem for en baggrund,
   så rækken ikke hopper i højden når man kører hen over prikkerne. */
.frow.fhit > a { background: var(--card); box-shadow: inset 3px 0 0 var(--accent); }

.fmap {
  margin: 0; background: var(--card); border: 1px solid var(--line);
  border-radius: 3px; padding: 16px 16px 12px;
}
/* Topbarens andet link. Den havde ét, hjem, og bar derfor ingen navigation: dette er stadig
   ikke en menu, men den ene genvej til den flade man ellers kun finder gennem foden. Den er
   undertrykt paa /risterier/ selv, hvor den ville vaere et selvlink. */
.topbar .in { display: flex; align-items: center; gap: 18px; }
.top-find { margin-left: auto; font-size: 13.5px; font-weight: 600; white-space: nowrap; }

.fmapwrap { position: relative; }
.fmap svg { width: 100%; height: auto; display: block; touch-action: none; cursor: grab; }
.fmap svg.traekker { cursor: grabbing; }
.fzoom { position: absolute; right: 8px; top: 8px; display: flex; flex-direction: column; gap: 5px; align-items: flex-end; }
.fzbtn {
  font-family: var(--sans); font-size: 15px; line-height: 1; font-weight: 600;
  width: 30px; height: 30px; padding: 0; cursor: pointer;
  border: 1px solid var(--line); border-radius: 3px;
  background: var(--card); color: var(--ink-2);
}
.fzbtn:hover { border-color: var(--accent); color: var(--accent); }
.fzbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.fzhele { width: auto; height: auto; font-size: 12px; padding: 6px 10px; }
.fhint { display: block; margin-top: 3px; opacity: .85; }

/* Navneskiltet. Baggrunden er kortets papir og ikke en mork boble: skiltet skal laegge sig
   PAA kortet som en etiket, ikke svaeve over det som en systemtooltip. */
.fskilt-bg { fill: var(--card); stroke: var(--line); stroke-width: 1; }
.fskilt-tx { fill: var(--ink); font-family: var(--sans); font-weight: 600; dominant-baseline: alphabetic; }
.flabel { pointer-events: none; }
.fcap { font-family: var(--sans); font-size: 12.5px; color: var(--ink-3); margin: 10px 2px 0; }

/* Kystlinjen er en hårstreg og ikke en kant: landet skal ligne papir med en
   kant tegnet på, ikke en udstanset form. */
/* Landet er en tone MØRKERE end kortets papir, ikke lysere: figuren skal ligge PÅ arket som
   tryksværte, ikke være stanset ud af det. Kystlinjen er en hårstreg i samme familie, så
   omridset har en kant uden at blive en streg man læser før man læser prikkerne. */
.w-kort .land { fill: #e6dccc; stroke: var(--line); stroke-width: 1.1; stroke-linejoin: round; }
@media (prefers-color-scheme: dark) { .w-kort .land { fill: #332b23; } }
.fdots .dot {
  fill: var(--accent); fill-opacity: .62; stroke: var(--paper); stroke-width: 1.4;
  transition: fill-opacity .18s ease, r .18s ease;
}
/* Halvgennemsigtige prikker lægger sig oven i hinanden og bliver mørkere hvor
   der er flest. Tætheden tegner altså sig selv, uden en varmekortsskala der
   ville påstå en præcision punkterne ikke har. */
.fdots a:hover .dot, .fdots a:focus-visible .dot { fill-opacity: 1; }
.fdots a:focus-visible { outline: none; }
.fdots a:focus-visible .dot { stroke: var(--ink); stroke-width: 2; }
/* Fravalgt, ikke fjernet. Se noten øverst i denne blok. */
.fdots a.fdim .dot { fill-opacity: .1; }
.fdots a.fhit .dot { fill-opacity: 1; stroke: var(--ink); stroke-width: 1.8; }

@media (prefers-reduced-motion: reduce) {
  .fdots .dot { transition: none; }
}
