/* ==========================================================================
   Alembic Blog — /blog index + /blog/<slug> reading page.

   A new file, following industry-marketing.css's precedent: marketing.css is
   consumed by 19 other pages and is not edited here. Load order in <head>:
       /fonts/fonts.css
       /css/marketing.css          (palette, fonts, .nav, .cta-section, focus)
       /css/industry-marketing.css (--im-* scale, .im-shell/.im-eyebrow/
                                    .im-breadcrumbs, .im-page reduce blanket)
       /css/blog.css               (this file)

   REQUIRED MARKUP CONTRACT — the page is broken without both of these, and
   tests/unit/marketing-blog.test.ts pins them:
     1. <body class="im-page page-blog">. --im-max / --im-10 are declared only
        on .im-page; without it .im-shell's width resolves to `auto` and the
        page loses its measure AND its gutters.
     2. renderCtaNoscriptFallback() in <head>. marketing.css hides
        .cta-section's children at opacity:0 unconditionally and waits for a
        scroll observer; with scripting off the final band never appears.

   No .al-btn anywhere. No marketing page uses the dot-morph canvas button
   system and marketing.css does not style .al-btn — .cta-button and
   .cta-secondary are the marketing precedent.

   No --copper. Its token comment restricts it to pain/limitations eyebrows,
   the pull-quote mark + left rule, and 01/02/03 numerals, and states the rule
   the restriction exists for: "Semantic, not decorative." A blog post has no
   pain section, and a quotation is neither "what Alembic does" (green) nor
   "what today costs you" (copper). Ink is the default; this surface earns no
   pigment. The blockquote rule below is --ink-200.
   ========================================================================== */

.page-blog {
  /* Reading measure. NOT var(--im-measure) — DM Sans's '0' advance is 0.6560em
     (measured from dm-sans-latin.woff2 with fontTools) while the mean prose
     advance is 0.4595em, so `ch` over-counts characters by 1.4278x and
     --im-measure: 62ch actually resolves to 88.5 characters. 66 chars x
     0.4595em = 30.33em; at the 19px desktop body size that is 576px. Declared
     in rem so the h1 and the prose share one declared column despite different
     font sizes. Worst case across the viewport range is 70.1 characters at
     ~608px, where the measure and the shell cross over. */
  --blog-measure: 36rem;

  /* Display column. 576px at 48px Fraunces is 26 characters — headlines would
     tower. 704px gives 32.3 at 48px / 38.8 at 40px. Display wider than text. */
  --blog-display-measure: 44rem;

  /* Body size. 17px at 390 -> 19px at >=922. Solve: 0.98rem + 0.36vw hits
     19px at V=922 and the 17px floor at V<=367. */
  --blog-body: clamp(1.0625rem, 0.98rem + 0.36vw, 1.1875rem);

  /* Card hover tone. NOT --action-wash: at its literal 8% light / 12% dark it
     is lopsided (dL* 3.55 light vs 7.25 dark). A flat 10% of --action — which
     is itself theme-dependent (#4A8B6A light / #5C9C7C dark) — lands at
     dL* 4.42 light / 5.90 dark, perceptible and near-symmetric.
       light: #FAF9F7 -> #E8EEE9      dark: #14110D -> #1B1F18
     This is the reason hover is NOT "lighter": in light theme --card is
     #FFFFFF and --paper is #FAF9F7, so any "raise on hover" paints white on
     white. Tone, computed differently in both themes. Never border-color. */
  --blog-hover-wash: color-mix(in srgb, var(--action) 10%, transparent);

  /* .nav is position:sticky, height 64px. h2/h3 carry ids (render-markdown.ts
     emits them), so a #heading deep link lands underneath the nav without
     this. --im-6 is the breathing room above the landed heading. */
  --blog-anchor-offset: calc(64px + var(--im-6));

  /* Declared, not inherited-by-luck. Both variable subsets default opsz to 9
     (a caption cut); `auto` is what raises Fraunces to a display cut at 48px
     and DM Sans to a text cut at 19px. Do NOT set font-optical-sizing:none,
     and do NOT use font-variation-settings anywhere on this page — either one
     silently reverts both faces to their 9pt cut. */
  font-optical-sizing: auto;
}

