/*!
 * Revamp overrides for the local mirror.
 *
 * Loaded after Elementor's generated CSS on every page, immediately after
 * _custom/fonts/apex-fonts.css. Everything here is additive — no mirrored
 * WordPress file is edited, so any change is undone by deleting a rule.
 *
 * Elements are targeted by their WordPress attachment class (wp-image-NNN),
 * which is the media library ID and so is identical on every page that uses
 * the image. Widget data-ids would work too but are per-template.
 */

:root {
	/* One face: Montserrat. The Apex Brand Guide (10.2025), page 6, is
	   explicit — "All text elements that support the Apex Custom Innovations
	   brand is to use the Montserrat font family", with Bold for headlines,
	   SemiBold for subtitles and Regular for body copy.

	   That replaces the Oxanium / Archivo / Inter trio these three variables
	   used to hold. The trio was a reasonable guess made before the guide
	   existed — Oxanium in particular was chosen as a stand-in for the square
	   sans in the logo's "INNOVATIONS" lockup — and it is simply wrong now.

	   **All three variables are kept and all three point at Montserrat.** Every
	   rule in this file already reads one of them, so nothing else has to
	   change, no weight or colour moves, and the heading/interface/body split
	   stays expressed in the CSS if the brand ever wants three faces again.
	   Montserrat's own weights carry the distinction now: the scale that used
	   to be "a different family" is the same family at 400 / 600 / 700. */
	--apex-heading: "Montserrat", "Helvetica Neue", Arial, sans-serif;
	--apex-display: "Montserrat", "Helvetica Neue", Arial, sans-serif;
	--apex-body: "Montserrat", "Helvetica Neue", Arial, sans-serif;

	/* The brand palette, from the Apex Brand Guide (10.2025) page 5.
	
	   **One blue, and it is theirs.** This file used to carry two — #3f8ee0
	   for type on black and #1876D2 for fills under white type — a split
	   invented here because neither of those two could do both jobs. APEX BLUE
	   can: white on it is 4.89:1, and it is 4.30:1 against the page's black.
	   The guide names exactly one blue, so the split is retired and every
	   occurrence of both old values now resolves to this.
	
	   4.30:1 passes AA for large text and not for small, which is the one
	   trade in adopting it. Everything set in this blue is display type — the
	   88px hero headline, the 38-44px band headings, the 26px footer headers —
	   so it clears the bar everywhere it is actually used; the small blue on
	   the site is white-on-blue, which is the 4.89 case. Do not put this blue
	   on black at body size.
	
	   `--apex-blue-deep` is gone. It existed for the nav dropdown's white
	   panel, where the old light blue failed AA; APEX BLUE on white is the
	   same 4.89:1, so it is no longer needed, and nothing referenced it. */
	--apex-blue: #0072ce;      /* APEX BLUE       — Pantone 307 C */
	--apex-silver: #a0a0a0;    /* METALLIC SILVER — Pantone 422 C */
	--apex-steel: #4a4a4a;     /* STEEL GREY      — Pantone 7540 C */

	/* Was #9aa3ad, a grey picked here. METALLIC SILVER is the brand's own and
	   within a hair of it, so muted copy points at the guide's value. */
	--apex-muted: var(--apex-silver);
	--apex-hairline: rgba(255, 255, 255, 0.14);

	/* One vertical rhythm for stacked page sections. */
	--apex-section-gap: 120px;
}

/* ==========================================================================
   1. Typography
   The theme's reset.css set `body { font-family: -apple-system … }`, so body
   copy was rendering in whatever font the visitor's OS supplies — the site
   literally looked different on macOS and Windows. Everything text-bearing is
   pinned below. `i` is never targeted: icon fonts (Font Awesome, eicons) live
   there and would break.
   ========================================================================== */
body,
body p,
body li,
body dd,
body dt,
body td,
body th,
body blockquote,
body figcaption,
body label,
body input,
body select,
body textarea,
body .elementor-widget-text-editor,
body .elementor-icon-list-text,
body .jet-select__control,
body .jet-checkboxes-list__label,
/* Bare links. Two of them — "Privacy Policy" and "Terms and Conditions" in the
   copyright bar — carry no class of their own and were still inheriting
   Poppins from a theme rule this list did not reach. That mattered more once
   the Poppins stylesheet was removed in the twenty-eighth pass: the family was
   still being *asked* for and no longer being *loaded*, so those two links
   rendered in whatever the OS supplied. Swept up with `body a`, which is safe
   now that every element on the site is one family. */
body a {
	font-family: var(--apex-body) !important;
}

body {
	font-size: 17px;
	line-height: 1.7;
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

/* Headings take the heading weight of the brand face. */
body h1,
body h2,
body h3,
body h4,
body h5,
body h6,
body .elementor-heading-title {
	font-family: var(--apex-heading) !important;
}

/* Interface labels. These still need naming even though every variable now
   resolves to the same family: nav links and accordion titles carry their own
   font-family from the nav-menu and nested-accordion widgets, so inheritance
   alone leaves them on Poppins. */
body .elementor-button,
body .elementor-tab-title,
body .elementor-item,
body .elementor-nav-menu a,
body .e-n-accordion-item-title-text,
body .e-n-menu-title-text {
	font-family: var(--apex-display) !important;
}

/* Navigation reads as a label, not body copy. */
body .elementor-item,
body .elementor-nav-menu a {
	font-size: 0.8125rem !important;
	font-weight: 500 !important;
	letter-spacing: 0.14em !important;
	text-transform: uppercase;
}

/* Large + heaviest weight + no tracking reads loud, not expensive. The scale
   below goes the other way: bigger steps, lighter weight, tighter leading. */
body h1,
body h1.elementor-heading-title {
	font-size: clamp(2.75rem, 7vw, 6.5rem) !important;
	font-weight: 600 !important;
	letter-spacing: -0.02em !important;
	line-height: 0.95 !important;
}

body h2,
body h2.elementor-heading-title {
	font-size: clamp(1.75rem, 3.2vw, 2.75rem) !important;
	font-weight: 600 !important;
	letter-spacing: -0.01em !important;
	line-height: 1.12 !important;
}

body h3,
body h3.elementor-heading-title {
	font-size: 1.375rem !important;
	font-weight: 600 !important;
	letter-spacing: 0 !important;
	line-height: 1.3 !important;
}

/* The single biggest "high-end" signal: wide-tracked small caps for labels. */
.apex-eyebrow,
.elementor-widget-heading.apex-eyebrow .elementor-heading-title {
	font-family: var(--apex-display) !important;
	font-size: 0.75rem !important;
	font-weight: 500 !important;
	letter-spacing: 0.18em !important;
	text-transform: uppercase;
	color: var(--apex-muted) !important;
}

/* ==========================================================================
   2. Kill the chrome-blue gradient on headings
   Was linear-gradient(90deg,#0f3d75,#0072ce,#fff,#0072ce,#0f3d75) painted
   through background-clip:text. Headings have no legitimate background image,
   so clearing it wholesale is safe.
   ========================================================================== */
body h1,
body h2,
body h3,
body .elementor-heading-title {
	background-image: none !important;
	-webkit-background-clip: border-box !important;
	background-clip: border-box !important;
	-webkit-text-fill-color: currentColor !important;
}

/* ==========================================================================
   3. Stray defaults nobody styled
   #CC3366 was Hello Elementor's default link colour, inherited by 83 elements.
   Kept at specificity 0,0,1 so any deliberate Elementor colour still wins.
   ========================================================================== */
a {
	color: inherit;
	text-decoration-thickness: 1px;
	text-underline-offset: 0.2em;
}

a:hover {
	color: var(--apex-blue);
}

/* Facebook-blue chips with grey glyphs, straight out of the box. */
.elementor-social-icon {
	background-color: transparent !important;
	border: 1px solid var(--apex-hairline) !important;
	color: #fff !important;
	transition: background-color 0.18s ease, border-color 0.18s ease;
}

.elementor-social-icon:hover {
	background-color: var(--apex-blue) !important;
	border-color: var(--apex-blue) !important;
}

/* ==========================================================================
   4. Shape language
   Photos are rounded (12px); buttons sit softer than the photos at 8px, which
   also lets the rotating border in section 10 sweep the corners cleanly.
   ========================================================================== */
.elementor-widget-image img,
.elementor-widget-theme-post-featured-image img {
	border-radius: 12px !important;
}

.elementor-button {
	border-radius: 8px !important;
	font-size: 0.8125rem !important;
	font-weight: 600 !important;
	letter-spacing: 0.12em !important;
	text-transform: uppercase;
	padding: 1rem 2.25rem !important;
}

/* ==========================================================================
   5. Scrolling Corvette  (image-30.png = wp-image-139)
   Removed from all 24 pages that carry it. The "APEX INNOVATIONS" wordmark
   sharing this band (Layer-1-1.png) is kept — hiding the widget rather than
   just the <img> lets the band collapse instead of reserving 359px of empty
   space.
   ========================================================================== */
.elementor-widget-image:has(img.wp-image-139) {
	display: none !important;
}

/* ==========================================================================
   6. Hero furniture, scoped by page id:
     316 PPF              423 Suspension        340 Apex Armor
     346 Signature Finish 557 Vinyl Wraps       394 Wheels & Tires
     355 Window Tint      415 Vehicle Accessories
     387 About Us
   wp-image-77 is the cut-out Jeep; wp-image-81 is the blue teardrop, which
   carries the "WHO WE ARE" lettering baked into the same PNG. Both are left
   in place on the other 19 pages that use them.

   The contact page gets the same treatment separately, in section 21.
   ========================================================================== */
.page-id-316 .elementor-widget-image:has(img.wp-image-77),
.page-id-423 .elementor-widget-image:has(img.wp-image-77),
.page-id-340 .elementor-widget-image:has(img.wp-image-77),
.page-id-346 .elementor-widget-image:has(img.wp-image-77),
.page-id-557 .elementor-widget-image:has(img.wp-image-77),
.page-id-394 .elementor-widget-image:has(img.wp-image-77),
.page-id-355 .elementor-widget-image:has(img.wp-image-77),
.page-id-415 .elementor-widget-image:has(img.wp-image-77),
.page-id-387 .elementor-widget-image:has(img.wp-image-77),
.page-id-316 .elementor-widget-image:has(img.wp-image-81),
.page-id-423 .elementor-widget-image:has(img.wp-image-81),
.page-id-340 .elementor-widget-image:has(img.wp-image-81),
.page-id-346 .elementor-widget-image:has(img.wp-image-81),
.page-id-557 .elementor-widget-image:has(img.wp-image-81),
.page-id-394 .elementor-widget-image:has(img.wp-image-81),
.page-id-355 .elementor-widget-image:has(img.wp-image-81),
.page-id-415 .elementor-widget-image:has(img.wp-image-81),
.page-id-387 .elementor-widget-image:has(img.wp-image-81) {
	display: none !important;
}

/* ==========================================================================
   7. Replacement photos
   The originals were 16:9 shots dropped into a 548x600 portrait frame and
   center-cropped. The new photos are wider (1.55:1 to 2.04:1), so the same
   crop would cut the nose and tail off the vehicle.
   ========================================================================== */
img.apex-replaced {
	height: auto !important;
	max-height: none !important;
	aspect-ratio: auto !important;
	object-fit: contain !important;
	width: 100% !important;
	border-radius: 12px !important;
}

.elementor-widget-image:has(img.apex-replaced),
.elementor-widget-image:has(img.apex-replaced) .elementor-widget-container {
	height: auto !important;
	align-self: center;
}

/* ==========================================================================
   8. Wheels & Tires — the showcase photograph
   This slot held one 1024x820 photo being force-cropped into a portrait frame.
   It became a 3-up grid of round wheel tiles — a round subject in a round
   frame, three finishes to match the copy's "matte black, bronze, polished" —
   and the client has now asked for one photograph of a whole vehicle in its
   place: the Raptor R on Vossen HFX-1s, which is the frame on this site where
   the wheel face is largest and best lit.

   The one rule that is load-bearing is `height: auto !important`. The Elementor
   image widget this stands inside still carries generated CSS pinning
   `img { height: 600px }` — it stretched the tiles into ovals before, and it
   would letterbox the photograph now.

   The aspect ratio is stated as well as the height released. 4/3 is the file's
   own shape, so it changes nothing today; it is there so that a replacement
   photograph of any proportion drops into the same box rather than resizing
   the column around it, which is the lesson the gallery cards taught in
   section 52.
   ========================================================================== */
.apex-wheel-showcase {
	margin: 0;
}

.apex-wheel-showcase img {
	display: block;
	width: 100%;
	height: auto !important;
	aspect-ratio: 4 / 3;
	object-fit: cover;
	border-radius: 12px;          /* photos are 12px, section 4 */
}

.apex-wheel-showcase figcaption {
	margin-top: 1rem;
	font-family: var(--apex-display);
	font-size: 0.6875rem;
	font-weight: 500;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--apex-muted);
	line-height: 1.5;
}

.apex-wheel-showcase .apex-wheel-name {
	display: block;
	color: #fff;
	margin-bottom: 0.15rem;
}

/* ==========================================================================
   9. Partners page
   Both partner marks are dark-on-light (Vanguard ships a white-background
   JPEG, Five Star a transparent PNG whose ink averages 70/255), so neither is
   legible straight onto the black page. Each sits on a white plate — which is
   also how a partner wall conventionally reads.
   ========================================================================== */
.apex-partners-page {
	display: block;
	padding: clamp(3rem, 8vw, 7rem) clamp(1.25rem, 5vw, 4rem);
}

.apex-partners {
	max-width: 1120px;
	margin: 0 auto;
	text-align: center;
}

.apex-partners .apex-eyebrow {
	margin: 0 0 1rem;
}

.apex-partners h1 {
	margin: 0 0 1.25rem;
}

.apex-partners-intro {
	max-width: 46rem;
	margin: 0 auto clamp(2.5rem, 6vw, 4.5rem);
	color: var(--apex-muted);
}

/* Plates are capped rather than filling the row. At full width they were 541px
   across, and the Five Star lockup — which only exists at 159x115 and is not
   upscaled any more — sat marooned in the middle of one. Capping the plate
   makes both marks fill more of their own tile without either being stretched. */
.apex-partner-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(240px, 400px));
	justify-content: center;
	gap: clamp(1.5rem, 3vw, 2.5rem);
	margin: 0;
	padding: 0;
	list-style: none;
}

.apex-partner-card {
	display: flex;
	flex-direction: column;
	gap: 1.25rem;
	text-decoration: none;
	color: inherit;
}

/* The same tile as the brand wall rather than flat #fff. Both partner marks
   are dark navy on light, so they need a light ground to stay legible — but a
   pure white panel reads as a hole punched in the page, which is exactly what
   the old logo strips looked like. */
.apex-partner-plate {
	display: grid;
	place-items: center;
	overflow: hidden;            /* clips the zoom to the plate */
	border-radius: 12px;
	background: linear-gradient(168deg, var(--apex-tile-hi), var(--apex-tile));
	box-shadow: inset 0 1px 0 var(--apex-tile-edge);
	border: 1px solid var(--apex-hairline);
	aspect-ratio: 2 / 1;
	padding: 8% 10%;
}

/* Sized by max-width/max-height rather than width:100%, so a mark is never
   stretched past its own pixels. The Five Star Ford lockup only exists at
   159x115 — that is the file the dealership itself serves — and blowing it up
   to fill the plate is what made it look low quality. It now sits at its
   native size with air around it, which is sharp, while Vanguard's larger
   artwork still fills the plate. */
.apex-partner-plate img {
	display: block;
	width: auto;
	height: auto;
	max-width: 100%;
	max-height: 100%;
	object-fit: contain;
	border-radius: 0 !important;   /* section 4 rounds every widget image */
	transform: scale(1);
	transition: transform 0.45s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.apex-partner-card:hover .apex-partner-plate img,
.apex-partner-card:focus-visible .apex-partner-plate img {
	transform: scale(1.06);
}

.apex-partner-card:focus-visible .apex-partner-plate {
	outline: 2px solid var(--apex-blue);
	outline-offset: 3px;
}

.apex-partner-meta {
	display: block;
}

.apex-partner-name {
	display: block;
	font-family: var(--apex-display);
	font-size: 0.9375rem;
	font-weight: 600;
	letter-spacing: 0.04em;
	color: #fff;
	transition: color 0.18s ease;
}

.apex-partner-card:hover .apex-partner-name {
	color: var(--apex-blue);
}

.apex-partner-blurb {
	display: block;
	margin-top: 0.35rem;
	font-size: 0.8125rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--apex-muted);
}

/* Respect a reduced-motion preference — the zoom is decorative. */
@media (prefers-reduced-motion: reduce) {
	.apex-partner-plate img {
		transition: none;
	}
	.apex-partner-card:hover .apex-partner-plate img,
	.apex-partner-card:focus-visible .apex-partner-plate img {
		transform: none;
	}
}

/* ==========================================================================
   10. Rotating border on buttons
   The PPF page shipped this on one button as inline CSS behind a .sty-bord
   wrapper class. This is the general version. A conic gradient sweeps because
   --apex-ring-angle is a registered @property, which is what makes an angle
   animatable at all; ::after masks the middle so only the ring shows.

   Applied to .apex-btn-ring, which button-rings.js puts on every dark or
   outline button. Light-filled buttons keep their solid fill and stay the
   primary call to action — the ring needs a transparent background, so
   applying it to everything would flatten the hierarchy.

   The blue is --apex-blue (#0072ce), the site accent, rather than the #0072ce
   the original inline copy used; change that one variable to go back.
   ========================================================================== */
@property --apex-ring-angle {
	syntax: "<angle>";
	inherits: false;
	initial-value: 0deg;
}

@keyframes apexRingRotate {
	to { --apex-ring-angle: 360deg; }
}

body .elementor-button.apex-btn-ring {
	position: relative !important;
	display: inline-flex !important;
	align-items: center;
	justify-content: center;
	isolation: isolate;          /* keeps the negative z-index inside the button */
	z-index: 1;
	background: transparent !important;
	border: 1px solid transparent !important;
	border-radius: 8px !important;
	color: #fff !important;
}

body .elementor-button.apex-btn-ring::before {
	content: "";
	position: absolute;
	inset: -1px;
	border-radius: inherit;
	background: conic-gradient(
		from var(--apex-ring-angle),
		var(--apex-blue) 0deg,
		var(--apex-blue) 55deg,
		#ffffff 90deg,
		#ffffff 145deg,
		var(--apex-blue) 360deg
	);
	z-index: -2;
	animation: apexRingRotate 3s linear infinite;
	pointer-events: none;
}

body .elementor-button.apex-btn-ring::after {
	content: "";
	position: absolute;
	inset: 1.5px;
	border-radius: inherit;
	background: #000;
	z-index: -1;
	pointer-events: none;
}

/* A moving border is decoration, so it stops when the visitor asks for less
   motion. The ring stays as a static blue edge rather than disappearing. */
@media (prefers-reduced-motion: reduce) {
	body .elementor-button.apex-btn-ring::before {
		animation: none;
		background: var(--apex-blue);
	}
}

/* ==========================================================================
   11. Footer
   Headers take the heading face at their existing 26px, and the column text is
   lifted slightly, as requested. This named Oxanium outright until the brand
   guide arrived; it reads the variable now, like everything else.
   ========================================================================== */
body footer h1,
body footer h2,
body footer h3,
body footer h4,
body footer h5,
body footer h6,
body footer .elementor-heading-title {
	font-family: var(--apex-heading) !important;
	font-size: 26px !important;
	font-weight: 800 !important;
	letter-spacing: 0.02em !important;
	line-height: 1.2 !important;
	text-transform: uppercase;
}

/* Lift the footer content a little without collapsing the band.
   The outer container keeps a top gap: zeroing it put the Apex logo flush
   against the last page section, so it appeared to clip into whatever photo
   that section ended with. Only the inner container is flattened. */
body footer .e-con > .e-con-inner {
	padding-top: 0 !important;
}

body footer > .e-con:first-child {
	padding-top: 72px !important;
}

body footer .elementor-widget-heading {
	margin-top: -0.35rem;
}

body footer .elementor-widget {
	margin-bottom: 0.6rem;
}

/* ==========================================================================
   12. Homepage vertical rhythm
   Top padding on the stacked sections ran 0 / 30 / 60 / 120 / 160px against
   bottom padding of 0 / 60px, so the gap between one section and the next
   varied from 30px to 220px. Setting one top value and clearing the bottom
   makes every gap exactly --apex-section-gap: the pictures above "CERTIFIED
   PROTECTION" now sit 120px from it, the same as everywhere else.

   The first section stays flush (it is the hero) and the last keeps a bottom
   gap so it does not butt against the footer.
   ========================================================================== */
/* Scoped through .page-content deliberately: the <footer> element also carries
   the class "elementor", so a bare `.elementor > .e-con.e-parent` matched the
   footer's own containers and forced 120px onto the copyright bar. */
body.home .page-content .elementor > .e-con.e-parent {
	padding-top: var(--apex-section-gap) !important;
	padding-bottom: 0 !important;
}

body.home .page-content .elementor > .e-con.e-parent:first-child {
	padding-top: 0 !important;
}

body.home .page-content .elementor > .e-con.e-parent:last-child {
	padding-bottom: var(--apex-section-gap) !important;
}

/* Sections also carry a --min-height authored in the Elementor editor. The
   "Certified Protection" one is set to 879px while its content needs 602px,
   so 277px of dead space sat between the button and "Brands We Serve" — on
   top of that section's own 120px. None of these sections has a background
   that depends on the height, so with padding now driving the rhythm the
   authored minimums only fight it. The hero keeps its own. */
body.home .page-content .elementor > .e-con.e-parent:not(:first-child) {
	--min-height: 0 !important;
}

@media (max-width: 767px) {
	:root {
		--apex-section-gap: 72px;
	}
}

/* ==========================================================================
   13. XPEL lockup alignment
   The logo and the "CERTIFIED PROTECTION..." heading are two flex columns
   whose boxes already start at the same y, yet read as misaligned: the PNG
   carries 12px of transparent padding above its artwork on a 278px canvas
   (6.64px once scaled to the rendered 154px), while the heading's cap height
   begins 9.46px below its line box. Nudging the logo down by the 2.8px
   difference puts the top of the artwork level with the top of the "C".
   ========================================================================== */
/* Only while the two sit side by side. The columns stack below 768px, where
   there is nothing to align against and the nudge would just push the logo
   past the section edge. */
@media (min-width: 768px) {
	img.wp-image-2395 {
		margin-top: 2.8px;
	}
}

/* ==========================================================================
   14. Apex blue on titles
   Page titles (h1) and the big section statements (h2) carry the accent —
   "DESIGN. BUILD. DRIVE.", "CUSTOM BUILDS. PREMIUM PROTECTION. UNMATCHED
   CRAFTSMANSHIP.", "CERTIFIED PROTECTION FOR YOUR ENTIRE VEHICLE".

   Everything below h2 stays white so the blue keeps meaning "new section":
   h3 covers small labels (ADDRESS, CALL US, HOURS), service card names and
   the gallery's vehicle titles, and h4 is the footer columns.

   Section 2 already forces -webkit-text-fill-color to currentColor to clear
   the old gradient, so setting color here is what actually paints the glyphs.
   #0072ce on black is 6.2:1 — above AA for both large and normal text.
   ========================================================================== */
body h1,
body h2,
body h1.elementor-heading-title,
body h2.elementor-heading-title,
body .elementor-widget-heading h1.elementor-heading-title,
body .elementor-widget-heading h2.elementor-heading-title {
	color: var(--apex-blue) !important;
}

/* The footer headers were already blue — but #0072ce, a second blue a shade
   darker than the accent. With h1/h2 now carrying --apex-blue, two near-
   identical blues on one page read as a mistake, so the footer text and its
   rule are unified onto the accent.

   The copyright bar's background keeps #0072ce deliberately: it is a filled
   band with white text on it, and white on #0072ce is 3.4:1 — below AA —
   whereas white on #0072ce is 4.6:1 and passes. A darker blue for a filled
   surface and a lighter one for text on black is a real distinction, not an
   inconsistency. */
body footer h4,
body footer h4.elementor-heading-title {
	color: var(--apex-blue) !important;
	border-color: var(--apex-blue) !important;
}

/* ==========================================================================
   15. Footer column alignment
   The footer block, its logo and the social icons all sit dead-centre, but the
   four columns beneath them were left-aligned (text-align: start), so the
   footer read as off-centre even though nothing was. Centring the columns puts
   the whole thing on one axis.

   Scoped to the column row so the bottom bar keeps its left/right split.
   Elementor's list items are flex containers, so they need justify-content as
   well as text-align to actually move.
   ========================================================================== */
body footer .elementor-element-37f278b,
body footer .elementor-element-37f278b h1,
body footer .elementor-element-37f278b h2,
body footer .elementor-element-37f278b h3,
body footer .elementor-element-37f278b h4,
body footer .elementor-element-37f278b p,
body footer .elementor-element-37f278b .elementor-heading-title,
body footer .elementor-element-37f278b .elementor-widget-text-editor,
body footer .elementor-element-37f278b .elementor-icon-list-text {
	text-align: center !important;
}

body footer .elementor-element-37f278b .elementor-icon-list-items,
body footer .elementor-element-37f278b .elementor-icon-list-item,
body footer .elementor-element-37f278b .elementor-icon-list-item > a {
	justify-content: center !important;
}

/* ==========================================================================
   16. Client feedback — Title Case body copy
   Body text rendered as "From Simple Upgrades To Bold Builds" because a
   text-transform: capitalize sat on 49 elements. The source text is already
   correctly sentence-cased, so clearing the transform restores it — and fixes
   "Info@Apexcustoms.Net" back to a real address.

   Checked before applying: the only elements whose source is all-lowercase are
   the two phone numbers, which contain no letters, so nothing renders wrong.
   Buttons, nav and headings set their own uppercase and are untouched.
   ========================================================================== */
body p,
body li,
body .elementor-widget-text-editor,
body .elementor-widget-text-editor *,
body .elementor-icon-list-text,
body .elementor-icon-list-item {
	text-transform: none !important;
}

/* ==========================================================================
   17. Client feedback — hero headline on one line
   "DESIGN. BUILD. DRIVE." needed 1006px at 89.6px but the container is 912px,
   so it wrapped to two lines. 6.2vw puts it at ~79px there — one line with
   room to spare — while the clamp still lets it grow on wide screens and stay
   readable on phones, where it wraps regardless.
   ========================================================================== */
body.home .elementor-element-33cea79 h1,
body.home .elementor-element-33cea79 h1.elementor-heading-title {
	font-size: clamp(2rem, 6.2vw, 5.5rem) !important;
}

/* The "Build Your Dream Vehicle." heading is wrapped in a link to the quote
   form; keep it looking like a heading rather than a link. */
a.apex-heading-link,
a.apex-heading-link:hover {
	text-decoration: none;
	color: inherit;
	display: block;
}

