/*! Calculi-TN (calculi.tn) - (c) 2022-2026 Ali Abdelkader Aydi. All rights reserved.
 *  Proprietary and confidential. Reproduction, redistribution or reuse of this
 *  file, in whole or in part, is prohibited without written permission.
 *  Contact: aliayditn02@gmail.com */

/* ==================================================================
 * THE PALETTE — an instrument panel, not a broadsheet
 * ==================================================================
 *
 * This section used to be cream paper (#fcfbf9), a high-contrast serif and a
 * terracotta accent (#a8371a). That combination is the single most common
 * house style a generated "essay publication" arrives in, and it said nothing
 * about either this publication or the site it hangs off.
 *
 * What replaced it comes from two places that are actually true here:
 *
 *   1. THE PARENT BRAND. /Local_imgs/plus.png — the Calculi mark — is a
 *      cyan-to-blue ring. A reader who follows a link from calculi.tn should
 *      recognise the family; a reader who arrives from Hacker News should not
 *      be handed a magazine. So the accent is that cyan, darkened until it is
 *      readable as text.
 *
 *   2. THE NAME. An invariant is what a transformation preserves, so the
 *      furniture is measurement: a coordinate lattice for ground, and one
 *      2px datum rule that runs across the masthead, divides the sections and
 *      underlines the active nav item. Everything is placed against that line.
 *
 * --inv-accent IS A TEXT COLOUR (5.31:1 on paper, 9.77:1 on the inverted
 * band). --inv-accent-bright IS NOT — it is 2.55:1 and exists only for rules,
 * marks and pattern strokes where no text sits. The same split the rest of
 * this codebase already runs: --tess-N vs --tess-N-ink on the blog,
 * --og-human vs --og-human-deep on the guide. Do not set type in the bright
 * one because it looks better; it is unreadable and the check will not catch
 * it.
 */
.inv-page {
  --inv-paper: #f6f8f9;
  --inv-paper-2: #edf1f3;
  --inv-paper-3: #e0e6ea;

  --inv-ink: #0f171d;
  --inv-ink-2: #3a4752;
  --inv-ink-3: #5f6d78;

  --inv-rule: #dce3e8;
  --inv-rule-2: #bfcad2;

  --inv-accent: #0b6f93;
  --inv-accent-2: #095a78;
  --inv-accent-bright: #12a9dc;
  --inv-accent-wash: #e4f1f7;

  /*
   * THE TOPIC HUES.
   *
   * Six, assigned by hashing a topic's SLUG — never its index in the sorted
   * list, which is the mistake utils/viewHelpers.js#tessera documents avoiding
   * on the blog: the list is sorted by name, so renaming one topic would
   * reshuffle the colour of every topic after it.
   *
   * They are used as LEGEND MARKS — a 7px dot beside a sentence-case topic
   * name — and as the tint on a topic panel's contour art. They are never a
   * fill behind text and never the text itself, which is what keeps six
   * colours from turning an engineering publication into a fruit bowl. The
   * label beside a dot is always --inv-ink.
   */
  --inv-h1: #0b6f93;
  --inv-h2: #5d7029;
  --inv-h3: #8f5222;
  --inv-h4: #5f4f9e;
  --inv-h5: #95304f;
  --inv-h6: #256b58;

  /*
   * TYPE — two voices and a tool.
   *
   *   Space Grotesk   display and UI. A technical grotesque with drawn rather
   *                   than derived letterforms; it reads as instrumentation.
   *                   It replaced setting everything in the body serif, which
   *                   is what made the old page read as a literary quarterly.
   *   Source Serif 4  the body, and only the body. Drawn for documentation
   *                   rather than for print, so it holds up at 19px on a
   *                   laptop through a 3,000-word argument.
   *   IBM Plex Mono   code, figures and measurements. Unchanged: tokens.css
   *                   already uses it, and one monospace across all four
   *                   sections of the site is worth more than a fourth
   *                   opinion about monospace.
   */
  --inv-serif: "Source Serif 4", Georgia, "Times New Roman", serif;
  --inv-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --inv-sans: "Space Grotesk", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  --inv-measure: 40rem;
  --inv-wrap: 72rem;

  --inv-t-body: 1.1875rem;
  --inv-lh-body: 1.7;

  --inv-s1: 0.25rem;
  --inv-s2: 0.5rem;
  --inv-s3: 0.75rem;
  --inv-s4: 1rem;
  --inv-s5: 1.5rem;
  --inv-s6: 2rem;
  --inv-s7: 3rem;
  --inv-s8: 4.5rem;
  --inv-s9: 7rem;

  --inv-stamp: 0.075em;

  background: var(--inv-paper);
  color: var(--inv-ink);
  font-family: var(--inv-serif);
  font-size: var(--inv-t-body);
  line-height: var(--inv-lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-optical-sizing: auto;
}

/*
 * Dark is written out twice on purpose, and tokens.css explains why at
 * length: there are THREE theme states, not two. `:root` alone, the OS
 * preference with no explicit choice, and an explicit choice. The dark
 * override used to be written for the third only, so anyone on a dark OS who
 * never touched the toggle — the DEFAULT — got the light palette.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .inv-page {
    --inv-paper: #0b1014;
    --inv-paper-2: #111920;
    --inv-paper-3: #18222a;

    --inv-ink: #e9eff3;
    --inv-ink-2: #a6b4be;
    --inv-ink-3: #77858f;

    --inv-rule: #1e2830;
    --inv-rule-2: #2e3b45;

    --inv-accent: #4ecdf2;
    --inv-accent-2: #8addf6;
    --inv-accent-bright: #4ecdf2;
    --inv-accent-wash: #10262f;

    --inv-h1: #5cc4e8;
    --inv-h2: #a8c25c;
    --inv-h3: #d99a5c;
    --inv-h4: #a496e8;
    --inv-h5: #e8809b;
    --inv-h6: #5fc4a6;
  }
}

:root[data-theme="dark"] .inv-page {
  --inv-paper: #0b1014;
  --inv-paper-2: #111920;
  --inv-paper-3: #18222a;

  --inv-ink: #e9eff3;
  --inv-ink-2: #a6b4be;
  --inv-ink-3: #77858f;

  --inv-rule: #1e2830;
  --inv-rule-2: #2e3b45;

  --inv-accent: #4ecdf2;
  --inv-accent-2: #8addf6;
  --inv-accent-bright: #4ecdf2;
  --inv-accent-wash: #10262f;

  --inv-h1: #5cc4e8;
  --inv-h2: #a8c25c;
  --inv-h3: #d99a5c;
  --inv-h4: #a496e8;
  --inv-h5: #e8809b;
  --inv-h6: #5fc4a6;
}

.inv-page *,
.inv-page *::before,
.inv-page *::after {
  box-sizing: border-box;
}

:where(.inv-page a) {
  color: var(--inv-accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.16em;
}

:where(.inv-page a:hover) {
  color: var(--inv-accent-2);
}

.inv-page ::selection {
  background: var(--inv-accent-wash);
  color: var(--inv-ink);
}

.inv-page :focus-visible {
  outline: 2px solid var(--inv-accent);
  outline-offset: 3px;
  border-radius: 2px;
}

.inv-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.inv-skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: var(--inv-s3) var(--inv-s4);
  background: var(--inv-ink);
  color: var(--inv-paper);
  text-decoration: none;
}

.inv-skip:focus {
  left: var(--inv-s4);
  top: var(--inv-s4);
}

.inv-i {
  display: inline-block;
  width: 1.05em;
  height: 1.05em;
  vertical-align: -0.16em;
  flex: none;
}

.inv-i-flip {
  transform: scaleX(-1);
}

.inv-wrap {
  width: 100%;
  max-width: var(--inv-wrap);
  margin-inline: auto;
  padding-inline: var(--inv-s5);
}

.inv-wrap-narrow {
  width: 100%;
  max-width: var(--inv-measure);
  margin-inline: auto;
  padding-inline: var(--inv-s5);
}

.inv-progress {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  height: 2px;
  z-index: 60;
  background: transparent;
  pointer-events: none;
}

.inv-progress-bar {
  height: 100%;
  width: 0;
  background: var(--inv-accent);
  transition: width 0.08s linear;
}

/* ==================================================================
 * THE HEADER
 * ==================================================================
 *
 * Sticky, one row, and it ends in THE DATUM — a 2px accent rule rather than
 * the 1px hairline every other border on this page uses. That rule is the
 * design's one recurring device: it closes the header, it closes the
 * masthead, it opens the subscribe band, and it underlines the active topic.
 * A reader never has to be told what it means; they only have to notice that
 * it is always the same line.
 */
.inv-head {
  border-block-end: 2px solid var(--inv-accent);
  background: var(--inv-paper);
  position: sticky;
  inset-block-start: 0;
  z-index: 50;
}

.inv-head-in {
  max-width: var(--inv-wrap);
  margin-inline: auto;
  padding: var(--inv-s3) var(--inv-s5);
  display: flex;
  align-items: center;
  gap: var(--inv-s5);
}

.inv-mark {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-family: var(--inv-sans);
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--inv-ink);
  text-decoration: none;
  white-space: nowrap;
}

