/*
Theme Name: DFI2026 Child
Theme URI: https://dfiproductions.com
Template: dfi2026
Author: DFI Productions
Author URI: https://dfiproductions.com
Description: Child theme of DFI2026. Put site-specific customizations here so the parent theme stays clean and updatable.
Version: 1.0.0
Requires at least: 6.4
Requires PHP: 8.0
License: GNU General Public License v2 or later
License URI: LICENSE
Text Domain: dfi2026-child
*/

/* ---------------------------------------------------------------------------
 * Site-specific CSS goes below.
 *
 * This file loads AFTER the parent's style.css and after the parent's
 * generated inline CSS (the :root variable blocks from Typography, Top Bar,
 * Header Nav, Mobile Menu, Footer and Blog options), so a plain rule here
 * beats the parent at equal specificity -- no !important needed.
 *
 * To retune the theme's design tokens rather than override rules, redeclare
 * the variables:
 *
 *   :root {
 *       --dfi-footer-bg: #202a4d;
 *       --dfi-nav-hover-color: #cb2c31;
 *   }
 *
 * ------------------------------------------------------------------------ */

/*
 * Plain content page baseline (About Us, the 10 Services pages, and any
 * future page built the same way): section headings big/bold/uppercase
 * like the home page's own block headings (e.g. .dfi-image-text-columns
 * __text h2), and an explicit list indent since a plain core/list block
 * otherwise reads as flush-left. Scoped to .entry-content rather than a
 * specific page id so it applies to every page built this way, not just
 * one. Deliberately placed here, near the top of the file's site-specific
 * section rather than down with other page-specific rules -- anything
 * more specifically targeted further down this file (e.g. .dfi-why-
 * choose-us h2 on the home page, same h2/.entry-content specificity)
 * needs to still win by simple source-order, the same convention this
 * file's own intro comment describes for beating the parent theme.
 */
.entry-content h2,
.entry-content h3 {
	font-size: 2.5em;
	font-weight: 900;
	text-transform: uppercase;
	line-height: 1.1;
}

/* !important on the box-model properties here: this rule was already
   losing to something pushing the bullet markers to the left of the
   heading text above them (list-style-position:outside hangs the marker
   in the space padding-left provides -- with that space actually zero,
   the marker renders flush against, or past, the content edge instead).
   Rather than keep guessing at the exact competing rule, pin the values
   that actually matter for the marker's position outright. */
.entry-content ul {
	margin-left: 0 !important;
	padding-left: 2rem !important;
	list-style: disc outside !important;
}

.entry-content li {
	margin-bottom: 0.5em;
}

/*
 * Home page tagline band: full-width breakout (.dfi-full-bleed, from the
 * parent's style.css) directly below the hero -- content lives in page 6
 * ("Home")'s own post_content as a raw HTML block, since the front page
 * gets its H1 from its own editor content rather than a template (see
 * index.php's comment on $suppress_title).
 */
.dfi-home-tagline {
	/* margin-top/bottom only -- NOT the shorthand. .dfi-full-bleed (same
	   element) sets margin-left/right to center the breakout; a `margin:
	   0` shorthand here would win the tie (equal specificity, this rule
	   loads later) and reset those back to 0, canceling the centering
	   while leaving .dfi-full-bleed's width:100vw in place -- exactly
	   what caused it to bleed off only one side instead of centering. */
	margin-top: 0;
	margin-bottom: 2rem;
	padding: 1rem;
	background: #242424;
	color: #ffffff;
	font-size: 2vw;
	text-align: center;
	text-transform: uppercase;
	border-top: 2px solid #ffffff;
}

@media (max-width: 768px) {
	.dfi-home-tagline {
		font-size: 3vw;
	}
}

/*
 * Animated logo placeholder: while data-defer-src is still present (see
 * dfi2026_child_animated_logo() in functions.php), .custom-logo is holding
 * a 1x1 transparent GIF, which has no real aspect ratio and stretches to
 * fill the logo's reserved box -- visible as a blank box until it's swapped
 * for the real image on window load. Hiding it via the attribute itself
 * means this rule stops applying automatically the instant the swap script
 * removes data-defer-src, no extra class/JS needed.
 */
.custom-logo[data-defer-src] {
	opacity: 0;
}

.custom-logo {
	transition: opacity 0.3s ease;
}

/*
 * Two-row header (see header.php override): row 1 is social/logo/phone,
 * row 2 is the centered primary nav. Both live inside the same <header
 * class="site-header">, so the existing --dfi-header-bg background
 * (Header & Navigation options) paints both rows as one solid band --
 * no separate background needed here.
 */

.site-header__top {
	display: grid;
	/* auto center column, not flex space-between, so the logo stays
	   dead-center regardless of how wide the social/phone columns are. */
	grid-template-columns: 1fr auto 1fr;
	align-items: center;
	gap: 1rem;
	padding: 0.75rem 1.5rem;
	/* max-height (rather than height/auto, which can't be transitioned)
	   collapses this row clean off-screen once scrolled -- see
	   .is-scrolled below. --dfi-header-top-height is set from JS (see
	   assets/js/sticky-header.js) to this row's own real measured
	   height; 200px is only a pre-JS fallback, not a real target --
	   overshooting the real height here is what made the collapse/
	   expand transitions feel asymmetric. */
	max-height: var(--dfi-header-top-height, 200px);
	overflow: hidden;
	/* ease-out here (applies when EXPANDING -- see the .is-scrolled rule
	   below for the collapse direction's own transition): "ease" has a
	   pronounced slow-start, which on a max-height growing from 0 lands
	   right at the moment content should first become visible -- read as
	   a stall before it "catches up." ease-out starts at full speed
	   instead, so the reveal begins immediately; its deceleration only
	   happens once the row is already mostly shown, where it's not
	   noticeable. */
	transition: max-height 0.3s ease-out, padding 0.3s ease-out, opacity 0.3s ease-out;
}

.site-header__social {
	justify-self: start;
	display: flex;
	align-items: center;
	gap: 0.75rem;
	min-width: 0;
}

