/* Web Portfolio Screens block. Corner-pins a screenshot into an illustrated
   device frame — same technique/markup contract as the Web Mockup hero
   (assets/css/conditional/heroes/hero-web-mockup.css, .mockupStage etc.,
   prefixed .psMockupStage etc. here) but mirrored horizontally via
   transform: scaleX(-1) on .psMockupLineart below — see
   template-parts/blocks/block-portfolio-screens.php's header comment for how
   the corner-pin geometry itself is mirrored to match. Alongside it, 1-12
   clickable screen cards render in a layout that depends only on count —
   clicking one swaps its image into the monitor (assets/js/conditional/blocks/portfolioScreens.js).

   The device is deliberately taken out of normal flow (.psMockupCol,
   .psMockupStage are position:absolute — see below) so the background and
   overall block height are defined ONLY by the card grid, and the device is
   free to render taller than that and visibly overflow above/below it — see
   assets/js/conditional/blocks/portfolioScreens.js's balanceDevice() for how
   its height is actually computed. Desktop sizes independently, off this
   file's own .psMockupStage height clamp (bigger under .alignfull, further
   below), specifically so it always overflows its background rather than
   sometimes matching it depending on how tall the card grid happens to be.
   Tablet/Mobile keep the original approach instead: targeting the screen
   cards' own image height at the device's right edge, via
   --ps-right-edge-fraction below. */

.portfolioScreensBlock
{
	position: relative;
	padding: clamp(1.5rem, 4vw, 3rem) clamp(1.25rem, 4vw, 2rem);
	overflow: visible;
}

/* Always bleeds to the full viewport width regardless of the block's own
   alignment (Content Width included) — the standard full-bleed-background
   trick already used elsewhere in this theme (assets/css/conditional/blocks/clientLogos.css),
   here on an absolutely positioned element: left:50% + translateX(-50%)
   centers it against the page regardless of how narrow the block itself is.
   top/bottom:0 here is only the no-JS fallback (matches the block's own
   padded box) — balanceDevice() in portfolioScreens.js overrides both
   inline to hug the card grid's own rendered bounds instead, padded evenly
   (BG_VERTICAL_PAD in that file) so the cards/titles aren't flush against
   its edges (see that function for why the grid's own bounds, not the
   block's, is the right reference). */
.portfolioScreensBlockBg
{
	position: absolute;
	top: 0;
	bottom: 0;
	left: 50%;
	width: 100vw;
	transform: translateX(-50%);
	z-index: 0;
	pointer-events: none;
}

/* Width control (block toolbar Wide/Full): this is the ONLY thing alignment
   changes. Full makes the background layer above (and .portfolioScreensBlock
   itself, via the theme's standard .alignfull handling) span the full
   viewport; Content Width leaves it at the normal content column. Either way
   the actual device+screens content below stays capped at
   --content-max-width and centered inside whichever box that turns out to
   be — it never stretches edge-to-edge on its own.

   CSS Grid, not flexbox, for this row specifically: the two columns'
   widths need to come from a FIXED share of the row, not from each other,
   or portfolioScreens.js's height-balancing creates a feedback loop — device
   height is computed from the grid's rendered height, which changed if the
   device column's width (and therefore the grid column's leftover space)
   changed too, which it did under the old flex:1/flex:0 split, so every
   correction triggered another one (visible as rapid flicker/strobing).
   Explicit grid-template-columns fixes each column's width to the row's
   total width alone, so sizing the device never feeds back into the grid's
   own width. */
.portfolioScreensLayout
{
	position: relative;
	z-index: 1;
	display: grid;
	grid-template-columns: minmax(220px, 1fr) minmax(280px, 1.15fr);
	align-items: stretch;
	justify-content: center;
	/* Tight on purpose — the device and cards are meant to read as one close
	   group, not two things with daylight between them. */
	gap: clamp(0.75rem, 1.5vw, 1.25rem);
	max-width: var(--content-max-width, 1400px);
	margin: 0 auto;
}

/* Full-width alignment: the background already bleeds full-viewport
   unconditionally (see .portfolioScreensBlockBg above) — this rule is only
   about the device+card GROUP, which stays a single centered unit with room
   to breathe either side, not spread to the viewport edges, so it reads as
   one wide-but-bounded block rather than two things pinned to opposite
   corners of the page. The column split is biased further toward the cards
   (0.85fr/1.5fr vs the default ~1fr/1.15fr): Desktop's own size is set
   independently of this column's width either way (see .psMockupStage below
   and balanceDevice() in portfolioScreens.js), so the device column doesn't
   need extra width — handing it more would just be dead space. */
.portfolioScreensBlock.alignfull .portfolioScreensLayout
{
	max-width: min(1900px, 92vw);
	grid-template-columns: minmax(220px, 0.85fr) minmax(280px, 1.5fr);
}

/* Device Position: Last (block-portfolio-screens.php's $is_device_last) —
   swaps which of the two grid items lands in which explicit track via
   `order` alone, rather than reordering the markup itself. CSS Grid's
   auto-placement algorithm places items into the explicit tracks in
   order-modified document order, so this alone moves the cards into track 1
   (visually left) and the device into track 2 (visually right) — no other
   rule in this file needs to know which side is which. portfolioScreens.js's
   balanceDevice() mirrors this (its own isDeviceLast check) when it writes
   the two tracks' actual pixel widths, so the sizing still lands in the
   track each item now actually occupies. NOT applied at the ≤640px
   stacked tier too, where grid-template-columns collapses to a single
   column and `order` would reorder the rows — reset there (≤640px block
   below) so the device always comes first when stacked. */
.portfolioScreensBlock--deviceLast .psMockupCol
{
	order: 2;
}

.portfolioScreensBlock--deviceLast .portfolioScreensGrid
{
	order: 1;
}

/* The un-mirrored orientation (PHP skips shew_mirror_quad_corners() for both
   the screenshot's corner-pin quad and the clip mask when device-last — see
   that file's header comment) needs the line-art's own visual flip dropped
   to match, or the artwork would still face the mirrored (first-position)
   direction while the screenshot/clip geometry underneath it had already
   flipped back — a mismatched double-negative. */
