/* =========================================================================
   CortexCommerce — blog + changelog styles (#7)

   Namespaced under `.blogpage` on <main>, same structural reason as
   `.scanpage`: these rules must never leak into the rest of the site.

   NAMED DEVIATION from the `.scanpage` precedent (tech-lead ruling 1, #7):
   unlike scan-tokens.css, this file does NOT redeclare the token bundle.
   scan-tokens.css redeclared ~30 tokens because 25 of its class names
   collide with styles.css (brand, btn, chip, g, n…); every class here is
   `bl-`-prefixed and collides with nothing, so there is nothing to isolate
   against and the smaller diff on the apex is worth more than pattern
   symmetry. The one real problem inherited from styles.css is `--muted`
   (#8c9196, 3.18:1 on white) failing WCAG AA at normal text size on what is
   a long-form reading surface; this reuses the exact AA-safe value
   scan-tokens.css already established (#666e80, 5.11:1), scoped here.
   ========================================================================= */

.blogpage {
  --muted: #666e80;
}

/* ---- shared: hero band (index + changelog) ---- */
.blogpage .bl-hero {
  padding: 148px 0 56px;
}
.blogpage .bl-hero h1 {
  font-size: clamp(34px, 4.4vw, 50px);
  letter-spacing: -1.4px;
  line-height: 1.08;
  margin: 14px 0 0;
  max-width: 720px;
}
.blogpage .bl-lede {
  font-size: 18px;
  color: var(--body);
  margin: 16px 0 0;
  max-width: 640px;
  line-height: 1.6;
}
@media (max-width: 560px) {
  .blogpage .bl-hero {
    padding: 112px 0 40px;
  }
}

/* ---- shared: measure column (post page) ---- */
.blogpage .bl-measure {
  max-width: 680px;
  margin: 0 auto;
  padding: 0 28px;
}
@media (max-width: 560px) {
  .blogpage .bl-measure {
    padding: 0 20px;
  }
}

/* ---- shared: dot separator, tag pill, empty state ---- */
.blogpage .bl-card-dot {
  color: var(--muted);
}
.blogpage .bl-tag,
.blogpage .bl-card-tag {
  display: inline-flex;
  align-items: center;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  padding: 5px 11px;
  border-radius: 999px;
  background: var(--indigo-soft);
  color: var(--indigo-2);
}
.blogpage .bl-tag--new {
  background: var(--indigo-soft);
  color: var(--indigo-2); /* 6.98:1 */
}
/* DEVIATION from the handoff spec (§7), flagged to design for sign-off:
   the spec set this pill to var(--accent-deep) on var(--accent-soft), which
   resolves to #0096bd on #d1f6fe = 3.00:1 — a fail at 11px, and the one pair
   §6's contrast table did not measure. The site already ships that pair on
   .demo-live/.cv-bar .live/.ph-match, so it is a pre-existing sitewide gap
   (ruling 6's territory), but blog must not add a NEW failing component to
   it. #2a3550 is not a new colour: it is the literal styles.css/product.css
   already use for text on --accent-soft (.bv-sugg), and the one the spec
   itself uses for .bl-callout. The pill keeps its cyan identity through the
   background. 10.66:1. */
.blogpage .bl-tag--improved {
  background: var(--accent-soft);
  color: #2a3550;
}
.blogpage .bl-tag--fixed {
  background: #e7f7ee;
  color: #1a7f4b; /* 4.53:1 */
}
.blogpage .bl-tag--product {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--body); /* 8.09:1 */
}
.blogpage .bl-empty {
  text-align: center;
  padding: 64px 24px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
}
.blogpage .bl-empty h2 {
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.4px;
  margin: 0 0 8px;
}
.blogpage .bl-empty p {
  font-size: 15px;
  color: var(--body);
  margin: 0;
  line-height: 1.6;
}
.blogpage .bl-empty a {
  color: var(--indigo-2);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ---- index: card grid ---- */
.blogpage .bl-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
}
.blogpage .bl-grid > * {
  min-width: 0;
}
@media (max-width: 900px) {
  .blogpage .bl-grid {
    grid-template-columns: 1fr;
  }
}

