/* Our Insights (blog index, category archives, single posts). */

/* --- Shared: badges --- */
.insights-badge {
	/* The blur fill lives on ::before, not here — applying backdrop-filter
	   to an element with real text content is what blurred the text
	   itself in an earlier pass; keeping it on a background-only pseudo
	   with the label as an unblended sibling avoids that.
	   This uses a semi-transparent background + backdrop-filter, the same
	   proven frosted-glass technique as .insights-hero__title-bar,
	   rather than Figma's literal opaque-color + mix-blend-mode:soft-light
	   — that combination rendered as a flat opaque fill with no visible
	   blur at all (soft-light needs real contrast in what it's blending
	   against to read as anything other than a flat tint, and combining
	   it with backdrop-filter on/around the same element proved unreliable
	   in practice). Same "frosted pill over a photo" look, without the
	   fragility. */
	position: relative;
	display: inline-flex;
	align-items: center;
	padding: 10px 16px;
	border-radius: 100px;
	font-family: var(--font-display);
	font-weight: 800;
	font-size: 12px;
	color: var(--color-white);
	text-decoration: none;
	white-space: nowrap;
}

.insights-badge::before {
	content: '';
	position: absolute;
	inset: 0;
	border-radius: inherit;
	background: rgba(99, 99, 99, 0.45);
	-webkit-backdrop-filter: blur(7px);
	backdrop-filter: blur(7px);
}

.insights-badge__label {
	/* Without its own stacking context, browsers can end up compositing
	   this plain text into the same layer as ::before's blur, blurring
	   the text along with it. position+z-index forces the label onto a
	   separate layer, painted on top and untouched by it. */
	position: relative;
	z-index: 1;
}

.insights-badge--dark::before {
	background: rgba(0, 0, 0, 0.45);
	-webkit-backdrop-filter: none;
	backdrop-filter: none;
}

.insights-badge--outline::before {
	content: none;
}

.insights-badge--outline {
	border: 1px solid var(--color-ink);
	color: var(--color-ink);
}

/* --- Page intro + filters --- */
/* Both padding values respond to viewport HEIGHT, not just width — on a
   short "laptop" window, this (plus .insights__label-row's margin-bottom
   and .insights-hero's max-height, further down) is what lets the
   featured post fit above the fold without scrolling. Shared by this page
   and every filtered category archive (category.php uses the same
   classes), so the fix applies to both automatically. Calibrated so nothing
   changes at ~1050px of viewport height and up — only screens shorter than
   that start tightening. The padding-top floor (116px) is a hard minimum,
   not just an arbitrary small number — .site-header__inner (fixed,
   base.css) is 106px tall unscrolled, so anything shorter lets the title
   render underneath/behind the header instead of below it. */
.insights__intro {
	padding-top: clamp(116px, calc(7.25vh + 69px), 145px);
	padding-bottom: clamp(8px, calc(7.5vh - 39px), 40px);
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	justify-content: space-between;
	gap: 24px;
}

/* The desktop floor above (116px) was set against .site-header__inner's
   106px UNSCROLLED height — on mobile the header is locked to a single,
   much shorter ~82px (base.css's own mobile media query for it) with no
   roomy-start state to clear, so that same floor left considerably more
   blank space above the h1 than actually needed to clear it. */
@media (max-width: 900px) {
	.insights__intro {
		padding-top: 104px;
	}
}

.insights__title {
	margin: 0;
	/* font-display (Montserrat), not font-body — Roboto has no real 800
	   weight loaded (only 400/500/600, see base.css @font-face), so
	   font-weight:800 on it would just get browser-synthesized fake bold
	   instead of an actual bold typeface, same as every other 800-weight
	   use across the site. */
	font-family: var(--font-display);
	font-weight: 800;
	font-size: 3.1rem;
	color: var(--color-ink);
}

/* No explicit line-height above, so it inherits body's 1.5 — loose enough
   on a single line, but this often wraps to 2 lines on mobile (this title
   and category archives' own dynamic titles), where 1.5 reads as a big
   gap between the two. Tighter, closer to how every other large display
   heading on this site sets its own line-height already. */
@media (max-width: 900px) {
	.insights__title {
		line-height: 1.05;
	}
}

.insights__subtitle {
	margin: 0;
	font-family: var(--font-body);
	font-size: 2.1rem;
	color: var(--color-ink);
	flex: 1 1 auto;
	text-align: right;
}

/* No Figma mobile design for this page — sized down by eye. At the
   desktop size (2.1rem) this sat right underneath the 3.1rem h1 and read
   almost as large as it, competing with it instead of clearly reading as
   secondary copy. */
@media (max-width: 900px) {
	.insights__subtitle {
		font-size: 1.125rem;
		text-align: left;
	}
}

.insights__subtitle--category {
	font-size: 1.5rem;
}

/* Heading + filters share a row (e.g. "The latest" on the Our Insights
   index, or the category title on an archive), filters pushed to the
   right rather than stacked below. */
.insights__label-row {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 16px 24px;
	/* Same short-viewport tightening as .insights__intro above. */
	margin-bottom: clamp(12px, calc(3vh - 7.5px), 24px);
}

.insights__label-row .insights__label,
.insights__label-row .insights__title {
	margin-bottom: 0;
}

.insights-filters {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
}

/* .btn (base.css) supplies the rolling-text hover — the same "cool
   animation" used site-wide — this just overrides its default CTA sizing
   (min-width:14rem etc.) with the smaller outlined-pill look this design
   needs, and adds the filled "currently viewing this category" state. */
.insights-filters__pill {
	min-width: 0;
	height: 44px;
	padding: 0 20px;
	border-width: 0.5px;
	border-color: var(--color-ink);
	border-radius: 100px;
	color: var(--color-ink);
}

.insights-filters__pill:hover,
.insights-filters__pill:focus-visible {
	background: var(--color-ink);
	color: var(--color-white);
}

.insights-filters__pill .btn__roll-text {
	font-size: 12px;
}

.insights-filters__pill.is-active {
	background: var(--color-ink);
	color: var(--color-white);
}

/* --- "The latest" hero --- */
.insights__label {
	margin: 0 0 24px;
	font-family: var(--font-body);
	font-weight: 500;
	font-size: 1.5rem;
	color: var(--color-ink);
}

/* "The latest." and "All insights." (the only 2 uses of this class) drop
   entirely on mobile — the featured post/video now look identical to the
   grid below them (see .insights-hero/.insights-video's own mobile rules),
   so these section labels only re-introduced a visual break the rest of
   the page no longer has, working against "the same continuous flow" they
   were meant to signal. */
@media (max-width: 900px) {
	.insights__label {
		display: none;
	}
}

.insights-hero {
	position: relative;
	display: block;
	width: 100%;
	color: var(--color-white);
	text-decoration: none;
}

/* No Figma mobile design for this page — the featured post is meant to
   read as part of the same grid as "All insights" below it, not as a
   visually distinct hero. .insights-hero__media (not this element) now
   carries the shape/photo treatment, so it can match
   .insights-card__media's own aspect ratio exactly and stay INSET like a
   normal grid card, not full-bleed — .insights-hero-badges/the quote
   overlay/the title all follow its lead from here. */
@media (max-width: 900px) {
	.insights-hero {
		display: flex;
		flex-direction: column;
		gap: 12px;
	}
}

.insights-hero__media {
	position: relative;
	width: 100%;
	aspect-ratio: 1340 / 659;
	/* Caps the aspect-ratio height on a short viewport (object-fit:cover on
	   .insights-hero__image, below, means the photo just crops in a bit
	   tighter rather than distorting) — see .insights__intro's comment for
	   the calibration. Never binds above ~1050px of viewport height, since
	   659px (this card's normal width-derived height at the standard
	   1340px container) is already below the clamp's value by then.
	   The explicit width:100% above is required for that — without it,
	   once max-height caps the height, the browser recalculates WIDTH from
	   the aspect-ratio too (to keep the ratio exact), so the whole card
	   narrows instead of staying full-width with just the photo cropping
	   in tighter. */
	max-height: clamp(390px, calc(67vh - 47px), 659px);
	overflow: hidden;
	background: var(--color-ink);
}

/* Matches .insights-card__media's own ratio exactly, now that this is
   meant to look like just another grid card — see .insights-hero's own
   comment above. */
@media (max-width: 900px) {
	.insights-hero__media {
		aspect-ratio: 433 / 321;
		max-height: none;
	}
}

.insights-hero__media::after {
	content: '';
	position: absolute;
	inset: 0;
	/* Flat tint rather than a bottom-weighted gradient — the quote overlay
	   now sits centered (any part of the image), not just at the bottom,
	   so it needs even legibility across the whole frame. */
	background: rgba(0, 0, 0, 0.35);
}