.site-header__social-link {
	display: inline-flex;
	color: var(--dfi-social-color, #ffffff);
	font-size: var(--dfi-social-size, 1.1rem);
	line-height: 1;
	text-decoration: none;
}

.site-header__phone {
	justify-self: end;
	display: flex;
	align-items: center;
	min-width: 0;
}

.site-header__phone-link {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	color: var(--dfi-phone-color, #ffffff);
	font-size: var(--dfi-phone-icon-size, 1.4rem);
	line-height: 1;
	text-decoration: none;
}

/* Number sits beside the icon on desktop; the icon above stays sized by
   --dfi-phone-icon-size (Top Bar options), the number by --dfi-phone-size/
   --dfi-phone-weight -- same vars the old .top-bar__phone-number used. */
.site-header__phone-number {
	font-size: var(--dfi-phone-size, 1rem);
	font-weight: var(--dfi-phone-weight, 400);
	white-space: nowrap;
	font-variant-numeric: lining-nums tabular-nums;
}

@media (max-width: 768px) {
	.site-header__phone-number {
		display: none;
	}
}

.site-branding {
	justify-self: center;
}

/* Full-width, centered nav row. .primary-menu already spaces its items
   with flexbox `gap` (--dfi-nav-gap, from the Header & Navigation
   options page) rather than per-item margin/padding, so centering this
   row is all that's needed here. */
.site-header__nav-row {
	display: flex;
	justify-content: center;
	width: 100%;
	padding: 0 1rem 1.5rem;
}

/* Sticky/scrolled collapse: parent's equivalent rule targets
   .site-header__inner, which this layout no longer uses. .is-scrolled
   always lands on .site-header itself (see assets/js/sticky-header.js),
   regardless of whether --sticky or --transparent-home triggered it, so
   one selector covers both. The top row (social/logo/phone) collapses
   away entirely, leaving just the nav row's own slight padding shrink. */
.site-header.is-scrolled .site-header__top {
	max-height: 0;
	padding-top: 0;
	padding-bottom: 0;
	opacity: 0;
	pointer-events: none;
	/* ease-in for collapsing (opposite direction from the base rule's
	   ease-out): its slow portion falls at the START, while the row is
	   still fully visible, so it's not noticeable; it then accelerates
	   into the actual disappearance, reading as snappy. */
	transition: max-height 0.3s ease-in, padding 0.3s ease-in, opacity 0.3s ease-in;
}

.site-header.is-scrolled .site-header__nav-row {
	padding-top: 0.4rem;
	padding-bottom: 0.4rem;
}

/*
 * Mobile header: a single row (logo, social, phone, hamburger) instead
 * of the desktop two-row layout, and never transparent/floating over
 * the hero video -- there isn't room on a small screen for the two-row
 * layout the transparent-home treatment was designed around.
 *
 * display:contents on .site-header__top and .site-header__nav-row
 * removes THEIR OWN boxes (so their padding/grid/flex properties stop
 * applying -- fine, since <header> below takes over) while promoting
 * their children to be direct flex children of <header> itself. That's
 * what lets branding/social/phone (previously one row) and the
 * hamburger (previously a different row) all share this single row
 * regardless of which desktop row they originally belonged to.
 * .main-navigation stays position:fixed (off-canvas) at this breakpoint
 * regardless of promotion, so it doesn't take part in or disturb this
 * row's layout.
 *
 * Sizes (logo_width_mobile, social_font_size_mobile,
 * phone_icon_font_size_mobile, toggle_size -- all vw-based) are the
 * Header & Navigation / Top Bar theme options, not hardcoded here, so
 * they stay adjustable from the admin the same way the rest of the
 * theme's sizing is -- these are starting values, meant to be retuned.
 */
@media (max-width: 768px) {
	.site-header__top,
	.site-header__nav-row {
		display: contents;
	}

	.site-header {
		display: flex;
		align-items: center;
		gap: 3vw;
		padding: 2vw 4vw;
	}

	.site-branding {
		order: 1;
		margin-right: auto;
	}

	.site-header__social {
		order: 2;
	}

	.site-header__phone {
		order: 3;
	}

	.menu-toggle {
		order: 4;
	}

	/* Always the normal solid header on mobile, regardless of scroll
	   position -- the floating/transparent treatment is desktop-only. */
	.site-header--transparent-home {
		position: sticky;
		top: 0;
		background: var(--dfi-header-bg, #ffffff);
		z-index: 900;
	}

	.site-header--transparent-home::before {
		content: none;
	}
}

/*
 * Transparent home header: before scrolling, .site-header--transparent-home
 * has no background of its own (it floats over the Vimeo hero), so the
 * white/light logo, nav, social and phone number have nothing to contrast
 * against. This adds a faint black-to-transparent gradient behind just the
 * header's own rows, fading out toward the bottom of the header -- gone
 * entirely once .is-scrolled hands off to the solid --dfi-header-bg fill.
 */
.site-header--transparent-home:not(.is-scrolled)::before {
	content: "";
	position: absolute;
	inset: 0;
	background: linear-gradient(to bottom, rgba(0, 0, 0, 0.35), rgba(0, 0, 0, 0) 100%);
	pointer-events: none;
	z-index: 0;
}

.site-header--transparent-home .site-header__top,
.site-header--transparent-home .site-header__nav-row {
	position: relative;
	z-index: 1;
}

/*
 * Top/bottom bar hover effect, matching the old site's nav (their Duda
 * "effect-top-bottom" style): a thin bar slides in from above and another
 * from below, both fading in, sandwiching the link text. Top-level items
 * only (not dropdown links), same scope as the old site's version.
 * currentColor means these follow --dfi-nav-color/--dfi-nav-hover-color
 * automatically, so if either changes later the bars stay in sync.
 */
.primary-menu > li > a {
	position: relative;
}

.primary-menu > li > a::before,
.primary-menu > li > a::after {
	content: "";
	position: absolute;
	left: 0;
	width: 100%;
	height: 2px;
	background: currentColor;
	opacity: 0;
	transition: transform 0.2s ease-out, opacity 0.2s ease-out;
}

.primary-menu > li > a::before {
	top: 0;
	transform: translate3d(0, -8px, 0);
}

.primary-menu > li > a::after {
	bottom: 0;
	transform: translate3d(0, 8px, 0);
}

.primary-menu > li > a:hover::before,
.primary-menu > li > a:hover::after,
.primary-menu > li > a:focus::before,
.primary-menu > li > a:focus::after {
	opacity: 1;
	transform: translate3d(0, 0, 0);
}

/*
 * Static home-page hero overlay (see header.php): a plain, always-visible
 * heading/subheading/button centered over the Vimeo video -- deliberately
 * NOT using the built-in Vimeo Slides system (inc/vimeo-shortcode.php),
 * since that's built around timed fade/slide transitions and this needs
 * to just sit there. .dfi-hero-overlay-wrap wraps only the vimeo render
 * output, so inset:0 on the overlay lines up exactly with the video's own
 * box regardless of its height mode (aspect ratio / viewport fill / vh).
 */
.dfi-hero-overlay-wrap {
	position: relative;
}

/* Flat, constant dimmer over the whole video -- separate from the
   header's own fade-out gradient above, which only darkens the strip
   behind the nav and disappears once scrolled. This one stays put. */
.dfi-hero-overlay-dim {
	position: absolute;
	inset: 0;
	z-index: 5;
	background: rgba(0, 0, 0, 0.15);
	pointer-events: none;
}

.dfi-hero-overlay {
	position: absolute;
	inset: 0;
	z-index: 6;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	text-align: center;
	padding: 2rem;
	pointer-events: none;
}

/* Nudges the content down so it centers in the space between the bottom
   of the (absolutely-positioned, floating) nav and the bottom of the
   video, rather than dead-center in the full video box behind the header.
   --dfi-hero-overlay-pad-top is set from JS (see
   dfi2026_child_hero_overlay_padding_script() in functions.php) to half
   the header's actual rendered height, since that varies with content/
   viewport width and can't be known in plain CSS. Desktop only -- the
   header's own layout is different enough on mobile that this isn't
   applied there. */
@media (min-width: 769px) {
	.dfi-hero-overlay {
		padding-top: var(--dfi-hero-overlay-pad-top, 2rem);
	}
}

.dfi-hero-overlay__title {
	margin: 0 0 1rem;
	font-size: 4vw;
	font-weight: 700;
	line-height: 1.15;
	color: #ffffff;
	text-shadow: 0 2px 10px rgba(0, 0, 0, 0.65);
}

.dfi-hero-overlay__subtitle {
	margin: 0 0 1.75rem;
	font-size: 2vw;
	line-height: 1.4;
	color: #ffffff;
	max-width: 60vw;
	text-shadow: 0 2px 8px rgba(0, 0, 0, 0.65);
}

/* Mobile: roughly double the desktop vw values -- at a narrow viewport
   width, the same vw number renders much smaller in absolute px than it
   does on desktop, so it needs its own larger value here rather than
   just inheriting the desktop one. Button intentionally untouched. */
@media (max-width: 768px) {
	.dfi-hero-overlay__title {
		font-size: 8vw;
	}

	.dfi-hero-overlay__subtitle {
		font-size: 4vw;
		max-width: none;
	}
}

/* Button: white text on black, same glossy-swipe shine as the CTA
   banner's button (inc/cta.php's .dfi-cta-anim-glossy-swipe), rebuilt
   here standalone since that stylesheet only loads on pages where the
   CTA banner itself is enabled. */
.dfi-hero-overlay__button {
	position: relative;
	overflow: hidden;
	pointer-events: auto;
	display: inline-block;
	padding: 0.9rem 2.25rem;
	background: #000000;
	color: #ffffff;
	font-size: 1.1rem;
	font-weight: 600;
	text-decoration: none;
	border-radius: 6px;
	transition: filter 0.15s ease;
}

.dfi-hero-overlay__button:hover,
.dfi-hero-overlay__button:focus-visible {
	filter: brightness(1.3);
}

.dfi-hero-overlay__button::after {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	width: 35%;
	height: 100%;
	background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.55) 50%, rgba(255, 255, 255, 0) 100%);
	animation: dfiHeroOverlayGlossySwipe 4s ease-in-out infinite;
	pointer-events: none;
}

@keyframes dfiHeroOverlayGlossySwipe {
	0%, 100% { transform: translateX(-150%) skewX(-20deg); }
	15%      { transform: translateX(250%) skewX(-20deg); }
}

@media (prefers-reduced-motion: reduce) {
	.dfi-hero-overlay__button::after {
		animation: none !important;
	}
}

/*
 * Vimeo + Text Columns block (inc/vimeo-text-columns-block.php): videos
 * stack vertically in a left column; text sits in a right column, left-
 * justified by default, vertically centered against the video stack's
 * full height. align-items:stretch (flex's own default on the row) is
 * what makes both columns share the taller side's height in the first
 * place; align-items:center on .dfi-vimeo-text-columns__content then
 * centers its own content within that shared height. Gaps/split below
 * are starting values -- meant to be retuned per instance.
 */
.dfi-vimeo-text-columns {
	display: flex;
	gap: 2rem;
	/* Only matters when Full Width Breakout is on -- .entry-content's own
	   left/right padding is deliberately skipped for .dfi-full-bleed
	   elements (see style.css's comment on that rule) so they can go
	   truly edge to edge. This puts a smaller inset back specifically
	   for this block, so the left video and the right-hand text don't
	   sit flush against the viewport edges. */
	padding-left: 2rem;
	padding-right: 2rem;
}

@media (min-width: 769px) {
	.dfi-vimeo-text-columns {
		gap: 4rem;
	}
}

.dfi-vimeo-text-columns__videos {
	/* --dfi-vtc-video-width set inline per instance (render_callback,
	   from the block's Video Column Width control); 50% is only the
	   pre-JS/fallback value. */
	flex: 0 0 var(--dfi-vtc-video-width, 50%);
	display: flex;
	flex-direction: column;
	gap: 1rem;
	min-width: 0;
}

.dfi-vimeo-text-columns__video {
	position: relative;
	width: 100%;
	/* aspect-ratio is set inline per instance (render_callback, from
	   dfi2026_child_vimeo_aspect_ratio()) to each video's own real
	   ratio -- a shared 16/9 here would pillarbox any video that isn't
	   actually 16:9 (e.g. a portrait video would get black bars down
	   both sides), making its visible content narrower than the others
	   even though the boxes were technically the same width. 16/9 is
	   still the fallback if that lookup ever fails. */
	aspect-ratio: 16 / 9;
}

.dfi-vimeo-text-columns__video iframe {
	position: absolute;
	inset: 0;
	/* !important: Vimeo's player.js (loaded sitewide for the hero
	   background video, inc/vimeo-shortcode.php) auto-detects any
	   player.vimeo.com iframe on the page and can set its own inline
	   width/height based on each video's native resolution. An inline
	   style always beats a plain external rule regardless of
	   specificity -- only !important overrides it -- which is what was
	   making the three videos different widths from each other instead
	   of all matching this column's width. */
	width: 100% !important;
	height: 100% !important;
	border: 0;
}

/*
 * Scroll-reveal: each video fades and slides in from the left the first
 * time it enters the viewport (see dfi2026_child_vimeo_reveal_script()
 * in functions.php, which adds .is-visible via IntersectionObserver).
 * Desktop only -- on mobile the videos stack in normal document flow
 * near the top of the page, where a slide-in reads as more of a delay
 * than an effect. A small per-item transition-delay staggers the three
 * videos instead of having them all animate in at once.
 */
@media (min-width: 769px) {
	.dfi-vimeo-text-columns__video {
		opacity: 0;
		transform: translateX(-40px);
		transition: opacity 0.6s ease-out, transform 0.6s ease-out;
	}

	.dfi-vimeo-text-columns__video.is-visible {
		opacity: 1;
		transform: translateX(0);
	}

	.dfi-vimeo-text-columns__video:nth-child(2) {
		transition-delay: 0.1s;
	}

	.dfi-vimeo-text-columns__video:nth-child(3) {
		transition-delay: 0.2s;
	}

	/* Same reveal, applied to the headline, the bold subtext, and each
	   body paragraph individually -- same JS observer as the videos
	   (dfi2026_child_vimeo_reveal_script() in functions.php), just a
	   second selector added to its query. */
	.dfi-vimeo-text-columns__text > * {
		opacity: 0;
		transform: translateX(40px);
		transition: opacity 0.6s ease-out, transform 0.6s ease-out;
	}

	.dfi-vimeo-text-columns__text > *.is-visible {
		opacity: 1;
		transform: translateX(0);
	}

	.dfi-vimeo-text-columns__text > *:nth-child(2) {
		transition-delay: 0.1s;
	}

	.dfi-vimeo-text-columns__text > *:nth-child(3) {
		transition-delay: 0.2s;
	}

	.dfi-vimeo-text-columns__text > *:nth-child(4) {
		transition-delay: 0.3s;
	}

	.dfi-vimeo-text-columns__text > *:nth-child(n + 5) {
		transition-delay: 0.4s;
	}
}

@media (prefers-reduced-motion: reduce) {
	.dfi-vimeo-text-columns__text > * {
		opacity: 1 !important;
		transform: none !important;
		transition: none !important;
	}
}

@media (prefers-reduced-motion: reduce) {
	.dfi-vimeo-text-columns__video {
		opacity: 1 !important;
		transform: none !important;
		transition: none !important;
	}
}

.dfi-vimeo-text-columns__content {
	flex: 1 1 auto;
	display: flex;
	align-items: center;
	min-width: 0;
	padding-top: 0;
}

.dfi-vimeo-text-columns__text {
	width: 100%;
	text-align: left;
	padding-top: 0;
}

.dfi-vimeo-text-columns__text h2 {
	margin-top: 0;
	margin-bottom: 0;
	font-size: 3.5em;
	line-height: 1;
	text-transform: uppercase;
	/* 900 (Black) -- the heaviest weight actually self-hosted for the
	   site's H2 font (Montserrat), see wp-content/uploads/dfi2026-fonts/
	   montserrat/. */
	font-weight: 900;
}

/* The bold subtext paragraph immediately follows the headline in the
   authored content (see page 6's block content), so this targets it
   structurally rather than needing its own wrapper class. */
.dfi-vimeo-text-columns__text h2 + p {
	font-size: 2em;
	color: #656262;
}

/*
 * Home page only, desktop only: this specific instance's 3 videos are 2
 * normal widescreen ones (1st and 3rd in the block's list) plus one tall
 * portrait video (2nd) that was taking up too much vertical room in a
 * plain top-to-bottom stack. Rearranged visually (nth-child, not a DOM
 * reorder) into the tall video on the left spanning both rows, with the
 * two widescreen videos stacked on the right beside it.
 *
 * grid-auto-rows:auto (not 1fr) is what lets each video keep respecting
 * its own real aspect-ratio (set inline per-video by render_callback)
 * instead of being stretched to fill an evenly-split row -- Grid's
 * auto-track-sizing correctly accounts for the spanning left video when
 * sizing both rows, unlike a fixed 1fr split would.
 *
 * Scoped to this page (body.page-id-6) rather than the block generally,
 * since this arrangement assumes exactly this 2-widescreen-plus-1-tall
 * video mix -- a different instance elsewhere with a different video mix
 * should keep the block's normal top-to-bottom stack.
 */
@media (min-width: 769px) {
	body.page-id-6 .dfi-vimeo-text-columns__videos {
		/* .dfi-vimeo-text-columns is a flex row with the default
		   align-items:stretch, which was stretching this whole grid
		   wrapper to match the text column's height (the long body copy)
		   rather than its own natural, aspect-ratio-driven height. That
		   extra space had nowhere sensible to go inside an auto-sized
		   grid, and combined with video 3's align-self:end below, showed
		   up as a growing gap between the two wide videos -- worse at
		   narrower widths, where the text wraps onto more lines (taller)
		   while the video area itself shrinks (shorter), widening the
		   mismatch. align-self:start opts this item out of stretch
		   entirely, so it's always exactly as tall as its own content,
		   pinned to the top of the row alongside the taller text column
		   -- keeps the videos visible near the top on narrower widths,
		   rather than getting pushed further down as centering would. */
		align-self: start;
		display: grid;
		/* minmax(0, 1fr), not a bare 1fr: a plain 1fr track has an
		   implicit auto minimum based on its content's intrinsic size --
		   a <iframe> with no width/height HTML attributes has a browser
		   default intrinsic size of 300x150px that our CSS width:100%/
		   height:100% (percentages) don't count for that calculation.
		   That was letting the grid grow past this column's real 50%
		   flex-basis instead of shrinking to fit it, pushing the text
		   column off the page. minmax(0, 1fr) removes that floor.

		   The 1 / 1.575 split: video 2 (tall) is exactly the transposed
		   dimensions of videos 1 and 3 (240x426 vs 426x240), so height is
		   just width times the inverse ratio for each. Solving width_tall
		   * (426/240) = 2 * width_wide * (240/426) for width_tall /
		   width_wide gives ~1 : 1.575 -- that's what makes the tall
		   video's full height line up with both wide videos stacked
		   (ignoring the small fixed 1rem row-gap between them, which
		   doesn't scale proportionally with the columns, so this is exact
		   at one specific container width and very close at others). */
		grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
		/* minmax(0, auto), not a bare auto -- same fix as the columns
		   above and for the same reason: a bare auto row also carries an
		   implicit content-based minimum, which the iframes' default
		   300x150 intrinsic size was inflating past what the aspect-
		   ratio math actually calls for, showing up as a gap between the
		   two wide videos. */
		grid-auto-rows: minmax(0, auto);
		gap: 1rem;
		min-width: 0;
	}

	body.page-id-6 .dfi-vimeo-text-columns__video:nth-child(1) {
		grid-column: 2;
		grid-row: 1;
	}

	body.page-id-6 .dfi-vimeo-text-columns__video:nth-child(2) {
		grid-column: 1;
		grid-row: 1 / 3;
	}

	body.page-id-6 .dfi-vimeo-text-columns__video:nth-child(3) {
		grid-column: 2;
		grid-row: 2;
		/* The tall video spanning both rows can force row 2 taller than
		   this video's own natural (aspect-ratio) height, leaving empty
		   space in its cell. align-self:end sinks it to the bottom of
		   that cell instead of the default top, lining its bottom edge
		   up with the tall video's. */
		align-self: end;
	}
}

/* Home page gallery (inc/gallery.php): the parent's default
   .dfi-gallery__item is a 1/1 square crop; this instance's thumbs are
   landscape instead. object-fit:cover on .dfi-gallery__thumb (parent
   CSS) still handles the actual cropping to this new ratio. */
[data-gallery-instance="home-gallery"].dfi-gallery .dfi-gallery__item {
	aspect-ratio: 16 / 9;
	/* box-sizing:border-box keeps the border inside the item's existing
	   footprint (grid cell size, aspect-ratio) instead of adding to it. */
	box-sizing: border-box;
	border: 2px solid #ffffff;
}

/* Being full-bleed, this doesn't get the usual automatic top/bottom
   spacing regular (non-full-bleed) blocks get from .entry-content's own
   padding rules -- adding it back here specifically for this instance. */
[data-gallery-instance="home-gallery"].dfi-gallery {
	margin-top: 2rem;
	margin-bottom: 2rem;
	background: #242424;
}

@media (min-width: 769px) {
	[data-gallery-instance="home-gallery"].dfi-gallery {
		margin-top: 4rem;
		margin-bottom: 4rem;
	}
}

/* Second home page gallery strip (below the Image + Text Columns block) --
   same treatment as the home-gallery instance above, just re-scoped to
   this instance's own data attribute. */
[data-gallery-instance="home-gallery-2"].dfi-gallery .dfi-gallery__item {
	aspect-ratio: 16 / 9;
	box-sizing: border-box;
	border: 2px solid #ffffff;
}

[data-gallery-instance="home-gallery-2"].dfi-gallery {
	margin-top: 2rem;
	margin-bottom: 2rem;
	background: #242424;
}

@media (min-width: 769px) {
	[data-gallery-instance="home-gallery-2"].dfi-gallery {
		margin-top: 4rem;
		margin-bottom: 4rem;
	}
}

/*
 * "Home Services" button row (inc/button-rows.php): restyled from the
 * parent's default (full-bleed, dimmed image with text overlaid on top
 * of it) into white rounded cards -- photo on top, a plain heading below
 * it on a white background, and a circular arrow button straddling the
 * boundary between the two. The parent component has no such button;
 * it's added here as a ::after pseudo-element using Font Awesome's
 * arrow-right glyph (already loaded sitewide) rather than editing the
 * parent's PHP to add a real element.
 *
 * Scoped to this specific instance (data-instance="home-services") via
 * attribute selector, so it doesn't change the look of any other Button
 * Row elsewhere on the site.
 */
.dfi-button-row[data-instance="home-services"] {
	/* Full-width breakout -- the parent's Button Row component has no
	   built-in toggle for this (unlike Gallery/Landing Cards/etc.), so
	   this reimplements .dfi-full-bleed's own technique (see that rule
	   in the parent's style.css) directly, since a CSS rule can't add
	   the class itself to the shortcode's rendered wrapper. */
	width: var(--dfi-viewport-width, 100vw);
	margin-left: calc(50% - (var(--dfi-viewport-width, 100vw) / 2));
	margin-right: calc(50% - (var(--dfi-viewport-width, 100vw) / 2));

	/* height:auto, not a fixed value -- each item's own aspect-ratio:1/1
	   below is what gives the row real height now, sized off each
	   item's own (flex-distributed) width rather than a hardcoded px/vh
	   number. */
	height: auto;
	gap: 1.5rem;
	align-items: stretch;
}

.dfi-button-row[data-instance="home-services"] .dfi-button-row__item {
	aspect-ratio: 1 / 1;
	flex-direction: column;
	align-items: stretch;
	justify-content: flex-start;
	background: #ffffff;
	box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
	border-left: 0;
}

/* Image fills only the top ~78% of the card, inset slightly on three
   sides so a sliver of the white card shows around it -- the parent's
   own rule covers the item's full box (inset:0) instead. */
.dfi-button-row[data-instance="home-services"] .dfi-button-row__bg,
.dfi-button-row[data-instance="home-services"] .dfi-button-row__dim {
	inset: 10px 10px auto 10px;
	height: 78%;
	border-radius: calc(var(--dfi-br-radius, 12px) - 6px);
}

/* Pinned to the bottom slice the same way .dfi-button-row__bg is pinned
   to the top slice, rather than relying on flex flow: .bg/.dim are both
   position:absolute (out of flow), which made this the ONLY in-flow
   child of the column -- flex:1 1 auto was expanding it to fill the
   entire card and centering the text in the middle of that, landing
   right on top of the image instead of in a separate strip below it. */
.dfi-button-row[data-instance="home-services"] .dfi-button-row__content {
	position: absolute;
	inset: auto 0 0 0;
	height: 22%;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0.5rem 1rem;
	z-index: 1;
}

.dfi-button-row[data-instance="home-services"] .dfi-button-row__h2 {
	font-weight: 700;
}

/* The circle + arrow button from the screenshot -- straddles the
   boundary between the image and the white text area below it. */
.dfi-button-row[data-instance="home-services"] .dfi-button-row__item::after {
	content: "\f061";
	font-family: "Font Awesome 7 Free";
	font-weight: 900;
	position: absolute;
	top: 78%;
	left: 50%;
	transform: translate(-50%, -50%);
	width: 3vw;
	height: 3vw;
	border-radius: 50%;
	background: #1a1a1a;
	color: #ffffff;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 1.1vw;
	z-index: 2;
	/* White outline ring: box-shadow (not a real border) so it follows
	   the circle's own border-radius instead of squaring off the
	   corners the way `border` or `outline` would need extra
	   border-radius bookkeeping to avoid. */
	box-shadow: 0 0 0 5px #ffffff;
	transition: transform 0.2s ease;
}

.dfi-button-row[data-instance="home-services"] .dfi-button-row__item:hover::after,
.dfi-button-row[data-instance="home-services"] .dfi-button-row__item:focus-visible::after {
	transform: translate(-50%, -50%) scale(1.1);
}

/*
 * Scroll-reveal, staggered across the 4 cards (fade + slide up), same
 * mechanism as the Vimeo + Text Columns block above (JS added in
 * dfi2026_child_button_row_reveal_script(), functions.php). Placed
 * BEFORE the hover-grow rule below rather than after: both this rule's
 * .is-visible and that rule's :hover ultimately resolve to the same
 * specificity on this element, so whichever is later in the file wins a
 * tie -- putting hover later means it still wins (scale, not
 * translateY(0)) once a card is both revealed and hovered at the same
 * time, instead of the reveal's resting transform overriding it.
 */
.dfi-button-row[data-instance="home-services"] .dfi-button-row__item {
	opacity: 0;
	transform: translateY(40px);
	transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}

.dfi-button-row[data-instance="home-services"] .dfi-button-row__item.is-visible {
	opacity: 1;
	transform: translateY(0);
}

.dfi-button-row[data-instance="home-services"] .dfi-button-row__item:nth-child(2) {
	transition-delay: 0.1s;
}

.dfi-button-row[data-instance="home-services"] .dfi-button-row__item:nth-child(3) {
	transition-delay: 0.2s;
}

.dfi-button-row[data-instance="home-services"] .dfi-button-row__item:nth-child(4) {
	transition-delay: 0.3s;
}

@media (prefers-reduced-motion: reduce) {
	.dfi-button-row[data-instance="home-services"] .dfi-button-row__item {
		opacity: 1 !important;
		transform: none !important;
		transition: none !important;
	}
}

/* Whole-card grow on hover instead of just the inner photo: the parent's
   own hover rule (.dfi-button-row__item:hover .dfi-button-row__bg {
   transform: scale(1.08) }) only scales the image layer, clipped inside
   this item's overflow:hidden -- reset that back to a no-op here, and
   scale the card itself instead. z-index bump lifts it above its
   siblings while enlarged, since transform doesn't reflow layout and
   would otherwise render under/behind the neighboring cards' edges.
   (transition itself is already declared on this same selector by the
   scroll-reveal rule above -- redeclaring it here would just overwrite
   that one instead of merging with it, since it's an identical
   selector.) */
.dfi-button-row[data-instance="home-services"] .dfi-button-row__item:hover,
.dfi-button-row[data-instance="home-services"] .dfi-button-row__item:focus-visible {
	transform: scale(1.05);
	z-index: 3;
}

.dfi-button-row[data-instance="home-services"] .dfi-button-row__item:hover .dfi-button-row__bg,
.dfi-button-row[data-instance="home-services"] .dfi-button-row__item:focus-visible .dfi-button-row__bg {
	transform: none;
}

/* Mobile: cards stack one per row (still 1:1, but each is now nearly
   full viewport width instead of ~1/4 of it), so both the heading and
   the arrow circle need to scale up to match rather than reusing the
   desktop vw values, which would read as tiny on a much bigger card.
   !important on the heading specifically: its font-size comes from an
   inline custom property (--dfi-br-h2-size, set via the Button Rows
   admin field), and an inline style always beats a plain external rule
   regardless of specificity -- same reason the Vimeo iframe sizing
   fix earlier in this file needed !important too. The arrow circle
   isn't inline-styled, so no !important needed there. */
@media (max-width: 768px) {
	.dfi-button-row[data-instance="home-services"] .dfi-button-row__h2 {
		font-size: 4vw !important;
	}

	.dfi-button-row[data-instance="home-services"] .dfi-button-row__item::after {
		width: 6vw;
		height: 6vw;
		font-size: 2.2vw;
	}
}

@media (max-width: 768px) {
	.dfi-vimeo-text-columns {
		flex-direction: column;
	}

	.dfi-vimeo-text-columns__videos {
		flex: 0 0 auto;
		width: 100%;
	}
}

/*
 * Image + Text Columns block (inc/image-text-columns-block.php): the
 * image-based sibling of the Vimeo + Text Columns block above, with the
 * columns swapped -- text sits in a left column, images stack vertically
 * (cropped to 16:9, small gap between each) in a right column, vertically
 * centered against the text's full height. Same flex row + align-items:
 * stretch/center technique as the Vimeo version.
 */
.dfi-image-text-columns {
	display: flex;
	gap: 2rem;
	padding-left: 2rem;
	padding-right: 2rem;
}

@media (min-width: 769px) {
	.dfi-image-text-columns {
		gap: 4rem;
	}
}

.dfi-image-text-columns__content {
	flex: 1 1 auto;
	display: flex;
	align-items: center;
	min-width: 0;
}

.dfi-image-text-columns__text {
	width: 100%;
	text-align: left;
}

.dfi-image-text-columns__text h2 {
	margin-top: 0;
	margin-bottom: 0;
	font-size: 3.5em;
	line-height: 1;
	text-transform: uppercase;
	font-weight: 900;
}

.dfi-image-text-columns__text h2 + p {
	font-size: 2em;
	color: #656262;
}

.dfi-image-text-columns__images {
	/* --dfi-itc-image-width set inline per instance (render_callback,
	   from the block's Image Column Width control); 50% is only the
	   pre-JS/fallback value. */
	flex: 0 0 var(--dfi-itc-image-width, 50%);
	display: flex;
	flex-direction: column;
	gap: 1rem;
	min-width: 0;
}

.dfi-image-text-columns__image {
	position: relative;
	width: 100%;
	/* Cropped to 16:9 regardless of the source photo's own proportions --
	   object-fit:cover on the <img> below fills this box without
	   distorting, cropping off whatever doesn't fit instead. */
	aspect-ratio: 16 / 9;
	overflow: hidden;
}

.dfi-image-text-columns__image img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	border: 0;
	display: block;
}

