/**
 * Modularis - component layer
 *
 * The hexagon design language, expressed as block style variations
 * (.is-style-*) so editors pick them from the UI rather than typing classes.
 *
 * Zero literal colors. Every value resolves to a token.
 */

/* =========================================================================
   EYEBROW - mono, tracked-out, hex bullet. The section label device.
   ========================================================================= */

.is-style-eyebrow {
	display: inline-flex;
	align-items: center;
	gap: 10px;
	margin-block-end: 18px;
	color: var(--primary);
	font-family: var(--mono);
	font-size: var(--wp--preset--font-size--eyebrow);
	letter-spacing: var(--wp--custom--tracking--eyebrow);
	text-transform: uppercase;
}

/* The hex bullet is generated, so editors never have to insert a marker. */
.is-style-eyebrow::before {
	content: "";
	flex: none;
	inline-size: 9px;
	block-size: 10px;
	background: currentcolor;
	clip-path: var(--hex);
}

.is-style-eyebrow.has-violet-light-color::before,
.is-style-eyebrow.has-muted-color::before {
	background: currentcolor;
}

/**
 * Centring an eyebrow needs more than text-align. The eyebrow is an inline-flex
 * box so that its hexagon sits on the baseline of the label, and text-align on
 * an element does nothing to the element's own box - it would take the PARENT
 * centring the line. Told to centre itself, it becomes a block-level flex row
 * and centres its own contents instead.
 */
.is-style-eyebrow.has-text-align-center {
	display: flex;
	justify-content: center;
}

/* =========================================================================
   LEAD - the oversized intro paragraph.
   ========================================================================= */

.is-style-lead {
	max-width: 60ch;
	color: var(--muted);
	font-size: var(--wp--preset--font-size--lead);
	line-height: var(--wp--custom--leading--lead);
}

/**
 * Core's constrained layout centres every child with `margin-left/right: auto
 * !important`. Anything capped narrower than its container therefore floats to
 * the middle - which indents a lead against the full-width heading above it.
 * Pinning the start margin lets the measure shorten from the end instead, so
 * the lead lines up with the heading. --column-inset (base.css) is what keeps
 * "the start" meaning the content column rather than the section padding, for a
 * lead dropped straight into a full-bleed section; inside a narrower box it
 * resolves to zero, which is why one value serves both cases.
 *
 * Centred text is the one case where those auto margins are wanted, so it keeps
 * them. `:not()` carries the specificity of its argument, which puts this rule
 * at two classes - above core's one - and settles it without relying on which
 * stylesheet is printed last.
 */
.is-style-lead:not(.has-text-align-center) {
	margin-inline-start: var(--column-inset) !important;
}

/**
 * The mirror image: a centred lead needs its box centred, not just its text.
 * Core's constrained layout would have done it with auto margins, but a lead
 * inside a flow group - a full-bleed band, say - never meets that rule, so a
 * 60ch measure would sit at the start of the line with its text centred inside
 * it, which reads as slightly-off-centre rather than centred.
 */
.is-style-lead.has-text-align-center {
	margin-inline: auto;
}

/* =========================================================================
   NOTCH CARDS - hex-derived corner chamfer. Notch size scales with the card.
   ========================================================================= */

.is-style-notch-card,
.is-style-notch-card-primary,
.is-style-notch-card-danger {
	--notch: var(--wp--custom--notch--lg);

	position: relative;
	border: 1px solid var(--border-soft);
	background: var(--surface);
	padding: 34px 26px 30px;
	clip-path: polygon(
		0 0,
		calc(100% - var(--notch)) 0,
		100% var(--notch),
		100% 100%,
		var(--notch) 100%,
		0 calc(100% - var(--notch))
	);
	transition:
		transform var(--wp--custom--effect--dur-base) ease,
		background var(--wp--custom--effect--dur-base) ease,
		border-color var(--wp--custom--effect--dur-base) ease;
}

.is-style-notch-card:hover,
.is-style-notch-card-primary:hover,
.is-style-notch-card-danger:hover {
	transform: translateY(-5px);
	border-color: var(--border);
	background: var(--surface-2);
}

.is-style-notch-card-primary {
	border-color: var(--border);
	background: var(--wp--preset--gradient--card-primary);
}

.is-style-notch-card-danger {
	border-color: var(--danger-dim);
	background: var(--wp--preset--gradient--card-danger);
}

/* Optional index marker for card grids that are a real sequence. */
.is-style-notch-card .card-index {
	position: absolute;
	inset-block-start: 20px;
	inset-inline-end: 22px;
	color: var(--faint);
	font-family: var(--mono);
	font-size: var(--wp--preset--font-size--eyebrow);
}

