/*
 * Chef Ashish — app.css
 *
 * Everything theme.json cannot express. Kept small on purpose: if a rule can
 * live in theme.json it lives there, so the Site Editor stays the source of
 * truth and this file does not quietly override it.
 *
 * Design tokens are NOT defined here. They are declared under settings.custom
 * in theme.json and reach us as --wp--custom--*, which means the editor canvas
 * and the front end resolve them from the same place.
 */

/* ---------------------------------------------------------------------------
   Section rhythm

   112px at desktop, 56px at mobile, held consistently: the rhythm is what
   makes a long page feel composed. It is a system property rather than a
   value re-typed into eighty-seven blocks, so it can be retuned in one place
   and an editor cannot half-set it.
   ------------------------------------------------------------------------- */

/*
 * Sections are full bleed, so the ground runs to the viewport edge and the
 * page reads as a stack of bands rather than a column of tinted cards.
 *
 * The gutter is the section's own, not the root padding: 64px at desktop
 * down to 22px on a phone. Without it the edge of the tint is also the edge
 * of the text, which is what made the tinted bands look like they had been
 * cropped. The block class is in the selector so this beats core's
 * .has-global-padding rule regardless of stylesheet order.
 */
/*
 * The gutter comes from the root padding in theme.json rather than a second
 * copy of the same clamp. A section is full bleed, so it cancels the root
 * padding and has to put it back; when the two values drifted apart the band's
 * own content sat 2px off everything outside the band, which showed up on
 * mobile as a title and its article body on different edges.
 */
.wp-block-group.ca-section {
	padding-inline:
		var(--wp--style--root--padding-left, clamp(22px, 4vw, 64px))
		var(--wp--style--root--padding-right, clamp(22px, 4vw, 64px));
}

.ca-section {
	padding-block: var(--wp--preset--spacing--90);
}

/*
 * Clamped, not the flat 80px preset. The default rhythm is
 * clamp(56px, 8vw, 112px), so a flat value meant --tight was lighter than the
 * default on a desktop and heavier than it on a phone, which is the opposite
 * of what the modifier says it does. It has to scale with the thing it is a
 * quieter version of.
 */
.ca-section--tight {
	padding-block: clamp(40px, 6vw, 80px);
}

.ca-section--compact {
	padding-block: var(--wp--preset--spacing--60);
}

/* A page that is mostly one short message, such as the 404, needs the air. */
.ca-section--chapter {
	padding-block: var(--wp--preset--spacing--100);
}

/* An opener sits directly under the header, which already supplies air. */
.ca-section--opener {
	padding-block-start: var(--wp--preset--spacing--60);
	padding-block-end: var(--wp--preset--spacing--90);
}

/*
 * Two sections on the same ground have no visible boundary between them, so
 * their facing paddings read as one dead band of twice the rhythm rather than
 * as a section break. Collapse them back to a single unit.
 *
 * Tone should alternate by design, and in the shipped patterns it does. This
 * rule is what keeps a page composed after someone reorders sections in the
 * editor, which is the one thing no pattern can defend against on its own.
 */
.ca-section.is-style-bone + .ca-section.is-style-bone,
.ca-section.is-style-paper + .ca-section.is-style-paper,
.ca-section.is-style-mist + .ca-section.is-style-mist,
.ca-section.is-style-ink + .ca-section.is-style-ink,
.ca-section.is-style-teal + .ca-section.is-style-teal {
	padding-block-start: 0;
}

/*
 * Bands must touch.
 *
 * theme.json declares a global block gap of 24px, and core applies it as
 * margin-block-start on every child of a flow or constrained layout. That is
 * the right rhythm from one paragraph to the next, and exactly wrong between
 * two full bleed bands: it opens a 24px window onto the body ground between
 * them.
 *
 * The bug hid for as long as it did because the body ground is bone, so every
 * seam between two bone sections was invisible. It only surfaced where the
 * tones either side were something else, and the clearest case was the case
 * study template: mist above, paper below, and 24px of bone showing between
 * the two, which reads as a stray grey strip under the problem/solution/result
 * band rather than as a gap.
 *
 * It also quietly disabled the collapse rule above. Two same tone sections
 * cannot collapse their facing padding into one unit when there is a margin
 * holding them apart, so that rule was narrowing a seam it was written to
 * remove.
 *
 * Three selectors because a band reaches the page by three routes: as a
 * section in a template, as a section inside post-content on the page-full
 * template, and as a template part wrapping a section. Specificity is the
 * point in each: core's rule is `:root :where(.is-layout-constrained) > *`,
 * which resolves to 0,1,0, so 0,2,0 or 0,1,1 wins it on merit and nothing here
 * needs !important.
 */
.wp-block-group.ca-section {
	margin-block: 0;
}

.wp-block-template-part:has(> .ca-section) {
	margin-block: 0;
}

.wp-site-blocks > * {
	margin-block-start: 0;
}

/* ---------------------------------------------------------------------------
   Typographic craft
   ------------------------------------------------------------------------- */

