/*
 * Replaces Isotope-driven absolute positioning on the portfolio grid
 * (templates/portfolio-grid.php) and gallery grid (templates/gallery-grid-
 * masonry.php) with plain CSS Grid. Isotope computed column width as a naive
 * containerWidth/columnCount with no gutter subtracted, then set inline
 * position/left/top via JS after imagesLoaded fired - fighting the theme's
 * own percentage-width-with-gutter CSS for the same elements, which is what
 * produced the misaligned/overlapping rows. The parent's own filter-tab
 * feature that would justify keeping Isotope has no corresponding UI markup
 * in either template, so dropping it costs no live functionality.
 *
 * Both templates share the same two-level markup: an outer .element (the
 * actual grid item) containing an inner .one_third/.one_half/.one_fourth div
 * that wraps the image link. portfolio-grid.php's .element additionally has
 * a sibling .portfolio_desc caption block in normal flow below the image -
 * the fixed-aspect "image frame" treatment below therefore targets the INNER
 * div (.pm-uniform-grid__frame), not .element itself, so that caption is
 * never clipped. .element only gets the float/width/margin reset.
 */
/*
 * REAL root cause of the zero-visible-height regression, confirmed via
 * getComputedStyle(), not the two theories guessed at below without browser
 * access. Neither theory was it: .pm-uniform-grid__cell/__frame both had
 * correct real computed height (416px/342px) all along. The actual container
 * - #portfolio_filter_wrapper, .portfolio_filter_wrapper in the parent's
 * screen.css:8942 - starts every instance of this element at
 * `height:0; visibility:hidden; opacity:0` as a "reveal after JS finishes"
 * pattern: the OLD Isotope integration script (no longer enqueued, replaced
 * by this stylesheet) was responsible for BOTH adding a `.visible` class
 * (screen.css:8966, resets visibility/opacity but NOT height) AND setting
 * an explicit inline pixel height via JS after computing its masonry
 * layout. Removing that script correctly removed the layout bug it caused,
 * but also silently removed the only thing that ever un-hid this element -
 * nothing has set `.visible` or a real height since. Since this grid is
 * pure CSS Grid now with no JS-computed layout to wait for, the fix is to
 * stop participating in that reveal pattern entirely rather than trying to
 * re-trigger it.
 */
/* gap is a fixed px value, not the original 2% - percentage gaps on a
 * display:grid container with height:auto create a circular sizing
 * dependency for row-gap specifically (row-gap % resolves against the
 * container's own height, which is itself derived from its rows+gaps).
 * The container's own auto-height calculation ends up excluding the
 * row-gaps entirely while the actual rendered rows still space themselves
 * out by the real gap value, so every row after the first pushes the
 * container's reported bottom edge further out of sync with its own last
 * row's real position - confirmed live at the single-column (narrow-
 * viewport) breakpoint, where this is large enough in absolute pixels to
 * visibly overlap whatever content follows the grid on the page. Fixed
 * units have no such ambiguity. */
.pm-uniform-grid {
	display: grid;
	gap: 24px;
	height: auto !important;
	visibility: visible !important;
	opacity: 1 !important;
}
.pm-uniform-grid.two_cols {
	grid-template-columns: repeat(2, 1fr);
}
.pm-uniform-grid.three_cols {
	grid-template-columns: repeat(3, 1fr);
}
.pm-uniform-grid.four_cols {
	grid-template-columns: repeat(4, 1fr);
}

/* Ultra-wide tiers: the legacy Isotope-based system this replaced added
 * columns at 1430/1720/2200px for very large displays (confirmed directly
 * against the parent theme's screen.css: .three_cols.gallery.fullwidth goes
 * 3->4->5->6 columns at exactly those three breakpoints, screen.css:7343-
 * 7365; a second, independent legacy grid, #photo_wall_wrapper, corroborates
 * the same +1-column-per-tier step across its own two breakpoints,
 * screen.css:8465-8488). This grid never gained an equivalent, so tiles
 * stretch unbounded past four_cols' fixed 4 columns on any display wider
 * than a normal desktop - FULLSTACK-AUDIT.md §3. Matching the confirmed
 * three_cols step exactly, extrapolated to two_cols/four_cols at the same
 * three breakpoints for consistency (only three_cols could be checked
 * directly against the legacy CSS). */
@media (min-width: 1430px) {
	.pm-uniform-grid.two_cols { grid-template-columns: repeat(3, 1fr); }
	.pm-uniform-grid.three_cols { grid-template-columns: repeat(4, 1fr); }
	.pm-uniform-grid.four_cols { grid-template-columns: repeat(5, 1fr); }
}
@media (min-width: 1720px) {
	.pm-uniform-grid.two_cols { grid-template-columns: repeat(4, 1fr); }
	.pm-uniform-grid.three_cols { grid-template-columns: repeat(5, 1fr); }
	.pm-uniform-grid.four_cols { grid-template-columns: repeat(6, 1fr); }
}
@media (min-width: 2200px) {
	.pm-uniform-grid.two_cols { grid-template-columns: repeat(5, 1fr); }
	.pm-uniform-grid.three_cols { grid-template-columns: repeat(6, 1fr); }
	.pm-uniform-grid.four_cols { grid-template-columns: repeat(7, 1fr); }
}

/* Grid item: strip the old float/percentage-width system. Matches the exact
   parent selector combination it overrides plus our own class, so it wins
   on specificity without !important. */
.two_cols.gallery .element.pm-uniform-grid__cell,
.three_cols.gallery .element.pm-uniform-grid__cell,
.four_cols.gallery .element.pm-uniform-grid__cell {
	width: auto;
	margin: 0;
	float: none;
	min-width: 0;
}

/* Defensive fix for the zero-visible-height regression (MISSION-AUDIT.md,
 * "highest-priority open item"). Two independent theories exist for the
 * root cause and this covers both without needing to confirm which is
 * live, since either way the fix is "force this element back into normal
 * flow": (1) the parent's screen.css still applies legacy float/position to
 * .pm-uniform-grid__frame via its own .one_third/.classic classes (still
 * present alongside it), and a bare single-class rule on .pm-uniform-grid__
 * frame isn't guaranteed to beat that in specificity; (2) Isotope's JS
 * (loaded via the parent's templates/script-gallery-grid.php, never fully
 * removed, only visually superseded by this file) may still be setting an
 * inline position:absolute on .element/.pm-uniform-grid__cell itself - CSS
 * Grid excludes absolutely-positioned items from track sizing entirely,
 * which collapses the row to zero height. Inline styles can only be beaten
 * by !important, and the rule above never declares `position` at all, so
 * it wouldn't contest an inline position even if theory 2 is real. Both
 * rules below are chained through classes this stylesheet owns (not the
 * parent's) so they win regardless of the parent's exact selector, and
 * !important is deliberate insurance since none of this can be visually
 * re-verified from this machine before shipping - safe to force
 * unconditionally either way, since the old float/absolute positioning
 * system is being deliberately and fully replaced here, not coexisted with.
 */
