/* ===========================================================================
   BRANDING - the one stylesheet for anything carrying the Arkode name.

   The website links this file and nothing else. The dashboard views link it
   too, then widgets.css on top for the parts that only a dashboard has
   (tiles, charts, the matrix). The CV wrapper links it, then CV.css.

   What lives here: the design tokens (the only place a colour is decided),
   the reset, type, links, images, tables, lists, code, the header and footer,
   the reading measure and the section bands, cards, the button, the switch
   (hidden radios, labels as buttons, panes shown by CSS - no script anywhere),
   the chip, print and reduced motion.

   The markdown is the source, the build writes the HTML, this file is the
   look. A script never carries a colour: inline SVG takes var(--green) as a
   fill, a bar's width arrives as a custom property, and the Photo Tagger reads
   the token lines below at start.

   Fonts resolve relative to THIS FILE, so it must sit beside Fonts/ wherever
   it is copied (the site build copies both to /shared).
   ======================================================================== */

/* Montserrat as ONE variable file, regular to bold, Latin only: 52 KB where
   the old regular-only TTF was 245 KB, and headings get a real bold instead
   of the browser smearing the regular to fake one. The ?v= is its version:
   bump it (here AND in the website template's preload) whenever the file
   changes, because the server caches versioned urls for a year. The TTF stays
   second as the fallback the dashboards have always had. (24/09) */
@font-face {
  font-family: 'Montserrat';
  src: url('Fonts/montserrat.woff2?v=2') format('woff2'),
       url('Fonts/montserrat-regular.ttf') format('truetype');
  font-weight: 400 700;
  font-display: swap;
}
/* While Montserrat is still arriving, headings draw in Arial stretched to
   Montserrat's measurements (worked out from the two fonts' own metrics), so
   the swap doesn't shove every heading sideways when it lands. */
@font-face {
  font-family: 'Montserrat Fallback';
  src: local('Arial');
  size-adjust: 110.38%;
  ascent-override: 87.70%;
  descent-override: 22.74%;
  line-gap-override: 0%;
}

:root {
  /* Surfaces, darkest to lightest - the page, then anything raised off it,
     then anything raised again inside that (quotes, cards, table heads).

     They aren't neutral grey. Each one carries a trace of the brand pink's
     hue, which is what stops a dark site reading as "unstyled black" - the
     eye can't name the tint, but it can tell the difference between a page
     that was coloured and one that just wasn't. */
  --bg: #17161a;
  --band: #232228;      /* every other section, so the page has a rhythm */
  --bg-alt: #2a2830;
  --panel: #322f3a;
  --line: #403c4a;

  /* body text sits a little under white so bold has somewhere to go */
  --text: #d8d5dd;
  --head: #ffffff;
  --muted: #9c98a6;

  /* The brand trio stays, but not at equal weight - three saturated colours
     all shouting is what made the old site restless. Pink is the interface
     colour (links), green is structure (rules, card trim), yellow is reserved
     for focus rings alone, where its 11:1 contrast is an asset.
     --pink-text is the readable version: the brand pink is 2.9:1 on the page
     and fails as body text; this one is 7.9:1. */
  --pink: #C23C8F;
  --pink-text: #EC8FC2;
  --pink-hover: #F7B4D8;
  --green: #4FBF87;
  --yellow: #F1E731;

  /* Rose is the one colour that means "wrong": overdue, blocked, a warning
     tile, money going out. Pink is never used for that, so a pink thing is
     always an interface and a rose thing always a problem. --rose-text is the
     readable version, the way --pink-text is. */
  --rose: #a63d5f;
  --rose-text: #e39ab2;

  /* One radius on everything that can have one; mixed radii read as several
     unrelated widgets rather than one design. */
  --radius: 6px;

  /* the reading measure, ~77 characters. Fixed, NOT in ch: ch is relative to
     each element's own font, so headings would come out wider than the body
     text and every block would start at a different left edge.

     44rem is a compromise, and worth knowing as one. 40rem read at about 70
     characters, which is ideal, but the card grid then sat wider than every
     other block on the page and the misalignment was the thing you noticed.
     Fitting three cards at their old 240px minimum would have needed 47rem,
     or ~82 characters, which is most of the way back to the 95 that made the
     site hard to read in the first place. This holds the line just inside
     comfortable and lets the cards come in narrower to meet it.
     A dashboard is a wall of instruments, not a read, and sets .wide on its
     main to take 72rem instead. */
  --measure: 44rem;
  --gutter: 3rem;

  /* Header geometry, measured not guessed: banner 150 + logo 48 + nav 52.
     --chrome-up is the part allowed to scroll away, --nav-h the strip that
     stays. The breadcrumb parks directly under it. Change the banner height
     and these two are the only numbers that follow. */
  --chrome-up: 198px;
  --nav-h: 54px;        /* header height 252 minus --chrome-up, incl. its border */

  /* Montserrat is a display face - fine for headings, tiring for a long
     read, so body copy uses whatever the reader's system does well */
  --font-head: 'Montserrat', 'Montserrat Fallback', system-ui, sans-serif;
  --font-body: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-mono: Consolas, 'Cascadia Mono', monospace;
}
* { box-sizing: border-box; }
html {
  color-scheme: dark;                 /* scrollbars and form controls match */
}
/* the footer sits at the bottom of the window on a short page instead of
   floating halfway up it */
body {
  margin: 0; min-height: 100dvh;
  display: flex; flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.7;
  overflow-wrap: break-word;          /* a long path can't widen the page */
}
::selection { background: var(--pink); color: #fff; }

/* Where a jump lands is tinted, and the tint STAYS - it used to flash and
   fade over 1.8s, which is motion a photosensitive reader didn't ask for
   (24/09). An INSET SHADOW, not a background: a background would replace a
   banded section's own colour and grid; a shadow lays over whatever is there
   and leaves it alone. A shared "link to highlight" marks its words too. */
:target { box-shadow: inset 0 0 0 100vmax rgba(236,143,194,.06); }
::target-text { background: var(--pink); color: #fff; }

/* links are underlined everywhere they sit in prose - colour alone was the
   only thing marking them, and it was the weakest colour on the page */
a { color: var(--pink-text); text-decoration: underline; text-underline-offset: .16em; text-decoration-thickness: 1px; }
a:hover { color: var(--pink-hover); }
a:focus-visible { outline: 2px solid var(--yellow); outline-offset: 3px; border-radius: 2px; }
.site-header a, .site-footer a, .card { text-decoration: none; }

/* bold is weight and brightness, not colour - green speckled every page,
   but plain 600 on near-white body text didn't stand out either */
strong, b { font-weight: 700; color: var(--head); }

img { display: block; max-width: 100%; height: auto; margin: 1rem auto; border-radius: var(--radius); }
hr { border: 0; border-top: 1px solid var(--line); margin: 2.5rem 0; }

/* header
   Two images, no classes to target - so they're addressed by position:
   first paragraph = the wide blockout banner, second = the arkode logo,
   third = the nav. The dashboards write the same three paragraphs. */
/* The banner and logo ride up out of view and the nav lands against the top
   of the window. Sticky is clipped by its own parent, so the whole header is
   the sticky element and its top offset is negative by exactly the height of
   the part that's allowed to leave. */
.site-header {
  background: var(--bg-alt); text-align: center; border-bottom: 1px solid var(--green);
  position: sticky; top: calc(-1 * var(--chrome-up)); z-index: 50;
}
.site-header p:nth-child(3) { background: var(--bg-alt); }
@media (max-width: 640px) { :root { --chrome-up: 130px; --nav-h: 53px; } }

/* BREADCRUMB - every level above this page, each one a link. It sits outside
   the content column and parks under the nav on scroll, so it stays reachable
   without taking a line out of the writing. Root pages emit none at all. */
.crumbs {
  position: sticky; top: var(--nav-h); z-index: 45;
  background: var(--bg-alt); border-bottom: 1px solid var(--line);
  padding: .45rem 0; font-size: .8rem;
}
.crumbs-inner {
  display: block; width: min(var(--measure), 100% - var(--gutter));
  margin: 0 auto;
  white-space: nowrap; overflow-x: auto; scrollbar-width: none;
}
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--green); }
.crumb-sep { color: var(--line); padding: 0 .45rem; }

/* ON-PAGE CONTENTS - the sections of THIS page, in the left margin.
   Built by Pandoc's --toc from the headings, so it needs no maintenance and
   no note ever mentions it. The page is a centred column, so past 1180px
   there is dead space either side doing nothing; below that it isn't drawn
   at all, because a phone has no margin to put it in.

   Two things it does with :has() and structure rather than markup:
   the page's own h1 is the outer list item, and its link is hidden so the
   list starts at the sections - and a contents list with only ONE section in
   it is noise, so it removes itself. */
.page-toc { display: none; }
@media (min-width: 1180px) {
  .page-toc {
    display: block; position: fixed; left: 2rem; z-index: 40;
    /* Centred in the space BELOW the header, not in the window. Centring on
       the window put the top of a long list at 15vh, which is behind the
       header while the page is scrolled to the top - so the first few
       entries were cut off. top + bottom + margin-block:auto centres it
       inside the safe band instead, so it can never ride up under the nav
       and it still centres on shorter lists. */
    top: calc(var(--chrome-up) + var(--nav-h) + 1rem);
    bottom: 2rem;
    height: fit-content; margin-block: auto;
    max-height: calc(100vh - var(--chrome-up) - var(--nav-h) - 3rem);
    width: 12rem; overflow-y: auto;
    font-size: .78rem; line-height: 1.45;
    padding-left: .9rem; border-left: 1px solid var(--line);
  }
  .page-toc ul { list-style: none; margin: 0; padding: 0; }
  .page-toc > ul > li > a { display: none; }        /* that's the page title */
  .page-toc:not(:has(li + li)) { display: none; }   /* one section isn't a contents list */
  .page-toc li { margin: 0; }
  .page-toc a {
    display: block; padding: .3rem 0; color: var(--muted);
    text-decoration: none; border-left: 2px solid transparent; padding-left: .6rem;
    margin-left: -.6rem;
  }
  .page-toc a:hover { color: var(--text); border-left-color: var(--green); }
  .page-toc ul ul ul a { padding-left: 1.5rem; font-size: .74rem; }
}

