{"id":"wave-text","version":"1.0.0","title":"Wave Text","summary":"A line of bold text that ripples like a flag: every letter rises, tilts and changes colour as a wave passes along it. Loops perfectly.","kind":"text","target":"canvas2d","collections":["kinetic-text","hypnotic-loops"],"tags":["wave text","ripple","flag","wavy text","gradient text","title","banner","sale","loop","text animation"],"loopable":true,"motion":"procedural","durationSeconds":6,"status":"published","media":{"poster":"https://aipixelwizard.com/media/wave-text/1.0.0/poster.webp","preview":"https://aipixelwizard.com/media/wave-text/1.0.0/preview.mp4","sample":"https://aipixelwizard.com/media/wave-text/1.0.0/sample.mp4","sampleBytes":380259,"sampleNote":"Pre-rendered with the default settings. Customised settings must be exported locally (npm run render)."},"urls":{"page":"https://aipixelwizard.com/visuals/wave-text","manifest":"https://aipixelwizard.com/api/v1/visuals/wave-text/versions/1.0.0","agentMd":"https://aipixelwizard.com/registry/wave-text/1.0.0/agent.md","files":"https://aipixelwizard.com/registry/wave-text/1.0.0/","demo":"https://aipixelwizard.com/registry/wave-text/1.0.0/index.html"},"description":"A line of text that ripples like a flag in the wind. Every letter rides a travelling sine wave: it rises and falls, tilts with the slope of the wave, swells a little on the crest and shifts colour along a gradient as the wave passes. The wave makes a whole number of cycles per loop, so it repeats forever. Type the words, pick a gradient (sunset, ocean, mint or mono), how many waves run through the line per loop, how high they rise and the backdrop colour.","role":"Text scene (Canvas 2D): a lively, seamless title or sale banner.","useCases":["A sale, promo or summer banner","A looping title behind a video","A playful social post headline"],"synonyms":["wavy letters","text wave","rippling text","waving title","flag effect","undulating text"],"digest":"93cd4f46bbf1c9194560c5d5c96cae84833aa679ae151e8c31408fb89695d91a","implementation":{"target":"canvas2d","runtime":"browser","language":"javascript","module":"esm","entry":"src/wave-text.js","exports":[{"name":"createWaveText","signature":"createWaveText({ text?, gradient?, waves?, amplitude?, weight?, maxWidth?, fontFamily? }) => { draw(ctx, info, width, height) }","description":"Canvas 2D text layer; info.progress (or time and duration) sets the place in the loop."},{"name":"GRADIENTS","signature":"GRADIENTS: { sunset, ocean, mint, mono }","description":"Colour gradients along the line."}],"reusable":["src/wave-text.js"],"demo":"demo.js","harness":"index.html"},"dependencies":[{"id":"apw-runtime","version":"1.0.0","scope":"demo","reason":"Demo harness. The reusable source has no dependencies."}],"uses":[],"usedBy":[],"params":[{"key":"text","label":"Text","type":"text","default":"SUMMER SALE","maxLength":24,"sensitive":true},{"key":"gradient","label":"Colours","type":"select","default":"sunset","options":[{"value":"sunset","label":"Sunset"},{"value":"ocean","label":"Ocean"},{"value":"mint","label":"Mint"},{"value":"mono","label":"Mono"}]},{"key":"waves","label":"Waves per loop","type":"integer","min":1,"max":4,"step":1,"default":2},{"key":"amplitude","label":"Wave height","type":"number","min":0.2,"max":2,"step":0.1,"default":1},{"key":"background","label":"Backdrop","type":"color","default":"#1a1530"}],"presets":[{"id":"default","label":"Sunset, two waves","params":{}},{"id":"ocean","label":"Ocean, gentle","params":{"gradient":"ocean","amplitude":0.6,"waves":1,"text":"DEEP BLUE","background":"#0a1428"}},{"id":"mint","label":"Mint, lively","params":{"gradient":"mint","amplitude":1.6,"waves":3,"text":"FRESH DROP","background":"#0a1c1a"}},{"id":"mono","label":"Mono, big wave","params":{"gradient":"mono","amplitude":1.9,"waves":2,"text":"MAKE WAVES","background":"#14151a"}}],"output":{"width":1080,"height":1080,"fps":30,"durationSeconds":6,"posterSeconds":1.5,"alpha":false,"motion":"procedural","loop":{"seamless":true,"cycleSeconds":6,"method":"The wave makes a whole number of cycles per loop, so the last frame meets the first."},"seed":null},"composition":{"inputs":"Canvas 2D context, info.progress (0 to 1 over the loop), width, height.","outputs":"Draws the text on top of the canvas; does not clear it.","coordinates":"Centred; the line is scaled to fit 84% of the width; wave height is a fraction of the type size.","lighting":"A soft drop shadow lifts the letters.","timing":"Pure function of info.progress (or time and duration).","compatibleWith":["kinetic-title","gradient-background"],"notes":["Colour follows the letter's position plus a small shift by the wave's height, so the gradient seems to flow."]},"reuse":["Sale banner: Sunset, two waves.","Calm title: Ocean, gentle.","Energetic drop: Mint, lively."],"integration":{"newProject":"Create the text once, then each frame draw your backdrop and call wave.draw(ctx, { progress }, width, height).","existingProject":"Copy src/wave-text.js. Call draw on your existing 2D canvas after the rest of the frame."},"performance":{"notes":"One fill per letter per frame; negligible.","limits":"One line only; best under about 20 characters."},"limitations":["System fonts differ between computers; pass fontFamily and load the font first for identical renders.","There is no line wrapping."],"license":{"spdx":"MIT","notice":"Copyright (c) 2026 AI Pixel Wizard. Released under the MIT License. See LICENSE in this folder.","thirdParty":[]},"rights":{"approvedForPublicRelease":true},"provenance":"Original code written for AI Pixel Wizard in October 2026, to the catalog quality bar (docs/QUALITY.md).","changelog":[{"version":"1.0.0","date":"2026-10-08","notes":"Initial release."}],"verification":{"testedOn":"2026-10-08","inputs":{"wave-text@1.0.0":"93cd4f46bbf1c9194560c5d5c96cae84833aa679ae151e8c31408fb89695d91a","apw-runtime@1.0.0":"5be859cdac74823ba0bcc32c6daf06688a871104d24db5d4ceaffffd84d7c40d"},"environment":"Chrome 154.0.8037.97 headless on Linux 7.0.0-38-generic (x64), Node 22.14.0","renderer":"Canvas 2D","width":1080,"height":1080,"fps":30,"frames":180,"renderSeconds":13.8,"loopCheck":{"checked":true,"seamless":true,"maxDifference":0,"note":"Frame 0 vs end state (progress 1): mean |Δ| 0.0000, max |Δ| 0 of 255."},"determinism":{"checked":true,"identical":true},"errors":[],"media":{"poster":"/media/wave-text/1.0.0/poster.webp","preview":"/media/wave-text/1.0.0/preview.mp4","sample":"/media/wave-text/1.0.0/sample.mp4","sampleBytes":380259}},"files":[{"path":"LICENSE","size":1072,"sha256":"8a5f3a96f07e11522ec4bdd0a583356f99bad1245920f9709cee09b995a45153","contentType":"text/plain; charset=utf-8","url":"https://aipixelwizard.com/registry/wave-text/1.0.0/LICENSE"},{"path":"asset.json","size":6361,"sha256":"0851a124921f4b191229153fc9fa58d499f545ccaf0ec07150c0466bd71f3bcc","contentType":"application/json; charset=utf-8","url":"https://aipixelwizard.com/registry/wave-text/1.0.0/asset.json"},{"path":"demo.js","size":875,"sha256":"19844b81d33358210e02c04dabb9c7203278e983184a8600f67e0df46b597f60","contentType":"text/javascript; charset=utf-8","url":"https://aipixelwizard.com/registry/wave-text/1.0.0/demo.js"},{"path":"index.html","size":523,"sha256":"129767ac443cbd0f5121e4b7153d72ea941ee14d547247d367a42c5fd039e5e1","contentType":"text/html; charset=utf-8","url":"https://aipixelwizard.com/registry/wave-text/1.0.0/index.html"},{"path":"src/wave-text.js","size":3878,"sha256":"242728c2975d85b57d738a0037dad28c605e3647b53d595dd0d44de46759e6ed","contentType":"text/javascript; charset=utf-8","url":"https://aipixelwizard.com/registry/wave-text/1.0.0/src/wave-text.js"}],"warnings":[]}