/**
 * borrego/board-directory — flip cards for the Board of Directors.
 *
 * Declared as the block's `style` in block.json, so WP_Block::render() enqueues
 * it only on pages that actually contain the block. That is why none of this
 * lives in style.css (global, every page) or art-direction.css (loaded only on
 * Home and About).
 *
 * READ THIS BEFORE TOUCHING .bb-board-card__inner.
 * It is the only element carrying `transform-style: preserve-3d`, and the spec
 * flattens that to `flat` if the SAME element also has any of: overflow other
 * than visible, clip-path, mask-image, filter, opacity below 1, mix-blend-mode,
 * contain: paint, or isolation: isolate. This theme's decorative helpers set
 * three of those — .bb-shape-blob-card sets overflow, .bb-shape-wave-* set
 * mask-image, and style.css blanket-applies isolation: isolate to all of them.
 * Putting any bb-shape-* class on the inner, or on an ancestor of the grid,
 * silently kills the flip and leaves the back face showing through mirrored.
 * Clipping belongs on the faces; decoration belongs on a sibling.
 */

/* --------------------------------------------------------------------------
   Intro chrome (rendered by patterns/board-directory.php)
   -------------------------------------------------------------------------- */

/* The intro lives in the pattern, but its CSS lives here because this file is
   the only stylesheet the board page loads. The alternative was style.css,
   which every page on the site pays for, to serve one page.
   .bb-botanical and .bb-section-brush have base rules in art-direction.css.
   When the rules below were written that file was gated to Home and About, so
   the handful this page needs were restated; the classic-theme port removed
   the gate (see inc/enqueue.php), so art-direction.css now loads here too and
   these restatements SHADOW the originals rather than substitute for them.
   Keep them in sync if the originals change. */

.bb-board-intro {
	background-color: var( --wp--preset--color--base );
	overflow-x: clip;
	padding-block: var( --wp--preset--spacing--70 ) var( --wp--preset--spacing--60 );
	position: relative;
}

.bb-board-intro__inner {
	margin-inline: auto;
	max-width: var( --wp--style--global--content-size, 760px );
	padding-inline: var( --wp--preset--spacing--40 );
	position: relative;
	z-index: 2;
}

.bb-board-intro__eyebrow {
	color: var( --wp--preset--color--terracotta-text );
	margin: 0 0 var( --wp--preset--spacing--20 );
}

.bb-board-intro__title {
	color: var( --wp--preset--color--forest );
	font-family: var( --wp--preset--font-family--display );
	font-size: var( --wp--preset--font-size--xx-large );
	font-weight: 500;
	line-height: 1.1;
	margin: 0 0 var( --wp--preset--spacing--20 );
}

.bb-board-intro__lead {
	color: var( --wp--preset--color--contrast );
	margin: 0;
	max-width: 46rem;
}

.bb-board-intro .bb-section-brush {
	color: var( --wp--preset--color--terracotta );
	display: block;
	height: 0.55rem;
	margin-bottom: 1rem;
	width: 7.5rem;
}

.bb-board-intro .bb-botanical {
	color: var( --wp--preset--color--gold );
	max-width: 100%;
	pointer-events: none;
	position: absolute;
	user-select: none;
	z-index: 1;
}

/* bottom: 0, not -6%: the sprig that was here tapered to a point, so the
   overhang cost nothing. A beavertail's lower pad is its widest part, and it
   hung across into .bb-board, which has a different background. Width and
   opacity follow the same retune as the art-direction.css slots — see the note
   above .bb-botanical--programs there. */
.bb-board-intro .bb-botanical--board {
	bottom: 0;
	opacity: 0.6;
	right: 2%;
	width: clamp( 3.5rem, 7.5vw, 7rem );
}

@media ( max-width: 899px ) {
	.bb-board-intro .bb-botanical--board { display: none; }
}

/* --------------------------------------------------------------------------
   Section and grid
   -------------------------------------------------------------------------- */

.bb-board {
	background-color: var( --wp--preset--color--base-alt );
	padding-block: var( --wp--preset--spacing--60 );
	position: relative;
}

