/**
 * MJM presentational CSS.
 *
 * Design tokens live in theme.json. This file covers two things theme.json
 * cannot express: the light/dark colour scheme swap, and the bespoke
 * interactions (circle-grow buttons, pinned scroll sections, the mode toggle).
 */

/* -------------------------------------------------------------------------
 * Focus rings
 *
 * WordPress core ships `:where(.wp-site-blocks :focus) { outline: solid 2px }`,
 * which is `:focus` rather than `:focus-visible`. On a phone that means a tap
 * leaves a 2px black box drawn around whatever you touched -- a <summary>, a
 * pip, a button -- and it stays there until you tap something else. It is not a
 * devtools artefact: tapping the accordion header in a touch context gives
 * `:focus` true, `:focus-visible` false, and a computed `solid 2px rgb(0,0,0)`.
 *
 * So drop it in exactly the case the browser is telling us not to draw it, and
 * leave it alone otherwise -- keyboard users still get core's ring on anything
 * we have not styled ourselves. Every focus style of our own is already
 * `:focus-visible`, so nothing here loses its ring.
 *
 * Specificity: core's `:where()` counts for nothing, so its selector is (0,1,0)
 * and ours is (0,3,0).
 * ---------------------------------------------------------------------- */
.wp-site-blocks :focus:not(:focus-visible) {
	outline: none;
}

/* -------------------------------------------------------------------------
 * Colour scheme
 *
 * theme.json emits the light values as :root custom properties. We redefine
 * only the two semantic tokens (base = page ground, contrast = text) so every
 * core block that uses them flips for free. Brand colours never change.
 * ---------------------------------------------------------------------- */

/* Semantic tokens. Anything that differs between light and dark is declared
   here once, so the rules below never have to know which mode they are in and
   there are no colour literals scattered through the file. */
:root {
	/* Client logos: monochrome at rest, revealed on hover.
	 *
	 * No fade at rest in light mode. MJM found these too faint to read, and the
	 * fade was most of why: several of the lockups are pale grey line art to
	 * begin with, so dropping the opacity on top of that was what pushed them
	 * out of reach. Measuring mean ink luminance across the 22, 0.6 gave 165 and
	 * the palest of them 192; full opacity gives 106 and 154. A contrast boost
	 * was tried instead and came out worse than simply not fading them.
	 *
	 * Hover still returns the colour, which was always the larger half of the
	 * effect -- it is only the fade that has gone. */
	--mjm-logo-filter: grayscale(1);
	--mjm-logo-filter-active: none;
	--mjm-logo-rest: 1;
	--mjm-logo-dim: 0.28;
}

:root[data-theme="dark"] {
	--wp--preset--color--base: #000000;
	--wp--preset--color--contrast: #ffffff;

	/* The logo artwork is near-black line art with no baked-in white plate, so
	   on a dark ground it has to be tinted white rather than merely desaturated.
	   brightness(0) forces every pixel to black, invert(1) then to pure white,
	   which works whatever colour the source art is. */
	--mjm-logo-filter: brightness(0) invert(1);
	--mjm-logo-filter-active: brightness(0) invert(1);

	/* Lifted for the same reason, but not all the way. Dark mode tints the
	   artwork white with the same filter at rest and on hover, so opacity is the
	   only thing hover has left to change -- at 1 there would be no hover state
	   at all. 0.85 puts the ink at #d9d9d9, well clear of the #999 it was. */
	--mjm-logo-rest: 0.85;
	--mjm-logo-dim: 0.28;
}

:root[data-theme="dark"] body {
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
}

/* Honour the OS preference when the visitor has not chosen explicitly. The
   inline head script sets data-theme before first paint, so this is a
   belt-and-braces fallback for a no-JS visitor. */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) {
		--wp--preset--color--base: #000000;
		--wp--preset--color--contrast: #ffffff;
	}

	:root:not([data-theme="light"]) body {
		background-color: var(--wp--preset--color--base);
		color: var(--wp--preset--color--contrast);
	}
}