/*
 * Scroll-reveal: same IntersectionObserver mechanism as the Vimeo + Text
 * Columns block (dfi2026_child_image_text_columns_reveal_script() in
 * functions.php), but mirrored -- the text column slides in from the
 * left, the images slide in from the right, since the columns themselves
 * are swapped here. Desktop only, same reasoning as the Vimeo version
 * (a slide-in reads as a delay rather than an effect once these are
 * already stacked in normal mobile document flow).
 */
@media (min-width: 769px) {
	.dfi-image-text-columns__text > * {
		opacity: 0;
		transform: translateX(-40px);
		transition: opacity 0.6s ease-out, transform 0.6s ease-out;
	}

	.dfi-image-text-columns__text > *.is-visible {
		opacity: 1;
		transform: translateX(0);
	}

	.dfi-image-text-columns__text > *:nth-child(2) {
		transition-delay: 0.1s;
	}

	.dfi-image-text-columns__text > *:nth-child(3) {
		transition-delay: 0.2s;
	}

	.dfi-image-text-columns__text > *:nth-child(4) {
		transition-delay: 0.3s;
	}

	.dfi-image-text-columns__text > *:nth-child(n + 5) {
		transition-delay: 0.4s;
	}

	.dfi-image-text-columns__image {
		opacity: 0;
		transform: translateX(40px);
		transition: opacity 0.6s ease-out, transform 0.6s ease-out;
	}

	.dfi-image-text-columns__image.is-visible {
		opacity: 1;
		transform: translateX(0);
	}

	.dfi-image-text-columns__image:nth-child(2) {
		transition-delay: 0.1s;
	}

	.dfi-image-text-columns__image:nth-child(3) {
		transition-delay: 0.2s;
	}
}

