/*
 * Overrides for behaviour the WordPress front end got from JavaScript we do not
 * ship. Loaded last so it wins on order rather than on !important where it can.
 *
 * Kept in public/ alongside harvested/shared.css, deliberately unprocessed: it
 * has to sit after the theme's own CSS in the cascade, and Astro's bundled
 * stylesheets are emitted ahead of the harvested <link>s.
 */

/* ---------------------------------------------------------------------------
 * Category archives: the theme lays the post grid out with Isotope.
 *
 * Isotope's initial markup state is `isotope-hidden` — opacity 0, visibility
 * hidden — which its JS clears once it has absolutely positioned every item.
 * Without that JS all 100 posts stay invisible AND stack in normal flow, which
 * is why /category/blog/ rendered blank at 19,385px tall against the live
 * page's 15,879px.
 *
 * Rebuilt with CSS Grid rather than by shipping Isotope and jQuery. Live is a
 * 3-column masonry of 340px items with a 20px gutter; grid reproduces the
 * columns and the gutter natively.
 *
 * Row-major order is deliberate. CSS multi-column would give a truer masonry
 * (no ragged bottom edge) but fills column 1 top-to-bottom first, so the newest
 * posts would read down the left column instead of across the top row. On an
 * archive, order carries meaning and the ragged edge does not — card heights
 * vary by ~15%, so the gaps are small.
 * ------------------------------------------------------------------------- */
/* The first selector matches the theme rule this has to beat --
   html[xmlns] .clearfix -- at exactly its specificity: element +
   attribute-or-class + class, (0,2,1). It wins on source order because this
   file loads last. A plain .blog-grid is (0,1,0), loses, and the grid
   silently reverts to a block. */
html .blog-grid.clearfix,
.blog-grid {
	display: grid;
	/* Live's cards are a fixed 340px with a 20px gutter, first card at x=20, and
	   Isotope fits floor((gridWidth - 20) / 360) of them per row -- so at 1440px
	   it lays out three and leaves ~360px empty on the right.
	   An earlier version of this used fluid 1fr columns that filled the row. It
	   looked tidier, but it is not what the site does: the wider cards stopped
	   post titles wrapping to two lines, which is most of why archive pages
	   measured ~7% shorter than live. Fixed 340px reproduces live exactly.
	   The breakpoints below are that floor() solved for gridWidth: n columns
	   need 360n + 20. */
	grid-template-columns: repeat(1, minmax(0, 340px));
	gap: 20px;
	justify-content: start;
	/* Grid items stretch to the row height by default, which made every card as
	   tall as the tallest in its row -- 432px against live's natural 396px, with
	   the content still ending at 396 and 36px of stretched box below it. Isotope
	   positions each card at its own height, so: start. */
	align-items: start;
	/* live: 10px of container padding plus a 10px item margin, on every side --
	   a single 419px card sits in a 459px grid. */
	padding: 20px;
	position: static;
	/* Not !important: masonry.ts sets an explicit pixel height once it has
	   positioned the cards out of flow. */
	height: auto;
}

@media (min-width: 740px) {
	html .blog-grid.clearfix,
	.blog-grid {
		grid-template-columns: repeat(2, 340px);
	}
}
@media (min-width: 1100px) {
	html .blog-grid.clearfix,
	.blog-grid {
		grid-template-columns: repeat(3, 340px);
	}
}
@media (min-width: 1460px) {
	html .blog-grid.clearfix,
	.blog-grid {
		grid-template-columns: repeat(4, 340px);
	}
}
@media (min-width: 1820px) {
	html .blog-grid.clearfix,
	.blog-grid {
		grid-template-columns: repeat(5, 340px);
	}
}

/* These reset Isotope's absolute positioning. They are NOT !important, on
   purpose: src/lib/masonry.ts positions the cards itself to reproduce live's
   column packing, and !important here would beat its inline styles. The
   declarations still apply whenever that script has not run, which is the
   no-JavaScript case the CSS Grid above is there for. */
.blog-grid article,
.blog-grid .post {
	position: static;
	left: auto;
	top: auto;
	width: auto;
	margin: 0;
	transform: none;
}

/* The class exists only to be removed by Isotope on init. */
.blog-grid .isotope-hidden {
	opacity: 1 !important;
	visibility: visible !important;
	pointer-events: auto !important;
}