body {
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

/* Guidelines forbid Thin and ExtraLight; make it impossible to hit them by
   accident through a variable-font weight animation or inherited value. */
body,
body * {
	font-variation-settings: normal;
}

/* -------------------------------------------------------------------------
 * Box model
 *
 * WordPress ships no global border-box reset, so any of our components that set
 * an explicit width or inline-size alongside padding grows by exactly that
 * padding. On a phone that pushed the hero 40px and the Story/System/Good cards
 * 96px past the viewport; the hero was overflowing on desktop too, just off the
 * right edge where it was easy to miss.
 *
 * Scoped to our own class namespace rather than a universal selector, so core
 * block metrics are left exactly as WordPress calculates them.
 * ---------------------------------------------------------------------- */

[class*="mjm-"],
[class*="mjm-"]::before,
[class*="mjm-"]::after {
	box-sizing: border-box;
}

/* -------------------------------------------------------------------------
 * Root-level spacing
 *
 * theme.json sets a 1.25rem blockGap, and core turns that into
 * `:where(.wp-site-blocks) > * { margin-block-start: 1.25rem }` -- a gap
 * between the header, main and footer regions, and between every top-level
 * section inside the content.
 *
 * That is wrong here. Every one of those regions and sections carries its own
 * vertical padding, so the gap is dead space -- and because it sits outside
 * the section it shows the page background through. Above the black footer
 * that read as a 20px white band; between two coloured bands it would read as
 * a seam. The blockGap is still wanted *within* a section, so scope this to
 * the top level only.
 * ---------------------------------------------------------------------- */

.wp-site-blocks > header,
.wp-site-blocks > main,
.wp-site-blocks > footer {
	margin-block-start: 0;
}

/* Keep the footer at the bottom of the window on pages shorter than the
   viewport -- Contact, mainly. main takes the slack rather than the footer
   growing, so the footer keeps its own padding.
   svh rather than vh so mobile browser chrome does not push the footer below
   the fold; the vh line is the fallback for anything without svh support. */
.wp-site-blocks {
	display: flex;
	flex-direction: column;
	min-height: 100vh;
	min-height: 100svh;
}

.wp-site-blocks > main {
	flex: 1 0 auto;
}

/* Full-bleed sections inside the content, same reasoning. Ordinary blocks --
   paragraphs, headings on a plain page -- keep their gap. */
.wp-block-post-content > .alignfull,
.wp-block-post-content > [class*="mjm-"] {
	margin-block-start: 0;
}

/* -------------------------------------------------------------------------
 * Header
 * ---------------------------------------------------------------------- */

/* Sticky lives on the template part's own <header>, not the group inside it:
   a sticky element cannot leave its containing block, and the inner group's
   block was only as tall as the header itself -- so it scrolled straight away.
   As a direct child of .wp-site-blocks it has the whole page to stick within. */
.wp-site-blocks > header {
	position: sticky;
	top: 0;
	z-index: 100;
	background-color: var(--wp--preset--color--base);
}

/* The wordmark is a fixed mark, not something to squeeze: without this it is a
   flex item with the default shrink factor, and between 600 and 767 it gave up
   as much as 27 of its 105px to the nav beside it. Its size comes from the
   block's own width attribute -- see `.mjm-logo svg`. */
.mjm-header__logo {
	flex-shrink: 0;
}

/* The header decides for itself when to fold the menu away.
 *
 * Core switches to its overlay under 600px, which is too late for this menu:
 * the four links measure 222px before any gap at all, against the room left
 * once the wordmark and the Contact/toggle group have taken theirs. It used to
 * take three breakpoints to paper over that -- an overlay threshold at 660 and
 * a tighter gap between 660 and 767 to squeeze the row into the gap between
 * them. One threshold does it, and the widths that were being squeezed get the
 * overlay, which is what it is for.
 *
 * Asked of the header's own width rather than the window's. It is a full-width
 * element so the two agree today, but the question the menu is really asking is
 * "do I have room", and a container query is that question. Put anything in a
 * narrower column later and it still answers correctly. The number is that
 * content box rather than the window. 809 is the width at which the four links
 * stop fitting beside the wordmark at the 48px gap the design gives them: above
 * it the menu is drawn as designed, below it the overlay takes over. There is
 * no third state where the row is squeezed, which is what the two breakpoints
 * either side of 660 were buying, and what wrapped the menu onto a second line
 * when either was slightly wrong.
 *
 * Both selectors carry the core ones plus `.mjm-nav`, because core sets these
 * inside its own `@media (min-width: 600px)` block at three and two classes
 * respectively. */
.mjm-header {
	container: mjm-header / inline-size;
}

@container mjm-header (max-width: 809px) {
	.mjm-nav .wp-block-navigation__responsive-container:not(.hidden-by-default):not(.is-menu-open) {
		display: none;
	}

	.mjm-nav .wp-block-navigation__responsive-container-open:not(.always-shown) {
		display: flex;
	}
}

/* 18px Light, from the nav frame in Figma. The `large` token is that 18px. */
.mjm-nav a {
	font-size: var(--wp--preset--font-size--large);
	font-weight: 300;
	text-decoration: none;
}

.mjm-nav a:hover,
.mjm-nav a:focus-visible {
	text-decoration: underline;
	text-underline-offset: 0.25em;
}

/* -------------------------------------------------------------------------
 * Light / dark mode toggle
 *
 * The SVG is inlined into the header so the paths are animatable. The thumb
 * travels from cx 11.43 to cx 35.36 — a 24px slide in a 47-unit viewBox.
 * ---------------------------------------------------------------------- */

.mjm-mode-toggle {
	display: inline-flex;
	align-items: center;
	padding: 0;
	border: 0;
	background: none;
	cursor: pointer;
	line-height: 0;
	border-radius: 999px;
}

/* The parent theme sets `:where(.wp-site-blocks *:focus) { outline-width: 2px;
   outline-style: solid }` with no colour, so a plain mouse click paints a
   currentColor (black) ring offset from the pill. Suppress that, and keep a
   deliberate ring for keyboard users only. */
.mjm-mode-toggle:focus {
	outline: none;
}

.mjm-mode-toggle:focus-visible {
	outline: 2px solid var(--wp--preset--color--bonfire);
	outline-offset: 0;
}

.mjm-mode-toggle svg {
	display: block;
	width: 47px;
	height: 23px;
	overflow: visible;
}

/* Thumb travels from cx 11.43 to cx 35.36 -- a 24px slide in a 47-unit
   viewBox, rounded to an even integer. */
.mjm-mode-toggle [id="thumb-shape"] {
	transform: translateX(0);
}

.mjm-mode-toggle [id="background"],
.mjm-mode-toggle [id="thumb-shape"],
.mjm-mode-toggle [id="sun-shape"],
.mjm-mode-toggle [id="moon-shape"] {
	transition: transform 320ms cubic-bezier(0.4, 0, 0.2, 1), fill 200ms ease,
		opacity 200ms ease;
}

/* Only the icon the thumb is NOT covering is shown, so the visible glyph
   always names the mode you would switch to. Light leaves the asset as it
   ships: dark pill, white thumb on the left over a hidden sun, white moon on
   the right. */
.mjm-mode-toggle [id="sun-shape"] {
	opacity: 0;
}

.mjm-mode-toggle [id="moon-shape"] {
	opacity: 1;
}

/* Dark inverts the whole control -- white pill, black thumb, black sun -- so
   it reads against the black page instead of disappearing into it. All three
   fills are overridden because the asset ships the opposite way round. */
:root[data-theme="dark"] .mjm-mode-toggle [id="background"] {
	fill: var(--wp--preset--color--clarity);
}

:root[data-theme="dark"] .mjm-mode-toggle [id="thumb-shape"] {
	fill: var(--wp--preset--color--circle-black);
	transform: translateX(24px);
}

:root[data-theme="dark"] .mjm-mode-toggle [id="sun-shape"] {
	fill: var(--wp--preset--color--circle-black);
	opacity: 1;
}

:root[data-theme="dark"] .mjm-mode-toggle [id="moon-shape"] {
	opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
	.mjm-mode-toggle [id="thumb-shape"],
	.mjm-mode-toggle [id="sun-shape"],
	.mjm-mode-toggle [id="moon-shape"] {
		transition: none;
	}
}

/* -------------------------------------------------------------------------
 * Circle-grow buttons
 *
 * Default: a filled circle sitting to the left of the label. On hover the
 * circle expands into a pill behind the whole label. Implemented with a
 * pseudo-element that scales in width only, so the text never reflows.
 * ---------------------------------------------------------------------- */

.mjm-cbtn {
	--mjm-cbtn-color: var(--wp--preset--color--bonfire);
	--mjm-cbtn-size: 2.625rem;
	--mjm-cbtn-gap: 0.75rem;

	position: relative;
	display: inline-flex;
	align-items: center;
	gap: var(--mjm-cbtn-gap);
	/* Trailing space matches the leading circle + gap, so once the background
	   has grown into a full pill the label sits centred in it rather than
	   pushed right by the space the circle used to occupy. */
	padding-inline: 0 calc(var(--mjm-cbtn-size) + var(--mjm-cbtn-gap));
	padding-block: 0;
	min-height: var(--mjm-cbtn-size);
	font-size: var(--wp--preset--font-size--large);
	font-weight: 600;
	line-height: 1.2;
	text-decoration: none;
	color: var(--wp--preset--color--contrast);
	isolation: isolate;
}

/* The growing shape. Width is animated from the circle diameter to 100%. */
/* The circle has to stay a circle however tall the label is. inset-block: 0 tied
   its height to the button's, which is fine while the label fits the 42px
   min-height, but the stacked variant puts the client name over the discipline
   and on a phone the discipline takes a second line of its own -- three lines,
   55px, and the circle rendered as an oval. So it is a fixed square at rest,
   centred on the label, and still grows into the full pill on hover. */
.mjm-cbtn::before {
	content: "";
	position: absolute;
	inset-inline-start: 0;
	inset-block-start: 50%;
	translate: 0 -50%;
	z-index: -1;
	width: var(--mjm-cbtn-size);
	height: var(--mjm-cbtn-size);
	border-radius: 999px;
	background-color: var(--mjm-cbtn-color);
	transition:
		width 280ms cubic-bezier(0.4, 0, 0.2, 1),
		height 280ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Reserve the circle's footprint so the label starts to its right. */
.mjm-cbtn__spacer {
	display: inline-block;
	inline-size: var(--mjm-cbtn-size);
	flex: 0 0 auto;
}

.mjm-cbtn__label {
	position: relative;
	transition: color 280ms ease;
}

@media (hover: hover) and (pointer: fine) {
	.mjm-cbtn:hover::before,
	.mjm-cbtn:focus-visible::before {
		width: 100%;
		height: 100%;
	}

	.mjm-cbtn:hover .mjm-cbtn__label,
	.mjm-cbtn:focus-visible .mjm-cbtn__label {
		color: var(--wp--preset--color--clarity);
	}
}

/* Touch: no animation, a tap navigates. */
@media not all and (hover: hover) {
	.mjm-cbtn::before {
		transition: none;
	}
}

@media (prefers-reduced-motion: reduce) {
	.mjm-cbtn::before,
	.mjm-cbtn__label {
		transition: none;
	}
}

/* Arrow-style secondary button ("↘ View Our Work"). */
.mjm-arrow-btn {
	display: inline-flex;
	align-items: center;
	gap: 0.625rem;
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 600;
	text-decoration: none;
}

.mjm-arrow-btn svg {
	width: 18px;
	height: 18px;
	flex: 0 0 auto;
	transition: transform 200ms ease;
}

.mjm-arrow-btn:hover svg {
	transform: translate(2px, 2px);
}

/* -------------------------------------------------------------------------
 * Utilities
 * ---------------------------------------------------------------------- */

.mjm-serif {
	font-family: var(--wp--preset--font-family--serif);
	font-weight: 400;
	letter-spacing: -0.015em;
}

.mjm-eyebrow {
	font-size: var(--wp--preset--font-size--tiny);
	font-weight: 700;
	letter-spacing: 0.01em;
}

.mjm-screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* Logo block: width is set per-instance via a custom property. */
.mjm-logo svg {
	display: block;

	/* Capped at the container, because the width is a per-instance number from
	   the block and the footer wordmark's 392px -- which the comp calls for --
	   is wider than a 390px phone, so uncapped it pushed every page sideways.
	 *
	 * As max-width rather than min(width, 100%). The two render identically, but
	 * a percentage inside `width` is indefinite while a flex item's base size is
	 * being worked out, so the browser fell back to the SVG's own width
	 * attribute: the header wordmark drew at 105px while reserving 600, which
	 * left the nav no room and wrapped it onto a second line. A definite width
	 * contributes 105px, and max-width still clamps it on a narrow screen. */
	width: var(--mjm-logo-width, 105px);
	max-width: 100%;
	height: auto;
}

/* -------------------------------------------------------------------------
 * Footer
 *
 * Three columns separated by hairline rules, as in the comp. The rules are
 * their own elements rather than borders on the middle column: as a border the
 * right-hand rule sat on that column's own edge, which is 1116 where the comp
 * draws it at 1185 -- the column is narrower than the room it is given, and a
 * border cannot tell the difference. As flex items they are placed by the same
 * distribution as everything else, and they stretch to the row's height rather
 * than to the middle column's content.
 * ---------------------------------------------------------------------- */

/* The footer answers its own layout question -- when three columns stop being
   three columns is about the room this block has, not about the window.
 *
 * On the wrapper rather than on the flex row itself: an inline-size container
 * stops its own contents from informing its width, and with it on the row the
 * columns lost their intrinsic sizes entirely -- at 390 the wordmark rendered
 * 71px wide and the brand logos 25px. */
.mjm-footer {
	container: mjm-footer / inline-size;
}

.mjm-footer a {
	color: inherit;
	text-decoration: none;
}

.mjm-footer a:hover,
.mjm-footer a:focus-visible {
	text-decoration: underline;
}

.mjm-footer__grid {
	align-items: flex-start;
}

/* The column gap is a floor, not the spacing.
 *
 * justify-content: space-between hands out whatever is left over, so where
 * there is room the separators sit well clear on their own and the gap is
 * never reached. Where there is not -- anywhere below about 1150 -- there is
 * no free space to hand out, and without a gap the two rules end up flush
 * against the sections either side of them.
 *
 * 48 rather than the 64 this block asks for: free space alone puts 50 between
 * everything at 1440, so any floor at or under that leaves the wide layout
 * exactly as it was and only does something once the row tightens. 64 would
 * start squeezing the columns at full width to pay for itself.
 *
 * Two classes to out-specify the generated .wp-container-* rule the block
 * writes its blockGap into; the row gap it sets is left alone, because that is
 * the spacing between the stacked sections below 684. */
.mjm-footer__grid.wp-block-group {
	column-gap: var(--wp--preset--spacing--50);
}

/* Sized to the wordmark it holds. Without a basis of its own this column took
   the row's free space -- 598px -- and pushed the contact column onto a second
   line. */
/* One row down to tablet widths, with all three columns scaling to hold it.
 *
 * With wrapping on, flex breaks the line the moment the base sizes exceed it --
 * before any shrinking is considered -- so the footer jumped to two rows at
 * around 1100px while every column was still at full size. Held on one line the
 * same shortfall is taken out of all of them: at 1096 the wordmark goes 392 to
 * 287 and the brand logos 209 to 150, where pinning the wordmark's width made
 * the logos alone collapse to 99.
 *
 * `auto` bases rather than fixed ones are what makes that share fair -- each
 * column starts from its own content and gives up a proportional amount. Two
 * classes on the grid to out-specify the generated .wp-container-* rule that
 * carries the block's own flexWrap setting. */
.mjm-footer__grid.wp-block-group {
	flex-wrap: nowrap;
}

.mjm-footer__brand {
	flex: 0 1 auto;
	min-width: 0;
}

@container mjm-footer (max-width: 684px) {
	/* Below this there is not enough room to divide three ways, and stacking is
	   better than three columns none of which can be read. Keep in step with the
	   separator rules further down, which turn horizontal at the same width. */
	.mjm-footer__grid.wp-block-group {
		flex-wrap: wrap;
	}

	/* Stacked means one section per row, each taking the whole width. Without
	   this they keep the widths they had as columns -- the brand block stays
	   392 wide and sits against the left edge of a much wider row, which is not
	   a stack so much as three narrow columns in a vertical queue. */
	/* Every section, by position rather than by name -- and at three classes so
	   it outranks the column widths set further down this file, which otherwise
	   win on source order and leave the contact block sharing a row. */
	.mjm-footer__grid.wp-block-group > .wp-block-group {
		flex-basis: 100%;
	}

	/* The wordmark then stops short of that width once it reaches its drawn
	   size, so centre it rather than leaving it hard against the left edge of a
	   block it no longer fills. Its own max-width still caps it; this only
	   places the space left over. */
	.mjm-footer__brand .mjm-logo svg {
		margin-inline: auto;
	}
}

/* The wordmark scales with its column instead of pinning it open.
 *
 * A flex item will not shrink below its own content, and a 392px SVG made this
 * column's min-content 392 -- so it stayed full size however little room there
 * was and the whole footer wrapped to two rows instead of tightening. Sized as
 * a percentage the SVG contributes nothing to that minimum, so the column is
 * free to shrink, while max-width still holds it at its drawn size wherever
 * there is room for it.
 *
 * Scoped to the footer on purpose. The header's wordmark keeps an absolute
 * width, because there it is the flex item's base size that matters, and a
 * percentage made it reserve the SVG's intrinsic 600px and wrap the menu. */
.mjm-footer__brand .mjm-logo svg {
	width: 100%;
	max-width: var(--mjm-logo-width, 392px);
}

/* Columns sized to the logos rather than to a share of the row, so each one
   renders at the width the comp gives it. As equal fractions they were all
   capped at whatever the column happened to be -- 165px, whatever their own
   width said. */
/* Two classes again: the block carries its own flex-basis from the editor, and
   a single-class rule here loses to it. An auto basis is what lets this column
   start from its logos' own width and give up a proportional share, rather than
   absorbing the whole shortfall from a fixed 320px. */
.mjm-footer__brands.wp-block-group {
	flex: 0 1 auto;
}

.mjm-footer__brands {
	min-width: 0;

	/* minmax(0, max-content) rather than max-content: each column still takes
	   its logo's own width when the room is there, but is allowed to give some
	   back when it is not, which max-content alone refused to do. */
	grid-template-columns: repeat(2, minmax(0, max-content));
	column-gap: var(--wp--preset--spacing--60);
	align-items: center;
}

.mjm-footer__rule {
	align-self: stretch;
	border-inline-start: 1px solid color-mix(in srgb, currentColor 25%, transparent);
}

.mjm-footer__brand-logo {
	margin: 0;
}

.mjm-footer__brand-logo img {
	/* Each logo carries its own width, which is what keeps them at the four
	   different sizes the comp draws rather than one shared column width. The
	   cap is on the column rather than a fixed size, so they hold those widths
	   while there is room and scale together once there is not. */
	max-width: 100%;
	height: auto;
}

/* The comp is drawn at 1440, and all three columns only fit at full size there.
   Narrower than that the logos scale down rather than the contact column
   dropping to a row of its own, which is what happened when they were pinned to
   their exact widths. */
/* These used to sit behind a 1400px window check, which the footer row can never
   exceed anyway -- it is capped at the 1248px content column -- so the condition
   was always true and only made the rules harder to find. The `flex` line is
   dead too: an auto basis is set on this block further up, at two classes, to
   beat the width the editor puts on it. What is left applies always. */
.mjm-footer__brands {
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

.mjm-footer__brand-logo img {
	max-width: 100%;
}

.mjm-footer__contact {
	/* The comp gives this column about 144px, which is what leaves the brands
	   block room for its logos at full size. */
	flex: 0 1 160px;
}

/* An absolute size, not `smaller`.
 *
 * `smaller` is relative to the parent, and this theme has fluid typography
 * turned on in theme.json -- every preset becomes a clamp() with a vw term, so
 * the footer inherits a body size that runs from 16.7px at a 900px window to
 * 19.2px at 1512. `smaller` is five sixths of that, so these lines grew with the
 * window rather than staying put: 13.9px at 900, 16px at 1512, at which point
 * the address and the email no longer fit the column and wrapped.
 *
 * The line height is a ratio rather than the flat 14px it was, so it follows the
 * font instead of being outgrown by it. */
.mjm-footer__contact p {
	/* The `small` token, which is this 14px -- taking it from the scale rather
	   than restating the number means it still answers to the type settings,
	   which a literal would not. What it must not do is inherit, which is what
	   `smaller` did. */
	font-size: var(--wp--preset--font-size--small);
	line-height: calc(16 / 14);
}

/* The coloured dots that used to sit in front of these are gone in v3.
   Their line rhythm is not. */
.mjm-footer__contact p {
	margin-block: 0 0.75rem;
}

/* The rules turn with the layout: they separate columns while the footer is a
   row, and rows once it stacks.
 *
 * This has to be the same width the grid stops wrapping at, 760px above. It was
 * 900, so between 760 and 900 the footer was still one row but drawing
 * horizontal rules through it, which read as a stacked layout that had not
 * actually stacked. If one of these two breakpoints moves, the other has to. */
@container mjm-footer (max-width: 684px) {
	/* The same two elements, turned. Stacked they are full-width items in the
	   flow between the sections, which is where the horizontal rules go -- so
	   one mechanism draws both orientations instead of two. */
	.mjm-footer__rule {
		align-self: auto;
		border-inline-start: 0;
		border-block-start: 1px solid color-mix(in srgb, currentColor 25%, transparent);
	}
}

/* -------------------------------------------------------------------------
 * Services
 *
 * A discipline is a row: text on the left, a square photograph filling the
 * right half and running off the edge of the page. The comp draws the block
 * 1440x720 on a 1440 frame -- so the photograph is exactly half the page and
 * exactly as tall as it is wide, and the text sits 140px in.
 * ---------------------------------------------------------------------- */

.mjm-service {
	display: grid;
	grid-template-columns: 1fr 1fr;
	align-items: center;
}

/* The comp insets this page's text 140px, where the rest of the site sits on
   the 96 its content column gives. One value, shared by the opening block and
   every row, so they line up down the left edge. 10vw rather than 8 because 8
   lands at 115 on the 1440 the comp is drawn at. */
.mjm-services__intro,
.mjm-service__text {
	--mjm-services-inset: min(140px, 10vw);
}

.mjm-service__text {
	padding-inline: var(--mjm-services-inset) var(--wp--preset--spacing--40);

	/* The comp sets the text at 450 of the 720 it has. Capping the measure
	   rather than the column keeps the line length readable without narrowing
	   the row. */
	max-width: 450px;
	justify-self: center;
}

.mjm-service__title {
	color: var(--mjm-service-color, var(--wp--preset--color--contrast));
	margin-block: 0 0.5em;
	text-wrap: balance;
}

.mjm-service__body p {
	margin-block: 0 1em;
}

.mjm-service__body p:last-child {
	margin-block-end: 0;
}

.mjm-service__media {
	/* Square, and it keeps that shape at every width -- the comp's photographs
	   are composed as squares. */
	aspect-ratio: 1;
}

.mjm-service__img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

@media (max-width: 900px) {
	/* Stacked, photograph under its text, in source order. */
	.mjm-service {
		grid-template-columns: 1fr;
		gap: var(--wp--preset--spacing--40);
	}

	.mjm-service__text {
		/* Back on the page's own gutter once it is the full width. */
		padding-inline: var(--wp--preset--spacing--40);
		max-width: none;
	}
}

/* The page's opening: serif title with its intro below rather than beside it,
   which is what separates this from mjm_seed_masthead().
 *
 * A default layout rather than a constrained one, deliberately. Constrained
 * centres any child narrower than the content column, and does it with
 * `margin-left/right: auto !important` -- so a capped paragraph drifted to the
 * middle of the page while the heading stayed left. Owning the gutter here
 * means the cap below is just a cap. */
.mjm-services__intro {
	padding-block: var(--wp--preset--spacing--80);
	padding-inline: var(--mjm-services-inset);
}

.mjm-services__lede {
	max-width: 28em;
}

/* -------------------------------------------------------------------------
 * Services scroll stage
 *
 * The page pins while it scrolls past the disciplines, cross-fading one into
 * the next, then releases -- and only the disciplines are in the pin. The
 * opening title, the ticker and the call to action are ordinary page flow
 * above and below it.
 *
 * Two nested groups: .mjm-services__stage is a tall spacer and its first child
 * is the sticky viewport. The rows inside are the same mjm/service-row blocks
 * the stacked page uses, so nothing here depends on a second rendering of
 * them -- off the stage the markup is already the plain stack.
 * ---------------------------------------------------------------------- */

/* The sticky site header the pin parks under.
 *
 * Measured, it is not the constant it was described as: 71px up to 1200, then
 * 72, 73, 74 as the wordmark and nav scale with the type. Two values rather
 * than four, and each rounded up -- too tall leaves a hairline gap under the
 * header, too short slides the pinned content beneath it, and only one of
 * those is worth avoiding. */
:root {
	--mjm-stage-top: 71px;
}

@media (min-width: 1280px) {
	:root {
		--mjm-stage-top: 74px;
	}
}

/* How much scroll the stage owes, counted off its own rows rather than
   declared -- each rule that matches overrides the one above it, so the highest
   wins. CSS cannot turn a child count into a number any other way.
 *
 * On the stage, not on the viewport inside it: the height below is the stage's,
 * and a custom property set on a child does not reach its parent. */
.mjm-services__stage:has(.mjm-service:nth-child(2)) { --mjm-stage-steps: 2; }
.mjm-services__stage:has(.mjm-service:nth-child(3)) { --mjm-stage-steps: 3; }
.mjm-services__stage:has(.mjm-service:nth-child(4)) { --mjm-stage-steps: 4; }
.mjm-services__stage:has(.mjm-service:nth-child(5)) { --mjm-stage-steps: 5; }
.mjm-services__stage:has(.mjm-service:nth-child(6)) { --mjm-stage-steps: 6; }
.mjm-services__stage:has(.mjm-service:nth-child(7)) { --mjm-stage-steps: 7; }
.mjm-services__stage:has(.mjm-service:nth-child(8)) { --mjm-stage-steps: 8; }

/* Each row's place in the run, for the slice of the timeline it owns. */
.mjm-services__viewport > .mjm-service:nth-child(1) { --i: 0; }
.mjm-services__viewport > .mjm-service:nth-child(2) { --i: 1; }
.mjm-services__viewport > .mjm-service:nth-child(3) { --i: 2; }
.mjm-services__viewport > .mjm-service:nth-child(4) { --i: 3; }
.mjm-services__viewport > .mjm-service:nth-child(5) { --i: 4; }
.mjm-services__viewport > .mjm-service:nth-child(6) { --i: 5; }
.mjm-services__viewport > .mjm-service:nth-child(7) { --i: 6; }
.mjm-services__viewport > .mjm-service:nth-child(8) { --i: 7; }

/* Hidden either side of its slice and fully drawn through the middle, so
   consecutive rows cross-fade rather than cut. */
@keyframes mjm-stage-step {
	0% { opacity: 0; visibility: visible; }
	14% { opacity: 1; visibility: visible; }
	86% { opacity: 1; visibility: visible; }
	100% { opacity: 0; visibility: visible; }
}

/*
 * Gated on support, on having the height to pin in, and on the reader not
 * having asked for less motion. Anything else gets the stack the markup
 * already is -- every discipline present, in order, nothing hidden and nothing
 * needing script to be read.
 */
@supports (animation-timeline: view()) {
	/* No width condition: MJM want the cross-fade on a phone and a tablet too,
	   where the row stacks rather than sitting side by side. The height one
	   stays -- a landscape phone has no room to pin anything in, and gets the
	   plain stack.

	   svh rather than vh throughout. vh is the viewport with the mobile
	   browser's toolbars hidden, so the pinned panel would be taller than what
	   is actually on screen for as long as they are showing. */
	@media (min-height: 700px) and (prefers-reduced-motion: no-preference) {
		.mjm-services__stage {
			view-timeline-name: --mjm-stage;
			view-timeline-axis: block;

			/* One viewport to read in, plus 70svh of scrolling per step. */
			height: calc(100svh + var(--mjm-stage-steps, 0) * 70svh);
		}

		.mjm-services__viewport {
			position: sticky;

			/* Parked under the header and exactly as tall as what is left, so
			   the composition is in the same place before and after it sticks. */
			top: var(--mjm-stage-top, 0px);
			height: calc(100svh - var(--mjm-stage-top, 0px));
			box-sizing: border-box;

			/* Every row in the same cell, so the stack is as tall as the
			   viewport and nothing reflows as they cross-fade.
			 *
			 * The row is stated rather than left to size itself: the panel has a
			 * definite height, and only a definite row passes it down to the
			 * rows and on to the cap on the photograph. Left auto, each step of
			 * that chain measured its own contents instead. */
			display: grid;
			grid-template-rows: minmax(0, 1fr);
			overflow: clip;
		}

		.mjm-services__viewport > .mjm-service {
			grid-area: 1 / 1;
			block-size: 100%;

			/* Core's block gap, which is meaningless once every row shares one
			   grid cell -- and which pushed the whole stack 20px down, so each
			   row ended that far below the panel. */
			margin-block: 0;

			/* A definite row, so the percentage cap on the photograph below has
			   something to resolve against. Left to size itself the row grew to
			   fit the photograph and `max-block-size: 100%` then measured
			   itself -- at 1440x900 that put a 960px image in an 826px panel. */
			grid-template-rows: minmax(0, 1fr);

			/* Outside its own slice a row falls back to these, so hidden is the
			   resting state and only the slice in view overrides it. visibility
			   rides along with the opacity, so a row that cannot be seen is out
			   of the reading order too. */
			opacity: 0;
			visibility: hidden;
			animation: mjm-stage-step linear;
			animation-timeline: --mjm-stage;
			animation-range:
				contain calc(var(--i, 0) / var(--mjm-stage-steps, 1) * 100%)
				contain calc((var(--i, 0) + 1) / var(--mjm-stage-steps, 1) * 100%);
		}

		/*
		 * The `contain` range only runs while the stage fills the viewport, so
		 * on the way in and on the way out no slice is in force and the pin
		 * would stand empty with the section plainly on screen. The two ends
		 * reach into `cover` instead: the first row is up before the pin
		 * catches, the last holds until the section is gone. The hand-overs
		 * between them are untouched.
		 */
		.mjm-services__viewport > .mjm-service:first-child {
			animation-range: cover 0% contain calc(1 / var(--mjm-stage-steps, 1) * 100%);
		}

		.mjm-services__viewport > .mjm-service:last-child {
			animation-range:
				contain calc((var(--mjm-stage-steps, 1) - 1) / var(--mjm-stage-steps, 1) * 100%)
				cover 100%;
		}

		/*
		 * Pinned, the photograph fills its half of the screen and runs to the
		 * edge -- the comp asks for no margin -- and only its HEIGHT is capped.
		 *
		 * It was `aspect-ratio` with a height cap, which is the wrong way round:
		 * a ratio ties the two axes together, so clamping the height pulled the
		 * width in with it and the photograph was never as wide as the column
		 * it sits in. Capping the height alone lets it widen to the full half
		 * wherever there is room, and only where the window is taller than 4:3
		 * allows does it stop growing and sit centred, with the page showing
		 * above and below.
		 *
		 * The cap is in container units, off the row's own width rather than
		 * the viewport's: the column is half the row, so 50cqw, and 4/3 of that
		 * is the tallest it may be. vw would have counted a scrollbar the row
		 * does not have.
		 */
		.mjm-services__viewport > .mjm-service {
			container-type: inline-size;
		}

		.mjm-services__viewport .mjm-service__media {
			/* The stacked rule sets a square; pinned, the box is sized by the
			   panel and only its height is capped, so the ratio has to be
			   released or it drags the width along with the cap again. */
			aspect-ratio: auto;
			inline-size: 100%;
			block-size: 100%;
			max-block-size: calc(50cqw * 4 / 3);
			align-self: center;
		}

		/* Centred in its own half rather than pinned to a fixed inset. At the
		   1440 the comp is drawn at this puts the text's left edge at 135
		   against its 139, and it holds that relationship at every other width
		   instead of only at one. */
		.mjm-services__viewport .mjm-service__text {
			justify-self: center;
			padding-inline: var(--wp--preset--spacing--40);
		}

		/* Narrow: photograph over the words, both stacked in the pin.
		 *
		 * By grid row rather than by source order, so the heading is still read
		 * before its photograph -- the visual order is the only thing that
		 * changes. */
		@media (max-width: 900px) {
			/*
			 * One measure for both, so the photograph and the words share an
			 * edge rather than one being inset from the other.
			 *
			 * 4:3 landscape, and it is the WIDTH that is capped, not the
			 * height: clamping the height left the width at full bleed and the
			 * crop widened to 2.25 on a tablet, which is a slot cut out of a
			 * square photograph. Half the panel's height is the ceiling, which
			 * on a phone is never reached -- there the measure is simply the
			 * page less its gutters.
			 */
			.mjm-services__viewport > .mjm-service {
				--mjm-service-measure: min(
					100% - 2 * var(--wp--preset--spacing--40),
					calc(50svh * 4 / 3)
				);

				grid-template-columns: 1fr;

				/* Both rows sized to their contents, and the pair centred in
				   what is left, rather than the words taking the slack as a
				   second row of free space below them. `safe` so that a panel
				   too short for the pair overflows downward only, where it can
				   still be scrolled to, instead of off both edges. */
				grid-template-rows: auto auto;
				align-content: center;
				align-content: safe center;
				gap: var(--wp--preset--spacing--30);
			}

			.mjm-services__viewport .mjm-service__media {
				grid-row: 1;
				aspect-ratio: 4 / 3;
				inline-size: var(--mjm-service-measure);
				block-size: auto;
				max-block-size: none;
				justify-self: center;
			}

			.mjm-services__viewport .mjm-service__text {
				grid-row: 2;
				justify-self: center;
				inline-size: var(--mjm-service-measure);
				max-width: none;
				padding-inline: 0;
			}
		}
	}
}

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

.mjm-hero {
	/* The hero's two narrow-width adjustments -- dropping the caption and laying
	   a scrim behind the text -- are about how much room the hero has, so it is
	   asked directly. 780 is what it measures at the window width they used. */
	container: mjm-hero / inline-size;

	position: relative;
	display: grid;
	min-height: min(958px, 88svh);
	isolation: isolate;
	color: var(--wp--preset--color--clarity);
}

.mjm-hero__media {
	position: absolute;
	inset: 0;
	z-index: -1;
	overflow: hidden;
}

.mjm-hero__slide {
	position: absolute;
	inset: 0;
	opacity: 0;
	transition: opacity 700ms ease;
}

.mjm-hero__slide.is-active {
	opacity: 1;
}

.mjm-hero__asset {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* Bottom-weighted scrim: the headline sits low in the frame, so the gradient
   is strongest where the text is and clears entirely by the top third. */
.mjm-hero__scrim {
	position: absolute;
	inset: 0;
	background: linear-gradient(
		to top,
		rgba(0, 0, 0, var(--mjm-scrim, 0.5)) 0%,
		rgba(0, 0, 0, calc(var(--mjm-scrim, 0.5) * 0.55)) 45%,
		rgba(0, 0, 0, 0) 78%
	);
}

.mjm-hero__caption {
	position: absolute;
	right: var(--wp--preset--spacing--40);
	bottom: var(--wp--preset--spacing--60);
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

.mjm-hero__inner {
	align-self: end;
	width: 100%;
	max-width: var(--wp--style--global--wide-size, 1440px);
	margin-inline: auto;
	padding: var(--wp--preset--spacing--80) var(--wp--preset--spacing--60);

	/* The buttons are the last thing in here, so this is what sets their
	   distance from the bottom of the hero: 50px, per the design. */
	padding-bottom: 50px;
}

/* Drawn heading. The h1 becomes a frame for the artwork, so its type styling
   would only affect the alt text nobody sees -- but it is left in place rather
   than reset, since the block falls back to real type whenever the wordmark is
   switched off or its file is missing. */
.mjm-hero__heading--wordmark {
	margin-bottom: var(--wp--preset--spacing--40);
}

.mjm-hero__wordmark {
	display: block;
	/* Tracks the type it replaces: the same 15ch-ish measure at full size,
	   shrinking with the viewport rather than overflowing it. Never drawn above
	   its natural size, where the raster fallback would soften. */
	width: min(100%, 675px);
	height: auto;
}

.mjm-hero__heading {
	font-family: var(--wp--preset--font-family--serif);
	font-size: var(--wp--preset--font-size--display);
	font-weight: 400;
	line-height: 1.02;
	letter-spacing: -0.02em;
	max-width: 15ch;
	margin: 0 0 var(--wp--preset--spacing--40);
}

.mjm-hero__body {
	max-width: 46ch;
	font-size: var(--wp--preset--font-size--medium);
}

.mjm-hero__body p {
	margin: 0 0 0.5em;
}

.mjm-hero__tagline {
	margin: var(--wp--preset--spacing--30) 0 0;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

.mjm-hero__buttons {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--30) var(--wp--preset--spacing--40);
	margin-top: var(--wp--preset--spacing--50);
}

/* On media the label is already white, so only the hover colour differs. */
.mjm-cbtn--on-media {
	color: var(--wp--preset--color--clarity);
}

.mjm-hero__dots {
	position: absolute;
	right: var(--wp--preset--spacing--40);
	bottom: var(--wp--preset--spacing--40);
	display: flex;
	gap: 0.5rem;
}

.mjm-hero__dot {
	width: 9px;
	height: 9px;
	padding: 0;
	border: 1px solid var(--wp--preset--color--clarity);
	border-radius: 50%;
	background: none;
	cursor: pointer;
	transition: background-color 200ms ease;
}

.mjm-hero__dot.is-active {
	background-color: var(--wp--preset--color--clarity);
}

@media (prefers-reduced-motion: reduce) {
	.mjm-hero__slide {
		transition: none;
	}
}

@container mjm-hero (max-width: 780px) {
	.mjm-hero__heading {
		max-width: none;
	}

	.mjm-hero__caption {
		display: none;
	}
}

/* -------------------------------------------------------------------------
 * Pinned heading + scrolling cards
 *
 * The heading column is sticky; the card track scrolls horizontally with
 * snap points. A mask fades cards out at the left edge so they disappear
 * before they would slide under the pinned heading.
 * ---------------------------------------------------------------------- */

.mjm-pinned {
	padding-block: var(--wp--preset--spacing--80);
	overflow: clip;
}

.mjm-pinned__inner {
	position: relative;
	display: grid;
	grid-template-columns: minmax(280px, 31%) 1fr;
	max-width: var(--wp--style--global--wide-size, 1440px);
	margin-inline: auto;
	padding-inline: var(--wp--preset--spacing--60);
}

.mjm-pinned__aside {
	grid-column: 1;
	grid-row: 1;
}

.mjm-pinned__sticky {
	position: sticky;
	top: 8rem;
}

.mjm-pinned__heading {
	margin: 0 0 var(--wp--preset--spacing--30);
	text-wrap: balance;
}

/* The treatment follows the variant, not the tag, because the two are separate
   decisions: all three of these headings are their page's h1, but only Services
   is drawn as Headline 1.
 *
 * The columns variant is left unstyled so it inherits Headline 1 from
 * theme.json -- Bona Nova regular at the display size -- rather than having it
 * restated here where it could drift from the token.
 *
 * The boxes variant is the sans section heading both comps draw: `Homepage.png`
 * and `Career Page.png` show "Every breakthrough needs a story." and "MJM
 * Values" in the sans bold at roughly 44px, and the brand guidelines use the
 * former as their H2 specimen. It is stated explicitly rather than inherited,
 * so it holds whichever tag the heading is rendered as. Headline 1 cannot go
 * here as things stand: at 110px "breakthrough" alone is 631px wide against a
 * 407px column, so it broke out of the column and ran under the first card. */
.mjm-pinned--boxes .mjm-pinned__heading {
	font-family: var(--wp--preset--font-family--sans);
	font-size: var(--wp--preset--font-size--xx-large);
	font-weight: 700;
	/* 1, matching Headline 2: the comp sets these lines exactly one font size
	   apart -- 48px at 48px. */
	line-height: 1;
	letter-spacing: -0.015em;
}

/* Standard body copy: 18 on a 22px line. */
.mjm-pinned__body {
	font-size: var(--wp--preset--font-size--large);
	line-height: calc(22 / 18);
	max-width: 40ch;
}

.mjm-pinned__body p {
	margin: 0 0 var(--wp--preset--spacing--30);
}

/* This section sits 20px further in than the page's standard gutter. Both comps
   draw it that way: on Homepage.png the body copy's ink edge is at 103 and this
   heading's at 118, and Career Page.png puts "MJM Values" at 122 against the
   same gutter. (Ink edges understate it slightly, since a 44px bold glyph
   carries more left bearing than 16px body text.) */
.mjm-pinned--boxes .mjm-pinned__inner {
	padding-inline-start: calc(var(--wp--preset--spacing--60) + 20px);

	/* Wider than the 31% the two variants shared. At Headline 2's 48px the
	   homepage heading wrapped to five lines in a 401px column; the comp gives
	   it about 545px and breaks it across four. */
	grid-template-columns: minmax(280px, 44%) 1fr;
}

.mjm-pinned__track {
	/* The gutter between the heading column and the cards. It lives here rather
	   than as a gap on the grid so that it is *inside* the scrollport: cards
	   scroll through it and out, which is what lets them appear to pass behind
	   the heading instead of stopping short of it. The same value positions the
	   fade below, so the two cannot drift apart. */
	--mjm-track-inset: var(--wp--preset--spacing--60);

	grid-column: 2;
	grid-row: 1;
	display: flex;
	gap: var(--wp--preset--spacing--40);
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scrollbar-width: none;
	padding-block: 0.5rem;
	padding-inline-start: var(--mjm-track-inset);

	/* Snap to the inset, not to the scrollport edge, or a snapped card lands
	   under the fade. */
	scroll-padding-inline-start: var(--mjm-track-inset);
}

/* Cards fade out as they pass into the gutter, so they read as going behind the
   heading column rather than dissolving in open space.
 *
 * The fade is exactly the width of the inset, which is why it no longer needs
 * to be switched on only after scrolling: a card at rest begins at the inset
 * and so is never under it. Previously the fade was a percentage of the whole
 * track, which covered the first card at rest and looked like a rendering
 * fault -- hence the old is-scrolled class, now unnecessary. */
.mjm-pinned__track {
	-webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--mjm-track-inset), #000 100%);
	mask-image: linear-gradient(to right, transparent 0, #000 var(--mjm-track-inset), #000 100%);
}

.mjm-pinned__track::-webkit-scrollbar {
	display: none;
}

.mjm-pinned__track:focus-visible {
	outline: 2px solid var(--wp--preset--color--bonfire);
	outline-offset: 4px;
}

.mjm-pinned__card {
	flex: 0 0 auto;
	scroll-snap-align: start;
}

/* Filled boxes: Story / System / Good, and the values on the careers page.
 *
 * The comp draws a fixed 506x408 card with the title 95px down, the copy 90px
 * below it and whatever is left over as slack at the bottom. The inline inset
 * is 57px, wider than the block one. */
.mjm-pinned--boxes .mjm-pinned__card {
	inline-size: min(506px, 78vw);
	min-block-size: 408px;
	display: flex;
	flex-direction: column;
	padding: 95px 57px 57px;
	border-radius: 28px;
	background-color: var(--mjm-card-bg, var(--wp--preset--color--olive-branch));
	color: var(--wp--preset--color--clarity);
}

.mjm-pinned--boxes .mjm-pinned__card-title {
	font-family: var(--wp--preset--font-family--serif);
	font-size: var(--wp--preset--font-size--box-title);
	font-weight: 400;
	line-height: 1;
	margin: 0;
}

.mjm-pinned--boxes .mjm-pinned__card-text {
	font-size: var(--wp--preset--font-size--large);
	margin-block-start: 90px;
	max-inline-size: 306px;
}

.mjm-pinned__card-text p {
	margin: 0 0 0.75em;
}

.mjm-pinned__card-text p:last-child {
	margin-bottom: 0;
}

.mjm-pinned__progress {
	position: absolute;
	right: 0;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	flex-direction: column;
	gap: 0.625rem;
}

/* A ring in the colour of the card it points at, filled once that card is the
   one showing -- the same treatment as the case study carousel's pips, and for
   the same reason: with a colour per pip, hue cannot also be what marks the
   active one. The five services carry five different brand colours, so the rail
   ends up showing the whole palette rather than one accent five times. */
.mjm-pinned__pip {
	box-sizing: border-box;
	width: 9px;
	height: 9px;
	border: 1.5px solid var(--mjm-pip-color, var(--wp--preset--color--curious-george));
	border-radius: 50%;
	background-color: transparent;
	transition: background-color 200ms ease;
}

.mjm-pinned__pip.is-active {
	background-color: var(--mjm-pip-color, var(--wp--preset--color--curious-george));
}

@media (max-width: 900px) {
	/* Two classes, because .mjm-pinned--boxes sets the two-column grid at two
	   classes of its own and was winning on specificity -- so this section never
	   stacked at any width. The track was left in the second column of a grid
	   that still thought it had two: 280px wide on a phone, and the same tiny
	   carousel on an iPad. */
	.mjm-pinned .mjm-pinned__inner {
		grid-template-columns: 1fr;

		/* The gutter moves off the container and onto the heading below, so the
		   track is free to run the full width of the screen. */
		padding-inline: 0;
	}

	/* The heading keeps the page's gutter; the cards do not. */
	.mjm-pinned__aside {
		padding-inline: var(--wp--preset--spacing--40);
	}

	.mjm-pinned__aside,
	.mjm-pinned__track {
		grid-column: 1;
		grid-row: auto;
	}

	.mjm-pinned__sticky {
		position: static;
	}

	.mjm-pinned__progress {
		display: none;
	}

	/* Stacked, the heading is above the track rather than beside it, so there is
	   nothing for a card to disappear behind and no reason to fade the left edge.
	   The first card starts at the page's gutter and the rest run off the right
	   edge of the screen, which is what makes it read as a strip that continues
	   rather than a panel with white bars either side. */
	.mjm-pinned__track {
		padding-inline: var(--wp--preset--spacing--40) 0;
		scroll-padding-inline-start: var(--wp--preset--spacing--40);
		-webkit-mask-image: none;
		mask-image: none;
	}
}

/* 102px between the last row of roles and the band that follows. The group had
   its padding set inline and lost it when this page was restructured, which put
   the band hard against the names. */
.mjm-team {
	padding-bottom: 102px;

	/* Query container for the people grids below, which are drawn wider than
	   this column. Measuring against the group rather than the viewport keeps a
	   scrollbar out of the sum -- 100vw includes it, and overshooting by its
	   width is how a page ends up scrolling sideways on Windows. Safe to make
	   this an inline-size container: the group is alignfull, so its width comes
	   from the page rather than from anything inside it. */
	container: mjm-team / inline-size;
}

/* -------------------------------------------------------------------------
 * About page photography
 * ---------------------------------------------------------------------- */

/* Full bleed under the masthead. A fixed ratio rather than the file's own, so
   the band stays the same depth whatever photograph is dropped in. */
.mjm-about__hero {
	margin-block: 0 var(--wp--preset--spacing--90);
}

.mjm-about__hero img {
	display: block;
	width: 100%;

	/* 1440x544 in the v2 comp, down from 654. */
	aspect-ratio: 1440 / 544;
	object-fit: cover;

	/* Top, not centre. The supplied photograph is 3:2 and this band is 2.65:1,
	   so well over a third of its height is discarded -- and all of what it can
	   afford to lose is at the bottom, which is parking lot. Centring the crop
	   cuts the roofline off the building instead. */
	object-position: center top;
}

/* The Careers page uses the same photograph, cropped shallower: 1440x357. */
/* Careers runs the same band shallower: 1440x511 in the v2 comp, against the
   About page's 544. Same top-weighted crop -- the photograph's foreground is a
   bench and a plant pot, and the comp keeps the neon sign. */
.mjm-about__hero--shallow img {
	aspect-ratio: 1440 / 511;
}

/* Photograph left, heading and copy right. */
.mjm-about__intro {
	gap: var(--wp--preset--spacing--80);
	margin-bottom: var(--wp--preset--spacing--80);
}

.mjm-about__team-figure {
	margin: 0;
}

.mjm-about__team-figure img {
	display: block;
	width: 100%;
	height: auto;
	/* 620x620 in the v2 comp. It was 619x555 in v1, and the crop is what
	   changed rather than the photograph's own shape. */
	aspect-ratio: 1;
	object-fit: cover;
	border-radius: 24px;
}

@media (max-width: 900px) {
	.mjm-about__intro {
		gap: var(--wp--preset--spacing--50);
	}
}

/* -------------------------------------------------------------------------
 * Tagged image
 * ---------------------------------------------------------------------- */

.mjm-tagged {
	position: relative;
	margin: 0;
}

.mjm-tagged__img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: 24px;
}

.mjm-tagged__tags {
	position: absolute;
	left: var(--wp--preset--spacing--30);
	bottom: var(--wp--preset--spacing--30);
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
}

.mjm-tagged__tag {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.3rem 0.75rem 0.3rem 0.35rem;
	border-radius: 999px;
	background-color: var(--mjm-tag-bg, var(--wp--preset--color--anchor));
	color: var(--wp--preset--color--clarity);
	font-size: var(--wp--preset--font-size--tiny);
	font-weight: 600;
	line-height: 1.4;
}

.mjm-tagged__dot {
	width: 18px;
	height: 18px;
	border-radius: 50%;
	background-color: color-mix(in srgb, currentColor 80%, transparent);
	flex: 0 0 auto;
}

/* -------------------------------------------------------------------------
 * Logo grid
 * ---------------------------------------------------------------------- */

.mjm-logos--grid {
	display: grid;

	/* As many logos as fit at a legible size, rather than a count per
	   breakpoint. min(…, 100%) keeps a single column from overflowing a
	   container narrower than the track itself. */
	grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
	gap: var(--wp--preset--spacing--40);
	align-items: center;
	justify-items: center;
}

/* Ticker. The strip is full-bleed and clipped; the marquee inside it is twice
   the content width because the logos are emitted twice, so translating it by
   half puts the copy exactly where the original was and the loop is seamless. */
.mjm-logos--row {
	overflow: hidden;

	/* Fade the two edges rather than cutting a logo mid-word. The strip runs the
	   full width of the page, so without this a logo crossing either end is
	   simply sliced. */
	--mjm-ticker-fade: var(--wp--preset--spacing--70);

	-webkit-mask-image: linear-gradient(
		to right,
		transparent 0,
		#000 var(--mjm-ticker-fade),
		#000 calc(100% - var(--mjm-ticker-fade)),
		transparent 100%
	);
	mask-image: linear-gradient(
		to right,
		transparent 0,
		#000 var(--mjm-ticker-fade),
		#000 calc(100% - var(--mjm-ticker-fade)),
		transparent 100%
	);
}

.mjm-logos__marquee {
	display: flex;
	width: max-content;

	/* No gap between the two sets. The loop translates the marquee by half its
	   width, which only lands the copy exactly where the original began if the
	   sets tile edge to edge -- a gap here made that half-width fall short by
	   half a gap, which is the jump that showed at every repeat. The spacing
	   after the last logo is carried by the set instead, so one set's width is
	   one full period of the pattern. */
	gap: 0;
	animation: mjm-logos-ticker var(--mjm-logo-duration, 40s) linear infinite;
}

.mjm-logos__set {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--70);
	padding-inline-end: var(--wp--preset--spacing--70);
}

@keyframes mjm-logos-ticker {
	to {
		transform: translateX(-50%);
	}
}

/* Pause so a logo can actually be looked at -- and so the hover-to-colour
   treatment below is usable on a moving target at all. focus-within covers
   keyboard users, since a paused ticker is the only way to reach one. */
.mjm-logos--row:hover .mjm-logos__marquee,
.mjm-logos--row:focus-within .mjm-logos__marquee {
	animation-play-state: paused;
}

.mjm-logos--row .mjm-logos__item {
	flex: 0 0 auto;
}

@media (prefers-reduced-motion: reduce) {
	/* Back to a strip the visitor scrolls themselves, which makes the second
	   set redundant rather than a run of duplicates. */
	.mjm-logos__marquee {
		animation: none;
	}

	.mjm-logos--row {
		overflow-x: auto;
		scrollbar-width: none;
	}

	.mjm-logos--row::-webkit-scrollbar {
		display: none;
	}

	.mjm-logos__set[aria-hidden="true"] {
		display: none;
	}
}

.mjm-logos__img {
	display: block;
	max-width: 100%;
	height: auto;
	max-height: 64px;
	width: auto;
}

/* The Services ticker is in colour at rest, where the expertise wall is grey
   until hovered. Set as the variables rather than as a flat `filter: none`, so
   dark mode still gets its white tint: `-active` is `none` in light mode and
   `brightness(0) invert(1)` in dark, which is what these lockups need on black.
   MJM asked for the row in colour all the time. */
.mjm-logos--row {
	--mjm-logo-filter: var(--mjm-logo-filter-active);
	--mjm-logo-rest: 1;
}

/* Same treatment as the expertise wall, so the Services ticker inherits it. */
.mjm-logos__item {
	filter: var(--mjm-logo-filter);
	opacity: var(--mjm-logo-rest);
	transition: opacity 200ms ease, filter 200ms ease;
}

.mjm-logos__item:hover,
.mjm-logos__item:focus-within {
	filter: var(--mjm-logo-filter-active);
	opacity: 1;
}

/* -------------------------------------------------------------------------
 * Expertise block — "Our Experience Spans" list
 * ---------------------------------------------------------------------- */

.mjm-spans {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0.75rem var(--wp--preset--spacing--40);
	list-style: none;
	margin: var(--wp--preset--spacing--30) 0 var(--wp--preset--spacing--60);
	padding: 0;
}

.mjm-span {
	display: flex;
	align-items: center;
	gap: 0.625rem;
	font-size: var(--wp--preset--font-size--small);
}

.mjm-span__icon {
	display: inline-flex;
	width: 20px;
	flex: 0 0 auto;
}

.mjm-span__icon svg {
	width: 100%;
	height: auto;
}



/* -- Social accounts, and the colophon ---------------------------------- */

.mjm-social {
	display: flex;
	align-items: center;
	gap: 6px;
	list-style: none;
	padding: 0;

	/* 21px below the email, as the comp spaces them. Collapses with that
	   paragraph's 12px bottom margin rather than adding to it. */
	margin-block: 21px 0;
	margin-inline: 0;
}

.mjm-social__link {
	display: grid;
	place-items: center;

	/* 28px circles, 6px apart, as the comp draws them. */
	inline-size: 28px;
	block-size: 28px;
	border-radius: 50%;
	background: var(--mjm-social-color, var(--wp--preset--color--olive-branch));

	/* The mark is one white glyph on three different circles, so it takes its
	   colour from here rather than carrying its own. */
	color: var(--wp--preset--color--clarity);
	transition: transform 150ms ease;
}

.mjm-social__link:hover,
.mjm-social__link:focus-visible {
	transform: scale(1.12);
}

@media (prefers-reduced-motion: reduce) {
	.mjm-social__link {
		transition: none;
	}

	.mjm-social__link:hover,
	.mjm-social__link:focus-visible {
		transform: none;
	}
}

.mjm-social__icon {
	inline-size: 16px;
	block-size: 16px;
	display: block;
}

/* The colophon runs the full width of the footer rather than sitting under the
   wordmark, so it centres on the page and not on its column. */
.mjm-footer__colophon {
	margin-bottom: 0;
	text-align: center;
}

/* -------------------------------------------------------------------------
 * Our Work grid
 * ---------------------------------------------------------------------- */

.mjm-work__grid .wp-block-post-featured-image img {
	width: 100%;
	height: auto;
	aspect-ratio: 545 / 451;
	object-fit: cover;
	border-radius: 20px;
}

.mjm-work__grid .wp-block-post-title {
	margin-block: var(--wp--preset--spacing--30) 0.25rem;
}

.mjm-work__grid .wp-block-post-excerpt {
	margin-block: 0;
}

/* 76px from the foot of the card row to the band below. It was set inline on
   the group as spacing|90 -- 160px, and unreachable from here, since an inline
   style beats any rule. Stated once, in the stylesheet, where it can be tuned.
 *
 * Measured from the row's box, not from the cards at rest: the row reserves the
 * height of its tallest expanded card so that opening one cannot shift the page,
 * so at rest there is that much empty space inside it before this padding
 * begins. */
.mjm-work {
	padding-bottom: 4em;
}

.mjm-work__intro {
	padding-left: 20px;
}

/* Both measures in characters, so they hold their line count as the type scale
   moves. 720px and 624px were these same measures at one size, and would have
   started wrapping differently the moment the type changed. */
.mjm-work__intro > .wp-block-heading {
	max-width: 27ch;
	text-wrap: balance;
}

.mjm-work__intro > p {
	max-width: 58ch;
	margin-bottom: var(--wp--preset--spacing--60);
}

/* Featured images fall back to a brand plate when a case study has no art. */
.mjm-work__grid .wp-block-post-featured-image:empty {
	display: block;
	aspect-ratio: 545 / 451;
	border-radius: 20px;
	background: linear-gradient(
		160deg,
		var(--wp--preset--color--bonfire),
		var(--wp--preset--color--curious-george)
	);
}

/* -------------------------------------------------------------------------
 * Full-bleed CTA band
 * ---------------------------------------------------------------------- */

/* The band takes its height from its own content and its own type.
 *
 * It used to be pinned: min-height 364px with 84/85px of padding, both measured
 * off one comp at one type size. That made it a magic number -- when the type
 * scale grew, the copy grew inside a box that could not, and the padding stayed
 * put while everything it was balancing got bigger.
 *
 * 4.4em is the same proportion the comp drew (84px against a 19.2px body), but
 * stated as a ratio of the band's own text, so it breathes with the type instead
 * of against it. No minimum height: the two call-to-action bands genuinely have
 * different amounts of copy, and letting them differ is the honest result.
 * The stage trims this when the band has to share a viewport. */
.mjm-band {
	padding-block: 4.4em;
}

/* Three dots instead of an orange ground.
 *
 * The call-to-action bands used to be a solid bonfire block with white text.
 * v2 drops the ground entirely -- the band is now the same colour as the page
 * -- and separates it from what comes before with three brand-coloured dots.
 *
 * Drawn here rather than in the markup because all four bands are otherwise
 * identical block groups, and a pseudo-element gives every one of them the
 * separator without four copies of the same three <span>s in the seed. Three
 * dots also need three boxes, and an element only has two pseudo-elements, so
 * they are painted as three backgrounds on one.
 *
 * 138x34 with 34px dots, from the comp: at that size the gaps come out at 17.5
 * and the outer dots sit flush with the ends, so left/center/right positioning
 * lands them within half a pixel of Figma's own centres. Tokens rather than the
 * comp's literals -- Figma has drifted slightly from the palette (#776E00 for
 * olive branch against our #756e1a) and the palette is what the rest of the
 * site is drawn from. */
.mjm-band::before {
	content: "";
	display: block;
	width: 138px;
	height: 34px;
	margin-inline: auto;
	margin-block-end: 2.4em;
	background-image:
		radial-gradient(circle closest-side, var(--wp--preset--color--olive-branch) 99%, transparent 100%),
		radial-gradient(circle closest-side, var(--wp--preset--color--bonfire) 99%, transparent 100%),
		radial-gradient(circle closest-side, var(--wp--preset--color--morning-dew) 99%, transparent 100%);
	background-size: 34px 34px;
	background-position: left center, center center, right center;
	background-repeat: no-repeat;
}

/* Body copy in a band is 18 on a 22px line, the same measure Figma gives the
   About page's band and the people bios. */
.mjm-band p {
	font-size: var(--wp--preset--font-size--large);
	line-height: calc(22 / 18);

	/* Measured in characters rather than pixels. 596px was the comp's column at
	   18px text, which is 59 characters; holding the character count keeps the
	   copy breaking the same way at any type size, where holding the pixel width
	   would have added a line every time the type grew. */
	max-width: 59ch;
	margin-inline: auto;
}

/* Links inherit the band's white rather than the page's link colour, which left
   the address in black on the red ground, and keep their underline so they are
   still identifiable as links without a colour difference to rely on.
 *
 * Prose links only. A core button is an anchor too, so an unqualified
 * `.mjm-band a` underlined every call to action in every band -- "See Open
 * Positions", "See Our Work", "Explore Positions", "Start a Conversation". A
 * button is already unmistakably a control from its pill, and does not need the
 * underline that an inline link does. Excluded here rather than undone in the
 * button rule below, so one place decides what gets an underline. */
.mjm-band a:not(.wp-block-button__link) {
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 0.2em;
}

.mjm-band .wp-block-heading {
	font-size: var(--wp--preset--font-size--xx-large);
}

/* The comp draws this button 351x39. Core's default button padding made it
   substantially taller, which showed as too much white above and below the
   label, so the vertical padding is set explicitly rather than inherited. */
.mjm-band .wp-block-button__link {
	padding-block: 0.4375rem;
	padding-inline: var(--wp--preset--spacing--60);
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 600;
	line-height: 1.5;
	text-align: center;
}

.mjm-band .wp-block-button {
	min-inline-size: min(351px, 100%);
}

.mjm-band .wp-block-button__link {
	inline-size: 100%;
}

/* Three separate centred paragraphs. The comp spaces them evenly at roughly
   20px, with a slightly wider gap under the heading and more air before the
   button -- not the 56px between paragraphs that core's block gap produced.
   The heading occupies 66px before the body starts, measured off the comp:
   our 48px line box plus 18. (Figma reports the heading frame as 54.2 and the
   gap as 11.8, which is the same distance split differently -- it sizes the
   frame to the font's natural line box rather than to the 48px leading.) */
/* .mjm-band--tall is deliberately gone. It existed to force the homepage band
   to a second fixed height (442 against the others' 364), which the content now
   produces on its own -- it has three paragraphs where the others have one. */

/* 18px under a 48px heading in the comp: three eighths of its own size. */
.mjm-band .wp-block-heading {
	margin-block: 0 0.375em;
}

/* Roughly a line's worth of space between paragraphs and before the button --
   24px against the 22.4px body it separates, so stated as 1.1em rather than as
   the pixel it happened to be at one size. */
.mjm-band p {
	margin-block: 0 1.1em;
}

.mjm-band p:last-of-type {
	margin-block-end: 1.1em;
}

/* -------------------------------------------------------------------------
 * Case study cards
 * ---------------------------------------------------------------------- */

/* -------------------------------------------------------------------------
 * Case study carousel: the bar above the row, and its chevrons
 * ---------------------------------------------------------------------- */

.mjm-carousel {
	/* Whether the chevrons can hang outside the row is a question about the row,
	   not the window. 432 is its width at the 480 this used to watch. */
	container: mjm-carousel / inline-size;

	position: relative;
}

/* Dots at one end, the section's link at the other, on the line above the
   cards -- which is where the comp puts "View Our Work". */
.mjm-carousel__bar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--40);
	margin-bottom: var(--wp--preset--spacing--40);
	min-height: 1.5rem;
}

/* Holds the row's left edge even when there is no link opposite. */
.mjm-carousel__link {
	margin-inline-start: auto;
}

.mjm-carousel__pips {
	display: flex;
	gap: 0.5rem;
	margin-left: 20px;
}

/* A pip carries the brand colour of the card it stands for -- the same colour
   that card's circle button uses -- set per pip by the block. Since the colour
   now varies, it cannot also be what marks the active one, so these follow the
   idiom the hero's slide dots already established: a ring while inactive,
   filled once active. The fallback matters only if a pip is ever rendered
   without its variable. */
.mjm-carousel__pip {
	/* A button, so it needs the usual chrome taken off it. */
	position: relative;
	padding: 0;
	appearance: none;
	cursor: pointer;

	width: 8px;
	height: 8px;
	border: 1.5px solid var(--mjm-pip-color, var(--wp--preset--color--curious-george));
	border-radius: 50%;
	background-color: transparent;
	transition: background-color 200ms ease;
}

/* Same hit slop as the hero's slide dots, and for the same reason: 8px is far
   short of the ~44px a finger needs, and growing the dot would change the
   design. The pips sit 0.5rem apart, so each takes its own width plus that gap
   and they meet without overlapping. */
.mjm-carousel__pip::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	translate: -50% -50%;
	width: calc(100% + 0.5rem);
	min-height: 44px;
}

