:root {
  color-scheme: light dark;
  /* Neutrals follow x.com: pure white ground with cool grays (fg #0f1419,
     muted #536471 are X's own values), because the warm off-white read as
     "old paper" next to the sites people arrive from. --card flips to sit
     slightly BELOW the ground now that the ground is pure white. */
  --bg: #ffffff; --fg: #0f1419; --muted: #536471;
  --line: #e1e8ed; --accent: #1c5d99; --card: #f7f9f9;
  /* Semantic colours are tokens for the same reason the palette is: the old
     hard-coded #b3261e read at 2.7:1 on the dark background. Each value is
     checked at >= 4.5:1 against its own theme's --bg. */
  --danger: #b3261e; --tension: #8a6203;
  /* the scroll-shadow smudge on inner-scrolling strips */
  --scrollhint: rgba(15, 20, 25, 0.18);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* X's "dim" palette rather than its lights-out black: cards and hairlines
       still need a value distinguishable from the ground. */
    --bg: #15202b; --fg: #f7f9f9; --muted: #8b98a5;
    --line: #38444d; --accent: #7cb0e0; --card: #1e2732;
    --danger: #f2726a; --tension: #d4a336;
    --scrollhint: rgba(0, 0, 0, 0.55);
  }
}
* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--fg);
  font: 16px/1.6 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Fill the viewport and pin the footer to its bottom. Not (only) cosmetic:
     on a short page, expanding a <details> card used to grow the document
     itself, and the resulting scroll clamp / re-anchoring teleported content
     above or below the card depending on scroll position. With the slack
     held by main, a toggle consumes empty space instead of resizing the
     document, so nothing else on screen moves. */
  min-height: 100vh; min-height: 100svh;
  display: flex; flex-direction: column;
}
main { max-width: 44rem; margin: 0 auto; padding: 1.5rem 1.25rem 4rem; flex: 1 0 auto; width: 100%; }
.top {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
  /* width: 100% because body is a column flex container: a flex item with
     auto horizontal margins is not stretched, so without an explicit width
     the header shrinks to its content and floats centered. Same on .foot. */
  max-width: 44rem; width: 100%; margin: 0 auto; padding: 0.65rem 1.25rem;
  border-bottom: 1px solid var(--line);
}
.brand { font-weight: 650; letter-spacing: -0.01em; text-decoration: none; color: var(--fg); }
/* The signed-out call to action, styled after x.com's logged-out header
   "Log in": plain bold text in the foreground colour, no pill, no underline
   (their filled black pill is the separate "Open app" action, which we do
   not have). Generous tap padding keeps it a 44px target despite looking
   like text. */
.top .signin {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 0.25rem; color: var(--fg); font-weight: 700; font-size: 0.95rem;
}
.top .signin:hover { text-decoration: underline; }
a { color: var(--accent); }
nav a { margin-left: 0.9rem; text-decoration: none; }
/* The relative half of a timestamp ("2 hours ago"), added by static/time.js.
   Quieter than the date it follows: the date is the fact, this is the reading
   aid, and on a feed of forty rows two equally loud clauses per row is noise. */
.ago { color: var(--muted); }

/* ------------------------------------------------------------- feed cards */
/* Name over words. The head line is faces, then who, then when, wrapping as one
   line on a phone; the claim below is body text, not a headline -- at 550 every
   card shouted and forty of them read as a wall of titles rather than a feed of
   things people said. */
.feedhead {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.2rem 0.5rem;
  margin: 0 0 0.35rem; font-size: 0.9rem; line-height: 1.3;
}
.feedfaces { display: inline-flex; gap: 2px; }
.feedhead .alsochip { width: 24px; height: 24px; font-size: 0.6rem; }
.feedname { font-weight: 600; }
.feedname a { color: var(--fg); text-decoration: none; }
.feedname a:hover { text-decoration: underline; text-underline-offset: 2px; }
.feedname.muted { color: var(--muted); font-weight: 500; }
/* No separator dot: the head wraps on a phone, and a dot that starts the second
   line reads as a bullet in a list of one. The gap and the colour do the job. */
.feedwhen { color: var(--muted); }
/* A card whose holders have all turned still carries their name; this is the
   word that stops it reading as an endorsement. */
.feedwas { color: var(--muted); font-size: 0.82rem; }
.feedwhen strong { color: var(--fg); font-weight: 600; }
.feedadded { color: var(--fg); }
.feedrow .claim-link { font-weight: 400; font-size: 1.05rem; margin-bottom: 0; }
/* The framing tag sits between the byline and the claim, small enough to be
   read as a caption on the sentence rather than a second line of the byline. */
.feedrow .framing { margin: 0 0 0.1rem; }

/* --------------------------------------------------------- search typeahead */
/* The panel hangs off the form, so it lines up under the box that produced it
   on the phone (where the box is a full-width row of its own) and under the
   header field on a desktop. Width is clamped to the viewport rather than set
   from the field: a claim is a sentence, and 14rem of header field is not a
   readable width for one. */
form[role="search"] { position: relative; }
.sugg {
  position: absolute; z-index: 30; left: 0; top: calc(100% + 6px);
  width: max(100%, 20rem); max-width: calc(100vw - 2.5rem);
  margin: 0; padding: 0.3rem; list-style: none;
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 6px 24px rgb(0 0 0 / 0.14);
  max-height: min(60vh, 26rem); overflow-y: auto;
}
.sugrow {
  display: flex; gap: 0.5rem; align-items: flex-start;
  padding: 0.5rem 0.55rem; border-radius: 6px; cursor: pointer;
}
.sugrow.on, .sugrow:hover { background: var(--bg); }
.sugg .mg { flex: none; margin: 2px 0 0; vertical-align: 0; }
.sugmain { min-width: 0; }
.suglabel {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; font-size: 0.92rem; line-height: 1.35;
}
.sugsub { display: block; color: var(--muted); font-size: 0.78rem; }
.sugall { color: var(--accent); font-size: 0.85rem; border-top: 1px solid var(--line);
  margin-top: 0.2rem; padding-top: 0.55rem; border-radius: 0 0 6px 6px; }

/* ------------------------------------------------------------ account menu */
/* The header badge is the same monogram-and-hue the rest of the site gives a
   person, one size up, because at 20px it is an ornament and at 32px it is a
   button. The panel is anchored to the header rather than the badge so it
   cannot hang off the right edge of a narrow phone. */
.usermenu { position: relative; display: inline-block; margin-left: 0.9rem; }
.usermenu > summary {
  list-style: none; cursor: pointer; display: grid; place-items: center;
  width: 40px; height: 40px; margin: -4px 0; border-radius: 50%;
}
.usermenu > summary::-webkit-details-marker { display: none; }
.usermenu > summary:hover { background: var(--card); }
.usermenu > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.usermenu .mg { width: 32px; height: 32px; font-size: 0.78rem;
  vertical-align: 0; margin-right: 0; }
.usermenu .menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 20;
  min-width: 12rem; padding: 0.35rem; background: var(--card);
  border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 6px 24px rgb(0 0 0 / 0.12);
}
.usermenu .menuwho {
  margin: 0; padding: 0.4rem 0.6rem 0.5rem; font-size: 0.8rem; color: var(--muted);
  border-bottom: 1px solid var(--line); white-space: nowrap;
}
.usermenu .menuwho strong { color: var(--fg); font-weight: 600; }
.usermenu .menu a,
.usermenu .menu button {
  display: block; width: 100%; margin: 0; padding: 0.55rem 0.6rem;
  border: 0; border-radius: 6px; background: none; color: var(--fg);
  font: inherit; font-size: 0.9rem; text-align: left; text-decoration: none;
  cursor: pointer;
}
.usermenu .menu a:hover, .usermenu .menu button:hover { background: var(--bg); }
.usermenu .menu form { margin: 0.2rem 0 0; padding-top: 0.2rem; border-top: 1px solid var(--line); }
.usermenu .menu button { color: var(--danger); }

.pitch h1 { font-size: 1.6rem; letter-spacing: -0.02em; margin: 1.5rem 0 0.5rem; }
.pitch p { color: var(--muted); max-width: 34rem; }
.korent-list { list-style: none; padding: 0; margin: 2rem 0 0; }
.korent-list li { padding: 0.9rem 0; border-top: 1px solid var(--line); }
.korent-list a { text-decoration: none; font-weight: 500; }
.by { display: block; color: var(--muted); font-size: 0.85rem; }
.empty { color: var(--muted); }
.foot {
  max-width: 44rem; width: 100%; margin: 0 auto; padding: 2rem 1.25rem;
  color: var(--muted); font-size: 0.85rem; border-top: 1px solid var(--line);
}

/* ---------------------------------------------------------------- shared */
.meta { color: var(--muted); font-size: 0.85rem; }
.note { margin: 0.4rem 0; }
.src { margin: 0.3rem 0 0; font-size: 0.85rem; }
.src a { color: var(--muted); }
.opt { color: var(--muted); font-weight: 400; font-size: 0.85rem; }
.hint { display: block; color: var(--muted); font-size: 0.8rem; margin-top: 0.25rem; }
.err { color: var(--danger); margin-top: 0.75rem; }
.page-h1 { font-size: 1.5rem; letter-spacing: -0.02em; }
.sec { font-size: 1rem; text-transform: uppercase; letter-spacing: 0.06em;
       color: var(--muted); margin: 2.5rem 0 0.75rem; font-weight: 600; }
blockquote {
  margin: 0.5rem 0; padding: 0.4rem 0 0.4rem 0.9rem;
  border-left: 3px solid var(--line); color: var(--fg); font-style: italic;
}