/* ==========================================================================
   18. Hero — centred, and the sub-headline made visible
   The hero column sat left inside its 1140px parent, so the headline, the
   sub-headline and the button all hugged the left edge. Centring the column
   centres all three.

   The sub-headline was #000 on a hero with no background — black on black,
   effectively invisible. Elementor still sets that colour on the widget, so
   the rule below states the fill explicitly rather than only clearing what
   used to sit here. paint-order keeps the stroke behind the fill, so the
   letterforms stay crisp instead of being eaten into from the inside.
   ========================================================================== */
/* The type sits on the concrete, not on the building.

   Measured down the left third of the photograph, the forecourt runs from a
   mean luminance of 184 where the sun catches it at 59% of the frame to 57 at
   74% and 18 at 80%, as the black fade takes over — and its standard deviation
   drops from 23 to 7, so it is flat as well as dark. White type on the
   building above was a median 3.45:1 with its worst patch at 1.51:1; on this
   band it is 5.5:1 at the top of the block and better than 9:1 by the middle
   of it.

   The move is made by anchoring the inner container to the bottom of the band
   rather than by positioning the text, so the block keeps its place in the
   flow and the hero still collapses sensibly when the band hits its 340px
   floor on a phone. The authored 120px of top padding goes with it — it was
   what held the block against the top.

   Left, not centred. The column was centred in an earlier pass because all
   three lines hugged the left edge of a 1140px parent and looked stranded
   there; the client has since asked for the block on the left of the picture,
   which is where the forecourt is emptiest. */
/* 92px, up from 60. The client asked for the headline block to sit "up a tiny
   bit" — this is the only number that moves it, since the block is bottom
   anchored by `justify-content: flex-end` and has no margins of its own. It
   lifts the headline's top from 266px above the band's bottom edge to 298px.

   Two things move with it and must not be left behind: the crop, because the
   car has to rise by the same amount or the clearance the last pass bought is
   spent (prep_hero.py now places the car's bottom at 66% rather than 70%), and
   the gradient in section 28, whose stops are pixel offsets from the same
   bottom edge and are shifted up by the same 32px. */
body.home .elementor-element-33cea79 > .e-con-inner {
	justify-content: flex-end !important;
	padding: 0 0 92px !important;
}

/* Centred across the band, now that the block is down on the forecourt. The
   earlier move to the left was about getting the type off the building rather
   than about the left edge as such, and the forecourt runs the full width, so
   centring costs nothing: measured under the block, centred reads 18.58:1
   against 18.42:1 hard left.

   The 16px lift above is the client's "barely at all", and it is deliberately
   small. Every pixel upward walks the block back toward the lit part of the
   forecourt: at this height the worst 1% of the ground under the type is
   about 3.1:1, at 28px it is 2.46:1, and over the building it was 1.51:1.
   There is not much more room in that direction. */
body.home .elementor-element-cee3068 {
	margin-inline: auto !important;
	text-align: center !important;
}

body.home .elementor-element-cee3068 h1,
body.home .elementor-element-cee3068 p,
body.home .elementor-element-cee3068 .elementor-widget-button,
body.home .elementor-element-cee3068 .elementor-widget-container {
	text-align: center !important;
}

/* The sub-headline is plain white now. It used to carry a 1.2px Apex-blue
   -webkit-text-stroke that pulsed to white and back on the buttons' 3s beat,
   which the client has asked to lose. The colour still has to be stated: the
   theme sets #000 on this widget, and black on a photograph is invisible —
   that was the original bug this rule was written to fix, and it is still
   here after the outline has gone.

   -webkit-text-fill-color is set alongside `color` because the two can
   disagree, and the fill wins where it is supported. The stroke is zeroed
   explicitly rather than simply left out, so that a browser holding the old
   stylesheet cannot keep painting it.

   apexOutlinePulse is left defined in section 10's neighbourhood; nothing
   references it now.
   ========================================================================== */
body.home .elementor-element-cee3068 p {
	color: #ffffff;
	-webkit-text-fill-color: #ffffff;
	-webkit-text-stroke: 0 !important;
	animation: none;
}

/* ==========================================================================
   19. "Build Your Dream Vehicle." aligned with "Tulsa"
   Its column centres its two children vertically, which pushed the headline
   139px down the column while "Tulsa" sat 72px down its own. Anchoring the
   column to the top and matching that 72px lines the two headlines up.
   ========================================================================== */
body.home .elementor-element-7b0c175 {
	justify-content: flex-start !important;
	padding-top: 42px !important;
}

/* ==========================================================================
   20. Footer copyright bar
   120px of padding top and bottom around a single line of text made the blue
   band 266px tall. It now fits its content.
   ========================================================================== */
body footer .elementor-element-a471638 {
	padding-top: 22px !important;
	padding-bottom: 22px !important;
}

/* ==========================================================================
   21. Contact page — same hero furniture removal as the service pages
   285 is the contact page. contact-carrollton and contact-tulsa still carry
   the Jeep and teardrop; add their ids here if they should go too.
   ========================================================================== */
.page-id-285 .elementor-widget-image:has(img.wp-image-77),
.page-id-285 .elementor-widget-image:has(img.wp-image-81) {
	display: none !important;
}

/* ==========================================================================
   23. Services grid card titles
   The six cards under the photos on the homepage — Window Tint, Ceramic
   Coating, Apex Armor, Apex Signature Finish, Wheels & Tires, Paint
   Protection Film — take the accent instead of white.

   Section 14 deliberately stops the blue at h2 so it keeps meaning "new
   section", and these are h3. They are the exception because they are not
   sub-headings inside a section: each one is the label on a link tile, so the
   blue reads as "this is clickable" rather than as a heading level. (This grid
   briefly sat on its own Apex-blue background, section 29, which would have
   made blue titles invisible and moved them to white for that reason — the
   blue background was removed and this reverted with it.)

   Scoped to the two grid containers, not to their parent section. Two traps
   sit either side of that choice:

   - The parent section (cc789bb) also holds the ADDRESS / CALL US / HOURS
     labels for both locations, which must stay white.
   - cc789bb is reused on eight service pages, where it holds a bullet list of
     benefits ("Heat and UV rejection", "Comprehensive coverage options")
     rather than the services grid. Scoping to the section would have turned
     those blue too.

   3c2c465 is the live grid; dfc0fb8 is a second copy that is hidden at every
   breakpoint. Both are listed so they cannot drift apart if that copy is ever
   switched back on. Neither id appears on any other page, and body.home keeps
   it that way. Section 2 already forces -webkit-text-fill-color to
   currentColor on h3, so setting colour here is what paints the glyphs.
   ========================================================================== */
/* 700, not the 600 these carried before, and the reason is contrast rather
   than taste. APEX BLUE on black is 4.30:1. WCAG counts text as "large" — and
   drops the bar from 4.5 to 3 — at 24px, or at 18.66px when it is bold. These
   titles are 22px, so at 600 they were normal text sitting 0.2 short of the
   threshold; at 700 they are large text and clear it with room. Nothing else
   on the site is set in this blue below 24px, so this is the only rule that
   needed it. The brand guide asks for Bold on headline type in any case. */
body.home .elementor-element-3c2c465 h3,
body.home .elementor-element-3c2c465 h3.elementor-heading-title,
body.home .elementor-element-dfc0fb8 h3,
body.home .elementor-element-dfc0fb8 h3.elementor-heading-title {
	color: var(--apex-blue) !important;
	font-weight: 700 !important;
}

/* ==========================================================================
   24. Brand wall  ("Brands We Serve", homepage) — three marquee rows,
   forty-fifth pass
   Was a static 9-column grid of light tiles (before that, two flat 1800x200
   PNGs — see git history / README for that lineage). The client's feedback
   this pass was that the grid "feels out of place compared to the rest of
   the design" and stretched edge-to-edge on a phone; both are symptoms of the
   same thing, a light static block dropped into an otherwise dark, always-
   moving page (the site now has two other full-bleed marquees: builds,
   filters). Converted to the same mechanism as "Recent Builds & Inspirations"
   (section 54): fixed-width tiles on a flex track, doubled and translated
   -50% for a seamless loop, one animation per row.

   THREE ROWS NOW, NOT ONE GRID, grouped and ordered exactly as the client
   specified: top row trucks/SUVs/off-road scrolling right, middle row
   luxury/exotic/premium scrolling left, bottom row electric/everyday/other
   scrolling right. customise.py's BRAND_TIERS holds the membership and
   direction per row; the CSS below only has to know there are three rows
   that alternate.

   The dark tile went LIGHT because on a black page a flat monochrome mark and
   full colour both failed (see the old note in git history); moving to a
   marquee did not change that trade-off, so the tile keeps its own light
   surface and the knock-back-then-reveal-on-hover treatment — hover still
   works mid-scroll, because CSS hover tracks whatever element is actually
   under the pointer regardless of its animated position.

   REPEATED TILES ARE NOT FOCUSABLE. Same call as the builds marquee: each row
   is written BRAND_REPEAT times over so the loop never runs dry on a wide
   monitor, and a keyboard user tabbing through four identical copies of
   "Ford" adds nothing a single stop wouldn't. `tabindex="-1"` on every tile
   and `aria-hidden="true"` on the wall; the six makes that link into the
   gallery are reachable from the gallery's own Make filter.
   ========================================================================== */
:root {
	/* Light, but deliberately not white — the client asked for a tile that sits
	   with the site rather than glaring out of it. Pulled slightly cool so it
	   agrees with --apex-blue instead of reading as neutral grey.
	   Alternatives worth trying: warmer #E4E2DF, or darker #C9D0D7. */
	--apex-tile: #DCE1E6;
	--apex-tile-hi: #ECEFF2;
	--apex-tile-edge: rgba(255, 255, 255, 0.55);
}

/* Full-bleed, the same technique as .apex-builds (section 54): negative
   margins out to the section's own 60px padding rather than 100vw, which
   would count the scrollbar and overflow by its width. */
body .apex-brandwall {
	width: auto !important;
	margin: 0 -60px;
	padding: 0;
	list-style: none;
	overflow: hidden;
}

.apex-brand-row {
	overflow: hidden;
	margin-bottom: 14px;
}

.apex-brand-row:last-child {
	margin-bottom: 0;
}

.apex-brand-track {
	display: flex;
	width: max-content;
	will-change: transform;
	backface-visibility: hidden;
	transform: translate3d(0, 0, 0);
}

/* The tile: FIXED width now (170px), not a grid column that shares out the
   row — a flex track needs every tile the same width or -50% does not land
   on an exact copy boundary, the same reason the builds cards are 300px
   flat rather than sized to their column. */
/* [class] on the selector, not just decorative — same trap as .apex-brand-
   logo's radius line above. Elementor's own widget-image plugin CSS ships
   `.elementor-widget-image a { display: inline-block }`, (0,1,1), which beat
   a bare `.apex-brand` at (0,1,0): the tile rendered `block`, flex never
   applied, and every logo sat at the TOP of its padding box regardless of
   align-items — that was the actual "not centred" bug, not the padding.
   `[class]` bumps this to (0,2,0). */
.apex-brand[class] {
	position: relative;
	display: flex;
	flex: 0 0 auto;
	width: 170px;
	margin-right: 20px;
	align-items: center;
	justify-content: center;
	height: 96px;
	/* Symmetric now — the client asked for the logos centred. The old 14/20
	   split (forty-fifth pass) reserved extra room at the bottom for
	   .apex-brand-name, but that label is `position: absolute` and was never
	   actually in flow, so the asymmetry bought nothing but an off-centre
	   logo: 19px above it, 55px below, on a 96px tile. */
	padding: 17px 16px;
	border-radius: 10px;
	background: linear-gradient(168deg, var(--apex-tile-hi), var(--apex-tile));
	box-shadow: inset 0 1px 0 var(--apex-tile-edge);
	text-decoration: none;
	overflow: hidden;
	transition: transform 0.22s cubic-bezier(0.22, 0.61, 0.36, 1),
	            box-shadow 0.22s ease,
	            background 0.22s ease;
}

/* Width and height are set inline per logo — they are optically balanced by ink
   coverage, not matched heights, so a tall badge like Rolls-Royce and a wide
   wordmark like Range Rover carry the same visual weight. See prep_brandwall.py.

   grayscale alone leaves the marks looking washed out rather than deliberate;
   the brightness drop is what makes them read as one material, and the contrast
   bump keeps knockout lettering from muddying into its own badge. */
/* [class] is load-bearing on the radius line, not decorative: every logo was
   still rendering at section 4's 12px. That rule reaches `.elementor-widget-
   image img` — element + class, (0,1,1) — which outranks a bare `.apex-
   brand-logo` at (0,1,0) even with !important on both. The doubled selector
   bumps this rule to (0,2,0), the same trick as the `[class][class]`
   Elementor-container fills need elsewhere in this file. */
.apex-brand-logo {
	display: block;
	max-width: 100%;
}

.apex-brand-logo[class] {
	border-radius: 0 !important;   /* section 4 rounds every widget image */
	/* 0.5 rather than lower: artwork that is already black is unaffected by a
	   brightness drop (0 stays 0), so pushing it further only crushed the
	   badges whose detail is knocked out in white — BMW's roundel, Cadillac's
	   crest, the Harley shield — into solid blobs. */
	filter: grayscale(1) brightness(0.5) contrast(1.12);
	transition: filter 0.22s ease, transform 0.22s ease;
}

/* The make name, revealed under the mark. Space is reserved by the tile's
   bottom padding so nothing shifts when it appears. */
.apex-brand-name {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 9px;
	font-family: var(--apex-display);
	font-size: 0.5625rem;
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	text-align: center;
	color: #4A525B;
	opacity: 0;
	transition: opacity 0.22s ease;
}

/* Every tile behaves the same on hover — colour, lift and name — whether or not
   it links anywhere. Reserving the movement for the linked ones made the
   wall look broken rather than informative: hovering Ferrari and getting
   nothing reads as a bug, not as "there is no Ferrari gallery". The cursor
   still tells you which are clickable, since only the links are anchors.
   Selector is `.apex-brand` directly now — the tile is several levels under
   `.apex-brandwall` (row, then track), not a direct child of it. */
.apex-brand:hover,
.apex-brand:focus-visible {
	transform: translateY(-4px);
	background: linear-gradient(168deg, #F4F6F8, var(--apex-tile-hi));
	box-shadow: 0 10px 26px rgba(0, 0, 0, 0.45),
	            inset 0 1px 0 rgba(255, 255, 255, 0.8);
	outline: none;
}

a.apex-brand:focus-visible {
	box-shadow: 0 0 0 2px var(--apex-blue),
	            0 10px 26px rgba(0, 0, 0, 0.45);
}

.apex-brand:hover .apex-brand-logo,
.apex-brand:focus-visible .apex-brand-logo {
	filter: none;
	transform: translateY(-3px);
}

.apex-brand:hover .apex-brand-name,
.apex-brand:focus-visible .apex-brand-name {
	opacity: 1;
}

/* translate3d, not translateX — same reasoning as the builds marquee
   (section 54): the 2-D form can leave the track on the main thread and the
   drift arrives in visible steps, backface-visibility pins the composited
   layer so it is rasterised once rather than repainted every frame. */
@keyframes apexBrandsRight {
	from { transform: translate3d(-50%, 0, 0); }
	to   { transform: translate3d(0, 0, 0); }
}

@keyframes apexBrandsLeft {
	from { transform: translate3d(0, 0, 0); }
	to   { transform: translate3d(-50%, 0, 0); }
}

/* Duration is set per row by customise.py as an inline custom property, from
   that row's own tile count — one pace whatever a row holds, the same reason
   the builds marquee's duration is computed rather than fixed. Fallbacks are
   what the current three rows measure. */
.apex-brand-row--right .apex-brand-track {
	animation: apexBrandsRight var(--apex-brand-duration, 268s) linear infinite;
}

.apex-brand-row--left .apex-brand-track {
	animation: apexBrandsLeft var(--apex-brand-duration, 380s) linear infinite;
}

@media (prefers-reduced-motion: reduce) {
	.apex-brand-track {
		animation-play-state: paused;
	}

	.apex-brand,
	.apex-brand-logo,
	.apex-brand-name {
		transition: none;
	}

	a.apex-brand:hover,
	a.apex-brand:focus-visible {
		transform: none;
	}

	a.apex-brand:hover .apex-brand-logo,
	a.apex-brand:focus-visible .apex-brand-logo {
		transform: none;
	}
}

/* Tighter on a phone — smaller tile, tighter margin, same fixed-width flex
   mechanism. The old grid could not do this at all: it scaled as one image
   and collapsed to 370x41 (roughly 21px per logo) before the wall existed. */
@media (max-width: 600px) {
	body .apex-brandwall {
		margin: 0 -20px;
	}

	.apex-brand {
		width: 112px;
		height: 72px;
		margin-right: 12px;
		padding: 10px;
		border-radius: 7px;
	}

	/* No hover on touch, so the name would never show — reclaim its space. */
	.apex-brand-name {
		display: none;
	}
}

/* The two leftover strips. There were three in all, not two: the luxury row
   (14e3784, replaced in place by the wall), a second row (e8d924a), and a third
   further down the page (d9e93dd) holding the sixteen newer brands — Tesla
   through MasterCraft. All 45 of those marks are in the wall above, so both
   remaining strips are now just white bars.

   Hidden rather than deleted: the markup is Elementor's and must stay
   reversible. If a fourth ever appears, look for an <img> at 1800x200. */
body.home .elementor-element-e8d924a,
body.home .elementor-element-d9e93dd {
	display: none !important;
}

/* The wall's own section carried 10px of padding sized for a 138px-tall strip.
   The grid is much taller, so it needs the page's normal breathing room. */
body.home .elementor-element-14e3784 > .e-con-inner {
	padding-top: 0 !important;
	padding-bottom: 0 !important;
}

/* ==========================================================================
   25. Header dropdown opens instead of appearing
   The Services menu is driven by SmartMenus, the jQuery plugin Elementor ships
   with its nav widget, and it works by toggling `display` between none and
   block. `display` is not an animatable property, so the panel simply existed
   or did not — a hard flash of a 156x368 white rectangle.

   The panel is therefore left permanently displayed and hidden by other means:
   opacity, visibility and a small translate, all of which do animate. Toggling
   `display` underneath now changes nothing visually, so SmartMenus can carry on
   doing exactly what it did while this file owns the appearance.

   `visibility` matters as well as opacity. It keeps a panel that is only
   transparent out of the accessibility tree and out of the tab order, so a
   keyboard user does not travel through eight invisible links. It is
   transitioned at 0s but delayed on the way out, so it holds until the fade
   has finished rather than snapping away at the start.

   Scoped to .elementor-nav-menu--main, which is the desktop nav. The mobile
   menu is a separate <nav class="elementor-nav-menu--dropdown"> that opens from
   the hamburger, and forcing display on it would jam it open.
   ========================================================================== */
@media (min-width: 1025px) {
	body .elementor-nav-menu--main .sub-menu {
		display: block !important;   /* beats SmartMenus' inline toggle */
		opacity: 0;
		visibility: hidden;
		pointer-events: none;
		transform: translateY(-9px) scale(0.985);
		transform-origin: top center;

		/* Softened so the reveal lands on a panel rather than a bare rectangle:
		   square corners and no shadow made even a smooth fade read as a flash. */
		border-radius: 10px;
		overflow: hidden;
		box-shadow: 0 20px 44px rgba(0, 0, 0, 0.5),
		            0 0 0 1px rgba(0, 0, 0, 0.07);

		/* Fast in, slower settle. The curve is heavily weighted to the end, so
		   the panel arrives quickly and eases to a stop instead of gliding at a
		   constant speed — which is what reads as "smooth" rather than "slow". */
		transition: opacity 0.24s ease,
		            transform 0.4s cubic-bezier(0.32, 0.72, 0, 1),
		            visibility 0s linear 0.28s;
	}

	body .elementor-nav-menu--main .menu-item-has-children:hover > .sub-menu,
	body .elementor-nav-menu--main .menu-item-has-children:focus-within > .sub-menu {
		opacity: 1;
		visibility: visible;
		pointer-events: auto;
		transform: translateY(0) scale(1);
		transition: opacity 0.2s ease,
		            transform 0.4s cubic-bezier(0.32, 0.72, 0, 1),
		            visibility 0s;
	}

	/* The items rise in just behind the panel. Small numbers on purpose — the
	   whole sequence is over inside a third of a second, so it reads as the
	   menu having depth rather than as a queue of things arriving. */
	body .elementor-nav-menu--main .sub-menu > li {
		opacity: 0;
		transform: translateY(-7px);
		transition: opacity 0.22s ease, transform 0.32s cubic-bezier(0.32, 0.72, 0, 1);
	}

	body .elementor-nav-menu--main .menu-item-has-children:hover > .sub-menu > li,
	body .elementor-nav-menu--main .menu-item-has-children:focus-within > .sub-menu > li {
		opacity: 1;
		transform: translateY(0);
	}

	body .elementor-nav-menu--main .menu-item-has-children:hover > .sub-menu > li:nth-child(1),
	body .elementor-nav-menu--main .menu-item-has-children:focus-within > .sub-menu > li:nth-child(1) { transition-delay: 0.03s; }
	body .elementor-nav-menu--main .menu-item-has-children:hover > .sub-menu > li:nth-child(2),
	body .elementor-nav-menu--main .menu-item-has-children:focus-within > .sub-menu > li:nth-child(2) { transition-delay: 0.055s; }
	body .elementor-nav-menu--main .menu-item-has-children:hover > .sub-menu > li:nth-child(3),
	body .elementor-nav-menu--main .menu-item-has-children:focus-within > .sub-menu > li:nth-child(3) { transition-delay: 0.08s; }
	body .elementor-nav-menu--main .menu-item-has-children:hover > .sub-menu > li:nth-child(4),
	body .elementor-nav-menu--main .menu-item-has-children:focus-within > .sub-menu > li:nth-child(4) { transition-delay: 0.105s; }
	body .elementor-nav-menu--main .menu-item-has-children:hover > .sub-menu > li:nth-child(5),
	body .elementor-nav-menu--main .menu-item-has-children:focus-within > .sub-menu > li:nth-child(5) { transition-delay: 0.13s; }
	body .elementor-nav-menu--main .menu-item-has-children:hover > .sub-menu > li:nth-child(6),
	body .elementor-nav-menu--main .menu-item-has-children:focus-within > .sub-menu > li:nth-child(6) { transition-delay: 0.155s; }
	body .elementor-nav-menu--main .menu-item-has-children:hover > .sub-menu > li:nth-child(7),
	body .elementor-nav-menu--main .menu-item-has-children:focus-within > .sub-menu > li:nth-child(7) { transition-delay: 0.18s; }
	body .elementor-nav-menu--main .menu-item-has-children:hover > .sub-menu > li:nth-child(n+8),
	body .elementor-nav-menu--main .menu-item-has-children:focus-within > .sub-menu > li:nth-child(n+8) { transition-delay: 0.2s; }

	/* Closing is immediate for the items — staggering the exit as well makes
	   the menu feel reluctant to leave. */
	body .elementor-nav-menu--main .sub-menu > li {
		transition-delay: 0s;
	}

	/* The panel sits directly under the tab, but sub-pixel rounding can leave a
	   hairline gap that drops the hover and closes the menu mid-reach. This
	   bridges it without shifting the panel. */
	body .elementor-nav-menu--main .menu-item-has-children > .sub-menu::before {
		content: "";
		position: absolute;
		left: 0;
		right: 0;
		top: -6px;
		height: 6px;
	}

	@media (prefers-reduced-motion: reduce) {
		body .elementor-nav-menu--main .sub-menu,
		body .elementor-nav-menu--main .sub-menu > li {
			transition: none;
			transform: none;
		}
	}
}


/* ==========================================================================
   26. Nav underline — pill shape, left-to-right sweep
   Elementor ships this nav with pointer style "underline" and animation
   "fade" (see the widget's own CSS,
   wp-content/plugins/elementor-pro/assets/css/widget-nav-menu.min.css): a
   full-width 3px bar sat permanently at left:0/width:100%, shown or hidden by
   toggling opacity. "Fade" has no grow, slide or draw animation at all — the
   bar does not move, it simply appears.

   Rebuilt as a reveal instead of a fade: the bar is always opacity 1, and a
   transform-origin:left scaleX (0 -> 1) grows it from the left edge to the
   right on hover, focus and on the active page's own tab, retracting the same
   way when the pointer leaves. Corners are rounded into a pill; height stays
   at Elementor's original 3px, so this reads as a variant of the stock
   underline rather than a new element replacing it.

   Scoped to .elementor-nav-menu--main .elementor-item, the desktop top-level
   links only. Dropdown items use a different class (.elementor-sub-item), so
   the Services panel is untouched by this rule.
   ========================================================================== */
body .elementor-nav-menu--main .elementor-item::after {
	height: 3px !important;
	border-radius: 999px !important;
	opacity: 1 !important;
	transform: scaleX(0);
	transform-origin: left center;
	transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1) !important;
}

body .elementor-nav-menu--main .elementor-item:hover::after,
body .elementor-nav-menu--main .elementor-item:focus::after,
body .elementor-nav-menu--main .elementor-item-active::after,
body .elementor-nav-menu--main .elementor-item.highlighted::after {
	transform: scaleX(1);
}

/* No sliding for a visitor who has asked for less motion — the tab still
   shows which page is current, it just appears rather than growing in. */
@media (prefers-reduced-motion: reduce) {
	body .elementor-nav-menu--main .elementor-item::after {
		transform: scaleX(1) !important;
		opacity: 0;
		transition: opacity 0.2s ease !important;
	}

	body .elementor-nav-menu--main .elementor-item:hover::after,
	body .elementor-nav-menu--main .elementor-item:focus::after,
	body .elementor-nav-menu--main .elementor-item-active::after,
	body .elementor-nav-menu--main .elementor-item.highlighted::after {
		opacity: 1;
	}
}

/* ==========================================================================
   27. Gallery card hover — whole card, Apex-blue border
   Each card in the gallery grid (.e-loop-item) is one big <a> — the photo and
   the caption underneath are the same clickable element, not two separate
   pieces — so the border wraps that single anchor and the photo and text light
   up together.

   The border fades in rather than sweeping in the direction of section 25's
   nav pill or the header dropdown's reveal. Both of those techniques are
   directional (left-to-right, top-down) because they animate a single edge or
   a single axis; a border runs around all four sides of a box with no natural
   "direction" to sweep from, so forcing one on would mean animating each edge
   on its own delay, which reads as gimmicky on a dense grid of many cards
   rather than smooth. What carries over instead is the same feel: the eased,
   slightly-overshooting curve already used for the pill and the dropdown, plus
   the lift-and-shadow language already established on the brand wall and
   partner tiles elsewhere on this site — so this reads as the same family of
   motion without borrowing an effect that does not fit the shape.

   12px radius matches the photo radius (section 4) exactly, so the border
   traces the same corner the image already has rather than a mismatched one.
   Scoped to the gallery loop grid specifically, so this never reaches a card
   layout used elsewhere on the site.
   ========================================================================== */