/* ---------------------------------------------------------------------------
 * Elementor "Stretch Section": full-bleed sections that JavaScript used to size.
 *
 * The theme pads .page-content horizontally (15% / 10% / 30px, by breakpoint)
 * so ordinary page copy sits in a column. Sections marked "stretch" in Elementor
 * are supposed to escape that padding and run edge to edge. Elementor does this
 * at runtime: its StretchElement module measures the section, then writes
 * `style="width: 1440px; left: -216px"` onto it and recomputes on every resize.
 *
 * We do not ship Elementor's frontend JS, so the class survived and nothing
 * acted on it — every section stayed boxed at 70% width with 216px of white
 * either side. Visible on the home page as a band of white left and right of
 * everything below the hero, and on 225 other pages.
 *
 * Reproduced in CSS, because the geometry is derivable. Percentages on `width`
 * and `left` resolve against the containing block — .elementor, whose width is
 * already (100% - 2p) of .page-content. So to fill .page-content:
 *
 *     width = 1 / (1 - 2p)        left = -p / (1 - 2p)
 *
 * Elementor's own CSS supplies `position: relative`, and using `left` rather
 * than a margin means the widened box does not disturb its siblings — the same
 * mechanism the JS used.
 *
 * Scoped under .page-content deliberately: single blog posts have no
 * .page-content wrapper at all, so their .elementor is already full width and
 * their stretched sections need no correction. Live measurement confirms both
 * shapes (see the padding ladder below, taken from the live site rather than
 * read off the stylesheet — the theme's 17% and 10% rules under 980px are
 * outranked by `.sidebar-show .page-content`, which pins it to a flat 30px).
 * ------------------------------------------------------------------------- */

/* 981px - 1399px: padding 10%  ->  1/0.8 = 125%, -0.1/0.8 = -12.5% */
.page-content .elementor-top-section.elementor-section-stretched {
	width: 125%;
	left: -12.5%;
}

/* >= 1400px: padding 15%  ->  1/0.7 = 142.857%, -0.15/0.7 = -21.429% */
@media all and (min-width: 1400px) {
	.page-content .elementor-top-section.elementor-section-stretched {
		width: 142.857143%;
		left: -21.428571%;
	}
}

/* <= 980px: padding is a flat 30px, so the breakout is absolute too. */
@media all and (max-width: 980px) {
	.page-content .elementor-top-section.elementor-section-stretched {
		width: calc(100% + 60px);
		left: -30px;
	}
}

/* ---------------------------------------------------------------------------
 * Essential Grid: the portfolio grids on 128 pages.
 *
 * The plugin ships every item as `visibility:hidden; display:none` and reveals
 * them from JS, which then absolutely positions each one and writes a pixel
 * height onto the <ul>. Without that JS the section collapsed to 362px against
 * live's 1013px -- six items present in the markup, none of them laid out.
 *
 * Same treatment as the blog archives: CSS Grid instead of shipping the
 * plugin's JS. Live's geometry, measured at ten viewport widths, is three
 * equal columns with a 10px gutter down to 640px and a single column below it,
 * so that is what this reproduces -- `1fr` columns rather than the plugin's
 * computed pixel widths, which lands within a pixel of live at every width.
 *
 * The images are handled at harvest time instead: the plugin's src is a
 * transparent placeholder with the real file in data-lazysrc, so
 * scripts/harvest-pages.mjs bakes the real URL into src. Nothing here needs to
 * touch them.
 *
 * `> ul` and not `.mainul`: that class is added by the plugin's JS, so it is
 * absent from the served markup.
 * ------------------------------------------------------------------------- */
/* Column count per breakpoint.
 *
 * The amounts come from each grid's own config, emitted as --esg-c<width>
 * custom properties by scripts/harvest-pages.mjs. The breakpoints are hard-coded
 * because all 42 grids on the site share the same eight.
 *
 * min-width, ascending, so the largest matching query wins -- the plugin picks
 * the largest configured width that is <= the viewport, NOT the smallest one
 * >= it. Measured against live: at 1100px a 6/4/3 grid renders 4 columns (the
 * 1024 bucket), not 6, and at 1000px it renders 3 (the 960 bucket).
 *
 * Below 480px nothing matches and the base rule applies, which is the 480
 * bucket -- the plugin's own behaviour at its narrowest.
 */
.esg-grid {
	--esg-cols: var(--esg-c480, 1);
}
@media (min-width: 480px) {
	.esg-grid {
		--esg-cols: var(--esg-c480, 3);
	}
}
@media (min-width: 640px) {
	.esg-grid {
		--esg-cols: var(--esg-c640, 3);
	}
}
@media (min-width: 778px) {
	.esg-grid {
		--esg-cols: var(--esg-c778, 3);
	}
}
@media (min-width: 960px) {
	.esg-grid {
		--esg-cols: var(--esg-c960, 3);
	}
}
@media (min-width: 1024px) {
	.esg-grid {
		--esg-cols: var(--esg-c1024, 3);
	}
}
@media (min-width: 1170px) {
	.esg-grid {
		--esg-cols: var(--esg-c1170, 3);
	}
}
@media (min-width: 1400px) {
	.esg-grid {
		--esg-cols: var(--esg-c1400, 3);
	}
}
@media (min-width: 1900px) {
	.esg-grid {
		--esg-cols: var(--esg-c1900, 3);
	}
}

.esg-grid > ul {
	display: grid;
	grid-template-columns: repeat(var(--esg-cols, 3), minmax(0, 1fr));
	gap: var(--esg-space, 10px);
	position: static;
	height: auto !important; /* the plugin's JS sets an inline pixel height */
}

.esg-grid > ul > li {
	/* .esg-grid li is display:none + visibility:hidden until the JS runs. */
	display: block;
	/* A tile is as tall as its own image, not stretched to the row's tallest:
	   on live's masonry grids 469x299 and 469x294 sit side by side. */
	align-self: start;
	visibility: visible;
	position: static;
	left: auto;
	top: auto;
	width: auto;
	transform: none;
}

