/**
 * GEEKS text stairs.
 *
 * An isometric flight built from two alternating skews — no 3D transform, so
 * there is no perspective to get wrong and the type stays as sharp as any other
 * heading. Every surface is a window exactly one line tall; the engine slides
 * the list of lines through it.
 *
 * Everything here is in `em`, so one font size on `.geeks-stairs` scales the
 * whole flight — faces, line heights and the spacing between steps alike.
 */

.geeks-stairs-box {
	display: flex;
	align-items: center;
	justify-content: center;
	/* The resting height, HERE rather than as a control default: a value kept
	   only in PHP is filled in when the CSS is rendered but is missing from the
	   editor's model, so the field shows empty beside a box that plainly has a
	   height — and typing the number the field already implies changes nothing.
	   Below the panel, an emptied field means "the resting height" and any typed
	   value is a real change. */
	height: 420px;
	/* Full width even inside a row container, where a widget otherwise
	   shrink-wraps to its content and leaves nothing to align within. */
	width: 100%;
	/* A flight leans well outside the words in it; the author decides whether
	   that shows or is cropped. */
	overflow: visible;
}

.geeks-stairs {
	/* Not selectable, and not draggable. Every surface is showing its own part
	   of the same list — so a drag across the flight would hand the reader the
	   same words several times over, and a highlight would sit on top of the
	   animation. The staircase is a drawing made of type. */
	-webkit-user-select: none;
	user-select: none;
	-webkit-user-drag: none;

	/* Derived, not guessed. Both planes are built on a 30° axis, so moving one
	   line down the flight means moving `tan(30°)` of it across — get this wrong
	   and the words no longer meet at the fold. The author can override it, but
	   by default it can never drift out of step with the line.

	   --geeks-stair-band is the line box in pixels, written here by the engine.
	   It cannot be worked out in CSS: a line height may be typed in px, em, rem,
	   lh or as a bare number, and no `calc()` can read the computed result back
	   out. Measuring gives one number whatever was typed. `1lh` is the fallback
	   for the moment before the engine has measured, and for a page whose script
	   never arrives. */
	--geeks-stair-rise: calc( var( --geeks-stair-band, 0.8em ) * 0.57735 );
	--geeks-stair-top:  #141428;
	--geeks-stair-face: #ff5a1f;

	display: inline-block;
	/* Shrink-wrapped to the longest line. `display: inline-block` alone is not
	   enough — inside an Elementor widget container it computes to block and the
	   faces then take the full column width, which turns the flight into a
	   sprawl. `max-content` holds regardless of what display it ends up with. */
	width: max-content;
	max-width: 100%;
	position: relative;
	/* A staircase at the body's 16px is unreadable as a staircase — the whole
	   effect lives at display sizes. This is only a starting point; the widget's
	   own typography group overrides it. */
	font-size: clamp( 34px, 7vw, 120px );
	font-weight: 900;
	/* THE LINE HEIGHT IS THE FACE HEIGHT. Whatever the author sets in Typography
	   lands here and every surface follows it, which is why there is no separate
	   face-height setting to keep in step with it. This is only the resting
	   value. */
	line-height: 0.8;
	/* Two shifts, added together: the one the engine works out to centre what is
	   actually DRAWN — the skews and the per-face offsets throw the drawing well
	   outside its own layout box — and the author's own nudge on top. */
	transform: translate(
		calc( var( --geeks-stair-ax, 0px ) + var( --geeks-stair-nx, 0px ) ),
		calc( var( --geeks-stair-ay, 0px ) + var( --geeks-stair-ny, 0px ) )
	);
	text-transform: uppercase;
	white-space: nowrap;
}

/* One surface: a window one line tall, holding the three cells the engine
 * slides through it.
 *
 * The negative margin pulls each surface onto the one above so no hairline
 * shows at the fold, and the side padding keeps letters off the edges. */
.geeks-stair {
	display: block;
	position: relative;
	/* The window that hides the travel — and, when the author sets a step width,
	   what crops a line too long for its step. */
	overflow: hidden;
	/* Exactly one line tall, in the line's OWN unit.
	   `1lh` rather than the measured pixel value on purpose: it makes the face
	   follow the line height natively, so the flight's own box changes the
	   moment the type does — and that is the only thing a ResizeObserver can
	   see. Pinned to a pixel value instead, a font swap or a container query
	   would change the line and resize nothing, and the observer would never
	   hear about it. */
	height: 1lh;
	margin: -1px 0;
	padding: 0 0.1em;
}

@supports not ( height: 1lh ) {
	.geeks-stair,
	.geeks-stair-line { height: var( --geeks-stair-band, 0.8em ); }
}

/* The two planes. The tops lie back, the faces stand up; the scaleY on the
   tops undoes the foreshortening the skew introduces, so both read as the same
   type at the same size. */
.geeks-stair.is-top  { transform: skew( 60deg, -30deg ) scaleY( 0.6666666667 ); }
.geeks-stair.is-face { transform: skew( 0deg, -30deg ) scaleY( 1.3333333333 ); }

.geeks-stair.is-top  .geeks-stair-line,
.geeks-stair.is-top  .geeks-stair-mark { color: var( --geeks-stair-top ); }
.geeks-stair.is-face .geeks-stair-line,
.geeks-stair.is-face .geeks-stair-mark { color: var( --geeks-stair-face ); }

