/* Message Hub — an iOS Messages thread at 440pt (iPhone Pro Max), centered on
   wider screens. Thread styles are carried over from the original design file;
   the list and toolbar styles extend the same system.

   Light is the default here and dark comes in under prefers-color-scheme, the
   way Messages follows the system appearance. The toggle in the header can pin
   either one via data-theme on <html>, which outranks the media query. Every
   colour is a token so the two themes stay in step. */

:root{
  /* Light — iOS Messages light appearance */
  --bg:#fff;
  --bubble:#e9e9eb;
  --text:#000;
  --meta:#8e8e93;
  --name:#8e8e93;
  --sep:rgb(0 0 0 / .09);
  --glass:rgb(249 249 249 / .80);
  --glass-edge:rgb(0 0 0 / .10);
  --accent:#007aff;
  --link:#0066cc;
  --quote-edge:rgb(0 0 0 / .20);
  --quote-text:#3a3a3c;
  --field:#e8e8ed;        /* iOS search field */
  --control:rgb(0 0 0 / .05);
  --control-hover:rgb(0 0 0 / .09);
  --hover:rgb(0 0 0 / .045);
  --clear-ring:#a8a8ae;
  --day-off:rgb(0 0 0 / .07);
  --day-on:#3987e5;

  /* iOS Messages renders in SF Pro. On Apple devices -apple-system IS SF Pro,
     so the thread matches the original screenshots exactly. SF Pro cannot be
     web-embedded (Apple's licence forbids it), so elsewhere we fall back to
     Inter, which was drawn as an open stand-in for it. Inter must outrank
     both system-ui AND BlinkMacSystemFont -- current Chrome normalises
     BlinkMacSystemFont to system-ui on every platform, so leaving it ahead
     of Inter hands Windows Segoe UI and Android Roboto. */
  --sf:-apple-system,"SF Pro Text",Inter,BlinkMacSystemFont,system-ui,"Helvetica Neue",Arial,
    "Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",sans-serif;
  --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;

  color-scheme:light dark;
  background:var(--bg);
  box-sizing:border-box;
  --top-inset:env(safe-area-inset-top,0px);
}

/* Dark, two ways: automatically when the system asks for it, and explicitly
   when the toggle pins it. :not([data-theme="light"]) lets an explicit light
   choice win on a dark system. The two blocks must stay identical. */
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    /* Dark — the original design's palette */
    --bg:#000;
    --bubble:#262629;
    --text:#fff;
    --meta:#838389;
    --name:#8c8c92;
    --sep:rgb(255 255 255 / .10);
    --glass:rgb(30 30 32 / .78);
    --glass-edge:rgb(255 255 255 / .13);
    --accent:#0a84ff;
    --link:#54a8ff;
    --quote-edge:rgb(255 255 255 / .22);
    --quote-text:#e6e6ea;
    --field:#1c1c1e;
    --control:rgb(255 255 255 / .055);
    --control-hover:rgb(255 255 255 / .10);
    --hover:rgb(255 255 255 / .05);
    --clear-ring:#636366;
    --day-off:rgb(255 255 255 / .08);
    --day-on:#2a78d6;
  }
}
:root[data-theme="light"]{color-scheme:light}
:root[data-theme="dark"]{
  color-scheme:dark;
  /* Dark — the original design's palette */
  --bg:#000;
  --bubble:#262629;
  --text:#fff;
  --meta:#838389;
  --name:#8c8c92;
  --sep:rgb(255 255 255 / .10);
  --glass:rgb(30 30 32 / .78);
  --glass-edge:rgb(255 255 255 / .13);
  --accent:#0a84ff;
  --link:#54a8ff;
  --quote-edge:rgb(255 255 255 / .22);
  --quote-text:#e6e6ea;
  --field:#1c1c1e;
  --control:rgb(255 255 255 / .055);
  --control-hover:rgb(255 255 255 / .10);
  --hover:rgb(255 255 255 / .05);
  --clear-ring:#636366;
  --day-off:rgb(255 255 255 / .08);
  --day-on:#2a78d6;
}

