/* ==========================================================
   ARKIV — a theme for oerum.org, on Grav.
   No webfonts, no frameworks, no libraries. System stacks only.
   Every colour on the page comes from the token block below.
   Lifted unchanged from the specimen this theme was designed in;
   the specimen is the reference rendering.
   ========================================================== */
/* One ground, deliberately: black on white, on screen and on paper alike.
   This does NOT follow the OS theme — a visitor in dark mode still gets white,
   and there is no prefers-color-scheme block anywhere in this file. */
:root{
  --paper:#ffffff;
  --ink:#111318;            /* not #000: pure black on pure white glares at 17px */
  --rule:#c9ccc4;
  --dim:#5f645b;

  --flash:#0047ff;          /* set per work type, see below */
  --flash-on:#ffffff;

  --mono:ui-monospace,"Cascadia Mono","Segoe UI Mono",Consolas,"SF Mono",Menlo,monospace;
  --text:ui-serif,Charter,"Iowan Old Style",Georgia,"Times New Roman",serif;

  --pad:.8rem;
}

/* work type carries the colour; a work with no type flashes plain ink */
.t-ex  {--flash:#0047ff;--flash-on:#ffffff}
.t-pub {--flash:#c4001a;--flash-on:#ffffff}
.t-on  {--flash:#00674b;--flash-on:#ffffff}
.t-no  {--flash:#111318;--flash-on:#ffffff}

/* ---------- ground ---------- */
*{box-sizing:border-box}

/* THE `hidden` ATTRIBUTE MUST WIN, AND WITHOUT THIS IT LOSES.
   `hidden` only hides through the UA stylesheet's [hidden]{display:none},
   and ANY author rule setting display beats a UA rule whatever its
   specificity. So every element this stylesheet gives a display to -
   .entry (grid), .cell (flex), #grid (grid) - stayed on screen when the
   script set el.hidden = true. Measured on the live site: clicking a blog
   tag set the attribute on 179 rows, hid 0 of them, and left the counter
   reading `151 of 330` over 330 visible rows - a filter that lies is
   worse than no filter. The works index shipped the table and the grid
   stacked, 6818px where it should have been 3282.

   It only ever went unnoticed because table rows have no author display
   rule, so the ONE filter anybody tested - the works table - worked.

   !important, and general rather than per-selector, deliberately: this
   is the fourth time this file has hit it (#hud and #peek carry their own
   overrides further down) and a rule that cannot lose beats three that
   have been checked. Same argument as .entry.re:not(:hover) below. */
[hidden]{display:none!important}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; background:var(--paper); color:var(--ink);
  font-family:var(--text); font-size:17px; line-height:1.5;
}
::selection{background:var(--flash); color:var(--flash-on)}
a{color:inherit; text-underline-offset:.18em; text-decoration-thickness:from-font}
a:hover,a:focus-visible{background:var(--flash); color:var(--flash-on); text-decoration:none; outline:none}
:focus-visible{outline:2px solid var(--flash); outline-offset:1px}
/* Headings take focus on navigation so a keyboard user is not dropped back at
   the top of the document. They cannot be tabbed to — only focused in code —
   so the ring is noise: it drew a box round every page title. Screen readers
   still announce the move. */
[tabindex="-1"]:focus,[tabindex="-1"]:focus-visible{outline:none}
button{font:inherit; color:inherit}
hr{border:0; border-top:1px solid var(--rule); margin:0}

.mono{font-family:var(--mono); font-variant-numeric:tabular-nums lining-nums}
.lab{font-family:var(--mono); font-size:.68rem; letter-spacing:.08em;
     text-transform:uppercase; color:var(--dim)}

/* ---------- masthead ---------- */
#top{
  position:sticky; top:0; z-index:20; background:var(--paper);
  border-bottom:1px solid var(--ink); display:flex; flex-wrap:wrap; align-items:stretch;
}
#top .id{display:flex; flex-wrap:wrap; align-items:baseline; gap:.2em .7em;
  padding:.5rem var(--pad); flex:1 1 auto; min-width:0}
#top .id b{font-family:var(--mono); font-size:.72rem; font-weight:700; letter-spacing:.1em; text-transform:uppercase}
/* The name is a link now — the way back to the works index. It must not pick
   up the browser's underline, which the nav anchors were already spared. The
   hover flash stays: it is how every other link on this page says it is one. */
#top .id b a{text-decoration:none}
#top .id span{font-family:var(--mono); font-size:.68rem; color:var(--dim);
  min-width:0; overflow-wrap:anywhere}
#nav{display:flex; flex-wrap:wrap; align-items:stretch; flex:1 1 auto}
#nav a{
  display:flex; align-items:center; text-decoration:none;
  background:none; border:0; border-left:1px solid var(--rule);
  font-family:var(--mono); font-size:.68rem; letter-spacing:.06em; text-transform:uppercase;
  color:var(--dim); padding:.5rem .7rem; cursor:pointer; white-space:nowrap;
}
#nav a:hover,#nav a:focus-visible{background:var(--flash); color:var(--flash-on); outline:none}
#nav a[aria-current="page"]{color:var(--ink); font-weight:700}

/* ---------- page head ---------- */
.head{padding:1.5rem var(--pad) 1.1rem; border-bottom:1px solid var(--ink);
      display:flex; flex-wrap:wrap; align-items:baseline; gap:.5rem 2rem}
.head h1{font-size:clamp(1.5rem,4vw,2.2rem); font-weight:400; margin:0;
         letter-spacing:-.01em; line-height:1.1; text-wrap:balance}
.head p{margin:0; max-width:58ch; color:var(--dim); font-size:.95rem}

/* ---------- toolbar ---------- */
#tools{display:flex; flex-wrap:wrap; align-items:stretch; border-bottom:1px solid var(--rule)}
#tools>*{border-right:1px solid var(--rule)}
#tools .srch{display:flex; align-items:center; gap:.55em; padding:.45rem var(--pad)}
#find{border:0; background:none; color:var(--ink); font-family:var(--mono); font-size:.82rem;
      padding:.1rem 0; width:14ch; border-bottom:1px solid var(--ink)}
#find::placeholder{color:var(--dim)}
#find:focus{outline:none; border-bottom-color:var(--flash); box-shadow:0 1px 0 var(--flash)}
/* The blog's tag filter is the same control as the works index's type
   filter, so it wears the same rules rather than a copy of them. The ids
   differ because the two lists coexist in one stylesheet and one script,
   never on one page. #btags has no `i` swatch: a tag carries no colour,
   and an empty square would imply one. */
