/* Snippets — light-first tokens, redefined for dark under both
   prefers-color-scheme and an explicit html[data-theme] so the toggle wins in
   either direction. */
:root {
  --bg: #ffffff;
  --bg-subtle: #f6f8fa;
  --bg-inset: #f0f3f6;
  --fg: #1f2328;
  --fg-muted: #59636e;
  --border: #d1d9e0;
  --border-strong: #b7c0c9;
  --accent: #0969da;
  --accent-fg: #ffffff;
  --danger: #cf222e;
  --ok-bg: #dafbe1;
  --ok-fg: #1a7f37;
  --error-bg: #ffebe9;
  --error-fg: #cf222e;
  --pill-bg: #eaeef2;
  --line-highlight: #fff8c5;
  --line-highlight-edge: #d4a72c;
  --radius: 8px;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0d1117;
    --bg-subtle: #151b23;
    --bg-inset: #010409;
    --fg: #e6edf3;
    --fg-muted: #9198a1;
    --border: #3d444d;
    --border-strong: #4d545d;
    --accent: #4493f8;
    --accent-fg: #ffffff;
    --danger: #f85149;
    --ok-bg: #12261e;
    --ok-fg: #3fb950;
    --error-bg: #2d1214;
    --error-fg: #f85149;
    --pill-bg: #262c36;
    --line-highlight: #3d2e0a;
    --line-highlight-edge: #d29922;
  }
}

:root[data-theme="dark"] {
  --bg: #0d1117;
  --bg-subtle: #151b23;
  --bg-inset: #010409;
  --fg: #e6edf3;
  --fg-muted: #9198a1;
  --border: #3d444d;
  --border-strong: #4d545d;
  --accent: #4493f8;
  --accent-fg: #ffffff;
  --danger: #f85149;
  --ok-bg: #12261e;
  --ok-fg: #3fb950;
  --error-bg: #2d1214;
  --error-fg: #f85149;
  --pill-bg: #262c36;
  --line-highlight: #3d2e0a;
  --line-highlight-edge: #d29922;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.55;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.wrap { max-width: 980px; margin: 0 auto; padding: 0 20px; }

/* --- header / footer ----------------------------------------------------- */

/* Positioned so the account menu's panel has a stacking context of its own to
   sit in, rather than competing with whatever the page below happens to
   position. */
.site-header {
  position: relative;
  z-index: 30;
  border-bottom: 1px solid var(--border);
  background: var(--bg-subtle);
}

/* Full width rather than .wrap. The page's text is easier to read in a column,
   but a header is furniture: an app's mark belongs against the left edge of
   the window and its account controls against the right, the way every other
   web application puts them, so a reader's hand knows where to go without
   looking. */
.header-inner {
  display: flex;
  align-items: center;
  gap: 8px 20px;
  min-height: 56px;
  padding: 0 20px;
  flex-wrap: wrap;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  color: var(--fg);
  font-size: 17px;
  margin-right: 4px;
}
.brand:hover { text-decoration: none; }
.brand-mark { font-family: var(--mono); color: var(--accent); }

.site-nav {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}

/* Section links read as one target each: the icon and the word share a
   background on hover rather than the word alone lighting up. */
.nav-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border-radius: var(--radius);
  color: var(--fg);
  font-size: 14px;
  white-space: nowrap;
}
.nav-link:hover { background: var(--bg-inset); text-decoration: none; }
.icon-nav { width: 16px; height: 16px; color: var(--fg-muted); }
.nav-link:hover .icon-nav { color: var(--fg); }

/* margin-left:auto is what pins the account controls to the right edge, and it
   survives the header wrapping onto a second row. */
.header-tail {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.nav-new { display: inline-flex; align-items: center; gap: 5px; }
.nav-new .icon-nav { color: currentColor; }

/* --- account menu -------------------------------------------------------- */

.user-menu { position: relative; }

.user-menu-button {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px 4px 3px 3px;
  border-radius: 999px;
  border: 1px solid transparent;
  cursor: pointer;
  list-style: none;
}
/* Both spellings: the marker is ::-webkit-details-marker in Safari and
   ::marker everywhere else, and a stray triangle beside an avatar is the whole
   reason this element is not used more often. */
.user-menu-button::-webkit-details-marker { display: none; }
.user-menu-button::marker { content: ""; }
.user-menu-button:hover { background: var(--bg-inset); border-color: var(--border); }
.user-menu-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.icon-chevron { width: 14px; height: 14px; color: var(--fg-muted); }
.user-menu[open] .icon-chevron { transform: rotate(180deg); }

.user-menu-panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 20;
  min-width: 200px;
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.16);
}

