/* Ioskeley Mono, subset and vendored in fonts/ — see fonts/README.md. Four faces
   only: 400/700 upright and italic. Anything else is synthesised by the browser. */
@font-face {
  font-family: "Ioskeley Mono";
  src: url("/fonts/IoskeleyMono-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Ioskeley Mono";
  src: url("/fonts/IoskeleyMono-Italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Ioskeley Mono";
  src: url("/fonts/IoskeleyMono-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Ioskeley Mono";
  src: url("/fonts/IoskeleyMono-BoldItalic.woff2") format("woff2");
  font-weight: 700;
  font-style: italic;
  font-display: swap;
}

/* Monokai Pro Light (Filter Sun) for light, Monokai Pro (Filter Ristretto) for
   dark — the warm/rose pair from the same family, so the two themes share a cast.
   Names in comments are the upstream palette slots. */
:root {
  color-scheme: light;
  --fg: #29242a;   /* sun: text */
  --bg: #faf4f2;   /* sun: background */
  --accent: #7058be; /* sun: accent6 */
  --line: #a59fa0; /* sun: dimmed3 */
  --mono: "Ioskeley Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}

[data-theme="dark"] {
  color-scheme: dark;
  --fg: #fff1f3;   /* ristretto: text */
  --bg: #2c2525;   /* ristretto: background */
  --accent: #a8a9eb; /* ristretto: accent6 */
  --line: #72696a; /* ristretto: dimmed3 */
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--mono);
  color: var(--fg);
  background: var(--bg);
  line-height: 1.5;
}

.grid {
  display: grid;
  /* Side columns need a floor, not just leftovers — the sidebar carries real
     text now, and at 1fr it collapsed to a couple of characters per line. The
     centre column yields first; it is capped at 64rem anyway. */
  grid-template-columns: minmax(12rem, 1fr) minmax(0, 64rem) minmax(12rem, 1fr);
  grid-template-rows: auto 1fr auto;
  min-height: 100vh;
}

.grid > * {
  padding: 1.25rem;
  border: 1px dashed var(--line);
  margin: -1px 0 0 -1px;
  /* Grid items default to min-width:auto, i.e. they refuse to shrink below
     their content. Without this, an overflow-x container inside a cell (the
     heatmap) widens the whole page instead of scrolling itself. */
  min-width: 0;
}

.grid > *:nth-child(3n+1) { margin-left: 0; }

header.brand { grid-column: 1; grid-row: 1; }
/* Two children: the links, and #ticker pushed to the right edge by its own auto
   margin. Baseline alignment, not centre — the ticker is smaller type and should
   sit on the same line as the links, not float in the middle of it. */
nav {
  grid-column: 2;
  grid-row: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 1rem;
}
.theme-toggle { grid-column: 3; grid-row: 1; display: flex; align-items: center; justify-content: flex-end; }

.sidebar { grid-column: 1; grid-row: 2; }
.content { grid-column: 2; grid-row: 2; }
.toc     { grid-column: 3; grid-row: 2; }

.corner-bl { grid-column: 1; grid-row: 3; }
footer     { grid-column: 2; grid-row: 3; text-align: center; }
.corner-br { grid-column: 3; grid-row: 3; }

/* body is already var(--mono) now — these only carry weight/size.
   The brand is the mark and the wordmark on one line, so the anchor is the flex
   row that holds them together and both are inside the same link. */
.brand a {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--fg);
}

/* Height only, so the drawing keeps its own square ratio. It is sized in rem
   against the wordmark rather than in px: the two should stay in step if the
   brand's type ever changes. The file is 400px square, far bigger than it ever
   renders — it is the favicon too, and that wants the pixels. */
.brand-mark { height: 1.75rem; width: auto; }
.nav-links { display: flex; flex-wrap: wrap; gap: 1rem; }
/* Scoped to .nav-links, not `nav a` — the ticker's link is in this cell too, and
   dimming it here as well as on .ticker compounded to about half strength. */
.nav-links a { text-decoration: none; color: var(--fg); opacity: 0.7; }
.nav-links a:hover { opacity: 1; color: var(--accent); }
/* The page you are on. Full strength and a dashed underline, matching the rules
   the rest of the grid is drawn with — colour is left alone deliberately, so
   this reads as "you are here" and not as the accent below, which means "this
   line is about that". Both can be on one entry at once. */
.nav-links a[aria-current="page"] {
  opacity: 1;
  text-decoration: underline dashed;
  text-underline-offset: 0.35em;
}
/* ticker.mjs lights the entry whose section the ticker is showing. Same look as
   :hover deliberately — it says "this line is about that", which is exactly what
   pointing at the link says. */
.nav-links a.nav-lit { opacity: 1; color: var(--accent); }
.sidebar, .toc, footer { font-size: 0.85rem; opacity: 0.7; }

/* h1 is the page's lead heading, one per page. h2 labels a top-level region — a
   sidebar panel, or a section of page content. h3 labels a panel nested inside
   one of those. h4 and h5 exist only inside a post body, where org.clj maps an
   org heading of level n to h(n+1) and the file decides how deep that goes.
   Everything below h1 renders identically: the level carries the outline, and
   only h1 is set apart by type. Do not reach for a deeper one to make something
   look smaller — it will not.
   Sizes are in rem, not em, so they land identically inside the 0.85rem
   sidebars and the full-size content column.
   None of them take a top margin, deliberately — .stat-panel does its own
   spacing, and a margin here would collapse out through it and break the toc's
   top alignment.
   One rule for all three, with only font-size differing. The line box is an
   absolute 1.5rem rather than a ratio, so a heading's height does not track its
   type size and the dashed rule lands at the same y in all three columns —
   letting it scale with font-size dropped the h1's rule 8px below the sidebars'. */
h1, h2, h3, h4, h5, .panel-title {
  font-size: 0.85rem;
  font-weight: 700;
  line-height: 1.5rem;
  margin: 0 0 0.75rem;
  padding-bottom: 0.3rem;
  border-bottom: 1px dashed var(--line);
}

h1 { font-size: 1.15rem; }

/* The home page's ascii art, and the only thing on that page.

   The stage fills the content cell and centres the art in both directions, so
   the picture sits in the middle of the page rather than at the top of it.
   `min-height: 100%` rather than `height`: the art is taller than the cell on a
   short window, and a fixed height would clip it instead of growing the row.

   Sizing is the point of the container: 100cqw is the width of the middle
   column, and --ascii-cols is how many characters the picture is across, which
   build.clj measures off the file, so the type size that fits is that width
   divided by that many advances.

   The overflow lives on the pre, not here, deliberately: a scroll container's
   content box excludes its scrollbar, so putting it on the container itself
   makes the scrollbar shrink 100cqw, which shrinks the type, which removes the
   scrollbar, which grows it back. */
.ascii-stage {
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  min-height: 100%;
}

/* line-height 1 because the art was drawn on a character grid — the site's 1.5
   would stretch it vertically into something half again as tall as intended.
   Ioskeley Mono's advance measures 0.602em; the 0.61 is that rounded *up*, so
   the art lands a whisker inside the column rather than a whisker outside it —
   at the exact figure it overflowed by a fraction of a character, which is
   enough to raise a scrollbar. Measure again if the typeface ever changes.
   overflow-x is the safety net for the moment before the webfont lands, when
   the fallback's own advance can push the last column or two out; overflow-y is
   pinned because otherwise a horizontal scrollbar eats into the height and
   summons a vertical one too. */
.ascii-art {
  margin: 0;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  font-size: calc(100cqw / (var(--ascii-cols) * 0.61));
  line-height: 1;
  white-space: pre;
}

/* Where the picture came from. Sized and dimmed like the side columns, so it
   reads as a caption under the art rather than as the page's one piece of
   content. */
.ascii-credit {
  font-size: 0.85rem;
  opacity: 0.7;
  color: inherit;
  text-decoration: none;
}
.ascii-credit:hover { opacity: 1; color: var(--accent); }

/* The ticker, at the right of the nav line on every page. It stays on one line —
   the top row's three cells share a height, so a ticker that wrapped would
   change the height of the whole row every ten seconds as it rotates. Hence
   nowrap with an ellipsis, and min-width: 0 so a long title shrinks the box
   rather than widening the cell. */
.ticker {
  margin-left: auto;
  min-width: 0;
  font-size: 0.85rem;
  opacity: 0.7;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity 200ms ease;
}
.ticker a { color: inherit; text-decoration: none; }
.ticker a:hover { color: var(--accent); }
/* ticker.mjs swaps the contents while this is on, so a rotation is a fade out
   and back rather than a jump cut. After .ticker, which it has to beat on equal
   specificity. The 200ms here is the fade-ms in ticker.cljs — change both. */
.ticker-out { opacity: 0; }

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

/* --- /music stats panels. Every .stat-body is filled by music-stats.mjs. --- */

.range-toggle { display: flex; gap: 0.5rem; margin: 2rem 0 1.5rem; }
.range-btn {
  font: inherit;
  font-size: 0.85rem;
  color: inherit;
  background: none;
  border: 1px dashed var(--line);
  padding: 0.2rem 0.7rem;
  cursor: pointer;
}
.range-btn:hover { color: var(--accent); }
.range-btn[aria-pressed="true"] {
  border-style: solid;
  border-color: var(--accent);
  color: var(--accent);
}

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 1rem;
}
.tile-value { font-size: 1.75rem; font-weight: 700; line-height: 1.1; }
/* The "most played" tile holds a name, not a figure, so it is the one that can
   wrap — at the same size as the figures, which is what makes the row read as
   three of a kind. A long name costs the row height rather than the match. */
.tile-text .tile-value { overflow-wrap: anywhere; }
.tile-label { font-size: 0.8rem; opacity: 0.7; }

.stat-columns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.5rem;
}
.stat-panel { margin-top: 2rem; }
.stat-note { font-size: 0.8rem; opacity: 0.6; margin: 0.5rem 0 0; }

