: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); }

/* ---------------------------------------------------------------- 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); }

/* ---------------------------------------------------------- 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; }

/* --------------------------------------------------- person page (/people/:handle)
   Phone first, and built for the stranger who arrived from a search result:
   compact header, one visible line of disclaimer, then the channels -- all
   above the fold at 390px. Every channel is a collapsed full-width card whose
   whole face is the tap target; tapping expands it in place (native
   <details>) rather than leaving the site. */
.pcompact h1 { margin: 0.35rem 0 0; }
.pcompact .bio { margin: 0.3rem 0 0; }
/* Picture beside the name, not above it: at 390px a stacked avatar costs a
   whole row of the first screen, which is the one thing this header must not
   spend. The image is our own /avatar proxy, so it always renders something. */
.pwho { display: flex; align-items: center; gap: 0.8rem; margin-top: 0.35rem; }
.pwho .pavatar { flex: none; width: 56px; height: 56px; border-radius: 50%;
                 border: 1px solid var(--line); background: var(--card); }
.pwho .pwho-text { min-width: 0; }
.pwho h1 { margin: 0; }
.pwho .bio { margin: 0.15rem 0 0; }

/* The disclaimer: its load-bearing sentence always visible, the rest one tap
   away. Small and quiet on purpose -- it must be readable, not dominant. */
.pagenote { margin-top: 0.75rem; font-size: 0.82rem; line-height: 1.45; color: var(--muted); }
.pagenote summary {
  list-style: none; cursor: pointer; display: flex; align-items: center;
  gap: 0.35rem; flex-wrap: wrap; min-height: 34px; padding: 0.2rem 0;
}
.pagenote summary::-webkit-details-marker { display: none; }
.pagenote strong { color: var(--muted); font-weight: 600; }
.pagenote .pagenote-more { color: var(--accent); }
.pagenote .chev { flex: none; color: var(--muted); transition: transform 0.15s; }
.pagenote[open] .chev { transform: rotate(180deg); }
.pagenote p { margin: 0.2rem 0 0; max-width: 36rem; }

.pblock { margin-bottom: 2rem; }
main .pblock h2.sec { margin-top: 1.4rem; }
/* Heading row: section title left, the expand/collapse-all toggle right,
   sharing the baseline so the control reads as part of the heading. */
.sechead { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.expand-all {
  border: 0; background: none; padding: 0.25rem 0; cursor: pointer;
  font: inherit; font-size: 0.85rem; font-weight: 550; color: var(--accent);
  min-height: 44px; white-space: nowrap;
}
.expand-all:hover { text-decoration: underline; }
.chan-group {
  font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); font-weight: 600; margin: 1.25rem 0 0.5rem;
}
.channels { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.5rem; }
/* Card chrome on the DIRECT child details only: the Recent list nests a
   second details ("Show N more") inside the card, and unscoped `.chan details`
   dressed it as a second card -- and rotated its chevron whenever the OUTER
   card was open, so "Show more" pointed up while still closed. */
.chan > details {
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
}
.chan > details[open] { border-color: var(--accent); }
.chan > details > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center;
  gap: 0.6rem; padding: 0.6rem 0.8rem; min-height: 44px; border-radius: 10px;
}
.chan summary::-webkit-details-marker { display: none; }
.chan summary:hover, .chan summary:focus-visible { outline: none; }
.chan > details:hover, .chan > details:focus-within { border-color: var(--accent); }
.chan-body { flex: 1; min-width: 0; }
.chan .chev { flex: none; color: var(--muted); transition: transform 0.15s; }
.chan > details[open] > summary .chev { transform: rotate(180deg); }
/* The visit-now ↗ on the card head. The summary reserves a slot in its flex
   row (so text never runs under it) and the real anchor -- which must not
   live inside the summary, or tapping it would also toggle the card -- is
   overlaid on that slot, aligned to the head row whether open or closed. */
/* The visit-now ↗, inline right after the title so link and label read as
   one thing. The negative margins buy a ~40px tap target without adding a
   pixel to the row height; person.js keeps its taps from toggling the card. */
.chan-open {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; margin: -12px -8px; align-self: center;
  color: var(--accent);
}
.chan-open .ext { width: 16px; height: 16px; }
/* The app-list head: icon column left (grid, spanning head and note rows so
   the note aligns under the NAME, not under the icon), name leading, the kind
   word breathing on the right edge. Labelled pills were tried twice --
   ragged, then equal-width -- and both made the head heavier than its
   content; a bare icon plus a quiet word carries the same information. */
