/*
  Bravith — components.css
  Reusable UI components: buttons, cards, badges, breadcrumbs, TOC, forms.
*/

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 0.75rem 1.5rem;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: 700;
  border: 1px solid transparent;
  transition: background-color var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast), transform var(--transition-fast);
  white-space: nowrap;
}

.btn:active {
  transform: translateY(1px);
}

.btn-primary {
  background-color: var(--color-accent);
  color: var(--color-on-accent);
  border-color: var(--color-accent);
}

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

.btn-secondary {
  background-color: var(--color-surface);
  border-color: var(--color-border-strong);
  color: var(--color-text);
}

.btn-secondary:hover {
  background-color: var(--color-surface-hover);
  border-color: var(--color-text-faint);
}

.btn-block {
  width: 100%;
}

/* ---------- Badges & tags ---------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.35rem 0.85rem;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  transition: background-color var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}

.badge-accent {
  background-color: var(--color-accent-soft);
  border-color: var(--color-accent-border);
  color: var(--color-accent);
}

.badge-secondary {
  background-color: var(--color-secondary-soft);
  color: var(--color-secondary);
}

.badge-info {
  background-color: var(--color-info-soft);
  color: var(--color-info);
}

/* Takes its colour from --tint on an ancestor, which the homepage generator
   sets per category. Falls back to the brand accent, so it is safe to use
   anywhere, not only under an element that supplies a tint. */
.badge-tint {
  background-color: var(--tint-soft, var(--color-accent-soft));
  color: var(--tint, var(--color-accent));
  border-color: transparent;
}

.badge-outline {
  background-color: var(--color-surface);
  border-color: var(--color-border-strong);
  color: var(--color-text-muted);
  text-transform: none;
  font-weight: 600;
  letter-spacing: normal;
}

a.badge:hover {
  filter: brightness(1.15);
}

.tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* ---------- Breadcrumbs ----------
   ONE treatment for every page on the site. Two selectors, because the trail
   lives in two places in the markup and moving it would mean rewriting twenty
   published resource pages for a visual change:

     .breadcrumbs              a <nav> between the header and <main>, on the
                               six category pages and the seven static/guide
                               pages. Markup is hand-written per page but is
                               byte-identical to bravith_render_breadcrumb_nav()
                               in app/publish.php, which is the specification —
                               a test asserts every page still matches it.
     .resource-hero-breadcrumb the same trail inline at the top of a resource
                               page's hero.

   Both are the same object as far as a reader is concerned, so everything that
   decides how it LOOKS is declared once, for both.

   It used to be a full-width bordered strip. That gave a line of nine words its
   own horizontal rule across the page, which read as a piece of site furniture
   rather than as the first line of the page it belongs to. Now it is small,
   borderless, and sits close enough to the title to be read as part of it —
   which is what a breadcrumb is. The links carry the brand accent because they
   are the only thing on that line that goes anywhere. */
.breadcrumbs {
  padding-block: var(--space-8) var(--space-5);
}

/* The heading block below is where the page's own top spacing normally comes
   from, and with a trail above it that spacing lands twice. The breadcrumb owns
   it instead — one rule, both page shapes, no HTML touched. */
.breadcrumbs + main > .page-header,
.breadcrumbs + main > .category-hero {
  padding-top: 0;
}

.breadcrumbs ol,
.resource-hero-breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.breadcrumbs,
.resource-hero-breadcrumb {
  font-size: var(--text-sm);
  color: var(--color-text-faint);
}

.breadcrumbs a,
.resource-hero-breadcrumb a {
  color: var(--color-accent);
  transition: color var(--transition-fast);
}

.breadcrumbs a:hover,
.resource-hero-breadcrumb a:hover {
  color: var(--color-accent-hover);
}

.breadcrumbs li:not(:last-child)::after,
.resource-hero-breadcrumb li:not(:last-child)::after {
  content: "/";
  margin-left: var(--space-2);
  color: var(--color-text-faint);
}

.breadcrumbs li[aria-current="page"],
.resource-hero-breadcrumb li[aria-current="page"] {
  color: var(--color-text-muted);
}

/* ---------- Category cards (homepage) ---------- */
.category-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-6);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  transition: border-color var(--transition-fast), transform var(--transition-fast), background-color var(--transition-fast);
}

.category-card:hover {
  border-color: var(--color-accent-border);
  background-color: var(--color-surface-hover);
  transform: translateY(-2px);
}

.category-icon {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-xs);
  background-color: var(--color-accent-soft);
  color: var(--color-accent);
}

.category-icon svg {
  width: 24px;
  height: 24px;
}

.category-card h3 {
  font-size: var(--text-lg);
}

.category-card p {
  font-size: var(--text-sm);
  flex: 1;
}

.category-card .card-link {
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-accent);
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

/* ---------- Article / resource cards ---------- */
.card {
  display: flex;
  flex-direction: column;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  transition: border-color var(--transition-fast), transform var(--transition-fast);
  height: 100%;
}

.card:hover {
  border-color: var(--color-border-strong);
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.card-media {
  aspect-ratio: 16 / 9;
  background-color: var(--color-bg-elevated);
  border-bottom: 1px solid var(--color-border);
}

.card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.card-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  flex: 1;
}

/* A badge sitting directly in the card body is a flex item in a COLUMN, so it
   stretches the full card width unless told otherwise — which made the type
   pill on category and archive cards read as a banner. Scoped to this one
   position on purpose: badges that sit in a row (.home-card-tags,
   .home-latest-head, .tag-row) are centred on the cross axis there, and a
   blanket rule would shunt them to the top of their line. */
.card-body > .badge {
  align-self: flex-start;
}

.card-title {
  font-size: var(--text-md);
  line-height: var(--leading-tight);
}

.card-title a::after {
  content: "";
  position: absolute;
  inset: 0;
}

.card {
  position: relative;
}

.card-excerpt {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  flex: 1;
}

.card-footer {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-text-faint);
  margin-top: auto;
}

.card-footer .dot::before {
  content: "\2022";
  margin-right: var(--space-3);
}

/* ---------- Guide cards ---------- */
.guide-card {
  display: flex;
  gap: var(--space-4);
  padding: var(--space-5);
  background-color: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  position: relative;
  transition: border-color var(--transition-fast), transform var(--transition-fast);
}

.guide-card:hover {
  border-color: var(--color-accent-border);
  transform: translateY(-2px);
}

.guide-index {
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-xs);
  background-color: var(--color-accent-soft);
  color: var(--color-accent);
  font-weight: 800;
  font-size: var(--text-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.guide-card h3 {
  font-size: var(--text-md);
}

.guide-card p {
  font-size: var(--text-sm);
  margin-top: var(--space-1);
}

.guide-card .card-title a::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* ---------- Resource list rows (popular resources) ---------- */
.resource-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  position: relative;
  transition: border-color var(--transition-fast), transform var(--transition-fast);
}

.resource-row:hover {
  border-color: var(--color-accent-border);
  transform: translateY(-2px);
}

.resource-row-media {
  width: 52px;
  height: 52px;
  border-radius: var(--radius-xs);
  border: 1px solid var(--color-border);
  flex-shrink: 0;
  overflow: hidden;
  background-color: var(--color-bg-elevated);
}

.resource-row-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.resource-row-body {
  flex: 1;
  min-width: 0;
}

.resource-row-body h3 {
  font-size: var(--text-base);
}

.resource-row-body h3 a::after {
  content: "";
  position: absolute;
  inset: 0;
}

.resource-row-body p {
  font-size: var(--text-sm);
  margin-top: 2px;
}

.resource-row-meta {
  font-size: var(--text-xs);
  color: var(--color-text-faint);
  margin-top: var(--space-1);
}

/* ---------- Latest content list ---------- */
.latest-list {
  display: flex;
  flex-direction: column;
}

.latest-item {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: var(--space-5);
  padding-block: var(--space-5);
  border-bottom: 1px solid var(--color-border);
}

.latest-item:last-child {
  border-bottom: none;
}

.latest-date {
  font-size: var(--text-xs);
  color: var(--color-text-faint);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding-top: 2px;
}

.latest-body h3 {
  font-size: var(--text-base);
}

.latest-body p {
  font-size: var(--text-sm);
  margin-top: var(--space-1);
}

/* ---------- Table of contents ---------- */
.toc {
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  padding: var(--space-5);
}

.toc-title {
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-faint);
  margin-bottom: var(--space-3);
}

.toc ol {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  counter-reset: toc;
}

.toc a {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  display: block;
}

.toc a:hover,
.toc a.is-active {
  color: var(--color-accent);
}

/* ---------- Definition / meta table ---------- */
.meta-table {
  width: 100%;
  font-size: var(--text-sm);
}

.meta-table tr {
  border-bottom: 1px solid var(--color-border);
}

.meta-table tr:last-child {
  border-bottom: none;
}

.meta-table th,
.meta-table td {
  text-align: left;
  padding: var(--space-3) 0;
  vertical-align: top;
}

.meta-table th {
  width: 40%;
  color: var(--color-text-faint);
  font-weight: 600;
}

.meta-table td {
  color: var(--color-text);
}

/* ---------- Steps ----------
   Numbered indicators are solid-filled (not soft-tinted) so they read
   as strong anchors, connected by a hairline "rail" so the sequence
   reads as one continuous flow rather than isolated rows. */
.steps {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  counter-reset: step;
}

.steps {
  gap: var(--space-6);
}

.step {
  display: grid;
  grid-template-columns: 30px 1fr;
  gap: var(--space-4);
  position: relative;
}

.step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 14px;
  /* Follows the marker: it is pulled up 2px by .step-number below, so the rail
     starts 2px higher to keep the same gap beneath the circle. */
  top: 34px;
  bottom: calc(var(--space-6) * -1 + 4px);
  width: 1px;
  background-color: var(--color-border);
}

/* Smaller than it was. At 40px the markers read as a dashboard; at 30px they
   are still unmistakably the step numbers but the text beside them stays the
   thing you look at first, which is what the reference does. */
.step-number {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background-color: var(--color-accent);
  color: var(--color-on-accent);
  font-size: var(--text-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  z-index: 1;
  /* Sit against the first line of the heading, not the middle of the whole
     step. Aligning to the row would drop the marker to the centre of a
     two-line heading; this keeps it beside line one either way. The 2px is
     half the difference between the marker (30px) and the heading's first
     line box, so the two read as level. */
  align-self: start;
  margin-top: -2px;
}

.step h3 {
  font-size: var(--text-md);
  margin-bottom: var(--space-2);
  /* A step heading sits beside its number, so it should not carry the top
     margin the surrounding article gives an ordinary h3. */
  margin-top: 0;
  line-height: 1.35;
}

/* THE ALIGNMENT FIX. `.guide-body h3` further down this file and `.prose h3`
   in main.css both give an article heading a 2rem top margin, and both have
   the same specificity as `.step h3` above while coming later in the cascade —
   so inside a guide the step heading won a margin it was never meant to carry.
   The marker stayed at the top of the grid row while the heading dropped, and
   that gap is what read as the number sitting too high. Raising the
   specificity restores margin-top alone; the font size and line height a guide
   gives the heading are deliberately left as they are. */
.steps .step h3 {
  margin-top: 0;
}

.step p {
  font-size: var(--text-sm);
}

.step p:last-child {
  margin-bottom: 0;
}

/* The step's own body column. Guides written before this class existed use a
   bare <div> in the same slot, so both are addressed. */
.step > div,
.step-body {
  min-width: 0;
  padding-bottom: var(--space-2);
}

/* ---------- Callouts ----------
   A tinted, left-accented strip rather than another bordered box. The colour
   carries the register — but never alone: each variant is introduced by its own
   icon, and an authored .callout-title names it in words, so the meaning
   survives greyscale, colour-blindness and a screen reader.

   Variants: (default) note/info, tip, important, warning, success. */
.callout {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-info);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background-color: var(--color-info-soft);
  font-size: var(--text-sm);
  line-height: 1.6;
  margin-block: var(--space-6);
}

.callout-icon {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  margin-top: 0.1em;
  color: var(--color-info);
}

.callout-body {
  min-width: 0;
}

.callout-title {
  display: block;
  margin-bottom: var(--space-1);
  font-weight: 700;
  color: var(--color-text);
}

.callout p {
  margin: 0;
}

.callout p + p {
  margin-top: var(--space-3);
}

.callout-warning {
  border-left-color: var(--color-warning);
  background-color: var(--color-warning-soft);
}

.callout-warning .callout-icon {
  color: var(--color-warning);
}

/* Important is the loudest register, so it borrows the brand accent rather
   than a status colour — it means "this is the thing people get wrong". */
.callout-important {
  border-left-color: var(--color-accent);
  background-color: var(--color-accent-soft);
}

.callout-important .callout-icon {
  color: var(--color-accent);
}

.callout-tip,
.callout-success {
  border-left-color: var(--color-success);
  background-color: var(--color-success-soft);
}

.callout-tip .callout-icon,
.callout-success .callout-icon {
  color: var(--color-success);
}

/* Note is the quietest: the page's own surface, no tint at all. */
.callout-note {
  border-left-color: var(--color-border-strong);
  background-color: var(--color-surface-hover);
}

.callout-note .callout-icon {
  color: var(--color-text-faint);
}

/* ---------- Pagination / prev-next ---------- */
.article-nav {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}

.article-nav a {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  transition: border-color var(--transition-fast);
}

.article-nav a:hover {
  border-color: var(--color-accent-border);
}

.article-nav .nav-direction {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-faint);
}

.article-nav .nav-prev {
  text-align: left;
  align-items: flex-start;
}

.article-nav .nav-next {
  text-align: right;
  align-items: flex-end;
}

/* ---------- Author byline ---------- */
.byline {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.byline-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background-color: var(--color-accent-soft);
  color: var(--color-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: var(--text-sm);
  flex-shrink: 0;
}

.byline-name {
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text);
}

.byline-role {
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

/* .byline-avatar can also hold a small icon (download/user glyphs)
   instead of initials — used for sidebar metadata rows. */
.byline-avatar svg {
  width: 18px;
  height: 18px;
}

/* When the name itself is the link (e.g. an external author profile),
   rather than the div Guides use for a plain byline. */
a.byline-name {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  transition: color var(--transition-fast);
}

a.byline-name:hover {
  color: var(--color-accent);
}

a.byline-name svg {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  color: var(--color-text-faint);
}

/* ---------- Forms ---------- */
.form-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-5);
}

.form-field label {
  font-size: var(--text-sm);
  font-weight: 700;
}

.form-field input,
.form-field textarea,
.form-field select {
  padding: var(--space-3) var(--space-4);
  background-color: var(--color-input-bg);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  color: var(--color-text);
}

.form-field input:focus,
.form-field textarea:focus,
.form-field select:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
  border-color: var(--color-accent);
}

.form-field .field-hint {
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

/* ---------- Empty / placeholder state ---------- */
.placeholder-note {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-faint);
  padding: 0.3rem 0.85rem;
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-pill);
}

/* ---------- CTA banner ---------- */
.cta-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-6);
  padding: var(--space-8);
  border-radius: var(--radius-lg);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
}

.cta-banner h2 {
  font-size: var(--text-xl);
}

.cta-actions {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
}

/* ==========================================================
   Resource page template
   Reusable across /mods/*, /shaders/*, /texture-packs/*, /maps/*
   ========================================================== */

/* Resource pages read as a compact, centered workspace rather than
   spanning the full wide container other sections may use. Reusing
   --container-width (the same token the header's .container uses)
   instead of a new hardcoded value means the resource page's left/right
   edges line up with the header's content area exactly, at every
   breakpoint. Scoped to .resource-page (set on <main> by the resource
   template) so it never touches .container-wide's own max-width for
   any other page that adopts it. */
.resource-page .container-wide {
  max-width: var(--container-width);
}

/* ---------- Resource hero ----------
   Shares the page's own background (no elevated panel color) so the
   hero, the nav below it and the content below that all read as one
   continuous surface. The divider lives on the inner container (not
   the full-bleed <header>) so it's constrained to the same content
   width as the hero/nav/sidebar below it, instead of spanning the
   full viewport. It's the ONLY divider between the hero and
   everything below it — nothing else in this template should
   introduce another horizontal line. */
.resource-hero {
  padding-top: var(--space-8);
}

.resource-hero .container-wide {
  padding-bottom: var(--space-6);
  border-bottom: 1px solid var(--color-border);
}

/* HOW FAR THE DARK RUNS ON BELOW THE DOWNLOAD / AUTHOR ROW.
   Two paddings stack under the last line of hero content: the inner
   container's (above) and the hero's own, which main.css sets to
   space-12 + --ink-curve for every page type that opens the ink band.
   A resource hero is the most compact opening on the site — breadcrumb,
   name, blurb, one metadata line — so the shared figure left roughly
   115px of mostly empty black under it and the curve read as a wave in a
   field rather than an edge.
   Only the hero's OWN component is reduced here, from space-12 to
   space-4; the --ink-curve term is kept verbatim so the curve still has
   its full height to be drawn in and is never cut off. Content therefore
   clears the top of the curve by 16px and its actual line by rather more,
   since the path starts around half way down its box.
   Scoped to .resource-hero, so the homepage, category, static-page and
   guide openings keep the shared spacing untouched. The class is doubled
   to match the specificity of the rule in main.css; this sheet loads
   after it, so equal specificity is enough and no !important is needed. */
.resource-hero.resource-hero {
  padding-bottom: calc(var(--space-4) + var(--ink-curve));
}

.resource-hero-grid {
  display: grid;
  gap: var(--space-8);
}

.resource-hero-main {
  min-width: 0;
}

/* The resource page's copy of the trail. Everything about how it LOOKS is
   declared once with .breadcrumbs, near the top of this file — only its
   position in the hero belongs here. */
.resource-hero-breadcrumb {
  margin-bottom: var(--space-4);
}

/* Belt and braces: the breadcrumb is text-only by design, so anything with an
   intrinsic size that ever lands in here is a mistake — cap it at the line
   rather than letting it grow to fill the row, which is how an unsized inline
   SVG previously blew the hero open when this stylesheet was cached. */
.resource-hero-breadcrumb svg,
.resource-hero-breadcrumb img {
  width: 1em;
  height: 1em;
  vertical-align: middle;
}

/* Icon sits directly beside the resource name so the two read as one
   identity unit, instead of the name being stacked under a separate
   type badge. */
.resource-hero-title-row {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  /* Only engages when the name genuinely cannot fit beside the icon (a long
     one at 36px on a narrow phone). Then the name drops to its own full-width
     line instead of overflowing the viewport or being chopped mid-word. Every
     name that already fitted stays exactly where it was. */
  flex-wrap: wrap;
}

.resource-hero-icon {
  width: 84px;
  height: 84px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--color-border);
  background-color: var(--color-surface);
  flex-shrink: 0;
}

.resource-hero-icon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.resource-hero-title {
  font-size: var(--text-3xl);
  letter-spacing: -0.01em;
  /* As a flex child this defaults to min-width:auto, so it refused to shrink
     below its longest word and pushed a long name ("Complementary Shaders")
     past the viewport on narrow phones. The wrap on .resource-hero-title-row
     is what actually resolves that; these two are the backstop for a name so
     long it overflows even a full-width line. Both only engage in that case. */
  min-width: 0;
  overflow-wrap: break-word;
}

/* Secondary to the H1 by design: smaller than the old --text-md size
   and tighter leading. No max-width cap — .resource-hero-main is
   already its own grid column (separate from .resource-hero-side),
   so the text can't reach the Download button regardless; capping it
   further here only forced extra, unnecessary line wraps. */
.resource-hero-desc {
  margin-top: var(--space-3);
  font-size: var(--text-base);
  line-height: var(--leading-tight);
  color: var(--color-text-muted);
}

/* Downloads/Author as one compact inline metadata line under the
   description — plain small icons directly beside the text rather
   than the .byline avatar-circle component (that component reads as
   an author-credit widget, which is the wrong metaphor for a resource
   stat like a download count). A bullet separates the two items using
   the same ::before convention as .card-footer .dot. */
.resource-hero-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.resource-hero-meta-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.resource-hero-meta-item + .resource-hero-meta-item::before {
  content: "\2022";
  margin-right: var(--space-1);
  color: var(--color-text-faint);
}

.resource-hero-meta-item strong {
  color: var(--color-text);
  font-weight: 700;
}

.resource-hero-meta-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--color-text-faint);
}

.resource-hero-meta-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-text);
  font-weight: 700;
  transition: color var(--transition-fast);
}

.resource-hero-meta-link:hover {
  color: var(--color-accent);
}

.resource-hero-meta-link svg {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  color: var(--color-text-faint);
}

.resource-hero-side {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.resource-hero-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-3);
}

/* Metrics deliberately match .resource-nav-list a (same padding, same
   --text-sm label) so the Download CTA and the resource-nav tabs read
   as siblings — the coral fill alone marks this one as the primary
   action. Radius is inherited from .btn rather than overridden, so the
   button matches every other button in the system instead of carrying
   a bespoke, softer corner. padding-block is left at --space-2: it
   keeps the ~38px height that makes the CTA comfortably clickable, so
   this reads as tighter and crisper rather than merely smaller. */
.resource-hero-actions .btn {
  padding: var(--space-2) var(--space-4);
}

/* Sized to the 14px label beside it instead of overpowering it. Scoped
   here so the shared .btn-icon component keeps its default size. */
.resource-hero-actions .btn-icon {
  width: 14px;
  height: 14px;
}

.btn-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.btn-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  padding: 0.75rem 1.5rem;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: 700;
  background-color: var(--color-accent-soft);
  color: var(--color-accent);
  border: 1px solid var(--color-accent-border);
  cursor: default;
}

