/* ---------- tokens ---------- */
:root {
  --bg: #f7f6f3;
  --bg-elev: #ffffff;
  --bg-sunken: #efece6;
  --text: #1b1a17;
  --text-soft: #5f5b53;
  --text-faint: #8b857a;
  --line: #e2ded5;
  --line-strong: #cfc9bd;
  --accent: #8a3324;
  --accent-soft: #f3e7e3;
  --accent-text: #ffffff;
  --focus: #2f6fed;
  --shadow: 0 1px 2px rgba(24, 20, 12, 0.06), 0 8px 24px rgba(24, 20, 12, 0.06);
  --radius: 10px;
  --maxw: 1360px;
  --sans: ui-sans-serif, -apple-system, "Segoe UI", "Helvetica Neue", "PingFang HK", "Noto Sans CJK HK",
    "Microsoft JhengHei", sans-serif;
  --serif: ui-serif, Georgia, "Songti TC", "Noto Serif CJK TC", serif;
}

:root[data-theme='dark'] {
  --bg: #14140f;
  --bg-elev: #1e1e18;
  --bg-sunken: #26251d;
  --text: #f0ece2;
  --text-soft: #b7b1a3;
  --text-faint: #8a8477;
  --line: #33322a;
  --line-strong: #4a483c;
  --accent: #e0866f;
  --accent-soft: #3a251f;
  --accent-text: #1a1410;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --bg: #14140f;
    --bg-elev: #1e1e18;
    --bg-sunken: #26251d;
    --text: #f0ece2;
    --text-soft: #b7b1a3;
    --text-faint: #8a8477;
    --line: #33322a;
    --line-strong: #4a483c;
    --accent: #e0866f;
    --accent-soft: #3a251f;
    --accent-text: #1a1410;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35);
  }
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Author-level display rules would otherwise beat the UA [hidden] rule. */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a {
  color: inherit;
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 4px;
}

.skip {
  position: absolute;
  left: -9999px;
}
.skip:focus {
  left: 12px;
  top: 12px;
  z-index: 60;
  background: var(--bg-elev);
  padding: 8px 12px;
  border-radius: 8px;
}

/* ---------- top bar ---------- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}

.topbar-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 10px 20px;
  display: flex;
  align-items: center;
  gap: 16px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  flex: 0 0 auto;
}
.brand-mark {
  /* Same height as the search box, at every width. */
  width: 44px;
  height: 44px;
  border-radius: 10px;
  display: block;
  flex: 0 0 auto;
}
.brand-text {
  display: flex;
  flex-direction: column;
  line-height: 1.15;
}
.brand-text strong {
  font-family: var(--serif);
  font-size: 17px;
  letter-spacing: -0.01em;
}
.brand-text small {
  color: var(--text-faint);
  font-size: 11.5px;
}

.search {
  position: relative;
  flex: 1 1 auto;
  max-width: 620px;
  margin: 0 auto;
}
.search input {
  width: 100%;
  padding: 10px 40px 10px 38px;
  font: inherit;
  color: inherit;
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
}
.search input:focus {
  border-color: var(--focus);
}
.search input::-webkit-search-cancel-button {
  cursor: pointer;
}
.search-icon {
  position: absolute;
  left: 13px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  fill: none;
  stroke: var(--text-faint);
  stroke-width: 2;
  stroke-linecap: round;
  pointer-events: none;
}
.hint {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  font: 11px/1.6 var(--sans);
  color: var(--text-faint);
  border: 1px solid var(--line-strong);
  border-radius: 5px;
  padding: 0 6px;
  pointer-events: none;
}
.search input:focus ~ .hint {
  display: none;
}

.topbar-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  flex: 0 0 auto;
}

/* Wide screens: no hamburger, the panel is just the row of buttons. */
.menu-toggle {
  display: none;
}
.menu-toggle svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}
.menu-panel {
  display: flex;
  gap: 8px;
  align-items: center;
}
.menu-label {
  display: none;
}
.menu-dot {
  display: none;
}

/* ---------- buttons ---------- */
.btn {
  font: inherit;
  color: inherit;
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  padding: 7px 12px;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.btn:hover {
  border-color: var(--text-faint);
}
.btn.ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-soft);
}
.btn.ghost:hover {
  background: var(--bg-sunken);
  border-color: transparent;
}
.btn.icon {
  padding: 7px 10px;
}
.linkish {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---------- layout ---------- */
#browse-view {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 22px 20px 60px;
  display: grid;
  grid-template-columns: 244px minmax(0, 1fr);
  gap: 28px;
  align-items: start;
}