/* ══ Index hero + post header ═══════════════════════════════════════════════
   Both wrap an .im-shell, so padding lives on the outer element. --im-16 top
   matches .im-hero; the bottom is --im-10 rather than --im-12 because a
   standfirst leads straight into the list/prose with no section rule between.
   .im-eyebrow is inherited from industry-marketing.css unchanged — mono,
   --action-text, 6px --action dot. Nothing to restyle. */
.blog-index-hero,
.blog-post-header { padding-block: var(--im-16) var(--im-10); }

.blog-index-hero h1,
.blog-post-header h1 {
  max-width: var(--blog-display-measure);
  margin: 0;
  color: var(--ink-900);
  font-family: var(--font-display);
  /* 34px -> 48px. Deliberately NOT --im-display's 34->54: an industry hero is
     a pitch, an article title is an article title, and 54px in a 704px column
     is 28 characters. */
  font-size: clamp(2.125rem, 1.72rem + 1.72vw, 3rem);
  font-weight: 400;          /* Fraunces subset is 400-500 only. 600 = synthesised. */
  font-style: italic;        /* matches .im-hero h1 / .cta-headline */
  line-height: var(--im-lh-display);          /* 1.12 */
  letter-spacing: var(--im-track-display);    /* -0.02em */
  text-wrap: balance;
}

/* Standfirst = the frontmatter `description`, which is simultaneously the meta
   description. One string, two jobs, zero duplication (Law 3). --ink-700
   rather than --ink-600 because it is the second-loudest thing on the page. */
.blog-standfirst,
.blog-post-standfirst {
  max-width: var(--blog-measure);
  margin: var(--im-5) 0 0;
  color: var(--ink-700);
  font-size: clamp(1.1875rem, 1.13rem + 0.24vw, 1.3125rem);  /* 19 -> 21px */
  line-height: var(--im-lh-lead);   /* 1.6 */
  text-wrap: pretty;                /* orphan control; no-ops where unsupported */
}

/* Byline is a sentence, so it stays in the body face. The card date below is a
   standalone datum, so it goes mono. Same information, different grammatical
   role, different treatment — mono is for data and labels, sans is for
   sentences, which is the rule the rest of the site already follows. */
.blog-post-byline {
  max-width: var(--blog-measure);
  margin: var(--im-4) 0 0;
  color: var(--ink-600);
  font-size: var(--im-small);       /* 15px */
  line-height: 1.5;
}

/* ══ Index list ════════════════════════════════════════════════════════════
   Rule-separated rows, not bordered cards. Three reasons, in order:
   (a) DESIGN_SYSTEM § Cards: "a hard 1px frame reads cheap", prefer the
       borderless form. .ds-raise / --ri-raise-shadow are product tokens and do
       not exist on the marketing surface, and --im-shadow-lift on a full-width
       row reads as a floating slab, so the borderless form here is the rule
       stack, not a shadow.
   (b) .im-related-card — the nearest existing marketing card — expresses hover
       as `border-color: var(--action)`, which the current Law explicitly
       forbids ("hover/open express as tone, never border-color"). Copying it
       would import a known violation.
   (c) A blog index is a list of essays, not a grid of features. The row stack
       is the same grammar .im-faq-item already uses on this site: padding-
       block + one hairline, on --paper.
   The class name .blog-card is inherited from the markup contract and from
   tests/unit/marketing-blog.test.ts. It is a row. Renaming it would cost test
   churn for nothing; this comment is the correction. */
.blog-list {
  max-width: var(--blog-measure);
  margin: 0 0 var(--im-16);
  padding: 0;
  list-style: none;
}