.btn-placeholder-note {
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-faint);
  text-align: center;
}

/* Compact label/value rows for sidebar resource metadata (type,
   edition, loader, category) — the section's own divider/padding
   already separates it from neighboring sidebar sections. */
.resource-panel-facts {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.resource-fact {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: var(--text-sm);
}

.resource-fact-label {
  color: var(--color-text-faint);
}

.resource-fact-value {
  color: var(--color-text);
  font-weight: 600;
  text-align: right;
}

.sidebar-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-accent);
  font-size: var(--text-sm);
  font-weight: 600;
  transition: color var(--transition-fast);
}

.sidebar-link:hover {
  color: var(--color-accent-hover);
}

.sidebar-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* A variable-length list of external/internal resource links — reuses
   .sidebar-link per item. The whole section is omitted in markup when
   a resource has no real links, rather than rendering empty rows. */
.sidebar-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Sizes both the leading type icon and the trailing external-link icon
   on a .sidebar-link row (e.g. Official Website, Wiki, Discord). */
.sidebar-link svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

/* Anchor targets (including #download) need clearance so the sticky
   header + sticky resource-nav don't cover the heading on jump/scroll. */
.resource-main h2[id] {
  scroll-margin-top: 150px;
}

/* ---------- Resource section navigation ----------
   A compact segmented control (a pill track holding pill tabs) rather
   than a full-width row of browser-style tabs. The active tab gets a
   solid accent fill, matching the primary button treatment. Its
   background matches the page background exactly (not the header's
   elevated tone), so at rest it's indistinguishable from the content
   above and below it — only the pill track itself has visual weight.
   It stays legible while sticky/pinned because that shared background
   is opaque, not because of a translucent overlay. */
.resource-nav {
  position: sticky;
  top: 72px;
  z-index: 50;
  background-color: var(--color-bg);
  padding-block: var(--space-3);
  margin-bottom: var(--space-8);
}

/* .resource-nav now lives inside .resource-main (the grid's left
   column) so its top edge starts the same grid row as .resource-sidebar
   — the two line up automatically instead of via a hardcoded offset.
   Only a small gap is needed above it here, since the hero already
   ends in its own divider; the generous gap before the first heading
   comes from .resource-nav's margin-bottom above instead. */
.resource-content-section {
  padding-top: var(--space-4);
}

.resource-nav-list {
  display: inline-flex;
  max-width: 100%;
  gap: 2px;
  padding: 4px;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  overflow-x: auto;
  scrollbar-width: none;
}

.resource-nav-list::-webkit-scrollbar {
  display: none;
}

.resource-nav-list a {
  display: inline-flex;
  align-items: center;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-muted);
  white-space: nowrap;
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

.resource-nav-list a:hover {
  color: var(--color-text);
  background-color: var(--color-surface-hover);
}

.resource-nav-list a.is-active {
  color: var(--color-on-accent);
  background-color: var(--color-accent);
  font-weight: 700;
}

/* ---------- Resource content layout ---------- */
.resource-layout {
  display: grid;
  gap: var(--space-8);
}

.resource-main {
  min-width: 0;
}

/* .prose defaults to a 72ch reading measure for long-form articles, but on
   the resource page it wraps structured components (gallery, feature list,
   compatibility rows) as well as body paragraphs — all of it should use the
   full resource-main column width instead of being capped independently,
   so text runs up to the existing grid gap rather than leaving empty space
   before the sidebar. */
.resource-main .prose {
  max-width: none;
}

/* ---------- Resource section panels ----------
   Each nav tab maps to one panel. Every panel ships visible in the
   HTML — JS hides the inactive ones at runtime — so the full document
   stays crawlable and the page still reads as a normal anchor-linked
   article with scripts disabled. */
.resource-panel[hidden] {
  display: none;
}

.resource-panel[id] {
  scroll-margin-top: 150px;
}

/* Every panel opens with its own <h2>. .prose gives headings a large
   top margin for separation mid-article, which as a panel's first child
   would just leave a dead gap under the nav on every section switch. */
.resource-panel > h2:first-child {
  margin-top: 0;
}

/* ---------- Version browser ----------
   A version table, not a stack of cards. Delimited by hairlines rather
   than wrapped in a rounded container, so it reads as part of the page
   instead of a widget dropped onto it. The Minecraft version is the row's
   anchor — set in the mono face, since a version string is data, not
   prose — and every field that would repeat identically on each row
   (resource name, edition) is deliberately absent: a column with one
   value everywhere is noise, not information.

   The toolbar's filter controls are injected by JS from the data-*
   attributes on the rows, so this has to hold for any number of filter
   groups, and for two builds as readily as two hundred. */
/* Filter panel: one bordered surface holding a labelled control per filter
   dimension. On a resource page each dimension is a dropdown (.build-filter
   below) — the value sets are open-ended, and a resource carrying a dozen
   game versions turned a chip row into a wall of buttons. The chip variant
   is still what the category listings use, where there is one short, fixed
   set of options and keeping them all visible is the point. */
.version-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-5) var(--space-8);
  padding: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
}

.version-filter-groups {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5) var(--space-8);
  flex: 1 1 auto;
}

.version-filter-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}


.version-filter-label {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-faint);
}

.version-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.version-chip {
  display: inline-flex;
  align-items: center;
  padding: var(--space-2) var(--space-4);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  background-color: var(--color-bg);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast);
}

.version-chip:hover {
  background-color: var(--color-surface-hover);
  border-color: var(--color-text-faint);
}

.version-chip[aria-pressed="true"] {
  color: var(--color-accent);
  background-color: var(--color-accent-soft);
  border-color: var(--color-accent-border);
}

.version-chip[hidden] {
  display: none;
}

/* Overflow control — keeps a resource with dozens of game versions from
   turning the panel into a wall of chips. */
.version-chip-more {
  color: var(--color-text-faint);
  border-style: dashed;
}

/* ----- Build filter dropdowns -----
   The resource build browser's filters. Every dimension gets the same
   control, so however many the page declares they read as one set: a label,
   a trigger showing the current value, and a popover of the values actually
   present in the build rows. */
.build-filters .build-filter-fields {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-4);
  max-width: 34rem;
}

/* Fields share the row and wrap onto their own lines when it runs out.
   min-width: 0 is what keeps that promise: without it a field's automatic
   minimum is its content, and two of them side by side push the panel —
   and with it the whole page — wider than a phone screen. */
.build-filter {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  flex: 1 1 11rem;
  min-width: 0;
  max-width: 18rem;
}

.build-filter-shell {
  position: relative;
  min-width: 0;
}

.build-filter-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  text-align: left;
  color: var(--color-text);
  background-color: var(--color-input-bg);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background-color var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast);
}

.build-filter-trigger:hover {
  border-color: var(--color-text-faint);
}

.build-filter-trigger:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

/* A narrowed filter is the state worth seeing from across the panel, so the
   trigger takes the accent the active chip used to carry. */
.build-filter-shell.is-set .build-filter-trigger {
  color: var(--color-accent);
  background-color: var(--color-accent-soft);
  border-color: var(--color-accent-border);
}

/* A long loader string ("Fabric, NeoForge & Quilt") truncates rather than
   widening the control — the full value is one click away in the menu. */
.build-filter-value {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.build-filter-caret {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: var(--color-text-faint);
  transition: transform var(--transition-fast);
}

.build-filter-shell.is-set .build-filter-caret {
  color: inherit;
}

.build-filter-trigger[aria-expanded="true"] .build-filter-caret {
  transform: rotate(180deg);
}

.build-filter-menu {
  position: absolute;
  z-index: 20;
  top: calc(100% + 6px);
  left: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 100%;
  max-width: min(20rem, calc(100vw - var(--space-12)));
  max-height: 15rem;
  overflow-y: auto;
  padding: var(--space-2);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
}

.build-filter-menu[hidden] {
  display: none;
}

/* Flipped by JS when the popover would otherwise reach past the viewport. */
.build-filter-shell.is-flipped .build-filter-menu {
  left: auto;
  right: 0;
}

.build-filter-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  text-align: left;
  color: var(--color-text);
  background: none;
  border: none;
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.build-filter-option:hover {
  background-color: var(--color-surface-hover);
}

.build-filter-option:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

.build-filter-option[aria-checked="true"] {
  color: var(--color-accent);
  background-color: var(--color-accent-soft);
}

.build-filter-option-text {
  min-width: 0;
  overflow-wrap: anywhere;
}

.build-filter-check {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  opacity: 0;
}

.build-filter-option[aria-checked="true"] .build-filter-check {
  opacity: 1;
}

/* The sort control is the same dropdown as the filters above it, sitting
   inline beside its label rather than under one. Its width is held steady so
   the toolbar does not shuffle when a longer order is chosen — and capped, so
   a 320px screen still fits the label beside it. */
.build-sort {
  /* A preferred width that still gives way: the trigger is the last thing on
     the toolbar's line, and on a narrow screen it has to shrink into what is
     left — or wrap onto its own line — rather than push the page sideways.
     The trigger keeps a floor so the order stays readable, and the popover a
     wider one so an option never wraps onto two lines. */
  flex: 0 1 11.5rem;
  min-width: 0;
}

.build-sort .build-filter-trigger {
  min-width: 9.5rem;
}

.build-sort .build-filter-menu {
  min-width: 12rem;
}

/* Scripting replaces the native control with the dropdown; without it the
   select is what a visitor gets, still styled by .version-sort select. */
.version-sort select[hidden] {
  display: none;
}

.version-reset {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
  padding: 0;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-faint);
  background: none;
  border: none;
  cursor: pointer;
  transition: color var(--transition-fast);
}

.version-reset svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

.version-reset:hover:not(:disabled) {
  color: var(--color-accent);
}

.version-reset:disabled {
  opacity: 0.45;
  cursor: default;
}

/* Results bar — sits between the filter surface and the results card,
   carrying the live count and the sort control. */
.version-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-6);
  margin-bottom: var(--space-4);
}

.version-count {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-muted);
}

.version-sort {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* So the control inside can shrink with it instead of overflowing it. */
  min-width: 0;
}

.version-sort label {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-faint);
  /* Two words, one line: the control beside it gives way first. */
  white-space: nowrap;
}

.version-sort select {
  padding: var(--space-1) var(--space-3);
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--color-text);
  background-color: var(--color-input-bg);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-xs);
}

.version-sort select:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

/* ---------- Build table ----------
   Mobile-first: rows stack into labelled blocks and the column headers are
   suppressed. The grid that aligns headers to rows is layered on at the
   tablet breakpoint in responsive.css, driven by --build-cols so the header
   and every row stay in step from one declaration. */
.build-head {
  display: none;
}

/* The results card. Column headers deliberately sit outside it, on the page
   background, so the card holds only data — that separation is what stops
   the header reading as just another row. */
.build-rows {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  overflow: hidden;
}

.build-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  transition: background-color var(--transition-fast);
}

.build-row:hover {
  background-color: var(--color-surface-hover);
}

.build-row[hidden] {
  display: none;
}

/* Divider between *visible* rows only — a :first-child or adjacent-sibling
   rule would strand a border on whichever row a filter leaves showing first. */
.build-row:not([hidden]) ~ .build-row:not([hidden]) {
  border-top: 1px solid var(--color-border);
}

.build-cell {
  display: block;
  min-width: 0;
}

/* Release-status dot. Its own cell in the column grid rather than an
   ornament inside the version cell, so the version string starts on the
   column's own edge and lines up with the "Version" header above it. It
   says exactly what the "Latest" tag says, so the stacked mobile row —
   where there is no column to sit in — drops it rather than spending a
   line on it. */
.build-cell-status {
  display: none;
}

/* The lead cell carries the build's identity and is the row's anchor. */
.build-version {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.build-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background-color: var(--color-border-strong);
}

.build-dot-latest {
  background-color: var(--color-success);
}

/* Version strings and loader lists are data, not prose: they wrap inside
   their column instead of forcing it wider, so one long value can never
   drag the shared column structure out of shape. */
.build-value-lead {
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--color-text);
  overflow-wrap: anywhere;
}

.build-value {
  display: block;
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text);
  overflow-wrap: anywhere;
}

.build-meta {
  display: block;
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

/* Release-channel chips. Smaller and quieter than .badge — they qualify
   the version rather than competing with it. */
.build-tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.build-tag {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--space-2);
  border-radius: var(--radius-xs);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-muted);
  background-color: var(--color-surface-hover);
}

.build-tag-latest {
  color: var(--color-success);
  background-color: var(--color-success-soft);
}

.build-platform {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

.build-platform-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--color-text-faint);
}

.build-cell-action {
  display: flex;
}

/* Inert until a verified URL exists: styled as the primary action it will
   become, but rendered as a <span aria-disabled> rather than a link, so it
   can't be clicked or focused while it leads nowhere. Swap to <a href> and
   drop aria-disabled once the link is confirmed. */
.build-download {
  width: 100%;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-sm);
  cursor: default;
}

.build-download[aria-disabled="true"] {
  opacity: 0.9;
}

.build-download:active {
  transform: none;
}

.version-empty {
  padding: var(--space-10) var(--space-5);
  font-size: var(--text-sm);
  color: var(--color-text-faint);
  text-align: center;
}

.version-empty[hidden] {
  display: none;
}

/* The build list's pager reuses .category-pagination / .category-page-btn
   from the listing section below — one control, one set of rules. It replaced
   the .build-note callout that used to sit under the table; that element and
   its rules are gone. */

/* Separate blocks (Resource Info / Resource Stats / Requirements /
   Details), not one continuous panel — each .resource-sidebar-panel
   below is its own card, spaced apart rather than internally divided. */
.resource-sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}

.resource-sidebar-panel {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  overflow: hidden;
}

.resource-sidebar-section {
  padding: var(--space-5) var(--space-6);
  border-bottom: 1px solid var(--color-border);
}

.resource-sidebar-section:last-child {
  border-bottom: none;
}

.resource-sidebar-section h3 {
  font-size: var(--text-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-faint);
  margin-bottom: var(--space-3);
}

/* A short, honest "we don't have this data yet" line — used wherever a
   sidebar block has no real value to show (e.g. Details' publish/update
   dates) rather than inventing one or leaving the block empty. */
.trending-empty {
  font-size: var(--text-sm);
  color: var(--color-text-faint);
}

/* ---------- Feature grid (compact, not card-heavy) ----------
   A divided grid — borders form the cell lines directly, so there's
   no gap-plus-border double-treatment, and it reads as one connected
   surface rather than four separate cards. */
.feature-list {
  display: grid;
  grid-template-columns: 1fr;
  border-top: 1px solid var(--color-border);
  border-left: 1px solid var(--color-border);
}

.feature-item {
  display: flex;
  gap: var(--space-4);
  padding: var(--space-5);
  border-bottom: 1px solid var(--color-border);
  border-right: 1px solid var(--color-border);
  min-width: 0;
}

.feature-item > div {
  min-width: 0;
  flex: 1;
}

.feature-item-icon {
  width: 36px;
  height: 36px;
  border-radius: var(--radius-xs);
  background-color: var(--color-accent-soft);
  color: var(--color-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.feature-item-icon svg {
  width: 18px;
  height: 18px;
}

.feature-item h3 {
  font-size: var(--text-sm);
  margin-bottom: 2px;
}

.feature-item p {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin: 0;
}

/* ---------- Compatibility list ---------- */
.compat-list {
  display: flex;
  flex-direction: column;
}

.compat-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-sm);
}

.compat-row:last-child {
  border-bottom: none;
}

.compat-label {
  color: var(--color-text);
  font-weight: 600;
}

.compat-status {
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 3px 12px;
  border-radius: var(--radius-pill);
  flex-shrink: 0;
}

.compat-status-yes {
  background-color: var(--color-success-soft);
  color: var(--color-success);
}

.compat-status-no {
  background-color: var(--color-surface-active);
  color: var(--color-text-faint);
}

/* ---------- Two-column content block (e.g. installation steps + note) ---------- */
.resource-two-col {
  display: grid;
  gap: var(--space-8);
}

/* ---------- Gallery ---------- */
.gallery-main {
  border-radius: var(--radius-lg);
  overflow: hidden;
  background-color: var(--color-bg-elevated);
  box-shadow: var(--shadow-md);
}

.gallery-main img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  display: block;
}

.gallery-thumbs {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-4);
  overflow-x: auto;
}

.gallery-thumb {
  flex-shrink: 0;
  width: 128px;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: 2px solid transparent;
  padding: 0;
  cursor: pointer;
  opacity: 0.55;
  transition: opacity var(--transition-fast), border-color var(--transition-fast), transform var(--transition-fast);
}

.gallery-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.gallery-thumb:hover {
  opacity: 0.85;
  transform: translateY(-2px);
}

.gallery-thumb.is-active {
  opacity: 1;
  border-color: var(--color-accent);
}

.gallery-caption {
  margin-top: var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

/* ---------- Accordion (native details/summary — no JS required) ----------
   A divided list rather than stacked boxes: each item is a plain row
   separated by a hairline, with a subtle hover highlight on the row
   itself rather than a permanent card background. */
.accordion-group {
  border-top: 1px solid var(--color-border);
}

.accordion-item {
  border-bottom: 1px solid var(--color-border);
}

.accordion-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-inline: calc(var(--space-2) * -1);
  padding: var(--space-5) var(--space-2);
  border-radius: var(--radius-xs);
  cursor: pointer;
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--color-text);
  list-style: none;
  transition: background-color var(--transition-fast);
}

.accordion-item summary:hover {
  background-color: var(--color-surface-hover);
}

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

.accordion-item summary::after {
  content: "+";
  font-size: var(--text-lg);
  line-height: 1;
  color: var(--color-accent);
  flex-shrink: 0;
  transition: transform var(--transition-fast);
}

.accordion-item[open] summary::after {
  transform: rotate(45deg);
}

.accordion-item .accordion-body {
  padding: 0 var(--space-2) var(--space-5);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}


/* ===========================================================================
   Homepage
   ---------------------------------------------------------------------------
   The homepage is the one page that shows the whole library at once, so it has
   its own components rather than reusing the category-page grid: shorter
   cards, a compact list, a side panel. Everything inside a BRAVITH-CMS-HOME-*
   region is generated from the database (app/indexes.php) — these rules style
   that output, they never hardcode any of it.

   Mobile-first, as everywhere else. Breakpoint overrides live in
   responsive.css so all of the site's breakpoints stay in one file.
=========================================================================== */

.home-accent {
  color: var(--color-success);
}

/* ---------- Category artwork ----------
   The six official 256x256 category images. `contain` rather than `cover`, so
   the artwork can never be stretched or cropped — the tiles are square and so
   are the files, so today it fills them exactly, and a differently-shaped
   asset would letterbox rather than distort.

   Every tile that holds artwork carries a `-art` modifier and every tile that
   holds the fallback glyph carries `-glyph`, because the two need opposite
   framing: the artwork brings its own rounded, tinted background, so nothing
   is drawn behind it; the glyph is a bare stroke and needs the chip. */
.category-mark {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* The artwork's own background IS the chip. Anything painted behind it would
   show as a second square peeking out from under its rounded corners, and a
   container radius smaller than the artwork's would clip its corners off.
   The tag is in the selector on purpose: these override base tile rules that
   sit further down this file, and equal specificity would let source order
   decide. The tiles are always spans — see bravith_render_category_tile(). */
span.home-category-icon-art,
span.home-latest-media-art,
span.home-explore-media-art,
span.home-library-icon-art,
span.category-rank-index-art,
span.category-art-mark-art {
  background-color: transparent;
  border-color: transparent;
  box-shadow: none;
  overflow: visible;
}

/* The tile that holds the fallback glyph instead of artwork — a category added
   before its icon has been drawn. The glyph is a bare stroke where the artwork
   brings its own background, so it needs the tinted chip. */
.category-art-mark-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  background-color: var(--tint-soft, var(--color-accent-soft));
  color: var(--tint, var(--color-accent));
}

/* The hero plate already paints the tinted field, so its glyph fallback needs
   the stroke and nothing behind it. */
.category-art .category-art-mark-glyph {
  background-color: transparent;
}

.category-art-mark-glyph svg { width: 68px; height: 68px; }

/* ---------- Hero ---------- */
.home-hero {
  padding-block: var(--space-10) var(--space-12);
  border-bottom: 1px solid var(--color-border);
}

/* The homepage sits its curve a little closer to the hero than the other page
   openings do. main.css reserves `--space-12 + --ink-curve` below every hero so
   the shape never draws over content; with the feature row gone there is less
   to clear here, and the extra room left the dark band running on well past the
   artwork. Trimming one step off the clearance lifts the whole transition
   toward the image and the buttons without touching the shape, its colour or
   its height. The doubled class is the same specificity trick main.css uses —
   see the note above its own rule — and only .home-hero is named, so every
   other hero keeps the shared spacing. */
.home-hero.home-hero {
  padding-bottom: calc(var(--space-10) + var(--ink-curve));
}

.home-hero-layout {
  display: grid;
  gap: var(--space-10);
  align-items: center;
}

.home-hero-content {
  display: grid;
  gap: var(--space-5);
  align-content: start;
}

.home-hero h1 {
  font-size: var(--text-3xl);
  letter-spacing: -0.02em;
}

.home-hero .lede {
  max-width: 48ch;
}

.home-hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* The two hero CTAs are text only. .btn keeps its `gap` for buttons elsewhere
   that do carry a glyph; with a single child here it collapses to nothing. */

/* ---------- Hero visual ---------- */
/* The artwork is the whole of this column now — no search pill sits on it, so
   the wrapper needs no positioning context of its own. */