/* =========================================================================
   CASE CARD - core/quote as a notched testimonial panel, and CASE BARE,
   the same testimonial without the panel.

   Two styles, one set of innards. The attribution row, its hexagon mark, the
   headshot and the floor-pinning all belong to the testimonial; only the
   border, surface, padding and chamfer belong to the card. :is() keeps that
   split readable and costs nothing - it takes the specificity of its most
   specific argument, one class, so every selector here weighs exactly what it
   did when there was only one style.

   Registered as a block style on core/quote (see functions.php), so a
   testimonial is a Quote block with a style picked from a menu, not a stack of
   divs an editor has to assemble and can take apart. Quote typography already
   comes from theme.json; this is only the shell and the attribution.

   The selectors are compounded on purpose. theme.json's core/quote rules -
   including the border-left reset - land in the document as `:root :where(...)`
   and, with separate block assets enabled, can be printed after this file. Two
   classes beat that whatever the order.
   ========================================================================= */

/**
 * The column is shared by both styles: it is what lets the attribution sit on
 * the floor via `margin-block-start: auto`, so two quotes side by side line
 * their bylines up whether or not they are wearing a card.
 */
.wp-block-quote:is( .is-style-case-card, .is-style-case-bare ) {
	display: flex;
	flex-direction: column;
}

/* Everything below here is the card itself - the part .is-style-case-bare
   deliberately does without. */
.wp-block-quote.is-style-case-card {
	/* Same hex-derived chamfer as the notch cards, one step up the scale. */
	--notch: var(--wp--custom--notch--xl);

	border: 1px solid var(--border);
	background: var(--surface);
	padding: clamp(32px, 4vw, 52px);
	clip-path: polygon(
		0 0,
		calc(100% - var(--notch)) 0,
		100% var(--notch),
		100% 100%,
		var(--notch) 100%,
		0 calc(100% - var(--notch))
	);
}

/* Attribution sits on the floor of the card, so cards in a row align. */
/**
 * Two case cards side by side on a wide screen, stacked on a narrow one.
 *
 * A Row with wrapping enabled breaks the line when its items' base size stops
 * fitting - and a flex item's base size is its own content width unless
 * something else is given. The editor's "Fill" only emits `flex-grow: 1`, so
 * two quotes full of prose each ask for the width of their longest possible
 * line, never fit alongside each other, and wrap at every screen size. That is
 * why enabling wrap looks like it stacks them permanently.
 *
 * A basis moves the decision from the length of the text to the width of the
 * screen: two cards share a line until the row is narrower than about 780px
 * (2 x 380px plus the 22px default row gap), then stack. Raise or lower the
 * 380px to move that threshold.
 *
 * min() keeps the basis from ever exceeding the row itself, so a card cannot
 * push out of a container narrower than the basis.
 */
.is-layout-flex > .wp-block-quote:is( .is-style-case-card, .is-style-case-bare ) {
	flex: 1 1 min( 100%, 380px );
}

/**
 * Core centres flex-row children - `.is-layout-flex{align-items:center}`, not
 * wrapped in :where(), so it takes a real class to beat - which leaves a short
 * card floating beside a tall one and defeats the `margin-block-start: auto`
 * below that pins the attribution to the card floor. Cards in a row are the one
 * case that wants equal heights.
 */
.is-layout-flex:has( > .wp-block-quote.is-style-case-card, > .wp-block-quote.is-style-case-bare ) {
	align-items: stretch;
}

.wp-block-quote:is( .is-style-case-card, .is-style-case-bare ) cite {
	display: flex;
	gap: 15px;
	align-items: center;
	margin-block-start: auto;
	padding-block-start: 30px;
	color: var(--text);
	font-family: var(--body);
	font-size: var(--wp--preset--font-size--sm);
	font-style: normal;
	font-weight: 600;
}

/**
 * The prototype put the speaker's initials in this hexagon. Initials cannot be
 * derived from the citation in CSS, and hard-coding them would put content in a
 * stylesheet - so the mark is the hexagon in the face gradient. The hex-face-*
 * tokens were extracted from the prototype for exactly this element.
 */
.wp-block-quote:is( .is-style-case-card, .is-style-case-bare ) cite::before {
	content: "";
	flex: none;
	inline-size: 30px;
	block-size: 33px;
	background: linear-gradient(
		150deg,
		var(--wp--custom--color--hex-face-top),
		var(--wp--custom--color--hex-face-bottom)
	);
	clip-path: var(--hex);
}

