:root {
    --color-light: #eeddaa;
    /* The production surface and backdrop, named so the dev-only "live colours"
       toggle has something authoritative to restore from: development.css
       overrides --color-light and the backdrop, and these two are what it puts
       back. Production itself never reads them. */
    --color-light-live: #eeddaa;
    --page-backdrop: linear-gradient(lightblue, seagreen);
    --color-dark: #000000;
    --color-shadow: #ccb;
    --border-thin: 0.2rem;
    --color-tag: rgba(240, 150, 0, 0.5);
    /* The same orange at full strength, for the "carry on here" mark at the end
       of this file. --color-tag is half-transparent, which is right behind a
       word in running text and far too quiet for a line that has to be found
       from across a screenful. */
    --color-continue: rgb(240, 150, 0);
    --color-link: #4040e0;
    /* An embedded post -- one shown inside a listing rather than on its own
       page. Deliberately not a colour, just a lift of whatever is underneath. */
    --color-embedded: #ffffff47;
    /* The wash on a box inside a box -- see `.bordered .bordered` below. Named
       here because the stream entries and the open <details> want it too. */
    --color-inner-wash: #a099f1a6;
    /* How far down the viewport the block to carry on from lands when a
       "Continue reading" link is followed -- see the rules at the end of this
       file. Just under half a screen: the page really does jump, the mark
       arrives near the middle of the viewport where the reader is already
       looking, and the tail of what they read comes along above it.

       It was 100vh to begin with, which for any boundary less than a screenful
       into the post clamps to the top of the page and so never scrolls at all.
       That was the gentle choice and it was the wrong one: a reader who lands
       at the top of a post looks at the top of a post, and the mark 600-odd
       pixels below them is doing its work where nobody is looking. Landing
       them on it is the whole point. */
    --continue-offset: 40vh;
    /* How long the blocks above the mark take to step back once the page is
       there -- see the rules at the end of this file. A second: slow enough
       that it reads as something happening to the post rather than as how the
       post was drawn, and short enough that a reader who came here to read
       isn't waiting on it. It is a variable so that reduced motion can put it
       to 0s in one place. */
    --continue-fade: 1s;
    color: var(--color-dark);
    font-family: "Arial", sans-serif;
    font-size: var(--s0); /* TODO use something from modular scale */
}

html {
    /* Force visibility of scroll bar. This solves an irritating problem */
    /* where the positioning of content boxes and the background pattern */
    /* seems to "jump" somewhat when moving between a page too short to */
    /* have a scroll bar, and one long enough to have one. */
    overflow-y: scroll;
}

.bordered {
    border-color: var(--color-dark);
    border-width: var(--border-thin);
    border-style: solid;
    /* Three depths, and they say what contains what: the page's boxes sit above
       the background, an embedded post (.post-entry) sits above the box that
       lists it, and a box inside a box -- a comment, the note on an archived
       post, an open <details> -- stays flat, because the wash it already
       carries is what sets it apart. Take these two rules out and the layout is
       exactly as it was; nothing depends on them. */
    box-shadow: 0 2px 6px #0000002e;
}

.bordered .bordered {
    box-shadow: none;
}


.not-padded {
    padding: 0rem;
}

div.e-content > aside {
  margin-top: var(--s-5);
}


.box *:not(a):not(.sithlord-name):not(.hayleybloo) {
    /* "By forcing inheritance, you can change the color—along with the background-color—in one place: on the Box itself" */
    /*  https://every-layout.dev/layouts/box/#use-cases */
    /* Also, we exclude links from the inheritance, rather than using "box *", so that link colors override the default text color. */
    color: inherit;
}

.box *.normal-color {
    /* for <a> tags, usually u-* microformats, where I want to de-emphasize that they are a link and restore normal coloration of the text */
    color: var(--color-dark);
}

.box .invert {
    /* Dark becomes light; light becomes dark */
    color: var(--color-light);
    background-color: var(--color-dark);
    border-color: var(--color-light);
}

a {
    text-decoration: none; /* no underlines on links */
    color: var(--color-link);
}

a:visited {
    color: #9000c0;
}

code {
    background-color: #d9cec2;
}

figcaption {
    /* same as .bordered */
    /* border-color: var(--color-dark); */
    /* border-width: var(--border-thin); */
    /* border-style: solid; */
    /* same as .muted */
    font-size: var(--s-1);
    font-style: italic;
}

figure {
    /* same as .center */
    box-sizing: content-box;
    margin-inline: auto;
    text-align: center;
    max-inline-size: var(--measure);
    display:flex;
    flex-direction: column;
    align-items: center;
    /* same as .stack */
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    margin-block: 0;
    margin-block-start: var(--space, --ratio);
}



blockquote {
    background-color: #eee;
    margin-block: 0;
    margin-block-start: var(--space, --ratio);

    /* /\* /\\* if the fit-content attributes aren't supported *\\/ *\/ */
    /* width: auto; */
    /* /\* /\\*several names for fit-content *\\/ *\/ */
    /* width: -moz-fit-content; */
    /* width: -webkit-fit-content; */
    /* width: -webkit-fill-available; */
    /* width: fit-content; */
}

/* blockquote p { */
/*     padding: 4px; */
/*     /\* prevent first paragraph of blockquote from being indented downward overmuch *\/ */
/*     margin: 0px 4px 1% 4px; */
/* } */


cite {
    font-style: italic;
}

/* cite::before { */
/*     content: "(source: "; */
/* } */


/* cite::after { */
/*     content: ")"; */
/* } */


/* blockquote pre { */
/*     padding: 4px; */
/*     margin: 0px; */
/* } */

.hayleybloo {
    color: cornflowerblue;
}

#logo {
    font-size: var(--s3);
}


pre {
    display: block;
    font-family: inherit;
    white-space: pre-wrap;
    word-wrap: break-word;
}

.poem {
    padding: 4px;
    margin: 4px;
}

