/**
 * Elementor GSAP effects — base styles.
 *
 * Hover follower: an absolutely-positioned <img> that GSAP translates to the
 * cursor and never intercepts the pointer. With Clip on it lives inside the host
 * (clipped to it); with Clip off it is moved into a fixed, full-screen top layer
 * (.geeks-gsap-portal) so it can float above every container — its per-element
 * styles are copied inline by the engine before the move. It fades in only while
 * it is the active follower (.is-shown). Width / radius / shadow / etc. come from
 * the per-element Elementor controls.
 */
.geeks-gsap-hover {
	/* Clipped by default: clips the follower with Clip on, and stops the resting
	   (hidden) Clip-off follower from expanding the page before it is portaled. */
	overflow: hidden;
	/* Own default so a nested host never inherits its parent's offset. */
	--geeks-gsap-ox: 0px;
	--geeks-gsap-oy: 0px;
}

/* THE CONTAINING BLOCK, ONLY WHERE ONE IS MISSING.
 *
 * A clipped follower is positioned inside its host, so the host has to be a
 * containing block — but anything the author has already positioned IS one.
 * Writing `position: relative` on every host overrode an author's `absolute`
 * and moved their element back into the flow the moment the effect was switched
 * on. The engine reads the computed position and adds this class only where it
 * finds `static`. */
.geeks-gsap-hover.geeks-gsap-anchor {
	position: relative;
}

/* Base follower styles — global (not scoped to the host) so they also apply once
   the follower is moved into the portal. Per-element control styles stay scoped
   to {{WRAPPER}} > .geeks-gsap-follower and are copied inline before portaling. */
.geeks-gsap-follower {
	position: absolute;
	top: 0;
	left: 0;
	display: block;
	box-sizing: border-box;
	max-width: none !important;
	height: auto;
	pointer-events: none;
	/* Don't inherit the Image widget's CSS Filters (Elementor applies them to
	   every <img> in the widget, incl. its :hover variant, 0,2,1). */
	filter: none !important;
	opacity: 0;
	transition-property: opacity;
	transition-timing-function: ease;
	will-change: transform, opacity;
	z-index: 2;
}

/* The end of the fade is where the author's Opacity is read: the follower
   still travels from 0, it simply arrives wherever they asked. */
.geeks-gsap-follower.is-shown {
	opacity: var( --geeks-hvf-opacity, 1 );
}

/* THE POINTER, WHERE THE AUTHOR ASKED FOR IT TO GO.
 *
 * The class is on the host and the engine puts it there only while the
 * follower is actually shown, so the pointer comes back the moment the
 * follower steps aside for a scroll.
 *
 * Descendants too: a link inside the host sets `cursor: pointer` of its own,
 * and without this the arrow returns over exactly the parts an author is most
 * likely to have put inside. */
.geeks-gsap-hover.geeks-gsap-nopointer,
.geeks-gsap-hover.geeks-gsap-nopointer * {
	cursor: none;
}

/* Gallery-trail images: GSAP drives opacity/scale directly, so the CSS opacity
   transition must not fight the tweens. The Fade control's transition-duration
   is still set — the engine reads it as the tween duration. */
.geeks-gsap-follower.geeks-gsap-trail {
	transition-property: none;
}

/* The Clip-off top layer: fixed to the viewport, clips anything past the screen
   edges, never intercepts the pointer, above all page content. */
.geeks-gsap-portal {
	position: fixed;
	inset: 0;
	overflow: hidden;
	pointer-events: none;
	z-index: 2147483000;
}

/* Touch-enabled hosts: suppress the long-press callout / text selection / tap
   highlight so our drag / long-press gesture isn't hijacked by the browser. */
.geeks-gsap-hover[data-gsap-touch="drag"],
.geeks-gsap-hover[data-gsap-touch="longpress"] {
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	user-select: none;
	-webkit-tap-highlight-color: transparent;
}

/* ------------------------------------------------------------------------- *
 * GEEKS Text widget
 *
 * The pieces and their mask boxes are the only things the engine animates; the
 * element itself stays a normal Elementor element so the other GSAP features
 * can own it. Nothing here paints — paint comes from the widget's own
 * variables, so a gradient or an outline is one property with one writer.
 * ------------------------------------------------------------------------- */
.geeks-gtx {
	margin: 0;
}

/* Pre-hidden until the split has been built and seeded, so the finished state
   never flashes before the reveal. Removed by the engine — and never applied at
   all when the effect is off, so text without an animation cannot be hidden by
   a JS failure. */
.geeks-gtx-hide {
	visibility: hidden;
}

/* The translation twin. Deliberately NOT `display: none` and NOT
   `visibility: hidden`: a translator may skip what is not rendered, and this
   copy exists precisely to be translated. So it is rendered, laid out, and
   clipped to nothing — out of sight, out of the layout, out of the accessible
   tree (aria-hidden in the markup), and inert to the pointer. */
.geeks-gtx-i18n {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
	pointer-events: none;
	user-select: none;
}