.elementor-loop-container .e-loop-item > a {
	display: block;
	border-radius: 12px;
	border: 2px solid transparent;
	box-shadow: 0 0 0 rgba(0, 114, 206, 0);
	transform: translateY(0);
	transition: border-color 0.35s cubic-bezier(0.16, 1, 0.3, 1),
	            box-shadow 0.35s cubic-bezier(0.16, 1, 0.3, 1),
	            transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}

.elementor-loop-container .e-loop-item > a:hover,
.elementor-loop-container .e-loop-item > a:focus-visible {
	border-color: var(--apex-blue);
	box-shadow: 0 14px 32px rgba(0, 0, 0, 0.45),
	            0 0 0 4px rgba(0, 114, 206, 0.18);
	transform: translateY(-4px);
	outline: none;
}

@media (prefers-reduced-motion: reduce) {
	.elementor-loop-container .e-loop-item > a {
		transition: border-color 0.2s ease, box-shadow 0.2s ease;
	}
	.elementor-loop-container .e-loop-item > a:hover,
	.elementor-loop-container .e-loop-item > a:focus-visible {
		transform: none;
	}
}

/* ==========================================================================
   28. Hero — storefront photo, taller ratio, blue gradient scrim
   The hero band (section 33cea79, "DESIGN. BUILD. DRIVE.") carried a single
   fixed --min-height:447px at every breakpoint, and its background was a
   live YouTube embed. 447px against a page that runs 1200px+ wide is a
   2.8:1 band, nearly three times as wide as it is tall — that cropped away
   well over a third of the video's vertical frame.

   min-height is a clamp instead of a fixed pixel value: a floor close to the
   original on narrow screens, scaling with viewport width in between, and a
   ceiling so it never turns into a full-bleed banner on an ultrawide
   monitor. One rule instead of Elementor's usual three breakpoint-specific
   ones, and it never has a viewport width it was not tuned for.

   The video is now the client's own storefront photo. elementor-background-
   video-embed is where Elementor's JS would inject the YouTube iframe — it's
   hidden rather than deleted so nothing ever paints there, but the
   surrounding elementor-background-video-container (still the real thing:
   position:absolute, inset:0, full-bleed) is what the photo goes on, because
   that is the element already proven to sit behind the hero text and above
   nothing else — a scrim on the *section* itself would sit on top of its own
   descendants, text included, since a background paints behind an element's
   content but a positioned ::after does not.

   The gradient is heavier than the old video-only version: a photo of a
   bright daytime storefront has light sky and glass through most of the
   frame, not just at the edges, so the scrim now holds some presence
   top-to-bottom instead of starting fully transparent.
   ========================================================================== */
/* 38vw, which holds the band at a constant 2.63:1 — and 2.63 is not a taste,
   it is the shape an ultrawide was already getting.

   The band used to be 51vw, the photograph's own 1.960:1, so the whole frame
   showed with nothing cropped. That is true only until the 1300px cap binds,
   and on a 3440x1440 monitor it binds hard: the band is 3425x1300, a 2.63:1
   window over a 1.960:1 picture, and `cover` fills it by showing the **bottom
   74%** of the frame. The car comes up huge and its roof runs off the top.
   That is the look the client has been seeing and asked to have everywhere.

   On a 16:9 screen the same 51vw gave 1905x979 — 1.945:1, near enough the
   file's own shape that the whole frame showed and the car sat small in the
   middle of it. Same rule, two completely different pictures, because the cap
   only ever bit on the wide monitor.

   38vw held the ultrawide's exact 2.63 everywhere, which matched the crop but
   cut the car in half — the client could not see the Corvette. 44vw is the
   answer to both: 2.27:1, which is the widest band that still shows the whole
   car once the crop stopped zooming (prep_hero.py now takes the source's full
   width, putting the car at 50% of the frame's height rather than 68%).

       1280   1265x563   2.25   shows 87%   car whole
       1920   1905x839   2.27   shows 86%   car whole
       2560   2545x1121  2.27   shows 86%   car whole
       3440   3425x1300  2.63   shows 74%   roofline trimmed (the cap)

   **The trade, stated because it cannot be avoided:** on 16:9 the hero goes
   from 91% of the screen's height to 67%. The two cannot both be matched — a
   16:9 window is shorter relative to its width than a 21:9 one, so holding the
   crop means giving up the height. The crop is what was asked for, and a
   shorter hero also puts the section below it on screen, which is what the
   client asked for in an earlier round.

   Above about 3421px the cap binds and nothing changes from before. */
body.home .elementor-element-33cea79 {
	min-height: clamp(340px, 44vw, 1300px) !important;
}

/* Forty-ninth pass — mobile only, desktop's clamp above untouched.
   The mobile floor (340px) is short enough that the headline/sub-headline/
   button block — a fixed pixel height, not something that shrinks with the
   band — takes up 240 of the 340px (71%), leaving almost nothing for the
   photograph to show through clear of text regardless of which end it's
   anchored to. `cover` on this box shows the WHOLE height of the source
   photo already (the box is proportionally taller/narrower than the image,
   so cover crops the sides, never the top or bottom) — there is no crop
   position left to reach for. The only real lever is more band, so the same
   fixed-height text block is a smaller fraction of it.

   Fifty-first pass — brought back down to 420px (was 480) and the text
   block pushed toward the bottom of it. 480px worked but read as too much
   band on its own; the client's actual ask was for the type to clear the
   car, not for the section to be as tall as possible. `.e-con-inner`'s
   `padding-bottom` is what anchors the text block's distance from the
   band's own bottom edge (`justify-content: flex-end`) — Elementor's
   authored 92px (same value desktop uses, at a band 700px+ tall) left the
   type sitting in the band's upper two-thirds on mobile; 16px drops it to
   the very bottom of the (now shorter) band, over the pavement rather than
   the car's cabin, the same logic as desktop's own bottom-anchored type
   just carried further because there is far less band to work with here.

   Fifty-second pass — 420px still wasn't enough: the car's silhouette in
   THIS photo (cover, full height always visible per the note above) sits at
   a fixed 17%-64% of the band regardless of band height, so at 420px the
   text block's own fixed ~240px height inevitably reached up into that
   band — measured, the headline started 93px above where the car's own
   lower edge fell. Two changes, not one: 540px gives the pavement-only
   region below the car enough absolute pixels to hold the text block, and
   the text block itself was trimmed (tighter gaps, smaller sub-headline,
   a touch less button padding) rather than relying on height alone, so the
   band didn't have to grow as far as pure geometry would otherwise demand.

   Fifty-fifth pass — that fix cropped the car to reach it, which is the
   thing the client actually objected to next ("shrink the corvette image
   so it shows the full car"). `contain` (the whole photograph, letterboxed
   against black) did show all of it, but the client's actual reaction to
   THAT was to dislike the result — a ~199px car adrift in a mostly-black
   band read as small and empty next to desktop's own confident, frame-
   filling shot, and the ask became "like the desktop, but on mobile,
   showing the car."

   Neither pure `cover` nor pure `contain` gets both at once: `cover` at
   this box's narrow aspect ratio is always height-bound, which crops the
   SIDES to fill the height — tight enough that it was cutting into the car
   itself, not just the empty pavement around it (measured against the
   photograph: the car's own bumper-to-spoiler span is ~2227px of the
   3400px original, wider than `cover`'s ~1614px visible slice). `contain`
   crops nothing but leaves the car small. The fix is a crop in between —
   sized and positioned by hand rather than by either keyword, wide enough
   to hold the car's full width (2227px plus ~10% breathing room ≈ 2672px)
   with no vertical letterboxing at that width. That is what desktop's own
   `cover` amounts to at ITS aspect ratio (wide enough that height, not
   width, is the tight dimension, so the crop falls on sky/ground instead
   of the car) — mobile just needs its own numbers to land in the same
   place, because its box is a different shape. */
@media (max-width: 767px) {
	body.home .elementor-element-33cea79 {
		min-height: 380px !important;
	}

	body.home .elementor-element-33cea79 .e-con-inner {
		padding-bottom: 16px !important;
	}

	body.home .elementor-element-33cea79 .elementor-element-cee3068 {
		row-gap: 6px !important;
	}

	body.home .elementor-element-33cea79 .elementor-element-73c84cc p {
		font-size: 16px !important;
		line-height: 21px !important;
		margin-bottom: 6px !important;
	}

	body.home .elementor-element-33cea79 .elementor-element-684c01b .elementor-button {
		padding: 11px 30px !important;
	}
}

/* Descendant, not direct-child: the container sits inside .e-con-inner,
   one level below the section itself
   (33cea79 > .e-con-inner > .elementor-background-video-container). A `>`
   combinator here silently matched nothing and the scrim never appeared —
   caught by checking .matches() against the real DOM in the browser rather
   than assuming the nesting. */
body.home .elementor-element-33cea79 .elementor-background-video-embed {
	display: none !important;
}

body.home .elementor-element-33cea79 .elementor-background-video-container {
	position: absolute !important;
	inset: 0 !important;
	overflow: hidden;
	/* The fade is a gradient here rather than something baked into the file, and
	   the stops are in **pixels up from the bottom of the band**, not
	   percentages. That is the whole point of it.
	
	   The hero's type block is a fixed height and sits about 260px above the
	   band's bottom edge whatever the window — the headline's top lands at 61%
	   of the band at 1280px, 73% at 1920 and 79% at the cap. A fade anchored to
	   the photograph covers it at one width and misses at another; the baked
	   version measured 3.17 at the worst patch at 1920 and **1.02 at 1280**,
	   where the headline climbs into the car's rear flank. Anchored to the band
	   in pixels, the same gradient sits over the same part of the type at every
	   width.
	
	   Measured behind the headline, after the gradient, at four widths:
	
	       1280   worst 3.78   median 5.43
	       1600   worst 3.59   median 5.21
	       1920   worst 3.67   median 5.08
	       2560   worst 3.56   median 5.03
	
	   against the storefront hero's 5.77 worst. 3.40 clears the 3:1 AA floor for
	   type at 88px everywhere. A harder gradient was measured too — worst 4.35
	   to 4.70 — and it takes the car's bottom edge to 92% black at 1280 against
	   this one's 86%. That edge is the shadow under the car more than the car,
	   but the client asked for the Corvette to be the main stage and this is the
	   shallowest gradient that still clears the floor. */
	background-image:
		linear-gradient(to top,
			rgba(0, 0, 0, 1) 0px,
			rgba(0, 0, 0, 0.95) 152px,
			rgba(0, 0, 0, 0.80) 282px,
			rgba(0, 0, 0, 0.55) 392px,
			rgba(0, 0, 0, 0.22) 532px,
			rgba(0, 0, 0, 0) 672px),
		url("img/homepage-hero-corvette-stingray-hangar-apex-custom-innovations.jpg");
	background-size: 100% 100%, cover;
	background-repeat: no-repeat, no-repeat;

	/* Black behind both layers. The band's height is 51vw and so fractional at
	   almost every width, and a fractional box means the last device row of the
	   photograph is sampled and blended rather than drawn — with nothing stated
	   underneath, that row came out as a pale hairline of the concrete the frame
	   ends on. prep_hero.py now paints the file's own bottom edge black as the
	   first guard; this is the second, and between them there is nothing bright
	   left at that edge for a rounding error to find. */
	background-color: #000000;

	/* The photograph is still anchored to the BOTTOM. Above the 1300px cap the
	   crop eats upward into concrete, which is the part of this window with
	   nothing in it. The gradient is sized to the box, so its own position is
	   moot — it is stated for both layers only because the shorthand takes a
	   list. */
	background-position: 50% 100%, 50% 100%;
}

/* Forty-eighth pass — mobile only, desktop untouched.
   The desktop gradient's stops are pixel offsets FROM THE BOTTOM, tuned so
   the same darkening sits behind the same part of the type across every
   desktop width — the whole design depends on the band's height staying in
   the 700-1300px range those offsets (up to 672px) were measured against.

   Below 767px the band is clamped to its 340px FLOOR, not scaled down with
   it, so the same 672px-tall gradient no longer fits inside a 340px box: at
   the very TOP of the band — 340px up from the bottom, where the gradient
   interpolates between its 282px (80%) and 392px (55%) stops — the
   photograph is still under roughly two-thirds black, and every pixel below
   that climbs toward the 0px stop's full black. The client's own screenshot
   showed the car essentially gone; this is why.

   The type sits differently on mobile too — near the TOP of the band (8-69px
   for the headline, to 162px for the sub-headline, to 248px for the button),
   not anchored to the bottom the way desktop's is. So the fix is not "the
   same gradient, scaled": it is inverted, `to bottom` instead of `to top`,
   strongest over the band the headline and sub-headline actually occupy,
   clearing above and below that band so BOTH ends of the photograph —
   whatever sits above the type and the stretch below the button — are left
   alone.

   Re-measured for the forty-ninth pass's 480px band (was 340px): with the
   same fixed-height text block now a smaller fraction of a taller band, the
   type sits at 148-388px instead of 8-248, which moved the whole gradient
   down with it — this is not the same shape scaled, the stops changed
   because the text's own pixel position changed, not because the band's
   height did.

   A second fade at the very bottom, same pass, then a THIRD revision this
   pass: the "dark behind the type, clear again below it, dark again at the
   very bottom" shape read as the fade sitting in the wrong place — the
   heaviest darkening was in the upper-middle (behind the headline), with a
   bright "window" reopening below it before darkening again at the edge.
   Desktop does not do this: its gradient runs ONE direction, clear at the
   top of the band and progressively darker toward the bottom, nothing else.
   Mobile now matches that same shape — a single monotonic fade, never
   lightening once it starts darkening — rather than the wave the last two
   passes built. The curve still has to carry the type: 0.55 by 150px (the
   headline's own top) and 0.78 by 300px (through the sub-headline) are
   close to what the old spike-in-the-middle gave the text, just reached by
   climbing steadily instead of by a separate dark island.

   Re-measured again for the fifty-first pass's 420px band and its own
   16px-from-the-bottom text (was 480px/92px): the type now runs 164-404px,
   almost to the band's own edge, so there is barely any clear stretch left
   below the button — the curve has to stay dark nearly to 420px instead of
   easing off by 400 the way the 480px version could.

   Fifty-fifth pass, revised again the same day — the `contain` version
   above lasted one round: full car, but small and adrift in black, and the
   client asked for the desktop treatment instead ("like the desktop... but
   showing the car"). The custom crop described above (see `min-height`)
   fills the band edge to edge again the way `cover` used to, so the
   gradient's job reverts too — it is back to carrying type over a
   photograph, not separating the two. The type sits at 187-364px of a
   380px band; the difference from every earlier version of this same
   problem is that the crop is now WIDE (the car's full width plus margin,
   not a tight zoomed slice), so the car's own busiest region — the
   cabin/greenhouse — reads smaller within the frame than it did at any
   previous zoom level, and a moderate curve (light over the roofline,
   ramping to dark by the headline's own top at 190px) is enough to carry
   the text without needing to fog the photograph the way the old tighter
   crops did.

   Same day, one more round — the client asked for the car moved up
   further and the same curve darkened. `background-position-y` on the
   photo layer only (79% 30% → 79% 10%) is what moves the car: with the
   image (253px) shorter than the band (380px), that percentage is how
   much of the leftover 127px sits above the car versus below it — 10%
   leaves ~13px of clearance under the fixed header rather than 30%'s
   ~38px. The gradient's own stop POSITIONS are untouched, exactly as
   asked ("same place") — only each stop's opacity went up (0.05→0.10,
   0.12→0.20, 0.55→0.62, 0.78→0.85, 0.9→0.95), same shape, uniformly
   heavier.

   Same day, yet another round — "car picture is slightly to the left, can
   we center it." It was: the 79% chosen for `background-position-x` back
   when this crop was built was estimated by eye from a downscaled preview,
   not measured. Actually measuring it — sampling the source photograph's
   own pixels (its blue paint is the reliable signal; its black trim near
   the edges, cross-checked separately) — put the car's true left and right
   extremes at x≈560 and x≈2710 of the 3400px original, center x≈1635. 79%
   was centered on x≈1912 instead, close enough to the car's own right edge
   that the visible window's LEFT edge (measured: x≈574) was already past
   the car's own left edge (560) — the old crop was clipping a sliver off
   the rear spoiler, not just leaving extra room on the right. 41% centers
   the same visible window on the car's actual measured center; the two
   margins that fall out of that (262px and 261px of the 3400px original,
   left and right of the car) are within a pixel of each other. */
@media (max-width: 767px) {
	body.home .elementor-element-33cea79 .elementor-background-video-container {
		background-image:
			linear-gradient(to bottom,
				rgba(0, 0, 0, 0.10) 0px,
				rgba(0, 0, 0, 0.20) 150px,
				rgba(0, 0, 0, 0.62) 190px,
				rgba(0, 0, 0, 0.85) 250px,
				rgba(0, 0, 0, 0.95) 380px),
			url("img/homepage-hero-corvette-stingray-hangar-apex-custom-innovations.jpg");
		background-size: 100% 100%, 496px 253px;
		background-position: 50% 100%, 41% 10%;
		background-repeat: no-repeat, no-repeat;
		background-color: #000000;
	}
}

/* The blue scrim that used to sit here is gone at the client's request. It was
   written for a bright daytime storefront with nothing else on it; what
   carries this hero into the black page below is the fade at the foot of the
   photograph, and a second wash of blue over the top was fighting it.

   On the storefront picture that fade was the photographer's, baked into the
   file. On this one it is ours and it is a CSS gradient — see the stops above
   for why it cannot be baked in, and for what it measures.

   Nothing replaces it. If the headline ever needs help holding against the
   building behind it, the answer is a black scrim on the left third rather
   than a full-frame tint — the type is left-aligned now, so the right two
   thirds of the picture are doing nothing but being looked at. */

/* ==========================================================================
   29. Services header — full-bleed Apex-blue band
   Blue is scoped to db10bd8 alone now (the "SERVICES" eyebrow through the
   "VIEW ALL SERVICES" button) — an earlier version of this also coloured the
   six-photo grid (3c2c465) and inset both to the page's 1145px content width
   so their rounded corners would actually show. That version is gone: the
   grid went back to plain black (section 29b handles its cards), and this
   header band is full-bleed edge-to-edge ("fully left to fully right") rather
   than inset, using the standard left:50%;width:100vw;transform:translateX(-50%)
   break-out trick.

   A full-bleed band's left and right edges sit exactly on the browser's own
   edge, so a border-radius there has nothing to visibly round — only the top
   and bottom of the band are real edges a curve can show against. All four
   corners are still set, harmlessly, in case this is ever narrowed to
   something less than the full viewport.

   Vertical extent: raised 16px above "SERVICES" (unchanged from before), and
   given real extra room at the bottom — db10bd8 itself gets 56px of
   padding-bottom, not just a taller mask, so "VIEW ALL SERVICES" actually
   sits further from the edge instead of the blue merely bleeding past empty
   space past the button.
   ========================================================================== */
/* Three things the client asked for, and they turn out to be one change.
   
   **The corners had nothing to round against.** The comment above says it: a
   100vw band's left and right edges sit exactly on the viewport's, so a
   border-radius there is drawn off-screen. The band is inset 20px each side
   now — `calc(100vw - 40px)` — which is the least that lets a corner read
   while keeping the full-bleed feel the client asked for originally. The
   radius goes to 16px for the same reason: at 12px against 20px of black it
   was too fine to see.

   **The blue was 16px above the eyebrow and 70px below the paragraph.** That
   asymmetry came from two different mechanisms doing the same job — a -16px
   pull on the pseudo at the top, and 56px of the element's own padding at the
   bottom, plus 14px of column stretch nobody was counting. Both ends are the
   element's own padding now, 44px each, and the pseudo simply covers the
   element (`top: 0; bottom: 0`). 44 sits between the 16 and the 70, so the
   band's overall height barely moves and nothing below it shifts.

   **`align-items: center` is what centres the button.** The two columns were
   stretching to the row's height, which made the text column 14px taller than
   its own text — so "symmetric padding" and "centred button" disagreed by
   exactly that 14px and could not both be satisfied. Centred, the row is the
   text column's natural height, the blue is equal above and below it, and the
   button lands on the band's true centre line. One property settles both. */
body.home .elementor-element-db10bd8 {
	position: relative;
	align-items: center !important;
	padding-top: 44px !important;
	padding-bottom: 44px !important;
}

/* The last 14px. With the padding equal the blue still sat 14px deeper below
   the paragraph than above the eyebrow, because the copy widget is 92px tall
   against its paragraph's 78 — the trailing margin on the last <p>. Zeroing it
   makes the column's bottom edge the text's bottom edge, which is the only way
   equal padding and a centred button can both be true at once. */
body.home .elementor-element-db10bd8 .elementor-element-48c1f2e p:last-of-type {
	margin-bottom: 0 !important;
}

body.home .elementor-element-db10bd8::before {
	content: "";
	position: absolute;
	left: 50%;
	width: calc(100vw - 40px);
	transform: translateX(-50%);
	top: 0;
	bottom: 0;
	background: #0072ce;
	border-radius: 16px;
	z-index: -1;
}

body.home .elementor-element-db10bd8 h2,
body.home .elementor-element-db10bd8 h2.elementor-heading-title {
	color: #ffffff !important;
}

/* Forty-eighth pass, part three — mobile only, desktop untouched, and NOT
   alignment this time. db10bd8 has no left/right padding of its own — on
   desktop the two-column split (af0f4b0 / 2c181ba) leaves enough gap that it
   never reads as cramped, but the blue ::before is sized to `db10bd8`'s own
   box (100vw - 40px, positioned via left:50%/translateX(-50%) against IT,
   not the viewport), so on mobile — where the row is 335px, the SAME width
   as the panel — the panel's edge and db10bd8's own edge land on the exact
   same pixel. Left-aligned text then sits flush against the blue with no
   inset at all, which is what read as "too far left, doesn't fit" — not a
   position problem, a missing-padding one. 20px repeats the page's own
   edge-to-panel inset (cc789bb's 20px, and the panel's own "100vw - 40px"
   is 20px a side), so the panel-to-text gap now matches the page-to-panel
   gap instead of being zero. */
@media (max-width: 767px) {
	body.home .elementor-element-db10bd8 {
		padding-left: 20px !important;
		padding-right: 20px !important;
	}
}

/* ==========================================================================
   29b. Service card hover — Apex-blue outline
   The six cards are back on the page's plain black (no background change of
   their own), so the hover outline goes back to the accent blue that matches
   every other hover treatment on the site — white was only ever needed while
   these sat on a blue fill of their own.
   ========================================================================== */
body.home .elementor-element-3c2c465 > a,
body.home .elementor-element-dfc0fb8 > a {
	display: block;
	border-radius: 12px;
	border: 2px solid transparent;
	box-shadow: 0 0 0 rgba(0, 114, 206, 0);
	transform: translateY(0);
	transition: border-color 0.35s cubic-bezier(0.16, 1, 0.3, 1),
	            box-shadow 0.35s cubic-bezier(0.16, 1, 0.3, 1),
	            transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}

body.home .elementor-element-3c2c465 > a:hover,
body.home .elementor-element-3c2c465 > a:focus-visible,
body.home .elementor-element-dfc0fb8 > a:hover,
body.home .elementor-element-dfc0fb8 > a:focus-visible {
	border-color: var(--apex-blue);
	box-shadow: 0 14px 32px rgba(0, 0, 0, 0.45),
	            0 0 0 4px rgba(0, 114, 206, 0.22);
	transform: translateY(-4px);
	outline: none;
}

@media (prefers-reduced-motion: reduce) {
	body.home .elementor-element-3c2c465 > a,
	body.home .elementor-element-dfc0fb8 > a {
		transition: border-color 0.2s ease, box-shadow 0.2s ease;
	}
	body.home .elementor-element-3c2c465 > a:hover,
	body.home .elementor-element-3c2c465 > a:focus-visible,
	body.home .elementor-element-dfc0fb8 > a:hover,
	body.home .elementor-element-dfc0fb8 > a:focus-visible {
		transform: none;
	}
}

/* ==========================================================================
   30. "APEX INNOVATIONS" wordmark band — removed site-wide
   efe4fa9 is the same scrolling band documented in section 5 — Layer-1-1.png
   (the wordmark, kept) sharing a widget with image-30.png (the cut-out
   Corvette, already hidden everywhere). It sits directly above the Services
   section's blue header band (section 29), and reads as a redundant repeat of
   the brand name sitting in the gap right before it.

   It was first removed from the homepage alone. The client has since asked for
   it to go everywhere — it repeats the logo already sitting in the header a
   few hundred pixels above on all 19 pages that carry it — so the scope is now
   the bare element. Section 34's service-page copy of this rule is therefore
   redundant and has been dropped.
   ========================================================================== */
body .elementor-element-efe4fa9 {
	display: none !important;
}

/* ==========================================================================
   29c. Service card photo + caption — one shape, not two
   Each card is a photo (its own 12px radius, section 4) sitting directly
   above a black caption strip that also carries its own radius. Both round
   all four corners independently, so where they touch the photo's bottom
   corners curve one way and the caption's top corners curve the other —
   a visible pinch right at the seam, which is the "dipping in" look.

   Fixed by only rounding the two corners that are actually on the outside of
   the combined shape: the photo keeps its top corners and loses its bottom
   ones, the caption keeps its bottom corners and loses its top ones. The
   middle seam is a flat, shared edge, which is what makes it read as one
   card rather than two stacked pieces.

   Both pieces are matched by position, not by the six different widget ids
   Elementor gives each card's image and caption — every card's structure is
   identical (image wrapper, then caption wrapper, as the anchor's only two
   direct children), so last-child reaches the caption on all six without
   naming any of them.
   ========================================================================== */
body.home .elementor-element-3c2c465 > a img,
body.home .elementor-element-dfc0fb8 > a img {
	border-bottom-left-radius: 0 !important;
	border-bottom-right-radius: 0 !important;
}

body.home .elementor-element-3c2c465 > a > div:last-child,
body.home .elementor-element-dfc0fb8 > a > div:last-child {
	border-top-left-radius: 0 !important;
	border-top-right-radius: 0 !important;
}

/* ==========================================================================
   31. (retired) Inventory nav tab + page
   The page and its tab are both gone at the client's request; customise.py
   deletes the folder and strips the nav item on every run. The rules that
   styled them — the blue tab, the light #F3EEE3 page, the card treatment —
   had nothing left to select and have been removed rather than left to rot.
   ========================================================================== */

/* ==========================================================================
   32. Client feedback — white outline off blue headings
   Elementor's generated CSS puts -webkit-text-stroke-width:1px with
   -webkit-text-stroke-color:var(--e-global-color-primary) — white — on the
   big display headings. Against the Apex-blue fill from section 14 that
   reads as a white halo around every letter, which the client asked to lose.

   Both the prefixed and unprefixed properties are zeroed: Elementor writes
   `stroke-width` alongside `-webkit-text-stroke-width`, and leaving the
   former set means an SVG-aware engine can still paint it.
   ========================================================================== */
body h1,
body h2,
body h3,
body h4,
body h5,
body h6,
body .elementor-heading-title {
	-webkit-text-stroke-width: 0 !important;
	stroke-width: 0 !important;
}