/* The filter bar is `visibility:hidden` until the plugin's JS adds
   `esg-nav-by-shortcode` to an ancestor, which its own CSS keys the reveal off
   (.esg-nav-by-shortcode .esg-filter-wrapper{visibility:visible}). Applied
   directly here instead. Layout was already pixel-identical to live -- the bar
   occupied its 1060x58 box all along, just invisibly. */
.esg-filter-wrapper {
	visibility: visible;
}

/* The caption panel over each thumbnail. The plugin writes opacity:0 and
   visibility:hidden onto it inline at init and animates it in on hover, so
   without the JS every grid showed its titles burned permanently over the
   artwork. Restored as a CSS hover, which is what the interaction actually is.
   The plugin also parks the inner elements at scale(1.5) and a skew and
   animates them to rest; those are left at rest, so the panel fades rather than
   flying in. */
/* Hidden with opacity and pointer-events rather than visibility, which would
   also take its links out of the tab order, so :focus-within could never
   open it from the keyboard. */
.esg-entry-cover {
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.3s ease;
}

.esg-grid > ul > li:hover .esg-entry-cover,
.esg-grid > ul > li:focus-within .esg-entry-cover {
	opacity: 1;
	pointer-events: auto;
}

/* `filming-pages` repeats each destination on two text links, while the image
   itself is inert once Essential Grid's JavaScript is gone. pages.ts adds
   .naf-esg-tile only after it safely replaces those duplicates with one sibling
   anchor. The cover stays visual-only so it cannot intercept that native link. */
.esg-grid > ul > li.naf-esg-tile .esg-media-cover-wrapper {
	position: relative;
}

.esg-grid > ul > li.naf-esg-tile .naf-esg-tile-link {
	position: absolute;
	inset: 0;
	z-index: 3;
	display: block;
}

.esg-grid:where(:not(.naf-esg-ready)) > ul > li.naf-esg-tile .esg-entry-cover,
.esg-grid > ul > li.naf-esg-tile--expanded .esg-entry-cover {
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 5px;
	box-sizing: border-box;
	padding: 12px;
	pointer-events: none;
}

.esg-grid:where(:not(.naf-esg-ready)) > ul > li.naf-esg-tile .esg-entry-cover .esg-center,
.esg-grid > ul > li.naf-esg-tile--expanded .esg-entry-cover .esg-center {
	width: 100%;
	margin: 0;
	text-align: center;
}

.esg-grid:where(:not(.naf-esg-ready)) > ul > li.naf-esg-tile .esg-entry-cover .esg-line-break,
.esg-grid > ul > li.naf-esg-tile--expanded .esg-entry-cover .esg-line-break {
	display: none;
}

.esg-grid:where(:not(.naf-esg-ready)) > ul > li.naf-esg-tile .eg-filming-pages-element-11,
.esg-grid > ul > li.naf-esg-tile--expanded .eg-filming-pages-element-11 {
	font-size: 13px !important;
	line-height: 1.15;
	overflow-wrap: anywhere;
}

.esg-grid:where(:not(.naf-esg-ready)) > ul > li.naf-esg-tile .eg-filming-pages-element-10,
.esg-grid > ul > li.naf-esg-tile--expanded .eg-filming-pages-element-10 {
	font-size: 10px !important;
	line-height: 1.1;
	overflow-wrap: anywhere;
}

/* Rare harvested captions include entire case studies. Their card grows from a
   conservative line estimate instead of clipping or hiding copy. */
.esg-grid > ul > li.naf-esg-tile--expanded .esg-media-cover-wrapper {
	min-height: var(--naf-tile-min-height);
}

.esg-grid > ul > li.naf-esg-tile--expanded .esg-entry-media {
	position: absolute;
	inset: 0;
}

.esg-grid > ul > li.naf-esg-tile--expanded .esg-entry-media img.esg-entry-media-img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.esg-grid > ul > li.naf-esg-tile--expanded .eg-filming-pages-element-11 {
	font-size: 11px !important;
}

.esg-grid > ul > li.naf-esg-tile--expanded .eg-filming-pages-element-10 {
	font-size: 9px !important;
}

.esg-grid > ul > li.naf-esg-tile .naf-esg-tile-link:focus-visible {
	outline: 3px solid #fff;
	outline-offset: -3px;
}

@media (max-width: 639px) {
	.esg-grid:where(:not(.naf-esg-ready)) > ul > li.naf-esg-tile .eg-filming-pages-element-11,
	.esg-grid > ul > li.naf-esg-tile--expanded .eg-filming-pages-element-11 {
		font-size: 14px !important;
	}

	.esg-grid:where(:not(.naf-esg-ready)) > ul > li.naf-esg-tile .eg-filming-pages-element-10,
	.esg-grid > ul > li.naf-esg-tile--expanded .eg-filming-pages-element-10 {
		font-size: 11px !important;
	}

	.esg-grid > ul > li.naf-esg-tile--expanded .eg-filming-pages-element-11 {
		font-size: 12px !important;
	}

	.esg-grid > ul > li.naf-esg-tile--expanded .eg-filming-pages-element-10 {
		font-size: 10px !important;
	}
}