.poem-container {
    /* /\* if the fit-content attributes aren't supported *\/ */
    width: auto;
    /* /\*several names for fit-content *\/ */
    width: -moz-fit-content;
    width: -webkit-fit-content;
    width: -webkit-fill-available;
    width: fit-content;

    background-color: #fbd383;
    border: solid 1px var(--color-shadow);
}

mark {
    background-color: #c7e1eb;
}

.wip, .warn {
    color: #d72638;
}

code {
    background-color: #d9cec2;
}

/* classical chinese in vertical style */
.ccvert {
    writing-mode: vertical-rl;
    text-orientation: upright;
}


.callout {
    border-radius: 0.3em;
    border-top: darkgreen .1em solid;
}

.callout:hover {
    background-color: #22884422;
}

.explain{background-color:#ffb66f;}

s {
    text-decoration: line-through;
    text-decoration-thickness: 2px;
}

.sic {
    font-style: italic;
}

.status.symbol::after {
    content: "";
}

.new-v1::after {
    content: "new";
    font-size: calc(var(--s-3));
    background: #AA0044;
    color: white;
    box-shadow: 0 0 1px black;
    margin: .33em;
    padding: .1em;
}

.new::after {
    content: "!";
    font-size: calc(var(--s-2));
    box-shadow: 0 0 1px black;
    margin: .33em;
    padding: .1em;
    color: black;
    border-color: black;
    writing-mode: vertical-rl;
    text-orientation: upright;
    display: inline-block;
    --egg-width: calc(var(--s-) * 1.3);
    --egg-height: calc(1.42 * var(--egg-width));
    width: var(--egg-width);
    height: var(--egg-height);
    background-color: #f2c387;
    border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
}

.smallcaps {
    font-style: smallcaps;
}

.internal-1 {
    background-color: #aaE7F2;
}

.internal-2 {
    background-color: #bbF5FF;
}

.internal-3 {
    background-color: #ccF7FF;
}

.internal-4 {
    background-color: #ddF9FF;
}

.bigclick {
    font-size: var(--s1);
}

ul.tight {
    /* margin-top: 0.5rem; */
    /* margin-bottom: 0.5rem; */
    list-style-type: none;
}

/* li + li { */
/*     margin-top: 0.5rem; */
/* } */


/* a:not(.button):not(.linkOnDomain):focus, a:not(.button):not(.linkOnDomain):hover { */
/*     background-color: linear-gradient(to right,red,blue); */
/* } */

.drongo {
    font-style: italic;
}

/* ".brag" styles were for my UCSC admission portfolio */

.brag.tech::after {
    content: "tech";
    font-size: calc(var(--s-3));
    background: #AA0044;
    color: white;
    box-shadow: 0 0 1px black;
    margin: .33em;
    padding: .1em;
}

.brag.social::after {
    content: "community";
    font-size: calc(var(--s-3));
    background: orange;
    box-shadow: 0 0 1px black;
    margin: .33em;
    padding: .1em;
}

.brag.game::after {
    content: "game";
    font-size: calc(var(--s-3));
    color: white;
    background: magenta;
    box-shadow: 0 0 1px black;
    margin: .33em;
    padding: .1em;
}


.brag.ling::after {
    content: "ling";
    font-size: calc(var(--s-3));
    background: pink;
    box-shadow: 0 0 1px black;
    margin: .33em;
    padding: .1em;
}


.brag.media::after {
    content: "media";
    font-size: calc(var(--s-3));
    background: blue;
    color: white;
    box-shadow: 0 0 1px black;
    margin: .33em;
    padding: .1em;
}

.brag.pro::after {
    content: "pro";
        font-size: calc(var(--s-3));
    background: green;
    color: white;
    box-shadow: 0 0 1px black;
    margin: .33em;
    padding: .1em;
}
.brag.hobby::after {
    content: "hobby";
        font-size: calc(var(--s-3));
    background: green;
    color: white;
    box-shadow: 0 0 1px black;
    margin: .33em;
    padding: .1em;
}

.brag.vol::after {
    content: "volunteer";
    font-size: calc(var(--s-3));
    background: green;
    color: white;
    box-shadow: 0 0 1px black;
    margin: .33em;
    padding: .1em;
}

.brag.chin::after {
    content: "chinese";
    font-size: calc(var(--s-3));
    background: black;
    color: white;
    box-shadow: 0 0 1px black;
    margin: .33em;
    padding: .1em;
}

.emote {
	font-style: italic;
}

.emote::before {
	font-style: italic;
	content: "*";
}

.emote::after {
	font-style: italic;
	content: "*";
}

.proclamation-start {
	text-transform: uppercase;
}

details > summary {
  cursor: pointer;
}

details > summary::after {
  content: " (click to open)";
  /* same as .muted */
  font-size: var(--s-1);
  font-style: italic;
}

details[open] > summary::after {
  content: " (click to close)";
  /* same as .muted */
  font-size: var(--s-1);
  font-style: italic;
}


#mc_embed_signup .button {
	background-color: green;
}

@keyframes pulse {
    /* an example animaton created with @keyframes
    /* https://github.com/jbranchaud/til/blob/master/css/animate-smoothly-between-two-background-colors.md     */
    0% {
        background-color: red;
    }
    50% {
        background-color: blue;
    }
    100% {
        background-color: red;
    }
}

.pulsing {
    animation: pulse 4s infinite;
}

.tilt {
    transition: 0.5s;
}

.tilt:hover {
    transform: rotate(2.5deg);
}

/* things that use italics */

.art-title {
    /* TODO
if I include an .art-title (or .foreign) element inside a .center element
why are there line breaks before and after the .art-title element?
I use .art-title on spans, and spans are inline elements.
   */
    font-style: italic;
}

.muted {
    font-size: var(--s-1);
    font-style: italic;
}

/* Used for foreign words in running text (including English words, if discussing linguistics.) See "Books" for examples.*/
.foreign {
    font-style: italic;
    background-color: pink;
}

