/* ==========================================================================
   theme-zhihu — article page & prose typography.
   ========================================================================== */

.article {
  --card-pad-top: 24px;
  --card-pad-x: 28px;
  padding: var(--card-pad-top) var(--card-pad-x) 20px;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.article__header {
  padding-bottom: 16px;
  margin-bottom: 20px;
}

/* 有封面时头部整块变成封面背景，文字叠在遮罩上；没有封面的文章完全不启用这套，
   边距用卡片内边距的变量取负，改断点时不会漏掉出血量。 */
.article__header--cover {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 220px;
  margin: calc(-1 * var(--card-pad-top)) calc(-1 * var(--card-pad-x)) 20px;
  padding: 22px var(--card-pad-x) 18px;
  border-radius: var(--radius) var(--radius) 0 0;
  overflow: hidden;
  background: #141414;
  color: #fff;
}

.article__cover {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* 上浅下深，保证标题、作者与发布信息在任何封面上都读得清 */
.article__header--cover::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.82), rgba(0, 0, 0, 0.55) 50%, rgba(0, 0, 0, 0.16));
}

.article__header--cover > :not(.article__cover) {
  position: relative;
  z-index: 2;
}

.article__header--cover .article__title,
.article__header--cover .article__author {
  color: #fff;
}

.article__header--cover .badge--primary {
  background: rgba(255, 255, 255, 0.22);
  color: #fff;
}

.article__header--cover .article__crumbs a {
  background: rgba(255, 255, 255, 0.18);
  color: rgba(255, 255, 255, 0.92);
}

.article__header--cover .article__crumbs a:hover {
  background: rgba(255, 255, 255, 0.3);
  color: #fff;
}

.article__header--cover .article__crumbs .icon {
  color: #fff;
}

/* On a cover the scrim does the contrast work, so the mark joins the text in
   white instead of fighting the photo with the accent. */
.article__header--cover .article__byline .icon {
  color: #fff;
}

.article__header--cover .article__meta {
  color: rgba(255, 255, 255, 0.72);
}

.article__crumbs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}

/* A category reads as a quiet chip above the title: the folder mark carries the
   accent, the label stays in the body ink so a row of them never looks like a
   second set of links competing with the headline. */
.article__crumbs a {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 24px;
  padding: 0 10px;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--muted-strong);
  font-size: 12px;
}

.article__crumbs .icon {
  width: 13px;
  height: 13px;
  color: var(--accent);
}

.article__crumbs a:hover {
  background: rgba(var(--accent-rgb), 0.1);
  color: var(--accent);
}

.article__title {
  font-size: 26px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.01em;
  word-break: break-word;
}

.article__byline {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 16px;
}

/* The mark is part of the name, not a badge beside it: it takes the same ink as
   the author label, and its drawn circle is measured to land on the 13px ink
   height of a 15px CJK glyph — a 15px box only paints 11px and reads smaller. */
.article__byline .icon {
  width: 17px;
  height: 17px;
  color: var(--text);
}

.article__byline-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.article__author {
  color: var(--text);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.4;
}

a.article__author:hover {
  color: var(--accent);
}

.article__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  margin-top: 12px;
  color: var(--muted);
  font-size: 13px;
}

/* ---------- prose ---------- */

.prose {
  color: var(--text);
  font-size: 16px;
  line-height: 1.85;
  word-break: break-word;
}

.prose > :first-child {
  margin-top: 0;
}

.prose > :last-child {
  margin-bottom: 0;
}

.prose p {
  margin: 0 0 1.1em;
}

.prose h1,
.prose h2,
.prose h3,
.prose h4,
.prose h5,
.prose h6 {
  margin: 1.7em 0 0.7em;
  color: var(--text);
  font-weight: 600;
  line-height: 1.4;
}

.prose h1 {
  font-size: 1.55em;
}

.prose h2 {
  font-size: 1.32em;
}

.prose h3 {
  font-size: 1.16em;
}

.prose h4,
.prose h5,
.prose h6 {
  font-size: 1.04em;
}

.prose a {
  color: var(--accent);
  word-break: break-all;
}

.prose a:hover {
  text-decoration: underline;
}

.prose strong {
  font-weight: 600;
}

.prose ul,
.prose ol {
  margin: 0 0 1.1em;
  padding-left: 1.6em;
  list-style: revert;
}

.prose li {
  margin: 0.3em 0;
}

.prose li > ul,
.prose li > ol {
  margin: 0.3em 0;
}

.prose blockquote {
  margin: 1.4em 0;
  padding: 10px 16px;
  border-left: 3px solid var(--border-strong);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--surface-2);
  color: var(--muted-strong);
}

.prose blockquote p:last-child {
  margin-bottom: 0;
}

.prose img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0 auto;
  border-radius: var(--radius);
}

.prose figure {
  position: relative;
  margin: 1.6em 0;
}

.prose figcaption {
  margin-top: 6px;
  color: var(--muted);
  font-size: 12px;
  text-align: center;
}

/* ---------- figure captions & media boxes ---------- */

/* The editor writes the caption beside the image as a plain flex child; pin it
   to the image's bottom edge instead, above a scrim so the text still reads on
   bright photos. */