.geeks-gtx .gtx-w,
.geeks-gtx .gtx-c,
.geeks-gtx .gtx-l {
	display: inline-block;
}

/* A word never breaks internally, which is what makes per-character splitting
   safe: the line can only break between words. */
.geeks-gtx .gtx-w {
	white-space: pre;
}

.geeks-gtx .gtx-l {
	display: block;
}

/* A line box is as wide as the column it sits in, so a highlight painted on it
   runs past the last word. Authors who want the bar to stop with the text ask
   for it — the line then shrinks to its own content. Safe because the line was
   already broken before it was wrapped: there is nothing left to re-flow. */
.geeks-gtx .gtx-l.gtx-l-fit {
	display: inline-block;
}

/* The clipping box. `vertical-align: bottom` keeps a masked inline piece on the
   text baseline instead of hanging below it, which is the usual giveaway that a
   reveal was bolted on. */
.geeks-gtx .gtx-m {
	display: inline-block;
	overflow: hidden;
	vertical-align: bottom;
}
.geeks-gtx .gtx-m:has(> .gtx-l),
.geeks-gtx .gtx-l > .gtx-m {
	display: block;
}
/* Line reveal's single box: it wraps the WHOLE text, carries the one rule, and
   is the only thing clipped — so it is block-level and full width whatever the
   split inside it happens to be. */
.geeks-gtx .gtx-m.gtx-m-all {
	display: block;
}
/* ...unless the author asked the box to stop with the words: the mask then
   shrinks like the line it wraps, so the rule it carries — and the wipe that
   draws that rule — end where the text ends rather than at the column edge. */
.geeks-gtx .gtx-m.gtx-m-fit {
	display: inline-block;
}

/* The mask's own border — the line a heading rises from. It belongs to the mask
   rather than the text because the mask is what animates. */
.geeks-gtx .gtx-m[data-border="bottom"] { border-bottom: var(--gtx-mb, 4px) solid var(--gtx-mb-c, currentColor); padding-bottom: var(--gtx-mb-gap, 0); }
.geeks-gtx .gtx-m[data-border="top"]    { border-top: var(--gtx-mb, 4px) solid var(--gtx-mb-c, currentColor); padding-top: var(--gtx-mb-gap, 0); }
.geeks-gtx .gtx-m[data-border="left"]   { border-left: var(--gtx-mb, 4px) solid var(--gtx-mb-c, currentColor); padding-left: var(--gtx-mb-gap, 0); }
.geeks-gtx .gtx-m[data-border="right"]  { border-right: var(--gtx-mb, 4px) solid var(--gtx-mb-c, currentColor); padding-right: var(--gtx-mb-gap, 0); }

/* Block reveal's cover, inside the piece so it inherits its box. */
.geeks-gtx .gtx-p {
	position: relative;
}
/* The block-reveal cover. It sits inside its piece, and the piece is hidden
   while the block draws itself — so the cover has to opt back IN to being seen,
   or the whole growth happens invisibly and the block only turns up at the
   moment it starts retracting. */
.geeks-gtx .gtx-b {
	position: absolute;
	inset: 0;
	pointer-events: none;
	visibility: visible;
}

/* Paint. The gradient is painted on the BLOCK and clipped to its text, so it
   reads as one gradient across the whole heading rather than restarting in
   every letter.

   The repeated class is not a typo. Elementor's lazy-load optimisation ships
   `.e-con.e-parent:nth-of-type(n+4):not(.e-lazyloaded):not(.e-no-lazyload) *
   { background-image: none !important }` — five classes of specificity and an
   !important — which would blank our gradient until the container scrolls into
   view. With `color: transparent` that is not a missing gradient, it is
   INVISIBLE TEXT. Six classes outrank it. */
.geeks-gtx-grad {
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}
.geeks-gtx-grad.geeks-gtx-grad.geeks-gtx-grad.geeks-gtx-grad.geeks-gtx-grad.geeks-gtx-grad {
	background-image: var(--gtx-grad) !important;
}
/* A piece carrying its own slice of the gradient or image. The picture arrives
   as a custom property and becomes the background HERE, with !important, because
   Elementor's lazy-load rule blanks `background-image` on everything inside a
   container past the third until it scrolls into view. The block's paint has
   always been defended this way; this is the same defence for the per-piece
   slices, which an inline style could never win.

   The class is repeated seven times on purpose. Elementor's rule carries SIX
   class-level tokens (.e-con, .e-parent, :nth-of-type, and two :not() arguments)
   and !important, so specificity is what decides between two !important rules —
   three classes lost, seven wins. Same arithmetic as the block's own paint. */
.gtx-paint.gtx-paint.gtx-paint.gtx-paint.gtx-paint.gtx-paint.gtx-paint {
	background-image: var(--gtx-p-img) !important;
}

