/* DRXKE BLOG — one flat stylesheet, no build step.
 *
 * NAMING: this file has no scoping of any kind, so a class name defined
 * twice silently resolves by source order and the later one wins. Every
 * area therefore owns a prefix, and new rules must use theirs:
 *
 *   (none)  reader-facing chrome: .card, .hero, .chip, .prose, .article
 *   .auth-  the sign-in page
 *   .dash-  the author dashboard
 *   .ed-    the editor
 *   .adm-   the admin panel
 *   .acct-  account settings
 *
 * Before adding a rule, search for the name. A collision between the
 * editor and the reader pages is not obvious from either file.
 */

/* ---- Tokens ------------------------------------------------------- */

:root {
  --bg: #0b0c0f;
  --bg-soft: #101218;
  --surface: #14161b;
  --surface-2: #1a1d24;
  --line: #23262e;
  --line-soft: #1b1e25;

  --text: #e8eaf0;
  --text-dim: #a2a8b8;
  --muted: #8b90a0;

  --accent: #e11d2e;
  --accent-hot: #ff2f42;
  --accent-ink: #ffffff;

  --ok: #2fb872;
  --warn: #f0a020;

  /* Category pages and chips set --cat inline; this is the fallback so
     a post with no category still paints something. */
  --cat: var(--accent);

  --sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --serif: Georgia, "Iowan Old Style", "Times New Roman", serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --wrap: 1200px;
  --radius: 10px;
  --radius-sm: 6px;

  --shadow: 0 8px 30px rgba(0, 0, 0, .45);
  --header-h: 60px;
}

/* ---- Base --------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
}

img, svg, video { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--accent-hot);
  outline-offset: 2px;
  border-radius: 3px;
}

/* Any element with [hidden] must stay hidden. The UA rule is a bare
   attribute selector, so any class that sets `display` outranks it and
   privileged UI leaks into view for everyone. Consequence: you can no
   longer un-hide something by adding a class. */
[hidden] { display: none !important; }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--accent); color: var(--accent-ink);
  padding: 10px 16px; font-weight: 700;
}
.skip-link:focus { left: 8px; top: 8px; }

.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 20px; }
.wrap-narrow { max-width: 760px; }

/* ---- Masthead ----------------------------------------------------- */

.masthead {
  position: sticky; top: 0; z-index: 100;
  background: rgba(11, 12, 15, .92);
  backdrop-filter: saturate(140%) blur(10px);
  border-bottom: 1px solid var(--line);
  transition: box-shadow .22s ease, border-bottom-color .22s ease;
  /* Named so a cross-document view transition treats this as the same
     element on both sides and leaves it alone, rather than crossfading
     it with an identical copy of itself. */
  view-transition-name: masthead;
}
.masthead-inner {
  max-width: var(--wrap); margin: 0 auto; padding: 0 20px;
  height: var(--header-h);
  display: flex; align-items: center; gap: 20px;
}

.wordmark {
  /* center, not baseline — the lockup now leads with an SVG, and an SVG
     has no baseline to align text against. */
  display: inline-flex; align-items: center; gap: 8px;
  font-weight: 900; letter-spacing: -.5px; font-size: 20px;
  white-space: nowrap;
}
.logo-mark { flex: 0 0 auto; display: block; color: var(--text); }

/* Stacked and centred, for the middle of a card where a header-shaped
   lockup would read as navigation rather than identity. */
.logo-stack {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  margin-bottom: 22px;
}
.logo-stack .wordmark { font-size: 22px; }
.wordmark-a { color: var(--text); }
/* Outlined, not filled. The mark is now a solid crimson tile, and with a
   filled chip here the header carried three competing red blocks — the
   logo, this, and the Write button. The mark owns the colour; this
   echoes it; Write is the only other solid, which is what makes it read
   as the action. */
.wordmark-b {
  color: var(--accent-hot);
  border: 1.5px solid color-mix(in srgb, var(--accent) 55%, transparent);
  padding: 0 6px; border-radius: 4px; font-size: 14px;
  letter-spacing: .5px; line-height: 1.5;
}

.nav { display: flex; align-items: center; gap: 4px; flex: 1; min-width: 0; overflow: hidden; }
.nav-link {
  padding: 7px 11px; border-radius: var(--radius-sm);
  font-size: 14px; font-weight: 650; color: var(--text-dim);
  white-space: nowrap; position: relative;
  transition: color .15s, background .15s;
}
.nav-link:hover { color: var(--text); background: var(--surface-2); }
.nav-link.is-active { color: var(--text); }
.nav-link.is-active::after {
  content: ""; position: absolute; left: 11px; right: 11px; bottom: 1px;
  height: 2px; background: var(--cat, var(--accent)); border-radius: 2px;
}

/* margin-left:auto moved to .nav-search, which now precedes this and
   carries the whole right-hand group. Leaving it here too would split
   the free space between them and strand the search box mid-header. */
.account { display: flex; align-items: center; gap: 8px; }

/* The reader header's account corner is server-rendered as the
   signed-out "Sign in" button — those pages are cached byte-for-byte, so
   the real state can only arrive from site.js afterwards. Measured: the
   corner grows 79px -> 177px on hydration, which dragged the search box
   97px to the left. That is the flash.
   Reserving the corner's width makes hydration a change of content
   inside a fixed box rather than a change of the box, so nothing to its
   left can move. Scoped to the id: the app header's .account holds only
   a Sign out button and needs no reserve. */
/* The two numbers below are a pair and must be kept so. Measured: the
   corner's fixed furniture — Write button, gaps, avatar, button padding —
   is 119px, so the reserve has to be that plus whatever the name is
   allowed to reach. 119 + 72 = 191, hence 192.
   Getting this wrong is silent: the name simply outgrows the reserve and
   the search box starts moving again, which is the bug this exists to
   prevent. A first attempt at 184/92 did exactly that. */
#accountSlot { min-width: 192px; justify-content: flex-end; }

