/* ---------------------------------------------------------------------------
   editorial.css - a ROUTE stylesheet, not part of the always-loaded core.

   The seven public editorial pages (issue #851): /faq, /reviews,
   /reviews/{Slug}, /guides, /guides/{Slug}, /glossary and /glossary/{Slug}.

   Loaded by those seven pages' own markup (<RouteStylesheets Sheets="..." />),
   so it arrives AFTER tokens/reset/app/components: at equal specificity a rule
   here beats the same selector in the core sheet.

   WHY THIS IS ITS OWN SHEET AND NOT A SECTION OF components.css. core is
   105,811 bytes and every page pays it, /collection/{Id} included - which sits
   1,431 bytes under ShippedAssetWeightTests.MaxWorstPageCssBytes, a bound
   docs/architecture/performance-budgets.md says may never rise. A rule put in
   the core to style a page nobody reaches from a collection is charged to that
   collection, which is the whole argument issue #844's split was made on.

   WHY ONE SHEET FOR FOUR FAMILIES RATHER THAN FOUR. The four are one content
   shape: a heading, prose somebody wrote, and a bounded list of it. They share
   .bd-prose (the single wrapper §5.3 of content-pages.md specifies, because
   the sanitiser refuses `class` on an authored element and presentation has to
   come from the page), the language banner, the jump strip and the card. Four
   sheets would repeat all of that four times and no page would get lighter.

   AUTHORED AT 360px (design-system.md §1.1 / §4). The overflow hazards here
   are an authored table and an authored <pre> inside .bd-prose, which is why
   both scroll inside their own box rather than being left to reflow. A slug
   with no break opportunity in a definition list is the other, which is what
   overflow-wrap below is for.
   --------------------------------------------------------------------------- */

/* ============================== Shared furniture ==========================
   Everything on this page family that is not the prose itself. */

.bd-editorial {
  padding: var(--bd-space-2) 0 var(--bd-space-10);
  /* min-width: 0 is what stops a wide descendant - an authored table, a long
     glossary alias - forcing the column wider than the viewport. See
     docs/reuse-outastory-app-layer.md §5. */
  min-width: 0;
}

/* The breadcrumb's own wrapper on the three detail pages. It is NOT .bd-editorial: that block
   carries the page's bottom padding, so putting it on the trail as well would open a
   --bd-space-10 gap between the trail and the content it introduces. The trail's own spacing is
   all it needs; .bd-breadcrumb (routes/breadcrumb.css) draws the list inside it. */
.bd-editorial__trail {
  margin: var(--bd-space-4) 0;
  min-width: 0;
}

.bd-editorial__intro {
  font-size: var(--bd-type-body-lg-size);
  line-height: var(--bd-type-body-lg-line);
  color: var(--bd-color-muted);
  margin: 0 0 var(--bd-space-5);
  max-width: 68ch;
}

/* THE LANGUAGE BANNER (content-pages.md §6.2). It says, in the READER's
   language, that the document they are about to read is in the other one. It
   is a notice rather than an error - a page that exists and is readable - so it
   takes the section surface rather than the warning colours. */
.bd-editorial__language-notice {
  display: block;
  background: var(--bd-color-section-surface);
  border: 1px solid var(--bd-color-border);
  border-radius: var(--bd-radius-md);
  padding: var(--bd-space-3) var(--bd-space-4);
  margin: 0 0 var(--bd-space-5);
  font-size: var(--bd-type-body-sm-size);
  line-height: var(--bd-type-body-sm-line);
  color: var(--bd-color-on-surface);
  max-width: 68ch;
}

/* THE JUMP STRIP: category chips on /faq, topics on /guides, vocabularies on
   /glossary. Anchors to an id on the same page, so it needs no circuit. It
   WRAPS rather than scrolling - design-system.md forbids a horizontal scroll
   on the page, and a strip that scrolls hides its own last entry at 360px. */
.bd-editorial__jump {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bd-space-2);
  margin: 0 0 var(--bd-space-5);
  padding: 0;
  list-style: none;
}