.prose figure[data-content-type="image"],
.prose figure[data-content-type="video"] {
  overflow: hidden;
  border-radius: var(--radius);
}

.prose figure[data-content-type="image"] > img,
.prose figure[data-content-type="image"] .lightbox-link img,
.prose figure[data-content-type="video"] video {
  margin: 0 auto;
}

.prose figure[data-content-type="image"] figcaption,
.prose figure[data-content-type="video"] figcaption {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1;
  width: auto !important;
  max-width: none !important;
  margin: 0;
  padding: 26px 14px 10px;
  border-radius: 0 0 var(--radius) var(--radius);
  background: linear-gradient(
    180deg,
    rgba(10, 9, 12, 0) 0%,
    rgba(10, 9, 12, 0.55) 58%,
    rgba(10, 9, 12, 0.8) 100%
  );
  color: rgba(255, 255, 255, 0.92);
  font-size: 12.5px;
  line-height: 1.55;
  text-align: center;
  pointer-events: none;
}

.prose figure[data-content-type="image"] figcaption:empty,
.prose figure[data-content-type="video"] figcaption:empty {
  display: none;
}

/* Halo 给正文音频的 figure 写了行内 align-items:start，原生控件会缩回 300px
   固有宽度，这里撑满。开着自定义播放条时 audio.css 会藏掉 audio 并接管 figure，
   这条尺寸就不起作用了。 */
.prose figure[data-content-type="audio"] audio {
  width: 100%;
  height: 40px;
}

/* Media boxes clip their zoom/follow transform so the picture never spills
   into the neighbouring text or gallery tile. */
.prose .lightbox-link,
.prose .lightbox-video {
  display: block;
  overflow: hidden;
  border-radius: var(--radius);
}

.prose .lightbox-video {
  position: relative;
}

.prose .lightbox-video video {
  display: block;
  width: 100%;
  height: auto;
}

/* The inline player has no controls, so it needs its own play affordance;
   clicking the tile opens the lightbox player. */
.prose .lightbox-video::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 46px;
  height: 46px;
  margin: -23px 0 0 -23px;
  border-radius: 50%;
  background-color: rgba(12, 12, 14, 0.55);
  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: 20px 20px;
  background-repeat: no-repeat;
  pointer-events: none;
}

.prose [data-type="gallery"] .lightbox-link {
  overflow: hidden;
}

/* ---------- columns card ---------- */

.prose .columns {
  margin: 1.4em 0;
}

.prose .column {
  padding: 14px 16px;
  border-radius: var(--radius);
  background: var(--surface-2);
}

.prose .column > :last-child {
  margin-bottom: 0;
}

@media (max-width: 640px) {
  .prose .columns {
    flex-direction: column;
  }

  .prose .column {
    flex: 0 0 auto !important;
  }
}

/* ---------- details block ---------- */

/* Both rows keep one fixed tint: toggling only rotates the chevron, so the
   block never flashes a different palette when it opens. */
.prose details.details {
  margin: 1.3em 0;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface-2);
}

.prose details.details > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 11px 14px;
  color: var(--text);
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  list-style: none;
  background: transparent;
}

.prose details.details > summary::-webkit-details-marker {
  display: none;
}

/* A masked chevron stays optically centred through the rotation, which a
   border-drawn arrow does not. */
.prose details.details > summary::after {
  content: "";
  flex: none;
  width: 10px;
  height: 10px;
  background-color: var(--muted-strong);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 10px 7px no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 10px 7px no-repeat;
  transform: rotate(0deg);
  transition: transform 0.2s ease;
}

.prose details.details[open] > summary {
  background: transparent;
  color: var(--text);
}

.prose details.details[open] > summary::after {
  transform: rotate(180deg);
}

.prose details.details [data-type="detailsContent"] {
  padding: 12px 14px 14px;
  background: var(--surface-3);
  color: var(--text-2);
  font-size: 14.5px;
}

.prose details.details [data-type="detailsContent"] > :first-child {
  margin-top: 0;
}

.prose details.details [data-type="detailsContent"] > :last-child {
  margin-bottom: 0;
}

.prose code {
  padding: 0.12em 0.35em;
  border-radius: 3px;
  background: var(--surface-3);
  font-family: var(--font-mono);
  font-size: 0.88em;
}

.prose pre {
  margin: 1.4em 0;
  padding: 14px 16px;
  border-radius: var(--radius);
  background: #f7f8fa;
  overflow-x: auto;
  line-height: 1.6;
  -webkit-overflow-scrolling: touch;
}

.dark .prose pre {
  background: #161616;
}

.prose pre code {
  padding: 0;
  background: none;
  font-size: 13.5px;
  white-space: pre;
}

.prose hr {
  height: 0;
  margin: 2em 0;
  border: 0;
  border-top: 1px solid var(--border);
}

.prose table {
  display: block;
  width: 100%;
  margin: 1.4em 0;
  overflow-x: auto;
  border-collapse: collapse;
  font-size: 14px;
}

.prose th,
.prose td {
  padding: 8px 12px;
  border: 1px solid var(--border);
  text-align: left;
}

