/* =============================================================================
   Labs page — /labs/.

   Ported from the "Refference" Figma file (jreoVvSg4lM9TPJB5nRi09): desktop frame
   1:673 "LABS - Desktop" (1440x5090), mobile frame 1:196. Copy, colours, type and
   the product mockups are from that file's design context.

   Mirrors pages/our-work.css (centred reading band, orange eyebrows, a final CTA
   band the contact panel expands out of) and the home page's service carousel
   (the product list is a horizontal snap-scroller on mobile).

   Still approximated (re-measure if it matters): the hero and CTA background
   washes are linear-gradients standing in for the frame's image fills, and the
   Mr. Orange card shows the avatar alone rather than the avatar + greeting bubble
   the frame draws.
   ========================================================================== */

.page-labs {
	font: var(--body-l);
	color: var(--color-carbon);
	background: var(--color-cream);
	/* Opt out of the fixed-header offset (like the home page): the gradient wash
	   draws behind the header, up to the very top of the page. The hero's own top
	   padding keeps its content clear of the bar. */
	padding-top: 0;
}

.page-labs .site-footer {
	margin-top: 0;
}

/* --- Header ------------------------------------------------------------- */
/* The dark theme of the shared header (frame 1:1623) — `headerTheme: dark` in
   the front matter; the theme itself is in base.css. */

/* --- Footer (frame 1:1336) ------------------------------------------------ */
/* The dark theme of the shared footer — `footerTheme: dark` in the front
   matter; its colours, mark and orange social icons live in base.css. What
   follows is this frame's own layout. */

/* The cream social row is mobile-only; on desktop the footer keeps its own. */
.page-labs .labs-social {
	display: none;
}

/* Layout to match frame 1:1336: tighter column type and no divider. The
   offices-then-icons row at the bottom right is the shared footer's own. */
.page-labs .site-footer__divider {
	display: none;
}

.page-labs .site-footer__cols {
	gap: 51px;
}

.page-labs .site-footer__col {
	gap: 4px;
}

.page-labs .site-footer__col h2 {
	margin-bottom: 4px;
	font-size: var(--text-md);
	letter-spacing: var(--ls-label);
}

.page-labs .site-footer__col a {
	font-size: var(--text-sm);
}

/* With the divider gone, the row needs its own space above it (frame). */
.page-labs .site-footer__bottom {
	margin-top: 84px;
}

.page-labs .site-footer__locations {
	gap: 43px;
}

.page-labs .site-footer__city {
	font-size: var(--text-md);
	letter-spacing: var(--ls-label);
}

/* Content band. The product cards measure 1030px in the frame (x 204..1234 of the
   1440 band); the band is that plus a small min-gutter so it never touches the
   edge at in-between widths. Centring supplies the large desktop margin. */
.page-labs {
	--labs-band: 1030px;
}

.page-labs .labs-built,
.page-labs .labs-open,
.page-labs .labs-notes {
	max-width: calc(var(--labs-band) + 2 * var(--page-gutter-sm));
	margin-inline: auto;
	padding-inline: var(--page-gutter-sm);
}

/* Section eyebrows: Label/Intermedio, centred (same as Our Work). */
.page-labs .labs-built__eyebrow,
.page-labs .labs-open__eyebrow {
	margin-block: 0;
	font: var(--label-int);
	letter-spacing: var(--ls-label);
	text-align: center;
}

/* "[THINGS WE'VE BUILT]" is orange; "[OPEN SOURCE]" is black (frames 1:1578 /
   1:1474, and their mobile twins). */
.page-labs .labs-built__eyebrow {
	color: var(--color-brand);
}

.page-labs .labs-open__eyebrow {
	color: var(--color-black);
}

/* --- Background zones (frame 9:27) ---------------------------------------- */
/* Top zone: the frame's gradient wash behind the hero and "things we've built",
   from the very top of the page. The wash itself is --gradient-labs-wash in
   tokens.css, with the other mesh fills. */
.page-labs .labs-top {
	position: relative;
	overflow: hidden;
	background-color: var(--color-black);
	background-image: var(--gradient-labs-wash);
}

/* Lower zone: a light D8D4C8 rounded-square grid on cream (F0EEE9), full-bleed
   behind the open-source card, the notes and the final CTA. The grid does not
   start at the top of the zone — it begins ~125px below the top of the open-source
   card (frame 9:27) — so it is a ::before offset from the top rather than a plain
   background. Its cream base is the section's own background above that line. */