/* WHY THE RATIO IS NOT THE FILE'S RATIO. The source is 1200x900 (4/3) and the
   box is 13/8, so the artwork is centred and cropped a little off the top and
   bottom by object-fit: cover — never squashed, and the composition through the
   middle is untouched. Declaring aspect-ratio as well as width/height still
   reserves the slot before the file loads, so nothing below the hero reflows.
   Changing this ratio is how the hero's height is tuned: the column width comes
   from the grid in responsive.css, and the height follows from it. */
.home-hero-image {
  width: 100%;
  aspect-ratio: 13 / 8;
  object-fit: cover;
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-md);
}

/* ---------- Section shell ---------- */
/* No rule between sections: the padding is what separates them. A hairline
   across the full container at every join turned the page into a stack of
   filed slips, and each section already announces itself with its own head. */
.home-section {
  padding-block: var(--space-16);
}

/* ----- The closing run: Latest updates → Why Bravith → the library panel -----
   These three read as one closing sequence, but each join was carrying two
   full 4rem paddings — 8rem of empty page between blocks that belong together,
   and the two largest gaps on the homepage by a wide margin. Both joins are
   halved to 2rem a side.

   Deliberately scoped to these two joins and nothing else: .home-section keeps
   its 4rem everywhere above, so the rhythm down to Latest updates is untouched
   and the closing run simply reads tighter than the browsing sections. Applies
   at every width — there is no breakpoint override of .home-section — which is
   also where the old spacing hurt most, since 8rem between stacked mobile
   sections was most of a phone screen of nothing. */
.home-section-run-start {
  padding-bottom: var(--space-8);
}

/* ----- The category row closes tighter into what follows -----
   Measured, because the two are not the same thing: the PADDING at this join
   is identical to every other browsing join (4rem a side, 128px, at every
   width). What is not identical is what sits above it. The category cards are
   white on white, bounded by a hairline and ending on a small arrow glyph with
   the card's own bottom padding under it, so the section's trailing space and
   the cards' interior read as one continuous field — measured from the last
   line of card text to the next heading it is about 204px, against 153px at
   the featured → latest join below, where the cards carry artwork and a dense
   meta row and stop the eye at their own edge.

   So the correction belongs at this join and nowhere else: 2rem here against
   the following section's 4rem brings the band to 96px, which reads level with
   the 128px joins under the heavier blocks. Same token, same mechanism and the
   same reasoning as the closing run above — one named join, not a change to
   .home-section, which keeps its 4rem everywhere else on the page. */
.home-section-tight-end {
  padding-bottom: var(--space-8);
}

.home-section-band {
  padding-block: var(--space-8);
}

/* The section after the band gives up its facing padding too. A sibling
   selector rather than a third class, so the closing panel does not have to
   know it is in the run. */
.home-section-band + .home-section {
  padding-top: var(--space-8);
}

.home-section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}

.home-section-head h2 {
  font-size: var(--text-2xl);
  letter-spacing: -0.01em;
}

/* The optional action on the right of a section heading. A pill rather than a
   .btn: it sits on the baseline of a 1.875rem heading, and a full button there
   competes with the heading instead of following it.

   It still has to look finished, though — as a bare hairline outline with
   muted text it read as an unstyled link that had been left behind. It now
   carries the same surface, border and card shadow every other raised thing on
   this page uses, the label is full-strength text with the arrow muted behind
   it, and the arrow sits in its own tinted disc so the control has a focal
   point at the size it actually renders. No new colours: every value below is
   an existing token. */
.home-section-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 5px 5px 5px var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background-color: var(--color-surface);
  box-shadow: var(--shadow-card);
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text);
  white-space: nowrap;
  transition: background-color var(--transition-fast), border-color var(--transition-fast),
    box-shadow var(--transition-fast);
}

.home-section-link:hover {
  background-color: var(--color-surface-hover);
  border-color: var(--color-border-strong);
}

/* The disc is what stops the pill reading as plain text with a stray arrow. */
.home-section-link-arrow {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background-color: var(--color-accent-soft);
  color: var(--color-accent);
  transition: transform var(--transition-fast);
}

.home-section-link:hover .home-section-link-arrow {
  transform: translateX(2px);
}

.home-section-link-arrow svg {
  flex-shrink: 0;
}

/* ---------- Explore by category ---------- */
.home-category-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}

/* A fixed row of parts — icon, name, count, description, arrow — laid out on
   grid rows rather than as a free-flowing stack, so every card in the row puts
   each part at the same height. The description track is the only flexible one,
   which is what keeps the arrows on a single line across the whole row however
   long the individual blurbs are. */
.home-category-card {
  display: grid;
  grid-template-rows: auto auto auto 1fr auto;
  gap: var(--space-1);
  padding: var(--space-5);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--transition-fast), transform var(--transition-fast),
    box-shadow var(--transition-fast);
}

.home-category-card:hover {
  border-color: var(--tint, var(--color-border-strong));
  transform: translateY(-2px);
  box-shadow: var(--shadow-card);
}

/* One container size, one radius, one stroke weight for every category — the
   glyphs differ, the frame never does. */
.home-category-icon {
  width: 44px;
  height: 44px;
  margin-bottom: var(--space-3);
  border-radius: var(--radius-sm);
  background-color: var(--tint-soft, var(--color-accent-soft));
  color: var(--tint, var(--color-accent));
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.home-category-icon svg {
  width: 21px;
  height: 21px;
}

.home-category-name {
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--color-text);
}

.home-category-count {
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

/* Clamped rather than truncated in PHP: the full first sentence stays in the
   markup for crawlers, and the card stays the same height either way. */
.home-category-desc {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--color-text-muted);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.home-category-arrow {
  margin-top: var(--space-4);
  color: var(--tint, var(--color-accent));
  display: inline-flex;
  align-self: start;
}

.home-category-arrow svg {
  width: 18px;
  height: 18px;
}

/* ---------- Featured resources ---------- */
.home-featured-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
}

.home-featured-card .card-body {
  gap: var(--space-3);
  padding: var(--space-4);
}

.home-featured-card .card-title {
  font-size: var(--text-base);
}

.home-card-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.home-card-tags .badge {
  padding: 0.2rem 0.6rem;
  font-size: 0.6875rem;
}

.home-clamp-3 {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.home-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-top: auto;
  padding-top: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

.home-meta-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.home-meta-item svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

/* ---------- Latest updates + side panel ---------- */
.home-split {
  display: grid;
  gap: var(--space-10);
  align-items: start;
}

.home-split-main {
  min-width: 0;
}

/* The sidebar column is two stacked cards rather than one, so it needs its own
   wrapper: making the community card a third child of .home-split would drop
   it into the main column's track on the second row once the grid splits. */
.home-split-side {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.home-latest-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.home-latest-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2) var(--space-4);
  align-items: center;
  padding: var(--space-4);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--transition-fast), background-color var(--transition-fast);
}

.home-latest-row:hover {
  border-color: var(--color-border-strong);
  background-color: var(--color-surface-hover);
}

/* Two states, one footprint. With the resource's own artwork it is a cropped
   thumbnail; without it, a tinted tile carrying the category glyph — see
   bravith_home_row_media(). Both are 48px with the same radius, so the column
   of tiles down the list stays a straight edge either way. */
.home-latest-media {
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-bg-elevated);
}

.home-latest-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.home-latest-media-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-color: transparent;
  background-color: var(--tint-soft, var(--color-accent-soft));
  color: var(--tint, var(--color-accent));
}

.home-latest-media-glyph svg {
  width: 23px;
  height: 23px;
}

.home-latest-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.home-latest-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.home-latest-head .badge {
  padding: 0.2rem 0.6rem;
  font-size: 0.6875rem;
}

.home-latest-name {
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--color-text);
}

.home-latest-desc {
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--color-text-muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Below the body on a phone; a third column from the tablet breakpoint up. */
.home-latest-meta {
  grid-column: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

/* A figure, not a caption: the one number in the row gets a chip so it reads
   as a value rather than as more of the description. */
.home-meta-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px var(--space-2);
  border-radius: var(--radius-pill);
  background-color: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  font-weight: 600;
  color: var(--color-text-muted);
  white-space: nowrap;
}

.home-meta-chip svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}

.home-panel {
  padding: var(--space-5);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
}

.home-panel-heading {
  font-size: var(--text-md);
}

.home-panel-note {
  margin-top: 2px;
  margin-bottom: var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

/* Pulled out to the card's edges so the hover fill reads as a full-width row
   rather than as a floating rectangle inside the padding. */
.home-explore-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  margin-inline: calc(var(--space-3) * -1);
  border-radius: var(--radius-xs);
  transition: background-color var(--transition-fast);
}

.home-explore-row:hover {
  background-color: var(--color-surface-hover);
}

.home-explore-media {
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  background-color: var(--color-bg-elevated);
}

.home-explore-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.home-explore-media-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-color: transparent;
  background-color: var(--tint-soft, var(--color-accent-soft));
  color: var(--tint, var(--color-accent));
}

.home-explore-media-glyph svg {
  width: 19px;
  height: 19px;
}

.home-explore-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.home-explore-name {
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.home-explore-meta {
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

.home-panel-cta {
  margin-top: var(--space-4);
}

/* ---------- Stay in the loop ----------
   A secondary CTA under the Explore panel, so it is deliberately quieter than
   the panel above it: the same card shell and no artwork. It must not read as
   a second section.

   The wash is Discord's own blurple at the same 10% the site's soft tints
   use, so the card says where its button goes before the button is read. It
   is declared here, on the card, and not in the palette: it is that brand's
   colour, not one of Bravith's, and nothing else on the site may pick it up.
   It replaced the green wash so this card and the "Why Bravith" band no
   longer read as two of the same thing. */
.home-community {
  --discord: #5865f2;
  --discord-soft: rgba(88, 101, 242, 0.1);
  padding: var(--space-5);
  background-color: var(--discord-soft);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

/* Glyph and heading on one line, so the copy below gets the card's full width
   instead of a text column squeezed beside a left rail. */
.home-community-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.home-community-icon {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-xs);
  border: 1px solid var(--color-border);
  background-color: var(--color-bg);
  color: var(--discord);
}

.home-community-icon img {
  width: 18px;
  height: 18px;
  object-fit: contain;
}

.home-community-title {
  min-width: 0;
  font-size: var(--text-base);
}

.home-community p {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.home-community-cta {
  margin-top: var(--space-4);
}

/* ---------- Why Bravith ----------
   A band, not a panel. This was a two-column block — four stacked points in a
   1.5fr track beside a 520px illustration — which made the shortest section on
   the page one of the tallest. It is now one column of four short points that
   go four-across on a desktop, which is the same information in about a third
   of the height.

   The wash is the palette's warm secondary tint rather than the green it
   shared with the Discord card above it. Two green cards in a row read as one
   thing continued; amber sits opposite the Discord card's blue, so the band
   is plainly its own section, and at this dilution it reads as the warm
   paper the rest of the palette is built on rather than as a warning. */
.home-why {
  padding: var(--space-6);
  background-color: var(--color-secondary-soft);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}

.home-why h2 {
  font-size: var(--text-xl);
}

.home-why-list {
  display: grid;
  gap: var(--space-4);
  margin-top: var(--space-5);
}

.home-why-list li {
  display: flex;
  gap: var(--space-3);
}

/* Grid children need this explicitly: the default min-width:auto lets a long
   word push the track wider than its share and shove the page sideways. */
.home-why-list li > div {
  min-width: 0;
}

.home-why-bullet {
  flex-shrink: 0;
  font-size: var(--text-base);
  line-height: 1;
  margin-top: 3px;
  color: var(--color-secondary);
}

.home-why-list h3 {
  font-size: var(--text-sm);
}

.home-why-list p {
  margin-top: 2px;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* ---------- Closing call to action ----------
   Always dark, so it reads as a deliberate closing panel rather than as the
   page background. */
/* The last section on the page, so it gets a little more room beneath it than
   the rhythm between two sections. */
.home-cta-section {
  padding-bottom: var(--space-20);
}

.home-cta-copy {
  min-width: 0;
}

.home-cta {
  display: grid;
  gap: var(--space-6);
  align-items: center;
  padding: var(--space-8);
  border-radius: var(--radius-lg);
  background-color: #151312;
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: #f3efec;
}

.home-cta h2 {
  font-size: var(--text-2xl);
  color: #ffffff;
}

.home-cta .home-accent {
  color: #3fcb7c;
}

.home-cta p {
  margin-top: var(--space-3);
  max-width: 54ch;
  font-size: var(--text-sm);
  color: #b2aba3;
}

/* A grid rather than a wrapping flex row, so the shortcuts line up in columns
   instead of ending in a ragged last row. An odd final one spans the full
   width, which keeps any number of them looking deliberate.

   These were five text-only buttons ("All Mods", "All Shaders", …). They now
   carry the same category artwork the grid at the top of the page uses, and
   Guides — the sixth category, and the only one the closing panel used to
   leave out — is among them. */
.home-library-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}

.home-library-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background-color: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #f3efec;
  transition: background-color var(--transition-fast), border-color var(--transition-fast);
}

.home-library-link:hover {
  background-color: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.32);
}

/* The artwork brings its own rounded background, so nothing is painted behind
   it; the drawn glyph is a bare stroke and takes the category tint, exactly as
   it does in the category grid. */
.home-library-icon {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.home-library-icon img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.home-library-icon-glyph {
  border-radius: var(--radius-xs);
  background-color: var(--tint-soft, rgba(255, 255, 255, 0.12));
  color: var(--tint, #f3efec);
}

.home-library-icon-glyph svg {
  width: 16px;
  height: 16px;
}

.home-library-name {
  min-width: 0;
  font-size: var(--text-sm);
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ===========================================================================
   CATEGORY PAGES

   One template for six pages: /mods/, /shaders/, /texture-packs/, /maps/,
   /modpacks/ and /guides/. Nothing below names a category: the generator
   writes --tint and --tint-soft onto the identity panel and onto the listing
   wrapper, and every coloured thing here reads those two properties — which
   is why a category added tomorrow is styled without a line changing in this
   file.

   The colour is used as an accent and nothing more: hairline borders, a chip
   behind an icon, an active state, a hover. Surfaces stay white; type stays
   the text colour. That restraint is the
   design — a resource directory, not a coloured dashboard.

   Mobile-first, as everywhere else. Breakpoint overrides are in
   responsive.css so all of the site's breakpoints stay in one file.
=========================================================================== */

/* ---------- Hero ----------
   Three pieces on an areas grid: the page's own copy, the generated
   statistics strip and the artwork plate. Areas rather than source order,
   because the strip is written by the generator NEXT TO the plate (one
   marker, one region) but belongs UNDER the copy — see
   bravith_render_category_panel(). One template change moves it; no page is
   edited. */
.category-hero {
  padding-block: var(--space-10) var(--space-12);
  border-bottom: 1px solid var(--color-border);
}

.category-hero-layout {
  display: grid;
  gap: var(--space-6);
  align-items: start;
  grid-template-areas:
    "art"
    "copy"
    "stats";
}

.category-hero-copy {
  grid-area: copy;
  min-width: 0;
}

/* The page title carries the hero. Tighter tracking and leading than the base
   heading rule, because at this size the default spacing reads as loose. */
.category-hero-copy h1 {
  margin-top: var(--space-3);
  font-size: var(--text-3xl);
  line-height: 1.08;
  letter-spacing: -0.02em;
}

.category-hero-copy .lede {
  margin-top: var(--space-4);
}

/* ---------- Artwork plate ----------
   Where a landscape hero image goes when Bravith has one — see
   bravith_category_hero_art(). Until then it frames the category's own
   official mark, at the square aspect ratio that artwork was drawn for. Both
   states are this one box, so registering a real image later changes no
   layout.

   The field is NEUTRAL, not tinted, and that is deliberate: every one of the
   six marks is drawn on its own pale rounded tile in its own category colour,
   so a tinted plate behind one puts the same wash twice and the mark stops
   being the subject. The colour in this hero comes from the mark itself and
   from the tinted chips in the strip below it.

   Below the desktop split there IS no plate. A frame 700px wide holding one
   140px mark is empty space with a border around it, so the mark goes inline
   above the title instead, the way an avatar does, and the hero's whole width
   goes to the words. responsive.css builds the plate at 1024. */
.category-art {
  grid-area: art;
  display: grid;
  justify-items: start;
}

.category-art-mark {
  display: block;
  width: 64px;
  height: 64px;
}

/* The photograph state, if a category is ever given one: the image IS the
   plate, edge to edge, and it is worth the full width at every size — so it
   keeps the frame the mark state gives up. aspect-ratio holds the 16:9 box
   open before the file loads and `cover` frames a differently-shaped asset
   rather than stretching it. */
.category-art-image {
  justify-items: stretch;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-elevated);
  overflow: hidden;
}

.category-art-image img {
  width: 100%;
  height: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* ---------- Statistics strip ----------
   One bordered unit divided into cells, not three loose figures: the numbers
   are a single statement about the category and they read as one. Stacked
   with horizontal rules on a phone, a row with vertical ones from 600px up
   (responsive.css). */
.category-stats {
  grid-area: stats;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  margin: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  overflow: hidden;
}

.category-stat {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
}

.category-stat + .category-stat {
  border-top: 1px solid var(--color-border);
}

.category-stat-icon {
  grid-row: 1 / 3;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-sm);
  background-color: var(--tint-soft, var(--color-accent-soft));
  color: var(--tint, var(--color-accent));
}

.category-stat-icon svg {
  width: 20px;
  height: 20px;
}

/* dd before dt in the source, because the number is the headline and the
   label explains it — the grid rows put them back in reading order. */
.category-stat-value {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--color-text);
}

.category-stat-label {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-faint);
}

/* ---------- Two-column body ---------- */
.category-body {
  padding-block: var(--space-10) var(--space-16);
}

/* ----- The gap between the listing and the guides block, on /mods/ -----
   A category page ends with the listing's own bottom padding and then opens
   the tinted guides band with a full section's top padding: 4rem of white and
   4rem of tint, 8rem in total, above a heading that follows a small centred
   row of page buttons. Between two ordinary sections that boundary reads as
   one break; here it reads as a hole, because the pagination leaves the column
   almost empty on the way into it.

   Halved to 4.5rem, still a clear editorial break and still two distinct
   pieces of the page. SCOPED TO THE ONE PAGE by the per-category class
   bravith_render_category_guides() emits, so /shaders/, /texture-packs/ and
   /modpacks/ keep the spacing they have; the resource and guide pages never
   carried these classes at all.

   :has() is only the second half of it. Where it is unsupported the listing
   keeps its 4rem and the band still tightens — a smaller improvement, never a
   broken layout. */
.category-guides-mods {
  padding-top: var(--space-8);
}

.category-body:has(+ .category-guides-mods) {
  padding-bottom: var(--space-10);
}

.category-layout {
  display: grid;
  gap: var(--space-10);
  align-items: start;
}

.category-main {
  min-width: 0;
}

/* Both empty states — a category with nothing published yet, and a filter
   that matched nothing — land here. A bare grey sentence at the top of a
   column 800px wide and half a screen tall reads as a page that failed to
   load; a panel the size of the grid it stands in for reads as an answer. */
.category-main > .trending-empty {
  display: grid;
  place-items: center;
  min-height: 220px;
  padding: var(--space-10) var(--space-6);
  text-align: center;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-elevated);
}

.category-main > .trending-empty[hidden] {
  display: none;
}

/* ---------- Controls ----------
   The chips, the result count and the sort control are the version browser's
   components, reused rather than reinvented. Two things differ here. The
   surface is stripped off: on a resource page the filters are a panel among
   panels, but on a category page they are the top edge of the listing and
   have to start on the same line the grid does. And the active chip is the
   category's colour rather than the brand accent — filled, not tinted, so at
   a glance across a wrapped row there is exactly one chip that reads as on. */
.category-filters {
  gap: var(--space-3);
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  margin-bottom: 0;
}

.category-filters .version-filter-groups,
.category-filters .version-filter-group {
  gap: 0;
}

.category-filters[hidden],
.version-sort[hidden] {
  display: none;
}

/* One height for every chip, whatever the label — a row of pills that step up
   and down as the words change is the single loudest sign of an unconsidered
   control. */
.category-filters .version-chip {
  min-height: 40px;
  padding-inline: var(--space-4);
}

.category-filters .version-chip[aria-pressed="true"],
.category-filters .version-chip[aria-pressed="true"]:hover {
  color: var(--color-on-accent);
  background-color: var(--tint, var(--color-accent));
  border-color: var(--tint, var(--color-accent));
}

.category-toolbar {
  margin-top: var(--space-6);
  margin-bottom: var(--space-5);
}

/* The sort control is built to the chips' dimensions — same height, same
   radius, same border — so the row above it and this one read as one set of
   controls rather than a filter panel and a stray form field. */
.category-toolbar .version-sort select {
  min-height: 40px;
  padding-inline: var(--space-3);
  font-weight: 600;
  border-radius: var(--radius-sm);
}

.category-toolbar .version-sort label {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* ---------- The grid ----------
   auto-fill rather than a fixed column count, so the number of columns
   follows the width actually left beside the sidebar. 248px is the floor at
   which a card still holds a title, three lines of description and its two
   meta items without the text turning into a column of single words. */
.category-grid {
  grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
}

.listing-card {
  position: relative;
}

.listing-card[hidden] {
  display: none;
}

.listing-card:hover {
  border-color: var(--tint, var(--color-border-strong));
}

.listing-card .card-media {
  position: relative;
}

/* Two chips, not one pill: what it is, in the category's colour, then what it
   runs on, in neutral. They sit with the meta row rather than above the title
   — see the ORDER note in bravith_render_resource_card(). */
.listing-card .card-tags {
  margin-top: var(--space-1);
}

.listing-card .card-title {
  font-size: var(--text-md);
}

.listing-card .card-excerpt {
  line-height: 1.55;
}

/* The editorial Featured flag. A real stored field, and the only claim this
   card makes that is not measured. */
.card-flag {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  padding: 0.25rem 0.6rem;
  border-radius: var(--radius-xs);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-on-accent);
  background-color: var(--tint, var(--color-accent));
}

