/*  rkroll default theme.
 *
 *  Photo-friendly: prose stays narrow for readability; figures break
 *  out of the prose column to fill the wider content area. Drives the
 *  custom-property surface documented in docs/theming.md; layered on
 *  top of base.css (which carries a11y + overflow primitives).
 */

:root {
  /* color tokens */
  --rkr-bg: #fdfdfb;
  --rkr-text: #1a1a1a;
  --rkr-muted: #707070;
  --rkr-rule: #e5e5e2;
  --rkr-link: #1a4f7f;
  --rkr-link-hover: #0d2c4d;
  --rkr-danger: #cf222e;
  --rkr-shadow: rgba(0, 0, 0, 0.08);

  /* layout tokens */
  --rkr-content: 76rem;     /* outer column for figures + galleries */
  --rkr-prose: 38rem;       /* inner column for prose */
  --rkr-gap: 1.25rem;
  --rkr-radius: 4px;

  /* type tokens */
  --rkr-prose-font: ui-serif, Georgia, "Times New Roman", Times, serif;
  --rkr-display-font: -apple-system, BlinkMacSystemFont, "Segoe UI",
    Roboto, "Helvetica Neue", Arial, sans-serif;
  --rkr-mono-font: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --rkr-base-size: 1.0625rem; /* 17px */
  --rkr-line-height: 1.6;

  /* structural tokens — themes override these to reshape chrome without
     writing raw class overrides against default.css selectors */
  --rkr-head-border: 1px solid var(--rkr-rule); /* border-bottom on .rkr-site-head */
  --rkr-head-padding: 1.25rem 1.5rem 0.85rem;
  --rkr-header-direction: row;       /* flex-direction on .rkr-site-head-inner */
  --rkr-header-text-align: left;     /* text-align on .rkr-site-head */
  --rkr-foot-border: 1px solid var(--rkr-rule);
  --rkr-title-size: 1.45rem;
  --rkr-title-weight: 700;
  --rkr-title-style: normal;
  --rkr-title-transform: none;
  --rkr-title-tracking: -0.01em;
  --rkr-h1-size: 2rem;
  --rkr-h1-style: normal;
  --rkr-h1-weight: bold;
  --rkr-h2-style: normal;
  --rkr-h2-weight: bold;
  --rkr-list-cols: 1fr 8rem;         /* grid-template-columns on .post-list li (title | fixed date col) */
  --rkr-blockquote-border: 3px solid var(--rkr-rule);
  --rkr-main-margin-left: auto;      /* override for asymmetric layouts (e.g. tufte) */
  --rkr-main-pad: 1.25rem 1.5rem;    /* padding on <main> */
  --rkr-figure-margin: 2rem;         /* vertical margin on .rkr-figure (horizontal stays auto) */
  --rkr-list-row-pad: 0.6rem;        /* vertical padding on .post-list li */
  --rkr-teaser-gap: 1.5rem;          /* teaser margin-bottom + padding-bottom */
}

@media (prefers-color-scheme: dark) {
  :root {
    --rkr-bg: #14140e;
    --rkr-text: #ebeae4;
    --rkr-muted: #9c9c95;
    --rkr-rule: #2a2a25;
    --rkr-link: #8fb3da;
    --rkr-link-hover: #b9d1eb;
    --rkr-shadow: rgba(0, 0, 0, 0.4);
  }
}

/* Theme-owned body typography. base.css covers margin reset +
   overflow-clip; everything visual (font, line height, colors)
   belongs to the theme. */
body {
  font-family: var(--rkr-prose-font);
  line-height: var(--rkr-line-height);
  color: var(--rkr-text);
  background: var(--rkr-bg);
}

/* ---- site chrome -------------------------------------------------- */

.rkr-site-head {
  border-bottom: var(--rkr-head-border);
  padding: var(--rkr-head-padding);
  text-align: var(--rkr-header-text-align);
}
.rkr-site-head .rkr-site-head-inner {
  max-width: var(--rkr-content);
  margin: 0 auto;
  display: flex;
  flex-direction: var(--rkr-header-direction);
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.rkr-site-head-brand {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
}
.rkr-site-title {
  font-family: var(--rkr-display-font);
  font-size: var(--rkr-title-size);
  font-weight: var(--rkr-title-weight);
  font-style: var(--rkr-title-style);
  text-transform: var(--rkr-title-transform);
  letter-spacing: var(--rkr-title-tracking);
  margin: 0;
}
.rkr-site-title a {
  color: inherit;
  text-decoration: none;
}
.rkr-site-tagline {
  color: var(--rkr-muted);
  font-size: 0.9rem;
}
.rkr-site-head-auth { display: flex; align-items: center; }
.rkr-site-head-auth-form { display: inline; margin: 0; }
.rkr-site-head-auth-btn {
  font-size: 0.8rem; color: var(--rkr-muted); text-decoration: none;
  background: none; border: none; padding: 0; cursor: pointer; font-family: inherit;
}
.rkr-site-head-auth-btn:hover { color: var(--rkr-link); text-decoration: underline; }

/* Posts-list rail controls: sort toggle + search at the top of the
   tag rail, side by side. Pure CSS; the form is a no-JS GET form. */
.rkr-rail-controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 1.9rem;
  margin-bottom: 0.75rem;
}
.rkr-rail-controls .rkr-sort-toggle { flex: none; margin: 0; }
.rkr-site-search { flex: 1; min-width: 0; margin: 0; display: flex; }
.rkr-site-search input {
  width: 100%;
  font: inherit;
  font-size: 0.85rem;
  color: var(--rkr-text);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--rkr-rule);
  padding: 0.15rem 0.25rem;
  transition: border-color 160ms ease;
}
.rkr-site-search input::placeholder { color: var(--rkr-muted); }
/* Static on focus — just a subtle underline accent, no expand/overlay. */
.rkr-site-search input:focus {
  outline: none;
  border-bottom-color: var(--rkr-link);
}
.rkr-tag-pills { display: block; }

