/* src/web/static/css/source-dossier.css
   Compact source-comparison MATRIX in the search detail modal.
   Fields are rows, sources are columns — so one field reads straight across
   without scrolling, and the whole block stays short. */

.dossier { margin: 16px 0 8px; }
.dossier-head {
  font-family: ui-monospace, "JetBrains Mono", monospace;
  font-size: 10px; letter-spacing: .2em; text-transform: uppercase; color: var(--text-muted);
  margin-bottom: 8px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.dossier-head .canon-flag { color: var(--accent-canonical); }
.dossier-head .ref-note {
  margin-left: auto; text-transform: none; letter-spacing: .02em;
  color: var(--text-muted); font-size: 10.5px;
}

.dossier-matrix-wrap {
  overflow-x: auto;
  border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 12px 32px -24px rgba(0,0,0,.55);
}
table.dossier-matrix {
  border-collapse: separate; border-spacing: 0; width: 100%;
  font-family: ui-monospace, "JetBrains Mono", monospace; font-size: 12px;
}
.dossier-matrix th, .dossier-matrix td {
  padding: 8px 11px; text-align: left; vertical-align: top;
  border-bottom: 1px solid var(--border);
}
.dossier-matrix tbody tr:last-child td { border-bottom: 0; }

/* sticky field-label column (stays put on horizontal scroll) */
.dossier-matrix .rowhead {
  position: sticky; left: 0; z-index: 2; background: var(--surface-2);
  font-size: 9px; letter-spacing: .13em; text-transform: uppercase; color: var(--text-muted);
  border-right: 1px solid var(--border); white-space: nowrap; width: 1%;
}
.dossier-matrix .rowhead.corner { background: var(--surface); }

/* source header columns — flexible width so 2-4 sources fill the row with
   no horizontal scroll; a low floor keeps them legible, 5+ sources scroll. */
.dossier-matrix thead th.srccol {
  width: auto; min-width: 116px; cursor: pointer; background: var(--surface);
  border-top: 3px solid var(--ink); border-bottom: 1px solid var(--border);
  transition: background .15s;
}
.dossier-matrix thead th.srccol:hover { background: color-mix(in srgb, var(--ink) 7%, var(--surface)); }
.source-record-header { min-width: 0; }
.source-record-header .name {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  color: var(--text);
  font-weight: 700;
}
.source-record-header .dot {
  width: 8px;
  height: 8px;
  flex: 0 0 8px;
  border-radius: 50%;
  background: var(--ink, var(--accent-canonical));
}
.source-record-header .seal-tag {
  margin-left: auto;
  color: var(--accent-canonical);
}
.source-record-header .source-auto-tag {
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 600;
}
.source-record-header .source-id {
  margin-top: 0.35rem;
  color: var(--text-muted);
  font-size: 0.74rem;
  font-weight: 500;
  overflow-wrap: anywhere;
}
.source-record-header .source-attribution {
  margin-top: 0.3rem;
  color: var(--text-muted);
  font-size: 0.74rem;
  font-weight: 500;
  overflow-wrap: anywhere;
}
.source-record-header .source-attribution a {
  color: var(--text);
  font-weight: 600;
}
.source-record-header .source-external-link:focus-visible {
  border-radius: var(--radius-sm);
  outline: 2px solid var(--accent-canonical);
  outline-offset: 3px;
}
.srccol .name {
  font-weight: 700; font-size: 12.5px; display: flex; align-items: center; gap: 6px;
  color: var(--text);
}
.srccol .name .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ink); }
.srccol .name .seal-tag { color: var(--accent-canonical); margin-left: auto; }
.srccol .price { font-weight: 700; color: var(--ink); margin-top: 3px; font-size: 13px; }
.srccol .price .best {
  font-size: 8.5px; letter-spacing: .1em; margin-left: 5px; color: var(--success);
  border: 1px solid var(--success); border-radius: 3px; padding: 0 4px; vertical-align: 1px;
}
.source-external-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 6px;
  color: var(--ink);
  font-size: 10px;
  font-weight: 700;
  text-decoration: none;
}
.source-external-link:hover {
  color: color-mix(in srgb, var(--ink) 82%, var(--text));
  text-decoration: underline;
}
.srccol .thact { margin-top: 8px; }
.srccol .thact .seal {
  font-size: 8.5px; letter-spacing: .14em; text-transform: uppercase; font-weight: 700;
  color: #1c160b; background: var(--accent-canonical); border-radius: 3px; padding: 3px 8px;
}
.srccol .thact .btn-truth {
  font-size: 8.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--text-muted);
  background: transparent; border: 1px dashed var(--border); border-radius: 3px;
  padding: 3px 8px; cursor: pointer;
}
.srccol .thact .btn-truth:hover { color: var(--text); border-color: var(--border); }

/* reference + canonical column tinting */
.dossier-matrix th.srccol.is-canonical { background: var(--glass-bg); border-top-color: var(--accent-canonical); }
.dossier-matrix td.canoncol { background: var(--glass-bg); }
.dossier-matrix th.srccol.is-ref:not(.is-canonical) { background: var(--surface-2); }
.dossier-matrix td.refcol:not(.canoncol) { background: var(--surface-2); }

/* value cells */
.dossier-matrix tbody td { color: var(--text); max-width: 300px; white-space: normal; word-break: break-word; }
.dossier-matrix tbody td.muted { color: var(--text-muted); }
.dossier-matrix tbody td.diff { color: var(--accent-interest); background: rgba(var(--accent-interest-rgb), .07); }
.dossier-matrix tbody td.diff::after { content: " \2260"; color: var(--accent-interest); font-weight: 700; }
.dossier-matrix tbody td.field-canoncol {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-canonical) 65%, transparent);
}
.source-field-cover {
  display: block;
  width: 54px;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
/* Folded cells: prose over 240 characters and every list/dict field. A single
   Description can run to a few thousand characters, which without this pushes
   every row below it off the screen and makes the matrix unreadable — the
   opposite of what widening it was for. */
.source-field-blob > summary {
  cursor: pointer;
  color: var(--text-muted);
  font-size: 11px;
  letter-spacing: .04em;
}
.source-field-blob[open] > summary { margin-bottom: 4px; }
.source-field-blob code,
.source-field-blob .source-field-text {
  display: block;
  max-height: 16rem;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 11px;
  line-height: 1.45;
  color: var(--text);
}
/* Fields outside the curated head are appended alphabetically; the rule keeps
   the two halves of the matrix visually distinct. */
.dossier-matrix tbody tr.is-extended-field > td.rowhead { color: var(--text-muted); }
.dossier-matrix tbody tr.is-first-extended > td { border-top: 1px solid var(--border); }

.btn-field-truth {
  display: block; margin-top: 5px; font-size: 8px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-muted); background: transparent;
  border: 1px dashed var(--border); border-radius: 3px; padding: 2px 6px;
  cursor: pointer;
}
.btn-field-truth:hover { color: var(--text); border-color: var(--border); }
.btn-field-truth.is-active {
  color: #1c160b; background: var(--accent-canonical);
  border-color: var(--accent-canonical);
}

