/* ==========================================================================
   theme-zhihu — shell: top bar, navigation, drawer, sidebar, footer, tab bar,
   search dialog, pager and shared page headers.
   ========================================================================== */

/* ---------- top bar ---------- */

.topbar {
  position: relative;
  background: var(--surface);
  border-bottom: 1px solid var(--divider);
  z-index: 50;
}

/* Frosted surfaces (appearance > 全局磨砂玻璃): nav bars, drawer and the search
   layer share one switch, so content scrolling underneath reads through as a
   soft blur. The search control inside the top bar has no second blur pass: it
   just tints the bar's existing backdrop, because blurring an already blurred
   layer flattens it into solid grey. Saturation is kept near neutral and the
   blur wide, otherwise a vivid photo passing underneath turns the bar into a
   coloured stripe. */
/* The bottom chrome is one sheet, not two panes. A fixed panel sitting behind
   the tab bar and the 赞同/评论/分享 strip carries the blur and the veil for
   both, and the bars themselves paint nothing. Each bar used to filter its
   own box, so a photo scrolling up frosted the strip the moment it touched
   the strip and left the tab bar flat until the photo physically arrived:
   two separate samples of the page with a seam between them. One box means
   one backdrop sample, so the whole band answers together. */
html[data-glass="on"] .mobile-chrome {
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  background: rgba(var(--surface-rgb), var(--nav-veil));
}

/* The top bar paints its frost on a pseudo-element instead of itself: a
   backdrop-filter on the bar would make the account panel hanging below it a
   descendant of that backdrop root, and the panel would sample the bar's flat
   fill rather than the page, killing its own blur. */
html[data-glass="on"] .topbar {
  background: transparent;
}

html[data-glass="on"] .topbar::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  background: rgba(var(--surface-rgb), var(--nav-veil));
  pointer-events: none;
}

html[data-glass="on"] .topbar__search {
  background: var(--nav-tint);
}

html[data-glass="on"] .topbar__search:hover {
  background: var(--nav-tint-hover);
}

html[data-glass="on"] .topbar__search-kbd {
  background: rgba(var(--surface-rgb), 0.72);
}

/* The mobile drawer is opened from the frosted bar, so it uses the same
   material; its scrim lightens so the blurred page behind still reads. */
html[data-glass="on"] .mobile-nav {
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  background: rgba(var(--surface-rgb), var(--nav-veil-panel));
}

html[data-glass="on"] .mobile-nav::before {
  background: rgba(18, 18, 18, 0.3);
}

html[data-glass="on"] .topbar {
  border-bottom-color: color-mix(in srgb, var(--divider) 65%, transparent);
}

html[data-glass="on"] .mobile-chrome {
  border-top-color: color-mix(in srgb, var(--divider) 65%, transparent);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  html[data-glass="on"] .topbar,
  html[data-glass="on"] .mobile-chrome,
  html[data-glass="on"] .to-top,
  html[data-glass="on"] .mobile-nav {
    background: var(--surface);
  }
}

.topbar--sticky {
  position: sticky;
  top: 0;
}

.topbar__inner {
  display: flex;
  align-items: center;
  gap: 18px;
  max-width: var(--container);
  height: var(--topbar-h);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: none;
  color: var(--text);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.brand__logo {
  width: 30px;
  height: 30px;
  border-radius: var(--radius);
  object-fit: cover;
}

.brand__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--accent-on);
  font-size: 17px;
  font-weight: 700;
}

.brand__name {
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 主标题＋副标题竖排；副标题为可选的第二行（设置开启时）。 */
.brand__text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  line-height: 1.25;
}

.brand__subtitle {
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--muted);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0;
}

/* ---------- primary nav ---------- */

.topnav {
  display: flex;
  flex: 1 1 0;
  align-self: stretch;
  min-width: 0;
}

/* The inline bootstrap in layout.html adds nav-ready right after collapsing the
   items that do not fit. Until then the page is invisible as a whole: painting
   the header first and the menu text a frame later read as a flicker, so the
   first paint happens once, complete. The animation is a pure-CSS failsafe that
   reveals the page after a second if that script never runs. */
html:not(.nav-ready) body {
  visibility: hidden;
  animation: page-reveal 1ms linear 1s forwards;
}

@keyframes page-reveal {
  to {
    visibility: visible;
  }
}

/* nav-ready fires from inside the header, before the sidebar has been parsed.
   The shell content used to be held back until shell-ready so the feed could
   not be painted at full width and then pushed left; the column is reserved in
   CSS now, so the content shows as soon as it parses. */

.topnav__list {
  display: flex;
  align-items: stretch;
  gap: 2px;
}

.topnav__item {
  position: relative;
  display: flex;
  align-items: stretch;
}

.topnav__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 0 13px;
  color: var(--muted-strong);
  font-size: 15px;
  white-space: nowrap;
  transition: color 0.15s ease;
}

.topnav__link:hover {
  color: var(--accent);
}

.topnav__item > .topnav__link.is-active {
  color: var(--text);
  font-weight: 600;
}

.topnav__item > .topnav__link.is-active::after {
  content: "";
  position: absolute;
  left: 13px;
  right: 13px;
  bottom: 0;
  height: 3px;
  border-radius: 3px 3px 0 0;
  background: var(--accent);
}

.topnav__sub {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 30;
  display: none;
  flex-direction: column;
  min-width: 148px;
  padding: 6px;
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-pop);
}

/* The flyout hangs below a frosted bar, so it joins the same global glass
   switch. Only the outer panel takes the veil; nested levels keep their own
   flat, transparent rule below. */
html[data-glass="on"] .topnav > .topnav__list > .topnav__item > .topnav__sub {
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  background: rgba(var(--surface-rgb), var(--nav-veil-panel));
}

/* Rows sit on glass here, so the solid --surface-2 hover would paint an opaque
   patch; use the same accent tint as the other floating menus. */
html[data-glass="on"] .topnav__sub .topnav__link:hover {
  background: rgba(var(--accent-rgb), 0.1);
  color: var(--accent);
}

.topnav__item.has-sub:hover > .topnav__sub,
.topnav__item.has-sub:focus-within > .topnav__sub {
  display: flex;
}

.topnav__sub .topnav__link {
  padding: 8px 12px 8px 18px;
  border-radius: var(--radius);
  font-size: 14px;
  font-weight: 400;
}

.topnav__sub .topnav__link:hover {
  background: var(--surface-2);
}

/* Second- and third-level rows carry a short vertical rail, so the flyout
   reads as one indented tree instead of a flat list. */
.topnav__sub .topnav__link::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 7px;
  width: 2px;
  height: 12px;
  margin-top: -6px;
  border-radius: 2px;
  background: currentColor;
  opacity: 0.26;
  transition: opacity 0.15s ease;
}

.topnav__sub .topnav__link:hover::before,
.topnav__sub .topnav__link.is-active::before {
  opacity: 0.85;
}

.topnav__sub .topnav__item {
  display: block;
}

/* The first flyout scrolls when the tree is deep; nested levels below are part
   of that same scroll area. */
.topnav > .topnav__list > .topnav__item > .topnav__sub {
  max-height: min(70vh, 520px);
  overflow-y: auto;
}

/* Third level and deeper stay expanded under their parent, so opening a
   top-level menu shows the whole tree at once instead of asking for a second
   hover on the second-level row. */
.topnav__sub .topnav__sub {
  position: static;
  display: flex;
  min-width: 0;
  padding: 0 0 4px 10px;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}

.topnav__sub .topnav__sub .topnav__link {
  padding: 6px 12px 6px 18px;
  color: var(--muted);
  font-size: 13px;
}

/* Sub items are direct children of a li too, so the top-level underline rule
   above matched them; this selector has to outrank it. */
.topnav__sub .topnav__item > .topnav__link.is-active::after {
  display: none;
}

.topnav__sub .topnav__link.is-active {
  color: var(--accent);
}

/* auto overflow: items that do not fit collapse into a "more" flyout */

.topnav__item--more > .topnav__sub {
  right: 0;
  left: auto;
  max-height: min(70vh, 520px);
  overflow-y: auto;
}

.topnav__item--more.is-open > .topnav__sub {
  display: flex;
}