.pm-uniform-grid .pm-uniform-grid__cell {
	position: static !important;
}
.pm-uniform-grid .pm-uniform-grid__cell .pm-uniform-grid__frame {
	position: relative !important;
	float: none !important;
	display: block !important;
	width: auto !important;
}

/* Image frame: the fixed-aspect, cropped box. */
.pm-uniform-grid__frame {
	position: relative;
	aspect-ratio: 1 / 1;
	overflow: hidden;
}
/*
 * This frame div also always carries the parent's generic column class
 * ($column_class in
 * portfolio-grid.php - "one_third"/"one_fourth"/etc, needed for the parent's
 * own column-width system elsewhere), and the parent's screen.css has
 * `.one_third img, .one_fourth img, ... { height: auto }` at the exact same
 * specificity (one class + one type selector) as a bare `.pm-uniform-grid__
 * frame img`. Tied specificity resolves by cascade order, and the parent's
 * rule was landing later - it was silently winning, so `height:100%` above
 * never actually reached the <img>, and every horizontal/landscape photo
 * sized itself by its own real aspect ratio (confirmed: a 705x529 image's
 * computed height matched 529/705 of the frame's width almost exactly, not
 * the frame's own height) instead of being cropped to fill the square frame
 * - vertical/portrait photos happened to still look filled because their
 * native ratio already exceeds the square, not because the rule was working.
 * Fixed the normal way (chain the parent class so this selector has strictly
 * higher specificity), not with !important, matching how the selector two
 * rules above this one already solved the identical class of problem.
 */
.pm-uniform-grid .pm-uniform-grid__frame img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* Resting depth on every tile - a subtle two-layer shadow (tight contact
 * shadow + soft ambient falloff) that reads as "lifted" without a heavy
 * single-value shadow. No will-change/translateZ(0): category pages can
 * have 50-80 tiles, and forcing that many always-on compositing layers
 * would hurt scroll/paint on exactly the lower-end mobile devices most
 * likely to visit a photography portfolio. overflow:hidden above only
 * clips children, not this element's own box-shadow, so it renders fine
 * alongside the existing rule. */
.pm-uniform-grid__frame {
	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.10), 0 10px 24px -12px rgba(0, 0, 0, 0.28);
	transition: box-shadow var(--duration-base, 0.35s) ease;
}
/*
 * Hover shadow is intentionally tuned lighter than a naive 2-2.5x jump over
 * the resting shadow (previously 0 6px 14px/0.16 + 0 20px 40px -14px/0.38) -
 * a more proportionate lift instead of a dramatic jump; the resting shadow
 * itself needed no change.
 *
 * A second reported symptom - the hover shadow "visually disappears on
 * vertical images specifically" - is NOT addressed by this tuning pass, and
 * on closer reading probably isn't explained by the portfolio-thumbnail
 * crop bug (abd70f1) either, despite that being the working theory when
 * this was first logged: that bug letterboxed HORIZONTAL photos (their
 * <img> rendered at native height:auto, shorter than the square frame);
 * vertical photos were unaffected because their native height already
 * exceeded the square even pre-fix (MISSION-AUDIT.md says as much:
 * "vertical images don't have this issue"). Box-shadow is painted from
 * .pm-uniform-grid__frame's own box either way, which was already a
 * correctly-sized square regardless of this bug for every orientation - so
 * a fix that only changed how the <img> fills that box shouldn't have
 * touched the shadow specifically for vertical tiles. Not able to reproduce
 * or find a code-level cause for the disappearing-on-vertical report from
 * static reading alone; needs either a live-devtools instance or a
 * screenshot of the specific hovered tile to diagnose further rather than a
 * guessed fix.
 */
.pm-uniform-grid__frame:hover,
.pm-uniform-grid__frame:focus-within,
.pm-uniform-grid__frame:active {
	box-shadow: 0 4px 9px rgba(0, 0, 0, 0.13), 0 15px 30px -13px rgba(0, 0, 0, 0.33);
}

/* Homepage grid only (templates/portfolio-grid.php's .classic frames):
 * retire the dated scale(1.3)+overlay+plus-icon treatment - removed, not
 * restyled, since its exact current CSS lives in the unreadable parent
 * screen.css - and replace with a gradient scrim + category title, using
 * the data-title attribute the template now provides. gallery_type frames
 * (category-page masonry, templates/gallery-grid-masonry.php) are untouched:
 * no per-tile title to show, no icon overlay to retire.
 *
 * No scoped rule is needed here: style.css's ported Customizer "Additional
 * CSS" already has a sitewide, !important-flagged
 * .portfolio_classic_icon_wrapper { display: none !important; } rule that
 * fully covers this case - a duplicate here would change nothing rendered. */

.pm-uniform-grid__frame.classic img {
	transition: transform 0.6s var(--ease-signature, cubic-bezier(0.22, 1, 0.36, 1));
}
.pm-uniform-grid__frame.classic:hover img,
.pm-uniform-grid__frame.classic:focus-within img,
.pm-uniform-grid__frame.classic:active img {
	transform: scale(1.06);
}

.pm-uniform-grid__frame.classic::before {
	content: '';
	position: absolute;
	inset: auto 0 0 0;
	height: 55%;
	background: linear-gradient(to top, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0) 100%);
	opacity: 0;
	transition: opacity var(--duration-base, 0.35s) ease;
	pointer-events: none;
}
.pm-uniform-grid__frame.classic::after {
	content: attr(data-title);
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	padding: 0 20px 20px;
	color: #fff;
	font-size: 18px;
	text-align: center;
	opacity: 0;
	transform: translateY(10px);
	transition: opacity var(--duration-base, 0.35s) ease, transform var(--duration-base, 0.35s) ease;
	pointer-events: none;
}
.pm-uniform-grid__frame.classic:hover::before,
.pm-uniform-grid__frame.classic:focus-within::before,
.pm-uniform-grid__frame.classic:active::before,
.pm-uniform-grid__frame.classic:hover::after,
.pm-uniform-grid__frame.classic:focus-within::after,
.pm-uniform-grid__frame.classic:active::after {
	opacity: 1;
}
.pm-uniform-grid__frame.classic:hover::after,
.pm-uniform-grid__frame.classic:focus-within::after,
.pm-uniform-grid__frame.classic:active::after {
	transform: translateY(0);
}