.prose th {
  background: var(--surface-2);
  font-weight: 600;
}

.prose video,
.prose iframe,
.prose embed {
  max-width: 100%;
  border-radius: var(--radius);
}

.prose iframe {
  width: 100%;
  aspect-ratio: 16 / 9;
  height: auto;
  border: 0;
}

.prose mark {
  padding: 0.05em 0.15em;
  background: rgba(255, 214, 0, 0.35);
  color: inherit;
  border-radius: 2px;
}

/* Halo's task list wraps the box and the text in separate blocks, so the
   native inline checkbox drops onto its own line. Flex puts the box back in
   front of the first line, and the box borrows the login form's look. */
.prose ul[data-type="taskList"] {
  padding-left: 0;
  list-style: none;
}

.prose ul[data-type="taskList"] li[data-type="taskItem"] {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 0.4em 0;
}

.prose ul[data-type="taskList"] li[data-type="taskItem"] > label {
  display: flex;
  flex: none;
  margin: 7px 0 0;
  padding: 0;
  line-height: 0;
  /* The box only mirrors the saved post; ticking it would fake an edit. */
  pointer-events: none;
  user-select: none;
}

.prose ul[data-type="taskList"] li[data-type="taskItem"] > label > span {
  display: none;
}

.prose ul[data-type="taskList"] li[data-type="taskItem"] > div {
  flex: 1;
  min-width: 0;
}

.prose ul[data-type="taskList"] li[data-type="taskItem"] > div > :last-child {
  margin-bottom: 0;
}

.prose [data-type="taskItem"] input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 16px;
  height: 16px;
  margin: 0;
  border: 1px solid var(--border-strong);
  border-radius: 3px;
  background-color: var(--surface);
  background-position: center;
  background-repeat: no-repeat;
  background-size: 100% 100%;
  cursor: default;
}

.prose [data-type="taskItem"] input[type="checkbox"]:disabled {
  opacity: 1;
}

.prose [data-type="taskItem"] input[type="checkbox"]:checked {
  border-color: var(--accent);
  background-color: var(--accent);
  background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
}

.prose [data-type="taskItem"] input[type="checkbox"]:focus-visible {
  outline: 2px solid rgba(var(--accent-rgb), 0.55);
  outline-offset: 2px;
}

/* ---------- article footer ---------- */

.article__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 24px 0 4px;
}

.tag-pill {
  display: inline-flex;
  align-items: center;
  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;
}

.tag-pill:hover {
  background: var(--surface-3-hover);
}

.article__actions {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 22px 0 4px;
}

.vote-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 40px;
  padding: 0 20px;
  border-radius: var(--radius-pill);
  background: rgba(var(--accent-rgb), 0.16);
  color: var(--accent);
  font-size: 15px;
  font-weight: 600;
  line-height: 1;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.vote-btn .icon {
  font-size: 18px;
}

.vote-btn:hover {
  background: rgba(var(--accent-rgb), 0.22);
}

.vote-btn.is-active {
  background: var(--accent);
  color: var(--accent-on);
}

.vote-btn__count {
  font-variant-numeric: tabular-nums;
}

.vote-btn.is-bump .icon {
  animation: moment-bump 0.32s ease;
}

.article__action {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted-strong);
  font-size: 14px;
  transition: color 0.15s ease;
}

.article__action .icon {
  font-size: 17px;
}

.article__action:hover {
  color: var(--accent);
}

/* ---------- prev / next ---------- */

.post-nav {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-top: 26px;
}

.post-nav__item {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 14px;
  border-radius: var(--radius);
  background: var(--surface-2);
  transition: background-color 0.15s ease;
}

.post-nav__item:hover {
  background: var(--surface-3);
}

.post-nav__label {
  color: var(--muted);
  font-size: 12px;
}

