You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

63 lines
2.5 KiB

import { Controller } from "@hotwired/stimulus"
// The animation this drives only exists on small screens, and never for
// someone who asked for less motion. Anywhere else the link just navigates.
const MOTION = "(max-width: 1023.98px) and (prefers-reduced-motion: no-preference)"
export default class extends Controller {
static targets = ["video"]
// Tapping through to the next stage hands over to the stage's own video: the
// copy leaves, the hero grows into the space it frees, and the video fades up
// behind the result. The next screen then arrives over the video's tail.
exit(event) {
if (this.leaving) return
if (!this.hasVideoTarget) return
if (!window.matchMedia(MOTION).matches) return
event.preventDefault()
this.link = event.currentTarget
const answers = this.element.querySelector(".answers-container")
if (answers) this.element.style.setProperty("--exit-shift", `${answers.offsetHeight}px`)
// The last video hands over to the results rather than to another stage, so
// the header leaves with the copy and the video takes the whole screen.
if (event.params.full) {
const header = this.element.querySelector(".stage-header-container")
if (header) this.element.style.setProperty("--header-shift", `${header.offsetHeight}px`)
this.element.classList.add("is-finishing")
}
this.element.classList.add("is-exiting")
this.videoTarget.addEventListener("timeupdate", this.checkTail)
this.videoTarget.addEventListener("ended", this.follow, { once: true })
this.videoTarget.addEventListener("error", this.follow, { once: true })
this.videoTarget.play().catch(this.follow)
}
// The next stage assembles itself over the tail of the video rather than
// after it, so the last frame is still moving while the new screen arrives.
checkTail = () => {
const { duration, currentTime } = this.videoTarget
if (!duration || duration === Infinity) return
if ((duration - currentTime) * 1000 <= this.#lead()) this.follow()
}
follow = () => {
if (this.leaving) return
this.leaving = true
this.videoTarget.removeEventListener("timeupdate", this.checkTail)
this.link?.click()
}
// How much of the video the incoming screen overlaps -- the same length the
// transition itself runs for.
#lead() {
const declared = getComputedStyle(document.documentElement).getPropertyValue("--dur-base")
return parseFloat(declared) * (declared.includes("ms") ? 1 : 1000) || 600
}
}