*,*::before,*::after{box-sizing:inherit}
/* Author display rules below (.tools, .sort-btn, button) outrank the UA
   default for [hidden], so state it explicitly or hiding silently fails. */
[hidden]{display:none!important}

html,body{background:var(--bg);color:var(--text)}
/* The thread was drawn against an iPhone, where the safe-area inset leaves
   room above the bar. A desktop browser reports no inset, which pins the bar
   to the top edge of the window, so give it a floor. The body padding, the
   cover strip and the bar all have to move together. */
body.thread{--top-inset:max(20px,env(safe-area-inset-top,0px))}
body{
  margin:0;font-family:var(--sf);-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%;
  padding-top:var(--top-inset);padding-bottom:env(safe-area-inset-bottom,0px);
  padding-left:max(16px,env(safe-area-inset-left,0px));padding-right:max(16px,env(safe-area-inset-right,0px));
}
body::before{content:"";position:fixed;inset:0 0 auto;height:var(--top-inset);z-index:10;background:var(--bg)}
img{max-width:100%}
button{all:unset;cursor:pointer}
a{color:inherit;text-decoration:none}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:6px}

.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

.screen{max-width:440px;margin:0 auto;position:relative;
  padding-top:68px;padding-bottom:calc(48px + env(safe-area-inset-bottom,0px))}

/* ---------- top bar (thread) ----------
   The gradient is black-on-black: invisible against the background, but it
   dims anything underneath it, so where the first line of content lands
   inside it matters. The bar height and .screen's top padding are a pair --
   at 76px and 68px the date stamp sits at 89% down the gradient, under about
   0.19 alpha, while the bottom of the back button is still under 0.76. Shrink
   the bar without moving the padding and the stamp slides up into the opaque
   part and washes out. */
.top{position:fixed;left:0;right:0;top:var(--top-inset);z-index:5;height:76px;pointer-events:none;
  background:linear-gradient(to bottom,
    color-mix(in srgb,var(--bg) 92%,transparent) 0,
    color-mix(in srgb,var(--bg) 80%,transparent) 55%,
    transparent 100%)}
.top-in{position:relative;max-width:440px;margin:0 auto;height:100%;
  padding-left:env(safe-area-inset-left,0px);padding-right:env(safe-area-inset-right,0px)}
.back{position:absolute;left:20.5px;top:0;width:43.4px;height:43.4px;border-radius:50%;
  background:var(--glass);border:1px solid var(--glass-edge);display:grid;place-items:center;
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);pointer-events:auto;color:var(--text)}
.pill{position:absolute;left:50%;top:5.9px;transform:translateX(-50%);height:31.7px;padding:0 13px 0 14px;
  border-radius:16px;background:var(--glass);border:1px solid var(--glass-edge);
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  display:flex;align-items:center;gap:5px;font-size:17px;font-weight:600;letter-spacing:-.2px;
  white-space:nowrap;color:var(--text)}
.pill svg{opacity:.45;flex:none}
/* The back button and the appearance toggle sit at the edges of this bar, so
   cap the pill short of them and let a long name truncate, as Messages does
   with a long conversation name. Without this the pill collides with the back
   button around 320px. */
.pill{max-width:calc(100% - 148px)}
.pill-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ---------- thread ---------- */
.stamp{margin:0;text-align:center;font-size:12px;line-height:16px;color:var(--meta);font-variant-numeric:tabular-nums}
.stamp b{font-weight:600}
.stamp .clock{margin-left:.15em}
.from{margin:4px 0 0 58px;font-size:12px;line-height:16px;color:var(--name)}

.msg{position:relative;margin:3px 0 0 43px;width:min(334px,calc(100% - 43px))}
.bubble{position:relative;z-index:1;background:var(--bubble);border-radius:18px;padding:10.3px 14px 10.3px 14.3px;
  font-size:17px;line-height:22px;letter-spacing:-.4px;color:var(--text);overflow-wrap:anywhere}