body {
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

/*
 * Running text gets a measure. Full width lines of body copy are a slop tell.
 *
 * This no longer applies to .entry-content > p. A paragraph capped here while
 * the headings around it were capped by the block layout's contentSize left the
 * two on different widths, and a constrained layout centres each on its own
 * width, so the article ended up with one edge for headings and another for
 * paragraphs. The layout owns the reading width now; this class is for the odd
 * block that needs the measure on its own.
 */
.ca-measure {
	max-width: var(--wp--custom--measure);
}

/*
 * Long-form article body.
 *
 * The reading width is set by the block layout's contentSize, not here, so
 * every child is capped to the same number and core centres the column on the
 * page. An earlier version capped each child here and left the column pinned to
 * the wide gutter; on a 1440 screen that put a 665px ribbon of text against the
 * left edge with half the viewport empty beside it. A single column of prose
 * has nothing to sit beside, so it belongs in the middle.
 *
 * Media is still exempt: a photograph or a table wants the full container,
 * only the reading line needs capping.
 */
.ca-prose > .alignwide,
.ca-prose > .alignfull,
.ca-prose > figure,
.ca-prose > .wp-block-image,
.ca-prose > .wp-block-gallery,
.ca-prose > .wp-block-table,
.ca-prose > .wp-block-embed,
.ca-prose > .wp-block-columns,
.ca-prose > .wp-block-separator {
	max-width: none;
}

/*
 * Section head: a heading and the line that introduces it.
 *
 * These two belong to each other and must share a left edge. Written as loose
 * siblings of a constrained section they do not: the heading is usually
 * alignwide (wideSize) while the paragraph falls back to contentSize and is
 * then auto-centred, so the subheading lands a full (wideSize - contentSize)/2
 * to the right of the heading it belongs to. On a 1440 viewport that measured
 * 120px versus 335px, with the spacer between them on a third edge at 270px.
 *
 * The wrapper is alignwide with flow layout, so every child starts at the same
 * wide gutter and only the reading line is capped. Bumping wideSize or
 * contentSize can no longer pull the pair apart.
 */
.ca-section__head > * {
	max-width: none;
}

.ca-section__head > p {
	max-width: var(--wp--custom--measure);
}

/*
 * The section spine.
 *
 * A constrained section caps any child that carries no alignment at contentSize
 * and then centres it, while its alignwide siblings sit on the wide gutter. The
 * result is a third left edge: an eyebrow, a button group or a stray paragraph
 * lands (wideSize - contentSize)/2 to the right of the heading it belongs to,
 * which reads as neither left aligned nor centred, just adrift. On a 1440
 * viewport that was 270px against a spine of 120px.
 *
 * Unaligned children keep their reading measure but start on the same gutter as
 * the wide ones, so every block in a section shares one left edge. Below
 * wideSize the max() resolves to 0 and everything sits flush against the
 * section's own padding.
 *
 * The :has() gate is the whole point, and skipping it was a mistake worth
 * naming. A section is only pulled left when it actually contains a wide
 * sibling to line up with. A section whose content is a single narrow column
 * has nothing to align to, and dragging it to the wide gutter just strands it
 * against the left edge with a screen of dead space beside it. Those stay
 * centred, which is core's behaviour and the right one.
 *
 * Deliberately centred content opts out too: that is the one case where the box
 * position carries meaning, because moving the box would move the optical
 * centre with it.
 *
 * The !important is not a shortcut. Core's constrained layout ships
 * `margin-left: auto !important` on exactly this set of elements, so an
 * important declaration is the only thing that can reach them. Ours carries the
 * higher specificity of the two, which is what decides it.
 */
.ca-section.is-layout-constrained:has(> .alignwide, > .alignfull) > :where(
	:not(.alignwide):not(.alignfull):not(.alignleft):not(.alignright):not(.has-text-align-center)
) {
	margin-inline-start: max(0px, (100% - var(--wp--style--global--wide-size)) / 2) !important;
	margin-inline-end: auto !important;
}

/* Instrument Serif italic descenders clip at tight leading. Give them room. */
h1 em, h2 em, h3 em,
h1 i, h2 i, h3 i {
	line-height: 1.15;
	padding-block-end: 0.06em;
	display: inline-block;
}

/* Stat numerals are set in the display serif. See DESIGN.md. */
.ca-stat__figure {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--display-m);
	line-height: 1;
	letter-spacing: -0.015em;
	margin: 0;
}

/* ---------------------------------------------------------------------------
   Buttons
   ------------------------------------------------------------------------- */

.wp-block-button.is-style-outline-ink > .wp-block-button__link {
	background-color: transparent;
	color: var(--wp--preset--color--contrast);
	border: 1px solid var(--wp--preset--color--contrast);
}

.wp-block-button.is-style-outline-ink > .wp-block-button__link:hover,
.wp-block-button.is-style-outline-ink > .wp-block-button__link:focus-visible {
	background-color: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--surface);
	border-color: var(--wp--preset--color--contrast);
}

/* On dark grounds the outline flips to bone. */
.is-style-ink .wp-block-button.is-style-outline-ink > .wp-block-button__link,
.is-style-teal .wp-block-button.is-style-outline-ink > .wp-block-button__link,
.has-contrast-background-color .wp-block-button.is-style-outline-ink > .wp-block-button__link,
.has-teal-background-color .wp-block-button.is-style-outline-ink > .wp-block-button__link {
	color: var(--wp--preset--color--base);
	border-color: color-mix(in srgb, var(--wp--preset--color--base) 45%, transparent);
}

.is-style-ink .wp-block-button.is-style-outline-ink > .wp-block-button__link:hover,
.is-style-teal .wp-block-button.is-style-outline-ink > .wp-block-button__link:hover,
.is-style-ink .wp-block-button.is-style-outline-ink > .wp-block-button__link:focus-visible,
.is-style-teal .wp-block-button.is-style-outline-ink > .wp-block-button__link:focus-visible,
.has-contrast-background-color .wp-block-button.is-style-outline-ink > .wp-block-button__link:hover,
.has-teal-background-color .wp-block-button.is-style-outline-ink > .wp-block-button__link:hover {
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
	border-color: var(--wp--preset--color--base);
}

.wp-block-button.is-style-quiet-link > .wp-block-button__link {
	background-color: transparent;
	border-color: transparent;
	color: var(--wp--preset--color--accent);
	padding-inline: 0;
	padding-block: 4px;
	text-underline-offset: 0.28em;
	text-decoration-thickness: 1px;
	text-decoration: underline;
	text-decoration-color: color-mix(in srgb, var(--wp--preset--color--accent) 40%, transparent);
}

.wp-block-button.is-style-quiet-link > .wp-block-button__link:hover,
.wp-block-button.is-style-quiet-link > .wp-block-button__link:focus-visible {
	background-color: transparent;
	color: var(--wp--preset--color--contrast);
	text-decoration-color: currentColor;
}

.is-style-ink .wp-block-button.is-style-quiet-link > .wp-block-button__link,
.is-style-teal .wp-block-button.is-style-quiet-link > .wp-block-button__link {
	color: var(--wp--preset--color--base);
}

.is-style-ink .wp-block-button.is-style-quiet-link > .wp-block-button__link:hover,
.is-style-teal .wp-block-button.is-style-quiet-link > .wp-block-button__link:hover {
	color: var(--wp--preset--color--surface);
}

/* A tactile shift on press. Motion dial is low, so this is the whole budget. */
.wp-block-button__link {
	transition:
		background-color var(--wp--custom--transition--tap),
		color var(--wp--custom--transition--tap),
		border-color var(--wp--custom--transition--tap),
		transform var(--wp--custom--transition--press);
}

.wp-block-button__link:active {
	transform: translateY(1px);
}

/* Focus is visible everywhere, on every ground. */
:where(a, button, input, select, textarea, summary, .wp-block-button__link):focus-visible {
	outline: 2px solid var(--wp--preset--color--accent);
	outline-offset: 3px;
	border-radius: var(--wp--custom--radius--chip);
}

.is-style-ink :focus-visible,
.is-style-teal :focus-visible {
	outline-color: var(--wp--preset--color--base);
}

/* ---------------------------------------------------------------------------
   Media
   ------------------------------------------------------------------------- */

.wp-block-image img,
.wp-block-post-featured-image img,
.wp-block-cover {
	border-radius: var(--wp--custom--radius--media);
}

.wp-block-image.is-style-square-corners img {
	border-radius: 0;
}

/* Bleed past the container to the viewport edge. A signature move, used
   sparingly: variance is 0.5, not 0.9. */
@media (min-width: 1024px) {
	.wp-block-image.is-style-bleed-right {
		margin-right: calc(50% - 50vw);
	}

	.wp-block-image.is-style-bleed-right img {
		border-start-end-radius: 0;
		border-end-end-radius: 0;
		width: 100%;
	}

	.wp-block-image.is-style-bleed-left {
		margin-left: calc(50% - 50vw);
	}

	.wp-block-image.is-style-bleed-left img {
		border-start-start-radius: 0;
		border-end-start-radius: 0;
		width: 100%;
	}
}

/* A light warm grade unifies a mixed quality photo library without a filter
   that reads as a filter. */
