// The carousel effects of the free plugin, and the way any effect is driven.
//
// A partial rather than rules of its own, because it is included twice by
// `style.scss`: once for the browsers that drive the animations from their own
// timelines, and once for the lists `timelines.js` keeps the timelines of by
// hand - where the browser has none, Firefox and Safari before 26, and under
// RTL, whose timelines Chromium measures from the wrong end - where the same
// rules are held still at the time it names. One description of an effect,
// so the two cannot drift. Pro describes its own effects the same way.
//
// Under RTL both apply: the native rules lay the effect out from the first
// frame, `style.scss` holds its animations off until the script has marked
// the list, and the scripted rules, one class heavier, bind them to the
// numbers - over the native moved-round rules too, which leave a marked list
// alone.

$item: ".wp-block-visual-portfolio-item-template__item";
$slide: ".wp-block-visual-portfolio-item-template__slide";
$card: ".wp-block-visual-portfolio-item-template__card";

// The range of a view timeline an animation is played over, and the timeline.
//
// Natively that is `animation-range` and `animation-timeline`, stated in the
// two named ranges: `cover`, from the box's first edge entering the frame to
// its last edge leaving, and `contain`, from the whole box being inside to its
// first edge leaving. `$start` and `$end` are fractions of that range, and may
// reach past it.
//
// Driven by the module there is no timeline. The animation is a second long
// and paused, and its delay is set backwards by the fraction of the range the
// item has covered, which the module publishes on it - so the animation is
// held at the same time the timeline would have played it at, and the fill
// mode holds an item past either end of the range where the timeline would
// have held it. Reaching before a range is written as a sum with its own
// sign: the minifier does not read a subtracted negative.
//
// @param {boolean} $native   Whether the browser drives the animation.
// @param {string}  $timeline The view timeline, natively.
// @param {string}  $range    `cover` or `contain`.
// @param {number}  $start    Where the animation begins, as a fraction of the range.
// @param {number}  $end      Where it ends, as a fraction of the range.
@mixin scrubbed($native, $timeline, $range: cover, $start: 0, $end: 1) {
	@if $native {
		@if $start == 0 and $end == 1 {
			animation-range: $range;
		} @else {
			animation-range: #{$range} #{$start * 100%} #{$range} #{$end * 100%};
		}

		animation-timeline: $timeline;
	} @else {
		$covered: "var(--vp-carousel-#{$range})";

		@if $start < 0 {
			$covered: "(#{$covered} + #{-$start})";
		} @else if $start > 0 {
			$covered: "(#{$covered} - #{$start})";
		}

		@if $end - $start != 1 {
			$covered: "#{$covered} / #{$end - $start}";
		}

		animation-duration: 1s;
		animation-play-state: paused;
		animation-delay: calc(#{$covered} * -1s);
		animation-range: normal;
		animation-timeline: auto;
	}
}

// The carousel effects, for one of the two ways of driving them. Each draws
// every card from where its own slide is in the frame - the timeline of the
// slide, natively, or the numbers `timelines.js` writes on it - so each runs
// round a repeating carousel: a slide the loop has moved round is animated
// from the scroll of the list instead, over the range the module writes on
// it, and `timelines.js` reads such a slide where it is drawn.
//
// Included inside the block selector, so `&` is the list.
//
// @param {boolean} $native Whether the browser drives the animations.
@mixin carousel-effects($native) {
	// Any carousel where the browser has the timelines, and one
	// `timelines.js` has marked.
	$lists: if($native, "", ".vp-carousel-scripted");

	// Cards one whole slide wide over boxes half that, so every card
	// overhangs its neighbours by a quarter of itself on each side. The
	// overlap is what makes a cover flow read as a stack seen edge on,
	// rather than as a row of pictures that happen to be tilted.
	&.vp-layout-carousel#{$lists}.vp-carousel-coverflow {
		// The list is narrowed to one snapping box and the rest of the
		// width is padding, so the first and the last card can come to the
		// middle like any other. Stated against the block rather than
		// against the narrowed list, which is the one way of writing it
		// that does not depend on its own outcome.
		gap: 0;

		// Over the padding a centred carousel is given too, which weighs one
		// class more than the list and comes earlier.
		&,
		&.vp-carousel-snap-center {
			padding-inline: calc(50cqw - 25cqw / max(var(--vp-layout-current-columns), 2));
			scroll-padding-inline: 0;
		}

		> #{$item} {
			// The one snapping box the padding narrows the list to, stated
			// against the frame for the same reason a plain slide is: a
			// repeating carousel is padded by half its width at each end
			// instead, and a box sized against what that padding leaves
			// came out at nothing at all.
			width: calc(50cqw / max(var(--vp-layout-current-columns), 2));
			scroll-snap-align: center;

			// The window the turn is played over: a little under three
			// quarters of a card to each side of the middle. Beyond it a
			// card is already as far on its edge as it goes.
			@if $native {
				view-timeline-name: --vp-carousel-slide;
				view-timeline-axis: inline;
				view-timeline-inset: calc(50cqw - 70cqw / max(var(--vp-layout-current-columns), 2));
			} @else {
				--vp-carousel-timeline-inset: calc(50cqw - 70cqw / max(var(--vp-layout-current-columns), 2));
			}

			// The raised stacking that keeps the card in the middle in front
			// of the ones it overhangs, on the item rather than on the card:
			// Blossom translates every item of a repeating carousel, which
			// makes each one a stacking context of its own, and a z-index on
			// the card inside one ranks nothing outside it.
			animation-name: vp-carousel-coverflow-item;
			animation-timing-function: linear;
			animation-fill-mode: both;

			@include scrubbed($native, --vp-carousel-slide, contain);
		}

		#{$slide} {
			position: relative;
			width: 200%;
			margin-inline: -50%;
			// One vanishing point per card, at its own middle, so a card
			// turns on the spot instead of being seen from the side of the
			// gallery. Stated in card widths rather than in pixels, so the
			// depth of the turn is the same whatever the gallery is sized
			// at - and so the card it foreshortens grows by the same
			// fraction every time, which is what the compensating scale in
			// the keyframes is worked out from.
			perspective: calc(var(--vp-carousel-slide-width) * 5);
			animation-name: vp-carousel-coverflow-slide;
			animation-timing-function: linear;
			animation-fill-mode: both;

			@include scrubbed($native, --vp-carousel-slide, contain);
		}

		#{$card} {
			animation-name: vp-carousel-coverflow-card;
			animation-timing-function: linear;
			animation-fill-mode: both;

			@include scrubbed($native, --vp-carousel-slide, contain);
		}

		@if $native {
			&:not(.vp-carousel-scripted) > #{$item}.vp-carousel-moved-round {
				&,
				#{$slide},
				#{$card} {
					animation-range: var(--vp-carousel-copy-contain);
					animation-timeline: --vp-carousel-scroll;
				}
			}
		}
	}

	// One slide riding over the one it replaces. The picture travels at half
	// the speed of the carousel, so the arriving slide covers the leaving
	// one rather than dissolving into it - nothing here touches opacity.
	&.vp-layout-carousel#{$lists}.vp-carousel-slideshow {
		// Slides that touch, so the arriving one meets the leaving one
		// rather than following it across a gutter.
		gap: 0;

		#{$slide} {
			overflow: hidden;

			@if $native {
				view-timeline-name: --vp-carousel-slide;
				view-timeline-axis: inline;
			}
		}

		#{$card} {
			animation-name: vp-carousel-slideshow-card;
			animation-timing-function: linear;
			animation-fill-mode: both;

			@include scrubbed($native, --vp-carousel-slide);
		}

		@if $native {
			&:not(.vp-carousel-scripted) > #{$item}.vp-carousel-moved-round #{$card} {
				animation-range: var(--vp-carousel-copy-cover);
				animation-timeline: --vp-carousel-scroll;
			}
		}
	}

	// One slide giving way to the next in place. A card is carried back by
	// what its slide is carried forward, so it stays over the frame while the
	// slide scrolls under it, and which of two cards over the frame is on top
	// is the item's to say, on a timeline of its own. The card is see-through
	// on its way in and solid from the middle on, and stacked above its
	// neighbours until it reaches the middle - so scrolled on, the next card
	// fades in over the current one, and scrolled back, the current card
	// fades out over the previous one.
	&.vp-layout-carousel#{$lists}.vp-carousel-fade {
		// A slide is carried two of its widths over its passage, one of them
		// to the middle, so a card held over the frame is carried back by its
		// own width.
		--vp-carousel-pin: 100%;

		gap: 0;

		> #{$item} {
			animation-name: vp-carousel-arriving-above;
			animation-timing-function: linear;
			animation-fill-mode: both;

			@if $native {
				view-timeline-name: --vp-carousel-item;
				view-timeline-axis: inline;
			}

			@include scrubbed($native, --vp-carousel-item);
		}

		@if $native {
			#{$slide} {
				view-timeline-name: --vp-carousel-slide;
				view-timeline-axis: inline;
			}
		}

		#{$card} {
			animation-name: vp-carousel-fade-card;
			animation-timing-function: linear;
			animation-fill-mode: both;

			@include scrubbed($native, --vp-carousel-slide);
		}

		@if $native {
			&:not(.vp-carousel-scripted) > #{$item}.vp-carousel-moved-round {
				&,
				#{$card} {
					animation-range: var(--vp-carousel-copy-cover);
					animation-timeline: --vp-carousel-scroll;
				}
			}
		}
	}
}
