{
  "schemaVersion": 1,
  "id": "last-match",
  "version": "1.0.0",
  "status": "published",
  "kind": "background",
  "title": "The Last Match",
  "summary": "A tiny lit city lives inside the flame of a single match: hundreds of windows, rooftops and street lights glow above the charred head. A dark cinematic macro. Loops.",
  "description": "A macro shot of a burning match in the dark. Inside the amber flame stands a miniature city of ray-marched buildings with hundreds of individually lit windows, rooftop spires and street lights, while translucent flame sheets flow around it and embers rise from the charred wooden head. The camera eases closer over the 12 seconds and the film returns to its start, so it loops. It is restrained and cinematic rather than a physical flame simulation. The film has no settings.",
  "role": "Shader film (three.js): Luminous miniature, a finished 12-second portrait film with no settings.",
  "collections": [
    "impossible-worlds",
    "hypnotic-loops"
  ],
  "tags": [
    "match",
    "flame",
    "fire",
    "city",
    "miniature",
    "macro",
    "embers",
    "night",
    "cinematic",
    "surreal",
    "tiny world",
    "loop",
    "portrait film"
  ],
  "synonyms": [
    "city in a flame",
    "burning match animation",
    "miniature city in fire",
    "macro match",
    "tiny city",
    "cinematic fire loop"
  ],
  "useCases": [
    "A moody title or trailer backdrop",
    "A storytelling or podcast cover loop",
    "A dark, warm background for a quote"
  ],
  "featured": null,
  "publishedAt": "2026-10-10",
  "implementation": {
    "target": "three",
    "runtime": "browser",
    "language": "javascript",
    "module": "esm",
    "entry": "src/last-match.js",
    "exports": [
      {
        "name": "createLastMatch",
        "signature": "createLastMatch() => { scene, camera, setSize(width, height), update(time), dispose() }",
        "description": "The film as one full-screen shader; render its scene with its camera straight to the canvas."
      }
    ],
    "reusable": [
      "src/last-match.js"
    ],
    "demo": "demo.js",
    "harness": "index.html"
  },
  "dependencies": [
    {
      "id": "three",
      "version": "0.185.1",
      "reason": "Renderer and a full-screen shader material."
    },
    {
      "id": "apw-runtime",
      "version": "1.0.0",
      "scope": "demo",
      "reason": "Demo harness."
    }
  ],
  "params": [],
  "presets": [],
  "output": {
    "width": 1080,
    "height": 1350,
    "fps": 30,
    "durationSeconds": 12,
    "posterSeconds": 7.5,
    "alpha": false,
    "motion": "procedural",
    "loop": {
      "seamless": true,
      "cycleSeconds": 12,
      "method": "Every part of the film is a function of the time modulo its length, and every motion completes whole cycles, so the last frame meets the first."
    },
    "seed": null
  },
  "composition": {
    "inputs": "The time in seconds, 0 to 12. The film has no other settings.",
    "outputs": "An opaque full-frame picture, already in display colours: render it straight to the canvas.",
    "coordinates": "Composed for a 4:5 portrait frame and measured by the frame height: in a wider frame the picture stays centred at full height and its background fills the sides; in a narrower one, such as 9:16, the sides are cropped.",
    "lighting": "The flame is the light: amber glow on the buildings and the charred match head, a violet edge at the base of the flame.",
    "timing": "The flame sheets flow, embers rise and the camera eases closer to the miniature city; the last frame matches the first.",
    "compatibleWith": [],
    "notes": [
      "Hundreds of windows are lit individually.",
      "The match head is textured charred wood, not a flat shape.",
      "Every pixel is a function of the time alone, so frames can be rendered in any order."
    ]
  },
  "reuse": [
    "Trailer card: the flame and city are in place from the first frame, so the film can open on them.",
    "Seamless loop: it returns to its start, so it repeats for any length.",
    "Quote or poem backdrop: the dark space either side of the flame holds white text."
  ],
  "integration": {
    "newProject": "Create a WebGLRenderer on a canvas (no antialiasing), call createLastMatch() once and film.setSize(width, height); then for each frame call film.update(timeInSeconds) and renderer.render(film.scene, film.camera).",
    "existingProject": "Copy src/last-match.js, which needs only three. Render film.scene with film.camera into your own canvas or render target; the output is opaque and already in display colours, so use no tone mapping or colour conversion."
  },
  "performance": {
    "notes": "One full-screen fragment shader. About 11 ms a frame at 1080 by 1350 on an integrated Intel GPU (Iris Xe), and about 2 s a frame with software WebGL.",
    "limits": "Plays at 30 frames a second at full size on an integrated GPU."
  },
  "limitations": [
    "The film is finished: it has no settings. To change colours or timing, edit the shader.",
    "It is composed for a 4:5 portrait frame.",
    "Its length is fixed at 12 seconds; repeat the loop to fill a longer clip."
  ],
  "license": {
    "spdx": "MIT",
    "notice": "Copyright (c) 2026 AI Pixel Wizard. Released under the MIT License. See LICENSE in this folder.",
    "thirdParty": []
  },
  "provenance": "From the owner's collection Twenty Impossible Worlds: the original GLSL ES 3.00 shader of the film “The Last Match”, created with ChatGPT / Codex and used as published, wrapped for AI Pixel Wizard as a three.js full-screen shader in October 2026. The collection's synthesised soundtrack is not part of this ingredient.",
  "changelog": [
    {
      "version": "1.0.0",
      "date": "2026-10-10",
      "notes": "Initial release."
    }
  ]
}