/* per-source ink (applied to the header <th>) */
.ink-prh { --ink: #2f7d57; } .ink-ist { --ink: #1a8a55; }
.ink-previewsworld { --ink: #c97a2c; } .ink-locg { --ink: #7b54c4; }
.ink-gcd { --ink: #3d78b8; }
.ink-lunar { --ink: #c79a1f; } .ink-edelweiss { --ink: #c2447e; }
.ink-fantagraphics { --ink: #b84f3d; } .ink-google_books { --ink: #3d78b8; }
.ink-unknown { --ink: #6c757d; }

.dossier-source-actions {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 0.75rem;
}

.dossier-source-full-link {
  color: var(--accent-canonical);
  font-size: 0.88rem;
  font-weight: 700;
  text-decoration: none;
}

.dossier-source-full-link:hover {
  color: color-mix(in srgb, var(--accent-canonical) 82%, var(--text));
  text-decoration: underline;
}

.edition-source-page {
  min-height: 100vh;
  background: var(--bg);
}

.edition-source-hero {
  padding: 2rem 0 1rem;
  border-bottom: 1px solid var(--border);
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--surface-2) 78%, transparent), transparent);
}

.edition-source-titlebar {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  gap: 1.1rem;
  align-items: center;
  margin-top: 1rem;
}

.edition-source-cover {
  width: 96px;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
}

.edition-source-titlebar h1 {
  margin: 0;
  color: var(--text);
  font-size: clamp(1.75rem, 4vw, 3rem);
  line-height: 1.05;
}

.edition-source-subtitle {
  max-width: 70ch;
  margin: 0.6rem 0 0;
  color: var(--text-muted);
}

.edition-source-content {
  padding-top: 1.25rem;
  padding-bottom: 3rem;
}

.edition-source-page .dossier-matrix-wrap {
  max-height: none;
}

.edition-source-page .dossier-matrix .source-id {
  margin-top: 0.35rem;
  color: var(--text-muted);
  font-size: 0.74rem;
  font-weight: 500;
  word-break: break-word;
}

@media (max-width: 640px) {
  .source-record-header .source-external-link {
    min-height: 44px;
    padding-block: 10px;
  }

  .edition-source-titlebar {
    grid-template-columns: 72px minmax(0, 1fr);
  }

  .edition-source-cover {
    width: 72px;
  }
}

/* reconciled result-card badge */
.comic-card-reconciled-badge {
  position: absolute; left: 8px; bottom: 8px; font-size: 10px; font-weight: 600;
  background: rgba(var(--accent-canonical-rgb), .92); color: #1c160b; border-radius: 4px;
  padding: 2px 7px; display: inline-flex; align-items: center; gap: 4px;
}

/* Secondary-membership badge — muted, bottom-right corner of cover */
.comic-card-secondary-badge {
  position: absolute; right: 8px; bottom: 8px; font-size: 10px; font-weight: 500;
  background: rgba(0, 0, 0, .55); color: #ccc; border-radius: 4px;
  padding: 2px 6px; pointer-events: none;
}

/* Best Guess column — gold, leading reference column */
.dossier-matrix .bestguess-col { --ink: var(--accent-canonical); }
.dossier-matrix thead th.srccol.bestguess-col {
  background: var(--glass-bg); border-top-color: var(--accent-canonical); cursor: default;
}
.dossier-matrix thead th.srccol.bestguess-col .name { color: #7a5d12; }
.dossier-matrix td.bestguess-col {
  background: var(--glass-bg); color: var(--accent-canonical); font-weight: 600;
  border-right: 1px solid var(--border);
}
.dossier-matrix td.bestguess-col.muted { color: #c9bd95; font-weight: 400; }

/* Dark-mode legibility for the best-guess column (gold-on-cream only suits light) */
[data-theme="dark"] .dossier-matrix td.bestguess-col,
[data-theme="dark"] .dossier-matrix thead th.srccol.bestguess-col .name {
    color: var(--text);
}
[data-theme="dark"] .dossier-matrix td.bestguess-col.muted {
    color: var(--text-muted);
}

/* ============================================================
   Direction B — sticky rail + scrolling content (2B-1)
   ============================================================ */
.dossier-panes { display: flex; gap: 1.5rem; align-items: flex-start; }

.dossier-rail {
  width: 210px; flex: none; position: sticky; top: calc(var(--nav-h) + 0.5rem); align-self: flex-start;
}
.dossier-rail .rail-cover {
  width: 100%; aspect-ratio: 2 / 3; object-fit: cover; border-radius: var(--radius-sm);
  background: var(--surface-2); box-shadow: var(--shadow-lg); display: block;
}
.dossier-rail .rail-price,
.dossier-buy-band .rail-price { font-family: var(--font-display); font-size: 1.5rem; margin: 0.6rem 0 0.1rem; color: var(--text); }
.dossier-rail .rail-facts { font-size: 0.8rem; color: var(--text-muted); margin-top: 0.4rem; }
.dossier-rail .rail-facts .rf { display: flex; justify-content: space-between; gap: 0.5rem; padding: 0.25rem 0; border-top: 1px solid var(--border); }
.dossier-rail .rail-facts .rf b { color: var(--text); font-weight: 600; text-align: right; }
/* Themed fact links (publisher / series) — override Bootstrap's default blue anchor.
   Scoped to anchors so plain-text (non-linked) series names keep --text. */
.rail-facts .rf a,
a.edition-series-link {
  color: color-mix(in srgb, var(--brand) 78%, var(--text));
  text-decoration: none;
  transition: color 0.15s ease;
}
.rail-facts .rf a:hover,
a.edition-series-link:hover {
  color: color-mix(in srgb, var(--brand) 68%, var(--text));
  text-decoration: underline;
}
.dossier-rail .rail-facts .rf.edition-series-fact {
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 0.35rem;
}
.edition-view .dossier-rail .rail-facts .rf.edition-series-fact {
  flex-direction: column;
  align-items: stretch;
}
.edition-series-value {
  min-width: 0;
  width: 100%;
  max-width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
.edition-series-fact .edition-series-value {
  text-align: left;
}
.edition-series-list {
  min-width: 0;
  width: 100%;
  max-width: 100%;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.4rem;
}
.edition-series-membership {
  display: block;
  max-width: 100%;
}
.edition-series-membership.is-primary { order: 0; flex-basis: 100%; }
.edition-series-membership.is-secondary { order: 1; }
.edition-series-link.is-primary { display: inline; }
.edition-series-link.is-secondary {
  display: inline-flex;
  max-width: 100%;
  padding: 0.28rem 0.5rem;
  border: 1px solid color-mix(in srgb, var(--brand) 28%, var(--border));
  border-radius: 999px;
  background: color-mix(in srgb, var(--brand) 8%, var(--surface-2));
  color: color-mix(in srgb, var(--brand) 78%, var(--text));
  font-size: 0.75rem;
  line-height: 1.25;
  text-align: left;
  white-space: normal;
  overflow-wrap: anywhere;
}
.edition-series-link.is-secondary span {
  min-width: 0;
  max-width: 100%;
  letter-spacing: normal;
}
.edition-series-link.is-secondary:hover {
  border-color: color-mix(in srgb, var(--brand) 58%, var(--border));
  background: color-mix(in srgb, var(--brand) 13%, var(--surface-2));
  text-decoration: none;
}
.edition-series-link.is-secondary:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  text-decoration: none;
}
.dossier-rail .rail-actions { display: flex; gap: 0.4rem; margin-top: 0.7rem; }
.dossier-rail .rail-store-links,
.dossier-buy-band .rail-store-links { margin-top: 0.75rem; display: grid; gap: 0.35rem; }
.dossier-rail .rail-store-title,
.dossier-buy-band .rail-store-title {
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-muted); font-weight: 700;
}
.dossier-rail .rail-store-link,
.dossier-buy-band .rail-store-link {
  min-height: 34px; display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 0.35rem 0.5rem; color: var(--text); background: var(--surface-2);
  text-decoration: none; font-size: 0.82rem; font-weight: 650;
}
.dossier-rail .rail-store-link:hover,
.dossier-buy-band .rail-store-link:hover { border-color: var(--brand); color: var(--brand); }
.dossier-rail .rail-store-link.is-search,
.dossier-buy-band .rail-store-link.is-search { color: var(--text-muted); font-weight: 600; }
.dossier-rail .rail-store-link small,
.dossier-buy-band .rail-store-link small {
  color: var(--text-muted); font-size: 0.65rem; text-transform: uppercase;
}
/* Keep the short labels ("ISBN", "search", "as of ...") on one line, but hold the
   nowrap off the <small>s that carry arbitrary-length retailer copy. As part of
   the rule above it (0,2,1) it out-specified those elements' own white-space:
   normal (0,1,0), pinning the long "Item $X · shipping estimate unavailable"
   fallback to a single line -- which pushed the whole card past the 240px rail.
   Scope the nowrap, don't stack another override on top.

   .rail-price-variant-of and .rail-price-condition are the same shape and were
   missed the first time round. The variant line is "Variant cover: " + ANOTHER
   edition's FullTitle, so its length is a book title's, not a label's, and the
   condition string is whatever the retailer typed. Measured against the served
   page 2026-08-26 -- the AbeBooks offer on /edition/cns_2b70336a00c55c72,
   "Variant cover: Amazing Spider-Man By Zeb Wells Omnibus Vol. 2 John Romita
   Jr. Cover": the nowrap <small> laid out 408px wide inside a 294px card, and
   because a flex item is min-width: auto by default that min-content travelled
   all the way up the box tree. At a 1440 viewport the text ran 129px past the
   band's right border and document.scrollWidth reached 1463 -- a horizontal
   scrollbar on the whole page, from one retailer's copy. Same at 820 (851).
   At 430 it was worse in kind rather than degree: .rail-price-compare itself
   was sized to 437px against a 380px band, so EVERY card overflowed, not only
   the one holding the long string. Letting the two wrap costs the card one
   extra line and nothing else -- the row is align-items: stretch, so its
   neighbours match the new height. */
.dossier-rail .rail-store-link small:not(.rail-price-split, .rail-price-variant-of, .rail-price-condition),
.dossier-buy-band .rail-store-link small:not(.rail-price-split, .rail-price-variant-of, .rail-price-condition) { white-space: nowrap; }
.rail-price-compare {
  border-top: 1px solid var(--border-subtle);
  padding-top: 0.65rem;
}
/* The heading over one run of offers -- "New", "Used", "Pre-order" -- set by
   whereToBuyGroupLabel(). The mono label voice, because it names a group of
   rows and is not a row itself. Shared here because the dossier renders on
   the shelf and issue surfaces too; edition_page.css adds only the full-line
   flex rule its band needs. */
.rail-offer-group {
  margin: 0.35rem 0 0.1rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.rail-ebay-value {
  margin-top: 0.75rem;
  border-top: 1px solid var(--border-subtle);
  padding-top: 0.65rem;
}
.ebay-value-lead {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--text);
}
.ebay-value-meta {
  font-weight: 500;
  color: var(--text-muted);
  font-size: 0.78rem;
  margin-left: 0.25rem;
}
.ebay-value-sub {
  margin-top: 0.2rem;
  font-size: 0.7rem;
  color: var(--text-muted);
}
.rail-price-row {
  align-items: stretch;
  flex-direction: column;
  gap: 0.35rem;
}
.dossier-rail .rail-store-link.rail-market-row,
.dossier-buy-band .rail-store-link.rail-market-row {
  border-style: dashed;
  font-weight: 700;
}
.rail-market-row .rail-price-row-meta small {
  text-transform: none;
  letter-spacing: 0;
}
.rail-price-row-main,
.rail-price-row-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.45rem;
}
.rail-price-store {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The logo chip and store name must sit side by side as a single unit
   within .rail-price-row-main -- that row is itself a flex container with
   justify-content: space-between between the name and the price values, so
   without this wrapper the chip would become its own flex item and get
   shoved to the far edge instead of hugging the name. */
.rail-price-store-name {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
}
/* Same reasoning for the plain "Buy / find" store links: .rail-store-link
   is a flex row (name, stock pill, confidence hint) with space-between. */
.rail-store-link-name {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
  flex: 1 1 auto;
}
/* The name shrinks (min-width: 0 above) to make room for the stock pill, so a
   store name with no wrap opportunity ("InStockTrades") would otherwise overflow
   its box and run under the pill. Let it break instead -- wrapping to a second
   line keeps the whole name readable, which truncation in a 240px rail does not
   ("InSto..."). */
.rail-store-link-label {
  min-width: 0;
  overflow-wrap: anywhere;
}
/* .rail-store-links is a grid and grid items are min-width:auto, so a row whose
   min-content exceeds the rail track would size to that min-content and push
   out of the rail rather than shrinking inside it. */
.dossier-rail .rail-store-links > *,
.dossier-buy-band .rail-store-links > * { min-width: 0; }
/* Give the name the whole row and drop the pill/hint beneath it once both no
   longer fit. Without this the name keeps its full-height column beside a
   nowrap pill, so wrapping a normal retailer ("Cheap Graphic Novels") spent
   four lines in a ~50px-wide box -- a 131px rail row. Wrapping the pill
   instead lets the same name sit on one line at full width (66px), and keeps
   the label wrapping rather than truncating in the genuinely-long cases.
   Excludes .rail-price-row, which is already a column. */
.dossier-rail .rail-store-link:not(.rail-price-row),
.dossier-buy-band .rail-store-link:not(.rail-price-row) { flex-wrap: wrap; }
/* Direct children only: the priced rows nest their <small> inside
   .rail-price-row-meta, which keeps its own space-between layout. */
.dossier-rail .rail-store-link > .rail-stock,
.dossier-buy-band .rail-store-link > .rail-stock,
.dossier-rail .rail-store-link > small,
.dossier-buy-band .rail-store-link > small {
  flex: 0 0 auto;
  margin-left: auto;
}
.rail-price-amount {
  color: var(--text);
  font-weight: 800;
}
.rail-price-values {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.08rem;
  text-align: right;
}
.rail-price-split {
  color: var(--text-muted);
  font-size: 0.68rem;
  font-weight: 500;
  line-height: 1.3;
  overflow-wrap: anywhere;
  text-transform: none;
  white-space: normal;
}
.rail-price-list {
  color: var(--text-muted);
  text-decoration: line-through;
}
.rail-price-chip {
  border: 1px solid color-mix(in srgb, var(--success) 45%, transparent);
  border-radius: 999px;
  color: var(--status-success-fg);
  font-size: 0.72rem;
  font-weight: 800;
  padding: 0.05rem 0.35rem;
}
/* Cross-edition price comparison (Task 4): the priced sibling caption on
   family rail rows ("Reprints and newer editions" / "Alternate editions")
   and on the "cheaper printing available" callout below. These reuse
   .rail-store-link's small-caption slot (.dossier-rail .rail-store-link
   small, further up this file) which uppercases -- fine for a bare "as of"
   label, wrong for a real price/condition string ("Delivered $12.99 · Very
   Fine" must not read "DELIVERED $12.99 · VERY FINE").
   A bare single-class rule is NOT enough here: .dossier-rail .rail-store-link
   small carries specificity (0,2,1), a plain .rail-family-price etc. is only
   (0,1,0), and higher specificity wins regardless of source order -- so an
   unqualified override would silently lose the cascade (this is the exact
   (0,2,1) vs (0,1,0) gap the :not(.rail-price-split) exclusion just below
   works around for white-space). Qualify with the same ancestor chain +
   element the losing rule uses -- .dossier-rail .rail-store-link small -- to
   reach (0,3,1), which strictly beats it. */
.dossier-rail .rail-store-link small.rail-family-price,
.dossier-buy-band .rail-store-link small.rail-family-price,
.dossier-rail .rail-store-link small.rail-price-variant-of,
.dossier-buy-band .rail-store-link small.rail-price-variant-of,
.dossier-rail .rail-store-link small.rail-price-condition,
.dossier-buy-band .rail-store-link small.rail-price-condition {
  text-transform: none;
}
/* The nowrap exclusion above is only half the job for these two, because both
   carry free-form retailer copy: a whole edition title, and a condition grade
   somebody typed. A title with no space in it would still size the card by its
   own min-content once wrapping is allowed. `anywhere` rather than
   `break-word` on purpose -- only `anywhere` also shrinks the min-content
   measurement, and min-content is exactly what the flex row sizes on. Same
   pairing .rail-price-split already carries, for the same reason. */
.dossier-rail .rail-store-link small.rail-price-variant-of,
.dossier-buy-band .rail-store-link small.rail-price-variant-of,
.dossier-rail .rail-store-link small.rail-price-condition,
.dossier-buy-band .rail-store-link small.rail-price-condition {
  overflow-wrap: anywhere;
}
/* A cover-variant row is an offer for ANOTHER edition, merged in because the
   two jackets are one printing. It must read as a related offer, not as this
   book's own: the leading rule marks it apart without demoting the price,
   which is frequently the best one on the rail. Same (0,3,1) qualification as
   the block above. */
.dossier-rail .rail-store-link.is-cover-variant,
.dossier-buy-band .rail-store-link.is-cover-variant {
  border-left: 2px solid color-mix(in srgb, var(--text) 22%, transparent);
  padding-left: 0.45rem;
}
.dossier-rail .rail-store-link small.rail-price-variant-of,
.dossier-buy-band .rail-store-link small.rail-price-variant-of {
  color: var(--text-muted);
  font-style: italic;
}
.dossier-rail .rail-store-link small.rail-family-price,
.dossier-buy-band .rail-store-link small.rail-family-price {
  color: var(--text);
  font-weight: 650;
}
/* Savings badge: same pill recipe as .rail-price-chip's "% off" above (border
   + tinted text, not colour alone -- the pill shape and the "Save $" wording
   both carry the meaning too), sized down to sit beside a rail-row title
   instead of a price-row's larger value. Same (0,3,1) qualification as above. */
.dossier-rail .rail-store-link small.rail-family-savings,
.dossier-buy-band .rail-store-link small.rail-family-savings {
  display: inline-block;
  border: 1px solid color-mix(in srgb, var(--success) 45%, transparent);
  border-radius: 999px;
  color: var(--status-success-fg);
  font-size: 0.68rem;
  font-weight: 800;
  padding: 0.05rem 0.35rem;
  text-transform: none;
}
/* Give the callout its own card treatment (border + faint success tint)
   instead of blending into the plain link lists above and below it --
   .rail-store-links alone (grid + margin-top) reads as just another list.
   Unlike the <small> classes above, .rail-cheaper-alternative sits on the
   *container* div, not on a small inside .rail-store-link, so
   .rail-store-link small's uppercase rule never targets it -- there is no
   competing rule to out-specify here. It only has to beat the plain
   .dossier-rail .rail-store-links rule (0,2,0) above, and qualifying with
   .dossier-rail + .rail-store-links + its own class already reaches (0,3,0),
   so no extra qualification was needed for this one. */
.dossier-rail .rail-store-links.rail-cheaper-alternative,
.dossier-buy-band .rail-store-links.rail-cheaper-alternative {
  margin-top: 1rem;
  padding: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--success) 35%, var(--border));
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--success) 7%, var(--surface));
}
/* Qualified with .dossier-rail (rather than left as the bare
   .rail-cheaper-alternative .rail-store-title) so this doesn't tie
   .dossier-rail .rail-store-title's (0,2,0) and fall back to source-order --
   a rule that only wins by appearing later in the file is one accidental
   reordering away from losing the cascade again. */