/* ---- 404 (/not-found) -------------------------------------------- */
.rkr-notfound {
  max-width: var(--rkr-prose);
  margin: 4rem auto;
  padding: 0 1.25rem;
  text-align: center;
}
.rkr-notfound h1 {
  font-family: var(--rkr-display-font);
  font-size: 2.25rem;
  letter-spacing: -0.01em;
  margin: 0 0 0.75rem;
}
.rkr-notfound p {
  color: var(--rkr-muted);
  margin: 0 0 1.25rem;
}
.rkr-notfound a {
  color: var(--rkr-link);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
}
.rkr-notfound a:hover { color: var(--rkr-link-hover); }

/* ---- login (/admin/login) ---------------------------------------- */
.rkr-login {
  max-width: 24rem;
  margin: 2rem auto;
}
.rkr-login h1 {
  font-family: var(--rkr-display-font);
  font-size: 1.5rem;
  margin: 0 0 1.5rem;
}
.rkr-login hr {
  border: 0;
  border-top: 1px solid var(--rkr-rule);
  margin: 2rem 0;
}
.rkr-login-google {
  display: inline-block;
  padding: 0.5rem 1rem;
  border: 1px solid var(--rkr-rule);
  border-radius: var(--rkr-radius);
  text-decoration: none;
  color: var(--rkr-text);
}
.rkr-login-google:hover { color: var(--rkr-link); }
.rkr-login-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.rkr-login-form label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.9rem;
}
.rkr-login-form input {
  padding: 0.5rem;
  font: inherit;
  border: 1px solid var(--rkr-rule);
  border-radius: var(--rkr-radius);
  background: var(--rkr-bg);
  color: var(--rkr-text);
}
.rkr-login-submit {
  padding: 0.5rem 1rem;
  font: inherit;
  background: var(--rkr-link);
  color: var(--rkr-bg);
  border: 1px solid var(--rkr-link);
  border-radius: var(--rkr-radius);
  cursor: pointer;
}
.rkr-login-submit:hover { background: var(--rkr-link-hover); border-color: var(--rkr-link-hover); }
.rkr-login-hint { color: var(--rkr-muted); font-size: 0.9rem; }

/* ---- admin posts list (admin-only /admin/posts) ----------------- */
.rkr-admin-posts-heading {
  font-family: var(--rkr-display-font);
  font-size: 1.5rem;
  margin: 0 0 1rem;
}
/* Horizontal-scroll container for the admin posts table on narrow
   viewports. Without this, the table widens the body past the
   viewport and iOS Safari anchors fixed-position FABs to the body's
   right edge — shoving the + and gear buttons off-screen. The
   wrapper absorbs the overflow so the body stays viewport-width. */
.rkr-admin-posts-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 0 -0.5rem;
  padding: 0 0.5rem;
}
.rkr-admin-posts {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.95rem;
  /* Don't let cell wrap on narrow viewports — the scroll container
     above provides the horizontal escape. Specific cells (title)
     can opt into wrapping locally. */
  white-space: nowrap;
}
.rkr-admin-posts td:first-child,
.rkr-admin-posts th:first-child { white-space: normal; }
.rkr-admin-posts th, .rkr-admin-posts td {
  text-align: left;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--rkr-rule);
  /* Mixed content per row — text, a <select>, a <button>, a <time>.
     Baseline alignment would shift each by its own font metrics; pin
     everything to the row's vertical centre so columns read as a real
     table. */
  vertical-align: middle;
}
.rkr-admin-posts th {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--rkr-muted);
  font-weight: 500;
}
.rkr-admin-posts td a {
  color: var(--rkr-text);
  text-decoration: none;
}
.rkr-admin-posts td a:hover { color: var(--rkr-link); text-decoration: underline; }
.rkr-admin-posts-status {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--rkr-rule);
  background: transparent;
  color: var(--rkr-muted);
  cursor: pointer;
  font: inherit; font-size: 0.75rem;
}
.rkr-admin-posts-status.is-published {
  border-color: color-mix(in srgb, var(--rkr-link) 40%, transparent);
  color: var(--rkr-link);
}
.rkr-admin-posts-status-form { display: inline; margin: 0; }
.rkr-admin-posts-status-btn {
  background: none; border: 0; padding: 0.2rem; cursor: pointer;
  display: inline-flex; align-items: center; vertical-align: middle; line-height: 1;
  color: var(--rkr-muted);
}
.rkr-admin-posts-status-btn.is-published { color: var(--rkr-link); }
.rkr-admin-posts-status-btn:hover { color: var(--rkr-link); }
/* Each action lives in its own column so the controls stack on a
   vertical line across rows. `width:1%` lets the browser shrink-wrap
   the column to the widest cell content; the title cell soaks up the
   remaining width. white-space:nowrap stops the column from collapsing
   onto multiple lines under tight viewports. */