.bubble > :first-child{margin-top:0}
.bubble > :last-child{margin-bottom:0}
.bubble p{margin:0 0 20px}
.bubble ul,.bubble ol{margin:0 0 20px;padding-left:1.15em}
.bubble li{margin:0 0 4px}
.bubble li::marker{color:var(--meta)}
.bubble strong{font-weight:600}
.bubble em{font-style:italic}
.bubble a{color:var(--link);text-decoration:underline;text-underline-offset:2px}
/* A quotation inside a text. */
.bubble blockquote{margin:0 0 20px;padding-left:11px;border-left:2px solid var(--quote-edge);color:var(--quote-text)}
.bubble h2,.bubble h3{margin:0 0 6px;font-size:17px;line-height:22px;font-weight:600;letter-spacing:-.4px}
/* Code. A fenced block needs `overflow-x`, or one long line pushes the whole
   document wider than the viewport -- a 334px bubble leaves 282px inside the
   block, which is about thirty-six monospace characters -- and the reader
   gets a horizontal scrollbar on the page rather than on the block. Inline
   code keeps the bubble's `overflow-wrap`, so a long attribute breaks
   instead of doing the same thing. */
.bubble code{font-family:var(--mono);font-size:13.5px}
.bubble pre{margin:0 0 20px;padding:10px 12px;border-radius:12px;
  background:var(--control);overflow-x:auto}
.bubble pre code{display:block;white-space:pre;font-size:13px;line-height:18px}
/* A picture or a clip in a message. It fills the bubble and keeps its own
   proportions; give the tag a width and height in the post so the thread
   does not reflow as the file loads. The flat fill behind it covers the
   gap before a poster frame or an image has arrived. */
.bubble img,.bubble video{display:block;width:100%;height:auto;border-radius:13px;background:var(--quote-edge)}
/* Sent on its own, the picture or clip *is* the bubble, the way Messages
   draws one: the padding goes so it reaches the edges, and the tail keeps
   its shape behind it. A browser with no :has() keeps the ordinary padded
   bubble, which is only a little different -- nothing breaks.

   Two shapes to match: markdown-it wraps `![alt](src)` in a paragraph, but
   leaves a hand-written <img> tag as a bare child of the bubble. */
.bubble:has(> :is(img,video,.attach):only-child),
.bubble:has(> p:only-child > :is(img,video,.attach):only-child){padding:0}
.bubble:has(> :is(img,video,.attach):only-child) :is(img,video,.attach),
.bubble:has(> p:only-child > :is(img,video,.attach):only-child) :is(img,video,.attach){border-radius:18px}
/* Audio is the exception: it keeps the bubble's padding. A picture has
   proportions of its own and can fill the bubble; a player is a fixed bar
   with its own rounded fill, and flush to the bubble's edge it reads as a
   mistake. The control draws itself in the light or dark style the page's
   `color-scheme` asks for, so it follows the appearance toggle. */
.bubble audio{display:block;width:100%}
/* An attachment: a file with no sensible inline rendering at a bubble's
   width, so it arrives as a row -- icon, name, size -- the way Messages
   shows a document. Alone in a bubble it fills it, like a picture does.
   `a.attach` rather than `.attach`, to outrank `.bubble a` above, which
   would otherwise make it blue and underlined. */
.bubble a.attach{display:flex;align-items:center;gap:10px;padding:11px 12px;
  color:var(--text);text-decoration:none}