.page-labs .labs-bottom {
	position: relative;
	/* flow-root establishes a block formatting context so the inline contact
	   modal's margin-bottom stays inside this box instead of collapsing out
	   through main; that keeps the grid ::before (bottom: 0) reaching all the
	   way down to the footer rather than stopping at the CTA band. */
	display: flow-root;
	background-color: var(--color-cream);
}

.page-labs .labs-bottom::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	/* The open-source card sits ~228px into this zone; the grid starts 125px below
	   its top edge (frame 9:27). */
	top: 353px;
	bottom: 0;
	background: url(../../media/labs/labs-grid-tile.svg) 0 0 / 68px 68px repeat;
	pointer-events: none;
}

/* Content sits above the grid. Not the contact panel: this rule is as
   specific as components/contact-modal.css's `position: fixed` overlay and loads
   after it, so it pinned the "dialog" into the page flow at the bottom of Labs —
   the header CTA and /#contact scrolled there instead of opening a dialog. The
   panel gets the same stacking only in its inline role, on desktop. :where()
   keeps this at its old specificity for every other child. */
.page-labs .labs-bottom > :where(:not(.contact-modal)) {
	position: relative;
	z-index: 1;
}

@media (min-width: 768px) {
	.page-labs .labs-bottom > .contact-modal:not(.is-overlay) {
		position: relative;
		z-index: 1;
	}
}

/* --- 1. Hero — horizontal split (frame 1:1579, 942x279) ------------------- */
.page-labs .labs-hero {
	position: relative;
	z-index: 1;
	/* 294 = 180px design gap below the fixed header's bottom edge (~114px). */
	padding: 294px 0 110px;
}

.page-labs .labs-hero__inner {
	display: flex;
	align-items: center;
	gap: 66px;
	max-width: 942px;
	margin-inline: auto;
	padding-inline: var(--page-gutter-sm);
}

/* Left: mascot on a lattice patch (frame 1:1580 group, 305x249). */
.page-labs .labs-hero__art {
	position: relative;
	flex: none;
	width: 305px;
	height: 249px;
}

.page-labs .labs-hero__grid {
	position: absolute;
	left: 56px;
	top: 0;
	width: 249px;
	height: 249px;
}

/* --- Lattice motion ------------------------------------------------------- */
/* hero-grid.svg draws all nine squares as ONE filled path inside an <img>, so
   nothing in it can be lit on its own. These nine cells lie exactly on top of
   its lines instead: the numbers are read off the path — a 79.417 hole on an
   82.103 pitch with a 2.686 band — so a cell spans band+hole+band and its
   border lands on the pixels the SVG already paints. Adjacent cells share a
   band on purpose; that is one lattice line, not two.

   Kept as one set of px and scaled as a whole at the mobile size, rather than
   restating the geometry per breakpoint. */
.page-labs .labs-hero__glow {
	position: absolute;
	left: 56px;
	top: 0;
	width: 249px;
	height: 249px;
	transform-origin: 0 0;
	pointer-events: none;
}

.page-labs .labs-hero__cell {
	position: absolute;
	left: calc(var(--col) * 82.103px);
	top: calc(var(--row) * 82.103px);
	width: 84.789px;
	height: 84.789px;
	border: 2.686px solid var(--color-cream);
	border-radius: 16.564px;
	opacity: 0;
	/* No border-width here on purpose: the stroke stays 2.686px lit or not, so
	   only the opacity and the glow move. */
	transition:
		opacity 0.75s ease-out,
		filter 0.75s ease-out;
}

/* Lit, a cell matches the reference lattice on the home page's team collage
   (media/home/team-grid-tile.svg): #F0EEE9 at FULL opacity. Hence 1 and not the
   0.5 this started at — the base SVG is already at 0.5, so half on top only
   reached 0.75 and read washed out beside the home grid. Solid cream covers it.

   drop-shadow rather than box-shadow: it follows the border's own alpha, so the
   glow hugs the rounded stroke instead of haloing a square box around it. */
.page-labs .labs-hero__cell.is-lit {
	opacity: 1;
	/* Two shadows, not one: a tight bright core plus a wider soft bloom. A single
	   drop-shadow reads as a flat halo; stacking them is what gives it the flash. */
	filter:
		drop-shadow(0 0 4px color-mix(in srgb, var(--color-cream) 85%, transparent))
		drop-shadow(0 0 14px color-mix(in srgb, var(--color-cream) 45%, transparent));
	transition-duration: 0.45s;
}

/* Decorative, and the lattice is legible without it. */
@media (prefers-reduced-motion: reduce) {
	.page-labs .labs-hero__glow {
		display: none;
	}
}

/* The mascot is the ski-goggles cut (frame 1:1583); the 5.48deg tilt is baked
   into the exported PNG, so no CSS rotation here. */