.rkr-admin-posts-action {
  width: 1%;
  white-space: nowrap;
  text-align: right;
}
/* WCAG-compliant visually-hidden label for the per-row status select. */
.rkr-vh {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.rkr-admin-posts-del { display: inline; margin: 0; }
.rkr-admin-posts-del-btn,
.rkr-admin-posts-pin {
  background: none; border: 0; padding: .2rem; cursor: pointer;
  color: var(--rkr-muted); display: inline-flex; align-items: center;
  vertical-align: middle; line-height: 1;
}
/* Destructive: muted at rest, red on hover/focus. Same pattern
   used for every trash button in the admin (figure delete, cell
   delete, posts-list delete) — see --rkr-danger token + the
   admin-styles.ts rules. */
.rkr-admin-posts-del-btn:hover,
.rkr-admin-posts-del-btn:focus-visible { color: var(--rkr-danger); }
.rkr-admin-posts-pin:hover:not(:disabled) { color: var(--rkr-link); }
.rkr-admin-posts-pin:disabled { cursor: progress; opacity: 0.6; }
/* Pin icon shows CURRENT STATE, not the click action: when pinned,
   the pin is the active accent colour; when not, it's muted (same
   default as the rest button). The icon swap (pin vs pin-off) in
   posts-list.ts reinforces it visually. */
.rkr-admin-posts-pin[aria-pressed="true"] { color: var(--rkr-link); }
.rkr-admin-posts-empty {
  color: var(--rkr-muted);
  text-align: center;
  padding: 2rem;
  font-style: italic;
}
.rkr-admin-posts-empty a { color: var(--rkr-link); }

/* Sort toggle — anonymous view: plain link; admin view: button.
   Both get the same look: muted small text with leading icon. */
.rkr-sort-toggle {
  display: inline-flex; align-items: center; gap: 0.3rem;
  font: inherit; font-size: 0.85rem;
  color: var(--rkr-muted); text-decoration: none;
  background: none; border: 0; padding: 0; cursor: pointer;
  margin-bottom: 0.75rem;
}
.rkr-sort-toggle:hover { color: var(--rkr-link); }
/* In the header nav the toggle is a flex row item — drop the
   column-layout bottom margin so it aligns with the nav links. */
.rkr-site-head-nav .rkr-sort-toggle { margin-bottom: 0; }

/* Tag rail — right sidebar on the index page. */
.rkr-index-layout { display: block; }
.rkr-index-layout--has-rail {
  display: flex;
  align-items: flex-start;
  gap: 2rem;
  max-width: var(--rkr-content);
  margin: 0 auto;
}
.rkr-index-posts { flex: 4; min-width: 0; }
.rkr-tag-rail {
  flex: 1;
  padding-top: 0.25rem;
  position: sticky;
  top: 1rem;
  align-self: flex-start;
}
@media (max-width: 640px) {
  .rkr-index-layout--has-rail { flex-direction: column; }
  .rkr-tag-rail { width: 100%; border-top: 1px solid var(--rkr-rule); padding-top: 1rem; }
}
.rkr-tag-pill {
  display: inline-block;
  font-size: 0.85rem;
  padding: 0.15rem 0.6rem;
  margin: 0.2rem 0.15rem;
  border-radius: 999px;
  border: 1px solid var(--rkr-rule);
  color: var(--rkr-text); text-decoration: none;
  white-space: nowrap;
}
.rkr-tag-pill:hover { border-color: var(--rkr-link); color: var(--rkr-link); }
.rkr-tag-pill[aria-current="page"] {
  background: var(--rkr-link); border-color: var(--rkr-link); color: var(--rkr-bg);
}


/* Editor tag-input chip widget (inside .rkr-meta grid). */
.rkr-tag-input {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem;
  min-height: 2.1rem; padding: 0.25rem 0.4rem;
  border: 1px solid var(--rkr-rule); border-radius: var(--rkr-radius);
  background: var(--rkr-bg); cursor: text;
}
.rkr-tag-input:focus-within { outline: 2px solid var(--rkr-link); outline-offset: -1px; }
.rkr-tag-input-field {
  border: 0; outline: 0; background: transparent;
  font: inherit; font-size: 0.9rem; flex: 1; min-width: 8rem; color: var(--rkr-text);
}
.rkr-tag-pill-editor {
  display: inline-flex; align-items: center; gap: 0.2rem;
  font-size: 0.8rem; padding: 0.1rem 0.5rem;
  background: color-mix(in srgb, var(--rkr-link) 12%, transparent);
  color: var(--rkr-link); border-radius: 999px;
}
.rkr-tag-remove {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: inherit; font-size: 0.95rem; line-height: 1; opacity: 0.65;
}
.rkr-tag-remove:hover { opacity: 1; }

/* Settings page: a vertical two-column grid mirroring the editor's
   meta panel (label on the left, input on the right). */
.rkr-admin-settings-heading { font-size: 1.5rem; margin: 1rem 0; }
.rkr-admin-settings {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.5rem 0.75rem;
  align-items: center;
  max-width: 40rem;
  margin: 1rem 0;
}
.rkr-admin-settings label { color: var(--rkr-muted); }
.rkr-admin-settings input,
.rkr-admin-settings select {
  padding: 0.35rem 0.5rem;
  font: inherit;
  min-width: 0; /* let inputs shrink inside the 1fr column on narrow viewports */
}
/* Number knobs hold 1–4 digits; let them sit as a narrow chip on the
   left of the column instead of stretching to fill the 1fr track. */
.rkr-admin-settings input[type="number"] {
  width: 7rem;
  justify-self: start;
}
.rkr-admin-settings-hint {
  grid-column: 1 / -1;
  margin: 0.25rem 0;
  color: var(--rkr-muted);
  font-size: 0.85rem;
}
.rkr-admin-settings-section {
  grid-column: 1 / -1;
  font-size: 1.15rem;
  margin: 1.5rem 0 0.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rkr-rule);
}
.rkr-admin-settings-hint code {
  background: color-mix(in srgb, var(--rkr-text) 8%, transparent);
  padding: 0 0.25rem;
  border-radius: 3px;
}
.rkr-admin-settings-submit {
  grid-column: 1 / -1;
  justify-self: start;
  padding: 0.4rem 0.9rem;
  cursor: pointer;
}
/* Build chip at the bottom of /admin/settings — muted footer hint
   so the operator can confirm which deploy is live. The short
   hash (12 chars) is the click-friendly form; the full sha lives
   in the <code title=…> tooltip for copy/paste verification. */
