/**
 * ManicSoup Labs — Services + FAQ pages
 *
 * Install as: manicsoup-child/assets/css/manicsoup-services.css
 * Loaded only on the services and FAQ pages, after manicsoup.css.
 *
 * Every rule is scoped under .ms-services (added to body by
 * ms_services_body_class) so nothing can leak onto another page or into the
 * Divi builder UI.
 *
 * Typography matches the live site: Oswald headings, Montserrat body.
 * Color is brand Manic Violet #7B40D6, NOT the #5f249f used by some older
 * components in manicsoup.css.
 *
 * @version 1.0.0
 */

/* ==========================================================================
   Tokens
   ========================================================================== */

.ms-services {
	--ms-night: #1a1628;
	--ms-deep: #120f1c;
	--ms-ink: #f2effa;
	--ms-dim: #a79eca;
	--ms-violet: #7b40d6;
	--ms-violet-deep: #46299f;
	--ms-violet-light: #a37cf2;
	--ms-blue: #3f9fe8;
	--ms-line: #2a2340;
	--ms-line-2: #3a3155;
	--ms-display: Oswald, "Montserrat", system-ui, sans-serif;
	--ms-body: Montserrat, system-ui, -apple-system, sans-serif;
	--ms-label: Oswald, system-ui, sans-serif;

	/* One vertical rhythm for the whole page. See "Section rhythm" below. */
	--ms-section-space: 104px;
}

/* ==========================================================================
   Base
   ========================================================================== */

.ms-services,
.ms-services .et_pb_section {
	background-color: var(--ms-deep);
	color: var(--ms-ink);
}

.ms-services .et_pb_text,
.ms-services p,
.ms-services li {
	font-family: var(--ms-body);
	color: var(--ms-dim);
}

.ms-services h1,
.ms-services h2,
.ms-services h3,
.ms-services h4 {
	font-family: var(--ms-display);
	font-weight: 700;
	color: #fff;
	text-wrap: balance;
}

/* Divi 5 dropped the fractional column classes, so columns are sized from
   structure rather than .et_pb_column_1_2 and friends. */

/*
 * ONE MEASURE FOR EVERY ROW.
 *
 * Divi's default row is `width: 80%`, which resolves against the SECTION's
 * content box — so a row inside a section padded 106px each side came out
 * 1046px wide, while any row this stylesheet turned into a grid (the card
 * decks, the proof strip, the pricing tiers) was given `width: 100%` and
 * landed on the 1120px cap instead.
 *
 * Live result, reported 09/13/2026: every section heading sat 37px further
 * right than the cards underneath it. Nothing looked broken on its own; the
 * page just felt off, all the way down.
 *
 * Both numbers were defensible. Having two of them was not.
 */
.ms-services .et_pb_row {
	width: 100%;
	max-width: 1120px;
	margin-left: auto;
	margin-right: auto;
}

/* Equal-height columns: the card backgrounds line up instead of ending ragged. */
.ms-services .ms-equal .et_pb_column {
	display: flex;
	flex-direction: column;
	justify-content: flex-start;
}

/*
 * Modules take their natural height — they do NOT share the column's height.
 *
 * This was `flex: 1`, which in a flex column means every module grows to an
 * equal slab. In an equal-height row a short column is stretched to match its
 * tallest neighbor, and that extra height got divided evenly among the
 * modules inside it. The eyebrow, the title and the body of each card were
 * pushed apart by a different amount depending on how much text that
 * particular card had, so titles across one row of cards never shared a
 * baseline, and a two-column row put ~200px of dead air between a heading and
 * its own paragraph.
 *
 * The cards stay equal height without this: `.ms-card` / `.ms-cap` /
 * `.ms-tier` each carry `height: 100%`, and the grid stretches the column.
 * Spacing between modules is the column's `gap`, one value, the same
 * everywhere.
 */
.ms-services .ms-equal .et_pb_column > .et_pb_module {
	flex: 0 0 auto;
}



/*
 * Reading measure for the section ledes.
 *
 * Unifying every row at 1120px (above) fixed the alignment but left the
 * one-column intro paragraphs running the full 1120 — about 130 characters a
 * line, which is far past comfortable. Cap only the paragraph that directly
 * follows a section heading: that is the lede in every case, and it leaves
 * card decks, lists and the pricing table at full width.
 */