.blog-card { margin: 0; }

.blog-card-link {
  display: block;
  position: relative;
  /* The negative inline margin lets the hover tone and the hairline extend
     --im-4 past the text column on both sides, so the row reads as a target
     rather than as a tinted paragraph. At <=40rem the shell gutter is also
     16px, so the row lands exactly flush with the viewport edge — no overflow,
     no horizontal scrollbar. */
  margin-inline: calc(var(--im-4) * -1);
  padding: var(--im-6) var(--im-4);
  /* border-block-START, first row suppressed: the list reads as separated, not
     boxed, and the .cta-section below already supplies a floor (it carries its
     own border-top and a --card background). Same shape as
     .feature-block:first-of-type. --card-border, not --mock-hairline: the
     latter is scoped by its own token comment to "mockup-internal hairlines
     only". Measured 1.23:1 light / 1.24:1 dark against --paper — decorative,
     no SC applies, and consistency with .im-faq-item beats a local win. */
  border-block-start: 1px solid var(--card-border);
  border-radius: var(--im-r-md);
  color: var(--ink-900);
  text-decoration: none;
  transition: background-color 0.2s var(--ease-standard);
}

.blog-card:first-child .blog-card-link { border-block-start: 0; }

.blog-card-link:hover { background-color: var(--blog-hover-wash); }

/* outline-offset is NEGATIVE here, not the site's usual +2px: the row already
   bleeds --im-4 past the text and sits flush to the viewport edge at <=40rem,
   where a +2px ring would be clipped. Precedent: .ph-banner:focus-visible
   uses -3px for the same reason. --action measures 3.84:1 light / 5.83:1 dark
   on --paper and 3.44 / 5.18 on the hover wash — clears the 3:1 non-text
   floor in every combination. */
.blog-card-link:focus-visible {
  outline: 2px solid var(--action);
  outline-offset: -2px;
  background-color: var(--blog-hover-wash);
}

/* Date: mono, --ink-600, sentence case. This is the .im-eyebrow slot — a small
   mono line above a heading — minus the dot, because the dot is --action and
   marks a section label. A date is not a section label. No "4 min read", no
   progress ring, no glyph: Law 2 wants the real datum, and the real datum is
   the publication date, which is also what <time datetime> carries for
   machines. */
.blog-card-date {
  display: block;
  margin-bottom: var(--im-2);
  color: var(--ink-600);
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.02em;
}

.blog-card-title {
  margin: 0;
  color: var(--ink-900);
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1.39rem + 0.47vw, 1.75rem);  /* 24 -> 28px */
  font-weight: 400;
  font-style: normal;   /* italic is the h1's job; a list of italic titles is noise */
  line-height: 1.2;
  letter-spacing: var(--im-track-h2);
  text-wrap: balance;
}

/* The real standfirst, in full. Not truncated, not line-clamped: it is the
   one sentence that tells a reader whether to open the post, and it is the
   same string the search engine is being shown. */
.blog-card-standfirst {
  max-width: var(--blog-measure);
  margin: var(--im-3) 0 0;
  color: var(--ink-700);
  font-size: var(--im-body);   /* 17px */
  line-height: 1.55;
  text-wrap: pretty;
}

/* The row's ONLY affordance, and it is what pays for dropping the 1px frame,
   the box-shadow and the border-color hover of .im-related-card. Same grammar
   as .im-industry-action, so it is not a new idea. aria-hidden on the whole
   span in the markup: it adds nothing to the link's accessible name, which
   already reads date + title + standfirst. */
.blog-card-more {
  display: inline-flex;
  align-items: center;
  gap: var(--im-2);
  min-height: 44px;              /* tap target; matches .im-industry-action */
  margin-top: var(--im-2);
  color: var(--action-text);     /* 6.16:1 light / 6.53:1 dark on --paper */
  font-size: var(--im-small);
  font-weight: 700;
}

