/*----------------------------------------------

[Custom CSS]

Small, self-authored stylesheet that replaces the
old vendor "menu-engine" / "menu-grid" files (the
nav's megamenu system) with a flat, simplified nav,
and adds the few extra components introduced when
main.js was rebuilt without jQuery/GSAP/ProgressBar.

theme.css / core.css / main.css are untouched — this
file only adds what those no longer cover.

----------------------------------------------*/

/* #region Heading gradient (Firefox fallback for the theme's own .text-gradient)

main.css/core.css already ship a .text-gradient + .scheme-1/2/3 utility
that fills text with a two-tone brand gradient (used on the About,
Portfolio, and stat-counter headings). It only sets the WebKit-specific
-webkit-text-fill-color, so non-WebKit browsers fall back to a solid
color instead of the clipped gradient. This adds the standard property
too, with !important since several ancestor wrappers (.basic-intro,
.card-body, etc.) set their own higher-specificity `color`. */

.text-gradient,
.text-gradient > * {
	color: transparent !important;
}

/* The accent word inside a heading (e.g. <mark>passion</mark>,
<mark>Pop</mark>) sits one level deeper than .text-gradient > *
reaches, so it was inheriting "transparent" without inheriting the
gradient background needed to fill it back in — making the word
disappear. Rather than just giving it back a flat color, give it the
same shifting brand-color shimmer as the nav logo text, so the
accent word pops at least as much as the rest of the heading.

This shimmer has to live on an inner .mark-text span, not on <mark>
itself. A background-image always paints at the bottom of an
element's own stacking order — below its own negative z-index
children — so putting it directly on <mark> made the underline bar
(::before, z-index:-1) paint on top of the gradient letters instead
of behind them. The inner span is genuine later content, so it
paints after (in front of) the bar, restoring the correct order. */
.text-gradient mark .mark-text {
	background-image: linear-gradient(90deg, var(--primary-color), var(--secondary-color), var(--tertiary-color), var(--primary-color));
	background-size: 300% auto;
	background-position: 0% 50%;
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
	color: transparent !important;
	animation: brand-gradient-shift 5s linear infinite;
}

/* Give each accent word's underline a different hue than its own
text, instead of matching (the underline's color rides on the same
--color variable as the text by default). Picked the opposite end of
that heading's gradient so text + underline read as two deliberate
brand colors rather than one flat color repeated twice. */
.text-gradient mark.primary-50.animated-underline::before {
	background-color: var(--tertiary-color);
}

.text-gradient mark.tertiary.animated-underline::before {
	background-color: var(--primary-color);
}

/* #endregion Heading gradient */

/* #region Nav */

.mip-header {
	position: fixed;
	top: 0;
	left: 0;
	width: 100%;
	z-index: 100;
}

.mip-header .navbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 1rem 0;
	background-color: rgba(26, 26, 32, 0.55);
	-webkit-backdrop-filter: blur(10px);
	backdrop-filter: blur(10px);
	transition: background-color 0.3s ease;
}

.mip-header .navbar .container {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
}

.mip-header .navbar-brand {
	display: flex;
	align-items: center;
	text-decoration: none;
}

.mip-header .navbar-brand .logo {
	height: 34px;
	width: auto;
}

.navbar-brand-text {
	display: flex;
	flex-direction: column;
	margin-left: 0.85rem;
	line-height: 1.1;
}

.navbar-brand .brand-tag {
	font-family: 'Courier New', ui-monospace, monospace;
	font-size: 0.7rem;
	font-weight: 700;
	letter-spacing: 0.03em;
	text-transform: uppercase;
	color: var(--white-65-color);
}

.navbar-brand .brand-name {
	margin-top: 2px;
	font-family: var(--secondary-font);
	font-size: 1.25rem;
	font-weight: 800;
	letter-spacing: 0.01em;
	text-transform: uppercase;
	background-image: linear-gradient(90deg, var(--primary-color), var(--secondary-color), var(--tertiary-color), var(--primary-color));
	background-size: 300% auto;
	background-position: 0% 50%;
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
	color: transparent;
	animation: brand-gradient-shift 5s linear infinite;
	transition: filter 0.3s ease;
}

.navbar-brand:hover .brand-name {
	filter: brightness(1.25) drop-shadow(0 0 10px var(--tertiary-50-color));
}

@keyframes brand-gradient-shift {
	0% { background-position: 0% 50%; }
	100% { background-position: 300% 50%; }
}

@media (max-width: 420px) {

	/* The tag line is the brand now, not a strapline — keep it and shrink
	   it rather than dropping it. Revert to `display: none` for the old
	   behaviour. */
	.navbar-brand .brand-tag {
		font-size: 0.62rem;
	}

	.navbar-brand .brand-name {
		font-size: 1rem;
	}
}

.mip-header .navbar-toggler {
	width: 32px;
	height: 22px;
	position: relative;
	background: none;
	border: none;
	padding: 0;
	cursor: pointer;
}

.mip-header .navbar-toggler-icon {
	position: relative;
	display: block;
	width: 100%;
	height: 100%;
}

.mip-header .navbar-toggler .line {
	position: absolute;
	left: 0;
	width: 100%;
	height: 2px;
	background-color: var(--white-color);
	border-radius: 2px;
	transition: transform 0.3s ease, opacity 0.3s ease, top 0.3s ease;
}

.mip-header .navbar-toggler .line:nth-child(1) { top: 0; }
.mip-header .navbar-toggler .line:nth-child(2) { top: 10px; }
.mip-header .navbar-toggler .line:nth-child(3) { top: 20px; }

.mip-header .navbar-toggler[aria-expanded="true"] .line:nth-child(1) {
	top: 10px;
	transform: rotate(45deg);
}

.mip-header .navbar-toggler[aria-expanded="true"] .line:nth-child(2) {
	opacity: 0;
}

.mip-header .navbar-toggler[aria-expanded="true"] .line:nth-child(3) {
	top: 10px;
	transform: rotate(-45deg);
}

.mip-header .navbar-nav {
	display: flex;
	flex-direction: row;
	align-items: center;
	list-style: none;
	margin: 0;
	padding: 0;
	gap: 2rem;
}

.mip-header .nav-link {
	display: block;
	font-family: var(--secondary-font);
	font-size: 0.85rem;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--white-color);
	text-decoration: none;
	position: relative;
	padding: 0.25rem 0;
}

.mip-header .nav-link::after {
	content: '';
	position: absolute;
	left: 0;
	bottom: 0;
	width: 0;
	height: 2px;
	background-color: var(--tertiary-color);
	transition: width 0.3s ease;
}

.mip-header .nav-link:hover::after {
	width: 100%;
}

@media (max-width: 991px) {

	.mip-header .navbar-collapse {
		position: absolute;
		top: 100%;
		left: 0;
		width: 100%;
		max-height: 0;
		overflow: hidden;
		background-color: rgba(26, 26, 32, 0.97);
		transition: max-height 0.35s ease;
	}

	.mip-header .navbar-collapse.show {
		max-height: 400px;
	}

	.mip-header .navbar-nav {
		flex-direction: column;
		align-items: flex-start;
		gap: 0;
		padding: 1rem 1.5rem 1.5rem;
	}

	.mip-header .navbar-nav .nav-item {
		width: 100%;
		padding: 0.75rem 0;
		border-bottom: 1px solid var(--white-10-color);
	}
}

@media (min-width: 992px) {

	.mip-header .navbar-collapse {
		display: flex !important;
		flex-basis: auto;
		flex-grow: 0;
		margin-left: auto;
	}

	.mip-header .navbar-toggler {
		display: none;
	}
}

/* #endregion Nav */

/* #region Typed text */

/* #typed-strings is just the data source main.js reads from to
   type out one line at a time — it isn't meant to be visible. */
#typed-strings {
	display: none;
}

/* #endregion Typed text */

/* #region Scroll reveal (replaces AOS) */

[data-aos] {
	opacity: 0;
	transform: translateY(24px);
	transition: opacity 0.6s ease, transform 0.6s ease;
}

[data-aos].in-view {
	opacity: 1;
	transform: translateY(0);
}

[data-aos][data-aos-delay="100"].in-view {
	transition-delay: 0.1s;
}

/* #endregion Scroll reveal */

/* #region Stat counter (replaces ProgressBar.js) */

.stat-counter {
	text-align: center;
}

.stat-counter .value {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 88px;
	padding: 0.5rem 1rem;
	/* Brand gradient rather than flat black, drifting slowly so the row has
	   some life in it. Each column is offset so they never move in lockstep. */
	background-color: var(--secondary-color);
	background-image: linear-gradient(120deg,
		var(--primary-color), var(--secondary-color),
		var(--tertiary-color), var(--primary-color));
	background-size: 300% auto;
	background-position: 0% 50%;
	animation: sjh-stat-drift 9s linear infinite;
	color: var(--white-color);
	font-family: var(--secondary-font);
	font-size: 1.75rem;
	font-weight: 800;
	border-radius: 999px;
	/* Pull the number away from its label — they were almost touching. */
	margin-top: 1rem;
	margin-bottom: 0.75rem;
	box-shadow: 0 6px 18px rgba(105, 23, 208, 0.22);
}