.ms-services .et_pb_row > .et_pb_column:only-child > .et_pb_heading + .et_pb_text .et_pb_text_inner {
	max-width: 74ch;
}

/*
 * Pricing tiers: line the price rows up across the three cards.
 *
 * The tier descriptions are two or three lines depending on the tier, so with
 * every module at its natural height "Starting at" landed at a different
 * y in each card. Letting the module immediately BEFORE .ms-from absorb the
 * slack pushes every price row onto the same line without going back to
 * distributing whitespace evenly (which is what caused the original problem).
 */
.ms-services .ms-tier > .et_pb_module:has( + .ms-from ) {
	flex: 1 1 auto;
}

/*
 * ...and when there is no slack to absorb, subgrid does it properly.
 *
 * Flex-grow only helps if the card has spare height. It often doesn't: the
 * Refresh tier has a two-line description and a two-line footnote, the other
 * two have three lines and one, so all three cards come out the same height
 * with nothing left over — and "Starting at" still sits 24px higher in the
 * first card than in the other two.
 *
 * Subgrid makes each tier share the ROW's track sizing, so line 3 of every
 * card is the same line 3. Six tracks: eyebrow, title, description, "starting
 * at", amount, footnote.
 *
 * Unsupported browsers drop the two subgrid declarations as invalid and fall
 * back to the flex layout above, which is the previous, working behavior.
 */
.ms-services .et_pb_row:has(.ms-tier) {
	grid-template-rows: repeat(6, auto);
	grid-auto-rows: auto;
}

/* Specificity note: `.ms-services .ms-equal .et_pb_column` (0,3,0) sets
   display:flex on these same elements, so the subgrid rule has to match it or
   lose. Shipped once at (0,2,0) and silently did nothing. */
.ms-services .ms-equal .et_pb_column.ms-tier {
	display: grid;
	grid-template-rows: subgrid;
	grid-row: span 6;
	gap: 30px;
	align-content: start;
}

/* ==========================================================================
   Intrinsic responsive rows

   The proof strip, capability cards and pricing tiers used Divi column widths
   plus media queries. That squeezes instead of stacking whenever the media
   query does not fire at the width the content actually has — seen live in
   Divi's phone preview, which constrains width WITHOUT changing the viewport,
   so "Sites live and under my care right now" wrapped one word per line.

   auto-fit grids adapt to the space they are actually given, at any width,
   with no breakpoint involved. Divi's column widths are overridden to auto.
   ========================================================================== */

.ms-services .ms-proof .et_pb_row {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
	gap: 0;
	width: 100%;
	max-width: 1120px;
}

.ms-services .et_pb_row:has(.ms-card),
.ms-services .et_pb_row:has(.ms-cap),
.ms-services .et_pb_row:has(.ms-tier) {
	display: grid;
	gap: 18px;
	width: 100%;
}

.ms-services .et_pb_row:has(.ms-card) {
	grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}

/*
 * The eight capability cards are ONE row of eight columns, not two rows of
 * four. They were two rows, and because each Divi row is its own grid, the
 * top four came out 262px tall and the bottom four 284px — eight cards of the
 * same kind, in two visibly different sizes. `grid-auto-rows: 1fr` only
 * equalizes rows inside a single grid, so the fix had to be structural: the
 * rows were merged in the page content (09/13/2026) and every card now takes
 * the height of the tallest card in the whole deck, at any column count the
 * auto-fit lands on.
 */
.ms-services .et_pb_row:has(.ms-cap) {
	grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
	grid-auto-rows: 1fr;
}

.ms-services .et_pb_row:has(.ms-tier) {
	grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}

.ms-services .ms-proof .et_pb_column,
.ms-services .et_pb_row:has(.ms-card) .et_pb_column,
.ms-services .et_pb_row:has(.ms-cap) .et_pb_column,
.ms-services .et_pb_row:has(.ms-tier) .et_pb_column {
	width: auto !important;
	max-width: none !important;
	margin: 0 !important;
	float: none !important;
}

/* The bio row: portrait beside text on desktop, stacked when narrow. */
.ms-services .et_pb_row:has(.ms-portrait) {
	display: grid;
	grid-template-columns: minmax(200px, 270px) minmax(300px, 1fr);
	gap: 52px;
	align-items: start;
	width: 100%;
}

