/* lf-ui component layer — shared rules for elements that should render
   identically across consumers. Depends on lf-theme.css's tokens (fonts,
   --bs-border-color, --bs-box-shadow(-lg), --site-transition/--site-transform-*).
   Rules with real per-site differences (card border-hover retargeting tied
   to site-specific link classes) stay local to each consumer instead of
   being forced identical here. */

/* Text selection — a 20% tint of the link color behind the
   text, which keeps its own color so it stays readable in light and dark
   mode. A consumer can still override this in its local style.css, which
   loads after this file. */
::selection {
    background-color: rgba(var(--bs-link-color-rgb), 0.2);
}

/* Headings — the vendored Bootstrap 5.3.2 build hardcodes font-weight:500
   with no CSS-variable hook (checked against the actual compiled CSS, not
   assumed), and sets no font-family for headings at all, so this can't be
   done via Bootstrap's own variables the way --bs-primary/--bs-border-color
   can. "Public Sans Bold" (not "Public Sans" + font-weight: 700) is deliberate:
   it's a dedicated bold-only font-family, so any more-specific rule
   downstream that resets font-weight (inside a card, alert, badge — any
   "box" component) still renders the bold glyphs, because there's only one
   file registered under this family name regardless of what weight number
   ends up applying. No !important — this file loads after
   bootstrap.min.css, so plain source-order specificity is enough to win. */

/* .h1-.h6 too, not just the real tags — Bootstrap's own utility classes
   exist so a non-heading element can look like a heading (a card title
   that deliberately isn't a real <h*>, to stay out of a page's own
   heading-scanning TOC/outline), and "look like" should include the
   custom heading font, not just size/weight/line-height. */
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
    font-family: "Public Sans Bold", var(--bs-font-sans-serif);
    letter-spacing: -0.02em;
}

/* Standard space below every page's h1, replacing hand-typed mb-2/mb-3/mb-4
   on individual <h1> tags across jumbotron.html's branches — one value
   instead of drifted near-duplicates. margin-bottom is zeroed because
   Bootstrap's reboot already puts margin-bottom:.5rem on every heading —
   left alone, that stacks under this padding for 1.5rem total instead of
   the intended single 1rem. */
h1 {
    margin-bottom: 0;
    padding-bottom: 0.75rem;
}

/* Same reasoning as headings above — a dedicated bold-only family instead
   of relying on the browser's implicit <strong> bold, which is just as
   vulnerable to a downstream font-weight reset. */
strong {
    font-family: "Public Sans Bold", var(--bs-font-sans-serif);
}

/* No color override — Bootstrap's own h1-h6 rule reads color:
   var(--bs-heading-color), and that variable's own real default is
   inherit. Headings should render in whatever the surrounding text color
   is, not a separately-managed "heading color" — overriding it here would
   just be a second, redundant place for that value to drift out of sync. */

/* Share widget — body color in light, link color in dark. [data-bs-theme]
   (not [data-theme]) because that's the one dark-mode attribute both
   consumers' theme-toggle scripts actually set, regardless of which
   attribute/class each site's own local CSS keys its other dark-mode rules
   off of. */

.share {
    font-size: calc(0.875em * 0.875);
}

/* The "Share:" label and each icon link/button render at the standard
   small-text size, not .share's own smaller base size — explicit here
   since a bare descendant would otherwise inherit the smaller value. */
.share span,
.share a,
.share button {
    font-size: var(--site-small-font-size);
}

.share a, .share a:visited, .share .share-copy-btn {
    color: var(--bs-body-color);
    text-decoration: none;
}

.share a:hover, .share .share-copy-btn:hover {
    text-decoration: underline;
}

[data-bs-theme=dark] .share a,
[data-bs-theme=dark] .share a:visited,
[data-bs-theme=dark] .share .share-copy-btn {
    color: var(--bs-link-color);
}

/* govfresh's a[href^="mailto:"]:not(.icon-link)::after rule decorates plain
   prose mailto links with an extra envelope glyph — useful in body text,
   but it also matches the share widget's own "Share via Email" link (which
   already has a real Font Awesome envelope icon inside it), producing two
   envelopes stacked next to each other. Scoped off here rather than
   touching that rule's selector, since the prose use case is legitimate.
   !important is needed because this selector and govfresh's own
   a[href^="mailto:"]:not(.icon-link)::after tie on specificity (two
   class-level selectors each) — without it, whichever stylesheet loads
   later wins the tie and silently defeats this override. */
.share a[href^="mailto:"]::after {
    content: none !important;
}

.share-copy-btn {
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    font-family: inherit;
    font-size: inherit;
}

/* Secondary/muted text — a real Bootstrap color utility, but this site
   language also treats it as "metadata" text (dates, captions, bylines),
   set in the monospace typeface like the rest of that vocabulary
   (nav, badges, footer). */

.text-body-secondary {
    font-family: var(--site-font-secondary);
}

/* Secondary font alone, no color change — for identifiers, file paths,
   and other monospace-styled text that isn't muted/secondary-colored
   prose. Real <code>/<kbd>/<pre>/<samp> already get the monospace family
   for free (lf-theme.css); this is for everything else — a <dd>, <a>,
   <li>, <td> — that wants the same typeface without also picking up
   .text-body-secondary's color change. */
.font-secondary {
    font-family: var(--site-font-secondary);
}

/* Read-time progress bar — read-time-bar.html/read-time-bar.js are
   byte-identical between consumers already; this is the matching CSS. */

#read-time-container {
    position: sticky;
    top: 0;
    width: 100%;
    background: none;
    z-index: 100;
    pointer-events: none;
}

#read-time-bar {
    height: 0.25rem;
    background-color: var(--bs-link-color);
    width: 0;
    transition: width 0.1s;
}

/* People profile layout — sidebar (photo, name/position/org, social,
   role badge) left-aligned flush with the avatar, equal section spacing. */

/* display/flex-direction/align-items come from Bootstrap's own .d-flex.
   flex-column.align-items-start on the markup — this only sets the
   gap, which doesn't match a Bootstrap gap-* step (gap-2 is 0.5rem,
   gap-3 is 1rem). */
.people-sidebar {
    gap: 0.75rem;
}

.people-sidebar ul {
    margin-bottom: 0;
    font-family: var(--site-font-secondary);
}

.people-sidebar li {
    margin-inline-start: 0;
    margin-bottom: 0.15rem;
}

.people-layout-sidebar {
    min-width: 200px;
}