.portfolioScreensBlock--deviceLast .psMockupLineart
{
	transform: none;
}

/* ── Mockup stage ───────────────────────────────────────────────────────── */
/* .psMockupCol is a plain in-flow grid item — its column TRACK width comes
   from grid-template-columns above (fixed relative to the row, independent
   of the device's own rendered size), and it stretches to the row's full
   height by default (CSS Grid's align-items:stretch) so the absolutely
   positioned .psMockupStage inside it can center against that full height.
   portfolioScreens.js only ever sets the stage's HEIGHT (width follows from
   aspect-ratio) — it does not touch this column's width, which is what
   breaks the feedback loop described above. The device may render narrower
   or wider than its column; it's left-aligned within it either way. */

.psMockupCol
{
	position: relative;
}

.psMockupStage
{
	position: absolute;
	left: 0;
	top: 50%;
	transform: translateY(-50%);
	/* This clamp is now the actual governing size for Desktop (see
	   balanceDevice() in portfolioScreens.js) rather than a no-JS/initial-paint
	   fallback — raised from 420px so it reliably renders taller than a
	   typical card grid and overflows its own background, per Design Tokens
	   → Web Portfolio Screens for the configurable min/preferred-vw terms.
	   .alignfull below raises this further, since that layout's row is wider
	   still. */
	height: clamp(var(--ps-device-min, 260px), var(--ps-device-max, 32vw), 560px);
	width: auto;
	aspect-ratio: var(--ps-mockup-aspect-w, 1) / var(--ps-mockup-aspect-h, 1);
}

/* Full-width alignment: a taller, independent size target. Normally
   balanceDevice() (portfolioScreens.js) derives the device's height from the
   card cluster's own rendered height so the two visually match — but that
   means collapsing the cards to one short row (see .alignfull rules above)
   would shrink the device to match, when the point of full-width is for the
   device to read as BIGGER than the background, not smaller. In alignfull,
   balanceDevice() skips that card-derived calc and leaves sizing to this
   clamp instead (still capped against overlapping the card column, and
   margins still computed from whatever height actually results). */
.portfolioScreensBlock.alignfull .psMockupStage
{
	height: clamp(320px, 34vw, 640px);
}

/* Click-to-expand trigger (assets/js/conditional/shared/screen-image-zoom.js,
   opted in via [data-screen-image-zoom-scope] on .psMockupCol above). The
   .screenImageZoomTrigger/.screenImageModal component itself is styled in
   screenImageDemo.css (shared with the Screen Image system's own consumers,
   e.g. Web Screen and Text) — only this hover rule needs to be reimplemented
   here, since that file's own version is scoped under `.screenImage:hover`,
   a base class this block deliberately doesn't take (see the block-portfolio-
   screens.php comment on data-screen-image-zoom-scope for why). */
.psMockupStage:hover .screenImageZoomTrigger svg,
.psMockupStage .screenImageZoomTrigger:focus-visible svg
{
	opacity: 1;
	transform: scale(1);
}

.psMockupClipDefs
{
	position: absolute;
	width: 0;
	height: 0;
	overflow: hidden;
}

.psMockupScreenshotMask
{
	position: absolute;
	inset: 0;
	overflow: hidden;
	/* Matches .portfolioScreensBlockBg exactly (--ps-screen-bg, set in PHP
	   from the same ps_block_bg value) so the opacity crossfade between
	   screens (initScreenSwap() in portfolioScreens.js) fades through the
	   row's own background instead of a mismatched flash. */
	background: var(--ps-screen-bg, transparent);
}

.psMockupScreenshotLayer
{
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	overflow: hidden;
	transform-origin: 0 0;
	will-change: transform;
}

/* Same scale/position logic as the Web Mockup hero's screenshot (assets/css/
   conditional/heroes/hero-web-mockup.css) — width:100%, height:auto, top-
   anchored, so the image renders at the same relative scale in both places
   instead of this block's previous object-fit:cover (which zoomed/cropped
   far more aggressively than the hero for the same source image). Switches
   to .is-covering (portfolioScreens.js's fitScreenshot(), a copy of the
   hero's) when a width-scaled image would leave a gap at the bottom of the
   frame, scaling by height instead and centering horizontally. */
.psMockupScrollInner
{
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
}

.psMockupScrollInner IMG
{
	display: block;
	width: 100%;
	height: auto;
	/* Crossfade on click (assets/js/conditional/blocks/portfolioScreens.js
	   toggles opacity 1<->0 around the src swap) — duration controlled by
	   Design Tokens → Web Portfolio Screens → Screen Swap Fade. */
	transition: opacity var(--ps-swap-duration, 250ms) ease;
}

.psMockupScrollInner.is-covering IMG
{
	position: relative;
	width: auto;
	max-width: none;
	left: 50%;
	transform: translateX(-50%);
}

/* Per-page override (inc/hero.php shew_portfolio_screens_stretch_post_ids())
   — same "stretch" distortion as the Web Mockup hero's stretchMode: whole
   image visible, top/left/right/bottom all matched to the frame exactly,
   aspect ratio disregarded (object-fit: fill) instead of the default above. */
.psMockupScrollInner.is-stretch
{
	position: absolute;
	inset: 0;
	width: auto;
}
.psMockupScrollInner.is-stretch IMG
{
	width: 100%;
	height: 100%;
	object-fit: fill;
}

/* The mirror: same illustration as the Web Mockup hero, flipped left-right.
   Purely visual — the corner-pin quad above is mirrored separately in PHP
   (shew_mirror_quad_corners()), so the screenshot itself never flips. */
.psMockupLineart
{
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	pointer-events: none;
	transform: scaleX(-1);
}

/* ── Screen cards ───────────────────────────────────────────────────────── */
/* Flexbox (not CSS Grid) so any count from 1-12 wraps and centers on its
   own. Cards flex-grow:1 (below) so every row's cards expand to fill the
   full row width evenly, rather than sitting at a fixed size with leftover
   gap — including a lone last-row card (e.g. the 3rd of a 2+1 layout), which
   grows to fill that row's full width alone. Count-specific rules only set a
   card's flex-basis/grid max-width to force the exact hand-tuned wrap point
   at 1-4 rows; 5+ just flows and grows generically. */