/* The trigger lights up when the active page sits inside the flyout, but the
   3px bar is reserved for real page links. */
.topnav__item--more > .topnav__more-trigger.is-active::after {
  display: none;
}

.topnav__more-trigger {
  height: 100%;
  border: 0;
  background: none;
  cursor: pointer;
  font: inherit;
}

.topnav__more-trigger > span {
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 1px;
}

.topnav__item--nested > .topnav__link {
  padding-left: 26px;
  color: var(--muted);
  font-size: 13px;
}

/* Items folded into the ··· panel are still top-level links, so they keep the
   level-one look; only their real children get the level-two indent and rail. */
.topnav__sub--overflow > .topnav__item:not(.topnav__item--nested) > .topnav__link {
  padding-left: 12px;
  color: var(--text-2);
  font-size: 14px;
}

.topnav__sub--overflow > .topnav__item:not(.topnav__item--nested) > .topnav__link::before {
  content: none;
}

/* That level-one colour rule above carries the same specificity as the active
   row rules and comes later in the file, so it erased the accent from every
   folded page link: the current page sat in the panel looking like its
   siblings, with only a weight change to tell them apart. Re-assert the
   accent here, matching how a level-two row marks itself active. */
.topnav__sub--overflow > .topnav__item:not(.topnav__item--nested) > .topnav__link.is-active {
  color: var(--accent);
}

/* ---------- header search ---------- */

.topbar__search {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 1 300px;
  height: 34px;
  margin-left: auto;
  padding: 0 12px;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--muted);
  font-size: 14px;
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
}

.topbar__search:hover {
  background: var(--surface-3);
}

.topbar__search .icon {
  font-size: 16px;
}

.topbar__search-label {
  flex: 1;
  text-align: left;
}

.topbar__search-kbd {
  padding: 1px 5px;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--surface);
  color: var(--muted);
  font-family: var(--font-sans);
  font-size: 10px;
  line-height: 1.6;
}

/* ---------- header actions ---------- */

.topbar__actions {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: none;
}

.topbar__login {
  margin-left: 2px;
}

.theme-toggle .icon-moon {
  display: none;
}

.dark .theme-toggle .icon-sun {
  display: none;
}

.dark .theme-toggle .icon-moon {
  display: block;
}

.nav-toggle {
  display: none;
}

.nav-toggle .icon-close {
  display: none;
}

html.nav-open .nav-toggle .icon-menu {
  display: none;
}

html.nav-open .nav-toggle .icon-close {
  display: block;
}

/* ---------- user menu ---------- */

.user-menu {
  position: relative;
  display: flex;
  align-items: center;
}

/* The avatar sits inside the same 34px hover circle as the icon buttons; the
   old 32px disc plus 2px padding filled it edge to edge and read far heavier
   than an 18px glyph, so the row looked tighter on its right. */
.user-menu__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 50%;
  transition: background-color 0.15s ease;
}

.user-menu__trigger:hover,
.user-menu__trigger[aria-expanded="true"] {
  background: var(--surface-3);
}

.user-menu__avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--surface-3);
}

.user-menu__avatar--fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(var(--accent-rgb), 0.12);
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
}

.user-menu__panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 40;
  display: none;
  min-width: 208px;
  padding: 6px;
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-pop);
}

.user-menu.is-open .user-menu__panel {
  display: block;
}

/* The account panel follows the global glass switch like the nav bars it hangs
   from. */
html[data-glass="on"] .user-menu__panel {
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  background: rgba(var(--surface-rgb), var(--nav-veil-panel));
}

.user-menu__head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--divider);
  margin-bottom: 6px;
}

.user-menu__name {
  font-size: 14px;
  font-weight: 600;
}

.user-menu__account {
  color: var(--muted);
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.user-menu__item {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 36px;
  padding: 0 12px;
  border-radius: var(--radius);
  color: var(--text-2);
  font-size: 14px;
}

.user-menu__item .icon {
  font-size: 16px;
  color: var(--muted);
}

.user-menu__item:hover {
  background: var(--surface-2);
  color: var(--text);
}

.user-menu__item--danger:hover {
  color: #d9534f;
}

/* The compose menu reuses the account panel's shell, so it inherits the glass
   switch and the hover tokens; two short labels just need less width. */
.topbar__create .user-menu__panel {
  min-width: 156px;
}

/* ---------- mobile drawer ---------- */

.mobile-nav {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 90;
  display: flex;
  flex-direction: column;
  width: min(320px, 84vw);
  background: var(--surface);
  box-shadow: -8px 0 32px rgba(18, 18, 18, 0.16);
  transform: translateX(105%);
  /* visibility 是离散属性，写进列表并延迟整段滑出时长，退出才有动画：
     去掉 is-open 时它先保持 visible 播完 0.24s，再翻成 hidden。 */
  transition: transform 0.24s cubic-bezier(0.2, 0, 0, 1), visibility 0s linear 0.24s;
  visibility: hidden;
  /* 退出动画期间面板仍然 visible，不加这层的话滑出途中那 0.24s 里的点击会落在
     已经离开的菜单项上；遮罩 ::before 自带 pointer-events，不受这里影响。 */
  pointer-events: none;
}

.mobile-nav::before {
  content: "";
  position: fixed;
  top: 0;
  right: 100%;
  bottom: 0;
  width: 100vw;
  background: rgba(18, 18, 18, 0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.24s ease;
}

.mobile-nav.is-open {
  transform: translateX(0);
  visibility: visible;
  pointer-events: auto;
  /* 打开时反过来：延迟清零，立刻可见，否则抽屉会晚 0.24s 才出现。 */
  transition: transform 0.24s cubic-bezier(0.2, 0, 0, 1), visibility 0s;
}

.mobile-nav.is-open::before {
  opacity: 1;
  pointer-events: auto;
}

.mobile-nav__head {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: var(--topbar-h);
  padding: calc(env(safe-area-inset-top) + 8px) 12px 8px 20px;
  border-bottom: 1px solid var(--divider);
}

.brand--drawer .brand__name {
  font-size: 15px;
}

.mobile-nav__scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 16px 20px calc(24px + env(safe-area-inset-bottom));
  overscroll-behavior: contain;
}

.mobile-nav__group {
  padding-bottom: 18px;
  margin-bottom: 18px;
  border-bottom: 1px solid var(--divider);
}

/* The divider separates two groups; with nothing under the last one it would
   hang in the air as a stray line. */
.mobile-nav__group:only-child {
  padding-bottom: 0;
  margin-bottom: 0;
  border-bottom: 0;
}

.mobile-nav__label {
  margin-bottom: 6px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
}

.mobile-nav__list {
  display: flex;
  flex-direction: column;
}

.mobile-nav__link {
  display: block;
  padding: 11px 10px;
  border-radius: var(--radius);
  color: var(--text-2);
  font-size: 15px;
}

.mobile-nav__link:hover,
.mobile-nav__link.is-active {
  background: var(--surface-2);
  color: var(--text);
}

.mobile-nav__link.is-active {
  color: var(--accent);
  font-weight: 600;
}

.mobile-nav__link--sub {
  position: relative;
  padding-left: 26px;
  color: var(--muted-strong);
  font-size: 14px;
}

.mobile-nav__link--sub::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 13px;
  width: 2px;
  height: 12px;
  margin-top: -6px;
  border-radius: 2px;
  background: currentColor;
  opacity: 0.26;
}

.mobile-nav__foot {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.mobile-nav__foot-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 10px;
  border-radius: var(--radius);
  color: var(--text-2);
  font-size: 14px;
}

.mobile-nav__foot-link:hover {
  background: var(--surface-2);
}

/* ---------- shell ---------- */

.shell {
  min-height: 60vh;
}

.shell__inner {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  max-width: var(--container);
  margin: 0 auto;
  padding: 12px var(--gutter) 0;
}

.main {
  flex: 1 1 auto;
  min-width: 0;
}

/* The aside is 296px plus the row's 10px gap and it is parsed after the feed,
   so the main column used to measure full width first and lose 306px once the
   column arrived — which is what the shell-ready hold existed to hide. has-
   sidebar is decided on the server from the same condition that renders the
   aside, so the basis here is exact and the first paint already has the final
   width. Below 1024 the aside is display:none, so the reserve stops there. */