.attach-icon{flex:none;color:var(--meta)}
.attach-body{min-width:0;display:flex;flex-direction:column}
.attach-name{font-size:15px;line-height:20px;letter-spacing:-.2px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.attach-meta{font-size:13px;line-height:17px;color:var(--meta);
  font-variant-numeric:tabular-nums}
.attach-go{flex:none;margin-left:auto;color:var(--meta)}
.bubble a.attach:hover{background:var(--control-hover)}
@media (prefers-reduced-motion:no-preference){
  .bubble a.attach{transition:background-color .12s ease}
}

.tail{position:absolute;z-index:0;left:0;bottom:-6.4px;width:22px;height:25px;display:block}
.avatar{position:absolute;left:-39px;bottom:-6.3px;width:32px;height:32px;display:block;border-radius:50%}
/* Stand-in for a sender we have no photo of. */
.avatar-other{color:var(--meta);background:var(--bubble);overflow:hidden}

/* ---------- tags ---------- */
/* Lined up with the bubbles, not the page, so they read as belonging to the
   message rather than to the layout. */
.tags{list-style:none;display:flex;flex-wrap:wrap;gap:6px;
  margin:18px 0 0 43px;padding:0;width:min(334px,calc(100% - 43px))}
.tag{display:inline-block;font-size:13px;line-height:18px;letter-spacing:-.1px;
  color:var(--meta);padding:5px 11px;border-radius:13px;
  border:1px solid var(--glass-edge);background:var(--control)}
.tag:hover{color:var(--text);background:var(--control-hover)}
.tag-count{margin-left:7px;color:var(--meta);font-variant-numeric:tabular-nums}
.tag:hover .tag-count{color:var(--meta)}
/* The index is a page of its own, so it is not tied to the bubble column. */
.tags-index{margin:6px 4px 0;width:auto}
.tags-index .tag{font-size:15px;line-height:20px;padding:7px 13px;border-radius:15px}
.tags-all{margin:26px 4px 0;font-size:13px;line-height:18px;color:var(--accent)}
.tags-all a:hover{text-decoration:underline}

/* ---------- prev / next ---------- */
.pager{display:flex;flex-direction:column;gap:8px;margin:34px 0 0 43px;width:min(334px,calc(100% - 43px))}
.pager-link{font-size:13px;line-height:18px;color:var(--meta);padding:9px 13px;border-radius:14px;
  border:1px solid var(--glass-edge);background:var(--control)}
.pager-link:hover{color:var(--text);background:var(--control-hover)}
/* Purely positional: the bottom row, whichever post is in it. */
.pager-right{text-align:right}

/* ---------- conversation list (home) ---------- */
.screen-list{padding-top:18px;padding-bottom:calc(40px + env(safe-area-inset-bottom,0px))}
.list-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:22px 4px 14px}
.list-head-text{min-width:0}
.list-head h1{margin:0;font-size:34px;line-height:41px;font-weight:700;letter-spacing:-.9px}
.list-sub{margin:3px 0 0;font-size:15px;line-height:20px;color:var(--meta)}

/* ---------- page navigation (list) ---------- */
/* Horizontal, so there is no up/down ambiguity: newer on the left, older on
   the right, matching the direction the list already reads. The empty end
   keeps the label centred when only one of the two links renders.

   The bottom margin is not decoration: .to-top is fixed to the bottom-right
   corner, which is where "Older" lands, and at phone widths they overlap at
   the end of the page without it. */
.pagenav{display:flex;align-items:center;gap:10px;margin:26px 4px 46px}
.pagenav-link{flex:none;font-size:13px;line-height:18px;color:var(--meta);
  padding:9px 13px;border-radius:14px;border:1px solid var(--glass-edge);
  background:var(--control)}
.pagenav-link:hover{color:var(--text);background:var(--control-hover)}
.pagenav-end{flex:none;width:84px}
.pagenav-at{flex:1;text-align:center;font-size:13px;line-height:18px;
  color:var(--meta);font-variant-numeric:tabular-nums}