.portfolioScreensGrid
{
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: flex-start;
	gap: var(--ps-card-gap, 1.5rem);
	/* Width comes from its grid TRACK (.portfolioScreensLayout's
	   grid-template-columns) up to this cap — no flex-grow property here
	   since the parent is CSS Grid now, not flex (see that rule's comment
	   for why). */
	max-width: 600px;
	/* CSS Grid's default (stretch) would otherwise fill this box out to the
	   FULL column track — which is usually wider than the cards actually
	   need — and the justify-content:center above would then center the
	   cards WITHIN that oversized box, reading as dead space on both sides
	   instead of the cards sitting right next to the device. justify-self:
	   start makes this box shrink-wrap its own content and sit flush at the
	   track's near edge (the .portfolioScreensLayout gap above is then the
	   only space between the two). */
	justify-self: start;
}

.portfolioScreensCard
{
	/* Reset button defaults */
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	font: inherit;
	cursor: pointer;
	text-align: inherit;

	display: flex;
	flex-direction: column;
	gap: var(--ps-image-title-gap, 0.6rem);
	flex: 1 1 200px;
	/* Without this a flex item can't shrink below its longest unbreakable
	   word, so a long title word ("educational") widened that one card —
	   and, at a fixed aspect ratio, made it taller too. Cards are sized by
	   flex-basis alone; the title handles its own overflow (below). */
	min-width: 0;
}

/* GeneratePress's base theme CSS sets a plain `button:hover` background
   (rgb(63,63,63)) sitewide — this card is a real <button>, so without this
   it inherits a dark box behind the whole image+title on hover. A class
   selector already out-specifies GP's bare element selector, so this wins
   without needing !important. */
.portfolioScreensCard:hover,
.portfolioScreensCard:focus
{
	background: none;
}

/* Count-specific widths/wrap points — everything else (2, and 5-12) uses the
   base 200px flex-basis above and wraps/centers naturally. */

.portfolioScreensGrid--count-1
{
	max-width: 360px;
}

.portfolioScreensGrid--count-1 .portfolioScreensCard
{
	flex-basis: 320px;
}

.portfolioScreensGrid--count-2 .portfolioScreensCard
{
	flex-basis: 260px;
}

.portfolioScreensGrid--count-3,
.portfolioScreensGrid--count-4
{
	max-width: 480px;
}

.portfolioScreensGrid--count-3 .portfolioScreensCard,
.portfolioScreensGrid--count-4 .portfolioScreensCard
{
	flex-basis: 220px;
}

/* Device Position/Tablet sizing (block-portfolio-screens.php's
   $is_device_last / 'tablet' === $device wrapper classes) — Tablet's screen
   is portrait, so a card sized like the others above reads nearly as tall as
   the tablet mockup itself. Rather than distort the thumbnail's aspect ratio
   to fake a shorter card (that stretched the actual screenshot into a
   landscape crop, no longer reading as a tablet-portrait screenshot), this
   halves the card's WIDTH instead — --ps-card-aspect (PHP, the screen's true
   portrait proportions) is untouched, so the thumbnail is still a correctly
   proportioned tablet-portrait crop, just a smaller one; the height comes
   down for free at that same fixed ratio. Same specificity as the
   count-specific rules above (2 classes each) but placed after them, so it
   wins regardless of count. */
.portfolioScreensBlock--deviceTablet .portfolioScreensCard
{
	/* Fluid, not a flat 110px — the device itself already scales smoothly
	   with the viewport (its own vw-based height clamps), so a fixed card
	   size read as static/disconnected next to it, holding steady while
	   everything else resized. 110px is still the calibration point
	   right_edge_fraction was tuned against (block-portfolio-screens.php's
	   $card_shrink_factor) — clamp()'s max here, so anything above ~1571px
	   viewport renders identically to before this change; narrower
	   viewports scale the card down toward the 70px floor instead of
	   staying pinned. */
	flex-basis: clamp(70px, 7vw, 110px);
	/* Without this, the base .portfolioScreensCard rule's flex-grow: 1 lets
	   a Tablet card stretch well past its own basis to fill a wide row
	   (alignfull's own flex-grow: 0 override already prevented this there,
	   but a Contained-width instance has no such override) — inflating the
	   rendered card size (and therefore height, at a fixed aspect ratio)
	   past what the right_edge_fraction halving in
	   block-portfolio-screens.php was calibrated against, which is what let
	   the mockup balloon to several times its intended size for Contained
	   Tablet instances. */
	flex-grow: 0;
}

/* Mobile: same reasoning/technique as Tablet above (block-portfolio-screens.php's
   'mobile' === $device wrapper class), just a bigger basis (150px vs 110px) —
   Mobile's screen is a more extreme portrait than Tablet's, so a flat 110px
   would have overshot well past the "about half the phone's height" target;
   150px lands there instead. The matching right_edge_fraction scale factor
   for this exact px value lives in block-portfolio-screens.php's
   $card_shrink_factor. */
.portfolioScreensBlock--deviceMobile .portfolioScreensCard
{
	/* Fluid, same reasoning as Tablet above — 150px stays the clamp() max
	   (the exact point $card_shrink_factor in block-portfolio-screens.php
	   was calibrated against), scaling down toward a 90px floor on
	   narrower viewports instead of holding a fixed size next to a device
	   that's visibly resizing around it. */
	flex-basis: clamp(90px, 9vw, 150px);
	flex-grow: 0;
}

/* Full-width alignment: drop the hand-tuned max-width caps above so the
   grid can spread across the wider row from .alignfull .portfolioScreensLayout,
   and stop cards from flex-growing past their own basis. Growth is what we
   don't want here — a wider row with flex-grow still on would inflate each
   card's rendered height (fixed aspect-ratio), which balanceDevice() would
   then read back as a taller device. Fixed-size cards instead just fit more
   per row as the row widens, wrapping to fewer rows without changing size —
   which keeps the device's computed height exactly where it already was. */
