/*
  Public blog — the index, the article page, and the block vocabulary an article is
  written in.

  Built entirely on marketing.css's tokens (--ink, --deep, --coral, --border, and the
  three font families), so the blog reads as part of the same site rather than a bolted-on
  section. Nothing here is a new colour.

  The .bp-* classes at the bottom are the CONTRACT with the editor: they are the only
  classes an author can use, they are what the drag-and-drop blocks insert, and they are
  what the sanitiser's allowlist is sized around. Adding a block means adding it in three
  places — here, in the block palette (admin-blog-editor.js) and, if it needs new tags, in
  BlogContentSanitizer.
*/

/* Narrower than .wrap: an article is read line by line, and a 1240px measure is far past
   the ~75 characters where prose stops being comfortable. */
.wrap-narrow { max-width: 760px; }

/* ---------- index ---------- */

.blog-hero {
  padding: 88px 32px 40px;
  background: linear-gradient(180deg, var(--chip) 0%, var(--bg) 100%);
  border-bottom: 1px solid var(--border);
}
.blog-hero h1 { font-family: var(--disp); font-size: clamp(34px, 4vw, 52px); line-height: 1.08; margin: 14px 0 12px; color: var(--ink); }
.blog-hero-sub { font-size: 17px; line-height: 1.6; color: var(--muted); max-width: 620px; margin: 0; }

.blog-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 26px; }
@media (max-width: 980px) { .blog-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .blog-grid { grid-template-columns: 1fr; } }

.blog-card {
  display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--border); border-radius: 18px;
  overflow: hidden; transition: transform .25s, box-shadow .25s;
}
.blog-card:hover { transform: translateY(-4px); box-shadow: 0 20px 44px rgba(34, 26, 56, .10); }
.blog-card-img { display: block; aspect-ratio: 16 / 9; overflow: hidden; background: var(--chip); }
.blog-card-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.blog-card-body { padding: 22px 24px 24px; display: flex; flex-direction: column; flex: 1; }
.blog-card h2 { font-family: var(--disp); font-size: 20px; line-height: 1.25; margin: 0 0 10px; }
.blog-card h2 a { color: var(--ink); text-decoration: none; }
.blog-card h2 a:hover { color: var(--deep); }
.blog-card p { font-size: 14.5px; line-height: 1.6; color: var(--muted); margin: 0 0 16px; flex: 1; }
.blog-card-meta {
  display: flex; gap: 14px; align-items: center;
  font-family: var(--mono); font-size: 11.5px; letter-spacing: .04em; color: var(--faint);
}

.blog-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.blog-tags span {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  background: var(--chip); color: var(--deep); border-radius: 100px; padding: 4px 10px;
}

.blog-empty { text-align: center; color: var(--muted); padding: 60px 0; }

.blog-pager { display: flex; align-items: center; justify-content: center; gap: 18px; margin-top: 52px; }
.blog-pager-btn {
  font-weight: 700; font-size: 14.5px; color: var(--deep); text-decoration: none;
  border: 1px solid var(--border); background: var(--card); border-radius: 100px; padding: 10px 20px;
}
.blog-pager-btn:hover { border-color: var(--deep); background: var(--chip); }
.blog-pager-at { font-family: var(--mono); font-size: 12px; color: var(--faint); }

/* ---------- article ---------- */

/*
  The reset is load-bearing, not tidiness.

  marketing.css:28 sets `header { position: fixed; top: 0; left: 0; right: 0; z-index: 100 }`
  for the site's sticky top bar — on the bare ELEMENT. An article header written as
  <header> inherited it, left the flow, and rendered pinned across its own body text. The
  renderer now emits a <div>, and this pins that down for anything that reaches for a
  <header> inside an article later.
*/
.blog-article, .blog-article header, .blog-article-head {
  position: static;
  inset: auto;
  z-index: auto;
}

.blog-article-head { padding: 56px 32px 30px; }

/*
  The same element-name trap as <header>, one line further down marketing.css:
  `nav { background: rgba(22,17,46,.86); backdrop-filter: blur(14px); border-bottom: ... }`
  is the site's dark top bar, on the bare element. A breadcrumb and a pager are both
  genuinely <nav> — that is the correct element for them and screen readers rely on it —
  so the element stays and the site-bar styling is undone here instead.
*/
.blog-crumbs, .blog-pager {
  background: none;
  backdrop-filter: none;
  border-bottom: none;
}

