@@ -71,6 +71,22 @@ function createManualRaf() {
7171 } ;
7272}
7373
74+ function withStudioIframe ( run : ( ) => void ) : void {
75+ const originalParent = window . parent ;
76+ Object . defineProperty ( window , "parent" , {
77+ configurable : true ,
78+ value : { } ,
79+ } ) ;
80+ try {
81+ run ( ) ;
82+ } finally {
83+ Object . defineProperty ( window , "parent" , {
84+ configurable : true ,
85+ value : originalParent ,
86+ } ) ;
87+ }
88+ }
89+
7490describe ( "initSandboxRuntimeModular" , ( ) => {
7591 const originalRequestAnimationFrame = window . requestAnimationFrame ;
7692 const originalCancelAnimationFrame = window . cancelAnimationFrame ;
@@ -413,6 +429,207 @@ describe("initSandboxRuntimeModular", () => {
413429 expect ( sceneB . style . visibility ) . toBe ( "visible" ) ;
414430 } ) ;
415431
432+ it ( "hides GSAP tween targets inside a hidden timed clip (issue #1387)" , ( ) => {
433+ withStudioIframe ( ( ) => {
434+ const root = document . createElement ( "div" ) ;
435+ root . setAttribute ( "data-composition-id" , "main" ) ;
436+ root . setAttribute ( "data-root" , "true" ) ;
437+ root . setAttribute ( "data-start" , "0" ) ;
438+ root . setAttribute ( "data-duration" , "8" ) ;
439+ root . setAttribute ( "data-width" , "1920" ) ;
440+ root . setAttribute ( "data-height" , "1080" ) ;
441+ document . body . appendChild ( root ) ;
442+
443+ const captionOne = document . createElement ( "div" ) ;
444+ captionOne . id = "t01" ;
445+ captionOne . setAttribute ( "data-start" , "0" ) ;
446+ captionOne . setAttribute ( "data-duration" , "4" ) ;
447+ root . appendChild ( captionOne ) ;
448+
449+ const lineOne = document . createElement ( "div" ) ;
450+ lineOne . className = "line" ;
451+ // Studio stamps full-duration pseudo-clips on GSAP tween targets.
452+ lineOne . setAttribute ( "data-start" , "0" ) ;
453+ lineOne . setAttribute ( "data-duration" , "8" ) ;
454+ captionOne . appendChild ( lineOne ) ;
455+
456+ const captionTwo = document . createElement ( "div" ) ;
457+ captionTwo . id = "t02" ;
458+ captionTwo . setAttribute ( "data-start" , "4" ) ;
459+ captionTwo . setAttribute ( "data-duration" , "4" ) ;
460+ root . appendChild ( captionTwo ) ;
461+
462+ const lineTwo = document . createElement ( "div" ) ;
463+ lineTwo . className = "line" ;
464+ lineTwo . setAttribute ( "data-start" , "0" ) ;
465+ lineTwo . setAttribute ( "data-duration" , "8" ) ;
466+ captionTwo . appendChild ( lineTwo ) ;
467+
468+ window . __timelines = {
469+ main : createMockTimeline ( 8 ) ,
470+ } ;
471+
472+ initSandboxRuntimeModular ( ) ;
473+
474+ const player = window . __player ;
475+ expect ( player ) . toBeDefined ( ) ;
476+
477+ player ?. seek ( 1 ) ;
478+
479+ expect ( captionOne . style . visibility ) . toBe ( "visible" ) ;
480+ expect ( lineOne . style . visibility ) . toBe ( "visible" ) ;
481+ expect ( captionTwo . style . visibility ) . toBe ( "hidden" ) ;
482+ expect ( lineTwo . style . visibility ) . toBe ( "hidden" ) ;
483+
484+ player ?. seek ( 5 ) ;
485+
486+ expect ( captionOne . style . visibility ) . toBe ( "hidden" ) ;
487+ expect ( lineOne . style . visibility ) . toBe ( "hidden" ) ;
488+ expect ( captionTwo . style . visibility ) . toBe ( "visible" ) ;
489+ expect ( lineTwo . style . visibility ) . toBe ( "visible" ) ;
490+ } ) ;
491+ } ) ;
492+
493+ it ( "does not suppress descendant visibility in render mode (top-level page)" , ( ) => {
494+ const root = document . createElement ( "div" ) ;
495+ root . setAttribute ( "data-composition-id" , "main" ) ;
496+ root . setAttribute ( "data-root" , "true" ) ;
497+ root . setAttribute ( "data-start" , "0" ) ;
498+ root . setAttribute ( "data-duration" , "8" ) ;
499+ root . setAttribute ( "data-width" , "1920" ) ;
500+ root . setAttribute ( "data-height" , "1080" ) ;
501+ document . body . appendChild ( root ) ;
502+
503+ const panel = document . createElement ( "div" ) ;
504+ panel . id = "panel" ;
505+ panel . setAttribute ( "data-start" , "0" ) ;
506+ panel . setAttribute ( "data-duration" , "2" ) ;
507+ root . appendChild ( panel ) ;
508+
509+ const headline = document . createElement ( "h1" ) ;
510+ headline . className = "headline" ;
511+ // Authored child window outlives the parent clip — render keeps legacy behavior.
512+ headline . setAttribute ( "data-start" , "0" ) ;
513+ headline . setAttribute ( "data-duration" , "8" ) ;
514+ panel . appendChild ( headline ) ;
515+
516+ window . __timelines = {
517+ main : createMockTimeline ( 8 ) ,
518+ } ;
519+
520+ initSandboxRuntimeModular ( ) ;
521+
522+ const player = window . __player ;
523+ expect ( player ) . toBeDefined ( ) ;
524+
525+ player ?. seek ( 3 ) ;
526+
527+ expect ( panel . style . visibility ) . toBe ( "hidden" ) ;
528+ expect ( headline . style . visibility ) . toBe ( "visible" ) ;
529+ } ) ;
530+
531+ it ( "does not stamp Studio timing on GSAP targets inside authored timed clips" , ( ) => {
532+ withStudioIframe ( ( ) => {
533+ const root = document . createElement ( "div" ) ;
534+ root . setAttribute ( "data-composition-id" , "main" ) ;
535+ root . setAttribute ( "data-root" , "true" ) ;
536+ root . setAttribute ( "data-start" , "0" ) ;
537+ root . setAttribute ( "data-duration" , "8" ) ;
538+ root . setAttribute ( "data-width" , "1920" ) ;
539+ root . setAttribute ( "data-height" , "1080" ) ;
540+ document . body . appendChild ( root ) ;
541+
542+ const caption = document . createElement ( "div" ) ;
543+ caption . id = "t01" ;
544+ caption . setAttribute ( "data-start" , "0" ) ;
545+ caption . setAttribute ( "data-duration" , "4" ) ;
546+ root . appendChild ( caption ) ;
547+
548+ const line = document . createElement ( "div" ) ;
549+ line . className = "line" ;
550+ caption . appendChild ( line ) ;
551+
552+ const tweenTarget = {
553+ targets : ( ) => [ line ] ,
554+ } ;
555+ const timeline = createMockTimeline ( 8 ) as RuntimeTimelineLike & {
556+ getChildren : ( nested ?: boolean ) => Array < { targets : ( ) => Element [ ] } > ;
557+ } ;
558+ timeline . getChildren = ( ) => [ tweenTarget ] ;
559+
560+ window . __timelines = {
561+ main : timeline ,
562+ } ;
563+
564+ initSandboxRuntimeModular ( ) ;
565+
566+ expect ( line . hasAttribute ( "data-start" ) ) . toBe ( false ) ;
567+ expect ( line . hasAttribute ( "data-duration" ) ) . toBe ( false ) ;
568+ } ) ;
569+ } ) ;
570+
571+ it ( "hides tween targets inside inactive multi-panel beats (niemmo panel stack)" , ( ) => {
572+ withStudioIframe ( ( ) => {
573+ const root = document . createElement ( "div" ) ;
574+ root . setAttribute ( "data-composition-id" , "niemmo-launch-50" ) ;
575+ root . setAttribute ( "data-root" , "true" ) ;
576+ root . setAttribute ( "data-start" , "0" ) ;
577+ root . setAttribute ( "data-duration" , "50" ) ;
578+ root . setAttribute ( "data-width" , "1280" ) ;
579+ root . setAttribute ( "data-height" , "720" ) ;
580+ document . body . appendChild ( root ) ;
581+
582+ const panelA = document . createElement ( "div" ) ;
583+ panelA . className = "panel clip" ;
584+ panelA . setAttribute ( "data-composition-id" , "cold-open" ) ;
585+ panelA . setAttribute ( "data-start" , "0" ) ;
586+ panelA . setAttribute ( "data-duration" , "2" ) ;
587+ root . appendChild ( panelA ) ;
588+
589+ const headlineA = document . createElement ( "h1" ) ;
590+ headlineA . className = "co-headline" ;
591+ headlineA . setAttribute ( "data-start" , "0" ) ;
592+ headlineA . setAttribute ( "data-duration" , "50" ) ;
593+ panelA . appendChild ( headlineA ) ;
594+
595+ const panelB = document . createElement ( "div" ) ;
596+ panelB . className = "panel clip" ;
597+ panelB . setAttribute ( "data-composition-id" , "problem-dev-beat" ) ;
598+ panelB . setAttribute ( "data-start" , "2" ) ;
599+ panelB . setAttribute ( "data-duration" , "2.5" ) ;
600+ root . appendChild ( panelB ) ;
601+
602+ const headlineB = document . createElement ( "h1" ) ;
603+ headlineB . className = "pb-headline" ;
604+ headlineB . setAttribute ( "data-start" , "0" ) ;
605+ headlineB . setAttribute ( "data-duration" , "50" ) ;
606+ panelB . appendChild ( headlineB ) ;
607+
608+ window . __timelines = {
609+ "niemmo-launch-50" : createMockTimeline ( 50 ) ,
610+ } ;
611+
612+ initSandboxRuntimeModular ( ) ;
613+
614+ const player = window . __player ;
615+ expect ( player ) . toBeDefined ( ) ;
616+
617+ player ?. seek ( 1 ) ;
618+
619+ expect ( panelA . style . visibility ) . toBe ( "visible" ) ;
620+ expect ( headlineA . style . visibility ) . toBe ( "visible" ) ;
621+ expect ( panelB . style . visibility ) . toBe ( "hidden" ) ;
622+ expect ( headlineB . style . visibility ) . toBe ( "hidden" ) ;
623+
624+ player ?. seek ( 3 ) ;
625+
626+ expect ( panelA . style . visibility ) . toBe ( "hidden" ) ;
627+ expect ( headlineA . style . visibility ) . toBe ( "hidden" ) ;
628+ expect ( panelB . style . visibility ) . toBe ( "visible" ) ;
629+ expect ( headlineB . style . visibility ) . toBe ( "visible" ) ;
630+ } ) ;
631+ } ) ;
632+
416633 it ( "clamps nested media to the authored host window on seek" , ( ) => {
417634 const root = document . createElement ( "div" ) ;
418635 root . setAttribute ( "data-composition-id" , "main" ) ;
0 commit comments