/* ==========================================================================
   33. Tulsa removal — footer column
   The Tulsa location is gone from the site. Its pages are deleted outright
   in customise.py, but the footer address column is one of four flex children
   inside 37f278b and is removed here instead, so no generated Elementor
   markup is touched.

   The row was authored with the default `justify-content: normal`, which is
   flex-start — fine at four columns filling 1145px, but at three it leaves
   357px of dead space on the right and the block reads as shunted left.
   Centring restores the symmetry the client asked for, and is correct at any
   surviving column count rather than tuned to exactly three.
   ========================================================================== */
body .elementor-element-6e55d36 {
	display: none !important;
}

body .elementor-element-37f278b {
	justify-content: center !important;
}

/* The Dallas column is authored at --width:100% and only sat at 327px because
   Tulsa was claiming its share of the row; with Tulsa gone it stretched to
   663px. Section 43 now sizes all three columns to their content, which fixes
   this too — no width cap is needed here. */

/* The homepage "Visit Us" block lists the two shops side by side with the
   quote CTA. Its row already centres its children, so removing the Tulsa card
   re-centres the remaining two on its own. */
body.home .elementor-element-1f6b09d {
	display: none !important;
}

/* The contact page runs the same pair as two equal 568px cards, but in a row
   left-aligned by default — so the surviving Dallas card needs the row
   centred explicitly, the same fix as the footer above. */
body .elementor-element-9fa4c64 {
	display: none !important;
}

body .elementor-element-fa2879c {
	justify-content: center !important;
}

/* Both the contact and service-contact pages use these same three ids, so one
   pair of rules covers both. The surviving Dallas card is authored at
   --width:100% and only measured 568px because Tulsa was taking the other
   half; on its own it stretched to the full 1145px row. Capping it at the
   568px it was designed for is what gives the centring above something to
   centre — same fix, same reason, as the footer column. */
@media (min-width: 1025px) {
	body .elementor-element-a5d8383 {
		max-width: 568px;
	}
}

/* A Google Maps embed pinned to the Tulsa address, sitting on four Texas city
   pages — Carrollton, Irving, Lewisville and Plano. Removed rather than
   repointed: the map's coordinates are baked into the embed URL, so there is
   nothing to correct without rebuilding the widget. */
body .elementor-element-79fbe46 {
	display: none !important;
}

/* The AI-discovery page's Bixby / Tulsa paragraph is REWORDED now, not
   hidden (forty-fourth pass, customise.py's ai-discovery-page entry in
   PAGE_TEXT_REPLACEMENTS) — Carrollton copy in the same shape, so the page
   keeps a visible intro under its H1 instead of a gap. Hiding elementor-
   element-3a46e1d here would hide that replacement text too, so the rule
   is retired rather than left stacked on top of it. */

/* Two identical "ceramic coating near Tulsa, OK" entries in the blog post's
   FAQ accordion. The stock markup gives each item its own id, which is the
   only stable handle — the widget ids around them are reused by the other
   nine questions.

   **Scoped to page-id-366, the blog post, and that scope is load-bearing.**
   `e-n-accordion-item-2321` / `-2322` are NOT unique to this post: Elementor
   numbers accordion items per template, and the same two ids land on the
   second and third FAQ item of **fourteen** pages — every service page and
   the six city pages. Unscoped, this rule hid two questions on all of them,
   which is why each service page showed three FAQ entries where the live site
   shows five. The same widget-id reuse trap as cc789bb and 8e32bf0, one level
   down in the accordion.

   Item 2322 no longer exists on the post — it was the duplicate, and
   dedupe_faq() now removes it — but it stays named here so a freshly mirrored
   tree, where the duplicate is still present, is covered before that pass
   runs. */
body.page-id-366 #e-n-accordion-item-2321,
body.page-id-366 #e-n-accordion-item-2322 {
	display: none !important;
}

/* ==========================================================================
   34. (retired) Service pages — wordmark band under the hero photo
   Superseded by section 30, which now hides efe4fa9 on every page.
   ========================================================================== */

/* ==========================================================================
   35. About — founder name
   ========================================================================== */
body .apex-founder-name {
	color: var(--apex-blue);
}

/* ==========================================================================
   36. Service pages — the hero title that repeats the section title
   Every service page said its own name twice: an h1 in the hero — "SUSPENSION",
   "PAINT PROTECTION FILM (PPF)" — under a "• DALLAS" eyebrow, and then the same
   words again as the h2 at the top of the left copy column a few hundred pixels
   down.

   The client wants the top one gone and the lower one kept, so the whole hero
   container goes: hiding the h1 alone would leave the eyebrow stranded on a
   line of its own, and the two images beside it were already hidden as hero
   furniture, so nothing else in 33cea79 is doing any work. The page now opens
   on the copy section, whose tagline ("Get Ground Clearance. And Ground
   Respect.", "XPEL Certified Installation Facility") sits above the surviving
   h2 exactly as authored.

   Scoped to .apex-service-page — the class customise.py adds to exactly the
   eight service pages — plus page-id-387, which is About. The About hero
   ("• DALLAS / about us") originally stayed because its h1 was the only title
   on the page, but in the thirty-seventh pass the client asked for it gone to
   match the services look, accepting that the page then opens on the copy.
   Contact keeps its hero: it was not asked about.

   The h2 is the page's title now, so it takes a step up from the authored 41px.
   Note for whoever ships this in WordPress: the h1 is still in the document,
   just not painted — which is a mirror's way of doing it, not a template's.
   Rebuilt properly, the hero comes out and the heading that carries the page
   name becomes the h1.
   ========================================================================== */
body.apex-service-page .elementor-element-33cea79,
body.page-id-387 .elementor-element-33cea79 {
	display: none !important;
}

body.apex-service-page .elementor-element-01355d1 {
	width: 100% !important;
	max-width: 100% !important;
}

/* 41px, not the 48px this was first set to. The client asked for two specific
   titles to break onto a second line — "APEX ARMOR™ / VEHICLE PROTECTIVE
   SPRAY" and "APEX SIGNATURE FINISH™ / EXTERIOR PAINT" — and a <br> only
   delivers that if each half then holds one line of its own. The widest half
   is "Vehicle Protective Spray", which needs 637px at 48px against the 548px
   column this heading sits in; it fits at 41px with 4px to spare, and there is
   nowhere else for the width to come from without pushing into the photo
   beside it.

   That lands within a pixel of the size the page was authored at, so the
   heading is really back to its original scale with the hero gone from above
   it. The band headings below sit at 38px, which keeps the order right. */
body.apex-service-page .elementor-element-01355d1 .elementor-heading-title {
	font-size: clamp(2rem, 3.3vw, 2.5625rem) !important;
	line-height: 1.12;
}

/* ==========================================================================
   37. Service pages — the blue bands
   The Key Features section sat on black between two white-ish rules: one
   injected above it by customise.py, one the divider at the top of the gallery
   section below. The client asked for the rules in white and the whole area
   between them filled blue, so the band is painted on the section itself and
   its rules are its own borders — exactly as wide as the fill, full-bleed with
   it, and needing no markup at all. The injector and its .apex-divider-lead
   block are gone, and the gallery's own divider (2645ad1) is hidden here so
   the band's lower rule is not shadowed by a second white line 60px under it.

   Four sections carry the fill, and on every page the one that carries it is
   the one holding the icon cards: Key Features on all eight, plus Windshield &
   Interior Screen Protection on PPF, Leveling Kits Key Features on Suspension
   and Interior Lighting Upgrades on Accessories. The write-ups that are a
   photo and a spec list — the Aston Martin and F-350 builds — stay on black.
   Down each page the run then alternates fill, dark, fill.

   #0072ce, not --apex-blue. Everything on the fill is white, and white on the
   lighter #0072ce is 3.4:1, below AA. This is the pairing the copyright bar
   already uses, at 4.6:1 — confirmed with the client, who otherwise reads
   "blue" as --apex-blue everywhere else in this file.

   cc789bb is the id CLAUDE.md warns about: on the homepage it is the section
   around the services grid, and on About it holds "Design. Build. Drive."
   .apex-service-page keeps the fill off both. The other ids appear on
   exactly one page each.

   Thirty-seventh pass: the band set grew from four to seven. The dark middle
   sections — Lowering Kits (a0ca73f), Tires (399a393), Custom Bumpers
   (1f22cac) — sat between two bands with the old vertical icon cards, and the
   client read that as "icons and info don't match". They now take the same
   layout and the section-61 tile design as their neighbours. The two
   photo-and-spec write-ups (a4581a8, a720ef5) are not icon sections and stay
   dark — that contrast is content, not inconsistency.

   **The middle one of each run is STEEL GREY, not blue** (see below). Three
   identical blue bands in a row read as one enormous blue block with hairlines
   in it; alternating the middle gives each page its rhythm back while keeping
   all three sections the same *design*, which is what the client asked for.
   ========================================================================== */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) {
	background-color: #0072ce !important;
}

/* Every band and every section between them carries a white rule along its
   bottom edge, so each boundary down the page is drawn once and the same way —
   including the last one before the gallery, which the client asked for after
   seeing it left open. cc789bb takes a rule on top as well: it is the first of
   the run, so nothing above it draws one.

   Their authored padding runs 120px top against 60px bottom, which would leave
   every rule twice as far from the content above it as from the content below.
   All of them go to 72px. */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-a0ca73f,
	.elementor-element-a4581a8,
	.elementor-element-97041b8,
	.elementor-element-a720ef5,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) {
	padding-top: 72px !important;
	padding-bottom: 72px !important;
}

/* Every one of them draws the rule except the F-350 build, which the client
   took back off: it is the last section on PPF, and the line sat between the
   truck photo and the gallery heading with nothing to separate. Its rhythm
   stays with the rest — only the border goes. */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-a0ca73f,
	.elementor-element-a4581a8,
	.elementor-element-97041b8,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) {
	border-bottom: 5px solid #ffffff;
}

body.apex-service-page .elementor-element-cc789bb {
	border-top: 5px solid #ffffff;
}

body.apex-service-page .elementor-element-2645ad1 {
	display: none !important;
}

/* The break customise.py puts before "Key Features" only helps if the service
   name itself holds one line, and these headings were authored at ~78% of an
   already-narrow column: 448px, against the 586px "PAINT PROTECTION FILM
   (PPF)" needs at the authored 41px. So it broke twice and the title came out
   three lines deep.

   The heading takes its column's full 568px and drops to 38px, which puts the
   longest of the eight — that one — at 544px with room to spare, and leaves
   every other page at two lines as well. 38px is still comfortably under the
   48px page title above it, so the hierarchy is unchanged.

   Desktop only: below 1025px the columns stack and the heading has the whole
   width already. */
@media (min-width: 1025px) {
	body.apex-service-page :is(
		.elementor-element-cc789bb,
		.elementor-element-97041b8,
		.elementor-element-a0ca73f,
		.elementor-element-399a393,
		.elementor-element-1f22cac,
		.elementor-element-ed5be2b,
		.elementor-element-6c8b76b) .elementor-widget-heading {
		width: 100% !important;
		max-width: 100% !important;
	}

	body.apex-service-page :is(
		.elementor-element-cc789bb,
		.elementor-element-97041b8,
		.elementor-element-a0ca73f,
		.elementor-element-399a393,
		.elementor-element-1f22cac,
		.elementor-element-ed5be2b,
		.elementor-element-6c8b76b) .elementor-widget-heading .elementor-heading-title {
		font-size: 2.375rem !important;
		line-height: 1.15;
	}
}

/* Everything on the fill goes white. The h2 is the one that needs saying twice
   — section 14 paints every h1/h2 --apex-blue, which on this ground is
   invisible. The list items and their bold labels are the build sheets, which
   are body copy inside a text editor rather than icon boxes. */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b),
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) :is(
	h2, h3, p, li, strong, em,
	.elementor-heading-title,
	.elementor-icon-list-text,
	.elementor-icon-box-title,
	.elementor-icon-box-title span,
	.elementor-icon-box-description,
	.elementor-widget-text-editor) {
	color: #ffffff !important;
}

/* The eyebrow's bullet is an inline <svg> that the page's own generated CSS
   fills #0072ce — invisible on a #0072ce band. !important is needed, and is
   the reason the icon-box rule below does not need it: that one only has to
   beat a presentation attribute on the <circle>, while this one is up against
   `.elementor-316 .elementor-element.elementor-element-85b30e0
   .elementor-icon-list-icon svg`, which outweighs anything scoped through a
   body class. */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-list-icon svg,
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-list-icon svg path {
	fill: #ffffff !important;
}

/* 38. The icons invert with the ground they sit on.
   Each is one inline <svg>: a <circle fill="white"> with the pictogram over it
   in fill="#0072ce" paths. On black the pair was swapped so the disc took the
   accent blue; against a blue band that reads as a hole, so it swaps back —
   white disc, blue pictogram, which is how the artwork was drawn. Both are
   presentation attributes, so any CSS fill wins.

   The cards these sit on are black at 20px radius, authored that way and
   invisible while the section behind them was also black. On the fill they
   read as cards, which is what they always were. */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-box-icon svg > circle {
	fill: #ffffff;
}

body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-box-icon svg > path {
	fill: #0072ce;
}

/* The one button in each band. button-rings.js measured a transparent fill and
   gave it the rotating ring from section 10, whose ::after fills the middle
   with #000 — a black pill on a blue band. On this ground the white pill with
   blue lettering is the right call anyway: it is the site's primary CTA shape,
   and it is the only call to action inside the fill. The ring's two
   pseudo-elements are switched off rather than restyled. */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-button {
	background: #ffffff !important;
	color: #0072ce !important;
	border: 1px solid #ffffff !important;
}

body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-button.apex-btn-ring::before,
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-button.apex-btn-ring::after {
	display: none !important;
}

@media (max-width: 767px) {
	body.apex-service-page :is(
		.elementor-element-cc789bb,
		.elementor-element-a0ca73f,
		.elementor-element-a4581a8,
		.elementor-element-97041b8,
		.elementor-element-399a393,
		.elementor-element-1f22cac,
		.elementor-element-a720ef5,
		.elementor-element-ed5be2b,
		.elementor-element-6c8b76b) {
		padding-top: 48px !important;
		padding-bottom: 48px !important;
	}
}

/* ==========================================================================
   38b. Icon cards — one grid, the same on every page
   The card rows were authored per page, and the widths did not match. In the
   Suspension band each card is 19% of the row, so five of them sit on a single
   line at 218px; in the Accessories band the same widget id is 32%, so five
   become three and two. Same component, same page template, two different
   layouts — which is the staggered-versus-in-a-line the client is looking at.

   One rule now covers every group: three equal columns at
   calc((100% - 40px) / 3) with a 20px gutter, wrapping, and every row —
   including a short last one — starting on the same left edge. Three columns
   is the only count that works across the range the pages actually hold — 3,
   4, 5, 6 and 7 cards — and it keeps every card the same 368px wide on every
   page, which is the half of "the same way" that shows. Two across below
   1025px, one across below 768px.

   The short last row was centred first, and that is what the client was still
   seeing as staggered: a group of six put its cards on the same three x
   positions in both rows, while a group of five pushed its last two inward by
   half a column. Same rule, two different pictures, because the offset is a
   function of how many cards the row happens to hold. Left-aligned, every card
   in every group on every service page lands on one of three columns —
   measured at 1265px: 80, 468, 857 — and a group of five simply leaves the
   third column of its last row empty. That hole is the price of the alignment
   and it is the right trade: the client asked for the pages to match each
   other, and this is the only arrangement that does not depend on the count.

   Selected structurally rather than by id: the groups are the containers whose
   grandchildren are icon-box widgets, and :has() can say exactly that. Every
   such group on these pages is one of these rows — the wrapper around each
   card holds the widget directly, so it does not match itself, and the section
   above holds containers rather than widgets, so it does not match either.
   ========================================================================== */
body.apex-service-page .e-con:has(> .e-con > .elementor-widget-icon-box) {
	display: flex !important;
	flex-wrap: wrap !important;
	justify-content: flex-start !important;
	align-items: stretch !important;
	gap: 20px !important;
}

body.apex-service-page .e-con:has(> .e-con > .elementor-widget-icon-box) > .e-con {
	width: calc((100% - 40px) / 3) !important;
	max-width: calc((100% - 40px) / 3) !important;
	flex: 0 0 auto !important;
}

@media (max-width: 1024px) {
	body.apex-service-page .e-con:has(> .e-con > .elementor-widget-icon-box) > .e-con {
		width: calc((100% - 20px) / 2) !important;
		max-width: calc((100% - 20px) / 2) !important;
	}
}

@media (max-width: 767px) {
	body.apex-service-page .e-con:has(> .e-con > .elementor-widget-icon-box) > .e-con {
		width: 100% !important;
		max-width: 100% !important;
	}
}

/* ==========================================================================
   39. Footer copyright bar centred
   a471638 is a flex row set to space-between, which centres nothing when it
   holds a single child — the line sat hard against the left padding while the
   columns above it were centred. This is the "measured centred, looked
   off-centre" trap noted below in reverse: the row was never centred at all.
   ========================================================================== */
body footer .elementor-element-a471638 {
	justify-content: center !important;
}

body footer .elementor-element-30df477 {
	text-align: center !important;
}

/* ==========================================================================
   40. Background videos removed
   Two hosted background videos, both at the client's request:

   - 8e32bf0 "Ready to Customize Your Vehicle?" (11 pages) — a Jeep behind a
     full-width XPEL wordmark, which is the XPEL branding they asked to lose.
   - 3619b08 "Built for the Road Ahead" (22 pages) — the Corvette that drove
     past just above the footer.

   Only the video layer goes; each section keeps its heading, copy and CTA and
   falls back to its own background. Descendant selectors, not `>`: the
   container sits inside .e-con-inner, which is the nesting mistake documented
   in the gotchas below.
   ========================================================================== */
body .elementor-element-8e32bf0 .elementor-background-video-container,
body .elementor-element-3619b08 .elementor-background-video-container {
	display: none !important;
}

/* ==========================================================================
   41. Collapse the space the videos were holding open
   Both bands were authored tall enough to show a video behind their text:
   1000px on 8e32bf0 against 312px of content, 850px on 3619b08 against 138px.
   With the videos gone that authored --min-height was pure empty black — a
   little over 500px on one and nearly 600px on the other.

   Clearing it lets each band close down to its own copy plus its padding.
   Safe to apply unscoped: 3619b08 is the same video band on all 22 pages that
   have it, and the homepage's reuse of 8e32bf0 already computes to 0 through
   the homepage rhythm rule in section 12.
   ========================================================================== */
body .elementor-element-8e32bf0,
body .elementor-element-3619b08 {
	min-height: 0 !important;
}

/* ==========================================================================
   42. XPEL band — Paint Protection Film page only
   The "Ready to Customize Your Ride?" band is a two-column row: copy and a
   CTA on the left, the XPEL logo on the right. The client wants it kept only
   on the PPF page, where XPEL is the subject, and gone — logo and copy both —
   everywhere else.

   Driven by the .apex-no-xpel body class rather than by widget id. Section
   8e32bf0 and its row cf75538 are both reused by the homepage's "Certified
   Protection" section, so an id-based rule would delete that too; see the
   customise.py note beside XPEL_MARKER.
   ========================================================================== */
body.apex-no-xpel .elementor-element-8e32bf0 {
	display: none !important;
}

/* ==========================================================================
   43. Footer columns — flush left under their own headings
   Every column was full-width with its text centred, so each list sat centred
   under a heading that was itself centred in a wider box — "Home" floated to
   the middle of the space under "QUICK LINKS" rather than starting at the Q.

   Two changes make that read straight. The columns shrink to their content
   instead of holding authored percentage widths (20.118% and friends), and
   everything inside them aligns left, so heading and list share one left
   edge. The row then centres the three as a group with an even gap, which is
   what removes the lopsided space on the left of the block.

   .elementor-icon-list-item is a flex row, so it needs justify-content, not
   text-align — the latter moves nothing on a flex container.
   ========================================================================== */
@media (min-width: 768px) {
	body footer .elementor-element-37f278b {
		justify-content: center !important;
		gap: clamp(2.5rem, 6vw, 6rem) !important;
	}

	body footer .elementor-element-37f278b > .e-con {
		width: auto !important;
		max-width: none !important;
		flex: 0 0 auto !important;
	}
}

/* Fifty-first pass — the columns' below-768px gap was never stated, so it
   fell through to Elementor's own generated 10px. Fine when the three
   columns sit side by side (10px between vertical rules reads as intentional
   spacing); wrong once they wrap and stack, where the same 10px sits between
   a column's LAST list item and the NEXT column's own heading — "Our
   Services" and "Dallas Metroplex" read as crowding the content above them
   rather than starting a new section. Row-gap only: column-gap is moot once
   nothing sits side by side, and this does not touch the desktop rule above
   it. */
@media (max-width: 767px) {
	body footer .elementor-element-37f278b {
		row-gap: 40px !important;
	}
}

/* Fifty-first pass — the copyright bar's two lines read as "funky" on
   mobile because they disagreed: 30df477 (the copyright line) is centred,
   3fecefe (Privacy Policy / Terms and Conditions) is left-aligned — its own
   `<ul>` is a flex row with justify-content's default, which is flex-start,
   not the block-level `text-align` the rest of this file's footer rules
   reach for. Centred to match the copyright line under it (the bar itself
   is `flex-direction: column-reverse`, so "under" is visual, not DOM
   order). */
@media (max-width: 767px) {
	body footer .elementor-element-3fecefe .elementor-icon-list-items {
		justify-content: center !important;
	}
}

body footer .elementor-element-37f278b .elementor-heading-title,
body footer .elementor-element-37f278b .elementor-widget-container,
body footer .elementor-element-37f278b .elementor-icon-list-text {
	text-align: left !important;
}

body footer .elementor-element-37f278b .elementor-icon-list-item {
	justify-content: flex-start !important;
	text-align: left !important;
}

/* The li was the wrong box. Each one holds a single full-width <a> which is
   itself a flex row, centred — so the list stayed centred under a heading that
   had already moved left, and "Home" sat 63px in from the Q it was supposed to
   line up with. The <a> is what needs the alignment.

   That still leaves the bullet's 8px column between the edge and the text, so
   the icon slot goes with it: the dots in Quick Links and Our Services are
   decoration, and the map-pin, phone, card and clock in the address column are
   restating labels the text already carries ("Address:", "Call Us:", "Email
   Us:"). With the slot gone every line starts on the column's own left edge,
   which is where the heading starts. */
body footer .elementor-element-37f278b .elementor-icon-list-item > a {
	justify-content: flex-start !important;
}

body footer .elementor-element-37f278b .elementor-icon-list-icon {
	display: none !important;
}


/* ==========================================================================
   44. Homepage "Visit Us" block — one centred, symmetrical pair
   The block was three unrelated alignments stacked on top of each other. The
   heading row carried 250px of authored top padding and sat hard left; the
   Dallas card and the quote CTA were authored at 35.1% and 30% of the row, so
   the pair measured 746px inside 1145px and read as two loose columns rather
   than one composition; and inside the card the divider and GET DIRECTIONS
   were a space-between row, which left the button stranded in the middle of
   the page with 400px of black either side.

   It is now a centred header over two equal cards. The cards carry the site's
   surface — #0c0c0c on a hairline, the 30px radius the Dallas card was already
   authored with — so the pair reads as one object, and both stretch to the
   same height with their calls to action pushed to the bottom, which is what
   lines the two buttons up with each other.

   Scoped to body.home: ffa7032 / f857ec3 / a2758ab are the same ids the
   contact and service-contact pages use for their own location cards.
   ========================================================================== */
body.home .elementor-element-0d4b736 {
	padding: 0 0 44px !important;
	justify-content: center !important;
}

body.home .elementor-element-9a011e5 {
	align-items: center !important;
	text-align: center !important;
	gap: 10px;
}

body.home .elementor-element-5ea1ffa .elementor-icon-list-items {
	justify-content: center !important;
}

body.home .elementor-element-5ea1ffa .elementor-icon-list-item,
body.home .elementor-element-5ea1ffa .elementor-icon-list-item > a {
	justify-content: center !important;
}

body.home .elementor-element-928d745 {
	width: 100% !important;
	text-align: center !important;
}

/* The row: two equal children, stretched, with a real gap between them. */
body.home .elementor-element-06b7976 {
	align-items: stretch !important;
	justify-content: center !important;
	flex-wrap: wrap;
	gap: 24px !important;
}

body.home .elementor-element-ffa7032,
body.home .elementor-element-7b0c175 {
	width: auto !important;
	flex: 1 1 380px !important;
	max-width: 520px !important;
	background-color: #0c0c0c !important;
	border: 1px solid var(--apex-hairline) !important;
	border-radius: 30px !important;
	padding: 48px 40px !important;
	justify-content: flex-start !important;
}

/* The inner card was doing the padding and holding a 600px min-height that no
   longer has anything to fill; the surface moved up to its parent, so it goes
   back to being a plain stack. */
body.home .elementor-element-f857ec3 {
	min-height: 0 !important;
	padding: 0 !important;
	border-radius: 0 !important;
	background: none !important;
	justify-content: flex-start !important;
	gap: 20px !important;
	height: 100%;
}

body.home .elementor-element-036f8dc {
	padding: 0 !important;
	align-items: flex-start !important;
	gap: 20px !important;
}

body.home .elementor-element-77c87ed {
	width: 100% !important;
}

body.home .elementor-element-2f8e822 {
	width: auto !important;
	flex: 0 0 auto;
	padding: 0 !important;
}

/* The address card grows with its content and pushes GET DIRECTIONS to its
   bottom edge. The quote card holds two short things, so stretching them to
   the same 632px would leave 380px of black between the headline and the
   button; its pair is centred in the card as one group instead, which is what
   makes it read as a panel rather than as a column that ran out of copy. */
body.home .elementor-element-28aa927 {
	margin-top: auto !important;
}

body.home .elementor-element-7b0c175 {
	justify-content: center !important;
	gap: 34px !important;
}

body.home .elementor-element-28aa927,
body.home .elementor-element-357352c {
	width: 100% !important;
	text-align: left !important;
}

/* Divider above the button rather than beside it — the space-between row is
   what was throwing GET DIRECTIONS into the middle of the page. */
body.home .elementor-element-28aa927 {
	flex-direction: column !important;
	align-items: stretch !important;
	justify-content: flex-start !important;
	gap: 26px !important;
	padding-top: 8px !important;
}

body.home .elementor-element-123b4ea,
body.home .elementor-element-a2758ab {
	width: 100% !important;
	text-align: left !important;
}

@media (max-width: 767px) {
	body.home .elementor-element-ffa7032,
	body.home .elementor-element-7b0c175 {
		padding: 36px 28px !important;
		max-width: none !important;
	}

	body.home .elementor-element-0d4b736 {
		padding-bottom: 32px !important;
	}
}

