/**
 * Shuster Elements - Shuster Swap Hover.
 *
 * Every swapping piece - the line (.su-swap-hover__span), or each word or
 * character once the script has split it - holds its incoming copy in its
 * own ::after, one full height below it (From: Bottom) or above it (From:
 * Top). On hover the piece moves up (or down) by its own height, so its copy
 * lands exactly where it was, while a clipping box above it hides whatever
 * is outside: the span wrapper for the line, a mask per word for words and
 * characters, and each icon's own box for the icons.
 *
 * Skew: only while moving. A piece's progress, --su-swap-hover-p, runs 0 -> 1
 * alongside the move, and the lean is skew x sin(p x 180deg): nothing at
 * rest, most mid-way, nothing again once the swap is done - so both resting
 * states are plain translations and the text is drawn crisp, never
 * resampled through a skew. The leaving piece leans away from view (its far
 * end leads; a negative skew shears about the right edge instead of the
 * left, --su-swap-hover-origin), and its copy arrives leaned and
 * straightens. The move is the `translate` property (run by the compositor)
 * and the lean the `transform` property; where @property or sin() is
 * missing, the text still swaps, just without the lean.
 *
 * Per-instance values come in as custom properties on the root:
 *   --su-swap-hover-skew       angle
 *   --su-swap-hover-duration   time
 *   --su-swap-hover-ease       timing function
 *   --su-swap-hover-fromtype   1 = from bottom, -1 = from top
 *   --su-swap-hover-origin     left top, or right top for a negative skew
 * and, per piece, from the script:
 *   --su-swap-hover-delay      the stagger
 *   --su-swap-hover-bg-size/-bg-pos   a gradient's slice (Words/Chars)
 */

.su-swap-hover {
	position: relative;
	display: block;
	width: fit-content;
	padding: 0;
	line-height: normal;
}

.su-swap-hover__anchor {
	display: block;
	width: fit-content;
	/* Or fit-content would take a widened line's full width, whatever room
	   the element has. */
	max-width: 100%;
	height: fit-content;
	color: inherit;
	text-decoration: none;
}

/* Hides the incoming line until it slides into view. */
.su-swap-hover__span-wrapper {
	display: block;
	overflow: hidden;
}

/* ------------------------------------------------------ swapping pieces */

@property --su-swap-hover-p {
	syntax: '<number>';
	inherits: true;
	initial-value: 0;
}

/* will-change from the start: a transform transition otherwise gets its
   own compositor layer only while it runs, and the text is redrawn from a
   resampled texture until that layer is dropped at the end - a short blur
   just after the text looks settled (measured on the Ripple Button). */
.su-swap-hover__span,
.su-swap-hover__word,
.su-swap-hover__char {
	--su-swap-hover-p: 0;
	position: relative;
	display: inline-block;
	max-width: 100%;
	vertical-align: top;
	transform-origin: var(--su-swap-hover-origin, left top);
	transform: skewY(calc(var(--su-swap-hover-skew, 0deg) * var(--su-swap-hover-fromtype, 1) * -1 * sin(var(--su-swap-hover-p) * 180deg)));
	will-change: transform, translate;
	transition:
		translate var(--su-swap-hover-duration, 1s) var(--su-swap-hover-ease, cubic-bezier(0.19, 1, 0.22, 1)) var(--su-swap-hover-delay, 0s),
		--su-swap-hover-p var(--su-swap-hover-duration, 1s) var(--su-swap-hover-ease, cubic-bezier(0.19, 1, 0.22, 1)) var(--su-swap-hover-delay, 0s);
}

.su-swap-hover__span::after,
.su-swap-hover__word::after,
.su-swap-hover__char::after {
	content: attr(data-su-after-content);
	/* The same, with empty alternative text, so screen readers do not read
	   the copy as well; browsers without the syntax keep the line above. */
	content: attr(data-su-after-content) / "";
	position: absolute;
	top: calc(var(--su-swap-hover-fromtype, 1) * 100%);
	left: 0;
	width: 100%;
	/* Moves with its piece. Its own lean, on top of the piece's, leaves it
	   leaned by skew x (1 - p): fully while waiting, none once landed. */
	transform-origin: var(--su-swap-hover-origin, left top);
	transform: skewY(calc(var(--su-swap-hover-skew, 0deg) * var(--su-swap-hover-fromtype, 1) * (1 - var(--su-swap-hover-p) + sin(var(--su-swap-hover-p) * 180deg))));
}