/* Content + optional TOC layout — content left, TOC right, top-aligned.
   Named for toc-layout.html, the shared include that renders this markup
   (not "thoughts-layout" — its original name, a holdover from when only
   note/thoughts posts used it; toc-layout.html now backs every page that
   might show a TOC: about, work, home, notes, articles). Breakpoint
   matches Bootstrap's own lg (991.98px), not an arbitrary custom value. */

/* Column widths/wrap come from Bootstrap's own .row/.col/.col-auto
   (markup) — this sets the gutter to this component's own 2rem instead of
   .row's default, and aligns items to the top instead of Bootstrap's
   default stretch (a .col-auto sidebar shorter than .col's content
   shouldn't stretch to match it). */
.toc-layout {
    --bs-gutter-x: 2rem;
    align-items: flex-start;
}

/* A flex item's default min-width is auto, not 0 — wide unbroken content
   inside .col (a long <pre> line, e.g.) sets its intrinsic minimum width
   above what's left over after .toc-sidebar, and Bootstrap's .row wraps
   flex-wrap by default, so the sidebar drops below the content entirely
   instead of the <pre> just scrolling within its own already-narrower
   box (pre's own overflow:auto, elsewhere in this file). */
.toc-layout > .col {
    min-width: 0;
}

/* Fixed width, not sized to its own content — needs to hold the same
   width whether it's rendering toc.html's real output or standing in
   empty as a spacer (toc-layout.html's no-TOC branch), so .col reading
   width stays identical either way. */
.toc-sidebar {
    width: 240px;
    position: sticky;
    top: var(--site-sticky-offset);
}

/* Generic sticky-sidebar primitive — same offset as .toc-sidebar (they
   should always agree), but with no width/side opinion, for a sidebar
   that isn't the "On this page" TOC (a cross-page nav list, e.g.). */
.sticky-sidebar {
    position: sticky;
    top: var(--site-sticky-offset);
}

@media (max-width: 991.98px) {
    .toc-sidebar {
        display: none;
    }
}

/* Navbar */

.navbar {
    padding: 0.5rem 1rem;
    background: var(--bs-body-bg);
    border-bottom: 0.0625rem solid var(--bs-border-color);
    font-family: var(--site-font-secondary);
    font-size: 0.95rem;
}

.navbar-brand {
    font-family: "Public Sans Bold", var(--bs-font-sans-serif);
    font-size: 1.1rem;
    letter-spacing: -0.02em;
    color: var(--bs-body-color);
    text-decoration: none;
    padding: 0.5rem 0;
}

.navbar-brand:hover { color: var(--bs-link-color); }

.navbar-nav {
    gap: 0.25rem;
}

.nav-link {
    color: var(--bs-body-color);
    text-decoration: none;
    padding: 0.625rem 1rem;
    min-height: 2.75rem;
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-size: var(--site-small-font-size);
    transition: color var(--site-transition);
}

.nav-link:hover,
.nav-link[aria-current="page"] {
    color: var(--bs-link-color);
    text-decoration: underline;
}

.navbar-toggler {
    background: none;
    border: none;
    padding: 0.4rem;
    color: var(--bs-body-color);
    font-size: 1.2rem;
    min-height: 2.75rem;
    min-width: 2.75rem;
    cursor: pointer;
    box-shadow: none !important;
}

.navbar-toggler:hover {
    color: var(--bs-link-color);
}

.navbar-toggler:focus-visible {
    outline: 0.125rem solid var(--bs-link-color);
    outline-offset: 0.125rem;
}

@media (max-width: 991.98px) {
    .navbar-collapse {
        background: var(--bs-body-bg);
        border: 0.0625rem solid var(--bs-border-color);
        margin-top: 0.5rem;
        padding: 0.25rem 0;
    }
}

@media (max-width: 768px) {
    /* min-height alone leaves the label stuck to the top of the taller box
       (more space below than above); flex-centering splits it evenly. gap
       replaces the inline whitespace between an icon and its label. */
    .btn {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 0.35em;
        font-size: 0.85rem;
        min-height: 2.25rem;
        padding: 0.25rem 0.75rem;
    }
}

/* Logo — a CSS mask, not an inline <svg> or <img>, so the mark inherits
   .navbar-brand's currentColor and tracks light/dark mode and hover
   automatically, using nothing but each site's plain, unedited .svg
   export — no per-site conversion to inline markup, no fill="currentColor"
   find-and-replace; drop a new logo.svg in and it works. --brand-mark-src
   (the file) and aspect-ratio (the file's own intrinsic width/height) are
   set inline per site from site.json's logo config; only the two height
   tiers live here, since they're a real shared choice, not a per-site
   number: some marks' visible glyphs fill their own viewBox close to
   edge-to-edge (short is enough), others have more internal padding
   baked into the viewBox relative to their ink and read smaller at the
   same height unless bumped up (tall). */
.brand-mark {
    display: inline-block;
    vertical-align: middle;
    width: auto;
    background-color: currentColor;
    -webkit-mask-image: var(--brand-mark-src);
    mask-image: var(--brand-mark-src);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: contain;
    mask-size: contain;
    transition: background-color var(--site-transition);
}

/* Optional small-screen mark (site.data.site.logo.mobile_file in nav.html):
   a square icon swaps in for the wordmark at phone widths. The icon's ink
   sits inside its viewBox with some padding, so the box is taller than the
   wordmark's and nudged left to keep its ink on the page's left edge. */
.brand-mark-icon { display: none; height: 2rem; margin-left: -0.25rem; }
@media (max-width: 768px) {
    .brand-mark-full { display: none; }
    .brand-mark-icon { display: inline-block; }
}

.logo-height-short { height: 1.2rem; }
.logo-height-tall { height: 1.6rem; }

/* .drop-shadow removed — it was byte-for-byte identical to Bootstrap's
   own .shadow-lg utility (box-shadow: var(--bs-box-shadow-lg)), just
   under a different name. Every former .drop-shadow site now uses
   .shadow-lg directly. */

/* image.html's posts/thoughts top-of-page image — a dedicated class
   instead of keying off .xxl/.border, which several unrelated branches
   (briefs/labs/events/orgs) also use. */
.post-top-image { border-radius: var(--bs-border-radius); }

/* Hero — page-title/description block padding, shared across every
   jumbotron.html branch on both consumers instead of each branch hand-
   typing its own pt-N / pb-N combo. Margin-bottom stays a plain Bootstrap
   mb-* utility (generic spacing, not hero-specific); .hero-divider adds
   the border-bottom some branches need. Compose freely: class="container
   hero-md hero-divider mb-4". Top padding was originally ported at its
   pre-existing value (0.25rem, effectively no visible space above the
   page title on every "else"-branch page — about, work, press, etc.) —
   bumped to read as real intentional spacing next to Bootstrap's native
   (large) h1 size, confirmed against the rendered page, not guessed. */
