/* Palette.
   These are the colors the site already used, pulled into one place. The old
   class names said "green" while the values were purple; the names changed, the
   colors did not.

   Contrast against white, WCAG AA needs 4.5:1 for body text:
     --brand        purple      #800080   9.42:1
     --brand-light  blueviolet  #8A2BE2   5.96:1
   Against --brand-light as a background:
     --on-brand         #ffffff  5.96:1
     --on-brand-muted   #ede9fe  5.02:1  */
:root {
  --brand: #800080;
  --brand-light: #8a2be2;
  --brand-deep: #00008b;
  --brand-wash: rgba(138, 43, 226, .07);
  --danger-wash: rgba(176, 0, 32, .06);
  --danger-wash-hover: rgba(176, 0, 32, .13);
  --useful-wash: rgba(255, 193, 7, .13);
  --useful-wash-hover: rgba(255, 193, 7, .28);
  /* Text colours for a hovered marked row, all AA on their own wash:
     --on-useful #664d03 7.99:1, --on-new #146c43 6.45:1, --danger 7.33:1. */
  --on-useful: #664d03;
  --on-new: #146c43;
  /* Tuned against the green the badge already uses (#198754). */
  --new-wash: rgba(25, 135, 84, .08);
  --new-wash-hover: rgba(25, 135, 84, .17);
  /* Bootstrap's focus ring is rgba(13, 110, 253, .25) - same shape, our hue. */
  --focus-ring: rgba(138, 43, 226, .25);

  --link: var(--brand-light);
  --link-hover: var(--brand);

  --on-brand: #fff;
  --on-brand-muted: #ede9fe;

  --surface: #fafafa;
  --danger: #b00020;

  /* Typography, in one place for the same reason the colours are.

     The site never set a family on <body> at all, so prose fell through to
     Bootstrap's system stack and rendered as a different face on every OS.
     Source Sans 3 fixes that and holds up over the documentation bodies, which
     are the only long-form text here.

     IBM Plex Mono carries everything that is code or names code: the brand
     lockup, the tag and filter names in the catalog lists, <code> and <pre>.
     That is most of the site's voice, which is why it is the deliberate half
     of the pairing rather than a fallback.

     Both keep a system stack behind them so a blocked or slow Google Fonts
     request degrades to something reasonable instead of to Times. */
  --font-sans: 'Source Sans 3', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;

  /* One column for the whole page. The header ran edge to edge in a
     .container-fluid while <main> was capped and centred, and the footer used
     Bootstrap's .container at a third width, so the brand, the h1 and the
     footer links each started at a different x. */
  --page-max: 1200px;
  --page-pad: 2rem;
}

/* The home page and the search results are two-column indexes of short names,
   where the measure cap that keeps documentation readable only squeezes them.
   Set on <body> so the header and footer widen with the content rather than
   being left behind at 1200px. */