@media (min-width: 1024px) {
  body.has-sidebar .shell__inner > .main {
    flex: 0 1 calc(100% - 306px);
  }
}

/* ---------- sidebar ---------- */

.sidebar {
  position: sticky;
  top: calc(var(--topbar-h) + 12px);
  display: flex;
  flex: 0 0 296px;
  /* A nowrap moment excerpt raises the column's min-content width, which made
     the flex item grow to 317px and shoved the feed 21px narrower once the
     sidebar finished parsing. The basis is fixed; the excerpt ellipsizes. */
  min-width: 296px;
  max-width: 296px;
  flex-direction: column;
  align-self: flex-start;
  gap: 10px;
}

.sidebar--static {
  position: static;
}

.side-card {
  padding: 16px;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.side-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.side-card__title {
  font-size: 15px;
  font-weight: 600;
}

.side-card__more {
  color: var(--muted);
  font-size: 12px;
}

.side-card__more:hover {
  color: var(--accent);
}

.side-card__empty {
  color: var(--muted);
  font-size: 13px;
}

.side-card--profile {
  padding: 20px 16px 14px;
}

.side-profile {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.side-profile__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--accent);
  color: var(--accent-on);
  font-size: 22px;
  font-weight: 700;
}

.side-profile__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.side-profile__name {
  margin-top: 10px;
  font-size: 17px;
  font-weight: 600;
}

.side-profile__bio {
  margin-top: 4px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.6;
}

/* 统计项数由设置决定（1–7 项），列数写在 --stat-cols 上：≤3 一行排完，
   4 项 2×2，5 项以上三列换行。分隔线靠 is-row-start / is-first-row 两个类
   画，而不是相邻兄弟选择器——换行后「相邻」不再等于「同一行」。 */
.side-profile__stats {
  display: grid;
  grid-template-columns: repeat(var(--stat-cols, 3), minmax(0, 1fr));
  row-gap: 12px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--divider);
}

.side-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 0;
  border-left: 1px solid var(--divider);
  color: var(--muted);
  font-size: 12px;
}

.side-stat.is-row-start {
  border-left: 0;
}

/* 末行只剩一项时（例如 7 项三列）让它横跨整行，而不是缩在左角留两个空格。 */
.side-stat.is-wide {
  grid-column: 1 / -1;
  border-left: 0;
}

.side-stat:not(.is-first-row) {
  padding-top: 12px;
  border-top: 1px solid var(--divider);
}

.side-stat strong {
  color: var(--text);
  font-size: 17px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

a.side-stat:hover strong {
  color: var(--accent);
}

/* 评论 / 浏览 / 赞同没有对应的列表页，渲染成 span：不给指针手型，也不借焦点色，
   免得看起来能点却点不动。 */
.side-stat--plain {
  cursor: default;
}

.side-posts {
  display: flex;
  flex-direction: column;
}

.side-posts__item {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 7px 0;
}

.side-posts__rank {
  flex: none;
  width: 16px;
  color: var(--muted);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.45;
  font-style: italic;
}

.side-posts__rank.is-hot {
  color: var(--accent);
}

.side-posts__link {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.side-posts__title {
  display: -webkit-box;
  overflow: hidden;
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.side-posts__link:hover .side-posts__title {
  color: var(--accent);
}

.side-posts__meta {
  display: flex;
  gap: 8px;
  color: var(--muted);
  font-size: 12px;
}

.side-moments {
  display: flex;
  flex-direction: column;
}

.side-moments__item + .side-moments__item {
  border-top: 1px solid var(--divider);
}

.side-moments__link {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 0;
}

.side-moments__badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--muted);
  font-size: 14px;
}

.side-moments__body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.side-moments__text {
  overflow: hidden;
  color: var(--text-2);
  font-size: 13px;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.side-moments__time {
  color: var(--muted);
  font-size: 12px;
}

.side-moments__link:hover .side-moments__text {
  color: var(--accent);
}

.side-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.side-tags__item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 28px;
  padding: 0 10px;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--muted-strong);
  font-size: 12px;
  transition: background-color 0.15s ease;
}

.side-tags__item:hover {
  background: var(--surface-3-hover);
}

.side-tags__count {
  color: var(--muted);
  font-size: 11px;
}

/* ---------- configurable sidebar blocks ---------- */

.side-notice {
  color: var(--text-2);
  font-size: 13px;
  line-height: 1.75;
  white-space: pre-line;
}

.side-card--image {
  padding: 0;
  overflow: hidden;
}

.side-image {
  display: block;
  line-height: 0;
}

.side-image img {
  display: block;
  width: 100%;
  height: auto;
}

.side-html {
  color: var(--text-2);
  font-size: 13px;
  line-height: 1.75;
}

.side-html img {
  max-width: 100%;
  height: auto;
  border-radius: var(--radius);
}

.side-html a {
  color: var(--accent);
}

.side-comments__source {
  display: none;
}

.side-comments__list {
  display: flex;
  flex-direction: column;
}

.side-comments__item + .side-comments__item {
  border-top: 1px solid var(--divider);
}

.side-comments__link {
  display: flex;
  gap: 10px;
  padding: 9px 0;
}

.side-comments__item:first-child .side-comments__link {
  padding-top: 2px;
}

.side-comments__avatar {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--surface-3);
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
}

.side-comments__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.side-comments__body {
  flex: 1;
  min-width: 0;
}

.side-comments__text {
  display: -webkit-box;
  overflow: hidden;
  color: var(--text-2);
  font-size: 13px;
  transition: color 0.15s ease;
  line-height: 1.6;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.side-comments__author {
  margin-right: 4px;
  color: var(--text);
  font-weight: 600;
  transition: color 0.15s ease;
}

.side-comments__meta {
  display: flex;
  gap: 8px;
  margin-top: 3px;
  color: var(--muted);
  font-size: 12px;
}

.side-comments__post {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 悬停整条评论内容（用户名+正文）一起上焦点色，正文靠继承、用户名直接覆盖。 */
.side-comments__link:hover .side-comments__text,
.side-comments__link:hover .side-comments__author {
  color: var(--accent);
}

.side-card--quiet {
  padding: 14px 16px;
  background: transparent;
  box-shadow: none;
}

.side-note {
  display: flex;
  flex-wrap: wrap;
  gap: 0 6px;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.7;
}

.side-note--small {
  margin-top: 2px;
  font-size: 11px;
  opacity: 0.8;
}

.side-note a {
  color: var(--muted-strong);
}

.side-note a:hover {
  color: var(--accent);
}

/* ---------- footer ---------- */

.site-footer {
  max-width: var(--container);
  margin: 0 auto;
  padding: 24px var(--gutter) 36px;
}

.site-footer__inner {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 20px;
  color: var(--muted);
  font-size: 12px;
}

.site-footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
}

.site-footer__links a:hover {
  color: var(--accent);
}

.site-footer__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.site-footer__meta a:hover {
  color: var(--accent);
}

.site-footer__license {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
}

.site-footer__license a:hover {
  color: var(--accent);
}

.site-footer__sep {
  opacity: 0.6;
}

/* ---------- bottom tab bar ---------- */

/* One panel carries the material for the whole bottom chrome: its height is
   the union of whichever bars the page actually shows (the tab bar alone, the
   article action strip alone, or both stacked) and it sits below both, so the
   buttons and labels paint crisply on top of the frost instead of being
   blurred by each other. Placed here, next to the bars it backs. */
.mobile-chrome {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 54;
  display: none;
  height: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--divider);
  background: var(--surface);
  pointer-events: none;
}

/* With the 赞同/评论/分享 strip above it the sheet grows to cover both, and the
   hairline goes: mid-chrome it only drew the seam between the two bars, and
   the frost edge is already where the chrome ends. */
body.has-action-stack .mobile-chrome {
  border-top: 0;
}

body.has-tabbar.has-action-stack .mobile-chrome {
  height: calc(2 * var(--tabbar-h) + env(safe-area-inset-bottom));
}

.tabbar {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 60;
  display: none;
  height: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
  background: transparent;
}