/**
 * HEADSHOTS
 *
 * The hexagon above is a stand-in. There are two ways to put a real face there,
 * and both are supported so nobody has to rebuild a card to use the other.
 *
 * 1. An inline image inside the citation itself. The cite is already a flex row,
 *    so the image becomes its first item and the stand-in steps aside. The width
 *    needs !important because the editor's inline-image format writes a pixel
 *    width as an inline style, and an inline style is the one thing specificity
 *    cannot outrank - without it, a 150px portrait would stretch the hexagon
 *    into a lozenge.
 *
 * 2. An Image block inside the quote, just above the attribution. It takes over
 *    pinning the byline to the card floor, and the cite tucks under it.
 */
.wp-block-quote:is( .is-style-case-card, .is-style-case-bare ) cite img {
	flex: none;
	inline-size: 34px !important;
	block-size: 37px;
	object-fit: cover;
	clip-path: var(--hex);
}

/* With a real face in the row, the stand-in would read as a second avatar. */
.wp-block-quote:is( .is-style-case-card, .is-style-case-bare ) cite:has(img)::before,
.wp-block-quote:is( .is-style-case-card, .is-style-case-bare ) > .wp-block-image + cite::before {
	display: none;
}

.wp-block-quote:is( .is-style-case-card, .is-style-case-bare ) > .wp-block-image {
	margin-block: auto 0;
	padding-block-start: 30px;
}

.wp-block-quote:is( .is-style-case-card, .is-style-case-bare ) > .wp-block-image img {
	inline-size: 46px;
	block-size: 50px;
	object-fit: cover;
	clip-path: var(--hex);
}

/* The image has taken the floor pinning, so the cite follows it closely. */
.wp-block-quote:is( .is-style-case-card, .is-style-case-bare ) > .wp-block-image + cite {
	margin-block-start: 0;
	padding-block-start: 12px;
}

/**
 * Bare quotes carry a portrait at twice the card's size, by both routes.
 *
 * The card sizes its headshot to sit politely inside a bordered panel; with the
 * panel gone the attribution is the only furniture the quote has, so the face
 * can carry it. 34x37 becomes 68x74 inline, 46x50 becomes 92x100 as a block.
 *
 * These sit immediately after the shared rules and match them on specificity,
 * so order is what settles it - keep them below.
 */
.wp-block-quote.is-style-case-bare cite img {
	inline-size: 68px !important;
	block-size: 74px;
}

.wp-block-quote.is-style-case-bare > .wp-block-image img {
	inline-size: 92px;
	block-size: 100px;
}

/* =========================================================================
   VALUE SPINE - a list turned into an infographic: copy alternating either
   side of a honeycomb chain running down the middle.

   Flow layout, not grid, on purpose. Alternating columns in a grid needs an
   explicit grid-row per item, because auto-placement puts the first two items
   in the SAME row - one per column - rather than staggering them. Auto margins
   on ordinary blocks stagger for free and work for any number of items.

   Each hexagon is two absolutely positioned layers on its own item: ::before is
   the outline, ::after the fill, inset by the stroke width and painted on top.
   A single element cannot do it - clip-path cuts through borders and box
   shadows, so the "outline" has to be a larger shape showing from underneath.
   The dot rides along in the fill's background, which saves a third layer.
   ========================================================================= */

.is-style-value-spine {
	/* Width of the middle column, and therefore of each hexagon. */
	--spine: clamp(76px, 9vw, 108px);
	--spine-gap: clamp(20px, 3.5vw, 52px);
	--stroke: 1.5px;
	--hex-h: calc(var(--spine) * 1.08);

	/**
	 * 0.75 is the vertical period of a honeycomb: stack pointy-top hexagons
	 * three-quarters of their own height apart and their sloped edges meet, which
	 * is what makes the column read as interlocking pieces rather than a row of
	 * separate badges. Raise it towards 1 to pull them apart, lower it to overlap.
	 */
	--nest: 0.75;
	--hex-frame: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);

	margin: 0;
	padding: 0;
	list-style: none;
}

/**
 * Each item is exactly one honeycomb row tall, and its copy is centred in that
 * row rather than setting its height. That is the whole trick: the chain's
 * rhythm comes from the hexagon, so the pieces stay locked together however
 * long or short the sentences are.
 *
 * Copy longer than a row overflows it, by design - and harmlessly, because the
 * item above and below are on the opposite side of the spine. The nearest
 * same-side neighbour is two rows away.
 *
 * Grid rather than flex for the centring: a flex item sized to its content
 * would sit at the start of the line and take text-align with it, so a
 * one-line value would end up on the wrong edge. A grid item stretches.
 */
