/* blog.css — the Reading Room + 广场 (the Square).
   Loaded only on blog pages (base.html head_extra). All classes are prefixed
   `rr-` and the whole thing hangs off body.blog-page, so nothing here can
   collide with the 14k-line app stylesheet. Colours/typography come from the
   Atelier tokens in style.css (--primary, --bg, --card, --text, --muted,
   --border, --surface-2, --shadow, --font-*); only the section accent hues are
   defined here, and they sit on coloured tiles so they read in both themes. */

body.blog-page {
  --s-news: #d9552f;  --s-global: #4f8fb0; --s-uni: #7b6bb0; --s-tech: #2f9e8f;
  --s-biz: #c78a2c;   --s-culture: #e0844a; --s-faith: #6a6ab0; --s-read: #5b9e6a;
  /* Level hues are one ramp, not eight unrelated colours like the sections:
     the levels ARE a scale, so the colour carries that — light and green at
     HSK 1, deepening through teal and indigo to near-black at native. A
     reader can find their level by depth before reading a single label. */
  --l-1: #6aa96f;     --l-2: #4f9a86;       --l-3: #3f8ba0;   --l-4: #4a6fa5;
  --l-5: #5a5a9c;     --l-6: #4a3f6b;
  background: var(--bg);
}
/* Blog controls its own full-bleed layout (default .container is 760px). */
body.blog-page .container { max-width: none; margin: 0; padding: 0; }

.rr a { color: inherit; text-decoration: none; }
.rr *, .rr *::before, .rr *::after { box-sizing: border-box; }

/* ── top bar ──────────────────────────────────────────────────────────── */
.rr-bar { position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(12px) saturate(1.2); border-bottom: 1px solid var(--border); }
.rr-bar-in { max-width: 1180px; margin: 0 auto; display: flex; align-items: center;
  gap: 14px; padding: 11px 24px; }
.rr-brand { display: flex; align-items: center; gap: 10px; font-family: var(--font-display);
  font-weight: 600; font-size: 1.05rem; color: var(--text); }
.rr-brand .rr-mark { width: 31px; height: 31px; border-radius: 8px; display: flex;
  align-items: center; justify-content: center; color: #fff; font-family: var(--font-hanzi-serif);
  font-size: 1.05rem; background: linear-gradient(145deg, var(--primary), var(--primary-dark)); }
.rr-tabs { display: flex; gap: 3px; background: var(--surface-2); border-radius: 999px; padding: 3px; }
/* nowrap is load-bearing, not cosmetic: squeezed in a phone-width bar, a
   two-character label like 广场 breaks BETWEEN the characters and the pill
   grows to two lines (Duke, 2026-08-07). CJK has no spaces, so the browser is
   free to break anywhere unless told otherwise. */
.rr-tab { font-size: var(--t-label); font-weight: 600; color: var(--muted);
  padding: 6px 15px; border-radius: 999px; cursor: pointer; white-space: nowrap;
  transition: background .2s ease, color .2s ease; }
.rr-tab:hover { color: var(--text); }
.rr-tab .han { font-family: var(--font-hanzi-serif); }
.rr-tab.on { background: var(--card); color: var(--primary-dark);
  box-shadow: 0 1px 3px rgba(80, 40, 20, .14); }
.rr-tab.on:hover { color: var(--primary-dark); }
.rr-search { flex: 1; max-width: 360px; margin-left: auto; display: flex; align-items: center;
  gap: 9px; background: var(--card); border: 1px solid var(--border); border-radius: 999px;
  padding: 8px 15px; color: var(--muted); }
.rr-search input { flex: 1; border: 0; background: none; font: inherit; font-size: var(--t-body-sm);
  color: var(--text); outline: none; }
.rr-search svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; }
.rr-cta { font-size: var(--t-label); font-weight: 600; color: #fff; background: var(--primary);
  border-radius: 999px; padding: 8px 15px; white-space: nowrap; }
@media (max-width: 720px) { .rr-search { display: none; } }

/* ── masthead band ────────────────────────────────────────────────────── */
.rr-mast { border-bottom: 1px solid var(--border); }
.rr-mast-in { max-width: 1180px; margin: 0 auto; padding: 20px 24px 18px;
  display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
.rr-kick { font-size: var(--t-eyebrow); letter-spacing: .16em; text-transform: uppercase;
  font-weight: 700; color: var(--primary-dark); }
.rr-mast h1 { font-family: var(--font-hanzi-serif); font-weight: 600; font-size: 1.6rem;
  margin: 0; letter-spacing: .02em; color: var(--text); }
.rr-mast .rr-lede { font-family: var(--font-display); color: var(--muted);
  font-size: var(--t-body-sm); margin-left: auto; }
@media (max-width: 640px) { .rr-mast .rr-lede { display: none; } }

/* ── shell: sidebar + main ────────────────────────────────────────────── */
.rr-shell { max-width: 1180px; margin: 0 auto; padding: 26px 24px 100px;
  display: grid; grid-template-columns: 216px 1fr; gap: 36px; }
@media (max-width: 900px) { .rr-shell { grid-template-columns: 1fr; gap: 18px; } }

.rr-side { position: sticky; top: 68px; align-self: start; }
.rr-side .rr-lbl { font-size: var(--t-eyebrow); letter-spacing: .13em; text-transform: uppercase;
  font-weight: 700; color: var(--muted); margin: 0 0 10px 8px; }
.rr-nav { display: flex; flex-direction: column; gap: 1px; margin-bottom: 26px; }
.rr-nav a { display: flex; align-items: center; gap: 11px; padding: 9px 12px; border-radius: 11px;
  font-size: var(--t-body-sm); font-weight: 500; color: var(--muted); cursor: pointer;
  transition: background .14s, color .14s; }
.rr-nav a .han { font-family: var(--font-hanzi-serif); font-weight: 600; }
.rr-nav a .en { color: var(--muted); font-size: var(--t-caption); margin-left: auto; }
.rr-nav a:hover { background: var(--surface-2); color: var(--text); }
.rr-nav a.on { background: var(--text); color: var(--bg); }
.rr-nav a.on .en { opacity: .7; }
.rr-nav a .sw { width: 9px; height: 9px; border-radius: 3px; flex: none; }
.rr-levels { display: flex; flex-wrap: wrap; gap: 6px; }
.rr-chip { font-size: var(--t-caption); font-weight: 600; padding: 6px 12px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--card); color: var(--muted); cursor: pointer;
  transition: all .15s; font-family: var(--font-sans); }
.rr-chip:hover { border-color: var(--primary); }
.rr-chip.on { background: var(--text); color: var(--bg); border-color: transparent; }
@media (max-width: 900px) {
  .rr-side { position: static; }
  .rr-nav { flex-direction: row; overflow-x: auto; gap: 8px; padding-bottom: 6px; margin-bottom: 14px; }
  .rr-nav a { flex: none; border: 1px solid var(--border); }
  .rr-nav a .en { display: none; } .rr-side .rr-lbl:first-of-type { display: none; }
}

/* ── shared bits ──────────────────────────────────────────────────────── */
.rr-eyebrow { display: inline-flex; align-items: center; gap: 7px; font-size: var(--t-eyebrow);
  letter-spacing: .13em; text-transform: uppercase; font-weight: 700; }
.rr-eyebrow .dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
.rr-badge { font-weight: 600; font-size: var(--t-caption); letter-spacing: .02em; padding: 3px 9px;
  border-radius: 999px; background: color-mix(in srgb, var(--primary) 15%, transparent);
  color: var(--primary-dark); border: 1px solid color-mix(in srgb, var(--primary) 28%, transparent); }
.rr-sechead { display: flex; align-items: baseline; justify-content: space-between; margin: 38px 0 15px; }
.rr-sechead h3 { font-family: var(--font-display); font-size: var(--t-title); margin: 0; color: var(--text); }
.rr-sechead a { font-size: var(--t-caption); font-weight: 600; color: var(--primary-dark); }

/* ── lead story ───────────────────────────────────────────────────────── */
.rr-lead { display: grid; grid-template-columns: 1.25fr 1fr; border: 1px solid var(--border);
  border-radius: 22px; overflow: hidden; background: var(--card); box-shadow: var(--shadow);
  margin-bottom: 16px; cursor: pointer; transition: box-shadow .2s, transform .2s; }
.rr-lead:hover { transform: translateY(-3px); }
.rr-lead .rr-art { min-height: 280px; position: relative; }
.rr-lead .rr-art .wm { position: absolute; right: 16px; bottom: 4px; font-family: var(--font-hanzi-serif);
  font-size: 128px; font-weight: 700; color: rgba(255, 255, 255, .15); line-height: 1; }
.rr-lead .rr-b { padding: 30px; display: flex; flex-direction: column; justify-content: center; }
.rr-lead .rr-b h2 { font-family: var(--font-hanzi-serif); font-weight: 600;
  font-size: clamp(1.4rem, 3vw, 1.95rem); line-height: 1.3; margin: 14px 0 9px; color: var(--text); }
.rr-lead .rr-b .sub { font-family: var(--font-display); font-style: italic; color: var(--muted);
  font-size: var(--t-lead); margin: 0 0 14px; }
.rr-lead .rr-b p { color: var(--muted); font-size: var(--t-body-sm); line-height: 1.6; margin: 0 0 18px; }
.rr-meta { display: flex; align-items: center; gap: 11px; font-size: var(--t-caption); color: var(--muted); flex-wrap: wrap; }
@media (max-width: 640px) { .rr-lead { grid-template-columns: 1fr; } .rr-lead .rr-art { min-height: 150px; } }

/* ── the lead stage (rotating front-page slot) ────────────────────────── */
/* Every slide is a complete card in the DOM; the stage stacks them in ONE
   grid cell, so its height is the tallest slide and nothing jumps as they
   change -- and with JavaScript off the first card simply stays put. A
   carousel that slides sideways was explicitly not wanted (Duke, 2026-08-08),
   so the change is a cross-fade with a slow settle: the photograph eases down
   from a hair over full size while the text rises in behind it. */
.rr-stage { margin-bottom: 16px; }
.rr-stage.rotates { display: grid; grid-template-rows: auto auto; }
.rr-stage .rr-lead { margin-bottom: 0; }
.rr-stage.rotates > .rr-lead { grid-area: 1 / 1; opacity: 0; visibility: hidden;
  pointer-events: none; transition: opacity .55s ease, visibility .55s; }
.rr-stage.rotates > .rr-lead.on { opacity: 1; visibility: visible; pointer-events: auto; }
/* The hover lift has to be restated: the .on selector outranks .rr-lead:hover. */
.rr-stage.rotates > .rr-lead.on:hover { transform: translateY(-3px); }

.rr-stage.rotates .rr-art > * { transform: scale(1.045); transition: transform 1.15s cubic-bezier(.2, .7, .2, 1); }
.rr-stage.rotates .rr-lead.on .rr-art > * { transform: none; }
.rr-stage.rotates .rr-b > * { opacity: 0; transform: translateY(9px);
  transition: opacity .5s ease, transform .6s cubic-bezier(.2, .7, .2, 1); }
.rr-stage.rotates .rr-lead.on .rr-b > * { opacity: 1; transform: none; }
.rr-stage.rotates .rr-lead.on .rr-b > *:nth-child(1) { transition-delay: .10s; }
.rr-stage.rotates .rr-lead.on .rr-b > *:nth-child(2) { transition-delay: .16s; }
.rr-stage.rotates .rr-lead.on .rr-b > *:nth-child(3) { transition-delay: .21s; }
.rr-stage.rotates .rr-lead.on .rr-b > *:nth-child(4) { transition-delay: .25s; }
.rr-stage.rotates .rr-lead.on .rr-b > *:nth-child(5) { transition-delay: .29s; }

/* Pager. The active dot stretches into a pill and fills over the hold, so the
   page tells you when it is about to move — and stops filling when you hover,
   which is how you can see that hovering paused it. */
.rr-stage-dots { grid-row: 2; justify-self: end; display: flex; align-items: center; gap: 7px;
  padding: 12px 4px 0; }
.rr-dot { position: relative; width: 7px; height: 7px; padding: 0; border: 0; overflow: hidden;
  border-radius: 99px; background: color-mix(in srgb, var(--text) 20%, transparent);
  cursor: pointer; transition: width .45s cubic-bezier(.2, .7, .2, 1), background .3s; }
.rr-dot:hover { background: color-mix(in srgb, var(--text) 34%, transparent); }
.rr-dot.on { width: 32px; background: color-mix(in srgb, var(--dot) 24%, transparent); }
.rr-dot i { position: absolute; inset: 0; width: 0; border-radius: 99px; background: var(--dot); }
.rr-dot.on i { animation: rr-dotfill 8s linear forwards; }
.rr-stage:hover .rr-dot.on i, .rr-stage:focus-within .rr-dot.on i { animation-play-state: paused; }
@keyframes rr-dotfill { from { width: 0; } to { width: 100%; } }

@media (prefers-reduced-motion: reduce) {
  .rr-stage.rotates .rr-art > *, .rr-stage.rotates .rr-b > * { transform: none; transition: opacity .3s ease; }
  .rr-dot.on i { animation: none; width: 100%; }
}

/* ── section directory tiles ──────────────────────────────────────────── */
.rr-sections { display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 14px; }
.rr-stile { border: 1px solid var(--border); border-radius: 16px; overflow: hidden; background: var(--card);
  cursor: pointer; transition: transform .18s cubic-bezier(.2, .7, .3, 1), box-shadow .18s; }
.rr-stile:hover { transform: translateY(-3px); box-shadow: var(--shadow); }
.rr-stile .top { height: 82px; position: relative; display: flex; align-items: center; padding: 0 16px; }
.rr-stile .top .wm { position: absolute; right: 8px; bottom: -14px; font-family: var(--font-hanzi-serif);
  font-size: 66px; font-weight: 700; color: rgba(255, 255, 255, .18); line-height: 1; }
.rr-stile .top .zh { font-family: var(--font-hanzi-serif); font-weight: 600; font-size: 1.4rem;
  color: #fff; z-index: 1; text-shadow: 0 1px 4px rgba(0, 0, 0, .25); }
/* The same slot holding a Latin or Cyrillic name: "Университеты" needs more
   room than 大学, so it drops to the display face, wraps to two lines, and
   keeps clear of the icon in the bottom-right corner. */
/* A flex item will not shrink below its longest word (min-width defaults to
   auto), and "Университеты" is one word -- so the size has to come down
   rather than the box, or the name runs under the icon in the corner. */
.rr-stile .top .zh.lat { font-family: var(--font-display); font-size: 1rem; line-height: 1.2;
  min-width: 0; padding-right: 56px; }
/* A Latin or Cyrillic name needs the width a two-character Chinese one never
   did, so on those tiles the icon retreats further into its corner. */
.rr-stile .top.lat .rr-stile-ico { right: -18px; bottom: -18px; }
/* The Chinese name under it is a name, not a category tag: no uppercasing,
   no letter-spacing, which look wrong on hanzi. */
.rr-stile .b .en.zhsub { font-family: var(--font-hanzi-serif); text-transform: none;
  letter-spacing: 0; font-size: var(--t-body-sm); font-weight: 500; }
.rr-stile .b { padding: 11px 14px 14px; }
.rr-stile .b .en { font-size: var(--t-eyebrow); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted); }
.rr-stile .b .head { font-family: var(--font-hanzi-serif); font-size: var(--t-body-sm); color: var(--text);
  margin-top: 4px; line-height: 1.4; font-weight: 500; }

/* ── latest cards ─────────────────────────────────────────────────────── */
.rr-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 18px; }
.rr-card { background: var(--card); border: 1px solid var(--border); border-radius: 16px; overflow: hidden;
  cursor: pointer; transition: transform .18s cubic-bezier(.2, .7, .3, 1), box-shadow .18s;
  display: flex; flex-direction: column; }
.rr-card:hover { transform: translateY(-3px); box-shadow: var(--shadow); }
.rr-card.hide { display: none; }
.rr-card .rr-art { height: 112px; position: relative; }
.rr-card .rr-art .lvl { position: absolute; top: 10px; left: 10px; background: rgba(255, 255, 255, .92);
  color: #3a2a20; border-color: transparent; }
.rr-card .b { padding: 14px 15px 15px; display: flex; flex-direction: column; flex: 1; }
.rr-card .b .rr-eyebrow { margin-bottom: 8px; }
.rr-card .b h4 { font-family: var(--font-hanzi-serif); font-weight: 600; font-size: var(--t-title-sm);
  line-height: 1.35; margin: 0 0 4px; color: var(--text); }
.rr-card .b .sub { font-family: var(--font-display); font-style: italic; color: var(--muted);
  font-size: var(--t-body-sm); margin: 0 0 11px; line-height: 1.35; }
.rr-card .foot { margin-top: auto; display: flex; align-items: center; gap: 9px;
  font-size: var(--t-caption); color: var(--muted); }

.rr-empty { display: none; text-align: center; color: var(--muted); font-family: var(--font-display);
  font-size: var(--t-lead); padding: 40px 0; }
.rr-note { margin: 44px 0 0; padding: 15px 0 0; font-size: var(--t-caption); line-height: 1.6;
  color: var(--muted); border-top: 1px dashed var(--border); }

@media (prefers-reduced-motion: reduce) { .rr *, .rr-lead, .rr-card, .rr-stile { transition: none !important; } }

/* ── article reader ───────────────────────────────────────────────────── */
.rr-hero { max-width: 1100px; margin: 0 auto; height: 300px; border-radius: 0 0 28px 28px;
  position: relative; overflow: hidden; }
.rr-hero .wm { position: absolute; right: 4vw; bottom: -8px; font-family: var(--font-hanzi-serif);
  font-size: 200px; font-weight: 700; color: rgba(255, 255, 255, .14); line-height: 1; }
.rr-hero::after { content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(10, 6, 14, .5), transparent 55%); }
.rr-read { max-width: 680px; margin: 0 auto; padding: 0 24px 120px; }
.rr-read .rr-eyebrow { margin: 26px 0 12px; }
.rr-read h1 { font-family: var(--font-hanzi-serif); font-weight: 600; line-height: 1.28;
  font-size: clamp(1.9rem, 6vw, 2.7rem); margin: 0 0 8px; text-wrap: balance; color: var(--text); }
.rr-read .rr-subtitle { font-family: var(--font-display); font-style: italic;
  font-size: clamp(1.1rem, 3.5vw, 1.35rem); color: var(--muted); margin: 0 0 18px; }
.rr-read .rr-artmeta { display: flex; flex-wrap: wrap; align-items: center; gap: 12px;
  padding-bottom: 20px; border-bottom: 1px solid var(--border); color: var(--muted); font-size: var(--t-caption); }
.rr-read .rr-artmeta b { color: var(--text); font-weight: 600; }