.user-menu-who {
  padding: 6px 10px 8px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
  color: var(--fg-muted);
  font-size: 13px;
  line-height: 1.4;
}
.user-menu-who strong { color: var(--fg); font-weight: 600; }

/* One rule for the links and for the sign-out button, so a <form> in the list
   does not look like a different kind of thing than the anchors above it. */
.user-menu-item {
  display: block;
  width: 100%;
  padding: 6px 10px;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--fg);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
}
.user-menu-item:hover { background: var(--bg-inset); text-decoration: none; }
.user-menu-form { margin: 0; }

/* Narrow screens: the primary action keeps its plus and loses its label, so
   the mark, the sections and the account still fit on one row instead of the
   header growing to two. */
@media (max-width: 620px) {
  .nav-new span { display: none; }
  .nav-new { padding: 5px 9px; }
  .nav-link span { display: none; }
  .nav-link { padding: 5px 8px; }
}

.site-footer {
  margin-top: 56px;
  padding: 20px 0;
  border-top: 1px solid var(--border);
  color: var(--fg-muted);
  font-size: 13px;
}

/* The build line sits opposite the tagline on a wide screen and wraps under it
   on a narrow one, which is what the footer already does with its own text. */
.footer-inner {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  align-items: baseline;
  justify-content: space-between;
}

.footer-build {
  display: inline-flex;
  gap: 6px;
  align-items: baseline;
  white-space: nowrap;
}

.footer-sep { opacity: 0.6; }

/* The feed link stays plain inline text so its baseline is the baseline of the
   word "Feed", which is what lines it up with Source and the build hash beside
   it. An inline-flex link would hand its baseline to the SVG instead and ride
   high. The icon is nudged below the baseline to sit centred on the text. */
.footer-feed { white-space: nowrap; }
.footer-feed .icon-feed {
  width: 13px;
  height: 13px;
  margin-right: 4px;
  vertical-align: -0.15em;
}

.inline-form { display: inline; margin: 0; }

/* --- buttons / inputs ---------------------------------------------------- */

.button {
  display: inline-block;
  padding: 5px 14px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
  white-space: nowrap;
}
.button:hover { background: var(--bg-inset); text-decoration: none; }

.button-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-fg);
}
.button-primary:hover { filter: brightness(1.08); background: var(--accent); }

.button-danger { color: var(--danger); border-color: var(--danger); }
.button-quiet { background: transparent; border-color: transparent; color: var(--fg-muted); }
.button-quiet:hover { background: var(--bg-inset); }
.button-icon { display: inline-flex; align-items: center; gap: 5px; }
.icon { width: 14px; height: 14px; flex: none; }
/* The clipboard is the resting state; the check takes over while the link is
   sitting on the clipboard. */
.button-icon .icon-copied { display: none; }
.button-icon.is-copied .icon-copy { display: none; }
.button-icon.is-copied .icon-copied { display: block; }
.button-block { width: 100%; padding: 8px; font-size: 15px; }
.button-provider { display: block; text-align: center; padding: 9px; }
.theme-toggle { font-size: 16px; line-height: 1; padding: 5px 9px; }

.input {
  width: 100%;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  font-size: 14px;
}
.input:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
.input-content, .input-filename, .input-language { font-family: var(--mono); }
.input-content { resize: vertical; min-height: 160px; }

.field { margin-bottom: 16px; }
.field > label { display: block; margin-bottom: 4px; font-weight: 600; font-size: 14px; }
.hint { color: var(--fg-muted); font-weight: 400; font-size: 13px; }

.choice { display: flex; gap: 8px; align-items: flex-start; margin-bottom: 8px; }
.choice input { margin-top: 5px; }

fieldset { border: none; margin: 0 0 16px; padding: 0; }
legend { font-weight: 600; font-size: 14px; padding: 0 0 4px; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* --- banners / pills ----------------------------------------------------- */

.banner {
  padding: 10px 14px;
  border-radius: var(--radius);
  margin: 16px 0;
  border: 1px solid transparent;
}
.banner-ok { background: var(--ok-bg); color: var(--ok-fg); }
.banner-error { background: var(--error-bg); color: var(--error-fg); }

.pill {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--pill-bg);
  color: var(--fg-muted);
  font-size: 12px;
}
.pill-private { color: var(--error-fg); background: var(--error-bg); }

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