.blogpage .bl-card {
  display: flex;
  flex-direction: column;
  color: inherit;
  text-decoration: none;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition:
    transform 0.3s cubic-bezier(0.2, 0.7, 0.2, 1),
    box-shadow 0.3s,
    border-color 0.3s;
}
.blogpage .bl-card:hover {
  transform: translateY(-6px);
  box-shadow: var(--shadow-lg);
  border-color: color-mix(in oklab, var(--indigo-1) 30%, var(--line));
  text-decoration: none;
}
.blogpage .bl-card-media {
  display: block;
  aspect-ratio: 16 / 9;
  background: var(--bg-soft);
  overflow: hidden;
}
.blogpage .bl-card-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* The safety net for a post that ships before its art, NOT the common case.
   Re-skinned to the CortexCommerce logomark (design §1.3) — the glyph only, no
   rounded-rect plate, because the tile already IS the plate: same indigo
   gradient, same card radius. A generic document icon is what read as "missing
   image"; the mark reads as a branded surface on purpose. Decorative and
   aria-hidden either way. 0.85 white over the gradient's lightest stop measures
   3.70:1, over its darkest 6.16:1 — both clear the 3:1 non-text floor. */
.blogpage .bl-card-media--flat {
  background: var(--grad);
  display: grid;
  place-items: center;
  color: #fff;
}
.blogpage .bl-card-media--flat > svg {
  opacity: 0.85;
}
.blogpage .bl-card-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  padding: 20px 22px 22px;
}
.blogpage .bl-card-title {
  font-size: 19px;
  font-weight: 800;
  letter-spacing: -0.3px;
  line-height: 1.25;
  color: var(--ink);
  margin: 0;
}
.blogpage .bl-card-excerpt {
  font-size: 15px;
  color: var(--body);
  line-height: 1.55;
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.blogpage .bl-card-meta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  margin-top: 2px;
}
/* PRE-EXISTING GAP, found by tests/unit/blogStyles.test.ts: the card has
   emitted `.bl-card-date` since #38 with no rule anywhere, which nobody saw
   because it inherits size and colour from .bl-card-meta above. Given the one
   declaration it actually wants rather than deleted from the renderer, since
   the class is the hook a future card layout will need: tabular figures, so the
   dates line up down a column of cards. It is a column of digits, which is what
   makes the declaration earn its place — the changelog's own day heading is
   full prose on one line and deliberately does not carry it (design §4). */
.blogpage .bl-card-date {
  font-variant-numeric: tabular-nums;
}

/* ---- post: head, cover, body, foot ---- */
.blogpage .bl-post-head {
  padding: 148px 0 0;
}
.blogpage .bl-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--indigo-2);
  margin-bottom: 22px;
}
.blogpage .bl-back:hover {
  text-decoration: underline;
}
/* .bl-back is also the index <-> changelog cross-link in the hero band
   (#7 ruling 2). The handoff spec named no class for that link and the
   renderer reuses this one, which is right — same affordance, opposite
   direction — but its post-page margins (0 top, 22px bottom) put it flush
   against .bl-lede there. Re-spaced for the hero context only; no new class. */
.blogpage .bl-hero .bl-back {
  margin: 22px 0 0;
}
.blogpage .bl-post-title {
  font-size: clamp(30px, 4.2vw, 46px);
  letter-spacing: -1px;
  line-height: 1.12;
  margin: 14px 0 0;
}
.blogpage .bl-byline {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: var(--muted);
  margin: 16px 0 0;
}
.blogpage .bl-post-cover-wrap {
  margin: 36px auto 0;
}
.blogpage .bl-body {
  padding: 40px 0 8px;
}
.blogpage .bl-post-foot {
  padding: 24px 0 80px;
  margin-top: 24px;
  border-top: 1px solid var(--line-soft);
}