.dossier-rail .rail-cheaper-alternative .rail-store-title,
.dossier-buy-band .rail-cheaper-alternative .rail-store-title {
  color: var(--status-success-fg);
}
/* "You already own another printing" overlay (TTA-141): the per-row shelf
   marker on family rail rows, and the two summary callouts above the
   cheaper-printing callout.

   Same (0,2,1) vs (0,1,0) cascade trap as the price captions above --
   .rail-shelf-state and .rail-printing-label are <small>s inside
   .rail-store-link, so both are qualified to (0,3,1) and turn text-transform
   back off. "✓ On your shelf" must not read "✓ ON YOUR SHELF". */
.dossier-rail .rail-store-link small.rail-shelf-state,
.dossier-buy-band .rail-store-link small.rail-shelf-state,
.dossier-rail .rail-store-link small.rail-printing-label,
.dossier-buy-band .rail-store-link small.rail-printing-label {
  text-transform: none;
}
/* Owned and wanted are distinguished by wording and glyph first (✓ / ♡), with
   colour as reinforcement only -- the state is never carried by colour alone. */
.dossier-rail .rail-store-link small.rail-shelf-state,
.dossier-buy-band .rail-store-link small.rail-shelf-state {
  display: inline-block;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: 999px;
  color: var(--text);
  font-size: 0.68rem;
  font-weight: 700;
  padding: 0.05rem 0.35rem;
}
.dossier-rail .rail-store-link small.rail-shelf-state.is-owned,
.dossier-buy-band .rail-store-link small.rail-shelf-state.is-owned {
  border-color: color-mix(in srgb, var(--success) 45%, transparent);
  color: var(--status-success-fg);
}
/* Both callouts get the same card treatment as .rail-cheaper-alternative, in
   the neutral/accent tint rather than the success tint -- "you own this" is
   information, not a deal. (0,3,0), so it beats .dossier-rail
   .rail-store-links (0,2,0) without relying on source order. */
