/* ==========================================================================
   bestiary.css — the Bestiary section: a filterable, reference-only monster
   browser. Layout = filter sidebar (desktop) / collapsible drawer (mobile) +
   a responsive grid of reference cards.

   Reuse: the reference cards are self-contained (.bestiary-card__*) and styled
   1:1 with the Treasure page's .treasure-card so the two browse surfaces read
   identically. The [i] full-stat-block sheet still reuses .action-modal /
   .enc-info-modal / .enc-sheet from encounter.css verbatim (via showInfoModal +
   renderMonsterSheetHTML in bestiary.js).

   No new design tokens are introduced — all color/space/type values come from
   base.css custom properties.
   ========================================================================== */

/* ---- Page header: a single two-column row matching the Encounters page
   (.encounter-module__header) — title on the LEFT, count + sort control
   clustered on the RIGHT (count immediately left of the dropdown). This
   replaces the old full-width title band (which left an empty strip under the
   title) and the separate results bar. The filter sidebar + results grid sit
   below it. ----------------------------------------------------------------- */

.bestiary__page-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
  /* No own margin: the parent .module-view flex column gap governs the
     header->content step (matches the other section pages). */
  margin: 0;
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--color-border);
}

/* Left side: the page title. */
.bestiary__head-left {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-md);
  min-width: 0;
}

.bestiary__page-head .bestiary__title {
  margin: 0;
}

/* Right side: the live monster count immediately to the left of the sort
   dropdown. margin-left:auto pins the cluster right even when the title wraps. */
.bestiary__head-right {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-md);
  margin-left: auto;
  min-width: 0;
}

/* ---- Layout: sidebar + main ------------------------------------------------ */

.bestiary__layout {
  display: grid;
  grid-template-columns: 17rem 1fr;
  gap: var(--space-lg);
  align-items: start;
}

/* Collapsed (desktop): the rail shrinks to a thin spine (its head only) and the
   results reflow into the freed width — the grid flips to `min-content 1fr`.
   Mirrors the shared roster_filter rail's rf-layout--collapsed behavior. */
.bestiary__layout--collapsed {
  grid-template-columns: min-content minmax(0, 1fr);
}

.bestiary__layout--collapsed .bestiary__panel-body {
  display: none;
}

/* On the thin spine the chevron + active dot carry the meaning; keep the title
   for screen readers but visually hide it so the spine stays narrow. */
.bestiary__layout--collapsed .bestiary__panel-title,
.bestiary__layout--collapsed .bestiary__clear {
  display: none;
}

.bestiary__layout--collapsed .bestiary__panel {
  max-height: none;
  overflow: visible;
}

/* Filter panel — a sticky rail on desktop. */
.bestiary__panel {
  position: sticky;
  top: var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-md);
  border: 1px solid var(--color-border);
  background-color: var(--color-surface);
  max-height: calc(100vh - 2 * var(--space-md));
  overflow-y: auto;
}

.bestiary__panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}

/* The chevron collapse trigger wraps the chevron + the title + the active dot so
   the whole head row reads as a heading but behaves as a button (mirrors the
   shared rf-collapse). */
.bestiary__collapse {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  flex: 1 1 auto;
  min-width: 0;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  color: inherit;
  text-align: left;
}

.bestiary__collapse-chevron {
  width: 0.85rem;
  height: 0.85rem;
  flex: none;
  color: var(--color-text-muted);
  transition: transform var(--transition-fast, 0.12s ease);
}

/* Expanded: chevron points down (rotate the left-pointing glyph). Collapsed: it
   keeps its authored left/right direction. */
.bestiary__collapse[aria-expanded="true"] .bestiary__collapse-chevron {
  transform: rotate(-90deg);
}

.bestiary__collapse:hover .bestiary__collapse-chevron,
.bestiary__collapse:focus-visible .bestiary__collapse-chevron {
  color: var(--color-accent);
}

.bestiary__panel-title {
  margin: 0;
  font-family: var(--font-heading-ui);
  font-size: 0.95rem;
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  color: var(--color-heading);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Active-filter dot — a small accent pip that stays lit (even on the collapsed
   spine) whenever any filter is applied, so a narrowed roster still reads as
   filtered. */
.bestiary__active-dot {
  width: 0.5rem;
  height: 0.5rem;
  flex: none;
  border-radius: 999px;
  background-color: var(--color-accent);
}

.bestiary__active-dot[hidden] {
  display: none;
}

/* The body wraps search + facet groups so one collapse hides them all with no
   layout jump (re-establishes the column flow/gap the panel gave those children
   directly, so expanded looks identical to before). */
.bestiary__panel-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  min-width: 0;
}