/* Label and count share the top row; the bar is a third row spanning both, so
   long titles wrap without pushing the count around. */
.bars { list-style: none; margin: 0; padding: 0; font-size: 0.85rem; }
.bars li {
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: 0.5rem;
  margin-bottom: 0.6rem;
}
.bar-label { grid-column: 1; overflow-wrap: anywhere; }
.bar-count { grid-column: 2; opacity: 0.6; }
.bar {
  grid-column: 1 / -1;
  height: 0.4rem;
  margin-top: 0.2rem;
  border: 1px solid var(--line);
}
.bar-fill { height: 100%; background: var(--accent); opacity: 0.75; }

/* Transposed: 7 day columns across, 24 hour rows down, to suit the tall narrow
   right column. Cells are square, so the grid's width sets its height — cap the
   width (not the cell) or aspect-ratio stops holding, and on mobile, where this
   cell goes full-bleed, 7 unbounded columns would make it 1000px tall. */
.heatmap-wrap { overflow-x: auto; }
.heatmap {
  display: grid;
  grid-template-columns: auto repeat(7, minmax(0.55rem, 1fr));
  gap: 2px;
  max-width: 12rem;
}
.hm-cell {
  aspect-ratio: 1;
  background: var(--line);
  opacity: 0.15;
}
.hm-hour, .hm-day { font-size: 0.65rem; opacity: 0.6; line-height: 1.2; }
.hm-day { text-align: center; }
.hm-hour { padding-right: 0.3rem; text-align: right; }

