/*
 * Rules that exist only because WordPress is not the static build.
 *
 * main.css is shared verbatim with the static site and stays that way. Anything
 * here compensates for a difference Elementor introduces, and would be dead
 * weight in the static build.
 */

/*
 * Each section now lives in its own Elementor container, which is what makes the
 * page editable as a normal Elementor page. The cost is that the hero and the
 * first section are no longer siblings, so `.phero + section.sec` - which tunes
 * the seam between them - stops matching. The importer marks that first section
 * instead, and the rules are mirrored here against the marker.
 */
section.sec.afterhero {
	background: linear-gradient( 180deg, rgba( 255, 255, 255, .025 ), rgba( 255, 255, 255, 0 ) 380px );
}
section.sec.afterhero[style*="padding-top:92px"] { padding-top: 62px !important; }
section.sec.afterhero[style*="padding-top:76px"] { padding-top: 56px !important; }

@media ( max-width: 900px ) {
	section.sec.afterhero {
		background: linear-gradient( 180deg, rgba( 255, 255, 255, .025 ), rgba( 255, 255, 255, 0 ) 260px );
	}
	section.sec.afterhero[style*="padding-top:92px"] { padding-top: 52px !important; }
	section.sec.afterhero[style*="padding-top:76px"] { padding-top: 48px !important; }
}
@media ( max-width: 620px ) {
	section.sec.afterhero[style*="padding-top:92px"] { padding-top: 44px !important; }
	section.sec.afterhero[style*="padding-top:76px"] { padding-top: 42px !important; }
}

/* ==========================================================================
   Contact Form 7
   --------------------------------------------------------------------------
   CF7's template is the design's own markup, so the only thing to undo is the
   wrappers it adds around it: a <div class="wpcf7">, the <form>, and a <span>
   around every field. Left alone they become layout boxes inside a flex column
   and a two-column grid, and the form falls apart. display:contents removes them
   from layout while keeping them in the DOM, which is exactly what is wanted.
   ========================================================================== */
.fc > .wpcf7,
.fc > .wpcf7 > form,
.nlf > .wpcf7,
.nlf > .wpcf7 > form,
.fc .wpcf7-form-control-wrap,
.nlf .wpcf7-form-control-wrap {
	display: contents;
}

/* CF7's hidden meta fields and its screen-reader response list must not take a
   slot in the flex column. */
.fc .hidden-fields-container,
.nlf .hidden-fields-container,
.fc form > div[style*="display: none"],
.nlf form > div[style*="display: none"] { display: none !important; }
.fc > .wpcf7 > .screen-reader-response,
.nlf > .wpcf7 > .screen-reader-response {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect( 0 0 0 0 );
	clip-path: inset( 50% );
	white-space: nowrap;
}

/*
 * The submit control.
 *
 * The design's button is an <a> carrying a decorative gloss span, which cannot
 * wrap an <input>. The span keeps the button's skin and the input is stripped
 * back and stretched inside it, so the gloss and the gradient survive.
 */
.sysub { position: relative; padding: 0; overflow: hidden; display: inline-flex; }
.sysub input[type="submit"] {
	-webkit-appearance: none;
	appearance: none;
	background: none;
	border: 0;
	color: inherit;
	font: inherit;
	letter-spacing: inherit;
	cursor: pointer;
	padding: 13px 26px;
	border-radius: inherit;
	position: relative;
	z-index: 2;
}
.nlf .sysub input[type="submit"] { padding: 12px 22px; }
.sysub input[type="submit"]:disabled { opacity: .6; cursor: progress; }

/* CF7's spinner sits inside the button and is only visible while a submission is
   in flight, but it reserves its 24px the whole time - which made the Subscribe
   button a third wider than the design's. Taken out of flow it costs nothing
   when idle and still appears in place when it matters. */
.fc .wpcf7-spinner,
.nlf .wpcf7-spinner {
	position: absolute;
	right: 10px;
	top: 50%;
	margin: -12px 0 0;
	z-index: 3;
}

/*
 * Validation and response messages.
 *
 * CF7 ships its own borders and colours for these; the design has a voice of its
 * own, so they are restated rather than inherited.
 */
.fc .wpcf7-response-output,
.nlf .wpcf7-response-output {
	width: 100%;
	margin: 14px 0 0;
	padding: 10px 14px;
	border: 1px solid var( --line );
	border-radius: 11px;
	font-size: 12.5px;
	color: var( --mu );
	background: rgba( 255, 255, 255, .03 );
}
.fc form.sent .wpcf7-response-output,
.nlf form.sent .wpcf7-response-output {
	border-color: rgba( 0, 245, 160, .45 );
	color: var( --em );
	background: rgba( 0, 245, 160, .05 );
}
.fc form.invalid .wpcf7-response-output,
.fc form.failed .wpcf7-response-output,
.nlf form.invalid .wpcf7-response-output,
.nlf form.failed .wpcf7-response-output {
	border-color: rgba( 255, 95, 168, .45 );
	color: #ff9ec9;
	background: rgba( 255, 95, 168, .05 );
}
.fc .wpcf7-not-valid-tip,
.nlf .wpcf7-not-valid-tip {
	font-size: 11.5px;
	color: #ff9ec9;
	margin-top: 6px;
	display: block;
}
.fc input.wpcf7-not-valid,
.fc textarea.wpcf7-not-valid,
.nlf input.wpcf7-not-valid { border-color: rgba( 255, 95, 168, .55 ); }

/* ==========================================================================
   Testimonial avatar — photo instead of an initial
   --------------------------------------------------------------------------
   The circle grows from 104px to 164px and holds a client photo. When there is
   no photo it holds an abstract disc drawn here, so a testimonial is never
   waiting on an asset to look finished.
   ========================================================================== */
.tside .tav.tavp {
	width: 164px;
	height: 164px;
	padding: 3px;
	box-shadow: 0 18px 44px rgba( 0, 0, 0, .5 );
}
.tav.tavp .tavr {
	width: 100%;
	height: 100%;
	border-radius: 50%;
	overflow: hidden;
	position: relative;
	background: #0c0e11;
	display: flex;
	align-items: center;
	justify-content: center;
}
.tav.tavp .tavr img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* The glow behind the circle; colour comes from the card, set inline. */
.tside .tavh {
	position: absolute;
	z-index: 1;
	width: 230px;
	height: 230px;
	border-radius: 50%;
	filter: blur( 38px );
	opacity: .55;
	pointer-events: none;
}

/* The placeholder: brand-coloured mesh, faint grid, neutral figure. */
.tavph {
	position: absolute;
	inset: 0;
	border-radius: 50%;
	overflow: hidden;
	background: radial-gradient( 120% 120% at 22% 18%, #1a2f3a 0%, #14202c 42%, #0d1218 100% );
}
.tavph::before {
	content: '';
	position: absolute;
	inset: -20%;
	background:
		radial-gradient( closest-side at 28% 26%, rgba( 0, 245, 160, .55 ), transparent 70% ),
		radial-gradient( closest-side at 74% 34%, rgba( 139, 92, 246, .55 ), transparent 70% ),
		radial-gradient( closest-side at 52% 82%, rgba( 255, 95, 168, .42 ), transparent 70% );
	filter: blur( 14px );
}
.tavph::after {
	content: '';
	position: absolute;
	inset: 0;
	opacity: .16;
	background-image:
		linear-gradient( rgba( 255, 255, 255, .5 ) 1px, transparent 1px ),
		linear-gradient( 90deg, rgba( 255, 255, 255, .5 ) 1px, transparent 1px );
	background-size: 14px 14px;
}
.tavph svg {
	position: absolute;
	left: 50%;
	top: 52%;
	transform: translate( -50%, -50% );
	width: 78px;
	height: 78px;
	color: rgba( 255, 255, 255, .82 );
	filter: drop-shadow( 0 4px 14px rgba( 0, 0, 0, .45 ) );
}

@media ( max-width: 820px ) {
	.tside .tav.tavp { width: 124px; height: 124px; }
	.tside .tavh { width: 180px; height: 180px; }
	.tavph svg { width: 58px; height: 58px; }
}

/* ==========================================================================
   Client logos
   --------------------------------------------------------------------------
   The design drew every client as type because there were no logo files. Now
   there are, and a cell holds either. .has-logo only appears when an image is
   actually present, so a half-filled wall still reads as one deliberate row.
   ========================================================================== */
.cwi.has-logo,
.bsc.has-logo {
	display: flex;
	align-items: center;
	justify-content: center;
}
/*
 * Capped on BOTH axes, not just height.
 *
 * A height-only cap sizes every mark by its aspect ratio, and the marks in
 * this wall run from 5.6:1 (TotalSnacc, a long wordmark) to 1:1 (Applied
 * Vision Baseball, a square shield). At a 30px cap that put TotalSnacc at
 * 165x30 and Applied Vision Baseball at 30x30 - one covers five and a half
 * times the area of the other, so the square marks read as empty cells and
 * the wall looks half-filled.
 *
 * Capping width as well lets a square mark use the vertical space a wordmark
 * cannot, which brings the spread from 5.5x down to about 2.3x. min() keeps
 * the old behaviour in any cell narrower than the cap.
 */
.cwi .clogo,
.bsc .clogo {
	max-width: min( 100%, 138px );
	max-height: 46px;
	width: auto;
	height: auto;
	object-fit: contain;
	/*
	 * The wall is monochrome by design; colour arrives on hover. Brightness is
	 * deliberately left alone - pushing it turned logos that ship on a white
	 * background into grey slabs.
	 */
	filter: grayscale( 1 );
	opacity: .78;
	transition: filter .4s var( --ease ), opacity .4s var( --ease );
}

/* Artwork drawn dark on white needs flipping to read on the dark panel. */
.cwi .clogo.is-invert,
.bsc .clogo.is-invert { filter: grayscale( 1 ) invert( 1 ); }
.cwi.has-logo:hover .clogo.is-invert,
.bsc.has-logo:hover .clogo.is-invert { filter: invert( 1 ); }
.cwi.has-logo:hover .clogo,
.bsc.has-logo:hover .clogo {
	filter: none;
	opacity: 1;
}
.bsc .clogo { max-height: 44px; }

@media ( max-width: 640px ) {
	.cwi .clogo,
	.bsc .clogo {
		max-width: min( 100%, 118px );
		max-height: 34px;
	}
}

/* Founder note: a real portrait replaces the monogram in the same plate.

   The plate was drawn for initials: a 96px circle centred in a 1:1.08 tile of
   brand gradient. Put a photograph in that circle and the tile reads as a
   large empty field with a small face in it. So when a photo is present the
   photo becomes the plate, exactly as it does on the homepage, and the name
   and role move onto a scrim at the foot instead of floating in the gradient.
   Without a photo the monogram treatment is untouched. */
.fnportrait .fnav2p { padding: 0; overflow: hidden; }

.fnportrait:has( .fnav2p img ) {
	justify-content: flex-end;
	align-items: flex-start;
	gap: 3px;
	padding: 0 24px 22px;
	background: #0b0e11;
}

.fnportrait:has( .fnav2p img )::before { display: none; }

.fnportrait:has( .fnav2p img )::after {
	content: '';
	position: absolute;
	inset: auto 0 0;
	height: 58%;
	z-index: 1;
	pointer-events: none;
	background: linear-gradient( to top, rgba( 6, 8, 10, .93 ) 10%, rgba( 6, 8, 10, .55 ) 44%, transparent );
}

.fnportrait:has( .fnav2p img ) .fnav2p {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	border-radius: 0;
	background: none;
	box-shadow: none;
	z-index: 0;
}

.fnportrait .fnav2p img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: 50% 20%;
	border-radius: 50%;
	display: block;
}

.fnportrait:has( .fnav2p img ) .fnav2p img { border-radius: 0; }

.fnportrait:has( .fnav2p img ) b { font-size: 19px; letter-spacing: -.01em; }
.fnportrait:has( .fnav2p img ) span { color: var( --mu ); }

/* Brand marquee: a client cell may carry a real mark instead of a name. */
.tblog .clogo {
	max-width: 100%;
	max-height: 34px;
	width: auto;
	height: auto;
	object-fit: contain;
	filter: grayscale(1) brightness(1.7);
	opacity: .62;
	transition: filter .4s var(--ease), opacity .4s var(--ease);
}
.tblog div:hover .clogo { filter: none; opacity: 1; }
.tblog .clogo.is-invert { filter: grayscale(1) invert(1) brightness(1.9); }
.tblog div:hover .clogo.is-invert { filter: invert(1); }

/* Careers roles: the build set this paragraph with an inline style. The widget
   gives it a class instead, so the rules live here rather than on every row. */
.role .rbody {
	font-size: 13px;
	color: var(--mu);
	font-weight: 300;
	margin-top: 9px;
	max-width: 640px;
	line-height: 1.65;
}

