@@ -31,14 +31,30 @@ const product = (axes) =>
3131const caseId = ( c ) =>
3232 [ c . gesture , c . gsap , c . placement , `r${ c . rotation } ` , c . nesting , `z${ c . zoom } ` ] . join ( "-" ) ;
3333
34+ // An <img> as Studio places a dropped picture, full-frame or smaller, at gsap none (one tween for contrast).
35+ // Each pointer gesture also gets the stray no-button move a hand drag meets (case.mjs strayMove).
36+ // The id's placement token is letters only, so `-img(contain|cover)` selects them.
37+ function imageCases ( ) {
38+ const fitted = product ( {
39+ fit : [ "contain" , "cover" ] ,
40+ size : [ "full" , "small" ] ,
41+ nesting : AXES . nesting ,
42+ gesture : GESTURES ,
43+ } ) . map ( ( c ) => ( { ...c , gsap : "none" } ) ) ;
44+ return fitted
45+ . concat ( { fit : "contain" , size : "full" , nesting : "root" , gesture : "move" , gsap : "tween" } )
46+ . map ( ( c ) => ( { ...c , placement : `img${ c . fit } ${ c . size } ` , rotation : 0 , zoom : 100 , stray : true } ) )
47+ . map ( ( c ) => ( { id : caseId ( c ) , ...c } ) ) ;
48+ }
49+
3450/** `pr` is a smaller slice for CI; its final size is still an open decision. */
3551export function buildGrid ( kind = "full" ) {
3652 return (
3753 product ( { ...AXES , gesture : GESTURES } )
3854 // xPercent only exists through GSAP on the target itself.
3955 . filter ( ( c ) => c . placement !== "xpercent" || ! [ "none" , "idle" ] . includes ( c . gsap ) )
4056 . map ( ( c ) => ( { id : caseId ( c ) , ...c , other : c . gsap === "idle" } ) )
41- . concat ( dragCases ( ) )
57+ . concat ( dragCases ( ) , imageCases ( ) )
4258 . filter ( ( c ) => kind !== "pr" || ( c . zoom === 100 && c . nesting === "root" ) )
4359 ) ;
4460}
@@ -59,6 +75,32 @@ const OTHER_PLACEMENT_CSS = {
5975 center : "left: 75%; top: 75%; translate: -50% -50%;" ,
6076} ;
6177
78+ // A flat 4:3 picture in the target colour: contain letterboxes it in a 16:9 box and cover crops it.
79+ const IMAGE_SRC =
80+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAEElEQVR42mP4cEABjhhwcgCyFhXBc/l8wQAAAABJRU5ErkJggg==" ;
81+
82+ /** The target's composition frame in composition px: nothing outside it reaches the producer frame. */
83+ export function frameOf ( spec ) {
84+ const h = spec . nesting === "nested" ? NESTED_HOST : { left : 0 , top : 0 , ...COMPOSITION } ;
85+ return { left : h . left , top : h . top , right : h . left + h . width , bottom : h . top + h . height } ;
86+ }
87+
88+ // fallow-ignore-next-line complexity
89+ function targetBox ( spec ) {
90+ if ( ! spec . fit )
91+ return `${ PLACEMENT_CSS [ spec . placement ] } width: ${ TARGET . width } px; height: ${ TARGET . height } px;` ;
92+ const f = frameOf ( spec ) ;
93+ const [ w , h ] = spec . size === "full" ? [ f . right - f . left , f . bottom - f . top ] : [ 640 , 360 ] ;
94+ const at = spec . size === "full" ? [ 0 , 0 ] : [ 640 , 360 ] ;
95+ // The box colour fills contain's letterbox, so the producer's pixel box is the element box the bench measures.
96+ return `left: ${ at [ 0 ] } px; top: ${ at [ 1 ] } px; width: ${ w } px; height: ${ h } px; object-fit: ${ spec . fit } ;` ;
97+ }
98+
99+ const targetEl = ( spec , attrs ) =>
100+ spec . fit
101+ ? `<img id="target"${ attrs } src="${ IMAGE_SRC } " alt="" />`
102+ : `<div id="target"${ attrs } >${ targetText ( spec ) } </div>` ;
103+
62104const TEXT = "Edit accuracy bench" ;
63105const targetText = ( spec ) => ( spec . text ? TEXT : "" ) ;
64106
@@ -69,7 +111,7 @@ function targetCss(spec) {
69111 : "" ;
70112 // White text reads as full coverage in the render's luminance box, so it leaves that metric alone.
71113 const text = spec . text ? " color: #ffffff; font: 28px/1.2 sans-serif;" : "" ;
72- return `#target { position: absolute; ${ PLACEMENT_CSS [ spec . placement ] } width: ${ TARGET . width } px; height: ${ TARGET . height } px; background: ${ TARGET . color } ;${ rotate } ${ text } }${ other } ` ;
114+ return `#target { position: absolute; ${ targetBox ( spec ) } background: ${ TARGET . color } ;${ rotate } ${ text } }${ other } ` ;
73115}
74116
75117// fallow-ignore-next-line complexity
@@ -99,7 +141,7 @@ function rootHtml(spec) {
99141 const nested = spec . nesting === "nested" ;
100142 const body = nested
101143 ? `<div id="scene-sub" data-composition-id="sub" data-composition-src="compositions/sub.html" data-start="0" data-duration="4" data-track-index="1" style="position: absolute; left: ${ NESTED_HOST . left } px; top: ${ NESTED_HOST . top } px; width: ${ NESTED_HOST . width } px; height: ${ NESTED_HOST . height } px; overflow: hidden"></div>`
102- : `<div id="target" class="clip" data-start="0" data-duration="4" data-track-index="1"> ${ targetText ( spec ) } </div> ${ spec . other ? `\n <div id="other" class="clip" data-start="0" data-duration="4" data-track-index="2"></div>` : "" } ` ;
144+ : `${ targetEl ( spec , ' class="clip" data-start="0" data-duration="4" data-track-index="1"' ) } ${ spec . other ? `\n <div id="other" class="clip" data-start="0" data-duration="4" data-track-index="2"></div>` : "" } ` ;
103145 const script = spec . gsap === "none" ? "" : timelineScript ( "main" , nested ? [ ] : gsapLines ( spec ) ) ;
104146 return `<!doctype html>
105147<html lang="en">
@@ -125,7 +167,7 @@ function subHtml(spec) {
125167 const script = spec . gsap === "none" ? "" : timelineScript ( "sub" , gsapLines ( spec ) ) ;
126168 return `<template id="sub-template">
127169 <div id="sub" data-composition-id="sub" data-width="${ NESTED_HOST . width } " data-height="${ NESTED_HOST . height } ">
128- <div id="target"> ${ targetText ( spec ) } </div> ${ spec . other ? `\n <div id="other"></div>` : "" }
170+ ${ targetEl ( spec , "" ) } ${ spec . other ? `\n <div id="other"></div>` : "" }
129171 <style>
130172 #sub { position: relative; width: ${ NESTED_HOST . width } px; height: ${ NESTED_HOST . height } px; background: ${ BACKGROUND } ; overflow: hidden; }
131173 ${ targetCss ( spec ) }
0 commit comments