em em,
.art-title .art-title,
.foreign .foreign,
.muted .muted,
em .muted,
em .art-title,
em .foreign,
.art-title .muted,
.art-title em,
.art-title .foreign,
.foreign .art-title,
.foreign .muted,
.foreign em,
.muted em,
.muted .art-title,
.muted .foreign {
    font-style: normal;
}

.tag {
    padding: var(--border-thin);
    background-color: var(--color-tag);
    border-radius: var(--s-5);
}

/* Marks a post that was generated rather than written -- a "beat" (issue #94).
   Shaped like a tag, but outlined instead of filled, because it isn't a link.
   currentColor rather than --color-dark so it stays visible inside .invert. */
.badge {
    padding: 0 var(--border-thin);
    border: solid 1px currentColor;
    border-radius: var(--s-5);
    font-size: var(--s-1);
}

/* The modular scale's ratio is 1.1, which is a fine rhythm for body copy and
   far too tight for headings: an h1 at --s4 came out 29px against 20px body,
   and the browser's default h2 (1.5em = 30px) actually out-sized it. Headings
   step off the scale and onto their own, so a title reads as a title. */
/* `text-wrap: pretty`, not `balance`: balancing evens the lines out by pulling
   words down off the first one, which on a long generated title ("Review of
   Butch Cassidy and the Sundance Kid") left the top line stopping well short of
   the box and reading as a mis-wrap. Pretty fills each line and only guards the
   last one against a single-word orphan, which is the fault worth fixing. */
h1 {
    font-size: calc(var(--s0) * 2);
    line-height: 1.1;
    letter-spacing: -0.01em;
    text-wrap: pretty;
}

h2 {
    font-size: calc(var(--s0) * 1.4);
    line-height: 1.2;
}

h3 {
    font-size: calc(var(--s0) * 1.15);
    line-height: 1.25;
}

/* A heading that is also a link is still a heading -- at every level, in every
   context, with no exceptions. The anchor is how a title carries its u-url, and
   on a stream page it is also the way through to the post; either way a reader
   should see a title, not a big blue link. Colour and decoration come from the
   heading and the pointer is the only tell, which is the whole affordance a
   clickable heading needs. (`normal-color` does this for one case; this does it
   for all of them, including :visited, which that selector loses to.) */
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a,
h1 a:visited, h2 a:visited, h3 a:visited,
h4 a:visited, h5 a:visited, h6 a:visited {
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}

/* The browser's own heading margins are ems of the heading's own font-size, so
   they scale with it -- and for h4-h6, whose default multiplier grows as the
   font shrinks, they invert: an h6 out-spaces an h2. Everything else here is on
   the one fixed rhythm, --space: .stack and .stack-recursive impose it where
   they apply, and a paragraph's 1em default happens to equal it. Put headings
   on it too, for the boxes no stack reaches -- the post body, the homepage, the
   stream pages, the responses container. The stack rules are class selectors,
   so they still win inside a stack and those pages don't move. */
h1, h2, h3, h4, h5, h6 {
    margin-block: 0;
    margin-block-start: var(--space);
}

.sithlord-name {
    background-image: linear-gradient(to bottom, red 50%, black);
    background-size: 100%;
    background-clip: text;
    text-fill-color: transparent;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    -moz-background-clip: text;
    -moz-text-fill-color: transparent;

}

.sithlord {
    background: linear-gradient(to left, aliceblue, lightgray);
}



body::before {
    /* Use the "before" pseudo-element to set the background colors rather than setting them directly on the body's background attribute itself. This solved an issue where slight differences in layout between pages would cause the background to visibly differ. */
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z-index: -1;
    background: var(--page-backdrop);
}

body {
    background: none;
}

header, article, footer {
    background-color: var(--color-light);
}

body {
    /* setting min-height to 100% of the viewport fixes the issue where if
   the homepage isn't as tall as the viewport, then the background for
   the HTML gets tiled twice and looks ugly. */
    min-height: 100vh;
    margin: 0px;
}

/* A box's padding is the space above whatever it opens with, so the first
   child brings no margin of its own. This was once article-only; every box
   needs it now that headings carry a margin -- #webmentions opens with the
   "Responses" heading, and its margin used to land on top of the padding. */
.box > :first-child,
article > :first-child {
    margin-block-start: 0px;
}

/* The same fact, stated structurally rather than per-container, because "the
   first thing in a box" is not the same as "a direct child of the box": the
   homepage wraps its opening heading in a <section>, which a reader cannot see
   and which the rule above therefore stops one level too early -- the section
   lost its margin and the <h2> inside it kept its own, landing on top of the
   padding exactly as before.

   Anything that opens its container is spaced by the container, whatever the
   container is: a box's padding, a .stack's gap, or the margin the wrapper
   carries itself. So nothing that opens one brings a top margin, at any depth
   and at any heading level. This also settles the first paragraph of a stream
   excerpt, whose 1em browser default was stacking on top of the gap .stack had
   already left under a review's stars.

   :where() keeps the whole list at the specificity of :first-child alone, so
   the handful of places that do want a margin on an opening element (see
   `div.e-content > aside`) still say so and still win. */
:where(h1, h2, h3, h4, h5, h6, p, ul, ol, blockquote, figure, pre, table):first-child {
    margin-block-start: 0;
}

.round-corner {
    border-radius: var(--s-5);
}

/* Comments brought over with an imported post (issue #20). The surface colour
   comes from the `header, article, footer` selector above, which a <section>
   isn't, so take the same variable directly -- the same thing .sidebar-inner
   and #webmentions do, and for the same reason: development.css recolours the
   site by overriding that variable alone, so anything painting its own colour
   would stay khaki there. */
#comments {
    background-color: var(--color-light);
}