.bestiary__clear {
  font-family: var(--font-heading-ui);
  font-size: 0.72rem;
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  color: var(--color-accent);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.1rem 0.2rem;
}

.bestiary__clear:hover:not(:disabled),
.bestiary__clear:focus-visible:not(:disabled) {
  color: var(--color-accent-hover);
}

.bestiary__clear:disabled {
  color: var(--color-text-muted);
  opacity: 0.4;
  cursor: default;
}

.bestiary__search {
  margin-bottom: var(--space-xs);
}

/* ---- Filter groups (collapsible <details>) -------------------------------- */

.bestiary__group {
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-xs);
}

.bestiary__group-summary {
  font-family: var(--font-heading-ui);
  font-size: 0.78rem;
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  color: var(--color-text-muted);
  cursor: pointer;
  padding: 0.35rem 0;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

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

.bestiary__group-summary::after {
  content: "+";
  font-family: var(--font-mono);
  color: var(--color-text-muted);
}

.bestiary__group[open] > .bestiary__group-summary::after {
  content: "–";
}

.bestiary__group-summary:hover,
.bestiary__group[open] > .bestiary__group-summary {
  color: var(--color-text);
}

.bestiary__group-body {
  padding: var(--space-xs) 0 var(--space-sm);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.bestiary__group-note {
  margin: 0;
  font-size: 0.72rem;
  font-style: italic;
  color: var(--color-text-muted);
}

/* ---- Checkboxes ----------------------------------------------------------- */

.bestiary__checks {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.bestiary__checks--inline {
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.bestiary__check {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: 0.85rem;
  color: var(--color-text);
  cursor: pointer;
}

.bestiary__check input {
  accent-color: var(--color-accent);
  cursor: pointer;
  flex: none;
}

.bestiary__check-label {
  flex: 1 1 auto;
  min-width: 0;
}

.bestiary__checks--inline .bestiary__check-label {
  flex: 0 0 auto;
}

.bestiary__check-count {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--color-text-muted);
  flex: none;
}

/* ---- Range (min/max) ------------------------------------------------------ */

.bestiary__range {
  display: flex;
  align-items: flex-end;
  gap: var(--space-xs);
}

.bestiary__range-field {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  flex: 1 1 0;
  min-width: 0;
}

.bestiary__range-label {
  font-family: var(--font-heading-ui);
  font-size: 0.62rem;
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.bestiary__range-input {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  padding: 0.3rem 0.4rem;
}

.bestiary__range-sep {
  color: var(--color-text-muted);
  padding-bottom: 0.4rem;
}

/* ---- Main column: result bar + grid --------------------------------------- */

.bestiary__main {
  min-width: 0;
}

/* Count sits in the right cluster, immediately left of the sort dropdown. */
.bestiary__count {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  color: var(--color-text);
  align-self: center;
}

/* Sort control on the header's right side, after the count. */
.bestiary__sort {
  flex: none;
  min-width: 11rem;
  align-self: center;
}

/* Result grid — same auto-fill model as the Treasure page so the two surfaces
   tile identically. min-width:0 lets the grid resolve tracks against the
   available inline space rather than the widest card's max-content — without it
   a child that can't shrink (a nowrap tag, a mono stat) inflates 1fr and
   collapses auto-fill back to one column. */
.bestiary__results {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: var(--space-md);
  align-items: start;
  min-width: 0;
}

/* Grid children must also permit shrinking below their content size, or they
   re-inflate the track they sit in (classic CSS-grid/flex overflow fix). */
.bestiary__results > * {
  min-width: 0;
}

.bestiary__results.is-empty {
  display: block;
}

/* ---- Reference card --------------------------------------------------------
   Self-contained card styled 1:1 with the Treasure page's .treasure-card so the
   two browse surfaces read identically: a square media well (token art, with an
   inline-SVG type emblem fallback), the monster name as a stretched-link button
   inside an <h3>, a LVL/AC/HP stat line, a tag row, and a line-clamped flavor
   preview. position:relative anchors the button's stretched ::after overlay. - */

.bestiary-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-md);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  background-color: var(--color-surface);
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease;
  /* Allow the card to shrink within its grid track instead of forcing the
     track to its (large) content width. */
  min-width: 0;
}

.bestiary-card:hover,
.bestiary-card:focus-within {
  background-color: var(--color-surface-hover);
  border-color: var(--color-accent);
}

.bestiary-card__header {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  min-width: 0;
}

/* Media well — a square frame (OSR sharp corners). The token PNG fills it
   (cover); the inline-SVG type emblem fallback centers a tasteful mark. Matches
   the Treasure card's media well dimensions/treatment. */
.bestiary-card__media {
  position: relative;
  flex: none;
  width: 3.75rem;
  height: 3.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--color-oracle-bg, var(--color-surface));
  border: 1px solid var(--color-border);
  overflow: hidden;
}

.bestiary-card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 20%;
  display: block;
}

