/*
* Hero Headline Offset
*/
.hero-headline-offset {
	left: -180px;
}

@media (max-width: 991px) {
	.hero-headline-offset {
		left: 0;
	}
}

/*
* Home Hero - Container Height, Set Correctly on First Paint
* The hero container ships with a hardcoded style="height: 700px" in the
* HTML as a fallback, corrected by the Porto theme's own
* data-dynamic-height plugin (js/theme.js) to the right value for the
* current viewport - but that plugin only auto-runs its initial
* correction for widths under 992px ("Mobile First Load", js/theme.js);
* at >=992px 700px already happens to be correct, so it's left alone.
* On any narrower viewport (tablet/mobile), that means the container
* briefly paints at the wrong, too-tall 700px before JS runs and shrinks
* it down to the real 600/500/410px value - which visibly resized the
* background slideshow underneath it on every load once the pan/zoom
* treatment was added (previously just a static image, so the resize
* wasn't as noticeable).
*
* Fix: replicate the exact same breakpoint -> height mapping as plain
* CSS, !important so it always wins over both the hardcoded inline
* fallback and any inline height the JS plugin sets later on resize.
* Being CSS (parsed and applied in <head> before the body paints), the
* correct height is there from the very first frame - no JS timing to
* race, no flash to hide. The JS plugin keeps running harmlessly
* alongside this (data-dynamic-height/theme.js untouched); its own
* inline-height writes are just always immediately masked by this rule.
*/
.hero-dynamic-height {
	height: 700px !important;
}

@media (max-width: 991px) {
	.hero-dynamic-height {
		height: 600px !important;
	}
}

@media (max-width: 767px) {
	.hero-dynamic-height {
		height: 500px !important;
	}
}

@media (max-width: 575px) {
	.hero-dynamic-height {
		height: 410px !important;
	}
}

/*
* Home Hero - Pan & Crossfade Background Slideshow
* Replaces the old horizontal sliding-track marquee entirely. 4 stacked
* layers (bg1-4.jpg), each position-absolute/inset:0, fill the same hero
* container the marquee track used to occupy - only one is ever opaque
* at a time.
*
* Sizing is deliberately split into two independent concerns so neither
* one can distort the image:
*   - background-size: cover (below) always preserves the image's native
*     aspect ratio while fully filling the box, on any container shape -
*     this is what changes on narrow/short mobile boxes, and it never
*     stretches. (background-size: 105% 105% previously did this AND the
*     zoom in one step, forcing both dimensions to a fixed percentage of
*     the container independent of the image's own ratio - that's what
*     was stretching it out of proportion on mobile, where the
*     container's aspect ratio diverges further from the images'.)
*   - the extra ~5% zoom buffer needed for the pan is applied separately,
*     as transform: scale(1.05) on the layer itself - a uniform scale
*     that can't distort proportions the way an independent width/height
*     background-size percentage can.
*
* The pan itself is now transform: translateX(...) instead of
* background-position-x, since background-size: cover leaves no size
* overflow within the box for background-position to reveal. translateX
* is listed BEFORE scale() in the transform value so its percentage
* resolves against the layer's own unscaled width (not stretched by the
* scale) - translateX(2.5%) / translateX(-2.5%) uses exactly the 5%
* (2.5% either side of center) of slack the 1.05 scale creates, so the
* image's left edge sits flush at pan-start and its right edge sits
* flush at pan-end, same as the background-position-x 0%->100% version
* did, just via a different property.
*
* All 4 layers share ONE @keyframes shape and are spaced by exactly 1/4
* of the shared 50s cycle via animation-delay (0s/12.5s/25s/37.5s), with
* animation-fill-mode: backwards so each stays invisible (opacity 0,
* matching keyframe 0%) until its own delay elapses. Because every layer
* runs the identical shape just phase-shifted by exactly one quarter-
* cycle, and that shape's fade-out tail (25%-27.5%, i.e. the last 1.25s
* of its 12.5s turn) is the same real-world width as the next layer's
* fade-in head (0%-2.5%), the two always land on the exact same 1.25s
* window in real time - a true overlapping crossfade, not a hard cut -
* including the wrap from bg4's fade-out back into bg1's fade-in on the
* next lap (both fall at t=50s/0s, since every layer's cycle repeats
* every 50s). None of this timing changed from the previous version -
* only what's being sized/animated did. Percentage-based pan/timing
* throughout, independent of the fluid container width and the
* JS-driven per-breakpoint height (data-dynamic-height, js/theme.js) -
* neither was touched.
*/
.hero-bg-slide-layer {
	background-size: cover;
	background-position: center;
	background-repeat: no-repeat;
	opacity: 0;
	transform: translateX(2.5%) scale(1.05);
}