@media (prefers-reduced-motion: reduce) {
	.dfi-image-text-columns__text > *,
	.dfi-image-text-columns__image {
		opacity: 1 !important;
		transform: none !important;
		transition: none !important;
	}
}

@media (max-width: 768px) {
	.dfi-image-text-columns {
		flex-direction: column;
	}

	.dfi-image-text-columns__images {
		flex: 0 0 auto;
		width: 100%;
	}
}

/*
 * Undoes the parent's mobile treatment for this specific instance only:
 * by design (see the parent's own comment on that @media (max-width:
 * 768px) block in style.css), Image Text Panel drops its background
 * photo entirely on mobile and falls back to plain text on white --
 * meant for the block's original photo-teaser use case, where there's
 * no room for text to sit legibly over an arbitrary image on a small
 * screen. This hero is different: a solid, opaque box (not text
 * directly over the photo), so the same problem doesn't apply, and it
 * should just keep looking like the desktop version, scaled down.
 */
@media (max-width: 768px) {
	.dfi-button-row[data-instance="home-services"] + .dfi-image-text-panel {
		display: flex;
		align-items: flex-end;
		min-height: 70vh;
		background-color: #f4f4f4;
		background-image: var(--dfi-image-text-panel-bg, none);
		background-size: cover;
		background-position: var(--dfi-itp-bg-position, center);
	}

	.dfi-button-row[data-instance="home-services"] + .dfi-image-text-panel .dfi-image-text-panel__box {
		max-width: none;
		width: 90%;
		margin: 0 auto -3rem;
		padding: 1.5rem;
		background: var(--dfi-itp-box-bg-color, rgba(255, 255, 255, 0.88));
		border-radius: var(--dfi-itp-box-radius, 12px);
	}

	.dfi-button-row[data-instance="home-services"] + .dfi-image-text-panel .dfi-image-text-panel__heading {
		width: auto;
		margin: 0 0 0.5em;
		padding: 0;
		background: transparent;
		border-radius: 0;
	}
}