@keyframes sjh-stat-drift {
	to { background-position: 300% 50%; }
}

.col-6:nth-child(2) .stat-counter .value { animation-delay: -2.25s; }
.col-6:nth-child(3) .stat-counter .value { animation-delay: -4.5s; }
.col-6:nth-child(4) .stat-counter .value { animation-delay: -6.75s; }

@media (prefers-reduced-motion: reduce) {
	.stat-counter .value { animation: none; background-position: 25% 50%; }
}

.stat-counter .title {
	margin: 0;
}

/* #endregion Stat counter */

/* #region Portfolio filter */

/*
The shuffle-item tiles are nested one level inside the
".row" (row > .gapped-grid.shuffle-container > .shuffle-item),
so Bootstrap's own grid context never reaches them, and the
old col-12/col-md-6/col-lg-4 percentage widths only resolve
correctly at exact Bootstrap breakpoints — on a laptop viewport
that falls between them, a tile can fall back to col-12 (100%
width) and balloon across the whole screen. Rather than fight
those breakpoints, this drops in a real CSS grid with a capped
column width, so tiles reflow naturally at any screen size and
never grow past a sensible max.
*/

.shuffle-container.gapped-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(220px, 320px));
	justify-content: center;
	gap: 1.5rem;
	width: 100%;
	max-width: none;
	margin: 0;
}

.shuffle-item {
	/* neutralise the old Bootstrap col-* width/flex rules so
	   they can't fight the grid at any breakpoint */
	width: 100% !important;
	max-width: 100% !important;
	flex: none !important;
	padding: 0;
	margin: 0;
	transition: opacity 0.3s ease;
}

.shuffle-item .item {
	display: block;
	height: 100%;
}

/* The source images are raw, mixed-aspect-ratio screenshots
(some near-square, some wide, some tall). Left at width:100%/
height:auto they render at wildly different, often huge,
heights. Cropping each tile to a consistent box keeps the grid
tidy and stops any one image from towering over the page. */

.shuffle-item .image-wrapper {
	position: relative;
	overflow: hidden;
	aspect-ratio: 4 / 3;
}

.shuffle-item .image-wrapper .image {
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: top center;
	transition: transform 0.6s cubic-bezier(0.2, 1, 0.2, 1), filter 0.6s ease;
}

/* Hover trick: the image zooms in and desaturates slightly behind a
brand-gradient wash, while a "view" icon pops in at the center with
a little overshoot. All driven off hovering the tile link so the
whole card is the hit area, not just the pixels under the cursor. */

.shuffle-item .image-wrapper::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 1;
	background-image: linear-gradient(135deg, var(--secondary-75-color), var(--tertiary-75-color));
	opacity: 0;
	transition: opacity 0.45s ease;
}

.shuffle-item a.item:hover .image-wrapper .image {
	transform: scale(1.12) rotate(0.5deg);
	filter: saturate(1.15);
}

.shuffle-item a.item:hover .image-wrapper::before {
	opacity: 0.6;
}

.hover-reveal {
	position: absolute;
	z-index: 2;
	top: 50%;
	left: 50%;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 56px;
	height: 56px;
	border-radius: 50%;
	background-color: var(--white-color);
	color: var(--black-color);
	font-size: 1.1rem;
	opacity: 0;
	transform: translate(-50%, -50%) scale(0.4);
	transition: opacity 0.35s ease, transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
	pointer-events: none;
}

.shuffle-item a.item:hover .hover-reveal {
	opacity: 1;
	transform: translate(-50%, -50%) scale(1);
}

/* #endregion Portfolio filter */

/* #region Closing CTA gradient (replaces missing video asset) */

/*
.content-wrapper is absolutely positioned (out of normal flow), so
it never grows the section to fit itself — it just overflows past
the bottom of whatever height the gradient background happens to
be. That was fine for one short button, but the contact form is much
taller. Flip it: let .content-wrapper sit in normal flow (so the
section is exactly as tall as the form needs), and make the
gradient + dark overlay absolutely fill whatever height that turns
out to be, instead of the reverse.
*/

/*
The same collapse hits the short closing CTA at the foot of every case
study, for a different reason: .image-wrapper is height:auto and its
only child, .gradient-panel, is height:100%. A percentage height
against an auto parent resolves to auto, so the panel measures its own
(empty) content and the banner ends up with no height at all. Both
cases are cured by the same flip, so .sjh-closing-cta rides along with
#say-hello rather than getting a near-identical copy of these rules.
*/

#say-hello .banner,
.sjh-closing-cta .banner {
	display: block;
	height: auto;
	min-height: 60vh;
}

#say-hello .content-wrapper,
.sjh-closing-cta .content-wrapper {
	position: relative;
	padding: 5rem 1.5rem;
}

#say-hello .image-wrapper,
.sjh-closing-cta .image-wrapper {
	position: absolute;
	inset: 0;
	height: 100%;
}

/* main.css's own .has-overlay .overlay uses height:100%, which is
unreliable against a parent whose height is auto/content-driven —
inset:0 ties it to the parent's actual box no matter how that
height was determined. */
#say-hello .overlay,
.sjh-closing-cta .overlay {
	position: absolute;
	inset: 0;
	height: 100%;
}

/* The closing CTA is one heading and a button, so it does not need the
   full-height treatment the homepage form does. */
.sjh-closing-cta .banner {
	min-height: 42vh;
}

.gradient-panel {
	width: 100%;
	height: 100%;
	background: linear-gradient(120deg, var(--secondary-color), var(--tertiary-color), var(--primary-color));
	background-size: 200% 200%;
	animation: gradient-shift 12s ease infinite;
}

@keyframes gradient-shift {
	0% { background-position: 0% 50%; }
	50% { background-position: 100% 50%; }
	100% { background-position: 0% 50%; }
}

/* #endregion Closing CTA gradient */

/* #region Money out the window (homepage audit pitch) */

/*
The background of the #free-audit band: banknotes and coins converging on a lit
window and accelerating through it.

Two decisions worth knowing before changing anything here.

First, the notes are positioned AT the window and animate FROM an offset back to
zero, rather than starting where they are and travelling to the window. It reads
the same on screen, but it means the destination is the window at every screen
width, with no JavaScript measuring anything. Starting offsets are in vw so they
scale with the band instead of drifting off-target on a wide monitor.

Second, the easing is ease-IN, which is the whole effect. A note moving at a
constant speed looks like it is floating; a note that barely moves and then
accelerates into the glass looks like it is being pulled. Swap the curve for
something ease-out and the section instantly becomes a snowstorm.
*/

#free-audit .mip-money {
	position: absolute;
	inset: 0;
	z-index: 1;
	overflow: hidden;
	pointer-events: none;
	perspective: 900px;
	perspective-origin: 80% 50%;
}

/* --- the window ------------------------------------------------------- */

#free-audit .mip-window {
	position: absolute;
	right: 9%;
	top: 50%;
	width: 150px;
	height: 200px;
	transform: translateY(-50%) rotateY(-24deg);
	transform-style: preserve-3d;
}

#free-audit .mip-window .pane {
	position: absolute;
	inset: 0;
	border: 2px solid rgba(255, 255, 255, 0.68);
	border-radius: 3px;
	background: radial-gradient(ellipse at 50% 45%,
		rgba(255, 255, 255, 0.58) 0%,
		rgba(255, 255, 255, 0.19) 55%,
		rgba(255, 255, 255, 0) 80%);
	box-shadow: 0 0 99px 25px rgba(255, 255, 255, 0.22);
	animation: mip-breathe 4.5s ease-in-out infinite;
}

#free-audit .mip-window .bar {
	position: absolute;
	background: rgba(255, 255, 255, 0.53);
}

#free-audit .mip-window .bar.v { left: 50%; top: 0; bottom: 0; width: 2px; margin-left: -1px; }
#free-audit .mip-window .bar.h { top: 44%; left: 0; right: 0; height: 2px; }

/* The pane brightens very slightly on a slow cycle, so the window reads as
   pulling rather than simply being a window. */
@keyframes mip-breathe {
	0%, 100% { filter: brightness(1); }
	50%      { filter: brightness(1.22); }
}

/* Two rings turning opposite ways in the plane of the glass. Small, faint, and
   the single clearest signal that this is suction and not a draught. */
#free-audit .mip-swirl {
	position: absolute;
	left: 50%;
	top: 44%;
	width: 118px;
	height: 118px;
	margin: -59px 0 0 -59px;
	border-radius: 50%;
	opacity: 0.62;
}