.chan-body { display: grid; grid-template-columns: 24px 1fr; column-gap: 0.55rem; }
.chan-ico {
  grid-row: 1 / span 2; display: flex; justify-content: center;
  color: var(--muted); margin-top: 0.2rem;
}
.chan-ico svg { width: 18px; height: 18px; }
.chan-head { grid-column: 2; display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
/* No text-transform: the labels carry their own casing ("GitHub", "x.com"),
   and X.COM in forced caps read as shouting a domain. margin-left: auto keeps
   the kind right-aligned even when a long name wraps it to its own line. */
.chan-kind {
  /* margin-right buys the chevron daylight: flush against it, a thumb aiming
     to expand landed on the kind word and read it as a separate control. */
  margin-left: auto; margin-right: 1rem; font-size: 0.72rem; letter-spacing: 0.02em;
  color: var(--muted); font-weight: 600; white-space: nowrap;
}
.chan-note { grid-column: 2; }
.chan-label { font-weight: 600; color: var(--fg); }
/* One line while collapsed, whatever the note's length, so every card in the
   stack stands the same height; the ellipsis is honest about the cut and the
   full note flows back the moment the card opens. min-width: 0 lets the grid
   item actually shrink instead of forcing the column wide. */
.chan-note {
  display: block; color: var(--muted); font-size: 0.88rem; margin-top: 0.1rem;
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chan > details[open] .chan-note { white-space: normal; overflow: visible; }
/* The expansion: what you'll find there, then the way out. */
.chan-more { padding: 0 0.8rem 0.7rem; }
.chan-more p { margin: 0.2rem 0 0; font-size: 0.92rem; }
.chan-visit a {
  display: inline-flex; align-items: center; gap: 0.35rem;
  min-height: 44px; font-weight: 550; text-decoration: none;
}
.chan-visit a:hover { text-decoration: underline; }
.chan-visit .ext { flex: none; }
.chan-more p.chan-checked { color: var(--muted); font-size: 0.78rem; margin-top: 0; }

/* The korents card closing the page. The negative margin cancels the section
   break so the card sits one list-gap (0.5rem) under the channels, reading as
   part of the same stack rather than a stray. */
.kblock { margin-top: calc(0.5rem - 2rem); }
.kall { margin: 0.6rem 0 0; }

/* A belief row that expands in place: summary = the claim sentence, the
   expansion = the quote, its source (icon for the medium), and the record
   link. The li drops its flex so the nested details owns the row. */
.recent-list li.krow-li { display: block; padding: 0; }
.khint { margin: 0.15rem 0 0; font-size: 0.8rem; color: var(--muted); }
/* Feed rows: the medium's icon leads each row; the list starts right under
   the summary, so the card's divider line is not doubled. */
.ficon { flex: none; color: var(--muted); align-self: flex-start; margin-top: 3px; }
.feedcard .recent { border-top: 0; margin-top: 0.2rem; padding-top: 0; }
/* Filter & sort: a quiet collapsed line whose right side always states the
   current view; open, two chip rows -- sources (toggle each) and order. */
.ffilter { font-size: 0.85rem; border-bottom: 1px solid var(--line); margin-bottom: 0.2rem; }
.ffilter > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center;
  gap: 0.5rem; min-height: 40px; color: var(--muted); font-weight: 550;
}
.ffilter > summary::-webkit-details-marker { display: none; }
.ffilter .fstate { margin-left: auto; font-weight: 400; font-size: 0.78rem; }
.ffilter .chev { flex: none; width: 16px; height: 16px; color: var(--muted); transition: transform 0.15s; }
.ffilter[open] > summary .chev { transform: rotate(180deg); }
.fbody { display: flex; flex-direction: column; gap: 0.45rem; padding: 0 0 0.6rem; }
.fchips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.fchip {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.25rem 0.7rem; min-height: 32px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--bg); color: var(--muted);
  font: inherit; font-size: 0.8rem; font-weight: 550; cursor: pointer;
}
.fchip.on { border-color: var(--accent); color: var(--accent); }
.fchip svg { width: 12px; height: 12px; }
/* The by-source view: group headers and per-group show-more rows live inside
   the same list. [hidden] must beat the li's flex display. */
.recent-list li[hidden] { display: none; }
.recent-list li.fgroup {
  border-top: 0; padding: 0.55rem 0 0.1rem; gap: 0.4rem; align-items: center;
  color: var(--muted); font-size: 0.72rem; font-weight: 600; letter-spacing: 0.02em;
}
.recent-list li.fgroup svg { width: 12px; height: 12px; flex: none; }
.recent-list li.fgroup-more { border-top: 0; padding: 0; }
.fgroup-more button {
  border: 0; background: none; padding: 0.2rem 0; min-height: 36px; cursor: pointer;
  font: inherit; font-size: 0.82rem; font-weight: 550; color: var(--accent);
}
.fgroup-more button:hover { text-decoration: underline; }
.krow > summary {
  list-style: none; cursor: pointer; display: flex; align-items: baseline;
  gap: 0.6rem; padding: 0.32rem 0; min-height: 40px;
}
.krow > summary::-webkit-details-marker { display: none; }
/* Regular weight: at 500 a run of three claim sentences read as a wall of
   headlines. The sentences are content, not titles. */