body:has(#home),
body:has(#search-page) {
  --page-max: 1400px;
}

/* Header and footer inner wrappers. Bootstrap gives .container-fluid and
   .container their own width and .75rem gutters; these rules are class-level
   too and base.css loads after the CDN, so they win on source order. */
.page-shell {
  margin: 0 auto;
  max-width: var(--page-max);
  padding-left: var(--page-pad);
  padding-right: var(--page-pad);
  width: 100%;
}

html, body {
  height: 100%;
}

body {
  display: flex;
  flex-direction: column;
  font-family: var(--font-sans);
  min-height: 100vh;
}

/* Without this, unstyled links fall through to Bootstrap's default blue, which
   belongs to no palette this site uses.

   Bootstrap colours its components with a single class - .navbar-brand,
   .dropdown-item, .list-group-item, .btn - so any rule of ours at class-level
   specificity or above silently overrides them. A bare `a:visited` is 0-1-1 and
   beat .dropdown-item; `a:not(.btn):visited` was 0-2-1 and beat
   .navbar-dark .navbar-brand, which is how the site title turned violet once
   clicked.

   The exclusions live inside :where(), which contributes zero specificity, so
   these rules stay at 0-0-1 (0-1-1 for :visited) and cannot outrank a component
   that styles itself. Add to the list rather than raising specificity. */
a:not(:where(.btn, .navbar-brand, .nav-link, .dropdown-item, .list-group-item, .skip-link, .page-link, .include-option)) {
  color: var(--link);
}

a:not(:where(.btn, .navbar-brand, .nav-link, .dropdown-item, .list-group-item, .skip-link, .page-link, .include-option)):hover,
a:not(:where(.btn, .navbar-brand, .nav-link, .dropdown-item, .list-group-item, .skip-link, .page-link, .include-option)):focus {
  color: var(--link-hover);
}

a:not(:where(.btn, .navbar-brand, .nav-link, .dropdown-item, .list-group-item, .skip-link, .page-link, .include-option)):visited {
  color: var(--link);
}

a:focus-visible {
  outline: 2px solid var(--link-hover);
  outline-offset: 2px;
}

/* The exclusion list above assumes each excluded component styles its own
   hover. Two of them do not, and Bootstrap's reboot fills the gap with blue:
   `a:hover { color: #0a58ca }` is 0-1-1 and outranks the flat 0-1-0 rules that
   set their resting colour.

   .list-group-item is the tag and filter names on the home page and the two
   list pages. Bootstrap only styles .list-group-item-action on hover, which
   these do not carry, so every name turned blue under the cursor. The wash
   background is what .list-group-item-action would have given them.

   .skip-link sets color: #fff at 0-1-0 and lost the same way, turning blue on
   its own black background the moment it was focused - which is the only time
   anyone sees it.

   Both selectors are 0-2-1, above reboot and above the component defaults. */
a.list-group-item:hover,
a.list-group-item:focus {
  background-color: var(--brand-wash);
  color: var(--link-hover);
}

a.skip-link:hover,
a.skip-link:focus {
  color: var(--on-brand);
}

/* Hovering a marked row deepens its own colour instead of going purple. The
   purple wash above is the neutral case; on a row that already means something,
   swapping it for the brand colour threw away the signal at the exact moment
   the reader was pointing at it.

   Specificity: a + two classes + :has() + :hover is 0-4-1, over the 0-2-1 of
   the plain hover rule. Same last-match-wins order as the resting washes -
   deprecated beats new, new beats most-useful - so a row cannot change which
   colour it means just because the pointer is over it.

   Only anchors: the tags and filters list pages render <li> rows whose name is
   the link, and those have no hover state to correct. */
a.list-group-item.catalog-row:has(.catalog-star):hover,
a.list-group-item.catalog-row:has(.catalog-star):focus {
  background-color: var(--useful-wash-hover);
  color: var(--on-useful);
}

a.list-group-item.catalog-row:has(.added-mark):hover,
a.list-group-item.catalog-row:has(.added-mark):focus {
  background-color: var(--new-wash-hover);
  color: var(--on-new);
}

a.list-group-item.catalog-row:has(.deprecation-badge):hover,
a.list-group-item.catalog-row:has(.deprecation-badge):focus {
  background-color: var(--danger-wash-hover);
  color: var(--danger);
}

/* Focus rings. Bootstrap draws these in #0d6efd with a rgba(13, 110, 253, .25)
   glow, which is the blue that survived everywhere the site never restyled a
   form. The selectors match Bootstrap's own, so ours win on source order rather
   than on specificity - base.css loads after the CDN stylesheet.

   .btn covers the variants too: .btn-primary:focus and friends set their own
   tinted glow at the same 0-1-1 specificity, and are also outranked by order.
   The ring is kept at Bootstrap's .25rem so nothing shifts, and the colour is
   only ever a ring - no text sits on it, so there is no contrast pair to
   check. */
.form-control:focus,
.form-select:focus,
.form-check-input:focus {
  border-color: var(--brand-light);
  box-shadow: 0 0 0 .25rem var(--focus-ring);
}

.form-check-input:checked {
  background-color: var(--brand-light);
  border-color: var(--brand-light);
}

.btn:focus,
.btn.focus,
.btn-close:focus {
  box-shadow: 0 0 0 .25rem var(--focus-ring);
}

/* Brand lockup. The {% %} marks echo the watermark on the social card, so the
   header and a shared link read as the same thing. aria-hidden in the template
   keeps them out of the accessible name. */
.navbar-brand {
  font-family: var(--font-mono);
  font-weight: 700;
}

.brand-mark {
  color: var(--on-brand-muted);
  font-weight: 400;
  opacity: .7;
}

/* Below the lg breakpoint the toggler appears and the brand has to share the
   row with it. The lockup is 38 characters of monospace, which is 456px at
   Bootstrap's 1.25rem - wider than a phone viewport, so it pushed
   document.scrollWidth to 472px at 390px wide and the whole page scrolled
   sideways. This is not fallout from the typeface change: Roboto Mono measured
   456.1px against IBM Plex Mono's 456.0px, because both advance 0.6em.

   It is kept on one line by scaling the type to the row instead of wrapping.
   The string is a fixed 38 characters and every glyph advances 0.6em, so the
   width is exactly 22.8 * font-size and the size that fits solves in one step:

     22.8 * fs <= 100vw - 1.5rem (the --page-pad gutters, trimmed below)
                          - 42px  (the toggler, slimmed below)
                          - 8px   (a gap so they never touch)
     fs <= 4.386vw - 3.16px

   The middle term below is that, rounded down for safety. The clamp bounds it:
   1.25rem is Bootstrap's own .navbar-brand size, so nothing changes on wide
   screens, and the floor stops it disappearing on a very narrow one.

   Bootstrap's toggler is 56px here - a 30px icon (1.5em of its own 20px type)
   in 24px of padding - which is a lot of row to spend on one glyph when the
   brand is competing for the same pixels. Shrinking its type to 1rem takes the
   icon to 24px and the whole control to 42px. The gutters give back another
   8px. That is 22px returned to the brand, which is only about 1px of type:
   the 38-character string dominates, so this buys balance more than size. */
@media (max-width: 991.98px) {

  .navbar-brand {
    font-size: clamp(.55rem, calc(4.3vw - 3.4px), 1.25rem);
    white-space: nowrap;
  }

  .navbar-toggler {
    font-size: 1rem;
    padding: .3rem .5rem;
  }

  .navbar > .page-shell {
    --page-pad: .75rem;
  }
}

/* On-palette button, replacing Bootstrap's primary blue and warning yellow. */
.btn-brand {
  background-color: var(--brand-light);
  border-color: var(--brand-light);
  color: var(--on-brand);
}

.btn-brand:hover,
.btn-brand:focus {
  background-color: var(--brand);
  border-color: var(--brand);
  color: var(--on-brand);
}

.bg-brand {
  background-color: var(--brand);
}

.bg-brand-light {
  background-color: var(--brand-light);
}

.errorlist {
  color: var(--danger);
}

/* Sole owner of the page inset. <main> also carried Bootstrap's .m-4, which
   stacked 1.5rem of margin on top of this padding for 3.5rem of gutter.

   The max-width caps the measure: documentation paragraphs ran the full width
   of the window, and past roughly 100 characters a line is hard to track back
   from. Wide enough that the home page's two columns keep their room. */
main {
  flex: 1;
  margin: 0 auto;
  max-width: var(--page-max);
  padding: 1rem var(--page-pad);
  width: 100%;
}

section.documentation, section.commentary, section.feedback {
  margin-top: 1rem;
  margin-bottom: 1rem;
}

/* Section heading bar. The black bottom border was the one hard black line on
   the page and read as a seam; the bar carries the separation on its own. */
section.documentation h2, section.commentary h2 {
  background-color: var(--brand-light);
  border-radius: .25rem;
  color: var(--on-brand);
  padding: .35rem .6rem;
}


/* Body text lines up with the h1, the lead and the definition list above it.
   The 2rem inset this had made every section read as a quotation, and cost a
   fifth of the column on a laptop for no structural reason - the heading bar is
   what separates the sections. */
section.documentation article, section.commentary article {
  padding: 1rem 0;
}

/* Documentation bodies are database HTML, so their tables are as wide as their
   content. Scroll the table rather than the page. */
section.documentation article table, section.commentary article table {
  display: block;
  max-width: 100%;
  overflow-x: auto;
}

/* Shifted up a level when the page headings were promoted so every page has an
   h1. The sizes are unchanged from what h2/h3/h4 rendered before: this is a
   semantic change, not a visual one.

   h4 keeps the h3 size for anything still using it. The documentation bodies
   used to, which is what made a detail page run h1 -> h2 -> h4; they are h3
   now, and the sizes were already identical, so nothing moved. */
h1 {
  font-size: 1.5rem;
}

h2 {
  font-size: 1.2rem;
  font-weight: normal
}

h3, h4 {
  font-size: 1rem;
}

/* Home page columns.

   The lists used to sit in a .m-3 box, indented a rem from the heading they
   belong to and from the intro above them - the same stray inset the
   documentation sections had. They line up now, and the spacing between
   categories does the separating.

   The column headings get a brand rule rather than the full purple bar the
   detail sections use: two bars that size, side by side above dense lists, read
   as tabs. */
#home h2 {
  border-bottom: 2px solid var(--brand-light);
  margin-bottom: .75rem;
  padding-bottom: .25rem;
}

