/* === Reset === */
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* === Variables === */
:root {
  --bg: #fff;
  --fg: #111;
  --muted: #666;
  --border: #ddd;
  --accent: #0055aa;
  --danger: #c33;
  /* One step below danger: something to look at, not something
     that failed. Shares its hue with --action-renumber, which is
     the same "worth a look" register. */
  --warning: #8a5a00;
  --radius: 4px;
  --font: system-ui, -apple-system, sans-serif;
  --font-mono: ui-monospace, monospace;

  /* Merge actions. Every one is also written out in words, so
     the colour is a second signal and never the only one. */
  --action-merge: #2a7a2a;
  --action-move: #0055aa;
  --action-renumber: #8a5a00;

  /* A fuse deletes a row on weaker evidence than a merge does --
     no file name agreed -- so it reads as the danger colour and
     not as a shade of merge. */
  --action-fuse: #c33;

  /* Spacing scale (4px base) */
  --space-xs: 0.25rem;   /* 4px */
  --space-sm: 0.5rem;    /* 8px */
  --space-md: 0.75rem;   /* 12px */
  --space-lg: 1rem;      /* 16px */
  --space-xl: 1.5rem;    /* 24px */
  --space-2xl: 2rem;     /* 32px */
  --space-3xl: 3rem;     /* 48px */

  /* Type scale */
  --text-xs: 0.75rem;    /* 12px — badges, small labels */
  --text-sm: 0.875rem;   /* 14px — meta, secondary text */
  --text-base: 1rem;     /* 16px — body */
  --text-lg: 1.25rem;    /* 20px — stat values */
  --text-hero: 3rem;     /* 48px — error page */
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #101010;
    --fg: #e0e0e0;
    --muted: #b0b0b0;
    --border: #444;
    --accent: #5599dd;
    --danger: #e55;
    --warning: #d9a441;
    --action-merge: #6cc06c;
    --action-move: #5599dd;
    --action-renumber: #d9a441;
    --action-fuse: #e55;
  }
}

/* === Base === */
body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--fg);
  line-height: 1.6;
}

a {
  color: var(--accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* === Focus === */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* === Skip link === */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: var(--space-sm) var(--space-lg);
  background: var(--accent);
  color: #fff;
}

.skip-link:focus {
  left: 0;
}

/* === Layout === */
header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  padding: var(--space-md) var(--space-lg);
}

main {
  padding: var(--space-lg);
  max-width: 960px;
  margin: 0 auto;
}

/* === Nav === */
nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-lg);
  align-items: center;
}

nav .brand {
  font-weight: 700;
}

/* === Nav toggle (hamburger) === */
.nav-toggle {
  display: none;
  flex-direction: column;
  gap: 4px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  margin-left: auto;
}

.nav-toggle span {
  display: block;
  width: 20px;
  height: 2px;
  background: var(--fg);
  border-radius: 1px;
  transition: transform 0.2s, opacity 0.2s;
}

/* === Nav menu container === */
.nav-menu {
  display: contents;
}

/* === Nav icon (admin) === */
.nav-icon {
  display: flex;
  align-items: center;
  color: var(--muted);
}

.nav-icon:hover {
  color: var(--accent);
  text-decoration: none;
}

/* === Search form in nav === */
.search-form {
  margin-left: auto;
  display: flex;
  align-items: center;
}

.search-form .search-input {
  width: 12rem;
  padding: var(--space-xs) var(--space-sm);
  margin: 0;
  font-size: var(--text-sm);
}

/* === Mobile nav === */
@media (max-width: 600px) {
  .nav-toggle {
    display: flex;
  }

  .nav-menu {
    display: none;
    width: 100%;
    flex-direction: column;
    gap: var(--space-sm);
    padding-top: var(--space-sm);
  }

  .nav-menu.nav-open {
    display: flex;
  }

  .nav-menu .search-form {
    margin-left: 0;
  }

  .nav-menu .search-form .search-input {
    width: 100%;
  }
}

.search-form.loading .search-input {
  opacity: 0.6;
  cursor: wait;
}

/* === Search results === */
.search-results h1 {
  margin-bottom: var(--space-lg);
}

/* === Buttons === */
/* font-family and line-height are inherited on purpose: the
   reset above does not give form controls their page font, so a
   <button class="btn"> came out in the UA's own Arial with
   line-height normal while an <a class="btn"> beside it used the
   site font. Measured on the story page's action row: 34.0 px
   tall against 40.4 px for the Edit link next to it. */
.btn {
  display: inline-block;
  padding: var(--space-sm) var(--space-lg);
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: var(--radius);
  font-family: inherit;
  font-size: var(--text-base);
  line-height: inherit;
  cursor: pointer;
  text-align: center;
}

.btn:hover:not(:disabled) {
  opacity: 0.9;
  text-decoration: none;
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-danger {
  background: var(--danger);
}

.btn-secondary {
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--border);
  font-size: var(--text-sm);
}

.btn-secondary:hover:not(:disabled) {
  background: var(--border);
  opacity: 1;
}

/* === Cards === */
.card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-lg);
  margin-bottom: var(--space-lg);
}

.card h2 {
  margin-bottom: var(--space-sm);
}

.card p {
  color: var(--muted);
}

/* === Forms === */
label {
  display: block;
  margin-bottom: var(--space-xs);
  font-weight: 600;
}

