/**
 * Front page only: hero, section rhythm, gallery accordion, article index, about.
 */

/* ---------- Scroll reveal ---------- */

/*
 * Gated on data-bf-motion, which inc/setup.php sets before first paint. The
 * hidden state must never apply unless something has confirmed it can be
 * undone, or a failed module leaves the page blank.
 */
html[data-bf-motion="on"] [data-bf-reveal] {
	opacity: 0;
	transform: translateY(18px);
}

html[data-bf-motion="on"] [data-bf-reveal].is-in {
	opacity: 1;
	transform: none;
	transition: opacity 0.7s var(--bf-ease), transform 0.7s var(--bf-ease);
}

@media (prefers-reduced-motion: reduce) {
	html[data-bf-motion="on"] [data-bf-reveal] {
		opacity: 1;
		transform: none;
		transition: none;
	}
}

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

/*
 * The only dark surface on the site: the reference's slate, which is also the
 * ink everywhere else. Everything below the hero is paper-white.
 */
.bf-hero {
	--bf-ink: var(--bf-hero-ink);
	--bf-paper: var(--wp--preset--color--contrast);
	/*
	 * Slate base plus one soft pool of light behind the portrait, so the figure
	 * sits in a lit space instead of on a flat fill. Offset above centre to
	 * meet the subject; the outer stop lands on transparent so it fades into
	 * the base colour rather than to black.
	 */
	background-color: var(--wp--preset--color--contrast);
	background-image: radial-gradient(
		72% 58% at 52% 42%,
		rgb(232 234 230 / 0.1) 0%,
		rgb(232 234 230 / 0.045) 38%,
		transparent 72%
	);
	color: var(--bf-ink);
	position: relative;
	/* The theme has no global border-box, so min-height would otherwise add
	   the padding on top of the viewport height and push the fold down. */
	box-sizing: border-box;
	isolation: isolate;
	display: grid;
	place-items: center;
	min-height: 100svh;
	padding: calc(var(--bf-header-h) + 24px) 20px 96px;
	overflow: clip;
}

.bf-sentinel {
	position: absolute;
	inset: 0 0 auto 0;
	height: 96px;
	pointer-events: none;
}

/*
 * The h1 stays static with no transform, opacity or z-index of its own, so it
 * is NOT a stacking context — only then do the two words' z-indexes resolve
 * against .bf-hero alongside the figure, which is what puts the outline word
 * behind the photo and the solid word in front of it. One h1, so the
 * accessible name stays "Brendan Francisco".
 */
.bf-hero__title {
	position: static;
	justify-self: stretch;
	width: 100%;
	margin: 0;
	line-height: 0.8;
	text-align: left;
	/* Both words are sized in vw and allowed to run past the edge; .bf-hero
	   clips the overflow so the page never gains a horizontal scrollbar. */
	white-space: nowrap;
}

.bf-hero__word {
	position: relative;
	display: block;
	/* Scroll depth on Y, plus the pointer drift (--bf-mx/--bf-my, -1..1, written
	   by home.js) scaled by each layer's --bf-mdepth. Negative depth reads as
	   further away: it moves against the pointer. */
	transform: translate3d(
		calc(var(--bf-mx, 0) * var(--bf-mdepth, 0) * 1px),
		calc(var(--bf-hero-p, 0) * var(--bf-depth, 0) * 1px + var(--bf-my, 0) * var(--bf-mdepth, 0) * 1px),
		0
	);
	will-change: transform;
}

/*
 * Load-in. The name first appears whole and centred, then travels out to its
 * resting place while the portrait fades up behind it (home.js, initHeroIntro).
 * Until the script takes over, the words and the photo sit hidden — but only
 * behind data-bf-motion, and with a failsafe that fades them in on its own, so
 * a failed or slow module can never leave the hero empty. Once the script
 * starts it sets data-bf-intro, which drops both the hidden state and the
 * failsafe and hands every frame to the Web Animations it runs.
 */