.bb-board__grid {
	display: grid;
	gap: var( --wp--preset--spacing--40 );
	grid-template-columns: 1fr;
	margin-inline: auto;
	max-width: var( --wp--style--global--wide-size, 1200px );
	padding-inline: var( --wp--preset--spacing--40 );
}

@media ( min-width: 640px ) {
	.bb-board__grid { grid-template-columns: repeat( 2, 1fr ); }
}

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

@media ( min-width: 1280px ) {
	.bb-board__grid { grid-template-columns: repeat( 4, 1fr ); }
}

.bb-board__empty,
.bb-board__notice {
	margin-inline: auto;
	max-width: var( --wp--style--global--content-size, 760px );
	padding-inline: var( --wp--preset--spacing--40 );
	text-align: center;
}

.bb-board__notice {
	background-color: var( --wp--preset--color--base );
	border-left: 3px solid var( --wp--preset--color--terracotta-text );
	color: var( --wp--preset--color--contrast );
	font-size: var( --wp--preset--font-size--small );
	margin-block-end: var( --wp--preset--spacing--40 );
	max-width: var( --wp--style--global--wide-size, 1200px );
	padding: var( --wp--preset--spacing--30 ) var( --wp--preset--spacing--40 );
	text-align: start;
}

/* --------------------------------------------------------------------------
   The card, and the 3D rig
   -------------------------------------------------------------------------- */

.bb-board-card {
	display: flex;
	perspective: 1600px;
	position: relative;
	transition: transform 0.3s cubic-bezier( 0.2, 0.7, 0.2, 1 );
}

/* The lift is suppressed while this card is open — a flipped card that also
   floats reads as two separate things happening to one object. */
.bb-board-card:hover:not( :has( .is-flipped ) ):not( :has( .bb-board-card__face--back:target ) ) {
	transform: translateY( -6px );
}

.bb-board-card__inner {
	display: flex;
	flex-direction: column;
	position: relative;
	transform-style: preserve-3d;
	-webkit-transform-style: preserve-3d;
	transition: transform 0.7s cubic-bezier( 0.2, 0.8, 0.2, 1 );
	width: 100%;
}

/* TWO MECHANISMS, ONLY ONE ACTIVE AT A TIME.
 * `:target` is the no-JavaScript flip. view.js stamps data-bb-hydrated on the
 * section as soon as it takes over, and every :target rule below is scoped to
 * its absence — so once the store is running, the URL fragment cannot rotate
 * anything. They must not both be live: a fragment that outlived the store's
 * state used to hold a card open that Escape and the close button could not
 * shut, because CSS went on matching something the store had already cleared. */
.bb-board-card__inner.is-flipped,
.bb-board:not( [data-bb-hydrated] ) .bb-board-card__inner:has( .bb-board-card__face--back:target ) {
	transform: rotateY( 180deg );
}

.bb-board-card__face {
	backface-visibility: hidden;
	-webkit-backface-visibility: hidden;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	/* Safari lets absolutely-positioned children of a hidden-backface element
	   bleed through without its own compositing layer. */
	transform: translateZ( 0 );
	transition: box-shadow 0.3s;
	width: 100%;
}

/* The front is in normal flow and therefore defines the card's height; the back
   is absolutely positioned and simply matches it. That is what makes every card
   in a grid row line up without a fixed height. */
.bb-board-card__face--front {
	background-color: var( --wp--preset--color--base );
	border-radius: 6px 26px 6px 26px;
	box-shadow: rgb( 42 42 36 / 0.12 ) 0 12px 30px;
	flex: 1;
	position: relative;
}

/* Mirrored radius, not the same radius. rotateY(180deg) flips the box
   horizontally, so an asymmetric corner set would visibly change the card's
   silhouette halfway through the flip. */
.bb-board-card__face--back {
	background: linear-gradient( 160deg, var( --wp--preset--color--forest-deep ) 0%, var( --wp--preset--color--forest ) 100% );
	border-radius: 26px 6px 26px 6px;
	box-shadow: rgb( 42 42 36 / 0.12 ) 0 12px 30px;
	inset: 0;
	position: absolute;
	transform: rotateY( 180deg ) translateZ( 0 );
}