/* Filming-location cards have a single, verified destination in the harvested
   markup. Keep its label visible without Essential Grid's removed hover script,
   while the sibling anchor supplies one keyboard-accessible hit area. */
.esg-grid > ul > li.naf-esg-location-tile .esg-media-cover-wrapper {
	position: relative;
}

.esg-grid > ul > li.naf-esg-location-tile .naf-esg-tile-link {
	position: absolute;
	inset: 0;
	z-index: 3;
	display: block;
}

.esg-grid:where(:not(.naf-esg-ready)) > ul > li.naf-esg-location-tile .esg-entry-cover {
	display: flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	padding: 12px;
	opacity: 1;
	visibility: visible;
	transition: none;
	pointer-events: none;
}

.esg-grid:where(:not(.naf-esg-ready)) > ul > li.naf-esg-location-tile .esg-center {
	width: 100%;
	margin: 0;
	text-align: center;
}

.esg-grid:where(:not(.naf-esg-ready)) > ul > li.naf-esg-location-tile .esg-line-break {
	display: none;
}

.esg-grid:where(:not(.naf-esg-ready)) > ul > li.naf-esg-location-tile .eg-item-skin-2-element-12-a {
	margin: 0 !important;
}

.esg-grid:where(:not(.naf-esg-ready)) > ul > li.naf-esg-location-tile .eg-item-skin-2-element-12 {
	display: block;
	line-height: 1.15 !important;
	overflow-wrap: anywhere;
	text-shadow: 0 1px 2px rgb(0 0 0 / 0.75);
}

.esg-grid > ul > li.naf-esg-location-tile .naf-esg-tile-link:focus-visible {
	outline: 3px solid #fff;
	outline-offset: -3px;
}

.naf-esg-tile .naf-visually-hidden,
.naf-esg-location-tile .naf-visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* The filter script hides items with the `hidden` property. Without this the
   `display:block` above would win over the UA stylesheet's [hidden] rule and
   filtering would appear to do nothing. */
.esg-grid > ul > li[hidden] {
	display: none;
}

/* ---------------------------------------------------------------------------
 * Essential Grid with its behaviour restored (src/lib/esg-layout.ts,
 * esg-hover.ts, esg-entrance.ts).
 *
 * Everything above is the no-JavaScript layout: the cover hidden until hover
 * and its content in normal flow. esg-layout.ts adds `naf-esg-ready` once it has
 * built the plugin's top/centre/bottom containers, and from then on the tile
 * behaves as on live: the cover itself stays up, anything not marked
 * esg-transition (the skin-for-templates title bar, the location labels) is
 * always visible, and each esg-transition element is parked and animated by
 * esg-hover.ts through inline styles and the Web Animations API. CSS
 * transitions on those elements would outrank the animations in the cascade,
 * so they are switched off.
 * ------------------------------------------------------------------------- */
.esg-grid.naf-esg-ready > ul > li .esg-entry-cover {
	opacity: 1;
	visibility: visible;
	pointer-events: auto;
	transition: none;
}

.esg-grid.naf-esg-ready li .esg-overlay {
	opacity: 1;
	transition: none;
}

.esg-grid.naf-esg-ready li .esg-transition {
	transition: none !important;
}

/* The circlezoom overlay (skin-for-templates): two discs grow from the centre,
   the overlay's own colour quickly and data-animcolor over it more slowly. The
   plugin's settings.css scales them off `.esg-hovered`; the colours and timings
   come from a stylesheet its JS writes, reproduced here. Measured on
   /filming-in-austria/: red fills the tile within ~0.3s, then black covers it. */
.esg-grid.naf-esg-ready .esg-overlay[data-transition^='esg-circlezoom'] {
	background: transparent !important;
}

.esg-grid.naf-esg-ready .esg-overlay[data-transition^='esg-circlezoom']::before {
	transition: all 0.3s ease-out;
	background: var(--naf-esg-circle-bg);
}

.esg-grid.naf-esg-ready .esg-overlay[data-transition^='esg-circlezoom']::after {
	transition: transform 1s ease;
	background: var(--naf-esg-circle-color);
}

@media (prefers-reduced-motion: reduce) {
	.esg-grid.naf-esg-ready .esg-overlay[data-transition^='esg-circlezoom']::before,
	.esg-grid.naf-esg-ready .esg-overlay[data-transition^='esg-circlezoom']::after {
		transition: none;
	}
}

/* Even grids: the media box takes the plugin's rounded item height (see
   sizeEven in esg-layout.ts). Case-study tiles keep their grown box. */
.esg-grid.naf-esg-even > ul > li:not(.naf-esg-tile--expanded) .esg-entry-media {
	aspect-ratio: auto;
	height: var(--naf-esg-item-h, auto);
}

/* Masonry grids, packed by esg-layout.ts into independent columns. */
.esg-grid.naf-esg-masonry > ul {
	display: block;
	position: relative;
}

.esg-grid.naf-esg-masonry > ul > li {
	position: absolute;
}