/* The site's mark, /Local_imgs/plus.png. It is a RASTER with its own gradient,
   so unlike the symbol it replaced it does not follow `color` — which is fine,
   because the cyan reads on both grounds and the whole point of using the site
   mark is that it looks the same here as everywhere else on calculi.tn.
   Sized in `em` so it tracks the wordmark beside it. */
.inv-mark-i {
  width: 1.5em;
  height: 1.5em;
  flex: none;
  object-fit: contain;
}

a.inv-mark:hover {
  color: var(--inv-accent);
}

.inv-mark-sm {
  font-size: 0.875rem;
}

.inv-burger {
  display: none;
  margin-inline-start: auto;
  background: none;
  border: 0;
  padding: var(--inv-s2);
  color: var(--inv-ink);
  cursor: pointer;
  font-size: 1.25rem;
  line-height: 1;
}

.inv-nav {
  display: flex;
  align-items: center;
  gap: var(--inv-s5);
  margin-inline-start: auto;
  min-width: 0;
}

.inv-nav-list {
  display: flex;
  align-items: center;
  gap: var(--inv-s5);
  list-style: none;
  margin: 0;
  padding: 0;
  min-width: 0;
  overflow: hidden;
}

.inv-nav-link {
  font-family: var(--inv-sans);
  font-size: 0.875rem;
  color: var(--inv-ink-2);
  text-decoration: none;
  white-space: nowrap;
  padding-block: var(--inv-s1);
  border-block-end: 2px solid transparent;
}

.inv-nav-link:hover {
  color: var(--inv-ink);
}

.inv-nav-link.is-active {
  color: var(--inv-ink);
  border-block-end-color: var(--inv-accent);
}

.inv-tools {
  display: flex;
  align-items: center;
  gap: var(--inv-s1);
}

.inv-tool {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border: 0;
  border-radius: 2px;
  background: none;
  color: var(--inv-ink-3);
  cursor: pointer;
  text-decoration: none;
}

.inv-tool:hover {
  color: var(--inv-ink);
  background: var(--inv-paper-2);
}

.inv-theme .inv-i-sun {
  display: none;
}

.inv-theme .inv-i-moon {
  display: inline-block;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .inv-theme .inv-i-sun {
    display: inline-block;
  }
  :root:not([data-theme="light"]) .inv-theme .inv-i-moon {
    display: none;
  }
}

:root[data-theme="dark"] .inv-theme .inv-i-sun {
  display: inline-block;
}

:root[data-theme="dark"] .inv-theme .inv-i-moon {
  display: none;
}

/* ==================================================================
 * THE LATTICE GROUND
 * ==================================================================
 *
 * The `#inv-lattice` pattern lives in the document once (see
 * views/partials/inv_assets.ejs); any block paints it by putting an
 * absolutely-positioned `<svg><rect fill="url(#inv-lattice)">` behind its
 * content. `.inv-field` is that backdrop, and it is decoration — the markup
 * marks it aria-hidden.
 *
 * It fades out downward. A full-strength grid behind a paragraph is graph
 * paper, which is a different and much worse idea than a ground.
 */
.inv-field {
  position: absolute;
  inset: 0;
  /*
   * width/height ARE REQUIRED and are not belt-and-braces.
   *
   * `inset: 0` stretches a normal box. An <svg> is a REPLACED element, and for
   * one of those CSS resolves `width: auto` to the INTRINSIC width even with
   * both left and right pinned — which for an SVG carrying no dimensions is
   * the 300x150 default. The first version of this shipped with inset alone
   * and painted a 300x150 patch of lattice in the top-left corner of every
   * masthead, plate and panel, which reads as a rendering fault rather than as
   * a ground.
   */
  width: 100%;
  height: 100%;
  z-index: 0;
  color: var(--inv-rule-2);
  opacity: 0.55;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0, 0, 0, 0.4) 55%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, rgba(0, 0, 0, 0.4) 55%, transparent 100%);
}

.inv-field rect {
  width: 100%;
  height: 100%;
}

/*
 * The contour composition. Same absolute backdrop as .inv-field, but it is a
 * SYMBOL with a viewBox rather than a tiled pattern, so it needs no `rect` and
 * scales as one picture (see views/partials/inv_assets.ejs). It inherits
 * `color` from whatever surface it is on, which is how one drawing serves six
 * topic hues in two themes.
 */
.inv-art {
  position: absolute;
  inset: 0;
  /* Replaced element — `inset: 0` alone leaves it at the 300x150 intrinsic
     default. Same trap as .inv-field above. */
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  fill: none;
  stroke: currentColor;
}

/* ==================================================================
 * THE MASTHEAD
 * ==================================================================
 */
.inv-masthead {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-block: var(--inv-s8) var(--inv-s7);
  border-block-end: 2px solid var(--inv-accent);
  background: var(--inv-paper);
}

.inv-masthead .inv-wrap {
  position: relative;
  z-index: 1;
}

.inv-masthead-mark {
  width: 3.25rem;
  height: 3.25rem;
  display: block;
  object-fit: contain;
  margin-block-end: var(--inv-s4);
}

.inv-masthead-h {
  font-family: var(--inv-sans);
  font-size: clamp(2.9rem, 8vw, 5rem);
  line-height: 0.98;
  font-weight: 700;
  letter-spacing: -0.045em;
  margin: 0;
}

.inv-masthead-tag {
  font-size: clamp(1.125rem, 2.4vw, 1.45rem);
  font-style: italic;
  color: var(--inv-ink-2);
  margin: var(--inv-s3) 0 0;
  max-width: 34rem;
}

.inv-masthead-blurb {
  font-family: var(--inv-sans);
  font-size: 0.9375rem;
  font-weight: 400;
  line-height: 1.6;
  color: var(--inv-ink-3);
  margin: var(--inv-s4) 0 0;
  max-width: 38rem;
}

/* ==================================================================
 * THE LEGEND
 * ==================================================================
 *
 * Topics, drawn as the key to a plotted field: a coloured dot, the name in
 * sentence case, the count in figures. It replaced a row of pill chips set in
 * tracked uppercase — the label treatment that turns up on every generated
 * page regardless of subject, and which said nothing here either.
 *
 * THE DOT CARRIES THE COLOUR AND THE NAME STAYS INK. That is what lets six
 * hues coexist without the row becoming a toolbar: colour does exactly one
 * job — saying which series this is — and no readability is spent on it.
 */
.inv-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--inv-s3) var(--inv-s5);
  list-style: none;
  margin: var(--inv-s6) 0 0;
  padding: 0;
}

.inv-legend a {
  font-family: var(--inv-sans);
  font-size: 0.875rem;
  color: var(--inv-ink);
  text-decoration: none;
  display: inline-flex;
  align-items: baseline;
  gap: 0.5em;
  padding-block-end: 2px;
  border-block-end: 2px solid transparent;
}

.inv-legend a:hover {
  border-block-end-color: var(--inv-hue, var(--inv-accent));
}

.inv-dot {
  width: 0.5rem;
  height: 0.5rem;
  flex: none;
  border-radius: 50%;
  background: var(--inv-hue, var(--inv-accent));
  transform: translateY(-0.05em);
}

.inv-count {
  font-family: var(--inv-mono);
  font-size: 0.6875rem;
  color: var(--inv-ink-3);
  font-variant-numeric: tabular-nums;
}

/* The six hue slots. A wrapper sets --inv-hue and everything inside reads it —
   the same containment `.tess-N` gives the blog: one component, six colours,
   no duplicated rules. */
.inv-hue-1 { --inv-hue: var(--inv-h1); }
.inv-hue-2 { --inv-hue: var(--inv-h2); }
.inv-hue-3 { --inv-hue: var(--inv-h3); }
.inv-hue-4 { --inv-hue: var(--inv-h4); }
.inv-hue-5 { --inv-hue: var(--inv-h5); }
.inv-hue-6 { --inv-hue: var(--inv-h6); }

/* ==================================================================
 * PAGE HEADS (topic, tag, archive, search)
 * ==================================================================
 */
.inv-pagehead .inv-art {
  color: var(--inv-hue, var(--inv-accent));
  opacity: 0.22;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 92%);
  mask-image: linear-gradient(to bottom, #000 0%, transparent 92%);
}

/*
 * NO `overflow: hidden` HERE, and that is deliberate.
 *
 * The masthead carries it to clip its ground; this block does not need it,
 * because an outermost <svg> clips its own content to its own viewport by
 * default — `.inv-art` and `.inv-field` are already sized to this box and
 * cannot escape it.
 *
 * What DOES need to escape is the search page's suggestion panel, which is
 * absolutely positioned below the field and therefore below this block's
 * bottom edge. With `overflow: hidden` it was cut off after the first row: the
 * second suggestion and the result count were both inside the box and both
 * invisible.
 */