.bb-board-card:hover:not( :has( .is-flipped ) ):not( :has( .bb-board-card__face--back:target ) ) .bb-board-card__face {
	box-shadow: rgb( 42 42 36 / 0.2 ) 0 22px 46px;
}

/* backface-visibility hides pixels but leaves the content in the accessibility
   tree and the tab order. `visibility` hides both — and unlike `inert`, CSS can
   toggle it, so the no-JS :target path gets correct semantics for free.
 *
 * THE DELAY IS ASYMMETRIC, AND THAT IS THE WHOLE TRICK.
 * Hiding is delayed to the midpoint of the rotation, so the outgoing face stays
 * painted while it is still facing the viewer. Revealing is IMMEDIATE, because
 * a `visibility: hidden` element cannot take focus — with a delay on the way in,
 * the script's focus() call lands on a still-hidden panel, silently does
 * nothing, and leaves focus stranded on <body>. Since backface-visibility hides
 * the incoming face's pixels until it rotates around anyway, making it visible
 * at once costs nothing visually.
 *
 * A transition uses the delay declared on the state it is moving TO, so each
 * rule below carries the delay for the direction it represents. */
.bb-board-card__face--front {
	transition: box-shadow 0.3s, visibility 0s linear 0s;
	visibility: visible;
}

.bb-board-card__face--back {
	transition: box-shadow 0.3s, visibility 0s linear 0.35s;
	visibility: hidden;
}

.bb-board-card__inner.is-flipped .bb-board-card__face--back,
.bb-board:not( [data-bb-hydrated] ) .bb-board-card__inner:has( .bb-board-card__face--back:target ) .bb-board-card__face--back {
	transition: box-shadow 0.3s, visibility 0s linear 0s;
	visibility: visible;
}

.bb-board-card__inner.is-flipped .bb-board-card__face--front,
.bb-board:not( [data-bb-hydrated] ) .bb-board-card__inner:has( .bb-board-card__face--back:target ) .bb-board-card__face--front {
	transition: box-shadow 0.3s, visibility 0s linear 0.35s;
	visibility: hidden;
}

/* --------------------------------------------------------------------------
   Front face
   -------------------------------------------------------------------------- */

/* Portraits arrive at whatever shape Airtable holds — of the eleven current
   members, five are LANDSCAPE and one is a 224px square; none matches the
   well. So the well is a fixed SQUARE box, matching the hub crop editor's 1:1
   frame: an uncropped portrait is CONTAINED inside it, letterboxed against the
   forest gradient (object-fit: cover would crop the tops off half the board),
   while a manager-authored grid crop fills it exactly (see --cropped below).
   The fixed ratio also pins layout, so no width/height attributes are needed
   to avoid a layout shift.

   THE 1:1 RATIO IS LOAD-BEARING, not cosmetic. A cropped image uses
   object-fit: fill, which is lossless only because the crop's percentages
   were authored against a square — change this ratio and every cropped
   photo silently distorts. Do not touch it without changing the fit
   strategy. */
.bb-board-card__well {
	aspect-ratio: 1 / 1;
	background: linear-gradient( 135deg, var( --wp--preset--color--forest-deep ) 0%, var( --wp--preset--color--forest ) 100% );
	border-radius: 6px 26px 0 0;
	display: grid;
	flex-shrink: 0;
	overflow: hidden;
	place-items: center;
	position: relative;
	width: 100%;
}

.bb-board-card__photo {
	height: 100%;
	inset: 0;
	object-fit: contain;
	position: absolute;
	width: 100%;

	/* Carrier for the low-quality placeholder, when the feed sends one. Inert
	   until then. `contain` here is not a guess: it fits the blur into the SAME
	   box object-fit fits the photo into, so the two are pixel-aligned by
	   construction and the sharp image simply paints over its own blur. No
	   fade, no observer, no flash — the browser has the data URI the instant it
	   parses the attribute. */
	background-position: center;
	background-repeat: no-repeat;
	background-size: contain;
}