.blog-crumbs { font-family: var(--mono); font-size: 11.5px; letter-spacing: .06em; color: var(--faint); margin-bottom: 20px; }
.blog-crumbs a { color: var(--muted); text-decoration: none; }
.blog-crumbs a:hover { color: var(--deep); }
.blog-crumbs span { margin: 0 6px; }

.blog-article-head h1 {
  font-family: var(--disp); font-size: clamp(30px, 3.8vw, 46px); line-height: 1.12;
  margin: 0 0 16px; color: var(--ink); letter-spacing: -.01em;
}
.blog-standfirst { font-size: 18px; line-height: 1.6; color: var(--body); margin: 0 0 22px; }
.blog-byline {
  display: flex; flex-wrap: wrap; gap: 16px; align-items: center;
  font-family: var(--mono); font-size: 12px; color: var(--faint);
  padding-top: 18px; border-top: 1px solid var(--border);
}
.blog-author { color: var(--deep); font-weight: 700; }

.blog-cover { margin: 0 0 8px; border-radius: 18px; overflow: hidden; border: 1px solid var(--border); }
.blog-cover img { display: block; width: 100%; height: auto; }

/* ---------- the article body ----------
   Every rule below is scoped under .blog-body, which is also the selector the sanitiser
   prefixes onto the editor's own stylesheet — so an author's CSS and this baseline live
   at the same specificity and in the same box, and neither can reach the page around it. */

.blog-body { font-size: 17px; line-height: 1.75; color: var(--body); padding: 34px 0 10px; }
.blog-body > *:first-child { margin-top: 0; }
.blog-body p { margin: 0 0 22px; }
.blog-body h2 {
  font-family: var(--disp); font-size: clamp(24px, 2.4vw, 30px); line-height: 1.2;
  color: var(--ink); margin: 46px 0 16px; letter-spacing: -.01em;
}
.blog-body h3 { font-family: var(--disp); font-size: 20px; line-height: 1.3; color: var(--ink); margin: 34px 0 12px; }
.blog-body a { color: var(--deep); text-decoration: underline; text-underline-offset: 3px; }
.blog-body a:hover { color: var(--deep-hover); }
.blog-body strong, .blog-body b { color: var(--ink); font-weight: 700; }
.blog-body ul, .blog-body ol { margin: 0 0 22px; padding-left: 24px; }
.blog-body li { margin-bottom: 9px; }
.blog-body img { max-width: 100%; height: auto; border-radius: 12px; }
.blog-body code {
  font-family: var(--mono); font-size: .88em;
  background: var(--chip); color: var(--deep); border-radius: 5px; padding: 2px 6px;
}
.blog-body pre {
  background: var(--dark); color: var(--dk-text); border-radius: 14px;
  padding: 18px 20px; overflow-x: auto; font-family: var(--mono); font-size: 13.5px; line-height: 1.6;
}
.blog-body pre code { background: none; color: inherit; padding: 0; }
.blog-body hr { border: none; border-top: 1px solid var(--border); margin: 40px 0; }

/* The standfirst inside the body, when an author opens with one. */
.blog-body .bp-lead { font-size: 19px; line-height: 1.62; color: var(--ink); margin-bottom: 26px; }

/* Tables are the one thing that reliably breaks a narrow measure, so the wrapper is
   mandatory in the markup contract and scrolls rather than overflowing the page. */
.blog-body .bp-table-wrap { overflow-x: auto; margin: 0 0 26px; border: 1px solid var(--border); border-radius: 14px; background: var(--card); }
.blog-body table { width: 100%; border-collapse: collapse; font-size: 14.5px; min-width: 460px; }
.blog-body th, .blog-body td { padding: 11px 16px; text-align: left; border-bottom: 1px solid var(--border); }
.blog-body th { font-family: var(--mono); font-size: 11.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); background: var(--bg); }
.blog-body tr:last-child td { border-bottom: none; }
.blog-body td:not(:first-child) { font-family: var(--mono); font-size: 13.5px; white-space: nowrap; }