.insights-hero__image {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.insights-hero__badges {
	position: absolute;
	top: clamp(20px, 3.5vw, 36px);
	/* Same value as .insights-hero__title-bar's horizontal padding below,
	   so the badges and the title line up to one shared left edge instead
	   of two slightly different insets. */
	left: clamp(24px, 4vw, 44px);
	z-index: 2;
	display: flex;
	flex-wrap: wrap;
	gap: 5px;
}

.insights-hero__title-bar {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 2;
	/* Vertical padding also shrinks on a short viewport (clamped against a
	   vh term, floor 12px so it never fully collapses) — same reasoning as
	   .insights-quote--large's own comment just above: this bar's own
	   footprint was part of what the centered quote/author content could
	   collide with once .insights-hero__media shrinks on a short "small
	   laptop" window. Shrinks a bit further than the 28px→~25px first pass
	   did — confirmed live there was still room to tighten this further
	   once the quote/author content itself stopped overlapping it. */
	padding: clamp(12px, calc(2.6vh + 1px), 28px) clamp(24px, 4vw, 44px);
	/* flex row (not just the title alone) so the hover arrow sits as a
	   real flex sibling, vertically centered and never overlapping the
	   title text regardless of its length. */
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 24px;
	background: rgba(0, 0, 0, 0.35);
	-webkit-backdrop-filter: blur(7px);
	backdrop-filter: blur(7px);
}

/* A plain in-flow row below the image instead of an overlay across it —
   matches .insights-card__title's own plain-text treatment now that this
   is meant to look like just another grid card (see .insights-hero's own
   comment above). */
@media (max-width: 900px) {
	.insights-hero__title-bar {
		position: static;
		padding: 0;
		background: none;
		-webkit-backdrop-filter: none;
		backdrop-filter: none;
	}
}

.insights-hero__title {
	margin: 0;
	font-family: var(--font-body);
	font-weight: 600;
	font-size: min(1.75rem, calc(0.6rem + 3vh));
	color: var(--color-white);
}

/* No Figma mobile design for this page — sized down by eye. At the
   desktop size (1.75rem) this was wrapping to 3 lines in the narrower
   mobile card, growing .insights-hero__title-bar tall enough to collide
   with the quote overlay above it (see .insights-quote--large's own
   comment). */
/* Matches .insights-card__title's own mobile treatment exactly (colour +
   size) — the title-bar it sits in is no longer an overlay across the
   image here (see its own comment above), so it needs the same dark-on-
   white treatment as the grid's plain title, not the white-on-photo
   colour the overlay version needed. */
@media (max-width: 900px) {
	.insights-hero__title {
		color: var(--color-ink);
		font-size: 1.35rem;
	}
}

.insights-hero__hover-arrow {
	flex-shrink: 0;
	display: flex;
	width: 59px;
	height: 59px;
	opacity: 0;
	transition: opacity 0.3s ease;
	pointer-events: none;
}

/* This only ever reveals on :hover, which a touch device never triggers —
   dropping it on mobile also gives .insights-hero__title's own text that
   much more width to wrap in, further reducing how tall the title bar
   grows (see its own comment above). */
@media (hover: none) {
	.insights-hero__hover-arrow {
		display: none;
	}
}

.insights-hero__hover-arrow img {
	width: 100%;
	height: 100%;
	transform: rotate(-90deg);
	/* Cancels .arrow-circle--light's filter:invert(1) (base.css) — that
	   rule assumes the base <img> is the plain dark-stroke icon needing
	   inversion to white, but this one already uses arrow-icon-white.svg,
	   so inverting it turned the resting icon black. The hover-fill state
	   (::after, a separate image) is unaffected either way. */
	filter: none;
}

/* Matches the img rotation above, so the filled hover state (.arrow-circle
   in base.css) points the same direction as the outline icon. */
.insights-hero__hover-arrow::after {
	transform: rotate(-90deg);
}

.insights-hero:hover .insights-hero__hover-arrow,
.insights-hero:focus-visible .insights-hero__hover-arrow {
	opacity: 1;
	/* Revealed on card hover with pointer-events:none (above) so it can't
	   be "hovered" at all while invisible — this re-enables hit-testing
	   once it's actually visible, so hovering the icon itself can trigger
	   its own further fill-in state (.arrow-circle, base.css). */
	pointer-events: auto;
}

/* --- Quote + author overlay (shared by the hero and grid cards) --- */
.insights-quote {
	/* Centers .insights-quote__inner as ONE block (positioned in the
	   middle of the card) — the quote text and author row live INSIDE
	   that inner wrapper instead of being direct flex children here,
	   because two independently-centered children can end up different
	   widths and therefore different left edges. align-items:flex-start
	   on the inner wrapper is what keeps both flush to the same shared
	   left edge, whatever their individual widths. */
	position: absolute;
	inset: 0;
	z-index: 1;
	display: flex;
	align-items: center;
	justify-content: center;
}

.insights-quote__inner {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	text-align: left;
}

.insights-quote__text {
	margin: 0;
	color: var(--color-white);
	font-family: var(--font-body);
	font-weight: 400;
}

.insights-quote__author {
	display: flex;
	align-items: center;
	justify-content: flex-start;
}

.insights-quote__author-photo {
	border-radius: 50%;
	object-fit: cover;
	flex-shrink: 0;
}

.insights-quote__author-name {
	margin: 0;
	color: var(--color-white);
	font-family: var(--font-body);
	font-weight: 400;
}

.insights-quote__author-role {
	margin: 0;
	color: var(--color-white);
	font-family: var(--font-body);
	opacity: 0.8;
}

/* Every size/space below that's min()'d against a "calc(Xvh + Ypx)" term
   is doing the same job: the existing value (vw-based, or just a flat
   number) sizes this fine whenever the viewport is tall enough, same as
   before, but none of it used to respond to viewport HEIGHT at all —
   only .insights-hero__media's own max-height (further up) did. On a
   wide-but-short "small laptop" window that media box shrinks well
   before this centered content does, and confirmed live: eventually the
   author row (photo/name/role) grows taller than the shrunk box has
   room for and gets clipped straight under .insights-hero__title-bar.
   The vh terms are calibrated so nothing changes at ~1050px of viewport
   height and up (same floor as .insights__intro/.insights-hero__media),
   only actually shrinking below that. */
.insights-quote--large {
	/* Symmetric padding, not a bigger bottom value to "clear" the title
	   bar — that pushed the centered content noticeably higher than true
	   centre, since flexbox centers within the space AFTER padding is
	   subtracted, and 130px was far more than the title bar (~75-90px)
	   actually needed. */
	padding: min(40px, calc(4.5vh + 2px));
}

.insights-quote--large .insights-quote__inner {
	/* max-width lives here, not on __text — __text is now nested one level
	   deeper (inside __inner), so a percentage there would have no
	   definite basis to resolve against. __inner IS a direct flex child
	   of the inset:0 outer .insights-quote, which gives percentages here
	   something real to resolve against. */
	gap: min(24px, calc(2.5vh + 1px));
	max-width: 740px;
}

.insights-quote--large .insights-quote__text {
	font-size: min(clamp(1.4rem, 2.8vw, 2.1rem), calc(0.6rem + 3.2vh));
	line-height: 1.3;
}

.insights-quote--large .insights-quote__author {
	/* width:100% so the divider spans the full quote width, not just the
	   author row's own content width — .insights-quote__inner is
	   align-items:flex-start, so without this the row (and its border-top)
	   would only be as wide as the photo + name/role text. */
	width: 100%;
	gap: min(14px, calc(1.5vh));
	padding-top: min(20px, calc(2vh));
	border-top: 2px solid rgba(255, 255, 255, 0.7);
}

.insights-quote--large .insights-quote__author-photo {
	width: min(76px, calc(6vh + 12px));
	height: min(76px, calc(6vh + 12px));
}

.insights-quote--large .insights-quote__author-name {
	font-size: min(clamp(1.4rem, 2.8vw, 2.1rem), calc(0.6rem + 3.2vh));
	/* Default (inherited) line-height of 1.5 leaves a lot of empty space
	   below text at this size, which read as a big gap before the job
	   title even with margin:0 on both — tightening this is what actually
	   closes that gap. */
	line-height: 1.1;
}

.insights-quote--large .insights-quote__author-role {
	margin-top: 4px;
	font-size: min(clamp(1.25rem, 2.4vw, 1.75rem), calc(0.5rem + 2.6vh));
	line-height: 1.1;
	/* Base .insights-quote__author-role sets opacity:0.8 for the smaller
	   card variant, which reads as grey at this larger size — full opacity
	   here keeps it a true white. */
	opacity: 1;
}

.insights-quote--small {
	/* align-items is the CROSS axis on the base .insights-quote (a row
	   flex container), i.e. vertical — overriding its center here bottom-
	   aligns the quote+author block on small grid cards only; --large
	   keeps the inherited vertical centering. */
	align-items: flex-end;
	padding: 16px 16px 28px;
}

.insights-quote--small .insights-quote__inner {
	/* Figma's own quote-card reference works out to ~16px here (see
	   author-photo below) — scaled down another ~10% across this whole
	   block, same as everything else in --small, to sit a touch smaller
	   than that reference. */
	gap: 14px;
	max-width: 90%;
}

.insights-quote--small .insights-quote__text {
	font-size: 1.35rem;
	line-height: 1.3;
}

.insights-quote--small .insights-quote__author {
	gap: 10px;
	padding-top: 14px;
	border-top: 1px solid rgba(255, 255, 255, 0.4);
}

.insights-quote--small .insights-quote__author-photo {
	/* Figma's avatar-to-name-text ratio (~2.15x) scaled to our text size,
	   then trimmed down ~10% with the rest of this block. */
	width: 46px;
	height: 46px;
}

.insights-quote--small .insights-quote__author-name {
	font-size: 1.35rem;
	line-height: 1.1;
}

.insights-quote--small .insights-quote__author-role {
	margin-top: 2px;
	font-size: 1.1rem;
	line-height: 1.1;
}

/* No Figma mobile design for this page — sized down by eye. Bottom-
   anchored (align-items:flex-end, above) means this grows UPWARD as its
   content gets taller, and at the desktop sizes it grew tall enough on a
   single-column mobile card to run into .insights-card__badges pinned at
   the top — confirmed live (quote starting 38px from the card top, badges
   ending at 60px). Smaller text/avatar/gaps here shrinks the block enough
   to clear that regardless of exactly how long a given quote is.
   .insights-quote--large (the featured hero) is folded into this same
   treatment on mobile — its own desktop-only vertical centering had the
   exact same problem colliding with .insights-hero__title-bar, back when
   that title sat overlaid across the image. Now that the title is a plain
   row below the image instead (.insights-hero__title-bar's own mobile
   rule, further up) there's nothing left for the quote to collide with,
   so this is genuinely identical to --small, no exception needed. */
@media (max-width: 900px) {
	.insights-quote--large {
		align-items: flex-end;
	}

	.insights-quote--small,
	.insights-quote--large {
		padding: 12px 12px 16px;
	}

	.insights-quote--small .insights-quote__inner,
	.insights-quote--large .insights-quote__inner {
		gap: 8px;
	}

	.insights-quote--small .insights-quote__text,
	.insights-quote--large .insights-quote__text {
		font-size: 1.15rem;
		line-height: 1.3;
	}

	.insights-quote--small .insights-quote__author,
	.insights-quote--large .insights-quote__author {
		gap: 8px;
		padding-top: 10px;
	}

	.insights-quote--small .insights-quote__author-photo,
	.insights-quote--large .insights-quote__author-photo {
		width: 36px;
		height: 36px;
	}

	.insights-quote--small .insights-quote__author-name,
	.insights-quote--large .insights-quote__author-name {
		font-size: 1.05rem;
	}

	.insights-quote--small .insights-quote__author-role,
	.insights-quote--large .insights-quote__author-role {
		font-size: 0.9rem;
	}
}

/* --- "Latest video" row --- */
.insights-video {
	display: grid;
	grid-template-columns: minmax(0, 670fr) minmax(0, 595fr);
	gap: clamp(24px, 4vw, 45px);
	margin-top: clamp(24px, 4vw, 38px);
}

/* Matches .insights-card's own image-to-title gap exactly — the featured
   video is meant to look like just another grid card on mobile, not a
   visually distinct spotlight (see .insights-video__media's own comment
   below). */
@media (max-width: 900px) {
	.insights-video {
		gap: 12px;
	}
}

.insights-video__media {
	position: relative;
	display: block;
	aspect-ratio: 670 / 413;
	overflow: hidden;
	background: var(--color-offwhite, #fafafa);
}

/* No Figma mobile design for this page — matches .insights-card__media's
   own ratio exactly, and stays INSET like a normal grid card (previously
   full-bleed) now that this is meant to look like just another grid card,
   not a distinct spotlight — same reasoning as .insights-hero's own
   mobile treatment (blog.css, further up). */
@media (max-width: 900px) {
	.insights-video__media {
		aspect-ratio: 433 / 321;
	}
}

/* Mobile-only duplicate of the desktop badge just below — shown overlaid
   on the image there (matching .insights-card__badges' own position/
   treatment) instead of in the content column, hence the separate copy
   rather than one element doing both jobs; see .insights-video__badge
   --mobile's own display rule and the desktop badge's for how each is
   scoped to just one context. */
.insights-video__badge--mobile {
	display: none;
}

@media (max-width: 900px) {
	.insights-video__badge--mobile {
		display: flex;
		position: absolute;
		top: 22px;
		left: 20px;
		z-index: 1;
	}
}

.insights-video__image {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.insights-video__content {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	justify-content: center;
	gap: 24px;
}

.insights-video__title-link {
	text-decoration: none;
}

.insights-video__title {
	margin: 0;
	font-family: var(--font-body);
	font-weight: 600;
	font-size: clamp(1.25rem, 2vw, 1.75rem);
	color: var(--color-ink);
}

/* Matches .insights-card__title's own mobile size exactly, not the site's
   plain global h3 baseline (1.7rem) this used to match — the featured
   video's title is meant to look identical to a grid card's now. */
@media (max-width: 900px) {
	.insights-video__title {
		font-size: 1.35rem;
	}
}

.insights-video__excerpt {
	margin: 0;
	max-width: 480px;
	font-family: var(--font-body);
	font-weight: 300;
	font-size: 20px;
	line-height: 1.35;
	color: var(--color-ink);
}

.insights-video__arrow {
	display: flex;
	width: 59px;
	height: 59px;
}

/* The desktop badge (in the content column) is replaced by
   .insights-video__badge--mobile, overlaid on the image instead — see its
   own comment further up. Excerpt and the separate arrow-circle link
   aren't part of a grid card's own content at all, so both drop here too,
   for the same "look like just another grid card" reason. Placed after
   these 3 elements' own unconditional rules above (not grouped with
   .insights-video__title's override above) — a media query adds no
   specificity of its own, so an earlier override here would otherwise
   still lose to those same-specificity unconditional rules, which come
   later in source order. */
@media (max-width: 900px) {
	.insights-video__content .insights-badge--outline,
	.insights-video__excerpt,
	.insights-video__arrow {
		display: none;
	}
}

.insights-video__arrow img {
	width: 100%;
	height: 100%;
	transform: rotate(-90deg);
}

.insights-video__arrow::after {
	transform: rotate(-90deg);
}

/* --- "All insights" grid --- */
.insights__all {
	/* This section and "The latest" above it are two separate .container
	   divs sat directly back to back with no gap of their own — this is
	   what actually creates the breathing room that signals a new section
	   starting, rather than the grid just reading as a continuation of
	   the hero/video block above it. */
	margin-top: clamp(56px, 7vw, 100px);
}

/* On mobile this section is no longer its own distinct block (no heading,
   no divider — see .insights__label/.insights__divider's own mobile
   rules) — matching the grid's own row-gap here instead of the much
   bigger "new section starting" gap above is what actually makes the
   featured video and the first grid card read as one continuous flow. */
@media (max-width: 900px) {
	.insights__all {
		margin-top: clamp(24px, 3vw, 47px);
	}
}

.insights__divider {
	margin: 0 0 clamp(24px, 4vw, 40px);
	border: none;
	border-top: 1px solid var(--color-ink);
}

/* Was only ever there to sit under the now-hidden "All insights." heading
   (.insights__label's own mobile rule, further up) — with that gone, a
   bare rule line here would just be a stray mark between the featured
   video and the grid, not a heading underline. */
@media (max-width: 900px) {
	.insights__divider {
		display: none;
	}
}

.insights-grid {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: clamp(24px, 3vw, 47px) clamp(20px, 2.5vw, 40px);
	padding-bottom: clamp(48px, 6vw, 90px);
}

.insights-card {
	display: flex;
	flex-direction: column;
	gap: 24px;
	text-decoration: none;
}

/* Tighter than the desktop 24px — that read as the title floating
   separately from its own card image rather than belonging to it. */
@media (max-width: 900px) {
	.insights-card {
		gap: 12px;
	}
}

.insights-card__media {
	position: relative;
	aspect-ratio: 433 / 321;
	overflow: hidden;
	background: var(--color-offwhite, #fafafa);
}

.insights-card__image {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Press & Awards photos (screenshots/logos etc, not people shots) tend to
   crop awkwardly centered — nudged down within the frame. Scoped to this
   category only via .insights-card__media--press-awards (added in
   template-parts/blog/card.php) rather than changing every insights
   card's default crop. Tweak the vertical % to taste. */
.insights-card__media--press-awards .insights-card__image {
	object-position: center 30%;
}

.insights-card__media::after {
	content: '';
	position: absolute;
	inset: 0;
	background: rgba(0, 0, 0, 0.3);
	transition: background-color 0.3s ease;
}

.insights-card:hover .insights-card__media::after,
.insights-card:focus-visible .insights-card__media::after {
	background: rgba(0, 0, 0, 0.65);
}

.insights-card__hover-arrow {
	position: absolute;
	right: 24px;
	bottom: 24px;
	z-index: 2;
	display: flex;
	width: 59px;
	height: 59px;
	opacity: 0;
	transition: opacity 0.3s ease;
	pointer-events: none;
}

.insights-card__hover-arrow img {
	width: 100%;
	height: 100%;
	transform: rotate(-90deg);
	/* See the matching comment on .insights-hero__hover-arrow — cancels
	   .arrow-circle--light's filter:invert(1), which turned this
	   already-white icon black. */
	filter: none;
}

.insights-card__hover-arrow::after {
	transform: rotate(-90deg);
}

.insights-card:hover .insights-card__hover-arrow,
.insights-card:focus-visible .insights-card__hover-arrow {
	opacity: 1;
	/* See the matching comment on .insights-hero__hover-arrow — needed so
	   the icon can register its own hover once actually visible. */
	pointer-events: auto;
}

.insights-card__badges {
	/* justify-content:flex-end matches Figma's own alignment for this row. */
	position: absolute;
	top: 22px;
	left: 20px;
	right: 20px;
	z-index: 1;
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-end;
	gap: 5px;
}

.insights-card__title {
	margin: 0;
	font-family: var(--font-body);
	font-weight: 500;
	font-size: 1.5rem;
	line-height: 1.33;
	color: var(--color-ink);
}

/* Exception to the site's global mobile h3 baseline (1.7rem, see
   base.css) — requested smaller specifically for this grid. */
@media (max-width: 900px) {
	.insights-card__title {
		font-size: 1.35rem;
	}
}

.insights__empty {
	padding-bottom: clamp(48px, 6vw, 90px);
	font-family: var(--font-body);
	font-size: 20px;
	color: var(--color-ink);
}

/* --- Pagination --- */
.insights-load-more {
	display: flex;
	justify-content: center;
	padding-bottom: clamp(48px, 6vw, 90px);
	margin-top: -24px;
}

.insights-load-more__btn.is-loading {
	opacity: 0.6;
	pointer-events: none;
}

/* Staggered fade/rise-in for cards appended by "Load more" (blog-index.js
   sets each card's transition-delay before flipping is-visible a frame
   later — see that file's own comment for why two rAFs). Skipped
   entirely under reduced motion: cards just appear, no delay/animation. */
.insights-card--enter {
	opacity: 0;
	transform: translateY(24px);
	transition: opacity 0.5s ease, transform 0.5s ease;
}

.insights-card--enter.is-visible {
	opacity: 1;
	transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
	.insights-card--enter {
		opacity: 1;
		transform: none;
		transition: none;
	}
}

/* --- Single post --- */
.insights-single__inner {
	padding-block: clamp(48px, 6vw, 90px);
	max-width: 800px;
}

.insights-single__badges {
	display: flex;
	flex-wrap: wrap;
	gap: 5px;
	margin-bottom: 24px;
}

.insights-single__title {
	margin: 0 0 24px;
	font-family: var(--font-display);
	font-weight: 800;
	font-size: clamp(2rem, 4vw, 3rem);
	color: var(--color-ink);
}

/* Global mobile h1 baseline (3.1rem) — see base.css. */
@media (max-width: 900px) {
	.insights-single__title {
		font-size: clamp(2rem, 13vw, 3.1rem);
	}
}

.insights-single__author {
	display: flex;
	align-items: center;
	gap: 14px;
	margin-bottom: 32px;
}

.insights-single__author-photo {
	width: 48px;
	height: 48px;
	border-radius: 50%;
	object-fit: cover;
}

.insights-single__author-name {
	margin: 0;
	font-family: var(--font-body);
	font-weight: 400;
	font-size: 18px;
	color: var(--color-ink);
}

.insights-single__author-role {
	margin: 0;
	font-family: var(--font-body);
	font-size: 16px;
	color: var(--color-ink);
	opacity: 0.7;
}

.insights-single__media {
	margin-bottom: 40px;
	overflow: hidden;
}

.insights-single__image {
	width: 100%;
	height: auto;
}

.insights-single__content {
	font-family: var(--font-body);
	font-size: 18px;
	line-height: 1.7;
	color: var(--color-ink);
}

.insights-single__content p {
	margin: 0 0 1.5em;
}

.insights-single__content img {
	height: auto;
}

.insights-single__back {
	margin-top: 40px;
}

.insights-single__back a {
	font-family: var(--font-display);
	font-weight: 800;
	font-size: 14px;
	color: var(--color-ink);
}

/* --- Single post: "Blog" category ---
   Top section matches Figma node 4378:25825; editors never build any of
   this by hand — the theme auto-splits the post's own content on real H2
   headings (see invanity_split_content_by_h2() in inc/blog.php) into the
   intro shown here plus the sticky-nav/body sections below. Writing the
   post is just normal paragraphs/lists/H2s, the same as any other document. */
.blog-single {
	/* No top padding — .blog-header__hero is meant to run flush to the very
	   top of the page, behind the transparent/inverted site header, not
	   pushed down below it like a plain white section would need. */
	padding-block: 0 clamp(64px, 8vw, 120px);
	/* Shared by .blog-divider--first-section and .blog-body (see the
	   comment above .blog-section further down) — declared up here since
	   the divider sits as a sibling BEFORE .blog-body, not inside it, and
	   still needs to read the same value. */
	--section-clearance: 100px;
	/* Shared by .blog-divider--first-section and .blog-section + .blog-section
	   (both are margin-top, further down) — scroll-margin-top reserves
	   space starting from an element's full MARGIN box, not just its
	   border/padding, so its own margin-top is part of where it actually
	   lands when scrolled to. Both scroll targets MUST use the identical
	   value here, or they land at different heights relative to the
	   sticky nav despite using the same --section-clearance — which is
	   exactly what happened when this was only added to the first-section
	   divider (for fitting the intro above the fold) without also
	   updating this one to match. */
	--section-gap-top: min(4rem, 5vh);
}

.blog-back {
	display: flex;
	align-items: center;
	gap: 17px;
	font-family: var(--font-body);
	font-weight: 500;
	font-size: 26px;
	color: var(--color-ink);
	text-decoration: none;
}

/* No Figma mobile design for this page — sized down by eye. At the
   desktop size this read as a second heading-weight line competing with
   .insights__title right above it, on top of the filter pills right
   below — a cluttered top of page. Smaller/tighter here marks it as the
   secondary nav link it actually is. */
@media (max-width: 900px) {
	.blog-back {
		gap: 10px;
		font-size: 17px;
	}
}

/* Two copies of the same link (category.php) — .blog-back--mobile sits
   directly under the title in .insights__intro, .blog-back--desktop stays
   down in .insights__label-row with the filters. Only one is ever shown,
   swapped per breakpoint below, rather than trying to reposition ONE
   element across two entirely separate parent containers (.insights__intro
   and the .insights__label-row further down aren't siblings, so neither a
   flex order nor a display:contents trick could move it between them). */
.blog-back--mobile {
	display: none;
}

@media (max-width: 900px) {
	.blog-back--mobile {
		display: flex;
		/* .insights__intro (its parent) is a wrapping flex ROW (title +
		   subtitle side by side on desktop) — without a full-width basis
		   this just flows into the remaining space on the title's own
		   line instead of starting a new one below it. */
		flex-basis: 100%;
		margin-top: 12px;
		margin-bottom: clamp(24px, 5vw, 40px);
	}

	.blog-back--desktop {
		display: none;
	}
}

.blog-back__arrow {
	display: flex;
	flex-shrink: 0;
	width: 2.4rem;
	height: 2.4rem;
}

@media (max-width: 900px) {
	.blog-back__arrow {
		width: 1.75rem;
		height: 1.75rem;
	}
}

.blog-back__arrow img {
	width: 100%;
	height: 100%;
	/* arrow-icon.svg points down by default — rotated to point left for a
	   "back" link (other reuses of this same asset rotate it to point
	   right or stay down, see .recent-projects__arrow / .insights-video__arrow). */
	transform: rotate(90deg);
}

.blog-back__arrow::after {
	transform: rotate(90deg);
}

/* .arrow-circle's own :hover (base.css) only fires when the pointer is
   literally over the icon itself — this extends the same fill-in to
   hovering anywhere in the "All insights" link, icon or text, since the
   whole thing is one clickable target. */
.blog-back:hover .blog-back__arrow::after,
.blog-back:focus-visible .blog-back__arrow::after {
	opacity: 1;
}

.blog-header {
	position: relative;
	/* Both .blog-header__hero's own height and .blog-header__quote's
	   vertical position/size are driven off this one value (plus its own
	   matching vh clamp — see .blog-header__quote), so they can never
	   drift out of sync with each other.

	   vh-driven, not vw — on a short-but-wide "laptop with browser chrome"
	   viewport, the constraint that actually matters is how much vertical
	   space is left, not how wide the screen is. This (plus the other vh
	   clamps through .blog-header__top/__lead/__nav-row and
	   .blog-divider--first-section below) is what lets the whole intro
	   section shrink to fit above the fold on a shorter screen.

	   The 44vh factor spans realistic viewport heights: the 420px ceiling
	   is reached around a 955px-tall viewport (955 * 0.44 ≈ 420) — tall
	   enough for a generous monitor to actually show a noticeably bigger
	   hero, rather than the previous 320px cap most screens never
	   approached — and the 200px floor releases around 455px
	   (200 / 0.44 ≈ 455). Since a single linear clamp ties both ends of the
	   range together, raising the ceiling like this also nudges short
	   viewports up a bit too (e.g. ~286px at 650px tall, vs ~240px before)
	   — still well below the original fixed 315-320px baseline, just not
	   quite as aggressively trimmed as the previous factor. */
	--hero-height: clamp(200px, 44vh, 420px);
}

.blog-header__hero {
	height: var(--hero-height);
	/* background-size:cover sizes against the VIEWPORT under fixed
	   attachment (that's spec-defined, not a bug) — since the viewport is
	   much taller than this hero box, cover was scaling the image up to
	   fill the full screen height, and only a thin, heavily-zoomed sliver
	   of that oversized image showed through the short window. 100% sizes
	   the image to the viewport's WIDTH instead (height following the
	   image's own aspect ratio), which is the dimension that actually
	   matters for a wide, short hero strip like this one — no
	   viewport-height-driven zoom. */
	background-size: 100%;
	background-position: top center;
	/* Pins the photo in place (viewport-relative) while .blog-header__top
	   and everything after it scrolls up and over it — a pure-CSS
	   parallax-style reveal, no JS. Known trade-off: iOS Safari doesn't
	   support background-attachment:fixed and just scrolls it normally
	   instead — an accepted, commonly-hit limitation of this technique. */
	background-attachment: fixed;
}

/* The frosted pull-quote card — same visual language as .insights-quote
   (Our Insights hero/grid cards: frosted glass, italic quote, author
   photo), reused here as a standalone floating card rather than an inset
   overlay. Positioned to straddle the hero/content seam (vertically
   centered ON .blog-header__hero's bottom edge, per Figma), not confined
   to sitting neatly inside either section.

   The whole card is one link to the author's page, with a hover state:
   the quote collapses away and .blog-header__quote-more (a plain
   ghost-outline pill, same treatment as the "More from X" link elsewhere)
   fades in below the author line. Height is FIXED (rather than auto, which
   the plain stacked layout could otherwise use) specifically so that
   collapsing/revealing those two pieces doesn't change the card's own box
   size — since the card is positioned via top + translateY(-50%), a
   size change would shift its vertical centering and make the whole thing
   visibly jump on hover. justify-content:center then re-centers whatever
   IS currently visible within that fixed box, which is what actually
   produces the "photo and author line move together" effect: with the
   quote gone, centering the remaining (shorter) stack pulls the photo down
   and the author line up to meet near the middle — no explicit
   move-toward-each-other logic needed, it falls out of the recentering. */
.blog-header__quote {
	position: absolute;
	top: var(--hero-height);
	right: max(var(--gutter), calc((100% - var(--container-width)) / 2 + var(--gutter)));
	transform: translateY(-50%);
	z-index: 2;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	width: 449px;
	/* Shrinks in step with --hero-height (same reasoning, same 37vh-style
	   calibration) — kept in sync deliberately: this card is centred ON
	   --hero-height (top: var(--hero-height); translateY(-50%) above), so
	   if this stayed fixed at 422px while the hero shrank a lot, at small
	   enough heights the card's top edge would end up above the hero
	   entirely (negative position, poking above the fixed site header). */
	height: clamp(300px, 55vh, 422px);
	max-width: calc(100% - 2 * var(--gutter));
	padding: 50px;
	overflow: hidden;
	text-align: center;
	text-decoration: none;
	color: inherit;
	background: rgba(250, 250, 250, 0.5);
	border: 3px solid var(--color-white);
	-webkit-backdrop-filter: blur(28.75px);
	backdrop-filter: blur(28.75px);
}

/* The photo and author line move via their own transform — a fixed
   distance, not driven by the quote text collapsing away (it just fades in
   place now, still occupying its normal space) — so it's expected/fine for
   them to briefly overlap the (invisible) quote while they're in motion. */
.blog-header__quote-photo {
	width: 100px;
	height: 100px;
	border-radius: 50%;
	object-fit: cover;
	flex-shrink: 0;
	margin-bottom: 25px;
	transition: transform 0.7s ease;
}

.blog-header__quote-text {
	margin: 0 0 25px;
	font-family: var(--font-body);
	font-weight: 300;
	font-style: italic;
	font-size: 1.4375rem;
	line-height: 1.52;
	color: var(--color-ink);
	transition: opacity 0.5s ease;
}

.blog-header__quote-author {
	margin: 0;
	font-family: var(--font-body);
	font-weight: 500;
	font-size: 1.25rem;
	color: var(--color-ink);
	transition: transform 0.7s ease;
}

/* Deliberately NOT a flex child of the photo/quote/author stack — it's
   position:absolute at a fixed spot, so it plays no part in that reflow at
   all and never itself moves. Fading its opacity is the only thing that
   ever happens to it. Plain ghost-outline styling (matches Figma's own
   separate "More from X" component); it never gets its own filled/hover
   state, since it isn't independently interactive — the whole card is
   already the one link. */
.blog-header__quote-more {
	position: absolute;
	left: 50%;
	bottom: 50px;
	transform: translateX(-50%);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	height: 44px;
	padding: 0 40px;
	border: 0.5px solid var(--color-ink);
	border-radius: 100px;
	font-family: var(--font-display);
	font-weight: 800;
	font-size: 14px;
	color: var(--color-ink);
	white-space: nowrap;
	opacity: 0;
	transition: opacity 0.5s ease;
}

.blog-header__quote:hover .blog-header__quote-text,
.blog-header__quote:focus-visible .blog-header__quote-text {
	opacity: 0;
}

.blog-header__quote:hover .blog-header__quote-photo,
.blog-header__quote:focus-visible .blog-header__quote-photo {
	transform: translateY(67px);
}

.blog-header__quote:hover .blog-header__quote-author,
.blog-header__quote:focus-visible .blog-header__quote-author {
	transform: translateY(-67px);
}

.blog-header__quote:hover .blog-header__quote-more,
.blog-header__quote:focus-visible .blog-header__quote-more {
	opacity: 1;
}

.blog-header__top {
	position: relative;
	z-index: 1;
	padding-top: clamp(20px, 4vh, 56px);
}

.blog-header__badges {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
	margin-bottom: 24px;
}

/* Layers the site's rolling-hover-text button mechanism (.btn/.btn__roll,
   base.css) onto the compact .insights-badge shape, for the ONE badge
   that's a real link (the category) — the date badge next to it stays a
   plain static pill since it isn't clickable. .btn's own default CTA
   sizing (min-width, bigger padding) is reset back to the badge's own
   smaller footprint, and .btn__roll's height/font-size is scaled down to
   match the badge's 12px label instead of a full-size button's 18px one. */
.insights-badge--roll {
	min-width: 0;
}

.insights-badge--roll:hover,
.insights-badge--roll:focus-visible {
	background: var(--color-ink);
	color: var(--color-white);
}

.insights-badge--roll .btn__roll {
	height: 15px;
}

.insights-badge--roll .btn__roll-text {
	font-size: 12px;
	line-height: 15px;
}

.blog-header__title {
	margin: 0 0 20px;
	max-width: 681px;
	font-family: var(--font-body);
	font-weight: 600;
	font-size: clamp(1.75rem, 4vw, 2.8125rem);
	line-height: 1.2;
	color: var(--color-ink);
}

/* Smaller than the site's usual mobile h1 baseline (3.1rem, see
   base.css) — requested specifically for this title: unlike a page
   heading, post titles are real, often-long sentences ("Pain points are
   just best guesses.", "Attribution in a changing user journey."), and
   at the standard baseline size a longer one wrapped to 3-4 lines felt
   oversized for a phone screen. */
@media (max-width: 900px) {
	.blog-header__title {
		font-size: clamp(1.6rem, 8vw, 2.3rem);
	}
}

.blog-header__lead {
	max-width: 658px;
	margin-bottom: min(32px, 4vh);
}

.blog-header__nav-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	padding-top: clamp(16px, 3vh, 40px);
}

.blog-header__scroll-arrow {
	display: flex;
	flex-shrink: 0;
	width: 3.4rem;
	height: 3.4rem;
}

.blog-header__scroll-arrow img {
	width: 100%;
	height: 100%;
}

/* Only the ONE divider between the header and the first section runs the
   full container width. Every divider after that belongs to
   .blog-section__body instead (border-top, below) — scoped to just the
   right-hand copy column, so it never draws a line under the left-hand
   heading and that column reads as one continuous, undivided list. */
.blog-divider {
	margin: 4rem 0;
	border: none;
	border-top: 1px solid var(--color-ink);
}

/* The TOC (.blog-toc) and the sections column (.blog-sections) are flex
   siblings with the default align-items:stretch, so .blog-toc's box always
   spans the full combined height of every section — that's what lets the
   sticky nav inside it release naturally right after the last section,
   with no JS height measurement needed (contrast the old per-H2 sticky
   label this replaced, which did need that). */
.blog-body {
	display: flex;
	gap: clamp(24px, 4vw, 64px);
}

.blog-toc {
	flex: 0 0 272px;
}

.blog-toc__nav {
	position: sticky;
	/* + 4rem matches .blog-section's own padding-top (divider to heading) —
	   without it the nav would dock level with the (mostly hidden) divider
	   rather than the H2 heading itself. */
	top: calc(var(--section-clearance) + 4rem);
}

.blog-toc__list {
	position: relative;
	margin: 0;
	/* Left padding reserves room for .blog-toc__indicator, which sits at
	   left:0 — flush with .blog-toc's own edge, same as the old sticky
	   label's edge — rather than flush with the link text. max-width caps
	   how far the link text itself can extend within .blog-toc's own
	   (wider) column — narrower than the column's full width — so wrapped
	   headings leave real empty space before the .blog-body gap even
	   starts, instead of running right up against it. */
	padding: 0 0 0 24px;
	max-width: 280px;
	list-style: none;
}

.blog-toc__item + .blog-toc__item {
	margin-top: 18px;
}

.blog-toc__link {
	display: block;
	font-family: var(--font-body);
	font-weight: 400;
	font-size: 20px;
	line-height: 1.27;
	color: var(--color-ink);
	text-decoration: none;
}

/* Position/height are set from JS (assets/js/blog-single.js) to track
   whichever .blog-toc__link is currently active — a fixed height would be
   wrong for any heading long enough to wrap onto two lines. */
.blog-toc__indicator {
	position: absolute;
	left: 0;
	top: 0;
	width: 3px;
	height: 22px;
	background: #b89c02;
	transition: transform 0.25s ease, height 0.25s ease;
}

.blog-sections {
	flex: 1 1 auto;
	min-width: 0;
	max-width: 904px;
}

/* Clicking a .blog-toc link scrolls its section flush to the very top of
   the viewport by default, which would tuck its divider/heading directly
   behind the fixed .site-header__inner — scroll-margin-top reserves
   clearance so that never happens. .site-header__inner's real height while
   reading an article (past the scroll threshold, so always in its compact
   --scrolled state, and --solid since this template's data-header-theme is
   "light") works out to a fixed ~84px (31.6px padding-top + 34px tallest
   row child, the menu toggle + its 8px padding, + 16px padding-bottom) —
   not responsive, so a flat px value here rather than a vw-based guess.

   --section-clearance (.blog-single) adds a further ~16px on top of that:
   enough that a section's divider line peeks out just below the header
   once scrolled to, confirming you've landed at the top of the right
   section, rather than the divider sitting completely hidden behind the
   header (the exact 84px clearance would do that). .blog-toc__nav (below)
   reuses this same value — plus the 4rem gap between a divider and its
   heading — so the sticky nav's own resting position stays level with the
   H2 once a section has scrolled into view, rather than sitting higher up.

   Every section but the first gets its divider from its own border-top
   (below); section 1's equivalent divider is the separate
   .blog-divider--first-section hr right before .blog-body, which carries
   this same scroll-margin-top itself (see single-blog.php) — its own
   margin:4rem 0 (see .blog-divider) already provides the same gap to the
   heading that the padding-top below provides for every other section. */
.blog-section + .blog-section {
	margin-top: var(--section-gap-top);
	padding-top: 4rem;
	border-top: 1px solid var(--color-ink);
	scroll-margin-top: var(--section-clearance);
}

.blog-divider--first-section {
	scroll-margin-top: var(--section-clearance);
	/* Must match .blog-section + .blog-section's own margin-top exactly
	   (see --section-gap-top, .blog-single) — .blog-divider's own
	   margin-bottom (the gap to .blog-body) stays the full 4rem, since
	   that's load-bearing for the scroll-target math described above, not
	   just visual spacing. */
	margin-top: var(--section-gap-top);
}

.blog-section__heading {
	margin: 0 0 1.4rem;
	font-family: var(--font-body);
	font-weight: 500;
	/* Was 1.5rem (24px) — smaller than .blog-prose h3's own 1.25em of a
	   20px base (25px), so an H2 read as barely distinct from the H3s
	   underneath it. */
	font-size: 1.875rem;
	line-height: 1.25;
	color: var(--color-ink);
}

.blog-prose {
	font-family: var(--font-body);
	font-size: 20px;
	line-height: 1.6;
	color: var(--color-ink);
}

.blog-prose p {
	margin: 0 0 1.4em;
}

.blog-prose p:last-child {
	margin-bottom: 0;
}

.blog-prose h3 {
	margin: 1.4em 0 0.6em;
	font-weight: 600;
	font-size: 1.25em;
}

/* Global mobile h3 baseline (1.7rem) — see base.css. Still smaller than
   .blog-section__heading's own 1.875rem (its comment above explains why
   that one needs to stay bigger than this), so the H2/H3 hierarchy holds. */
@media (max-width: 900px) {
	.blog-prose h3 {
		font-size: 1.7rem;
	}
}

/* When an H3 comes directly after an H2, it's the very first thing in
   .blog-section__body — the H3's own top margin then pushes it down out
   of line with the H2 label sitting beside it in the left column. Only
   strip that margin for this specific case, not H3s appearing deeper into
   a section's body copy, where the usual spacing above it is still right. */
.blog-section__body h3:first-child {
	margin-top: 0;
}

.blog-prose blockquote {
	margin: 1.4em 0;
	padding-left: 24px;
	font-style: italic;
}

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

.blog-prose blockquote p:last-child {
	margin-bottom: 0;
	font-style: normal;
	font-size: 0.85em;
	opacity: 0.75;
}

/* Native list-style-position:outside markers sit wherever the browser's
   own font metrics put them (usually a small, fixed gap before the text),
   regardless of the list's own padding-left — so bumping padding-left just
   pushes bullet+text inward together, it doesn't line the bullet itself up
   with paragraph text's left edge or let the gap be tuned. A custom
   ::before marker gives control over both: it sits at left:0 (flush with
   .blog-prose p), and li's padding-left is the actual bullet-to-text gap. */
.blog-prose ul,
.blog-prose ol {
	margin: 0 0 1.4em;
	padding-left: 0;
	list-style: none;
}

.blog-prose ol {
	counter-reset: blog-ol;
}

.blog-prose li {
	position: relative;
	padding-left: 1.5em;
	margin-bottom: 0.6em;
	line-height: 1.5;
}

.blog-prose ul li::before {
	content: '\2022';
	position: absolute;
	left: 0;
	top: 0;
	font-size: 1.5em;
	line-height: 1.1;
}

.blog-prose ol li {
	counter-increment: blog-ol;
}

.blog-prose ol li::before {
	content: counter(blog-ol) '.';
	position: absolute;
	left: 0;
	top: 0;
}

.blog-prose a {
	color: #b89c02;
	text-decoration: none;
}

.blog-prose a:hover,
.blog-prose a:focus-visible {
	text-decoration: underline;
}

.blog-prose strong {
	font-weight: 600;
}

.blog-prose img {
	width: 100%;
	height: auto;
	margin: 0.6em 0;
}

.blog-prose .has-highlight-color {
	color: #b89c02;
}

.blog-author-card {
	display: flex;
	align-items: center;
	gap: 40px;
	margin-top: clamp(48px, 6vw, 80px);
	/* Lines up with .blog-section__body (the right-hand copy column), not
	   the container's left edge — same offset as the label column
	   (.blog-section__label-zone) plus the row's own gap. */
	margin-left: calc(272px + clamp(24px, 4vw, 64px));
}

.blog-author-card__photo {
	width: 126px;
	height: 126px;
	border-radius: 50%;
	object-fit: cover;
	flex-shrink: 0;
}

.blog-author-card__details {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 12px;
}

/* Row for name+role — was just the two <p>s directly in __details before;
   the LinkedIn icon needs a flex row to sit beside them (vertically
   centered against the pair, not the whole card, hence a dedicated wrapper
   rather than adding the icon straight into __details, whose own column
   layout also holds the bio/"more from" copy below). */
.blog-author-card__heading {
	display: flex;
	align-items: center;
	gap: 16px;
}

.blog-author-card__name-role {
	display: flex;
	flex-direction: column;
}

.blog-author-card__name {
	margin: 0;
	font-family: var(--font-body);
	font-weight: 400;
	font-size: 20px;
	color: var(--color-ink);
}

.blog-author-card__role {
	margin: -8px 0 0;
	font-family: var(--font-body);
	font-size: 16px;
	color: var(--color-ink);
	opacity: 0.7;
}

.blog-author-card__linkedin {
	display: flex;
	flex-shrink: 0;
}

.blog-author-card__linkedin img {
	display: block;
	width: 1.25rem;
	height: auto;
}

.blog-author-card__bio {
	margin: 0;
	max-width: 738px;
	font-family: var(--font-body);
	font-weight: 300;
	font-size: 18px;
	line-height: 1.5;
	color: var(--color-ink);
}

.blog-author-card__more {
	font-family: var(--font-body);
	font-weight: 500;
	font-size: 16px;
	color: var(--color-ink);
}

@media (max-width: 900px) {
	/* No Figma mobile frame for this — a floating card straddling the
	   hero/content seam is a desktop-width layout trick that has nowhere
	   sensible to sit on a narrow screen, so it drops into normal flow
	   below the hero instead. background-attachment:fixed is disabled here
	   too — a pinned parallax hero doesn't really work on a touchscreen,
	   and iOS Safari ignores the property anyway. */
	.blog-header {
		/* Smaller than desktop's clamp(200px, 44vh, 420px) — at typical
		   phone heights that was rendering nearly as tall as the desktop
		   version despite the photo not really carrying much (no quote
		   card overlaid on it here any more, see .blog-header__quote's
		   own comment below). */
		--hero-height: clamp(140px, 24vh, 240px);
	}

	.blog-header__hero {
		background-attachment: scroll;
		/* Without fixed attachment, background-size is sized against this
		   element's own (short) box again, not the viewport — so cover no
		   longer causes the zoom issue 100% was working around above, and
		   reads better here since it fully fills the box edge-to-edge. */
		background-size: cover;
	}

	/* Dropped entirely on mobile, not just repositioned — the photo/quote/
	   "Written by" here just repeats the author bio card already at the
	   bottom of the post (.blog-author-card), and this one floats over
	   the hero seam specifically to read as a desktop pull-quote moment;
	   in normal in-flow mobile layout it read as a redundant, oddly-
	   placed second author block right after the (now much shorter)
	   hero, doing the same job as .blog-author-card does properly. */
	.blog-header__quote {
		display: none;
	}

	/* No Figma mobile frame for this — the side menu is a desktop-width
	   pattern (a fixed 272px column), and every heading it links to is
	   already visible in-flow as a normal .blog-section__heading, so
	   hiding it here loses nothing, it just drops the extra chrome. */
	.blog-toc {
		display: none;
	}

	.blog-author-card {
		margin-left: 0;
		flex-wrap: wrap;
	}
}

/* --- Responsive: below this, defer full mobile pass, but avoid the grid
   breaking outright --- */
@media (max-width: 900px) {
	.insights-grid {
		grid-template-columns: repeat(2, 1fr);
	}

	.insights-video {
		grid-template-columns: 1fr;
	}
}

@media (max-width: 600px) {
	.insights-grid {
		grid-template-columns: 1fr;
	}
}

/* --- Single post — Press & Awards (Figma node 3361:22719) --- */

.press-single__grid {
	display: grid;
	grid-template-columns: minmax(0, 790fr) minmax(0, 650fr);
	min-height: 100vh;
}

.press-single__content {
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	gap: 48px;
	padding-block: clamp(104px, 9vw, 128px) clamp(48px, 6vw, 80px);
	padding-right: var(--gutter);
	/* Unlike .container (max-width:1440px, centred with auto margins),
	   .press-single__grid is genuinely full-bleed — the media column runs
	   to the true right edge of the viewport — so this column can't just
	   use a fixed gutter from ITS OWN edge (also the true viewport edge)
	   without drifting left of the header/footer's centred .container
	   content on any screen wider than 1440px. This reproduces .container's
	   own inset instead (gutter, plus half of whatever width exists beyond
	   1440px) so the two line up at every width. */
	padding-left: max(var(--gutter), calc((100vw - var(--container-width)) / 2 + var(--gutter)));
}

.press-single__nav-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: 16px;
}

.press-single__badges {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 11px;
}

/* Shared by the plain date <span> and the category <a> — the latter now
   uses invanity_button()'s own .btn/.btn--dark markup (roll-track hover +
   invert-to-ink fill, same as every other pill button site-wide) instead
   of being a bare link, so this overrides .btn's bigger CTA-button sizing
   back down to badge size rather than restyling from scratch; .btn--dark
   already supplies the border colour/hover fill. */
.press-single__badge {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 0;
	height: 44px;
	padding: 0 20px;
	border: 0.5px solid var(--color-ink);
	border-radius: 100px;
	font-family: var(--font-display);
	font-weight: 800;
	font-size: 0.75rem;
	color: var(--color-ink);
	text-decoration: none;
	white-space: nowrap;
}

.press-single__badge .btn__roll {
	height: 20px;
}

.press-single__badge .btn__roll-text {
	font-family: var(--font-display);
	font-weight: 800;
	font-size: 0.75rem;
	line-height: 20px;
}

.press-single__body {
	display: flex;
	flex-direction: column;
	gap: 30px;
	max-width: 42.5625rem;
}

.press-single__title {
	margin: 0;
	font-family: var(--font-body);
	font-weight: 600;
	font-size: clamp(1.75rem, 3vw, 2.1875rem);
	line-height: 1.34;
	color: var(--color-ink);
}

/* Global mobile h1 baseline (3.1rem) — see base.css. */
@media (max-width: 900px) {
	.press-single__title {
		font-size: clamp(1.75rem, 13vw, 3.1rem);
	}
}

.press-single__copy p {
	margin: 0 0 1.4em;
	font-family: var(--font-body);
	font-weight: 300;
	font-size: 1.125rem;
	line-height: 1.72;
	color: var(--color-ink);
}

.press-single__copy p:first-child {
	font-weight: 500;
}

.press-single__copy p:last-child {
	margin-bottom: 0;
}

.press-single__copy p:empty {
	display: none;
}

/* No padding-top — bleeds up behind the fixed nav, same as every other
   page's first section handles that overlay (padding on the CONTENT
   column instead), rather than being pushed down to clear it. Grid's own
   default align-items:stretch matches its height to .press-single__content
   automatically. */
.press-single__media {
	position: relative;
	min-height: 26rem;
	overflow: hidden;
	background: var(--color-offwhite, #fafafa);
}

.press-single__image {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

@media (max-width: 900px) {
	.press-single__grid {
		grid-template-columns: 1fr;
		min-height: 0;
	}

	.press-single__media {
		min-height: 22rem;
		order: -1;
	}

	.press-single__content {
		padding-top: clamp(60px, 11vw, 96px);
	}
}

/* --- Single post — Videos (Figma node 3373:24366) --- */

.video-single {
	padding-top: clamp(96px, 9vw, 128px);
	padding-bottom: clamp(48px, 6vw, 80px);
}

.video-single__panel {
	display: grid;
	grid-template-columns: minmax(0, 587fr) minmax(0, 752fr);
	/* Lets .video-single__media (below) size its own min-height off its
	   OWN rendered width via cqw, whatever that width happens to be at any
	   screen size — see its comment. */
	container-type: inline-size;
	/* Default align-items:stretch — .video-single__media (full-bleed,
	   object-fit:cover) always fills this row top-to-bottom with no gap,
	   whatever height it ends up being. .video-single__content's own
	   padding below is what actually controls that height day-to-day — see
	   its comment. */
	margin-top: clamp(24px, 3vw, 40px);
	background: var(--color-offwhite, #fafafa);
}

.video-single__content {
	display: flex;
	flex-direction: column;
	align-self: center;
	/* Trimmed from 25px — tried reducing the left/right padding first to
	   see if it would let the title/copy wrap to fewer lines and free up
	   room that way, but even at 0 padding the title still needed the
	   full column width and more (scrollWidth exceeded it), so that had
	   no effect at all. Gap actually shrinks the content's own height
	   directly, so the savings go straight into more top/bottom padding
	   below instead — same total content height (so the video's aspect
	   ratio calibration there is untouched), just less of it eaten by the
	   gaps and more by breathing room top/bottom. */
	gap: 14px;
	/* This padding (not the video) sets the row's height, since the video
	   just stretches to match it. The vertical value here used to lock at
	   its own ceiling (56px) by 1120px of viewport width — but the PANEL
	   itself keeps shrinking all the way up to 1440px (.container's own
	   max-width), so from 1120-1440px the video's width kept shrinking
	   while this padding (and so the row's height) stayed flat, and the
	   crop quietly got worse the whole way down that range — worst right
	   around 1366px, a common laptop width, where it became visible even
	   on an ordinarily-short post. 15.8vw/167.6px below re-derives that
	   same ceiling (60px) at the exact viewport width (1440px) where the
	   panel's own shrinking stops, so the two now move together instead
	   of one lagging the other — a longer post, or a narrower screen
	   forcing the copy to wrap more, will still grow the row and crop the
	   video further, which is expected/acceptable here. */
	padding: clamp(35px, calc(15.8vw - 167.6px), 60px) clamp(20px, 5vw, 60px);
}

.video-single__title {
	margin: 0;
	font-family: var(--font-body);
	font-weight: 600;
	font-size: 2.1875rem;
	line-height: 1.34;
	color: var(--color-ink);
}

/* Global mobile h1 baseline (3.1rem) — see base.css. */
@media (max-width: 900px) {
	.video-single__title {
		font-size: 3.1rem;
	}
}

.video-single__copy p {
	margin: 0 0 0.8em;
	max-width: 24.375rem;
	font-family: var(--font-body);
	font-weight: 300;
	font-size: 1.125rem;
	line-height: 1.72;
	color: var(--color-ink);
}

.video-single__copy p:last-child {
	margin-bottom: 0;
}

.video-single__watch {
	align-self: flex-start;
	gap: 9px;
}

.video-single__watch-icon {
	display: block;
	width: 1.583rem;
	height: auto;
	flex-shrink: 0;
}

.video-single__media {
	/* display:block matters now this can be an <a> (when video_url is set)
	   as well as a <div> — height comes from the grid row (stretch), which
	   .video-single__content's padding controls — see its own comment.
	   That's fine when the row ends up TALLER than a true 16:9 (a longer
	   post crops left/right, which is fine) — but a short post (e.g. no
	   body copy, no "Watch Video" button) can shrink the row shorter than
	   that, and object-fit:cover crops top/bottom instead once the box is
	   wider-than-16:9. min-height stops it going shorter than a true 16:9
	   for its own width without capping how much taller it's allowed to
	   grow: 56.25% (9/16) of this column's own share (752/1339) of the
	   panel's width — cqw needs .video-single__panel's own
	   container-type:inline-size (above) to mean anything. */
	display: block;
	position: relative;
	min-height: calc(752 / 1339 * 9 / 16 * 100cqw);
	background: var(--color-ink);
	overflow: hidden;
}

.video-single__video {
	position: absolute;
	inset: -1px;
	/* base.css sets a sitewide max-width:100% on every img/svg/video, to
	   stop a plain image ever overflowing its container — that was
	   silently capping the deliberate 1px-per-side overshoot below back
	   down to exactly 100% width (height was unaffected — no matching
	   max-height rule exists), which is why the bottom edge's rounding
	   gap (see below) seemed fixed but the same gap just reappeared on
	   the right edge instead. This overrides that cap specifically here. */
	max-width: none;
	/* .video-single__media's own height comes from a flex/clamp() chain
	   (.video-single__content's padding, all the way up), so it's almost
	   always a fractional pixel value (423.4px, 400.0px, etc). A <video>
	   sized at an exact 100% of a fractional-height container can end up
	   rounded a hair short by the browser, leaving a thin sliver of
	   .video-single__media's own dark background showing along one edge
	   — a rendering rounding gap, not an actual gap in the source footage
	   (confirmed against the raw video file directly, which fills edge-
	   to-edge with no such line baked in). Deliberately overshooting the
	   box by 1px on every side guarantees full coverage regardless of
	   which way the rounding falls; .video-single__media's own
	   overflow:hidden clips the harmless 1px overflow back off. */
	width: calc(100% + 2px);
	height: calc(100% + 2px);
	/* Fills the panel edge-to-edge top and bottom, cropping left/right of
	   whatever doesn't fit — the trimmed padding above (.video-single__
	   content) keeps this close to a true 16:9 crop under normal content
	   lengths at a normal desktop width; a longer post or a narrower
	   screen will crop it further, which is fine. */
	object-fit: cover;
	display: block;
}

/* Was max-width:900px — checked against a real, longer post: down at
   1200px and below, the two-column layout's crop (.video-single__media,
   above) got severe enough to zoom into a small corner of the video
   rather than a mild crop. 1280px stayed fine; 1200px didn't — 1279px
   is the highest breakpoint that keeps the bad range out. 1366px itself
   stays in the two-column layout — see .video-single__content's own
   padding, tuned specifically to keep that width crop-free too. */
@media (max-width: 1279px) {
	.video-single__panel {
		grid-template-columns: 1fr;
	}

	.video-single__media {
		order: -1;
		/* Stacked now, not sharing a grid row with .video-single__content
		   any more — nothing left to stretch against (the video itself is
		   position:absolute, so it can't set this container's height on
		   its own), so this needs its own explicit size again. A plain
		   16:9 box rather than a min-height: there's no competing content
		   height to crop against here either, so there's no reason for
		   this to crop at all on a stacked layout. */
		aspect-ratio: 16 / 9;
	}

	.video-single__title,
	.video-single__copy p {
		max-width: none;
	}
}

/* --- Single post — eBooks (Figma node 3361:24075) --- */

.ebook-single {
	padding-top: clamp(96px, 9vw, 128px);
}

.ebook-single__hero {
	position: relative;
	width: 100%;
	aspect-ratio: 1440 / 609;
	margin-top: clamp(24px, 3vw, 40px);
	overflow: hidden;
	background: #d9d9d9;
}

.ebook-single__hero-image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Same frosted-glass technique as .insights-badge above — the blur fill
   lives on ::before (a background-only pseudo), not this element itself,
   so the white text on top of it stays crisp rather than blurring too. */
.ebook-single__panel {
	position: absolute;
	inset: 0;
	right: 50.69%;
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 30px;
	padding-block: clamp(24px, 3vw, 40px);
	/* Double the normal side padding here specifically — this content
	   sits over a photo, not plain body copy, so it reads better with
	   more breathing room off the frosted panel's own edges. */
	padding-inline: clamp(64px, 8vw, 104px);
	color: var(--color-white);
}

.ebook-single__panel::before {
	content: '';
	position: absolute;
	inset: 0;
	background: rgba(99, 99, 99, 0.45);
	-webkit-backdrop-filter: blur(26px);
	backdrop-filter: blur(26px);
}

.ebook-single__panel > * {
	position: relative;
	z-index: 1;
}

.ebook-single__author {
	display: flex;
	align-items: center;
	gap: 15px;
}

.ebook-single__author-photo {
	width: 4.25rem;
	height: 4.25rem;
	border-radius: 50%;
	object-fit: cover;
	flex-shrink: 0;
}

.ebook-single__author-name,
.ebook-single__author-role {
	margin: 0;
	font-family: var(--font-body);
	font-size: 1.25rem;
	line-height: 1.5rem;
	color: var(--color-white);
}

.ebook-single__author-name {
	font-weight: 500;
}

.ebook-single__author-role {
	font-weight: 400;
}

.ebook-single__title {
	margin: 0;
	font-family: var(--font-body);
	font-weight: 600;
	font-size: 2.1875rem;
	line-height: 1.34;
	color: var(--color-white);
}

/* Global mobile h1 baseline (3.1rem) — see base.css. */
@media (max-width: 900px) {
	.ebook-single__title {
		font-size: 3.1rem;
	}
}

.ebook-single__copy p {
	margin: 0;
	font-family: var(--font-body);
	font-weight: 500;
	font-size: 1.125rem;
	line-height: 1.72;
	color: var(--color-white);
}

/* .ebook-single__panel is a flex column with no align-items set, so its
   default (stretch) was pulling this button out to the panel's full
   width — .btn itself is only inline-flex, which controls its own
   contents, not how the panel lays IT out as a flex item. */
.ebook-single__download {
	align-self: flex-start;
}

/* Overrides .btn's own sitewide hover (fills solid var(--color-ink)) —
   that reads fine on the plain-colour sections .btn is normally used in,
   but felt heavy/flat here, sat on top of this page's own already-dark,
   blurred photo backdrop. A bright white fill instead gives a crisper
   pop against that backdrop; text flips to ink to stay legible against
   it (.btn__roll-text has no colour of its own, so it just inherits this
   like it already inherits the default white). border-color is pinned
   back to white explicitly — .btn's own border is `currentColor`, so it
   would otherwise flip to ink right along with the text colour above. */
.ebook-single__download:hover,
.ebook-single__download:focus-visible {
	background: var(--color-white);
	border-color: var(--color-white);
	color: var(--color-ink);
}

@media (max-width: 900px) {
	.ebook-single__hero {
		aspect-ratio: auto;
		min-height: 32rem;
	}

	.ebook-single__panel {
		right: 0;
		padding-inline: clamp(24px, 3vw, 40px);
	}
}

/* --- Author archive (Figma node 4653:36697) --- */

.author-hero {
	position: relative;
	width: 100%;
	aspect-ratio: 1440 / 773;
	/* Was clamped down as low as 62vh — on any normal-height window that's
	   well short of this ratio's real height at typical widths, so
	   object-fit:cover (below) was cropping in far tighter than Figma's own
	   1440x773 frame, zooming in past the desk/environment context and
	   straight into a close-up of the subject — which also made the lower-
	   resolution photos (not everyone's source photo is as high-res) look
	   soft/pixelated from the extra effective upscale. 90vh only steps in
	   on an unusually short window; on any ordinary screen the hero now
	   reaches its full designed height. min-height is a floor for the
	   narrow-but-not-yet-mobile range just above the 900px breakpoint
	   below, where aspect-ratio alone would otherwise start pinching this
	   short. */
	max-height: 90vh;
	min-height: 460px;
	overflow: hidden;
	background: var(--color-ink);
	color: var(--color-white);
}

.author-hero__media {
	position: absolute;
	inset: 0;
}

.author-hero__image {
	/* Crop, left-to-right darkening, and the progressive blur under the text
	   are all baked into this file already (exported per-person straight
	   from Figma) — CSS previously tried to reproduce all three (transform
	   scale/object-position per person, a backdrop-filter+mask blur, a
	   gradient overlay) and never quite matched; baked-in art replaced all
	   of it, so this is just a plain full-bleed image now. */
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.author-hero__content {
	position: absolute;
	inset: 0;
	z-index: 1;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	justify-content: center;
	gap: 25px;
	max-width: 600px;
	padding-inline: var(--gutter);
}

.author-hero__role {
	margin: 0;
	font-family: var(--font-body);
	font-weight: 600;
	font-size: 1rem;
	color: var(--color-white);
}

.author-hero__name {
	margin: 0;
	font-family: var(--font-display);
	font-weight: 800;
	font-size: clamp(1.75rem, 4vw, 2.5rem);
	line-height: 1.2;
	color: var(--color-white);
}

/* Brought in line with the site's new global mobile h1 baseline (3.1rem) —
   this page title previously topped out smaller (2.5rem) than every other
   single-post title on the site. */
@media (max-width: 900px) {
	.author-hero__name {
		font-size: clamp(1.75rem, 13vw, 3.1rem);
	}
}

.author-hero__bio {
	margin: 0;
	font-family: var(--font-body);
	/* See body-copy weight convention (300, not Figma's mislabeled 400) used
	   everywhere else prose appears on this site. */
	font-weight: 300;
	font-size: 1.25rem;
	line-height: 1.6;
	color: var(--color-white);
}

.author-hero__social img {
	display: block;
	width: 2.125rem;
	height: auto;
}

/* Pulls the grid (and, now, the "{Author}'s posts" heading above it) up to
   peek over the hero's bottom edge, matching every Figma author frame (the
   grid starts ~66px before the hero photo actually ends) — a negative
   margin rather than positioning the hero shorter/the grid absolutely,
   since this still needs to work as plain flow content of whatever height
   the post grid ends up being. Bumped more negative than before (was
   clamp(-90px, -5vw, -40px)) now that the heading sits at the top of this
   pulled-up zone too — the old value left the heading looking barely
   tucked into the photo at all once it had to share the space with the
   grid below it. Mobile has its own much smaller override just below —
   see that rule's own comment for why this can't just scale down the same
   way on narrow screens. */
.author__posts {
	margin-top: clamp(-180px, -10vw, -70px);
}

/* Sits right at the top of .author__posts, in the same negative-margin
   zone that overlaps the hero photo's own bottom edge — white, matching
   .author-hero__name/__role, since it's still over that photo (baked-in
   darkening/blur) rather than the plain white background the grid itself
   sits on further down. */
.author__posts-heading {
	position: relative;
	z-index: 1;
	margin: 0 0 24px;
	font-family: var(--font-body);
	font-weight: 500;
	font-size: 1.5rem;
	color: var(--color-white);
}

@media (max-width: 900px) {
	.author-hero {
		aspect-ratio: auto;
		min-height: 32rem;
	}

	.author-hero__content {
		max-width: none;
		padding-inline: var(--gutter);
	}

	/* Much smaller than the desktop value above (clamp bottoming out at
	   -70px) — on a narrow phone the hero's own content (role/name/bio/
	   social) is centred in a fixed 32rem-tall box, so a long bio (Simon
	   Batten's, the longest in use, wraps to 7 lines at 375px) already
	   leaves very little clearance below it before the hero's own bottom
	   edge — confirmed live: the old -40px was already enough to overlap
	   this heading straight on top of that bio's last line, AND the
	   heading's own full (desktop-size) height didn't fit inside what's
	   left of the photo either, so part of it fell below the photo onto
	   the plain white background underneath — invisible there, being
	   white text. Smaller font/line-height (below) shrinks the heading
	   enough to fit entirely within that leftover strip with a small
	   clear gap above the bio, checked live against that same
	   longest-bio case (the actual worst case in current use). */
	.author__posts {
		margin-top: -22px;
	}

	.author__posts-heading {
		font-size: 1rem;
		line-height: 1.2;
	}
}