.ms-services .et_pb_row:has(.ms-portrait) .et_pb_column {
	width: auto !important;
	margin: 0 !important;
	float: none !important;
}

@media (max-width: 700px) {
	.ms-services .et_pb_row:has(.ms-portrait) {
		grid-template-columns: 1fr;
		gap: 28px;
	}

	.ms-services .ms-portrait {
		max-width: 240px;
	}
}

/* ==========================================================================
   Section rhythm

   Every content section set its own vertical padding, and the numbers had
   drifted badly. Measured live 09/13/2026, top/bottom in px:

     hero            124 / 92
     proof strip       0 / 0
     Two reasons      92 / 20     <- 20px above the seam, 96px below it
     What a system    96 / 88
     What gets built   0 / 24     <- heading sat flat against the seam
     Who I work with  52 / 52
     Recent work      72 / 72
     What it costs    92 / 24
     Larry Hearn      92 / 92
     Closing CTA      86 / 110

   The seam sections themselves carry no padding, so a seam inherited whatever
   its two neighbors happened to be — 20px of air on one side and 96px on the
   other, and none at all above "What gets built". Reported as the animated
   line sitting too close to everything, everywhere, which is exactly right.

   One token now sets both sides of every content section, so every seam gets
   the same air above and below it by construction rather than by luck.

   Specificity: Divi writes these as `.et_pb_section_7.et_pb_section` (0,2,0)
   in the deferred unified stylesheet, which loads after this file — so the
   selectors below carry a :not() to reach (0,4,0) and win without !important.
   ========================================================================== */

.ms-services .et_pb_section:not(.ms-seam-row):not(.ms-hero) {
	padding-top: var(--ms-section-space);
	padding-bottom: var(--ms-section-space);
}

/* The proof strip belongs to the hero — it keeps its join at the top and takes
   the shared spacing only on the underside, where the first seam is. */
.ms-services .ms-proof.et_pb_section:not(.ms-seam-row):not(.ms-hero) {
	padding-top: 0;
}

/* The hero opens the page and sets its own height; it only needs the shared
   value underneath, where it meets the proof strip. */
.ms-services .ms-hero.et_pb_section:not(.ms-seam-row) {
	padding-bottom: 76px;
}

@media (max-width: 980px) {
	.ms-services {
		--ms-section-space: 76px;
	}
}

@media (max-width: 700px) {
	.ms-services {
		--ms-section-space: 58px;
	}
}


/* ==========================================================================
   Section seams  [ms_seam]

   The seam sits in a Text module inside a Divi row, but must span the full
   section. Rather than fight Divi's row width settings (and risk an unverified
   attribute path), a section carrying .ms-seam-row lets its row run full width
   with no padding.
   ========================================================================== */

.ms-services .ms-seam-row .et_pb_row {
	max-width: 100%;
	width: 100%;
	padding: 0;
	margin: 0;
}

.ms-services .ms-seam-row {
	padding: 0 !important;
}

.ms-services .ms-seam {
	position: relative;
	width: 100%;
	height: 1px;
	/* clip is the one overflow value that can differ per axis: the pulse is
	   clipped sideways so it cannot widen the page, while the glow still
	   escapes vertically. */
	overflow-x: clip;
	overflow-y: visible;
	background: linear-gradient(
		90deg,
		transparent 0%,
		var(--ms-line) 14%,
		#4b3a78 50%,
		var(--ms-line) 86%,
		transparent 100%
	);
}

.ms-services .ms-seam::before {
	content: "";
	position: absolute;
	left: 50%;
	top: -40px;
	width: min(560px, 80vw);
	height: 80px;
	transform: translateX(-50%);
	pointer-events: none;
	background: radial-gradient(ellipse at center, rgba(123, 64, 214, 0.2), transparent 68%);
}

.ms-services .ms-seam i {
	position: absolute;
	top: -1.5px;
	left: 0;
	width: 74px;
	height: 4px;
	border-radius: 4px;
	pointer-events: none;
	opacity: 0;
	filter: blur(0.4px);
	background: linear-gradient(90deg, transparent, var(--ms-blue), var(--ms-violet-light), transparent);
	animation: ms-travel 9s linear infinite;
}