/* The right column reads right-to-left against the page edge, and starts at the
   top like the left sidebar — .stat-panel's 2rem top margin is for stacking
   panels in the middle column, so the first one here has to lose it. The grid
   is a block with a max-width, so text-align cannot move it; auto margin does. */
.toc { text-align: right; }
.toc .stat-panel { margin-top: 0; }
.toc .heatmap { margin-left: auto; }

/* The /music sidebar. #recent-listens holds a placeholder string until
   recent-listens.mjs swaps in the <ul>, so style both states. */
.listens ul { list-style: none; margin: 0; padding: 0; }
.listens li {
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: 0.5rem;
  padding: 0.4rem 0;
  border-top: 1px dashed var(--line);
  overflow-wrap: anywhere;
}
.listens li:first-child { border-top: none; padding-top: 0; }
.listen-track { grid-column: 1; }
.listen-artist { grid-column: 1; opacity: 0.7; font-style: italic; }
/* Spans both rows so the timestamp sits against the track, not below it. */
.listen-when { grid-column: 2; grid-row: 1 / span 2; opacity: 0.6; }

/* The "more" link under a sidebar panel — shared by /music and /movies. */
.panel-more {
  display: inline-block;
  margin-top: 0.75rem;
  color: inherit;
  text-decoration: none;
}
.panel-more:hover { color: var(--accent); }

