{"tiddlers":{"$:/plugins/tiddlywiki/confetti/confetti-manager.js":{"title":"$:/plugins/tiddlywiki/confetti/confetti-manager.js","text":"/*\\\ntitle: $:/plugins/tiddlywiki/confetti/confetti-manager.js\ntype: application/javascript\nmodule-type: global\n\nConfetti manager\n\n\\*/\n\"use strict\";\n\nvar confetti = require(\"$:/plugins/tiddlywiki/confetti/confetti.js\");\n\nfunction ConfettiManager() {\n\tthis.outstandingTimers = [];\n}\n\nConfettiManager.prototype.launch = function (delay,options) {\n\tvar self = this,\n\t\tdefaultOptions = {\n\t\t\tscalar: 1.2,\n\t\t\tparticleCount: 400,\n\t\t\tzIndex: 2000\n\t\t};\n\toptions = $tw.utils.extend(defaultOptions,options);\n\tif(delay > 0) {\n\t\tvar id = setTimeout(function() {\n\t\t\tvar p = self.outstandingTimers.indexOf(id);\n\t\t\tif(p !== -1) {\n\t\t\t\tself.outstandingTimers.splice(p,1);\n\t\t\t} else {\n\t\t\t\tconsole.log(\"Confetti Manager Error: Cannot find previously stored timer ID\");\n\t\t\t\t// debugger;\n\t\t\t}\n\t\t\tconfetti(options);\n\t\t},delay);\n\t\tthis.outstandingTimers.push(id);\n\t} else {\n\t\tconfetti(options);\n\t}\n};\n\nConfettiManager.prototype.reset = function () {\n\t$tw.utils.each(this.outstandingTimers,function(id) {\n\t\tclearTimeout(id);\n\t});\n\tthis.outstandingTimers = [];\n\tconfetti.reset();\n};\n\nexports.ConfettiManager = ConfettiManager;\n","type":"application/javascript","module-type":"global"},"$:/plugins/tiddlywiki/confetti/confetti-widget.js":{"title":"$:/plugins/tiddlywiki/confetti/confetti-widget.js","text":"/*\\\ntitle: $:/plugins/tiddlywiki/confetti/confetti-widget.js\ntype: application/javascript\nmodule-type: widget\n\nConfetti widget\n\n\\*/\n\"use strict\";\n\nvar Widget = require(\"$:/core/modules/widgets/widget.js\").widget;\n\nvar confetti = require(\"$:/plugins/tiddlywiki/confetti/confetti.js\");\n\nvar ConfettiWidget = function(parseTreeNode,options) {\n\tthis.initialise(parseTreeNode,options);\n};\n\n/*\nInherit from the base widget class\n*/\nConfettiWidget.prototype = new Widget();\n\n/*\nRender this widget into the DOM\n*/\nConfettiWidget.prototype.render = function(parent,nextSibling) {\n\tvar self = this;\n\t// Remember parent\n\tthis.parentDomNode = parent;\n\t// Compute attributes and execute state\n\tthis.computeAttributes();\n\tthis.execute();\n\t// Launch confetti\n\tif($tw.browser) {\n\t\tvar options = {};\n\t\t$tw.utils.each(this.attributes,function(attribute,name) {\n\t\t\toptions[name] = self.getAttribute(name);\n\t\t});\n\t\t$tw.confettiManager.launch(options.delay,options);\n\t}\n\t// Render children\n\tthis.renderChildren(parent,nextSibling);\n};\n\n/*\nCompute the internal state of the widget\n*/\nConfettiWidget.prototype.execute = function() {\n\t// Make child widgets\n\tthis.makeChildWidgets();\n};\n\n/*\nSelectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering\n*/\nConfettiWidget.prototype.refresh = function(changedTiddlers) {\n\treturn this.refreshChildren(changedTiddlers);\n};\n\nexports.confetti = ConfettiWidget;\n","type":"application/javascript","module-type":"widget"},"$:/plugins/tiddlywiki/confetti/examples/staggered":{"title":"$:/plugins/tiddlywiki/confetti/examples/staggered","tags":"$:/tags/ConfettiExample","text":"<$button>\n<$action-sendmessage $message=\"tm-confetti-launch\"/>\n<$action-sendmessage $message=\"tm-confetti-launch\" delay=300 originY=0.6  spread=100 scalar=1.5/>\n<$action-sendmessage $message=\"tm-confetti-launch\" delay=400 originY=0.55 spread=130/>\n<$action-sendmessage $message=\"tm-confetti-launch\" delay=500 originY=0.55 spread=170 scalar=2/>\nLaunch four staggered rounds of confetti\n</$button>\n"},"$:/plugins/tiddlywiki/confetti/examples/typing-trigger":{"title":"$:/plugins/tiddlywiki/confetti/examples/typing-trigger","tags":"$:/tags/ConfettiExample","text":"Type the word \"launch\": <$edit-text tiddler=\"$:/temp/confetti/launchstatus\" tag=\"input\" placeholder=\"Type here\"/>\n\n<$list filter=\"[{$:/temp/confetti/launchstatus}match:caseinsensitive[launch]]\" variable=\"ignore\">\nLaunched!\n<$confetti particleCount=100/>\n<$confetti particleCount=100 spread=170 delay=300/>\n</$list>\n"},"$:/plugins/tiddlywiki/confetti/confetti.js":{"text":"(function main(global, module, isWorker, workerSize) {\n  var canUseWorker = !!(\n    global.Worker &&\n    global.Blob &&\n    global.Promise &&\n    global.OffscreenCanvas &&\n    global.OffscreenCanvasRenderingContext2D &&\n    global.HTMLCanvasElement &&\n    global.HTMLCanvasElement.prototype.transferControlToOffscreen &&\n    global.URL &&\n    global.URL.createObjectURL);\n\n  function noop() {}\n\n  // create a promise if it exists, otherwise, just\n  // call the function directly\n  function promise(func) {\n    var ModulePromise = module.exports.Promise;\n    var Prom = ModulePromise !== void 0 ? ModulePromise : global.Promise;\n\n    if (typeof Prom === 'function') {\n      return new Prom(func);\n    }\n\n    func(noop, noop);\n\n    return null;\n  }\n\n  var raf = (function () {\n    var TIME = Math.floor(1000 / 60);\n    var frame, cancel;\n    var frames = {};\n    var lastFrameTime = 0;\n\n    if (typeof requestAnimationFrame === 'function' && typeof cancelAnimationFrame === 'function') {\n      frame = function (cb) {\n        var id = Math.random();\n\n        frames[id] = requestAnimationFrame(function onFrame(time) {\n          if (lastFrameTime === time || lastFrameTime + TIME - 1 < time) {\n            lastFrameTime = time;\n            delete frames[id];\n\n            cb();\n          } else {\n            frames[id] = requestAnimationFrame(onFrame);\n          }\n        });\n\n        return id;\n      };\n      cancel = function (id) {\n        if (frames[id]) {\n          cancelAnimationFrame(frames[id]);\n        }\n      };\n    } else {\n      frame = function (cb) {\n        return setTimeout(cb, TIME);\n      };\n      cancel = function (timer) {\n        return clearTimeout(timer);\n      };\n    }\n\n    return { frame: frame, cancel: cancel };\n  }());\n\n  var getWorker = (function () {\n    var worker;\n    var prom;\n    var resolves = {};\n\n    function decorate(worker) {\n      function execute(options, callback) {\n        worker.postMessage({ options: options || {}, callback: callback });\n      }\n      worker.init = function initWorker(canvas) {\n        var offscreen = canvas.transferControlToOffscreen();\n        worker.postMessage({ canvas: offscreen }, [offscreen]);\n      };\n\n      worker.fire = function fireWorker(options, size, done) {\n        if (prom) {\n          execute(options, null);\n          return prom;\n        }\n\n        var id = Math.random().toString(36).slice(2);\n\n        prom = promise(function (resolve) {\n          function workerDone(msg) {\n            if (msg.data.callback !== id) {\n              return;\n            }\n\n            delete resolves[id];\n            worker.removeEventListener('message', workerDone);\n\n            prom = null;\n            done();\n            resolve();\n          }\n\n          worker.addEventListener('message', workerDone);\n          execute(options, id);\n\n          resolves[id] = workerDone.bind(null, { data: { callback: id }});\n        });\n\n        return prom;\n      };\n\n      worker.reset = function resetWorker() {\n        worker.postMessage({ reset: true });\n\n        for (var id in resolves) {\n          resolves[id]();\n          delete resolves[id];\n        }\n      };\n    }\n\n    return function () {\n      if (worker) {\n        return worker;\n      }\n\n      if (!isWorker && canUseWorker) {\n        var code = [\n          'var CONFETTI, SIZE = {}, module = {};',\n          '(' + main.toString() + ')(this, module, true, SIZE);',\n          'onmessage = function(msg) {',\n          '  if (msg.data.options) {',\n          '    CONFETTI(msg.data.options).then(function () {',\n          '      if (msg.data.callback) {',\n          '        postMessage({ callback: msg.data.callback });',\n          '      }',\n          '    });',\n          '  } else if (msg.data.reset) {',\n          '    CONFETTI && CONFETTI.reset();',\n          '  } else if (msg.data.resize) {',\n          '    SIZE.width = msg.data.resize.width;',\n          '    SIZE.height = msg.data.resize.height;',\n          '  } else if (msg.data.canvas) {',\n          '    SIZE.width = msg.data.canvas.width;',\n          '    SIZE.height = msg.data.canvas.height;',\n          '    CONFETTI = module.exports.create(msg.data.canvas);',\n          '  }',\n          '}',\n        ].join('\\n');\n        try {\n          worker = new Worker(URL.createObjectURL(new Blob([code])));\n        } catch (e) {\n          // eslint-disable-next-line no-console\n          typeof console !== undefined && typeof console.warn === 'function' ? console.warn('🎊 Could not load worker', e) : null;\n\n          return null;\n        }\n\n        decorate(worker);\n      }\n\n      return worker;\n    };\n  })();\n\n  var defaults = {\n    particleCount: 50,\n    angle: 90,\n    spread: 45,\n    startVelocity: 45,\n    decay: 0.9,\n    gravity: 1,\n    drift: 0,\n    ticks: 200,\n    x: 0.5,\n    y: 0.5,\n    shapes: ['square', 'circle'],\n    zIndex: 100,\n    colors: [\n      '#26ccff',\n      '#a25afd',\n      '#ff5e7e',\n      '#88ff5a',\n      '#fcff42',\n      '#ffa62d',\n      '#ff36ff'\n    ],\n    // probably should be true, but back-compat\n    disableForReducedMotion: false,\n    scalar: 1\n  };\n\n  function convert(val, transform) {\n    return transform ? transform(val) : val;\n  }\n\n  function isOk(val) {\n    return !(val === null || val === undefined);\n  }\n\n  function prop(options, name, transform) {\n    return convert(\n      options && isOk(options[name]) ? options[name] : defaults[name],\n      transform\n    );\n  }\n\n  function onlyPositiveInt(number){\n    return number < 0 ? 0 : Math.floor(number);\n  }\n\n  function randomInt(min, max) {\n    // [min, max)\n    return Math.floor(Math.random() * (max - min)) + min;\n  }\n\n  function toDecimal(str) {\n    return parseInt(str, 16);\n  }\n\n  function colorsToRgb(colors) {\n    return colors.map(hexToRgb);\n  }\n\n  function hexToRgb(str) {\n    var val = String(str).replace(/[^0-9a-f]/gi, '');\n\n    if (val.length < 6) {\n        val = val[0]+val[0]+val[1]+val[1]+val[2]+val[2];\n    }\n\n    return {\n      r: toDecimal(val.substring(0,2)),\n      g: toDecimal(val.substring(2,4)),\n      b: toDecimal(val.substring(4,6))\n    };\n  }\n\n  function getOrigin(options) {\n    var origin = prop(options, 'origin', Object);\n    origin.x = prop(origin, 'x', Number);\n    origin.y = prop(origin, 'y', Number);\n\n    return origin;\n  }\n\n  function setCanvasWindowSize(canvas) {\n    canvas.width = document.documentElement.clientWidth;\n    canvas.height = document.documentElement.clientHeight;\n  }\n\n  function setCanvasRectSize(canvas) {\n    var rect = canvas.getBoundingClientRect();\n    canvas.width = rect.width;\n    canvas.height = rect.height;\n  }\n\n  function getCanvas(zIndex) {\n    var canvas = document.createElement('canvas');\n\n    canvas.style.position = 'fixed';\n    canvas.style.top = '0px';\n    canvas.style.left = '0px';\n    canvas.style.pointerEvents = 'none';\n    canvas.style.zIndex = zIndex;\n\n    return canvas;\n  }\n\n  function ellipse(context, x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise) {\n    context.save();\n    context.translate(x, y);\n    context.rotate(rotation);\n    context.scale(radiusX, radiusY);\n    context.arc(0, 0, 1, startAngle, endAngle, antiClockwise);\n    context.restore();\n  }\n\n  function randomPhysics(opts) {\n    var radAngle = opts.angle * (Math.PI / 180);\n    var radSpread = opts.spread * (Math.PI / 180);\n\n    return {\n      x: opts.x,\n      y: opts.y,\n      wobble: Math.random() * 10,\n      wobbleSpeed: Math.min(0.11, Math.random() * 0.1 + 0.05),\n      velocity: (opts.startVelocity * 0.5) + (Math.random() * opts.startVelocity),\n      angle2D: -radAngle + ((0.5 * radSpread) - (Math.random() * radSpread)),\n      tiltAngle: (Math.random() * (0.75 - 0.25) + 0.25) * Math.PI,\n      color: opts.color,\n      shape: opts.shape,\n      tick: 0,\n      totalTicks: opts.ticks,\n      decay: opts.decay,\n      drift: opts.drift,\n      random: Math.random() + 2,\n      tiltSin: 0,\n      tiltCos: 0,\n      wobbleX: 0,\n      wobbleY: 0,\n      gravity: opts.gravity * 3,\n      ovalScalar: 0.6,\n      scalar: opts.scalar\n    };\n  }\n\n  function updateFetti(context, fetti) {\n    fetti.x += Math.cos(fetti.angle2D) * fetti.velocity + fetti.drift;\n    fetti.y += Math.sin(fetti.angle2D) * fetti.velocity + fetti.gravity;\n    fetti.wobble += fetti.wobbleSpeed;\n    fetti.velocity *= fetti.decay;\n    fetti.tiltAngle += 0.1;\n    fetti.tiltSin = Math.sin(fetti.tiltAngle);\n    fetti.tiltCos = Math.cos(fetti.tiltAngle);\n    fetti.random = Math.random() + 2;\n    fetti.wobbleX = fetti.x + ((10 * fetti.scalar) * Math.cos(fetti.wobble));\n    fetti.wobbleY = fetti.y + ((10 * fetti.scalar) * Math.sin(fetti.wobble));\n\n    var progress = (fetti.tick++) / fetti.totalTicks;\n\n    var x1 = fetti.x + (fetti.random * fetti.tiltCos);\n    var y1 = fetti.y + (fetti.random * fetti.tiltSin);\n    var x2 = fetti.wobbleX + (fetti.random * fetti.tiltCos);\n    var y2 = fetti.wobbleY + (fetti.random * fetti.tiltSin);\n\n    context.fillStyle = 'rgba(' + fetti.color.r + ', ' + fetti.color.g + ', ' + fetti.color.b + ', ' + (1 - progress) + ')';\n    context.beginPath();\n\n    if (fetti.shape === 'circle') {\n      context.ellipse ?\n        context.ellipse(fetti.x, fetti.y, Math.abs(x2 - x1) * fetti.ovalScalar, Math.abs(y2 - y1) * fetti.ovalScalar, Math.PI / 10 * fetti.wobble, 0, 2 * Math.PI) :\n        ellipse(context, fetti.x, fetti.y, Math.abs(x2 - x1) * fetti.ovalScalar, Math.abs(y2 - y1) * fetti.ovalScalar, Math.PI / 10 * fetti.wobble, 0, 2 * Math.PI);\n    } else if (fetti.shape === 'star') {\n      var rot = Math.PI / 2 * 3;\n      var innerRadius = 4 * fetti.scalar;\n      var outerRadius = 8 * fetti.scalar;\n      var x = fetti.x;\n      var y = fetti.y;\n      var spikes = 5;\n      var step = Math.PI / spikes;\n\n      while (spikes--) {\n        x = fetti.x + Math.cos(rot) * outerRadius;\n        y = fetti.y + Math.sin(rot) * outerRadius;\n        context.lineTo(x, y);\n        rot += step;\n\n        x = fetti.x + Math.cos(rot) * innerRadius;\n        y = fetti.y + Math.sin(rot) * innerRadius;\n        context.lineTo(x, y);\n        rot += step;\n      }\n    } else {\n      context.moveTo(Math.floor(fetti.x), Math.floor(fetti.y));\n      context.lineTo(Math.floor(fetti.wobbleX), Math.floor(y1));\n      context.lineTo(Math.floor(x2), Math.floor(y2));\n      context.lineTo(Math.floor(x1), Math.floor(fetti.wobbleY));\n    }\n\n    context.closePath();\n    context.fill();\n\n    return fetti.tick < fetti.totalTicks;\n  }\n\n  function animate(canvas, fettis, resizer, size, done) {\n    var animatingFettis = fettis.slice();\n    var context = canvas.getContext('2d');\n    var animationFrame;\n    var destroy;\n\n    var prom = promise(function (resolve) {\n      function onDone() {\n        animationFrame = destroy = null;\n\n        context.clearRect(0, 0, size.width, size.height);\n\n        done();\n        resolve();\n      }\n\n      function update() {\n        if (isWorker && !(size.width === workerSize.width && size.height === workerSize.height)) {\n          size.width = canvas.width = workerSize.width;\n          size.height = canvas.height = workerSize.height;\n        }\n\n        if (!size.width && !size.height) {\n          resizer(canvas);\n          size.width = canvas.width;\n          size.height = canvas.height;\n        }\n\n        context.clearRect(0, 0, size.width, size.height);\n\n        animatingFettis = animatingFettis.filter(function (fetti) {\n          return updateFetti(context, fetti);\n        });\n\n        if (animatingFettis.length) {\n          animationFrame = raf.frame(update);\n        } else {\n          onDone();\n        }\n      }\n\n      animationFrame = raf.frame(update);\n      destroy = onDone;\n    });\n\n    return {\n      addFettis: function (fettis) {\n        animatingFettis = animatingFettis.concat(fettis);\n\n        return prom;\n      },\n      canvas: canvas,\n      promise: prom,\n      reset: function () {\n        if (animationFrame) {\n          raf.cancel(animationFrame);\n        }\n\n        if (destroy) {\n          destroy();\n        }\n      }\n    };\n  }\n\n  function confettiCannon(canvas, globalOpts) {\n    var isLibCanvas = !canvas;\n    var allowResize = !!prop(globalOpts || {}, 'resize');\n    var globalDisableForReducedMotion = prop(globalOpts, 'disableForReducedMotion', Boolean);\n    var shouldUseWorker = canUseWorker && !!prop(globalOpts || {}, 'useWorker');\n    var worker = shouldUseWorker ? getWorker() : null;\n    var resizer = isLibCanvas ? setCanvasWindowSize : setCanvasRectSize;\n    var initialized = (canvas && worker) ? !!canvas.__confetti_initialized : false;\n    var preferLessMotion = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion)').matches;\n    var animationObj;\n\n    function fireLocal(options, size, done) {\n      var particleCount = prop(options, 'particleCount', onlyPositiveInt);\n      var angle = prop(options, 'angle', Number);\n      var spread = prop(options, 'spread', Number);\n      var startVelocity = prop(options, 'startVelocity', Number);\n      var decay = prop(options, 'decay', Number);\n      var gravity = prop(options, 'gravity', Number);\n      var drift = prop(options, 'drift', Number);\n      var colors = prop(options, 'colors', colorsToRgb);\n      var ticks = prop(options, 'ticks', Number);\n      var shapes = prop(options, 'shapes');\n      var scalar = prop(options, 'scalar');\n      var origin = getOrigin(options);\n\n      var temp = particleCount;\n      var fettis = [];\n\n      var startX = canvas.width * origin.x;\n      var startY = canvas.height * origin.y;\n\n      while (temp--) {\n        fettis.push(\n          randomPhysics({\n            x: startX,\n            y: startY,\n            angle: angle,\n            spread: spread,\n            startVelocity: startVelocity,\n            color: colors[temp % colors.length],\n            shape: shapes[randomInt(0, shapes.length)],\n            ticks: ticks,\n            decay: decay,\n            gravity: gravity,\n            drift: drift,\n            scalar: scalar\n          })\n        );\n      }\n\n      // if we have a previous canvas already animating,\n      // add to it\n      if (animationObj) {\n        return animationObj.addFettis(fettis);\n      }\n\n      animationObj = animate(canvas, fettis, resizer, size , done);\n\n      return animationObj.promise;\n    }\n\n    function fire(options) {\n      var disableForReducedMotion = globalDisableForReducedMotion || prop(options, 'disableForReducedMotion', Boolean);\n      var zIndex = prop(options, 'zIndex', Number);\n\n      if (disableForReducedMotion && preferLessMotion) {\n        return promise(function (resolve) {\n          resolve();\n        });\n      }\n\n      if (isLibCanvas && animationObj) {\n        // use existing canvas from in-progress animation\n        canvas = animationObj.canvas;\n      } else if (isLibCanvas && !canvas) {\n        // create and initialize a new canvas\n        canvas = getCanvas(zIndex);\n        document.body.appendChild(canvas);\n      }\n\n      if (allowResize && !initialized) {\n        // initialize the size of a user-supplied canvas\n        resizer(canvas);\n      }\n\n      var size = {\n        width: canvas.width,\n        height: canvas.height\n      };\n\n      if (worker && !initialized) {\n        worker.init(canvas);\n      }\n\n      initialized = true;\n\n      if (worker) {\n        canvas.__confetti_initialized = true;\n      }\n\n      function onResize() {\n        if (worker) {\n          // TODO this really shouldn't be immediate, because it is expensive\n          var obj = {\n            getBoundingClientRect: function () {\n              if (!isLibCanvas) {\n                return canvas.getBoundingClientRect();\n              }\n            }\n          };\n\n          resizer(obj);\n\n          worker.postMessage({\n            resize: {\n              width: obj.width,\n              height: obj.height\n            }\n          });\n          return;\n        }\n\n        // don't actually query the size here, since this\n        // can execute frequently and rapidly\n        size.width = size.height = null;\n      }\n\n      function done() {\n        animationObj = null;\n\n        if (allowResize) {\n          global.removeEventListener('resize', onResize);\n        }\n\n        if (isLibCanvas && canvas) {\n          document.body.removeChild(canvas);\n          canvas = null;\n          initialized = false;\n        }\n      }\n\n      if (allowResize) {\n        global.addEventListener('resize', onResize, false);\n      }\n\n      if (worker) {\n        return worker.fire(options, size, done);\n      }\n\n      return fireLocal(options, size, done);\n    }\n\n    fire.reset = function () {\n      if (worker) {\n        worker.reset();\n      }\n\n      if (animationObj) {\n        animationObj.reset();\n      }\n    };\n\n    return fire;\n  }\n\n  // Make default export lazy to defer worker creation until called.\n  var defaultFire;\n  function getDefaultFire() {\n    if (!defaultFire) {\n      defaultFire = confettiCannon(null, { useWorker: true, resize: true });\n    }\n    return defaultFire;\n  }\n\n  module.exports = function() {\n    return getDefaultFire().apply(this, arguments);\n  };\n  module.exports.reset = function() {\n    getDefaultFire().reset();\n  };\n  module.exports.create = confettiCannon;\n}((function () {\n  if (typeof window !== 'undefined') {\n    return window;\n  }\n\n  if (typeof self !== 'undefined') {\n    return self;\n  }\n\n  return this || {};\n})(), module, false));\n","type":"application/javascript","title":"$:/plugins/tiddlywiki/confetti/confetti.js","module-type":"library"},"$:/plugins/tiddlywiki/confetti/license":{"text":"ISC License\n\nCopyright (c) 2020, Kiril Vatev\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n","type":"text/plain","title":"$:/plugins/tiddlywiki/confetti/license"},"$:/plugins/tiddlywiki/confetti/readme":{"title":"$:/plugins/tiddlywiki/confetti/readme","text":"\\define show-example(name)\n<$let title={{{ [[$:/plugins/tiddlywiki/confetti/examples/]addsuffix<__name__>] }}}>\n\nFor example:\n\n<$macrocall $name=\"copy-to-clipboard-above-right\" src=<<__src__>>/>\n\n<$codeblock code={{{ [<title>get[text]] }}}/>\n\nRenders as:\n\n<$transclude tiddler=<<title>> mode=\"block\"/>\n\n</$let>\n\\end\n\n! Introduction\n\nThis plugin adds a programmable confetti cannon to your TiddlyWiki. It is based on https://www.kirilv.com/canvas-confetti/ by Kiril Vatev.\n\n! Usage\n\nThe confetti cannon can be controlled using messages or via the `<$confetti>` widget. Use the message approach when triggering confetti in response to an action (such as clicking a button). Use the widget approach when confetti is to be triggered by a condition (such as a target number of words being reached).\n\n!! Messages: tm-confetti-launch and tm-confetti-reset\n\nThe `tm-confetti-launch` message launches the confetti cannon. See below for the available parameters.\n\nThe `tm-confetti-reset` message cancels any confetti that is in progress.\n\n<<show-example staggered>>\n\n!! Widget: `<$confetti>`\n\nThe `<$confetti>` widget launches the confetti cannon when it is first rendered. See below for the available attributes.\n\nTypically it is used in conjunction with a `<$list>` or `<$reveal>` widget that shows the widget when the conditions required to trigger the confetti are satisfied.\n\nIn this example, the confetti will be launched when the word \"launch\" in typed into the box.\n\n<<show-example typing-trigger>>\n\n!! Confetti Launch parameters\n\nThe following options are supported:\n\n|!Name |!Description |!Default |\n|''delay'' |Number of milliseconds to delay the launch |0 |\n|''particleCount'' |The number of confetti to launch |50 |\n|''angle'' |The angle in which to launch the confetti, in degrees (90 is straight up) |90 |\n|''spread'' |How far off center the confetti can go, in degrees. 45 means the confetti will launch at the defined `angle` plus or minus 22.5 degrees |45 |\n|''startVelocity'' |How fast the confetti will start going, in pixels |45 |\n|''decay'' |How quickly the confetti will lose speed. Keep this number between 0 and 1, otherwise the confetti will gain speed |0.9 |\n|''gravity'' |How quickly the particles are pulled down. 1 is full gravity, 0.5 is half gravity, etc. |1 |\n|''drift'' |How much to the side the confetti will drift. The default is 0, meaning that they will fall straight down. Use a negative number for left and positive number for right |0 |\n|''ticks'' |How many times the confetti will move (this is an abstract quantity; the designed recommends playing with it if the confetti disappears too quickly for you) |200 |\n|''originX'' |The `x` position on the page, with `0` being the left edge and `1` being the right edge |0.5 |\n|''originY'' |The `y` position on the page, with `0` being the top edge and `1` being the bottom edge |0.5 |\n|''colors'' |A space separated list of color strings in hex format (eg `#bada55` or `#ce5`) | |\n|''shapes'' |A space separated list of shapes for the confetti. The possible values are `square`, `circle`, and `star`. The default is to use both squares and circles in an even mix. To use a single shape, you can provide just one shape in the list, such as `star`. You can also change the mix by providing a value such as `circle circle square` to use two third circles and one third squares | |\n|''scalar'' |Scale factor for each confetti particle. Use decimals to make the confetti smaller |1 |\n|''zIndex'' |Z-index of confetti. Increase the value if the confetti is appearing behind other on-screen elements|100 |\n|''disableForReducedMotion'' |Set to `yes` to entirely disable confetti for users that [[prefer reduced motion|https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion]] |`no` |\n"},"$:/plugins/tiddlywiki/confetti/startup.js":{"title":"$:/plugins/tiddlywiki/confetti/startup.js","text":"/*\\\ntitle: $:/plugins/tiddlywiki/confetti/startup.js\ntype: application/javascript\nmodule-type: startup\n\nSetup the root widget event handlers\n\n\\*/\n\"use strict\";\n\n// Export name and synchronous status\nexports.name = \"confetti\";\nexports.platforms = [\"browser\"];\nexports.after = [\"startup\"];\nexports.synchronous = true;\n\n// Install the root widget event handlers\nexports.startup = function() {\n\t$tw.confettiManager = new $tw.ConfettiManager();\n\t$tw.rootWidget.addEventListener(\"tm-confetti-launch\",function(event) {\n\t\tvar paramObject = event.paramObject || {},\n\t\t\toptions = {},\n\t\t\textractNumericParameter = function(name) {\n\t\t\t\tif(paramObject[name]) {\n\t\t\t\t\toptions[name] = $tw.utils.parseNumber(paramObject[name]);\n\t\t\t\t}\n\t\t\t},\n\t\t\textractListParameter = function(name) {\n\t\t\t\tif(paramObject[name]) {\n\t\t\t\t\toptions[name] = $tw.utils.parseStringArray(paramObject[name]);\n\t\t\t\t}\n\t\t\t},\n\t\t\textractBooleanParameter = function(name) {\n\t\t\t\tif(paramObject[name]) {\n\t\t\t\t\toptions[name] = paramObject[name] === \"yes\";\n\t\t\t\t}\n\t\t\t};\n\t\t$tw.utils.each(\"particleCount angle spread startVelocity decay gravity drift ticks scalar zIndex\".split(\" \"),function(name) {\n\t\t\textractNumericParameter(name);\n\t\t});\n\t\t$tw.utils.each(\"colors shapes\".split(\" \"),function(name) {\n\t\t\textractListParameter(name);\n\t\t});\n\t\tif(paramObject.originX && paramObject.originY) {\n\t\t\toptions.origin = {\n\t\t\t\tx: paramObject.originX && $tw.utils.parseNumber(paramObject.originX),\n\t\t\t\ty: paramObject.originY && $tw.utils.parseNumber(paramObject.originY)\n\t\t\t};\n\t\t}\n\t\textractBooleanParameter(\"disableForReducedMotion\");\n\t\tvar delay = paramObject.delay ? $tw.utils.parseNumber(paramObject.delay) : 0;\n\t\t$tw.confettiManager.launch(delay,options);\n\t});\n\t$tw.rootWidget.addEventListener(\"tm-confetti-reset\",function(event) {\n\t\t$tw.confettiManager.reset();\n\t});\n};\n","type":"application/javascript","module-type":"startup"}}}