.is-style-value-spine > li {
	position: relative;
	display: grid;
	align-content: center;
	block-size: calc(var(--hex-h) * var(--nest));
	max-inline-size: calc(50% - var(--spine) / 2 - var(--spine-gap));
	color: var(--text);

	/**
	 * Fluid rather than the md preset. With the rows now fixed to the honeycomb
	 * period, the copy has a height budget - roughly two rows before it reaches
	 * the next value on its own side - and that budget shrinks with the column.
	 * Sizing the type to the viewport keeps long values inside it instead of
	 * letting them collide somewhere around 1200px.
	 */
	font-size: clamp(15px, 1.2vw, 16px);
	line-height: var(--wp--custom--leading--lead);
}

/**
 * The sequence starts on the right and alternates, as the reference does. Each
 * item is flush against the spine - the left-hand ones end-aligned, the
 * right-hand ones start-aligned - so the ragged edges face outwards and the
 * chain reads as the axis both columns hang off.
 */
.is-style-value-spine > li:nth-child(odd) {
	margin-inline-start: auto;
	text-align: start;
}

.is-style-value-spine > li:nth-child(even) {
	margin-inline-end: auto;
	text-align: end;
}

/**
 * The lead-in is the value's name, so it is set as one: display face, heavier,
 * and a fifth again the size of the sentence it opens. It stays inline rather
 * than taking its own line - the copy reading straight on from the name is what
 * keeps each node compact enough for the chain's rhythm - but at this weight it
 * is the first thing the eye lands on in every block.
 *
 * Sized in em so it tracks the item's own fluid type instead of drifting away
 * from it at narrow widths.
 */
.is-style-value-spine > li strong {
	color: var(--primary);
	font-family: var(--display);
	font-size: 1.2em;
	font-weight: 700;
	letter-spacing: var(--wp--custom--tracking--tight);
	line-height: 1.2;
}

.is-style-value-spine > li::before,
.is-style-value-spine > li::after {
	content: "";
	position: absolute;
	inset-block: 0;
	margin-block: auto;
	inline-size: var(--spine);
	block-size: var(--hex-h);
	clip-path: var(--hex-frame);
	pointer-events: none;
}

/**
 * Where the chain sits. The hexagon fills the middle column, then shifts a
 * quarter of its own width AWAY from the item it belongs to - which is what
 * makes consecutive hexagons zig-zag past each other into a honeycomb instead
 * of stacking in a straight line, and it is the direction the reference leans.
 */
.is-style-value-spine > li:nth-child(odd)::before,
.is-style-value-spine > li:nth-child(odd)::after {
	inset-inline-end: calc(100% + var(--spine-gap) + var(--spine) * 0.25);
}

.is-style-value-spine > li:nth-child(even)::before,
.is-style-value-spine > li:nth-child(even)::after {
	inset-inline-start: calc(100% + var(--spine-gap) + var(--spine) * 0.25);
}

.is-style-value-spine > li::before {
	background: var(--primary);
}

.is-style-value-spine > li::after {
	inline-size: calc(var(--spine) - var(--stroke) * 2);
	block-size: calc(var(--hex-h) - var(--stroke) * 2);
	background:
		radial-gradient(circle at 50% 50%, var(--primary) 0 4.5px, transparent 4.5px),
		var(--surface);
}

/* Keeping the smaller fill centred inside the outline, from either side. */
.is-style-value-spine > li:nth-child(odd)::after {
	margin-inline-end: var(--stroke);
}

.is-style-value-spine > li:nth-child(even)::after {
	margin-inline-start: var(--stroke);
}

/**
 * Narrow screens. Two columns of prose either side of a chain needs width the
 * page does not have - and below about 1180px the measure gets short enough
 * that a long value runs past the item two rows above it - so it becomes a
 * single left-aligned list and the hexagon shrinks back to a bullet: the same
 * shape doing the same job at the size that fits.
 *
 * `:nth-child(n)` matches every item, which is how these rules reach the
 * specificity of the ones above without !important.
 */
@media (max-width: 1180px) {
	.is-style-value-spine > li:nth-child(n) {
		display: block;
		block-size: auto;
		max-inline-size: none;
		margin-inline: 0;
		padding-inline-start: 30px;
		text-align: start;
	}

	.is-style-value-spine > li:nth-child(n) + li {
		margin-block-start: 22px;
	}

	.is-style-value-spine > li:nth-child(n)::before {
		inset-block: 0.5em auto;
		inset-inline: 0 auto;
		margin-block: 0;
		inline-size: 13px;
		block-size: 14px;
	}

	.is-style-value-spine > li:nth-child(n)::after {
		display: none;
	}
}