#free-audit .mip-swirl::before,
#free-audit .mip-swirl::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: 50%;
	border: 1px solid rgba(255, 255, 255, 0.5);
	border-right-color: transparent;
	border-bottom-color: transparent;
}

#free-audit .mip-swirl::before { animation: mip-spin 3.2s linear infinite; }
#free-audit .mip-swirl::after  { inset: 22px; animation: mip-spin 2.1s linear infinite reverse; }

@keyframes mip-spin { to { transform: rotate(360deg); } }

/* The light leaning out of it. Blurred hard so it reads as glow, not a shape. */
#free-audit .mip-shaft {
	position: absolute;
	right: 3%;
	top: 50%;
	width: 48%;
	height: 330px;
	transform: translateY(-50%);
	background: linear-gradient(255deg,
		rgba(255, 255, 255, 0.19) 0%,
		rgba(255, 255, 255, 0.03) 40%,
		rgba(255, 255, 255, 0) 74%);
	filter: blur(16px);
}

/* --- the money -------------------------------------------------------- */

/* Anchored at the window; the margins put the note's centre on the window's
   centre, so translate3d(0,0,…) at the end of the loop lands it in the glass. */
#free-audit .note,
#free-audit .coin {
	position: absolute;
	right: 9%;
	top: 50%;
	opacity: 0;
	animation-name: mip-suck;
	animation-timing-function: cubic-bezier(0.55, 0.01, 0.86, 0.28);
	animation-iteration-count: infinite;
	will-change: transform, opacity;
}

#free-audit .note {
	width: 46px;
	height: 25px;
	margin: -12px 52px 0 0;
	border-radius: 3px;
	transform-style: preserve-3d;
	background: linear-gradient(115deg,
		rgba(255, 255, 255, 0.94) 0%,
		rgba(226, 240, 238, 0.88) 46%,
		rgba(255, 255, 255, 0.96) 100%);
	box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3);
	animation-name: mip-suck, mip-tumble;
	animation-timing-function: cubic-bezier(0.55, 0.01, 0.86, 0.28), cubic-bezier(0.55, 0.01, 0.86, 0.28);
	animation-iteration-count: infinite, infinite;
}

/* The printed border and the £, drawn rather than fetched. */
#free-audit .note::before {
	content: "";
	position: absolute;
	inset: 3px;
	border: 1px solid rgba(105, 23, 208, 0.28);
	border-radius: 2px;
}

#free-audit .note::after {
	content: "£";
	position: absolute;
	inset: 0;
	display: grid;
	place-items: center;
	font-family: var(--secondary-font);
	font-weight: 700;
	font-size: 12px;
	color: rgba(105, 23, 208, 0.62);
}

/* Flip runs on the inner span so it can keep its own clock — a note tumbling
   end over end at a different rate from its spin never repeats a pose. */
#free-audit .note .inner {
	position: absolute;
	inset: 0;
	transform-style: preserve-3d;
	animation: mip-flip 3s linear infinite;
}

#free-audit .coin {
	width: 13px;
	height: 13px;
	margin: -6px 68px 0 0;
	border-radius: 50%;
	background: radial-gradient(circle at 34% 30%,
		rgba(255, 255, 255, 0.95),
		rgba(7, 190, 184, 0.55) 62%,
		rgba(7, 190, 184, 0.15) 100%);
	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
	filter: blur(0.4px);
}

@keyframes mip-suck {
	0%   { transform: translate3d(var(--sx), var(--sy), 0) scale(1); opacity: 0; }
	10%  { opacity: 0.95; }
	82%  { opacity: 0.92; }
	100% { transform: translate3d(0, 0, -640px) scale(0.06); opacity: 0; }
}

@keyframes mip-tumble {
	0%   { rotate: z 0deg; }
	100% { rotate: z var(--spin, 540deg); }
}

@keyframes mip-flip {
	0%   { transform: rotate3d(1, 0.35, 0, 0deg); }
	100% { transform: rotate3d(1, 0.35, 0, 360deg); }
}

/* Per-note paths. Negative delays start every note mid-flight, so the section
   is already full of money on the first paint rather than filling up over the
   first fifteen seconds. Durations are deliberately non-round and share no
   common factor, which keeps the loop from visibly resetting. */
#free-audit .n1  { --sx: -34vw; --sy: -168px; --spin: 480deg; animation-duration: 11.3s; animation-delay: -2.1s, -2.1s; }
#free-audit .n2  { --sx: -58vw; --sy:   96px; --spin: 720deg; animation-duration: 13.7s; animation-delay: -9.4s, -9.4s; }
#free-audit .n3  { --sx: -22vw; --sy:  184px; --spin: 400deg; animation-duration:  9.6s; animation-delay: -5.2s, -5.2s; }
#free-audit .n4  { --sx: -71vw; --sy:  -74px; --spin: 610deg; animation-duration: 14.9s; animation-delay: -1.3s, -1.3s; }
#free-audit .n5  { --sx: -45vw; --sy: -210px; --spin: 540deg; animation-duration: 12.2s; animation-delay: -7.8s, -7.8s; }
#free-audit .n6  { --sx: -63vw; --sy:  152px; --spin: 860deg; animation-duration: 10.4s; animation-delay: -3.6s, -3.6s; }
#free-audit .n7  { --sx: -29vw; --sy:  -38px; --spin: 460deg; animation-duration: 13.1s; animation-delay: -11.2s, -11.2s; }
#free-audit .n8  { --sx: -80vw; --sy:   28px; --spin: 700deg; animation-duration: 15.3s; animation-delay: -6.5s, -6.5s; }
#free-audit .n9  { --sx: -51vw; --sy:  212px; --spin: 380deg; animation-duration:  9.9s; animation-delay: -0.7s, -0.7s; }
#free-audit .n10 { --sx: -38vw; --sy: -128px; --spin: 640deg; animation-duration: 12.8s; animation-delay: -8.9s, -8.9s; }
#free-audit .n11 { --sx: -67vw; --sy:  -18px; --spin: 520deg; animation-duration: 11.7s; animation-delay: -4.4s, -4.4s; }
#free-audit .n12 { --sx: -26vw; --sy:  118px; --spin: 780deg; animation-duration: 14.2s; animation-delay: -12.6s, -12.6s; }
#free-audit .n13 { --sx: -75vw; --sy: -196px; --spin: 430deg; animation-duration: 10.8s; animation-delay: -2.9s, -2.9s; }
#free-audit .n14 { --sx: -43vw; --sy:   62px; --spin: 590deg; animation-duration: 13.4s; animation-delay: -10.1s, -10.1s; }

#free-audit .n1 .inner  { animation-duration: 2.3s; }
#free-audit .n2 .inner  { animation-duration: 4.1s; }
#free-audit .n3 .inner  { animation-duration: 2.9s; }
#free-audit .n4 .inner  { animation-duration: 3.7s; }
#free-audit .n5 .inner  { animation-duration: 2.1s; }
#free-audit .n6 .inner  { animation-duration: 4.6s; }
#free-audit .n7 .inner  { animation-duration: 3.2s; }
#free-audit .n8 .inner  { animation-duration: 2.6s; }
#free-audit .n9 .inner  { animation-duration: 4.3s; }
#free-audit .n10 .inner { animation-duration: 3.4s; }
#free-audit .n11 .inner { animation-duration: 2.8s; }
#free-audit .n12 .inner { animation-duration: 4.8s; }
#free-audit .n13 .inner { animation-duration: 3.9s; }
#free-audit .n14 .inner { animation-duration: 2.4s; }

#free-audit .c1 { --sx: -40vw; --sy: -142px; animation-duration: 14.6s; animation-delay: -3.1s; }
#free-audit .c2 { --sx: -66vw; --sy:  128px; animation-duration: 12.4s; animation-delay: -8.2s; }
#free-audit .c3 { --sx: -24vw; --sy:  -66px; animation-duration: 16.1s; animation-delay: -1.9s; }
#free-audit .c4 { --sx: -54vw; --sy:  196px; animation-duration: 13.3s; animation-delay: -10.7s; }
#free-audit .c5 { --sx: -77vw; --sy:  -12px; animation-duration: 15.5s; animation-delay: -5.8s; }
#free-audit .c6 { --sx: -33vw; --sy:  164px; animation-duration: 11.9s; animation-delay: -0.4s; }
#free-audit .c7 { --sx: -61vw; --sy: -182px; animation-duration: 16.8s; animation-delay: -7.3s; }
#free-audit .c8 { --sx: -47vw; --sy:   44px; animation-duration: 12.7s; animation-delay: -12.1s; }
#free-audit .c9 { --sx: -70vw; --sy:  -98px; animation-duration: 14.1s; animation-delay: -4.9s; }

/* On a phone the band is short and the window is close to the text, so thin the
   traffic out rather than cramming the same twenty-three objects into a third of
   the width. Coins go first — they are depth, not subject. */