.browse-head {
  display: none;
  grid-column: 1 / -1;
}
.filters-toggle {
  display: none;
  grid-column: 1 / -1;
}

.sidebar {
  position: sticky;
  top: 72px;
  max-height: calc(100vh - 92px);
  overflow-y: auto;
  padding-right: 6px;
}
.sidebar-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 10px;
}
.sidebar h2 {
  font: 600 12px/1 var(--sans);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 0;
}

.facet {
  border-top: 1px solid var(--line);
  padding: 12px 0;
}
.facet > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 13px;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}
.facet > summary::-webkit-details-marker {
  display: none;
}
.facet > summary::after {
  content: '＋';
  color: var(--text-faint);
  font-size: 12px;
}
.facet[open] > summary::after {
  content: '−';
}
.facet-body {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 260px;
  overflow-y: auto;
}
.facet-search {
  width: 100%;
  font: inherit;
  font-size: 13px;
  padding: 5px 8px;
  margin-bottom: 6px;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: 6px;
  color: inherit;
}
.opt {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  padding: 3px 4px;
  border-radius: 6px;
  cursor: pointer;
  color: var(--text-soft);
}
.opt:hover {
  background: var(--bg-sunken);
  color: var(--text);
}
.opt input {
  accent-color: var(--accent);
  margin: 0;
  flex: 0 0 auto;
}
.opt .label {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.opt .n {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.facet-empty {
  margin: 6px 2px 2px;
  font-size: 12.5px;
  color: var(--text-faint);
}

.facet-more {
  margin-top: 6px;
  font-size: 12px;
}

/* ---------- toolbar ---------- */
.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.count {
  margin: 0;
  color: var(--text-soft);
  font-size: 13.5px;
}
.count strong {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.toolbar-right {
  display: flex;
  gap: 10px;
  align-items: center;
}
.select {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  color: var(--text-faint);
}
.select select {
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  padding: 6px 8px;
}
.viewtoggle {
  display: flex;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  overflow: hidden;
}
.viewtoggle button {
  font: inherit;
  background: var(--bg-elev);
  color: var(--text-faint);
  border: 0;
  padding: 6px 10px;
  cursor: pointer;
}
.viewtoggle button.active {
  background: var(--accent);
  color: var(--accent-text);
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 3px 6px 3px 10px;
}
.chip button {
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
  padding: 2px 4px;
  border-radius: 999px;
}
.chip button:hover {
  background: rgba(0, 0, 0, 0.08);
}

/* ---------- grid ---------- */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 24px 20px;
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 9px;
  text-decoration: none;
  color: inherit;
}

/* The title link covers the card, so clicking anywhere opens the book — but
   it sits below the author, publisher and own controls, which stay clickable
   in their own right. */
.card-link {
  color: inherit;
  text-decoration: none;
}
.card-link::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
}
.card-meta a,
.price-tag,
.tagrow,
.own {
  position: relative;
  z-index: 1;
}

/* These have to *look* clickable while sitting in dense grey metadata: a
   dotted underline reads as interactive without shouting, and firms up to
   the accent colour on hover. */
.meta-link {
  /* Two cues, because one was not enough to notice: brighter than the dim
     metadata around it, and underlined. */
  color: var(--text-soft);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
  text-underline-offset: 3px;
  transition: color 0.12s ease;
}
.meta-link:hover,
.meta-link:focus-visible {
  color: var(--accent);
  text-decoration-style: solid;
  text-decoration-color: currentColor;
}
.card:hover .cover {
  transform: translateY(-3px);
  box-shadow: var(--shadow);
}
.card:hover .card-title a {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.cover {
  position: relative;
  aspect-ratio: 2 / 3;
  border-radius: 4px 8px 8px 4px;
  overflow: hidden;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  transition: transform 0.16s ease, box-shadow 0.16s ease;
}
/* Sits over the generated fallback. It must keep a layout box — a
   display:none image is never "near the viewport", so loading="lazy" would
   never fetch it. */
.cover img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity 0.18s ease;
}
.cover img.ready {
  opacity: 1;
}
.cover-fallback {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 12px 11px;
  color: #fff;
  text-align: left;
}
.cover-fallback::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 5px;
  background: rgba(0, 0, 0, 0.22);
}
.cover-fallback .ft {
  font-family: var(--serif);
  font-size: 13.5px;
  line-height: 1.28;
  font-weight: 600;
  display: -webkit-box;
  -webkit-line-clamp: 6;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
.cover-fallback .fa {
  font-size: 10.5px;
  opacity: 0.85;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-title {
  font-family: var(--serif);
  font-size: 14.5px;
  line-height: 1.3;
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* The other-language form of the title: present, but clearly secondary. */
.card-alt {
  font-size: 12.5px;
  line-height: 1.3;
  color: var(--text-faint);
  margin: -2px 0 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.grid.list .card-alt {
  font-size: 13.5px;
}

.card-meta {
  font-size: 12px;
  color: var(--text-faint);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
mark {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: inherit;
  border-radius: 3px;
  padding: 0 1px;
}

/* list layout */
.grid.list {
  display: block;
}
.grid.list .card {
  flex-direction: row;
  gap: 16px;
  align-items: flex-start;
  padding: 14px 4px;
  border-bottom: 1px solid var(--line);
}
.grid.list .cover {
  flex: 0 0 62px;
  width: 62px;
}
.grid.list .cover-fallback {
  padding: 6px 5px;
}
.grid.list .cover-fallback .ft {
  font-size: 9px;
  line-height: 1.2;
  -webkit-line-clamp: 5;
}
.grid.list .cover-fallback .fa {
  display: none;
}
.grid.list .card-body {
  flex: 1 1 auto;
  min-width: 0;
}
.grid.list .card-title {
  font-size: 16px;
  -webkit-line-clamp: 2;
}
.grid.list .card-meta {
  font-size: 13px;
  -webkit-line-clamp: 3;
}
.grid.list .card-sub {
  color: var(--text-soft);
  font-size: 13.5px;
  margin: 2px 0 0;
}

.tagrow {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}
.tag {
  font-size: 11px;
  color: var(--text-soft);
  background: var(--bg-sunken);
  border-radius: 999px;
  padding: 1px 8px;
  white-space: nowrap;
}
a.tag-link {
  text-decoration: none;
  border: 1px solid transparent;
}
a.tag-link:hover,
a.tag-link:focus-visible {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

.more {
  display: block;
  margin: 28px auto 0;
  padding: 10px 22px;
}
.empty {
  text-align: center;
  padding: 64px 20px;
  color: var(--text-soft);
}
.empty h3 {
  font-family: var(--serif);
  margin: 0 0 6px;
}

/* ---------- detail ---------- */
.detail {
  max-width: 1020px;
  margin: 0 auto;
  padding: 22px 20px 80px;
}
.backlink {
  display: inline-block;
  margin-bottom: 20px;
  color: var(--text-soft);
  text-decoration: none;
  font-size: 13.5px;
}
.backlink:hover {
  color: var(--text);
}
.detail-head {
  display: grid;
  grid-template-columns: 208px minmax(0, 1fr);
  gap: 34px;
  align-items: start;
}
.detail-head .cover {
  border-radius: 4px 10px 10px 4px;
  box-shadow: var(--shadow);
}
.detail h1 {
  font-family: var(--serif);
  font-size: 30px;
  line-height: 1.16;
  margin: 0 0 6px;
  letter-spacing: -0.015em;
}
.detail .sub {
  font-family: var(--serif);
  font-size: 18px;
  color: var(--text-soft);
  margin: 0 0 14px;
  line-height: 1.3;
}
.detail .byline {
  font-size: 15px;
  margin: 0 0 16px;
}
.detail .byline a {
  color: var(--accent);
}
/* In the metadata tables the links sit in ordinary body text, so they need
   the same underline the cards use rather than relying on colour alone. */
.meta-table .meta-link {
  color: var(--text);
}
.meta-table .meta-link:hover,
.meta-table .meta-link:focus-visible {
  color: var(--accent);
}
.lede {
  font-size: 15px;
  color: var(--text-soft);
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: 0 8px 8px 0;
  padding: 12px 16px;
  margin: 0 0 18px;
}
.derived {
  display: inline-block;
  font-size: 10.5px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-faint);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  padding: 0 7px;
  margin-left: 6px;
  vertical-align: 2px;
  cursor: help;
}

.section-title {
  font: 600 12px/1 var(--sans);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 34px 0 12px;
}
.meta-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}
.meta-table th,
.meta-table td {
  text-align: left;
  vertical-align: top;
  padding: 9px 12px 9px 0;
  border-bottom: 1px solid var(--line);
}
.meta-table th {
  width: 210px;
  font-weight: 500;
  color: var(--text-faint);
}
.meta-table td {
  color: var(--text);
}
.meta-table code {
  font-size: 13px;
  background: var(--bg-sunken);
  padding: 1px 5px;
  border-radius: 4px;
}
.linkrow {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}

/* ---------- about ---------- */
.about {
  max-width: 820px;
  margin: 0 auto;
  padding: 22px 20px 80px;
}
.about h1 {
  font-family: var(--serif);
  font-size: 28px;
  margin: 0 0 10px;
}
.about h2 {
  font-family: var(--serif);
  font-size: 20px;
  margin: 30px 0 8px;
}
.about p,
.about li {
  color: var(--text-soft);
}
.about code,
.about pre {
  background: var(--bg-sunken);
  border-radius: 6px;
  font-size: 13px;
}
.about code {
  padding: 1px 5px;
}
.about pre {
  padding: 12px 14px;
  overflow-x: auto;
  border: 1px solid var(--line);
}
.dscard {
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px 18px;
  margin-bottom: 14px;
}
.dscard h3 {
  font-family: var(--serif);
  margin: 0 0 4px;
  font-size: 18px;
}
.stats {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
}
.stats li {
  font-size: 12.5px;
  color: var(--text-faint);
}
.stats strong {
  display: block;
  font-size: 19px;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.switch {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 14px;
  color: var(--text-soft);
}

.footer {
  border-top: 1px solid var(--line);
  padding: 18px 20px 40px;
}
.footer p {
  max-width: var(--maxw);
  margin: 0 auto;
  font-size: 12.5px;
  color: var(--text-faint);
}
.footer a {
  color: var(--text-soft);
}

.badge {
  display: inline-block;
  min-width: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-text);
  font-size: 11px;
  text-align: center;
}

.skeleton {
  background: linear-gradient(90deg, var(--bg-sunken) 25%, var(--bg-elev) 50%, var(--bg-sunken) 75%);
  background-size: 400% 100%;
  animation: shimmer 1.4s infinite;
  border-radius: 6px;
}
@keyframes shimmer {
  to {
    background-position: -400% 0;
  }
}

/* ---------- responsive ---------- */
@media (max-width: 900px) {
  .topbar-inner {
    flex-wrap: wrap;
    gap: 10px;
  }
  /* The mark alone, left of the search box — small enough to cost nothing
     and it gives the header back its way home. The wordmark and the counts
     stay gone; the counts are in the row under 篩選 instead. */
  .brand {
    display: flex;
    order: 1;
    flex: 0 0 auto;
    margin-right: 0;
  }
  /* The mark is 44px from the base rule — the same height as the search
     box, which is pinned here rather than left to its padding so the two
     match exactly. Stretching the mark to the flex line instead is
     circular: the item that defines a line's cross size cannot also be
     sized from it. 44px is also the minimum comfortable tap target, which
     matters because the mark is the only way home on a narrow screen. */
  .search input {
    height: 44px;
    padding-top: 0;
    padding-bottom: 0;
    padding-left: 34px;
  }
  .search-icon {
    left: 11px;
  }
  .brand-text {
    display: none;
  }
  .search {
    order: 2;
    /* Zero basis, not auto: with flex-wrap on, a line wraps on base sizes
       before anything shrinks, so a content-sized search box would push the
       ☰ onto a row of its own. */
    flex: 1 1 0;
    max-width: none;
    min-width: 0;
    margin: 0;
  }
  .topbar-actions {
    order: 3;
    position: relative;
  }

  .menu-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: relative;
  }
  .menu-panel {
    display: none;
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 70;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    min-width: 196px;
    padding: 6px;
    background: var(--bg-elev);
    border: 1px solid var(--line-strong);
    border-radius: 12px;
    box-shadow: var(--shadow);
  }
  .menu-panel.open {
    display: flex;
  }
  /* Every row the same: plain text, flush left. The glyphs are dropped
     rather than kept, because only two of the four have one and the labels
     would step in and out. */
  .menu-panel .btn,
  .menu-panel .btn.icon {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    text-align: start;
    padding: 11px 12px;
    border-radius: 8px;
    background: transparent;
    border-color: transparent;
    color: var(--text);
  }
  .menu-panel .btn:hover {
    background: var(--bg-sunken);
  }
  .menu-panel .btn svg,
  .menu-panel .btn > [aria-hidden='true'] {
    display: none;
  }
  .menu-label {
    display: inline;
  }
  /* The shelf count lives inside the closed menu, so mark the ☰ instead. */
  .menu-dot {
    display: block;
    position: absolute;
    top: 4px;
    right: 4px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--accent);
  }
  #browse-view {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
  }
  .browse-head {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
  }
  .filters-toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    justify-self: start;
    flex: 0 0 auto;
  }
  /* Only .toolbar-right is left in the toolbar here, so give it the full
     width and let it keep 排序 left and the view switch right. */
  .toolbar-right {
    flex: 1 1 100%;
    justify-content: space-between;
  }
  .sidebar {
    display: none;
    position: static;
    max-height: none;
    background: var(--bg-elev);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 14px 16px;
  }
  .sidebar.open {
    display: block;
  }
  .detail-head {
    grid-template-columns: 130px minmax(0, 1fr);
    gap: 20px;
  }
  .detail h1 {
    font-size: 24px;
  }
  .meta-table th {
    width: 130px;
  }
}