/* Category labels sit between the column heading and the names, so they are
   styled as labels rather than as another heading competing with either. */
#home h3 {
  color: var(--brand);
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .06em;
  margin-top: 1.25rem;
  text-transform: uppercase;
}

#home .home-list > :first-child {
  margin-top: 0;
}

/* The search form was a hard 300px inline, which overflowed a 320px screen
   once the page padding was taken out. */
#search-page form {
  max-width: 300px;
  width: 100%;
}

/* "More Utility Tags" at the foot of a detail page. A rule rather than the
   purple bar the Documentation and Commentary sections use: this is a
   footnote to the page, not another section of it. */
.category-siblings {
  margin-top: 2rem;
}

.category-siblings h2 {
  border-bottom: 2px solid var(--brand-light);
  margin-bottom: .75rem;
  padding-bottom: .25rem;
}

/* Count under the h1 on the tag and filter listings. */
.listing-count {
  color: #6c757d;
  font-size: .875rem;
  margin-bottom: .75rem;
}

/* Spec list on the detail pages: term and value in two columns, aligned on the
   widest term rather than on a col-sm-3 guess. Bootstrap's .row also carries
   negative side margins, which is why this could never sit on a panel without
   overhanging it.

   Stacks below 576px, where a max-content column would squeeze the values into
   a sliver. */