#chips,#btags{display:flex; flex-wrap:wrap}
#chips button,#btags button{
  background:none; border:0; border-right:1px solid var(--rule);
  font-family:var(--mono); font-size:.68rem; letter-spacing:.05em; text-transform:uppercase;
  color:var(--dim); padding:.55rem .7rem; cursor:pointer; display:flex; align-items:center; gap:.45em;
}
#chips button:last-child,#btags button:last-child{border-right:0}
#chips i{width:.6em; height:.6em; background:var(--flash); display:block; flex:none}
#chips button:hover,#chips button:focus-visible,#chips button[aria-pressed="true"],
#btags button:hover,#btags button:focus-visible,#btags button[aria-pressed="true"]{
  background:var(--flash); color:var(--flash-on); outline:none}
#chips button:hover i,#chips button:focus-visible i,#chips button[aria-pressed="true"] i{background:var(--flash-on)}
#count,#bcount{padding:.55rem var(--pad); font-family:var(--mono); font-size:.68rem;
       letter-spacing:.05em; color:var(--dim); text-transform:uppercase; display:flex; align-items:center}
#modes{margin-left:auto; display:flex; border-right:0}
#modes button{background:none; border:0; border-left:1px solid var(--rule);
  font-family:var(--mono); font-size:.68rem; letter-spacing:.06em; text-transform:uppercase;
  color:var(--dim); padding:.55rem .7rem; cursor:pointer}
#modes button[aria-pressed="true"]{color:var(--ink); font-weight:700}
#modes button:hover,#modes button:focus-visible{background:var(--flash); color:var(--flash-on); outline:none}

/* ---------- list ---------- */
/* `overflow-x:auto` makes this a scroll container, and a scroll container
   captures any position:sticky inside it — so the sticky table head stopped
   sticking to the viewport and simply scrolled away. The wrapper only needs to
   scroll where the table genuinely does not fit, so above 900px it is let go
   and the head sticks properly again.
   NOTE: computed `top` being 67px proves nothing here. Measure the rect after
   scrolling; that is what caught this. */
.tablewrap{overflow-x:auto}
@media (min-width:900px){ .tablewrap{overflow-x:visible} }
/* below that the wrapper must keep scrolling, so the head cannot stick to the
   viewport anyway — make it static rather than leave it half-stuck, which
   collided with the masthead by a few pixels */
@media (max-width:899px){ thead th{position:static} }
table{border-collapse:collapse; width:100%; font-size:.98rem}
thead th{position:sticky; top:var(--stick,0px); z-index:5; background:var(--paper);
  text-align:left; padding:0; border-bottom:1px solid var(--ink); white-space:nowrap}
thead button{
  width:100%; text-align:left; background:none; border:0; cursor:pointer;
  font-family:var(--mono); font-size:.66rem; letter-spacing:.08em; text-transform:uppercase;
  color:var(--dim); padding:.5rem var(--pad); display:flex; gap:.4em; align-items:center;
}
thead th[aria-sort] button{color:var(--ink); font-weight:700}
thead button:hover,thead button:focus-visible,thead th[aria-sort] button:hover{
  background:var(--flash); color:var(--flash-on); outline:none}
thead .arrow{font-size:.8em; opacity:0}
thead th[aria-sort] .arrow{opacity:1}
tbody td{padding:.4rem var(--pad); border-bottom:1px solid var(--rule); vertical-align:baseline}
tbody tr:hover td,tbody tr:has(a:focus-visible) td{background:var(--flash); color:var(--flash-on)}
tbody tr:hover .num,tbody tr:hover .date,tbody tr:hover .type,tbody tr:hover .place,
tbody tr:has(a:focus-visible) .num,tbody tr:has(a:focus-visible) .date,
tbody tr:has(a:focus-visible) .type,tbody tr:has(a:focus-visible) .place{color:var(--flash-on)}
tbody tr:hover a{background:none; color:inherit}
.num,.date{font-family:var(--mono); font-variant-numeric:tabular-nums lining-nums;
  font-size:.74rem; color:var(--dim); white-space:nowrap}
.ttl{line-height:1.25}
.ttl a{text-decoration:underline}
.ttl a:hover{background:none}
.ttl small{display:block; font-family:var(--mono); font-size:.66rem; color:var(--dim); margin-top:.1em}
tbody tr:hover .ttl small{color:var(--flash-on)}
.type,.place{font-family:var(--mono); font-size:.7rem; color:var(--dim)}
.type{white-space:nowrap}
.type i{width:.55em; height:.55em; background:var(--flash); display:inline-block; margin-right:.5em}
tbody tr:hover .type i{background:var(--flash-on)}
.place{max-width:24ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.none{opacity:.45}
/* A work that PREDATES the numbering, which is not the same as one waiting for
   a number. The archive system starts around 2015 and holds nothing before it,
   so these will never get one — and an em dash identical to the waiting rows
   would say the sequence has holes in it where it has an edge. Lower case
   against the figures, so it reads as a note and not as a value. */
.num .pre{font-size:.82em; letter-spacing:.06em; opacity:.55;
  font-variant-numeric:normal}

/* the resemblance column: a number and nothing else, deliberately unexplained
   in the row itself. It is a button because it goes somewhere. */
.like{width:1%; white-space:nowrap; text-align:right}
.like a,.like button{background:none; border:0; cursor:pointer; padding:0 0 0 .4em;
  text-decoration:none;
  font-family:var(--mono); font-variant-numeric:tabular-nums lining-nums;
  font-size:.74rem; color:var(--dim)}
.like a::before,.like button::before{content:"≈ "; opacity:.55}
.like a:hover,.like a:focus-visible,.like button:hover,.like button:focus-visible{
  background:var(--flash); color:var(--flash-on); outline:none}
tbody tr:hover .like a,tbody tr:hover .like button{color:var(--flash-on)}
tbody tr:hover .like a:hover,tbody tr:hover .like button:hover{background:var(--flash-on); color:var(--flash)}

/* ---------- grid ---------- */
#grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr));
      gap:1px; background:var(--rule); border-bottom:1px solid var(--rule)}
.cell{background:var(--paper); display:flex; flex-direction:column; text-decoration:none}
.cell img{width:100%; aspect-ratio:1/1; object-fit:cover; display:block;
          filter:grayscale(1) contrast(1.05); background:var(--rule)}