.rkr-admin-settings-build {
  margin: 2rem 0 0;
  color: var(--rkr-muted);
  font-size: 0.85rem;
}
.rkr-admin-settings-build code {
  background: color-mix(in srgb, var(--rkr-text) 6%, transparent);
  padding: 0 0.25rem;
  border-radius: 3px;
}
.rkr-admin-settings-flash {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--rkr-rule);
  border-radius: 4px;
  max-width: 40rem;
}
.rkr-admin-settings-flash.is-ok {
  border-color: color-mix(in srgb, var(--rkr-link) 40%, transparent);
  color: var(--rkr-link);
}
.rkr-admin-settings-flash.is-error {
  border-color: #cf222e;
  color: #cf222e;
}

.rkr-site-foot {
  border-top: var(--rkr-foot-border);
  margin-top: 4rem;
  padding: 1.5rem;
  font-size: 0.85rem;
  color: var(--rkr-muted);
  text-align: center;
}


/* Floating action buttons (FABs) — replaced the old admin chrome
   strip. Circular icon buttons fixed to the bottom-right corner of
   the viewport. Slots stack upward: --slot-1 is the lowest, --slot-2
   sits above it, and so on. Page templates emit FABs only when
   isAdmin so anonymous visitors see clean pages. */
.rkr-fab {
  position: fixed;
  right: 1.25rem;
  z-index: 50;
  width: 3rem; height: 3rem;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px;
  border: 1px solid var(--rkr-rule);
  background: var(--rkr-bg);
  color: var(--rkr-link);
  text-decoration: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
  transition: transform 80ms ease, box-shadow 80ms ease;
}
.rkr-fab:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.22);
}
.rkr-fab:focus-visible { outline: 2px solid var(--rkr-link); outline-offset: 2px; }
.rkr-fab--slot-1 { bottom: 1.25rem; }
.rkr-fab--slot-2 { bottom: 5rem; }
.rkr-fab--slot-3 { bottom: 8.75rem; }

/* ---- main column -------------------------------------------------- */

main {
  max-width: var(--rkr-content);
  margin: 0 auto;
  margin-left: var(--rkr-main-margin-left);
  padding: var(--rkr-main-pad);
}

/* ---- prose -------------------------------------------------------- */

/* Post page matches the posts-list page width: both are bounded by
   --rkr-content (the list has no prose clamp; main caps it there).
   Deliberately wider than --rkr-prose per request — parity with the
   list is preferred over the narrower prose measure. */
article {
  max-width: var(--rkr-content);
  margin: 0 auto;
}

article > header {
  margin-bottom: 2rem;
  position: relative;
}
article > header h1 {
  font-family: var(--rkr-display-font);
  font-size: var(--rkr-h1-size);
  font-style: var(--rkr-h1-style);
  font-weight: var(--rkr-h1-weight);
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin: 0 0 .5rem;
  padding-right: 6.5rem;
}
/* Copy-link icon sitting flush after the post title. Inherits the
   title's font-size-down to keep it visually balanced; padding lets
   the click target meet the WCAG 24x24 minimum. The data-state
   attribute is set by /static/site/copy-link.js for ~1.5s after a
   click — green for a successful copy, red for a clipboard error;
   matches the editor's rkr-copy-link affordance so the gesture is
   the same wherever the author lives. */