/* NOT sticky any more. Duke liked the bar that followed the scroll but wanted
   it to be the Blog/Square/Library switch instead -- that is the control you
   reach for mid-article, where pinyin and translation are set once and left
   (2026-08-09). See body.blog-page .nc-app-bar-full below. */
.rr-controls { margin: 22px 0 30px;
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center; justify-content: space-between;
  padding: 9px 12px; border-radius: 16px; border: 1px solid var(--border);
  background: color-mix(in srgb, var(--card) 85%, transparent); backdrop-filter: blur(12px) saturate(1.2);
  box-shadow: var(--shadow); }
.rr-cgroup { display: flex; align-items: center; gap: 9px; }
.rr-clabel { font-size: var(--t-eyebrow); text-transform: uppercase; letter-spacing: .1em;
  color: var(--muted); font-weight: 700; }
.rr-seg { display: inline-flex; background: var(--surface-2); border-radius: 11px; padding: 3px; }
.rr-seg button { border: 0; background: none; font: inherit; font-size: var(--t-label); font-weight: 600;
  color: var(--muted); padding: 6px 12px; border-radius: 8px; cursor: pointer; font-family: var(--font-hanzi-serif); }
.rr-seg button.eng { font-family: var(--font-sans); }
.rr-seg button.on { background: var(--card); color: var(--primary-dark); box-shadow: 0 1px 3px rgba(80, 40, 20, .14); }
/* Text-size stepper (Kindle-style): a small A and a big A in one pill, sized and
   coloured to sit alongside the EN/RU segmented control as one group. */
.rr-size { display: inline-flex; align-items: center; background: var(--surface-2); border-radius: 11px; padding: 2px; }
.rr-size-btn { position: relative; border: 0; background: none; cursor: pointer; color: var(--muted);
  display: inline-flex; align-items: center; justify-content: center; min-width: 34px; height: 30px;
  border-radius: 9px; font-family: var(--font-hanzi-serif); font-weight: 600;
  transition: background .13s, color .13s, transform .1s; }
.rr-a { line-height: 1; display: block; }
.rr-a.sm { font-size: 0.8rem; }
.rr-a.lg { font-size: 1.15rem; }
.rr-size-btn + .rr-size-btn::before { content: ''; position: absolute; left: 0; top: 8px; bottom: 8px;
  width: 1px; background: var(--border); }
.rr-size-btn:hover:not(:disabled) { background: var(--card); color: var(--primary-dark); }
.rr-size-btn:active:not(:disabled) { transform: scale(.88); }
.rr-size-btn:disabled { opacity: .3; cursor: default; }
.rr-toggle { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; user-select: none; }
.rr-toggle .sw2 { width: 42px; height: 24px; border-radius: 999px; background: var(--surface-2);
  position: relative; transition: background .22s; border: 1px solid var(--border); }
.rr-toggle .sw2::after { content: ""; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px;
  border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .25); transition: transform .22s; }
.rr-toggle.on .sw2 { background: var(--primary); border-color: transparent; }
.rr-toggle.on .sw2::after { transform: translateX(18px); }
.rr-toggle span { font-size: var(--t-label); font-weight: 600; color: var(--muted); }

.rr-body .rr-p + .rr-p { margin-top: 28px; }
.rr-body .rr-cn { font-size: var(--rr-fs, 1.05rem); color: var(--text); margin: 0; line-height: 2.05; }
/* With pinyin on, each character box is tall (pinyin stacked over hanzi); without
   extra leading the rows touch, so a selection underline drawn under one line
   lands on the next line's pinyin (Duke, 2026-08-10) -- and the text reads
   cramped. Open the rows up when pinyin is showing. */
.rr:not(.rr-nopinyin) .rr-cn { line-height: 3.6; }
.rr-nopinyin .zi-py { display: none; }
.rr-trans { display: none; font-family: var(--font-display); font-size: var(--t-body); line-height: 1.6;
  color: var(--muted); margin: 10px 0 0; padding: 11px 16px; border-left: 2.5px solid var(--primary);
  background: color-mix(in srgb, var(--primary) 6%, transparent); border-radius: 0 10px 10px 0; }
.rr-showen .rr-trans[data-lang="en"], .rr-showru .rr-trans[data-lang="ru"] { display: block; }

.rr-vocab { margin-top: 50px; padding: 22px; border-radius: 18px; background: var(--surface-2);
  border: 1px solid var(--border); }
.rr-vocab h3 { font-family: var(--font-display); margin: 0 0 14px; font-size: var(--t-title); color: var(--text); }
.rr-vgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.rr-vcard { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 11px 13px; }
.rr-vcard .h { font-family: var(--font-hanzi-serif); font-size: 1.2rem; font-weight: 600; color: var(--text); }
.rr-vcard .p { font-size: var(--t-caption); color: var(--primary-dark); font-weight: 600; margin-top: 1px; }
.rr-vcard .m { font-size: var(--t-caption); color: var(--muted); margin-top: 3px; }

.rr-sources { margin-top: 26px; font-size: var(--t-caption); color: var(--muted); }
.rr-sources b { color: var(--text); }
.rr-wip { margin-top: 30px; padding: 16px 18px; border: 1px dashed var(--border); border-radius: 14px;
  color: var(--muted); font-size: var(--t-body-sm); }
.rr-back { display: inline-flex; align-items: center; gap: 6px; margin: 20px auto 0; max-width: 680px;
  padding: 0 24px; color: var(--muted); font-size: var(--t-body-sm); font-weight: 600; }

/* ── tappable words + lookup popover ──────────────────────────────────── */
/* `.rr-word` is a plain inline span wrapping one or more `.zi` (pinyin-over-
   character) atoms. A plain `display:inline` box is sized from ITS OWN
   inherited font/line-height -- it does NOT grow to enclose a taller
   inline-flex child. `.zi` (pinyin + character stacked) is taller than that
   inherited line box, so the hover/tap highlight -- painted within .rr-word's
   own (too-short) box -- landed on roughly the pinyin row and barely grazed
   the character underneath (Duke screenshot, 2026-08-17). Matching `.zi`'s own
   layout (inline-flex, bottom-aligned) makes `.rr-word` size itself to its
   tallest child and share the same line-bottom anchor, so the highlight
   covers the whole word, pinyin and character both. */
.rr-word { display: inline-flex; vertical-align: bottom; cursor: pointer;
  border-radius: 5px; transition: background .13s; }
.rr-word:hover { background: color-mix(in srgb, var(--primary) 14%, transparent); }
/* The tapped word is a soft coral HIGHLIGHTER, not a solid coral chip. The old
   solid fill put white text on light coral (#ff8563), which is ~2:1 contrast --
   the character all but vanished (Duke, 2026-08-10). A tint with the character
   left in its normal dark ink reads cleanly and matches the drag-selection
   highlight below. */
.rr-word.rr-on { background: color-mix(in srgb, var(--primary) 24%, transparent);
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--primary) 42%, transparent); }
.rr-word.rr-on .zi-han { color: var(--text); }
.rr-word.rr-on .zi-py { color: var(--primary-dark); }

/* Dragging to select a phrase. The characters are flex-stacked over their
   pinyin, so the browser's own selection paints one little box PER CHARACTER
   (90 for a paragraph, all different heights) -- a broken "brick wall" (Duke,
   2026-08-10). So we hide the native selection entirely and draw our own: one
   clean coral bar PER LINE, in an overlay that tracks the drag. The pinyin
   layer stays unselectable so a selection only ever grabs the hanzi. */
/* isolation keeps the selection overlay's negative z-index (.rr-hi, below) inside
   the reading body, so the highlighter fill lands behind the glyphs but never
   slips behind the page itself. */
.rr-body { position: relative; isolation: isolate; }
.rr-body .zi-py { -webkit-user-select: none; user-select: none; }
.rr-body ::selection { background: transparent; }
.rr-body ::-moz-selection { background: transparent; }
/* On a touch device, turn the browser's OWN selection all the way off -- the
   long-press magnifier, the blue drag handles and the Copy/Look-Up callout all
   come from native selection, and they landed on top of our coral bar and read
   as Safari's chrome, not ours (Duke, 2026-08-18). The JS engine in
   blog_article.html rebuilds selection from scratch on these devices. Mouse
   users keep native drag-select; they have no loupe to fight. */
@media (pointer: coarse) {
  .rr-body { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
}
/* The two grips at the ends of a touch selection: our take on the iOS handles,
   so it reads at a glance that the selection can be stretched to cover a whole
   sentence. A short stalk rises into the line and a coral ball sits just below
   the baseline; the box around it is a generous finger target. */
.rr-hand { position: absolute; top: 0; left: 0; width: 34px; height: 34px;
  margin-left: -17px; z-index: 6; opacity: 0; pointer-events: none;
  touch-action: none; transition: opacity .14s ease; }
.rr-hand.show { opacity: 1; pointer-events: auto; }
.rr-hand::before { content: ''; position: absolute; left: 50%; top: 1px;
  width: 2px; height: 15px; margin-left: -1px; border-radius: 2px; background: var(--primary); }
.rr-hand::after { content: ''; position: absolute; left: 50%; top: 13px;
  width: 15px; height: 15px; margin-left: -7.5px; border-radius: 50%; background: var(--primary);
  box-shadow: 0 1px 7px color-mix(in srgb, var(--primary) 55%, transparent); }
/* The highlight overlay sits BEHIND the glyphs (z-index below the text, kept in
   its own stacking context by .rr-body's isolation) so a filled selection reads
   like a highlighter marker, not a wash painted over the ink. */
.rr-hi { position: absolute; inset: 0; pointer-events: none; z-index: -1;
  opacity: 0; transition: opacity .16s ease; }
.rr-hi.show { opacity: 1; }
/* One clean box PER LINE (never the native per-character bricks): a soft coral
   fill so the reader sees exactly what a drag is catching, with a stronger coral
   underline along its base to keep the crisp edge of the original marker (Duke
   2026-08-18: wanted the fill AS WELL as the underline for visibility). The JS
   sets x / y / width / height; the transitions let it glide as the drag grows.
   The earlier "the fill looks very odd" was the broken per-character version;
   one rounded box per line is calm. */
/* No transition on the box geometry: the highlight must be glued to the finger
   as it drags. A width/transform transition here left the block lagging behind
   (and, animating together, sometimes wedged at its starting width) so the
   selection appeared not to follow the finger at all (Duke, 2026-08-18). Only
   the show/hide opacity on .rr-hi is eased. */
.rr-hi span { position: absolute; top: 0; left: 0; border-radius: 6px;
  background: color-mix(in srgb, var(--primary) 20%, transparent);
  box-shadow: inset 0 -2.5px 0 color-mix(in srgb, var(--primary) 62%, transparent); }

/* The reading body is set in the serif (the same face as the headline), not the
   UI sans, and at a regular weight -- the sans at 500 read heavy and blocky, "as
   if each character is highlighted" (Duke, 2026-08-10). A book face is calmer to
   read and looks premium. */
.rr-body .zi-han { font-family: var(--font-hanzi-serif); font-weight: 400; }

/* Numbers / Latin / punctuation match the character size (1.5em over the base)
   and its baseline, and share the serif so the whole line reads as one face.
   line-height MUST match .zi's 1.15, not some other compact value: .zi-han
   (what .rr-lat sits beside) inherits .zi's 1.15 and renders ~29px tall at
   this font-size, while .rr-lat at line-height:1 rendered only ~25px -- a
   ~4px mismatch that, combined with vertical-align:bottom pinning both to a
   taller shared line (the pinyin row pushes it up), was enough to visibly
   drift punctuation and Latin text off the character baseline (Duke
   screenshot, 2026-08-17: "」" and "(IBBY)" reads as sitting up on the pinyin
   line). Matching the line-height makes the two boxes the same height, so
   bottom-alignment lands them on the identical edge. */
.rr-body .rr-lat { font-size: 1.5em; font-family: var(--font-hanzi-serif); font-weight: 400;
  line-height: 1.15; vertical-align: bottom; }
/* Glue: punctuation the renderer attached to its preceding character so neither
   wraps to line-start alone. */
.rr-nb { white-space: nowrap; }

/* Full-width CJK punctuation (，。「」！？…) is rendered as a character-style .zi
   cell — an inline-flex box with an empty pinyin row over the mark — rather than
   a plain inline atom, so it bottom-aligns with the hanzi on every engine (iOS
   Safari floated the old inline version up onto the pinyin line; Duke 2026-08-19).
   It carries no pinyin, so nothing shows in the top row; margin:0 keeps the mark
   hugging its neighbour instead of taking the inter-character gap .zi otherwise
   gets with pinyin on. */
.rr-body .zi-punct { margin: 0; cursor: text; }

/* Extra space between characters is only wanted with PINYIN ON (so the syllables
   above them don't collide). With pinyin off it just makes the text look spaced
   out and blocky, so scope it to the pinyin-on state (Duke, 2026-08-10). */
.rr:not(.rr-nopinyin) .zi { margin: 0 1.3px; }
.rr-body .zi-py { font-size: 0.56em; letter-spacing: 0.02em; }

.rr-pop { position: absolute; z-index: 60; width: 250px; background: var(--card);
  border: 1px solid var(--border); border-radius: 16px; box-shadow: var(--shadow); padding: 15px 16px;
  opacity: 0; transform: translateY(6px) scale(.97); pointer-events: none;
  transition: opacity .15s, transform .15s; }
.rr-pop.show { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
.rr-pop .ph { font-family: var(--font-hanzi-serif); font-size: 1.6rem; font-weight: 600; line-height: 1; color: var(--text); }
.rr-pop .pp { color: var(--primary-dark); font-weight: 600; font-size: var(--t-label); margin-top: 5px; }
.rr-pop .pm { color: var(--muted); font-size: var(--t-body-sm); margin-top: 7px; line-height: 1.4; }
.rr-pop .pm.ru { color: var(--muted); opacity: .85; margin-top: 3px; }
.rr-pop .pacts { display: flex; gap: 8px; margin-top: 13px; }
.rr-pop .pacts a { flex: 1; border: 0; border-radius: 10px; padding: 9px 8px; font: inherit;
  font-size: var(--t-caption); font-weight: 600; cursor: pointer; display: flex; align-items: center;
  justify-content: center; gap: 5px; text-decoration: none; }
.rr-pop .b-card { background: var(--primary); color: #fff; }
.rr-pop .b-char { background: var(--surface-2); color: var(--text); border: 1px solid var(--border); }
.rr-pop .b-char .g { font-family: var(--font-hanzi-serif); }

/* ── clip (save a sentence) ───────────────────────────────────────────── */
/* Selection bar: one action per button, the card action appearing only when
   what was selected is a single word (see isWord() in blog_article.html).
   Built from the same parts as the word popover above — card surface, hairline
   border, one filled primary action — so the two floating things that can
   appear over an article look like one family. It is kept in the layout and
   hidden with visibility, not display, because the JS measures its width to
   centre it on the selection before showing it. */
.rr-selbar { position: absolute; z-index: 55; display: inline-flex; align-items: center; gap: 0;
  background: var(--card); border: 1px solid var(--border); border-radius: 15px; padding: 4px;
  box-shadow: var(--shadow); white-space: nowrap;
  visibility: hidden; opacity: 0; transform: translateY(5px) scale(.97); pointer-events: none;
  transition: opacity .14s ease, transform .16s cubic-bezier(.2, .8, .3, 1.3), visibility .14s; }
.rr-selbar.show { visibility: visible; opacity: 1; transform: none; pointer-events: auto; }
/* The tail points back at the selected words. JS sets --tail to the distance
   from the bar's left edge to the middle of the selection, so it keeps
   pointing at the right place even when the bar is nudged off a screen edge. */
.rr-selbar::after { content: ''; position: absolute; bottom: -5px; left: var(--tail, 50%);
  width: 9px; height: 9px; margin-left: -5px; background: var(--card);
  border-right: 1px solid var(--border); border-bottom: 1px solid var(--border);
  transform: rotate(45deg); border-bottom-right-radius: 2px; }
/* The two actions are peers, not a primary and a runner-up: a reader who
   selected a word is as likely to want one as the other, and giving one of
   them a filled slab made the bar read as a button next to a label (Duke,
   2026-08-08). Equal weight, a hairline between them, and the accent carried
   by the icon and the hover rather than a block of colour. */
.rr-selbtn { display: inline-flex; align-items: center; gap: 7px; background: none; border: 0;
  color: var(--text); font-family: var(--font-sans); font-size: var(--t-caption); font-weight: 600;
  padding: 8px 14px; border-radius: 11px; cursor: pointer; text-decoration: none;
  position: relative; transition: background .15s ease, color .15s ease; }
.rr-selbtn:hover { background: var(--surface-2); text-decoration: none; }
.rr-selbtn svg { width: 15px; height: 15px; flex: none; fill: none; stroke: currentColor;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; color: var(--muted); }
.rr-selbtn:hover svg { color: currentColor; }
.rr-selbtn svg circle, .rr-selbtn svg rect { fill: none; }
/* Only ever drawn between two visible actions: the card button is the second
   child and the one that hides for a sentence, so the rule goes with it. */
.rr-selbtn + .rr-selbtn::before { content: ''; position: absolute; left: 0; top: 9px; bottom: 9px;
  width: 1px; background: var(--border); }
.rr-selbtn-card { color: var(--text); }
.rr-selbtn-card svg { color: var(--primary); }
.rr-selbtn-card:hover { background: color-mix(in srgb, var(--primary) 14%, transparent); }
.rr-selbtn-card:hover svg { color: var(--primary); }

/* A snackbar toast. The whole pill is the action when it carries an href, so
   there is no separate "Sign in" word to duplicate the message and blow the
   bubble onto a ragged second line. The 22px radius equals half a single line's
   height, so a short message reads as a clean pill and a longer (Russian) one
   settles into a tidy rounded rectangle instead of clipping (Duke, 2026-08-18). */
.rr-toast { position: fixed; left: 50%; bottom: 26px; transform: translate(-50%, 20px);
  display: block; width: max-content; text-align: center; text-decoration: none;
  background: var(--text); color: var(--bg); font-family: var(--font-sans);
  font-size: var(--t-body-sm); font-weight: 600; line-height: 1.3;
  padding: 11px 20px; border-radius: 22px; box-shadow: var(--shadow);
  max-width: min(92vw, 440px); opacity: 0; pointer-events: none;
  transition: opacity .2s, transform .2s; z-index: 80; }
.rr-toast.show { opacity: 1; transform: translate(-50%, 0); }
a.rr-toast { pointer-events: auto; cursor: pointer; }
a.rr-toast:active { transform: translate(-50%, 0) scale(.97); }
.rr-toast .arw { color: var(--primary); font-weight: 800; margin-left: 5px; }

/* ── Library (enriched phrase collection, 2026-08-23) ─────────────────────
   A saved line is no longer bare hanzi: it carries its per-character pinyin
   (the global .zi stack, retuned calmer here; off by default), the human
   translation of the sentence it came from, and a note. One older line is
   quietly resurfaced at the top -- re-exposure, never a test. */
.lib { max-width: 720px; margin: 0 auto; padding: 26px 22px 120px; }

.lib-mast { position: relative; overflow: hidden; border-radius: 22px;
  padding: 28px 24px 22px; margin-bottom: 22px;
  background: linear-gradient(160deg, var(--surface-2), var(--card));
  border: 1px solid var(--border); }
.lib-wm { position: absolute; right: -6px; bottom: -34px; font-family: var(--font-hanzi-serif);
  font-weight: 600; font-size: 9rem; line-height: 1; color: var(--primary);
  opacity: .10; pointer-events: none; user-select: none; }
.lib-mast h1 { font-family: var(--font-hanzi-serif); font-weight: 600; font-size: var(--t-title-lg);
  margin: 0; letter-spacing: .5px; color: var(--text); }
.lib-sub { font-family: var(--font-display); color: var(--muted); font-size: var(--t-body-sm);
  margin: 6px 0 0; max-width: 44ch; }
.lib-mast-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 16px; }
.lib-count { font-family: var(--font-display); font-size: var(--t-body-sm); color: var(--text); }

/* the saved line -- reuse the global .zi pinyin stack, tuned calmer. Pinyin is
   hidden per clip by default (.py-off) and revealed by that clip's own toggle. */
.lib-line { font-family: var(--font-hanzi-serif); font-size: 1.02rem; line-height: 2.0;
  color: var(--text); margin: 0; }
.lib .zi-py { color: var(--muted); font-weight: 500; font-size: .5em; }
.lib-clip.py-off .zi-py, .lib-resurf.py-off .zi-py { display: none; }

/* Translation is clamped to a few lines so a long clip doesn't dump a wall of
   text; a tap expands it (JS adds .is-clamped only to the ones that overflow). */
.lib-trans { font-family: var(--font-display); font-size: var(--t-body-sm); color: var(--muted);
  margin: 12px 0 0; line-height: 1.5; max-width: 52ch;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; }
.lib-trans.open { display: block; overflow: visible; -webkit-line-clamp: unset; }
.lib-trans.is-clamped { cursor: pointer; }
.lib-trans.is-clamped:hover { color: var(--text); }

/* section header per source article */
/* source-article header: a clear "these came from this article" band, so the
   title reads as a title even to a non-Chinese reader (icon + eyebrow label +
   the article's Chinese title and its translation). */
.lib-grp { margin-top: 26px; }
.lib-src { display: flex; align-items: center; gap: 12px; margin: 0 2px 12px; padding: 4px 2px;
  text-decoration: none; color: var(--text); }
.lib-src-ic { flex: none; width: 38px; height: 38px; border-radius: 11px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--primary-light); color: var(--primary-dark); font-size: 1.1rem; }
.lib-src-ic svg { width: 20px; height: 20px; }
.lib-src-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.lib-src-eyebrow { font-family: var(--font-sans); font-size: .66rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.lib-src-title { font-family: var(--font-hanzi-serif); font-weight: 600; font-size: 1.12rem;
  color: var(--text); line-height: 1.25; }
.lib-src:hover .lib-src-title { color: var(--primary-dark); }
.lib-src-sub { font-family: var(--font-display); font-style: italic; font-size: var(--t-body-sm);
  color: var(--muted); line-height: 1.3; }
.lib-src-loose { cursor: default; }
.lib-src-loose .lib-src-ic { font-family: var(--font-hanzi-serif); }
.lib-pill { margin-left: auto; align-self: center; font-family: var(--font-hanzi-serif);
  font-size: .72rem; font-weight: 600; letter-spacing: .02em; color: var(--primary-dark);
  background: var(--primary-light); padding: 4px 11px; border-radius: 99px; white-space: nowrap; }

/* search */
.lib-search { display: flex; align-items: center; gap: 9px; margin-top: 16px;
  background: var(--card); border: 1px solid var(--border); border-radius: 999px;
  padding: 9px 16px; color: var(--muted); max-width: 460px;
  transition: border-color .15s, box-shadow .15s; }
.lib-search:focus-within { border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-light); }
.lib-search svg { width: 17px; height: 17px; flex: none; }
.lib-search input { flex: 1; min-width: 0; border: 0; background: none; font: inherit;
  font-family: var(--font-sans); font-size: var(--t-body-sm); color: var(--text); outline: none; }
.lib-search input::-webkit-search-cancel-button { -webkit-appearance: none; }
.lib-search-clear { flex: none; font-family: var(--font-sans); font-size: var(--t-caption);
  font-weight: 600; color: var(--primary-dark); text-decoration: none; white-space: nowrap; }
.lib-search-clear:hover { text-decoration: underline; }

/* pager: reuse the my-tests .mt-pager layout, recoloured for the coral Library */
.lib-pager { margin-top: 28px; }
.lib .mt-pg.is-cur { background: var(--primary-light); color: var(--primary-dark); }

/* clip card */
.lib-clip { position: relative; border: 1px solid var(--border); border-radius: 18px;
  background: var(--card); padding: 18px 20px 12px; margin-bottom: 14px;
  box-shadow: 0 1px 2px rgba(60,40,20,.04), 0 10px 26px -16px rgba(120,70,40,.16);
  transition: transform .2s, box-shadow .2s; }
.lib-clip:hover { transform: translateY(-2px);
  box-shadow: 0 2px 6px rgba(60,40,20,.06), 0 22px 46px -22px rgba(120,70,40,.30); }

/* note */
.lib-note-slot { margin-top: 12px; }
.lib-note { display: flex; align-items: flex-start; gap: 8px; font-family: var(--font-display);
  font-style: italic; color: var(--text); font-size: var(--t-body-sm);
  background: var(--surface-2); border-radius: 10px; padding: 9px 12px; }
.lib-note .q { color: var(--primary); font-family: var(--font-hanzi-serif); font-size: 1.15rem;
  line-height: 1; flex: none; transform: translateY(2px); font-style: normal; }
.lib-note-t { flex: 1; }
.lib-note-edit { margin-left: auto; color: var(--muted); cursor: pointer; font: inherit;
  font-style: normal; font-size: var(--t-caption); background: none; border: 0; flex: none; }
.lib-note-edit:hover { color: var(--primary-dark); }
.lib-addnote { display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-sans);
  font-size: var(--t-body-sm); color: var(--muted); background: none;
  border: 1px dashed var(--border); border-radius: 10px; padding: 8px 12px; cursor: pointer;
  transition: all .16s; }