/* A work can have no photograph at all — `speaker1` is a text and a film, and
   it is the first one. Without this the cell collapses to its caption and the
   grid goes ragged around it. An empty square holds the rhythm and says plainly
   that there is no picture, which is the choice the untyped works already make:
   absence reads as absence, not as another kind of thing. */
.cell:not(:has(img))::before{content:""; display:block; aspect-ratio:1/1;
  background:var(--paper); border-bottom:1px solid var(--rule)}
.cell .cap{padding:.4rem .5rem .5rem; display:flex; flex-direction:column; gap:.1rem}
.cell .ct{font-size:.9rem; line-height:1.2}
.cell .cd{font-family:var(--mono); font-size:.66rem; color:var(--dim);
          font-variant-numeric:tabular-nums lining-nums}
.cell:hover img,.cell:focus-visible img{filter:none}
.cell:hover .cap,.cell:focus-visible .cap{background:var(--flash); color:var(--flash-on)}
.cell:hover .cd,.cell:focus-visible .cd{color:var(--flash-on)}
.cell:hover,.cell:focus-visible{background:var(--paper); color:var(--ink);
  outline:2px solid var(--flash); outline-offset:-2px}

/* ---------- record ---------- */
.rechead{padding:1.4rem var(--pad) 1.1rem; border-bottom:1px solid var(--ink)}
.back{font-family:var(--mono); font-size:.68rem; letter-spacing:.06em;
      text-transform:uppercase; text-decoration:none; color:var(--dim)}
.rechead h1{font-size:clamp(1.7rem,4.6vw,2.9rem); font-weight:400; margin:.45rem 0 0;
  letter-spacing:-.015em; line-height:1.05; text-wrap:balance; max-width:24ch}
/* Details is a native <details>, shut by default. It used to be a sticky
   column beside the text; a grid item's sticky containing block is the whole
   grid, not its row, so it went on sticking over the image band below. */
details.facts{border-bottom:1px solid var(--rule)}
details.facts>summary{
  list-style:none; cursor:pointer; display:flex; align-items:baseline; gap:.6em;
  font-family:var(--mono); font-size:.7rem; letter-spacing:.07em; text-transform:uppercase;
  color:var(--dim); padding:.55rem var(--pad);
}
details.facts>summary::-webkit-details-marker{display:none}
details.facts>summary::before{content:"+"; width:1ch; display:inline-block; font-weight:700}
details.facts[open]>summary::before{content:"\2212"}
details.facts>summary:hover,details.facts>summary:focus-visible{
  background:var(--flash); color:var(--flash-on); outline:none}
details.facts>summary .n{margin-left:auto; font-variant-numeric:tabular-nums lining-nums}
details.facts dl{margin:0; padding:.2rem 0 .7rem; border-top:1px solid var(--rule)}
details.facts dl div{display:grid; grid-template-columns:6.5rem minmax(0,1fr); gap:.5rem;
  padding:.3rem var(--pad); align-items:baseline; max-width:78ch}
details.facts dl div:hover{background:var(--flash); color:var(--flash-on)}
details.facts dl div:hover dt{color:var(--flash-on)}
details.facts dt{font-family:var(--mono); font-size:.64rem; letter-spacing:.06em;
  text-transform:uppercase; color:var(--dim); margin:0}
details.facts dd{margin:0; font-size:.9rem; line-height:1.35;
  min-width:0; overflow-wrap:anywhere}   /* a bare URL has nothing to break on */
details.facts dd.mono{font-size:.76rem}
.prose{padding:1.4rem 1.3rem 2.2rem; border-bottom:1px solid var(--rule)}
.prose p{margin:0 0 1em; max-width:64ch; font-size:1.02rem; line-height:1.6; text-wrap:pretty}
.prose p.empty{font-family:var(--mono); font-size:.8rem; color:var(--dim)}
/* A work's own text may carry headings, and EIGHT pages do - `## Project
   Description`, `### Process`, `### Results`. There was no rule for them here,
   so they fell through to the browser's defaults: bold Times at 1.5em in the
   middle of a page whose whole argument is that every size and face is chosen.
   Nobody had noticed because the sub-page renderer (.doc) has its own heading
   styles and the record page does not share them.

   The mono label is the same gesture the section rail uses, one level quieter:
   a heading inside a work's text is a marker in a column, not a new document. */
.prose h2,.prose h3,.prose h4{
  max-width:64ch; font-family:var(--mono); font-weight:400;
  letter-spacing:.08em; text-transform:uppercase; color:var(--dim);
  margin:2em 0 .7em}
.prose h2{font-size:.72rem}
.prose h3,.prose h4{font-size:.68rem}
.prose>:first-child{margin-top:0}
.prose ul,.prose ol{max-width:64ch; margin:0 0 1em; padding-left:1.2em}
.prose li{margin:0 0 .3em; line-height:1.6}
.prose blockquote{margin:0 0 1em; padding-left:1rem;
  border-left:1px solid var(--rule); color:var(--dim)}
/* ---------- other media ----------
   The gallery is not only photographs: a work can carry film, sound, a PDF or
   an outward link. Each is a typed card rather than a naked embed, so the row
   reads as a list of what exists before anything loads. */
.recmedia{display:grid; gap:1px; background:var(--rule);
  grid-template-columns:repeat(auto-fill,minmax(260px,1fr));
  border-bottom:1px solid var(--rule)}
.recmedia .m{background:var(--paper); display:flex; flex-direction:column;
  gap:.35rem; padding:.7rem var(--pad) .8rem; text-decoration:none; min-width:0}
.recmedia .k{font-family:var(--mono); font-size:.62rem; letter-spacing:.11em;
  text-transform:uppercase; color:var(--dim); display:flex; gap:.5em; align-items:center}