/*
 * Image Text Panel block: left-aligns the heading and drops its own
 * padding, matching the "years in business" hero on the home page,
 * where headingBgColor is set to transparent so the heading just reads
 * as the top line of the box's own text rather than a separate colored
 * band. NOTE: this block has no per-instance selector the way Button
 * Rows/Gallery do (data-instance / data-gallery-instance), so this
 * applies to every use of Image Text Panel site-wide, not just this
 * one -- fine while this is the only instance, but worth knowing if the
 * block gets used elsewhere later expecting the parent's default
 * centered heading.
 */
.dfi-image-text-panel__heading {
	padding: 0 0 0.5em;
	text-align: left;
}

/* Styled as an actual button (light grey, dark text for contrast)
   rather than a plain inherited-color text link, with the same
   hover-grow + glossy-swipe shine as the CTA banner button (inc/cta.php)
   and the home hero's own button (.dfi-hero-overlay__button below) --
   reuses that same @keyframes rule rather than redefining it. position:
   relative + overflow:hidden is what contains the shine sweep to the
   button's own rounded box instead of spilling outside it. */
.dfi-image-text-panel__text a {
	display: inline-block;
	position: relative;
	overflow: hidden;
	padding: 0.6em 1.5em;
	background: #d9d9d9;
	color: #1a1a1a;
	border-radius: 4px;
	text-decoration: none;
	transition: transform 0.2s ease, filter 0.15s ease;
}

.dfi-image-text-panel__text a:hover,
.dfi-image-text-panel__text a:focus-visible {
	transform: scale(1.08);
	filter: brightness(1.05);
}