.post-nav__title {
  display: -webkit-box;
  overflow: hidden;
  color: var(--text-2);
  font-size: 14px;
  line-height: 1.5;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.post-nav__item--next {
  grid-column: 2;
  text-align: right;
}

.post-nav__item--next .post-nav__title {
  align-self: flex-end;
}

.post-nav__item:hover .post-nav__title {
  color: var(--accent);
}

/* ---------- mobile action bar ---------- */

/* Paints nothing: the material behind the whole bottom chrome is one panel
   (.mobile-chrome in layout.css) that also carries the tab bar, so this strip
   only lays its buttons on top of it. No border-top either — mid-chrome a
   hairline only marked the seam between this bar and the tab bar below it.
   --ab-gap is the space the strip leaves to whatever sits below it (the tab
   bar's slot, or nothing) and --ab-h its own height. 整行 takes the whole 52px
   row it is handed; the two capsule styles shrink to 40px and use the gap to
   sit either in the middle of that row (胶囊) or above the tab bar entirely
   (悬浮). Driving the row from those two numbers — instead of writing capsule
   geometry for every state — is what lets the slide-out, the slot hand-over and
   the pinned overlays below stay one set of rules for all three styles. */
.mobile-action-bar {
  --ab-gap: 0px;
  --ab-h: 52px;
  display: none;
  position: fixed;
  right: 0;
  bottom: calc(var(--ab-gap) + env(safe-area-inset-bottom));
  left: 0;
  z-index: 55;
  align-items: center;
  gap: 10px;
  height: var(--ab-h);
  padding: 0 12px;
}

body.has-tabbar .mobile-action-bar {
  bottom: calc(var(--tabbar-h) + var(--ab-gap) + env(safe-area-inset-bottom));
}

/* Frosted from the same switch (appearance > 全局磨砂玻璃) as the tab bar, but
   not by its own filter: .mobile-chrome in layout.css is one panel behind both
   bars and carries the blur, the veil and the no-backdrop-filter fallback for
   the pair. That is the whole reason it exists — filtering each bar separately
   sampled the page twice, so content reaching the strip frosted the strip and
   left the tab bar flat until the same content arrived there. The 悬浮 capsule
   is the exception: it is detached from the panel, so it frosts itself. */

/* When the tab bar slides out (layout > 滚动时底部标签栏) this bar does not hide
   with it — it moves down into the slot the tab bar vacated, which is exactly
   the geometry the strip has when there is no tab bar at all. Only bottom
   changes any more: the height belongs to the style, not to the state, so the
   capsule keeps its 40px while the row it used to sit in collapses. */
html[data-tabbar-scroll="auto"] .mobile-action-bar {
  transition: bottom 0.18s ease;
}

html[data-tabbar-scroll="auto"].tabbar-hidden body.has-tabbar .mobile-action-bar {
  bottom: calc(var(--ab-gap) + env(safe-area-inset-bottom));
}

/* No overlay exception either, and that one has to be stated because the strip
   is the bar an overlay can be opened from: with the tab bar no longer pinned
   back while the drawer, the search sheet or the share dialog is open, the
   strip that already moved into the vacated slot has to stay there. Pulling it
   up by a tab bar height would park it 52px above the bottom of the screen over
   empty frost. */

.vote-btn--mobile {
  flex: 1;
  justify-content: center;
  height: 38px;
  font-size: 14px;
}

/* The unvoted pill carries the same accent tint as on desktop, so the row reads
   as a button instead of bare text on the frost. Press feedback stays on the
   accent ladder (never the neutral tone, because the idle state is tinted too).
   Voted keeps the solid accent: that is a state, not a surface. */
html[data-glass="on"] .mobile-action-bar .vote-btn:not(.is-active):active {
  background: rgba(var(--accent-rgb), 0.22);
}

.mobile-action-bar .article__action {
  flex-direction: column;
  gap: 1px;
  min-width: 44px;
  font-size: 11px;
}

.mobile-action-bar .article__action .icon {
  font-size: 19px;
}

/* 评论 gets the number the way 赞同 already has one. Word and figure travel as
   one line so the icon-over-text chips in this strip never grow a third row and
   the chip keeps the 44px box it already had; tabular figures stop the width
   twitching when a comment lands.
   layout > 评论按钮显示评论数 takes the number back out everywhere — a bare
   评论 in this row, a bare icon in the capsules. */
.action__line {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
}

.action__count {
  font-variant-numeric: tabular-nums;
}

html[data-action-count="off"] .action__count {
  display: none;
}

/* ---------- the two capsule styles ---------- */

/* Both capsules leave the full-width row and become the box that fits their
   actions, and both detach from the bottom panel: the sheet behind them stays
   one tab bar tall, so whatever they are made of has to be their own. The only
   difference between the two is where the box sits — centred (胶囊) or against
   the right edge (悬浮). width:fit-content with auto margins is what centres it
   while left:0/right:0 stay untouched, so the slide-out still has something to
   animate, and --ab-gap is the 12px the box keeps from whatever is below it. */
html[data-action-bar="pill"] .mobile-action-bar,
html[data-action-bar="float"] .mobile-action-bar {
  --ab-gap: 12px;
  --ab-h: 40px;
  width: fit-content;
  max-width: calc(100% - 24px);
  gap: 2px;
  padding: 0 4px;
  margin-inline: auto;
  border-radius: var(--radius-pill);
  background: var(--surface);
}

/* No border: the frost edge is the outline, and a hairline around a small chip
   is what makes it read as a plate rather than as glass. Same veil and blur as
   .mobile-chrome, one notch lighter (--nav-veil-panel) because heavy fog on a
   small chip reads solid. Glass off — or no backdrop-filter support — it is a
   plain opaque chip instead, exactly like the rest of the frosted family. */
html[data-glass="on"][data-action-bar="pill"] .mobile-action-bar,
html[data-glass="on"][data-action-bar="float"] .mobile-action-bar {
  -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));
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  html[data-glass="on"][data-action-bar="pill"] .mobile-action-bar,
  html[data-glass="on"][data-action-bar="float"] .mobile-action-bar {
    background: var(--surface);
  }
}

/* 悬浮 is the same box anchored to the right instead of centred. The 8px is the
   same edge the standalone 返回顶部 disc keeps (see .to-top-rail in
   responsive.css), so the two line up whether the arrow is merged into the
   capsule or sits 12px above it. */