/* SCOPED THROUGH .bd-editorial__jump, AND THAT IS SPECIFICITY ARITHMETIC RATHER THAN STYLE.
   app.css colours every <a> as a link and re-colours it per theme with
   `:root:not([data-theme="light"]) a` and `:root[data-theme="dark"] a` - both (0,2,1). A bare
   `.bd-editorial__jump-link` is (0,1,0) and loses to them, so a chip would come out link-coloured
   in dark theme against --bd-color-surface-raised, which is an undocumented pair and reads as a
   link rather than as a chip. The parent class brings this to (0,2,1), which ties and then wins on
   order because a route sheet loads after the core. components.css does exactly this for
   `.bd-sidebar a:is(.bd-sidebar__row, ...)`, for the same reason. */
.bd-editorial__jump .bd-editorial__jump-link {
  display: inline-flex;
  align-items: center;
  padding: var(--bd-space-1) var(--bd-space-3);
  border: 1px solid var(--bd-color-border);
  border-radius: var(--bd-radius-pill);
  background: var(--bd-color-surface-raised);
  color: var(--bd-color-on-surface);
  font-size: var(--bd-type-body-sm-size);
  line-height: var(--bd-type-body-sm-line);
  text-decoration: none;
  /* 44px of vertical target at 360px without a fixed height, which would clip
     the German labels when they wrap. */
  min-height: 2.75rem;
}

.bd-editorial__jump .bd-editorial__jump-link:hover,
.bd-editorial__jump .bd-editorial__jump-link:focus-visible {
  border-color: var(--bd-color-border-strong);
  background: var(--bd-color-surface-overlay);
}

/* A FILTER THAT IS A REAL GET FORM. /faq and /glossary are public pages whose
   first response has no circuit, so the filter has to be a form the server
   answers - the same decision /minifigs and /events already made. */
.bd-editorial__filter {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bd-space-2);
  align-items: flex-end;
  margin: 0 0 var(--bd-space-6);
}

.bd-editorial__filter-field {
  flex: 1 1 14rem;
  min-width: 0;
}

.bd-editorial__group {
  margin: 0 0 var(--bd-space-8);
  /* scroll-margin so a #fragment jump does not land the heading under the
     56px top strip. The strip is fixed below 905px; above it there is none,
     and the extra space costs nothing. */
  scroll-margin-top: calc(var(--bd-space-8) + var(--bd-safe-area-top));
}

.bd-editorial__group-title {
  margin: 0 0 var(--bd-space-3);
}

.bd-editorial__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bd-space-1) var(--bd-space-3);
  margin: 0 0 var(--bd-space-4);
  font-size: var(--bd-type-body-sm-size);
  line-height: var(--bd-type-body-sm-line);
  color: var(--bd-color-muted);
}

.bd-editorial__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bd-space-3);
  margin: var(--bd-space-6) 0 0;
}

/* THE CLOSING BLOCK on /faq: where to go when the answer was not here. */
.bd-editorial__still-stuck {
  margin: var(--bd-space-8) 0 0;
  padding: var(--bd-space-4);
  border: 1px solid var(--bd-color-border);
  border-radius: var(--bd-radius-md);
  background: var(--bd-color-section-surface);
  max-width: 68ch;
}

.bd-editorial__still-stuck-title {
  margin: 0 0 var(--bd-space-3);
}

/* ============================== Authored prose ============================
   .bd-prose is the ONE wrapper content-pages.md §5.3 specifies, and the reason
   is structural rather than stylistic: the sanitiser refuses `class` on an
   authored element, so an author cannot reach the design system and every rule
   below has to hang off the semantic element instead. That is also why nothing
   here is a BEM element - there is no markup to put an element class on. */

.bd-prose {
  max-width: 68ch;
  min-width: 0;
  color: var(--bd-color-on-surface);
}

.bd-prose p,
.bd-prose li {
  font-size: var(--bd-type-body-size);
  line-height: var(--bd-type-body-line);
  /* An authored paragraph can carry a set number, a URL or a German compound
     with no break opportunity; at 360px one of those is wider than the column
     and would push the page into the horizontal scroll nothing may do. */
  overflow-wrap: break-word;
}