input[type="text"],
input[type="password"],
input[type="email"],
input[type="search"],
textarea,
select {
  width: 100%;
  padding: var(--space-sm);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--fg);
  font-size: var(--text-base);
  margin-bottom: var(--space-md);
}

/* === Unlock page === */
.unlock {
  max-width: 400px;
  margin: var(--space-2xl) auto;
}

.unlock h1 {
  margin-bottom: var(--space-sm);
}

.unlock p {
  margin-bottom: var(--space-lg);
  color: var(--muted);
}

.unlock button {
  width: 100%;
}

/* === Error messages === */
.error {
  color: var(--danger);
  margin-bottom: var(--space-sm);
}

/* === Admin update page === */
.admin-update {
  margin: var(--space-lg) 0;
}

.admin-update h1 {
  margin-bottom: var(--space-lg);
}

.update-info {
  margin-bottom: var(--space-lg);
  color: var(--muted);
}

.update-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-md);
  margin-bottom: var(--space-xl);
}

.update-progress {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-lg);
}

.progress-row {
  display: flex;
  justify-content: space-between;
  padding: var(--space-xs) 0;
}

.progress-label {
  font-weight: 600;
}

.progress-value {
  font-variant-numeric: tabular-nums;
}

.update-runs {
  margin-top: var(--space-xl);
}

.update-runs h2 {
  margin-bottom: var(--space-md);
  font-size: var(--text-base);
}

.update-runs table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.update-runs th,
.update-runs td {
  padding: var(--space-sm) var(--space-md);
  border-bottom: 1px solid var(--border);
  text-align: left;
}

.update-runs th {
  color: var(--muted);
  font-size: var(--text-sm);
  font-weight: 600;
}

.run-status {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.run-status-running {
  color: var(--accent);
}

.run-status-interrupted {
  color: var(--danger);
}

/* === Series merge preview ===
   The last screen before a destructive action, so it is laid out
   to be read rather than skimmed: the summary is held away from
   the table, and the table shows the series the merge produces
   with each side named. See SPECS.md, "Merges". */
.merge-preview-counts {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-xl);
}

.merge-preview-counts li {
  padding: var(--space-xs) 0;
}

.merge-count {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.merge-preview-merges .merge-count {
  color: var(--action-merge);
}

.merge-preview-moves .merge-count {
  color: var(--action-move);
}

.merge-preview-renumbers .merge-count {
  color: var(--action-renumber);
}

.merge-preview-fuses .merge-count {
  color: var(--action-fuse);
}

.merge-preview-changed {
  margin-bottom: var(--space-xl);
}

.merge-preview-table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--space-xl);
}

.merge-preview-table th,
.merge-preview-table td {
  padding: var(--space-sm) var(--space-md);
  border-bottom: 1px solid var(--border);
  text-align: left;
}

/* The file cells are set smaller and in another face, so the
   cells are aligned on the baseline -- the default, restated
   because it is load-bearing here. Aligning their tops instead
   lines up the line boxes, and text of two sizes sits centred in
   boxes of two heights: the names come out a couple of pixels
   above the number and the action. */
.merge-preview-table td {
  vertical-align: baseline;
}

/* The header stacks a role over a title, and wants its top edge
   level with the single-line columns beside it. */
.merge-preview-table th {
  color: var(--muted);
  font-size: var(--text-sm);
  font-weight: 600;
  vertical-align: top;
}

/* Which column belongs to which series is stated, never
   inferred: the role sits above the series' own title. */
