← AI

Magic Wand Transform

A little magic wand sweeps across some content with a flick of the wrist, and wherever it passes the content changes into its new version, trailing a shower of gold sparkles that tumble and fade. Good for “rewrite”, “fix” or “make it better” moments. Tap it to cast the spell again.

Make it friendlier

need the report by fri. dont be late again

Could you send the report over by Friday? Thanks so much, it really helps!

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-magic-wand-transform to a box with two children: the content before, then the content after.
  3. They sit on top of each other, so the box is as big as the larger of the two.

Options

Add any of these attributes to the same element.

data-durationHow long the sweep takes. Default 1600 ms.
data-delayHow long the original shows before the wand appears. Default 900 ms.
data-colorColour of the sparkles. Default #f5b301.
data-sparklesHow many sparkles, as a multiple. Default 1; 0 for none.
data-loopFade back to the original and cast again.
data-holdHow long the new version shows before looping. Default 2600 ms.

Call animacious.magicWandTransform(el) to cast it from your own button.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Magic Wand Transform: a wand sweeps across some content and, wherever it
    // passes, the content changes into its new version in a shower of sparkles.
    // Usage: <div data-magic-wand-transform>
    //          <p>pls send report asap</p>
    //          <p>Could you send the report when you get a moment? Thanks!</p>
    //        </div>
    // The first child is the "before", the second the "after"; they sit on top
    // of each other, so the box is as big as the larger of the two.
    // Optional: data-duration (ms the sweep takes, default 1600), data-delay (ms
    // before it starts, default 900), data-color (sparkle colour, default
    // #f5b301 gold), data-sparkles (how many, default 1), data-loop (fade back
    // and cast again), data-hold (ms the result shows before looping, default 2600).
    // Script use: animacious.magicWandTransform(el) casts it again; tapping or
    // clicking the box does too.
    var DEFAULTS = { duration: 1600, delay: 900, color: '#f5b301', sparkles: 1, hold: 2600 };

    var css =
      '.mwt{position:relative;display:grid}' +
      '.mwt>.mwt-face{grid-area:1/1;margin:0}' +
      '.mwt-after{clip-path:inset(0 100% 0 0)}' +
      '.mwt-fx{position:absolute;inset:0;pointer-events:none;z-index:2}' +
      '.mwt-glow{position:absolute;top:-6%;bottom:-6%;left:0;width:2.2em;margin-left:-1.1em;opacity:0;' +
      'background:radial-gradient(closest-side,color-mix(in srgb,var(--mwt-c) 45%,transparent),transparent);filter:blur(3px)}' +
      '.mwt-wand{position:absolute;left:0;top:0;width:2.6em;height:2.6em;margin:-.39em 0 0 -.39em;opacity:0;transform-origin:.39em .39em;' +
      'filter:drop-shadow(0 3px 4px rgba(20,10,40,.3))}' +
      '.mwt-wand svg{display:block;width:100%;height:100%;overflow:visible}' +
      '.mwt-spark{position:absolute;left:0;top:0;width:.55em;height:.55em;margin:-.275em 0 0 -.275em;color:var(--mwt-c)}' +
      '.mwt-spark.mwt-dot{width:.26em;height:.26em;margin:-.13em 0 0 -.13em;border-radius:50%;background:currentColor}' +
      '.mwt-spark svg{display:block;width:100%;height:100%}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    var STAR = '<svg viewBox="0 0 20 20" aria-hidden="true"><path fill="currentColor" d="M10 0c.7 5.6 4.4 9.3 10 10-5.6.7-9.3 4.4-10 10-.7-5.6-4.4-9.3-10-10C5.6 9.3 9.3 5.6 10 0z"/></svg>';
    // The wand: a dark stick with a white band, tip at the top left.
    var WAND = '<span class="mwt-wand"><svg viewBox="0 0 40 40" aria-hidden="true">' +
      '<path d="M6 6l30 30" stroke="#231a33" stroke-width="5" stroke-linecap="round"/>' +
      '<path d="M6 6l6 6" stroke="#fff" stroke-width="5" stroke-linecap="round"/>' +
      '<path d="M6 6l6 6" stroke="#231a33" stroke-opacity=".25" stroke-width="5" stroke-linecap="round" fill="none"/>' +
      '<path fill="#ffd84d" d="M6 -1.5c.4 3.6 3.9 7.1 7.5 7.5-3.6.4-7.1 3.9-7.5 7.5-.4-3.6-3.9-7.1-7.5-7.5 3.6-.4 7.1-3.9 7.5-7.5z" transform="translate(0 .5)"/></svg></span>';

    function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }

    function setup(el) {
      var kids = [].slice.call(el.children);
      if (kids.length < 2) return false;
      var before = kids[0], after = kids[1];
      el.classList.add('mwt');
      el.style.setProperty('--mwt-c', el.dataset.color || DEFAULTS.color);
      before.classList.add('mwt-face', 'mwt-before');
      after.classList.add('mwt-face', 'mwt-after');
      after.setAttribute('aria-hidden', 'true');
      var fx = document.createElement('div');
      fx.className = 'mwt-fx';
      fx.setAttribute('aria-hidden', 'true');
      fx.innerHTML = '<span class="mwt-glow"></span>' + WAND;
      el.appendChild(fx);
      el._mwt = { before: before, after: after, fx: fx, glow: fx.firstChild, wand: fx.lastChild, run: 0, timers: [], anims: [], sparks: [] };
      el.addEventListener('click', function () { if (!reduced()) play(el, true); });
      return true;
    }

    function show(st, p) {
      // p = 0..1, how far the change has spread from the left.
      var pct = (p * 100).toFixed(2);
      st.after.style.clipPath = 'inset(-20% ' + (100 - pct) + '% -20% -2%)';
      st.before.style.clipPath = 'inset(-20% -2% -20% ' + pct + '%)';
    }

    function sparkle(el, st, x, y, color) {
      var s = document.createElement('span');
      var dot = Math.random() < 0.45;
      s.className = 'mwt-spark' + (dot ? ' mwt-dot' : '');
      if (!dot) s.innerHTML = STAR;
      if (Math.random() < 0.3) s.style.color = '#fff';
      st.fx.appendChild(s);
      var h = el.clientHeight;
      var a = anime.animate(s, {
        x: [x, x + anime.utils.random(-28, 28)],
        y: [{ to: y + anime.utils.random(-22, -4), duration: 260, ease: 'out(2)', from: y },
            { to: Math.min(h + 4, y + anime.utils.random(10, 34)), duration: 520, ease: 'in(2)' }],
        scale: [{ from: 0, to: anime.utils.random(0.6, 1.25, 2), duration: 200 }, { to: 0, duration: 580, ease: 'in(2)' }],
        rotate: [0, anime.utils.random(-180, 180)],
        duration: 780,
        onComplete: function () { s.remove(); }
      });
      st.anims.push(a);
    }

    function play(el, now) {
      var st = el._mwt, d = el.dataset;
      if (!st) return;
      var duration = +d.duration || DEFAULTS.duration;
      var delay = now ? 0 : (d.delay !== undefined ? +d.delay : DEFAULTS.delay);
      var hold = d.hold !== undefined ? +d.hold : DEFAULTS.hold;
      var density = d.sparkles !== undefined ? +d.sparkles : DEFAULTS.sparkles;
      var run = ++st.run;
      st.timers.forEach(clearTimeout);
      st.anims.forEach(function (a) { a.pause(); });
      st.timers = [];
      st.anims = [];
      [].slice.call(st.fx.querySelectorAll('.mwt-spark')).forEach(function (s) { s.remove(); });
      function later(fn, ms) { st.timers.push(setTimeout(function () { if (run === st.run) fn(); }, ms)); }

      show(st, 0);
      st.after.setAttribute('aria-hidden', 'true');
      st.before.removeAttribute('aria-hidden');
      anime.utils.set(st.before, { opacity: 1 });
      anime.utils.set(st.after, { opacity: 1 });
      anime.utils.set(st.wand, { opacity: 0 });
      anime.utils.set(st.glow, { opacity: 0 });

      later(function () {
        var w = el.clientWidth, h = el.clientHeight;
        var fs = parseFloat(getComputedStyle(el).fontSize) || 16;
        var midY = h / 2;
        var p = { t: 0 }, lastSpark = 0;
        // The wand fades in just left of the content, sweeps across with a
        // gentle bob and a flick of the wrist, then fades out on the right.
        st.anims.push(anime.animate(st.wand, { opacity: [0, 1], duration: 220, ease: 'out(2)' }));
        st.anims.push(anime.animate(st.glow, { opacity: [0, 1], duration: 260, ease: 'out(2)' }));
        st.anims.push(anime.animate(p, {
          t: [0, 1], duration: duration, ease: 'inOut(1.6)',
          onUpdate: function () {
            var t = p.t;
            var x = -fs * 0.4 + t * (w + fs * 0.8);
            var y = midY + Math.sin(t * Math.PI * 3) * Math.min(h * 0.28, fs * 0.9) - fs * 0.2;
            anime.utils.set(st.wand, { x: x, y: y, rotate: Math.sin(t * Math.PI * 6) * 10 - 4 });
            anime.utils.set(st.glow, { x: x });
            show(st, Math.max(0, Math.min(1, x / w)));
            var nowT = performance.now();
            if (density > 0 && nowT - lastSpark > 34 / density && t > 0.02 && t < 0.98) {
              lastSpark = nowT;
              sparkle(el, st, x, y, d.color);
              if (Math.random() < 0.5 * density) sparkle(el, st, x, y + anime.utils.random(-h * 0.3, h * 0.3), d.color);
            }
          },
          onComplete: function () {
            show(st, 1);
            st.after.removeAttribute('aria-hidden');
            st.before.setAttribute('aria-hidden', 'true');
            st.anims.push(anime.animate([st.wand, st.glow], { opacity: 0, duration: 300, ease: 'in(2)' }));
            st.anims.push(anime.animate(st.after, { scale: [1, 1.025, 1], duration: 500, ease: 'out(2)' }));
            if (d.loop !== undefined) later(function () {
              // Fade back to the original, then cast again.
              st.anims.push(anime.animate(st.after, { opacity: [1, 0], duration: 450, ease: 'inOut(2)' }));
              st.before.style.clipPath = 'none';
              st.anims.push(anime.animate(st.before, { opacity: [0, 1], duration: 450, ease: 'inOut(2)',
                onComplete: function () { if (run === st.run) later(function () { play(el); }, 0); } }));
            }, hold);
          }
        }));
      }, delay);
    }

    function init() {
      var io = new IntersectionObserver(function (entries) {
        entries.forEach(function (e) {
          if (!e.isIntersecting) return;
          io.unobserve(e.target);
          play(e.target);
        });
      }, { threshold: 0.2 });
      document.querySelectorAll('[data-magic-wand-transform]').forEach(function (el) {
        if (el._mwt || el._mwtSkip) return;
        if (!setup(el)) { el._mwtSkip = true; return; }
        if (reduced()) { show(el._mwt, 1); el._mwt.after.removeAttribute('aria-hidden'); el._mwt.before.setAttribute('aria-hidden', 'true'); }
        else io.observe(el);
      });
    }

    window.animacious = window.animacious || {};
    window.animacious.magicWandTransform = function (el) { if (el && el._mwt) play(el, true); };

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<div data-magic-wand-transform>
  <p>pls send report asap</p>
  <p>Could you send the report when you have a moment? Thanks!</p>
</div>