.btn {
  display: inline-block; margin-top: 1rem; padding: 0.55rem 1.1rem;
  background: var(--accent); color: #fff; border: 0; border-radius: 6px;
  font: inherit; font-weight: 500; cursor: pointer; text-decoration: none;
}
.btn.ghost { background: transparent; color: var(--muted); border: 1px solid var(--line); }
.btn.danger { background: transparent; color: var(--danger); border: 1px solid var(--danger); margin-left: 0.5rem; }

.badge {
  display: inline-block; padding: 0.1rem 0.45rem; border-radius: 4px;
  border: 1px solid var(--line); color: var(--muted);
  font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em;
}
.badge.danger { color: var(--danger); border-color: var(--danger); }

/* ----------------------------------------------------------------- forms */
.form label { display: block; margin: 1.1rem 0; font-weight: 500; }
.form label.inline { display: inline-flex; align-items: center; gap: 0.4rem; margin-right: 1rem; font-weight: 400; }
.form input[type=text], .form input[type=url], .form input[type=date],
.form input:not([type]), .form textarea {
  display: block; width: 100%; margin-top: 0.35rem; padding: 0.5rem 0.6rem;
  background: var(--card); color: var(--fg);
  border: 1px solid var(--line); border-radius: 6px; font: inherit;
}
.form input[type=range] { width: 100%; margin-top: 0.5rem; }
.form fieldset { border: 0; padding: 0; margin: 1.1rem 0; }
.form legend { font-weight: 500; padding: 0; }
.logout { margin-top: 2.5rem; }

/* ---------------------------------------------------------- korent page */
.claim { font-size: 1.45rem; line-height: 1.35; letter-spacing: -0.015em; margin: 1.25rem 0 0.5rem; }
.details { color: var(--fg); }
.counts { color: var(--muted); font-size: 0.9rem; margin-top: 1rem;
          padding-top: 0.75rem; border-top: 1px solid var(--line); }
.yours { margin: 1.25rem 0; padding: 1rem; background: var(--card);
         border: 1px solid var(--line); border-radius: 8px; }
.yours label { display: block; margin: 0.6rem 0; }
.yours textarea, .yours input[type=url] {
  display: block; width: 100%; margin-top: 0.35rem; padding: 0.5rem 0.6rem;
  background: var(--bg); color: var(--fg); border: 1px solid var(--line);
  border-radius: 6px; font: inherit;
}
.yours input[type=range] { width: 100%; }
.history { margin: 1rem 0; }
.history summary { cursor: pointer; color: var(--muted); font-size: 0.9rem; }

.holders { list-style: none; padding: 0; margin: 0; }
.holder { padding: 0.9rem 0; border-top: 1px solid var(--line); }
.holder-head { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.holder-head a { text-decoration: none; font-weight: 500; }
.holder .conf { font-variant-numeric: tabular-nums; font-weight: 600; }
.holder.archive { opacity: 0.95; }
.disclaimer {
  color: var(--muted); font-size: 0.82rem; line-height: 1.5;
  padding: 0.6rem 0.75rem; border: 1px dashed var(--line); border-radius: 6px;
}

/* -------------------------------------------------------------- profile */
.profile h1 { font-size: 1.5rem; letter-spacing: -0.02em; margin-bottom: 0.25rem; }
/* Labels carry counts now ("6 changes of mind"), which wrapped to two lines at
   390px and made a 69px-tall ragged tab bar. Never wrap a tab; if the set is
   genuinely too wide for the screen, scroll it instead. */
.tabs { margin-top: 1.25rem; border-bottom: 1px solid var(--line); display: flex; gap: 1.1rem;
        overflow-x: auto; scrollbar-width: none; }
.tabs::-webkit-scrollbar { display: none; }
/* margin-left: 0 undoes the global `nav a` rule -- .tabs is a <nav>, so each tab
   was silently carrying 14.4px of left margin on top of the flex gap. Invisible
   with the old one-word labels; with counts it overflowed the row by 43px and
   clipped the last tab. */
.tabs a { text-decoration: none; padding: 0.5rem 0.15rem 0.6rem; color: var(--muted); min-height: 44px;
          display: inline-flex; align-items: center; white-space: nowrap; margin-left: 0; }
.tabs a.on { color: var(--fg); border-bottom: 2px solid var(--accent); font-weight: 500; }

.changes { list-style: none; padding: 0; margin: 1.5rem 0 0; }
.changes li { padding: 1rem 0; border-top: 1px solid var(--line); }
.changes li a { text-decoration: none; font-weight: 500; }
.delta { font-variant-numeric: tabular-nums; margin: 0.4rem 0; }
.delta span { color: var(--muted); text-decoration: line-through; }
.changes li.turn .delta strong { color: var(--danger); }

.korent-list .meta { display: block; margin-top: 0.2rem; }

/* ------------------------------------------------------------------ home */
.hero { padding: 2rem 0 0.5rem; }
.hero h1 { font-size: 2rem; letter-spacing: -0.03em; margin: 0 0 0.75rem; }
.lede { color: var(--muted); font-size: 1.05rem; max-width: 36rem; margin: 0 0 1.5rem; }
.lede em { color: var(--fg); font-style: italic; }
.cta { margin: 0.25rem 0 0; }
.block { margin-top: 3rem; }

.search { display: flex; gap: 0.5rem; margin: 1.25rem 0; }
.search input {
  flex: 1; min-width: 0; padding: 0.6rem 0.75rem; font: inherit;
  background: var(--card); color: var(--fg);
  border: 1px solid var(--line); border-radius: 6px;
}
.search button {
  padding: 0.6rem 1rem; font: inherit; font-weight: 500; cursor: pointer;
  background: var(--accent); color: #fff; border: 0; border-radius: 6px;
}
.topsearch { flex: 1; max-width: 14rem; margin: 0 0.75rem; }
.topsearch input {
  width: 100%; padding: 0.35rem 0.6rem; font: inherit; font-size: 0.9rem;
  background: var(--card); color: var(--fg);
  border: 1px solid var(--line); border-radius: 6px;
}
/* Narrow screens have no room for a permanent search field in the header and no
   room for one on the page either -- it was costing a third of the first screen
   for something used occasionally. So it becomes an icon that unfolds the same
   header form onto a second row. Checkbox rather than script: the site is
   server-rendered on purpose and this should work before app.js arrives. */
.searchbtn {
  display: none; align-items: center; justify-content: center;
  width: 38px; height: 38px; margin-left: auto; margin-right: -0.35rem;
  color: var(--fg); border-radius: 8px; cursor: pointer;
}
.searchbtn:hover { background: var(--card); }
#searchtoggle:focus-visible + .topsearch + .searchbtn {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

/* turns feed - the site's thesis, made visible above everything else */
.turnfeed { list-style: none; padding: 0; margin: 0; }
.turnfeed li { padding: 1.1rem 0; border-top: 1px solid var(--line); }
.turnhead { display: flex; align-items: center; gap: 0.55rem; flex-wrap: wrap; margin: 0 0 0.4rem; }
.turnhead a { text-decoration: none; font-weight: 600; }
.turnfeed .delta { font-variant-numeric: tabular-nums; font-size: 0.85rem; }
.turnfeed .delta span { color: var(--muted); text-decoration: line-through; }
.turnfeed .delta strong { color: var(--danger); }
.claim-link { display: block; font-size: 1.05rem; line-height: 1.4; text-decoration: none; margin-bottom: 0.3rem; }

/* people grid */
.people { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.9rem;
          grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); }
.people li { padding: 0.9rem 1rem; background: var(--card);
             border: 1px solid var(--line); border-radius: 8px; }
.pname { text-decoration: none; font-weight: 600; display: inline-block; }
.people .meta { display: block; margin-top: 0.15rem; }
.hook { margin: 0.5rem 0 0; font-size: 0.9rem; color: var(--fg); line-height: 1.45; }

/* legal pages */
.prose { padding-left: 1.15rem; }
.prose li { margin: 0.45rem 0; }
main h2.sec:first-of-type { margin-top: 2rem; }
code { font-size: 0.9em; background: var(--card); padding: 0.1rem 0.3rem;
       border: 1px solid var(--line); border-radius: 4px; }
.foot { display: flex; justify-content: space-between; gap: 1rem; }
.foot nav a { color: var(--muted); margin-left: 0.9rem; }

@media (max-width: 34rem) {
  .top { flex-wrap: wrap; row-gap: 0; }
  .brand { order: 1; }
  .searchbtn { display: inline-flex; order: 2; }
  .top nav { order: 3; }
  .topsearch { display: none; order: 4; flex-basis: 100%; max-width: none; margin: 0.55rem 0 0.1rem; }
  #searchtoggle:checked ~ .topsearch { display: block; }
  .hero h1 { font-size: 1.6rem; }
}
.stats { color: var(--muted); font-size: 0.9rem; margin: 0 0 0.5rem; }
.stats strong { color: var(--fg); }
.deltaline { margin: 0.4rem 0; }
.changes .delta { font-variant-numeric: tabular-nums; }
.changes li.turn .delta strong { color: var(--danger); }

/* --------------------------------------------------------------- timeline */
/* Geometry is derived from two variables so the dot can never drift off the
   rail: --rail is the rail's centre measured from the timeline's left edge,
   and every other offset is calculated from it. Changing the gutter or the dot
   size on mobile only means overriding the variables. */
.timeline {
  --rail: 7px;          /* centre of the vertical line */
  --dot: 11px;
  --gutter: 1.9rem;     /* text indent; must clear the rail */
  list-style: none; margin: 1.5rem 0 0; padding: 0 0 0 var(--gutter);
  position: relative;
}

