{
  "schemaVersion": 1,
  "id": "wave-text",
  "version": "1.0.0",
  "status": "published",
  "kind": "text",
  "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.",
  "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.",
  "collections": [
    "kinetic-text",
    "hypnotic-loops"
  ],
  "tags": [
    "wave text",
    "ripple",
    "flag",
    "wavy text",
    "gradient text",
    "title",
    "banner",
    "sale",
    "loop",
    "text animation"
  ],
  "synonyms": [
    "wavy letters",
    "text wave",
    "rippling text",
    "waving title",
    "flag effect",
    "undulating text"
  ],
  "useCases": [
    "A sale, promo or summer banner",
    "A looping title behind a video",
    "A playful social post headline"
  ],
  "featured": null,
  "publishedAt": "2026-10-08",
  "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."
    }
  ],
  "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": []
  },
  "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."
    }
  ]
}