.ca-graded img {
	filter: saturate(1.04) contrast(1.02);
}

figcaption,
.wp-element-caption {
	margin-block-start: 10px;
	font-size: var(--wp--preset--font-size--x-small);
	color: var(--wp--preset--color--muted);
}

/* ---------------------------------------------------------------------------
   Lists
   ------------------------------------------------------------------------- */

.wp-block-list.is-style-plain,
.wp-block-list.is-style-checklist {
	list-style: none;
	padding-inline-start: 0;
}

.wp-block-list.is-style-checklist > li {
	position: relative;
	padding-inline-start: 26px;
	margin-block-end: 10px;
}

.wp-block-list.is-style-checklist > li::before {
	content: "";
	position: absolute;
	inset-inline-start: 0;
	inset-block-start: 0.62em;
	width: 9px;
	height: 9px;
	border-radius: var(--wp--custom--radius--pill);
	background: var(--wp--preset--color--accent);
}

.is-style-ink .wp-block-list.is-style-checklist > li::before,
.is-style-teal .wp-block-list.is-style-checklist > li::before {
	background: var(--wp--preset--color--base);
}

/* ---------------------------------------------------------------------------
   Chips
   ------------------------------------------------------------------------- */

.wp-block-group.is-style-chip,
.wp-block-post-terms.is-style-chip a {
	display: inline-block;
	padding: 6px 12px;
	border-radius: var(--wp--custom--radius--chip);
	background: var(--wp--custom--tint--chip);
	color: var(--wp--preset--color--contrast);
	font-size: var(--wp--preset--font-size--x-small);
	line-height: 1.2;
	text-decoration: none;
}

.wp-block-post-terms.is-style-chip {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
}

.wp-block-post-terms.is-style-chip .wp-block-post-terms__separator {
	display: none;
}

/* ---------------------------------------------------------------------------
   Rules and dividers
   ------------------------------------------------------------------------- */

/* width:100% overrides core's centred 100px default rule. max-width is left
   alone on purpose: setting it to none defeats alignwide, which had the
   hairline overshooting the container it was supposed to underline. */
.wp-block-separator.is-style-hairline {
	border: none;
	border-top: var(--wp--custom--rule);
	opacity: 1;
	width: 100%;
	margin-block: 0;
}

.is-style-ink .wp-block-separator.is-style-hairline,
.is-style-teal .wp-block-separator.is-style-hairline {
	border-top-color: var(--wp--custom--tint--on-dark-line);
}

@media (min-width: 782px) {
	.wp-block-columns.is-style-divided > .wp-block-column + .wp-block-column {
		border-inline-start: var(--wp--custom--rule);
		padding-inline-start: var(--wp--preset--spacing--50);
	}

	.is-style-ink .wp-block-columns.is-style-divided > .wp-block-column + .wp-block-column,
	.is-style-teal .wp-block-columns.is-style-divided > .wp-block-column + .wp-block-column {
		border-inline-start-color: var(--wp--custom--tint--on-dark-line);
	}
}

/* ---------------------------------------------------------------------------
   Grids

   A grid set with columnCount holds that count at every width, which turns a
   comfortable row of four into four unreadable slivers on a tablet. Step the
   count down instead. Grids sized by minimumColumnWidth already reflow on
   their own and land on the same counts, so one rule covers both.
   ------------------------------------------------------------------------- */

@media (max-width: 900px) {
	.wp-block-group.is-layout-grid,
	.wp-block-post-template.is-layout-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (max-width: 600px) {
	.wp-block-group.is-layout-grid,
	.wp-block-post-template.is-layout-grid {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* ---------------------------------------------------------------------------
   Short items stay two up on a phone

   The rule above is right for anything with a paragraph in it and wrong for a
   set of short labels. A figure and its caption, or a brand name on one line,
   occupies a third of the screen width and the whole of a row, so a set of
   four becomes four screens of scrolling and stops reading as one block of
   proof. Both sets below stay two up all the way down.
   ------------------------------------------------------------------------- */

/*
 * Selected experience.
 *
 * A grid, so it only needs its column count holding. Written three classes
 * deep so it outranks the single column rule above wherever the two meet,
 * rather than relying on which one happens to be printed last. The names are
 * set at x-large for the desktop wall; at half a phone's width that measures
 * four lines for the longest of them, so the two up state takes the next size
 * down.
 */
@media (max-width: 600px) {
	.ca-namewall.wp-block-group.is-layout-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--wp--preset--spacing--40) var(--wp--preset--spacing--30);
	}

	.ca-namewall p {
		font-size: var(--wp--preset--font-size--large);
	}
}

/*
 * The figure rows.
 *
 * These are Columns blocks, not grids, and core stacks every column to
 * flex-basis 100% below 782px with an important declaration. Switching the
 * container to grid is what settles it: flex-basis has no meaning on a grid
 * item, so the important declaration stops applying rather than having to be
 * out-shouted, and the gap core already wrote onto the layout container
 * carries over unchanged.
 *
 * The gate is the stat marker rather than a class on the Columns block, so the
 * rule reaches the figure rows already saved into page content as well as any
 * built later from the pattern. :has() takes the specificity of its argument,
 * which is what puts this above core's rule.
 */
@media (max-width: 781px) {
	.wp-block-columns:has(> .wp-block-column > .ca-stat__figure) {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		align-items: start;
	}

	.wp-block-columns:has(> .wp-block-column > .ca-stat__figure) > .wp-block-column {
		flex-basis: auto !important;
	}
}

/* ---------------------------------------------------------------------------
   Brand wall

   The logos arrive as square files carrying their own grounds: some are black
   squares, some are white, one is a blue circle. Sitting them straight on a
   section tone would dissolve the white ones and turn the black ones into
   holes, so each gets a paper tile and the tone does the separating. That is
   the same move the rest of the theme makes instead of bordering a card.
   ------------------------------------------------------------------------- */

.ca-brandwall__tile {
	padding: var(--wp--preset--spacing--30);
	border-radius: var(--wp--custom--radius--card);
}

.ca-brandwall__tile .wp-block-post-featured-image {
	margin-block: 0;
}

/*
 * Media carries the 20px radius everywhere else on the site. At this size that
 * reads as a rounded blob rather than a logo, so the tile keeps the chip
 * radius and lets the artwork stay square.
 */
.ca-brandwall__tile img {
	display: block;
	width: 100%;
	border-radius: var(--wp--custom--radius--chip);
}

/*
 * Five across, stepped down by hand, with a floor of three.
 *
 * The global grid rule takes every grid to two columns at 900px and one at
 * 600px. One logo per row is not a wall, it is a slideshow, so this grid
 * declares its own steps and never reaches a single column. These three are
 * written two classes deep, which is enough to outrank the global rule above
 * 600px. It is not enough below it, where core has its own collapse to fight;
 * see the note on the phone rule at the end of this block.
 */
.ca-brandwall .wp-block-post-template.is-layout-grid {
	grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 1100px) {
	.ca-brandwall .wp-block-post-template.is-layout-grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

@media (max-width: 860px) {
	.ca-brandwall .wp-block-post-template.is-layout-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/*
 * The phone state, and the one place on this site where core has to be
 * out-weighted rather than out-ordered.
 *
 * wp-includes/blocks/post-template/style.min.css ships this:
 *
 *   @media (max-width: 600px) {
 *     .wp-block-post-template-is-layout-grid
 *     .wp-block-post-template-is-layout-grid
 *     .wp-block-post-template-is-layout-grid
 *     .wp-block-post-template-is-layout-grid { grid-template-columns: 1fr }
 *   }
 *
 * One class repeated four times, on purpose, which puts core at 0,4,0. The
 * three class rules above sit at 0,3,0 and lose, and printing later does not
 * help because order only decides a tie. So the two column floor this block
 * used to claim never actually held: every phone got one logo per row at full
 * content width. That is the bug this replaces.
 *
 * Repeating the layout class four times here reaches 0,5,0 and wins on weight.
 * Three is the floor rather than two because a logo is a square with no text
 * in it. It survives being small in a way a card with a paragraph does not,
 * and three across keeps ten brands to four rows instead of ten.
 *
 * The gap tightens with the columns. At 360px the band has 316px of content to
 * spend, so the 24px desktop gap would take 48 of it and leave the artwork
 * smaller than the tile padding around it.
 */
@media (max-width: 600px) {
	.ca-brandwall .wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--wp--preset--spacing--30);
	}

	.ca-brandwall__tile {
		padding: var(--wp--preset--spacing--20);
	}
}

/*
 * Small phones. 320px leaves 276px of content; three tiles and two 16px gaps
 * would put the artwork under 60px. Trimming the gap and the tile padding buys
 * roughly 15px back per logo without dropping a column.
 */
@media (max-width: 400px) {
	.ca-brandwall .wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid {
		gap: var(--wp--preset--spacing--20);
	}

	.ca-brandwall__tile {
		padding: var(--wp--preset--spacing--10);
	}
}
/* ---------------------------------------------------------------------------
   Pattern furniture
   ------------------------------------------------------------------------- */

.ca-eyebrow {
	font-family: var(--wp--preset--font-family--text);
	font-size: var(--wp--preset--font-size--x-small);
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
}

.is-style-ink .ca-eyebrow,
.is-style-teal .ca-eyebrow {
	color: color-mix(in srgb, var(--wp--preset--color--base) 72%, transparent);
}

.ca-serif,
.ca-serif p {
	font-family: var(--wp--preset--font-family--display);
	line-height: 1.25;
	letter-spacing: -0.005em;
}

.ca-namewall p {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--x-large);
	line-height: 1.2;
	margin: 0;
}