.blog-card-more i {
  /* U+2192 is absent from all four font subsets AND outside their
     unicode-range, so it renders from the fallback face. That is a clean
     fallback, not a bug — but the borrowed glyph needs its baseline pinned. */
  display: inline-block;
  font-style: normal;
  line-height: 1;
  transition: transform 0.3s var(--spring);
}

.blog-card-link:hover .blog-card-more i,
.blog-card-link:focus-visible .blog-card-more i { transform: translateX(4px); }

/* Empty state (.blog-empty, exercised by tests/unit/marketing-blog.test.ts).
   A sentence. No dashed box, no illustration, no icon. */
.blog-empty {
  max-width: var(--blog-measure);
  margin: 0 0 var(--im-16);
  padding-block: var(--im-12);
  color: var(--ink-600);
  font-size: var(--im-body);
}

/* ══ Prose ═════════════════════════════════════════════════════════════════
   Body copy is DM Sans, not Fraunces, for three mechanical reasons: the
   Fraunces subset is font-weight 400-500 so <strong> would synthesise; its
   opsz axis defaults to 9 (a caption cut); and its Georgia fallback would
   change the whole essay's face on a font miss. The essay register comes from
   the measure, the leading and the italic display face. */
.blog-prose {
  max-width: var(--blog-measure);
  color: var(--ink-700);          /* 11.16:1 light / 11.46:1 dark on --paper */
  font-family: var(--font-body);
  font-size: var(--blog-body);
  line-height: var(--im-lh-body); /* 1.7 — the site's body leading */
}

.blog-prose > :first-child { margin-block-start: 0; }

.blog-prose p {
  margin: 0 0 var(--im-6);        /* 24px ~= 0.74 x the 32.3px line box */
  text-wrap: pretty;
}

/* NO lead paragraph. industry-marketing.css sizes .im-section-body > p:first-
   child up to --im-lead, but the blog already has a standfirst directly above
   the prose — a second lead-sized paragraph is the summary-stacked-on-the-
   answer-line that Law 3 exists to forbid. The standfirst IS the lead. */

.blog-prose h2 {
  margin: var(--im-12) 0 var(--im-3);   /* 48 top / 12 bottom — a 4:1 bind */
  color: var(--ink-900);
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1.34rem + 0.7vw, 1.875rem);   /* 24 -> 30px */
  font-weight: 400;
  font-style: normal;   /* the h1 is italic; six italic h2s down an essay is a shout */
  line-height: 1.25;
  letter-spacing: var(--im-track-h2);
  text-wrap: balance;
  scroll-margin-top: var(--blog-anchor-offset);
}

/* h3 is SMALLER than h2 but HEAVIER. That is not an inversion error: Fraunces
   at 30px carries at weight 400, and at 21px it does not — 500 is what gives
   the third level presence without borrowing the second level's size. 500 is
   the top of the shipped subset's range, so nothing synthesises. 21px is
   --im-h3's value, reused rather than reinvented. */
.blog-prose h3 {
  margin: var(--im-8) 0 var(--im-2);
  color: var(--ink-900);
  font-family: var(--font-display);
  font-size: 1.3125rem;   /* 21px */
  font-weight: 500;
  line-height: 1.3;
  text-wrap: balance;
  scroll-margin-top: var(--blog-anchor-offset);
}

/* Paragraph-after-heading. Paragraphs carry bottom margin only, so the
   heading's own margin-block-end is the sole gap and no collapse arithmetic is
   involved. Declared anyway so a future `p { margin-top }` cannot reopen it. */
.blog-prose h2 + p,
.blog-prose h3 + p,
.blog-prose h2 + ul,
.blog-prose h2 + ol,
.blog-prose h3 + ul,
.blog-prose h3 + ol { margin-block-start: 0; }