/* No bottom padding: the gap element owns the whitespace between entries, so
   the interval label can sit exactly halfway between them instead of drifting
   toward whichever entry has less padding above it. */
.tl-item { position: relative; padding: 0; }
.tl-item.tl-last { padding-bottom: 0.5rem; }

/* the dot, centred exactly on the rail */
.tl-item::before {
  content: ''; position: absolute; z-index: 1;
  left: calc(var(--rail) - var(--gutter) - (var(--dot) / 2));
  top: 0.3rem; width: var(--dot); height: var(--dot);
  border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 0 3px var(--bg);
}
.tl-item.turn::before { background: var(--bg); border: 2px solid var(--danger); }

/* rail segment from this dot down to the next one. The last item draws none,
   so the line ends at the final dot rather than dangling into whitespace. */
.tl-item:not(.tl-last)::after {
  content: ''; position: absolute;
  left: calc(var(--rail) - var(--gutter) - 1px);
  top: calc(0.3rem + (var(--dot) / 2)); bottom: 0;
  width: 2px; background: var(--line);
}

.tl-gap { position: relative; height: var(--h); display: flex; align-items: center; }
.tl-gap::before {
  content: ''; position: absolute;
  left: calc(var(--rail) - var(--gutter) - 1px);
  top: 0; bottom: 0; width: 2px; background: var(--line);
}
/* The interval is a marker ON the rail, not a line of the entry above it.
   Sitting it left of the text column, with the rail running behind its
   background, makes it read as an interruption in the line rather than as
   trailing content of the previous post. */
.tl-gap span {
  position: absolute; z-index: 1;
  left: calc(var(--rail) - var(--gutter) - 0.3rem);
  top: 50%; transform: translateY(-50%);
  font-size: 0.7rem; color: var(--muted); letter-spacing: 0.01em;
  padding: 0.15rem 0.4rem; background: var(--bg);
  border-radius: 4px; white-space: nowrap;
}

.tl-date { margin: 0 0 0.3rem; color: var(--muted); font-size: 0.85rem; }
.tl-date .conf { color: var(--fg); font-variant-numeric: tabular-nums; }

/* Claims read as headings that happen to be clickable, not as blue link text.
   A page where every claim is a big blue block is a wall, and it made internal
   navigation look like an outbound link. External links keep the arrow. */
.claim-link, .korent-list a, .turnfeed .claim-link {
  color: var(--fg); font-weight: 550; text-decoration: none;
}
.claim-link:hover, .korent-list a:hover { text-decoration: underline; text-underline-offset: 2px; }
.ext { font-size: 0.85em; opacity: 0.75; }
.src a { text-decoration: none; }
.src a:hover { text-decoration: underline; }

/* the turn colour moved with the markup onto the timeline item */
.tl-item.turn .delta strong { color: var(--danger); }
.tl-item .deltaline { margin: 0 0 0.35rem; }
.tl-item .delta { font-variant-numeric: tabular-nums; font-size: 0.9rem; }
.tl-item .delta span { color: var(--muted); text-decoration: line-through; }

/* ------------------------------------------------------------ source link */
/* A citation, not a domain: the title is the link, the domain is context. */
.src { margin: 0.5rem 0 0; }
/* Inline, so a long title wraps as a sentence. As a wrapping flex row the arrow
   sat alone on its own line above any title too long for the phone. */
.src a { display: inline; text-decoration: none; line-height: 1.4; }
.src-arrow { color: var(--muted); font-size: 0.8rem; margin-right: 0.35rem; }
.src-title {
  color: var(--accent); font-size: 0.9rem;
  text-decoration: underline; text-underline-offset: 2px;
  text-decoration-color: color-mix(in srgb, var(--accent) 40%, transparent);
}
.src a:hover .src-title { text-decoration-color: var(--accent); }
.src-dom { color: var(--muted); font-size: 0.78rem; margin-left: 0.4rem; }

/* ----------------------------------------------------------------- chart */
.chart {
  margin: 1.25rem 0 0; padding: 0.75rem 0.5rem 0.25rem;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
}
.chart figcaption {
  font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); margin: 0 0 0.25rem 0.5rem; font-weight: 600;
}
.chart svg { display: block; width: 100%; height: auto; }
.ch-line { stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; }
.ch-dot { fill: var(--accent); }
.ch-grid { stroke: var(--line); stroke-width: 1; }
.ch-axis { fill: var(--muted); font-size: 10px;
           font-family: ui-sans-serif, system-ui, sans-serif; }

/* holder entries sit on the timeline like everything else */
.tl-item .holder-head { display: flex; align-items: center; gap: 0.55rem;
                        flex-wrap: wrap; margin: 0 0 0.35rem; }
.tl-item .holder-head a { text-decoration: none; font-weight: 600; color: var(--fg); }
.tl-item .holder-head a:hover { text-decoration: underline; text-underline-offset: 2px; }
.tl-item .conf { font-variant-numeric: tabular-nums; font-weight: 600; }

@media (max-width: 34rem) {
  /* narrower gutter on a phone; the calc keeps the dot on the rail */
  .timeline { --rail: 6px; --gutter: 1.5rem; }
}

/* A one-line description of who someone is. Editorial -- our words, not a
   quote -- so it is kept short, factual, and visually distinct from the
   verbatim material below it. */
.bio { margin: 0.4rem 0 0.9rem; color: var(--fg); max-width: 34rem; }

/* subject chips: a claim's subjects, linking to that subject's timeline */
.subjects { margin: 0.6rem 0 0; display: flex; flex-wrap: wrap; gap: 0.35rem; }
.chip {
  display: inline-block; padding: 0.12rem 0.5rem;
  font-size: 0.75rem; text-decoration: none;
  color: var(--muted); background: var(--card);
  border: 1px solid var(--line); border-radius: 999px;
}
.chip:hover { color: var(--fg); border-color: var(--accent); }

/* ---------------------------------------------------------- touch polish */
/* Android Chrome paints a rectangular tap highlight over the touch target and
   ignores border-radius, so a square flashed over the pill-shaped chips.
   Suppress it and supply our own pressed state, which follows the shape. */
a, button, summary, .chip, input, textarea, select {
  -webkit-tap-highlight-color: transparent;
}
.chip:active { background: var(--line); border-color: var(--accent); color: var(--fg); }
.btn:active { filter: brightness(0.92); }
.claim-link:active, .src a:active, .pname:active { text-decoration: underline; }

/* :hover sticks after a tap on a touch screen, leaving a chip looking selected
   long after you have moved on. Hover affordances only apply to real pointers. */
@media (hover: none) {
  .chip:hover { color: var(--muted); border-color: var(--line); }
  .claim-link:hover, .korent-list a:hover, .pname:hover { text-decoration: none; }
}

/* keyboard focus still needs a visible ring, matching each element's shape */
a:focus-visible, button:focus-visible, summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px;
}
.chip:focus-visible { border-radius: 999px; }

/* subject reference block */
.subject-blurb { font-size: 1rem; margin: 0.75rem 0 0.5rem; }
.subject-links { display: flex; flex-wrap: wrap; gap: 0.9rem; margin: 0 0 1.25rem; }
.subject-links a { font-size: 0.85rem; text-decoration: none; }
.subject-links a:hover { text-decoration: underline; text-underline-offset: 2px; }

/* admin key/value tables */
.kv { width: 100%; border-collapse: collapse; margin: 0.5rem 0 1.5rem; }
.kv th { text-align: left; font-weight: 400; color: var(--muted); padding: 0.45rem 0.75rem 0.45rem 0;
         border-bottom: 1px solid var(--line); vertical-align: top; }
.kv td { text-align: right; font-variant-numeric: tabular-nums; padding: 0.45rem 0;
         border-bottom: 1px solid var(--line); }
.kv td.warn { color: var(--danger); font-weight: 600; }
.kv td .meta { display: block; }

/* -------------------------------------------------- hierarchy in an entry */
/* A person's name and their claim were both bold and both foreground-coloured,
   so "who" and "what they said" looked like the same kind of thing. The claim
   is the subject of the page and stays dominant; the name is navigation, so it
   takes the link colour and drops a size. */
.tl-item .holder-head a,
.turnhead a {
  color: var(--accent); font-weight: 600; font-size: 0.95rem;
  text-decoration: none;
}
.tl-item .holder-head { margin-bottom: 0.15rem; }
.tl-item .claim-link { font-size: 1.02rem; line-height: 1.4; }

/* the hero earns much less space now that browsing starts immediately below */
.hero.compact { padding: 1.5rem 0 0; }
.hero.compact h1 { font-size: 1.55rem; margin-bottom: 0.5rem; }
.hero.compact .lede { font-size: 0.98rem; margin-bottom: 0.6rem; max-width: 32rem; }
.hero.compact .search { margin: 0.9rem 0 0; }
.block:first-of-type { margin-top: 2rem; }

/* subject chips as a browsable cloud */
.subjects.wrap { gap: 0.4rem; }
.subjects.wrap .chip { padding: 0.25rem 0.65rem; font-size: 0.82rem; }
.chip-n { color: var(--muted); font-variant-numeric: tabular-nums; font-size: 0.9em; }

/* ------------------------------------------------------------ browse rows */
.browse { margin: 0.6rem 0 0; }
.browse .subjects.wrap { margin: 0; }
.browse .more { margin-top: 0.45rem; }
.browse .more > summary {
  display: inline-block; cursor: pointer; list-style: none;
  font-size: 0.8rem; color: var(--accent);
  padding: 0.15rem 0; user-select: none;
}
.browse .more > summary::-webkit-details-marker { display: none; }
.browse .more > summary::after { content: ' \2193'; }
.browse .more[open] > summary::after { content: ' \2191'; }
.browse .more[open] > summary { margin-bottom: 0.45rem; }
.block .disclaimer { margin-top: 0.75rem; }