.ms-services .ms-seam:nth-of-type(2n) i {
	animation-duration: 11s;
	animation-delay: -4s;
}

.ms-services .ms-seam:nth-of-type(3n) i {
	animation-duration: 13s;
	animation-delay: -7s;
}

@keyframes ms-travel {
	0% {
		left: -10%;
		opacity: 0;
	}
	12% {
		opacity: 0.95;
	}
	88% {
		opacity: 0.95;
	}
	100% {
		left: 104%;
		opacity: 0;
	}
}

/* ==========================================================================
   Hero + vortex  [ms_vortex]
   ========================================================================== */

.ms-services .ms-hero {
	position: relative;
	overflow: hidden;
}

.ms-services .ms-vortex {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
	z-index: 0;
}

/*
 * The canvas is absolutely positioned with inset:0, so it fills its nearest
 * POSITIONED ancestor. Divi gives modules position:relative, which made the
 * canvas resolve against its own Text module wrapper — a box with no height —
 * so it painted correctly into a backing store that was then displayed at
 * zero height. Verified live: canvas 1901x1150 backing, 1046x0 on screen.
 *
 * So: the hero row and the canvas's own wrapper are forced static, which
 * walks the containing block up to .ms-hero. Content is raised above the
 * canvas per module instead of via the row.
 */
.ms-services .ms-hero .et_pb_row,
.ms-services .ms-hero .et_pb_column,
.ms-services .ms-vortex-wrap,
.ms-services .ms-vortex-wrap .et_pb_text_inner {
	/*
	 * Divi stacks FOUR positioned ancestors between the canvas and the section:
	 * row, column, the Text module, and et_pb_text_inner. Each one had to be
	 * made static before the canvas could resolve against .ms-hero. Verified
	 * live at each step: 1046x0 (resolved to et_pb_text_inner, zero height),
	 * then 1046x704 (resolved to the column, too narrow), then the full hero.
	 */
	position: static;
}

.ms-services .ms-hero .et_pb_module:not(.ms-vortex-wrap) {
	position: relative;
	z-index: 2;
}

/* The wrapper holds nothing but the canvas — it must not take up space. */
.ms-services .ms-vortex-wrap {
	margin: 0 !important;
	padding: 0 !important;
	line-height: 0;
}

.ms-services .ms-eyebrow p,
.ms-services .ms-eyebrow {
	font-family: var(--ms-label);
	font-size: 11.5px;
	letter-spacing: 0.22em;
	text-transform: uppercase;
	color: var(--ms-blue);
	margin: 0 0 22px;
}