.hero-xs { padding-top: 2rem; }
.hero-sm { padding-top: 2rem; padding-bottom: 0.5rem; }
.hero-md { padding-top: 2rem; padding-bottom: 1rem; }
.hero-lg { padding-top: 2rem; }

.hero-divider {
    border-bottom: 0.0625rem solid var(--bs-border-color);
}

/* Standard "hero, then body content, no TOC" wrapper — govfresh hand-typed
   this as container py-4 (padding, top+bottom), lukefretwell as container
   mt-4 (margin, top only); same role, two slightly different values.
   Reconciled to one: both now use real Bootstrap .py-4 directly in
   markup (padding, not margin, for the same reason h1/lead moved off
   margin earlier this session — and py-4 is exactly 1.5rem top+bottom,
   so no custom class is needed at all). Previously this lived here as
   its own .content-body class using the "1.5rem 0" 2-value shorthand,
   which also zeroed left/right padding and silently canceled Bootstrap's
   own .container gutter — fixed by switching to explicit padding-top/
   -bottom, then removed entirely once that was recognized as identical
   to .py-4. */

/* Card grid — the gutter (g-3) is already identical at every one of the
   18 places this pattern appears; baking it in here means one fewer
   utility class to hand-type, and one less thing that could silently
   drift to a different gutter value somewhere. Column-count progression
   (row-cols-*) stays a plain Bootstrap utility on the markup — that part
   is genuinely content-dependent, not a role this class should own. */
.card-group {
    --bs-gutter-x: 1rem;
    --bs-gutter-y: 1rem;
    margin-bottom: 1.5rem;
}

/* Modifier for a card grid that's one of several stacked on the same
   page (topic.html's category sections) — the divider + extra top
   space only makes sense when something precedes it on the page, so
   it's additive, not baked into the base class. */
.card-group-divider {
    margin-top: 1.5rem;
    padding-bottom: 1.5rem;
    border-bottom: 0.0625rem solid var(--bs-border-color);
}

/* When a breadcrumb precedes the hero (breadcrumb.html's wrapper is always
   the sibling right before <main>, regardless of nesting inside <main> —
   see .breadcrumb-bar below), the breadcrumb already provides visual
   separation from the nav, so the hero doesn't need its full no-breadcrumb
   top padding on top of that — this reduces it theme-wide, automatically,
   for every layout that includes breadcrumb.html, no per-page changes. */
.breadcrumb-bar + main .hero-xs,
.breadcrumb-bar + main .hero-sm,
.breadcrumb-bar + main .hero-md,
.breadcrumb-bar + main .hero-lg {
    padding-top: 1rem;
}

/* Small/secondary text — each role below is a semantic class (what the
   text IS, not how it's sized/colored/set), and each references
   --site-small-font-size, --site-font-secondary (lf-theme.css) and
   --bs-secondary-color directly rather than generic
   "small"/"text-body-secondary" utilities. No small/.small or
   text-body-secondary fallback: nothing in this theme's markup pairs a
   utility class onto these roles going forward — the class alone carries
   the full treatment. Unscoped (not `.card p.X`) — applies the same
   wherever the role appears, inside a card or not. */

/* Icon + optional label + value line — post metadata (date, author,
   duration...), event/org contact fields. Margin comes from a plain
   Bootstrap mb-* utility passed alongside this class, not baked in here,
   since it varies by context (standalone line vs. a stacked block). */
.icon-line {
    font-size: var(--site-small-font-size);
    font-family: var(--site-font-secondary);
    color: var(--bs-secondary-color);
    line-height: 1.5;
}

/* Fixed-width icon slot so labels start at the same x on every line
   (calendar, calendar-check, clock and user icons differ in width). */
.icon-line > i {
    display: inline-block;
    width: 1.25em;
    text-align: center;
}

/* image.html's figures — shrink-to-fit the image's own rendered width
   (instead of the full-width block a bare <figure> defaults to), so a
   centered figure (e.g. the book branch's .text-center wrapper) centers
   image+caption as one unit rather than centering the image while a
   full-width figcaption centers its text independently across a wider
   box than the photo. */
.image-figure {
    display: inline-block;
    margin: 0;
}

/* Image caption text (image.html's <figcaption><p>). Always left-aligned
   within .image-figure's now image-width box, so the caption's left edge
   matches the photo's left edge regardless of whether an ancestor
   centers the figure. */
.img-caption {
    font-size: var(--site-small-font-size);
    font-family: var(--site-font-secondary);
    color: var(--bs-secondary-color);
    text-align: left;
    margin-bottom: 1.5rem;
}

/* Rule under an image block (image.html: image, caption, credit, download
   link). Same line as .hero-divider, as its own element because the image
   sits inside a container that doesn't carry the border. */
.image-divider {
    border-bottom: 0.0625rem solid var(--bs-border-color);
    margin-bottom: 1.5rem;
}

/* Photo credit + high-resolution download link (image-credit.html), set
   under an image. Same type treatment as .img-caption, with tighter line
   spacing so the two lines read as one block, and more room above than a
   caption gets so it doesn't crowd the photo. */
.img-credit {
    font-size: var(--site-small-font-size);
    font-family: var(--site-font-secondary);
    color: var(--bs-secondary-color);
    text-align: left;
    line-height: 1.5rem;
    margin-top: 0.875rem;
    margin-bottom: 1rem;
}
.img-credit p { margin-bottom: 0; }
/* 24px-tall rows: each link is a 24px touch target (WCAG 2.5.8). */
.img-credit a { display: inline-block; text-decoration: underline; }
.img-credit a:hover { text-decoration: none; }

/* Card body text — a card's description paragraph, or a secondary byline/
   meta line (byline, episode number, date-only line). Only .card-byline
   gets a baked-in margin — every usage is mb-0, but .card-description's
   margin varies by context (mb-0/mb-2/mb-3 depending on what follows it),
   so that one stays a plain Bootstrap utility in markup. */
.card-description,
.card-byline {
    font-size: var(--site-small-font-size);
    font-family: var(--site-font-secondary);
    color: var(--bs-secondary-color);
}

.card-byline {
    margin-bottom: 0;
}