@media (max-width: 520px) {
  .grid {
    grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
    gap: 20px 14px;
  }
  .hint {
    display: none;
  }
  /* The ⌨ hint is gone here, so stop reserving its room — the search box
     is narrow enough already with the mark and the ☰ beside it. */
  .search input {
    padding-right: 14px;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}

/* ---------- bookshelf ---------- */

/* On the cover, not under the text: always in the same place, and it does not
   move when a title or publisher line runs long. */
.own-icon {
  position: absolute;
  top: 7px;
  right: 7px;
  z-index: 3;
  width: 30px;
  height: 30px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  line-height: 1;
  color: #fff;
  background: rgba(20, 18, 14, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.45);
  border-radius: 50%;
  cursor: pointer;
  backdrop-filter: blur(3px);
  transition: transform 0.12s ease, background 0.12s ease;
}
.own-icon:hover {
  transform: scale(1.08);
  background: rgba(20, 18, 14, 0.85);
}
.own-icon.owned,
.own-icon.owned:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
}
.own-inline {
  align-self: flex-start;
  margin-top: 8px;
  font: inherit;
  font-size: 12.5px;
  color: var(--text-soft);
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  padding: 3px 11px;
  cursor: pointer;
}
.own-inline:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.own-inline.owned {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
}

/* The primary action on a book page, so it does not read as another link. */
.own-long {
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--accent-text);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 8px;
  padding: 8px 16px;
  cursor: pointer;
}
.own-long:hover {
  filter: brightness(1.08);
}
.own-long.owned {
  background: transparent;
  color: var(--accent);
}