.inv-pagehead {
  position: relative;
  isolation: isolate;
  /*
   * z-index, because `isolation: isolate` made this a stacking context and a
   * stacking context at `z-index: auto` is painted in DOM order — so every
   * `.inv-story` below it, which is positioned for its stretched link, painted
   * OVER anything escaping this block. The symptom was the search page's
   * suggestion panel showing the results bleeding through its lower rows.
   *
   * 20 sits above the content and below `.inv-head` (50), which has to stay on
   * top of everything as the reader scrolls.
   */
  z-index: 20;
  padding-block: var(--inv-s7) var(--inv-s6);
  border-block-end: 2px solid var(--inv-hue, var(--inv-accent));
}

.inv-pagehead .inv-wrap {
  position: relative;
  z-index: 1;
}

.inv-pagehead-kicker {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5em;
  font-family: var(--inv-sans);
  font-size: 0.8125rem;
  color: var(--inv-ink-3);
  margin: 0 0 var(--inv-s3);
}

.inv-pagehead-h {
  font-family: var(--inv-sans);
  font-size: clamp(2.1rem, 5.5vw, 3.2rem);
  line-height: 1.04;
  font-weight: 700;
  letter-spacing: -0.035em;
  margin: 0;
}

.inv-pagehead-desc {
  font-size: 1.0625rem;
  color: var(--inv-ink-2);
  margin: var(--inv-s3) 0 0;
  max-width: 38rem;
}

.inv-pagehead-sub {
  font-family: var(--inv-mono);
  font-size: 0.75rem;
  color: var(--inv-ink-3);
  margin: var(--inv-s4) 0 0;
  font-variant-numeric: tabular-nums;
}

/* ==================================================================
 * IMAGE PLATES
 * ==================================================================
 *
 * EVERY image in this section is inside one of these, and that is the whole
 * answer to a cover that took over the page. A plate is a ratio-locked box
 * with `overflow: hidden`: nothing an author uploads can change the height of
 * the page, spill past the column, or eat the viewport, because the box is
 * sized before the image is known and the image is fitted into it.
 *
 * `aspect-ratio` alone is not enough. Layout shift comes from the browser not
 * knowing the box until the bytes arrive, so every <img> also carries real
 * width and height attributes — and the plate is what makes those safe to
 * state, because they no longer have to be the file's true dimensions, only
 * its ratio.
 *
 * THE FIT IS CHOSEN PER SIZE, AND THAT IS THE DELIBERATE PART:
 *
 *   .is-contain   the article cover and the lead. These covers are diagrams,
 *                 architecture sketches and screenshots of query plans —
 *                 pictures whose content runs to the edges. `cover` crops
 *                 exactly that away, so a 1500x2200 diagram would arrive as a
 *                 slice of its own middle. Letterboxed against the lattice it
 *                 is legible, and it is honest about the shape of the file.
 *   (default)     thumbnails. At 300px the image is an anchor, not
 *                 information, and a ragged grid of letterboxed thumbnails
 *                 reads as broken. `cover` is right here and only here.
 */
.inv-plate {
  position: relative;
  display: block;
  aspect-ratio: var(--plate-ratio, 16 / 10);
  overflow: hidden;
  background: var(--inv-paper-2);
  border: 1px solid var(--inv-rule);
  border-radius: 2px;
  isolation: isolate;
}

.inv-plate img {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* When a cover IS cropped, the top half is where the subject is — a diagram's
     title, a terminal's first lines, a face. Dead centre cuts all three. */
  object-position: 50% 35%;
}

.inv-plate.is-contain img {
  object-fit: contain;
  object-position: 50% 50%;
}

/* The lattice shows through wherever a contained image does not reach, so an
   unusual ratio reads as a plate with a picture in it rather than as a picture
   with grey bars beside it. */
.inv-plate .inv-field {
  opacity: 0.4;
  -webkit-mask-image: none;
  mask-image: none;
}

/*
 * NO COVER AT ALL.
 *
 * An essay with no featured image gets the contour art tinted by its topic,
 * not a grey box and not a placeholder icon. The cover is genuinely optional
 * here, so the empty state has to be a design rather than a gap — the same
 * argument blog_tile.ejs makes for falling back to the tessellation.
 */
.inv-plate.is-empty {
  background: var(--inv-paper-2);
  color: var(--inv-hue, var(--inv-accent));
}

.inv-plate.is-empty .inv-art {
  /* Heavier than the lattice ground — this is the ONLY thing in the frame, and
     at the lattice's weight it read as a smudge. Not much heavier: two
     coverless cards side by side are two large drawings, and the headline has
     to stay the loudest thing in the card. */
  opacity: 0.48;
}

/* ==================================================================
 * STORIES
 * ==================================================================
 */
.inv-story {
  position: relative;
}

.inv-story-h {
  font-family: var(--inv-sans);
  margin: 0;
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.15;
  font-size: 1.375rem;
}

.inv-story-link {
  color: var(--inv-ink);
  text-decoration: none;
}

.inv-story-link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
}

.inv-story:hover .inv-story-link {
  color: var(--inv-accent);
}

.inv-story-sf {
  margin: var(--inv-s3) 0 0;
  color: var(--inv-ink-2);
  font-size: 1.0625rem;
  line-height: 1.55;
  max-width: 42rem;
}

/*
 * THE META LINE IS A MEASUREMENT, NOT A SENTENCE.
 *
 * It used to be `6 September 2026 · 2 min` — a middot-joined string, which is
 * the meta treatment that appears on every generated card whatever the fields
 * mean. Here the two values are read off opposite ends of a hairline: when it
 * was published on the left, how long it takes on the right, the way a figure
 * is labelled against an axis. Same information, and the reading time is now
 * findable without parsing a sentence.
 */
.inv-story-meta {
  margin: var(--inv-s4) 0 0;
  padding-block-start: var(--inv-s2);
  border-block-start: 1px solid var(--inv-rule);
  font-family: var(--inv-mono);
  font-size: 0.75rem;
  color: var(--inv-ink-3);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--inv-s4);
  font-variant-numeric: tabular-nums;
}

/* The topic above a headline: the same legend mark as the masthead, so the
   two are recognisably one system. */
.inv-topic {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5em;
  font-family: var(--inv-sans);
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--inv-ink-2);
  text-decoration: none;
  margin-block-end: var(--inv-s3);
  position: relative;
  z-index: 4;
}

.inv-topic:hover {
  color: var(--inv-ink);
}

/* ---- the lead ---- */

/*
 * The one place this design raises its voice.
 *
 * Asymmetric on purpose: the cover takes seven columns and the argument takes
 * five, so the headline sets very large in a narrow measure — which is what
 * makes it read as a lead rather than as the first row of a list. The cover is
 * `contain`, so a wide architecture diagram arrives whole.
 */
.inv-lead {
  padding-block: var(--inv-s7);
  border-block-end: 1px solid var(--inv-rule);
}

.inv-story-lead {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--inv-s6) var(--inv-s7);
  align-items: center;
}

.inv-story-lead .inv-story-body {
  min-width: 0;
}

.inv-story-lead .inv-plate {
  --plate-ratio: 16 / 10;
}

.inv-story-lead .inv-story-h {
  font-size: clamp(2.1rem, 4.6vw, 3.4rem);
  line-height: 1.02;
  letter-spacing: -0.04em;
}

.inv-story-lead .inv-story-sf {
  font-size: 1.125rem;
  margin-block-start: var(--inv-s4);
}

/* ---- the grid variant: plate above the headline ---- */

/*
 * A FIXED COLUMN COUNT PER BREAKPOINT, not `auto-fit` and not `auto-fill`.
 *
 * Both auto- keywords get one of the two ends wrong here, and this grid sees
 * both ends: eleven cards on the front page, and exactly one on a topic page
 * that has published a single essay.
 *
 *   auto-fill  keeps its empty tracks, so two essays sit against the left edge
 *              with a phantom third column beside them — the failure blog.css
 *              documents for its category grid.
 *   auto-fit   collapses them, so the surviving 1fr track absorbs the whole
 *              row: ONE essay on a topic page became a 1100px card with a
 *              620px-tall cover, which is not a card, it is an accident.
 *
 * Fixed columns give a card the same size whatever the count, which is the
 * thing a card is for. A lone essay sits in the first of three tracks and
 * looks like every other essay on the section.
 */
.inv-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--inv-s7) var(--inv-s6);
}