/* A box inside another box: a comment under a post, a book on /books, the note
   at the top of an imported post. Sharing the surface colour of the box around
   it leaves the border as the only thing setting it apart, so wash it a little
   darker. Black at low alpha rather than a colour of its own, so it works over
   whatever is underneath -- khaki in production, green under development.css.
   The sithlord entries keep their gradient: that is a colour they chose. */
.bordered .bordered:not(.sithlord) {
    background-color: var(--color-inner-wash);
}

/* The closing footer lives in base.html, below the .stack that each page's
   content templates open, so it can't get its spacing from that stack and
   brings its own. .center sizes to the measure with content-box, which would
   leave the padding and border hanging outside it; counting them in lines the
   footer up with the content box above rather than out-denting past it. Same
   treatment, same reasons, as #webmentions (see webmention.css). */
#closing {
    box-sizing: border-box;
    margin-block: var(--space);
}

.hidden {
    display: none !important;
}

.wiki-home-link {
    padding: var(--s-5);
    background-color: var(--color-tag);
    display: block;
    transition: border 0.25s;
    text-decoration: none;
}

.wiki-home-link:hover {
    /* font-size: var(--s3); */
    border-color: red;
}

.three-column {
  display: grid;
  grid-auto-flow: column;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-5);
}

/* The column beside the post: the older/newer links, and under them the "on
   this day" box (issue #122). In the normal flow they're two more boxes in the
   post page's stack, sitting between the closing footer and the responses box;
   on a wide enough screen the column moves into the margin beside the post (see
   the media query below). */

:root {
    --sidebar-width: 20ch;
    --sidebar-gap: var(--s0);
}

.sidebar h2 {
    font-size: var(--s1);
    /* .sidebar-inner is a .stack, which spaces the heading from the line under
       it; a bottom margin here would be added to that rather than collapsed
       into it, since stack children are flex items. */
    margin-block: 0;
}

.sidebar ul {
    padding-inline-start: var(--s0);
    margin-block: 0;
}

/* The boxes in the column paint their own surface: neither a <nav> nor an
   <aside> is covered by the `header, article, footer` rule above, and the
   wrapper around them is bare so that the gap between them shows the page
   through. The variable rather than a colour of their own, so development.css
   recolours them along with everything else by overriding it. */
.on-this-day,
.neighbors {
    background-color: var(--color-light);
}

/* The column is 20ch wide, so the two links stack; the arrows say which way
   each one goes. In the flow below the breakpoint they stack too, which reads
   as what it is -- a short list of where to go next. */
.neighbors .newer {
    text-align: right;
}

/* Wide enough that the margin beside the centred column can hold the box: lift
   it out of the stack and into that margin, leaving the post exactly where it
   was. Below this it stays in the flow, which is the layout above.

   The breakpoint has to be a literal because media queries can't read custom
   properties, and it is derived rather than picked: the column is --measure
   (70ch, ~779px with the border and padding), and the box needs --sidebar-width
   plus its gap in each margin, plus a little clear of the viewport edge. Under
   that the box would hang off the right of the screen. If --measure or
   --sidebar-width changes, this number has to change with it. */
@media (min-width: 1320px) {
    /* .has-sidebar wraps the post and the closing footer, not the whole page:
       it starts at the top of the post, so top: 0 below puts the top of the box
       level with the top of the post rather than with the header above it. */
    .has-sidebar {
        position: relative;
    }

    .has-sidebar > .sidebar {
        position: absolute;
        left: 100%;
        top: 0;
        /* Full height of the wrapper, so the box inside can stick down the
           length of the post rather than only within its own height. */
        height: 100%;
        width: var(--sidebar-width);
        margin-inline-start: var(--sidebar-gap);
        /* .stack spaces its in-flow children; this one is out of flow. */
        margin-block-start: 0;
    }

    /* One sticky wrapper for the whole column rather than a sticky box each:
       two sticky siblings would slide over one another on the way down. */
    .has-sidebar > .sidebar > .sidebar-inner {
        position: sticky;
        top: var(--s-5);
    }
}

/* The books-read-per-year table on /books (issue #123). Figures only read as a
   column if each cell is aligned to its own edge: centred cells drift sideways
   the moment one row is wider than the others, which the rows carrying a note
   ("2015 (incomplete)", "20 so far") always are. */
.year-counts th,
.year-counts td {
  text-align: left;
  padding-right: var(--s1);
}

.year-counts td {
  font-variant-numeric: tabular-nums;
}


/* --- 2026 redesign (issue #88) ------------------------------------------

   The streams (homepage, /blog, a month archive, a tag page) now show the top
   of each post rather than a title link, so an entry is a block of prose and
   needs the spacing and separation of one. */

/* The pick of the site, above the newest of it on the homepage. Set a little
   smaller than the posts under it: it is a signpost, not the page's content.
   The wash comes from the `.bordered .bordered` rule above -- this is a box
   inside the page's box, and gets the same treatment as any other. */
/* One post in a stream (homepage, /blog, a month archive, a tag page).
   
   Each entry is its own bounded block floating inside the page's content box,
   the way an imported comment or a Sith Lord entry is -- that is the level of
   separation an entry needs, because a reader has to be able to tell where one
   post stops and the next starts when both are showing prose.

   Deliberately a step lighter than those, though: a hairline border and no
   wash, where a comment gets the full --border-thin black and the inner-box
   colour. An entry here is a teaser pointing at a post, not the post; keeping
   it visually below the site's real content boxes is what stops the listing
   from reading as a page full of embedded documents. */
.post-entry {
    --space: var(--s-3);
    border: 1px solid #0004;
    border-radius: var(--s-5);
    padding: var(--s-2);
    /* Lifted off the page rather than washed with a colour of its own: white at
       low alpha keeps an entry in the surface's own family -- paler khaki in
       production, paler green under development.css -- so the site still reads
       as one thing. With the shadow below, an entry sits on the page like a
       card on a desk, which is the relationship: the page is the surface, the
       entries are things laid on it. */
    background-color: var(--color-embedded);
    box-shadow: 0 1px 3px #00000026;
}