/* The reader asked for the two rows to be divided again, but as a hairline
   rather than as a second surface: the panel behind them is still one sheet,
   so this only says where the 赞同/评论/分享 row stops and the tabs start. It
   stays weaker than the outer edge (30% of --divider against the 65% the
   chrome edge gets) so the pair still reads as one card, and it rides with
   the tab bar, so the line leaves together with the bar when it slides away
   instead of floating in the middle of the strip. */
body.has-tabbar.has-action-stack .tabbar {
  border-top: 1px solid color-mix(in srgb, var(--divider) 30%, transparent);
}


/* Auto-hide is its own switch (layout > 滚动时底部标签栏) rather than a fourth
   value of 移动端底部标签栏: 全局显示/仅列表页显示 answers which pages get the
   bar, this answers whether it gets out of the way, and a reader can want any
   combination. The slide rides on bottom, not transform, on purpose. The 赞同/
   评论/分享 strip has to grow into the slot the tab bar vacates (its safe-area
   padding included, so the buttons never land on the home indicator), and
   growing a box is a layout change. transform is composited, so the two bars
   would run on different clocks and, while the page is scrolling, the
   compositor paints the tab bar ahead of the strip: a hairline of bare page
   background opens between them. Same property, same layout pass, they cannot
   separate. */
html[data-tabbar-scroll="auto"] .tabbar {
  transition: bottom 0.18s ease;
}

html[data-tabbar-scroll="auto"].tabbar-hidden .tabbar {
  bottom: calc(-1 * (var(--tabbar-h) + env(safe-area-inset-bottom)));
}

/* An open overlay does not bring the bar back, and there is deliberately no
   exception rule here any more: the drawer (90), the search sheet and the share
   dialog (110) all paint above the chrome (54/60), so nothing the reader needs
   is hidden by leaving the bar slid out -- and while it was pinned back, the
   slide itself was visible through the share and search scrims as a strip of
   chrome moving at the bottom of the screen. The chrome keeps whatever state the
   scroll left it in for as long as the overlay stays open. */

/* The sheet rides the same channel as the bars it backs, so its top edge is
   the top edge of the chrome at every frame of the slide. */
html[data-tabbar-scroll="auto"] .mobile-chrome {
  transition: bottom 0.18s ease, height 0.18s ease;
}

/* No strip above it: the sheet is the tab bar's own box, so it leaves with it. */
html[data-tabbar-scroll="auto"].tabbar-hidden .mobile-chrome {
  bottom: calc(-1 * (var(--tabbar-h) + env(safe-area-inset-bottom)));
}

/* With the strip the sheet never leaves: the strip takes the slot the tab bar
   vacates, so the panel keeps covering their union and only its top edge comes
   down. That is the same geometry the strip is given on its own, which is why
   these two rules can differ only in which property they animate. */
html[data-tabbar-scroll="auto"].tabbar-hidden body.has-tabbar.has-action-stack .mobile-chrome {
  bottom: 0;
  height: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
}

.tabbar__item {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  color: var(--muted);
  font-size: 10px;
  line-height: 1.2;
}

.tabbar__item .icon {
  font-size: 22px;
}

/* Icons from the Halo icon library come in as inline <svg> with their own
   width/height and colour, so size them against the label here. */
.tabbar__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 22px;
  font-size: 22px;
  line-height: 1;
}

.tabbar__icon > svg {
  width: 1em;
  height: 1em;
}

/* A tab left without an icon still lines its label up with the tabs that have
   one; when no tab carries an icon at all, the labels centre instead. */
.tabbar:has(.tabbar__icon) .tabbar__item:not(:has(.tabbar__icon)) {
  padding-top: 24px;
}

/* The active tab is marked by colour alone: at 10px a weight change moves the
   label's ink width enough to be read as a jump. */
.tabbar__item.is-active {
  color: var(--accent);
}

/* ---------- back to top ---------- */

/* The disc does not live in the corner of the window: it hangs just outside
   the right edge of the content container, in the margin the page already
   leaves there. That is decided by a rail rather than by the button, because
   the only box that knows where the container ends is the container itself.
   The rail is the same centred max-width box as .shell__inner, widened by the
   disc plus its 12px gap on both sides (2 * 56 = 112, plus the 24px floor
   below), so it follows --container through every breakpoint without ever
   measuring anything. The 24px padding-right is what keeps the disc off the
   window edge once the page is too narrow to have a margin at all.
   pointer-events:none: the rail spans the full width, and the strip of page it
   would otherwise sit on is not the button's to take.
   两条轨道共用这段横向几何，所以目录盘和返回盘永远贴在同一个容器右缘上。 */
.to-top-rail,
.toc-rail {
  position: fixed;
  right: 0;
  left: 0;
  z-index: 64;
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  max-width: calc(var(--container) + 112px + 2 * 24px);
  margin: 0 auto;
  padding-right: 24px;
  pointer-events: none;
}

.to-top-rail {
  bottom: 24px;
}

/* 目录钉在阅读区顶端：顶栏下沿再留 24px，和返回盘离视口底沿的 24px 上下对称。
   不能用 top:24px——轨道 z-index 64 高于顶栏，那样盘子会压在顶栏上。 */
.toc-rail {
  top: calc(var(--topbar-h) + 24px);
}

/* 两颗浮盘无边框，阴影就是它们与页面之间唯一的边界：近处一层接触影把盘子钉住，
   远处一层环境影给它高度。只给桌面——手机上盘子离底部磨砂面板只有 12px，影尾会
   糊到面板顶沿，而那颗盘子本来就和面板同属一层 chrome。 */
@media (min-width: 769px) {
  .to-top-rail .to-top,
  .toc-rail .toc-fab {
    box-shadow: var(--shadow-fab);
  }
}

/* Below the drawer, the search sheet and the share dialog, so the disc can
   never sit on top of something the reader opened. Hover is the neutral
   surface tone, not the accent: the arrow is navigation, not a primary
   action. The reveal rides on opacity plus a short lift, and visibility:none
   takes it out of the tab order while it is still invisible. */
.to-top {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 50%;
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  pointer-events: auto;
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity 0.18s ease, transform 0.18s ease, background-color 0.15s ease;
}