.shelf {
  max-width: 1360px;
  margin: 0 auto;
  padding: 22px 20px 80px;
}
.shelf h1 {
  font-family: var(--serif);
  font-size: 28px;
  margin: 0 0 6px;
}
.shelf-count {
  color: var(--text-soft);
  margin: 0 0 14px;
}
.shelf-count strong {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.shelf-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}
.shelf-actions .btn {
  cursor: pointer;
}
.shelf-note {
  font-size: 12.5px;
  color: var(--text-faint);
  margin: 0 0 22px;
  max-width: 60ch;
}
.shelf-empty {
  color: var(--text-soft);
  padding: 40px 0;
}

/* ---------- asking price ---------- */
.price-tag {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: 6px;
}
.price-value {
  font-size: 14px;
  font-weight: 600;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.price-value.unpriced {
  font-weight: 400;
  color: var(--text-faint);
}
.price-edit {
  font: inherit;
  font-size: 12px;
  color: var(--text-faint);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.price-edit:hover {
  color: var(--accent);
}

.price-dialog {
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--bg-elev);
  color: var(--text);
  padding: 22px 24px;
  max-width: 400px;
  width: calc(100vw - 40px);
  box-shadow: var(--shadow);
}
.price-dialog::backdrop {
  background: rgba(10, 8, 6, 0.55);
}
.price-dialog h3 {
  font-family: var(--serif);
  font-size: 19px;
  margin: 0 0 4px;
}
.pd-book {
  margin: 0 0 16px;
  color: var(--text-soft);
  font-size: 14px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.pd-field {
  display: block;
  font-size: 13px;
  color: var(--text-faint);
}
.pd-input {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  padding: 8px 12px;
}
.pd-input i {
  font-style: normal;
  color: var(--text-faint);
  font-size: 14px;
}
.pd-input input {
  flex: 1;
  font: inherit;
  font-size: 17px;
  color: var(--text);
  background: none;
  border: 0;
  outline: none;
  min-width: 0;
}
.pd-hint {
  margin: 10px 0 0;
  font-size: 12.5px;
  color: var(--text-faint);
  line-height: 1.5;
}
.price-dialog menu {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin: 20px 0 0;
  padding: 0;
}
.price-dialog .btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
  font-weight: 500;
}

/* ---------- install to home screen / dock ---------- */

.install-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--accent);
}
.install-btn svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.install-btn:hover {
  background: var(--accent-soft);
}