.ca-step__num {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xx-large);
	line-height: 1;
	color: var(--wp--preset--color--accent);
	margin: 0;
}

.is-style-ink .ca-step__num,
.is-style-teal .ca-step__num {
	color: var(--wp--preset--color--base);
	opacity: 0.55;
}

.ca-lede {
	font-size: var(--wp--preset--font-size--large);
	line-height: 1.55;
	color: var(--wp--preset--color--muted);
	max-width: 60ch;
}

.is-style-ink .ca-lede,
.is-style-teal .ca-lede {
	color: inherit;
	opacity: 0.85;
}

/* ---------------------------------------------------------------------------
   Testimonials

   A testimonial is a record in the Testimonials content type, so the quote is
   a bound Paragraph rather than a core/quote block and the marks are supplied
   here. The field instructions ask for the quote without them: text pasted out
   of an email usually arrives carrying its own smart quotes and would double
   up.

   Three layouts share one component. The quote is always .ca-quote and the
   attribution is always .ca-cite, so a change to either lands in all three and
   the bands cannot drift into three separate treatments of the same thing.

   Everything except the quote and the role is optional, including the featured
   image, which may be a portrait, a property logo, or nothing at all. No rule
   below may assume an image exists.
   ------------------------------------------------------------------------- */

.ca-quote {
	font-family: var(--wp--preset--font-family--display);
	line-height: 1.2;
	letter-spacing: -0.01em;
	margin: 0;
	max-width: var(--ca-quote-measure, 30ch);
	text-wrap: pretty;
	quotes: "\201C" "\201D" "\2018" "\2019";
}

.ca-quote::before {
	content: open-quote;
}

.ca-quote::after {
	content: close-quote;
}

/*
 * The opening mark hangs into the margin.
 *
 * Set inline it pushes the first line right by the width of the glyph, so the
 * first line of a quote starts clear of every line beneath it and of
 * everything else in the band. At display scale that offset is around 20px,
 * and it was the most visible thing wrong with the old quotes. A negative
 * text-indent moves the first line only, which is exactly the line the mark
 * sits on.
 *
 * Held back below 782px. The hang is measured against the section gutter,
 * which is 22px on a phone, and a large mark would hang past it to the
 * viewport edge.
 */
@media (min-width: 782px) {
	.ca-quote {
		text-indent: -0.42em;
	}
}

/*
 * Attribution.
 *
 * Role and company used to be joined into one muted line, which gave "General
 * Manager" and "Upper upscale hotel, Lucknow" the same weight and put two
 * commas into a six word string. Three slots let a reader take the person,
 * then the seniority, then the property, in that order.
 *
 * The weight sits on :first-child rather than on the name, so a quote with no
 * name promotes its role to the top line and still reads as composed. That is
 * a normal state rather than an edge case: a name goes up only where there is
 * permission to publish it.
 *
 * Whether the media sits beside the lines or above them is core's flex layout,
 * set on the block itself, not a declaration here. Two stylesheets describing
 * one flex container is how the editor ends up showing an arrangement the
 * front end does not have.
 */
.ca-cite {
	margin-block-start: var(--wp--preset--spacing--50);
}

.ca-cite .ca-cite__lines > * {
	margin-block: 0;
	font-size: var(--wp--preset--font-size--small);
	line-height: 1.45;
	color: var(--wp--preset--color--muted);
}

.ca-cite .ca-cite__lines > :first-child {
	font-weight: 600;
	color: var(--wp--preset--color--contrast);
}

/*
 * The media slot takes whatever the client can supply: a headshot, a property
 * logo, or nothing.
 *
 * It is bounded by height with the width left to the image, so a square
 * portrait lands square and a wide logo keeps its own proportions instead of
 * being cropped square to fit a box it was never drawn for. That is also why
 * there is no object-fit here: cover would crop the logo and contain would
 * letterbox the portrait, and only one of those can be right at a time.
 *
 * The 20px radius theme.json gives every featured image is meant for a hero
 * and would round a 56px mark most of the way to a circle, so the chip radius
 * applies instead.
 */
.ca-cite__media {
	flex: none;
	margin: 0;
	line-height: 0;
}

.ca-cite__media img {
	width: auto;
	height: auto;
	max-height: var(--ca-cite-media, 56px);
	max-width: 150px;
	border-radius: var(--wp--custom--radius--chip);
}