/* A background paints BEHIND replaced content, so an LQIP whose aspect ratio
   ever disagreed with the photo would fringe the letterbox bars permanently
   rather than briefly. Retire it once the real thing has painted. */
.bb-board-card__well.is-loaded .bb-board-card__photo { background-image: none; }

/* A hub-authored square crop: the inline style carries width/height/left/top
   percentages precomputed by the hub, turning the img into an oversized offset
   box that the square well clips. Mirrors .bb-scholar-card__photo--cropped in
   awardees.css. The blur LQIP stays: the crop box has the photo's aspect ratio
   by construction, so at 100% 100% the blur and the pixels remain aligned (and
   the base center/contain background rules become no-ops). The .is-loaded
   retirement above still applies. */
.bb-board-card__photo--cropped {
	inset: auto;                 /* base inset:0 would pin right/bottom against inline left/top */
	max-width: none;
	object-fit: fill;            /* box ratio equals crop ratio by construction; neutralises contain */
	object-position: 0 0;
	background-size: 100% 100%;
}

/* The bust and the monogram share ONE cell rather than stacking into two rows.
   The well is a grid, and until now it held exactly one in-flow item — the
   <img> is absolutely positioned and so is not a grid item at all. Adding the
   bust as a second in-flow child would create two implicit rows and split the
   free space between them, pushing the monogram below centre and resolving the
   bust's height against a half-height track. That misfires precisely on the
   two cards this feature exists for: a member with no photograph, and one
   whose photograph failed. */
.bb-board-card__silhouette,
.bb-board-card__initials {
	grid-area: 1 / 1;
}

/* Heights here are % of the WELL height, which shrank 20% when the well went
   from 4:5 to square — so both silhouette sizes are scaled ×1.25 (42→52.5,
   72→90) to keep the same absolute size the original values were tuned to,
   against the fixed 3.2rem monogram. */
.bb-board-card__silhouette {
	color: var( --wp--preset--color--terracotta-on-dark );
	height: 52.5%;
	opacity: 0.35;
	transition: opacity 200ms ease;
	width: auto;
}

/* Once the portrait has painted the bust would only ever show in the letterbox
   bars, so it goes. Without JavaScript this class never lands and the bust
   stays there: mildly odd, never broken. */
.bb-board-card__well.is-loaded .bb-board-card__silhouette { opacity: 0; }

/* Whenever the MONOGRAM is on screen — a member with no photograph at all, or
   one whose photograph failed — the bust steps back to a watermark. The two
   share a single grid cell, and at their natural sizes they are close enough
   to the same height that they read as one muddy shape rather than as initials
   over a silhouette. Bigger and much fainter separates them: the letters stay
   the thing you read, the bust becomes texture behind them.

   Both selectors use :has() on the WELL deliberately. The bust is a PRECEDING
   sibling of the <img>, so a `~` combinator would silently never match — and
   it has to stay in front, because style.css pairs the image with the monogram
   using an ADJACENT combinator further down.

   Order matters: these come after the .is-loaded rule at equal specificity, so
   a portrait that loads and only later fails still brings the bust back. */
.bb-board-card__well:has( > .bb-board-card__initials:not( .bb-board-card__initials--fallback ) ) .bb-board-card__silhouette,
.bb-board-card__well:has( .bb-board-card__photo.is-hidden ) .bb-board-card__silhouette {
	height: 90%;
	opacity: 0.16;
}

.bb-board-card__initials {
	color: var( --wp--preset--color--terracotta-on-dark );
	font-family: var( --wp--preset--font-family--display );
	font-size: 3.2rem;
	font-weight: 600;
	letter-spacing: 0.04em;
	line-height: 1;
}

/* Rendered but idle whenever a photo exists, so a portrait that fails to load
   can be swapped for the monogram without re-rendering anything. */
.bb-board-card__initials--fallback { display: none; }
.bb-board-card__photo.is-hidden { display: none; }
.bb-board-card__photo.is-hidden + .bb-board-card__initials--fallback { display: block; }

.bb-board-card__body {
	display: flex;
	flex: 1;
	flex-direction: column;
	padding: var( --wp--preset--spacing--40 );
}