.recmedia .k::before{content:""; width:.55em; height:.55em; background:var(--flash)}
.recmedia .t{font-size:.95rem; line-height:1.25; overflow-wrap:anywhere}
.recmedia .u{font-family:var(--mono); font-size:.66rem; color:var(--dim);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
a.m:hover,a.m:focus-visible,button.m:hover,button.m:focus-visible{
  background:var(--flash); color:var(--flash-on); outline:none}
a.m:hover .k,a.m:hover .u,button.m:hover .k,button.m:hover .u{color:var(--flash-on)}
a.m:hover .k::before,button.m:hover .k::before{background:var(--flash-on)}
button.m{border:0; cursor:pointer; font:inherit; text-align:left; color:inherit}
.recmedia audio{width:100%; height:32px}
.recmedia .demo{font-family:var(--mono); font-size:.62rem; color:var(--dim);
  border:1px solid var(--rule); padding:.05em .4em; align-self:flex-start}
/* A FILM MUST SPAN THE WHOLE ROW, and this is why. The card it replaces sits in
   one cell of an auto-fill grid, so without `grid-column` the iframe simply
   inherits that cell — measured on a 1280px window: 316 x 177 inside a 1265px
   content area, a quarter of the width, which is what "plays very small" was.
   `width:100%` was doing exactly what it says and the container was the wrong
   one. Opening it full width is also the gesture the lightbox already makes for
   a photograph: the reveal is the point.

   The height is capped so a 16/9 film on a wide screen does not come out taller
   than the window and push the rest of the page out of sight — 1265px wide
   would be 711px tall. Capping WIDTH rather than height keeps the ratio honest;
   capping height would leave the iframe wider than 16/9 and YouTube would
   letterbox inside it. */
.recmedia .film{grid-column:1/-1; aspect-ratio:16/9; width:100%;
  max-width:calc((100dvh - 9rem) * 16 / 9); margin-inline:auto;
  border:0; display:block; background:var(--rule)}

.recimgs{display:grid; gap:1px; background:var(--rule);
  grid-template-columns:repeat(auto-fill,minmax(290px,1fr))}
.recimgs figure{margin:0; background:var(--paper)}
.recimgs img{width:100%; display:block; filter:grayscale(1) contrast(1.05)}
.recimgs figure:hover img{filter:none}

/* ==========================================================
   CAPTIVE PORTAL — a different entity, and it has to look like one.

   It is Kristoffer's artist-run space, not part of the portfolio, so it does
   not wear the portfolio's identity. Three things change at once, which is
   about as far as you can go while still plainly being the same website:

     ground     inverted. You walk into a dark room.
     type       mono throughout — a notice board, not a register. The main
                site's serif is what carries the archive's prose; dropping it
                is the clearest possible signal that this is somewhere else.
     accent     one signal amber, its own. The portfolio's four colours mean
                work-type and mean nothing here.

   ALL OF THIS IS A TOKEN BLOCK. Because every colour and both faces in this
   stylesheet already come from custom properties, an entire second identity is
   nine declarations — which is the payoff for having been strict about it.
   ========================================================== */
body.cp{
  --paper:#0b0b0b;
  --ink:#f2f2ee;
  --rule:#2b2b26;
  --dim:#8e8e86;
  --flash:#ffd400;          /* a system notice, not a work type */
  --flash-on:#0b0b0b;
  --text:var(--mono);       /* one line, and the whole page changes voice */
}
body.cp .doc section{grid-template-columns:minmax(0,1fr); gap:0}
body.cp .doc section>h2{
  text-align:left; padding:.9rem var(--pad) .3rem 0; color:var(--ink);
  font-size:.78rem; letter-spacing:.14em}
body.cp .doc .sec-body{padding:.2rem 1.3rem 1.3rem 0; font-size:.86rem; line-height:1.65}
body.cp .doc p{max-width:74ch}
body.cp .doc li{margin-bottom:.45em}
body.cp .rechead h1{font-family:var(--mono); font-size:clamp(1.3rem,3.4vw,2rem);
  letter-spacing:.06em; text-transform:uppercase}
body.cp .head h1{font-family:var(--mono)}
body.cp #top .id b{color:var(--flash)}

/* on paper it goes back to black on white like everything else: an inverted
   ground is a screen decision, not a printing one */
@media print{
  body.cp{--paper:#fff; --ink:#000; --rule:#000; --dim:#444;
          --flash:#000; --flash-on:#fff}
}

/* ---------- the blog, which is its own entity too ----------
   A THIRD IDENTITY, and it costs six declarations for the same reason Captive
   Portal cost nine: every colour and both faces already come from tokens.

   It goes the opposite way from Captive Portal on purpose. CP inverts because
   you walk into a dark room; the blog is 330 posts and the longest reading on
   the site, so the ground stays white and the TYPE carries the distinction
   instead. `--mono: var(--serif)` is the whole gesture: the portfolio splits
   serif for reading and mono for the machine layer, and a blog has no machine
   layer, so dates and labels set in the serif make it read as written rather
   than catalogued. Note the token is `--text`, not `--serif`: the first
   version of this block said `var(--serif)`, which does not exist, so the
   declaration resolved to nothing and the page silently kept the mono. An
   undefined custom property fails quietly — reading the computed value back
   is the only way to know it took.

   The accent is violet, and it is MEASURED like the others: white on #6d28d9
   is 7.1:1, which sits in the same band as the three work colours (6.3, 6.2,
   6.9) and clear of Captive Portal's amber. One token to change. */
body.bl{
  --flash:#6d28d9;
  --flash-on:#ffffff;
  --mono:var(--text);       /* one line, and the whole page changes voice */
}
body.bl #top .id b{color:var(--flash)}

/* LARGER SERIF FOR READING, and the measure matters more than the size.
   Measured on a real prose page: `max-width:64ch` renders as 79 ACTUAL
   characters, because `ch` is the width of the digit zero and that is far
   wider than the average lowercase letter — so the column has been well past
   the comfortable 45-75 all along. And because the column is pinned in `ch` it
   scales WITH the type, so raising the size alone changes nothing: 79
   characters at 16.32px, 79 at 21px, only a wider column.

   Worse, on a phone the container binds instead and size only subtracts: 44
   characters now, 36 at a flat 20px. So it is two changes, not one — a clamp
   that leaves phones exactly where they are and reaches 20px on the desktop,
   and a tighter measure that brings 79 down to 65. Verified at both ends:
   375px stays at 44 characters, 1280px lands at 65. */
/* THE POST: pictures under the headline, then the text, both to one measure.
   `--measure` is set once on the wrapper and nothing else states a width, so
   an image is exactly as wide as the text by construction rather than by two
   numbers being kept in agreement. Change the one token to change both.

   It is WIDER than the 52ch the prose used alone — asked for, so the text
   fills more of the page. The cost, measured rather than guessed: 52ch reads
   as 65 characters a line and this reads as about 85, which is past the
   comfortable 45-75. It is a deliberate trade of readability for presence and
   the token is where to undo it.

   RANGED LEFT, not centred. Centring it was tried and looked wrong for a
   reason worth writing down: the headline above is ranged left, as is the
   works table, the listing, the section rails and every hairline rule on the
   site. A centred column is foreign to this page, and it left the title
   starting in one place and the text in another. Left-aligned, the headline,
   the picture and the prose all begin at the same edge. */
body.bl .postbody{
  --measure:52rem;
  max-width:var(--measure);
}
body.bl .prose p,
body.bl .prose li{
  font-size:clamp(1.02rem, 0.6rem + 1vw, 1.25rem);
  max-width:none;          /* the wrapper is the measure now, not the paragraph */
  line-height:1.62;
}
/* The pictures. Full measure, stacked, and the greyscale the rest of the site
   uses is deliberately NOT applied: these are the post's own images and the
   post is the only place they appear. */
body.bl .postpix{
  padding:1.4rem 1.3rem 0;
  display:flex; flex-direction:column; gap:1.1rem;
}
body.bl .postpix img{
  width:100%; height:auto; display:block;
}
@media (max-width:720px){
  body.bl .postpix{padding:1rem var(--pad) 0}
}

/* ---------- third level: blog posts, Captive Portal exhibitions ----------
   A listing and an item, twice over. The two share this CSS and differ only by
   which identity is on the body — which is the whole point of the token
   discipline, and is what the Grav templates should inherit. */
.listing{border-bottom:1px solid var(--rule)}
.entry{display:grid; grid-template-columns:minmax(0,7rem) minmax(0,1fr);
  gap:0 1.4rem; align-items:start; border-bottom:1px solid var(--rule);
  width:100%; background:none; border-left:0; border-right:0; border-top:0;
  text-align:left; cursor:pointer; padding:0; color:inherit; font:inherit;
  /* THE WHOLE CARD IS THE LINK, so the browser underlined the whole card —
     date, title and two lines of summary, every row. Nothing here ever turned
     the default off, and it went unseen because until the blog was imported
     nothing had rendered a listing with real content in it. The works index
     does not have the fault: there the row is a <tr> and only the title is an
     anchor. The title carries the underline here for the same reason, and the
     hover inversion is what says the rest of the row is live. */
  text-decoration:none}
.entry h3{text-decoration:underline; text-decoration-thickness:from-font}
.entry:hover h3,.entry:focus-visible h3{text-decoration:none}
.entry .when{padding:.9rem var(--pad) .9rem 0; text-align:right;
  font-family:var(--mono); font-size:.7rem; color:var(--dim);
  font-variant-numeric:tabular-nums lining-nums; white-space:nowrap}
.entry .body{padding:.9rem 1.3rem .9rem 0; min-width:0; display:grid;
  grid-template-columns:minmax(0,1fr) auto; gap:0 1rem; align-items:start}
.entry .body>*{min-width:0}   /* or the 1fr track refuses to shrink and pushes
                                 the thumbnail past the edge */
.entry h3{margin:0 0 .2em; font-size:1.05rem; font-weight:400; line-height:1.25}
/* THE EMPHASISED TAG — see `emphasise_tag` in arkiv.yaml. The reflective pieces
   are what the blog is for, so they carry more weight in the list than the
   announcements around them.

   SIZE ONLY: no colour, no rule, no badge. The listing is 330 rows and anything
   louder would turn a scan into a fairground. The summary stays small on
   purpose, so the difference reads as the title being larger rather than the
   whole row being bigger. */
.entry.em h3{font-size:1.45rem; line-height:1.2; letter-spacing:-.01em}
@media (max-width:560px){ .entry.em h3{font-size:1.25rem} }
.entry p{margin:0; color:var(--dim); font-size:.9rem; line-height:1.45;
  max-width:64ch; overflow:hidden; display:-webkit-box;
  -webkit-line-clamp:2; -webkit-box-orient:vertical}
/* THE RECEDING TAG — see `recede_tag` in arkiv.yaml, the counterpart to
   `emphasise_tag`. The project announcements step back a little so the
   reflective pieces stand out without having to shout.

   A LITTLE: the ground is #f7f7f4 against white, and the text #3a4046 against
   ink's #111318 — 9.8:1 where the ordinary rows are 18.6:1, so it reads as
   quieter and never as disabled. The summary keeps `--dim` (5.7:1 on this
   ground) because it was already the lighter thing.

   THE COLOUR IS SET ON THE ROW, NOT ON THE TITLE. `.entry.re h3` would be
   (0,3,0) and would BEAT `.entry:hover` at (0,2,0), so the title would stay
   grey on the flashed background — the same specificity accident this file
   records for #peek, for the sorted column, and for the phone-sized #type in
   the splash.

   AND IT EXCLUDES THE HOVER STATES OUTRIGHT rather than relying on coming
   first. Written plainly, `.entry.re` and `.entry:hover` are BOTH (0,2,0), so
   which one wins is decided by source order — correct today at 158 against 162,
   and one reordering away from silently breaking. `:not(:hover):not(:focus-
   visible)` means the two rules can never both apply, so the order stops
   mattering. A cascade accident you cannot have is better than one you have
   checked. (It could not be tested here: :hover cannot be simulated, and
   :focus-visible deliberately ignores programmatic focus.) */
.entry.re:not(:hover):not(:focus-visible){background:#f7f7f4; color:#3a4046}

.entry .thumb{width:76px; height:76px; object-fit:cover; display:block;
  filter:grayscale(1) contrast(1.05); background:var(--rule)}
.entry:hover,.entry:focus-visible{background:var(--flash); color:var(--flash-on); outline:none}
.entry:hover .when,.entry:hover p,.entry:focus-visible .when,.entry:focus-visible p{
  color:var(--flash-on)}
.entry:hover .thumb{filter:none}
.listnote{padding:1rem var(--pad) 2rem; color:var(--dim); font-size:.85rem;
  font-family:var(--mono); max-width:70ch; line-height:1.6}

@media (max-width:719px){
  .entry{grid-template-columns:1fr; gap:0}
  .entry .when{text-align:left; padding:.7rem var(--pad) 0}
  .entry .body{padding:.2rem var(--pad) .9rem}
}

/* ---------- text pages: C.V., Press, Didactics… ---------- */
/* Each page is split on whichever heading level it uses (h2 on the C.V., h3 on
   Press) and the heading goes in a rail, so a year or a section name reads as
   an index down the left edge instead of interrupting the column. */
.doc section{display:grid; grid-template-columns:minmax(0,11rem) minmax(0,1fr);
  gap:0 1.4rem; border-bottom:1px solid var(--rule); align-items:start}
/* a section with no heading of its own takes the full measure */
.doc section:has(h2:empty){grid-template-columns:minmax(0,1fr)}
.doc section:has(h2:empty)>h2{display:none}
.doc section:has(h2:empty)>.sec-body{padding-left:var(--pad)}
.doc section>h2{margin:0; padding:1rem var(--pad) 1rem 0;
  font-family:var(--mono); font-size:.7rem; font-weight:400; letter-spacing:.08em;
  text-transform:uppercase; color:var(--dim); text-align:right;
  font-variant-numeric:tabular-nums lining-nums}
.doc .sec-body{padding:1rem 1.3rem 1.1rem 0; min-width:0}
.doc .sec-body>*:first-child{margin-top:0}
.doc .sec-body>*:last-child{margin-bottom:0}
.doc p{margin:0 0 .55em; max-width:64ch; line-height:1.5}
.doc h3{margin:1.1em 0 .35em; font-size:1.05rem; font-weight:400; letter-spacing:-.01em}
.doc h4{margin:1em 0 .3em; font-family:var(--mono); font-size:.72rem;
  letter-spacing:.06em; text-transform:uppercase; color:var(--dim); font-weight:400}
.doc ul,.doc ol{margin:0 0 .6em; padding:0; list-style:none; max-width:74ch}
.doc li{margin:0 0 .3em; line-height:1.45; padding-left:1.1em; text-indent:-1.1em}
.doc li::before{content:"·"; color:var(--dim); margin-right:.6em}
.doc li strong{font-family:var(--mono); font-size:.76rem; font-weight:400;
  color:var(--dim); font-variant-numeric:tabular-nums lining-nums}
.doc img{max-width:100%; height:auto; display:block; filter:grayscale(1) contrast(1.05)}
.doc img:hover{filter:none}
.doc hr{margin:.8em 0}
.doc .away{font-family:var(--mono); font-size:.8rem}

/* ---------- lightbox ----------
   A native <dialog> opened with showModal(), which puts it in the browser's
   top layer — above the arcade canvas and the falling words without any
   z-index arms race, and with Escape and the backdrop handled for free. */
/* inset:0 with auto sizing, NOT width:100vw — 100vw includes the scrollbar of
   the page underneath, which pushed the Close button off the right edge */
#lightbox{border:0; padding:0; inset:0; width:auto; height:auto;
  max-width:100%; max-height:100%; background:var(--paper); color:var(--ink)}
#lightbox::backdrop{background:var(--paper)}
#lightbox .frame{display:grid; grid-template-rows:1fr auto; height:100%}
#lightbox .stage{display:grid; place-items:center; overflow:hidden; padding:1rem}
#lightbox img{max-width:100%; max-height:100%; object-fit:contain; display:block;
  filter:none}                      /* the greyscale comes off here: this is the look */
#lightbox .bar{display:flex; align-items:stretch; border-top:1px solid var(--ink);
  font-family:var(--mono); font-size:.72rem}