.su-swap-hover:is(:hover, :has(.su-swap-hover__anchor:focus-visible)) :is(.su-swap-hover__span, .su-swap-hover__word, .su-swap-hover__char) {
	--su-swap-hover-p: 1;
	translate: 0 calc(var(--su-swap-hover-fromtype, 1) * -100%);
}

/* Words / Chars: the words and characters swap; the line stays put. */
.su-swap-hover--remove-line .su-swap-hover__span {
	translate: none !important;
	transform: none !important;
	will-change: auto;
}

.su-swap-hover--remove-line .su-swap-hover__span::after {
	content: none;
}

/* Words in the other direction from the rest of the line, read their own
   way round. */
.su-swap-hover__run {
	unicode-bidi: isolate;
}

/* One per word, from the script: clips the word (or its characters) so a
   copy waiting below never shows on the line underneath. */
.su-swap-hover__mask {
	display: inline-block;
	overflow: hidden;
	vertical-align: top;
}

/* The full text for screen readers, where the split pieces are hidden. */
.su-swap-hover__sr {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* ---------------------------------------------------------------- icons */

/* An icon can't be copied by a pseudo element, so it swaps with a real copy
   of itself, inside its own clipping box. */
.su-swap-hover__icon {
	display: inline-flex;
	overflow: hidden;
	line-height: 1;
	vertical-align: middle;
}

.su-swap-hover__icon--left  { margin-inline-end: 0.5em; }
.su-swap-hover__icon--right { margin-inline-start: 0.5em; }

.su-swap-hover__icon svg {
	width: 1em;
	height: 1em;
	fill: currentColor;
}

.su-swap-hover__icon-inner {
	--su-swap-hover-p: 0;
	position: relative;
	display: inline-flex;
	transform-origin: var(--su-swap-hover-origin, left top);
	transform: skewY(calc(var(--su-swap-hover-skew, 0deg) * var(--su-swap-hover-fromtype, 1) * -1 * sin(var(--su-swap-hover-p) * 180deg)));
	will-change: transform, translate;
	transition:
		translate var(--su-swap-hover-duration, 1s) var(--su-swap-hover-ease, cubic-bezier(0.19, 1, 0.22, 1)) var(--su-swap-hover-delay, 0s),
		--su-swap-hover-p var(--su-swap-hover-duration, 1s) var(--su-swap-hover-ease, cubic-bezier(0.19, 1, 0.22, 1)) var(--su-swap-hover-delay, 0s);
}

.su-swap-hover__icon-copy {
	position: absolute;
	top: calc(var(--su-swap-hover-fromtype, 1) * 100%);
	left: 0;
	display: inline-flex;
	transform-origin: var(--su-swap-hover-origin, left top);
	transform: skewY(calc(var(--su-swap-hover-skew, 0deg) * var(--su-swap-hover-fromtype, 1) * (1 - var(--su-swap-hover-p) + sin(var(--su-swap-hover-p) * 180deg))));
}

.su-swap-hover:is(:hover, :has(.su-swap-hover__anchor:focus-visible)) .su-swap-hover__icon-inner {
	--su-swap-hover-p: 1;
	translate: 0 calc(var(--su-swap-hover-fromtype, 1) * -100%);
}

/* ------------------------------------------------------------- gradient */

/* The Gradient control writes background-image on the pieces and their
   copies; this clips it to the text. Only under --gradient, which the
   element adds only when a gradient is set, so text never goes invisible
   for want of one. */
.su-swap-hover--gradient :is(.su-swap-hover__span, .su-swap-hover__word, .su-swap-hover__char),
.su-swap-hover--gradient :is(.su-swap-hover__span, .su-swap-hover__word, .su-swap-hover__char)::after {
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

/* Words / Chars: the pieces paint the gradient, not the line (whose own
   background would show through), and each piece paints its slice of one
   gradient across the whole line, so it reads as one - not one per word. */
.su-swap-hover--gradient.su-swap-hover--remove-line .su-swap-hover__span {
	background-image: none !important;
}

.su-swap-hover--gradient.su-swap-hover--remove-line :is(.su-swap-hover__word, .su-swap-hover__char),
.su-swap-hover--gradient.su-swap-hover--remove-line :is(.su-swap-hover__word, .su-swap-hover__char)::after {
	background-size: var(--su-swap-hover-bg-size, auto);
	background-position: var(--su-swap-hover-bg-pos, 0 0);
	background-repeat: no-repeat;
}

@media (prefers-reduced-motion: reduce) {
	.su-swap-hover__span,
	.su-swap-hover__word,
	.su-swap-hover__char,
	.su-swap-hover__icon-inner {
		transition-duration: 1ms;
		transition-delay: 0s;
	}
}