/* ------------------------------------------------- before / after labelling */
/* A change of mind is two statements and a reader should not have to infer
   which is which. The small label names each half. */
.wasnow { margin: 0.35rem 0; }
.wasnow .lbl {
  display: inline-block; margin-right: 0.4rem;
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); vertical-align: 1px;
}
.wasnow .claim-link { display: inline; }
.wasnow .note { color: var(--fg); }
blockquote .lbl {
  display: block; margin-bottom: 0.2rem;
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); font-style: normal;
}

/* ------------------------------------------------- a change of mind, quietly */
/* The state was shown three ways at once -- a red badge, a red delta, and the
   labels -- which read as an alarm about the person rather than a note about
   the claim. It now sits once, on the claim it is actually about, in the same
   muted weight as every other label. Red is for errors; changing your mind is
   not one. */
.lbl.was { color: var(--muted); }
.tl-item.turn::before { border-color: var(--muted); }
.turnfeed .delta strong, .tl-item.turn .delta strong,
.changes li.turn .delta strong { color: var(--fg); font-weight: 600; }
.badge.danger { color: var(--muted); border-color: var(--line); }

/* --------------------------------------------- held versus no longer held */
/* The hierarchy was backwards: the abandoned claim was the boldest thing on
   the entry while the correction underneath it was smaller. What someone no
   longer believes should recede, and what they say now should carry the
   weight. The distinction is past-versus-present, not good-versus-bad, so it
   is made with contrast rather than colour -- solid dot and full-contrast text
   for a live position, hollow dot and receded text for an abandoned one. */
.tl-item.turn .claim-link {
  color: var(--muted); font-weight: 500;
}
.tl-item.turn .wasnow .note,
.turnfeed li .wasnow .note { color: var(--fg); }
.lbl.was {
  color: var(--muted); border: 1px solid var(--line);
  border-radius: 999px; padding: 0.08rem 0.45rem;
}
.tl-item::before { background: var(--accent); }
.tl-item.turn::before { background: var(--bg); border: 2px solid var(--line); }

/* ------------------------------------------------------ person identity */
/* A monogram in a stable hue, so the same person looks the same everywhere.
   Hue is derived from the handle; saturation and lightness are fixed here, so
   nobody gets an unreadable colour and nobody shouts louder than the text. */
.timeline.with-avatars { --rail: 14px; --gutter: 2.9rem; }
.tl-item.has-marker::before { display: none; }
.tl-avatar {
  position: absolute; z-index: 1;
  left: calc(var(--rail) - var(--gutter) - 12px);
  top: 0.05rem; width: 24px; height: 24px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: hsl(var(--h) 42% 42%); color: #fff;
  font-size: 0.62rem; font-weight: 700; letter-spacing: 0.02em;
  box-shadow: 0 0 0 3px var(--bg);
}
.tl-item.turn .tl-avatar { background: var(--bg); color: hsl(var(--h) 42% 42%);
  box-shadow: 0 0 0 3px var(--bg), inset 0 0 0 2px hsl(var(--h) 42% 55%); }

/* --------------------------------------------------------------- filters */
.filters { margin: 1rem 0 0; padding: 0.75rem 0.85rem; background: var(--card);
           border: 1px solid var(--line); border-radius: 8px; }

/* The collapsible variant. Scoped to details.filters so the browse page's plain
   div.filters keeps its own padding untouched. */
details.filters { padding: 0; margin-top: 0.6rem; }
details.filters > .fsum {
  display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap;
  padding: 0.55rem 0.8rem; cursor: pointer; list-style: none;
  font-size: 0.85rem;
}
details.filters > .fsum::-webkit-details-marker { display: none; }
details.filters > .fsum::after {
  content: "›"; margin-left: auto; color: var(--muted);
  transform: rotate(90deg); transition: transform 120ms ease;
}
details.filters[open] > .fsum::after { transform: rotate(-90deg); }
details.filters[open] > .fsum { border-bottom: 1px solid var(--line); }
.fsum-l { font-weight: 600; color: var(--fg); }
.fsum-v { color: var(--muted); }
.filterbody { padding: 0.7rem 0.8rem; }

/* With the hero gone the feed heading is the first thing in main, so it stops
   carrying the 2.5rem of air it needed when it followed one. */
.block.feedblock { margin-top: 0; }
.feedblock > .sec:first-child { margin-top: 0; }
.filterrow { display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; }
.filterrow + .filterrow { margin-top: 0.55rem; }
.flabel { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.06em;
          color: var(--muted); min-width: 3rem; }