.account-name {
  display: block; max-width: 72px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The header search box.
 *
 * margin-left:auto pushes it to the right-hand end of the nav, so it
 * sits against the Write button rather than floating between section
 * links. In the mobile drawer .nav becomes a column and this rule is
 * overridden there — see the 760px block.
 *
 * flex-shrink is what keeps the header from breaking: .nav is
 * `overflow: hidden`, so anything that doesn't fit is silently clipped
 * rather than wrapped. Letting the box compress to 120px means it gives
 * way before a section link disappears without trace. */
.nav-search {
  display: flex; align-items: center; gap: 7px;
  /* Shrinkable but never to nothing: below ~130px the field holds no
     visible text and is worse than absent. .account carries the
     margin-left:auto that pushes this pair to the right. */
  flex: 0 1 210px; min-width: 130px; margin-left: auto;
  padding: 0 10px; height: 34px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  transition: border-color .15s, background .15s;
}
.nav-search:hover { border-color: #333846; }
/* The whole box lights up, not just the input, because the border is on
   the wrapper — :focus-within is the only way to reach it. */
.nav-search:focus-within { border-color: var(--accent); background: var(--surface-2); }

/* The magnifier is a submit button, so it needs the button reset. */
.nav-search-go {
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto; padding: 0; margin: 0; border: 0; background: none;
  color: var(--muted); cursor: pointer; transition: color .15s;
}
.nav-search-go:hover { color: var(--text); }
.nav-search-go:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
.nav-search-icon { flex: 0 0 auto; display: block; }
.nav-search:focus-within .nav-search-go { color: var(--accent); }

.nav-search-input {
  flex: 1 1 auto; min-width: 0;
  background: none; border: 0; padding: 0; margin: 0;
  color: var(--text); font-family: inherit; font-size: 14px;
}
.nav-search-input::placeholder { color: var(--muted); }
/* The wrapper already shows focus, so the input's own ring would be a
   second box inside the first. */
.nav-search-input:focus { outline: none; }
/* WebKit draws a clear button that ignores the dark palette entirely. */
.nav-search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }

.nav-toggle {
  display: none; background: none; border: 0; cursor: pointer;
  padding: 8px; margin-left: -8px;
  flex-direction: column; gap: 4px;
}
.nav-toggle span {
  display: block; width: 20px; height: 2px; background: var(--text); border-radius: 2px;
  transition: transform .2s, opacity .2s;
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* ---- Buttons ------------------------------------------------------ */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 9px 16px; border-radius: var(--radius-sm);
  border: 1px solid transparent; background: var(--surface-2); color: var(--text);
  font-size: 14px; font-weight: 700; cursor: pointer; white-space: nowrap;
  transition: background .15s, border-color .15s, opacity .15s, transform .06s;
}
.btn:hover { background: #22262f; }
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn.is-busy { opacity: .5; pointer-events: none; }

.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: var(--accent-hot); }

.btn-ghost { background: transparent; border-color: var(--line); color: var(--text-dim); }
.btn-ghost:hover { background: var(--surface-2); color: var(--text); border-color: #333846; }

.btn-danger { background: transparent; border-color: #4a2027; color: #ff7b88; }
.btn-danger:hover { background: #2a1216; color: #ff9aa4; }

.btn-sm { padding: 6px 11px; font-size: 13px; }
.btn-block { width: 100%; }

/* ---- Chips, tags -------------------------------------------------- */

.chip {
  display: inline-block; padding: 3px 9px; border-radius: 4px;
  transition: background .15s ease, border-color .15s ease;
  background: color-mix(in srgb, var(--cat) 18%, transparent);
  color: var(--cat);
  font-size: 11px; font-weight: 800; letter-spacing: .8px; text-transform: uppercase;
  border: 1px solid color-mix(in srgb, var(--cat) 35%, transparent);
}
a.chip:hover { background: color-mix(in srgb, var(--cat) 28%, transparent); }

/* The label row above a headline. `.card-body`, `.hero-body` and
   `.article-head-inner` are all flex columns, so without this wrapper a
   featured post's two chips would stack vertically instead of sitting
   side by side. */
.chip-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }

/* An admin featured this post. Same chip shape as the category so the
   two read as one row, but pinned to the site accent rather than a
   category's colour, so it can never be mistaken for a section.
   .chip sets --cat, so this only has to override that one value — and
   it must come AFTER .chip, since this file is flat and unscoped and a
   same-specificity rule wins on source order alone. */
.chip-featured { --cat: var(--accent); }

.tag-row { display: flex; flex-wrap: wrap; gap: 8px; max-width: 720px; margin: 32px auto 0; padding: 0 20px; }
.tag {
  font-size: 12px; color: var(--muted); background: var(--surface);
  border: 1px solid var(--line); padding: 4px 9px; border-radius: 20px;
  transition: color .15s ease, border-color .15s ease;
}

.badge {
  display: inline-block; padding: 2px 8px; border-radius: 20px;
  font-size: 11px; font-weight: 800; letter-spacing: .5px; text-transform: uppercase;
  border: 1px solid var(--line); color: var(--muted); background: var(--surface);
}
.badge-live { color: var(--ok); border-color: #1d4634; background: #10241b; }
.badge-draft { color: var(--warn); border-color: #4a3a15; background: #241d10; }
.badge-admin { color: var(--accent); border-color: #4a2027; background: #241014; }

/* ---- Byline ------------------------------------------------------- */

.byline {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  font-size: 13px; color: var(--muted);
}
.byline a { color: var(--text-dim); font-weight: 650; }
.byline a:hover { color: var(--text); }
.byline-avatar { font-size: 15px; line-height: 1; }
.dot { color: #4a4f5e; }

/* ---- Front page --------------------------------------------------- */

.lede {
  display: grid; grid-template-columns: minmax(0, 1.9fr) minmax(280px, 1fr);
  gap: 28px; padding: 28px 0 36px;
}

.hero { display: flex; flex-direction: column; gap: 0; }
.hero-media {
  transition: box-shadow .3s ease;
  display: block; border-radius: var(--radius); overflow: hidden;
  aspect-ratio: 16 / 9; background: var(--surface);
}
.hero-media .cover { width: 100%; height: 100%; object-fit: cover; transition: transform .4s ease; }
.hero:hover .hero-media .cover { transform: scale(1.02); }
.hero-body { padding: 16px 2px 0; display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.hero-title { margin: 0; font-size: clamp(26px, 3.4vw, 40px); line-height: 1.1; font-weight: 850; letter-spacing: -1px; }
.hero-title a:hover { color: var(--accent-hot); }
.hero-dek { margin: 0; color: var(--text-dim); font-size: 16px; line-height: 1.5; max-width: 62ch; }

.rail { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.rail-head {
  margin: 0 0 2px; font-size: 12px; font-weight: 800; letter-spacing: 1.6px;
  text-transform: uppercase; color: var(--muted);
  padding-bottom: 10px; border-bottom: 2px solid var(--accent);
}

/* ---- Cards -------------------------------------------------------- */

.grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: 28px 24px; padding-bottom: 8px;
}

.card { display: flex; flex-direction: column; gap: 11px; min-width: 0; transition: transform .25s ease; }
.card-media {
  display: block; border-radius: var(--radius-sm); overflow: hidden;
  aspect-ratio: 16 / 9; background: var(--surface);
  transition: box-shadow .25s ease;
}
.card-media .cover { width: 100%; height: 100%; object-fit: cover; transition: transform .35s ease; }
.card:hover .card-media .cover { transform: scale(1.03); }
.card-body { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; min-width: 0; }
.card-title { margin: 0; font-size: 18px; line-height: 1.25; font-weight: 750; letter-spacing: -.3px; }
.card-title a:hover { color: var(--accent-hot); }
.card-excerpt {
  margin: 0; color: var(--muted); font-size: 14px; line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* Rail variant: image beside the headline, no excerpt. */
.card-sm {
  flex-direction: row; gap: 12px; align-items: flex-start;
  padding-bottom: 14px; border-bottom: 1px solid var(--line-soft);
}
.card-sm:last-child { border-bottom: 0; padding-bottom: 0; }
.card-sm .card-media { width: 104px; flex: 0 0 104px; aspect-ratio: 4 / 3; }
.card-sm .card-title { font-size: 15px; line-height: 1.3; }
.card-sm .chip { font-size: 10px; padding: 2px 6px; }

.cover-empty {
  width: 100%; height: 100%; display: grid; place-items: center;
  background: linear-gradient(135deg, var(--surface-2), var(--surface));
  border: 1px solid var(--line);
}
.cover-empty span {
  font-size: 26px; font-weight: 900; letter-spacing: 1px; color: #333846;
}

/* ---- Section headings --------------------------------------------- */

.section-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  margin: 12px 0 22px; padding-bottom: 12px; border-bottom: 2px solid var(--line);
  position: relative;
}
.section-head::after {
  content: ""; position: absolute; left: 0; bottom: -2px; width: 64px; height: 2px;
  background: var(--accent);
}
.section-head h2 {
  margin: 0; font-size: 15px; font-weight: 850; letter-spacing: 1.6px; text-transform: uppercase;
}
.section-more { font-size: 13px; font-weight: 700; color: var(--muted); }
.section-more:hover { color: var(--accent-hot); }

.page-head { padding: 34px 0 26px; border-bottom: 1px solid var(--line); margin-bottom: 28px; }
.page-head h1 {
  margin: 0 0 8px; font-size: clamp(28px, 4vw, 42px); font-weight: 900; letter-spacing: -1.2px;
  border-left: 5px solid var(--cat); padding-left: 14px;
}
.page-head p { margin: 0 0 10px; color: var(--text-dim); font-size: 16px; max-width: 60ch; }
.page-head-count { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 1.2px; font-weight: 700; }

.author-head { display: flex; gap: 20px; align-items: center; padding: 34px 0 26px; border-bottom: 1px solid var(--line); margin-bottom: 28px; }
.author-head-avatar {
  font-size: 40px; width: 78px; height: 78px; flex: 0 0 78px;
  display: grid; place-items: center; background: var(--surface);
  border: 1px solid var(--line); border-radius: 50%;
}
.author-head h1 { margin: 0 0 6px; font-size: clamp(24px, 3vw, 34px); font-weight: 850; letter-spacing: -.8px; }
.author-head p { margin: 0 0 8px; color: var(--text-dim); max-width: 60ch; }

/* ---- Pagination --------------------------------------------------- */

.pagination { display: flex; align-items: center; justify-content: center; gap: 16px; padding: 40px 0 56px; }
.page-btn {
  padding: 9px 18px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  font-size: 14px; font-weight: 700; color: var(--text-dim); background: var(--surface);
}
.page-btn:hover { background: var(--surface-2); color: var(--text); }
.page-btn.is-disabled { opacity: .35; pointer-events: none; }
.page-count { font-size: 13px; color: var(--muted); }

/* ---- Article ------------------------------------------------------ */

.article-head { padding-top: 0; }
.article-cover {
  width: 100%; max-height: 460px; overflow: hidden;
  border-bottom: 1px solid var(--line); background: var(--surface);
}
.article-cover img { width: 100%; height: 100%; max-height: 460px; object-fit: cover; }
.article-head-inner {
  max-width: 760px; margin: 0 auto; padding: 32px 20px 0;
  display: flex; flex-direction: column; gap: 14px; align-items: flex-start;
}
.article-head-inner h1 {
  margin: 0; font-size: clamp(30px, 5vw, 46px); line-height: 1.08;
  font-weight: 900; letter-spacing: -1.4px;
}
.article-dek {
  margin: 0; font-size: 19px; line-height: 1.5; color: var(--text-dim);
  font-family: var(--serif); font-style: italic;
}
.article-meta { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; font-size: 14px; color: var(--muted); padding-top: 4px; }
.author-chip { display: inline-flex; align-items: center; gap: 7px; font-weight: 700; color: var(--text-dim); }
.author-chip:hover { color: var(--text); }
.author-chip-avatar { font-size: 18px; }

.article-foot {
  max-width: 760px; margin: 40px auto 0; padding: 24px 20px 48px;
  border-top: 1px solid var(--line);
  display: flex; align-items: flex-start; justify-content: space-between; gap: 20px;
}
.author-card { display: flex; gap: 14px; align-items: flex-start; }
.author-card-avatar {
  font-size: 26px; width: 50px; height: 50px; flex: 0 0 50px;
  display: grid; place-items: center; background: var(--surface);
  border: 1px solid var(--line); border-radius: 50%;
}
.author-card-name { font-weight: 800; font-size: 16px; }
.author-card-name:hover { color: var(--accent-hot); }
.author-card p { margin: 4px 0 0; color: var(--muted); font-size: 14px; max-width: 46ch; }

/* ---- Prose (rendered markdown) ------------------------------------ */

.prose {
  max-width: 760px; margin: 0 auto; padding: 30px 20px 0;
  font-family: var(--serif); font-size: 19px; line-height: 1.72; color: #dfe3ec;
}
.prose > *:first-child { margin-top: 0; }
.prose p { margin: 0 0 1.25em; }
.prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
  font-family: var(--sans); font-weight: 800; letter-spacing: -.5px;
  line-height: 1.25; margin: 1.9em 0 .6em; scroll-margin-top: calc(var(--header-h) + 16px);
}
.prose h2 { font-size: 1.5em; }
.prose h3 { font-size: 1.25em; }
.prose h4 { font-size: 1.08em; }
.prose h5, .prose h6 { font-size: 1em; color: var(--text-dim); }
/* :not(.btn) is load-bearing. `.prose a` is specificity (0,1,1) and
   `.btn-primary` is (0,1,0), so without it every button placed inside
   prose loses to the link rule and renders as accent-red text on an
   accent-red background, underlined — which is what happened to the
   "Create an account" button on /about. */
.prose a:not(.btn) {
  color: var(--accent-hot); text-decoration: underline;
  text-underline-offset: 3px; text-decoration-thickness: 1px;
  transition: text-decoration-thickness .15s ease, text-underline-offset .15s ease;
}
.prose a:not(.btn):hover { text-decoration-thickness: 2px; }
.prose strong { font-weight: 700; color: #fff; }
.prose ul, .prose ol { margin: 0 0 1.25em; padding-left: 1.4em; }
.prose li { margin-bottom: .5em; }
.prose li::marker { color: var(--accent); }
.prose hr { border: 0; border-top: 1px solid var(--line); margin: 2.4em 0; }
.prose img { border-radius: var(--radius-sm); margin: 0 auto; }

.prose blockquote {
  margin: 1.6em 0; padding: 4px 0 4px 22px;
  border-left: 3px solid var(--accent);
  font-size: 1.06em; font-style: italic; color: #eef1f7;
}

.prose code {
  font-family: var(--mono); font-size: .82em;
  background: var(--surface-2); border: 1px solid var(--line);
  padding: 1px 5px; border-radius: 4px; color: #ffd2d6;
}
.prose pre {
  margin: 1.5em 0; padding: 16px 18px; overflow-x: auto;
  background: #0e1015; border: 1px solid var(--line); border-radius: var(--radius-sm);
  font-size: 14px; line-height: 1.6;
}
.prose pre code { background: none; border: 0; padding: 0; font-size: 14px; color: #d8dde8; }

.prose figure { margin: 1.9em 0; }
.prose figcaption {
  margin-top: 9px; font-family: var(--sans); font-size: 13px;
  color: var(--muted); text-align: center;
}

/* Wide content must scroll inside its own box, never the page. */
.table-wrap { overflow-x: auto; margin: 1.6em 0; border: 1px solid var(--line); border-radius: var(--radius-sm); }
.prose table { border-collapse: collapse; width: 100%; font-family: var(--sans); font-size: 15px; }
.prose th, .prose td { padding: 10px 14px; text-align: left; border-bottom: 1px solid var(--line); }
.prose th { background: var(--surface); font-weight: 800; font-size: 13px; text-transform: uppercase; letter-spacing: .6px; }
.prose tbody tr:last-child td { border-bottom: 0; }

.callout {
  display: block; margin: 1.7em 0; padding: 16px 18px;
  background: var(--surface); border: 1px solid var(--line);
  border-left: 3px solid var(--muted); border-radius: var(--radius-sm);
  font-family: var(--sans); font-size: 16px; line-height: 1.6;
}
.callout-label {
  display: block; font-size: 11px; font-weight: 800; letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--muted); margin-bottom: 6px;
}
.callout-note { border-left-color: #3b9df5; }
.callout-note .callout-label { color: #3b9df5; }
.callout-tip { border-left-color: var(--ok); }
.callout-tip .callout-label { color: var(--ok); }
.callout-warn { border-left-color: var(--warn); }
.callout-warn .callout-label { color: var(--warn); }
.callout-spoiler { border-left-color: var(--accent); }
.callout-spoiler .callout-label { color: var(--accent); }
.callout-spoiler .callout-body {
  filter: blur(5px); transition: filter .2s; cursor: pointer;
}
.callout-spoiler:hover .callout-body, .callout-spoiler:focus-within .callout-body { filter: none; }

.embed {
  position: relative; margin: 1.8em 0; padding-bottom: 56.25%; height: 0;
  border-radius: var(--radius-sm); overflow: hidden; background: #000;
}
.embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* ---- Contributors ------------------------------------------------- */

/* ---- About page ---- */

.about-block { margin: 44px 0 0; }
.about-h2 {
  margin: 0 0 18px; font-size: 13px; font-weight: 850; letter-spacing: 1.6px;
  text-transform: uppercase; color: var(--muted);
  padding-bottom: 10px; border-bottom: 2px solid var(--line); position: relative;
}
.about-h2::after {
  content: ""; position: absolute; left: 0; bottom: -2px; width: 48px; height: 2px;
  background: var(--accent);
}

.about-sections { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
.about-section {
  display: flex; flex-direction: column; align-items: flex-start; gap: 9px;
  padding: 16px 18px; border-radius: var(--radius);
  background: var(--surface); border: 1px solid var(--line);
  border-left: 3px solid var(--cat);
  transition: border-color .15s, transform .12s, background .15s;
}
.about-section:hover { background: var(--surface-2); transform: translateY(-2px); }
.about-section-blurb { font-size: 14px; color: var(--text-dim); line-height: 1.5; }
.about-section-count {
  font-size: 11px; font-weight: 800; letter-spacing: 1px; text-transform: uppercase;
  color: var(--muted); margin-top: auto;
}

.about-cta {
  margin: 44px 0 0; padding: 26px 28px; border-radius: var(--radius);
  background: linear-gradient(135deg, var(--surface-2), var(--surface));
  border: 1px solid var(--line);
  display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
}
.about-cta h2 { margin: 0; font-size: 21px; font-weight: 850; letter-spacing: -.5px; }
.about-cta p { margin: 0; color: var(--text-dim); font-size: 15px; max-width: 52ch; }
.about-cta .btn { margin-top: 6px; }

/* Heading styling comes from .about-h2 in the markup. A descendant rule
   here (`.contributors h2`) would be specificity (0,1,1) and silently
   outrank it — the same collision that broke the button above. */
.contributors { margin: 44px 0 20px; }
.contributor-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
.contributor {
  display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center;
  padding: 18px 12px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  transition: border-color .15s, transform .12s;
}
.contributor:hover { border-color: #333846; transform: translateY(-2px); }
.contributor-avatar { font-size: 28px; }
.contributor-name { font-weight: 750; font-size: 14px; }
.contributor-count { font-size: 12px; color: var(--muted); }

/* ---- Empty state -------------------------------------------------- */

.empty {
  text-align: center; padding: 80px 20px; max-width: 520px; margin: 0 auto;
  display: flex; flex-direction: column; align-items: center; gap: 12px;
}
.empty h2 { margin: 0; font-size: 24px; font-weight: 800; letter-spacing: -.5px; }
.empty p { margin: 0; color: var(--muted); }
.empty .btn { margin-top: 10px; }

/* ---- Footer ------------------------------------------------------- */

.footer { border-top: 1px solid var(--line); background: var(--bg-soft); margin-top: 40px; }
.footer-inner {
  max-width: var(--wrap); margin: 0 auto; padding: 44px 20px 28px;
  display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 32px;
}
.footer-brand p { margin: 12px 0 0; color: var(--muted); font-size: 14px; max-width: 34ch; }
.footer-links { display: flex; flex-direction: column; gap: 9px; }
.footer-links a { font-size: 14px; color: var(--text-dim); }
.footer-links a:hover { color: var(--accent-hot); }
.footer-legal {
  max-width: var(--wrap); margin: 0 auto; padding: 18px 20px 32px;
  border-top: 1px solid var(--line-soft);
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  font-size: 13px; color: var(--muted);
}

/* ---- Forms -------------------------------------------------------- */

.field { display: flex; flex-direction: column; gap: 7px; margin-bottom: 18px; }
.field > label, .field-label {
  font-size: 12px; font-weight: 800; letter-spacing: 1px; text-transform: uppercase; color: var(--muted);
}
.field-hint { font-size: 13px; color: var(--muted); margin: 0; }
.field-error { font-size: 13px; color: #ff7b88; margin: 0; }

.input, .textarea, .select {
  width: 100%; padding: 11px 13px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius-sm);
  color: var(--text); font-size: 15px;
  transition: border-color .15s, background .15s;
}
.input:focus, .textarea:focus, .select:focus {
  outline: none; border-color: var(--accent); background: var(--surface);
}
.input::placeholder, .textarea::placeholder { color: #5b6070; }
.textarea { resize: vertical; min-height: 110px; line-height: 1.6; }
.select { appearance: none; cursor: pointer; padding-right: 34px;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 17px) 50%, calc(100% - 12px) 50%;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
}

.notice {
  padding: 12px 15px; border-radius: var(--radius-sm); font-size: 14px;
  border: 1px solid var(--line); background: var(--surface); color: var(--text-dim);
  margin-bottom: 18px;
}
.notice-error { border-color: #4a2027; background: #1e1114; color: #ffb3ba; }
.notice-ok { border-color: #1d4634; background: #10241b; color: #8ee0b4; }
.notice-warn { border-color: #4a3a15; background: #201a10; color: #f3ce8b; }

/* ---- Toast -------------------------------------------------------- */

.toast-host {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%);
  z-index: 300; display: flex; flex-direction: column; gap: 8px; align-items: center;
  pointer-events: none; width: min(92vw, 440px);
}
.toast {
  background: var(--surface-2); border: 1px solid var(--line); box-shadow: var(--shadow);
  padding: 11px 17px; border-radius: 40px; font-size: 14px; font-weight: 650;
  animation: toast-in .22s ease-out;
}
.toast-ok { border-color: #1d4634; color: #8ee0b4; }
.toast-error { border-color: #4a2027; color: #ffb3ba; }
@keyframes toast-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* ---- Auth page ---------------------------------------------------- */

.auth-shell { min-height: calc(100vh - var(--header-h)); display: grid; place-items: center; padding: 40px 20px 70px; }
.auth-card {
  width: 100%; max-width: 420px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 32px;
  box-shadow: var(--shadow);
}
.auth-card h1 { margin: 0 0 6px; font-size: 25px; font-weight: 850; letter-spacing: -.6px; }
.auth-lead { margin: 0 0 24px; color: var(--muted); font-size: 15px; }
.auth-code {
  text-align: center; font-size: 30px; font-weight: 800; letter-spacing: 12px;
  font-family: var(--mono); padding-left: 12px;
}
.auth-alt { margin: 18px 0 0; text-align: center; font-size: 13px; color: var(--muted); }
.auth-legal { margin: 0 0 16px; font-size: 12.5px; line-height: 1.5; color: var(--muted); text-align: center; }
.auth-legal a { color: var(--text-dim); text-decoration: underline; text-underline-offset: 2px; }
.auth-legal a:hover { color: var(--accent-hot); }
.auth-alt button { background: none; border: 0; color: var(--accent-hot); cursor: pointer; font-weight: 700; padding: 0; }
.auth-alt button:hover { text-decoration: underline; }
.auth-devcode {
  margin-top: 14px; padding: 11px 14px; border-radius: var(--radius-sm);
  background: #201a10; border: 1px solid #4a3a15; color: #f3ce8b; font-size: 13px;
}
.auth-devcode strong { font-family: var(--mono); font-size: 18px; letter-spacing: 3px; }

.auth-switch { margin: 20px 0 0; text-align: center; font-size: 14px; color: var(--muted);
  padding-top: 16px; border-top: 1px solid var(--line); }
.auth-switch button, .auth-switch a { background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--accent-hot); font-weight: 700; font-size: 14px; }
.auth-switch button:hover, .auth-switch a:hover { text-decoration: underline; }

/* Show/hide toggle. Typing a long passphrase blind into a narrow field
   is a large part of why people choose short ones. */
.auth-reveal { position: relative; display: flex; }
.auth-reveal .input { padding-right: 62px; }
.auth-reveal-btn {
  position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  background: var(--surface-2); border: 1px solid var(--line); color: var(--text-dim);
  border-radius: 4px; padding: 5px 9px; font-size: 12px; font-weight: 700; cursor: pointer;
}
.auth-reveal-btn:hover { color: var(--text); border-color: #333846; }

.auth-meter { height: 4px; background: var(--line); border-radius: 3px; overflow: hidden; margin-top: 4px; }
.auth-meter span {
  display: block; height: 100%; width: 0; border-radius: 3px;
  background: var(--accent); transition: width .18s, background .18s;
}
.auth-meter span[data-score="0"], .auth-meter span[data-score="1"] { background: #e11d2e; }
.auth-meter span[data-score="2"] { background: var(--warn); }
.auth-meter span[data-score="3"] { background: #7cc243; }
.auth-meter span[data-score="4"] { background: var(--ok); }

/* ---- Avatars ------------------------------------------------------ */
/* One shape for both kinds, so an uploaded picture and a generated
   letter are always sized and cropped identically. --av-size is set
   inline by avatarHtml(). */

.avatar {
  width: var(--av-size, 40px); height: var(--av-size, 40px);
  flex: 0 0 var(--av-size, 40px);
  border-radius: 50%; display: inline-grid; place-items: center;
  overflow: hidden; user-select: none; vertical-align: middle;
}
.avatar-img { object-fit: cover; background: var(--surface-2); }
.avatar-letter {
  background: var(--av-bg, var(--accent)); color: #fff;
  font-weight: 800; letter-spacing: 0;
  /* Scales with the avatar so one rule covers a 24px byline and a 96px
     account header. */
  font-size: calc(var(--av-size, 40px) * 0.44);
  line-height: 1;
}

/* ---- Shared app-page shell (dashboard / admin / account) ---------- */

.app-head {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; flex-wrap: wrap;
  padding: 34px 0 20px; border-bottom: 1px solid var(--line); margin-bottom: 26px;
}
.app-head h1 { margin: 0 0 6px; font-size: clamp(24px, 3.4vw, 34px); font-weight: 900; letter-spacing: -1px; }
.app-head p { margin: 0; color: var(--muted); font-size: 15px; }

.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: 24px; overflow-x: auto; }
.tab {
  background: none; border: 0; border-bottom: 2px solid transparent;
  padding: 11px 15px; font-size: 14px; font-weight: 700; color: var(--muted);
  cursor: pointer; white-space: nowrap; margin-bottom: -1px;
}
.tab:hover { color: var(--text); }
.tab.is-active { color: var(--text); border-bottom-color: var(--accent); }

.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 14px; margin-bottom: 28px; }
.stat {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px 18px;
}
.stat-n { font-size: 26px; font-weight: 850; letter-spacing: -.8px; line-height: 1.1; }
.stat-k { font-size: 12px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--muted); margin-top: 4px; }

/* ---- Dashboard ---------------------------------------------------- */

.dash-list { display: flex; flex-direction: column; gap: 10px; }
.dash-row {
  display: flex; align-items: center; gap: 16px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px 16px; transition: border-color .15s;
}
.dash-row:hover { border-color: #333846; }
.dash-thumb {
  width: 76px; flex: 0 0 76px; aspect-ratio: 16 / 10; border-radius: var(--radius-sm);
  object-fit: cover; background: var(--surface-2);
}
.dash-main { flex: 1; min-width: 0; }
.dash-title { font-size: 16px; font-weight: 750; margin: 0 0 5px; letter-spacing: -.2px; }
.dash-title a:hover { color: var(--accent-hot); }
.dash-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 12.5px; color: var(--muted); }
.dash-actions { display: flex; gap: 7px; flex-shrink: 0; }

.dash-apply { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 28px; max-width: 640px; }
.dash-apply h2 { margin: 0 0 8px; font-size: 21px; font-weight: 800; letter-spacing: -.5px; }
.dash-apply > p { margin: 0 0 20px; color: var(--muted); font-size: 15px; }

/* ---- Editor ------------------------------------------------------- */

.ed-body { background: var(--bg); }

.ed-bar {
  position: sticky; top: var(--header-h); z-index: 90;
  background: rgba(11, 12, 15, .95); backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.ed-bar-inner {
  max-width: 1400px; margin: 0 auto; padding: 10px 20px;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.ed-status { font-size: 13px; color: var(--muted); display: flex; align-items: center; gap: 7px; min-width: 118px; }
.ed-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); flex: 0 0 7px; }
.ed-dot.is-saving { background: var(--warn); }
.ed-dot.is-saved { background: var(--ok); }
.ed-dot.is-error { background: var(--accent-hot); }
.ed-bar-spacer { flex: 1; }

.ed-modes { display: flex; gap: 2px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 2px; }
.ed-mode {
  background: none; border: 0; padding: 6px 12px; border-radius: 4px;
  font-size: 13px; font-weight: 700; color: var(--muted); cursor: pointer;
}
.ed-mode:hover { color: var(--text); }
.ed-mode.is-active { background: var(--surface-2); color: var(--text); }

.ed-wrap { max-width: 1400px; margin: 0 auto; padding: 24px 20px 80px; }

/* Three regions: the writing area, the live preview, and the settings
   sidebar. The first two share .ed-main and swap according to the mode
   buttons; the sidebar is always present on desktop. */
.ed-shell { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 26px; align-items: start; }
.ed-main { min-width: 0; }
.ed-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 24px; align-items: start; }
.ed-layout.is-write { grid-template-columns: minmax(0, 1fr); }
.ed-layout.is-write .ed-preview { display: none; }
.ed-layout.is-preview { grid-template-columns: minmax(0, 1fr); }
.ed-layout.is-preview .ed-compose { display: none; }

.ed-compose { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

.ed-title {
  width: 100%; background: none; border: 0; padding: 4px 0;
  font-size: clamp(26px, 3.6vw, 38px); font-weight: 900; letter-spacing: -1.2px;
  line-height: 1.1; resize: none; overflow: hidden;
}
.ed-title:focus { outline: none; }
.ed-title::placeholder { color: #3a3f4d; }

.ed-dek {
  width: 100%; background: none; border: 0; padding: 2px 0;
  font-family: var(--serif); font-style: italic; font-size: 18px; color: var(--text-dim);
  line-height: 1.5; resize: none; overflow: hidden;
}
.ed-dek:focus { outline: none; }
.ed-dek::placeholder { color: #3a3f4d; }

.ed-rule { border: 0; border-top: 1px solid var(--line); margin: 2px 0; }

.ed-toolbar {
  display: flex; gap: 2px; flex-wrap: wrap; align-items: center;
  padding: 6px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); position: sticky; top: calc(var(--header-h) + 52px); z-index: 20;
}
.ed-tool {
  background: none; border: 0; padding: 6px 9px; border-radius: 4px; cursor: pointer;
  font-size: 13px; font-weight: 700; color: var(--text-dim); min-width: 30px;
}
.ed-tool:hover { background: var(--surface-2); color: var(--text); }
.ed-tool-sep { width: 1px; height: 18px; background: var(--line); margin: 0 4px; }

.ed-text {
  width: 100%; min-height: 460px; padding: 18px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius-sm);
  font-family: var(--mono); font-size: 14.5px; line-height: 1.75; resize: vertical;
  tab-size: 2;
}
.ed-text:focus { outline: none; border-color: #333846; }
.ed-text.is-dropping { border-color: var(--accent); background: #16121400; }

.ed-count { font-size: 12px; color: var(--muted); display: flex; gap: 14px; }

/* Preview pane reuses .prose so what you see is what publishes. */
.ed-preview {
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius);
  min-height: 400px; padding-bottom: 24px; position: sticky; top: calc(var(--header-h) + 68px);
  max-height: calc(100vh - var(--header-h) - 90px); overflow-y: auto;
}
.ed-preview-head {
  padding: 12px 20px; border-bottom: 1px solid var(--line);
  font-size: 11px; font-weight: 800; letter-spacing: 1.4px; text-transform: uppercase; color: var(--muted);
  position: sticky; top: 0; background: var(--bg-soft); z-index: 2;
}
.ed-preview .prose { padding-top: 20px; }
.ed-preview-title {
  max-width: 760px; margin: 0 auto; padding: 22px 20px 0;
  font-size: 30px; font-weight: 900; letter-spacing: -1px; line-height: 1.12;
}
.ed-preview-dek {
  max-width: 760px; margin: 10px auto 0; padding: 0 20px;
  font-family: var(--serif); font-style: italic; font-size: 18px; color: var(--text-dim);
}

/* Right-hand settings column, collapsible on mobile. */
.ed-side { display: flex; flex-direction: column; gap: 18px; }
.ed-panel { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px; }
.ed-panel h2 { margin: 0 0 14px; font-size: 12px; font-weight: 800; letter-spacing: 1.2px; text-transform: uppercase; color: var(--muted); }

.ed-cover {
  border: 1px dashed var(--line); border-radius: var(--radius-sm); padding: 22px 16px;
  text-align: center; color: var(--muted); font-size: 13px; cursor: pointer;
  transition: border-color .15s, background .15s;
}
.ed-cover:hover, .ed-cover.is-dropping { border-color: var(--accent); background: var(--surface-2); color: var(--text); }
.ed-cover-preview { position: relative; border-radius: var(--radius-sm); overflow: hidden; }
.ed-cover-preview img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.ed-cover-clear {
  position: absolute; top: 8px; right: 8px;
  background: rgba(0, 0, 0, .7); border: 1px solid var(--line); color: #fff;
  border-radius: 4px; padding: 4px 9px; font-size: 12px; font-weight: 700; cursor: pointer;
}
.ed-cover-clear:hover { background: var(--accent); border-color: var(--accent); }

.ed-check { display: flex; flex-direction: column; gap: 9px; }
.ed-check-item { display: flex; align-items: flex-start; gap: 9px; font-size: 13.5px; color: var(--muted); }
.ed-check-mark {
  width: 17px; height: 17px; flex: 0 0 17px; border-radius: 50%; margin-top: 1px;
  border: 1.5px solid var(--line); display: grid; place-items: center;
  font-size: 10px; font-weight: 900; color: transparent;
}
.ed-check-item.is-done { color: var(--text-dim); }
.ed-check-item.is-done .ed-check-mark { background: var(--ok); border-color: var(--ok); color: #06210f; }
.ed-check-item.is-optional .ed-check-mark { border-style: dashed; }

.ed-tagbox { display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  padding: 7px; background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius-sm); }
.ed-tagbox:focus-within { border-color: var(--accent); }
.ed-tagchip {
  display: inline-flex; align-items: center; gap: 5px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 20px;
  padding: 3px 5px 3px 10px; font-size: 12.5px; font-weight: 650;
}
.ed-tagchip button { background: none; border: 0; color: var(--muted); cursor: pointer; font-size: 14px; line-height: 1; padding: 0 3px; }
.ed-tagchip button:hover { color: var(--accent-hot); }
.ed-taginput { flex: 1; min-width: 90px; background: none; border: 0; font-size: 14px; padding: 4px; }
.ed-taginput:focus { outline: none; }

/* Block menu, opened by typing "/" on an empty line. Appended to
   <body> rather than nested in the editor so it can't be clipped by the
   compose column's overflow. */
.ed-slash {
  position: fixed; z-index: 150; width: 270px; max-height: 300px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 5px;
}
.ed-slash-item {
  display: flex; flex-direction: column; gap: 2px; width: 100%; text-align: left;
  background: none; border: 0; border-radius: var(--radius-sm); padding: 8px 10px; cursor: pointer;
}
.ed-slash-item:hover, .ed-slash-item.is-active { background: var(--surface-2); }
.ed-slash-label { font-size: 14px; font-weight: 700; color: var(--text); }
.ed-slash-hint { font-size: 12px; color: var(--muted); }

.ed-shortcuts {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 20px 24px; margin-bottom: 20px; max-width: 620px;
}
.ed-shortcuts h2 { margin: 0 0 14px; font-size: 12px; font-weight: 800; letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--muted); }
.ed-shortcuts dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 9px 18px; align-items: baseline; }
.ed-shortcuts dt { white-space: nowrap; }
.ed-shortcuts dd { margin: 0; font-size: 14px; color: var(--text-dim); }
.ed-shortcuts kbd {
  font-family: var(--mono); font-size: 12px; padding: 2px 6px;
  background: var(--bg-soft); border: 1px solid var(--line); border-bottom-width: 2px;
  border-radius: 4px; color: var(--text);
}
.ed-shortcuts .field-hint { margin: 16px 0 0; }

/* The header on the signed-in pages is drawn by site.js once
   /api/auth/me answers, so #siteHeader starts EMPTY and everything below
   it dropped 61px the moment it arrived. Measured on /dashboard: a
   layout shift of 0.046, the largest on the site and about thirty times
   the reader pages'.
   Reserving the height it is always going to take costs nothing and
   removes the jump outright. It does not fight the focus-mode rule
   below: an element set to display:none occupies no space whatever its
   min-height says. */
/* --header-h is the height of .masthead-inner; .masthead adds a 1px
   bottom border on top of that, so the reserve is 61px, not 60. Reserving
   the round number left a 1px shift behind — small, but it is the same
   bug, and a residual that size is exactly the sort that gets explained
   away later. */
#siteHeader { min-height: calc(var(--header-h) + 1px); }

/* Focus mode: everything that isn't the writing gets out of the way.
   The save indicator stays, because "is my work safe" is the one thing
   you still need to know. */
body.is-focus #siteHeader,
body.is-focus .ed-side,
body.is-focus .ed-modes,
body.is-focus .ed-count { display: none; }
body.is-focus .ed-bar { top: 0; }
body.is-focus .ed-shell { grid-template-columns: minmax(0, 1fr); }
body.is-focus .ed-layout { grid-template-columns: minmax(0, 1fr); }
body.is-focus .ed-preview { display: none; }
body.is-focus .ed-compose { max-width: 760px; margin: 0 auto; }
body.is-focus .ed-text { min-height: calc(100vh - 240px); }

/* ---- Admin -------------------------------------------------------- */

.adm-list { display: flex; flex-direction: column; gap: 10px; }
.adm-row {
  display: flex; align-items: center; gap: 14px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px;
}
.adm-row-main { flex: 1; min-width: 0; }
.adm-row-title { font-size: 15px; font-weight: 750; margin: 0 0 4px; }
.adm-row-sub { font-size: 13px; color: var(--muted); display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.adm-actions { display: flex; gap: 7px; align-items: center; flex-shrink: 0; flex-wrap: wrap; }

.adm-swatch { width: 14px; height: 14px; border-radius: 3px; flex: 0 0 14px; border: 1px solid rgba(255,255,255,.18); }
.adm-grab { cursor: grab; color: #454a58; padding: 0 4px; font-size: 15px; user-select: none; }
.adm-row.is-dragging { opacity: .45; border-color: var(--accent); }
.adm-row.is-over { border-top: 2px solid var(--accent); }

/* ---- Category editor ---- */

.adm-cat-row { transition: border-color .15s; }
.adm-cat-row:hover { border-color: #333846; }
.adm-cat-row.is-editing { display: block; padding: 20px; border-color: #333846; background: var(--surface-2); }
.adm-cat-row.is-danger { border-color: #4a2027; background: #1c1114; }

/* Reordering by keyboard and thumb. Drag alone is unusable from a
   keyboard and awkward on a phone, and this list sets the site nav. */
.adm-move { display: inline-flex; flex-direction: column; gap: 2px; margin-right: 4px; }
.adm-move-btn {
  background: var(--bg-soft); border: 1px solid var(--line); color: var(--text-dim);
  border-radius: 4px; width: 26px; height: 18px; line-height: 1;
  font-size: 11px; cursor: pointer; padding: 0;
}
.adm-move-btn:hover:not(:disabled) { background: var(--surface-2); color: var(--text); border-color: #333846; }
.adm-move-btn:disabled { opacity: .3; cursor: default; }

.adm-add { margin-bottom: 22px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
.adm-add > summary {
  cursor: pointer; padding: 14px 18px; font-weight: 700; font-size: 14px;
  list-style: none; display: flex; align-items: center; gap: 9px;
}
.adm-add > summary::-webkit-details-marker { display: none; }
.adm-add > summary:hover { color: var(--accent-hot); }
.adm-add[open] > summary { border-bottom: 1px solid var(--line); }
.adm-add-plus {
  display: grid; place-items: center; width: 20px; height: 20px; border-radius: 4px;
  background: var(--accent); color: var(--accent-ink); font-weight: 900; font-size: 14px;
}
.adm-add[open] .adm-add-plus { transform: rotate(45deg); }

.adm-cat-form { padding: 18px; display: flex; flex-direction: column; gap: 14px; }
.adm-cat-row.is-editing .adm-cat-form { padding: 0; }
.adm-cat-form .field { margin-bottom: 0; }
.adm-cat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; }

.adm-cat-editing-head {
  display: flex; align-items: center; gap: 10px; font-size: 14px;
  padding-bottom: 12px; border-bottom: 1px solid var(--line); margin-bottom: 2px;
}

.adm-cat-colour { display: flex; align-items: flex-end; gap: 18px; flex-wrap: wrap; }
.adm-swatches { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 7px; }
.adm-swatch-btn {
  width: 26px; height: 26px; border-radius: 6px; cursor: pointer; padding: 0;
  background: var(--sw); border: 2px solid transparent;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .12) inset;
  transition: transform .1s, border-color .1s;
}
.adm-swatch-btn:hover { transform: scale(1.12); }
.adm-swatch-btn.is-active { border-color: var(--text); }

/* The preview is the real .chip component with the real class, so what
   is shown here is exactly what renders on a card and an article. */
.adm-cat-preview { display: flex; flex-direction: column; gap: 7px; }
.adm-cat-preview .chip { align-self: flex-start; }

.adm-cat-actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 4px; }
.adm-slug-warning { margin: 0; }
.adm-reorder-hint { margin: 0 0 12px; }

.adm-pitch {
  margin: 10px 0 0; padding: 12px 14px; background: var(--bg-soft);
  border: 1px solid var(--line-soft); border-radius: var(--radius-sm);
  font-size: 14px; line-height: 1.6; color: var(--text-dim); white-space: pre-wrap;
}

.adm-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; align-items: end;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px; margin-bottom: 22px; }
.adm-form .field { margin-bottom: 0; }

.adm-color {
  width: 46px; height: 34px; flex: 0 0 46px; padding: 2px; cursor: pointer;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius-sm);
}

.adm-search { display: flex; gap: 10px; margin-bottom: 18px; flex-wrap: wrap; }
.adm-search .input { flex: 1; min-width: 200px; }
.adm-search .select { width: auto; min-width: 140px; }

.adm-email { font-family: var(--mono); font-size: 12.5px; color: var(--muted); }

/* ---- Account ------------------------------------------------------ */

.acct-grid { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: 28px; align-items: start; }
.acct-panel { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 24px; }
.acct-panel h2 { margin: 0 0 18px; font-size: 16px; font-weight: 800; letter-spacing: -.2px; }
.acct-avatars { display: grid; grid-template-columns: repeat(auto-fill, minmax(44px, 1fr)); gap: 7px; }
.acct-avatar {
  aspect-ratio: 1; display: grid; place-items: center; font-size: 21px; cursor: pointer;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius-sm);
  transition: border-color .12s, background .12s;
}
.acct-avatar:hover { background: var(--surface-2); }
.acct-avatar.is-active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent); }

.acct-meta { display: flex; flex-direction: column; gap: 12px; font-size: 14px; }
.acct-meta-row { display: flex; justify-content: space-between; gap: 12px; }
.acct-meta-k { color: var(--muted); }
.acct-rule { border: 0; border-top: 1px solid var(--line); margin: 20px 0; }

.acct-avatar-row { display: flex; gap: 18px; align-items: flex-start; }
.acct-avatar-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start; }
.acct-avatar-actions .field-hint { flex-basis: 100%; margin: 0; }

.acct-panel + .acct-panel { margin-top: 22px; }
.acct-2fa-on { display: flex; align-items: center; gap: 9px; margin: 0 0 10px; font-size: 14px; }

.acct-steps { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 22px; font-size: 14px; }
.acct-steps li::marker { color: var(--accent); font-weight: 800; }
.acct-steps strong { display: block; margin-bottom: 8px; }

/* The QR sits on its own white card. An inverted QR is out of spec and
   plenty of readers refuse it, so the code stays dark-on-white even
   though the page around it is dark. */
.acct-qr {
  display: inline-block; background: #fff; padding: 10px; border-radius: var(--radius-sm);
  line-height: 0; margin: 4px 0 12px;
}
.acct-qr svg { display: block; width: 200px; height: 200px; }

/* The secret is transcribed by hand into a phone, so it gets monospace,
   generous tracking and selectable text — the usual reason enrolment
   fails is a misread character. */
.acct-secret {
  font-family: var(--mono); font-size: 16px; letter-spacing: 2px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 12px 14px; word-break: break-all; user-select: all; color: var(--text);
}
.acct-secret-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }

.acct-codes {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 8px;
  margin: 14px 0 0;
}
.acct-codes code {
  font-family: var(--mono); font-size: 14px; text-align: center; user-select: all;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 9px 6px; color: var(--text);
}

@media (max-width: 600px) {
  .acct-avatar-row { flex-direction: column; }
}

/* ---- Responsive --------------------------------------------------- */

@media (max-width: 1180px) {
  /* Not enough width for writing, previewing and settings at once.
     Settings drop below, and split mode collapses to writing only —
     the mode buttons still reach the full preview. */
  .ed-shell { grid-template-columns: minmax(0, 1fr); }
  .ed-side { position: static; }
}

@media (max-width: 1080px) {
  .ed-layout { grid-template-columns: minmax(0, 1fr); }
  .ed-preview { position: static; max-height: none; }
  .ed-layout.is-split .ed-preview { display: none; }
}

@media (max-width: 900px) {
  .lede { grid-template-columns: minmax(0, 1fr); gap: 32px; }
  .acct-grid { grid-template-columns: minmax(0, 1fr); }
  .footer-inner { grid-template-columns: 1fr 1fr; }
  .article-foot { flex-direction: column; }
}

/* 1024, not 760.
 *
 * Measured on 2026-08-24: with seven categories the nav needs ~492px of
 * links, and at 900px it only had 463px — so `About` was being clipped
 * away by .nav's `overflow: hidden` with no scrollbar and no warning,
 * before any search box existed. Collapsing to the drawer earlier is
 * what stops links disappearing silently.
 *
 * This number assumes roughly six or fewer nav items. Categories are
 * admin-configurable, so adding several more needs this revisited —
 * there is no breakpoint that is correct for an unbounded list. */
@media (max-width: 1024px) {
  .nav-toggle { display: flex; }
  .nav {
    position: fixed; inset: var(--header-h) 0 auto 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--bg-soft); border-bottom: 1px solid var(--line);
    padding: 8px; overflow-y: auto; max-height: calc(100vh - var(--header-h));
    transform: translateY(-8px); opacity: 0; pointer-events: none;
    transition: opacity .16s, transform .16s;
  }
  .nav.is-open { transform: none; opacity: 1; pointer-events: auto; }
  .nav-link { padding: 12px 14px; font-size: 15px; }

  /* The nav goes `position: fixed` here, so it stops taking space in the
     header row and hands all of it to the search box. */
  .nav-search { flex: 1 1 auto; }
  .nav-search-input { font-size: 16px; }   /* 16px or iOS zooms on focus */
  .nav-link.is-active::after { left: 14px; width: 22px; right: auto; bottom: 6px; }

  .wordmark { font-size: 18px; }
  .grid { grid-template-columns: minmax(0, 1fr); gap: 24px; }
  .prose { font-size: 17.5px; }
  .article-head-inner { padding-top: 24px; }
  .footer-inner { grid-template-columns: 1fr; gap: 24px; }
  .stat-row { grid-template-columns: repeat(2, 1fr); }

  .dash-row { flex-wrap: wrap; }
  .dash-thumb { width: 56px; flex-basis: 56px; }
  .dash-actions { width: 100%; }

  .adm-row { flex-wrap: wrap; }
  .adm-actions { width: 100%; }

  .ed-bar-inner { gap: 8px; }
  .ed-status { min-width: 0; }
  .ed-toolbar { position: static; }
}

/* Below this the header cannot hold a field as well as the wordmark,
   the hamburger and the account chip — measured at 510px, where the four
   together overflowed by 11px and pushed the account off the edge.
   The field collapses and the magnifier remains: still one tap to
   search, and it submits empty, which lands on /search's own autofocused
   box. */
@media (max-width: 560px) {
  /* 20px of gap between four header items, plus 20px of padding each
     side, is 100px of a 375px screen. Tightening both is what pays for
     the search icon's slot.
     Measured at 393px: a signed-OUT header (the cached one every reader
     gets) needs 385px and fits. A signed-in one carries the Write button
     too and runs flush to the edge — tight, but nothing is clipped and
     nothing scrolls sideways. */
  .masthead-inner { gap: 12px; padding: 0 12px; }
  .nav-search {
    flex: 0 0 auto; min-width: 0; width: 38px; padding: 0;
    justify-content: center; background: none; border-color: transparent;
  }
  .nav-search:hover { border-color: transparent; }
  .nav-search:focus-within { background: none; border-color: transparent; }
  .nav-search-input { display: none; }
  .nav-search-go { width: 38px; height: 34px; }

  /* The wordmark, Write and the account chip together overflow a narrow
     header. The avatar and the title attribute still say whose account
     it is, so the name is what gives way.
     Moved here from the 480px block so that "compact header" is ONE
     breakpoint: between 480 and 560 the name was still showing while the
     corner's reserve had already shrunk, which put the reserve back out
     of step with the content it is meant to hold. */
  .account-name { display: none; }
  #accountSlot { min-width: 116px; }
}

@media (max-width: 480px) {
}

@media (max-width: 420px) {
  .stat-row { grid-template-columns: 1fr; }
  .auth-code { letter-spacing: 8px; font-size: 26px; }
}

/* ---- Image cropper ------------------------------------------------ */
/* Prefixed .crop- ; see the naming note at the top of this file. */

.crop-overlay {
  position: fixed; inset: 0; z-index: 400;
  background: rgba(0, 0, 0, .72); backdrop-filter: blur(3px);
  display: grid; place-items: center; padding: 20px;
}
.crop-dialog {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 22px; max-width: 380px; width: 100%;
  display: flex; flex-direction: column; align-items: center; gap: 12px;
}
.crop-title { margin: 0; font-size: 17px; font-weight: 800; letter-spacing: -.3px; align-self: flex-start; }

.crop-stage { position: relative; width: 320px; max-width: 100%; aspect-ratio: 1; }
.crop-canvas {
  width: 100%; height: 100%; display: block; border-radius: var(--radius-sm);
  background: var(--bg-soft); cursor: grab; touch-action: none;
}
.crop-canvas:active { cursor: grabbing; }

/* Shows the circle the avatar will actually be cropped to. Pointer
   events pass through, or it would eat every drag. */
.crop-mask {
  position: absolute; inset: 0; pointer-events: none; border-radius: var(--radius-sm);
  box-shadow: inset 0 0 0 9999px rgba(11, 12, 15, .55);
  /* `closest-side` is load-bearing. Without a size keyword CSS defaults
     to farthest-corner, so the percentages resolve against the half
     *diagonal* (226px here, not 160) — which drew a circle at 71% of the
     real radius and showed only half the area the finished avatar does.
     Whatever you framed, the avatar then revealed twice as much. */
  -webkit-mask: radial-gradient(circle closest-side at 50% 50%, transparent 99%, #000 100%);
  mask: radial-gradient(circle closest-side at 50% 50%, transparent 99%, #000 100%);
}

.crop-hint { margin: 0; font-size: 13px; color: var(--muted); text-align: center; }

/* The finished avatar, at the two sizes it is really shown at. The crop
   frame is 320px and the avatar is 88px or 22px, and things that look
   fine at 320 do not always survive the reduction. */
.crop-previews { display: flex; align-items: center; gap: 14px; width: 100%; }
.crop-previews canvas { border-radius: 50%; display: block; flex: 0 0 auto; }
.crop-previews-label { font-size: 12px; color: var(--muted); }

.crop-controls { display: flex; align-items: center; gap: 10px; width: 100%; }
.crop-zoom { flex: 1; min-width: 0; accent-color: var(--accent); cursor: pointer; }

/* Fit and Fill. Two clicks that cover the framings people actually want,
   so nobody has to hunt for them on the slider. */
.crop-step {
  background: var(--surface-2); border: 1px solid var(--line); color: var(--text-dim);
  border-radius: var(--radius-sm); padding: 5px 10px; font-size: 12px; font-weight: 700;
  cursor: pointer; flex: 0 0 auto;
}
.crop-step:hover { color: var(--text); border-color: #333846; }

.crop-actions { display: flex; gap: 8px; justify-content: flex-end; width: 100%; }

@media (max-width: 380px) {
  .crop-stage { width: 260px; }
}

/* ---- Admin: uploaded files ---------------------------------------- */

.adm-media-summary,
.adm-search-summary {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 12px;
  margin-bottom: 20px;
}
.adm-orphan {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 10px 14px;
}
.adm-orphan-thumb {
  width: 46px; height: 46px; flex: 0 0 46px; border-radius: var(--radius-sm);
  object-fit: cover; background: var(--surface-2); border: 1px solid var(--line);
}
.adm-orphan-main { flex: 1; min-width: 0; }

@media (max-width: 620px) {
  /* The key is monospace and long; below this the row cannot fit a
     thumbnail, the key and two buttons without pushing the page
     sideways, so the actions drop to their own line. */
  .adm-orphan .adm-actions { width: 100%; }
}
.adm-orphan-key {
  font-family: var(--mono); font-size: 12.5px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---- Legal pages -------------------------------------------------- */

.legal-head { text-align: center; padding: 40px 0 28px; border-bottom: 1px solid var(--line); margin-bottom: 8px; }
.legal-head h1 { margin: 0 0 10px; font-size: clamp(28px, 4vw, 40px); font-weight: 900; letter-spacing: -1.2px; }
.legal-lead { margin: 0 auto 12px; color: var(--text-dim); max-width: 46ch; }
.legal-updated {
  margin: 0; font-size: 12px; font-weight: 700; letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--muted);
}

/* Legal prose is scanned, not read start to finish, so the headings are
   heavier and better separated than in an article. */
.legal .prose h2 {
  font-size: 1.15em; margin-top: 2.2em;
  padding-top: 1.1em; border-top: 1px solid var(--line-soft);
}
.legal .prose h2:first-child { border-top: 0; padding-top: 0; margin-top: 1em; }
.legal .prose ul { margin-bottom: 1.4em; }
.legal .prose li { margin-bottom: .7em; }

.legal-foot {
  max-width: 760px; margin: 40px auto 60px; padding: 22px 20px 0;
  border-top: 1px solid var(--line); text-align: center;
}
.legal-foot p { margin: 0 0 8px; font-size: 14px; color: var(--text-dim); }
.legal-foot a { color: var(--accent-hot); text-decoration: underline; text-underline-offset: 3px; }

/* ---- Third-party sign-in ------------------------------------------ */

.auth-divider {
  display: flex; align-items: center; gap: 12px;
  margin: 22px 0 14px; font-size: 12px; font-weight: 700;
  letter-spacing: 1px; text-transform: uppercase; color: var(--muted);
}
.auth-divider::before, .auth-divider::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}

.auth-providers { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
.auth-provider {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 12px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--line); color: var(--text);
  font-size: 14px; font-weight: 700; cursor: pointer; text-decoration: none;
  transition: background .15s, border-color .15s;
}
.auth-provider:hover { background: #22262f; border-color: #333846; }
.auth-provider svg { flex: 0 0 auto; }

.adm-invite-code {
  font-family: var(--mono); font-size: 15px; letter-spacing: 1.5px;
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 3px 9px; user-select: all; color: var(--text);
}

/* ---- Search --------------------------------------------------------
 *
 * Reader chrome, so unprefixed, per the list at the top of this file.
 * Checked against the duplicate-class grep before naming: none of these
 * existed.
 */

/* Visually hidden but still read aloud. The search box's label needs to
   exist for screen readers without putting a second "Search" above an
   input that already says so. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.search-form { display: flex; gap: 8px; margin: 0 0 28px; }

.search-input {
  flex: 1 1 auto; min-width: 0;
  padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--line); color: var(--text);
  font-family: inherit; font-size: 16px;   /* 16px or iOS zooms on focus */
}
.search-input::placeholder { color: var(--muted); }
.search-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-color: var(--accent); }

/* type="search" draws a UA clear button in WebKit that ignores the dark
   palette entirely. */
.search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }

.search-submit { flex: 0 0 auto; }

/* Each result is a card plus its snippet, so the cell is a column rather
   than the bare card the other grids place. */
.search-result { display: flex; flex-direction: column; gap: 10px; min-width: 0; }

.search-snippet {
  margin: 0; color: var(--muted); font-size: 14px; line-height: 1.55;
  /* The snippet is a fragment from the middle of an article and has no
     business setting the row height. */
  overflow-wrap: anywhere;
}

/* The matched term. Outlined rather than the browser's yellow fill,
   which is unreadable on this background. */
.search-snippet mark {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--text); border-radius: 3px; padding: 0 3px; font-weight: 700;
}

@media (max-width: 560px) {
  .search-form { flex-wrap: wrap; }
  .search-submit { width: 100%; }
}

/* ---- Motion and polish ---------------------------------------------
 *
 * Everything here is decoration, and all of it is already covered by the
 * prefers-reduced-motion block near the top of this file, which zeroes
 * every transition and animation duration site-wide. Nothing below needs
 * its own reduced-motion guard except the view transitions, which are a
 * navigation behaviour rather than a duration.
 *
 * Reader chrome, so unprefixed. All names checked against the duplicate
 * grep before being added.
 */

/* Selected text. The browser default is a flat blue block that fights
   every other colour on the page; this is the site accent at a tint low
   enough to keep body text at full contrast rather than reversing it. */
::selection {
  background: color-mix(in srgb, var(--accent) 30%, transparent);
  color: var(--text);
  text-shadow: none;
}
/* An inline code run has its own background, so the same tint over it
   lands much darker. Lifted slightly so a selection stays visible. */
.prose code::selection,
.prose pre ::selection {
  background: color-mix(in srgb, var(--accent) 42%, transparent);
}

/* Cards lift toward the reader. The cover already scales on hover; the
   shadow under it is what stops that reading as the image drifting
   inside a static frame. Two pixels — enough to notice, not enough to
   shove the text around. */
.card:hover { transform: translateY(-2px); }
.card:hover .card-media { box-shadow: 0 12px 28px rgba(0, 0, 0, .5); }
/* The whole card is one target, so a keyboard user tabbing to the title
   should get the same response a mouse user gets. */
.card:focus-within { transform: translateY(-2px); }
.card:focus-within .card-media { box-shadow: 0 12px 28px rgba(0, 0, 0, .5); }

.hero:hover .hero-media { box-shadow: 0 18px 44px rgba(0, 0, 0, .5); }

/* The header earns a shadow only once the page has actually moved, so a
   page sitting at the top stays flat. site.js sets the class. */
.masthead.is-scrolled {
  box-shadow: 0 6px 24px rgba(0, 0, 0, .45);
  border-bottom-color: #2b2f39;
}

a.chip:focus-visible { outline-offset: 3px; }
.tag:hover { color: var(--text-dim); border-color: #333846; }

/* Reading progress, driven entirely by the scroll position with no
   JavaScript at all.
 *
 * Wrapped in @supports because scroll-driven animations are not
 * universal yet. Where they are missing the bar simply stays at
 * scaleX(0) — zero width, invisible — so the fallback is "no bar"
 * rather than a broken one, and there is no JS path to keep in step. */
.read-progress {
  position: fixed; inset: 0 0 auto 0; height: 2px; z-index: 200;
  background: linear-gradient(90deg, var(--accent), var(--accent-hot));
  transform: scaleX(0); transform-origin: 0 50%;
  pointer-events: none;
}
@supports (animation-timeline: scroll()) {
  .read-progress {
    /* Longhand, not the `animation` shorthand. The shorthand sets every
       property it omits back to its initial value, and for duration that
       is 0s — which for a scroll-driven animation means it is over
       before it starts and the bar never moves. `auto` is what maps the
       animation across the whole length of the timeline. */
    animation-name: read-progress;
    animation-duration: auto;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: scroll(root block);
  }
  @keyframes read-progress {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
  }
}

/* Cross-document view transitions: a short crossfade between reader
   pages instead of a white flash. Pure progressive enhancement — a
   browser without support ignores the at-rule and navigates as before,
   and there is no JavaScript involved either way.
 *
 * The masthead is named so it is treated as the same element on both
 * sides of a navigation and stays put, rather than crossfading with an
 * identical copy of itself. */
@view-transition { navigation: auto; }

@media (prefers-reduced-motion: reduce) {
  /* A navigation behaviour, not a duration, so the blanket rule at the
     top of this file does not cover it. */
  @view-transition { navigation: none; }
  .card:hover, .card:focus-within { transform: none; }
  .read-progress { display: none; }
}