@media (prefers-reduced-motion: no-preference) {
	.hero-bg-slide-layer {
		animation: heroBgPanCrossfade 50s linear infinite;
		animation-fill-mode: backwards;
	}

	.hero-bg-slideshow > .hero-bg-slide-layer:nth-child(1) {
		animation-delay: 0s;
	}

	.hero-bg-slideshow > .hero-bg-slide-layer:nth-child(2) {
		animation-delay: 12.5s;
	}

	.hero-bg-slideshow > .hero-bg-slide-layer:nth-child(3) {
		animation-delay: 25s;
	}

	.hero-bg-slideshow > .hero-bg-slide-layer:nth-child(4) {
		animation-delay: 37.5s;
	}
}

@keyframes heroBgPanCrossfade {
	0% {
		opacity: 0;
		transform: translateX(2.5%) scale(1.05);
	}
	2.5% {
		opacity: 1;
	}
	25% {
		opacity: 1;
		transform: translateX(-2.5%) scale(1.05);
	}
	27.5% {
		opacity: 0;
	}
}

/*
* Home Hero - Reduced Motion Fallback
* No sliding-track marquee, no pan, no crossfade - just bg1 shown as a
* single static image (plain cover/center, not the 105%/pan treatment)
* for users with prefers-reduced-motion enabled at the OS level.
*/
@media (prefers-reduced-motion: reduce) {
	.hero-bg-slideshow > .hero-bg-slide-layer:first-child {
		opacity: 1;
		transform: none;
	}
}

/*
* Home Hero - Scrim
* Sits above the marquee, below the text/content container
* (.container.z-index-1 holding the headline, engineer photo and
* tagline - untouched). Subtle dark tint so text contrast stays
* consistent no matter which part of which background photo is
* currently in view, without visibly darkening or flattening the
* photos themselves.
*/
.hero-bg-scrim {
	background-color: rgba(0, 0, 0, 0.18);
	pointer-events: none;
}

/*
* Footer Newsletter Form - Equal Field Heights
* First Name and Email Address sit in separate .input-group instances
* (First Name has no button, so nothing stretches it - Bootstrap's
* .input-group align-items:stretch only equalizes elements WITHIN the
* same group, not across separate ones). The submit button contains a
* 27px icon image, which - once its own padding is added - renders
* taller than a plain .form-control, so Email's group (stretched to
* match the button) ends up taller than First Name's. Pin both fields
* and the button to one explicit shared height so they match regardless
* of that content-driven sizing, and centre the icon within the
* button's now-fixed height.
*/
.newsletter .mailerlite-form .form-control,
.newsletter .mailerlite-form .btn {
	height: 44px;
}

.newsletter .mailerlite-form .btn {
	display: flex;
	align-items: center;
	justify-content: center;
}

/*
* Faster Scroll-In Animations - Mobile
* The theme sets each element's fade-in "animation-delay"/"animation-duration"
* as an inline style (from data-appear-animation-delay/-duration attributes),
* often staggered up to 1-2+ seconds per element. That choreography reads fine
* on desktop but feels laggy/uncertain on mobile scrolling. Collapse both to
* near-instant on mobile only - inline styles need !important to be overridden.
* Desktop timing is untouched.
*/
@media (max-width: 991px) {
	.appear-animation {
		animation-delay: 50ms !important;
		animation-duration: 250ms !important;
		transition-delay: 50ms !important;
		transition-duration: 250ms !important;
	}
}

/*
* Sticky Sidebar - Prevent Visual Re-Fade on Scroll
* Reported: sidebar content on the sticky-sidebar pages (data-plugin-sticky
* - About, Services, and the 5 individual service pages) fades in
* correctly on load, then appears to fade in again the first time the
* user scrolls far enough for the sticky plugin (js/theme.js) to flip
* the <aside> between static/fixed/absolute positioning.
*
* Investigated: this is NOT the earlier-diagnosed nested-wrapper bug
* (a wrapper animating on top of already-animated children) - an
* exhaustive scan found no instance of that pattern anywhere on the
* site, including these pages. The sticky position toggle is the only
* scroll-triggered mechanism active specifically on these pages, so it's
* the strongest remaining candidate, but confirming the exact rendering
* mechanism needs live browser inspection (devtools Animations panel),
* which wasn't available here.
*
* This is a mitigation, not a root-cause fix: once an element's own
* single-fire appear-animation has genuinely already completed (theme.js
* adds .appear-animation-visible after that happens, never before), pin
* its opacity at the settled end value with !important - a normal
* declaration's !important overrides a running/replaying CSS animation's
* computed value for that property regardless of what retriggers it,
* without altering the shared animation system itself or affecting the
* real first play-through. If this doesn't fully resolve it, the actual
* trigger needs identifying with devtools before a more targeted fix is
* possible.
*/
[data-plugin-sticky] .appear-animation.appear-animation-visible {
	opacity: 1 !important;
}