/* Entrance (esg-entrance.ts): tiles wait, unseen, until the grid scrolls into
   view, and lazily loaded images then fade in over a blurred copy. Opacity
   rather than visibility, because esg-hover.ts sets visibility inline on the
   elements inside. */
.esg-grid.naf-esg-waiting > ul > li {
	opacity: 0;
	pointer-events: none;
}

@media print {
	.esg-grid.naf-esg-waiting > ul > li {
		opacity: 1;
	}
}

.esg-entry-media.naf-esg-lazy::before {
	content: '';
	position: absolute;
	inset: 0;
	background: var(--naf-esg-thumb) center / cover no-repeat;
	filter: blur(25px);
}

.esg-entry-media.naf-esg-lazy > img {
	position: relative;
	z-index: 1;
}

/* ---------------------------------------------------------------------------
 * The navigation menu.
 *
 * header#sidebar is display:none until the site's own jQuery fadeToggles it in
 * (see src/lib/menu-toggle.ts). The button that does it -- <div class="Toggle_dv">
 * -- hangs off <body> outside both #content and the header, so it was missing
 * from the harvested chrome entirely and no page had a way to open its nav.
 *
 * The open state is driven from a class on <html> so the sidebar can be shown
 * without the script writing inline styles the way jQuery's fadeToggle does.
 * Everything else -- the bars morphing to a cross, the 285px content shift, the
 * body dimming -- is already in the harvested CSS under Span_style, padding_L
 * and Body_overlay.
 * ------------------------------------------------------------------------- */
html.is-menu-open header#sidebar {
	display: block;
	animation: naf-menu-fade 1s ease forwards;
}

/* The archived child stylesheet supplies the original accordion transition and
   submenu geometry. Its old script inserted a span; the replacement is a real
   button, reset to the same visual footprint with a CSS-drawn disclosure mark.
   `visibility:hidden` in that stylesheet also removes closed descendants from
   keyboard navigation while allowing the observed max-height/opacity motion. */
#menu .top-menu > li.parent {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
}

/* The source links are block-level. A flex row restores the source arrow's
   adjacent placement, while the full-width submenu is forced onto its own row. */
#menu .top-menu > li.parent > a {
	flex: 1 1 0;
	min-width: 0;
}

#menu .top-menu > li.parent > button.submenu-toggle-arrow {
	display: flex;
	flex: 0 0 30px;
	align-items: center;
	justify-content: center;
	margin-left: -2px;
	padding: 0;
	border: 0;
	background: transparent;
	font: inherit;
}

#menu .top-menu > li.parent > .sub-menu {
	flex-basis: 100%;
	width: 100%;
}

#menu .top-menu > li.parent > button.submenu-toggle-arrow::before {
	content: '';
	display: inline-block;
	border-top: 6px solid currentColor;
	border-right: 5px solid transparent;
	border-left: 5px solid transparent;
}

#menu .top-menu > li.parent > button.submenu-toggle-arrow:focus-visible,
#menu .top-menu > li.parent > .sub-menu a:focus-visible {
	outline: 2px solid #fff;
	outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
	#menu .top-menu > li.parent > .submenu-toggle-arrow,
	#menu .top-menu > li.parent > .sub-menu,
	#menu .top-menu > li.parent > .sub-menu li,
	#menu .top-menu > li.parent > .sub-menu li a {
		transition: none;
	}
}

@keyframes naf-menu-fade {
	from {
		opacity: 0;
	}
	to {
		opacity: 1;
	}
}

@media (prefers-reduced-motion: reduce) {
	html.is-menu-open header#sidebar {
		animation: none;
	}
}

/* Essential Grid media ratio.
 *
 * 24 of the 42 grids force their thumbnails to a fixed ratio -- 7:4, 4:3, 3:3
 * or 1:1 -- rather than using each image's own aspect. The plugin does it by
 * building a .esg-media-poster div from JS and sizing that; we keep the real
 * <img>, so the ratio is applied to its box instead.
 *
 * The properties are emitted per grid by scripts/harvest-pages.mjs and are
 * absent on the 18 grids with no configured ratio, where the fallbacks leave
 * the image at its natural height. On /filming-in-tirana/ this is the
 * difference between a 250x166 thumbnail and live's 250x188.
 */
.esg-entry-media {
	aspect-ratio: var(--esg-ratio, auto);
}

.esg-entry-media img.esg-entry-media-img {
	/* Block, so the media box is exactly the image: inline, it added the line's
	   descender below it and every tile came out 7px taller than live's. */
	display: block;
	height: var(--esg-img-h, auto);
	object-fit: var(--esg-img-fit, fill);
}

/* ---------------------------------------------------------------------------
 * Elementor accordion and toggle panels.
 *
 * The plugin's CSS ships .elementor-tab-content as display:none and its JS is
 * what opens a panel. Without that JS every panel on the 93 accordion pages and
 * 11 toggle pages was shut with no way to open one, so the text sat in the DOM
 * unreachable -- a content bug rather than a layout one.
 *
 * src/lib/accordion.ts toggles .elementor-active; this is the rule that makes
 * it mean something. The plugin already styles the open/closed icons off the
 * same class on the title, so those follow for free. (0,2,0) beats the
 * plugin's own (0,1,0) display:none.
 * ------------------------------------------------------------------------- */