/* ==========================================================================
   45. "Recent Builds & Inspirations" — smaller tiles
   The gallery is Elementor's grid layout: three columns, 20px gaps, 1:1 tiles,
   laid out by JS from percentages of whatever width the container reports. At
   the full 1145px content width that is three 368px squares, which is the
   largest thing on most of these pages.

   Capping the widget rather than changing --columns is deliberate. The column
   and row indices are computed in JS and written back as custom properties, so
   overriding --columns from CSS re-flows the tiles against positions that were
   calculated for three; a narrower container keeps every one of those numbers
   correct, since they are all percentages.

   ab9eda1 is the same widget id on all eleven pages that carry the block, so
   this is global by construction.
   ========================================================================== */
body .elementor-element-ab9eda1 {
	max-width: 880px !important;
	margin-left: auto !important;
	margin-right: auto !important;
}

/* ==========================================================================
   46. Services dropdown — sized to its longest item
   The panel is 154px wide with overflow hidden (section 25 rounds its corners,
   which needs the clip), while its links are white-space: nowrap. "Paint
   Protection Film (PPF)" wants 228px and "Apex Signature Finish" 187px, so
   both were simply cut off mid-word.

   max-content sizes the panel to the longest link instead of the other way
   round; the min keeps it from shrinking under the tab it hangs from.
   ========================================================================== */
@media (min-width: 1025px) {
	body .elementor-nav-menu--main .sub-menu {
		width: max-content !important;
		min-width: 156px !important;
		max-width: 340px !important;
	}

	body .elementor-nav-menu--main .sub-menu .elementor-sub-item {
		white-space: nowrap;
	}
}

/* ==========================================================================
   47. About — "Design. Build. Drive." reads left like the rest of the site
   The heading and the paragraph under it were the only centred type on the
   page, and they were inset as well: both widgets are authored at ~79% of the
   container and centred in it, so the block started 120px right of the left
   edge every other section on About begins at.

   Scoped to the About page id — cc789bb, db10bd8, af0f4b0, d4d12e2 and 48c1f2e
   are all reused as the Key Features block on the eight service pages, which
   section 37 has just painted blue.
   ========================================================================== */
body.page-id-387 .elementor-element-d4d12e2,
body.page-id-387 .elementor-element-48c1f2e {
	margin-left: 0 !important;
	margin-right: auto !important;
	text-align: left !important;
}

body.page-id-387 .elementor-element-d4d12e2 .elementor-heading-title,
body.page-id-387 .elementor-element-48c1f2e,
body.page-id-387 .elementor-element-48c1f2e p,
body.page-id-387 .elementor-element-48c1f2e div {
	text-align: left !important;
}

body.page-id-387 .elementor-element-af0f4b0 {
	align-items: flex-start !important;
}

/* ==========================================================================
   48. About — the gallery block goes
   6e69624 is the whole "GALLERY / Recent Builds & Inspirations" section: its
   own divider, the eyebrow and heading, the VIEW GALLERY button and the tile
   grid. display: none takes the box out of flow entirely, so the 60px of
   padding it was holding open goes with it and no gap is left behind — which
   is why the section is hidden rather than its contents.

   About only. The same section is the gallery block on the homepage and on
   every service page, where it stays.
   ========================================================================== */
body.page-id-387 .elementor-element-6e69624 {
	display: none !important;
}

/* ==========================================================================
   49. Contact — centred, one location, readable on the blue
   Three things, all in the location block:

   - "Select your Location" is a heading for a choice that no longer exists;
     with Tulsa gone there is one card under it. The heading and the row that
     held it both go, so no empty 46px band is left in the stack.
   - "Dallas / Fort Worth" is an h2, and section 14 paints every h2
     --apex-blue. Inside a #0072ce card that is blue on blue at 1.4:1. White
     on that fill is 4.6:1 and is the pairing the rest of the site uses.
   - Everything in the card was left-aligned inside a box that was itself
     centred, so the card read as off-centre even though it was not. The icon
     rows are flex, so they need justify-content; text-align moves nothing on
     them.
   ========================================================================== */
body.page-id-285 .elementor-element-12f0fcd,
body.page-id-285 .elementor-element-9c25785 {
	display: none !important;
}

body.page-id-285 .elementor-element-a5d8383 {
	align-items: center !important;
	text-align: center !important;
}

body.page-id-285 .elementor-element-a5d8383 .elementor-heading-title,
body.page-id-285 .elementor-element-a5d8383 .elementor-icon-box-title,
body.page-id-285 .elementor-element-a5d8383 .elementor-icon-box-description,
body.page-id-285 .elementor-element-a5d8383 p {
	text-align: center !important;
}

body.page-id-285 .elementor-element-2391e89 .elementor-heading-title {
	color: #ffffff !important;
}

/* ADDRESS / CALL US / HOURS each sit in an icon box built as a two-column flex
   row: the icon in one column, and a content block holding the title and the
   line under it in the other. Centring the row put the pair in the middle of
   the card, but the content block is as wide as its longest line — the address
   is 378px — so the title was centred inside that block and ended up 140px
   away from the icon that belongs to it. The icon read as floating off to the
   left of nothing.

   `display: contents` on the content block drops it out of the layout and
   promotes the title and the description to flex items of the row itself. The
   icon and the title then share the first line, centred as a pair, and the
   description is pushed onto its own line by a 100% flex-basis and centred
   under both. No markup moves, and the box keeps its own structure for
   anything that reads it.

   Scoped to the contact page's card. contact-carrollton and service-contact
   carry the same a5d8383 id and are left as they are, which is also what the
   centring rules above already do. */
body.page-id-285 .elementor-element-a5d8383 .elementor-icon-box-wrapper {
	display: flex !important;
	flex-wrap: wrap !important;
	align-items: center !important;
	justify-content: center !important;
	column-gap: 5px !important;
	row-gap: 5px !important;
	text-align: center !important;
}

/* The gaps carry !important because the authored `gap: 5px` on this widget was
   beating them: `column-gap: 12px` and `row-gap: 4px` were written here and
   neither ever applied. They are stated at the 5px that was actually rendering,
   so pinning them moves nothing — the point is that the number below can be
   derived from a value this file controls rather than one it was losing to. */

/* The label is what gets centred, not the icon-and-label pair.
   Centring the pair is what the rule above did, and it is defensible on its own
   terms — but it puts the word off-centre by half the icon's width, and the
   line under it is centred on the card, so ADDRESS sat 20px to the right of
   the address beneath it. Every other line in the card measured 0.0 off centre
   and those three measured +20.0, which is what the client was seeing.

   A negative left margin equal to the icon's own width plus the row's gap
   makes the icon contribute nothing to the line's width. The flex centring
   then has only the label to place, so the label lands on the card's centre
   and the icon hangs to its left, still attached to it by the same 5px.

   -40px is 35 + 5: the icon box is 35px (a 25px glyph in a 10px-padded slot)
   and the gap is the 5px pinned above. Both are stated in this file, so if
   either changes this number is one subtraction away. It is independent of the
   label — that is the whole point of doing it this way rather than nudging
   each row: ADDRESS, CALL US and HOURS are 98, 85 and 73px wide and all three
   land on the same centre. */
body.page-id-285 .elementor-element-a5d8383 .elementor-icon-box-content {
	display: contents;
}

/* The negative margin lives in this rule rather than one of its own. A separate
   `margin-left` further up the file lost silently to the `margin: 0 !important`
   that was already here — the shorthand resets all four sides, and !important
   beats a later plain declaration whatever the order. Stating the sides out
   long is what makes the left one survivable. */
body.page-id-285 .elementor-element-a5d8383 .elementor-icon-box-icon {
	flex: 0 0 auto;
	margin: 0 0 0 -40px !important;
}

body.page-id-285 .elementor-element-a5d8383 .elementor-icon-box-title {
	flex: 0 0 auto;
	margin: 0 !important;
}

body.page-id-285 .elementor-element-a5d8383 .elementor-icon-box-description {
	flex: 0 0 100%;
	margin: 0 !important;
	text-align: center !important;
}

/* The divider and GET DIRECTIONS were a space-between row, the same shape the
   homepage card had — stacked and centred instead. */
body.page-id-285 .elementor-element-b7f1ab9 {
	flex-direction: column !important;
	align-items: center !important;
	justify-content: center !important;
	gap: 24px !important;
}

body.page-id-285 .elementor-element-fe38c2e,
body.page-id-285 .elementor-element-764af71 {
	width: 100% !important;
	text-align: center !important;
}

body.page-id-285 .elementor-element-995db22,
body.page-id-285 .elementor-element-5a65487 {
	align-items: center !important;
}

body.page-id-285 .elementor-element-abf156f,
body.page-id-285 .elementor-element-abf156f .elementor-heading-title {
	text-align: center !important;
}

/* ==========================================================================
   50. Gallery filter rail — the button ring, on hover
   e69f6af is the whole filter box: a 284x833 panel down the left of the
   gallery holding the heading, the six dropdowns, the category checkboxes and
   the status line, on #0c0c0c at a 20px radius. The client asked for the
   buttons' blue-and-white sweep around the whole of it.

   Section 10 draws that ring with two pseudo-elements because a button has a
   transparent fill and needs its middle masked back in. The same is true here
   once the ring is behind the panel, so the panel's own #0c0c0c moves to
   ::after and ::before carries the ring underneath it.

   It rotates on hover and focus-within rather than continuously, which is the
   one deliberate difference from the buttons: at 833px tall, a ring turning
   forever beside the vehicle grid pulls the eye off the vehicles. At rest it
   is a static --apex-blue edge, so the treatment still reads as the same
   family. focus-within matters as much as hover — the panel is six form
   controls, and a keyboard user should see the same thing a mouse user does.

   --apex-ring-angle is registered with inherits: false, so the animation has
   to run on the element that reads it. That is ::before, exactly as in
   section 10.
   ========================================================================== */
body .elementor-element-e69f6af {
	position: relative;
	isolation: isolate;
	z-index: 0;
	background-color: transparent !important;  /* redrawn on ::after */
	border-radius: 20px;
}

/* Sized with width/height rather than `inset: -2px`, which is what the first
   version used and what left the ring showing along the top and left of the
   panel and missing down the right and bottom.

   An absolutely positioned box only stretches to opposing offsets while its
   width is auto. Something in the Elementor stack gives .e-con pseudo-elements
   a width and height of 100% — that is how its background-overlay layer is
   built — so this box arrived over-constrained: left and top were honoured,
   the box stayed exactly the panel's size, and right and bottom were dropped.
   The result is a ring shifted 2px up and left instead of a ring around
   anything. Stating the size outright takes the guesswork out: 100% of the
   panel plus the 2px it needs on each side.

   Measured after the change: 288.2 x 836.8 against a 284.2 x 832.8 panel. */
body .elementor-element-e69f6af::before {
	content: "";
	position: absolute;
	top: -2px;
	left: -2px;
	width: calc(100% + 4px);
	height: calc(100% + 4px);
	border-radius: 22px;
	z-index: -2;
	background: var(--apex-blue);
	pointer-events: none;
}

body .elementor-element-e69f6af::after {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	border-radius: inherit;
	z-index: -1;
	background: #0c0c0c;
	pointer-events: none;
}

body .elementor-element-e69f6af:hover::before,
body .elementor-element-e69f6af:focus-within::before {
	background-image: conic-gradient(
		from var(--apex-ring-angle),
		var(--apex-blue) 0deg,
		var(--apex-blue) 55deg,
		#ffffff 90deg,
		#ffffff 145deg,
		var(--apex-blue) 360deg
	);
	animation: apexRingRotate 3s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
	body .elementor-element-e69f6af:hover::before,
	body .elementor-element-e69f6af:focus-within::before {
		animation: none;
		background-image: none;
		background-color: var(--apex-blue);
	}
}

/* ==========================================================================
   51. About — the three words the section is named after
   "Design. Build. Drive." is the heading, and the copy under it then opens
   three of its paragraphs with the same three words in bold: "Design — Every
   build starts with a conversation", and so on. They were white like the rest
   of the body copy, so the structure the heading promises was invisible until
   you read the sentences.

   p > strong:first-child is what picks exactly those three. The block holds
   two more <strong>s — "Apex Armor™" and "Apex Signature Finish™" — but both
   sit inside links partway through a paragraph, so neither is its paragraph's
   first child and neither is touched.

   --apex-blue here, not the #0072ce the service bands use: this is text on
   black, which is the job that blue has.
   ========================================================================== */
body.page-id-387 .elementor-element-48c1f2e p > strong:first-child {
	color: var(--apex-blue);
}

/* ==========================================================================
   52. Gallery grid — one card shape
   Every card in the loop grid renders its photo in a 220x250 box with
   object-fit: cover. The Corvette's came from the client's new shoot, which is
   portrait 4:5, and Elementor sized that one to 220x275 and switched it to
   `contain` — so the card stood taller than the rest and the run of them
   stepped. Pinning the box makes the shape the card's rather than the file's,
   which is what it should have been all along: the next replacement photo of
   any proportion drops in without moving anything.
   ========================================================================== */
/* The widget as well as the image. A card whose photo came from the client's
   shoot rendered its widget at 200px against 220px for its neighbours — the
   replacement <img> has no srcset, and without one the widget sizes itself
   from the file rather than from the card. Stating both leaves the shape to
   the card, which is where it belongs. */
body .elementor-element-3e1bad4 .elementor-widget-theme-post-featured-image {
	width: 100% !important;
}

body .elementor-element-3e1bad4 .elementor-widget-theme-post-featured-image img {
	height: 250px !important;
	width: 100% !important;
	object-fit: cover !important;
}

/* ==========================================================================
   53. Vehicle pages — the "About This Project" band is blue
   3716c3f is authored with a #0072ce fill, and section 14 paints every h1 and
   h2 --apex-blue. Blue on blue is 1.4:1 — the section's own eyebrow heading
   and its title were both effectively invisible, while the paragraph between
   them was already white. Everything on the fill goes white, and the button
   becomes the white pill the service-page bands use, for the same reason: it
   is the only call to action on the fill and the ring's ::after paints its
   middle #000.
   ========================================================================== */
/* The selector has to out-specify section 14, which is where the blue comes
   from: its longest arm is `body .elementor-widget-heading h2.elementor-heading-title`
   at (0,2,2) with !important, and a plain `body .elementor-element-3716c3f h2`
   is only (0,1,2) — !important against !important is settled on specificity,
   so the shorter selector lost silently. Naming the widget wrapper and the
   heading class takes this to (0,3,2). */
body .elementor-element-3716c3f h2,
body .elementor-element-3716c3f h3,
body .elementor-element-3716c3f p,
body .elementor-element-3716c3f li,
body .elementor-element-3716c3f .elementor-heading-title,
body .elementor-element-3716c3f .elementor-widget-heading h2.elementor-heading-title,
body .elementor-element-3716c3f .elementor-widget-heading h3.elementor-heading-title {
	color: #ffffff !important;
}

body .elementor-element-3716c3f .elementor-button {
	background: #ffffff !important;
	color: #0072ce !important;
	border: 1px solid #ffffff !important;
}

body .elementor-element-3716c3f .elementor-button.apex-btn-ring::before,
body .elementor-element-3716c3f .elementor-button.apex-btn-ring::after {
	display: none !important;
}

/* ==========================================================================
   54. "Recent Builds & Inspirations" — two drifting rows
   The block was Elementor's grid gallery: six photographs, three across,
   capped at 880px by section 45. The client asked for it to run the full width
   of the page with room for several more, in two rows moving in opposite
   directions — the top to the right, the bottom to the left.

   The gallery widget is hidden rather than deleted, which is this file's rule
   for removals, and customise.py injects the marquee just before it. Section
   45's cap now has nothing to act on and is left where it is: putting the
   gallery back is one line here, and the cap should come back with it.

   Full-bleed out of a container that is padded 60px: 100vw wide, pulled left by
   half the difference. `calc(50% - 50vw)` rather than a negative margin in
   pixels, because the container's own width is not something this rule knows.

   The gap between tiles is a margin on each tile rather than `gap` on the
   track, and that is load-bearing. A seamless loop works by writing the tiles
   twice and translating the track exactly -50% of its own width; with `gap`
   the two halves are separated by one extra gutter, so -50% lands a gap short
   and the row visibly hitches once a cycle. As a margin, the gutter belongs to
   the tile and travels with it, so half the track is exactly one copy.
   ========================================================================== */
body .elementor-element-ab9eda1 {
	display: none !important;
}

/* Out through the section's own padding rather than out to the viewport.
   `width: 100vw` was the first attempt and it lost silently — Elementor sizes
   its descendants and a bare single-class selector does not beat that, so the
   box stayed `auto` and only the negative margin took, leaving it 67px wider
   than its container instead of full width.

   Negative margins on both sides with `width: auto` is the better answer
   anyway: it lands exactly on the section's edges, and it never overflows by
   the width of the scrollbar the way 100vw does — 100vw counts the scrollbar,
   the document does not. 60px is the section's horizontal padding, 20px on a
   phone. */
body .apex-builds {
	width: auto !important;
	margin-left: -60px;
	margin-right: -60px;
	overflow: hidden;
}

.apex-builds-row {
	overflow: hidden;
	margin-bottom: 20px;
}

.apex-builds-row:last-child {
	margin-bottom: 0;
}

.apex-builds-track {
	display: flex;
	width: max-content;
	will-change: transform;
	backface-visibility: hidden;
	transform: translate3d(0, 0, 0);
}

/* Each tile is a card now, not a bare photograph: the shot, then a blue
   category chip and the vehicle's name under it, which is how the gallery
   already presents the same thirteen vehicles. prep_builds.py reads them out
   of the gallery's own loop markup, so the names cannot drift from it, and
   customise.py lays them out.

   The gutter is still a margin on the card rather than `gap` on the track —
   see the note above; that is what makes -50% land exactly one copy along. */
.apex-builds-track .apex-build-card {
	display: block;
	flex: 0 0 auto;
	width: 300px;
	margin-right: 20px;
	border-radius: 12px;          /* photos are 12px, section 4 */
	overflow: hidden;
	background: #0c0c0c;
	border: 1px solid rgba(255, 255, 255, 0.08);
	text-decoration: none;
	transition: border-color 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}

.apex-builds-track .apex-build-card:hover {
	border-color: var(--apex-blue);
}

.apex-builds-track .apex-build-shot {
	display: block;
	line-height: 0;
}

.apex-builds-track .apex-build-card img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 4 / 3;
	object-fit: cover;
	background: #0c0c0c;
}

.apex-builds-track .apex-build-meta {
	display: block;
	padding: 14px 16px 16px;
}

/* The chip. Small, blue, uppercase — the gallery's own category line, given
   the shape the client drew. */
.apex-builds-track .apex-build-cat {
	display: inline-block;
	font-family: var(--apex-display), sans-serif;
	font-size: 0.5625rem;
	font-weight: 600;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: #ffffff;
	background: #0072ce;
	border-radius: 999px;
	padding: 5px 11px;
}

.apex-builds-track .apex-build-title {
	display: block;
	margin-top: 10px;
	font-family: var(--apex-heading), sans-serif;
	font-size: 0.9375rem;
	font-weight: 600;
	line-height: 1.25;
	color: #ffffff;
}

/* The two rows run at slightly different speeds as well as in opposite
   directions. Matched speeds read as one object sliding apart; a few seconds
   between them keeps the pair from ever lining up into a pattern. */
/* translate3d, not translateX. The 2-D form leaves the track on the main
   thread in some builds and the drift arrives in visible steps — the "clunky"
   the client described. The 3-D form promotes it to its own layer, and
   backface-visibility pins that layer's rasterisation so it is composited once
   and moved rather than repainted each frame. */
@keyframes apexBuildsLeft {
	from { transform: translate3d(0, 0, 0); }
	to   { transform: translate3d(-50%, 0, 0); }
}

@keyframes apexBuildsRight {
	from { transform: translate3d(-50%, 0, 0); }
	to   { transform: translate3d(0, 0, 0); }
}

/* The duration is set per row by customise.py, as an inline custom property,
   from the row's own width — one pace whatever the row holds, where a fixed
   72s got faster every time a card was added. The fallbacks are what twelve
   cards used to measure. */
.apex-builds-row--right .apex-builds-track {
	animation: apexBuildsRight var(--apex-builds-duration, 72s) linear infinite;
}

.apex-builds-row--left .apex-builds-track {
	animation: apexBuildsLeft var(--apex-builds-duration, 84s) linear infinite;
}

/* It does not stop under the cursor. Pausing was here first, on the reasoning
   that a gallery you cannot look at is a banner — the client watched it and
   asked for the drift to be continuous instead, so the rows now run past the
   pointer. Every photograph in them is also a card in the gallery, which is
   where a visitor goes to stop on one. */

/* Fifty-first pass. The full-bleed pull is gone on mobile — the client's
   own words were that the section "needs to be position corrected," and
   the actual mismatch was the container: the "Recent Builds & Inspirations"
   heading sits at the page's normal 20px inset, and the marquee's negative
   margin pulled its OWN box out to the true screen edge, so the two boxes
   were different widths even though the eye reads them as one section. A
   continuously-scrolling track has no fixed "first card" to align with the
   heading anyway — which card sits at the left edge changes every frame —
   so the fix is the container width, not a card position: 0 margin here
   matches the heading's box exactly, and the track still scrolls freely
   inside it, just clipped to the same width as everything else on the page
   instead of bleeding past it. */
@media (max-width: 767px) {
	body .apex-builds {
		margin-left: 0;
		margin-right: 0;
	}

	.apex-builds-track .apex-build-card {
		width: 232px;
		margin-right: 14px;
	}

	.apex-builds-track .apex-build-meta {
		padding: 11px 13px 13px;
	}

	.apex-builds-row {
		margin-bottom: 14px;
	}

}

@media (prefers-reduced-motion: reduce) {
	.apex-builds-track {
		animation: none !important;
	}

	/* Still scrollable by hand, so the photographs are not simply lost to
	   anyone who has asked for less movement. */
	.apex-builds-row {
		overflow-x: auto;
	}
}

/* Fifty-first pass — the row can be dragged by hand on mobile too, at the
   client's request ("scroll the pictures manually as well"), not just the
   reduced-motion fallback above.

   Fifty-sixth pass — rebuilt around "keep the animation it had no matter
   what." The row no longer becomes a scroll surface at all — `overflow:
   hidden` (section 45's own default) is exactly what the real `animation`
   this section defines needs, and that animation is what builds-drag.js
   now leaves running untouched except for the span of an actual drag. This
   class marks a row whose JS has finished attaching (added once, at setup,
   never toggled per-drag) — the cursor affordance and touch handling below
   are gated on it so they only ever show up once dragging genuinely works,
   not the instant the class list happens to exist. `touch-action: pan-y`
   is what lets builds-drag.js drive a touch drag by hand (via `transform`,
   the same as a mouse drag) without the browser also trying to pan the
   page horizontally underneath it; vertical page scroll is left alone. */
.apex-builds-row.apex-builds-draggable {
	touch-action: pan-y;
	cursor: grab;
}

.apex-builds-row.apex-builds-draggable.apex-builds-dragging {
	cursor: grabbing;
	user-select: none;
}

/* Fifty-seventh pass — the row says it can be dragged, and a card opens the
   vehicle it shows.

   THE HINT. `cursor: grab` was the only thing on the page that said the row
   was a control rather than a banner, and it says it to a mouse only, only
   once the pointer is already sitting on the row. This is the same statement
   made where it can be read before anyone reaches for it: two chevrons
   pointing outwards with DRAG between them, directly under the row, in the
   category chip's own typography (section 54) so it belongs to the cards
   rather than arriving as a piece of browser chrome. A hairline pill around
   it for the same reason the chip has one — bare 9px type under a row of
   420px photographs is not a control, it is a caption, and it was read as
   one in the pane; the outline is what makes it something offered rather
   than something said.

   `pointer-events: none` because it sits inside `.apex-builds`, within reach
   of a drag that overshoots the row; it must never become the thing under the
   pointer. builds-drag.js adds `--done` the first time a real drag happens —
   an instruction that has been followed is clutter — and the transition here
   is what makes that a fade rather than a disappearance. `visibility` rides
   along with `opacity` so the faded-out label is genuinely gone rather than
   invisible-but-present. */
.apex-builds-hint {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 10px;
	width: max-content;
	max-width: 100%;
	margin: 18px auto 0;
	padding: 7px 15px;
	border: 1px solid rgba(255, 255, 255, 0.14);
	border-radius: 999px;
	background: rgba(255, 255, 255, 0.03);
	pointer-events: none;
	font-family: var(--apex-display), sans-serif;
	font-size: 0.625rem;
	font-weight: 600;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: rgba(255, 255, 255, 0.62);
	transition: opacity 0.5s ease, visibility 0.5s ease;
}

.apex-builds-hint.apex-builds-hint--done {
	opacity: 0;
	visibility: hidden;
}

/* The chevrons are two borders on a rotated pseudo-element — no icon font to
   load and no SVG to inject, and they take the blue the chip and the card's
   hover ring already use. The rotation lives on `::before` and the animation
   on the box around it, so the two never fight over one `transform`. */
.apex-builds-hint-arrow {
	position: relative;
	display: block;
	width: 10px;
	height: 10px;
	flex: 0 0 auto;
}

.apex-builds-hint-arrow::before {
	content: "";
	position: absolute;
	top: 1px;
	width: 7px;
	height: 7px;
	border-left: 2px solid var(--apex-blue);
	border-bottom: 2px solid var(--apex-blue);
}

.apex-builds-hint-arrow--l::before {
	left: 2px;
	transform: rotate(45deg);
}

.apex-builds-hint-arrow--r::before {
	left: 1px;
	transform: rotate(-135deg);
}

/* Both arrows drift outwards together and come back — the gesture the row
   wants, not a blink. Slow and small on purpose: it is a hint under a moving
   row, and anything faster competes with the row itself for attention. */
.apex-builds-hint-arrow--l {
	animation: apexBuildsHintL 2.6s ease-in-out infinite;
}

.apex-builds-hint-arrow--r {
	animation: apexBuildsHintR 2.6s ease-in-out infinite;
}

@keyframes apexBuildsHintL {
	0%, 100% { transform: translateX(0); opacity: 0.55; }
	50%      { transform: translateX(-4px); opacity: 1; }
}

@keyframes apexBuildsHintR {
	0%, 100% { transform: translateX(0); opacity: 0.55; }
	50%      { transform: translateX(4px); opacity: 1; }
}

/* THE CARDS. A card that opens something and a card that does not should not
   look the same. The hover ring (section 54) is the promise, so a card with no
   gallery page behind it — customise.py renders those as a <span>, not a dead
   <a> — never gets one, and its name stays white where a real link's turns
   blue. Nothing else changes: the client asked for those to be ignored on a
   click, not taken out of the row. */
.apex-builds-track .apex-build-card--nolink:hover {
	border-color: rgba(255, 255, 255, 0.08);
}

.apex-builds-track a.apex-build-card:hover .apex-build-title {
	color: var(--apex-blue);
}

.apex-builds-track .apex-build-title {
	transition: color 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}

/* Both of these belong to the hint and so they live with it, AFTER the rules
   they override — not up in section 54's own mobile and reduced-motion blocks
   where the rest of the row's variants are. A media query adds no specificity,
   so a `@media` block written earlier in the file loses to a plain rule
   written later, silently: the mobile padding measured 7px/15px and the
   reduced-motion `animation: none` never took. Source order is the whole
   mechanism here. */