.page-head { margin: 32px 0 20px; }
.page-head h1 { margin: 0 0 6px; font-size: 26px; }
.lede { color: var(--fg-muted); margin: 0 0 8px; }

/* --- avatars ------------------------------------------------------------- */

/* The width and height attributes are on the element so the row does not
   reflow when a remote picture finally arrives; this only rounds it and keeps
   a non-square source from stretching. */
.avatar {
  border-radius: 50%;
  object-fit: cover;
  background: var(--bg-inset);
  vertical-align: middle;
  flex: none;
}

/* A name with its picture is one link, so it lines up as one row rather than
   as an image that happens to sit before some text.

   vertical-align is the load-bearing part. An inline-flex box with
   `align-items: center` has no baseline of its own to offer the line it sits
   in, so the browser synthesizes one from the bottom edge of the first item —
   the picture. That put the whole byline, name included, a picture-height above
   the "· 1 file · created …" text sitting beside it. Aligning the box by its
   middle instead lines the name up with that text at any avatar size. */
.byline {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  vertical-align: middle;
}

/* Room for the picture in a line of 13px text: without this the line box is
   shorter than the avatar and consecutive rows sit tighter than the text alone
   would. */
.snippet-card-meta, .snippet-meta { line-height: 1.8; }

.profile-head {
  display: flex;
  align-items: center;
  gap: 16px;
}
.profile-head .avatar { border: 1px solid var(--border); }
/* The name and the handle bring bottom margins with them from .page-head. In a
   centred row that margin is part of the block being centred, so the text sits
   higher than the picture it is meant to sit beside. */
.profile-head h1, .profile-head .lede { margin-bottom: 0; }
/* The feed is the reason a lot of people land on a profile, so it gets a place
   in the header rather than only the site-wide link down in the footer. Pushed
   to the far end of the row so it never crowds a long display name. */
.profile-head .profile-feed { margin-left: auto; }

@media (max-width: 520px) {
  .profile-head { flex-wrap: wrap; }
  /* Once the row wraps there is no end of the row to push against: the button
     would sit alone against the left edge, so let it fill the second line. */
  .profile-head .profile-feed { margin-left: 0; width: 100%; justify-content: center; }
}

.search-form { display: flex; gap: 10px; align-items: center; margin-bottom: 20px; }
.htmx-indicator { opacity: 0; color: var(--fg-muted); font-size: 13px; transition: opacity .2s; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { opacity: 1; }

.sort-tabs {
  display: flex;
  gap: 4px;
  margin-bottom: 16px;
}
.sort-tab {
  padding: 4px 10px;
  border-radius: 999px;
  color: var(--fg-muted);
  font-size: 13px;
}
.sort-tab:hover { background: var(--bg-inset); text-decoration: none; }
.sort-tab.is-current {
  background: var(--pill-bg);
  color: var(--fg);
  font-weight: 600;
}

.snippet-list { list-style: none; margin: 0; padding: 0; }

.snippet-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 12px;
  background: var(--bg);
}
.snippet-card-head { display: flex; align-items: center; gap: 10px; }
.snippet-card-title { font-weight: 600; font-size: 16px; }
.snippet-card-desc { margin: 6px 0 0; }
.snippet-card-meta { color: var(--fg-muted); font-size: 13px; margin-top: 6px; }
.snippet-card-preview {
  margin-top: 10px;
  padding: 8px 10px;
  background: var(--bg-subtle);
  border-radius: var(--radius);
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--fg-muted);
  overflow-x: auto;
  white-space: nowrap;
}

.empty { color: var(--fg-muted); padding: 24px 0; list-style: none; }

/* --- API tokens ---------------------------------------------------------- */

.token-list { list-style: none; margin: 0 0 16px; padding: 0; }
.token-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.token-label { font-weight: 600; }
.token-form { display: flex; gap: 10px; align-items: center; max-width: 520px; }
/* The token and its copy button sit side by side on a wide screen and stack
   on a narrow one, so the button never squeezes the token into a column. */
.token-reveal {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 10px;
  margin: 8px 0;
}
.token-reveal .token-value { flex: 1 1 260px; margin: 0; }
.token-reveal .button { flex: none; }

/* A token is meant to be selected in one gesture, so it gets its own line and
   wraps rather than scrolling out of reach on a phone. */