html[data-action-bar="float"] .mobile-action-bar {
  right: 8px;
  left: auto;
  margin-inline: 0;
}

/* 居中 shares its row with the 返回顶部 disc (see .to-top-rail in
   responsive.css), so the capsule never grows into the disc's lane: the disc
   needs 8px of edge + 44px of itself + 8px of gap on the right, and being
   centred the capsule takes the same room on both sides. */
html[data-action-bar="pill"] .mobile-action-bar {
  max-width: calc(100% - 120px);
}

/* Inside a 40px capsule a stretched 赞同 button would be the capsule, so it
   sizes to its content again; the two icon actions keep their column (icon
   over label) but trade the row's 44px minimum width for a 36px one plus their
   own padding, because a chip that wide inside a 40px pill still reads as a
   tap target without becoming the container. */
html[data-action-bar="pill"] .mobile-action-bar .vote-btn--mobile,
html[data-action-bar="float"] .mobile-action-bar .vote-btn--mobile {
  flex: none;
  height: 32px;
  padding: 0 10px;
  font-size: 13px;
}

html[data-action-bar="pill"] .mobile-action-bar .article__action,
html[data-action-bar="float"] .mobile-action-bar .article__action {
  min-width: 36px;
  min-height: 32px;
  padding: 0 7px;
  border-radius: var(--radius-pill);
  font-size: 10.5px;
}

html[data-action-bar="pill"] .mobile-action-bar .article__action:active,
html[data-action-bar="float"] .mobile-action-bar .article__action:active {
  background: var(--nav-tint-hover);
}

/* A capsule is 40px of glass with 32px of controls inside it: there is no room
   for a second line of type, so both capsule styles are icons-only by
   definition — 赞同 keeps its number because that is a count, not a label, and
   screen readers are unaffected because the markup carries 评论/分享 aria-labels
   and the vote handler names its own button. With the label gone the column has
   a single child left and its default flex-start would park that icon against
   the top of the 32px chip, so the chip centres it both ways. */
html[data-action-bar="pill"] .mobile-action-bar .vote-btn__label,
html[data-action-bar="float"] .mobile-action-bar .vote-btn__label,
html[data-action-bar="pill"] .mobile-action-bar .article__action > span:not(.action__line),
html[data-action-bar="float"] .mobile-action-bar .article__action > span:not(.action__line) {
  display: none;
}

html[data-action-bar="pill"] .mobile-action-bar .article__action,
html[data-action-bar="float"] .mobile-action-bar .article__action {
  justify-content: center;
}

/* A capsule has one row and no room for the word, so the line wrapper steps
   aside: the icon and the figure become the chip's two items, which is how
   赞同 already reads, at the same 13px. */
html[data-action-bar="pill"] .mobile-action-bar .article__action--comment,
html[data-action-bar="float"] .mobile-action-bar .article__action--comment {
  flex-direction: row;
  gap: 4px;
}

html[data-action-bar="pill"] .mobile-action-bar .article__action--comment .action__line,
html[data-action-bar="float"] .mobile-action-bar .article__action--comment .action__line {
  display: contents;
}

html[data-action-bar="pill"] .mobile-action-bar .action__label,
html[data-action-bar="float"] .mobile-action-bar .action__label {
  display: none;
}

html[data-action-bar="pill"] .mobile-action-bar .action__count,
html[data-action-bar="float"] .mobile-action-bar .action__count {
  font-size: 13px;
}

/* ---------- what the 赞同 state is made of, inside a capsule ---------- */

/* 背景胶囊 (default): the desktop plate, so the primary action still reads as
   the one thing in the capsule with a surface of its own.
   只变颜色: no plate anywhere. The button joins the two icon controls as bare
   ink and the state moves into the colour — neutral grey before, accent after —
   which also means the press feedback has to become the neutral tone the sibling
   chips use, because the accent tint it used to darken was the plate's.
   The button keeps the same box in both modes so the capsule never resizes. */
html[data-action-vote="color"][data-action-bar="pill"] .mobile-action-bar .vote-btn:not(.is-active),
html[data-action-vote="color"][data-action-bar="float"] .mobile-action-bar .vote-btn:not(.is-active) {
  background: transparent;
  color: var(--muted-strong);
}

html[data-action-vote="color"][data-action-bar="pill"] .mobile-action-bar .vote-btn:not(.is-active):hover,
html[data-action-vote="color"][data-action-bar="float"] .mobile-action-bar .vote-btn:not(.is-active):hover,
html[data-action-vote="color"][data-action-bar="pill"] .mobile-action-bar .vote-btn:not(.is-active):active,
html[data-action-vote="color"][data-action-bar="float"] .mobile-action-bar .vote-btn:not(.is-active):active {
  background: var(--nav-tint-hover);
  color: var(--muted-strong);
}

html[data-action-vote="color"][data-action-bar="pill"] .mobile-action-bar .vote-btn.is-active,
html[data-action-vote="color"][data-action-bar="float"] .mobile-action-bar .vote-btn.is-active {
  background: transparent;
  color: var(--accent);
}

/* ---------- 返回顶部 as a fourth chip ---------- */