@media (max-width: 767px) {
	.apex-builds-hint {
		margin-top: 13px;
		padding: 6px 13px;
	}
}

/* The label stays under reduced motion — with the drift stopped, dragging is
   the only way to see the rest of the row, so it matters more there, not less
   — but it stops nudging, which is the part that was asked to go away. */
@media (prefers-reduced-motion: reduce) {
	.apex-builds-hint-arrow--l,
	.apex-builds-hint-arrow--r {
		animation: none;
		opacity: 0.85;
	}
}

/* ==========================================================================
   55. Homepage services grid — one image box for all nine cards
   The six original cards render their photo in a fixed 244px-tall box with
   object-fit: cover, whatever the file's own proportions — their declared
   sizes run from 361x244 to 911x500 and they all come out identical. The three
   cards added in the eighth pass did not: they landed at `height: auto` and
   `object-fit: contain`, so each one sized itself from its file and the bottom
   row stood 7px shorter than the top two with the photo letterboxed inside it.

   Which rule the originals match and the copies do not was not worth chasing.
   Stating the box here settles it for all nine and for anything dropped in
   later, the same way section 52 does for the gallery grid: the shape belongs
   to the card, not to the file that happens to be in it.

   The box is the fixed 244px height the six originals were authored with, and
   the grid keeps Elementor's own 1fr columns, so the row spreads the way it
   always did and the six look exactly as they always have — a wider rectangle
   on a wider screen.

   What changes is only the three cards added in the eighth pass. Their
   photographs are cut to the box's 1.44 shape, so on a screen where the card
   is wider than that, `cover` fills the extra width by taking it off the ends
   of the vehicle — the bumper off the F-350, the bed off the Super Duty. Those
   three get `contain` instead: the picture shrinks until the whole of it fits
   and centres, which is what the client asked for in as many words.

   `contain` normally means letterboxing, and normally that is the reason not
   to use it. Here the card has no background of its own and the page behind is
   solid black, so the space either side of a shrunk photograph is the same
   black as everything around it. There is nothing to see.

   Three earlier attempts are worth recording. Pinning the height alone cropped
   the trucks on a wide screen. Capping the card at 355px held the size but left
   it stranded at the start of a 561px `1fr` track, so the columns had to be
   sized too — and that changed the layout. The third was `aspect-ratio`, which
   "stopped the cropping but grew the pictures with the screen" — and growing
   with the screen is exactly what the client chose in the thirty-sixth pass,
   once the alternative was understood to be losing the roof off the Tesla. The
   single rule below is the one that applies; a second copy pinning 244px used
   to sit here and was dead weight the later rule overrode.
   ========================================================================== */

/* The box is 16:9, and that is what makes every photograph show whole.

   A fixed 244px height cannot do it. The card's width comes from Elementor's
   own 1fr columns and the grid is uncapped, so a fixed height means the box's
   *ratio* climbs with the screen — measured on the page at 1.44 at 1280px,
   2.31 at 1920 and 3.19 at 2560. With `cover`, the fraction of a photograph
   that survives is `photo ratio / box ratio`, so a 16:9 file showed 100% of its
   height at 1280, 77% at 1920 and 56% at 2560. The client watched the Tesla
   lose its roof on a wide monitor and asked for the whole picture to fit.

   Pinning the *ratio* instead makes box and photograph the same shape at every
   width, so `cover` has nothing to crop. The cards now grow and shrink with
   their column — 351x197 at 1280, 564x317 at 1920, 778x438 at 2560 — which is
   the price of the whole picture, and the grid itself is untouched: still
   Elementor's 1fr columns, no cap, no centring.

   `height: auto` is required alongside it. The image widget's generated CSS
   sets a height, and an `aspect-ratio` loses to a definite height — the same
   trap as sections 8 and 62.

   This only works while the photographs are 16:9. Eight of the nine are: the
   four client selects are 3840x2160 and the four original mirror files run
   1.80-1.89, close enough that `cover` takes a sliver off the sides and no
   height at all. Suspension is the ninth and it is cut to 16:9 by
   prep_home_cards.py for exactly this reason. **A card photo added later must
   be 16:9 or wider**, or it will be the one thing on the page that crops.
   ========================================================================== */
body .elementor-element-3c2c465 .elementor-widget-image img {
	width: 100% !important;
	height: auto !important;
	aspect-ratio: 16 / 9 !important;
	object-fit: cover !important;
	object-position: center center;
}

/* Forty-seventh pass: the grid is capped now, at the client's request — at
   1920px wide it was running 568px cards, three rows at 429px tall each,
   1408px of grid before the section's own heading and padding, and the
   client sent a screenshot of the third row cut off by the bottom of their
   window. 1100px brings the same three rows to roughly 862px — inside a
   typical browser's usable height even on a laptop screen, not just a large
   monitor. It keeps every guarantee section 55 documents (16:9 boxes,
   `cover`, no crop): capping the grid only shrinks what its own 1fr columns
   have to divide, so all nine cards scale down together and stay the same
   shape. `margin-inline: auto` centres the capped grid in a section that
   still runs full width. */
body.home :is(.elementor-element-3c2c465, .elementor-element-dfc0fb8) {
	max-width: 1100px !important;
	margin-inline: auto !important;
}

/* ==========================================================================
   56. Homepage services grid — the caption bar fills on hover
   The bar under each photograph carries the service name and a blue disc with
   an arrow in it. On hover it now floods Apex blue from its own centre
   outwards, the name goes white, and the disc inverts — white circle, blue
   arrow — so the two ends of the bar change together.

   scaleX from a centred origin rather than a width transition: transforms are
   composited and width is not, so this stays smooth next to a 24-card marquee
   animating on the same page. `transform-origin: center` is what makes it open
   from the middle to both edges rather than wiping across.

   The fill goes on a pseudo-element at z-index -1 with `isolation: isolate` on
   the bar. That pairing is what puts it above the bar's own background — the
   bar is authored `background_background: classic` — and below the text, which
   a plain background on the bar could not do.

   The heading colour needs the long selector: section 23 paints these titles
   --apex-blue through `.elementor-widget-heading .elementor-heading-title` with
   !important, and !important against !important is settled on specificity.

   The bar is reached structurally, and that is the whole trick. Its widget id
   is 4532b7e on the Window Tint card and on the three cards cloned from it, and
   something different on each of the other five — 9c7bfed, f090614, fefb1c3,
   67fc1b5, 2119051. Keyed on the id, the fill worked on four cards out of nine,
   which is exactly what the client reported. Every card is an <a> holding two
   children: the image widget, which is a .elementor-widget, and the bar, which
   is the only .e-con among them. `> a > .e-con` names it on all nine and on
   anything added later.

   The heading and icon rules below were already generic and always worked on
   all nine; only the fill was pinned.
   ========================================================================== */
body .elementor-element-3c2c465 > a > .e-con {
	position: relative;
	isolation: isolate;
	overflow: hidden;
}

body .elementor-element-3c2c465 > a > .e-con::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	background: #0072ce;
	transform: scaleX(0);
	transform-origin: center;
	transition: transform 0.42s cubic-bezier(0.32, 0.72, 0, 1);
	pointer-events: none;
}

body .elementor-element-3c2c465 > a:hover > .e-con::before,
body .elementor-element-3c2c465 > a:focus-visible > .e-con::before {
	transform: scaleX(1);
}

body .elementor-element-3c2c465 .elementor-widget-heading .elementor-heading-title {
	transition: color 0.28s ease;
}

body .elementor-element-3c2c465 a:hover .elementor-widget-heading .elementor-heading-title,
body .elementor-element-3c2c465 a:focus-visible .elementor-widget-heading .elementor-heading-title {
	color: #ffffff !important;
}

/* The disc and the arrow are a <rect> and a <path> with presentation
   attributes, which any CSS fill beats. */
body .elementor-element-3c2c465 .elementor-widget-icon svg rect,
body .elementor-element-3c2c465 .elementor-widget-icon svg path {
	transition: fill 0.28s ease;
}

body .elementor-element-3c2c465 a:hover .elementor-widget-icon svg rect,
body .elementor-element-3c2c465 a:focus-visible .elementor-widget-icon svg rect {
	fill: #ffffff;
}

body .elementor-element-3c2c465 a:hover .elementor-widget-icon svg path,
body .elementor-element-3c2c465 a:focus-visible .elementor-widget-icon svg path {
	fill: #0072ce;
}

@media (prefers-reduced-motion: reduce) {
	body .elementor-element-3c2c465 > a > .e-con::before {
		transition: none;
	}
}

/* ==========================================================================
   57. Gallery grid — the whole card fills on hover
   The same treatment as section 56, on the thirteen vehicle cards: Apex blue
   floods the card from its centre outwards and the type on it goes white. The
   card already took an Apex-blue border on hover from section 27; the two now
   read as one gesture rather than an outline and a separate nothing.

   The fill was scoped to the caption block first, which left it as a square
   rectangle sitting inside a black margin — 20px of card either side of it,
   10px between it and the photograph, and whatever slack the equal-height row
   left below. The client asked for it to go right out to the line and around
   the picture, so it moved up to the anchor. Rounded to match the border.

   It opens as a circle, not a horizontal sweep. `scaleX` from a centred origin
   was the first shape — a line down the middle of the card growing to both
   edges — and the client asked for the blue to come out of the middle in every
   direction instead. `clip-path: circle()` does that without a second element:
   the pseudo is already the full card, and the clip decides how much of it is
   showing.

   **72%, and the number is not arbitrary.** A circle's percentage radius
   resolves against `sqrt(w² + h²) / sqrt(2)`, so the radius that reaches a
   corner — `sqrt(w² + h²) / 2` — is always `sqrt(2)/2`, or 70.71%, whatever
   the card measures. 72% clears it with a little margin and stays exact at
   every screen width. Anything at or under 70% leaves four blue-less corners
   at the end of the animation, which is the state the client said they liked.

   The transition is on `clip-path` rather than `transform`, so this animates
   on the main thread where the old one composited. One clip on a card-sized
   box, on one card at a time, is well inside what that costs.

   The category line above the title is #333 at rest, which is nearly invisible
   on the #0c0c0c card and was already so before this; on the blue it goes
   white with the title rather than being left behind.
   ========================================================================== */
/* The fill is the card's, not the caption's. It is painted on the anchor —
   the one element whose box is the whole card out to the border section 27
   draws — so the blue reaches that line on all four sides and runs around the
   photograph as well as under it. The photograph is opaque and in flow, so it
   stays on top and the blue reads as a frame around it continuous with the
   block beneath, rather than a rectangle floating inside a black surround.

   `isolation: isolate` + `z-index: -1` is section 56's trick: the pseudo has
   to paint above the anchor's own #0c0c0c background but below its content,
   and a negative-z-index child does exactly that inside a stacking context of
   its own.

   Sized with width/height, never `inset`. Elementor's stack already gives
   `.e-con::before` `width: max(100% + 0px, 100%)` and the same height, and the
   anchor is itself an `.e-con`; adding `inset: 0` on top over-constrains the
   box, so right and bottom are dropped and the fill comes out shifted up and
   left. Radius is 10px, not 12: the pseudo is sized to the padding box, inside
   the 2px border, and 12 - 2 traces exactly the corner the border does. */
body .elementor-element-3e1bad4 .e-loop-item > a {
	isolation: isolate;
}

body .elementor-element-3e1bad4 .e-loop-item > a::before {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	border-radius: 10px;
	z-index: -1;
	background: #0072ce;
	clip-path: circle(0% at 50% 50%);
	transition: clip-path 0.42s cubic-bezier(0.32, 0.72, 0, 1);
	pointer-events: none;
}

body .elementor-element-3e1bad4 .e-loop-item > a:hover::before,
body .elementor-element-3e1bad4 .e-loop-item > a:focus-visible::before {
	clip-path: circle(72% at 50% 50%);
}

body .elementor-element-3e1bad4 a .elementor-heading-title,
body .elementor-element-3e1bad4 a .elementor-icon-list-text {
	transition: color 0.28s ease;
}

body .elementor-element-3e1bad4 a:hover .elementor-heading-title,
body .elementor-element-3e1bad4 a:hover .elementor-icon-list-text,
body .elementor-element-3e1bad4 a:focus-visible .elementor-heading-title,
body .elementor-element-3e1bad4 a:focus-visible .elementor-icon-list-text {
	color: #ffffff !important;
}

@media (prefers-reduced-motion: reduce) {
	body .elementor-element-3e1bad4 .e-loop-item > a::before {
		transition: none;
	}
}

/* ==========================================================================
   58. Buttons — one hover, everywhere
   Every call to action on the site now does the same two things under the
   cursor: it grows very slightly, and if it is a white button it fills black.

   Neither was consistent before, and neither was ours. Elementor lets a hover
   animation be picked per widget from a dropdown, and whoever built these
   pages picked differently each time: "Get Your Quote" in the vinyl hero
   carries `elementor-animation-bob`, the button under it carries
   `elementor-animation-grow`, and "GET DIRECTIONS" on the homepage, "Let's
   Talk", "START YOUR BUILD TODAY" and most of the rest carry nothing at all.
   The fill was the same story from the other direction: the widget id ec04120
   — "Explore Protection Options" on the homepage — was authored with a hover
   of `background: --e-global-color-secondary` (#000) and `color: primary`
   (#fff), and it is the one the client pointed at. Other white buttons with
   different ids were authored with no hover state, so they sat there.

   So this section states both, for all of them, rather than trying to reach
   the ones that were missed.

   The zoom is 1.05, not the 1.1 Elementor's own `grow` uses. At the size these
   buttons run, 1.1 reads as a lurch; the client asked for "slight". It wins
   over `grow` on specificity — `body .elementor-button:hover` is (0,2,1)
   against `.elementor-animation-grow:hover` at (0,2,0) — but it cannot win
   over `bob` that way, because bob is a keyframe animation and an animation
   beats a declaration however specific. Bob is switched off by name instead.
   ========================================================================== */
body .elementor-button {
	transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1),
	            background-color 0.28s ease,
	            color 0.28s ease,
	            border-color 0.28s ease;
}

body .elementor-button:hover,
body .elementor-button:focus-visible {
	transform: scale(1.05);
}

/* Bob translates the button up and holds it there, on a loop. Two transforms
   cannot both apply, and the animated one wins, so the button would rise and
   never grow. */
body .elementor-button.elementor-animation-bob:hover,
body .elementor-button.elementor-animation-bob:focus,
body .elementor-button.elementor-animation-bob:active {
	animation: none !important;
}

/* The white buttons. button-rings.js has already measured every button's fill
   and tagged the light ones .apex-btn-solid — the same measurement that keeps
   the rotating ring off them — so the set is exactly right without naming a
   single widget id.

   Black fill, white lettering, and the border goes with the fill so the shape
   does not change size. On the blue Key Features bands the pill is white on
   #0072ce, and black-on-blue is the same gesture there: 2.7:1 for the pill
   against the band, and the white type on it 21:1.

   The second arm exists to out-specify section 37, which pins those band
   buttons white with !important at (0,3,1). !important against !important is
   settled on specificity and the first arm is also (0,3,1) — it would win only
   on being later in the file, which is true today and is not something to rely
   on. Naming the widget wrapper takes it to (0,4,1). */
body .elementor-button.apex-btn-solid:hover,
body .elementor-button.apex-btn-solid:focus-visible,
body .elementor-widget-button .elementor-button.apex-btn-solid:hover,
body .elementor-widget-button .elementor-button.apex-btn-solid:focus-visible {
	background-color: #000000 !important;
	/* Belt and braces: an authored gradient would otherwise sit on top of the
	   black fill and the hover would look like no hover at all. */
	background-image: none !important;
	color: #ffffff !important;
	/* White, not black. These buttons already carry a 1px white border at rest
	   — invisible against their own white fill — so switching the colour rather
	   than the width draws the outline the client asked for with no change in
	   size and nothing to jump when the cursor arrives. */
	border-color: #ffffff !important;
}

/* The ring buttons keep their transparent fill and their rotating edge — that
   is the primary/secondary hierarchy button-rings.js exists to preserve. The
   ::after that paints their middle black scales with the button, so nothing
   here needs to touch it. */

@media (prefers-reduced-motion: reduce) {
	body .elementor-button {
		transition: background-color 0.2s ease, color 0.2s ease;
	}

	body .elementor-button:hover,
	body .elementor-button:focus-visible {
		transform: none;
	}
}

/* ==========================================================================
   59. The Carrollton contact page — the hero cutouts go, the blue box reads
   "START YOUR BUILD TODAY" in the closing band links to /contact-carrollton/
   (widget 264d83d, on About and every page that carries that band), so that is
   the page the client landed on. Three things on it:

   The Jeep cutout (5df9918) and the blue "WHO WE ARE" teardrop (6709ebc) are
   the hero furniture of a block this site already hides in most places —
   section 21 takes it off Contact, section 36 off the eight service pages, and
   About too. It is still standing on contact-carrollton, service-contact,
   privacy-policy, terms, the six city landing pages and the blog post, because
   none of those were ever in scope. Rather than add a ninth page to a list,
   the two widgets go site-wide: they are decoration on a hero the rest of the
   site has already dropped, and hiding them here makes every page agree.

   Hidden, not deleted — the file's rule for removals. Their container 1f72cef
   is left alone deliberately, and that is worth stating because hiding it was
   the obvious move and is the wrong one. It is a flex row with no padding and
   no min-height, so with both children hidden it collapses to 0px by itself —
   measured at 0 on contact-carrollton and on the city pages. **And the id is
   shared**: on /ai-discovery-page/ the same 1f72cef holds a block of body copy
   instead of these two cutouts, which a container-level rule would reach for
   no reason. Widget ids repeat across pages all over this site; a rule that
   only needs to name a leaf should name the leaf.

   "We're Here to Help" is an h2, and section 14 paints every h2 --apex-blue
   (#0072ce). The box behind it is #0072ce. Blue on blue is 1.4:1 — the line
   was effectively invisible, the same fault section 53 fixed on the vehicle
   pages' "About This Project" band, and the same fix: everything on a blue
   fill goes white.

   The selector has to out-specify section 14, whose longest arm is
   `body .elementor-widget-heading h2.elementor-heading-title` at (0,2,2) with
   !important. Naming the box takes this to (0,3,2), and !important against
   !important is settled on specificity.
   ========================================================================== */
body .elementor-element-5df9918,
body .elementor-element-6709ebc {
	display: none !important;
}

body .elementor-element-a5d8383 h2,
body .elementor-element-a5d8383 .elementor-heading-title,
body .elementor-element-a5d8383 .elementor-widget-heading h2.elementor-heading-title {
	color: #ffffff !important;
}

/* ==========================================================================
   60. Vehicle pages — the build details read white, and the band is rounded
   Three things the client asked for on the Corvette page, all of which are on
   the other twelve vehicle pages too: 3716c3f is on thirteen and
   .apex-feature-item on twelve.

   The description lines under each heading were `#333` — inherited from
   .apex-feature-item, which the theme sets and which is the same near-black
   this file has already had to overrule on the gallery cards. On the black
   page it reads as a grey whisper; the headings beside them are white. Both go
   white now, which is what was asked and also what the rest of this site does
   with body copy on black.

   The icons are SVG *files* in an <img>, not inline SVG, so `fill` cannot
   reach inside them — the shape's colour lives in the file. `filter:
   brightness(0) invert(1)` is what does it: brightness(0) crushes every opaque
   pixel to black whatever colour it was, invert(1) takes that to white, and
   neither touches the alpha channel, so the disc behind still shows through
   the transparent parts. It is colour-blind by construction, which means it
   keeps working when an icon is swapped for one drawn in another colour.

   The blue band takes the site's 12px. It is full-bleed, so the corners round
   against the page edge rather than against a margin — that is the same 12px
   the photographs use, which is what "like everywhere else" means here.
   ========================================================================== */
body .apex-feature-item,
body .apex-feature-text,
body .apex-feature-text p,
body .apex-feature-text div {
	color: #ffffff;
}

body .apex-feature-icon img {
	filter: brightness(0) invert(1);
}

body .elementor-element-3716c3f {
	border-radius: 12px;
	overflow: hidden;
}

/* ==========================================================================
   61. Key Features cards — icon in a black square, title beside it
   The client sent a mockup: on the blue band, each feature is a black rounded
   square holding its icon, with the title set beside it rather than under it,
   the whole row left-aligned, and no card behind any of it. Three across, two
   down. This is that, built out of the markup already on the page — same
   widget, same icon, same words, nothing injected.

   **Scoped to the icon groups that sit inside a blue band**, which is exactly
   the set the client means by "the Key Features little button things": section
   37 puts the fill on whichever section holds the icon cards, so a group
   inside cc789bb / 97041b8 / ed5be2b / 6c8b76b is a Key Features group by
   construction. Checked page by page — vinyl has one such group, PPF two,
   Suspension and Accessories one each plus one that is *not* in a band
   (9909127 on #202020, ba5f7a0 on #1b1b1b). Those keep their black cards: a
   black square on a near-black section would be invisible, and they are not
   what was asked about.

   **There are no descriptions.** Every icon box on these pages is an icon and
   a title — `.elementor-icon-box-description` does not exist on any of them.
   The mockup shows a line of lorem under each heading; there is no verbatim
   copy on the site to put there, and the instruction was to use only what is
   already written, so the card is icon plus title and the space closes up.

   **The blue stays #0072ce.** That is the fill the client confirmed in the
   fifth pass, when they were asked directly whether it should become
   --apex-blue (#0072ce) and said no: white on the lighter blue is 3.4:1 and
   fails AA, white on this is 4.6:1. "Apex blue" on a fill means this one.

   The font is the site's own: Oxanium for the title, which is what the icon
   boxes already use.
   ========================================================================== */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .e-con:has(> .e-con > .elementor-widget-icon-box) {
	column-gap: 32px !important;
	row-gap: 52px !important;
}

/* The card is gone — no fill, no radius, no padding. What reads as a "card" in
   the mockup is the icon's own square. */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .e-con:has(> .e-con > .elementor-widget-icon-box) > .e-con {
	width: calc((100% - 64px) / 3) !important;
	max-width: calc((100% - 64px) / 3) !important;
	background-color: transparent !important;
	border-radius: 0 !important;
	padding: 0 !important;
}

/* Icon left, text right. The wrapper is authored as a column and centred. */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-box-wrapper {
	display: flex !important;
	flex-direction: row !important;
	flex-wrap: nowrap !important;
	align-items: flex-start !important;
	gap: 22px !important;
	text-align: left !important;
}

/* The square. 112px at 1440, which is the mockup's proportion of the row, and
   it does not grow with the column — the text takes the slack, as it does in
   the mockup. 22px radius against the site's 12px for photographs: this is a
   tile, and the mockup's corner is visibly softer than a photo's. */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-box-icon {
	flex: 0 0 auto !important;
	width: 112px !important;
	height: 112px !important;
	margin: 0 !important;
	background-color: #000000 !important;
	border-radius: 22px !important;
	display: flex !important;
	align-items: center !important;
	justify-content: center !important;
}

body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-box-icon .elementor-icon {
	display: block !important;
	width: 56px !important;
	height: 56px !important;
	font-size: 56px !important;
	line-height: 0 !important;
}

body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-box-icon svg {
	width: 56px !important;
	height: 56px !important;
}

/* The pictogram is white on the black square, and the disc it sat on goes.
   Section 37 draws these as a white disc with a blue glyph, which is right on
   the bare blue band and wrong inside a black tile — the disc would read as a
   circle stamped in a square. The glyph carries the icon on its own.

   Section 37's rules are plain declarations at (0,3,3); these name the same
   arms plus the widget's own class, so they win on specificity rather than on
   file order. */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-widget-icon-box .elementor-icon-box-icon svg > circle {
	fill: transparent !important;
}

body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-widget-icon-box .elementor-icon-box-icon svg > path {
	fill: #ffffff !important;
}

/* The title sets beside the square, left, uppercase, and wraps to two lines
   rather than being squeezed onto one. */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-box-content {
	flex: 1 1 auto !important;
	text-align: left !important;
	padding-top: 4px !important;
}

body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-box-title,
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-box-title a {
	text-align: left !important;
	text-transform: uppercase !important;
	/* --apex-heading, not --apex-display: the first is Oxanium, which is what
	   these titles are already set in and what every heading on the site uses;
	   --apex-display is Archivo and belongs on interface labels. */
	font-family: var(--apex-heading) !important;
	font-weight: 700 !important;
	font-size: 22px !important;
	line-height: 1.16 !important;
	color: #ffffff !important;
	margin: 0 !important;
}

@media (max-width: 1024px) {
	body.apex-service-page :is(
		.elementor-element-cc789bb,
		.elementor-element-97041b8,
		.elementor-element-a0ca73f,
		.elementor-element-399a393,
		.elementor-element-1f22cac,
		.elementor-element-ed5be2b,
		.elementor-element-6c8b76b) .e-con:has(> .e-con > .elementor-widget-icon-box) > .e-con {
		width: calc((100% - 32px) / 2) !important;
		max-width: calc((100% - 32px) / 2) !important;
	}
}

@media (max-width: 767px) {
	body.apex-service-page :is(
		.elementor-element-cc789bb,
		.elementor-element-97041b8,
		.elementor-element-a0ca73f,
		.elementor-element-399a393,
		.elementor-element-1f22cac,
		.elementor-element-ed5be2b,
		.elementor-element-6c8b76b) .e-con:has(> .e-con > .elementor-widget-icon-box) > .e-con {
		width: 100% !important;
		max-width: 100% !important;
	}

	body.apex-service-page :is(
		.elementor-element-cc789bb,
		.elementor-element-97041b8,
		.elementor-element-a0ca73f,
		.elementor-element-399a393,
		.elementor-element-1f22cac,
		.elementor-element-ed5be2b,
		.elementor-element-6c8b76b) .elementor-icon-box-icon {
		width: 88px !important;
		height: 88px !important;
		border-radius: 18px !important;
	}
}

/* ==========================================================================
   62. Showcase photographs — one box on every page
   The big "here is a car" photograph beside the copy is the same widget on
   every service page and on About, and it was rendering at a different height
   on each of them. Measured at a 1600px window, all 708px wide:

       PPF          952f9bc   708x347   2.04:1   contain
       PPF          9a9409c   708x600   1.18:1   cover
       PPF          f2b4f96   708x600   1.18:1   cover
       Vinyl Wrap   952f9bc   708x600   1.18:1   cover
       Suspension   952f9bc   708x495   1.43:1   contain
       About        952f9bc   708x398   1.78:1   contain
       every page   ddd2bbc   708x495   1.43:1   contain
       Wheels       .apex-wheel-showcase       1.33:1   cover

   Four different heights and two different fits, because each one was taking
   its shape from whatever file happened to be in it. That is the same fault
   section 52 fixed on the gallery cards, and the same fix applies: state the
   box, let `cover` make the shape the box's rather than the file's, and the
   next replacement photograph of any proportion drops in without moving
   anything.

   **16:9.** It is the shape a car is photographed in, it is the hero's own
   language, and it is the shortest of the four heights in that table — which
   matters, because these sit beside a copy column and the client has twice
   asked for less scrolling. At 708px wide it renders 398px tall, which is
   exactly what About was already doing.

   `aspect-ratio` rather than a pixel height, for the reason section 55
   records: a height measured at one window turns into a letterbox at another.
   `height: auto !important` because the image widget's generated CSS pins a
   height on several of these — that is where 600px and 495px come from.

   This supersedes the 4/3 stated for `.apex-wheel-showcase` in section 8; that
   rule is left where it is because it also carries the caption styling, and
   its own comment explains the `height: auto` this depends on.
   ========================================================================== */