.ms-services .ms-h1 h1 {
	font-size: clamp(38px, 6.2vw, 74px);
	line-height: 0.98;
	letter-spacing: -0.008em;
	margin: 0 0 26px;
	background: linear-gradient(102deg, #fff 8%, var(--ms-violet-light) 54%, var(--ms-blue) 96%);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

.ms-services .ms-lede p {
	font-size: clamp(16px, 1.7vw, 19px);
	line-height: 1.62;
	max-width: 62ch;
	margin: 0 0 34px;
}

/* ==========================================================================
   Buttons — Divi button modules, restyled
   ========================================================================== */

.ms-services .ms-btn .et_pb_button,
.ms-services .ms-btn.et_pb_button {
	background: var(--ms-violet);
	color: #fff;
	border: 0;
	border-radius: 10px;
	padding: 15px 30px !important;
	font-family: var(--ms-body);
	font-weight: 600;
	font-size: 15px;
	transition: 0.2s;
	box-shadow: 0 10px 34px -12px rgba(123, 64, 214, 0.9);
}

.ms-services .ms-btn .et_pb_button:hover,
.ms-services .ms-btn.et_pb_button:hover {
	background: #8d52e8;
	transform: translateY(-2px);
}

.ms-services .ms-btn-ghost .et_pb_button,
.ms-services .ms-btn-ghost.et_pb_button {
	background: transparent;
	color: var(--ms-ink);
	border: 1px solid var(--ms-line-2);
	box-shadow: none;
}

.ms-services .ms-btn-ghost .et_pb_button:hover,
.ms-services .ms-btn-ghost.et_pb_button:hover {
	border-color: var(--ms-violet-light);
	color: var(--ms-violet-light);
	background: transparent;
}

/* Divi appends an arrow glyph on hover. Off. */
.ms-services .et_pb_button::after {
	display: none !important;
}

.ms-services a:focus-visible,
.ms-services button:focus-visible,
.ms-services .et_pb_button:focus-visible {
	outline: 2px solid var(--ms-blue);
	outline-offset: 3px;
}

/* ==========================================================================
   Proof strip
   ========================================================================== */

.ms-services .ms-proof .et_pb_column {
	background: var(--ms-deep);
	padding: 26px 22px;
	border-left: 1px solid var(--ms-line);
}

.ms-services .ms-proof .et_pb_column:first-child {
	border-left: 0;
}

.ms-services .ms-proof {
	border-top: 1px solid var(--ms-line);
	border-bottom: 1px solid var(--ms-line);
}

.ms-services .ms-proof h3 {
	font-size: 26px;
	margin: 0 0 6px;
	font-variant-numeric: tabular-nums;
}

.ms-services .ms-proof p {
	font-size: 13px;
	line-height: 1.5;
	margin: 0;
}

/* ==========================================================================
   Cards: tracks, capabilities, tiers
   ========================================================================== */

.ms-services .ms-card {
	border: 1px solid var(--ms-line);
	border-radius: 16px;
	padding: 34px 32px;
	background: linear-gradient(168deg, rgba(163, 124, 242, 0.09), rgba(63, 159, 232, 0.03));
	transition: 0.26s;
	height: 100%;
}

.ms-services .ms-card:hover {
	border-color: var(--ms-line-2);
}

.ms-services .ms-card h3 {
	font-size: clamp(20px, 2.3vw, 27px);
	margin: 0 0 14px;
	letter-spacing: -0.004em;
	line-height: 1.14;
}

.ms-services .ms-card ul {
	margin: 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: 10px;
}

.ms-services .ms-card li {
	position: relative;
	padding-left: 22px;
	font-size: 14.5px;
	line-height: 1.5;
}

.ms-services .ms-card li::before {
	content: "";
	position: absolute;
	left: 0;
	top: 8px;
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--ms-violet);
}

.ms-services .ms-cap {
	background: linear-gradient(162deg, rgba(163, 124, 242, 0.11), rgba(63, 159, 232, 0.04));
	border: 1px solid var(--ms-line);
	border-radius: 14px;
	padding: 26px 24px;
	transition: 0.26s;
	height: 100%;
}

.ms-services .ms-cap:hover {
	transform: translateY(-5px);
	border-color: var(--ms-violet-light);
	box-shadow: 0 22px 50px -26px rgba(123, 64, 214, 0.85);
}

.ms-services .ms-cap h3 {
	font-size: 16px;
	font-weight: 600;
	margin: 0 0 10px;
}

.ms-services .ms-cap p {
	font-size: 14px;
	line-height: 1.6;
	margin: 0;
}

.ms-services .ms-num {
	display: block;
	font-family: var(--ms-label);
	font-size: 11px;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--ms-blue);
	margin-bottom: 14px;
}

/* ==========================================================================
   Pricing
   ========================================================================== */

.ms-services .ms-tier {
	border: 1px solid var(--ms-line);
	border-radius: 16px;
	padding: 32px 28px;
	background: linear-gradient(168deg, rgba(163, 124, 242, 0.07), rgba(63, 159, 232, 0.02));
	transition: 0.26s;
	height: 100%;
}

.ms-services .ms-tier:hover {
	border-color: var(--ms-line-2);
	transform: translateY(-4px);
}

.ms-services .ms-tier--mid {
	border-color: #4b3a78;
	background: linear-gradient(168deg, rgba(123, 64, 214, 0.16), rgba(63, 159, 232, 0.04));
}

.ms-services .ms-amt {
	font-family: var(--ms-display);
	font-weight: 700;
	font-size: 38px;
	color: #fff;
	line-height: 1;
	margin: 0;
	font-variant-numeric: tabular-nums;
}

.ms-services .ms-from {
	font-family: var(--ms-label);
	font-size: 11px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: #7e7499;
	margin: 0 0 6px;
}

.ms-services .ms-budget {
	border: 1px solid #4b3a78;
	border-radius: 16px;
	padding: 30px 32px;
	background: radial-gradient(120% 140% at 0% 0%, rgba(123, 64, 214, 0.18), transparent 70%);
}