.merge-col-role {
  display: block;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.merge-col-series {
  display: block;
  color: var(--fg);
}

.merge-file {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  word-break: break-all;
}

.merge-none {
  color: var(--muted);
}

.merge-number-was {
  display: block;
  color: var(--muted);
  font-size: var(--text-xs);
}

.merge-action {
  font-variant-caps: small-caps;
  letter-spacing: 0.04em;
  font-weight: 600;
}

.merge-action-merge {
  color: var(--action-merge);
}

.merge-action-move {
  color: var(--action-move);
}

.merge-action-renumber {
  color: var(--action-renumber);
}

.merge-action-fuse {
  color: var(--action-fuse);
}

/* The two file names never agree on a fused row -- that is what
   the action means -- so the row carries the weight that tells
   the operator to read them. */
.merge-preview-fuse .merge-file-name {
  font-weight: 600;
}

.merge-fuse-form {
  margin: 0 0 var(--space-xl);
  padding: var(--space-lg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.merge-fuse-label {
  display: block;
  margin-bottom: var(--space-md);
  color: var(--muted);
  font-size: var(--text-sm);
}

.merge-fuse-check {
  margin-right: var(--space-sm);
}

/* An untouched episode is there to show the result, not because
   anything happens to it, so it recedes. */
.merge-preview-untouched,
.merge-action-untouched {
  color: var(--muted);
}

@media (max-width: 480px) {
  .merge-preview-table thead {
    display: none;
  }

  .merge-preview-table tr {
    display: block;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: var(--space-sm);
    margin-bottom: var(--space-md);
  }

  .merge-preview-table td {
    display: block;
    padding: var(--space-xs) 0;
    border-bottom: none;
    text-align: right;
  }

  /* The label is prose, not a file name: the file cells set a
     monospaced face and the generated content would inherit it. */
  .merge-preview-table td[data-label]::before {
    content: attr(data-label);
    float: left;
    font-family: var(--font);
    font-weight: 600;
    color: var(--muted);
    font-size: var(--text-sm);
  }
}

/* === Collections page === */
.collections h1,
.collection-detail h1,
.series-detail h1 {
  margin-bottom: var(--space-lg);
}

.series-authors {
  margin-bottom: var(--space-lg);
  color: var(--muted);
}

.series-authors a {
  color: var(--accent);
}

.filter-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin-bottom: var(--space-lg);
}

.filter-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.filter-row input[type="search"] {
  flex: 1;
  margin-bottom: 0;
}

.filter-options {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.filter-form label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-weight: normal;
  margin-bottom: 0;
}

.followed-section {
  margin-bottom: var(--space-2xl);
}

.followed-section h2 {
  margin-bottom: var(--space-md);
}

.collection-list {
  display: grid;
  gap: var(--space-md);
}

.collection-card {
  display: block;
  color: var(--fg);
}

.collection-card:hover {
  border-color: var(--accent);
  text-decoration: none;
}

.collection-meta {
  display: flex;
  gap: var(--space-lg);
  color: var(--muted);
  font-size: var(--text-sm);
}

/* === Read progress bar === */
.read-progress {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  color: var(--muted);
  font-size: var(--text-sm);
  margin-top: var(--space-xs);
}

.read-count {
  white-space: nowrap;
}

.read-bar {
  flex: 1;
  height: 4px;
  background: var(--border);
  border-radius: 2px;
  min-width: 3rem;
}

.read-bar-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 2px;
  transition: width 0.3s ease;
}

/* === Breadcrumb === */
.breadcrumb {
  margin-bottom: var(--space-lg);
  color: var(--muted);
  font-size: var(--text-sm);
}

.breadcrumb .sep {
  margin: 0 var(--space-xs);
}

/* === Series & story lists === */
.series-section {
  margin-bottom: var(--space-xl);
}

.series-list {
  list-style: none;
  padding-left: 0;
}

.series-list li {
  padding: var(--space-xs) 0;
}

.story-list {
  list-style: none;
  padding-left: 0;
}

.story-item {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--border);
}

.story-index {
  color: var(--muted);
  min-width: 2rem;
}

.story-title {
  flex: 1;
}

/* === Reading badges === */
.reading-badge {
  font-size: var(--text-xs);
  padding: 0.125rem var(--space-sm);
  border-radius: var(--radius);
}

.badge-unread {
  background: var(--border);
  color: var(--muted);
}

.badge-reading {
  background: var(--accent);
  color: #fff;
}

.badge-finished {
  background: #2a7a2a;
  color: #fff;
}

/* === Pagination === */
.pagination {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  margin-top: var(--space-xl);
  justify-content: center;
}

.page-info {
  color: var(--muted);
}

/* === Story detail page === */
.story-header {
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
  margin-bottom: var(--space-md);
}

.story-header h1 {
  margin: 0;
}

.story-meta {
  margin-bottom: var(--space-xl);
}

.meta-row {
  display: flex;
  gap: var(--space-sm);
  padding: var(--space-xs) 0;
}

.meta-label {
  font-weight: 600;
  min-width: 6rem;
  color: var(--muted);
}

.detail-actions {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
}

.progress-text {
  color: var(--muted);
  font-size: var(--text-sm);
}

.series-nav {
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
  margin-top: var(--space-lg);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border);
}

.series-nav-next {
  margin-left: auto;
}

/* === Author page === */
.author-detail h1 {
  margin-bottom: var(--space-xs);
}

.author-email {
  color: var(--muted);
  font-size: var(--text-sm);
  margin-bottom: var(--space-xl);
}

.author-series {
  margin-bottom: var(--space-xl);
}

.author-series h2 {
  margin-bottom: var(--space-sm);
}

.author-standalone {
  margin-bottom: var(--space-xl);
}

.author-standalone h2 {
  margin-bottom: var(--space-sm);
}

/* === Reader === */
.reader-body {
  margin: 0;
  padding: 0;
}

.reader-bar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  padding: var(--space-sm) var(--space-lg);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

.reader-progress-bar {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 3px;
  width: 0;
  background: var(--accent);
  transition: width 0.15s ease-out;
}

.reader-back {
  flex-shrink: 0;
}

.reader-title {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-sm);
  color: var(--muted);
}

.reader-finish-form {
  flex-shrink: 0;
}

.btn-small {
  padding: var(--space-xs) var(--space-md);
  font-size: var(--text-sm);
}

.reader-finish-form .btn-small {
  background: transparent;
  color: var(--muted);
  border: 1px solid transparent;
  font-size: var(--text-xs);
  opacity: 0.7;
  transition: opacity 0.2s, border-color 0.2s;
}

.reader-finish-form .btn-small:hover {
  opacity: 1;
  border-color: var(--border);
}

.reader-menu {
  position: relative;
  flex-shrink: 0;
}

.reader-menu-toggle {
  background: transparent;
  color: var(--muted);
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--text-sm);
  line-height: 1;
  cursor: pointer;
  font-family: inherit;
  opacity: 0.7;
  transition: opacity 0.2s, border-color 0.2s;
}

.reader-menu-toggle:hover,
.reader-menu-toggle[aria-expanded="true"] {
  opacity: 1;
  border-color: var(--border);
}

/* Keeps [hidden] winning over any display given to these.
   The list's box is shared with .apply-author-menu. */
.reader-menu[hidden],
.reader-menu-list[hidden] {
  display: none;
}

