/*
 * Post Runtime Engine — frontend styles.
 *
 * Single stylesheet covering hero / body / sidebar / footer plus all four
 * grouping layout variants. Reads --aisb-* design tokens from Promptless
 * WP when active; falls back to defaults documented in
 * docs/AISB_TOKEN_CONTRACT.md when Promptless isn't installed.
 *
 * Mode handling: a small set of intermediate CSS variables (--pre-color-*)
 * are declared on .pre-single and overridden inside .aisb-section--dark
 * ancestors and on body.aisb-neo-brutalist-cards. This keeps mode logic
 * in ONE place — every color reference inside the tree just reads the
 * intermediate variable and inherits the correct mode automatically.
 * The pattern mirrors how Form Runtime Engine consumes the same tokens.
 *
 * No !important. No hardcoded colors. No fixed pixel sizes that should
 * scale with the design system — those use tokens with fallbacks.
 */

/* ----------------------------------------------------------------------- */
/* Mode variables                                                           */
/*                                                                          */
/* Light mode (default). Inside the tree, every reference reads from these  */
/* intermediates rather than the raw --aisb-* tokens. To switch modes, only */
/* these intermediate values change — the rest of the file is mode-blind.   */
/* ----------------------------------------------------------------------- */

.pre-single {
	/*
	 * Light-mode defaults. Each intermediate reads from the
	 * corresponding --aisb-* token emitted by Promptless WP, with a
	 * documented fallback for standalone use (no Promptless active).
	 *
	 * IMPORTANT: these MUST point at --aisb-* tokens, not at
	 * themselves. Self-referencing (e.g. --pre-color-surface:
	 * var(--pre-color-surface)) creates a CSS variable cycle which
	 * resolves to the guaranteed-invalid value, which then makes
	 * `background: var(--pre-color-surface)` invalid and the property
	 * falls back to its initial value (transparent for background,
	 * 0px for border-width). This was the cause of the v0.1.0
	 * "cards have no surface in light mode" bug — caught by a global
	 * find-replace during the Phase 4 token refactor.
	 */
	--pre-color-text: var(--aisb-color-text, #1f2937);
	--pre-color-text-muted: var(--aisb-color-text-muted, #6b7280);
	--pre-color-background: var(--aisb-color-background, #ffffff);
	--pre-color-surface: var(--aisb-color-surface, #f9fafb);
	--pre-color-border: var(--aisb-color-border, #e5e7eb);
	--pre-color-primary: var(--aisb-color-primary, #6366f1);

	/* Smart-link / smart-icon tokens — split section vs surface so brand
	 * elements rendered on the page background and inside card surfaces
	 * can have independently tuned contrast. All four default to brand
	 * primary; Promptless's SmartColorManager (when enabled in global
	 * settings) recomputes them at wp_head priority 11 with WCAG-correct
	 * contrast against each background, which is critical when the user
	 * picks a low-contrast brand primary like a light yellow on white.
	 *
	 * Icons (decorative graphics, cta arrows) and links (interactive) are
	 * separated tokens upstream because their contrast targets differ —
	 * non-text UI is 3:1 per WCAG 1.4.11, body text and links are 4.5:1
	 * per 1.4.3. Promptless's runtime computes both at the appropriate
	 * targets and exposes them as the four `*-{icon,link}` variants below.
	 *
	 * The hover-brightness pair is applied via filter: brightness() on
	 * link hover — 0.75 darkens for light mode, 1.2 brightens for dark
	 * mode (the dark-mode block below). */
	--pre-color-link: var(--aisb-smart-light-section-link, var(--aisb-color-primary, #6366f1));
	--pre-color-surface-link: var(--aisb-smart-light-surface-link, var(--aisb-color-primary, #6366f1));
	--pre-color-icon: var(--aisb-smart-light-icon, var(--aisb-color-primary, #6366f1));
	--pre-color-surface-icon: var(--aisb-smart-light-surface-icon, var(--aisb-color-primary, #6366f1));
	--pre-link-hover-brightness: var(--aisb-smart-light-link-hover-brightness, 0.75);
	--pre-surface-link-hover-brightness: var(--aisb-smart-light-surface-link-hover-brightness, 0.75);

	/* Card chrome — overridden under body.aisb-neo-brutalist-cards. */
	--pre-card-border-width: 1px;
	--pre-card-shadow: none;
}

/*
 * Dark mode. Triggered when the post is rendered inside any ancestor with
 * the .aisb-section--dark class (the convention Promptless WP and FRE both
 * share). The intermediate variables flip to the dark-token chain — Smart
 * tokens preferred (WCAG-correct contrast against the surface), with
 * dark-color tokens as the next fallback, and conservative defaults last.
 *
 * The default card shadow flips to a soft elevation cue. On dark
 * backgrounds, low-contrast surface vs. border isn't enough by itself —
 * a subtle drop shadow makes cards read as elevated planes instead of
 * blending into the background. Neo-brutalist mode overrides this with
 * its hard offset shadow.
 */
.aisb-section--dark .pre-single,
.pre-single--dark {
	--pre-color-text: var(--aisb-color-dark-text, #fafafa);
	--pre-color-text-muted: var(--aisb-smart-dark-surface-muted, var(--aisb-color-dark-text-muted, #9ca3af));
	--pre-color-background: var(--aisb-color-dark-background, #1a1a1a);
	--pre-color-surface: var(--aisb-color-dark-surface, #2a2a2a);
	--pre-color-border: var(--aisb-smart-dark-surface-border, var(--aisb-color-dark-border, #4b5563));
	/* Dark-mode smart tokens flip to brighten-on-hover. Both the link and
	 * icon tokens may resolve to different colors than their light-mode
	 * equivalents when Promptless's runtime computes a contrast-corrected
	 * variant for the user's brand primary against the dark surface. */
	--pre-color-link: var(--aisb-smart-dark-section-link, var(--aisb-color-primary, #6366f1));
	--pre-color-surface-link: var(--aisb-smart-dark-surface-link, var(--aisb-color-primary, #6366f1));
	--pre-color-icon: var(--aisb-smart-dark-icon, var(--aisb-color-primary, #6366f1));
	--pre-color-surface-icon: var(--aisb-smart-dark-surface-icon, var(--aisb-color-primary, #6366f1));
	--pre-link-hover-brightness: var(--aisb-smart-dark-link-hover-brightness, 1.2);
	--pre-surface-link-hover-brightness: var(--aisb-smart-dark-surface-link-hover-brightness, 1.2);
	--pre-card-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

/*
 * Extend the variant background full-width across the wrapping <main>
 * element so the dark/light treatment fills the viewport, not just the
 * 1200px-wide article. Scoped via :has() to wrappers that contain our
 * .pre-single — affects only Post Runtime Engine output, never sibling
 * content that happens to share the .aisb-section--{light|dark} class.
 *
 * Browser support: :has() is supported in Chrome 105+, Safari 15.4+,
 * Firefox 121+ (≥95% global usage as of 2024). On older browsers, the
 * background falls back to applying only on .pre-single (current
 * behavior — content is dark, viewport gutters show the body background).
 */
.aisb-section--dark:has(.pre-single) {
	background: var(--aisb-color-dark-background, #1a1a1a);
	color: var(--aisb-color-dark-text, #fafafa);
}

.aisb-section--light:has(.pre-single) {
	background: var(--aisb-color-background, #ffffff);
	color: var(--aisb-color-text, #1f2937);
}

/*
 * Neo-Brutalist mode. Promptless WP toggles this via two body classes
 * applied as a pair, mapping to the two variants the user picks in
 * Promptless's Global Settings:
 *
 *   • `.aisb-neo-brutalist-cards` alone        → "lifted" variant
 *     (thick border + offset shadow — the classic neo-brutalist treatment)
 *
 *   • `.aisb-neo-brutalist-cards` plus
 *     `.aisb-neo-brutalist-cards-outline`     → "outline" variant
 *     (thick border only, no shadow — the minimal treatment)
 *
 * To stay visually consistent with how Promptless renders its own Features
 * cards (the design reference), PRE matches Promptless's token choices:
 *   - Border color = section border color (--pre-color-border, which
 *     resolves to --aisb-color-border light / --aisb-color-dark-border
 *     in dark mode). NOT --aisb-neo-brutalist-primary-border, which
 *     defaults to black and breaks visual harmony with custom palettes.
 *   - Shadow uses the SAME border color, so the offset shadow reads as
 *     "the card lifted off its own border", not "a foreign black shadow".
 *
 * Dark mode flows through automatically because --pre-color-border
 * already switches to --aisb-color-dark-border under .aisb-section--dark
 * .pre-single in the dark-mode :root block above. No extra dark-mode
 * selectors needed for the neo-brutalist case.
 */
body.aisb-neo-brutalist-cards .pre-single {
	--pre-card-border-width: var(--aisb-neo-border-width, 4px);
	--pre-card-shadow:
		var(--aisb-neo-shadow-offset, 8px)
		var(--aisb-neo-shadow-offset, 8px)
		0
		var(--pre-color-border);
}

/* Outline variant: same thick border, no offset shadow. The shadow var
 * is set to `none` so any descendant card that consumes --pre-card-shadow
 * naturally drops its lift. */
body.aisb-neo-brutalist-cards.aisb-neo-brutalist-cards-outline .pre-single {
	--pre-card-shadow: none;
}

/* Stacking context for cards in lifted variant. Without this, the offset
 * shadow can land behind sibling cards' surfaces in tightly-packed grids
 * (compact-grid, card-grid). Mirrors the same fix in Promptless's
 * neo-brutalist.css. Outline variant doesn't strictly need it, but
 * applying universally keeps the behavior predictable. */
body.aisb-neo-brutalist-cards .pre-grouping__item,
body.aisb-neo-brutalist-cards .pre-footer__list > li {
	position: relative;
	z-index: 1;
}

/* ----------------------------------------------------------------------- */
/* Hero contrast band + full-bleed width (docs/HERO_CONTRAST_DESIGN.md)     */
/* ----------------------------------------------------------------------- */

/*
 * Forced hero themes. These re-declare the --pre-color-* intermediates ON
 * THE HERO ELEMENT ITSELF, which beats anything inherited from .pre-single
 * regardless of page mode — element-own custom-property declarations always
 * win over inherited ones. So a .pre-hero--theme-light inside a dark page
 * renders light, and vice versa, with zero specificity games.
 *
 * ORDERING REQUIREMENT: these blocks must stay AFTER the .aisb-section--dark
 * .pre-single block above. The var() declarations don't need it (per the
 * inheritance rule just described) but the background/color/padding rules do
 * — keep the "forced theme beats page mode" reading order intact.
 *
 * Same rule as everywhere in this file: every intermediate reads an
 * --aisb-* token (never another --pre-*) to avoid the self-reference cycle
 * bug class documented at the top of this file.
 *
 * A themed hero is a BAND: it gains its own background, internal padding,
 * and card radius so it reads as a distinct surface. An `inherit` hero
 * (no theme class) keeps today's backgroundless rendering untouched.
 */
.pre-hero--theme-dark {
	--pre-color-text: var(--aisb-color-dark-text, #fafafa);
	--pre-color-text-muted: var(--aisb-smart-dark-surface-muted, var(--aisb-color-dark-text-muted, #9ca3af));
	--pre-color-background: var(--aisb-color-dark-background, #1a1a1a);
	--pre-color-surface: var(--aisb-color-dark-surface, #2a2a2a);
	--pre-color-border: var(--aisb-smart-dark-surface-border, var(--aisb-color-dark-border, #4b5563));
	--pre-color-link: var(--aisb-smart-dark-section-link, var(--aisb-color-primary, #6366f1));
	--pre-color-surface-link: var(--aisb-smart-dark-surface-link, var(--aisb-color-primary, #6366f1));
	--pre-color-icon: var(--aisb-smart-dark-icon, var(--aisb-color-primary, #6366f1));
	--pre-color-surface-icon: var(--aisb-smart-dark-surface-icon, var(--aisb-color-primary, #6366f1));
	--pre-link-hover-brightness: var(--aisb-smart-dark-link-hover-brightness, 1.2);
	--pre-surface-link-hover-brightness: var(--aisb-smart-dark-surface-link-hover-brightness, 1.2);
	--pre-card-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
	/* Theme interop: the Promptless theme styles headings/links inside
	 * .promptless-content through its OWN intermediates — e.g.
	 * `.promptless-content h1 { color: var(--section-text); }` — which beat
	 * .pre-hero__title on specificity and read variables our --pre-* remap
	 * doesn't touch (found in smoke testing: dark band, near-invisible dark
	 * title). Re-declare the theme's --section-* set here so those theme
	 * rules resolve to the forced hero mode too. Harmless no-ops on
	 * non-Promptless themes (nothing reads them). */
	--section-text: var(--aisb-color-dark-text, #fafafa);
	--section-text-muted: var(--aisb-color-dark-text-muted, #9ca3af);
	--section-bg: var(--aisb-color-dark-background, #1a1a1a);
	--section-surface: var(--aisb-color-dark-surface, #2a2a2a);
	--section-border: var(--aisb-color-dark-border, #4b5563);
	--section-link: var(--aisb-smart-dark-section-link, var(--aisb-color-primary, #6366f1));
	--section-surface-link: var(--aisb-smart-dark-surface-link, var(--aisb-color-primary, #6366f1));
}

.pre-hero--theme-light {
	--pre-color-text: var(--aisb-color-text, #1f2937);
	--pre-color-text-muted: var(--aisb-color-text-muted, #6b7280);
	--pre-color-background: var(--aisb-color-background, #ffffff);
	--pre-color-surface: var(--aisb-color-surface, #f9fafb);
	--pre-color-border: var(--aisb-color-border, #e5e7eb);
	--pre-color-link: var(--aisb-smart-light-section-link, var(--aisb-color-primary, #6366f1));
	--pre-color-surface-link: var(--aisb-smart-light-surface-link, var(--aisb-color-primary, #6366f1));
	--pre-color-icon: var(--aisb-smart-light-icon, var(--aisb-color-primary, #6366f1));
	--pre-color-surface-icon: var(--aisb-smart-light-surface-icon, var(--aisb-color-primary, #6366f1));
	--pre-link-hover-brightness: var(--aisb-smart-light-link-hover-brightness, 0.75);
	--pre-surface-link-hover-brightness: var(--aisb-smart-light-surface-link-hover-brightness, 0.75);
	--pre-card-shadow: none;
	/* Theme interop — see the matching comment in .pre-hero--theme-dark. */
	--section-text: var(--aisb-color-text, #1f2937);
	--section-text-muted: var(--aisb-color-text-muted, #6b7280);
	--section-bg: var(--aisb-color-background, #ffffff);
	--section-surface: var(--aisb-color-surface, #f9fafb);
	--section-border: var(--aisb-color-border, #e5e7eb);
	--section-link: var(--aisb-smart-light-section-link, var(--aisb-color-primary, #6366f1));
	--section-surface-link: var(--aisb-smart-light-surface-link, var(--aisb-color-primary, #6366f1));
}

/* Band chrome shared by both forced themes. */
.pre-hero--theme-dark,
.pre-hero--theme-light {
	background: var(--pre-color-background);
	color: var(--pre-color-text);
	padding: var(--aisb-section-space-lg, 2rem) var(--aisb-section-space-md, 1.5rem);
	border-radius: var(--aisb-section-radius-card, 12px);
}

/*
 * Overlay hero (Phase B, docs/HERO_CONTRAST_DESIGN.md § Phase B).
 * The featured image fills the band; text sits bottom-left on a fixed
 * bottom-weighted scrim. Only rendered when the post has a featured
 * image (the renderer falls back to stacked otherwise).
 *
 * Compound selector (.pre-hero.pre-hero--overlay, 0-2-0) is deliberate:
 * it beats .pre-hero--theme-light (0-1-0) regardless of source order,
 * enforcing the contract rule that hero_theme is IGNORED when text sits
 * on a photograph — the dark token set always applies here.
 *
 * The scrim gradient below is a FUNCTIONAL contrast device with
 * palette-independent literal constants (NOT derived from
 * --aisb-color-dark-background): the WCAG guarantee must hold for any
 * uploaded photo and any user palette. The 0.86 near-black bottom stop
 * keeps #fafafa-class text ≥ AA over any image content.
 */
/* TOKEN REMAP ONLY in this compound-specificity (0-2-0) block: it must
 * beat .pre-hero--theme-light (0-1-0) to enforce "hero_theme is ignored
 * over a photo". Layout properties live in the plain (0-1-0) block
 * below so the shared .pre-hero--full width rules (radius reset +
 * grid-aligning padding-inline) win exactly as they do for the Phase A
 * band — attaching layout to this selector was a smoke-test bug that
 * left the full-bleed overlay with card radius and un-capped content. */
.pre-hero.pre-hero--overlay {
	--pre-color-text: var(--aisb-color-dark-text, #fafafa);
	--pre-color-text-muted: var(--aisb-smart-dark-surface-muted, var(--aisb-color-dark-text-muted, #9ca3af));
	--pre-color-background: var(--aisb-color-dark-background, #1a1a1a);
	--pre-color-surface: var(--aisb-color-dark-surface, #2a2a2a);
	--pre-color-border: var(--aisb-smart-dark-surface-border, var(--aisb-color-dark-border, #4b5563));
	--pre-color-link: var(--aisb-smart-dark-section-link, var(--aisb-color-primary, #6366f1));
	--pre-color-surface-link: var(--aisb-smart-dark-surface-link, var(--aisb-color-primary, #6366f1));
	--pre-color-icon: var(--aisb-smart-dark-icon, var(--aisb-color-primary, #6366f1));
	--pre-color-surface-icon: var(--aisb-smart-dark-surface-icon, var(--aisb-color-primary, #6366f1));
	--pre-link-hover-brightness: var(--aisb-smart-dark-link-hover-brightness, 1.2);
	--pre-surface-link-hover-brightness: var(--aisb-smart-dark-surface-link-hover-brightness, 1.2);
	--pre-card-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
	/* Theme interop — same reasoning as .pre-hero--theme-dark above. */
	--section-text: var(--aisb-color-dark-text, #fafafa);
	--section-text-muted: var(--aisb-color-dark-text-muted, #9ca3af);
	--section-bg: var(--aisb-color-dark-background, #1a1a1a);
	--section-surface: var(--aisb-color-dark-surface, #2a2a2a);
	--section-border: var(--aisb-color-dark-border, #4b5563);
	--section-link: var(--aisb-smart-dark-section-link, var(--aisb-color-primary, #6366f1));
	--section-surface-link: var(--aisb-smart-dark-surface-link, var(--aisb-color-primary, #6366f1));
}

.pre-hero--overlay {
	position: relative;
	display: flex;
	flex-direction: column;
	height: clamp(320px, 52vh, 560px);
	overflow: hidden;
	color: var(--pre-color-text);
	/* Dark fallback so text stays readable before the image paints. */
	background: var(--pre-color-background);
	padding: var(--aisb-section-space-lg, 2rem) var(--aisb-section-space-md, 1.5rem);
	/* Contained mode renders as a card, matching the Phase A contained
	 * band. Full-bleed mode gets radius 0 + grid-aligned padding from
	 * the shared .pre-hero--full rules below (they must keep winning —
	 * do NOT raise this block's specificity). */
	border-radius: var(--aisb-section-radius-card, 12px);
}

@media (max-width: 767px) {
	.pre-hero.pre-hero--overlay {
		height: clamp(260px, 45vh, 420px);
	}
}

.pre-hero--overlay .pre-hero__backdrop {
	position: absolute;
	inset: 0;
	z-index: 0;
}

.pre-hero--overlay .pre-hero__backdrop .pre-hero__image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
}

.pre-hero--overlay.pre-hero--focus-top .pre-hero__backdrop .pre-hero__image {
	object-position: center top;
}

.pre-hero--overlay.pre-hero--focus-bottom .pre-hero__backdrop .pre-hero__image {
	object-position: center bottom;
}

/*
 * Scrim stops are tuned for the WORST CASE: a near-white photo with a
 * tall text block (long title + excerpt + meta reaching ~25% from the
 * band top). At 25% the 0.50 stop yields ≥3:1 (AA large-text — the
 * title/headline are always large text) over pure white; from the title
 * line (~35%) down it clears ≥4.5:1 (AA normal text) for the excerpt
 * and meta rows that always sit lower. The top quarter stays light so
 * the photo reads as a photo. Initial 0.18/0.30/0.86 stops failed this
 * in smoke testing — title measured ~2:1 over a white test image.
 */
.pre-hero--overlay .pre-hero__scrim {
	position: absolute;
	inset: 0;
	z-index: 1;
	pointer-events: none;
	background: linear-gradient(
		180deg,
		rgba(10, 14, 18, 0.25) 0%,
		rgba(10, 14, 18, 0.55) 30%,
		rgba(10, 14, 18, 0.80) 55%,
		rgba(10, 14, 18, 0.92) 100%
	);
}

/* Inner column rides above the scrim; image_overlay badges render at the
 * top in flow (respecting band padding in both widths), and the text
 * block is pushed to the bottom. */
.pre-hero__inner--overlay {
	position: relative;
	z-index: 2;
	display: flex;
	flex-direction: column;
	flex: 1;
	min-height: 0;
}

.pre-hero__text--overlay {
	margin-top: auto;
}

/*
 * Full-bleed hero band. The hero lives INSIDE .pre-single (max-width
 * capped), so bleeding requires the classic viewport breakout:
 * margin-inline pulls the band to the viewport edges; padding-inline
 * re-caps the band's CONTENT at the page grid so hero text stays aligned
 * with .pre-body below it.
 *
 *   padding = max(page gutter, (50vw - maxwidth/2) + page gutter)
 *
 * Narrow viewports (< max-width): the calc() goes negative, max() picks
 * the plain gutter — content sits exactly where .pre-single's own padding
 * would put it. Wide viewports: the calc() term wins and re-centers the
 * content on the 1280px grid.
 *
 * 50vw includes the classic-scrollbar gutter, so the band can overhang by
 * ~15px on the right; overflow-x: clip on the article (below) swallows the
 * sliver without creating a new scroll container (unlike overflow: hidden).
 */
.pre-hero--full {
	margin-inline: calc(50% - 50vw);
	padding-inline: max(
		var(--aisb-section-space-md, 1.5rem),
		calc(50vw - (var(--aisb-section-max-width, 1280px) / 2) + var(--aisb-section-space-md, 1.5rem))
	);
	/* Edge-to-edge bands don't get card radius. */
	border-radius: 0;
}

/* The band should sit flush under the site header — move the article's
 * top padding onto the band itself so no page-background strip shows
 * above a full-bleed hero.
 *
 * Compound selector (.pre-single.pre-single--hero-full) is REQUIRED, not
 * decorative: the container rule `.pre-single { padding: ... }` lives
 * LATER in this file (Container + body layout section), so a plain
 * .pre-single--hero-full { padding-top: 0 } at equal specificity loses
 * the cascade to the shorthand (found in smoke testing: 48px white strip
 * above the band). The extra class wins on specificity regardless of
 * source order. */
.pre-single.pre-single--hero-full {
	padding-top: 0;
}

/*
 * Scrollbar-sliver containment for the breakout. IMPORTANT: this must live
 * on an ancestor that already spans the viewport (body), NOT on
 * .pre-single itself — overflow clipping affects painting, so clipping on
 * the max-width article would clip the band's own breakout back to the
 * article box (found in smoke testing: rect measured full-bleed while the
 * painted band stopped at the content edges).
 *
 * :has() support matches the existing dark-bleed rules above (Chrome 105+,
 * Safari 15.4+, Firefox 121+). On older browsers the rule is skipped: the
 * band still bleeds, and systems with classic scrollbars may show a
 * scrollbar-width overhang — cosmetic, not layout-breaking.
 */
body:has(.pre-single--hero-full) {
	overflow-x: clip;
}

.pre-single--hero-full .pre-hero--full {
	padding-top: var(--aisb-section-space-xl, 3rem);
	padding-bottom: var(--aisb-section-space-lg, 2rem);
}

/* ----------------------------------------------------------------------- */
/* Container + body layout                                                  */
/* ----------------------------------------------------------------------- */

.pre-single {
	max-width: var(--aisb-section-max-width, 1280px);
	margin: 0 auto;
	padding: var(--aisb-section-space-xl, 3rem) var(--aisb-section-space-md, 1.5rem);
	font-family: var(--aisb-section-font-body, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
	color: var(--pre-color-text);
	background: var(--pre-color-background);
}

.pre-body {
	display: block;
	margin-top: var(--aisb-section-space-lg, 2rem);
}

.pre-body--with-sidebar {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--aisb-section-space-lg, 2rem);
}

@media (min-width: 960px) {
	.pre-body--with-sidebar {
		grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
	}
}

.pre-body__main {
	min-width: 0; /* prevent grid blowout */
}

.pre-body__sidebar {
	min-width: 0;
}

@media (min-width: 960px) {
	.pre-body__sidebar {
		/* `align-self: start` keeps the sidebar pinned to the top of its
		   grid cell instead of stretching to match the main column height.
		   v1.0 ships with the sidebar in a static (scroll-with-page) layout —
		   sticky behavior can be added back as a per-CPT or per-template
		   option later if real users want it. */
		align-self: start;
	}
}

/* ----------------------------------------------------------------------- */
/* Hero                                                                     */
/* ----------------------------------------------------------------------- */

.pre-hero {
	margin-bottom: var(--aisb-section-space-lg, 2rem);
}

/* Shared element styles — apply to both layouts. */
.pre-hero__title {
	font-family: var(--aisb-section-font-heading, inherit);
	font-size: clamp(1.75rem, 4vw, 2.5rem);
	font-weight: 700;
	line-height: 1.2;
	margin: 0;
	color: var(--pre-color-text);
}

.pre-hero__excerpt {
	font-size: 1.125rem;
	line-height: 1.5;
	color: var(--pre-color-text-muted);
	margin: var(--aisb-section-space-sm, 1rem) 0 0 0;
}

.pre-hero__text {
	display: flex;
	flex-direction: column;
}

.pre-hero__image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* ----- Layout: stacked (default) -----
 * Featured image as a 16:9 banner above the title block. Capped at 540px
 * tall so banners stay banner-shaped on wide viewports — without the cap,
 * a 1280px container at 16:9 produces a 720px tall image, which dominates
 * the fold instead of acting as a lead-in. Falls back to a clean
 * text-only hero when no image is set.
 */
.pre-hero--stacked .pre-hero__inner {
	display: flex;
	flex-direction: column;
	gap: var(--aisb-section-space-md, 1.5rem);
}

.pre-hero--stacked .pre-hero__media {
	aspect-ratio: 16 / 9;
	max-height: 540px;
	border-radius: var(--aisb-section-radius-image, 8px);
	overflow: hidden;
	background: var(--pre-color-surface);
}

/* ----- Layout: split -----
 * Featured image side-by-side with the title block. Mobile collapses to a
 * single column with the image first, then text below. Desktop renders the
 * 50/50 grid; the image_position class controls which side the image sits on.
 *
 * No-image case: when .pre-hero--has-image is absent, we let .pre-hero__text
 * span the full width naturally (no grid columns to fight against).
 */
.pre-hero--split .pre-hero__inner {
	display: flex;
	flex-direction: column;
	gap: var(--aisb-section-space-md, 1.5rem);
}

.pre-hero--split .pre-hero__media {
	border-radius: var(--aisb-section-radius-image, 8px);
	overflow: hidden;
	background: var(--pre-color-surface);
}

/* Per-aspect sizing for split hero images. The aspect-ratio + max-width
 * pair sets the natural shape of the media slot so featured images crop
 * cleanly to the intended frame. Pick the aspect at CPT registration to
 * match the natural shape of the post's photos:
 *
 *   square (1:1) — headshots, profiles, team pages. Default.
 *   landscape (4:3) — property photos, product shots.
 *   wide (16:9) — cinematic banner imagery.
 *
 * Max dimensions cap each aspect at a sensible ceiling for narrow text
 * columns; the underlying object-fit:cover (set on .pre-hero__image
 * upstream) handles cropping when the source photo doesn't match. */
.pre-hero--split.pre-hero--aspect-square .pre-hero__media {
	aspect-ratio: 1 / 1;
	max-width: 480px;
	max-height: 480px;
}

.pre-hero--split.pre-hero--aspect-landscape .pre-hero__media {
	aspect-ratio: 4 / 3;
	max-width: 560px;
	max-height: 420px;
}

.pre-hero--split.pre-hero--aspect-wide .pre-hero__media {
	aspect-ratio: 16 / 9;
	max-width: 640px;
	max-height: 360px;
}

@media (min-width: 768px) {
	/* 40/60 grid — image takes ~40% of the row, text takes ~60%. This is
	 * the proportion modern profile-page layouts converge on (Cooley LLP,
	 * modern attorney bio sites, team pages). 50/50 makes the image too
	 * dominant relative to the text it supports.
	 *
	 * The image always lives in the 2fr (narrower) column, regardless of
	 * which side it sits on — the image's max-width caps are sized to
	 * fit a 2fr column at typical desktop widths. The image-right rule
	 * below swaps the column ratio (3fr 2fr) so the narrower column
	 * lands on the right; without that swap, image-right would put the
	 * image into the wider 3fr column where its max-width caps would
	 * make it float with empty space inside its cell. */
	.pre-hero--split.pre-hero--has-image .pre-hero__inner {
		display: grid;
		grid-template-columns: 2fr 3fr;
		gap: var(--aisb-section-space-xl, 3rem);
		align-items: center;
	}

	.pre-hero--split.pre-hero--image-right.pre-hero--has-image .pre-hero__inner {
		grid-template-columns: 3fr 2fr;
	}

	/* Anchor the image to the content edge so its outer edge aligns with
	 * the body grid below. On viewports wider than the image's max-width,
	 * the image would otherwise float inside its 2fr column, leaving a
	 * visible gap between the image edge and the content edge.
	 *
	 *   image-left  → image's left edge sits at the content's left edge
	 *   image-right → image's right edge sits at the content's right edge
	 *
	 * Both anchor points match the body content grid below the hero, so
	 * the hero image visually aligns with the rest of the page. */
	.pre-hero--split.pre-hero--has-image .pre-hero__media {
		justify-self: start;
	}

	/* When image-right, swap visual order via CSS — document order stays
	 * image-first so screen readers announce alt before title. */
	.pre-hero--split.pre-hero--image-right .pre-hero__media {
		order: 2;
		justify-self: end;
	}
	.pre-hero--split.pre-hero--image-right .pre-hero__text {
		order: 1;
	}

	/* Slightly tighter title in split — image carries visual weight, so
	 * the title doesn't need to be as oversized as in stacked. */
	.pre-hero--split .pre-hero__title {
		font-size: clamp(1.5rem, 3vw, 2.25rem);
	}
}

/* ----------------------------------------------------------------------- */
/* Main content area (the_content() output)                                 */
/* ----------------------------------------------------------------------- */

.pre-content {
	margin: var(--aisb-section-space-lg, 2rem) 0;
	font-size: 1rem;
	line-height: 1.7;
}

.pre-content > * + * {
	margin-top: 1.25em;
}

.pre-content h2,
.pre-content h3,
.pre-content h4 {
	font-family: var(--aisb-section-font-heading, inherit);
	color: var(--pre-color-text);
	line-height: 1.3;
	margin-top: 1.75em;
	margin-bottom: 0.5em;
}

/*
 * Body-content links sit on the page background = section level. Use the
 * section-link token (contrast-corrected by Promptless's runtime when
 * available) and the section hover-brightness factor. Hover treatment
 * mirrors Promptless's pattern: filter: brightness() darkens on light /
 * brightens on dark, plus a thicker underline for additional cue beyond
 * color. Focus indicator uses currentColor so it inherits whichever link
 * color is in effect — no separate brand-color outline needed.
 */
.pre-content a {
	color: var(--pre-color-link);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 2px;
	transition: filter var(--aisb-section-transition-base, 200ms ease);
}

.pre-content a:hover {
	filter: brightness(var(--pre-link-hover-brightness));
	text-decoration-thickness: 2px;
}

.pre-content a:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 2px;
	border-radius: 2px;
}

.pre-content img {
	max-width: 100%;
	height: auto;
	border-radius: var(--aisb-section-radius-image, 8px);
}

/* ----------------------------------------------------------------------- */
/* Grouping container                                                       */
/* ----------------------------------------------------------------------- */

.pre-grouping {
	margin: var(--aisb-section-space-lg, 2rem) 0;
}

.pre-grouping__label {
	font-family: var(--aisb-section-font-heading, inherit);
	font-size: 1.25rem;
	font-weight: 600;
	margin: 0 0 var(--aisb-section-space-sm, 1rem) 0;
	color: var(--pre-color-text);
}

.pre-grouping__items {
	list-style: none;
	margin: 0;
	padding: 0;
}

.pre-grouping__item {
	color: var(--pre-color-text);
}

/*
 * Stretched-link pattern: linked items have an <a> rendered as a sibling
 * of the media/content divs (not wrapping them) so the flex layout of the
 * <li> stays intact. The <a> is positioned absolute to cover the whole
 * <li>, making the entire card clickable.
 */
.pre-grouping__item--linked {
	position: relative;
	cursor: pointer;
	transition: color var(--aisb-section-transition-base, 200ms ease);
}

.pre-grouping__link-overlay {
	position: absolute;
	inset: 0;
	z-index: 1;
	overflow: hidden;
	text-indent: -9999px; /* visually hide the (empty) link text */
	border-radius: inherit; /* match item's rounded corners */
}

/*
 * Focus indicator for stretched-link cards. Per WCAG 2.4.7 the indicator
 * must be visible against the surrounding UI, and per WCAG 1.4.11 it
 * needs 3:1 contrast against adjacent colors. Using --pre-color-surface-link
 * routes the outline through Promptless's smart-link computation so it
 * stays visible even when the user picks a low-contrast brand primary
 * (e.g. light yellow). All stretched-link cards in PRE are surface-level
 * so the surface variant is the right choice across compact-grid /
 * card-grid / featured-card / horizontal-row.
 */
.pre-grouping__link-overlay:focus-visible {
	outline: 2px solid var(--pre-color-surface-link);
	outline-offset: 2px;
}

/*
 * Linked-item heading hover: shifts heading color from text-color to the
 * link-color so the user sees a visible response to hover. The destination
 * is the smart-link token rather than raw brand primary so contrast stays
 * WCAG-correct in dark mode. Section vs surface depends on the variant:
 *
 *   compact-grid + horizontal-row → items sit on the page background
 *   (no card chrome), so they're SECTION level.
 *   card-grid + featured-card → items have card surfaces, so they're
 *   SURFACE level.
 *
 * Both default to brand primary, but Promptless can compute different
 * contrast-corrected colors for each context at runtime.
 */
.pre-grouping--compact-grid .pre-grouping__item--linked:hover .pre-grouping__heading,
.pre-grouping--horizontal-row .pre-grouping__item--linked:hover .pre-grouping__heading {
	color: var(--pre-color-link);
}

.pre-grouping--card-grid .pre-grouping__item--linked:hover .pre-grouping__heading,
.pre-grouping--featured-card .pre-grouping__item--linked:hover .pre-grouping__heading {
	color: var(--pre-color-surface-link);
}

/*
 * Subtle arrow indicator for stretched-link variants — applied to card-grid
 * and featured-card. Visible at low opacity so the cue is unobtrusive;
 * brightens and slides on hover for feedback. Compact-grid and
 * horizontal-row already convey clickability through the heading
 * hover-color and don't need an additional indicator (the next rule
 * suppresses it for them).
 */
.pre-grouping--card-grid .pre-grouping__item--linked,
.pre-grouping--featured-card .pre-grouping__item--linked {
	padding-bottom: calc(var(--aisb-section-space-md, 1.5rem) + 1rem);
}

.pre-grouping--card-grid .pre-grouping__cta-arrow,
.pre-grouping--featured-card .pre-grouping__cta-arrow {
	position: absolute;
	bottom: var(--aisb-section-space-md, 1.5rem);
	right: var(--aisb-section-space-md, 1.5rem);
	/* Card-grid and featured-card items are surface-level cards. Use the
	 * surface-link token so the arrow matches the eventual hover-state
	 * heading color and inherits any contrast correction Promptless
	 * applies for the surface context. */
	color: var(--pre-color-surface-link);
	font-weight: 600;
	font-size: 1rem;
	line-height: 1;
	opacity: 0.55;
	transition: opacity 200ms ease, transform 200ms ease;
	pointer-events: none;
}

.pre-grouping--card-grid .pre-grouping__item--linked:hover .pre-grouping__cta-arrow,
.pre-grouping--featured-card .pre-grouping__item--linked:hover .pre-grouping__cta-arrow {
	opacity: 1;
	transform: translateX(3px);
}

/* Hide the cta-arrow span on variants that don't use it. compact-grid and
   horizontal-row would otherwise inherit position-absolute defaults that
   look odd. */
.pre-grouping--compact-grid .pre-grouping__cta-arrow,
.pre-grouping--horizontal-row .pre-grouping__cta-arrow {
	display: none;
}

.pre-grouping__heading {
	font-family: var(--aisb-section-font-heading, inherit);
	font-size: 1rem;
	font-weight: 600;
	margin: 0;
	color: inherit;
}

.pre-grouping__supporting {
	color: var(--pre-color-text-muted);
	font-size: 0.9375rem;
	line-height: 1.5;
	margin: 0.25rem 0 0 0;
}

/*
 * Media wrapper — forces flex so the SVG sits without its inline baseline
 * leaving phantom space underneath. Without this, the icon appears slightly
 * higher than the heading even with align-items: center on the parent.
 */
.pre-grouping__media {
	display: flex;
	align-items: center;
	justify-content: flex-start;
}

/*
 * Default grouping icon color = section-level smart icon. compact-grid and
 * horizontal-row use this as-is because their items have no card chrome
 * (icons sit on the page background = section level). Card-grid and
 * featured-card override below to the surface variant since their icons
 * sit on card surfaces.
 *
 * Why this matters: --pre-color-primary is the raw user-chosen brand
 * primary, which can be a low-contrast color (e.g. light yellow on white,
 * giving ~1.4:1). The smart-icon token routes the same color through
 * Promptless's runtime contrast adjustment so it always meets WCAG 1.4.11
 * (3:1 for non-text UI). Without this routing, icons disappear into the
 * background when users pick a brand-friendly low-contrast primary.
 */
/*
 * Icon sizing uses a single source-of-truth custom property,
 * `--pre-icon-size`, so the two icon render paths stay visually
 * identical:
 *
 *   1. Legacy curated icons → <span class="pre-grouping__icon"><svg>…</svg></span>
 *      The inner <svg> fills the span via .pre-grouping__icon svg
 *      { width: 100%; height: 100% }. The span itself is
 *      `--pre-icon-size` square.
 *
 *   2. Iconify codes → <iconify-icon class="pre-grouping__icon" …></iconify-icon>
 *      The web component's shadow-DOM <svg> renders at 1em (the host
 *      element's font-size), NOT at the host's CSS width/height.
 *      Without `font-size` set on the host, an iconify-icon styled
 *      to `width: 2rem; height: 2rem;` STILL paints its inner SVG at
 *      1em = 16px (default font-size), producing a visibly smaller
 *      icon than the legacy path. Discovered 2026-05-16 when
 *      `fluent:chat-20-regular` rendered 16×16 inside a 32×32 wrapper
 *      in the demo's first card-grid item.
 *
 *   Setting `font-size: var(--pre-icon-size)` makes the Iconify SVG
 *   render at the same size as the wrapper, matching the legacy
 *   path. Per-variant overrides set `--pre-icon-size` once and
 *   width/height/font-size all pick it up — no duplicate size
 *   declarations to drift apart when a new variant is added.
 */
.pre-grouping__icon {
	--pre-icon-size: 1.5rem;
	display: inline-flex;
	color: var(--pre-color-icon);
	width: var(--pre-icon-size);
	height: var(--pre-icon-size);
	font-size: var(--pre-icon-size);
	line-height: 1;
}

/* Surface-context override: card-grid and featured-card items have their
 * own card surface, which is a distinct background from the page. Smart
 * tokens compute different corrections per background, so the icon
 * inside a card uses the surface-icon variant. */
.pre-grouping--card-grid .pre-grouping__icon,
.pre-grouping--featured-card .pre-grouping__icon {
	color: var(--pre-color-surface-icon);
}

.pre-grouping__icon svg {
	width: 100%;
	height: 100%;
	display: block; /* belt-and-suspenders against baseline phantom space */
}

.pre-grouping__image {
	display: block;
	width: 100%;
	height: auto;
	border-radius: var(--aisb-section-radius-image, 8px);
}

/*
 * Image-optimizer <picture> wrappers (EWWW picture-WebP, WebP Express, CDN
 * rewriters) insert a <picture> element between the media box and the <img>.
 * <picture> defaults to display:inline and shrink-wraps, so the img's
 * width:100%/height:100% resolve against the wrapped picture instead of the
 * media box — card images rendered as part-width slivers (Harbor & Oak agent
 * page) and split-hero images left a bottom gap whenever the photo's aspect
 * was shorter than the media frame (Harbor & Oak listing page, 2026-07-10;
 * both invisible on hosts without an optimizer).
 *
 * display:contents removes the picture from layout entirely: the <img>
 * participates in the media box exactly as the direct child every PRE image
 * rule was written against — width AND height chains, aspect-ratio frames,
 * and the overlay backdrop all behave identically with or without an
 * optimizer. (An earlier fix used display:block + width:100%, which repaired
 * widths but not fixed-height/aspect frames — superseded by this.)
 * PRE never emits <picture> itself, so this only ever targets optimizer
 * wrappers. Same rule ships in Promptless WP (.aisb-section picture) and the
 * Promptless theme.
 */
.pre-grouping__media picture,
.pre-hero__media picture,
.pre-hero__backdrop picture {
	display: contents;
}

/* ----------------------------------------------------------------------- */
/* Variant: compact-grid                                                    */
/* Icon + heading, multi-column. No supporting text.                        */
/* ----------------------------------------------------------------------- */

.pre-grouping--compact-grid .pre-grouping__items {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
	gap: var(--aisb-section-space-sm, 1rem);
}

.pre-grouping--compact-grid .pre-grouping__item {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	padding: 0.75rem 1rem;
	background: var(--pre-color-surface);
	border: var(--pre-card-border-width) solid var(--pre-color-border);
	border-radius: var(--aisb-section-radius-card, 8px);
	box-shadow: var(--pre-card-shadow);
}

.pre-grouping--compact-grid .pre-grouping__media--icon {
	flex: 0 0 auto;
}

/* Note: compact-grid is icon-only by design. The renderer strips image_id
 * for this variant and falls back to the CPT default_icon when the item
 * has no per-item icon. The .pre-grouping__media--image modifier should
 * never appear inside .pre-grouping--compact-grid — if it does (legacy
 * data, edge case), the bare <img> inherits no width constraint and would
 * sprawl. Authors adding a new image-bearing variant should not extend
 * compact-grid; pick card-grid or featured-card instead. */

.pre-grouping--compact-grid .pre-grouping__heading {
	font-size: 0.9375rem;
	font-weight: 600;
}

.pre-grouping--compact-grid .pre-grouping__content {
	min-width: 0;
}

/* ----------------------------------------------------------------------- */
/* Variant: card-grid                                                       */
/* Icon + heading + supporting text in cards, multi-column.                 */
/* ----------------------------------------------------------------------- */

.pre-grouping--card-grid .pre-grouping__items {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
	gap: var(--aisb-section-space-md, 1.5rem);
}

.pre-grouping--card-grid .pre-grouping__item {
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
	padding: var(--aisb-section-space-md, 1.5rem);
	background: var(--pre-color-surface);
	border: var(--pre-card-border-width) solid var(--pre-color-border);
	border-radius: var(--aisb-section-radius-card, 8px);
	box-shadow: var(--pre-card-shadow);
}

/* Gap on the parent flex column handles spacing between media / heading /
   supporting text — the previous margin-bottom was double-spacing. */

.pre-grouping--card-grid .pre-grouping__media--icon .pre-grouping__icon {
	--pre-icon-size: 2rem;
}

.pre-grouping--card-grid .pre-grouping__image {
	/* Explicit box (not max-height) so every card's image fills the same
	   160px band regardless of the source photo's aspect ratio — max-height
	   left wide/panoramic photos shorter than their neighbors, and the
	   height must not depend on the img's natural size when an optimizer's
	   <picture> wrapper intervenes (see the picture rules above). */
	width: 100%;
	height: 160px;
	object-fit: cover;
}

.pre-grouping--card-grid .pre-grouping__heading {
	font-size: 1.0625rem;
}

/*
 * Card-grid image bleed — alignment with Promptless's global Card Image Style.
 *
 * Promptless's Features / PostGrid default cards bleed the card image flush to
 * the card's top + side edges, and only inset it when the site's global Card
 * Image Style is set to "inset" (which Promptless surfaces as the body class
 * `aisb-card-image-inset`). PRE's card-grid previously ALWAYS inset its image
 * inside the item's padding and ignored that global setting, so it looked out
 * of step with the rest of the cards on the page. These rules bring it in line:
 *
 *   - default            → image bleeds to the card's top + side edges; the
 *                          item's padding still indents the heading / text below.
 *   - body.aisb-card-image-inset → image returns inside the padding with a
 *                          halved radius, matching Promptless's inset treatment.
 *
 * Implementation mirrors Promptless's own approach (margin on the image media),
 * here a NEGATIVE margin equal to the item padding (--aisb-section-space-md) to
 * pull the media out to the card edges, with overflow:hidden on the item
 * clipping the image to the card's rounded corners (same technique featured-card
 * already uses). The featured-card variant already bleeds by design (it is
 * image-prominent, like the image-first cards Promptless intentionally excludes
 * from the inset toggle), so it is left as-is. The sidebar card-grid is a
 * horizontal list with a small 60px thumbnail, not a top image, so bleed does
 * not apply there — it is explicitly reset below.
 */
.pre-grouping--card-grid .pre-grouping__item {
	overflow: hidden; /* clip the bleeding image to the card's rounded corners */
}

.pre-grouping--card-grid .pre-grouping__media--image {
	margin:
		calc(-1 * var(--aisb-section-space-md, 1.5rem))
		calc(-1 * var(--aisb-section-space-md, 1.5rem))
		0;
}

.pre-grouping--card-grid .pre-grouping__media--image .pre-grouping__image {
	border-radius: 0; /* the item's overflow:hidden rounds the corners */
}

/* Global "inset" toggle: return the card-grid image inside the padding with a
 * halved radius, matching Promptless's card-image-inset treatment. */
body.aisb-card-image-inset .pre-grouping--card-grid .pre-grouping__media--image {
	margin: 0;
}

body.aisb-card-image-inset
	.pre-grouping--card-grid
	.pre-grouping__media--image
	.pre-grouping__image {
	border-radius: max(
		0px,
		calc(var(--aisb-section-radius-card, var(--aisb-section-radius-md, 8px)) / 2)
	);
}

/* Sidebar card-grid is a horizontal list with a small thumbnail — never bleed. */
.pre-body__sidebar .pre-grouping--card-grid .pre-grouping__media--image {
	margin: 0;
}

.pre-body__sidebar
	.pre-grouping--card-grid
	.pre-grouping__media--image
	.pre-grouping__image {
	border-radius: var(--aisb-section-radius-image, 8px);
}

/*
 * CTA arrow placement (card-grid). The arrow was absolutely positioned in the
 * bottom-right corner, which collided with the supporting text whenever the
 * text's last line reached the bottom-right (measured: a 16×16px overlap on
 * cards with full-width last lines). Reserving bottom padding alone was
 * fragile — whether it overlapped depended on the text length. Instead, flow
 * the arrow as a bottom-aligned flex item: `margin-top: auto` pushes it to the
 * bottom of the card's flex column (and, because grid cards stretch to equal
 * height, bottom-aligns the arrow consistently across a row), and
 * `align-self: flex-end` right-aligns it — so it always sits on its own row
 * below the text and can never overlap it. The featured-card variant keeps its
 * own absolute placement (its padded content wrapper isolates the text).
 */
.pre-grouping--card-grid .pre-grouping__item--linked {
	/* Arrow now flows at the bottom, so restore the normal bottom padding
	   (the absolute-arrow path reserved an extra ~1rem band). */
	padding-bottom: var(--aisb-section-space-md, 1.5rem);
}

.pre-grouping--card-grid .pre-grouping__cta-arrow {
	position: static;
	align-self: flex-end;
	margin-top: auto;
}

/* ----------------------------------------------------------------------- */
/* Variant: featured-card                                                   */
/* Single item, image-prominent. Image bleeds to the card edges (top, left, */
/* right in stacked layout; left/top/bottom in side-by-side). Content wrap  */
/* carries the padding so text never touches the image directly.            */
/* ----------------------------------------------------------------------- */

.pre-grouping--featured-card .pre-grouping__items {
	display: block;
}

.pre-grouping--featured-card .pre-grouping__item {
	display: grid;
	grid-template-columns: 1fr;
	background: var(--pre-color-surface);
	border: var(--pre-card-border-width) solid var(--pre-color-border);
	border-radius: var(--aisb-section-radius-card, 8px);
	box-shadow: var(--pre-card-shadow);
	overflow: hidden; /* Clip the bleeding image to the card's rounded corners. */
}

@media (min-width: 600px) {
	.pre-grouping--featured-card .pre-grouping__item {
		grid-template-columns: minmax(140px, 220px) 1fr;
		align-items: stretch; /* Image stretches to match content height. */
	}
}

/* Image variant — full bleed, 1:1 aspect ratio.
 *
 * Square is the v1 default because it (a) avoids the image-positioning
 * complexity of multi-aspect support and (b) reads well for the most
 * common featured-card use case (profile cards: agent, instructor, host,
 * speaker). Users with landscape source images are expected to crop
 * before upload — the validator doesn't enforce aspect.
 */
.pre-grouping--featured-card .pre-grouping__media:not(.pre-grouping__media--icon) {
	width: 100%;
	height: 100%;
	min-height: 0;
}

.pre-grouping--featured-card .pre-grouping__image {
	display: block;
	width: 100%;
	height: 100%;
	aspect-ratio: 1 / 1;
	object-fit: cover;
	border-radius: 0; /* Card's overflow:hidden handles the corners. */
	max-height: none;
}

/* Side-by-side at 600px+: image fills its 220px column AND stretches to
 * the card's full height instead of staying square. This keeps the right
 * edge of the image flush with the content column without leaving empty
 * card-background space below it when content is tall. */
@media (min-width: 600px) {
	.pre-grouping--featured-card .pre-grouping__image {
		aspect-ratio: auto;
		height: 100%;
	}
}

/* Icon variant: still padded inside the card (no image to bleed). */
.pre-grouping--featured-card .pre-grouping__media--icon {
	background: transparent;
	min-height: 0;
	padding: var(--aisb-section-space-md, 1.5rem) var(--aisb-section-space-md, 1.5rem) 0;
	overflow: visible;
}

.pre-grouping--featured-card .pre-grouping__media--icon .pre-grouping__icon {
	--pre-icon-size: 2.25rem;
}

/* Content wrapper: carries the card padding now that the item itself has
 * none. Padding is consistent for both image and icon variants. */
.pre-grouping--featured-card .pre-grouping__content {
	padding: var(--aisb-section-space-md, 1.5rem);
}

.pre-grouping--featured-card .pre-grouping__heading {
	font-size: 1.25rem;
	margin-bottom: 0.5rem;
}

/*
 * v1 uses the unified stretched-link + arrow pattern for all linked items
 * including featured-card. The arrow indicator is positioned in the
 * bottom-right of the card by the .pre-grouping__cta-arrow rules below.
 *
 * A per-item CTA button with customizable label is a v1.1 candidate,
 * gated on a new `link_label` field. See docs/ROADMAP.md.
 */

/* ----------------------------------------------------------------------- */
/* Variant: horizontal-row                                                  */
/* Inline chips for at-a-glance specs.                                      */
/* ----------------------------------------------------------------------- */

.pre-grouping--horizontal-row .pre-grouping__items {
	display: flex;
	flex-wrap: wrap;
	gap: var(--aisb-section-space-sm, 1rem);
	align-items: center;
}

.pre-grouping--horizontal-row .pre-grouping__item {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	font-size: 0.9375rem;
	color: var(--pre-color-text);
}

.pre-grouping--horizontal-row .pre-grouping__media--icon .pre-grouping__icon {
	--pre-icon-size: 1.125rem;
}

/* Horizontal-row is icon-only by design (same rationale as compact-grid
 * above). The renderer strips image_id; the variant's tight inline strip
 * doesn't accommodate thumbnails. */

.pre-grouping--horizontal-row .pre-grouping__heading {
	font-size: 0.9375rem;
	font-weight: 500;
}

.pre-grouping--horizontal-row .pre-grouping__item + .pre-grouping__item {
	border-left: 1px solid var(--pre-color-border);
	padding-left: var(--aisb-section-space-sm, 1rem);
}

/* ----------------------------------------------------------------------- */
/* Sidebar adjustments                                                      */
/* When a grouping renders in the sidebar slot, force single-column         */
/* presentation and tighten card-style variants so they fit a narrow column */
/* without overflowing or looking oversized.                                */
/* ----------------------------------------------------------------------- */

.pre-body__sidebar .pre-grouping {
	margin: 0 0 var(--aisb-section-space-md, 1.5rem) 0;
}

.pre-body__sidebar .pre-grouping__label {
	font-size: 1rem;
}

.pre-body__sidebar .pre-grouping--compact-grid .pre-grouping__items,
.pre-body__sidebar .pre-grouping--card-grid .pre-grouping__items {
	grid-template-columns: 1fr;
}

/* Card-grid in sidebar: pivot each item from "media stacked above heading"
   to "media on the left, heading + supporting text on the right". The
   sidebar is too narrow for the full vertical card pattern — horizontal
   layout reads more like a list and uses the available space better. */
.pre-body__sidebar .pre-grouping--card-grid .pre-grouping__item {
	flex-direction: row;
	align-items: center;
	gap: var(--aisb-section-space-sm, 1rem);
	padding: var(--aisb-section-space-sm, 1rem);
}

/* Heading column takes the slack so the arrow pins to the right edge no
   matter the heading length (short headings would otherwise leave the arrow
   floating mid-row). min-width:0 lets long headings wrap instead of forcing
   overflow. */
.pre-body__sidebar .pre-grouping--card-grid .pre-grouping__content {
	flex: 1 1 auto;
	min-width: 0;
}

/* The base card-grid arrow rules (position:static; align-self:flex-end;
   margin-top:auto) place the arrow at the bottom-right of the VERTICAL column
   card. In the sidebar's horizontal row those drop the arrow to the bottom of
   the row, out of line with the vertically-centered icon + heading (measured:
   icon mid 29px, heading 35px, arrow 45px on a 70px row). Re-center the arrow
   on the row's cross axis and pin it to the right edge. */
.pre-body__sidebar .pre-grouping--card-grid .pre-grouping__cta-arrow {
	align-self: center;
	margin-top: 0;
	margin-left: auto;
	flex: 0 0 auto;
}

.pre-body__sidebar .pre-grouping--card-grid .pre-grouping__media--icon .pre-grouping__icon {
	--pre-icon-size: 1.5rem;
}

.pre-body__sidebar .pre-grouping--card-grid .pre-grouping__image {
	width: 60px;
	height: 60px;
	max-height: none;
	object-fit: cover;
}

/*
 * Media is a fixed-size flex item, never a shrink target.
 *
 * In a horizontal item (.pre-body__sidebar card-grid and horizontal-row both
 * set flex-direction: row) the siblings are: media | content | arrow.
 * .pre-grouping__content is `flex: 1 1 auto` (greedy) and the arrow is
 * explicitly protected with `flex: 0 0 auto` — but the media wrapper was left
 * at the flex default `0 1 auto`. So when the content's natural width pushed
 * the row past its bounds, the browser shrank the only unprotected item: the
 * thumbnail. Measured on Sharp /publications/: a 340x226 image told
 * `width: 60px` above rendered 15px wide — a vertical sliver.
 *
 * BOTH levels need protecting. .pre-grouping__media is itself display:flex, so
 * the <img> is ALSO a flex item and its `width: 60px` was being shrunk out from
 * under it by its own parent's squeeze.
 *
 * This is why the bug only appears with IMAGES, never icons: at
 * --pre-icon-size: 1.5rem an icon is small enough that the greedy content
 * column absorbs all the slack and nothing ever overflows, so no shrink
 * pressure reaches the media. A 60px thumbnail crosses that threshold.
 *
 * featured-card is deliberately excluded — its media is `width: 100%` (a
 * full-bleed column, not a row thumbnail) and has no shrink exposure.
 */
.pre-body__sidebar .pre-grouping--card-grid .pre-grouping__media,
.pre-grouping--horizontal-row .pre-grouping__media {
	flex: 0 0 auto;
}

/* Descendant, not child: .pre-grouping__media picture is `display: contents`
   (see ~line 990), so the <img> may not be a direct child. */
.pre-grouping__media .pre-grouping__image {
	flex-shrink: 0;
}

.pre-body__sidebar .pre-grouping--card-grid .pre-grouping__heading {
	font-size: 0.9375rem;
}

/* Featured-card in sidebar: forced single-column (override the desktop
   side-by-side rule). Image stays 1:1 and bleeds to top/left/right; icon
   variant keeps its small padded treatment. */
.pre-body__sidebar .pre-grouping--featured-card .pre-grouping__item {
	grid-template-columns: 1fr;
}

/* Sidebar: keep image square (override the side-by-side aspect-ratio:auto). */
.pre-body__sidebar .pre-grouping--featured-card .pre-grouping__image {
	aspect-ratio: 1 / 1;
	height: auto;
}

.pre-body__sidebar .pre-grouping--featured-card .pre-grouping__media--icon {
	padding: var(--aisb-section-space-md, 1.5rem) var(--aisb-section-space-md, 1.5rem) 0;
	display: flex;
	justify-content: flex-start;
}

.pre-body__sidebar .pre-grouping--featured-card .pre-grouping__media--icon .pre-grouping__icon {
	--pre-icon-size: 1.75rem;
}

.pre-body__sidebar .pre-grouping--featured-card .pre-grouping__content {
	padding: var(--aisb-section-space-md, 1.5rem);
}

.pre-body__sidebar .pre-grouping--featured-card .pre-grouping__heading {
	font-size: 1.0625rem;
}

.pre-body__sidebar .pre-grouping--horizontal-row .pre-grouping__items {
	flex-direction: column;
	align-items: flex-start;
}

.pre-body__sidebar .pre-grouping--horizontal-row .pre-grouping__item + .pre-grouping__item {
	border-left: none;
	padding-left: 0;
	border-top: 1px solid var(--pre-color-border);
	padding-top: var(--aisb-section-space-sm, 1rem);
}

/* ----------------------------------------------------------------------- */
/* Related-posts footer                                                     */
/* ----------------------------------------------------------------------- */

.pre-footer {
	margin-top: var(--aisb-section-space-xl, 3rem);
	padding-top: var(--aisb-section-space-lg, 2rem);
	border-top: 1px solid var(--pre-color-border);
}

.pre-footer__heading {
	font-family: var(--aisb-section-font-heading, inherit);
	font-size: 1.25rem;
	font-weight: 600;
	margin: 0 0 var(--aisb-section-space-md, 1.5rem) 0;
}

.pre-footer__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
	gap: var(--aisb-section-space-md, 1.5rem);
}

/*
 * Footer link + title color rules use compound selectors to win against
 * Promptless WP's `.aisb-section--dark a` and Promptless theme's global
 * heading colors, both of which have higher specificity than a single
 * class would. We deliberately avoid !important — extra specificity is
 * the right tool here.
 */
.pre-single .pre-footer__link,
.pre-single .pre-footer__link:link,
.pre-single .pre-footer__link:visited {
	color: var(--pre-color-text);
	text-decoration: none;
	display: block;
}

.pre-single .pre-footer__title,
.pre-single .pre-footer__heading {
	color: var(--pre-color-text);
}

/*
 * Footer "related posts" items are presented as cards (each list item
 * has its own surface), so their hover destination uses the surface-link
 * token. Specificity is .pre-single .pre-footer__link:hover ... to win
 * against Promptless WP's `.aisb-section--dark a` styles in the same
 * way as the default footer link rules above.
 */
.pre-single .pre-footer__link:hover .pre-footer__title {
	color: var(--pre-color-surface-link);
}

.pre-footer__media {
	border-radius: var(--aisb-section-radius-image, 8px);
	overflow: hidden;
	margin-bottom: 0.75rem;
}

.pre-footer__image {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 10;
	object-fit: cover;
}

.pre-footer__title {
	font-size: 1rem;
	font-weight: 600;
	margin: 0;
	transition: var(--aisb-section-transition-base, 200ms ease);
}

/* =========================================================================
   Gallery variant (docs/GALLERY_VARIANT_DESIGN.md)
   -------------------------------------------------------------------------
   Responsive photo grid + lightbox for `variant: gallery` groupings.
   Contract §3: fixed 3-up desktop / 2-up below 1024px, 16:9 tiles,
   figure/figcaption markup, tiles are real <button>s.
   Contract §9: aspect-ratio reserves space (zero CLS).
   Contract §10: WCAG 2.2 AA — visible focus, ≥24px targets, reduced motion.
   All tokens below are already documented in docs/AISB_TOKEN_CONTRACT.md
   (no new token consumption).
   ========================================================================= */

.pre-gallery__grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: var(--aisb-section-space-sm, 16px);
}

@media (min-width: 1024px) {
	.pre-gallery__grid {
		grid-template-columns: repeat(3, 1fr);
	}
}

.pre-gallery__tile {
	margin: 0; /* figure reset */
	min-width: 0;
}

/* The tile button is the single interaction: full-bleed image, zero
   button chrome. aspect-ratio reserves layout space before the lazy
   image arrives — CLS stays at 0 by construction. */
.pre-gallery__tile-button {
	display: block;
	width: 100%;
	aspect-ratio: 16 / 9;
	padding: 0;
	border: none;
	background: none;
	cursor: zoom-in;
	border-radius: var(--aisb-section-radius-image, 8px);
	overflow: hidden;
}

.pre-gallery__tile-image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform var(--aisb-section-transition-base, 200ms ease);
}

.pre-gallery__tile-button:hover .pre-gallery__tile-image {
	transform: scale(1.03);
}

/* Visible focus indicator (WCAG 2.4.11/2.4.13-friendly): 2px ring offset
   outside the tile, using the smart surface-link token like the other
   grouping focus styles. */
.pre-gallery__tile-button:focus-visible {
	outline: 2px solid var(--pre-color-surface-link, var(--aisb-color-primary, #6366f1));
	outline-offset: 2px;
}

/* Definition-level tile crop (gallery_image_aspect — validator enum,
   shared with archive_image_aspect and the AISB section vocabulary:
   16:9 wide / 4:3 standard / 1:1 square / 4:5 portrait). 16:9 is the
   base rule above; the others override per grid. Tiles only — the
   lightbox always shows the full uncropped image. */
.pre-gallery--aspect-4-3 .pre-gallery__tile-button {
	aspect-ratio: 4 / 3;
}

.pre-gallery--aspect-1-1 .pre-gallery__tile-button {
	aspect-ratio: 1 / 1;
}

.pre-gallery--aspect-4-5 .pre-gallery__tile-button {
	aspect-ratio: 4 / 5;
}

.pre-gallery__caption {
	margin-top: 0.5rem;
	font-size: 0.875rem;
	color: var(--pre-color-text-muted, var(--aisb-color-text-muted, #6b7280));
	font-family: var(--aisb-section-font-body, inherit);
}

/* Sidebar position: single-column thumbnails (contract §3). */
.pre-sidebar .pre-gallery__grid {
	grid-template-columns: 1fr;
}

/* -------------------------------------------------------------------------
   Lightbox (rendered by assets/js/pre-lightbox.js into <body>)
   WAI-ARIA APG dialog styling: fixed overlay, centered stage, visible
   prev/next/close controls (≥44px targets — comfortably past the 24px
   WCAG 2.2 §2.5.8 minimum), caption + counter strip.
   ------------------------------------------------------------------------- */

.pre-lightbox {
	position: fixed;
	inset: 0;
	z-index: 100000;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	background: rgba(0, 0, 0, 0.92);
	opacity: 1;
	transition: opacity var(--aisb-section-transition-base, 200ms ease);
}

.pre-lightbox[hidden] {
	display: none;
}

.pre-lightbox__stage {
	max-width: min(1200px, 92vw);
	max-height: 78vh;
	display: flex;
	align-items: center;
	justify-content: center;
}

.pre-lightbox__image {
	max-width: 100%;
	max-height: 78vh;
	width: auto;
	height: auto;
	display: block;
	border-radius: var(--aisb-section-radius-image, 8px);
}

.pre-lightbox__meta {
	margin-top: 0.75rem;
	max-width: min(1200px, 92vw);
	text-align: center;
	color: #ffffff;
	font-family: var(--aisb-section-font-body, inherit);
}

.pre-lightbox__caption {
	/* Unified with the AISB gallery lightbox caption (2026-08-04):
	   same type size, opacity, and measure across both Promptless
	   lightboxes. AISB design tokens with standalone fallbacks. */
	font-size: var(--aisb-section-text-base, 1rem);
	opacity: 0.9;
	max-width: 600px;
	margin: 0 auto 0.25rem;
}

.pre-lightbox__counter {
	/* Unified with the AISB gallery lightbox counter pill (2026-08-04):
	   dark scrim at 0.65 with full-opacity white text holds >= 4.5:1
	   over any backdrop (the old bare text at 0.75 opacity relied on
	   the overlay being near-black). Same tokens, same shape, one
	   lightbox language across the stack. */
	display: inline-block;
	font-size: var(--aisb-section-text-sm, 0.875rem);
	color: #ffffff;
	background: rgba(0, 0, 0, 0.65);
	padding: var(--aisb-section-space-xs, 6px) var(--aisb-section-space-md, 16px);
	border-radius: var(--aisb-section-radius-full, 999px);
	margin: 0.5rem 0 0;
}

/* Controls: real buttons, high-contrast on the dark overlay, 44px hit
   areas. Drag/swipe is an enhancement — these buttons are the baseline
   navigation path (WCAG 2.2 §2.5.7 dragging alternatives). */
.pre-lightbox__control {
	position: absolute;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	padding: 0;
	border: none;
	border-radius: 50%;
	background: rgba(255, 255, 255, 0.12);
	color: #ffffff;
	cursor: pointer;
	transition: background var(--aisb-section-transition-base, 200ms ease);
}

.pre-lightbox__control:hover {
	background: rgba(255, 255, 255, 0.28);
}

.pre-lightbox__control:focus-visible {
	outline: 2px solid #ffffff;
	outline-offset: 2px;
}

.pre-lightbox__control svg {
	width: 22px;
	height: 22px;
	fill: none;
	stroke: currentColor;
	stroke-width: 2;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.pre-lightbox__close {
	top: 16px;
	right: 16px;
}

.pre-lightbox__prev {
	left: 16px;
	top: 50%;
	transform: translateY(-50%);
}

.pre-lightbox__next {
	right: 16px;
	top: 50%;
	transform: translateY(-50%);
}

@media (max-width: 640px) {
	.pre-lightbox__prev {
		left: 8px;
	}

	.pre-lightbox__next {
		right: 8px;
	}
}

/* Reduced motion: no fades, no tile zoom (contract §10). */
@media (prefers-reduced-motion: reduce) {
	.pre-lightbox {
		transition: none;
	}

	.pre-gallery__tile-image,
	.pre-gallery__tile-button:hover .pre-gallery__tile-image {
		transition: none;
		transform: none;
	}
}