/* ---- post body: typed block rendering ---- */
.blogpage .bl-h2 {
  font-size: 28px;
  font-weight: 800;
  letter-spacing: -0.6px;
  line-height: 1.2;
  color: var(--ink);
  margin: 44px 0 0;
}
.blogpage .bl-h3 {
  font-size: 21px;
  font-weight: 800;
  letter-spacing: -0.4px;
  line-height: 1.25;
  color: var(--ink);
  margin: 32px 0 0;
}
.blogpage .bl-p {
  font-size: 18px;
  line-height: 1.7;
  color: var(--body);
  margin: 20px 0 0;
}
/* Colour alone is not an acceptable link affordance inside a paragraph
   (WCAG 1.4.1); the sitewide `a { text-decoration: none }` is fine for nav
   items and buttons, which carry non-text affordance, and wrong here. */
.blogpage .bl-p a,
.blogpage .bl-quote a,
.blogpage .bl-callout a,
.blogpage .bl-cl-desc a {
  text-decoration: underline;
  text-underline-offset: 2px;
}
.blogpage .bl-list {
  font-size: 18px;
  line-height: 1.7;
  color: var(--body);
  margin: 20px 0 0;
  padding-left: 22px;
}
.blogpage .bl-list li {
  margin-bottom: 8px;
}
.blogpage .bl-figure {
  margin: 32px 0 0;
}
.blogpage .bl-img {
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  display: block;
}
.blogpage .bl-cap {
  font-size: 13.5px;
  color: var(--muted);
  text-align: center;
  margin: 10px 0 0;
}
.blogpage .bl-quote {
  margin: 32px 0 0;
  padding-left: 20px;
  border-left: 3px solid var(--indigo-1);
}
.blogpage .bl-quote p {
  font-size: 19px;
  font-style: italic;
  color: var(--ink);
  line-height: 1.5;
  margin: 0;
}
.blogpage .bl-quote-cite {
  display: block;
  font-size: 14px;
  font-style: normal;
  color: var(--muted);
  margin-top: 8px;
}
.blogpage .bl-quote-cite::before {
  content: "— ";
}
/* styles.css has no --mono custom property; this is the same literal stack
   .code-block already hardcodes, not a new type decision. */
.blogpage .bl-code {
  margin: 32px 0 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 14px;
  line-height: 1.65;
  color: #34405a;
  background: var(--bg-soft);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  padding: 16px 18px;
  white-space: pre;
  overflow-x: auto;
}
.blogpage .bl-callout {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  margin: 28px 0 0;
  padding: 16px 18px;
  border-radius: 12px;
  background: var(--accent-soft);
  color: #2a3550;
  font-size: 15.5px;
  line-height: 1.6;
}
.blogpage .bl-callout-ic {
  flex: 0 0 auto;
  color: var(--accent-deep);
}
.blogpage .bl-hr {
  border: none;
  border-top: 1px solid var(--line);
  margin: 48px 0 0;
}

/* ---------------------------------------------------------------------------
   #57 — index lead card, post hero breakout, body figure sizing.

   Nothing here restyles `.bl-card` or `.bl-grid`: option A keeps the existing
   grid exactly as it was and adds ONE new layout above it.
   --------------------------------------------------------------------------- */

/* `section.block` is 100px top and `.bl-hero` is already 56px bottom, which
   put 156px of nothing between the lede and the first card. The hero band owns
   the space below itself; the section adds none. Scoped to `.bl-index` so the
   changelog's identical hero/section pairing is untouched. */
.blogpage section.bl-index {
  padding-top: 0;
}

.blogpage .bl-lead {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  align-items: stretch;
  color: inherit;
  text-decoration: none;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  margin-bottom: 24px;
  transition:
    transform 0.3s cubic-bezier(0.2, 0.7, 0.2, 1),
    box-shadow 0.3s,
    border-color 0.3s;
}
/* Same lift as `.bl-card` — the lead is a bigger card, not a different
   interaction. */