.krow-t { flex: 1; min-width: 0; font-size: 0.9rem; line-height: 1.45; font-weight: 400; }
.krow .chev { flex: none; align-self: center; width: 16px; height: 16px;
              color: var(--muted); transition: transform 0.15s; }
.krow[open] > summary .chev { transform: rotate(180deg); }
.krow-more { padding: 0 0 0.6rem; }
/* The quote scrolls inside itself past ~9 lines, so one long monologue never
   buries the rows after it. */
.kq { max-height: 10rem; overflow-y: auto; overscroll-behavior: contain; font-size: 0.92rem; }
.ksrc { margin: 0.35rem 0 0; font-size: 0.85rem; display: flex; align-items: center;
        gap: 0.6rem; flex-wrap: wrap; }
.ksrc a { display: inline-flex; align-items: center; gap: 0.4rem; min-width: 0;
          text-decoration: none; font-weight: 550; }
.ksrc a:hover { text-decoration: underline; }
.ksrc a svg { flex: none; color: var(--muted); }
.ksrc a .ext { color: currentColor; }
.ksrc-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
          max-width: 15rem; }
.ksrc-when { color: var(--muted); font-size: 0.78rem; }
.ksrc-all { margin: 0.3rem 0 0; font-size: 0.85rem; }
.ksrc-all a { text-decoration: none; font-weight: 550; }
.ksrc-all a:hover { text-decoration: underline; }
.kall a { font-weight: 550; text-decoration: none; }
.kall a:hover { text-decoration: underline; }

/* The Recent list: the channel's latest items, three visible, the rest behind
   a nested "Show N more" whose list scrolls INSIDE itself -- however long the
   backlog, the next channel card stays one thumb-length away. */
.recent { margin-top: 0.6rem; border-top: 1px solid var(--line); padding-top: 0.5rem; }
.recent-h {
  margin: 0 0 0.15rem; font-size: 0.7rem; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--muted); font-weight: 600;
}
.recent-list { list-style: none; padding: 0; margin: 0; }
.recent-list li {
  display: flex; align-items: baseline; gap: 0.6rem;
  padding: 0.32rem 0; border-top: 1px solid var(--line);
}
.recent-list li:first-child { border-top: 0; }
.recent-list a {
  flex: 1; min-width: 0; text-decoration: none; font-size: 0.9rem; line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.recent-list a:hover { text-decoration: underline; }
.recent-list .when { flex: none; color: var(--muted); font-size: 0.78rem; white-space: nowrap;
                     text-align: right; line-height: 1.45; }
/* Video rows: a small proxied thumbnail that is also the play-here button,
   and a full-width 16:9 player that person.js drops in beneath the row. */
.recent-list li.vid { align-items: center; flex-wrap: wrap; }
.yt-thumb {
  position: relative; flex: none; width: 76px; height: 43px; padding: 0;
  border: 1px solid var(--line); border-radius: 6px; overflow: hidden;
  background: #000; cursor: pointer;
}
.yt-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.yt-badge {
  position: absolute; inset: 0; display: grid; place-items: center;
  color: #fff; background: rgba(0, 0, 0, 0.25);
}
.yt-thumb:hover .yt-badge, .yt-thumb:focus-visible .yt-badge { background: rgba(0, 0, 0, 0.45); }
.yt-player { flex-basis: 100%; aspect-ratio: 16 / 9; margin: 0.4rem 0 0.2rem; }
.yt-player iframe { width: 100%; height: 100%; border: 0; border-radius: 8px; }
/* "Show N more" is a nested details whose strip scrolls inside itself. The
   one-list variant (everything in a bordered scroll box, toggle underneath)
   was tried and reverted 2026-09-01: opening moved the toggle away from the
   finger, where this shape expands BELOW the tap point so the control holds
   its position for both open and close. */
.recent-more { margin-top: 0.2rem; }
.recent-more[open] > summary { margin-bottom: 0.15rem; }
/* display: flex (not inline-flex) so the summary spans the card and the WHOLE
   row is the tap target -- a thumb aims at the rectangle, not at the words. */
.recent-more > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center;
  gap: 0.25rem; min-height: 44px; color: var(--accent); font-size: 0.88rem; font-weight: 550;
}
.recent-more > summary::-webkit-details-marker { display: none; }
.recent-more .chev { color: var(--muted); transition: transform 0.15s; }
.recent-more[open] > summary .chev { transform: rotate(180deg); }
/* The scroll strip. max-height rather than a count so it holds at any font
   size; overscroll-behavior keeps a flicked list from dragging the page.
   Three quiet signals that this box scrolls, because nothing about a cut-off
   list says so on a phone: a hairline rounded boundary marking where the
   scrollable region begins and ends, a soft shadow smudge at whichever edge
   still hides content (the classic background-attachment: local trick -- the
   cover gradients scroll with the list and blot the smudge out at the ends),
   and a thin always-styled scrollbar instead of the vanishing overlay one. */