/* The title carries the entry: the block's own edges say where the entry
   starts, so the title doesn't need a colour to stand out as well. */
.post-entry-title {
    font-size: calc(var(--s0) * 1.2);
    margin-block-start: 0;
}

/* Date, badge and tags on one quiet line under the title. */
.post-entry-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--s-4);
}

.post-entry-meta .tag {
    font-size: var(--s-1);
    font-style: normal;
}

.post-entry .excerpt img,
.post-entry .entry-thumb img {
    max-block-size: 12rem;
    inline-size: auto;
}

/* A heading inside an excerpt is the post's own, and it must not outrank the
   entry's title above it, let alone the heading of the section listing it: an
   <h2> reading "Features" was coming out the same size as "New Posts". */
.post-entry .excerpt :is(h2, h3, h4, h5, h6) {
    font-size: var(--s0);
}

/* Spacing only between things, never above the first of them -- this selector
   out-specifies the opening-element rule above, so without the `* +` a heading
   that begins an excerpt would take a margin back. */
.post-entry .excerpt * + :is(h2, h3, h4, h5, h6) {
    margin-block-start: var(--s-3);
}

/* Upright, not the italic the site's other small meta text takes. This link and
   the mark it lands on are meant to read as one thing that moved, and on the way
   over they are the same words at the same size -- so a difference in slant is a
   difference the transition has to animate away, and which a reader without the
   transition sees as two unrelated bits of text. The label over in the mark is
   the one to keep this in step with. */
.continue-reading a {
    font-size: var(--s-1);
}

/* An excerpt is a flex item, so it establishes its own formatting context and
   the bottom margin of whatever block it ends with stays inside the box rather
   than collapsing away at its edge. The stack then adds its own gap underneath,
   and the space above "Continue reading" came out as the two of them stacked --
   nearly twice the gap between any other pair of things in the entry. Trimming
   the trailing margin leaves the stack in sole charge of the spacing, which is
   what it is there for. The excerpt is the only box here whose last block is
   whatever a post happened to end its opening with, so this is not a rule the
   rest of the entry wants. */
.excerpt > :last-child {
    margin-block-end: 0;
}

/* A review's score in a stream, with or without prose beneath it. Sized down
   from the post page's stars, where they are the headline. The line collapses
   to the height of the stars themselves: left at the body's line-height, the
   descender space under an inline image opened a gap wider than the one above
   the stars, so the score drifted towards the prose it belongs above. */
.post-entry .entry-rating {
    line-height: 1;
}

.post-entry .entry-rating .star {
    height: var(--s1);
    vertical-align: middle;
}

/* The date-and-tags line under a post's title (issue #88): the byline moved to
   the foot of the post, and the tags came up off their own block onto this
   line, so that the writing starts as near the top of the page as it can. */
h1 + .post-meta {
    margin-block-start: var(--s-5);
}

/* The line is .muted, which italicises; a tag chip is a label, not an aside. */
.post-meta .tag {
    font-size: var(--s-1);
    font-style: normal;
}

/* The way out of a stream. `.cluster.inner` lays the links out and spaces them;
   this rules it off from the last entry so it doesn't read as part of it. */
.archive-links {
    border-block-start: 1px solid #0002;
    padding-block-start: var(--s-2);
    font-size: var(--s-1);
}

/* The revealed half of a <details> is a box inside a box, and it says so: the
   same border, radius and wash any other inner box gets (the imported comments,
   the note on an archived post). It has to carry the border, not the padding
   alone -- a padded box with no edge reads as text mysteriously indented from
   everything around it, which is what a bare `padding` here produced. Closed,
   the element is one line of a paragraph and takes no box at all.

   The box goes on ::details-content, not on the <details>: it is the disclosed
   content that becomes a box, and the summary is the handle that discloses it.
   Styling the element itself put the summary inside the box too, so opening it
   pushed the summary down by the border and padding -- ~18px, most of a line --
   and the pointer that had just clicked "open" was no longer over "close". A
   control that moves out from under the cursor when you use it is the one thing
   a disclosure widget must not do. This is also the only way to say it in CSS:
   everything after the summary is a run of raw siblings with no wrapper of its
   own, and ::details-content is precisely that run. */
details {
    border-radius: var(--s-5);
}

details[open]::details-content {
    display: block;
    margin-block-start: var(--s-4);
    border: var(--border-thin) solid var(--color-dark);
    border-radius: var(--s-5);
    background-color: var(--color-inner-wash);
    padding: var(--s-4);
}

/* The disclosed run is a box, so what opens and closes it is spaced by the box
   -- the same rule the rest of the site follows, said again here because the
   structural version of it cannot reach this far. That one keys off
   :first-child, and the first thing inside a disclosure is the summary's
   sibling rather than anyone's first child, so a <ul> in an open <details> was
   laying its own 1em on top of the padding at both ends and leaving the box
   half empty above the first bullet. The summary's own gap is the margin on
   ::details-content above, or the summary's own margin in the fallback below;
   neither wants a second one from the content. */
details > summary + * {
    margin-block-start: 0;
}

details > :last-child {
    margin-block-end: 0;
}

/* The browser reserves 40px for a list marker, which is most of a box this
   size: inside the disclosure the bullets were sitting a third of the way in
   from an edge that already has padding of its own. Pulled back to the one step
   that holds a bullet or a single-digit number, the same thing and the same
   value as the lists in the sidebar. */
details > ul,
details > ol {
    padding-inline-start: var(--s0);
}

/* Browsers without ::details-content (it landed across the engines during
   2024-25) fall back to the old shape: the summary moves on them, as it always
   did, but the disclosed text still reads as a box rather than as prose that
   has wandered in. */
@supports not selector(::details-content) {
    details[open] {
        border: var(--border-thin) solid var(--color-dark);
        background-color: var(--color-inner-wash);
        padding: var(--s-4);
    }

    details[open] > summary {
        margin-block-end: var(--s-4);
    }
}

