Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Patternflow sandbox</title>
<style>
html, body { margin: 0; height: 100%; background: transparent; overflow: hidden; }
canvas {
display: block;
width: 100%;
height: 100%;
image-rendering: pixelated;
object-fit: contain;
}
</style>
</head>
<body>
<canvas id="screen" width="128" height="64"></canvas>
<script>
// Built by scripts/build-sandbox.ts from src/sandbox/sandbox.ts and the lab runtime in
// src/lib/pattern/. Do not edit: run `npm run build:sandbox` in web/.
"use strict";(()=>{function clampByte(value){return Number.isFinite(value)?Math.max(0,Math.min(255,Math.round(value))):0}var RAMP_MODES=["linear","smooth","step","hsvShort","hsvLong","oklab","oklchShort","oklchLong"];function rgbToHsv(r,g,b){let rf=r/255,gf=g/255,bf=b/255,max=Math.max(rf,gf,bf),min=Math.min(rf,gf,bf),delta=max-min,h=0;delta>0&&(max===rf?h=(gf-bf)/delta%6:max===gf?h=(bf-rf)/delta+2:h=(rf-gf)/delta+4,h/=6,h<0&&(h+=1));let s=max===0?0:delta/max;return[h,s,max]}function hsvToRgbTuple(h,s,v){h=h-Math.floor(h);let i=Math.floor(h*6),f=h*6-i,p=v*(1-s),q=v*(1-f*s),t=v*(1-(1-f)*s),r,g,b;switch(i%6){case 0:r=v,g=t,b=p;break;case 1:r=q,g=v,b=p;break;case 2:r=p,g=v,b=t;break;case 3:r=p,g=q,b=v;break;case 4:r=t,g=p,b=v;break;default:r=v,g=p,b=q;break}return[Math.round(r*255),Math.round(g*255),Math.round(b*255)]}function srgbChannelToLinear(c){return c/=255,c<=.04045?c/12.92:Math.pow((c+.055)/1.055,2.4)}function linearChannelToSrgb(c){let v=c<=.0031308?c*12.92:1.055*Math.pow(c,.4166666666666667)-.055;return Math.max(0,Math.min(1,v))*255}function srgbToOklab(r,g,b){let lr=srgbChannelToLinear(r),lg=srgbChannelToLinear(g),lb=srgbChannelToLinear(b),l=Math.cbrt(.4122214708*lr+.5363325363*lg+.0514459929*lb),m=Math.cbrt(.2119034982*lr+.6806995451*lg+.1073969566*lb),s=Math.cbrt(.0883024619*lr+.2817188376*lg+.6299787005*lb);return[.2104542553*l+.793617785*m-.0040720468*s,1.9779984951*l-2.428592205*m+.4505937099*s,.0259040371*l+.7827717662*m-.808675766*s]}function oklabToLinearRgb(L,A,B){let l_=L+.3963377774*A+.2158037573*B,m_=L-.1055613458*A-.0638541728*B,s_=L-.0894841775*A-1.291485548*B,l=l_*l_*l_,m=m_*m_*m_,s=s_*s_*s_;return[4.0767416621*l-3.3077115913*m+.2309699292*s,-1.2684380046*l+2.6097574011*m-.3413193965*s,-.0041960863*l-.7034186147*m+1.707614701*s]}var GAMUT_EPS=1e-4;function inLinearGamut(rgb){return rgb[0]>=-GAMUT_EPS&&rgb[0]<=1+GAMUT_EPS&&rgb[1]>=-GAMUT_EPS&&rgb[1]<=1+GAMUT_EPS&&rgb[2]>=-GAMUT_EPS&&rgb[2]<=1+GAMUT_EPS}function oklabToSrgb(L,A,B){let rgb=oklabToLinearRgb(L,A,B);if(!inLinearGamut(rgb)){let lo=0,hi=1;for(let i=0;i<16;i++){let mid=(lo+hi)/2;inLinearGamut(oklabToLinearRgb(L,A*mid,B*mid))?lo=mid:hi=mid}rgb=oklabToLinearRgb(L,A*lo,B*lo)}return[linearChannelToSrgb(rgb[0]),linearChannelToSrgb(rgb[1]),linearChannelToSrgb(rgb[2])]}function mixRampColors(a,b,t,mode){if(mode==="smooth"&&(t=t*t*(3-2*t)),mode==="oklab"||mode==="oklchShort"||mode==="oklchLong"){let[La,Aa,Ba]=srgbToOklab(a[0],a[1],a[2]),[Lb,Ab,Bb]=srgbToOklab(b[0],b[1],b[2]),L=La+(Lb-La)*t;if(mode==="oklab")return oklabToSrgb(L,Aa+(Ab-Aa)*t,Ba+(Bb-Ba)*t);let CHROMA_EPS=1e-4,TWO_PI=Math.PI*2,Ca=Math.sqrt(Aa*Aa+Ba*Ba),Cb=Math.sqrt(Ab*Ab+Bb*Bb),hueA=Math.atan2(Ba,Aa)/TWO_PI;hueA<0&&(hueA+=1);let hueB=Math.atan2(Bb,Ab)/TWO_PI;hueB<0&&(hueB+=1);let h1=Ca<CHROMA_EPS?Cb<CHROMA_EPS?0:hueB:hueA,dh=(Cb<CHROMA_EPS?h1:hueB)-h1;mode==="oklchShort"?(dh>.5&&(dh-=1),dh<-.5&&(dh+=1)):dh>0&&dh<.5?dh-=1:dh<=0&&dh>-.5&&(dh+=1);let C=Ca+(Cb-Ca)*t,H=(h1+dh*t)*TWO_PI;return oklabToSrgb(L,Math.cos(H)*C,Math.sin(H)*C)}if(mode==="hsvShort"||mode==="hsvLong"){let ha=rgbToHsv(a[0],a[1],a[2]),hb=rgbToHsv(b[0],b[1],b[2]),h1=ha[1]===0?hb[0]:ha[0],dh=(hb[1]===0?h1:hb[0])-h1;return mode==="hsvShort"?(dh>.5&&(dh-=1),dh<-.5&&(dh+=1)):dh>0&&dh<.5?dh-=1:dh<=0&&dh>-.5&&(dh+=1),hsvToRgbTuple(h1+dh*t,ha[1]+(hb[1]-ha[1])*t,ha[2]+(hb[2]-ha[2])*t)}return[a[0]+(b[0]-a[0])*t,a[1]+(b[1]-a[1])*t,a[2]+(b[2]-a[2])*t]}function stopAlpha(stop){let a=stop.alpha;return a===void 0||!Number.isFinite(a)?1:Math.max(0,Math.min(1,a))}function mixStopAlpha(a,b,t,mode){mode==="smooth"&&(t=t*t*(3-2*t));let aa=stopAlpha(a);return aa+(stopAlpha(b)-aa)*t}function sampleSortedRampRGBA(stops,mode,wrap,t){if(stops.length===0)return[255,255,255,1];if(stops.length===1)return[...stops[0].color,stopAlpha(stops[0])];t=Math.max(0,Math.min(1,t));let first=stops[0],last=stops[stops.length-1];if(t<=first.position||t>=last.position){if(!wrap){let edge=t<=first.position?first:last;return[...edge.color,stopAlpha(edge)]}let span=1-last.position+first.position,local=span<=0?0:(t>=last.position?t-last.position:t+1-last.position)/span;return mode==="step"?[...last.color,stopAlpha(last)]:[...mixRampColors(last.color,first.color,local,mode),mixStopAlpha(last,first,local,mode)]}for(let i=0;i<stops.length-1;i++){let a=stops[i],b=stops[i+1];if(t>b.position)continue;if(mode==="step")return[...a.color,stopAlpha(a)];let span=b.position-a.position,local=span<=0?0:(t-a.position)/span;return[...mixRampColors(a.color,b.color,local,mode),mixStopAlpha(a,b,local,mode)]}return[...last.color,stopAlpha(last)]}function buildRampLUT(ramp){let sorted=[...ramp.stops].sort((a,b)=>a.position-b.position),lut=new Uint8Array(256*3);for(let i=0;i<256;i++){let[r,g,b]=sampleSortedRampRGBA(sorted,ramp.mode,ramp.wrap,i/255);lut[i*3]=clampByte(r),lut[i*3+1]=clampByte(g),lut[i*3+2]=clampByte(b)}return lut}function buildRampLUTRGBA(ramp){let sorted=[...ramp.stops].sort((a,b)=>a.position-b.position),lut=new Uint8Array(256*4);for(let i=0;i<256;i++){let[r,g,b,a]=sampleSortedRampRGBA(sorted,ramp.mode,ramp.wrap,i/255);lut[i*4]=clampByte(r),lut[i*4+1]=clampByte(g),lut[i*4+2]=clampByte(b),lut[i*4+3]=clampByte(a*255)}return lut}function normalizePatternCode(code){return code.replace(/^\s*import\s+.*?;?\s*$/gm,"").replace(/\bexport\s+function\b/g,"function")}var WRAPPER_LINES_BEFORE_CODE=2,functionPreambleLines=null;function stackLocation(error){let stack=error instanceof Error?error.stack:null;if(!stack)return null;let match=stack.match(/(?:<anonymous>|Function|eval):(\d+):(\d+)/);return match?{line:Number(match[1]),column:Number(match[2])}:null}function getFunctionPreambleLines(){if(functionPreambleLines!==null)return functionPreambleLines;functionPreambleLines=2;try{new Function("throw new Error('probe');")()}catch(error){let location=stackLocation(error);location&&(functionPreambleLines=location.line-1)}return functionPreambleLines}function describePatternError(error,code){let message=error instanceof Error?error.message:String(error);if(!code)return message;let location=stackLocation(error);if(!location)return message;let line=location.line-getFunctionPreambleLines()-WRAPPER_LINES_BEFORE_CODE,sourceLines=code.split(`
`);if(line<1||line>sourceLines.length)return message;let source=sourceLines[line-1].trim(),quoted=source.length>80?`${source.slice(0,79)}\u2026`:source;return`${message}
line ${line}: ${quoted}`}function compilePatternCode(code){let wrapper=`
"use strict";
${normalizePatternCode(code)}
return {
setup: typeof setup === "function" ? setup : undefined,
update: typeof update === "function" ? update : undefined,
draw: typeof draw === "function" ? draw : undefined
};
`,moduleObject=new Function(wrapper)();if(typeof moduleObject.draw!="function")throw new Error("Pattern must export draw(display, params, time).");return moduleObject}var PatternRuntime=class{constructor(width=128,height=64){this.ramp=null;this.recolor=!1;this.rampLUT=null;this.rampLUTA=null;this.module=null;this.params={};this.source=null;this.loadError=null;this.width=width,this.height=height,this.data=new Uint8ClampedArray(width*height*4),this.display={width,height,setPixel:(x,y,r,g,b)=>{var _a,_b,_c;let xi=Math.floor(x),yi=Math.floor(y);if(xi<0||xi>=this.width||yi<0||yi>=this.height)return;let cr,cg,cb;Array.isArray(r)?(cr=(_a=r[0])!=null?_a:0,cg=(_b=r[1])!=null?_b:0,cb=(_c=r[2])!=null?_c:0):(cr=r,cg=g!=null?g:0,cb=b!=null?b:0);let index=(yi*this.width+xi)*4;this.data[index]=clampByte(cr),this.data[index+1]=clampByte(cg),this.data[index+2]=clampByte(cb),this.data[index+3]=255},setValue:(x,y,v)=>{let xi=Math.floor(x),yi=Math.floor(y);if(xi<0||xi>=this.width||yi<0||yi>=this.height)return;let value=Number.isFinite(v)?Math.max(0,Math.min(1,v)):0,index=(yi*this.width+xi)*4,lut=this.rampLUTA;if(lut){let li=Math.round(value*255)*4;this.data[index]=lut[li],this.data[index+1]=lut[li+1],this.data[index+2]=lut[li+2],this.data[index+3]=lut[li+3]}else{let byte=Math.round(value*255);this.data[index]=byte,this.data[index+1]=byte,this.data[index+2]=byte,this.data[index+3]=255}}}}setRamp(ramp){this.ramp=ramp,this.rampLUT=ramp&&ramp.stops.length>0?buildRampLUT(ramp):null,this.rampLUTA=ramp&&ramp.stops.length>0?buildRampLUTRGBA(ramp):null}loadCode(code){var _a,_b;this.source=code;try{return this.module=compilePatternCode(code),this.params={},this.data.fill(0),(_b=(_a=this.module).setup)==null||_b.call(_a,this.params),this.loadError=null,{ok:!0}}catch(error){return this.module=null,this.loadError=describePatternError(error,code),{ok:!1,error:this.loadError}}}renderFrame(dt,time,input){var _a,_b,_c;if(!this.module)return{ok:!1,error:(_a=this.loadError)!=null?_a:"No pattern loaded."};try{if(this.params.knobValues=input.knobValues,this.params.knobNormalized=input.knobNormalized,this.params.knobDeltas=input.knobDeltas,this.params.knobRanges=input.knobRanges,this.params.btnPressed=input.btnPressed,this.params.btnHeld=input.btnHeld,(_c=(_b=this.module).update)==null||_c.call(_b,dt,input,this.params),this.data.fill(0),this.module.draw(this.display,this.params,time),this.recolor&&this.rampLUTA){let lut=this.rampLUTA,data=this.data;for(let index=0;index<data.length;index+=4){if(data[index+3]===0)continue;let li=Math.round(.2126*data[index]+.7152*data[index+1]+.0722*data[index+2])*4;data[index]=lut[li],data[index+1]=lut[li+1],data[index+2]=lut[li+2],data[index+3]=lut[li+3]}}return{ok:!0}}catch(error){return{ok:!1,error:describePatternError(error,this.source)}}}getParamsSnapshot(){return JSON.parse(JSON.stringify(this.params))}};function knobTargetToDelta(previous,next,wrap=!1,unitsPerTurn=1){let delta=next-previous;return wrap&&(delta>.5&&(delta-=1),delta<-.5&&(delta+=1)),delta*(24/unitsPerTurn)}function hexToRgb(hex){let value=parseInt(hex.slice(1),16);return[value>>16&255,value>>8&255,value&255]}var DEFAULT_MATRIX={width:128,height:64};var MATRIX_MIN=8,MATRIX_MAX=512,MATRIX_LINE_RE=/^[ \t]*\/\/[ \t]*@matrix[ \t]+(\d{1,4})[ \t]*[x×*][ \t]*(\d{1,4})[ \t]*$/m;function clampDimension(value){if(!Number.isFinite(value))return null;let rounded=Math.round(value);return rounded<MATRIX_MIN||rounded>MATRIX_MAX?null:rounded}var MATRIX_HEAVY_PIXELS=8192*4;function parseMatrixAnnotation(code){let match=code.match(MATRIX_LINE_RE);if(!match)return null;let width=clampDimension(Number(match[1])),height=clampDimension(Number(match[2]));return width===null||height===null?null:{width,height}}function matrixFromCode(code){var _a;return(_a=parseMatrixAnnotation(code))!=null?_a:DEFAULT_MATRIX}var MAX_ANNOTATION_STOPS=64,RAMP_LINE_RE=/^[ \t]*\/\/[ \t]*@ramp[ \t]+(.+)$/m,STOP_RE=/^(\d*\.?\d+):(#[0-9a-fA-F]{6})$/;function parseRampAnnotation(code){let match=code.match(RAMP_LINE_RE);if(!match)return null;let tokens=match[1].trim().split(/\s+/);if(tokens.length===0)return null;let mode=RAMP_MODES.includes(tokens[0])?tokens[0]:null;if(!mode)return null;let wrap=!1,recolor=!1,stops=[];for(let token of tokens.slice(1)){if(token==="wrap"){wrap=!0;continue}if(token==="recolor"){recolor=!0;continue}let stop=token.match(STOP_RE);if(!stop||stops.length>=MAX_ANNOTATION_STOPS)continue;let position=Number(stop[1]);Number.isFinite(position)&&stops.push({position:Math.max(0,Math.min(1,position)),color:stop[2].toLowerCase()})}return stops.length===0?null:{stops,mode,wrap,recolor}}var DEFAULT_RAMP={stops:[{position:0,color:[8,24,64]},{position:.55,color:[255,77,0]},{position:1,color:[255,232,154]}],mode:"linear",wrap:!1};function load(code,frame){let runtime=new PatternRuntime(frame.width,frame.height),annotation=parseRampAnnotation(code),ramp=annotation?{stops:annotation.stops.map(stop=>({position:stop.position,color:hexToRgb(stop.color)})),mode:annotation.mode,wrap:annotation.wrap}:DEFAULT_RAMP;runtime.setRamp(ramp),runtime.recolor=!!(annotation!=null&&annotation.recolor);let result=runtime.loadCode(code);return{loaded:{runtime},ok:result.ok,error:result.error}}function render(loaded,dt,time,input){return loaded.runtime.renderFrame(dt,time,input)}function makeInput(deltas,values,normalized,ranges){let flags=[!1,!1,!1,!1];return{knobDeltas:deltas!=null?deltas:[0,0,0,0],knobValues:values,knobNormalized:normalized,knobRanges:ranges,btnPressed:flags,btnHeld:flags.slice()}}function normalize(values,ranges){return values.map((v,i)=>{var _a;let range=(_a=ranges[i])!=null?_a:[0,1],span=Math.max(.001,range[1]-range[0]);return(v-range[0])/span})}var canvas=document.getElementById("screen"),context=canvas.getContext("2d"),imageData=context.createImageData(canvas.width,canvas.height);function resizeLive(frame){canvas.width===frame.width&&canvas.height===frame.height||(canvas.width=frame.width,canvas.height=frame.height,imageData=context.createImageData(frame.width,frame.height))}var MAX_STEP=.05,MAX_CATCHUP=.25,live={loaded:null,running:!0,simTime:0,lastNow:0,knobValues:[.5,.5,.5,.5],knobRanges:[[0,1],[0,1],[0,1],[0,1]],knobWrap:[!1,!1,!1,!1],knobUnitsPerTurn:[1,1,1,1],prevKnobs:null,failed:!1,painted:!1,frame:0,get runtime(){var _a,_b;return(_b=(_a=this.loaded)==null?void 0:_a.runtime)!=null?_b:null}};function paint(loaded){imageData.data.set(loaded.runtime.data),context.putImageData(imageData,0,0)}function post(message){parent.postMessage(message,"*")}function step(dt,withKnobDeltas){var _a;if(!live.loaded)return!1;live.simTime+=dt;let deltas=null;if(withKnobDeltas){let prev=(_a=live.prevKnobs)!=null?_a:live.knobValues;deltas=live.knobValues.map((v,i)=>knobTargetToDelta(prev[i],v,live.knobWrap[i],live.knobUnitsPerTurn[i]||1)),live.prevKnobs=live.knobValues.slice()}let input=makeInput(deltas,live.knobValues,normalize(live.knobValues,live.knobRanges),live.knobRanges),result=render(live.loaded,dt,live.simTime,input);return result.ok?!0:(live.failed=!0,post({type:"pf-status",ok:!1,error:result.error}),!1)}function wake(){live.frame!==0||live.failed||!live.loaded||(live.lastNow=performance.now(),live.frame=requestAnimationFrame(tick))}function tick(now){live.frame=0;let loaded=live.loaded;if(!loaded||live.failed)return;let elapsed=Math.min(Math.max(0,(now-live.lastNow)/1e3),MAX_CATCHUP);if(live.lastNow=now,!live.running){!live.painted&&step(0,!0)&&(paint(loaded),live.painted=!0);return}let remaining=elapsed,first=!0;do{let slice=remaining>MAX_STEP?MAX_STEP:remaining;if(!step(slice,first))return;remaining-=slice,first=!1}while(remaining>1e-5);paint(loaded),live.painted=!0,live.frame=requestAnimationFrame(tick)}function renderStill(request){var _a,_b,_c;let code=String((_a=request.code)!=null?_a:""),frame=matrixFromCode(code),{loaded,ok,error}=load(code,frame);if(!ok)return{ok:!1,error};let fps=request.fps||15,seconds=typeof request.seconds=="number"?request.seconds:.9,frames=Math.max(1,Math.floor(seconds*fps)),values=(_b=request.knobValues)!=null?_b:[.5,.5,.5,.5],ranges=(_c=request.knobRanges)!=null?_c:[[0,1],[0,1],[0,1],[0,1]],normalizedValues=normalize(values,ranges);for(let f=0;f<frames;f++){let result=render(loaded,1/fps,f/fps,makeInput(null,values,normalizedValues,ranges));if(!result.ok)return{ok:!1,error:result.error}}let off=document.createElement("canvas");off.width=frame.width,off.height=frame.height;let offContext=off.getContext("2d"),offImage=offContext.createImageData(frame.width,frame.height);return offImage.data.set(loaded.runtime.data),offContext.putImageData(offImage,0,0),{ok:!0,dataUrl:off.toDataURL("image/png")}}window.addEventListener("message",event=>{var _a;let msg=event.data;if(!(!msg||typeof msg.type!="string")){if(msg.type==="pf-load"){let code=String((_a=msg.code)!=null?_a:""),frame=matrixFromCode(code);resizeLive(frame),live.simTime=0,live.lastNow=performance.now(),live.failed=!1,live.prevKnobs=null,live.painted=!1,Array.isArray(msg.knobValues)&&(live.knobValues=msg.knobValues.slice(0,4)),Array.isArray(msg.knobRanges)&&(live.knobRanges=msg.knobRanges),Array.isArray(msg.knobWrap)&&(live.knobWrap=msg.knobWrap),Array.isArray(msg.knobUnitsPerTurn)&&(live.knobUnitsPerTurn=msg.knobUnitsPerTurn),typeof msg.running=="boolean"&&(live.running=msg.running);let{loaded,ok,error}=load(code,frame);live.loaded=loaded,ok?(paint(loaded),post({type:"pf-status",ok:!0}),wake()):(live.failed=!0,post({type:"pf-status",ok:!1,error}))}else if(msg.type==="pf-knobs")Array.isArray(msg.values)&&(live.knobValues=msg.values.slice(0,4)),Array.isArray(msg.ranges)&&(live.knobRanges=msg.ranges),live.painted=!1,wake();else if(msg.type==="pf-run")live.running=!!msg.running,wake();else if(msg.type==="pf-still"){let result;try{result=renderStill(msg)}catch(error){result={ok:!1,error:error instanceof Error?error.message:String(error)}}post({...result,type:"pf-still-result",id:msg.id})}}});globalThis.__pfLive=live;post({type:"pf-ready"});})();
</script>
</body>
</html>
07e931b46This file is pinned to the selected release. Download the original to open it in your design tool.