.dfi-image-text-panel__text a::after {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	width: 35%;
	height: 100%;
	background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.7) 50%, rgba(255, 255, 255, 0) 100%);
	animation: dfiHeroOverlayGlossySwipe 4s ease-in-out infinite;
	pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
	.dfi-image-text-panel__text a::after {
		animation: none !important;
	}
}

/* Scoped via the adjacent-sibling combinator (this hero directly follows
   the Home Services button row in page 6's content) rather than a bare
   .dfi-image-text-panel selector, so -- unlike the two rules above --
   this doesn't touch any other future use of the block elsewhere. */
.dfi-button-row[data-instance="home-services"] + .dfi-image-text-panel {
	margin-top: 3rem;
	/* margin, not padding: padding-bottom would add to the panel's own
	   content box, which shifts align-self:flex-end's reference point
	   up by the same amount AND stretches the cover background photo to
	   fill that extra space -- both exactly canceling out the box's
	   -3rem overhang below, undoing it entirely. margin adds the space
	   after the element instead, leaving the panel's own box (and the
	   overhang relative to it) untouched. */
	margin-bottom: 3rem;
}

/* Staggers the box so it hangs off the bottom edge of the photo instead
   of sitting fully inside it: align-self:flex-end bottom-aligns it
   (overriding the panel's own align-items:center), then a negative
   margin-bottom pulls it further down, past the panel's own bottom
   edge, onto the plain page background below. */
.dfi-button-row[data-instance="home-services"] + .dfi-image-text-panel .dfi-image-text-panel__box {
	align-self: flex-end;
	margin-bottom: -3rem;
}

/*
 * "Why Families Choose Patio Paradise" section (home page, core/html
 * block): plain centered text, kept to the normal content width -- no
 * .dfi-full-bleed class, so it just inherits .entry-content's usual
 * side padding/max-width like any other regular block.
 */
.dfi-why-choose-us {
	text-align: center;
	margin-top: 3rem;
	margin-bottom: 3rem;
}

.dfi-why-choose-us h2 {
	margin-top: 0;
	font-size: 2.75em;
	line-height: 1.1;
	text-transform: uppercase;
	font-weight: 900;
}

.dfi-why-choose-us__badge-link {
	display: inline-block;
	margin-top: 1.5rem;
	transition: transform 0.3s ease;
}

.dfi-why-choose-us__badge-link:hover,
.dfi-why-choose-us__badge-link:focus-visible {
	transform: scale(1.08);
}

.dfi-why-choose-us__badge {
	width: 130px;
	height: auto;
	display: block;
}

@media (prefers-reduced-motion: reduce) {
	.dfi-why-choose-us__badge-link {
		transition: none;
	}
}

/*
 * Pricing Packages (inc/packages-block.php): a vertical stack of package
 * sections, each a photo + text side by side, alternating sides every
 * other package (nth-of-type(even) flips the row direction) so a long
 * list of packages doesn't read as a monotonous single-column repeat --
 * purely automatic, no per-package setting needed.
 */
.dfi-packages {
	display: flex;
	flex-direction: column;
	gap: 3rem;
	margin-top: 2rem;
	margin-bottom: 2rem;
}

/* Only matters when the block's own Full Width Breakout toggle is on --
   .entry-content's own left/right padding is deliberately skipped for
   .dfi-full-bleed elements (see style.css's comment on that rule) so they
   can go truly edge to edge. This puts a smaller inset back specifically
   for this block, so each package's photo/text doesn't sit flush against
   the viewport edges -- same convention as the Vimeo + Text Columns
   block's own Full Width Breakout. */
.dfi-packages.dfi-full-bleed {
	padding-left: 2rem;
	padding-right: 2rem;
	box-sizing: border-box;
}

/*
 * Each package is its own card now (light grey outline on all sides)
 * rather than a plain divided list -- replaces the earlier top-border-only
 * divider between items. position:relative + a will-change hint since
 * this is also the element the scroll-focus effect below transforms/
 * elevates; the transition lives here so it covers both the focus scale
 * and any hover someone adds later.
 */
.dfi-package-item {
	display: flex;
	flex-direction: column;
	gap: 2rem;
	border: 1px solid #a0a0a0;
	border-radius: 8px;
	padding: 2rem;
	box-sizing: border-box;
	background: #ffffff;
	position: relative;
	transition: transform 0.4s ease, box-shadow 0.4s ease;
}

/*
 * "Package in focus" scroll effect (dfi2026_child_package_focus_script()
 * in functions.php): as the page scrolls, whichever card's center sits
 * closest to the viewport's vertical center gets .is-package-focused,
 * and grows + lifts with a shadow -- a transform, not an actual width/
 * layout change, so it "breaks out" visually (scaling past its own
 * column's edges into the surrounding margin) without reflowing its
 * neighbors or triggering horizontal scroll. Desktop only: on a mobile
 * single-column layout there's no margin on either side for a scaled-up
 * card to break out into, so the effect would just feel like inconsistent
 * jitter.
 */
@media (min-width: 769px) {
	.dfi-package-item.is-package-focused {
		transform: scale(1.04);
		box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
		border-color: #83be42;
		z-index: 2;
	}
}

@media (prefers-reduced-motion: reduce) {
	.dfi-package-item {
		transition: none;
	}

	.dfi-package-item.is-package-focused {
		transform: none;
		box-shadow: none;
	}
}

@media (min-width: 769px) {
	.dfi-package-item {
		flex-direction: row;
		align-items: center;
	}

	.dfi-package-item:nth-of-type(even) {
		flex-direction: row-reverse;
	}
}

.dfi-package-item__media {
	flex: 0 0 auto;
	/* Anchors both the gradient fade (::after) and the price chip, which
	   are each positioned relative to this wrapper rather than the image
	   itself, since the wrapper's box always matches the image's own
	   rendered size exactly (it's the only content in it). */
	position: relative;
}

@media (min-width: 769px) {
	.dfi-package-item__media {
		flex: 0 0 45%;
	}
}

/* No forced aspect-ratio/object-fit crop -- these are the original source
   photos (mixed landscape/portrait/panorama), and cropping to a fixed box
   was cutting off parts of some of them. Sized by width only (the
   .dfi-package-item__media column's own flex-basis, set above) with
   height:auto so each photo keeps its real proportions. */
.dfi-package-item__media img {
	display: block;
	width: 100%;
	height: auto;
}

/* Price/financing line + Call Now CTA, overlaid on the photo's bottom-
   right corner as a chip rather than plain text in the content column. */
.dfi-package-item__price-chip {
	position: absolute;
	right: 1rem;
	bottom: 1rem;
	display: flex;
	align-items: center;
	gap: 0.75em;
	max-width: calc(100% - 2rem);
	padding: 0.6em 0.9em;
	background: #242424;
	border-radius: 6px;
	box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
}

.dfi-package-item__price-chip-text {
	color: #ffffff;
	font-weight: 700;
	font-size: 0.85rem;
	line-height: 1.3;
}

.dfi-package-item__price-chip-cta {
	flex: 0 0 auto;
	padding: 0.5em 0.9em;
	background: #83be42;
	color: #ffffff;
	font-weight: 700;
	font-size: 0.85rem;
	text-decoration: none;
	border-radius: 4px;
	white-space: nowrap;
	transition: background-color 0.3s ease;
}

.dfi-package-item__price-chip-cta:hover,
.dfi-package-item__price-chip-cta:focus-visible {
	background: #242424;
	outline: 1px solid #ffffff;
}

@media (max-width: 480px) {
	.dfi-package-item__price-chip {
		flex-direction: column;
		align-items: flex-end;
		gap: 0.4em;
	}
}

/* On mobile the single-column card is narrow enough that the chip --
   price text plus a stacked Call Now button at that width -- ends up
   covering a large chunk of the photo it's overlaid on. Below 768px it
   drops out of the photo entirely and sits in normal flow right under
   it instead, full width, no absolute positioning. */
@media (max-width: 768px) {
	.dfi-package-item__media {
		display: flex;
		flex-direction: column;
	}

	.dfi-package-item__price-chip {
		position: static;
		flex-direction: row;
		align-items: center;
		justify-content: space-between;
		width: 100%;
		max-width: none;
		margin-top: 0.75rem;
		box-shadow: none;
		border-radius: 6px;
	}
}

.dfi-package-item__content {
	flex: 1 1 auto;
	min-width: 0;
}

.dfi-package-item__title {
	margin-top: 0;
	margin-bottom: 0.5rem;
	font-size: 1.8em;
	font-weight: 900;
	text-transform: uppercase;
}

.dfi-package-item__body ul {
	padding-left: 1.2em;
}