/* ---------- back to top ---------- */
.to-top{position:fixed;z-index:6;right:max(16px,env(safe-area-inset-right,0px));
  bottom:max(16px,calc(env(safe-area-inset-bottom,0px) + 8px));
  width:44px;height:44px;border-radius:50%;display:grid;place-items:center;
  color:var(--text);background:var(--glass);border:1px solid var(--glass-edge);
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  box-shadow:0 2px 10px rgb(0 0 0 / .18);
  opacity:0;transform:translateY(6px);pointer-events:none}
.to-top.is-shown{opacity:1;transform:none;pointer-events:auto}
.to-top:hover{background:var(--control-hover)}
@media (prefers-reduced-motion:no-preference){
  .to-top{transition:opacity .16s ease,transform .16s ease}
}

/* ---------- activity graph ----------
   A row per month, a column per day of the month. 31 columns of 1fr, so the
   dots size themselves to the container and the graph fits the list column
   at any width -- no horizontal scrolling.

   One fill for every day that has a post, whatever the count. The blue is
   checked against both surfaces with the data-viz validator; what matters
   with a single fill is contrast against the background, and both steps
   clear it. */
.activity{margin:0 0 18px;padding:0 4px}
.activity-months{display:flex;flex-direction:column;gap:3px}
.amonth{display:flex;align-items:center;gap:7px}
.amonth-name{flex:0 0 24px;font-size:10px;line-height:12px;color:var(--meta);
  text-align:right;font-variant-numeric:tabular-nums}
.amonth-days{flex:1;min-width:0;display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:2px}
/* aspect-ratio keeps the cell square whatever width the column is, so the
   circle stays a circle rather than going oval. The hover and focus rings
   are `outline`, which follows border-radius in every browser this already
   needs for `:has()`, so they come out round too. */
.day{aspect-ratio:1;min-width:0;border:0;border-radius:50%;padding:0;
  background-color:var(--day-off)}
.day-on{background-color:var(--day-on)}
button.day{cursor:pointer}
button.day:hover{outline:1.5px solid var(--text);outline-offset:1px}
button.day:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* The row the reader jumped to, so the eye lands in the right place. */
.row-jumped{animation:rowFlash 1.4s ease-out}
@keyframes rowFlash{from{background:var(--control-hover)}to{background:transparent}}
@media (prefers-reduced-motion:reduce){.row-jumped{animation:none;background:var(--control-hover)}}

/* ---------- appearance + palette controls ---------- */
/* The two buttons stack, palette under appearance. No ring and no fill on
   either: the glyph alone, sized to what the old circle was. */
.controls{flex:none;position:relative;display:flex;flex-direction:column;
  align-items:center;gap:1px;margin-top:4px}
.icon-btn{display:grid;place-items:center;width:34px;height:34px;padding:0;
  border:0;background:none;color:var(--meta)}
.icon-btn:hover{color:var(--text)}
.icon-btn svg{display:block;width:100%;height:100%}
/* On a thread the pair sits in the fixed bar, opposite the back button, at the
   same 34px as the list header. That button is 43.4px, so nudge down by half
   the difference to keep the first glyph optically aligned with it. */
.top .controls{position:absolute;right:20.5px;top:4.7px;margin-top:0;
  pointer-events:auto;z-index:7}
.top .icon-btn{color:var(--text)}

/* The picker. Glass like the other floating chrome, and anchored to the
   controls rather than the viewport so it lands under the palette button on
   both the list and a thread. No outline -- the blur and the drop shadow
   separate it from the page on their own. */
.palette-panel{position:absolute;top:calc(100% + 7px);right:0;z-index:8;
  width:min(238px,calc(100vw - 34px));padding:9px;border-radius:15px;
  background:var(--glass);
  -webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);
  box-shadow:0 8px 26px rgb(0 0 0 / .30)}
.palette-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:6px}
/* Split on the diagonal, because a palette carries a light and a dark side
   and showing only one of them would misrepresent half of them. */
.swatch{width:100%;aspect-ratio:1;min-width:0;padding:0;position:relative;
  border-radius:50%;border:1px solid var(--glass-edge);cursor:pointer;
  background:linear-gradient(135deg,var(--sw-l) 0 50%,var(--sw-d) 50% 100%)}