.rkr-post-copylink {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--rkr-radius);
  color: var(--rkr-muted);
  padding: 0.25rem 0.4rem;
  margin-left: 0.5rem;
  vertical-align: middle;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.rkr-post-copylink:hover,
.rkr-post-copylink:focus-visible {
  color: var(--rkr-link);
  border-color: var(--rkr-rule);
}
.rkr-post-copylink[data-state='copied'] {
  color: #2ea44f;
  border-color: color-mix(in srgb, #2ea44f 30%, transparent);
}
.rkr-post-copylink[data-state='error'] {
  color: #cf222e;
  border-color: color-mix(in srgb, #cf222e 30%, transparent);
}

/* Comment-count bubble: flush top-right of the post header, mirrors
   the roll-along (Twenty Eleven) treatment. Plain anchor → #respond
   (the comment form); no JS. Colours track the copy-link affordance. */
.rkr-comment-bubble {
  position: absolute;
  top: 0;
  right: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--rkr-muted);
  border: 1px solid transparent;
  border-radius: var(--rkr-radius);
  padding: 0.5rem 0.9rem;
  font-size: 1.6rem;
  line-height: 1;
  text-decoration: none;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.rkr-comment-bubble:hover,
.rkr-comment-bubble:focus-visible {
  color: var(--rkr-link);
  border-color: var(--rkr-rule);
}
.rkr-comment-bubble-count {
  font-weight: 600;
}

/* Subtitle: under the h1, muted, slightly larger than body so it
   reads as a deck head rather than a stray paragraph. */
.rkr-post-subtitle {
  font-family: var(--rkr-display-font);
  font-size: 1.1rem;
  font-weight: 400;
  color: var(--rkr-muted);
  margin: 0 0 .5rem;
  line-height: 1.35;
}
article > header time {
  display: block;
  color: var(--rkr-muted);
  font-size: 0.9rem;
  font-family: var(--rkr-display-font);
}

article p,
article ul,
article ol,
article blockquote {
  margin: 1.1em 0;
}
article h2,
article h3,
article h4 {
  font-family: var(--rkr-display-font);
  font-style: var(--rkr-h2-style);
  font-weight: var(--rkr-h2-weight);
  letter-spacing: -0.015em;
  line-height: 1.25;
  margin: 2.4em 0 .6em;
}
article h2 { font-size: 1.5rem; }
article h3 { font-size: 1.2rem; }
article h4 { font-size: 1rem; text-transform: uppercase; letter-spacing: 0.05em; }

article a {
  color: var(--rkr-link);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
article a:hover { color: var(--rkr-link-hover); }

article blockquote {
  border-left: var(--rkr-blockquote-border);
  padding-left: 1rem;
  color: var(--rkr-muted);
  font-style: italic;
}

article code {
  font-family: var(--rkr-mono-font);
  font-size: 0.92em;
  background: var(--rkr-rule);
  padding: 0.1em 0.35em;
  border-radius: var(--rkr-radius);
}
article pre {
  font-family: var(--rkr-mono-font);
  background: var(--rkr-rule);
  padding: 1rem;
  border-radius: var(--rkr-radius);
  overflow-x: auto;
  font-size: 0.9rem;
  line-height: 1.45;
}
article pre code {
  background: transparent;
  padding: 0;
}

article hr {
  border: 0;
  border-top: 1px solid var(--rkr-rule);
  margin: 3rem auto;
  width: 8ch;
}

/* ---- ::figure widget (unified, spec.md §9) ----------------------- */
/* The only image directive (legacy widgets removed). Grid mode for
   uniform NxM layouts; flow modes for justified / masonry; carousel
   mode kicks in when len(ids) exceeds matrix capacity. Per-cell styles
   live below; figure-level shell + caption styling here. */

.rkr-figure {
  display: block;
  margin: var(--rkr-figure-margin) auto;
}
/* When the banner/hero is the page-leading element (bannerAboveHeader),
   it is a direct body child before the header — kill its top margin so
   it sits flush to the viewport top, and its bottom margin so the
   header sits close beneath it (the header's own top padding supplies
   the gap). Vertical only — preserve the bleed's horizontal margins.
   Positional, so not a token. */
body > .rkr-figure:first-child {
  margin-top: 0;
  margin-bottom: 0;
}
.rkr-figure-grid {
  display: grid;
  gap: var(--rkr-gap);
  width: 100%;
}
.rkr-figure-cell {
  display: block;
  /* Reserve aspect-ratio per cell. Defaults to the figure-level
     --rkr-cell-aspect (set on the <figure>); cells override with their
     own --rkr-image-aspect when no explicit aspect is given on the
     directive. */
  aspect-ratio: var(--rkr-cell-aspect, var(--rkr-image-aspect, 1));
  overflow: hidden;
  border-radius: var(--rkr-radius);
  background: var(--rkr-rule);
}
.rkr-figure-cell picture,
.rkr-figure-cell img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 0;
  box-shadow: none;
}
.rkr-fit-cover .rkr-figure-cell img { object-fit: cover; }
.rkr-fit-contain .rkr-figure-cell img { object-fit: contain; }

/* Block placement (mirrors rkr-pos-* but renamed per spec). */
.rkr-justify-center {
  width: 100%;
  max-width: var(--rkr-content);
  margin-left: auto;
  margin-right: auto;
}
.rkr-justify-left {
  float: left;
  width: 40%;
  margin: 0.5rem 1rem 0.5rem 0;
  shape-margin: 0.5rem;
}
.rkr-justify-right {
  float: right;
  width: 40%;
  margin: 0.5rem 0 0.5rem 1rem;
  shape-margin: 0.5rem;
}
@media (max-width: 600px) {
  .rkr-justify-left,
  .rkr-justify-right { float: none; width: 100%; margin: 1rem 0; }
}
.rkr-justify-full {
  width: 100%;
  max-width: var(--rkr-content);
  margin-left: auto;
  margin-right: auto;
}
.rkr-justify-bleed {
  /* Break out of the containing column to span the full viewport. */
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  max-width: none;
}
/* A full-bleed figure runs edge-to-edge against the viewport — rounded
   corners there read as a mistake. Square the cell (the cell carries the
   radius + overflow:hidden; the inner img is already radius 0). */
.rkr-justify-bleed .rkr-figure-cell { border-radius: 0; }
/* Anon posts-list teaser hero: never break out of the posts column
   (a bleed/full banner otherwise spans 100vw and overlaps the sticky
   tag rail). Force the centered, column-contained treatment. The
   teaser is anon-only; this does not touch the post page's own banner.
   Higher specificity than .rkr-justify-* so it wins regardless of the
   featured post's justify. */
.rkr-teaser .rkr-figure {
  width: 100%;
  max-width: var(--rkr-content);
  margin-left: auto;
  margin-right: auto;
}
/* Inline mode wraps the picture in a <span> rather than <figure>; styling
   matches the legacy rkr-pos-inline so authors get identical behaviour. */
span.rkr-justify-inline {
  display: inline-block;
  margin: 0 0.25em;
  vertical-align: text-bottom;
}
span.rkr-justify-inline img {
  display: inline-block;
  height: 1.5em;
  width: auto;
  border-radius: 0;
  box-shadow: none;
}

.rkr-figure > figcaption {
  margin-top: 0.5rem;
  font-family: var(--rkr-display-font);
  font-size: 0.875rem;
  color: var(--rkr-muted);
  text-align: center;
}

/* ---- ::figure flow layouts (justified, masonry) ------------------- */
/* Both modes are CSS-only. Each image keeps its native aspect;
   `aspect` / `fit` are ignored at the directive level. */

.rkr-figure-justified .rkr-figure-flow {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rkr-gap);
}
.rkr-figure-justified .rkr-figure-cell {
  /* Each cell is a fixed-height row item; aspect-ratio drives intrinsic
     width. flex-grow with a basis of auto makes rows fill the container
     by stretching items proportionally — the standard CSS flex-justified
     trick. The visual compromise is that the LAST row also stretches
     (Flickr's original behaviour leaves it un-stretched); pure CSS
     can't distinguish "last incomplete row" without JS. Acceptable
     trade-off for now. */
  height: var(--rkr-row-height, 240px);
  flex: 1 1 auto;
  aspect-ratio: var(--rkr-image-aspect, 1.5);
  /* Override the grid-mode defaults that would force a fixed cell
     aspect from --rkr-cell-aspect. */
  background: transparent;
  overflow: hidden;
  border-radius: var(--rkr-radius);
}
.rkr-figure-justified .rkr-figure-cell picture,
.rkr-figure-justified .rkr-figure-cell img {
  width: 100%;
  height: 100%;
  object-fit: cover; /* fill the row-height box; aspect mismatch is rare */
}

.rkr-figure-masonry .rkr-figure-flow {
  /* Multi-column layout with the column count from --rkr-cols. Modern
     browsers (Firefox 110+, Chrome 119+, Safari 18+) also support
     `grid-template-rows: masonry` — when adoption is broad enough we
     can switch for a more Pinterest-faithful "shortest column" layout.
     `column-count` flows top-to-bottom which is visually similar. */
  column-count: var(--rkr-cols, 3);
  column-gap: var(--rkr-gap);
  display: block;
}
.rkr-figure-masonry .rkr-figure-cell {
  display: block;
  break-inside: avoid;
  margin: 0 0 var(--rkr-gap) 0;
  /* Override grid-mode aspect-ratio so each masonry cell's height
     follows its image's native aspect. */
  aspect-ratio: var(--rkr-image-aspect, 1);
  background: transparent;
  overflow: hidden;
  border-radius: var(--rkr-radius);
}
.rkr-figure-masonry .rkr-figure-cell picture,
.rkr-figure-masonry .rkr-figure-cell img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---- carousel widget ---------------------------------------------- */
/* Scroll-snap drives the actual slide motion: native touch swipe works
   for free, no JS swipe handler needed. Buttons + dots are wired by
   src/site/carousel.ts. */

.rkr-carousel {
  position: relative;
  display: block;
  width: 100%;
  max-width: var(--rkr-content);
  margin: 2.5rem auto;
  outline: none;
}
.rkr-carousel:focus-visible {
  box-shadow: 0 0 0 3px var(--rkr-link);
  border-radius: var(--rkr-radius);
}
.rkr-carousel > figcaption {
  margin-top: .75rem;
  text-align: center;
  color: var(--rkr-muted);
  font-size: 0.9rem;
  font-style: italic;
  max-width: var(--rkr-prose);
  margin-left: auto;
  margin-right: auto;
}

.rkr-carousel-track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;             /* Firefox */
  -webkit-overflow-scrolling: touch; /* iOS momentum */
  border-radius: var(--rkr-radius);
}
.rkr-carousel-track::-webkit-scrollbar { display: none; }
/* WCAG 2.3.3: respect prefers-reduced-motion. Auto-scroll instead of
   smooth keeps the carousel functional without animating motion the
   user explicitly opted out of. */