/*
 * Reuses the parent theme's own Photo Gallery markup/classes
 * (.dfi-gallery / .dfi-gallery__item) for its lightbox -- see the
 * docblock on dfi2026_child_render_package_item_block() in
 * packages-block.php. This isn't a visible photo grid though, just the
 * lightbox's click target and its hidden sibling items, so gallery.css's
 * own grid/square-crop rules on those same classes need to be fully
 * overridden back out.
 *
 * gallery.css is only conditionally enqueued (dfi2026_child_packages_assets()
 * in packages-block.php, since package items don't use the [dfi_gallery]
 * shortcode gallery.css normally ships with) -- it happens to print AFTER
 * this child stylesheet in the page <head>, not before, so a same-
 * specificity rule here would lose the cascade to gallery.css's
 * .dfi-gallery / .dfi-gallery__item rules despite being "more specific to
 * this component." Every override below is therefore written as a
 * descendant selector scoped under .dfi-package-item__gallery (two
 * selectors/classes) to outrank gallery.css's bare one-class selectors
 * regardless of load order, with the hidden-items rule also carrying
 * !important since display:none vs display:block is exactly the kind of
 * all-or-nothing toggle !important exists for.
 */
/* inline-block rather than block so the dark box shrink-wraps to however
   wide the thumbnail row actually needs to be, instead of always
   stretching to fill .dfi-package-item__content's full width. */
.dfi-package-item__gallery.dfi-gallery {
	display: inline-block;
	max-width: 100%;
	padding: 1rem 1.2rem 1.2rem;
	margin-top: 1rem;
	background: linear-gradient(135deg, #242424 0%, #6e6e6e 100%);
	border-radius: 6px;
}

.dfi-package-item__gallery .dfi-gallery__item {
	aspect-ratio: auto;
	overflow: visible;
}

.dfi-package-item__gallery-label {
	margin: 0 0 0.75rem;
	color: #ffffff;
	font-weight: 700;
	position: relative;
}

/* Photo-stack icon (Font Awesome \f302 "images", already loaded sitewide
   -- same raw content+font-family convention as the Button Rows arrow
   elsewhere in this file). */
.dfi-package-item__gallery-label::before {
	content: "\f302";
	font-family: "Font Awesome 7 Free";
	font-weight: 900;
	margin-right: 0.5em;
}

/* Every photo in the gallery as its own small clickable thumbnail, filling
   out what used to be empty space in this dark box now that the price
   moved to the chip on the photo above. Each one is still a real
   .dfi-gallery__item -- clicking any thumbnail opens the same lightbox
   (gallery.js) starting at that exact photo, not just the first one. */
.dfi-package-item__gallery-thumbs {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem;
}

.dfi-package-item__gallery-thumb {
	display: block;
	flex: 0 0 auto;
	width: 2.75rem;
	height: 2.75rem;
	box-sizing: border-box;
	border: 1px solid #ffffff;
	border-radius: 3px;
	overflow: hidden;
	transition: transform 0.2s ease;
}

.dfi-package-item__gallery-thumb img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.dfi-package-item__gallery-thumb:hover,
.dfi-package-item__gallery-thumb:focus-visible {
	transform: scale(1.1);
}

@media (prefers-reduced-motion: reduce) {
	.dfi-package-item__gallery-thumb {
		transition: none;
	}
}

/*
 * Services mega dropdown (inc/services-mega-menu.php injects the
 * .dfi-mega-menu-trigger class and .dfi-mega-menu__image markup; assets/
 * js/mega-menu.js handles the hover image swap and keeps
 * --dfi-mega-menu-top in sync with the sticky header's real height).
 *
 * Both the list (.sub-menu, left) and the image (.dfi-mega-menu__image,
 * right) are switched to position:fixed rather than the parent theme's
 * default position:absolute -- an absolutely positioned dropdown is
 * anchored to its own <li> (.primary-menu li has position:relative),
 * which sits wherever that nav item happens to be, not the viewport's
 * left edge. Fixed positioning anchors to the viewport itself instead,
 * which is what actually lets this span the full browser width
 * regardless of where "Services" falls in the nav. Visibility itself
 * still comes from the parent theme's existing :hover/:focus-within
 * opacity rule (.primary-menu li:hover > .sub-menu etc.) -- only
 * position/size/color are overridden here, so show/hide keeps working
 * exactly like every other (non-mega) dropdown, no new JS needed for
 * that part.
 *
 * Mobile is entirely unaffected: this whole block is desktop-only, and
 * the image panel is explicitly hidden below the breakpoint further
 * down -- the accordion-style mobile submenu (nav-toggle.js,
 * "li.submenu-is-open > .sub-menu" in the parent's style.css) still
 * finds .sub-menu exactly where it expects it, as a direct child of the
 * <li>, since this never moves it in the DOM.
 */
@media (min-width: 769px) {
	.dfi-mega-menu-trigger {
		/* Single source of truth for both panels below, so the white list
		   and the image are always exactly the same height regardless of
		   item count or image size -- rather than the list's height being
		   whatever its content naturally adds up to (which was shorter
		   than the image's own fixed height, leaving the image visibly
		   taller/overflowing below the white panel).
		   clamp() with a vh middle value rather than a flat rem: a fixed
		   rem/px height is a constant number of pixels regardless of the
		   screen, which eats a much bigger share of a 1080p display's
		   vertical space than the same number of pixels does on a 4K one
		   -- vh ties it to actual viewport height instead, so it scales
		   down on shorter screens the same way it scales up on taller
		   ones. The clamp floor/ceiling just keep it from ever getting
		   unreasonably cramped or oversized at the extremes. */
		--dfi-mega-menu-height: clamp(260px, 28vh, 420px);
	}

	.dfi-mega-menu-trigger > .sub-menu {
		position: fixed;
		top: var(--dfi-mega-menu-top, 120px);
		left: 0;
		width: 66.6667%;
		height: var(--dfi-mega-menu-height);
		min-width: 0;
		margin: 0;
		padding: 2rem 3rem;
		background: #ffffff;
		box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15);
		box-sizing: border-box;
		/* Two columns of 5 rows (matches the old site's spread-out list
		   and this menu's current 10 items) -- grid rather than CSS
		   multicol so grid-template-rows:1fr can stretch each row to
		   fill the panel's full height evenly, spacing the items out,
		   instead of them all packing tightly at the top. */
		display: grid;
		grid-template-columns: 1fr 1fr;
		grid-template-rows: repeat(5, 1fr);
		grid-auto-flow: column;
		column-gap: 3rem;
	}

	.dfi-mega-menu-trigger .sub-menu li {
		display: flex;
		align-items: center;
	}

	/* Same sliding top/bottom bar hover effect as the top-level nav items
	   (.primary-menu > li > a above) -- currentColor so it follows
	   whatever text color is active (the green hover color below). */
	.dfi-mega-menu-trigger .sub-menu a {
		/* No width:100% -- the hover bars below are sized off this
		   element's own width (currentColor, width:100% of the <a>), so
		   this needs to shrink-to-fit the link text rather than stretch
		   to fill the grid cell, or the bars read as far wider than the
		   word itself. */
		display: inline-block;
		position: relative;
		padding: 0.4rem 0;
		color: #1a1a1a;
		background-color: transparent;
	}

	.dfi-mega-menu-trigger .sub-menu a::before,
	.dfi-mega-menu-trigger .sub-menu a::after {
		content: "";
		position: absolute;
		left: 0;
		width: 100%;
		height: 2px;
		background: currentColor;
		opacity: 0;
		transition: transform 0.2s ease-out, opacity 0.2s ease-out;
	}

	.dfi-mega-menu-trigger .sub-menu a::before {
		top: 0;
		transform: translate3d(0, -6px, 0);
	}

	.dfi-mega-menu-trigger .sub-menu a::after {
		bottom: 0;
		transform: translate3d(0, 6px, 0);
	}

	.dfi-mega-menu-trigger .sub-menu a:hover,
	.dfi-mega-menu-trigger .sub-menu a:focus {
		color: #83be42;
		background-color: transparent;
	}

	.dfi-mega-menu-trigger .sub-menu a:hover::before,
	.dfi-mega-menu-trigger .sub-menu a:hover::after,
	.dfi-mega-menu-trigger .sub-menu a:focus::before,
	.dfi-mega-menu-trigger .sub-menu a:focus::after {
		opacity: 1;
		transform: translate3d(0, 0, 0);
	}

	.dfi-mega-menu__image {
		position: fixed;
		top: var(--dfi-mega-menu-top, 120px);
		left: 66.6667%;
		right: 0;
		height: var(--dfi-mega-menu-height);
		background: #111111;
		opacity: 0;
		visibility: hidden;
		pointer-events: none;
		transition: opacity 200ms ease, visibility 0s linear 200ms;
	}

	.primary-menu li.dfi-mega-menu-trigger:hover > .dfi-mega-menu__image,
	.primary-menu li.dfi-mega-menu-trigger:focus-within > .dfi-mega-menu__image {
		opacity: 1;
		visibility: visible;
		pointer-events: auto;
		transition-delay: 0s;
	}

	.dfi-mega-menu__image-img {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
	}
}