.blog-prose ul,
.blog-prose ol {
  margin: 0 0 var(--im-6);
  padding-inline-start: var(--im-6);
}

.blog-prose li { margin-bottom: var(--im-2); }
.blog-prose li:last-child { margin-bottom: 0; }

/* --ink-600 (6.15:1 in BOTH themes on --paper), not --ink-400. An <ol> marker
   is content, and --ink-400 measures 2.91:1 in both themes — under the floor
   by construction, in both directions of the inverting scale. */
.blog-prose li::marker { color: var(--ink-600); }

/* A quotation, not a pull-quote. Same Fraunces-italic register as
   .im-pullquote but a step smaller (it sits IN the flow rather than pulling
   out of it) and the rule is --ink-200, not --copper — see the file header for
   why copper is refused. */
.blog-prose blockquote {
  max-width: var(--blog-measure);
  margin: var(--im-8) 0;
  padding-inline-start: var(--im-6);
  border-inline-start: 2px solid var(--ink-200);
  color: var(--ink-900);
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 1.14rem + 0.5vw, 1.5rem);   /* 20 -> 24px */
  font-weight: 400;
  font-style: italic;
  line-height: 1.35;
}

.blog-prose blockquote p { margin: 0; }

/* Code sits on --well, not on --code-bg. --code-bg is the dark API panel on
   /features — a product screenshot analogue that is dark in BOTH themes. A
   dark slab dropped into a light essay inverts the reading contrast mid-page
   and stops following the theme. --well is the marketing surface's recessed-
   panel token and does follow it: --ink-900 measures 16.29:1 light /
   14.61:1 dark on it.
   No syntax highlighting — see the subtraction note at the foot of this file. */
.blog-prose code {
  padding: 0.15em 0.4em;
  border-radius: var(--im-r-sm);
  background: var(--well);
  color: var(--ink-900);
  /* 0.9em, not 0.875: IBM Plex Mono's x-height is 0.516 against DM Sans's
     0.526, so 0.9 is where the two x-heights actually line up. */
  font-family: var(--font-mono);
  font-size: 0.9em;
  font-weight: 400;   /* the mono subset ships 400 + 500 only */
}

.blog-prose pre {
  max-width: var(--blog-measure);
  margin: var(--im-8) 0;
  padding: var(--im-5);
  border-radius: var(--im-r-md);
  background: var(--well);
  overflow-x: auto;
  /* Paired with tabindex="0" on the <pre> in render-markdown.ts — a
     horizontally scrollable region must be keyboard-reachable (WCAG 2.1.1).
     Precedent: .compare-scroll:focus-visible in marketing.css. */
  color: var(--ink-900);
  font-size: 0.875rem;   /* 14px */
  line-height: 1.65;
}

.blog-prose pre:focus-visible { outline: 2px solid var(--action); outline-offset: 2px; }

.blog-prose pre code {
  padding: 0;
  border-radius: 0;
  background: none;
  font-size: inherit;
}

.blog-prose strong { color: var(--ink-900); font-weight: 600; }   /* real weight: DM Sans ships 300-700 */
.blog-prose em { font-style: italic; }                            /* real face: dm-sans-italic-latin.woff2 */

/* NOTE: the italic subset ships weight 400 ONLY, so <strong><em> synthesises
   its bold. Synthesis is left ON deliberately — font-synthesis:none would
   render bold-italic as plain italic and silently drop the emphasis, which is
   worse than a slightly heavy stroke. Authors should avoid nesting the two. */

/* <hr> renders the site's own three-dot rule rather than a line. Same
   geometry as .dot-rule in industry-marketing.css — 5px --ink-300, 6px
   --action, 5px --ink-300, centres at +/-17.5px — reproduced with three
   background gradients because an <hr> cannot carry the three <span>s
   .dot-rule needs. No new divider idea, no markup change. */
