/*
Theme Name: Agility Web
Theme URI: https://agilityweb.io
Author: Agility Web LLC
Author URI: https://agilityweb.io
Description: Custom ACF-driven theme for Agility Web Consulting. Listen. Adjust. Deliver.
Version: 1.0.0
Requires at least: 6.4
Requires PHP: 8.0
Text Domain: agility-web
*/

/* -------------------- Variables -------------------- */
:root {
	/* The 12-color brand palette: Primary/Secondary/Tertiary/Grey, each a
	   dark base + generated Light/Lightest tint (white text is assumed to
	   read fine against every *base* color - see agility_brand_color_tints()
	   in inc/color-utils.php). These are static fallbacks only - the real,
	   live values (recomputed from whatever's actually set in Theme
	   Settings > Colors) are injected as a :root override appended after
	   this stylesheet, see agility_theme_settings_style_overrides() in
	   inc/enqueue.php. */
	--color-brandPrimary: #16263b;
	--color-brandPrimaryLight: #7f8893;
	--color-brandPrimaryLightest: #d5d8dc;
	--color-brandSecondary: #2c8c99;
	--color-brandSecondaryLight: #8bc0c7;
	--color-brandSecondaryLightest: #d9eaed;
	--color-brandTertiary: #e8a33d;
	--color-brandTertiaryLight: #f2cc94;
	--color-brandTertiaryLightest: #fbeedc;
	--color-brandGrey: #4a4a4a;
	--color-brandGreyLight: #9b9b9b;
	--color-brandGreyLightest: #dedede;
	--color-white: #ffffff;
	--color-bg: #f6f8fa;
	--color-card-bg: #dedede;
	--color-icon: #e8a33d;
	--color-border: #e1e6eb;
	--color-text: #2c3540;
	--color-text-muted: #5b6b7a;
	--color-breadcrumb: #9aa4ae;
	--color-hero-heading: #ffffff;
	--color-heading-safe: #16263b;
	--color-header-bg: #ffffff;
	--color-header-text: #16263b;
	--color-footer-bg: #16263b;
	--color-footer-text: #ffffff;
	--color-cta-banner-bg: #2c8c99;
	--color-cta-banner-text: #ffffff;
	--color-brandTertiary-text: #16263b;
	--color-brandTertiary-hover-text: #16263b;
	/* Theme Settings > Accents - static fallbacks only, same as the brand
	   palette above; the real, live values are injected as a :root
	   override, see agility_theme_settings_style_overrides() in
	   inc/enqueue.php. */
	--color-interactive-accent: #16263b;
	--color-stats-bg: #16263b;
	--color-stats-text: #ffffff;
	--color-stats-accent: #d9eaed;
	--color-stats-accent-light: #eaf3f5;
	--color-stats-accent-dark: #b8c7c9;
	--color-stats-icon: #e8a33d;
	--color-stats-value: #2c8c99;
	--color-stats-subtext: #2c8c99;
	--color-testimonials-bg: #dedede;
	--color-project-client-label: #e8a33d;
	--color-case-study-marker: #e8a33d;
	--color-case-study-marker-light: #fbe9d1;
	--color-case-study-marker-darkest: #e8a33d;
	--color-case-study-marker-family-light: #f2cc94;
	--color-faq-answer: #e8a33d;
	--color-social-icon: #d9eaed;
	--color-process-number: #16263b;
	--logo-max-width: 15.625rem;
	--fallback-logo-border-width: 10;
	--header-border-width: 0px;
	--header-border-color: transparent;
	--corner-radius: 28px;

	--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

	/* Single shared value, not two close-but-different ones (was 71.25rem/
	   68.75rem) - every section's content edges (header, footer, and
	   every .container-wrapped section alike) should align with one
	   another exactly, not just approximately. Both variables are kept,
	   rather than collapsing to one, since they still serve distinct
	   roles: --container-width is what .container itself uses (nearly
	   everything on the site); --content-width is for the handful of
	   text-heavy blocks that opt into it on top of that (.section-heading's
	   intro copy, .hero__inner, .faq) - genuinely narrower than this would
	   read as long, hard-to-scan lines for those specifically, this just
	   stops it from ALSO being narrower than the rest of the page's own
	   edges.
	   1600px (100rem) here is only the DEFAULT for a brand-new site/before
	   Theme Settings has ever been saved - Container Width (Theme Settings
	   > Accents) overrides both to the same admin-picked value from a
	   fixed set of choices (1200/1400/1600/1800/1900px, see
	   agility_theme_settings_style_overrides(), inc/enqueue.php) once it
	   has been. */
	--container-width: 100rem;
	--content-width: 100rem;
	--space-1: 0.5rem;
	--space-2: 1rem;
	--space-3: 1.5rem;
	--space-4: 2.5rem;
	--space-5: 4rem;
	--radius: 0.5rem;
	--section-padding-block: 2.8125rem;
}

/* -------------------- Reset -------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
	margin: 0;
	font-family: var(--font-body);
	color: var(--color-text);
	background: var(--color-white);
	line-height: 1.6;
	font-size: 1.0625rem;
}
img { max-width: 100%; height: auto; display: block; }
a { color: var(--color-heading-safe); text-decoration: none; }
a:hover { opacity: 0.8; }
ul { margin: 0; padding: 0; list-style: none; }
h1, h2, h3, h4, h5, h6 { color: #000000; line-height: 1.25; margin: 0 0 var(--space-2); }
h1 { font-size: 3.2rem; }
h2 { font-size: 2.4rem; }
h3 { font-size: 1.4rem; }
h4 { font-size: 1.4rem; }
p { margin: 0 0 var(--space-2); }
button { font-family: inherit; }

/* Standard visually-hidden-but-still-announced utility (same technique/
   values WordPress core itself uses under this exact class name) - content
   present for screen readers/heading structure without taking up visible
   space. First used by the Case Study tabs' per-panel <h2> (see
   single-project.php and .case-study-tabs below) - each panel keeps its
   own real heading for screen-reader navigation even though its visible
   label already lives on the tab itself. */
.screen-reader-text {
	position: absolute !important;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* -------------------- Layout -------------------- */
.container {
	width: 100%;
	max-width: var(--container-width);
	margin-inline: auto;
	padding-inline: var(--space-3);
}
.section { padding-block: var(--section-padding-block); }
.section--muted { background: var(--color-bg); }
/* "What Clients Say" specifically (see template-parts/flexible/testimonials.php) -
   uses the same configurable Slider Background Color (Theme Settings >
   Accents, defaults to Primary) as the Stats/"Trusted By" band and the
   single Work page's Gallery slider - see --color-stats-bg below. Text
   color isn't overridden here - unlike Stats below, this stays light
   enough by design that the default dark text/heading colors keep
   reading fine against it with no separate contrast handling needed. */
.section--brandGrey { background: var(--color-testimonials-bg); }
/* Stats/"Trusted By" band (template-parts/flexible/stats.php) - background
   is Theme Settings > Accents > Slider Background Color (defaults to
   Primary, matching the original hardcoded look); text color is
   auto-picked for real contrast against whatever that resolves to
   (agility_best_text_color(), inc/enqueue.php), same treatment as the
   Header/Footer/CTA Banner above - a fixed white assumption stopped
   being safe once the background became configurable. */
.section--brandPrimary { background: var(--color-stats-bg); color: var(--color-stats-text); }
/* Re-uses --color-cta-banner-bg/-text (inc/enqueue.php) rather than raw
   --color-brandSecondary directly - those are already computed to stay
   readable against the Secondary brand color no matter what it's set to,
   and the CTA banner is the other section already using that same
   background, so this keeps the two in sync automatically. */
.section--brandSecondary { background: var(--color-cta-banner-bg); color: var(--color-cta-banner-text); }
/* Equal top/bottom whitespace: the section's own padding is symmetric, so
   the first/last content element must never add its own leading/trailing
   margin on top of that, or the two sides drift out of sync. */
.section > .container > *:first-child { margin-top: 0; }
.section > .container > *:last-child { margin-bottom: 0; }
.section--brandPrimary h1, .section--brandPrimary h2, .section--brandPrimary h3 { color: var(--color-stats-text); }
.section--brandSecondary h1, .section--brandSecondary h2, .section--brandSecondary h3 { color: var(--color-cta-banner-text); }
.section-heading { text-align: center; max-width: var(--content-width); margin: 0 auto; display: flow-root; }
.section-heading + .grid { margin-top: var(--space-3); }
.section-heading p { color: var(--color-text-muted); }

/* Insights archive's "Filter by" Category dropdown (archive.php - see
   agility_render_insight_category_filter() in inc/insights.php), sitting
   between .section-heading and the card grid - carries its own spacing
   rather than relying on .section-heading + .grid above, since that
   sibling selector no longer matches once this form sits between them. */
.insight-filter { display: flex; align-items: center; justify-content: center; gap: var(--space-1); margin: var(--space-3) 0 var(--space-4); text-align: center; }
.insight-filter label { font-weight: 600; }
.insight-filter select {
	font: inherit;
	padding: 0.4em 0.8em;
	border: 0.0625rem solid var(--color-border);
	border-radius: var(--radius);
	background: var(--color-bg);
	color: var(--color-heading-safe);
}

.grid { display: grid; gap: var(--space-3); }
.grid--2 { grid-template-columns: repeat(2, 1fr); }
.grid--3 { grid-template-columns: repeat(3, 1fr); }
/* Contact page: extra breathing room between the info column and the
   form column specifically, on top of the base .grid gap. */
.contact-grid { column-gap: 6rem; }
/* About page: same idea, between the bio text and the headshot/highlight
   column - 60px rather than the base .grid gap's 24px. */
.about-intro { column-gap: 3.75rem; }
/* Single Technology/Service page: a primary (left) column paired with a
   sidebar (right) column - FAQs take the primary column whenever present
   (the Pie Chart takes it instead when there are no FAQs); the Pie Chart
   is first priority for the sidebar, Related Services/Work second -
   see single-technology.php/single-service.php for the full priority
   logic (which of the two columns each piece of content lands in, and
   when Related Services/Work is bumped out of this layout entirely into
   its own full-width section instead). 60/40 rather than the base
   .grid--2's even 1fr/1fr split - a list of FAQ questions (or, when
   there are none, a centered chart) reads fine with the extra room;
   whatever's sharing the row with it doesn't need as much. align-items:
   start rather than the base .grid's stretch - the two sides are
   different heights, and stretching the shorter one to match would just
   leave awkward empty space in it. */
.tech-related-grid, .service-faq-grid { grid-template-columns: 60fr 40fr; align-items: start; }
/* Sidebar column - its own left padding (on top of the grid's own
   column-gap) to visually separate it from the column beside it, and its
   Related Services/Work heading centered above the card stack rather
   than left-aligned. Holds exactly one thing at a time (the Pie Chart, or
   Related Services/Work's own heading + list) - never both, so there's
   no need for a shared margin between them the way earlier versions of
   this layout needed. */
.tech-related-grid__aside-col, .service-faq-grid__aside-col { padding-left: 45px; }
.tech-related-grid__aside-col h2, .service-faq-grid__aside-col h2 { text-align: center; }
/* Related Services/Work, 2 per row here in the sidebar rather than the
   3-across grid it gets in its own full-width section - still reads as a
   compact grid rather than one long single-file list, just narrower to
   fit this column. */
.related-cards-stack { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-2); }
/* Every Related Services/Work card on a single Technology/Service page -
   both here in the sidebar (.related-cards-stack above) and in its own
   full-width section elsewhere on the same two templates
   (.related-cards-grid, added alongside .grid--3 in single-technology.php/
   single-service.php) - shown at roughly half the size a normal
   .card--service/.card--work gets everywhere else on the site (the
   Services/Work archives, Home page teasers, etc., all untouched). These
   are secondary, "you might also be interested in" links here, not the
   main content of the page, so they don't need full card-sized real
   estate to do their job. */
/* Shortened to a 16:9 box (by request - matches how these read before
   the padding/icon/font reductions further down made them noticeably
   taller than wide) rather than letting height follow content - flex +
   overflow:hidden so the icon/heading/caption stack centers inside that
   fixed shape instead of stretching it. height:auto overrides the base
   .card's own height:100% (meant for stretching to a taller grid-row
   sibling elsewhere on the site); min-height:0 overrides .card--work's
   own 17.5rem min-height the same way - both would otherwise force this
   box taller than the ratio calls for, which (since the width was still
   only auto/stretched) made the browser solve the ratio by widening the
   card instead of shrinking it, well past its actual grid/flex track and
   overlapping its neighbors. */
.related-cards-stack .card,
.related-cards-grid .card {
	aspect-ratio: 16 / 9;
	height: auto;
	min-height: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	overflow: hidden;
	padding: 0.75rem;
}
.related-cards-stack .card__icon,
.related-cards-grid .card__icon {
	width: 1.5rem;
	height: 1.5rem;
	margin-bottom: var(--space-1);
}
/* Text sizing here is deliberately NOT scaled down anywhere near as far
   as the padding/icon above - literally halving font-size (0.7rem/
   0.6rem, an earlier version of this rule) made both unreadable. The
   icon + padding reduction alone already reads as "smaller card"; these
   two just stop short of illegible while still sitting below the normal
   1.4rem/1.0625rem a full .card--service/.card--work gets. */