/* The marker sweep and the underline draw. The bar used to be a background on
   the piece, which is the same channel a gradient or an image fill paints in —
   so a highlight silently replaced the fill and the words lost their picture.
   It is its own layer now: behind the letters, in front of nothing else, and
   out of the way of whatever the text is painted with.

   Only for UNPAINTED text, though. A gradient or image fill makes the letters
   the piece's own background, and an element's background paints before its
   ::after — so on a painted piece this bar would cover the words instead of
   sitting under them. There the bar is drawn as a lower background layer
   instead; see paintPieces. */
/* The stripe sits BEHIND the letters, which is what `z-index: -1` on the layer
   below buys — and it would otherwise buy too much: a negative layer falls
   behind every ancestor's background as well, so on any container with a
   background colour the marker and the underline simply were not there.
   `isolation` keeps the piece its own stacking context, so the layer can go
   behind the text and no further. */
.geeks-gtx .gtx-p.gtx-bar {
	position: relative;
	isolation: isolate;
}
.geeks-gtx .gtx-p.gtx-bar:not(.gtx-paint)::after {
	content: '';
	position: absolute;
	left: 0;
	top: var(--gtx-bar-t, 0);
	width: var(--gtx-bar-w, 0);
	height: var(--gtx-bar-h, 0);
	background: var(--gtx-bar-c, transparent);
	pointer-events: none;
	z-index: -1;
}

/* An image fill: painted on the block and clipped to its letters, exactly like
   the gradient — one picture across the whole heading rather than a copy inside
   every word. The background COLOUR is the fallback: it is clipped to the same
   letters, so it is what you read while the image loads and if it never comes.
   The repeated class answers Elementor's lazy-load rule; see the gradient. */