@media (prefers-reduced-motion: reduce) {
  .rkr-carousel-track { scroll-behavior: auto; }
}

.rkr-carousel-slide {
  flex: 0 0 100%;
  scroll-snap-align: start;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Reserve height from --aspect set by the renderer. */
  aspect-ratio: var(--aspect, 1.5);
  background: var(--rkr-rule);
}
.rkr-carousel-slide picture,
.rkr-carousel-slide img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* ---- ::figure carousel (multi-cell pages) ------------------------- */
/* When a figure overflows its matrix, each "slide" is a page containing
   `matrix.rows × matrix.cols` cells. Pages compose with the legacy
   .rkr-carousel-* CSS for the track + nav + dots; only the page contents
   differ (a grid instead of a single centered image). */

.rkr-figure-page {
  /* Override the single-image-slide assumptions inherited from
     .rkr-carousel-slide. Pages are grids; the inline `style` from the
     widget sets grid-template-rows/columns to the matrix shape. */
  display: grid;
  align-items: stretch;
  justify-content: stretch;
  aspect-ratio: auto;
  background: transparent;
  gap: var(--rkr-gap);
  padding: 0;
}
.rkr-figure-page .rkr-figure-cell {
  /* Each cell behaves the same as in the non-carousel grid path. */
  aspect-ratio: var(--rkr-cell-aspect, var(--rkr-image-aspect, 1));
}
/* Object-fit on the cell image itself is determined by the figure's
   rkr-fit-* class; the picture/img inside the page stretches to the cell. */