#lightbox .bar>*{display:flex; align-items:center; padding:.5rem .8rem;
  border-right:1px solid var(--rule)}
#lightbox .bar>*:last-child{border-right:0}
#lightbox button{background:none; border:0; cursor:pointer; color:var(--dim);
  font-family:var(--mono); font-size:.72rem; letter-spacing:.08em; text-transform:uppercase}
#lightbox button:hover,#lightbox button:focus-visible{
  background:var(--flash); color:var(--flash-on); outline:none}
#lightbox button[disabled]{opacity:.3; cursor:default}
#lightbox .count{font-variant-numeric:tabular-nums lining-nums;
  background:var(--flash); color:var(--flash-on)}
#lightbox .cap{color:var(--dim); flex:1; min-width:0; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap}
#lightbox .hint{color:var(--dim); font-size:.66rem}
.recimgs img,.doc img{cursor:zoom-in}

/* ---------- arcade ---------- */
/* The canvas never takes a pointer event, so the archive underneath stays
   usable with the game running. The HUD sits IN the masthead rather than
   floating over the page — an overlay would cover the first row of the list
   on a short window, which is the one thing this layer must not do. */
#arcade{position:fixed; inset:0; z-index:30; pointer-events:none}
/* the score is drawn in the 3x5 bitmap type, not set in the page's mono face:
   it should read as a machine's counter, not as one more line of metadata */