.geeks-gtx-img {
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
	background-color: var(--gtx-img-bg, #141428);
	background-size: var(--gtx-img-size, cover);
	background-position: var(--gtx-img-pos, center center);
	background-repeat: var(--gtx-img-rep, no-repeat);
}
.geeks-gtx-img.geeks-gtx-img.geeks-gtx-img.geeks-gtx-img.geeks-gtx-img.geeks-gtx-img {
	background-image: var(--gtx-img) !important;
}

.geeks-gtx-stroke {
	color: transparent;
	-webkit-text-stroke: var(--gtx-stroke, 1px) var(--gtx-stroke-c, currentColor);
}

/* Behaviour markers. A rotator's variants are stacked in one slot so a swap
   never re-wraps the sentence around it; the typewriter and the counter reserve
   their finished width for the same reason. */
.geeks-gtx .gtx-rot,
.geeks-gtx .gtx-mk-count,
.geeks-gtx .gtx-mk-type,
.geeks-gtx .gtx-mk-scramble {
	display: inline-block;
	white-space: pre;
}
.geeks-gtx .gtx-rot-slot {
	position: relative;
	display: inline-block;
	overflow: hidden;
	vertical-align: bottom;
}

/* `mask=0`: the words travel outside their box. Nothing clips them, so they
   pass over whatever is beside them on the line — which is the point, and the
   reason it is not the default. */
.geeks-gtx .gtx-rot-slot.gtx-rot-open {
	overflow: visible;
}
.geeks-gtx .gtx-rot-w {
	display: inline-block;
	white-space: pre;
}

/* Before the engine builds, a behaviour marker is showing its FINISHED state —
   the whole typed word, the target number, every rotator variant at once. Left
   alone it flashes that state and then visibly changes when the engine boots.
   Both rules below apply only inside `.geeks-gtx-on` (the widget is active) and
   only until `.gtx-mk-ready` is set, so text with no animation, text in the
   editor, and text whose engine never arrives are all unaffected. */
.geeks-gtx-on .gtx-mk-hold:not(.gtx-mk-ready) {
	visibility: hidden;
}

/* The rotator keeps its first variant visible and holds the rest out of the
   layout, so the first paint is exactly what the engine will show and the line
   never re-measures. */
/* The phrases are STACKED in one grid cell before the engine takes over, so the
   run already reserves the width of its widest phrase — which is exactly what
   the engine reserves once it builds. Hiding the extras with `display: none`
   instead would reserve only the first phrase and the sentence would jump the
   moment the engine measured the rest. */
.geeks-gtx-on .gtx-mk-rotate:not(.gtx-mk-ready),
.geeks-gtx-on .gtx-mk-type:not(.gtx-mk-ready),
.geeks-gtx-on .gtx-mk-scramble:not(.gtx-mk-ready) {
	display: inline-grid;
	vertical-align: bottom;
}
.geeks-gtx-on .gtx-mk-rotate:not(.gtx-mk-ready) .gtx-rot-v,
.geeks-gtx-on .gtx-mk-type:not(.gtx-mk-ready) .gtx-type-v,
.geeks-gtx-on .gtx-mk-scramble:not(.gtx-mk-ready) .gtx-scr-v {
	grid-area: 1 / 1;
}
.geeks-gtx-on .gtx-mk-rotate:not(.gtx-mk-ready) .gtx-rot-v ~ .gtx-rot-v,
.geeks-gtx-on .gtx-mk-type:not(.gtx-mk-ready) .gtx-type-v ~ .gtx-type-v,
.geeks-gtx-on .gtx-mk-scramble:not(.gtx-mk-ready) .gtx-scr-v ~ .gtx-scr-v {
	visibility: hidden;
}

/* The typewriter's caret — decoration only, and hidden from assistive tech at
   the markup level. */
.geeks-gtx .gtx-caret {
	display: inline-block;
	width: 0.08em;
	height: 1em;
	margin-left: 0.06em;
	background: currentColor;
	vertical-align: -0.1em;
	animation: geeks-gtx-caret 1s steps(1) infinite;
}
@keyframes geeks-gtx-caret {
	0%, 50% { opacity: 1; }
	50.01%, 100% { opacity: 0; }
}

/* A wider caret, the old terminal block. */
.geeks-gtx .gtx-caret-block {
	width: 0.5em;
	vertical-align: -0.12em;
}

/* Any other caret is a CHARACTER — an underscore, a dot, whatever was typed —
   so it drops the drawn bar and is laid out as text, keeping the blink. */
.geeks-gtx .gtx-caret-txt {
	width: auto;
	height: auto;
	background: none;
	vertical-align: baseline;
}
@media (prefers-reduced-motion: reduce) {
	.geeks-gtx .gtx-caret { animation: none; }
}

/* The reveal window: a second paint of the same words, shown through a moving
   shape. The paint lives INSIDE each piece — one overlay per piece, not one
   copy of the sentence — so it inherits whatever moves that piece: the wave,
   the pointer response, the entrance. A single sibling copy could only ever be
   right while the text stood perfectly still. Hidden from assistive tech, so
   the sentence is still announced once. */
.geeks-gtx {
	position: relative;
}
.geeks-gtx .gtx-win-p {
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	pointer-events: none;
	white-space: inherit;
	/* The overlay repeats its piece's words. Selecting the sentence must not
	   pick them up twice, and nothing here is content. */
	user-select: none;
	-webkit-user-select: none;
	/* Until the engine paints a mask the overlay shows nothing; a second copy
	   stacked on the first would otherwise flash on the very first frame. */
	-webkit-mask-image: linear-gradient(transparent, transparent);
	mask-image: linear-gradient(transparent, transparent);
}


/* The media marker's slot: reserved at render, opened by the engine. Width, not
   display, so the paragraph's line breaks are computed WITH the space and never
   move when it opens. */
.geeks-gtx .gtx-mk-media {
	position: relative;
	padding-right: var(--gtx-media-w, 160px);
}
.geeks-gtx .gtx-media {
	position: absolute;
	top: 50%;
	right: 0;
	display: block;
	width: 0;
	height: 0.8em;
	overflow: hidden;
	border-radius: var(--gtx-media-r, 0px);
	transform: translateY(-50%);
	transition: width .7s cubic-bezier(.2, .7, .3, 1);
	will-change: width;
}
.geeks-gtx .gtx-media.is-open {
	width: var(--gtx-media-w, 160px);
}
.geeks-gtx .gtx-media img,
.geeks-gtx .gtx-media video {
	width: var(--gtx-media-w, 160px);
	height: 100%;
	max-width: none;
	object-fit: cover;
	object-position: center;
	transform: scale(1.25);
	transition: transform .7s cubic-bezier(.2, .7, .3, 1);
}
.geeks-gtx .gtx-media.is-open img,
.geeks-gtx .gtx-media.is-open video {
	transform: scale(1);
}
@media (prefers-reduced-motion: reduce) {
	.geeks-gtx .gtx-media,
	.geeks-gtx .gtx-media img,
	.geeks-gtx .gtx-media video { transition: none; }
}

/* GEEKS reveal's block bar. It covers the element's own box and lives inside it,
   so — exactly like the text widget's cover — it has to opt back in to being
   seen while the element it covers is hidden. z-index keeps it over the
   element's content without needing a stacking context of its own. */
/* GEEKS Path. The widget itself takes no room: the mover is absolute inside
   it, so a route drawn across the page never pushes the layout around. The
   line is drawn in the mover's own coordinates, which is where the waypoints
   were measured, so the two agree by construction. */
.geeks-path {
	position: relative;
	width: 0;
	height: 0;
}

.geeks-path-mover {
	position: absolute;
	top: 0;
	left: 0;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	line-height: 1;
	white-space: nowrap;
	pointer-events: none;
	will-change: transform;
}

.geeks-path-mover img {
	display: block;
	max-width: none;
}

.geeks-path-icon svg,
.geeks-path-icon i {
	display: block;
	width: 1em;
	height: 1em;
	font-size: inherit;
	fill: currentColor;
}

/* The line's box is set by the engine to the route's own bounds — an outer
   <svg> clips to its viewport whatever `overflow` says, so a zero-sized one
   paints nothing. */
.geeks-path-line {
	position: absolute;
	overflow: visible;
	pointer-events: none;
}

/* An element borrowed from the page travels out of the flow, or the layout
   would reflow around it at every frame. */
.geeks-path-moving {
	pointer-events: none;
}

@media ( prefers-reduced-motion: reduce ) {
	.geeks-path-mover { will-change: auto; }
}

/* GEEKS edge effects. One overlay per chosen side, lying just outside that edge
   and reaching past it by the pull limit so a bulge is never clipped by its own
   box. It takes no pointer events: the effect watches the pointer at the
   document, and the container's own content must stay clickable. */
.geeks-edge {
	position: absolute;
	overflow: visible;
	pointer-events: none;
}

/* Cut corners. A zero-size box with transparent borders and ONE side coloured,
   rotated 135° so its flat edge lies across the corner — the corner then reads
   as cut, in the colour of whatever lies behind the container.

   The shadow belongs to that whole square, so most of it has to be masked away:
   the engine clips each piece to its own triangle plus the band beyond the flat
   edge, no deeper than the shadow's distance plus its blur. Without that mask
   the square's own shadow escapes as a floating diamond as soon as the blur is
   raised. */
.geeks-cut {
	position: absolute;
	width: 0;
	height: 0;
	border: var( --geeks-cut-size, 50px ) solid transparent;
	pointer-events: none;
	box-shadow: var( --geeks-cut-sh, none );
}

.geeks-cut-tl,
.geeks-cut-tr { border-top-color: var( --geeks-cut-c, #fff ); }

.geeks-cut-bl,
.geeks-cut-br { border-bottom-color: var( --geeks-cut-c, #fff ); }

.geeks-cut-tl,
.geeks-cut-br { transform: rotate( 135deg ); }

.geeks-cut-tr,
.geeks-cut-bl { transform: rotate( -135deg ); }

.geeks-cut-tl,
.geeks-cut-tr { top: calc( -1 * ( var( --geeks-cut-size, 50px ) + var( --geeks-cut-over, 0px ) + var( --geeks-cut-bw, 0px ) ) ); }

.geeks-cut-bl,
.geeks-cut-br { bottom: calc( -1 * ( var( --geeks-cut-size, 50px ) + var( --geeks-cut-over, 0px ) + var( --geeks-cut-bw, 0px ) ) ); }

.geeks-cut-tl,
.geeks-cut-bl { left: calc( -1 * ( var( --geeks-cut-size, 50px ) + var( --geeks-cut-over, 0px ) + var( --geeks-cut-bw, 0px ) ) ); }

.geeks-cut-tr,
.geeks-cut-br { right: calc( -1 * ( var( --geeks-cut-size, 50px ) + var( --geeks-cut-over, 0px ) + var( --geeks-cut-bw, 0px ) ) ); }

/* GEEKS reveal — Line reveal. The rule is the element's own ::before, because a
   pseudo-element costs no markup and cannot be reached by anything else on the
   page. It draws by scaling on the axis its side implies, from the end its
   direction implies (--grev-l-o), and the engine tweens --grev-l from 0 to 1.

   The element carries `overflow: hidden` while the reveal runs, so the rule is
   inside the clip like everything else — it sits ON the edge, never outside it. */
.geeks-rev-line {
	position: relative;
}

.geeks-rev-line::before {
	content: '';
	position: absolute;
	z-index: var( --geeks-rev-z, 9 );
	pointer-events: none;
	background-color: var( --grev-l-c, #141428 );
	background-image: var( --grev-l-img, none );
	background-size: var( --grev-l-size, auto );
	background-position: var( --grev-l-pos, center center );
	background-repeat: var( --grev-l-rep, no-repeat );
	border-radius: var( --grev-l-r, 0 );
	transform-origin: var( --grev-l-o, 0% 50% );
}

.geeks-rev-line[data-grev-line="bottom"]::before,
.geeks-rev-line[data-grev-line="top"]::before {
	left: 0;
	right: 0;
	height: var( --grev-l-w, 4px );
	transform: scaleX( var( --grev-l, 0 ) );
}

.geeks-rev-line[data-grev-line="bottom"]::before { bottom: 0; }
.geeks-rev-line[data-grev-line="top"]::before    { top: 0; }

.geeks-rev-line[data-grev-line="left"]::before,
.geeks-rev-line[data-grev-line="right"]::before {
	top: 0;
	bottom: 0;
	width: var( --grev-l-w, 4px );
	transform: scaleY( var( --grev-l, 0 ) );
}

.geeks-rev-line[data-grev-line="left"]::before  { left: 0; }
.geeks-rev-line[data-grev-line="right"]::before { right: 0; }

/* A rule carrying a gradient, an image or rounded ends is not scaled: the paint
   would be squashed at the start and stretch as the rule drew, and a radius
   would flatten into an ellipse. Such a rule is laid out at full length and
   uncovered by an inset built from the same --grev-l, so it never moves — only
   how much of it is shown. */
.geeks-rev-line[data-grev-lcut]::before {
	transform: none;
}

.geeks-rev-line[data-grev-lcut][data-grev-ldir="lr"]::before {
	clip-path: inset( 0 calc( ( 1 - var( --grev-l, 0 ) ) * 100% ) 0 0 );
}

.geeks-rev-line[data-grev-lcut][data-grev-ldir="rl"]::before {
	clip-path: inset( 0 0 0 calc( ( 1 - var( --grev-l, 0 ) ) * 100% ) );
}

.geeks-rev-line[data-grev-lcut][data-grev-ldir="cx"]::before {
	clip-path: inset( 0 calc( ( 1 - var( --grev-l, 0 ) ) * 50% ) 0 calc( ( 1 - var( --grev-l, 0 ) ) * 50% ) );
}

.geeks-rev-line[data-grev-lcut][data-grev-ldir="tb"]::before {
	clip-path: inset( 0 0 calc( ( 1 - var( --grev-l, 0 ) ) * 100% ) 0 );
}

.geeks-rev-line[data-grev-lcut][data-grev-ldir="bt"]::before {
	clip-path: inset( calc( ( 1 - var( --grev-l, 0 ) ) * 100% ) 0 0 0 );
}

.geeks-rev-line[data-grev-lcut][data-grev-ldir="cy"]::before {
	clip-path: inset( calc( ( 1 - var( --grev-l, 0 ) ) * 50% ) 0 calc( ( 1 - var( --grev-l, 0 ) ) * 50% ) 0 );
}

.geeks-rev-b {
	position: absolute;
	inset: 0;
	pointer-events: none;
	visibility: visible;
	z-index: var( --geeks-rev-z, 9 );
}

/* The ink overlay: the strip is a mask, not a picture, so the layer under it can
   be any colour, gradient or photograph. The mask is sized frames × 100% and the
   engine steps its position along, one frame at a time. */
.geeks-rev-ink {
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-position: 0% 0%;
	mask-position: 0% 0%;
}

/* The per-widget translating cover, drawn as the widget's own ::before so no
   element is added to markup the theme does not own. It covers the whole widget
   box — padding and background included — while the translator swaps the words,
   and its colour, timings, icon and icon size arrive as custom properties from
   the widget's own settings.

   `visibility` rides along with opacity so a faded-out cover cannot swallow a
   click during its last frames, and the whole thing is pointer-transparent
   anyway. z-index keeps it over the widget's content without needing a stacking
   context of its own. */
.elementor-widget.geeks-gtx-cover-on::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 9;
	pointer-events: none;
	opacity: 0;
	visibility: hidden;
	background-color: var(--gtx-cov-c, #fff);
	background-image: var(--gtx-cov-icon, none);
	background-repeat: no-repeat;
	background-position: center;
	background-size: var(--gtx-cov-size, 48px) auto;
	transition-property: opacity, visibility;
	transition-duration: var(--gtx-cov-t, 400ms);
	transition-timing-function: ease;
}
.elementor-widget.geeks-gtx-cover-on.is-covering::before {
	opacity: 1;
	visibility: visible;
}
@media (prefers-reduced-motion: reduce) {
	.elementor-widget.geeks-gtx-cover-on::before { transition: none; }
}

/* ---------------------------------------------------------------------------
 * GEEKS Background — the container's own backdrop, in layers.
 *
 * One sheet, inserted as the container's FIRST child, holding a layer per row
 * the author stacked. Everything about where it sits is decided here rather
 * than in the engine, so a layer only has to worry about what it draws.
 *
 * On the stacking, which is the whole trick: Elementor gives every widget
 * `position: relative`, and a positioned element with `z-index: auto` paints in
 * TREE ORDER among its positioned siblings. First child means everything else
 * is written after it, so the content paints over the sheet without a single
 * z-index fighting another — which is exactly how Elementor's own container
 * overlay (`.e-con::before`) stays behind the widgets.
 *
 * `inset: 0`, not `width/height: 100%`: a percentage measures the CONTENT box,
 * so a container with padding would leave the sheet short on every side.
 * ------------------------------------------------------------------------- */
.geeks-bg {
	position: absolute;
	inset: 0;
	z-index: 0;
	/* It is scenery. It never takes a press meant for the container, and a
	   screen reader is not told about it at all. */
	pointer-events: none;
	/* The container's own shape. A square sheet inside a rounded container
	   shows its corners, and a layer that draws past the edge has to be cut off
	   by the same curve the container is cut by. */
	border-radius: inherit;
	overflow: hidden;
}

.geeks-bg-layer {
	position: absolute;
	inset: 0;
	border-radius: inherit;
}

/* One rule of the Lines preset.
 *
 * The resting look lives here rather than in a control default: a value kept
 * only in PHP is missing from the editor's own model, so the field would show
 * empty beside a line that plainly has a thickness — and typing the 1 it
 * already implies would not be a change and would never save. An empty field
 * means these values. */
.geeks-bg-line {
	position: absolute;
	background: var( --bgl-col, rgba( 255, 255, 255, .1 ) );
}

/* A rule that draws itself hands its paint to a child.

   The pulses live inside the rule, and a rule that grew by scaling itself would
   squash the light along with it. So the paint moves to a box of its own, the
   pulse stays a sibling of that box, and only the box is scaled.

   Made only for a drawing entrance. A wall that arrives any other way is built
   exactly as it always was, with the paint on the rule itself. */
.geeks-bg-line.has-ink {
	background: none;
}

.geeks-bg-ink {
	position: absolute;
	inset: 0;
	background: var( --bgl-col, rgba( 255, 255, 255, .1 ) );
	/* The rule is drawn from one end, so it must not be smoothed back towards
	   the middle by the browser's own idea of where a scaled box starts. */
	will-change: transform;
}

/* Centred ON its position rather than starting at it, so the middle rule of an
   odd number sits exactly on the container's centre line. */
.geeks-bg-line.is-v {
	top: 0;
	bottom: 0;
	width: var( --bgl-w, 1px );
	transform: translateX( -50% );
}

.geeks-bg-line.is-h {
	left: 0;
	right: 0;
	height: var( --bgl-w, 1px );
	transform: translateY( -50% );
}

/* The light travelling along a rule.
 *
 * Clipped by its rule at the ENDS and nowhere else. `overflow: hidden` was the
 * obvious way to do that and it was wrong: overflow clips both axes, so a pulse
 * asked to be thicker than its rule was cut back to the rule's own thickness —
 * ten pixels of light drawn one pixel wide on a one-pixel rule, and no way to
 * tell from the panel that the setting had been quietly ignored.
 *
 * `clip-path` can be told which way to cut. A negative inset opens that side
 * out, so a vertical rule clips top and bottom while letting the sides through,
 * and a horizontal one does the reverse. The pulse is still hidden before it
 * arrives and after it leaves, and is still free to be a bead on a wire. */
.geeks-bg-line.is-v {
	clip-path: inset( 0 -100vw );
}

.geeks-bg-line.is-h {
	clip-path: inset( -100vh 0 );
}

.geeks-bg-drop {
	position: absolute;
	background: var( --bgd-col, #fff );
	border-radius: var( --bgd-r, 0 );
	/* Free on the compositor, unlike animating `top`. Forty rules with a light
	   apiece is forty layout recalculations a frame if this is done with `top`. */
	will-change: transform;
}

.geeks-bg-line.is-v .geeks-bg-drop {
	top: 0;
	left: 50%;
	width: var( --bgd-w, 100% );
	height: var( --bgd-len, 20% );
	transform: translateX( -50% );
}

.geeks-bg-line.is-h .geeks-bg-drop {
	left: 0;
	top: 50%;
	height: var( --bgd-w, 100% );
	width: var( --bgd-len, 20% );
	transform: translateY( -50% );
}

/* The keyframes are not here.
 *
 * A light's journey is its own length before the rule, the rule itself, and its
 * own length past the end — and a translate is always a share of the element
 * being moved, so how far that is depends on how long the light is. The wait
 * after each journey is a HOLD at the last keyframe, and a keyframe's position
 * cannot come from a custom property. Neither number can be a custom property,
 * so the engine writes the rule instead — one per distinct journey, shared by
 * every light that makes the same one. */

@media ( prefers-reduced-motion: reduce ) {

	.geeks-bg-drop {
		display: none;
	}

	/* The engine already refuses to start either of these, so this is the belt
	   to that braces: a reader who changes the setting with the page open is not
	   left with a backdrop still drifting under them. */
	.geeks-bg-par,
	.geeks-bg-media {
		animation: none !important;
		transform: none !important;
	}

	/* A wall that draws itself is already there. An entrance is motion, and a
	   reader who asked for less gets the state it was on its way to. */
	.geeks-bg-ink {
		transform: none !important;
		transition: none !important;
	}

	/* The fills stop where they stand. Their rounds are already refused, but a
	   step begun a moment before the setting changed would otherwise finish. */

	.geeks-bg-cell {
		transition: none !important;
	}
}

/* The Media preset.
 *
 * The resting look lives here, as everywhere else: cover, centred, no blur. An
 * untouched layer needs no inline style at all, and the engine writes only what
 * the author actually chose.
 *
 * `display: block` because an image is inline by default, and an inline box
 * carries a line-box's descender gap under it — invisible on a cropped photo,
 * and a hairline of the container's own colour along the bottom edge of one
 * that is contained.
 *
 * Scoped through the layer for the specificity, not for the reach: Elementor
 * ships `.elementor img { height: auto }`, which is a class and an element and
 * therefore beats a lone class of ours. A layer photograph would then be drawn
 * at its own aspect ratio inside a box it was told to fill, and the difference
 * only shows on containers whose shape happens to differ from the file's. */
.geeks-bg-layer .geeks-bg-media {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
	/* Growing past the container is the whole mechanism of a parallax: the layer
	 * is drawn larger than its box so that sliding it never opens a band of bare
	 * container along an edge. Both the theme and Elementor cap an image at
	 * `max-width: 100%`, which is right for one in the flow and quietly fatal
	 * here — the element simply refuses to grow while the transform goes on
	 * moving it, so the slide walks it off its own box. */
	max-width: none;
	max-height: none;
}

/* A filled cell.
 *
 * The resting look lives here as everywhere else, so an untouched fill is
 * visible without the panel having to say anything — and so the styling the
 * author does choose arrives as ordinary CSS on top of it.
 *
 * Written into the layer before the rules, so the hairlines paint over the
 * blocks rather than under them. */
.geeks-bg-cell {
	position: absolute;
}

/* The colour, on a layer of its own.

   It used to be the cell's own background, which meant anything that dimmed the
   colour dimmed the picture, the pattern and the words with it — the cell is
   their parent, and a parent's opacity takes its children down too. Given a
   layer, a weight can be put on the COLOUR and leave everything above it solid.

   First child, so it is underneath the rest; `inset: 0` so it is exactly the
   cell; and no shape of its own, because the cell clips it. */
.geeks-bg-paint {
	position: absolute;
	inset: 0;
	background: rgba( 255, 255, 255, .06 );
}

/* What sits inside a filled cell.
 *
 * The picture is its own element rather than the cell's background, so the
 * colour the author chose stays underneath it instead of being replaced by it —
 * and so the overlay can be a third thing on top of both.
 *
 * `max-width: none` for the same reason the Media layer needs it: Elementor caps
 * an image at the width of its box, which is right in the flow and wrong for one
 * being used as a crop. */
.geeks-bg-cell {
	overflow: hidden;
}

.geeks-bg-cell .geeks-bg-cell-media {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
	max-width: none;
	max-height: none;
	object-fit: cover;
	border: 0;
}

.geeks-bg-ov {
	position: absolute;
	inset: 0;
}

/* Words in a fill.

   A box the size of the cell, so the two alignments have something to work
   with: down the box is the flex box's own `align-items`, and across it is
   `text-align`, which a second line follows as well as the first — the reason
   the two are separate controls rather than one anchor.

   The words are wrapped in a span of full width, because a bare text node in a
   flex container becomes an anonymous item that shrinks to its content, and
   `text-align` on a box exactly as wide as its text aligns nothing.

   `pre-line` keeps the line breaks the author typed while still collapsing the
   runs of spaces an editor leaves behind.

   Last child, so it sits above the colour, the picture and the overlay. */
.geeks-bg-text {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: center;
}

.geeks-bg-text > span {
	width: 100%;
	white-space: pre-line;
}

/* The one thing in the backdrop that may answer to a pointer.

   The sheet takes no pointer events at all, so it can never swallow a press
   meant for the container's own content. A descendant may take them back, and
   this class is how an author asks for it on one row's words — everything else
   in the backdrop still ignores the pointer entirely.

   Not selectable, though, and that is the whole reason the two `user-select`
   lines are here: taking pointer events back also makes text selectable, and
   these words are decoration in a sheet screen readers are told to ignore.
   Letting a sighted reader copy text assistive software says does not exist is
   worse than either answer alone.

   The box is the size of its cell, so a hover is anywhere in that cell rather
   than over the letters themselves. */
.geeks-bg-text.geeks-bg-live {
	pointer-events: auto;
	-webkit-user-select: none;
	user-select: none;
}

/* A cell the reader lit.
 *
 * The resting look is here so a cursor fill shows without the panel having said
 * anything; the author's own colour arrives as generated CSS on top of it, and
 * out-specifies this by two classes to one. */
.geeks-bg-cell-cur > .geeks-bg-paint {
	background: rgba( 255, 255, 255, .18 );
}

/* The wrapper a parallax moves.
 *
 * It exists so that two effects can own two transforms. A drift and a Ken Burns
 * both write `transform`, and an element has exactly one — so the drift is given
 * to this box and the zoom to the picture inside it, and they compose instead of
 * overwriting each other. Without a parallax there is nothing to separate and no
 * wrapper is made. */
.geeks-bg-par {
	position: absolute;
	inset: 0;
}

/* An embed's crop.
 *
 * The frame inside is sized in pixels by the engine — `object-fit` does not
 * reach into an iframe — so this is what cuts the overflow off, and what any
 * blur is applied to, so the softening happens before the crop rather than
 * bleeding past the container's corners. */
.geeks-bg-frame {
	position: absolute;
	inset: 0;
	overflow: hidden;
	border-radius: inherit;
}

.geeks-bg .geeks-bg-frame iframe {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate( -50%, -50% );
	border: 0;
	/* Overflowing is the whole point.
	 *
	 * Elementor caps every embed at `max-width: 100%`, which is right for one in
	 * the flow and wrong for one being used as a crop: the engine sizes this
	 * frame past its box deliberately, and a cap silently undoes that without
	 * touching the width it was given — the inline style still reads 1598px
	 * while the frame draws at 1280. */
	max-width: none;
	max-height: none;
}

/* GEEKS Background — the canvas presets.
 *
 * The layer, its blend mode and its opacity are the sheet's own rules; a canvas
 * only has to fill its layer. Events are off unless the look actually wants the
 * pointer, or a decorative background would be eating the clicks of whatever the
 * container holds. */
.geeks-bg-canvas {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
	pointer-events: none;
}

.geeks-bg-canvas-live {
	pointer-events: auto;
}