.bd-prose h2 {
  font-size: var(--bd-type-title-size);
  line-height: var(--bd-type-title-line);
  margin: var(--bd-space-6) 0 var(--bd-space-2);
  /* The sanitiser generates a stable id for every h2-h4, so these are jump
     targets; the same clearance the groups above get. */
  scroll-margin-top: calc(var(--bd-space-8) + var(--bd-safe-area-top));
}

.bd-prose h3,
.bd-prose h4 {
  font-size: var(--bd-type-body-lg-size);
  line-height: var(--bd-type-body-lg-line);
  margin: var(--bd-space-5) 0 var(--bd-space-2);
  scroll-margin-top: calc(var(--bd-space-8) + var(--bd-safe-area-top));
}

.bd-prose blockquote {
  margin: var(--bd-space-4) 0;
  padding: 0 0 0 var(--bd-space-4);
  border-left: 3px solid var(--bd-color-primary);
  color: var(--bd-color-muted);
}

.bd-prose code,
.bd-prose kbd {
  background: var(--bd-color-surface-overlay);
  border-radius: var(--bd-radius-sm);
  padding: 0 var(--bd-space-1);
  overflow-wrap: break-word;
}

/* A <pre> and a <table> are the two elements that legitimately cannot wrap, so
   each scrolls inside its own box and the page does not. */
.bd-prose pre {
  overflow-x: auto;
  max-width: 100%;
  background: var(--bd-color-surface-overlay);
  border-radius: var(--bd-radius-md);
  padding: var(--bd-space-3);
}

.bd-prose table {
  display: block;
  overflow-x: auto;
  max-width: 100%;
  border-collapse: collapse;
}

.bd-prose th,
.bd-prose td {
  border: 1px solid var(--bd-color-border);
  padding: var(--bd-space-2);
  text-align: left;
}

/* NO COLOUR HERE, DELIBERATELY. app.css already colours every <a> as a link and re-colours it per
   theme; an authored link inside prose SHOULD look like a link, so there is nothing to override.
   A `color: var(--bd-color-primary-dark)` was written here first and was wrong twice over: dead in
   dark theme, because app.css's own (0,2,1) theme overrides beat this (0,1,1) selector, and
   redundant in light theme, because it restated the value the core rule already sets. */
.bd-prose a {
  overflow-wrap: break-word;
}

.bd-prose details {
  margin: var(--bd-space-4) 0;
  border: 1px solid var(--bd-color-border);
  border-radius: var(--bd-radius-md);
  padding: var(--bd-space-3);
}

.bd-prose summary {
  cursor: pointer;
  font-weight: 600;
  /* The target is bought with padding rather than a flex box, because display
     other than list-item removes the native disclosure marker in WebKit - and
     then the only thing saying the block opens is the cursor. */
  padding: var(--bd-space-2) 0;
}

/* ============================== The FAQ ===================================
   Native details/summary, which is the accordion with no JavaScript at all -
   and that matters because /faq is public and a signed-out visitor's circuit
   is rate-limited, so the first response has to be the whole feature. */

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

.bd-faq__entry {
  border-bottom: 1px solid var(--bd-color-border);
  scroll-margin-top: calc(var(--bd-space-8) + var(--bd-safe-area-top));
}

.bd-faq__question {
  cursor: pointer;
  padding: var(--bd-space-3) 0;
  font-size: var(--bd-type-body-lg-size);
  line-height: var(--bd-type-body-lg-line);
  color: var(--bd-color-on-surface);
}

.bd-faq__answer {
  padding: 0 0 var(--bd-space-4);
}

/* ============================== Reviews ===================================
   NO HERO PHOTOGRAPH IS RENDERED, and that is a decision rather than an
   oversight. SetReview.HeroMediaKey is a key in an editorial media store that
   does not exist yet - /admin/media is not built - so there is no address to
   put in a src. An <img> pointing at a bare key 404s, and one pointing at
   whatever an author pasted would be a third-party subresource fetched before
   the visitor was asked, which is the one thing AGENTS.md forbids outright.
   When the store ships, the hero goes on .bd-review-card and .bd-review, both
   of which already reserve the space by leaving their first child unstyled. */

.bd-review-card {
  border: 1px solid var(--bd-color-border);
  border-radius: var(--bd-radius-md);
  background: var(--bd-color-card-surface);
  padding: var(--bd-space-4);
  display: flex;
  flex-direction: column;
  gap: var(--bd-space-2);
  min-width: 0;
}

