/*
 * dbi services · Neve Child Theme
 * assets/css/dbi-blog.css — shared blog styles.
 *
 * Loaded on the front page AND all post-list pages (see inc/enqueue.php).
 * The design-token bridge (.dbi-scope) lives in tokens.css, loaded
 * site-wide — this file only consumes the tokens. Contains:
 *   2. The category filter bar, styled for wp_nav_menu() output
 *      (menu location "dbi-blog-filter" — see inc/category-filter.php).
 *   3. The archive post list, styled for Neve's native grid markup to
 *      match the homepage cards (front-page.css). Scoped to
 *      body.dbi-scope, added by inc/archive.php on post-list pages;
 *      grid structure (columns, radius, card bg) is Customizer-driven,
 *      seeded by .agent/scripts/seed-neve-customizer.php §5.
 *
 * @package dbi-neve-child
 */

/* ════════════════════════════════════════════════
   1 · DESIGN TOKENS → moved to tokens.css
   (site-wide dependency of this file, see
   inc/enqueue.php)
   ════════════════════════════════════════════════ */


/* ════════════════════════════════════════════════
   2 · CATEGORY FILTER BAR (sticky)
   Markup: nav.dbi-filter-bar > ul.dbi-filter-inner
           > li.menu-item > a   (wp_nav_menu output)
   Active state: WordPress adds .current-menu-item
   to the li of the archive being viewed — no JS.
   Selectors are prefixed with .dbi-filter-bar: on
   the homepage the nav renders inside the page's
   content area, whose generic list/content rules
   otherwise out-rank single-class selectors (the
   ul lost its centering and gained margins there).
   ════════════════════════════════════════════════ */
.dbi-filter-bar {
	background: rgba( 255, 255, 255, .93 );
	backdrop-filter: blur( 16px ) saturate( 180% );
	-webkit-backdrop-filter: blur( 16px ) saturate( 180% );
	border-bottom: 1px solid var( --border );
	position: sticky;
	/* Offset hook: if Neve's sticky header is ever enabled, set this
	   var to the header height instead of retuning the bar. */
	top: var( --dbi-sticky-offset, 0px );
	z-index: 90;
	width: 100%;
	margin: 0;
}

.dbi-filter-bar .dbi-filter-inner {
	max-width: 1160px;
	margin: 0 auto;
	padding: 10px 24px;
	display: flex;
	gap: 6px;
	align-items: center;
	list-style: none;
	overflow-x: auto;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
}
.dbi-filter-bar .dbi-filter-inner::-webkit-scrollbar { display: none; }

.dbi-filter-bar .dbi-filter-inner li {
	margin: 0;
	padding: 0;
	list-style: none;
	flex-shrink: 0;
}

.dbi-filter-bar .dbi-filter-inner a {
	display: inline-flex;
	align-items: center;
	padding: 6px 16px;
	border-radius: 999px;
	font-family: var( --f-body );
	font-size: .78rem;
	font-weight: 500;
	white-space: nowrap;
	cursor: pointer;
	text-decoration: none !important;
	border: 1px solid var( --border );
	color: var( --muted );
	background: transparent;
	transition: background var( --t ) var( --ease ), color var( --t ), border-color var( --t );
}
.dbi-filter-bar .dbi-filter-inner a:hover {
	background: var( --teal-light );
	color: var( --teal-dark );
	border-color: rgba( 0, 170, 160, .30 );
}

/* Active pill — .current-menu-item is set by WordPress core; the
   ancestor classes keep the branch pill lit on child archives
   (e.g. Database while viewing /category/postgresql/). */
.dbi-filter-bar .current-menu-item > a,
.dbi-filter-bar .current-menu-ancestor > a,
.dbi-filter-bar .current-category-ancestor > a {
	background: var( --teal-dark );
	color: #fff !important;
	border-color: var( --teal-dark );
}

/* ── Post-count chip inside a pill ──
   Rendered server-side (dbi_category_filter_count_badge): the branch
   total — the term's posts plus all descendants'. Inherits the pill's
   text color so it tracks every state; only the tint changes. */