.page-labs .labs-hero__mascot {
	position: absolute;
	left: 0;
	top: 62px;
	width: 138px;
	height: auto;
}

/* Right: title over two lead paragraphs, left-aligned, cream (frame 1:1620). */
.page-labs .labs-hero__copy {
	flex: none;
	width: 571px;
	display: flex;
	flex-direction: column;
	gap: 12px;
}

.page-labs .labs-hero__title {
	margin: 0;
	max-width: 527px;
	font: 700 var(--text-display-l) / 1.05 var(--font-sans);
	letter-spacing: var(--ls-display);
	color: var(--color-cream);
}

.page-labs .labs-hero__leads {
	display: flex;
	flex-direction: column;
	gap: 22px;
}

.page-labs .labs-hero__leads p {
	margin: 0;
	font: 400 var(--text-lg) / 1.1 var(--font-sans);
	color: var(--color-cream);
}

.page-labs .labs-hero__leads strong {
	font-weight: 700;
}

/* --- 2. Things we've built ----------------------------------------------- */
/* Sits on the dark gradient wash (inside .labs-top), so its intro copy is cream
   and it keeps some dark space below the cards before the wash turns cream. */
.page-labs .labs-built {
	/* Frame 1:1489: 270px below the hero, 117px above the open-source section.
	   Keep the inline gutter so the 1030px band is not widened to the full
	   max-width (the shorthand would otherwise zero padding-inline). */
	padding: 158px var(--page-gutter-sm) 60px;
}

.page-labs .labs-built__intro {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 18px;
	text-align: center;
}

.page-labs .labs-built__lead {
	margin: 0;
	/* Frame 1:1577: the intro copy is boxed at 483px. */
	max-width: 483px;
	font: 400 var(--text-md) / 1.5 var(--font-sans);
	color: var(--color-cream-dimmer);
}

.page-labs .labs-built__list {
	margin-top: 44px;
	display: flex;
	flex-direction: column;
	/* 33px between card rectangles (frame 1:1489). */
	gap: 33px;
}

/* Product cards: a cream→sand vertical gradient tile, orange title, dark body,
   the product mockup on the right (frame 1:1490 etc.: from #f0eee9 to #d8d4c8,
   radius 30, drop-shadow 0 4px 2px rgba(0,0,0,0.2), no border). */
.page-labs .labs-card {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 40px;
	padding: 44px 69px;
	background: linear-gradient(
		180deg,
		var(--color-cream),
		var(--color-sand-pale)
	);
	border-radius: 30px;
	box-shadow: var(--shadow-card);
}

.page-labs .labs-card__body {
	flex: 1;
	min-width: 0;
	max-width: 488px;
	display: flex;
	flex-direction: column;
}

.page-labs .labs-card__title {
	margin: 0 0 4px;
	font: 700 var(--text-display-s) / 1 var(--font-sans);
	letter-spacing: var(--ls-display);
	color: var(--color-brand);
}

.page-labs .labs-card__category {
	/* 8px category→tags in the frame (1:1489). */
	margin: 0 0 8px;
	/* NOT an eyebrow, despite sitting where one would. Measured off the frame's
	   own export: the O's are circular and the line is untracked, so this is
	   Plus Jakarta Sans Bold, not Eurostile — the tags below it ARE Eurostile and
	   tracked, which is what makes the two read differently. Cap height is 12 in a
	   1:1 export, and PJS's cap ratio puts that at 16; the frame's text box (node
	   1:1557) is 22 tall, which is 16 x 1.375. */
	font: 700 var(--text-base) / 1.375 var(--font-sans);
	letter-spacing: normal;
	text-transform: uppercase;
	color: var(--color-carbon);
}

.page-labs .labs-card__tags {
	/* 15px tags→description. */
	margin: 0 0 15px;
	font: var(--label-m);
	letter-spacing: var(--ls-label);
	color: var(--color-carbon-soft);
}

.page-labs .labs-card__lead {
	/* 31px description→CTA (DrillRoom/Kezmo). */
	margin: 0 0 31px;
	font: 400 var(--text-base) / 1.35 var(--font-sans);
	/* Body copy on the cream card: a step lighter than the near-black heading and
	   labels above it (a designer comment on frame 1614:976), which is what the
	   "card body copy on cream" token is for. */
	color: var(--color-carbon-muted);
}

/* The product-card button (frame 1:1560 / 1:539): a dark outline pill on the
   cream card — the shared Secondary / Dark variant (.btn--outline), set in
   Label/M (Eurostile 15px, 0.15em) rather than the variant's 12px. Colours come
   from the variant, including its hover: this used to recolour
   .btn--outline-muted to black here, which also won on :hover and left black
   text on the black hover fill. */