@media (max-width: 768px) {
	.dfi-mega-menu__image {
		display: none;
	}
}

/*
 * FAQ Accordion (inc/faq-accordion-block.php). Expand/collapse itself is
 * assets/js/faq-accordion.js animating max-height (see that file's
 * comment for why); everything here is just the resting look + the plus
 * icon rotating into an X-ish state when open.
 */
.dfi-faq-accordion {
	display: flex;
	flex-direction: column;
}

.dfi-faq-accordion__item {
	border-bottom: 1px solid #dddddd;
}

.dfi-faq-accordion__item:first-child {
	border-top: 1px solid #dddddd;
}

/* .dfi-faq-accordion .dfi-faq-accordion__question (two classes) rather
   than the bare class alone: this is an <h2>, so the sitewide big/bold
   heading rule near the top of this file (.entry-content h2, one class +
   one element = higher specificity than a single class) was winning and
   blowing this up to that size regardless of what was set here. */
.dfi-faq-accordion .dfi-faq-accordion__question {
	margin: 0;
	font-size: 1em;
	font-weight: 400;
	text-transform: none;
}

.dfi-faq-accordion__trigger {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	width: 100%;
	padding: 1.25rem 0.5rem;
	background: none;
	border: 0;
	text-align: left;
	font: inherit;
	font-weight: 700;
	font-size: 1.15em;
	color: #1a1a1a;
	cursor: pointer;
}

.dfi-faq-accordion__trigger:hover,
.dfi-faq-accordion__trigger:focus-visible {
	color: #83be42;
}

/* Plus sign, rotating into a (visual) minus/X when the panel is open --
   drawn with two pseudo-elements rather than a font-icon dependency. */
.dfi-faq-accordion__icon {
	position: relative;
	flex: 0 0 auto;
	width: 1.1em;
	height: 1.1em;
}

.dfi-faq-accordion__icon::before,
.dfi-faq-accordion__icon::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	background: currentColor;
	transition: transform 0.25s ease;
}

.dfi-faq-accordion__icon::before {
	width: 100%;
	height: 2px;
	transform: translate(-50%, -50%);
}

.dfi-faq-accordion__icon::after {
	width: 2px;
	height: 100%;
	transform: translate(-50%, -50%);
}

.dfi-faq-accordion__item.is-open .dfi-faq-accordion__icon::after {
	transform: translate(-50%, -50%) rotate(90deg);
	opacity: 0;
}

.dfi-faq-accordion__panel {
	max-height: 0;
	overflow: hidden;
	transition: max-height 0.3s ease;
}

.dfi-faq-accordion__panel-inner {
	padding: 0 0.5rem 1.25rem;
	color: #444444;
	line-height: 1.6;
}

.dfi-faq-accordion__panel-inner p:first-child {
	margin-top: 0;
}

.dfi-faq-accordion__panel-inner p:last-child {
	margin-bottom: 0;
}

@media (prefers-reduced-motion: reduce) {
	.dfi-faq-accordion__panel {
		transition: none;
	}

	.dfi-faq-accordion__icon::before,
	.dfi-faq-accordion__icon::after {
		transition: none;
	}
}

/*
 * Company-name wordmark for the animated logo (functions.php:
 * dfi2026_child_animated_logo() / dfi2026_child_defer_logo_script()).
 * Centered directly below the logo mark, fading in once JS adds
 * .is-visible, timed to when the logo mark's own WebP reveal animation
 * actually finishes playing.
 *
 * Normal document flow (a flex column on .custom-logo-link), NOT
 * position:absolute as this was originally built: .site-header__top --
 * the row both the logo and this wordmark live in -- has overflow:hidden
 * and a JS-measured max-height (both load-bearing: that's what makes the
 * row collapse cleanly on scroll, see sticky-header.js). An absolutely
 * positioned wordmark sitting below the logo didn't contribute to that
 * row's own measured content height at all, so it rendered entirely
 * outside the max-height box and got clipped off by overflow:hidden --
 * "the text is cut off." In normal flow it actually adds to the row's
 * real height, which the max-height/scrollHeight measurement then
 * correctly accounts for, and its own margin-top gives it real spacing
 * under the logo instead of needing room carved out of the nav row
 * below.
 *
 * Width is a multiple of the logo's own current width (3x desktop, 2x
 * mobile) rather than a fixed size, so it stays in proportion if the
 * logo's own size is ever changed later -- height:auto preserves the
 * PNG's real aspect ratio at whatever width that works out to.
 */
.custom-logo-link {
	display: flex;
	flex-direction: column;
	align-items: center;
}

.custom-logo-name-overlay {
	width: calc(var(--dfi-logo-width, 70px) * 3);
	margin-top: -0.5em;
	/* The parent theme's global `img { max-width: 100% }` reset would
	   otherwise cap this against .custom-logo-link's own width, which
	   (now a flex column) stretches to whichever child is wider anyway --
	   not strictly load-bearing under this layout, but no reason to leave
	   an unnecessary constraint sitting there either. */
	max-width: none;
	height: auto;
	opacity: 0;
	transition: opacity 0.6s ease;
	pointer-events: none;
}

/* 25% smaller than the home page's version (3x -> 2.25x the logo's own
   width) -- on top of already scaling down naturally from the smaller
   sub-page logo (--dfi-logo-width) vs. the home page's larger one
   (--dfi-logo-width-home). */
body:not(.home) .custom-logo-name-overlay {
	width: calc(var(--dfi-logo-width, 70px) * 2.25);
}

@media (max-width: 768px) {
	.custom-logo-name-overlay {
		width: calc(var(--dfi-logo-width-mobile, 20vw) * 2);
	}
}

.custom-logo-name-overlay.is-visible {
	opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
	.custom-logo-name-overlay {
		transition: none;
	}
}

/* More breathing room below the Contact page's "Get In Touch" heading --
   the parent's own rule only gives it 0.75rem. */
.dfi-contact-layout__heading {
	margin-bottom: 2rem;
}

/*
 * Plain-content scroll-reveal (About Us, the Services pages, and any
 * future page built the same way): fades/slides in the page's own
 * top-level paragraphs/headings/lists exactly like the Image + Text
 * Columns block's own text already does, so a heading or bullet list
 * sitting in plain content below/after that block doesn't just appear
 * fully-formed while everything around it animates in -- same visual
 * language sitewide instead of some elements feeling static.
 *
 * Scoped to DIRECT children of .entry-content specifically (the `>`
 * combinator) so this never reaches into a custom block's own nested
 * headings/paragraphs/lists that already have their own distinct
 * treatment -- e.g. .dfi-package-item__title (an h2, but nested several
 * levels deep, not a direct child) or .dfi-why-choose-us h2 on the home
 * page, both unaffected by this rule.
 */
@media (min-width: 769px) {
	.entry-content > h2,
	.entry-content > h3,
	.entry-content > p,
	.entry-content > ul {
		opacity: 0;
		transform: translateY(30px);
		transition: opacity 0.6s ease-out, transform 0.6s ease-out;
	}

	.entry-content > h2.is-visible,
	.entry-content > h3.is-visible,
	.entry-content > p.is-visible,
	.entry-content > ul.is-visible {
		opacity: 1;
		transform: translateY(0);
	}
}

@media (prefers-reduced-motion: reduce) {
	.entry-content > h2,
	.entry-content > h3,
	.entry-content > p,
	.entry-content > ul {
		opacity: 1 !important;
		transform: none !important;
		transition: none !important;
	}
}

/* Breathing room below the prev/next post nav so it doesn't butt
   against the footer -- the parent's .dfi-blog-layout has no
   bottom padding at all. */
.dfi-blog-layout {
	padding-bottom: 3rem;
}


/*
 * Static image home hero (header.php): stands in for the Vimeo background
 * video when none is assigned to the front page. Same full-viewport
 * footprint as .dfivimeo's viewport_fill mode, so .dfi-hero-overlay and
 * the transparent header sit over it exactly as they do over the video.
 */
.dfi-hero-static {
	height: 100vh;
	min-height: 420px;
	background-color: #242424;
	background-position: center;
	background-size: cover;
	background-repeat: no-repeat;
}

/* In-page anchors (Services page sections linked from the home button
   row): keep the target heading clear of the sticky header. */
.entry-content [id] {
	scroll-margin-top: 12rem;
}