.reader-dim[aria-pressed="true"]::before {
  content: "\2713\00a0";
}

/* Below WCAG contrast on purpose: chosen by the reader for a
   dark room. See SPECS.md section 8.4. */
@media (prefers-color-scheme: dark) {
  .reader-dimmed {
    --bg: #000;
    --fg: #555;
    --muted: #444;
    --border: #222;
    --accent: #2f4a66;
  }

  /* These paint #fff on the accent, which no token reaches. */
  .reader-dimmed .reader-next .btn,
  .reader-dimmed .reader-pdf-original .btn,
  .reader-dimmed .reader-menu-list button:hover {
    color: var(--fg);
  }
}

.reader-content {
  max-width: var(--reader-max-width, 680px);
  margin: 0 auto;
  padding: var(--space-2xl) var(--space-lg);
  font-family: var(--reader-font-family, serif);
  font-size: var(--reader-font-size, 18px);
  line-height: var(--reader-line-height, 1.7);
  text-align: justify;
}

.reader-html {
  word-wrap: break-word;
}

.reader-html p {
  margin-bottom: 1em;
}

.reader-pdf {
  width: 100%;
  height: calc(100vh - 3rem);
  border: none;
}

.reader-pdf-original {
  margin-top: var(--space-xl);
  text-align: center;
}

.reader-next {
  margin-top: var(--space-3xl);
  padding-top: var(--space-xl);
  border-top: 1px solid var(--border);
  text-align: center;
}

/* === Homepage === */
.homepage h2 {
  margin-bottom: var(--space-md);
}

.home-section {
  margin-bottom: var(--space-2xl);
}

.home-section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}

.home-section-header h2 {
  margin-bottom: 0;
}

.home-view-all {
  font-size: var(--text-sm);
}

.home-story-list {
  list-style: none;
  padding-left: 0;
}

.home-story-item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs) var(--space-sm);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--border);
}

.home-story-main {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  flex: 1;
  min-width: 0;
}

.home-story-main .story-title {
  flex: 1;
}

.home-story-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-sm);
  font-size: var(--text-sm);
  color: var(--muted);
  width: 100%;
}

.meta-collection::before,
.meta-series::before {
  content: "·";
  margin-right: var(--space-sm);
}

.meta-collection a,
.meta-series a {
  color: var(--muted);
}

.meta-collection a:hover,
.meta-series a:hover {
  color: var(--accent);
}

.meta-date {
  font-variant-numeric: tabular-nums;
}

.story-reading-time {
  font-variant-numeric: tabular-nums;
}

.story-reading-time::before {
  content: "·";
  margin-right: var(--space-sm);
}

.story-author {
  color: var(--muted);
  font-size: var(--text-sm);
}

.update-banner {
  padding: var(--space-md) var(--space-lg);
  margin-bottom: var(--space-xl);
  background: var(--accent);
  color: #fff;
  border-radius: var(--radius);
  text-align: center;
}

.update-banner a {
  color: #fff;
  font-weight: 600;
}

/* === Admin dashboard === */
.admin-dashboard h1 {
  margin-bottom: var(--space-lg);
}

.dashboard-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-md);
  margin-bottom: var(--space-xl);
}

.stat-card {
  text-align: center;
}

.stat-label {
  display: block;
  color: var(--muted);
  font-size: var(--text-sm);
  margin-bottom: var(--space-xs);
}

.stat-value {
  display: block;
  font-size: var(--text-lg);
  font-weight: 700;
}

/* The qualifier under a stat: which kind of run is next, or how
   long the one in flight has been going. Muted and small, because
   it is what the value means and not the value -- and rendered
   only when it has something to say, so no card grows a blank
   line. */
.stat-note {
  display: block;
  color: var(--muted);
  font-size: var(--text-xs);
  margin-top: var(--space-xs);
}

