/**
 * Industry Showcase.
 *
 * A tall section with a panel pinned inside it. Scrolling the section walks a
 * list of industries; the panel stays put and its three columns — the names,
 * the picture, the words — change under it.
 *
 * Built for a dark section. Everything that carries a colour reads it from a
 * custom property the widget writes from its Elementor controls, so the
 * defaults here are what it looks like with nothing set, not the only thing it
 * can look like.
 *
 * @package ErudaToolkit
 */

/* Owned rather than assumed. Elementor sets border-box globally and a theme
   usually does too, but this widget sizes a pinned panel in viewport units and
   then pads it: under content-box that padding is added to the height, the
   panel ends up taller than the screen it is pinned to, and its last line can
   never be scrolled into view because it is pinned. Measured at 979px on an
   844px screen. */
.eind,
.eind *,
.eind *::before,
.eind *::after {
	box-sizing: border-box;
}

.eind {
	--eind-ink: #ffffff;
	--eind-muted: rgba( 255, 255, 255, 0.34 );
	--eind-body: rgba( 255, 255, 255, 0.72 );
	--eind-accent: #7ab648;
	--eind-line: rgba( 255, 255, 255, 0.18 );
	--eind-frame: rgba( 255, 255, 255, 0.06 );

	/* Layout. Every one of these is an Elementor control; the values here are
	   what it looks like with nothing set. */
	/* These stay responsive until a control replaces them, which is why the
	   controls ship with no default of their own: an Elementor default is
	   written out on every page and would flatten the clamp to one number at
	   every width. */
	--eind-frame-w: clamp( 260px, 30vw, 400px );
	--eind-ratio: 3 / 4;
	--eind-gap: clamp( 24px, 5vw, 72px );
	--eind-pad: clamp( 20px, 4vw, 48px );
	--eind-text-w: clamp( 300px, 32vw, 440px );
	--eind-stack-gap: 16px;
	--eind-list-gap: clamp( 8px, 1.4vh, 16px );

	/* The grid reveal. Block size is in px and the script turns it into a cell
	   count from the frame's measured size, so the blocks stay square whatever
	   shape the frame is. */
	--eind-block: 44px;
	--eind-wipe: 900ms;
	--eind-band: var( --eind-accent );
	--eind-jitter: 0.55;

	--eind-fade: 0.6s;
	--eind-settle: 1.2s;
	--eind-ease: cubic-bezier( 0.22, 1, 0.36, 1 );

	/* Scroll progress through the section, 0 to 1. The script writes it. */
	--eind-p: 0;

	position: relative;
}

/* The panel that stays still while the section moves past it. min-height
   rather than height alone: on a short laptop window a 100vh panel would clip
   its own text, and a panel that clips is worse than one that scrolls. */
.eind__pin {
	position: sticky;
	top: 0;

	/* svh, not vh. On a phone 100vh is the viewport with the browser's own
	   toolbars hidden, which is taller than what is actually on screen -- so a
	   panel sized in vh puts its own last line under the address bar, and
	   nothing inside it ever comes into view because the panel is pinned. svh
	   is the viewport at its smallest, which is the one that always fits. */
	height: 100vh;
	height: 100svh;
	min-height: 480px;
	overflow: hidden;
	display: grid;

	/* The picture takes the width it is given and the words and names share
	   what is left, so the panel spans whatever section it is dropped into
	   rather than sitting in a column of its own. min() keeps it inside a
	   narrow window when the setting is wider than the screen. */
	grid-template-columns: 1fr min( var( --eind-frame-w ), 46vw ) 1fr;
	align-items: center;
	gap: var( --eind-gap );
	padding: 0 var( --eind-pad );
}

.eind__label {
	position: absolute;
	top: clamp( 20px, 4vh, 40px );
	left: 0;
	right: 0;
	text-align: center;
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var( --eind-muted );
}