/* layout > 返回顶部并入胶囊, capsule styles only. The shell keeps its own disc
   for every other page, so this is a second .to-top that only the strip renders
   (post.html / page.html) and responsive.css steps the disc down for.
   It keeps the disc's reveal exactly — main.js drives every .to-top from one
   scroll state, so it arrives past 200px and leaves at the top — and only the
   8px lift is dropped, because a chip that slides down out of a 40px capsule
   looks like it is escaping the glass. What goes with the circle is its own
   plate: nothing at rest, and the same neutral tone the sibling chips show once
   the pointer is on them.
   The blur has to be dropped with it. `background: transparent` alone is not
   "nothing": the disc's glass rule (html[data-glass="on"] .to-top) still leaves
   a live backdrop-filter on this box, and a transparent layer that filters its
   backdrop is a second frosted pane inside an already frosted capsule — which
   is exactly the "still has a background" the reader sees. The icon chips beside
   it are blur:none, so matching them means blur:none here too. */
.mobile-action-bar .to-top {
  display: none;
}

html[data-action-back="on"][data-action-bar="pill"] .mobile-action-bar .to-top,
html[data-action-back="on"][data-action-bar="float"] .mobile-action-bar .to-top {
  display: grid;
  width: 36px;
  height: 32px;
  min-width: 0;
  padding: 0;
  overflow: hidden;
  border-radius: var(--radius-pill);
  background: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  transform: none;
  transition: width 0.18s ease, opacity 0.18s ease, visibility 0s;
}

html[data-action-back="on"][data-action-bar="pill"] .mobile-action-bar .to-top .icon,
html[data-action-back="on"][data-action-bar="float"] .mobile-action-bar .to-top .icon {
  width: 19px;
  height: 19px;
}

/* The arrow shares the siblings' hover/press plate. The capsule paints nothing at
   rest, so the plate is the only thing that distinguishes this chip when the
   pointer is on it — and the siblings all have one (赞同 on hover and press,
   评论/分享 on press), so leaving it out makes the fourth control read as inert.
   Tonal --nav-tint-hover, not the accent tint, and no transition, exactly like
   the rule the siblings use. The base rule above and this one tie on
   specificity, so this has to come after it to win. */
html[data-action-back="on"][data-action-bar="pill"] .mobile-action-bar .to-top:active,
html[data-action-back="on"][data-action-bar="float"] .mobile-action-bar .to-top:active {
  background: var(--nav-tint-hover);
}

/* Only for a pointer, for the same reason the rail disc is: a tap leaves the
   hover state on the chip, the chip then collapses back to width 0, and it
   reopens already plated. The press rule above still fires on touch, so the
   capsule keeps its feedback while it is held and shows nothing after. */
@media (hover: hover) and (pointer: fine) {
  html[data-action-back="on"][data-action-bar="pill"] .mobile-action-bar .to-top:hover,
  html[data-action-back="on"][data-action-bar="float"] .mobile-action-bar .to-top:hover {
    background: var(--nav-tint-hover);
  }
}

/* The progress ring inside a chip: the box is 32px tall, so the disc's 32px ring
   has to come in to 28 to keep 2px of clearance on every side. The arrow itself
   is untouched, and the ring is centred by the chip's own grid, so it stays put
   while the chip widens from 0 to 36 on reveal.
   The other two readings of the same fraction get the same box: the segmented
   rim is a ring, and the water is clipped to a circle of that box, so in the
   capsule the pool is a 28px disc inside the chip rather than the full 44px one
   it has on the rail. */
html[data-action-back="on"][data-action-bar="pill"] .mobile-action-bar .to-top__ring,
html[data-action-back="on"][data-action-bar="float"] .mobile-action-bar .to-top__ring,
html[data-action-back="on"][data-action-bar="pill"] .mobile-action-bar .to-top__segs,
html[data-action-back="on"][data-action-bar="float"] .mobile-action-bar .to-top__segs,
html[data-action-back="on"][data-action-bar="pill"] .mobile-action-bar .to-top__water,
html[data-action-back="on"][data-action-bar="float"] .mobile-action-bar .to-top__water {
  width: 28px;
  height: 28px;
}

/* Hidden means gone, not parked: the slot closes by width, so a centred capsule
   shrinks back to three controls and re-centres while it does it, and the -2px
   eats the row's own gap that no width:0 can remove. visibility waits out the
   collapse instead of cutting it short, so the arrow is still painted while it
   is being squeezed shut. */
html[data-action-back="on"][data-action-bar="pill"] .mobile-action-bar .to-top:not(.is-visible),
html[data-action-back="on"][data-action-bar="float"] .mobile-action-bar .to-top:not(.is-visible) {
  width: 0;
  opacity: 0;
  visibility: hidden;
  margin-right: -2px;
  transition: width 0.18s ease, opacity 0.18s ease, margin-right 0.18s ease,
    visibility 0s linear 0.18s;
}

