/**
 * Homepage Services carousel (template-parts/home/services.php, node 255:29).
 *
 * Slide ruling: 480x600 (4:5); the node's 500x600 is superseded.
 * Reveal architecture: state-driven via the --reveal custom property. Desktop
 * hover and :focus-within merely set --reveal: 1, exactly like the
 * .is-revealed class that the future mobile active-slide pass will toggle.
 */

.services {
	padding-block: var(--section-pad-y);
}

.services .eyebrow {
	margin-bottom: 32px;
}

/* align-items: start, not center. Centering made the gap under the eyebrow
   depend on the DESCRIPTION's height: whichever column is shorter gets pushed
   down by half the difference, so a one-line headline beside a three-line
   description sat 20.5px lower than a two-line one. That is a rule that
   silently re-breaks every time copy changes, which the Slovak translation
   will do. Top-aligning makes the eyebrow-to-headline gap the eyebrow's own
   margin, 32px, on every page regardless of copy length. */
.services__header {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 124px;
	align-items: start;
}

.services__text {
	font-size: var(--type-body-lg-size);
	line-height: var(--type-body-lg-line);
	font-weight: var(--type-body-lg-weight);
	color: var(--color-body-text);
}

/* --------------------------------------------------------------------
 * Scroller: starts at the container's left line and bleeds off the right
 * viewport edge. The left clip is the track's own edge (the wrapper is
 * positioned to start at the container line - no mask element needed).
 * -------------------------------------------------------------------- */

.services__carousel {
	margin-top: 64px;
}

.services__track {
	position: relative;
	margin-left: calc((100% - min(var(--container-max), 100% - 2 * var(--container-pad))) / 2);
	display: flex;
	gap: 16px;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	padding-inline-end: 4rem; /* 64px trailing space that survives scroll-to-end */
	scrollbar-width: none; /* arrows + snap are the affordance, per design */
}

.services__track::-webkit-scrollbar {
	display: none;
}

.services__nav {
	margin-top: 64px;
	display: flex;
	justify-content: flex-end;
	gap: 12px;
}

/* --------------------------------------------------------------------
 * Slide
 * -------------------------------------------------------------------- */

.services__slide {
	--reveal: 0;
	/* House easing (tokens.css --motion-ease): fast start, long soft settle. */
	--svc-ease: var(--motion-ease);
	/* Defaults below are the EXIT timing: symmetric 650ms, no stagger. */
	--svc-dur-block: 650ms;
	--svc-dur-item: 650ms;
	--svc-delay-desc: 0ms;
	--svc-delay-cta: 0ms;
	position: relative;
	flex: 0 0 480px;
	/* The 4:5 ruling is a FLOOR, not a fixed size: 480 x 1.25 = 600, the same
	   height the card had before, but content taller than that now grows the
	   card instead of being clipped. Applied at every width, not just mobile,
	   so longer copy (the Slovak translation) cannot break it later. */
	min-height: 600px;
	scroll-snap-align: start;
	/* Column, bottom-aligned: the content block is in flow, so its full
	   height (title AND the revealed block) drives the card. */
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	border-radius: 6px;
	overflow: hidden;
	background-color: var(--color-surface-darkest);
	text-decoration: none;
	color: var(--color-heading-text-on-dark);
}

.services__slide:hover,
.services__slide:focus-within,
.services__slide.is-revealed {
	--reveal: 1;
	/* ENTER timing: same 650ms block, staggered children. */
	--svc-dur-item: 550ms;
	--svc-delay-desc: 80ms;
	--svc-delay-cta: 160ms;
}

.services__slide-img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Image zoom rides the reveal on hover-capable devices only. Deliberately
   tied to :hover/:focus-within and NOT to .is-revealed, so the future mobile
   active-slide auto-reveal never zooms. Gated on no-preference so reduced
   motion gets NO zoom at all, not an instant jump. The static gradient layer
   never scales; the card's overflow clips the zoomed image. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
	.services__slide-img {
		transition: transform var(--svc-dur-block) var(--svc-ease);
		will-change: transform;
	}

	.services__slide:hover .services__slide-img,
	.services__slide:focus-within .services__slide-img {
		transform: scale(1.04);
	}
}

/* STATIC overlay, exactly the Figma gradient, identical in default and
   revealed states. Never transitioned, never transformed - per the designer,
   final. Only the content block and its children animate on the card. */