.admin-nav {
  display: flex;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

/* === Admin settings === */
.admin-settings h1 {
  margin-bottom: var(--space-lg);
}

.settings-section {
  margin-bottom: var(--space-2xl);
}

.settings-section h2 {
  margin-bottom: var(--space-md);
}

/* Says what a field means, above the fields it applies to. */
.settings-note {
  color: var(--muted);
  font-size: var(--text-sm);
  margin-bottom: var(--space-md);
  max-width: 60ch;
}

.danger-zone {
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  padding: var(--space-lg);
}

.danger-form {
  margin-bottom: var(--space-lg);
}

.danger-form p {
  color: var(--muted);
  margin-bottom: var(--space-sm);
}

.success {
  color: #2a7a2a;
  margin-bottom: var(--space-sm);
}

/* === Admin sessions === */
.admin-sessions h1 {
  margin-bottom: var(--space-lg);
}

.sessions-table {
  width: 100%;
  border-collapse: collapse;
}

.sessions-table th,
.sessions-table td {
  text-align: left;
  padding: var(--space-sm);
  border-bottom: 1px solid var(--border);
}

.sessions-table th {
  font-weight: 600;
  color: var(--muted);
  font-size: var(--text-sm);
}

.ua-cell {
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.badge-current {
  font-size: var(--text-xs);
  padding: 0.125rem var(--space-sm);
  border-radius: var(--radius);
  background: #2a7a2a;
  color: #fff;
}

@media (max-width: 480px) {
  .sessions-table thead {
    display: none;
  }

  .sessions-table tr {
    display: block;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: var(--space-sm);
    margin-bottom: var(--space-md);
  }

  .sessions-table td {
    display: block;
    padding: var(--space-xs) 0;
    border-bottom: none;
    text-align: right;
  }

  .sessions-table td[data-label]::before {
    content: attr(data-label);
    float: left;
    font-weight: 600;
    color: var(--muted);
    font-size: var(--text-sm);
  }

  .sessions-table .ua-cell {
    max-width: none;
    white-space: normal;
    overflow: visible;
  }
}

/* === Passkey on unlock === */
.passkey-btn {
  width: 100%;
  margin-bottom: var(--space-md);
}

.passkey-error {
  margin-bottom: var(--space-sm);
}

.unlock-divider {
  text-align: center;
  margin: var(--space-md) 0;
  color: var(--muted);
  font-size: var(--text-sm);
}

.passkey-status {
  margin-top: var(--space-sm);
  color: var(--muted);
}

/* === Error page === */
.error-page {
  text-align: center;
  padding: var(--space-3xl) var(--space-lg);
}

.error-page h1 {
  font-size: var(--text-hero);
  color: var(--muted);
  margin-bottom: var(--space-sm);
}

.error-page p {
  margin-bottom: var(--space-xl);
}

/* === Autocomplete === */
.autocomplete-wrap {
  position: relative;
}

.autocomplete-list {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 20;
  background: var(--bg);
  border: 1px solid var(--border);
  border-top: none;
  border-radius: 0 0 var(--radius) var(--radius);
  max-height: 200px;
  overflow-y: auto;
}

.autocomplete-list:empty {
  display: none;
}

.autocomplete-item {
  padding: var(--space-sm);
  cursor: pointer;
}

.autocomplete-item:hover,
.autocomplete-active {
  background: var(--accent);
  color: #fff;
}

.autocomplete-email,
.autocomplete-author {
  color: var(--muted);
  font-size: var(--text-sm);
}

.autocomplete-item:hover .autocomplete-email,
.autocomplete-active .autocomplete-email,
.autocomplete-item:hover .autocomplete-author,
.autocomplete-active .autocomplete-author {
  color: rgba(255, 255, 255, 0.7);
}

/* === Merge suggestions === */
.merge-suggestions {
  margin-bottom: var(--space-xl);
}

.merge-suggestions h2 {
  font-size: var(--text-base);
  margin-bottom: var(--space-sm);
}

.suggestion-list {
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.suggestion-item {
  padding: var(--space-sm);
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-sm);
}

.suggestion-item:not(:last-child) {
  border-bottom: 1px solid var(--border);
}

.suggestion-item:hover {
  background: var(--accent);
  color: #fff;
}

.suggestion-authors {
  color: var(--muted);
  font-size: var(--text-sm);
  white-space: nowrap;
}

.suggestion-item:hover .suggestion-authors {
  color: rgba(255, 255, 255, 0.7);
}

/* === Tag pills in story lists === */
.meta-tag {
  display: inline-flex;
  align-items: center;
  font-size: var(--text-xs);
  line-height: 1;
  padding: 0.25rem 0.4rem;
  border-radius: var(--radius);
  background: var(--border);
  color: var(--fg);
  text-decoration: none;
  white-space: nowrap;
}

.meta-tag:hover {
  background: var(--accent);
  color: #fff;
  text-decoration: none;
}

.meta-tags {
  display: flex;
  gap: var(--space-xs) var(--space-sm);
  flex-wrap: wrap;
}

/* === Tag chip input === */
.tag-input-wrap {
  margin-bottom: var(--space-md);
}

.tag-chips {
  display: flex;
  gap: var(--space-xs);
  flex-wrap: wrap;
  margin-bottom: var(--space-sm);
}

.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 0.2rem 0.5rem;
  background: var(--accent);
  color: #fff;
  border-radius: var(--radius);
  font-size: var(--text-sm);
}

.tag-remove {
  background: none;
  border: none;
  color: #fff;
  cursor: pointer;
  font-size: var(--text-base);
  line-height: 1;
  padding: 0 0.15rem;
  opacity: 0.7;
}

.tag-remove:hover {
  opacity: 1;
}

/* The chip background is --accent, so the default focus ring is
   invisible: use the chip text color inside, and outline the whole
   chip against the page so the focused tag stands out. */
.tag-remove:focus-visible {
  outline-color: #fff;
  opacity: 1;
}

.tag-chip:has(.tag-remove:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* === Episode bulk edit === */
.episode-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-xs);
}

.episode-index {
  min-width: 2rem;
  text-align: right;
  color: var(--muted);
}

.episode-title-input,
.episode-slug-input {
  flex: 1;
  margin-bottom: 0;
}

.pattern-section {
  margin-bottom: var(--space-lg);
}

.pattern-row {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
}

.pattern-row input {
  flex: 1;
  margin-bottom: 0;
}

.pattern-help {
  font-size: var(--text-sm);
  color: var(--muted);
  margin-top: var(--space-xs);
}

.episodes-section {
  margin-bottom: var(--space-lg);
}

/* === Empty state === */
.empty {
  color: var(--muted);
  padding: var(--space-2xl) 0;
}

/* === Reading list button === */
.reading-list-btn.in-reading-list {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}

.reading-list-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* === Followed === */
.detail-header {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.follow-btn {
  padding: 0.35rem 0.75rem;
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--text-sm);
  cursor: pointer;
}

