|
|
import "@hotwired/turbo-rails"
|
|
|
import { Application } from "@hotwired/stimulus"
|
|
|
|
|
|
import LanguageMenuController from "language_menu_controller"
|
|
|
import CarouselController from "carousel_controller"
|
|
|
import IntroController from "intro_controller"
|
|
|
import ChanceController from "chance_controller"
|
|
|
import ShareController from "share_controller"
|
|
|
import ModalController from "modal_controller"
|
|
|
import RatingController from "rating_controller"
|
|
|
import StartController from "start_controller"
|
|
|
import AnswerController from "answer_controller"
|
|
|
|
|
|
const application = Application.start()
|
|
|
|
|
|
application.register("language-menu", LanguageMenuController)
|
|
|
application.register("carousel", CarouselController)
|
|
|
application.register("intro", IntroController)
|
|
|
application.register("chance", ChanceController)
|
|
|
application.register("share", ShareController)
|
|
|
application.register("modal", ModalController)
|
|
|
application.register("rating", RatingController)
|
|
|
application.register("start", StartController)
|
|
|
application.register("answer", AnswerController)
|
|
|
|
|
|
// The whole game runs inside one turbo-frame so the URL never changes. Frame
|
|
|
// navigations don't reset scroll the way a full page visit does, so each new
|
|
|
// screen would otherwise open at the previous screen's scroll offset.
|
|
|
document.addEventListener("turbo:frame-render", (event) => {
|
|
|
if (event.target.id === "game") window.scrollTo(0, 0)
|
|
|
})
|
|
|
|
|
|
// Which screen pairs swap with a view transition instead of appearing outright,
|
|
|
// keyed by the template class the layout puts on <main>. The value names the
|
|
|
// animation, which lives in application.css under [data-screen-transition].
|
|
|
const SCREEN_TRANSITIONS = {
|
|
|
"intro>stage": "slide-left",
|
|
|
"stage>good_answer": "answer",
|
|
|
"stage>bad_answer": "answer",
|
|
|
"good_answer>stage": "stage-in",
|
|
|
"bad_answer>stage": "stage-in",
|
|
|
"good_answer>last_save": "last-save-in",
|
|
|
"bad_answer>last_save": "last-save-in",
|
|
|
"last_save>good_answer": "answer",
|
|
|
"last_save>bad_answer": "answer",
|
|
|
"good_answer>results": "slide-up",
|
|
|
"bad_answer>results": "slide-up"
|
|
|
}
|
|
|
|
|
|
// The desktop layout is a different composition -- two columns rather than one
|
|
|
// stack -- so the pairs that animate there, and how, are their own map.
|
|
|
const WIDE_SCREEN_TRANSITIONS = {
|
|
|
"intro>stage": "stage-in-wide",
|
|
|
"stage>good_answer": "answer-wide",
|
|
|
"stage>bad_answer": "answer-wide",
|
|
|
"good_answer>stage": "stage-in-wide",
|
|
|
"bad_answer>stage": "stage-in-wide",
|
|
|
"good_answer>last_save": "stage-in-wide",
|
|
|
"bad_answer>last_save": "stage-in-wide",
|
|
|
"last_save>good_answer": "answer-wide",
|
|
|
"last_save>bad_answer": "answer-wide",
|
|
|
"good_answer>results": "results-in-wide",
|
|
|
"bad_answer>results": "results-in-wide"
|
|
|
}
|
|
|
|
|
|
const TRANSITION_MEDIA = "(max-width: 1023.98px) and (prefers-reduced-motion: no-preference)"
|
|
|
const WIDE_TRANSITION_MEDIA = "(min-width: 1024px) and (prefers-reduced-motion: no-preference)"
|
|
|
|
|
|
function screenTransition(newFrame) {
|
|
|
if (!document.startViewTransition) return null
|
|
|
|
|
|
const from = document.querySelector("turbo-frame#game main")?.classList[0]
|
|
|
const to = newFrame.querySelector("main")?.classList[0]
|
|
|
const pair = `${from}>${to}`
|
|
|
|
|
|
if (window.matchMedia(TRANSITION_MEDIA).matches) return SCREEN_TRANSITIONS[pair]
|
|
|
if (window.matchMedia(WIDE_TRANSITION_MEDIA).matches) return WIDE_SCREEN_TRANSITIONS[pair]
|
|
|
|
|
|
return null
|
|
|
}
|
|
|
|
|
|
// Turbo hands over the function that swaps the frame's contents, so running it
|
|
|
// inside a view transition puts the old and the new screen on screen together
|
|
|
// for as long as the animation lasts.
|
|
|
document.addEventListener("turbo:before-frame-render", (event) => {
|
|
|
if (event.target.id !== "game") return
|
|
|
|
|
|
const name = screenTransition(event.detail.newFrame)
|
|
|
if (!name) return
|
|
|
|
|
|
const render = event.detail.render
|
|
|
event.detail.render = (currentElement, newElement) => {
|
|
|
document.documentElement.dataset.screenTransition = name
|
|
|
|
|
|
// Two navigations at once -- the intro video ending on top of a tap -- leave
|
|
|
// the first transition aborted, so the settled state is reached either way.
|
|
|
const clear = () => delete document.documentElement.dataset.screenTransition
|
|
|
|
|
|
// Anything a screen wants to animate once it has landed hangs off this: the
|
|
|
// snapshots are gone by then, so it plays on the real elements.
|
|
|
const settle = () => {
|
|
|
clear()
|
|
|
document.querySelector("turbo-frame#game main")?.classList.add("is-settling")
|
|
|
}
|
|
|
|
|
|
const transition = document.startViewTransition(() => render(currentElement, newElement))
|
|
|
transition.finished.then(settle, clear)
|
|
|
|
|
|
return transition.updateCallbackDone.catch(clear)
|
|
|
}
|
|
|
})
|
|
|
|
|
|
// Configure Stimulus development experience
|
|
|
application.debug = false
|
|
|
window.Stimulus = application
|
|
|
|
|
|
export { application }
|
|
|
|