/* An ARIA button may hold only phrasing content, and these cards hold a heading
   and a paragraph — which is what made the accessibility tree ill-formed. The
   control moved inside the heading, so the button has to inherit the heading
   completely and leave no trace of itself. */
.exi h3 > .ext,
.ip h4 > .ipt {
	all: unset;
	display: block;
	width: 100%;
	cursor: pointer;
	font: inherit;
	color: inherit;
	letter-spacing: inherit;
	text-align: inherit;
}
.exi h3 > .ext:focus-visible,
.ip h4 > .ipt:focus-visible {
	outline: 2px solid var(--em);
	outline-offset: 4px;
	border-radius: 4px;
}

/* The carousel arrows are divs the runtime wires up. They are reachable now, so
   they need a focus ring of their own. */
.ar:focus-visible,
.tdt:focus-visible,
.cx:focus-visible {
	outline: 2px solid var(--em);
	outline-offset: 3px;
}

/* The newsletter strip sizes its input by hand — min-width:230px, no flex-grow —
   and Contact Form 7's submit is an <input>, so it inherited that 230px and the
   Subscribe button came out three times its width. The design's submit was an
   <a>, which never matched that selector. */
.nlf input[type="submit"],
.nlf .wpcf7-submit {
	min-width: 0;
	width: auto;
}

/* ==========================================================================
   Thank-you page
   ==========================================================================
   Three pieces the rest of the site had no equivalent for: the hero's
   acknowledgement animation, the two-column layout the next-steps list needs
   to not look lost, and the closing band.
   -------------------------------------------------------------------------- */

/* --- The hero visual (.tyv) ---------------------------------------------
   Same idiom as the contact page's .ctx: one loop, var(--ease), no runtime.
   6s, because the four beats need room to read as a sequence rather than a
   flicker, and because the page is not asking anyone to watch it twice.     */

.tyv { position: relative; width: 300px; height: 320px;
	display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px; }

.tystage { position: relative; width: 100%; height: 188px; display: grid; place-items: center; }

/* Two rings, half a second apart, so the acknowledgement reads as a pulse. */
.tyring { position: absolute; width: 128px; height: 128px; border-radius: 50%;
	border: 1px solid rgba( 0, 245, 160, .5 ); animation: tyring 6s var( --ease ) infinite; }
.tyring:nth-of-type( 2 ) { animation-delay: .45s; }
@keyframes tyring {
	0%, 30% { transform: scale( .5 ); opacity: 0; }
	46%     { opacity: .9; }
	100%    { transform: scale( 1.7 ); opacity: 0; }
}

/* The brief: three lines that fill, then hand over. */
.tycard { position: absolute; width: 214px; border-radius: 16px; border: 1px solid var( --line );
	background: #0b0e11; padding: 16px; box-shadow: 0 16px 40px rgba( 0, 0, 0, .5 );
	animation: tycard 6s var( --ease ) infinite; }
@keyframes tycard {
	0%        { transform: translateY( 14px ) scale( .96 ); opacity: 0; }
	8%, 24%   { transform: none; opacity: 1; }
	34%, 100% { transform: translateY( -10px ) scale( .9 ); opacity: 0; }
}
.tycard .cxh { display: flex; align-items: center; gap: 9px; margin-bottom: 13px; }
.tycard .cxh span { width: 28px; height: 28px; border-radius: 9px; background: rgba( 255, 255, 255, .05 );
	border: 1px solid var( --line ); display: flex; align-items: center; justify-content: center; color: var( --em ); }
.tycard .cxh span > svg { width: 14px; height: 14px; }
.tycard .cxh b { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var( --fa ); font-weight: 700; }
.tycard i { display: block; height: 7px; border-radius: 4px; background: rgba( 255, 255, 255, .09 );
	margin-bottom: 8px; transform-origin: left; animation: tyline 6s var( --ease ) infinite; }
.tycard i:nth-of-type( 1 ) { width: 100%; animation-delay: .1s; }
.tycard i:nth-of-type( 2 ) { width: 84%; animation-delay: .3s; }
.tycard i:nth-of-type( 3 ) { width: 56%; animation-delay: .5s; background: rgba( 0, 245, 160, .42 ); margin-bottom: 0; }
@keyframes tyline {
	0%, 4%    { transform: scaleX( 0 ); opacity: 0; }
	16%, 28%  { transform: scaleX( 1 ); opacity: 1; }
	34%, 100% { transform: scaleX( 1 ); opacity: 0; }
}

/* The acknowledgement. The tick draws rather than appearing. */
.tybadge { position: absolute; width: 106px; height: 106px; border-radius: 50%; background: var( --grad );
	display: flex; align-items: center; justify-content: center; color: var( --ink );
	box-shadow: 0 24px 64px -18px rgba( 0, 245, 160, .6 ); animation: typop 6s var( --ease ) infinite; }
@keyframes typop {
	0%, 30%   { transform: scale( .35 ); opacity: 0; }
	44%       { transform: scale( 1.07 ); opacity: 1; }
	54%, 100% { transform: scale( 1 ); opacity: 1; }
}
.tybadge svg { width: 48px; height: 48px; }
.tybadge path { fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round;
	stroke-dasharray: 34; stroke-dashoffset: 34; animation: tydraw 6s var( --ease ) infinite; }
@keyframes tydraw {
	0%, 42%   { stroke-dashoffset: 34; }
	60%, 100% { stroke-dashoffset: 0; }
}