.token-value {
  margin: 8px 0;
  padding: 8px 10px;
  background: var(--bg-inset);
  border-radius: var(--radius);
  font-family: var(--mono);
  font-size: 13px;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

/* --- snippet view -------------------------------------------------------- */

.snippet-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  margin: 32px 0 20px;
  flex-wrap: wrap;
}
.snippet-head h1 { margin: 0 0 6px; font-size: 24px; }
.snippet-meta { color: var(--fg-muted); font-size: 13px; margin: 0; }
.snippet-desc { margin: 10px 0 0; }
.snippet-actions { display: flex; gap: 8px; align-items: flex-start; flex-wrap: wrap; }

/* --- stars --------------------------------------------------------------- */

.star { display: inline-flex; }
.button-star { display: inline-flex; align-items: center; gap: 6px; }
.star-mark { color: var(--fg-muted); line-height: 1; }
/* Starred is a state worth seeing across a page, so the mark carries the
   colour rather than the whole button: a filled button next to Edit and Delete
   would read as the page's primary action, which it is not. */
.button-star.is-starred { border-color: var(--accent); }
.button-star.is-starred .star-mark { color: var(--accent); }
.star-count {
  padding: 0 6px;
  border-radius: 999px;
  background: var(--pill-bg);
  color: var(--fg-muted);
  font-size: 12px;
}
.star-count-inline { white-space: nowrap; }

.file {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 20px;
  overflow: hidden;
}
.file-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  background: var(--bg-subtle);
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.file-name { margin: 0; font-size: 14px; font-family: var(--mono); font-weight: 600; }
.file-name a { color: var(--fg); }
.file-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--fg-muted);
  font-size: 12.5px;
}
.file-body { overflow-x: auto; }
.is-hidden { display: none; }
/* Rendered but out of sight: the clipboard fallback needs a field it can
   select, and a hidden one cannot be selected. */
.offscreen { position: fixed; top: 0; left: -9999px; opacity: 0; }

/* --- reactions ----------------------------------------------------------- */

.reactions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin: 12px 0 20px;
}

.reaction {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg);
  color: var(--fg-muted);
  font: inherit;
  font-size: 13px;
  line-height: 1.6;
  cursor: pointer;
}
.reaction:hover { background: var(--bg-inset); }
.reaction.is-static { cursor: default; }
/* Your own reaction reads at a glance, so pressing one twice is obviously an
   undo rather than a second vote. */
.reaction.is-mine { border-color: var(--accent); background: var(--bg-subtle); color: var(--fg); }
.reaction-char { font-size: 14px; line-height: 1; }
.reaction-count { font-variant-numeric: tabular-nums; }

/* <details> is the whole picker mechanism: it opens and closes with no script,
   which is what lets the page keep a CSP with no inline anything. */
.reaction-picker { position: relative; display: inline-block; }
.reaction-picker > summary { list-style: none; }
.reaction-picker > summary::-webkit-details-marker { display: none; }
.reaction-menu {
  position: absolute;
  z-index: 10;
  top: calc(100% + 4px);
  left: 0;
  display: flex;
  /* The set is long enough now to run off a narrow screen in one line, and an
     absolutely positioned menu has no scrollbar to rescue it. width is
     max-content because the menu's containing block is the picker button,
     which is one emoji wide: left to size itself against that, it wraps to a
     single column and max-width, being only a ceiling, cannot widen it back. */
  flex-wrap: wrap;
  width: max-content;
  max-width: min(280px, calc(100vw - 32px));
  gap: 4px;
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: 0 4px 14px rgb(0 0 0 / 12%);
}
.reaction-choice { padding: 2px 6px; border-color: transparent; }

/* --- rendered markdown / html -------------------------------------------- */

.markdown-body { padding: 16px 20px; }
.markdown-body > :first-child { margin-top: 0; }
.markdown-body > :last-child { margin-bottom: 0; }
.markdown-body h1, .markdown-body h2 {
  padding-bottom: .3em;
  border-bottom: 1px solid var(--border);
}
.markdown-body img { max-width: 100%; height: auto; }
.markdown-body table { border-collapse: collapse; display: block; overflow-x: auto; }
.markdown-body th, .markdown-body td { border: 1px solid var(--border); padding: 6px 12px; }
.markdown-body th { background: var(--bg-subtle); }
.markdown-body blockquote {
  margin: 0 0 16px;
  padding: 0 1em;
  color: var(--fg-muted);
  border-left: .25em solid var(--border);
}
.markdown-body code {
  font-family: var(--mono);
  font-size: 85%;
  padding: .2em .4em;
  background: var(--pill-bg);
  border-radius: 6px;
}
.markdown-body pre { overflow-x: auto; border-radius: var(--radius); }
.markdown-body pre code { padding: 0; background: transparent; font-size: 100%; }
/* A thematic break is an explicit section divider, so it is heavier than the
   thin rules under headings, the way GitHub-flavored markdown draws it. */