/* Caption under a title/icon (card-grid.html's theme cards, social.html's
   connect-icon captions) and a card's inline tag list. */
.card-caption,
.card-tags {
    font-size: var(--site-small-font-size);
    font-family: var(--site-font-secondary);
    color: var(--bs-secondary-color);
    margin-bottom: 0;
}

/* Post byline/date on signal cards and feed listings (card.html's signal
   type, signal/index.html) — this rule was missing entirely until now, so
   this text has been rendering at ambient body size, not intended. */
.post-meta {
    font-size: var(--site-small-font-size);
    font-family: var(--site-font-secondary);
    color: var(--bs-secondary-color);
}

/* bio-card.html's bio blurb. Deliberately NOT distinguished from the
   "more about" link paragraph via :has(a) — author-supplied bio prose can
   itself contain links (confirmed: at least one author's bio-short does),
   which would wrongly match that selector. Real, separately-named class
   for the link line instead. */
.bio-text {
    font-size: var(--site-small-font-size);
    font-family: var(--site-font-secondary);
    color: var(--bs-secondary-color);
    margin-top: 0.5rem;
}

/* bio-card.html's "more about X" link line — always just the one link,
   nothing else. */
.bio-more-link {
    font-size: var(--site-small-font-size);
    font-family: var(--site-font-secondary);
    color: var(--bs-secondary-color);
    margin-bottom: 0;
}

/* toc.html's "On this page" heading. */
.toc-heading {
    font-family: var(--site-font-secondary);
    color: var(--bs-secondary-color);
    margin-bottom: 1rem;
}

/* On-page table of contents (toc.html + toc-active.js, both real vendored
   lf-ui files now — previously separately-maintained copies on each site
   that stayed byte-identical only by hand, and their supporting CSS
   wasn't shared at all, which is how lukefretwell ended up missing the
   scroll-margin-top rule below and got a visibly different scroll
   landing position than govfresh for the same TOC-link click. */
.toc-list {
    padding-left: 1rem;
    margin: 0;
    list-style-position: outside;
    list-style-type: square;
    font-size: var(--site-small-font-size);
}

.toc-list li {
    margin-inline-start: 0;
    margin-bottom: 0.25rem;
}

.toc-list li.toc-h3 {
    list-style-type: none;
    padding-inline-start: 0;
}

.toc-link, .toc-link:visited {
    color: var(--bs-body-color);
    text-decoration: none;
}

.toc-link:hover {
    color: var(--bs-link-color);
    text-decoration: underline;
}

.toc-link.active {
    color: var(--bs-link-color);
    font-family: "Public Sans Bold", var(--bs-font-sans-serif);
}

/* So a clicked TOC link doesn't land its heading flush at the very top of
   the viewport. */
.post h2, .post h3, .post h4 {
    scroll-margin-top: 1.5rem;
}

/* Bootstrap's own .lead is font-size:1.25rem/font-weight:300 — this theme
   renders "lead" (page-description) text at body size/weight/color
   instead, so the class stays as a semantic marker without that visual
   footprint; only the font-family (the secondary/description font) is
   still a deliberate departure from plain body text. Loads after
   bootstrap.min.css, so plain source-order specificity is enough to win,
   no !important needed. */
.lead {
    font-size: inherit;
    font-weight: inherit;
    font-family: var(--site-font-secondary);
    color: var(--bs-body-color);
    margin-bottom: 0;
    padding-bottom: 1rem;
}

/* Smaller, secondary-colored description tier — same font-family as .lead,
   for a description role that isn't the page-level one (e.g. a section
   intro line). Reusable in place of coining a one-off class per use. */
.lead-2 {
    font-size: var(--site-small-font-size);
    font-family: var(--site-font-secondary);
    color: var(--bs-body-color);
    margin-bottom: 0;
    padding-bottom: 1rem;
}

/* Buttons — base appearance (padding, border-radius) comes from Bootstrap's
   own .btn; this layer adds the shared color/shadow/motion treatment. */

.btn {
    background: var(--bs-body-color);
    color: var(--bs-body-bg);
    border: 0.0625rem solid var(--bs-border-color);
    box-shadow: var(--bs-box-shadow);
    font-family: "Roboto Mono Regular", var(--site-font-secondary);
    text-decoration: none;
    transition: transform var(--site-transition), box-shadow var(--site-transition), background var(--site-transition), color var(--site-transition);
}

.btn:hover {
    transform: var(--site-transform-hover);
    box-shadow: var(--bs-box-shadow-lg);
    background: var(--bs-body-bg);
    color: var(--bs-body-color);
    /* Bootstrap's own .btn:hover (higher specificity than the base .btn
       rule above, since a pseudo-class outweighs a plain class) resets
       border-color to transparent by default — it's designed for a
       .btn-primary/-secondary/etc. variant to supply a real hover color,
       which this bare .btn never adds. Without this, hover would fall
       through to that transparent default instead of staying the
       standard border token. */
    border-color: var(--bs-border-color);
    text-decoration: none;
}

.btn:active {
    transform: var(--site-transform-active);
}

/* Outline variants stay genuinely outlined — transparent background,
   colored border/text from whichever variant Bootstrap's own
   .btn-outline-* class already set via --bs-btn-color/-border-color —
   instead of the bare .btn solid dark-pill treatment above. A page needs
   a lighter-weight action next to a heavier one (e.g. "Download JSON"
   beside a copy-to-clipboard field) and Bootstrap's outline vocabulary
   already exists for exactly that; the higher-specificity attribute
   selector here is what lets it win over the plain .btn rule. */
.btn[class*="btn-outline-"] {
    background: var(--bs-btn-bg, transparent);
    color: var(--bs-btn-color);
    border-color: var(--bs-btn-border-color);
}

.btn[class*="btn-outline-"]:hover {
    background: var(--bs-btn-hover-bg);
    color: var(--bs-btn-hover-color);
    border-color: var(--bs-btn-hover-border-color);
}

/* CTA banner — fixed band shared by both consumers' subscribe/connect CTA.
   A real nested .container (via Bootstrap's own d-flex/align-items-center/
   justify-content-between utility classes in the markup) centers the
   button+sponsor row at Bootstrap's actual breakpoint max-widths,
   replacing a previous per-consumer hand-rolled
   `padding: 0.5rem max(1rem, calc((100vw - 1140px) / 2 + 1rem))` hack that
   only centered correctly above one hardcoded breakpoint. The outer
   #cta-banner stays full-bleed (background/border) so no custom flex CSS
   is needed here. cta-banner.html always emits this wrapper, not
   conditionally on a sponsor — a single consistent treatment across both
   consumers was the point. The button itself uses plain .btn above (its
   bg/border/shadow already read clearly against the band's own opaque
   background) — no contrast-inversion. */