.blogpage .bl-lead:hover {
  transform: translateY(-6px);
  box-shadow: var(--shadow-lg);
  border-color: color-mix(in oklab, var(--indigo-1) 30%, var(--line));
  text-decoration: none;
}
/* No `aspect-ratio` here on purpose: the media is a grid cell that stretches to
   whatever the copy column needs, so the cover crops rather than dictating the
   card's height. `min-height` is the floor for a short excerpt. */
.blogpage .bl-lead-media {
  display: block;
  overflow: hidden;
  background: var(--bg-soft);
  min-height: 300px;
}
.blogpage .bl-lead-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  justify-content: center;
  padding: 32px 34px;
}
.blogpage .bl-lead-pill {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--indigo-2);
  background: var(--indigo-soft);
  padding: 4px 10px;
  border-radius: 999px;
}
.blogpage .bl-lead-title {
  font-size: 27px;
  font-weight: 800;
  letter-spacing: -0.6px;
  line-height: 1.2;
  margin: 0;
  color: var(--ink);
  text-wrap: balance;
}
.blogpage .bl-lead-excerpt {
  font-size: 15px;
  line-height: 1.62;
  color: var(--body);
  margin: 0;
}

/* Stacks at the same 900px the grid already collapses at, so the page never
   shows a two-column lead above a one-column grid. */
@media (max-width: 900px) {
  .blogpage .bl-lead {
    grid-template-columns: 1fr;
  }
  .blogpage .bl-lead-media {
    min-height: 0;
    aspect-ratio: 16 / 9;
  }
  .blogpage .bl-lead-body {
    padding: 22px 22px 24px;
  }
  .blogpage .bl-lead-title {
    font-size: 22px;
  }
}

/* The hero cover leaves the 680px measure. Wider than the prose, narrower than
   the page, so it reads as the top of the post rather than a large paragraph. */
.blogpage .bl-post-cover-wrap {
  max-width: 940px;
  margin: 36px auto 0;
  padding: 0 28px;
}
@media (max-width: 560px) {
  .blogpage .bl-post-cover-wrap {
    padding: 0 20px;
  }
}

/* Every cover and body image is 1600×900 by rule, so pinning the ratio here
   reserves the exact final height before the JPEG arrives. `imageHtml` emits no
   width/height attributes; without this the whole post reflows as each figure
   loads. `object-fit` is belt and braces — at a true 16:9 it never crops. */
.blogpage .bl-img {
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* ---- changelog (design pass 2, #55): one card per change ----
   The list is wider than the reading column: a two-column card needs the
   room, and 1040px is the width the approved mockup was built at. ---- */
.blogpage .bl-cl-wide {
  max-width: 1040px;
  margin: 0 auto;
  padding: 0 28px;
}
.blogpage .bl-cl-group {
  margin-bottom: 44px;
}
.blogpage .bl-cl-group:last-child {
  margin-bottom: 0;
}
/* Sticky, as before — one sticky element in the column. The count sits inside
   the uppercase heading and resets the transform: "4 changes" is a number, not
   a label token. The ::after is the row's own hairline, so the heading reads as
   a divider and not as a floating label. */
.blogpage .bl-cl-month {
  position: sticky;
  top: calc(var(--header-height) + 12px);
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--bg);
  padding: 4px 0 16px;
  margin: 0;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--indigo-1);
}
.blogpage .bl-cl-month::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line-soft);
}
.blogpage .bl-cl-count {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--muted);
}
.blogpage .bl-cl-entries {
  display: grid;
  gap: 18px;
}
/* The card: the note on the left, what it means on the right. Padding is on
   the card now — the trigger is the pill inside it, not the card. `overflow:
   clip` keeps the edge stripe inside the radius without making the card a
   scroll container (which `hidden` would, and which would unstick the month). */
.blogpage .bl-cl-entry {
  position: relative;
  display: grid;
  grid-template-columns: 1.25fr 1fr;
  gap: 32px;
  padding: 28px 32px 28px 34px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow: clip;
  transition:
    transform 0.3s cubic-bezier(0.2, 0.7, 0.2, 1),
    box-shadow 0.3s;
}
.blogpage .bl-cl-entry::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 5px;
  background: var(--line);
}
/* -2px, not .bl-card's -6px: these sit in an 18px-gap stack and a bigger lift
   reads as overlapping the card above. */
