[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"guide-nav":3,"doc-\u002Fdocs\u002Fguide\u002Fvideo-rendering":153},[4,10,16,20,24,28,32,36,40,44,48,52,56,60,64,69,73,77,81,86,90,94,98,102,106,110,114,118,122,125,128,132,135,139,142,146,149],{"path":5,"title":6,"description":7,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide","Getting started","Shaders is a library of GPU effect components for Vue, React, Svelte, Solid, JavaScript and Framer, with a design editor for building with them.","code",null,{"path":11,"title":12,"description":13,"category":8,"exclude":14,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fquickstart","Quickstart","Install Shaders, render your first effect, and drive a prop from state.",[15],"js",{"path":17,"title":18,"description":19,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fcomposing-effects","Composing effects","Stack components as layers and nest them to control which layers an effect touches.",{"path":21,"title":22,"description":23,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fblending-masking","Blending and masking","Control how a layer mixes with the layers below it, and reveal one layer through another.",{"path":25,"title":26,"description":27,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fprops-reactivity","Props and reactivity","Bind props to your framework's state so effects update when it changes, and animate props with an animation library.",{"path":29,"title":30,"description":31,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fdynamic-props","Dynamic props","Drive any numeric or position prop from time, the cursor, or another layer, without writing animation code.",{"path":33,"title":34,"description":35,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Flayout-positioning","Layout and positioning","Size and place a shader in your page with CSS, as a background, a fill, or an inline block.",{"path":37,"title":38,"description":39,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Ftransforms","Transforms","Position, size, and rotate any layer inside the canvas with a bounding box, in % or px units.",{"path":41,"title":42,"description":43,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fshape-effects","Shape effects","Pick the 2D, 3D or custom SVG shape that Glass, Neon, Emboss and other shape effects draw inside.",{"path":45,"title":46,"description":47,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fcolor-space","Color space","Match colors to your design tool with the output color space, and keep bright layers from clipping with tone mapping.",{"path":49,"title":50,"description":51,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fhooks-events","Hooks and events","Run code when an effect is ready to show, and show a fallback when the GPU can't run it.",{"path":53,"title":54,"description":55,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fperformance","Performance","Learn what makes an effect expensive on the GPU and how to keep it inside the frame budget.",{"path":57,"title":58,"description":59,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fcustom-shaders","Custom components","Write your own component in TypeScript, use it like any component in the library, and write WGSL by hand when you need to.",{"path":61,"title":62,"description":63,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Ftelemetry","Telemetry","The `\u003CShader>` component occasionally sends an anonymous performance report to help us improve the engine over time.",{"path":65,"title":66,"description":67,"category":68,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fmcp","Shaders MCP","Connect your AI coding agent to Shaders so it can find, install and edit effects in your codebase.","workflows",{"path":70,"title":71,"description":72,"category":68,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fcli","Shaders CLI","Install shaders and presets into your codebase as component files, and keep them in sync with the design editor.",{"path":74,"title":75,"description":76,"category":68,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fagent-skill","Agent skill","Give your coding agent a skill that teaches it to build with Shaders components, the CLI and presets.",{"path":78,"title":79,"description":80,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fvideo-rendering","Rendering to video","Turn a shader effect into a video file, from the design editor or from a frame-by-frame pipeline such as Remotion, Hyperframes or Puppeteer.",{"path":82,"title":83,"description":84,"category":85,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fquick-tour-of-the-design-editor","Quick tour of the design editor","A short walkthrough of the full Shaders workflow, from designing on the infinite canvas to exporting code.","editor",{"path":87,"title":88,"description":89,"category":85,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fintro-to-the-interface","Intro to the interface","A tour of the design editor interface: the canvas, layers, the Properties panel, and the ways to share and export.",{"path":91,"title":92,"description":93,"category":85,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fcomposing-your-first-shader-effect","Composing your first shader effect","Building a first shader effect in the design editor by stacking and nesting components as layers.",{"path":95,"title":96,"description":97,"category":85,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fmasking-and-blending","Masking and blending","How blend modes and masks combine components in the design editor.",{"path":99,"title":100,"description":101,"category":85,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fdynamic-prop-mapping","Dynamic prop mapping","Turning static props into dynamic ones in the design editor: mapping one layer's output into another's prop, following the cursor, or animating automatically.",{"path":103,"title":104,"description":105,"category":85,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fcustom-media-and-transformations","Custom media and transformations","Uploading images, videos, and SVG files to the media gallery, adding them to a composition, and placing them with transformations.",{"path":107,"title":108,"description":109,"category":85,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Finstalling-a-shader-via-mcp","Installing a shader via MCP","Installing a shader from the design editor into a hero section with the Shaders MCP.",{"path":111,"title":112,"description":113,"category":85,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fexploring-shape-effects","Exploring shape effects","Applying shape effects such as liquid glass, liquid metal, and glowing edges to built-in shapes and your own SVG.",{"path":115,"title":116,"description":117,"category":85,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fglass-component-spotlight","Component spotlight: Glass","A short look at the Glass component and the liquid-glass look it creates.",{"path":119,"title":120,"description":121,"category":85,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fdigital-looks-component-spotlight","Component spotlight: Digital looks","A look at the digital stylization components for glitchy, futuristic, or technical effects.",{"path":123,"title":12,"description":124,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fjs\u002Fquickstart","Install Shaders without a framework, render your first effect on a canvas, and update props at runtime.",{"path":126,"title":12,"description":127,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Freact\u002Fquickstart","Install Shaders in a React app, render your first effect, and drive a prop from state.",{"path":129,"title":130,"description":131,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Freact\u002Fssr","Next.js and SSR","Render Shaders in Next.js and other React SSR apps by keeping each shader on the client.",{"path":133,"title":12,"description":134,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fsolid\u002Fquickstart","Install Shaders in a Solid app, render your first effect, and drive a prop from a signal.",{"path":136,"title":137,"description":138,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fsolid\u002Fssr","SolidStart and SSR","Render Shaders in SolidStart and other Solid SSR apps by keeping each shader on the client.",{"path":140,"title":12,"description":141,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fsvelte\u002Fquickstart","Install Shaders in a Svelte app, render your first effect, and drive a prop from state.",{"path":143,"title":144,"description":145,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fsvelte\u002Fssr","SvelteKit and SSR","Render Shaders in SvelteKit and other Svelte SSR apps, where each shader already waits for the browser.",{"path":147,"title":12,"description":148,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fvue\u002Fquickstart","Install Shaders in a Vue app, render your first effect, and drive a prop from a ref.",{"path":150,"title":151,"description":152,"category":8,"exclude":9,"forceFramework":9},"\u002Fdocs\u002Fguide\u002Fvue\u002Fssr","Nuxt and SSR","Render Shaders in Nuxt and other Vue SSR apps by keeping each shader on the client.",{"id":154,"title":79,"body":155,"category":8,"description":80,"exclude":9,"extension":1689,"forceFramework":9,"icon":1690,"meta":1691,"navigation":226,"path":78,"seo":1692,"stem":1693,"__hash__":1694},"guide\u002Fdocs\u002Fguide\u002F17.video-rendering.md",{"type":156,"value":157,"toc":1681},"minimark",[158,162,166,171,174,177,181,192,315,326,330,341,747,762,766,777,1110,1116,1146,1150,1157,1650,1668,1672,1678],[159,160,79],"h1",{"id":161},"rendering-to-video",[163,164,165],"p",{},"Turn an effect into an MP4 or WebM: record it in the design editor, or render it frame by frame from Remotion, Hyperframes or your own headless Chrome script.",[167,168,170],"h2",{"id":169},"recording-in-the-design-editor","Recording in the design editor",[163,172,173],{},"Requires Shaders Pro.",[163,175,176],{},"Open the recording dialog from the editor toolbar, pick a duration, frame rate and format, and the editor renders the effect and downloads the file. MP4 and WebM are available; WebM keeps a transparent background when the effect has one. This is the fastest route when the effect stands on its own and you don't need it inside a larger edit.",[167,178,180],{"id":179},"rendering-frame-by-frame","Rendering frame by frame",[163,182,183,184,187,188,191],{},"The other routes share one idea. A video renderer asks for frame 120, then frame 7, often from several browser tabs at once, and every request must give the same image. Wall-clock time can't do that, so the headless renderer's ",[8,185,186],{},"renderFrame()"," takes a ",[8,189,190],{},"deltaSeconds"," option that advances the effect by exactly that many seconds:",[193,194,199],"pre",{"className":195,"code":196,"language":197,"meta":198,"style":198},"language-javascript shiki shiki-themes github-dark","import { createRendererFromJSON } from 'shaders\u002Fcore'\n\nconst renderer = createRendererFromJSON({\n  components: [{ type: 'Aurora', props: { speed: 1 } }],\n})\nawait renderer.initialize(canvas)\n\nawait renderer.renderFrame({ deltaSeconds: 2 }) \u002F\u002F the frame two seconds in\n","javascript","",[8,200,201,221,228,248,266,272,287,292],{"__ignoreMap":198},[202,203,206,210,214,217],"span",{"class":204,"line":205},"line",1,[202,207,209],{"class":208},"snl16","import",[202,211,213],{"class":212},"s95oV"," { createRendererFromJSON } ",[202,215,216],{"class":208},"from",[202,218,220],{"class":219},"sU2Wk"," 'shaders\u002Fcore'\n",[202,222,224],{"class":204,"line":223},2,[202,225,227],{"emptyLinePlaceholder":226},true,"\n",[202,229,231,234,238,241,245],{"class":204,"line":230},3,[202,232,233],{"class":208},"const",[202,235,237],{"class":236},"sDLfK"," renderer",[202,239,240],{"class":208}," =",[202,242,244],{"class":243},"svObZ"," createRendererFromJSON",[202,246,247],{"class":212},"({\n",[202,249,251,254,257,260,263],{"class":204,"line":250},4,[202,252,253],{"class":212},"  components: [{ type: ",[202,255,256],{"class":219},"'Aurora'",[202,258,259],{"class":212},", props: { speed: ",[202,261,262],{"class":236},"1",[202,264,265],{"class":212}," } }],\n",[202,267,269],{"class":204,"line":268},5,[202,270,271],{"class":212},"})\n",[202,273,275,278,281,284],{"class":204,"line":274},6,[202,276,277],{"class":208},"await",[202,279,280],{"class":212}," renderer.",[202,282,283],{"class":243},"initialize",[202,285,286],{"class":212},"(canvas)\n",[202,288,290],{"class":204,"line":289},7,[202,291,227],{"emptyLinePlaceholder":226},[202,293,295,297,299,302,305,308,311],{"class":204,"line":294},8,[202,296,277],{"class":208},[202,298,280],{"class":212},[202,300,301],{"class":243},"renderFrame",[202,303,304],{"class":212},"({ deltaSeconds: ",[202,306,307],{"class":236},"2",[202,309,310],{"class":212}," }) ",[202,312,314],{"class":313},"sAwPA","\u002F\u002F the frame two seconds in\n",[163,316,317,318,320,321,325],{},"The first call with ",[8,319,190],{}," ",[322,323,324],"strong",{},"frame-locks"," the renderer: its clocks restart at zero and it stops drawing on its own. From then on, time only moves when you pass a delta. A negative delta rewinds, so scrubbing backwards works. Each section below applies this to one tool.",[167,327,329],{"id":328},"rendering-with-remotion","Rendering with Remotion",[163,331,332,333,336,337,340],{},"Remotion gives each render a frame number through ",[8,334,335],{},"useCurrentFrame",". Keep the renderer in a ref, remember how many seconds it has advanced, and render the difference. ",[8,338,339],{},"delayRender"," holds the frame until the canvas is drawn:",[193,342,346],{"className":343,"code":344,"language":345,"meta":198,"style":198},"language-jsx shiki shiki-themes github-dark","import { useEffect, useRef } from 'react'\nimport { useCurrentFrame, useVideoConfig, delayRender, continueRender } from 'remotion'\nimport { createRendererFromJSON } from 'shaders\u002Fcore'\n\nexport function ShaderScene({ preset }) {\n  const canvasRef = useRef(null)\n  const rendererRef = useRef(null)\n  const advancedRef = useRef(0) \u002F\u002F seconds rendered so far in this tab\n  const frame = useCurrentFrame()\n  const { fps, width, height } = useVideoConfig()\n\n  useEffect(() => {\n    const handle = delayRender('shader frame')\n    ;(async () => {\n      if (!rendererRef.current) {\n        rendererRef.current = createRendererFromJSON(preset)\n        await rendererRef.current.initialize(canvasRef.current)\n      }\n      const target = frame \u002F fps\n      await rendererRef.current.renderFrame({ deltaSeconds: target - advancedRef.current })\n      advancedRef.current = target\n      continueRender(handle)\n    })()\n  }, [frame, fps, preset])\n\n  return \u003Ccanvas ref={canvasRef} width={width} height={height} style={{ width, height }} \u002F>\n}\n","jsx",[8,347,348,360,372,382,386,407,429,446,468,484,517,522,537,558,574,589,602,616,622,642,661,672,681,687,693,698,741],{"__ignoreMap":198},[202,349,350,352,355,357],{"class":204,"line":205},[202,351,209],{"class":208},[202,353,354],{"class":212}," { useEffect, useRef } ",[202,356,216],{"class":208},[202,358,359],{"class":219}," 'react'\n",[202,361,362,364,367,369],{"class":204,"line":223},[202,363,209],{"class":208},[202,365,366],{"class":212}," { useCurrentFrame, useVideoConfig, delayRender, continueRender } ",[202,368,216],{"class":208},[202,370,371],{"class":219}," 'remotion'\n",[202,373,374,376,378,380],{"class":204,"line":230},[202,375,209],{"class":208},[202,377,213],{"class":212},[202,379,216],{"class":208},[202,381,220],{"class":219},[202,383,384],{"class":204,"line":250},[202,385,227],{"emptyLinePlaceholder":226},[202,387,388,391,394,397,400,404],{"class":204,"line":268},[202,389,390],{"class":208},"export",[202,392,393],{"class":208}," function",[202,395,396],{"class":243}," ShaderScene",[202,398,399],{"class":212},"({ ",[202,401,403],{"class":402},"s9osk","preset",[202,405,406],{"class":212}," }) {\n",[202,408,409,412,415,417,420,423,426],{"class":204,"line":274},[202,410,411],{"class":208},"  const",[202,413,414],{"class":236}," canvasRef",[202,416,240],{"class":208},[202,418,419],{"class":243}," useRef",[202,421,422],{"class":212},"(",[202,424,425],{"class":236},"null",[202,427,428],{"class":212},")\n",[202,430,431,433,436,438,440,442,444],{"class":204,"line":289},[202,432,411],{"class":208},[202,434,435],{"class":236}," rendererRef",[202,437,240],{"class":208},[202,439,419],{"class":243},[202,441,422],{"class":212},[202,443,425],{"class":236},[202,445,428],{"class":212},[202,447,448,450,453,455,457,459,462,465],{"class":204,"line":294},[202,449,411],{"class":208},[202,451,452],{"class":236}," advancedRef",[202,454,240],{"class":208},[202,456,419],{"class":243},[202,458,422],{"class":212},[202,460,461],{"class":236},"0",[202,463,464],{"class":212},") ",[202,466,467],{"class":313},"\u002F\u002F seconds rendered so far in this tab\n",[202,469,471,473,476,478,481],{"class":204,"line":470},9,[202,472,411],{"class":208},[202,474,475],{"class":236}," frame",[202,477,240],{"class":208},[202,479,480],{"class":243}," useCurrentFrame",[202,482,483],{"class":212},"()\n",[202,485,487,489,492,495,498,501,503,506,509,512,515],{"class":204,"line":486},10,[202,488,411],{"class":208},[202,490,491],{"class":212}," { ",[202,493,494],{"class":236},"fps",[202,496,497],{"class":212},", ",[202,499,500],{"class":236},"width",[202,502,497],{"class":212},[202,504,505],{"class":236},"height",[202,507,508],{"class":212}," } ",[202,510,511],{"class":208},"=",[202,513,514],{"class":243}," useVideoConfig",[202,516,483],{"class":212},[202,518,520],{"class":204,"line":519},11,[202,521,227],{"emptyLinePlaceholder":226},[202,523,525,528,531,534],{"class":204,"line":524},12,[202,526,527],{"class":243},"  useEffect",[202,529,530],{"class":212},"(() ",[202,532,533],{"class":208},"=>",[202,535,536],{"class":212}," {\n",[202,538,540,543,546,548,551,553,556],{"class":204,"line":539},13,[202,541,542],{"class":208},"    const",[202,544,545],{"class":236}," handle",[202,547,240],{"class":208},[202,549,550],{"class":243}," delayRender",[202,552,422],{"class":212},[202,554,555],{"class":219},"'shader frame'",[202,557,428],{"class":212},[202,559,561,564,567,570,572],{"class":204,"line":560},14,[202,562,563],{"class":212},"    ;(",[202,565,566],{"class":208},"async",[202,568,569],{"class":212}," () ",[202,571,533],{"class":208},[202,573,536],{"class":212},[202,575,577,580,583,586],{"class":204,"line":576},15,[202,578,579],{"class":208},"      if",[202,581,582],{"class":212}," (",[202,584,585],{"class":208},"!",[202,587,588],{"class":212},"rendererRef.current) {\n",[202,590,592,595,597,599],{"class":204,"line":591},16,[202,593,594],{"class":212},"        rendererRef.current ",[202,596,511],{"class":208},[202,598,244],{"class":243},[202,600,601],{"class":212},"(preset)\n",[202,603,605,608,611,613],{"class":204,"line":604},17,[202,606,607],{"class":208},"        await",[202,609,610],{"class":212}," rendererRef.current.",[202,612,283],{"class":243},[202,614,615],{"class":212},"(canvasRef.current)\n",[202,617,619],{"class":204,"line":618},18,[202,620,621],{"class":212},"      }\n",[202,623,625,628,631,633,636,639],{"class":204,"line":624},19,[202,626,627],{"class":208},"      const",[202,629,630],{"class":236}," target",[202,632,240],{"class":208},[202,634,635],{"class":212}," frame ",[202,637,638],{"class":208},"\u002F",[202,640,641],{"class":212}," fps\n",[202,643,645,648,650,652,655,658],{"class":204,"line":644},20,[202,646,647],{"class":208},"      await",[202,649,610],{"class":212},[202,651,301],{"class":243},[202,653,654],{"class":212},"({ deltaSeconds: target ",[202,656,657],{"class":208},"-",[202,659,660],{"class":212}," advancedRef.current })\n",[202,662,664,667,669],{"class":204,"line":663},21,[202,665,666],{"class":212},"      advancedRef.current ",[202,668,511],{"class":208},[202,670,671],{"class":212}," target\n",[202,673,675,678],{"class":204,"line":674},22,[202,676,677],{"class":243},"      continueRender",[202,679,680],{"class":212},"(handle)\n",[202,682,684],{"class":204,"line":683},23,[202,685,686],{"class":212},"    })()\n",[202,688,690],{"class":204,"line":689},24,[202,691,692],{"class":212},"  }, [frame, fps, preset])\n",[202,694,696],{"class":204,"line":695},25,[202,697,227],{"emptyLinePlaceholder":226},[202,699,701,704,707,711,714,716,719,721,723,726,728,730,733,736,738],{"class":204,"line":700},26,[202,702,703],{"class":208},"  return",[202,705,706],{"class":212}," \u003C",[202,708,710],{"class":709},"s4JwU","canvas",[202,712,713],{"class":243}," ref",[202,715,511],{"class":208},[202,717,718],{"class":212},"{canvasRef} ",[202,720,500],{"class":243},[202,722,511],{"class":208},[202,724,725],{"class":212},"{width} ",[202,727,505],{"class":243},[202,729,511],{"class":208},[202,731,732],{"class":212},"{height} ",[202,734,735],{"class":243},"style",[202,737,511],{"class":208},[202,739,740],{"class":212},"{{ width, height }} \u002F>\n",[202,742,744],{"class":204,"line":743},27,[202,745,746],{"class":212},"}\n",[163,748,749,750,753,754,757,758,761],{},"Remotion renders in parallel tabs, and each tab tracks its own ",[8,751,752],{},"advanced"," time, so a tab that starts at frame 150 lands on the same pixels as one that stepped there. On Remotion 4, render with ",[8,755,756],{},"--gl=angle"," (or ",[8,759,760],{},"chromiumOptions: { gl: 'angle' }"," in the Node API) so headless Chrome exposes WebGPU. Remotion 5 turns this on by default.",[167,763,765],{"id":764},"rendering-with-hyperframes","Rendering with Hyperframes",[163,767,768,769,772,773,776],{},"Hyperframes seeks the page to each frame and fires an ",[8,770,771],{},"hf-seek"," event with the time in seconds. Render the difference from the last seek and hand the promise to ",[8,774,775],{},"waitUntil",", so the frame isn't captured until the canvas is drawn:",[193,778,782],{"className":779,"code":780,"language":781,"meta":198,"style":198},"language-html shiki shiki-themes github-dark","\u003Cdiv data-composition-id=\"aurora\" data-start=\"0\" data-duration=\"4\"\n     data-width=\"1920\" data-height=\"1080\" data-requires-webgpu>\n  \u003Ccanvas id=\"fx\" width=\"1920\" height=\"1080\">\u003C\u002Fcanvas>\n\u003C\u002Fdiv>\n\n\u003Cscript type=\"module\">\n  import { createRendererFromJSON } from 'shaders\u002Fcore'\n\n  const renderer = createRendererFromJSON({\n    components: [{ type: 'Aurora', props: { speed: 1 } }],\n  })\n  const ready = renderer.initialize(document.getElementById('fx'))\n\n  let advanced = 0\n  window.addEventListener('hf-seek', (e) => {\n    const done = ready.then(() => {\n      const delta = e.detail.time - advanced\n      advanced = e.detail.time\n      return renderer.renderFrame({ deltaSeconds: delta })\n    })\n    e.detail.waitUntil(done) \u002F\u002F waitUntil must be called synchronously in the listener\n  })\n\u003C\u002Fscript>\n","html",[8,783,784,816,840,876,885,889,906,917,921,933,946,951,978,982,995,1020,1041,1058,1068,1080,1085,1098,1102],{"__ignoreMap":198},[202,785,786,789,792,795,797,800,803,805,808,811,813],{"class":204,"line":205},[202,787,788],{"class":212},"\u003C",[202,790,791],{"class":709},"div",[202,793,794],{"class":243}," data-composition-id",[202,796,511],{"class":212},[202,798,799],{"class":219},"\"aurora\"",[202,801,802],{"class":243}," data-start",[202,804,511],{"class":212},[202,806,807],{"class":219},"\"0\"",[202,809,810],{"class":243}," data-duration",[202,812,511],{"class":212},[202,814,815],{"class":219},"\"4\"\n",[202,817,818,821,823,826,829,831,834,837],{"class":204,"line":223},[202,819,820],{"class":243},"     data-width",[202,822,511],{"class":212},[202,824,825],{"class":219},"\"1920\"",[202,827,828],{"class":243}," data-height",[202,830,511],{"class":212},[202,832,833],{"class":219},"\"1080\"",[202,835,836],{"class":243}," data-requires-webgpu",[202,838,839],{"class":212},">\n",[202,841,842,845,847,850,852,855,858,860,862,865,867,869,872,874],{"class":204,"line":230},[202,843,844],{"class":212},"  \u003C",[202,846,710],{"class":709},[202,848,849],{"class":243}," id",[202,851,511],{"class":212},[202,853,854],{"class":219},"\"fx\"",[202,856,857],{"class":243}," width",[202,859,511],{"class":212},[202,861,825],{"class":219},[202,863,864],{"class":243}," height",[202,866,511],{"class":212},[202,868,833],{"class":219},[202,870,871],{"class":212},">\u003C\u002F",[202,873,710],{"class":709},[202,875,839],{"class":212},[202,877,878,881,883],{"class":204,"line":250},[202,879,880],{"class":212},"\u003C\u002F",[202,882,791],{"class":709},[202,884,839],{"class":212},[202,886,887],{"class":204,"line":268},[202,888,227],{"emptyLinePlaceholder":226},[202,890,891,893,896,899,901,904],{"class":204,"line":274},[202,892,788],{"class":212},[202,894,895],{"class":709},"script",[202,897,898],{"class":243}," type",[202,900,511],{"class":212},[202,902,903],{"class":219},"\"module\"",[202,905,839],{"class":212},[202,907,908,911,913,915],{"class":204,"line":289},[202,909,910],{"class":208},"  import",[202,912,213],{"class":212},[202,914,216],{"class":208},[202,916,220],{"class":219},[202,918,919],{"class":204,"line":294},[202,920,227],{"emptyLinePlaceholder":226},[202,922,923,925,927,929,931],{"class":204,"line":470},[202,924,411],{"class":208},[202,926,237],{"class":236},[202,928,240],{"class":208},[202,930,244],{"class":243},[202,932,247],{"class":212},[202,934,935,938,940,942,944],{"class":204,"line":486},[202,936,937],{"class":212},"    components: [{ type: ",[202,939,256],{"class":219},[202,941,259],{"class":212},[202,943,262],{"class":236},[202,945,265],{"class":212},[202,947,948],{"class":204,"line":519},[202,949,950],{"class":212},"  })\n",[202,952,953,955,958,960,962,964,967,970,972,975],{"class":204,"line":524},[202,954,411],{"class":208},[202,956,957],{"class":236}," ready",[202,959,240],{"class":208},[202,961,280],{"class":212},[202,963,283],{"class":243},[202,965,966],{"class":212},"(document.",[202,968,969],{"class":243},"getElementById",[202,971,422],{"class":212},[202,973,974],{"class":219},"'fx'",[202,976,977],{"class":212},"))\n",[202,979,980],{"class":204,"line":539},[202,981,227],{"emptyLinePlaceholder":226},[202,983,984,987,990,992],{"class":204,"line":560},[202,985,986],{"class":208},"  let",[202,988,989],{"class":212}," advanced ",[202,991,511],{"class":208},[202,993,994],{"class":236}," 0\n",[202,996,997,1000,1003,1005,1008,1011,1014,1016,1018],{"class":204,"line":576},[202,998,999],{"class":212},"  window.",[202,1001,1002],{"class":243},"addEventListener",[202,1004,422],{"class":212},[202,1006,1007],{"class":219},"'hf-seek'",[202,1009,1010],{"class":212},", (",[202,1012,1013],{"class":402},"e",[202,1015,464],{"class":212},[202,1017,533],{"class":208},[202,1019,536],{"class":212},[202,1021,1022,1024,1027,1029,1032,1035,1037,1039],{"class":204,"line":591},[202,1023,542],{"class":208},[202,1025,1026],{"class":236}," done",[202,1028,240],{"class":208},[202,1030,1031],{"class":212}," ready.",[202,1033,1034],{"class":243},"then",[202,1036,530],{"class":212},[202,1038,533],{"class":208},[202,1040,536],{"class":212},[202,1042,1043,1045,1048,1050,1053,1055],{"class":204,"line":604},[202,1044,627],{"class":208},[202,1046,1047],{"class":236}," delta",[202,1049,240],{"class":208},[202,1051,1052],{"class":212}," e.detail.time ",[202,1054,657],{"class":208},[202,1056,1057],{"class":212}," advanced\n",[202,1059,1060,1063,1065],{"class":204,"line":618},[202,1061,1062],{"class":212},"      advanced ",[202,1064,511],{"class":208},[202,1066,1067],{"class":212}," e.detail.time\n",[202,1069,1070,1073,1075,1077],{"class":204,"line":624},[202,1071,1072],{"class":208},"      return",[202,1074,280],{"class":212},[202,1076,301],{"class":243},[202,1078,1079],{"class":212},"({ deltaSeconds: delta })\n",[202,1081,1082],{"class":204,"line":644},[202,1083,1084],{"class":212},"    })\n",[202,1086,1087,1090,1092,1095],{"class":204,"line":663},[202,1088,1089],{"class":212},"    e.detail.",[202,1091,775],{"class":243},[202,1093,1094],{"class":212},"(done) ",[202,1096,1097],{"class":313},"\u002F\u002F waitUntil must be called synchronously in the listener\n",[202,1099,1100],{"class":204,"line":674},[202,1101,950],{"class":212},[202,1103,1104,1106,1108],{"class":204,"line":683},[202,1105,880],{"class":212},[202,1107,895],{"class":709},[202,1109,839],{"class":212},[163,1111,1112,1115],{},[8,1113,1114],{},"data-requires-webgpu"," on the composition tells Hyperframes the block needs a GPU. Then render as usual:",[193,1117,1121],{"className":1118,"code":1119,"language":1120,"meta":198,"style":198},"language-bash shiki shiki-themes github-dark","npx hyperframes render --fps 60 --output aurora.mp4\n","bash",[8,1122,1123],{"__ignoreMap":198},[202,1124,1125,1128,1131,1134,1137,1140,1143],{"class":204,"line":205},[202,1126,1127],{"class":243},"npx",[202,1129,1130],{"class":219}," hyperframes",[202,1132,1133],{"class":219}," render",[202,1135,1136],{"class":236}," --fps",[202,1138,1139],{"class":236}," 60",[202,1141,1142],{"class":236}," --output",[202,1144,1145],{"class":219}," aurora.mp4\n",[167,1147,1149],{"id":1148},"rendering-with-puppeteer-or-playwright","Rendering with Puppeteer or Playwright",[163,1151,1152,1153,1156],{},"For your own pipeline, launch Chrome with WebGPU enabled, step the renderer one frame at a time, and read each frame back with ",[8,1154,1155],{},"toBlob()",":",[193,1158,1160],{"className":195,"code":1159,"language":197,"meta":198,"style":198},"import puppeteer from 'puppeteer'\nimport { mkdir, writeFile } from 'node:fs\u002Fpromises'\n\nconst browser = await puppeteer.launch({\n  args: ['--enable-unsafe-webgpu', '--use-angle=metal'], \u002F\u002F --use-angle=vulkan on Linux\n})\ntry {\n  const page = await browser.newPage()\n  await page.goto('http:\u002F\u002Flocalhost:5173\u002Fscene.html') \u002F\u002F creates the renderer as `window.renderer`\n  await mkdir('frames', { recursive: true })\n\n  const fps = 60\n  for (let frame = 0; frame \u003C 300; frame++) {\n    const dataUrl = await page.evaluate(async (dt) => {\n      await window.renderer.renderFrame({ deltaSeconds: dt, waitForGpu: false })\n      const blob = await new Promise(resolve => window.canvas.toBlob(resolve, 'image\u002Fpng'))\n      return new Promise(resolve => {\n        const reader = new FileReader()\n        reader.onload = () => resolve(reader.result)\n        reader.readAsDataURL(blob)\n      })\n    }, frame === 0 ? 0 : 1 \u002F fps) \u002F\u002F frame 0 is time zero; every later frame advances one step\n    const png = Buffer.from(dataUrl.split(',')[1], 'base64')\n    await writeFile(`frames\u002F${String(frame).padStart(4, '0')}.png`, png)\n  }\n} finally {\n  await browser.close()\n}\n",[8,1161,1162,1174,1186,1190,1210,1229,1233,1240,1259,1280,1301,1305,1317,1351,1382,1399,1438,1454,1471,1491,1501,1506,1536,1573,1619,1624,1634,1645],{"__ignoreMap":198},[202,1163,1164,1166,1169,1171],{"class":204,"line":205},[202,1165,209],{"class":208},[202,1167,1168],{"class":212}," puppeteer ",[202,1170,216],{"class":208},[202,1172,1173],{"class":219}," 'puppeteer'\n",[202,1175,1176,1178,1181,1183],{"class":204,"line":223},[202,1177,209],{"class":208},[202,1179,1180],{"class":212}," { mkdir, writeFile } ",[202,1182,216],{"class":208},[202,1184,1185],{"class":219}," 'node:fs\u002Fpromises'\n",[202,1187,1188],{"class":204,"line":230},[202,1189,227],{"emptyLinePlaceholder":226},[202,1191,1192,1194,1197,1199,1202,1205,1208],{"class":204,"line":250},[202,1193,233],{"class":208},[202,1195,1196],{"class":236}," browser",[202,1198,240],{"class":208},[202,1200,1201],{"class":208}," await",[202,1203,1204],{"class":212}," puppeteer.",[202,1206,1207],{"class":243},"launch",[202,1209,247],{"class":212},[202,1211,1212,1215,1218,1220,1223,1226],{"class":204,"line":268},[202,1213,1214],{"class":212},"  args: [",[202,1216,1217],{"class":219},"'--enable-unsafe-webgpu'",[202,1219,497],{"class":212},[202,1221,1222],{"class":219},"'--use-angle=metal'",[202,1224,1225],{"class":212},"], ",[202,1227,1228],{"class":313},"\u002F\u002F --use-angle=vulkan on Linux\n",[202,1230,1231],{"class":204,"line":274},[202,1232,271],{"class":212},[202,1234,1235,1238],{"class":204,"line":289},[202,1236,1237],{"class":208},"try",[202,1239,536],{"class":212},[202,1241,1242,1244,1247,1249,1251,1254,1257],{"class":204,"line":294},[202,1243,411],{"class":208},[202,1245,1246],{"class":236}," page",[202,1248,240],{"class":208},[202,1250,1201],{"class":208},[202,1252,1253],{"class":212}," browser.",[202,1255,1256],{"class":243},"newPage",[202,1258,483],{"class":212},[202,1260,1261,1264,1267,1270,1272,1275,1277],{"class":204,"line":470},[202,1262,1263],{"class":208},"  await",[202,1265,1266],{"class":212}," page.",[202,1268,1269],{"class":243},"goto",[202,1271,422],{"class":212},[202,1273,1274],{"class":219},"'http:\u002F\u002Flocalhost:5173\u002Fscene.html'",[202,1276,464],{"class":212},[202,1278,1279],{"class":313},"\u002F\u002F creates the renderer as `window.renderer`\n",[202,1281,1282,1284,1287,1289,1292,1295,1298],{"class":204,"line":486},[202,1283,1263],{"class":208},[202,1285,1286],{"class":243}," mkdir",[202,1288,422],{"class":212},[202,1290,1291],{"class":219},"'frames'",[202,1293,1294],{"class":212},", { recursive: ",[202,1296,1297],{"class":236},"true",[202,1299,1300],{"class":212}," })\n",[202,1302,1303],{"class":204,"line":519},[202,1304,227],{"emptyLinePlaceholder":226},[202,1306,1307,1309,1312,1314],{"class":204,"line":524},[202,1308,411],{"class":208},[202,1310,1311],{"class":236}," fps",[202,1313,240],{"class":208},[202,1315,1316],{"class":236}," 60\n",[202,1318,1319,1322,1324,1327,1329,1331,1334,1337,1339,1342,1345,1348],{"class":204,"line":539},[202,1320,1321],{"class":208},"  for",[202,1323,582],{"class":212},[202,1325,1326],{"class":208},"let",[202,1328,635],{"class":212},[202,1330,511],{"class":208},[202,1332,1333],{"class":236}," 0",[202,1335,1336],{"class":212},"; frame ",[202,1338,788],{"class":208},[202,1340,1341],{"class":236}," 300",[202,1343,1344],{"class":212},"; frame",[202,1346,1347],{"class":208},"++",[202,1349,1350],{"class":212},") {\n",[202,1352,1353,1355,1358,1360,1362,1364,1367,1369,1371,1373,1376,1378,1380],{"class":204,"line":560},[202,1354,542],{"class":208},[202,1356,1357],{"class":236}," dataUrl",[202,1359,240],{"class":208},[202,1361,1201],{"class":208},[202,1363,1266],{"class":212},[202,1365,1366],{"class":243},"evaluate",[202,1368,422],{"class":212},[202,1370,566],{"class":208},[202,1372,582],{"class":212},[202,1374,1375],{"class":402},"dt",[202,1377,464],{"class":212},[202,1379,533],{"class":208},[202,1381,536],{"class":212},[202,1383,1384,1386,1389,1391,1394,1397],{"class":204,"line":576},[202,1385,647],{"class":208},[202,1387,1388],{"class":212}," window.renderer.",[202,1390,301],{"class":243},[202,1392,1393],{"class":212},"({ deltaSeconds: dt, waitForGpu: ",[202,1395,1396],{"class":236},"false",[202,1398,1300],{"class":212},[202,1400,1401,1403,1406,1408,1410,1413,1416,1418,1421,1424,1427,1430,1433,1436],{"class":204,"line":591},[202,1402,627],{"class":208},[202,1404,1405],{"class":236}," blob",[202,1407,240],{"class":208},[202,1409,1201],{"class":208},[202,1411,1412],{"class":208}," new",[202,1414,1415],{"class":236}," Promise",[202,1417,422],{"class":212},[202,1419,1420],{"class":402},"resolve",[202,1422,1423],{"class":208}," =>",[202,1425,1426],{"class":212}," window.canvas.",[202,1428,1429],{"class":243},"toBlob",[202,1431,1432],{"class":212},"(resolve, ",[202,1434,1435],{"class":219},"'image\u002Fpng'",[202,1437,977],{"class":212},[202,1439,1440,1442,1444,1446,1448,1450,1452],{"class":204,"line":604},[202,1441,1072],{"class":208},[202,1443,1412],{"class":208},[202,1445,1415],{"class":236},[202,1447,422],{"class":212},[202,1449,1420],{"class":402},[202,1451,1423],{"class":208},[202,1453,536],{"class":212},[202,1455,1456,1459,1462,1464,1466,1469],{"class":204,"line":618},[202,1457,1458],{"class":208},"        const",[202,1460,1461],{"class":236}," reader",[202,1463,240],{"class":208},[202,1465,1412],{"class":208},[202,1467,1468],{"class":243}," FileReader",[202,1470,483],{"class":212},[202,1472,1473,1476,1479,1481,1483,1485,1488],{"class":204,"line":624},[202,1474,1475],{"class":212},"        reader.",[202,1477,1478],{"class":243},"onload",[202,1480,240],{"class":208},[202,1482,569],{"class":212},[202,1484,533],{"class":208},[202,1486,1487],{"class":243}," resolve",[202,1489,1490],{"class":212},"(reader.result)\n",[202,1492,1493,1495,1498],{"class":204,"line":644},[202,1494,1475],{"class":212},[202,1496,1497],{"class":243},"readAsDataURL",[202,1499,1500],{"class":212},"(blob)\n",[202,1502,1503],{"class":204,"line":663},[202,1504,1505],{"class":212},"      })\n",[202,1507,1508,1511,1514,1516,1519,1521,1524,1527,1530,1533],{"class":204,"line":674},[202,1509,1510],{"class":212},"    }, frame ",[202,1512,1513],{"class":208},"===",[202,1515,1333],{"class":236},[202,1517,1518],{"class":208}," ?",[202,1520,1333],{"class":236},[202,1522,1523],{"class":208}," :",[202,1525,1526],{"class":236}," 1",[202,1528,1529],{"class":208}," \u002F",[202,1531,1532],{"class":212}," fps) ",[202,1534,1535],{"class":313},"\u002F\u002F frame 0 is time zero; every later frame advances one step\n",[202,1537,1538,1540,1543,1545,1548,1550,1553,1556,1558,1561,1564,1566,1568,1571],{"class":204,"line":683},[202,1539,542],{"class":208},[202,1541,1542],{"class":236}," png",[202,1544,240],{"class":208},[202,1546,1547],{"class":212}," Buffer.",[202,1549,216],{"class":243},[202,1551,1552],{"class":212},"(dataUrl.",[202,1554,1555],{"class":243},"split",[202,1557,422],{"class":212},[202,1559,1560],{"class":219},"','",[202,1562,1563],{"class":212},")[",[202,1565,262],{"class":236},[202,1567,1225],{"class":212},[202,1569,1570],{"class":219},"'base64'",[202,1572,428],{"class":212},[202,1574,1575,1578,1581,1583,1586,1589,1591,1594,1597,1600,1602,1605,1607,1610,1613,1616],{"class":204,"line":689},[202,1576,1577],{"class":208},"    await",[202,1579,1580],{"class":243}," writeFile",[202,1582,422],{"class":212},[202,1584,1585],{"class":219},"`frames\u002F${",[202,1587,1588],{"class":243},"String",[202,1590,422],{"class":219},[202,1592,1593],{"class":212},"frame",[202,1595,1596],{"class":219},").",[202,1598,1599],{"class":243},"padStart",[202,1601,422],{"class":219},[202,1603,1604],{"class":236},"4",[202,1606,497],{"class":219},[202,1608,1609],{"class":219},"'0'",[202,1611,1612],{"class":219},")",[202,1614,1615],{"class":219},"}.png`",[202,1617,1618],{"class":212},", png)\n",[202,1620,1621],{"class":204,"line":695},[202,1622,1623],{"class":212},"  }\n",[202,1625,1626,1629,1632],{"class":204,"line":700},[202,1627,1628],{"class":212},"} ",[202,1630,1631],{"class":208},"finally",[202,1633,536],{"class":212},[202,1635,1636,1638,1640,1643],{"class":204,"line":743},[202,1637,1263],{"class":208},[202,1639,1253],{"class":212},[202,1641,1642],{"class":243},"close",[202,1644,483],{"class":212},[202,1646,1648],{"class":204,"line":1647},28,[202,1649,746],{"class":212},[163,1651,1652,1653,1655,1656,1659,1660,1663,1664,1667],{},"Then encode the folder with ffmpeg. ",[8,1654,1155],{}," waits for the GPU itself, which is why ",[8,1657,1658],{},"waitForGpu: false"," is safe here and saves a fence per frame. Keep the default if you read the canvas any other way, such as ",[8,1661,1662],{},"page.screenshot()"," or ",[8,1665,1666],{},"drawImage"," into another canvas, because those read whatever is on screen and can catch the previous frame. Without the launch flags, headless Chrome reports a WebGPU adapter but draws nothing.",[167,1669,1671],{"id":1670},"simulations-depend-on-the-step-order","Simulations depend on the step order",[163,1673,1674,1677],{},[322,1675,1676],{},"Simulation"," components keep state from one frame to the next: CursorRipples, DataMosh, FloatingParticles, Boids, and any other effect whose particles or ripples carry over between frames. For them a jump of 2s isn't the same as 120 steps of 1\u002F60s, by design. Render those in order, in one tab, at the output frame rate. Every other component gives the same pixels however you reach a frame.",[735,1679,1680],{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}",{"title":198,"searchDepth":223,"depth":223,"links":1682},[1683,1684,1685,1686,1687,1688],{"id":169,"depth":223,"text":170},{"id":179,"depth":223,"text":180},{"id":328,"depth":223,"text":329},{"id":764,"depth":223,"text":765},{"id":1148,"depth":223,"text":1149},{"id":1670,"depth":223,"text":1671},"md","film",{},{"title":79,"description":80},"docs\u002Fguide\u002F17.video-rendering","m1xaYOpvlyUx1perm2ngNGhISMmb6D6bB3jDRmuXHCE"]