.mjm-carousel__pip:focus-visible {
	outline: 2px solid var(--wp--preset--color--bonfire);
	outline-offset: 3px;
}

/* Hovering a pip fills it faintly, so it reads as something you can press
   rather than only as a read-out of where you are. */
.mjm-carousel__pip:hover:not(.is-active) {
	background-color: color-mix(
		in srgb,
		var(--mjm-pip-color, var(--wp--preset--color--curious-george)) 35%,
		transparent
	);
}

.mjm-carousel__pip.is-active {
	background-color: var(--mjm-pip-color, var(--wp--preset--color--curious-george));
}

/* Chevrons sit over the row's edges and stay out of the way until the pointer
   is on the section, matching the other card track. They are always reachable
   by keyboard, which is why the visibility is opacity rather than display. */
.mjm-carousel__nav {
	position: absolute;
	top: calc(50% + 1.5rem);
	z-index: 2;
	display: grid;
	place-items: center;
	width: 44px;
	height: 44px;
	padding: 0;
	border: 0;
	border-radius: 50%;
	background-color: var(--wp--preset--color--base);
	box-shadow: 0 2px 12px rgb(0 0 0 / 18%);
	color: var(--wp--preset--color--contrast);
	cursor: pointer;
	opacity: 0;
	transition: opacity 200ms ease, background-color 200ms ease;
}