/*
* Header Scroll Background Opacity
* The theme's construction demo sets the sticky header background to 80%
* opacity (css/components.css). Raised further to 90% so a
* busy part of the page scrolling behind it is much less distracting.
*/
html.sticky-header-active #header .header-body {
	background: rgba(255, 255, 255, 0.9);
}

/*
* LinkedIn Ribbon - Multi-Platform Social Icons
* Each icon gets its own fixed-size solid circle so it reads clearly at a
* glance (LinkedIn blue, Instagram purple, Facebook pale blue), all
* identical in size. Static links, not buttons - no hover state needed.
*/
.ribbon-social-icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 28px;
	height: 28px;
	border-radius: 100%;
	font-size: 14px;
	text-decoration: none !important;
	flex: 0 0 auto;
}

.ribbon-social-icon-linkedin {
	background-color: #0073b2;
	color: #fff !important;
}

.ribbon-social-icon-instagram {
	background-color: #8134af;
	color: #fff !important;
}

.ribbon-social-icon-facebook {
	background-color: #cfe3f7;
	color: #1a3a5c !important;
}

/*
* Trust Badges - Desaturated, Full Colour on Hover
* Grayscale applies on all devices, including touch/mobile (which has no
* hover to reveal colour, so they just stay desaturated there). The colour
* reveal on hover only fires on devices that actually support hovering.
*/
.trust-badge {
	filter: grayscale(100%);
	transition: filter 0.3s ease;
}

@media (hover: hover) {
	.trust-badge:hover {
		filter: grayscale(0%);
	}
}

/*
* Pending-info placeholders
* Marks values not yet available (email, VAT/ICO numbers, GTM ID, etc.)
* so they're easy to spot in the browser and to grep for ("todo-placeholder").
*/
.todo-placeholder {
	background: #fff3a3;
	padding: 0 4px;
	border-radius: 2px;
}

/*
* Case Study Image Placeholders
* Used on project/case-study pages where a Before/During/After photo doesn't
* exist yet. Dashed border + icon + label - visibly a placeholder, not a
* real asset, until a proper photo is supplied.
*/
.case-study-placeholder-image {
	aspect-ratio: 2 / 1;
	border: 2px dashed #ccc;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	color: #999;
	text-align: center;
	padding: 1.5rem;
}

/*
* .mb-5 Override
* Bootstrap sets this utility to margin-bottom: 3rem !important. Reduced
* site-wide to 1rem. Loaded after bootstrap.min.css so this wins the cascade
* despite both being !important.
*/
.mb-5 {
	margin-bottom: 1rem !important;
}

/*
* Service Card Extra Spacing (Home)
* .mb-5 above is reduced to 1rem (16px) site-wide. These 5 home service
* cards need 30px more than that (46px total). Scoped class, not .mb-5.
*/
.service-card-col {
	margin-bottom: 46px !important;
}

/*
* Blog List Article Spacing (blog.html only)
* .mb-5 above is reduced to 1rem (16px) site-wide. These list cards need
* 40px more (56px total). Scoped to blog.html's list articles only -
* .post-block/.post-author etc on the single-post pages are unaffected.
*/
.blog-list-article {
	margin-bottom: 56px !important;
}

/*
* Blog List Article Separator (blog.html only)
* Adds a divider under each card's "Read Article" link so it's clearer
* where one article ends and the next begins. Skipped on the last card
* so there's no stray line sitting right before the sidebar/footer gap.
*/
.blog-list-article:not(:last-child) {
	padding-bottom: 40px;
	border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}

