/* ===========================================================================
   FS — HOME
   The Ritovex-derived homepage. Kept separate from sections.css so the Live
   Trace hero styling there stays intact and ?fs_preview=hero keeps working.

   Tokens only — no raw hex, no raw duration, no raw easing. Layout lengths
   are literal because they are geometry, not design decisions.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   PAGE FADE-IN

   Requested explicitly, overriding the earlier "no page-load animation" rule.
   Implemented in CSS rather than JavaScript on purpose: a deferred script
   cannot fade a page in without first letting it paint, which produces a
   flash and is worse than no fade. animation-fill-mode:both holds opacity 0
   through the delay, so there is nothing to flash.

   It also earns its keep. motion.js sets the GSAP start states after first
   paint; the fade covers exactly that window, so entrance elements are never
   seen jumping to their offset positions.

   COST, stated plainly: this delays the largest contentful paint by up to
   900ms. Everything below the fold is unaffected. If LCP regresses past
   budget, this block is the first thing to remove.
   --------------------------------------------------------------------------- */

@keyframes fs-page-in {
	from { opacity: 0; }
	to   { opacity: 1; }
}

body {
	animation: fs-page-in var(--fs-duration-page-in) var(--fs-ease)
		var(--fs-delay-page-in) both;
}

/* The global reduced-motion block zeroes animation DURATION but not delay,
   which would leave the page blank for 400ms and then snap. Kill both. */
@media (prefers-reduced-motion: reduce) {
	body {
		animation: none;
		opacity: 1;
	}
}

/* ---------------------------------------------------------------------------
   PLACEHOLDER MARKER — .fs-todo

   Deliberately loud. This marks content that is not real yet: unsourced
   metrics, unnamed clients, quotes nobody said. It must be impossible to ship
   by accident, so it is a dashed rose outline plus a label, not a subtle grey.

   If you are tempted to tone this down, the correct move is to fill the
   content or delete the section.
   --------------------------------------------------------------------------- */

.fs-todo {
	position: relative;
	outline: 2px dashed var(--fs-rose);
	outline-offset: -2px;
	border-radius: var(--fs-radius-card);
	background: var(--fs-rose-soft);
}

.fs-todo__label,
.fs-todo__hint {
	display: block;
	font-family: var(--fs-font-mono);
	font-size: var(--fs-t-mono);
	color: var(--fs-rose-ink);
	text-align: center;
}

.fs-todo__hint {
	margin-block-start: var(--fs-space-xs);
	opacity: 0.75;
}

.fs-todo--inline {
	padding: var(--fs-space-xs) var(--fs-space-md);
}

.fs-todo__note {
	margin-block-start: var(--fs-space-md);
	color: var(--fs-rose-ink);
	font-family: var(--fs-font-mono);
	font-size: var(--fs-t-mono);
	text-align: center;
}

/* ---------------------------------------------------------------------------
   SHARED
   --------------------------------------------------------------------------- */

/* Centred section heads. The component is left-aligned by default because
   most sections want that; the homepage centres several of them. */
.fs-head-center .fs-section-head,
.fs-head-center {
	text-align: center;
}

.fs-head-center .fs-section-head {
	margin-inline: auto;
}

.fs-head-center .fs-marker {
	justify-content: center;
}

/* .fs-rise carries NO hidden state here. motion.js sets the start state; if
   it never runs, the element is simply visible. Content must never depend on
   a stylesheet to become readable. */

/* ---------------------------------------------------------------------------
   HERO BANNER — .fs-hero-banner
   --------------------------------------------------------------------------- */

.fs-hero-banner {
	padding-block: var(--fs-section-feature) var(--fs-section-base);
	overflow: hidden;
}

.fs-hero-banner__inner {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: var(--fs-space-xxl);
	align-items: center;
}

/* Ritovex's .banner-title is its own step above h1 - 60px at desktop,
   stepping 56/46/32 - and it is the one heading that does NOT take the 150%
   leading, dropping to 130% so a two-line headline holds together. */