.page-labs .labs-card__cta {
	align-self: flex-start;
	padding-inline: var(--space-8);
	font-size: var(--text-md);
	letter-spacing: var(--ls-label);
}

/* The mockup renders at its own measured size (per-card --media-w), transparent
   PNG device art, so no crop and no radius. */
.page-labs .labs-card__media {
	flex: none;
	width: var(--media-w, auto);
}

/* Direct child only, so the nested chatbot avatar below keeps its own size. */
.page-labs .labs-card__media > img {
	display: block;
	width: 100%;
	height: auto;
	object-fit: contain;
}

/* Mr. Orange's card: the chatbot mark with a greeting bubble below it (frame
   1:1518). The avatar sits top-right, the sand bubble spans the width below, and
   the avatar's foot overlaps the bubble's top-right. */
.page-labs .labs-card--chatbot .labs-card__media {
	width: 276px;
}

.page-labs .labs-chatbot {
	position: relative;
	width: 276px;
	height: 194px;
}

.page-labs .labs-chatbot__avatar {
	position: absolute;
	left: 73px;
	top: 0;
	width: 131px;
	height: auto;
	z-index: 1;
}

.page-labs .labs-chatbot__bubble {
	position: absolute;
	left: 0;
	top: 91px;
	width: 276px;
	min-height: 102px;
	margin: 0;
	padding: 13px 21px 17px 42px;
	display: flex;
	align-items: center;
	background: var(--color-sand-soft);
	border-radius: 15px;
	font: 400 var(--text-lg) / 1.1 var(--font-sans);
	color: var(--color-black);
}

/* Carousel controls — mobile only (labs.js fills the dots). */
.page-labs .labs-built__nav {
	display: none;
}

/* [hidden] needs reinforcing: the mobile block below sets display:flex here,
   which would beat the UA stylesheet's [hidden]. labs.js sets it when only one
   product is visible. */
.page-labs .labs-built__nav[hidden] {
	display: none;
}

/* --- 3. Open source: build to share -------------------------------------- */
.page-labs .labs-open {
	/* 117px below the products (frame 1:1472); bottom gap comes from .labs-notes.
	   Inline gutter kept so the 1030px band holds (see .labs-built). */
	padding: 57px var(--page-gutter-sm) 0;
	text-align: center;
}

.page-labs .labs-open__title {
	margin: 8px 0 20px;
	font: 700 var(--text-display-s) / 1 var(--font-sans);
	letter-spacing: var(--ls-display);
	/* Orange at every breakpoint (frame 1:1476 / mobile 1:506). */
	color: var(--color-brand);
}

.page-labs .labs-open__lead {
	margin: 0 auto 40px;
	max-width: 560px;
	font: 400 var(--text-md) / 1.5 var(--font-sans);
	color: var(--color-carbon-soft);
}

.page-labs .labs-open__card {
	display: flex;
	align-items: center;
	gap: 56px;
	padding: 36px 61px;
	text-align: left;
	background: var(--color-black);
	border-radius: 30px;
	box-shadow: var(--shadow-card);
}

.page-labs .labs-open__body {
	flex: 1;
	min-width: 0;
	max-width: 470px;
	display: flex;
	flex-direction: column;
}

.page-labs .labs-open__name {
	margin: 0 0 11px;
	font: 700 var(--text-display-s) / 1.05 var(--font-sans);
	letter-spacing: var(--ls-display);
	color: var(--color-cream);
}

.page-labs .labs-open__tags {
	margin: 0 0 11px;
	font: var(--label-m);
	letter-spacing: var(--ls-label);
	color: var(--color-sand-soft);
}

.page-labs .labs-open__desc {
	margin: 0 0 10px;
	max-width: 404px;
	font: 400 var(--text-base) / 1.15 var(--font-sans);
	color: var(--color-cream);
}

/* The button aligns to the end of the text column (frame 1:1481 is items-end), so
   it sits toward the centre of the card rather than under the copy's left edge. */
.page-labs .labs-open__cta {
	align-self: flex-end;
}

.page-labs .labs-open__media {
	flex: none;
	width: 449px;
}

.page-labs .labs-open__media img {
	display: block;
	width: 100%;
	height: auto;
	object-fit: contain;
}

/* --- 4. Notes from the blog ---------------------------------------------- */
/* Frame 1:1390. The head places the spinning "BLOG" badge at the left and the
   title right-aligned beside it; the cards are components/post-card.css, in
   their black variant. */