.to-top.is-visible {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.to-top .icon {
  width: 20px;
  height: 20px;
}

/* 返回顶部进度圆圈 (layout > 返回顶部进度圆圈): a hollow ring that fills
   clockwise with how far the reader has actually gone -- nothing at the top, one
   complete circle at the very bottom, and it drains back the same way on the way
   up, because the arc is not an animation but a reading of the scroll offset.
   main.js writes that fraction into --tt-progress on the root; the dasharray is
   the circle's own circumference (r=14 in a 32 box), so only the offset moves,
   and the -90deg turn puts the start at 12 o'clock. clamp() hides the ring while
   the progress is still zero, because a round linecap would otherwise paint a
   dot at the top of an empty circle. Both forms carry it: the disc here, and on a
   capsule detail page the fourth chip, which sizes it down in article.css.
   The colour is its own knob (layout > 进度圆圈颜色). currentColor is the default
   because it is the one value that cannot disagree with the arrow it wraps: the
   ring and the glyph are drawn by the same button, so whatever the icon colour is
   in this scheme, on this surface, the ring already matches it. The accent and the
   picked swatch override that, and only the accent one tracks dark mode by itself. */
.to-top .icon,
.to-top__ring,
.to-top__segs,
.to-top__water {
  grid-area: 1 / 1;
}

/* The water is poured behind the arrow, so the arrow has to be the top layer,
   and the markup cannot say that: the icon is the first child because it is the
   only part of this button a pointer may hit, and the decoration follows it.
   A grid item obeys z-index without being positioned, so one number fixes the
   paint order without moving anything. */
.to-top .icon {
  z-index: 1;
}

.to-top__ring {
  display: none;
  width: 32px;
  height: 32px;
  transform: rotate(-90deg);
  pointer-events: none;
}

html[data-action-ring="on"][data-action-ring-style="arc"] .to-top__ring {
  display: block;
}

/* On the disc the ring is not an inset decoration: it belongs on the disc's own
   perimeter and reads as a border. The box grows to 48 so the stroke centre lands
   on 42 and its outer edge on the disc's own 44, and the width drops to 1.3333
   user units because a 48 box is 1.5x the 32 viewBox — that keeps the painted
   border at the same 2px the capsule chip draws. The 48px box is taller than the
   disc, so the track is pinned to the content box: left to itself an auto grid
   row grows to the biggest item and shifts the arrow off centre by half the
   overhang. With a definite track, place-items centres the oversized ring and it
   overflows by the same 2px on all four sides. Nothing else is clipped: the
   button has no overflow rule. */
.to-top-rail .to-top {
  grid-template-columns: 100%;
  grid-template-rows: 100%;
}

.to-top-rail .to-top__ring {
  width: 48px;
  height: 48px;
}

.to-top-rail .to-top__ring circle {
  stroke-width: 1.3333;
}

.to-top__ring circle {
  fill: none;
  stroke: var(--tt-ring);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dasharray: 87.9646px;
  stroke-dashoffset: calc(87.9646px * (1 - var(--tt-progress, 0)));
  opacity: clamp(0, calc(var(--tt-progress, 0) * 25), 1);
}

/* One place decides what the progress is drawn in, for all three styles. The
   default is currentColor because it is the one value that cannot disagree with
   the arrow it wraps; 进度圆圈颜色 either overrides it with the accent or with a
   picked hex written into --tt-ring-color on the root. The accent has to be a
   rule and not a value: theme-init.html recomputes --accent for the scheme, and
   a hex baked into the markup at render time would stop following it. */
.to-top {
  --tt-ring: var(--tt-ring-color, currentColor);
}

html[data-action-ring-color="accent"] .to-top {
  --tt-ring: var(--accent);
}

/* ---------- 进度圆圈样式: 贴边分环 ---------- */

/* N separate arcs instead of one continuous one: the ring is cut into 段数
   equal slots and the slots light up one at a time as the reader passes them,
   so the reading is a gauge rather than a line. There is no trigonometry in it
   and no JavaScript beyond the --tt-progress the arc already gets: the markup
   writes one dash pattern per circle -- a dash of unit*0.76 and a gap of the
   rest of the circumference -- and shifts each one's start by -i*unit, which
   walks the little arcs around the circle at equal spacing. 0.76 leaves the same
   12% gap at both ends of a slot that the preview used, and butt caps because a
   round cap would eat the gap back at low segment counts.
   --tt-seg-n and --tt-seg-i are plain numbers, so which slot is lit is pure
   arithmetic: (progress*n - i) is negative for a slot not reached yet and
   positive for one already passed, and multiplying by 999 inside clamp() turns
   that sign into 0 or 1. Unlit slots keep a 0.13 shadow so the gauge still
   shows the whole scale, and lit slots take the 浓度 value -- the same knob the
   water uses, and the reason this style reads differently from the arc, which
   is always at full strength. */
.to-top__segs {
  display: none;
  width: 32px;
  height: 32px;
  transform: rotate(-90deg);
  pointer-events: none;
}

html[data-action-ring="on"][data-action-ring-style="segments"] .to-top__segs {
  display: block;
}

.to-top-rail .to-top__segs {
  width: 48px;
  height: 48px;
}

.to-top-rail .to-top__segs circle {
  stroke-width: 1.3333;
}

.to-top__segs circle {
  fill: none;
  stroke: var(--tt-ring);
  stroke-width: 2;
  stroke-linecap: butt;
  opacity: calc(0.13 + clamp(0, calc((var(--tt-progress, 0) * var(--tt-seg-n, 8) - var(--tt-seg-i, 0)) * 999), 1) * (var(--tt-alpha, 0.5) - 0.13));
}

/* ---------- 进度圆圈样式: 水波 ---------- */

/* Water, not a gauge: the disc fills from the bottom as the page is read and is
   full at the end. Same fraction as the two rings, so all three agree about
   where the reader is.
   The level itself is CSS, and that is why it is CSS: the fill is a geometry
   translation of a number the root already carries, so it is correct on the
   first frame of a reload -- before main.js has run -- and correct again with
   the script broken or switched off. The script only owns the crest and the
   bubbles, i.e. the part that is actually animation.
   Two layers of the same path are painted in crest space, both anchored to
   y=0 and pushed down by the same amount, so the wave slides with the level
   without the path being recomputed for it: the front one is the water the
   reader judges, the back one is a fainter, slower second swell behind it that
   stops the surface from reading as a single sine. Until the script has drawn
   them the flat rectangle stands in for the surface, and once it has, the
   script marks the drawing live and the rectangle steps out of the way --
   otherwise its straight top edge would be visible wherever the crest dips
   below the level, and the surface would look cut in half. */
.to-top__water {
  display: none;
  width: 32px;
  height: 32px;
  pointer-events: none;
}

html[data-action-ring="on"][data-action-ring-style="water"] .to-top__water {
  display: block;
}

/* The water is not the ring: its clip is the whole viewBox, so the box has to be
   the disc and not the ring's 48. A 48 box here would pour a 48px pool over a
   44px disc and the rim of the water would sit outside the plate it is in. */
.to-top-rail .to-top__water {
  width: 44px;
  height: 44px;
}

.to-top__water-fill,
.to-top__water-back,
.to-top__water-wave {
  fill: var(--tt-ring);
  transform: translateY(calc(32px - var(--tt-progress, 0) * 32px));
}

.to-top__water-fill,
.to-top__water-wave {
  opacity: var(--tt-alpha, 0.5);
}

.to-top__water-back {
  transform: translateY(calc(33.5px - var(--tt-progress, 0) * 32px));
  opacity: calc(var(--tt-alpha, 0.5) * 0.45);
}

.to-top__water[data-live] .to-top__water-fill {
  display: none;
}

/* Bubbles are the one part that has no useful value before the script runs, so
   they start outside the clip and stay transparent until it puts them in the
   water and gives them a life of their own. */
.to-top__water-dot {
  fill: var(--accent-on);
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .to-top__water-back,
  .to-top__water-wave {
    display: none;
  }
}

/* Hover belongs to a pointer. A touch device parks it on whatever was
   tapped last and only releases it on the next tap, so the disc would stay
   lit through the scroll back to the top, hide, and be found lit again on
   the way down. Gating it by (hover: hover) leaves the press rule below
   untouched, so a tap still answers while it is held. */
@media (hover: hover) and (pointer: fine) {
  .to-top:hover {
    background: var(--surface-3-hover);
  }
}

.to-top.is-visible:active {
  transform: scale(0.94);
}

/* Frosted: the veil densifies on hover instead of being replaced by an opaque
   chip, so the disc keeps reading as glass with more fog on it. */
html[data-glass="on"] .to-top {
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  background: rgba(var(--surface-rgb), var(--nav-veil));
}

@media (hover: hover) and (pointer: fine) {
  html[data-glass="on"] .to-top:hover {
    background: rgba(var(--surface-rgb), calc(var(--nav-veil) + 0.14));
  }
}

/* ---------- page head ---------- */

.page-head {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: var(--ph-align, flex-start);
  /* 中间 needs both axes: justify-content moves the block down, text-align
     centres the lines inside it. 左下角 leaves text-align at its default. */
  text-align: var(--ph-x, left);
  min-height: var(--ph-h, auto);
  padding: 20px;
  margin-bottom: 10px;
  border-radius: var(--radius);
  /* Three layers, any of which may be absent: a scrim on top, then a cover
     image, then a colour or gradient underneath. The skin rule only writes the
     knobs that were filled in, so an untouched page keeps --surface and its own
     content height and nothing here changes. */
  background: var(--ph-tint, none), var(--ph-img, none), var(--ph-bg, var(--surface));
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  box-shadow: var(--shadow-card);
}

.page-head__eyebrow {
  margin-bottom: 6px;
  color: var(--ph-fg-eyebrow, var(--muted));
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page-head__title {
  color: var(--ph-fg, inherit);
  font-size: 22px;
  font-weight: 600;
  line-height: 1.4;
}

.page-head__description {
  margin-top: 6px;
  color: var(--ph-fg-sub, var(--muted));
  font-size: 14px;
}

.page-head__description + .page-head__description {
  margin-top: 2px;
}

/* ---------- head tools / tag menu ---------- */

/* 筛选不再单独占卡：入口钉在页头右上角，点开原地落下标签面板；
   首页把文章/瞬间胶囊和计数也放进同一行。 */
.page-head__tools {
  position: absolute;
  top: 20px;
  right: 20px;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 8px;
}

.page-head__tools .feed-head--inline {
  display: contents;
}

/* 右上角让位，长标题不钻到按钮底下。 */
.page-head:has(.page-head__tools) {
  --tools-reserve: 128px;
}

.page-head--home:has(.page-head__tools) {
  --tools-reserve: 216px;
}

.page-head--home:has(.tag-menu) {
  --tools-reserve: 300px;
}

.page-head:has(.page-head__tools) .page-head__title,
.page-head:has(.page-head__tools) .page-head__description {
  padding-right: var(--tools-reserve, 0px);
}

.tag-menu {
  position: relative;
}

.tag-menu__trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  padding: 0 12px;
  border: 0;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--muted-strong);
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

html[data-glass="on"] .tag-menu__trigger {
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  background: rgba(var(--surface-rgb), var(--nav-veil));
}

html[data-glass="on"] .tag-menu__trigger:hover,
html[data-glass="on"] .tag-menu.is-open .tag-menu__trigger {
  background: rgba(var(--surface-rgb), calc(var(--nav-veil) + 0.14));
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  html[data-glass="on"] .tag-menu__trigger {
    background: var(--surface);
  }
}

.tag-menu__trigger:hover,
.tag-menu.is-open .tag-menu__trigger {
  background: var(--surface-3-hover);
  color: var(--text);
}

.tag-menu__trigger .icon {
  font-size: 15px;
}

.tag-menu__caret {
  transition: transform 0.18s ease;
}

.tag-menu.is-open .tag-menu__caret {
  transform: rotate(180deg);
}

/* absolute 锚在按钮下方，打开前位置就已确定，不会开后再跳。 */
.tag-menu__panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 40;
  width: max-content;
  max-width: min(340px, calc(100vw - 32px));
  max-height: 280px;
  padding: 10px;
  overflow-y: auto;
  border-radius: var(--radius-lg);
  background: var(--surface-3);
  box-shadow: var(--shadow-pop);
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0.15s;
  pointer-events: none;
  scrollbar-width: thin;
}