.dbi-filter-bar .dbi-filter-count {
	margin-left: 7px;
	padding: 1px 7px;
	border-radius: 999px;
	background: var( --divider );
	font-size: .85em;
	font-weight: 500;
	font-variant-numeric: tabular-nums;
	transition: background var( --t ) var( --ease );
}
.dbi-filter-bar a:hover .dbi-filter-count,
.dbi-filter-bar .dbi-filter-sub .dbi-filter-sub-current > a .dbi-filter-count {
	background: var( --teal-mid );
	color: var( --teal-deep ); /* teal-dark on the tint is 4.3:1, small text needs 4.5 */
}
.dbi-filter-bar .current-menu-item > a .dbi-filter-count,
.dbi-filter-bar .current-menu-ancestor > a .dbi-filter-count,
.dbi-filter-bar .current-category-ancestor > a .dbi-filter-count {
	background: rgba( 0, 0, 0, .22 ); /* darkens the pill; a white tint dropped white text to 3.5:1 */
}

/* Keyboard focus mirrors hover (a11y baseline, design-principles §6) */
.dbi-filter-bar a:focus-visible {
	outline: 2px solid var( --teal );
	outline-offset: 2px;
}

/* ── Scroll affordance: edge fades ──
   .has-fade-left/right are toggled by assets/js/filter-bar.js while
   pills are scrolled out of view on that side. Mask-based so the fade
   sits at the visible edge of the scroll container. No JS → no fade —
   the rows scroll fine either way. */