.elementor-tab-content.elementor-active {
	display: block;
}

/* ---------------------------------------------------------------------------
 * WPBakery full-width rows, on the 6 pages built with it rather than Elementor.
 *
 * The same shape as the Elementor stretch above, from a different plugin. A row
 * marked data-vc-full-width is served with a transient `-temp` attribute whose
 * CSS pulls it full-bleed:
 *
 *     .vc_row[data-vc-full-width-temp] { margin-left: calc(-50vw + 50%); width: 100vw }
 *
 * WPBakery's JS then replaces that with measured left/width/padding, where the
 * padding puts the row's CONTENT back at the column width -- the row stretches,
 * the text does not. Without the JS the page stayed in the temp state, so body
 * copy ran the full 1,410px instead of live's 1,008px, wrapped less, and
 * /contact-us/ came out 276px short.
 *
 * `data-vc-full-width` alone (no data-vc-stretch-content) is exactly the
 * "stretch row, not content" case, which is what every row here uses.
 *
 * Geometry: the row's containing block is already (100% - 2p) of .page-content,
 * so filling it is width = 1/(1-2p) and left = -p/(1-2p), with the padding
 * matching the offset. The 15px is vc_row-fluid's own gutter, which live keeps
 * as a negative margin -- hence the calc() rather than a bare percentage.
 * ------------------------------------------------------------------------- */
.page-content .vc_row[data-vc-full-width] {
	position: relative;
	box-sizing: border-box;
	margin-left: -15px;
	margin-right: -15px;
	/* 981px - 1399px: .page-content is padded 10% */
	width: 125%;
	left: calc(15px - 12.5%);
	padding-left: calc(12.5% - 15px);
	padding-right: calc(12.5% - 15px);
}

@media all and (min-width: 1400px) {
	.page-content .vc_row[data-vc-full-width] {
		width: 142.857143%;
		left: calc(15px - 21.428571%);
		padding-left: calc(21.428571% - 15px);
		padding-right: calc(21.428571% - 15px);
	}
}

@media all and (max-width: 980px) {
	.page-content .vc_row[data-vc-full-width] {
		width: calc(100% + 60px);
		left: -15px;
		padding-left: 15px;
		padding-right: 15px;
	}
}

/* Elementor Gallery responsive columns.
   e-gallery.min.css reads a single --columns; the widget stores three values and
   its JS swaps them per breakpoint. The harvester emits all three as
   --columns-desktop/tablet/mobile and these rules pick one — it cannot set
   --columns itself, because an inline custom property would outrank every media
   query below. Breakpoints are Elementor's own defaults. */
.e-gallery-grid {
	--columns: var(--columns-desktop, 6);
}
@media (max-width: 1024px) {
	.e-gallery-grid {
		--columns: var(--columns-tablet, 6);
	}
}
@media (max-width: 767px) {
	.e-gallery-grid {
		--columns: var(--columns-mobile, 2);
	}
}

/* Animated heroes converted to video (scripts/convert-heroes.mjs).
   The section keeps its own background — now the poster frame — and the video
   sits on top of it, under the section's content. `pointer-events: none` so it
   never intercepts a click meant for the hero's buttons. */
.naf-hero-video {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	z-index: 0;
	pointer-events: none;
}
:is(.elementor-section, .vc_row, .elementor-widget):has(> .naf-hero-video) {
	position: relative;
}
/* Everything after the video in its own box sits above it. This was two
   selectors naming Elementor's section children, which missed the six pages
   where the hero is a text-editor widget rather than a section background
   (/production-assistants/, /pre-production/, /video-production-services/,
   /film-production-services/, /documentary-production/ and the post-production
   page): there the video is the widget's first child and the H1 follows it in
   the widget container, so the video painted over the page title. The video
   carries pointer-events:none, which is why the title still answered a
   hit-test while being invisible. */
.naf-hero-video ~ * {
	position: relative;
	z-index: 1;
}

/* WordPress's FitVids runtime wraps this Vimeo iframe in a second 16:9 box.
   The static migration does not ship that runtime, so the source widget's
   compensating negative margin pulls the unwrapped player up behind the Dubai
   hero. Reset that one source-widget margin; its inline aspect-ratio wrapper
   then places the live iframe in normal flow after the page heading. */
body.page-id-19637 .vc_custom_1733148637451 {
	margin-top: 0 !important;
}

/* The Riyadh content player is the Saudi Arabia page at this harvest date;
   `/filming-in-riyadh/` is not a published route. FitVids normally turns the
   source 1920×1080 iframe into its container's 16:9 size. Preserve that source
   ratio in normal flow without applying an iframe rule to other players. */
body.page-id-19629 .vc_custom_1734009381409 iframe[src*='player.vimeo.com/video/913369876'] {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 9;
}

/* This harvested Adidas post has a bare 560×315 YouTube iframe. WordPress's
   FitVids runtime grows it to the article column; the static build does not.
   Keep the correction to this confirmed route and provider frame so other
   case-study embeds retain their source-specific sizing. */