/* Touch: this is real information (which category is this?), not just
   flourish - show it always rather than gating it behind a hover concept
   that doesn't exist on touch. */
@media (hover: none) {
	.pm-uniform-grid__frame.classic::before,
	.pm-uniform-grid__frame.classic::after {
		opacity: 1;
		transform: none;
	}
}

@media (max-width: 480px) {
	.pm-uniform-grid__frame.classic::after {
		font-size: 15px;
		padding: 0 12px 14px;
	}
}

/* ---------------------------------------------------------------------------
 * Homepage editorial scale (2026-08-22 homepage pass, from the fresh-visitor
 * review: "~120px squares on a photography site is like a gallery hanging
 * 4x6 prints"). Scoped to WordPress's own `.home` body class so every other
 * page's grid keeps the denser 4/5/6-column tiers above. Three big tiles per
 * row in a widened container, and the tile titles visible at REST, not just
 * on hover - at rest the grid was an anonymous wall of squares.
 *
 * Every selector chains >= 3 classes so it beats both the 2-class base
 * column rules and the ultra-wide @media tiers above on specificity alone
 * (media queries add no specificity), same no-!important convention as the
 * rest of this file.
 */
.home .ppb_portfolio .standard_wrapper {
	width: 92%;
	max-width: 1280px;
}
.home .ppb_portfolio .pm-uniform-grid.two_cols,
.home .ppb_portfolio .pm-uniform-grid.three_cols,
.home .ppb_portfolio .pm-uniform-grid.four_cols {
	grid-template-columns: repeat(3, 1fr);
}
@media (max-width: 767px) {
	.home .ppb_portfolio .pm-uniform-grid.two_cols,
	.home .ppb_portfolio .pm-uniform-grid.three_cols,
	.home .ppb_portfolio .pm-uniform-grid.four_cols {
		grid-template-columns: repeat(2, 1fr);
	}
}
@media (max-width: 480px) {
	.home .ppb_portfolio .pm-uniform-grid.two_cols,
	.home .ppb_portfolio .pm-uniform-grid.three_cols,
	.home .ppb_portfolio .pm-uniform-grid.four_cols {
		grid-template-columns: 1fr;
	}
}
/* Titles at rest: same scrim/title pseudo-elements the hover treatment
 * already renders, just no longer gated behind :hover on this page. The
 * scrim is retuned lighter (0.55 vs the base rule's hover-only 0.72) and
 * stays constant on hover - these rules tie the base :hover variants on
 * specificity (3 classes each) and sit later in the file, so they win in
 * both states; the hover feedback on this page is the image zoom the base
 * `.classic:hover img` transform still provides, not a scrim change. */
.home .pm-uniform-grid__frame.classic::before {
	opacity: 1;
	height: 45%;
	/* Three stops, not two: a flat 0.55->0 ramp left the title band too
	   thin over bright imagery (white stone, sky) - the deeper bottom stop
	   plus a mid stop holds contrast where the title actually sits while
	   the upper half of the scrim stays as light as before. */
	background: linear-gradient(to top, rgba(0, 0, 0, 0.68), rgba(0, 0, 0, 0.28) 55%, rgba(0, 0, 0, 0) 100%);
}
.home .pm-uniform-grid__frame.classic::after {
	opacity: 1;
	transform: none;
	/* Belt to the scrim's suspenders: keeps the title legible even where a
	   bright highlight sits directly behind the glyphs themselves. */
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.45), 0 2px 14px rgba(0, 0, 0, 0.5);
}

/* Homepage statement/intro copy (the canvas heading+paragraph inserted
 * after the hero in this same pass): the paragraph block renders
 * unconstrained, which at full page width is an unreadable one-line
 * ribbon. Reading-measure width, centered. Scoped to .home - the only
 * other paragraph node on the homepage is the "PORTFOLIO" label, which
 * is already centered and unaffected by a max-width + auto margins. */
.home .kcb-paragraph {
	max-width: var(--measure-reading, 640px);
	margin-left: auto;
	margin-right: auto;
	padding: 0 20px;
	text-align: center;
	font-size: 16px;
	line-height: 1.8;
	color: #555;
}

/* Homepage CTA button (the canvas Button node under the expedition teaser
 * band): the button block renders left-flush in a full-width container.
 * Centered, with breathing room against the full-bleed band above it and
 * the SPONSORED BY heading below. display:table = shrink-wrapped width
 * that still honors margin:auto centering. */
.home a.kcb-button {
	display: table;
	margin: 32px auto 70px;
}

/*
 * Cinematic entry, part 2 (Field Plan Phase 2, 2026-08-23) - js/homepage-
 * reveal.js adds .pm-section-reveal to the homepage's top-level canvas
 * sections (statement, teaser) on load and .is-revealed once each scrolls
 * into view. The opacity:0 start only ever applies once JS has armed a
 * section, so a page with JS disabled (or a failed load) simply never adds
 * this class and every section stays at its normal, fully-visible default -
 * same fail-safe shape as gallery-reveal.js's .pm-reveal-armed.
 */
.pm-section-reveal {
	opacity: 0;
	transform: translateY(28px);
	transition: opacity var(--duration-moderate, 500ms) var(--ease-signature, ease),
		transform var(--duration-moderate, 500ms) var(--ease-signature, ease);
}

.pm-section-reveal.is-revealed {
	opacity: 1;
	transform: none;
}

/* ---------------------------------------------------------------------------
 * Loading tone, site-wide (same review: galleries "paint as blank white
 * cards before the lazy images arrive... the site reads broken before it
 * reads beautiful"). A quiet warm grey behind every grid image so the
 * reserved boxes (width/height attrs give each <img> its aspect ratio
 * before pixels arrive) read as a deliberate placeholder instead of a
 * broken white card. Invisible once the image paints - every grid <img>
 * fully covers its own box.
 */