.ms-services .ms-budget p {
	color: var(--ms-ink);
	font-size: 17px;
	line-height: 1.7;
	max-width: 72ch;
	margin: 0;
}

.ms-services .ms-budget strong {
	color: #fff;
}

/* ==========================================================================
   Schematic  [ms_schematic]
   ========================================================================== */

.ms-services .ms-diagram {
	background: rgba(26, 22, 40, 0.72);
	border: 1px solid var(--ms-line);
	border-radius: 18px;
	padding: 26px;
	margin: 0;
	box-shadow: 0 34px 80px -46px #000;
}

.ms-services .ms-diagram figcaption {
	font-family: var(--ms-label);
	font-size: 10.5px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--ms-dim);
	margin-bottom: 16px;
}

.ms-services .ms-wire {
	fill: none;
	stroke: var(--ms-violet-deep);
	stroke-width: 1.7;
	stroke-dasharray: 280;
	stroke-dashoffset: 280;
	animation: ms-draw 1.7s cubic-bezier(0.6, 0, 0.3, 1) forwards;
}

@keyframes ms-draw {
	to {
		stroke-dashoffset: 0;
	}
}

.ms-services .ms-node rect {
	fill: #221b38;
	stroke: var(--ms-line-2);
	stroke-width: 1.4;
	transition: 0.22s;
}

.ms-services .ms-node text {
	font-family: var(--ms-label);
	font-size: 10.5px;
	fill: var(--ms-dim);
	letter-spacing: 0.04em;
}

.ms-services .ms-node:hover rect {
	stroke: var(--ms-violet-light);
	fill: #2e2350;
}

.ms-services .ms-node:hover text {
	fill: #fff;
}

.ms-services .ms-hub rect {
	fill: var(--ms-violet);
	stroke: var(--ms-violet);
}

.ms-services .ms-hub text {
	fill: #fff;
	font-weight: 700;
}

.ms-services .ms-pulse {
	fill: var(--ms-blue);
}

/* ==========================================================================
   Who I work with — chip row
   ========================================================================== */

.ms-services .ms-chips ul {
	display: flex;
	flex-wrap: wrap;
	gap: 10px 12px;
	margin: 0;
	padding: 0;
	list-style: none;
}

.ms-services .ms-chips li {
	font-family: var(--ms-display);
	font-weight: 600;
	font-size: clamp(15px, 1.9vw, 21px);
	color: #6f6690;
	padding: 9px 18px;
	border: 1px solid var(--ms-line);
	border-radius: 999px;
	transition: 0.22s;
}

.ms-services .ms-chips li::before {
	display: none;
}

.ms-services .ms-chips li:hover {
	color: #fff;
	border-color: var(--ms-violet);
	background: rgba(123, 64, 214, 0.14);
}

/* ==========================================================================
   Bio
   ========================================================================== */

.ms-services .ms-portrait .et_pb_image_wrap,
.ms-services .ms-portrait img {
	border-radius: 18px;
	display: block;
	width: 100%;
}

.ms-services .ms-portrait {
	box-shadow: 0 28px 64px -34px rgba(123, 64, 214, 0.75);
	border-radius: 18px;
}

.ms-services .ms-creds p {
	font-family: var(--ms-label);
	font-size: 12.5px;
	letter-spacing: 0.12em;
	color: var(--ms-violet-light);
	margin: 0 0 24px;
}

.ms-services .ms-bio p {
	font-size: 16.5px;
	line-height: 1.72;
	max-width: 56ch;
	margin: 0 0 18px;
}

.ms-services .ms-bio strong {
	color: #fff;
	font-weight: 600;
}

/* ==========================================================================
   FAQ page
   ========================================================================== */

.ms-services .ms-faq h2 {
	font-family: var(--ms-label);
	font-size: 11.5px;
	font-weight: 400;
	letter-spacing: 0.2em;
	text-transform: uppercase;
	color: var(--ms-blue);
	margin: 0 0 26px;
}

.ms-services .ms-faq h3 {
	font-size: clamp(19px, 2.2vw, 25px);
	margin: 0 0 14px;
}

/* The lead answer. First 40-60 words must stand alone - this is what gets
   extracted into AI answers, so it is styled as the answer, not as a pull
   quote that a parser might treat as decoration. */