/* A marked run carries its own value for one or both of those two properties,
   so it takes its own colour on whichever plane it happens to be on — and falls
   back to the widget's for the plane it was not given. */
.geeks-stair-mark { display: inline; }

/* NOT promoted. There is deliberately no `will-change` here.

   `will-change: transform` is a promise that something is about to move, and a
   browser answers it by giving that element a compositing layer of its own. A
   flight is a run per surface, each as wide as the longest line and three lines
   tall, and there may be forty of them. On a full-width heading that is
   megapixels of texture apiece at one device pixel per CSS pixel — and a phone
   draws at two or three, against a GPU memory budget far smaller than a
   laptop's. The browser cannot keep all the tiles, throws some away and paints
   them a frame late: a blank line, at random. That IS the flicker, and it was
   the promotion causing it, not the length of time it was held — scoping the
   promise to the seconds the words actually walk did not help a phone at all.

   Nothing is lost by leaving it off. A run moves by a plain translateY, which a
   browser composites perfectly well unasked, and repainting three lines of type
   costs nothing. The class the engine still puts on while the flight walks is
   left in place: it says truthfully that the words are moving, and a page that
   wants to do something with that can. It just does not promote anything. */

.geeks-stair-line {
	display: block;
	height: 1lh;
	/* Where a line sits on its own step. The faces are all as wide as the
	   longest line, so a short line has room inside its step to sit left,
	   centred or right — which is a different question from where the whole
	   flight sits in the box. */
	text-align: var( --geeks-stair-text, left );
}

/* Held back until the engine has painted its first frame.
 *
 * The flight is built by the engine, so until it runs there is no staircase at
 * all — and the reader would watch one appear out of nothing, uncentred for a
 * frame. The class goes on once the words are where they belong.
 *
 * The animation is the failsafe: if the engine never arrives — a failed script,
 * an error upstream — the words appear anyway after 2.5s, plainly stacked but
 * readable, which is better than a widget that is never there. A reader who
 * asked for reduced motion never waits at all. */
.geeks-stairs-box:not( .is-live ) {
	visibility: hidden;
	animation: geeks-stairs-failsafe 0s 2.5s forwards;
}

@keyframes geeks-stairs-failsafe {
	to { visibility: visible; }
}

/* What the failsafe has to show.
 *
 * The faces are the engine's, so a page whose script never arrives has none —
 * turning the box visible would reveal nothing at all. The list it would have
 * built from is already in the markup as a hidden template, and this is what
 * puts it on screen: one plain stack of the lines, until `is-live` says a
 * staircase exists and takes it away again.
 *
 * Landscape's copy only. Which way up the screen is, is a question only the
 * engine asks, and this is the state where the engine never spoke. */
.geeks-stairs-box:not( .is-live ) .geeks-stair-tpl:not( .is-portrait ) {
	display: block;
}

@media ( prefers-reduced-motion: reduce ) {
	.geeks-stairs-box:not( .is-live ) {
		visibility: visible;
		animation: none;
	}
}

/* A run's picture, unfolded beside the words.
 *
 * One line tall at most, and the width follows from that: `height` plus
 * `width: auto` lets the file's own ratio decide, so a picture is never
 * squashed to fit. It cannot be taller — a face is a window one line high and
 * the walk moves the list by whole multiples of that line, so a taller picture
 * would be sliced by its own window, and making the line taller would raise
 * every step in the flight rather than just this one.
 *
 * It unfolds by WIDTH, not opacity: the words shift along to make room, which
 * is what makes it read as part of the sentence rather than a layer over it. */
.geeks-stair-media {
	--geeks-stair-mh: 1lh;

	display: inline-block;
	/* `top`, not `middle`. A face IS the line box, and the picture is exactly one
	   line tall — so aligning it to the top of that box makes it fill the step
	   the words occupy, edge to edge. Centred on the line it sat lower than the
	   letters by the difference between the type's own box and the cropped line
	   height, which at a display size is a visible dozen pixels. */
	vertical-align: top;
	height: var( --geeks-stair-mh );
	max-height: 1lh;
	/* The staircase is skewed; a picture inside it would be skewed with the type
	   unless it is put back. The inverse of the face's own transform is not
	   knowable here, so the media sits upright by having no transform of its own
	   and being small enough that the shear reads as intent rather than error. */
	white-space: nowrap;
}

.geeks-stair-media.is-before { margin-right: 0.25em; }
.geeks-stair-media.is-after  { margin-left: 0.25em; }

.geeks-stair-media img,
.geeks-stair-media video {
	display: block;
	height: 100%;
	width: auto;
	max-width: none;
	object-fit: cover;
}


/* Dragging walks the flight sideways. `pan-y` is the whole reason it is
   sideways: the browser keeps the vertical gesture, so a reader can still
   scroll the page with a finger that lands on the widget. */
.geeks-stairs-box.is-draggable {
	touch-action: pan-y;
	cursor: grab;
}

.geeks-stairs-box.is-dragging {
	cursor: grabbing;
	/* A drag across type selects it in every browser; the flight is a drawing. */
	user-select: none;
}

/* The translator's copy: present in the page and translatable, but never seen.
   Not `display: none` — a hidden-that-way element is skipped by some
   translators, and this one exists precisely to be rewritten. */
.geeks-stairs-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;
}