.follow-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.follow-btn.is-followed {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}

.follow-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.follow-bookmark {
  color: var(--muted);
  margin-right: var(--space-xs);
}

.follow-bookmark svg {
  vertical-align: -0.125em;
}

.follow-bookmark svg path {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linejoin: round;
}

.follow-bookmark[data-slug] {
  cursor: pointer;
}

.follow-bookmark.is-followed {
  color: #f5a623;
}

.follow-bookmark.is-followed svg path {
  fill: currentColor;
}

/* === Apply author (inline action) === */
.apply-author-action {
  position: relative;
  margin-left: auto;
}

.link-action {
  background: none;
  border: none;
  color: var(--muted);
  font-size: var(--text-sm);
  cursor: pointer;
  padding: 0;
  font-family: inherit;
}

.link-action:hover {
  color: var(--accent);
  text-decoration: underline;
}

.apply-author-menu {
  display: none;
}

.apply-author-menu,
.reader-menu-list {
  position: absolute;
  right: 0;
  top: 100%;
  margin-top: var(--space-xs);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  min-width: 10rem;
  z-index: 20;
}

.apply-author-menu.open {
  display: block;
}

.apply-author-menu button,
.reader-menu-list button {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: var(--space-sm) var(--space-md);
  color: var(--fg);
  font-size: var(--text-sm);
  cursor: pointer;
  font-family: inherit;
}

.apply-author-menu button:hover,
.reader-menu-list button:hover {
  background: var(--accent);
  color: #fff;
}

.apply-author-menu form + form button {
  border-top: 1px solid var(--border);
}

/* === Tablet (600-960px) === */
@media (min-width: 601px) and (max-width: 960px) {
  .collection-list {
    grid-template-columns: repeat(2, 1fr);
  }

  .detail-actions {
    flex-wrap: wrap;
  }
}

/* === Toast notifications === */
.toast {
  position: fixed;
  bottom: var(--space-lg);
  left: 50%;
  transform: translateX(-50%) translateY(1rem);
  background: var(--danger);
  color: #fff;
  padding: var(--space-sm) var(--space-lg);
  border-radius: var(--radius);
  font-size: var(--text-sm);
  z-index: 100;
  opacity: 0;
  transition: opacity 0.3s, transform 0.3s;
  pointer-events: none;
}

.toast-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* === Keyboard shortcuts === */
.shortcuts-overlay {
  position: fixed;
  inset: 0;
  background: rgb(0 0 0 / 50%);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-lg);
  z-index: 200;
}

.shortcuts-overlay[hidden] {
  display: none;
}

.shortcuts-panel {
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-xl);
  max-width: 32rem;
  width: 100%;
  max-height: 80vh;
  overflow-y: auto;
}

.shortcuts-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  margin-bottom: var(--space-lg);
}

.shortcuts-close {
  background: none;
  border: none;
  color: var(--muted);
  font-size: var(--text-lg);
  line-height: 1;
  cursor: pointer;
  padding: var(--space-xs) var(--space-sm);
}

.shortcuts-close:hover {
  color: var(--fg);
}

.shortcuts-list {
  list-style: none;
  display: grid;
  gap: var(--space-sm);
}

.shortcut-row {
  display: grid;
  grid-template-columns: 4rem 1fr;
  align-items: baseline;
  gap: var(--space-md);
}

.shortcut-row kbd {
  font-family: ui-monospace, monospace;
  font-size: var(--text-sm);
  background: var(--border);
  border-radius: var(--radius);
  padding: var(--space-xs) var(--space-sm);
  text-align: center;
}

.shortcut-desc {
  color: var(--muted);
  font-size: var(--text-sm);
}

/* The sticky header would otherwise cover the row that
   j and k just scrolled into view. */
.home-story-item,
.collection-card {
  scroll-margin-top: 5rem;
  scroll-margin-bottom: var(--space-lg);
}

.kbd-selected {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-radius: var(--radius);
}

/* The highlighted row already says which story is selected, so
   the ring is dropped on the selected row alone: a title reached
   with Tab keeps it. See SPECS.md, "Keyboard shortcuts". */
.kbd-selected .story-title:focus-visible {
  outline: none;
}

/* === Story preview panel === */

/* The [hidden] companion is not optional: a flex overlay
   ignores the attribute, and the panel would cover the site
   from first paint. */
.preview-overlay {
  position: fixed;
  inset: 0;
  background: rgb(0 0 0 / 50%);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-lg);
  z-index: 200;
}

.preview-overlay[hidden] {
  display: none;
}

.preview-panel {
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  max-width: 44rem;
  width: 100%;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
}

.preview-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--border);
}