.blogpage .bl-cl-entry:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}
/* Only the newest change is loud: the gradient edge, the resting shadow, and
   the filled pill further down. Everything older is the quiet version. */
.blogpage .bl-cl-entry--latest {
  box-shadow: var(--shadow-md);
}
.blogpage .bl-cl-entry--latest::before {
  background: var(--grad);
}
.blogpage .bl-cl-main {
  display: grid;
  gap: 12px;
  justify-items: start;
  align-content: start;
  min-width: 0;
}
.blogpage .bl-cl-when {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin: 0;
}
.blogpage .bl-cl-when time {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.blogpage .bl-cl-latest {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--indigo-2);
}
.blogpage .bl-cl-latest::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--accent);
  animation: bl-cl-pulse 2.2s infinite;
}
@keyframes bl-cl-pulse {
  0% {
    box-shadow: 0 0 0 0 color-mix(in oklch, var(--accent) 50%, transparent);
  }
  70% {
    box-shadow: 0 0 0 9px transparent;
  }
  100% {
    box-shadow: 0 0 0 0 transparent;
  }
}
.blogpage .bl-cl-title {
  font-size: 24px;
  font-weight: 800;
  letter-spacing: -0.6px;
  line-height: 1.18;
  color: var(--ink);
  margin: 0;
}
.blogpage .bl-cl-desc {
  display: block;
  font-size: 16px;
  color: var(--body);
  line-height: 1.6;
  margin: 0;
  max-width: 58ch;
}

/* ---- the disclosure. `<details>` stays the mechanism (ADR-001 §C): with JS
   off the pill opens the note in place, under the card's text. The script
   moves the note into the drawer instead — the pill is the same element either
   way, which is why it is a <summary> styled as a button and not a button. ---- */
.blogpage .bl-cl-detail {
  margin-top: 6px;
  min-width: 0;
  justify-self: stretch;
}
.blogpage .bl-cl-more {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  list-style: none;
  cursor: pointer;
  padding: 11px 18px;
  border-radius: 999px;
  background: var(--indigo-soft);
  color: var(--indigo-2);
  font-weight: 700;
  font-size: 14.5px;
  transition: transform 0.25s cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* `list-style` alone does not remove the triangle in Safari. */
.blogpage .bl-cl-more::-webkit-details-marker {
  display: none;
}
.blogpage .bl-cl-more::after {
  content: "→";
}
.blogpage .bl-cl-more:hover {
  transform: translateY(-1px);
}
.blogpage .bl-cl-entry--latest .bl-cl-more {
  background: var(--grad);
  color: #fff;
  box-shadow: 0 14px 30px -16px rgba(67, 56, 202, 0.8);
}

/* In-place expansion (JS off). Flow spacing driven by what precedes an
   element, so the renderer can add or drop a section (notes, the optional
   link) without a new rule each time. The same rules follow the note into the
   drawer, because the drawer moves this element rather than rebuilding it. */
.blogpage .bl-cl-body {
  margin: 18px 0 0;
  padding: 18px 0 4px;
  border-top: 1px solid var(--line-soft);
}
.blogpage .bl-cl-body > * {
  margin: 0;
}
.blogpage .bl-cl-body > * + * {
  margin-top: 10px;
}
.blogpage .bl-cl-body > * + .bl-cl-label {
  margin-top: 18px;
}
.blogpage .bl-cl-body > .bl-cl-label + * {
  margin-top: 6px;
}
.blogpage .bl-cl-body .bl-cl-desc {
  font-size: 15px;
  max-width: 68ch;
}
/* Same recipe as .demo-label, given its own blog-scoped class rather than
   reusing a homepage-hero one. The UPPERCASE is `text-transform` and must stay
   that way: the renderer authors these in sentence case on purpose, because
   literal all-caps in the DOM is what some screen readers spell out letter by
   letter. 5.11:1 on white at 11px. */
.blogpage .bl-cl-label {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--muted);
}
/* .bl-list is the post-body list (18px/1.7) reused for the value bullets — the
   renderer reuses it rather than inventing a class, which is right, but 18px is
   the reading-column size and this note's prose is 15px. */