.dossier-rail .rail-store-links.rail-owned-printings,
.dossier-buy-band .rail-store-links.rail-owned-printings,
.dossier-rail .rail-store-links.rail-wanted-printings,
.dossier-buy-band .rail-store-links.rail-wanted-printings {
  margin-top: 1rem;
  padding: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 7%, var(--surface));
}
.dossier-rail .rail-owned-printings .rail-store-title,
.dossier-buy-band .rail-owned-printings .rail-store-title,
.dossier-rail .rail-wanted-printings .rail-store-title,
.dossier-buy-band .rail-wanted-printings .rail-store-title {
  color: var(--text);
}
/* Status ink is distinct from decorative accents. Filled pills use an opaque
   surface mix so retailer-row tints and page gradients cannot lower contrast. */
.dossier-rail .rail-store-link .rail-stock,
.dossier-buy-band .rail-store-link .rail-stock {
  font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em;
  font-weight: 700; white-space: nowrap; line-height: 1.5;
  padding: 0.08rem 0.42rem; border-radius: 999px;
}
.dossier-rail .rail-store-link .rail-stock.is-in,
.dossier-buy-band .rail-store-link .rail-stock.is-in {
  color: var(--status-success-fg);
  background: color-mix(in srgb, var(--success) 16%, var(--surface));
}
.dossier-rail .rail-store-link .rail-stock.is-preorder,
.dossier-buy-band .rail-store-link .rail-stock.is-preorder {
  color: var(--text);
  background: color-mix(in srgb, var(--brand) 14%, var(--surface));
}
.dossier-rail .rail-store-link .rail-stock.is-backorder,
.dossier-buy-band .rail-store-link .rail-stock.is-backorder {
  color: var(--status-warning-fg);
  background: color-mix(in srgb, var(--accent-upcoming) 14%, var(--surface));
}
.dossier-rail .rail-store-link .rail-stock.is-out,
.dossier-buy-band .rail-store-link .rail-stock.is-out {
  color: var(--status-muted-fg);
  background: color-mix(in srgb, var(--text-muted) 14%, var(--surface));
}
/* "Unreleased" -- the edition has not shipped yet, so nobody stocks it. It must
   NOT borrow the muted out-of-stock treatment: this is a forthcoming book, not
   a sold-out one, and the pill is the only thing distinguishing the two. */