.swatch::after{content:"";position:absolute;left:50%;top:50%;
  width:38%;height:38%;margin:-19% 0 0 -19%;border-radius:50%;background:var(--sw-p)}
.swatch.is-default{--sw-l:#fff;--sw-d:#000;--sw-p:#0a84ff}
.swatch:hover{outline:1.5px solid var(--text);outline-offset:1px}
.swatch:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.swatch[aria-selected="true"]{outline:2px solid var(--accent);outline-offset:2px}

/* ---------- activity: pulse ----------
   A row per month, newest first, bar length by post count. Twelve rows are
   shown and the rest scroll, so the component keeps one height however long
   the archive runs -- the calendar grows a row per month, this one does not.
   Same tokens as the calendar, so palettes and dark themes carry over. */
.pulse{--prow:13px;margin:0 0 18px;padding:0 4px}
/* On macOS the scrollbar is an overlay that only appears while scrolling, and
   row twelve sits flush with the edge, so without this nothing says there is
   more below. Painted over the scroll area rather than masked onto it, which
   would fade the scrollbar too. */
.pulse-window{position:relative}
.pulse-window.has-more::after{content:"";position:absolute;left:0;right:0;bottom:0;
  height:17px;pointer-events:none;
  background:linear-gradient(to bottom,transparent,var(--bg))}
.pulse-scroll{max-height:calc(12 * var(--prow));overflow-y:auto;overscroll-behavior:contain}
.pulse-scroll:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:4px}
/* A thin scrollbar is the only thing saying there is more below, so it is
   left visible rather than hidden for tidiness. */
.pulse-scroll{scrollbar-width:thin;scrollbar-color:var(--day-off) transparent}
.pulse-scroll::-webkit-scrollbar{width:4px}
.pulse-scroll::-webkit-scrollbar-thumb{background:var(--day-off);border-radius:2px}

.pmonth{display:flex;align-items:center;gap:7px;width:100%;height:var(--prow);
  padding:0;border:0;background:none;text-align:left}