@media (max-width: 767px) {
	#free-audit .coin,
	#free-audit .n4,
	#free-audit .n8,
	#free-audit .n11,
	#free-audit .n13 { display: none; }

	#free-audit .mip-window { width: 104px; height: 140px; right: 6%; }
	#free-audit .mip-swirl { width: 84px; height: 84px; margin: -42px 0 0 -42px; }
	#free-audit .note { width: 38px; height: 21px; margin-right: 34px; }
	#free-audit .note::after { font-size: 10px; }
}

/* #endregion Money out the window */

/* #region Website audit page */

/* The line crediting where the statistics come from. Deliberately quiet — it
   should be findable by anyone who wants to check the numbers without
   competing with the numbers themselves. */
.sjh-audit-source {
	font-size: 0.85rem;
	line-height: 1.6;
	opacity: 0.75;
}

/* #endregion Website audit page */

/* #region Cursor active state */

.cursor-effect.is-active .cursor-effect-inner {
	stroke-width: 2px;
}

/* #endregion Cursor active state */

/* #region Mail toast */

.mail-toast {
	position: fixed;
	left: 50%;
	bottom: 2rem;
	z-index: 1000;
	display: flex;
	align-items: center;
	gap: 0.75rem;
	max-width: calc(100% - 2rem);
	padding: 0.9rem 1.5rem;
	border-radius: 999px;
	background-color: var(--black-color);
	color: var(--white-color);
	font-family: var(--primary-font);
	font-size: 0.95rem;
	font-weight: 700;
	box-shadow: 0 20px 50px rgba(0, 0, 0, 0.25);
	opacity: 0;
	pointer-events: none;
	transform: translate(-50%, 16px);
	transition: opacity 0.35s ease, transform 0.35s cubic-bezier(0.2, 1, 0.2, 1);
}

.mail-toast.show {
	opacity: 1;
	transform: translate(-50%, 0);
}

.mail-toast i {
	color: var(--tertiary-color);
	font-size: 1.1rem;
}

@media (max-width: 480px) {

	.mail-toast {
		font-size: 0.85rem;
		text-align: left;
		padding: 0.8rem 1.1rem;
	}
}

/* #endregion Mail toast */

/* #region Contact form */

/*
main.css already ships .form-area / .form-floating / .form-alert
styling, but it's tuned for light backgrounds — this section sits on
the dark, colorful closing CTA, so text/underlines/alerts need
lighter values than the theme defaults resolve to here. It also
covers the pieces the old jQuery form-sending code used to toggle
(.form-alert.show, .sending) now that main.js does that itself.
*/

.contact-form {
	max-width: 560px;
	margin: 2rem auto 0;
	text-align: left;
}

.contact-form .contact-submit {
	margin: 0.5rem 0 0;
}

.contact-form .form-control {
	color: var(--white-color);
	border-bottom-color: var(--white-25-color) !important;
}

.contact-form .form-control:focus {
	border-bottom-color: var(--tertiary-color) !important;
}

.contact-form .form-label {
	color: var(--white-65-color);
}

.contact-form .form-control::placeholder {
	color: transparent;
}

.contact-form.sending,
.contact-form .contact-submit:disabled {
	opacity: 0.6;
	pointer-events: none;
}

.contact-form .form-alert {
	display: none;
	text-align: left;
}

.contact-form .form-alert.show {
	display: block;
}

.contact-form .form-alert.success {
	color: var(--white-color);
}

.contact-form .form-alert.success::before {
	color: var(--primary-color);
}

.contact-form .form-alert.error {
	color: var(--white-85-color);
}

.contact-form .form-alert.error::before {
	color: var(--tertiary-color);
}

.contact-form .form-alert.error a {
	color: var(--white-color);
	text-decoration: underline;
}

/* #endregion Contact form */

/* #region Reduced motion */

/*
For visitors who've set "reduce motion" at the OS level (Settings >
Accessibility on Mac/iOS/Windows/Android) — collapses every CSS
animation/transition on the page to effectively instant, rather than
turning off individual effects one by one. Elements still reach
their normal end state (hover states, scroll-reveal, nav toggle,
etc. all still work), they just don't animate getting there.
*/
@media (prefers-reduced-motion: reduce) {

	*, *::before, *::after {
		animation-duration: 0.001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.001ms !important;
		scroll-behavior: auto !important;
	}

	/* The custom cursor and the floating hero bubbles are
	continuous, decorative motion with no functional purpose —
	the safety net above stops their CSS keyframes, but the
	cursor's position is also driven every frame straight from
	JS (main.js checks this same media query and skips that
	loop), so hide the element outright as a second guarantee. */
	.cursor-effect {
		display: none !important;
	}

	.bubble {
		display: none;
	}

	/* Scroll-reveal and the portfolio hover-zoom involve real
	directional movement (translate/scale), which is exactly the
	kind of motion this preference is meant to remove — show
	final state immediately instead of sliding/zooming into it. */
	[data-aos] {
		opacity: 1 !important;
		transform: none !important;
	}

	.shuffle-item a.item:hover .image-wrapper .image {
		transform: none;
	}
}

/* #endregion Reduced motion */

/* #region Project template */

/*
Used on portfolio-project.html (the reusable case-study template) —
a dashed placeholder box for wherever a real project screenshot
should eventually go, so the template never looks broken before it's
filled in, and it's obvious at a glance what still needs replacing.
*/

.image-placeholder {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 0.75rem;
	width: 100%;
	min-height: 260px;
	border: 2px dashed var(--gray-50-color);
	border-radius: 1rem;
	background: linear-gradient(135deg, var(--primary-15-color), var(--secondary-15-color));
	color: var(--gray-color);
	text-align: center;
	padding: 2rem;
}

.image-placeholder i {
	font-size: 2rem;
	color: var(--gray-50-color);
}

.image-placeholder span {
	font-size: 0.9rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.05em;
}

.image-placeholder.tall {
	min-height: 420px;
}

/* Quick-facts pills (Role / Timeline / Tools) under the project title */
.project-meta {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem;
	justify-content: center;
	margin-top: 1.5rem;
}

.project-meta .badge {
	padding: 0.5rem 1rem;
}

/* Back-to-portfolio link sitting above the project title */
.project-back {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	margin-bottom: 1.5rem;
	font-size: 0.85rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--white-65-color);
	text-decoration: none;
}

.project-back:hover {
	color: var(--white-color);
}

.project-back i {
	transform: rotate(180deg);
	transition: transform 0.3s ease;
}

.project-back:hover i {
	transform: rotate(180deg) translateX(4px);
}

/* #endregion Project template */
/* ==========================================================
   SAY HELLO — process + enquiry call to action
   Append to the bottom of assets/css/custom.css

   Designed for the dark banner the Say Hello section already
   uses. Every rule is scoped under .sjh-hello, so no other
   part of the site can be affected.
   ========================================================== */

.sjh-hello {
  --sjh-white: #fff;
  --sjh-soft: rgba(255, 255, 255, .78);
  --sjh-faint: rgba(255, 255, 255, .55);
  --sjh-glass: rgba(255, 255, 255, .09);
  --sjh-glass-hi: rgba(255, 255, 255, .14);
  --sjh-edge: rgba(255, 255, 255, .18);
  --sjh-cyan: #6BE9F5;
}

.sjh-hello .content-wrapper { padding-top: 70px; padding-bottom: 70px; }
.sjh-hello .basic-intro.max-w-75 { max-width: 1080px; }
.sjh-hello .description { margin-bottom: 42px; }

/* ---------- the process rail ----------
   A single column at every width. The track is a hairline; the fill grows
   over it as the steps advance. Steps behind the current one stay teal so
   the whole shape of the process is readable at a glance. */
.sjh-hello .sjh-rail {
  position: relative;
  list-style: none;
  max-width: 720px;
  margin: 40px auto 0;
  padding: 0 0 0 62px;
  text-align: left;
  counter-reset: none;
}

/* core.css numbers every <ol> item with a generated "1. 2. 3." counter.
   The rail draws its own numbered dots, so that would print each number
   twice. Suppress the counter here rather than changing the theme. */
.sjh-hello .sjh-rail > li::before { content: none; margin-left: 0; }
.sjh-hello .sjh-rail > li::marker { content: none; }
.sjh-hello .sjh-rail > li { counter-increment: none; margin: 0 0 10px; }