/**
 * Story cards: a logo, an outcome, then the customer's own words, over their
 * case-study photograph.
 *
 * The photograph is a real Image block rather than a CSS background, so it can
 * be swapped in the editor and carries empty alt (the logo beside it names the
 * company). It is pulled out of flow and pinned behind the text, desaturated
 * and held at low opacity: fourteen photographs at full strength would be
 * fourteen colour schemes fighting each other and the copy.
 *
 * isolation on the card keeps that z-index local, so the photo cannot slide
 * behind the section's own background.
 */
.story-card {
	isolation: isolate;
	overflow: hidden;
}

.story-card .story-bg {
	position: absolute;
	z-index: -1;
	inset: 0;
	margin: 0;
}

.story-card .story-bg img {
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
	filter: grayscale(1) contrast(0.92);
	opacity: 0.18;
}

/* The logo, white like the proof bar's, so fourteen brand palettes read as one
   page. Every file behind these is a transparent cut-out. */
.story-card .story-logo {
	margin-block: 0 20px;
}

.story-card .story-logo img {
	inline-size: auto;
	block-size: auto;
	max-inline-size: clamp(110px, 12vw, 150px);
	max-block-size: 34px;
	filter: brightness(0) invert(1);
	opacity: 0.88;
}

/**
 * The quote under the outcome. It wears .is-style-case-bare, so all that is
 * needed is the space between the two and a rule to separate them.
 */
.story-card .wp-block-quote {
	margin-block-start: 18px;
	padding-block-start: 18px;
	border-block-start: 1px solid var(--border-soft);
}

.story-card .wp-block-quote p {
	font-size: var(--wp--preset--font-size--base);
	line-height: var(--wp--custom--leading--snug);
}

.story-card .wp-block-quote cite {
	padding-block-start: 16px;
}

/* =========================================================================
   HEX LIST - hexagon bullets instead of discs.
   ========================================================================= */

.is-style-hexlist {
	display: flex;
	flex-direction: column;
	gap: 14px;
	padding-left: 0;
	list-style: none;
}

.is-style-hexlist > li {
	position: relative;
	padding-left: 26px;
	color: var(--text);
	font-size: var(--wp--preset--font-size--md);
}

.is-style-hexlist > li::before {
	content: "";
	position: absolute;
	inset-inline-start: 0;
	inset-block-start: 0.55em;
	inline-size: 11px;
	block-size: 12px;
	background: var(--primary);
	clip-path: var(--hex);
}

/* Two-column variant used by the AI band. */
.is-style-hexlist.is-columns-2 {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 16px 28px;
}

@media (max-width: 820px) {
	.is-style-hexlist.is-columns-2 {
		grid-template-columns: 1fr;
	}
}

/* =========================================================================
   BUTTONS - theme.json styles the primary; this is the ghost variant.
   ========================================================================= */

.wp-block-button.is-style-ghost .wp-block-button__link {
	background: transparent;
	color: var(--text);
	box-shadow: inset 0 0 0 1px var(--primary);
}

.wp-block-button.is-style-ghost .wp-block-button__link:hover {
	background: var(--wp--custom--color--primary-wash2);
	box-shadow:
		inset 0 0 0 1px var(--primary),
		var(--wp--custom--effect--glow-ghost);
}

/* Arrow nudge on hover, for buttons whose label ends in an arrow span. */
.wp-block-button__link .arrow {
	display: inline-block;
	transition: transform var(--wp--custom--effect--dur-fast) ease;
}

.wp-block-button__link:hover .arrow {
	transform: translateX(4px);
}

/* =========================================================================
   CARD CAROUSEL - a scroll-snap track with previous / next controls.

   The track is a working scroller with no JavaScript at all: touch, trackpad,
   shift+wheel and the keyboard all move it. assets/js/carousel.js only adds the
   buttons on top, so nothing here depends on the script having run.
   ========================================================================= */

/**
 * The carousel's head runs the width of the track, not the 720px measure
 * base.css gives every other section head.
 *
 * That measure exists to keep a paragraph readable. This head is one sentence
 * of display type with no lead under it, and at 720px it broke into three
 * lines that stopped a long way short of the cards' right edge - a ragged
 * column above a full row, with the empty third of the band drawing the eye.
 *
 * contentSize, not `none`: the track is a constrained child, so it is capped
 * there too. Anything wider would overhang the cards and undo the column
 * alignment --column-inset is doing on the other side.
 */
.wp-block-group.section-head.carousel-head {
	max-width: var(--wp--style--global--content-size);
}

/**
 * Compounded with .wp-block-group because core's flex layout ships
 * `.is-layout-flex{align-items:center}` at the same specificity and is not
 * :where()-wrapped - at a tie it would win on order and leave cards of unequal
 * height floating mid-row instead of squaring off.
 */
