11/* eslint-disable @typescript-eslint/ban-ts-comment -- GSAP class component uses @ts-ignore extensively for dynamic internal state */
22// @ts -nocheck
33"use client" ;
4- import { CLICKABLE , preloadClick , unlockClick , playClick } from './clickSound' ;
4+ import { CLICKABLE , preloadClick , warmClick , unlockClick , playClick } from './clickSound' ;
55/* eslint-disable @typescript-eslint/no-explicit-any -- view-model is dynamically constructed and consumed across many child views */
66/* eslint-disable @typescript-eslint/no-unused-vars -- some destructured vars are kept for future use */
77
@@ -262,14 +262,17 @@ export class Innovision extends Component<Props, State> {
262262 try { this . hintSeen = localStorage . getItem ( HINT_KEY ) === '1' ; } catch { }
263263 this . setState ( { saved, compact : innerWidth < 1100 , narrow : innerWidth < 720 , coarse : matchMedia ( '(pointer: coarse)' ) . matches } ) ;
264264 if ( lowPowerDevice ( ) ) this . goLowPower ( ) ;
265- // Resize work forces layout (title fit, map panels), so it runs at most once per frame.
266- let rz = 0 ;
265+ // Resize work forces layout (title fit, map panels), so it runs at most once per frame. Both depend on the width
266+ // only: a phone's toolbar or keyboard changes just the height, and re-tweening the map panels then cost a second of layout.
267+ let rz = 0 , rw = innerWidth ;
267268 this . listen ( window , 'resize' , ( ) => {
268269 if ( rz ) return ;
269270 rz = requestAnimationFrame ( ( ) => {
270271 rz = 0 ;
271272 const c = innerWidth < 1100 , n = innerWidth < 720 ;
272273 if ( c !== this . state . compact || n !== this . state . narrow ) this . setState ( { compact : c , narrow : n } , ( ) => { if ( this . state . view === 'detail' ) this . setupDetailScroll ( true ) ; } ) ;
274+ if ( innerWidth === rw ) return ;
275+ rw = innerWidth ;
273276 this . fitTitle ( ) ;
274277 if ( this . ctx ) this . openMap ( this . mapOpen ) ;
275278 } ) ;
@@ -666,6 +669,8 @@ export class Innovision extends Component<Props, State> {
666669 /** The only sound is the click (clickSound.ts): every button, link or tab clicked anywhere answers with a tick. */
667670 sound ( ) {
668671 preloadClick ( ) ;
672+ // Created while the loader counts up, so that a first tap during the landing animation doesn't stall it either.
673+ this . cleanups . push ( this . idle ( warmClick ) ) ;
669674 [ 'pointerdown' , 'keydown' ] . forEach ( ( ev ) => this . listen ( window , ev , unlockClick , { once : true , capture : true } ) ) ;
670675 this . listen ( document , 'click' , ( e ) => {
671676 const t = ( e . target as Element | null ) ?. closest ?.( CLICKABLE ) ;
@@ -702,6 +707,12 @@ export class Innovision extends Component<Props, State> {
702707 // The overlay and view a visitor is most likely to open next are fetched ahead too (their own hover/tap also does).
703708 // The pages of their own too, so MERCH (and REGISTER while closed) open at once (a no-op on the dev server).
704709 this . cleanups . push ( this . idle ( ( ) => { this . prefetch ( 'auth' ) ; this . prefetch ( 'detail' ) ; OWN_PAGES . forEach ( ( p ) => this . props . router ?. prefetch ( p ) ) ; } ) ) ;
710+ // The menu (where the top bar has a MENU button) and the register / log-in overlay are mounted, closed, ahead of
711+ // time: mounting one on its first tap blocked a phone for ~100 ms just as its reveal began. One per idle period.
712+ this . cleanups . push ( this . idle ( ( ) => {
713+ if ( this . state . compact ) void this . mount ( 'menu' ) ;
714+ this . cleanups . push ( this . idle ( ( ) => { void this . mount ( 'auth' ) ; } ) ) ;
715+ } ) ) ;
705716 const queue = [ ...PRELOAD_DEFERRED ] ;
706717 let cancel = ( ) => { } , live = true ;
707718 const batch = ( ) => {
@@ -1528,7 +1539,9 @@ export class Innovision extends Component<Props, State> {
15281539 this . pEls = v ? [ ...v . querySelectorAll < HTMLElement > ( '[data-depth]' ) ] . filter ( ( el ) => { const s = el . closest < HTMLElement > ( '[data-slide]' ) ; return ! s || + ( s . dataset . slide || 0 ) === this . state . index ; } ) : [ ] ;
15291540 }
15301541 hover = ( e : MouseEvent < HTMLElement > ) => {
1531- if ( this . reduce || ! this . ctx ) return ;
1542+ // Touch screens fire mouseenter on every tap: the scramble swaps in letters of other widths, so the tapped label
1543+ // (MENU, REGISTER) changed width for half a second and the top bar around it shook. Hover effects are for mice only.
1544+ if ( this . reduce || ! this . ctx || this . state . coarse ) return ;
15321545 const sp = e . currentTarget . querySelector < HTMLElement > ( '[data-scr]' ) ;
15331546 if ( ! sp ) return ;
15341547 sp . dataset . text = sp . dataset . text || sp . textContent || '' ;
@@ -1922,7 +1935,8 @@ export class Innovision extends Component<Props, State> {
19221935 pagePush ( z ) {
19231936 const sc = z ? String ( 1 + .12 * z ) : '' , o = this . authO , to = z && o ? o . x . toFixed ( 0 ) + 'px ' + o . y . toFixed ( 0 ) + 'px' : '' ;
19241937 // @ts -ignore
1925- ( this . page || [ ] ) . forEach ( ( el ) => { el . style . scale = sc ; el . style . transformOrigin = to ; } ) ;
1938+ // will-change while pushed: without it every frame of the zoom re-rastered the whole page (the home page is many screens tall).
1939+ ( this . page || [ ] ) . forEach ( ( el ) => { el . style . scale = sc ; el . style . transformOrigin = to ; el . style . willChange = sc ? 'transform' : '' ; } ) ;
19261940 const veil = this . $ && this . $ ( '[data-rift-veil]' ) ; if ( veil ) veil . style . opacity = String ( z * .85 ) ;
19271941 }
19281942 // @ts -ignore
@@ -2038,13 +2052,14 @@ export class Innovision extends Component<Props, State> {
20382052 return ;
20392053 }
20402054 g . set ( ins , { autoAlpha : 0 , y : 26 } ) ; g . set ( planet , { autoAlpha : 0 , scale : .7 , yPercent : 16 } ) ; g . set ( ui , { scale : .94 } ) ;
2055+ this . authLayers ( true ) ;
20412056 const r = this . _rift = this . riftNew ( 0 , 0 , 1 , 0 ) ;
20422057 this . riftDraw ( ) ;
20432058 const k = this . speed ( ) ;
20442059 this . warp ( 2.4 / k ) ;
20452060 // @ts -ignore
20462061 this . authTl = g . timeline ( { onUpdate : ( ) => this . riftDraw ( ) , onComplete : ( ) => {
2047- this . _rift = null ; sec . style . clipPath = 'none' ;
2062+ this . _rift = null ; sec . style . clipPath = 'none' ; this . authLayers ( false ) ;
20482063 this . pagePush ( 0 ) ; this . authBusy = false ; this . focusAuth ( ) ;
20492064 } } )
20502065 . fromTo ( sec , { autoAlpha : fromDisc ? 0 : 1 } , { autoAlpha : 1 , duration : fromDisc ? .35 : .01 , ease : 'power1.out' } , 0 )
@@ -2065,14 +2080,15 @@ export class Innovision extends Component<Props, State> {
20652080 this . page = this . livePage ( ) ;
20662081 // @ts -ignore
20672082 const done = ( ) => {
2068- this . _rift = null ;
2083+ this . _rift = null ; this . authLayers ( false ) ;
20692084 g . set ( root , { autoAlpha : 0 , pointerEvents : 'none' } ) ; g . set ( sec , { autoAlpha : 1 } ) ; sec . style . clipPath = 'none' ;
20702085 this . pagePush ( 0 ) ;
20712086 this . authSpin ( false ) ; this . authClosing = false ; this . authBusy = false ;
20722087 this . setState ( { auth : false , err : { } , busyLbl : '' , drag : '' } ) ;
20732088 if ( after ) after ( ) ;
20742089 } ;
20752090 if ( this . reduce ) { g . to ( sec , { autoAlpha : 0 , duration : .3 , onComplete : ( ) => { g . set ( sec , { autoAlpha : 1 } ) ; done ( ) ; } } ) ; return ; }
2091+ this . authLayers ( true ) ;
20762092 const r = this . _rift = this . riftNew ( 1 , 1 , 0 , 1 ) ;
20772093 this . riftDraw ( ) ;
20782094 // @ts -ignore
@@ -2090,11 +2106,19 @@ export class Innovision extends Component<Props, State> {
20902106 if ( this . authTl ) this . authTl . kill ( ) ;
20912107 clearTimeout ( this . _wait ) ; this . warpStop ( ) ; this . _rift = null ;
20922108 const sec = this . $ ( '[data-auth]' ) ; if ( sec ) { sec . style . clipPath = 'none' ; gsap . set ( sec , { autoAlpha : 1 } ) ; }
2109+ this . authLayers ( false ) ;
20932110 gsap . set ( this . $ ( '[data-auth-root]' ) , { autoAlpha : 0 , pointerEvents : 'none' } ) ;
20942111 this . pagePush ( 0 ) ;
20952112 this . authSpin ( false ) ; this . authBusy = false ; this . authClosing = false ;
20962113 this . setState ( { auth : false , busyLbl : '' , drag : '' } ) ;
20972114 }
2115+ /**
2116+ * Gives the form and its planet their own layers while the rift opens or closes: both scale, and the rift's clip
2117+ * changes every frame, which otherwise re-rastered them each frame. Dropped once still, so the form stays crisp.
2118+ */
2119+ authLayers ( on : boolean ) {
2120+ this . $$ ( '[data-a-ui],[data-a-planet-wrap]' ) . forEach ( ( el ) => { el . style . willChange = on ? 'transform' : '' ; } ) ;
2121+ }
20982122 // @ts -ignore
20992123 authSpin ( on ) {
21002124 // @ts -ignore
0 commit comments