.rkr-figure-page picture,
.rkr-figure-page img {
  width: 100%;
  height: 100%;
}

/* Nav row sits below the track; on hover, prev/next overlay the image. */
.rkr-carousel-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  margin-top: .5rem;
}
.rkr-carousel-prev,
.rkr-carousel-next,
.rkr-carousel-play {
  background: transparent;
  border: 1px solid var(--rkr-rule);
  color: var(--rkr-text);
  border-radius: 999px;
  width: 2rem;
  height: 2rem;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.rkr-carousel-prev:hover,
.rkr-carousel-next:hover,
.rkr-carousel-play:hover {
  background: var(--rkr-rule);
}

.rkr-carousel-dots {
  display: inline-flex;
  gap: .25rem;
  align-items: center;
}
/* WCAG 2.5.8 (AA, 2.2): 24x24px hit area. The visible dot stays small
   for design; padding + box-shadow make the dot itself, but the
   button is sized for the touch/click target. */
.rkr-carousel-dot {
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 50%;
  border: none;
  background: transparent;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.rkr-carousel-dot::after {
  content: '';
  display: block;
  width: .55rem;
  height: .55rem;
  border-radius: 50%;
  border: 1px solid var(--rkr-muted);
  background: transparent;
}
.rkr-carousel-dot.is-active::after {
  background: var(--rkr-text);
  border-color: var(--rkr-text);
}

/* ---- index page --------------------------------------------------- */

/* Visually-hidden but accessible — the index has site chrome that already
   shows the title, but every page needs a document-level <h1>. */
.rkr-index-heading {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%);
  white-space: nowrap; border: 0;
}

.post-list {
  list-style: none;
  padding: 0;
  max-width: var(--rkr-prose);
  margin: 0 auto;
}
.post-list li {
  display: grid;
  grid-template-columns: var(--rkr-list-cols);
  gap: 1.25rem;
  align-items: baseline;
  padding: var(--rkr-list-row-pad) 0;
  border-bottom: 1px solid var(--rkr-rule);
}
.post-list li:last-child {
  border-bottom: 0;
}
.post-list time {
  /* Left-justified within the fixed-width date column so date-only
     and date+time labels share the same left edge regardless of
     length. (A max-content column collapses under the 1fr title and
     defeats this — hence the fixed track in --rkr-list-cols.) */
  text-align: left;
  color: var(--rkr-muted);
  font-size: 0.85rem;
  font-family: var(--rkr-display-font);
  white-space: nowrap;
}
.post-list a {
  font-family: var(--rkr-display-font);
  color: var(--rkr-text);
  text-decoration: none;
  font-size: 1.05rem;
  font-weight: 500;
}
.post-list a:hover {
  color: var(--rkr-link);
}

/* ---- teaser (anonymous index, postTeaser on) ---------------------- */
.rkr-teaser {
  max-width: var(--rkr-content);
  margin: 0 auto var(--rkr-teaser-gap);
  padding-bottom: var(--rkr-teaser-gap);
  border-bottom: 1px solid var(--rkr-rule);
}
/* Teaser hero leads the page (first element in the first .rkr-teaser),
   so kill its inherited --rkr-figure-margin top — main's padding already
   sets the header gap. Mirrors body > .rkr-figure:first-child. */
.rkr-teaser .rkr-figure { margin-top: 0; margin-bottom: 1.25rem; }
.rkr-teaser-body { max-width: var(--rkr-prose); margin: 0 auto; }
.rkr-teaser-title {
  font-family: var(--rkr-display-font);
  font-size: 1.75rem;
  line-height: 1.2;
  margin: 0 0 0.35rem;
}
.rkr-teaser-title a { color: var(--rkr-text); text-decoration: none; }
.rkr-teaser-title a:hover { color: var(--rkr-link); }
.rkr-teaser time {
  display: block;
  color: var(--rkr-muted);
  font-family: var(--rkr-display-font);
  font-size: 0.85rem;
  margin-bottom: 0.75rem;
}
.rkr-teaser-excerpt { font-family: var(--rkr-prose-font); color: var(--rkr-text); }
.rkr-teaser-excerpt p { margin: 0 0 0.75rem; }
.rkr-teaser-more {
  display: block;
  width: fit-content;
  margin-left: auto;
  font-family: var(--rkr-display-font);
  font-size: 0.9rem;
  color: var(--rkr-link);
  text-decoration: none;
}
.rkr-teaser-more:hover { text-decoration: underline; }

/* pager */
nav[aria-label="pagination"] {
  margin: 2.5rem auto;
  text-align: center;
  font-size: 0.9rem;
  color: var(--rkr-muted);
}
nav[aria-label="pagination"] a {
  color: var(--rkr-link);
  margin: 0 .75rem;
  text-decoration: none;
}

/* ---- mobile ------------------------------------------------------- */

@media (max-width: 640px) {
  html { font-size: 16px; }
  main { padding: 1.25rem 1rem; }
  .rkr-site-head { padding: 1rem 1rem 0.75rem; }
  article > header h1 { font-size: 1.6rem; }
  .post-list li {
    grid-template-columns: 1fr;
    gap: 0.25rem;
  }
  /* Wrap-positioned figures stack at mobile widths — wrapping prose
     around tiny images on narrow screens is unreadable. */
  .rkr-justify-left,
  .rkr-justify-right {
    float: none;
    width: 100%;
    max-width: var(--rkr-prose);
    margin: 1.5rem auto;
  }
  /* Full-bleed clamps to viewport (already 100vw); keep as-is. */

  /* Masonry shrinks to two columns on narrow screens. */
  .rkr-figure-masonry .rkr-figure-flow { column-count: 2; }
  /* Justified: shorter rows for narrower viewports. */
  .rkr-figure-justified .rkr-figure-cell { height: 140px; }

  /* Carousel: keep slides full-width; shrink nav buttons. */
  .rkr-carousel-prev,
  .rkr-carousel-next,
  .rkr-carousel-play {
    width: 1.75rem;
    height: 1.75rem;
    font-size: 0.9rem;
  }
}

@media (max-width: 400px) {
  .rkr-figure-masonry .rkr-figure-flow { column-count: 1; }
}

/* figure-only mode (?mode=figure): hide post metadata and text-format
   chrome so the editor shows just the image widget. */
body[data-mode="figure"] .rkr-page-title-row,
body[data-mode="figure"] .rkr-meta {
  display: none;
}

/* Comment moderation page (/admin/comments). Uses the theme tokens so
   it follows the active theme (incl. dark mode) instead of a bespoke
   inline stylesheet. */
.amc-wrap {
  max-width: 60rem;
  margin: 2rem auto;
  padding: 0 1.5rem;
}
.amc-list {
  list-style: none;
  padding: 0;
}
.amc-row {
  border: 1px solid var(--rkr-rule);
  border-radius: var(--rkr-radius);
  padding: 1rem;
  margin: 0.75rem 0;
}
.amc-queued {
  border-color: var(--rkr-link);
}
.amc-meta {
  font-size: 0.85rem;
  color: var(--rkr-muted);
}
.amc-body {
  white-space: pre-wrap;
  margin: 0.5rem 0;
}
.amc-actions {
  display: flex;
  gap: 0.5rem;
}
.amc-actions form {
  margin: 0;
}
.amc-actions button {
  appearance: none;
  background: transparent;
  color: var(--rkr-text);
  border: 1px solid var(--rkr-rule);
  border-radius: var(--rkr-radius);
  padding: 0.3rem 0.7rem;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.amc-actions button:hover,
.amc-actions button:focus-visible {
  color: var(--rkr-link);
  border-color: var(--rkr-link);
}