.bestiary-card__emblem {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-muted);
}

.bestiary-card__emblem svg {
  width: 55%;
  height: 55%;
  opacity: 0.7;
}

.bestiary-card__heading {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.bestiary-card__name {
  margin: 0;
  font-family: var(--font-heading-ui);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--color-heading);
  line-height: 1.2;
  /* A long single-word name must break rather than widen the track. */
  overflow-wrap: anywhere;
}

/* The open control — visually just the monster name, but a genuine focusable,
   screen-reader-announced button. Reset UA chrome and let it sit inline. */
.bestiary-card__open {
  display: inline;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

/* Stretch the button's hit area over the entire card ("stretched link"). Lives
   on the button only, so a future nested control above it in z-order stays
   clickable. */
.bestiary-card__open::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

.bestiary-card__open:focus-visible {
  outline-offset: 2px;
}

/* Compact stat line under the name (LVL / AC / HP). */
.bestiary-card__stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-sm);
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--color-text);
}

.bestiary-card__stat-label {
  font-family: var(--font-heading-ui);
  font-size: 0.6rem;
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* The attribute "i" tooltip rides on the stat label. Raise it above the card's
   stretched open-overlay (z-index:1 on .bestiary-card__open::after) so the info
   button stays independently clickable and its popover isn't intercepted. */
.bestiary-card__stat-label .info-icon {
  position: relative;
  z-index: 2;
}

/* Tags row (alignment + type + Fearless). */
.bestiary-card__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.bestiary-card__tag {
  font-family: var(--font-heading-ui);
  font-size: 0.65rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: 0.08rem 0.4rem;
  white-space: nowrap;
}

.bestiary-card__tag--fearless {
  color: var(--color-accent);
  border-color: var(--color-accent);
}

/* Terrain chips (bestiary terrain chips, 2026-06-28) — a small image per terrain
   the creature is found in. Sits between the stats and the tags. Each chip carries
   a title + alt/aria-label so hover and screen readers both announce the terrain
   name (e.g. "Arctic"). Sized + bordered consistent with the tag tokens above. */
.bestiary-card__terrains {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  align-items: center;
  /* Raise above the card's stretched open-overlay (z-index:1) so the per-chip
     hover title (e.g. "Arctic") surfaces on mouseover instead of being masked. */
  position: relative;
  z-index: 2;
}

.bestiary-card__terrain-img {
  width: 1.4rem;
  height: 1.4rem;
  object-fit: cover;
  border: 1px solid var(--color-border);
  background-color: var(--color-surface);
}

/* Glyph fallback when both webp + png 404: a bordered square with the terrain's
   initial, matching the image chip footprint so the row never reflows. */
.bestiary-card__terrain-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  font-family: var(--font-heading-ui);
  font-size: 0.7rem;
  text-transform: uppercase;
  color: var(--color-text-muted);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
}

/* Compact preview only — the full flavor lives in the stat-block modal. Clamp
   to 3 lines so a long blurb can't drive a card into a full-width row (the
   layout driver behind the desktop one-wide-column / mobile overflow bug). The
   line-clamp also caps height; min-width:0 + overflow-wrap keep long tokens in
   bounds. */
.bestiary-card__desc {
  margin: 0;
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--color-text-muted);
  line-height: var(--lh-body);
  min-width: 0;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

/* ---- Saved-monster card (My Monsters tab, UI batch 7 Phase B) --------------
   The My Monsters library renders the SAME .bestiary-card as the browse list
   (one card builder, in bestiary.js). saved.js paints them into renderRoster's
   card grid (.char-card-grid, already an auto-fill grid). The only extra chrome
   the saved card needs is a raised corner Delete that sits ABOVE the card's
   stretched open overlay (z-index:1 on .bestiary-card__open::after), so its own
   click deletes rather than opening the record. Anchored bottom-right (not top)
   so it never overlaps the header's name/stat line; the card reserves a little
   foot padding so a clamped flavor line clears it. Always visible (not
   hover-only) for touch + keyboard discoverability. */