.mjm-carousel__nav svg {
	width: 20px;
	height: 20px;
}

.mjm-carousel__nav--prev {
	left: -22px;
	transform: scaleX(-1);
}

.mjm-carousel__nav--next {
	right: -22px;
}

/* :not([disabled]) rather than a separate rule to hide the dead one, matching
   the pinned track: a reveal that does not exclude disabled controls
   out-specifies any later rule trying to hide them, and the exhausted chevron
   reappears on hover.
 *
 * The button itself is included alongside the container, because it overhangs
 * the carousel by half its width -- a pointer travelling onto its outer half
 * leaves .mjm-carousel and would otherwise fade the control out from under the
 * cursor. */
.mjm-carousel:hover .mjm-carousel__nav:not([disabled]),
.mjm-carousel:focus-within .mjm-carousel__nav:not([disabled]),
.mjm-carousel__nav:not([disabled]):hover,
.mjm-carousel__nav:not([disabled]):focus-visible {
	opacity: 1;
}

.mjm-carousel__nav:not([disabled]):hover {
	background-color: var(--wp--preset--color--clarity);
}

/* Nothing left to scroll to in that direction. */
.mjm-carousel__nav[disabled] {
	pointer-events: none;
}

@media (hover: none) {
	/* No hover to reveal them, and swiping is the native control. */
	.mjm-carousel__nav {
		display: none;
	}
}