.bd-review-card--featured {
  border-color: var(--bd-color-border-strong);
  background: var(--bd-color-surface-raised);
}

.bd-review-card__title {
  margin: 0;
  font-size: var(--bd-type-body-lg-size);
  line-height: var(--bd-type-body-lg-line);
}

.bd-review-card__title a {
  color: var(--bd-color-on-surface);
}

.bd-review-card__summary {
  margin: 0;
  color: var(--bd-color-muted);
  overflow-wrap: break-word;
}

.bd-review-card__footer {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bd-space-2) var(--bd-space-3);
  align-items: center;
  font-size: var(--bd-type-body-sm-size);
  line-height: var(--bd-type-body-sm-line);
  color: var(--bd-color-muted);
}

/* THE STAR ROW. Five glyphs and a text alternative rather than an image: no
   fetch, no token, and a screen reader gets "4 out of 5" from the label rather
   than five repeated characters. The filled and empty states are two colours
   of the same character, both already in TokenContrastTests' table. */
.bd-review-stars {
  display: inline-flex;
  gap: 0.125rem;
  /* The visible glyphs are decoration over a real text label, so they are
     hidden from assistive technology in the markup; this only paints them. */
  letter-spacing: 0.05em;
}

/* THE FILLED STAR TAKES THE THREE-STATE PATTERN, not one unconditional token, because
   --bd-color-primary is only a documented pair against a card or section surface in the LIGHT
   palette; the dark palette's counterpart is --bd-color-primary-light. Both pairs are in
   TokenContrastTests at the non-text floor (a star row is a graphical object with a real text
   alternative beside it, never text), and writing one token for both themes would have been an
   invented pair in one of them. The order is tokens.css's own: light on bare :root, the
   prefers-color-scheme override guarded against an explicit light choice, then the explicit dark
   choice last so the switch wins in both directions. */
.bd-review-stars__on {
  color: var(--bd-color-primary);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bd-review-stars__on {
    color: var(--bd-color-primary-light);
  }
}

:root[data-theme="dark"] .bd-review-stars__on {
  color: var(--bd-color-primary-light);
}

/* THE UNFILLED STAR IS --bd-color-muted, AND THE FIRST ATTEMPT MEASURED 2.92:1.
   --bd-color-border-strong was the obvious choice - it is the divider token - and
   TokenContrastTests refused it: #868C90 on the verdict block's #EAEEF0 is 2.92:1, below the 3.0:1
   non-text floor, in both light palettes. Which glyphs are NOT filled is half of how "three of
   five" is read, so an unfilled star has to clear that floor rather than merely be present.
   --bd-color-muted clears 4.5:1 on a card surface and on a tinted section, both already documented,
   and it is the right word for the job besides. The filled and unfilled states differ in hue as
   well as in lightness, and the rating travels as prose in a visually hidden span regardless. */
.bd-review-stars__off {
  color: var(--bd-color-muted);
}

/* THE VERDICT BLOCK on a review's own page: stars, the one-sentence summary,
   build hours and the link to the set. content-pages.md §2.4 requires all four
   above the fold on a 390px phone, which is why it is a bordered block at the
   top rather than a sidebar. */
.bd-review__verdict {
  border: 1px solid var(--bd-color-border-strong);
  border-radius: var(--bd-radius-md);
  background: var(--bd-color-section-surface);
  padding: var(--bd-space-4);
  margin: 0 0 var(--bd-space-5);
  display: flex;
  flex-direction: column;
  gap: var(--bd-space-3);
  max-width: 68ch;
}

.bd-review__verdict-heading {
  margin: 0;
  font-size: var(--bd-type-label-size);
  line-height: var(--bd-type-label-line);
  letter-spacing: var(--bd-type-label-tracking);
  text-transform: uppercase;
  color: var(--bd-color-muted);
}

.bd-review__summary {
  margin: 0;
  font-size: var(--bd-type-body-lg-size);
  line-height: var(--bd-type-body-lg-line);
}

/* ============================== Guides ====================================
   The outcome line and the time estimate, together, in one bordered block -
   content-pages.md §3.4: a reader deciding whether to start needs exactly
   these two facts and nothing else. */