.related-cards-stack .card h3,
.related-cards-grid .card h3 {
	font-size: 1rem;
}
.related-cards-stack .card p,
.related-cards-grid .card p {
	font-size: 0.85rem;
	/* Clamped rather than left to wrap freely - the fixed 16:9 height
	   above doesn't grow for a longer caption, so this keeps overflow
	   text from spilling past the card's own edge instead of just
	   getting cut off mid-line by overflow:hidden alone. */
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
/* Related Services/Work's own full-width section (as opposed to
   .related-cards-stack in the sidebar, 2 per row) - 6 per row by
   request, rather than the 3 every other .grid--3 on the site uses;
   these compact cards have the room for it. */
.related-cards-grid { grid-template-columns: repeat(6, 1fr); }
@media (max-width: 64.0625rem) /* Bootstrap lg: 1025px - matches .grid--3's own column-count breakpoints in spirit, chosen so 6 compact cards don't get uncomfortably narrow before wrapping */ {
	.related-cards-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 48rem) /* Bootstrap md: 768px */ {
	.grid--2, .grid--3 { grid-template-columns: 1fr; }
	.related-cards-stack, .related-cards-grid { grid-template-columns: repeat(2, 1fr); }
	.tech-related-grid__aside-col, .service-faq-grid__aside-col { padding-left: 0; }
}

/* Fewer than 3 cards: center them with even spacing instead of leaving an empty grid column */
.grid--3:not(:has(> :nth-child(3))) {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-evenly;
}
.grid--3:not(:has(> :nth-child(3))) > * {
	flex: 0 1 21.25rem;
}
/* .related-cards-grid's own version of the same idea, further up - fewer
   than 6 (its own full row) rather than fewer than 3, and its own
   smaller flex-basis matching its compact card size. Comes after the
   .grid--3 fallback above so it wins the cascade on the 1-2 card overlap
   between both selectors (same specificity; source order decides), not
   the 21.25rem basis meant for full-size cards. */
.related-cards-grid:not(:has(> :nth-child(6))) {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-evenly;
}
.related-cards-grid:not(:has(> :nth-child(6))) > * {
	flex: 0 1 11.25rem;
}

/* -------------------- Buttons -------------------- */
.btn {
	display: inline-flex;
	align-items: center;
	gap: 0.5em;
	padding: 0.75em 1.5em;
	/* Theme Settings > Accents > Corner Roundness (--corner-radius), not
	   the site's usual fixed var(--radius) - every CTA button (.btn-
	   -secondary/--tertiary/--outline/--view-all included, none of which
	   redeclare their own border-radius) shares this one value, same as
	   Service/Work cards, Case Study tabs, skill pills, team photos, and
	   slider arrows already do (see agility_theme_settings_style_overrides()
	   in inc/enqueue.php for the full list). */
	border-radius: var(--corner-radius);
	font-weight: 600;
	background: var(--color-brandPrimary);
	color: var(--color-white);
	border: 0.125rem solid var(--color-brandPrimary);
}
/* Hover softens to the color's own Lightest tint rather than flipping all
   the way to solid white - reads as "this same button, brightened" instead
   of a full invert. The border stays the full-strength base color as a
   crisp, readable edge. */
.btn:hover { background: var(--color-brandPrimaryLightest); color: var(--color-brandPrimary); border-color: var(--color-brandPrimary); }

/* Secondary's own Light tint, not the raw base color - the base is
   assumed dark (Theme Settings > Colors), and this button's text is a
   fixed black rather than auto-contrast, so a dark base here would read
   as black-on-black. Hover steps one tier lighter still (Lightest), same
   progression as .btn/.btn--tertiary above. */
.btn--secondary { background: var(--color-brandSecondaryLight); color: #000000; border: 0.125rem solid transparent; }
.btn--secondary:hover { background: var(--color-brandSecondaryLightest); color: #000000; border-color: var(--color-brandSecondary); }

/* "View All Services"/"View All Work" (Home page teasers,
   template-parts/flexible/services_teaser.php and work_teaser.php) -
   still carries .btn--secondary for the shared padding/border-radius/
   font-weight, but overrides its color progression rather than changing
   .btn--secondary itself, which is also used elsewhere (the "All
   Services"/"All Work" back-links on the archive pages, untouched here).
   Centered on the raw Secondary base color at rest - white text, same
   "assumed dark, white reads fine on it" convention .btn--tertiary above
   already uses for its own base color, rather than a live contrast check
   just for this one button. Hover softens to Secondary's own Lightest
   tint (same "hover softens" direction .btn/.btn--tertiary use), so text
   switches to --color-heading-safe there - the same safe-dark-text
   variable .card--add:hover already reaches for on that same pale a
   background. Active nudges one step darker than the resting base color
   via a live color-mix (no tier past "base" exists to step to otherwise).
   Border stays genuinely transparent at rest, and focus-visible gets its
   own explicit outline - neither .btn nor .btn--secondary style that
   state at all today, left to the browser default. */
.btn--view-all {
	background: var(--color-brandSecondary);
	color: var(--color-white);
	border: 0.125rem solid transparent;
}
.btn--view-all:hover {
	background: var(--color-brandSecondaryLightest);
	color: var(--color-heading-safe);
	border-color: var(--color-brandSecondary);
}
.btn--view-all:focus-visible {
	outline: 0.125rem solid var(--color-brandSecondary);
	outline-offset: 0.125rem;
}
.btn--view-all:active {
	background: color-mix(in srgb, var(--color-brandSecondary) 85%, black);
	color: var(--color-white);
	border-color: var(--color-brandSecondary);
}

.btn--tertiary { background: var(--color-brandTertiary); border: 0.125rem solid var(--color-brandTertiary); color: var(--color-white); }
/* Hover softens to Tertiary's own Lightest tint (not solid white), so
   white text would still nearly vanish - keeps its own auto-contrast
   color (already distinct from the resting state's forced white) rather
   than reusing it. */
.btn--tertiary:hover { background: var(--color-brandTertiaryLightest); border-color: var(--color-brandTertiary); color: var(--color-brandTertiary-hover-text); }
.btn--outline { background: transparent; color: var(--color-white); border-color: var(--color-white); }
.btn--outline:hover { background: var(--color-white); color: var(--color-brandPrimary); }

/* -------------------- Site Sub Navigation -------------------- */
/* Optional slim bar above the main .site-header (template-parts/header/
   site-subnav.php) - only rendered at all once an admin assigns a menu
   to the "Header Sub Navigation" location (inc/setup.php). Deliberately
   plain/neutral (not tied to Header Background Color/Image, --color-bg
   instead) rather than trying to coordinate with whatever the header
   below it is configured to look like - a thin border-bottom is enough
   to visually separate the two without needing to match them. Sits in
   normal document flow (not sticky, unlike .site-header itself) - a
   utility strip like this scrolling away is expected. */
.site-subnav { background: var(--color-bg); border-bottom: 0.0625rem solid var(--color-border); font-size: 0.75rem; }
.site-subnav__inner { display: flex; justify-content: flex-end; padding-block: 0.5rem; }
/* Centered to match the two "Centered" Style choices (Theme Settings >
   Header) instead of the default right-alignment every "standard*"
   choice keeps - see agility_header_is_centered_style(), inc/setup.php,
   for why. */
.site-subnav--centered .site-subnav__inner { justify-content: center; }
.site-subnav__nav ul { display: flex; flex-wrap: wrap; gap: var(--space-2); list-style: none; margin: 0; padding: 0; }
/* Padding enlarges the actual clickable/tappable area beyond just the
   text itself; the matching negative margin cancels that same padding
   back out of the surrounding layout, so the links still sit as close
   together (and the bar reads just as thin) as they would with no
   padding at all - only the hit area grows, not the visible spacing. */
.site-subnav__nav a { display: inline-block; padding: 0.5rem; margin: -0.5rem; color: var(--color-text-muted); text-decoration: none; }
.site-subnav__nav a:hover, .site-subnav__nav a:focus-visible { color: var(--color-interactive-accent); text-decoration: underline; }

/* -------------------- Site Header -------------------- */
/* Bottom Border Size/Color (Theme Settings > Header, right under Logo
   Size) - a plain, always-on border-bottom driven directly by those two
   fields (--header-border-width/--header-border-color, inc/enqueue.php),
   not tied to scroll position or the header's own background color the
   way an earlier version of this border was. Width defaults to 0 above
   in :root, which alone already renders nothing (a 0-width border is
   invisible regardless of color), so there's no separate on/off class to
   maintain - .is-scrolled (assets/js/main.js) and site-header--white-bg
   (template-parts/header/site-header.php) are both still toggled but no
   longer read by anything here; left in place only because removing
   either is its own separate cleanup, not because this rule needs them. */
.site-header {
	position: sticky;
	top: 0;
	z-index: 100;
	background: var(--color-header-bg);
	border-bottom: var(--header-border-width) solid var(--header-border-color);
}
.site-header__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding-block: var(--space-2);
}
.site-logo { display: flex; align-items: center; gap: var(--space-1); }
/* Wraps just the logo+controls row for the Centered (main nav underneath)
   layout (Theme Settings > Header > Style) - .site-logo and
   .site-header__controls are its only two children there (the nav itself
   is a separate, later sibling of THIS wrapper, not of it - see
   template-parts/header/site-header.php). A plain flex row unconditionally
   (not just at the desktop breakpoint below) so mobile still lays logo
   and controls out side by side exactly like every other layout's mobile
   header does - only .site-header--nav-below's own desktop rule further
   down changes this to center the logo and pull controls out via
   position: absolute. */
.site-header__top-row { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.site-logo__info { padding-left: 0.9375rem; }
.site-logo img, .site-logo svg { display: block; width: 100%; max-width: var(--logo-max-width); aspect-ratio: 1 / 1; object-fit: cover; }
/* Fallback logo only (agility_fallback_logo_svg(), inc/fallback-logo.php -
   a Custom Logo upload has no .agility-logo-ring at all, so this simply
   doesn't match then). Scales Border Width (Theme Settings > Fallback
   Logo, --fallback-logo-border-width below) down proportionally to Logo
   Size (--logo-max-width) instead of applying it unmodified - the ring's
   stroke-width is drawn with vector-effect="non-scaling-stroke", so it
   renders at a constant PIXEL thickness regardless of how small the
   circle itself is displayed; left at a wider Border Width, it would look
   disproportionately chunky against a ring this small. The ratio is
   Logo Size / 100px, capped at 1 via min() - at Logo Size 100px and up
   this reduces to exactly Border Width itself (its "true" configured
   value, unmodified), and below 100px it tapers down continuously
   (e.g. a Border Width of 10 reads as ~8.5 at a 85px logo, 3 at 30px, 0
   at 0px) rather than snapping at a fixed threshold. Both custom
   properties are unitless/px so this same rule needs no separate
   size-gated class the way earlier fixed-value attempts did. */
.site-logo .agility-logo-ring {
	stroke-width: calc(var(--fallback-logo-border-width) * min(var(--logo-max-width), 100px) / 100px);
}
.site-logo__text { font-weight: 700; color: var(--color-header-text); font-size: 1.5rem; line-height: 1.1; }
.site-footer__brand { color: var(--color-footer-text); }
/* Muted via opacity on the same --color-header-text, not a separate fixed
   grey (--color-text-muted assumes a white/light background, and Header
   Background Color - Theme Settings > Header - might not be) - this way
   it's always readable against whatever the header background actually
   is, just visually secondary to the main logo text next to it. */
.site-logo__tagline { display: block; font-size: 0.85rem; font-weight: 400; color: var(--color-header-text); opacity: 0.65; letter-spacing: 0.04em; text-transform: uppercase; }
/* Logo (left aligned + Tagline) - Header Style choice with no Company
   Name, so .site-logo__tagline is the only thing in .site-logo__info
   (:only-child, rather than a dedicated class - matches whenever the
   Tagline shows without the Name next to it, regardless of which
   header layout produces that). Standing in for the company name line
   there, not a small subtitle under it, so it gets that line's own
   1.5rem size instead of its usual small-caps one. */
.site-logo__tagline:only-child { font-size: 1.5rem; }
@media (max-width: 48rem) /* 768px */ {
	.site-logo__text { font-size: 1.2rem; }
	.site-logo__tagline { font-size: 0.7rem; }
	.site-logo__tagline:only-child { font-size: 1.2rem; }
}

.site-header__controls { display: flex; align-items: center; gap: var(--space-2); }

.nav-toggle {
	display: none;
	background: none;
	border: none;
	cursor: pointer;
	padding: var(--space-1);
}
.nav-toggle span { display: block; width: 3.25rem; height: 0.25rem; background: var(--color-header-text); margin: 0.625rem 0; }

.primary-nav ul { display: flex; }
.primary-nav a { display: inline-block; color: var(--color-header-text); font-weight: 500; font-size: 1.6rem; padding-inline: 20px; }
.primary-nav a:hover { opacity: 0.8; }
/* Hover/focus fill - only once Logo Size (Theme Settings > Header) drops
   below 100px (.site-header--compact-logo, set in
   template-parts/header/site-header.php), and only for the nav that
   actually lives inside .site-header__controls (every Logo (left
   aligned...) Style choice - the Centered split-nav and nav-below
   layouts have their own separate <nav>s elsewhere and keep their
   existing underline treatment regardless of Logo Size). A full-height
   color block reads fine next to a small compact logo; next to a large
   one it would just be a heavy, oversized slab of color for no real
   reason, so this stays off until the logo is actually small. Desktop
   only - the mobile dropdown below stays the plain stacked list it
   already was; "spans the entire header height" is a single-row-header
   idea that doesn't map onto that the same way.
   The stretch chain (.site-header__controls -> .primary-nav -> <li> ->
   <a>, each a flex item taking its default align-items: stretch) is
   needed because the header's own height isn't fixed - it's driven by
   whichever child is tallest (usually the logo) - so a hardcoded height
   here would drift out of sync with it. .primary-nav's own negative
   margin-block cancels out .site-header__inner's padding-block
   specifically for this stretched chain, so the hover fill reaches
   .site-header's actual top/bottom edges instead of stopping at the
   inside of that padding. */
@media (min-width: 48.0625rem) {
	.site-header--compact-logo .site-header__controls { align-self: stretch; }
	.site-header--compact-logo .site-header__controls .primary-nav {
		display: flex;
		align-self: stretch;
		margin-block: calc(-1 * var(--space-2));
	}
	.site-header--compact-logo .site-header__controls .primary-nav ul li { display: flex; }
	.site-header--compact-logo .site-header__controls .primary-nav a {
		display: flex;
		align-items: center;
		justify-content: center;
	}
	.site-header--compact-logo .site-header__controls .primary-nav a:hover,
	.site-header--compact-logo .site-header__controls .primary-nav a:focus-visible {
		background: var(--color-brandPrimary);
		color: var(--color-white);
		opacity: 1;
	}
	/* Overrides the general .primary-nav a:hover/.is-current-nav-item
	   underline rule below (higher specificity: 5 class-level selectors
	   here vs. that rule's 3) - a solid full-height fill doesn't need its
	   own underline on top of it. .is-current-nav-item's own persistent
	   underline (not a hover/focus state) is deliberately untouched. */
	.site-header--compact-logo .site-header__controls .primary-nav a:hover .primary-nav__label,
	.site-header--compact-logo .site-header__controls .primary-nav a:focus-visible .primary-nav__label {
		border-bottom-color: transparent;
	}
}
.primary-nav__label { display: inline-block; border-bottom: 0.375rem solid transparent; }
.primary-nav a:hover .primary-nav__label { border-bottom-color: var(--color-interactive-accent); }
/* Active state - same bottom border as :hover, just persistent rather
   than needing the pointer over it. See agility_nav_item_is_active() /
   agility_mark_current_nav_item() in inc/setup.php for how a menu item
   gets marked current - matches on URL path (works for About/Contact and
   the Services/Work archives directly), plus a single Service or Project
   post counts as a match for its post type's archive link, so "Services"/
   "Work" stay highlighted while reading one specific service/project. */
.primary-nav .is-current-nav-item .primary-nav__label { border-bottom-color: var(--color-interactive-accent); }

@media (max-width: 48rem) /* Bootstrap md: 768px */ {
	.nav-toggle { display: block; }
	.primary-nav {
		display: none;
		position: absolute;
		top: 100%;
		left: 0;
		right: 0;
		background: var(--color-header-bg);
		border-bottom: 0.0625rem solid var(--color-border);
	}
	.primary-nav.is-open { display: block; }
	.primary-nav ul { flex-direction: column; padding: var(--space-2) var(--space-3); gap: var(--space-2); }
}

/* Site search - icon button that expands a small flyout form, anchored
   under the icon so it works the same way at every breakpoint instead of
   needing separate mobile/desktop layouts (unlike the primary nav). */
.site-search { position: relative; }
.site-search__toggle {
	display: flex;
	background: none;
	border: none;
	cursor: pointer;
	padding: var(--space-1);
	color: var(--color-header-text);
}
/* Icon color swap (currentColor inside the SVG, see agility_render_icon())
   instead of the plain opacity dim most other hover states on the site
   use - Brand Grey (Light), a fixed accent rather than something
   Icon Color (Theme Settings > Cards) or --color-header-text itself
   drives, so it reads the same regardless of what either of those is
   configured to. */
.site-search__toggle:hover { color: var(--color-brandGreyLight); }
.site-search__toggle-icon { width: 1.75rem; height: 1.75rem; }

.site-search__form {
	display: flex;
	align-items: center;
	gap: var(--space-1);
	position: absolute;
	top: calc(100% + var(--space-1));
	right: 0;
	width: 16rem;
	max-width: calc(100vw - 2 * var(--space-3));
	background: var(--color-white);
	border: 0.0625rem solid var(--color-border);
	border-radius: var(--radius);
	padding: var(--space-1);
	box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.12);
	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	transform: translateY(-0.5rem);
	transition: opacity 0.15s ease, transform 0.15s ease;
}
.site-search.is-open .site-search__form {
	opacity: 1;
	visibility: visible;
	pointer-events: auto;
	transform: none;
}
.site-search__input {
	flex: 1 1 auto;
	min-width: 0;
	border: none;
	outline: none;
	font-size: 1rem;
	font-family: inherit;
	padding: var(--space-1);
	color: #000000;
}
.site-search__submit {
	display: flex;
	flex-shrink: 0;
	background: none;
	border: none;
	cursor: pointer;
	padding: var(--space-1);
	color: var(--color-heading-safe);
}
.site-search__submit:hover { opacity: 0.8; }
.site-search__submit-icon { width: 1.25rem; height: 1.25rem; }

/* Centered (main nav split) header layout (Theme Settings > Header >
   Style) - logo centered, primary nav split evenly to either side of it,
   search/mobile toggle at the far right. This block covers that specific
   split arrangement; .site-header--nav-below further down covers the
   other Centered choice (main nav underneath) that shares this same
   selector's "no company name/tagline" logo treatment and
   .site-header__controls positioning rule, just with the nav laid out as
   its own row below instead of split. Desktop-only: below the mobile
   breakpoint both fall back to the exact same collapsed-menu behavior the
   Logo (left aligned...) layouts use (.primary-nav is still one
   #primary-navigation element with the same .nav-toggle/.is-open JS, see
   template-parts/header/site-header.php and assets/js/main.js - the split
   is achieved with CSS Grid here, not a second copy of the menu markup).
   3 grid columns (nav-left / logo / nav-right), not 4: an earlier version
   put .site-header__controls in its own 4th column, reasoning the
   resulting off-center logo would be "imperceptible" - it wasn't (visibly
   off against anything else genuinely centered on the page, e.g. a
   Technology logo centered via plain margin:auto). A 4th column sized to
   its own content, with nothing matching it on the left, unbalances the
   two nav columns' 1fr split by exactly that width. Fixed by taking
   .site-header__controls out of the grid entirely (position: absolute,
   anchored to .site-header__inner below) so it can never affect how the
   1fr/1fr nav columns - and therefore the logo's own center column - get
   sized.
   The reserved space for it is real padding on .site-header__inner, not
   a minmax() floor on the grid columns - tried that first, and it didn't
   actually work: a plain 1fr/auto/1fr track still grows to fit whatever
   content justify-self:end pins to its edge (nav-right's own links, here),
   so the "reserved" minimum did nothing to stop nav-right from extending
   under the absolutely-positioned controls and overlapping them (confirmed
   by measuring both elements' actual rendered edges, not just eyeballing
   it). Padding on the grid container itself is a real box-model boundary
   grid content can't grow past, which a track-sizing minimum isn't. Equal
   padding on both sides (nothing sits in the left one) is what keeps the
   two nav columns - and therefore the logo's own center column -
   genuinely symmetric, same reasoning as before. */
@media (min-width: 48.0625rem) /* just above the 768px mobile breakpoint */ {
	.site-header--centered .site-header__inner {
		position: relative;
		display: grid;
		grid-template-columns: 1fr auto 1fr;
		align-items: center;
		gap: var(--space-2);
		padding-inline: 4rem;
	}
	/* Unwraps the <nav> itself so its two <ul> children become direct grid
	   items (placeable in different columns) instead of one block the nav
	   would otherwise lay out internally. */
	.site-header--centered .primary-nav--split { display: contents; }
	.site-header--centered .primary-nav__group { display: flex; align-items: center; gap: var(--space-2); }
	/* grid-row: 1 on both is load-bearing, not decoration: the nav wrapper
	   unwraps to [left-group, right-group] in DOM order, followed by
	   .site-logo - so without an explicit row, Grid's auto-placement
	   cursor (forward-only, sparse packing) reaches column 2 (the logo)
	   AFTER already having placed something in column 3, and pushes it to
	   a second row instead of backfilling column 2 in row 1. Pinning every
	   item's row sidesteps auto-placement entirely rather than fighting
	   it. */
	.site-header--centered .primary-nav__group--left { grid-column: 1; grid-row: 1; justify-self: start; }
	.site-header--centered .site-logo { grid-column: 2; grid-row: 1; justify-self: center; }
	.site-header--centered .primary-nav__group--right { grid-column: 3; grid-row: 1; justify-self: end; }
	/* Also the positioning context .site-header--nav-below relies on below -
	   .site-header__controls is a descendant of .site-header__top-row in
	   THAT layout (not a direct child of .site-header__inner the way it is
	   here), so this same rule already resolves relative to the nearer,
	   row-scoped wrapper there instead of this one, with no override
	   needed - see .site-header--nav-below .site-header__top-row further
	   down for why that matters (centering the logo within just its own
	   row's height, not the full logo+nav block's). */
	.site-header--centered .site-header__controls {
		position: absolute;
		right: 1rem;
		top: 50%;
		transform: translateY(-50%);
	}
	/* Centered (main nav underneath) - shares .site-header--centered's own
	   "no company name/tagline" treatment and its .site-header__controls
	   positioning rule above, but lays the logo+controls row and the nav
	   below it out as two stacked rows instead of splitting the nav around
	   the logo in one. */
	.site-header--nav-below .site-header__inner {
		display: flex;
		flex-direction: column;
		gap: var(--space-1);
	}
	/* Same "reserve equal padding-inline rather than a real grid column"
	   fix .site-header--centered's own docblock above already explains for
	   the split layout - .site-header__controls is pulled out of normal
	   flow here too (see the shared rule above), so .site-logo is this
	   row's only real flex item, and justify-content: center genuinely
	   centers just it rather than centering it as part of a two-item group
	   together with controls. position: relative makes this row (not the
	   taller .site-header__inner, which spans both rows) the positioning
	   context .site-header__controls' position: absolute resolves against. */
	.site-header--nav-below .site-header__top-row {
		position: relative;
		justify-content: center;
		padding-inline: 4rem;
	}
	.site-header--nav-below .primary-nav--below ul { justify-content: center; }
}
@media (max-width: 48rem) /* mobile - both nav groups collapse into one dropdown */ {
	/* .primary-nav ul (below) pads each <ul> individually - fine for the
	   Standard layout's single list, but here it'd double up the gap
	   between the two stacked groups (left's bottom padding + right's top
	   padding). Move the padding to the shared nav wrapper instead so the
	   two groups read as one continuous list. */
	.site-header--centered .primary-nav--split { padding: var(--space-2) var(--space-3); }
	.site-header--centered .primary-nav__group { padding: 0; }
}

/* -------------------- Hero -------------------- */
.hero {
	background: linear-gradient(135deg, var(--color-brandPrimary), var(--color-brandSecondary));
	color: var(--color-white);
	padding-block: var(--section-padding-block);
	background-size: cover;
	background-position: center;
}
.hero__inner { max-width: var(--content-width); }
.hero h1, .hero h2, .hero h3 { color: var(--color-hero-heading); }
.hero p { color: var(--color-hero-heading); opacity: 0.85; font-size: 1.15rem; }
.hero__actions { display: flex; gap: var(--space-2); margin-top: var(--space-3); }
.hero__row { display: flex; align-items: center; gap: var(--space-4); }
.hero__content { flex: 2 1 0%; }
.hero__image { flex: 1 1 0%; }
.hero__image img { width: 100%; border-radius: var(--radius); }
/* Text Alignment (Hero block, front-page.php's page builder) - Right/
   Centered (right side) swap which side the text and Side Image sit on;
   row-reverse rather than reordering the DOM keeps the text as the first
   thing a screen reader/keyboard-only visitor reaches regardless of which
   side it's visually drawn on. Deliberately placed before the mobile
   @media below, same specificity (one class each) - that rule needs to
   keep winning at mobile widths, where "reversed" stops meaning anything
   (both stack in the same single column either way). */
.hero__row--reverse { flex-direction: row-reverse; }
/* The Centered (left side)/Centered (right side) choices - centers the
   text block itself within its column (rather than its normal
   edge-aligned reading position or plain Right's own hard-right
   alignment below), and the button row along with it (.hero__actions is
   its own flex row, so centering the text block's own text-align doesn't
   touch it on its own). */
.hero__content--centered { text-align: center; }
.hero__content--centered .hero__actions { justify-content: center; }
/* Plain Right (template-parts/flexible/hero.php) - always right-aligns
   the text itself, not just whenever there's no Side Image to swap
   .hero__row--reverse's column order against. Without this, Right with a
   Side Image present would only ever move the text to the other column
   (row-reverse) while the text inside stayed at its normal left-reading
   position - technically "on the right side of the page" but not
   actually right-aligned, which reads as a bug rather than a deliberate
   choice once you're looking for it. */
.hero__content--align-right { text-align: right; }
.hero__content--align-right .hero__actions { justify-content: flex-end; }
@media (max-width: 48rem) {
	.hero__row { flex-direction: column; }
}

/* -------------------- Cards -------------------- */
.card {
	background: var(--color-card-bg);
	border: 0.0625rem solid var(--color-border);
	border-radius: var(--radius);
	padding: var(--space-3);
	height: 100%;
	text-align: center;
	position: relative;
	transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
/* Service and Work cards specifically (not Client, Team, or the "+ Add..."
   cards, which all share the plain fixed --radius above unchanged) - the
   one card-radius pair Theme Settings > Accents > Corner Roundness
   (--corner-radius) was actually asked to control. border-radius:inherit
   further down (the photo-scrim ::before pseudo-elements) picks this up
   automatically, no separate override needed there. */
.card--service,
.card--work {
	border-radius: var(--corner-radius);
}
/* Stretches the title link's hit area over the whole card (one real link,
   full-card click target) and gives immediate hover feedback the moment
   the pointer enters the card, not just over the title text. The "+
   Add..." cards (.card--add below) are a separate, already-Tertiary-
   bordered treatment and aren't matched by this selector at all (no h3 a
   in their markup). */
.card:has(h3 a):hover {
	box-shadow: 0 0.5rem 1.25rem rgba(0, 0, 0, 0.08);
	cursor: pointer;
}
/* Border color change specifically excluded for Work cards - unlike
   Service/Client/Team cards, they already have their own hover feedback
   (the photo scrim lightening, or one of the Card Style reveal panels -
   see .card--work.card--has-image's own :hover rules further down), so
   an added border color here was redundant/competing rather than
   helpful. Brand Tertiary specifically - a fixed brand color, not
   Interactive Accent Color (Theme Settings > Accents, defaults to
   Primary) or --color-icon (Icon Color, defaults to Tertiary too, but
   independently configurable) - so changing either of those doesn't
   also change this hover accent. */
.card:has(h3 a):hover:not(.card--work) {
	border-color: var(--color-brandTertiary);
}
/* The lift itself (not the shadow/border/cursor above, which every card
   style keeps) is excluded for the three photo-reveal Card Styles -
   Reveal on Hover, Partial View (title), Reveal on Hover (Light) - since
   each of those already has its own internal panel sliding up on hover
   (see their own :hover ::before rules below); the whole card shifting
   up too, on top of that, competed with it instead of reading as one
   clean motion. Dark and Light don't have any internal hover motion of
   their own, so they're the only two styles that still get this lift. */
.card:has(h3 a):hover:not(.card--style-reveal):not(.card--style-reveal-title):not(.card--style-reveal-light) {
	transform: translateY(-0.25rem);
}
.card h3 a::after { content: ''; position: absolute; inset: 0; }
.card__icon { width: 3rem; height: 3rem; margin-bottom: var(--space-2); margin-inline: auto; color: var(--color-icon); }
.card h3 { margin-bottom: var(--space-1); }
.card p { color: var(--color-text-muted); margin-bottom: 0; }

/* Theme Settings > Layout > Card Alignment can switch this to left-aligned */
body.card-align-left .card { text-align: left; }
body.card-align-left .card__icon { margin-inline: 0; }

/* Work cards: fixed height so cards stay uniform regardless of how long
   the title/caption is or whether a photo is present, and content is
   vertically centered so shorter text doesn't look stranded at the top. */
.card--work {
	min-height: 17.5rem;
	display: flex;
	flex-direction: column;
	justify-content: center;
}
/* Caption gets the same size/weight treatment on every Work card, photo
   background or not - only the color differs (handled separately below),
   since that's what actually depends on what's behind the text. */
.card--work p { font-size: 1.2rem; font-weight: 500; }
.card--work.card--has-image {
	background-size: cover;
	background-position: center;
	border-color: transparent;
	/* Forces this card to be its own stacking context at all times (not
	   just on :hover, when the translateY() transform below would create
	   one anyway). Without this, the ::before scrim's z-index:-1 resolves
	   against whatever distant ancestor happens to be the nearest real
	   stacking context, which put it BEHIND this card's own background
	   image in the resting state - so every past tweak to the scrim's
	   darkness was invisible except while hovering (transform creates a
	   stacking context, which is why it only ever looked right on hover).
	   isolation:isolate pins the scrim's stacking to this element itself,
	   in every state. */
	isolation: isolate;
}
/* Dark scrim behind the text so it stays legible over any photo, no matter
   how bright or busy that photo is - client images are unpredictable and
   uncontrolled. Two layers: a flat wash everywhere (floor of darkening,
   even at the corners) plus extra darkening concentrated in the center,
   where the card's centered title/caption always sit - so the text zone
   clears WCAG AAA contrast even against a pure-white pixel, while the
   corners stay lighter and the photo still reads as a photo. pointer-
   events:none so it never blocks the card's click target. */
.card--work.card--has-image::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	border-radius: inherit;
	background:
		radial-gradient(ellipse at center, rgba(0, 0, 0, 0.3), transparent 65%),
		rgba(0, 0, 0, 0.5);
	pointer-events: none;
	transition: background 0.2s ease;
}
/* Hover: this is deliberately tuned to the lowest darkening that still
   clears WCAG AA "large text" (3:1) against a worst-case bright pixel in
   this photo, verified with the actual WCAG relative-luminance formula
   (not an approximation) at high sample resolution, plus a small safety
   margin. Going any lighter measurably drops below 3:1. Mouse-hover
   only, matching the existing lift/shadow hover rule above - keyboard/
   focus users still get the safer, fully legible resting state. */
.card--work.card--has-image:hover::before {
	background:
		radial-gradient(ellipse at center, rgba(0, 0, 0, 0.21), transparent 65%),
		rgba(0, 0, 0, 0.36);
}
/* The photo itself already carries the visual weight - a decorative icon
   on top of it reads as clutter, so it's hidden for photo cards only. */
.card--work.card--has-image .card__icon { display: none; }
.card--work.card--has-image h3 a,
.card--work.card--has-image p {
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
.card--work.card--has-image h3 a { color: #ffffff; }
/* Opaque white (not the old rgba 0.85) - the bold/19px sizing itself is
   shared with the no-image variant now (see .card--work p above); this
   was originally bumped specifically so this caption would qualify as
   WCAG "large text" (>=18.66px + bold) the same as the title, since at
   17px/regular it needed the stricter 4.5:1 normal-text threshold, which
   the lightened hover scrim couldn't actually clear. */
.card--work.card--has-image p {
	color: #ffffff;
}
/* Work card - last year of the Date Range field, smaller/lighter than the
   caption above it since it's supplementary, not primary, info. */
.card__year { font-size: 0.8rem; font-weight: 500; color: var(--color-text-muted); margin-top: var(--space-1); margin-bottom: 0; }
.card--work.card--has-image .card__year { color: rgba(255, 255, 255, 0.65); text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7); }

/* Card Style: Light (Theme Settings > Accents, .card--style-light added in
   agility_render_project_card(), inc/post-types.php) - the same overlay
   idea as the Dark treatment above, mirrored: a heavy WHITE wash instead
   of a dark one, so the photo reads as strongly lightened/washed-out
   rather than darkened, with black text on top of it instead of white.
   Resting-state opacity is intentionally high ("extremely" lightened, by
   request) - a lighter wash risked black text going unreadable over an
   uncontrolled, unpredictable client photo's darker areas. Hover backs
   off by the same proportion the Dark version's hover state does,
   revealing a bit more of the actual photo as an interactive cue. Text
   shadow flips to white-on-dark's opposite (a soft white glow) for the
   same reason the Dark version's text needs a dark one - legibility over
   whatever part of the photo peeks through the wash. */
.card--work.card--has-image.card--style-light::before {
	background:
		radial-gradient(ellipse at center, rgba(255, 255, 255, 0.85), transparent 65%),
		rgba(255, 255, 255, 0.75);
}
.card--work.card--has-image.card--style-light:hover::before {
	background:
		radial-gradient(ellipse at center, rgba(255, 255, 255, 0.6), transparent 65%),
		rgba(255, 255, 255, 0.52);
}
.card--work.card--has-image.card--style-light h3 a,
.card--work.card--has-image.card--style-light p {
	text-shadow: 0 1px 3px rgba(255, 255, 255, 0.8);
	color: #000000;
}
.card--work.card--has-image.card--style-light .card__year {
	color: rgba(0, 0, 0, 0.65);
	text-shadow: 0 1px 3px rgba(255, 255, 255, 0.8);
}

/* Card Style: Reveal on Hover (Theme Settings > Accents, .card--style-reveal
   added in agility_render_project_card(), inc/post-types.php) - the photo
   shows completely clear at rest, with no title/caption/year visible
   either, so the featured image is the whole card. On hover (or keyboard
   focus via :focus-within, so this isn't mouse-only), a semi-transparent
   panel slides up from below (translateY: the base ::before's own resting
   background swapped for a flat color here, pushed fully below the card
   at rest via transform rather than the Dark/Light variants' own opacity-
   based approach - it's the same element sliding INTO view, not fading
   in) into that same ::before footprint, arriving right as the text fades
   in on top of it - reads as the backdrop rising to meet the words as
   they appear, rather than the words just fading in over nothing.
   overflow: hidden (not needed by Dark/Light - their own ::before never
   moves beyond the card's own bounds) keeps that panel actually clipped
   out of view at rest instead of visibly spilling out below the card.
   Panel opacity (0.55) is deliberately lighter than Dark's own - the
   photo needs to stay visibly "still there" through it, not just tinted
   like Dark's - while still clearing WCAG AA (4.5:1) for white text
   against a worst-case pure-white photo pixel (verified: white blended
   45/55 with black ~= #737373, ~4.74:1 contrast). */
.card--work.card--has-image.card--style-reveal {
	overflow: hidden;
}
.card--work.card--has-image.card--style-reveal::before {
	background: rgba(0, 0, 0, 0.55);
	transform: translateY(100%);
	transition: transform 0.3s ease;
}
.card--work.card--has-image.card--style-reveal:hover::before,
.card--work.card--has-image.card--style-reveal:focus-within::before {
	transform: translateY(0);
}
.card--work.card--has-image.card--style-reveal h3 a,
.card--work.card--has-image.card--style-reveal p,
.card--work.card--has-image.card--style-reveal .card__year {
	opacity: 0;
	transition: opacity 0.3s ease;
}
.card--work.card--has-image.card--style-reveal:hover h3 a,
.card--work.card--has-image.card--style-reveal:hover p,
.card--work.card--has-image.card--style-reveal:hover .card__year,
.card--work.card--has-image.card--style-reveal:focus-within h3 a,
.card--work.card--has-image.card--style-reveal:focus-within p,
.card--work.card--has-image.card--style-reveal:focus-within .card__year {
	opacity: 1;
}
.card--work.card--has-image.card--style-reveal h3 a,
.card--work.card--has-image.card--style-reveal p {
	color: #ffffff;
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
.card--work.card--has-image.card--style-reveal .card__year {
	color: rgba(255, 255, 255, 0.65);
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}

/* Card Style: Reveal on Hover (Title Tab) (Theme Settings > Accents,
   .card--style-reveal-title added in agility_render_project_card(),
   inc/post-types.php) - a variant of Reveal on Hover above: same sliding
   panel, but it never fully leaves view. At rest it's only translated
   70% of the way down instead of Reveal's full 100%, so a "tab" of it
   still peeks up from the bottom - and the title sits pinned on top of
   that peeking tab, always visible, rather than fading in with the rest.
   Hovering slides the SAME panel up the remaining distance (to
   translateY(0), same resting position Reveal's hover ends at), which is
   what reveals the caption/year - those two stay opacity:0 until then,
   exactly like every other text line does in Reveal on Hover.
   The title has to be pulled out of the card's normal centered flex flow
   (position:absolute, pinned to the bottom of the card's own padding box
   via .card's position:relative) since it no longer moves or centers
   with the caption/year the way Reveal's does - it has its own fixed
   spot regardless of hover state.
   h3 itself spans the card's ENTIRE content box (inset: var(--space-3)
   on all four sides, not just left/right/bottom) rather than just
   hugging its own text height, with flex used to push that text down to
   the bottom edge visually - this looks identical to only sizing h3 to
   its text, but matters for a reason that has nothing to do with layout:
   .card h3 a::after (the "stretch the link over the whole card" trick,
   see that rule further up) is positioned relative to h3, not .card,
   the moment h3 itself becomes position:absolute - h3 replaces .card as
   ITS nearest positioned ancestor. A text-height-only h3 made that
   ::after (and therefore the whole card's click target) shrink down to
   just the title's own thin strip, which is the actual bug that got
   reported - sizing h3 to the full card again gives that ::after the
   same full-card reach it has everywhere else, title position included. */
.card--work.card--has-image.card--style-reveal-title {
	overflow: hidden;
}
.card--work.card--has-image.card--style-reveal-title::before {
	background: rgba(0, 0, 0, 0.55);
	transform: translateY(70%);
	transition: transform 0.3s ease;
}
.card--work.card--has-image.card--style-reveal-title:hover::before,
.card--work.card--has-image.card--style-reveal-title:focus-within::before {
	transform: translateY(0);
}
.card--work.card--has-image.card--style-reveal-title h3 {
	position: absolute;
	/* inset:0 + padding (not inset: var(--space-3)) - reaches all the way
	   to .card's own padding box, flush with its border, same as .card h3
	   a::after does for every other card style. Using padding instead to
	   recreate the visual gap keeps h3's own box (and so a::after's
	   containing block, see the comment above) exactly as large as
	   .card's, so the click target reaches the same corners it does on
	   Dark/Light/Reveal/Reveal on Hover (Light) instead of stopping one
	   --space-3 short of them. */
	inset: 0;
	padding: var(--space-3);
	margin: 0;
	z-index: 1;
	display: flex;
	align-items: flex-end;
	justify-content: center;
}
.card--work.card--has-image.card--style-reveal-title h3 a {
	color: #ffffff;
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
.card--work.card--has-image.card--style-reveal-title p,
.card--work.card--has-image.card--style-reveal-title .card__year {
	opacity: 0;
	transition: opacity 0.3s ease;
	color: #ffffff;
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
.card--work.card--has-image.card--style-reveal-title .card__year {
	color: rgba(255, 255, 255, 0.65);
}
.card--work.card--has-image.card--style-reveal-title:hover p,
.card--work.card--has-image.card--style-reveal-title:hover .card__year,
.card--work.card--has-image.card--style-reveal-title:focus-within p,
.card--work.card--has-image.card--style-reveal-title:focus-within .card__year {
	opacity: 1;
}

/* Card Style: Reveal on Hover (Light) (Theme Settings > Accents,
   .card--style-reveal-light added in agility_render_project_card(),
   inc/post-types.php) - Reveal on Hover's exact slide-up mechanic
   (photo clear at rest, panel + text hidden until hover/focus-within),
   just with the panel's own look swapped for Card Style: Light's above -
   the identical two-layer white wash background (same values, reused
   verbatim so the two styles always match if Light's own tuning ever
   changes) instead of Reveal's flat dark one, and black text/shadow to
   match, rather than a new color scheme invented just for this variant. */
.card--work.card--has-image.card--style-reveal-light {
	overflow: hidden;
}
.card--work.card--has-image.card--style-reveal-light::before {
	background:
		radial-gradient(ellipse at center, rgba(255, 255, 255, 0.85), transparent 65%),
		rgba(255, 255, 255, 0.75);
	transform: translateY(100%);
	transition: transform 0.3s ease;
}
.card--work.card--has-image.card--style-reveal-light:hover::before,
.card--work.card--has-image.card--style-reveal-light:focus-within::before {
	transform: translateY(0);
}
.card--work.card--has-image.card--style-reveal-light h3 a,
.card--work.card--has-image.card--style-reveal-light p,
.card--work.card--has-image.card--style-reveal-light .card__year {
	opacity: 0;
	transition: opacity 0.3s ease;
}
.card--work.card--has-image.card--style-reveal-light:hover h3 a,
.card--work.card--has-image.card--style-reveal-light:hover p,
.card--work.card--has-image.card--style-reveal-light:hover .card__year,
.card--work.card--has-image.card--style-reveal-light:focus-within h3 a,
.card--work.card--has-image.card--style-reveal-light:focus-within p,
.card--work.card--has-image.card--style-reveal-light:focus-within .card__year {
	opacity: 1;
}
.card--work.card--has-image.card--style-reveal-light h3 a,
.card--work.card--has-image.card--style-reveal-light p {
	color: #000000;
	text-shadow: 0 1px 3px rgba(255, 255, 255, 0.8);
}
.card--work.card--has-image.card--style-reveal-light .card__year {
	color: rgba(0, 0, 0, 0.65);
	text-shadow: 0 1px 3px rgba(255, 255, 255, 0.8);
}

/* A Project with "Hide Project" checked, visible only to an Administrator
   (see agility_render_project_card()) - faded/desaturated so it visibly
   reads as set apart from the site's real, live portfolio, plus an
   explicit "Hidden" badge so there's no ambiguity about why - grayscale
   alone could too easily read as a rendering glitch or a loading state.
   The badge lives in .card-slot--archived, a thin wrapper AROUND .card
   rather than inside it - opacity/filter on .card--archived composites
   its whole subtree at that reduced opacity/desaturation as one unit, so
   anything left inside it (the badge included) would have no way to
   visually opt back out to full strength. */
.card-slot--archived { position: relative; height: 100%; }
.card--archived { opacity: 0.35; filter: grayscale(85%); }
.card--archived:hover { opacity: 0.6; }
/* Hardcoded to Tertiary rather than a Theme Settings dropdown - this
   badge only ever shows to admins (a Work card whose Hide Project
   checkbox is on), not worth an admin field of its own. */
.card__archived-badge {
	position: absolute;
	top: var(--space-2);
	right: var(--space-2);
	z-index: 1;
	background: var(--color-brandTertiary);
	color: var(--color-white);
	font-size: 0.7rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	padding: 0.25rem 0.625rem;
	border-radius: 0.25rem;
}

/* Insight ("blog post") cards - see agility_render_insight_card() in
   inc/insights.php. Deliberately mirrors .card--work above (same fixed
   height, centered layout, optional photo background with the same dark
   scrim/hover treatment), not the plainer .card--service look, since an
   Insight post - like a Project - usually has a real uploaded photo, not
   just a decorative icon. Kept as its own selector rather than literally
   sharing the .card--work class (so the HTML doesn't call a blog post
   "work", and the two can diverge later without one affecting the other),
   but every value below is intentionally identical today so an Insight
   card matches a Work card sitting next to it in size, style, and hover -
   right down to reusing the plain .card__year class for its date line. */
.card--insight {
	min-height: 17.5rem;
	display: flex;
	flex-direction: column;
	justify-content: center;
}
.card--insight p { font-size: 1.2rem; font-weight: 500; }
.card--insight.card--has-image {
	background-size: cover;
	background-position: center;
	border-color: transparent;
	isolation: isolate;
}
.card--insight.card--has-image::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	border-radius: inherit;
	background:
		radial-gradient(ellipse at center, rgba(0, 0, 0, 0.3), transparent 65%),
		rgba(0, 0, 0, 0.5);
	pointer-events: none;
	transition: background 0.2s ease;
}
.card--insight.card--has-image:hover::before {
	background:
		radial-gradient(ellipse at center, rgba(0, 0, 0, 0.21), transparent 65%),
		rgba(0, 0, 0, 0.36);
}
.card--insight.card--has-image .card__icon { display: none; }
.card--insight.card--has-image h3 a,
.card--insight.card--has-image p {
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
.card--insight.card--has-image h3 a { color: #ffffff; }
.card--insight.card--has-image p { color: #ffffff; }
.card--insight.card--has-image .card__year { color: rgba(255, 255, 255, 0.65); text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7); }

/* Single Insight page - Date/Time-to-read sit in the page-header's right
   column, same row as the title (like .page-icon--inline does for Service/
   Work above), stacked vertically and right-aligned so they line up under
   the breadcrumb trail already sitting above them (see .breadcrumbs'
   comment above). Categories (if any) didn't move with them - they print
   as their own plain line below the header instead (.insight-categories).
   The featured image comes after that, above the post content itself. */
.page-header .insight-meta {
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: 0.25rem;
	flex-shrink: 0;
	margin-bottom: 0;
	/* .page-header is align-items: flex-start, so without this the meta
	   box's top edge lines up flush with the title's own top edge - a
	   small nudge down keeps it from reading as if it's floating above
	   the title rather than sitting in the same row as it. */
	padding-top: var(--space-1);
	color: var(--color-text-muted);
	text-align: right;
	white-space: nowrap;
}
.insight-meta p { margin: 0; }
.insight-meta a { color: inherit; }
.insight-meta__reading-time { display: inline-flex; align-items: center; gap: 0.3em; }
.insight-meta__icon { width: 1em; height: 1em; }
@media (max-width: 48rem) {
	.page-header:has(.insight-meta) { flex-direction: column; }
	.page-header .insight-meta { align-items: flex-start; text-align: left; white-space: normal; }
}

.insight-categories { color: var(--color-text-muted); margin-top: calc(-1 * var(--space-1)); margin-bottom: var(--space-3); }
.insight-categories a { color: inherit; }
.insight-featured-image {
	display: block;
	width: 100%;
	max-height: 25rem;
	object-fit: cover;
	border-radius: var(--radius);
	margin-bottom: var(--space-3);
}

/* Sitemap page (page-sitemap.php) - see agility_render_sitemap() in
   inc/sitemap.php. The one-link pages (Home/About/Contact/Privacy Policy)
   print above as a compact single row, not as grid columns of their own -
   giving a single link the same column width as Technologies' 18-item
   list is what made the grid feel cramped. */
.sitemap__quick-links {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--space-1) var(--space-3);
	list-style: none;
	margin: 0 0 var(--space-4);
	padding: 0;
	font-weight: 600;
}
.sitemap__quick-links a:hover,
.sitemap__quick-links a:focus-visible { color: var(--color-interactive-accent); }

/* Auto-fit grid of the remaining (list-bearing) sections - a taller
   minmax floor than a typical auto-fit card grid on this site (e.g.
   .about-process__list's 14rem) so a long list (Technologies, Services)
   gets real column width instead of wrapping onto many short lines; at
   this site's container width that settles at 3 columns rather than 4,
   with no column count hardcoded so it still adapts if the container or
   content ever changes. Stacks to a single column on narrow screens same
   as any other auto-fit grid here. A Technologies or Team section is left
   out of the grid entirely (not just left empty) whenever that post type
   has no posts yet, so the column count already varies on top of that. */
.sitemap {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
	gap: var(--space-4);
}
.sitemap__section h2 { font-size: 1.25rem; margin-bottom: var(--space-1); }
.sitemap__section h2 a { color: inherit; }
.sitemap__section h2 a:hover,
.sitemap__section h2 a:focus-visible { color: var(--color-interactive-accent); }
.sitemap__section ul { list-style: none; margin: 0; padding: 0; }
.sitemap__section li { margin-top: 0.375rem; }
.sitemap__section li a { color: var(--color-text-muted); }
.sitemap__section li a:hover,
.sitemap__section li a:focus-visible { color: var(--color-interactive-accent); }

/* Style Guide page (page-style-guide.php, admin-only) - "Brand Colors"
   swatches up top, see agility_render_style_guide_brand_colors() in
   inc/google-fonts.php. Each of the 4 base brand colors (Primary/
   Secondary/Tertiary/Grey) gets one large swatch, with its own Light/
   Lightest tints as smaller ones underneath - one flex row per family,
   wrapping to stack on narrow screens, all 4 families laid out as their
   own flex row centered on the page. Every swatch is real background-
   color + inline text color (agility_best_text_color()'s own contrast
   check, computed server-side per swatch - a fixed CSS color couldn't
   stay readable against both a dark base color AND its own pale
   Lightest tint), so no color-specific CSS lives here at all - only
   layout/sizing. */
.style-guide__brand-colors { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-4); margin-bottom: var(--space-4); }
.style-guide__brand-color-group { display: flex; flex-direction: column; align-items: center; gap: var(--space-1); }
.style-guide__brand-color-label { font-weight: 600; margin: 0; }
.style-guide__swatch-row { display: flex; gap: var(--space-1); }
/* Corner Roundness (--corner-radius, Theme Settings > Accents) drives
   every swatch's border-radius here, large and small alike - see this
   whole rule block's own top comment for why the small ones are
   included too even though only the large 4 were actually asked for. */
.style-guide__swatch {
	display: flex;
	align-items: center;
	justify-content: center;
	text-align: center;
	border-radius: var(--corner-radius);
	font-weight: 700;
	line-height: 1.2;
	overflow-wrap: break-word;
	padding: var(--space-1);
}
/* 9rem/4.5rem - not arbitrary, the exact same pair of sizes Team photos
   (.team-member__photo, 9rem) and the Stats band's icons (.stats__icon,
   4.5rem) already use elsewhere on the site, at the same 2:1 large:small
   ratio this section had before (100px/50px) - increased size that still
   lines up with an existing sitewide scale instead of a new one-off
   number. (Case Study tab icons used to share this same 4.5rem too, but
   are now their own fixed 50px by request - no longer part of this
   cross-reference.) */
.style-guide__swatch--large { width: 9rem; height: 9rem; font-size: 1.15rem; }
.style-guide__swatch--small { width: 4.5rem; height: 4.5rem; font-size: 0.85rem; }

/* Related Cards (page-style-guide.php, admin-only) - see
   agility_render_style_guide_related_cards() in inc/style-guide.php. The
   card itself is already sized by .related-cards-grid (shared with the
   real "Related Services"/"Related Work" sections it's demonstrating,
   single-technology.php/single-service.php) - this just adds the label
   underneath identifying which one it is, same idea as
   .style-guide__brand-color-label above. */
.style-guide__related-card-label { font-weight: 600; text-align: center; margin: var(--space-1) 0 0; }

/* Style Guide page (page-style-guide.php, admin-only) - see
   agility_render_style_guide_font_list() in inc/google-fonts.php. A
   centered slider up top (assets/js/style-guide.js) live-resizes every
   .style-guide__font-sample at once, so a typeface can be checked at a
   heading size or a body-copy size without touching any code - left
   untouched until actually moved, so the plain CSS default below still
   governs on first load.

   Two levels of columns here, not to be confused with each other:
   - .style-guide__groups lays the four category buckets (Standard/
     Business/Artistic/Fun) out in 2 CSS multi-columns (not a grid - a
     grid would need every bucket to be the same height to look right,
     and these vary a lot in font count; multi-column layout instead
     flows each whole bucket into whichever column is currently
     shorter, balancing the page automatically). break-inside: avoid on
     .style-guide__group keeps a single bucket from ever being split
     across the two columns.
   - Within one bucket, .style-guide__font is its own 2-column row - a
     fixed-width name on the left, the live sample filling the rest, both
     aligned the same way down every row (a classic type-specimen
     layout) rather than the name sitting above its own sample. */
.style-guide__size-control {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-2);
	margin: 0 auto var(--space-4);
	text-align: center;
}
.style-guide__size-control label { font-weight: 600; }
.style-guide__size-control input[type="range"] { width: 16rem; max-width: 50vw; }
.style-guide__size-control output { font-variant-numeric: tabular-nums; color: var(--color-text-muted); min-width: 3.5em; text-align: left; }
.style-guide__groups { columns: 2; column-gap: var(--space-4); }
/* Above 40px on the size slider (assets/js/style-guide.js toggles this
   class, not a media query - it's driven by the live slider value, not
   viewport width) - drops back to a single column, same as the
   max-width: 64rem tablet/mobile fallback below, so a large sample gets
   the full page width instead of wrapping sooner in a half-width column. */
.style-guide__groups.style-guide__groups--stacked { columns: 1; }
.style-guide__group { break-inside: avoid; margin-bottom: var(--space-4); }
.style-guide__category {
	margin-top: 0;
	margin-bottom: var(--space-2);
	padding-bottom: var(--space-1);
	border-bottom: 0.0625rem solid var(--color-border);
}
/* Extra breathing room specifically between the active-font previews
   ("Default"/"Logo", agility_render_style_guide_font_list() in inc/
   google-fonts.php) and the next one's own sub-title - only matches a
   .style-guide__category that directly follows a .style-guide__fonts
   sibling, which only happens in that active-font loop (the full
   comparison list's own .style-guide__category is always the first
   thing inside its .style-guide__group instead, already spaced from the
   group before it via that group's own margin-bottom). */
.style-guide__fonts + .style-guide__category { margin-top: var(--space-4); }
.style-guide__fonts { display: flex; flex-direction: column; gap: var(--space-3); }
.style-guide__font {
	display: grid;
	grid-template-columns: 10rem 1fr;
	align-items: center;
	gap: var(--space-3);
	border: 0.0625rem solid var(--color-border);
	border-radius: var(--radius);
	padding: var(--space-3);
}
/* Name column widens by 30px (10rem -> 11.875rem) once the sample size
   slider reaches 30px (assets/js/style-guide.js) - its own separate
   check, even though it happens to share that same 30px number with
   .style-guide__groups--stacked right above; that one's about the
   bucket columns, this one's about the name/sample split inside a
   single font row, and there's no guarantee those two stay the same
   threshold forever just because they do today. */
.style-guide__groups--wide-name-column .style-guide__font { grid-template-columns: 11.875rem 1fr; }
/* Only used within the full font comparison list (Theme Settings >
   Typography > "View All Fonts on Style Guide" checked -
   agility_render_style_guide_font_list(), inc/google-fonts.php) - marks
   whichever font(s) are actually selected in Custom Font and/or Font
   (Fallback Logo) with Brand Tertiary, at the same border-width as the
   active Case Study tab on a single Work page (.case-study-tabs__tab.is-
   active above), instead of the plain neutral, thinner --color-border
   every other font in the catalog keeps. Not used at all when that
   checkbox is unchecked - every row shown there is already one of these
   same two active fonts, so the accent would have nothing left to
   distinguish. */
.style-guide__font--active { border-width: 0.1875rem; border-color: var(--color-brandTertiary); }
/* The font's own name, deliberately NOT set in that font - a label
   should read the same regardless of which typeface it's labeling. 1.2rem
   by default; .style-guide__groups--large-names (assets/js/style-guide.js,
   toggled at the same sample-size slider the stacked-columns class uses,
   just its own separate 40px threshold) bumps it to 1.5rem once the
   sample text itself is big enough that a still-small name looks
   undersized next to it. */
/* margin: 0 - a real <p> tag, so without this it inherits the site-wide
   p { margin: 0 0 var(--space-2); } (style.css's own base rule) same as
   any other paragraph; .style-guide__font-sample right next to it
   already zeroes its own margin the same way, this one just never had
   its matching override until now - that mismatch (0 vs. 16px trailing
   margin) was exactly what made align-items:center on .style-guide__font
   center the two on visibly different lines instead of together. */
.style-guide__font-name { margin: 0; font-size: 1.2rem; font-weight: 600; color: var(--color-text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.style-guide__groups--large-names .style-guide__font-name { font-size: 1.5rem; }
.style-guide__font-sample { font-size: 2rem; line-height: 1.3; margin: 0; overflow-wrap: break-word; min-width: 0; }
@media (max-width: 64rem) {
	/* Narrower than .style-guide__groups' own 2-column split (64rem, not
	   48rem like the rest of this block) - the name/sample row inside
	   each bucket already gets tight once a ~half-width column drops much
	   below tablet width, well before the site's usual mobile breakpoint. */
	.style-guide__groups { columns: 1; }
	.style-guide__group { break-inside: auto; }
}
@media (max-width: 48rem) {
	.style-guide__font,
	.style-guide__groups--wide-name-column .style-guide__font {
		grid-template-columns: 1fr;
	}
	/* No margin-bottom here any more (used to add breathing room above
	   the sample once this stacks to a single column) - removed so
	   .style-guide__font-name's own margin box doesn't skew where
	   .style-guide__font's align-items:center actually centers it,
	   which was throwing off vertical alignment between the name and
	   the sample text sitting next to/under it. */
	.style-guide__font-sample { font-size: 1.4rem; }
}

/* Style Guide page's decorative flamingo (page-style-guide.php) - fixed
   to the viewport (not the page), starting at the left edge. Its
   horizontal position is set directly by assets/js/style-guide-
   flamingo.js as an inline transform:translateX(...) on THIS outer
   element, one continuous function of scroll PROGRESS (0 at the top of
   the page, 1 at the bottom) rather than anything time- or pixel-based,
   which is what actually guarantees it reaches the right edge exactly
   when the page is scrolled all the way down, on any page length. No
   CSS transition on that transform - the JS already recalculates it on
   every scroll event, so it should track the scrollbar 1:1, not lag
   behind it toward wherever it's headed. Nothing else about this outer
   element touches transform/translate/rotate/scale AT ALL - tried
   putting the flip+bob+rotate animation directly here first, and the
   scale(-1 ...) horizontal flip ended up mirroring the JS's own
   translateX too (composited individual-transform-properties apply
   before the `transform` property itself, per spec) - it walked
   backwards, off the LEFT edge, instead of toward the right one. The
   walk/bob/rotate cycle now lives entirely on .style-guide__flamingo-
   inner below instead, fully isolated from this element's own
   transform, so the two can never fight each other again. */
.style-guide__flamingo {
	position: fixed;
	left: 0;
	bottom: var(--space-2);
	z-index: 100;
	pointer-events: none;
}
/* Flip (Twemoji's own artwork faces left, and it's walking right) + the
   continuously-looping walk/bob/rotate cycle, independent of scroll
   position entirely - the two effects (walking in place here + being
   carried rightward by the outer element above) combine into one
   "walking across the screen" look without needing to be the same
   mechanism, or even the same element. */
.style-guide__flamingo-inner {
	display: inline-block;
	line-height: 1;
	transform-origin: bottom center;
	scale: -1 1;
	animation: style-guide-flamingo-walk 0.5s ease-in-out infinite;
}
.style-guide__flamingo-inner svg { display: block; height: 4.5rem; width: auto; }
/* The flamingo's own two original pink tones (page-style-guide.php's
   own comment on this markup has the full "why"), remapped to Tertiary/
   Tertiary Light instead of their original fixed Twemoji hex values -
   everything else in that artwork (the rock, its eye) keeps its
   original color untouched. */
.style-guide__flamingo-body { fill: var(--color-brandTertiary); }
.style-guide__flamingo-shade { fill: var(--color-brandTertiaryLight); }
@keyframes style-guide-flamingo-walk {
	0%, 100% { translate: 0 0; rotate: -4deg; }
	50% { translate: 0 -0.625rem; rotate: 4deg; }
}
@media (prefers-reduced-motion: reduce) {
	.style-guide__flamingo-inner { animation: none; }
}

/* Style Guide page's Konami-code easter egg (assets/js/style-guide-
   konami.js) - a missile launcher that appears bottom-center once the
   code is entered, and the missiles/explosions it fires off. All of
   .style-guide__missile/.style-guide__explosion's own positioning
   (left/top) is set directly by that script per shot - only their
   LOOK lives here. */
.style-guide__missile-launcher {
	position: fixed;
	left: 50%;
	bottom: 0;
	transform: translateX(-50%);
	width: 4rem;
	height: 3.5rem;
	z-index: 300;
	pointer-events: none;
	transition: opacity 0.6s ease;
}
.style-guide__missile-launcher--done { opacity: 0; }
/* Tank treads - a repeating dark/darker stripe pattern reads as tread
   links without needing individual elements per link. */
.style-guide__missile-launcher-treads {
	position: absolute;
	left: 50%;
	bottom: 0;
	transform: translateX(-50%);
	width: 4rem;
	height: 0.625rem;
	background: repeating-linear-gradient(90deg, #1a1a1a 0 0.3125rem, #333333 0.3125rem 0.625rem);
	border-radius: 0.3125rem;
	box-shadow: inset 0 0.0625rem 0.125rem rgba(0, 0, 0, 0.5);
}
/* Angular hull (clip-path, not just a rounded rectangle) in a gunmetal
   gradient instead of a flat brand color, with a Tertiary accent stripe
   along the bottom edge - the one deliberate spot of brand color on an
   otherwise deliberately military-grey machine, echoing how the
   missiles/shrapnel already lean on the brand palette elsewhere in this
   same easter egg. Small rivet dots for detail. */
.style-guide__missile-launcher-base {
	position: absolute;
	left: 50%;
	bottom: 0.5rem;
	transform: translateX(-50%);
	width: 3.75rem;
	height: 1.5rem;
	background: linear-gradient(180deg, #6b6f72 0%, #4a4d50 55%, #35373a 100%);
	clip-path: polygon(12% 0%, 88% 0%, 100% 100%, 0% 100%);
	border-bottom: 0.1875rem solid var(--color-brandTertiary);
}
.style-guide__missile-launcher-base::before,
.style-guide__missile-launcher-base::after {
	content: '';
	position: absolute;
	top: 0.375rem;
	width: 0.25rem;
	height: 0.25rem;
	border-radius: 50%;
	background: #2b2d2f;
	box-shadow: inset 0 0.0625rem 0.0625rem rgba(255, 255, 255, 0.15);
}
.style-guide__missile-launcher-base::before { left: 0.625rem; }
.style-guide__missile-launcher-base::after { right: 0.625rem; }
/* Rotated by the script itself (inline style, per shot) to aim at
   whatever it's about to fire at - rotate(0deg) here is its resting
   straight-up pose, matching what aim()'s own "+90" offset assumes.
   Same gunmetal gradient as the base, a Tertiary warning band partway
   up, and a darker flared muzzle tip (::after) instead of a plain
   rounded end. */
.style-guide__missile-launcher-barrel {
	position: absolute;
	left: 50%;
	bottom: 1.5rem;
	width: 0.75rem;
	height: 2.125rem;
	margin-left: -0.375rem;
	background: linear-gradient(90deg, #35373a 0%, #7a7e82 45%, #35373a 100%);
	border-radius: 0.1875rem;
	transform-origin: bottom center;
	transition: transform 0.25s ease;
}
.style-guide__missile-launcher-barrel::before {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	top: 55%;
	height: 0.25rem;
	background: var(--color-brandTertiary);
}
.style-guide__missile-launcher-barrel::after {
	content: '';
	position: absolute;
	left: 50%;
	top: -0.1875rem;
	transform: translateX(-50%);
	width: 1.0625rem;
	height: 0.4375rem;
	background: #2b2d2f;
	border-radius: 0.1875rem;
}
.style-guide__missile {
	position: fixed;
	width: 0.5rem;
	height: 1.375rem;
	background: var(--color-brandTertiary);
	border-radius: 50% 50% 20% 20%;
	z-index: 299;
	pointer-events: none;
	box-shadow: 0 0 0.5rem 0.125rem rgba(255, 140, 0, 0.6);
}
/* Small exhaust flame trailing the missile's own tail end (the missile
   is rotated by the script to always point at its target, so "tail" is
   consistently this element's own local bottom regardless of which way
   it's actually flying on screen). */
.style-guide__missile::after {
	content: '';
	position: absolute;
	left: 50%;
	top: 100%;
	transform: translateX(-50%);
	width: 0.375rem;
	height: 0.875rem;
	background: linear-gradient(180deg, #ffe082 0%, #ff8c00 55%, transparent 100%);
	border-radius: 0 0 50% 50%;
}
.style-guide__explosion {
	position: fixed;
	width: 0;
	height: 0;
	z-index: 301;
	pointer-events: none;
}
/* 3 flame layers per burst (the script appends 3 .style-guide__explosion
   -flame divs), staggered by nth-of-type so they bloom one after another
   instead of all at once - reads as a fireball actually rolling outward.
   Semi-transparent throughout (peak opacity 0.55, was fully opaque) by
   request, specifically so the target element - or by the time this
   plays, shatterTarget()'s own pieces in its place - stays visible
   through the fire instead of being fully hidden behind it. */
.style-guide__explosion-flame {
	position: absolute;
	left: 0;
	top: 0;
	width: 8rem;
	height: 8rem;
	margin-left: -4rem;
	margin-top: -4rem;
	border-radius: 50%;
	background: radial-gradient(circle, #fff5cc 0%, #ffcc00 25%, #ff8c00 55%, #ff4500 75%, transparent 100%);
	animation: style-guide-explosion-flame 1.1s ease-out forwards;
}
.style-guide__explosion-flame:nth-of-type(2) {
	width: 6rem;
	height: 6rem;
	margin-left: -3rem;
	margin-top: -3rem;
	background: radial-gradient(circle, #fff5cc 0%, #ffb200 30%, #ff6a00 60%, #d32f00 80%, transparent 100%);
	animation-delay: 0.15s;
	animation-duration: 1.3s;
}
.style-guide__explosion-flame:nth-of-type(3) {
	width: 9.5rem;
	height: 9.5rem;
	margin-left: -4.75rem;
	margin-top: -4.75rem;
	background: radial-gradient(circle, #ffe9a3 0%, #ff9500 35%, #ff3d00 65%, transparent 100%);
	animation-delay: 0.3s;
	animation-duration: 1.5s;
}
@keyframes style-guide-explosion-flame {
	0% { transform: scale(0.15); opacity: 0.55; }
	55% { transform: scale(1.3); opacity: 0.55; }
	100% { transform: scale(1.9); opacity: 0; }
}
/* The element a missile hits (assets/js/style-guide-konami.js's
   shatterTarget()) - visibility:hidden, not display:none or removal, so
   it keeps its exact original layout footprint (by request - it does
   NOT shrink or otherwise change size) and everything around it stays
   exactly where it already was. It's "hidden" rather than visible
   because shatterTarget() has, by this point, already built a whole
   grid of .style-guide__shard-wrap pieces standing in for it - the ones
   that "remain" sit in this exact same footprint, so this element being
   hidden underneath them is what actually reads as "mostly gone, a few
   pieces left". */
.style-guide__shattered { visibility: hidden; }
/* One <div> per shard, sized/positioned to exactly one grid cell of the
   original target (JS sets width/height/left/top inline, per shard,
   since every hit is a different size/shape/grid) - overflow:hidden is
   what actually does the clipping; .style-guide__shard-content inside
   is a full-size clone of the ORIGINAL element, shifted by its own
   negative left/top (also inline, JS) so only this one cell's slice of
   its real rendered content - not a generic colored square - shows
   through this window. */
.style-guide__shard-wrap {
	overflow: hidden;
	z-index: 302;
	pointer-events: none;
}
.style-guide__shard-content {
	position: absolute;
	margin: 0;
	/* Cancels out any hover/lift transform (cards, tabs, etc. all use
	   one) the clone would otherwise have inherited by matching the same
	   CSS as the original it was cloned from - a shard sitting still
	   under a stray :hover match would look broken. */
	transform: none !important;
	pointer-events: none;
}
/* --shard-x/--shard-y/--shard-rotate set per flying piece by the script
   (its own random angle, distance, AND spin - see shatterTarget() in
   assets/js/style-guide-konami.js), not fixed here - every flying piece
   shares this one keyframes animation, just heading off in its own
   direction at its own spin. Pieces that "remain" (per request, 10-20%
   of the total) never get this class at all - they're a separate,
   permanent, unanimated .style-guide__shard-wrap sitting in its
   original grid cell forever. */
.style-guide__shard-wrap--flying {
	animation: style-guide-shard-fly 1.4s ease-out forwards;
}
/* translate/rotate as their own standalone properties, not the transform
   shorthand that .style-guide__shard-content's own !important rule above
   already claims for canceling out inherited hover transforms - keeping
   these independent is what lets both take effect on the same element
   tree at once instead of one silently overwriting the other.
   rotate: var(--shard-rotate) - not a fixed rotation amount/direction
   shared by every piece (what used to make the whole burst read as
   spinning "in unison" instead of each piece tumbling independently,
   even though their translate directions were already randomized).
   Opacity now fades gradually across the ENTIRE flight (was fully
   opaque until 60%, then a late drop) - reads as each piece actually
   dissolving away the farther out it travels, not surviving fully
   intact right up until it vanishes. No more artificial upward arc at
   60% either (another thing every piece shared identically) - translate
   now moves in a straight line from center to its own --shard-x/-y, the
   spin is what keeps each piece's motion reading as distinct. */
@keyframes style-guide-shard-fly {
	0% { translate: 0 0; rotate: 0deg; opacity: 1; }
	35% { opacity: 0.85; }
	70% { opacity: 0.4; }
	100% { translate: var(--shard-x) var(--shard-y); rotate: var(--shard-rotate); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
	.style-guide__explosion-flame,
	.style-guide__shard-wrap--flying {
		animation: none;
	}
}

/* -------------------- Stats -------------------- */
.stats { text-align: center; }
/* Secondary's own Lightest tint, not the raw base color - this sits on
   the dark Primary background (.section--brandPrimary, see stats.php),
   and the base Secondary is assumed dark too (Theme Settings > Colors),
   so the raw base here would read as dark text on a dark backdrop.
   Lightest keeps it clearly Secondary-branded while staying legible -
   unlike a light background (the CTA banner, say), a dark backdrop only
   gets MORE legible the lighter this goes, so there's no tradeoff here
   the way there is picking Light vs Lightest for a background fill.
   stroke-width scales with the icon's box size (no non-scaling-stroke),
   so doubling width/height above also doubled the visible stroke - this
   thins it back down to a lighter weight at the larger size. */
.stats__icon { width: 4.5rem; height: 4.5rem; margin: 0 auto var(--space-1); color: var(--color-stats-icon); stroke-width: 1; }
.stats__number { font-size: 3.2rem; font-weight: 700; line-height: 1; color: var(--color-stats-value); }
.stats__label { margin-top: 0.25rem; color: var(--color-stats-subtext); text-transform: uppercase; font-size: 1rem; letter-spacing: 0.04em; }

/* Stats slider (template-parts/flexible/stats.php, assets/js/main.js) - the
   Stats grid is slide one, "Trusted By" is slide two when there's at least
   one Client to show; with none, this degrades to a single plain slide
   (arrows/dots aren't printed at all - see the $agility_trusted_by_html
   checks in the template). Horizontal padding on the viewport keeps the
   floating arrows from overlapping the first/last stat or logo. */
.content-slider__viewport { position: relative; padding-inline: 3.5rem; }
@media (max-width: 30rem) {
	.content-slider__viewport { padding-inline: 3rem; }
}
/* Active slide is a flex item stretched to the track's full min-height
   (below), then centers its own content vertically within that - without
   this, the shorter slide would just sit pinned to the top with all the
   leftover space dumped underneath it instead of evenly balanced. */
.content-slider__slide { display: none; width: 100%; }
.content-slider__slide.is-active { display: flex; flex-direction: column; justify-content: center; }
/* min-height on the track, not either individual slide - only the active
   slide is ever in the flow (display: none on the rest), so without this
   the whole section changes height, and everything below it on the page
   jumps, every time you switch slides. Sized to the taller of the two
   slides at each tier (measured directly, not guessed): "Trusted By"'s
   logo count varies, but this covers today's 5.
   Three tiers, not two - a single "desktop" value sized for the narrowest
   desktop width (~768-1023px, where the client-strip logos wrap into more
   rows) left a lot of dead space at typical wider desktop widths, where
   the same content needs barely half that. 64rem/1024px picks up right
   where that extra room stops being needed; 48rem/768px still matches the
   breakpoint the Stats/client-strip grids themselves switch layout at. */
.content-slider__track { display: flex; min-height: 410px; }
@media (min-width: 64rem) {
	.content-slider__track { min-height: 220px; }
}
@media (max-width: 48rem) {
	.content-slider__track { min-height: 620px; }
}
/* "Trusted By" sits directly on the slider's own section background - no
   panel behind the whole slide (that read as one heavy solid slab). Each
   logo gets its own small badge instead (below) - just enough of a
   surface for a mark authored assuming a light backdrop (a dark logo on a
   transparent PNG, say) to stay legible, without wrapping everything in
   one giant rectangle. */
.content-slider__panel { text-align: center; }
/* Real black-or-white auto-contrast text (--color-stats-text, the same
   variable every other heading in this section already uses - see
   .section--brandPrimary h1/h2/h3 above), not Slider Arrow Color
   (--color-stats-accent) - that field and Slider Background Color
   are two fully independent choices now, so nothing guarantees the
   accent color actually reads against whatever background gets picked.
   This heading needs to reliably read against the section itself, the
   same as every other heading in it. */
.section--brandPrimary .content-slider__panel h2 { color: var(--color-stats-text); }
.content-slider__panel .client-strip { margin-top: var(--space-3); }
.content-slider__panel .client-strip__logo {
	/* Same solid fill as the section this slider sits in
	   (.section--brandPrimary's own --color-stats-bg) - the badge is no
	   longer a visually distinct surface from its background, by request. */
	background: var(--color-stats-bg);
	border-radius: var(--radius);
	padding: var(--space-2) var(--space-3);
}
/* Solid-ish circles in Slider Arrow Color's own dark derivative
   (--color-stats-accent-dark, computed in inc/enqueue.php from Theme
   Settings > Accents > Slider Arrow Color - not a fixed color, so this
   recolors automatically whenever that field changes), same 88%-opacity/
   white-icon treatment the single Project page's own Gallery slider
   arrows now share too (.project-slider__arrow, the same
   --color-stats-accent-dark variable, not just a similar look)
   rather than the pale, translucent circle this used before - a light
   tint of what's often already a pale Highlight Color read as too
   washed-out against the dark .section--brandPrimary background to stay
   clearly visible/defined. Corners also share Corner Roundness
   (--corner-radius, Theme Settings > Accents) with every other element
   it drives - a true circle at that field's own max, same as the fixed
   border-radius: 50% this replaced always was, just adjustable now. */
.content-slider__arrow {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	align-items: center;
	justify-content: center;
	width: 3rem;
	height: 3rem;
	border: none;
	border-radius: var(--corner-radius);
	background: color-mix(in srgb, var(--color-stats-accent-dark) 88%, transparent);
	color: var(--color-white);
	cursor: pointer;
	transition: background 0.15s ease, transform 0.15s ease;
}
.content-slider__arrow:hover,
.content-slider__arrow:focus-visible {
	background: var(--color-stats-accent-dark);
	transform: translateY(-50%) scale(1.08);
}
.content-slider__arrow--prev { left: 0; }
.content-slider__arrow--next { right: 0; }
.content-slider__arrow svg { width: 1.375rem; height: 1.375rem; }
.content-slider__dots { display: flex; justify-content: center; gap: var(--space-1); margin-top: var(--space-3); }
/* Grey now, not an accent-derived color - request was specifically for
   something "slightly darker" than the old pale, accent-tinted dot, and
   a neutral grey reads as plain pagination chrome (which is all these
   are) rather than competing for attention with the slide content the
   way a brand accent color did. Solid brandGreyLight (Theme Settings >
   Colors > Grey's own Light tint), not a translucent color-mix like
   before - grey doesn't need the opacity trick a saturated accent color
   did to look "subtle" at rest. This slider sits on a DARK background
   (.section--brandPrimary), so rest/hover/active gets LIGHTER as it goes
   - the opposite direction from the Testimonials slider's own dots right
   below, which sit on a light background and need to go DARKER instead;
   same grey palette, just used in the direction that keeps each one
   readable against its own background. */
.content-slider__dot {
	width: 0.625rem;
	height: 0.625rem;
	padding: 0;
	border: none;
	border-radius: 50%;
	background: var(--color-brandGreyLight);
	cursor: pointer;
	transition: background 0.15s ease;
}
.content-slider__dot:hover,
.content-slider__dot:focus-visible { background: var(--color-brandGreyLightest); }
.content-slider__dot.is-active { background: var(--color-brandGreyLightest); }

/* "What Clients Say" slider (template-parts/flexible/testimonials.php) -
   arrows are NOT overridden here (unlike a previous version of this rule
   block) - the shared .content-slider__arrow rule above already uses
   Slider Arrow Color's own dark derivative
   (--color-stats-accent-dark), and this slider is meant to match the
   Stats/Trusted By slider's arrows exactly now, by request, not use a
   different accent. Dots still get their own light-BACKGROUND treatment
   below (this slider sits on a light section, not a dark one) - same
   Grey palette .content-slider__dot's own rest state uses above, just
   going DARKER instead of lighter as it goes from rest to hover to
   active, since a light dot wouldn't read against a light background at
   all. Track height isn't fixed here (unlike the Stats/Trusted By
   slider's own measured min-height, sized for that specific,
   predictable content) - a testimonial quote's length varies too much
   per-Client to safely guess a number that fits every slide without
   either clipping a long one or leaving a short one awkwardly padded. */
.testimonials-slider .content-slider__track { min-height: 0; }
.testimonials-slider .content-slider__dot { background: var(--color-brandGreyLight); }
.testimonials-slider .content-slider__dot:hover,
.testimonials-slider .content-slider__dot:focus-visible { background: var(--color-brandGrey); }
.testimonials-slider .content-slider__dot.is-active { background: var(--color-brandGrey); }

.page-icon { width: 4rem; height: 4rem; margin-bottom: var(--space-2); color: var(--color-icon); }

/* Breadcrumb trail (single Service / single Work) - right-aligned, sitting
   above .page-header so it lines up over the page icon on the right.
   Light grey and small on purpose - it's a secondary wayfinding aid, not
   competing with the h1/icon below it. */
.breadcrumbs { text-align: right; font-size: 0.8rem; color: var(--color-breadcrumb); margin-bottom: 45px; }
.breadcrumbs a { color: var(--color-breadcrumb); }
.breadcrumbs a:hover, .breadcrumbs a:focus-visible { color: var(--color-text-muted); }
.breadcrumbs__sep { margin: 0 0.375rem; }
.breadcrumbs__current { color: var(--color-text-muted); }

/* Single Project / Single Service page - title and icon share a row,
   icon pushed to the far right; hidden under 768px since it's decorative
   and the title needs the room on narrow screens. */
.page-header { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-2); }
.page-header h1 { margin-bottom: 0; }
.page-icon--inline { flex-shrink: 0; margin-bottom: 0; }
@media (max-width: 48rem) {
	.page-icon--inline { display: none; }
}

/* Single Project page - client name, in the tertiary/CTA brand color */
.project-client { color: var(--color-project-client-label); text-transform: uppercase; font-size: 1rem; letter-spacing: 0.04em; }
/* Administrator-only reveal of a confidential Client's real name (see
   agility_admin_confidential_client_hint()) - reuses the same light grey
   already established for breadcrumbs, since both are quiet/secondary
   text that isn't meant to compete with the line it sits next to. Never
   shown to a real visitor, so it doesn't need its own brand-color
   treatment the way the rest of this line does. */
.project-client__admin-hint { color: var(--color-breadcrumb); }

/* Single Project page - Date Range / Location, when either is set */
.project-meta { color: var(--color-text-muted); font-size: 1.6rem; }

/* Single Project page - the body content's opening paragraph is styled as
   a lead-in (was a dedicated Project Summary field; the content itself
   now lives in post_content, so this just styles its first paragraph
   the same way instead). */
.single-project .wysiwyg-content > p:first-child { color: var(--color-text-muted); font-size: 1.15rem; margin-bottom: var(--space-3); }

/* Single Project page - exactly 60px between the description above and
   the case study section right below it (tabbed or stacked - see
   .case-study-tabs/.case-study__heading further down), whichever one
   actually renders. The adjacent-sibling selectors only ever match
   .wysiwyg-content when ONE of those two immediately follows it, which
   only happens on this one template, right where the case study section
   starts - not e.g. between the tab strip and its own panels below. A
   literal 60px rather than one of the --space-* tokens since it doesn't
   land on any of their values (closest, --space-4, is 40px). */
.wysiwyg-content + .case-study-tabs,
.wysiwyg-content + .case-study__heading {
	margin-top: 60px;
}

/* Single Project page - case-study section markers (The Challenge / The
   Approach / The Result) - either a step number (in a tertiary ring badge,
   same treatment as the About page's Skills "+" bubble) or a plain themed
   icon (no badge chrome - the icon reads fine on its own at this size) to
   the left of each heading, per Theme Settings > Layout > Case Study Icon
   Style. */
.case-study__heading { display: flex; align-items: center; gap: var(--space-2); }
.case-study__heading h2 { margin-bottom: 0; }
.case-study__marker {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
}
/* Numbers: ring badge, same as before, but the digit itself is now much
   larger and Tertiary (was the smaller, heading-safe-colored text). */
.case-study__marker--number {
	width: 2.75rem;
	height: 2.75rem;
	border: 0.125rem solid var(--color-case-study-marker);
	border-radius: 50%;
	background: var(--color-bg);
	color: var(--color-case-study-marker);
	font-size: 2.25rem;
	font-weight: 700;
}
/* Icons: no ring/background - just the icon itself, fixed at 50x50px by
   request (not a --space-*/rem token - a literal pixel size, same as the
   tab-strip variant below). */
.case-study__marker--icon { width: 50px; height: 50px; }
.case-study__marker--icon svg { width: 50px; height: 50px; color: var(--color-case-study-marker); }

/* Case Study tabs (single-project.php) - The Challenge/Approach/Result as
   a tab strip of standalone rounded buttons, one panel visible at a
   time, instead of always stacked. Only rendered by PHP at all once
   there are 2+ filled-in sections (a single tab isn't a tab strip) - the
   older always-stacked .case-study__heading/h2 layout above still covers
   0-1 sections. assets/js/case-study-tabs.js is what actually wires up
   the click/keyboard behavior and hides the inactive panels (see
   .is-enhanced below) - everything here renders and lays out correctly
   even if that never runs, just without the interactivity: every tab is
   a real #anchor link and every panel stays visible, stacked, same as
   before.
   Previous version of this rule block used a "folder tab" look - rounded
   top corners only, flat bottom edge flush against a border under the
   whole row. That stopped reading as tabs at all once Corner Roundness
   (--corner-radius, Theme Settings > Accents) started driving this same
   radius globally alongside much rounder elements (cards, pills) - at
   its higher values the flat bottom edge looked like an arbitrary cut
   across an otherwise-round shape rather than a deliberate tab notch.
   Standalone rounded buttons don't have that problem at any radius, so
   there's no shared border under the row to butt up against any more. */
.case-study-tabs__list {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	/* A literal 30px, not one of the --space-* tokens - doesn't land on
	   any of their values (closest are --space-2 at 16px and --space-3
	   at 24px). */
	gap: 30px;
	margin-bottom: var(--space-4);
}
/* Rest: a plain card-like surface (background/border both match .card's
   own, so an untouched tab reads as "a button sitting here" even before
   any interaction, instead of bare text floating on the page - the thing
   that made the previous version feel unfinished/"ugly"). Icon stacked
   directly above its label (flex-direction:column + centered on both
   axes) rather than sitting side by side with it. */
.case-study-tabs__tab {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--space-1);
	padding: var(--space-2) var(--space-3);
	border: 0.125rem solid var(--color-border);
	border-radius: var(--corner-radius);
	background: var(--color-card-bg);
	color: var(--color-brandGreyLight);
	font-size: 1.25rem;
	font-weight: 600;
	text-decoration: none;
	text-align: center;
	cursor: pointer;
	box-shadow: 0 0 0 rgba(0, 0, 0, 0);
	transition: color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}
/* Hover/focus: background deliberately stays the same plain surface rest
   uses (by request) - no more Project Case Studies light-tint fill here.
   Font color is brand Grey (its own base color, not a Light/
   Lightest tint) rather than --color-heading-safe - a deliberately
   neutral step up from the tab's own Grey Light resting text color,
   short of the full accent color the icon still switches to on hover.
   Still gets its own lift + shadow + accent border/icon so a hovered tab
   clearly "picks up" off the page, just without a background color
   swap doing any of that work. */
.case-study-tabs__tab:hover,
.case-study-tabs__tab:focus-visible {
	color: var(--color-brandGrey);
	border-color: var(--color-case-study-marker);
	transform: translateY(-0.1875rem);
	box-shadow: 0 0.375rem 0.75rem rgba(0, 0, 0, 0.12);
}
/* Active: background stays the same plain surface hover/rest use (no
   accent tint) - what actually marks this one as selected is a solid
   border plus its own (slightly stronger) shadow, so it visibly sits
   above the row even at rest, not just on hover. Border and font color
   both use --color-case-study-marker-darkest - by request, NOT a
   computed darker blend, but whichever brand color FAMILY was chosen
   (Primary/Secondary/Tertiary/Grey) at its own base tier, regardless of
   whether the base, Light, or Lightest tier of that family was actually
   picked (see the PHP comment on this custom property in
   inc/enqueue.php for exactly how) - e.g. picking Tertiary (Light)
   still gives both of these plain Tertiary, since that base tier is
   already the darkest one that family has. Deliberately still its own
   distinct step from the icon right above it though (the LIGHTER
   derivative of whatever tier was literally chosen, see
   .case-study-tabs__tab.is-active .case-study__marker--icon svg further
   down) - two different treatments of the one configured field used
   across this one tab. Wins over the plain :hover rule above on
   specificity (two classes vs. one) if a visitor's pointer happens to
   be resting on the already-active tab. */
.case-study-tabs__tab.is-active {
	color: var(--color-case-study-marker-darkest);
	background: var(--color-card-bg);
	/* Thicker than the 0.125rem every other tab keeps (rest/hover both
	   above) - on top of the shadow/color, a heavier border is its own
	   independent signal that this is the selected tab, still readable
	   even where the shadow/color difference is subtle. */
	border-width: 0.1875rem;
	border-color: var(--color-case-study-marker-darkest);
	box-shadow: 0 0.25rem 0.625rem rgba(0, 0, 0, 0.1);
}
/* Icon marker fixed at 50x50px by request - was matched to the Home page
   Stats band's own icons (.stats__icon below, 4.5rem/72px); no longer
   cross-referencing that now that this has its own explicit fixed size.
   The number marker (below) keeps its own independent size (3.3rem) -
   only the icon variant was asked to shrink. */
.case-study-tabs__tab .case-study__marker--icon { width: 50px; height: 50px; }
/* Grey by default (not Project Case Studies, what an inactive icon
   would otherwise inherit from the base .case-study__marker--icon svg
   rule above) - an inactive tab's icon reading the same accent color as
   the active one made every tab look equally "selected" at a glance.
   Grey Light - same tier as the tab's own Grey Light text (right below),
   by request. Hover switches to the real, full-strength accent color so
   the icon joins the rest of that tab's own hover treatment (border/
   text already go accent-colored there too). Active is its own separate
   rule further down, deliberately NOT the same full-strength color
   hover uses. */
.case-study-tabs__tab .case-study__marker--icon svg {
	width: 50px;
	height: 50px;
	color: var(--color-brandGreyLight);
}
.case-study-tabs__tab:hover .case-study__marker--icon svg,
.case-study-tabs__tab:focus-visible .case-study__marker--icon svg {
	color: var(--color-case-study-marker);
	/* Not just the color swap above - a thin (1.5, this icon set's
	   sitewide default) stroke outline of an exact hex value still reads
	   visibly lighter/washed-out than a bold, solid-filled shape of that
	   SAME value would (mostly transparent negative space vs. a fully
	   inked glyph) - obvious right next to this tab's own label text,
	   which IS solid and uses this identical color. Bolding the stroke
	   gives the icon enough ink coverage to read as the real, full-
	   strength color instead of a diluted one. */
	stroke-width: 2.25;
}
/* Active tab's icon - a lighter derivative of Project Case Studies
   (--color-case-study-marker-light, the same 85%-white-blend tint
   already computed for every other *-light custom property, see
   agility_theme_settings_style_overrides() in inc/enqueue.php), by
   request - NOT the full-strength color hover above uses, and NOT the
   grey the label text next to it still uses at rest. Keeps the same
   bolded stroke-width as hover, for the same reason (a thin stroke of
   even this lighter value would read as fainter still otherwise). */
.case-study-tabs__tab.is-active .case-study__marker--icon svg {
	color: var(--color-case-study-marker-light);
	stroke-width: 2.25;
}
.case-study-tabs__tab .case-study__marker--number {
	width: 3.3rem;
	height: 3.3rem;
	font-size: 2.7rem;
	border-width: 0.125rem;
}
.case-study-tabs__tab-label { white-space: nowrap; }
/* Small accent divider under the whole Case Study section (single Work
   page - either the tab strip or, with 0-1 filled-in sections, the plain
   stacked .case-study__heading layout above) - by request, 200px rather
   than the full-width .section-divider every other section boundary on
   the site uses, and colored with the Light tier of the same brand color
   family as the active tab's own border (--color-case-study-marker-
   family-light, inc/enqueue.php - NOT --color-case-study-marker-light,
   an unrelated 85%-white blend already used elsewhere on this same tab
   strip) instead of the plain neutral --color-border every other <hr> on
   the site uses, so it reads as belonging to this one section rather
   than a generic page break. */
.case-study__divider {
	width: 200px;
	max-width: 100%;
	margin: 60px auto 15px;
	border: none;
	border-top: 0.125rem solid var(--color-case-study-marker-family-light);
}

/* Inactive panels only ever actually disappear once JS has proven it's
   running (.is-enhanced, added by case-study-tabs.js) - see the big PHP
   comment in single-project.php for why that matters. The panel that
   JUST became active gets a small fade-and-rise-in on top of that, purely
   decorative - it plays whenever the browser applies display:block to a
   freshly is-active panel, i.e. every real tab switch. */
.case-study-tabs.is-enhanced .case-study-tabs__panel:not(.is-active) {
	display: none;
}
.case-study-tabs.is-enhanced .case-study-tabs__panel.is-active {
	animation: case-study-panel-in 0.35s ease;
}
@keyframes case-study-panel-in {
	from { opacity: 0; transform: translateY(0.75rem); }
	to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
	.case-study-tabs.is-enhanced .case-study-tabs__panel.is-active {
		animation: none;
	}
}

/* Single Service / Single Project "back to archive" button */
.back-link { margin-top: var(--section-padding-block); }

/* Single Service page - light divider between the content and Related Work */
.section-divider { border: none; border-top: 0.0625rem solid var(--color-border); margin: var(--section-padding-block) 0; }

/* Home page "What We Do" (Services Teaser) - View All link under the grid */
.services-teaser__view-all { margin-top: var(--section-padding-block); }

/* Home page "Our Work Speaks For Itself" (Work Teaser) - View All link under the grid */
.work-teaser__view-all { margin-top: var(--section-padding-block); }

/* Single Project page - Gallery slider (see the #data-project-slider markup
   in single-project.php and its click/arrow behavior in assets/js/main.js).
   The main stage shows each slide uncropped via object-fit: contain (a
   mixed set of screenshots - and the optional Client Logo leading them -
   rarely share one aspect ratio, so cropping to fill would cut content
   off); the background fills the letterboxing so it never looks like a
   stray image floating on the page. Follows the same background/button
   logic as the Stats/Trusted By and Testimonials sliders (--color-stats-bg,
   --color-stats-accent-dark) rather than the page's own plain background
   and Interactive Accent Color - one shared "slider" look site-wide
   instead of a one-off treatment just for this Gallery. */
.project-slider { margin-top: var(--space-3); margin-bottom: var(--space-3); }
.project-slider__viewport {
	position: relative;
	background: var(--color-stats-bg);
	border-radius: var(--radius);
	overflow: hidden;
	padding-top: 15px;
	padding-bottom: 15px;
}
.project-slider__image {
	display: block;
	width: 100%;
	height: 28rem;
	object-fit: contain;
}
@media (max-width: 48rem) {
	.project-slider__image { height: 16rem; }
}
/* Prev/next arrows float over the image, centered vertically, one on each
   side - a solid, dark circle reads as a clearly clickable control against
   any photo, unlike the original faint translucent-white treatment. Same
   Slider Arrow Color-derived fill as the Stats/Trusted By and Testimonials
   sliders' own arrows (--color-stats-accent-dark) rather than Interactive
   Accent Color, so all three sliders' controls read as one consistent
   "slider" component site-wide - same shared Corner Roundness
   (--corner-radius) too, not just the color. A drop shadow keeps the
   edge crisp even over a light-colored image. */
.project-slider__arrow {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	align-items: center;
	justify-content: center;
	width: 3.5rem;
	height: 3.5rem;
	border: none;
	border-radius: var(--corner-radius);
	background: color-mix(in srgb, var(--color-stats-accent-dark) 88%, transparent);
	box-shadow: 0 0.125rem 0.75rem rgba(0, 0, 0, 0.3);
	color: var(--color-white);
	cursor: pointer;
	transition: background 0.15s ease, transform 0.15s ease;
}
.project-slider__arrow:hover,
.project-slider__arrow:focus-visible {
	background: var(--color-stats-accent-dark);
	transform: translateY(-50%) scale(1.08);
}
.project-slider__arrow--prev { left: var(--space-3); }
.project-slider__arrow--next { right: var(--space-3); }
.project-slider__arrow svg { width: 1.75rem; height: 1.75rem; stroke-width: 2.5; }
/* Thumbnail strip - clicking one loads that image into the stage above
   (assets/js/main.js swaps .project-slider__image's src/alt and toggles
   .is-active) rather than navigating anywhere, so this stays a plain
   button row rather than a set of links. */
.project-slider__thumbs { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-1); margin-top: var(--space-2); }
.project-slider__thumb {
	flex-shrink: 0;
	width: 5rem;
	height: 3.5rem;
	padding: 0;
	border: 0.125rem solid transparent;
	border-radius: var(--radius);
	overflow: hidden;
	background: none;
	cursor: pointer;
}
.project-slider__thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.project-slider__thumb.is-active { border-color: var(--color-interactive-accent); }

/* Single Project page - Visit Project button */
.visit-project { margin-top: var(--space-3); }

/* Single Project page - All Work and Visit Project share one row, Visit
   Project pushed to the far right. Falls back gracefully to just All
   Work (left-aligned) when there's no External Link. */
.project-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); margin-top: var(--section-padding-block); }
.project-actions .back-link,
.project-actions .visit-project { margin-top: 0; }

/* -------------------- Testimonials -------------------- */
.testimonial { background: var(--color-white); border-radius: var(--radius); padding: var(--space-3); border: 0.0625rem solid var(--color-border); }
/* "What Clients Say" (.section--brandGrey - see
   template-parts/flexible/testimonials.php). An 18% live blend with white
   (color-mix), the same ratio agility_brand_color_tints() (inc/color-
   utils.php) uses to generate every *Lightest custom property - so the
   card always reads as "Slider Background Color, but paler," never
   literally identical to the section behind it (which
   would make the card invisible if this were a flat 1:1 copy) and never
   a fixed white that ignores what that field is actually set to. The
   single Project page's own "What the Client Said" cards
   (.project-testimonials .testimonial below) get a different treatment -
   see the comment down there for why. */
.section--brandGrey .testimonial { background: color-mix(in srgb, var(--color-testimonials-bg) 18%, white); }
.testimonial__quote { font-size: 1.05rem; font-style: italic; color: var(--color-text); }
.testimonial__name { font-weight: 700; color: var(--color-heading-safe); margin-top: var(--space-2); }
.testimonial__company { color: var(--color-text-muted); font-size: 0.9rem; }

/* Single Project page's "What the Client Said" (see single-project.php) -
   the same .testimonial markup as the Home page's testimonials section,
   scaled way up and laid out as its own centered, evenly-wrapped row
   rather than a fixed grid column count - a page featuring one project's
   own quote(s) can afford (and reads better) much larger than a section
   showing several quotes side by side. */
.project-testimonials { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-3); margin-top: var(--space-3); }
/* Unlike the Home page's cards above, this page has no colored section
   sitting behind the card to read as "paler than" - the card sits
   directly on the plain white page, so the base .testimonial rule's fixed
   white background is invisible here (a white card with only a thin
   border, on a white page). Takes Slider Background Color directly, at
   full strength, instead - the card itself becomes the
   colored surface rather than a pale echo of one. */
.project-testimonials .testimonial { flex: 1 1 26rem; max-width: 34rem; padding: var(--space-4); background: var(--color-testimonials-bg); }
.project-testimonials .testimonial__quote { font-size: 1.6rem; line-height: 1.4; }
.project-testimonials .testimonial__name { font-size: 1.1rem; margin-top: var(--space-3); }
.project-testimonials .testimonial__company { font-size: 1rem; }

/* -------------------- CTA banner -------------------- */
.cta-banner {
	position: relative;
	overflow: hidden;
	text-align: center;
	background: var(--color-cta-banner-bg);
	color: var(--color-cta-banner-text);
}
.cta-banner h1, .cta-banner h2, .cta-banner h3 { color: var(--color-cta-banner-text); }
.cta-banner .btn { margin-top: var(--space-2); }
.cta-banner .container { position: relative; z-index: 1; }

/* Decorative only - full color, deliberately asymmetric (size/tilt/palette)
   on each side so the two trees read as a loose pair, not a mirrored copy */
.cta-banner__palm {
	position: absolute;
	height: auto;
	pointer-events: none;
	z-index: 0;
}
.cta-banner__palm--left {
	width: 10rem;
	left: -0.75rem;
	bottom: -0.5rem;
	transform: rotate(-7deg);
}
.cta-banner__palm--right {
	width: 6.75rem;
	right: 2rem;
	bottom: -0.5rem;
	transform: rotate(10deg);
}

/* Leaned up against the left tree's trunk - rendered after it in the
   markup so it paints on top where the two overlap */
.cta-banner__surfboard {
	position: absolute;
	left: 1.6rem;
	bottom: -0.3rem;
	height: 6rem;
	width: auto;
	transform: rotate(18deg);
	transform-origin: bottom center;
	pointer-events: none;
	z-index: 0;
}
@media (max-width: 30rem) {
	.cta-banner__surfboard { height: 4rem; left: 1.1rem; }
}
/* Center stripe - Secondary's own Light tint, the third piece of the
   surfboard's brand-derived palette alongside the board/sun-circle colors
   passed into agility_surfboard_svg() (Tertiary/Primary Light - see
   inc/cta-banner.php). Built entirely from the brand palette + its
   derivatives, no dedicated Theme Settings field for it. */
.agility-surfboard__accent { fill: var(--color-brandSecondaryLight); }
@media (max-width: 30rem) {
	.cta-banner__palm--left { width: 6.5rem; }
	.cta-banner__palm--right { width: 4.5rem; right: 0.5rem; }
}

/* Generic decorative placement for the non-beach CTA art styles (icon
   shapes rather than the palm tree's tall silhouette) - the same icon
   rendered on both sides via agility_render_cta_decor_pair(), mirrored
   and counter-rotated on the right so the pair reads as a loose set
   rather than an exact mirrored copy. */
.cta-banner__decor {
	position: absolute;
	bottom: -0.25rem;
	width: 7rem;
	height: auto;
	opacity: 0.9;
	pointer-events: none;
	z-index: 0;
}
.cta-banner__decor--left { left: 0.75rem; transform: rotate(-6deg); }
.cta-banner__decor--right { right: 0.75rem; transform: rotate(6deg) scaleX(-1); }
@media (max-width: 30rem) {
	.cta-banner__decor { width: 4.5rem; }
}

/* -------------------- Footer -------------------- */
.site-footer { position: relative; overflow: hidden; background: var(--color-footer-bg); color: var(--color-footer-text); padding-block: var(--space-4); }
.site-footer a { color: var(--color-footer-text); }
/* Oversized, ghosted tagline as the footer's background element - no
   illustration, just the brand's own words, sized to bleed off both
   edges and sit behind the real content (z-index:1 on .site-footer__inner
   / .site-footer__bottom below). */
.footer-motto-bg {
	position: absolute;
	left: 50%;
	z-index: 0;
	width: max-content;
	max-width: none;
	margin: 0;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.02em;
	white-space: nowrap;
	color: color-mix(in srgb, var(--color-footer-text) 10%, transparent);
	pointer-events: none;
	user-select: none;
}
/* Default layout - vertically and horizontally centered behind the
   footer's content. */
.footer-motto-bg--centered {
	top: 50%;
	transform: translate(-50%, -50%);
	font-size: clamp(2.5rem, 11vw, 8rem);
}
/* Alternate layout - larger, anchored flush to the bottom edge of the
   footer instead of centered; line-height: 1 keeps it flush rather than
   leaving the normal line-box padding below the glyphs. */
.footer-motto-bg--bottom {
	bottom: 0;
	transform: translateX(-50%);
	font-size: clamp(3rem, 13vw, 10rem);
	line-height: 1;
}
.site-footer__phone { font-weight: 700; }
.site-footer__inner {
	position: relative;
	z-index: 1;
	display: grid;
	grid-template-columns: 1fr auto 1fr;
	align-items: start;
	gap: var(--space-3);
}
.site-footer__inner > *:first-child { justify-self: start; }
.site-footer__inner > *:nth-child(2) { justify-self: center; text-align: center; }
.site-footer__inner > *:last-child { justify-self: end; }
@media (max-width: 48rem) {
	.site-footer__inner { grid-template-columns: 1fr; justify-items: center; text-align: center; }
	.site-footer__inner > *:first-child,
	.site-footer__inner > *:nth-child(2),
	.site-footer__inner > *:last-child { justify-self: center; }
}
.site-footer__social { display: flex; gap: var(--space-2); margin-top: var(--space-2); }

/* Social icon row (Theme Settings > Contact & Social) - shared between
   the footer and the Contact page. Each link is a circle badge in
   Social Icon Color (Theme Settings > Accents, defaults to Secondary
   Lightest) with a fixed black text/glyph rather than auto-contrast -
   an explicit choice, same as how .btn--secondary already treats its own
   color. Hover darkens the chosen color slightly via color-mix (not a
   fixed "...Light" custom property - Social Icon Color can be set to any
   of the 12 brand tiers, not just Secondary, so there's no single
   adjacent tier to reliably step to anymore). */
.social-icons__link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2.75rem;
	height: 2.75rem;
	border-radius: 50%;
	background: var(--color-social-icon);
	color: #000000;
	flex-shrink: 0;
	transition: transform 0.15s ease, opacity 0.15s ease;
}
/* .site-footer a { color: var(--color-footer-text); } outranks the rule
   above on specificity alone (class+tag beats a single class), which was
   silently overriding this to white in the footer specifically. */
.site-footer .social-icons__link { color: #000000; }
.social-icons__link:hover { background: color-mix(in srgb, var(--color-social-icon) 80%, black); transform: translateY(-0.125rem); }
.social-icons__glyph { width: 1.35rem; height: 1.35rem; }
/* Contact page: large icons, sized up from the footer's compact version
   so they read as a real feature under the address, grouped closely
   together (small gap) rather than spread across the column. */
.social-icons--contact { display: flex; gap: var(--space-2); margin-top: var(--space-5); }
.social-icons--contact .social-icons__link { width: 5rem; height: 5rem; }
.social-icons--contact .social-icons__glyph { width: 2.5rem; height: 2.5rem; }
/* Muted via opacity on the same --color-footer-text used for the regular
   footer text above it, not a separately computed fixed color - the same
   technique .site-logo__tagline (header) already uses for the same
   "secondary, de-emphasized text" need. This is what actually delivers
   "lighter if Footer Text Color is black, darker if it's white": opacity
   blends the text color toward whatever's behind it, so a dark color
   drifts lighter and a light color drifts darker, automatically, in
   whichever direction is correct - no separate light/dark branching
   needed. 0.5 (was 0.65) for a more pronounced drift in both directions.
   (This does mean the fade isn't contrast-guaranteed the way a dedicated
   computed color could be - deliberately traded for staying a true
   derivative of Footer Text Color, per what was asked for here.) */
.site-footer__bottom { position: relative; z-index: 1; margin-top: var(--space-4); padding-top: var(--space-3); border-top: 0.0625rem solid color-mix(in srgb, var(--color-footer-text) 15%, transparent); font-size: 0.85rem; text-align: center; color: var(--color-footer-text); opacity: 0.5; }
.site-footer__bottom p { margin: 0 0 0.25rem; }
.site-footer__bottom p:last-child { margin-bottom: 0; }
.site-footer__bottom .site-footer__disclaimer { max-width: 40.625rem; margin: 0 auto 0.9375rem; }
/* Powered By logos (Theme Settings > Footer > Powered By Logos repeater) -
   shown after the "Powered By: " label added in
   template-parts/footer/site-footer.php, underneath the Privacy Policy/
   Sitemap/Style Guide line. Small and inline with that text, not a full
   section of their own - .site-footer__bottom's own font-size/opacity
   above already applies here too, same muted treatment as the rest of
   this line. */
.site-footer__powered-by { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 0.5rem; }
/* Overrides .site-footer__bottom p's own margin shorthand below (which
   would otherwise win on source order alone - same class+element
   specificity bump .site-footer__bottom .site-footer__disclaimer above
   already uses for the same reason). */
.site-footer__bottom .site-footer__powered-by { margin-top: 30px; }
/* .site-footer__powered-by-logo lands on an <a> when the repeater row has
   a Link (wrapping the <img>), or directly on the <img> itself when it
   doesn't - either way the visible box is a fixed 50x50px, so the two
   selectors below just target "whichever element the image itself is". */
.site-footer__powered-by-logo { display: inline-flex; width: 50px; height: 50px; }
.site-footer__powered-by-logo img { width: 100%; height: 100%; object-fit: contain; }
img.site-footer__powered-by-logo { object-fit: contain; }

/* -------------------- Forms (Contact Form 7) -------------------- */
.wpcf7-form p { margin-bottom: var(--space-2); }
.wpcf7-form input[type="text"],
.wpcf7-form input[type="email"],
.wpcf7-form input[type="tel"],
.wpcf7-form textarea {
	width: 100%;
	padding: 0.75em;
	border: 0.0625rem solid var(--color-border);
	border-radius: var(--radius);
	font-family: inherit;
	font-size: 1rem;
}
.wpcf7-form textarea { height: 9.375rem; }
.wpcf7-form input[type="submit"] {
	display: inline-flex;
	padding: 0.75em 1.5em;
	border-radius: var(--radius);
	font-weight: 600;
	background: var(--color-brandPrimary);
	color: var(--color-white);
	border: 0.125rem solid var(--color-brandPrimary);
	cursor: pointer;
}
.wpcf7-form input[type="submit"]:hover { background: var(--color-brandPrimaryLightest); color: var(--color-brandPrimary); }

/* -------------------- Utilities -------------------- */
.text-center { text-align: center; }
.about-skills-heading { margin-top: var(--space-5); margin-bottom: var(--space-3); }
/* "Our Team" specifically (see agility_render_team_section() in
   inc/team.php) - centered like the About page's Our Process heading
   right above it. A second class rather than centering
   .about-skills-heading itself, which is shared with Our Expertise here
   and Other Technologies/Experts on single-technology.php - neither of
   those was asked to change. */
.team-grid__heading { text-align: center; }
.skills-list { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); margin-bottom: var(--space-3); }
.skills-list li {
	background: var(--color-bg);
	border: 0.0625rem solid var(--color-border);
	/* Theme Settings > Accents > Corner Roundness (--corner-radius) - was
	   a fixed, arbitrarily huge value (62.4375rem) purely as a "however
	   big this pill ever gets, still fully round" hack; the shared
	   --corner-radius value reaches that same full-pill look on its own
	   well before its own max (these pills are short), and now also lets
	   it go all the way down to a square pill at 0, matching every other
	   element this same field drives. */
	border-radius: var(--corner-radius);
	padding: 0.3em 0.9em;
	/* Was 1.4rem - trimmed ~14% (within the 10-15% site-wide reduction
	   asked for) across every pill list this class powers: single Work's
	   Services Rendered/Technologies Used, single Technology's Related
	   Services/Other Technologies, the About page's Our Expertise, and the
	   Team single's Skills column. Padding is in em, so it (and the
	   pill's overall size) shrinks right along with the text - nothing
	   else here needed to change to keep it looking proportional. */
	font-size: 1.2rem;
	color: var(--color-heading-safe);
}
/* "Linked pill" modifier - each pill is a real link (Related Services and
   Technologies Used on the single Project page, Skills on the About
   page), styled to inherit the plain pill look rather than default link
   blue/underline, with underline only on hover/focus as the interactive
   cue. Name is a holdover from its first use (Related Services) - kept
   rather than renamed across every file that already references it. */
.skills-list--services li { padding: 0; }
.skills-list--services li a {
	display: block;
	padding: 0.3em 0.9em;
	color: inherit;
	text-decoration: none;
}
.skills-list--services li a:hover,
.skills-list--services li a:focus-visible {
	text-decoration: underline;
}

/* About page's "Our Process" section (see agility_render_about_process_steps()
   in inc/process.php) - an arbitrary-length row of numbered steps (not
   fixed at 3, in case Listen/Adjust/Deliver ever grows a step), wrapping
   to fit narrower screens via auto-fit rather than a fixed column count.
   scroll-margin-top clears the sticky header (.site-header) when something
   links straight to #process (e.g. a Footer Menu custom link) - generous
   on purpose since the header's own height varies with Logo Size (Theme
   Settings > Header, 100-200px). */
.about-process { margin-top: var(--space-5); scroll-margin-top: 14rem; }
.about-process__heading { text-align: center; margin-bottom: var(--space-3); }
.about-process__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
	gap: var(--space-3);
	text-align: center;
}
.about-process__number {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 3.25rem;
	height: 3.25rem;
	margin-bottom: var(--space-2);
	/* Theme Settings > Accents > Corner Roundness (--corner-radius) - a
	   true circle well before that field's own max (52px wide, needs
	   just >=26px radius), same as every other small round element it
	   drives; see agility_theme_settings_style_overrides() in
	   inc/enqueue.php for the full list. */
	border-radius: var(--corner-radius);
	background: var(--color-process-number);
	color: var(--color-white);
	font-weight: 700;
	font-size: 1.35rem;
}
.about-process__step h3 { margin-bottom: var(--space-1); }
.about-process__step p { color: var(--color-text-muted); margin-bottom: 0; }

/* Capstone at the end of the About page's Skills list - a circular "+"
   bubble rather than another text pill, playing off the pill shape
   without pretending to be one more skill. Tertiary is the border/ring;
   the "+" glyph itself is Tertiary's own Light tint, tying the two
   together rather than using the unrelated heading color. For an
   Administrator it's also a real shortcut to add a new Technology (see
   page-about.php) - the <a> fills the whole circle so the entire bubble
   is clickable, not just the glyph; for everyone else it stays purely
   decorative. */
/* Hardcoded to Tertiary/Tertiary Light rather than a Theme Settings
   dropdown - this bubble is a minor, always-visible-but-rarely-noticed
   list-ending decoration, not worth a whole admin field of its own. */
.skills-list li.skills-list__more {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 2.75rem;
	height: 2.75rem;
	padding: 0;
	border: 0.125rem solid var(--color-brandTertiary);
	border-radius: 50%;
	background: var(--color-bg);
	color: var(--color-brandTertiaryLight);
	font-size: 1.6rem;
	font-weight: 700;
	line-height: 1;
}
.skills-list li.skills-list__more a {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	height: 100%;
	color: inherit;
	text-decoration: none;
}
.skills-list li.skills-list__more:has(a):hover {
	/* Tertiary's own Lightest tint - ties the hover state to the same
	   brand Tertiary color as this bubble's own border. */
	background: var(--color-brandTertiaryLightest);
	border-color: var(--color-heading-safe);
	cursor: pointer;
}

/* -------------------- Search results -------------------- */
.search-results__count { color: var(--color-text-muted); }
.search-results__group-heading { margin-top: var(--section-padding-block); }
.search-results__group-heading:first-of-type { margin-top: var(--space-3); }
.search-results__list { margin-top: var(--space-3); }
.search-results__list li { padding: var(--space-2) 0; border-bottom: 0.0625rem solid var(--color-border); }
.search-results__list li:first-child { padding-top: 0; }
.search-results__list h3 { margin-bottom: var(--space-1); }
.search-results__list p { color: var(--color-text-muted); margin: 0; }
.search-results__empty { text-align: center; padding-block: var(--space-3) var(--section-padding-block); }
.search-results__suggestions { display: flex; justify-content: center; gap: var(--space-2); flex-wrap: wrap; margin-top: var(--space-3); }

/* About page intro - column 1 (the bio text) of the 2-column grid it
   shares with the headshot/highlight column. Desktop-only: the grid
   collapses to a single stacked column on mobile (.grid--2 below 768px),
   where extra left padding would just look like an unbalanced margin
   rather than breathing room next to a second column. */
@media (min-width: 48.0625rem) {
	.about-intro__content { padding-left: var(--space-3); }
}

/* About page headshot - sized to 67% of its grid column (33% smaller than
   filling it) and centered within the resulting gap, so it doesn't
   dominate the layout next to the bio text. */
.about-headshot { width: 67%; margin-inline: auto; border-radius: var(--radius); }

/* About page's Highlighted Team Member spotlight (page-about.php,
   agility_render_highlighted_team_member() in inc/team.php) - takes the
   same right-column slot .about-headshot uses above, but fills the whole
   column (a full card, not a plain photo) rather than the 67%-width
   treatment. Links to the member's own profile like every other team
   photo on the site, so the hover lift matches .team-member's. */
.about-highlight {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	text-align: center;
	color: inherit;
	text-decoration: none;
	border-radius: var(--radius);
	overflow: hidden;
	background: var(--color-bg);
	padding: var(--space-3);
	min-height: 20rem;
	transition: transform 0.15s ease;
}
.about-highlight:hover, .about-highlight:focus-visible { transform: translateY(-0.1875rem); }
/* The page's own Featured Image, when set, becomes a backdrop behind the
   member's portrait instead of just disappearing - absolutely positioned
   to fill the card, with the scrim below darkening it enough for the
   name/role to stay readable over any photo (same reasoning as the Work
   card photo-background scrim, .card--work.card--has-image::before). */
.about-highlight__bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0; }
.about-highlight__scrim { position: absolute; inset: 0; z-index: 1; background: linear-gradient(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.55)); }
.about-highlight__content { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; }
/* --corner-radius (Theme Settings > Accents > Corner Roundness) - see
   .team-profile__photo's own comment further down for why every team
   member photo on the site shares this same value. */
.about-highlight__photo {
	width: 9rem;
	height: 9rem;
	object-fit: cover;
	border-radius: var(--corner-radius);
	border: 0.25rem solid var(--color-white);
	box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.25);
	margin-bottom: var(--space-2);
}
/* No headshot uploaded yet for this member - same circle-letter idea as
   the site's own fallback logo, rather than a broken image. */
/* No background here - Team Member Missing Image (About page sidebar, per-
   page now rather than a site-wide Theme Settings field) is applied as an
   inline style instead, right where it's resolved in
   agility_render_highlighted_team_member() (inc/team.php), since it's no
   longer a global value with anything to share via a :root custom
   property. */
.about-highlight__photo--fallback { display: flex; align-items: center; justify-content: center; color: var(--color-white); font-size: 3rem; font-weight: 700; }
.about-highlight__name { font-weight: 700; font-size: 1.3rem; }
.about-highlight__role { font-size: 0.95rem; margin-top: 0.125rem; }
/* Text color depends on whether there's a photo backdrop behind it - white
   reads against the scrim, but would vanish on the plain --color-bg card
   a member with no Featured Image on the About page falls back to. */
.about-highlight--has-bg .about-highlight__name,
.about-highlight--has-bg .about-highlight__role { color: var(--color-white); }
.about-highlight:not(.about-highlight--has-bg) .about-highlight__name { color: var(--color-heading-safe); }
.about-highlight:not(.about-highlight--has-bg) .about-highlight__role { color: var(--color-text-muted); }

/* Our Team section (page-about.php, agility_render_team_section()) - an
   auto-filling photo grid so the column count adapts to however many team
   members currently have a headshot, rather than a fixed 3-across grid
   leaving an awkward gap. Photos are already hard-cropped to a 1:1 square
   at upload time (agility-headshot image size, inc/setup.php); object-fit:
   cover here is just a safety net for any image referenced before that
   size was generated. Same rounded-corner radius as .about-headshot above,
   for a consistent headshot treatment across the page. */
/* Fixed 9rem tracks (not minmax(...,1fr)) + justify-content: center - with
   a stretchy 1fr max, a handful of members (this site currently has 2)
   would either balloon each photo up to fill the row or, with auto-fill,
   sit hugging the left edge with a wall of invisible empty tracks to their
   right. Fixed-size auto-fit tracks collapse away when there's no card to
   fill them, so justify-content can center just the real cards as one
   balanced group regardless of how many there are. Gap is a specific
   requested 3.75rem (60px), not one of the --space-* tokens (closest are
   --space-4 at 40px and --space-5 at 64px, neither of which is 60px). */
.team-grid { display: grid; grid-template-columns: repeat(auto-fit, 9rem); justify-content: center; gap: 3.75rem; margin-top: var(--space-3); margin-bottom: var(--space-3); }
/* Each card links to that member's own profile (single-team.php) - reset
   the usual link underline/color so it still reads as a plain card, with
   a subtle hover lift as the only hint it's clickable. */
.team-member { display: block; text-align: center; color: inherit; text-decoration: none; transition: transform 0.15s ease; }
.team-member:hover, .team-member:focus-visible { transform: translateY(-0.1875rem); }
/* Theme Settings > Accents > Corner Roundness (--corner-radius, not the
   site's usual fixed var(--radius)) - already a perfect square via
   aspect-ratio: 1/1 + object-fit: cover, so at --corner-radius's own max
   this clips into a true circle same as the old fixed border-radius: 50%
   did, with every value below that giving a rounded-square instead.
   Shared by both agility_render_team_member_card() grids (Our Team on
   the About page, and a single Technology page's Experts section), so
   both stay consistent. */
.team-member__photo { display: block; width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: var(--corner-radius); margin-bottom: var(--space-1); }
/* No Featured Image uploaded yet for this member - same circle-letter
   idea as .about-highlight__photo--fallback (Highlighted Team Member
   spotlight) and the site's own fallback logo, rather than leaving them
   out of the grid entirely. Layers on top of .team-member__photo above
   (same size/circle/spacing), just centering a letter instead of an
   <img> - font-size matches .about-highlight__photo--fallback's own
   3rem exactly, since both circles happen to already be the same 9rem
   diameter. Background color is Team Member Missing Image (About page
   sidebar field), applied inline right where it's resolved in
   agility_render_team_member_card() (inc/team.php), same as the
   Highlighted spotlight's own fallback. */
.team-member__photo--fallback { display: flex; align-items: center; justify-content: center; color: var(--color-white); font-size: 3rem; font-weight: 700; }
.team-member__name { font-weight: 600; color: var(--color-heading-safe); margin: 0; }
/* Position/Organization, same pairing as .team-profile__role (single Team
   profile) and .about-highlight__role (Highlighted Team Member spotlight) -
   muted and smaller than the name, secondary info rather than competing
   with it. */
.team-member__role { font-size: 0.85rem; color: var(--color-text-muted); margin: 0.125rem 0 0; }

/* Single Team Member page (single-team.php) - photo and info share a row
   like .page-header, stacking centered on narrow screens. */
.team-profile { display: flex; gap: var(--space-4); align-items: flex-start; margin-bottom: var(--space-3); }
/* Same --corner-radius as .team-member__photo (Our Team/Experts grids)
   and .about-highlight__photo (Highlighted Team Member spotlight) -
   every team member photo on the site stays consistent with each other,
   not just the grid ones - this is also the largest of the three
   (12rem/192px), which is what sets --corner-radius's own max (see
   inc/enqueue.php) - a true circle here only at that top value, every
   smaller team photo reaches its own true circle sooner. */
.team-profile__photo { width: 12rem; height: 12rem; object-fit: cover; border-radius: var(--corner-radius); flex-shrink: 0; }
/* Grows to fill the space between the fixed-width photo and the Skills
   column (margin-left: auto, see .team-profile__skills below) - needed now
   that the page's bio copy (.team-profile__bio) lives in here too, so a
   paragraph of prose has real room to wrap instead of being squeezed to
   whatever width the name/role/contact lines alone would need. min-width:0
   is the actual fix that lets it wrap at all - without it, a flex item's
   default min-width:auto refuses to shrink narrower than its widest
   unbreakable content (a long word/URL), which can force the row wider
   than the page instead. */
.team-profile__info { flex: 1 1 auto; min-width: 0; }
.team-profile__info h1 { margin-bottom: 0; }
.team-profile__role { font-size: 1.15rem; color: var(--color-text-muted); margin-top: var(--space-1); }
.team-profile__meta { color: var(--color-text-muted); font-size: 0.95rem; margin-top: var(--space-1); }
.team-profile__contact { display: flex; flex-direction: column; gap: 0.375rem; margin-top: var(--space-2); padding: 0; list-style: none; }
/* LinkedIn specifically gets its own brand icon-only badge - the exact
   same .social-icons__link/__glyph component the footer and Contact page
   use (agility_render_social_icon(), same inline-SVG library), not a
   one-off treatment, just sized up (1.5x the footer's own size - was 2x,
   reduced) since it's standing in for the link's entire label here - no
   "LinkedIn" text next to it, redundant once the recognizable badge is
   already there. Phone doesn't get an equivalent icon - it doesn't have
   an equally recognizable, unambiguous glyph of its own worth adding
   just for symmetry. */
.team-profile__social-link { width: 4rem; height: 4rem; margin-top: var(--space-1); }
.team-profile__social-link .social-icons__glyph { width: 2rem; height: 2rem; }
.team-profile__bio { margin-top: var(--space-2); }
/* Skills - a third column in the same row as the photo/contact info
   (margin-left: auto pushes it flush to the row's right edge regardless
   of how wide .team-profile__info ends up), rather than its own
   full-width section further down the page. Pills reuse the same
   .skills-list/.skills-list--services pill look used everywhere else
   (About page, single Project/Technology) - only the layout differs here:
   stacked one per line (flex-direction: column) instead of wrapping
   horizontally, each right-edge-aligned (align-items: flex-end) so a
   shorter pill doesn't look adrift from a longer one above/below it. */
.team-profile__skills { margin-left: auto; text-align: right; flex-shrink: 0; }
.team-profile__skills h2 { margin-bottom: var(--space-1); }
.team-profile__skills-list {
	flex-direction: column;
	flex-wrap: nowrap;
	align-items: flex-end;
}
@media (max-width: 48rem) {
	.team-profile { flex-direction: column; align-items: center; text-align: center; }
	.team-profile__contact { align-items: center; }
	.team-profile__skills { margin-left: 0; text-align: center; }
	.team-profile__skills-list { align-items: center; }
}

/* -------------------- Client Logos strip -------------------- */
/* Three parallel copies of the strip (.client-strip--mobile/-tablet/
   -desktop, each already pre-grouped into balanced rows server-side - see
   agility_render_client_logos_section(), inc/flexible-content.php, and
   agility_balanced_rows(), inc/post-types.php) - exactly one is ever
   visible at a time, picked by the min/max-width media queries below.
   This isn't "3x the logos" visually, just 3 different row-groupings of
   the same logos, since how many comfortably fit per row is a function of
   viewport width. A single flex-wrap container could reflow on its own,
   but a naive reflow can't guarantee no row ends up much shorter than the
   one above it (see agility_balanced_rows()'s own doc comment) - only a
   precomputed, fixed grouping can. */
.client-strip { display: none; flex-direction: column; align-items: center; gap: var(--space-3); margin-top: var(--space-3); }
.client-strip__row { display: flex; flex-wrap: nowrap; align-items: center; justify-content: center; gap: var(--space-4); }
.client-strip__logo { display: block; max-width: 100%; }
/* Bounded by both height AND width (a wide horizontal wordmark logo was
   only capped by height before, so it could grow arbitrarily wide and
   bleed past its row/container) - width/height: auto lets the browser
   scale it down to fit inside that box while keeping its own aspect
   ratio, whatever shape the logo happens to be. */
.client-strip__logo img { max-height: 7rem; max-width: 12rem; width: auto; height: auto; }
@media (max-width: 48rem) {
	.client-strip--mobile { display: flex; }
	.client-strip__logo img { max-height: 5rem; max-width: 8rem; }
}
@media (min-width: 48.0625rem) and (max-width: 64rem) {
	.client-strip--tablet { display: flex; }
}
@media (min-width: 64.0625rem) {
	.client-strip--desktop { display: flex; }
}

/* -------------------- FAQ accordion -------------------- */
/* Native <details>/<summary> - expand/collapse and keyboard support come
   from the browser, no JS needed. */
.faq { max-width: var(--content-width); margin-inline: auto; }
.faq__item { border-bottom: 0.0625rem solid var(--color-border); padding-block: var(--space-2); }
.faq__item:first-child { border-top: 0.0625rem solid var(--color-border); }
.faq__question {
	cursor: pointer;
	font-weight: 600;
	font-size: 1.15rem;
	list-style: none;
	position: relative;
	padding-right: var(--space-3);
}
.faq__question::-webkit-details-marker { display: none; }
.faq__question::after {
	content: '+';
	position: absolute;
	right: 0;
	top: 0;
	font-weight: 400;
	color: var(--color-text-muted);
}
.faq__item[open] .faq__question::after { content: '\2212'; }
.faq__answer { margin-top: var(--space-2); color: var(--color-faq-answer); }
.faq__answer > *:last-child { margin-bottom: 0; }

/* -------------------- Pie Chart -------------------- */
/* Single Service and single Technology pages (Theme Settings has nothing
   to do with this - the data is per-post, Pie Chart Text/Pie Chart Items
   in acf-json/group_service_fields.json and
   acf-json/group_technology_fields.json). Sits next to the FAQs -
   .service-faq-grid on single-service.php, .tech-related-grid's aside
   column on single-technology.php - see agility_render_pie_chart(),
   inc/pie-chart.php, for how the conic-gradient() below is actually built
   from the repeater. */
/* Pie Chart Text doubles as this section's title - sized/weighted like a
   heading (rather than a plain paragraph) so it actually reads as one
   next to the FAQ heading beside it. Centered, and capped narrower than
   the full container, so it reads as a distinct title sitting above the
   chart rather than a full-width line of body copy. */
.pie-chart__text { max-width: 20rem; margin: 0 auto var(--space-3); font-size: 1.35rem; font-weight: 700; color: var(--color-heading-safe); line-height: 1.25; text-align: center; }
/* Stacked - chart on top, legend below - rather than side by side, so the
   legend is free to use the full width of its container instead of being
   squeezed into a narrow column next to the circle. */
.pie-chart__body { display: flex; flex-direction: column; align-items: center; gap: var(--space-3); }
/* Fixed size rather than something proportional to the column - a pie
   chart doesn't get more readable by getting bigger past a certain point,
   and a fixed size keeps it visually consistent across every post that
   uses one, whatever else that column ends up containing. */
.pie-chart__circle { position: relative; flex: 0 0 auto; width: 14rem; height: 14rem; border-radius: 50%; }
/* Each slice's own percent, plotted at its midpoint angle by
   inc/pie-chart.php (agility_render_pie_chart()) as inline left/top
   percentages - translate(-50%, -50%) centers the text ON that point
   rather than starting a box there. White reads reliably against every
   brand color this can be set to (Theme Settings' named-color set is all
   mid-to-dark tones); text-shadow is a cheap way to keep it legible on
   the rare lighter tint (e.g. a Lightest color) without needing to
   compute per-slice contrast the way --color-header-text etc. do
   elsewhere. aria-hidden - the circle's own role="img" aria-label
   already covers every slice's label + percent for assistive tech, so
   these would just be duplicated. */
.pie-chart__slice-label { position: absolute; transform: translate(-50%, -50%); color: #fff; font-weight: 700; font-size: 0.85rem; text-shadow: 0 0.0625rem 0.125rem rgba(0, 0, 0, 0.35); pointer-events: none; }
/* One column - every slice stacks vertically rather than wrapping into
   multiple columns. Fixed width (same 20rem as .pie-chart__text above,
   so the title and legend read as one aligned column) rather than
   stretching to the container's own width - plain margin-inline: auto
   centers a fixed-width block with no JS measurement needed, unlike
   trying to center something that's sized by its own widest row of
   content. */
.pie-chart__legend { display: grid; grid-template-columns: 1fr; gap: var(--space-1); width: 20rem; max-width: 100%; margin-inline: auto; }
.pie-chart__legend li { display: flex; align-items: center; gap: 0.5rem; }
.pie-chart__swatch { flex: 0 0 auto; width: 0.9375rem; height: 0.9375rem; border-radius: 50%; }
/* Bumped from the browser default to 700 (matching .pie-chart__text's own
   weight) - a legend of slice names is easy to skim past at a lighter
   weight, particularly now that each one has a full row to itself. */
.pie-chart__legend-label { font-weight: 700; }
.pie-chart__legend-percent { font-weight: 600; color: var(--color-text-muted); }

/* Footer legal line - Privacy Policy link inherits the muted, dimmed
   color of the surrounding copyright text (.site-footer__bottom already
   sets opacity: 0.75) instead of the bright link color, so it reads as a
   quiet legal-line link rather than a prominent nav item. */
.site-footer__legal a { color: inherit; text-decoration: underline; }

/* Admin-only "+ Add ..." card (end of the Work grid / Services grid) -
   dashed border and a lighter, partly-transparent fill (rather than the
   solid .card-bg real cards use) distinguish it as a placeholder/utility
   slot at a glance, not a piece of real content. Shared by
   agility_render_add_project_card() and agility_render_add_service_card()
   (inc/post-types.php) - same look everywhere it's used. Hover uses
   --color-heading-safe (guaranteed readable against the card's light
   background) rather than the brand Secondary color directly - Secondary
   can be configured to a very pale tone (Theme Settings > Colors) that
   would otherwise make the hover state unreadable. */
/* Border stays hardcoded to the Tertiary family rather than a Theme
   Settings dropdown - this "+ Add Service/Project" card is admin-only
   (see agility_render_add_item_card(), inc/post-types.php), not worth an
   admin field of its own. */
.card--add {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--space-1);
	min-height: 12rem;
	background: color-mix(in srgb, var(--color-card-bg) 22%, white);
	border-style: dashed;
	border-width: 0.125rem;
	border-color: var(--color-brandTertiary);
	color: var(--color-text-muted);
}
.card--add:hover {
	/* Brand Grey (Lightest), by request - independent of the card's own
	   Tertiary border color above (kept as-is), not a tint derived from
	   it. */
	background: var(--color-brandGreyLightest);
	border-color: var(--color-heading-safe);
	color: var(--color-heading-safe);
	cursor: pointer;
	transform: translateY(-0.25rem);
}
.card--add__plus {
	font-size: 8rem;
	line-height: 1;
	font-weight: 200;
	color: var(--color-text-muted);
	opacity: 0.7;
	transition: opacity 0.15s ease, color 0.15s ease;
}
.card--add:hover .card--add__plus {
	color: var(--color-heading-safe);
	opacity: 1;
}
.card--add__label { font-weight: 600; letter-spacing: 0.02em; }