.is-style-ink .ca-cite .ca-cite__lines > *,
.is-style-teal .ca-cite .ca-cite__lines > * {
	color: color-mix(in srgb, var(--wp--preset--color--base) 74%, transparent);
}

.is-style-ink .ca-cite .ca-cite__lines > :first-child,
.is-style-teal .ca-cite .ca-cite__lines > :first-child {
	color: var(--wp--preset--color--base);
}

/*
 * The ledger.
 *
 * Quotes stack as rows, each one an attribution rail with the quote in the
 * column beside it. It replaced a two column grid of bare paragraphs that
 * could hold exactly two quotes, gave the attribution no column of its own,
 * and left both quotes stopping well short of their column because the 30ch
 * measure had been tuned for a single display sized quote.
 *
 * The rail is a fixed width rather than a percentage so every quote in the
 * band starts on the same edge. At a percentage the rails landed within a few
 * pixels of each other, which reads as a mistake rather than as a variation.
 *
 * The quote comes first in the markup and is placed into the second column, so
 * a screen reader and a phone both get the words before the name while the
 * desktop composition still leads with who said them.
 */
.ca-quote-ledger {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: var(--wp--preset--spacing--80);
	--ca-cite-media: 64px;
}

.ca-quote-ledger > li {
	margin-block: 0;
	display: grid;
	grid-template-columns: 260px minmax(0, 1fr);
	column-gap: var(--wp--preset--spacing--70);
	align-items: start;
}

.ca-quote-ledger .ca-quote {
	grid-column: 2;
	grid-row: 1;
	--ca-quote-measure: 46ch;
}

.ca-quote-ledger .ca-cite {
	grid-column: 1;
	grid-row: 1;
	/* Optical only: drops the rail onto the quote's cap height rather than its
	   line box, which sits a few pixels higher. */
	margin-block-start: 5px;
}

@media (max-width: 781px) {
	.ca-quote-ledger {
		gap: var(--wp--preset--spacing--70);
	}

	.ca-quote-ledger > li {
		grid-template-columns: minmax(0, 1fr);
	}

	.ca-quote-ledger .ca-quote,
	.ca-quote-ledger .ca-cite {
		grid-column: 1;
		grid-row: auto;
	}

	.ca-quote-ledger .ca-cite {
		margin-block-start: var(--wp--preset--spacing--40);
	}
}

/*
 * The feature quote.
 *
 * One quote at display scale with the attribution in the column beside it.
 * This band used to spend 38% of its width on the testimonial's featured
 * image, and on every record that image was a photograph of food, so a stock
 * plate of eggs sat next to a general manager's words and vouched for nothing.
 * The attribution earns the column instead, and the image, where there is one,
 * becomes a mark beside the name.
 */
.ca-quote-feature {
	--ca-quote-measure: 26ch;
	--ca-cite-media: 76px;
}

.ca-quote-feature .ca-cite {
	margin-block-start: 0;
}

@media (max-width: 781px) {
	.ca-quote-feature .ca-cite {
		margin-block-start: var(--wp--preset--spacing--40);
	}
}

/*
 * Quote with portrait.
 *
 * The only layout that leads with the image, so the only one that has to
 * survive the image being absent. When chefashish_strip_empty_optional()
 * removes the media column the body column is left as the only child, and the
 * width it carries is an inline flex-basis written by the editor, which a
 * stylesheet cannot reach without !important. That is what the declaration
 * below is for, and it is the whole reason the rule exists.
 */
.ca-quote-portrait > .wp-block-column:only-child {
	flex-basis: 100% !important;
}

.ca-quote-portrait__body {
	--ca-quote-measure: 34ch;
}

/*
 * The carousel.
 *
 * The fourth layout and now the usual one. The ledger it replaced could only
 * ever show its quotes as a vertical stack, so a page with six of them spent
 * six screens saying the same thing, and a page with one looked like a
 * mistake. Reading two or three at once is what makes a wall of proof read as
 * a wall rather than as a queue.
 *
 * It is a scroll container first and a carousel second. The track is a plain
 * scroll-snap strip, so with no JavaScript at all it is still swipeable on a
 * phone and scrollable with a trackpad, and the controls and the autoplay are
 * added on top by assets/js/carousel.js. Nothing below assumes they exist.
 *
 * Slides are separated by a rule and whitespace rather than boxed. A card
 * would need its own ground, and this band takes whichever tone the page gives
 * it: bone on Services, mist on Projects, teal on Home. A tile light enough to
 * sit on bone would be invisible, and one dark enough to sit on teal would
 * fight the section it is in.
 */
.ca-quote-carousel {
	--ca-carousel-gap: var(--wp--preset--spacing--60);
	--ca-carousel-slides: 1.12;
}

@media (min-width: 600px) {
	.ca-quote-carousel {
		--ca-carousel-slides: 2;
	}
}

@media (min-width: 1024px) {
	.ca-quote-carousel {
		--ca-carousel-slides: 3;
		--ca-carousel-gap: var(--wp--preset--spacing--70);
	}
}

/*
 * The fractional slide count on a phone is deliberate. A strip that ends flush
 * with the screen edge looks like a finished row, and nobody swipes a finished
 * row. Letting the next quote show by a tenth is the whole affordance.
 */
.ca-quote-carousel__track {
	display: flex;
	gap: var(--ca-carousel-gap);
	margin: 0;
	padding: 0;
	list-style: none;
	overflow-x: auto;
	overscroll-behavior-x: contain;
	scroll-snap-type: x mandatory;
	scroll-behavior: smooth;
	scrollbar-width: none;
}

.ca-quote-carousel__track::-webkit-scrollbar {
	display: none;
}

.ca-quote-carousel__track > li {
	flex: 0 0 calc(
		(100% - (var(--ca-carousel-slides) - 1) * var(--ca-carousel-gap)) /
		var(--ca-carousel-slides)
	);
	margin: 0;
	padding-block-start: var(--wp--preset--spacing--40);
	border-block-start: var(--wp--custom--rule);
	scroll-snap-align: start;
}

.is-style-teal .ca-quote-carousel__track > li,
.is-style-ink .ca-quote-carousel__track > li {
	border-block-start-color: var(--wp--custom--tint--on-dark-line);
}

/*
 * The slide owns the measure, so the quote fills the column it was given
 * instead of stopping short of it at the 30ch the single feature quote wants.
 *
 * The hanging open quote is switched off here for a mechanical reason: the
 * mark hangs into the left margin, and the leftmost slide's margin is the edge
 * of a scroll container, which clips it.
 */
.ca-quote-carousel .ca-quote {
	--ca-quote-measure: none;
	text-indent: 0;
}

.ca-quote-carousel .ca-cite {
	margin-block-start: var(--wp--preset--spacing--40);
}

/*
 * Controls.
 *
 * Written by JavaScript, and absent until it runs, so the band never shows a
 * dead control. They are also removed when every slide is already visible:
 * paging controls on a carousel that cannot page are furniture.
 */
.ca-quote-carousel__controls {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--40);
	margin-block-start: var(--wp--preset--spacing--50);
}

.ca-quote-carousel__dots {
	display: flex;
	align-items: center;
	gap: 10px;
}