.tyref { position: absolute; top: 4px; right: 6px; font-size: 10px; font-weight: 700; padding: 6px 12px;
	border-radius: 999px; background: rgba( 139, 92, 246, .16 ); border: 1px solid rgba( 139, 92, 246, .45 );
	color: #c4b5fd; animation: tyref 6s var( --ease ) infinite; }
@keyframes tyref {
	0%, 46%   { opacity: 0; transform: translateY( -6px ); }
	58%, 100% { opacity: 1; transform: none; }
}

.tyreply { border-radius: 16px; border: 1px solid rgba( 0, 245, 160, .4 ); background: rgba( 0, 245, 160, .08 );
	padding: 14px 16px; display: flex; align-items: center; gap: 12px; opacity: 0;
	animation: tyreply 6s var( --ease ) infinite; }
@keyframes tyreply {
	0%, 62%   { opacity: 0; transform: translateY( 12px ); }
	76%, 100% { opacity: 1; transform: none; }
}
.tyreply .cravt { width: 36px; height: 36px; border-radius: 50%; background: var( --grad );
	display: flex; align-items: center; justify-content: center; font-family: var( --fd ); font-size: 15px;
	color: var( --ink ); flex-shrink: 0; }
.tyreply .cravt svg { width: 17px; height: 17px; fill: none; stroke: var( --ink ); stroke-width: 2.7; stroke-linecap: round; stroke-linejoin: round; }
.tyreply b { font-size: 13px; display: block; }
.tyreply span { font-size: 11.5px; color: var( --mu ); }

/*
 * Motion here is decoration; the final frame is what carries the meaning.
 * So when motion is unwelcome the sequence does not slow down, it stops on
 * the frame that says the thing.
 */
@media ( prefers-reduced-motion: reduce ) {
	.tyring,
	.tycard { display: none; }
	.tybadge,
	.tyreply,
	.tyref,
	.tybadge path { animation: none; opacity: 1; transform: none; stroke-dashoffset: 0; }
}

@media ( max-width: 900px ) {
	.tyv { width: 100%; max-width: 300px; margin-inline: auto; }
}

/* --- Next steps, two columns --------------------------------------------
   .nxt carries max-width:430px from the CTA band it was designed for. On its
   own in a full-width section that leaves 700px of nothing beside it, so the
   panel fills the space with the questions the call will open with.          */

.tycols { display: grid; grid-template-columns: 430px 1fr; gap: 60px; align-items: start; }
@media ( max-width: 900px ) { .tycols { grid-template-columns: 1fr; gap: 32px; } }

.typanel { border: 1px solid var( --line ); border-radius: var( --rm ); background: var( --card ); padding: 28px 30px; }
.typanel h3 { font-family: var( --fd ); font-size: 21px; line-height: 1.25; margin-bottom: 7px; }
.typanel > p { color: var( --mu ); font-size: 13.5px; line-height: 1.6; margin-bottom: 6px; }
.tyq { display: flex; align-items: flex-start; gap: 12px; padding: 14px 0; border-top: 1px solid var( --line ); }
.tyq:first-of-type { margin-top: 12px; }
.tyq > span { flex-shrink: 0; width: 22px; height: 22px; border-radius: 7px; margin-top: 1px;
	background: rgba( 0, 245, 160, .12 ); border: 1px solid rgba( 0, 245, 160, .35 );
	display: flex; align-items: center; justify-content: center; color: var( --em ); }
.tyq > span svg { width: 12px; height: 12px; }
.tyq b { display: block; font-size: 13.5px; margin-bottom: 2px; }
.tyq p { color: var( --mu ); font-size: 12.5px; line-height: 1.55; }

/* --- Closing band -------------------------------------------------------- */

.tyend { border: 1px solid var( --line ); border-radius: var( --rl ); background: var( --lift );
	padding: 58px 40px; text-align: center; }
.tyend h2 { margin-bottom: 12px; }
.tyend p { color: var( --mu ); max-width: 560px; margin: 0 auto 26px; }
.tyend .hc { justify-content: center; }
@media ( max-width: 700px ) { .tyend { padding: 42px 22px; } }

/* --- Archive pagination -------------------------------------------------
   Only ever seen on a category or tag deep enough to have a second page, so
   it borrows the chip's shape rather than introducing another control style. */

.pag { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 38px; }
.pag .page-numbers { display: inline-flex; align-items: center; justify-content: center;
	min-width: 40px; height: 40px; padding: 0 14px; border-radius: 999px;
	border: 1px solid var( --line ); color: var( --mu ); font-size: 13px; font-weight: 600;
	transition: .3s var( --ease ); }
.pag .page-numbers:hover { border-color: var( --line2 ); color: var( --tx ); }
.pag .page-numbers.current { background: var( --grad ); border-color: transparent; color: var( --ink ); }
.pag .dots { border-color: transparent; }

/* --- Link card plates ---------------------------------------------------
   A flat gradient with a label on it reads as an image that did not load.
   The motif is a large ghosted glyph with three bars drifting behind it —
   enough to look considered, cheap enough to cost nothing: no requests, no
   layout, and it inherits the plate's own colour so every card stays on
   palette without a second decision.                                        */

.arc .arcv { position: relative; overflow: hidden; }

.arcart { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }

.arcart i { position: absolute; left: 14%; height: 8px; border-radius: 5px;
	background: rgba( 255, 255, 255, .16 ); animation: arcdrift 9s var( --ease ) infinite; }
.arcart i:nth-of-type( 1 ) { top: 30%; width: 46%; animation-delay: 0s; }
.arcart i:nth-of-type( 2 ) { top: 46%; width: 62%; animation-delay: -3s; }
.arcart i:nth-of-type( 3 ) { top: 62%; width: 34%; animation-delay: -6s; }
@keyframes arcdrift {
	0%, 100% { transform: translateX( 0 );    opacity: .5; }
	50%      { transform: translateX( 14px ); opacity: .95; }
}

.arcglyph { position: relative; display: flex; align-items: center; justify-content: center;
	width: 88px; height: 88px; border-radius: 26px;
	background: rgba( 0, 0, 0, .22 ); border: 1px solid rgba( 255, 255, 255, .16 );
	backdrop-filter: blur( 6px ); color: #fff;
	animation: arcfloat 7s var( --ease ) infinite; }
.arcglyph svg { width: 40px; height: 40px; opacity: .92; }
@keyframes arcfloat {
	0%, 100% { transform: translateY( 0 ); }
	50%      { transform: translateY( -7px ); }
}

/* The card is a link, so the motif answers the pointer rather than sitting inert. */
.arc:hover .arcglyph { transform: translateY( -7px ) scale( 1.05 ); animation-play-state: paused; }
.arc:hover .arcart i { opacity: 1; }

@media ( prefers-reduced-motion: reduce ) {
	.arcart i,
	.arcglyph { animation: none; }
}

@media ( max-width: 760px ) {
	.arcglyph { width: 70px; height: 70px; border-radius: 22px; }
	.arcglyph svg { width: 32px; height: 32px; }
}

/* The header is fixed, so an in-page jump has to stop short of it. */
.sy-anchor { scroll-margin-top: 104px; }

/* A glyph inside a button sits on the text baseline, not above it. */
.btn > .ic { display: inline-flex; align-items: center; width: 16px; height: 16px; margin-right: -2px; }
.btn > .ic svg { width: 100%; height: 100%; }

/* --- Article shell ------------------------------------------------------
   The two nudges the captured articles carried inline. They live here rather
   than in a per-element CSS field because a 20px offset that only exists in
   the database is a 20px offset nobody can find. */

.sy-pt20 { padding-top: 20px; }
.sy-mt20 { margin-top: 20px; }

/* The contents list, built from the article's own headings. */
.artoc { display: flex; flex-direction: column; gap: 2px; }
.artoc a { font-size: 12.5px; line-height: 1.5; color: var( --mu ); padding: 6px 0;
	border-top: 1px solid var( --line ); transition: color .3s var( --ease ); }
.artoc a:first-child { border-top: none; }
.artoc a:hover { color: var( --em ); }
.artoc a.sub { padding-left: 14px; font-size: 12px; color: var( --fa ); }

/* ==========================================================================
   Article sidebar spacing
   ==========================================================================
   The panels were 16px apart with 24px of padding inside them, so the gap
   between two cards read as tighter than the gap between a card's border and
   its own content — which is what makes a stack look crowded rather than
   spaced. And the contents links sat flush against the panel's left edge,
   touching the border they were meant to sit inside.
   -------------------------------------------------------------------------- */

.artside { gap: 22px; }

.artoc a { padding-left: 12px; }
.artoc a.sub { padding-left: 26px; }

/* The rule between entries starts where the text does, not at the border. */
.artoc a { border-top-color: var( --line ); }

@media ( max-width: 980px ) {
	.artside { gap: 18px; }
}

/* ==========================================================================
   The logo wall below desktop
   ==========================================================================
   Under 980px main.css turns the six-column grid into a horizontal scroller.
   Its item widths were 46% on tablet and 62% on phones, which shows two and a
   half logos on an iPad and one and a half on a phone — and a half a logo
   reads as a rendering fault, not as "there is more to the right".

   Whole logos only: three across on a tablet, two on a phone.
   -------------------------------------------------------------------------- */

@media ( max-width: 980px ) {
	.cwx { gap: 10px; }
	.cwx > * { flex: 0 0 31%; }
	.cwx .cwi { padding: 22px 12px; border-radius: var( --rs ); border: 1px solid var( --line ); }
}

@media ( max-width: 620px ) {
	.cwx > * { flex: 0 0 46%; }
	.cwx .cwi { padding: 20px 10px; min-height: 76px; }
}

/*
 * Scroll snapping and a continuous auto-scroll are the same mechanism pulling
 * in opposite directions: the snap drags the wall back to the nearest logo the
 * moment the script nudges it forward. The script turns snapping off on the
 * walls it drives, and leaves it on wherever it does not run - a phone with
 * reduced motion still gets a snapping swipe.
 */

/* ==========================================================================
   Featured image in the hero
   ==========================================================================
   .imph is a dashed upload placeholder. With a real image in it, the dashes,
   the grid and the corner marks are all describing something that is no longer
   missing — so the frame becomes a plain rounded window and gets out of the
   way.
   -------------------------------------------------------------------------- */

.imph.is-set { padding: 0; border: 1px solid var( --line ); background: var( --card ); overflow: hidden; }
.imph.is-set::before { content: none; }
.imph.is-set:hover { border-color: var( --line2 ); background: var( --card ); }
.imphimg { display: block; width: 100%; height: 100%; min-height: inherit; object-fit: cover; border-radius: inherit; }

/* A real image in a case-study figure gets the frame the placeholder had. */
.csfig .csfigimg { display: block; width: 100%; height: auto; border-radius: 16px;
	border: 1px solid var( --line ); background: var( --card ); }

/*
 * The sidebar panels are siblings inside the widget's own wrapper, not
 * children of the aside — so aside.artside's column gap applies to exactly one
 * element (the wrapper) and never reaches the panels, which is why they sat
 * flush against one another top to bottom. Space them directly instead, which
 * holds however deeply the builder wraps them.
 */
.artside .asbox + .asbox { margin-top: 22px; }

@media ( max-width: 980px ) {
	.artside .asbox + .asbox { margin-top: 18px; }
}

/* ==========================================================================
   The client band below the hero, on small screens
   ==========================================================================
   .bsgrid is the second logo surface — "Trusted by brands across 20+
   countries" on the homepage and the landing pages — and it had the same
   problem as the wall, worse: 58% cells on a tablet and 78% on a phone, which
   is one logo and a slice of the next. Same rule as the wall, so both logo
   surfaces on the site behave identically.
   -------------------------------------------------------------------------- */

@media ( max-width: 980px ) {
	.bsgrid { gap: 10px; padding: 10px 0; border-top: 1px solid var( --line ); }
	.bsgrid > * { flex: 0 0 31%; }
	.bsgrid .bsc { border-right: none; border: 1px solid var( --line ); border-radius: var( --rs );
		min-height: 92px; background: var( --bg ); }
}

@media ( max-width: 620px ) {
	.bsgrid > * { flex: 0 0 46%; }
	.bsgrid .bsc { min-height: 84px; }
}


/* ==========================================================================
   Logo marquee (below 980px)
   ==========================================================================
   Both logo surfaces become horizontal scrollers on small screens. Nudging
   scrollLeft a fraction of a pixel per frame does not work: browsers round the
   value back to whole pixels, so the position never accumulates and the row
   twitches instead of travelling — and scroll snapping drags it back besides.

   So it does not scroll. main.js moves the cells into a track and duplicates
   them once; this translates the track by exactly one set, which loops with no
   seam, runs on the compositor rather than the main thread, and stops on hover
   or when the visitor has asked for less motion.
   -------------------------------------------------------------------------- */

@media ( max-width: 980px ) {

	.cwx.is-marquee,
	.bsgrid.is-marquee { display: flex !important; overflow: hidden; gap: 0; scroll-snap-type: none; }

	.is-marquee .sytrack {
		display: flex;
		gap: 10px;
		width: max-content;
		/* One cell per three seconds reads as unhurried at any list length. */
		animation: sy-marquee calc( var( --sy-n, 12 ) * 3s ) linear infinite;
	}

	/* The width itself is written onto each cell by main.js, together with the
	   travel distance derived from it, so the two can never disagree. */
	.is-marquee .sytrack > * { flex: 0 0 auto; }

	/*
	 * Exactly one set, measured in JavaScript rather than derived from a cell
	 * width — the cells do not always end up the width the variable asks for,
	 * and a thirteen-pixel error times twelve is a jump every time the loop
	 * comes round.
	 */
	@keyframes sy-marquee {
		to { transform: translateX( calc( -1 * var( --sy-shift, 100% ) ) ); }
	}

	.is-marquee:hover .sytrack { animation-play-state: paused; }
}

@media ( prefers-reduced-motion: reduce ) {
	.is-marquee .sytrack { animation: none; }
	.cwx.is-marquee,
	.bsgrid.is-marquee { overflow-x: auto; }
}

/* ==========================================================================
   FINAL AUDIT — round 1
   ========================================================================== */

/* ---- the industries accordion stops collapsing on phones and tablets ------
   Below 900px the accordion stacks, and main.css gives the panels explicit
   heights for that: 104px closed, 260px open. A later unmediated rule -
   `.ip.on{flex:3.6}` from the round-18 slider work - reintroduced flex-basis:0
   on the open panel. In a column flex container flex-basis IS the height, so
   it beat the 260px and the open panel rendered as a two-pixel sliver with its
   content clipped away entirely.

   The visible result: "Industries we serve" opened on 01 Retail & eCommerce
   and showed 02, 03 and 04 with nothing above them. The first sector was
   simply absent from the page, on every screen under 900px.

   flex:0 0 auto returns the height to being the height. Scoped through .iacc
   so it outranks the bare .ip.on rule without having to be moved above it. */
@media ( max-width: 900px ) {
	.iacc .ip { flex: 0 0 auto; height: 104px; }
	.iacc .ip.on { flex: 0 0 auto; height: 260px; }
}

/* ---- "Currently serving" stops fighting for width on a phone -------------
   .gwcov is a flex row: an uppercase label, a bag of country chips, and a
   button. At 760px it already drops the button to full width, but the label
   keeps white-space:nowrap and its own column, which on a 390px screen leaves
   the chips about 190px to work in - so eleven countries stack nearly one per
   line beside a mostly empty left column.

   Below 560px the three parts become three rows. The label reads as a heading,
   which is what it is. */
@media ( max-width: 560px ) {
	.gwcov { flex-direction: column; align-items: stretch; gap: 12px; }
	.gwcovl { white-space: normal; }
	.gwcov .gwcc { width: 100%; }
}

/* ---- the footer drops to one column on a phone ---------------------------
   Two columns at 390px gives each about 168px, and the address does not fit:
   "Om Tower, 7th / Floor", "32 Chowringhee / Road", "Mon-Fri . 10:30- / 22:30
   IST". Every line breaks in the wrong place. One column is longer and reads
   correctly, which is the right trade on a screen you scroll anyway. */
@media ( max-width: 560px ) {
	.fg { grid-template-columns: 1fr; gap: 30px; }
}

/* ---- the "In a hurry?" pill stops pushing the page sideways ---------------
   .avail is an inline-flex pill with flex-wrap:nowrap holding a whole
   sentence: "In a hurry? - call +91 70034 16634 or email hello@syscodes.in and
   reference your enquiry." It cannot fit on a 390px screen, and because it
   refuses to wrap it measured 392px inside a 350px column - which made the
   thank-you page 412px wide and gave the whole document a horizontal scroll.

   A visitor arriving on that page has just submitted a form; a page that
   slides under the thumb is a poor first impression of the follow-through.
   Below 640px the pill wraps like the paragraph it actually is. */
@media ( max-width: 640px ) {
	.avail {
		display: flex;
		flex-wrap: wrap;
		max-width: 100%;
		border-radius: 18px;
		row-gap: 2px;
	}
}

/* ---- the contact map plate gets enough room to show the business ---------
   The embed is now a place embed rather than an address search, so it carries
   our own marker and the name chip beside it. Google centres both in the
   frame, and at 300px tall in a 513px-wide plate the chip was landing under
   the "Open in Google Maps" pill and the marker sat almost on the bottom edge.

   380px is the height at which the marker, its label and enough surrounding
   street to recognise the area are all in view at once. The phone breakpoint
   keeps a shorter plate, because there the map is a reference rather than
   something anyone navigates by. */
.cmap { min-height: 380px; }

@media ( max-width: 960px ) {
	.cmap { min-height: 300px; }
}

/* ---- the header's right-hand pair, and the nav beside it -----------------
   The WhatsApp pill and "Start a project" sit next to each other, and they
   were not the same height: 39px against 47px. Their centres lined up, which
   is why nothing looked broken, but two pills of different heights on one row
   read as misaligned rather than as a hierarchy - the eye compares the top and
   bottom edges before it finds the centre.

   Matching the height is the whole fix. 12px of vertical padding against the
   button's 13px, because the pill carries a 1px border the button does not,
   and 12 + 1 is 13.

   The glyph goes to 17px as well. A filled mark reads smaller than a stroked
   one at the same box size - it has no interior white space to lend it
   presence - so the 14px WhatsApp icon looked undersized beside 14px outline
   icons elsewhere in the chrome.

   The label stays muted at weight 500. It is the secondary action on the row
   and matching the button's weight would have the two competing. */
.nav-r .ph {
	font-size: 13.5px;
	font-weight: 500;
	gap: 9px;
	padding: 12px 18px;
}

.nav-r .ph .ic {
	width: 17px;
	height: 17px;
}

/* 13.5px was a size chosen for a nav that also had to survive a 1000px laptop.
   It is the primary navigation of the site and it is read at a glance, so it
   takes the extra pixel. The chevron follows it up so the pairing keeps its
   proportion. */
.nl a.top {
	font-size: 14.5px;
	padding: 10px 17px;
}

.nl .navcv {
	width: 14px;
	height: 14px;
}

/* The pill never wraps, and on a small laptop it drops its label.

   Growing it to match the button cost about twenty pixels, and at 1024px -
   where the full desktop nav is still on screen, because .nl only hides below
   1000 - that was twenty pixels the row did not have. The number broke onto a
   second line and took the whole header to 68px tall.

   So: it does not wrap, and between the nav's own breakpoint and 1140px it
   becomes the glyph alone in a circle. The number is a nice thing to show and
   the action is the necessary one; when only one of them fits, it is the
   action. aria-label carries "Chat on WhatsApp" either way, so nothing is lost
   to anyone reading with a screen reader. */
.nav-r .ph {
	white-space: nowrap;
	flex-shrink: 0;
}

@media ( max-width: 1140px ) {
	.nav-r .ph {
		/* font-size:0 rather than a selector, because the number is a bare text
		   node beside the icon span - there is nothing to hide it by. The SVG
		   is sized in pixels, so zero font-size does not touch it. */
		font-size: 0;
		gap: 0;
		width: 47px;
		/* Height stated, not inferred. With the label gone the line box is the
		   17px glyph, so padding alone gave a 43px circle beside a 47px button
		   - the very mismatch this set out to remove. */
		height: 47px;
		padding: 0;
		justify-content: center;
	}
}

/* And the burger joins them at 47.

   It was 44px, which is the accessible minimum and a perfectly good number on
   its own. Sitting third in a row of three it was 3px short at the top and 3px
   short at the bottom, and once the other two matched each other that gap was
   the only thing left out of line. Still comfortably above the minimum. */
.nav-r .burger {
	width: 47px;
	height: 47px;
}

/* ==========================================================================
   THE HERO DASHBOARD
   ========================================================================== */

/* The panel was drawn as an illustration of a dashboard rather than as one.

   Six wide bars in saturated green, a bright line tracing the same numbers the
   bars already drew, four hairlines nobody reads as a grid, no axis, no
   baseline, no legend, and a glow on the tallest bar that bled into its
   neighbour. Every element was turned up, and none of them said anything the
   others did not.

   What a real instrument does instead: one accent, used once. Quantities you
   can read off an axis. Two series that mean two different things. Restraint
   everywhere else. That is the whole change.

   The one honest addition is the second series: the line now carries the
   conversion rate rather than repeating revenue, so the picture makes the
   argument the copy makes - conversion moved first, revenue followed. */

/* ---- the plot and its value axis ---------------------------------------- */
.dchart {
	display: flex;
	gap: 10px;
	margin-bottom: 0;
}

.dyax {
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	/* Fixed, so the plot starts at the same x whatever the numbers say, and
	   the month labels below can be offset by a number rather than a guess. */
	width: 34px;
	flex-shrink: 0;
	padding-bottom: 1px;
}

.dyax i {
	font-style: normal;
	font-size: 8.5px;
	font-weight: 600;
	line-height: 1;
	text-align: right;
	color: var( --fa );
	opacity: .6;
	font-variant-numeric: tabular-nums;
	/* Half a line up, so each label reads as sitting ON its gridline rather
	   than in the band beneath it. */
	transform: translateY( -3px );
}

.dplot {
	position: relative;
	flex: 1;
	min-width: 0;
	height: 126px;
}

/* ---- the column tracks --------------------------------------------------
   A faint column behind every bar, so a short bar reads as a low value rather
   than as a small object floating above nothing.

   Drawn as a repeating gradient rather than a wrapper per bar, because the bar
   is the element the runtime scales to reveal it - a track inside it would
   grow with it. The 2% gap is what makes this exact: six 15% columns and five
   2% gaps land the pattern flush on the right edge at any width, and put every
   column centre on a round fraction the trend line can be plotted against. A
   pixel gap could do neither. */
.dplot::before {
	content: '';
	position: absolute;
	inset: 0;
	background: repeating-linear-gradient( 90deg,
		rgba( 255, 255, 255, .022 ) 0 15%,
		transparent 15% 17% );
}

/* ---- the grid gets a floor ---------------------------------------------- */
.dplot .dgrid span { height: 0; background: none; border-top: 1px dashed rgba( 255, 255, 255, .05 ); }
.dplot .dgrid span:last-child { border-top: 1px solid rgba( 255, 255, 255, .13 ); }

/* ---- the bars -----------------------------------------------------------
   Quiet. All of them but one.

   Highlighting the current period and leaving the rest neutral is the oldest
   idiom in analytics, and it is the reason the sixth bar looked wrong before:
   it was not the accent in a calm series, it was the loudest of six loud
   things, wearing a glow that spilled onto M5.

   The cap across the top few per cent is what stops a bar reading as a slab -
   it terminates in a value marker rather than just stopping. */
.dbars { gap: 2%; }

.dbars i {
	border-radius: 5px 5px 2px 2px;
	background: linear-gradient( 180deg,
		rgba( 210, 230, 240, .17 ) 0,
		rgba( 210, 230, 240, .09 ) 24%,
		rgba( 210, 230, 240, .028 ) 100% );
	box-shadow: inset 0 1px 0 rgba( 255, 255, 255, .16 );
}

.dbars i:last-child {
	background: linear-gradient( 180deg,
		rgba( 0, 245, 160, .72 ) 0,
		rgba( 0, 245, 160, .3 ) 26%,
		rgba( 0, 245, 160, .05 ) 100% );
	box-shadow: inset 0 1px 0 rgba( 255, 255, 255, .45 );
}

/* ---- the two series, named ----------------------------------------------
   In the header row, not on one of its own. The panel lives in a fixed stage
   and a legend that costs 24px pushes the title up under the chip that hangs
   off the top-left corner - which is exactly what the first attempt did. */
.dhd { align-items: center; }

.dleg {
	display: inline-flex;
	align-items: center;
	gap: 12px;
	margin-left: auto;
	margin-right: 12px;
	font-size: 8.5px;
	font-weight: 600;
	letter-spacing: .07em;
	text-transform: uppercase;
	color: var( --fa );
	white-space: nowrap;
}

.dleg > span { display: inline-flex; align-items: center; gap: 5px; }
.dleg i { width: 7px; height: 7px; border-radius: 2px; flex-shrink: 0; }
.dleg .lgb { background: rgba( 0, 245, 160, .55 ); }
.dleg .lgl { border-radius: 999px; background: linear-gradient( 90deg, #00f5a0, #8b5cf6 ); }

/* ---- the month axis -----------------------------------------------------
   Offset by the value axis and its gap, so a label sits under its own column. */
.dxax {
	display: flex;
	gap: 2%;
	margin: 7px 0 11px;
}

.dxax[ data-gutter ] { padding-left: 44px; }

.dxax i {
	flex: 1;
	min-width: 0;
	text-align: center;
	font-style: normal;
	font-size: 8.5px;
	font-weight: 600;
	letter-spacing: .1em;
	color: var( --fa );
	opacity: .65;
	font-variant-numeric: tabular-nums;
}

.dxax i:last-child { color: var( --em ); opacity: 1; }

/* ---- the metric cards ---------------------------------------------------
   Label, then figure and delta on one line, then the shape of the six months.
   The card was a lot of empty space beside a number; the sparkline is the
   thing a reader actually wants next to "5% from 2%", and it is six divs. */
.dfoot .dk { gap: 7px; }
.dkr { display: flex; align-items: baseline; gap: 8px; }

/* The sparkline is a line, not six bars.

   Six wide blocks under a large number read as a segmented progress bar - a
   different claim entirely, and one the panel is not making. The mark had to
   change, not its proportions. */
.dsp {
	display: block;
	height: 26px;
	margin-top: 6px;
}

.dsp svg { display: block; width: 100%; height: 100%; overflow: visible; }
.dspf { fill: rgba( 0, 245, 160, .12 ); }

.dspl {
	fill: none;
	stroke: rgba( 0, 245, 160, .8 );
	stroke-width: 1.6;
	stroke-linecap: round;
	stroke-linejoin: round;
	/* The viewBox is stretched to the card's width; without this the stroke
	   stretches with it and the line thins out horizontally. */
	vector-effect: non-scaling-stroke;
}

/* The trend line, thinner and without the halo it used to need to be seen
   over six bright bars. */
.dpath { stroke-width: 1.9; }
.ddot.go { r: 3.4; }

/* The revenue chip clears the panel title.

   It hangs off the top-left corner at top:9%, which was clear of "STORE
   REVENUE" while the panel was 430px tall. The panel is taller now - it
   carries a value axis and a month axis it did not have - and a taller panel
   in a fixed stage starts higher, which walked the title up under the chip.

   Nudged up rather than out: further left would push it past the hero column
   on a narrow desktop. Desktop only, because below 1000px the chips are
   repositioned wholesale and this would fight that. */
@media ( min-width: 1001px ) {
	.stage .f1 { top: 5.5%; }
}

/* ---- the panel and its chips on small screens ---------------------------
   Below 1000px the stage is pinned to 410px, a number chosen when the panel
   was 410px tall. The panel carries a value axis, a month axis and two
   sparklines now and stands at 472, so it overflowed its own stage by 62px -
   and because the floating chips are positioned against the stage rather than
   against the panel, the bottom chip stopped sitting under the metric cards
   and started sitting on top of the conversion figure.

   Letting the stage size to its contents fixes both at once. Nothing is lost:
   the fixed height existed to hold a composition that only exists on desktop,
   where the hero is two columns and the panel is tilted in perspective. */
@media ( max-width: 1000px ) {
	.stage { height: auto; }
}

/* The legend is desktop-only.

   Three things share the panel header - title, legend, range toggle - and at
   350px there is room for two. The legend was the one that lost: the metric
   cards below name both numbers anyway, so on a phone it repeats what is
   already there while pushing "STORE REVENUE" out of the row. */
@media ( max-width: 1000px ) {
	.dleg { display: none; }
}

/* ==========================================================================
   THE CHAT BOX
   ========================================================================== */

.sychat { position: fixed; right: 24px; bottom: 24px; z-index: 900; font-family: var( --fb ); }

@media ( max-width: 520px ) {
	.sychat { right: 14px; bottom: 14px; left: 14px; }
}

/* ---- launcher ---- */
.sylaunch {
	position: absolute; right: 0; bottom: 0;
	width: 58px; height: 58px; border-radius: 50%; border: 0; cursor: pointer;
	background: var( --grad ); color: var( --ink );
	display: grid; place-items: center;
	box-shadow: 0 16px 40px -12px rgba( 0, 245, 160, .55 ), inset 0 0 0 1px rgba( 255, 255, 255, .09 );
	transition: transform .4s var( --ease ), box-shadow .4s var( --ease );
}

.sylaunch:hover { transform: translateY( -3px ) scale( 1.04 ); }
.sylaunch svg { width: 24px; height: 24px; display: block; }
.sylaunch .ico-chat { display: grid; place-items: center; }
.sylaunch .ico-close { display: none; }
.sychat.is-open .sylaunch { background: var( --card2 ); color: var( --tx ); box-shadow: 0 10px 30px -14px #000, inset 0 0 0 1px var( --line2 ); }
.sychat.is-open .sylaunch .ico-chat { display: none; }
.sychat.is-open .sylaunch .ico-close { display: block; }

/* the unread dot: the launcher has to read as "there is something here" */
.sylaunch::after {
	content: ''; position: absolute; top: 3px; right: 3px;
	width: 11px; height: 11px; border-radius: 50%;
	background: var( --pk ); border: 2px solid var( --bg );
	opacity: 0; transform: scale( .4 ); transition: .35s var( --ease );
}

.sychat.has-ping .sylaunch::after { opacity: 1; transform: scale( 1 ); }
.sychat.is-open .sylaunch::after { opacity: 0; }

/* ---- the peek ---- */
.sypeek {
	position: absolute; right: 74px; bottom: 8px;
	width: max-content; max-width: 250px;
	background: var( --card2 ); border: 1px solid var( --line2 );
	border-radius: 16px 16px 4px 16px; padding: 13px 16px;
	font-size: 13px; line-height: 1.5; color: var( --tx );
	box-shadow: 0 20px 50px -20px rgba( 0, 0, 0, .9 );
	opacity: 0; transform: translateY( 8px ) scale( .96 ); transform-origin: 100% 100%;
	pointer-events: none; transition: .5s var( --ease );
}

.sychat.has-peek .sypeek { opacity: 1; transform: none; pointer-events: auto; }
.sypeek b { display: block; font-weight: 600; margin-bottom: 2px; }
.sypeek span { color: var( --mu ); font-size: 12px; }

/* 26, not 22. The audit counts anything under 24px as a tap target nobody can
   reliably hit, and it was right to: this is the one control on the widget a
   visitor presses when they want it to go away, and missing it opens the chat
   instead. */
.sypeekx {
	position: absolute; top: -10px; right: -10px; width: 26px; height: 26px;
	border-radius: 50%; background: var( --panel ); border: 1px solid var( --line2 );
	color: var( --mu ); cursor: pointer; display: grid; place-items: center;
	font-size: 13px; line-height: 1; padding: 0;
}

.sypeekx:hover { color: var( --tx ); }

@media ( max-width: 520px ) {
	.sypeek { right: 74px; left: 0; max-width: none; }
}

/* ---- panel ---- */
.sypanel {
	position: absolute; right: 0; bottom: 74px;
	width: 378px; height: min( 560px, calc( 100vh - 130px ) );
	display: flex; flex-direction: column; overflow: hidden;
	background: linear-gradient( 180deg, rgba( 16, 20, 24, .96 ), rgba( 8, 10, 13, .98 ) );
	backdrop-filter: blur( 26px ) saturate( 1.4 );
	border: 1px solid var( --line2 ); border-radius: var( --rl );
	box-shadow: 0 40px 90px -30px rgba( 0, 0, 0, .95 ), inset 0 0 0 1px rgba( 255, 255, 255, .03 );
	opacity: 0; transform: translateY( 14px ) scale( .97 ); transform-origin: 100% 100%;
	pointer-events: none;
	transition: opacity .38s var( --ease ), transform .38s var( --ease );
}

.sychat.is-open .sypanel { opacity: 1; transform: none; pointer-events: auto; }
.sypanel::before { content: ''; position: absolute; inset: 0 0 auto; height: 2px; background: var( --grad ); opacity: .85; }

@media ( max-width: 520px ) {
	.sypanel { width: auto; left: 0; right: 0; height: min( 74vh, 560px ); }
}

/* ---- head ---- */
.syhead { display: flex; align-items: center; gap: 12px; padding: 18px 18px 14px; border-bottom: 1px solid var( --line ); flex-shrink: 0; }

.syav {
	width: 38px; height: 38px; border-radius: 12px; flex-shrink: 0;
	display: grid; place-items: center;
	background: linear-gradient( 150deg, rgba( 0, 245, 160, .2 ), rgba( 139, 92, 246, .22 ) );
	border: 1px solid rgba( 255, 255, 255, .1 ); color: var( --em );
}

.syav svg { width: 19px; height: 19px; }
.syhead h4 { font-size: 14px; font-weight: 600; letter-spacing: -.01em; margin: 0; }
.syhead .syon { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var( --mu ); margin-top: 3px; }
.syhead .syon i { width: 6px; height: 6px; border-radius: 50%; background: var( --em ); box-shadow: 0 0 0 3px rgba( 0, 245, 160, .16 ); }

/* A close inside the panel as well as the launcher: on a phone the launcher is
   behind the sheet, and reaching past a dialog to dismiss it is not a gesture. */
.syclose {
	margin-left: auto; width: 30px; height: 30px; flex-shrink: 0;
	background: none; border: 1px solid var( --line ); border-radius: 9px;
	color: var( --mu ); cursor: pointer; display: grid; place-items: center; transition: .25s;
}

.syclose:hover { color: var( --tx ); border-color: var( --line2 ); }
.syclose svg { width: 14px; height: 14px; }

/* ---- progress ---- */
.syprog { height: 2px; background: rgba( 255, 255, 255, .05 ); flex-shrink: 0; }
.syprog i { display: block; height: 100%; width: 0; background: var( --grad ); transition: width .55s var( --ease ); }

/* ---- log ---- */
.sylog { flex: 1; overflow-y: auto; padding: 20px 18px 8px; display: flex; flex-direction: column; gap: 12px; scrollbar-width: thin; }
.sylog::-webkit-scrollbar { width: 5px; }
.sylog::-webkit-scrollbar-thumb { background: rgba( 255, 255, 255, .11 ); border-radius: 9px; }

.symsg { max-width: 84%; font-size: 13.5px; line-height: 1.6; padding: 11px 14px; border-radius: 16px; animation: syin .42s var( --ease ) both; }

@keyframes syin { from { opacity: 0; transform: translateY( 9px ); } to { opacity: 1; transform: none; } }

.symsg.bot { align-self: flex-start; background: var( --card2 ); border: 1px solid var( --line ); border-bottom-left-radius: 5px; color: var( --tx ); }
.symsg.bot a { color: var( --em ); }
.symsg.me { align-self: flex-end; background: linear-gradient( 140deg, rgba( 0, 245, 160, .17 ), rgba( 139, 92, 246, .17 ) ); border: 1px solid rgba( 0, 245, 160, .24 ); border-bottom-right-radius: 5px; color: var( --tx ); font-weight: 500; }
.symsg small { display: block; font-size: 11.5px; color: var( --mu ); margin-top: 4px; line-height: 1.5; }

.sytyping { align-self: flex-start; display: flex; gap: 4px; padding: 13px 15px; background: var( --card2 ); border: 1px solid var( --line ); border-radius: 16px; border-bottom-left-radius: 5px; }
.sytyping i { width: 5px; height: 5px; border-radius: 50%; background: var( --mu ); animation: sydot 1.3s infinite; }
.sytyping i:nth-child( 2 ) { animation-delay: .18s; }
.sytyping i:nth-child( 3 ) { animation-delay: .36s; }

@keyframes sydot { 0%, 60%, 100% { opacity: .25; transform: translateY( 0 ); } 30% { opacity: 1; transform: translateY( -3px ); } }

/* ---- composer ---- */
.syfoot { border-top: 1px solid var( --line ); padding: 12px 14px 14px; flex-shrink: 0; background: rgba( 0, 0, 0, .22 ); }

.sychips { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 10px; }
.sychips:empty { display: none; }

.sychips button {
	font: inherit; font-size: 12px; font-weight: 500; color: var( --tx ); cursor: pointer;
	background: rgba( 255, 255, 255, .04 ); border: 1px solid var( --line2 );
	border-radius: 999px; padding: 8px 13px; transition: .25s var( --ease );
}

.sychips button:hover { border-color: rgba( 0, 245, 160, .45 ); background: rgba( 0, 245, 160, .1 ); color: var( --em ); transform: translateY( -1px ); }
.sychips button.ghost { color: var( --fa ); border-style: dashed; }
.sychips button.ghost:hover { color: var( --mu ); background: rgba( 255, 255, 255, .03 ); border-color: var( --line2 ); }

.syrow { display: flex; gap: 8px; align-items: flex-end; }
.syrow.is-off { opacity: .35; pointer-events: none; }

.syinput { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; background: rgba( 255, 255, 255, .04 ); border: 1px solid var( --line2 ); border-radius: 14px; padding: 0 4px 0 13px; transition: .25s; }
.syinput:focus-within { border-color: rgba( 0, 245, 160, .5 ); background: rgba( 0, 245, 160, .05 ); }
.syinput input { flex: 1; min-width: 0; background: none; border: 0; outline: none; color: var( --tx ); font: inherit; font-size: 13.5px; padding: 12px 0; }
.syinput input::placeholder { color: var( --fa ); }

/* The label is there for a screen reader; the placeholder is there for eyes. */
.syvis { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect( 0 0 0 0 ); clip-path: inset( 50% ); white-space: nowrap; }

/* The chat's dialling-code picker.
   Same control the contact form uses, so .ccbtn, .ccp, .ccl and .cci all come
   from main.css. Three things differ: the row is a third as wide, so the button
   drops to flag plus code and loses its own border; the row sits at the bottom
   of the viewport, so the panel opens upward; and the panel is narrower than
   the phone it has to fit inside. */
.sycc { position: relative; flex: none; }
.sycc[hidden] { display: none; }
.sycc .ccbtn { width: auto; gap: 5px; padding: 11px 9px 11px 0; margin-right: 2px; border: 0; border-right: 1px solid var( --line ); border-radius: 0; background: none; color: var( --mu ); font-size: 13px; }
.sycc .ccbtn:hover { border-color: var( --line ); color: var( --tx ); }
.sycc .ccbtn.open { background: none; border-color: var( --line ); color: var( --em ); }
.sycc .ccbtn .dc { flex: none; }
.sycc .ccbtn .cvr { width: 10px; height: 10px; }
.sycc .ccp { top: auto; bottom: calc( 100% + 12px ); left: -10px; width: 272px; max-width: calc( 100vw - 64px ); transform: translateY( 6px ); }
.sycc .ccp.open { transform: none; }
.sycc .ccl { max-height: 208px; }

.sysend { width: 38px; height: 38px; flex-shrink: 0; border: 0; border-radius: 12px; cursor: pointer; display: grid; place-items: center; background: var( --grad ); color: var( --ink ); transition: .3s var( --ease ); }
.sysend:hover { transform: translateY( -2px ); }
.sysend svg { width: 17px; height: 17px; }

.syfine { margin-top: 9px; font-size: 10.5px; color: var( --fa ); display: flex; align-items: center; gap: 6px; line-height: 1.5; }
.syfine svg { width: 11px; height: 11px; flex-shrink: 0; color: var( --em ); }

/* ---- the close ---- */
.sydone { text-align: center; padding: 8px 4px 4px; animation: syin .5s var( --ease ) both; }
.sydone .tick { width: 52px; height: 52px; margin: 0 auto 14px; border-radius: 50%; display: grid; place-items: center; background: rgba( 0, 245, 160, .12 ); border: 1px solid rgba( 0, 245, 160, .4 ); color: var( --em ); }
.sydone .tick svg { width: 24px; height: 24px; }
.sydone h5 { font-family: var( --fd ); font-size: 20px; font-weight: 400; margin: 0 0 7px; }
.sydone p { font-size: 13px; color: var( --mu ); line-height: 1.6; max-width: 31ch; margin: 0 auto; }

.syref { display: inline-block; margin-top: 13px; font-size: 11.5px; letter-spacing: .06em; color: var( --em ); background: rgba( 0, 245, 160, .09 ); border: 1px solid rgba( 0, 245, 160, .28 ); border-radius: 999px; padding: 6px 13px; font-variant-numeric: tabular-nums; }

.sylinks { display: flex; gap: 8px; justify-content: center; margin-top: 16px; flex-wrap: wrap; }
.sylinks a { font-size: 12px; font-weight: 600; color: var( --tx ); text-decoration: none; border: 1px solid var( --line2 ); border-radius: 999px; padding: 9px 15px; transition: .25s; }
.sylinks a:hover { border-color: rgba( 0, 245, 160, .45 ); color: var( --em ); }

@media ( prefers-reduced-motion: reduce ) {
	.sychat *,
	.sychat *::before,
	.sychat *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* The conversation sits on the composer until it has enough to fill the panel.

   A 560px panel holding one greeting looked like a loading state - a bubble
   marooned at the top with 300px of nothing under it. Anchoring to the bottom
   is how every chat behaves and it makes the first message feel like it just
   arrived rather than like it was always there.

   A ::before with margin-top:auto rather than justify-content:flex-end, which
   makes content that overflows the top unreachable in a scroll container. */
.sylog::before { content: ''; margin-top: auto; }

/* Once the enquiry is in, the composer has nothing left to do. */
.sychat.is-done .syfoot { display: none; }

/* The chat steps aside for the mobile menu.
   The drawer covers the page at z-index 300, the chat sits at 900, so the
   launcher floated over an open menu. chat.js watches #mdrawer and sets this. */
.sychat.is-away { opacity: 0; visibility: hidden; pointer-events: none; }

/* Turnstile.
   The wrapper must take no space when the integration is off and none while an
   interaction-only widget is sitting quietly - but it must NOT be display:none
   at page load. Cloudflare's implicit renderer walks the document looking for
   .cf-turnstile the moment its script arrives, and a hidden container is a
   container it refuses to render into: "Could not find widget for provided
   container", no token, every form refused. An empty div is already zero
   pixels tall; it only needed its margins withheld until it has content. */
.sycap { width: 100%; margin: 0; }
.wpcf7-form .sycap:not( :empty ) { margin: 4px 0 0; }
/* The chat's widget is in execute mode, which reserves a 70px placeholder from
   the moment it renders - a gap in the middle of a chat window, for a challenge
   that will not run until the visitor has finished answering. Collapse it, and
   let chat.js open it for the few seconds the challenge is actually live: it
   must be visible then, or Cloudflare will not run in it. */
.sychat .sycap { max-height: 0; overflow: hidden; margin: 0; }
.sychat .sycap.is-live { max-height: 140px; overflow: visible; margin: 0 0 10px; }

/* ============================================================
   Blog article imagery and closing CTA
   ============================================================ */

/* The featured image sits beside the headline, and stays in its column.
   The original fault was never the two-column layout - it was that the image
   had no max-width, so a 1600px file forced the second column to 1600px
   inside a 1220px wrap and hung off the right edge. Constrain the image and
   the side-by-side hero works, without the extra scroll depth that stacking
   it underneath adds. */
.single-post .pgrid.hero2 { align-items: center; }
.single-post .pgrid.hero2 > .imfig { margin: 0; max-width: 100%; min-width: 0; }

@media ( max-width: 900px ) {
	.single-post .pgrid.hero2 > .imfig { margin-top: 26px; }
}

/* Prose inside a sidebar panel.
   .asbox was built for .asr rows - a label and a value - so it has no rules
   for lists or paragraphs. Dropping written HTML into one produced text with
   no rhythm at all. */
.asbox > ul,
.asbox > ol { margin: 0; padding-left: 18px; }

.asbox > ul li,
.asbox > ol li {
	margin: 0 0 10px;
	font-size: 13.5px;
	line-height: 1.62;
	color: var( --mu, #9299a5 );
}

.asbox > ul li::marker { color: var( --em, #00f5a0 ); }
.asbox > ul li:last-child,
.asbox > ol li:last-child { margin-bottom: 0; }

.asbox > p {
	margin: 0 0 12px;
	font-size: 13.5px;
	line-height: 1.62;
	color: var( --mu, #9299a5 );
}

.asbox > p:last-child { margin-bottom: 0; }
.asbox > p .btn { margin-top: 4px; }

/* The contents panel needs to stick, and to look like a list of links. */
.asbox .artoc { display: flex; flex-direction: column; gap: 9px; }

.asbox .artoc a {
	font-size: 13px;
	line-height: 1.45;
	color: var( --mu, #9299a5 );
	text-decoration: none;
	padding-left: 11px;
	border-left: 2px solid rgba( 255, 255, 255, .10 );
	transition: color .2s ease, border-color .2s ease;
}

.asbox .artoc a:hover,
.asbox .artoc a.on {
	color: var( --tx, #f4f5f7 );
	border-left-color: var( --em, #00f5a0 );
}

/* The gradient frame.
   Flat dark screenshots on a near-black page have no edge - they read as a
   hole rather than a picture. A thin brand-gradient rim gives the image a
   boundary, and the blurred wash underneath lifts it off the background. */
.imfig {
	position: relative;
	display: block;
	margin: 40px 0;
	padding: 9px;
	border-radius: 22px;
	background: linear-gradient( 135deg, rgba( 0, 245, 160, .34 ), rgba( 139, 92, 246, .26 ) 46%, rgba( 255, 95, 168, .32 ) );
	box-shadow: 0 34px 80px -46px #000;
}

.imfig::before {
	content: '';
	position: absolute;
	inset: -12% -6%;
	z-index: -1;
	background: radial-gradient( 58% 62% at 50% 42%, rgba( 0, 245, 160, .13 ), transparent 70% );
	filter: blur( 34px );
	pointer-events: none;
}

.imfig img {
	display: block;
	width: 100%;
	height: auto;
	max-width: 100%;
	border-radius: 14px;
}

.imfig figcaption {
	margin: 12px 4px 2px;
	font-size: .88rem;
	line-height: 1.5;
	color: var( --mut, #9299a5 );
}

.artbody .imfig { margin: 38px 0; }

/* The byline needs air under it. It sat directly on the opening paragraph,
   so the article looked like it started mid-sentence. */
.artbody .authbox { margin-bottom: 34px; }

/* Tables.
   The theme had no table styling at all - the comparison tables in these
   articles rendered as bare rows of text with no rules, which reads as broken
   rather than as minimal. Horizontal rules only: vertical borders make a
   small table look like a spreadsheet. */
.artbody .tblw {
	margin: 30px 0;
	overflow-x: auto;
	border: 1px solid rgba( 255, 255, 255, .09 );
	border-radius: 14px;
	background: rgba( 255, 255, 255, .015 );
}

/* min-width:0 on the column, or the table takes the page with it.
   A grid and flex item defaults to min-width:auto, meaning it refuses to
   shrink below its content. The 520px table inside therefore forced the whole
   article column to 522px on a 390px screen and the page scrolled sideways -
   even though .tblw has its own overflow-x. The wrapper can only scroll once
   the column is allowed to be narrower than what it holds. */
.art > *,
.artbody,
.artbody > * { min-width: 0; }

.artbody .tblw { max-width: 100%; }

.artbody .tblw table {
	width: 100%;
	min-width: 520px;
	border-collapse: collapse;
	font-size: .93rem;
}

.artbody .tblw th {
	text-align: left;
	padding: 15px 18px;
	font-size: .72rem;
	font-weight: 700;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var( --em, #00f5a0 );
	background: rgba( 255, 255, 255, .035 );
	border-bottom: 1px solid rgba( 255, 255, 255, .10 );
	white-space: nowrap;
}

.artbody .tblw td {
	padding: 14px 18px;
	vertical-align: top;
	line-height: 1.55;
	color: var( --mu, #9299a5 );
	border-bottom: 1px solid rgba( 255, 255, 255, .06 );
}

.artbody .tblw tbody tr:last-child td { border-bottom: 0; }

/* Lists get the same treatment as tables.
   A numbered sequence of steps and a comparison table are the same kind of
   content - structured, scannable, referred back to - and both were rendering
   as undecorated text. A numbered list becomes a panel of rows; a bulleted
   list keeps its rhythm with hairlines rather than a box, so the two do not
   compete when they sit near each other. */
.artbody > ol,
.artbody .artp > ol,
.artbody ol:not( .artoc ) {
	counter-reset: syli;
	list-style: none;
	margin: 28px 0;
	padding: 0;
	border: 1px solid rgba( 255, 255, 255, .09 );
	border-radius: 14px;
	overflow: hidden;
	background: rgba( 255, 255, 255, .015 );
}

.artbody ol:not( .artoc ) > li {
	counter-increment: syli;
	position: relative;
	margin: 0;
	padding: 15px 22px 15px 62px;
	border-bottom: 1px solid rgba( 255, 255, 255, .06 );
	line-height: 1.62;
}

.artbody ol:not( .artoc ) > li::before {
	content: counter( syli, decimal-leading-zero );
	position: absolute;
	left: 22px;
	top: 16px;
	font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
	font-size: 12px;
	font-weight: 700;
	color: var( --em, #00f5a0 );
}

.artbody ol:not( .artoc ) > li:last-child { border-bottom: 0; }

.artbody ul:not( .tag-row ):not( .sylinks ) {
	list-style: none;
	margin: 26px 0;
	padding: 0;
}

.artbody ul:not( .tag-row ):not( .sylinks ) > li {
	position: relative;
	margin: 0;
	padding: 12px 0 12px 30px;
	border-bottom: 1px solid rgba( 255, 255, 255, .055 );
	line-height: 1.62;
}

.artbody ul:not( .tag-row ):not( .sylinks ) > li::before {
	content: '';
	position: absolute;
	left: 8px;
	top: 21px;
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: var( --em, #00f5a0 );
	box-shadow: 0 0 0 4px rgba( 0, 245, 160, .10 );
}

.artbody ul:not( .tag-row ):not( .sylinks ) > li:last-child { border-bottom: 0; }
.artbody ol:not( .artoc ) > li strong:first-child,
.artbody ul:not( .tag-row ) > li strong:first-child { color: var( --tx, #f4f5f7 ); }
.artbody .tblw tbody tr:hover td { background: rgba( 255, 255, 255, .022 ); }
.artbody .tblw td:first-child { color: var( --tx, #f4f5f7 ); font-weight: 500; }

/* Card tiles carry a 4:3 featured image, so they are taller than the old
   gradient plates and the image has to cover rather than letterbox.
   wp_get_attachment_image emits a plain <img>, not the <picture> the case
   cards use, so it needs positioning of its own. */
.arcv.has-img { height: 208px; }

.arcv.has-img .arcimg {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transform: none;
}

/* Category label, moved out of the image and into the card body.
   .acat is positioned absolutely for the case-study tiles, so the blog
   variant has to undo that rather than inherit it. */
/* .arcb is a flex column, so a stretched inline-block ran the full width of
   the card and read as a banner rather than a tag. */
.arcb .acat-b {
	position: static;
	display: inline-block;
	align-self: flex-start;
	width: fit-content;
	max-width: 100%;
	margin: 0 0 12px;
	padding: 5px 11px;
	background: rgba( 0, 245, 160, .09 );
	border: 1px solid rgba( 0, 245, 160, .28 );
	color: var( --em, #00f5a0 );
	backdrop-filter: none;
	font-size: 10px;
	font-weight: 700;
	letter-spacing: .1em;
	text-transform: uppercase;
	border-radius: 999px;
}

.arcv.has-img .arcscrim {
	position: absolute;
	inset: 0;
	background: linear-gradient( 180deg, rgba( 4, 5, 6, .55 ) 0%, rgba( 4, 5, 6, .05 ) 42%, rgba( 4, 5, 6, .72 ) 100% );
}

@media ( max-width: 700px ) {
	.imfig { padding: 6px; border-radius: 16px; margin: 28px 0; }
	.imfig img { border-radius: 10px; }
}

/* The closing CTA.
   One per article, at the end of the body. Deliberately not a plain paragraph
   with a link: by the time somebody has read 2,500 words they have earned a
   real invitation, and the page should look like it expected them to finish. */
.syccta {
	position: relative;
	margin: 60px 0 8px;
	padding: 40px 42px 42px;
	border-radius: 24px;
	overflow: hidden;
	background: linear-gradient( 150deg, #0e1114, #12161a );
	border: 1px solid rgba( 255, 255, 255, .10 );
}

.syccta::before {
	content: '';
	position: absolute;
	inset: 0;
	background:
		radial-gradient( 72% 130% at 0% 0%, rgba( 0, 245, 160, .17 ), transparent 62% ),
		radial-gradient( 62% 130% at 100% 100%, rgba( 255, 95, 168, .15 ), transparent 62% );
	pointer-events: none;
}

.syccta > * { position: relative; }

.syccta .ctak {
	display: inline-block;
	margin-bottom: 14px;
	font-size: .74rem;
	font-weight: 600;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var( --em, #00f5a0 );
}

/* --fd, not --fh. There is no --fh on this site; the display face is --fd,
   so var(--fh, inherit) silently fell back to the body font and every
   heading I added came out in Onest while the rest of the page was Gloock. */
.syccta h3 {
	margin: 0 0 12px;
	font-family: var( --fd, Gloock, serif );
	font-size: clamp( 1.4rem, 2.4vw, 1.9rem );
	line-height: 1.22;
	color: var( --tx, #f4f5f7 );
}

.syccta p { margin: 0; max-width: 62ch; color: var( --mut, #9299a5 ); }
.syccta p + p { margin-top: 10px; }

.syccta .ctarow {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
	margin-top: 26px;
}

.syccta .ctafine {
	margin-top: 18px;
	font-size: .84rem;
	color: rgba( 255, 255, 255, .42 );
}

/* The secondary action.
   The theme ships exactly one button treatment, .bg1, which is the gradient
   pill. Two gradient pills side by side compete and neither reads as the
   primary, so the second action gets an outline instead. Scoped to .syccta
   rather than added globally - the rest of the site has been designed around
   having one button, and this is not the place to change that. */
.syccta .btn.gho {
	background: rgba( 255, 255, 255, .04 );
	border: 1px solid rgba( 255, 255, 255, .18 );
	color: var( --tx, #f4f5f7 );
	box-shadow: none;
	transition: border-color .25s ease, background .25s ease, transform .25s ease;
}

.syccta .btn.gho:hover {
	border-color: rgba( 0, 245, 160, .45 );
	background: rgba( 0, 245, 160, .07 );
}

.syccta .btn.gho .gl { display: none; }

@media ( max-width: 700px ) {
	.syccta { padding: 28px 24px 30px; border-radius: 18px; margin: 44px 0 8px; }
}

/* ============================================================
   Related reading — heading + horizontal slider
   ============================================================ */

/* No rule above it and less air around it.
   The section already follows a closing CTA panel, so a border plus 40px of
   padding plus the section's own margin stacked three separators on top of
   each other before the newsletter strip. */
/* One hairline, once.
   The rule is worth having as a separator between the article and what comes
   after it - the problem before was never the line, it was the line plus 40px
   of padding plus the widget's own 118px section padding plus its .dv divider,
   which read as three separators in a row. With those gone, a single rule and
   a bit of air is exactly right. */
.syrel {
	margin: 52px 0 0;
	padding-top: 34px;
	border-top: 1px solid rgba( 255, 255, 255, .09 );
}

/* Strip the nested section's chrome.
   syscodes-related-posts extends the section base, so it draws its own
   <section class="sec"> with 118px of padding top and bottom and a .dv
   divider underneath. That is correct when the widget is a page section in
   its own right; inside this wrapper - which already supplies the heading -
   it added 236px of dead space and an empty hairline block at the bottom. */
.syrel .sec {
	padding: 0;

	/*
	 * And no rendering placeholder.
	 *
	 * The section base ships content-visibility:auto with an intrinsic size of
	 * 900px, which is a good trade for a full-height page section: the browser
	 * skips laying it out until it is near the viewport, and 900px is a fair
	 * guess at what it will be. This strip is 492px, so the guess reserved
	 * roughly 400px of empty space under the cards and then collapsed as you
	 * scrolled into it. A block this short has nothing to gain from the
	 * optimisation and everything to lose from the guess being wrong.
	 */
	content-visibility: visible;
	contain-intrinsic-size: auto;
}
.syrel .sec > .wrap { padding: 0; max-width: none; width: 100%; }
.syrel .dv,
.syrel .sechd,
.syrel .secdiv { display: none; }

.syrel .syrelhd {
	display: flex;
	align-items: flex-end;
	justify-content: space-between;
	gap: 20px;
	flex-wrap: wrap;
	margin-bottom: 16px;
}

.syrel .syrelhd h2 {
	margin: 0 0 4px;
	font-family: var( --fd, Gloock, serif );
	font-size: clamp( 1.3rem, 2.2vw, 1.72rem );
	line-height: 1.2;
	color: var( --tx, #f4f5f7 );
}

.syrel .syrelsub { margin: 0; font-size: .92rem; color: var( --mu, #9299a5 ); }

.syrel .syrelnav { display: flex; gap: 8px; }

.syrel .syrelnav button {
	width: 40px;
	height: 40px;
	border-radius: 50%;
	display: grid;
	place-items: center;
	cursor: pointer;
	background: rgba( 255, 255, 255, .04 );
	border: 1px solid rgba( 255, 255, 255, .14 );
	color: var( --tx, #f4f5f7 );
	transition: border-color .2s ease, background .2s ease, opacity .2s ease;
}

.syrel .syrelnav button:hover:not( [disabled] ) {
	border-color: rgba( 0, 245, 160, .45 );
	background: rgba( 0, 245, 160, .08 );
}

.syrel .syrelnav button[disabled] { opacity: .3; cursor: default; }
.syrel .syrelnav svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 2.1; }

/* Three across, then scroll. grid-auto-flow:column keeps every card the same
   width regardless of how much text is in it, which a flex row does not. */
.syrel .arcgrid {
	display: grid;
	grid-auto-flow: column;
	grid-auto-columns: calc( ( 100% - 2 * 22px ) / 3 );
	gap: 22px;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scroll-behavior: smooth;
	padding: 2px 2px 16px;
	scrollbar-width: thin;
	scrollbar-color: rgba( 255, 255, 255, .18 ) transparent;
	overscroll-behavior-x: contain;
}

.syrel .arcgrid > * { scroll-snap-align: start; }
.syrel .arcgrid::-webkit-scrollbar { height: 6px; }
.syrel .arcgrid::-webkit-scrollbar-track { background: rgba( 255, 255, 255, .05 ); border-radius: 99px; }
.syrel .arcgrid::-webkit-scrollbar-thumb { background: rgba( 255, 255, 255, .18 ); border-radius: 99px; }
.syrel .arcgrid::-webkit-scrollbar-thumb:hover { background: rgba( 0, 245, 160, .45 ); }

@media ( max-width: 1000px ) {
	.syrel .arcgrid { grid-auto-columns: calc( ( 100% - 22px ) / 2 ); }
}

@media ( max-width: 640px ) {
	.syrel { margin-top: 48px; padding-top: 30px; }
	.syrel .arcgrid { grid-auto-columns: 84%; gap: 16px; }
	.syrel .syrelnav { display: none; }
}

/* Tap targets.

   Three controls sat under the 24px minimum on phones. Nothing here changes how
   anything looks: the two toggles are `all: unset` buttons whose height is just
   their text line, so a minimum gains a pixel or two, and the carousel dots keep
   their 8px dot and gain an invisible pad around it. A fingertip is about 9mm
   across; an 8px dot is a coin toss. */
.exi h3 > .ext,
.ip h4 > .ipt {
	min-height: 24px;
}

.tdt::after {
	content: '';
	position: absolute;
	inset: -8px;
}

/* The active dot stretches to 28px wide, so it only needs the vertical pad. */
.tdt.on::after {
	inset: -8px 0;
}

/* ==========================================================================
   Blog cards, second pass
   ========================================================================== */

/* The tile was a fixed 208px against a 4:3 source, so every card showed a
   horizontal slice out of the middle of the artwork - the brandmark at the top
   and the note panel at the bottom both fell outside it. Matching the tile to
   the source means nothing is cropped and the whole picture is legible at card
   size.

   Scoped to .arcvp - the article tile. Case-study cards are photographs and
   screenshots framed for a 208px letterbox, and giving them 4:3 as well
   re-cropped the whole case-studies archive to fix a problem it did not
   have. */
.arcvp.has-img {
	height: auto;
	aspect-ratio: 4 / 3;
}

.arcvp.has-img .arcimg {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
	transform: none;
}

/* The scrim was there to keep an overlaid category pill readable. The pill
   moved into the card body, so the only thing it does now is fog the bottom
   third of the picture - which reads as a blurry image rather than as a
   gradient, because the eye has no reason to expect a gradient there. */
.arcvp.has-img .arcscrim { display: none; }

/* A card is as tall as its longest title and blurb, which on a grid of three
   means two short cards next to a very tall one. Clamping both gives every
   card the same height without truncating anything a reader needed.
   Article cards only: a case-study card's headline is the whole pitch. */
.arc:not( .arcx ) .arcb h3 {
	display: -webkit-box;
	-webkit-line-clamp: 3;
	line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.arc:not( .arcx ) .arcb > p {
	display: -webkit-box;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

/* ==========================================================================
   Related reading, second pass
   ========================================================================== */

/* Desktop: three, side by side, and that is the whole strip.
   The rail below used grid-auto-columns with a percentage inside calc(); in a
   scroll container a percentage resolves against an intermediate size, and the
   browser sized three tracks at 235px and the fourth at 364px. Above 1000px
   there is room for three cards without scrolling at all, so the simplest fix
   is to stop scrolling. */
@media ( min-width: 1001px ) {
	.syrel .arcgrid {
		grid-auto-flow: row;
		grid-template-columns: repeat( 3, minmax( 0, 1fr ) );
		grid-auto-columns: auto;
		overflow: visible;
		scroll-snap-type: none;
		padding: 2px;
	}

	/* The widget fetches more than three so the smaller layouts have something
	   to swipe through. */
	.syrel .arcgrid > *:nth-child( n + 4 ) { display: none; }
	.syrel .syrelnav { display: none; }
}

/* Tablet and phone: a swipe rail. Widths in vw rather than percentages, for
   the reason above - vw is definite inside a scroll container and a percentage
   is not. */
@media ( max-width: 1000px ) {
	.syrel .arcgrid {
		/* The actual cause of the uneven cards.
		   .arcgrid carries an explicit grid-template-columns for the archive
		   grid, and grid-auto-columns only sizes tracks beyond it - so the
		   first three cards were laid out on squeezed explicit tracks and only
		   the fourth got the width the rail asked for. Clearing the template
		   makes every track implicit, which is what a rail needs. */
		grid-template-columns: none;
		grid-auto-columns: 46vw;
		padding-bottom: 14px;
	}
}

@media ( max-width: 640px ) {
	.syrel { margin-top: 44px; padding-top: 28px; }

	.syrel .arcgrid { grid-auto-columns: 78vw; gap: 14px; }
	.syrel .syrelnav { display: none; }

	/* Shorter cards on a phone: the picture is the point, the blurb is not. */
	.syrel .arc .arcb { padding: 18px 18px 20px; }
	.syrel .arc .arcb h3 { -webkit-line-clamp: 2; line-clamp: 2; font-size: 1.02rem; }
	.syrel .arc .arcb > p { -webkit-line-clamp: 2; line-clamp: 2; font-size: .88rem; }
}

/* ==========================================================================
   Filter chips, now links
   ========================================================================== */

/* They were spans filtering the cards already on the page, which meant they
   filtered ten of thirty-four articles. As links to the category archives they
   work across pages, survive a reload, and give a crawler somewhere to go. */
.fbar a.fchip { text-decoration: none; }

.fbar a.fchip .fcn {
	margin-left: 7px;
	opacity: .55;
	font-variant-numeric: tabular-nums;
	font-size: .86em;
}

.fbar a.fchip.on .fcn { opacity: .8; }

/* Links in article prose.
   They were inheriting the paragraph colour with no underline, which made an
   internal link indistinguishable from the sentence around it - the reader
   could not see it and the link did no work. This is the .plink treatment the
   legal and prose-aside blocks already use, applied to prose links whether a
   writer placed them or the autolink layer did. Buttons are excluded: the CTA
   at the foot of an article carries .btn and has its own look. */
.artbody p a:not( .btn ),
.artbody li a:not( .btn ),
.artbody td a:not( .btn ),
.artbody blockquote a:not( .btn ) {
	color: var( --em, #00f5a0 );
	font-weight: 500;
	text-decoration: none;
	border-bottom: 1px solid rgba( 0, 245, 160, .30 );
	transition: border-color .3s var( --ease, ease ), color .3s var( --ease, ease );
}

.artbody p a:not( .btn ):hover,
.artbody li a:not( .btn ):hover,
.artbody td a:not( .btn ):hover,
.artbody blockquote a:not( .btn ):hover,
.artbody p a:not( .btn ):focus-visible,
.artbody li a:not( .btn ):focus-visible,
.artbody td a:not( .btn ):focus-visible,
.artbody blockquote a:not( .btn ):focus-visible {
	border-bottom-color: var( --em, #00f5a0 );
}

/* The sidebar is taller than the viewport on a long article.
   It is one sticky column, so everything past the first screenful - the offer
   panel, the case studies, the search box - was pinned below the fold with no
   way to reach it: the column never scrolls, so those panels were simply
   unreachable on desktop. Bounding the column to the space it actually has and
   letting it scroll inside puts them back. */
@media ( min-width: 981px ) {
	.artside {
		align-self: start;
		max-height: calc( 100vh - 146px );
		overflow-y: auto;
		overscroll-behavior: contain;
		scrollbar-width: thin;
		scrollbar-color: rgba( 255, 255, 255, .16 ) transparent;
		padding-right: 6px;
	}

	.artside::-webkit-scrollbar { width: 6px; }
	.artside::-webkit-scrollbar-track { background: transparent; }

	.artside::-webkit-scrollbar-thumb {
		background: rgba( 255, 255, 255, .16 );
		border-radius: 999px;
	}

	.artside:hover::-webkit-scrollbar-thumb { background: rgba( 255, 255, 255, .28 ); }
}

/* The archive swaps its cards in place; a short fade covers the exchange. */
#blgrid { transition: opacity .18s ease; }

/* "Work like this" - the case-study panel in the article sidebar.
   Two links and a way through to the rest. Deliberately quieter than the
   contents panel above it: this is evidence offered, not navigation. */
.asbox > ul.aswork {
	margin: 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.asbox > ul.aswork li { margin: 0; }

.asbox > ul.aswork a {
	display: block;
	padding: 10px 12px;
	border-radius: 10px;
	text-decoration: none;
	border: 1px solid transparent;
	transition: background-color .2s ease, border-color .2s ease;
}

.asbox > ul.aswork a:hover,
.asbox > ul.aswork a:focus-visible {
	background: rgba( 255, 255, 255, .04 );
	border-color: rgba( 255, 255, 255, .10 );
}

.asbox > ul.aswork a b {
	display: block;
	font-size: 13.5px;
	font-weight: 600;
	line-height: 1.45;
	color: var( --tx, #e8ebf0 );
}

.asbox > ul.aswork a span {
	display: block;
	margin-top: 3px;
	font-size: 11.5px;
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var( --em, #00f5a0 );
}

.asbox a.aswall {
	display: inline-block;
	margin: 12px 0 0 12px;
	font-size: 12.5px;
	color: var( --mu, #9299a5 );
	text-decoration: none;
	border-bottom: 1px solid rgba( 255, 255, 255, .18 );
	padding-bottom: 1px;
	transition: color .2s ease, border-color .2s ease;
}

.asbox a.aswall:hover,
.asbox a.aswall:focus-visible {
	color: var( --tx, #e8ebf0 );
	border-color: var( --em, #00f5a0 );
}

/* ==========================================================================
   Footer: the signal card, and the wash behind it
   ==========================================================================

   Consolidated from the block that was injected into footer template 675
   while this was being designed live. That block was six rounds of overrides
   stacked on top of each other; what follows is the result those rounds
   settled on, written once.

   Two things it is worth knowing before editing. The pill around the email
   field is a grid pseudo-element, not a border on the form: Contact Form 7
   puts its response paragraph and the Turnstile widget inside the <form>, and
   a real border would have stretched around both. And the footer's own top
   hairline is drawn here rather than by the strip, because for a while both
   drew one and the seam read as two lines a pixel apart. */

.fstrip {
	background: none;
	border-top: 0;
	border-bottom: 0;
	padding: 64px 22px 58px;
}

.fmain { padding-top: 0; }

.fstrip .fsi {
	position: relative;
	overflow: hidden;
	align-items: center;
	gap: 48px;
	padding: 38px 42px;
	border: 1px solid rgba( 255, 255, 255, .09 );
	border-radius: var( --rl, 24px );
	background:
		radial-gradient( 720px 300px at 8% -30%, rgba( 0, 245, 160, .14 ), transparent 62% ),
		radial-gradient( 620px 320px at 98% 130%, rgba( 139, 92, 246, .16 ), transparent 60% ),
		linear-gradient( 180deg, #0f1317, #0b0e11 );
	box-shadow: 0 40px 90px -50px rgba( 0, 0, 0, .95 );
}

/* The lit edge. White rather than brand-coloured: the footer already draws a
   brand hairline directly above it, and two coloured lines that close
   together read as a printing error. */
.fstrip .fsi::before {
	content: '';
	position: absolute;
	inset: 0 0 auto;
	height: 1px;
	opacity: .85;
	background: linear-gradient( 90deg, transparent, rgba( 255, 255, 255, .2 ) 22%, rgba( 255, 255, 255, .24 ) 50%, rgba( 255, 255, 255, .2 ) 78%, transparent );
}

/* Masked to the top-left corner so it never sits behind the input, where it
   would read as noise rather than as texture. */
.fstrip .fsi::after {
	content: '';
	position: absolute;
	inset: 0;
	pointer-events: none;
	opacity: .5;
	background-image: radial-gradient( rgba( 255, 255, 255, .13 ) 1px, transparent 1px );
	background-size: 22px 22px;
	-webkit-mask-image: radial-gradient( 560px 240px at 12% 0%, #000, transparent 70% );
	mask-image: radial-gradient( 560px 240px at 12% 0%, #000, transparent 70% );
}

.fstrip .fsl,
.fstrip .nlf {
	position: relative;
	z-index: 1;
}

.fstrip .fsl {
	display: block;
	flex: 1;
	min-width: 0;
}

.fstrip .fsl .ic {
	display: grid;
	place-items: center;
	width: 46px;
	height: 46px;
	margin-bottom: 18px;
	border-radius: 14px;
	color: var( --em, #00f5a0 );
	background: linear-gradient( 160deg, rgba( 0, 245, 160, .18 ), rgba( 139, 92, 246, .12 ) );
	border: 1px solid rgba( 0, 245, 160, .28 );
	box-shadow: 0 0 26px -8px rgba( 0, 245, 160, .7 );
}

.fstrip .fsl .ic svg { width: 22px; height: 22px; }

.fstrip .nlk {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	margin-bottom: 13px;
	font-size: 10.5px;
	font-weight: 700;
	letter-spacing: .13em;
	text-transform: uppercase;
	color: var( --em, #00f5a0 );
}

.fstrip .nlk i {
	display: block;
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: var( --em, #00f5a0 );
	box-shadow: 0 0 12px var( --em, #00f5a0 );
}

.fstrip .fsl b {
	display: block;
	font-family: var( --fd, serif );
	font-weight: 400;
	font-size: 30px;
	line-height: 1.18;
	letter-spacing: -.01em;
}

.fstrip .fsl b em {
	font-style: normal;
	background: var( --grad );
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

.fstrip .fsl .nlsub {
	display: block;
	margin-top: 11px;
	max-width: 46ch;
	font-size: 14px;
	line-height: 1.6;
	color: var( --mu, #9299a5 );
}

.fstrip .nlf {
	width: 400px;
	flex: none;
}

/* Field and button as one control.
   Two boxes with a gap between them is the shape of a default form; seating
   the button inside the field is what makes it look considered. The CF7
   markup is untouched, so the pill is a grid item rather than a border, and
   the captcha and the response line get rows of their own underneath. */
.fstrip .nlf .wpcf7-form {
	position: relative;
	display: grid;
	grid-template-columns: 1fr auto;
	align-items: center;
	row-gap: 12px;
	width: 100%;
	max-width: none;
	padding: 0;
	border: 0;
	background: none;
	box-shadow: none;
}

.fstrip .nlf .wpcf7-form > * { grid-column: 1 / -1; }

.fstrip .nlf .wpcf7-form::before {
	content: '';
	grid-column: 1 / -1;
	grid-row: 1;
	align-self: stretch;
	min-height: 56px;
	border: 1px solid var( --line, rgba( 255, 255, 255, .07 ) );
	border-radius: 999px;
	background: rgba( 255, 255, 255, .035 );
	transition: border-color .3s var( --ease ), box-shadow .3s var( --ease );
}

.fstrip .nlf .wpcf7-form:focus-within::before {
	border-color: rgba( 0, 245, 160, .5 );
	box-shadow: 0 0 0 4px rgba( 0, 245, 160, .09 );
}

.fstrip .nlf .wpcf7-form-control-wrap {
	position: relative;
	z-index: 1;
	display: block;
	grid-column: 1;
	grid-row: 1;
	min-width: 0;
	padding: 5px 0 5px 4px;
}

.fstrip .nlf input[type="email"] {
	width: 100%;
	height: 46px;
	padding: 0 8px 0 18px;
	border: 0;
	border-radius: 999px;
	background: transparent;
	font-family: var( --fb );
	font-size: 14px;
	font-weight: 300;
	color: var( --tx, #f4f5f7 );
	outline: none;
}

.fstrip .nlf input[type="email"]::placeholder { color: #6c7480; }

.fstrip .nlf .sysub {
	position: relative;
	z-index: 1;
	grid-column: 2;
	grid-row: 1;
	height: 44px;
	margin: 5px 5px 5px 0;
}

/* CF7's bookkeeping: hidden inputs and the unstyled div it wraps them in.
   Left visible they take a grid row each and push the captcha down. */
.fstrip .nlf .hidden-fields-container,
.fstrip .nlf .wpcf7-form > div:not( [class] ),
.fstrip .nlf .wpcf7-form > input { display: none; }

/* Turnstile gets its own row. Rendered inside the pill it stretched the
   control into something two lines tall with a field floating in it. */
.fstrip .nlf .sycap {
	position: static;
	grid-column: 1 / -1;
	grid-row: 2;
	max-width: 100%;
	margin: 0;
	overflow: hidden;
}

.sycap { max-width: 100%; overflow: hidden; }
.fstrip .nlf .sycap iframe { max-width: 100%; }

.fstrip .nlf .wpcf7-response-output {
	position: static;
	grid-column: 1 / -1;
	grid-row: 3;
	width: auto;
	margin: 0;
	padding: 9px 16px;
	border-radius: 12px;
	font-size: 12.5px;
}

/* What the subline used to have to carry. Three facts, small, in a row.
   They hide once CF7 has something of its own to say, so the message lands
   where the eye already is rather than below three chips repeating the terms. */
.fstrip .nlch {
	display: flex;
	flex-wrap: wrap;
	gap: 7px;
	margin: 14px 0 0;
	padding: 0 0 0 6px;
	list-style: none;
}

.fstrip .nlch li {
	padding: 5px 11px;
	border: 1px solid var( --line, rgba( 255, 255, 255, .07 ) );
	border-radius: 999px;
	background: rgba( 255, 255, 255, .02 );
	font-size: 11px;
	color: var( --fa, #8b93a1 );
}

.fstrip .nlf:has( .wpcf7-form.sent ) .nlch,
.fstrip .nlf:has( .wpcf7-form.invalid ) .nlch,
.fstrip .nlf:has( .wpcf7-form.failed ) .nlch { visibility: hidden; }

/* The footer's own light. One hairline across the top, and a three-point
   brand wash behind everything, so the block reads as lit rather than as the
   page simply running out. */
footer.elementor-location-footer {
	position: relative;
	border-top: 0;
}

footer.elementor-location-footer::before {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	top: 0;
	z-index: 2;
	height: 1px;
	background: linear-gradient( 90deg, transparent, rgba( 0, 245, 160, .5 ), rgba( 139, 92, 246, .45 ), rgba( 255, 95, 168, .4 ), transparent );
}

footer.elementor-location-footer::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 0;
	pointer-events: none;
	background:
		radial-gradient( 900px 400px at 10% 0%, rgba( 0, 245, 160, .1 ), transparent 60% ),
		radial-gradient( 780px 440px at 90% 24%, rgba( 139, 92, 246, .11 ), transparent 62% ),
		radial-gradient( 640px 340px at 60% 100%, rgba( 255, 95, 168, .07 ), transparent 60% );
}

footer.elementor-location-footer > * { position: relative; z-index: 1; }

/* The reply line under a form. The avatars are gone from the markup, so the
   row closes up rather than carrying their gap. */
.teamrow { gap: 0; }

@media ( max-width: 980px ) {
	.fstrip { padding: 48px 18px 44px; }

	.fstrip .fsi {
		flex-direction: column;
		align-items: stretch;
		gap: 26px;
		padding: 30px 24px;
	}

	.fstrip .fsl b { font-size: 25px; }
	.fstrip .nlf { width: 100%; }

	/* On a phone the composite pill becomes a field with a full-width button
	   under it: a 46px button sharing a row with an email field leaves neither
	   of them a comfortable tap target. */
	.fstrip .nlf .wpcf7-form {
		grid-template-columns: 1fr;
		row-gap: 9px;
	}

	.fstrip .nlf .wpcf7-form::before { display: none; }
	.fstrip .nlf .wpcf7-form-control-wrap { padding: 0; }

	.fstrip .nlf input[type="email"] {
		height: 50px;
		padding: 0 18px;
		border: 1px solid var( --line, rgba( 255, 255, 255, .07 ) );
		border-radius: 14px;
		background: rgba( 255, 255, 255, .035 );
	}

	.fstrip .nlf .sysub {
		grid-column: 1;
		grid-row: 2;
		width: 100%;
		height: 50px;
		margin: 0;
		border-radius: 14px;
	}

	.fstrip .nlf .sycap { grid-row: 3; }
	.fstrip .nlf .wpcf7-response-output { grid-row: 4; }
}


/* Founder plate: a real portrait fills the plate; ring and grid stand down. */
.fnp .fnphoto{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:50% 24%;z-index:1;}
.fnp .fvig{z-index:2;}
.fnp:has(.fnphoto) .fnring,.fnp:has(.fnphoto) .fmk{display:none;}


/* ==========================================================================
   Blog grid: now a card rail
   ==========================================================================

   The section renders the standard .arc article card inside the standard .ctk
   track, so .cc, .ctk > .arc and the .arcvp image rules already cover it and
   there is almost nothing to add here.

   The block below is dead: .bfeat / .bfv / .bsv / .bcol belonged to the old
   one-off layout - a large featured tile beside a column of three small ones -
   which no longer renders. Kept only until the next pass confirms nothing else
   reaches for those classes, then it goes. */

/* The homepage section was drawn before the posts had artwork, so the card
   showed its category gradient and nothing else. The image goes inside the same
   box the gradient painted, so the existing hover-scale on .bgd carries the
   artwork with it and no part of the layout moves. */
.bfv .bgd .bimg,
.bsv .bimg {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* The artwork sets the height, at the ratio it was drawn in.

   These illustrations are 1200x900 - 4:3, not 16:9 - and densely drawn:
   numbered lists, labelled panels, charts. The original fixed 240px band cut a
   horizontal slice out of the middle of a diagram, which reads as a broken
   image rather than a crop, and 16:9 still lost a quarter of it. Given its own
   ratio the whole illustration lands uncropped, and at roughly 640px wide it is
   legible, which is the entire point of drawing it. */
.bfv:has( .bgd.has-img ) {
	height: auto;
	aspect-ratio: 4 / 3;
}

/* The side thumbnails, widened from the original 96px square.

   A 96px square of a dense diagram is grey mush - too small to resolve the
   structure, so it reads as a rendering fault rather than a picture. At 4:3 and
   150px the panels and colour bands come through, which is all a thumbnail has
   to do; the card has a title for the rest. */
.bsv:has( .bimg ) {
	width: 150px;
	height: 113px;
	border-radius: 12px;
}

/* The dot grid was texture over a flat gradient. Over an illustration it is
   just noise, so it stands down when there is something to look at. */
.bfv .bgd.has-img .bmk,
.bsv.has-img .bmk2 { display: none; }

@media ( max-width: 560px ) {
	.bsv:has( .bimg ) { width: 104px; height: 78px; }
}

/* The card carries no scrim of its own, so a pale illustration would swallow
   the category chip. Kept light, and weighted to the top, so it does not dull
   the artwork it sits on. */
.bfv .bgd.has-img::after {
	content: '';
	position: absolute;
	inset: 0;
	background: linear-gradient( 180deg, rgba( 6, 8, 10, .52 ) 0%, rgba( 6, 8, 10, .1 ) 38%, transparent 100% );
}

/* The chip moves right when there is artwork.

   These illustrations carry their own title, set left, at the top - exactly
   where the chip sits. Two titles in one corner, one partly behind the other,
   reads as a mistake. Nothing else competes for the right-hand side. */
.bfv:has( .bgd.has-img ) + .bcat,
.bfv:has( .bgd.has-img ) .bcat,
.bfeat:has( .bgd.has-img ) .bcat {
	left: auto;
	right: 16px;
}