.fs-hero-banner__title {
	margin-block-end: var(--fs-space-md);
	font-size: var(--fs-t-hero);
	line-height: var(--fs-lh-130);
}

/* The second clause carries the emphasis. Rose at display size is 4.17:1,
   which passes AA for text this large — it would not at body size. */
.fs-hero-banner__accent {
	display: block;
	color: var(--fs-rose);
}

.fs-hero-banner__sub {
	max-inline-size: 52ch;
	margin-block-end: var(--fs-space-lg);
	font-size: var(--fs-t-lede);
	line-height: 1.6;
	color: var(--fs-muted);
}

.fs-hero-banner__proof {
	margin-block-start: var(--fs-space-lg);
	font-family: var(--fs-font-mono);
	font-size: var(--fs-t-mono);
	color: var(--fs-muted);
}

.fs-hero-banner__media {
	position: relative;
}

.fs-hero-banner__image {
	display: grid;
	place-content: center;
	gap: var(--fs-space-xs);
	inline-size: 100%;
	aspect-ratio: 4 / 3;
	border-radius: var(--fs-radius-lg);
	object-fit: cover;
	padding: var(--fs-space-md);
}

/* The notch. Upstream this is a cut-out in the bottom-leading corner of the
   photo; drawn here with a radial mask rather than shipped as an image. */
.fs-hero-banner__notch {
	position: absolute;
	inset-block-end: 0;
	inset-inline-start: 0;
	inline-size: 96px;
	block-size: 96px;
	background: var(--fs-bg);
	border-start-end-radius: var(--fs-radius-lg);
	pointer-events: none;
}

/* The notch only reads as a deliberate cut against a real photograph. Over
   the dashed placeholder the outline continues behind it and it looks like a
   rendering fault, so it stays hidden until an image is set. */
.fs-hero-banner__media:has(.fs-todo) .fs-hero-banner__notch {
	display: none;
}

/* ---------------------------------------------------------------------------
   DESKTOP: a bigger frame, paid for out of slack that was already there.

   The frame was 597x448 inside a 1290px content width, against a type column
   measuring 508px tall - so the media column was sitting 60px SHORTER than the
   text beside it, and there was a 96px channel between them. Both are free
   space. Neither the headline, the subhead nor the buttons are touched.

   THE TEXT COLUMN IS PINNED, and this is the part that matters. At a 96px gap
   two 1fr columns each resolve to (100% - 96px) / 2, which is identical to
   calc(50% - 48px). Stating it that way means narrowing the gap CANNOT feed a
   single pixel back into the type column: the text keeps exactly the width it
   had, at every desktop width, so the headline cannot rewrap. Every pixel the
   gap gives up goes to the media column instead.

   Frame 597x448 -> 660x495. That is still under the 508px the type column
   already occupies, so the hero band does not grow and the vertically centred
   text does not shift. 48px of channel remains between text and image.
   --------------------------------------------------------------------------- */
@media (min-width: 1024px) {
	.fs-hero-banner__inner {
		grid-template-columns: calc(50% - 48px) minmax(0, 1fr);
		gap: var(--fs-space-l);
	}

	/* NOT bled into the container gutter, though the 15px is dead space. Below
	   1320px the container already fills the viewport, so spending it would put
	   the photo's rounded corner flush against the right screen edge - 2.3% more
	   width for a corner that looks like a mistake. The gap and the padding are
	   where the real space is. */

	/* The 24px inset is placeholder furniture - it holds the TODO label off the
	   dashed edge, so it stays for .fs-todo. On the real <img> it was doing two
	   things nobody asked for: shrinking the photograph inside its own frame,
	   and - because aspect-ratio applies to the BORDER box under border-box
	   sizing - leaving the CONTENT box at 549x400, a ratio of 1.372 rather than
	   1.333, so object-fit: cover was quietly trimming ~12px of height off a
	   source that is already exactly 4:3. Dropping it here lets the photo fill
	   the frame and leaves cover with nothing to crop.

	   Desktop-scoped on purpose: mobile keeps its inset exactly as it was. */
	.fs-hero-banner__image:not(.fs-todo) {
		padding: 0;
	}
}