.portfolioScreensBlock.alignfull .portfolioScreensGrid
{
	max-width: none;
}

.portfolioScreensBlock.alignfull .portfolioScreensCard
{
	flex-grow: 0;
}

.portfolioScreensCardImage
{
	position: relative;
	display: block;
	/* Tablet/Mobile get their own device's screen proportions
	   (--ps-card-aspect, set in PHP from the corner-pin quad); Desktop keeps
	   this 4:3 default. */
	aspect-ratio: var(--ps-card-aspect, 4 / 3);
	overflow: hidden;
	/* Transparent, not a fixed white — object-fit:cover already fills the
	   box completely so this never shows within a card, but it means the
	   card never introduces a color of its own between screens either;
	   whatever's behind (the block's own background) reads through. */
	background: var(--ps-card-bg, transparent);
	border-radius: var(--ps-card-radius, 16px);
	border: var(--ps-card-border-width, 0) solid var(--ps-card-border-color, transparent);
	box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
	transition: box-shadow 0.2s ease, transform 0.2s ease;
}

.portfolioScreensCardImage IMG
{
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center top;
	transition: filter 0.2s ease, transform 0.3s ease;
}

.portfolioScreensCard:hover .portfolioScreensCardImage
{
	box-shadow: 0 10px 28px rgba(0, 0, 0, 0.14);
}

/* No background wash on hover — the shadow lift above plus this scale is
   the whole affordance, so the card's own imagery stays undimmed and
   readable (dimming is reserved for is-active, below, so the two states
   stay visually distinct from each other). */
.portfolioScreensCard:hover .portfolioScreensCardImage IMG
{
	transform: scale(1.04);
}

.portfolioScreensCard:focus-visible .portfolioScreensCardImage
{
	outline: 2px solid var(--colA, currentColor);
	outline-offset: 2px;
}

/* Active state: dim + soften the thumbnail itself via filter, rather than
   a separate ::after rectangle stacked at inset:0 — that overlay ignored
   the card's own border-width/radius, sitting flush against the border
   with no breathing room and reading as a layout glitch. A filter on the
   image respects the existing border and radius automatically, with
   nothing extra to align.
   The scale(1.08) matters here, not just polish — blur() softens the
   image's edges by sampling past its own actual pixels, and with nothing
   there to sample, the edge reveals whatever's behind it: the card's own
   --ps-card-bg color. Scaling the (still object-fit:cover, still
   overflow:hidden-clipped) image up pushes that blurred edge outside the
   visible box entirely, so no background color ever shows through. */
.portfolioScreensCard.is-active .portfolioScreensCardImage IMG
{
	/* Design Tokens → Web Portfolio Screens → Selected Screen Effect
	   (--ps-active-dim/--ps-active-blur/--ps-active-desaturation/
	   --ps-active-scale, inc/portfolio-screens-tokens.php) — literal
	   fallbacks here are this rule's own original hand-picked values.
	   Desaturation defaults to 0 (off) — brightness/blur alone read as a
	   lighting change rather than "inactive"; grayscale is what actually
	   sells the inactive/not-selected read, dial it in alongside a lighter
	   Dim if brightness(0.6) alone still looks too dark/moody. */
	filter: brightness(var(--ps-active-dim, 0.6)) grayscale(var(--ps-active-desaturation, 0)) blur(var(--ps-active-blur, 1.5px));
	transform: scale(var(--ps-active-scale, 1.08));
}