.page-labs .labs-notes {
	/* 128px below the open-source card; 171px above the CTA (frame 1:1390).
	   Inline gutter kept so the band holds (see .labs-built). */
	padding: 128px var(--page-gutter-sm) 0;
	/* The notes band is wider than the 1030 product/open band: the frame draws
	   three 348px cards (1:1390 is 1126 wide), so this holds a 1088px content
	   width — 3×348 + 2×22 gap — overriding the shared band above. */
	max-width: calc(1088px + 2 * var(--page-gutter-sm));
}

/* The title sits 27px above the cards (frame 1:1390: title top 3775.6, 64 tall,
   first card top 3867.0). */
.page-labs .labs-notes__head {
	position: relative;
	z-index: 1;
	margin-bottom: 27px;
}

/* The badge is pinned to the FIRST CARD's top-left corner, not to the title:
   frame 1:1390 draws it 36px left of the card's edge and 73px above its top,
   so it overlaps the corner ("should be fixed to the corner of the first blog
   card", staging review 2026-09-24). Anchoring it by `bottom` puts it relative
   to where the grid starts, so a title that wraps to two lines moves the badge
   with the cards instead of leaving it behind: -(27 gap + 134 - 73) = -88.
   The 36px overhang is only taken where the page has that much margin: the
   content's left edge is max(gutter, (100vw - 1088) / 2) from the viewport,
   so on a window just above the 980 cut the badge stops at the screen edge
   instead of pushing the page sideways. */
.page-labs .labs-notes__badge {
	position: absolute;
	left: max(-36px, min(calc(-1 * var(--page-gutter-sm)), calc((1088px - 100vw) / 2)));
	bottom: -88px;
	width: 134px;
	height: auto;
}

/* Display/S, right-aligned, "Labs." in orange. The left padding keeps the text
   clear of the badge's column (it reaches at most 134 - 20 = 114px into the
   content): at 1440 the title is 872 wide in a 1088 band, so nothing moves;
   narrower, the title wraps rather than running under the badge. */
.page-labs .labs-notes__title {
	margin: 0;
	padding-left: 116px;
	text-align: right;
	font: 700 var(--text-display-s) / 1.1 var(--font-sans);
	letter-spacing: var(--ls-display);
	color: var(--color-carbon);
}

.page-labs .labs-notes__title span {
	color: var(--color-brand);
}

.page-labs .labs-notes__grid {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 22px;
	align-items: stretch;
}

.page-labs .labs-notes__actions {
	margin-top: 51px;
	text-align: center;
}

/* Cream-filled pill with a black ring (frame 1:1410). */
.page-labs .labs-notes__cta {
	border: 1.5px solid var(--color-black);
}

/* Hovers like "Send us your resume" on Careers: the pill fills black and the
   label turns orange. That button is .btn--outline and gets this from the
   variant; this one is .btn--solid-cream with a black border added above, and
   solid-cream has no hover at all — so nothing happened here. Borrowing the
   hover rather than the variant on purpose: .btn--outline also carries its own
   12px/0.14em type, which would resize this button (it renders 13px/0.15em). */
.page-labs .labs-notes__cta:hover {
	background: var(--color-black);
	border-color: var(--color-black);
	color: var(--color-brand);
}

/* The BLOG badge spins — a wind-up every 2s, same motion as the home page's mark.
   Its keyframes live on the home page's CSS, which this page does not load, so
   they are redeclared here. */
.page-labs .labs-notes__badge {
	animation: labs-badge-spin 2s linear infinite;
	transform-origin: 50% 50%;
}

@keyframes labs-badge-spin {
	0% {
		transform: rotate(-360deg);
		animation-timing-function: cubic-bezier(0.5, 0, 0.1, 1);
	}
	38.282% {
		transform: rotate(0deg);
	}
	100% {
		transform: rotate(0deg);
	}
}

@media (prefers-reduced-motion: reduce) {
	.page-labs .labs-notes__badge {
		animation: none;
	}
}

/* --- 5. Final CTA -------------------------------------------------------- */
/* The band is components/final-cta.css; this page owns only the space above
   it — 171px below the notes (frame 1:1376). Bottom 0: the contact panel
   follows and supplies the gap to the footer. */
.page-labs .final-cta {
	padding-top: 171px;
}

/* =============================================================================
   Responsive. The mobile frame (1:196) turns the product list into a horizontal
   swipe carousel with dots; everything else stacks.
   ========================================================================== */