/*
* Hero Diagonal Title Banner - 2-line fix
* The dark diagonal SVG banner behind the hero <h1> text is a fixed
* height sized for one line - it must stay that size (not grow/resize).
* When js/custom.js detects the title text wraps to 2 lines, it adds
* .hero-diagonal-title-two-line to the <h1> - shift only the text span
* up so the extra line still reads cleanly against the unchanged banner.
* The banner (.custom-svg-position-1 and its svg) is untouched here.
* Value tuned/confirmed in Chrome DevTools against the real render.
*/
.hero-diagonal-title.hero-diagonal-title-two-line > span:first-child {
	margin-top: -18px;
}

/*
* Hero Diagonal Title Banner - 2-line fix, shorter titles
* The -18px shift above was tuned against long titles that wrap onto a
* visually heavy 2nd line - short titles (<=17 characters, e.g. "Bird
* Control") look over-corrected with the same shift. js/custom.js also
* measures the title text length and adds this class (alongside
* -two-line) only when the title is short, to zero the shift back out.
* One extra class than the rule above, so this always wins regardless of
* source order.
*/
.hero-diagonal-title.hero-diagonal-title-two-line.hero-diagonal-title-two-line-short > span:first-child {
	margin-top: 0;
}

/*
* Hero Diagonal Title Banner - 2-line fix, mobile horizontal clearance
* On mobile the wrapped 2nd line otherwise gets clipped by the polygon's
* diagonal cut on its left edge. Hardcoded nudge, not calculated from
* text/line count - value tuned/confirmed in Chrome DevTools.
*/
@media (max-width: 767px) {
	.hero-diagonal-title.hero-diagonal-title-two-line > span:first-child {
		margin-right: 15px;
	}
}

/*
* Hero Title Container - Reduced Top Padding
* Bootstrap's .pt-5 utility (3rem/48px, !important) is used site-wide,
* so it can't be changed globally. This hero container needs less top
* padding to bring the polygon/breadcrumb layout together - scoped to
* just this element instead (see about.html etc: replaces "pt-5" in
* the class list on .container.position-relative).
*/
.hero-title-container {
	padding-top: 1rem;
}

/*
* Hero Breadcrumb - Relocated Below the Diagonal Title Banner
* The breadcrumb used to live inside the same col as the <h1>/polygon,
* directly above it. It's now a sibling of .row.justify-content-end
* (see about.html etc.), moved out of the polygon/title area entirely,
* and absolutely positioned within the position:relative .container so
* it lands in the grey hero band below the polygon's bottom edge.
*
* Same left at every width. Top is 118px below 992px, 168px at 992px+
* (matches the header's own mobile-nav breakpoint) - both
* tuned/confirmed in Chrome DevTools against the real render.
*/
.hero-breadcrumb-relocated {
	position: absolute;
	left: 12px;
	top: 118px;
	text-align: left;
}

@media (min-width: 992px) {
	.hero-breadcrumb-relocated {
		top: 168px;
	}
}

/*
* Mobile Submenu Smooth Expand/Collapse
* Base theme hard-swaps display:none/block on .dropdown-menu with no
* transition, unlike the smooth ~400-500ms height animation used for the
* main mobile nav. Override to a max-height + opacity transition so
* opening a submenu (e.g. Services, Company) feels consistent.
*
* margin-left is set here unconditionally (theme.css only applies it via
* `li.dropdown.open > .dropdown-menu`/`li.dropdown-submenu.open > .dropdown-menu`,
* with no transition on the property) so it no longer changes at the
* moment .open is toggled. That untransitioned jump from 20px -> 0 was
* invisible while opening (it happens under opacity: 0, before the fade
* in), but on closing it fired while the submenu was still fully opaque,
* snapping every item 20px left in a single frame before the opacity/
* height transitions played out.
*/
@media (max-width: 991px) {
	#header .header-nav-main nav > ul li.dropdown .dropdown-menu {
		display: block !important;
		max-height: 0;
		overflow: hidden;
		opacity: 0;
		margin-left: 20px;
		transition: max-height 350ms ease, opacity 300ms ease;
	}

	#header .header-nav-main nav > ul li.dropdown.open > .dropdown-menu {
		max-height: 500px;
		opacity: 1;
	}
}

/*
* Mobile Nav - Never Internally Scrollable
* Base theme caps #header .header-nav-main nav to max-height: 50vh with
* overflow-y: auto (theme.css), so once expanded content (e.g. both the
* Company and Services dropdowns open at once) exceeds half the viewport
* height, later items like Contact end up inside that internal scroll
* area instead of pushing the page taller - reachable only by dragging
* within the nav itself, not by scrolling the page. custom.js already
* resizes .header-body to fit whatever is currently expanded (see
* js/custom.js), so remove the inner cap/scrollbar and let nav grow to
* its natural content height instead.
*/
@media (max-width: 991px) {
	#header .header-nav-main nav {
		max-height: none;
		overflow: visible;
	}
}