.pm-uniform-grid__frame {
	background-color: var(--surface-warm, #edebe7);
}
.pm-uniform-grid__frame img {
	background-color: var(--surface-warm, #edebe7);
}

/* Per-tile staggered scroll-reveal, activated only by gallery-reveal.js
 * adding .pm-reveal-armed - never present in server HTML, so a tile can
 * never get stuck invisible if that script fails to load or throws. */
.pm-reveal-armed .pm-uniform-grid__frame {
	opacity: 0;
	transform: translateY(18px);
	transition: opacity var(--duration-moderate, 500ms) ease, transform var(--duration-moderate, 500ms) ease;
}
.pm-reveal-armed .pm-uniform-grid__frame.is-revealed {
	opacity: 1;
	transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
	.pm-reveal-armed .pm-uniform-grid__frame {
		opacity: 1;
		transform: none;
		transition: none;
	}
}

@media (max-width: 767px) {
	.pm-uniform-grid.two_cols,
	.pm-uniform-grid.three_cols,
	.pm-uniform-grid.four_cols {
		grid-template-columns: repeat(2, 1fr);
	}
}
@media (max-width: 480px) {
	.pm-uniform-grid.two_cols,
	.pm-uniform-grid.three_cols,
	.pm-uniform-grid.four_cols {
		grid-template-columns: 1fr;
	}
}

/* Full-gallery mosaic (templates/gallery-grid-masonry.php only, marked with
 * .pm-mosaic-grid alongside .pm-uniform-grid) - these tiles ARE the actual
 * photographs for one portfolio piece, not preview thumbnails. Force-cropping
 * every tile into a uniform square (the .pm-uniform-grid__frame default
 * above) hid every vertical/portrait shot's real composition - exactly the
 * distinction this section exists to correct: "mosaic layout grid is not
 * contained... containment should occur on portfolio thumbnails [...] not
 * [full galleries]." Real masonry via CSS multi-column instead: each photo
 * keeps its native aspect ratio, tiles flow top-to-bottom per column. All
 * selectors below chain both .pm-uniform-grid and .pm-mosaic-grid together so
 * they win on specificity over the base uniform-grid rules regardless of
 * source order, matching this file's existing no-!important convention.
 */
.pm-uniform-grid.pm-mosaic-grid {
	display: block;
	column-gap: 28px;
}
/* padding-top (not margin-top) and !important, on top of the two-class
 * selector above not being enough on its own: the parent's screen.css has
 * `.portfolio_filter_wrapper.gallery.three_cols { margin: 0; ... }` (also
 * matches .two_cols/.four_cols) at 3-class specificity, beating a bare
 * `.pm-uniform-grid.pm-mosaic-grid` (2 classes) regardless of source order.
 * margin-top alone also isn't enough even once it wins that fight: this
 * container is the first in-flow child of the 'contain' layout's
 * .standard_wrapper div, so a top margin here collapses straight through
 * into that parent and produces zero visible space - confirmed via
 * getBoundingClientRect() showing an exact 0px gap even with margin-top
 * computing correctly to 30px. Padding
 * never collapses, so it's used here instead. Separates the mosaic from
 * whatever precedes it on the page (often a full-width horizontal
 * .slider_wrapper gallery immediately above, e.g. single-portfolio.php's
 * "Extradionare" case, which has no bottom margin of its own - the two
 * galleries touched with a literal 0px gap). Safe on every other call site
 * too: none rely on zero top spacing. */
.pm-uniform-grid.pm-mosaic-grid.two_cols,
.pm-uniform-grid.pm-mosaic-grid.three_cols,
.pm-uniform-grid.pm-mosaic-grid.four_cols {
	padding-top: 30px !important;
}
.pm-uniform-grid.pm-mosaic-grid.two_cols {
	columns: 2;
}
.pm-uniform-grid.pm-mosaic-grid.three_cols {
	columns: 3;
}
.pm-uniform-grid.pm-mosaic-grid.four_cols {
	columns: 4;
}
/* !important deliberate here (unlike the rest of this file): the parent's
 * screen.css has `.three_cols.gallery.wide .element { width: 33.32%; ... }`
 * tied in specificity with the plain reset above, and cascade order let it
 * win. That
 * 33.32% resolves against the *column* box in a multicol formatting context,
 * not the container, so it silently shrank every tile to ~1/9 of the page
 * width instead of filling its column. Same "deliberately and fully
 * replacing the old float/percentage system, not coexisting with it"
 * rationale as the frame/position rules above already use !important for.
 *
 * margin-bottom is a fixed px value, not a percentage like column-gap above
 * intentionally: percentage margins on a block resolve against its
 * *containing block* width, which inside a multicol context is the narrow
 * column box, not the grid container - a "2%" row gap and a "2%" column gap
 * looked wildly inconsistent (7px vs 23px, confirmed live) even though the
 * numbers matched in the stylesheet. A fixed value sidesteps that entirely
 * and reads as one consistent gutter in both directions. */
.pm-uniform-grid.pm-mosaic-grid .pm-uniform-grid__cell {
	width: 100% !important;
	margin: 0 0 28px !important;
	break-inside: avoid;
}
.pm-uniform-grid.pm-mosaic-grid .pm-uniform-grid__frame {
	aspect-ratio: auto;
	overflow: visible;
}
.pm-uniform-grid.pm-mosaic-grid .pm-uniform-grid__frame img {
	width: 100%;
	height: auto;
	object-fit: initial;
	display: block;
}
/*
 * Tablet / small-laptop ramp (A5, 2026-08-26 audit).
 *
 * The standard-density mosaic stepped straight from its base 2/3/4 columns
 * to 2 columns at 767px, leaving 768-1279px on the BASE count. Measured on
 * live Landscapes (four_cols, 60 photos), rendered cell width was:
 *
 *     375px ->   1 col, 306px      600px ->  2 cols, 246px
 *     480px ->   1 col, 413px      768px ->  4 cols, 154px  <-- broken
 *     900px ->  4 cols, 188px     1024px ->  4 cols, 224px
 *
 * So a 480px phone showed his photographs at 413px and a 768px iPad showed
 * the same photographs at 154px - the image got SMALLER as the screen got
 * bigger, on a photography portfolio. That is the defect.
 *
 * The ramp below keeps cell width growing with the viewport instead:
 *   <=480   1 col     (existing rule below)
 *   <=767   2 cols    (existing rule below)
 *   <=1023  2 cols    -> 768px now renders ~349px, up from 154px
 *   <=1279  3 cols    -> a step, so four_cols does not jump 2 -> 4 at once
 *   >=1280  base      -> his configured 4-up survives at real desktop widths
 *
 * The 3-column tier is scoped to four_cols only; three_cols is already 3
 * there and two_cols is already 2, so both are left alone. Ordered before
 * the narrower queries deliberately - equal specificity, so the LAST
 * matching rule wins and the narrow bands must be able to override these.
 *
 * Precedent for a 1024 tier on this grid already exists in this file: the
 * --dense contact-sheet variant has its own max-width:1024px step. Only the
 * standard density was missing one.
 */
@media (max-width: 1279px) {
	.pm-uniform-grid.pm-mosaic-grid.four_cols {
		columns: 3;
	}
}
@media (max-width: 1023px) {
	.pm-uniform-grid.pm-mosaic-grid.two_cols,
	.pm-uniform-grid.pm-mosaic-grid.three_cols,
	.pm-uniform-grid.pm-mosaic-grid.four_cols {
		columns: 2;
	}
}
@media (max-width: 767px) {
	.pm-uniform-grid.pm-mosaic-grid.two_cols,
	.pm-uniform-grid.pm-mosaic-grid.three_cols,
	.pm-uniform-grid.pm-mosaic-grid.four_cols {
		columns: 2;
	}
}
@media (max-width: 480px) {
	.pm-uniform-grid.pm-mosaic-grid.two_cols,
	.pm-uniform-grid.pm-mosaic-grid.three_cols,
	.pm-uniform-grid.pm-mosaic-grid.four_cols {
		columns: 1;
	}
}

/* Single/one-item masonry case (templates/gallery-grid-masonry.php and
 * templates/portfolio-grid.php's own new 'masonry' arrangement, both server-
 * add this modifier only when exactly one tile is being rendered): forces a
 * single column regardless of the block's configured column count, so a lone
 * tile fills the full width and reads as a deliberate single photo rather
 * than an accidental narrow sliver stuck in column 1 of an otherwise-empty
 * 3- or 4-column layout. */
.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--single {
	columns: 1;
}

/* ---------------------------------------------------------------------------
 * Contact-sheet density toggle (templates/gallery-grid-masonry.php,
 * js/gallery-density-toggle.js). `.pm-density-toggle__btn` renders once per
 * gallery, right above the grid it controls (matched by `data-target` ->
 * the grid's own `id`, not DOM proximity - layout='wide' vs 'contain' put
 * different markup between the two). Clicking it toggles
 * `.pm-mosaic-grid--dense` on that grid and flips `aria-pressed`, which
 * this file reads for the active-state styling below - no separate
 * ".active" class needed.
 * ------------------------------------------------------------------------ */
/* Docked to the grid's top-LEFT with a hairline rule filling the rest of
 * the row (Kedyn's placement call, 2026-08-23): the old top-right float
 * had no visual anchor and read as a stray header item under the sticky
 * site nav. The rule ties the pill to the grid it controls; the ::after
 * uses --color-border, which darkroom.css already redefines for dark
 * portfolio pages, so the line works on both palettes for free. */
.pm-density-toggle {
	display: flex;
	align-items: center;
	gap: 16px;
	margin-bottom: 18px;
}
.pm-density-toggle::after {
	content: '';
	flex: 1;
	height: 1px;
	background: var(--color-border);
}
.pm-density-toggle__btn {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	/* min-height rather than more padding: 44px is the touch tap-target
	   floor; the flex centering absorbs the extra height without changing
	   the pill's text metrics. */
	min-height: 44px;
	padding: 9px 18px;
	background: var(--color-white);
	border: 1px solid var(--color-border);
	border-radius: 22px;
	font-family: var(--font-family-heading);
	font-size: 11px;
	letter-spacing: var(--button-letter-spacing);
	text-transform: uppercase;
	color: #666;
	cursor: pointer;
	transition: border-color 0.2s ease, color 0.2s ease;
}
.pm-density-toggle__btn i {
	font-size: 13px;
	transition: color 0.2s ease;
}
.pm-density-toggle__btn:hover,
.pm-density-toggle__btn:focus-visible {
	border-color: var(--color-accent);
	color: var(--color-heading-text);
}
.pm-density-toggle__btn[aria-pressed="true"] {
	border-color: var(--color-accent);
	color: var(--color-heading-text);
}
.pm-density-toggle__btn[aria-pressed="true"] i {
	color: var(--color-accent);
}

/* Dense grid itself. Every selector chains 4 classes (vs the base masonry
 * rules' 3, e.g. `.pm-uniform-grid.pm-mosaic-grid.two_cols` above) so it
 * wins on specificity alone at every breakpoint, including against this
 * file's own 767px/480px step-downs further up - same no-!important,
 * specificity-over-source-order convention already used throughout this
 * file (see e.g. `.pm-uniform-grid .pm-uniform-grid__cell` above). */
.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense {
	column-gap: 8px;
}
.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense.two_cols,
.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense.three_cols,
.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense.four_cols {
	columns: 6;
}
.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense .pm-uniform-grid__cell {
	margin: 0 0 8px !important;
}
@media (max-width: 1024px) {
	.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense.two_cols,
	.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense.three_cols,
	.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense.four_cols {
		columns: 4;
	}
}
@media (max-width: 767px) {
	.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense.two_cols,
	.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense.three_cols,
	.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense.four_cols {
		columns: 3;
	}
}
@media (max-width: 480px) {
	.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense.two_cols,
	.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense.three_cols,
	.pm-uniform-grid.pm-mosaic-grid.pm-mosaic-grid--dense.four_cols {
		columns: 2;
	}
}

/* templates/portfolio-grid.php's new 'masonry' arrangement only: a real
 * desandro Masonry.js progressive enhancement (assets/js/masonry-init.js,
 * kedynsierra-content-builder plugin) layered on top of the CSS multi-column
 * masonry above. This wrapper is deliberately its OWN element, nested one
 * level inside `#container.pm-uniform-grid.pm-mosaic-grid` rather than
 * applied to that element directly - see portfolio-grid.php's own docblock
 * for why: Masonry sets an explicit inline pixel height on whatever element
 * it's given, and `.pm-uniform-grid { height: auto !important }` above would
 * silently win that fight (an !important stylesheet rule beats a plain
 * inline style) and collapse the whole layout back to zero visible height -
 * the exact regression this file's own top-of-file docblock already
 * documents fixing once. Scoping Masonry to this untouched wrapper instead
 * means its inline height can never collide with that rule.
 *
 * `position: relative` gives Masonry's absolutely-positioned items (each
 * `.pm-uniform-grid__cell`) a containing block that's actually this wrapper,
 * not some further ancestor up the page.
 */
.pm-mosaic-grid__js-layer {
	position: relative;
}

/* The sizer element masonry-init.js points `columnWidth` at (desandro's own
 * documented pattern for a fluid/percentage-width grid - see that library's
 * "Percent width" example on masonry.desandro.com). Zero height/content -
 * purely a width reference. Matching width on `.pm-uniform-grid__cell` below
 * is what actually determines each column's real width; both must move
 * together so Masonry lays out the same width it visually renders at.
 *
 * `!important` is deliberate, matching the CSS-multi-column masonry rules
 * further up this file: `.pm-uniform-grid.pm-mosaic-grid .pm-uniform-grid__
 * cell { width: 100% !important; ... }` already exists for the no-JS
 * fallback (each cell fills its multi-column column), and these rules need
 * to win over that for cells sitting inside the JS layer specifically, once
 * Masonry takes over and switches them to explicit fractional widths instead.
 * The percentages below leave room for a gutter via the cell's own existing
 * `margin: 0 0 28px !important` (vertical) and a matching subtracted amount
 * here (horizontal) - approximate, not visually re-verified from this
 * machine (no browser access - see brief), tune the subtracted px if the
 * real gutter reads tighter or looser than the CSS-column fallback's 28px
 * once someone can see it live.
 */
.pm-mosaic-grid__js-layer[data-columns="2"] .pm-mosaic-grid__sizer,
.pm-mosaic-grid__js-layer[data-columns="2"] .pm-uniform-grid__cell {
	width: calc(50% - 14px) !important;
}
.pm-mosaic-grid__js-layer[data-columns="3"] .pm-mosaic-grid__sizer,
.pm-mosaic-grid__js-layer[data-columns="3"] .pm-uniform-grid__cell {
	width: calc(33.333% - 19px) !important;
}
.pm-mosaic-grid__js-layer[data-columns="4"] .pm-mosaic-grid__sizer,
.pm-mosaic-grid__js-layer[data-columns="4"] .pm-uniform-grid__cell {
	width: calc(25% - 21px) !important;
}
/* Same 767px/480px step-down as the CSS-column fallback further up this file
 * (`.pm-uniform-grid.pm-mosaic-grid.two_cols/.three_cols/.four_cols` media
 * rules) - keeps the JS-enhanced layout's column count in visual sync with
 * what the no-JS fallback would show at the same breakpoint. Masonry's own
 * built-in window-resize listener (enabled by default) re-runs layout() using
 * this sizer's new computed width automatically - no resize handling needed
 * in masonry-init.js itself. */
@media (max-width: 767px) {
	.pm-mosaic-grid__js-layer[data-columns="3"] .pm-mosaic-grid__sizer,
	.pm-mosaic-grid__js-layer[data-columns="3"] .pm-uniform-grid__cell,
	.pm-mosaic-grid__js-layer[data-columns="4"] .pm-mosaic-grid__sizer,
	.pm-mosaic-grid__js-layer[data-columns="4"] .pm-uniform-grid__cell {
		width: calc(50% - 14px) !important;
	}
}
@media (max-width: 480px) {
	.pm-mosaic-grid__js-layer .pm-mosaic-grid__sizer,
	.pm-mosaic-grid__js-layer .pm-uniform-grid__cell {
		width: 100% !important;
	}
}
/* Defense in depth alongside masonry-init.js's own `transitionDuration: 0`
 * option for a reduced-motion visitor (same intent as this file's existing
 * `.pm-reveal-armed` reduced-motion rule above): Masonry applies its
 * transition via an inline style on each item, which a stylesheet rule can
 * only override with !important - covers the (unlikely but possible) case of
 * a stale cached script still animating positions. */
@media (prefers-reduced-motion: reduce) {
	.pm-mosaic-grid__js-layer .pm-uniform-grid__cell {
		transition: none !important;
	}
}

/* templates/portfolio-grid.php's tile caption uses <h3>, not the parent
 * theme's bare <h5> - avoids a 3-level heading skip under this page's real
 * <h1> (every homepage tile caption is structurally mid-importance, not
 * H5-deep). This rule preserves the exact stock H5 visual size (DESIGN.md:
 * 18px) the parent theme's tag-selector CSS provides, so the semantic
 * markup differs with zero visual change. */
.portfolio_desc h3 {
	font-size: 18px;
}

/* ---------------------------------------------------------------------------
 * `arrangement: filmstrip` - equal-width editorial spread.
 *
 * Pattern 4 of docs/specs/CAROUSEL-UX-PATTERNS-2026-08-12.md: equal-width
 * columns edge-to-edge like a magazine spread, an overlaid italic title, a
 * small caption, and an index number.
 *
 * COMPOSES with the existing grid instead of replacing it. The wrapper still
 * carries `pm-uniform-grid`, so column count still comes from the block's own
 * `columns` prop, and the ultra-wide tiers and the 767px/480px collapses above
 * all still apply unchanged. Filmstrip only collapses the gap, retunes the
 * frame aspect, and promotes the overlay. The source doc's "five columns" is
 * read as incidental to its screenshot - honouring the `columns` control the
 * editor already exposes beats hardcoding 5 and silently ignoring it.
 *
 * REUSES portfolio-grid's existing overlay rather than adding a second one.
 * `.pm-uniform-grid__frame.classic` already renders a gradient scrim via
 * ::before and the tile title via ::after (`content: attr(data-title)`),
 * revealed on hover. Filmstrip keeps both pseudo-elements and simply makes
 * the title permanent, larger and left-aligned. gallery-grid-masonry.php's
 * frame has no `.classic` class and so had no overlay at all - the same rules
 * below create one there, fed by a `data-title` this pass added to that
 * template (attachment caption, the closest equivalent an image list has to
 * portfolio-grid's post title).
 *
 * SPECIFICITY is deliberate, not source-order luck. A bare
 * `.pm-filmstrip .pm-uniform-grid__frame::after` would tie exactly with the
 * existing `.pm-uniform-grid__frame.classic::after` at (0,2,1) and win only
 * because it sits later in this file - the same accident that silently broke
 * coverflow when `--timed` was added to blocks.css (see MISSION-AUDIT
 * 2026-08-12). Chaining `.pm-uniform-grid.pm-filmstrip` makes these (0,3,1),
 * and the interaction-state variants are listed explicitly so the `:hover`
 * and `:focus-within` rules above cannot re-assert themselves. No !important.
 *
 * NOT full-bleed to the viewport, deliberately. The pattern describes the
 * strip as full-bleed, but this theme has no `100vw` breakout anywhere, and
 * the two possible containers disagree: `body` carries no `overflow-x` guard,
 * while `.ppb_wrapper` - which wraps builder content - sets
 * `overflow-x: hidden`. A breakout would therefore either be clipped inside
 * the builder or produce a horizontal scrollbar outside it, and which one
 * cannot be determined without a browser. Edge-to-edge within the container
 * delivers the pattern's actual character; the breakout is one rule to add if
 * a real-browser pass settles it.
 *
 * NO tone or saturation filter, deliberately. The source doc notes the
 * reference's photo tones read muted and coordinated, but that is a property
 * of the photographs chosen for that spread, not of the layout. Desaturating
 * a working photographer's images by default would be editing his work, not
 * offering a layout, so it is left to the photographs.
 *
 * TYPEFACE is an open decision, exposed as a custom property. The pattern
 * calls for an italic serif; this design system has no serif at all - it runs
 * Oswald for display and Lato for body, both sans. Rather than introduce a
 * typeface the brand has never used, the title below uses Lato italic (a real
 * italic, unlike Oswald which has none and would be synthesised). Swapping in
 * a serif once one is chosen is a one-line change to
 * `--pm-filmstrip-title-font`.
 *
 * MOTION: none by design. This is the one pattern of the five with no tilt,
 * offset, or transform, so there is nothing here for a reduced-motion block
 * to suppress - and the rules below actively remove the inherited
 * translateY, so filmstrip is calmer than the default tile, not busier.
 * ------------------------------------------------------------------------ */
.pm-uniform-grid.pm-filmstrip {
	--pm-filmstrip-title-font: Lato, 'Helvetica Neue', Arial, Verdana, sans-serif;
	gap: 0;
	counter-reset: pm-filmstrip;
}

.pm-uniform-grid.pm-filmstrip .pm-uniform-grid__cell {
	counter-increment: pm-filmstrip;
}

/* Taller than the default 1/1 so the columns read as a magazine spread rather
 * than a contact sheet. Images still object-fit: cover via the base rule. */
.pm-uniform-grid.pm-filmstrip .pm-uniform-grid__frame {
	aspect-ratio: 3 / 4;
}

/* Scrim: always on here (the base `.classic` rule fades it in on hover only),
 * because the title it exists to make legible is now always on too. Also
 * creates one for gallery-grid-masonry frames, which have no `.classic`. */
.pm-uniform-grid.pm-filmstrip .pm-uniform-grid__frame::before,
.pm-uniform-grid.pm-filmstrip .pm-uniform-grid__frame:hover::before,
.pm-uniform-grid.pm-filmstrip .pm-uniform-grid__frame:focus-within::before {
	content: '';
	position: absolute;
	inset: auto 0 0 0;
	height: 60%;
	background: linear-gradient(to top, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0) 100%);
	opacity: 1;
	transform: none;
	pointer-events: none;
}

/* Index + title in one pseudo-element: ::before is the scrim and ::after is
 * the only other one available on this frame, so the index is prefixed to the
 * title rather than given an element of its own. Reads as editorial run-in
 * numbering, which is what the reference does anyway. */
.pm-uniform-grid.pm-filmstrip .pm-uniform-grid__frame::after,
.pm-uniform-grid.pm-filmstrip .pm-uniform-grid__frame:hover::after,
.pm-uniform-grid.pm-filmstrip .pm-uniform-grid__frame:focus-within::after {
	content: counter(pm-filmstrip, decimal-leading-zero) '\00a0\00a0' attr(data-title);
	position: absolute;
	right: 0;
	bottom: 0;
	left: 0;
	padding: 0 18px 18px;
	color: #fff;
	font-family: var(--pm-filmstrip-title-font);
	font-style: italic;
	font-size: 24px;
	line-height: 1.2;
	text-align: left;
	opacity: 1;
	transform: none;
	pointer-events: none;
}

/* portfolio-grid only - gallery-grid-masonry renders no description block.
 * The pattern's "small caption text below" maps onto the existing excerpt;
 * gap is 0 now, so it needs its own inset or it would run edge to edge. */
.pm-uniform-grid.pm-filmstrip .portfolio_desc {
	padding: 12px 18px 0;
}

.pm-uniform-grid.pm-filmstrip .portfolio_desc .post_detail {
	font-size: 13px;
	opacity: 0.72;
}

@media (max-width: 767px) {
	.pm-uniform-grid.pm-filmstrip .pm-uniform-grid__frame::after,
	.pm-uniform-grid.pm-filmstrip .pm-uniform-grid__frame:hover::after,
	.pm-uniform-grid.pm-filmstrip .pm-uniform-grid__frame:focus-within::after {
		font-size: 17px;
		padding: 0 12px 12px;
	}
}

/* Legacy vendor [tg_masonry_gallery] markup (class="portfolio_filter_wrapper
 * gallery {two|three|four}_cols {wide|contain}", cells .element.grid.classicN_
 * cols - emitted only by that shortcode; every other consumer of these
 * classes either adds .pm-uniform-grid, uses only the #portfolio_filter_
 * wrapper id without the class, or is the separately-named
 * #portfolio_mixed_filter_wrapper family). This markup expected Isotope to
 * size and reveal it: screen.css starts every .portfolio_filter_wrapper at
 * height:0/visibility:hidden/opacity:0, and the enqueued
 * templates/script-gallery-grid.php throws on its .isotope() call (Isotope
 * has no references anywhere in wp-content), so its own .addClass('visible')
 * is never reached and nothing ever un-hides the grid. Same "stop
 * participating in the reveal pattern" fix as .pm-uniform-grid at the top of
 * this file, laid out via the same CSS multi-column approach as
 * .pm-mosaic-grid above - all of that section's hard-won multicol rules
 * apply here identically: fixed px gaps (percentages resolve against the
 * column box), padding-top not margin-top (collapses through the 'contain'
 * layout's .standard_wrapper), width:100% !important on cells (percentage
 * widths shrink to a fraction of the column, not the container).
 * :not(.pm-uniform-grid) keeps the modern grid system untouched if the two
 * class sets ever meet on one element. */
.portfolio_filter_wrapper.gallery.two_cols:not(.pm-uniform-grid),
.portfolio_filter_wrapper.gallery.three_cols:not(.pm-uniform-grid),
.portfolio_filter_wrapper.gallery.four_cols:not(.pm-uniform-grid) {
	display: block;
	height: auto !important;
	visibility: visible !important;
	opacity: 1 !important;
	width: 100% !important; /* screen.css's 102% compensated float gutters */
	column-gap: 28px;
	padding-top: 30px !important;
}
.portfolio_filter_wrapper.gallery.two_cols:not(.pm-uniform-grid) {
	columns: 2;
}
.portfolio_filter_wrapper.gallery.three_cols:not(.pm-uniform-grid) {
	columns: 3;
}
.portfolio_filter_wrapper.gallery.four_cols:not(.pm-uniform-grid) {
	columns: 4;
}
.portfolio_filter_wrapper.gallery.two_cols:not(.pm-uniform-grid) .element,
.portfolio_filter_wrapper.gallery.three_cols:not(.pm-uniform-grid) .element,
.portfolio_filter_wrapper.gallery.four_cols:not(.pm-uniform-grid) .element {
	width: 100% !important;
	margin: 0 0 28px !important;
	float: none;
	break-inside: avoid;
}
/* .element's own child - screen.css's classic grid markup, e.g.
 * ".one_third.gallery3" - is still float:left from the pre-multicol system
 * above. .element itself was un-floated by the rule above but was never
 * given anything to clear that child, so it collapses to height:0 with only
 * the floated child inside - invisible to this multicol layout, which packs
 * columns by each cell's real box height. The photo still paints (floated
 * content still renders), just outside its collapsed parent's box, so cells
 * overlapped and the whole grid's own height read far short of its real
 * content - the "broken padding below the gallery" is this same collapse,
 * not a separate bug. */
.portfolio_filter_wrapper.gallery.two_cols:not(.pm-uniform-grid) .element::after,
.portfolio_filter_wrapper.gallery.three_cols:not(.pm-uniform-grid) .element::after,
.portfolio_filter_wrapper.gallery.four_cols:not(.pm-uniform-grid) .element::after {
	content: "";
	display: table;
	clear: both;
}
/* The inner .gallery_type div is screen.css's second JS-dependent hiding
 * layer (opacity:0, lifted only by script-gallery-grid.php's dead .fadeIn
 * pass in the same never-reached imagesLoaded callback as .visible). */
.portfolio_filter_wrapper.gallery.two_cols:not(.pm-uniform-grid) .element .gallery_type,
.portfolio_filter_wrapper.gallery.three_cols:not(.pm-uniform-grid) .element .gallery_type,
.portfolio_filter_wrapper.gallery.four_cols:not(.pm-uniform-grid) .element .gallery_type {
	opacity: 1 !important;
}
.portfolio_filter_wrapper.gallery.two_cols:not(.pm-uniform-grid) .element img,
.portfolio_filter_wrapper.gallery.three_cols:not(.pm-uniform-grid) .element img,
.portfolio_filter_wrapper.gallery.four_cols:not(.pm-uniform-grid) .element img {
	width: 100%;
	height: auto;
	display: block;
}
/* Same responsive column steps as .pm-mosaic-grid above. */
@media (max-width: 767px) {
	.portfolio_filter_wrapper.gallery.two_cols:not(.pm-uniform-grid),
	.portfolio_filter_wrapper.gallery.three_cols:not(.pm-uniform-grid),
	.portfolio_filter_wrapper.gallery.four_cols:not(.pm-uniform-grid) {
		columns: 2;
	}
}
@media (max-width: 480px) {
	.portfolio_filter_wrapper.gallery.two_cols:not(.pm-uniform-grid),
	.portfolio_filter_wrapper.gallery.three_cols:not(.pm-uniform-grid),
	.portfolio_filter_wrapper.gallery.four_cols:not(.pm-uniform-grid) {
		columns: 1;
	}
}


/* ---------------------------------------------------------------------------
 * Builder-page frame fill (2026-08-29). On any page whose content renders
 * inside #page_content_wrapper (every content-builder page), the theme's own
 * `#page_content_wrapper img { height: auto }` (screen.css, specificity
 * 1,0,1) silently beats the `.pm-uniform-grid .pm-uniform-grid__frame img`
 * fill rule above (0,3,1) - so filmstrip and `grid` arrangement frames
 * showed the image pinned to the frame top with dead space (and the always-
 * on filmstrip scrim) below it. Exact same ID-beats-classes trap documented
 * in hero-essay.css's own image rule. Mosaic/masonry is EXCLUDED because it
 * genuinely wants height:auto (the :526 rule above) - and the theme ID rule
 * already delivers that there, which is why masonry never showed the bug.
 * ------------------------------------------------------------------------ */
/* The masonry-template frame wraps its img in <a class=fancy-gallery>,
 * an auto-height block - a percentage img height resolves against IT and
 * collapses to auto (the circular-percentage rule), so the img rule below
 * alone was not enough. The anchor must fill the frame first. */
#page_content_wrapper .pm-uniform-grid:not(.pm-mosaic-grid) .pm-uniform-grid__frame > a,
.page_content_wrapper .pm-uniform-grid:not(.pm-mosaic-grid) .pm-uniform-grid__frame > a {
	display: block;
	height: 100%;
}

/*
 * MOBILE (2026-08-29, second pass). The rule below was (1,3,1) and TIED
 * with grid.css 's own `#page_content_wrapper .inner .sidebar_content img
 * { height: auto }` inside `@media (max-width: 767px)` - and grid.css loads
 * later, so the tie went to it. Desktop was unaffected (that rule is behind
 * the media query), which is exactly why the filmstrip looked fixed on a
 * laptop and still showed a photo stuck to the top of a grey 3:4 frame on a
 * phone - the platform most readers use.
 *
 * Two independent belts, neither of them !important:
 *  - `.sidebar_content` joins the chain, making this (1,4,1) and a genuine
 *    win rather than a source-order coin flip;
 *  - `min-height` carries the same job on a property NOTHING else on this
 *    site sets for these images (verified live: zero competing rules), so
 *    the frame still fills even if some future rule out-specifies `height`.
 * Same lesson as the sponsor wall earlier today: stop racing the theme on
 * `height`, and win on a property it never claims.
 */
#page_content_wrapper .inner .sidebar_content .pm-uniform-grid:not(.pm-mosaic-grid) .pm-uniform-grid__frame img,
.page_content_wrapper .inner .sidebar_content .pm-uniform-grid:not(.pm-mosaic-grid) .pm-uniform-grid__frame img,
#page_content_wrapper .pm-uniform-grid:not(.pm-mosaic-grid) .pm-uniform-grid__frame img,
.page_content_wrapper .pm-uniform-grid:not(.pm-mosaic-grid) .pm-uniform-grid__frame img {
	width: 100%;
	height: 100%;
	min-height: 100%;
	object-fit: cover;
}

/* Clips a portrait source that min-height cannot letterbox away. */
#page_content_wrapper .pm-uniform-grid:not(.pm-mosaic-grid) .pm-uniform-grid__frame,
.page_content_wrapper .pm-uniform-grid:not(.pm-mosaic-grid) .pm-uniform-grid__frame {
	overflow: hidden;
}