.blog-prose hr {
  height: 6px;
  margin-block: var(--im-12);
  border: 0;
  background-repeat: no-repeat;
  background-position: calc(50% - 17.5px) center, center, calc(50% + 17.5px) center;
  background-size: 5px 5px, 6px 6px, 5px 5px;
  background-image:
    radial-gradient(circle closest-side, var(--ink-300) 100%, transparent 100%),
    radial-gradient(circle closest-side, var(--action) 100%, transparent 100%),
    radial-gradient(circle closest-side, var(--ink-300) 100%, transparent 100%);
}

/* ══ Inline links in prose ═════════════════════════════════════════════════
   The one genuinely new component: no marketing page currently has links
   inside body copy, and .page-legal .legal-prose a — the closest thing —
   is `color: --action-text; text-decoration: none`, which fails WCAG 1.4.1
   (colour is the only distinguisher at rest). That should be fixed separately.

   Resting state carries a real underline, so colour is never the sole signal.
   Hover changes THICKNESS as well as colour, so the hover state is also not
   colour-alone. */
.blog-prose a {
  color: var(--action-text);      /* 6.16:1 light / 6.53:1 dark on --paper */
  /* 75% of currentColor, not 100%: a full-strength underline under every link
     in a 2000-word essay reads as a ruled page. Measured as an independent
     graphical distinguisher against --paper: 3.57:1 light / 4.16:1 dark, over
     the 3:1 floor. 70% is the actual floor (3.20 light) — 75% is the margin.
     60% fails light at 2.64 and must not be used. */
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, currentcolor 75%, transparent);
  text-decoration-skip-ink: auto;
  text-decoration-thickness: 1px;     /* fixed, not from-font: DM Sans's own
                                         value varies with the opsz axis */
  text-underline-offset: 0.16em;
  transition: color 0.2s var(--ease-standard),
              text-decoration-color 0.2s var(--ease-standard);
}

.blog-prose a:hover {
  /* Colour holds at --action-text (proven 6.16/6.53); the underline goes to
     full strength and doubles. The change a user perceives is weight, which
     is what keeps the hover state itself off colour-alone. */
  text-decoration-color: currentcolor;
  text-decoration-thickness: 2px;
}

.blog-prose a:focus-visible {
  outline: 2px solid var(--action);
  outline-offset: 2px;
  border-radius: var(--im-r-sm);      /* the site's convention, verbatim */
  text-decoration-color: currentcolor;
}

/* An inline link inside a sentence is exempt from WCAG 2.5.8 target size —
   the "inline" exception. Do not pad these to 24px; it would break the leading. */

/* ══ Post footer ═══════════════════════════════════════════════════════════ */
.blog-post-footer {
  max-width: var(--blog-measure);
  margin-block-start: var(--im-12);
  padding-block-start: var(--im-6);
  border-block-start: 1px solid var(--card-border);
}

/* Tags are PLAIN TEXT. Tag archive routes do not exist in this version, so a
   pill, a chip, a background, a border or a radius here would be a control
   that does not control anything (Law 1). If /blog/tag/<slug> ever ships,
   these become <a>s and inherit .blog-prose a's treatment — not chips. */
.blog-post-tags {
  margin: 0 0 var(--im-5);
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-600);
  font-size: var(--im-small);
  line-height: 1.6;
}

.blog-post-tags-label { color: var(--ink-600); }

/* CRITICAL. marketing.css sets .cta-secondary { opacity:0;
   transform:translateY(16px) } UNCONDITIONALLY and only reveals it via
   .cta-section.visible / .is-revealed. This link is not inside a
   .cta-section, so without this override it is invisible on every post, in
   every browser, forever. Exactly the defect industry-marketing.css patches
   for .im-cta-group--hero .cta-secondary. */
.blog-post-footer .cta-secondary {
  min-height: 44px;   /* marketing.css's 12px padding + an 18px line box = 42px */
  opacity: 1;
  transform: none;
  transition: color 0.2s var(--ease-standard);
}