.wp-block-group.carousel-track {
	align-items: stretch;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	overscroll-behavior-inline: contain;

	/* The buttons are the affordance; the native bar would sit under the cards. */
	scrollbar-width: none;
}

.carousel-track::-webkit-scrollbar {
	display: none;
}

.carousel-track > * {
	flex: 0 0 clamp(280px, 32%, 420px);
	scroll-snap-align: start;
}

/**
 * Card anatomy: eyebrow, headline, logo, action. The logo takes the slack, so
 * the logo and the button below it sit on the floor of every card and line up
 * across the row however deep the headline wraps.
 *
 * The image and the buttons are selected structurally rather than by class -
 * a card owns one of each - which keeps two classes out of the pattern markup.
 */
.carousel-card {
	display: flex;
	flex-direction: column;
}

.carousel-card > .wp-block-image {
	margin-block-start: auto;
	margin-block-end: 0;
	padding-block-start: 28px;
}

.carousel-card > .wp-block-image img {
	inline-size: auto;
	block-size: 30px;
	max-inline-size: 100%;
	object-fit: contain;
}

/**
 * The eyebrow inside a carousel card is a label - which product, which sector -
 * not an accent. Five oranges in a row down the track competed with the
 * headlines and with the button that is the actual thing to click, so the cards
 * take it back to grey. Section eyebrows stay primary: there is only ever one
 * on screen, and there it is doing the accenting.
 *
 * The generated hex bullet needs no rule of its own; it is painted with
 * currentcolor and follows.
 */
.carousel-card .is-style-eyebrow {
	color: var(--muted);
}

/**
 * Orange rule across the top of each card. The colour asked for is exactly the
 * `primary` token, so it goes in as the variable - a literal would say the same
 * thing and fail the colour audit.
 *
 * Two classes deep because .is-style-notch-card sets the `border` shorthand at
 * one, and the :hover rule below it resets `border-color` on all four sides -
 * which would wipe this back to grey on hover, so the top colour is restated
 * there too.
 *
 * The chamfer clips the last few pixels of the line at the top right. That is
 * the notch doing its job, not the border failing: it follows the card's actual
 * silhouette rather than crossing it.
 */
.wp-block-group.carousel-card {
	border-block-start: 2px solid var(--primary);
}

.wp-block-group.carousel-card:hover {
	border-block-start-color: var(--primary);
}

/**
 * The card's ghost button wears a white ring rather than the primary one.
 *
 * What looks like a border is an inset box-shadow (see the ghost button style
 * above): a real border would sit outside the notch clip-path and get its
 * corner sliced off, so the ring is drawn inside the element instead. Recolour
 * it the same way, or the shadow underneath simply reappears.
 *
 * Scoped to the cards on purpose - the hero's ghost button is the page's second
 * call to action and keeps the primary ring. Three classes deep puts this above
 * the base rule whichever order the stylesheets land in.
 */
.carousel-card .wp-block-button.is-style-ghost .wp-block-button__link {
	box-shadow: inset 0 0 0 1px var(--text);
}

.carousel-card .wp-block-button.is-style-ghost .wp-block-button__link:hover {
	box-shadow:
		inset 0 0 0 1px var(--text),
		var(--wp--custom--effect--glow-ghost);
}

.carousel-card > .wp-block-buttons {
	margin-block-start: 20px;
}

/* The track is focusable so it can be scrolled without a mouse. */
.carousel-track:focus-visible {
	outline: 2px solid var(--primary);
	outline-offset: 4px;
}

.carousel-nav {
	display: flex;
	gap: 12px;
	margin-block-start: 32px;
}

.carousel-btn {
	--notch: var(--wp--custom--notch--sm);

	display: grid;
	place-items: center;
	inline-size: 48px;
	block-size: 48px;
	border: 0;
	background: transparent;
	box-shadow: inset 0 0 0 1px var(--primary);
	color: var(--text);
	font-size: var(--wp--preset--font-size--lg);
	line-height: 1;
	cursor: pointer;
	clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
	transition: background var(--wp--custom--effect--dur-fast) ease, color var(--wp--custom--effect--dur-fast) ease;
}

.carousel-btn:hover:not(:disabled) {
	background: var(--wp--custom--color--primary-wash2);
}

/**
 * clip-path crops an outline, so the usual focus ring cannot be seen on these.
 * Inverting the button is the indicator instead - a full contrast swap - with a
 * transparent outline left in place so forced-colors mode still draws its own.
 */
.carousel-btn:focus-visible {
	outline: 2px solid transparent;
	background: var(--primary);
	color: var(--bg);
}

.carousel-btn:disabled {
	box-shadow: inset 0 0 0 1px var(--border-soft);
	color: var(--faint);
	cursor: default;
}

