Mattias Bodlund 3 weeks ago
parent
commit
40d0fee522
3 changed files with 140 additions and 3 deletions
  1. +117
    -0
      app/assets/stylesheets/application.css
  2. +12
    -2
      app/javascript/application.js
  3. +11
    -1
      app/javascript/modal_controller.js

+ 117
- 0
app/assets/stylesheets/application.css View File

@ -352,6 +352,21 @@ main, .modal-panel {
to { opacity: 1; transform: translateY(0); }
}
@keyframes wave-rise-in {
from { translate: 0 200px; }
to { translate: 0 0; }
}
@keyframes hero-detail-slide-in {
from { transform: translateX(100vw); }
to { transform: translateX(0); }
}
@keyframes hero-wave-slide-in {
from { transform: translateY(100vw); }
to { transform: translateY(0); }
}
@media (max-width: 1023.98px) and (prefers-reduced-motion: no-preference) {
.start .hero-container {
overflow: clip;
@ -610,6 +625,108 @@ main, .modal-panel {
}
@media (min-width: 1024px) and (prefers-reduced-motion: no-preference) {
body:has(.start) header {
animation: header-drop-in var(--dur-base) var(--ease-out) 100ms both;
}
.start > article {
animation: fade-in var(--dur-slow) var(--ease-out) 250ms both;
}
.start .hero-container {
animation: fade-in var(--dur-slow) var(--ease-out) both;
}
.start .hero-container svg.ico-wave {
animation: wave-rise-in var(--dur-slow) var(--ease-out) both;
}
.facts-modal[open] {
translate: 0 0;
transition: translate var(--dur-base) var(--ease-out);
}
@starting-style {
.facts-modal[open] {
translate: 0 100dvh;
}
}
.facts-modal[open]::backdrop {
opacity: 1;
transition: opacity var(--dur-base) var(--ease-out);
}
@starting-style {
.facts-modal[open]::backdrop {
opacity: 0;
}
}
.facts-modal[open].is-leaving {
translate: 0 -100dvh;
transition-timing-function: var(--ease-in);
pointer-events: none;
}
.facts-modal[open].is-leaving::backdrop {
opacity: 0;
}
html[data-screen-transition="stage-in-wide"] main {
view-transition-name: game;
}
html[data-screen-transition="stage-in-wide"]::view-transition-old(root),
html[data-screen-transition="stage-in-wide"]::view-transition-new(root),
html[data-screen-transition="stage-in-wide"]::view-transition-old(game),
html[data-screen-transition="stage-in-wide"]::view-transition-new(game) {
mix-blend-mode: normal;
}
html[data-screen-transition="stage-in-wide"]::view-transition-old(root),
html[data-screen-transition="stage-in-wide"]::view-transition-new(root),
html[data-screen-transition="stage-in-wide"]::view-transition-old(game) {
animation: none;
}
html[data-screen-transition="stage-in-wide"]::view-transition-new(game) {
animation: screen-slide-in var(--dur-base) var(--ease-out) both;
}
html[data-screen-transition="stage-in-wide"] .stage .hero-container img {
view-transition-name: hero-image;
}
html[data-screen-transition="stage-in-wide"]::view-transition-new(hero-image) {
animation: fade-in var(--dur-base) linear var(--dur-base) both;
mix-blend-mode: normal;
}
html[data-screen-transition="stage-in-wide"] .stage .hero-container svg.ico-wave {
view-transition-name: hero-wave;
}
html[data-screen-transition="stage-in-wide"] .stage .hero-container #inter-ikea-systems {
view-transition-name: hero-logo;
}
html[data-screen-transition="stage-in-wide"]::view-transition-new(hero-wave),
html[data-screen-transition="stage-in-wide"]::view-transition-new(hero-logo) {
mix-blend-mode: normal;
}
html[data-screen-transition="stage-in-wide"]::view-transition-new(hero-wave) {
animation: hero-wave-slide-in var(--dur-base) var(--ease-out) both;
}
html[data-screen-transition="stage-in-wide"]::view-transition-new(hero-logo) {
animation: hero-detail-slide-in var(--dur-base) var(--ease-out) both;
}
}
.facts {
--clr-bg: var(--clr-green);
--clr-action: var(--clr-white);


+ 12
- 2
app/javascript/application.js View File

@ -47,16 +47,26 @@ const SCREEN_TRANSITIONS = {
"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"
}
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
if (!window.matchMedia(TRANSITION_MEDIA).matches) 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 SCREEN_TRANSITIONS[`${from}>${to}`]
return null
}
// Turbo hands over the function that swaps the frame's contents, so running it


+ 11
- 1
app/javascript/modal_controller.js View File

@ -41,7 +41,7 @@ export default class extends Controller {
// The screen underneath may still be animating out. The wave is the last
// thing to leave, so waiting on it holds the modal until the exit is done.
async #openWhenSettled() {
const wave = document.querySelector(".is-exiting .hero-container svg.ico-wave")
const wave = this.#isMobile && document.querySelector(".is-exiting .hero-container svg.ico-wave")
if (wave) await this.#settle(wave, "animationend")
if (!this.element.isConnected) return
@ -58,6 +58,10 @@ export default class extends Controller {
// hero pushes past it. The wave rides the same shrink from the screen edge
// up to the seam, since it no longer sits inside the hero it draws under.
async #playEntrance() {
// Desktop opens the panel as a centred dialog that slides up whole, so the
// hero never needs to fill the screen first.
if (!this.#isMobile) return
const panel = this.element.querySelector(".modal-panel")
const frame = panel?.querySelector(".carousel-frame")
const carousel = frame?.querySelector(".carousel")
@ -94,6 +98,12 @@ export default class extends Controller {
boxes.forEach((box) => (box.style.height = ""))
}
// The screen exit and the panel's full-bleed entrance are mobile choreography;
// the desktop panel just slides up over a screen that stays put.
get #isMobile() {
return window.matchMedia("(max-width: 1023.98px)").matches
}
// Resolves when the element's animation or transition ends. Safari doesn't
// reliably have one registered at the moment we ask for it, so the declared
// timing is used as a fallback rather than getAnimations().


Loading…
Cancel
Save