.sjh-hello .sjh-rail-track,
.sjh-hello .sjh-rail-fill {
  position: absolute;
  left: 21px;
  top: 22px;
  width: 2px;
  border-radius: 99px;
}
.sjh-hello .sjh-rail-track { bottom: 22px; background: rgba(255, 255, 255, .18); }
.sjh-hello .sjh-rail-fill {
  height: 0;
  background: linear-gradient(to bottom, var(--sjh-cyan), #ff1791);
  transition: height .6s cubic-bezier(.4, 0, .2, 1);
}

.sjh-hello .sjh-rail-step {
  position: relative;
  border-radius: 16px;
  border: 1px solid transparent;
  background: transparent;
  transition: background-color .4s ease, border-color .4s ease;
}

.sjh-hello .sjh-rail-step.is-on {
  background: var(--sjh-glass);
  border-color: var(--sjh-edge);
}

.sjh-hello .sjh-rail-btn {
  display: block;
  width: 100%;
  padding: 16px 18px;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.sjh-hello .sjh-rail-btn:focus-visible {
  outline: 3px solid var(--sjh-white);
  outline-offset: 3px;
  border-radius: 14px;
}

.sjh-hello .sjh-rail-dot {
  position: absolute;
  left: -62px;
  top: 12px;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .08);
  border: 2px solid var(--sjh-edge);
  color: var(--sjh-faint);
  font-family: var(--secondary-font);
  font-weight: 700;
  font-size: 15px;
  transition: background-color .4s ease, border-color .4s ease, color .4s ease, box-shadow .4s ease;
}

/* Steps already passed stay filled, so the process reads as a whole. */
.sjh-hello .sjh-rail-step.is-done .sjh-rail-dot {
  background: var(--sjh-cyan);
  border-color: var(--sjh-cyan);
  color: #10303a;
}

.sjh-hello .sjh-rail-step.is-on .sjh-rail-dot {
  background: var(--sjh-white);
  border-color: var(--sjh-white);
  color: #22224a;
  box-shadow: 0 0 0 6px rgba(255, 255, 255, .14);
}

.sjh-hello .sjh-rail-title {
  display: block;
  font-family: var(--secondary-font);
  font-size: 16.5px;
  font-weight: 700;
  line-height: 1.35;
  color: var(--sjh-soft);
  transition: color .4s ease;
}
.sjh-hello .sjh-rail-step.is-on .sjh-rail-title { color: var(--sjh-white); }

.sjh-hello .sjh-rail-here {
  display: inline-block;
  margin-top: 8px;
  padding: 3px 10px;
  border-radius: 99px;
  background: var(--sjh-white);
  color: #22224a;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
}

/* No [data-js] means the script never ran: every step stays open. */
.sjh-hello .sjh-rail-body { padding: 0 18px 18px; }

.sjh-hello .sjh-rail[data-js] .sjh-rail-body {
  padding-bottom: 0;
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  transition: max-height .55s ease, opacity .4s ease, padding-bottom .4s ease;
}

.sjh-hello .sjh-rail[data-js] .sjh-rail-step.is-on .sjh-rail-body {
  max-height: 320px;
  opacity: 1;
  padding-bottom: 18px;
}

.sjh-hello .sjh-rail-text {
  margin: 0;
  font-size: 15px;
  line-height: 1.6;
  color: var(--sjh-faint);
}

.sjh-hello .sjh-rail-need {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 14px 0 0;
  padding: 11px 13px;
  border-radius: 10px;
  background: rgba(0, 0, 0, .16);
  font-size: 14px;
  color: var(--sjh-soft);
}
.sjh-hello .sjh-rail-need i { margin-top: 3px; color: var(--sjh-cyan); }
.sjh-hello .sjh-rail-need strong { color: var(--sjh-cyan); font-weight: 700; }

@media (max-width: 575px) {
  .sjh-hello .sjh-rail { padding-left: 52px; margin-top: 30px; }
  .sjh-hello .sjh-rail-track,
  .sjh-hello .sjh-rail-fill { left: 18px; }
  .sjh-hello .sjh-rail-dot { left: -52px; width: 38px; height: 38px; font-size: 14px; }
  .sjh-hello .sjh-rail-btn { padding: 14px 15px; }
  .sjh-hello .sjh-rail-body { padding: 0 15px 15px; }
  .sjh-hello .sjh-rail[data-js] .sjh-rail-step.is-on .sjh-rail-body { padding-bottom: 15px; }
  .sjh-hello .sjh-rail-title { font-size: 15.5px; }
  .sjh-hello .sjh-rail[data-js] .sjh-rail-step.is-on .sjh-rail-body { max-height: 420px; }
}

@media (prefers-reduced-motion: reduce) {
  .sjh-hello .sjh-rail-fill,
  .sjh-hello .sjh-rail-step,
  .sjh-hello .sjh-rail-dot,
  .sjh-hello .sjh-rail-title,
  .sjh-hello .sjh-rail[data-js] .sjh-rail-body { transition: none; }
}

/* ---------- contact globe ----------
   Sits above the closing call to action. Purely decorative: if the canvas
   never runs, the space simply collapses and nothing is lost. */
.sjh-hello .sjh-globe {
  position: relative;
  width: 100%;
  max-width: 560px;
  height: 320px;
  margin: 0 auto 6px;
  pointer-events: none;
}

.sjh-hello .sjh-globe-canvas {
  display: block;
  margin: 0 auto;
}

@media (max-width: 767px) {
  .sjh-hello .sjh-globe { height: 240px; }
}

@media (max-width: 479px) {
  .sjh-hello .sjh-globe { height: 200px; }
}

/* ---------- step dwell bar ----------
   Shows how long the current step has left before it advances, so the
   movement is expected rather than startling. */


/* ---------- panel icon ---------- */


@media (max-width: 575px) {
}

/* ---------- call to action ----------
   Two columns on desktop: the globe carries the atmosphere, the right-hand
   side carries the ask. One button, high contrast, nothing competing. */
.sjh-hello .sjh-cta {
  margin-top: 56px;
  padding-top: 48px;
  border-top: 1px solid rgba(255, 255, 255, .18);
}

.sjh-hello .sjh-cta-inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  align-items: center;
  max-width: 1000px;
  margin: 0 auto;
  text-align: center;
}

@media (min-width: 992px) {
  .sjh-hello .sjh-cta-inner {
    grid-template-columns: 0.95fr 1.05fr;
    gap: 40px;
    text-align: left;
  }
}

.sjh-hello .sjh-cta-visual { position: relative; text-align: center; }

.sjh-hello .sjh-globe-note {
  margin: 0;
  font-size: 12.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .62);
}

.sjh-hello .sjh-cta-eyebrow {
  margin: 0 0 8px;
  font-family: 'Courier New', ui-monospace, monospace;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--sjh-cyan);
}

.sjh-hello .sjh-cta-title {
  font-size: 40px;
  line-height: 1.1;
  font-weight: 800;
  color: var(--sjh-white);
  margin: 0 0 14px;
  letter-spacing: -.02em;
}

.sjh-hello .sjh-cta-sub {
  max-width: 460px;
  margin: 0 0 30px;
  color: var(--sjh-soft);
  font-size: 17px;
  line-height: 1.55;
}

/* Solid white on the purple, because an outlined button on this background
   was the least visible thing in the section. */
.sjh-hello .sjh-cta-btn {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 18px 34px;
  border-radius: 99px;
  background: var(--sjh-white);
  color: #22224a;
  font-size: 17px;
  font-weight: 800;
  text-decoration: none;
  box-shadow: 0 12px 32px rgba(0, 0, 0, .28);
  transition: transform .25s ease, box-shadow .25s ease, background-color .25s ease, color .25s ease;
}

.sjh-hello .sjh-cta-btn i { font-size: 15px; transition: transform .25s ease; }

.sjh-hello .sjh-cta-btn:hover,
.sjh-hello .sjh-cta-btn:focus-visible {
  background: #ff1791;
  color: #fff;
  transform: translateY(-3px);
  box-shadow: 0 18px 40px rgba(255, 23, 145, .38);
}

.sjh-hello .sjh-cta-btn:hover i { transform: translateX(4px); }

.sjh-hello .sjh-cta-btn:focus-visible {
  outline: 3px solid var(--sjh-white);
  outline-offset: 4px;
}

.sjh-hello .sjh-cta-points {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
  justify-content: center;
  list-style: none;
  margin: 24px 0 0;
  padding: 0;
  font-size: 14px;
  color: var(--sjh-faint);
}
.sjh-hello .sjh-cta-points i { margin-right: 7px; opacity: .8; color: var(--sjh-cyan); }

.sjh-hello .sjh-cta-alt {
  margin: 16px 0 0;
  font-size: 14px;
  color: var(--sjh-faint);
}
.sjh-hello .sjh-cta-alt a { color: var(--sjh-white); text-decoration: underline; }

@media (min-width: 992px) {
  .sjh-hello .sjh-cta-points { justify-content: flex-start; }
}

@media (max-width: 575px) {
  .sjh-hello .sjh-cta-title { font-size: 30px; }
  .sjh-hello .sjh-cta-btn { width: 100%; justify-content: center; padding: 17px 24px; }
}