.detail-specs {
  background-color: var(--surface);
  border: 1px solid rgba(0, 0, 0, .08);
  border-radius: .35rem;
  display: grid;
  gap: .4rem 1.25rem;
  grid-template-columns: max-content 1fr;
  margin-bottom: 1.25rem;
  padding: .85rem 1.1rem;
}

.detail-specs dt {
  font-weight: 600;
}

.detail-specs dd {
  margin: 0;
}

.detail-specs .badge + .badge {
  margin-left: .4rem;
}

@media (max-width: 576px) {

  .detail-specs {
    grid-template-columns: 1fr;
    row-gap: .15rem;
  }

  .detail-specs dd + dt {
    margin-top: .5rem;
  }
}

/* The version banners lead with a heading, which the rule above renders at
   body size - so it did not read as one. Scoped to .alert so the subheads
   inside the CKEditor documentation keep their deliberate 1rem. */
.alert h3 {
  font-size: 1.1rem;
  font-weight: 600;
  margin-bottom: .35rem;
}

/* Bootstrap's .lead is 1.25rem, which put the one-line summary above the h1's
   own section headings (1.2rem) in the type hierarchy. */
.lead {
  font-size: 1.15rem;
}

/* Superuser controls on the detail pages. Floated right individually before,
   which took them out of flow: on a narrow window the h1 ran underneath them. */
#form-buttons {
  display: flex;
  gap: .5rem;
  justify-content: flex-end;
}

/* Catalog row - see includes/_catalog_row.html.

   icon | name | description ......................... version  !!  star

   One flex line rather than the pile of floats, ms-2s and stray text this
   replaced. The gap owns all the spacing, so nothing carries its own margin,
   and the marks rail is pushed right by margin-left: auto instead of by
   floating individual pieces out of the flow.

   Baseline alignment, not centre: the name, the description and the marks all
   sit on one line of text, and a description long enough to wrap still starts
   level with the name it belongs to. */
.catalog-row {
  display: flex;
  align-items: baseline;
  gap: .5rem;
}

.catalog-link {
  display: inline-flex;
  align-items: baseline;
  gap: .4rem;
  flex: none;
}

/* Fixed width so the names start at the same x whatever the glyph - fa-tag and
   fa-filter are not the same width, which showed up as a ragged left edge once
   the home page put two lists side by side. */
.catalog-icon {
  width: 1em;
  flex: none;
  text-align: center;
}

.catalog-name {
  font-family: var(--font-mono);
}

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

.catalog-marks {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  margin-left: auto;
  flex: none;
}

.catalog-star {
  cursor: help;
}

/* The star's slot is held open on rows that did not earn one. The marks rail is
   pinned right, so a starred row pushed its load mark and version pills a
   star-width further left than the row above it, and the column read as ragged.
   Hidden rather than removed: visibility keeps the box and drops it out of the
   accessibility tree. See includes/_most_useful_star.html. */
.catalog-star-slot {
  visibility: hidden;
}

/* On a phone the description shares the line with the name, which leaves it a
   sliver beside a long one like get_static_prefix. Let it drop to its own line;
   the marks stay up on the first. */
@media (max-width: 576px) {

  .catalog-row {
    flex-wrap: wrap;
  }

  .catalog-desc {
    flex-basis: 100%;
  }
}

/* The three row washes, weakest claim first. They are all the same specificity,
   so a row that qualifies for more than one takes the last it matches:
   deprecated beats new, new beats most-useful. The marks rail still shows every
   signal - the wash only decides which one colours the line.

   A "most useful" row washes yellow, the star's own colour. */
.catalog-row:has(.catalog-star),
#searchPalette .palette-item:has(.catalog-star) {
  background-color: var(--useful-wash);
}

/* A row for something with a Django version on it washes faintly green,
   picking up the colour of the badge at the end of it.

   Keyed on .added-mark, not .version-mark: the red removal pill shares the pill
   shape and so shares that class.

   Rows added in RECENT_DJANGO_VERSION or later also carry .version-mark-recent.
   Nothing styles it today - it is the hook for treating a 1.x arrival
   differently from a 6.x one, which is a judgement about "really old" that has
   not been made yet.

   Deprecation is declared after this and at the same specificity, so anything
   both versioned and deprecated lands on red. */