.cta-banner {
    opacity: 0;
    pointer-events: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    background-color: var(--bs-body-bg);
    border-top: 0.0625rem solid var(--bs-border-color);
    padding: 0.5rem 0;
    z-index: 1000;
}

/* Visibility (hidden below md, inline at md+) comes from Bootstrap's own
   .d-none.d-md-inline on the markup — this only adds the font treatment. */
.cta-banner-sponsor {
    font-family: var(--site-font-secondary);
    font-size: 0.875rem;
}

.cta-banner-sponsor a {
    color: var(--bs-link-color);
    text-decoration: none;
}

.cta-banner-sponsor a:hover {
    text-decoration: underline;
}

/* Cards — position: relative needed for stretched card-link; display is
   left to Bootstrap's own .card (flex column), not overridden. */

.card {
    /* Bootstrap's own --bs-card-border-color defaults to the raw, never-
       themed --bs-border-color-translucent — a different, unrelated
       shade from the site's real border token used everywhere else
       (navbar, hr). Retargeting it here is enough; nothing else needs
       --bs-border-color-translucent overridden globally. */
    --bs-card-border-color: var(--bs-border-color);
    position: relative;
    width: 100%;
    overflow: hidden;
    box-shadow: var(--bs-box-shadow);
}

/* No lift/transform on any card, linked or not (a plain icon/caption
   theme card on /services previously still lifted, since only linked
   cards got the no-lift override) — just a subtle border/shadow darken as
   the hover affordance. Scoped to cards that actually go somewhere
   (.stretched-link makes the whole card clickable) — a plain stat/info
   card with no link of its own shouldn't visually react to a hover that
   does nothing. */
.card:has(.stretched-link):hover {
    --bs-card-border-color: color-mix(in srgb, var(--bs-border-color) 85%, black 15%);
    /* Same tight blur radii as --bs-box-shadow (lf-theme.css), just darker
       — hardcoded rather than the variable since this is specifically a
       darker-than-resting hover state, not reusable elsewhere. */
    box-shadow: 0 0.0625rem 0.125rem rgba(0,0,0,0.11), 0 0.0625rem 0.1875rem rgba(0,0,0,0.07);
}

/* Skip-to-content link — Bootstrap's own .visually-hidden-focusable
   already handles hiding it until focused; this only adds a visible
   "button" look for the focused state, which real Bootstrap has no
   opinion on. */
.visually-hidden-focusable:focus {
    padding: 0.5rem;
    background: var(--bs-body-bg);
    border: 0.125rem solid var(--bs-link-color);
    z-index: 1000;
}

/* Heading anchor links — generated by anchor_headings.html (Jekyll
   consumers, via anchorClass="heading-hashtag") or an equivalent transform
   (schemaGov, which has no Liquid to run that plugin, so it inlines the
   icon SVG itself rather than relying on Font Awesome — schemaGov
   doesn't load an icon font at all). A class, not an id — anchor_headings.html
   also hardcodes id="heading-hashtag" on every anchor it generates, which
   is harmless under Jekyll (never validated) but is a hard build error
   under Eleventy's IdAttributePlugin (duplicate ids across a page); the
   class is what both actually style off of. Hidden until the heading is
   hovered, so the page doesn't show a permanent link icon next to every
   heading. */

h2 a.heading-hashtag, h3 a.heading-hashtag, h4 a.heading-hashtag {
    color: var(--bs-body-color);
    display: none;
    text-decoration: none;
}

.heading-hashtag svg {
    width: 0.75em;
    height: 0.6em;
    vertical-align: middle;
}

h2:hover a.heading-hashtag, h3:hover a.heading-hashtag, h4:hover a.heading-hashtag {
    color: var(--bs-link-color);
    display: revert;
}

/* Icon/social links — body color by default, link color on hover, no
   underline (matches .breadcrumb-item a's treatment — .nav-link now
   underlines on hover to match .footer-nav, but icon links stay plain
   since they're icon-only glyphs, not text a reader scans for). */

.icon-link, .icon-link:visited {
    color: var(--bs-body-color);
    text-decoration: none;
}

.icon-link:hover {
    color: var(--bs-link-color);
}

/* External link icon — auto-appended to any absolute http(s) link inside
   prose content, so a reader knows before clicking that it leaves the
   current page for another site. .icon-link/.card-link/.btn are excluded
   because those already carry their own explicit affordance (an icon
   glyph, a stretched-link card, a button); .pdf is excluded so a
   consumer's own file-type icon for PDFs (if it has one) isn't fought
   over by two ::after rules on the same element. This can't know a given
   consumer's own domain, so an absolute link back to the site's own host
   (e.g. in prose that happens to spell it out in full) would still match
   here — a consumer with that pattern in its content needs a small local
   `content: none !important` override keyed to its own domain (see
   govfresh's style.css for the pattern).
   mask-image (not a plain <img>/background-image) is what lets a single-
   color SVG tint via background-color instead of needing a pre-colored
   asset per theme. Promoted here after two consumers independently built
   the same feature two different ways (one via this mask-image technique,
   one via a Font Awesome ligature) — one shared implementation instead of
   two drifted ones. Icon source: assets/img/icons/arrow-up-right-from-
   square.svg — each consumer vendors this one file (Font Awesome's free
   glyph, MIT-licensed, extracted as a static SVG so no icon font is
   required for it to work here). */
.post a[href^="http"]:not(.icon-link):not(.card-link):not(.btn):not([href$=".pdf"])::after {
    content: "";
    display: inline-block;
    width: 0.7em;
    height: 0.7em;
    background-color: var(--bs-body-color);
    margin-left: 0.3rem;
    vertical-align: middle;
    mask-image: url(/assets/img/icons/arrow-up-right-from-square.svg);
    mask-size: contain;
    mask-repeat: no-repeat;
    mask-position: center;
}

/* Body-color links — always body color regardless of theme, underline on
   hover. Used by social.html's default (card-grid) branch — baked
   directly into that shared include's markup as class="body-link", so
   this can't stay per-site the way .card-link's own hover/dark-mode
   treatment does (documented exception, tied to site-specific link
   classes) — a shared include emitting a fixed class needs shared CSS
   for it, or one consumer silently gets unstyled links the moment it
   actually calls that code path. */

.body-link, .body-link:visited {
    color: var(--bs-body-color);
    text-decoration: none;
}