/* 图标动画 (layout > 📱 返回顶部图标动画, on by default). One soft hand-off:
   the arrow already there drifts up 5px and is fully transparent long before it
   reaches the rim, one short beat with nothing painted, then the arrow that stays
   glides in from 6px below and decelerates into the rest pose.
   Two things the ±18px relay got wrong. It crossed the edge, so half of every
   pass was a glyph sliced by the chip's straight cut and the circle's rim — that
   is the hard click the eye reads as 生硬 — and one Material standard curve
   stretched over the whole 1.25s made the two passes and the landing run at three
   different speeds. Here 6px stays under the 6.5px of slack a 19px icon has inside
   a 32px chip (the disc has 12px), so nothing is clipped and no overflow rule is
   needed, and each leg carries its own curve: accelerate away, decelerate in.
   The entry is monotone on purpose — 6 → 0 with no overshoot, because a settle
   bounce is a wobble at this size.
   0.1s hold first: the slot is still opening by width. The reset leg is invisible
   at both ends, so the slide back down is never seen; 100% is the rest pose so
   `both` holds it there; driving it off .is-visible replays it on every reveal.
   base.css's prefers-reduced-motion clamps the duration, leaving a plain fade. */
@keyframes ab-arrow-drift {
  0% {
    transform: translateY(0);
    opacity: 1;
    animation-timing-function: cubic-bezier(0.35, 0, 0.75, 0.3);
  }

  28% {
    transform: translateY(-5px);
    opacity: 0;
  }

  44% {
    transform: translateY(6px);
    opacity: 0;
    animation-timing-function: cubic-bezier(0.16, 0.7, 0.3, 1);
  }

  100% {
    transform: translateY(0);
    opacity: 1;
  }
}

html[data-action-back="on"][data-action-back-anim="on"][data-action-bar="pill"] .mobile-action-bar .to-top.is-visible .icon,
html[data-action-back="on"][data-action-back-anim="on"][data-action-bar="float"] .mobile-action-bar .to-top.is-visible .icon {
  animation: ab-arrow-drift 1.15s linear 0.1s both;
}

/* The same hand-off on the standalone disc — 谁在场谁接力. Without this the arrow
   only moves when it was merged into the capsule, and the disc is what actually
   carries 返回顶部 when 并入胶囊 is off: 右下悬浮 puts it 12px above the capsule on
   the same right edge, 整行 and 居中 put it beside or above the strip.
   Mobile cutoff matches the chrome's own 768px, and the desktop disc keeps the
   plain 0.18s lift it has always had. When 并入胶囊 is on the rail is display:none
   here, so the two carriers can never animate at the same time. */
@media (max-width: 768px) {
  html[data-action-back-anim="on"] .to-top-rail .to-top.is-visible .icon {
    animation: ab-arrow-drift 1.15s linear 0.1s both;
  }
}

/* ---------- 文章目录 ---------- */

/* 一份条目三个入口：侧栏卡片（桌面有侧栏）、右缘圆盘下方的浮层（平板 / 无侧栏）、
   底部操作条里分享右边的芯片（手机）。三者共用 .toc 这套列表样式，所以高亮、
   缩进、标记条只写一遍。 */