#hud[hidden]{display:none}
#hud{display:flex; align-items:stretch; margin-left:auto; flex-wrap:wrap;
     border-left:1px solid var(--rule); font-family:var(--mono); min-width:0}
#hud button{background:none; border:0; cursor:pointer; color:var(--dim);
            border-right:1px solid var(--rule); display:flex; align-items:center;
            padding:.5rem .6rem; font-size:.68rem; letter-spacing:.06em}
#hud button:last-child{border-right:0}
#hud button:hover,#hud button:focus-visible{
  background:var(--flash); color:var(--flash-on); outline:none}
#hud-toggle{text-transform:uppercase; letter-spacing:.1em}
#hud-toggle #hud-onoff{margin-left:.5em; color:var(--ink); font-weight:700}
#hud-toggle[aria-pressed="false"]{color:var(--dim)}
#hud-toggle[aria-pressed="false"] #hud-onoff{color:var(--dim); font-weight:400}
#hud-canvas{display:block; image-rendering:pixelated}
.sr{position:absolute; width:1px; height:1px; margin:-1px; padding:0;
    overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0}
/* Eleven tab stops stood between the top of the page and the content.
   Clipped rather than pushed to left:-9999px: an off-screen box still has a
   box, and a width audit has to special-case it forever. */
.skip{position:absolute; width:1px; height:1px; overflow:hidden;
  clip-path:inset(50%); white-space:nowrap;
  background:var(--flash); color:var(--flash-on);
  font-family:var(--mono); font-size:.72rem; text-decoration:none}
.skip:focus{position:fixed; top:0; left:0; z-index:40;
  width:auto; height:auto; clip-path:none; padding:.5rem .8rem}

/* falling targets are real buttons, so they can be clicked, focused and read.
   The container ignores the pointer; only the words themselves take it. */
/* width:0 on purpose. A fixed `inset:0` box is sized from the initial
   containing block, which INCLUDES the scrollbar, so it hung ten pixels past
   the right edge and made the page scroll sideways. This element only needs to
   be an origin for its absolutely positioned children. */
#targets{position:fixed; top:0; left:0; width:0; height:0; z-index:31; pointer-events:none}
#targets button{position:absolute; top:0; left:0; padding:.35em .5em;
  background:var(--flash); border:0; cursor:pointer; pointer-events:auto;
  will-change:transform; line-height:0}