.services__gradient {
	position: absolute;
	inset: 0;
	background: linear-gradient(180deg, rgba(10, 11, 13, 0) 0%, var(--color-surface-darkest) 100%);
}

/* Bottom-anchored content. Default: pushed down by exactly the hidden part's
   height (--slide-hidden, measured by services-carousel.js), so only the
   title shows inside the padding. Revealed: rises to translateY(0). No
   layout properties are animated; the title never fades, it only rides
   this block's transform. */
.services__slide-content {
	position: relative; /* above the image and gradient, but IN FLOW */
	/* 96px of clear space above the title: 64 here plus the 32 padding
	   below. It is a margin rather than padding so it also sets the card's
	   minimum content height (96 + content + 32). */
	margin-top: 64px;
	padding: 32px;
	transform: translateY(calc((1 - var(--reveal)) * var(--slide-hidden, 0px)));
	transition: transform var(--svc-dur-block) var(--svc-ease);
	will-change: transform;
}

.services__slide-title {
	color: var(--color-heading-text-on-dark);
	line-height: normal; /* node renders auto; the base h3 token's 1.2 is overridden here */
}

/* Wrapper only: defines the measured hidden height. The staggered fades
   live on its children. */
.services__slide-more {
	margin-top: 12px;
	display: flex;
	flex-direction: column;
}

/* card-body, now --type-card-body-*. This rule is where that set was
   measured from: 15px/400/normal per node.
   Stagger: starts +80ms after the block on enter, no delay on exit. */
.services__slide-desc {
	font-size: var(--type-card-body-size);
	line-height: var(--type-card-body-line);
	font-weight: var(--type-card-body-weight);
	color: var(--color-body-text-on-dark);
	opacity: var(--reveal);
	transform: translateY(calc((1 - var(--reveal)) * 12px));
	transition:
		opacity var(--svc-dur-item) var(--svc-ease) var(--svc-delay-desc),
		transform var(--svc-dur-item) var(--svc-ease) var(--svc-delay-desc);
}

/* btn-text: card-body size at 600, per node. The weight is declared rather
   than taken from --type-card-body-weight on purpose: this is the one card-body
   use that is a control label, and it is deliberately not the 14px btn-primary
   ruling, which covers primary buttons only. Stagger: +160ms on enter. */
.services__slide-cta {
	margin-top: 32px;
	display: flex;
	align-items: center;
	justify-content: space-between;
	font-size: var(--type-card-body-size);
	line-height: var(--type-card-body-line);
	font-weight: 600;
	color: var(--color-button-primary-text);
	opacity: var(--reveal);
	transform: translateY(calc((1 - var(--reveal)) * 12px));
	transition:
		opacity var(--svc-dur-item) var(--svc-ease) var(--svc-delay-cta),
		transform var(--svc-dur-item) var(--svc-ease) var(--svc-delay-cta);
}

/* Hover-less devices at desktop widths (touch monitors): everything visible,
   nothing animated. The mobile active-slide mechanism is a responsive-pass
   concern, not this rule. */
@media (hover: none) {
	.services__slide {
		--reveal: 1;
	}

	.services__slide-content {
		transform: translateY(0);
	}

	.services__slide-content,
	.services__slide-desc,
	.services__slide-cta {
		transition: none;
	}
}

/* --------------------------------------------------------------------
 * Wide variant (services page "What we do"): one two-column slide at a time,
 * with a CONSTANT peek of the next one at any viewport width.
 *
 * The slide is .svc-wide below, its own component. Only the track sizing
 * lives here, because that is what makes the peek constant.
 *
 * The peek math. The track starts at the container's left line and runs to
 * the viewport edge, so its box width grows with the viewport. For the next
 * slide to show exactly --peek pixels at scroll 0:
 *
 *     slide = trackBorderBox - gap - peek
 *
 * A percentage flex-basis resolves against the flex container's CONTENT box,
 * which is the border box minus the trailing padding, so the trailing padding
 * is added back. It is named rather than repeated so the two cannot drift.
 * Nothing here refers to the viewport, so the scrollbar cannot skew it.
 * -------------------------------------------------------------------- */

.services--wide {
	--peek: 160px; /* tune here; 120 / 160 / 200 are on the table */
}