/* The chevrons overhang the row by half their width so they straddle its edges.
   That is wider than the page gutter on a narrow window, and it pushed the
   document 22px sideways at 390. A phone never sees it -- the rule above takes
   them out of the layout where there is no hover -- but a narrow desktop window
   does, so tuck them inside the row instead of outside it. */
@container mjm-carousel (max-width: 432px) {
	.mjm-carousel__nav--prev {
		left: 0;
	}

	.mjm-carousel__nav--next {
		right: 0;
	}
}

/* Cards are drawn at a fixed 545x451 in the comp rather than dividing the
   container, and three of them plus gaps come to 1731px against a 1248px
   container -- so the row scrolls. The comp shows exactly that, with the first
   and last cards cut off by the viewport edges. */
.mjm-cards--grid {
	/* The comp draws the artwork at 545x451. The card is wider than its artwork
	   by the frame it carries -- the reveal padding plus its border on each
	   side -- so that is added back here rather than the 545 being quietly
	   eroded to 523. */
	--mjm-card-frame: 10px;
	--mjm-card-artwork: 545px;

	/* How much of the next card must stay in view. More than the card's own
	   corner radius, so the sliver reads as an invitation to scroll rather than
	   as something clipped by accident. This is the visible card, not the space
	   after the current one -- the gap has to be subtracted separately, or it
	   spends the peek before any card is shown. */
	--mjm-card-peek: 45px;

	/* Held in a variable because the width formula below has to subtract it. As
	   two separate literals they drifted: the formula reserved 45px of room and
	   the gap then took 32 of it, leaving 13px of the next card. */
	--mjm-card-gap: var(--wp--preset--spacing--40);

	/* The drawn size where there is room for it, and never so wide that the peek
	   disappears. Without the second term there is a band of viewports -- around
	   1264 to 1294, which includes 1280 -- where the second card is trimmed by
	   only a dozen pixels and looks like a rendering fault. Inside that band the
	   card gives up a few pixels instead; at 1440 and above it is untouched. */
	--mjm-card-width: min(
		calc(var(--mjm-card-artwork) + 2 * (var(--mjm-card-frame) + 1px)),
		calc((100% - 2 * var(--mjm-card-gap) - var(--mjm-card-peek)) / 2)
	);

	display: grid;
	grid-template-columns: repeat(var(--mjm-card-cols, 3), var(--mjm-card-width));

	/* The endless variant clones the set either side of the real cards, so the
	   row holds three times what the template describes. Column flow keeps the
	   extras in the same row instead of wrapping them onto new ones. */
	grid-auto-flow: column;
	grid-auto-columns: var(--mjm-card-width);

	/* Tighter than the section rhythm elsewhere, and deliberately so. With cards
	   at a fixed width the sliver of the next one is whatever the container has
	   left -- container minus two cards and two gaps -- so the gap is what tunes
	   it. At 3rem only 18px of the third card showed, less than its own 30px
	   corner radius, which reads as a clipping mistake rather than an invitation
	   to scroll. Every pixel off the gap buys two of peek. */
	gap: var(--mjm-card-gap);
	overflow-x: auto;
	scroll-snap-type: x proximity;
	scrollbar-width: none;
	/* Each card sizes to its own content. The comp draws the three at 517, 735
	   and 556 tall, so they are not equal-height -- and with the default stretch,
	   opening one card's quote grew all three. */
	align-items: start;
}

.mjm-cards--grid::-webkit-scrollbar {
	display: none;
}

.mjm-cards--grid .mjm-cards__card {
	scroll-snap-align: start;
}

.mjm-cards--rows {
	display: grid;
	gap: var(--wp--preset--spacing--90);
}

.mjm-cards__card {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--30);

	/* Whether the client name fits beside the "Case Study" link is a question
	   about the card, which is why it is asked of the card. 455 is a card's width
	   at the 600px window the old rule watched -- but the card is now free to be
	   that narrow at any window, in a column or a sidebar, and it still stacks. */
	container: mjm-card / inline-size;
}

/* Stacked rows put the quote beside the artwork, as on the Work page. */
.mjm-cards--rows .mjm-cards__card {
	display: grid;
	grid-template-columns: minmax(220px, 26%) 1fr;
	gap: var(--wp--preset--spacing--60);
	align-items: start;
}

/* Left column stacks name, quote and the arrow link; artwork spans the rows
   on the right, as on the Work page. */
.mjm-cards--rows .mjm-cards__media {
	grid-column: 2;
	grid-row: 1 / span 3;
}

.mjm-cards--rows .mjm-cards__head {
	grid-column: 1;
	grid-row: 1;
}

.mjm-cards__name {
	font-size: var(--wp--preset--font-size--x-large);
	font-weight: 400;
	margin: 0;
}

.mjm-cards__name a {
	text-decoration: none;
}

.mjm-cards__discipline {
	font-size: var(--wp--preset--font-size--small);
	font-weight: 700;
	margin: 0.15rem 0 0;
}

.mjm-cards--rows .mjm-cards__quote {
	grid-column: 1;
	grid-row: 2;
	margin: var(--wp--preset--spacing--50) 0 0;
	font-size: var(--wp--preset--font-size--small);
	align-self: start;
}

.mjm-cards__quote p {
	margin: 0 0 var(--wp--preset--spacing--30);
}

.mjm-cards__quote footer strong {
	display: block;
}

.mjm-cards__quote footer span {
	opacity: 0.75;
}

.mjm-cards--rows .mjm-cards__foot {
	grid-column: 1;
	grid-row: 3;
	align-self: end;
}

.mjm-cards__media {
	display: block;
	text-decoration: none;
}

.mjm-cards__img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: var(--mjm-card-ar, 545 / 451);
	object-fit: cover;
	border-radius: 20px;
}

/*
 * The Work page draws these at 840x560 in v3, where the homepage carousel is
 * still the squarer 545x451. It matters rather than being a taste: Peer to
 * Peer Academy's card art has words set into it, and the taller crop cut them
 * off at both ends.
 */
.mjm-cards--rows {
	--mjm-card-ar: 840 / 560;
}

/* Placeholder plate for a case study with no artwork yet. */
.mjm-cards__plate {
	display: grid;
	place-items: center;
	aspect-ratio: var(--mjm-card-ar, 545 / 451);
	border-radius: 20px;
	padding: var(--wp--preset--spacing--40);
	background-color: var(--mjm-plate, var(--wp--preset--color--bonfire));
	color: var(--wp--preset--color--clarity);
	text-align: center;
}

.mjm-cards__plate-text {
	font-family: var(--wp--preset--font-family--serif);
	font-size: var(--wp--preset--font-size--box-title);
	font-weight: 400;
	line-height: 1.05;
}

.mjm-cards__foot {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--30);
	margin-top: auto;
}

/* Two-line label variant: client name over discipline. */
.mjm-cbtn--stacked {
	align-items: center;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 400;
}

.mjm-cbtn--stacked .mjm-cbtn__label {
	display: flex;
	flex-direction: column;
	line-height: 1.3;
}

.mjm-cbtn--stacked .mjm-cbtn__label strong {
	font-weight: 700;
}

@media (max-width: 900px) {
	/* One card at a time, rather than two plus a peek.
	 *
	 * The desktop --mjm-card-width reserves room for two cards and the peek, so
	 * each card gets a bit under half the row. There is no room for that here:
	 * at 390px it left every card 121px wide -- a fifth of the 567px it is drawn
	 * at -- which squeezed the client name in the footer button onto three
	 * lines. The artwork override below was already trying to fix this and could
	 * not, because the two-card term stayed the smaller half of the min() and so
	 * always won.
	 *
	 * Below this width the card takes the row less one peek, which keeps the
	 * next card's edge in view as the invitation to scroll sideways. */
	.mjm-cards--grid {
		--mjm-card-artwork: min(545px, 78vw);

		/* Tighter than the desktop rhythm. Showing one card at a time, the room
		   after it is shared between the gap and the next card, so every pixel
		   of gap is a pixel of the next card that does not show -- and at 2rem
		   only 13px of it did, well under its corner radius, which reads as a
		   clipped card rather than as more to scroll to. */
		--mjm-card-gap: var(--wp--preset--spacing--30);

		--mjm-card-width: min(
			calc(var(--mjm-card-artwork) + 2 * (var(--mjm-card-frame) + 1px)),
			calc(100% - var(--mjm-card-peek) - var(--mjm-card-gap))
		);
	}

	.mjm-cards--rows .mjm-cards__card {
		grid-template-columns: 1fr;
	}

	.mjm-cards--rows .mjm-cards__media,
	.mjm-cards--rows .mjm-cards__quote,
	.mjm-cards--rows .mjm-cards__foot {
		grid-column: 1;
		grid-row: auto;
	}
}