.lib-addnote:hover { color: var(--primary-dark); border-color: var(--primary); background: var(--primary-light); }
.lib-note-input { width: 100%; font: inherit; font-family: var(--font-display); color: var(--text);
  background: var(--card); border: 1px solid var(--primary); border-radius: 10px; padding: 9px 12px;
  resize: vertical; outline: none; box-sizing: border-box; }
.lib-note-actions { display: flex; justify-content: flex-end; margin-top: 8px; }
.lib-note-save { font-family: var(--font-sans); font-size: var(--t-body-sm); font-weight: 600;
  color: #fff; background: var(--primary); border: 0; border-radius: 9px; padding: 7px 16px; cursor: pointer; }
.lib-note-save:hover { background: var(--primary-dark); }

/* action row: soft pill buttons that read clearly as controls, a plain date,
   and a real trash affordance for delete (not a bare ×). */
.lib-acts { display: flex; align-items: center; gap: 8px; margin-top: 14px; padding-top: 12px;
  border-top: 1px solid var(--border); }
.lib-chip { display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-sans);
  font-size: var(--t-body-sm); font-weight: 500; color: var(--text); background: var(--surface-2);
  border: 1px solid transparent; border-radius: 99px; padding: 8px 15px; cursor: pointer;
  text-decoration: none; transition: background .15s, color .15s, border-color .15s; }
.lib-chip:hover { background: var(--surface-2-hover); color: var(--primary-dark); }
.lib-chip .ic { width: 16px; height: 16px; flex: none; opacity: .75; }
/* per-clip pinyin toggle: lit while this clip is showing its pinyin */
.lib-pyt.on { background: var(--primary-light); color: var(--primary-dark); }
.lib-pyt.on:hover { background: var(--primary-light); }
.lib-when { font-size: var(--t-caption); color: var(--muted); white-space: nowrap; margin-left: auto; }
.lib-del { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px;
  flex: none; border: 0; background: none; color: var(--muted); border-radius: 50%; cursor: pointer;
  transition: background .15s, color .15s; }
.lib-del svg { width: 17px; height: 17px; }
.lib-del:hover { color: var(--accent-dark); background: var(--accent-light); }

/* resurfaced phrase of the day */
.lib-resurf { position: relative; border-radius: 20px; padding: 20px 22px 16px; margin-bottom: 28px;
  background: radial-gradient(120% 140% at 12% -20%, var(--primary-light), transparent 60%), var(--card);
  border: 1px solid var(--primary); }
.lib-eyebrow { display: inline-flex; align-items: center; gap: 7px; font-family: var(--font-sans);
  font-size: .72rem; font-weight: 600; letter-spacing: .09em; text-transform: uppercase;
  color: var(--primary-dark); margin-bottom: 10px; }
.lib-eyebrow .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--primary-dark); }
@media (prefers-reduced-motion: no-preference) {
  .lib-eyebrow .dot { animation: libPulse 2.4s ease-in-out infinite; }
}
@keyframes libPulse { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: .4; transform: scale(.7); } }

.lib-reveal { opacity: 0; transform: translateY(10px); }
.lib-reveal.in { opacity: 1; transform: none;
  transition: opacity .5s ease, transform .5s cubic-bezier(.2,.7,.3,1); }

.lib-empty { text-align: center; color: var(--muted); font-family: var(--font-display);
  font-size: var(--t-lead); padding: 60px 20px; }

@media (max-width: 520px) {
  .lib { padding: 16px 12px 110px; }
  .lib-mast { padding: 22px 18px 18px; }
  .lib-line { font-size: .96rem; }
}

/* "In text" from the Library scrolls to the clip's paragraph and flashes it. */
.rr-p.rr-hit { background: var(--primary-light); border-radius: 10px;
  box-shadow: 0 0 0 6px var(--primary-light); }
@media (prefers-reduced-motion: no-preference) {
  .rr-p.rr-hit { animation: rrHit 2.4s ease-out forwards; }
}
@keyframes rrHit {
  0%   { background: var(--primary-light); box-shadow: 0 0 0 6px var(--primary-light); }
  100% { background: transparent;          box-shadow: 0 0 0 6px transparent; }
}

/* ── comments + 动态 (under an article) ────────────────────────────────── */
.rr-comments { margin-top: 56px; border-top: 1px solid var(--border); padding-top: 26px; }
.rr-comments h3 { font-family: var(--font-display); font-size: var(--t-title); margin: 0 0 18px; color: var(--text); }
.rr-comments h3 .n { color: var(--muted); font-weight: 400; }

.rr-composer { display: flex; gap: 11px; background: var(--card); border: 1px solid var(--border);
  border-radius: 16px; padding: 13px; margin-bottom: 20px; }
.rr-cav { border-radius: 50%; flex: none; display: flex; align-items: center; justify-content: center;
  color: #fff; font-family: var(--font-hanzi-serif); font-weight: 600; background: linear-gradient(145deg, var(--primary), var(--primary-dark));
  background-size: cover; background-position: center; }
.rr-composer .rr-cav { width: 40px; height: 40px; font-size: 1rem; }
.rr-composer .box { flex: 1; min-width: 0; }
.rr-composer textarea { width: 100%; border: 0; background: none; font: inherit; font-size: var(--t-body);
  color: var(--text); padding: 8px 2px; outline: none; resize: none; min-height: 26px; line-height: 1.5; }
.rr-composer textarea::placeholder { color: var(--muted); }
.rr-composer .brow { display: flex; align-items: center; justify-content: space-between; margin-top: 6px; gap: 10px; }
.rr-composer .tools { display: flex; gap: 6px; align-items: center; }
.rr-tool { display: inline-flex; align-items: center; gap: 5px; font-size: var(--t-caption); font-weight: 600;
  color: var(--muted); border: 1px solid var(--border); background: var(--surface-2); border-radius: 9px;
  padding: 5px 10px; cursor: pointer; }
.rr-tool svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.9; }
.rr-post { font-size: var(--t-label); font-weight: 600; color: #fff; background: var(--primary);
  border: 0; border-radius: 999px; padding: 8px 18px; cursor: pointer; }
.rr-post:disabled { opacity: .5; cursor: default; }
.rr-attach { display: none; align-items: center; gap: 8px; margin-top: 8px; font-size: var(--t-caption); color: var(--muted); }
.rr-attach.show { display: flex; }
.rr-attach img { height: 40px; border-radius: 8px; }
.rr-attach button { background: none; border: 0; color: var(--accent-dark); cursor: pointer; font: inherit; }

.rr-signin-note { background: var(--surface-2); border: 1px solid var(--border); border-radius: 14px;
  padding: 15px 18px; margin-bottom: 20px; color: var(--muted); font-size: var(--t-body-sm); }
.rr-signin-note a { color: var(--primary-dark); font-weight: 600; }

.rr-citem { display: flex; gap: 11px; padding: 15px 2px; }
/* Every comment is wrapped in .rr-cw (swipe + ⋯ affordances); the divider that
   used to sit between .rr-citem siblings now sits between the wrappers. */