.services--wide .services__track {
	--track-trailing: 4rem; /* mirrors the base trailing padding, named for the sum below */
	padding-inline-end: var(--track-trailing);
	align-items: stretch; /* every slide as tall as the tallest, so the peek edge is clean */
}

.services--wide .svc-wide {
	flex: 0 0 calc(100% + var(--track-trailing) - 16px - var(--peek));
	scroll-snap-align: start;
}

/* --------------------------------------------------------------------
 * Wide slide: content box beside its image, no interaction of any kind.
 *
 * Deliberately the calmest component on the site: no hover, no reveal, no
 * zoom, no gradient. The description is always visible, so none of the
 * service-card machinery (--reveal, --slide-hidden, the gradient layer) has
 * any place here and none of it is imported.
 * -------------------------------------------------------------------- */

.svc-wide {
	display: grid;
	grid-template-columns: 1fr 1fr; /* equal columns, per the ruling */
	gap: 16px;
	/* A floor, never a fixed height: taller copy grows the slide and both
	   columns with it, because grid rows are auto and both children stretch. */
	min-height: 480px;
}

/* Title pinned to the top padding line, description to the bottom one, so the
   box uses its full height deliberately rather than leaving the slack under
   the text that stacking them at the top produced.
   The gap stays as the MINIMUM separation: space-between distributes only the
   free space left after gaps, so once copy grows past the floor there is no
   free space and the 32px still holds the two apart instead of letting them
   meet. */
.svc-wide__content {
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	gap: 32px;
	padding: 48px;
	border-radius: 6px;
	background-color: var(--color-surface-card);
}

/* No type rule for .svc-wide__title on purpose. It is an h3 and takes the h3
   token straight from base.css, which is the established card-title size that
   the homepage service card also uses. It carried the h2 token briefly, but at
   40px it matched the section headline exactly and the two competed with
   neither leading; the heading level and the size now agree. */

.svc-wide__text {
	font-size: var(--type-body-lg-size);
	line-height: var(--type-body-lg-line);
	letter-spacing: var(--type-body-lg-spacing);
	font-weight: var(--type-body-lg-weight);
	color: var(--color-body-text);
}

/* On its own: no wrapper, no grey behind it. height 100% is what makes it
   finish exactly level with the content box top and bottom. */
.svc-wide__image {
	display: block;
	width: 100%;
	height: 100%;
	border-radius: 6px;
	object-fit: cover;
}

/* --------------------------------------------------------------------
 * Deliver variant (services page "What we deliver"): three square boxes
 * filling the container, the fourth clipped by the right bleed.
 *
 * There are no images in this section, so the image-backed card is wrong for
 * it and .wd-box below replaces it entirely. No hover, no reveal, no zoom.
 *
 * The box width is container-derived, so three boxes plus their two gaps land
 * exactly on the container's right line and the boxes align with the section
 * header above. container-type makes the section queryable so the derivation
 * can reference the section width from a rule that applies to the box, where
 * a raw percentage would resolve against the track instead.
 * -------------------------------------------------------------------- */

.services--deliver {
	container-type: inline-size;
	--wd-gap: 16px;
	/* Zero: the three boxes fill the container exactly, so the third ends on
	   the container's right line and the boxes align with the section header,
	   which matters more here than peek width. The fourth then shows only the
	   right bleed, 48px at 1440. That sliver is made to read as a card edge
	   rather than a clipped word by the box padding below, which exceeds it,
	   so no glyph can fall inside it. Raising this trades that alignment for a
	   wider peek. */
	--wd-peek-boost: 0px;
	--wd-box: calc(
		(min(var(--container-max), 100cqw - 2 * var(--container-pad))
			- 2 * var(--wd-gap) - var(--wd-peek-boost)) / 3
	);
}

.services--deliver .wd-box {
	flex: 0 0 var(--wd-box);
	/* Square is a FLOOR derived from the box's own width, so long copy grows
	   the box instead of clipping. Never aspect-ratio: it holds the ratio and
	   clips the overflow. */
	min-height: var(--wd-box);
	scroll-snap-align: start;
}

/* --------------------------------------------------------------------
 * Deliver box: icon top left, title and description bottom left.
 * -------------------------------------------------------------------- */

/* Padding EXCEEDS the 48px sliver the fourth box shows at 1440, which is what
   makes that sliver read as a card edge: no glyph and no chip can fall inside
   it, because the content box starts at or past the viewport edge. Measured at
   1440, 48px puts the leftmost content exactly on that edge with nothing
   visible. It also matches the "What we do" content box. At 1600 and 1920 the
   bleed is wider and the peek shows real content, which is correct. */