.toc-count {
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* 侧栏比视口高时 main.js 会把 .sidebar 改成 static，粘性元素只能在父盒子里移动，
   而侧栏盒子只有它自己那么高——文章两万字、侧栏两千像素，目录滚过侧栏末尾就跟着
   走了。所以有目录时把这一列拉到整行的高度：卡片就能粘到文章读完为止，其余卡片
   照常滚上去，从它下面滑过（z-index 与不透明底色负责遮挡）。
   只在 --static 时拉：短侧栏本来就整列 sticky，拉满反而让它没得可粘。 */
html[data-toc="on"] .sidebar--static {
  align-self: stretch;
}

/* 卡片自己 sticky，粘的是卡片而不是整列。 */
.side-card--toc {
  position: sticky;
  top: calc(var(--topbar-h) + 12px);
  z-index: 1;
  padding: 14px 0 8px;
}

.side-card--toc .side-card__head {
  margin-bottom: 8px;
  padding: 0 16px;
}

.toc-progress {
  height: 2px;
  margin: 0 16px 6px;
  border-radius: 1px;
  background: var(--divider);
  overflow: hidden;
}

.toc-progress__bar {
  display: block;
  height: 100%;
  background: var(--accent);
  transform: scaleX(var(--toc-progress, 0));
  transform-origin: left center;
  transition: transform 0.18s linear;
}

/* 列表自己是滚动容器：文章长到几十节时卡片不能把侧栏撑成一屏。
   overscroll-behavior 让滚到底之后不把整页带着一起走。 */
.toc__scroll {
  position: relative;
  max-height: var(--toc-max, min(46vh, 380px));
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}

.toc__scroll::-webkit-scrollbar {
  width: 6px;
}

.toc__scroll::-webkit-scrollbar-track {
  background: transparent;
}

.toc__scroll::-webkit-scrollbar-thumb {
  border-radius: 3px;
  background: var(--border-strong);
}

.toc__scroll::-webkit-scrollbar-thumb:hover {
  background: var(--muted);
}

/* 当前节的那根竖条。高度和位移由 main.js 按条目实测值写，所以标题折行、字号
   变化都不会错位；它在滚动容器内部，跟着条目一起滚，不需要再减 scrollTop。 */
.toc__marker {
  position: absolute;
  top: 0;
  left: 0;
  width: 2px;
  border-radius: 1px;
  background: var(--accent);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.22s ease, height 0.22s ease, opacity 0.18s ease;
}

.toc__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.toc__link {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 5px 14px 5px 16px;
  color: var(--text-2);
  font-size: 13px;
  line-height: 1.5;
  text-decoration: none;
  transition: color 0.15s ease;
}

.toc__item[data-level="1"] > .toc__link {
  padding-left: 28px;
  font-size: 12.5px;
}

.toc__item[data-level="2"] > .toc__link {
  padding-left: 40px;
  font-size: 12.5px;
}

.toc__link:hover {
  color: var(--accent);
}

/* 当前节只换颜色，不换字重：加粗会让省略号的位置每跳一节就动一下。 */
.toc__link.is-active {
  color: var(--accent);
}

.toc__num {
  flex: none;
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.toc__link.is-active .toc__num {
  color: var(--accent);
}

.toc__text {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* ---------- 右缘的目录圆盘 ---------- */

/* 和返回顶部同一颗盘子：同尺寸、同底色、同磨砂、同 hover 色调，只是它不等滚动
   阈值——目录是出发前就要用的东西。它自己占一条钉顶的轨道（.toc-rail），和钉底的
   返回盘上下对称，两颗盘子各自的位置都不受对方出没影响。 */
.toc-fab {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--surface);
  color: var(--text);
  pointer-events: auto;
  transition: background-color 0.15s ease, transform 0.15s ease;
}

.toc-fab .icon {
  width: 20px;
  height: 20px;
}

@media (hover: hover) and (pointer: fine) {
  .toc-fab:hover {
    background: var(--surface-3-hover);
  }
}

.toc-fab:active {
  transform: scale(0.94);
}

html[data-glass="on"] .toc-fab {
  -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"] .toc-fab:hover {
    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"] .toc-fab {
    background: var(--surface);
  }
}

/* 面板开着的时候入口保持按下态，读者知道这块面板是谁开的。 */
body.toc-open .toc-fab {
  background: var(--surface-3-hover);
}

html[data-glass="on"] body.toc-open .toc-fab {
  background: rgba(var(--surface-rgb), calc(var(--nav-veil) + 0.14));
}

body.toc-open [data-toc-chip] {
  color: var(--accent);
}

/* ---------- 目录面板 ---------- */

/* 和搜索 / 分享同一套壳：[hidden] 容器 + .is-open 由 JS 在取消隐藏的下一帧加上，
   关闭留 180ms 给退场。桌面上它是圆盘下方的小浮层（无遮罩，点外面关掉），位置由
   main.js 按圆盘的实际矩形写，所以跟着容器边缘走而不是钉在窗口角；手机上它变成
   从底部升起的目录单，那部分在 responsive.css。
   z-index 100：高于抽屉 90 与右缘轨道 64，低于搜索 / 分享 110。 */
/* 退场那 180ms 里面板还在屏上淡出，但桌面形态没有遮罩挡住它原来那块地方，
   所以容器一摘掉 is-open 就整棵子树停止命中，点下去直接落到正文上。 */
.toc-pop {
  position: fixed;
  z-index: 100;
  display: flex;
  pointer-events: none;
}

.toc-pop.is-open {
  pointer-events: auto;
}

.toc-pop__scrim {
  display: none;
}

/* max-height:inherit 把 JS 写在容器上的那个高度接过来：容器高度是 auto，
   百分比 max-height 在 auto 高的父级上不解析，继承拿到的是同一个像素值。 */
.toc-pop__panel {
  display: flex;
  flex-direction: column;
  width: 300px;
  max-width: calc(100vw - 24px);
  max-height: inherit;
  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;
}

.toc-pop.is-open .toc-pop__panel {
  opacity: 1;
  transform: none;
}

/* 与搜索面板同一块材质：一样的 blur/saturate，一样的 --nav-veil-panel。
   原先用的是 --pop-veil（0.9），那层几乎不透的底色把模糊压成了一块灰白，
   手机上并排看就是「目录比搜索实」。遮罩也照搜索那套（浅色 .24 / 深色 .36），
   在 responsive.css 里。 */
html[data-glass="on"] .toc-pop__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));
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  html[data-glass="on"] .toc-pop__panel {
    background: var(--surface);
  }
}

.toc-pop__head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px 8px 16px;
}

.toc-pop__title {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
}

.toc-pop__head .toc-count {
  margin-right: auto;
}

.toc-pop__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-pill);
  color: var(--muted-strong);
  font-size: 16px;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.toc-pop__close:hover {
  background: var(--surface-3-hover);
  color: var(--text);
}

/* 面板里的列表吃掉剩余高度：头部与进度条固定，滚动只发生在条目上。 */
.toc--pop {
  display: flex;
  flex: 1;
  min-height: 0;
  padding-bottom: 6px;
}

.toc--pop .toc__scroll {
  flex: 1;
  min-height: 0;
  max-height: none;
}