body :is(
	.elementor-element-952f9bc,
	.elementor-element-ddd2bbc,
	.elementor-element-9a9409c,
	.elementor-element-f2b4f96) {
	width: 100% !important;
}

body :is(
	.elementor-element-952f9bc,
	.elementor-element-ddd2bbc,
	.elementor-element-9a9409c,
	.elementor-element-f2b4f96) img,
body .apex-wheel-showcase img {
	display: block;
	width: 100% !important;
	height: auto !important;
	aspect-ratio: 16 / 9 !important;
	object-fit: cover !important;
	border-radius: 12px;          /* photos are 12px, section 4 */
}

/* ==========================================================================
   63. Hero — the photograph is letterboxed rather than sliced on a very wide
   screen
   The band's height caps at 1300px, so past that width its ratio keeps
   climbing while the photograph's does not, and `cover` answers by throwing
   away more and more of the frame's height. At 2954px the cap binds at 2.27:1;
   by 3690px — the client's window — the band is 2.84:1 and shows 69% of the
   file, which cuts the roof off the car.

   Zooming the crop out fixed it once and cannot fix it again. The arithmetic:
   a band of ratio r shows the bottom 1.96/r of the file and the type takes 298
   more pixels off the bottom, so at r = 2.84 the car has to fit inside 46% of
   the file's height. At the source's full 4864px width — the widest window
   there is — the car is 50%. To get it to 46% the window would have to be
   7710px wide. The photograph does not contain the pixels.

   So above the width where the crop would start eating the car, the picture
   stops filling the band and sits inside it instead: `contain`, centred, on
   the black the section already paints. The whole car, on a black field, which
   is what the page is made of anyway.

   2990px is where r reaches 2.30 — the widest band that still holds the car
   whole under `cover` — so this hands over exactly as the crop runs out and
   never before.
   ========================================================================== */
@media (min-width: 2990px) {
	body.home .elementor-element-33cea79 .elementor-background-video-container {
		background-size: 100% 100%, contain !important;
		background-position: 50% 100%, 50% 100% !important;
	}
}

/* ==========================================================================
   64. Button labels — bigger type in the same button
   The client asked for the wording inside every button to read larger without
   the button itself growing. Those are opposing requirements past a point: a
   button is sized by its text plus its padding, so the only room to give the
   type is the padding's.

   13px to 15px is +2px of cap height, and the vertical padding comes down by
   the same 2px so the button's height is unchanged. The width still grows by
   whatever the longer glyphs measure — that part cannot be given back without
   fixing a width and clipping the longest label — but it is the smaller half
   of the change, and the box no longer grows in the direction that would push
   the layout around it.

   Section 58's `scale(1.05)` hover is untouched; the client asked only for the
   resting size to stay put.
   ========================================================================== */
body .elementor-button {
	font-size: 15px !important;
	/* The line box is pinned so the button's height does not move. It measured
	   30px before with 13px type in it; 28px is that less the 2px the larger
	   type adds, and it holds every button at the 62px it measured.
	   `line-height: 1` was tried first and took them to 49px, which is the same
	   mistake in the other direction, and 30px left them 2px tall. The width still
	   grows by whatever the larger glyphs measure; that cannot be given back
	   without fixing a width and clipping the longest label. */
	line-height: 28px !important;
}

body .gform_wrapper .gform_next_button,
body .gform_wrapper .gform_previous_button,
body .gform_wrapper .gform_button,
body .gform_wrapper [id^="gform_submit_button"] {
	font-size: 0.9375rem !important;   /* 15px, matching .elementor-button */
	padding-top: 11px !important;
	padding-bottom: 11px !important;
}

/* ==========================================================================
   65. Gallery filters — the apey rack
   The client asked for these to look like the concept site in
   `website ideas/apey`. That design is a stack of black controls: a hairline
   border, a chevron drawn into the control rather than pulled from an icon
   font, Archivo at 0.8rem/600 in white, and a filled blue state when the
   dimension is actually holding a selection.

   Here they were the stock JetSmartFilters selects — white fills, Inter 16px,
   #666 borders — which is the one part of this page still reading as a form on
   a light site. Nothing about the markup changes: these are the same eight
   `<select>`s gallery-filters.js already drives.

   `appearance: none` is what lets the chevron be ours. It also removes the
   native arrow, so the padding has to leave room for the drawn one — 38px on
   the right against 15px on the left, exactly as apey has it.

   The blue "is set" state cannot be done in CSS: a select cannot be asked
   whether its chosen option is the empty placeholder. apey's filter engine
   sets a class; ours does not, so `:has(option[value=""]:checked)` is used to
   detect the *unset* case instead and the blue is applied to everything else.
   That is the same answer arrived at from the other side, and it needs no JS.
   ========================================================================== */
body .elementor-element-e69f6af .jet-select select,
body .elementor-element-e69f6af select {
	appearance: none !important;
	-webkit-appearance: none !important;
	width: 100% !important;
	font-family: var(--apex-display), sans-serif !important;
	font-size: 0.8125rem !important;
	font-weight: 600 !important;
	letter-spacing: 0.04em !important;
	color: #ffffff !important;
	background-color: #000000 !important;
	/* a hairline chevron, drawn rather than pulled from an icon font */
	background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%230072ce' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important;
	background-repeat: no-repeat !important;
	background-position: right 14px center !important;
	border: 1px solid var(--apex-hairline) !important;
	border-radius: 8px !important;      /* buttons are 8px, section 4 */
	padding: 13px 38px 13px 15px !important;
	height: auto !important;
	transition: border-color 0.3s ease, background-color 0.3s ease,
	            transform 0.15s ease;
}

body .elementor-element-e69f6af select:hover {
	border-color: rgba(0, 114, 206, 0.55) !important;
}

/* Press feedback, milder than the chips' — 98.5%. The native picker opens on
   mousedown, so anything stronger reads as the control flinching away from
   the list it just opened. */
body .elementor-element-e69f6af select:active {
	transform: scale(0.985);
}

body .elementor-element-e69f6af select:focus-visible {
	outline: none !important;
	border-color: var(--apex-blue) !important;
	box-shadow: 0 0 0 3px rgba(0, 114, 206, 0.24);
}

/* Holding a selection. Read as "not on the empty option" rather than as "on a
   real one", because that is the half CSS can see. */
body .elementor-element-e69f6af select:not(:has(option[value=""]:checked)) {
	border-color: var(--apex-blue) !important;
	background-color: rgba(0, 114, 206, 0.16) !important;
}

body .elementor-element-e69f6af select option {
	background: #000000;
	color: #ffffff;
}

body .elementor-element-e69f6af select option:disabled {
	color: rgba(255, 255, 255, 0.35);
}

/* The categories become apey's chips: a pill per category, filled blue when
   checked, with the native box hidden rather than restyled — a checkbox cannot
   be made into a pill, so the label is the control and the input only carries
   the state. */
body .elementor-element-e69f6af .jet-checkboxes-list {
	display: flex !important;
	flex-wrap: wrap !important;
	gap: 8px !important;
}

body .elementor-element-e69f6af .jet-checkboxes-list__item {
	position: relative;
	display: inline-flex !important;
	align-items: center;
	gap: 0;
	margin: 0 !important;
	font-family: var(--apex-display), sans-serif !important;
	font-size: 0.6875rem !important;
	font-weight: 600 !important;
	letter-spacing: 0.14em !important;
	text-transform: uppercase !important;
	color: rgba(255, 255, 255, 0.72) !important;
	background: #000000;
	border: 1px solid var(--apex-hairline);
	border-radius: 999px;
	padding: 10px 16px !important;
	cursor: pointer;
	/* isolate: the checked fill lives on a z-index:-1 ::before (below), and
	   isolation is what keeps it above the pill's own black but below its
	   label — the section 56 pattern. */
	isolation: isolate;
	transition: color 0.3s ease, border-color 0.3s ease,
	            background-color 0.3s ease, transform 0.12s ease;
}

body .elementor-element-e69f6af .jet-checkboxes-list__item input[type="checkbox"] {
	position: absolute;
	opacity: 0;
	width: 1px;
	height: 1px;
	margin: 0;
}

body .elementor-element-e69f6af .jet-checkboxes-list__item:hover {
	border-color: rgba(0, 114, 206, 0.55);
	color: #ffffff !important;
}

/* Pressing a chip. Two motions, both already in the site's vocabulary:

   1. The pill dips to 95% while the pointer is down — the same "the control
      felt that" cue section 58 gives buttons, inverted (down, not up).
   2. The blue fill arrives as the circle wipe the gallery cards (section 57)
      and the quote form's service rows already use — clip-path circle(0%) to
      circle(72%), 72% because a circle's percentage radius resolves against
      the diagonal/sqrt(2), so sqrt(2)/2 is the exact radius that reaches the
      corners whatever the pill measures.

   The fill is painted on a ::before at z-index:-1 rather than on the pill's
   background — an instant background swap underneath would make the wipe
   invisible. inset:0 puts it at the padding box, so it never covers the
   border, and width/height are stated as well in case anything in the stack
   hands pseudo-elements a size (the .e-con ring trap). The unchecking wipe
   runs faster than the check: a collapsing circle lingering under the label
   reads as the control disagreeing with the click. */
body .elementor-element-e69f6af .jet-checkboxes-list__item:active {
	transform: scale(0.95);
}

body .elementor-element-e69f6af .jet-checkboxes-list__item::before {
	content: "";
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	z-index: -1;
	background: #0072ce;
	border-radius: inherit;
	clip-path: circle(0% at 50% 50%);
	transition: clip-path 0.28s ease-out;
	pointer-events: none;
}

body .elementor-element-e69f6af .jet-checkboxes-list__item:has(input:checked)::before {
	clip-path: circle(72% at 50% 50%);
	transition: clip-path 0.42s cubic-bezier(0.32, 0.72, 0, 1);
}

body .elementor-element-e69f6af .jet-checkboxes-list__item:has(input:checked) {
	border-color: #0072ce;
	color: #ffffff !important;
}

body .elementor-element-e69f6af .jet-checkboxes-list__item:has(input:focus-visible) {
	outline: 2px solid var(--apex-blue);
	outline-offset: 2px;
}

/* The fill needed a stronger arm than the block above. `background-color` was
   the one declaration in section 65 that did not take — radius, font, colour,
   appearance and the drawn chevron all did — so something in the Jet or theme
   stack is setting it at a specificity this file's `body .elementor-element-…
   select` (0,2,2) does not clear. `select[class][class]` repeats the attribute
   selector to reach (0,4,2) without naming a class that might change. */
body .elementor-element-e69f6af select[class][class] {
	background-color: #000000 !important;
	border-color: var(--apex-hairline) !important;
}

body .elementor-element-e69f6af select[class][class]:not(:has(option[value=""]:checked)) {
	background-color: rgba(0, 114, 206, 0.16) !important;
	border-color: var(--apex-blue) !important;
}

/* The pill's own background stays black in BOTH states now — the checked
   blue is the ::before wipe above. Forcing the background blue here (as this
   rule originally did) would paint behind the pseudo and swallow the
   animation. Only the border still flips instantly-ish (it rides the item's
   0.3s border-color transition). */
body .elementor-element-e69f6af .jet-checkboxes-list__item[class][class] {
	background-color: #000000 !important;
}

body .elementor-element-e69f6af .jet-checkboxes-list__item[class][class]:has(input:checked) {
	border-color: #0072ce !important;
}

/* Fiftieth pass — the filter rail collapses into a drawer, mobile only.
   Nine controls (four JetSmartFilters selects, four service-filter selects,
   the category checkboxes) used to stack full-height above the first
   vehicle card; the client asked for "a subwindow, a little dropdown."

   The heading and its divider stay visible above the toggle — they read as
   the section's own title either way. Everything from the divider down
   (customise.py's add_gallery_filter_toggle) moves inside
   .apex-gallery-filters-body, hidden by default below 767px and shown by
   the adjacent-sibling rule keyed on the button's own `aria-expanded`
   attribute — no separate class to keep in sync with the button's state,
   the attribute IS the state.

   The button needs a default `display: none` OUTSIDE the media query, not
   just the `display: flex` inside it — left unstated, a bare <button> falls
   back to the browser's own default appearance (and default visibility) on
   desktop, which is exactly what showed up sitting above the always-visible
   selects there until this was measured on a real desktop screenshot rather
   than assumed to follow the mobile-only rules below it.

   Styled to match this section's own "apey rack" language above: black
   fill, the hairline border, Archivo/apex-display at the same weight, 8px
   radius, and the identical drawn chevron used on every select in this
   rail — just rotated 180° when open instead of swapped for a different
   glyph. */
body .elementor-element-e69f6af .apex-gallery-filters-toggle {
	display: none;
}

@media (max-width: 767px) {
	body .elementor-element-e69f6af .apex-gallery-filters-toggle {
		display: flex !important;
		align-items: center;
		justify-content: space-between;
		width: 100%;
		font-family: var(--apex-display), sans-serif;
		font-size: 0.875rem;
		font-weight: 600;
		letter-spacing: 0.04em;
		text-transform: uppercase;
		color: #ffffff;
		background-color: #000000;
		border: 1px solid var(--apex-hairline);
		border-radius: 8px;
		padding: 13px 15px;
		margin-bottom: 16px;
		cursor: pointer;
		transition: border-color 0.3s ease, background-color 0.3s ease;
	}

	body .elementor-element-e69f6af .apex-gallery-filters-toggle svg {
		flex: 0 0 auto;
		color: var(--apex-blue);
		transition: transform 0.25s ease;
	}

	body .elementor-element-e69f6af .apex-gallery-filters-toggle[aria-expanded="true"] {
		border-color: var(--apex-blue);
		background-color: rgba(0, 114, 206, 0.16);
	}

	body .elementor-element-e69f6af .apex-gallery-filters-toggle[aria-expanded="true"] svg {
		transform: rotate(180deg);
	}

	body .elementor-element-e69f6af .apex-gallery-filters-body {
		display: none;
	}

	body .elementor-element-e69f6af
	.apex-gallery-filters-toggle[aria-expanded="true"] + .apex-gallery-filters-body {
		display: block;
	}
}

/* ==========================================================================
   66. FAQ — an open answer is a blue panel
   The accordion is Elementor's nested one: a `<details class="e-n-accordion-
   item">` per question, the question in `<summary>`, the answer in a container
   inside. Open and closed both sat on the page's black with a hairline between
   them, so an opened answer looked like more page rather than like a panel —
   the client could not tell where one ended and the next began.

   Open takes the fill. `details[open]` is the state selector, so no JS is
   involved and it works on every accordion on the site at once.

   #0072ce again, the fill blue confirmed in the fifth pass, and everything on
   it goes white for the same reason it does in section 37.
   ========================================================================== */
body details.e-n-accordion-item[open] > .e-con,
body details.e-n-accordion-item[open] > div:not(summary) {
	background-color: #0072ce !important;
	border-radius: 0 0 8px 8px;
}

body details.e-n-accordion-item[open] > .e-con :is(p, li, span, a, h2, h3, h4),
body details.e-n-accordion-item[open] > div:not(summary) :is(p, li, span, a) {
	color: #ffffff !important;
}

/* The question keeps the black, so the pair reads as a label above its panel
   rather than as one blue block. */
body details.e-n-accordion-item[open] > summary {
	border-bottom-color: #0072ce !important;
}

/* ==========================================================================
   67. Key Features cards — the icon fills its tile, the row centres, the
   button centres
   Four things the client asked for on the band section 61 built.

   **The pictogram is 84px in the 112px tile**, up from 56. Half the tile read
   as a small mark floating in a large square; three quarters reads as the tile
   being the icon's own shape, which is what the mockup drew.

   **No movement under the cursor.** These cards are authored with Elementor's
   `_transform_scale_effect_hover`, which is why the whole tile crept when the
   mouse crossed it. The client asked for it gone; the section's own hover
   language is carried by the buttons and the gallery, not by these.

   **The title centres against the tile.** The wrapper was `align-items:
   flex-start`, so a one-line title sat at the top of a 112px tile and a
   two-line one did not — the rows never lined up with each other. Centred, the
   text is on the tile's axis whatever it wraps to.

   **The button centres.** Its container is authored `text-align: right`, which
   put "Start Your PPF Project Today" hard against the right edge of a
   full-width band while everything else in the section started on the left.
   ========================================================================== */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-box-icon .elementor-icon {
	width: 84px !important;
	height: 84px !important;
	font-size: 84px !important;
}

body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-box-icon svg {
	width: 84px !important;
	height: 84px !important;
}

body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-icon-box-wrapper {
	align-items: center !important;
}

/* Elementor writes the hover scale onto the card container, which is the
   `.e-con` holding the widget. Both it and the widget are pinned so nothing in
   the chain can move. */
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .e-con:has(> .e-con > .elementor-widget-icon-box) > .e-con,
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-widget-icon-box {
	transform: none !important;
	transition: none !important;
}

body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-widget-button {
	text-align: center !important;
}

body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-widget-button .elementor-widget-container,
body.apex-service-page :is(
	.elementor-element-cc789bb,
	.elementor-element-97041b8,
	.elementor-element-a0ca73f,
	.elementor-element-399a393,
	.elementor-element-1f22cac,
	.elementor-element-ed5be2b,
	.elementor-element-6c8b76b) .elementor-widget-button .elementor-button-wrapper {
	text-align: center !important;
}

/* The fill needed a stronger arm, exactly as the filter selects did in section
   65. The radius from the block above applied and the background did not, so
   Elementor is setting the container's colour at a specificity this file's
   `body details… > .e-con` does not clear — its containers carry their fill
   through a generated `.elementor-element-NNNN` rule. Repeating the attribute
   selector reaches past it without naming an id that could change. */
body details.e-n-accordion-item[open] > div[class][class] {
	background-color: #0072ce !important;
}

body details.e-n-accordion-item[open] > div[class][class] :is(p, li, span, a) {
	color: #ffffff !important;
}

/* ==========================================================================
   68. Homepage — "Two Convenient Locations" goes
   The fourth item in the arrow list under the hero copy (widget 232bf1a) still
   promised two shops. There has been one since the Tulsa removal, so it is a
   claim the site cannot keep — the same cleanup as the footer column and the
   contact cards in sections 33-36, just missed at the time because it is
   phrased as a benefit rather than as a place name.

   Hidden rather than deleted, which is this file's rule for removals: the
   markup is mirrored and mirror.py would write it back.

   `:nth-child(4)` because CSS cannot select on text. The list is Premium
   Custom Builds / Certified Protection / Experienced Team / Two Convenient
   Locations, and it exists on the homepage only — checked, the phrase appears
   nowhere else on the site. **If an item is ever added above it, this number
   moves.**
   ========================================================================== */
body.home .elementor-element-232bf1a .elementor-icon-list-items > li:nth-child(4) {
	display: none !important;
}

/* ==========================================================================
   69. Hero headline — the room Montserrat needs to stay on one line
   Section 17 exists because the client asked for "DESIGN. BUILD. DRIVE." to
   read as a single line. Montserrat sets it wider than Oxanium did — 1005px at
   88px against Oxanium's 895 — and the block it sits in is 912px, so the
   brand font pushed it onto two.

   The block is 912 because cee3068 carries 114px side margins inside a 1140px
   row. Dropping those to 40px gives the headline 1060px, which clears 1005
   with room to spare, and the sub-headline and button are centred inside the
   same block so neither moves.

   **The size is untouched at 88px**, which is the point: the client signed off
   on that scale, and shrinking the headline to fit a box is the wrong end of
   the problem to pull when the box is the thing with slack in it. Measured at
   1280, 1600 and 1920 the block is the same 912 before and 1060 after — this
   is a fixed-width block, not a fluid one, so one number fixes every width.

   Scoped to 1025px and up. Below that the row is narrower than the headline at
   any size and the wrap is the correct behaviour.
   ========================================================================== */
@media (min-width: 1025px) {
	body.home .elementor-element-cee3068 {
		width: min(1060px, 100%) !important;
	}
}

/* The block is sized, not margined. `margin-inline: 40px` was the first
   attempt and did nothing: cee3068 is authored at 80% of its 1140px row, which
   is the 912, and section 18's `margin-inline: auto` merely centres that width
   — the computed `0px 114px` is auto resolving to (1140-912)/2, not a margin
   anyone set. Widening has to be done on the width. */

/* The XPEL band's heading gets its column back for the same reason. At 44px
   Montserrat "Protection" alone is 275px, and the heading widget is authored
   narrow — 342px of a 728px container at 1600, 267 at 1280 — so a single word
   hung outside the box. Full width of the container it already sits in; the
   copy beside it is unaffected because it is a separate widget. */
body.home .elementor-element-f08e513 {
	width: 100% !important;
	max-width: 100% !important;
}

/* Fifty-second pass, mobile only — the "Certified Protection" heading (and
   its paragraph and button, which share its left edge) sat flush with the
   page's standard 20px inset and read as too far left. ff9ac0d is the
   column all three sit in — scoped to body.home because the same id is
   also the XPEL page's own heading column (see the note above), which
   wasn't part of this ask and keeps its original inset. padding-left on a
   border-box column shifts the content right without touching the column's
   own right edge, so the 20px margin on that side is unchanged; the
   heading's own wrap (3 lines either way) doesn't gain a new orphan word
   at the narrower width, so no font-size cut was needed. */
@media (max-width: 767px) {
	body.home .elementor-element-ff9ac0d {
		padding-left: 14px !important;
	}
}

/* Fifty-third pass, mobile only — "Brands Served… Ever Expanding" sat flush
   against both screen edges, not just left of the standard inset: the
   section (9e9ae84) is a genuinely full-bleed row, 0 padding all the way up
   from `.page-content`, because the logo marquee injected right after it
   needs to run edge to edge. The heading is the section's only real
   Elementor content, though, and it inherited that same 0 padding it never
   needed — on desktop the widget is centered (Elementor's own responsive
   text-align), which is why this only shows up here. `text-align: left`
   was left alone; the ask was to give it the same margin as every other
   heading, not to change how it's aligned. 20px each side, box-sizing
   forced to border-box since this widget doesn't carry it by default and
   padding would otherwise add to, not eat into, its width. */
@media (max-width: 767px) {
	body.home .elementor-element-46c0fde {
		padding-left: 20px !important;
		padding-right: 20px !important;
		box-sizing: border-box !important;
	}
}

/* ==========================================================================
   70. Authored blue fills repainted to APEX BLUE
   Section 1 collapsed this file's two blues into the guide's one, but a
   handful of fills are not this file's to begin with — they are #1876D2
   written into Elementor's own generated CSS when the site was built, so a
   find-and-replace across `_custom/` could never reach them. Found by scanning
   the rendered pages for `rgb(24, 118, 210)` rather than by reading source:

     a471638   the footer copyright bar          (every page)
     a5d8383   the blue contact card             (contact, service-contact,
                                                  contact-carrollton)
     9fa4c64   the contact page's second panel
     3716c3f   "About This Project" band         (13 vehicle pages)

   Anything on these already goes white through sections 49 and 53, and white
   on APEX BLUE is 4.89:1 against the old fill's 4.6:1 — so this is the one
   change in the palette swap that makes contrast better rather than worse.

   `[class][class]` for the containers, because an Elementor container carries
   its fill through its own generated `.elementor-element-NNNN` rule and a
   single-class selector loses to it even with `!important`. That is the trap
   from the twenty-seventh pass, hit for the third time.
   ========================================================================== */
body .elementor-element-a471638[class][class],
body .elementor-element-a5d8383[class][class],
body .elementor-element-9fa4c64[class][class],
body .elementor-element-3716c3f[class][class] {
	background-color: var(--apex-blue) !important;
}

/* Two more authored fills the id sweep above could not name, both found the
   same way — by scanning the rendered page for `rgb(24, 118, 210)`.

   `.apex-feature-icon` is the 44px disc behind each build-detail icon on the
   thirteen vehicle pages; its blue is written by the theme's own shortcode
   CSS, not by a widget id. And the nav's current-page item carries an
   authored blue of its own, which is why it survived section 26's pill. */
body .apex-feature-icon {
	background-color: var(--apex-blue) !important;
}

/* The current-page nav item is blue TEXT, not a blue fill. The first version
   of this rule set `background-color` — read off a scan that only reported
   "this element contains rgb(24,118,210)" without saying which property — and
   that put blue type on a blue box, i.e. an invisible tab. Colour, border and
   outline are the three that actually carry it. */
body .elementor-nav-menu .elementor-item.elementor-item-active,
body .elementor-nav-menu .current-menu-item > .elementor-item {
	color: var(--apex-blue) !important;
	border-color: var(--apex-blue) !important;
	outline-color: var(--apex-blue) !important;
}

/* ==========================================================================
   71. "Serving Dallas" — the pair swap treatments
   The client asked for the two things in this block to trade places: the
   buttons take the white style, and the ring the buttons were wearing moves
   out onto the two cards behind them.

   **The buttons.** a2758ab ("GET DIRECTIONS") and 357352c ("START YOUR BUILD
   TODAY") are retagged `apex-btn-solid` in `_tools/button-tags.json` — index
   6 and 8 of that page's ten — which is what hands them section 58's hover:
   black fill, white type, white outline. The tag is not a lie about a
   measurement: `button-rings.js` reads a button's *computed* fill, and with
   the rule below painting them white a fresh crawl now returns 'S' for both.

   Section 58 only writes the hover, because a solid button normally arrives
   with its own light fill from Elementor. These two did not, so the resting
   white is stated here — the same thing section 37 does for the band buttons.
   ========================================================================== */
body.home :is(
	.elementor-element-a2758ab,
	.elementor-element-357352c) .elementor-button {
	background-color: #ffffff !important;
	/* **`background-image: none` is the whole fix for the right-hand button.**
	   357352c is authored with
	   `background-image: linear-gradient(180deg, secondary 0%, accent 100%)` —
	   black to blue — and a `background-color` paints *behind* a background
	   image, so the white went on and the gradient stayed on top of it. Its
	   neighbour a2758ab has a flat authored colour and no image, which is why
	   one button looked right and the other came out blue.
	
	   Section 10 never had to think about this: `.apex-btn-ring` sets the
	   `background` *shorthand*, which resets image and colour together. A
	   longhand `background-color` does not. */
	background-image: none !important;
	color: #000000 !important;
	border: 1px solid #ffffff !important;
}