@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }
.site-header p { margin: 0; }
.site-header p:first-child img {          /* banner: a strip, not a wall */
  width: 100%; max-width: none; height: 150px; object-fit: cover;
  margin: 0; border-radius: 0;
}
.site-header p:nth-child(2) img {         /* logo: sized, and lifted to overlap */
  width: 96px; max-width: none; height: auto;
  margin: -48px auto .1rem; position: relative;
}
@media (max-width: 640px) {
  .site-header p:first-child img { height: 96px; }
  .site-header p:nth-child(2) img { width: 68px; margin-top: -34px; }
}
.site-header a {
  display: inline-block; margin: 0 .5rem; padding: .9rem .2rem; color: var(--text);
  font-family: var(--font-head);
  text-transform: uppercase; letter-spacing: .05em; font-size: .85rem;
  border-bottom: 2px solid transparent;
}
.site-header a:hover { color: var(--green); }
/* the page you are on - the build marks the nav link of this page's root */
.site-header a.here { color: var(--head); border-bottom-color: var(--green); }

/* content
   The page runs full width and the SECTIONS carry the colour: every other one
   sits on --band, so the page reads as a stack of blocks rather than one long
   slab. --section-divs in the build is what makes this possible - CSS has no
   way to say "everything between this h2 and the next".

   Nothing is centred by a wrapper. Each piece of content sizes itself to
   min(measure, window minus gutters) and centres itself, so a band can run the
   full width of the screen while the text inside it stays a readable measure. */
.content { max-width: none; margin: 0; padding: 2rem 0 1rem; flex: 1 0 auto; }
.content.wide { --measure: 72rem; }         /* a dashboard, not a read */
.content section { max-width: none; }

:is(.content, .content section) > *:not(section):not(img) {
  width: min(var(--measure), 100% - var(--gutter));
  margin-left: auto; margin-right: auto;
}

/* A section is banded only if it owns an h2 - an h3 subsection stays inside
   the band it sits in rather than starting a new one.

   ODD, not even: the page title and its opening paragraphs are a block in
   their own right, sitting on the page colour, so the FIRST h2 section has to
   be the banded one or the two run together as one long slab. */
.content section:has(> h2) { padding: 3rem 0; }
.content section:has(> h2):nth-of-type(odd) { background: var(--band); }

/* Cards sit on the measure like everything else. They used to break out to
   68rem, which made the portfolio grid the only block on the site that
   didn't share a left edge with its own heading. The width won back is not
   worth the page looking like two documents. */
.content .cards, .content .cards.rows { width: min(var(--measure), 100% - var(--gutter)); }

/* paragraph gaps have to beat the line gaps inside them, or paragraphs
   don't separate: line-height 1.7 puts lines ~1.7em apart, so 1em margins
   made the block one grey slab */
p { margin: 0 0 1.4em; text-wrap: pretty; }

/* a paragraph that introduces a list keeps its list close, so "Gunzilla
   Games - Junior Level Designer" and its bullets read as one block */
.content p:has(+ ul), .content p:has(+ ol) { margin-bottom: .5em; }
ul, ol { margin: 0 0 1.4em; padding-left: 2.2rem; }
li { margin: .3rem 0; }
li::marker { color: var(--muted); }

/* one colour for headings - size, weight and space do the ranking.
   scroll-margin keeps an anchor jump off the top edge. */
.content h1, .content h2, .content h3, .content h4 {
  font-family: var(--font-head); color: var(--head);
  scroll-margin-top: 5rem;            /* clears the sticky nav */
  text-wrap: balance;
}
.content section[id] { scroll-margin-top: 5rem; }   /* --section-divs moves the anchor id onto the section */
.content h1 { font-size: 2.1rem; line-height: 1.25; margin: .2rem 0 1.4rem; }
/* no top margin: the band's own padding is the space above a section heading,
   and stacking the two is what made the gaps look uneven */
.content h2 { font-size: 1.5rem; margin: 0 0 1.4rem; padding-bottom: .5rem; border-bottom: 1px solid var(--line); }
.content h3 { font-size: 1.2rem; margin: 2.2rem 0 .8rem; }
.content h4 {                                 /* a label, not a third shout */
  font-size: .78rem; margin: 1.6rem 0 .5rem;
  color: var(--muted); text-transform: uppercase; letter-spacing: .09em; font-weight: 600;
}

/* images sit inside the column instead of defining it, and the space below
   is bigger than the space above so a picture belongs to what it follows */
.content img { max-width: min(560px, 100% - var(--gutter)); margin: 1.25rem auto 2rem; }

/* IMAGE CLUSTERS, with no new markdown syntax: put two or more images in the
   SAME paragraph (consecutive lines, no blank line between) and Pandoc emits
   them in one <p>. :has() spots that and lays them out in a row.

   TWO to a row, never more. Fitting three across made every one of them too
   small to read and pushed the block far wider than the text it belongs to;
   two means each image is big enough to actually look at. A set with an odd
   number lets its LAST image run the full width rather than leaving a
   half-empty row, which is what makes three or five sit properly.
   Below 52em they stack one to a row. */
.content p:has(img + img) {
  display: grid; grid-template-columns: 1fr 1fr; gap: .6rem;
  width: min(52rem, 100% - var(--gutter));
  margin: 1.5rem auto 2.25rem;
}
.content p:has(img + img) img {
  width: 100%; height: auto; max-width: none; min-width: 0;
  aspect-ratio: 16 / 10; object-fit: cover;
  margin: 0;
}
.content p:has(img + img) img:last-child:nth-child(odd) {
  grid-column: 1 / -1; aspect-ratio: 21 / 9;
}
@media (max-width: 52em) {
  .content p:has(img + img) { grid-template-columns: 1fr; }
  .content p:has(img + img) img:last-child:nth-child(odd) { aspect-ratio: 16 / 10; }
}

/* A single image sharing a paragraph with words sits beside them. Only where
   there's room: on a phone it drops back to a full-width block, because a
   third of a phone screen is not a picture. */
@media (min-width: 46em) {
  .content p.with-text:not(:has(img + img)) > :is(img, a.img-full) {
    float: right; width: 42%; max-width: 17rem;
    margin: .3rem 0 1rem 1.75rem;
  }
  .content p.with-text:not(:has(img + img)) { overflow: visible; }
  .content p.with-text > a.img-full img { width: 100%; margin: 0; }
}

/* The line under a page title, printed from frontmatter (or by the script
   that drew the page) rather than typed into the writing - so the date shown
   and the date sorted by can't drift apart. Pulled up under the h1's own
   bottom margin so it reads as part of the title block. */
.content .page-meta {
  margin-top: -1.1rem; margin-bottom: 2.2rem;
  color: var(--muted); font-size: .85rem;
}

/* A LINE OF LINKS BECOMES A ROW OF BUTTONS, with no new markdown syntax:
   write "[Portfolio »](/portfolio) · [Qualifications ↓](#qualifications)" as
   a paragraph of its own and the build marks it, because a paragraph that is
   nothing but links is never prose - it's a set of choices.

   Outlined, not filled. The brand pink is a background colour that white
   text only just clears, and a row of solid pink slabs would be the loudest
   thing on any page it sat on.

   ONE rule dresses every button on the site and every switch label on the
   dashboards. The chosen label of a switch is the same button, lit. */
p.button-row {
  display: flex; flex-wrap: wrap; gap: .6rem;
  margin: 1.7rem auto 2.1rem;
}
p.button-row a, .switch label {
  display: inline-block; padding: .55rem 1.2rem;
  font-family: var(--font-head); font-size: .85rem; letter-spacing: .03em;
  color: var(--pink-text); background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius);
  text-decoration: none; cursor: pointer;
  transition: color .2s ease, border-color .2s ease, background .2s ease, transform .2s ease;
}
p.button-row a:hover, .switch label:hover {
  color: var(--pink-hover); border-color: var(--pink);
  background: var(--bg-alt);
}
p.button-row a:hover { transform: translateY(-2px); }

/* THE FEED BUTTON, on the articles page. The row it sits in is the ordinary
   button-row above; all this adds is the universal RSS glyph, so the button
   is recognisable at a glance without a word of explanation.

   The icon is a MASK rather than a coloured image, so it takes the link's own
   colour and moves with it on hover. A fixed orange square would be the one
   thing on the page not speaking the palette, and this site has never shouted
   to be noticed. */
p.button-row a.rss::before {
  --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='6.18' cy='17.82' r='2.18'/%3E%3Cpath d='M4 4.44v2.83c7.03 0 12.73 5.7 12.73 12.73h2.83c0-8.59-6.97-15.56-15.56-15.56zm0 5.66v2.83c3.9 0 7.07 3.17 7.07 7.07h2.83c0-5.47-4.43-9.9-9.9-9.9z'/%3E%3C/svg%3E");
  content: "";
  display: inline-block;
  width: .8em; height: .8em;
  margin-right: .5em;
  vertical-align: -.02em;
  background: currentColor;
  -webkit-mask: var(--glyph) center / contain no-repeat;
  mask: var(--glyph) center / contain no-repeat;
}

/* THE SWITCH - a row of buttons choosing which of several panes is on show,
   or which cards stay in a grid. Pure CSS: the radios hold the state and
   :has() reads it. No script, and no CSS is generated per switch either: the
   rule is POSITIONAL - the nth option shows the nth pane - so the same ten
   rules serve a two-way CV switch, a four-way chart range and a seven-topic
   fact filter alike. A switch may have up to ten options.

   Markup (the build and the dashboard scripts both write exactly this):

     <div class="sw">
       <div class="switch">
         <input class="pick" type="radio" name="x" id="x-1" checked><label for="x-1">A</label>
         <input class="pick" type="radio" name="x" id="x-2"><label for="x-2">B</label>
       </div>
       <div class="panes">
         <div>pane A</div>
         <div>pane B</div>
       </div>
     </div>

   A first option marked class="pick all" shows every pane (a filter's "All").
   A switch that sits under the page title (class="switch page", a direct
   child of main) drives every div.panes.page anywhere below it, so one range
   picker can run several charts down the page.

   A card grid filters instead of paning: <div class="sw tags"> holds the
   switch and the .cards; each card carries k2, k3... for the options it
   answers to, and the nth option hides every card without k<n>.

   The radios are moved out of sight rather than display:none'd, so they stay
   focusable and the group still behaves as keyboard radios; the chosen state
   and the focus ring are drawn on the LABEL, because the input is off-screen. */