#targets canvas{display:block; image-rendering:pixelated}
#targets button:hover,#targets button:focus-visible{
  outline:3px solid var(--ink); outline-offset:0}

#scores{border:3px solid var(--ink); background:var(--paper); color:var(--ink);
        padding:1.2rem; max-width:92vw; font-family:var(--mono)}
#scores::backdrop{background:color-mix(in srgb, var(--ink) 55%, transparent)}
#board{display:block; image-rendering:pixelated; margin-bottom:1rem}
#scores footer{padding:0}
#scores button{font-family:var(--mono); font-size:.68rem; letter-spacing:.08em;
  text-transform:uppercase; background:none; border:1px solid var(--ink);
  padding:.35rem .8rem; cursor:pointer}
#scores button:hover{background:var(--flash); color:var(--flash-on); border-color:var(--flash)}

/* ---------- forms ----------
   Nothing styled a form field, and Grav's form plugin renders real ones on
   Contact — they would have arrived as browser defaults against everything
   else. Same tokens as the rest: a hairline under the field, no rounding, the
   flash on focus. */
/* THE CLASS NAMES BELOW ARE THE FORM PLUGIN'S, NOT THIS THEME'S — and that was
   the fault worth catching. These rules were written against `.f` and `.row`,
   invented here, while the plugin emits `.form-field`, `.form-label`,
   `.form-data` and `.buttons`. Everything element-based inside `.form` would
   have worked and the layout would have collapsed, which is the same mistake
   CLAUDE.md records about the first Twig build: markup styled against what was
   assumed rather than what is emitted.

   Both sets are kept. The plugin's markup has changed across versions, so the
   element selectors are the ones actually load-bearing; the classes only add
   spacing. */
.form{max-width:34rem; padding:1.2rem var(--pad) 2rem 0}
.form form,.form>.f{display:grid; gap:1rem}
.form .f,.form .form-field{display:grid; gap:.3rem}
.form .form-field{margin:0 0 1rem}
.form .form-fields{display:grid; gap:0}
.form label,.form .form-label{font-family:var(--mono); font-size:.66rem; letter-spacing:.09em;
  text-transform:uppercase; color:var(--dim)}
.form input,.form textarea,.form select{
  font:inherit; font-size:.95rem; color:var(--ink); background:none;
  border:0; border-bottom:1px solid var(--ink); border-radius:0;
  padding:.35rem 0; width:100%; min-width:0}
.form textarea{resize:vertical; min-height:6.5em; line-height:1.5}
.form input:focus,.form textarea:focus,.form select:focus{
  outline:none; border-bottom-color:var(--flash); box-shadow:0 1px 0 var(--flash)}
.form input:user-invalid,.form textarea:user-invalid{
  border-bottom-color:var(--flash); box-shadow:0 1px 0 var(--flash)}
.form .hint,.form .form-field .field-description{font-family:var(--mono); font-size:.66rem; color:var(--dim)}
.form .row,.form .buttons{display:flex; align-items:center; gap:1rem; flex-wrap:wrap}
.form button{font-family:var(--mono); font-size:.72rem; letter-spacing:.1em;
  text-transform:uppercase; background:none; color:var(--ink);
  border:1px solid var(--ink); padding:.5rem 1.1rem; cursor:pointer}
.form button:hover,.form button:focus-visible{
  background:var(--flash); color:var(--flash-on); border-color:var(--flash); outline:none}
.form button[disabled]{color:var(--dim); border-color:var(--rule); cursor:default}
.form .form-messages,.form .alert{font-family:var(--mono); font-size:.72rem; color:var(--ink); border-left:2px solid var(--flash); padding-left:.7rem; margin:0 0 1rem}
.form .note{font-family:var(--mono); font-size:.66rem; color:var(--dim);
  border-left:2px solid var(--rule); padding-left:.7rem; line-height:1.6}

/* ---------- the two pages that are really tables ----------
   The C.V. and Press are dated records, not prose. Giving them the generic
   section layout made them read as essays that happen to begin with a year;
   aligning the dates is the whole difference between a list you scan and a
   list you have to read. */
.rows{display:grid; grid-template-columns:minmax(0,8.5rem) minmax(0,1fr); gap:0}
.rows>*{padding:.3rem 0; border-bottom:1px solid var(--rule); min-width:0}
.rows .when{font-family:var(--mono); font-size:.72rem; color:var(--dim);
  font-variant-numeric:tabular-nums lining-nums; white-space:nowrap;
  padding-right:1rem}
.rows .what{line-height:1.4}
.rows .what a{text-decoration:underline}
.rows .out{font-family:var(--mono); font-size:.7rem; color:var(--dim);
  margin-left:.6em; white-space:nowrap}
.rows .pdf{font-family:var(--mono); font-size:.62rem; letter-spacing:.08em;
  border:1px solid var(--rule); padding:0 .35em; margin-left:.5em;
  text-decoration:none; white-space:nowrap}
.rows .pdf:hover{background:var(--flash); color:var(--flash-on); border-color:var(--flash)}
.rows .r:hover .when,.rows .r:hover .what{background:var(--flash); color:var(--flash-on)}
.rows .r{display:contents}
@media (max-width:719px){
  .rows{grid-template-columns:1fr}
  .rows .when{padding:.5rem 0 0; border-bottom:0}
  .rows .what{padding-top:0}
}

/* ---------- 404 ---------- */
.e404 .big{font-family:var(--mono); font-size:clamp(3rem,14vw,7rem);
  line-height:1; letter-spacing:.04em; margin:0; font-weight:700}
.e404 .said{font-family:var(--mono); font-size:.8rem; color:var(--dim);
  word-break:break-all; padding:.6rem var(--pad) 0 0}
/* borders on the cells, not gap-as-background: a grid whose last row is not
   full shows the gap colour through the empty cells, which prints and reads as
   a grey block sitting in the corner for no reason */
.e404 .ways{display:grid; grid-template-columns:repeat(auto-fill,minmax(190px,1fr));
  gap:0; border-top:1px solid var(--ink); margin-top:1.4rem}
.e404 .ways a,.e404 .ways button{background:var(--paper);
  border:0; border-right:1px solid var(--rule); border-bottom:1px solid var(--rule);
  padding:.7rem var(--pad); text-align:left; font:inherit; color:inherit;
  cursor:pointer; text-decoration:none; display:flex; flex-direction:column; gap:.2rem}
.e404 .ways .k{font-family:var(--mono); font-size:.62rem; letter-spacing:.1em;
  text-transform:uppercase; color:var(--dim)}