.dossier-rail .rail-store-link .rail-stock.is-unreleased,
.dossier-buy-band .rail-store-link .rail-stock.is-unreleased {
  color: var(--status-warning-fg);
  background: color-mix(in srgb, var(--accent-upcoming) 14%, var(--surface));
}
/* "Stock unknown" -- we hold a price but never observed availability. Outlined
   rather than filled so it reads as an absence of information next to the
   filled in/out pills, instead of as a fifth stock state. */
.dossier-rail .rail-store-link .rail-stock.is-unknown,
.dossier-buy-band .rail-store-link .rail-stock.is-unknown {
  color: var(--status-muted-fg);
  background: transparent;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text-muted) 30%, transparent);
}
.dossier-rail .rail-src { display: inline-block; margin-top: 0.6rem; font-size: 0.8rem; color: var(--brand); }

/* Remainder-mark disclosure dot (Book Outlet publisher-overstock rows). A dot
   rather than text so it costs no rail space; the tooltip carries the wording
   (see availability_display.js retailerNote / template_globals._retailer_note).
   No colour token here on purpose -- it inherits the row's own theme-aware
   text colour rather than asserting a new one.

   The dot and the bubble are split across two DIFFERENT parents on purpose:

   .rail-note-dot is back INSIDE the row anchor (.rail-store-link), in flow
   next to the stock label, same as before this rail existed as a sibling
   arrangement at all. It is purely decorative (aria-hidden) and contributes
   nothing to the anchor's accessible name, so nesting it there is fine -- and
   it is not a focus/hover target of its own, since the anchor content model
   forbids a focusable descendant (a real <button> trigger here would still be
   invalid, unlike edition_market.css's .rail-note-dot-wrap, where the dot is a
   sibling of a much narrower anchor and can safely use one).

   .rail-note-bubble is a SIBLING of the row anchor, in the wrapping
   .rail-store-link-row div, never a descendant: accessible-name-from-content
   walks descendants, so a bubble nested inside the anchor joins its whole
   sentence to the anchor's accessible name *and* gets announced a second
   time via aria-describedby. :aria-describedby (set on the anchor in the
   template) still points at this sibling bubble's id -- cross-element
   association does not require DOM nesting. It is absolutely positioned
   against .rail-store-link-row (not against the dot) with pointer-events:
   none, so it opens above the WHOLE row rather than overlapping any part of
   it, and every pointer/keyboard interaction passes through to the anchor
   beneath. The reveal keys off the anchor's own :hover/:focus-visible via the
   general sibling combinator (~), since the bubble is not a descendant. */
.rail-store-link-row {
  position: relative;
}
.rail-note-dot {
  opacity: 0.55;
  font-size: 1.1em;
  line-height: 1;
}
.dossier-rail .rail-store-link:hover .rail-note-dot,
.dossier-buy-band .rail-store-link:hover .rail-note-dot,
.dossier-rail .rail-store-link:focus-visible .rail-note-dot,
.dossier-buy-band .rail-store-link:focus-visible .rail-note-dot { opacity: 1; }

.rail-note-bubble {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 0.4rem);
  transform: translateX(-50%) translateY(0.2rem) scale(0.98);
  z-index: 5;
  width: max-content;
  max-width: min(16rem, calc(100vw - 2rem));
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: min(var(--radius, 8px), 8px);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-lg, 0 12px 30px rgba(0, 0, 0, 0.18));
  font: 500 0.78rem/1.3 var(--font-body, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
  white-space: normal;
  text-align: left;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--motion-fast, 120ms) var(--ease-standard, ease), transform var(--motion-fast, 120ms) var(--ease-standard, ease);
}
.dossier-rail .rail-store-link:hover ~ .rail-note-bubble,
.dossier-buy-band .rail-store-link:hover ~ .rail-note-bubble,
.dossier-rail .rail-store-link:focus-visible ~ .rail-note-bubble,
.dossier-buy-band .rail-store-link:focus-visible ~ .rail-note-bubble {
  opacity: 1;
  transform: translateX(-50%) translateY(0) scale(1);
}

.dossier-content { flex: 1; min-width: 0; }

.dossier-content .dsec { margin-bottom: 1.25rem; }
.dossier-content .dsec > h6,
.dossier-content .dsec-h {
  font-family: var(--font-display); font-size: 0.72rem; font-weight: 400;
  letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-muted);
  border-bottom: 1px solid var(--border); padding-bottom: 0.3rem; margin: 0 0 0.6rem;
}
.dossier-content .dsec p { font-size: 0.9rem; line-height: 1.55; color: var(--text); margin: 0; }
.dossier-content .dsec .role-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin: 0.5rem 0 0.3rem; }

.dossier-related-row {
  display: grid;
  gap: 0.45rem;
  padding: 0;
  background: var(--surface);
  color: var(--text);
}
.dossier-related-row + .dossier-related-row {
  border-top: 1px solid var(--border);
}
.dossier-related-main {
  width: 100%;
  min-height: 42px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.85rem;
  padding: 0.7rem 0.85rem;
  border: 0;
  background: transparent;
  color: var(--text);
  text-align: left;
  text-decoration: none;
  font: inherit;
  cursor: pointer;
}
.dossier-related-main:hover {
  background: color-mix(in srgb, var(--brand) 8%, transparent);
  color: var(--text);
}
.dossier-related-main > span {
  min-width: 0;
}
.dossier-related-main small {
  flex: none;
  text-align: right;
}
/* Collected in / across rows carry a format · publisher · year line under the
   title (GH-1109). The overlap count is flex: none, so on a phone it would
   squeeze that column to a word per line; let it drop to its own line. */