@media (max-width: 1023px) {
	.fs-hero-banner__inner {
		grid-template-columns: minmax(0, 1fr);
		gap: var(--fs-space-xl);
	}

	.fs-hero-banner__notch {
		display: none;
	}
}

/* ---------------------------------------------------------------------------
   MARQUEE — .fs-marquee
   Two identical tracks translated by -50% loop seamlessly; a single track
   snaps visibly when it wraps. The clone is aria-hidden in the markup.
   --------------------------------------------------------------------------- */

@keyframes fs-marquee-shift {
	from { transform: translateX(0); }
	to   { transform: translateX(-50%); }
}

.fs-marquee {
	display: flex;
	inline-size: 100%;
	overflow: hidden;
	user-select: none;
	mask-image: linear-gradient(
		to right,
		transparent,
		#000 8%,
		#000 92%,
		transparent
	);
}

.fs-marquee__track {
	display: flex;
	flex: none;
	align-items: center;
	gap: var(--fs-space-xl);
	min-inline-size: 50%;
	margin: 0;
	padding: 0 var(--fs-space-lg) 0 0;
	list-style: none;
	animation: fs-marquee-shift var(--fs-duration-marquee)
		var(--fs-ease-linear) infinite;
}

.fs-marquee--reverse .fs-marquee__track {
	animation-direction: reverse;
}

.fs-marquee__item {
	flex: none;
	white-space: nowrap;
}

/* Pausing on hover is not a nicety: WCAG 2.2 SC 2.2.2 requires a mechanism to
   stop movement that starts automatically and lasts more than five seconds. */
.fs-marquee:hover .fs-marquee__track,
.fs-marquee:focus-within .fs-marquee__track {
	animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {
	.fs-marquee__track {
		animation: none;
	}

	/* Static and scrollable rather than static and truncated. */
	.fs-marquee {
		overflow-x: auto;
		mask-image: none;
	}
}

/* ---------------------------------------------------------------------------
   LOGOS
   --------------------------------------------------------------------------- */

.fs-logos__title {
	margin-block-end: var(--fs-space-lg);
	text-align: center;
	font-family: var(--fs-font-body);
	font-weight: 500;
	letter-spacing: var(--fs-track-none);
	color: var(--fs-muted);
}

.fs-logos__logo {
	font-family: var(--fs-font-display);
	font-size: var(--fs-t-h4);
	font-weight: 700;
	color: var(--fs-muted);
}

/* ---------------------------------------------------------------------------
   ABOUT — static image + stat cards that rise in as a group
   --------------------------------------------------------------------------- */

.fs-about__inner {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: var(--fs-space-xxl);
	align-items: center;
}

.fs-about__media {
	margin: 0;
}

.fs-about__frame {
	position: relative;
	overflow: hidden;
	border-radius: var(--fs-radius-lg);
}

.fs-about__image {
	display: grid;
	place-content: center;
	inline-size: 100%;
	aspect-ratio: 4 / 3;
	/* The current photograph is exactly 1280x960, so this crops nothing today.
	   It is here because the default is object-fit: fill - swap in a photo that
	   is not 4:3 and it would STRETCH rather than crop, which is the one failure
	   mode nobody notices in review. cover fails safe. */
	object-fit: cover;
}

.fs-about__stats {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--fs-space-md);
	margin-block: var(--fs-space-xl);
}

.fs-stat {
	padding: var(--fs-space-md);
	background: var(--fs-bg-alt);
	border-radius: var(--fs-radius-card);
}

/* One outline icon per card, in the register already set by the button arrow,
   the nav chevron and the services arrow: inline SVG, currentColor, 1.5 stroke,
   no fill. Colour comes from --fs-rose-text rather than --fs-rose because that
   token already resolves to the darker rose on light surfaces and the lifted
   rose on dark ones, so these stay legible if the section is ever moved onto a
   dark band. */