.tag-menu.is-open .tag-menu__panel {
  opacity: 1;
  visibility: visible;
  transform: none;
  pointer-events: auto;
  transition: opacity 0.15s ease, transform 0.15s ease;
}

html[data-glass="on"] .tag-menu__panel {
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  background: rgba(var(--surface-rgb), var(--nav-veil-panel));
}

.tag-menu__panel .chips--filter {
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  overflow: visible;
}

/* 面板里的小胶囊也骑全局玻璃：半透 veil + blur；选中项保持实心焦点色。 */
html[data-glass="on"] .tag-menu__panel .chip {
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  background: rgba(var(--surface-rgb), var(--nav-veil));
}

html[data-glass="on"] .tag-menu__panel .chip:hover {
  background: rgba(var(--surface-rgb), calc(var(--nav-veil) + 0.14));
  color: var(--text);
}

html[data-glass="on"] .tag-menu__panel .chip.is-active {
  background: var(--accent);
  color: var(--accent-on);
}

html[data-glass="on"] .tag-menu__panel .chip.is-active:hover {
  background: var(--accent-hover);
}

@media (prefers-reduced-motion: reduce) {
  .tag-menu__panel,
  .tag-menu__caret {
    transition: none;
  }
}

/* 页头关掉时工具落回独立行：首页贴着胶囊条，其它页右对齐。 */
.feed-head-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

.feed-head-wrap .feed-head {
  flex: 1;
  margin-bottom: 0;
}

.tag-menu-row {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 10px;
}

/* 左上角档：钉在卡片左上，不随页头垂直对齐设置漂；内容整体让出一行。 */
.page-head__left {
  position: absolute;
  top: 20px;
  left: 20px;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 8px;
}

.page-head:has(.page-head__left) {
  padding-top: 62px;
}

.tag-menu-row--left {
  justify-content: flex-start;
}

/* ---------- back link ---------- */

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-bottom: 10px;
  color: var(--muted-strong);
  font-size: 14px;
}

.back-link .icon {
  font-size: 15px;
}

.back-link:hover {
  color: var(--accent);
}

/* ---------- author card (Zhihu-style personal center) ---------- */

.author-card {
  --author-overlap: 40px;
  display: flex;
  flex-direction: column;
  /* 与其它页 .page-head 同高：跟随全局页头固定高度 --ph-h。 */
  min-height: var(--ph-h, auto);
  margin-bottom: 10px;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

/* 封面横带：知乎个人中心的封面位。Halo 作者没有封面图字段，用焦点色淡染的
    tonal 渐变占位，浅色/深色都跟随 token，不引入外部图片。 */
.author-card__cover {
  /* 吸收 min-height(--ph-h) 的多余高度，使卡片与其它页头部同高且不留底部空白。 */
  flex: 1 0 auto;
  height: 112px;
  /* 未取到头像色前的中性兜底（不用焦点色，避免刷新时先闪一下蓝色）。 */
  background: linear-gradient(135deg, color-mix(in srgb, var(--text) 6%, var(--surface-2)), var(--surface-2));
}

/* 封面色取自头像：head 内联脚本先用 localStorage 缓存把 --author-cover-rgb 与
   html[data-author-cover] 在首帧前写好（刷新不闪色）；author-cover.js 再采样
   头像主色更新缓存。半透明头像色叠在 surface-2 上，明暗自动跟随。
   无头像 / 跨域无 CORS / 加载失败时移除标记，回退上面的中性兜底。 */
html[data-author-cover] .author-card__cover {
  background:
    linear-gradient(135deg, rgba(var(--author-cover-rgb), 0.42), rgba(var(--author-cover-rgb), 0.16)),
    var(--surface-2);
}

.author-card__main {
  flex: none;
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 0 24px 20px;
}

/* 头像压住封面下缘（负 margin），圆角方形＋surface 色描边，仿知乎。 */
.author-card__avatar {
  flex: none;
  width: 96px;
  height: 96px;
  margin-top: calc(-1 * var(--author-overlap));
  border: 4px solid var(--surface);
  border-radius: 6px;
  object-fit: cover;
  background: var(--surface-2);
}

.author-card__avatar--fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: var(--accent-on);
  font-size: 38px;
  font-weight: 700;
}

/* 名字/简介/资料行三行整体与头像垂直居中：body 上移量＝头像压边量，配合
   main 的 align-items:center，两者 margin box 中心重合即等于头像可见中心，
   与三行总高度无关（简介缺失或换行时仍对齐）。 */
.author-card__body {
  min-width: 0;
  padding: 0;
  margin-top: calc(-1 * var(--author-overlap));
}

.author-card__name {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 22px;
  font-weight: 600;
}

.author-card__badge {
  flex: none;
  padding: 2px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
  font-size: 12px;
  font-weight: 500;
}

.author-card__bio {
  margin-top: 6px;
  color: var(--muted-strong);
  font-size: 14px;
}

.author-card__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 8px;
  color: var(--muted);
  font-size: 13px;
}

.author-card__meta-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.author-card__meta .icon {
  font-size: 14px;
}

/* ---------- term cloud (tags page) ---------- */

.term-cloud {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 20px;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.term-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 36px;
  padding: 0 14px;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text-2);
  font-size: 14px;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.term-pill:hover {
  background: var(--surface-3-hover);
}

.term-pill__hash {
  color: var(--muted);
  font-size: 13px;
}

.term-pill__count {
  color: var(--muted);
  font-size: 12px;
}

/* ---------- tags page: weighted cloud ----------
   The weighted style reuses the cloud's material; the template hands each pill
   an inline font-size scaled by post count and an flex order that puts the
   busiest tags first, so the pill stops using the fixed 36px row height. */

.term-cloud--weighted {
  align-items: center;
  gap: 8px 12px;
}

.term-cloud--weighted .term-pill {
  height: auto;
  padding: 6px 14px;
  line-height: 1.4;
}

.term-cloud--weighted .term-pill__hash {
  font-size: 0.9em;
}

.term-cloud--weighted .term-pill__count {
  font-size: 0.78em;
}

/* ---------- tags page: card grid ---------- */