body.postid-14748 .post-content iframe[src='https://www.youtube.com/embed/5Qc4ecsWj8w'] {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 560 / 315;
}

/* ---------------------------------------------------------------------------
 * WPBakery toggles and waypoint reveals.
 *
 * The source CSS hides both toggle content and every waypoint candidate until
 * WPBakery's runtime changes them. The static build must instead fail visible:
 * no JavaScript, an unsupported observer, and unrecognised effect classes all
 * retain readable content. src/lib/wpbakery.ts then enhances only source-backed
 * right-to-left reveals and the vc_toggle controls.
 * ------------------------------------------------------------------------- */
.vc_toggle > .vc_toggle_content {
	display: block;
}

.vc_toggle > .vc_toggle_content[hidden] {
	display: none !important;
}

.vc_toggle > .vc_toggle_title[role='button'] {
	cursor: pointer;
}

.vc_toggle > .vc_toggle_title[role='button']:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 3px;
}

/* Do not allow an uninitialised or unknown WPBakery effect to hide content. */
.wpb_animate_when_almost_visible {
	filter: none;
	opacity: 1;
	transform: none;
}

/* WPBakery's confirmed right-to-left effect starts at translateX(10%). Its
   effect rule declares 0.7s, but the source `.animated` cascade produces the
   measured 1s browser duration on both original and local pages; do not override
   that effective parity. This state exists only after a supported observer. */
html.naf-wpb-reveals-ready
	.wpb_animate_when_almost_visible.wpb_right-to-left:not(.wpb_start_animation) {
	animation: none;
	filter: alpha(opacity=0);
	opacity: 0;
	transform: translateX(10%);
}

/* animate.css's fadeInRight supplies its own starting offset, so the waiting
   state only needs to be invisible. */
html.naf-wpb-reveals-ready
	.wpb_animate_when_almost_visible.wpb_fadeInRight:not(.wpb_start_animation) {
	animation: none;
	opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
	html.naf-wpb-reveals-ready .wpb_animate_when_almost_visible.wpb_right-to-left,
	html.naf-wpb-reveals-ready .wpb_animate_when_almost_visible.wpb_fadeInRight {
		animation: none;
		filter: none;
		opacity: 1;
		transform: none;
		transition: none;
	}
}

/* The source's declared 5 s TTA autoplay is supplemented by an explicit,
   visible pause control. It follows the widget so the original panel and dot
   layout remain intact, and its wrapping text cannot overflow narrow screens. */
.naf-testimonial-autoplay-control {
	background: transparent;
	border: 1px solid currentColor;
	border-radius: 2px;
	color: inherit;
	display: block;
	font: inherit;
	line-height: 1.2;
	margin: 0 auto 1rem;
	max-width: 100%;
	min-height: 44px;
	padding: 0.4rem 0.75rem;
	white-space: normal;
}

.naf-testimonial-autoplay-control:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 3px;
}

/* Confirmed private-video stills retain the source widget's 16:9 slot without
   retaining a video affordance or a YouTube request. */
.naf-private-video-replacement .naf-private-video-still-frame {
	aspect-ratio: var(--video-aspect-ratio, 16 / 9);
	overflow: hidden;
}

.naf-private-video-replacement .naf-private-video-still {
	display: block;
	height: 100%;
	object-fit: cover;
	object-position: center;
	width: 100%;
}

/* Classic-editor posts: the still sits inside a <p>, so it is built from spans,
   capped at the 560px of the iframe it replaces. */
.naf-private-video-replacement--inline,
.naf-private-video-replacement--inline .naf-private-video-still-frame {
	display: block;
	max-width: 560px;
}

/* ---------------------------------------------------------------------------
 * Elementor buttons inside a block painted their own colour.
 *
 * src/lib/button-tiles.ts marks the block, the wrappers between it and the
 * link, and the link. The link's ::after then covers the whole block, so the
 * area that looks like the button is the area that follows it.
 * ------------------------------------------------------------------------- */
.naf-button-tile {
	position: relative;
}

/* frontend.min.css makes every .elementor-widget relative. Left in place, the
   stretched link would stop at the widget's box instead of the block's. */
.naf-button-tile .naf-button-tile-path {
	position: static;
}

.naf-button-tile-link::after {
	content: '';
	position: absolute;
	inset: 0;
}

/* ---------------------------------------------------------------------------
 * Photo cards whose heading link covers the whole photo.
 *
 * scripts/lib/service-card-links.mjs gives the dead `#` links on
 * /video-production/'s five photo cards a destination and marks the widget.
 * frontend.min.css already makes the widget relative, so the link's ::after
 * spans the card, photo included.
 * ------------------------------------------------------------------------- */
.naf-card-link .elementor-heading-title a::after {
	content: '';
	position: absolute;
	inset: 0;
}

/* ---------------------------------------------------------------------------
 * The Formspree forms hide themselves with the `hidden` attribute on success.
 *
 * The theme's `.clearfix, form, .payment_methods li { display: inline-block }`
 * outranks the browser's own `[hidden] { display: none }`, so the form stayed
 * on screen after a successful send and the thank-you message appeared a full
 * form's height below it, out of sight.
 * ------------------------------------------------------------------------- */