.ca-quote-carousel__dot {
	width: 30px;
	height: 2px;
	padding: 12px 0;
	border: none;
	background: none;
	cursor: pointer;
	box-sizing: content-box;
}

.ca-quote-carousel__dot::before {
	content: "";
	display: block;
	height: 2px;
	background: var(--wp--preset--color--line);
	transition: background-color var(--wp--custom--transition--tap);
}

.ca-quote-carousel__dot[aria-current="true"]::before {
	background: var(--wp--preset--color--contrast);
}

.is-style-teal .ca-quote-carousel__dot::before,
.is-style-ink .ca-quote-carousel__dot::before {
	background: var(--wp--custom--tint--on-dark-line);
}

.is-style-teal .ca-quote-carousel__dot[aria-current="true"]::before,
.is-style-ink .ca-quote-carousel__dot[aria-current="true"]::before {
	background: var(--wp--preset--color--base);
}

.ca-quote-carousel__arrows {
	display: flex;
	gap: 10px;
}

/*
 * Pill and hairline, the same shape as the secondary button, because that is
 * the one outline treatment this design has and a carousel arrow is not a
 * reason to invent a second.
 */
.ca-quote-carousel__arrow {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	padding: 0;
	border: 1px solid var(--wp--preset--color--line);
	border-radius: var(--wp--custom--radius--pill);
	background: none;
	color: var(--wp--preset--color--contrast);
	cursor: pointer;
	transition:
		background-color var(--wp--custom--transition--tap),
		border-color var(--wp--custom--transition--tap),
		color var(--wp--custom--transition--tap);
}

.ca-quote-carousel__arrow::before {
	content: "";
	width: 20px;
	height: 20px;
	background-color: currentColor;
	-webkit-mask: var(--ca-ico) center / contain no-repeat;
	mask: var(--ca-ico) center / contain no-repeat;
}

.ca-quote-carousel__arrow:hover {
	border-color: var(--wp--preset--color--contrast);
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
}

.ca-quote-carousel__arrow[disabled] {
	opacity: 0.35;
	cursor: default;
}

.ca-quote-carousel__arrow[disabled]:hover {
	border-color: var(--wp--preset--color--line);
	background: none;
	color: var(--wp--preset--color--contrast);
}

.is-style-teal .ca-quote-carousel__arrow,
.is-style-ink .ca-quote-carousel__arrow {
	border-color: var(--wp--custom--tint--on-dark-line);
	color: var(--wp--preset--color--base);
}

.is-style-teal .ca-quote-carousel__arrow:hover,
.is-style-ink .ca-quote-carousel__arrow:hover {
	border-color: var(--wp--preset--color--base);
	background: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
}

.is-style-teal .ca-quote-carousel__arrow[disabled]:hover,
.is-style-ink .ca-quote-carousel__arrow[disabled]:hover {
	border-color: var(--wp--custom--tint--on-dark-line);
	background: none;
	color: var(--wp--preset--color--base);
}

/*
 * The motion dial on this design is 0.15, which is a budget for a tap
 * response, not for a slideshow. Autoplay is switched off outright under a
 * reduced motion preference, in the script as well as here, and the snap jump
 * stops animating.
 */
@media (prefers-reduced-motion: reduce) {
	.ca-quote-carousel__track {
		scroll-behavior: auto;
	}
}

.ca-note {
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--muted);
}

.is-style-ink .ca-note,
.is-style-teal .ca-note {
	color: inherit;
	opacity: 0.8;
}

.ca-tile {
	padding: var(--wp--preset--spacing--50);
	border-radius: var(--wp--custom--radius--card);
}

.ca-inset {
	padding: var(--wp--preset--spacing--60);
	border-radius: var(--wp--custom--radius--media);
}

.ca-chips {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
}

.ca-chips > * {
	padding: 7px 14px;
	border-radius: var(--wp--custom--radius--chip);
	background: var(--wp--custom--tint--chip);
	font-size: var(--wp--preset--font-size--x-small);
	margin: 0;
}

.is-style-ink .ca-chips > *,
.is-style-teal .ca-chips > * {
	background: var(--wp--custom--tint--on-dark-fill);
}

.ca-sticky {
	position: sticky;
	top: var(--wp--preset--spacing--60);
}

/*
 * Fact list on the single templates. Label and value alternate in the markup
 * and the grid pairs them up, so adding a field is two paragraphs and no CSS.
 * Bare values were ambiguous: "Lucknow, Uttar Pradesh" above "Eleven weeks"
 * tells a reader nothing about which is which.
 */
.ca-meta {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: 10px var(--wp--preset--spacing--40);
	align-items: baseline;
}

.ca-meta__label {
	margin: 0;
	font-size: var(--wp--preset--font-size--x-small);
	color: var(--wp--preset--color--muted);
}

.ca-meta__value {
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--contrast);
}

.is-style-ink .ca-meta__label,
.is-style-teal .ca-meta__label {
	color: color-mix(in srgb, var(--wp--preset--color--base) 70%, transparent);
}

.is-style-ink .ca-meta__value,
.is-style-teal .ca-meta__value {
	color: var(--wp--preset--color--base);
}

/* ---------------------------------------------------------------------------
   Page opener

   Every template starts with the same block: a title on one side, and a short
   lede or a set of facts on the other. It was written eight separate times as
   a Columns block with verticalAlignment "bottom", which only holds while the
   two sides are a similar height. The moment the right side is taller,
   "bottom" pushes the title down and leaves a screen of dead space above it.
   That is what the service pages were doing: a title with a 200px void over
   it, because the summary and the audience row together outran a single line
   of heading.

   The openers are top aligned now, which cannot strand either side whatever
   the content does. The aside carries a small optical offset so a lede set at
   body size sits against the title's baseline rather than its cap height.
   ------------------------------------------------------------------------- */

.ca-pagehead .wp-block-column > *:first-child {
	margin-block-start: 0;
}

@media (min-width: 782px) {
	.ca-pagehead__aside {
		padding-block-start: 0.5em;
	}
}

/* ---------------------------------------------------------------------------
   Split sections

   The recurring composition across the site: a short heading rail on one side,
   a long list or grid on the other. Left to itself the rail sits at the top of
   a column several times its own height and the section reads as half empty,
   which is what "Three ways owners use me" and "Four phases, eight steps" were
   doing on the homepage.

   The rail travels with the reader instead. It is a scroll affordance rather
   than motion, so it costs nothing against the motion dial, and it is switched
   off below the columns breakpoint where the two sides stack and there is
   nothing to travel alongside.
   ------------------------------------------------------------------------- */

@media (min-width: 782px) {
	.ca-rail {
		position: sticky;
		top: var(--wp--preset--spacing--80);
	}

	/*
	 * Two class names deep on purpose. Core ships
	 * .is-vertically-aligned-center { align-self: center } on the column, which
	 * ties the rule on specificity and then wins or loses on stylesheet order,
	 * and block library CSS is printed inline in the head where the theme cannot
	 * reliably out-order it. Winning on specificity is the only stable answer.
	 */
	.wp-block-column.ca-rail,
	.wp-block-column > .ca-rail,
	.ca-rail {
		align-self: flex-start;
	}
}