.ms-services .ms-answer p {
	font-size: 17.5px;
	line-height: 1.68;
	color: var(--ms-ink);
	border-left: 2px solid var(--ms-violet);
	padding-left: 20px;
	margin: 0 0 18px;
}

/* ==========================================================================
   Scroll reveal
   Hidden state exists ONLY under .ms-js-reveal, which JS adds. Without JS -
   and for any crawler with JS off - every word is visible.
   ========================================================================== */

.ms-js-reveal .ms-services .ms-reveal,
.ms-services.ms-js-reveal .ms-reveal {
	opacity: 0;
	transform: translateY(18px);
	transition:
		opacity 0.7s cubic-bezier(0.2, 0.7, 0.3, 1),
		transform 0.7s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.ms-js-reveal .ms-services .ms-reveal.ms-in,
.ms-services.ms-js-reveal .ms-reveal.ms-in {
	opacity: 1;
	transform: none;
}

/* ==========================================================================
   Motion preference
   ========================================================================== */

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

/* ==========================================================================
   Responsive
   ========================================================================== */

@media (max-width: 980px) {
	/* Once the grid has wrapped, a left border on a first-in-row cell reads as
	   a stray line, so borders soften to a top rule instead. */
	.ms-services .ms-proof .et_pb_column {
		border-left: 0;
		border-top: 1px solid var(--ms-line);
	}

	.ms-services .ms-proof .et_pb_column:first-child {
		border-top: 0;
	}
}

@media (max-width: 767px) {
	/* Phones: lighter vortex so hero text stays the focus, and tighter rhythm. */
	.ms-services .ms-vortex {
		opacity: 0.55;
	}

	.ms-services .ms-h1 h1 {
		line-height: 1.04;
	}

	.ms-services .ms-amt {
		font-size: 32px;
	}

	.ms-services .ms-budget p,
	.ms-services .ms-bio p {
		font-size: 15.5px;
	}

	.ms-services .ms-diagram {
		padding: 16px;
	}

	.ms-services .ms-chips li {
		font-size: 15px;
		padding: 8px 14px;
	}

	.ms-services .ms-card,
	.ms-services .ms-tier {
		padding: 26px 22px;
	}

	.ms-services .ms-budget {
		padding: 24px 22px;
	}
}


/* ==========================================================================
   Intake form

   The closing call to action had a button pointing at the section it already
   sat in, and the site had no form, no email link and no contact page. The
   button therefore did nothing, correctly. This is the destination it should
   have had.
   ========================================================================== */

.ms-services .ms-intake {
	max-width: 1120px;
	margin: 0 auto;
	/* No side inset. The form used to sit 7% in from the section measure, which
	   put its left edge ~106px right of the heading above it — the kind of
	   near-miss alignment that reads as sloppy rather than deliberate. It now
	   shares the one 1120px measure every other row on the page uses. */
	padding: 0 0 var(--ms-section-space);
	scroll-margin-top: 140px;
}

.ms-services .ms-intake-form,
.ms-services .ms-intake-done {
	background: linear-gradient(168deg, rgba(163, 124, 242, 0.1), rgba(63, 159, 232, 0.035));
	border: 1px solid var(--ms-line);
	border-radius: 18px;
	padding: 40px 38px 34px;
}

.ms-services .ms-intake-done h3 {
	margin: 0 0 10px;
	font-size: clamp(22px, 2.6vw, 30px);
}

.ms-services .ms-intake-done p {
	margin: 0;
	max-width: 62ch;
}

.ms-services .ms-intake-error {
	margin: 0 0 18px;
	padding: 13px 18px;
	border-radius: 10px;
	border: 1px solid rgba(224, 123, 59, 0.5);
	background: rgba(224, 123, 59, 0.12);
	color: #f3c69f;
}

/* Two across when there is room, stacked when there is not — no breakpoint,
   so it adapts to whatever width it is actually given. */
.ms-services .ms-intake-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
	gap: 0 22px;
}

.ms-services .ms-field {
	margin: 0 0 20px;
	display: flex;
	flex-direction: column;
	gap: 7px;
}

.ms-services .ms-field label {
	font-family: var(--ms-label);
	font-size: 13px;
	letter-spacing: 0.07em;
	text-transform: uppercase;
	color: var(--ms-dim);
}