html[data-bf-motion] .bf-hero:not([data-bf-intro]) :is(.bf-hero__word, .bf-hero__photo) {
	opacity: 0;
	animation: bf-hero-failsafe 0.8s ease 2.5s forwards;
}

@keyframes bf-hero-failsafe {
	to {
		opacity: 1;
	}
}

.bf-hero__word--back {
	z-index: 1;
	--bf-depth: -140;
	--bf-mdepth: -3;
	margin-left: -0.04em;
	font-size: clamp(56px, 17.5vw, 260px);
	color: var(--bf-ink);
}

/* Enhancement in the safe direction: without stroke support the word stays
   solid and legible rather than turning invisible. */
@supports (-webkit-text-stroke: 1px currentColor) {
	.bf-hero__word--back {
		color: transparent;
		-webkit-text-stroke: var(--bf-stroke-w) color-mix(in srgb, var(--bf-ink) 60%, transparent);
	}
}

@media (forced-colors: active) {
	.bf-hero {
		background-image: none;
	}

	.bf-hero__word--back {
		color: CanvasText;
		-webkit-text-stroke-width: 0;
	}
}

.bf-hero__word--front {
	z-index: 3;
	--bf-depth: 60;
	--bf-mdepth: 5;
	margin-top: -0.1em;
	margin-left: 14%;
	font-size: clamp(60px, 15.5vw, 235px);
	color: var(--wp--preset--color--sage);
}