.body-link:hover {
    text-decoration: underline;
}

/* Card heading link — standardized hover: link color, no underline.
   Previously diverged both ways: govfresh added an underline on hover
   but kept body-color (no color change at all in light mode); lukefretwell
   explicitly forced body-color on hover (no visual feedback beyond the
   container-level border/shadow change above).
   Selector is `.card .card-link:hover` (0,3,0), not the simpler
   `.card-link:hover` (0,2,0) — lukefretwell's own base rule
   `.card a.card-link { color: ... }` is (0,2,1), which would otherwise
   keep beating a lower-specificity hover rule regardless of source
   order, since lf-components.css loads before each consumer's own
   css/style.css. A second, plain `.card-link:hover` (0,2,0) covers
   .card-link used outside a .card (e.g. the homepage feature section) —
   lukefretwell's higher-specificity base rule only matches inside .card,
   so there's nothing to lose to there; govfresh's own base rule is only
   (0,1,0), so this still safely wins in both places it needs to. */
.card .card-link:hover,
.card-link:hover {
    color: var(--bs-link-color);
    text-decoration: none;
}

/* Stretched-link pattern — makes the whole card clickable, not just the
   link text. */
.card .body-link::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
}

/* Badges */

.badge {
    border: none;
    border-radius: var(--bs-border-radius);
    box-shadow: none;
    /* Explicit regular family, not just font-weight: normal on its own —
       a numeric reset doesn't help if an ancestor's family is "Public Sans
       Bold" (that family renders bold glyphs regardless of requested
       weight, same reasoning as the h1-h6 rule above). But unlike that
       bold-only family, "Public Sans" itself has real 400 and 700 faces
       registered (see lf-theme.css), so Bootstrap's own .badge
       font-weight: 700 still resolves to genuine bold glyphs within this
       family — the font-family override alone does not neutralize it.
       font-weight: 400 is needed too: a status badge is a label, not
       emphasis. */
    font-family: var(--bs-font-sans-serif);
    font-weight: 400;
}

a:has(> .badge) {
    text-decoration: none;
    color: inherit;
}

/* Eyebrow/caption label — a small heading-like tag above a page or section
   title ("sections", "schemas", "term set"). Sentence case, not
   text-transform: uppercase — full caps reads as shouting and is one more
   thing a non-technical reader has to parse as "not really a word". Replaces
   the old bare `text-uppercase small fw-semibold text-body-secondary`
   utility-class combo so the no-caps rule lives in one place instead of
   depending on every consumer remembering to drop `text-uppercase`. */
.lf-eyebrow {
    font-size: .875rem;
    font-weight: 600;
    color: var(--bs-secondary-color, var(--bs-body-color));
}

/* Breadcrumb — a real, clickable link (link-color, underlined) like any
   other link on the page, not a subdued one-off treatment; a reader should
   be able to tell it's clickable without hovering first. */

/* Wraps breadcrumb.html's markup — real Bootstrap .container plus this
   theme's own top padding, replacing the inline pt-2 utility so the value
   lives in one place (also targeted by the .hero-* padding-reduction rule
   above, via the + main descendant selector). */
.breadcrumb-bar {
    padding-top: 0.5rem;
}

/* Bootstrap's own .breadcrumb already sets display:flex, flex-wrap:wrap,
   list-style:none, and defaults its padding to 0 via
   --bs-breadcrumb-padding-x/-y — margin-bottom (Bootstrap's default is
   1rem) is set to a smaller value via Bootstrap's own variable instead of
   a flat property so the rest of the component's variable-driven behavior
   stays intact. A breadcrumb sitting flush against the heading right
   below it reads as unfinished, but Bootstrap's own 1rem is more gap than
   the two need. */
.breadcrumb {
    --bs-breadcrumb-margin-bottom: 0.75rem;
}

/* font-family/size lives on the item itself, not just the link inside it —
   put only on .breadcrumb-item a, a linked item's own font-size (inherited
   from the page's body text) differs from .active's, so the two <li>s
   establish different line-box heights and the shorter/smaller one's text
   renders visibly lower than its sibling. Setting it here instead sizes
   every item's line box identically regardless of whether it holds a link
   or the plain active text. */
.breadcrumb-item {
    font-family: var(--site-font-secondary);
    font-size: var(--site-small-font-size);
}

.breadcrumb-item a,
.breadcrumb-item a:visited {
    display: inline-block;
    color: var(--bs-link-color);
}

.breadcrumb-item+.breadcrumb-item::before {
    content: "/";
    padding: 0 0.5rem;
    color: var(--bs-secondary-color);
}

/* Bio callout — used identically by both consumers' bio.html */

.bio-callout {
    margin-top: 3rem;
    margin-bottom: 1rem;
    box-shadow: var(--bs-box-shadow);
    border-radius: 0.375rem;
    /* Bootstrap's real .alert-light sets its own color: var(--bs-alert-color)
       -> --bs-light-text-emphasis, a hardcoded gray unrelated to either
       site's --bs-body-color. Without this, the bio name heading and any
       other plain text in the box silently inherits that gray instead of
       real text color. */
    color: var(--bs-body-color);
}

.bio-avatar {
    filter: drop-shadow(0.1875rem 0.1875rem 0 var(--bs-border-color));
}

/* Divider between stacked subsections on one page (services.html's
   Services -> Themes -> Get started) — same border-top pt-4 mt-4 combo
   hand-typed on both <section>s in that file. Distinct role from
   govfresh's own .section-divider (that one separates a whole related-
   content section at a page's end, a bigger 3rem/1rem gap); this is a
   tighter divider between subsections within one flow. */
.subsection-divider {
    margin-top: 1.5rem;
    padding-top: 1.5rem;
    border-top: 0.0625rem solid var(--bs-border-color);
}

/* Footer — footer.html/footer-nav.html are shared markup; this is the
   matching shared CSS. Previously each consumer hand-rolled its own
   version of this (different padding, a bespoke desktop side-by-side
   flex layout on one consumer only, different font sizes) despite the
   markup being identical — that's exactly the kind of drift that's easy
   to miss because nothing tracks per-site CSS the way check-sync.js
   tracks shared files. Moving it here closes that gap: any future edit
   has to go through lf-ui, so both consumers move together. */

footer {
    font-family: var(--site-font-secondary);
}

/* CTA text inside an .alert.alert-light (footer.html's feedback link,
   demos.html's subscribe link) — color comes from .bio-callout's own rule
   above, not duplicated here since .alert-light's default gray text is
   actually fine for a muted callout, unlike the bio box. */