.preview-label {
  color: var(--muted);
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.preview-close {
  background: none;
  border: none;
  color: var(--muted);
  font-size: var(--text-lg);
  line-height: 1;
  cursor: pointer;
  padding: var(--space-xs) var(--space-sm);
}

.preview-close:hover {
  color: var(--fg);
}

/* The scroll is the point of the character budget: the panel
   shows the head of the story and the rest of the preamble is
   one flick away. */
.preview-content {
  overflow-y: auto;
  padding: var(--space-lg);
}

.preview-title {
  font-size: var(--text-lg);
  margin-bottom: var(--space-xs);
}

.preview-meta,
.preview-notice {
  color: var(--muted);
  font-size: var(--text-sm);
  margin-bottom: var(--space-md);
}

.preview-body p {
  margin-bottom: var(--space-md);
}

/* Story bodies carry their own headings, and a chapter h2 set
   at page scale outranks the panel's own title. They are
   flattened to body size here: this is an excerpt, not a
   document. */
.preview-body h1,
.preview-body h2,
.preview-body h3,
.preview-body h4,
.preview-body h5,
.preview-body h6 {
  font-size: var(--text-base);
  margin-bottom: var(--space-sm);
}

.preview-actions {
  display: flex;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
}

/* === Preview trigger (story lists) === */

/* The detail page's trigger carries .btn as well, and these
   rules would strip its border and padding: same specificity,
   declared later, so it would win the cascade and render as
   bare text beside the Read button. Hence :not(.btn). */
.preview-btn:not(.btn) {
  background: none;
  border: none;
  padding: 0;
  color: var(--muted);
  margin-right: var(--space-xs);
  cursor: pointer;
}

.preview-btn:not(.btn):hover {
  color: var(--accent);
}

.preview-btn svg {
  vertical-align: -0.125em;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linejoin: round;
}

/* === Reading list flag (story lists) === */
.reading-flag {
  color: var(--muted);
  margin-right: var(--space-xs);
  cursor: pointer;
}

.reading-flag svg {
  vertical-align: -0.125em;
}

.reading-flag svg path {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linejoin: round;
}

/* Deliberately not the amber of .follow-bookmark: the two
   states sit side by side and must stay tellable apart. */
.reading-flag.in-reading-list {
  color: var(--accent);
}

.reading-flag.in-reading-list svg path {
  fill: currentColor;
}

/* A second guard under cardReadPercent's clamp. The width comes
   from a computed percentage, and an overflowing fill would widen
   the whole document rather than just the bar. */
.read-bar-fill {
  max-width: 100%;
}

/* === Hide toggle (detail pages) === */
.hide-toggle {
  color: var(--muted);
  margin-right: var(--space-xs);
  cursor: pointer;
}

.hide-toggle svg {
  vertical-align: -0.125em;
}

.hide-toggle svg path {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linejoin: round;
}

/* A third colour, for the same reason the reading flag is not
   the bookmark's amber: all three can sit on one heading. */
.hide-toggle.is-hidden {
  color: var(--warning);
}

.hide-toggle.is-hidden svg path {
  fill: currentColor;
}

/* Inert: the thing is hidden by a parent, so there is nothing
   here to write. It still has to read as hidden, hence the state
   colour, but it must not invite a click. */
.hide-toggle.is-inert {
  cursor: default;
  opacity: 0.55;
}

/* In flight. A class rather than an inline style, so the state
   is testable and the rule lives with the rest of them. */
.hide-toggle.is-busy {
  opacity: 0.5;
  pointer-events: none;
}

/* The same archive box as the toggle, and the same state colour,
   but on a listing row: a mark, not a control. No cursor, because
   it does nothing -- the flag is written from the story's own
   page. */
.hidden-badge {
  color: var(--warning);
  margin-right: var(--space-xs);
}

.hidden-badge svg {
  vertical-align: -0.125em;
}

.hidden-badge svg path {
  fill: currentColor;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linejoin: round;
}

.hidden-cause {
  margin: var(--space-sm) 0;
  padding: var(--space-sm) var(--space-md);
  border-left: 3px solid var(--warning);
  color: var(--muted);
}

/* Upstream paths on the story and series edit forms. The control
   is a real checkbox so the form works with no JavaScript: the
   box itself is hidden and its label carries the icon, which is
   why the focus ring has to be moved onto the label. */
.upstream-paths {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-sm);
  margin: var(--space-md) 0;
}

.upstream-paths legend {
  color: var(--muted);
  font-size: var(--text-sm);
  padding: 0 var(--space-xs);
}

.upstream-path-row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-xs);
  padding: 0.2rem 0;
}

.upstream-remove {
  color: var(--muted);
  cursor: pointer;
  line-height: 1;
}

.upstream-remove input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.upstream-remove:hover {
  color: var(--danger);
}

/* Checked is the state that matters, since it says what Save is
   about to do, so it reads as danger and the path is struck
   through with it. */
.upstream-remove:has(input:checked) {
  color: var(--danger);
}

.upstream-remove:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.upstream-path-row:has(input:checked) a,
.upstream-path-row:has(input:checked)
  .upstream-path-broken {
  text-decoration: line-through;
  color: var(--danger);
}

/* An id no URL can be rebuilt from. It is shown, not hidden,
   because removing it is the only thing that can be done with
   it. See SPECS.md, "Upstream path removal". */
.upstream-path-broken {
  color: var(--danger);
}

.upstream-path-note {
  flex-basis: 100%;
  color: var(--muted);
  font-size: var(--text-xs);
  margin: 0 0 0 1.5rem;
}

/* Scrape run failures, live and in the runs table. The reason
   is the part worth scanning, so it carries the colour; the URL
   is monospaced so a shared prefix lines up down the list. */
.run-issues-live,
.run-issue-list {
  list-style: none;
  padding: 0;
  margin: 0.5rem 0 0;
}

.run-issue {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.25rem 0;
  font-size: var(--text-sm);
}

.run-issue-reason {
  flex: 0 0 auto;
  font-weight: 600;
  color: var(--muted);
}

/* An empty listing fails the same way on every later run, so it
   is the one reason that needs a human and the only one coloured
   as such. See SPECS.md, "Caching and revalidation". */