/* **The cards.** ffa7032 (the Dallas card) and 7b0c175 ("Build Your Dream
   Vehicle") take the buttons' rotating conic ring — same gradient, same 3s
   linear loop, so it reads as the same gesture at a larger size. Section 10
   owns `--apex-ring-angle` and the keyframes; this only borrows them.

   Built the way section 50 built the filter rail, and for the same reason:
   `::before` carries the ring and `::after` re-paints the card's own #0c0c0c
   over the middle, because a ring is a background with a hole in it and a
   pseudo-element is the only place to put the hole.

   **Sized with width/height, never `inset` alone.** These are `.e-con`s, and
   something in the Elementor stack gives `.e-con` pseudo-elements
   `width/height: 100%` — an `inset: -2px` box arrives over-constrained, left
   and top win, right and bottom are dropped, and the ring comes out shifted
   up and left instead of surrounding anything. That is the third rule in this
   file to be written this way.

   Radius 32 on the ring against the card's 30: the ring sits 2px outside on
   every edge, so its corner has to be 2px larger to stay concentric. */
body.home :is(
	.elementor-element-ffa7032,
	.elementor-element-7b0c175) {
	position: relative;
	isolation: isolate;
	z-index: 0;
	background-color: transparent !important;   /* redrawn on ::after */
	border-radius: 30px;
}

body.home :is(
	.elementor-element-ffa7032,
	.elementor-element-7b0c175)::before {
	content: "";
	position: absolute;
	top: -2px;
	left: -2px;
	width: calc(100% + 4px);
	height: calc(100% + 4px);
	border-radius: 32px;
	z-index: -2;
	background: conic-gradient(
		from var(--apex-ring-angle),
		var(--apex-blue) 0deg,
		var(--apex-blue) 55deg,
		#ffffff 90deg,
		#ffffff 145deg,
		var(--apex-blue) 360deg
	);
	animation: apexRingRotate 3s linear infinite;
	pointer-events: none;
}

body.home :is(
	.elementor-element-ffa7032,
	.elementor-element-7b0c175)::after {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	border-radius: inherit;
	z-index: -1;
	background: #0c0c0c;
	pointer-events: none;
}

/* Decoration stops when the visitor asks for less motion, as it does on the
   buttons — the ring stays, it just holds still. */
@media (prefers-reduced-motion: reduce) {
	body.home :is(
		.elementor-element-ffa7032,
		.elementor-element-7b0c175)::before {
		animation: none;
		background: var(--apex-blue);
	}
}

/* Fifty-first pass — the Dallas card's icon rows (ADDRESS/CALL US/EMAIL/
   HOURS) had the icon sitting visibly above its own label. Each row is a
   plain flex wrapper (icon, then the title+description column) with
   `align-items` left at its default, which stretches both children to the
   row's own height — the icon's SVG is a small 25px glyph, but the *box*
   holding it stretched to match its sibling's full height (68px for a
   one-line description, 146px for HOURS' four lines), and the glyph anchors
   to the TOP of that stretched box rather than sitting level with the title
   next to it. `align-items: flex-start` stops the stretch: both the icon
   and the text column now size to their own natural height and start at
   the row's own top edge, which is what puts the icon level with the title
   whatever the description underneath runs to — one line or Hours' four. */
body.home .elementor-element-ffa7032 .elementor-icon-box-wrapper {
	align-items: flex-start !important;
}

/* Fifty-second pass — flex-start lines up the two boxes' TOP edges, but the
   icon's own box (a tight 25px around the glyph) and the title's rendered
   line-box (28.6px — a heading's line-height carries space the glyph's box
   doesn't) are different heights, so even matched tops left the icon's
   center sitting ~10px above the title's. Present at every width (measured
   the same on desktop and mobile, not something the flex-start pass
   introduced only here) — margin-top nudges the icon down to match the
   title's own vertical center instead of just its top edge.

   Fifty-fifth pass — 8px closed most of it but still measured ~2px high;
   10px lands at 0.2px, the closest this gets without splitting a pixel. */
body.home .elementor-element-ffa7032 .elementor-icon-box-icon {
	margin-top: 10px !important;
}

/* ==========================================================================
   72. Vehicle Accessories — the stray bullet under Interior Lighting Upgrades
   The section carries a leftover `<ul class="brxe-block features__grid">`
   holding one `<li>` whose card is empty:

       <li><article><div class="card-features__container">&nbsp;</div></article>

   The list item still draws its marker, which is the lone bullet the client
   saw under that section's copy.

   **The `<li>` is not empty**, so neither `:empty` nor `:not(:has(li *))`
   reaches it — it holds two wrapper elements around a single non-breaking
   space, and that was the first attempt at this rule. What is actually empty
   is the innermost container, so the test is whether any card container has an
   element inside it. A populated grid has an icon and a title in there.

   This is the only such grid on the site, but the rule states the condition
   rather than naming the page, so a second one would also be caught. Hidden
   rather than deleted, like every other removal here — the markup is generated
   and `mirror.py` would bring it back.
   ========================================================================== */
ul.features__grid:not(:has(.card-features__container > *)) {
	display: none !important;
}

/* ==========================================================================
   73. Signature Finish — the stranded line under the Key Features band
   ae440b9, a one-line text widget ("If you want a protective finish that
   blends performance with polish… is your go-to") sitting after the band's
   button, so it reads as a caption to nothing. The client asked for it gone.
   The id appears on this page only — checked against all 52.
   ========================================================================== */
body.apex-service-page .elementor-element-ae440b9 {
	display: none !important;
}

/* ==========================================================================
   74. Vehicle pages — no vehicle title inside "About This Project"
   The blue card (3716c3f) opened with two stacked headings: the vehicle's
   make ("Jeep", "RAM", "Ford" — widget d3a0c6c) and then "About This
   Project". The page's own h1 already names the vehicle in full directly
   above, so the make reads as a stray label. The client asked for it gone.
   Scoped through the section id: d3a0c6c is a loop-template id and means
   this heading only inside this card.
   ========================================================================== */
.elementor-element-3716c3f .elementor-element-d3a0c6c {
	display: none !important;
}

/* ==========================================================================
   75. About — the two product links match the page's other blue text
   The Apex Armor™ / Apex Signature Finish™ links in the copy rendered in the
   theme's link blue (#115699) while every other blue on the page — the
   Design / Build / Drive lead-ins two lines up — is APEX BLUE. Same words,
   two blues, and the client read it as a mistake. #115699 appears nowhere
   else in the copy, so this is the whole set.
   ========================================================================== */
body.page-id-387 .page-content a[href*="apex-armor"],
body.page-id-387 .page-content a[href*="apex-signature-finish"] {
	color: #0072ce !important;
}

/* ==========================================================================
   76. Contact — "START YOUR BUILD TODAY" on one line
   The card's button (684c01b, Contact only) broke the label onto two lines:
   its authored padding left the text box 225px against the 249px the label
   sets at this size, while the same words in the site header sit on one line.
   nowrap and let the pill size to its label, like every other button.
   ========================================================================== */
.elementor-element-684c01b .elementor-button,
.elementor-element-684c01b .elementor-button-text {
	white-space: nowrap !important;
}

/* The injected About This Project photo on the Rubicon page (see
   RUBICON_ABOUT_PHOTO in customise.py). The clone keeps 302dd7c's widget id,
   but this page never carried that widget, so Elementor emitted no generated
   CSS for it here — and inside the card's flex column an unsized widget
   shrinks to its unloaded image, i.e. to 0x0. Same lesson as the injected
   .e-con gotcha: state the size yourself. */
.elementor-element-3716c3f .apex-rubicon-about {
	width: 100% !important;
}
.elementor-element-3716c3f .apex-rubicon-about img {
	width: 100% !important;
	height: auto !important;
	border-radius: 12px;
}

/* ==========================================================================
   77. The middle band of each run is STEEL GREY, not APEX BLUE
   Section 37 gives all three icon sections on Suspension and Accessories the
   blue fill, which made each page one continuous blue block. The client asked
   for the middle section to go grey.

   **STEEL GREY #4a4a4a is the brand's own** — Pantone 7540 C, already
   declared as --apex-steel in section 1 from the Brand Guide. It is used here
   rather than the near-blacks those sections previously carried (#1b1b1b /
   #1a1a1a / #202020), which are surface colours picked in the mirror, not
   brand values, and which read as "off" rather than as a deliberate band.

   Contrast on #4a4a4a: white type 8.9:1 (AA at any size, against 4.9:1 on the
   blue), and the black icon tiles 2.4:1 — softer than the 4.3:1 they get on
   blue, but the tile is a shape behind a white pictogram, not text, and the
   pictogram itself is 8.9:1. The white 5px rules that separate the bands are
   unchanged and still read against both fills.

   This is the only rule that needs to beat section 37's fill, and it is
   later in the file at equal specificity, so it wins on order. Both ids are
   page-unique — checked against all 52 pages.
   ========================================================================== */
body.apex-service-page :is(
	.elementor-element-a0ca73f,
	.elementor-element-1f22cac) {
	background-color: var(--apex-steel) !important;
}

/* Buttons on the steel-grey bands.

   The one button inside these two sections (Suspension's "Start Your
   Suspension Project Today", a0ca73f) is tagged `.apex-btn-ring` while the
   identical buttons in the blue bands above and below it are
   `.apex-btn-solid`. It only *looked* right because section 37 pins band
   buttons white with !important — but the tag drives the hover, so it was the
   one button in the run that did not take section 58's black fill, and its
   rotating conic ring is meant for dark/outline buttons and has no business
   on a light pill.

   Rather than retag it in button-tags.json (which would need the resting fill
   stated separately anyway — see the thirty-third pass), the band states both
   states for whatever is in it. White pill on #4a4a4a is 8.9:1 with its blue
   lettering at 4.9:1 on the white; the hover is section 58's black fill with
   white type, identical to its neighbours.
   ========================================================================== */
body.apex-service-page :is(
	.elementor-element-a0ca73f,
	.elementor-element-1f22cac) .elementor-button {
	background-color: #ffffff !important;
	background-image: none !important;
	color: var(--apex-blue) !important;
	border-color: #ffffff !important;
}

body.apex-service-page :is(
	.elementor-element-a0ca73f,
	.elementor-element-1f22cac) .elementor-button::before {
	display: none !important;
}

body.apex-service-page :is(
	.elementor-element-a0ca73f,
	.elementor-element-1f22cac) .elementor-button:hover,
body.apex-service-page :is(
	.elementor-element-a0ca73f,
	.elementor-element-1f22cac) .elementor-button:focus-visible {
	background-color: #000000 !important;
	background-image: none !important;
	color: #ffffff !important;
	border-color: #ffffff !important;
}

/* ==========================================================================
   78. PPF loses its two featured builds
   PPF was the only service page carrying photo-and-spec write-ups of
   individual vehicles: "Aston Martin | Full Protection Build" (a4581a8) and
   "2026 Ford F-350 Super Duty XL …" (a720ef5). Counted from the DOM, the
   other seven service pages run hero → icon band(s) → Recent Builds, with no
   featured build anywhere — which is what the Recent Builds gallery is for,
   and why the client asked for these two to come off.

   The Mustang GTD stays: it is 375fb30, the page's own title-and-photo
   header, the same slot every service page fills with its own vehicle. It is
   not a featured build.

   Hidden rather than deleted, per the project rule — mirror.py re-downloads
   this page, and a display:none section takes no part in the run above it, so
   the blue Windshield band (97041b8) closes straight up under Key Features
   (cc789bb). That is the "move the blue up" the client asked for, and it
   leaves PPF with the same shape as Wheels & Tires: two adjacent blue bands,
   then the gallery.

   Both ids are page-unique — checked against all 52 pages — but they are
   scoped anyway. An unscoped widget id is exactly what cost fourteen pages
   two FAQ entries each in the thirty-seventh pass.

   Nothing else needs saying about the rules: 97041b8 already carries the
   white 5px bottom border from section 37, so the last band before the
   gallery still closes with a line, which the client asked for three times.
   The a4581a8/a720ef5 arms in those lists are now inert but are left in
   place, so unhiding these sections restores their spacing intact.
   ========================================================================== */
body.apex-service-page.page-id-316 :is(
	.elementor-element-a4581a8,
	.elementor-element-a720ef5) {
	display: none !important;
}

/* ==========================================================================
   79. The wordmark is pinned to the box it always had
   The replacement logo is the same drawing at 1062x246 instead of 204x47
   (section: GLOBAL_IMAGE_SWAPS in customise.py). Both slots sized themselves
   from the file's natural width — the header widget renders `width: auto`
   inside a 358px container — so swapping in a sharper file made the header
   logo jump from 204x47 to 358x83 and the footer's from 620x141 to 1062x246.

   Nothing about the intended size changed, so both are stated here. The
   width/height attributes on the elements are correct and still reserve the
   right box before the stylesheet lands; these rules are what hold it
   afterwards.

   Selected on the FILENAME, not on a widget id: the src carries a different
   `../` prefix on every directory depth, and `[src$=]` ignores all of it.
   `height: auto` is required beside the width — the same trap as sections 8,
   55 and 62, where a generated pixel height would otherwise squash the
   aspect. max-width keeps the footer's 620px from overflowing a phone.
   ========================================================================== */
/* border-radius here fixes the same bug as .apex-brand-logo above: the
   wordmark is a widget image too, so section 4 was rounding its corners at
   12px — visible as a rounded box around the "APEX" text. Both selectors
   already carry an attribute selector on top of the element ones, (0,1,2),
   which outranks section 4's (0,1,1) without needing a [class] bump. */
header img[src$="logo-apex-wordmark.png"] {
	width: 204px !important;
	height: auto !important;
	max-width: 100% !important;
	border-radius: 0 !important;
}

footer img[src$="logo-apex-wordmark.png"] {
	width: 620px !important;
	height: auto !important;
	max-width: 100% !important;
	border-radius: 0 !important;
}

/* ==========================================================================
   80. The header logo sits on the header's centre line
   The client asked for the header to be rebuilt and the quote button centred
   on the right. Measured first: the button is already centred, horizontally
   (115px of column either side of it) and vertically (its midpoint and the
   header's are both y=46). Nothing to do there, and nothing to rebuild.

   What IS wrong is the LOGO. Its column `dcf31a2` is a flex **column** whose
   authored `justify-content: flex-end` pins the logo to the bottom of the
   row — main axis, not cross axis, so `align-items: center` beside it changes
   nothing. The logo ends up 30px below the header's top edge and 15px above
   its bottom: 7.5px below the centre line the nav and the button both sit on.
   That is the "logo issue", and it is one declaration rather than a rebuild.

   Scoped to the header because Elementor reuses container ids across
   templates — the CLAUDE.md warning about never selecting a widget id without
   a scope.
   ========================================================================== */
header .elementor-element-dcf31a2 {
	justify-content: center !important;
}

/* Forty-ninth pass — mobile only. The header's "Get Your Quote" button
   (d9172ab, wrapping widget 54c2f19) sits under the logo row and reads as
   its own full-width banner on a narrow screen instead of a header action —
   the client's own word for it. It is part of the shared header template
   (elementor-location-header), so this one rule reaches every page without
   page scoping, the same way the logo-radius fixes did. The quote form is
   still reachable on mobile without it — the hero's own "START YOUR BUILD
   TODAY" is right there in the same first screen — and the header's
   hamburger menu is untouched. */
@media (max-width: 767px) {
	header .elementor-element-d9172ab {
		display: none !important;
	}
}

/* Forty-ninth pass — mobile only. The header follows the scroll now, at the
   client's request.

   `position: sticky` was the first attempt and does not work here: `body`
   and `html` both carry `overflow-x: hidden` (unrelated to this file — it is
   what keeps every full-bleed marquee and breakout section on this site from
   opening a horizontal scrollbar, and is not safe to remove for this). A
   sticky element's containing block is its nearest ancestor with a
   non-`visible` overflow on EITHER axis, and `overflow-x: hidden` on `body`
   qualifies even though `overflow-y` is `auto` — the header stuck to that
   containing block instead of the viewport and scrolled away with the page.
   Measured, not guessed: `position` read back as `sticky` and `top: 0px`,
   but the header's own `getBoundingClientRect().top` was -1000px after a
   1000px scroll — it was never sticking at all.

   `position: fixed` is unaffected by an ancestor's overflow (short of a
   `transform`/`filter` on one, and neither `body` nor `html` here has
   either), so it is the actual fix. Fixed removes the header from document
   flow entirely, which sticky did not — `main`'s own `padding-top: 68px`
   below is what puts the page back where it was, matching the header's
   measured height, so the hero starts in the exact same place on first
   paint and only the SCROLLING behaviour changes.

   The header's own background is transparent by design on the hero, which
   is fine sitting once over a photograph but not fixed permanently over
   whatever scrolls underneath it — solid black, so the logo and the menu
   button stay legible against every section, not just the first one.

   9997 is the dropdown nav's own z-index (unrelated CSS, in the page's own
   theme, not this stylesheet) — the header sits under that on purpose, so
   the mobile menu panel still opens on top of the header that triggered it,
   not behind it. */
@media (max-width: 767px) {
	header.elementor-location-header {
		position: fixed !important;
		top: 0;
		left: 0;
		width: 100%;
		z-index: 500;
		background-color: #000000 !important;
	}

	body main {
		padding-top: 68px;
	}
}

/* ==========================================================================
   81. Ceramic Coating (the XPEL-vs-ceramic blog post) — blue-box titles,
   bigger subject headings, the comparison grid framed in blue
   Scoped to body.page-id-366 — CLAUDE.md's own note for "366 is the blog
   post" the nav's Ceramic Coating tab points at. Three pieces of feedback on
   this one page.

   BLUE-BOX TITLES WERE BLUE ON BLUE. This page carries nine WordPress-
   authored callout boxes ("KEY BENEFITS OF XPEL PPF FOR TRUCKS:", "COMMON
   PAINT HAZARDS INCLUDE:", "MOST PROTECTED AREAS INCLUDE:" and six more) at
   #1876D2 — the client likes these and keeps them. Their body copy is
   already white; their h2/h3 TITLES were not — section 14 paints every
   heading site-wide in --apex-blue (#0072ce), which on this fill is close
   enough to the box colour to read as blank. White, matching the copy
   underneath it.
   ========================================================================== */
body.page-id-366 :is(
	.elementor-element-82c7f95,
	.elementor-element-1ae6d8c,
	.elementor-element-fafbb21,
	.elementor-element-5be0f36,
	.elementor-element-7a0a298,
	.elementor-element-d61b81e,
	.elementor-element-7174987,
	.elementor-element-2762adf,
	.elementor-element-5de8c18) :is(h2, h3) {
	color: #ffffff !important;
}

/* "Common Combined Protection Setup" (efc591b, widget a928969) is not one of
   the nine blue boxes above — it sits on the page's plain black, where every
   h2 is section 14's --apex-blue by default. The client asked for this one
   heading white; the others on black stay blue, which is why this is a
   single-id rule rather than folded into the :is() list above.

   FORTY-SEVENTH PASS FIX: this was scoped to .elementor-element-efc591b
   (the whole section) rather than a928969 (the heading's own widget id), and
   efc591b's subtree also holds "XPEL PPF on High-Impact Areas" (84a4ea5) and
   "Ceramic Coating Across the Entire Vehicle" (26b4f79) several levels down
   — the descendant selector painted both of those white too, which the
   client had never asked for and then explicitly asked to undo. Scoped to
   the heading widget's own id now, which cannot reach anything else. */
body.page-id-366 .elementor-element-a928969 .elementor-heading-title {
	color: #ffffff !important;
}

/* SUBJECT HEADINGS SLIGHTLY BIGGER. Every h2 on this page — blue boxes and
   plain prose sections alike — shares one 44px rule; 48px is "slightly
   bigger", scoped to this page so it does not reach the 44px used
   identically on the other 43 pages that rule also touches. */
body.page-id-366 h2.elementor-heading-title {
	font-size: 48px !important;
}

/* THE COMPARISON GRID (forty-seventh pass): the blue frame around it is
   gone — the client's first look at the redesign asked for the outline off
   — and the rounding moves onto the white grid itself instead, at 20px, the
   same radius as the Key Features icon cards on all eight service pages and
   this page's own callout boxes above it: the one figure that reads as "the
   site's roundness" rather than a page-local choice. The section keeps the
   page's plain black; only the table is a shaped object on it now. */
body.page-id-366 .elementor-element-01f785c {
	background-color: transparent;
	border-radius: 0;
	padding: 0;
}

/* Defensive: eleven rows of three columns has nowhere to shrink to under
   roughly 480px, and a bare <table> has no wrap behaviour of its own. */
body.page-id-366 .elementor-element-01f785c .elementor-widget-text-editor {
	overflow-x: auto;
}

/* The grid itself is white — text recoloured to match: white on the header
   row (still apex-blue — that fill is the header's own styling, not "the
   outline", and stays) and dark charcoal in the body, where it had been
   white-on-transparent (correct on the old black section, invisible on the
   new white one). `border-collapse: separate` + zero spacing, not
   `collapse`, is what lets `border-radius` + `overflow: hidden` clip a
   table's corners reliably — collapsed cell borders paint outside a
   `collapse` table's own radius in some engines. */
body.page-id-366 table.comparison-table {
	width: 100%;
	min-width: 480px;
	border-collapse: separate;
	border-spacing: 0;
	border-radius: 20px;
	overflow: hidden;
	background-color: #ffffff;
}

body.page-id-366 table.comparison-table th {
	background-color: var(--apex-blue);
	color: #ffffff !important;
	font-family: var(--apex-heading), sans-serif;
	font-weight: 700;
	text-transform: uppercase;
	font-size: 15px;
	letter-spacing: 0.02em;
	padding: 16px 18px;
	text-align: left;
}

body.page-id-366 table.comparison-table td {
	color: #0c0c0c !important;
	padding: 14px 18px;
	border-bottom: 1px solid rgba(12, 12, 12, 0.12);
	font-size: 15px;
}

body.page-id-366 table.comparison-table td.feature-col {
	font-weight: 700;
}

body.page-id-366 table.comparison-table tr:last-child td {
	border-bottom: none;
}

/* Fifty-sixth pass, mobile only — the table read fine but needed a scroll
   in both directions to see all of it: 480px min-width against a 390px
   viewport (horizontal), and eleven rows at the desktop padding/font came
   to 805px tall against an 844px screen with a heading already above it
   (vertical). Three changes together, not one:

   `min-width: 480px` is what forced the horizontal scroll in the first
   place — it existed because "eleven rows of three columns has nowhere to
   shrink to under roughly 480px" (see the note above `.elementor-widget-
   text-editor`), which is true at the desktop font size but stops being
   true once the font and padding come down with it. `table-layout: fixed`
   plus explicit percentage widths keep the feature column from grabbing
   more than its share now that cell content is what's driving width.

   The font/padding cut (15px/16px·18px → 10.5px/9.5px, 6px 5px) is the
   real lever on height — most rows were two lines at the old size (a
   170px-wide first column wrapping "Rock Chip Protection" and friends);
   at the smaller size and a similar column share they still wrap, but a
   wrapped line is ~12px instead of ~20px, so the cost of wrapping is much
   smaller per row.

   The header's own text was shortened in the HTML (PAGE_TEXT_REPLACEMENTS,
   customise.py) from "XPEL Paint Protection Film" to "XPEL PPF" — matching
   the page's own title, and its opening section still spells the term out
   in full — which was worth doing on both counts here: one less wrapped
   header line, and more of the 34% column width left for the actual
   answers underneath it.

   Measured result: 805px → 378px, and 480px → 350px (no horizontal
   scrollbar). Desktop is untouched — none of this is inside the media
   query, and the header's shortened text reads fine at any width. */
@media (max-width: 767px) {
	body.page-id-366 table.comparison-table {
		min-width: 0;
		width: 100%;
		table-layout: fixed;
	}

	body.page-id-366 table.comparison-table th:first-child,
	body.page-id-366 table.comparison-table td.feature-col {
		width: 32%;
	}

	body.page-id-366 table.comparison-table th:not(:first-child),
	body.page-id-366 table.comparison-table td:not(.feature-col) {
		width: 34%;
	}

	body.page-id-366 table.comparison-table th {
		font-size: 9.5px;
		padding: 6px 5px;
		letter-spacing: 0.01em;
		line-height: 1.15;
	}

	body.page-id-366 table.comparison-table td {
		font-size: 10.5px;
		padding: 6px 5px;
		line-height: 1.2;
	}
}


/* ==========================================================================
   82. The header logo assembles itself on arrival
   The client's own logo animation, chosen from two mock-ups. Only the mark in
   the top-left moves, and (see logo-intro.js) only once a visit; the other
   mock-up put it fullscreen over a black screen and was not the one picked.

   The static <img> is underneath the whole time and is what the video fades
   back to, so the header never changes size and the ordinary logo is what
   shows whenever the video cannot play. `screen` is what keeps that <img>
   from showing THROUGH the video during the early frames — the video's
   background is pure #000, so screen drops it out against the black header,
   and without hiding the <img> the assembled mark would be visible behind
   every frame of it assembling.
   ========================================================================== */
.apex-logo-anim {
	position: relative;
	display: inline-block;
	line-height: 0;
}

.apex-logo-anim > img {
	display: block;
	transition: opacity 0.35s ease;
}

.apex-logo-anim.is-playing > img {
	opacity: 0;
}

/* THE GEOMETRY IS MEASURED, NOT CHOSEN. The logo occupies a known box inside
   the video frame — left 3.87%, top 22.04%, 92.29% wide, 55.93% tall, read off
   the animation's own final frame at full resolution — and its aspect (4.339)
   matches logo-apex-wordmark.png (4.336) to within half a percent. Inverting
   that box is where these four numbers come from: the video is sized and
   offset so ITS logo lands exactly on the <img>'s box, which is what makes the
   hand-off at the end invisible. Verified at 0.01px on all four edges.

   All four `!important`, and that is not belt-and-braces. The video has to
   OVERFLOW its 204x47 slot — the mark is 92% x 56% of the frame, so the frame
   around it is necessarily bigger than the box the mark lands in, 221x84 here
   — and something in the header's stack pins a media element to its container
   instead: `left`, `top` and `height` from this rule all applied while `width`
   computed 204px against the 108.35% written beside them. The symptom was
   subtle enough to ship, a mark 7.7% small ending in a small pop rather than
   nothing at all. Section 79 pins the <img> itself; this pins the video to
   match it. */
.apex-logo-anim-vid {
	position: absolute;
	left: -4.19%;
	top: -39.41%;
	width: 108.35% !important;
	height: 178.79% !important;
	max-width: none !important;
	max-height: none !important;
	pointer-events: none;
	mix-blend-mode: screen;
	opacity: 0;
	transition: opacity 0.40s ease;
}

.apex-logo-anim-vid.is-on {
	opacity: 1;
}

/* logo-intro.js removes the element outright under reduced motion; this is
   the belt to that braces, so a stylesheet that arrives before the script
   cannot show a frame of it either. */
@media (prefers-reduced-motion: reduce) {
	.apex-logo-anim-vid {
		display: none !important;
	}

	.apex-logo-anim.is-playing > img {
		opacity: 1;
	}
}