.blogpage .bl-cl-body .bl-list {
  font-size: 15px;
  line-height: 1.6;
  padding-left: 20px;
}
.blogpage .bl-cl-body .bl-list li {
  margin-bottom: 6px;
}
.blogpage .bl-cl-body .bl-list li:last-child {
  margin-bottom: 0;
}

/* ---- the side column: what this change means, as tiles ---- */
.blogpage .bl-cl-side {
  display: grid;
  gap: 12px;
  align-content: start;
  min-width: 0;
  padding-left: 32px;
  border-left: 1px solid var(--line-soft);
}
.blogpage .bl-cl-tiles {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 10px;
}
.blogpage .bl-cl-tiles li {
  position: relative;
  padding: 11px 14px 11px 40px;
  background: var(--bg-soft);
  border-radius: 12px;
  color: var(--body);
  font-size: 14.5px;
  line-height: 1.5;
}
.blogpage .bl-cl-tiles li::before {
  content: "";
  position: absolute;
  left: 14px;
  top: 13px;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  background: var(--indigo-soft);
}
.blogpage .bl-cl-tiles li::after {
  content: "";
  position: absolute;
  left: 19px;
  top: 17px;
  width: 6px;
  height: 3px;
  border-left: 2px solid var(--indigo-1);
  border-bottom: 2px solid var(--indigo-1);
  transform: rotate(-45deg);
}

/* ---- the drawer: a real <dialog>, fixed to the right edge. Padding is 0 on
   the dialog itself so a click that reaches the dialog element IS a backdrop
   click — the script closes on exactly that. Built and inserted by
   changelogClientScript.ts; nothing here renders without it. ---- */
.blogpage .bl-cl-drawer {
  position: fixed;
  inset: 0 0 0 auto;
  width: min(600px, 100vw);
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--bg);
  color: var(--ink);
  box-shadow: -30px 0 80px -40px rgba(0, 0, 0, 0.5);
  display: none;
  grid-template-rows: auto 1fr auto;
}
.blogpage .bl-cl-drawer[open] {
  display: grid;
  animation: bl-cl-slide 0.38s cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* Transform only — no opacity. If an animation ever stalls (a background
   tab pauses them), a translated panel is still readable; a transparent one
   is not. */
@keyframes bl-cl-slide {
  from {
    transform: translateX(24px);
  }
  to {
    transform: none;
  }
}
.blogpage .bl-cl-drawer::backdrop {
  background: rgba(15, 18, 32, 0.45);
}
.blogpage .bl-cl-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px 12px 28px;
  border-bottom: 1px solid var(--line-soft);
}
.blogpage .bl-cl-close {
  border: 0;
  background: var(--bg-soft);
  color: var(--ink);
  width: 34px;
  height: 34px;
  border-radius: 999px;
  cursor: pointer;
  font-size: 20px;
  line-height: 1;
  display: grid;
  place-items: center;
}
.blogpage .bl-cl-drawer-body {
  overflow: auto;
  padding: 24px 28px 32px;
}
.blogpage .bl-cl-drawer-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0;
  color: var(--muted);
  font-size: 14px;
}
.blogpage .bl-cl-drawer-title {
  font-size: 24px;
  font-weight: 800;
  letter-spacing: -0.5px;
  line-height: 1.2;
  margin: 12px 0 10px;
  color: var(--ink);
}
.blogpage .bl-cl-drawer-lede {
  font-size: 16px;
  color: var(--body);
  line-height: 1.6;
  margin: 0;
  max-width: 60ch;
}
/* The moved note keeps its in-place rules; only the top margin changes,
   because the lede above it already breathes. */