/* The archive as a calendar (issue #88). Every year that has anything in it
   shows all twelve of its months in a 3-wide grid, so a year is a shape you
   can read at a glance rather than a ragged list of the months that happened
   to survive. Reading order runs across, which puts each row on a quarter. */
.archive-year + .archive-year {
    margin-block-start: var(--s2);
}

.month-grid {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--s-4);
    margin-block-start: var(--s-3);
}

.month-cell {
    display: flex;
    flex-direction: column;
    gap: var(--s-6);
    padding: var(--s-4) var(--s-3);
    border: 1px solid #0004;
    border-radius: var(--s-5);
    text-decoration: none;
}

a.month-cell:hover,
a.month-cell:focus-visible {
    border-color: var(--color-dark);
    background-color: var(--color-inner-wash);
}

/* A month with nothing in it has nowhere to go, and says so plainly instead of
   claiming a count of zero: "no posts" is the fact; "0 posts" is arithmetic. */
.month-cell.empty {
    border-style: dashed;
    border-color: #0002;
    opacity: 0.55;
    cursor: default;
}

.month-name {
    font-weight: bold;
}

a.month-cell .month-name {
    color: var(--color-link);
}

.month-count {
    font-size: var(--s-2);
    font-style: italic;
    font-variant-numeric: tabular-nums;
}

@media (max-width: 34rem) {
    .month-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* Greatest Hits (issue #88): the strands of work worth arriving for.

   A row each -- the name in bold and linked, what the thing is running on after
   it -- rather than a card each. The card was doing two jobs, telling the
   strands apart and holding each one's contents together, and only the first
   needed doing: the colour stripe tells them apart on its own, while the
   border, the wash and the padding cost a fixed 76px per item before a word of
   content.

   Nothing here is laid out in columns, so a row that doesn't fit simply wraps
   onto a second line. That is deliberate: on a phone every one of these wraps,
   and a label column or a fixed two-up grid would spend its effort fighting the
   thing the layout should let happen.

   The colour is the stripe and nothing else. It is opaque, so it reads as
   itself over khaki in production and over green under development.css, where
   the wash the cards also carried came out as a tint of whatever was underneath
   and six tints of the same ground look alike. */
.greatest-hits h2 {
    margin-block-end: var(--s-3);
}

.hits-grid {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
}

.hit {
    padding-inline-start: var(--s-2);
    border-inline-start: var(--s-4) solid var(--hit-stripe);
    font-size: var(--s-2);
    line-height: 1.35;
}

/* The name is the link; the description is plain text beside it, so it stays
   out of the target and out of the link colour. */
.hit-title {
    font-weight: bold;
}

/* The mark between the name and what it is belongs to this layout rather than
   to the writing, so it comes from here and not from the template, and the span
   it hangs off is left empty so that this declaration is the only place it
   lives: change the character here, or in devtools, and every row follows.

   Mark then a non-breaking space: a trailing ordinary space in `content` is
   dropped by white-space processing, which ran the description straight into
   the mark. The space in front of the span, being an ordinary one, stays
   breakable, so the mark wraps down with the description rather than being
   left behind at the end of a line. */
.greatest-hit-separator::before {
    content: "\27A5\00A0";
}

/* Games, the RPG writing and the software around it: the thesis, the essays,
   and Zingor, which has no landing page of its own yet (/zingor redirects to
   the repository -- see web/urls.py) but belongs to the same strand. */
.hit-games {
    --hit-stripe: #0f7b6c;
}

.hit-drongo {
    --hit-stripe: #c25a10;
}

.hit-chinese {
    --hit-stripe: #b32222;
}

.hit-sith {
    --hit-stripe: #1c1c28;
}


/* The photo strip: the newest few pictures on the site in one row, newest at
   the left, and the way to the rest of them in the last cell.

   One row, always. The row is what makes it a strip rather than a gallery, and
   the point of a strip is that it costs one row's height: it sits between the
   Greatest Hits and the newest posts, and anything it spends comes out of how
   much of the stream a reader can see without scrolling. The cells share the
   column between them and are square, so the row is as tall as a cell is wide
   and the arithmetic is one number rather than six pictures' worth of guessing.

   Square, and the pictures cropped to fill. The photographs are a mix of
   portrait and landscape, and a row of thumbnails at their own proportions is a
   ragged line of different heights with the tall ones setting the row's height
   for everybody. A crop loses the edges of a frame; the frame is one click
   away, and the row reads as a row. */
.photo-strip h2 {
    margin-block-end: var(--s-3);
}

.photo-row {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: var(--s-4);
}

.photo-cell {
    aspect-ratio: 1;
    overflow: hidden;
    border: 1px solid #0004;
    border-radius: var(--s-5);
}

.photo-cell a {
    display: flex;
    block-size: 100%;
    inline-size: 100%;
    align-items: center;
    justify-content: center;
    text-decoration: none;
}

.photo-cell img {
    /* Fills its cell and is cropped to it, rather than being letterboxed inside
       it: see the note above about a ragged row. */
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    display: block;
}

.photo-cell:hover,
.photo-cell:focus-within {
    border-color: var(--color-dark);
}

/* The way out of the strip, and not a photograph: drawn as the empty month
   cells on /archive are, so that a cell which isn't a picture doesn't look like
   a picture that failed to load. */
.photo-more {
    border-style: dashed;
}

.photo-more a {
    padding: var(--s-4);
    font-size: var(--s-2);
    line-height: 1.35;
    text-align: center;
    text-wrap: balance;
}

.photo-more:hover,
.photo-more:focus-within {
    background-color: var(--color-inner-wash);
}

/* On a narrow screen six cells across the column would be a row of postage
   stamps, so the cells keep a size worth looking at and the row scrolls
   sideways instead. It stays one row, which is the part that matters: the
   newest picture is still the leftmost thing, the strip still costs the page a
   single row's height, and the cells past the edge are a swipe away rather than
   a scroll past. */
@media (max-width: 34rem) {
    .photo-row {
        /* The root font-size is --s0, so this is a 100px cell: three and a bit
           of them across a phone, which is enough of the row showing that the
           rest of it is obviously there to be pushed at. */
        grid-auto-columns: 5rem;
        overflow-x: auto;
        /* Room for the focus ring and the scrollbar, so neither is drawn on top
           of the first cell's border. */
        padding-block-end: var(--s-5);
        scroll-snap-type: x proximity;
    }

    .photo-cell {
        scroll-snap-align: start;
    }
}

/* --- Before and after -------------------------------------------------------

   For posts that show the site changing: the old screenshot and the new one,
   each captioned above the picture rather than below it. Above, because the two
   images are nearly the same image and the word is what tells them apart -- a
   caption underneath is read after the reader has already spent the comparison.

   Stacked, always, at whatever width the column is. Side by side was the
   obvious layout and it doesn't survive contact with this site: a pair of
   full-page screenshots inside 70ch is two illegible slivers, and the only way
   to give them room is to break out of the measure -- which on a post page runs
   straight under the sidebar that moves into the margin at 1320px. Stacked,
   each shot is as wide as the column allows, in the order the words say, and
   there is nothing to go wrong at any width.

   The grid is here for its gap and for the row it makes of each figure; a
   single column is the whole layout. */

.before-after {
    display: grid;
    gap: var(--s0);
    margin-block: var(--s2);
}

/* The site's <figure> brings its own margin, which would double the gap above,
   and its own stack spacing, which is looser than a caption sitting on top of
   the thing it names wants. */
.before-after figure {
    margin: 0;
    gap: var(--s-3);
}

.before-after figcaption {
    font-weight: bold;
    text-align: center;
}

/* Bordered like every other box on the site, so a screenshot of a page reads as
   a picture of a page and not as part of this one. The width is left to the
   `img` rule in every_layout.css: a desktop shot is wider than the column and
   comes down to fit it, and nothing is ever blown up past its own size. */
.before-after img {
    display: block;
    border: var(--border-thin) solid var(--color-dark);
}

/* --- Where to carry on reading (issue #88) ---------------------------------

   A reader who followed "Continue reading" has already read the top of the post
   and arrives with no way of telling how far down that was. The excerpt engine
   cut at a block boundary, so the answer is exact rather than a guess at some
   text: web/excerpts.py gives that block the id below, and the "Continue
   reading" fragment aims at it. (The id is spelled out in both places; there is no way to hand
   Python's name to CSS, so the two have to be kept in step by hand.)

   Everything here hangs off :target, which is only on when the fragment was
   actually followed. A reader who arrived from a search engine sees an ordinary
   post -- the id is in the markup for them too, and does nothing. */

#continue-reading {
    scroll-margin-block-start: var(--continue-offset);
    /* For the mark below, which is drawn out of the flow. */
    position: relative;
    /* How far back past this block's own left edge the rule reaches. Nothing
       for a top-level block, which already starts at the edge of the column. */
    --continue-outdent: 0px;
    /* The band above the block that the mark is drawn in. */
    --continue-room: var(--s1);
}

/* The mark is drawn out of the flow, so it needs a gap of its own to land in.
   Between two paragraphs there is one already; between two list items there is
   next to none, and the rule came down across the words of the item above.
   Making the room here rather than living off whatever happens to be there
   means the mark looks the same wherever it lands. Only under :target, so a
   post nobody was sent into is spaced exactly as it always was. */
#continue-reading:target {
    margin-block-start: var(--continue-room);
}

