docs: function(data, oncomplete, onprogress, onerror) {
          if (Array.isArray(data)) {
              Crafty.log(
                  "Calling Crafty.load with an array of assets no longer works; see the docs for more details."
              );
              return;
          }
  
          data = typeof data === "string" ? JSON.parse(data) : data;
  
          var j = 0,
              total =
                  (data.audio ? Object.keys(data.audio).length : 0) +
                  (data.images ? Object.keys(data.images).length : 0) +
                  (data.sprites ? Object.keys(data.sprites).length : 0),
              current,
              fileUrl,
              obj,
              type,
              asset,
              paths = Crafty.paths(),
              getFilePath = function(type, f) {
                  return f.search("://") === -1
                      ? type === "audio"
                          ? paths.audio + f
                          : paths.images + f
                      : f;
              },
              // returns null if 'a' is not already a loaded asset, obj otherwise
              isAsset = function(a) {
                  return Crafty.asset(a) || null;
              },
              isSupportedAudio = function(f) {
                  return (
                      Crafty.support.audio &&
                      Crafty.audio.supports(Utility.fileTypeOf(f).type)
                  );
              },
              isValidImage = function(f) {
                  return (
                      -1 <
                      Crafty.imageWhitelist.indexOf(Utility.fileTypeOf(f).type)
                  );
              },
              shortURLOf = function(URI) {
                  return Utility.fileTypeOf(URI).schema === "data"
                      ? URL.createObjectURL(Utility.toBlob(URI))
                      : URI;
              },
              onImgLoad = function(obj, url) {
                  obj.onload = pro;
                  if (Crafty.support.prefix === "webkit") obj.src = ""; // workaround for webkit bug
                  obj.src = url;
              };
  
          //Progress function
          function pro() {
              var src = this.src;
  
              //Remove events cause audio trigger this event more than once(depends on browser)
              if (this.removeEventListener)
                  this.removeEventListener("canplaythrough", pro, false);
  
              j++;
              //if progress callback, give information of assets loaded, total and percent
              if (onprogress)
                  onprogress({
                      loaded: j,
                      total: total,
                      percent: (j / total) * 100,
                      src: src
                  });
  
              if (j === total && oncomplete) oncomplete();
          }
  
          //Error function
          function err() {
              var src = this.src;
              if (onerror)
                  onerror({
                      loaded: j,
                      total: total,
                      percent: (j / total) * 100,
                      src: src
                  });
  
              j++;
              if (j === total && oncomplete) oncomplete();
          }
  
          for (type in data) {
              for (asset in data[type]) {
                  if (!data[type].hasOwnProperty(asset)) continue; // maintain compatibility to other frameworks while iterating array
  
                  current = data[type][asset];
                  obj = null;
  
                  if (type === "audio") {
                      current =
                          typeof current === "object"
                              ? current
                              : { "": current + "" };
  
                      // Disable (Don't make functions in a loop) warning
                      // jshint -W083
                      var files = Object.keys(current).filter(function(key) {
                          var fileUrl = getFilePath(type, current[key]);
  
                          if (
                              !isAsset(fileUrl) &&
                              isSupportedAudio(current[key]) &&
                              !Crafty.audio.sounds[asset]
                          )
                              return shortURLOf(fileUrl);
                      });
                      // jshint +W083
  
                      if (files[0]) obj = Crafty.audio.add(asset, files);
                      //extract actual audio obj if audio creation was successfull
                      if (obj) obj = obj.obj;
  
                      //addEventListener is supported on IE9 , Audio as well
                      if (obj && obj.addEventListener)
                          obj.addEventListener("canplaythrough", pro, false);
                  } else {
                      asset = type === "sprites" ? asset : current;
  
                      fileUrl = getFilePath(type, asset);
  
                      if (!isAsset(fileUrl) && isValidImage(asset)) {
                          obj = new Image();
                          fileUrl = shortURLOf(fileUrl);
  
                          if (type === "sprites")
                              Crafty.sprite(
                                  current.tile,
                                  current.tileh,
                                  fileUrl,
                                  current.map,
                                  current.paddingX,
                                  current.paddingY,
                                  current.paddingAroundBorder
                              );
  
                          Crafty.asset(fileUrl, obj);
  
                          onImgLoad(obj, fileUrl);
                      }
                  }
  
                  if (obj) {
                      obj.onerror = err;
                  } else {
                      err.call({ src: fileUrl });
                  }
              }
          }
  
          // If we aren't trying to handle *any* of the files, that's as complete as it gets!
          if (total === 0 && oncomplete) oncomplete();
      }