.naf-form-wrap [hidden] {
	display: none;
}

/* The confirmation that replaces the form. Styled here rather than in the two
   form components so both get it, and with its own light panel because the
   contact form sits on black sections and the network form on white — text
   inheriting the section colour was black on black on /contact-us/. */
/* :not([hidden]) because this rule comes after the [hidden] one above with the
   same specificity, so it used to win and show the empty panel as a white bar
   under every form's submit button. */
.naf-form-wrap .naf-form__thanks:not([hidden]) {
	/* A new block formatting context, as the theme gives <form> with
	   inline-block: on /our-global-network/ the form sits beside floated photo
	   tiles, and a plain block ran its background and bar underneath them. */
	display: flow-root;
	padding: 1.25rem 1.5rem;
	border-left: 4px solid #f7941d;
	background: #fff;
	color: #111;
	font-size: 1.15rem;
	line-height: 1.5;
	outline: none;
}

/* Essential Grid's hover overlay.
 *
 * `.esg-overlay` is the black-at-65% layer the skins fade in when you hover a
 * tile. Its resting state is set by the plugin's JavaScript, which this rebuild
 * does not ship -- so it sat at the CSS default of opacity 1 and every tile was
 * permanently dark. Measured against live on 2026-09-17: live rests the overlay
 * at opacity 0 and takes it to 1 on hover, on both the `item-skin-2` tiles
 * (/filming-locations/) and the `filming-pages` tiles (/film-fixers/,
 * /portfolio/); ours was at 1 on all three.
 *
 * On the project skin the whole `.esg-entry-cover` is hidden at rest anyway, so
 * the overlay was invisible there and only the location tiles looked wrong. The
 * rule is written for both so the two skins do not drift apart again.
 *
 * :focus-within gives the same reveal to keyboard users, which the plugin's
 * mouse-only handler never did.
 */
.esg-grid li .esg-overlay {
	opacity: 0;
	transition: opacity 0.3s ease;
}

.esg-grid li:hover .esg-overlay,
.esg-grid li:focus-within .esg-overlay {
	opacity: 1;
}

/* Image lightbox (src/lib/lightbox.ts), standing in for esgbox and the theme's
   fancybox. Colours and control placement follow esgbox as measured on live:
   a rgb(30,30,30) backdrop, counter top-left, close top-right, arrows at the
   vertical centre of each edge on rgba(30,30,30,0.6) squares. */
html.naf-lightbox-open {
	overflow: hidden;
}

.naf-lightbox {
	align-items: center;
	background: rgba(30, 30, 30, 0.9);
	display: flex;
	inset: 0;
	justify-content: center;
	padding: 44px;
	position: fixed;
	z-index: 100000;
}

.naf-lightbox[hidden] {
	display: none;
}

.naf-lightbox__image {
	box-shadow: 0 0 40px rgba(0, 0, 0, 0.5);
	max-height: 100%;
	max-width: 100%;
	object-fit: contain;
}

.naf-lightbox__counter {
	color: #ccc;
	font: 13px/44px sans-serif;
	left: 0;
	min-width: 44px;
	padding: 0 10px;
	position: absolute;
	top: 0;
}

.naf-lightbox__button {
	align-items: center;
	background: rgba(30, 30, 30, 0.6);
	border: 0;
	color: #ccc;
	cursor: pointer;
	display: flex;
	font-size: 22px;
	height: 44px;
	justify-content: center;
	padding: 0;
	position: absolute;
	width: 44px;
}

.naf-lightbox__button:hover,
.naf-lightbox__button:focus-visible {
	color: #fff;
}

.naf-lightbox__button:focus-visible {
	outline: 2px solid #fff;
	outline-offset: -4px;
}

.naf-lightbox__button[hidden] {
	display: none;
}

.naf-lightbox__close {
	right: 0;
	top: 0;
}

.naf-lightbox__prev,
.naf-lightbox__next {
	top: 50%;
	transform: translateY(-50%);
}

.naf-lightbox__prev {
	left: 0;
}

.naf-lightbox__next {
	right: 0;
}

@media (max-width: 600px) {
	.naf-lightbox {
		padding: 44px 8px;
	}
}

/* FitVids' wrapper (scripts/lib/fit-vids.mjs adds it at build time), with the
   rules FitVids injects as a <style> on live: the wrapper holds the aspect
   ratio in its padding-top and the iframe fills it. */
.fluid-width-video-wrapper {
	padding: 0;
	position: relative;
	width: 100%;
}

.fluid-width-video-wrapper iframe,
.fluid-width-video-wrapper object,
.fluid-width-video-wrapper embed {
	height: 100%;
	left: 0;
	position: absolute;
	top: 0;
	width: 100%;
}

/* The blog carousel's slides link to their posts (scripts/lib/loop-carousel-links.mjs),
   which live's do not. The link wraps the slide's own sections, so it has to be a
   block and must not restyle what it contains. */
.naf-loop-item-link {
	color: inherit;
	display: block;
	text-decoration: none;
}

.naf-loop-item-link:focus-visible {
	outline: 2px solid #f7941d;
	outline-offset: -2px;
}