.markdown-body hr {
  border: none;
  border-top: 5px solid var(--border);
  margin: 24px 0;
}

/* chroma output: the colours come from chroma.css, the box model from here */
.code-body .chroma, .markdown-body .chroma {
  margin: 0;
  padding: 12px 0;
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.45;
  overflow-x: auto;
}
.chroma .lntable { border-spacing: 0; border-collapse: collapse; width: 100%; }
.chroma .lntable td { padding: 0; vertical-align: top; }
.chroma .lntd:first-child { width: 1%; }
.chroma .lnt, .chroma .ln {
  user-select: none;
  padding: 0 12px;
  opacity: .55;
}
.chroma code { padding: 0; background: transparent; }
.chroma .lnlinks { color: inherit; text-decoration: none; }
.chroma .lnlinks:hover { text-decoration: none; opacity: 1; }

/* Selected lines. The gutter span and the code span live in different table
   cells, so both are marked and the pair reads as one highlighted row.
   :target covers the no-JS single-line case; .is-line-selected is what the
   click handler sets, including for ranges. */
.chroma .lnt:target,
.chroma .lnt.is-line-selected {
  background: var(--line-highlight);
  box-shadow: inset 2px 0 0 var(--line-highlight-edge);
  opacity: 1;
}
.chroma .line.is-line-selected { background: var(--line-highlight); }

/* The copy control pops up over the start of the line above the selection
   rather than sitting in the file header. It hangs off a line element and is
   placed with plain left/top, so no coordinates have to be measured; the line
   is the positioning context. It is unselectable so that dragging across the
   code does not copy its label. */
.chroma .line { position: relative; }

/* The pair travels as one group, parked at the start of the line so it sits
   where the eye already is instead of trailing off the end of a long line. */
.line-controls {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
/* Spelled out again here because .is-hidden and .line-controls carry the same
   weight and this one comes later, so the shared rule alone lost the tie and
   left the pair on screen with nothing selected: parked back at the section
   they belong to, absolutely positioned against no line, reading as two chips
   adrift at the left edge of the page. */
.line-controls.is-hidden { display: none; }

/* Both line controls are the same chip: the reader sees one pair of controls,
   not a chip and a page button that happen to sit together. */
.line-copy,
.line-note-add {
  padding: 0 8px;
  font-family: var(--sans);
  font-size: 11.5px;
  line-height: 18px;
  user-select: none;
  background: var(--bg);
  border-color: var(--border-strong);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .18);
}

/* --- comments ------------------------------------------------------------ */

.comments { margin: 32px 0 48px; max-width: 900px; }
.comments-head {
  font-size: 16px;
  margin: 0 0 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
}

.comment-list { list-style: none; margin: 0 0 20px; padding: 0; }
/* No overflow: hidden here, unlike .file: a comment carries a reaction picker,
   and clipping the frame clips the menu that opens out of it. The head rounds
   its own top corners instead, which is all the clipping was doing. */
.comment {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 12px;
}
.comment-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  background: var(--bg-subtle);
  border-bottom: 1px solid var(--border);
  /* Inside the border, so a pixel of head does not show past the frame. */
  border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
  font-size: 13px;
  flex-wrap: wrap;
}
.comment-time { color: var(--fg-muted); }
/* The rendered body reuses .markdown-body, so this only tightens the padding:
   a comment is a paragraph, not a document. */
.comment-body { padding: 12px 16px; }
/* The bar is a sibling of the body rather than part of it, so it takes the
   body's side padding itself instead of sitting flush against the frame. */
.comment > .reactions { margin: 0 16px 12px; }

.comment-form { display: block; margin-top: 16px; }
/* The edit box replaces a rendered comment inside its own frame, so it sits on
   the body's padding rather than the section's top margin. */
.comment-edit { margin-top: 0; padding: 12px 16px; }
.comment-edit .editor-actions { margin-bottom: 0; }
.editor-actions .hint { margin: 0; }
.comment-form label { display: block; font-weight: 600; margin-bottom: 6px; font-size: 14px; }
/* Deliberately not .input-content: that class makes Tab insert a tab
   character, which is right in a code editor and wrong in a prose box where
   Tab has to keep moving focus. */
.comment-form textarea { width: 100%; resize: vertical; min-height: 110px; font-family: inherit; }

/* --- line notes ---------------------------------------------------------- */