@media (prefers-reduced-motion: reduce) {
  .sjh-hello .sjh-cta-btn,
  .sjh-hello .sjh-cta-btn i { transition: none; }
  .sjh-hello .sjh-cta-btn:hover { transform: none; }
}

/* ---------- responsive ---------- */
@media (max-width: 640px) {
  .sjh-hello .content-wrapper { padding-top: 50px; padding-bottom: 50px; }
  .sjh-hello .sjh-cta-title { font-size: 22px; }
  .sjh-hello .sjh-cta-points { gap: 8px 16px; font-size: 13px; }
}

/* ==========================================================================
   Footer — Sarah J Hill
   Everything here is scoped under .mip-footer so it cannot leak into the
   rest of the page. The theme already styles .mip-footer, .footer-brand,
   .nav-link etc; this only adds the parts it does not cover.
   ========================================================================== */

.mip-footer .footer-content {
  padding: 70px 0 30px;
}

.mip-footer .footer-content .footer-text {
  max-width: 42ch;
  font-size: 0.95rem;
  line-height: 1.7;
  color: var(--gray-color);
}

.mip-footer .footer-content .sjh-footer-meta {
  margin-bottom: 0;
  font-size: 0.9rem;
  opacity: 0.75;
}

.mip-footer .footer-content .sjh-footer-meta i {
  margin-right: 8px;
  color: var(--color);
}

/* Divider + copyright row */
.sjh-footer-bottom {
  margin-top: 3rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--color-3);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 6px 24px;
}

.sjh-footer-bottom .sjh-copyright,
.sjh-footer-bottom .sjh-legal {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--gray-color);
}

.sjh-footer-bottom .sjh-copyright {
  font-weight: 600;
  color: var(--color-2);
}

.sjh-footer-bottom .sjh-legal {
  max-width: 52ch;
  opacity: 0.7;
}

@media (max-width: 767.98px) {
  .mip-footer .footer-content { padding: 50px 0 26px; }
  .sjh-footer-bottom { margin-top: 2.25rem; flex-direction: column; }
}

/* ==========================================================================
   About — benefit list
   Scoped to #about so it cannot affect other .description blocks.
   ========================================================================== */

#about .sjh-lede {
  font-size: 1.15rem;
  font-weight: 600;
  line-height: 1.5;
  color: var(--black-color);
}

/* Uses the theme's own .icon-list so the ticks are identical to the ones on
   the case-study pages — same glyph, same tertiary colour, same offset.
   Only spacing and the bold lead-in are set here. */
#about .sjh-benefits {
  margin: 0 0 1.5rem;
}

#about .sjh-benefits .item {
  margin-bottom: 0.85rem;
  line-height: 1.6;
}

#about .sjh-benefits .item:last-child {
  margin-bottom: 0;
}

/* The theme positions .icon absolutely with `left:0` and NO `top`, which is
   deliberate: with top:auto the icon keeps its static position, so each one
   stays on its own line. Setting `top` here resolved it against the list box
   instead and stacked all six ticks onto the first item. Nudge with margin,
   which leaves the static position intact. */
#about .sjh-benefits .icon {
  font-size: 1.05rem;
  top: auto;            /* explicit: a cached older sheet may still set one */
  margin-top: 0.12em;
}

#about .sjh-benefits strong {
  color: var(--black-color);
}

/* ==========================================================================
   Portfolio — project cards
   Everything scoped under .sjh-work.
   ========================================================================== */

.sjh-work {
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--black-10-color, rgba(0, 0, 0, 0.1));
  border-radius: 14px;
  background: #fff;
  transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
}

.sjh-work:hover {
  transform: translateY(-4px);
  border-color: transparent;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.13);
}