.eind__count {
	position: absolute;
	right: clamp( 20px, 4vw, 48px );
	bottom: clamp( 20px, 4vh, 36px );
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.1em;
	color: var( --eind-muted );
	font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------- the bed --- */

/* A background that belongs to the pinned panel rather than to the section.
   A background set on the section scrolls with the section -- which is the
   whole length of the showcase -- so a pattern on it slides past while the
   panel in front of it stays still, and the two read as unrelated. Painted
   here it is pinned along with everything else and genuinely holds still.

   It is empty until something is set on it, so a widget that wants the
   section's own background still gets it. */
.eind__bed {
	position: absolute;
	inset: 0;
	z-index: 0;
	pointer-events: none;
}

/* Keeps the content above the bed.

   The label and the counter are left out of the position half deliberately:
   both are already absolutely positioned, and giving them `position: relative`
   put them back into the grid as real items — five children in a three-column
   grid, which wraps to a second row and pushes the whole panel out of its own
   pinned box. They only need the stacking order. */
.eind__label,
.eind__count {
	z-index: 1;
}

.eind__list,
.eind__media,
.eind__text {
	position: relative;
	z-index: 1;
}

/* ------------------------------------------------------------ the list --- */

.eind .eind__list {
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: var( --eind-list-gap );
	margin: 0;
	padding: 0;
	list-style: none;
}

.eind .eind__list > li {
	margin: 0;
	padding: 0;
	list-style: none;
}

.eind .eind__list > li::before,
.eind .eind__list > li::marker {
	content: none;
}

/* These are <button> elements, and every theme in existence styles buttons: a
   fill, a radius, a border, a shadow, a min-height, uppercase letters. A
   single class loses to most of what a theme writes, so this carries three and
   puts back every property a button might have been given rather than only the
   ones that have bitten so far. It has to read as a line of text in a list,
   not as a control. */
.eind .eind__list .eind__name {
	display: flex;
	align-items: center;
	gap: 12px;
	width: auto;
	min-width: 0;
	min-height: 0;
	margin: 0;
	padding: 4px 0;
	border: 0;
	border-radius: 0;
	background: none;
	background-color: transparent;
	background-image: none;
	box-shadow: none;
	text-shadow: none;
	cursor: pointer;
	font: inherit;
	font-size: clamp( 16px, 1.5vw, 21px );
	font-weight: inherit;
	line-height: 1.2;
	letter-spacing: inherit;
	text-align: right;
	text-transform: none;
	text-decoration: none;
	color: var( --eind-muted );
	appearance: none;
	-webkit-appearance: none;
	transition: color 0.35s var( --eind-ease );
}

/* A theme's hover and focus fills have to go the same way, or the button
   reappears the moment it is pointed at or tabbed to. */
.eind .eind__list .eind__name:hover,
.eind .eind__list .eind__name:focus,
.eind .eind__list .eind__name:focus-visible,
.eind .eind__list .eind__name:active,
.eind .eind__list .eind__name[ aria-current="true" ] {
	background: none;
	background-color: transparent;
	background-image: none;
	border: 0;
	box-shadow: none;
}

/* Which leaves keyboard users with nothing, so it is given back deliberately
   as something that belongs here: an offset ring in the accent colour, shown
   only for keyboard focus and never for a mouse click. */
.eind .eind__list .eind__name:focus {
	outline: none;
}

.eind .eind__list .eind__name:focus-visible {
	outline: 2px solid var( --eind-accent );
	outline-offset: 4px;
}

/* The dot only exists for the current one. Scaled rather than faded so it
   arrives with the name rather than behind it. */
.eind .eind__list .eind__name::before {
	content: "";
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var( --eind-accent );
	transform: scale( 0 );
	transition: transform 0.35s var( --eind-ease );
}

.eind .eind__list .eind__name:hover,
.eind .eind__list .eind__name:focus-visible {
	color: var( --eind-body );
}

.eind .eind__list .eind__name[ aria-current="true" ] {
	color: var( --eind-ink );
}

.eind .eind__list .eind__name[ aria-current="true" ]::before {
	transform: scale( 1 );
}

/* ----------------------------------------------------------- the frame --- */

.eind__media {
	display: flex;
	align-items: flex-end;
	gap: 14px;
}

.eind__frame {
	position: relative;
	flex: 1 1 auto;
	aspect-ratio: var( --eind-ratio );
	max-height: 78vh;
	border-radius: 20px;
	overflow: hidden;
	background: var( --eind-frame );
}

/* The picture fills its frame and is cropped to do it, rather than being
   fitted inside it with bars down the sides: the frame's proportions are a
   design decision and the photograph's are an accident of whoever took it.

   Every picture is stacked in the same box and only one is lit. The incoming
   one settles out of a 6% overscale over twice the length of its fade, which
   is what stops the change reading as a blink: the fade is done while the
   movement is still finishing. */
.eind .eind__frame .eind__shot {
	display: block;
	position: absolute;
	inset: 0;

	/* Written three ways on purpose. inset alone stretches the box, but a
	   framework reset that sets `height: auto` on every img wins on
	   specificity against a single class -- Elementor ships
	   `.elementor img { height: auto; max-width: 100% }`, which is a class and
	   an element and therefore outranks `.eind__shot`. Three classes here beat
	   it outright, and min/max pin the box even if something later sets a
	   height of its own. */
	width: 100%;
	height: 100%;
	min-width: 100%;
	min-height: 100%;
	max-width: none;
	max-height: none;
	object-fit: cover;
	object-position: center;
	opacity: 0;
	transform: scale( 1.06 );
	transition:
		opacity var( --eind-fade ) ease,
		transform var( --eind-settle ) var( --eind-ease );
}

.eind .eind__frame .eind__shot--on {
	opacity: 1;
	transform: scale( 1 );
}

.eind__ticks {
	display: flex;
	flex-direction: column;
	gap: 9px;
	align-items: center;
	padding-bottom: 4px;
}

.eind__tick {
	width: 4px;
	height: 4px;
	border-radius: 4px;
	background: var( --eind-line );
	transition: height 0.4s var( --eind-ease ), background 0.4s ease;
}

.eind__tick--on {
	height: 34px;
	background: var( --eind-ink );
}

/* ------------------------------------------------------ the grid wipe --- */

/* How the picture changes. Not a crossfade: a hard edge sweeps across the
   frame and the new picture appears behind it, while a ragged band of coloured
   blocks rides the edge. The blocks are what make it read as a pattern rather
   than as a wipe -- each one is offset by its own number, so the front eats
   the old picture unevenly.
 *
 * The reference does this on the GPU, quantising the image in a fragment
 * shader. Nothing here needs to: a grid of cells, a position along one axis
 * and one random number per cell is the whole of it, and this way the
 * photograph stays a real img that can be selected, saved and read by a screen
 * reader. */
.eind__grid {
	position: absolute;
	inset: 0;
	z-index: 2;
	display: grid;
	pointer-events: none;
}

.eind__cell {
	background: var( --eind-band );
	opacity: 0;
	will-change: opacity;

	/* Each block peaks at its own opacity, not at full. Without it the middle
	   of the band is a solid slab of colour with a ragged edge either side;
	   with it the whole band is mottled, which is what the reference's shader
	   gets by folding its random number into the alpha rather than only into
	   the position. */
}

/* The picture being replaced stays put underneath until the front has passed
   over it. Without this the old one vanishes the moment the new one is marked
   and the front sweeps across an empty frame. */
.eind__shot--out {
	opacity: 1;
	transform: none;
	transition: none;
	z-index: 0;
}

.eind__frame--wipe .eind__shot--on {
	z-index: 1;
	opacity: 1;
	transform: none;
	transition: none;
	animation: eind-reveal var( --eind-wipe ) linear both;
}

.eind__frame--wipe .eind__cell {
	animation: eind-block var( --eind-wipe ) linear both;

	/* The stagger has to be wider than the window a cell is lit for, or every
	   block is lit at the same moment and the band stops being a band. First
	   attempt spread the delays over 62% of the run while each cell stayed
	   visible for 63% of it, and the whole grid flashed at once. */
	animation-delay: calc( var( --eind-wipe ) * var( --d, 0 ) * 0.75 );
}

/* On, briefly, then off. The lit window is about a fifth of the run against a
   three-quarter stagger, which puts roughly a quarter of the grid alight at
   once -- a band crossing the frame rather than the frame flashing. The
   reference's shader draws a band 20% deep for the same reason. */
@keyframes eind-block {
	0% { opacity: 0; }
	8% { opacity: var( --o, 1 ); }
	20% { opacity: var( --o, 1 ); }
	30% { opacity: 0; }
	100% { opacity: 0; }
}

/* The hard edge. inset() is clipped from the side the front comes from, and
   the script writes --eind-reveal-from to match the direction it was given. */
/* Finishing at 83% rather than at the end keeps the edge under the band: the
   blocks stop arriving at three quarters plus their own lead-in, and a clip
   that ran the full duration would trail along behind them uncovered. */
@keyframes eind-reveal {
	0% { clip-path: var( --eind-reveal-from, inset( 0 0 100% 0 ) ); }
	83% { clip-path: inset( 0 0 0 0 ); }
	100% { clip-path: inset( 0 0 0 0 ); }
}

/* ----------------------------------------------------------- the words --- */

/* Fixed height with the panels stacked inside it, so the column does not
   change height as the text does and shunt everything beside it. */
.eind__text {
	position: relative;
	height: 340px;
	max-width: var( --eind-text-w );
}

.eind__panel {
	position: absolute;
	left: 0;
	right: 0;
	top: 50%;
	display: flex;
	flex-direction: column;
	gap: var( --eind-stack-gap );
	transform: translateY( -44% );
	opacity: 0;
	pointer-events: none;
	transition:
		opacity 0.45s ease,
		transform 0.6s var( --eind-ease );
}

.eind__panel--on {
	opacity: 1;
	transform: translateY( -50% );
	pointer-events: auto;
}

.eind__heading {
	margin: 0;
	color: var( --eind-ink );
	font-size: clamp( 26px, 3vw, 40px );
	line-height: 1.05;
}

.eind__body {
	margin: 0;
	color: var( --eind-body );
	font-size: clamp( 15px, 1.1vw, 17px );
	line-height: 1.6;
}

.eind__link {
	align-self: flex-start;
	margin-top: 4px;
	padding-bottom: 6px;
	color: var( --eind-ink );
	text-decoration: none;
	font-size: 15px;
	font-weight: 600;
	border-bottom: 1.5px solid currentColor;
	transition: color 0.2s ease;
}

.eind__link:hover,
.eind__link:focus-visible {
	color: var( --eind-accent );
}

/* ------------------------------------------------- tablet and phone --- */

/* It does not become a list. The one-at-a-time, scroll-driven presentation is
   the whole idea, and the reference keeps it at every width -- only the
   arrangement changes: the names go, the picture takes the upper part, the
   words sit under it, and the ticks move to the edge of the screen.

   A pinned panel read from scroll position is fine on touch. What is not fine
   is translating content against the scroll, which is a different technique
   and not this one: nothing here captures a gesture, and position: sticky does
   the holding. */
@media ( max-width: 900px ) {
	.eind__pin {
		/* One column: the picture and the words share the height rather than
		   sitting beside each other. */
		grid-template-columns: 1fr;
		grid-template-rows: minmax( 0, 1fr ) auto;
		align-items: stretch;
		gap: clamp( 18px, 4vh, 32px );
		padding: clamp( 56px, 10vh, 96px ) var( --eind-pad ) clamp( 24px, 6vh, 56px );
	}

	/* Navigation is by scrolling here, and the ticks say where you are. */
	.eind .eind__list {
		display: none;
	}

	.eind__media {
		/* Static on purpose. It is position: relative at desktop widths for
		   stacking, and while it stays that way the ticks below anchor to it
		   rather than to the panel -- which on a tablet parks them inside the
		   content column instead of at the edge of the screen. */
		position: static;
		min-height: 0;
		align-items: stretch;
	}

	/* The picture fills the room it is given rather than holding a shape. On a
	   tall narrow screen a 3:4 frame either leaves the words no space or
	   leaves a band of nothing above them. */
	.eind .eind__frame {
		aspect-ratio: auto;
		width: 100%;
		height: 100%;
		max-height: none;
	}

	/* Off the picture and onto the edge of the screen, which is where the
	   reference puts them and the only place they do not crowd it. */
	.eind__ticks {
		position: absolute;
		right: max( 10px, calc( var( --eind-pad ) / 2 ) );
		top: 50%;
		transform: translateY( -50% );
		padding-bottom: 0;
		z-index: 2;
	}

	/* The words become a row of their own, so the column stops being a fixed
	   box with panels floating inside it. The one showing takes part in the
	   layout; the rest stay out of it. */
	.eind__text {
		position: relative;
		height: auto;
		min-height: 0;
		max-width: none;
	}

	.eind__panel {
		position: absolute;
		inset: auto 0 0 0;
		top: auto;
		transform: translateY( 8px );
	}

	.eind__panel--on {
		position: relative;
		transform: translateY( 0 );
	}

	.eind__heading {
		font-size: clamp( 24px, 5.5vw, 34px );
	}

	.eind__count {
		right: var( --eind-pad );
	}
}

/* Wider than a phone but still one column: the words are a column rather than
   the whole width, the way the reference has them on a tablet. */
@media ( min-width: 601px ) and ( max-width: 900px ) {
	.eind__text {
		width: 100%;
		max-width: 520px;
		margin: 0 auto;
	}
}

/* ------------------------------------------------------ without script --- */

/* The panel shows one industry and scrolling decides which. With no script
   there is nothing to decide it, so the stack -- which is in the markup either
   way -- takes over and every industry can still be read. The widget itself
   prints the rule that does the swapping, inside a noscript block. */
.eind__stack {
	display: none;
}

.eind__stack .eind__card {
	display: flex;
	flex-direction: column;
	gap: 12px;
}

.eind .eind__stack .eind__card img {
	display: block;
	width: 100%;
	height: auto;
	max-width: 100%;
	aspect-ratio: var( --eind-ratio );
	object-fit: cover;
	object-position: center;
	border-radius: 16px;
	margin-bottom: 4px;
}

/* ----------------------------------------------------- reduced motion --- */

/* The section still works: it is a list, some pictures and some words, and
   scrolling still changes which one is showing. What stops is the movement. */
@media ( prefers-reduced-motion: reduce ) {
	.eind .eind__frame .eind__shot,
	.eind__panel,
	.eind__tick,
	.eind .eind__list .eind__name,
	.eind .eind__list .eind__name::before {
		transition: none;
	}

	/* Has to match the specificity the sizing rules were raised to, or the
	   picture keeps its overscale and its fade. */
	.eind .eind__frame .eind__shot,
	.eind .eind__frame .eind__shot--on {
		transform: none;
	}

	/* No sweeping front and no blocks. The picture still changes; it just
	   changes without a performance. */
	.eind__grid {
		display: none;
	}

	.eind__frame--wipe .eind__shot--on,
	.eind__frame--wipe .eind__cell {
		animation: none;
	}

	.eind__shot--out {
		opacity: 0;
	}
}