/* Collapsed by default and quiet by default. A reader came for the file; the
   notes are a row of markers they can open, not a wall they have to scroll
   past.

   Where a list of them does show, it is a footer on the file card and is
   dressed like one -- the same ground and the same rule as the card's header,
   on the other edge. Undressed it landed flush under the rendered output, and
   a markdown page that ends in prose followed by remarks about the prose gave
   the reader no way to tell which was the document. */
.line-notes {
  padding: 12px 16px;
  border-top: 1px solid var(--border);
  background: var(--bg-subtle);
}

/* Dressed only while it is actually carrying a list. That covers all three
   ways it can end up with none: a file nobody has remarked on, a reader who
   is not signed in and so has no form either, and -- once the script has run
   -- a file whose every thread has been parked under its own line, leaving
   just the "comment on a line" form down here with nothing to frame.
   :has rather than :empty, because the section is never empty of whitespace. */
.line-notes:not(:has(> .line-thread)) {
  padding: 0 16px 12px;
  border-top: 0;
  background: none;
}
.line-notes:not(:has(> .line-thread)) .line-notes-head { display: none; }

.line-notes-head {
  margin: 0 0 8px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--fg-muted);
}


.line-thread {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-subtle);
  margin-bottom: 6px;
}
/* Listed in the footer, a thread would otherwise be the footer's own colour.
   Parked under a line it keeps --bg-subtle, which is what lifts it off the
   code. */
.line-notes > .line-thread { background: var(--bg); }

.line-thread-summary {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 6px 10px;
  cursor: pointer;
  font-size: 13px;
  list-style: none;
}
.line-thread-summary::-webkit-details-marker { display: none; }
.line-thread-summary::marker { content: ""; }
.line-thread-summary:hover { background: var(--bg-inset); border-radius: var(--radius); }
.line-thread-summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* A triangle of our own, because the native marker is suppressed to keep the
   row a flex line rather than a list item. */
.line-thread-summary::before {
  content: "▸";
  color: var(--fg-muted);
  font-size: 11px;
}
.line-thread[open] > .line-thread-summary::before { content: "▾"; }

.line-thread-line { font-family: var(--mono); font-weight: 600; }
.line-thread-count { color: var(--fg-muted); }
/* The first note, truncated: enough to decide whether to open the thread,
   never enough to become the wall the collapsing was for. */
.line-thread-peek {
  color: var(--fg-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.pill-outdated { color: var(--line-highlight-edge); background: var(--line-highlight); }
/* Green, the page's one "this is settled" colour, already spent on the ok
   banner. A resolved thread is the only good news the gutter carries. */
.pill-resolved { color: var(--ok-fg); background: var(--ok-bg); }

/* A settled conversation reads as background rather than as something asking
   to be opened. Dimmed rather than hidden: it is still a remark somebody left
   about this line, and the reader who wants it can still find and open it. */
.line-thread.is-resolved > .line-thread-summary .line-thread-line,
.line-thread.is-resolved > .line-thread-summary .line-thread-peek {
  color: var(--fg-muted);
}
/* The line number is the one part of the row that is not already muted, and
   striking it is what says "dealt with" at a glance down a file. */
.line-thread.is-resolved > .line-thread-summary .line-thread-line {
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  font-weight: 400;
}
/* Opened, it is a live thread again -- everything in it is as readable as any
   other note. The dimming is about the folded row, not about the words. */
.line-thread.is-resolved[open] > .line-thread-summary .line-thread-line {
  text-decoration: none;
}

.line-thread-resolved-note {
  margin: 0 0 8px;
  font-size: 13px;
  color: var(--fg-muted);
}

/* Parked at the foot of the thread, under the reply box, because it is the
   last thing you do with a conversation rather than the first. Right-aligned
   to sit under the Reply button rather than to start a new column. */
.line-thread-resolve {
  display: flex;
  justify-content: flex-end;
  margin-top: 8px;
}

.line-thread-body { padding: 0 10px 10px; }
.line-thread-code { margin: 0 0 8px; font-size: 13px; }

.line-note-list { list-style: none; margin: 0; padding: 0; }
.line-note { padding: 8px 0; border-top: 1px solid var(--border); }
.line-note-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--fg-muted);
  margin-bottom: 4px;
}
.line-note-body { font-size: 14px; }
.line-note-body > :first-child { margin-top: 0; }
.line-note-body > :last-child { margin-bottom: 0; }

.line-note-form { margin-top: 8px; }
.line-note-form textarea { font-family: inherit; resize: vertical; }
.line-note-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 6px; }
.button-small { padding: 2px 8px; font-size: 13px; }