/* Cover: either a screenshot, or a coloured tile with initials */
.sjh-work-cover {
  position: relative;
  aspect-ratio: 16 / 10;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: linear-gradient(135deg, #2b2350, #123a52);
}

.sjh-work-shot {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  transition: transform 0.5s ease;
}


.sjh-work-initial {
  font-family: var(--secondary-font, Poppins, sans-serif);
  font-size: 2.9rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  color: #fff;
  opacity: 0.95;
}

.sjh-work-cover[data-tone="1"] { background: linear-gradient(135deg, #f0654a, #b8321a); }
.sjh-work-cover[data-tone="2"] { background: linear-gradient(135deg, #3b6fd4, #1d3b7a); }
.sjh-work-cover[data-tone="3"] { background: linear-gradient(135deg, #1f9e8c, #0d5f54); }
.sjh-work-cover[data-tone="4"] { background: linear-gradient(135deg, #d4a03b, #8a6413); }
.sjh-work-cover[data-tone="5"] { background: linear-gradient(135deg, #8e5ad4, #4c2a7c); }
.sjh-work-cover[data-tone="6"] { background: linear-gradient(135deg, #6b6f76, #33363b); }

/* Body */
.sjh-work-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  padding: 20px 22px 22px;
}

.sjh-work-kind {
  display: inline-block;
  margin-bottom: 8px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--primary-color, #ff5a3c);
}

.sjh-work.is-wip .sjh-work-kind { color: #8a8a82; }

.sjh-work-title {
  margin: 0 0 8px;
  font-family: var(--secondary-font, Poppins, sans-serif);
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.25;
  color: var(--black-color);
}

.sjh-work-desc {
  margin: 0 0 14px;
  font-size: 0.93rem;
  line-height: 1.6;
  color: var(--gray-color, #5c5c57);
}

/* Descriptions differ in length, so without this the tag row and the links
   row landed at a different height on every card and the grid looked ragged.
   Letting the description absorb the slack aligns them across the row. */
.sjh-work-desc {
  flex: 1 1 auto;
}

.sjh-work-tags {
  margin: 0 0 16px;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  /* align-items defaults to `stretch`, which inflated any tag sitting on a
     wrapped line to the full line height — measured 38px against 22px for the
     rest, which is what made the rows look ragged. */
  align-items: flex-start;
  align-content: flex-start;
  gap: 6px;
  /* Cards with three tags wrap to two rows; cards with two fit on one. That
     12px difference was absorbed by the flexible description above, which
     pushed the tag rows to different heights across the grid. Reserving the
     two-row height on every card fixes it.

     39px, not 38: the natural two-row block measures 38.35px, so a 38px floor
     only clamps the one-row cards and leaves a third of a pixel of drift.
     39 clamps both, and the offsets then match to two decimal places. */
  min-height: 39px;
}

.sjh-work-tags li {
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.055);
  font-size: 0.73rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: #4a4a45;
}

.sjh-work-links {
  margin-top: auto;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
}

.sjh-work-links a {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 0.87rem;
  font-weight: 700;
  text-decoration: none;
  color: var(--black-color);
  border-bottom: 2px solid transparent;
  transition: color 0.25s ease, border-color 0.25s ease;
}

.sjh-work-links a:hover {
  color: var(--primary-color, #ff5a3c);
  border-bottom-color: currentColor;
}

.sjh-work-links a i { font-size: 0.8em; }

/* Filter tabs wrap tidily now there are six of them */
#pages .horizontal-tab .nav-tabs {
  flex-wrap: wrap;
  gap: 4px 0;
}

@media (max-width: 575.98px) {
  .sjh-work-body { padding: 16px 18px 18px; }
  .sjh-work-title { font-size: 1.12rem; }
  .sjh-work-initial { font-size: 2.3rem; }
}

@media (prefers-reduced-motion: reduce) {
  .sjh-work,
  .sjh-work-shot { transition: none; }
  .sjh-work:hover { transform: none; }
  .sjh-work:hover .sjh-work-shot { transform: none; }
}

/* ==========================================================================
   Case-study hero — stack the two halves of the title

   The heading was already centred to the pixel (measured: ink centre 800,
   container centre 800). What made it LOOK shoved right is the theme's
   outline effect: .text-1 is hollow and .text-2 is solid, so on a single
   line all the visual weight lands on the right-hand word and the eye
   reads that as the centre.

   Stacking the halves is how the theme uses this effect everywhere it
   already looks right — and it stops a long project name running the full
   width of the screen. Opt-in via .sjh-stack-title so it only affects the
   heroes we've chosen, never every #top on the site.
   ========================================================================== */
.sjh-stack-title .basic-intro .title .text-1,
.sjh-stack-title .basic-intro .title .text-2 {
  display: block;
}

.sjh-stack-title .basic-intro .title {
  line-height: 1.05;
}

/* ==========================================================================
   Footer link visibility

   The theme paints .nav-link .text with a split gradient and sets
   -webkit-text-fill-color:transparent so the colour can slide on hover.
   That gradient is built from var(--color), which is only set by the theme's
   colour utility classes. Our footer links carry no such class, so --color
   resolved to nothing, the gradient became invalid, background-image computed
   to `none`, and the labels rendered as transparent text on no background —
   invisible.

   Setting --color restores the intended effect. The explicit fill is a belt
   and braces fallback so the text can never vanish again, gradient or not.
   ========================================================================== */
.mip-footer .footer-content .nav-link {
  --color: var(--white-color);
}

.mip-footer .footer-content .nav-link .text {
  -webkit-text-fill-color: var(--gray-color);
  color: var(--gray-color);
}

.mip-footer .footer-content .nav-link:hover .text,
.mip-footer .footer-content .nav-link:focus-visible .text {
  -webkit-text-fill-color: var(--white-color);
  color: var(--white-color);
}

/* ==========================================================================
   Card tags

   The <li> stayed display:list-item inside the flex row, so the pills sized
   themselves inconsistently from card to card. Making each one an inline-flex
   box gives every tag the same height and vertical centring.
   ========================================================================== */
.sjh-work-tags li {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  line-height: 1.4;
  /* THE actual cause of the staggered pills. core.css sets `ul > li {margin:
     8px 0}` and then zeroes it for :first-child and :last-child — so in a row
     of three, the MIDDLE pill kept an 8px top margin and sat 8px lower than
     its neighbours. Measured: tops 333.49 / 341.49 / 333.49.

     Everything I changed before this (align-items, min-height, flex on the
     description) was aligning the tag BLOCK across cards, which was a real
     but different problem. This is the one that was visible. */
  margin: 0;
}

/* ==========================================================================
   Portfolio covers — still image at rest, the live site on hover

   amiresponsive does not show pictures: it embeds the real sites in iframes,
   which is why its screens scroll. Same idea here.

   At rest each card shows a still image, so the page stays light — no site
   loads until you actually point at one. On hover the real site is injected
   in an iframe, rendered at desktop width and scaled down to fit the card,
   then slid slowly upward so you see it from top to bottom.

   The iframe is pointer-events:none on purpose. A live, scrollable frame
   inside a small card would swallow the mouse wheel and trap the page scroll,
   which is fine on a dedicated tool like amiresponsive but hostile on a
   portfolio. The auto-scroll shows the whole page without stealing control.
   ========================================================================== */
.sjh-work-cover.has-shot {
  position: relative;
  overflow: hidden;
  /* A brand-tinted ground rather than near-black. This is what shows while a
     lazy image is still arriving, and a dark rectangle reads as a broken card
     where a tinted one reads as a loading one. */
  background: linear-gradient(135deg, #2b2350, #123a52);
}

/* A card whose live frame refused to load, or took too long. It simply keeps
   its photograph and stops advertising a preview that is not coming. */
.sjh-work-cover.is-shy::after { display: none; }

.sjh-work-mock {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  object-position: center;
  opacity: 1;
  z-index: 1;
}

.sjh-work-live {
  position: absolute;
  top: 0;
  left: 0;
  width: 1280px;                 /* render at desktop width... */
  height: 3200px;                /* ...and tall enough to hold a long page */
  transform-origin: top left;
  opacity: 0;
  pointer-events: none;          /* never hijacks the page scroll */
  /* The scale is applied HERE, not on hover. Putting it in the hovered rule
     meant the 7s transform transition animated the scale as well: the frame
     appeared at full 1280px size and slowly shrank into the card, which read
     as a huge zoom. Scale is identical in both states now, so the transition
     has only the translateY left to animate. */
  transform: scale(var(--sjh-fit, .25)) translateY(0);
  transition: opacity .5s ease;
  z-index: 2;
}

/* --sjh-fit is the scale that makes 1280px fit the card, set in JS.
   --sjh-travel is how far it may slide before the page ends.
   The photograph underneath is deliberately left visible: it costs nothing
   once the frame paints over it, and it is what you see instead of an empty
   card while the frame is still on its way. */

.sjh-work-cover.is-live .sjh-work-live {
  opacity: 1;
  transform: scale(var(--sjh-fit, .25)) translateY(var(--sjh-travel, 0px));
  transition: opacity .5s ease, transform 7s cubic-bezier(.33,0,.2,1);
}

/* The frame lives inside the wrapper; the WRAPPER is what gets transformed.
   Some engines refuse a transform applied directly to an <iframe>. */
.sjh-work-live iframe {
  width: 1280px;
  height: 3200px;
  border: 0;
  display: block;
}

/* Small "live" flag so it is obvious the frame is the real site */
.sjh-work-cover[data-live]::after {
  content: "Live";
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 3;
  padding: 2px 9px;
  border-radius: 999px;
  background: rgba(17, 22, 26, .72);
  color: #fff;
  font-family: var(--secondary-font);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  opacity: 0;
  transition: opacity .4s ease;
  pointer-events: none;
}

.sjh-work-cover.is-live::after { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .sjh-work-live { transition: opacity .3s ease; }
  .sjh-work-cover.is-live .sjh-work-live {
    transform: scale(var(--sjh-fit, .25));
  }
}

/* Touch devices get no hover, and eight iframes on a phone would be unkind.
   The still image is the whole experience there. */
/* Touch: the preview is opt-in, so the frame is only in the way until it is
   asked for. The "Live" flag is replaced by a real control. */
@media (hover: none) {
  .sjh-work-cover:not(.is-live) .sjh-work-live { display: none; }
  .sjh-work-cover[data-live]::after { display: none; }
}

.sjh-work-play { display: none; }

@media (hover: none) {
  .sjh-work-cover[data-live] .sjh-work-play {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    position: absolute;
    right: 10px;
    bottom: 10px;
    z-index: 3;
    padding: 8px 14px;
    min-height: 44px;              /* a real hit target, not a decoration */
    border: 0;
    border-radius: 999px;
    background: rgba(17, 22, 26, .82);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    color: #fff;
    font-family: var(--secondary-font);
    font-size: 12.5px;
    font-weight: 700;
    letter-spacing: .04em;
    cursor: pointer;
  }

  .sjh-work-cover[data-live].is-shy .sjh-work-play { display: none; }

  .sjh-work-play.is-playing { background: #ff1791; }
  .sjh-work-play.is-playing i { transform: rotate(90deg); }
  .sjh-work-play i { font-size: 11px; transition: transform .3s ease; }
}

/* ==========================================================================
   What I do — skill cards and client quotes
   ========================================================================== */

/* Five cards, three then two. The two on the last row are nudged to the
   middle rather than left hanging at the left edge: a six-column grid where
   every card spans two, and cards 4 and 5 start one column in. */
.sjh-skills {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 20px;
  grid-template-columns: 1fr;
}

@media (min-width: 576px) {
  .sjh-skills { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 992px) {
  .sjh-skills { grid-template-columns: repeat(6, 1fr); }
  .sjh-skill { grid-column: span 2; }
  .sjh-skill:nth-child(4) { grid-column: 2 / span 2; }
  .sjh-skill:nth-child(5) { grid-column: 4 / span 2; }
}

.sjh-skill {
  position: relative;
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 30px 28px 26px;
  border: 1px solid var(--gray-25-color);
  border-radius: 14px;
  background: #fff;
  transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
}

/* The gradient hairline along the top only appears on hover, so the resting
   state stays quiet and the row does not look like five lit-up buttons. */
.sjh-skill::before {
  content: "";
  position: absolute;
  inset: -1px -1px auto;
  height: 3px;
  border-radius: 14px 14px 0 0;
  background: linear-gradient(to right, var(--primary-color), var(--tertiary-color));
  opacity: 0;
  transition: opacity 0.3s ease;
}

.sjh-skill:hover {
  transform: translateY(-4px);
  border-color: transparent;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.12);
}

.sjh-skill:hover::before { opacity: 1; }

.sjh-skill-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  margin-bottom: 18px;
  border-radius: 14px;
  font-size: 1.25rem;
  color: var(--primary-color);
  background: var(--gray-10-color);
  transition: color 0.3s ease, background-color 0.3s ease;
}

.sjh-skill:hover .sjh-skill-icon {
  color: #fff;
  background: linear-gradient(135deg, var(--primary-color), var(--tertiary-color));
}

.sjh-skill-title {
  margin: 0 0 10px;
  font-family: var(--secondary-font);
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.3;
  color: var(--black-color);
}

.sjh-skill-body {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.65;
  color: var(--gray-color);
}

/* Pushes the payoff line to the bottom so it sits on the same baseline across
   the row, however long the paragraph above it happens to run. */
.sjh-skill-win {
  margin: 18px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--gray-25-color);
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--black-color);
  margin-top: auto;
}

.sjh-skill-win strong { color: var(--primary-color); }

/* ---------- client quotes ---------- */

.sjh-quotes { margin-top: 46px; }

.sjh-quotes-lead {
  margin: 0 0 22px;
  font-family: var(--secondary-font);
  font-size: 1.05rem;
  font-weight: 700;
  font-style: italic;
  color: var(--black-color);
}

.sjh-quote-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 20px;
  grid-template-columns: 1fr;
}

@media (min-width: 768px) {
  .sjh-quote-list { grid-template-columns: repeat(3, 1fr); }
}

.sjh-quote-list > li { margin: 0; }

.sjh-quote {
  height: 100%;
  margin: 0;
  padding: 26px 26px 22px;
  border-radius: 14px;
  background: var(--gray-10-color);
  display: flex;
  flex-direction: column;
}

/* An open quote mark, sized down and set in the heading face. Decorative, so
   it is a pseudo-element and never reaches a screen reader. */
.sjh-quote::before {
  content: "\201C";
  display: block;
  margin-bottom: -14px;
  font-family: var(--secondary-font);
  font-size: 2.6rem;
  line-height: 1;
  color: var(--primary-color);
}

.sjh-quote blockquote { margin: 0; }

.sjh-quote blockquote p {
  margin: 0;
  font-size: 0.98rem;
  font-style: italic;
  line-height: 1.6;
  color: var(--black-color);
}

.sjh-quote figcaption {
  margin-top: auto;
  padding-top: 16px;
  font-family: var(--secondary-font);
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--black-color);
}

.sjh-quote figcaption span {
  display: block;
  font-weight: 400;
  color: var(--gray-color);
}

@media (prefers-reduced-motion: reduce) {
  .sjh-skill,
  .sjh-skill:hover { transform: none; }
}

/* ==========================================================================
   Embedded video
   ========================================================================== */

/* 16:9 without the old padding-bottom hack. aspect-ratio has been safe in
   every browser this site targets for years. */
.sjh-video {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 14px;
  background: var(--black-color);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.14);
}

.sjh-video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.sjh-video-note {
  margin: 18px 0 0;
  text-align: center;
  font-size: 0.9rem;
}

.sjh-video-note a {
  color: var(--primary-color);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.sjh-video-note a:hover {
  color: var(--tertiary-color);
  border-bottom-color: currentColor;
}

.sjh-video-note .fa-solid { font-size: 0.75em; margin-left: 4px; }

/* ---------- media list ---------- */

.sjh-media-groups {
  display: grid;
  gap: 24px;
  grid-template-columns: 1fr;
}

@media (min-width: 768px) {
  .sjh-media-groups { grid-template-columns: repeat(3, 1fr); align-items: start; }
}

.sjh-media-group {
  padding: 26px 24px 20px;
  border-radius: 14px;
  background: #fff;
  border: 1px solid var(--gray-25-color);
}

.sjh-media-heading {
  margin: 0 0 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--gray-25-color);
  font-family: var(--secondary-font);
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--primary-color);
}

.sjh-media-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* core.css puts 8px of vertical margin on every ul > li, which is what
   staggered the portfolio tag pills. Zeroed here for the same reason. */
.sjh-media-list li { margin: 0; }

.sjh-media-list a {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 9px 0;
  font-size: 0.92rem;
  line-height: 1.45;
  color: var(--black-color);
  text-decoration: none;
  transition: color 0.2s ease;
}

.sjh-media-list a:hover { color: var(--tertiary-color); }

.sjh-media-list a .fa-solid {
  flex: 0 0 auto;
  font-size: 0.7em;
  color: var(--primary-color);
  transition: transform 0.2s ease, color 0.2s ease;
}

.sjh-media-list a:hover .fa-solid {
  transform: translateX(3px);
  color: var(--tertiary-color);
}

@media (prefers-reduced-motion: reduce) {
  .sjh-media-list a:hover .fa-solid { transform: none; }
}

/* ==========================================================================
   Archive grid and lightbox
   ========================================================================== */

/* A grid of its own rather than Bootstrap columns.

   .shuffle-item — reused here so the filter buttons work without new JS —
   carries `width: 100% !important` from the homepage portfolio, where the
   columns had to be neutralised so they could not fight that grid. No
   column class can outrank it, so the tiles were each taking a full row.
   Inside a grid, `width: 100%` means "fill your cell", which is right. */
.sjh-tiles {
  margin-top: 28px;
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}

@media (min-width: 576px) {
  .sjh-tiles { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
}

@media (min-width: 992px) {
  .sjh-tiles { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
}

/* A button, not a div with a click handler — it needs to be reachable by
   keyboard and announce itself, and a button does both for free. */
.sjh-tile {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid var(--gray-25-color);
  border-radius: 12px;
  overflow: hidden;
  background: #fff;
  cursor: pointer;
  transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
}

.sjh-tile img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 580 / 280;
  object-fit: cover;
  transition: transform 0.5s ease;
}

.sjh-tile:hover,
.sjh-tile:focus-visible {
  transform: translateY(-3px);
  border-color: transparent;
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.16);
  outline: none;
}

.sjh-tile:focus-visible { box-shadow: 0 0 0 3px var(--primary-color); }

.sjh-tile:hover img { transform: scale(1.05); }

/* Caption rides in from the bottom on hover. On touch there is no hover, so
   it is pinned open instead — see the media query at the end. */
.sjh-tile-cap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 22px 12px 10px;
  font-family: var(--secondary-font);
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1.3;
  color: #fff;
  text-align: left;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.82), transparent);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.sjh-tile:hover .sjh-tile-cap,
.sjh-tile:focus-visible .sjh-tile-cap {
  opacity: 1;
  transform: translateY(0);
}

/* ---------- the viewer ---------- */

.sjh-lightbox {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(10, 10, 14, 0.94);
  animation: sjh-lb-in 0.25s ease;
}

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

@keyframes sjh-lb-in { from { opacity: 0; } to { opacity: 1; } }

/* Stops the page behind scrolling while the viewer is open. */
body.sjh-lb-open { overflow: hidden; }

.sjh-lb-figure {
  margin: 0;
  max-width: 1100px;
  width: 100%;
  text-align: center;
}

.sjh-lb-image {
  max-width: 100%;
  max-height: 76vh;
  border-radius: 10px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
}

.sjh-lb-caption {
  margin-top: 18px;
  font-family: var(--secondary-font);
  font-size: 0.9rem;
  color: #fff;
}

.sjh-lb-count {
  display: block;
  margin-top: 6px;
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--gray-color);
}

.sjh-lb-close,
.sjh-lb-nav {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 50%;
  color: #fff;
  background: rgba(255, 255, 255, 0.08);
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}

.sjh-lb-close:hover,
.sjh-lb-nav:hover,
.sjh-lb-close:focus-visible,
.sjh-lb-nav:focus-visible {
  background: var(--tertiary-color);
  border-color: var(--tertiary-color);
  outline: none;
}

.sjh-lb-close {
  top: 20px;
  right: 20px;
  width: 44px;
  height: 44px;
  font-size: 1.6rem;
  line-height: 1;
}

.sjh-lb-nav {
  top: 50%;
  width: 52px;
  height: 52px;
  margin-top: -26px;
  font-size: 1rem;
}

.sjh-lb-next { right: 20px; }
.sjh-lb-prev { left: 20px; }

/* One arrow glyph in the icon font, turned around for "previous" — cheaper
   than carrying a second glyph for the sake of pointing the other way. */
.sjh-lb-prev .fa-solid { transform: rotate(180deg); }

@media (max-width: 575.98px) {
  .sjh-lightbox { padding: 12px; }
  .sjh-lb-nav { width: 42px; height: 42px; margin-top: -21px; }
  .sjh-lb-next { right: 8px; }
  .sjh-lb-prev { left: 8px; }
  .sjh-lb-image { max-height: 62vh; }
}

/* No hover on touch, so the caption would never appear. Pinned open instead. */
@media (hover: none) {
  .sjh-tile-cap { opacity: 1; transform: none; }
  .sjh-tile:hover img { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .sjh-tile,
  .sjh-tile:hover,
  .sjh-tile img,
  .sjh-tile:hover img { transform: none; transition: none; }
  .sjh-lightbox { animation: none; }
}

/* ==========================================================================
   Bootstrap utilities that the deep clean removed
   --------------------------------------------------------------------------
   Stripping bootstrap.min.css down to what the site used at the time was the
   right call — 483 KB to 90 KB. The catch is that anything added later can
   quietly reference a class that is no longer there, and a missing grid
   utility fails silently: no error, just a layout that collapses.

   .g-3 was the one that bit. .row sets --bs-gutter-y:0, and only a .g-* class
   raises it. Without .g-3 the archive tiles had horizontal gutters but zero
   vertical gap, so the rows sat flush on top of one another.

   Copied from Bootstrap 5.2.0 verbatim rather than reinvented, so they behave
   identically to the ones that survived.
   ========================================================================== */

.g-3,
.gx-3 { --bs-gutter-x: 1rem; }

.g-3,
.gy-3 { --bs-gutter-y: 1rem; }

.justify-content-center { justify-content: center !important; }

@media (min-width: 992px) {
  .col-lg-8 { flex: 0 0 auto; width: 66.66666667%; }
  .col-lg-9 { flex: 0 0 auto; width: 75%; }
}