/* The second-visit banner. Sits above the iOS home indicator rather than
   under it, which is why the inset is added to the padding. */
.install-nudge {
  position: fixed;
  z-index: 60;
  left: 16px;
  right: 16px;
  bottom: 16px;
  margin: 0 auto;
  max-width: 440px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 12px 12px 14px;
  padding-bottom: calc(12px + env(safe-area-inset-bottom));
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  box-shadow: var(--shadow);
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.22s ease, transform 0.22s ease;
}
.install-nudge.in {
  opacity: 1;
  transform: none;
}
.install-nudge img {
  border-radius: 9px;
  flex: 0 0 auto;
}
.in-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.in-text strong {
  font-size: 14.5px;
  font-weight: 600;
}
.in-text span {
  font-size: 12.5px;
  color: var(--text-soft);
}
.install-nudge .in-go {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
  font-weight: 500;
  flex: 0 0 auto;
}
.install-nudge .in-x {
  padding: 6px 8px;
  font-size: 13px;
  flex: 0 0 auto;
}

/* Safari's instruction sheet. */
.install-sheet {
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  background: var(--bg-elev);
  color: var(--text);
  padding: 22px 24px;
  width: min(400px, calc(100vw - 32px));
  box-shadow: var(--shadow);
}
.install-sheet::backdrop {
  background: rgba(16, 14, 10, 0.44);
}
.is-head {
  display: flex;
  gap: 14px;
  align-items: center;
}
.is-head img {
  border-radius: 11px;
  flex: 0 0 auto;
}
.is-head h3 {
  margin: 0;
  font-size: 17px;
}
.is-lead {
  margin: 3px 0 0;
  font-size: 13px;
  color: var(--text-soft);
  line-height: 1.5;
}
.is-steps {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.is-steps li {
  display: flex;
  gap: 11px;
  align-items: baseline;
  font-size: 14px;
  line-height: 1.55;
}
.is-steps b {
  flex: 0 0 auto;
  width: 21px;
  height: 21px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: flex-start;
  margin-top: 1px;
}
.is-glyph {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -2px;
  margin: 0 1px;
  color: var(--accent);
}
.is-note {
  margin: 16px 0 0;
  font-size: 12.5px;
  color: var(--text-faint);
  line-height: 1.55;
}
.install-sheet menu {
  display: flex;
  justify-content: flex-end;
  margin: 20px 0 0;
  padding: 0;
}
.install-sheet .btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
  font-weight: 500;
}

/* Running as an installed app: the install affordances are pointless, and the
   top bar wants the extra room under the status bar. */
@media (display-mode: standalone), (display-mode: minimal-ui), (display-mode: window-controls-overlay) {
  .install-btn,
  .install-nudge {
    display: none !important;
  }
  /* The OS already shows the name — on the home screen, in the app
     switcher, in the window title — so the wordmark and counts are
     clutter. The mark stays: in an installed window there is no browser
     UI, so it is the only way back to the catalogue. */
  .brand-text {
    display: none;
  }
  .topbar {
    padding-top: env(safe-area-inset-top);
  }
  .footer {
    padding-bottom: calc(24px + env(safe-area-inset-bottom));
  }
}