.bb-board-card__name {
	color: var( --wp--preset--color--forest );
	font-family: var( --wp--preset--font-family--display );
	font-size: var( --wp--preset--font-size--large );
	font-weight: 600;
	line-height: 1.2;
	margin: 0;
}

.bb-board-card__accent {
	background-color: var( --wp--preset--color--terracotta );
	border-radius: 2px;
	display: block;
	height: 3px;
	margin-block: var( --wp--preset--spacing--20 );
	width: 36px;
}

.bb-board-card__excerpt {
	-webkit-box-orient: vertical;
	color: var( --wp--preset--color--contrast );
	display: -webkit-box;
	font-size: var( --wp--preset--font-size--small );
	-webkit-line-clamp: 3;
	line-clamp: 3;
	margin: 0 0 var( --wp--preset--spacing--40 );
	overflow: hidden;
}

.bb-board-card__more {
	align-items: center;
	align-self: flex-start;
	background-color: var( --wp--preset--color--terracotta-text );
	border-radius: 999px;
	color: var( --wp--preset--color--base );
	display: inline-flex;
	font-size: var( --wp--preset--font-size--small );
	font-weight: 600;
	gap: 0.5em;
	/* Pins the control to the bottom regardless of how much bio sits above it,
	   so every card in a row has its button on the same line. */
	margin-top: auto;
	min-height: 44px;
	padding: 0.6em 1.3em;
	text-decoration: none;
	transition: transform 0.2s, box-shadow 0.2s, background-color 0.2s;
}

.bb-board-card__more:hover,
.bb-board-card__more:focus-visible {
	background-color: var( --wp--preset--color--forest );
	box-shadow: rgb( 143 69 41 / 0.35 ) 0 10px 22px;
	color: var( --wp--preset--color--base );
	transform: translateY( -2px );
}

.bb-board-card__arrow {
	display: inline-block;
	transition: transform 0.2s;
}

.bb-board-card__more:hover .bb-board-card__arrow,
.bb-board-card__more:focus-visible .bb-board-card__arrow {
	transform: translateX( 3px );
}

/* --------------------------------------------------------------------------
   Back face
   -------------------------------------------------------------------------- */

/* Solid, not the gradient. terracotta-on-dark measures 4.87:1 on forest-deep
   but only 4.25:1 on forest — below the 4.5 needed for normal-size text. Laying
   the office label on the gradient would drift it under the threshold as the
   panel lightens, so the header strip stays at the dark stop and the gradient
   begins below it. scripts/check-contrast.mjs asserts the pairing that survives. */
.bb-board-card__backhead {
	align-items: center;
	background-color: var( --wp--preset--color--forest-deep );
	border-bottom: 1px solid rgb( 227 217 198 / 0.16 );
	display: flex;
	flex-shrink: 0;
	gap: var( --wp--preset--spacing--30 );
	padding: var( --wp--preset--spacing--40 ) var( --wp--preset--spacing--40 ) var( --wp--preset--spacing--30 );
	border-radius: 26px 0 0 0;
}

.bb-board-card__avatar {
	align-items: center;
	background: linear-gradient( 135deg, var( --wp--preset--color--forest ), var( --wp--preset--color--forest-deep ) );
	border: 2px solid var( --wp--preset--color--terracotta-on-dark );
	border-radius: 50%;
	color: var( --wp--preset--color--terracotta-on-dark );
	display: flex;
	flex-shrink: 0;
	font-family: var( --wp--preset--font-family--display );
	font-size: var( --wp--preset--font-size--medium );
	height: 3.5rem;
	justify-content: center;
	overflow: hidden;
	/* Containing block for the cropped avatar img below. Without this the
	   nearest ancestor with a transform is the back FACE (rotateY), and the
	   crop percentages would resolve against the whole card. */
	position: relative;
	width: 3.5rem;
}

.bb-board-card__avatar img {
	height: 100%;
	object-fit: cover;
	width: 100%;
}