/* --- /movies. #movie-diary and #movie-detail are both filled by diary.mjs. --- */

.diary ul { list-style: none; margin: 0; padding: 0; }
.diary li { border-top: 1px dashed var(--line); }
.diary li:first-child { border-top: none; }

/* The whole row is the hit target, so the button carries the layout rather than
   sitting inside it — hence the reset down to inherited type and no chrome. */
.diary-entry {
  display: block;
  width: 100%;
  padding: 0.4rem 0;
  font: inherit;
  color: inherit;
  text-align: left;
  background: none;
  border: none;
  cursor: pointer;
  /* break-word, not anywhere: the column is narrow enough that `anywhere`
     split ordinary titles mid-word ("Face/Of f"). This breaks only what
     cannot fit on a line of its own. */
  overflow-wrap: break-word;
}
.diary-title { display: block; }
/* normal, not the break-word inherited from .diary-entry — this line is a date
   and a star run, both of which should move to the next line whole. */
.diary-meta { display: block; opacity: 0.6; overflow-wrap: normal; }

/* A rating is two stacked copies of the same five ★: a dimmed track with a
   filled layer clipped over it. Same glyph in both, so they line up exactly and
   the clip lands mid-star for a half. Not ☆ for the track — it matches ★'s
   advance width but is drawn with smaller ink, so it read as a shrunken star.
   ✩ and ½ are one cell wide against ★'s two, which is why neither is used. */
.stars {
  position: relative;
  display: inline-block;
  white-space: nowrap;
}
.stars-track { display: block; color: var(--line); }
.stars-fill {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  overflow: hidden;
  white-space: nowrap;
  color: var(--accent);
}

.diary-entry:hover .diary-title { color: var(--accent); }
.diary-entry[aria-current="true"] { opacity: 1; }
.diary-entry[aria-current="true"] .diary-title { color: var(--accent); font-weight: 700; }

/* Poster floats so a short review wraps beside it rather than leaving a column
   of white space; the panel clears it so the Letterboxd link never rides up
   alongside. Capped in rem, not a percentage — it should not grow with the
   64rem content column. */
.movie-detail { margin-top: 2rem; overflow: hidden; }
.movie-poster {
  float: left;
  width: 9rem;
  max-width: 40%;
  margin: 0 1.25rem 1rem 0;
  border: 1px solid var(--line);
}
.movie-meta { font-size: 0.85rem; margin: 0 0 1rem; }
.movie-stars { margin-right: 0.6rem; vertical-align: -0.1em; }
.movie-facts { opacity: 0.6; }
.movie-review { overflow-wrap: anywhere; }
.movie-noreview { opacity: 0.6; font-style: italic; }
/* clear, so the row never rides up alongside a poster taller than the review. */
.movie-links {
  clear: both;
  display: flex;
  gap: 1.5rem;
  margin: 1rem 0 0;
  font-size: 0.85rem;
}
.movie-links a { color: inherit; text-decoration: none; }
.movie-links a:hover { color: var(--accent); }

/* The five-star panel in the right column. Same shape as the diary list, but
   text-align is inherited rather than set: .toc aligns right, and the mobile
   query flips it back to left along with everything else in that cell. */
.favourites ul { list-style: none; margin: 0; padding: 0; }
.favourites li { border-top: 1px dashed var(--line); }
.favourites li:first-child { border-top: none; }

.fav-entry {
  display: block;
  width: 100%;
  padding: 0.4rem 0;
  font: inherit;
  color: inherit;
  text-align: inherit;
  background: none;
  border: none;
  cursor: pointer;
  overflow-wrap: break-word;
}
.fav-title { display: block; }
.fav-year { display: block; opacity: 0.6; }
.fav-entry:hover .fav-title { color: var(--accent); }
.fav-entry[aria-current="true"] .fav-title { color: var(--accent); font-weight: 700; }