/*
 * align-self matters when the rail IS the column rather than a group inside
 * one. Columns stretch to the row height by default, and a sticky element that
 * already fills its scroll container has nowhere to travel, so the rule would
 * silently do nothing. Shrinking the column to its content gives it the room.
 * On a block-level child of a stretched column the declaration is inert, so a
 * single class covers both shapes.
 */

/*
 * A sticky child only travels if its containing column is allowed to be taller
 * than the child itself. Core stretches columns to the row height by default,
 * which satisfies that, but a column carrying an explicit vertical alignment
 * shrinks to its own content and the rail then has nowhere to move.
 */
.wp-block-columns:has(> .wp-block-column > .ca-rail) {
	align-items: stretch;
}

/* ---------------------------------------------------------------------------
   Article body rhythm

   Headings inside running prose were separated from the paragraph above by the
   global 24px blockGap, the same distance as one paragraph to the next, so a
   section heading read as another paragraph rather than as a break. Prose is
   the one place the gap has to be asymmetric: generous above a heading, tight
   below it, so the heading binds to the text it introduces rather than
   floating between two blocks.
   ------------------------------------------------------------------------- */

.ca-prose :where(h2, h3, h4) {
	margin-block-start: var(--wp--preset--spacing--60);
}

.ca-prose :where(h2, h3, h4):first-child {
	margin-block-start: 0;
}

.ca-prose :where(h2, h3, h4) + * {
	margin-block-start: var(--wp--preset--spacing--30);
}

/* ---------------------------------------------------------------------------
   Footer
   ------------------------------------------------------------------------- */

/*
 * The design allows at most two uppercase letterspaced labels per page, and
 * the content eyebrows have first claim on them. A footer that ships on every
 * page would otherwise spend the whole budget on its own furniture, so its
 * column headings take a quieter, sentence case treatment.
 */
.wp-block-group > footer h6,
footer.wp-block-group h6 {
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	letter-spacing: 0;
	text-transform: none;
	color: var(--wp--preset--color--contrast);
}

/*
 * The footer reuses the primary menu but shows only its top level. A footer is
 * for orientation; reproducing every service page a second time buries the
 * handful of links someone actually scrolled down for.
 */
.ca-footer-nav .wp-block-navigation__submenu-container {
	display: none;
}

/* Six links stacked in one column made the footer twice as tall as it needed
   to be. Two short columns read faster and keep the whole footer on one
   screen. */
.ca-footer-nav .wp-block-navigation__container {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 8px var(--wp--preset--spacing--40);
}

/*
 * Two columns on a phone as well.
 *
 * This used to drop to one below 600px, which is the width where the footer's
 * three columns have already stacked and the nav therefore has the whole
 * screen to itself. Nine links down a single column made the footer taller
 * than the viewport on its own. The labels are short enough to sit in half a
 * phone width, so the pair of columns holds all the way down and the column
 * gap tightens instead.
 */
@media (max-width: 600px) {
	.ca-footer-nav .wp-block-navigation__container {
		gap: 10px var(--wp--preset--spacing--30);
	}
}

/* The one-line description under the site name. Kept short enough to read as a
   caption rather than a paragraph. */
.ca-footer__blurb {
	max-width: 34ch;
}

/* An icon line that wraps sits its glyph on the first line, not centred against
   the whole block. */
.ca-footer__links .ca-ico {
	align-items: start;
}

.ca-footer__links .ca-ico::before {
	margin-block-start: 0.15em;
}

.ca-footer .wp-block-site-title a {
	text-decoration: none;
}

/*
 * The profile download.
 *
 * Cut from the same cloth as the floating WhatsApp affordance rather than the
 * default button, for two reasons. The default button is solid accent, and the
 * accent is rationed here: spending it on a footer utility would have it
 * competing with the primary call to action one band above. Ink now, accent
 * only on hover, means the two persistent actions on the site read as one
 * family and the accent still lands where it is meant to.
 *
 * The glyph comes from the icon library through --ca-ico, set by the
 * ca-ico--download class on the block wrapper and inherited by the link. It is
 * masked in currentColor, so it follows the label through every state instead
 * of needing a second copy for the hover colour.
 */
.wp-block-button.ca-profile-download > .wp-block-button__link {
	display: inline-flex;
	align-items: center;
	gap: 9px;
	background-color: var(--wp--preset--color--contrast);
	border-color: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
}

.wp-block-button.ca-profile-download > .wp-block-button__link::before {
	content: "";
	flex: none;
	width: 1.15em;
	height: 1.15em;
	background-color: currentColor;
	-webkit-mask: var(--ca-ico) center / contain no-repeat;
	mask: var(--ca-ico) center / contain no-repeat;
}

.wp-block-button.ca-profile-download > .wp-block-button__link:hover,
.wp-block-button.ca-profile-download > .wp-block-button__link:focus-visible {
	background-color: var(--wp--preset--color--accent);
	border-color: var(--wp--preset--color--accent);
	color: var(--wp--preset--color--surface);
}

/*
 * Format and weight, so the reader knows what they are committing to before
 * they tap. Bound to the file rather than typed, so replacing the PDF cannot
 * leave a stale figure behind.
 */
.ca-profile-download__meta {
	margin-block-start: var(--wp--preset--spacing--10);
}

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

/*
 * The header no longer carries a separate "Work with me" pill. It pointed at
 * /contact/, which the Contact menu item already does, so the bar offered the
 * same destination twice under two names. The emphasis moved onto the menu item
 * itself; see .ca-nav-cta in blocks/navigation.css.
 */

/* The title is set at 25px, which wraps inside a 390px bar. */
@media (max-width: 480px) {
	.wp-block-site-title {
		font-size: var(--wp--preset--font-size--large);
	}
}

/* ---------------------------------------------------------------------------
   Accessibility furniture
   ------------------------------------------------------------------------- */

.screen-reader-text {
	border: 0;
	clip-path: inset(50%);
	height: 1px;
	margin: -1px;
	overflow: hidden;
	padding: 0;
	position: absolute;
	width: 1px;
	word-wrap: normal !important;
}

.skip-link:focus {
	clip-path: none;
	height: auto;
	width: auto;
	inset-block-start: 12px;
	inset-inline-start: 12px;
	z-index: 999999;
	padding: 12px 20px;
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
	border-radius: var(--wp--custom--radius--pill);
}

/* ---------------------------------------------------------------------------
   Persistent WhatsApp affordance
   ------------------------------------------------------------------------- */

.ca-whatsapp {
	position: fixed;
	inset-block-end: 20px;
	inset-inline-end: 20px;
	z-index: 900;
	display: inline-flex;
	align-items: center;
	gap: 9px;
	padding: 12px 20px;
	border-radius: var(--wp--custom--radius--pill);
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 500;
	text-decoration: none;
	transition:
		background-color var(--wp--custom--transition--tap),
		transform var(--wp--custom--transition--press);
}

.ca-whatsapp:hover,
.ca-whatsapp:focus-visible {
	background: var(--wp--preset--color--accent);
	color: var(--wp--preset--color--surface);
}