.run-issue-empty-listing .run-issue-reason {
  color: var(--danger);
}

/* The status a slug cannot carry: a 404 will fail the same way
   tomorrow, a 503 will not. */
.run-issue-status {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.run-issue-url {
  flex: 1 1 20rem;
  font-family: monospace;
  word-break: break-all;
}

/* Some rows link and some have no page to point at, and links
   carry no underline anywhere else here -- so without this the
   only difference is colour, on a screen whose whole job is to
   say where to look. */
.run-issue-url:any-link {
  text-decoration: underline;
}

/* The stories column carries no rule of its own: .update-runs
   table already sets tabular-nums and it inherits, and the count
   takes no colour -- nought stories is a fact about the run, not
   a fault. Its class is on the cell for the tests to aim at. */

/* The two counters mean different things, so they are coloured
   apart. Only a count that needs a look is coloured: a zero stays
   the body colour, or a column of noughts would shout. Both are
   always printed, so a zero is never a missing value. */
.run-counts {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.run-count {
  font-weight: 600;
}

.run-count-errors {
  color: var(--danger);
}

.run-count-rejected {
  color: var(--warning);
}

.run-count-sep {
  color: var(--muted);
  margin: 0 0.15rem;
}

/* The failures span the table on a row of their own: folded into
   the counts cell they wrapped against a column sized for two
   numbers, which is what made a long upstream id unreadable.

   A run and its detail are one entry, so the rule between them
   goes: the run row keeps its border only when nothing follows
   it. The border below the detail row is what separates the
   entry from the next run. */
.run-row:has(+ .run-detail-row) > td {
  border-bottom: 0;
}

.run-detail-row > td {
  padding-top: 0;
}

.run-issues summary {
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--muted);
}

.run-issues-truncated {
  margin: 0.5rem 0 0;
  font-size: var(--text-xs);
  color: var(--danger);
}

/* Story merge confirmation. The word counts are context beside
   the byte verdict, so they are quiet; the override is not, and
   sits close to the button it unlocks.
   See SPECS.md, "Merges". */
.merge-word-counts {
  list-style: none;
  padding: 0;
  margin: 0 0 1rem;
  color: var(--muted);
  font-size: var(--text-sm);
}

.merge-force {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 1rem 0;
  color: var(--danger);
}

/* Integrity page. Checks that found nothing are rendered too, so
   the quiet state has to read as a result rather than as
   absence. See SPECS.md, "Admin: integrity page". */
.integrity-intro {
  color: var(--muted);
  max-width: 42rem;
}

.integrity-check {
  border-top: 1px solid var(--border);
  padding-top: var(--space-sm);
  margin-top: var(--space-md);
}

.integrity-check h3 {
  margin: 0 0 0.2rem;
}

.integrity-detail {
  color: var(--muted);
  font-size: var(--text-sm);
  max-width: 42rem;
  margin: 0 0 var(--space-xs);
}

.integrity-clear {
  color: var(--muted);
  font-size: var(--text-sm);
  margin: 0;
}

.integrity-capped {
  color: var(--danger);
  font-size: var(--text-sm);
  margin: 0 0 var(--space-xs);
}

.integrity-findings {
  margin: 0;
  padding-left: 1.2rem;
}

.integrity-finding {
  margin: 0.15rem 0;
}

.integrity-note {
  color: var(--muted);
  font-size: var(--text-sm);
  margin-left: var(--space-xs);
}

/* Duplicate review queue. Both sections are rendered even when
   empty, so the quiet state has to read as a result rather than
   as absence -- same rule as the integrity page.
   See SPECS.md, "Admin: duplicate review". */
.duplicate-intro {
  color: var(--muted);
  max-width: 42rem;
}

.duplicate-links {
  font-size: var(--text-sm);
  margin: 0 0 var(--space-md);
}

.duplicate-section {
  border-top: 1px solid var(--border);
  padding-top: var(--space-sm);
  margin-top: var(--space-md);
}

.duplicate-detail {
  color: var(--muted);
  font-size: var(--text-sm);
  max-width: 42rem;
  margin: 0 0 var(--space-sm);
}

.duplicate-empty {
  color: var(--muted);
  font-size: var(--text-sm);
  margin: 0;
}

.duplicate-capped {
  color: var(--danger);
  font-size: var(--text-sm);
  margin: var(--space-xs) 0 0;
}

/* One form per group, with both buttons under the table. Two
   sibling forms laid side by side made the second button follow
   the width of the longest title, so it landed somewhere
   different in every group. */
.duplicate-group {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: var(--space-sm);
  padding: var(--space-sm);
}

.duplicate-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.duplicate-members {
  border-collapse: collapse;
  margin-bottom: var(--space-xs);
}

.duplicate-members th {
  color: var(--muted);
  font-size: var(--text-sm);
  font-weight: normal;
  padding-right: var(--space-sm);
  text-align: left;
}

.duplicate-members td {
  padding-right: var(--space-sm);
}

.duplicate-author {
  color: var(--muted);
  font-size: var(--text-sm);
}

.duplicate-author-none {
  font-style: italic;
}

.duplicate-ignored {
  align-items: center;
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  padding: var(--space-xs) 0;
}

.duplicate-ignored-kind {
  color: var(--muted);
  font-size: var(--text-sm);
}

.duplicate-ignored-members {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
}