.blogpage .bl-cl-drawer-note .bl-cl-body {
  margin-top: 22px;
}
.blogpage .bl-cl-tools {
  margin: 18px 0 0;
}
.blogpage .bl-cl-copy {
  border: 0;
  background: none;
  padding: 0;
  color: var(--indigo-2);
  font-weight: 600;
  font-size: 14px;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.blogpage .bl-cl-drawer-foot {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 14px 28px;
  border-top: 1px solid var(--line-soft);
}
.blogpage .bl-cl-newer,
.blogpage .bl-cl-older {
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  border-radius: 999px;
  padding: 9px 16px;
  cursor: pointer;
  font-weight: 600;
  font-size: 14px;
}
.blogpage .bl-cl-newer:disabled,
.blogpage .bl-cl-older:disabled {
  opacity: 0.4;
  cursor: default;
}

/* The changelog card stacks on a narrow viewport: text first, the tiles under
   it. This block sits AFTER the card's base rules on purpose — same
   specificity, so source order decides, and the index grid's own 900px block
   near the top of the sheet is too early to win. blogStyles.test.ts pins the
   order. */
@media (max-width: 900px) {
  .blogpage .bl-cl-entry {
    grid-template-columns: 1fr;
    gap: 20px;
  }
  .blogpage .bl-cl-side {
    padding-left: 0;
    border-left: 0;
    padding-top: 18px;
    border-top: 1px solid var(--line-soft);
  }
}

/* ---- anchors clear the fixed nav (same rule as .scanpage [id]) ---- */
.blogpage [id] {
  scroll-margin-top: calc(var(--header-height) + 12px);
}

/* ---- focus-visible: styles.css carries no global rule, so every namespaced
   surface supplies its own (.scanpage does the same). The sitewide gap is a
   real WCAG 2.4.7 miss with its own ticket; these pages are compliant
   regardless. ---- */
.blogpage a:focus-visible,
.blogpage button:focus-visible,
.blogpage summary:focus-visible {
  outline: 2px solid var(--indigo-1);
  outline-offset: 2px;
  border-radius: 3px;
}
/* Same ring, not a second focus style: only the corner radius changes, because
   the summary IS the pill and a square ring around a pill reads as a rendering
   bug. Must come after the rule above, which sets 3px for the general case. */
.blogpage .bl-cl-more:focus-visible {
  border-radius: 999px;
}

@media (max-width: 560px) {
  .blogpage .bl-post-title {
    letter-spacing: -0.6px;
  }
  .blogpage .bl-p,
  .blogpage .bl-list {
    font-size: 16.5px;
  }
  .blogpage .bl-h2 {
    font-size: 24px;
  }
  .blogpage .bl-h3 {
    font-size: 19px;
  }
  .blogpage .bl-code {
    font-size: 12.5px;
  }
  .blogpage .bl-cl-month {
    /* sticky months add more confusion than help on a narrow viewport that
       is already showing one card at a time */
    position: static;
  }
  .blogpage .bl-cl-wide {
    padding: 0 20px;
  }
  .blogpage .bl-cl-entry {
    padding: 22px 20px 22px 24px;
  }
  .blogpage .bl-cl-title {
    font-size: 21px;
  }
}

/* ---- reduced motion ----
   The hover lifts are nothing but motion, so they go entirely and the shadow
   carries the hover cue on its own; the drawer arrives instead of sliding, and
   the Latest dot holds still. styles.css's own reduced-motion block covers
   .reveal and the hero animations and reaches none of this. */
@media (prefers-reduced-motion: reduce) {
  .blogpage .bl-lead {
    transition: box-shadow 0.3s;
  }
  .blogpage .bl-lead:hover {
    transform: none;
  }
  .blogpage .bl-cl-entry {
    transition: box-shadow 0.3s;
  }
  .blogpage .bl-cl-entry:hover {
    transform: none;
  }
  .blogpage .bl-cl-more {
    transition: none;
  }
  .blogpage .bl-cl-more:hover {
    transform: none;
  }
  .blogpage .bl-cl-drawer[open] {
    animation: none;
  }
  .blogpage .bl-cl-latest::before {
    animation: none;
  }
}