@media (max-width: 980px) {
	/* Hero stacks and centres below the split's minimum width. */
	.page-labs .labs-hero {
		padding: 120px 0 80px;
	}
	.page-labs .labs-hero__inner {
		flex-direction: column;
		align-items: center;
		gap: 28px;
		text-align: center;
	}
	.page-labs .labs-hero__copy {
		width: 100%;
		max-width: 560px;
		align-items: center;
	}
	.page-labs .labs-hero__title {
		max-width: none;
	}

	/* Image on top, copy below (frame 1:508 stacks media then text). */
	.page-labs .labs-open__card {
		flex-direction: column-reverse;
		align-items: flex-start;
		gap: 32px;
	}

	.page-labs .labs-open__body,
	.page-labs .labs-open__media {
		width: 100%;
		max-width: none;
	}

	/* Stacked: the button returns to the left under the copy. */
	.page-labs .labs-open__cta {
		align-self: flex-start;
	}

	/* Stacked head: the badge centres above a centred title, rather than the
	   desktop badge-left / title-right split which has no room to breathe here. */
	.page-labs .labs-notes__head {
		margin-bottom: 40px;
		text-align: center;
	}
	.page-labs .labs-notes__badge {
		position: static;
		display: block;
		width: 104px;
		margin: 0 auto 16px;
	}
	.page-labs .labs-notes__title {
		padding-left: 0;
		text-align: center;
	}

	.page-labs .labs-notes__grid {
		grid-template-columns: 1fr;
		max-width: 420px;
		margin-inline: auto;
	}
}

/* Tablet only: the stacked open-source card keeps its CTA left-aligned, 32px
   below the copy. */
@media (min-width: 768px) and (max-width: 980px) {
	.page-labs .labs-open__desc {
		margin-bottom: 0;
	}
	.page-labs .labs-open__cta {
		align-self: flex-start;
		margin-top: 32px;
	}
}

/* --- Scroll-in entrance (components/reveal.css) ---------------------------- */
/* Both the list and each card carry data-reveal="fade-up", and the breakpoint
   picks one. On desktop the cards are a tall vertical stack, so each fades up as
   it is reached — one reveal on the whole list waited for most of a screen of
   scrolling and then moved five cards at once. On mobile the list is a
   horizontal carousel, where off-screen cards would stay hidden until swiped
   in, so the list reveals as one. The neutralising selectors carry .page-labs
   to outrank reveal.css. */
@media (min-width: 768px) {
	html.js:not(.reduced-motion) .page-labs .labs-built__list[data-reveal] {
		opacity: 1;
		transform: none;
	}
}

@media (max-width: 767px) {
	html.js:not(.reduced-motion) .page-labs .labs-card[data-reveal] {
		opacity: 1;
		transform: none;
	}
}