.bf-hero__photo {
	z-index: 2;
	position: absolute;
	bottom: 0;
	left: 50%;
	/* The photo has to end somewhere — the hero's clip edge below the fold, or
	   the source frame's own flat crop. Dissolve it into the background instead
	   of letting either one read as an unfinished image. */
	-webkit-mask-image: linear-gradient(to bottom, #000 82%, transparent 100%);
	mask-image: linear-gradient(to bottom, #000 82%, transparent 100%);
	/* Driven by height, not width: the portrait should fill the hero on any
	   viewport instead of shrinking on a wide, short one. */
	width: auto;
	margin: 0;
	transform: translate3d(
		calc(-50% + var(--bf-mx, 0) * var(--bf-mdepth, 0) * 1px),
		calc(var(--bf-hero-p, 0) * var(--bf-depth, 0) * 1px + var(--bf-my, 0) * var(--bf-mdepth, 0) * 1px),
		0
	);
	--bf-depth: -50;
	--bf-mdepth: 1.5;
	will-change: transform;
	pointer-events: none;
}

.bf-hero__img {
	display: block;
	/* Height drives it; the width follows the portrait's own ratio. No
	   max-width: clamping the width would letterbox the image inside a taller
	   box, leaving a dead band that the bottom mask then fades. Any horizontal
	   bleed is clipped by .bf-hero, which is the intended full-bleed look. */
	width: auto;
	height: min(96svh, 1180px);
}

/*
 * Scroll cue. Visible by default so it is already right at first paint, and
 * withdrawn by data-bf-at-top="no" the moment the page moves — it has done its
 * job by then. Returning to the very top brings it back.
 */
.bf-hero__scroll {
	position: absolute;
	bottom: 24px;
	left: 20px;
	z-index: 4;
	display: inline-flex;
	align-items: center;
	gap: 10px;
	color: color-mix(in srgb, var(--bf-ink) 58%, transparent);
	font-size: 12px;
	letter-spacing: 0.14em;
	text-decoration: none;
	text-transform: uppercase;
	/*
	 * The glow animates `filter` only, never `opacity`: an animated property
	 * outranks a plain declaration, so animating opacity here would make the
	 * cue impossible to hide.
	 */
	animation: bf-cue-glow 2.8s ease-in-out infinite;
	transition: opacity 0.45s ease, visibility 0s linear 0s;
}

.bf-hero__scroll svg {
	animation: bf-cue-drift 2.8s ease-in-out infinite;
}

.bf-hero__scroll:hover {
	color: var(--bf-ink);
}

html[data-bf-at-top="no"] .bf-hero__scroll {
	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	transition: opacity 0.45s ease, visibility 0s linear 0.45s;
}

@keyframes bf-cue-glow {
	0%,
	100% {
		color: color-mix(in srgb, var(--bf-ink) 45%, transparent);
		filter: drop-shadow(0 0 2px rgb(232 234 230 / 0));
	}

	50% {
		color: var(--bf-ink);
		filter: drop-shadow(0 0 10px rgb(232 234 230 / 0.65));
	}
}

@keyframes bf-cue-drift {
	0%,
	100% {
		transform: translateY(0);
	}

	50% {
		transform: translateY(4px);
	}
}

@media (max-width: 47.999em) {
	/*
	 * Narrow screens invert the arrangement. The portrait hangs from under the
	 * header instead of standing on the hero's floor, so the dead band moves
	 * out of the top; and the type drops into the lower half, over his torso,
	 * ending where the photo's mask fades. Growing the photo to close that gap
	 * instead would have to be done by height, and at this aspect ratio the
	 * extra width gets clipped off his arms.
	 */
	.bf-hero {
		--bf-hero-img-h: min(80svh, 820px);
		align-items: end;
		/* The hero ends where the photo's mask does, instead of running on to
		   a full viewport and leaving a dead slate band under him. */
		min-height: calc(var(--bf-header-h) + var(--bf-hero-img-h));
		padding-top: var(--bf-header-h);
		/* Lands the type across his torso, with the photograph still running on
		   below it — low enough to read as "in front of him", not as a caption
		   sitting under the picture. */
		padding-bottom: calc(var(--bf-hero-img-h) * 0.18);
	}

	.bf-hero__photo {
		top: var(--bf-header-h);
		bottom: auto;
	}

	.bf-hero__img {
		height: var(--bf-hero-img-h);
	}

	.bf-hero__scroll {
		display: none;
	}

	/* Too little room to offset the words without losing whole syllables. */
	.bf-hero__title {
		text-align: center;
	}

	/*
	 * Both words come to the front here. On a wide screen the portrait leaves
	 * the ends of BRENDAN clear, so hiding its middle behind him reads as
	 * depth; at this width he covers almost the whole line and only slivers
	 * survive, which reads as broken. The word is outlined either way, so the
	 * photograph still shows through it.
	 */
	.bf-hero__word--back {
		z-index: 3;
		margin-left: 0;
		font-size: clamp(42px, 19vw, 120px);
		/* A hairline stroke disappears against the dark knitwear. */
		--bf-stroke-w: 1.6px;
	}

	.bf-hero__word--front {
		margin-left: 0;
		font-size: clamp(38px, 16.5vw, 108px);
	}
}

/*
 * No parallax on phones, same as reduced motion (see inc/setup.php). Phones
 * still get the intro: it runs on `translate` / `scale`, not `transform`.
 */
@media (prefers-reduced-motion: reduce), (max-width: 47.999em) {
	.bf-hero__scroll,
	.bf-hero__scroll svg {
		animation: none;
	}

	.bf-hero__scroll {
		color: var(--bf-ink);
	}

	.bf-hero__word,
	.bf-hero__photo {
		transform: none;
		will-change: auto;
	}

	.bf-hero__photo {
		transform: translateX(-50%);
	}
}

@media (prefers-reduced-motion: reduce) {
	html[data-bf-motion] .bf-hero:not([data-bf-intro]) :is(.bf-hero__word, .bf-hero__photo) {
		opacity: 1;
		animation: none;
	}
}

/* ---------- Section shell ---------- */

.bf-section {
	padding-block: var(--bf-section-gap);
	/* A deep link must not park the title under the fixed header. */
	scroll-margin-top: calc(var(--bf-header-h) + 16px);
}

.bf-section__head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: 16px;
	padding-inline: 20px;
	margin-bottom: clamp(32px, 5vw, 64px);
}

.bf-section__title {
	margin: 0;
	font-size: var(--wp--preset--font-size--huge);
	font-weight: 800;
	line-height: 0.95;
	letter-spacing: -0.02em;
	text-transform: uppercase;
}

.bf-section__more {
	/* When the title wraps, this drops to its own line; keep it on the right
	   rather than orphaned under the first word. */
	margin-left: auto;
	display: inline-flex;
	align-items: center;
	gap: 8px;
	color: var(--wp--preset--color--muted);
	font-size: 14px;
	letter-spacing: 0.06em;
	text-decoration: none;
	text-transform: uppercase;
	white-space: nowrap;
}

.bf-section__more:hover {
	color: var(--wp--preset--color--contrast);
}

/* ---------- Gallery accordion ---------- */

/*
 * Narrow screens and touch get a plain stack of full-width panels; the
 * accordion below is layered on only where a real hover exists.
 */
.bf-folds {
	display: flex;
	flex-direction: column;
	gap: 8px;
	margin: 0;
	padding: 0 20px;
	list-style: none;
}

.bf-fold__link {
	position: relative;
	display: block;
	height: clamp(200px, 56vw, 320px);
	overflow: hidden;
	background: var(--wp--preset--color--contrast);
	color: var(--bf-on-media);
	text-decoration: none;
}

.bf-fold__link:focus-visible {
	outline: 2px solid var(--bf-ink);
	outline-offset: 3px;
}

.bf-fold__media {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: opacity 0.6s var(--bf-ease), transform 0.8s var(--bf-ease);
}

/* A scrim, not a wash: only the band the caption sits on is darkened. */
.bf-fold__link::after {
	content: "";
	position: absolute;
	inset: auto 0 0;
	height: 55%;
	background: linear-gradient(to top, rgb(20 22 24 / 0.72), rgb(20 22 24 / 0));
	pointer-events: none;
}

.bf-fold__caption {
	position: absolute;
	inset: auto 24px 24px;
	z-index: 1;
	display: flex;
	align-items: flex-end;
	justify-content: space-between;
	gap: 16px;
}

.bf-fold__title {
	min-width: 0;
	font-size: clamp(28px, 7vw, 44px);
	font-weight: 800;
	line-height: 0.92;
	letter-spacing: -0.02em;
	text-transform: uppercase;
	text-wrap: balance;
}

.bf-fold__arrow {
	flex: none;
	font-size: 28px;
	line-height: 1;
}

@media (min-width: 48em) and (hover: hover) and (pointer: fine) {
	.bf-folds {
		flex-direction: row;
		/* Spacing lives inside each item (padding), not in a flex gap: a gap
		   is dead space the pointer can fall into, and every crossing would
		   snap the accordion back to its resting state. */
		gap: 0;
		height: clamp(480px, 44vw, 680px);
		padding-inline: 16px;
	}

	.bf-fold {
		flex: 1 1 0;
		min-width: 0;
		padding-inline: 4px;
		transition: flex-grow 0.7s var(--bf-ease);
	}

	/* Resting state: the first panel open. Any panel under the pointer or
	   holding focus takes over, and the rest close. */
	.bf-fold:first-child,
	.bf-folds .bf-fold:hover,
	.bf-folds .bf-fold:focus-within {
		flex-grow: 3.4;
	}

	.bf-folds:has(.bf-fold:hover, .bf-fold:focus-within) .bf-fold:not(:hover, :focus-within) {
		flex-grow: 1;
	}

	.bf-fold__link {
		height: 100%;
	}

	.bf-fold__media {
		opacity: 0.55;
	}

	.bf-fold__title {
		font-size: 20px;
		transition: font-size 0.5s var(--bf-ease);
	}

	.bf-fold__arrow {
		opacity: 0;
		transition: opacity 0.4s var(--bf-ease);
	}

	/* Open-panel styling, keyed the same way as the width above. */
	.bf-fold:first-child .bf-fold__media,
	.bf-folds .bf-fold:hover .bf-fold__media,
	.bf-folds .bf-fold:focus-within .bf-fold__media {
		opacity: 1;
	}

	.bf-fold:first-child .bf-fold__title,
	.bf-folds .bf-fold:hover .bf-fold__title,
	.bf-folds .bf-fold:focus-within .bf-fold__title {
		font-size: clamp(40px, 4.4vw, 64px);
	}

	.bf-fold:first-child .bf-fold__arrow,
	.bf-folds .bf-fold:hover .bf-fold__arrow,
	.bf-folds .bf-fold:focus-within .bf-fold__arrow {
		opacity: 1;
	}

	.bf-folds:has(.bf-fold:hover, .bf-fold:focus-within) .bf-fold:not(:hover, :focus-within) .bf-fold__media {
		opacity: 0.55;
	}

	.bf-folds:has(.bf-fold:hover, .bf-fold:focus-within) .bf-fold:not(:hover, :focus-within) .bf-fold__title {
		font-size: 20px;
	}

	.bf-folds:has(.bf-fold:hover, .bf-fold:focus-within) .bf-fold:not(:hover, :focus-within) .bf-fold__arrow {
		opacity: 0;
	}

	.bf-fold__link:hover .bf-fold__media {
		transform: scale(1.03);
	}
}

@media (prefers-reduced-motion: reduce) {
	.bf-fold,
	.bf-fold__media,
	.bf-fold__title,
	.bf-fold__arrow {
		transition: none;
	}

	.bf-fold__link:hover .bf-fold__media {
		transform: none;
	}
}

/* ---------- Gallery card (archive grid) ---------- */

.bf-card__link {
	display: block;
	color: inherit;
	text-decoration: none;
}

.bf-card__frame {
	position: relative;
	display: block;
	aspect-ratio: 3 / 4;
	overflow: hidden;
	background: var(--wp--preset--color--surface);
}

.bf-card__media {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform 0.6s var(--bf-ease);
}

.bf-card__link:hover .bf-card__media {
	transform: scale(1.04);
}

.bf-card__title {
	display: block;
	margin-top: 14px;
	font-size: 15px;
	font-weight: 700;
	letter-spacing: 0.02em;
	text-transform: uppercase;
}

.bf-card__link:hover .bf-card__title {
	text-decoration: underline;
	text-underline-offset: 4px;
}

/* ---------- Article index ---------- */

/*
 * The one tinted band on the page. Photographs above and About below both sit
 * on paper, so the tint — not a louder heading — is what says the subject
 * changed. It bleeds the full width because .bf-main carries no padding of its
 * own; the content inside keeps the same 20px gutter as every other section.
 */
.bf-section--cultures {
	/*
	 * The preset muted (#767c79) lands at 4.2:1 on this tint, under AA for the
	 * 11-15px kicker, date and excerpt below. This tone reads as the same grey
	 * at those sizes and clears 5:1.
	 */
	--bf-quiet: #5f6663;
	/*
	 * A half-step of the surface preset (#f1f2ef), not the preset itself: the
	 * band only has to read as "not paper", and at the preset's weight it
	 * competed with the hero for attention.
	 */
	background: #f7f8f6;
	/* Hairlines at both edges, so the band reads as a deliberate stripe rather
	   than as a seam where two near-whites meet. */
	border-block: 1px solid var(--bf-hairline);
}

/* Head and content share one measure, so the title lines up with the lead
   frame on wide screens instead of hanging 20px further out. */
.bf-section--cultures .bf-section__head {
	max-width: 1400px;
	margin-inline: auto;
}

.bf-cultures {
	display: grid;
	/* Lead wide enough to carry a 16:9 frame at a readable size; the followers
	   take what is left, which is about the width of three words per line. */
	grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
	align-items: start;
	gap: clamp(32px, 5vw, 72px);
	max-width: 1400px;
	margin-inline: auto;
	padding-inline: 20px;
}

/* ---------- Lead article ---------- */

.bf-lead {
	display: flex;
	flex-direction: column;
	gap: clamp(16px, 2vw, 22px);
	color: inherit;
	text-decoration: none;
}

.bf-lead__frame {
	display: block;
	aspect-ratio: 16 / 9;
	overflow: hidden;
	background: var(--wp--preset--color--base);
}

.bf-lead__img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform 0.7s var(--bf-ease);
}