.bd-guide__outcome {
  border: 1px solid var(--bd-color-border-strong);
  border-radius: var(--bd-radius-md);
  background: var(--bd-color-section-surface);
  padding: var(--bd-space-4);
  margin: 0 0 var(--bd-space-4);
  max-width: 68ch;
}

.bd-guide__outcome-heading {
  margin: 0 0 var(--bd-space-1);
  font-size: var(--bd-type-label-size);
  line-height: var(--bd-type-label-line);
  letter-spacing: var(--bd-type-label-tracking);
  text-transform: uppercase;
  color: var(--bd-color-muted);
}

.bd-guide__outcome-text {
  margin: 0;
  font-size: var(--bd-type-body-lg-size);
  line-height: var(--bd-type-body-lg-line);
}

.bd-guide__contents {
  margin: 0 0 var(--bd-space-6);
  border: 1px solid var(--bd-color-border);
  border-radius: var(--bd-radius-md);
  padding: var(--bd-space-3);
  max-width: 68ch;
}

.bd-guide__contents-list {
  margin: var(--bd-space-2) 0 0;
  padding-left: var(--bd-space-5);
}

.bd-guide-card {
  border: 1px solid var(--bd-color-border);
  border-radius: var(--bd-radius-md);
  background: var(--bd-color-card-surface);
  padding: var(--bd-space-4);
  display: flex;
  flex-direction: column;
  gap: var(--bd-space-2);
  min-width: 0;
}

.bd-guide-card__title {
  margin: 0;
  font-size: var(--bd-type-body-lg-size);
  line-height: var(--bd-type-body-lg-line);
}

.bd-guide-card__title a {
  color: var(--bd-color-on-surface);
}

.bd-guide-card__outcome {
  margin: 0;
  color: var(--bd-color-muted);
  overflow-wrap: break-word;
}

.bd-guide-card__footer {
  font-size: var(--bd-type-body-sm-size);
  line-height: var(--bd-type-body-sm-line);
  color: var(--bd-color-muted);
}

/* ============================== Glossary ==================================
   The index renders every short definition inline - content-pages.md §4.3: a
   reader who lands on /glossary never has to click to learn anything, so there
   is no accordion. A 240-character definition is shorter than the control that
   would hide it. */

/* A description list, so the term-and-definition pairing is in the markup
   rather than only in the layout. */
.bd-glossary {
  margin: 0;
}

.bd-glossary__term {
  margin: var(--bd-space-4) 0 0;
  font-size: var(--bd-type-body-lg-size);
  line-height: var(--bd-type-body-lg-line);
  font-weight: 600;
  scroll-margin-top: calc(var(--bd-space-8) + var(--bd-safe-area-top));
  /* A headword can be an identifier with no break opportunity. */
  overflow-wrap: break-word;
}

.bd-glossary__definition {
  margin: var(--bd-space-1) 0 0;
  padding: 0;
  max-width: 68ch;
  color: var(--bd-color-on-surface);
  overflow-wrap: break-word;
}

.bd-glossary__aliases {
  display: block;
  margin-top: var(--bd-space-1);
  font-size: var(--bd-type-body-sm-size);
  line-height: var(--bd-type-body-sm-line);
  color: var(--bd-color-muted);
  overflow-wrap: break-word;
}

.bd-glossary__more {
  display: inline-block;
  margin-top: var(--bd-space-1);
  font-size: var(--bd-type-body-sm-size);
  line-height: var(--bd-type-body-sm-line);
}

.bd-glossary-term__definition {
  font-size: var(--bd-type-body-lg-size);
  line-height: var(--bd-type-body-lg-line);
  margin: 0 0 var(--bd-space-4);
  max-width: 68ch;
  overflow-wrap: break-word;
}

/* ============================== Above 905px ===============================
   The one responsive override in this sheet, and it is live rather than dead:
   below 905px the card lists are one column because 360px cannot hold two, and
   from 905px up they are a grid. Nothing else here changes with the viewport -
   every other rule is a reading measure in ch, which is already responsive. */

.bd-editorial__cards {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--bd-space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 905px) {
  .bd-editorial__cards {
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
  }
}