/* A list item is indented by the list's padding, and a rule that started at the
   item's own text would read as an underline on the item above it rather than
   as a line across the post. The mark belongs to the post, so it reaches back
   out to the column edge. 40px is the browser's own list indent: prose lists in
   a post take the UA default, which nothing in this site overrides. */
li#continue-reading {
    --continue-outdent: 40px;
}

/* The mark is a rule across the column with the label sitting on it, the way a
   forum draws a line at the first unread post. It takes two pseudo-elements
   because one of them has to be the words: ::before draws the rule and ::after
   is the label, given the surface's own colour so that it breaks the rule into
   two pieces on either side. Reading it as one rule interrupted by a label,
   rather than as a label with a rule above it, is what makes it a mark on the
   post rather than a heading in it.

   Both are taken out of the flow and drawn in the gap that already sits above
   the block. In the flow they would take space of their own, which shoves the
   post's text down the page at the moment the mark appears; worse, on a list
   item the rule became the item's first line box and the bullet moved down to
   sit beside the label instead of beside the words. Out of flow, the item's own
   text is its first line again and the bullet goes back where it belongs.

   The marked block is never the first thing in the post -- the excerpt always
   showed at least one block before the one it stopped at -- so there is always
   something above for the mark to sit between.

   The words live here rather than in the markup so they can be reworded without
   touching Python -- the whole mark is this one `content` string and the rules
   around it. They are the words on the link that was just clicked, over in
   macros.html, and that is the point of them: a reader who clicked "Continue
   reading" and lands on a line reading "Continue reading" has nothing left to
   work out. Nothing else here -- not the rule, not the dim, not any animation
   -- says as plainly that these two places are the same place, and unlike the
   rest of it, saying it this way survives a reader who looked away, a reader
   who came back an hour later, and a screen reader. So the two strings have to
   be reworded together.

   They stay an instruction rather than a statement about the reader. "You have
   read this far" sounds like a post that remembers you, and the first thing
   anyone would expect of it is that it still knows where they got to when they
   come back tomorrow. It doesn't: the mark is put here by the link that was
   just followed, and nothing is stored anywhere. Telling the reader where to
   carry on promises only what the site actually does.

   The arrow is the half the words can't carry. "Continue reading" names the
   boundary; the arrow says which side of it to be on, which is the one thing a
   reader arriving in the middle of a post needs and a horizontal line cannot
   tell them. */