.catalog-row:has(.added-mark),
#searchPalette .palette-item:has(.added-mark) {
  background-color: var(--new-wash);
}

/* A deprecated or removed row washes red, so the whole line reads as "don't
   reach for this" rather than only the mark at its end. Keyed off
   .deprecation-badge, the class both red marks share.

   :has() rather than a class from the template: the marks are decided inside
   _version_badges.html, and threading "is this deprecated" back out to four
   call sites and the palette's JS to set a row class would put the same fact in
   six places. Browsers without :has() simply keep the plain background.

   Left below the hover and palette-active rules on purpose. Both outrank this
   one, so pointing at a deprecated row still gives the usual purple feedback
   instead of leaving it stuck in red. */
.catalog-row:has(.deprecation-badge),
#searchPalette .palette-item:has(.deprecation-badge) {
  background-color: var(--danger-wash);
}

/* Compact version markers used in the list views: a bare "6.1" pill for the
   Django version a thing arrived in, and a "!!" circle for deprecation. Both
   share a height so a row carrying one of each lines up. */
.deprecation-mark,
.version-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.35rem;
  cursor: help;
}

/* Red, not Bootstrap's warning yellow. Yellow read as a highlight - the same
   thing the star means - when this marks the opposite: don't reach for it. The
   palette already had a colour for that. White on --danger is 7.33:1. */
.deprecation-badge {
  background-color: var(--danger);
  color: var(--on-brand);
}

/* Bootstrap's .rounded-circle only rounds the corners it is given, so the
   circle is sized to a fixed square here - padding alone would make it an
   oval, since "!!" is narrower than it is tall. */
.deprecation-mark {
  width: 1.35rem;
  padding: 0;
  border-radius: 50%;
  font-weight: 700;
  letter-spacing: -.05em;
}

.version-mark {
  padding: 0 .45rem;
  border-radius: .7rem;
}

/* The "needs a {% load %}" mark, showing the library name. Brand purple rather
   than the green/red the version marks use: those answer "which Django", and
   this answers "what has to be at the top of your template" - a precondition,
   not a point in history. White on --brand is 9.42:1.

   It shares .version-mark's geometry so a row carrying both lines up, and is
   declared after the version marks so the pill shape wins wherever a row has
   one of each. */
.load-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.35rem;
  padding: 0 .45rem;
  border-radius: .7rem;
  background-color: var(--brand);
  color: var(--on-brand);
  font-family: var(--font-mono, monospace);
  cursor: help;
}

/* The detail-page banner. Bootstrap's .alert-info is its own blue, which is the
   only blue on the page; this keeps it in the brand wash so it reads as part of
   the site rather than a framework default. */
.load-requirement {
  background-color: var(--brand-wash);
  border-color: var(--brand-light);
  color: inherit;
}

.load-requirement h3 {
  font-size: 1.1rem;
  margin-bottom: .5rem;
}

/* Two-up list group. Bootstrap's .list-group is a flex column; this flips it to
   a wrapping row of half-width items so a category's entries read across two
   columns. The border rules are hand-rolled because Bootstrap's
   ".list-group-item + .list-group-item { border-top-width: 0 }" assumes a single
   column - in a two-column wrap it would strip the top border from the second
   item of the first row. Instead every item keeps its top border and only items
   from the second row on (nth-child(n+3), exact because the layout is exactly
   two columns) drop it, while even items drop their left border so the seam
   between columns stays one pixel wide.

   The breakpoints skip 768-991px on purpose. That is where Bootstrap splits
   the home page into two col-md-6 columns while the viewport is still narrow,
   so a two-up list inside one of them gives each name about 170px - not enough
   for get_static_prefix. Below 768px the column is full width and two-up fits;
   at 992px the half-column is wide again. Under 576px it is one column, since
   two columns of names wrap badly on a phone. */
@media (min-width: 576px) and (max-width: 767.98px), (min-width: 992px) {

  .list-group-two-up {
    flex-direction: row;
    flex-wrap: wrap;
  }

  .list-group-two-up > .list-group-item {
    width: 50%;
    border-top-width: 1px;
    border-radius: 0;
  }

  .list-group-two-up > .list-group-item:nth-child(n+3) {
    border-top-width: 0;
  }

  .list-group-two-up > .list-group-item:nth-child(even) {
    border-left-width: 0;
  }
}

code {
  font-family: var(--font-mono);
}

pre {
  background-color: var(--surface);
  border: thin solid var(--brand-deep);
  border-width: 1px 2px 2px 1px;
  font-family: var(--font-mono);
  padding: .5rem 1rem;
}

/* Utility classes for inline style replacements */
.modal-responsive {
  max-width: 90%;
}