.cta-text {
    font-size: var(--site-small-font-size);
}

.alert .cta-text {
    font-family: var(--site-font-secondary);
}

.footer-bar {
    padding-top: 1rem;
    padding-bottom: 1.5rem;
}

.footer-nav {
    list-style: none;
    padding: 0;
    margin: 0 0 0.75rem 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
}

.footer-nav li {
    margin-inline-start: 0;
}

.footer-nav li a,
.footer-bar p a {
    color: var(--bs-body-color);
    text-decoration: none;
    font-size: var(--site-small-font-size);
}

.footer-nav li a:hover,
.footer-bar p a:hover {
    color: var(--bs-link-color);
    text-decoration: underline;
}

/* Layout primitives — .lf-cluster, .lf-stack, and .lf-flank are gone;
   .lf-cluster/.lf-stack were nothing but display:flex + a fixed gap,
   exactly what Bootstrap's own .d-flex.flex-wrap.align-items-center.gap-*
   (cluster) and .d-flex.flex-column.gap-* (stack, align-items:stretch is
   flex's own default) already do; .lf-flank (the "sidebar" pattern —
   content-sized first child, growing second child) was converted to real
   .row + .col-auto/.col at all 6 usages. Every former usage of all three
   now uses those directly. .lf-grid stays: a container-relative auto-fit
   grid, no real Bootstrap equivalent since row-cols-* is
   viewport-breakpoint-based, not container-relative. */

.lf-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(var(--min-column-size, 20ch), 100%), 1fr));
    --min-column-size: initial;
    margin-block: 0;
    margin-inline: 0;
    gap: var(--lf-space-m);
}

@media (max-width: 768px) {
    .lf-grid {
        --min-column-size: 100%;
    }
}

/* Image sizing — previously duplicated per-consumer with both a
   different scale (lukefretwell: xs/sm/md/lg/xxl; govfresh: xxxs through
   xxl, finer-grained) and a different property (lukefretwell: max-width,
   scales down but never forces upscaling past a small source image's
   real size; govfresh: width, forces the exact size regardless of
   intrinsic dimensions). Currently invisible since neither consumer's
   markdown content actually invokes these classes yet — reconciled here
   on govfresh's fuller scale, using max-width (the safer default) before
   either site's content starts relying on a particular behavior. Base
   img{} rule (scale down to fit the container, respecting these caps)
   needs to be shared alongside the scale itself, or the same class
   renders differently depending on which consumer's copy of this rule
   is present. */
img {
    height: auto;
    width: 100%;
}

img.xxxs { max-width: 2rem; }
img.xxs { max-width: 8rem; }
img.xs { max-width: 10rem; }
img.sm { max-width: 12.5rem; }
img.md { max-width: 15rem; }
img.lg { max-width: 18.75rem; }
img.xl { max-width: 20rem; }
img.xxl { max-width: 50rem; }

/* Data tables — ported from scangov/components/public/css/scangov.css.
   Bootstrap's own .table only draws horizontal rules between rows; this
   adds a full border on every cell plus a row-hover highlight, for
   tables that are actually being read/scanned as reference data rather
   than skimmed. Excludes .table-sm — a consumer that wants a dense,
   minimal table (e.g. an admin/dashboard view) opts out with that
   modifier, the same way scangov's own admin tables do. */
.table:not(.table-sm) > :not(caption) > * {
    border-width: var(--bs-border-width) 0;
}

.table:not(.table-sm) > :not(caption) > * > * {
    border-width: 0 var(--bs-border-width);
}

.table:not(.table-sm) > tbody > tr:hover > * {
    --bs-table-color-state: var(--bs-table-hover-color);
    --bs-table-bg-state: var(--bs-table-hover-bg);
}

.table:not(.table-sm) {
    font-size: 0.95rem;
}

/* Header row reads as a label for the data below it, not more content of
   the same weight — same secondary-color role .text-body-secondary/
   breadcrumbs/footer already use, not a new one-off gray. */
.table th {
    color: var(--bs-secondary-color);
    font-family: var(--site-font-secondary);
}

/* Code blocks — ported from scangov/components/public/css/code.css.
   Two rule sets, one shared palette (--code-* tokens, lf-theme.css):
   Rouge/Pygments classes for the Jekyll consumers (kramdown's fenced code
   blocks tokenize this way) and Prism.js classes for schemaGov (Eleventy
   + @11ty/eleventy-plugin-syntaxhighlight). Different tokenizers, same
   token *meanings* — each selector below maps to whichever --code-*
   variable is the closest conceptual match, so both ecosystems read as
   the same design even though neither shares a class name with the
   other. font-family is --bs-font-monospace directly, not
   --site-font-secondary — code should stay genuinely monospace even if
   the site's secondary/UI font is ever changed to something else (same
   reasoning as the code/kbd/pre/samp rule in lf-theme.css). */

.highlight pre,
pre.highlight,
pre[class*="language-"],
pre:has(code[class*="language-"]) {
    background-color: var(--code-bg);
    color: var(--code-name);
    border-radius: var(--bs-border-radius);
    font-family: var(--bs-font-monospace);
    margin-bottom: 2rem;
    padding: 1rem;
}

/* Rouge/Pygments (govfresh, lukefretwell) */