.blog-body .bp-callout {
  background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--deep);
  border-radius: 12px; padding: 18px 20px; margin: 0 0 26px;
}
.blog-body .bp-callout b { display: block; margin-bottom: 6px; color: var(--ink); }
.blog-body .bp-callout p { margin: 0; font-size: 15.5px; color: var(--muted); }
.blog-body .bp-callout.warn { border-left-color: var(--coral); background: var(--chip-warm); }

.blog-body .bp-takeaways {
  background: var(--chip); border-radius: 16px; padding: 24px 26px; margin: 0 0 30px;
}
.blog-body .bp-takeaways b {
  display: block; font-family: var(--mono); font-size: 11.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--deep); margin-bottom: 14px;
}
.blog-body .bp-takeaways ul { margin: 0; padding-left: 20px; }
.blog-body .bp-takeaways li { color: var(--ink); font-size: 15.5px; }

.blog-body .bp-quote { margin: 0 0 28px; padding: 0 0 0 22px; border-left: 3px solid var(--coral); }
.blog-body .bp-quote p { font-family: var(--disp); font-size: 21px; line-height: 1.45; color: var(--ink); margin: 0; }

.blog-body .bp-figure { margin: 0 0 28px; }
.blog-body .bp-figure figcaption { font-size: 13px; color: var(--faint); margin-top: 9px; text-align: center; }

.blog-body .bp-steps { margin: 0 0 28px; }
.blog-body .bp-steps ol { counter-reset: step; list-style: none; padding: 0; margin: 0; }
.blog-body .bp-steps li {
  position: relative; padding-left: 46px; margin-bottom: 18px; counter-increment: step;
}
.blog-body .bp-steps li::before {
  content: counter(step);
  position: absolute; left: 0; top: 0;
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--deep); color: #fff;
  display: grid; place-items: center;
  font-family: var(--mono); font-size: 13px; font-weight: 700;
}
.blog-body .bp-steps li b { display: block; color: var(--ink); margin-bottom: 3px; }

.blog-body .bp-stat-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin: 0 0 28px; }
@media (max-width: 560px) { .blog-body .bp-stat-row { grid-template-columns: 1fr; } }
.blog-body .bp-stat { background: var(--card); border: 1px solid var(--border); border-radius: 14px; padding: 18px; text-align: center; }
.blog-body .bp-stat b { display: block; font-family: var(--disp); font-size: 26px; color: var(--deep); }
.blog-body .bp-stat span { font-size: 12.5px; color: var(--muted); }