.img-responsive {
  max-width: 90%;
}

footer a {
  color: var(--on-brand);
  text-decoration: none;
}

footer a:hover,
footer a:focus {
  color: var(--on-brand);
  text-decoration: underline;
}

footer a:visited {
  color: var(--on-brand);
}

footer a:focus-visible {
  outline: 2px solid var(--on-brand);
  outline-offset: 2px;
}

.text-disclaimer {
  color: var(--on-brand-muted);
}

.text-disclaimer a {
  color: var(--on-brand-muted);
}

.text-disclaimer a:hover,
.text-disclaimer a:focus,
.text-disclaimer a:visited {
  color: var(--on-brand-muted);
}

/* "As Markdown" / copy pair on detail pages.
   A joined segmented control: label on the left, icon-only copy on the right. */
.markdown-actions {
  display: inline-flex;
  align-items: stretch;
  gap: 2px;
  font-family: var(--font-mono);
  font-size: .8rem;
  line-height: 1;
  vertical-align: middle;
  white-space: nowrap;
}

.markdown-actions a,
.markdown-actions button {
  align-items: center;
  background-color: var(--brand-wash);
  border: 0;
  color: var(--link);
  display: inline-flex;
  text-decoration: none;
  transition: background-color .2s ease, color .2s ease;
}

.markdown-actions-view {
  gap: .4rem;
  padding: .55rem .65rem;
}

.markdown-actions-copy {
  padding: .55rem;
}

.markdown-actions a:hover,
.markdown-actions a:focus-visible,
.markdown-actions button:hover,
.markdown-actions button:focus-visible {
  background-color: var(--brand-light);
  color: var(--on-brand);
}

.markdown-actions :focus-visible {
  outline: 2px solid currentColor;
  outline-offset: -2px;
}

.markdown-mark {
  height: .85em;
  width: auto;
  flex-shrink: 0;
}

.markdown-copy-icon {
  height: .85em;
  width: .85em;
}

/* Taxonomy links on the left, Markdown actions pushed to the right, rather
   than the actions sitting wedged between the links. */
.detail-meta {
  align-items: center;
  column-gap: 1rem;
  display: flex;
  flex-wrap: wrap;
  row-gap: .5rem;
}

.detail-meta > .markdown-actions {
  margin-left: auto;
}

@media (max-width: 576px) {

  .detail-meta > .markdown-actions {
    margin-left: 0;
  }
}

/* Cmd+K search palette */
.palette-hint {
  position: absolute;
  right: .5rem;
  top: 50%;
  transform: translateY(-50%);
  background: #eee;
  border-radius: .2rem;
  color: #555;
  font-size: .75rem;
  padding: .05rem .3rem;
  pointer-events: none;
}

#searchPalette .modal-body {
  max-height: 60vh;
  overflow-y: auto;
}

/* Breathing room. The palette is built from Bootstrap's tight defaults (py-2
   header, p-0 body), which packed the input, the rows and the key hints right
   up against each other and each other's edges. These rules are ID-scoped, so
   they outrank the utility classes in the markup without touching modals
   elsewhere on the site. */
#searchPalette .modal-header,
#searchPalette .modal-footer {
  padding: .6rem 1rem;
}

#searchPalette .modal-body {
  padding: .35rem 0;
}

#searchPalette .palette-item {
  cursor: pointer;
  padding: .6rem 1rem;
}

/* Key hints under the results. Bootstrap's .modal-footer is a flex row that
   pushes its content to the end; the hints are one caption about the whole
   palette, so they belong in the middle. */
#searchPalette .palette-hints {
  justify-content: center;
}

/* The arrows read as one key without this - "↑↓" rather than two things you
   can press. Margin rather than gap: they sit inside a line of text, not in a
   flex row of their own. */
#searchPalette .palette-hints kbd + kbd {
  margin-left: .25rem;
}

#searchPalette #palette-empty {
  padding: .6rem 1rem;
}

#searchPalette .palette-item.active {
  background-color: var(--brand-light);
  color: var(--on-brand);
}

#searchPalette .palette-item.active .text-muted,
#searchPalette .palette-item.active i {
  color: #eee !important;
}

/* Skip navigation link for accessibility */
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: #000;
  color: #fff;
  padding: 8px;
  z-index: 100;
}

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

@media (max-width: 768px) {

  :root {
    --page-pad: 1rem;
  }

  main {
    padding-bottom: .75rem;
    padding-top: .75rem;
  }

  section.documentation article, section.commentary article {
    padding: .2rem 0;
  }

  td code {
    white-space: nowrap;
  }

  #form-buttons a {
    font-size: .75rem;
  }

  #search-page form button {
    width: 100%;
  }
}