/* =========================================================================
   HEX MASK - clip an image to the flat-top hexagon.
   ========================================================================= */

.wp-block-image.is-style-hex-mask img {
	aspect-ratio: var(--wp--custom--hex--ratio);
	object-fit: cover;
	clip-path: var(--hex);
}

/* =========================================================================
   HEX FRAME - the icon container with the draw-on-scroll stroke.

   No longer used by any pattern: card icons are now the .is-style-hexicon-*
   block styles in sections.css, which are CSS classes an editor can pick
   rather than SVG markup they can break. The tradeoff is that a background
   image cannot animate its stroke, so the draw-on-scroll effect is lost on
   cards; the card itself still fades up via .reveal.

   These rules are kept because a future custom block (e.g. a lifecycle-steps
   block, where the geometry really is not authorable) should use inline SVG
   and will want them. Delete if that never happens.
   ========================================================================= */

.hex-frame {
	position: relative;
	display: grid;
	flex: none;
	place-items: center;
	inline-size: 64px;
	block-size: 64px;
}

.hex-frame svg.hex-shape {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
}

.hex-frame .hex-fill {
	fill: var(--surface-2);
}

.hex-frame .hex-stroke {
	fill: none;
	stroke: var(--primary);
	stroke-width: 2;
	stroke-dasharray: 320;
	stroke-dashoffset: 320;
	transition: stroke-dashoffset 1.1s var(--wp--custom--effect--ease-draw);
}

.is-revealed .hex-stroke {
	stroke-dashoffset: 0;
}

.hex-frame .glyph {
	position: relative;
	z-index: 2;
	display: grid;
	place-items: center;
	color: var(--primary);
}

/* =========================================================================
   HONEYCOMB PULSE - a glow that ignites at the centre cell and rolls outward.

   Registered as a block style on core/group, so any section or panel can host
   it. assets/js/honeycomb-pulse.js lays out the cells and tags each one with
   its ring index; everything below - timing, colour, reduced motion - stays
   here, which is why the cells carry no colour of their own.

   The prototype animated the fill colour itself through the keyframes, alpha
   and all. Animating fill-opacity against a single tokenised fill gets the same
   result with no literal colour, so the accent stays swappable from tokens.json.
   ========================================================================= */

.is-style-honeycomb-pulse {
	position: relative;
	isolation: isolate;
	overflow: hidden;

	/* Enough to read as a panel when the group is used on its own. Overridable
	   from the block's Dimensions panel. */
	min-block-size: 280px;
}

.honeycomb {
	position: absolute;
	z-index: -1;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	pointer-events: none;
}

/* The comb at rest. */
.honeycomb-grid path {
	fill: none;
	stroke: var(--primary);
	stroke-opacity: 0.14;
	stroke-width: 1.2;
}

/**
 * The travelling wave. Each cell runs the same loop, offset by its distance
 * from the centre plus a fixed wobble, both handed over as plain numbers - so
 * the cycle and the step live in the token pipeline rather than in the script.
 */
.honeycomb-wave path {
	opacity: 0;
	fill: var(--primary);
	stroke: var(--primary);
	animation: honeycomb-ignite var(--wp--custom--effect--pulse-cycle) linear infinite;
	animation-delay: calc(
		(var(--ring, 0) + var(--jitter, 0)) * var(--wp--custom--effect--pulse-step)
	);
}

.honeycomb-seed {
	fill: var(--primary);
	animation: honeycomb-seed var(--wp--custom--effect--pulse-cycle) ease-out infinite;
}

@keyframes honeycomb-ignite {
	0% {
		opacity: 0;
		fill-opacity: 0;
		stroke-opacity: 0.2;
		stroke-width: 1.2;
	}

	5% {
		opacity: 1;
		fill-opacity: 0.7;
		stroke-opacity: 1;
		stroke-width: 2.6;
	}

	14% {
		opacity: 1;
		fill-opacity: 0.34;
		stroke-opacity: 0.9;
		stroke-width: 1.8;
	}

	30% {
		opacity: 0.7;
		fill-opacity: 0.15;
		stroke-opacity: 0.6;
		stroke-width: 1.4;
	}

	52% {
		opacity: 0.25;
		fill-opacity: 0.04;
		stroke-opacity: 0.3;
	}

	70%,
	100% {
		opacity: 0;
		fill-opacity: 0;
		stroke-opacity: 0.2;
	}
}

@keyframes honeycomb-seed {
	0% {
		opacity: 0;
		r: 3;
	}

	5% {
		opacity: 0.8;
		r: 9;
	}

	22%,
	100% {
		opacity: 0;
		r: 46;
	}
}