@media (min-width: 40rem) {
  .inv-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 62rem) {
  .inv-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Wider than the row thumb's 4:3. A 3:2 plate on a card that grows to half the
   page is 330px of picture above a two-line headline, which inverts the
   hierarchy the card exists to state. */
.inv-story-grid .inv-plate {
  --plate-ratio: 16 / 9;
  margin-block-end: var(--inv-s4);
}

.inv-story-grid .inv-story-h {
  font-size: 1.25rem;
}

.inv-story-grid .inv-story-sf {
  font-size: 0.9375rem;
  margin-block-start: var(--inv-s2);
}

/* ---- the row variant: hairline-separated, plate beside ---- */

/*
 * The container for a dense scanning list — the archive, the search results.
 * Those pages are read differently from the front page: somebody is looking
 * for one thing among many, so the shape is a ruled column they can run an eye
 * down, with the cover as a small recognition anchor beside each row rather
 * than as the thing being scanned.
 */
.inv-list {
  padding-block: var(--inv-s5) var(--inv-s6);
}


/*
 * PLATE FIRST, and the columns are ordered to match.
 *
 * Written as `minmax(0, 1fr) 11rem` — text column first — the grid put the
 * plate in the wide track and the headline in the 11rem one, because the
 * partial emits the plate before the body and CSS grid places in SOURCE order.
 * An archive row came out as a 900px cover beside a headline broken across
 * four lines.
 *
 * The source order is the one that has to stay: on a phone this stacks, and
 * the picture belongs above the headline there. So the COLUMNS moved instead —
 * which also gives the conventional shape for a scanning list, a small
 * recognition thumbnail down the left edge with the text running beside it.
 */
.inv-story-row {
  display: grid;
  grid-template-columns: 11rem minmax(0, 1fr);
  gap: var(--inv-s6);
  align-items: start;
  padding-block: var(--inv-s6);
  border-block-end: 1px solid var(--inv-rule);
}

.inv-story-row:last-child {
  border-block-end: 0;
}

.inv-story-row .inv-plate {
  --plate-ratio: 4 / 3;
}

.inv-story-row .inv-story-body {
  min-width: 0;
}

.inv-story-compact {
  padding-block: var(--inv-s4);
}

.inv-story-compact .inv-story-h {
  font-size: 1.0625rem;
}

.inv-story-compact .inv-story-sf {
  font-size: 0.9375rem;
  margin-block-start: var(--inv-s1);
}

.inv-story-compact .inv-story-meta {
  margin-block-start: var(--inv-s2);
  padding-block-start: 0;
  border-block-start: 0;
}

/* ==================================================================
 * FRONT-PAGE SECTIONS
 * ==================================================================
 */
.inv-sec {
  padding-block: var(--inv-s7);
  border-block-end: 1px solid var(--inv-rule);
}

.inv-sec:last-of-type {
  border-block-end: 0;
}

/*
 * A section heading is set in the display face at reading size, with the datum
 * running off it to the right — the eye reads the word and then follows the
 * line across, which is the gesture the header and the masthead both make.
 * The old treatment was a tracked uppercase label: it announced a section
 * without giving it a shape.
 */
.inv-sec-h {
  display: flex;
  align-items: baseline;
  gap: var(--inv-s4);
  margin: 0 0 var(--inv-s6);
  font-family: var(--inv-sans);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--inv-ink);
}

.inv-sec-h::after {
  content: "";
  flex: 1;
  height: 2px;
  background: var(--inv-accent);
  opacity: 0.35;
  transform: translateY(-0.25em);
}

.inv-sec-note {
  font-family: var(--inv-sans);
  font-size: 0.8125rem;
  font-weight: 400;
  color: var(--inv-ink-3);
  white-space: nowrap;
}

/* Two columns where the content genuinely divides: a ranked list is a
   different shape from a topic index, and reads better beside one than under
   it. Collapses well before it gets tight. */
.inv-split {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--inv-s7) var(--inv-s8);
  align-items: start;
}

/* ---- most read: a real ranking, so real numerals ---- */

/*
 * Numbered markers are the commonest decoration on a generated page and are
 * almost never earned. They are earned here: this list IS an order, by reads,
 * and the numeral is the only thing that says so. Set in the mono face and
 * hung in the margin, so it reads as an index rather than as a bullet.
 */
.inv-rank {
  list-style: none;
  margin: 0;
  padding: 0;
}

.inv-rank-item {
  position: relative;
  display: grid;
  grid-template-columns: 2.25rem minmax(0, 1fr);
  gap: var(--inv-s3);
  align-items: baseline;
  padding-block: var(--inv-s4);
  border-block-start: 1px solid var(--inv-rule);
}

.inv-rank-item:first-child {
  border-block-start: 0;
  padding-block-start: 0;
}

.inv-rank-n {
  font-family: var(--inv-mono);
  font-size: 0.8125rem;
  color: var(--inv-accent);
  font-variant-numeric: tabular-nums;
}

.inv-rank-link {
  font-family: var(--inv-sans);
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.015em;
  color: var(--inv-ink);
  text-decoration: none;
}

.inv-rank-item:hover .inv-rank-link {
  color: var(--inv-accent);
}

.inv-rank-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.inv-rank-meta {
  grid-column: 2;
  margin: var(--inv-s1) 0 0;
  font-family: var(--inv-mono);
  font-size: 0.6875rem;
  color: var(--inv-ink-3);
  font-variant-numeric: tabular-nums;
}

/* ---- topic panels ---- */

/*
 * auto-fit, not auto-fill — the same call blog.css documents for its category
 * grid. auto-fill keeps its empty tracks, so three topics would sit against
 * one edge with half the row blank, which reads as a section that failed to
 * load rather than as a section with three topics in it.
 */
.inv-topics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--inv-s5);
}

.inv-topic-panel {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 9.5rem;
  padding: var(--inv-s5);
  border: 1px solid var(--inv-rule);
  border-block-start: 2px solid var(--inv-hue, var(--inv-accent));
  border-radius: 2px;
  background: var(--inv-paper-2);
  color: var(--inv-hue, var(--inv-accent));
  text-decoration: none;
}

.inv-topic-panel .inv-art {
  /* Faded from the bottom up, so the name and the count sit on clean ground
     while the artwork fills the head of the panel. */
  opacity: 0.65;
  -webkit-mask-image: linear-gradient(to top, transparent 8%, #000 62%);
  mask-image: linear-gradient(to top, transparent 8%, #000 62%);
}

.inv-topic-panel:hover {
  border-color: var(--inv-hue, var(--inv-accent));
}

.inv-topic-panel > * {
  position: relative;
  z-index: 1;
}

.inv-topic-n {
  font-family: var(--inv-sans);
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--inv-ink);
  margin: 0;
}

.inv-topic-c {
  font-family: var(--inv-mono);
  font-size: 0.6875rem;
  color: var(--inv-ink-3);
  margin: var(--inv-s2) 0 0;
  font-variant-numeric: tabular-nums;
}

/* ==================================================================
 * THE SUBSCRIBE BAND
 * ==================================================================
 *
 * The one inverted surface in the section, and the last thing on the front
 * page — so it is terminal punctuation as well as a call to act. It is here
 * because feeds are how this publication is actually read: this audience is
 * one of the few that still runs a reader, and daily.dev, Feedly and lobste.rs
 * consume exactly these two URLs.
 *
 * IT PAINTS FROM LITERALS ON PURPOSE. --inv-ink INVERTS between themes, so a
 * band written as `background: var(--inv-ink); color: var(--inv-paper)` would
 * be pale-on-dark in light mode and dark-on-pale in dark mode — it would stop
 * being an inverted band exactly when the page around it inverted. tokens.css
 * records the identical bug in the admin sidebar and the toast, where a
 * hardcoded white sat on a token that flipped underneath it.
 */
.inv-band {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  margin-block-start: var(--inv-s7);
  padding-block: var(--inv-s7);
  background: #0f171d;
  color: #e9eff3;
  border-block-start: 2px solid #4ecdf2;
}

.inv-band .inv-wrap {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--inv-s5);
}

.inv-band .inv-field {
  color: #4ecdf2;
  opacity: 0.22;
}

.inv-band-h {
  font-family: var(--inv-sans);
  font-size: clamp(1.25rem, 3vw, 1.75rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  margin: 0;
  color: #fff;
}

.inv-band-sub {
  font-family: var(--inv-sans);
  font-size: 0.9375rem;
  color: #a6b4be;
  margin: var(--inv-s2) 0 0;
  max-width: 34rem;
}

.inv-band-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--inv-s3);
}

/*
 * `!important` on the colour, and it is load-bearing rather than lazy: the
 * calculator side sets `a { color: inherit !important }` globally in
 * style.css, and although this section does not load that sheet, the same
 * pattern has bitten here once already — `:where(.inv-page a)` contributes
 * zero specificity precisely so component classes win, and a link inside an
 * inverted band is the one case where losing that fight is invisible in review
 * and unreadable on screen.
 */
.inv-band-link {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-family: var(--inv-sans);
  font-size: 0.9375rem;
  font-weight: 500;
  padding: 0.55em 1.1em;
  border: 1px solid #35434e;
  border-radius: 2px;
  color: #e9eff3 !important;
  text-decoration: none;
  white-space: nowrap;
}

.inv-band-link:hover {
  border-color: #4ecdf2;
  color: #4ecdf2 !important;
}

.inv-band-link.is-primary {
  background: #4ecdf2;
  border-color: #4ecdf2;
  color: #0f171d !important;
}

.inv-band-link.is-primary:hover {
  background: #8addf6;
  border-color: #8addf6;
  color: #0f171d !important;
}