.dossier-related-main--relation {
  flex-wrap: wrap;
  row-gap: 0.25rem;
}
.dossier-related-compare {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.6rem;
  padding: 0 0.85rem 0.7rem;
  color: var(--text-muted);
  font-size: 0.78rem;
}
.dossier-related-compare a {
  color: var(--brand);
  font-weight: 700;
  text-decoration: none;
}
.dossier-related-compare a:hover {
  color: var(--text);
  text-decoration: underline;
}
.dossier-related-pill {
  display: inline-flex;
  align-items: center;
  min-height: 22px;
  padding: 0.12rem 0.5rem;
  border: 1px solid color-mix(in srgb, var(--accent-canonical) 55%, var(--border));
  border-radius: var(--radius-pill);
  /* Gold remains the boundary; small text needs the semantic ink ramp on a
     known surface, including palettes whose gold is too light for text. */
  background: var(--surface-2);
  color: var(--text-muted);
  font-size: 0.68rem;
  font-weight: 750;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Bootstrap's fixed secondary gray fails on the dark dossier surface. Set its
   component variables so resting, hover, focus and unavailable Book states
   all use the theme's measured text/surface pairs. */
.dossier-contents-sort .btn-outline-secondary {
  --bs-btn-color: var(--text-muted);
  --bs-btn-border-color: var(--border-strong);
  --bs-btn-hover-color: var(--text);
  --bs-btn-hover-bg: var(--surface-2);
  --bs-btn-hover-border-color: var(--border-strong);
  --bs-btn-active-color: var(--text);
  --bs-btn-active-bg: var(--surface-2);
  --bs-btn-active-border-color: var(--border-strong);
  --bs-btn-disabled-color: var(--text-muted);
  --bs-btn-disabled-bg: var(--surface);
  --bs-btn-disabled-border-color: var(--border-strong);
  --bs-btn-disabled-opacity: 1;
}
.dossier-contents-sort .btn:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.dossier-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.dossier-chips .cc-pill { color: var(--text); text-decoration: none; }
/* Entity names are content, not short status labels. Keep the complete link
   within its disclosure, including bad imports with no word boundaries. */
.dossier-chips .dossier-entity-link {
  min-width: 0;
  max-width: 100%;
  white-space: normal;
  overflow-wrap: anywhere;
  line-height: 1.4;
}
.dossier-entity-link:hover { border-color: var(--border-strong); color: var(--text); }

.dossier-rail-action {
  flex: 1;
  min-height: var(--dossier-action-min-height, 44px);
  padding: var(--dossier-action-padding, 0.45rem 0.5rem);
  font-size: var(--dossier-action-font-size, 0.82rem);
  text-align: center;
}

.dossier-rail-action:not(.cc-btn--success):not(.cc-btn--interest) {
  border-color: var(--dossier-action-border, var(--border));
  background: var(--dossier-action-bg, var(--surface-2));
  color: var(--dossier-action-fg, var(--text));
}

.dossier-rail-action:hover { transform: var(--dossier-action-hover-transform, translateY(-1px)); }

@media (prefers-reduced-motion: reduce) {
  .dossier-rail-action { animation: none; transition: none; }
  .dossier-rail-action:hover { transform: none; }
}

.dossier-rail .rail-share {
  width: 100%;
  margin-top: 0.7rem;
}
.dossier-rail .rail-share-btn {
  width: 100%;
  min-height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.42rem 0.6rem;
  background: var(--surface-2);
  color: var(--text);
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
}
.dossier-rail .rail-share-btn:hover:not(:disabled) {
  border-color: var(--brand);
  color: var(--brand);
}
.dossier-rail .rail-share-btn:disabled {
  cursor: default;
  opacity: 0.65;
}
.dossier-rail .rail-share-feedback {
  margin-top: 0.35rem;
  font-size: 0.75rem;
  font-weight: 650;
  color: var(--success);
  text-align: center;
}
.dossier-rail .rail-share-feedback.is-error {
  color: var(--danger, #dc3545);
}

.dossier-manage {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-top: 1rem;
}
.dossier-manage > summary,
.dossier-manage .dm-head {
  cursor: pointer; list-style: none; padding: 0.7rem 0.9rem; display: flex;
  justify-content: space-between; align-items: center; color: var(--text); font-weight: 600; font-size: 0.85rem;
}
.dossier-manage .dm-body { padding: 0.9rem; border-top: 1px solid var(--border); }
/* Smooth height animation for a disclosure body, driven by dossier_disclosure.js.
   Applies to every disclosure in the dossier content area -- the reader-facing
   sections and the moderator panels alike -- so the class is keyed to the
   animating state, not to a particular body class.

   Only active during open/close (the JS adds/removes the class) so async content
   changes inside a panel don't animate. overflow:hidden clips during the reveal;
   when the class is removed the body returns to its default overflow, letting the
   Merge typeahead dropdown escape via .dossier-manage[open] { overflow: visible }. */
.dossier-content .cc-disclosure-animating,
.dossier-manage .cc-disclosure-animating {
  overflow: hidden;
  transition: height var(--motion-slow) var(--ease-standard);
}
.dossier-manage .dm-summary-label { display: inline-flex; align-items: center; gap: 0.5rem; }
.dossier-manage .dm-summary-label i { color: var(--accent-canonical); }

/* Segmented control: one focused pane at a time. */
.dm-tabs {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  margin-bottom: 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--surface-2) 70%, transparent);
  overflow-x: auto;
  scrollbar-width: none;
}
.dm-tabs::-webkit-scrollbar { display: none; }
.dm-tab {
  flex: 1 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-height: 38px;
  padding: 0.4rem 0.85rem;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  font-size: 0.82rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition:
    color var(--motion-fast) var(--ease-standard),
    background-color var(--motion-fast) var(--ease-standard);
}
.dm-tab i { font-size: 0.95em; }
.dm-tab:hover { color: var(--text); }
.dm-tab.active {
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-sm);
}

.dm-pane { animation: dm-pane-in var(--motion-med) var(--ease-enter); }
@keyframes dm-pane-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}
.dm-hint { margin: 0 0 0.75rem; color: var(--text-muted); font-size: 0.85rem; }

/* Status grid (own / want / pulled / read) */
.dm-status-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.5rem;
}
.dm-status {
  min-height: 44px;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
  transition:
    border-color var(--motion-fast) var(--ease-standard),
    background-color var(--motion-fast) var(--ease-standard),
    color var(--motion-fast) var(--ease-standard);
}
.dm-status:hover { border-color: var(--border-strong); }
.dm-status.active {
  border-color: var(--success);
  background: color-mix(in srgb, var(--success) 16%, var(--surface));
  color: var(--success);
}

/* Themed form fields (replace raw Bootstrap controls inside Manage) */
.dm-label {
  display: block;
  margin: 0.65rem 0 0.3rem;
  color: var(--text);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.dm-label:first-of-type { margin-top: 0; }
.dm-label-aside { color: var(--text-muted); font-weight: 600; text-transform: none; letter-spacing: 0; }
.dm-field {
  width: 100%;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 0.9rem;
}
textarea.dm-field { resize: vertical; min-height: 64px; }
.dm-field::placeholder { color: var(--text-dim); }
.dm-field:focus {
  outline: none;
  border-color: var(--accent-canonical);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-canonical) 22%, transparent);
}
.dm-edit-grid { display: grid; gap: 0.6rem; }

.dm-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.9rem;
}
.dm-btn {
  min-height: 40px;
  padding: 0.45rem 0.95rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
  transition:
    border-color var(--motion-fast) var(--ease-standard),
    background-color var(--motion-fast) var(--ease-standard),
    transform var(--motion-instant) var(--ease-standard);
}
.dm-btn:hover:not(:disabled) { border-color: var(--border-strong); transform: translateY(-1px); }
.dm-btn:disabled { opacity: 0.6; cursor: default; }
.dm-btn-sm { min-height: 32px; padding: 0.25rem 0.6rem; font-size: 0.78rem; }
.dm-btn-primary {
  border-color: var(--accent-canonical);
  background: var(--accent-canonical);
  color: #1a1206;
}
.dm-btn-primary:hover:not(:disabled) {
  border-color: var(--accent-canonical);
  background: color-mix(in srgb, var(--accent-canonical) 88%, #fff);
}
.dm-btn-warn { border-color: var(--accent-upcoming); color: var(--accent-upcoming); }
.dm-feedback { font-size: 0.82rem; color: var(--success); }
.dm-feedback-muted { color: var(--text-muted); }
.dm-feedback-error { color: var(--danger); }
.dm-full-editor-link {
  display: inline-flex;
  margin: -0.2rem 0 0.8rem;
  color: var(--text-muted);
  font-size: 0.8rem;
  font-weight: 700;
  text-decoration: none;
}
.dm-full-editor-link:hover { color: var(--accent-canonical); }
.dm-history-error { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; }
.dm-history-error .dm-feedback { margin: 0; }

/* Edit history */
.dm-history { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.6rem; }
.dm-history-item {
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--surface-2) 60%, transparent);
}
.dm-history-row { display: flex; align-items: center; gap: 0.5rem; }
.dm-history-field { font-weight: 700; color: var(--text); font-size: 0.85rem; }
.dm-history-source {
  color: var(--text-muted);
  font-size: 0.75rem;
  font-weight: 700;
}
.dm-history-change {
  display: flex; align-items: center; gap: 0.45rem;
  margin: 0.3rem 0 0.15rem; font-size: 0.85rem;
  overflow-wrap: anywhere;
}
.dm-history-change i { color: var(--text-muted); }
.dm-history-old { color: var(--text-muted); text-decoration: line-through; }
.dm-history-new { color: var(--success); font-weight: 600; }
.dm-history-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.55rem;
  align-items: center;
  color: var(--text-dim);
  font-size: 0.75rem;
}
.dm-history-user { color: var(--text-muted); font-weight: 700; }
.dm-history-time { margin-left: auto; color: var(--text-dim); }
.dm-history-kinds { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-bottom: 0.75rem; }
/* The kind filters are the shared .cc-chip primitive (theme.css), which
   already carries the pill/border/cursor treatment and a token-solved
   .is-active fill -- a local -chip class here would be a second copy of it,
   and the hand-written active state paired a token fill with a hardcoded
   #fff ink. */