.dbi-filter-bar .has-fade-right {
	-webkit-mask-image: linear-gradient( to right, #000 calc( 100% - 48px ), transparent );
	mask-image: linear-gradient( to right, #000 calc( 100% - 48px ), transparent );
}
.dbi-filter-bar .has-fade-left {
	-webkit-mask-image: linear-gradient( to right, transparent, #000 48px );
	mask-image: linear-gradient( to right, transparent, #000 48px );
}
.dbi-filter-bar .has-fade-left.has-fade-right {
	-webkit-mask-image: linear-gradient( to right, transparent, #000 48px, #000 calc( 100% - 48px ), transparent );
	mask-image: linear-gradient( to right, transparent, #000 48px, #000 calc( 100% - 48px ), transparent );
}

/* ── Fine pointer: the fade zone is the scroll control ──
   A mouse has no discoverable horizontal-scroll input (hidden
   scrollbar, shift+wheel unknown), so filter-bar.js — fine pointer
   only — wraps each row in .dbi-filter-scroll and lays a button over
   each fade: hovering it glides the row, clicking jumps ~2/3 of the
   visible width. Enabled only on a side that actually overflows
   (.has-fade-* on the row), revealed while the bar is hovered. Touch
   keeps the plain swipeable row. (enhancements #24) */
.dbi-filter-bar .dbi-filter-scroll {
	position: relative;
	max-width: 1160px;
	margin: 0 auto;
}
.dbi-filter-bar .dbi-filter-edge {
	position: absolute;
	top: 0;
	bottom: 0;
	width: 48px;
	margin: 0;
	padding: 0;
	border: 0;
	background: transparent;
	display: flex;
	align-items: center;
	justify-content: center;
	color: var( --muted );
	cursor: pointer;
	opacity: 0;
	pointer-events: none;
	transition: opacity var( --t ) var( --ease ), color var( --t );
}
.dbi-filter-bar .dbi-filter-edge--left { left: 0; }
.dbi-filter-bar .dbi-filter-edge--right { right: 0; }
.dbi-filter-bar .dbi-filter-edge svg {
	box-sizing: content-box;
	padding: 5px;
	border-radius: 999px;
	border: 1px solid var( --border );
	background: var( --bg-pure );
	box-shadow: 0 1px 3px rgba( 0, 0, 0, .08 );
	transition: transform var( --t ) var( --ease ), border-color var( --t );
}
/* Micro-feedback: the arrow nudges toward the scroll direction on
   hover and presses down on click, so the zone reads as live before
   the row has moved. */
.dbi-filter-bar .dbi-filter-edge--left:hover svg { transform: translateX( -2px ); }
.dbi-filter-bar .dbi-filter-edge--right:hover svg { transform: translateX( 2px ); }
.dbi-filter-bar .dbi-filter-edge:hover svg { border-color: rgba( 0, 170, 160, .30 ); }
.dbi-filter-bar .dbi-filter-edge:active svg { transform: scale( .92 ); }
.dbi-filter-bar .dbi-filter-sub ~ .dbi-filter-edge svg {
	width: 11px;
	height: 11px;
	padding: 4px;
}
.dbi-filter-bar .has-fade-left ~ .dbi-filter-edge--left,
.dbi-filter-bar .has-fade-right ~ .dbi-filter-edge--right {
	pointer-events: auto;
}
.dbi-filter-bar:hover .has-fade-left ~ .dbi-filter-edge--left,
.dbi-filter-bar:hover .has-fade-right ~ .dbi-filter-edge--right {
	opacity: 1;
}
.dbi-filter-bar .dbi-filter-edge:hover {
	color: var( --teal-dark );
}

/* ── Second row: child categories of the active branch ──
   Rendered by dbi_render_category_filter_children() on category
   archives only. Visually subordinate to the menu row: smaller
   type, no borders, tinted (not solid) active state. */
.dbi-filter-bar .dbi-filter-sub {
	max-width: 1160px;
	margin: 0 auto;
	padding: 8px 24px 10px;
	border-top: 1px solid var( --divider );
	display: flex;
	gap: 4px;
	align-items: center;
	list-style: none;
	overflow-x: auto;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
}
.dbi-filter-bar .dbi-filter-sub::-webkit-scrollbar { display: none; }

.dbi-filter-bar .dbi-filter-sub li {
	margin: 0;
	padding: 0;
	list-style: none;
	flex-shrink: 0;
}

.dbi-filter-bar .dbi-filter-sub a {
	display: inline-flex;
	align-items: center;
	padding: 3px 12px;
	border: 1px solid var( --border ); /* rest-state affordance, same as the post header's child pills (post.css) — owner feedback 2026-09-04 */
	border-radius: 999px;
	font-family: var( --f-body );
	font-size: .72rem;
	font-weight: 500;
	white-space: nowrap;
	text-decoration: none !important;
	color: var( --muted );
	background: transparent;
	transition: background var( --t ) var( --ease ), color var( --t ), border-color var( --t );
}
.dbi-filter-bar .dbi-filter-sub a:hover {
	background: var( --teal-light );
	border-color: transparent;
	color: var( --teal-dark );
}
.dbi-filter-bar .dbi-filter-sub .dbi-filter-sub-current > a {
	background: var( --teal-light );
	border-color: transparent;
	color: var( --teal-dark );
	font-weight: 600;
}


/* ════════════════════════════════════════════════
   3 · ARCHIVE POST LIST (Neve native markup)
   Card look mirroring the homepage grid
   (front-page.css): white card, 12px radius,
   hairline border, hover lift, serif titles,
   3-line excerpt, byline pinned to the bottom.
   Markup: .nv-index-posts > .posts-wrapper >
     article.layout-grid > .article-content-col >
       .content > h2.blog-entry-title
               + ul.nv-meta-list + .excerpt-wrap
               + [.nv-post-thumbnail-wrap]
   Scope: body.dbi-scope (see inc/archive.php).
   ════════════════════════════════════════════════ */

/* Neve (style-main-new.min.css) pushes the post list down 60px; the
   page-title zone below carries its own spacing, so drop it. */
body.dbi-scope .nv-index-posts {
	margin-top: 0;
}

/* Page title zone (Neve views/page-header.php) */
body.dbi-scope .nv-page-title-wrap {
	padding: 34px 0 10px;
}
body.dbi-scope .nv-page-title h1 {
	font-family: var( --f-display );
	font-size: clamp( 2.2rem, 4.5vw, 3.2rem );
	font-weight: 400;
	line-height: 1.08;
	letter-spacing: -.01em;
	color: var( --text );
	margin: 0;
}
/* Category description, when set (Posts → Categories) */
body.dbi-scope .nv-page-title p {
	font-family: var( --f-body );
	font-size: .95rem;
	color: var( --muted );
	line-height: 1.7;
	max-width: 60ch;
	margin: 10px 0 0;
}

/* Author page header (ADR 0019): the portrait comes from the "Author
   page portrait" Custom Layout (.agent/docs/author-page/), hooked at
   neve_before_page_title — so it lands inside .nv-page-title right
   before Neve's h1 (the author's name) and bio paragraph. Laid out as
   a two-column grid: portrait left, name over bio right; the portrait
   spans both rows and stays aligned with the name when there is no
   bio. Stacks under 600px.
   The group reads as one card (owner request 2026-09-28, Gestalt
   common region) in the post cards' language — white surface, hairline
   border, 12px radius — but static: no hover lift, it isn't a link. */
body.dbi-scope.author .nv-page-title {
	display: grid;
	grid-template-columns: auto 1fr;
	column-gap: 24px;
	align-items: start; /* portrait tops out with the name, not mid-bio */
	background: var( --bg-pure );
	border: 1px solid var( --border );
	border-radius: 12px;
	padding: 28px;
}
/* Room between the card and the post grid (plain archives keep the
   tighter 10px from the base rule above). */
body.dbi-scope.author .nv-page-title-wrap {
	padding-bottom: 22px;
}
body.dbi-scope.author .dbi-author-portrait {
	grid-row: 1 / span 2;
}
body.dbi-scope.author .dbi-author-portrait img {
	display: block;
	width: 96px;
	height: 96px;
	border-radius: 50%;
	object-fit: cover;
	border: 1px solid var( --border );
	background: var( --surface );
}
body.dbi-scope.author .nv-page-title h1 {
	align-self: end;
}
body.dbi-scope.author .nv-page-title p {
	align-self: start;
	margin-top: 6px;
	white-space: pre-line; /* Neve prints the bio's line breaks raw in one <p> */
}
@media ( max-width: 599px ) {
	body.dbi-scope.author .nv-page-title {
		grid-template-columns: 1fr;
		row-gap: 4px;
		padding: 20px;
	}
	body.dbi-scope.author .dbi-author-portrait {
		grid-row: auto;
		margin-bottom: 10px;
	}
	body.dbi-scope.author .dbi-author-portrait img {
		width: 72px;
		height: 72px;
	}
}

/* Author publishing stats (PoC, ADR 0029): [dbi_author_stats] rendered
   by the "Author page stats" Custom Layout at neve_after_page_title —
   inside the card, in DOM after the h1 and bio. Base (600–959px): in
   the text column under the bio, split off by a hairline; on the
   stacked mobile card the DOM order is already right. From 960px the
   card fills its right whitespace instead: bio and stats share the
   text area 60/40 as two columns (owner request 2026-09-29), the
   stats top-aligned with the bio under the name, hairline between. */
body.dbi-scope.author .nv-page-title .dbi-author-stats {
	grid-column: 2;
	border-top: 1px solid var( --divider );
	margin-top: 18px;
	padding-top: 16px;
	font-family: var( --f-body );
}
@media ( min-width: 960px ) {
	body.dbi-scope.author .nv-page-title {
		/* portrait · bio 60 · stats 40. Rows: name / bio / icons; the
		   1fr row absorbs the tall spanning stats column, so the icons
		   stay right under the bio instead of sinking. */
		grid-template-columns: auto minmax( 0, 3fr ) minmax( 0, 2fr );
		grid-template-rows: auto auto auto 1fr;
	}
	body.dbi-scope.author .nv-page-title h1 {
		grid-column: 2;
		grid-row: 1;
	}
	body.dbi-scope.author .nv-page-title p {
		grid-column: 2;
		grid-row: 2;
	}
	body.dbi-scope.author .nv-page-title > .dbi-author-profiles {
		grid-row: 3;
	}
	body.dbi-scope.author .nv-page-title .dbi-author-stats {
		grid-column: 3;
		grid-row: 2 / -1;
		border-top: 0;
		margin-top: 6px; /* the bio's own top margin, so both columns start level */
		padding-top: 0;
		border-left: 1px solid var( --divider );
		padding-left: 28px;
	}
}

/* v1 · count line: "187 posts · since March 2014" + the sparkline */
body.dbi-scope .dbi-author-stats__meta {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 4px 10px;
	margin: 0 0 12px;
	font-size: .85rem;
	line-height: 1.4;
	color: var( --text );
}
body.dbi-scope .dbi-author-stats__count {
	font-weight: 600;
}
body.dbi-scope .dbi-author-stats__since {
	color: var( --muted );
}
/* v2 · sparkline of posts per year (decorative, aria-hidden) */
body.dbi-scope .dbi-author-stats__spark {
	display: block;
	color: var( --teal );
	margin-left: 6px;
}

/* v1 · expertise chips — the branch-chip language of the post cards
   and the post-header eyebrow (post.css), values from the tokens. */
body.dbi-scope .dbi-author-stats__topics ul {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	margin: 0;
	padding: 0;
	list-style: none;
}
body.dbi-scope .dbi-author-stats__topics li {
	margin: 0;
}
body.dbi-scope .dbi-author-stats__topics a {
	display: inline-flex;
	align-items: center;
	padding: 4px 12px;
	border-radius: 999px;
	background: var( --teal-light );
	color: var( --teal-dark );
	font-size: .7rem;
	font-weight: 700;
	letter-spacing: .07em;
	text-transform: uppercase;
	text-decoration: none;
	transition: background var( --t ) var( --ease ), color var( --t );
}
body.dbi-scope .dbi-author-stats__topics a:hover,
body.dbi-scope .dbi-author-stats__topics a:focus-visible {
	background: var( --teal-dark );
	color: #fff;
}

/* v3 · year × month heatmap. One grid: a month-initial header row,
   then [year label + 12 cells] per year. Sequential teal scale derived
   from the token (never duplicated values); fixed thresholds
   1 / 2 / 3–4 / 5+ so the shade means the same on every author page. */
body.dbi-scope .dbi-author-heat {
	display: grid;
	grid-template-columns: auto repeat( 12, 13px );
	gap: 3px 4px;
	justify-content: start;
	align-items: center;
	margin-top: 16px;
}
body.dbi-scope .dbi-author-heat__mlabel {
	font-size: .6rem;
	line-height: 1;
	color: var( --faint );
	text-align: center;
	user-select: none;
}
body.dbi-scope .dbi-author-heat__ylabel {
	font-size: .65rem;
	line-height: 1;
	color: var( --muted );
	padding-right: 6px;
	justify-self: end;
	font-variant-numeric: tabular-nums;
	user-select: none;
}
body.dbi-scope .dbi-author-heat__cell {
	width: 13px;
	height: 13px;
	border-radius: 3px;
	background: var( --surface );
}
body.dbi-scope .dbi-author-heat__cell.is-void { background: transparent; }

/* Profile icons (LinkedIn / X / GitHub / Website) — the CORE Social
   Icons block, fed per-author URLs by dbi_author_profiles_block().
   Only the tint is ours: core's logos-only style keeps per-brand
   colors, which would break the single-accent rule, so the icons rest
   muted and take teal on interaction. Placement rules live with each
   surface (author card below, post box in post.css §5b). */
/* No `ul` in the selector: the placement rules (below and in post.css
   §5b) must out-cascade this shorthand reset. */
body.dbi-scope .wp-block-social-links.dbi-author-profiles {
	margin: 0;
	gap: 14px;
}
/* Core forces the anchor to currentcolor with a (0,4,0) inline rule,
   so the tint goes on the <li>, whose brand color core sets inside a
   zero-specificity :where(). */
body.dbi-scope .dbi-author-profiles .wp-block-social-link {
	color: var( --muted );
	transition: color var( --t ) var( --ease );
}
body.dbi-scope .dbi-author-profiles .wp-block-social-link:hover,
body.dbi-scope .dbi-author-profiles .wp-block-social-link:focus-within {
	color: var( --teal-dark );
	transform: none; /* core scales the item on hover — motion as decoration */
}
body.dbi-scope .dbi-author-profiles .wp-block-social-link a:focus-visible {
	outline: 2px solid var( --teal );
	outline-offset: 2px;
}

/* Author card: icons right under the bio, in the text column (the
   portrait column would catch them by auto-placement). */
body.dbi-scope.author .nv-page-title > .dbi-author-profiles {
	grid-column: 2;
	margin-top: 14px;
}
@media ( min-width: 960px ) {
	body.dbi-scope.author .nv-page-title > .dbi-author-profiles {
		grid-column: 2;
	}
}
body.dbi-scope .dbi-author-heat__cell.is-l1 { background: color-mix( in srgb, var( --teal ) 25%, var( --bg-pure ) ); }
body.dbi-scope .dbi-author-heat__cell.is-l2 { background: color-mix( in srgb, var( --teal ) 50%, var( --bg-pure ) ); }
body.dbi-scope .dbi-author-heat__cell.is-l3 { background: color-mix( in srgb, var( --teal ) 75%, var( --bg-pure ) ); }
body.dbi-scope .dbi-author-heat__cell.is-l4 { background: var( --teal ); }

@media ( max-width: 599px ) {
	body.dbi-scope.author .nv-page-title > .dbi-author-profiles {
		grid-column: auto;
	}
	body.dbi-scope.author .nv-page-title .dbi-author-stats {
		grid-column: auto;
		margin-top: 14px;
	}
	body.dbi-scope .dbi-author-heat {
		grid-template-columns: auto repeat( 12, 11px );
		gap: 3px;
	}
	body.dbi-scope .dbi-author-heat__cell {
		width: 11px;
		height: 11px;
	}
}

/* Breathing room under the (sticky) filter bar */
body.dbi-scope .dbi-filter-bar {
	margin-bottom: 28px;
}

/* Rows stay equal height (Neve's .posts-wrapper stretches every card
   in a row to the tallest one). Natural heights were tried on
   2026-09-02 and rejected: with a ~200px image band the row mixed
   210px and 420px cards and the voids under the short cards read
   worse than a hollow card. Image-less cards fill the height with a
   longer excerpt instead (see the excerpt clamp below). */

/* Card — the visual surface is Neve's .article-content-col: the
   article element itself includes the grid gutter (15px padding offset
   by negative wrapper margins), so bordering the article double-frames
   the card and overhangs small viewports. */
body.dbi-scope article.layout-grid {
	display: flex;
	flex-direction: column;
	min-width: 0;
}
body.dbi-scope article.layout-grid .article-content-col {
	position: relative; /* containing block for the stretched title link below */
	border: 1px solid var( --border );
	display: flex;
	flex-direction: column;
	flex: 1;
	min-width: 0;
	transition:
		border-color var( --t ) var( --ease ),
		transform    var( --t ) var( --ease ),
		box-shadow   var( --t ) var( --ease );
}
body.dbi-scope article.layout-grid:hover .article-content-col {
	border-color: rgba( 0, 170, 160, .30 );
	transform: translateY( -3px );
	box-shadow: 0 12px 36px rgba( 0, 0, 0, .08 ), 0 4px 10px rgba( 0, 0, 0, .04 );
}
body.dbi-scope article.layout-grid .content {
	display: flex;
	flex-direction: column;
	flex: 1;
	min-width: 0;
	gap: 10px;
}
/* Neve spaces .content children with margins; the flex gap replaces
   them (10px, as on the homepage cards) */
body.dbi-scope article.layout-grid .content > * { margin-bottom: 0; }

/* Visual order inside the card: title, excerpt, image, byline last.
   The DOM already comes out title-meta > excerpt > thumbnail from the
   Customizer content ordering (seed-neve-customizer.php); the order
   rules only pull the meta list (emitted together with the title) to
   the bottom. Keep the thumbnail after the title in the DOM: Neve's
   `.article-content-col:has(.img-wrap:first-child){padding-top:0}`
   would otherwise strip the card's top padding. */
body.dbi-scope article.layout-grid .blog-entry-title       { order: 0; }
body.dbi-scope article.layout-grid .excerpt-wrap           { order: 1; }
body.dbi-scope article.layout-grid .nv-post-thumbnail-wrap { order: 2; }
body.dbi-scope article.layout-grid .nv-meta-list           { order: 3; }

/* Featured image: text-first card, the image is an optional
   illustration below the excerpt, not a banner. 37% of published
   posts have one, 6% of those published since 2025 (counted
   2026-09-02), so the image-less card is the baseline design and the
   image a bonus. Neve full-bleeds the wrap with negative side margins
   (dynamic CSS, neve_blog_content_padding); inset it instead, with the
   card's small radius. Aspect ratio (2:1) and object-fit come from the
   Customizer (neve_blog_archive_aspect_ratio). */
body.dbi-scope article.layout-grid .nv-post-thumbnail-wrap {
	margin: 6px 0 6px;
	border-radius: 8px;
	overflow: hidden;
}
body.dbi-scope article.layout-grid .nv-post-thumbnail-wrap img {
	display: block;
	width: 100%;
	transition: transform .6s var( --ease );
}
body.dbi-scope article.layout-grid:hover .nv-post-thumbnail-wrap img {
	transform: scale( 1.03 );
}

/* Title — serif, same scale as homepage cards (front-page.css
   .wp-block-post-title): the largest text in the card, readers scan
   titles. line-height on the h2 itself: the block's line box wins
   over the inline a's value. */
body.dbi-scope article.layout-grid .blog-entry-title {
	margin: 0;
	line-height: 1.25;
}
body.dbi-scope article.layout-grid .blog-entry-title a {
	font-family: var( --f-display );
	font-size: clamp( 1.15rem, 1.8vw, 1.35rem );
	font-weight: 400;
	line-height: 1.25;
	color: var( --text );
	text-decoration: none;
	transition: color var( --t );
}
body.dbi-scope article.layout-grid .blog-entry-title a:hover,
body.dbi-scope article.layout-grid .blog-entry-title a:focus-visible {
	color: var( --teal-dark );
}

/* The whole card opens the post, not just the title (reviewer feedback
   2026-11-03, enhancements #77): the title link is stretched over the
   card with a pseudo-element, so there is still one link per card for
   keyboards and screen readers (the title text). The byline is raised
   above it so the author link keeps its own target; the featured image
   has its own link to the post and stays reachable through the overlay
   only by the stretched link, which lands on the same post. */
body.dbi-scope article.layout-grid .blog-entry-title a::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 1;
}
body.dbi-scope article.layout-grid .nv-meta-list {
	position: relative;
	z-index: 2;
}

/* Excerpt — 3-line clamp */
body.dbi-scope article.layout-grid .excerpt-wrap {
	font-family: var( --f-body );
	font-size: .86rem;
	color: var( --muted );
	line-height: 1.65;
	display: -webkit-box;
	-webkit-line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

/* No image: let the excerpt take the room the image band would have
   used, so the card fills its row with real content instead of empty
   space. 9 lines ≈ the 2:1 image plus its margins; the Customizer
   excerpt length (60 words, seed-neve-customizer.php) supplies enough
   text. Neve puts has-post-thumbnail on the article. */
body.dbi-scope article.layout-grid:not( .has-post-thumbnail ) .excerpt-wrap {
	-webkit-line-clamp: 9;
}

/* Byline — pinned to the card bottom, same stacked layout as the
   homepage cards (.dbi-author-row): avatar left, author name over
   date. Neve's markup is flat (li.author > img + .author-name,
   li.date), so the li.author dissolves via display:contents and the
   grid places the pieces. The &nbsp; Neve emits after the avatar is
   stripped in inc/archive.php — left in place it would become an
   anonymous grid item and open a visible empty row. */
body.dbi-scope article.layout-grid .nv-meta-list {
	margin: auto 0 0;
	padding: 14px 0 0;
	border-top: 1px solid var( --divider );
	display: grid;
	grid-template-columns: auto 1fr;
	align-items: center;
	column-gap: 10px;
	row-gap: 2px;
	list-style: none;
	font-family: var( --f-body );
	line-height: 1.35;
}
body.dbi-scope article.layout-grid .nv-meta-list li {
	margin: 0;
	min-width: 0;
	font-size: .775rem;
}
/* Neve's "/" separators off — the layout separates the pieces */
body.dbi-scope article.layout-grid .nv-meta-list li::after,
body.dbi-scope article.layout-grid .nv-meta-list li::before {
	content: none;
}
body.dbi-scope article.layout-grid .nv-meta-list li.author {
	display: contents;
}
/* The 64px portrait spans both rows, so each row grows to ~32px;
   align-self end/start keeps the name/date pair tight around the
   portrait's midline instead of letting align-items:center spread
   them to their own row centers. */
body.dbi-scope article.layout-grid .nv-meta-list .author img {
	grid-column: 1;
	grid-row: 1 / 3;
	width: 64px;
	height: 64px;
	border-radius: 50%;
	object-fit: cover;
	/* Neve's dynamic CSS nudges the photo (translateY + margin) to
	   align its default 20px gravatar with inline text — in the grid
	   byline that pushes the portrait out of its cell. */
	margin: 0;
	transform: none;
}
body.dbi-scope article.layout-grid .nv-meta-list .author .author-name {
	grid-column: 2;
	grid-row: 1;
	align-self: end;
	font-size: .775rem;
	font-weight: 600;
	color: var( --text );
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
body.dbi-scope article.layout-grid .nv-meta-list .author a {
	color: inherit;
	text-decoration: none;
}
body.dbi-scope article.layout-grid .nv-meta-list .author a:hover,
body.dbi-scope article.layout-grid .nv-meta-list .author a:focus-visible {
	color: var( --teal-dark );
}
body.dbi-scope article.layout-grid .nv-meta-list .date {
	grid-column: 2;
	grid-row: 2;
	align-self: start;
	font-size: .70rem;
	color: var( --faint );
	white-space: nowrap;
}

/* Pagination — centered pill numbers, as on the homepage */
body.dbi-scope .nv-index-posts ul.page-numbers {
	display: flex;
	justify-content: center;
	align-items: center;
	flex-wrap: wrap;
	gap: 6px;
	list-style: none;
	margin: 0;
	padding: 48px 0 64px;
	font-family: var( --f-body );
}
body.dbi-scope .nv-index-posts ul.page-numbers li { margin: 0; }
body.dbi-scope .nv-index-posts .page-numbers:not( ul ) {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 40px;
	height: 40px;
	padding: 0 12px;
	border-radius: 999px;
	border: 1px solid var( --border );
	font-size: .875rem;
	font-weight: 500;
	color: var( --muted );
	text-decoration: none;
	background: var( --bg-pure );
	transition: background 200ms ease, color 200ms ease, border-color 200ms ease;
}
body.dbi-scope .nv-index-posts a.page-numbers:hover {
	background: var( --teal-dark );
	color: #fff;
	border-color: var( --teal-dark );
}
body.dbi-scope .nv-index-posts .page-numbers.current {
	background: var( --teal-dark );
	color: #fff;
	border-color: var( --teal-dark );
}
body.dbi-scope .nv-index-posts .page-numbers.dots {
	border: none;
	background: transparent;
}

/* ════════════════════════════════════════════════
   §5 · Gravity Forms accents (contact form: page
   panel + homepage modal — both carry .dbi-scope).
   Submit is the page's primary action: solid teal,
   matching the topic buttons. Required markers in
   the accent, not alarm red (quiet voice).
   ════════════════════════════════════════════════ */
.dbi-scope .gform_wrapper .gform_button {
	background: var( --teal-dark );
	color: #fff;
	border: 1px solid var( --teal-dark );
	border-radius: 999px; /* Gravity Forms' theme CSS outranks Neve's button radius (enhancements #65) */
	transition: background var( --t ) var( --ease ),
		border-color var( --t ) var( --ease );
}

.dbi-scope .gform_wrapper .gform_button:hover,
.dbi-scope .gform_wrapper .gform_button:focus-visible {
	background: var( --teal-deep );
	border-color: var( --teal-deep );
}

.dbi-scope .gform_wrapper .gfield_required {
	color: var( --teal-dark );
}

/* Gravity Forms' legacy theme pads every field 16px on the right and
   relies on a negative margin the Otter modal and the contact panel do
   not provide, so the fields sit off-centre and the Message box ends
   16px short of the form edge (reviewer feedback 2026-11-03,
   enhancements #69). Full-width fields lose the padding; half-width
   pairs keep an 16px gutter split between them. */
.dbi-scope .gform_wrapper .gform_fields > .gfield {
	padding-right: 0;
}
.dbi-scope .gform_wrapper .gform_fields > .gfield.gf_left_half {
	padding-right: 8px;
}
.dbi-scope .gform_wrapper .gform_fields > .gfield.gf_right_half {
	padding-left: 8px;
}

/* ── WCAG AA: author name link in the single-post byline ──
   Neve paints links with the primary accent (2.7:1 on the site background)
   and this version has no link-color setting to change; scoped to the one
   link that failed the audit (enhancements #26). */
body.dbi-scope.single .nv-meta-list .author-name a {
	color: var( --teal-dark );
}