/* The measured facts, ruled off from the description above them. .card-footer
   already carries margin-top:auto and every card in a grid row is the same
   height, so these rules land on one line across the row however unequal the
   descriptions above them are. */
.listing-card .card-footer {
  gap: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
  color: var(--color-text-faint);
}

/* ---------- Pagination ----------
   Written by the script, and only ever present when there are more resources
   than one page holds. */
.category-pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-10);
}

.category-pagination[hidden] {
  display: none;
}

.category-page-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 40px;
  padding-inline: var(--space-3);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: background-color var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast);
}

.category-page-btn:hover:not(:disabled) {
  background-color: var(--color-surface-hover);
  border-color: var(--color-text-faint);
}

.category-page-btn[aria-current="page"] {
  color: var(--color-on-accent);
  background-color: var(--tint, var(--color-accent));
  border-color: var(--tint, var(--color-accent));
}

.category-page-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

.category-page-gap {
  padding-inline: var(--space-1);
  color: var(--color-text-faint);
}

/* ---------- Sidebar ----------
   Four panels of the same construction — same padding, same heading, same
   radius — so the column reads as one object with four sections rather than
   four widgets that happened to stack. The panels are the same card the grid
   beside them uses, minus the shadow: they are supporting material, and
   lifting them off the page would compete with the resources. */
.category-sidebar {
  display: grid;
  gap: var(--space-6);
  min-width: 0;
}

.category-panel {
  padding: var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
}

.category-panel-title {
  font-size: var(--text-md);
  font-weight: 700;
  letter-spacing: -0.01em;
  margin-bottom: var(--space-4);
}

.category-panel-text {
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--color-text-muted);
}

/* The panel's own call to action, built to match the button in "Need help?"
   below it rather than being a bare link — two panels making the same offer
   should not make it in two different shapes. */
.category-panel-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-5);
  padding: 0.6rem 1.1rem;
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.category-panel-link:hover {
  color: var(--tint, var(--color-accent));
  border-color: var(--tint, var(--color-accent));
}

.category-panel-link svg {
  width: 14px;
  height: 14px;
  transition: transform var(--transition-fast);
}

.category-panel-link:hover svg {
  transform: translateX(3px);
}

.category-panel-help .btn {
  margin-top: var(--space-5);
}

/* ----- Categories list -----
   The categories WITHIN this type, each a real URL carrying ?category=, each
   also a filter the script applies in place. Two columns now, not three: the
   rows used to be the six top-level types and carried their artwork, and there
   is no artwork for "Optimization" — nor should there be, because the tile was
   doing the work of telling six identical-looking rows apart, and a name and a
   count do that here. The tint is the page's own throughout, so the selected
   row is the only thing painted. */
.category-nav-list {
  display: grid;
  gap: var(--space-1);
}

/* A <button>, not a link: it changes what this page is showing, it does not go
   anywhere. The global reset already strips the border, background and font, so
   what is left is making it fill the panel and align like the row it replaced —
   a button is shrink-to-fit and centres its text by default. */
.category-nav-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: 40px;
  padding: var(--space-2) var(--space-3);
  text-align: left;
  border-radius: var(--radius-xs);
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.category-nav-item:hover {
  background-color: var(--color-surface-hover);
}

/* Selected state read from the ARIA attribute, the same way .version-chip does
   it, so there is one source of truth for "this filter is on" rather than a
   class the script has to remember to keep in step with the attribute. */
.category-nav-item[aria-pressed="true"] {
  color: var(--tint, var(--color-accent));
  background-color: var(--tint-soft, var(--color-accent-soft));
}

.category-nav-name {
  font-size: var(--text-sm);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.category-nav-count {
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--color-text-faint);
  font-variant-numeric: tabular-nums;
}

.category-nav-item[aria-pressed="true"] .category-nav-count {
  color: inherit;
}

/* ----- Most downloaded ----- */
.category-rank-list {
  display: grid;
  gap: var(--space-2);
}

.category-rank-row {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-xs);
  transition: background-color var(--transition-fast);
}

.category-rank-row:hover {
  background-color: var(--color-surface-hover);
}

/* The resource's own icon, in the slot that used to hold the rank number. The
   chip is unchanged — same 28px box, same radius, same tinted ground — so the
   column keeps its compact green treatment and only what sits inside it is
   different. The list is still ordered by downloads; the number was never what
   ordered it. */
.category-rank-index {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  overflow: hidden;
  /* The hairline is what the Explore rows already put around this same field,
     and it earns its place here for the same reason: several of these icons
     are dark screenshots, and without an edge they read as holes punched in a
     white panel rather than as a column of thumbnails. The tinted ground stays
     underneath for the glyph fallback; the -art tiles clear both, with the
     other artwork tiles further up this file. */
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  color: var(--tint, var(--color-accent));
  background-color: var(--tint-soft, var(--color-accent-soft));
}

/* cover, not contain: these are the square hero icons, already cropped to
   their own edges, and the box is square too — the same treatment the Explore
   rows give the same field. */
.category-rank-index img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The fallback for a resource with no icon of its own: its category's glyph on
   the tint, which is a bare stroke and keeps the chip behind it. The artwork
   variant is handled with the other -art tiles further up this file. */
.category-rank-index-glyph svg {
  width: 16px;
  height: 16px;
}

.category-rank-body {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.category-rank-name {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.category-rank-row:hover .category-rank-name {
  color: var(--tint, var(--color-accent));
}

.category-rank-meta {
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}


/* =====================================================================
   /guides/ — THE HUB

   Four blocks over one list: the core installation cards that open the
   page, what is new, the five topics, and the browsable library. The
   generator partitions the guides between them (see
   bravith_render_guides_hub()); this file only says what each shape
   looks like.

   DELIBERATELY NOT THE RESOURCE CARD GRID. A resource is chosen from its
   artwork and its download count; a guide is chosen by reading its title
   and its first sentence. So the page is editorial: large cards to start
   on, a ranked strip of what is new, and a ruled two-column index — not
   four rows of the same tile.

   Every colour, radius, border and space below is an existing token, and
   the category colours ride in on --tint / --kicker exactly as they do on
   the resource cards, so nothing here names a category or a slug.

   Mobile-first. Breakpoint overrides live in responsive.css with the
   rest of the site's.
   ===================================================================== */

/* ---------- Shared pieces ----------
   The kicker, the meta line and the rank marker appear in three of the
   four blocks, so they are declared once rather than per block. */

/* "INSTALLATION", in its category's colour. The colour arrives as a
   custom property from the generator — see bravith_guides_kicker() —
   which is why this file never has to know a category exists. */
.guides-kicker {
  font-size: var(--text-xs);
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--kicker, var(--color-accent));
}

/* "7 min read", and the publication date beside it. */
.guides-meta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

.guides-meta svg {
  flex-shrink: 0;
  width: 14px;
  height: 14px;
}

/* 01, 02, 03 — the ranked sequence through "Recently added". Tabular
   figures so the column of numbers is a column and not a ripple. */
.guides-rank {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--color-accent);
}

/* Like .home-section, the hub's sections are separated by their padding
   alone — no rule at the join. */
.guides-section-lede {
  max-width: 60ch;
  margin: var(--space-3) 0 0;
  color: var(--color-text-muted);
}

/* ---------- The masthead ----------
   Inside the site's dark band, so every colour here is read from the
   semantic tokens the band redeclares (--color-surface, --color-text,
   --color-border) rather than from the ink palette directly. That is
   what lets the same component be lifted onto a light page later without
   a single value changing. */
.guides-masthead-layout {
  display: grid;
  gap: var(--space-8);
  align-items: center;
}

.guides-masthead-copy {
  display: grid;
  gap: var(--space-4);
  align-content: start;
  min-width: 0;
}

/* Fluid rather than a size per breakpoint: the title is two words and
   has to carry the whole opening at every width. */
.guides-masthead h1 {
  margin-top: var(--space-2);
  font-size: clamp(2.125rem, 1.35rem + 3.2vw, 3.25rem);
  line-height: 1.03;
  letter-spacing: -0.025em;
  text-wrap: balance;
}

.guides-masthead .lede {
  max-width: 44ch;
}

.guides-masthead-visual {
  min-width: 0;
}

/* aspect-ratio as well as width/height, so the slot is held open before
   the artwork loads and the dark band does not jump. */
.guides-masthead-image {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}

/* ---------- Core installation guides ----------
   The largest card on the page, and the only place a guide gets one.
   Three across on a desktop; the media, the copy and the ruled footer
   are three bands of one object rather than a stack of loose parts. */
.guides-core {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
}

.guides-core-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background-color: var(--color-surface);
  box-shadow: var(--shadow-card);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--transition-fast), transform var(--transition-fast),
              box-shadow var(--transition-fast);
}

.guides-core-card:hover,
.guides-core-card:focus-visible {
  border-color: var(--color-border-strong);
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.guides-core-media {
  position: relative;
  display: block;
  border-bottom: 1px solid var(--color-border);
  background-color: var(--color-bg-elevated);
}

.guides-core-media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* The topic's own mark, dropped onto the corner of the plate. It is the
   one thing that tells three otherwise identical cards apart at a
   glance, and it is the same artwork the category carries everywhere
   else on the site. */
.guides-core-mark {
  position: absolute;
  left: var(--space-4);
  bottom: var(--space-4);
  display: inline-flex;
}

.guides-core-mark img {
  width: 36px;
  height: 36px;
  aspect-ratio: auto;
  object-fit: contain;
}

.guides-core-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: var(--space-3);
  padding: var(--space-5);
}

.guides-core-title {
  margin: 0;
  font-size: var(--text-md);
  line-height: 1.25;
  letter-spacing: -0.01em;
}

.guides-core-excerpt {
  flex: 1;
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--color-text-muted);
}

/* The measured facts, ruled off from the description. flex:1 above puts
   this line on the same baseline across a row of unequal cards. */
.guides-core-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}

.guides-core-arrow,
.guides-recent-arrow,
.guides-row-arrow {
  display: inline-flex;
  color: var(--color-accent);
  transition: transform var(--transition-fast);
}

.guides-core-arrow svg {
  width: 20px;
  height: 20px;
}

.guides-core-card:hover .guides-core-arrow {
  transform: translateX(3px);
}

/* ---------- Recently added ----------
   A lead item, a column of two beside it and a strip of three beneath —
   one ranked sequence in three shapes, so the block reads as an editor's
   page rather than a fourth grid. Every slot is optional: the generator
   fills what it has and the areas collapse around what it does not. */
.guides-recent {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-areas:
    "lead"
    "side"
    "strip";
  gap: var(--space-8);
}

.guides-recent-lead {
  position: relative;
  grid-area: lead;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  color: inherit;
  text-decoration: none;
}

.guides-recent-lead-media {
  display: block;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-elevated);
}

.guides-recent-lead-media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
}

/* A solid chip rather than bare figures: this number sits on artwork
   nobody has seen yet, and text alone cannot be guaranteed to read
   against it. */
.guides-recent-lead > .guides-rank {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  z-index: 1;
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius-xs);
  background-color: var(--color-accent);
  color: var(--color-on-accent);
}

.guides-recent-lead-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.guides-recent-lead-title {
  margin: 0;
  font-size: var(--text-xl);
  line-height: 1.2;
  letter-spacing: -0.015em;
}

.guides-recent-lead-excerpt {
  max-width: 52ch;
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--color-text-muted);
}

.guides-recent-side,
.guides-recent-strip {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}

.guides-recent-side {
  grid-area: side;
  align-content: start;
}

.guides-recent-strip {
  grid-area: strip;
}

.guides-recent-item {
  min-width: 0;
}

.guides-recent-row {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  border-top: 1px solid var(--color-border);
  color: inherit;
  text-decoration: none;
}

/* The first row of the side column starts level with the top of the
   lead's artwork, so it opens on white rather than on a rule. */
.guides-recent-side .guides-recent-item:first-child .guides-recent-row {
  border-top: 0;
  padding-top: 0;
}

.guides-recent-thumb {
  display: block;
  width: 88px;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  background-color: var(--color-bg-elevated);
}

.guides-recent-thumb img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.guides-recent-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.guides-recent-title {
  margin: 0;
  font-size: var(--text-base);
  line-height: 1.3;
}

.guides-recent-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
}

.guides-recent-arrow svg {
  width: 18px;
  height: 18px;
}

.guides-recent-row:hover .guides-recent-title,
.guides-recent-lead:hover .guides-recent-lead-title {
  color: var(--color-accent);
}

.guides-recent-row:hover .guides-recent-arrow {
  transform: translateX(3px);
}

/* ---------- Explore guides by topic ----------
   The one dark block on a light page. The cards are near-black because
   the topics are a way OUT of the guides section and should not be
   mistaken for another row of guides — and because the category marks
   were drawn to sit on a dark field.

   The colour is the category's own --tint, laid on as a single soft wash
   from the top-left corner. One gesture per card, no gradient anywhere
   else on the page: it is what makes five cards read as five different
   places rather than five copies of one. */
.guides-topics {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}

.guides-topic {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* The description takes the slack, so the "Explore" line lands on one level
     across a row of cards whose blurbs are different lengths. */
  grid-template-rows: auto auto 1fr auto;
  grid-template-areas:
    "icon"
    "name"
    "desc"
    "cta";
  gap: var(--space-2);
  overflow: hidden;
  padding: var(--space-5);
  border: 1px solid var(--ink-border);
  border-radius: var(--radius-md);
  background-color: var(--ink-bg-elevated);
  color: var(--ink-text);
  text-decoration: none;
  transition: border-color var(--transition-fast), transform var(--transition-fast);
}

.guides-topic::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 90% at 10% 0%, var(--tint-soft, transparent), transparent 65%);
  pointer-events: none;
}

/* Everything the card holds sits above the wash. */
.guides-topic > * {
  position: relative;
}

.guides-topic:hover,
.guides-topic:focus-visible {
  border-color: var(--ink-border-strong);
  transform: translateY(-2px);
}

.guides-topic-icon {
  grid-area: icon;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-xs);
}

.guides-topic-icon img {
  width: 40px;
  height: 40px;
  object-fit: contain;
}

/* The drawn glyph, for a category with no artwork on disk: a bare stroke
   needs the chip the artwork brings with it. */
.guides-topic-icon-glyph {
  background-color: var(--ink-surface);
  color: var(--ink-text);
}

.guides-topic-icon-glyph svg {
  width: 22px;
  height: 22px;
}

.guides-topic-name {
  grid-area: name;
  margin-top: var(--space-2);
  font-size: var(--text-md);
  font-weight: 750;
  letter-spacing: -0.01em;
}

/* Two lines, clamped: the blurbs are read from each category page's own
   lede and are not all the same length. */
/* A height cap, and NOT -webkit-line-clamp.
   This element is a grid item, and Chrome blockifies a grid item's
   display:-webkit-box to flow-root — which switches the clamp off with no
   warning and no error. Measured: computed display came back `flow-root` with
   `-webkit-line-clamp: 2` still sitting on it, and a blurb ran past its two
   lines at the width where a card is narrowest. A max-height survives
   blockification because it does not depend on the box type at all, and the em
   is the element's own 12px so the cap tracks the font size and the
   line-height below it rather than a magic pixel value.

   THREE lines, not two, and that number was measured rather than picked. Four
   of the five blurbs fit in two lines at the narrowest a card gets (182px of
   interior at 1280); the Maps lede runs to three and was being cut mid-phrase
   with nothing to show for it. Cutting the shared blurb length instead would
   have shortened the homepage's cards too, for a problem the homepage does not
   have. The cap stays as a backstop against a category page whose lede is
   rewritten much longer — bravith_home_category_blurb() already ends the text
   on a clause boundary, so it should never be reached. */
.guides-topic-desc {
  grid-area: desc;
  max-height: calc(3 * 1.45em);
  overflow: hidden;
  margin-bottom: var(--space-4);
  font-size: var(--text-xs);
  line-height: 1.45;
  color: var(--ink-text-muted);
}

/* The card's destination, spelled out. It replaced a guide count, which
   described the guides section while the card led somewhere else entirely;
   this says where the card actually goes. White label, accent arrow — the only
   red on an otherwise monochrome card, and the thing the eye follows along the
   row.

   min-width:0 on the label lets a long name shrink inside the flex row instead
   of pushing the arrow past the card's padding: "Explore Texture Packs" is the
   longest of the five and the one that decides this. */
.guides-topic-cta {
  grid-area: cta;
  align-self: end;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--ink-text);
}

.guides-topic-arrow {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--color-accent);
  transition: transform var(--transition-fast);
}

.guides-topic-arrow svg {
  width: 16px;
  height: 16px;
}

.guides-topic:hover .guides-topic-arrow {
  transform: translateX(3px);
}

/* ---------- The library ----------
   Every guide on the site, filtered by category and revealed a batch at
   a time by initGuideLibrary() in js/main.js.

   WITH NO JAVASCRIPT the whole list is here and visible, and the two
   controls carry `hidden` from the generator — so the section degrades
   to a complete, readable index rather than to an empty one. */
.guides-library {
  display: grid;
  gap: var(--space-6);
}

/* Editorial tabs, not buttons: a rule under the row and a red underline
   on the one that is on. A row of filled pills would out-shout the guide
   titles it exists to filter. */
.guides-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0 var(--space-6);
  border-bottom: 1px solid var(--color-border);
}

.guides-filter {
  position: relative;
  padding: var(--space-2) 0 var(--space-3);
  border: 0;
  background: none;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 650;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: color var(--transition-fast);
}

.guides-filter:hover {
  color: var(--color-text);
}

.guides-filter[aria-pressed="true"] {
  color: var(--color-text);
}

/* -1px so the marker sits ON the row's rule rather than above it. */
.guides-filter[aria-pressed="true"]::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: -1px;
  left: 0;
  height: 2px;
  background-color: var(--color-accent);
}

.guides-rows {
  display: grid;
  grid-template-columns: 1fr;
  margin: 0;
  padding: 0;
  list-style: none;
}

.guides-row {
  min-width: 0;
  border-top: 1px solid var(--color-border);
}

.guides-row-link {
  display: grid;
  grid-template-columns: 104px minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  color: inherit;
  text-decoration: none;
}

.guides-row-media {
  display: block;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  background-color: var(--color-bg-elevated);
}

.guides-row-media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.guides-row-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.guides-row-title {
  margin: 0;
  font-size: var(--text-base);
  line-height: 1.3;
}

.guides-row-excerpt {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-text-muted);
}

.guides-row-meta {
  margin-top: var(--space-1);
}

.guides-row-arrow svg {
  width: 18px;
  height: 18px;
}

.guides-row-link:hover .guides-row-title {
  color: var(--color-accent);
}

.guides-row-link:hover .guides-row-arrow {
  transform: translateX(3px);
}

.guides-more {
  display: flex;
  justify-content: center;
}

.guides-more-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 48px;
  padding-inline: var(--space-8);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text);
  cursor: pointer;
  transition: background-color var(--transition-fast), border-color var(--transition-fast);
}

.guides-more-btn:hover {
  background-color: var(--color-surface-hover);
  border-color: var(--color-text-faint);
}

.guides-more-icon {
  display: inline-flex;
  color: var(--color-accent);
}

.guides-more-icon svg {
  width: 18px;
  height: 18px;
}

.guides-empty {
  margin: 0;
  padding: var(--space-12) 0;
  text-align: center;
  color: var(--color-text-muted);
}

/* A CLASS THAT SETS `display` DEFEATS THE `hidden` ATTRIBUTE — the same
   trap documented over .search-page-result[hidden] further down this
   file. Every element the library toggles is declared here, including
   the ones that only inherit a display from a grid or a flex parent, so
   that a later restyle cannot quietly turn hiding back off. */
.guides-filters[hidden],
.guides-rows[hidden],
.guides-row[hidden],
.guides-empty[hidden],
.guides-more-btn[hidden] {
  display: none;
}

/* The button is the only thing in its row, so when it goes the row
   should stop reserving a gap for it. Progressive: a browser without
   :has simply leaves the grid gap behind, which is 24px of white space
   at the foot of a section that has already ended. */
.guides-more:has(> [hidden]) {
  display: none;
}

/* =====================================================================
   A single guide page (/guides/<slug>/) rendered by the CMS.
   These classes had no styles at all: the renderer emitted them, but
   nothing defined them, so a published guide would have arrived as
   unstyled markup inside the site chrome.
   ===================================================================== */

/* --------------------------------------------------------------------------
   THE GUIDE TEMPLATE

   One layout, driven entirely by what a guide's own data contains. The renderer
   decides WHICH blocks exist (see bravith_render_guide_page); this decides what
   they look like. A future guide inherits the whole thing by being published —
   there is no per-guide CSS anywhere, and there must never be.

   Structure:
     .guide-hero            dark, inside the ink band, ends on the shared curve
       .guide-hero-inner    container; two columns only when there is artwork
     .guide-layout          light; article + optional derived sidebar
       .guide-main          the reading column, capped at a real measure
       .guide-side          contents / quick nav / related — each optional
   -------------------------------------------------------------------------- */