.rr-cw + .rr-cw { border-top: 1px solid var(--border); }
.rr-citem .rr-cav { width: 38px; height: 38px; font-size: .95rem; }
.rr-citem.persona .rr-cav { background: linear-gradient(145deg, #ff9a6f, #d9552f); }
.rr-cbody { flex: 1; min-width: 0; }
.rr-chead { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.rr-chead .nm { font-weight: 600; font-size: var(--t-body-sm); color: var(--text); }
.rr-chead .tm { font-size: var(--t-caption); color: var(--muted); }
.rr-chead .desk { font-size: var(--t-eyebrow); font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  color: var(--primary-dark); border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent);
  border-radius: 6px; padding: 1px 6px; }
.rr-ctext { font-size: var(--t-body); line-height: 1.55; margin: 5px 0 0; color: var(--text); white-space: pre-wrap; word-wrap: break-word; }
/* The "@name" tag on a reply that answers another reply -- the thread is flat
   (two deep), so this is what tells you who was actually addressed. */
.rr-mention { font-weight: 600; color: var(--accent-dark); }
/* A comment photo is a THUMBNAIL, not a full-bleed image -- a picture must not
   push the whole conversation down the page (Duke, 2026-08-10). Click to open
   it full-size in the lightbox. */
.rr-cimg { margin-top: 10px; }
.rr-cimg img { max-width: 200px; max-height: 200px; width: auto; height: auto;
  border-radius: 12px; border: 1px solid var(--border); display: block;
  object-fit: cover; cursor: zoom-in; }

/* 动态 activity — a natural post with a soft study accent */
.rr-citem.activity .rr-actv { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: var(--t-body); color: var(--text); margin-top: 5px; line-height: 1.5; }
.rr-badge-act { font-size: var(--t-caption); font-weight: 700; padding: 2px 9px; border-radius: 7px;
  color: var(--primary-dark); background: color-mix(in srgb, var(--primary) 13%, transparent); white-space: nowrap; }
.rr-badge-act.clip { color: #7b6bb0; background: color-mix(in srgb, #7b6bb0 15%, transparent); }
.rr-chip-w { font-family: var(--font-hanzi-serif); font-size: var(--t-body); font-weight: 600;
  background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 2px 10px; color: var(--text); }
.rr-clipq { font-family: var(--font-hanzi-serif); font-size: var(--t-body); color: var(--muted);
  border-left: 2.5px solid #7b6bb0; padding-left: 11px; margin-top: 8px; display: block; line-height: 1.5; }
.rr-cempty { color: var(--muted); font-size: var(--t-body-sm); padding: 8px 2px 0; }

/* ── 广场 the Square (community feed) ──────────────────────────────────── */
.rr-sq { max-width: 600px; margin: 0 auto; padding: 22px 16px 100px; }
.rr-sq-intro { display: flex; align-items: baseline; gap: 10px; margin-bottom: 16px; color: var(--muted); font-size: var(--t-body-sm); }
.rr-sq-intro .h { font-family: var(--font-hanzi-serif); font-weight: 600; font-size: 1.2rem; color: var(--text); }

.rr-sq-card { background: var(--card); border: 1px solid var(--border); border-radius: 20px;
  box-shadow: var(--shadow); padding: 16px 17px; margin-bottom: 16px; }
.rr-sq-head { display: flex; align-items: flex-start; gap: 11px; }
.rr-sq-head .rr-cav { width: 44px; height: 44px; font-size: 1.05rem; }
/* The identity column takes the space between avatar and follow pill, and can
   shrink -- min-width:0 is what lets a long name ellipsis instead of shoving
   the flame onto its own line and clipping the pill (Duke, 2026-08-17). */
.rr-sq-id { flex: 1 1 auto; min-width: 0; }
/* The top line carries the name and the follow pill; the meta line below spans
   the id's full width, so a wide follow pill only ever shortens the NAME, never
   the desk/time or the streak (Duke, 2026-08-17). */
.rr-sq-topline { display: flex; align-items: center; gap: 9px; }
.rr-sq-name { display: flex; align-items: center; gap: 5px; min-width: 0; flex: 1 1 auto;
  font-weight: 700; font-size: var(--t-body-sm); flex-wrap: nowrap; color: var(--text); }
/* The name gets its own line and truncates if it must; the press mark keeps a
   fixed spot right after it. The streak lives on the meta line below, so a wide
   follow pill can never squeeze a short name into an ellipsis. */
.rr-sq-who { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rr-sq-name .rr-emoji-anim, .rr-sq-name .rr-status { flex: none; }
.rr-sq-name .v { color: var(--primary); font-size: .85rem; }
/* An animated emoji badge next to a name -- the journalist "press" mark or a
   user's chosen status. It ships as the plain glyph (a fine fallback); on view,
   JS swaps in the self-hosted Google Noto Lottie, a real vector animation that
   loops like a Telegram status and pauses off-screen (Duke, 2026-08-18). Fixed
   box so the glyph and the injected <svg> line up identically. */
.rr-emoji-anim, .rr-status { display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; font-size: 14px; line-height: 1; font-style: normal;
  vertical-align: -2px; }
.rr-emoji-anim svg, .rr-status svg { width: 100%; height: 100%; display: block; }
/* In a comment header the badge sits inline with small text. */
.rr-chead .rr-emoji-anim { vertical-align: -3px; }

/* Flag status: a real flag graphic that ripples via the shared SVG wave filter
   -- flags have no emoji animation, so we animate the cloth instead. */
.rr-status.rr-flag { width: 20px; height: 15px; overflow: visible; }
.rr-status.rr-flag img { width: 100%; height: 100%; object-fit: cover; border-radius: 2px;
  filter: url(#rr-flagwave); }
@media (prefers-reduced-motion: reduce) { .rr-status.rr-flag img { filter: none; } }

/* The admins-only badge: our own little chalkboard whose white chalk mark
   writes itself, holds, then wipes and repeats -- a real drawing motion, made
   here so there's no licence to carry (Duke picked it, 2026-08-18). */
.rr-status.rr-chalkboard { width: 20px; height: 20px; }
.rr-cb { width: 100%; height: 100%; display: block; }
.rr-cb-mark { stroke-dasharray: 18; stroke-dashoffset: 18;
  animation: rr-cb-write 3.2s ease-in-out infinite; }
@keyframes rr-cb-write { 0% { stroke-dashoffset: 18; } 45%, 82% { stroke-dashoffset: 0; } 100% { stroke-dashoffset: 18; } }
@media (prefers-reduced-motion: reduce) { .rr-cb-mark { animation: none; stroke-dashoffset: 0; } }

/* Bigger on the profile H1. */
.rr-pf-name .rr-status.rr-flag { width: 30px; height: 22px; }
.rr-pf-name .rr-status.rr-chalkboard { width: 30px; height: 30px; }

/* Picker option content: flags show the still graphic (no wave -- lighter, and
   the chosen one waves next to the name anyway). */
.rr-opt-flag { width: 26px; height: 19px; object-fit: cover; border-radius: 2px;
  box-shadow: 0 0 0 1px rgba(0,0,0,.06); }
/* The streak chip: a small accent that sits on the meta line, in the same
   spot on every card. */
.rr-sq-streak, .rr-sq-handle .rr-streak {
  display: inline-flex; align-items: center; gap: 2px; font-size: var(--t-eyebrow); font-weight: 800;
  color: var(--primary-dark); background: color-mix(in srgb, var(--primary) 15%, transparent);
  border-radius: 7px; padding: 1px 6px 1px 5px; }
.rr-sq-handle { display: flex; align-items: center; gap: 7px; flex-wrap: nowrap; min-width: 0;
  font-size: var(--t-caption); color: var(--muted); margin-top: 2px; }
/* The desk·time truncates before the streak does, so the flame keeps one
   fixed spot at the end of the meta line instead of wrapping onto its own. */
.rr-sq-meta { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rr-sq-handle .rr-sq-streak, .rr-sq-handle .rr-streak { flex: none; }
.rr-sq-follow { margin-left: auto; font-size: var(--t-caption); font-weight: 600; color: var(--primary-dark);
  border: 1px solid var(--primary); background: none; border-radius: 999px; padding: 5px 12px; cursor: pointer; }
.rr-sq-body { font-size: var(--t-body); line-height: 1.6; margin: 11px 1px 2px; color: var(--text);
  white-space: pre-wrap; word-wrap: break-word; }
.rr-sq-body .han { font-family: var(--font-hanzi-serif); }

.rr-sq-embed { margin: 12px 0 2px; border: 1px solid var(--border); border-radius: 15px; overflow: hidden;
  display: flex; cursor: pointer; transition: background .15s; text-decoration: none; color: inherit; }
.rr-sq-embed:hover { background: var(--surface-2); }
.rr-sq-embed .art { width: 96px; flex: none; position: relative; }
.rr-sq-embed .art .wm { position: absolute; right: 6px; bottom: -6px; font-family: var(--font-hanzi-serif);
  font-size: 54px; font-weight: 700; color: rgba(255, 255, 255, .2); line-height: 1; }
.rr-sq-embed .eb { padding: 12px 14px; }
.rr-sq-embed .ey { font-size: var(--t-eyebrow); letter-spacing: .12em; text-transform: uppercase; font-weight: 700; }
.rr-sq-embed h3 { font-family: var(--font-hanzi-serif); font-weight: 600; font-size: var(--t-title-sm);
  margin: 5px 0 2px; line-height: 1.3; color: var(--text); }
.rr-sq-embed .sub { font-family: var(--font-display); font-style: italic; color: var(--muted); font-size: var(--t-body-sm); }
.rr-sq-embed .m { font-size: var(--t-caption); color: var(--muted); margin-top: 7px; }

.rr-sq-media { margin: 12px 0 2px; border-radius: 14px; overflow: hidden;
  border: 1px solid var(--border); max-width: 460px; }
.rr-sq-media img { width: 100%; max-height: 420px; object-fit: cover; display: block; cursor: zoom-in; }

.rr-sq-actions { display: flex; gap: 2px; padding-top: 8px; margin-top: 10px; border-top: 1px solid var(--border); }
.rr .rr-sq-act { display: inline-flex; align-items: center; gap: 7px; font-size: var(--t-caption); font-weight: 500;
  color: var(--muted); background: none; border: 0; padding: 8px 12px; border-radius: 10px; cursor: pointer;
  text-decoration: none; transition: color .15s, background .15s; }
.rr-sq-act:hover { background: var(--surface-2); }
.rr-sq-act.liked { color: var(--primary-dark); }
.rr-sq-act svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.9;
  stroke-linecap: round; stroke-linejoin: round; }
.rr-sq-act.liked svg { fill: var(--primary-dark); stroke: var(--primary-dark); }
/* On a phone the three Russian labels (Нравится / Читать / Комментировать) are
   wider than the card, and the last one spilled off the right edge (Duke,
   2026-08-10). Tighten the buttons and spread them across the row; wrap is the
   safety net so a long label plus a comment count can never overflow again. */
@media (max-width: 560px) {
  .rr-sq-actions { flex-wrap: wrap; gap: 0 2px; justify-content: space-between; }
  .rr-sq-act { padding: 8px 6px; gap: 6px; }
}

/* ── like / reply, on a comment ───────────────────────────────────────── */
.rr-cacts { display: flex; align-items: center; gap: 2px; margin-top: 4px; margin-left: -8px; }
.rr-cact { display: inline-flex; align-items: center; gap: 5px; background: none; border: 0;
  color: var(--muted); font: inherit; font-size: var(--t-caption); font-weight: 600;
  padding: 5px 8px; border-radius: 8px; cursor: pointer;
  transition: color .15s, background .15s; }
.rr-cact:hover { background: var(--surface-2); color: var(--text); }
.rr-cact svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.9;
  stroke-linejoin: round; transition: transform .25s cubic-bezier(.2, .8, .3, 1.4); }
.rr-cact.like.on { color: var(--primary-dark); }
.rr-cact.like.on svg { fill: var(--primary); stroke: var(--primary); }
/* The heart springs on the tap that turns it on -- the one place in the
   Reading Room where a flourish is the point rather than a distraction. */
.rr-cact.like.on svg { animation: rr-pop .32s cubic-bezier(.2, .8, .3, 1.5); }
@keyframes rr-pop { 0% { transform: scale(.7); } 55% { transform: scale(1.25); } 100% { transform: none; } }
.rr-cact .n { font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: reduce) { .rr-cact.like.on svg { animation: none; } }

/* ── managing a comment: swipe-to-reveal (touch) + ⋯ menu (desktop) ────────
   Delete (author) / Hide (moderator) never crowd the row (Duke, 2026-08-10):
   on a phone you swipe the comment left to reveal the action; on desktop a ⋯
   appears on hover. Both fire the same data-delete / data-hide handlers. */
.rr-cw { position: relative; }
.rr-cw-act > .rr-citem { position: relative; }
/* The panel must fill its wrapper edge-to-edge: with the wrapper clipped for
   the swipe, any margin on the row would let the red reveal layer behind it
   peek out as a stuck bar at rest (bug, 2026-08-10). Spacing comes from the
   row's own padding + the .rr-cw divider instead. */
.rr-cw > .rr-citem { margin: 0 !important; }
.rr-cw[data-swipeable] > [data-swipe-panel] {
  transition: transform .34s cubic-bezier(0.22, 1, 0.36, 1); will-change: transform; }
/* Held-open state (after a snap): the JS clears its inline transform and lets
   this hold the row at the reveal width -- keep it in sync with REVEAL in app.js. */
.rr-cw.is-open > [data-swipe-panel] { transform: translateX(-84px); }
/* The revealed action sits behind the panel, pinned to the right edge. */
.rr-cw-del { display: none; position: absolute; top: 0; right: 0; bottom: 0; width: 84px;
  border: 0; cursor: pointer; color: #fff; background: var(--accent);
  flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  font: inherit; font-size: var(--t-caption); font-weight: 600; }
.rr-cw-del svg { width: 21px; height: 21px; fill: none; stroke: currentColor;
  stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.rr-cw-del.hide { background: var(--muted); }
/* Depth: while the row is being pulled it casts a shadow onto the red, so it
   reads as sliding OVER the action rather than a flat red block appearing. */
.rr-cw.is-swiping > [data-swipe-panel], .rr-cw.is-open > [data-swipe-panel] {
  box-shadow: 3px 0 14px rgba(20, 16, 12, .16); }
/* The trash/eye pops in when the row snaps open -- a little life on release. */
.rr-cw.is-open > .rr-cw-del svg { animation: rr-del-pop .34s cubic-bezier(.2, .8, .3, 1.5); }
@keyframes rr-del-pop { 0% { transform: scale(.4); } 60% { transform: scale(1.18); } 100% { transform: scale(1); } }

/* The ⋯ menu: quiet until you hover the comment (desktop). */
.rr-cmenu { position: absolute; top: 10px; right: 2px; }
.rr-cmenu-btn { opacity: 0; background: none; border: 0; padding: 4px; border-radius: 8px;
  color: var(--muted); cursor: pointer; transition: opacity .15s, background .15s, color .15s; }
.rr-citem:hover .rr-cmenu-btn, .rr-cmenu-btn[aria-expanded="true"] { opacity: 1; }
.rr-cmenu-btn:hover { background: var(--surface-2); color: var(--text); }
.rr-cmenu-btn svg { width: 18px; height: 18px; fill: currentColor; display: block; }
.rr-cmenu-pop { position: absolute; top: calc(100% + 2px); right: 0; z-index: 20; min-width: 132px;
  background: var(--card); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow); padding: 4px; }
.rr-cmenu-pop[hidden] { display: none; }
.rr-cmenu-pop button { display: block; width: 100%; text-align: left; background: none; border: 0;
  padding: 8px 10px; border-radius: 7px; font: inherit; font-size: var(--t-body-sm);
  color: var(--text); cursor: pointer; }
.rr-cmenu-pop button:hover { background: var(--surface-2); }
.rr-cmenu-pop button.danger { color: var(--accent); }

/* A status post's ⋯ menu (author only): right-aligned in the head, always
   visible, on every device -- posts use the menu, not swipe (Duke, 2026-08-10).
   Reuses the .rr-cmenu-btn / .rr-cmenu-pop look. */
.rr-sq-menu { margin-left: auto; position: relative; flex: none; }
.rr-sq-menu .rr-cmenu-btn { opacity: 1; }

/* Touch: enable the swipe clip + reveal, and drop the desktop ⋯ menu. */
@media (hover: none) and (pointer: coarse) {
  .rr-cw[data-swipeable] { overflow: hidden; }
  .rr-cw-act > .rr-citem { background: var(--card); z-index: 1; }
  /* The reveal layer is only present while a row is being swiped or held open,
     so it can never sit there as a stuck red bar. */
  .rr-cw.is-swiping > .rr-cw-del, .rr-cw.is-open > .rr-cw-del { display: flex; }
  .rr-cmenu { display: none; }
}
/* Desktop (and any fine pointer): no swipe reveal, ⋯ menu only. */
@media (hover: hover) and (pointer: fine) { .rr-cw-del { display: none; } }
@media (prefers-reduced-motion: reduce) {
  .rr-cw[data-swipeable] > [data-swipe-panel] { transition: none; }
  .rr-cw.is-open > .rr-cw-del svg { animation: none; }
}

/* A deleted/hidden comment: a muted placeholder that still carries its replies. */
.rr-cdeleted .rr-cbody { padding-left: 2px; }
.rr-cdeleted-line { display: flex; align-items: center; gap: 6px; padding: 8px 0;
  color: var(--muted); font-size: var(--t-body-sm); font-style: italic; }
.rr-cdeleted-line svg { width: 15px; height: 15px; flex: none; fill: none;
  stroke: currentColor; stroke-width: 1.9; }

/* Replies: one level, marked by an inset rule rather than a deep indent so a
   phone still has room for the words. */
.rr-creplies { margin-top: 8px; padding-left: 14px; border-left: 2px solid var(--border); }
.rr-creplies .rr-cav { width: 28px; height: 28px; font-size: var(--t-caption); }
.rr-creplies[hidden] { display: none; }

/* "View N replies" / "Hide replies" -- replies stay collapsed until asked for. */
.rr-creplies-toggle { display: inline-flex; align-items: center; gap: 5px; margin-top: 6px;
  background: none; border: 0; padding: 3px 0; cursor: pointer; font: inherit;
  font-size: var(--t-caption); font-weight: 600; color: var(--primary-dark); }
.rr-creplies-toggle:hover .lbl { text-decoration: underline; }
.rr-creplies-toggle .chev { width: 15px; height: 15px; fill: none; stroke: currentColor;
  stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; transition: transform .18s ease; }
.rr-creplies-toggle[aria-expanded="true"] .chev { transform: rotate(180deg); }

/* "Show earlier comments" / "View all N comments" pager. */
.rr-more-comments { display: inline-flex; align-items: center; background: none;
  border: 0; padding: 6px 0; cursor: pointer; font: inherit; font-size: var(--t-caption);
  font-weight: 600; color: var(--primary-dark); }
.rr-more-comments:hover { text-decoration: underline; }
button.rr-sq-more { background: none; border: 0; cursor: pointer; text-align: left; }

/* Full-size photo overlay (comment + post images open here). */
.rr-lightbox { position: fixed; inset: 0; z-index: 200; display: flex;
  align-items: center; justify-content: center; padding: 24px;
  background: rgba(10, 8, 6, .82); backdrop-filter: blur(4px); cursor: zoom-out;
  animation: rr-lb-in .16s ease; }
.rr-lightbox img { max-width: 100%; max-height: 100%; border-radius: 12px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, .5); }
@keyframes rr-lb-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .rr-lightbox { animation: none; } }

/* A student's streak, next to their name. */
.rr-streak { font-size: var(--t-caption); font-weight: 600; color: var(--primary-dark);
  background: color-mix(in srgb, var(--primary) 13%, transparent);
  padding: 1px 7px; border-radius: 999px; white-space: nowrap; }

/* The composer, told which comment it is answering. */
.rr-replyto { display: flex; align-items: center; gap: 8px; font-size: var(--t-caption);
  color: var(--muted); padding: 0 0 7px; }
.rr-replyto b { color: var(--primary-dark); font-weight: 600; }
.rr-replyto button { background: none; border: 0; color: var(--muted); font: inherit;
  font-size: var(--t-caption); text-decoration: underline; cursor: pointer; padding: 0; }

/* The tail of the article's thread, shown inside its Square card. Deliberately
   the SAME .rr-citem markup the article page uses -- one thread on two
   surfaces only reads as one thread if the entries look the same in both. The
   inset rule on the left is what says "this belongs to the post above". */
.rr-sq-thread:not(:empty) { margin-top: 4px; padding-left: 12px;
  border-left: 2px solid var(--border); }
.rr-sq-thread-list .rr-citem { margin: 10px 0; }
.rr-sq-thread-list .rr-citem .rr-cav { width: 30px; height: 30px; font-size: var(--t-caption); }
.rr-sq-more { display: inline-block; font-size: var(--t-caption); font-weight: 600;
  color: var(--primary-dark); padding: 2px 0 6px; }
.rr-sq-more:hover { text-decoration: underline; }
.rr-sq-reply { display: flex; align-items: center; gap: 9px; margin-top: 8px; }
.rr-sq-reply .rr-cav { width: 30px; height: 30px; font-size: var(--t-caption); flex: none; }
.rr-sq-reply input { flex: 1; min-width: 0; background: var(--surface-2); border: 1px solid transparent;
  border-radius: 999px; padding: 9px 15px; font: inherit; font-size: var(--t-body-sm);
  color: var(--text); transition: border-color .15s, background .15s; }
.rr-sq-reply input::placeholder { color: var(--muted); }
.rr-sq-reply input:focus { outline: none; background: var(--card); border-color: var(--primary); }
.rr-sq-reply button[type="submit"] { flex: none; border: 0; border-radius: 999px; padding: 9px 16px; cursor: pointer;
  font: inherit; font-size: var(--t-caption); font-weight: 600;
  background: var(--primary); color: #fff; transition: background .15s, opacity .15s; }
.rr-sq-reply button[type="submit"]:hover { background: var(--primary-dark); }
.rr-sq-reply button[type="submit"]:disabled { opacity: .55; cursor: default; }

/* Inline post editor (⋯ → Edit). Mirrors the composer box so editing reads as
   the same surface as writing. */
.rr-sq-edit { margin: 4px 0 2px; }
.rr-sq-edit textarea { width: 100%; box-sizing: border-box; min-height: 2.6em; resize: none;
  background: var(--surface-2); border: 1px solid transparent; border-radius: 14px;
  padding: 10px 13px; font: inherit; font-size: var(--t-body); line-height: 1.5;
  color: var(--text); transition: border-color .15s, background .15s; }
.rr-sq-edit textarea:focus { outline: none; background: var(--card); border-color: var(--primary); }
.rr-sq-edit-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 8px; }
.rr-sq-edit-actions button { border: 0; border-radius: 999px; padding: 7px 16px; cursor: pointer;
  font: inherit; font-size: var(--t-caption); font-weight: 600; transition: background .15s, opacity .15s; }
.rr-sq-edit-cancel { background: var(--surface-2); color: var(--text); }
.rr-sq-edit-cancel:hover { background: var(--surface-3, var(--surface-2)); }
.rr-sq-edit-save { background: var(--primary); color: #fff; }
.rr-sq-edit-save:hover { background: var(--primary-dark); }
.rr-sq-edit-save:disabled { opacity: .55; cursor: default; }

.rr-sq-empty { text-align: center; color: var(--muted); font-family: var(--font-display);
  font-size: var(--t-lead); padding: 50px 0; }

/* ── motion: smooth page transitions between Blog / Square / Library ──────
   Cross-document View Transitions turn the "cold refresh" on nav clicks into a
   soft cross-fade, and the active pill morphs from its old spot to its new one
   (both pages opt in via this stylesheet). Progressive enhancement: browsers
   without it just navigate normally. */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: rr-vt-out .18s ease both; }
::view-transition-new(root) { animation: rr-vt-in .26s cubic-bezier(.2,.7,.3,1) both; }
@keyframes rr-vt-out { to { opacity: 0; } }
@keyframes rr-vt-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
/* The lit pill is the same named element on every page, so the browser slides
   it between positions instead of snapping. */
.rr-tab.on { view-transition-name: rr-active-pill; }
/* Fallback fade for browsers without cross-document view transitions. */
@supports not (view-transition-name: none) {
  body.blog-page .container { animation: rr-page-in .28s ease both; }
}
@keyframes rr-page-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
  body.blog-page .container { animation: none !important; }
}

/* ── nav additions (ride inside the shared _nav.html actions row) ──────── */
/* The place-switcher. Centred in the bar, on its own track, with an icon per
   destination -- it is the control people use most on these pages and it
   should look like a switch, not like three links after the logo. */
.rr-navpills { position: relative; display: flex; gap: 2px; background: var(--surface-2);
  border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  border-radius: 999px; padding: 4px; box-shadow: inset 0 1px 2px rgba(60, 30, 15, .06); }
.rr-navpills .rr-tab { display: inline-flex; align-items: center; gap: 7px; }
.rr-navpills .rr-tab svg { width: 15px; height: 15px; flex: none; fill: none;
  stroke: currentColor; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round;
  opacity: .85; }
.rr-navpills .rr-tab.on svg { opacity: 1; }
.rr-navsearch { display: flex; align-items: center; gap: 9px; background: var(--card);
  border: 1px solid var(--border); border-radius: 999px; padding: 7px 14px; color: var(--muted);
  flex: 1 1 260px; max-width: 380px; }
.rr-navsearch input { flex: 1; min-width: 0; border: 0; background: none; font: inherit;
  font-size: var(--t-body-sm); color: var(--text); outline: none; }
.rr-navsearch input::-webkit-search-cancel-button { -webkit-appearance: none; }
.rr-navsearch svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2; flex: none; }
.rr-nav-cta { white-space: nowrap; }
/* Wide screens: the wrapper is invisible to layout, so pills and search are
   direct children of the actions row exactly as they were before it existed. */
.rr-navgroup { display: contents; }
/* Let the actions row breathe so the pills sit left, search centres, chip right. */
/* Centre the switch: the group takes the middle, search and bell sit after it.
   `position: absolute` on a wide bar keeps it optically centred on the PAGE
   rather than centred in whatever space the account chip leaves over, which
   moves as names get longer. */
body.blog-page .nc-app-bar-actions { flex: 1; gap: 12px; justify-content: flex-end; }
@media (min-width: 861px) {
  body.blog-page .nc-app-bar { position: relative; }
  body.blog-page .rr-navgroup { display: block; position: absolute; left: 50%;
    top: 50%; transform: translate(-50%, -50%); z-index: 1; }
}
/* ---- phone / small tablet: the bar folds into two rows ------------------
   One row cannot hold wordmark + three pills + search + account chip at
   390px, and the browser's answer was to crush the pills until each Chinese
   character sat on its own line and to drop the search box entirely
   (Duke, 2026-08-07).

   `display: contents` on the actions row promotes its children to direct
   children of the bar, so they can wrap onto a second line -- without
   duplicating the markup or forking the shared nav macro. Row one keeps the
   wordmark and the account chip (or the language pill when logged out); row
   two carries the Blog/Square pills and the search box, full width.

   space-between is dropped here because with four items on row one it spaces
   them all equally; margin-left:auto on the first right-hand item keeps that
   group together instead.

   The bell used to float alone right after the wordmark, with a lone
   margin-left:auto on the account chip pulling only itself away -- so on a
   logged-in phone view the bell sat isolated on the left with a gap before
   the avatar (Duke's screenshot, 2026-08-13). Search/bell/account chip are
   meant to read as one cluster, so all three default to margin-left:auto
   (whichever renders first absorbs the free space) and each later one in
   that trio cancels its own auto margin back to a normal gap, so they land
   snug against each other instead of each grabbing its own share. */
@media (max-width: 860px) {
  body.blog-page .nc-app-bar { flex-wrap: wrap; row-gap: 10px; justify-content: flex-start; }
  body.blog-page .nc-app-bar-actions { display: contents; }
  body.blog-page .nc-app-bar .rr-lang3,
  body.blog-page .nc-app-bar .rr-searchwrap,
  body.blog-page .nc-app-bar .rr-bell,
  body.blog-page .nc-app-bar .nc-login-chip,
  body.blog-page .nc-app-bar .nc-user-menu { margin-left: auto; }
  body.blog-page .nc-app-bar .rr-searchwrap ~ .rr-bell,
  body.blog-page .nc-app-bar .rr-searchwrap ~ .nc-login-chip,
  body.blog-page .nc-app-bar .rr-searchwrap ~ .nc-user-menu,
  body.blog-page .nc-app-bar .rr-bell ~ .nc-user-menu { margin-left: 0; }
  /* flex-basis 100% is what forces the break: pills + search take a full line
     of their own, in that order, instead of trailing the language pill. */
  /* min-width: 0 on both is what actually lets the search box shrink: a flex
     item's default min-width:auto is its min-content width, so without this
     the search kept its natural size and ran off the right edge. */
  body.blog-page .rr-navgroup { display: flex; align-items: center; gap: 10px;
    order: 3; flex: 1 0 100%; min-width: 0; flex-wrap: wrap; row-gap: 10px;
    justify-content: center; }
  body.blog-page .rr-navpills { flex: none; }
}
/* Phones: four full-word tabs (Reading/Square/Library/Profile) overflow a
   ~390px row and stretch the track edge to edge, clipping the first tab
   (Duke's screenshot, 2026-08-13). Collapse to ICONS ONLY, and let just the
   ACTIVE tab keep its word -- the iOS segmented / app tab-bar pattern. The
   track then hugs its content and sits centred instead of spanning the width. */
@media (max-width: 560px) {
  .rr-navpills { gap: 2px; }
  .rr-navpills .rr-tab { padding: 7px 10px; gap: 6px; }
  .rr-navpills .rr-tab span { display: none; }
  .rr-navpills .rr-tab.on span { display: inline; }
  .rr-navpills .rr-tab.on { padding: 7px 13px; }
  body.blog-page .nc-app-bar-full { padding: 16px 16px 0; }
}
/* 479px is the app's own phone breakpoint (the account chip drops its name
   there too). Below it the "Start reading free" pill is what gives, since the
   whole page is the pitch. */
@media (max-width: 479px) { .rr-nav-cta { display: none; } }

/* ── home page container (sidebar removed) ────────────────────────────── */
.rr-page { max-width: 1120px; margin: 0 auto; padding: 24px 24px 100px; }

/* section-tile + lead + card artwork icons */
.rr-stile .top { position: relative; overflow: hidden; }
.rr-stile-ico { position: absolute; right: -8px; bottom: -12px; width: 74px; height: 74px; color: rgba(255, 255, 255, .3); }
.rr-lead .rr-art { display: block; }
.rr-lead-ico { position: absolute; right: 6%; bottom: 5%; width: 150px; height: 150px; color: rgba(255, 255, 255, .22); }
.rr-card .rr-art { overflow: hidden; }
.rr-card-ico { position: absolute; right: 8px; bottom: 4px; width: 58px; height: 58px; color: rgba(255, 255, 255, .32); }

/* ── section page ─────────────────────────────────────────────────────── */
.rr-sec { max-width: 680px; margin: 0 auto; padding: 28px 20px 100px; }
.rr-sec-head { display: flex; align-items: center; gap: 15px; margin-bottom: 22px; }
.rr-sec-badge { width: 60px; height: 60px; border-radius: 16px; flex: none; display: flex; align-items: center; justify-content: center; }
.rr-sec-badge svg { width: 30px; height: 30px; color: #fff; }
/* Search has no desk colour of its own -- it spans every section -- so it
   wears the house coral instead of a section hue. */
/* A level has no icon of its own; the Chinese numeral is the mark. */
.rr-lev-badge span { font-family: var(--font-hanzi-serif); font-size: 2rem; font-weight: 600;
  color: #fff; line-height: 1; text-shadow: 0 1px 5px rgba(0, 0, 0, .28); }
.rr-sec-badge-search { background: linear-gradient(135deg, var(--primary-dark), var(--primary)); }
.rr-sec-badge-search svg { fill: none; stroke: #fff; stroke-width: 2; }
.rr-sec-zh { font-family: var(--font-hanzi-serif); font-weight: 600; font-size: 1.7rem; color: var(--text); line-height: 1.1; }
.rr-sec-zh.lat { font-family: var(--font-display); }
.rr-sec-en { font-size: var(--t-caption); font-weight: 700; text-transform: uppercase; letter-spacing: .08em; margin-top: 5px; }
.rr-sec-en .zhsub { font-family: var(--font-hanzi-serif); text-transform: none; letter-spacing: 0; }
/* The section header's own "← all sections/levels" link is gone: the back
   arrow beside the tool bar owns "back" everywhere now (2026-08-14). */
.rr-sec .rr-levels { margin-bottom: 18px; }
.rr-artlist { display: flex; flex-direction: column; gap: 12px; }
.rr-artrow { display: flex; gap: 0; background: var(--card); border: 1px solid var(--border); border-radius: 16px;
  overflow: hidden; transition: box-shadow .18s, transform .18s; cursor: pointer; }
.rr-artrow:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.rr-artrow .ic { width: 84px; flex: none; display: flex; align-items: center; justify-content: center; }
.rr-artrow .ic svg { width: 30px; height: 30px; color: rgba(255, 255, 255, .92); }
.rr-artrow .tx { padding: 14px 16px; flex: 1; min-width: 0; }
.rr-artrow .tx h4 { font-family: var(--font-hanzi-serif); font-weight: 600; font-size: var(--t-title-sm); margin: 4px 0 3px; color: var(--text); line-height: 1.35; }
.rr-artrow .tx .sub { font-family: var(--font-display); font-style: italic; color: var(--muted); font-size: var(--t-body-sm); margin-bottom: 9px; line-height: 1.35; }
.rr-artrow .tx .foot { display: flex; align-items: center; gap: 9px; font-size: var(--t-caption); color: var(--muted); }

/* ── article artwork: photograph, or the article's own hanzi ────────────────
   Added 2026-08-08 (Duke: "why isn't there at least one real photo?"). Every
   surface renders through templates/_blog_art.html, so a photo and a lettered
   tile are interchangeable in the same box.

   The photo sits ON TOP of the container's section gradient rather than
   replacing it: the gradient is the colour the box holds while the image
   loads, so a slow phone connection never shows a white hole. */
/* The photo starts invisible and fades in the instant it decodes. This is what
   kills the browser's own broken-image glyph (the blue "?" tile iOS paints over
   a still-loading <img>): with nothing to paint, the box shows only the section
   gradient and its shimmer until the real photo is ready to take over. */
.rr-photo { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block; border: 0;
  opacity: 0; transition: opacity .5s ease; }
.rr-photo.rr-ph-on { opacity: 1; }

/* Premium loading state: a soft diagonal light sweeps across the gradient tile
   while the photo loads, then stops and fades the moment the photo arrives.
   Keyed on :has(> .rr-photo) so it appears only on cards that actually hold a
   photo -- a lettered-hanzi tile never shimmers. */
.rr-sq-embed .art:has(> .rr-photo)::after,
.rr-lead .rr-art:has(> .rr-photo)::before,
.rr-card .rr-art:has(> .rr-photo)::before,
.rr-artrow .ic:has(> .rr-photo)::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg, transparent 25%,
    rgba(255, 255, 255, .28) 50%, transparent 75%);
  background-size: 220% 100%; background-position: 130% 0;
  animation: rr-ph-shimmer 1.4s ease-in-out infinite;
  transition: opacity .45s ease; }
.rr-sq-embed .art:has(> .rr-photo.rr-ph-on)::after,
.rr-lead .rr-art:has(> .rr-photo.rr-ph-on)::before,
.rr-card .rr-art:has(> .rr-photo.rr-ph-on)::before,
.rr-artrow .ic:has(> .rr-photo.rr-ph-on)::before { opacity: 0; animation: none; }
@keyframes rr-ph-shimmer {
  from { background-position: 130% 0; }
  to { background-position: -30% 0; } }
@media (prefers-reduced-motion: reduce) {
  .rr-photo { transition: none; }
  .rr-sq-embed .art:has(> .rr-photo)::after,
  .rr-lead .rr-art:has(> .rr-photo)::before,
  .rr-card .rr-art:has(> .rr-photo)::before,
  .rr-artrow .ic:has(> .rr-photo)::before { animation: none;
    background: rgba(255, 255, 255, .07); } }
/* A gentle darkening at the foot of the larger photos, so the white level
   badge and any overlaid type keep their contrast on a pale image. */
.rr-lead .rr-art::after, .rr-card .rr-art::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, rgba(10, 6, 14, .34), transparent 62%); }

/* The lettered alternative. One large character from the story, bled off the
   corner the way a masthead letter is -- not centred, which would read as an
   icon and put us back where we started. */
.rr-artchar { position: absolute; font-family: var(--font-hanzi-serif); font-weight: 700;
  color: rgba(255, 255, 255, .17); line-height: .82; user-select: none; }
.rr-artchar-lead { right: 4%; bottom: -6%; font-size: clamp(120px, 17vw, 190px); }
.rr-artchar-card { right: 4%; bottom: -14%; font-size: 92px; }
/* The row thumbnail is only ~84px wide, so a character bled off the corner
   showed as an unreadable sliver. Here it is centred and set solid enough to
   read as a deliberate mark. */
.rr-artchar-row { inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 40px; color: rgba(255, 255, 255, .82); line-height: 1; }
.rr-artrow .ic { position: relative; overflow: hidden; }
/* A photo in the narrow section-row thumbnail wants no dark wash -- it is
   small enough already. */
.rr-artrow .ic .rr-photo { border-radius: 0; }

.rr-hero.has-photo::after { background: linear-gradient(to top, rgba(10, 6, 14, .42), transparent 60%); }
/* Same loading behaviour as the feed photos: the hero image fades in on decode
   (no browser broken-image glyph over the gradient), with a light sweep across
   the coloured band while it loads. .has-photo is the ready-made hook. */
.rr-hero-photo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block;
  opacity: 0; transition: opacity .5s ease; }