.fs-stat__icon {
	display: block;
	margin-block-end: var(--fs-space-xs);
	color: var(--fs-rose-text);
}

.fs-stat__value {
	margin: 0;
	font-family: var(--fs-font-display);
	font-size: var(--fs-t-h2);
	font-weight: var(--fs-weight-bold);
	line-height: 1.1;
	letter-spacing: var(--fs-track-lg);
	font-variant-numeric: tabular-nums;
}

.fs-stat__label {
	margin: var(--fs-space-3xs) 0 0;
	font-size: var(--fs-t-small);
	color: var(--fs-muted);
}

@media (max-width: 1023px) {
	.fs-about__inner {
		grid-template-columns: minmax(0, 1fr);
		gap: var(--fs-space-xl);
	}
}

@media (max-width: 479px) {
	.fs-about__stats {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* ---------------------------------------------------------------------------
   SERVICES — numbered rows, dark
   Hover and focus states are pure CSS. :focus-within is included so the row
   responds to a keyboard, which the upstream mouse-only version did not.
   --------------------------------------------------------------------------- */

.fs-services__list {
	margin: 0;
	padding: 0;
	list-style: none;
	border-block-start: 1px solid var(--fs-border);
}

.fs-services__row {
	border-block-end: 1px solid var(--fs-border);
}

.fs-services__link {
	display: grid;
	grid-template-columns: auto minmax(0, 4fr) minmax(0, 6fr) auto;
	gap: var(--fs-space-lg);
	align-items: center;
	padding-block: var(--fs-space-lg);
	color: var(--fs-ink);
	text-decoration: none;
	transition: padding-inline var(--fs-duration-swap) var(--fs-ease);
}

.fs-services__link:hover,
.fs-services__link:focus-visible {
	padding-inline-start: var(--fs-space-md);
	color: var(--fs-ink);
}

.fs-services__number {
	font-family: var(--fs-font-mono);
	font-size: var(--fs-t-mono);
	color: var(--fs-muted);
	transition: color var(--fs-duration-swap) var(--fs-ease);
}

.fs-services__link:hover .fs-services__number {
	color: var(--fs-rose-lift);
}

.fs-services__name {
	font-family: var(--fs-font-display);
	font-size: var(--fs-t-h2);
	font-weight: var(--fs-weight-bold);
	line-height: 1.1;
	letter-spacing: var(--fs-track-lg);
}

.fs-services__desc {
	color: var(--fs-muted);
}

.fs-services__arrow {
	display: grid;
	place-content: center;
	inline-size: 48px;
	block-size: 48px;
	border: 1px solid var(--fs-border-strong);
	border-radius: var(--fs-radius-pill);
	transition:
		background var(--fs-duration-swap) var(--fs-ease),
		border-color var(--fs-duration-swap) var(--fs-ease),
		color var(--fs-duration-swap) var(--fs-ease),
		transform var(--fs-duration-swap) var(--fs-ease);
}

.fs-services__link:hover .fs-services__arrow,
.fs-services__link:focus-visible .fs-services__arrow {
	background: var(--fs-rose);
	border-color: var(--fs-rose);
	color: var(--fs-white);
	transform: rotate(45deg);
}

@media (max-width: 1023px) {
	.fs-services__link {
		grid-template-columns: auto minmax(0, 1fr) auto;
		gap: var(--fs-space-md);
	}

	.fs-services__desc {
		grid-column: 2 / -1;
	}
}

/* ---------------------------------------------------------------------------
   RAIL — pinned horizontal portfolio
   --------------------------------------------------------------------------- */

.fs-rail {
	padding-block: var(--fs-section-base);
	overflow: hidden;
}

.fs-rail__head {
	margin-block-end: var(--fs-space-xl);
}

.fs-rail__track {
	display: flex;
	gap: var(--fs-space-md);
	padding-inline: var(--fs-gutter);
	will-change: transform;
}

.fs-rail__card {
	flex: none;
	inline-size: min(900px, 72vw);
}

.fs-rail__thumb {
	display: grid;
	place-content: center;
	aspect-ratio: 16 / 10;
	border-radius: var(--fs-radius-lg);
}

.fs-rail__meta {
	margin-block-start: var(--fs-space-md);
	padding: var(--fs-space-md);
}

.fs-rail__client {
	margin: 0 0 var(--fs-space-3xs);
	font-size: var(--fs-t-h3);
}

.fs-rail__result {
	margin: 0;
	color: var(--fs-muted);
}

/* No pin below 1024px — a pinned section on a phone hijacks the only scroll
   axis the visitor has. Native overflow with snap points is better on touch. */
@media (max-width: 1023px) {
	.fs-rail__track {
		overflow-x: auto;
		scroll-snap-type: x mandatory;
		padding-block-end: var(--fs-space-sm);
		-webkit-overflow-scrolling: touch;
	}

	.fs-rail__card {
		inline-size: 86vw;
		scroll-snap-align: start;
	}
}

@media (prefers-reduced-motion: reduce) {
	.fs-rail__track {
		overflow-x: auto;
		scroll-snap-type: x mandatory;
	}

	.fs-rail__card {
		scroll-snap-align: start;
	}
}

/* ---------------------------------------------------------------------------
   STACK — sticky heading, cards dropping in
   The stick is CSS; only the drop is scripted. With motion.js absent the
   heading still pins and the cards sit in a normal column.
   --------------------------------------------------------------------------- */

.fs-stack__inner {
	display: grid;
	grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
	gap: var(--fs-space-xxl);
	align-items: start;
}

.fs-stack__aside {
	position: sticky;
	inset-block-start: calc(var(--fs-sticky-offset) + var(--fs-space-md));
}

.fs-stack__list {
	display: grid;
	gap: var(--fs-space-md);
	margin: 0;
	padding: 0;
	list-style: none;
}

.fs-stack__card {
	position: relative;
	padding: var(--fs-space-lg);
}

.fs-stack__number {
	color: var(--fs-rose-lift);
}

@media (max-width: 1023px) {
	.fs-stack__inner {
		grid-template-columns: minmax(0, 1fr);
		gap: var(--fs-space-xl);
	}

	.fs-stack__aside {
		position: static;
	}
}

/* ---------------------------------------------------------------------------
   TOOLS
   --------------------------------------------------------------------------- */

.fs-tools__title {
	margin-block-end: var(--fs-space-xl);
}

.fs-tools .fs-marquee + .fs-marquee {
	margin-block-start: var(--fs-space-md);
}

.fs-tools__tag {
	padding: var(--fs-space-2xs) var(--fs-space-md);
	border: 1px solid var(--fs-border);
	border-radius: var(--fs-radius-pill);
	font-size: var(--fs-t-small);
	color: var(--fs-ink);
}

/* ---------------------------------------------------------------------------
   QUOTES
   --------------------------------------------------------------------------- */

.fs-quotes__grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
	gap: var(--fs-space-md);
}

/* Shadow-led rather than border-led, which is a DELIBERATE departure from the
   card rule in components.css ("flat at rest - depth is a hairline border, and
   elevation happens only on interaction"). The reference design for this
   section is shadow-led, so the border is made transparent rather than removed
   - keeping the 1px means turning it back on shifts nothing. Held at
   --fs-shadow-sm so it reads as a lift, not the heavier elevation the system
   reserves for hover. */
.fs-quotes__card {
	gap: var(--fs-space-md);
	padding: var(--fs-space-lg);
	border-color: transparent;
	box-shadow: var(--fs-shadow-sm);
}

.fs-quotes__text {
	margin: 0;
	color: var(--fs-muted);
	line-height: 1.6;
}

/* Typographic quote marks drawn in CSS, so the characters stay out of the
   translatable string and out of the accessibility tree - a screen reader
   already announces a blockquote. Rose is the one brand accent on the card;
   the reference put a purple-to-orange gradient on the role line, which is in
   no part of this palette. */
.fs-quotes__text p::before,
.fs-quotes__text p::after {
	color: var(--fs-rose-ink);
}

.fs-quotes__text p::before {
	content: "\201C";
}

.fs-quotes__text p::after {
	content: "\201D";
}

/* The caption is a row now: avatar, then name over role. */
.fs-quotes__by {
	display: flex;
	align-items: center;
	gap: var(--fs-space-2xs);
}

.fs-quotes__meta {
	display: flex;
	flex-direction: column;
	gap: var(--fs-space-3xs);
	/* Long roles must be allowed to wrap rather than force the card wider. */
	min-inline-size: 0;
}

/* Circular, per the reference. One box for all three cases - portrait,
   organisation logo, or monogram - so the six cards keep a single shape even
   though only half of them have imagery. */
.fs-quotes__avatar {
	flex: none;
	inline-size: 48px;
	block-size: 48px;
	border-radius: var(--fs-radius-pill);
	background: var(--fs-bg-alt);
}

/* A face fills the circle. Safe to crop: the supplied headshot is already 1:1
   and centred, so cover removes nothing. */
.fs-quotes__avatar--face {
	object-fit: cover;
}

/* A WORDMARK MUST NOT be cropped to a circle. Both client logos are lettering
   that runs close to the edge of a square canvas, so a circular crop would eat
   the corners of the type. contain plus padding sits the whole logotype inside
   the circle instead, on its own white field with a hairline - without which a
   white-background logo has no edge against a white card. */
.fs-quotes__avatar--logo {
	object-fit: contain;
	padding: 5px;
	background: var(--fs-bg);
	border: 1px solid var(--fs-border);
}

.fs-quotes__avatar--initials {
	display: grid;
	place-content: center;
	font-family: var(--fs-font-mono);
	font-size: var(--fs-t-small);
	letter-spacing: 0.04em;
	color: var(--fs-muted);
	border: 1px solid var(--fs-border);
}

/* The reference sets the name in a 20px serif. There is no serif in this type
   system and adding one would mean a fourth family, so it takes the display
   face at the matching step. */
.fs-quotes__name {
	font-family: var(--fs-font-display);
	font-size: var(--fs-t-h6);
	font-weight: var(--fs-weight-medium);
	line-height: 1.2;
}

.fs-quotes__role {
	color: var(--fs-muted);
}

/* ---------------------------------------------------------------------------
   CTA
   --------------------------------------------------------------------------- */

.fs-cta__panel {
	padding: var(--fs-section-compact) var(--fs-gutter);
	border-radius: var(--fs-radius-lg);
	text-align: center;
}

.fs-cta__title {
	max-inline-size: 18ch;
	margin-inline: auto;
}

.fs-cta__lede {
	margin-inline: auto;
	margin-block-end: var(--fs-space-lg);
}

.fs-cta__actions {
	justify-content: center;
}

.fs-cta .fs-marker {
	justify-content: center;
}

/* ---------------------------------------------------------------------------
   JOURNAL
   --------------------------------------------------------------------------- */

.fs-journal__grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
	gap: var(--fs-space-md);
}

.fs-journal__media {
	margin: calc(var(--fs-space-md) * -1) calc(var(--fs-space-md) * -1)
		var(--fs-space-sm);
}

.fs-journal__media img {
	inline-size: 100%;
	aspect-ratio: 16 / 10;
	object-fit: cover;
	border-start-start-radius: var(--fs-radius-card);
	border-start-end-radius: var(--fs-radius-card);
}

.fs-journal__date {
	margin: 0;
	color: var(--fs-muted);
}

.fs-journal__card .fs-card__title a {
	color: inherit;
	text-decoration: none;
}

.fs-journal__card:hover .fs-card__title a {
	color: var(--fs-rose-ink);
}