/* ---------- Guide hero ---------- */
.guide-hero-inner {
  display: grid;
  /* Same reason as .guide-layout: the copy column carries a 68ch cap, and on an
     auto track that measure becomes the track's width and overflows a phone. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-8);
  align-items: center;
}

/* This container used to become two columns, with the featured image in the
   second, whenever the renderer added a has-art modifier. Both are gone: the
   artwork is now a sibling of this container and is sunk into the band itself
   — see THE SUNKEN HERO ARTWORK further down this file — so the container
   holds the copy and only the copy, at every width. */

.guide-header {
  min-width: 0;
}

.guide-header h1 {
  margin: 0 0 var(--space-4);
}

.guide-lede {
  margin: 0;
  font-size: var(--text-md);
  line-height: 1.55;
  color: var(--color-text-muted);
  max-width: 54ch;
}

/* A list, not a paragraph: these are discrete facts about the guide, and a
   screen reader should be able to move through them as such. The bullets are
   removed because the separators are visual, not semantic. */
.guide-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-5);
  margin: var(--space-5) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.guide-meta li {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

.guide-meta-icon {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  color: var(--color-text-faint);
}

.guide-byline {
  font-weight: 600;
  color: var(--color-text);
}

.guide-tag {
  padding: 0.2em 0.7em;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--color-text);
}

.guide-hero-art {
  margin: 0;
  min-width: 0;
}

.guide-hero-art img {
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border);
}

.guide-hero-art figcaption {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-faint);
}

/* ---------- Guide layout ---------- */
.guide-layout {
  display: grid;
  /* minmax(0, 1fr), NOT the default `auto`. An auto track is sized from its
     content, and .guide-main carries a 74ch measure — about 590px — so on a
     narrow screen the single column resolved to 74ch and pushed the whole
     article off the right edge of the viewport. A 0 minimum lets the track
     shrink to whatever the container actually is. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-8);
  padding-block: var(--space-10) var(--space-16);
  align-items: start;
}

/* The sidebar arrives only when the viewport can afford it without squeezing
   the reading column. Below this it stacks underneath, which is the ordering
   the markup already has — no reordering, so tab order stays correct.
   The proportions are the reference's: an article column around 850px against
   a 320px sidebar, separated by a narrow gutter rather than a wide one. */
@media (min-width: 1000px) {
  .guide-layout:not(.guide-layout-solo) {
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: var(--space-10);
  }
}

.guide-main {
  min-width: 0;
}

/* .prose caps itself at a 72ch measure, which is right for a bare article but
   wrong here: this column also carries steps, callouts, figures and the
   contents strip, and all of it should use the column the grid handed it
   rather than stopping short and leaving a gutter inside a gutter. The grid
   track is what limits the width now. */
.guide-main .guide-body {
  max-width: none;
}

.guide-layout-solo .guide-main {
  max-width: 78ch;
  margin-inline: auto;
}

/* ---------- Guide body ----------
   Reuses .prose for type, lists, code and links. Only the rhythm between
   top-level blocks and the heading scale are set here. */
.guide-body > * + * {
  margin-top: var(--space-5);
}

.guide-body h2 {
  margin-top: var(--space-12);
  font-size: var(--text-2xl);
  letter-spacing: -0.01em;
  line-height: 1.25;
}

/* A hairline above each section turns a long tutorial into visibly separate
   chapters without adding a box around every one of them. */
.guide-body h2 + * {
  margin-top: var(--space-4);
}

.guide-body h3 {
  margin-top: var(--space-8);
  font-size: var(--text-lg);
  line-height: 1.3;
}

/* A sticky header would otherwise cover the heading an anchor jumps to. */
.guide-body h2[id] {
  scroll-margin-top: calc(var(--space-16) + var(--space-4));
}

/* ---------- "On this page" ----------
   The horizontal section strip that opens the article, above the first
   heading. It shares its data with the sidebar contents list — both are built
   from the same headings pass — but does a different job: this one shows the
   shape of the whole guide at a glance before you start reading, where the
   sidebar is for jumping around once you have.

   It scrolls inside itself on narrow screens rather than wrapping into a tall
   block or pushing the page sideways. */
.guide-onthispage {
  margin: 0 0 var(--space-10);
  padding-bottom: var(--space-6);
  border-bottom: 1px solid var(--color-border);
}

.guide-onthispage h2 {
  margin: 0 0 var(--space-4);
  font-size: var(--text-base);
  font-weight: 700;
  letter-spacing: -0.01em;
}

/* A grid of equal columns, one per section, so every entry is visible at once
   and the row ends exactly where the article column does. A flex row with
   fixed-width items overflowed instead, hiding the last section behind a
   scroll — the opposite of what a map of the page is for. auto-fit on a 1fr
   minimum means the track count follows the heading count with nothing to
   configure. */
.guide-onthispage ol {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: ots;
}

.guide-onthispage li {
  counter-increment: ots;
  min-width: 0;
}

/* Below the two-column breakpoint seven equal columns would be a few pixels
   each, so the row becomes a strip that scrolls inside itself. It is the only
   thing on the page that scrolls horizontally, and it never moves the page. */
@media (max-width: 999px) {
  .guide-onthispage ol {
    grid-auto-columns: minmax(6.5rem, 1fr);
    overflow-x: auto;
    padding-bottom: var(--space-2);
    scrollbar-width: thin;
  }
}

/* ONE MAP OF THE PAGE AT A TIME. At and above the two-column breakpoint the
   sidebar contents list is on screen beside the article, built from the same
   headings pass and carrying the same entries, so the strip repeated every
   link a second time and pushed the first paragraph down by its own height.

   Hidden here rather than dropped from the renderer, because below 1000px the
   sidebar falls to the BOTTOM of the page, past the whole article — there the
   strip is the only map a reader gets before they start reading, and it stays.
   One breakpoint, the same 1000px .guide-layout uses for its second column, so
   the strip disappears exactly when the sidebar arrives: never both, never
   neither. Both are rendered from the same headings pass, and the strip only
   exists when the sidebar list does (three headings), so hiding it can never
   leave a guide with no contents list at all. */
@media (min-width: 1000px) {
  .guide-onthispage {
    display: none;
  }
}

.guide-onthispage a {
  display: grid;
  justify-items: center;
  gap: var(--space-2);
  text-align: center;
  text-decoration: none;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  line-height: 1.35;
}

.guide-onthispage a::before {
  content: counter(ots);
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--color-border-strong);
  background-color: var(--color-bg);
  font-size: var(--text-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
}

.guide-onthispage a:hover,
.guide-onthispage a:focus-visible {
  color: var(--color-text);
}

.guide-onthispage a:hover::before,
.guide-onthispage a:focus-visible::before {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

/* ---------- Before you start ----------
   A prerequisites panel for guides that declare one. Deliberately quieter than
   a callout: it is a checklist you read once, not a warning. */
.guide-prereq {
  margin-block: var(--space-8);
  padding: var(--space-5) var(--space-6);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-success);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  background-color: var(--color-success-soft);
}

.guide-prereq-title {
  margin: 0 0 var(--space-3);
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--color-text);
}

/* The compound selector is deliberate. This list lives inside .guide-body,
   which is .prose, and `.prose ul { list-style: disc }` is one class and one
   element — more specific than a bare `.guide-prereq-list`. A single class
   therefore lost, and the disc was drawn UNDERNEATH the tick below, giving
   every row two markers. Matching that specificity is the whole fix. */
.guide-prereq-list,
.prose .guide-prereq-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-2);
  font-size: var(--text-sm);
}

/* The tick is decorative — the list is already a list — so it is drawn in CSS
   rather than added to the markup as a character a screen reader would read. */
.guide-prereq-list li {
  position: relative;
  padding-left: var(--space-6);
}

.guide-prereq-list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.45em;
  width: 10px;
  height: 5px;
  border-left: 2px solid var(--color-success);
  border-bottom: 2px solid var(--color-success);
  transform: rotate(-45deg);
}

/* ---------- In-article figures ----------
   Applies to the template's own figures and to any a guide author writes, so a
   screenshot dropped between two steps is styled without extra classes. */
.guide-body figure,
.guide-figure {
  margin: var(--space-8) 0;
}

.guide-body img,
.guide-figure img {
  display: block;
  /* max-width, not width: a narrow screenshot is not stretched to the column,
     and a wide one can never push the layout sideways. */
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
}

.guide-body figcaption,
.guide-figure figcaption {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-text-faint);
}

/* ---------- FAQ ----------
   <details>/<summary>: an accordion that is keyboard operable, announced
   correctly and open-by-default when JavaScript is off, with no script at all
   and nothing for the CSP to object to. */
.guide-faq {
  margin-block: var(--space-8);
  display: grid;
  gap: var(--space-3);
}

.guide-faq details {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-elevated);
  overflow: hidden;
}

.guide-faq summary {
  padding: var(--space-4) var(--space-5);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

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

.guide-faq summary::after {
  content: "";
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--color-text-faint);
  border-bottom: 2px solid var(--color-text-faint);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--transition-fast, 150ms) ease;
}

.guide-faq details[open] summary::after {
  transform: rotate(-135deg) translate(-2px, -2px);
}

.guide-faq summary:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

.guide-faq details > *:not(summary) {
  padding: 0 var(--space-5) var(--space-5);
  margin: 0;
  font-size: var(--text-sm);
}

/* ---------- Troubleshooting ----------
   The panel a guide's "what went wrong" section sits in. The class was already
   allowlisted for authors but had no styling, so it rendered as a bare div.

   QUIETER THAN A CALLOUT, ON PURPOSE. A callout is an interruption and carries
   a tint plus a coloured edge; this is a section you arrive at deliberately and
   then read down, so it takes the plain surface, hairline border and radius the
   sidebar cards and FAQ rows already use. The only colour is one small red dot
   per problem, which is the site's existing accent doing the same job it does
   on list markers elsewhere.

   IT STYLES THE MARKUP GUIDES ALREADY CONTAIN, and adds no classes of its own:
   authors write either heading-and-answer pairs or a list of problems, and both
   shapes were already in the published guides before this existed. Anything
   else an author puts inside simply inherits the article's own prose styles. */
.guide-troubleshooting {
  margin-block: var(--space-8);
  padding: var(--space-2) var(--space-6) var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
}

/* Shape one: an <h3> problem followed by its explanation. The heading keeps the
   article's own type scale; only its spacing and marker change here, so a
   troubleshooting heading still reads as a heading in the table of contents and
   to a screen reader. */
.guide-troubleshooting h3 {
  position: relative;
  margin-top: var(--space-5);
  margin-bottom: var(--space-2);
  padding-left: var(--space-5);
}

/* A rule between problems rather than a box around each: the panel stays one
   object, and an arbitrary number of items simply extends it. */
.guide-troubleshooting h3:not(:first-child) {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border);
}

.guide-troubleshooting h3::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background-color: var(--color-accent);
}

/* The dot belongs to the heading, not to the rule above it. */
.guide-troubleshooting h3:not(:first-child)::before {
  top: calc(0.55em + var(--space-5));
}

.guide-troubleshooting h3 + p {
  padding-left: var(--space-5);
}

.guide-troubleshooting > p:last-child {
  margin-bottom: 0;
}

/* Shape two: one list, each item naming a problem in <strong> and explaining it
   inline. The compound selector matches `.prose ul`, which is one class and one
   element and would otherwise keep its disc alongside the dot below. Same
   reasoning as .guide-prereq-list above. */
.guide-troubleshooting ul,
.prose .guide-troubleshooting ul {
  margin: var(--space-4) 0 0;
  padding-left: 0;
  list-style: none;
  display: grid;
  gap: var(--space-4);
}

.guide-troubleshooting li {
  position: relative;
  padding-left: var(--space-5);
}

.guide-troubleshooting li + li {
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}

.guide-troubleshooting li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background-color: var(--color-accent);
}

.guide-troubleshooting li + li::before {
  top: calc(0.55em + var(--space-4));
}

/* The problem name is the scannable part of an inline item. */
.guide-troubleshooting li strong {
  color: var(--color-text);
}

/* ---------- End-of-guide CTA ----------
   A quiet hand-off, not an advert: a bordered strip in the page's own palette
   with one button, sitting where the tutorial finishes. */
.guide-cta {
  margin-top: var(--space-12);
  padding: var(--space-6);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-5);
  border: 1px solid var(--color-success-soft);
  border-radius: var(--radius-lg);
  /* Tinted rather than plain, matching the reference: it reads as an offer
     rather than another bordered box, without shouting like an advert. */
  background-color: var(--color-success-soft);
}

.guide-cta-icon {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-md);
  background-color: var(--color-bg);
  border: 1px solid var(--color-border);
  color: var(--color-success);
}

.guide-cta-icon svg {
  width: 22px;
  height: 22px;
}

/* Takes the slack so the button is pushed to the far end at any width. */
.guide-cta-copy {
  min-width: 0;
  flex: 1 1 14rem;
}

.guide-cta h2 {
  margin: 0 0 var(--space-1);
  font-size: var(--text-md);
  line-height: 1.3;
}

.guide-cta p {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* ---------- Guide sidebar ---------- */
/* THE SIDEBAR IS DELIBERATELY NOT STICKY. It sits in normal flow and scrolls
   with the article. An earlier version pinned it with position: sticky and a
   viewport-height cap; that is explicitly not wanted here, so there is no
   sticky, fixed, top offset or 100vh constraint anywhere in this block. */
.guide-side {
  min-width: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-5);
  align-content: start;
}

.guide-side > * {
  margin: 0;
  padding: var(--space-5) var(--space-5) var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background-color: var(--color-surface);
}

/* A readable card heading rather than a micro-label. The reference titles its
   panels the way an article titles a section — sentence case, full size, the
   page's own text colour — so the sidebar reads as part of the guide rather
   than as an admin panel bolted to the side of it. */
.guide-side h2 {
  margin: 0 0 var(--space-4);
  font-size: var(--text-base);
  font-weight: 700;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--color-text);
}

.guide-side ol,
.guide-side ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-1);
}

.guide-side a {
  display: block;
  padding: var(--space-2) var(--space-2);
  margin-inline: calc(var(--space-2) * -1);
  border-radius: var(--radius-xs);
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: var(--text-sm);
  line-height: 1.45;
}

.guide-side a:hover,
.guide-side a:focus-visible {
  color: var(--color-text);
  background-color: var(--color-surface-hover);
}

/* ---------- Contents list ----------
   Generated from the body's own headings at render time — see
   bravith_guide_headings() — so it can never fall out of step with the page. */
.guide-toc ol {
  counter-reset: toc;
}

.guide-toc li {
  counter-increment: toc;
}

.guide-toc a {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr);
  gap: var(--space-3);
  align-items: start;
}

/* The number is generated, so the anchor text stays exactly the heading text —
   nothing extra is added to the accessible name or to what a crawler reads. */
.guide-toc a::before {
  content: counter(toc);
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--color-border-strong);
  font-size: 0.7rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-faint);
}

.guide-toc a.is-active {
  color: var(--color-text);
  font-weight: 600;
}

/* The scroll-spy in main.js adds .is-active. Colour is not the only signal —
   the marker fills and the weight changes too. */
.guide-toc a.is-active::before {
  background-color: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-on-accent);
}

.guide-quicknav a,
.guide-related a {
  color: var(--color-accent);
}

.guide-related a {
  font-weight: 600;
}

.guide-related-all {
  margin: var(--space-4) 0 0;
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
  font-size: var(--text-sm);
}

.guide-related-all a {
  padding: 0;
  margin: 0;
}

@media (max-width: 999px) {
  /* Below the two-column breakpoint the sidebar simply follows the article in
     normal flow. It is not capped or scrolled inside itself — the whole point
     of dropping the sticky treatment is that these panels behave like ordinary
     page content at every width. */
  .guide-side {
    gap: var(--space-4);
  }
}

@media (max-width: 720px) {
  .guide-layout {
    padding-block: var(--space-8) var(--space-16);
    gap: var(--space-8);
  }

  .guide-cta {
    margin-top: var(--space-10);
    padding: var(--space-5);
  }

  .guide-cta .btn {
    width: 100%;
    justify-content: center;
  }
}

/* ==========================================================================
   The search results page (/search/?q=…)
   The overlay stays the quick preview; this is the full, filterable answer:
   the dark band carrying the field, then a filter column beside a list of
   result cards. Every control narrows, orders or pages a list the server
   already sent — nothing is fetched — and with scripting off the column and
   the toolbar's controls stay hidden while the whole list stays readable.
   ========================================================================== */

/* ---------- Hero ----------
   A page-header like any other, only shorter: the eyebrow, the title, one
   line of copy and the field, with the size of the index beside them. The
   results are what the visitor came for and they should be close to the top.
   .page-header.search-hero has to match the specificity of the
   .page-header.page-header rule in main.css that reserves the curve's depth;
   this file loads after it, so the later declaration wins. */
.page-header.search-hero {
  padding-top: var(--space-8);
  padding-bottom: calc(var(--space-8) + var(--ink-curve));
}

/* One column until 900px; then the copy and the field on the left and the
   index figure on the right, bottom-aligned with the field. */
.search-hero-layout {
  display: grid;
  gap: var(--space-5);
  align-items: end;
}

.search-hero-copy {
  min-width: 0;
}

/* The selector has to be .search-hero .search-hero-title: a lone class loses
   to .page-header h1 on specificity no matter which file loads last. */
.search-hero .search-hero-title {
  margin-top: var(--space-3);
  font-size: var(--text-3xl);
  line-height: 1.08;
  letter-spacing: -0.02em;
}

.search-hero-lede {
  /* The same measure as the field below it, so the two read as one block
     rather than as a paragraph that stops short of the bar under it. */
  max-width: 46rem;
  margin-top: var(--space-3);
  font-size: var(--text-base);
  color: var(--color-text-muted);
}

/* ONE COMPONENT, NOT A FIELD AND A BUTTON.
   The same pill the homepage hero uses: the icon, the input and the submit sit
   inside a single bordered surface, so the whole thing reads as the search bar
   rather than as a form control with something parked beside it. */
.search-page-form {
  max-width: 46rem;
  margin-top: var(--space-6);
}

.search-page-bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-5);
  background-color: #ffffff;
  border: 1px solid var(--ink-border-strong);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-md);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.search-page-bar:focus-within {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px rgba(226, 72, 63, 0.28);
}

.search-page-bar-icon {
  flex: 0 0 auto;
  display: inline-flex;
  width: 18px;
  height: 18px;
  color: #6f6862;
}

.search-page-bar-icon svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* Beats `.page-header input`, which paints its own field: inside the pill the
   input IS the pill, so it carries no box of its own. */
.search-page-bar input[type="search"] {
  flex: 1 1 auto;
  min-width: 0;
  border: none;
  background: none;
  font-size: var(--text-md);
  color: #201c18;
}

.search-page-bar input[type="search"]:focus {
  /* The ring is drawn by the pill, so a second one inside it is noise. */
  outline: none;
}

/* Chrome's own clear button, for the same reason the overlay hides it: it
   would sit in the pill for the whole of every search, in the browser's blue. */
.search-page-bar input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

.search-page-submit {
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  padding-block: 0.6rem;
}

/* What the index holds, as one hero statistic: the count on its own line with
   the wording under it. A block under the field on a phone; beside it, past a
   rule, from 900px. */
.search-hero-index {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* The figure carries the block, so it is set on its own line rather than at
   the head of a sentence. Deliberately a step under the h1 beside it: this is
   a statistic in the second column, not a second heading. */
.search-hero-index-figure {
  display: block;
  /* The hero's own near-white. Not a literal — .page-header rebinds
     --color-text to --ink-text for the dark band, so the token is already
     the right colour here and stays right if that band is ever retuned. */
  color: var(--color-text);
  font-size: 2rem;
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.02em;
}

.search-hero-index-label {
  display: block;
  margin-top: var(--space-1);
}

@media (min-width: 900px) {
  .search-hero-layout {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--space-10);
  }

  /* 15rem is what wraps the wording onto two comfortable lines beside the
     field; the rule and the padding are the separation from it. The column is
     bottom-aligned by .search-hero-layout, so the last line of the label sits
     level with the field rather than the figure floating above it. */
  .search-hero-index {
    max-width: 15rem;
    padding-bottom: var(--space-2);
    padding-left: var(--space-6);
    border-left: 1px solid var(--color-border);
    font-size: var(--text-base);
  }
}

/* ---------- Layout ---------- */
.search-section {
  /* Tighter than a stock .section: the hero already ends in the curve, and a
     full space-16 under it left the results stranded below a blank band. */
  padding-block: var(--space-8) var(--space-16);
}

.search-layout {
  display: grid;
  /* minmax(0, …) so a long result title cannot widen the track past the
     container and take the page with it. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
  /* Where a page turn scrolls to, clear of the sticky header. */
  scroll-margin-top: 88px;
}

/* The filter column takes a track only once the script has claimed it (the
   .has-filters class) and only when there is width to spare; below 1000px it
   is a drawer and takes nothing. With scripting off the list simply runs the
   full width. */
@media (min-width: 1000px) {
  .search-layout.has-filters {
    grid-template-columns: 264px minmax(0, 1fr);
    gap: var(--space-8);
  }
}

@media (min-width: 1280px) {
  .search-layout.has-filters {
    grid-template-columns: 280px minmax(0, 1fr);
    gap: var(--space-10);
  }
}

.search-main {
  min-width: 0;
}

/* ---------- Filter column ----------
   One bordered surface with a head, the groups and (on a phone) a foot, so it
   reads as one object rather than four widgets that happened to stack.
   Deliberately NOT sticky: four open groups are taller than a laptop viewport,
   and a sticky block taller than the viewport pins its top and puts its last
   group out of reach. */
.search-filters {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
}

.search-filters-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--color-border);
}

.search-filters-title {
  flex: 1 1 auto;
  margin: 0;
  font-size: var(--text-md);
  font-weight: 700;
  letter-spacing: -0.01em;
}

.search-filters-clear {
  padding: 0;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-accent);
}

.search-filters-clear:hover {
  text-decoration: underline;
}