.rr-hero-photo.rr-ph-on { opacity: 1; }
.rr-hero.has-photo:has(.rr-hero-photo)::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg, transparent 25%,
    rgba(255, 255, 255, .28) 50%, transparent 75%);
  background-size: 220% 100%; background-position: 130% 0;
  animation: rr-ph-shimmer 1.4s ease-in-out infinite;
  transition: opacity .45s ease; }
.rr-hero.has-photo:has(.rr-hero-photo.rr-ph-on)::before { opacity: 0; animation: none; }
@media (prefers-reduced-motion: reduce) {
  .rr-hero-photo { transition: none; }
  .rr-hero.has-photo:has(.rr-hero-photo)::before { animation: none; background: rgba(255, 255, 255, .07); } }
/* A tall photo is shown WHOLE and centred (contain), floating over a blurred,
   dimmed, over-scaled copy of itself that fills the wide band -- so a portrait
   subject keeps its head and feet instead of being cropped to a middle slice. */
.rr-hero-bg { position: absolute; inset: 0; background-size: cover; background-position: center;
  filter: blur(26px) brightness(.6) saturate(1.12); transform: scale(1.18); }
.rr-hero-tall .rr-hero-photo { object-fit: contain; }
/* Attribution. Deliberately quiet -- present, readable, never competing with
   the headline underneath it. */
.rr-hero-credit { max-width: 1100px; margin: 7px auto 0; padding: 0 24px;
  font-size: var(--t-caption); color: var(--muted); text-align: right; }
.rr-hero-credit a { color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }
.rr-hero-credit a:hover { color: var(--primary-dark); }
@media (max-width: 640px) { .rr-hero-credit { text-align: left; padding: 0 20px; } }

/* ---- hero height on a phone -------------------------------------------
   300px of flat colour reads as chrome; 300px of PHOTOGRAPH reads as a
   billboard, and on a phone it took better than a third of the screen before
   the headline even appeared (Duke, 2026-08-08: "the photo is breaking the
   modal size, can we keep it like earlier layout design").

   The desktop proportion he approved is untouched. Below 860px the band steps
   down so the article still opens on its title, and the lettered character
   steps down with it -- a 200px glyph in a 170px band is mostly off-canvas. */
@media (max-width: 860px) {
  .rr-hero { height: 210px; border-radius: 0 0 22px 22px; }
  .rr-hero .wm { font-size: 150px; }
}
@media (max-width: 560px) {
  .rr-hero { height: 168px; border-radius: 0 0 18px 18px; }
  .rr-hero .wm { font-size: 118px; bottom: -6px; }
}

/* Square embed uses the same two cases. */
.rr-sq-embed .art { position: relative; overflow: hidden; }

/* Like on a Square card: the same affordance as on a comment, at card scale. */
.rr-sq-act.like.on { color: var(--primary-dark); }
.rr-sq-act.like.on svg { fill: var(--primary); stroke: var(--primary);
  animation: rr-pop .32s cubic-bezier(.2, .8, .3, 1.5); }
.rr-sq-act .n { font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: reduce) { .rr-sq-act.like.on svg { animation: none; } }

/* A composer being dragged onto. Both composers already show the picked file
   in their own preview strip, so this only has to say "let go here". */
.rr-composer.rr-dropping .box { outline: 2px dashed var(--primary); outline-offset: 3px; }

/* ── notification bell ────────────────────────────────────────────────── */
.rr-bell { position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border-radius: 999px; color: var(--muted); flex: none;
  transition: background .15s, color .15s; }
.rr-bell:hover { background: var(--surface-2); color: var(--text); }
.rr-bell svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linejoin: round; stroke-linecap: round; }
.rr-bell.has { color: var(--primary-dark); }
.rr-bell .n { position: absolute; top: 1px; right: 0; min-width: 16px; height: 16px;
  padding: 0 4px; border-radius: 999px; background: var(--primary); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 16px; text-align: center;
  box-shadow: 0 0 0 2px var(--bg); font-variant-numeric: tabular-nums; }

/* ── the notification centre ──────────────────────────────────────────── */
.rr-notifs { max-width: 640px; margin: 0 auto; padding: 30px 20px 120px; }

/* Header: title + one-line lede on the left, a live status pill on the right. */
.rr-notif-top { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; margin: 0 4px 20px; }
.rr-notifs h1 { font-family: var(--font-display); font-size: var(--t-title-lg); margin: 0;
  color: var(--text); line-height: 1.1; }
.rr-notif-lede { margin: 6px 0 0; color: var(--muted); font-size: var(--t-body-sm);
  font-family: var(--font-sans); }
.rr-notif-pill { flex: none; margin-top: 4px; font-family: var(--font-sans);
  font-size: var(--t-caption); font-weight: 700; letter-spacing: .01em; white-space: nowrap;
  padding: 5px 12px; border-radius: 999px; color: #fff;
  background: linear-gradient(145deg, var(--primary), var(--primary-dark));
  box-shadow: 0 5px 14px -6px color-mix(in srgb, var(--primary) 75%, transparent); }