.highlight .hll { background-color: var(--code-bg-highlight-line); }
.highlight .c { color: var(--code-comment); }
.highlight .err { background-color: var(--code-error-bg); color: var(--code-error); }
.highlight .k { color: var(--code-keyword); }
.highlight .l { color: var(--code-literal); }
.highlight .n { color: var(--code-name); }
.highlight .o { color: var(--code-operator); }
.highlight .p { color: var(--code-punctuation); }
.highlight .c1, .highlight .ch, .highlight .cm, .highlight .cp, .highlight .cpf, .highlight .cs { color: var(--code-comment); }
.highlight .gd { color: var(--code-operator); }
.highlight .ge { font-style: italic; }
.highlight .gi { color: var(--code-generic-inserted); }
.highlight .gs { font-weight: 700; }
.highlight .gu { color: var(--code-comment); }
.highlight .kc, .highlight .kd { color: var(--code-keyword); }
.highlight .kn { color: var(--code-operator); }
.highlight .kp, .highlight .kr { color: var(--code-keyword); }
.highlight .kt { color: var(--code-type); }
.highlight .ld { color: var(--code-string-affix); }
.highlight .m { color: var(--code-number); }
.highlight .s { color: var(--code-string); }
.highlight .na { color: var(--code-class-function); }
.highlight .nb { color: var(--code-name); }
.highlight .nc { color: var(--code-class-function); }
.highlight .no { color: var(--code-keyword); }
.highlight .nd { color: var(--code-class-function); }
.highlight .ni { color: var(--code-name); }
.highlight .ne, .highlight .nf { color: var(--code-class-function); }
.highlight .nl, .highlight .nn { color: var(--code-name); }
.highlight .nx { color: var(--code-class-function); }
.highlight .py { color: var(--code-name); }
.highlight .nt { color: var(--code-operator); }
.highlight .nv { color: var(--code-name); }
.highlight .ow { color: var(--code-operator); }
.highlight .w { color: var(--code-name); }
.highlight .mb, .highlight .mf, .highlight .mh, .highlight .mi, .highlight .mo { color: var(--code-number); }
.highlight .s2, .highlight .sb, .highlight .sc, .highlight .sd { color: var(--code-string-affix); }
.highlight .se { color: var(--code-literal); }
.highlight .s1, .highlight .sh, .highlight .si, .highlight .sr, .highlight .ss, .highlight .sx { color: var(--code-string-affix); }
.highlight .bp, .highlight .vc, .highlight .vg, .highlight .vi { color: var(--code-name); }
.highlight .il { color: var(--code-number); }

/* Prism.js (schemaGov) — class-name groupings taken from
   @11ty/eleventy-plugin-syntaxhighlight's own demo/prism-theme.css, not
   guessed, so every token Prism actually emits is covered. */

.token.comment, .token.prolog, .token.doctype, .token.cdata { color: var(--code-comment); }
.token.punctuation { color: var(--code-punctuation); }
.token.property, .token.tag, .token.constant, .token.symbol, .token.deleted { color: var(--code-class-function); }
.token.boolean, .token.number { color: var(--code-number); }
.token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: var(--code-string); }
.token.operator, .token.entity, .token.url, .token.variable { color: var(--code-operator); }
.token.atrule, .token.attr-value, .token.function { color: var(--code-class-function); }
.token.keyword { color: var(--code-keyword); }
.token.regex, .token.important { color: var(--code-string-affix); }
.token.class-name { color: var(--code-type); }
.token.important, .token.bold { font-weight: 700; }
.token.italic { font-style: italic; }

/* Prism's max-height/overflow behavior (was schemaGov-local, moved here
   so it isn't split across two files from the same component). */
pre[class*="language-"], pre {
    max-height: 32rem;
    overflow: auto;
}

/* A link to #<accordion-item-id> should actually reveal that item, not
   just scroll a collapsed, still-empty-looking panel into view — Bootstrap
   only opens a .collapse via its own JS/`.show` class, so :target is the
   one pure-CSS hook available for "arrived via link" state. */
.accordion-collapse:target {
    display: block;
}

/* Copy-to-clipboard field (js/copy-field.js) — an identifier's own natural
   length, not the full container width; a schema ID or IRI is a value to
   copy, not prose to read at paragraph width. */
.copy-field {
    max-width: 34rem;
}

/* A schema field table's Type column (schemaGov's profile pages) can hold
   a long pipe-separated enum with no code list to move it to (a schema.org
   @type choice list, say) — Bootstrap's table default keeps <code> on one
   line and stretches the column to fit, which for a 10-value enum forces
   the whole row absurdly wide. Wrap it like ordinary text instead. */
.gs-field-table code {
    font-size: 0.8125rem;
    white-space: normal;
    word-break: break-word;
}


/* Dark-mode icon color — Font Awesome glyphs follow body text color. */
[data-bs-theme=dark] .fa,
[data-bs-theme=dark] .fa-solid,
[data-bs-theme=dark] .fa-regular,
[data-bs-theme=dark] .fa-light,
[data-bs-theme=dark] .fa-duotone,
[data-bs-theme=dark] .fab,
[data-bs-theme=dark] .fa-brands,
[data-bs-theme=dark] .svg-inline--fa {
    color: var(--bs-body-color) !important;
    fill: currentColor !important;
}

[data-bs-theme=dark] .btn .fa,
[data-bs-theme=dark] .btn .fa-solid,
[data-bs-theme=dark] .btn .fa-regular,
[data-bs-theme=dark] .btn .fa-brands,
[data-bs-theme=dark] .btn .svg-inline--fa {
    color: inherit !important;
}

/* ---- Shared base content rules --------------------------------------
   These used to be copy-pasted into each site's own style.css (and were
   missing from schemaGov, which has no site stylesheet), so the same
   markup rendered differently per site. lukefretwell's treatment is the
   standard. A site stylesheet must not redefine any of these. */

/* Prose links: link color, underlined, visited purple, underline off on
   hover. Class-based components (.btn, .card-link, .icon-link, .nav-link,
   ...) out-specify this on their own. */
main a, main a:visited {
    text-decoration: underline;
    color: var(--bs-link-color);
}

main a:visited {
    color: var(--site-link-visited);
}

main a:hover {
    text-decoration: none;
}

a {
    transition: color var(--site-transition), opacity var(--site-transition);
}

.btn:visited, .btn:visited:hover {
    color: inherit;
}

main a.btn, main a.btn:visited {
    text-decoration: none;
}

.post h2 { padding-top: 1rem; padding-bottom: 0.5rem; }
.post h3, .post h4 { padding-top: 1rem; padding-bottom: 1rem; }

/* Stretched card link — covers entire card */
.card a.card-link {
    color: var(--bs-body-color);
    text-decoration: none;
}

.card a.card-link::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
}

.card a.card-link:focus-visible {
    outline: none;
}

.card:has(a.card-link:focus-visible) {
    outline: 0.125rem solid var(--bs-link-color);
    outline-offset: 0.125rem;
}

.card a.card-link:visited { color: var(--bs-body-color); }

.card h2, .card h3, .card h4 { margin-bottom: 0.25rem; }

figcaption p { margin: 0.25rem 0; }

blockquote {
    border-left: 0.5rem solid var(--bs-secondary-color);
    margin-left: 1.5625rem;
    padding: 0 1.5625rem;
    margin-bottom: 1.5625rem;
    margin-top: 2rem;
}

blockquote p, blockquote ul {
    font-family: var(--site-font-secondary);
    padding-bottom: 0;
    padding-top: 0.25rem;
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        transition: none !important;
        animation: none !important;
    }
    .card:hover, .btn:hover, .btn:active {
        transform: none;
    }
}