.bf-lead:hover .bf-lead__img {
	transform: scale(1.04);
}

.bf-lead:hover .bf-lead__title {
	text-decoration: underline;
	text-underline-offset: 8px;
}

.bf-lead__body {
	display: flex;
	flex-direction: column;
	gap: 12px;
}

.bf-lead__title {
	font-size: clamp(28px, 3.4vw, 44px);
	font-weight: 800;
	letter-spacing: -0.025em;
	line-height: 1.03;
	text-transform: uppercase;
}

.bf-lead__excerpt {
	/* Measure, not column width: past ~52 characters the eye loses the line
	   return at this size. */
	max-width: 52ch;
	color: var(--bf-quiet);
	font-size: clamp(15px, 1.2vw, 17px);
	line-height: 1.55;
}

.bf-kicker {
	color: var(--bf-quiet);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.18em;
	text-transform: uppercase;
}

.bf-meta {
	color: var(--bf-quiet);
	font-size: 11px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
}

/* ---------- Followers ---------- */

.bf-articles {
	display: flex;
	flex-direction: column;
	margin: 0;
	padding: 0;
	list-style: none;
}

.bf-article {
	border-top: 1px solid var(--bf-hairline);
}

.bf-article:last-child {
	border-bottom: 1px solid var(--bf-hairline);
}