.ms-services .ms-req {
	color: var(--ms-violet-light);
}

.ms-services .ms-field input,
.ms-services .ms-field textarea,
.ms-services .ms-field select {
	width: 100%;
	padding: 13px 15px;
	border-radius: 10px;
	border: 1px solid var(--ms-line-2);
	background: rgba(9, 7, 16, 0.55);
	color: var(--ms-ink);
	font-family: var(--ms-body);
	font-size: 16px; /* 16px or iOS zooms the page on focus. */
	line-height: 1.45;
	transition: border-color 0.18s, box-shadow 0.18s;
}

.ms-services .ms-field textarea {
	resize: vertical;
	min-height: 128px;
}

.ms-services .ms-field input::placeholder,
.ms-services .ms-field textarea::placeholder {
	color: rgba(167, 158, 202, 0.6);
}

.ms-services .ms-field input:focus,
.ms-services .ms-field textarea:focus,
.ms-services .ms-field select:focus {
	outline: none;
	border-color: var(--ms-violet-light);
	box-shadow: 0 0 0 3px rgba(123, 64, 214, 0.28);
}

.ms-services .ms-intake-submit {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 16px 22px;
	margin: 6px 0 0;
}

.ms-services .ms-intake-submit .ms-btn {
	border: 0;
	cursor: pointer;
	font-family: var(--ms-label);
}

.ms-services .ms-intake-note {
	font-size: 14px;
	color: var(--ms-dim);
}

/* The honeypot must be reachable by a bot and invisible to a person — so it is
   moved off-screen rather than display:none, which some bots check for. */
.ms-services .ms-hp {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

@media (max-width: 700px) {
	.ms-services .ms-intake-form,
	.ms-services .ms-intake-done {
		padding: 28px 22px 24px;
	}
}

/* .ms-intake-title was removed in 1.7.3 — the section it sits in already has a
   heading and a lede. The rule is gone with it; do not reintroduce one without
   removing the Divi heading above the form first. */


/* ==========================================================================
   Case study screenshots  [.ms-shot]

   Real screenshots of shipped work. Framed rather than bled to the edge, so
   they read as "a picture of a website" and not as part of this page's own
   layout — the moment a visitor is unsure which site they are looking at, the
   case study has failed.
   ========================================================================== */

.ms-services .ms-shot {
	margin: 0;
	border: 1px solid var(--ms-line);
	border-radius: 14px;
	overflow: hidden;
	background: #000;
	box-shadow: 0 26px 60px -34px rgba(0, 0, 0, 0.9);
	transition: border-color 0.25s, transform 0.25s;
}

.ms-services .ms-shot:hover {
	border-color: var(--ms-line-2);
	transform: translateY(-2px);
}

.ms-services .ms-shot img {
	display: block;
	width: 100%;
	height: auto;
}


/* The case-study lead: the write-up with the home page beside it, then a row of
   details underneath. Both are auto-fit grids rather than media queries, so they
   collapse at whatever width the content actually runs out of room at. */
.ms-services .ms-case-lead {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
	gap: 34px 44px;
	align-items: start;
	width: 100%;
}

.ms-services .ms-case-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
	gap: 18px;
	width: 100%;
	margin-top: 22px;
}

.ms-services .ms-case-lead .et_pb_column,
.ms-services .ms-case-grid .et_pb_column {
	width: auto !important;
	max-width: none !important;
	margin: 0 !important;
	float: none !important;
	display: flex;
	flex-direction: column;
	gap: 18px;
}

/* The three detail shots were cropped from three different pages, so their
   natural ratios run 2.47 to 3.23 and the row bottoms came out ragged. One
   shared ratio with object-fit fixes that without re-cropping the files;
   object-position keeps the top of each shot, which is where the content is. */
.ms-services .ms-case-grid .ms-shot img {
	aspect-ratio: 2.9;
	height: auto;
	object-fit: cover;
	object-position: top center;
}

/* The detail shots are supporting evidence, not the argument — they stay quiet
   until someone looks straight at one. */
.ms-services .ms-case-grid .ms-shot {
	opacity: 0.88;
}

.ms-services .ms-case-grid .ms-shot:hover {
	opacity: 1;
}