/* Error pages (404, 500).

   The lockup echoes the header and the social cards: the code is set as a
   template tag, {% 404 %}, so the page reads as part of this site rather than
   as the server's default. It is aria-hidden and the <h1> carries the real
   wording, because "404" announced on its own tells a screen reader nothing.

   The size is clamped rather than fixed so the tag does not overflow a phone,
   the same problem the brand had: this is 8 characters of monospace, so at
   0.6em per glyph it needs 4.8 * font-size of room. */
.error-page {
  margin: 0 auto;
  max-width: 46rem;
  padding: 1rem 0 2rem;
  text-align: center;
}

.error-code {
  color: var(--brand-light);
  font-family: var(--font-mono);
  font-size: clamp(2.25rem, 13vw, 5rem);
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1;
  margin: 0 0 .75rem;
}

/* The braces sit back so the number reads first, the same treatment the header
   gives .brand-mark. */
.error-code .brand-mark {
  color: var(--brand-light);
  font-weight: 400;
  opacity: .45;
}

.error-page h1 {
  font-size: 1.6rem;
  margin-bottom: .5rem;
}

.error-lede {
  color: #555;
  margin: 0 auto 1.5rem;
  max-width: 34rem;
}

/* Search is the main recovery path on a 404, so it gets the width and sits
   above the links rather than beside them. */
.error-search {
  display: flex;
  gap: .5rem;
  margin: 0 auto 2.25rem;
  max-width: 30rem;
}

.error-search .form-control {
  flex: 1 1 auto;
  min-width: 0;
}

.error-search .btn {
  flex: none;
}

.error-links-heading {
  border-bottom: 2px solid var(--brand-light);
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .06em;
  margin-bottom: 1rem;
  padding-bottom: .25rem;
  text-align: left;
  text-transform: uppercase;
}

/* auto-fit rather than a fixed column count: the same grid holds seven cards on
   the 404 and three on the 500 without either page needing its own rule. */
.error-links {
  display: grid;
  gap: .75rem;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  text-align: left;
}

.error-link {
  align-items: baseline;
  background-color: var(--brand-wash);
  border: 1px solid transparent;
  border-radius: .5rem;
  display: flex;
  gap: .6rem;
  padding: .75rem .9rem;
  text-decoration: none;
  transition: border-color .12s ease, background-color .12s ease;
}

.error-link:hover,
.error-link:focus-visible {
  background-color: transparent;
  border-color: var(--brand-light);
}

.error-link i {
  color: var(--brand-light);
  flex: none;
  text-align: center;
  width: 1em;
}

.error-link-name {
  font-family: var(--font-mono);
  font-weight: 700;
}

.error-link-desc {
  color: #6c757d;
  display: block;
  font-size: .85rem;
  margin-top: .15rem;
}

@media (max-width: 576px) {

  .error-search {
    flex-direction: column;
  }

  .error-search .btn {
    width: 100%;
  }
}

/* ---------------------------------------------------------------------------
   Cheat sheet

   One page carrying the whole catalog, in two forms. On screen it is a dense
   index with working links; printed, the chrome drops away and it becomes a
   reference sheet. Both use the brand palette rather than a second one - a
   sheet on someone's desk should look like the site it came from.

   Layout is CSS columns, not a grid, so a category can break across a column
   boundary and the page fills. Pinning each category to its own column left a
   ragged half page wherever a category was short. .cs-group avoids breaking
   immediately after its heading, which is the one break that reads as a bug. */
.cheatsheet {
  --cs-rule: rgba(128, 0, 128, .18);
  max-width: 62rem;
  margin: 0 auto;
}

.cheatsheet-header h1 {
  margin-bottom: .25rem;
}

.cheatsheet-sub {
  color: var(--muted, #555);
  margin-bottom: .75rem;
}

.cheatsheet-key {
  display: inline-block;
  margin-left: .5rem;
}

.cheatsheet-family {
  margin-top: 1.5rem;
}

/* The family heading spans the columns, so Tags and Filters stay two blocks
   rather than interleaving. */
.cheatsheet-family-title {
  border-bottom: 2px solid var(--brand);
  padding-bottom: .2rem;
  margin-bottom: .6rem;
  color: var(--brand);
}

.cheatsheet-columns {
  column-count: 2;
  column-gap: 1.75rem;
}

/* Groups deliberately do NOT avoid breaking. Utility carries 28 tags, more
   than a column holds, so keeping it whole pushed it into a column of its own
   and left the third column of the printed page empty. Letting a long group
   flow across the boundary fills the page; the heading still travels with at
   least the start of its list, which is the break that would read as a bug. */
.cs-group {
  margin-bottom: .7rem;
}

.cs-group h3 {
  font-size: .82rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--brand);
  border-bottom: 1px solid var(--cs-rule);
  padding-bottom: .12rem;
  margin-bottom: .25rem;
  break-after: avoid;
}