.search-filters-clear:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* The drawer's own controls; a desktop has no drawer to close. */
.search-filters-close,
.search-filters-foot {
  display: none;
}

.search-facet {
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--color-border);
}

.search-facet:last-child {
  border-bottom: 0;
}

/* A disclosure: the group's heading IS the button, full width, so the whole
   line toggles it and a screen reader still hears a heading. */
.search-facet-title {
  margin: 0;
  font-size: inherit;
  line-height: inherit;
}

.search-facet-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  padding: 0;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text);
  text-align: left;
}

.search-facet-toggle svg {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  color: var(--color-text-faint);
  transition: transform var(--transition-fast);
}

.search-facet-toggle[aria-expanded="false"] svg {
  transform: rotate(-90deg);
}

.search-facet-toggle:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 3px;
  border-radius: 4px;
}

.search-facet-body {
  margin-top: var(--space-3);
}

/* Find-as-you-type inside a long group — the versions, mostly. */
.search-facet-find {
  display: block;
  margin-bottom: var(--space-2);
}

.search-facet-find input {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--color-text);
  background-color: var(--color-input-bg);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-xs);
}

.search-facet-find input::placeholder {
  color: var(--color-text-faint);
}

.search-facet-find input:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.search-facet-list {
  display: grid;
  gap: 1px;
}

/* The row is the label, so the whole line is the target. The negative inline
   margin lets its hover field run out to the group's padding edge. */
.search-facet-label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 36px;
  margin-inline: calc(var(--space-2) * -1);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-xs);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-text);
  cursor: pointer;
  transition: background-color var(--transition-fast);
}

.search-facet-label:hover {
  background-color: var(--color-surface-hover);
}

/* The native control, in the brand colour: it already knows how to be
   checked, focused and toggled from a keyboard. */
.search-facet-input {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--color-accent);
  cursor: pointer;
}

.search-facet-input:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.search-facet-art {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
}

.search-facet-art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* A category with no artwork on disk falls back to the drawn glyph, which is a
   bare stroke and needs the tinted chip painted behind it. */
.search-facet-art-glyph {
  border-radius: 5px;
  background-color: var(--tint-soft, var(--color-accent-soft));
  color: var(--tint, var(--color-accent));
}

.search-facet-art-glyph svg {
  width: 13px;
  height: 13px;
}

.search-facet-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.search-facet-input:checked ~ .search-facet-name {
  font-weight: 700;
}

.search-facet-count {
  flex: 0 0 auto;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-faint);
  font-variant-numeric: tabular-nums;
}

/* An option nothing on screen carries: dimmed and disabled, not removed, so
   the group holds still while a query is typed. */
.search-facet-option.is-empty .search-facet-label {
  color: var(--color-text-faint);
  cursor: default;
}

.search-facet-option.is-empty .search-facet-label:hover {
  background-color: transparent;
}

.search-facet-option.is-empty .search-facet-input {
  opacity: 0.45;
  cursor: default;
}

.search-facet-more {
  margin-top: var(--space-2);
  padding: var(--space-1) 0;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-accent);
}

.search-facet-more:hover {
  text-decoration: underline;
}

.search-facet-more:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: 4px;
}

.search-facet-none {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-faint);
}

/* ---------- Toolbar ----------
   The query is named ONCE, here, with the count as its subtitle; the h1 above
   stays the page's own name. The controls sit on the same line while it is
   wide enough and drop under it when it is not. */
.search-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3) var(--space-5);
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-border);
}

.search-toolbar-summary {
  flex: 1 1 18rem;
  min-width: 0;
}

.search-results-title {
  font-size: var(--text-lg);
  letter-spacing: -0.01em;
  /* A visitor-supplied query goes in here verbatim, so it has to be allowed to
     break wherever it must rather than push the layout sideways. */
  overflow-wrap: anywhere;
}

.search-page-count {
  margin: 2px 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.search-toolbar-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* Opens the drawer. A phone-only control; see the breakpoint below. */
.search-filters-open {
  display: none;
}

/* List or grid. One bordered shell, two buttons, the pressed one tinted — the
   same treatment as an active chip, so it reads as a state, not as a second
   button. */
.search-view {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
}

.search-view-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 30px;
  border-radius: 7px;
  color: var(--color-text-faint);
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.search-view-btn:hover {
  color: var(--color-text);
  background-color: var(--color-surface-hover);
}

.search-view-btn[aria-pressed="true"] {
  color: var(--color-accent);
  background-color: var(--color-accent-soft);
}

.search-view-btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.search-view-btn svg {
  width: 16px;
  height: 16px;
}

/* ---------- Applied filters ----------
   Each chip removes its filter. Present above the list on every width, so on a
   phone — where the column is a closed drawer — what is narrowing the list is
   still in view. */
.search-active {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.search-active-label {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-faint);
}

.search-active-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.3rem 0.5rem 0.3rem 0.75rem;
  font-family: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-accent);
  background-color: var(--color-accent-soft);
  border: 1px solid var(--color-accent-border);
  border-radius: var(--radius-pill);
  transition: border-color var(--transition-fast);
}

.search-active-chip:hover {
  border-color: var(--color-accent);
}

.search-active-chip:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.search-active-chip svg {
  width: 12px;
  height: 12px;
}

.search-active-clear {
  padding: 0;
  font-family: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-text-muted);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.search-active-clear:hover {
  color: var(--color-accent);
}

/* ---------- Result cards ----------
   One row per result: the icon, the title with its type badge, the blurb, the
   pills, and the download figure at the far end. The title's link is stretched
   over the whole card (the ::after below), so the click target is the card and
   the accessible name stays the title. */
.search-page-list {
  display: grid;
  gap: var(--space-3);
}

.search-result {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.search-result:hover {
  /* The category's own colour, the way every other Bravith surface signals
     which section a thing belongs to. */
  border-color: var(--tint, var(--color-border-strong));
  box-shadow: var(--shadow-card);
}

/* The link is the only focusable thing in the card, so the card draws the ring
   for it: a ring around the title alone would sit inside a card that is,
   visibly, the thing being chosen. */
.search-result:focus-within {
  border-color: var(--tint, var(--color-accent));
  box-shadow: 0 0 0 3px var(--tint-soft, var(--color-accent-soft));
}

.search-result-name a:focus-visible {
  outline: none;
}

.search-result-name a::after {
  content: "";
  position: absolute;
  inset: 0;
}

.search-result-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background-color: var(--tint-soft, var(--color-surface-hover));
}

/* contain, never cover: these are the resources' own square icons and cropping
   one to fill a box is how a logo loses its edges. */
.search-result-icon img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.search-result-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.search-result-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
}

.search-result-name {
  margin: 0;
  font-size: var(--text-md);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  overflow-wrap: anywhere;
}

.search-result-name a {
  color: inherit;
  transition: color var(--transition-fast);
}

.search-result:hover .search-result-name a {
  color: var(--tint, var(--color-accent));
}

/* The type label — "Mod", "Shader", "Guide". This is the whole of what keeps a
   guide distinguishable from a resource in a mixed list, so it stays legible
   at the smaller size a compact row needs. */
.search-result-badge {
  flex: 0 0 auto;
  padding: 0.2rem 0.55rem;
  font-size: 0.6875rem;
}

.search-result-desc {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-text-muted);
  /* Two lines, and the PHP has already cut on a word boundary — the clamp is
     the backstop for a narrow column, not the primary trim. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Loaders, versions, category: the facts a reader scans for, as quiet pills in
   one row. Neutral on purpose — the badge above already carries the category's
   colour, and a row of coloured pills would compete with it. */
.search-result-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  margin: var(--space-1) 0 0;
}

.search-result-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0.2rem 0.6rem;
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.4;
  color: var(--color-text-muted);
  background-color: var(--color-surface-hover);
  border-radius: var(--radius-pill);
  white-space: nowrap;
}

.search-result-tag svg {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  color: var(--color-text-faint);
}

/* "+4 more": a count of loaders not shown, drawn as a gap rather than a fact. */
.search-result-tag-more {
  background-color: transparent;
  border: 1px dashed var(--color-border-strong);
  color: var(--color-text-faint);
}

/* The far end of the row: the download figure the resource page itself shows,
   and the arrow. A guide has no figure and shows only the arrow. */
.search-result-side {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

.search-result-stat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 6.5rem;
}

.search-result-stat-value {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-base);
  font-weight: 700;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

.search-result-stat-value svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--color-text-faint);
}

.search-result-stat-label {
  padding-left: 22px;
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

.search-result-arrow {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  color: var(--color-text-faint);
  transition: transform var(--transition-fast), color var(--transition-fast);
}

.search-result-arrow svg {
  width: 100%;
  height: 100%;
  display: block;
}

.search-result:hover .search-result-arrow,
.search-result:focus-within .search-result-arrow {
  transform: translateX(3px);
  color: var(--tint, var(--color-accent));
}

/* ---------- Grid layout ----------
   The same cards, stacked: icon, then title and badge, blurb, pills, and the
   figure in a footer. Only from 640px — narrower than that, one column of rows
   already is the grid, and the switch is hidden. */
@media (min-width: 640px) {
  .search-page-list.is-grid {
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: var(--space-4);
  }

  .search-page-list.is-grid .search-result {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
    padding: var(--space-5);
  }

  .search-page-list.is-grid .search-result-body {
    flex: 1 1 auto;
    gap: var(--space-2);
  }

  .search-page-list.is-grid .search-result-desc {
    -webkit-line-clamp: 3;
  }

  .search-page-list.is-grid .search-result-side {
    justify-content: space-between;
    margin-top: auto;
    padding-top: var(--space-3);
    border-top: 1px solid var(--color-border);
  }

  .search-page-list.is-grid .search-result-stat {
    flex-direction: row;
    align-items: baseline;
    gap: var(--space-1);
    min-width: 0;
  }

  .search-page-list.is-grid .search-result-stat-label {
    padding-left: 0;
  }

  /* Always at the far end of the footer, so a guide — which has no figure to
     sit beside — keeps its arrow where every other card's is. */
  .search-page-list.is-grid .search-result-arrow {
    margin-left: auto;
  }
}

/* ---------- Pagination ----------
   Real links: a page is a URL, so it can be opened in a new tab or copied. The
   script handles a plain click in place. */
.search-pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-8);
}

.search-page-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 40px;
  padding-inline: var(--space-3);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  transition: background-color var(--transition-fast), border-color var(--transition-fast);
}

.search-page-btn svg {
  width: 16px;
  height: 16px;
}

a.search-page-btn:hover {
  background-color: var(--color-surface-hover);
  border-color: var(--color-text-faint);
}

.search-page-btn[aria-current="page"] {
  color: var(--color-on-accent);
  background-color: var(--color-accent);
  border-color: var(--color-accent);
}

.search-page-btn.is-disabled {
  opacity: 0.4;
}

.search-page-gap {
  padding-inline: var(--space-1);
  color: var(--color-text-faint);
}

/* ---------- Empty state ----------
   Served as markup, not written by the script, so a dead-end query still ends
   in somewhere to go rather than in one grey sentence. */
.search-page-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-12) var(--space-6);
  text-align: center;
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
}

.search-page-empty-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-pill);
  background-color: var(--color-accent-soft);
  color: var(--color-accent);
}

.search-page-empty-icon svg {
  width: 20px;
  height: 20px;
  display: block;
}

.search-page-empty-title {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 700;
  overflow-wrap: anywhere;
}

.search-page-empty-text {
  max-width: 40ch;
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.search-page-empty-clear {
  margin-top: var(--space-1);
}

.search-page-empty-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

/* ---------- Help line ---------- */
.search-page-help {
  margin: var(--space-8) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-faint);
}

.search-page-help a {
  font-weight: 600;
  color: var(--color-accent);
}

.search-page-help a:hover {
  text-decoration: underline;
}

/* ---------- The filter drawer (below 1000px) ----------
   The same column, off-canvas: it slides in from the left over a scrim,
   scrolls its own body, and closes from its head or its foot. Nothing about
   the groups changes between the two states — only where the column is. The
   drawer sits above the sticky header (z-index 100) and below the search
   overlay (200). */
@media (max-width: 999.98px) {
  .search-filters-open {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 40px;
    padding: 0 var(--space-4);
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--color-text);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-sm);
    transition: background-color var(--transition-fast), border-color var(--transition-fast);
  }

  .search-filters-open:hover {
    background-color: var(--color-surface-hover);
    border-color: var(--color-text-faint);
  }

  .search-filters-open:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 1px;
  }

  .search-filters-open svg {
    width: 16px;
    height: 16px;
    color: var(--color-text-faint);
  }

  .search-filters-open-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding-inline: 6px;
    font-size: var(--text-xs);
    font-weight: 700;
    color: var(--color-on-accent);
    background-color: var(--color-accent);
    border-radius: var(--radius-pill);
  }

  .search-filters {
    position: fixed;
    z-index: 150;
    top: 0;
    bottom: 0;
    left: 0;
    width: min(88vw, 360px);
    border: 0;
    border-right: 1px solid var(--color-border);
    border-radius: 0;
    box-shadow: var(--shadow-md);
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform var(--transition-base), visibility var(--transition-base);
  }

  .search-filters.is-open {
    transform: none;
    visibility: visible;
  }

  .search-filters-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }

  .search-filters-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    margin-right: calc(var(--space-2) * -1);
    border-radius: var(--radius-xs);
    color: var(--color-text-muted);
  }

  .search-filters-close:hover {
    color: var(--color-text);
    background-color: var(--color-surface-hover);
  }

  .search-filters-close:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 1px;
  }

  .search-filters-close svg {
    width: 18px;
    height: 18px;
  }

  .search-filters-foot {
    display: block;
    padding: var(--space-3) var(--space-5);
    border-top: 1px solid var(--color-border);
    background-color: var(--color-surface);
  }

  .search-scrim {
    position: fixed;
    inset: 0;
    z-index: 149;
    background-color: var(--color-scrim);
  }

  /* The page behind the drawer holds still. Named for the STATE, not after
     .search-filters-open — that is the button's class, and on <body> it would
     restyle the whole document as a button. */
  body.search-drawer-open {
    overflow: hidden;
  }
}

/* No grid on a phone: one column of rows already is the grid. */
@media (max-width: 639.98px) {
  .search-view {
    display: none;
  }
}

@media (max-width: 560px) {
  .search-page-bar {
    padding-left: var(--space-4);
    gap: var(--space-2);
  }

  .search-page-submit {
    padding-inline: var(--space-4);
  }

  /* Beats `.page-header input`'s size: 16px is the largest a phone keyboard
     leaves alone (smaller and iOS zooms the page on focus), and the pill has
     no room for 18px beside its button. */
  .search-page-bar input[type="search"] {
    font-size: var(--text-base);
  }

  /* Top-aligned, not centred: a body that has wrapped to three or four lines
     would otherwise float the icon halfway down the card, away from the title
     it belongs to. */
  .search-result {
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4);
  }

  .search-result-icon {
    width: 48px;
    height: 48px;
    margin-top: 2px;
  }

  /* Beside the icon, not under it: a zero basis keeps the text on the icon's
     line (its natural basis is the blurb's full width, which would wrap it
     onto a line of its own) and lets only the figure wrap below. */
  .search-result-body {
    flex: 1 1 0%;
  }

  /* The figure drops under the text, aligned with it past the icon. The whole
     card is the target on a touch screen, so the arrow can go rather than eat
     width from the title — and a guide, which has only the arrow, then has no
     footer at all. */
  .search-result-side {
    flex-basis: 100%;
    padding-left: calc(48px + var(--space-3));
  }

  .search-result-side:not(:has(.search-result-stat)) {
    display: none;
  }

  .search-result-arrow {
    display: none;
  }

  .search-result-stat {
    flex-direction: row;
    align-items: baseline;
    gap: var(--space-1);
    min-width: 0;
  }

  .search-result-stat-label {
    padding-left: 0;
  }

  .search-page-empty {
    padding: var(--space-8) var(--space-4);
  }

  .search-pagination {
    gap: var(--space-1);
  }

  .search-page-btn {
    min-width: 36px;
    height: 36px;
    padding-inline: var(--space-2);
  }
}

/* A CLASS THAT SETS `display` DEFEATS THE `hidden` ATTRIBUTE.
   The user agent hides [hidden] with `display: none`, but that is a bare
   attribute selector and loses to any class selector that sets display of its
   own. .search-result is display:flex, so hiding a non-matching result did
   nothing: the count read "15 results" above all 46 rows, still visible.
   Every element this page toggles with `hidden` is therefore listed here,
   whether or not it sets a display today, so adding one later cannot
   reintroduce the bug. */
.search-filters[hidden],
.search-facet-body[hidden],
.search-facet-option[hidden],
.search-facet-more[hidden],
.search-facet-none[hidden],
.search-filters-clear[hidden],
.search-filters-open-count[hidden],
.search-toolbar-controls[hidden],
.search-active[hidden],
.search-result[hidden],
.search-page-count[hidden],
.search-page-empty[hidden],
.search-page-empty-clear[hidden],
.search-pagination[hidden],
.search-scrim[hidden] {
  display: none;
}

/* Previous/next at the foot of a guide, and the publisher credit in its header.
   Both replace things the hand-written guides carried before they were imported
   into the CMS — derived here rather than stored, so they cannot go stale. */
.guide-nav {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-12);
  /* No rule above the pair: the CTA that precedes them is already a bordered
     box and the links below are bordered cards, so a hairline between the two
     only added a third line to a join that was never ambiguous. The spacing
     is kept exactly as it was. */
  padding-top: var(--space-8);
}

.guide-nav-link {
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 4px);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  color: inherit;
  text-decoration: none;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.guide-nav-link:hover,
.guide-nav-link:focus-visible {
  border-color: var(--color-border-strong);
  background-color: var(--color-surface-hover);
}

/* When there is only a next link it should sit on the right, where "next"
   belongs, rather than sliding into the previous slot. */
.guide-nav-next:only-child {
  grid-column: -2;
  text-align: right;
}

.guide-nav-label {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-faint);
}

.guide-nav-title {
  font-weight: 650;
  line-height: 1.3;
}

.guide-nav-all {
  margin-top: var(--space-5);
  text-align: center;
}

.guide-byline {
  font-weight: 650;
  color: var(--color-text-muted);
}

@media (max-width: 720px) {
  .guide-nav {
    margin-top: var(--space-8);
  }

  .guide-nav-next:only-child {
    grid-column: auto;
    text-align: left;
  }
}

/* ==========================================================================
   The contact page (/contact/)
   Everything here is scoped to .contact-*; the page reuses .page-header,
   .container, .card tokens, .btn and .form-field rather than redefining them.
   ========================================================================== */

/* ---------- Hero ----------
   Compact by design: a heading and one paragraph, then the curve. Matching the
   specificity of the .page-header.page-header rule in main.css that reserves
   the curve's depth — this file loads after it, so the later rule wins. */
.page-header.contact-hero {
  padding-top: var(--space-8);
  padding-bottom: calc(var(--space-8) + var(--ink-curve));
}

.contact-hero-copy {
  max-width: 62ch;
}

.contact-hero h1 {
  margin-top: 0;
}

/* THE DECOR CLIPS ITSELF, NOT THE HERO.
   Putting overflow:hidden on .page-header would also clip the curve's own -1px
   overlap, which exists to close a hairline seam. The cubes therefore live in
   their own absolutely positioned, clipped layer instead. It must state
   position:absolute explicitly: main.css gives every direct child of a hero
   position:relative so nothing falls behind the curve. */
.contact-hero-decor {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  /* The mark's green, at the weight of a watermark. */
  color: #3fa860;
  opacity: 0.15;
}

.contact-hero-decor svg {
  position: absolute;
  top: 50%;
  right: -40px;
  width: 420px;
  height: 300px;
  transform: translateY(-50%);
}

/* Below the two-column breakpoint the copy runs the full width of the hero, so
   a watermark behind it is just noise. */
@media (max-width: 899px) {
  .contact-hero-decor {
    display: none;
  }
}

/* ---------- Layout ---------- */
.contact-section {
  padding-block: var(--space-12) var(--space-16);
}

.contact-layout {
  display: grid;
  /* minmax(0, …) so a long address or placeholder cannot widen a track past
     the container and take the page sideways with it. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-6);
  /* stretch, not start: the two cards are a pair and a short left column
     hanging above the foot of the form reads as an unfinished panel. */
  align-items: stretch;
}

/* Information first, form second — the same order the columns stack in, so the
   mobile reading order needs no reversal. */
@media (min-width: 900px) {
  .contact-layout {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: var(--space-6);
  }
}

.contact-card {
  padding: var(--space-8);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background-color: var(--color-surface);
}

/* ---------- Information column ---------- */
.contact-info-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: var(--radius-md);
  background-color: var(--color-accent-soft);
  color: var(--color-accent);
}

.contact-info-icon svg {
  width: 22px;
  height: 22px;
}

.contact-info-title {
  margin-top: var(--space-5);
  font-size: var(--text-xl);
}

.contact-info-text {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* The address, generated into the CMS region above it. The arrow is drawn here
   rather than emitted by the generator, so app/indexes.php only ever has to
   write the link itself. */
.contact-email {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-5);
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--color-accent);
  /* An address is one token; it may break, but never off the card. */
  overflow-wrap: anywhere;
}

.contact-email::after {
  content: "\2192";
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  background-color: var(--color-accent-soft);
  font-size: var(--text-sm);
  transition: transform var(--transition-fast), background-color var(--transition-fast);
}

.contact-email:hover::after,
.contact-email:focus-visible::after {
  transform: translateX(3px);
  background-color: var(--color-accent-border);
}

.contact-points {
  list-style: none;
  margin: var(--space-6) 0 0;
  padding: var(--space-6) 0 0;
  border-top: 1px solid var(--color-border);
  display: grid;
  gap: var(--space-5);
}