/*
 * IN DARK MODE THE BAND LIFTS INSTEAD OF DROPPING.
 *
 * The literals above are what stop the text and the ground inverting out from
 * under each other — but "one inverted surface" is a description of a JOB, not
 * of a hex value, and the job is "a block that is visibly not the page". On a
 * #0b1014 page, a #0f171d band is four points of luminance away from its own
 * background and stops being a block at all.
 *
 * So the GROUND gets one value per theme and the text keeps its literals: pale
 * type is correct on both, which is the whole reason this surface could use
 * literals in the first place. Written out twice, for the three theme states
 * tokens.css documents — the OS-dark default is a different selector from the
 * explicit toggle, and writing only the second is the bug that gave the admin
 * sidebar a near-white ground under white text.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .inv-band {
    background: #1c2731;
    border-block-start-color: #4ecdf2;
  }
}

:root[data-theme="dark"] .inv-band {
  background: #1c2731;
  border-block-start-color: #4ecdf2;
}

.inv-empty {
  padding-block: var(--inv-s8);
  color: var(--inv-ink-2);
  font-size: 1.0625rem;
  max-width: 34rem;
}

.inv-article {
  padding-block-start: var(--inv-s8);
}

.inv-art-head {
  text-align: start;
}

.inv-art-h {
  font-family: var(--inv-sans);
  font-size: clamp(2.2rem, 6vw, 3.6rem);
  line-height: 1.02;
  font-weight: 700;
  letter-spacing: -0.042em;
  margin: 0;
  text-wrap: balance;
}

.inv-art-sf {
  font-size: clamp(1.125rem, 2.6vw, 1.375rem);
  font-style: italic;
  line-height: 1.5;
  color: var(--inv-ink-2);
  margin: var(--inv-s4) 0 0;
  text-wrap: pretty;
}

.inv-art-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55em;
  align-items: baseline;
  margin-block-start: var(--inv-s5);
  padding-block-start: var(--inv-s4);
  border-block-start: 2px solid var(--inv-accent);
  font-family: var(--inv-mono);
  font-size: 0.75rem;
  color: var(--inv-ink-3);
  font-variant-numeric: tabular-nums;
}

.inv-art-upd {
  color: var(--inv-accent);
}

/* ==================================================================
 * THE ARTICLE COVER
 * ==================================================================
 *
 * This was `max-width: 58rem` with `width:100%; height:auto` on the image —
 * i.e. the file decided how tall the page was. The covers on this section are
 * screenshots and diagrams at whatever ratio the tool that made them chose, so
 * a portrait one filled the viewport and pushed the first paragraph below the
 * fold, and every essay opened at a different height.
 *
 * It is a plate now: the box is fixed before the image is known.
 *
 *   · the ratio is 16/10, and `max-height` caps it in viewport units as well,
 *     so on a short laptop screen the cover cannot take the whole first
 *     screen no matter how wide the window is;
 *   · the fit is `contain`, because cropping a diagram removes the diagram —
 *     see the note on .inv-plate. The lattice fills whatever the image does
 *     not, so a tall screenshot reads as a plate with a picture in it;
 *   · it is the LCP element on almost every article, which is why the markup
 *     gives it `fetchpriority="high"`, eager loading and explicit dimensions,
 *     and why nothing here animates it in.
 */
.inv-art-cover {
  /* margin-block-start ONLY. Written as the `margin` shorthand it also set
     margin-inline to 0, which cancelled the `margin-inline: auto` this figure
     inherits from .inv-wrap-narrow — so the cover centred correctly in review
     and sat hard against the left edge of the window on the live page. */
  margin-block-start: var(--inv-s6);
}

.inv-art-cover .inv-plate {
  --plate-ratio: 16 / 10;
  max-height: min(58vh, 30rem);
}

.inv-art-cover figcaption {
  margin-block-start: var(--inv-s3);
  font-family: var(--inv-sans);
  font-size: 0.8125rem;
  color: var(--inv-ink-3);
  line-height: 1.5;
}

.inv-art-body {
  padding-block: var(--inv-s7) var(--inv-s6);
}

.inv-toc {
  margin-block-end: var(--inv-s6);
  padding: var(--inv-s4) var(--inv-s5);
  background: var(--inv-paper-2);
  border-inline-start: 2px solid var(--inv-rule-2);
  border-radius: 0 3px 3px 0;
}

.inv-toc > summary {
  font-family: var(--inv-sans);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: var(--inv-stamp);
  text-transform: uppercase;
  color: var(--inv-ink-3);
  cursor: pointer;
  list-style: none;
}

.inv-toc > summary::-webkit-details-marker {
  display: none;
}

.inv-toc > summary::after {
  content: " +";
  font-family: var(--inv-mono);
}

.inv-toc[open] > summary::after {
  content: " −";
}

.inv-toc ol {
  list-style: none;
  margin: var(--inv-s3) 0 0;
  padding: 0;
  font-family: var(--inv-sans);
  font-size: 0.875rem;
  line-height: 1.5;
}

.inv-toc li {
  margin-block-start: var(--inv-s2);
}

.inv-toc-l3 {
  padding-inline-start: var(--inv-s4);
  font-size: 0.8125rem;
}

.inv-toc a {
  color: var(--inv-ink-2);
  text-decoration: none;
}

.inv-toc a:hover {
  color: var(--inv-accent);
  text-decoration: underline;
}

.inv-toc a[aria-current="true"] {
  color: var(--inv-ink);
  font-weight: 600;
  box-shadow: -0.6em 0 0 -0.5em var(--inv-accent);
}

.inv-prose {
  font-family: var(--inv-serif);
  font-size: var(--inv-t-body);
  line-height: var(--inv-lh-body);
  color: var(--inv-ink);
}

/*
 * Section headings inside the article take the DISPLAY face, not the body
 * serif.
 *
 * Two voices doing two jobs: Source Serif carries the argument, Space Grotesk
 * labels it. The article's own H1 is set in the display face, so serif H2s
 * underneath it were a hierarchy that changed typeface halfway down the page
 * for no reason a reader could name. It also gives a scanning reader — which
 * is most of them, on a 3,000-word essay — a clear rung to stop on.
 */
.inv-prose h2,
.inv-prose h3,
.inv-prose h4 {
  font-family: var(--inv-sans);
  font-weight: 700;
  letter-spacing: -0.028em;
  line-height: 1.16;
  color: var(--inv-ink);
  scroll-margin-block-start: 5rem;
}

.inv-prose h2 {
  font-size: 1.75rem;
  margin-block: var(--inv-s7) var(--inv-s3);
}

.inv-prose h3 {
  font-size: 1.3125rem;
  margin-block: var(--inv-s6) var(--inv-s2);
}

.inv-prose h4 {
  font-size: 1.0625rem;
  margin-block: var(--inv-s5) var(--inv-s2);
}

.inv-prose p {
  margin-block: 0 var(--inv-s4);
}

.inv-prose a {
  color: var(--inv-accent);
}

.inv-prose blockquote {
  margin: var(--inv-s6) 0;
  padding-inline-start: var(--inv-s5);
  border-inline-start: 2px solid var(--inv-accent);
  font-style: italic;
  font-size: 1.25rem;
  line-height: 1.5;
  color: var(--inv-ink-2);
}

.inv-prose blockquote p:last-child {
  margin-block-end: 0;
}

.inv-prose code {
  font-family: var(--inv-mono);
  font-size: 0.855em;
  background: var(--inv-paper-2);
  border: 1px solid var(--inv-rule);
  border-radius: 3px;
  padding: 0.1em 0.34em;
}

.inv-prose pre {
  font-family: var(--inv-mono);
  font-size: 0.8125rem;
  line-height: 1.62;
  background: var(--inv-paper-2);
  border: 1px solid var(--inv-rule);
  border-radius: 3px;
  overflow-x: auto;
}

.inv-prose pre code {
  background: none;
  border: 0;
  padding: 0;
  font-size: inherit;
}

.inv-prose img {
  border-radius: 2px;
}

.inv-prose hr {
  border: 0;
  border-block-start: 1px solid var(--inv-rule);
  margin-block: var(--inv-s7);
}

.inv-prose ul,
.inv-prose ol {
  margin-block: 0 var(--inv-s4);
  padding-inline-start: var(--inv-s5);
}

.inv-prose li {
  margin-block-end: var(--inv-s2);
}

.inv-art-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--inv-s2);
  list-style: none;
  margin: var(--inv-s7) 0 0;
  padding-block-start: var(--inv-s5);
  border-block-start: 1px solid var(--inv-rule);
}

.inv-art-tags a {
  font-family: var(--inv-mono);
  font-size: 0.75rem;
  color: var(--inv-ink-3);
  text-decoration: none;
}

.inv-art-tags a:hover {
  color: var(--inv-accent);
}

.inv-share {
  margin-block-start: var(--inv-s6);
  padding-block-start: var(--inv-s5);
  border-block-start: 1px solid var(--inv-rule);
}

.inv-share-h {
  font-family: var(--inv-sans);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: var(--inv-stamp);
  text-transform: uppercase;
  color: var(--inv-ink-3);
  margin: 0 0 var(--inv-s3);
}

.inv-share-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--inv-s2);
  list-style: none;
  margin: 0;
  padding: 0;
}

.inv-share-list a,
.inv-copy {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  font-family: var(--inv-sans);
  font-size: 0.8125rem;
  color: var(--inv-ink-2);
  text-decoration: none;
  padding: 0.42em 0.85em;
  border: 1px solid var(--inv-rule);
  border-radius: 999px;
  background: none;
  cursor: pointer;
  line-height: 1.2;
}