.bestiary-card--saved {
  padding-bottom: calc(var(--space-md) + 1.6rem);
}

.bestiary-card__delete {
  position: absolute;
  z-index: 2;             /* above the .bestiary-card__open::after overlay */
  bottom: var(--space-sm);
  right: var(--space-sm);
  padding: 0.15rem 0.55rem;
  font-size: 0.7rem;
  line-height: 1.4;
}

/* ---- Empty state ---------------------------------------------------------- */

.bestiary__empty {
  text-align: center;
  padding: var(--space-2xl) var(--space-md);
  border: 1px dashed var(--color-border);
  background-color: var(--color-surface);
}

.bestiary__empty-title {
  font-family: var(--font-heading);
  font-size: 1.3rem;
  color: var(--color-heading);
  margin: 0 0 var(--space-xs);
}

.bestiary__empty-sub {
  color: var(--color-text-muted);
  font-style: italic;
  margin: 0 0 var(--space-md);
}

/* ---- Loading state (#8) ---------------------------------------------------
   The catalog fetch can be slow. The busy read is the SHARED animated-text idiom
   (`.roster-loading`, styled in roster_filter.css) — a pulsing muted line, never
   a rotating spinner (Alex Do-Not #11). This wrapper just centers that line in
   the parchment panel. ------------------------------------------------------ */

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

/* ---- Mobile filter toggle (hidden on desktop) ----------------------------- */

.bestiary__filter-toggle {
  display: none;
  align-items: center;
  gap: var(--space-xs);
  font-family: var(--font-heading-ui);
  font-size: 0.8rem;
  letter-spacing: var(--ls-cinzel-caps);
  text-transform: uppercase;
  color: var(--color-text);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: 0.5rem 0.85rem;
  cursor: pointer;
}

.bestiary__filter-toggle svg {
  width: 1rem;
  height: 1rem;
  flex: none;
}

.bestiary__filter-toggle-count {
  color: var(--color-accent);
  font-size: 1rem;
  line-height: 1;
}

.bestiary__filter-toggle:hover,
.bestiary__filter-toggle:focus-visible {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

/* ==========================================================================
   Responsive — below 820px the filter rail collapses into a top drawer toggled
   by the Filters button. The panel becomes a full-width block above results.
   ========================================================================== */

@media (max-width: 820px) {
  .bestiary__layout,
  .bestiary__layout--collapsed {
    grid-template-columns: 1fr;
  }

  /* The desktop rail-collapse spine doesn't apply on the stacked drawer layout:
     restore the body + title so the mobile Filters drawer shows its full panel. */
  .bestiary__layout--collapsed .bestiary__panel-body {
    display: flex;
  }

  .bestiary__layout--collapsed .bestiary__panel-title {
    display: inline;
  }

  /* Collapse the results to a single column at the drawer breakpoint. An
     auto-fill grid sizes its tracks from the container's min-content, and with
     the rail gone the cards' min-content (~2 columns) otherwise propagates back
     up through #app-content and pushes the page wider than the viewport — the
     mobile horizontal-scroll bug. One explicit column lets the chain shrink to
     the viewport (mirrors the Treasure page). */
  .bestiary__results {
    grid-template-columns: 1fr;
  }

  .bestiary__filter-toggle {
    display: inline-flex;
    margin-bottom: var(--space-sm);
  }

  .bestiary__panel {
    position: static;
    max-height: none;
    display: none;
    margin-bottom: var(--space-md);
  }

  .bestiary--filters-open .bestiary__panel {
    display: flex;
  }

  /* Header collapses to a stack: title, then the count + sort cluster full-width
     below, so nothing is cramped on a phone. */
  .bestiary__page-head {
    flex-direction: column;
    align-items: stretch;
  }

  .bestiary__head-right {
    margin-left: 0;
  }

  .bestiary__sort {
    min-width: 0;
    flex: 1 1 auto;
  }
}

@media (max-width: 480px) {
  .bestiary__results {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================================
   Reduced motion — the section already relies on app.js's content fade; this
   module adds no animation of its own, but honour the preference for the
   <details> toggles and any inherited transitions.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .bestiary__filter-toggle,
  .bestiary__clear,
  .bestiary__collapse-chevron,
  .bestiary-card {
    transition: none;
  }

}