.blog-post-footer .cta-secondary .cta-arrow { transition: transform 0.3s var(--spring); }

/* ══ Reveal on scroll ══════════════════════════════════════════════════════
   Index rows opt in via [data-reveal]; marketing.css's .js-reveal
   [data-reveal] baseline supplies the fade+rise, and because it is gated on
   .js-reveal (added to documentElement by the chrome script's first act) the
   no-JS case never hides anything. This is why the blog needs
   renderCtaNoscriptFallback() for .cta-section but needs NOTHING for its own
   elements — the inversion contract already answers no-JS. Do not add another
   default-hidden rule.

   The stagger step is capped at 6 steps so a 40-post index does not take 2.4
   seconds to settle. render-chrome-script.ts's applyRevealStagger sets
   --reveal-i on the direct children of [data-reveal-stagger]; no JS change is
   needed.

   .blog-prose is deliberately NOT [data-reveal]. Fading in an article's body
   delays the only thing the reader came for, and breaks in-page find and
   scroll restoration on a long document. */
.js-reveal .blog-list[data-reveal-stagger] > .blog-card[data-reveal] {
  transition-delay: calc(min(var(--reveal-i, 0), 5) * 60ms);
}

/* ══ Mobile — 390 x 844 ════════════════════════════════════════════════════
   .im-shell already narrows to min(100% - 32px, 1120px) at <=40rem, so the
   text column is 358px and every measure cap stops binding. The type scale is
   already responsive via clamp(); only the block rhythm and the two fixed
   panels need adjusting. */
@media (max-width: 48rem) {
  .blog-index-hero,
  .blog-post-header { padding-block: var(--im-12) var(--im-8); }

  .blog-list { margin-block-end: var(--im-12); }
  .blog-card-link { padding: var(--im-5) var(--im-4); }
  .blog-prose blockquote { margin-block: var(--im-6); padding-inline-start: var(--im-4); }
  .blog-prose pre { padding: var(--im-4); font-size: 0.8125rem; }   /* 13px — less sideways scroll */
  .blog-prose hr { margin-block: var(--im-10); }
  .blog-prose h2 { margin-block-start: var(--im-10); }
  .blog-post-footer { margin-block-start: var(--im-10); }
}

/* At <=40rem the shell gutter is 16px, which is exactly --im-4 — so the row's
   negative inline margin puts it flush with the viewport edge. Intended: a
   full-bleed hover row is the right phone behaviour and produces zero
   overflow, because the row is then exactly as wide as <body>. The radius is
   dropped there since it would only round against the screen edge. */
@media (max-width: 40rem) {
  .blog-card-link { border-radius: 0; }
}

/* ══ Reduced motion ════════════════════════════════════════════════════════
   industry-marketing.css's .im-page * blanket already zeroes transition and
   animation duration, and marketing.css's own reduce block un-hides
   [data-reveal]. Both are re-stated here rather than relied upon, so blog.css
   is correct on its own if the body class ever changes. Every transition this
   file declares is listed. */
@media (prefers-reduced-motion: reduce) {
  .js-reveal .blog-list[data-reveal-stagger] > .blog-card[data-reveal] {
    transition-delay: 0ms !important;
  }

  .blog-card-link,
  .blog-card-more i,
  .blog-prose a,
  .blog-post-footer .cta-secondary,
  .blog-post-footer .cta-secondary .cta-arrow {
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
  }

  .blog-card-more i,
  .blog-post-footer .cta-secondary .cta-arrow { transform: none !important; }
}

/* ══ Forced colours ════════════════════════════════════════════════════════
   In forced-colors the hover tone is overridden away, leaving the row with no
   hover signal at all. Highlight is the system's own affordance colour. */
@media (forced-colors: active) {
  .blog-card-link:hover,
  .blog-card-link:focus-visible { outline: 2px solid Highlight; outline-offset: -2px; }
}