.inv-share-list a:hover,
.inv-copy:not(:disabled):hover {
  color: var(--inv-ink);
  border-color: var(--inv-rule-2);
  background: var(--inv-paper-2);
}

.inv-copy:disabled {
  opacity: 0.45;
  cursor: default;
}

.inv-copy.is-done {
  color: var(--inv-accent);
  border-color: var(--inv-accent);
}

.inv-seq {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--inv-s5);
  padding-block: var(--inv-s6);
  border-block-start: 1px solid var(--inv-rule);
}

.inv-seq-item {
  text-decoration: none;
  color: var(--inv-ink);
}

.inv-seq-next {
  grid-column: 2;
  text-align: end;
}

.inv-seq-lbl {
  display: block;
  font-family: var(--inv-sans);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: var(--inv-stamp);
  text-transform: uppercase;
  color: var(--inv-ink-3);
  margin-block-end: var(--inv-s2);
}

.inv-seq-t {
  display: block;
  font-family: var(--inv-sans);
  font-size: 1.0625rem;
  line-height: 1.3;
  font-weight: 500;
  letter-spacing: -0.02em;
}

.inv-seq-item:hover .inv-seq-t {
  color: var(--inv-accent);
}

.inv-related {
  padding-block: var(--inv-s6) var(--inv-s8);
  border-block-start: 1px solid var(--inv-rule);
}

.inv-related-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0 var(--inv-s6);
}

.inv-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--inv-s4);
  padding-block: var(--inv-s6) var(--inv-s8);
  border-block-start: 1px solid var(--inv-rule);
  margin-block-start: var(--inv-s5);
}

.inv-pager-step {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  font-family: var(--inv-sans);
  font-size: 0.8125rem;
  color: var(--inv-ink-2);
  text-decoration: none;
  white-space: nowrap;
}

.inv-pager-step:hover {
  color: var(--inv-accent);
}

.inv-pager-step.is-off {
  opacity: 0.3;
  pointer-events: none;
}

.inv-pager-nums {
  display: flex;
  align-items: center;
  gap: var(--inv-s1);
  list-style: none;
  margin: 0;
  padding: 0;
}

.inv-pager-n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2rem;
  height: 2rem;
  padding-inline: 0.4em;
  font-family: var(--inv-mono);
  font-size: 0.8125rem;
  color: var(--inv-ink-2);
  text-decoration: none;
  border-radius: 3px;
  font-variant-numeric: tabular-nums;
}

.inv-pager-n:hover {
  background: var(--inv-paper-2);
  color: var(--inv-ink);
}

.inv-pager-n.is-current {
  background: var(--inv-ink);
  color: var(--inv-paper);
}

.inv-pager-gap {
  color: var(--inv-ink-3);
  padding-inline: var(--inv-s1);
}

/*
 * The search page's field, and the suggestion panel that hangs off it.
 *
 * The wrapper is the positioning context. The panel is `position: absolute`
 * over the results below rather than in the flow above them: this page already
 * shows results for the submitted query, and inserting a growing list above
 * them would move the thing the reader is reading on every keystroke.
 */
.inv-search-wrap {
  position: relative;
  max-width: 34rem;
  margin-block-start: var(--inv-s5);
  z-index: 10;
}

.inv-search-form {
  display: flex;
  gap: var(--inv-s2);
}

.inv-sugg {
  position: absolute;
  inset-inline: 0;
  inset-block-start: calc(100% + var(--inv-s2));
  background: var(--inv-paper);
  border: 1px solid var(--inv-rule-2);
  border-block-start: 2px solid var(--inv-accent);
  border-radius: 2px;
  box-shadow: 0 18px 44px -24px rgba(0, 0, 0, 0.4);
  overflow: hidden;
}

.inv-search-in {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--inv-serif);
  font-size: 1.0625rem;
  padding: 0.6em 0.85em;
  color: var(--inv-ink);
  background: var(--inv-paper);
  border: 1px solid var(--inv-rule-2);
  border-radius: 3px;
}

.inv-search-in::placeholder {
  color: var(--inv-ink-3);
}

.inv-search-go {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  font-family: var(--inv-sans);
  font-size: 0.875rem;
  padding: 0.6em 1.1em;
  color: var(--inv-paper);
  background: var(--inv-ink);
  border: 1px solid var(--inv-ink);
  border-radius: 3px;
  cursor: pointer;
  white-space: nowrap;
}

.inv-search-go:hover {
  background: var(--inv-accent);
  border-color: var(--inv-accent);
}

/* ==================================================================
 * THE SEARCH OVERLAY
 * ==================================================================
 *
 * The header's magnifier used to be a plain link to /invariant/search, and
 * that page only answered after a full round trip and a reload. On a
 * publication with a few dozen essays that is the wrong shape: the reader
 * almost always half-remembers a title, and what they need is to SEE the
 * titles narrow as they type.
 *
 * WHAT IT IS NOT: a replacement for /invariant/search. The overlay is an
 * enhancement over a link that still works — public/js/invariant.js only takes
 * the click over once it has attached, so with the script blocked, still
 * parsing, or served stale, the magnifier goes to the real page. Same rule as
 * every feature in blog.js, and the same reason: this section's readers arrive
 * from a link on a mid-range phone.
 *
 * Enter always submits to the real page, whatever is highlighted. A typeahead
 * whose Enter key does something different from its form is a typeahead people
 * learn not to trust.
 */
.inv-ovl {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: none;
  padding: clamp(1rem, 8vh, 6rem) var(--inv-s4) var(--inv-s4);
  background: color-mix(in srgb, var(--inv-paper) 88%, transparent);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.inv-ovl.is-open {
  display: block;
}

.inv-ovl-box {
  max-width: 40rem;
  margin-inline: auto;
  background: var(--inv-paper);
  border: 1px solid var(--inv-rule-2);
  border-block-start: 2px solid var(--inv-accent);
  border-radius: 2px;
  box-shadow: 0 24px 60px -30px rgba(0, 0, 0, 0.45);
  overflow: hidden;
}

.inv-ovl-bar {
  display: flex;
  align-items: center;
  gap: var(--inv-s3);
  padding: var(--inv-s3) var(--inv-s4);
  border-block-end: 1px solid var(--inv-rule);
}

.inv-ovl-bar .inv-i {
  color: var(--inv-ink-3);
  flex: none;
}

.inv-ovl-in {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--inv-sans);
  font-size: 1.0625rem;
  padding: 0.35em 0;
  color: var(--inv-ink);
  background: none;
  border: 0;
  outline: none;
}

.inv-ovl-in::placeholder {
  color: var(--inv-ink-3);
}

/*
 * The focus ring moves to the BAR, not the input.
 *
 * The overlay focuses this field the moment it opens, so the page-wide
 * `:focus-visible` rule drew a 2px accent box permanently around a borderless
 * input that already sits inside a bordered bar — two nested rectangles saying
 * the same thing. The indicator has to exist (it is the only thing telling a
 * keyboard user where they are), so it is not removed, it is relocated onto
 * the element a reader actually perceives as the control.
 */
.inv-ovl-in:focus-visible,
.inv-ovl-in:focus {
  outline: none;
}

.inv-ovl-bar:focus-within {
  background: var(--inv-accent-wash);
}

.inv-ovl-esc {
  font-family: var(--inv-mono);
  font-size: 0.6875rem;
  color: var(--inv-ink-3);
  border: 1px solid var(--inv-rule);
  border-radius: 3px;
  padding: 0.15em 0.5em;
  background: none;
  cursor: pointer;
  flex: none;
}

.inv-ovl-esc:hover {
  color: var(--inv-ink);
  border-color: var(--inv-rule-2);
}

.inv-ovl-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: min(56vh, 26rem);
  overflow-y: auto;
}

.inv-ovl-item a {
  display: block;
  padding: var(--inv-s3) var(--inv-s4);
  text-decoration: none;
  border-block-end: 1px solid var(--inv-rule);
}

.inv-ovl-list li:last-child a {
  border-block-end: 0;
}

/* Hover and keyboard selection are the SAME state, so arrowing down and moving
   the mouse cannot leave two rows looking chosen at once. */
.inv-ovl-item a:hover,
.inv-ovl-item.is-on a {
  background: var(--inv-accent-wash);
}

.inv-ovl-t {
  display: block;
  font-family: var(--inv-sans);
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.015em;
  color: var(--inv-ink);
}

.inv-ovl-m {
  display: block;
  margin-block-start: 0.2em;
  font-family: var(--inv-mono);
  font-size: 0.6875rem;
  color: var(--inv-ink-3);
}

/* The result count, and the empty and error states. All three are one row in
   the same place, because a box that changes height as it changes message is
   a box that moves the row under the reader's cursor. */
.inv-ovl-note {
  padding: var(--inv-s3) var(--inv-s4);
  font-family: var(--inv-sans);
  font-size: 0.8125rem;
  color: var(--inv-ink-3);
  border-block-start: 1px solid var(--inv-rule);
}