.cs-entry {
  font-size: .82rem;
  line-height: 1.35;
  padding: .06rem 0;
  break-inside: avoid;
}

.cs-name {
  font-family: var(--font-mono);
  font-weight: 600;
  color: var(--brand);
  text-decoration: none;
  white-space: nowrap;
}

.cs-name:hover {
  text-decoration: underline;
}

.cs-desc {
  color: var(--muted, #444);
}

.cs-star {
  color: #b8860b;
}

/* The library name, not the word "load" - which library it is is the part a
   reader needs. Small caps mass rather than a filled pill: at this density a
   solid purple badge on 27 rows would read as the loudest thing on the page. */
.cs-load {
  font-family: var(--font-mono);
  font-size: .68rem;
  color: var(--brand-light);
  border: 1px solid var(--cs-rule);
  border-radius: .5rem;
  padding: 0 .3rem;
}

.cheatsheet-footer {
  margin-top: 1.5rem;
  padding-top: .5rem;
  border-top: 1px solid var(--cs-rule);
  font-size: .75rem;
  color: var(--muted, #666);
}

/* Print. Everything that cannot be used on paper goes: navigation, the print
   button itself, the site chrome. Links keep their colour but not their URL -
   printing every href would double the length of the sheet, and the reader
   has the site in front of them if they want one. */
@media print {
  @page {
    size: letter;
    margin: .5in;
  }

  body {
    background: #fff;
  }

  /* Matched on the role rather than a class: the site footer carries neither
     .site-footer nor an id, and left in place it printed the hosting notice
     and the trademark disclaimer as a third page of their own. */
  header.site-header,
  footer[role="contentinfo"],
  footer.site-footer,
  nav,
  .no-print,
  .aside,
  .book-ad {
    display: none !important;
  }

  main {
    padding: 0 !important;
    margin: 0 !important;
    max-width: none !important;
  }

  .cheatsheet {
    max-width: none;
  }

  .cheatsheet-columns {
    column-count: 3;
    column-gap: .35in;
  }

  .cs-entry {
    font-size: 7.4pt;
    line-height: 1.25;
  }

  .cs-group h3 {
    font-size: 7pt;
  }

  .cs-name,
  .cs-desc,
  .cheatsheet-family-title,
  .cs-group h3 {
    color: #000;
  }

  /* The one place colour is worth the ink: it is what makes the two families
     and the load-only entries findable while scanning. */
  .cs-name {
    color: #800080;
  }

  .cs-load {
    color: #6a1b9a;
  }

  a {
    text-decoration: none;
  }
}

/* ---------------------------------------------------------------------------
   "How much of the catalog" control

   Three cumulative states rendered as links, so each is its own URL and the
   whole thing works with JavaScript off. Styled as a segmented control because
   the states are mutually exclusive and ordered - checkboxes would suggest you
   can have the third without the second. */
.include-filter {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
  margin: .5rem 0 .75rem;
}

/* Sized to the copy around it, not shrunk because it is a control. This sits
   between the intro paragraph and the listing it governs, and at .85rem it
   read as fine print next to both - which is the opposite of what a control
   deciding what the page shows should look like. */
.include-filter-label {
  color: var(--muted, #555);
}

.include-filter-options {
  display: inline-flex;
  border: 1px solid var(--brand-light);
  border-radius: 1.1rem;
  overflow: hidden;
}

.include-option {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .3rem .8rem;
  color: var(--brand);
  text-decoration: none;
  background: transparent;
  border-right: 1px solid var(--brand-light);
}

.include-option:last-child {
  border-right: 0;
}

/* .include-option is in the exclusion list at the top of this file, so the
   global link colours - including a:visited, which is 0-1-1 and outranks a
   bare class - leave it alone. That rule is why the selected option rendered
   as --link text on the --brand fill once you had clicked it. Excluded
   components have to style their own states, hence the explicit hover, focus,
   and visited below. */
.include-option:visited {
  color: var(--brand);
}

.include-option:hover,
.include-option:focus {
  background: var(--brand-wash);
  color: var(--brand);
  text-decoration: none;
}

/* The selected state is filled rather than merely bolder: this control decides
   what the page is showing, and a reader who cannot tell which state is on
   cannot tell whether something is missing or absent. White on --brand is
   9.42:1. */
.include-option-on {
  background: var(--brand);
  color: var(--on-brand);
}

.include-option-on:visited,
.include-option-on:hover,
.include-option-on:focus {
  background: var(--brand);
  color: var(--on-brand);
}

/* em, so the count tracks whatever size the option is rather than needing to
   be retuned alongside it. */
.include-count {
  font-size: .8em;
  opacity: .8;
}