.contact-point {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

.contact-point-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-xs);
  background-color: var(--color-accent-soft);
  color: var(--color-accent);
}

.contact-point-icon svg {
  width: 16px;
  height: 16px;
}

.contact-point-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.contact-point-body strong {
  color: var(--color-text);
  font-weight: 700;
}

/* ---------- Form column ---------- */
.contact-form-title {
  font-size: var(--text-xl);
}

.contact-form-intro {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.contact-form {
  margin-top: var(--space-6);
}

/* Taller, softer fields than the shared .form-field default. Scoped to this
   form so the shared component keeps its own proportions everywhere else. */
.contact-form .form-field {
  margin-bottom: var(--space-4);
}

.contact-form .form-field label {
  font-size: var(--text-sm);
  font-weight: 700;
}

.form-optional {
  font-weight: 500;
  color: var(--color-text-faint);
}

.contact-form .form-field input,
.contact-form .form-field textarea {
  padding: var(--space-3) var(--space-4);
  min-height: 48px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.contact-form .form-field textarea {
  min-height: 150px;
  resize: vertical;
  line-height: 1.6;
}

.contact-form .form-field input::placeholder,
.contact-form .form-field textarea::placeholder {
  color: var(--color-text-faint);
}

/* A ring rather than the shared outline: it reads as part of the field at this
   radius, where a hard outline offset sits away from the rounded corner. */
.contact-form .form-field input:focus,
.contact-form .form-field textarea:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-soft);
}

.contact-submit {
  margin-top: var(--space-2);
}

.contact-submit svg {
  width: 16px;
  height: 16px;
}

.contact-form-note {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-top: var(--space-4);
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

.contact-form-note svg {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  margin-top: 2px;
}

/* ---------- Suggest-a-resource callout ---------- */
.contact-suggest {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  margin-top: var(--space-6);
  padding: var(--space-6) var(--space-8);
  border: 1px solid var(--color-accent-border);
  border-radius: var(--radius-lg);
  background-color: var(--color-accent-soft);
}

.contact-suggest-art {
  flex: 0 0 auto;
  display: inline-flex;
  width: 84px;
  height: 84px;
  color: var(--color-accent);
}

.contact-suggest-art svg {
  width: 100%;
  height: 100%;
}

.contact-suggest-body {
  flex: 1 1 auto;
  min-width: 0;
}

.contact-suggest-title {
  font-size: var(--text-lg);
}

.contact-suggest-body p {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  max-width: 60ch;
}

/* The one outlined-accent button on the site. Scoped here rather than added to
   the shared .btn family, which has no accent outline variant and does not
   need one for a single callout. */
.contact-suggest-cta {
  flex: 0 0 auto;
  background-color: var(--color-surface);
  border-color: var(--color-accent-border);
  color: var(--color-accent);
}

.contact-suggest-cta:hover {
  border-color: var(--color-accent);
  background-color: var(--color-surface);
}

.contact-suggest-cta svg {
  width: 15px;
  height: 15px;
  transition: transform var(--transition-fast);
}

.contact-suggest-cta:hover svg {
  transform: translateX(3px);
}

@media (max-width: 719px) {
  .contact-card {
    padding: var(--space-5);
  }

  .contact-section {
    padding-block: var(--space-8) var(--space-12);
  }

  /* Art, then copy, then a full-width action — the callout reads top to bottom
     rather than squeezing three columns into a phone. */
  .contact-suggest {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
    padding: var(--space-5);
  }

  .contact-suggest-art {
    width: 64px;
    height: 64px;
  }

  .contact-suggest-cta {
    width: 100%;
  }

  .contact-submit {
    width: 100%;
  }
}

/* ==========================================================================
   THE SUNKEN HERO ARTWORK
   /about/, /guides/ and every published guide page

   One treatment, three page types. It was built for /about/ and is now shared
   verbatim rather than copied: the gradients are the part that took the most
   tuning, and three copies of them would drift apart on the first edit.

   WHAT IT DOES. The hero's picture stops being a framed image beside the copy
   and becomes the right-hand side of the dark band. It runs to the very top of
   the PAGE, behind the sticky menu and the breadcrumb row, and off the right of
   the viewport, so three of its four edges bleed rather than fade. Only the left
   and the bottom are dissolves, and both are long. The menu bar over it stops
   painting its own black and lets the picture read through, then fades back in
   over the first 56px of scroll so the links never sit on white content.

   WHAT EACH PAGE HAS TO PROVIDE. Two things, and they are the only per-page
   values below:

     --hero-art-width   how far across the band the picture reaches
     a cap on the copy  so the text ends before the picture starts to appear

   The two are ONE decision, not two. The picture's flat-black head ends at
   `100% - 0.82 x --hero-art-width` in viewport terms, so a wider picture pushes
   that end leftward and a page whose hero copy runs wide must take a narrower
   picture. /about/ carries a short lede and a two-word heading, so it takes 72%.
   Both guides pages carry a longer heading, a wider lede and a meta row, so they
   take 64% and the picture starts a little further right. That is the whole
   difference between them.

   MARKUP CONTRACT. The picture must be a DIRECT CHILD of the hero, not a cell
   inside its .container — a cell can never reach the viewport edge. All three
   pages were changed to emit it that way; for guide articles that is
   bravith_render_guide_page() in app/guides.php.

   One known limitation: a figure caption inside .guide-hero-art would be
   clipped by the box's own overflow. No published guide has one, and a caption
   has no place in a backdrop, but if one is ever wanted it needs somewhere else
   to live.
   ========================================================================== */

/* ---------- The band ----------
   Sized by its copy at every ordinary width. See the note in the /about/
   section on why the vw term is gentle and the floor sits under what the copy
   needs. Below 1024 the bottom padding is dropped because the artwork sits in
   flow at the foot of the band and reserves the curve's room itself. */
.page-header.about-hero,
.guide-hero.guide-hero,
.breadcrumbs + main > .category-hero.guides-masthead {
  padding-top: var(--space-8);
  padding-bottom: 0;
}

/* ---------- The artwork ----------
   Below 1024 it is a full-bleed banner at the foot of the hero: in flow after
   the copy, fading in from the black above it, and tall enough to run on under
   the curve so the white rises into the picture instead of meeting a straight
   edge.

   IT IS A SUPPORTING ELEMENT, NOT THE SUBJECT. The band was half picture at
   every width, which made the hero read as an image banner with a headline on
   it. The height here, and the box at 1024 below, are both deliberately modest:
   the heading leads, the artwork answers it.

   z-index: 0 is load-bearing. main.css gives every direct child of a hero
   position: relative and z-index: 1 so nothing falls behind the curve; this is
   the one thing that MUST fall behind it. The curve is the hero's ::after,
   later in tree order at the same level, so it paints over the picture, and the
   copy (still at 1) paints over both. */
.about-hero-art,
.guides-masthead-visual,
.guide-hero-art {
  z-index: 0;
  margin: var(--space-8) 0 0;
  height: calc(clamp(150px, 26vw, 240px) + var(--ink-curve));
  overflow: hidden;
  pointer-events: none;
}

/* The last three declarations undo the framed-image treatment the two guides
   pictures carried when they were cells in a grid: a border, a radius and a
   fixed aspect box are all wrong for something that has no visible edges. */
.about-hero-art img,
.guides-masthead-visual img,
.guide-hero-art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 58%;
  /* A touch under full brightness so the sky sits in the black instead of
     glaring out of it. */
  filter: brightness(0.9);
  aspect-ratio: auto;
  border: 0;
  border-radius: 0;
}

/* The fades are stated as percentages of the box, so shrinking the box would
   scale them with it and dissolve the whole picture. They are therefore
   shorter here than a taller banner would need: the top fade clears by 46%,
   which keeps a real picture in a short band. */
.about-hero-art::after,
.guides-masthead-visual::after,
.guide-hero-art::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to bottom, rgba(14, 12, 11, 1) 0%, rgba(14, 12, 11, 0.5) 18%, rgba(14, 12, 11, 0) 46%),
    linear-gradient(to right, rgba(14, 12, 11, 0.5) 0%, rgba(14, 12, 11, 0) 26%, rgba(14, 12, 11, 0) 74%, rgba(14, 12, 11, 0.5) 100%);
}

/* The guides hub's picture was the second cell of a two-column grid. With the
   picture gone from the container the grid has one child, so it stops being a
   grid; the copy takes its own cap below instead of half the container. Stated
   with the parent class because the two-column rule lives in responsive.css,
   which loads last. */
.guides-masthead .guides-masthead-layout {
  display: block;
}

@media (min-width: 1024px) {
  /* ---------- What each page contributes ----------
     See the note at the head of this section: width and copy cap are one
     decision. /about/ has the shortest hero copy and takes the widest picture. */
  body:has(.about-hero) {
    --hero-art-width: min(72%, 1600px);
  }

  /* The hub carries a two-word heading and a 44ch lede, so it sits between the
     other two. Measured: its widest line ends at 419px at 1024 against a head
     ending at 453, and at 711 against 849 at 1920. 72% would leave 1px at 1024
     and is the reason this is not simply /about/'s number. */
  body:has(.guides-masthead) {
    --hero-art-width: min(68%, 1500px);
  }

  /* Guide articles have the widest hero copy on the site: a heading that often
     wraps to two or three lines, a 54ch lede and a meta row. Their widest line
     ends between 804 and 838 at 1920, against a head ending at 912, and the
     lede's own measure — not the cap below — is what sets that. 68% would cut
     the margin to 11px. */
  body:has(.guide-hero) {
    --hero-art-width: min(64%, 1400px);
  }

  .guides-masthead-copy {
    max-width: 42%;
  }

  /* Beats the 68ch measure main.css gives this block: a character count is the
     right cap for a column of text and the wrong one for a hero that has to
     leave room for a picture beside it. */
  .guide-hero .guide-header {
    max-width: 42%;
  }

  /* THE BAND IS SIZED BY ITS COPY. See the /about/ section for why the vw term
     is only 17 and why the floor sits under what the copy needs. */
  .page-header.about-hero,
  .guide-hero.guide-hero,
  .breadcrumbs + main > .category-hero.guides-masthead {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: clamp(290px, 17vw, 380px);
    padding-top: var(--space-5);
    padding-bottom: calc(var(--space-6) + var(--ink-curve));
  }

  /* THE PICTURE FILLS THE BLACK REGION; IT DOES NOT SIT INSIDE IT.

     -9.5rem is 152px against the 147px of sticky nav (73) plus breadcrumb row
     (74), so the box clears the top of the page. IT OVERSHOOTS BY 5px ON
     PURPOSE: undershooting would leave a black gap under the menu, in plain
     sight, while the overshoot hides behind the menu where nothing can see it.

     WHY THE WIDTH IS A PERCENTAGE FIRST AND A CAP SECOND. An earlier version
     read min(56%, 780px), and 780px is 40% of a 1950px monitor — so on a wide
     screen the picture stopped growing and became a strip pinned to the right
     edge while the band beside it went on widening. The caps now sit far out of
     the way and only bound how far the 1280px-wide sources are upscaled.

     Nothing clips any of this: z-index: 0 puts the box over the breadcrumb
     strip's background (an unpositioned block) but under the header (sticky,
     z-index 100), and no hero sets overflow. bottom: 0 reaches the foot of the
     band, where the curve — the hero's ::after, later in tree order at the same
     level — still paints over it. */
  .about-hero-art,
  .guides-masthead-visual,
  .guide-hero-art {
    position: absolute;
    top: -9.5rem;
    right: 0;
    bottom: 0;
    width: var(--hero-art-width);
    height: auto;
    margin: 0;
  }

  .about-hero-art img,
  .guides-masthead-visual img,
  .guide-hero-art img {
    object-position: 50% 50%;
  }

  /* Two long dissolves and two settling veils, in that order of importance.

     THE COPY SITS ON THE PICTURE, IT DOES NOT WAIT FOR IT TO END. Earlier
     versions held a long flat-black head so that no letter ever overlapped a
     visible pixel, and the cost was a band of dead black between the last word
     and the point where the picture became perceptible: 115 to 170px at 1920.
     The reference does the opposite — its copy runs over the dissolving edge —
     and that is what makes the picture look like it belongs to the text rather
     than sitting across the hero from it.

     So the head is now 8%, barely more than the gap between the box's left edge
     and the container's, and the roll-off reaches the end of the widest line at
     around 0.85 to 0.9. Ten to fifteen per cent of a picture that is itself
     under the bottom dissolve is a few luminance units over a near-black ground:
     it reads as the picture arriving, never as texture behind the words. The
     worst case in the set is /about/'s note line at 1024, where the alpha falls
     to about 0.53 — verified at 18% visible after the bottom dissolve, which
     still leaves white text at roughly 14:1.

     WHAT FOLLOWS THE HEAD IS EASED, NOT LINEAR. A first attempt dropped straight
     from opaque to 0.55 over a seventh of the width, and against the bright sky
     in that corner the result read as a vertical edge — precisely the rectangle
     this treatment exists to avoid. The first stop after the head is therefore
     small (0.15 over 6% of the box, about 83px at 1920) and only then does the
     ramp accelerate. Total ramp length is unchanged; it starts earlier and ends
     in the same place.

     The bottom dissolve clears at 38%, so the picture fades out inside the band
     instead of running into the curve. The right is a veil, not a fade.

     THE TOP VEIL CARRIES THE NAV'S LEGIBILITY, which is why it is heavier than
     the others and why it must not be shortened. The menu bar below no longer
     paints anything over the picture, so nothing else darkens the strip the
     links sit on.

     IT WAS ALSO TOO BRIGHT AGAINST THE REFERENCE. At 0.8/0.55/0.3 the strip
     behind the links measured 84 to 102 in luminance and read as blown out
     beside the reference's own menu, which keeps the picture muted up there.
     The stops are now 0.88/0.72/0.45, which lands it near 70: still plainly the
     picture rather than a black bar, and closer to what the reference does.

     It is a veil rather than a fade all the same — long enough to be read as a
     fade and it would throw away the bleed behind the menu that this whole
     arrangement exists to produce. */
  .about-hero-art::after,
  .guides-masthead-visual::after,
  .guide-hero-art::after {
    background-image:
      linear-gradient(to right, rgba(14, 12, 11, 1) 0%, rgba(14, 12, 11, 1) 8%, rgba(14, 12, 11, 0.85) 14%, rgba(14, 12, 11, 0.6) 20%, rgba(14, 12, 11, 0.3) 27%, rgba(14, 12, 11, 0.1) 32%, rgba(14, 12, 11, 0) 36%),
      linear-gradient(to top, rgba(14, 12, 11, 1) 0%, rgba(14, 12, 11, 0.72) 10%, rgba(14, 12, 11, 0.28) 24%, rgba(14, 12, 11, 0) 38%),
      linear-gradient(to bottom, rgba(14, 12, 11, 0.88) 0%, rgba(14, 12, 11, 0.72) 7%, rgba(14, 12, 11, 0.45) 13%, rgba(14, 12, 11, 0.2) 20%, rgba(14, 12, 11, 0) 32%),
      linear-gradient(to left, rgba(14, 12, 11, 0.2) 0%, rgba(14, 12, 11, 0) 10%);
  }

  /* ---------- The menu bar over these heroes ----------
     THE ONLY RULES IN THIS FILE THAT TOUCH A GLOBAL COMPONENT. Each is scoped by
     body:has(...) to a page that actually has one of these heroes, so they
     cannot reach anywhere else; see the note on :has() further up this file.

     THE PROBLEM. The artwork runs to the top of the page, behind the bar. The
     bar painted rgba(14,12,11,0.86) over it, and at 14% transmission a bright
     sky pixel (RGB 200) comes out at RGB 28 against a ground of RGB 14 — which
     is why the menu read as a band of flat black cutting the picture off 73px
     down.

     TWO THINGS MAKE "JUST MAKE IT TRANSPARENT" WRONG.

     First, body is white. The bar is black only because it paints --ink-bg
     itself, so a transparent bar shows the white page through on the LEFT,
     beside the logo, where the artwork does not reach. Hence the gradient: the
     bar stays opaque over the copy's side and hands over to nothing over the
     picture's side.

     WHERE IT HANDS OVER IS THE WHOLE TRICK. The handover happens between 15px
     and 75px inside the artwork's left edge — inside the artwork's own flat
     black head, which is 18% of its width and so at least 118px even at 1024.
     Both sides of the handover are therefore #0e0c0b either way, and the bar
     contributes NOTHING wherever any of the picture is visible. That is what
     leaves no step in brightness at the bar's bottom edge: the picture is
     continuous from the top of the page down. Anchoring the stops to
     100% - var(--hero-art-width) rather than to a fixed percentage is what keeps
     that true at every width and on every one of these pages, whose pictures are
     not all the same width.

     Second, the bar is sticky, so it ends up over white content. A transparent
     bar there is white links on white. The ::before below is the solid bar,
     faded in over the first 56px of scroll — short enough that it is solid again
     well before it can reach the bright part of the picture.

     backdrop-filter is off here on purpose: an 8px blur behind the bar with a
     sharp picture below it draws exactly the horizontal seam all of the above
     exists to avoid. */
  @supports (animation-timeline: scroll()) {
    body:has(.about-hero) .site-header,
    body:has(.guides-masthead) .site-header,
    body:has(.guide-hero) .site-header {
      background-color: transparent;
      background-image: linear-gradient(to right,
        #0e0c0b 0,
        #0e0c0b calc(100% - var(--hero-art-width) + 15px),
        rgba(14, 12, 11, 0) calc(100% - var(--hero-art-width) + 75px));
      backdrop-filter: none;
    }

    /* Opacity of an overlay, not the bar's own background-image: one trivially
       interpolatable property, and the gradient's calc() and var() stay out of
       the keyframes entirely. */
    body:has(.about-hero) .site-header::before,
    body:has(.guides-masthead) .site-header::before,
    body:has(.guide-hero) .site-header::before {
      content: "";
      position: absolute;
      inset: 0;
      background-color: var(--ink-bg);
      opacity: 0;
      animation: bravith-hero-header linear both;
      animation-timeline: scroll(root block);
      animation-range: 0px 56px;
    }

    /* An absolutely positioned ::before paints above non-positioned inline
       content, so without this the overlay covers the logo and the links. */
    body:has(.about-hero) .site-header > .container,
    body:has(.about-hero) .site-header > .mobile-nav,
    body:has(.guides-masthead) .site-header > .container,
    body:has(.guides-masthead) .site-header > .mobile-nav,
    body:has(.guide-hero) .site-header > .container,
    body:has(.guide-hero) .site-header > .mobile-nav {
      position: relative;
      z-index: 1;
    }
  }
}

/* Declared outside the media query and the @supports block: a keyframes rule is
   only ever consulted by the animation that names it, and that animation is
   inside both. */
@keyframes bravith-hero-header {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* The global reset forces animation-duration: 0.01ms !important onto every
   element, and what that does to a progress-based timeline is not worth relying
   on. Pinning the solid bar is the known-safe outcome, and it is the same bar
   every other page has. */
@media (prefers-reduced-motion: reduce) {
  body:has(.about-hero) .site-header::before,
  body:has(.guides-masthead) .site-header::before,
  body:has(.guide-hero) .site-header::before {
    animation: none;
    opacity: 1;
  }
}

/* ==========================================================================
   The about page (/about/)

   Everything here is scoped to .about-*; the page reuses .page-header, the ink
   band, .container, .section, .eyebrow, .btn and the shared tokens rather
   than redefining them.

   ONE EXCEPTION, AND IT IS STILL SCOPED. The hero's artwork runs up behind the
   sticky menu bar, so a handful of rules at the foot of this section style
   .site-header — the only global component this file touches. Every one of them
   is qualified by body:has(.about-hero), so they cannot reach a page that has
   no about hero, and they sit inside @supports so an engine without
   scroll-driven animation keeps the ordinary bar. Nothing in this section can
   reach another page.

   AN EDITORIAL PAGE, NOT A DASHBOARD. An earlier version put nearly every
   piece of information inside a bordered white card. This one uses the page
   itself: copy sits on the ground, artwork runs to the viewport edge, the
   four "what you'll find" entries are an index with hairlines rather than
   tiles, and the one panel that remains (the closing callout) is the same
   one the contact page ends with.

   THE PAGE IS DARK ONCE, LIGHT AFTERWARDS, WITH ONE DARK PLATE IN BETWEEN.
   The hero belongs to the ink band declared in main.css and gets its curve
   from there. Below it the page is ordinary light content, except for the
   "Our approach" plate: a rounded panel inside the container painted in the
   band's own palette. It is a panel within the page, not a second band, so
   it does not join the list of band openings in main.css and instead
   declares the few ink tokens it needs on itself.

   ARTWORK SINKS INTO THE DARK; IT IS NOT FRAMED BY IT. The hero and the
   approach plate both lay a photograph under a stack of gradients painted in
   the surface's own colour, so the picture is strongest at the far edge and
   has dissolved into the ground before it reaches the copy. Gradients rather
   than a mask because the ground is one flat, known colour: the result is
   pixel-identical, and it needs no mask support and no compositing rules.
   The stops spell out the ink colour (14, 12, 11) at zero alpha instead of
   `transparent`, so no browser can interpolate through grey on the way.
   ========================================================================== */

/* ---------- Hero ----------
   The band's own sizing, the artwork and the menu bar over it are NOT here:
   they are shared with /guides/ and the guide pages, and live in THE SUNKEN
   HERO ARTWORK above. What is left here is what only this page has. */

.about-hero-copy {
  max-width: 40rem;
}

.about-hero h1 {
  margin-top: var(--space-3);
  font-size: clamp(2.5rem, 1.7rem + 3vw, 3.75rem);
  line-height: 1.05;
  letter-spacing: -0.025em;
}

.about-hero .lede {
  max-width: 44ch;
}

/* One short factual line under the lede, with a coral rule before it: the
   third level of the hero's typography, in place of the row of tiles that
   used to sit here. */
.about-hero-note {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-6);
  font-size: var(--text-sm);
  color: var(--color-text-faint);
}