.inv-ovl-note b {
  color: var(--inv-ink-2);
  font-weight: 500;
}

@media (prefers-reduced-motion: no-preference) {
  .inv-ovl.is-open .inv-ovl-box {
    animation: inv-ovl-in 0.16s ease-out;
  }
}

/* The one non-hover animation on the section, and it answers an action: the
   box arrives from where the button is, so it is obvious what opened. */
@keyframes inv-ovl-in {
  from {
    opacity: 0;
    transform: translateY(-6px);
  }
}

.inv-404 {
  padding-block: var(--inv-s9);
  text-align: center;
}

.inv-404-code {
  font-family: var(--inv-mono);
  font-size: 0.8125rem;
  letter-spacing: var(--inv-stamp);
  color: var(--inv-ink-3);
  margin: 0;
}

.inv-404-h {
  font-family: var(--inv-sans);
  font-size: clamp(1.9rem, 5vw, 2.75rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  margin: var(--inv-s3) 0 0;
}

.inv-404-sub {
  color: var(--inv-ink-2);
  margin: var(--inv-s3) auto 0;
  max-width: 28rem;
}

.inv-404-links,
.inv-404-topics {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--inv-s4);
  list-style: none;
  margin: var(--inv-s6) 0 0;
  padding: 0;
  font-family: var(--inv-sans);
  font-size: 0.9375rem;
}

.inv-404-topics-h {
  font-family: var(--inv-sans);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: var(--inv-stamp);
  text-transform: uppercase;
  color: var(--inv-ink-3);
  margin: var(--inv-s7) 0 0;
}

.inv-404-topics {
  margin-block-start: var(--inv-s3);
  font-size: 0.875rem;
}

.inv-foot {
  border-block-start: 1px solid var(--inv-rule);
  background: var(--inv-paper-2);
  margin-block-start: var(--inv-s8);
}

.inv-foot-in {
  max-width: var(--inv-wrap);
  margin-inline: auto;
  padding: var(--inv-s7) var(--inv-s5) var(--inv-s6);
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: var(--inv-s6);
}

.inv-foot-blurb {
  font-family: var(--inv-sans);
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--inv-ink-3);
  margin: var(--inv-s3) 0 0;
  max-width: 28rem;
}

/* The brand's accounts (views/partials/socialLinks.ejs). Squared, not round:
   nothing on this section has a large radius — it is instrumentation, not
   stationery — and a circle here would be the only one on the page.
   --inv-accent is the TEXT-weight accent (5.31:1); --inv-accent-bright is
   2.55:1 and is for strokes only, which is exactly what the border is. */
.inv-foot-social {
  list-style: none;
  display: flex;
  gap: var(--inv-s2);
  padding: 0;
  margin: var(--inv-s3) 0 0;
}

.inv-foot-social a {
  display: inline-grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border: 1px solid var(--inv-rule);
  color: var(--inv-ink-2);
  transition: color 0.15s, border-color 0.15s;
}

.inv-foot-social a:hover,
.inv-foot-social a:focus-visible {
  color: var(--inv-accent);
  border-color: var(--inv-accent-bright);
}

.inv-foot-social svg {
  width: 1rem;
  height: 1rem;
}

.inv-foot-feeds {
  font-family: var(--inv-mono);
  font-size: 0.75rem;
  margin: var(--inv-s4) 0 0;
  display: flex;
  gap: 0.55em;
  color: var(--inv-ink-3);
}

.inv-foot-h {
  font-family: var(--inv-sans);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: var(--inv-stamp);
  text-transform: uppercase;
  color: var(--inv-ink-3);
  margin: 0 0 var(--inv-s3);
}

.inv-foot-col ul {
  list-style: none;
  margin: 0;
  padding: 0;
  font-family: var(--inv-sans);
  font-size: 0.875rem;
  line-height: 1.5;
}

.inv-foot-col li {
  margin-block-end: var(--inv-s2);
}

.inv-foot-col a {
  color: var(--inv-ink-2);
  text-decoration: none;
}

.inv-foot-col a:hover {
  color: var(--inv-accent);
}

.inv-foot-base {
  max-width: var(--inv-wrap);
  margin-inline: auto;
  padding: var(--inv-s4) var(--inv-s5) var(--inv-s6);
  border-block-start: 1px solid var(--inv-rule);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--inv-s3);
  font-family: var(--inv-sans);
  font-size: 0.75rem;
  color: var(--inv-ink-3);
}

.inv-foot-base p {
  margin: 0;
}

.inv-foot-base a {
  color: var(--inv-ink-3);
  text-decoration: none;
}

.inv-foot-base a:hover {
  color: var(--inv-accent);
}

@media (max-width: 62rem) {
  .inv-foot-in {
    grid-template-columns: 1fr 1fr;
  }
  .inv-foot-brand {
    grid-column: 1 / -1;
  }

  /* The lead stacks with the COVER FIRST, which is the reverse of the source
     order. On a phone the picture is what stops a thumb, and the headline is
     immediately under it either way; on a desktop the argument leads and the
     picture supports it. `order` is the right tool precisely because the DOM
     order is the reading order for a screen reader and must not change. */
  .inv-story-lead {
    grid-template-columns: 1fr;
    gap: var(--inv-s5);
  }

  .inv-story-lead .inv-story-body {
    order: 2;
  }

  .inv-split {
    grid-template-columns: 1fr;
    gap: var(--inv-s7);
  }
}

@media (max-width: 48rem) {
  .inv-burger {
    display: inline-flex;
  }

  .inv-nav {
    display: none;
    position: absolute;
    inset-inline: 0;
    inset-block-start: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--inv-s3) var(--inv-s5) var(--inv-s5);
    background: var(--inv-paper);
    border-block-end: 1px solid var(--inv-rule);
  }

  .inv-nav.is-open {
    display: flex;
  }

  .inv-head-in {
    position: relative;
  }

  .inv-nav-list {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    overflow: visible;
  }

  .inv-nav-link {
    display: block;
    padding-block: var(--inv-s3);
    border-block-end: 1px solid var(--inv-rule);
    font-size: 0.9375rem;
  }

  .inv-nav-link.is-active {
    border-block-end-color: var(--inv-rule);
    color: var(--inv-accent);
  }

  .inv-tools {
    margin-block-start: var(--inv-s3);
  }

  .inv-toc {
    margin-inline: calc(var(--inv-s5) * -1);
    border-radius: 0;
  }

  .inv-seq {
    grid-template-columns: 1fr;
  }

  .inv-seq-next {
    grid-column: 1;
    text-align: start;
  }

  .inv-foot-in {
    grid-template-columns: 1fr;
  }

  .inv-pager-nums {
    display: none;
  }

  /* The row's thumbnail goes above the headline rather than beside it: an
     11rem plate next to a 20rem column leaves neither enough room, and the
     grid variant is already the shape that works at this width. */
  .inv-story-row {
    grid-template-columns: 1fr;
    gap: var(--inv-s4);
  }

  .inv-story-row .inv-plate {
    --plate-ratio: 16 / 9;
  }

  .inv-band .inv-wrap {
    display: block;
  }

  .inv-band-links {
    margin-block-start: var(--inv-s5);
  }
}

@media (prefers-reduced-motion: reduce) {
  .inv-page *,
  .inv-page *::before,
  .inv-page *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

@media print {
  .inv-head,
  .inv-foot,
  .inv-share,
  .inv-seq,
  .inv-related,
  .inv-progress,
  .inv-toc,
  [data-ad-spot] {
    display: none !important;
  }

  .inv-page {
    background: #fff;
    color: #000;
    font-size: 11pt;
  }

  .inv-art-body,
  .inv-wrap-narrow {
    max-width: none;
    padding-inline: 0;
  }

  .inv-prose a::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    color: #555;
  }
}

/*
 * ==================================================================
 * RESPONSES
 * ==================================================================
 *
 * The comment thread. Same Comment model, same routes and the same moderation
 * queue as the Arabic blog — see views/partials/inv_comments.ejs — but its own
 * furniture, because blog.css is not loaded on this layout and its
 * `.comment-*` rules are drawn for an RTL mosaic.
 *
 * ONE PREFIX, `inv-c`, AND NO BORROWED COMPONENT NAMES. `.inv-field` is
 * already the lattice backdrop above; a form label wearing it would be pulled
 * out of flow and painted over the essay, silently. Everything the form needs
 * is defined here rather than reaching for a shared input/button that does not
 * exist in this stylesheet.
 *
 * Nothing here sets `direction`. Every comment body carries dir="auto" from
 * the partial, and CSS direction beats an HTML attribute — the exact way the
 * blog's old `.post-content p { direction: rtl !important }` discarded the
 * server's decision. Logical properties only.
 */
.inv-comments {
  margin-block: var(--inv-s7);
  padding-block-start: var(--inv-s6);
  border-block-start: 1px solid var(--inv-rule);
}

/* The count sits beside the heading rather than inside its accessible name —
   "Responses 12" announced as a heading is worse than "Responses" with a
   number next to it. */
.inv-comments-n {
  font-family: var(--inv-mono);
  font-size: 0.8em;
  font-weight: 400;
  color: var(--inv-ink-3);
  margin-inline-start: 0.35em;
}