/* -------------------------------------------------------------------------
 * Page masthead
 * ---------------------------------------------------------------------- */

/* The masthead row: a heading and the intro paragraphs beside it.
 *
 * Two departures from the default content column, both so the paragraphs wrap
 * the way the comp does -- ours ran a line longer in each of the three.
 *
 * The row reaches 32px further right than the body column. Done with padding
 * rather than a margin because core sets `margin-left/right: auto !important`
 * on children of a constrained group, which cannot be overridden without
 * another !important; lifting the max-width lets the row fill the group and the
 * start padding puts its left edge back on the gutter, where the comp has it.
 *
 * The column gap is the comp's, and much tighter than the spacing token it used
 * to take. That token is a page-level rhythm and was never measured for this
 * row: at 128px it stole enough width from the paragraph column to cost a line
 * in every paragraph. */
.mjm-masthead > .wp-block-columns {
	/* Fill the group and re-create the content column with padding, because
	   core sets `margin-left/right: auto !important` on a constrained group's
	   children and a margin cannot be overridden without another !important.
	 *
	 * The inset is what the auto margins would have been -- half the difference
	 * between the group and the content column -- so the row's left edge lands
	 * exactly where every other block on the page starts. The right side takes
	 * the same inset less 32px, which is the only asymmetry.
	 *
	 * Both clamp at 0, and that is what makes this work at every width rather
	 * than only at 1440: once the page is narrower than the content column the
	 * inset is zero, the row simply fills the group, and the masthead lines up
	 * with everything else instead of sitting 32px in from it. Which is what a
	 * flat 32px padding did, at every width below 1376. */
	--mjm-masthead-inset: max(0px, calc((100% - var(--wp--style--global--content-size)) / 2));

	max-width: none;
	padding-inline-start: var(--mjm-masthead-inset);
	padding-inline-end: max(0px, calc(var(--mjm-masthead-inset) - 32px));

	column-gap: var(--mjm-masthead-gap, 67px);
}

.mjm-masthead h1 {
	margin: 0;
}

.mjm-masthead p {
	font-size: var(--wp--preset--font-size--medium);
	max-width: 46ch;
}

/* -------------------------------------------------------------------------
 * People grid
 * ---------------------------------------------------------------------- */

.mjm-people {
	margin-top: var(--wp--preset--spacing--70);
}

.mjm-people__heading {
	font-size: var(--wp--preset--font-size--xx-large);
	margin: 0 0 var(--wp--preset--spacing--40);
}

.mjm-people__grid {
	display: grid;

	/* The block asks for a column count; this honours it without a breakpoint.
	 *
	 * `--mjm-people-share` is the width one card gets when that many fit on a
	 * row, written as a percentage so it scales with the container rather than
	 * being a number someone measured. Feed it to auto-fit as the floor and the
	 * count comes out exactly as asked at any width -- a percentage floor tracks
	 * the container, so it never rounds to a different number.
	 *
	 * The 8rem is what makes it collapse. Once a card's share falls below that
	 * the floor takes over, the share stops scaling, and auto-fit drops to
	 * however many 8rem cards do fit -- two on a phone. So the count is
	 * declared, the collapse is intrinsic, and there is no width in either.
	 *
	 * The previous flat 30% floor is why this was needed: 30% x 4 is 120%, so
	 * four could never fit however many the block asked for, and the leadership
	 * row silently capped at three. */
	--mjm-people-gap: var(--wp--preset--spacing--40);
	--mjm-people-share: calc(
		(100% - (var(--mjm-people-cols, 4) - 1) * var(--mjm-people-gap)) / var(--mjm-people-cols, 4)
	);
	grid-template-columns: repeat(auto-fit, minmax(min(100%, max(8rem, var(--mjm-people-share))), 1fr));

	/* Widen past the body column to the comp's card size, where there is room.
	 *
	 * The comp draws these at 311x414 and the headshots are exported at exactly
	 * that, so a narrower card is scaling them down. Four of them plus gaps
	 * wants 1340 where the body column gives 1248.
	 *
	 * Capped by 100cqw -- the .mjm-team group's content box, which is the page
	 * minus its gutter, so the grid can use the full width of the section but
	 * never spills into the margin. Measuring the group rather than the viewport
	 * also keeps a scrollbar out of the sum; 100vw includes it, and overshooting
	 * by its width is how a page ends up scrolling sideways.
	 *
	 * A negative inline margin rather than a width, so the grid grows out of the
	 * column symmetrically and the heading above it stays in the column with the
	 * rest of the page's text -- which is the relationship the comp draws, where
	 * the heading sits further in than the cards do. min() with 0 means this can
	 * only ever widen. */
	--mjm-people-card: 311px;
	--mjm-people-target: min(
		100cqw,
		var(--mjm-people-cols, 4) * var(--mjm-people-card)
			+ (var(--mjm-people-cols, 4) - 1) * var(--mjm-people-gap)
	);
	margin-inline: min(0px, calc((100% - var(--mjm-people-target)) / 2));

	/* Rows are measured the way the design specifies them: from the baseline of
	   a role to the top of the next row's photograph, which wants 62px. At the
	   48px the scale offered that distance was 53. Columns keep their token. */
	gap: 57px var(--mjm-people-gap);
	align-items: start;
}

.mjm-people__photo {
	position: relative;
	aspect-ratio: 3 / 4;
	border-radius: 14px;
	overflow: hidden;
	/* The plate shows through as a gradient behind cut-out headshots, and
	   stands alone for anyone whose photo has not been taken yet. */
	background: linear-gradient(
		180deg,
		var(--mjm-people-plate, var(--wp--preset--color--curious-george)),
		color-mix(in srgb, var(--mjm-people-plate, var(--wp--preset--color--curious-george)) 55%, var(--wp--preset--color--clarity))
	);
}

.mjm-people__img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center top;
}

.mjm-people__initials {
	position: absolute;
	inset: 0;
	display: grid;
	place-items: center;
	font-family: var(--wp--preset--font-family--serif);
	font-size: var(--wp--preset--font-size--box-title);
	font-weight: 400;
	color: var(--wp--preset--color--clarity);
}

/* 18px on a 21px line, per the style sheet. The `large` token is that 18px, so
   it is used rather than a bare number; the line height is stated because 21px
   is not the ratio the token carries by default.
 *
 * Confirmed against the comp rather than only the spec: "Matt Jensen" is 99px
 * of ink there and 18px bold renders it at 101, where 16px gives 90. */
.mjm-people__name {
	font-size: var(--wp--preset--font-size--large);
	line-height: calc(21 / 18);
	font-weight: 700;
	margin: var(--wp--preset--spacing--20) 0 0;
}

.mjm-people__role {
	font-size: var(--wp--preset--font-size--large);
	line-height: calc(21 / 18);
	margin: 0;
	opacity: 0.75;
}

/* 18px on a 22px line. Taken from the Figma node itself rather than measured
   off a PNG: it carries the "Quotes" character style -- Hanken Grotesk Regular,
   size 18, lineHeight 22, letterSpacing 0. */
.mjm-people__bio {
	font-size: var(--wp--preset--font-size--large);
	line-height: calc(22 / 18);
	margin-top: var(--wp--preset--spacing--30);
}

.mjm-people__bio p {
	margin: 0 0 0.75em;
}

/* -------------------------------------------------------------------------
 * Careers benefits list
 * ---------------------------------------------------------------------- */

/* The "Benefits" section heading. An h1 rather than an h2 because the comp
   draws it in the headline serif at display size, which is exactly what
   theme.json already gives an h1 -- and the page carries more than one h1 by
   design already, the same call the homepage and Careers pinned headings make. */
.mjm-benefits__heading {
	margin-block: 0 var(--wp--preset--spacing--60);
}

.mjm-benefits {
	display: grid;

	/* Two columns, as the comp has. 20rem is about the narrowest a benefit reads
	   at before its heading starts breaking mid-phrase, so below that the row
	   drops to one; the 40% share holds it to two wherever there is room rather
	   than letting a third slot open on a wide screen. */
	grid-template-columns: repeat(auto-fit, minmax(min(100%, max(20rem, 40%)), 1fr));

	/* 95px between rows in the comp, which is the spacing|70 token. */
	gap: var(--wp--preset--spacing--70) var(--wp--preset--spacing--60);
	list-style: none;
	margin: 0;
	padding: 0;
}

.mjm-benefit__title {
	margin-block: 0 0.5em;
	color: var(--mjm-benefit-color, var(--wp--preset--color--contrast));
	text-wrap: balance;
}

.mjm-benefit__text {
	margin-block: 0;

	/* The comp sets these at 414-475px on an 18px body, so 26 characters short
	   of the column. Capping the measure rather than the column keeps the line
	   length readable on a wide screen without narrowing the section. */
	max-width: 32em;
}

/* -------------------------------------------------------------------------
 * Contact
 * ---------------------------------------------------------------------- */

.mjm-form-placeholder {
	padding: var(--wp--preset--spacing--40);
	border: 1px dashed color-mix(in srgb, var(--wp--preset--color--bonfire) 60%, transparent);
	border-radius: 12px;
}

/* Formidable's markup, styled to the comp: rounded Bonfire-outlined inputs.
 *
 * Formidable has a Styles screen that would set most of this, and it is not
 * used, for two reasons. It holds one colour per role with no light and dark
 * pair, and this site has a mode toggle, so every colour here has to resolve
 * through a theme token instead. And a style is a frm_styles post, which lives
 * in the database rather than the repository, so it would not travel with a
 * deploy and would have to be set again by hand on staging and production. */
.mjm-contact .frm_form_field input[type="text"],
.mjm-contact .frm_form_field input[type="email"],
.mjm-contact .frm_form_field textarea {
	width: 100%;
	padding: 0.75rem 1rem;
	border: 1px solid var(--wp--preset--color--bonfire);
	border-radius: 10px;
	background: transparent;
	color: var(--wp--preset--color--contrast);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--medium);
}

.mjm-contact .frm_form_field label {
	display: block;
	margin-bottom: 0.35rem;
	font-weight: 700;
	font-size: var(--wp--preset--font-size--small);
}

/* Formidable paints checkbox and radio labels from --check-label-color, which
   it fixes at the light mode ink and applies with !important. Redefining the
   property reaches that rule without answering !important with more of it, and
   is where a colour belongs anyway. */
.mjm-contact .with_frm_style {
	--check-label-color: var(--wp--preset--color--contrast);
}

.mjm-contact .frm_button_submit {
	padding: 0.7rem 1.5rem;
	border: 0;
	border-radius: 10px;
	background-color: var(--wp--preset--color--bonfire);
	color: var(--wp--preset--color--clarity);
	font-weight: 700;
	cursor: pointer;
}

/* -------------------------------------------------------------------------
 * Case study detail
 *
 * Two concentric measures, both taken from the v3 comps on their 1440 frame:
 * the image rows and the hero run 1400 wide (a 20px margin), and the copy runs
 * 1164 (a 138px margin). Neither is the site's content-size, so both are set
 * here rather than leaning on the constrained layout.
 * ---------------------------------------------------------------------- */

.mjm-cs {
	/* The comp's page margin, and its gutter between tiles. */
	--mjm-cs-edge: 20px;
	--mjm-cs-gap: 24px;
}

/*
 * Each piece sets its own measure rather than inheriting one from a shared
 * parent: the rows are mjm/case-study-row blocks inside post content now, so
 * they are not siblings of the header and cannot be selected together.
 */
.mjm-cs__head,
.mjm-cs__summary,
.mjm-cs__nav {
	max-width: min(1164px, 100% - var(--mjm-cs-edge) * 2);
	margin-inline: auto;
}

.mjm-cs__hero,
.mjm-cs__row {
	max-width: min(1400px, 100% - var(--mjm-cs-edge) * 2);
	margin-inline: auto;
}

.mjm-cs__hero-img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 1400 / 662;
	object-fit: cover;
	border-radius: 20px;
}

.mjm-cs__head {
	margin-block: var(--wp--preset--spacing--50) var(--wp--preset--spacing--40);
}

/* post-content contributes a wrapper of its own between the header and the
   first row; without this the gap collapses to nothing. */
.mjm-cs .entry-content {
	margin-block: 0;
}

.mjm-cs__title {
	font-family: var(--wp--preset--font-family--sans);
	font-size: var(--wp--preset--font-size--xx-large);
	font-weight: 400;
	margin: 0;
}

/* -- Discipline tags ---------------------------------------------------- */

.mjm-cs__tags {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem 0.35rem;
	list-style: none;
	margin: var(--wp--preset--spacing--30) 0 0;
	padding: 0;
}

.mjm-cs__tag {
	/* Unmatched tags fall back to the page's own ink rather than to one of the
	   five arbitrarily; see mjm_cs_tag_colors(). */
	background: var(--mjm-tag-color, var(--wp--preset--color--contrast));
	border-radius: 60px;
	color: var(--wp--preset--color--clarity);
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 600;
	line-height: 1.2;
	padding: 0.5em 1.15em;
}

/* -- Client / Objective / Solution -------------------------------------- */

.mjm-cs__summary {
	display: grid;

	/* 330px columns with 87px between them, on the comp's 1164. Below about
	   20rem a column starts breaking its own words, so it drops to one. */
	grid-template-columns: repeat(auto-fit, minmax(min(100%, max(18rem, 28%)), 1fr));
	column-gap: clamp(2rem, 6.04vw, 87px);
	row-gap: var(--wp--preset--spacing--50);
	margin-bottom: var(--wp--preset--spacing--60);
}

.mjm-cs__label {
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 600;
	line-height: 1.45;
	margin: 0;
}

.mjm-cs__label--results {
	margin-top: 1.45em;
}

.mjm-cs__text,
.mjm-cs__results {
	/* The comp sets these at 18px, which is this theme's `large`. They are on
	   `medium` instead: fluid typography scales `medium` to 19.2px at this
	   width and `large` to 22.4px, so `medium` is the nearer of the two and
	   `large` would set this page's copy larger than any other body text on
	   the site. The comps predate that scale-up, so their px values no longer
	   name the tokens they used to. */
	font-size: var(--wp--preset--font-size--medium);
	line-height: 1.45;
}

.mjm-cs__text p {
	margin: 0 0 1.45em;
}

.mjm-cs__text p:last-child {
	margin-bottom: 0;
}

.mjm-cs__results {
	list-style: none;
	margin: 0;
	padding: 0;
}

/* -- Image rows ---------------------------------------------------------
 *
 * Five shapes, all on the comp's 1400 / 24px gutter, so every track below is
 * an exact division of it: thirds are 451, halves 688, two-thirds 925. The
 * fr units reproduce those because grid shares out what is left after the
 * gutters, which is what the comp's numbers were laid out on too.
 * ---------------------------------------------------------------------- */

.mjm-cs__row {
	display: grid;
	gap: var(--mjm-cs-gap);

	/* Rows are separate blocks now, so the space between them is each row's
	   own bottom margin rather than one grid gap. */
	margin-block: 0 var(--mjm-cs-gap);
}

/* The last row hands its space to the nav below, which owns the whole gap
   between the final image and the footer. */
.mjm-cs__row:last-child {
	margin-block-end: 0;
}