.dm-kind-badge {
  border-radius: 3px; padding: 0 0.35rem; font-size: 0.68rem;
  letter-spacing: 0.04em; text-transform: uppercase; border: 1px solid var(--border);
}
.dm-chip {
  margin-left: auto;
  padding: 0.1rem 0.5rem;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--surface-2) 80%, var(--accent-canonical) 8%);
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.dossier-matrix th, .dossier-matrix td { padding: 6px 10px; }

.dsec-collapsible > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.55rem;
}

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

.dsec-collapsible > summary::after {
  content: "+";
  margin-left: auto;
  color: var(--ed-accent, var(--accent-canonical));
  font-size: 1rem;
}

.dsec-collapsible[open] > summary::after {
  content: "-";
}

/* .dsec-h's bottom margin separates a header from its body. A closed section
   has no body, so the margin was dead space under every collapsed title:
   measured 2026-10-08, a 24px summary in a 63px box, the title riding the top
   edge. Dropping it only while closed centres the title and keeps the open
   spacing exactly as it was. */
.dossier-content .dsec-collapsible:not([open]) > summary {
  margin-bottom: 0;
}

/* Every collapsed section states how much it holds. Before this, Related
   editions / Creators / Characters were identical closed boxes carrying no
   signal at all, and only Contents said what was inside. Lives here rather
   than in edition_page.css because the dossier renders on the shelf and issue
   surfaces too, and a count that appeared on one of them only would be the
   same missing signal wearing a different page.

   0.72rem, not <small>'s inherited 0.7em: .dsec-h is 0.8rem, so the browser
   default lands at ~11.2px and this page already carries 53 elements under
   11px. Tabular figures stop the header reflowing when Alpine swaps the
   count in. :empty covers the sections whose count is conditional -- an
   expression that resolves to '' must leave no gap before the marker. */
.dossier-content .dsec-count {
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
}
.dossier-content .dsec-count:empty { display: none; }

.dossier-contents-body,
.dsec-body {
  margin-top: 0.85rem;
}

.dossier-contents .contents-row--textonly {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 0.65rem;
  color: var(--text-muted);
  cursor: default;
}

.dossier-contents .contents-row-shell--textonly {
  background: color-mix(in srgb, var(--surface-2) 86%, var(--border) 14%);
}

@media (max-width: 640px) {
  .dossier-panes { flex-direction: column; gap: 1rem; }
  .dossier-rail { width: 100%; position: static; }
  .dossier-rail .rail-cover { width: 140px; }
  .rail-price-row-main { align-items: flex-start; }
  .rail-price-values { align-items: flex-start; text-align: left; }
  .dm-tab,
  .dm-actions .dm-btn,
  .dm-history-row .dm-btn,
  .dm-history-error .dm-btn { min-height: 44px; }
}

/* Auto-selected (survivorship) winner — subordinate to the solid pin seal.
   A left edge bar, not a bottom underline: the underline spanned the full
   cell width and read as a stray horizontal rule floating between rows. */