.ca-whatsapp:active {
	transform: translateY(1px);
}

@media (max-width: 600px) {
	.ca-whatsapp {
		padding: 14px;
	}

	.ca-whatsapp__label {
		display: none;
	}
}

@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}


/* ---------------------------------------------------------------------------
   Brand wall descriptors

   The wall used to be logos only, and the grid was built around that: five
   across, floor of three, on the reasoning that a square with no text in it
   survives being small. Each tile now carries a line of context under the
   logo, so that reasoning no longer holds and every step drops by one. Two is
   the phone floor rather than three, because the tile is now a card with a
   sentence in it.
   ------------------------------------------------------------------------- */

.ca-brandwall__desc {
	margin-block-start: var(--wp--preset--spacing--20);
	line-height: 1.4;
	text-wrap: pretty;
}


/* ---------------------------------------------------------------------------
   Resources

   A resource is a short piece of writing with a file at the end of it, and the
   page is built in that order. The cover sits beside the title rather than in
   a band of its own: at 20/9 across the full wide measure it was 530px of
   diagram before the reader reached a sentence, which is a lot of room to
   spend on a picture of a spreadsheet.

   The download lives under the article, where a reader arrives having read it.
   The jump link in the opener is for the reader who has already decided, and
   it carries the same ca-download class as its target so the pair disappears
   together on a resource with no file attached.
   ------------------------------------------------------------------------- */

.ca-jump {
	color: var(--wp--preset--color--accent);
}

.ca-jump a {
	color: inherit;
	text-decoration: none;
	padding-block-end: 2px;
	border-block-end: 1px solid color-mix(in srgb, currentColor 40%, transparent);
	transition: color var(--wp--custom--transition--tap);
}

.ca-jump:hover,
.ca-jump:focus-within {
	color: var(--wp--preset--color--contrast);
}

/*
 * The anchor target. Without a scroll margin the inset lands hard against the
 * top of the viewport and the reader cannot see that it is the end of an
 * article rather than the start of a page.
 */
.ca-getfile {
	scroll-margin-block-start: var(--wp--preset--spacing--60);
}

.ca-getfile > * {
	margin-block-start: 0;
}

/*
 * The listing, and the resource archive with it. Four items two up, and
 * deliberately not four cards: the cover carries the tile and the ground under
 * it stays the section's own. The previous version wrapped each item in a mist
 * tile inside a paper section, which is a box inside a box, and it left the
 * cover floating in a paper panel of its own.
 *
 * Two columns rather than three because there are four resources. app.css
 * already steps every grid to two at 900px and one at 600px, so a grid
 * authored at two needs no floor of its own.
 */
.ca-resource .wp-block-post-featured-image {
	margin-block: 0;
}

.ca-resource .wp-block-post-featured-image a {
	display: block;
	overflow: hidden;
	border-radius: var(--wp--custom--radius--media);
}

.ca-resource .wp-block-post-featured-image img {
	display: block;
	transition: transform 320ms ease;
}

/*
 * Without a card there is no edge to hover, so the cover takes the affordance.
 * Two per cent is under the motion budget and still reads as a response.
 */
.ca-resource:hover .wp-block-post-featured-image img {
	transform: scale(1.02);
}

.ca-resource .wp-block-post-title {
	text-wrap: balance;
}

.ca-resource .wp-block-post-title a {
	text-decoration: none;
}

/*
 * File weight on one side, the link on the other. The weight is bound to the
 * attachment rather than typed into a field, so replacing a PDF cannot leave a
 * stale figure behind. That binding is also what replaced the Format field:
 * one line of hand-maintained text that said the same thing the file already
 * knew.
 */
.ca-resource__foot {
	border-block-start: 1px solid var(--wp--preset--color--line);
	padding-block-start: var(--wp--preset--spacing--30);
}

.ca-resource__foot > p {
	margin-block: 0;
}

.ca-resource__foot .wp-block-read-more {
	text-decoration: none;
	color: var(--wp--preset--color--accent);
}

.ca-resource__foot .wp-block-read-more:hover,
.ca-resource__foot .wp-block-read-more:focus-visible {
	color: var(--wp--preset--color--contrast);
}

/* ---------------------------------------------------------------------------
   Infinite scroll

   Insights is eight pages of short pieces and nobody clicks through to page
   four. assets/js/infinite-posts.js replaces core's pagination with a button
   it also presses on the reader's behalf as the end of the list comes into
   view.

   The button is the real control and the observer is the convenience, which is
   what keeps this reachable from a keyboard. Core's pagination stays in the
   markup, hidden, and comes back if three fetches in a row fail.
   ------------------------------------------------------------------------- */

/*
 * Hiding core's pagination takes CSS as well as the attribute.
 *
 * The script sets `hidden` on the Query Pagination block once it has taken
 * over, which is correct and also removes it from the accessibility tree. On
 * screen it did nothing: `[hidden] { display: none }` is a user agent rule at
 * the bottom of the cascade, and core prints the block's own
 * `display: flex` as a single layout class inline in the head. The numbers
 * therefore sat under "That is everything." on a list that had already loaded
 * every post.
 *
 * Three classes deep wins on specificity, so the attribute is honoured. It has
 * to stay scoped to .ca-infinite: everywhere else on the site `hidden` is not
 * being set on this block and the rule would have nothing to do.
 */
.ca-infinite .wp-block-query-pagination[hidden] {
	display: none;
}

.ca-infinite__foot {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--wp--preset--spacing--30);
	margin-block-start: var(--wp--preset--spacing--80);
}

.ca-infinite__more {
	appearance: none;
	font-family: inherit;
	font-size: var(--wp--preset--font-size--small);
	line-height: 1;
	padding: 15px 30px;
	border: 1px solid var(--wp--preset--color--contrast);
	border-radius: var(--wp--custom--radius--pill);
	background: transparent;
	color: var(--wp--preset--color--contrast);
	cursor: pointer;
	transition:
		background-color var(--wp--custom--transition--tap),
		color var(--wp--custom--transition--tap);
}

.ca-infinite__more:hover,
.ca-infinite__more:focus-visible {
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
}

.ca-infinite__more[disabled] {
	opacity: 0.55;
	cursor: default;
}

/*
 * The status line is a live region, so it holds its height whether or not it
 * has anything to say. Letting it collapse would move the button under the
 * reader's cursor every time a page landed.
 */
.ca-infinite__status {
	margin: 0;
	min-height: 1.4em;
	font-size: var(--wp--preset--font-size--x-small);
	color: var(--wp--preset--color--muted);
	text-align: center;
}

/*
 * Appended posts fade rather than appear. 180ms is well inside the motion
 * budget and it does the one job that matters: saying that what just moved
 * under the reader's thumb is new, not a repaint. The global reduced motion
 * block above cuts it to nothing.
 */
.ca-infinite__new {
	animation: ca-fade-in 180ms ease-out both;
}

@keyframes ca-fade-in {
	from {
		opacity: 0;
	}

	to {
		opacity: 1;
	}
}