.mjm-cs__row--two {
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

.mjm-cs__row--three {
	grid-template-columns: repeat(3, minmax(0, 1fr));
}

.mjm-cs__row--one-two {
	grid-template-columns: minmax(0, 451fr) minmax(0, 925fr);
}

.mjm-cs__row--two-one {
	grid-template-columns: minmax(0, 925fr) minmax(0, 451fr);
}

.mjm-cs__tile {
	margin: 0;
	overflow: hidden;
	border-radius: 20px;
}

.mjm-cs__row--one .mjm-cs__tile {
	aspect-ratio: 1400 / 780;
}

.mjm-cs__row--two .mjm-cs__tile {
	aspect-ratio: 688 / 655;
}

.mjm-cs__row--three .mjm-cs__tile,
.mjm-cs__row--one-two .mjm-cs__tile,
.mjm-cs__row--two-one .mjm-cs__tile {
	aspect-ratio: 1;
}

/* The wide tile in a mixed row. Both tiles are 451 tall in the comp, so
   matching their heights is what these two ratios are for. */
.mjm-cs__row--one-two .mjm-cs__tile:last-child,
.mjm-cs__row--two-one .mjm-cs__tile:first-child {
	aspect-ratio: 925 / 451;
}

.mjm-cs__img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* -- Prev / next --------------------------------------------------------- */

.mjm-cs__nav {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--40);

	/* Tuned to the ink, not to the box: the comp puts 48px between the last
	   image and the top of the capital, and 35px between its baseline and the
	   footer. Solved against the measured offsets at this size -- see the
	   nav-link rule for where the rest of each number goes. */
	margin-block-start: 0;
	padding-block: 28px 26px;

	/* The comp sets this at 36px, its H3. No preset lands there -- x-large is
	   too small and xx-large is 48px at this width -- so it is spelt out, with
	   a floor that keeps it from crowding a phone. */
	font-size: clamp(1.5rem, 2.5vw, 2.25rem);
	line-height: 1.5;
}

.mjm-cs__nav-link {
	display: inline-flex;
	align-items: center;
	gap: 0.75rem;
	text-decoration: none;
}

.mjm-cs__nav-link:hover {
	text-decoration: underline;
	text-underline-offset: 0.25em;
}

@media (max-width: 900px) {
	/* One column throughout. A row's tiles keep the aspect they were drawn at
	   -- a square stays square -- because cropping them to something else is a
	   decision about the artwork, not about the width available. */
	.mjm-cs__row {
		grid-template-columns: 1fr;
	}
}

/* Below this the two client names cannot sit side by side without breaking
   mid-name -- "Vance / Thompson / Vision" in a 350px measure -- so they stack.
   The comp has no phone layout for this; 48/35 are desktop numbers. */
@media (max-width: 560px) {
	.mjm-cs__nav {
		flex-direction: column;
		align-items: flex-start;
		gap: 0.75em;
	}
}

/* -------------------------------------------------------------------------
 * Expertise + Industries
 *
 * Hovering an industry cross-fades to that industry's image and dims the
 * partner logos that are not assigned to it. Every image is in the DOM and
 * stacked, so the swap is a class toggle rather than a network request.
 * ---------------------------------------------------------------------- */

.mjm-expertise {
	padding-block: var(--wp--preset--spacing--90);
}

.mjm-expertise__inner {
	display: grid;
	grid-template-columns: minmax(0, 48%) minmax(0, 1fr);
	gap: var(--wp--preset--spacing--80);
	align-items: start;
	max-width: var(--wp--style--global--wide-size, 1440px);
	margin-inline: auto;
	padding-inline: var(--wp--preset--spacing--60);
}

/* Stack the industry images. The container owns the height via aspect-ratio
   and every figure is absolutely positioned, so the box cannot collapse when
   the active industry is not the first one. */
.mjm-expertise__media {
	position: relative;
	aspect-ratio: 620 / 727;
	border-radius: 24px;
	overflow: hidden;
}

.mjm-expertise__figure {
	position: absolute;
	inset: 0;
	margin: 0;
	opacity: 0;
	transition: opacity 320ms ease;
}

.mjm-expertise__figure.is-active {
	opacity: 1;
}

.mjm-expertise__img,
.mjm-expertise__placeholder {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.mjm-expertise__placeholder {
	background: linear-gradient(
		160deg,
		var(--wp--preset--color--anchor),
		var(--wp--preset--color--morning-dew)
	);
}

.mjm-expertise__tag {
	position: absolute;
	left: var(--wp--preset--spacing--30);
	bottom: var(--wp--preset--spacing--30);
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.3rem 0.75rem 0.3rem 0.35rem;
	border-radius: 999px;
	background-color: var(--mjm-tag-bg, var(--wp--preset--color--anchor));
	color: var(--wp--preset--color--clarity);
	font-size: var(--wp--preset--font-size--tiny);
	font-weight: 600;
}

.mjm-expertise__tag-dot {
	width: 18px;
	height: 18px;
	border-radius: 50%;
	background-color: color-mix(in srgb, currentColor 80%, transparent);
	flex: 0 0 auto;
}

.mjm-expertise__heading {
	font-size: var(--wp--preset--font-size--xx-large);
	margin: 0 0 var(--wp--preset--spacing--30);
	max-width: 22ch;
	text-wrap: balance;
}

.mjm-expertise__copy {
	max-width: 52ch;
}

.mjm-expertise__copy p {
	margin: 0 0 var(--wp--preset--spacing--30);
}

.mjm-expertise__label {
	margin: var(--wp--preset--spacing--50) 0 var(--wp--preset--spacing--30);
}

.mjm-expertise__spans {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0.75rem var(--wp--preset--spacing--40);
	list-style: none;
	margin: 0 0 var(--wp--preset--spacing--60);
	padding: 0;
}

.mjm-expertise__span-button {
	display: flex;
	align-items: center;
	gap: 0.625rem;
	width: 100%;
	padding: 0.4rem 0.6rem;
	margin-inline-start: -0.6rem;
	border: 0;
	border-radius: 999px;
	background: none;
	color: inherit;
	font: inherit;
	font-size: var(--wp--preset--font-size--small);
	text-align: left;
	cursor: pointer;
	transition: background-color 200ms ease;
}

.mjm-expertise__span-button.is-active {
	background-color: color-mix(in srgb, var(--wp--preset--color--contrast) 18%, transparent);
}

.mjm-expertise__span-button:focus-visible {
	outline: 2px solid var(--wp--preset--color--bonfire);
	outline-offset: 2px;
}

.mjm-expertise__icon {
	display: inline-flex;
	width: 20px;
	flex: 0 0 auto;
}

.mjm-expertise__icon svg {
	width: 100%;
	height: auto;
}



/* Logos: dim the ones outside the active industry rather than hiding them, so
   the grid never reflows mid-hover. */
.mjm-expertise__logos {
	/* Every row is this tall whatever it holds, so the rows read as a grid
	   rather than as four bands of different weights. The gap gives back what
	   the height takes: 52 and 20 keep the 72px pitch that 40 and 32 had, so
	   only the last row's extra height changes the block's overall size.
	 *
	   Height is what the near-square logos are limited by -- everything wider
	   than about 3.1:1 runs out of cell width first and does not change when
	   this does. See the cap on the image below. */
	--mjm-logo-row: 52px;

	gap: 20px;
	/* A column of industries, each of which lays its own three logos out in a
	   row. The row is the group's own grid rather than a slice of one big grid,
	   which is what makes the category the unit: hovering an industry lights
	   one row, and a category can never be split across rows however many
	   there are.
	 *
	   The obvious alternative -- one twelve-cell grid with the groups taken out
	   of the box tree by `display: contents` -- does not work on <details>. It
	   reports `display: contents` and a zero-width box, but Blink still lays
	   the children out inside a UA-generated content wrapper (`::details-content`),
	   so each group stayed a single grid item and every industry came out as a
	   vertical list in one column. Nothing about the flattening was visible in
	   the computed styles; only the tile positions showed it. */
	display: flex;
	flex-direction: column;
}

/* Three across, fixed. The three are one industry, so the count is the group
   size rather than a guess at what fits: letting auto-fit choose gave four at
   one width and two at another, neither of which is a category.
 *
 * Three stops working on a phone -- at 390 the wall is 350px, so a column is
 * 95px and Bausch + Lomb's 12.7:1 wordmark comes out 8px tall -- and there the
 * groups become an accordion instead. See the query further down. */
.mjm-expertise__group-logos {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	grid-auto-rows: var(--mjm-logo-row);
	gap: var(--wp--preset--spacing--40);
	align-items: center;
	justify-items: center;
}

/* Client logos rest monochrome and are revealed when relevant -- hovered
   directly, or their industry hovered. The two modes differ in what "revealed"
   means (light shows the artwork's colour, dark keeps the white tint and just
   goes opaque), so both states come from tokens rather than being branched here. */
.mjm-expertise__logo {
	filter: var(--mjm-logo-filter);
	opacity: var(--mjm-logo-rest);
	transition: opacity 200ms ease, filter 200ms ease;
}

.mjm-expertise__logo:hover,
.mjm-expertise__logo:focus-within {
	filter: var(--mjm-logo-filter-active);
	opacity: 1;
}

/* Real client logos are wide lockups, not the square marks the comp mocked up
   with -- from 1.6:1 for Vision Innovation Partners to 12.7:1 for Bausch +
   Lomb. Almost all of them are limited by the width of their cell rather than
   by this height, so the cap's job is only to stop the squarest ones towering
   over the widest. Four columns, as the comp draws, would put Bausch + Lomb's
   wordmark at 9px tall, so the grid packs three.

   The cap is the row height, so a logo grows until it reaches the width of its
   cell. Only the near-square ones ever get there: Vision Innovation Partners
   was drawn 64px wide in a 163px cell and read as the runt of the grid, and it
   is the one the extra height buys anything for. */
.mjm-expertise__logo-img {
	display: block;
	max-width: 100%;
	max-height: var(--mjm-logo-row);
	width: auto;
	height: auto;
}

/* A logo that ships its own dark-mode file uses it, rather than being tinted.
 *
 * The tint forces every pixel white, which is right for black line art and
 * wrong for artwork with shading: it left RxSight's gradient sphere as one flat
 * shape where the supplied file keeps nine tones. So for these the tint is
 * switched off and the second file is shown instead -- both are in the markup
 * and only one is ever displayed.
 *
 * Written twice, against the explicit choice and against the OS preference,
 * matching how every other dark rule in this file is expressed. */
.mjm-expertise__logo-img--dark {
	display: none;
}

:root[data-theme="dark"] .mjm-expertise__logo--has-dark {
	--mjm-logo-filter: none;
	--mjm-logo-filter-active: none;
}

:root[data-theme="dark"] .mjm-expertise__logo--has-dark .mjm-expertise__logo-img {
	display: none;
}

:root[data-theme="dark"] .mjm-expertise__logo--has-dark .mjm-expertise__logo-img--dark {
	display: block;
}

@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) .mjm-expertise__logo--has-dark {
		--mjm-logo-filter: none;
		--mjm-logo-filter-active: none;
	}

	:root:not([data-theme="light"]) .mjm-expertise__logo--has-dark .mjm-expertise__logo-img {
		display: none;
	}

	:root:not([data-theme="light"]) .mjm-expertise__logo--has-dark .mjm-expertise__logo-img--dark {
		display: block;
	}
}

/* Wide screens: the industry headers are not there. The rows they label are
   still rows, and the industry buttons above the wall already name them, so on
   a desktop a group is just an unannounced row of three. theme.js opens every
   <details> at these widths, because a closed one hides its content whatever
   this says. */
.mjm-expertise__group-summary {
	display: none;
}

/* One logo per row on a phone.
 *
 * Three columns are legible while the wall is wide enough -- 250px a column at
 * 900, 158px at 600 -- but at 430 that is 107px and at 390 it is 95, which
 * leaves the widest wordmarks under 10px tall. Full width gives every one of
 * them the 40px cap instead, and the industries stay in their given order, one
 * after another, so the grouping survives even without the rows. */
@media (max-width: 600px) {
	/* A phone gets a list of collapsible industries rather than a grid. Three
	   across is unreadable at this width -- 95px a column, which left the widest
	   wordmark 8px tall -- and one per row for twelve logos is a long scroll
	   with no structure. Sections give the structure and the full width. */
	/* Block, not the flex column above: the sections butt up against each other
	   and are separated by their own hairline rules, so the gap between groups
	   goes away. */
	.mjm-expertise__logos {
		display: block;
	}

	.mjm-expertise__group {
		border-block-end: 1px solid color-mix(in srgb, currentColor 15%, transparent);
	}

	.mjm-expertise__group-summary {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: var(--wp--preset--spacing--30);
		padding-block: var(--wp--preset--spacing--30);
		font-size: var(--wp--preset--font-size--large);
		font-weight: 600;
		cursor: pointer;

		/* The default disclosure triangle, removed both ways: `list-style` is
		   what the standard specifies and the ::-webkit- rule is what older
		   WebKit actually reads. */
		list-style: none;
	}

	.mjm-expertise__group-summary::-webkit-details-marker {
		display: none;
	}

	.mjm-expertise__group-summary:focus-visible {
		outline: 2px solid var(--wp--preset--color--bonfire);
		outline-offset: 3px;
	}

	/* The chevron ships pointing right, so it is turned down while the section
	   is closed and up once it is open -- the way a disclosure control is
	   expected to read. */
	.mjm-expertise__group-summary svg {
		/* Sized here because mjm_chevron_svg() carries a viewBox and no
		   dimensions -- left alone it filled the row, several hundred pixels of
		   it. The carousel navs size the same icon the same way. */
		flex: 0 0 auto;
		width: 18px;
		height: 18px;
		rotate: 90deg;
		transition: rotate 200ms ease;
	}

	.mjm-expertise__group[open] .mjm-expertise__group-summary svg {
		rotate: -90deg;
	}

	.mjm-expertise__group-logos {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: var(--wp--preset--spacing--40);
		padding-block: var(--wp--preset--spacing--20) var(--wp--preset--spacing--50);
	}

	/* Sized by the logo's own proportions rather than to one height. At a shared
	   height the widest of these is four times the width of the narrowest, which
	   is what made the stacked list look ragged; scaling by 1/sqrt(ratio) trades
	   a little height variation for much less width variation. The clamp stops
	   either end running away -- the near-square one from towering, the 13:1
	   wordmark from thinning to a line. */
	.mjm-expertise__logo-img {
		max-height: clamp(26px, calc(var(--mjm-logo-scale, 1) * 42px), 54px);
	}

	/* The industry buttons say the same four things the section headers now do,
	   so they go; the eyebrow above them stays and reads as the list's heading.
	   Opening a section is what selects an industry here -- theme.js treats the
	   two the same way. */
	.mjm-expertise__spans {
		display: none;
	}
}

.mjm-expertise.is-filtering .mjm-expertise__logo {
	opacity: var(--mjm-logo-dim);
}

.mjm-expertise.is-filtering .mjm-expertise__logo.is-highlighted {
	filter: var(--mjm-logo-filter-active);
	opacity: 1;
}



@media (prefers-reduced-motion: reduce) {
	.mjm-expertise__figure,
	.mjm-expertise__logo,
	.mjm-expertise__span-button {
		transition: none;
	}
}

@media (max-width: 900px) {
	.mjm-expertise__inner {
		grid-template-columns: 1fr;
		gap: var(--wp--preset--spacing--50);
	}

	.mjm-expertise__spans {
		grid-template-columns: 1fr;
	}

	/* No column count here any more. Three fitted when these were square
	   placeholder tiles; with the real wide lockups it meant 95px cells on a
	   phone and Bausch + Lomb's wordmark 8px tall. The base rule's auto-fit
	   drops to two on its own, which is what the artwork needs. */
}

/* -------------------------------------------------------------------------
 * Mobile corrections
 *
 * Found by auditing every page at 390px with touch emulation. Each of these is
 * a real defect at phone width rather than a taste adjustment.
 * ---------------------------------------------------------------------- */

/* Hero legibility.
 *
 * The per-slide scrim is tuned for desktop, where the headline sits low in the
 * frame over the darker part of the photograph. On a phone the text block fills
 * most of the height and rides up into the sky, leaving white on pale.
 *
 * Rather than darken the whole image -- which throws away the photograph the
 * slide was chosen for -- put the extra veil behind the text block only, fading
 * out above it. The editor's per-slide overlay choice still governs the image
 * itself, and the picture stays clean everywhere the copy is not. */