.chip.on { background: var(--accent); color: #fff; border-color: var(--accent); }
.chip.on .chip-n { color: rgba(255,255,255,0.75); }
.chip.person { display: inline-flex; align-items: center; gap: 0.35rem; }
.chip.person .mono {
  width: 16px; height: 16px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: hsl(var(--h) 42% 42%); color: #fff;
  font-size: 0.55rem; font-weight: 700;
}
.chip.person.on .mono { background: #fff; color: hsl(var(--h) 42% 38%); }

@media (max-width: 34rem) {
  .timeline.with-avatars { --rail: 13px; --gutter: 2.6rem; }
  .flabel { min-width: 100%; }
}

/* The quote is evidence, not the headline. It was rendering at full foreground
   weight and competing with the claim above it. */
blockquote { color: var(--muted); font-size: 0.95rem; }
.tl-item.turn blockquote { color: var(--muted); }
#topic-live[aria-busy="true"] { opacity: 0.55; transition: opacity 120ms ease; }

/* Sticky, because no amount of scroll restoration can hold a position on a
   page that just got shorter -- filtering to one person removes most of the
   height, and the browser clamps. Pinning the controls to the top of the
   viewport means the thing you are clicking never moves, whatever happens to
   the results below it. */
.filters {
  position: sticky; top: 0; z-index: 5;
  background: var(--card);
  box-shadow: 0 1px 0 var(--line), 0 6px 12px -8px rgba(0,0,0,0.25);
}

/* a subject several people are on record about is worth more than one nobody
   contests, so it gets a visible edge rather than being sorted and forgotten */
.chip.multi { border-color: var(--accent); color: var(--fg); }
.block .hint { margin-top: 0.5rem; }
#browse .filters { position: static; box-shadow: none; margin-bottom: 0.6rem; }

/* ------------------------------------ a change of mind, as a correction note */
/* Designed with Fable. The hierarchy problem was never weight: the abandoned
   claim came first AND carried the loudest label, so it read as the headline
   while the styling said it was subordinate. The fix is a step in SIZE and
   STRUCTURE, not more bold -- on a text-only site the prominence ladder is
   size > weight > colour-contrast, and colour-contrast is already spent.
   The old claim is rendered as CITED material, the way a newspaper sets a
   correction: quoted in small print with a rule beside it, not struck through.
   Strikethrough and opacity were both rejected -- line-through reads as
   deletion, opacity reads as disabled UI and fails contrast in dark mode.
   --muted measures 5.2:1 light and 6.13:1 dark against --bg, so it clears AA
   for body text and no extra token is needed. */
.entry-label {
  font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.06em;
  font-weight: 600; color: var(--muted); margin-right: 0.45em;
  white-space: nowrap;
}
.entry-was {
  margin: 0.2rem 0 0.5rem;
  font-size: 0.875rem; color: var(--muted); font-weight: 400; line-height: 1.5;
  border-left: 2px solid var(--line); padding-left: 0.6rem;
}
.entry-was .claim-link { font-size: inherit; font-weight: 400; color: var(--muted); }
.entry-now {
  margin: 0 0 0.35rem;
  font-size: 1rem; color: var(--fg); font-weight: 500; line-height: 1.45;
}
.entry-now .claim-link { font-size: inherit; font-weight: 500; color: var(--fg); }
/* Never accent text inside a turn: in a turn neither line is a live position,
   so accent would contradict the hollow dot. */
.tl-item.turn .entry-now .claim-link { color: var(--fg); }

/* ------------------------------------------- person vs person (experimental) */
/* Designed with Fable. Time is ordinal with labelled gap rows, never
   proportional: cards are tall variable-height text, so a real time scale
   collides same-date entries and turns silences into dead screens. A semantic
   table gives screen readers the cross-headers free. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.viewswitch { display: inline-flex; border: 1px solid var(--line);
  border-radius: 8px; overflow: hidden; margin: 0.5rem 0 1rem; }
.viewswitch a { padding: 0.35rem 0.8rem; font-size: 0.85rem;
  color: var(--muted); text-decoration: none; }
.viewswitch a[aria-current] { color: var(--fg); background: var(--card);
  box-shadow: inset 0 -2px 0 var(--accent); }

/* Only this scroller moves sideways; vertical scrolling stays the page's, so
   there is no nested-scroll trap and no scroll-jacking on touch. */
.vs-scroller { overflow-x: auto; -webkit-overflow-scrolling: touch;
  scroll-padding-left: 64px; outline-offset: -2px; }
.vs-scroller:focus-visible { outline: 2px solid var(--accent); }

.vs { border-collapse: separate; border-spacing: 0; table-layout: fixed; }
.vs th, .vs td { vertical-align: top; padding: 6px 8px; text-align: left; }
.vs thead .person { width: min(72vw, 300px);
  font-size: 0.82rem; color: var(--fg); padding-bottom: 10px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vs .t { width: 56px; min-width: 56px; position: sticky; left: 0; z-index: 2;
  background: var(--bg); border-right: 1px solid var(--line);
  font-size: 0.72rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.vs .t time b { display: block; font-weight: 400; }

.mg { display: inline-grid; place-items: center; width: 20px; height: 20px;
  border-radius: 50%; background: hsl(var(--hue) 55% 45%); color: #fff;
  font-size: 0.62rem; font-weight: 700; vertical-align: -4px; margin-right: 4px; }

.pos { background: var(--card); border: 1px solid var(--line);
  border-top: 3px solid hsl(var(--hue) 55% 50%); border-radius: 8px;
  padding: 10px; font-size: 0.85rem; line-height: 1.4; }
.pos + .pos { margin-top: 8px; }
.pos .stance { margin: 0; font-size: 0.68rem; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--muted); }
/* struck on the LABEL only, so the distinction survives greyscale without the
   claim itself reading as deleted */
.pos.dropped .stance { text-decoration: line-through; }
.pos .pos-claim { margin: 0.4em 0 0.3em; color: var(--fg);
  font-size: 0.85rem; font-weight: 500; line-height: 1.4; }
.pos .pos-claim a { color: var(--fg); text-decoration: none; font-size: inherit; }
.pos .pos-claim a:hover { text-decoration: underline; text-underline-offset: 2px; }
.pos .q summary { font-size: 0.75rem; color: var(--muted); cursor: pointer; }
.pos .q blockquote { margin: 0.4em 0 0; padding-left: 0.6em;
  border-left: 2px solid var(--line); font-size: 0.8rem; color: var(--muted); }
.pos .src { font-size: 0.75rem; color: var(--accent); text-decoration: none; }

/* silence, drawn as a continuity line in a neutral colour -- the person's hue
   should mean data, not absence */
.vs td:empty { background: linear-gradient(var(--line), var(--line)) 50% 0/1px 100% no-repeat; }

.vs tr.gap td { padding: 14px 0; text-align: left; }
.vs tr.gap span { position: sticky; left: 64px; display: inline-block;
  font-size: 0.7rem; color: var(--muted); letter-spacing: 0.05em; }
.vs tr.gap span::before, .vs tr.gap span::after { content: "·  "; color: var(--line); }

/* ------------------------------------------------- vs view: 2D panning */
/* The scroller now scrolls BOTH axes with a bounded height, so a finger can
   pan diagonally like a canvas instead of the browser picking one axis per
   gesture. The cost is a nested vertical scroller inside the page -- normally
   worth avoiding, but this view is a map and a map pans. Scroll chaining is
   left at its default so reaching an edge continues onto the page rather than
   trapping you.
   The bounded height also makes a sticky header row possible for the first
   time: sticky-top resolves against the nearest scrolling ancestor, which
   previously did not scroll vertically. */
.vs-scroller { overflow: auto; max-height: 78vh; overscroll-behavior: auto; }
.vs thead th { position: sticky; top: 0; z-index: 3; background: var(--bg);
  box-shadow: inset 0 -1px 0 var(--line); }
.vs thead th.t { left: 0; z-index: 4; }

/* The specified column width was being ignored: with table-layout:fixed and no
   explicit table width, the table shrinks to its container and redistributes,
   so 300px columns rendered at 124px. max-content lets the declared widths
   stand and the table overflow, which is what makes the scroller scroll. */
.vs { width: max-content; min-width: 100%; }
.vs thead .person { width: min(80vw, 340px); }

/* Narrower again so a column fits the phone without cutting sentences, with a
   sliver of the next column left visible as the affordance that more exists. */
.vs thead .person { width: min(70vw, 300px); }

/* The rail is now an index, not just a clock: it names who spoke at each
   moment even when their column is off-screen, which is the one thing the
   grid's emptiness otherwise hides. */
.railhints { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 5px; }
.railhint {
  display: inline-grid; place-items: center; width: 18px; height: 18px;
  border-radius: 50%; background: hsl(var(--hue) 55% 45%); color: #fff;
  font-size: 0.55rem; font-weight: 700; text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.railhint:active { transform: scale(0.92); }
.railhint:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vs td.flash > .pos { box-shadow: 0 0 0 2px hsl(var(--hue) 55% 50%); transition: box-shadow 200ms; }

/* ------------------------------------------- compare view (standings + threads) */
/* The standings grid is the honest side-by-side: everyone's CURRENT position,
   all visible at once on a phone, at the one timestamp where the comparison is
   actually true for every person. */
.standings ol { list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
.standings a { display: block; background: var(--card); border: 1px solid var(--line);
  border-top: 3px solid hsl(var(--hue) 55% 50%); border-radius: 8px;
  padding: 8px 10px; color: var(--fg); text-decoration: none; font-size: 0.8rem; }
.standings .who { display: flex; align-items: center; gap: 5px; font-weight: 600; }
.standings .asof { display: block; margin-top: 2px; font-size: 0.68rem;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.standings a.dropped .asof { text-decoration: line-through; }
.standings .sclaim { margin: 0.35em 0 0; color: var(--muted); line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }

/* One thread per person down the gutter, in their hue. A thread runs unbroken
   through other people's cards -- that line is the "silent, position still
   standing" information -- and starts and stops at their first and last entry,
   which reads as entering and leaving the conversation. */
.feed { --lane-w: 12px; list-style: none; margin: 1.5rem 0 0; padding: 0; }
.fentry, .gaprow { display: grid;
  grid-template-columns: calc(var(--lanes) * var(--lane-w) + 10px) 1fr; }
.feed .rail { position: relative; }
.feed .rail b { position: absolute; top: 0; bottom: 0; width: 2px; border-radius: 1px;
  left: calc(var(--lane) * var(--lane-w) + 4px);
  background: hsl(var(--hue) 55% 50% / 0.45); }
.feed .rail .node { position: absolute; top: 0.9rem; width: 10px; height: 10px;
  border-radius: 50%; left: calc(var(--lane) * var(--lane-w));
  background: hsl(var(--hue) 55% 50%); box-shadow: 0 0 0 3px var(--bg); }
.fentry.dropped .rail .node { background: var(--bg);
  box-shadow: 0 0 0 3px var(--bg), inset 0 0 0 2px hsl(var(--hue) 55% 50%); }
.fentry .card { min-width: 0; margin: 4px 0 18px; }
.gaprow .gaplabel { font-size: 0.7rem; color: var(--muted);
  padding: 10px 0; letter-spacing: 0.05em; }

/* --------------------------------------------- whose words are whose */
/* The bold headline on a card was korents' own restatement and the person's
   actual words were hidden behind a collapsed toggle -- the exact inversion of
   what this site claims to be. Verbatim text now leads and is labelled; the
   restatement follows, labelled as ours. A reader must never have to guess
   which sentence a human wrote. */
blockquote.verbatim {
  color: var(--fg); font-size: 1rem; font-style: normal;
  border-left: 3px solid var(--accent); padding: 0.1rem 0 0.1rem 0.8rem;
  margin: 0.35rem 0 0.5rem;
}
blockquote.verbatim .entry-label { display: block; margin-bottom: 0.2rem; }
.restated { margin: 0 0 0.4rem; font-size: 0.9rem; }
.restated .entry-label { display: block; margin-bottom: 0.15rem; }
.restated .claim-link { color: var(--muted); font-weight: 400; font-size: 0.9rem; }
.pos .pos-label { margin: 0.5em 0 0.1em; font-size: 0.62rem; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--muted); }
.pos .q[open] summary { margin-bottom: 0.25rem; }

/* ------------------------------------------------- also held by */
/* A co-holder who is on this screen can be jumped to; one who is filtered out
   or simply absent cannot. Same shape, different weight and destination, so
   the affordance never promises a jump it cannot honour. */
.alsoheld { display: flex; align-items: center; flex-wrap: wrap; gap: 4px;
  margin: 0.5rem 0 0; }
.alsoheld .entry-label { margin-right: 0.15rem; }
.alsochip { display: inline-grid; place-items: center; width: 19px; height: 19px;
  border-radius: 50%; font-size: 0.55rem; font-weight: 700; text-decoration: none;
  -webkit-tap-highlight-color: transparent; }
.alsochip.here { background: hsl(var(--hue) 55% 45%); color: #fff; }
/* hollow = present in the record but not on this screen */
.alsochip.away { background: var(--bg); color: hsl(var(--hue) 55% 42%);
  box-shadow: inset 0 0 0 2px hsl(var(--hue) 55% 55%); }
.alsochip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.alsomore { font-size: 0.7rem; color: var(--muted); text-decoration: none; margin-left: 0.2rem; }
.alsomore:hover { text-decoration: underline; }

/* The restated claim is a link to the korent, but muted-with-no-underline made
   it read as plain text. Claim links elsewhere on the site drop the blue to
   avoid a wall of colour and rely on size and weight to look clickable -- this
   one is deliberately small and light, so it has nothing left to signal with
   and needs the underline back. */
.restated .claim-link,
.pos .pos-claim a {
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
  text-underline-offset: 3px;
}
.restated .claim-link:hover,
.pos .pos-claim a:hover { text-decoration-color: currentColor; }
.restated .claim-link::after { content: ' \203A'; opacity: 0.6; }
.disclaimer strong { color: var(--fg); font-weight: 600; }

/* ------------------------------------------------ a quotation, continued */
/* The homepage feed can only carry the opening of a long quotation, so it stops
   at the end of a sentence and hides the remainder behind the ellipsis itself.
   The dots are where a reader's thumb already goes when a sentence breaks off,
   so the dots are the control rather than a separate button parked underneath.

   A hidden checkbox rather than <details>, and rather than script. See
   saidLine() in pages.js: <details> cannot reveal its content inline in Chrome,
   and the two ways to make it try both break the closed state. Everything here
   is an inline box, so the sentence closes up around the control when it goes.

   No collapse, deliberately: nobody who has just asked for the end of a
   sentence wants to put it back, and a "less" control would have to sit in the
   middle of the quote. */
.said-toggle { position: absolute; width: 0; height: 0; opacity: 0; }
.said-rest { display: none; }
.said-toggle:checked ~ .said-rest { display: inline; }
.said-toggle:checked ~ .said-cta { display: none; }
.said-cta {
  color: var(--accent); cursor: pointer; white-space: nowrap;
  font-size: 0.8125rem; font-weight: 600;
  /* Padding on an inline box grows the touch target without growing the line
     box, and the negative margin keeps the words either side where they were.
     A bare "... more" is a 51x19 target, which is a miss on a thumb. */
  padding: 0.55rem 0.3rem; margin: -0.55rem -0.3rem;
}
.said-toggle:focus-visible ~ .said-cta {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px;
}

/* ------------------------------------------------------------- home feed */
.feedlist { list-style: none; margin: 0.75rem 0 0; padding: 0; }
.feedrow { padding: 0.85rem 0; border-top: 1px solid var(--line); }
.feedrow .claim-link { display: block; font-size: 1rem; line-height: 1.4; }
.feedwho { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; margin: 0.45rem 0 0; }
.feedwho .meta { margin-left: 0.2rem; }
/* The holder's name is a link like it is everywhere else, but it sits in muted
   metadata, so it keeps the muted colour and earns a faint underline instead --
   enough to read as tappable without competing with the claim above it. */
.feedwho .meta a { color: inherit; text-decoration: underline;
  text-decoration-color: var(--line); text-underline-offset: 2px; }
.feedwho .meta a:hover { color: var(--accent); text-decoration-color: currentColor; }
#feed .filters { position: static; box-shadow: none; }
.footstats { margin-top: 2.5rem; padding-top: 1rem; border-top: 1px solid var(--line); }
.chip .mono { width: 16px; height: 16px; border-radius: 50%; display: inline-flex;
  align-items: center; justify-content: center; background: hsl(var(--hue) 42% 42%);
  color: #fff; font-size: 0.55rem; font-weight: 700; margin-right: 0.3rem; }
.chip.on .mono { background: #fff; color: hsl(var(--hue) 42% 38%); }

/* ------------------------------------------------------------- relations */
/* Two blocks with deliberately different weight. An asserted relation carries a
   reason and gets a card; a subject match asserts nothing and gets a list row. */
.edges { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.75rem; }
.edge {
  padding: 0.8rem 0.9rem; border: 1px solid var(--line);
  border-left: 3px solid var(--edge, var(--line)); border-radius: 8px; background: var(--card);
}
.edge-contradicts { --edge: var(--danger); }
.edge-tension     { --edge: var(--tension); }
.edge-depends_on  { --edge: var(--accent); }
.edge-kind {
  margin: 0 0 0.3rem; font-size: 0.72rem; font-weight: 650;
  text-transform: uppercase; letter-spacing: 0.07em; color: var(--edge, var(--muted));
}
.edge-claim { margin: 0 0 0.4rem; font-weight: 500; }
.edge-claim a { text-decoration: none; }
.edge-why { margin: 0; color: var(--muted); font-size: 0.88rem; }

.related { list-style: none; padding: 0; margin: 0; }
.related li { padding: 0.7rem 0; border-top: 1px solid var(--line); }
.related a { text-decoration: none; }
.rel-why { display: block; margin-top: 0.3rem; }
.mapcta { margin-top: 1.25rem; }

/* ------------------------------------------------------------------- map */
/* The map keeps its own scrollbar rather than shrinking to fit. Letting the SVG
   scale down with the column drove the claim text to an effective ~5px on a
   phone -- a picture of a map rather than a readable one. A min-width plus
   horizontal scroll keeps every label at a legible size at any viewport. */
.mapfig { margin: 1.5rem 0 0; padding: 0; overflow-x: auto; }
.mapsvg { width: 100%; min-width: 700px; height: auto; display: block; }
.crumb { margin: 0 0 0.5rem; font-size: 0.85rem; }
.crumb a { text-decoration: none; color: var(--muted); }

.mp-link { stroke: var(--line); stroke-width: 1.5; }
.mp-link.mp-related { stroke-dasharray: 2 4; }
.mp-link.mp-contradicts { stroke: var(--danger); stroke-width: 2; }
.mp-link.mp-tension { stroke: var(--tension); stroke-width: 2; stroke-dasharray: 6 4; }
.mp-link.mp-depends_on { stroke: var(--accent); stroke-width: 1.75; }
.mp-arrowhead { fill: var(--accent); }

.mp-box { fill: var(--card); stroke: var(--line); stroke-width: 1.5; }
.mp-self .mp-box { stroke: var(--fg); stroke-width: 2; }
.mp-contradicts .mp-box { stroke: var(--danger); }
.mp-tension .mp-box { stroke: var(--tension); }
.mp-title { font-size: 12px; fill: var(--fg); }
.mp-self .mp-title { font-weight: 600; }
.mp-node { text-decoration: none; }
.mp-node:hover .mp-box { stroke: var(--accent); }
/* A claim nobody is on record for yet, at the scrubbed date. Faded rather than
   removed, so the ring never reflows under the cursor. */
.mp-node.is-future { opacity: 0.25; }

.mp-dot { fill: hsl(var(--hue) 55% 45%); stroke: var(--card); stroke-width: 1.5; }
.mp-holder[data-status="turned"] .mp-dot { fill: var(--card); stroke: hsl(var(--hue) 55% 45%); }
.mp-mono { font-size: 8.5px; font-weight: 700; fill: #fff; letter-spacing: 0.02em; }
.mp-holder[data-status="turned"] .mp-mono { fill: hsl(var(--hue) 55% 45%); }
.mp-holder.is-off { display: none; }

.mplegend {
  display: flex; flex-wrap: wrap; gap: 0.9rem; margin-top: 0.75rem;
  color: var(--muted); font-size: 0.8rem;
}
.mpkey { display: inline-flex; align-items: center; gap: 0.4rem; }
.mpkey i { width: 18px; height: 0; border-top: 2px solid var(--line); }
.mpkey.mp-contradicts i { border-color: var(--danger); }
.mpkey.mp-tension i { border-top-style: dashed; border-color: var(--tension); }
.mpkey.mp-depends_on i { border-color: var(--accent); }
.mpkey.mp-related i { border-top-style: dashed; }

.scrub { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--line); }
.scrub label { display: block; font-size: 0.9rem; color: var(--muted); }
.scrub output { color: var(--fg); font-weight: 600; }
.scrub input[type="range"] { width: 100%; margin-top: 0.5rem; accent-color: var(--accent); }
.scrubnote { margin: 0.4rem 0 0; color: var(--muted); font-size: 0.85rem; }

/* ------------------------------------------------------- recording page */

/* The whole recording as one bar. Ticks are links into the list below, so the
   strip is navigation as well as an overview: it is the only place that shows
   WHERE in five hours the substance was, and that the last two hours are
   thinner than the first. */
.rstrip {
  margin: 1.25rem 0 0; padding: 0.4rem 0.5rem 0.1rem;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
}
.rstrip svg { display: block; width: 100%; height: 46px; }
.rs-base { stroke: var(--line); stroke-width: 2; }
.rs-hour { stroke: var(--line); stroke-width: 1; }
.rs-lab { font-size: 9px; fill: var(--muted); }
.rs-tick { stroke: var(--accent); stroke-width: 2.5; }
/* A turn is hollow everywhere else on the site (see .tl-item.turn::before); on a
   1px-wide tick hollow is invisible, so it changes colour instead. Same meaning,
   the only encoding the shape allows. */
.rs-tick.turn { stroke: var(--danger); }
.rstrip a:hover .rs-tick, .rstrip a:focus .rs-tick { stroke-width: 5; }
.rstrip a:focus-visible { outline: none; }

/* The claim comes first and the quote supports it, which is the reverse of a
   korent page -- there the claim is the <h1> and needs no repeating. */
.rt-claim { margin: 0.1rem 0 0.15rem; }
.rt-claim .claim-link { font-size: 1rem; }
.rt-at { font-variant-numeric: tabular-nums; }
a.rt-at { text-decoration: none; }
a.rt-at:hover { text-decoration: underline; }
.rt-turn { color: var(--danger); }
/* Scrolled-to entries land under nothing, so give the anchor room to breathe
   rather than pinning the dot to the very top of the viewport. */
.rtimeline .tl-item { scroll-margin-top: 1.5rem; }

/* The exact-offset line sits under the quote it belongs to, on its own row: it
   is an invitation to go and check, and inlining it with the provenance badge
   made two different kinds of claim look like one sentence. */
.prov-line { display: block; margin-top: 0.15rem; }
a.prov-at { text-decoration: none; font-variant-numeric: tabular-nums; }
a.prov-at:hover { text-decoration: underline; }

/* A run of korents from one recording, collapsed to one feed entry. Bordered
   and indented so it reads as a container of claims rather than as a claim
   itself -- the thing being linked is a conversation, not a position. */
.feedrec { }
.recbadge { color: var(--muted); }
.recname { margin: 0.1rem 0 0.4rem; }
.recname .claim-link { font-size: 1rem; }
.recclaims {
  list-style: none; margin: 0 0 0.4rem; padding: 0 0 0 0.8rem;
  border-left: 2px solid var(--line);
}
.recclaims li { margin: 0.25rem 0; }
.recclaims a { color: var(--fg); text-decoration: none; font-size: 0.93rem; }
.recclaims a:hover { text-decoration: underline; }

/* "Our framing" — the claim sentence is ours, said next to the claim.
   Sits on its own line above the claim so the page reads OUR FRAMING → claim →
   THEIR WORDS → quote: same visual grammar as the quote's own label, which is
   what teaches the distinction without any prose. Deliberately quiet: it is a
   correction to a misreading, not a warning. */
.framing { margin: 0 0 0.1rem; }
.framing .entry-label { cursor: help; }
.claim + .framing, .framing + .claim { margin-top: 0; }

/* "· watch on youtube.com ↗" trailing the timestamp. Muted and lighter than the
   time itself, so the entry still scans by when-it-was-said, but a reader can
   see the link leaves the site rather than jumping within it. */
.rt-go { color: var(--muted); font-weight: 400; font-size: 0.85rem; }
a.rt-at:hover .rt-go { color: var(--accent); }
.rt-go .src-arrow { margin-left: 0.15em; }

/* "Their words now" — the view they hold instead. Rendered as a second quote
   block under the one they left, so the entry ends where the person actually
   stands rather than on the belief they abandoned. Marked by the accent colour
   the site uses for a live position, against the muted rail of the old one. */
blockquote.verbatim.now { border-left-color: var(--accent); margin-top: 0.5rem; }
.tl-item.turn blockquote.verbatim { border-left-color: var(--line); }
.tl-item.turn blockquote.verbatim.now { border-left-color: var(--accent); }

/* ------------------------------------------------- attribution review (admin) */
.aq { list-style: none; padding: 0; margin: 1.5rem 0 0; }
.aq-row { padding: 0.9rem 0; border-top: 1px solid var(--line); }
/* The one case where the heuristic is actually in doubt must not look like the
   38 routine confirmations around it, or it gets clicked through with them. */
.aq-row.uncertain { border-left: 3px solid var(--danger); padding-left: 0.8rem; }
.aq-flag { color: var(--danger); font-weight: 600; }
.aq-q { margin: 0.15rem 0; }
.aq-q a { color: var(--fg); text-decoration: none; font-weight: 500; }
.aq-q a:hover { text-decoration: underline; }
.aq-cand { color: var(--danger); }
.aq-v { font-weight: 600; }
.aq-v.ok { color: #1a7f37; }
.aq-v.bad { color: var(--danger); }
.aq-v.muted { color: var(--muted); }

/* 16:9, and it must stay watchable on the phone this gets reviewed from. */
.aq-player {
  position: relative; width: 100%; aspect-ratio: 16 / 9;
  margin: 1rem 0; background: #000; border-radius: 8px; overflow: hidden;
}
.aq-player iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

.aq-form { margin: 1.25rem 0; }
.aq-picks { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 0.5rem 0 1rem; }
/* Full-width targets on a phone: this is a decision, not a toolbar. */
.aq-pick { flex: 1 1 12rem; }

/* The review screen's audio/video element. Its styles used to live in the
   retired screen's inline <style>; the CSP moved the script out and the rules
   had to follow, or the player renders at the browser's default 300px. */
.ev-v {
  display: block; width: 100%; margin: 1rem 0;
  background: #000; border-radius: 8px;
  /* Audio-only sources in a video container render as a bare control bar, so
     don't reserve 16:9 of black for them. */
  max-height: 60vh;
}
/* A decision form's fields are stacked, always: the correction box sitting
   inline beside its label made a 200px input for a sentence of transcript. */
.aq-form label { display: block; margin-top: 0.75rem; }
.aq-form textarea { display: block; width: 100%; margin-top: 0.3rem; }
.aq-pick.muted { background: var(--card); color: var(--fg); border: 1px solid var(--line); }

/* ------------------------------------------------------ claim page chrome */
/* ------------------------------------------------- korent page, second pass */
/* Redesigned 2026-09-02 from a phone screenshot. Before this a reader passed an
   explainer sentence, a floating "OUR FRAMING", a kind badge, a byline, a rule,
   a counts sentence, a section heading and a five-line boxed disclaimer --
   eleven pieces of chrome -- before reaching the one quote the page exists to
   show, and nearly every page holds exactly one or two quotes. The spine is now
   claim -> who -> their words -> source. Every honesty notice survives, attached
   to the thing it qualifies, in the smallest form that still says it. */
.eyebrow { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 .45rem;
  margin: .35rem 0 0; color: var(--muted); font-size: .8rem; line-height: 1.6; }
.eyebrow-k { font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
  font-size: .7rem; color: var(--fg); }
.eyebrow-sep { color: var(--muted); opacity: .55; }
.eyebrow a { color: var(--muted); text-decoration: none; }
.eyebrow a:hover { color: var(--fg); text-decoration: underline; text-underline-offset: 2px; }
.eyebrow .framing-l { cursor: help; }
.claim { font-size: 1.5rem; line-height: 1.3; letter-spacing: -0.02em; font-weight: 650;
  margin: .35rem 0 .9rem; text-wrap: pretty; }

/* Who stands where: faces, names, verb -- one row per group, the feed card's
   own byline grammar. A row about a turn is receded and its faces hollow, the
   same past-versus-present contrast the timeline dots use. */
.status { margin: 0; }
/* Inline, not flex: two faces plus two long names must wrap as one sentence,
   with the faces leading the first line, not as a row of faces over a row of
   text. */
.st { margin: 0 0 .4rem; font-size: .95rem; line-height: 1.5; }
.st .feedfaces { display: inline-flex; vertical-align: -6px; margin-right: .4rem; }
.st .alsochip { width: 22px; height: 22px; font-size: .58rem; }
.st-text a { color: var(--fg); font-weight: 600; text-decoration: none; }
.st-text a:hover { text-decoration: underline; text-underline-offset: 2px; }
.st-text strong { font-variant-numeric: tabular-nums; }
.st-turned .st-text, .st-turned .st-text a { color: var(--muted); }
.st-by .st-text, .st-kind .st-text { color: var(--muted); font-size: .88rem; }

/* the chips, labelled, on one wrapping line */
.currents { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; margin: .9rem 0 0; }
.currents .entry-label { margin-right: .1rem; }

/* the record: a heading, a two-sentence note, then the people */
/* Ours, in a panel. The korrent -- noun, sentence, whose words it was drawn
   from -- sits on the card ground with a hairline round it; the quotes sit on
   the open page. The two registers are told apart before a word is read. */
.kpanel { background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: .85rem 1rem .6rem; }
.kpanel .eyebrow { margin: 0; }
.kpanel .claim { margin: .35rem 0 .8rem; }
.kpanel .status .st:last-child { margin-bottom: 0; }
.kpanel .currents { margin-top: .75rem; }
.kpanel .cgloss { margin-top: .5rem; }
.kwhat { margin: .8rem 0 0; border-top: 1px solid var(--line); }
.kwhat > summary { display: flex; align-items: center; gap: .5rem; min-height: 40px;
  cursor: pointer; list-style: none; color: var(--muted); font-size: .84rem; user-select: none; }
.kwhat > summary::-webkit-details-marker { display: none; }
.kwhat > summary::before { content: "i"; flex: none; display: inline-grid; place-items: center;
  width: 17px; height: 17px; border-radius: 50%; border: 1.5px solid currentColor;
  font: italic 700 .7rem/1 Georgia, "Times New Roman", serif; }
.kwhat > summary:hover { color: var(--fg); }
.kwhat-b { color: var(--muted); font-size: .88rem; line-height: 1.55; padding: 0 0 .5rem; }
.kwhat-b p { margin: 0 0 .5rem; }
.kwhat-b strong { color: var(--fg); font-weight: 600; }
.kwhat-b a { white-space: nowrap; }

/* Theirs, on the open page, threaded by the current. The rail is drawn in the
   accent from the foot of the panel, past the heading, through every quote: the
   korrent flowing out of the words it was drawn from. Same geometry variables
   as .timeline.with-avatars so the line meets each monogram dead centre. */
.record { --rail: 14px; --gutter: 2.9rem;
  --flow: color-mix(in srgb, var(--accent) 45%, transparent);
  margin-top: 1.6rem; }
.record + .record { margin-top: 1.9rem; }
.rechead { position: relative; padding-left: var(--gutter); }
.rechead::before { content: ""; position: absolute; width: 2px; background: var(--flow);
  left: calc(var(--rail) - 1px); top: -1.6rem; bottom: -1rem; }
.record + .record .rechead::before { top: -1.9rem; }
/* between two record sections the line continues past the last quote */
.record:has(+ .record) .tl-item.tl-last::after { content: ""; position: absolute;
  left: calc(var(--rail) - var(--gutter) - 1px); top: calc(0.3rem + (var(--dot) / 2)); bottom: 0;
  width: 2px; background: var(--flow); }
.record .tl-item:not(.tl-last)::after, .record .tl-gap::before { background: var(--flow); }
.record .sec { margin: 0 0 .3rem; }
.recnote { margin: 0; color: var(--muted); font-size: .82rem; line-height: 1.5; max-width: 36rem; }
.recnote strong { color: var(--fg); font-weight: 600; }
.record .timeline { margin-top: 1rem; }
.record blockquote.verbatim { padding-top: .05rem; padding-bottom: .05rem; }
@media (max-width: 34rem) { .record { --rail: 13px; --gutter: 2.6rem; } }
.rec-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: .1rem .5rem;
  margin: 0 0 .3rem; line-height: 1.35; }
.rec-name { color: var(--fg); font-weight: 650; font-size: .98rem; text-decoration: none; }
.rec-name:hover { text-decoration: underline; text-underline-offset: 2px; }
.rec-head .conf { font-variant-numeric: tabular-nums; font-weight: 600; font-size: .9rem; }
.rec-when { color: var(--muted); font-size: .82rem; margin-left: auto; white-space: nowrap; }
.tl-item.turn .rec-name { color: var(--muted); }
.recfoot { margin: 1.4rem 0 0; color: var(--muted); font-size: .8rem; }
.recfoot a { color: var(--muted); }
.recfoot a:hover { color: var(--fg); }

/* Their words get their own voice. A serif for verbatim material only, so a
   quotation looks like quoted matter before any label says so, and real
   quotation marks around the words themselves; everything the site writes
   stays in the sans, labels included. */
blockquote.verbatim, .qtext {
  font-family: Charter, "Iowan Old Style", "Palatino Linotype", Palatino, Georgia,
    Cambria, "Noto Serif", "Times New Roman", serif;
}
.entry-label { font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
blockquote.verbatim { font-size: 1.0625rem; line-height: 1.5; }
.entry-was .qtext { font-size: .95rem; }
.entry-now .qtext { font-size: 1.05rem; }
.qtext::before { content: "\201C"; }
.qtext::after { content: "\201D"; }
.qtext::before, .qtext::after { color: var(--muted); }
.qtext.bare::before, .qtext.bare::after { content: none; }
.tl-item .note { color: var(--muted); font-size: .9rem; margin: .2rem 0 .4rem; }
.tl-item .note .entry-label { display: block; margin-bottom: .1rem; }

/* Section labels sit a step down on this page: the claim and the quotes are
   the two loud things, and a heading as large as the body text competed with
   both. Related claims read as claims, in the foreground colour, like every
   other claim link on the site. */
body[data-k-page="korent"] .sec { font-size: .82rem; letter-spacing: .08em; }
body[data-k-page="korent"] .related a { color: var(--fg); font-weight: 500; line-height: 1.4; }
body[data-k-page="korent"] .related a:hover { text-decoration: underline; text-underline-offset: 2px; }

/* the holders chart folds until it has something to show */
.chartbox { margin: 1.5rem 0 0; }
.chartbox > summary { display: flex; align-items: center; gap: .4rem; min-height: 44px;
  cursor: pointer; list-style: none; color: var(--muted); font-size: .85rem; user-select: none; }
.chartbox > summary::-webkit-details-marker { display: none; }
.chartbox > summary::after { content: "›"; display: inline-block; transform: rotate(90deg);
  transition: transform 120ms ease; }
.chartbox[open] > summary::after { transform: rotate(-90deg); }
.chartbox .chart { margin-top: 0; }
.chartbox .chart figcaption { display: none; }

/* What the current IS, for the reader meeting the name here. Set as a
   dictionary entry -- the name in the page's own colour, the gloss muted --
   so it reads as reference material about the tag and not as another thing
   the person said. Only currents with a blurb get one. */
.cgloss { margin: .55rem 0 0; max-width: 34rem;
  color: var(--muted); font-size: .85rem; line-height: 1.5; }
.cgloss b { color: var(--fg); font-weight: 600; }
.cgloss + .cgloss { margin-top: .3rem; }
/* Folded until asked for: an action, offered after the evidence it responds to. */
.holdbox { margin: 2rem 0 0; border: 1px solid var(--line); border-radius: 8px;
  background: var(--card); }
.holdbox > summary { display: flex; align-items: center; gap: .5rem; min-height: 48px;
  padding: .6rem .9rem; cursor: pointer; list-style: none; font-weight: 500; user-select: none; }
.holdbox > summary::-webkit-details-marker { display: none; }
.holdbox > summary::after { content: "›"; margin-left: auto; color: var(--muted);
  transform: rotate(90deg); transition: transform 120ms ease; }
.holdbox[open] > summary::after { transform: rotate(-90deg); }
.holdbox .holdbody { padding: 0 .9rem .9rem; }
.holdbox .yours { margin: 0; border: 0; background: none; padding: 0; }
.yourpos { margin-top: 2rem; }

/* --------------------------------------------------- following and lists */
/* The three feeds -- everything, the people you follow, a list -- are the same
   rows with a different set of people behind them, so they share one tab strip
   sitting directly under the page title. */
/* Four tabs do not fit across a 390px phone at the .tabs gap, and the fourth
   was clipped by the viewport edge with nothing to say it was there. Tighter
   gap plus a scroller: the strip stays one line and can always be reached. */
.feedtabs {
  margin: 0 0 1rem; gap: 0.9rem;
  overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.feedtabs::-webkit-scrollbar { display: none; }
.feedtabs a { white-space: nowrap; }
.block.feedblock .feedtabs { margin-top: 0.5rem; }

/* A follow button is a form, and a form is a block element by default, which
   put every button on its own line and made a card twice as tall as it needed
   to be. */
.followform, .pcard-act form { display: inline-block; margin: 0; }
.btn.tiny { margin-top: 0; padding: 0.3rem 0.8rem; font-size: 0.85rem; }

.profileacts { display: flex; align-items: center; gap: 0.5rem; margin: 0.7rem 0 0; }
/* The Lists menu reuses the account menu's popover, minus the round avatar:
   same element, same open/close behaviour, same keyboard handling. */
.listmenu { margin-left: 0; }
.listmenu > summary {
  list-style: none; cursor: pointer; padding: 0.3rem 0.8rem;
  border: 1px solid var(--line); border-radius: 6px;
  color: var(--muted); font-size: 0.85rem;
}
.listmenu > summary::-webkit-details-marker { display: none; }
.listmenu > summary:hover { color: var(--fg); border-color: var(--accent); }
.listmenu .menu { right: auto; left: 0; min-width: 12rem; }
.listmenu .menu form { margin: 0; padding: 0; border: 0; }
.listmenu .menu button { color: var(--fg); width: 100%; text-align: left; }

/* A person card carries a badge, a name and an action; the action goes on its
   own line so a thumb never has to hit a button inline with a link to
   somewhere else. */
.pcard-head { display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap; }
.pcard-head .mg { flex: none; }
.pcard-act { display: flex; gap: 0.4rem; margin-top: 0.6rem; }
.people.lists li, .people.picker li { display: block; }
/* The picker on a list's edit screen is a long roll of every person on the
   site, so it is one column on a phone and reads as a list rather than a grid
   of cards you have to scan in two directions. */
.people.picker { grid-template-columns: 1fr; gap: 0.5rem; }
.people.picker li { padding: 0.6rem 0.8rem; }

.newlist { margin-top: 1rem; }
.newlist .form label { margin: 0.7rem 0; }
.newlist .form input[type=text], .newlist .form input:not([type]) { max-width: 22rem; }
.sortrow { margin: 0.9rem 0; }
.danger-form { margin-top: 2.5rem; }
.members { margin: 1rem 0; }
.members .people { grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); }
/* Three lines of bio on a card, ended by the browser at a rendered line rather
   than by a character count that lands mid-word. */
.hook.clamp {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
  overflow: hidden;
}

/* ---------- /admin/stats ----------
 *
 * Phone first, like everything else here: the cards are a two-column grid at
 * 390px and the session timeline is a single column that never needs a
 * sideways scroll, because this page is read on a phone the moment a
 * notification arrives -- which is the only moment its contents are urgent.
 */
.instr {
  border: 1px solid var(--line); border-radius: 8px;
  padding: 0.6rem 0.8rem; margin: 0.5rem 0 1rem;
  color: var(--muted); font-size: 0.9rem;
}
.instr.bad { border-color: var(--danger); color: var(--fg); }
.instr strong { color: var(--fg); }

.vstats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.5rem; margin: 0.5rem 0 1rem; }
.vstat {
  border: 1px solid var(--line); border-radius: 8px; padding: 0.6rem 0.7rem;
  display: flex; flex-direction: column; gap: 0.1rem; min-width: 0;
}
.vstat b { font-size: 1.5rem; font-variant-numeric: tabular-nums; line-height: 1.1; }
.vstat span { color: var(--muted); font-size: 0.85rem; }
.vstat em { color: var(--muted); font-size: 0.72rem; font-style: normal; line-height: 1.3; }
@media (min-width: 700px) { .vstats { grid-template-columns: repeat(3, 1fr); } }

.sess { border-bottom: 1px solid var(--line); padding: 0.55rem 0; }
.sess > summary { cursor: pointer; display: block; }
.sess > summary strong { display: block; font-weight: 600; }
.sess > summary .meta { display: block; font-size: 0.82rem; }
/* My own visits stay in the list when "include me" is on, but dimmed: they are
 * context for the real ones, never the thing being read. */
.sess.is-self { opacity: 0.55; }

/* NOT .timeline. That class is the belief timeline (line ~358) and this block
 * used to be called it too, which silently turned every position on every
 * profile into a two-column grid: the claim squeezed to one word wide beside
 * its quote. A stats-only widget never takes a name the archive already owns. */
.vtl { list-style: none; margin: 0.6rem 0 0.2rem; padding: 0 0 0 0.2rem; }
.vtl li {
  display: grid; grid-template-columns: 3.4rem 1fr; gap: 0.1rem 0.5rem;
  padding: 0.3rem 0; border-top: 1px dotted var(--line); font-size: 0.86rem;
}
.vtl .tl-t { color: var(--muted); font-variant-numeric: tabular-nums; }
.vtl .tl-v { color: var(--muted); }
/* The path and the label wrap instead of scrolling: a query string is often
 * longer than a phone is wide, and it is the half worth reading. */
.vtl .tl-w, .vtl .tl-x { grid-column: 2; overflow-wrap: anywhere; }
.vtl .tl-x { color: var(--muted); font-size: 0.8rem; }
.vtl li.tl-mark .tl-v { color: var(--accent); font-weight: 600; }