/*
* Contact Page - Call Us / Send An Email Stacking
* Below 420px the two side-by-side blocks currently wrap unreliably via
* default flex-wrap. Force a clean stack and add spacing between them.
* Above 420px, unchanged (side by side, same height).
*/
@media (max-width: 420px) {
	.contact-methods-row .col-auto {
		flex: 0 0 100%;
		max-width: 100%;
	}

	.contact-methods-row .col-auto:nth-child(2) {
		margin-top: 30px;
	}
}

/*
* Home Hero Tagline - Mobile Font Size
* "Industrial Rope Access Specialists" beneath REACHING HIGHER uses the
* shared .text-5-5 utility (1.65em = 23.1px off a 14px body). Reduced by
* 1px on mobile only, scoped to this element (not the shared utility).
*/
@media (max-width: 991px) {
	.hero-tagline {
		font-size: 22.1px !important;
	}
}

/*
* Home Trust Badges - Mobile Row Spacing
* .mb-5 above is reduced to 1rem (16px) site-wide. These badge columns
* need 20px more (36px total) once shown 3-per-row on mobile.
*/
@media (max-width: 767px) {
	.col-4.col-sm-4.col-xl.mb-5 {
		margin-bottom: 36px !important;
	}
}

/*
* Case Study Filter Buttons - Border + Padding
* Previously only the active button had a border-bottom; inactive
* buttons had none at all, and .px-0 left them with no horizontal
* padding. Give every button a light border and proper padding so they
* read as clickable tag/button controls.
*/
.sort-source.sort-source-style-3 .nav-link.filter-pill {
	padding: 6px 14px !important;
	border: 1px solid var(--grey-500);
	border-radius: 20px;
}

.sort-source.sort-source-style-3 .nav-link.filter-pill.active {
	border: 1px solid var(--grey-500);
	background: var(--grey-100) !important;
}

/*
* Case Study Filter Buttons - Spacing
* .nav is display:flex/flex-wrap:wrap (Bootstrap), so a single `gap` on
* the container spaces every pill evenly in both directions at once -
* horizontally between pills in the same row, and vertically between
* rows once they wrap on narrower screens. Replaces an earlier
* margin-left-only approach (.filter-pill-spacing, since removed from
* the markup) that had no vertical spacing at all once pills wrapped.
*/
.sort-source.sort-source-style-3 {
	gap: 0.6rem;
}

/*
* Author Bio Box - Flexbox Layout
* Base theme (css/theme-blog.css) floats the avatar left with a fixed
* 100px height. Once bio text wraps past that height, subsequent lines
* clear the float and lose their left indent - a float wrap-around
* limitation, not fixable with a clearfix. Flexbox guarantees every
* wrapped line keeps the same left offset regardless of length.
*/
.post-author-body {
	display: flex;
	align-items: flex-start;
}

.post-author-body .img-thumbnail {
	float: none;
	flex-shrink: 0;
	margin-right: 20px;
}

.post-author-text {
	flex: 1;
	min-width: 0;
	margin-top: -45px;
}

/*
* Header Social Icons - Always Visible
* Base theme hides these below the sm breakpoint (d-none d-sm-flex/block,
* removed in includes/header.php) so they show at every viewport width.
* Below ~360px the header row (logo + social icons + hamburger) doesn't
* have room for the icons at their natural width, and since they're
* inline-block list items they wrap onto multiple lines instead of
* shrinking. Keep them on one line and pinned to their natural width so
* it's the hamburger's side of the row that yields space instead.
*/
#header .header-nav-features:has(.header-social-icons) {
	flex-shrink: 0;
}

#header .header-social-icons.social-icons {
	white-space: nowrap;
}

/* Below ~360px there isn't room for the icons at full size next to the
 * logo and hamburger button without the header row overflowing the
 * viewport - shrink the icons and their spacing so everything still
 * fits on one line. */
@media (max-width: 359.98px) {
	#header .header-nav-features:has(.header-social-icons) {
		padding-left: 4px;
		margin-left: 0 !important;
	}

	#header .header-social-icons.social-icons li {
		margin-right: 0;
	}

	#header .header-social-icons.social-icons li a {
		width: 24px;
		height: 24px;
		line-height: 24px;
		font-size: 11px;
	}
}