.wd-box {
	display: flex;
	flex-direction: column;
	justify-content: space-between; /* icon to the top, text to the bottom */
	gap: 32px;
	align-items: flex-start;
	padding: 48px;
	border-radius: 6px;
	background-color: var(--color-surface-card);
}

/* The whole set's icon colour. Swapping this one declaration to
   var(--color-accent) recolours all seven. */
.wd-box__icon {
	--icon-chip-glyph: var(--color-heading-text);
}

.wd-box__icon .icon-chip__glyph {
	width: 28px;
	height: 28px;
}

.wd-box__title {
	color: var(--color-heading-text);
}

.wd-box__desc {
	margin-top: 12px;
	font-size: var(--type-card-body-size);
	line-height: var(--type-card-body-line);
	font-weight: var(--type-card-body-weight);
	color: var(--color-text-secondary);
}

/* --------------------------------------------------------------------
 * Responsive (<=1024): stacked header, 1 slide + peek
 * -------------------------------------------------------------------- */

@media (max-width: 1024px) {
	.services__header {
		display: block;
	}

	.services__text {
		margin-top: 32px;
	}

	.services__carousel {
		margin-top: 48px;
	}

	/* One slide with the next peeking. The 4:5 floor is computed from the
	   slide's own width here, since it is viewport-derived rather than the
	   fixed 480px of the desktop track. */
	.services__slide {
		flex: 0 0 calc(100vw - 5rem);
		min-height: calc((100vw - 5rem) * 1.25);
	}

	.services__nav {
		margin-top: 32px;
	}

	/* The 64px trailing space is a desktop value; at the mobile inset it has
	   to match --container-pad, like every other scroller here. */
	.services__track {
		padding-inline-end: var(--container-pad);
	}

	/* The wide carousel sets its trailing space through --track-trailing on a
	   two-class selector, which outranks the one-class rule above no matter
	   which is written first, because a media query adds no specificity. So
	   the rule above was silently losing here and the last slide sat 64px from
	   the viewport edge instead of 16.

	   Corrected at the variable rather than by restating the padding, because
	   --track-trailing is the name the slide width calc reads too. */
	.services--wide .services__track {
		--track-trailing: var(--container-pad);
	}

	/* Stacked, the slide is two rows and both were auto, so the height the
	   slide has over its 480px floor was split between them and each grey
	   content box ended up sized by its own description: measured 203 to 240
	   at 768, a 37px spread across six boxes that sit in a row and read as one
	   set.

	   1fr on the content row instead. The media keeps its own height and the
	   content box takes the rest, so every box in the carousel is the same
	   height as every other WITHOUT a height being declared: the floor still
	   governs, and copy longer than the floor grows the slide, the track's
	   align-items: stretch carries that to its siblings, and the boxes stay
	   level. */
	.svc-wide {
		grid-template-rows: 1fr auto;
	}

	/* One redeclaration keeps the deliver box square: its width and its 1:1
	   floor both read this, so the mobile width cannot drift from the mobile
	   floor the way two separate values would. Three-across is a desktop
	   arrangement; here it returns to the site's one-plus-peek convention. */
	.services--deliver {
		--wd-box: calc(100vw - 5rem);
	}

	/* The 48px above exists to outrun the desktop sliver; there is no sliver at
	   one-per-view, so the box returns to the roomier 32px the "What we do"
	   content box also uses at this width. */
	.wd-box {
		padding: 32px;
	}

	/* Wide slide follows the same mobile slide-width convention as every other
	   scroller here; the constant peek is a wide-screen device and does not
	   survive one-slide-plus-peek at phone widths. */
	.services--wide .svc-wide {
		flex-basis: calc(100vw - 5rem);
	}

	/* MILAN'S RULING, not the client's: the two columns stack, content box
	   first and image below. Flag it if it reads wrong on the page. */
	.svc-wide {
		grid-template-columns: 1fr;
	}

	.svc-wide__content {
		padding: 32px;
		gap: 24px;
	}

	/* Stacked, the image has no column to match the height of, so it takes a
	   height of its own rather than collapsing to nothing. */
	.svc-wide__image {
		height: 15rem; /* 240px */
	}
}