/* --- the grid of everything, by month. Rendered into #movie-main. --- */

.month-group { margin-top: 2rem; }

.movie-grid {
  display: grid;
  /* auto-fill, not auto-fit: a month with one film should leave a single
     poster-sized tile, not stretch it across the whole column. */
  grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
  gap: 1.25rem 1rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.movie-tile {
  display: block;
  width: 100%;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  background: none;
  border: none;
  cursor: pointer;
}
/* 2:3 is the poster aspect Letterboxd serves; object-fit keeps the odd
   non-conforming one from stretching. */
.tile-poster {
  display: block;
  width: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  border: 1px solid var(--line);
}
.tile-noposter {
  display: flex;
  align-items: center;
  padding: 0.4rem;
  font-size: 0.7rem;
  border-style: dashed;
  overflow: hidden;
}
.tile-title {
  display: block;
  margin-top: 0.4rem;
  font-size: 0.75rem;
  line-height: 1.3;
  overflow-wrap: break-word;
}
.tile-sub { display: block; font-size: 0.7rem; opacity: 0.6; }
.movie-tile:hover .tile-title { color: var(--accent); }
.movie-tile:hover .tile-poster { border-color: var(--accent); }
.movie-tile[aria-current="true"] .tile-poster { border-color: var(--accent); }

/* --- /microblog. #microblog, #microblog-profile and #microblog-boosts are all
   filled by microblog.mjs, out of one file. --- */

/* Entries are separated by the same dashed rule the sidebar lists use rather
   than boxed as cards, so the column reads as one running feed. */
.post {
  padding: 1.5rem 0;
  border-top: 1px dashed var(--line);
}
.post:first-child { border-top: none; padding-top: 0.5rem; }

/* What a boost or a reply is, above the words. Originals carry no such line. */
.post-context { margin: 0 0 0.5rem; font-size: 0.8rem; opacity: 0.7; }
.post-who { font-weight: 700; }

/* pre-wrap because a line break inside a paragraph arrives as a \a in the run's
   text, not as markup — see microblog.cljs. */
.post-para {
  margin: 0 0 0.75rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
/* The last paragraph keeps its bottom margin, deliberately. .post-body has no
   padding or border, so that margin collapses out through the div and becomes
   the gap to whatever follows — the pictures, or the date line. Zeroing it here
   to avoid a double space instead left the text sitting directly on both. */
.post-empty { opacity: 0.6; font-style: italic; }

/* Links inside a post have to be findable mid-sentence, which none of the site's
   other styled links — all of them alone on their line — have to be. A dotted
   underline says so in the same language as the dashed rules everywhere else. */
.post-link, .post-acct {
  color: inherit;
  text-decoration: underline dotted;
  text-underline-offset: 0.25em;
}
.post-link:hover, .post-acct:hover { color: var(--accent); }
/* Tags and handles are addressing, not argument — a step back from the prose. */
.post-hashtag, .post-mention { opacity: 0.75; }

/* A row that wraps: two attachments sit side by side and four make a block,
   without a fixed grid, which would leave a hole for the usual case of one. */
.post-media {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  margin: 0 0 0.75rem;
  padding: 0;
}
.post-media-link { color: inherit; text-decoration: none; }
.post-media-link:hover { color: var(--accent); }
/* No `width`, so an image renders at its natural size up to the cap and is never
   scaled up; `height: auto` against the width/height attributes microblog.mjs
   sets from the cached thumbnail is what reserves the right shape before the
   file lands, so the timeline does not jump as images arrive. */
.post-image {
  display: block;
  max-width: min(100%, 20rem);
  height: auto;
  border: 1px dashed var(--line);
}
.post-media-link:hover .post-image { border-color: var(--accent); }

.post-cw { cursor: pointer; margin-bottom: 0.75rem; }

.post-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0;
  font-size: 0.8rem;
  opacity: 0.6;
}
.post-permalink { color: inherit; text-decoration: none; }
.post-permalink:hover { color: var(--accent); }

/* The /microblog sidebar. */
.profile-avatar {
  display: block;
  width: 3rem;
  height: 3rem;
  margin-bottom: 0.5rem;
  border: 1px dashed var(--line);
}
.profile-name { margin: 0; font-weight: 700; }
.profile-acct { margin: 0; opacity: 0.6; overflow-wrap: anywhere; }
.profile-note { margin: 0.5rem 0 0; overflow-wrap: anywhere; }

/* The right column, same shape as /movies' five-star panel — handle over date,
   stacked rather than in two columns, because the cell bottoms out at 12rem and
   a full handle is most of that on its own. */
.boosts ul { list-style: none; margin: 0; padding: 0; }
.boosts li { border-top: 1px dashed var(--line); }
.boosts li:first-child { border-top: none; }
.boost-entry {
  display: block;
  padding: 0.4rem 0;
  color: inherit;
  text-decoration: none;
}
.boost-acct { display: block; overflow-wrap: anywhere; }
.boost-when { display: block; opacity: 0.6; }
.boost-entry:hover .boost-acct { color: var(--accent); }

/* --- /blog. All of it is written at build time by build.clj out of org.clj's
   hiccup — there is no script on these pages beyond the two every page gets. --- */

/* The post list in the left column. Same shape as the /music and /movies
   sidebar lists: dashed rules between rows rather than boxes around them. */
.blog-list { list-style: none; margin: 0; padding: 0; }
.blog-list li { border-top: 1px dashed var(--line); }
.blog-list li:first-child { border-top: none; }
.blog-entry {
  display: block;
  padding: 0.4rem 0;
  color: inherit;
  text-decoration: none;
  overflow-wrap: break-word;
}
.blog-name { display: block; }
.blog-when { display: block; opacity: 0.6; }
.blog-entry:hover .blog-name { color: var(--accent); }
/* The post you are reading. Full strength and bold rather than the accent,
   matching the nav's rule: "you are here" and "this is about that" are two
   different things and should not borrow each other's colour. */
.blog-entry[aria-current="page"] { opacity: 1; }
.blog-entry[aria-current="page"] .blog-name { font-weight: 700; }

/* The chapters, in the right column. Both levels sit against the same edge —
   no indent — so the column has one margin rather than two. */
.chapters, .chapters-sub { list-style: none; margin: 0; padding: 0; }
.chapters > li { padding: 0.25rem 0; }
/* Weight and opacity are what separate the two levels, and they carry it alone
   now that there is no indent. They already had to: these titles are long
   enough to wrap, and a wrapped line lands at the parent's edge regardless. */
.chapters > li > a { font-weight: 700; }
.chapters-sub { opacity: 0.75; }
.chapters a { color: inherit; text-decoration: none; }
.chapters a:hover { color: var(--accent); }

/* The index in the middle of /blog. A card is a post's title, what it is, and
   its top-level chapters — for a literate config that says far more about what
   is inside than the first paragraph would. */
.blog-index { list-style: none; margin: 2rem 0 0; padding: 0; }
.blog-card {
  padding: 1.5rem 0;
  border-top: 1px dashed var(--line);
}
.blog-card:first-child { border-top: none; padding-top: 0; }
/* The h2 already carries the shared heading rule, including its dashed
   underline — the card does not need a second one on top of it. */
.blog-card h2 { border-bottom: none; padding-bottom: 0; margin-bottom: 0.35rem; }
.blog-card-link { color: inherit; text-decoration: none; }
.blog-card-link:hover { color: var(--accent); }
.blog-card-sub { margin: 0; font-style: italic; }
.blog-card-meta { margin: 0.35rem 0 0; font-size: 0.8rem; opacity: 0.6; }
.blog-card-toc { margin: 0.5rem 0 0; font-size: 0.8rem; opacity: 0.7; }

/* --- a post's body. Every org-* class is emitted by org.clj. --- */

.org-subtitle { margin: 0; font-style: italic; }
.org-meta { margin: 0.35rem 0 0; font-size: 0.8rem; opacity: 0.6; }
.org-body { margin-top: 2.5rem; }

/* Space above a section, which the shared heading rule cannot give: it sets no
   top margin on purpose, so that the toc's panels stay aligned to the top of
   their column. Scoped here, inside the content cell, that reasoning does not
   apply — and the body's own first heading does not want it either. */
.org-body h2, .org-body h3, .org-body h4, .org-body h5 { margin-top: 2.5rem; }
.org-body > :first-child { margin-top: 0; }

.org-para { margin: 0 0 1rem; }
.org-para code, .org-table code { overflow-wrap: anywhere; }

/* Links inside prose have to be findable mid-sentence — the same problem the
   microblog's have, so the same dotted answer. See .post-link below. */
.org-link { color: inherit; text-decoration: underline dotted; text-underline-offset: 0.25em; }
.org-link:hover { color: var(--accent); }

/* A source block is a labelled box: what language it is, and — these being
   literate configs — which file it tangles out to, which is half of what the
   block is saying. The head is dashed onto the top of the code rather than
   floated over it, so a block with neither still looks like the same object. */
.org-src { margin: 0 0 1.5rem; border: 1px dashed var(--line); }
.org-src-head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  padding: 0.3rem 0.75rem;
  font-size: 0.75rem;
  opacity: 0.6;
  border-bottom: 1px dashed var(--line);
}
.org-src-lang { font-weight: 700; }
/* Pushed to the far edge, and allowed to wrap: a tangle path is long and the
   column is not always wide. */