@container mjm-hero (max-width: 780px) {
	.mjm-hero__inner {
		position: relative;
		z-index: 0;
	}

	.mjm-hero__inner::before {
		content: "";
		position: absolute;
		/* Reaches above the text so the fade starts off-block. No horizontal
		   inset: the inner already spans the full hero width and carries the
		   gutters as its own padding, so pulling it wider only overflowed the
		   page by exactly one gutter. */
		inset: -4rem 0 0;
		z-index: -1;
		background: linear-gradient(
			to top,
			rgba(0, 0, 0, 0.62) 0%,
			rgba(0, 0, 0, 0.52) 60%,
			rgba(0, 0, 0, 0) 100%
		);
	}
}

@media (max-width: 900px) {
	/* The comp's insets are drawn against a 506px card. At 78vw there is not
	   room for them, and the fixed height only strands space under copy that
	   has wrapped onto more lines. */
	.mjm-pinned--boxes .mjm-pinned__card {
		min-block-size: 0;
		padding: var(--wp--preset--spacing--50);
	}

	.mjm-pinned--boxes .mjm-pinned__card-text {
		margin-block-start: var(--wp--preset--spacing--40);
		max-inline-size: none;
	}
}

/* The card footer wraps only when the client name is long, so a grid of cards
   ended up with some names beside their "Case Study" link and some above it.
   Stack every one of them below ~600px so the row reads consistently. */
@container mjm-card (max-width: 455px) {
	.mjm-cards__foot {
		flex-direction: column;
		align-items: flex-start;
		gap: var(--wp--preset--spacing--20);
	}
}

/* Touch targets, by hit slop rather than by inflating the control.
 *
 * The industry buttons render 34px tall and the hero's slide dots 9px, against
 * the ~44px a finger needs. Growing them would change the design, so instead
 * each gets a transparent ::after that extends the tappable area past its own
 * box. Nothing about the rendered appearance changes -- only what the browser
 * considers a hit.
 *
 * Note on the dots: they sit 8px apart, so a full 44px-wide slop each would
 * overlap and the later sibling would steal half its neighbour's target. Each
 * takes its own width plus half the gap horizontally, and the full 44px
 * vertically, which is the axis that actually matters for a control pinned to
 * the bottom of the hero.
 *
 * Deliberately not gated on `pointer: coarse`. The slop is invisible and the
 * geometry cannot reach a neighbouring control -- the industry buttons sit in a
 * 12px-gap grid and grow by 5px a side, the dots meet exactly without
 * overlapping -- so a mouse only benefits from the larger target too. It also
 * avoids depending on a media feature that cannot be emulated reliably, which
 * would have left this untestable.
 */
.mjm-expertise__span-button,
.mjm-hero__dot {
	position: relative;
}

.mjm-expertise__span-button::after,
.mjm-hero__dot::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	translate: -50% -50%;
	min-width: 100%;
	min-height: 44px;
}

.mjm-expertise__span-button::after {
	width: 100%;
}

.mjm-hero__dot::after {
	width: calc(100% + 0.5rem);
}

/* -------------------------------------------------------------------------
 * Case study card hover: border + pull quote
 *
 * The Brevium card in the homepage comp is drawn in its hover state -- it has a
 * layer named "Hover Border", carries the pull quote and attribution, and is
 * 735px tall against 517 and 556 for the two resting cards. So hovering a card
 * outlines it and opens the quote, pushing the footer down.
 * ---------------------------------------------------------------------- */

.mjm-cards--grid .mjm-cards__card {
	/* Constant padding and a transparent border, so revealing the border cannot
	   shift the image. The inset is invisible until the border colours in --
	   which is why the comp's border sits outside the artwork. The column width
	   adds this back, so the artwork itself lands at its drawn size. */
	padding: var(--mjm-card-frame);
	border: 1px solid transparent;
	border-radius: 30px;
	transition: border-color 240ms ease;
}

.mjm-cards--grid .mjm-cards__card:hover,
.mjm-cards--grid .mjm-cards__card:focus-within {
	border-color: var(--wp--preset--color--bonfire);
}

/* 0fr -> 1fr animates a genuinely unknown height, which max-height cannot do
   without hard-coding a guess that clips longer quotes. */
.mjm-cards__reveal {
	display: grid;
	grid-template-rows: 0fr;
	transition: grid-template-rows 320ms ease;
}

.mjm-cards--grid .mjm-cards__card:hover .mjm-cards__reveal,
.mjm-cards--grid .mjm-cards__card:focus-within .mjm-cards__reveal {
	grid-template-rows: 1fr;
}

.mjm-cards__pull {
	overflow: hidden;
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
}

.mjm-cards__pull p {
	margin: var(--wp--preset--spacing--30) 0 0;
}

.mjm-cards__pull footer {
	margin-top: var(--wp--preset--spacing--30);
}

.mjm-cards__pull footer strong {
	display: block;
	font-weight: 700;
}

.mjm-cards__pull footer span {
	display: block;
	opacity: 0.75;
}

@media (prefers-reduced-motion: reduce) {
	.mjm-cards--grid .mjm-cards__card,
	.mjm-cards__reveal {
		transition: none;
	}
}

/* No hover on touch, and tapping a card navigates rather than revealing. The
   quote is worth reading, so show it outright instead of hiding it behind an
   interaction that cannot happen. */
@media (hover: none) {
	.mjm-cards__reveal {
		grid-template-rows: 1fr;
	}
}

/* Copyright line: 12px, white. It was inheriting the 14px "small" size and an
   inline #9a9a9a, which read as off-white against the black footer. 12px is not
   in the type scale -- the smallest token is 13px -- and adding a global step
   for one line would be worse than setting it here. */
.mjm-footer__copyright {
	font-size: 0.75rem;
	line-height: 1.5;
	color: var(--wp--preset--color--clarity);
}

/* -------------------------------------------------------------------------
 * Pinned card track: scroll controls
 *
 * Side-to-side scrolling is close to undiscoverable with a mouse, so the track
 * gets explicit chevrons. They share the track's grid cell rather than being
 * positioned against the section, so they follow the track when the layout
 * collapses to one column.
 * ---------------------------------------------------------------------- */

.mjm-pinned__nav {
	grid-column: 2;
	grid-row: 1;
	align-self: center;
	z-index: 2;
	display: grid;
	place-items: center;
	width: 44px;
	height: 44px;
	padding: 0;
	border: 1px solid color-mix(in srgb, var(--wp--preset--color--contrast) 18%, transparent);
	border-radius: 50%;
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
	cursor: pointer;
	opacity: 0;
	transition: opacity 200ms ease, background-color 200ms ease;
}

.mjm-pinned__nav svg {
	width: 20px;
	height: 20px;
}

.mjm-pinned__nav--prev {
	justify-self: start;
	margin-inline-start: -14px;
}

.mjm-pinned__nav--prev svg {
	rotate: 180deg;
}

.mjm-pinned__nav--next {
	justify-self: end;
	margin-inline-end: -14px;
}

/* Revealed on hover or keyboard focus, but never when there is nothing left to
   scroll to in that direction -- :not([disabled]) keeps that out of the reveal
   rule rather than fighting it with !important. */
.mjm-pinned:hover .mjm-pinned__nav:not([disabled]),
.mjm-pinned:focus-within .mjm-pinned__nav:not([disabled]) {
	opacity: 1;
}

.mjm-pinned__nav:not([disabled]):hover {
	background-color: color-mix(in srgb, var(--wp--preset--color--contrast) 8%, var(--wp--preset--color--base));
}

.mjm-pinned__nav:focus-visible {
	outline: 2px solid var(--wp--preset--color--bonfire);
	outline-offset: 2px;
	opacity: 1;
}

/* Touch has swiping, so the controls would only be clutter. */
@media (hover: none) {
	.mjm-pinned__nav {
		display: none;
	}
}

@media (max-width: 900px) {
	.mjm-pinned__nav {
		grid-column: 1;
		grid-row: 2;
	}
}

@media (prefers-reduced-motion: reduce) {
	.mjm-pinned__nav {
		transition: none;
	}
}

/* -------------------------------------------------------------------------
 * Cross-document view transitions
 *
 * Opts every page into animating between navigations (Figma comment 23: the
 * case study thumbnail expanding into the header of the page it opens). The
 * pairing itself is done by matching `view-transition-name` on the card image
 * and the hero it becomes -- see mjm_view_transition_name().
 *
 * The rule has to be in both the outgoing and the incoming document, which it
 * is, because this stylesheet loads on every page. Where the browser does not
 * support it the navigation is simply ordinary, so there is nothing to fall
 * back to and no script involved.
 * ---------------------------------------------------------------------- */

@view-transition {
	navigation: auto;
}

/* The images are the point of the transition; the rest of the page cross-fading
   underneath them at speed is what makes it feel like a lurch. */
::view-transition-old(root),
::view-transition-new(root) {
	animation-duration: 320ms;
	animation-timing-function: ease;
}

@media (prefers-reduced-motion: reduce) {
	/* Opting out entirely rather than shortening it. This animation moves a
	   large element across the viewport, which is exactly what the preference is
	   asking us not to do. */
	@view-transition {
		navigation: none;
	}
}

/* -------------------------------------------------------------------------
 * 404 maze
 * ---------------------------------------------------------------------- */

.mjm-maze {
	padding-block: var(--wp--preset--spacing--90);
}

.mjm-maze__inner {
	display: grid;
	grid-template-columns: minmax(0, 42%) minmax(0, 1fr);
	gap: var(--wp--preset--spacing--80);
	align-items: center;
	max-width: var(--wp--style--global--wide-size, 1440px);
	margin-inline: auto;
	padding-inline: var(--wp--preset--spacing--60);
}

.mjm-maze__eyebrow {
	margin: 0 0 var(--wp--preset--spacing--20);
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--bonfire);
}

.mjm-maze__heading {
	margin: 0;
	font-size: var(--wp--preset--font-size--box-title);
	line-height: 1.05;
}

.mjm-maze__body {
	margin: var(--wp--preset--spacing--30) 0 0;
	max-width: 42ch;
	font-size: var(--wp--preset--font-size--large);
	opacity: 0.75;
}

.mjm-maze__out {
	margin: var(--wp--preset--spacing--50) 0 0;
}

.mjm-maze__stage {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--wp--preset--spacing--20);
}

/* No border-radius here on purpose. The maze draws its own rounded frame, and
   half of that stroke's width sits outside the grid -- a radius on the element
   would clip the corners of the wall it is meant to soften. touch-action stops
   a swipe across the maze scrolling the page instead of moving the dot. */
.mjm-maze__canvas {
	display: block;
	touch-action: none;
}

.mjm-maze__status {
	/* Holds its line so the win message cannot shift the maze. */
	min-height: 1.5em;
	margin: 0;
	font-size: var(--wp--preset--font-size--tiny);
	text-align: center;
	opacity: 0.6;
}

/* Only name the control the visitor actually has. */
.mjm-maze__hint--touch {
	display: none;
}

@media (hover: none) {
	.mjm-maze__hint--keys {
		display: none;
	}

	.mjm-maze__hint--touch {
		display: inline;
	}
}

.mjm-maze__tilt {
	padding: 0.5rem 1.25rem;
	border: 1px solid var(--wp--preset--color--contrast);
	border-radius: 999px;
	background: none;
	color: inherit;
	font: inherit;
	font-size: var(--wp--preset--font-size--small);
	cursor: pointer;
}

@media (max-width: 900px) {
	.mjm-maze__inner {
		grid-template-columns: minmax(0, 1fr);
		gap: var(--wp--preset--spacing--60);
	}
}

/* A brief flourish before the redirect, so solving it registers. */
.mjm-maze.is-solved .mjm-maze__canvas {
	animation: mjm-maze-solved 600ms ease;
}

@keyframes mjm-maze-solved {
	50% {
		transform: scale(1.03);
		filter: brightness(1.15);
	}
}

@media (prefers-reduced-motion: reduce) {
	.mjm-maze.is-solved .mjm-maze__canvas {
		animation: none;
	}
}

/* -------------------------------------------------------------------------
 * Policy pages
 *
 * Privacy, AI policy, terms. Everything else here is art-directed, and putting
 * that type on four thousand pixels of legal prose made it shout: a 110px
 * display title clipped under the sticky header, section headings at 48px --
 * as loud as a landing page's -- and lines running the full 1248px column, some
 * 140 characters, which is roughly twice what anyone reads comfortably.
 *
 * MJM's current site is the reference for the tone, and the thing it gets right
 * is the *step* between levels: a coloured serif title with room beneath it,
 * section headings barely larger than the body, and a measure short enough to
 * hold. Colour does the work that size was doing.
 * ---------------------------------------------------------------------- */

.mjm-policy main {
	/* Narrow the column by telling the constrained layout what its column is,
	   rather than setting max-width on the children and fighting the auto
	   margins it forces on them. One value, and every child agrees.
	 *
	 * In em, resolved once here against the body size, so the column follows the
	 * type: the site's body size is fluid, and at a fixed pixel width the same
	 * column held 77 characters at 1440 and 88 at 900 -- drifting past a
	 * readable measure exactly as the text got smaller. Not ch, which each child
	 * would resolve against its own font size, leaving the title and the body on
	 * different edges. */
	--wp--style--global--content-size: 38em;

	/* Clear of the sticky header: the title used to start underneath it. */
	padding-block: var(--wp--preset--spacing--70) var(--wp--preset--spacing--80);
}

.mjm-policy main h1 {
	font-size: var(--wp--preset--font-size--page-title);
	color: var(--wp--preset--color--bonfire);
	margin-block-end: var(--wp--preset--spacing--50);
	padding-block-end: var(--wp--preset--spacing--40);
	border-block-end: 2px solid currentColor;
}

/* Section headings step down to just above the body rather than towering over
   it, and take a rule above instead of size to mark the break. */
.mjm-policy main h2 {
	font-size: var(--wp--preset--font-size--x-large);
	line-height: 1.25;
	margin-block: var(--wp--preset--spacing--60) var(--wp--preset--spacing--30);
	padding-block-start: var(--wp--preset--spacing--30);
	border-block-start: 2px solid var(--mjm-policy-accent, var(--wp--preset--color--bonfire));
}

/* The rules carry the palette in turn, which is where the colour comes from
   once the headings have stopped being large enough to carry it themselves. */
.mjm-policy main h2:nth-of-type(5n + 1) { --mjm-policy-accent: var(--wp--preset--color--bonfire); }
.mjm-policy main h2:nth-of-type(5n + 2) { --mjm-policy-accent: var(--wp--preset--color--anchor); }
.mjm-policy main h2:nth-of-type(5n + 3) { --mjm-policy-accent: var(--wp--preset--color--olive-branch); }
.mjm-policy main h2:nth-of-type(5n + 4) { --mjm-policy-accent: var(--wp--preset--color--morning-dew); }
.mjm-policy main h2:nth-of-type(5n + 5) { --mjm-policy-accent: var(--wp--preset--color--curious-george); }

.mjm-policy main h3 {
	font-size: var(--wp--preset--font-size--large);
	line-height: 1.3;
	margin-block: var(--wp--preset--spacing--40) var(--wp--preset--spacing--20);
}

/* Legal prose is read slowly and in full, so it gets more leading than the
   marketing copy elsewhere. */
.mjm-policy main p,
.mjm-policy main li {
	line-height: 1.7;
}

.mjm-policy main ul {
	list-style: none;
	padding-inline-start: 0;
}

.mjm-policy main ul li {
	position: relative;
	padding-inline-start: 1.75rem;
	margin-block-end: var(--wp--preset--spacing--20);
}

/* A brand square rather than a bullet, matching the current site's list mark. */
.mjm-policy main ul li::before {
	content: "";
	position: absolute;
	inset-inline-start: 0;
	inset-block-start: 0.62em;
	width: 0.5rem;
	height: 0.5rem;
	background-color: var(--wp--preset--color--bonfire);
}