.portfolioScreensCardTitle
{
	text-align: center;
	font-family: var(--ps-title-family, inherit);
	font-weight: var(--ps-title-weight, 500);
	font-style: var(--ps-title-style, normal);
	font-size: var(--ps-title-size, 0.95rem);
	line-height: var(--ps-title-line-height, 1.3);
	letter-spacing: var(--ps-title-letter-spacing, normal);
	text-transform: var(--ps-title-text-transform, none);
	/* inherit (black body text) was the fallback, but a Title Type Role
	   with no color set makes --ps-title-color resolve to nothing at all
	   (Type Roles only emit --text-role-{slug}-color when one is actually
	   configured — see shew_ps_emit_title_type_role_css() in
	   inc/portfolio-screens-tokens.php) rather than falling back to white
	   the way the design intends for a caption sitting on the block's own
	   (often colored) background. */
	color: var(--ps-title-color, #fff);
	/* Shrink-wraps to the card's width but never below its longest word,
	   centered — so a word wider than the card overflows evenly on both
	   sides instead of resizing the card (see min-width: 0 above). */
	align-self: center;
}

/* Selected screen — a pill filled with the Screen Title Color, so the
   selection never rests on text color alone (a white title vanished on a
   light row). Text is the Selected Screen Title Color, which
   shew_ps_title_contrast_style() (inc/portfolio-screens-tokens.php) swaps
   per block for black/white when it wouldn't read on that fill. The
   negative margin cancels the padding so the swap doesn't shift layout. */
.portfolioScreensCardTitle
{
	border-radius: 999px;
	transition: background-color 0.2s ease, color 0.2s ease;
}

.portfolioScreensCard.is-active .portfolioScreensCardTitle
{
	color: var(--ps-active-title-color, #1f1f1f);
	background-color: var(--ps-title-color, #fff);
	padding: 0.1em 0.6em;
	margin: -0.1em -0.6em;
}

/* Tablet tier: keeps the side-by-side, JS-balanced treatment (each device's
   own real aspect ratio still drives its size — see --ps-mockup-aspect-w/h),
   but tightens the split so the device stays visibly dominant over the
   thumbnail cluster at these narrower widths instead of the two nearly
   matching, which is what the untouched desktop ratio drifts toward as the
   row narrows. */
@media only screen and (max-width: 1024px) and (min-width: 641px)
{
	/* Both selectors needed — see the same-shaped comment in the ≤640px
	   block below for why .alignfull's own unconditional (higher-specificity)
	   grid-template-columns otherwise keeps winning over this tier's tuned
	   split. */
	.portfolioScreensLayout,
	.portfolioScreensBlock.alignfull .portfolioScreensLayout
	{
		grid-template-columns: minmax(220px, 1.7fr) minmax(170px, 1fr);
		gap: clamp(1.5rem, 4vw, 2.5rem);
	}

	/* 460px, not narrower: the count-3/4 cards below keep their 220px
	   flex-basis at this tier, and two of those plus the gap need ~456px to
	   stay on one row — anything tighter collapses the 2x2 grid into a
	   single tall column, which then feeds an inflated height into
	   balanceDevice()'s JS (it sizes the device off the grid's own rendered
	   height) and balloons the mockup along with it. Both selectors needed,
	   same reason as .portfolioScreensLayout above. */
	.portfolioScreensGrid,
	.portfolioScreensBlock.alignfull .portfolioScreensGrid
	{
		max-width: 460px;
		gap: 1rem;
	}
}

@media only screen and (max-width: 640px)
{
	/* Both selectors needed: .alignfull's own grid-template-columns override
	   (above, outside any media query) is 3 class selectors deep vs. this
	   rule's 1, so without repeating it here specifically, the unconditional
	   alignfull rule keeps winning the cascade even under this breakpoint —
	   an alignfull instance never reached the stacked mobile layout below,
	   its two desktop tracks just collapsing toward their px floors (220+280
	   over a ~350px mobile width) instead. */
	.portfolioScreensLayout,
	.portfolioScreensBlock.alignfull .portfolioScreensLayout
	{
		grid-template-columns: 1fr;
	}

	/* Stacked layout: drop the absolute-positioned/overflow-the-background
	   treatment (that's a side-by-side effect) and go back to a plain,
	   normal-flow, width-driven device, centered above the grid.
	   portfolioScreens.js's balanceDevice() detects this breakpoint and
	   clears its inline height override so these rules apply. */
	.psMockupCol
	{
		position: static;
		width: 100%;
		display: flex;
		justify-content: center;
	}

	/* Both selectors needed — same specificity gap as .portfolioScreensLayout
	   above: .alignfull's own unconditional height: clamp(...) override
	   (higher up in this file, 2 classes deep) otherwise keeps winning over
	   this breakpoint's plain height: auto (1 class), so an alignfull
	   instance's device never actually dropped to its stacked-layout size at
	   mobile widths — it stayed pinned to that clamp's floor (320px+, sized
	   for the side-by-side desktop treatment) with width:auto still deriving
	   from THAT height via aspect-ratio, ballooning the whole illustration
	   far past the viewport instead of shrinking to fit above the cards. */
	.psMockupStage,
	.portfolioScreensBlock.alignfull .psMockupStage
	{
		/* NOT position:static — .psMockupLineart/.psMockupScreenshotMask/
		   .psMockupClipDefs inside are position:absolute with width/height:
		   100%, sized against their nearest positioned ancestor. Dropping
		   this stage to static removes it as that ancestor entirely, so
		   those children fall through to .psMockupCol instead (still full
		   row width here) and render far larger than this rule's own
		   width:min(100%,300px) — the illustration ballooning to overlap the
		   card grid below it. position:relative with no offsets keeps the
		   same in-flow sizing/centering while staying their containing
		   block. */
		position: relative;
		/* The base (desktop) rule's top: 50% — paired there with
		   transform: translateY(-50%) for absolute centering — has to be
		   reset explicitly here too. position:relative still honors top/left
		   offsets (just relative to its own normal-flow position rather than
		   the viewport), so leaving top: 50% in place while only resetting
		   transform/position pushed the whole device down by half its own
		   height, straight into the card grid below it — the mobile
		   overlap bug. */
		top: auto;
		transform: none;
		height: auto;
		width: min(100%, 300px);
	}

	/* The device always comes first when stacked, whatever Device Position
	   says — Last's `order` swap (top of this file) is a side-by-side
	   arrangement only. Same 2-class specificity, later in the file, wins. */
	.portfolioScreensBlock--deviceLast .psMockupCol,
	.portfolioScreensBlock--deviceLast .portfolioScreensGrid
	{
		order: 0;
	}

	/* Full screen width even on a Width: Wide page, whose content column
	   (SDstandards.css's --wideWidth, min(70vw, …)) otherwise squeezes the
	   device and card rows into 70% of a phone screen. Matches that rule's
	   specificity (1 id + 4 classes) plus one. */
	#SD .entry-content > .portfolioScreensBlock:not(.alignfull):not(.SDblock):not(.alignwide)
	{
		/* The block's own side padding stays inside that 100%. */
		box-sizing: border-box;
		width: 100%;
		max-width: none;
	}

	/* No orphans, one card size: every card is exactly 1 / --ps-sm-per-row
	   of the full row (block-portfolio-screens.php — the fullest row's count,
	   up to 3, rows split as evenly as possible), so the fullest rows span
	   the whole width and a shorter last row centers at the same card size
	   (5 → 3 + centered 2). Replaces every per-count/per-device flex-basis
	   above, which at phone widths could strand a single card on its own
	   row. Center has its own swipeable row (Device Position: Center
	   section) and is left out. */
	.portfolioScreensBlock:not(.portfolioScreensBlock--deviceCenter) .portfolioScreensGrid
	{
		--ps-sm-gap: 0.75rem;
		width: 100%;
		max-width: none;
		gap: 1rem var(--ps-sm-gap);
		justify-content: center;
		justify-self: stretch;
	}

	.portfolioScreensBlock:not(.portfolioScreensBlock--deviceCenter) .portfolioScreensCard
	{
		flex: 0 0 calc((100% - (var(--ps-sm-per-row, 1) - 1) * var(--ps-sm-gap)) / var(--ps-sm-per-row, 1));
		min-width: 0;
	}
}

/* Mobile, side-by-side tiers: sized like Desktop rather than off the cards
   (portfolioScreens.js's balanceDevice() treats 'mobile' like 'desktop') —
   the phone gets its own height clamp, and the cards sit in one row (wrapping
   after 4) at a larger size, so the pair reads with the same proportions as
   the Desktop monitor + 2x2 grid. The :not() keeps Center's ring layout out
   of this, and the 4-class depth beats the .alignfull/tablet-tier overrides
   above. */
@media only screen and (min-width: 641px)
{
	.portfolioScreensBlock.portfolioScreensBlock--deviceMobile:not(.portfolioScreensBlock--deviceCenter)
	{
		--ps-mobile-card-w: clamp(100px, 9.5vw, 150px);
	}

	.portfolioScreensBlock.portfolioScreensBlock--deviceMobile:not(.portfolioScreensBlock--deviceCenter) .psMockupStage
	{
		height: clamp(320px, 42vw, 660px);
	}

	.portfolioScreensBlock.portfolioScreensBlock--deviceMobile:not(.portfolioScreensBlock--deviceCenter) .portfolioScreensGrid
	{
		max-width: calc(4 * var(--ps-mobile-card-w) + 3 * var(--ps-card-gap, 1.5rem));
		gap: var(--ps-card-gap, 1.5rem);
	}

	.portfolioScreensBlock.portfolioScreensBlock--deviceMobile:not(.portfolioScreensBlock--deviceCenter) .portfolioScreensCard
	{
		flex: 0 0 var(--ps-mobile-card-w);
	}
}

/* ── Device Position: Center (radial) ─────────────────────────────────────
   block-portfolio-screens.php's .portfolioScreensBlock--deviceCenter. Above
   640px, portfolioScreens.js's initCenter() positions everything inline: the
   device (.psMockupCol) in the middle of .portfolioScreensLayout and each
   .portfolioScreensCard on an ellipse around it, then sets the layout's
   height to fit. The cards keep every Design Token of the grid layouts
   (radius, border, title Type Role, Selected Screen Effect) — only where
   they sit changes. At ≤640px it's pure CSS: device → Previous/Next →
   swipeable card row → Body text.

   Selectors below that need to beat the existing .alignfull / breakpoint
   rules above are written three classes deep
   (.portfolioScreensBlock.portfolioScreensBlock--deviceCenter …) and placed
   after them, so they win on both specificity and order. */

.psCenterWrap
{
	position: relative;
	z-index: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
}

.portfolioScreensBlock.portfolioScreensBlock--deviceCenter .portfolioScreensLayout
{
	width: 100%;
}

/* Nothing faces a side in the ring, so the line art stays un-mirrored —
   matches PHP's $skip_mirror for the screenshot/clip geometry. */
.portfolioScreensBlock--deviceCenter .psMockupLineart
{
	transform: none;
}

@media only screen and (min-width: 641px)
{
	.portfolioScreensBlock.portfolioScreensBlock--deviceCenter .portfolioScreensLayout
	{
		display: block;
		position: relative;
		/* Pre-JS placeholder height; initCenter() replaces it inline. */
		min-height: 0;
		height: clamp(420px, 48vw, 760px);
	}

	/* Pre-JS fallback: centred. initCenter() sets left/top/width/height
	   inline and drops the transform. */
	.portfolioScreensBlock.portfolioScreensBlock--deviceCenter .psMockupCol
	{
		position: absolute;
		left: 50%;
		top: 50%;
		width: min(460px, 38%);
		aspect-ratio: 5 / 4;
		transform: translate(-50%, -50%);
		z-index: 1;
	}

	/* The stage simply fills the column initCenter() sized — no clamp, no
	   vertical centring transform (both belong to the side-by-side layouts). */
	.portfolioScreensBlock.portfolioScreensBlock--deviceCenter .psMockupStage
	{
		position: absolute;
		inset: 0;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		transform: none;
	}

	/* The card container becomes a full-size overlay the cards are positioned
	   within. pointer-events pass through it so the device's own zoom trigger
	   underneath stays clickable; the cards re-enable them. */
	.portfolioScreensBlock.portfolioScreensBlock--deviceCenter .portfolioScreensGrid
	{
		position: absolute;
		inset: 0;
		display: block;
		width: auto;
		max-width: none;
		z-index: 2;
		pointer-events: none;
	}

	.portfolioScreensBlock.portfolioScreensBlock--deviceCenter .portfolioScreensCard
	{
		position: absolute;
		flex: none;
		pointer-events: auto;
	}

	/* Tablet/Mobile ring cards can be as narrow as 40-80px, where titles
	   otherwise broke mid-word ("Calculato / r"). Let the caption run wider
	   than its card, wrapping only between words — initCenter() measures
	   this width and spaces the ring by whichever is wider, card or caption. */
	.portfolioScreensBlock.portfolioScreensBlock--deviceCenter .portfolioScreensCardTitle
	{
		align-self: center;
		width: max-content;
		max-width: max(100%, 8.5em);
		overflow-wrap: normal;
		word-break: normal;
		hyphens: manual;
	}

	/* Dashed ellipse behind the ring, inserted by initCenter(). */
	.psRingGuide
	{
		position: absolute;
		inset: 0;
		width: 100%;
		height: 100%;
		overflow: visible;
		pointer-events: none;
		z-index: 0;
	}

	.psRingGuide ellipse
	{
		fill: none;
		stroke: var(--ps-title-color, currentColor);
		stroke-opacity: 0.3;
		stroke-width: 1.25;
		stroke-dasharray: 2 6;
		stroke-linecap: round;
	}
}

/* Desktop tier: the ring breaks out of the block's own width (Content or
   Wide alignment alike) to span the viewport, minus a gutter and capped at
   1700px, so initCenter()'s flat oval (flatRing() in portfolioScreens.js)
   has room to spread sideways instead of growing tall. The background
   already bleeds full-viewport (.portfolioScreensBlockBg). Centred with
   negative side margins rather than a transform so nothing inside picks up a
   new containing block. */
@media only screen and (min-width: 1025px)
{
	.portfolioScreensBlock.portfolioScreensBlock--deviceCenter .portfolioScreensLayout
	{
		--ps-center-ring-w: min(calc(100vw - 4rem), 1700px);
		width: var(--ps-center-ring-w);
		max-width: none;
		margin-left: calc((100% - var(--ps-center-ring-w)) / 2);
		margin-right: calc((100% - var(--ps-center-ring-w)) / 2);
	}
}

/* ── Previous / Next ── */
.psCenterNav
{
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: 0.875rem;
	margin-top: clamp(1.25rem, 3vw, 2rem);
	color: var(--ps-title-color, #fff);
}

/* Real <button>s, so GeneratePress's sitewide button/button:hover
   background has to be overridden explicitly (same issue as
   .portfolioScreensCard above). */
.psCenterNavBtn,
.psCenterNavBtn:hover,
.psCenterNavBtn:focus
{
	margin: 0;
	font: inherit;
	font-size: 0.95rem;
	font-weight: 600;
	line-height: 1.2;
	color: inherit;
	background: transparent;
	border: 1.5px solid currentColor;
	border-radius: 999px;
	padding: 0.55rem 1.15rem;
	min-height: 42px;
	cursor: pointer;
	transition: background-color 0.15s ease;
}

.psCenterNavBtn:hover
{
	background: color-mix(in srgb, currentColor 14%, transparent);
}

.psCenterNavBtn:focus-visible
{
	outline: 2px solid currentColor;
	outline-offset: 3px;
}

.psCenterNavCount
{
	min-width: 4.5ch;
	text-align: center;
	font-variant-numeric: tabular-nums;
	opacity: 0.75;
}

.psCenterNavCurrent
{
	font-weight: 700;
}

/* Position dots — stacked phones only (≤640px section below). */
.psCenterDots
{
	display: none;
}

/* ── Body text ──
   Every body sits in the same grid cell; only .is-active is visible, so the
   cell is always as tall as the longest one and switching never shifts the
   page. */
.psCenterBodies
{
	display: grid;
	width: 100%;
	max-width: 38rem;
	margin-top: clamp(1rem, 2.5vw, 1.5rem);
	text-align: center;
	color: var(--ps-title-color, #fff);
}

.psCenterBody
{
	grid-area: 1 / 1;
	opacity: 0;
	visibility: hidden;
	transition: opacity var(--ps-swap-duration, 250ms) ease, visibility 0s linear var(--ps-swap-duration, 250ms);
}

.psCenterBody.is-active
{
	opacity: 1;
	visibility: visible;
	transition: opacity var(--ps-swap-duration, 250ms) ease;
}

.psCenterBodyTitle
{
	margin: 0 0 0.35em;
	font-size: 1.15rem;
	font-weight: 700;
	line-height: 1.3;
}

.psCenterBodyText
{
	margin: 0;
	line-height: 1.6;
}

/* Swipeable-row card widths per device, shaped by --ps-card-aspect. */
.portfolioScreensBlock--deviceCenter
{
	--ps-strip-card-w: 132px;
}

.portfolioScreensBlock--deviceCenter.portfolioScreensBlock--deviceTablet
{
	--ps-strip-card-w: 92px;
}

.portfolioScreensBlock--deviceCenter.portfolioScreensBlock--deviceMobile
{
	--ps-strip-card-w: 66px;
}

@media only screen and (max-width: 640px)
{
	/* The layout's two children (device, card row) join .psCenterWrap's
	   column directly, so `order` can slot Previous/Next between them. */
	.portfolioScreensBlock.portfolioScreensBlock--deviceCenter .portfolioScreensLayout
	{
		display: contents;
	}

	.portfolioScreensBlock--deviceCenter .psMockupCol
	{
		order: 1;
		touch-action: pan-y;
	}

	/* Card row sits directly under the device, with the (compact) arrows +
	   dots after it, so device and cards read as one control. */
	.portfolioScreensBlock.portfolioScreensBlock--deviceCenter .portfolioScreensGrid
	{
		order: 2;
		display: flex;
		flex-wrap: nowrap;
		justify-content: flex-start;
		align-items: flex-start;
		width: 100%;
		max-width: none;
		gap: 0.875rem;
		margin-top: 1rem;
		/* Inline padding lets the first and last cards scroll to centre too,
		   so a neighbour always peeks in at one edge or both. */
		padding: 0.5rem calc(50% - var(--ps-strip-card-w) / 2) 0.25rem;
		box-sizing: border-box;
		overflow-x: auto;
		scroll-snap-type: x mandatory;
		-webkit-overflow-scrolling: touch;
		/* The cut-off neighbours + edge fade say "swipe"; no bar. */
		scrollbar-width: none;
		-webkit-mask-image: linear-gradient(to right, transparent 0, #000 12%, #000 88%, transparent 100%);
		mask-image: linear-gradient(to right, transparent 0, #000 12%, #000 88%, transparent 100%);
	}

	.portfolioScreensBlock.portfolioScreensBlock--deviceCenter .portfolioScreensGrid::-webkit-scrollbar
	{
		display: none;
	}

	.portfolioScreensBlock.portfolioScreensBlock--deviceCenter .portfolioScreensCard
	{
		flex: 0 0 var(--ps-strip-card-w);
		scroll-snap-align: center;
	}

	/* Bigger cards than the old fixed strip widths: the centred one is
	   clearly the subject, with part of each neighbour showing at the edges. */
	.portfolioScreensBlock--deviceCenter
	{
		--ps-strip-card-w: 44vw;
	}

	.portfolioScreensBlock--deviceCenter.portfolioScreensBlock--deviceTablet
	{
		--ps-strip-card-w: 34vw;
	}

	.portfolioScreensBlock--deviceCenter.portfolioScreensBlock--deviceMobile
	{
		--ps-strip-card-w: 26vw;
	}

	/* Active card: the desktop ring's dim + blur means "already in the
	   device", but in a single row it just reads as broken/loading. Here the
	   active card stays full-color and gets a ring in the title pill's
	   color instead; the others step back slightly. */
	.portfolioScreensBlock--deviceCenter .portfolioScreensCard .portfolioScreensCardImage IMG
	{
		transition: opacity 0.2s ease, transform 0.3s ease;
	}

	.portfolioScreensBlock--deviceCenter .portfolioScreensCard:not(.is-active) .portfolioScreensCardImage IMG
	{
		opacity: 0.7;
	}

	.portfolioScreensBlock--deviceCenter .portfolioScreensCard.is-active .portfolioScreensCardImage IMG
	{
		filter: none;
		transform: none;
		opacity: 1;
	}

	.portfolioScreensBlock--deviceCenter .portfolioScreensCard.is-active .portfolioScreensCardImage
	{
		box-shadow: 0 0 0 3px var(--ps-title-color, #fff), 0 10px 28px rgba(0, 0, 0, 0.18);
	}

	/* Compact arrows either side of the dots. */
	.portfolioScreensBlock--deviceCenter .psCenterNav
	{
		order: 3;
		flex-wrap: nowrap;
		gap: 0.5rem;
		margin-top: 0.75rem;
	}

	.portfolioScreensBlock--deviceCenter .psCenterNavLabel
	{
		display: none;
	}

	/* #SD + !important: has to beat global.min.css's
	   `#SD .button.btn-secondary { padding: … !important }`, which
	   shew_ps_nav_button_class() opts these buttons into. */
	#SD .portfolioScreensBlock--deviceCenter .psCenterNavBtn.psCenterNavBtn
	{
		flex: 0 0 auto;
		display: inline-flex;
		align-items: center;
		justify-content: center;
		width: 44px;
		height: 44px;
		min-height: 44px;
		padding: 0 !important;
		font-size: 1.1rem;
	}

	.portfolioScreensBlock--deviceCenter .psCenterNav--hasDots .psCenterNavCount
	{
		display: none;
	}

	.portfolioScreensBlock--deviceCenter .psCenterDots
	{
		display: flex;
		flex-wrap: wrap;
		justify-content: center;
		align-items: center;
	}

	/* 24px tap target, 8px visible dot. */
	.portfolioScreensBlock--deviceCenter .psCenterDot,
	.portfolioScreensBlock--deviceCenter .psCenterDot:hover,
	.portfolioScreensBlock--deviceCenter .psCenterDot:focus
	{
		position: relative;
		width: 24px;
		height: 24px;
		min-height: 0;
		margin: 0;
		padding: 0;
		border: 0;
		background: transparent;
		color: inherit;
		cursor: pointer;
	}

	.portfolioScreensBlock--deviceCenter .psCenterDot::before
	{
		content: "";
		position: absolute;
		top: 50%;
		left: 50%;
		width: 8px;
		height: 8px;
		margin: -4px 0 0 -4px;
		border-radius: 50%;
		border: 1.5px solid currentColor;
		box-sizing: border-box;
		transition: background-color 0.15s ease, transform 0.15s ease;
	}

	.portfolioScreensBlock--deviceCenter .psCenterDot.is-active::before
	{
		background-color: currentColor;
		transform: scale(1.25);
	}

	.portfolioScreensBlock--deviceCenter .psCenterDot:focus-visible
	{
		outline: 2px solid currentColor;
		outline-offset: -2px;
		border-radius: 50%;
	}

	.portfolioScreensBlock--deviceCenter .psCenterBodies
	{
		order: 4;
		text-align: left;
	}
}

/* ── Device: Desktop + Mobile ──────────────────────────────────────────────
   block-portfolio-screens.php's .portfolioScreensBlock--deviceDesktopMobile.
   The monitor is the normal Desktop stage; the phone is a second stage
   (.psMockupStage--phone) nested inside it, placed in % of the monitor's own
   box via --ps-dm-* (set on the monitor stage from shew_ps_dm_layout(),
   inc/helpers/portfolio-screens-desktop-mobile.php) so it scales with the
   monitor at every size. portfolioScreens.js's balanceDevice() shrinks the
   monitor so monitor + phone together fill the Desktop footprint. The
   4-class selectors beat every generic .psMockupStage rule above
   (.alignfull, the ≤640px stacked reset). */
.portfolioScreensBlock.portfolioScreensBlock--deviceDesktopMobile .psMockupStage.psMockupStage--phone
{
	position: absolute;
	left: var(--ps-dm-left);
	top: var(--ps-dm-top);
	height: var(--ps-dm-height);
	width: auto;
	transform: none;
	/* Decorative duplicate of the active screen — clicks fall through to the
	   monitor (zoom trigger) and cards. */
	pointer-events: none;
}

/* Solid fill under the phone's screenshot, so the monitor's lines don't show
   through the phone and hand. Mirrors with the line art. --ps-dm-backing is
   the block's Background Color (block-portfolio-screens.php); white when none
   is set. */
.psMockupBacking
{
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	pointer-events: none;
	fill: var(--ps-dm-backing, #fff);
	transform: scaleX(-1);
}

.portfolioScreensBlock--deviceLast .psMockupBacking,
.portfolioScreensBlock--deviceCenter .psMockupBacking
{
	transform: none;
}

@media only screen and (max-width: 640px)
{
	/* Stacked: monitor + phone together take the width the monitor alone
	   would (a bit wider than plain Desktop's 300px, since the monitor is
	   now sharing it), and shift as one unit so the pair — not just the
	   monitor — is centered. */
	.portfolioScreensBlock.portfolioScreensBlock--deviceDesktopMobile .psMockupStage:not(.psMockupStage--phone)
	{
		width: calc(min(100%, 360px) / (1 + var(--ps-dm-reach-l-sm, 0) + var(--ps-dm-reach-r-sm, 0)));
		translate: calc((var(--ps-dm-reach-l-sm, 0) - var(--ps-dm-reach-r-sm, 0)) / 2 * 100%) 0;
	}

	.portfolioScreensBlock.portfolioScreensBlock--deviceDesktopMobile .psMockupStage.psMockupStage--phone
	{
		left: var(--ps-dm-left-sm);
		top: var(--ps-dm-top-sm);
		height: var(--ps-dm-height-sm);
		translate: none;
	}
}

/* Desktop + Mobile: monitor and phone are always drawn mirrored (phone and
   hand on the monitor's right), overriding the Last / Center un-mirror rules
   above — block-portfolio-screens.php keeps $skip_mirror off for this option
   and shew_ps_dm_layout() places the phone on that assumption. */
.portfolioScreensBlock.portfolioScreensBlock--deviceDesktopMobile .psMockupLineart,
.portfolioScreensBlock.portfolioScreensBlock--deviceDesktopMobile .psMockupBacking
{
	transform: scaleX(-1);
}