.org-src-tangle { margin-left: auto; overflow-wrap: anywhere; }

/* Code keeps its own line breaks and scrolls itself rather than widening the
   page — .grid > * has min-width: 0 so that this actually works. 1.4 rather
   than the site's 1.5: these blocks run to fifty lines and the extra leading
   makes them harder to read down, not easier. */
.org-code {
  margin: 0;
  padding: 0.75rem;
  overflow-x: auto;
  font-size: 0.8rem;
  line-height: 1.4;
}
.org-example {
  margin: 0 0 1.5rem;
  border: 1px dashed var(--line);
}

/* A rule down the side rather than a box, so a quotation reads as an aside to
   the column rather than another block in it. */
.org-quote {
  margin: 0 0 1.5rem;
  padding-left: 1rem;
  border-left: 2px dashed var(--line);
  font-style: italic;
}
.org-quote .org-para:last-child { margin-bottom: 0; }
/* #+ATTR_HTML: :class warning. The accent is the site's one colour, so this is
   as loud as anything here gets. */
.org-quote.warning { border-left-color: var(--accent); font-style: normal; }

.org-list { margin: 0 0 1rem; padding-left: 1.25rem; }
.org-list .org-para { margin-bottom: 0.25rem; }

/* The wrapper scrolls, not the page — one of these tables is thirty columns
   across, and min-width on .grid > * is what lets it stay inside its cell. */