.bf-article__link {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: center;
	gap: clamp(16px, 2vw, 24px);
	padding-block: clamp(18px, 2.2vw, 26px);
	color: inherit;
	text-decoration: none;
}

.bf-article__body {
	display: flex;
	flex-direction: column;
	gap: 10px;
	min-width: 0;
}

.bf-article__title {
	font-size: clamp(18px, 1.6vw, 24px);
	font-weight: 800;
	letter-spacing: -0.02em;
	line-height: 1.12;
	text-transform: uppercase;
}

.bf-article__thumb {
	display: block;
	width: clamp(72px, 7vw, 96px);
	aspect-ratio: 1;
	overflow: hidden;
	background: var(--wp--preset--color--base);
}

.bf-article__img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform 0.6s var(--bf-ease);
}

.bf-article__link:hover .bf-article__img {
	transform: scale(1.06);
}

.bf-article__link:hover .bf-article__title {
	text-decoration: underline;
	text-underline-offset: 6px;
}

/*
 * One column before the lead frame gets too small to carry the band: below
 * this the followers read as a continuation of the lead, not as a sidebar.
 */
@media (max-width: 60em) {
	.bf-cultures {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* ---------- About ---------- */

.bf-about {
	display: grid;
	grid-template-columns: minmax(0, 420px) minmax(0, 1fr);
	/* The title heads the text column, so the columns line up at the top
	   rather than at their centres. */
	align-items: start;
	gap: clamp(28px, 5vw, 72px);
	max-width: 1200px;
	margin-inline: auto;
	padding-inline: 20px;
}

.bf-about__title {
	margin: 0 0 clamp(20px, 2.5vw, 32px);
}

.bf-about__figure {
	margin: 0;
	aspect-ratio: 4 / 5;
	overflow: hidden;
	background: var(--wp--preset--color--surface);
}

.bf-about__img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.bf-about__bio {
	margin: 0;
	font-size: clamp(17px, 1.6vw, 22px);
	line-height: 1.6;
}

.bf-about__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 24px;
	margin: 32px 0 0;
}

@media (max-width: 47.999em) {
	.bf-about {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* ---------- Gallery archive grid ---------- */

.bf-archive__title {
	margin: 0 0 clamp(32px, 5vw, 64px);
	font-size: var(--wp--preset--font-size--huge);
}

.bf-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
	gap: 32px 24px;
	margin: 0;
	padding: 0;
	list-style: none;
}