@media (max-width: 767px) {
	/* ---- Type: the mobile frame (1:196) steps every size down ------------- */
	/* Hero title 48 -> 32, lead 20 -> 14. */
	.page-labs .labs-hero__title {
		font: 700 var(--text-display-s) / 1 var(--font-sans);
		letter-spacing: var(--ls-display);
	}
	.page-labs .labs-hero__leads p {
		font-size: var(--text-sm);
	}
	/* Mobile section rhythm (frame 1:196): far tighter than desktop.
	   header->hero 32, hero->things 41, things->open 67, open->notes 76,
	   notes->CTA 137. */
	.page-labs .labs-hero {
		padding: 111px 0 21px;
	}
	.page-labs .labs-built {
		/* 25px from the swipe hint down to the cream zone. */
		padding: 20px var(--page-gutter-sm) 25px;
	}
	.page-labs .labs-notes {
		padding-top: 76px;
	}
	/* 90px from the "Explore our blog" button to the band. */
	.page-labs .final-cta {
		padding-top: 90px;
	}

	/* Social row on the grid, ~50px below the CTA band and above the dark footer
	   (frame 1:407): sand-coloured icons, masked so the black source SVGs take the
	   exact colour. The footer's own social row is hidden here to avoid a double. */
	.page-labs .labs-social {
		display: flex;
		justify-content: center;
		align-items: center;
		gap: 14px;
		padding: 50px 0 46px;
		position: relative;
		z-index: 1;
	}
	.page-labs .labs-social a {
		width: 27px;
		height: 27px;
		background-color: var(--color-sand-soft);
		-webkit-mask-image: var(--icon);
		mask-image: var(--icon);
		-webkit-mask-repeat: no-repeat;
		mask-repeat: no-repeat;
		-webkit-mask-position: center;
		mask-position: center;
		-webkit-mask-size: 27px 27px;
		mask-size: 27px 27px;
	}
	.page-labs .site-footer__social {
		display: none;
	}

	/* Footer, mobile (frame 1:376): columns stacked at left 37 / top 47 with a
	   17px rhythm, the mark on the right, a rule, then the two offices side by
	   side. Overrides the desktop Labs footer (51px gaps, offices bottom-right,
	   hidden rule). */
	.page-labs .site-footer {
		padding: 0 var(--page-gutter-sm) 40px 37px;
	}
	.page-labs .site-footer__top {
		max-width: none;
		margin-inline: 0;
	}
	.page-labs .site-footer__top {
		padding-top: 47px;
	}
	.page-labs .site-footer__cols {
		gap: 17px;
	}
	.page-labs .site-footer__col h2 {
		margin-bottom: 0;
	}
	.page-labs .site-footer__divider {
		display: block;
		max-width: none;
		margin: 34px 0 27px;
		border-top-color: color-mix(in srgb, var(--color-cream) 30%, transparent);
	}
	.page-labs .site-footer__locations {
		flex-direction: row;
		flex-wrap: nowrap;
		gap: 31px;
	}

	/* Hero art shrinks to the frame's mobile sizes (1:550): 125px grid, ~91px
	   mascot, in a 169x125 patch. */
	.page-labs .labs-hero__art {
		width: 169px;
		height: 125px;
	}
	.page-labs .labs-hero__grid {
		width: 125px;
		height: 125px;
		left: 44px;
	}
	/* Same box, scaled — keeps the cell geometry in one place. */
	.page-labs .labs-hero__glow {
		left: 44px;
		transform: scale(calc(125 / 249));
	}
	.page-labs .labs-hero__mascot {
		width: 91px;
		top: 14px;
	}
	/* Section eyebrows 20 -> 15. */
	.page-labs .labs-built__eyebrow,
	.page-labs .labs-open__eyebrow {
		font-size: var(--text-md);
	}
	.page-labs .labs-built__lead {
		font-size: var(--text-sm);
		max-width: 253px;
	}
	/* Product / open / notes / CTA titles: Display/S 32 -> 20. */
	.page-labs .labs-card__title,
	.page-labs .labs-open__name,
	.page-labs .labs-notes__title {
		font-size: var(--text-lg);
	}
	/* "Build to share": 32 -> 20 on mobile (already orange from the base rule). */
	.page-labs .labs-open__title {
		font-size: var(--text-lg);
	}
	/* Card category becomes Plus Jakarta Sans Bold 14 (not Eurostile) here. */
	.page-labs .labs-card__category {
		font: 700 var(--text-sm) / 1.35 var(--font-sans);
		letter-spacing: 0;
	}
	/* Tags 15 -> 10. */
	.page-labs .labs-card__tags,
	.page-labs .labs-open__tags {
		font-size: 10px;
	}
	/* Open-source description 16 -> 14. */
	.page-labs .labs-open__desc {
		font-size: var(--text-sm);
	}

	/* Grid tile shrinks 68 -> 49 on mobile (1.5px stroke, r10). Unlike desktop,
	   the mobile frame (1:197) starts the grid ~12px ABOVE the open-source card,
	   in the gap below the lead — not 125px into it. */
	.page-labs .labs-bottom::before {
		background-size: 49px 49px;
		top: 265px;
	}

	/* The mobile frame's wash is much lighter toward the foot of the dark zone
	   (behind the carousel dots) than the desktop mesh renders on this tall
	   portrait layout. Layer a cream fade over the bottom strip *as a background
	   layer* (not an overlay), so it only lightens the wash and never touches the
	   dots or swipe hint painted above it. */
	.page-labs .labs-top {
		background-image: linear-gradient(
				to bottom,
				transparent calc(100% - 240px),
				color-mix(in srgb, var(--color-cream) 55%, transparent) 100%
			),
			var(--gradient-labs-wash);
	}

	/* Product list → horizontal snap-carousel (mobile frame 1:196). Same
	   mechanism as the home page's service scroller. */
	.page-labs .labs-built__list {
		margin-top: 8px;
		flex-direction: row;
		flex-wrap: nowrap;
		gap: 16px;
		overflow-x: auto;
		scroll-snap-type: x proximity;
		scroll-padding-left: var(--page-gutter-sm);
		padding: 4px var(--page-gutter-sm);
		margin-inline: calc(var(--page-gutter-sm) * -1);
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
	}
	.page-labs .labs-built__list::-webkit-scrollbar {
		display: none;
	}

	/* All slides share one height (the tallest card); the copy stays centred
	   within each via justify-content on the card. */
	.page-labs .labs-built__list {
		align-items: stretch;
	}

	/* Each card becomes a slide: media on top, body below, fixed width (1:529). */
	.page-labs .labs-card {
		flex: 0 0 300px;
		width: 300px;
		scroll-snap-align: start;
		flex-direction: column-reverse;
		align-items: stretch;
		/* Content centred in a 534px floor (frame 21:1140): the button cards grow
		   past it to ~552, the others hold 534 with balanced top/bottom space. */
		justify-content: center;
		min-height: 534px;
		/* Image-to-copy gap is per-card in the frame (DrillRoom 5, Kesmo 19,
		   Mr. Orange 48, Sonia 21, CloseNet 29). */
		gap: var(--media-gap-mob, 5px);
		padding: 25px;
	}

	/* A carousel of one is not a carousel: with a single product visible the
	   slide would sit at the track's scroll-start, 20px from the left edge and
	   55 from the right. Auto margins hand it the leftover space evenly.

	   max-width because the slide is a fixed 300 and the track's content box is
	   narrower than that below ~340: auto margins resolve to 0 against negative
	   free space, so without it the card would keep its 300 and sit flush to the
	   right edge on a 320 screen. Letting it shrink to the gutters is the
	   symmetric answer there.

	   Both are scoped to :only-child and cost nothing with several cards, where
	   there is no free space to hand out. */
	.page-labs .labs-card:only-child {
		margin-inline: auto;
		max-width: 100%;
	}
	/* Card text rhythm on mobile: title/category/tags 3px apart, 16 to the
	   description, 18 to the CTA (1:529). */
	.page-labs .labs-card__title {
		margin-bottom: 3px;
	}
	.page-labs .labs-card__category {
		margin-bottom: 3px;
	}
	.page-labs .labs-card__tags {
		margin-bottom: 16px;
	}
	.page-labs .labs-card__lead {
		margin-bottom: 18px;
	}

	/* Mr. Orange's chatbot shrinks to its mobile size (frame 21:1075): 188x126,
	   85px avatar, a 14px bubble. The media stays full width so the 188px
	   composite centres in the card (justify-content: center) rather than
	   sitting off to one side. */
	.page-labs .labs-card--chatbot .labs-card__media {
		width: 100%;
	}
	.page-labs .labs-chatbot {
		width: 188px;
		height: 126px;
	}
	.page-labs .labs-chatbot__avatar {
		width: 85px;
		left: 81px;
	}
	.page-labs .labs-chatbot__bubble {
		width: 188px;
		top: 59px;
		min-height: 67px;
		padding: 12px 14px 16px 28px;
		font-size: var(--text-sm);
		border-radius: 12px;
	}

	.page-labs .labs-card__body,
	.page-labs .labs-card__media {
		width: 100%;
		max-width: none;
	}

	.page-labs .labs-card__media {
		display: flex;
		justify-content: center;
	}

	.page-labs .labs-card__media > img {
		width: auto;
		max-width: 100%;
		/* Each mockup has its own height on mobile (frame 21:1140): 190 DrillRoom,
		   176 Kesmo, 171 Sonia, 148 CloseNet. */
		max-height: var(--media-h-mob, 190px);
	}

	/* Chatbot composite stays its measured size, centred in the slide. */
	.page-labs .labs-card--chatbot .labs-card__media {
		width: 100%;
	}

	.page-labs .labs-card__lead {
		font-size: var(--text-sm);
	}

	/* Dots + swipe hint. */
	.page-labs .labs-built__nav {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 14px;
		margin-top: 26px;
	}
	.page-labs .labs-built__dots {
		/* The dots are components/carousel-dots.css: black in the frame (1:544),
		   orange when active. */
		--carousel-dot-radius: 1px;
		display: flex;
		gap: var(--space-2);
	}
	.page-labs .labs-built__hint {
		margin: 0;
		font-family: var(--font-sans);
		font-size: var(--text-xs);
		/* Black on the lightened foot of the wash (frame 1:548). */
		color: var(--color-black);
	}

	/* Open-source card (1:508): 291 wide, image on top, gap 28 to the copy. */
	.page-labs .labs-open__card {
		max-width: 291px;
		margin-inline: auto;
		gap: 28px;
		padding: 26px 17px;
	}
	.page-labs .labs-open__name {
		margin-bottom: 8px;
	}
	.page-labs .labs-open__tags {
		margin-bottom: 8px;
	}
	.page-labs .labs-open__desc {
		margin-bottom: 21px;
	}

	/* Open-source header block narrows with the card. */
	.page-labs .labs-open__lead {
		max-width: 246px;
	}

	/* Notes: the cards take components/post-card.css's mobile size. */
	.page-labs .labs-notes__grid {
		max-width: 282px;
	}
	/* Hero copy takes the frame's narrower mobile width. */
	.page-labs .labs-hero__copy {
		max-width: 275px;
	}
}