button.pmonth{cursor:pointer}
.pmonth-when{flex:0 0 44px;font-size:9.5px;line-height:var(--prow);color:var(--meta);
  text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
.pmonth-when b{font-weight:600;color:var(--text);margin-right:4px}
.pmonth-track{flex:1;min-width:0;height:7px;border-radius:4px;background:var(--day-off)}
.pmonth-fill{display:block;height:100%;border-radius:4px;background:var(--day-on);
  width:max(4px, calc(var(--v) * 100%))}
.pmonth-count{flex:0 0 16px;font-size:9.5px;line-height:var(--prow);color:var(--meta);
  text-align:right;font-variant-numeric:tabular-nums}
/* A month with nothing in it has no fill element at all, so the minimum width
   above cannot draw a bar for it. */
.pmonth.is-empty .pmonth-when{opacity:.5}
.pmonth:hover .pmonth-fill{filter:brightness(1.15)}
.pmonth:hover .pmonth-when{color:var(--text)}
.pmonth:focus-visible{outline:2px solid var(--accent);outline-offset:1px;border-radius:4px}

.pulse-legend{display:flex;gap:14px;margin:10px 0 0;font-size:11px;line-height:14px;
  color:var(--meta);font-variant-numeric:tabular-nums}
@media (prefers-reduced-motion:no-preference){
  .pmonth-fill{transition:filter .12s ease}
}

/* ---------- index link ----------
   A single row between the activity graph and the toolbar: icon, label, and
   a count pushed to the right. */
.index-link{display:flex;align-items:center;gap:8px;margin:0 4px 6px;padding:11px 12px;
  border-radius:12px;border:1px solid var(--glass-edge);background:var(--control);
  font-size:15px;line-height:20px;letter-spacing:-.2px}
.index-link:hover{background:var(--control-hover)}
.index-link svg{flex:none;color:var(--meta)}
.index-link .index-link-sub{margin-left:auto;color:var(--meta);font-size:13px}

/* ---------- search / sort toolbar ---------- */
.tools{display:flex;flex-direction:column;gap:7px;padding:2px 4px 12px}
.tools-row{display:flex;align-items:center;gap:8px}

.search{position:relative;display:flex;align-items:center;flex:1;min-width:0}
.search-icon{position:absolute;left:11px;color:var(--meta);pointer-events:none}
.search input{width:100%;height:37px;border-radius:10px;border:1px solid transparent;
  background:var(--field);color:var(--text);font:inherit;font-size:17px;letter-spacing:-.4px;
  padding:0 10px 0 33px;-webkit-appearance:none;appearance:none}
.search input::placeholder{color:var(--meta)}
.search input:focus{border-color:var(--glass-edge);outline:none}
.search input:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
/* SF Symbols xmark.circle.fill — WebKit's own glyph is invisible on dark. */
.search input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none;
  width:17px;height:17px;cursor:pointer;background:no-repeat center/contain;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='8' fill='%23a8a8ae'/%3E%3Cpath d='M5.4 5.4l5.2 5.2M10.6 5.4l-5.2 5.2' stroke='%23fff' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E")}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .search input::-webkit-search-cancel-button{
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='8' fill='%23636366'/%3E%3Cpath d='M5.4 5.4l5.2 5.2M10.6 5.4l-5.2 5.2' stroke='%231c1c1e' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E")}
}
:root[data-theme="dark"] .search input::-webkit-search-cancel-button{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='8' fill='%23636366'/%3E%3Cpath d='M5.4 5.4l5.2 5.2M10.6 5.4l-5.2 5.2' stroke='%231c1c1e' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E")}

.sort-btn{flex:none;display:inline-flex;align-items:center;gap:5px;height:37px;padding:0 11px;
  border-radius:10px;border:1px solid var(--glass-edge);background:var(--control);
  color:var(--text);font-size:14px;line-height:1;letter-spacing:-.2px;white-space:nowrap}
.sort-btn:hover{background:var(--control-hover)}
.sort-btn svg{color:var(--meta);flex:none}

.result-count{margin:0;min-height:18px;font-size:13px;line-height:18px;color:var(--meta)}
.no-match{margin:26px 0;text-align:center;font-size:15px;color:var(--meta)}

.rows{list-style:none;margin:0;padding:0}
.row + .row{border-top:1px solid var(--sep)}
.row.row-first{border-top:none}
.row-link{display:flex;gap:12px;align-items:flex-start;padding:11px 4px 12px;border-radius:12px}
.row-link:hover{background:var(--hover)}
/* Arrow-key navigation lands focus here, so make the current match obvious. */
.row-link:focus-visible{background:var(--hover);outline:2px solid var(--accent);outline-offset:-2px}
.row-avatar{flex:0 0 52px;width:52px;height:52px;border-radius:50%;display:block}
.row-body{flex:1;min-width:0}
.row-top{display:flex;align-items:baseline;gap:8px}
.row-title{flex:1;min-width:0;font-size:17px;line-height:22px;font-weight:600;letter-spacing:-.4px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.row-date{flex:none;display:flex;align-items:center;gap:4px;font-size:15px;line-height:22px;
  color:var(--meta);font-variant-numeric:tabular-nums}
.row-date svg{opacity:.55}
.row-preview{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;margin-top:1px;font-size:15px;line-height:20px;letter-spacing:-.2px;color:var(--meta)}

@media (prefers-reduced-motion:no-preference){
  .row-link,.pager-link,.pagenav-link,.sort-btn,.icon-btn,.swatch,.tag,.index-link{transition:background-color .12s ease,color .12s ease}
}
/* Set while the appearance switches, so the whole page changes at once. */
:root.theme-switching *{transition:none!important}