.rr-notif-pill.quiet { color: var(--muted); background: var(--surface-2);
  box-shadow: none; font-weight: 600; }

/* Day divider — a quiet eyebrow that turns the stack into a timeline. */
.rr-notif-daylabel { font-family: var(--font-sans); font-size: var(--t-caption);
  font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--muted);
  margin: 22px 6px 9px; }
.rr-notif-daylabel:first-of-type { margin-top: 4px; }

/* Each day's notifications share one card; hairline rules divide the rows, so
   they read as one surface rather than a run of detached boxes or a flat mush. */
.rr-notif-list { background: var(--card); border: 1px solid var(--border);
  border-radius: 18px; box-shadow: var(--shadow); overflow: hidden; }

.rr-notif { position: relative; display: flex; gap: 14px; align-items: flex-start;
  padding: 15px 17px; text-decoration: none; transition: background .15s ease; }
.rr-notif + .rr-notif { border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); }
.rr-notif:hover { background: var(--surface-2); }
.rr-notif:active { background: color-mix(in srgb, var(--primary) 8%, var(--surface-2)); }
.rr-notif.unread { background: color-mix(in srgb, var(--primary) 6%, transparent); }
.rr-notif.unread:hover { background: color-mix(in srgb, var(--primary) 10%, transparent); }
/* A coral spine on the unread rows — the glance-level "new since you left". */
.rr-notif.unread::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0;
  width: 3px; background: var(--primary); }