.org-table-wrap { margin: 0 0 1.5rem; overflow-x: auto; }
.org-table { border-collapse: collapse; font-size: 0.85rem; }
.org-table th, .org-table td {
  padding: 0.3rem 0.75rem;
  text-align: left;
  border: 1px dashed var(--line);
}
.org-table th { font-weight: 700; }

.org-figure { margin: 0 0 1.5rem; }
/* Capped on both axes, with auto on the other one, because a post's pictures are
   two different things: a wide screenshot should have the full column, and a
   portrait photo at the full column is a screenful of face. Height is what
   bounds the second case — a width cap would leave it just as tall. */
.org-image {
  display: block;
  max-width: 100%;
  max-height: 32rem;
  width: auto;
  height: auto;
  border: 1px dashed var(--line);
}
.org-figure figcaption { margin-top: 0.5rem; font-size: 0.8rem; opacity: 0.6; }

.theme-toggle button {
  background: none;
  border: none;
  font-size: 1.25rem;
  cursor: pointer;
  line-height: 1;
}

@media (max-width: 640px) {
  .grid { grid-template-columns: 1fr; grid-template-rows: none; }
  /* grid-row must go too, or the three cells of a row stack in one cell. */
  .grid > * { grid-column: 1 !important; grid-row: auto !important; }
  .corner-bl, .corner-br { display: none; }
  .theme-toggle { justify-content: flex-start; }
  /* One column, so there is no right-hand edge to sit against — the toc and the
     ticker go back to matching everything stacked above them. */
  .toc { text-align: left; }
  .toc .heatmap { margin-left: 0; }
  .ticker { margin-left: 0; }
}