/* Blurred filters are expensive; nothing animates while it is off screen. */
.is-style-honeycomb-pulse.is-paused path,
.is-style-honeycomb-pulse.is-paused circle {
	animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {
	.honeycomb-wave path {
		opacity: 0.4;
		fill-opacity: 0.14;
		stroke-opacity: 0.8;
		animation: none;
	}

	.honeycomb-seed {
		opacity: 0;
		animation: none;
	}
}

/* =========================================================================
   SCROLL REVEAL - progressive enhancement. Content is visible by default and
   only hidden once JS confirms it can reveal it (see assets/js/reveal.js),
   so a JS failure never blanks the page.
   ========================================================================= */

.js-reveal-ready .reveal {
	opacity: 0;
	transform: translateY(var(--wp--custom--effect--reveal-shift));
	transition:
		opacity var(--wp--custom--effect--dur-slow) var(--wp--custom--effect--ease-out),
		transform var(--wp--custom--effect--dur-slow) var(--wp--custom--effect--ease-out);
}

.js-reveal-ready .reveal.is-revealed {
	opacity: 1;
	transform: none;
}

.reveal[data-delay="1"] { transition-delay: 0.08s; }
.reveal[data-delay="2"] { transition-delay: 0.16s; }
.reveal[data-delay="3"] { transition-delay: 0.24s; }
.reveal[data-delay="4"] { transition-delay: 0.32s; }
.reveal[data-delay="5"] { transition-delay: 0.40s; }

@media (prefers-reduced-motion: reduce) {
	.js-reveal-ready .reveal {
		opacity: 1;
		transform: none;
		transition: none;
	}

	.hex-frame .hex-stroke {
		stroke-dashoffset: 0;
		transition: none;
	}

	.wp-block-button__link,
	.is-style-notch-card,
	.is-style-notch-card-primary,
	.is-style-notch-card-danger {
		transition: none;
	}

	.wp-block-button__link:hover,
	.is-style-notch-card:hover {
		transform: none;
	}
}

/* The editor canvas should show final state, not the pre-reveal state. */
.editor-styles-wrapper .reveal {
	opacity: 1;
	transform: none;
}

.editor-styles-wrapper .hex-stroke {
	stroke-dashoffset: 0;
}

/* =========================================================================
   NEWSLETTER FORM - built by assets/js/newsletter.js, posted to HubSpot.
   ========================================================================= */

/**
 * One row: field, button, and a status line that wraps underneath. The field
 * carries no clip-path on purpose - the notch is the button's job here, because
 * clipping an input would clip the focus ring off its own corner.
 */
.newsletter-form {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
	max-inline-size: 420px;
}

.newsletter-form__input {
	flex: 1 1 190px;
	min-inline-size: 0;
	padding: 12px 14px;
	background: var(--surface);
	border: 1px solid var(--border-soft);
	color: var(--text);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--sm);
	transition: border-color var(--wp--custom--effect--dur-base) ease;
}

.newsletter-form__input::placeholder {
	color: var(--faint);
}

.newsletter-form__input:focus {
	border-color: var(--primary);
}

/* The theme's button treatment, matched to the pricing CTAs. */
.newsletter-form__submit {
	--notch: var(--wp--custom--notch--xs);
	flex: none;
	padding: 12px 22px;
	border: 0;
	background: var(--primary);
	color: var(--bg);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--tight);
	cursor: pointer;
	clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
	transition:
		transform var(--wp--custom--effect--dur-fast) ease,
		box-shadow var(--wp--custom--effect--dur-base) ease;
}

.newsletter-form__submit:hover:not(:disabled) {
	transform: translateY(-2px);
	box-shadow: var(--wp--preset--shadow--glow-primary);
}

.newsletter-form__submit:disabled {
	opacity: 0.55;
	cursor: default;
}

/**
 * The status line is the only feedback the visitor gets, so it takes the whole
 * row and is announced. :empty keeps it from reserving space before it says
 * anything.
 */
.newsletter-form__status {
	flex: 1 0 100%;
	margin: 0;
	color: var(--muted);
	font-size: var(--wp--preset--font-size--xs);
}

.newsletter-form__status:empty {
	display: none;
}

.newsletter-form.is-error .newsletter-form__status {
	color: var(--danger-bright);
}

/* Honeypot: off-canvas rather than display:none, which bots skip. */
.newsletter-form__trap {
	position: absolute;
	inline-size: 1px;
	block-size: 1px;
	overflow: hidden;
	clip-path: inset(50%);
}

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

	.newsletter-form__submit:hover:not(:disabled) {
		transform: none;
	}
}