.pick {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.switch {
  width: min(var(--measure), 100% - var(--gutter));
  margin: 1.7rem auto -0.5rem;
  display: flex; flex-wrap: wrap; gap: .6rem;
}
.content > .switch.page { margin: -1rem auto 1.4rem; }
.pick:checked + label { color: var(--head); border-color: var(--green); background: var(--bg-alt); }
.pick:focus-visible + label { outline: 2px solid var(--yellow); outline-offset: 3px; }
.switch label i { font-style: normal; opacity: .55; margin-left: .35em; }   /* a count after the label */

.sw:has(> .switch > .pick:nth-of-type(1):not(.all):checked) > .panes > :not(:nth-child(1)),
.sw:has(> .switch > .pick:nth-of-type(2):checked) > .panes > :not(:nth-child(2)),
.sw:has(> .switch > .pick:nth-of-type(3):checked) > .panes > :not(:nth-child(3)),
.sw:has(> .switch > .pick:nth-of-type(4):checked) > .panes > :not(:nth-child(4)),
.sw:has(> .switch > .pick:nth-of-type(5):checked) > .panes > :not(:nth-child(5)),
.sw:has(> .switch > .pick:nth-of-type(6):checked) > .panes > :not(:nth-child(6)),
.sw:has(> .switch > .pick:nth-of-type(7):checked) > .panes > :not(:nth-child(7)),
.sw:has(> .switch > .pick:nth-of-type(8):checked) > .panes > :not(:nth-child(8)),
.sw:has(> .switch > .pick:nth-of-type(9):checked) > .panes > :not(:nth-child(9)),
.sw:has(> .switch > .pick:nth-of-type(10):checked) > .panes > :not(:nth-child(10)),
.content:has(> .switch.page > .pick:nth-of-type(1):not(.all):checked) .panes.page > :not(:nth-child(1)),
.content:has(> .switch.page > .pick:nth-of-type(2):checked) .panes.page > :not(:nth-child(2)),
.content:has(> .switch.page > .pick:nth-of-type(3):checked) .panes.page > :not(:nth-child(3)),
.content:has(> .switch.page > .pick:nth-of-type(4):checked) .panes.page > :not(:nth-child(4)),
.content:has(> .switch.page > .pick:nth-of-type(5):checked) .panes.page > :not(:nth-child(5)),
.content:has(> .switch.page > .pick:nth-of-type(6):checked) .panes.page > :not(:nth-child(6)),
.content:has(> .switch.page > .pick:nth-of-type(7):checked) .panes.page > :not(:nth-child(7)),
.content:has(> .switch.page > .pick:nth-of-type(8):checked) .panes.page > :not(:nth-child(8)),
.content:has(> .switch.page > .pick:nth-of-type(9):checked) .panes.page > :not(:nth-child(9)),
.content:has(> .switch.page > .pick:nth-of-type(10):checked) .panes.page > :not(:nth-child(10)) { display: none; }
/* the panes and the tag grid sit on the measure like any other block; the
   .sw wrapper itself is transparent to layout */
.content .sw, .content section > .sw { width: auto; }
.content .sw > .panes > *, .content .sw > .switch, .content .sw > .legend {
  width: min(var(--measure), 100% - var(--gutter)); margin-left: auto; margin-right: auto;
}

/* THE CHIP - a small pill for a state or a count, on the dashboards. The
   site's tags have their own chip (TAGS, 25/09). */
.chip {
  display: inline-block; font-size: .72rem; font-weight: normal;
  color: var(--muted); background: var(--bg);
  border: 1px solid var(--line); border-radius: 999px;
  padding: .05rem .55rem; white-space: nowrap;
  transition: border-color .25s ease, color .25s ease;
}

blockquote {
  margin: 1.2rem auto; padding: .45rem 1rem;
  border-left: 3px solid var(--pink); background: var(--panel);
  color: var(--text); font-style: italic;
  font-size: .92rem; line-height: 1.55;
}
blockquote p { margin: 0 0 .6em; }
blockquote p:last-child { margin-bottom: 0; }

/* an embedded PDF, built from an ![[file.pdf]] embed in the markdown. Two of
   them side by side in a section become a switch - see the build. */
.pdf-embed { width: min(var(--measure), 100% - var(--gutter)); margin: 1.5rem auto 2rem; }
.pdf-embed iframe {
  display: block; width: 100%; aspect-ratio: 1 / 1.35;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
  margin: 0;
}

code { background: var(--bg); padding: .1rem .35rem; border-radius: 4px; font-family: var(--font-mono); font-size: .88em; }
pre { background: var(--bg); padding: 1rem; border-radius: var(--radius); overflow-x: auto; border: 1px solid var(--line); }
pre code { background: none; padding: 0; }

/* tables span the measure. The old problem - a one-word grade flung 800px
   from its row - is solved by the narrower column, not by shrinking them.

   A table is reference material, not reading text, so it is allowed more room
   than the 44rem measure: four columns of prose at 44rem is a column of
   single words. It still centres on the same axis as everything else, and
   the box never exceeds the window. Anything wider than the box scrolls
   INSIDE it, so a wide table can no longer widen the whole page.
   The build strips Pandoc's inline width and adds this wrapper. */
.content .table-scroll {
  width: min(64rem, 100% - var(--gutter));
  margin: 1.5rem auto; overflow-x: auto;
}
.content .table-scroll > table { margin: 0; min-width: 100%; }
table { border-collapse: collapse; margin: 1.5rem auto; width: 100%; }
th, td { border: 1px solid var(--line); padding: .55rem .9rem; text-align: left; }
th { background: var(--panel); color: var(--head); font-weight: 600; }
tbody tr:nth-child(odd) { background: var(--band); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.pos { color: var(--green); }
.neg { color: var(--rose-text); }

/* ---------------------------------------------------------------------------
   THE REST OF THE MARKDOWN STANDARD

   Everything above styles what the pages happened to use. This styles the rest
   of what Pandoc can emit, so reaching for a definition list or a footnote
   lands designed instead of on browser defaults - which is what stopped the
   Mosaic page using either. Audited 2026-08-06 by rendering a document that
   exercises every construct and diffing the tags and classes it produced
   against the selectors in this file.
   --------------------------------------------------------------------------- */

/* Definition lists. The term carries the weight and the definition is set off
   a rule, so a long run reads as pairs rather than one column of text. This is
   the shape to reach for when a bulleted list is really term-and-explanation:
   the term becomes scannable and the prose stops competing with it. */
dl { margin: 1.4rem auto; }
dt { font-family: var(--font-head); color: var(--head); font-weight: 600; margin: 1.3rem 0 .4rem; }
dt:first-child { margin-top: 0; }
dd { margin: 0 0 1rem; padding-left: 1rem; border-left: 1px solid var(--line); }
dd > :last-child { margin-bottom: 0; }

/* Footnotes. The marker stays small enough not to interrupt the line it sits
   in, and the notes are set back so the end of an article doesn't read as more
   article. Pandoc's own <hr> above them is already styled and already sized to
   the measure, so it does the dividing. */
.footnote-ref { text-decoration: none; font-size: .8em; padding: 0 .15em; }
.footnotes { margin-top: 2.5rem; color: var(--muted); font-size: .92rem; }
.footnotes ol { margin: .8rem auto; }
.footnotes li { margin: .5rem 0; }
.footnote-back { text-decoration: none; margin-left: .35em; }

/* Fifth and sixth level headings. Rare, but without these a deep page falls
   back to browser defaults partway down. */
.content h5, .content h6 {
  font-family: var(--font-head); scroll-margin-top: 5rem;
  font-size: .95rem; font-weight: 600; margin: 1.5rem 0 .4rem;
}
.content h5 { color: var(--text); }
.content h6 { color: var(--muted); }

/* A table's caption, which Pandoc places above the table it describes. */
caption { caption-side: top; text-align: left; color: var(--muted); font-size: .9rem; padding-bottom: .5rem; }

/* A figure is an image that came with a caption. */
figure { margin: 1.25rem auto 2rem; }
.content figure img { margin: 0 auto .6rem; }
figcaption { color: var(--muted); font-size: .9rem; text-align: center; }

/* Task lists drop the bullet - the box is the marker. */
ul.task-list { list-style: none; padding-left: .3rem; }
ul.task-list li input { margin-right: .5em; accent-color: var(--pink); }

/* A line block keeps the breaks the author typed: addresses, verse, anything
   where the wrapping is the meaning. */
.line-block { margin: 1.4rem auto; line-height: 1.6; }

/* Inline marks. Some of these only arrive through raw HTML in the markdown,
   which is itself part of the standard. */
sub, sup { font-size: .75em; line-height: 0; }
del { color: var(--muted); }
ins { text-decoration: none; border-bottom: 1px solid var(--green); }
mark { background: var(--pink); color: var(--head); padding: 0 .2em; border-radius: 3px; }
abbr[title] { text-decoration: underline dotted; cursor: help; }
kbd {
  background: var(--panel); border: 1px solid var(--line); border-bottom-width: 2px;
  border-radius: 4px; padding: .05rem .35rem;
  font-family: var(--font-mono); font-size: .85em;
}

iframe, video { display: block; width: 100%; aspect-ratio: 16/9; border: 0; margin: 1.2rem auto; border-radius: var(--radius); background: var(--bg); }

/* A block-level replaced element is not allowed to overlap a float, so it
   gets shunted sideways into whatever space is left rather than sitting on
   the measure like everything else. On Off the Grid the floated logo hangs
   below its own paragraph and pushed the video 420px left of every other
   block on the page. `clear` is the fix: a video belongs under the text it
   follows, never beside a logo. Same trap waits for tables and image grids,
   which establish their own formatting context for the same reason. */
.content iframe, .content video, .content table, .content p:has(img + img), .content p.frames { clear: both; }

/* portfolio cards - no JavaScript anywhere; the movement is all CSS.
   Three across, fixed rather than auto-filled: auto-fill sizes itself to a
   minimum card width, which is what let the grid decide the column's width
   instead of the other way round. Three is the count that fills the measure
   without leaving a half-empty row on a six-piece portfolio. */
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
  margin: 1.2rem 0 2.6rem;
}
/* two across before the cards get too narrow to read, then one. Card ROWS
   are a single column at every size and set their own rules below. */
@media (max-width: 46em) { .cards:not(.rows) { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 30em) { .cards:not(.rows) { grid-template-columns: 1fr; } }
/* The hover shadow is drawn ONCE, on the card's own ::after layer, and only
   its opacity animates. A box-shadow transition repaints the shadow on every
   frame of the lift; an opacity change is the compositor's alone. Any .card
   anywhere gets this: nothing here knows about grids or pages (24/09).
   isolation keeps the layer behind the card's content but in front of the
   band it sits on; the card no longer clips (the shadow would be cut off),
   so the image rounds its own corners instead - see .card-img. */
.card {
  display: flex; flex-direction: column;
  position: relative; isolation: isolate;
  background: var(--panel);
  border: 1px solid var(--line); border-radius: 8px;
  color: var(--text);
  transition: transform .25s ease, border-color .25s ease;
  animation: card-in .45s ease-out both;
}
.card::after {
  content: ""; position: absolute; inset: -1px; z-index: -1; pointer-events: none;
  border-radius: inherit; box-shadow: 0 10px 26px rgba(0,0,0,.45);
  opacity: 0; transition: opacity .25s ease;
}
.card:hover, .card:focus-visible { transform: translateY(-6px); }
.card:hover::after, .card:focus-visible::after { opacity: 1; }
/* the three brand colours cycle across the grid, so no two neighbours match */
.card:nth-child(3n+1):hover, .card:nth-child(3n+1):focus-visible { border-color: var(--pink); }
.card:nth-child(3n+2):hover, .card:nth-child(3n+2):focus-visible { border-color: var(--green); }
.card:nth-child(3n+3):hover, .card:nth-child(3n+3):focus-visible { border-color: var(--yellow); }

.card-img { display: block; overflow: hidden; aspect-ratio: 16 / 10; background: var(--bg); border-radius: 7px 7px 0 0; }
.card-img img {
  width: 100%; height: 100%; max-width: none; object-fit: cover;
  margin: 0; border-radius: 0;
  transition: transform .5s ease;
}
.card:hover .card-img img, .card:focus-visible .card-img img { transform: scale(1.07); }

.card-body { display: block; padding: .85rem 1rem 1rem; }
.card-title { display: block; font-family: var(--font-head); color: var(--head); font-size: 1.05rem; line-height: 1.35; }
.card-role {
  display: block; font-family: var(--font-head); color: var(--green);
  font-size: .74rem; text-transform: uppercase; letter-spacing: .07em; margin-top: .2rem;
}
/* only ever a publication date - `updated` is housekeeping and never shown */
.card-date {
  display: block; font-family: var(--font-head); color: var(--muted);
  font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; margin-top: .3rem;
}
.card-desc {                                  /* clamped so cards stay level */
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
  color: var(--muted); font-size: .85rem; line-height: 1.5; margin-top: .55rem;
}
.card-tags { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .75rem; }
.card:hover .card-tags .tag { border-color: var(--muted); color: var(--text); }

/* Article rows - the same card laid on its side. One column, image left,
   text right, and it collapses back to a stacked card on a narrow screen. */
.cards.rows { grid-template-columns: 1fr; gap: .9rem; }
.cards.rows .card { flex-direction: row; align-items: stretch; }
.cards.rows .card-img { flex: 0 0 30%; max-width: 260px; aspect-ratio: 16 / 9; border-radius: 7px 0 0 7px; }
.cards.rows .card-body { flex: 1 1 auto; padding: 1rem 1.2rem; }
.cards.rows .card-desc { -webkit-line-clamp: 2; }
.cards.rows .card:hover, .cards.rows .card:focus-visible { transform: translateX(6px); }

@media (max-width: 620px) {
  .cards.rows .card { flex-direction: column; }
  .cards.rows .card-img { flex-basis: auto; max-width: none; border-radius: 7px 7px 0 0; }
}

/* cards arrive in sequence rather than all at once */
@keyframes card-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
.card:nth-child(1) { animation-delay: 0s }
.card:nth-child(2) { animation-delay: .07s }
.card:nth-child(3) { animation-delay: .14s }
.card:nth-child(4) { animation-delay: .21s }
.card:nth-child(5) { animation-delay: .28s }
.card:nth-child(6) { animation-delay: .35s }
.card:nth-child(7) { animation-delay: .42s }
.card:nth-child(8) { animation-delay: .49s }

@media (prefers-reduced-motion: reduce) {
  .card, .card::after, .card-img img, .card-tags .tag, p.button-row a, .switch label { animation: none; transition: none; }
  p.button-row a:hover { transform: none; }
}

/* printing - the CV page is the one people actually print */
@media print {
  .site-header, .site-footer, .pdf-embed, .switch, .crumbs, .page-toc { display: none; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .content section:has(> h2) { padding: .5rem 0; background: none !important; }
  .content h1, .content h2, .content h3, .content h4,
  .content h5, .content h6, dt, strong, b { color: #000; }
  dd { border-left-color: #999; }
  mark { background: none; border-bottom: 2px solid #000; color: #000; }
  a { color: #000; text-decoration: underline; }
  .content section { break-inside: auto; }
  h2, h3 { break-after: avoid; }
}

/* footer */
.site-footer { background: var(--bg-alt); text-align: center; padding: 1.5rem 1rem; border-top: 1px solid var(--green); color: var(--muted); font-size: .9rem; margin-top: auto; }
.site-footer p { margin: .25rem 0; }
.site-footer a { color: var(--muted); margin: 0 .4rem; }
.site-footer a:hover { color: var(--green); }
.site-footer code { font-size: .8em; }



/* ===========================================================================
   THE WEBSITE'S OWN LOOK. Everything above is shared with the dashboards and
   the CV; this block is what only arkodegames.com does, so each rule that
   changes page structure is scoped to body.site (the website's template sets
   it, the dashboards never do). Decided from the twenty-suggestion demo on
   14/09; the numbers in the comments are that note's. Not adopted: cross-page
   view transitions (a white frame on some page changes), film grain, the old
   je-font. No script anywhere: motion is scroll timelines and transitions,
   state is CSS. Reduced motion is honoured at the end of the block.

   Vocabulary used throughout: one small mono voice for every readout on the
   site - section numbers, the spec strip's labels, a card's stage badge, an
   image caption - so they read as one instrument panel.
   ======================================================================== */

.readout, .site .content h2::before, .site .content h3::before,
.page-spec, .card[data-stage]::before, .site figcaption {
  font-family: var(--font-mono); letter-spacing: .1em;
}

/* 2. SCROLL-DRIVEN REVEALS. Section headings and cards fade and rise as they
   enter the window, on the compositor. The range is a LENGTH so a heading and
   a card take the same 220px of travel. Replaces the load-time card stagger
   on the site (the dashboards keep theirs). Chrome, Edge, Safari 26, Firefox
   143+. Trimmed 24/09 from every block to headings and cards: Off the Grid
   was running 124 of these, and paragraphs are for reading, not arriving. */
@keyframes rise-in { from { opacity: 0; translate: 0 14px; } to { opacity: 1; translate: 0 0; } }
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .site .content section > :is(h2, h3),
    .site .content .card {
      animation: rise-in linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 220px;
    }
  }
}

/* 3. READING PROGRESS, with the page's sections marked on it. The 2px line
   under the nav is the scroll position. The markers are the root scroller's
   own scroll markers, one per h2 section: each a link to its section, the one
   on screen lit yellow (the third brand colour, kept for this and the focus
   ring). They sit at the right end of the line and ride up with the header
   until it sticks. CSS cannot place them where the sections fall on the page
   (that needs layout, i.e. script), so they are evenly spaced: a section
   index, not a ruler. Hidden on a phone, where the nav fills the strip. */
@keyframes grow-x { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes ride-up { from { translate: 0 0; } to { translate: 0 calc(-1 * var(--chrome-up)); } }
@supports (animation-timeline: scroll()) {
  .site .site-header::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
    background: var(--pink); transform-origin: 0 50%; z-index: 2;
    animation: grow-x linear both; animation-timeline: scroll(root);
  }
}
/* (The section dots at its right end were retired 26/09, his call: they
   repeated the contents list.) */

/* 4. THE NAV KNOWS WHEN IT IS STUCK: a shadow and brighter links once the
   banner has scrolled away. Chrome and Edge 133+; elsewhere the nav is as is. */
.site .site-header { container-type: scroll-state; }
@container scroll-state(stuck: top) {
  .site-header p:nth-child(3) { box-shadow: 0 10px 24px rgba(0,0,0,.45); background: var(--panel); }
  .site-header p:nth-child(3) a { color: var(--head); }
}

/* 5. THE CONTENTS LIST MARKS THE SECTION IN VIEW. Chrome, Edge; elsewhere unchanged. */
.page-toc { scroll-target-group: auto; }
.page-toc a:target-current { color: var(--head); border-left-color: var(--green); }

/* 6. BANNER TINT. The pink-to-green wash that ties the blockout render to the
   palette is baked into the banner file itself since 24/09
   (Branding/blockout-banner-tinted.png: the same 105deg gradient, overlay,
   38%), so the sticky header no longer carries a blend layer. */

/* 7. BLUEPRINT PAPER. The page itself is dotted at the crossings of a 24px
   grid (the title block, the plain sections and the foot all show it, as one
   continuous sheet); the banded sections paint over it with the same grid as
   faint lines. Two rulings of one sheet, both under 5% contrast. */
.site .content {
  background-image: radial-gradient(rgba(255,255,255,.11) 1px, transparent 1.5px);   /* .055 until 26/09: too faint on his screen */
  background-size: 24px 24px; background-position: 12px 12px;
}
.site .content section:has(> h2):nth-of-type(odd) {
  background-color: var(--band);
  background-image:
    linear-gradient(rgba(255,255,255,.055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.055) 1px, transparent 1px);
  background-size: 24px 24px;
}

/* 9. SECTION MARKERS. 01, 02 before each h2 and 02.1 before each h3 inside
   it, in green. CSS counters, no markup. An h3 straight under the title has
   no sector to belong to and stays unnumbered. */
.site .content { counter-reset: sec; }
.site .content section:has(> h2) { counter-increment: sec; counter-reset: sub; }
.site .content section:has(> h3) { counter-increment: sub; }
.site .content h2::before, .site .content h3::before {
  font-size: .7em; font-weight: 400; color: var(--green); margin-right: .7em; vertical-align: .14em;
}
.site .content h2::before { content: counter(sec, decimal-leading-zero); }
.site .content section:has(> h2) h3::before { content: counter(sec, decimal-leading-zero) "." counter(sub); }

/* 10 + 11. ONE HOVER LANGUAGE FOR IMAGES. Every content image answers a
   hover the same way, at the same speed: it lifts a touch; in a cluster the
   others dim; a figure's caption (Pandoc makes one from alt text written
   alone on the image's line) rides up over the corner as a label, and is
   always shown on touch. One transition per element, so nothing fights. */
.site :is(.content, .content section) > figure:not(section):not(img) {
  width: fit-content; max-width: min(560px, 100% - var(--gutter)); position: relative;
}
.site .content figure img { margin: 0; max-width: 100%; }
.site .content :is(figure, p.frames) img { transition: transform .3s ease, filter .3s ease; }
.site .content figure:hover img, .site .content p.frames .frame:hover img { transform: scale(1.02); }
.site .content p.frames:hover .frame:not(:hover) img { filter: brightness(.55); }
.site figcaption {
  position: absolute; left: .7rem; bottom: .7rem; margin: 0;
  font-size: .68rem; text-transform: uppercase; text-align: left;
  color: var(--head); background: color-mix(in srgb, var(--bg) 85%, transparent);
  border-left: 2px solid var(--green); padding: .3rem .6rem;
  opacity: 0; translate: 0 4px; transition: opacity .3s ease, translate .3s ease;
}
.site figure:hover figcaption, .site figure:focus-within figcaption { opacity: 1; translate: 0 0; }
@media (hover: none) { .site figcaption { opacity: 1; translate: 0 0; } }

/* 14. NUMBERS THAT LINE UP: every digit the same width, so dates on
   neighbouring cards and columns in tables align; the mono zero slashed. */
.page-meta, .card-date, .page-spec, .site table { font-variant-numeric: tabular-nums slashed-zero; }

/* 15. THE UNDERLINE GROWS on hover, every prose link. */
.site a:not(.card) { transition: color .15s ease, text-decoration-thickness .15s ease; }   /* not a card: it has its own lift (24/09) */
.site a:hover { text-decoration-thickness: 2px; }

/* 16. COLLAPSIBLE SECTIONS. The build writes <details> from an Obsidian
   folded callout - "> [!note]- Title" folds, "> [!note]+ Title" starts open,
   the body stays markdown (the rule lives beside Callouts in the build). The
   height animates to auto, no script. */
.content details {
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
  margin: 1.4rem auto; interpolate-size: allow-keywords;
}
.content summary {
  cursor: pointer; padding: .7rem 1rem; list-style: none;
  font-family: var(--font-head); color: var(--head);
  display: flex; align-items: center; gap: .6rem;
}
.content summary::-webkit-details-marker { display: none; }
.content summary::after { content: "+"; margin-left: auto; color: var(--green); font-size: 1.2rem; transition: rotate .2s ease; }
.content details[open] > summary::after { rotate: 45deg; }
.content details::details-content {
  block-size: 0; overflow-y: clip; padding: 0 1rem;
  transition: block-size .3s ease, content-visibility .3s allow-discrete, padding .3s ease;
}
.content details[open]::details-content { block-size: auto; padding-bottom: .4rem; }

/* 17. THE TITLE ARRIVES. On first paint the h1 and the lines under it fade
   up 10px over half a second, then hold still: one motion, once, at the top. */
@media (prefers-reduced-motion: no-preference) {
  .site .content h1, .site .content .page-meta, .site .content .page-spec {
    transition: opacity .5s ease-out, translate .5s ease-out;
    @starting-style { opacity: 0; translate: 0 10px; }
  }
}

/* 18. THE SPEC STRIP. Role, stage, tags and features from the frontmatter,
   written by the build as a loading-screen readout under the title (the date
   stays on the meta line above, once). Cells stack on a phone. */
/* THE PANEL (26/09, his call: the strip ran into the body and its cells
   wandered). A box of its own, a shade lighter than the page with a green
   edge, so it reads as the project's card and not as writing. Inside, the
   short facts sit in an even grid (as many to a row as fit, two on a
   phone) and software, tags, where to play it and the features each take a
   full row. Lines between cells are drawn by each cell's own right and
   bottom edge; the panel clips the outer ones. */
.content .page-meta:has(+ .page-spec) { margin-bottom: 1rem; }
.page-spec {
  display: grid; grid-template-columns: repeat(var(--cols, 4), minmax(0, 1fr));   /* --cols from the build: facts share rows evenly */
  margin: 0 auto 2.6rem; padding: 0; overflow: hidden;
  background: var(--bg-alt); border: 1px solid var(--line); border-left: 3px solid var(--green);
  border-radius: var(--radius);
}
.page-spec > div {
  display: flex; flex-direction: column; gap: .3rem; min-width: 0;
  margin: 0; padding: .75rem 1rem;
  box-shadow: 1px 0 0 var(--line), 0 1px 0 var(--line);
}
.page-spec > div:is(.spec-software, .spec-tags, .spec-link, .spec-features) { grid-column: 1 / -1; }
.page-spec dt { font: inherit; font-size: .6rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin: 0; }
.page-spec dd { font-size: .82rem; color: var(--head); margin: 0; padding: 0; border: 0; letter-spacing: .02em; overflow-wrap: anywhere; }
.page-spec .spec-stage dd { text-transform: capitalize; }
.page-spec .spec-features dd { display: flex; flex-wrap: wrap; gap: .3rem 1.2rem; font-size: .78rem; color: var(--text); }
.page-spec .spec-features dd span::before { content: "\25B8 "; color: var(--green); }
@media (max-width: 640px) { .page-spec { grid-template-columns: 1fr 1fr; } }

/* 19. STAGE BADGE. The build writes data-stage on a card from the page's
   `stage`; a corner tag says it at a glance: green shipped, pink in
   progress, muted for the rest. */
.card[data-stage] { position: relative; }
.card[data-stage]::before {
  content: attr(data-stage); position: absolute; top: .6rem; left: .6rem; z-index: 1;
  font-size: .62rem; text-transform: uppercase;
  padding: .18rem .5rem; border-radius: 3px;
  background: color-mix(in srgb, var(--bg) 85%, transparent);
  color: var(--green); border: 1px solid currentColor;
}
.card[data-stage="archived"]::before, .card[data-stage="concept"]::before { color: var(--muted); }
.card[data-stage="wip"]::before, .card[data-stage="active"]::before { color: var(--pink-text); }

/* the motion above honours the reader's setting */
@media (prefers-reduced-motion: reduce) {
  .content details::details-content, .site figcaption, .site a, .site .content :is(figure, p.frames) img { transition: none; }
}
@media print { .page-spec { border-color: #999; } }


/* ===========================================================================
   THE READING PASS, 24/09. From the review of that date (the numbers are its
   list). Website only, like the block above: scoped to body.site. Nothing
   here moves on its own.
   ======================================================================== */

.site .related-label { font-family: var(--font-mono); letter-spacing: .1em; }

/* 27. THE TEXT GROWS A LITTLE ON A BIG SCREEN. 17px up to a 1400px window,
   rising to 19px at 2560. The measure is in rem, so it widens with it. */
body.site { font-size: clamp(1.0625rem, 0.9116rem + 0.1724vw, 1.1875rem); }

/* 26. SOFTER UNDERLINES. A prose link's line is drawn at 40% of its colour
   and comes up to full on hover, so a paragraph dense with links stays calm. */
.site a:not(.card) { text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
          transition: color .15s ease, text-decoration-thickness .15s ease, text-decoration-color .15s ease; }
.site a:hover { text-decoration-color: currentColor; }

/* 25. LEAVING THE SITE. A small muted arrow after any link to another site,
   so a reader knows before they click. Buttons, cards and images excluded. */
.site .content a[href^="http"]:not([href*="arkodegames.com"]):not(.card):not(.img-full):not(.tag):not(.tag-ext):not(p.button-row a)::after {
  content: "\2197"; display: inline-block; margin-left: .15em;
  font-size: .75em; color: var(--muted); text-decoration: none;
}

/* 11. CODE IN COLOUR. Pandoc marks every token it recognises; these are the
   palette's own colours for them. Keywords pink, types green, literals a
   green-white, comments muted, anything wrong rose. */
.site .sourceCode :is(.kw, .cf) { color: var(--pink-text); }
.site .sourceCode :is(.dt, .bu) { color: var(--green); }
.site .sourceCode .fu { color: var(--head); }
.site .sourceCode :is(.st, .ch, .vs, .ss, .sc, .dv, .bn, .fl, .cn) { color: color-mix(in srgb, var(--green) 55%, var(--head)); }
.site .sourceCode :is(.co, .do, .cv, .an, .in) { color: var(--muted); font-style: italic; }
.site .sourceCode :is(.pp, .im, .at) { color: color-mix(in srgb, var(--pink-text) 60%, var(--muted)); }
.site .sourceCode :is(.al, .er, .wa) { color: var(--rose-text); }
.site .sourceCode a { text-decoration: none; }

/* 15. THE STANDFIRST: the page's description under its title, as a lead.
   The build only writes it where it doesn't repeat the opening line. */
.site .content .page-meta:has(+ .standfirst) { margin-bottom: 1.1rem; }
.site .content .standfirst {
  font-size: 1.2em; line-height: 1.55; margin-bottom: 2rem;
  color: color-mix(in srgb, var(--text) 70%, var(--muted));
}
@media (max-width: 640px) { .site .content .standfirst { font-size: 1.08em; } }
.site .content .page-meta a { color: inherit; }

/* 12. CONTENTS ON A PHONE. The same list as the margin's, folded under the
   title, for every window too narrow for the margin one. */
.site .content details.toc-mobile { margin: 0 auto 2rem; font-size: .9rem; scroll-target-group: auto; }
.site .toc-mobile ul { list-style: none; margin: 0; padding: 0 0 .5rem; }
.site .toc-mobile ul ul ul { padding: 0 0 0 1rem; }
.site .toc-mobile > ul > li > a { display: none; }       /* the page title */
.site .toc-mobile li { margin: .15rem 0; }
.site .toc-mobile a { color: var(--muted); text-decoration: none; }
.site .toc-mobile a:hover, .site .toc-mobile a:target-current { color: var(--head); }
@media (min-width: 1180px) { .site .content details.toc-mobile { display: none; } }

/* 14. HEADING LINKS. Each h2 and h3's words link to their own section (the
   build wraps them). They read as the heading, not a link; a # after them
   shows on hover or keyboard focus, and on a touch screen sits faintly all
   the time. The # has empty alt text, so a screen reader never says it (25/09:
   it used to be a real # inside the heading, read out as part of it). */
.site .content a.hlink { color: inherit; text-decoration: none; }
.site .content a.hlink::after {
  content: "#" / ""; margin-left: .45em; font-weight: 400; color: var(--muted);
  opacity: 0; transition: opacity .15s ease;
}
.site .content a.hlink:hover::after, .site .content a.hlink:focus-visible::after { opacity: 1; }
@media (hover: none) { .site .content a.hlink::after { opacity: .45; } }

/* 20. CLICK FOR THE FULL SIZE: a lone image links to its largest file. */
.site .content a.img-full img { cursor: zoom-in; }

/* 28. RELATED READING, after previous and next: the same card rows the
   article lists use, under a small readout label. */
.site .content .related { margin-top: 2.6rem; }
.site .related-label { margin: 0 0 .6rem; font-size: .72rem; text-transform: uppercase; color: var(--muted); }
.site .content .related .cards { width: auto; margin: 0; }

/* 13. SIDENOTES. A footnote sits in a small set-back aside straight after
   its paragraph, which is how it reads on a phone. With room in the right
   margin (the contents list has the left), it lifts out beside the line that
   cites it: the build names each reference as an anchor and points its aside
   at it. Without anchor positioning it simply stays inline. */
.site .content aside.footnotes-end-of-block {
  margin: -.6rem auto 1.4rem; padding-left: .8rem;
  border-left: 2px solid var(--line); color: var(--muted); font-size: .85rem; line-height: 1.5;
}
.site .footnotes-end-of-block ol { margin: 0; padding-left: 1.2rem; }
.site .footnotes-end-of-block li { margin: 0 0 .4rem; }
.site .footnotes-end-of-block p { margin: 0; }
@media (min-width: 1180px) {
  @supports (position-anchor: --a) {
    .site .content section:has(> aside.footnotes-end-of-block) { position: relative; }
    .site .content aside.footnotes-end-of-block {
      position: absolute; top: anchor(top); margin: 0;
      left: calc(50% + var(--measure) / 2 + 2rem);
      width: min(14rem, 50% - var(--measure) / 2 - 3rem);
    }
  }
}

/* 21. THE PRINT VERSION. The meta line's "Print version" opens the same page
   at #print, and while that is the target the page redraws itself for paper:
   light, no chrome, filmstrips laid out flat, every link's address written
   after it. "Back to the web version" leaves. No copy of the page, no script;
   the web version is untouched. */
.site .print-bar { display: none; }
.site .print-bar:target {
  display: flex; flex-wrap: wrap; gap: .4rem 1.2rem; justify-content: space-between; align-items: center;
  margin: 0 auto 1.6rem; padding: .7rem 1rem; box-shadow: none;
  border: 1px solid var(--line); border-radius: var(--radius); font-size: .85rem;
}
body.site:has(#print:target) {
  --bg: #fff; --band: #fff; --bg-alt: #f3f3f3; --panel: #f3f3f3; --line: #c9c9c9;
  --text: #1a1a1a; --head: #000; --muted: #555;
  --pink: #8a1f5f; --pink-text: #8a1f5f; --pink-hover: #5e1541; --green: #1f6f47; --rose-text: #8a1f3f;
  color-scheme: light;
}
html:has(#print:target) { color-scheme: light; }
html:has(#print:target)::scroll-marker-group,
body.site:has(#print:target) :is(.site-header, .crumbs, .page-toc, .toc-mobile, .related, .to-top, .site-footer, .page-options) { display: none; }
body.site:has(#print:target) .content a.hlink::after { content: none; }
body.site:has(#print:target) .content,
body.site:has(#print:target) .content section:has(> h2):nth-of-type(odd) { background: none; }
body.site:has(#print:target) .content section:has(> h2) { padding: 1.2rem 0; }
body.site:has(#print:target) * { animation: none !important; }
body.site:has(#print:target) figcaption { opacity: 1; translate: none; }
body.site:has(#print:target) :is(figure, p:has(img), .table-scroll, pre) { break-inside: avoid; }
body.site:has(#print:target) .content a[href^="http"]:not(.card):not(.img-full)::after {
  content: " (" attr(href) ")"; display: inline; margin: 0;
  font-size: .8em; color: var(--muted); overflow-wrap: anywhere;
}

/* and when a page is printed straight from the web version: nothing that
   only makes sense on a screen, nothing left mid-animation (a scroll-driven
   fade below the fold would otherwise print at nothing), filmstrips flat */
@media print {
  .site * { animation: none !important; }
  .print-bar, .toc-mobile, .related, .page-options, .to-top, .more-pics-label { display: none !important; }
  .site .content a.hlink::after { content: none; }
  .site figcaption { opacity: 1; translate: none; }
}

@media (prefers-reduced-motion: reduce) {
  .site .content a.hlink::after { transition: none; }
}
/* ===========================================================================
   THE THIRD PASS, 24/09 evening: his answers on the fifty.
   ======================================================================== */

/* 18. HYPHENATION. Prose breaks long words at the line end (the page is
   en-GB, so British rules), which keeps the right edge of a paragraph from
   going ragged. Headings, labels and code never hyphenate. */
.site .content :is(p:not([class]), p.with-text, p.opening, li, dd, blockquote p) {
  hyphens: auto; -webkit-hyphens: auto; hyphenate-limit-chars: 7 3 3;
}

/* 24. THE OPENING LINE. The first line of a long page's first paragraph in
   small caps, the quiet signal that the reading has started. */
.site .content p.opening::first-line { font-variant-caps: all-small-caps; letter-spacing: .05em; color: var(--head); }

/* 22. PULL QUOTE: a markdown quote marked [!quote]. Lifted out large between
   two green rules, no box; a written title is the attribution under it. */
.site .content .pull blockquote {
  margin: 2.2rem 0; padding: 1rem 0; background: none; border: 0;
  border-top: 1px solid var(--green); border-bottom: 1px solid var(--green);
  font-family: var(--font-head); font-style: normal; font-size: 1.35rem; line-height: 1.4;
  color: var(--head); text-align: center; text-wrap: balance;
}
.site .content .pull blockquote p { hyphens: manual; }
.site .content .pull cite {
  display: block; margin-top: .6rem; font-family: var(--font-mono); font-style: normal;
  font-size: .72rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
}

/* SHELVES: the {{featured}} and {{recent}} rows, headed like Related. */
.site .content .shelf { margin: 1.4rem auto 2.4rem; }
.site .shelf-label { margin: 0 0 .6rem; font: .72rem var(--font-mono); letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.site .content .shelf .cards { width: auto; margin: 0; }


/* 48 and 49. LIGHT and HIGH CONTRAST beside the print version: two page-local
   switches (checkboxes the CSS reads), plus the reader's own system setting
   for contrast, which applies everywhere without a click. Since 25/09 the
   three sit in a row of their own under the dates (.page-options), as small
   outlined buttons; a switch that is on is lit green like a ticked filter. */
.site .content .page-options {
  display: flex; flex-wrap: wrap; gap: .35rem;
  margin: -1.6rem auto 2rem;
}
.site .page-options :is(.print-link, .meta-toggle) {
  display: inline-grid; place-items: center; min-height: 1.7rem; padding: 0 .65rem; box-sizing: border-box;
  border: 1px solid var(--line); border-radius: var(--radius); background: none;
  font: .68rem var(--font-mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); text-decoration: none; cursor: pointer; user-select: none;
  transition: color .2s ease, border-color .2s ease;
}
.site .page-options :is(.print-link, .meta-toggle):hover { color: var(--pink-text); border-color: var(--pink); }
.site .page-options .pick:checked + .meta-toggle { color: var(--head); border-color: var(--green); }
.site .page-options .pick:checked + .meta-toggle::before { content: "\2713" / ""; color: var(--green); margin-right: .4em; }
.site .page-options .pick:focus-visible + .meta-toggle { outline: 2px solid var(--yellow); outline-offset: 2px; }

body.site:has(#theme-light:checked) {
  --bg: #fbfbfc; --band: #f1f0f4; --bg-alt: #e9e7ee; --panel: #efedf3; --line: #cfcbd8;
  --text: #25222b; --head: #0b0a0d; --muted: #5d5868;
  --pink: #a0306f; --pink-text: #92245f; --pink-hover: #6b1a45; --green: #1f7a4e; --rose-text: #8a1f3f;
  color-scheme: light;
}
html:has(#theme-light:checked) { color-scheme: light; }
body.site:has(#theme-light:checked) .content { background-image: radial-gradient(rgba(0,0,0,.13) 1px, transparent 1.5px); }
body.site:has(#theme-light:checked) .content section:has(> h2):nth-of-type(odd) {
  background-image: linear-gradient(rgba(0,0,0,.07) 1px, transparent 1px), linear-gradient(90deg, rgba(0,0,0,.07) 1px, transparent 1px);
}
/* high contrast on the dark theme: brighter text, stronger lines */
body.site:has(#theme-contrast:checked):not(:has(#theme-light:checked, #print:target)) {
  --text: #f4f2f7; --head: #fff; --muted: #d4d0db; --line: #7a7388;
  --pink-text: #f7bddc; --band: #1e1d23; --bg-alt: #1e1d23; --panel: #26242c;
}
/* ... and on the light one */
body.site:has(#theme-contrast:checked):has(#theme-light:checked) {
  --text: #000; --head: #000; --muted: #2e2b33; --line: #6d6778; --pink-text: #74134a; --bg: #fff;
}
@media (prefers-contrast: more) {
  body.site:not(:has(#theme-light:checked, #print:target)) {
    --text: #f4f2f7; --head: #fff; --muted: #d4d0db; --line: #7a7388;
    --pink-text: #f7bddc; --band: #1e1d23; --bg-alt: #1e1d23; --panel: #26242c;
  }
}
@media print { .site .page-options { display: none; } }
/* ===========================================================================
   TAGS, 25/09 (his call: the 24/09 buttons, tiles and card chips read as
   three accidents). ONE chip everywhere, one rule for what it does:
   - over cards it FILTERS (a checkbox label in the page's filter bar; tick
     several, a card stays if it carries any; Show all appears once one is on)
   - anywhere else it OPENS the tag's page; software in a spec strip adds a
     small arrow to the tool's website (.tag-pair > .tag-ext)
   - on a card it only labels (a card is already a link)
   Anatomy: [icon] Name [count]. The icon is software only (a CSS mask, url
   inline per tool, or a monogram); the count is its own box.
   ======================================================================== */
.site .tag {
  display: inline-flex; align-items: center; gap: .45em; vertical-align: middle;
  min-height: 1.95rem; box-sizing: border-box; padding: 0 .75rem;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
  color: var(--head); font-family: var(--font-head); font-size: .78rem; font-weight: 400;
  letter-spacing: .02em; line-height: 1.2; white-space: nowrap; text-decoration: none;
  transition: color .2s ease, border-color .2s ease, background .2s ease;
}
.site .tag .ico {
  display: inline-grid; place-items: center; flex: none;
  width: 1.05em; height: 1.05em; background: currentColor; font-style: normal;
  -webkit-mask: var(--icon) center / contain no-repeat; mask: var(--icon) center / contain no-repeat;
}
.site .tag.mono .ico {
  background: none; -webkit-mask: none; mask: none;
  width: auto; min-width: 1.05em; font: 700 .7em/1 var(--font-mono); letter-spacing: 0;
}
/* the count: its own box at the chip's end, so it never reads as the name */
.site .tag-n {
  align-self: stretch; display: grid; place-items: center;
  margin: 0 -.75rem 0 .15rem; padding: 0 .55rem; min-width: 1.1rem;
  border-left: 1px solid var(--line); background: var(--bg-alt);
  border-radius: 0 calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0;
  font: .68rem var(--font-mono); color: var(--muted); font-variant-numeric: tabular-nums;
}
/* a chip that goes somewhere, and a filter chip: one hover */
.site a.tag:hover, .site a.tag:focus-visible, .site label.tag:hover { color: var(--pink-text); border-color: var(--pink); background: var(--bg-alt); }
.site label.tag { cursor: pointer; user-select: none; }
/* ticked: green edge and a tick, so a multi-select reads as one */
.site .pick:checked + label.tag { color: var(--head); border-color: var(--green); background: var(--bg-alt); }
.site .pick:checked + label.tag::before { content: "\2713" / ""; color: var(--green); font-size: .8em; margin-right: -.1em; }
.site .pick:focus-visible + label.tag { outline: 2px solid var(--yellow); outline-offset: 2px; }
/* software's website, fixed to the chip's right edge */
.site .tag-pair { display: inline-flex; vertical-align: middle; }
.site .tag-pair > .tag { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.site .tag-ext {
  display: grid; place-items: center; min-width: 1.95rem; padding: 0 .35rem; box-sizing: border-box;
  border: 1px solid var(--line); border-left: 0; border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--panel); color: var(--muted); font: .78rem var(--font-mono); text-decoration: none;
  transition: color .2s ease, background .2s ease;
}
.site .tag-ext:hover, .site .tag-ext:focus-visible { color: var(--pink-text); background: var(--bg-alt); }

/* on a card: the same chip, smaller and quiet */
.site .card-tags { display: flex; flex-wrap: wrap; gap: .3rem; }
.site .card-tags .tag { min-height: 1.45rem; padding: 0 .5rem; font-size: .68rem; color: var(--muted); background: var(--bg); gap: .35em; }

/* in the spec strip, and a row of chips (the /tags/ page) */
.site .page-spec dd:has(.tag) { display: flex; flex-wrap: wrap; gap: .35rem; }
.site .content .tag-row, .site .content .filters {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
  width: min(var(--measure), 100% - var(--gutter)); margin: 1.2rem auto 1.4rem;
}
.site .filters-label {
  flex-basis: 100%; margin: .6rem 0 .1rem;
  font: .68rem var(--font-mono); letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
}
.site .filters-label:first-child { margin-top: 0; }
.site .filters-reset, .site .filters-more {
  display: inline-grid; place-items: center; min-height: 1.95rem; padding: 0 .75rem; box-sizing: border-box;
  border: 1px dashed var(--line); border-radius: var(--radius); background: none;
  font: .68rem var(--font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
  cursor: pointer; text-decoration: none;
}
.site .filters-more { border-color: transparent; }
.site :is(.filters-reset, .filters-more):is(:hover, :focus-visible) { color: var(--pink-text); border-color: var(--pink); }
/* Show all only once something is ticked */
.site .filters:not(:has(.pick:checked)) .filters-reset { display: none; }

/* The filter bar and its grid sit in .filtered, the grid's own wrapper;
   it is transparent to layout. */
.site .content .filtered { width: auto; }
.site .content .filtered > .cards { width: min(var(--measure), 100% - var(--gutter)); margin-left: auto; margin-right: auto; }

/* THE FILTER. Nothing ticked: every card. Something ticked: a card stays if
   it carries any ticked option (the build gives it f<k> for option k).
   Positional and fixed, 24 options; the Featured and Related rows are never
   filtered. No CSS is generated. */
.filtered:has(> .filters > .pick:checked) > .cards > .card:not(:is(
  .filtered:has(> .filters > .pick:nth-of-type(1):checked) .f1,   .filtered:has(> .filters > .pick:nth-of-type(2):checked) .f2,
  .filtered:has(> .filters > .pick:nth-of-type(3):checked) .f3,   .filtered:has(> .filters > .pick:nth-of-type(4):checked) .f4,
  .filtered:has(> .filters > .pick:nth-of-type(5):checked) .f5,   .filtered:has(> .filters > .pick:nth-of-type(6):checked) .f6,
  .filtered:has(> .filters > .pick:nth-of-type(7):checked) .f7,   .filtered:has(> .filters > .pick:nth-of-type(8):checked) .f8,
  .filtered:has(> .filters > .pick:nth-of-type(9):checked) .f9,   .filtered:has(> .filters > .pick:nth-of-type(10):checked) .f10,
  .filtered:has(> .filters > .pick:nth-of-type(11):checked) .f11, .filtered:has(> .filters > .pick:nth-of-type(12):checked) .f12,
  .filtered:has(> .filters > .pick:nth-of-type(13):checked) .f13, .filtered:has(> .filters > .pick:nth-of-type(14):checked) .f14,
  .filtered:has(> .filters > .pick:nth-of-type(15):checked) .f15, .filtered:has(> .filters > .pick:nth-of-type(16):checked) .f16,
  .filtered:has(> .filters > .pick:nth-of-type(17):checked) .f17, .filtered:has(> .filters > .pick:nth-of-type(18):checked) .f18,
  .filtered:has(> .filters > .pick:nth-of-type(19):checked) .f19, .filtered:has(> .filters > .pick:nth-of-type(20):checked) .f20,
  .filtered:has(> .filters > .pick:nth-of-type(21):checked) .f21, .filtered:has(> .filters > .pick:nth-of-type(22):checked) .f22,
  .filtered:has(> .filters > .pick:nth-of-type(23):checked) .f23, .filtered:has(> .filters > .pick:nth-of-type(24):checked) .f24
)) { display: none; }
@media (prefers-reduced-motion: reduce) { .site :is(.tag, .tag-ext) { transition: none; } }

/* 34. STAGE LABELS. A row or strip of images where alt text was written:
   each frame wears its alt as a readout. Laid out like any image cluster -
   pairs, full-width odd last - and a strip from three frames up. */
.site .content p.frames {
  display: grid; grid-template-columns: 1fr 1fr; gap: .6rem;
  width: min(52rem, 100% - var(--gutter)); margin: 1.5rem auto 2.25rem;
}
.site .content p.frames .frame { position: relative; display: block; min-width: 0; }
.site .content p.frames img { width: 100%; height: auto; max-width: none; aspect-ratio: 16 / 10; object-fit: contain; background-color: #000 !important; margin: 0; }
/* (26/09, his call: black bars, never a crop. The !important beats the
   placeholder colour the build writes inline, so the bars are black.) */
.site .content p.frames .frame > a.img-full { display: block; }
.site .content p.frames .frame:last-child:nth-child(odd) { grid-column: 1 / -1; }
.site .content p.frames .frame:last-child:nth-child(odd) img { aspect-ratio: 16 / 9; }
.site .content p.frames .frame[data-label]::after {
  content: attr(data-label); position: absolute; left: .6rem; bottom: .6rem;
  font: .68rem var(--font-mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--head); background: color-mix(in srgb, var(--bg) 85%, transparent);
  border-left: 2px solid var(--green); padding: .25rem .55rem;
}
@media (max-width: 52em) {
  .site .content p.frames { grid-template-columns: 1fr; }
  .site .content p.frames .frame:last-child:nth-child(odd) img { aspect-ratio: 16 / 10; }
}
@supports (scroll-marker-group: after) {
  .site .content p.frames:has(.frame + .frame + .frame) {
    display: flex; gap: .6rem; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none;
    scroll-marker-group: after; anchor-name: --strip; anchor-scope: --strip; margin-bottom: 0;
  }
  .site .content p.frames:has(.frame + .frame + .frame) .frame { flex: 0 0 100%; scroll-snap-align: center; }
  .site .content p.frames:has(.frame + .frame + .frame) img,
  .site .content p.frames:has(.frame + .frame + .frame) .frame:last-child:nth-child(odd) img { aspect-ratio: 16 / 9; }
  .site .content p.frames:has(.frame + .frame + .frame)::scroll-marker-group { display: flex; justify-content: center; gap: .45rem; padding: .8rem 0 2.25rem; }
  .site .content p.frames:has(.frame + .frame + .frame) .frame::scroll-marker {
    content: ""; width: 9px; height: 9px; border-radius: 50%; background: var(--line); border: 1px solid var(--muted);
  }
  .site .content p.frames:has(.frame + .frame + .frame) .frame::scroll-marker:target-current { background: var(--pink); border-color: var(--pink); }
  .site .content p.frames:has(.frame + .frame + .frame)::scroll-button(*) {
    position: absolute; position-anchor: --strip; top: anchor(center); translate: 0 -50%;
    width: 2.4rem; height: 2.4rem; border-radius: 50%; border: 1px solid var(--line);
    background: color-mix(in srgb, var(--bg) 80%, transparent); color: var(--head);
    font-size: 1.5rem; line-height: 1; cursor: pointer; z-index: 2;
  }
  .site .content p.frames:has(.frame + .frame + .frame)::scroll-button(left)  { content: "\2039" / "Previous image"; left: anchor(left); margin-left: .5rem; }
  .site .content p.frames:has(.frame + .frame + .frame)::scroll-button(right) { content: "\203A" / "Next image"; right: anchor(right); margin-right: .5rem; }
  .site .content p.frames:has(.frame + .frame + .frame)::scroll-button(*):disabled { opacity: .25; cursor: default; }
}
body.site:has(#print:target) .content p.frames:has(.frame + .frame + .frame) { display: grid; grid-template-columns: 1fr 1fr; overflow: visible; }
@media print { .site .content p.frames { display: grid; grid-template-columns: 1fr 1fr; overflow: visible; } }

/* 37. A CLIP: the video and, under it, a readout of its name and length. */
.site :is(.content, .content section) > figure.clip { width: min(var(--measure), 100% - var(--gutter)); max-width: none; margin: 1.5rem auto 2rem; }
.site .content figure.clip video { margin: 0; width: 100%; height: auto; }   /* height auto: the width and height attributes set the shape, not the size (25/09: a 720px-tall box round a 704px-wide video) */
.site figure.clip figcaption.clip-label {
  position: static; opacity: 1; translate: none; display: inline-block;
  margin-top: .55rem; background: none; color: var(--muted); padding: .1rem .55rem;
}

/* 44. SKIP LINK: out of sight until a keyboard reaches it, then top left. */
.site .skip-link {
  position: absolute; left: 1rem; top: -4rem; z-index: 100;
  background: var(--yellow); color: #17161a; padding: .5rem 1rem; border-radius: var(--radius);
  font-family: var(--font-head); font-size: .85rem; text-decoration: none;
}
.site .skip-link:focus { top: 1rem; }

/* ===========================================================================
   THE 25/09 PASS. His answers to the hundred, numbered as that list numbers
   them. Website only (body.site), like the two passes above.
   ======================================================================== */

/* 11. THE TAGLINE: a page's `tagline`, large, straight under its title. */
.site .content .tagline {
  margin-top: -1.1rem; margin-bottom: 1.6rem;
  font-family: var(--font-head); font-size: clamp(1.25rem, 1.05rem + .9vw, 1.6rem); line-height: 1.35;
  color: var(--head); text-wrap: balance;
}
.site .content .tagline + .page-meta { margin-top: 0; }

/* 13. THE STAT BAND: a page's `stats`, one readout cell each, number large. */
.site .content .stat-band {
  display: flex; flex-wrap: wrap; list-style: none; padding: 0;
  margin: -.6rem auto 2.2rem; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.site .content .stat-band li {
  flex: 1 1 9rem; display: flex; flex-direction: column; gap: .15rem;
  margin: 0; padding: .75rem 1rem .75rem 0; border-right: 1px solid var(--line);
}
.site .content .stat-band li + li { padding-left: 1rem; }
.site .content .stat-band li:last-child { border-right: 0; }
.site .content .stat-band b { font: 700 1.7rem/1.1 var(--font-head); color: var(--green); font-variant-numeric: tabular-nums; }
.site .content .stat-band span { font-size: .72rem; line-height: 1.35; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; }
@media (max-width: 640px) {
  .site .content .stat-band li { flex-basis: 100%; border-right: 0; padding-left: 0; border-bottom: 1px solid var(--line); flex-direction: row; align-items: baseline; gap: .7rem; }
  .site .content .stat-band li + li { padding-left: 0; }
  .site .content .stat-band li:last-child { border-bottom: 0; }
}

/* 22. WHEN AND ON WHAT, under a card's role: `year` and `platform`. */
.site .card-made { display: block; margin-top: .15rem; font: .68rem var(--font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }

/* 30. A SECOND PICTURE, `thumb2`, that a card fades to on hover or focus. */
.site .card-img { position: relative; }
.site .card-img .card-img2 { position: absolute; inset: 0; opacity: 0; transition: opacity .35s ease, transform .5s ease; }
.site .card:is(:hover, :focus-visible) .card-img .card-img2 { opacity: 1; }

/* 38. A LONG CLUSTER on a phone, where the browser can't make it a
   filmstrip: the first four pictures, and a switch for the rest. */
.site .more-pics-label { display: none; }
@supports not (scroll-marker-group: after) {
  @media (max-width: 52em) {
    .site .content p:has(+ .more-pics:not(:checked)) > :nth-child(n+5) { display: none; }
    .site .content .more-pics-label {
      display: block; width: min(var(--measure), 100% - var(--gutter)); margin: -1rem auto 2rem; box-sizing: border-box;
      padding: .55rem .9rem; text-align: center; cursor: pointer;
      border: 1px dashed var(--line); border-radius: var(--radius);
      font: .7rem var(--font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
    }
    .site .content .more-pics:checked + .more-pics-label { display: none; }
    .site .content .more-pics:focus-visible + .more-pics-label { outline: 2px solid var(--yellow); outline-offset: 2px; }
  }
}

/* 39. ON A PHONE, a section's title stays pinned under the nav (and the
   breadcrumb, where there is one) while its section scrolls past. It takes
   its section's own background, banded or not. */
@media (max-width: 640px) {
  /* the title keeps its column; its background runs to both window edges
     through a shadow clipped to its own height */
  .site .content section.level2 > h2 {
    --h2-bg: var(--bg);
    position: sticky; top: var(--nav-h); z-index: 6;
    padding: .5rem 0; background: var(--h2-bg); font-size: 1.2rem;
    box-shadow: 0 0 0 100vmax var(--h2-bg), 0 1px 0 100vmax var(--line); clip-path: inset(0 -100vmax -1px);
  }
  .site .content section.level2:nth-of-type(odd) > h2 { --h2-bg: var(--band); }
  body.site:has(.crumbs) .content section.level2 > h2 { top: calc(var(--nav-h) + 2.15rem); }
}

/* 41. THE LIGHTBOX. Hidden until its #lb-N is the target; then the picture
   fills the window over a dark backdrop that closes it when clicked, with
   previous, next, the full-size file and Close in a bar under it. */
.site .lightbox { display: none; }
.site .lightbox:target {
  display: grid; grid-template-rows: 1fr auto; place-items: center; gap: .6rem;
  position: fixed; inset: 0; z-index: 200; padding: 1.2rem;
  box-shadow: none; background: rgba(12, 11, 14, .94);
}
.site .lb-backdrop { position: absolute; inset: 0; z-index: 0; }
.site .lightbox img {
  position: relative; z-index: 1; margin: 0; max-width: 100%; max-height: calc(100vh - 5.5rem);
  width: auto; height: auto; object-fit: contain; border-radius: var(--radius); box-shadow: 0 10px 40px rgba(0,0,0,.6);
}
.site .lb-bar {
  position: relative; z-index: 1; display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: .4rem;
  margin: 0; font: .7rem var(--font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
}
.site .lb-bar span { padding: 0 .6rem; }
.site .lb-bar a {
  display: inline-grid; place-items: center; min-height: 1.95rem; padding: 0 .75rem;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
  color: var(--head); text-decoration: none;
}
.site .lb-bar a:hover, .site .lb-bar a:focus-visible { color: var(--pink-text); border-color: var(--pink); }
.site .lb-bar .lb-close { border-color: var(--green); }
/* the page behind doesn't scroll while one is open */
html:has(.lightbox:target) { overflow: hidden; }
/* an image the lightbox returns to isn't tinted as a jump target */
.site a.img-full:target { box-shadow: none; }
.site a.img-full { cursor: zoom-in; }
@media print { .site .lightbox { display: none !important; } }

/* 56. LINE NUMBERS on a long code block (Pandoc's numberLines): each line's
   first, empty anchor carries its number, which can't be selected, so a
   copy is still just the code. */
.site pre.numberSource code { counter-reset: ln; }
.site pre.numberSource code > span { counter-increment: ln; }
.site pre.numberSource code > span > a:first-child::before {
  content: counter(ln); display: inline-block; width: 2.4em; margin-right: 1.1em;
  text-align: right; color: var(--muted); opacity: .6; user-select: none;
}
.site pre.numberSource code > span > a:first-child { text-decoration: none; }

/* 59. BACK TO TOP at the foot of a long read, quiet like the way back up. */
.site .content p.to-top { width: min(var(--measure), 100% - var(--gutter)); margin: 2.4rem auto 0; font-size: .85rem; }
.site .content p.to-top a { color: var(--muted); text-decoration: none; }
.site .content p.to-top a:hover { color: var(--green); }

/* 70. THE FOOTER's third line: the pages the nav doesn't carry. */
.site-footer p:last-child a { font-size: .8rem; }

/* ===========================================================================
   26/09, the rest of the hundred he left to Robin's judgement.
   ======================================================================== */

/* 63. THE BREADCRUMB ENDS ON THIS PAGE, not a link, and quieter still. */
.crumb-here { color: var(--head); opacity: .8; }

/* 33. A METRICS TABLE: most cells numbers or measures, so the build marks it
   and it reads as a readout: mono figures, tabular, the header a label row. */
.site .content table.metrics td { font-family: var(--font-mono); font-size: .85rem; font-variant-numeric: tabular-nums slashed-zero; }
.site .content table.metrics th { font: .68rem var(--font-mono); letter-spacing: .1em; text-transform: uppercase; color: var(--muted); background: var(--bg-alt); }
.site .content table.metrics td:first-child { font-family: var(--font-body); font-size: .92rem; color: var(--head); }

/* 82. ON A TOUCH SCREEN every chip and small button is a full fingertip tall
   (44px, the usual guidance); a mouse keeps the compact size. */
@media (pointer: coarse) {
  .site :is(.tag, .tag-ext, .filters-reset, .filters-more, .page-options .print-link, .page-options .meta-toggle, .lb-bar a) { min-height: 44px; }
  .site .tag-ext { min-width: 44px; }
  .site .card-tags .tag { min-height: 1.45rem; }   /* labels on a card aren't targets */
}

/* 83. REDUCED MOTION: the filmstrip jumps between frames instead of gliding. */
@media (prefers-reduced-motion: reduce) {
  .site .content p.frames { scroll-behavior: auto; }
  html { scroll-behavior: auto; }
}

/* words for a screen reader only, such as "pages" after a tag's count */
.site .vh { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* 7 (26/09). THE PDF VIEWER, two views of one file: from 56rem a page wide
   enough for A4 at 100% (794px plus the viewer's own margin) and tall enough
   for the whole page; below that, the page fitted to the width. The link
   under it is for phones that won't show a PDF inside a page. */
.site .content .pdf-embed { width: min(52rem, 100% - var(--gutter)); }
.site .pdf-embed iframe.pdf-wide { aspect-ratio: 832 / 1210; }
.site .pdf-embed iframe.pdf-narrow { display: none; aspect-ratio: 1 / 1.45; }
@media (max-width: 56rem) {
  .site .pdf-embed iframe.pdf-wide { display: none; }
  .site .pdf-embed iframe.pdf-narrow { display: block; }
}
.site .pdf-embed .pdf-open { margin: .5rem 0 0; font: .68rem var(--font-mono); letter-spacing: .08em; text-transform: uppercase; }
.site .pdf-embed .pdf-open a { color: var(--muted); text-decoration: none; }
.site .pdf-embed .pdf-open a:hover { color: var(--pink-text); }

/* 6 and 11 (26/09). A YOUTUBE PLAYER, made by the build from a link on its
   own line: the measure's width, 16:9, like a clip. */
.site .content iframe.yt { width: min(var(--measure), 100% - var(--gutter)); margin: 1.5rem auto 2rem; }