/* Avatar + a small kind glyph stamped on its corner. */
.rr-notif-av { position: relative; flex: none; }
.rr-notif .rr-cav { width: 44px; height: 44px; font-size: 1.05rem; }
.rr-nk { position: absolute; right: -3px; bottom: -3px; width: 19px; height: 19px;
  border-radius: 50%; display: flex; align-items: center; justify-content: center;
  color: #fff; border: 2px solid var(--card); box-shadow: 0 2px 5px -2px rgba(60,30,15,.5); }
.rr-notif:hover .rr-nk { border-color: var(--surface-2); }
.rr-nk svg { width: 11px; height: 11px; }
.rr-nk-article { background: var(--primary); }
.rr-nk-reply   { background: #3b9c8f; }
.rr-nk-mention { background: #7c6ce0; }
.rr-nk-like    { background: #e0556f; }
.rr-nk-status  { background: #e0a13a; }
.rr-nk-follow  { background: #3d7fd6; }

.rr-notif .tx { flex: 1; min-width: 0; }
.rr-notif .hd { font-size: var(--t-body-sm); color: var(--text); line-height: 1.45; }
.rr-notif .hd b { font-weight: 700; }
.rr-notif .ex { color: var(--muted); font-size: var(--t-body-sm); margin-top: 3px;
  font-family: var(--font-hanzi-serif); line-height: 1.5; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }

/* Time + unread dot, pinned top-right. */
.rr-notif-meta { flex: none; display: flex; align-items: center; gap: 8px; padding-top: 2px; }
.rr-notif .tm { color: var(--muted); font-size: var(--t-caption); white-space: nowrap; }
.rr-notif-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--primary);
  flex: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent); }

/* Empty state. */
.rr-notif-empty { text-align: center; color: var(--muted); font-family: var(--font-display);
  font-size: var(--t-lead); padding: 64px 0; }
.rr-notif-empty-ic { display: inline-flex; align-items: center; justify-content: center;
  width: 60px; height: 60px; margin: 0 auto 16px; border-radius: 50%;
  background: var(--surface-2); color: var(--primary); }
.rr-notif-empty-ic svg { width: 28px; height: 28px; }

@media (max-width: 520px) {
  .rr-notifs { padding: 24px 14px 120px; }
  .rr-notif { padding: 14px 14px; gap: 12px; }
  .rr-notif .rr-cav { width: 40px; height: 40px; }
}

/* ── follow ───────────────────────────────────────────────────────────────
   The like pops. If follow popped too, two different actions would feel like
   one (Duke, 2026-08-08). So the button itself stays quiet -- the label rolls
   over like a departure board -- and the celebration happens OUTSIDE it, as a
   small firework fired from the pill (Duke, 2026-08-09: "not stay inside the
   button... some amazing 3d animation, like some small fireworks").

   Both labels live in the button and share ONE grid cell, so the pill is as
   wide as the LONGER of them. Absolutely positioning the second label sized
   the pill to "Follow" and clipped "✓ Following" in half (Duke's screenshot,
   2026-08-09). */
/* Industry-standard follow button (Instagram/YouTube): a solid "Follow" that
   becomes a quiet outlined "✓ Following", and reads "Unfollow" on hover. The
   WORD carries the state -- the old icon-only eye was ambiguous (2026-08-09),
   and a lone tick read as "done". */
.rr-sq-follow { position: relative; overflow: hidden; margin-left: auto; flex: none;
  font-weight: 700; border-radius: 999px; padding: 5px 13px; line-height: 1.15;
  border: 1px solid var(--primary); background: var(--primary); color: #fff;
  transition: background .2s ease, color .2s ease, border-color .2s ease; }
.rr-sq-follow:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
/* Followed: quiet outline. Hover shifts it to an unfollow (danger) tone so the
   click's effect is unmistakable. */
/* Followed: a quiet, COMPACT icon pill -- just a ✓, so a long localized word
   ("Вы подписаны") can never stretch it into a bar. Hover shifts it to a ✕ in
   a danger tone so the unfollow is unmistakable. */
.rr-sq-follow.on { background: none; color: var(--text); border-color: var(--border);
  padding: 5px 9px; }
.rr-sq-follow.on:hover { background: none; color: var(--danger, #d9534f);
  border-color: color-mix(in srgb, var(--danger, #d9534f) 55%, transparent); }
/* One label shows at a time via display, so the pill sizes to whichever is
   visible -- the "Follow" word when not following, a single icon when it is.
   (The old grid-stack sized to the WIDEST of all three at once, which is what
   made the RU followed pill a giant bar.) */
.rr-sq-follow .lbl { display: inline-flex; align-items: center; justify-content: center; }
.rr-sq-follow .lbl i { display: none; align-items: center; justify-content: center;
  gap: 4px; white-space: nowrap; font-style: normal; }
.rr-sq-follow .lbl .a { display: inline-flex; }        /* not following: the word */
.rr-sq-follow.on .lbl .a { display: none; }
.rr-sq-follow.on .lbl .b { display: inline-flex; }     /* following: ✓ only */
.rr-sq-follow.on:hover .lbl .b { display: none; }
.rr-sq-follow.on:hover .lbl .c { display: inline-flex; } /* hover: ✕ (unfollow) */
.rr-sq-follow .rr-fk { width: 15px; height: 15px; fill: none; stroke: currentColor;
  stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }

/* ---- Link unfurl card (Square) ------------------------------------------- */
.rr-sq-link-slot:empty { display: none; }
.rr-sq-link { display: block; margin: 11px 0 2px; border: 1px solid var(--border);
  border-radius: 15px; overflow: hidden; text-decoration: none; color: inherit;
  background: var(--surface-2); transition: border-color .15s, background .15s; }
a.rr-sq-link:hover { border-color: color-mix(in srgb, var(--primary) 40%, var(--border));
  background: var(--surface); }
.rr-sq-link-media { position: relative; aspect-ratio: 16 / 9; background: #000;
  overflow: hidden; }
.rr-sq-link-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.rr-sq-link-meta { padding: 10px 13px; }
.rr-sq-link-site { font-size: var(--t-caption); color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em; font-weight: 700; }
.rr-sq-link-title { font-size: var(--t-body); font-weight: 700; line-height: 1.35;
  margin-top: 2px; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.rr-sq-link-desc { font-size: var(--t-caption); color: var(--muted); line-height: 1.45;
  margin-top: 4px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* A link with no image is a compact chip, not a big empty card. */
.rr-sq-link-bare { background: var(--surface-2); }
.rr-sq-link-bare .rr-sq-link-title { -webkit-line-clamp: 1; }
/* YouTube: a play badge over the thumbnail; click swaps in the embed. */
.rr-sq-link-yt { cursor: pointer; }
.rr-sq-play { position: absolute; inset: 0; margin: auto; width: 54px; height: 54px;
  border: none; border-radius: 999px; cursor: pointer;
  background: rgba(0, 0, 0, .55); color: #fff; display: grid; place-items: center;
  transition: background .15s, transform .15s; }
.rr-sq-link-yt:hover .rr-sq-play { background: var(--primary); transform: scale(1.06); }
.rr-sq-play svg { width: 26px; height: 26px; fill: currentColor; margin-left: 3px; }
.rr-sq-yt-frame { width: 100%; height: 100%; border: 0; display: block; }

/* The firework. Fired into <body> so no card's overflow can clip it, and
   given a perspective so the shards genuinely tumble through depth rather
   than spinning flat. */
.rr-burst { position: fixed; z-index: 90; width: 0; height: 0;
  pointer-events: none; perspective: 520px; transform-style: preserve-3d; }
.rr-burst b { position: absolute; left: 0; top: 0; width: 26px; height: 26px;
  margin: -13px 0 0 -13px; border-radius: 50%; border: 2px solid var(--primary);
  animation: rr-burst-ring .6s cubic-bezier(.2, .7, .3, 1) forwards; }
.rr-burst i { position: absolute; left: 0; top: 0; display: block;
  width: var(--w); height: var(--h);
  margin: calc(var(--h) / -2) 0 0 calc(var(--w) / -2);
  background: var(--c); border-radius: 2px; opacity: 0;
  animation: rr-shard var(--d) cubic-bezier(.16, .62, .38, 1) forwards; }
@keyframes rr-burst-ring {
  from { transform: scale(.25); opacity: .85; }
  to   { transform: scale(3.1); opacity: 0; }
}
@keyframes rr-shard {
  0%   { transform: translate3d(0, 0, 0) scale(.35); opacity: 0; }
  12%  { opacity: 1; }
  55%  { transform: translate3d(calc(var(--x) * .64), var(--y1), var(--z))
                    rotateX(calc(var(--rx) * .55)) rotateY(calc(var(--ry) * .55))
                    rotateZ(calc(var(--rz) * .55)) scale(1); opacity: 1; }
  100% { transform: translate3d(var(--x), var(--y2), 0)
                    rotateX(var(--rx)) rotateY(var(--ry)) rotateZ(var(--rz))
                    scale(.7); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .rr-burst { display: none; }
}

/* ── landing on the exact comment from a notification ─────────────────── */
.rr-citem.rr-landed { animation: rr-land 3s ease forwards; border-radius: 14px; }
@keyframes rr-land {
  0%   { background: color-mix(in srgb, var(--primary) 26%, transparent);
         box-shadow: 0 0 0 6px color-mix(in srgb, var(--primary) 12%, transparent); }
  22%  { background: color-mix(in srgb, var(--primary) 20%, transparent);
         box-shadow: 0 0 0 6px color-mix(in srgb, var(--primary) 9%, transparent); }
  100% { background: transparent; box-shadow: 0 0 0 6px transparent; }
}
/* The reply chip inside a Square card's reply box. */
/* The "replying to X" line sits ABOVE the row on its own line. It is a flex
   item of the form, so it needs the 100% basis or it squeezes in beside the
   avatar -- which is exactly what it did (Duke's screenshot, 2026-08-08). */
.rr-sq-reply { flex-wrap: wrap; }
.rr-sq-replyto { flex: 1 0 100%; display: flex; align-items: center; gap: 8px;
  margin: 0; padding: 0 0 2px; font-size: var(--t-caption); color: var(--muted); }
.rr-sq-replyto[hidden] { display: none; }
.rr-sq-replyto .x { background: none; border: 0; padding: 0; color: var(--muted);
  font: inherit; font-size: var(--t-caption); text-decoration: underline; cursor: pointer; }
.rr-sq-replyto .x:hover { color: var(--text); }

@media (prefers-reduced-motion: reduce) {
  .rr-sq-follow.pop, .rr-sq-follow.pop::after { animation: none; }
  .rr-citem.rr-landed { animation: rr-land 1.6s ease forwards; }
}


/* ── the nav follows the scroll ───────────────────────────────────────────
   On blog pages only: the place-switcher stays reachable all the way down a
   long article, so you can leave for the Square without scrolling back up.
   Blurred rather than solid so the hero photograph still reads underneath. */
body.blog-page .nc-app-bar-full {
  position: sticky; top: 0; z-index: 60;
  padding-bottom: 12px;
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(14px) saturate(1.25);
  -webkit-backdrop-filter: blur(14px) saturate(1.25);
}

/* ── phone: the brand header scrolls away, the floating pill stays ─────────
   Duke, 2026-08-13: the tab switcher is a FLOATING PILL now (see style.css
   .nc-tabs), a sibling that sticks to the top on its own. So on the phone the
   brand header no longer needs to pin -- it scrolls off with the page, leaving
   just the pill hovering over the content. (On a wide screen the header stays
   sticky and the pill floats centred over it -- untouched here.) */
@media (max-width: 860px) {
  body.blog-page .nc-app-bar-full {
    position: static;
    background: transparent;
    backdrop-filter: none; -webkit-backdrop-filter: none;
  }
}
/* ── the back control ─────────────────────────────────────────────────────
   The shared .nc-back circle, sized to sit beside the wordmark rather than
   dominate it. */
body.blog-page .nc-back {
  width: 34px; height: 34px; font-size: 1rem;
  background: color-mix(in srgb, var(--card) 70%, transparent);
  transition: border-color .15s, transform .15s, background .15s;
}
body.blog-page .nc-back:hover { transform: translateX(-2px); background: var(--card); }


/* ── search: a button in the bar, a compact popover under it ───────────────
   The switcher is absolutely centred on the page. An inline search box used to
   fight it for the middle of the bar; now search is a magnifier BUTTON in the
   right-hand cluster, and a small card unfolds directly beneath it -- so the
   switcher stays centred and the search stays out of the way until you want it
   (Duke, 2026-08-11: not a giant full-width bar, a dynamic pop from the icon).
   The panel anchors to its .rr-searchwrap wrapper, not the header, so it tracks
   the icon; on phones the wrapper goes static and the panel spans the bar. */
.rr-searchtoggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex: none; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border); background: var(--card); color: var(--muted);
  transition: color .15s, border-color .15s, background .15s; }
.rr-searchtoggle svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 2; }
.rr-searchtoggle:hover { color: var(--text);
  border-color: color-mix(in srgb, var(--primary) 45%, var(--border)); }
.rr-searchtoggle[aria-expanded="true"] { color: var(--primary); border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 10%, var(--card)); }

/* The wrapper is the anchor: the panel is positioned against IT, so it drops
   directly under the magnifier rather than across the whole bar. */
.rr-searchwrap { position: relative; display: inline-flex; }

/* A compact card that UNFOLDS from the icon -- it scales up from its top-right
   corner (right under the button) with a small spring, so it reads as popping
   out of the magnifier, not a full-width bar sliding down (Duke, 2026-08-11). */
.rr-searchpanel {
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 55;
  width: min(400px, calc(100vw - 28px)); padding: 8px;
  background: var(--card); border: 1px solid var(--border); border-radius: 16px;
  box-shadow: 0 22px 44px -20px rgba(60, 30, 15, .55), 0 3px 10px rgba(60, 30, 15, .10);
  transform-origin: top right; transform: scale(.9) translateY(-6px);
  opacity: 0; visibility: hidden;
  transition: opacity .16s ease, transform .22s cubic-bezier(.34, 1.4, .5, 1), visibility .22s; }
.rr-searchpanel.is-open { opacity: 1; visibility: visible; transform: scale(1) translateY(0); }
/* No caret/tail. On the dark theme a small dark diamond above a dark panel just
   read as a black smudge next to the button (Duke, 2026-08-18). The panel grows
   out of the icon's corner (transform-origin: top right) and sits right beneath
   it, so it already feels tethered without one. */
.rr-searchpanel-form {
  display: flex; align-items: center; gap: 8px;
  background: var(--surface-2); border: 1px solid transparent; border-radius: 11px;
  padding: 5px 5px 5px 14px; transition: background .15s, border-color .15s, box-shadow .15s; }
.rr-searchpanel-form:focus-within { background: var(--bg); border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 16%, transparent); }
.rr-searchpanel-form > svg { width: 18px; height: 18px; flex: none;
  fill: none; stroke: var(--muted); stroke-width: 2; }
.rr-searchpanel-form input { flex: 1; min-width: 0; border: 0; background: none;
  font: inherit; font-size: var(--t-body-sm); color: var(--text); outline: none; padding: 7px 0; }
.rr-searchpanel-form input::-webkit-search-cancel-button { -webkit-appearance: none; }
.rr-searchpanel-go { flex: none; border: 0; border-radius: 9px; cursor: pointer;
  background: var(--primary); color: #fff; font: inherit; font-weight: 600;
  font-size: var(--t-caption); padding: 8px 15px; transition: background .15s, transform .1s; }
.rr-searchpanel-go:hover { background: var(--primary-dark); }
.rr-searchpanel-go:active { transform: translateY(1px); }
@media (prefers-reduced-motion: reduce) {
  .rr-searchpanel { transition: opacity .01s, visibility .01s; transform: none; }
  .rr-searchpanel.is-open { transform: none; }
}
/* Phones: the magnifier sits mid-bar, so a card anchored to its right edge
   would run off the left of the screen. Detach the panel from the icon wrapper
   (static wrapper -> panel falls back to the sticky header) and let it span the
   bar with a small margin, popping from the top. */
@media (max-width: 560px) {
  body.blog-page .rr-searchwrap { position: static; }
  body.blog-page .rr-searchpanel { left: 12px; right: 12px; width: auto;
    transform-origin: top center; }
}


/* ── hidden means hidden ──────────────────────────────────────────────────
   `.rr-replyto { display: flex }` and `[hidden] { display: none }` have the
   SAME specificity, so the later stylesheet won -- and the reply-to bar showed
   on every composer with nothing in it but its own "cancel" link. That is the
   stray cancel Duke reported twice, on two different surfaces, because both
   composers share the class (2026-08-09). One guard, so no future `display:`
   rule can resurrect a hidden element anywhere on these pages. */
[hidden] { display: none; }

/* ── nobody has commented yet ─────────────────────────────────────────────
   It was one grey sentence. An empty thread is the moment to ask for the
   comment, not to state a fact -- so: the looping writing animation and one
   light, inviting line. Nothing else. */
.rr-cempty { display: flex; flex-direction: column; align-items: center;
  text-align: center; gap: 12px; padding: 30px 20px 28px; margin-top: 6px;
  border: 1px dashed color-mix(in srgb, var(--border) 90%, transparent);
  border-radius: 18px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--primary) 5%, transparent), transparent 70%); }
/* A professionally-animated pencil writing a note, looped as a WebP (see the
   template). The image IS the animation, so all we style is its size. */
.rr-cempty .rr-writing { width: 150px; height: auto; flex: none; display: block; }
.rr-cempty-line { margin: 0; font-family: var(--font-display); font-weight: 600;
  font-size: var(--t-title-sm); line-height: 1.4; color: var(--text); max-width: 32ch; }
/* A looping WebP can't honour prefers-reduced-motion in CSS; the file is
   short and gentle, and users who need stillness get it via the OS image
   settings. Nothing to disable here. */


/* ── back, hanging off the switcher's left edge ───────────────────────────
   It is ABSOLUTE, not a flex child, so the three pills stay centred on the
   page (adding it to the flow shifted them -- Duke, 2026-08-09). It is its own
   little pill floating just left of the island, and it only appears once the
   bar is stuck to the top and travelling with the scroll: at rest the header
   is just the three tabs. */
.rr-navpills .rr-tab-back {
  position: absolute; right: calc(100% + 8px); top: 50%;
  transform: translateY(-50%) translateX(-4px);
  padding: 7px 10px; border-radius: 999px; color: var(--muted);
  background: var(--surface-2);
  border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  opacity: 0; pointer-events: none;
  transition: opacity .25s ease, transform .25s ease, color .15s ease; }
.nc-app-bar-full.is-stuck .rr-navpills .rr-tab-back {
  opacity: 1; pointer-events: auto; transform: translateY(-50%) translateX(0); }
.rr-navpills .rr-tab-back svg { width: 17px; height: 17px; stroke-width: 2; display: block;
  transition: transform .18s ease; }
.rr-navpills .rr-tab-back:hover { color: var(--text); }
.rr-navpills .rr-tab-back:hover svg { transform: translateX(-2px); }

/* ===== Square profiles, people lists & share (Phase 1, 2026-08-11) ========= */

/* Names, avatars and timestamps link through to a profile / permalink. */
a.rr-sq-nlink { color: inherit; text-decoration: none; }
a.rr-sq-nlink:hover { text-decoration: underline; }
a.rr-cav { text-decoration: none; color: #fff; }
.rr-sq-head a.rr-cav:hover { filter: brightness(1.07); }
a.rr-sq-when { color: var(--muted); text-decoration: none; }
a.rr-sq-when:hover { text-decoration: underline; }

/* Profile header */
.rr-pf { background: var(--card); border: 1px solid var(--border); border-radius: 18px;
  overflow: hidden; margin: 14px 0 18px; }
.rr-pf-cover { position: relative; height: 150px; background: linear-gradient(115deg, var(--primary-dark),
  var(--primary) 62%, color-mix(in srgb, var(--primary) 55%, #fff)); }
/* An uploaded / desk-art cover fills the band, centre-cropped. */
.rr-pf-cover.has-img { background-size: cover; background-position: center; background-repeat: no-repeat; }
/* "Change cover" -- a translucent pill in the corner of the band, over the
   owner's own header only. Glassy so it reads on any photo. */
.rr-pf-cover-edit {
  position: absolute; right: 12px; bottom: 12px; z-index: 2;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px 7px 10px; border-radius: 999px; cursor: pointer;
  border: 1px solid rgba(255, 255, 255, .5); color: #fff;
  background: rgba(30, 20, 14, .38); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  font-family: var(--font-sans); font-weight: 600; font-size: var(--t-caption);
  transition: background .15s ease, transform .12s ease; }
.rr-pf-cover-edit:hover { background: rgba(30, 20, 14, .55); }
.rr-pf-cover-edit:active { transform: translateY(1px); }
.rr-pf-cover-edit svg { width: 16px; height: 16px; }
@media (max-width: 420px) { .rr-pf-cover-edit span { display: none; } .rr-pf-cover-edit { padding: 8px; } }
/* Sit above the cover: the cover is position:relative now (to anchor its edit
   button), which paints it over static siblings -- so without this the avatar's
   overlap gets clipped behind the banner's bottom edge. */
.rr-pf-body { position: relative; z-index: 1; padding: 0 20px 20px; margin-top: -34px; }
.rr-pf-top { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; }
.rr-pf-av { width: 76px; height: 76px; border-radius: 50%; border: 3px solid var(--card);
  background: linear-gradient(150deg, #ff9a6f, #d9552f); background-size: cover;
  background-position: center; color: #fff; display: grid; place-items: center;
  font-family: var(--font-hanzi-serif); font-size: 1.85rem; font-weight: 700; }
.rr-pf-actions { padding-bottom: 5px; }
.rr-pf-edit { border: 1px solid var(--border); background: var(--card); color: var(--text);
  font-weight: 700; border-radius: 999px; padding: 7px 15px; cursor: pointer; font-size: var(--t-caption); }
.rr-pf-edit:hover { border-color: var(--primary); color: var(--primary-dark); }
.rr-pf-name { font-family: var(--font-display); font-size: 1.5rem; font-weight: 700;
  margin: 12px 0 2px; display: flex; align-items: center; gap: 9px; text-wrap: balance; }
.rr-pf-fire { font-size: .8rem; color: var(--primary-dark); font-weight: 700; }
.rr-pf-handle { color: var(--muted); font-size: var(--t-caption);
  font-family: ui-monospace, "SF Mono", Menlo, monospace; }
.rr-pf-bio { margin: 12px 0 8px; font-size: var(--t-body); line-height: 1.55; color: var(--text);
  white-space: pre-wrap; word-wrap: break-word; max-width: 60ch; }
.rr-pf-link { display: inline-flex; align-items: center; gap: 6px; color: var(--primary-dark);
  text-decoration: none; font-size: var(--t-caption); font-weight: 600; }
.rr-pf-link svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round; }
.rr-pf-link:hover { text-decoration: underline; }
.rr-pf-stats { display: flex; gap: 24px; margin-top: 15px; padding-top: 14px; border-top: 1px solid var(--border); }
.rr-pf-stat { color: var(--muted); font-size: var(--t-caption); text-decoration: none; }
a.rr-pf-stat:hover { color: var(--text); }
.rr-pf-stat b { color: var(--text); font-weight: 700; font-size: 1.05rem; margin-right: 4px; }

/* Edit-profile form */
.rr-pf-editform { background: var(--card); border: 1px solid var(--border); border-radius: 16px;
  padding: 14px; margin-bottom: 18px; display: flex; flex-direction: column; gap: 10px; }
.rr-pf-editform textarea, .rr-pf-editform input { width: 100%; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; color: var(--text);
  font: inherit; font-size: var(--t-body); resize: vertical; }
.rr-pf-editform textarea:focus, .rr-pf-editform input:focus { outline: none; border-color: var(--primary); }
.rr-pf-editrow { display: flex; justify-content: flex-end; gap: 10px; align-items: center; }
.rr-pf-cancel { background: none; border: none; color: var(--muted); cursor: pointer; font: inherit; padding: 8px 12px; }

/* ── Cover-photo crop modal (owner only) ──────────────────────────────────── */
/* Above the install-to-home-screen nudge (z-index 9000), so opening the cover
   editor is never half-hidden behind it. */
.rr-cover-modal { position: fixed; inset: 0; z-index: 9500; display: flex;
  align-items: center; justify-content: center; padding: 16px; }
.rr-cover-modal[hidden] { display: none; }
.rr-cover-backdrop { position: absolute; inset: 0; background: rgba(20, 12, 8, .55);
  opacity: 0; transition: opacity .25s ease; }
.rr-cover-modal.open .rr-cover-backdrop { opacity: 1; }
.rr-cover-sheet { position: relative; width: 100%; max-width: 520px;
  background: var(--card); border: 1px solid var(--border); border-radius: 20px;
  padding: 18px; box-shadow: 0 24px 60px -20px rgba(40, 20, 10, .6);
  transform: translateY(12px) scale(.98); opacity: 0;
  transition: transform .28s cubic-bezier(.2, .9, .25, 1), opacity .2s ease; }
.rr-cover-modal.open .rr-cover-sheet { transform: none; opacity: 1; }
.rr-cover-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.rr-cover-head h2 { font-family: var(--font-display); font-size: 1.2rem; font-weight: 700; margin: 0; }
.rr-cover-x { border: 0; background: none; color: var(--muted); font-size: 1.6rem; line-height: 1;
  cursor: pointer; padding: 2px 6px; border-radius: 8px; }
.rr-cover-x:hover { color: var(--text); background: var(--surface-2); }
/* chooser: a preview of the current banner + the pick / remove buttons */
.rr-cover-current { height: 120px; border-radius: 14px; overflow: hidden; margin-bottom: 14px;
  background: linear-gradient(115deg, var(--primary-dark), var(--primary) 62%,
    color-mix(in srgb, var(--primary) 55%, #fff)); }
.rr-cover-current img { width: 100%; height: 100%; object-fit: cover; display: block; }
.rr-cover-empty { display: block; width: 100%; height: 100%; }
.rr-cover-choose-row { display: flex; align-items: center; gap: 12px; }
.rr-cover-remove { border: 0; background: none; color: var(--muted); cursor: pointer;
  font-family: inherit; font-size: var(--t-label); font-weight: 600; padding: 8px 6px; }
.rr-cover-remove:hover { color: var(--danger, #c0392b); }
/* cropper: the 3:1 stage + zoom slider */
.rr-cover-stage { border-radius: 14px; overflow: hidden; background: var(--surface-2);
  border: 1px solid var(--border); touch-action: none; }
.rr-cover-stage canvas { display: block; width: 100%; height: auto; aspect-ratio: 3 / 1; cursor: grab; }
.rr-cover-stage canvas:active { cursor: grabbing; }
.rr-cover-zoom { width: 100%; margin: 14px 0 4px; accent-color: var(--primary); }
.rr-cover-hint { color: var(--muted); font-size: var(--t-caption); margin: 0 0 14px; text-align: center; }
.rr-cover-actions { display: flex; justify-content: flex-end; gap: 10px; align-items: center; }
.rr-cover-back { border: 0; background: none; color: var(--muted); cursor: pointer; font: inherit; padding: 8px 12px; }
.rr-cover-back:hover { color: var(--text); }
.rr-cover-err { color: var(--danger, #c0392b); font-size: var(--t-caption); margin: 12px 0 0; text-align: center; }
@media (prefers-reduced-motion: reduce) {
  .rr-cover-backdrop, .rr-cover-sheet { transition: opacity .15s ease; }
  .rr-cover-sheet { transform: none; }
}
/* Avatar cropper reuses the cover-modal shell; its stage is square with a
   circular window (the ring dims the corners so the crop reads as a circle). */
.rr-cover-stage.rr-av-stage { position: relative; max-width: 260px; margin: 0 auto; }
.rr-av-stage canvas { aspect-ratio: 1 / 1; }
.rr-av-ring { position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  box-shadow: 0 0 0 120px rgba(30, 20, 14, .34); }
.rr-av-current { width: 96px; height: 96px; border-radius: 50%; overflow: hidden; margin: 0 auto 16px;
  background: var(--surface-2); display: grid; place-items: center; }
/* The avatar preview is centred, so centre its button pair too -- left-aligned
   buttons under a centred photo read as misaligned. Scoped to the avatar modal:
   the cover modal keeps left-aligned buttons under its full-width preview. */
.rr-av-modal .rr-cover-choose-row { justify-content: center; }
.rr-av-current img { width: 100%; height: 100%; object-fit: cover; display: block; }
.rr-av-current-initial { font-family: var(--font-display); font-size: 2.4rem; font-weight: 700; color: var(--primary-dark); }

/* ── Edit-profile sheet ───────────────────────────────────────────────────
   One screen for banner + photo + name + handle + bio + link, the layout a
   mainstream social profile uses. Sits below the two croppers (9500) it opens. */
.rr-noscroll { overflow: hidden; }
.rr-edit { position: fixed; inset: 0; z-index: 9400; display: flex;
  align-items: center; justify-content: center; padding: 20px; }
.rr-edit[hidden] { display: none; }
.rr-edit-backdrop { position: absolute; inset: 0; background: rgba(20, 12, 8, .55);
  opacity: 0; transition: opacity .26s ease; }
.rr-edit.open .rr-edit-backdrop { opacity: 1; }
.rr-edit-sheet { position: relative; width: 100%; max-width: 540px; max-height: min(88vh, 760px);
  background: var(--card); border: 1px solid var(--border); border-radius: 20px; overflow: hidden;
  display: flex; flex-direction: column;
  box-shadow: 0 24px 60px -20px rgba(40, 20, 10, .5);
  transform: translateY(14px) scale(.98); opacity: 0; transition: transform .26s cubic-bezier(.2,.7,.3,1), opacity .26s ease; }
.rr-edit.open .rr-edit-sheet { transform: none; opacity: 1; }
.rr-edit-bar { display: grid; grid-template-columns: 40px 1fr auto; align-items: center; gap: 8px;
  padding: 11px 14px; border-bottom: 1px solid var(--border); flex: 0 0 auto;
  background: color-mix(in srgb, var(--card) 86%, transparent); backdrop-filter: blur(6px); }
.rr-edit-bar h2 { font-family: var(--font-display); font-size: 1.05rem; font-weight: 700; margin: 0; text-align: center; }
.rr-edit-x { width: 34px; height: 34px; border: 0; background: none; border-radius: 50%; cursor: pointer;
  color: var(--text); display: grid; place-items: center; }
.rr-edit-x svg { width: 20px; height: 20px; }
.rr-edit-x:hover { background: var(--surface-2); }
.rr-edit-save { border: 0; background: var(--primary); color: #fff; font-weight: 600;
  font-size: var(--t-label); padding: 8px 18px; border-radius: 999px; cursor: pointer; min-width: 74px; }
.rr-edit-save:hover { background: var(--primary-dark); }
.rr-edit-save:disabled { opacity: .55; cursor: default; }
.rr-edit-scroll { overflow-y: auto; -webkit-overflow-scrolling: touch; }

.rr-edit-banner { position: relative; height: 128px;
  background: linear-gradient(115deg, var(--primary-dark), var(--primary)); }
.rr-edit-banner.has-img { background-size: cover; background-position: center; background-repeat: no-repeat; }
.rr-edit-cam { position: absolute; border: 0; cursor: pointer; display: grid; place-items: center;
  color: #fff; background: rgba(30, 20, 14, .42); border-radius: 50%;
  box-shadow: 0 1px 8px rgba(0,0,0,.28); transition: background .15s ease, transform .1s ease; }
.rr-edit-cam:hover { background: rgba(30, 20, 14, .62); }
.rr-edit-cam:active { transform: scale(.94); }
.rr-edit-cam svg { width: 20px; height: 20px; }
.rr-edit-cam-cover { top: 50%; left: 50%; transform: translate(-50%, -50%); width: 46px; height: 46px; }
.rr-edit-cam-cover:active { transform: translate(-50%, -50%) scale(.94); }
.rr-edit-av { position: absolute; left: 20px; bottom: -34px; width: 88px; height: 88px; border-radius: 50%;
  border: 4px solid var(--card); background: var(--primary-dark) center/cover no-repeat;
  display: grid; place-items: center; }
.rr-edit-av-initial { font-family: var(--font-display); color: #fff; font-size: 2rem; font-weight: 700; }
.rr-edit-cam-av { right: -2px; bottom: -2px; width: 32px; height: 32px; border: 2px solid var(--card); }
.rr-edit-cam-av svg { width: 15px; height: 15px; }

.rr-edit-fields { padding: 46px 20px 22px; display: flex; flex-direction: column; gap: 16px; }
.rr-edit-namerow { display: flex; gap: 12px; }
.rr-edit-namerow .rr-field { flex: 1; min-width: 0; }
.rr-field { display: flex; flex-direction: column; gap: 6px; }
.rr-field-label { font-size: var(--t-caption); font-weight: 600; color: var(--muted); }
.rr-field input, .rr-field textarea { width: 100%; background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 12px; padding: 11px 13px; font: inherit;
  font-size: var(--t-body); transition: border-color .15s ease, box-shadow .15s ease; }
.rr-field textarea { resize: vertical; min-height: 74px; line-height: 1.5; }
.rr-field input:focus, .rr-field textarea:focus { outline: none; border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent); }
.rr-field input.is-bad { border-color: var(--danger, #c0392b); }

/* ── Animated status: profile header trigger + Telegram-style popover ───── */
/* Bigger next to the profile's H1 name than in the feed. */
.rr-pf-name .rr-pf-status { width: 26px; height: 26px; font-size: 22px; vertical-align: -4px; }

/* The inline trigger by the name. When a status is set it's just the badge in a
   tap target; when empty it's a small "+ Status" pill inviting a choice. */
.rr-pf-statusbtn { border: none; background: none; padding: 0; cursor: pointer; font: inherit;
  vertical-align: middle; display: inline-flex; align-items: center; border-radius: 999px; }
.rr-pf-statusbtn:not(.is-empty) { padding: 2px; }
.rr-pf-statusbtn.is-empty { background: color-mix(in srgb, var(--primary) 12%, transparent);
  padding: 4px 11px 4px 9px; }
.rr-pf-statusbtn.is-empty:hover { background: color-mix(in srgb, var(--primary) 20%, transparent); }
.rr-pf-addstatus { display: inline-flex; align-items: center; gap: 3px;
  font-family: var(--font-sans); font-size: var(--t-caption); font-weight: 700;
  color: var(--primary-dark); letter-spacing: 0; }
.rr-pf-addplus { font-size: 1.15em; line-height: 1; font-weight: 800; }

/* The bubble: fixed under the name, positioned in viewport coords by JS (so a
   positioned ancestor can't throw off the maths). */
.rr-statuspop { position: fixed; z-index: 60; width: min(340px, calc(100vw - 24px));
  background: var(--card); border: 1px solid var(--border); border-radius: 16px;
  box-shadow: 0 14px 40px rgba(0,0,0,.22); padding: 10px; }
.rr-statuspop-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
  gap: 3px; max-height: 264px; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.rr-status-opt { aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  font-size: 26px; line-height: 1; border: none; background: transparent; border-radius: 10px;
  cursor: pointer; padding: 0; transition: background .12s ease, transform .12s ease; }
.rr-status-opt:hover { background: color-mix(in srgb, var(--primary) 12%, transparent); transform: scale(1.14); }
.rr-status-opt.is-on { background: color-mix(in srgb, var(--primary) 20%, transparent);
  box-shadow: inset 0 0 0 2px var(--primary); }
/* The teacher badge leads the grid, set apart as the special one. */
.rr-status-opt.rr-status-teacher { grid-column: 1 / -1; aspect-ratio: auto; gap: 8px;
  padding: 7px 10px; margin-bottom: 4px; justify-content: flex-start; font-size: 22px;
  background: color-mix(in srgb, var(--gold, #d9a441) 16%, transparent); font-weight: 700; }
.rr-status-opt.rr-status-teacher .rr-cb { width: 26px; height: 26px; flex: none; }
.rr-status-opt.rr-status-teacher::after { content: attr(title); font-family: var(--font-sans);
  font-size: var(--t-body-sm); font-weight: 700; color: var(--text); }
.rr-status-opt.rr-status-teacher:hover { transform: none; background: color-mix(in srgb, var(--gold, #d9a441) 26%, transparent); }
.rr-statuspop-remove { display: block; width: 100%; margin-top: 8px; padding: 9px; border: none;
  background: var(--surface-2); color: var(--danger, #c0392b); border-radius: 11px; cursor: pointer;
  font: inherit; font-size: var(--t-body-sm); font-weight: 600; }
.rr-statuspop-remove:hover { background: color-mix(in srgb, var(--danger, #c0392b) 12%, transparent); }
.rr-statuspop-remove[hidden] { display: none; }   /* the display:block above would otherwise defeat [hidden] */
.rr-field-handle { display: flex; align-items: center; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 12px; padding-left: 12px;
  transition: border-color .15s ease, box-shadow .15s ease; }
.rr-field-handle:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent); }
.rr-field-at { color: var(--muted); font-size: var(--t-body); }
.rr-field-handle input { border: 0; background: none; border-radius: 0; padding-left: 4px; }
.rr-field-handle input:focus { box-shadow: none; }
.rr-uname-status { font-size: var(--t-caption); color: var(--muted); margin: 0; min-height: 1.1em; }
.rr-uname-status.is-ok { color: var(--success, #2f9e5e); }
.rr-uname-status.is-bad { color: var(--danger, #c0392b); }
.rr-field-count { font-size: var(--t-caption); color: var(--muted); text-align: right; margin-top: -2px; }
.rr-edit-err { color: var(--danger, #c0392b); font-size: var(--t-caption); margin: 0; text-align: center; }
@media (max-width: 460px) {
  .rr-edit { padding: 0; }
  .rr-edit-sheet { max-width: none; max-height: 100vh; height: 100%; border-radius: 0; border: 0; }
  .rr-edit-namerow { flex-direction: column; gap: 16px; }
}
@media (prefers-reduced-motion: reduce) {
  .rr-edit-backdrop, .rr-edit-sheet { transition: opacity .15s ease; }
  .rr-edit-sheet { transform: none; }
}

/* Followers / following list */
.rr-people-head { display: flex; flex-direction: column; gap: 3px; margin: 16px 2px 14px; }
.rr-people-head h1 { font-family: var(--font-display); font-size: 1.35rem; margin: 0; }
.rr-people { display: flex; flex-direction: column; }
.rr-person { display: flex; align-items: center; gap: 12px; padding: 11px 6px; border-bottom: 1px solid var(--border); }
.rr-person .rr-cav { width: 44px; height: 44px; font-size: 1.05rem; }
.rr-person-main { flex: 1; min-width: 0; text-decoration: none; color: inherit; }
.rr-person-name { font-weight: 700; display: block; }
.rr-person-bio { color: var(--muted); font-size: var(--t-caption); display: block;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Share button + "Link copied" toast */
.rr-sq-share { position: relative; }
.rr-sq-copied { position: absolute; bottom: 132%; left: 50%; transform: translateX(-50%);
  background: var(--text); color: var(--card); font-size: 11px; font-weight: 600;
  padding: 4px 9px; border-radius: 8px; white-space: nowrap; pointer-events: none; z-index: 5; }

/* Not-found / empty full pages */
.rr-empty-page { text-align: center; padding: 60px 20px; color: var(--muted); }
.rr-empty-page p { margin: 0 0 16px; }

/* A single post on its permalink sits a touch lower under the bar. */
.rr-permalink { margin-top: 14px; }

/* [hidden] must beat the flex display above (equal specificity, source order). */
.rr-pf-editform[hidden] { display: none; }

/* Pinned-post marker (profile only) */
.rr-sq-pinned { display: inline-flex; align-items: center; gap: 5px; font-size: var(--t-caption);
  font-weight: 700; color: var(--primary-dark); margin-bottom: 9px; }
.rr-sq-pinned svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round; }

/* [hidden] must beat the menu button display (pin vs unpin toggle). */
.rr-cmenu-pop button[hidden] { display: none; }


/* ===== Featured strip (admin-curated, 2026-08-11) ==========================
   A compact, swipeable ONE-ROW band at the top of the Square. Its height never
   grows with more pins -- extra items extend sideways and you scroll. Keeps
   curated content glanceable instead of burying the feed (Duke, 2026-08-11). */
.rr-noscroll { overflow: hidden; }

.rr-featured { margin: 6px 0 16px; }
.rr-featured-head { display: flex; align-items: center; gap: 8px; margin: 0 2px 9px; }
.rr-featured-eyebrow { display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-display); font-weight: 700; font-size: var(--t-caption);
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.rr-featured-eyebrow svg { width: 13px; height: 13px; fill: none; stroke: var(--primary);
  stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.rr-featured-manage { margin-left: auto; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: var(--t-caption); color: var(--primary-dark); font-weight: 700; }
.rr-featured-manage:hover { text-decoration: underline; }

/* overflow-x:auto silently makes overflow-y compute to auto too (CSS can't mix
   auto on one axis with visible on the other) -- so on hover, when a card lifts
   with translateY + a box-shadow, that shadow was getting clipped flush against
   the row's own height (Duke's screenshot, 2026-08-11). Padding on every side
   gives the lift + shadow somewhere to render into; margin can't do this since
   it sits outside the scroll container's clip box. */
.rr-featured-strip { display: flex; gap: 10px; overflow-x: auto;
  padding: 6px 2px 12px; margin: -6px -2px -12px;
  scrollbar-width: none; scroll-snap-type: x proximity; }
.rr-featured-strip::-webkit-scrollbar { display: none; }

.rr-fcard { scroll-snap-align: start; flex: 0 0 250px; display: flex; gap: 11px; align-items: center;
  padding: 9px 11px; border-radius: 13px; text-decoration: none; color: inherit;
  background: linear-gradient(180deg, color-mix(in srgb, var(--primary) 5%, transparent), transparent 72%), var(--card);
  border: 1px solid color-mix(in srgb, var(--primary) 22%, var(--border));
  transition: transform .14s ease, box-shadow .14s ease; }
.rr-fcard:hover { transform: translateY(-2px); box-shadow: 0 10px 24px -16px rgba(60,30,15,.5); }
.rr-fcard-thumb { flex: none; width: 54px; height: 54px; border-radius: 9px;
  background-size: cover; background-position: center; }
.rr-fcard-ic { flex: none; width: 54px; height: 54px; border-radius: 9px; display: grid; place-items: center;
  font-size: 23px; background: color-mix(in srgb, var(--primary) 12%, var(--card));
  border: 1px solid color-mix(in srgb, var(--primary) 20%, var(--border)); }
.rr-fcard-char { font-family: var(--font-hanzi-serif, var(--font-display)); font-weight: 700;
  font-size: 1.5rem; color: var(--primary-dark); }
.rr-fcard-body { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.rr-fcard-kicker { font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--primary-dark); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rr-fcard-title { font-family: var(--font-display); font-weight: 700; font-size: .95rem; line-height: 1.22;
  color: var(--text); overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.rr-fcard-meta { font-size: var(--t-caption); color: var(--muted); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.rr-fcard-meta.go { color: var(--primary-dark); font-weight: 600; }

/* Admin: the empty-state add button (only Duke sees it) */
.rr-featured-empty { display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  padding: 12px 16px; border-radius: 12px; font: inherit; font-size: var(--t-body-sm); color: var(--muted);
  background: none; border: 1px dashed color-mix(in srgb, var(--primary) 34%, var(--border)); }
.rr-featured-empty:hover { color: var(--text); border-color: var(--primary); }
.rr-featured-empty .p { color: var(--primary); font-weight: 800; font-size: 1.1rem; }

/* ── Admin Manage modal (admin-only; English chrome) ── */
.rr-fmodal { position: fixed; inset: 0; z-index: 220; background: rgba(0,0,0,.55);
  display: flex; align-items: flex-start; justify-content: center; padding: 5vh 16px; overflow-y: auto; }
.rr-fmodal[hidden] { display: none; }
.rr-fmodal-card { width: 100%; max-width: 480px; background: var(--card); border: 1px solid var(--border);
  border-radius: 18px; padding: 18px 18px 20px; box-shadow: 0 30px 60px -24px rgba(0,0,0,.6);
  display: flex; flex-direction: column; gap: 16px; }
.rr-fmodal-head { display: flex; align-items: center; }
.rr-fmodal-head h3 { margin: 0; font-family: var(--font-display); font-weight: 700; font-size: var(--t-title); color: var(--text); }
.rr-fmodal-x { margin-left: auto; background: none; border: 0; cursor: pointer; color: var(--muted);
  font-size: 26px; line-height: 1; padding: 0 4px; }
.rr-fmodal-x:hover { color: var(--text); }
.rr-fmodal-note { margin: -8px 0 0; font-size: var(--t-caption); color: var(--muted); }
.rr-fsec h4 { margin: 0 0 9px; font-family: var(--font-display); font-weight: 700; font-size: var(--t-title-sm);
  color: var(--text); display: flex; align-items: center; gap: 8px; }
.rr-fcount { font-family: var(--font-body, inherit); font-size: var(--t-caption); font-weight: 600; color: var(--muted); }

.rr-flist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.rr-flist li { display: flex; align-items: center; gap: 9px; }
.rr-fbadge { flex: none; font-size: 9.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  padding: 3px 7px; border-radius: 6px; color: var(--primary-dark);
  background: color-mix(in srgb, var(--primary) 12%, var(--card)); }
.rr-flabel { flex: 1; min-width: 0; font-size: var(--t-body-sm); color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rr-fremove { flex: none; border: 0; background: none; cursor: pointer; font: inherit; font-size: var(--t-caption);
  font-weight: 600; color: var(--muted); }
.rr-fremove:hover { color: #d9552f; }
.rr-fempty { margin: 0; font-size: var(--t-body-sm); color: var(--muted); }

.rr-farts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px;
  max-height: 190px; overflow-y: auto; }
.rr-farts li { display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: 9px;
  background: var(--surface-2); }
.rr-fart-meta { flex: none; font-size: 10px; font-weight: 700; color: var(--primary-dark); white-space: nowrap; }
.rr-fart-title { flex: 1; min-width: 0; font-size: var(--t-body-sm); color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rr-fart-pin { flex: none; border: 0; border-radius: 8px; cursor: pointer; background: var(--primary); color: #fff;
  font: inherit; font-weight: 700; font-size: var(--t-caption); padding: 5px 12px; }
.rr-fart-pin:hover { background: var(--primary-dark); }
.rr-fart-done { flex: none; font-size: var(--t-caption); font-weight: 600; color: var(--muted); }

.rr-fform { display: flex; flex-direction: column; gap: 9px; }
.rr-ftypes { display: flex; gap: 16px; font-size: var(--t-body-sm); color: var(--text); }
.rr-ftypes label { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.rr-fform input[type=text], .rr-fform input[type=date] { width: 100%; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 10px; padding: 9px 12px; color: var(--text); font: inherit; }
.rr-fform input[type=text]:focus, .rr-fform input[type=date]:focus { outline: none; border-color: var(--primary); }
.rr-frow { display: flex; gap: 9px; }
.rr-frow > * { flex: 1; min-width: 0; }
.rr-ffile, .rr-fexp { display: flex; flex-direction: column; gap: 4px; font-size: var(--t-caption); color: var(--muted); }
.rr-fhint { font-weight: 400; font-style: italic; opacity: .85; }
.rr-ffile input, .rr-fexp input { font-size: var(--t-body-sm); }
.rr-fform textarea { width: 100%; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 10px; padding: 9px 12px; color: var(--text); font: inherit; resize: vertical; min-height: 62px; }
.rr-fform textarea:focus { outline: none; border-color: var(--primary); }
.rr-fform-foot { display: flex; justify-content: flex-end; margin-top: 2px; }
@media (max-width: 480px) { .rr-frow { flex-direction: column; } }

/* ── Featured detail popup (built on .nc-modal: blurred backdrop + flip-in) ──
   The card a pinned item expands into: a hero, then kicker/title/subtitle/meta,
   the full text, and a prominent CTA. Aims premium -- this is where an article
   preview or an ad makes its case (Duke, 2026-08-11). */
.rr-fdetail-card { padding: 0; overflow: hidden; width: min(460px, calc(100% - 32px)); }
.rr-fdetail .nc-modal-close { background: rgba(0, 0, 0, .42); border-color: rgba(255, 255, 255, .28);
  color: #fff; -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
.rr-fdetail .nc-modal-close:hover { background: rgba(0, 0, 0, .6); border-color: rgba(255, 255, 255, .55); }
/* Any shape in, nothing cropped: a blurred/darkened copy of the image fills the
   band edge-to-edge (.rr-fdetail-hero-bg), the real image floats on top at its
   OWN aspect ratio via object-fit:contain. A square avatar, a tall portrait, a
   wide banner or a transparent logo all land centred and intact -- no forced
   zoom-crop chopping off a face (the bug in Duke's screenshot). */
.rr-fdetail-hero { position: relative; height: 210px; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2);
  border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); }
.rr-fdetail-hero-bg { position: absolute; inset: -20px; background-size: cover; background-position: center;
  filter: blur(26px) saturate(1.3) brightness(.6); transform: scale(1.15); }
.rr-fdetail-hero img { position: relative; z-index: 1; max-width: 88%; max-height: 88%;
  width: auto; height: auto; object-fit: contain; display: block; border-radius: 6px;
  box-shadow: 0 14px 32px -10px rgba(0, 0, 0, .45); }
.rr-fdetail-hero.noimg { height: 140px; border-bottom: 0;
  background:
    radial-gradient(120% 140% at 80% -10%, color-mix(in srgb, var(--primary) 30%, transparent), transparent 60%),
    linear-gradient(150deg, color-mix(in srgb, var(--primary) 16%, var(--card)), var(--card)); }
.rr-fdetail-hero.noimg .rr-fdetail-hero-bg { display: none; }
.rr-fdetail-char { font-family: var(--font-hanzi-serif, var(--font-display)); font-weight: 700;
  font-size: 3.4rem; line-height: 1; color: var(--primary-dark); }
.rr-fdetail-main { padding: 18px 24px 24px; display: flex; flex-direction: column; gap: 9px; }
.rr-fdetail-kicker { font-size: var(--t-caption); font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--primary-dark); }
.rr-fdetail-title { margin: 0; font-family: var(--font-display); font-weight: 700;
  font-size: var(--t-title); line-height: 1.25; color: var(--text); text-wrap: balance; }
.rr-fdetail-sub { margin: 0; font-style: italic; color: var(--muted); font-size: .98rem; line-height: 1.5; }
.rr-fdetail-meta { font-size: var(--t-caption); font-weight: 600; color: var(--muted); }
.rr-fdetail-text { color: var(--text); font-size: var(--t-body); line-height: 1.62; white-space: pre-line;
  margin-top: 2px; }
.rr-fdetail-cta { margin-top: 8px; align-self: flex-start; display: inline-flex; align-items: center;
  gap: 6px; background: var(--primary); color: #fff; text-decoration: none; font-weight: 700;
  font-size: var(--t-body-sm); padding: 11px 20px; border-radius: 12px;
  transition: background .15s ease, transform .12s ease; box-shadow: 0 8px 20px -10px color-mix(in srgb, var(--primary) 80%, transparent); }
.rr-fdetail-cta:hover { background: var(--primary-dark); transform: translateY(-1px); }
.rr-fdetail-cta:active { transform: translateY(0); }
/* [hidden] and this class both set `display` at equal specificity -- without
   this, source order wins and an empty CTA pill shows up on cards that have no
   link (the "orange dot" Duke spotted). Same trap as .rr-pf-editform[hidden]
   and .rr-cmenu-pop button[hidden] elsewhere in this file. */
.rr-fdetail-cta[hidden] { display: none; }
@media (max-width: 480px) {
  .rr-fdetail-hero { height: 170px; }
  .rr-fdetail-hero.noimg { height: 130px; }
  .rr-fdetail-main { padding: 16px 18px 20px; }
}

/* Pull-to-refresh. The installed PWA runs standalone, where iOS drops Safari's
   own pull-to-refresh, so the feed had no refresh gesture at all. The gesture
   logic lives in app.js, scoped to body.blog-page (browse pages only -- never a
   test or a flashcard session, where a reload loses your place).

   Like Telegram/Instagram, the CONTENT (.container) slides down under the
   finger and the disc sits CENTRED in the gap that opens (it descends at half
   the content's speed -- a parallax, so it never hugs the nav or drifts to the
   feed's edge). The ring DRAWS as you pull, glows when armed, then spins during
   the reload. Three layers keep their transforms from fighting: #rr-ptr does the
   parallax translate, .rr-ptr-disc does the scale/pop, the <svg> does the spin.

   Nesting/tuning history (2026-08-17): floating disc over a frozen page ->
   content now moves; spinner pinned to the very top -> now centred; static
   glyph -> drawing ring + glow + spin. */
body.blog-page { overscroll-behavior-y: contain; }
html:has(body.blog-page) { overscroll-behavior-y: none; }   /* stop the native bounce stacking on ours */
body.blog-page .container { position: relative; }
body.blog-page .container.rr-ptr-anim { transition: transform .34s cubic-bezier(.22, 1.2, .4, 1); }

#rr-ptr {
  position: absolute; z-index: 5;
  top: -22px; left: 50%; margin-left: -22px;
  width: 44px; height: 44px;
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
}
#rr-ptr.rr-ptr-anim { transition: transform .34s cubic-bezier(.22, 1.2, .4, 1), opacity .24s ease; }
.rr-ptr-disc {
  width: 100%; height: 100%;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--card);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  transform: scale(.55);
  will-change: transform;
}
#rr-ptr.rr-ptr-anim .rr-ptr-disc { transition: transform .34s cubic-bezier(.34, 1.56, .5, 1), box-shadow .2s ease; }
.rr-ptr-disc.on { border-color: var(--accent); box-shadow: 0 6px 20px var(--coral-glow); animation: rr-ptr-glow 1s ease-in-out infinite; }
@keyframes rr-ptr-glow {
  0%, 100% { box-shadow: 0 6px 18px var(--coral-glow); }
  50% { box-shadow: 0 6px 26px 2px var(--coral-glow); }
}
.rr-ptr-ring { width: 24px; height: 24px; display: block; }
.rr-ptr-ring circle { fill: none; stroke-width: 2.6; stroke-linecap: round;
  transform: rotate(-90deg); transform-origin: 50% 50%; }
.rr-ptr-ring .trk { stroke: color-mix(in srgb, var(--muted) 30%, transparent); }
.rr-ptr-ring .bar { stroke: var(--accent); stroke-dasharray: 56.5; stroke-dashoffset: 56.5; }
#rr-ptr.rr-ptr-go .rr-ptr-ring { animation: rr-ptr-rot .8s linear infinite; }
@keyframes rr-ptr-rot { to { transform: rotate(360deg); } }