/* The grid crop, applied inside the circle. The span's padding box is a 56px
   square (content-box; the 2px border sits outside), so the same percentages
   frame the face exactly as the front photo does, and the border-radius clip
   crops the circle out of that framing.

   The COMPOUND selector is required: the base rule above is (0,1,1) and a bare
   class (0,1,0) would lose object-fit — the box would be right but cover would
   re-crop inside it, a silent double-crop. tools/test-crops.php pins this
   selector verbatim. */
.bb-board-card__avatar img.bb-board-card__avatar-img--cropped {
	max-width: none;
	object-fit: fill;
	object-position: 0 0;
	position: absolute;
}

.bb-board-card__backhead-text {
	display: flex;
	flex-direction: column;
	gap: 0.15em;
	min-width: 0;
}

.bb-board-card__backname {
	color: var( --wp--preset--color--base );
	font-family: var( --wp--preset--font-family--display );
	font-size: var( --wp--preset--font-size--large );
	font-weight: 600;
	line-height: 1.15;
}

.bb-board-card__office {
	color: var( --wp--preset--color--terracotta-on-dark );
	font-size: var( --wp--preset--font-size--x-small );
}

/* The bio scrolls inside the card rather than growing it: the back is
   absolutely positioned to the front's height, and bios range from 436 to 1291
   characters. */
.bb-board-card__bio {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
	padding: var( --wp--preset--spacing--30 ) var( --wp--preset--spacing--40 ) var( --wp--preset--spacing--40 );
}

.bb-board-card__bio p {
	color: var( --wp--preset--color--base-alt );
	font-size: var( --wp--preset--font-size--small );
	line-height: 1.6;
	margin: 0;
	/* Biographies arrive with \n\n paragraph breaks and no markup. */
	white-space: pre-line;
}

.bb-board-card__bio-missing {
	font-style: italic;
	opacity: 0.75;
}

.bb-board-card__close {
	align-items: center;
	background-color: var( --wp--preset--color--forest );
	border-radius: 50%;
	color: var( --wp--preset--color--base );
	display: flex;
	font-size: 1.25rem;
	height: 2rem;
	inset-block-start: var( --wp--preset--spacing--20 );
	inset-inline-end: var( --wp--preset--spacing--20 );
	justify-content: center;
	line-height: 1;
	position: absolute;
	text-decoration: none;
	transition: background-color 0.2s;
	width: 2rem;
	z-index: 2;
}

.bb-board-card__close:hover,
.bb-board-card__close:focus-visible {
	background-color: var( --wp--preset--color--terracotta-text );
	color: var( --wp--preset--color--base );
}

/* --------------------------------------------------------------------------
   Focus
   -------------------------------------------------------------------------- */

/* The back panel is tabindex="-1" and receives focus only because the script
   moves it there, so a pointer user who clicked "Read full bio" would otherwise
   get the browser's default ring drawn around the whole card — an outline they
   did not ask for, in a colour the palette does not contain. :focus-visible
   still fires for keyboard users, who DO need to see where focus went, and they
   get the theme's own ring. */
.bb-board-card__face--back:focus {
	outline: none;
}

.bb-board-card__face--back:focus-visible {
	outline: 2px solid var( --wp--preset--color--terracotta-on-dark );
	outline-offset: -4px;
}

.bb-board-card__more:focus-visible,
.bb-board-card__close:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   Fallbacks
   -------------------------------------------------------------------------- */

/* Without :has(), the no-JS :target flip cannot be expressed at all. Rather
   than leave a card that half-rotates, drop out of 3D entirely and let the back
   face sit in flow beneath the front — every bio still readable, just not
   animated. */
@supports not ( selector( :has( * ) ) ) {
	.bb-board-card__inner {
		transform: none !important;
		transform-style: flat;
	}

	.bb-board-card__face--back {
		position: static;
		transform: none;
		visibility: visible;
	}

	.bb-board-card__face { backface-visibility: visible; }
}

@media ( prefers-reduced-motion: reduce ) {
	.bb-board-card,
	.bb-board-card__inner,
	.bb-board-card__face,
	.bb-board-card__more,
	.bb-board-card__arrow,
	.bb-board-card__silhouette {
		transition: none !important;
	}

	.bb-board-card:hover,
	.bb-board-card__more:hover {
		transform: none;
	}
}