#continue-reading:target::before {
    content: "";
    position: absolute;
    inset-block-end: calc(100% + var(--continue-room) / 2);
    inset-inline-start: calc(-1 * var(--continue-outdent));
    inline-size: calc(100% + var(--continue-outdent));
    border-block-start: 3px solid var(--color-continue);
}

#continue-reading:target::after {
    content: "Continue reading \2193";
    position: absolute;
    /* Bottom edge on the rule, then pushed back down by half its own height, so
       it straddles the line rather than sitting above it. */
    inset-block-end: calc(100% + var(--continue-room) / 2);
    transform: translate(-50%, 50%);
    /* Centred on the rule, which on a list item runs further left than the item
       does. */
    inset-inline-start: calc(50% - var(--continue-outdent) / 2);
    /* The surface showing through is what makes the gap in the rule.
       --color-light is what `header, article, footer` paint with, so this
       follows the page into the development colours rather than punching a
       khaki hole in a green post. */
    background-color: var(--color-light);
    padding-inline: var(--s-3);
    white-space: nowrap;
    /* The same words at the same size and weight as the link they arrive from,
       so the transition has nothing to animate but the distance, and a reader
       who never sees the transition still recognises them.

       This was a grey italic --s-2 whisper, which is the right size for an
       aside and the wrong one for the only thing on the page explaining why the
       reader is standing in the middle of a post. It went bold to fix that, and
       the bold turned out to be the wrong half to have made loud: the 3px rule
       at full-strength orange is what carries the mark across a screenful, and
       once that was doing the work the weight was only making the two ends of
       the transition disagree. Upright and at full text colour is enough. */
    font-size: var(--s-1);
    color: var(--color-dark);
}

/* What the reader has already been shown, stepped back so the eye lands below
   it rather than at the top of the post. Three selectors because the mark can
   land on a list item: the blocks above a marked block, the blocks above a list
   holding a marked item, and the items above it inside that list. Anything
   without :has() support simply shows an undimmed post, which is the old
   behaviour and no worse than it was. */
.e-content > :has(~ #continue-reading:target),
.e-content > :has(~ * #continue-reading:target),
.e-content li:has(~ #continue-reading:target) {
    opacity: 0.45;
    animation: continue-dim var(--continue-fade) ease-out;
}

/* The dim arrives rather than simply being there. A reader following "Read
   more" is looking at words they read a moment ago on another page, and the
   blocks start at the brightness those words had in the excerpt and then step
   back from it. The rule across the column says where the boundary is; this
   says which side of it they are on, which is the half a static dim leaves
   them to work out.

   Only the opening brightness is written here. The closing one is the 0.45
   above: a keyframe list with no `to` ends at the element's own computed
   value, so the two can't drift apart. */
@keyframes continue-dim {
    from {
        opacity: 1;
    }
}

/* Nothing is lost by not seeing it happen -- the mark and the dim both still
   say what they say, and this is a reader arriving to read, not to watch. */
@media (prefers-reduced-motion: reduce) {
    :root {
        --continue-fade: 0s;
    }
}

/* --- Carrying the mark across the navigation (issue #88) -------------------

   The link and the mark say the same words, which is what tells a reader the
   two are the same place. A view transition makes them the same *object*: the
   words they clicked are lifted off the stream page and set down as the label
   on the mark, so the connection is watched rather than worked out.

   Nothing depends on it. Firefox has no cross-document transitions and the
   navigation is an ordinary one there, which is the behaviour every reader had
   until now: they land on a loud orange line saying what they just clicked.

   Two things are worth knowing about the opt-in. It is an at-rule with no
   selector, because the browser has to decide whether to capture the outgoing
   page before a byte of the incoming one exists -- there is no element to hang
   it off at that moment, and so no way to scope it to this one link. Since it
   lives in the stylesheet every page loads, every same-origin navigation on
   the site is now eligible. What stops that from being a site-wide redesign is
   the rule under it: with the root's own animation off, a navigation with no
   matching name is drawn exactly as it always was, instantly. Only the pair
   below animates, and that pair only exists when a "Continue reading" was
   clicked. The residual cost is that the browser sets up a transition on
   navigations that end up doing nothing, which is cheap and buys the whole
   feature its one line of opt-in.

   The name is put on the outgoing link by hand -- see the script in base.html.
   A stream page carries one "Continue reading" per entry, and a duplicated
   view-transition-name makes the browser drop the transition entirely, so the
   name can only go on the one link that was actually clicked. */
@view-transition {
    navigation: auto;
}

/* The page itself keeps swapping the way it always has. */
::view-transition-old(root),
::view-transition-new(root) {
    animation: none;
}

@media (prefers-reduced-motion: no-preference) {
    /* The label rather than the block it hangs off: the words on the mark are
       the words on the link, so what travels is the phrase itself. Naming the
       marked paragraph instead would fly a screenful of text across the page
       to say the same thing less clearly. */
    #continue-reading:target::after {
        view-transition-name: continue-mark;
    }

    /* Except on the way out to somewhere new. :target is still matching when
       the reader leaves the post, so without this the mark offers a snapshot
       to every navigation off the page and gets an unpaired exit animation --
       the label drifting over the top of whatever they opened next. The class
       is put on the root by the script in base.html, which is the only place
       that knows whether this navigation is going somewhere the words can
       land: back to the stream they came from, where the link is waiting to
       catch them, or on to a page that has never heard of them. */
    .continue-not-returning #continue-reading:target::after {
        view-transition-name: none;
    }

    /* Slower than the 250ms default. The words cross a good part of the screen
       and change size on the way, and at the default they arrive before the
       eye has found them -- which is the mistake this whole line of work has
       been undoing. */
    ::view-transition-group(continue-mark),
    ::view-transition-old(continue-mark),
    ::view-transition-new(continue-mark) {
        animation-duration: 400ms;
        animation-timing-function: ease-in-out;
    }
}