.inv-comments-shut,
.inv-comments-empty {
  color: var(--inv-ink-3);
  font-family: var(--inv-sans);
  font-size: 0.9375rem;
  display: flex;
  align-items: center;
  gap: var(--inv-s2);
  margin-block: var(--inv-s5);
}

/* ---- the form ---------------------------------------------------- */

.inv-cform {
  display: grid;
  gap: var(--inv-s3);
  margin-block: var(--inv-s5) var(--inv-s6);
  padding: var(--inv-s5);
  background: var(--inv-paper-2);
  border: 1px solid var(--inv-rule);
}

.inv-cfield {
  display: grid;
  gap: var(--inv-s1);
}

.inv-cfield-l {
  font-family: var(--inv-sans);
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--inv-ink-2);
}

.inv-cfield-l abbr {
  color: var(--inv-accent);
  text-decoration: none;
}

.inv-cinput,
.inv-ctextarea {
  font: inherit;
  font-family: var(--inv-sans);
  font-size: 0.9375rem;
  color: var(--inv-ink);
  background: var(--inv-paper);
  border: 1px solid var(--inv-rule-2);
  border-radius: 0;
  padding: var(--inv-s3);
  width: 100%;
  /* A flex/grid item's default min-width is its content, so without this a
     textarea's intrinsic width wins and the field overflows its panel on a
     phone. */
  min-width: 0;
}

.inv-ctextarea {
  resize: vertical;
  min-height: 6.5rem;
  line-height: 1.55;
}

.inv-cinput:focus-visible,
.inv-ctextarea:focus-visible {
  outline: 2px solid var(--inv-accent);
  outline-offset: -1px;
  border-color: var(--inv-accent);
}

/* Monospace, so the number does not shift the "/ 2000" beside it as it grows.
   Same reason the blog has a .num class. */
.inv-cform-count {
  font-family: var(--inv-mono);
  font-size: 0.75rem;
  color: var(--inv-ink-3);
  text-align: end;
  margin: 0;
}

.inv-cform-count.is-near {
  color: var(--inv-accent);
}

.inv-cform-guest {
  display: grid;
  gap: var(--inv-s3);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}

.inv-cform-note {
  font-family: var(--inv-sans);
  font-size: 0.8125rem;
  color: var(--inv-ink-3);
  margin: 0;
}

.inv-cbtn {
  font-family: var(--inv-sans);
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--inv-paper);
  background: var(--inv-accent);
  border: 1px solid var(--inv-accent);
  padding: var(--inv-s3) var(--inv-s5);
  cursor: pointer;
  justify-self: start;
  transition: background 0.15s, border-color 0.15s;
}

.inv-cbtn:hover {
  background: var(--inv-accent-2);
  border-color: var(--inv-accent-2);
}

.inv-cbtn-sm {
  font-size: 0.8125rem;
  padding: var(--inv-s2) var(--inv-s4);
}

.inv-cbtn-ghost {
  color: var(--inv-ink-2);
  background: transparent;
  border-color: var(--inv-rule-2);
}

.inv-cbtn-ghost:hover {
  background: transparent;
  color: var(--inv-ink);
  border-color: var(--inv-ink-3);
}

/* ---- the thread -------------------------------------------------- */

.inv-clist,
.inv-c-replies {
  list-style: none;
  padding: 0;
  margin: 0;
}

.inv-c {
  display: flex;
  gap: var(--inv-s4);
  padding-block: var(--inv-s5);
  border-block-start: 1px solid var(--inv-rule);
}

.inv-clist > .inv-c:first-child {
  border-block-start: 0;
}

.inv-c-avatar {
  flex: none;
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  overflow: hidden;
  background: var(--inv-accent-wash);
  color: var(--inv-accent);
  font-family: var(--inv-sans);
  font-size: 0.9375rem;
  font-weight: 700;
}

.inv-c-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* `min-width: 0` is what lets a long unbroken URL in a response wrap instead
   of stretching this flex item and putting a horizontal scrollbar on the whole
   page — a flex item's default min-width is auto, i.e. its content. */
.inv-c-body {
  min-width: 0;
  flex: 1;
}

.inv-c-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--inv-s2);
  font-family: var(--inv-sans);
  font-size: 0.8125rem;
  margin: 0 0 var(--inv-s2);
}

.inv-c-who {
  font-weight: 700;
  color: var(--inv-ink);
}

.inv-c-badge {
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--inv-accent);
  border: 1px solid var(--inv-rule-2);
  padding: 0 0.35em;
}

.inv-c-when {
  color: var(--inv-ink-3);
}

/* The body is the SERIF, like the essay above it: a response is prose, and
   setting it in the UI grotesque would make it read as interface chrome rather
   than as something a person wrote. */
.inv-c-text {
  font-family: var(--inv-serif);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--inv-ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.inv-c-edit {
  display: grid;
  gap: var(--inv-s3);
  margin-block-start: var(--inv-s3);
}

.inv-c-edit-act {
  display: flex;
  gap: var(--inv-s2);
}

.inv-c-act {
  display: flex;
  align-items: center;
  gap: var(--inv-s4);
  margin-block-start: var(--inv-s3);
  font-family: var(--inv-sans);
  font-size: 0.8125rem;
}

.inv-c-like,
.inv-c-edit-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--inv-s1);
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--inv-ink-3);
  cursor: pointer;
}

.inv-c-like:hover,
.inv-c-edit-btn:hover {
  color: var(--inv-accent);
}

/* Signed out the count is a <span>, not a button: nothing to hover, nothing to
   click, and no cursor promising otherwise. */
.inv-c-like.is-static {
  cursor: default;
}

.inv-c-like.is-static:hover {
  color: var(--inv-ink-3);
}

/* Liked: the glyph FILLS. One symbol, two states — swapping to a second icon
   is how the admin's theme toggle once drew both at once. */
.inv-c-like.is-on {
  color: var(--inv-accent);
}

.inv-c-like.is-on svg {
  fill: currentColor;
}

.inv-c-likes {
  font-family: var(--inv-mono);
  font-size: 0.75rem;
}

.inv-c-replies {
  margin-block-start: var(--inv-s4);
  padding-inline-start: var(--inv-s5);
  border-inline-start: 2px solid var(--inv-rule);
}

.inv-c-reply {
  padding-block: var(--inv-s4);
  border-block-start: 0;
}

.inv-c-reply .inv-c-avatar {
  width: 1.75rem;
  height: 1.75rem;
  font-size: 0.8125rem;
}

.inv-c-reply .inv-c-text {
  font-size: 1rem;
}

.inv-cpager {
  display: flex;
  align-items: center;
  gap: var(--inv-s4);
  margin-block-start: var(--inv-s5);
  font-family: var(--inv-sans);
  font-size: 0.875rem;
}

.inv-cpager-at {
  color: var(--inv-ink-3);
  margin-inline: auto;
}

/*
 * ==================================================================
 * FLASH
 * ==================================================================
 *
 * The one surface that tells a reader their response was received. Without it
 * the form posts, redirects back, and the page looks byte-identical to the one
 * they submitted from — so a moderated comment reads as a comment that was
 * silently thrown away, and they write it again.
 *
 * Two literals per state rather than a token pair, and DELIBERATELY: these are
 * tinted panels whose text must stay legible against their own wash in both
 * themes, and the ink/paper tokens invert underneath them. That is the bug
 * tokens.css records in the admin sidebar and the toast.
 */
.inv-flash {
  margin-block: var(--inv-s5) 0;
  padding: var(--inv-s4);
  border-inline-start: 3px solid;
  font-family: var(--inv-sans);
  font-size: 0.9375rem;
}

.inv-flash-ok {
  color: #0a4a3a;
  background: #e3f5ef;
  border-color: #0f9d76;
}

.inv-flash-err {
  color: #6b1d1d;
  background: #fbe9e9;
  border-color: #c0392b;
}

/*
 * THE FLASH PANELS IN DARK MODE — ALL THREE THEME STATES.
 *
 * tokens.css records the bug this avoids: the admin sidebar was written with
 * only the `[data-theme="dark"]` selector, so anyone on a dark OS who never
 * touched the toggle — which is the DEFAULT — got the light rules. Three
 * states, not two: bare :root, the prefers-color-scheme default guarded
 * against an explicit light choice, and the explicit dark choice.
 *
 * These stay literals rather than tokens for the reason stated where they are
 * defined: they are tinted panels whose ink must hold against their own wash,
 * and --inv-ink/--inv-paper invert underneath them.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .inv-flash-ok {
    color: #a9e8d3;
    background: #10322a;
    border-color: #2fbb93;
  }

  :root:not([data-theme="light"]) .inv-flash-err {
    color: #f3b9b4;
    background: #38191a;
    border-color: #e05c4e;
  }
}

:root[data-theme="dark"] .inv-flash-ok {
  color: #a9e8d3;
  background: #10322a;
  border-color: #2fbb93;
}

:root[data-theme="dark"] .inv-flash-err {
  color: #f3b9b4;
  background: #38191a;
  border-color: #e05c4e;
}