.blog-body .bp-cta {
  background: var(--dark); border-radius: 18px; padding: 30px 32px; margin: 34px 0; text-align: center;
}
.blog-body .bp-cta b { display: block; font-family: var(--disp); font-size: 22px; color: #fff; margin-bottom: 8px; }
.blog-body .bp-cta p { color: var(--dk-muted); font-size: 15px; margin: 0 0 20px; }

.bp-btn {
  display: inline-block; background: var(--coral); color: #fff; text-decoration: none;
  font-weight: 700; font-size: 15px; border-radius: 100px; padding: 13px 28px;
}
.bp-btn:hover { background: var(--coral-deep); color: #fff; }

/* ---------- FAQ ----------
   <details> rather than a JS accordion: it is open-able without scripts, which matters
   because the same content is emitted as FAQPage JSON-LD and should be readable by
   whatever arrives, including a crawler that runs nothing. */
.blog-faq { margin: 44px 0 10px; padding-top: 34px; border-top: 1px solid var(--border); }
.blog-faq h2 { font-family: var(--disp); font-size: 26px; color: var(--ink); margin: 0 0 20px; }
.blog-faq-item { border-bottom: 1px solid var(--border); padding: 4px 0; }
.blog-faq-item summary {
  cursor: pointer; padding: 14px 0; font-weight: 700; color: var(--ink); font-size: 16px;
  list-style: none; display: flex; justify-content: space-between; align-items: center; gap: 16px;
}
.blog-faq-item summary::-webkit-details-marker { display: none; }
.blog-faq-item summary::after { content: "+"; font-family: var(--mono); color: var(--deep); font-size: 20px; }
.blog-faq-item[open] summary::after { content: "−"; }
.blog-faq-item p { margin: 0 0 16px; color: var(--muted); line-height: 1.65; }

.blog-more { padding-top: 70px; }
.blog-more-title { font-family: var(--disp); font-size: 26px; color: var(--ink); margin: 0 0 26px; }

/* ================================================================ two-column shell

   Both blog pages — the index and an article — share one layout primitive: a content
   column with an optional sidebar beside it. The sidebar is what the page gains width
   from; the ARTICLE MEASURE DOES NOT CHANGE. Prose past roughly 75 characters a line
   stops being comfortable, which is the whole reason .wrap-narrow exists, so the second
   column is added beside the text rather than taken out of it.

   Three states, and all three matter:

     .blog-shell                  index, no sidebar   -> one full-width track (as before)
     .blog-shell.is-article       article, no sidebar -> one 760px track, centred (as before)
     .blog-shell.has-side         either, with sidebar -> content + 320px rail

   The no-sidebar states are not a fallback nobody will hit. An install with no banners and
   all three sidebar widgets switched off lands there, and it has to look exactly like the
   blog did before this feature existed — not like a layout missing a column.
*/

.blog-listing { padding: 72px 0 96px; }

.blog-shell-in {
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 32px;
  display: grid;
  /* minmax(0, …) rather than a bare width: a grid track's default min is auto, so one long
     unbroken string in a post title or a wide code block would push the track past its
     stated size and take the page sideways with it. */
  grid-template-columns: minmax(0, 1fr);
  gap: 40px;
  justify-content: center;
  /*
    Stretch, NOT start, and this is what makes the sticky sidebar work.

    With align-items:start the sidebar track shrinks to its own content — measured at 532px
    beside a 7730px article. position:sticky can only travel inside its containing block, so
    the widgets unpinned after half a screen and scrolled away for the rest of the page,
    while still computing as position:sticky. Stretching the track to the row height gives
    them the whole article to stick down.
  */
  align-items: stretch;
}
.blog-shell.is-article .blog-shell-in { grid-template-columns: minmax(0, 760px); }
.blog-shell.has-side .blog-shell-in { grid-template-columns: minmax(0, 1fr) 320px; }
.blog-shell.is-article.has-side .blog-shell-in { grid-template-columns: minmax(0, 760px) 320px; }

/*
  The top gap belongs to the SHELL, not to whatever happens to be first inside it.

  It used to come from .blog-topslot's margin and .blog-article-head's 56px padding. Both
  are inside the left column, so with no banner configured the article text still cleared
  the nav — and the SIDEBAR, which has no padding of its own, started flush against it. The
  first widget sat hard under the top menu while the prose beside it began 56px lower.

  Putting the gap on .blog-shell-in means both columns start from the same line whether or
  not a banner is there. The article head's padding drops by the same amount the shell now
  adds, so the prose lands exactly where it always did — measured: crumbs unchanged.
*/
.blog-shell.is-article .blog-shell-in { padding-top: 40px; }
.blog-shell .blog-article-head { padding: 16px 0 30px; }

/*
  The article's own 32px inset, kept.

  .wrap-narrow was a 760px CONTAINER with .wrap's 32px padding inside it, so the text
  actually rendered at 696px. Moving the gutter out to .blog-shell-in and leaving the track
  at 760px would have widened every line by 64px — measured, 696 -> 760, about 85 characters
  where the comment on .wrap-narrow says 75 is already the limit. The page was asked to get
  wider; the prose was not. This puts those 32px back.
*/
.blog-shell.is-article .blog-main { padding-inline: 32px; }

/* With the rail present the index has ~816px for cards, which is two comfortably and three
   cramped. Two, and they come out slightly wider than the old three — the cards get better,
   not worse, for making room. */
.blog-shell.has-side .blog-grid { grid-template-columns: repeat(2, 1fr); }

/* ---------- top banner slot ---------- */

/* Spans BOTH grid tracks, so the banner runs the full width of the page and the article
   and the sidebar both start beneath it. Nested inside .blog-main it was only as wide as
   the article, with the sidebar starting level with it — not what a leaderboard is for. */
.blog-topslot { grid-column: 1 / -1; margin: 0 0 28px; display: flex; flex-direction: column; gap: 16px; }
.blog-topslot:empty { display: none; }

.blog-banner { line-height: 0; }
.blog-banner img { max-width: 100%; height: auto; display: block; margin: 0 auto; border-radius: 12px; }
/* Author-supplied markup, so it gets a containing box rather than free rein over the
   layout: an ad tag that returns a wide iframe would otherwise take the page sideways. */
.blog-banner > *:not(img):not(a) { max-width: 100%; }

/* ---------- sidebar ---------- */

.blog-side { min-width: 0; }
.blog-side-inner {
  position: sticky;
  /*
    24px, not the 88px a fixed top bar would need.

    marketing.css makes <header> fixed, but the blog shell's top bar is a <nav> and
    measures position:static — checked, not assumed. Reserving 88px here would have left an
    unexplained gap above the widgets on every scroll. If the blog nav is ever made fixed,
    this is the number that has to grow with it.
  */
  top: 24px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.blog-widget {
  background: var(--chip);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 18px 18px 20px;
}
.blog-widget h2 {
  font-family: var(--mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); margin: 0 0 14px; font-weight: 700;
}

.blog-side-posts { list-style: none; margin: 0; padding: 0; }
.blog-side-posts li + li { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.blog-side-posts a {
  display: block; font-size: 14px; line-height: 1.4; font-weight: 600;
  color: var(--ink); text-decoration: none;
}
.blog-side-posts a:hover { color: var(--deep); }
.blog-side-posts time { display: block; font-family: var(--mono); font-size: 10.5px; color: var(--faint); margin-top: 5px; }

.blog-side-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.blog-side-tags a {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  background: var(--bg); color: var(--deep); border: 1px solid var(--border);
  border-radius: 100px; padding: 4px 10px; text-decoration: none;
}
.blog-side-tags a:hover { border-color: var(--deep); }

.blog-side-cta { background: var(--deep); border-color: var(--deep); }
.blog-side-cta h2 { color: rgba(255, 255, 255, .62); font-family: var(--disp); font-size: 17px;
  letter-spacing: -.01em; text-transform: none; line-height: 1.25; margin-bottom: 8px; }
.blog-side-cta h2, .blog-side-cta p { color: #fff; }
.blog-side-cta p { font-size: 13px; line-height: 1.55; color: rgba(255, 255, 255, .72); margin: 0 0 14px; }
.blog-side-btn {
  display: inline-block; background: var(--coral); color: #fff; text-decoration: none;
  font-size: 13px; font-weight: 700; border-radius: 100px; padding: 9px 16px;
}
.blog-side-btn:hover { filter: brightness(1.06); }

/* ---------- article tags, now at the foot ---------- */

/* They used to sit above the <h1>. A reader arriving from a search result wants the
   headline first; tags answer "what else is like this", which is a question you have after
   reading, not before. The chips themselves are unchanged — .blog-tags is shared with the
   index cards, so only the placement moved. */
.blog-article-tags { margin: 36px 0 0; padding-top: 24px; border-top: 1px solid var(--border); }
.blog-article-tags h2 {
  font-family: var(--mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); margin: 0 0 12px; font-weight: 700;
}
.blog-article-tags .blog-tags { margin-bottom: 0; }
.blog-article-tags a { text-decoration: none; }

/* ---------- responsive ---------- */

@media (max-width: 1160px) {
  /* The rail drops below the content rather than squeezing it. Once there it is no longer
     a column, so the sticky offset would pin it mid-page — hence static.

     Both article rules are written at FOUR classes, matching the .is-article.has-side
     selector they have to beat. Written as ".blog-shell.is-article .blog-shell-in" the
     cap is only three classes, so on an article that HAS a sidebar the single-column rule
     above out-specified it and the measure ran to the full width of the page — 1036px at
     a 1100px window, which is exactly the unreadable line length .wrap-narrow exists to
     prevent. Specificity, not source order, was what decided it. */
  .blog-shell.has-side .blog-shell-in { grid-template-columns: minmax(0, 1fr); }
  .blog-shell.is-article.has-side .blog-shell-in,
  .blog-shell.is-article:not(.has-side) .blog-shell-in { grid-template-columns: minmax(0, 760px); }
  .blog-side-inner { position: static; }
  .blog-side { max-width: 760px; width: 100%; }
  /* The rail is gone from beside the cards, so the grid gets its own breakpoints back. */
  .blog-shell.has-side .blog-grid { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 980px) {
  .blog-shell.has-side .blog-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 700px) {
  .blog-shell-in { padding: 0 20px; }
  .blog-listing { padding: 48px 0 64px; }
  .blog-shell.is-article .blog-shell-in { padding-top: 24px; }
  .blog-shell .blog-article-head { padding: 12px 0 24px; }
  /* The 32px article inset is a DESKTOP measure control — it exists to hold the line
     length down on a wide screen. On a phone the shell's own 20px gutter is already the
     whole margin, and keeping the inset as well took the text from 335px to 271px:
     narrower than before this feature, on the screen that can least afford it. */
  .blog-shell.is-article .blog-main { padding-inline: 0; }
}

@media (max-width: 640px) {
  .blog-shell.has-side .blog-grid { grid-template-columns: 1fr; }
}

/* ================================================================ block variants

   Every .bp-* block has a default look and a small set of named alternates, applied as a
   modifier class. The editor exposes them as a "Style" dropdown on the selected block, so
   one article can be all callouts-as-notes and the next all callouts-as-warnings without
   either author touching CSS.

   Two rules hold this together and both matter:
     - a variant only ever changes COLOUR, BORDER and BACKGROUND, never the box model. The
       sanitiser allowlists these class names, and a variant that changed padding would
       reflow the article depending on which one was picked.
     - every variant is built from marketing.css tokens. Nothing here invents a colour, so
       the blog still reads as one site whichever combination an author lands on.
*/

/* ---------- callout ---------- */
.blog-body .bp-callout.tip { border-left-color: var(--green, #1a7f5a); background: var(--chip); }
.blog-body .bp-callout.tip b { color: var(--green, #1a7f5a); }
.blog-body .bp-callout.note { border-left-color: var(--border); background: var(--bg); }
.blog-body .bp-callout.note b { color: var(--muted); }
/* No left rule and no fill: for an aside that should read as a quiet parenthesis rather
   than an interruption. */
.blog-body .bp-callout.plain { border-left-width: 1px; background: transparent; }

/* ---------- key takeaways ---------- */
.blog-body .bp-takeaways.plain { background: transparent; border: 1px dashed var(--border); }
.blog-body .bp-takeaways.dark { background: var(--dark); }
.blog-body .bp-takeaways.dark b { color: var(--coral); }
.blog-body .bp-takeaways.dark li { color: #fff; }

/* ---------- pull quote ---------- */
.blog-body .bp-quote.plain { border-left: none; padding-left: 0; text-align: center; }
.blog-body .bp-quote.plain p { font-style: italic; }
.blog-body .bp-quote.boxed {
  border-left: none; background: var(--chip); border-radius: 16px; padding: 26px 28px;
}
.blog-body .bp-quote.big p { font-size: 26px; line-height: 1.35; }

/* ---------- steps ---------- */
/* A vertical rule joining the markers, so a sequence reads as one run rather than three
   separate points. The rule stops at the last marker via the :last-child override. */
.blog-body .bp-steps.timeline li { padding-bottom: 8px; }
.blog-body .bp-steps.timeline li::after {
  content: ""; position: absolute; left: 14px; top: 34px; bottom: -10px;
  width: 2px; background: var(--border);
}
.blog-body .bp-steps.timeline li:last-child::after { display: none; }
/* Ticks instead of numbers: for a checklist, where the order is not the point. */
.blog-body .bp-steps.checks li::before { content: "\2713"; font-size: 14px; }
.blog-body .bp-steps.checks ol { counter-reset: none; }
.blog-body .bp-steps.boxed li {
  background: var(--card); border: 1px solid var(--border); border-radius: 12px;
  padding: 16px 18px 16px 56px;
}
.blog-body .bp-steps.boxed li::before { left: 16px; top: 15px; }

/* ---------- stat row ---------- */
.blog-body .bp-stat-row.bare .bp-stat { background: none; border: none; padding: 8px; }
.blog-body .bp-stat-row.dark .bp-stat { background: var(--dark); border-color: var(--dark); }
.blog-body .bp-stat-row.dark .bp-stat b { color: #fff; }
.blog-body .bp-stat-row.dark .bp-stat span { color: var(--dk-muted); }
.blog-body .bp-stat-row.accent .bp-stat { background: var(--chip); border-color: transparent; }
/* Two-up rather than three, for a pair of figures that are being compared. */
.blog-body .bp-stat-row.two { grid-template-columns: repeat(2, 1fr); }

/* ---------- table ---------- */
.blog-body .bp-table-wrap.striped tbody tr:nth-child(even) { background: var(--bg); }
.blog-body .bp-table-wrap.bordered th,
.blog-body .bp-table-wrap.bordered td { border: 1px solid var(--border); }
.blog-body .bp-table-wrap.compact th,
.blog-body .bp-table-wrap.compact td { padding: 7px 12px; font-size: 13.5px; }
.blog-body .bp-table-wrap.plain { border: none; background: none; border-radius: 0; }

/* ---------- call to action ---------- */
.blog-body .bp-cta.soft { background: var(--chip); }
.blog-body .bp-cta.soft b { color: var(--ink); }
.blog-body .bp-cta.soft p { color: var(--muted); }
.blog-body .bp-cta.outline { background: none; border: 2px solid var(--deep); }
.blog-body .bp-cta.outline b { color: var(--ink); }
.blog-body .bp-cta.outline p { color: var(--muted); }
.blog-body .bp-cta.left { text-align: left; }

/* ================================================================ designed banners

   A banner composed in the admin from a preset background plus real text, rather than an
   uploaded PNG. The text stays text: editable without re-exporting an image, selectable,
   sharp on any display, and readable by a screen reader.

   The background is always a CLASS chosen from a fixed list. Never an inline style built
   from stored values — that would be a second injection surface, and a free colour field
   is also how you get a banner that clashes with the page under it.
*/

.blog-bd { position: relative; border-radius: 16px; overflow: hidden; }
.blog-bd-in { position: relative; z-index: 1; padding: 26px 30px; text-align: center; }
.blog-bd b {
  display: block; font-family: var(--disp); font-size: clamp(18px, 2.2vw, 24px);
  line-height: 1.25; color: #fff; letter-spacing: -.01em;
}
.blog-bd p { font-size: 14.5px; line-height: 1.55; color: rgba(255, 255, 255, .78); margin: 8px 0 0; }
.blog-bd-btn {
  display: inline-block; margin-top: 16px;
  background: var(--coral); color: #fff; text-decoration: none;
  font-weight: 700; font-size: 14px; border-radius: 100px; padding: 11px 24px;
}
.blog-bd-btn:hover { background: var(--coral-deep); color: #fff; }

/* When there is a link but no button label the whole banner is the target. Stretched over
   the card rather than wrapping it, so the heading stays a heading in the accessibility
   tree instead of becoming link text. */
.blog-bd-cover { position: absolute; inset: 0; z-index: 2; }

/* In the sidebar the banner is 320px wide, so the padding and type step down. */
.blog-side .blog-bd-in { padding: 20px 20px; }
.blog-side .blog-bd b { font-size: 17px; }
.blog-side .blog-bd p { font-size: 13px; }
.blog-side .blog-bd-btn { font-size: 13px; padding: 9px 18px; }

/* ---------- the presets ---------- */

.blog-bd-deep { background: var(--deep); }
.blog-bd-sunset { background: linear-gradient(115deg, var(--deep) 0%, var(--coral) 100%); }
.blog-bd-midnight { background: linear-gradient(160deg, var(--deep) 0%, var(--dark) 100%); }
.blog-bd-stripes {
  background:
    repeating-linear-gradient(45deg, rgba(255, 255, 255, .10) 0 12px, transparent 12px 24px),
    var(--coral);
}
/* The texture layers sit UNDER the text via .blog-bd-in's z-index, so a pattern never
   reduces the contrast of the words on top of it. */
.blog-bd-dots {
  background:
    radial-gradient(rgba(255, 255, 255, .18) 1.4px, transparent 1.5px) 0 0 / 14px 14px,
    var(--deep);
}
.blog-bd-grid {
  background:
    linear-gradient(rgba(255, 255, 255, .10) 1px, transparent 1px) 0 0 / 22px 22px,
    linear-gradient(90deg, rgba(255, 255, 255, .10) 1px, transparent 1px) 0 0 / 22px 22px,
    var(--deep);
}

/* The one light preset, so its text flips to ink. Everything above assumes white on a
   dark ground; this is the exception and it has to restate all three colours. */
.blog-bd-soft { background: var(--chip); border: 1px solid var(--border); }
.blog-bd-soft b { color: var(--ink); }
.blog-bd-soft p { color: var(--muted); }