.term-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
  gap: 12px;
}

.term-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  height: 72px;
  padding: 0 16px;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  transition: background-color 0.15s ease;
}

.term-card:hover {
  background: var(--surface-3-hover);
}

.term-card__name {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  color: var(--text-2);
  font-size: 15px;
}

.term-card:hover .term-card__name {
  color: var(--accent);
}

.term-card__hash,
.term-row__hash {
  color: var(--muted);
  font-size: 14px;
}

.term-card__name > span:last-child,
.term-row__name > span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.term-card__count {
  flex: none;
  color: var(--muted);
  font-size: 13px;
}

/* ---------- tags page: compact list ----------
   Rows carry their own 20px gutter instead of the card so the whole line,
   including the space right of the count, is clickable and lights up. */

.term-list {
  padding: 4px 0;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.term-list li:last-child .term-row {
  border-bottom: 0;
}

.term-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--divider);
  transition: background-color 0.15s ease;
}

.term-row:hover {
  background: var(--surface-3-hover);
}

.term-row__name {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  color: var(--text-2);
  font-size: 15px;
}

.term-row:hover .term-row__name {
  color: var(--accent);
}

.term-row__count {
  margin-left: auto;
  flex: none;
  color: var(--muted);
  font-size: 13px;
}

/* ---------- category tree ---------- */

.category-tree {
  padding: 4px 20px;
  margin-bottom: 10px;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.category-tree .category-tree {
  padding: 0 0 0 20px;
  margin: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}

.category-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--divider);
}

.category-tree__item:last-child > .category-row {
  border-bottom: 0;
}

.category-row__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: var(--radius);
  background: rgba(var(--accent-rgb), 0.08);
  color: var(--accent);
  font-size: 16px;
}

.category-row__body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.category-row__name {
  color: var(--text);
  font-size: 15px;
  font-weight: 500;
}

.category-row:hover .category-row__name {
  color: var(--accent);
}

.category-row__description {
  overflow: hidden;
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.category-row__count {
  flex: none;
  margin-left: auto;
  color: var(--muted);
  font-size: 12px;
}

/* ---------- pager ---------- */

.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 18px 0 6px;
}

.pager__btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 34px;
  padding: 0 16px;
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--muted-strong);
  font-size: 13px;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.pager__btn .icon {
  font-size: 14px;
}

.pager__btn:not(.is-disabled):hover {
  background: var(--surface-3-hover);
}

.pager__btn.is-disabled {
  opacity: 0.45;
  cursor: default;
}

.pager__jump {
  position: relative;
}

.pager__status {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 34px;
  padding: 0 14px;
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text-2);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.pager__status:hover,
.pager__jump.is-open .pager__status {
  background: var(--surface-3-hover);
}

.pager__caret {
  font-size: 13px;
  color: var(--muted);
  transition: transform 0.18s ease;
}

.pager__jump.is-open .pager__caret {
  transform: rotate(180deg);
}

.pager__menu {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  z-index: 25;
  width: 168px;
  max-height: 236px;
  padding: 6px;
  overflow-y: auto;
  border-radius: var(--radius-lg);
  background: var(--surface);
  transform: translateX(-50%);
}

/* The page list is a floating layer like the search panel, so it rides the same
   global glass switch instead of keeping its own material. */
html[data-glass="on"] .pager__menu {
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  background: rgba(var(--surface-rgb), var(--nav-veil-panel));
}

.pager__jump.is-below .pager__menu {
  top: calc(100% + 8px);
  bottom: auto;
}

.pager__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 32px;
  padding: 0 10px;
  border-radius: var(--radius);
  color: var(--text-2);
  font-size: 13px;
}

.pager__item:hover {
  background: var(--surface-2);
}

.pager__item.is-current {
  color: var(--accent);
  font-weight: 600;
}

.pager__item-check {
  display: none;
  font-size: 14px;
}

.pager__item.is-current .pager__item-check {
  display: block;
}

/* ---------- search dialog ---------- */

.search-dialog {
  position: fixed;
  inset: 0;
  z-index: 110;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 64px 16px 24px;
}

.search-dialog__scrim {
  position: absolute;
  inset: 0;
  background: rgba(18, 18, 18, 0.42);
}

.search-dialog__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 640px;
  max-width: 100%;
  max-height: calc(100vh - 96px);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-pop);
  overflow: hidden;
}

/* Search layer (appearance > 全局磨砂玻璃): the desktop popover and the
   full-screen mobile page ride the nav bars' material. The scrim lightens,
   otherwise the dark film under the panel flattens the blur into grey. */
html[data-glass="on"] .search-dialog__panel {
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  background: rgba(var(--surface-rgb), var(--nav-veil-panel));
}

html[data-glass="on"] .search-dialog__scrim {
  background: rgba(18, 18, 18, 0.24);
}

html.dark[data-glass="on"] .search-dialog__scrim {
  background: rgba(0, 0, 0, 0.36);
}

/* The field is part of the same pane, not an opaque chip on top of it: a light
   ink tint (the same one the top bar's search field uses) keeps the panel's
   blur visible inside the pill. A second backdrop-filter here would sample the
   panel's flat fill instead of the page and grey the blur out. */
html[data-glass="on"] .search-dialog__field {
  background: var(--nav-tint);
}

html[data-glass="on"] .search-dialog__field:focus-within {
  background: var(--nav-tint-hover);
  box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), 0.55);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  html[data-glass="on"] .search-dialog__panel,
  html[data-glass="on"] .pager__menu,
  html[data-glass="on"] .topnav > .topnav__list > .topnav__item > .topnav__sub,
  html[data-glass="on"] .user-menu__panel {
    background: var(--surface);
  }

  html[data-glass="on"] .search-dialog__field {
    background: var(--surface-2);
  }
}

.search-dialog__bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--divider);
}

.search-dialog__field-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
}

.search-dialog__field {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
  height: 40px;
  padding: 0 12px;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
}

.search-dialog__field:focus-within {
  background: var(--surface);
  box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), 0.55);
}

.search-dialog__icon {
  font-size: 17px;
  color: var(--muted);
}

.search-dialog__input {
  flex: 1;
  min-width: 0;
  height: 100%;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--text);
  font-size: 15px;
}

.search-dialog__input::placeholder {
  color: var(--muted);
}

.search-dialog__input::-webkit-search-cancel-button {
  -webkit-appearance: none;
}

.search-dialog__esc {
  flex: none;
  height: 26px;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--muted);
  font-size: 11px;
}

.search-dialog__cancel {
  display: none;
  flex: none;
  color: var(--muted-strong);
  font-size: 14px;
}

.search-dialog__content {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.search-dialog__body {
  padding: 8px 0;
}

.search-dialog__empty {
  padding: 28px 20px 30px;
  text-align: center;
}

.search-dialog__empty-title {
  color: var(--text-2);
  font-size: 15px;
  font-weight: 500;
}

.search-dialog__empty-hint {
  margin-top: 4px;
  color: var(--muted);
  font-size: 13px;
}

.search-history {
  margin-top: 20px;
  text-align: left;
}

/* The empty state is centred, so the history header keeps to a centred column
   instead of running the action out to the panel's far edge. */
.search-history__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  max-width: 340px;
  margin: 0 auto;
}

.search-history__label {
  color: var(--muted);
  font-size: 12px;
}

.search-history__clear {
  display: inline-flex;
  align-items: center;
  height: 28px;
  margin: -8px -10px -8px 0;
  padding: 0 10px;
  border-radius: var(--radius-pill);
  color: var(--muted);
  font-size: 12px;
  line-height: 1;
}

.search-history__clear:hover {
  background: var(--surface-2);
  color: var(--accent);
}

.search-history__list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
  justify-content: center;
}

.search-history__chip {
  height: 30px;
  padding: 0 12px;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text-2);
  font-size: 13px;
}

.search-history__chip:hover {
  color: var(--accent);
}

.search-dialog__state {
  padding: 36px 20px;
  text-align: center;
}

.search-dialog__state-title {
  font-size: 15px;
  font-weight: 500;
}

.search-dialog__state-hint {
  margin-top: 4px;
  color: var(--muted);
  font-size: 13px;
}