.e404 .ways a:hover,.e404 .ways button:hover,
.e404 .ways a:focus-visible,.e404 .ways button:focus-visible{
  background:var(--flash); color:var(--flash-on); outline:none}
.e404 .ways a:hover .k,.e404 .ways button:hover .k{color:var(--flash-on)}

/* ---------- note + status ---------- */
#status{display:none}
#peek{display:none}
@media (min-width:900px) and (hover:hover){
  #status{
    display:block; position:fixed; left:0; right:0; bottom:0; z-index:20;
    background:var(--paper); border-top:1px solid var(--ink);
    font-family:var(--mono); font-size:.68rem; letter-spacing:.04em; color:var(--dim);
    padding:.4rem var(--pad); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
  #status b{color:var(--ink); font-weight:400}
  body{padding-bottom:2rem}
  #peek{
    display:block; position:fixed; right:0; bottom:2rem; z-index:15; width:200px;
    border:2px solid var(--flash); border-right:0; background:var(--paper); margin:0;
  }
  #peek .pair{display:grid; grid-template-columns:1fr 1fr}
  /* no resemblance yet — one picture, one column, no blank half */
  #peek.solo .pair{grid-template-columns:1fr}
  #peek.solo #peek-like{display:none}
  #peek.solo figcaption{grid-template-columns:1fr}
  #peek img{display:block; width:100%; aspect-ratio:1/1; object-fit:cover}
  #peek figcaption{font-family:var(--mono); font-size:.66rem;
    background:var(--flash); color:var(--flash-on); padding:.25rem .4rem;
    display:grid; grid-template-columns:1fr 1fr; gap:.4em}
  #peek figcaption span:last-child{text-align:right; opacity:.75}
}
#peek[hidden]{display:none!important}

/* Four columns want 391px and a phone has 375, so the Type column goes and its
   colour comes with it as a dot on the title. No dot means no type, which is
   the same distinction the column made. */
@media (max-width:559px){
  thead th:nth-child(4),tbody td.type{display:none}
  .ttl a::before{content:""; display:inline-block; width:.5em; height:.5em;
    background:var(--flash); margin-right:.5em; vertical-align:.05em}
  .t-no .ttl a::before{display:none}
  tbody tr:hover .ttl a::before{background:var(--flash-on)}
}

@media (max-width:719px){
  #top{position:static}
  .prose{padding:1.1rem var(--pad) 1.8rem}
  .place,.like{display:none}
  thead th:nth-child(5),thead th:nth-child(6){display:none}
  .doc section{grid-template-columns:1fr; gap:0}
  .doc section>h2{text-align:left; padding:.8rem var(--pad) .1rem;
    border-bottom:0; color:var(--ink)}
  .doc .sec-body{padding:.3rem var(--pad) 1rem}
}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

/* ==========================================================
   PRINT — same ground as the screen, harder. The ink goes to
   true black and the rules with it, because a #c9ccc4 hairline
   that reads as a rule on a backlit screen prints as nothing.
   Nothing here is a colour literal outside this block's tokens.
   ========================================================== */
@media print{
  :root{
    --paper:#ffffff; --ink:#000000; --rule:#000000; --dim:#444444;
    --flash:#000000; --flash-on:#ffffff;
  }
  /* the type colours go: hover does not exist on paper, and four inks on a
     page that is otherwise plain would be the only decoration on it */
  .t-ex,.t-pub,.t-on,.t-no{--flash:#000000;--flash-on:#ffffff}

  @page{margin:16mm 14mm}
  html,body{background:#fff; color:#000}
  body{font-size:10.5pt; line-height:1.4; padding:0}

  /* controls do not print, and neither does the game */
  #nav,#tools,#status,#peek,.back,#modes,#chips,#btags,#hud,#arcade,#targets,#lightbox{display:none!important}
  #top{position:static; border-bottom:1.5pt solid #000; padding-bottom:.2em}
  #top .id{padding-left:0}
  thead th{position:static}

  /* the list prints; the grid does not, and vice versa — whichever is on
     screen is what comes out, so print from the view you are looking at */
  .tablewrap{overflow:visible}
  table{font-size:9.5pt}
  thead{display:table-header-group}          /* repeat the head on every sheet */
  tr,figure,.doc section,.recimgs figure,.entry{break-inside:avoid}
  tbody td{padding:.18em .5em .18em 0; border-bottom:.25pt solid #999}
  .num,.date,.type,.place{color:#000}
  .like{display:none}                         /* the pairing is a screen gesture */
  thead th:nth-child(6){display:none}
  .type i{display:none}                       /* the swatch carries no ink meaning */
  /* A PLAYED FILM IS A BLANK RECTANGLE ON PAPER. An iframe prints as its
     background and nothing else, and making the film span the full row on
     screen would otherwise have turned a quarter-width grey box into most of a
     sheet. Known limit: the card it replaced carried the video's id, and that
     id is not on the printed page once the film has been played. Restoring it
     would mean hiding the card rather than replacing it in arkiv.js, which is
     more machinery than a printed record of an unplayed page needs. */
  .recmedia .film{display:none}

  #grid{grid-template-columns:repeat(4,1fr); gap:4mm; background:none}
  .cell img,.recimgs img,.doc img{filter:none; max-height:70mm; object-fit:contain}

  .head,.rechead{padding-left:0; border-bottom:1.5pt solid #000}
  .rechead h1,.head h1{font-size:20pt}
  .prose{padding:.6em 0 1em}
  .prose p{max-width:none; font-size:10.5pt}
  /* Grey reads as a quieter voice on a backlit screen and as weak ink on
     paper, which is the same reasoning that hardens the hairlines above. */
  .prose h2,.prose h3,.prose h4{color:#000; max-width:none;
    margin:1.4em 0 .4em; break-after:avoid}
  .prose ul,.prose ol,.prose blockquote{max-width:none}
  .doc section{grid-template-columns:22mm minmax(0,1fr)}
  .doc .sec-body{padding-right:0}

  /* a shut <details> would print as one word; the beforeprint handler opens
     every one and shuts them again afterwards */
  details.facts>summary{list-style:none}
  details.facts>summary::before{content:""}

  /* a printed link is a dead end without its address — but only in running
     text, never in the index, where 71 URLs would drown the table */
  .prose a[href^="http"]::after,.doc a[href^="http"]::after{
    content:" <" attr(href) ">"; font-family:var(--mono); font-size:8pt; word-break:break-all}
}