.line-note-line-field { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 14px; }
.input-line { width: 6em; }

.line-note-new { margin-top: 4px; }
.line-note-new-summary {
  display: inline-block;
  padding: 4px 8px;
  border-radius: var(--radius);
  color: var(--fg-muted);
  font-size: 13px;
  cursor: pointer;
}
.line-note-new-summary:hover { background: var(--bg-inset); color: var(--fg); }

/* A thread parked under its line. It sits inside the code column's <pre>, so
   everything the surrounding monospace block imposes -- preserved whitespace,
   the code font, the code font size -- has to be undone here. It sticks to the
   left edge so that scrolling a wide file sideways does not slide the
   conversation out of the window. */
.line-thread-inline {
  display: block;
  position: sticky;
  left: 0;
  white-space: normal;
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  /* Narrower than the code it interrupts: prose reads badly at full width,
     and a thread that is wider than the file would add scroll of its own. */
  max-width: 64ch;
  /* Padding, not margin: app.js sizes the gutter's spacer from this element's
     offsetHeight, which counts padding and does not count margin. */
  padding: 4px 0 6px;
}
.line-thread-inline > .line-thread { margin-bottom: 0; }

/* A folded thread leaves nothing behind in the code at all -- not even the
   one summary row it used to leave, which was still a line of someone else's
   remarks between two lines of the file. All that is left of it is the gutter
   marker, and the file reads top to bottom the way it was written. */
.line-thread-inline:not(:has(> .line-thread[open])) { display: none; }

/* Inline, the peek is the first sentence of the note directly below it. */
.line-thread-inline .line-thread-peek { display: none; }

/* Inline, "Jump to line 12" points at the line the thread is already sitting
   under. The row stays only when it is carrying the note about what that line
   used to say. */
.line-thread-inline .line-thread-code:not(:has(.hint)) { display: none; }
.line-thread-inline .line-thread-code:has(.hint) > a { display: none; }

/* The number column's answer to the block above: no content, just the same
   height, so the numbers stay level with the lines they belong to. app.js
   measures the thread and sets this. */
.line-thread-gap { display: block; }

/* The gutter marker: a folded conversation's whole presence on the page, and
   the control that unfolds it. It rides in the number column beside the digits
   at the size of the digits, because a mark that small has to look like part
   of the line number to read as being about that line.

   In flow but zero-width, so it hangs into the column's padding and shifts
   nothing: every other line keeps its number in the same place. Being an
   inline-block also gives the icon a real box to centre against -- .lnt is an
   inline element, and positioning against one of those is guesswork.

   Added by app.js from the section's data attribute, so a reader without a
   script still gets every thread listed under the file -- they just do not get
   a marker beside the line. */
.lnt.has-note > .lnlinks { color: var(--accent); font-weight: 600; }

/* Room for the marker, given to the whole file's numbers rather than the
   commented ones, or the column would step in and out down the page. */
.chroma .lntable.has-notes .lnt { padding-left: 26px; }

.line-note-marker {
  position: relative;
  display: inline-block;
  width: 0;
  height: 1em;
  vertical-align: baseline;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
  /* The icon is painted outside this box, which some browsers would otherwise
     clip on a button -- and clipping it would take the click target with it. */
  overflow: visible;
  /* The gutter runs at .55 so the numbers stay out of the way. The marker is
     something to press, so it does not. */
  opacity: 1;
}

.line-note-bubble {
  position: absolute;
  /* Right edge just short of where the digits begin, centred on them. */
  right: 3px;
  top: 50%;
  transform: translateY(-50%);
  width: 1.25em;
  height: 1.25em;
  fill: currentColor;
  stroke: none;
}
.line-note-marker:hover .line-note-bubble { transform: translateY(-50%) scale(1.12); }

/* A settled thread's marker steps back out of the accent colour: running a
   finger down the gutter should pick out the conversations still wanting an
   answer, not every conversation there has ever been. Still a full-strength
   pressable target -- what is muted is the claim on attention, not the
   control. */
.line-note-marker.is-resolved { color: var(--fg-muted); }
.lnt.has-note.is-resolved > .lnlinks { color: var(--fg-muted); font-weight: 400; }

/* Outlined while the thread it opened is showing, so a glance down the gutter
   says which conversations are already on screen. */
.line-note-marker[aria-expanded="true"] .line-note-bubble {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linejoin: round;
}

/* The button itself has no width to draw a ring around, so the icon wears it. */
.line-note-marker:focus-visible { outline: none; }
.line-note-marker:focus-visible .line-note-bubble {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-radius: 2px;
}

