{"schemaVersion":1,"id":"recommended.sm-board-editor","version":"1.0.0","name":"Whiteboard workflow","description":"An editable production workflow using Whiteboard v2.2.0.","stages":[{"id":"main","blockId":"sm.board-editor","version":"2.2.0","label":"Whiteboard"}],"connections":[],"packages":[{"manifest":{"schemaVersion":1,"sdkVersion":"0.1.0","id":"sm.board-editor","version":"2.2.0","name":"Whiteboard","description":"Arrange and edit the complete Whiteboard Step—boards, timing, transitions, backgrounds, text, placed media, element motion, and retained custom fields—in one portable workspace.","kind":"workspace","runtime":"javascript","entry":"src/run.js","license":"MIT","provenance":{"notice":"Original source (authorship declared by the packager)\nLicense: MIT\nChanges: Original implementation declared by the packager; no reused repository or dependency code.\nFiles: LICENSE, src/run.js, src/view.json\n\nMIT License\n\nCopyright (c) 2026 StillMade contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","portableSources":[{"kind":"original","license":"MIT","paths":["LICENSE","src/run.js","src/view.json"],"evidence":["upstream/0/LICENSE","upstream/0/src/run.js","upstream/0/src/view.json"],"changes":"Original implementation declared by the packager; no reused repository or dependency code."}]},"inputs":{"board":{"type":"board-document","context":"board","primary":true,"required":false,"default":{"schemaVersion":1,"strip":[]},"description":"The complete current Whiteboard document. Existing text, media references, motion, timing, and custom fields are retained."},"updatedBoard":{"type":"board-document","required":false,"default":{"schemaVersion":1,"strip":[]},"description":"The complete edited Whiteboard document prepared in this workspace."},"commit":{"type":"boolean","default":false,"description":"Prepare the edited Whiteboard for StillMade review and Apply."},"action":{"type":"text","required":false,"default":"none","description":"The Block-owned interface selects a declared host action."},"presenterPrompt":{"type":"text","required":false,"default":"Friendly presenter explaining the narration","description":"Presenter direction sent only after approval."}},"outputs":{"board":{"type":"board-document","primary":true,"role":"whiteboard_document","description":"The complete edited Whiteboard document for the next Step."},"edit":{"type":"workspace-edit","description":"An exact before/after Whiteboard change reviewed before it updates the project."},"presenterAction":{"type":"approval","required":false,"role":"portable_media_action","description":"Generate presenter poses and return a reviewable Whiteboard edit."},"qualityAction":{"type":"approval","required":false,"role":"portable_media_action","description":"Analyze exact saved images."},"renderAction":{"type":"approval","required":false,"role":"portable_media_action","description":"Render the current board sequence."},"actionEdit":{"type":"workspace-edit","required":false,"description":"Reviewable presenter placement result."},"qualityReport":{"type":"metadata","required":false}},"permissions":{"project":["context.board.read","workspace.board.propose"],"network":[],"filesystem":[],"secrets":[],"actions":["presenter.generate","quality.analyze","final.render"]},"ui":[],"platforms":{"phone":{"supported":true},"browser":{"supported":true},"desktop":{"supported":true}}},"code":"const copy = value => JSON.parse(JSON.stringify(value));\nconst source = copy(input.board || {schemaVersion: 1, strip: []});\nconst candidate = input.commit ? copy(input.updatedBoard) : copy(source);\nif (!candidate || Array.isArray(candidate) || candidate.schemaVersion !== 1 || !Array.isArray(candidate.strip)) {\n  throw new Error('Keep the complete version 1 Whiteboard document and its strip.');\n}\nif (candidate.strip.length > 2000 || !candidate.strip.every(board => board && typeof board === 'object' && !Array.isArray(board))) {\n  throw new Error('Whiteboard supports up to 2,000 valid board rows.');\n}\nconst canvasBoards = candidate.strip.filter(board => board.type === 'canvas');\nif (!canvasBoards.every(board => !board.elements || Array.isArray(board.elements) && board.elements.length <= 5000 && board.elements.every(element => element && typeof element === 'object' && !Array.isArray(element)))) {\n  throw new Error('Each board needs a valid element list with up to 5,000 elements.');\n}\nconst itemEnd=item=>Math.max(0,Number(item.start)||0)+Math.max(.001,Number(item.duration)||Number(item.durationMs)/1000||.001);\nconst outputs={board:candidate,edit:{schemaVersion:1,title:`Update Whiteboard (${candidate.strip.length} boards)`,field:'board',before:source,after:candidate}};const images=candidate.strip.flatMap(board=>board.elements||[]).filter(item=>item&&item.kind==='image'&&item.assetId&&item.versionId&&item.url).slice(0,16);\nif(input.action==='presenter')outputs.presenterAction={schemaVersion:1,kind:'stillmade.media-action',operation:'presenter.generate',request:{board:candidate,prompt:input.presenterPrompt,voiceover:candidate.audio||{schemaVersion:1,words:[]}},resultPort:'actionEdit'};\nif(input.action==='quality'&&images.length)outputs.qualityAction={schemaVersion:1,kind:'stillmade.media-action',operation:'quality.analyze',request:{assets:images.map(({assetId,versionId,url})=>({kind:'image',assetId,versionId,url}))},resultPort:'qualityReport'};\nif(input.action==='render'){const clips=candidate.strip.map((board,index)=>({id:board.panelId||'board-'+index,start:(Number(board.startMs)||0)/1000,duration:Math.max(.001,(Number(board.durationMs)||1000)/1000),canvas:board})),targetDuration=Math.max(.001,...clips.map(itemEnd));outputs.renderAction={schemaVersion:1,kind:'stillmade.media-action',operation:'final.render',request:{schemaVersion:1,kind:'video',timeline:{schemaVersion:1,activeTimelineId:'whiteboard',clips,audio:[],overlays:[],targetDuration},settings:{fps:30,resolution:1080,quality:'high'},source:{id:'sm.board-editor',version:'2.2.0'}},resultPort:null};}\nreturn outputs;\n","view":{"html":"<main class=\"board-workspace\">\n  <header><div><small>Production workspace</small><h1>Whiteboard</h1><p id=\"summary\">Loading the Whiteboard…</p></div><div class=\"actions\"><button id=\"reset\" type=\"button\" class=\"secondary\">Reset changes</button><button id=\"add\" type=\"button\" class=\"secondary\">Add board</button><button id=\"save\" type=\"button\">Review changes</button></div></header>\n  <output id=\"status\" role=\"status\" aria-live=\"polite\"></output>\n  <section class=\"settings\"><h2>Workspace settings</h2><label>Aspect ratio<input id=\"aspect\" type=\"text\" placeholder=\"16:9\"></label></section>\n  <section id=\"boards\" class=\"boards\" aria-label=\"Whiteboard boards\"></section>\n  <details><summary>Complete Whiteboard data</summary><p>Every field and placed media reference is retained. Use this editor for custom fields not shown on the board cards.</p><textarea id=\"raw\" class=\"raw\" spellcheck=\"false\"></textarea><button id=\"applyRaw\" type=\"button\" class=\"secondary\">Apply JSON to workspace</button></details>\n<section><h2>Presenter and delivery</h2><label>Presenter direction<input id=\"presenterPrompt\" type=\"text\" value=\"Friendly presenter explaining the narration\"></label><div class=\"actions\"><button id=\"generatePresenter\" type=\"button\" class=\"secondary\">Generate presenter poses</button><button id=\"qualityReview\" type=\"button\" class=\"secondary\">Review image quality</button><button id=\"renderFinal\" type=\"button\">Render final video</button></div></section></main>\n","css":".board-workspace{max-width:1120px;margin:0 auto;padding:8px;color:var(--fg)}header,.actions,.card-head,.row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}header{justify-content:space-between;margin-bottom:14px}header h1{margin:2px 0}header p,header small,details p,output{color:var(--fg-muted)}\n.settings,.card,details{margin:12px 0;padding:16px;border:1px solid var(--border-tok);border-radius:var(--r-3);background:var(--bg-elev-1)}.settings h2,.card h2{margin:0;font-size:18px}.boards{display:grid;gap:12px}.card-head{justify-content:space-between}.grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}.elements{display:grid;gap:8px;margin-top:10px}.element{padding:10px;border:1px solid var(--border-tok);border-radius:var(--r-2);background:var(--bg)}.retained-row{border-style:dashed}label{display:grid;gap:5px;margin:8px 0;font-size:13px;font-weight:600}input,textarea,select{width:100%;background:var(--bg)}button,input,textarea,select,summary{min-height:44px}button{cursor:pointer}button:disabled{cursor:not-allowed;opacity:.55}textarea{min-height:76px;resize:vertical}.wide{grid-column:1/-1}.secondary{background:transparent;color:var(--fg)}.danger{background:transparent;color:var(--signal)}.raw{min-height:360px;font-family:var(--font-mono);font-size:12px}output{display:block;min-height:24px;margin:8px 2px}\n@media(max-width:820px){.grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:560px){.board-workspace{padding:0}.grid{grid-template-columns:1fr}.actions{width:100%}.actions button{flex:1}.settings,.card,details{padding:12px}}\n","javascript":"const status=document.getElementById('status'),summary=document.getElementById('summary'),raw=document.getElementById('raw'),save=document.getElementById('save'),aspect=document.getElementById('aspect');\nlet source={schemaVersion:1,strip:[]},draft={schemaVersion:1,strip:[]},busy=false,revision=0;\nconst clone=value=>JSON.parse(JSON.stringify(value));\nconst text=value=>value===undefined||value===null?'':String(value);\nconst field=(label,action,value,type='text',wide=false)=>({tag:'label',className:wide?'wide':'',children:[{tag:'span',text:label},type==='textarea'?{tag:'textarea',action,value:text(value),disabled:busy,label}:{tag:'input',type,action,value:Number.isFinite(value)?value:text(value),disabled:busy,label}]});\nconst choice=(label,action,value,options)=>({tag:'label',children:[{tag:'span',text:label},{tag:'select',action,value:text(value),disabled:busy,label,children:options.map(option=>({tag:'option',text:option,value:option}))}]});\nconst toggle=(label,action,value)=>({tag:'label',children:[{tag:'span',text:label},{tag:'input',type:'checkbox',action,checked:!!value,disabled:busy,label}]});\nconst optionList=(value,options)=>options.includes(text(value))||!text(value)?options:[...options,text(value)];\nconst moveButton=(action,label,disabled)=>({tag:'button',action,text:label,className:'secondary',disabled:busy||disabled});\nconst elementCard=(element,boardIndex,elementIndex)=>{const key=String(boardIndex)+':'+String(elementIndex),kind=text(element.kind)||'text',isText=kind==='text',isMedia=kind==='image'||kind==='video',owner=draft.strip.find((item,position)=>position===boardIndex);return {tag:'article',className:'element',children:[{tag:'div',className:'card-head',children:[{tag:'strong',text:kind+' · '+(text(element.text)||text(element.descriptor)||text(element.query)||text(element.id)||String(elementIndex+1))},{tag:'div',className:'row',children:[moveButton('element-up:'+key,'Up',elementIndex===0),moveButton('element-down:'+key,'Down',elementIndex===(owner.elements||[]).length-1),{tag:'button',action:'duplicate-element:'+key,text:'Duplicate',className:'secondary',disabled:busy},{tag:'button',action:'remove-element:'+key,text:'Remove',className:'danger',disabled:busy}]}]},{tag:'div',className:'grid',children:[field('Element ID','element-id:'+key,element.id),choice('Kind','element-kind:'+key,kind,optionList(kind,['text','image','video'])),field('Appears (ms)','element-appear:'+key,element.appearMs,'number'),field('Leaves (ms)','element-leave:'+key,element.leaveMs,'number'),field('Size','element-size:'+key,element.size,'number'),field('X position','element-x:'+key,element.pos&&element.pos.x,'number'),field('Y position','element-y:'+key,element.pos&&element.pos.y,'number'),toggle('Pinned position','element-pinned:'+key,element.pinned),choice('Entrance','element-entrance:'+key,element.entrance||element.anim&&element.anim.type||'pop',optionList(element.entrance||element.anim&&element.anim.type,['pop','fade','draw','slide','none'])),field('Sound effect','element-sfx:'+key,element.sfx),...(isText?[field('Text','element-text:'+key,element.text,'textarea',true),field('Font','element-font:'+key,element.font),field('Color','element-color:'+key,element.color),field('Weight','element-weight:'+key,element.weight,'number'),toggle('Reveal per word','element-per-word:'+key,element.anim&&element.anim.perWord),field('Animation speed (ms)','element-speed:'+key,element.anim&&element.anim.speedMs,'number')]:[]),...(isMedia?[field('Description','element-descriptor:'+key,element.descriptor,'textarea',true),field('Asset ID','element-asset:'+key,element.assetId),field('Version ID','element-version:'+key,element.versionId),field('Media source','element-src:'+key,element.src,'textarea',true)]:[]),...(kind==='video'?[field('Source in (ms)','element-source-in:'+key,element.sourceInMs,'number'),field('Source out (ms)','element-source-out:'+key,element.sourceOutMs,'number'),toggle('Muted','element-muted:'+key,element.muted),toggle('Loop','element-loop:'+key,element.loop)]:[])]}]}};\nconst canvasPositions=()=>draft.strip.map((board,index)=>board.type==='canvas'?index:-1).filter(index=>index>=0);\nconst boardCard=(board,index)=>{if(board.type!=='canvas')return {tag:'article',className:'card retained-row',children:[{tag:'h2',text:'Retained '+(text(board.type)||'workspace')+' row'},{tag:'p',text:'This non-board row stays in its exact strip position and is available in Complete Whiteboard data.'}]};const positions=canvasPositions(),position=positions.indexOf(index);return {tag:'article',className:'card',children:[{tag:'div',className:'card-head',children:[{tag:'h2',text:'Board '+String(position+1)+' · '+(text(board.panelId)||'untitled')},{tag:'div',className:'row',children:[moveButton('move-up:'+String(index),'Up',position===0),moveButton('move-down:'+String(index),'Down',position===positions.length-1),{tag:'button',action:'add-text:'+String(index),text:'Add text',className:'secondary',disabled:busy},{tag:'button',action:'remove-board:'+String(index),text:'Remove board',className:'danger',disabled:busy||positions.length<=1}]}]},{tag:'div',className:'grid',children:[field('Board ID','board-id:'+String(index),board.panelId),field('Scene ID','board-scene:'+String(index),board.sceneId),field('Background','board-background:'+String(index),board.background),field('Starts (ms)','board-start:'+String(index),board.startMs,'number'),field('Length (ms)','board-duration:'+String(index),board.durationMs,'number'),choice('Transition','board-transition:'+String(index),board.transitionIn||'cut',optionList(board.transitionIn,['cut','fade','swirl','wipe','slide'])),field('Transition length (ms)','board-transition-ms:'+String(index),board.transitionMs,'number'),toggle('Automatic arrangement','board-arrange:'+String(index),board.autoArrange!==false)]},{tag:'div',className:'elements',children:Array.isArray(board.elements)&&board.elements.length?board.elements.map((element,elementIndex)=>elementCard(element,index,elementIndex)):[{tag:'p',text:'No elements yet. Add text, request a presenter, or use Complete Whiteboard data for retained media.'}]}]}};\nconst ensure=()=>{if(!draft||Array.isArray(draft)||draft.schemaVersion!==1)draft={schemaVersion:1,strip:[]};if(!Array.isArray(draft.strip))draft.strip=[];if(!draft.meta||Array.isArray(draft.meta))draft.meta={}};\nconst renumber=()=>{let start=0;draft.strip=draft.strip.map(board=>{if(board.type!=='canvas')return board;const next={...board,startMs:start};start+=Math.max(0,Number(board.durationMs)||0);return next})};\nconst draw=()=>{ensure();StillMade.render('boards',draft.strip.length?draft.strip.map(boardCard):[{tag:'p',text:'No boards yet. Add one to begin.'}]);raw.value=JSON.stringify(draft,null,2);aspect.value=text(draft.meta.aspect);const count=draft.strip.filter(board=>board.type==='canvas').length;summary.textContent=String(count)+' board'+(count===1?'':'s')+' in this complete Whiteboard';save.disabled=busy;document.getElementById('reset').disabled=busy;document.getElementById('add').disabled=busy};\nconst changed=()=>{revision+=1;raw.value=JSON.stringify(draft,null,2);status.textContent='Changes are local until you choose Review changes.'};\naspect.addEventListener('input',()=>{ensure();draft.meta.aspect=aspect.value;changed()});\nStillMade.onAction(event=>{if(busy)return;ensure();const parts=event.action.split(':'),kind=parts[0],boardIndex=Number(parts[1]),elementIndex=Number(parts[2]),board=draft.strip.find((item,position)=>position===boardIndex),element=board&&Array.isArray(board.elements)?board.elements.find((item,position)=>position===elementIndex):null;\n const positions=canvasPositions(),canvasPosition=positions.indexOf(boardIndex),wantedPosition=kind==='move-up'?canvasPosition-1:kind==='move-down'?canvasPosition+1:-1,moveTarget=positions.find((value,position)=>position===wantedPosition);\n if(kind==='remove-board'&&positions.length>1){draft.strip=draft.strip.filter((item,position)=>position!==boardIndex);renumber();changed();draw();return}if(moveTarget!==undefined&&board){const target=draft.strip.find((item,position)=>position===moveTarget);draft.strip=draft.strip.map((item,position)=>position===moveTarget?board:position===boardIndex?target:item);renumber();changed();draw();return}if(!board||board.type!=='canvas')return;\n if(kind==='add-text'){if(!Array.isArray(board.elements))board.elements=[];board.elements=[...board.elements,{id:text(board.panelId)+'-text-'+String(board.elements.length+1)+'-'+String(revision+1),kind:'text',text:'New text',appearMs:0,leaveMs:null,pos:null,size:0.08,font:'Hanken Grotesk',color:'#111111',weight:800,anim:{type:'pop',perWord:false,speedMs:180},pinned:false}];changed();draw();return}if(kind==='remove-element'&&element){board.elements=board.elements.filter((item,position)=>position!==elementIndex);changed();draw();return}if(kind==='duplicate-element'&&element){const copy=clone(element);copy.id=text(board.panelId)+'-'+text(element.kind||'element')+'-'+String(board.elements.length+1)+'-'+String(revision+1);board.elements=[...board.elements.slice(0,elementIndex+1),copy,...board.elements.slice(elementIndex+1)];changed();draw();return}if((kind==='element-up'||kind==='element-down')&&element){const targetIndex=kind==='element-up'?elementIndex-1:elementIndex+1,target=board.elements.find((candidate,candidatePosition)=>candidatePosition===targetIndex);if(target)board.elements=board.elements.map((item,position)=>position===targetIndex?element:position===elementIndex?target:item);changed();draw();return}\n if(kind==='board-id')board.panelId=event.value;if(kind==='board-scene')board.sceneId=event.value||null;if(kind==='board-background')board.background=event.value;if(kind==='board-start')board.startMs=Math.max(0,Number(event.value)||0);if(kind==='board-duration'){board.durationMs=Math.max(1,Number(event.value)||1);renumber()}if(kind==='board-transition')board.transitionIn=event.value;if(kind==='board-transition-ms')board.transitionMs=Math.max(0,Number(event.value)||0);if(kind==='board-arrange')board.autoArrange=event.checked;\n if(element){if(kind==='element-id')element.id=event.value;if(kind==='element-kind')element.kind=event.value;if(kind==='element-text')element.text=event.value;if(kind==='element-descriptor')element.descriptor=event.value;if(kind==='element-asset')element.assetId=event.value||null;if(kind==='element-version')element.versionId=event.value||null;if(kind==='element-src')element.src=event.value||null;if(kind==='element-appear')element.appearMs=Math.max(0,Number(event.value)||0);if(kind==='element-leave')element.leaveMs=event.value===''?null:Math.max(0,Number(event.value)||0);if(kind==='element-size')element.size=Math.max(.01,Number(event.value)||.01);if(kind==='element-x'){if(!element.pos||Array.isArray(element.pos))element.pos={x:0,y:0};element.pos.x=Number(event.value)||0}if(kind==='element-y'){if(!element.pos||Array.isArray(element.pos))element.pos={x:0,y:0};element.pos.y=Number(event.value)||0}if(kind==='element-pinned')element.pinned=event.checked;if(kind==='element-entrance'){element.entrance=event.value;if(!element.anim||Array.isArray(element.anim))element.anim={};element.anim.type=event.value}if(kind==='element-sfx')element.sfx=event.value;if(kind==='element-font')element.font=event.value;if(kind==='element-color')element.color=event.value;if(kind==='element-weight')element.weight=Number(event.value)||400;if(kind==='element-per-word'){if(!element.anim||Array.isArray(element.anim))element.anim={};element.anim.perWord=event.checked}if(kind==='element-speed'){if(!element.anim||Array.isArray(element.anim))element.anim={};element.anim.speedMs=Math.max(1,Number(event.value)||1)}if(kind==='element-source-in')element.sourceInMs=Math.max(0,Number(event.value)||0);if(kind==='element-source-out')element.sourceOutMs=Math.max(0,Number(event.value)||0);if(kind==='element-muted')element.muted=event.checked;if(kind==='element-loop')element.loop=event.checked}changed();if(kind==='element-kind')draw()});\ndocument.getElementById('add').addEventListener('click',()=>{if(busy)return;ensure();const start=draft.strip.filter(board=>board.type==='canvas').reduce((total,board)=>total+Math.max(0,Number(board.durationMs)||0),0),id='portable-board-'+String(draft.strip.length+1)+'-'+String(revision+1);draft.strip=[...draft.strip,{panelId:id,type:'canvas',sceneId:null,background:'#ffffff',autoArrange:true,startMs:start,durationMs:5000,transitionIn:draft.strip.length?'swirl':'cut',elements:[]}];changed();draw()});\ndocument.getElementById('applyRaw').addEventListener('click',()=>{if(busy)return;try{const value=JSON.parse(raw.value);if(!value||Array.isArray(value)||value.schemaVersion!==1||!Array.isArray(value.strip))throw Error('Whiteboard JSON must be a version 1 object with a strip.');draft=clone(value);changed();draw();status.textContent='Complete Whiteboard data applied to this workspace.'}catch(error){status.textContent=error.message}});\ndocument.getElementById('reset').addEventListener('click',()=>{if(!busy){draft=clone(source);revision+=1;draw();status.textContent='Changes reset to the current project Whiteboard.'}});\nsave.addEventListener('click',async()=>{if(busy)return;const started=revision;busy=true;draw();status.textContent='Checking the complete Whiteboard…';try{const result=await StillMade.run({board:source,updatedBoard:draft,commit:true});draft=clone(result.board);if(started===revision)status.textContent='Whiteboard changes are ready below. Review the before and after, then choose Apply.'}catch(error){status.textContent=error.message||'The Whiteboard could not be prepared.'}finally{busy=false;draw()}});\nStillMade.onInput(values=>{source=clone(values.board||{schemaVersion:1,strip:[]});draft=clone(source);revision+=1;draw();status.textContent='Edit boards and elements. Existing text, media, motion, and custom fields are retained.'});\n\nconst mediaAction=async(action,extra={})=>{if(busy)return;busy=true;draw();try{await StillMade.run({board:source,updatedBoard:draft,commit:false,action,...extra});status.textContent='The approved action is ready below.'}catch(error){status.textContent=error.message||'The media action could not be prepared.'}finally{busy=false;draw()}};document.getElementById('generatePresenter').addEventListener('click',()=>mediaAction('presenter',{presenterPrompt:document.getElementById('presenterPrompt').value}));document.getElementById('qualityReview').addEventListener('click',()=>mediaAction('quality'));document.getElementById('renderFinal').addEventListener('click',()=>mediaAction('render'));\n"},"tests":[{"name":"Retain the complete current Whiteboard","input":{"board":{"schemaVersion":1,"meta":{"aspect":"16:9"},"strip":[{"panelId":"board-1","type":"canvas","startMs":0,"durationMs":5000,"background":"#ffffff","elements":[{"id":"image-1","kind":"image","src":"https://media.example/retained.png","assetId":"asset-1","versionId":"version-2","custom":{"keep":true}}]}]},"updatedBoard":{"schemaVersion":1,"strip":[]},"commit":false},"expected":{"board":{"schemaVersion":1,"meta":{"aspect":"16:9"},"strip":[{"panelId":"board-1","type":"canvas","startMs":0,"durationMs":5000,"background":"#ffffff","elements":[{"id":"image-1","kind":"image","src":"https://media.example/retained.png","assetId":"asset-1","versionId":"version-2","custom":{"keep":true}}]}]},"edit":{"schemaVersion":1,"title":"Update Whiteboard (1 boards)","field":"board","before":{"schemaVersion":1,"meta":{"aspect":"16:9"},"strip":[{"panelId":"board-1","type":"canvas","startMs":0,"durationMs":5000,"background":"#ffffff","elements":[{"id":"image-1","kind":"image","src":"https://media.example/retained.png","assetId":"asset-1","versionId":"version-2","custom":{"keep":true}}]}]},"after":{"schemaVersion":1,"meta":{"aspect":"16:9"},"strip":[{"panelId":"board-1","type":"canvas","startMs":0,"durationMs":5000,"background":"#ffffff","elements":[{"id":"image-1","kind":"image","src":"https://media.example/retained.png","assetId":"asset-1","versionId":"version-2","custom":{"keep":true}}]}]}}}},{"name":"Prepare one exact complete Whiteboard update","input":{"board":{"schemaVersion":1,"meta":{"aspect":"16:9"},"strip":[{"panelId":"board-1","type":"canvas","startMs":0,"durationMs":5000,"transitionIn":"cut","elements":[{"id":"title-1","kind":"text","text":"Original","appearMs":0}]}]},"updatedBoard":{"schemaVersion":1,"meta":{"aspect":"16:9"},"strip":[{"panelId":"board-1","type":"canvas","startMs":0,"durationMs":6000,"transitionIn":"fade","elements":[{"id":"title-1","kind":"text","text":"Updated","appearMs":250}]}]},"commit":true},"expected":{"board":{"schemaVersion":1,"meta":{"aspect":"16:9"},"strip":[{"panelId":"board-1","type":"canvas","startMs":0,"durationMs":6000,"transitionIn":"fade","elements":[{"id":"title-1","kind":"text","text":"Updated","appearMs":250}]}]},"edit":{"schemaVersion":1,"title":"Update Whiteboard (1 boards)","field":"board","before":{"schemaVersion":1,"meta":{"aspect":"16:9"},"strip":[{"panelId":"board-1","type":"canvas","startMs":0,"durationMs":5000,"transitionIn":"cut","elements":[{"id":"title-1","kind":"text","text":"Original","appearMs":0}]}]},"after":{"schemaVersion":1,"meta":{"aspect":"16:9"},"strip":[{"panelId":"board-1","type":"canvas","startMs":0,"durationMs":6000,"transitionIn":"fade","elements":[{"id":"title-1","kind":"text","text":"Updated","appearMs":250}]}]}}}}],"portable":{"format":"stillmade-block/1","dependencies":[],"sources":[{"kind":"original","license":"MIT","paths":["LICENSE","src/run.js","src/view.json"],"evidence":["upstream/0/LICENSE","upstream/0/src/run.js","upstream/0/src/view.json"],"changes":"Original implementation declared by the packager; no reused repository or dependency code."}],"files":{"upstream/0/LICENSE":"MIT License\n\nCopyright (c) 2026 StillMade contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","upstream/0/src/run.js":"const copy = value => JSON.parse(JSON.stringify(value));\nconst source = copy(input.board || {schemaVersion: 1, strip: []});\nconst candidate = input.commit ? copy(input.updatedBoard) : copy(source);\nif (!candidate || Array.isArray(candidate) || candidate.schemaVersion !== 1 || !Array.isArray(candidate.strip)) {\n  throw new Error('Keep the complete version 1 Whiteboard document and its strip.');\n}\nif (candidate.strip.length > 2000 || !candidate.strip.every(board => board && typeof board === 'object' && !Array.isArray(board))) {\n  throw new Error('Whiteboard supports up to 2,000 valid board rows.');\n}\nconst canvasBoards = candidate.strip.filter(board => board.type === 'canvas');\nif (!canvasBoards.every(board => !board.elements || Array.isArray(board.elements) && board.elements.length <= 5000 && board.elements.every(element => element && typeof element === 'object' && !Array.isArray(element)))) {\n  throw new Error('Each board needs a valid element list with up to 5,000 elements.');\n}\nconst itemEnd=item=>Math.max(0,Number(item.start)||0)+Math.max(.001,Number(item.duration)||Number(item.durationMs)/1000||.001);\nconst outputs={board:candidate,edit:{schemaVersion:1,title:`Update Whiteboard (${candidate.strip.length} boards)`,field:'board',before:source,after:candidate}};const images=candidate.strip.flatMap(board=>board.elements||[]).filter(item=>item&&item.kind==='image'&&item.assetId&&item.versionId&&item.url).slice(0,16);\nif(input.action==='presenter')outputs.presenterAction={schemaVersion:1,kind:'stillmade.media-action',operation:'presenter.generate',request:{board:candidate,prompt:input.presenterPrompt,voiceover:candidate.audio||{schemaVersion:1,words:[]}},resultPort:'actionEdit'};\nif(input.action==='quality'&&images.length)outputs.qualityAction={schemaVersion:1,kind:'stillmade.media-action',operation:'quality.analyze',request:{assets:images.map(({assetId,versionId,url})=>({kind:'image',assetId,versionId,url}))},resultPort:'qualityReport'};\nif(input.action==='render'){const clips=candidate.strip.map((board,index)=>({id:board.panelId||'board-'+index,start:(Number(board.startMs)||0)/1000,duration:Math.max(.001,(Number(board.durationMs)||1000)/1000),canvas:board})),targetDuration=Math.max(.001,...clips.map(itemEnd));outputs.renderAction={schemaVersion:1,kind:'stillmade.media-action',operation:'final.render',request:{schemaVersion:1,kind:'video',timeline:{schemaVersion:1,activeTimelineId:'whiteboard',clips,audio:[],overlays:[],targetDuration},settings:{fps:30,resolution:1080,quality:'high'},source:{id:'sm.board-editor',version:'2.2.0'}},resultPort:null};}\nreturn outputs;\n","upstream/0/src/view.json":"{\n  \"html\": \"<main class=\\\"board-workspace\\\">\\n  <header><div><small>Production workspace</small><h1>Whiteboard</h1><p id=\\\"summary\\\">Loading the Whiteboard…</p></div><div class=\\\"actions\\\"><button id=\\\"reset\\\" type=\\\"button\\\" class=\\\"secondary\\\">Reset changes</button><button id=\\\"add\\\" type=\\\"button\\\" class=\\\"secondary\\\">Add board</button><button id=\\\"save\\\" type=\\\"button\\\">Review changes</button></div></header>\\n  <output id=\\\"status\\\" role=\\\"status\\\" aria-live=\\\"polite\\\"></output>\\n  <section class=\\\"settings\\\"><h2>Workspace settings</h2><label>Aspect ratio<input id=\\\"aspect\\\" type=\\\"text\\\" placeholder=\\\"16:9\\\"></label></section>\\n  <section id=\\\"boards\\\" class=\\\"boards\\\" aria-label=\\\"Whiteboard boards\\\"></section>\\n  <details><summary>Complete Whiteboard data</summary><p>Every field and placed media reference is retained. Use this editor for custom fields not shown on the board cards.</p><textarea id=\\\"raw\\\" class=\\\"raw\\\" spellcheck=\\\"false\\\"></textarea><button id=\\\"applyRaw\\\" type=\\\"button\\\" class=\\\"secondary\\\">Apply JSON to workspace</button></details>\\n<section><h2>Presenter and delivery</h2><label>Presenter direction<input id=\\\"presenterPrompt\\\" type=\\\"text\\\" value=\\\"Friendly presenter explaining the narration\\\"></label><div class=\\\"actions\\\"><button id=\\\"generatePresenter\\\" type=\\\"button\\\" class=\\\"secondary\\\">Generate presenter poses</button><button id=\\\"qualityReview\\\" type=\\\"button\\\" class=\\\"secondary\\\">Review image quality</button><button id=\\\"renderFinal\\\" type=\\\"button\\\">Render final video</button></div></section></main>\\n\",\n  \"css\": \".board-workspace{max-width:1120px;margin:0 auto;padding:8px;color:var(--fg)}header,.actions,.card-head,.row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}header{justify-content:space-between;margin-bottom:14px}header h1{margin:2px 0}header p,header small,details p,output{color:var(--fg-muted)}\\n.settings,.card,details{margin:12px 0;padding:16px;border:1px solid var(--border-tok);border-radius:var(--r-3);background:var(--bg-elev-1)}.settings h2,.card h2{margin:0;font-size:18px}.boards{display:grid;gap:12px}.card-head{justify-content:space-between}.grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}.elements{display:grid;gap:8px;margin-top:10px}.element{padding:10px;border:1px solid var(--border-tok);border-radius:var(--r-2);background:var(--bg)}.retained-row{border-style:dashed}label{display:grid;gap:5px;margin:8px 0;font-size:13px;font-weight:600}input,textarea,select{width:100%;background:var(--bg)}button,input,textarea,select,summary{min-height:44px}button{cursor:pointer}button:disabled{cursor:not-allowed;opacity:.55}textarea{min-height:76px;resize:vertical}.wide{grid-column:1/-1}.secondary{background:transparent;color:var(--fg)}.danger{background:transparent;color:var(--signal)}.raw{min-height:360px;font-family:var(--font-mono);font-size:12px}output{display:block;min-height:24px;margin:8px 2px}\\n@media(max-width:820px){.grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:560px){.board-workspace{padding:0}.grid{grid-template-columns:1fr}.actions{width:100%}.actions button{flex:1}.settings,.card,details{padding:12px}}\\n\",\n  \"javascript\": \"const status=document.getElementById('status'),summary=document.getElementById('summary'),raw=document.getElementById('raw'),save=document.getElementById('save'),aspect=document.getElementById('aspect');\\nlet source={schemaVersion:1,strip:[]},draft={schemaVersion:1,strip:[]},busy=false,revision=0;\\nconst clone=value=>JSON.parse(JSON.stringify(value));\\nconst text=value=>value===undefined||value===null?'':String(value);\\nconst field=(label,action,value,type='text',wide=false)=>({tag:'label',className:wide?'wide':'',children:[{tag:'span',text:label},type==='textarea'?{tag:'textarea',action,value:text(value),disabled:busy,label}:{tag:'input',type,action,value:Number.isFinite(value)?value:text(value),disabled:busy,label}]});\\nconst choice=(label,action,value,options)=>({tag:'label',children:[{tag:'span',text:label},{tag:'select',action,value:text(value),disabled:busy,label,children:options.map(option=>({tag:'option',text:option,value:option}))}]});\\nconst toggle=(label,action,value)=>({tag:'label',children:[{tag:'span',text:label},{tag:'input',type:'checkbox',action,checked:!!value,disabled:busy,label}]});\\nconst optionList=(value,options)=>options.includes(text(value))||!text(value)?options:[...options,text(value)];\\nconst moveButton=(action,label,disabled)=>({tag:'button',action,text:label,className:'secondary',disabled:busy||disabled});\\nconst elementCard=(element,boardIndex,elementIndex)=>{const key=String(boardIndex)+':'+String(elementIndex),kind=text(element.kind)||'text',isText=kind==='text',isMedia=kind==='image'||kind==='video',owner=draft.strip.find((item,position)=>position===boardIndex);return {tag:'article',className:'element',children:[{tag:'div',className:'card-head',children:[{tag:'strong',text:kind+' · '+(text(element.text)||text(element.descriptor)||text(element.query)||text(element.id)||String(elementIndex+1))},{tag:'div',className:'row',children:[moveButton('element-up:'+key,'Up',elementIndex===0),moveButton('element-down:'+key,'Down',elementIndex===(owner.elements||[]).length-1),{tag:'button',action:'duplicate-element:'+key,text:'Duplicate',className:'secondary',disabled:busy},{tag:'button',action:'remove-element:'+key,text:'Remove',className:'danger',disabled:busy}]}]},{tag:'div',className:'grid',children:[field('Element ID','element-id:'+key,element.id),choice('Kind','element-kind:'+key,kind,optionList(kind,['text','image','video'])),field('Appears (ms)','element-appear:'+key,element.appearMs,'number'),field('Leaves (ms)','element-leave:'+key,element.leaveMs,'number'),field('Size','element-size:'+key,element.size,'number'),field('X position','element-x:'+key,element.pos&&element.pos.x,'number'),field('Y position','element-y:'+key,element.pos&&element.pos.y,'number'),toggle('Pinned position','element-pinned:'+key,element.pinned),choice('Entrance','element-entrance:'+key,element.entrance||element.anim&&element.anim.type||'pop',optionList(element.entrance||element.anim&&element.anim.type,['pop','fade','draw','slide','none'])),field('Sound effect','element-sfx:'+key,element.sfx),...(isText?[field('Text','element-text:'+key,element.text,'textarea',true),field('Font','element-font:'+key,element.font),field('Color','element-color:'+key,element.color),field('Weight','element-weight:'+key,element.weight,'number'),toggle('Reveal per word','element-per-word:'+key,element.anim&&element.anim.perWord),field('Animation speed (ms)','element-speed:'+key,element.anim&&element.anim.speedMs,'number')]:[]),...(isMedia?[field('Description','element-descriptor:'+key,element.descriptor,'textarea',true),field('Asset ID','element-asset:'+key,element.assetId),field('Version ID','element-version:'+key,element.versionId),field('Media source','element-src:'+key,element.src,'textarea',true)]:[]),...(kind==='video'?[field('Source in (ms)','element-source-in:'+key,element.sourceInMs,'number'),field('Source out (ms)','element-source-out:'+key,element.sourceOutMs,'number'),toggle('Muted','element-muted:'+key,element.muted),toggle('Loop','element-loop:'+key,element.loop)]:[])]}]}};\\nconst canvasPositions=()=>draft.strip.map((board,index)=>board.type==='canvas'?index:-1).filter(index=>index>=0);\\nconst boardCard=(board,index)=>{if(board.type!=='canvas')return {tag:'article',className:'card retained-row',children:[{tag:'h2',text:'Retained '+(text(board.type)||'workspace')+' row'},{tag:'p',text:'This non-board row stays in its exact strip position and is available in Complete Whiteboard data.'}]};const positions=canvasPositions(),position=positions.indexOf(index);return {tag:'article',className:'card',children:[{tag:'div',className:'card-head',children:[{tag:'h2',text:'Board '+String(position+1)+' · '+(text(board.panelId)||'untitled')},{tag:'div',className:'row',children:[moveButton('move-up:'+String(index),'Up',position===0),moveButton('move-down:'+String(index),'Down',position===positions.length-1),{tag:'button',action:'add-text:'+String(index),text:'Add text',className:'secondary',disabled:busy},{tag:'button',action:'remove-board:'+String(index),text:'Remove board',className:'danger',disabled:busy||positions.length<=1}]}]},{tag:'div',className:'grid',children:[field('Board ID','board-id:'+String(index),board.panelId),field('Scene ID','board-scene:'+String(index),board.sceneId),field('Background','board-background:'+String(index),board.background),field('Starts (ms)','board-start:'+String(index),board.startMs,'number'),field('Length (ms)','board-duration:'+String(index),board.durationMs,'number'),choice('Transition','board-transition:'+String(index),board.transitionIn||'cut',optionList(board.transitionIn,['cut','fade','swirl','wipe','slide'])),field('Transition length (ms)','board-transition-ms:'+String(index),board.transitionMs,'number'),toggle('Automatic arrangement','board-arrange:'+String(index),board.autoArrange!==false)]},{tag:'div',className:'elements',children:Array.isArray(board.elements)&&board.elements.length?board.elements.map((element,elementIndex)=>elementCard(element,index,elementIndex)):[{tag:'p',text:'No elements yet. Add text, request a presenter, or use Complete Whiteboard data for retained media.'}]}]}};\\nconst ensure=()=>{if(!draft||Array.isArray(draft)||draft.schemaVersion!==1)draft={schemaVersion:1,strip:[]};if(!Array.isArray(draft.strip))draft.strip=[];if(!draft.meta||Array.isArray(draft.meta))draft.meta={}};\\nconst renumber=()=>{let start=0;draft.strip=draft.strip.map(board=>{if(board.type!=='canvas')return board;const next={...board,startMs:start};start+=Math.max(0,Number(board.durationMs)||0);return next})};\\nconst draw=()=>{ensure();StillMade.render('boards',draft.strip.length?draft.strip.map(boardCard):[{tag:'p',text:'No boards yet. Add one to begin.'}]);raw.value=JSON.stringify(draft,null,2);aspect.value=text(draft.meta.aspect);const count=draft.strip.filter(board=>board.type==='canvas').length;summary.textContent=String(count)+' board'+(count===1?'':'s')+' in this complete Whiteboard';save.disabled=busy;document.getElementById('reset').disabled=busy;document.getElementById('add').disabled=busy};\\nconst changed=()=>{revision+=1;raw.value=JSON.stringify(draft,null,2);status.textContent='Changes are local until you choose Review changes.'};\\naspect.addEventListener('input',()=>{ensure();draft.meta.aspect=aspect.value;changed()});\\nStillMade.onAction(event=>{if(busy)return;ensure();const parts=event.action.split(':'),kind=parts[0],boardIndex=Number(parts[1]),elementIndex=Number(parts[2]),board=draft.strip.find((item,position)=>position===boardIndex),element=board&&Array.isArray(board.elements)?board.elements.find((item,position)=>position===elementIndex):null;\\n const positions=canvasPositions(),canvasPosition=positions.indexOf(boardIndex),wantedPosition=kind==='move-up'?canvasPosition-1:kind==='move-down'?canvasPosition+1:-1,moveTarget=positions.find((value,position)=>position===wantedPosition);\\n if(kind==='remove-board'&&positions.length>1){draft.strip=draft.strip.filter((item,position)=>position!==boardIndex);renumber();changed();draw();return}if(moveTarget!==undefined&&board){const target=draft.strip.find((item,position)=>position===moveTarget);draft.strip=draft.strip.map((item,position)=>position===moveTarget?board:position===boardIndex?target:item);renumber();changed();draw();return}if(!board||board.type!=='canvas')return;\\n if(kind==='add-text'){if(!Array.isArray(board.elements))board.elements=[];board.elements=[...board.elements,{id:text(board.panelId)+'-text-'+String(board.elements.length+1)+'-'+String(revision+1),kind:'text',text:'New text',appearMs:0,leaveMs:null,pos:null,size:0.08,font:'Hanken Grotesk',color:'#111111',weight:800,anim:{type:'pop',perWord:false,speedMs:180},pinned:false}];changed();draw();return}if(kind==='remove-element'&&element){board.elements=board.elements.filter((item,position)=>position!==elementIndex);changed();draw();return}if(kind==='duplicate-element'&&element){const copy=clone(element);copy.id=text(board.panelId)+'-'+text(element.kind||'element')+'-'+String(board.elements.length+1)+'-'+String(revision+1);board.elements=[...board.elements.slice(0,elementIndex+1),copy,...board.elements.slice(elementIndex+1)];changed();draw();return}if((kind==='element-up'||kind==='element-down')&&element){const targetIndex=kind==='element-up'?elementIndex-1:elementIndex+1,target=board.elements.find((candidate,candidatePosition)=>candidatePosition===targetIndex);if(target)board.elements=board.elements.map((item,position)=>position===targetIndex?element:position===elementIndex?target:item);changed();draw();return}\\n if(kind==='board-id')board.panelId=event.value;if(kind==='board-scene')board.sceneId=event.value||null;if(kind==='board-background')board.background=event.value;if(kind==='board-start')board.startMs=Math.max(0,Number(event.value)||0);if(kind==='board-duration'){board.durationMs=Math.max(1,Number(event.value)||1);renumber()}if(kind==='board-transition')board.transitionIn=event.value;if(kind==='board-transition-ms')board.transitionMs=Math.max(0,Number(event.value)||0);if(kind==='board-arrange')board.autoArrange=event.checked;\\n if(element){if(kind==='element-id')element.id=event.value;if(kind==='element-kind')element.kind=event.value;if(kind==='element-text')element.text=event.value;if(kind==='element-descriptor')element.descriptor=event.value;if(kind==='element-asset')element.assetId=event.value||null;if(kind==='element-version')element.versionId=event.value||null;if(kind==='element-src')element.src=event.value||null;if(kind==='element-appear')element.appearMs=Math.max(0,Number(event.value)||0);if(kind==='element-leave')element.leaveMs=event.value===''?null:Math.max(0,Number(event.value)||0);if(kind==='element-size')element.size=Math.max(.01,Number(event.value)||.01);if(kind==='element-x'){if(!element.pos||Array.isArray(element.pos))element.pos={x:0,y:0};element.pos.x=Number(event.value)||0}if(kind==='element-y'){if(!element.pos||Array.isArray(element.pos))element.pos={x:0,y:0};element.pos.y=Number(event.value)||0}if(kind==='element-pinned')element.pinned=event.checked;if(kind==='element-entrance'){element.entrance=event.value;if(!element.anim||Array.isArray(element.anim))element.anim={};element.anim.type=event.value}if(kind==='element-sfx')element.sfx=event.value;if(kind==='element-font')element.font=event.value;if(kind==='element-color')element.color=event.value;if(kind==='element-weight')element.weight=Number(event.value)||400;if(kind==='element-per-word'){if(!element.anim||Array.isArray(element.anim))element.anim={};element.anim.perWord=event.checked}if(kind==='element-speed'){if(!element.anim||Array.isArray(element.anim))element.anim={};element.anim.speedMs=Math.max(1,Number(event.value)||1)}if(kind==='element-source-in')element.sourceInMs=Math.max(0,Number(event.value)||0);if(kind==='element-source-out')element.sourceOutMs=Math.max(0,Number(event.value)||0);if(kind==='element-muted')element.muted=event.checked;if(kind==='element-loop')element.loop=event.checked}changed();if(kind==='element-kind')draw()});\\ndocument.getElementById('add').addEventListener('click',()=>{if(busy)return;ensure();const start=draft.strip.filter(board=>board.type==='canvas').reduce((total,board)=>total+Math.max(0,Number(board.durationMs)||0),0),id='portable-board-'+String(draft.strip.length+1)+'-'+String(revision+1);draft.strip=[...draft.strip,{panelId:id,type:'canvas',sceneId:null,background:'#ffffff',autoArrange:true,startMs:start,durationMs:5000,transitionIn:draft.strip.length?'swirl':'cut',elements:[]}];changed();draw()});\\ndocument.getElementById('applyRaw').addEventListener('click',()=>{if(busy)return;try{const value=JSON.parse(raw.value);if(!value||Array.isArray(value)||value.schemaVersion!==1||!Array.isArray(value.strip))throw Error('Whiteboard JSON must be a version 1 object with a strip.');draft=clone(value);changed();draw();status.textContent='Complete Whiteboard data applied to this workspace.'}catch(error){status.textContent=error.message}});\\ndocument.getElementById('reset').addEventListener('click',()=>{if(!busy){draft=clone(source);revision+=1;draw();status.textContent='Changes reset to the current project Whiteboard.'}});\\nsave.addEventListener('click',async()=>{if(busy)return;const started=revision;busy=true;draw();status.textContent='Checking the complete Whiteboard…';try{const result=await StillMade.run({board:source,updatedBoard:draft,commit:true});draft=clone(result.board);if(started===revision)status.textContent='Whiteboard changes are ready below. Review the before and after, then choose Apply.'}catch(error){status.textContent=error.message||'The Whiteboard could not be prepared.'}finally{busy=false;draw()}});\\nStillMade.onInput(values=>{source=clone(values.board||{schemaVersion:1,strip:[]});draft=clone(source);revision+=1;draw();status.textContent='Edit boards and elements. Existing text, media, motion, and custom fields are retained.'});\\n\\nconst mediaAction=async(action,extra={})=>{if(busy)return;busy=true;draw();try{await StillMade.run({board:source,updatedBoard:draft,commit:false,action,...extra});status.textContent='The approved action is ready below.'}catch(error){status.textContent=error.message||'The media action could not be prepared.'}finally{busy=false;draw()}};document.getElementById('generatePresenter').addEventListener('click',()=>mediaAction('presenter',{presenterPrompt:document.getElementById('presenterPrompt').value}));document.getElementById('qualityReview').addEventListener('click',()=>mediaAction('quality'));document.getElementById('renderFinal').addEventListener('click',()=>mediaAction('render'));\\n\"\n}"},"lineage":{"parents":[],"changes":"Packaged original Block source for StillMade."}}}],"license":"MIT"}