.recent-list.scrolly {
  max-height: 15rem; overflow-y: auto; overscroll-behavior: contain;
  border: 1px solid var(--line); border-radius: 8px; padding: 0 0.6rem;
  background:
    linear-gradient(var(--bg) 30%, transparent),
    linear-gradient(transparent, var(--bg) 70%) 0 100%,
    radial-gradient(farthest-side at 50% 0, var(--scrollhint), transparent),
    radial-gradient(farthest-side at 50% 100%, var(--scrollhint), transparent) 0 100%;
  background-color: var(--bg);
  background-repeat: no-repeat;
  background-size: 100% 26px, 100% 26px, 100% 12px, 100% 12px;
  background-attachment: local, local, scroll, scroll;
  scrollbar-width: thin;
  scrollbar-color: var(--muted) transparent;
}

/* Open and close animate rather than snap (Chrome 131+ and equivalents;
   elsewhere these rules are ignored and the toggle stays instant). A details
   toggle used to change the card's height in a single frame, which is the
   other half of why the page felt like it teleported: even with the document
   height stable, a one-frame reveal reads as a jump. Animated over 220ms, the
   reveal is legible and scroll anchoring tracks it frame by frame. */
@supports (interpolate-size: allow-keywords) {
  :root { interpolate-size: allow-keywords; }
}
@media (prefers-reduced-motion: no-preference) {
  .chan details::details-content,
  .pagenote::details-content {
    block-size: 0; overflow-y: clip;
    transition: block-size 0.22s ease, content-visibility 0.22s allow-discrete;
  }
  .chan details[open]::details-content,
  .pagenote[open]::details-content { block-size: auto; }
}

/* ------------------------------------------------- people directory (/people)
   A card per person: real avatar, name, what they have on record, the media
   they publish on. Search and filters are client-side over these same cards
   (static/people.js), so the page is complete before any script runs. */
.psearch { margin: 1.1rem 0 0.5rem; }
.psearch input {
  width: 100%; padding: 0.6rem 0.8rem; font: inherit;
  background: var(--card); color: var(--fg);
  border: 1px solid var(--line); border-radius: 999px;
}
.psearch input:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
.pfilter { border-bottom: 1px solid var(--line); margin-bottom: 0.4rem; }
.pfilter .frow-l {
  margin: 0.35rem 0 0.1rem; font-size: 0.72rem; letter-spacing: 0.02em;
  color: var(--muted); font-weight: 600;
}
.fchip-n { color: var(--muted); font-weight: 400; font-size: 0.72rem; }
.fchip.on .fchip-n { color: inherit; }
.pcount { color: var(--muted); font-size: 0.82rem; margin: 0.5rem 0 0; }
.linkish {
  border: 0; background: none; padding: 0; font: inherit; color: var(--accent);
  font-weight: 550; cursor: pointer;
}
.linkish:hover { text-decoration: underline; }

.people.pdir { display: grid; gap: 0.5rem; margin-top: 0.9rem; }
.pdir .pcard {
  display: flex; align-items: flex-start; gap: 0.7rem;
  padding: 0.7rem 0.8rem; border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); border-top: 1px solid var(--line);
}
.pdir .pcard[hidden] { display: none; }
.pdir .pc-av { flex: none; display: block; }
.pdir .pc-av img {
  width: 44px; height: 44px; border-radius: 50%;
  border: 1px solid var(--line); background: var(--bg); display: block;
}
.pdir .pc-body { flex: 1; min-width: 0; }
.pdir .pname { display: block; font-weight: 600; text-decoration: none; color: var(--fg); }
.pdir .pname:hover { text-decoration: underline; }
.pdir .meta { display: block; margin-top: 0.05rem; }
.pdir .hook { margin: 0.2rem 0 0; font-size: 0.88rem; color: var(--muted); }
/* The media they publish on, as quiet words rather than another row of pills:
   on a card this size a second set of chips competes with the name. */
.pc-kinds { display: block; margin-top: 0.3rem; font-size: 0.72rem; color: var(--muted); }
.pc-kind:not(:last-child)::after { content: ' · '; }
.pdir .pc-act { flex: none; }