.search-results {
  display: flex;
  flex-direction: column;
  list-style: none;
}

.search-results__link {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 10px 20px;
  border-left: 3px solid transparent;
}

.search-results__item.is-selected .search-results__link,
.search-results__link:hover {
  background: var(--surface-2);
  border-left-color: var(--accent);
}

.search-results__title {
  color: var(--text);
  font-size: 15px;
  line-height: 1.5;
}

.search-results__title--excerpt {
  display: -webkit-box;
  overflow: hidden;
  color: var(--text-2);
  font-size: 14px;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.search-results__title mark {
  padding: 0 1px;
  border-radius: 2px;
  background: rgba(var(--accent-rgb), 0.16);
  color: var(--text);
  font-weight: 600;
}

.search-results__meta {
  color: var(--muted);
  font-size: 12px;
}

.search-loading {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.search-loading__pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  background: var(--surface);
  box-shadow: var(--shadow-pop);
  color: var(--muted-strong);
  font-size: 13px;
}

.search-spinner {
  width: 14px;
  height: 14px;
  border: 2px solid rgba(var(--accent-rgb), 0.25);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: zhihu-spin 0.7s linear infinite;
}

.search-dialog__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 20px;
  border-top: 1px solid var(--divider);
  color: var(--muted);
  font-size: 12px;
}

.search-dialog__keys {
  display: flex;
  gap: 14px;
}

.search-dialog__keys kbd {
  display: inline-block;
  min-width: 16px;
  padding: 0 4px;
  margin-right: 2px;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--surface-2);
  font-family: var(--font-sans);
  font-size: 10px;
  line-height: 1.7;
  text-align: center;
}

.search-dialog__status {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

/* ---------- share panel ---------- */

/* Same shell contract as the search dialog: a [hidden] container, a scrim that
   closes on click, and an is-open class the JS adds one frame after unhiding,
   so the open and the 180ms close timer line up with the transition below. */
.share-dialog {
  position: fixed;
  inset: 0;
  z-index: 110;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 16px;
}

.share-dialog__scrim {
  position: absolute;
  inset: 0;
  background: rgba(18, 18, 18, 0.42);
  opacity: 0;
  transition: opacity 0.18s ease;
}

.share-dialog__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 420px;
  max-width: 100%;
  max-height: calc(100vh - 48px);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-pop);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.18s ease, transform 0.18s ease;
  overflow: hidden;
}

.share-dialog.is-open .share-dialog__scrim {
  opacity: 1;
}

.share-dialog.is-open .share-dialog__panel {
  opacity: 1;
  transform: none;
}

html[data-glass="on"] .share-dialog__panel {
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  backdrop-filter: blur(var(--nav-blur)) saturate(115%);
  background: rgba(var(--surface-rgb), var(--nav-veil-panel));
}

html[data-glass="on"] .share-dialog__scrim {
  background: rgba(18, 18, 18, 0.24);
}

html.dark[data-glass="on"] .share-dialog__scrim {
  background: rgba(0, 0, 0, 0.36);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  html[data-glass="on"] .share-dialog__panel {
    background: var(--surface);
  }
}

.share-dialog__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--divider);
}

.share-dialog__title {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
}

.share-dialog__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--muted-strong);
  font-size: 18px;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.share-dialog__close:hover {
  background: var(--surface-3-hover);
  color: var(--text);
}

.share-dialog__body {
  display: flex;
  flex-direction: column;
  padding: 14px 16px 16px;
  overflow-y: auto;
}

.share-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

/* the chip recipe (base.css), squared: tonal surface, tonal hover. The tiles no
   longer select a stage, so there is no persistent pressed colour. */
.share-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 0;
  height: 68px;
  padding: 0 4px;
  border: 0;
  border-radius: var(--radius);
  background: var(--surface-3);
  color: var(--muted-strong);
  font-family: inherit;
  font-size: 12px;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.share-tile .icon {
  font-size: 20px;
}

.share-tile span {
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.share-tile:hover {
  background: var(--surface-3-hover);
  color: var(--text);
}

.share-tile:focus-visible {
  outline: 2px solid rgba(var(--accent-rgb), 0.55);
  outline-offset: -2px;
}

/* 磨砂玻璃开着的时候，格子换成比面板深一点的墨色薄片：面板的模糊能透上来，
   格子又还分得出来。格子自己不再加 backdrop-filter，套第二层会糊成灰片。 */
html[data-glass="on"] .share-tile {
  background: var(--glass-tile);
}

html[data-glass="on"] .share-tile:hover {
  background: var(--glass-tile-hover);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  html[data-glass="on"] .share-tile {
    background: var(--surface-3);
  }

  html[data-glass="on"] .share-tile:hover {
    background: var(--surface-3-hover);
  }
}

/* 面板里只留格子和链接：链接单独占一行，用分隔线跟网格分开。 */
.share-link {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--divider);
  max-width: 100%;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.5;
  text-align: center;
  word-break: break-all;
  user-select: all;
}

/* ---------- share viewer (二维码 / 海报 二级弹窗) ---------- */

/* 复用面板的外壳，只是叠在它上面一层，宽度收窄到刚好放下二维码。 */
.share-viewer {
  z-index: 120;
}

.share-viewer .share-dialog__panel {
  width: 340px;
}

.share-viewer__body {
  align-items: center;
}

.share-view {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

/* QR stays on white in both schemes: a scanner needs the contrast, and a dark
   inverted code is not readable by every app. */
.share-qr {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #ffffff;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.6;
}

.share-qr canvas {
  display: block;
}

.share-note {
  margin-top: 10px;
  max-width: 100%;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.6;
  text-align: center;
}

.share-poster {
  display: flex;
  justify-content: center;
  width: 100%;
}

.share-poster__img {
  display: block;
  width: 100%;
  max-width: 280px;
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
}

/* 保存按钮去掉后这里只剩一句提示：触屏说长按，鼠标说右键，各显示各的那句。 */
.share-poster__bar {
  display: flex;
  justify-content: center;
  margin-top: 12px;
}

.share-poster__bar .share-note {
  margin-top: 0;
  text-align: center;
}

.share-note--mouse {
  display: none;
}

@media (hover: hover) and (pointer: fine) {
  .share-poster__bar .share-note--touch {
    display: none;
  }

  .share-note--mouse {
    display: inline;
  }
}

/* ---------- error page ---------- */

.error-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 72px 24px;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  text-align: center;
}

.error-page__code {
  color: var(--accent);
  font-size: 64px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.04em;
}

.error-page__title {
  margin-top: 16px;
  font-size: 20px;
  font-weight: 600;
}

.error-page__detail {
  margin-top: 8px;
  max-width: 460px;
  color: var(--muted);
  font-size: 14px;
}

.error-page__actions {
  display: flex;
  gap: 10px;
  margin-top: 24px;
}

/* ---------- lightbox caption & video thumbs ---------- */

/* The caption is composed as two spans; a column keeps the name above the
   description even when both are present. */
.lightbox-caption {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  text-align: center;
}

.lightbox-caption__title {
  color: #f5f1fa;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.5;
}

.lightbox-caption__text {
  color: rgba(245, 241, 250, 0.72);
  font-size: 12.5px;
  line-height: 1.6;
}

/* Video slides have no still frame, so their strip tile gets a state badge
   mirroring the player: play while paused, pause while playing. */
.f-thumbs__slide.for-video::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 2;
  width: 22px;
  height: 22px;
  margin: -11px 0 0 -11px;
  border-radius: 50%;
  background-color: rgba(12, 12, 14, 0.62);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath d='M9 6.8a.8.8 0 0 1 1.2-.7l8 5.2a.8.8 0 0 1 0 1.4l-8 5.2a.8.8 0 0 1-1.2-.7z'/%3E%3C/svg%3E");
  background-position: center;
  background-size: 12px 12px;
  background-repeat: no-repeat;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
  pointer-events: none;
  transition: background-color 0.15s ease;
}

.f-thumbs__slide.for-video.is-playing::after {
  background-color: rgba(12, 12, 14, 0.72);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Crect x='7' y='6' width='3.6' height='12' rx='1'/%3E%3Crect x='13.4' y='6' width='3.6' height='12' rx='1'/%3E%3C/svg%3E");
}
