[
  {
    "path": "css/style.css",
    "content": "html, body {\n  width: 100%;\n  height: 100%;\n  margin: 0px;\n  overflow: hidden;\n}\n\ncanvas {\n  position: absolute;\n  cursor: crosshair;\n  -webkit-user-select: none;\n}\n\n/* ----- INFO & SHARING -----  */\n\n#back {\n    font-family: Helvetica, Arial, \"Lucida Grande\";            \n    position: fixed;\n    top: 7px;\n    width: 190px;\n    height: 36px;\n    box-shadow: 0px 0px 4px 0px #888;\n    line-height: 10px;\n    left: -153px;\n    background: #fff;\n    z-index: 1000;\n    \n    -webkit-transition: left 250ms; \n       -moz-transition: left 250ms; \n         -o-transition: left 250ms;\n          -ms-trantion: left 250ms;\n            transition: left 250ms;\n}\n\n#back.open {\n    left: 0px;\n}\n\n#back a {\n    color: #1C86EE;\n    margin-left: 0px;\n    margin-right: 0px;\n    padding: 13px;\n    padding-right: 0px;\n    position: absolute;\n}\n\n#back a:hover {\n    color: hotpink; \n}\n\n#back span {\n    font-size: 50px;\n    line-height: 0px;\n    color: #444;\n    text-decoration: none;\n    font-family: Times;\n    position: absolute;\n    right: 10px;\n    top: 17px;\n    -webkit-margin-before: -3px;\n}\n\n#info-tab * {\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box\n}\n\n#info-tab {\n\n    font-family: Helvetica, Arial, \"Lucida Grande\";\n\n    -webkit-box-shadow: 0px 0px 4px 0px #888;\n    box-shadow: 0px 0px 4px 0px #888;\n\n    -webkit-transition: left 300ms, -webkit-box-shadow 500ms; \n       -moz-transition: left 300ms, -moz-box-shadow 500ms; \n         -o-transition: left 300ms, -o-box-shadow 500ms;\n          -ms-trantion: left 300ms, -ms-box-shadow 500ms;\n            transition: left 300ms, box-shadow 500ms;\n\n    background-color: #ffffff;\n    height: 150px;\n    position: fixed;\n    padding-left: 5px;\n    line-height: 36px;\n    width: 630px;\n    margin-top: 11px;\n    font-size: 17px;\n    color: #444;\n    left: -598px;\n    z-index: 1000;\n    top: 40px;\n}\n\n#info-tab.open {\n    left: 0px;\n}\n\n#info-tab.highlight {\n     -webkit-box-shadow: 0px 0px 12px 0px #222;\n    box-shadow: 0px 0px 12px 0px #222;   \n}\n\n#info-tab.open #title {\n    opacity: 0;\n}\n\n\n#info-tab #title {\n    -webkit-transition: opacity 300ms; \n       -moz-transition: opacity 300ms; \n         -o-transition: opacity 300ms;\n          -ms-trantion: opacity 300ms;\n            transition: opacity 300ms; \n\n\n    -webkit-transform: rotate( 90deg ) translateZ(0px); \n       -moz-transform: rotate( 90deg ) translateZ(0px); \n        -ms-transform: rotate( 90deg ) translateZ(0px); \n         -o-transform: rotate( 90deg ) translateZ(0px); \n            transform: rotate( 90deg ) translateZ(0px);\n\n    -webkit-transform-origin: 100% 0%;\n       -moz-transform-origin: 100% 0%;\n        -ms-transform-origin: 100% 0%;\n         -o-transform-origin: 100% 0%;\n            transform-origin: 100% 0%;\n\n    float: right;\n    width: 150px;\n    height: 30px;\n    position: absolute;\n    text-align: center;\n    right: 0px;\n    bottom: -30px;\n}\n\n#info-tab .info {\n    margin-left: 18px;\n    font-size: 14px;\n\n    line-height: 16px;\n    height: 110px;\n    width: 282px;\n    float: left;\n    padding-right: 18px;\n    margin-top: 20px;\n    margin-bottom: 20px;\n    color: #111;\n    margin-right: 5px;\n    position: relative;\n}\n\n#info-tab .first {\n    border-right: 1px solid #ddd;   \n}\n\n#info-tab p {\n    position: absolute;\n    bottom: 0px;\n    font-size: 12px;\n    margin: 0px;\n}\n\n#info-tab a {\n    color: #1C86EE;\n}\n\n#info-tab a:hover {\n    color: hotpink;\n}\n\n#info-tab iframe {\n    vertical-align: bottom;\n}"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Texter - Draw with Words</title>\n    <meta name=\"description\" content=\"A creative tool that allows you to draw with words.\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\n    <!-- CSS -->\n    <link href=\"css/style.css\" rel=\"stylesheet\" type=\"text/css\" />\n\n    <!-- JS -->\n    <script type=\"text/javascript\" src=\"js/libs/dat.gui.min.js\"></script>\n    <script type=\"text/javascript\" src=\"js/texter.js\"></script>\n  </head>\n\n  <body>\n    <!-- Header -->\n    <div id=\"info-tab\">\n      <div class=\"info first\">\n        <b>Texter</b> is a little javascript experiment that lets you explore\n        your creativity by drawing with words. This app is an extension of a\n        demo from this <a href=\"http://generative-gestaltung.de/\">book</a>\n        <p>\n          Made by: <a href=\"http://tholman.com\"> Tim Holman </a> -\n          <a\n            href=\"http://twitter.com/twholman\"\n            title=\"You'll love my tweets, I promise ;)\"\n          >\n            @twholman\n          </a>\n        </p>\n      </div>\n      <div class=\"info\">\n        This has been made using <i> Javascript </i> and the HTML5\n        <i> canvas </i> element. You can find the source on\n        <a href=\"http://github.com/tholman/texter\">Github</a>. If you feel like supporting me, you can always <a href=\"https://ko-fi.com/tholman\" target=\"_blank\">buy me a coffee</a>.\n        <p>\n          <a\n            href=\"https://twitter.com/share\"\n            class=\"twitter-share-button\"\n            data-url=\"http://tholman.com/texter\"\n            data-text=\"Texter - Draw pictures with text! - by @twholman -\"\n          >\n            Tweet\n          </a>\n          <iframe\n            src=\"http://www.facebook.com/plugins/like.php?href=http%3A%2F%2Ftholman.com%2Fexperiments%2Fhtml5%2Ftexter&amp;send=false&amp;layout=button_count&amp;width=450&amp;show_faces=false&amp;action=like&amp;colorscheme=light&amp;font&amp;height=21\"\n            scrolling=\"no\"\n            frameborder=\"0\"\n            style=\"\n              border: none;\n              overflow: hidden;\n              width: 107px;\n              height: 21px;\n              margin-bottom: -1px;\n            \"\n            allowTransparency=\"true\"\n          ></iframe>\n        </p>\n      </div>\n      <div id=\"title\">Texter</div>\n    </div>\n    <div id=\"back\">\n      <a href=\"/experiments\"> More experiments </a> <span>&lsaquo;</span>\n    </div>\n    <script type=\"text/javascript\" src=\"js/header.js\"></script>\n\n    <!-- APP -->\n    <canvas id=\"canvas\"></canvas>\n    <script>\n      var texter = new Texter();\n      texter.initialize();\n\n      var gui = new dat.GUI();\n      gui\n        .add(texter, \"text\")\n        .name(\"Text\")\n        .onChange(function () {\n          texter.onTextChange();\n        });\n      gui.add(texter, \"minFontSize\", 3, 100).name(\"Minimum Size\");\n      gui.add(texter, \"maxFontSize\", 3, 400).name(\"Maximum Size\");\n      gui.add(texter, \"angleDistortion\", 0, 2).step(0.1).name(\"Random Angle\");\n      gui\n        .addColor(texter, \"textColor\")\n        .name(\"Text Color\")\n        .onChange(function (value) {\n          texter.applyNewColor(value);\n        });\n      gui\n        .addColor(texter, \"bgColor\")\n        .name(\"Background Color\")\n        .onChange(function (value) {\n          texter.setBackground(value);\n        });\n      gui.add(texter, \"clear\").name(\"Clear\");\n      gui.add(texter, \"save\").name(\"Save\");\n    </script>\n\n    <!-- Misc -->\n    <script>\n      !(function (d, s, id) {\n        var js,\n          fjs = d.getElementsByTagName(s)[0];\n        if (!d.getElementById(id)) {\n          js = d.createElement(s);\n          js.id = id;\n          js.src = \"http://platform.twitter.com/widgets.js\";\n          fjs.parentNode.insertBefore(js, fjs);\n        }\n      })(document, \"script\", \"twitter-wjs\");\n    </script>\n    <script type=\"text/javascript\">\n      var _gaq = _gaq || [];\n      _gaq.push([\"_setAccount\", \"UA-22825241-1\"]);\n      _gaq.push([\"_trackPageview\"]);\n\n      (function () {\n        var ga = document.createElement(\"script\");\n        ga.type = \"text/javascript\";\n        ga.async = true;\n        ga.src =\n          (\"https:\" == document.location.protocol\n            ? \"https://ssl\"\n            : \"http://www\") + \".google-analytics.com/ga.js\";\n        var s = document.getElementsByTagName(\"script\")[0];\n        s.parentNode.insertBefore(ga, s);\n      })();\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "js/header.js",
    "content": "var headers = [\n    document.getElementById(\"back\"),\n    document.getElementById(\"info-tab\"),\n];\nvar headerMouseDown = false;\nvar headerToggleTimeOut = [];\n\ndocument.addEventListener(\n    \"mousedown\",\n    function () {\n        headerMouseDown = true;\n    },\n    false\n);\n\ndocument.addEventListener(\n    \"mouseup\",\n    function () {\n        headerMouseDown = false;\n    },\n    false\n);\n\nfor (var i = 0; i < headers.length; i++) {\n    headerToggleTimeOut.push(-1);\n\n    headers[i].addEventListener(\n        \"mouseover\",\n        function () {\n            if (!headerMouseDown) {\n                interval = clearInterval(interval);\n\n                var _this = this;\n\n                clearTimeout(headerToggleTimeOut);\n\n                headerToggleTimeOut[i] = setTimeout(function () {\n                    _this.setAttribute(\"class\", \"open\");\n                }, 50);\n            }\n        },\n        false\n    );\n\n    headers[i].addEventListener(\n        \"mouseout\",\n        function () {\n            var _this = this;\n\n            clearTimeout(headerToggleTimeOut);\n\n            headerToggleTimeOut[i] = setTimeout(function () {\n                _this.setAttribute(\"class\", \"\");\n            }, 50);\n        },\n        false\n    );\n}\n\nfunction pulseHeader() {\n    if (interval == undefined) {\n        return;\n    }\n    headers[1].setAttribute(\"class\", \"highlight\");\n\n    setTimeout(function () {\n        if (interval == undefined) {\n            return;\n        }\n        headers[1].setAttribute(\"class\", \"\");\n\n        setTimeout(function () {\n            if (interval == undefined) {\n                return;\n            }\n            headers[1].setAttribute(\"class\", \"highlight\");\n\n            setTimeout(function () {\n                if (interval == undefined) {\n                    return;\n                }\n                headers[1].setAttribute(\"class\", \"\");\n            }, 400);\n        }, 400);\n    }, 400);\n}\n\nvar interval = setInterval(function () {\n    pulseHeader();\n}, 10000);\n"
  },
  {
    "path": "js/texter.js",
    "content": "/*\n *  Texter - Drawing with Text.\n *  - Ported from demo in Generative Design book - http://www.generative-gestaltung.de\n *  - generative-gestalung.de original licence: http://www.apache.org/licenses/LICENSE-2.0\n *\n *  - Modified and maintained by Tim Holman - tholman.com - @twholman\n */\n\nfunction Texter() {\n  var _this = this;\n\n  // Application variables\n  position = { x: 0, y: window.innerHeight / 2 };\n  textIndex = 0;\n  this.textColor = \"#000000\";\n  this.bgColor = \"#ffffff\";\n  this.minFontSize = 8;\n  this.maxFontSize = 300;\n  this.angleDistortion = 0.01;\n\n  var queryString = window.location.search;\n  var urlParams = new URLSearchParams(queryString);\n  var urlText = urlParams.get('text')\n\n  this.text = urlText || \n    \"There was a table set out under a tree in front of the house, and the March Hare and the Hatter were having tea at it: a Dormouse was sitting between them, fast asleep, and the other two were using it as a cushion, resting their elbows on it, and talking over its head. 'Very uncomfortable for the Dormouse,' thought Alice; 'only, as it's asleep, I suppose it doesn't mind.'\";\n\n  // Drawing Variables\n  canvas = null;\n  context = null;\n  mouse = { x: 0, y: 0, down: false };\n\n  bgCanvas = null;\n  bgContext = null;\n\n  this.initialize = function () {\n    canvas = document.getElementById(\"canvas\");\n    context = canvas.getContext(\"2d\");\n    canvas.width = window.innerWidth;\n    canvas.height = window.innerHeight;\n\n    canvas.addEventListener(\"mousemove\", onMove, false);\n    canvas.addEventListener(\"mousedown\", onDown, false);\n    canvas.addEventListener(\"mouseup\", onUp, false);\n    canvas.addEventListener(\"mouseout\", onUp, false);\n\n    canvas.addEventListener(\"touchstart\", onDown, false);\n    canvas.addEventListener(\"touchmove\", onMove, false);\n    canvas.addEventListener(\"touchend\", onUp, false);\n    canvas.addEventListener(\"touchcancel\", onUp, false);\n\n    bgCanvas = document.createElement(\"canvas\");\n    bgContext = bgCanvas.getContext(\"2d\");\n    bgCanvas.width = canvas.width;\n    bgCanvas.height = canvas.height;\n    _this.setBackground(_this.bgColor);\n\n    window.onresize = function (event) {\n      canvas.width = window.innerWidth;\n      canvas.height = window.innerHeight;\n      bgCanvas.width = window.innerWidth;\n      bgCanvas.height = window.innerHeight;\n      _this.setBackground(_this.bgColor);\n      _this.clear();\n    };\n\n    update();\n  };\n\n  var update = function () {\n    requestAnimationFrame(update);\n    draw();\n  };\n\n  var draw = function () {\n    if (mouse.down) {\n      var newDistance = distance(position, mouse);\n      var fontSize = _this.minFontSize + newDistance / 2;\n\n      if (fontSize > _this.maxFontSize) {\n        fontSize = _this.maxFontSize;\n      }\n\n      var letter = _this.text[textIndex];\n      var stepSize = textWidth(letter, fontSize);\n\n      if (newDistance > stepSize) {\n        var angle = Math.atan2(mouse.y - position.y, mouse.x - position.x);\n\n        context.font = fontSize + \"px Georgia\";\n\n        context.save();\n        context.translate(position.x, position.y);\n        context.rotate(\n          angle +\n            (Math.random() * (_this.angleDistortion * 2) -\n              _this.angleDistortion)\n        );\n        context.fillText(letter, 0, 0);\n        context.restore();\n\n        textIndex++;\n        if (textIndex > _this.text.length - 1) {\n          textIndex = 0;\n        }\n\n        position.x = position.x + Math.cos(angle) * stepSize;\n        position.y = position.y + Math.sin(angle) * stepSize;\n      }\n    }\n  };\n\n  var distance = function (pt, pt2) {\n    var xs = 0;\n    var ys = 0;\n\n    xs = pt2.x - pt.x;\n    xs = xs * xs;\n\n    ys = pt2.y - pt.y;\n    ys = ys * ys;\n\n    return Math.sqrt(xs + ys);\n  };\n\n  var onDown = function (event) {\n    const eventObject = event.touches && event.touches.item(0) || event\n    mouse.down = true;\n    position.x = eventObject.pageX;\n    position.y = eventObject.pageY;\n    mouse.x = eventObject.pageX;\n    mouse.y = eventObject.pageY;\n  };\n\n  var onUp = function () {\n    mouse.down = false;\n  };\n\n  var onMove = function (event) {\n    const eventObject = event.touches && event.touches.item(0) || event\n    mouse.x = eventObject.pageX;\n    mouse.y = eventObject.pageY;\n    draw();\n  };\n\n  var textWidth = function (string, size) {\n    context.font = size + \"px Georgia\";\n\n    if (context.fillText) {\n      return context.measureText(string).width;\n    } else if (context.mozDrawText) {\n      return context.mozMeasureText(string);\n    }\n  };\n\n  this.clear = function () {\n    canvas.width = canvas.width;\n    context.fillStyle = _this.textColor;\n  };\n\n  this.applyNewColor = function (value) {\n    _this.textColor = value;\n    context.fillStyle = _this.textColor;\n  };\n\n  this.setBackground = function (value) {\n    _this.bgColor = value;\n    canvas.style.backgroundColor = value;\n  };\n\n  this.onTextChange = function () {\n    textIndex = 0;\n  };\n\n  this.save = function () {\n    // Prepare the background canvas's color\n    bgContext.rect(0, 0, bgCanvas.width, bgCanvas.height);\n    bgContext.fillStyle = _this.bgColor;\n    bgContext.fill();\n\n    // Draw the front canvas onto the bg canvas\n    bgContext.drawImage(canvas, 0, 0);\n\n    // Open in a new window\n    window.open(bgCanvas.toDataURL(\"image/png\"), \"mywindow\");\n  };\n}\n"
  }
]