.about-hero-note::before {
  content: "";
  flex: 0 0 auto;
  width: 28px;
  height: 2px;
  background-color: var(--color-accent);
}

/* ---------- Our story and Who it's for ----------
   Two mirrored splits, both COMPOSED INSIDE THE CONTAINER.

   They were built as full-width grids whose picture track ran from the middle
   of the page to the viewport edge — 720 x 518 at 1440. At that size the
   picture stopped supporting the text and became the section: a decorative
   strip with some copy parked beside it. The bleed is gone. Each section is
   now an ordinary .section-shaped block whose .container is the grid, so the
   copy and the picture sit on the same measure as every other section on the
   page and the picture is sized as an illustration rather than a backdrop.

   The grid lives on the .container rather than on an extra wrapper element:
   the section has exactly one child and giving it a class of its own would
   only name the same box twice. */
.about-story,
.about-who {
  padding-top: var(--space-12);
}

.about-story > .container,
.about-who > .container {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-8);
  align-items: center;
}

.about-story-copy,
.about-who-copy {
  min-width: 0;
  max-width: 60ch;
}

.about-story h2,
.about-who h2,
.about-index h2,
.about-approach h2 {
  margin-top: var(--space-3);
  font-size: var(--text-2xl);
  letter-spacing: -0.015em;
}

.about-story p,
.about-who p {
  margin-top: var(--space-4);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
}

.about-story p a {
  color: var(--color-accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--color-accent-border);
}

.about-story p a:hover {
  text-decoration-color: var(--color-accent);
}

.about-story-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

.about-story-actions svg {
  width: 15px;
  height: 15px;
}

.about-story-art,
.about-who-art {
  min-width: 0;
  margin: 0;
}

/* The picture leads in the source order of "Who it's for" so that it can take
   the left track on a desktop. On one column that would open the section on an
   unexplained image, so it is ordered after the copy instead. */
.about-who-art {
  order: 2;
}

/* Rounded on every side now that no edge is the viewport's. No border, no
   shadow and no panel around it: it is a picture on the page, not a card. */
.about-story-art img,
.about-who-art img {
  width: 100%;
  height: clamp(200px, 42vw, 300px);
  object-fit: cover;
  border-radius: var(--radius-lg);
}

.about-story-art img {
  object-position: 50% 60%;
}

/* The standing stones sit left of centre in the frame. */
.about-who-art img {
  object-position: 42% 50%;
}

/* Two audiences as a definition list with hairlines, not two tiles. */
.about-who-levels {
  margin-top: var(--space-6);
}

.about-who-levels > div {
  padding-block: var(--space-4);
  border-top: 1px solid var(--color-border-strong);
}

.about-who-levels dt {
  font-weight: 700;
  color: var(--color-text);
}

.about-who-levels dd {
  margin-top: var(--space-1);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
}

/* ---------- What you'll find ----------
   Four entries in an index, not four tiles: a hairline over each, a number
   in the accent, a title and a line of copy, set on the page itself. */
.about-index-head {
  max-width: 40rem;
}

.about-index-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6) var(--space-8);
  margin-top: var(--space-8);
}

.about-index-list li {
  min-width: 0;
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border-strong);
}

.about-index-num {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--color-accent);
}

.about-index-list h3 {
  margin-top: var(--space-3);
  font-size: var(--text-lg);
  letter-spacing: -0.01em;
}

.about-index-list p {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
}

/* ---------- Our approach ----------
   The one dark surface below the hero: see the note at the top of this
   section. The plate paints the band's colour and declares the band's text
   and border tokens on itself, so the eyebrow, heading and copy inside it
   read exactly as they do in the hero. Below 1024 the artwork is a banner
   across the top of the plate, fading down into the black the copy sits on. */
.about-approach-section {
  padding-block: 0;
}

.about-approach {
  --color-text: var(--ink-text);
  --color-text-muted: var(--ink-text-muted);
  --color-text-faint: var(--ink-text-faint);
  --color-border: var(--ink-border);
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background-color: var(--ink-bg);
  color: var(--ink-text);
}

.about-approach-art {
  position: relative;
  height: clamp(220px, 48vw, 380px);
  overflow: hidden;
}

.about-approach-art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 68% 40%;
}

.about-approach-art::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to top, rgba(14, 12, 11, 1) 0%, rgba(14, 12, 11, 0.6) 26%, rgba(14, 12, 11, 0) 64%);
}

.about-approach-copy {
  position: relative;
  padding: 0 var(--space-6) var(--space-8);
}

.about-approach p {
  margin-top: var(--space-4);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
  max-width: 46ch;
}

.about-approach-list {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

.about-approach-list li {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-sm);
  font-weight: 700;
}

/* The mark's green, lightened to carry on the near-black plate.
   --color-success is tuned for the white page and goes muddy here. */
.about-approach-list svg {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  color: #5cc281;
}

/* ---------- Closing note and callout ----------
   The independence statement is set as a note, not a card: a hairline, an
   eyebrow and one paragraph of small text. The callout after it is the same
   accent-tinted container the contact page closes with, at the same weight,
   so the two pages end the same way. */
.about-note {
  max-width: 70ch;
  padding-top: var(--space-6);
  border-top: 1px solid var(--color-border-strong);
}

.about-note p {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
}

.about-cta {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  margin-top: var(--space-12);
  padding: var(--space-6);
  border: 1px solid var(--color-accent-border);
  border-radius: var(--radius-lg);
  background-color: var(--color-accent-soft);
}

.about-cta-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  border: 1px solid var(--color-accent-border);
  color: var(--color-accent);
}

.about-cta-icon svg {
  width: 24px;
  height: 24px;
}

.about-cta-body {
  flex: 1 1 auto;
  min-width: 0;
}

.about-cta-title {
  font-size: var(--text-lg);
  letter-spacing: -0.01em;
}

.about-cta-body p {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
  max-width: 62ch;
}

.about-cta-link {
  flex: 0 0 auto;
}

.about-cta-link svg {
  width: 15px;
  height: 15px;
  transition: transform var(--transition-fast);
}

.about-cta-link:hover svg {
  transform: translateX(3px);
}

/* ---------- Community card ----------
   THE HOMEPAGE'S CARD, AT THIS PAGE'S SCALE. Everything that makes
   .home-community recognisable is kept: Discord's own blurple at the same 10%
   the site's soft tints use, the real Discord mark from assets/icons rather
   than a generic chat glyph, a heading, one short line of copy and a single
   btn-secondary action. Only the shape differs, because the two places differ:
   the homepage card is a 320px sidebar item stacked head, copy, block button,
   and this is a full-width row like the callout above it.

   The blurple is declared here on the component, exactly as .home-community
   declares it, and for the same reason: it is Discord's colour, not one of
   Bravith's, and nothing else on the site may pick it up.

   The wash is also what separates the two closing blocks. Coral for the
   contact callout, blurple for this one: in the same shape and back to back,
   two tints read as two things rather than as one thing continued.

   THE CARD AND ITS ADDRESS ARE GENERATED from the discord_url setting — see
   bravith_render_about_community() and the marker comment in the page. */
.about-community {
  --discord-soft: rgba(88, 101, 242, 0.1);
  --discord-border: rgba(88, 101, 242, 0.3);
  display: flex;
  align-items: center;
  gap: var(--space-5);
  margin-top: var(--space-6);
  padding: var(--space-6);
  border: 1px solid var(--discord-border);
  border-radius: var(--radius-lg);
  background-color: var(--discord-soft);
}

.about-community-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--discord-border);
  background-color: var(--color-surface);
}

.about-community-icon img {
  width: 24px;
  height: 24px;
  object-fit: contain;
}

.about-community-body {
  flex: 1 1 auto;
  min-width: 0;
}

.about-community-title {
  font-size: var(--text-lg);
  letter-spacing: -0.01em;
}

.about-community-body p {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
  max-width: 62ch;
}

.about-community-link {
  flex: 0 0 auto;
}

/* ---------- 600: the index pairs up ---------- */
@media (min-width: 600px) {
  .about-index-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ---------- 768: the two closing blocks get their wider padding ---------- */
@media (min-width: 768px) {
  .about-cta,
  .about-community {
    gap: var(--space-6);
    padding: var(--space-6) var(--space-8);
  }
}

/* ---------- 1024: the hero splits, the pictures reach the edges ---------- */
@media (min-width: 1024px) {
  .about-hero-copy {
    max-width: 48%;
  }

  .about-story,
  .about-who {
    padding-top: var(--space-16);
  }

  /* Copy in the wider track, picture in the narrower one, mirrored between the
     two sections. The picture is the smaller half on purpose: it accompanies
     the text rather than competing with it. */
  .about-story > .container,
  .about-who > .container {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr);
    gap: var(--space-16);
  }

  .about-who > .container {
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr);
  }

  /* The measure now comes from the track, not from a character count. */
  .about-story-copy,
  .about-who-copy {
    max-width: none;
  }

  /* Back to source order, which puts this picture in the left track. */
  .about-who-art {
    order: 0;
  }

  .about-story-art img,
  .about-who-art img {
    height: clamp(300px, 26vw, 400px);
  }

  .about-story h2,
  .about-who h2,
  .about-index h2,
  .about-approach h2 {
    font-size: var(--text-3xl);
  }

  .about-index-list {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--space-8);
    margin-top: var(--space-10);
  }

  /* The plate becomes a fixed-height stage: the copy centred on the left,
     the artwork out of flow on the right and fading into the black under
     the copy, exactly as in the hero. */
  .about-approach {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: center;
    min-height: 520px;
  }

  .about-approach-art {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 70%;
    height: auto;
  }

  /* The house is on the right of the frame. */
  .about-approach-art img {
    object-position: 78% 45%;
  }

  .about-approach-art::after {
    background-image:
      linear-gradient(to right, rgba(14, 12, 11, 1) 0%, rgba(14, 12, 11, 0.92) 10%, rgba(14, 12, 11, 0.55) 28%, rgba(14, 12, 11, 0.12) 48%, rgba(14, 12, 11, 0) 62%),
      linear-gradient(to top, rgba(14, 12, 11, 0.65) 0%, rgba(14, 12, 11, 0) 34%),
      linear-gradient(to bottom, rgba(14, 12, 11, 0.5) 0%, rgba(14, 12, 11, 0) 32%);
  }

  .about-approach-copy {
    max-width: 46%;
    padding: var(--space-12);
  }
}

/* ---------- Phones: the two closing blocks stack ---------- */
@media (max-width: 599px) {
  .about-cta,
  .about-community {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
  }

  .about-cta-link,
  .about-community-link {
    width: 100%;
  }
}

/* ==========================================================================
   The legal pages (/terms/ and /privacy/)

   Scoped to .legal-*; the page reuses .page-header and the ink band, the
   curve, .container, .section and .eyebrow rather than redefining them.
   Nothing here can reach another page.

   A DOCUMENT, NOT A LANDING PAGE. There is no card per section, no artwork
   and no panel: the terms are set as one column of text with hairlines
   between the sections, and the only other element on the page is a column
   of plain links. The .legal-* names are generic rather than .terms-* so a
   second legal document can adopt the same layout without a second block
   here.
   ========================================================================== */

/* ---------- Hero ----------
   Matching the specificity of the .page-header.page-header rule in main.css
   that reserves the curve's depth; this file loads after it, so the later
   rule wins. Compact by design: an eyebrow, a title, one line and a date. */
.page-header.legal-hero {
  padding-top: var(--space-8);
  padding-bottom: calc(var(--space-8) + var(--ink-curve));
}

.legal-hero h1 {
  letter-spacing: -0.02em;
}

.legal-hero .lede {
  max-width: 54ch;
}

/* Plain metadata rather than a chip: it is the last line of the hero, and a
   bordered pill here would read as a control. The band has already remapped
   --color-text-* to its own palette, so these are the ink values. */
.legal-updated {
  margin-top: var(--space-5);
  font-size: var(--text-sm);
  color: var(--color-text-faint);
}

.legal-updated strong {
  font-weight: 600;
  color: var(--color-text-muted);
}

/* ---------- The two columns ----------
   One column until 1024. The nav comes first in the source so a phone reads
   label, links, then the document — and so the jump links are reachable
   before the text they point into rather than stranded under it. */
.section.legal-section {
  padding-block: var(--space-12) var(--space-16);
}

.legal-layout {
  display: grid;
  gap: var(--space-8);
  align-items: start;
}

.legal-side {
  display: grid;
  gap: var(--space-6);
  align-content: start;
  min-width: 0;
  /* Stacked, the nav needs an edge of its own; at 1024 the columns supply it
     and this is removed. */
  padding-bottom: var(--space-6);
  border-bottom: 1px solid var(--color-border);
}

.legal-nav-title {
  margin: 0 0 var(--space-3);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-faint);
}

.legal-nav ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 2px;
}

/* The negative inline margin lets a link's hover surface extend past its text
   while the text itself stays on the column's left edge — the same treatment
   the guide sidebar uses. */
.legal-nav a {
  display: block;
  padding: var(--space-2) var(--space-3);
  margin-inline: calc(var(--space-3) * -1);
  border-radius: var(--radius-xs);
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--color-text-muted);
  text-decoration: none;
}

.legal-nav a:hover,
.legal-nav a:focus-visible {
  color: var(--color-text);
  background-color: var(--color-surface-hover);
}

/* The page you are on. Weight and colour carry it as well as the fill, so it
   is not signalled by background alone. */
.legal-nav a[aria-current="page"] {
  color: var(--color-text);
  font-weight: 600;
  background-color: var(--color-surface-hover);
}

/* ---------- The document ----------
   An <ol>: the numbering down the left is the document's real structure, not
   six hand-typed labels. Capped to a reading measure — legal prose set across
   the full container is not readable at any type size. */
.legal-doc {
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
  max-width: 72ch;
}

/* The rule belongs BETWEEN sections, so the first one does not open on a line
   immediately under the curve. */
.legal-item + .legal-item {
  margin-top: var(--space-8);
  padding-top: var(--space-8);
  border-top: 1px solid var(--color-border);
}

/* A sticky header would otherwise cover the section a jump link lands on. */
.legal-item[id] {
  scroll-margin-top: calc(var(--space-16) + var(--space-4));
}

.legal-item h2 {
  font-size: var(--text-lg);
  letter-spacing: -0.01em;
}

.legal-num {
  margin-right: var(--space-2);
  color: var(--color-text-faint);
  /* Equal-width digits, so 1 through 6 sit on one vertical edge. */
  font-variant-numeric: tabular-nums;
}

.legal-item p {
  margin-top: var(--space-3);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
}

.legal-item a {
  color: var(--color-accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--color-accent-border);
}

.legal-item a:hover {
  text-decoration-color: var(--color-accent);
}

/* ---------- 1024: the nav moves beside the document ----------
   The divider is the document's own left border rather than a border on the
   nav, so it spans the height of the text it separates and stops with it. */
@media (min-width: 1024px) {
  .legal-hero h1 {
    font-size: var(--text-4xl);
  }

  .legal-layout {
    grid-template-columns: 220px minmax(0, 1fr);
    gap: var(--space-8);
  }

  .legal-side {
    padding-bottom: 0;
    border-bottom: 0;
  }

  .legal-doc {
    padding-left: var(--space-8);
    border-left: 1px solid var(--color-border);
  }
}

/* ==========================================================================
   ERROR PAGES — /404.html
   --------------------------------------------------------------------------
   An error page is a hero and nothing else. It reuses .page-header, so it
   inherits the ink band and its curve from main.css exactly as every other
   page opening does, and it adds no component of its own: the numeral is a
   paragraph, the actions are the site's buttons.

   The one thing it needs that a content hero does not is room. There is no
   section below the hero to give the page its weight, so the space around
   the message IS the design — without it the page reads as a stub rather
   than a decision.
   ========================================================================== */

/* Matching the specificity of the .page-header.page-header rule in main.css
   that reserves the curve's depth; this file loads after it, so the later
   rule wins. */
.page-header.error-hero {
  padding-top: var(--space-16);
  padding-bottom: calc(var(--space-16) + var(--ink-curve));
  text-align: center;
}

.error-hero .container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
}

/* The numeral carries the hierarchy, so it is the only thing on the site that
   leaves the type scale. It tracks the viewport rather than a breakpoint: at
   320px it is large without wrapping, and it stops growing before it starts
   competing with the header above it. A line-height under 1 pulls the heading
   up beneath it so the two read as one block rather than two stacked lines. */
.error-code {
  margin: 0;
  font-size: clamp(4.5rem, 17vw, 8.5rem);
  line-height: 0.85;
  font-weight: 800;
  letter-spacing: -0.045em;
  color: var(--color-text);
}

/* Smaller than a content hero's h1, which is the point: the numeral is the
   loud element and the heading is what you actually read. */
.error-hero h1 {
  margin-top: 0;
  font-size: var(--text-2xl);
  letter-spacing: -0.02em;
}

.error-hero .lede {
  margin-top: 0;
  max-width: 44ch;
}

.error-hero .cta-actions {
  justify-content: center;
  margin-top: var(--space-2);
}

/* The curve opens onto the light page and the footer closes it with a hard
   edge. With no content between them the two dark bands would meet across a
   sliver of white and the curve would read as a seam, so the page keeps a
   clear run of the content colour below it. */
.error-main {
  padding-bottom: var(--space-20);
}

/* ===========================================================================
   COOKIE CONSENT

   Built by initConsent() in js/main.js, and only while a consent-gated
   service is configured: no page carries this markup, and a site that loads
   nothing third-party never shows it. Two pieces. A compact banner pinned to
   the bottom-left corner (full width on a phone) that asks once and does not
   block the page, and a preferences dialog reachable from the banner and from
   "Cookie settings" in the footer. Both are the site's own card shell,
   buttons and type, so they belong to the page rather than sitting on it.

   The two decisions are the same size and sit side by side: rejecting is one
   press, exactly as accepting is. "Manage preferences" is the quieter third
   action because it is a route to a screen, not a decision.
=========================================================================== */
.consent-banner {
  position: fixed;
  left: var(--space-4);
  right: var(--space-4);
  bottom: var(--space-4);
  /* Under the sticky header (100) and the open mobile menu (99): a notice
     must never cover navigation. Above everything in the page flow. */
  z-index: 90;
  max-width: 640px;
  padding: var(--space-5);
  background-color: var(--color-bg-elevated);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  color: var(--color-text);
}

.consent-banner[hidden] {
  display: none;
}

.consent-banner-title {
  font-size: var(--text-base);
  font-weight: 700;
}

.consent-banner-text {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-text-muted);
}

.consent-banner-text a {
  color: var(--color-text);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.consent-banner-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.consent-banner-actions .btn,
.consent-dialog-actions .btn {
  padding: 0.65rem 1.1rem;
}

.consent-link-btn {
  padding: 0.65rem var(--space-2);
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-muted);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.consent-link-btn:hover {
  color: var(--color-text);
}

/* ---------- Preferences dialog ----------
   The same scrim and card the search overlay uses, centred rather than
   top-aligned because it is a short form, not a results list. */
.consent-overlay {
  position: fixed;
  inset: 0;
  z-index: 190;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background-color: var(--color-scrim);
}

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

.consent-dialog {
  width: 100%;
  max-width: 520px;
  max-height: 100%;
  overflow-y: auto;
  padding: var(--space-6);
  background-color: var(--color-bg-elevated);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  color: var(--color-text);
}

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

.consent-dialog-title {
  font-size: var(--text-lg);
}

.consent-dialog-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background-color: var(--color-surface);
  color: var(--color-text-muted);
  cursor: pointer;
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

.consent-dialog-close:hover {
  color: var(--color-text);
  background-color: var(--color-surface-hover);
}

.consent-dialog-close svg {
  width: 18px;
  height: 18px;
}

.consent-dialog-intro {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-text-muted);
}

.consent-options {
  margin-top: var(--space-5);
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--color-border);
}

.consent-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-border);
}

.consent-option-copy {
  min-width: 0;
}

.consent-option-name {
  display: block;
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text);
}

.consent-option-copy p {
  margin-top: var(--space-1);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-text-muted);
}

.consent-option-fixed {
  flex-shrink: 0;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-success);
}

/* The switch is a native checkbox drawn as a pill, so it keeps keyboard and
   screen-reader behaviour for free and announces as a switch. */
.consent-switch {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex-shrink: 0;
  width: 44px;
  height: 26px;
  margin: 0;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  background-color: var(--color-surface-active);
  cursor: pointer;
  transition: background-color var(--transition-fast), border-color var(--transition-fast);
}

.consent-switch::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background-color: var(--color-bg);
  box-shadow: var(--shadow-sm);
  transition: transform var(--transition-fast);
}

.consent-switch:checked {
  background-color: var(--color-success);
  border-color: var(--color-success);
}

.consent-switch:checked::after {
  transform: translateX(18px);
}

.consent-switch:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.consent-dialog-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-5);
}

/* A phone: the banner spans the width and the decisions share a row. */
@media (max-width: 599px) {
  .consent-banner {
    left: var(--space-3);
    right: var(--space-3);
    bottom: var(--space-3);
    max-width: none;
    padding: var(--space-4);
  }

  .consent-banner-actions .btn,
  .consent-dialog-actions .btn {
    flex: 1 1 auto;
  }

  .consent-dialog {
    padding: var(--space-5);
  }
}