/* The whole-file control only means anything with a script: with none, every
   thread is already a row in the list under the file that opens on its own. */
.notes-toggle { display: none; }
.js .notes-toggle { display: inline-block; }

/* --- version history ----------------------------------------------------- */

.version-banner { display: flex; gap: 12px; align-items: center; }
/* Only the history page's meta row carries a button, so only it lays out as a
   flex row; the listing cards elsewhere stay running text with · separators. */
.version-card-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

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

.editor { max-width: 820px; }
.editor-file {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 14px;
  padding: 10px;
  background: var(--bg-subtle);
}
.editor-file-head { display: flex; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
.input-filename { flex: 2 1 220px; width: auto; }
.input-language { flex: 1 1 160px; width: auto; }
.editor-actions { display: flex; align-items: center; gap: 8px; margin: 16px 0; }

/* --- editor: write / preview --------------------------------------------- */

/* Hidden until app.js marks the document as scripted. The tabs only mean
   anything once something can swap the panes, and a tab strip that does
   nothing is worse than no tab strip. */
.editor-tabs { display: none; }
.js .editor-tabs {
  display: flex;
  gap: 2px;
  margin-bottom: -1px;
}

.editor-tab {
  padding: 5px 12px;
  border: 1px solid transparent;
  border-radius: var(--radius) var(--radius) 0 0;
  background: none;
  color: var(--fg-muted);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
}
.editor-tab:hover { color: var(--fg); }
/* The current tab joins the pane below it: same background, and a bottom edge
   the same colour as that background so the border reads as open on one side. */
.editor-tab.is-current {
  color: var(--fg);
  font-weight: 600;
  background: var(--bg);
  border-color: var(--border);
  border-bottom-color: var(--bg);
}
.editor-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Both panes stay in the DOM; the row's state decides which one is on top, so
   switching back to Write is instant and nothing typed is ever re-parsed. */
.editor-preview { display: none; }
.editor-file.is-previewing .input-content { display: none; }
.editor-file.is-previewing .editor-preview { display: block; }

.editor-preview {
  min-height: 160px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 0 var(--radius) var(--radius) var(--radius);
  background: var(--bg);
  overflow-x: auto;
}
/* A pane that has never been fetched, and one whose file is empty, both say so
   rather than showing a convincing blank. */
.editor-preview:empty::before {
  content: "Rendering…";
  color: var(--fg-muted);
  font-size: 14px;
}
/* The pane is its own htmx indicator: a re-render dims what is already there
   instead of replacing it with a spinner in a different place. */
.editor-preview.htmx-request { opacity: 0.55; }

/* The write pane keeps the square top-left corner the tab strip sits on. */
.js .editor-file .input-content { border-radius: 0 var(--radius) var(--radius) var(--radius); }
.spacer { flex: 1; }

/* --- auth ---------------------------------------------------------------- */

.auth-card {
  max-width: 380px;
  margin: 48px auto;
  padding: 24px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.auth-card h1 { margin: 0 0 20px; font-size: 22px; text-align: center; }
.auth-alt { text-align: center; color: var(--fg-muted); font-size: 14px; margin: 16px 0 0; }
.provider-buttons { display: grid; gap: 8px; }

.dev-signin { margin-top: 24px; border-top: 1px dashed var(--border); padding-top: 16px; }
.dev-signin h2 { margin: 0 0 4px; font-size: 15px; text-align: center; }
.dev-signin-list { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 8px; }

.divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 18px 0;
  color: var(--fg-muted);
  font-size: 13px;
}
.divider::before, .divider::after {
  content: "";
  flex: 1;
  border-top: 1px solid var(--border);
}

.error-page { text-align: center; margin: 80px 0; }
.error-page h1 { font-size: 64px; margin: 0; color: var(--fg-muted); }

/* --- emoji completion ----------------------------------------------------- */

/* Positioned by app.js, which writes left/top through the CSSOM. The values
   here are where it lands if that never runs — off in a corner rather than
   stretched across the page. */
.emoji-picker {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 50;
  margin: 0;
  padding: 4px;
  list-style: none;
  min-width: 200px;
  max-width: 320px;
  max-height: 232px;
  overflow-y: auto;
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(31, 35, 40, 0.2);
}

.emoji-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
}
.emoji-option.is-active { background: var(--accent); color: var(--accent-fg); }
.emoji-glyph { font-size: 16px; line-height: 1; }
.emoji-name { font-family: var(--sans); font-size: 13px; }