.dossier-matrix td.field-autocol {
  box-shadow: inset 3px 0 0 var(--accent-canonical, #b8932f);
}
.dossier-matrix td.field-autocol .field-rule,
.bestguess-legend {
  display: block;
  margin-top: 2px;
  font-size: 0.7rem;
  color: var(--text-muted, #8a8a8a);
}
.dm-member-list {
  list-style: none;
  margin: 0 0 0.75rem;
  padding: 0;
  max-height: 320px;
  overflow-y: auto;
  border: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
  border-radius: 8px;
}
.dm-member { border-bottom: 1px solid var(--border-subtle, rgba(255,255,255,0.06)); }
.dm-member:last-child { border-bottom: 0; }
.dm-member-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0.6rem;
  margin: 0;
  cursor: pointer;
}
.dm-member-row input[type="checkbox"] { flex: 0 0 auto; }
.dm-member-cover {
  flex: 0 0 auto;
  width: 32px;
  height: 48px;
  object-fit: cover;
  border-radius: 3px;
  background: var(--surface-2, rgba(255,255,255,0.04));
}
.dm-member-meta { display: flex; flex-direction: column; min-width: 0; gap: 0.15rem; }
.dm-member-title {
  font-size: 0.85rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dm-member-sub {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  color: var(--text-muted);
  font-size: 0.75rem;
}
.dm-source-badge {
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-weight: 700;
  font-size: 0.65rem;
  padding: 0.05rem 0.35rem;
  border-radius: 4px;
  background: var(--surface-2, rgba(255,255,255,0.06));
}
.dm-tag {
  font-size: 0.65rem;
  padding: 0.05rem 0.35rem;
  border-radius: 4px;
  background: var(--surface-2, rgba(255,255,255,0.06));
}
.dm-tag-warn { color: var(--accent-warn, #d9a441); }
.dm-manual { margin-bottom: 0.5rem; }
.dm-manual-summary {
  cursor: pointer;
  color: var(--text-muted);
  font-size: 0.8rem;
}

/* Edition-level out-of-print flags (show_out_of_print). Same tinted-chip
   recipe as .rail-stock above -- color-mix over a semantic status var, so
   both tiers already track light/dark theme without a separate override. */
.rail-oop-flag, .rail-tentative-flag { display: flex; flex-direction: column; gap: 2px; }
.rail-oop-badge, .rail-tentative-badge {
  align-self: flex-start;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 999px;
}
.rail-oop-flag.is-observed .rail-oop-badge {
  color: var(--status-danger-fg);
  background: color-mix(in srgb, var(--status-danger-fg) 16%, transparent);
}
.rail-oop-flag.is-inferred .rail-oop-badge {
  color: var(--status-warning-fg);
  background: color-mix(in srgb, var(--status-warning-fg) 16%, transparent);
}
/* Reprinted is good news -- the book is buyable again in a newer printing. */
.rail-oop-flag.is-reprinted .rail-oop-badge {
  color: var(--status-success-fg);
  background: color-mix(in srgb, var(--status-success-fg) 16%, transparent);
}
/* Tentative is an inference, so it takes the inferred tier's warning colour
   rather than danger -- the book is unconfirmed, not known to be gone. Shares
   the chip recipe above rather than restating it: the CSS primitive ratchet
   has no headroom. */
.rail-tentative-badge {
  color: var(--status-warning-fg);
  background: color-mix(in srgb, var(--status-warning-fg) 16%, transparent);
}
.rail-oop-note, .rail-tentative-note { font-size: 0.72rem; opacity: 0.75; }


/* The purchase band is the third child of .dossier-panes. Under .edition-view
   the panes are a grid and the band spans both columns (edition_page.css).
   This file's base layout is flex, which edition_social.html still uses -- it
   includes _edition_view_main.html inside .es-rail with no .edition-view
   wrapper -- so the band has to claim its own line there instead of being
   squeezed in beside the content column. :has() keeps the wrap off every other
   .dossier-panes on the site (index, issue, issue sources), none of which has
   a band; without JS support for :has() the band merely sits inline, which is
   the pre-existing shape rather than a broken one. */
.dossier-panes:has(> .dossier-buy-band) { flex-wrap: wrap; }
.dossier-buy-band { flex: 1 1 100%; min-width: 0; }


/* ---------------------------------------------------------------------------
   Price position + the wait control (.rail-wait)

   Sits under the "Where to buy" rows and belongs to the edition, not to any
   one storefront. The sentence is the evidence; the button is what a reader
   does about it. Reading order is number (the rows above) -> position -> the
   action, and a reader who stops after the sentence has still gained
   something -- that sequence is why the signal sits above the control rather
   than inside it.
   --------------------------------------------------------------------------- */

.rail-wait {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 0 2px;
  border-top: 1px solid var(--border);
  margin-top: 4px;
}

.rail-wait-line {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  margin: 0;
  /* Secondary text, so the retailer prices above stay the largest thing in
     the rail. --text-muted clears AA body text on all three surfaces. */
  color: var(--text-muted);
  font-size: 0.78rem;
  line-height: 1.35;
}

.rail-wait-line.is-at-low {
  color: var(--status-success-fg);
  font-weight: 500;
}

/* The sparkline is a graphical MARK, not words: it takes --border-strong,
   which is solved for the 3:1 non-text floor against every surface it lands
   on. Borrowing a text tier here would either fail that floor or drag the
   sentence's colour with it. currentColor on the polyline resolves to this. */
.rail-wait-spark {
  flex: none;
  display: block;
  overflow: visible;
  color: var(--border-strong);
}

/* The endpoint carries where the price is NOW -- the one point in the series
   the reader is actually standing on. It reads green only when the sentence
   beside it already says the book is at its low, so colour never makes a
   claim the words have not made. */
.rail-wait-spark-end { fill: var(--border-strong); }
.rail-wait-line.is-at-low .rail-wait-spark-end { fill: var(--status-success-fg); }

.rail-wait-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* Tertiary until touched: it must not compete with the retailer's own buy
   link, which stays the primary action for a reader who has decided to buy
   now. Hover and focus promote it; nothing else does. */
.rail-wait-action {
  font-size: 0.76rem;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: color 140ms ease-out, border-color 140ms ease-out, background-color 140ms ease-out;
}

.rail-wait-action:hover,
.rail-wait-action:focus-visible {
  color: var(--brand);
  border-color: var(--brand);
  background: color-mix(in srgb, var(--brand) 8%, transparent);
}

/* Armed. Persistent and on the row itself, never a toast: a reader must be
   able to come back a week later and see the site is still holding this. */
.rail-wait-action.is-armed {
  color: var(--brand);
  border-color: color-mix(in srgb, var(--brand) 55%, transparent);
  background: color-mix(in srgb, var(--brand) 10%, transparent);
  font-weight: 500;
}

/* The alert already fired. Saying "waiting" here would be the site claiming
   to be doing work it has finished. */
.rail-wait-action.is-met {
  color: var(--status-success-fg);
  border-color: color-mix(in srgb, var(--status-success-fg) 55%, transparent);
  background: color-mix(in srgb, var(--status-success-fg) 12%, transparent);
  font-weight: 500;
}

.rail-wait-state {
  font-size: 0.7rem;
  color: var(--text-dim);
  line-height: 1.3;
}
.rail-wait-state.is-met { color: var(--status-success-fg); }
.rail-wait-state.is-warn { color: var(--status-warning-fg); }

/* ---- the inline form ---- */

.rail-wait-form {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.rail-wait-label {
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.rail-wait-field {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.rail-wait-currency {
  font-size: 0.86rem;
  color: var(--text-muted);
  font-weight: 500;
}

.rail-wait-input {
  width: 7ch;
  flex: 0 1 auto;
  min-width: 0;
  padding: 5px 8px;
  font: inherit;
  font-size: 0.86rem;
  /* Prices line up against the amounts in the rows above. */
  font-variant-numeric: tabular-nums;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
}

.rail-wait-input:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
  border-color: var(--brand);
}

/* The number is the content; the browser's spinners are chrome that steals
   width from a 7ch field and cannot be themed to match anything here. */
.rail-wait-input::-webkit-outer-spin-button,
.rail-wait-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.rail-wait-input[type="number"] { -moz-appearance: textfield; appearance: textfield; }

.rail-wait-save,
.rail-wait-cancel {
  font-size: 0.76rem;
  padding: 5px 12px;
  border-radius: var(--radius-pill);
  cursor: pointer;
}

.rail-wait-save[disabled] {
  opacity: 0.6;
  cursor: progress;
}

.rail-wait-hint {
  margin: 0;
  font-size: 0.7rem;
  line-height: 1.4;
  color: var(--text-dim);
}
.rail-wait-hint.is-warn { color: var(--status-warning-fg); }

.rail-wait-error {
  margin: 0;
  font-size: 0.72rem;
  color: var(--status-danger-fg);
}

.rail-wait-clear {
  align-self: flex-start;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.7rem;
  color: var(--text-dim);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.rail-wait-clear:hover,
.rail-wait-clear:focus-visible { color: var(--status-danger-fg); }

/* The rail is inside `@container edition`, so this responds to the column it
   actually occupies rather than to the viewport. The line wraps onto its own
   row before it truncates: a price a reader cannot finish reading is worse
   than one that takes two lines, and an ellipsized number is worthless. */
@container edition (max-width: 860px) {
  .rail-wait-line { font-size: 0.75rem; }
}

/* One authored moment, on the element the feature is actually about: the
   sparkline draws itself once when the position lands, so the eye is taken to
   the evidence rather than to a button. Nothing else on this block animates,
   and it never loops. 240 is comfortably longer than the 58-unit box's path,
   so the dash always fully covers it before the sweep starts. */
@media (prefers-reduced-motion: no-preference) {
  .rail-wait-spark polyline {
    stroke-dasharray: 240;
    stroke-dashoffset: 240;
    animation: rail-wait-draw 620ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
  }
  .rail-wait-spark-end {
    opacity: 0;
    animation: rail-wait-dot 260ms ease-out 520ms forwards;
  }
}

@keyframes rail-wait-draw {
  to { stroke-dashoffset: 0; }
}

@keyframes rail-wait-dot {
  to { opacity: 1; }
}

/* Curator warning about a printing (show_edition_warnings).
   Amber rather than the out-of-print treatment: a different claim deserves a
   different colour, and amber reads as "read this" without the alarm of an
   error state. --accent-upcoming is the palette's already-solved amber; it is
   used here only for an EDGE and an ICON, never for words, which stay on the
   solved neutral --text so the ramp holds against --surface-2. */
.rail-warning {
  border-left: 3px solid var(--accent-upcoming);
  background: var(--surface-2);
}
.rail-warning-head {
  display: flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 600; color: var(--text);
}
.rail-warning-head .bi { color: var(--accent-upcoming); }
.rail-warning-body { margin: 6px 0 0; font-size: 13px; color: var(--text); }
.rail-warning-link { display: inline-block; margin-top: 8px; font-size: 13px; font-weight: 600; }

/* Warning marker — top-right, the only corner not already spoken for.
   Theme-constant scrim with a matching hardcoded ink, exactly as
   .comic-card-secondary-badge does: the backplate does not follow the theme,
   so pairing it with a themed token would break the contrast in one of them. */
.comic-card-warning-badge {
  position: absolute; right: 8px; top: 8px; z-index: 3;
  width: 18px; height: 18px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; line-height: 1;
  background: rgba(0, 0, 0, .62); color: #ffcf7a;
  pointer-events: none;
}
