packages feed

linden (empty) → 0

raw patch · 32 files changed

+2897/−0 lines, 32 filesdep +SHAdep +aesondep +basesetup-changedbinary-added

Dependencies added: SHA, aeson, base, bifunctors, bytestring, containers, deepseq, delay, exceptions, filepath, hashable, hedis, http-types, lens, linden, monad-supply, mtl, random-fu, rosezipper, text, time, unordered-containers, uuid, wai, wai-app-static, wai-cors, wai-extra, warp, zlib

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2016, davean++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of davean nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Linden.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE OverloadedStrings #-}+module Main where++import Data.Text (Text)+import Data.Tree+import Control.Monad+import Control.Monad.Trans+import Control.Monad.Reader+import Data.Random.Distribution.Normal+import Data.Hashable (hash)++import qualified Network.Wai.Handler.Warp as Warp++import Linden.Types+import Linden.RuleDSL+import Linden.Server+import Linden.Store.Memory++import Paths_linden++rule :: ReaderT FilePath IO Rule+rule = do+    basicBranch <- readBranchSym [hash ("basic"::Text)] "branch"+    return . compileRules . noDup $ do+      b <- curBranch+      --cs <- offspring <$> ask+      let s = bScale b+      angSum <- angleSum+      ls <- lightCones+      doNothing 100 -- Doing nothing is always an option+      when (not . null $ ls) $ do+        when (s==1) $ do+          addBranch 50 basicBranch 0 tscl 1+        when (s==tscl) $ do+          addBranch 50 basicBranch 0 tscl 1+          a <- lift . lift $ normal 20 10+          when (angSum < 180) $ addBranch 1 basicBranch a tscl 1+          when (angSum > -180) $ addBranch 1 basicBranch (-a) tscl 1+  where+    tscl = 1++axiom :: AxiomSource+axiom u t =+  return . LS u t 2 [Light 1 (0, 300) (-90) 90 0.7] .+         Just . PrettyJSTree $ Node (Branch 0 (Just "branch.png") (2, 0) (2, 19) 0 1 True True (map hash ["basic"::Text]) Nothing 0.5) []++main :: IO ()+main = do+  bp <- getDataDir+  ms <- memStore+  rule' <- return <$> runReaderT rule bp+  Warp.runEnv 8080 $ lindenApp bp ms "87fhn34wdfd2" 5.0 axiom rule' (return "")
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ art/add-light.png view

binary file changed (absent → 560 bytes)

+ art/add-light.svg view
@@ -0,0 +1,257 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<!-- Created with Inkscape (http://www.inkscape.org/) -->++<svg+   xmlns:dc="http://purl.org/dc/elements/1.1/"+   xmlns:cc="http://creativecommons.org/ns#"+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"+   xmlns:svg="http://www.w3.org/2000/svg"+   xmlns="http://www.w3.org/2000/svg"+   xmlns:xlink="http://www.w3.org/1999/xlink"+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"+   width="40"+   height="40"+   viewBox="0 0 39.999999 39.999999"+   id="svg2"+   version="1.1"+   inkscape:version="0.91 r13725"+   sodipodi:docname="add-light.svg"+   inkscape:export-filename="/home/chromakode/dev/xkcd/2016/art/add-light.png"+   inkscape:export-xdpi="180"+   inkscape:export-ydpi="180">+  <defs+     id="defs4" />+  <sodipodi:namedview+     id="base"+     pagecolor="#ffffff"+     bordercolor="#666666"+     borderopacity="1.0"+     inkscape:pageopacity="0"+     inkscape:pageshadow="2"+     inkscape:zoom="22.627417"+     inkscape:cx="20.870016"+     inkscape:cy="22.55294"+     inkscape:document-units="px"+     inkscape:current-layer="layer1"+     showgrid="true"+     units="px"+     borderlayer="true"+     inkscape:window-width="2560"+     inkscape:window-height="1440"+     inkscape:window-x="0"+     inkscape:window-y="0"+     inkscape:window-maximized="0">+    <inkscape:grid+       type="xygrid"+       id="grid4324" />+  </sodipodi:namedview>+  <metadata+     id="metadata7">+    <rdf:RDF>+      <cc:Work+         rdf:about="">+        <dc:format>image/svg+xml</dc:format>+        <dc:type+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />+        <dc:title></dc:title>+      </cc:Work>+    </rdf:RDF>+  </metadata>+  <g+     inkscape:label="Layer 1"+     inkscape:groupmode="layer"+     id="layer1"+     transform="translate(0,-1012.3622)">+    <rect+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"+       id="rect4203"+       width="12.931219"+       height="3.4205158"+       x="3.017005"+       y="1041.5504" />+    <rect+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"+       id="rect4203-3"+       width="12.931219"+       height="3.4205158"+       x="1036.7952"+       y="-11.192872"+       transform="matrix(0,1,-1,0,0,0)" />+    <image+       y="820.42047"+       x="530.83942"+       id="image4321"+       xlink:href="data:image/png;base64,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"+       preserveAspectRatio="none"+       height="61.498058"+       width="35.114033"+       transform="matrix(0.85827999,0.51318171,-0.51318171,0.85827999,0,0)" />+  </g>+</svg>
+ art/lamp-45.png view

binary file changed (absent → 9447 bytes)

+ art/light.png view

binary file changed (absent → 1804 bytes)

+ art/loading.png view

binary file changed (absent → 1479 bytes)

+ art/remove.png view

binary file changed (absent → 365 bytes)

+ art/remove.svg view
@@ -0,0 +1,86 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<!-- Created with Inkscape (http://www.inkscape.org/) -->++<svg+   xmlns:dc="http://purl.org/dc/elements/1.1/"+   xmlns:cc="http://creativecommons.org/ns#"+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"+   xmlns:svg="http://www.w3.org/2000/svg"+   xmlns="http://www.w3.org/2000/svg"+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"+   width="40"+   height="40"+   viewBox="0 0 39.999999 39.999999"+   id="svg2"+   version="1.1"+   inkscape:version="0.91 r13725"+   sodipodi:docname="remove.svg"+   inkscape:export-filename="/home/chromakode/dev/xkcd/2016/art/remove.png"+   inkscape:export-xdpi="180"+   inkscape:export-ydpi="180">+  <defs+     id="defs4" />+  <sodipodi:namedview+     id="base"+     pagecolor="#ffffff"+     bordercolor="#666666"+     borderopacity="1.0"+     inkscape:pageopacity="0"+     inkscape:pageshadow="2"+     inkscape:zoom="22.627417"+     inkscape:cx="11.854404"+     inkscape:cy="18.159802"+     inkscape:document-units="px"+     inkscape:current-layer="layer1"+     showgrid="true"+     units="px"+     borderlayer="true"+     inkscape:window-width="2560"+     inkscape:window-height="1440"+     inkscape:window-x="0"+     inkscape:window-y="0"+     inkscape:window-maximized="0">+    <inkscape:grid+       type="xygrid"+       id="grid4324" />+  </sodipodi:namedview>+  <metadata+     id="metadata7">+    <rdf:RDF>+      <cc:Work+         rdf:about="">+        <dc:format>image/svg+xml</dc:format>+        <dc:type+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />+        <dc:title></dc:title>+      </cc:Work>+    </rdf:RDF>+  </metadata>+  <g+     inkscape:label="Layer 1"+     inkscape:groupmode="layer"+     id="layer1"+     transform="translate(0,-1012.3622)">+    <g+       id="g4908"+       transform="matrix(0.8958018,0.8958018,-0.8958018,0.8958018,940.87405,84.126685)"+       style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1">+      <rect+         y="1041.5504"+         x="3.017005"+         height="3.4205158"+         width="24.510092"+         id="rect4203"+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />+      <rect+         transform="matrix(0,1,-1,0,0,0)"+         y="-16.982309"+         x="1031.0056"+         height="3.4205158"+         width="24.510092"+         id="rect4203-1"+         style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />+    </g>+  </g>+</svg>
+ assets/branch.png view

binary file changed (absent → 33 bytes)

+ assets/f235a44f2c511cdd.json view
@@ -0,0 +1,1 @@+{"attach": [], "root": {"y": 1, "x": 13}}
+ client/build/index.html view
@@ -0,0 +1,10 @@+<!doctype html>+<html>+<head>+<meta name="viewport" content="width=device-width, initial-scale=1">+</head>+<body>+<div id="comic"></div>+<script src="linden.js" data-endpoint="/" data-image-endpoint="/art/" data-title="title text"></script>+</body>+</html>
+ client/build/linden.js view
@@ -0,0 +1,25 @@+/*! linden by chromako.de */+!function(t){function e(i){if(n[i])return n[i].exports;var o=n[i]={exports:{},id:i,loaded:!1};return t[i].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){n(55),n(53),n(52),n(51),n(54),n(50),n(109),n(103);var i=n(45),o=document.getElementsByTagName("script"),r=o[o.length-1];window.garden=new i({name:"garden",el:document.getElementById("comic"),width:740,height:462,title:r.getAttribute("data-title"),endpoint:r.getAttribute("data-endpoint"),imgPrefix:r.getAttribute("data-image-endpoint"),spread:{reload:60},debug:"?debug"===location.search}).run(),window.addEventListener("keydown",function(t){8===t.which&&t.preventDefault()},!1)},function(t,e,n){var i=n(40)("wks"),o=n(29),r=n(2).Symbol,s="function"==typeof r;t.exports=function(t){return i[t]||(i[t]=s&&r[t]||(s?r:o)("Symbol."+t))}},function(t,e){var n=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(t,e){var n=t.exports={version:"2.2.1"};"number"==typeof __e&&(__e=n)},function(t,e,n){var i=n(10);t.exports=function(t){if(!i(t))throw TypeError(t+" is not an object!");return t}},function(t,e,n){var i=n(2),o=n(3),r=n(9),s=n(13),a=n(6),c="prototype",u=function(t,e,n){var h,l,d,A,f=t&u.F,p=t&u.G,g=t&u.S,v=t&u.P,m=t&u.B,y=p?i:g?i[e]||(i[e]={}):(i[e]||{})[c],b=p?o:o[e]||(o[e]={}),w=b[c]||(b[c]={});p&&(n=e);for(h in n)l=!f&&y&&void 0!==y[h],d=(l?y:n)[h],A=m&&l?a(d,i):v&&"function"==typeof d?a(Function.call,d):d,y&&s(y,h,d,t&u.U),b[h]!=d&&r(b,h,A),v&&w[h]!=d&&(w[h]=d)};i.core=o,u.F=1,u.G=2,u.S=4,u.P=8,u.B=16,u.W=32,u.U=64,u.R=128,t.exports=u},function(t,e,n){var i=n(17);t.exports=function(t,e,n){if(i(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,i){return t.call(e,n,i)};case 3:return function(n,i,o){return t.call(e,n,i,o)}}return function(){return t.apply(e,arguments)}}},function(t,e,n){t.exports=!n(14)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},function(t,e,n){var i=n(15),o=n(23);t.exports=n(7)?function(t,e,n){return i.f(t,e,o(1,n))}:function(t,e,n){return t[e]=n,t}},function(t,e){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)}},function(t,e){t.exports={}},function(t,e,n){var i=n(2),o=n(9),r=n(8),s=n(29)("src"),a="toString",c=Function[a],u=(""+c).split(a);n(3).inspectSource=function(t){return c.call(t)},(t.exports=function(t,e,n,a){var c="function"==typeof n;c&&(r(n,"name")||o(n,"name",e)),t[e]!==n&&(c&&(r(n,s)||o(n,s,t[e]?""+t[e]:u.join(String(e)))),t===i?t[e]=n:a?t[e]?t[e]=n:o(t,e,n):(delete t[e],o(t,e,n)))})(Function.prototype,a,function(){return"function"==typeof this&&this[s]||c.call(this)})},function(t,e){t.exports=function(t){try{return!!t()}catch(e){return!0}}},function(t,e,n){var i=n(4),o=n(35),r=n(43),s=Object.defineProperty;e.f=n(7)?Object.defineProperty:function(t,e,n){if(i(t),e=r(e,!0),i(n),o)try{return s(t,e,n)}catch(a){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(t[e]=n.value),t}},function(t,e,n){var i=n(22),o=n(20);t.exports=function(t){return i(o(t))}},function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,e,n){var i=n(6),o=n(22),r=n(28),s=n(27),a=n(58);t.exports=function(t,e){var n=1==t,c=2==t,u=3==t,h=4==t,l=6==t,d=5==t||l,A=e||a;return function(e,a,f){for(var p,g,v=r(e),m=o(v),y=i(a,f,3),b=s(m.length),w=0,x=n?A(e,b):c?A(e,0):void 0;b>w;w++)if((d||w in m)&&(p=m[w],g=y(p,w,v),t))if(n)x[w]=g;else if(g)switch(t){case 3:return!0;case 5:return p;case 6:return w;case 2:x.push(p)}else if(h)return!1;return l?-1:u||h?h:x}}},function(t,e,n){var i=n(11),o=n(1)("toStringTag"),r="Arguments"==i(function(){return arguments}()),s=function(t,e){try{return t[e]}catch(n){}};t.exports=function(t){var e,n,a;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(n=s(e=Object(t),o))?n:r?i(e):"Object"==(a=i(e))&&"function"==typeof e.callee?"Arguments":a}},function(t,e){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on  "+t);return t}},function(t,e,n){var i=n(10),o=n(2).document,r=i(o)&&i(o.createElement);t.exports=function(t){return r?o.createElement(t):{}}},function(t,e,n){var i=n(11);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==i(t)?t.split(""):Object(t)}},function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},function(t,e,n){var i=n(15).f,o=n(8),r=n(1)("toStringTag");t.exports=function(t,e,n){t&&!o(t=n?t:t.prototype,r)&&i(t,r,{configurable:!0,value:e})}},function(t,e,n){var i=n(40)("keys"),o=n(29);t.exports=function(t){return i[t]||(i[t]=o(t))}},function(t,e){var n=Math.ceil,i=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?i:n)(t)}},function(t,e,n){var i=n(26),o=Math.min;t.exports=function(t){return t>0?o(i(t),9007199254740991):0}},function(t,e,n){var i=n(20);t.exports=function(t){return Object(i(t))}},function(t,e){var n=0,i=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+i).toString(36))}},function(t,e){t.exports=function n(t,e){function i(){for(var i=0;i<t.children.length;i++)n(t.children[i],e)}t&&e(t.node,i)}},function(t,e){function n(t,e,n){return n>e?e>t?e:t>n?n:t:n>t?n:t>e?e:t}t.exports=n},function(t,e,n){var i=n(1)("unscopables"),o=Array.prototype;void 0==o[i]&&n(9)(o,i,{}),t.exports=function(t){o[i][t]=!0}},function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,e,n){t.exports=n(2).document&&document.documentElement},function(t,e,n){t.exports=!n(7)&&!n(14)(function(){return 7!=Object.defineProperty(n(21)("div"),"a",{get:function(){return 7}}).a})},function(t,e,n){"use strict";var i=n(37),o=n(5),r=n(13),s=n(9),a=n(8),c=n(12),u=n(64),h=n(24),l=n(73),d=n(1)("iterator"),A=!([].keys&&"next"in[].keys()),f="@@iterator",p="keys",g="values",v=function(){return this};t.exports=function(t,e,n,m,y,b,w){u(n,e,m);var x,E,C,D=function(t){if(!A&&t in O)return O[t];switch(t){case p:return function(){return new n(this,t)};case g:return function(){return new n(this,t)}}return function(){return new n(this,t)}},M=e+" Iterator",S=y==g,R=!1,O=t.prototype,k=O[d]||O[f]||y&&O[y],N=k||D(y),T=y?S?D("entries"):N:void 0,L="Array"==e?O.entries||k:k;if(L&&(C=l(L.call(new t)),C!==Object.prototype&&(h(C,M,!0),i||a(C,d)||s(C,d,v))),S&&k&&k.name!==g&&(R=!0,N=function(){return k.call(this)}),i&&!w||!A&&!R&&O[d]||s(O,d,N),c[e]=N,c[M]=v,y)if(x={values:S?N:D(g),keys:b?N:D(p),entries:T},w)for(E in x)E in O||r(O,E,x[E]);else o(o.P+o.F*(A||R),e,x);return x}},function(t,e){t.exports=!1},function(t,e,n){var i=n(74),o=n(33);t.exports=Object.keys||function(t){return i(t,o)}},function(t,e){e.f={}.propertyIsEnumerable},function(t,e,n){var i=n(2),o="__core-js_shared__",r=i[o]||(i[o]={});t.exports=function(t){return r[t]||(r[t]={})}},function(t,e,n){var i=n(14);t.exports=function(t,e){return!!t&&i(function(){e?t.call(null,function(){},1):t.call(null)})}},function(t,e,n){var i,o,r,s=n(6),a=n(60),c=n(34),u=n(21),h=n(2),l=h.process,d=h.setImmediate,A=h.clearImmediate,f=h.MessageChannel,p=0,g={},v="onreadystatechange",m=function(){var t=+this;if(g.hasOwnProperty(t)){var e=g[t];delete g[t],e()}},y=function(t){m.call(t.data)};d&&A||(d=function(t){for(var e=[],n=1;arguments.length>n;)e.push(arguments[n++]);return g[++p]=function(){a("function"==typeof t?t:Function(t),e)},i(p),p},A=function(t){delete g[t]},"process"==n(11)(l)?i=function(t){l.nextTick(s(m,t,1))}:f?(o=new f,r=o.port2,o.port1.onmessage=y,i=s(r.postMessage,r,1)):h.addEventListener&&"function"==typeof postMessage&&!h.importScripts?(i=function(t){h.postMessage(t+"","*")},h.addEventListener("message",y,!1)):i=v in u("script")?function(t){c.appendChild(u("script"))[v]=function(){c.removeChild(this),m.call(t)}}:function(t){setTimeout(s(m,t,1),0)}),t.exports={set:d,clear:A}},function(t,e,n){var i=n(10);t.exports=function(t,e){if(!i(t))return t;var n,o;if(e&&"function"==typeof(n=t.toString)&&!i(o=n.call(t)))return o;if("function"==typeof(n=t.valueOf)&&!i(o=n.call(t)))return o;if(!e&&"function"==typeof(n=t.toString)&&!i(o=n.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},function(t,e,n){function i(t){return t===p||t===g}function o(t){return y.test(t)}var r=n(97),s=0,a=1,c=2,u=3,h=4,l=5,d=6,A=7,f=8,p=9,g=10,v=11,m=12;t.exports=function(t,e){function n(t){return"function"==typeof t?t:"string"==typeof t?t:t&&"object"==typeof t?t:y("",t)}t=r(t),e||(e={});var y=e.concat||function(t,e){return String(t)+String(e)};return function(e){function r(t){var e=[];b===A&&(b=h);for(var n=0;n<t.length;n++){var o=t.charAt(n);b===a&&"<"===o?(w.length&&e.push([a,w]),w="",b=c):">"!==o||i(b)?b===a?w+=o:b===c&&/\s/.test(o)?(e.push([c,w]),w="",b=h):b===c?w+=o:b===h&&/[\w-]/.test(o)?(b=l,w=o):b===h&&/\s/.test(o)?(w.length&&e.push([l,w]),e.push([m])):b===l&&/\s/.test(o)?(e.push([l,w]),w="",b=d):b===l&&"="===o?(e.push([l,w],[v]),w="",b=A):b===l?w+=o:b!==d&&b!==h||"="!==o?b!==d&&b!==h||/\s/.test(o)?b===A&&'"'===o?b=g:b===A&&"'"===o?b=p:b===g&&'"'===o?(e.push([f,w],[m]),w="",b=h):b===p&&"'"===o?(e.push([f,w],[m]),w="",b=h):b!==A||/\s/.test(o)?b===f&&/\s/.test(o)?(e.push([m],[f,w]),w="",b=h):b!==f&&b!==p&&b!==g||(w+=o):(b=f,n--):(e.push([m]),/[\w-]/.test(o)?(w+=o,b=l):b=h):(e.push([v]),b=A):(b===c?e.push([c,w]):b===l?e.push([l,w]):b===f&&w.length&&e.push([f,w]),e.push([u]),w="",b=a)}return b===a&&w.length?(e.push([a,w]),w=""):b===f&&w.length?(e.push([f,w]),w=""):b===g&&w.length?(e.push([f,w]),w=""):b===p&&w.length?(e.push([f,w]),w=""):b===l&&(e.push([l,w]),w=""),e}for(var b=a,w="",x=arguments.length,E=[],C=0;C<e.length;C++)if(x-1>C){var D=arguments[C+1],M=r(e[C]),S=b;S===g&&(S=f),S===p&&(S=f),S===A&&(S=f),S===h&&(S=l),M.push([s,S,D]),E.push.apply(E,M)}else E.push.apply(E,r(e[C]));for(var R=[null,{},[]],O=[[R,-1]],C=0;C<E.length;C++){var k=O[O.length-1][0],M=E[C],N=M[0];if(N===c&&/^\//.test(M[1])){var T=O[O.length-1][1];O.length>1&&(O.pop(),O[O.length-1][0][2][T]=t(k[0],k[1],k[2].length?k[2]:void 0))}else if(N===c){var L=[M[1],{},[]];k[2].push(L),O.push([L,k[2].length-1])}else if(N===l||N===s&&M[1]===l){for(var I,U="";C<E.length;C++)if(E[C][0]===l)U=y(U,E[C][1]);else{if(E[C][0]!==s||E[C][1]!==l)break;if("object"!=typeof E[C][2]||U)U=y(U,E[C][2]);else for(I in E[C][2])E[C][2].hasOwnProperty(I)&&!k[1][I]&&(k[1][I]=E[C][2][I])}E[C][0]===v&&C++;for(var B=C;C<E.length;C++)if(E[C][0]===f||E[C][0]===l)k[1][U]?k[1][U]=y(k[1][U],E[C][1]):k[1][U]=n(E[C][1]);else{if(E[C][0]!==s||E[C][1]!==f&&E[C][1]!==l){!U.length||k[1][U]||C!==B||E[C][0]!==u&&E[C][0]!==m||(k[1][U]=U.toLowerCase());break}k[1][U]?k[1][U]=y(k[1][U],E[C][2]):k[1][U]=n(E[C][2])}}else if(N===l)k[1][M[1]]=!0;else if(N===s&&M[1]===l)k[1][M[2]]=!0;else if(N===u){if(o(k[0])&&O.length){var T=O[O.length-1][1];O.pop(),O[O.length-1][0][2][T]=t(k[0],k[1],k[2].length?k[2]:void 0)}}else if(N===s&&M[1]===a)void 0===M[2]||null===M[2]?M[2]="":M[2]||(M[2]=y("",M[2])),Array.isArray(M[2][0])?k[2].push.apply(k[2],M[2]):k[2].push(M[2]);else if(N===a)k[2].push(M[1]);else if(N!==v&&N!==m)throw new Error("unhandled: "+N)}if(R[2].length>1&&/^\s*$/.test(R[2][0])&&R[2].shift(),R[2].length>2||2===R[2].length&&/\S/.test(R[2][1]))throw new Error("multiple root elements must be wrapped in an enclosing tag");return Array.isArray(R[2][0])&&"string"==typeof R[2][0][0]&&Array.isArray(R[2][0][2])&&(R[2][0]=t(R[2][0][0],R[2][0][1],R[2][0][2])),R[2][0]}};var y=(Object.prototype.hasOwnProperty,RegExp("^("+["area","base","basefont","bgsound","br","col","command","embed","frame","hr","img","input","isindex","keygen","link","meta","param","source","track","wbr","animate","animateTransform","circle","cursor","desc","ellipse","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","font-face-format","font-face-name","font-face-uri","glyph","glyphRef","hkern","image","line","missing-glyph","mpath","path","polygon","polyline","rect","set","stop","tref","use","view","vkern"].join("|")+")(?:[.#][a-zA-Z0-9-￿_:-]+)*$"))},function(t,e,n){n(99);var i=n(47),o=n(30),r=n(48),s=function(t){this.opts=t,this.data=null,this.gardenId=null,this.gardenKey=null};s.prototype={record:function(t,e){(new Image).src="http://xkcd.com/events.gif?re="+t+"&ev="+e},log:function(){this.opts.debug&&console.log.apply(console,arguments)},_url:function(){var t=this.opts.endpoint+"run";return this.gardenId&&(t+="/"+this.gardenId),t},_connect:function(){var t=this._url();this.source=new EventSource(t),this.log("connecting to event source:",t),this.source.addEventListener("open",this.onConnect.bind(this),!1),this.source.addEventListener("error",this.onConnectionError.bind(this),!1),this.source.addEventListener("linden-key",this.onLindenKey.bind(this),!1),this.source.addEventListener("linden",this.onLindenUpdate.bind(this),!1),this.source.addEventListener("reload",this.onReload.bind(this),!1)},run:function(){try{this.opts.endpoint&&(location.hash&&(this.gardenId=location.hash.substr(1),this.gardenKey=localStorage["key-"+this.gardenId]),this._connect()),this.linden=new i({el:this.opts.el,width:this.opts.width,height:this.opts.height,title:this.opts.title,urlPrefix:this.opts.imgPrefix,loading:!0,readOnly:!this.gardenKey,debug:this.opts.debug,onLightChanging:this.handleLightChanging.bind(this),onSetLight:this.handleSetLight.bind(this),onRemoveLight:this.handleRemoveLight.bind(this),onPruneNode:this.handlePruneNode.bind(this)}),this.opts.data&&this.linden.update(this.opts.data),r(this.linden)}catch(t){throw this.record(this.opts.name,"js_error"),t}return this},onConnect:function(){this.record(this.opts.name,"connect_start"),this.dirtyLights={},this.inFlightRequests=0,this.linden.renderUI()},onConnectionError:function(t){this.log("connection error",t),this.record(this.opts.name,"connect_error"),this.linden.opts.loading=!0,this.linden.resetTree(),this.linden.renderUI(),this.source.url!==this._url()&&(this.source.close(),this._connect())},onLindenKey:function(t){this.log("linden key from EventSource",t);var e=JSON.parse(t.data);this.gardenId=location.hash=e.garden,this.gardenKey=localStorage["key-"+e.garden]=e.key,this.linden.opts.readOnly=!1,this.linden.renderUI()},_acceptUpdate:function(){if(this.inFlightRequests>0)return this.log("rejecting update, inflight:",this.inFlightRequests),!1;for(var t in this.dirtyLights)if(this.dirtyLights.hasOwnProperty(t)&&this.dirtyLights[t]===!0)return this.log("rejecting update, dirty light:",t),!1;return!0},_update:function(t){this._acceptUpdate()&&(this.data=t,this.linden.update(t))},onLindenUpdate:function(t){this.log("linden from EventSource",t),this._update(JSON.parse(t.data))},onReload:function(){var t=Math.round(Math.random()*this.opts.spread.reload);this.log("reloading in",t+5,"seconds"),setTimeout(function(){this.record(this.opts.name,"reloading"),setTimeout(function(){location.reload()},5e3)},1e3*t)},request:function(t,e){var n=function(){this.inFlightRequests--,this.log("inflight--",this.inFlightRequests)}.bind(this),i={method:"POST"};Object.assign(i,e),i.body=JSON.stringify(e.body),this.log("request",t,i);var o=fetch(t,i);return o.then(n,n),this.inFlightRequests++,this.log("inflight++",this.inFlightRequests),o},handleLightChanging:function(t){this.dirtyLights[t]=!0},handleSetLight:function(t,e){this.log("set light:",t,e),this.request(this.opts.endpoint+"edit",{body:Object.assign({tag:"EditLight",garden:this.data.garden,editKey:this.gardenKey,light:t},e)}).then(function(t){return t.json()}).then(function(t){this._update(Object.assign({},this.data,{lights:t}))}.bind(this)),delete this.dirtyLights[t]},_sendRemove:function(t){this.request(this.opts.endpoint+"edit",{body:{tag:"Del",garden:this.data.garden,editKey:this.gardenKey,node:t}})},handleRemoveLight:function(t){this.log("remove light:",t);var e=this.data.lights.filter(function(e){return e.id!==t});this._update(Object.assign({},this.data,{lights:e})),this._sendRemove(t)},handlePruneNode:function(t){this.log("prune node:",t);var e={children:[]},n=e;o(this.data.tree,function(e,i){var o=n;n={node:e,children:[]},e.id!==t&&o.children.push(n),i(),n=o}),this._update(Object.assign({},this.data,{tree:e.children[0]})),this._sendRemove(t)}},t.exports=s},function(t,e){function n(){this.imgs={},this.callbacks={}}n.prototype={get:function(t,e){var n=this.imgs[t];void 0===n?(this.imgs[t]=!0,this.callbacks[t]=[e],n=new Image,n.onload=this._onDone.bind(this,t,n,n),n.onerror=this._onDone.bind(this,t,n,!1),n.crossOrigin="anonymous",n.src=t):n===!0?this.callbacks[t].push(e):e&&e(t,n)},_onDone:function(t,e,n){this.imgs[t]=n;for(var i=0;i<this.callbacks[t].length;i++){var o=this.callbacks[t][i];o&&o(t,n)}delete this.callbacks[t]},getAll:function(t,e){function n(t,n){o[t]=n,i--,0===i&&e(o)}if(!t.length)return void e([]);for(var i=t.length,o={},r=0;r<t.length;r++)this.get(t[r],n)}},t.exports=n},function(t,e,n){function i(t,e){return Object.freeze(Object.defineProperties(t,{raw:{value:Object.freeze(e)}}))}function o(t){return Math.sin(t*O)}function r(t){return Math.cos(t*O)}function s(t,e){return(t%e+e)%e}function a(t,e,n,i,a,c,u){var h,l,d,A,f=s(t,360);90>=f?(h=i,l=u,d=c,A=a):180>=f?(h=c,l=a,d=i,A=u):270>=f?(h=c,l=u,d=i,A=a):(h=i,l=u,d=c,A=a),h-=e,l-=n,d-=e,A-=n;var p={x1:e+h*r(f)-l*o(f),y1:n+h*o(f)+l*r(f),x2:e+d*r(f)-A*o(f),y2:n+d*o(f)+A*r(f)};if(p.x2<p.x1){var g=p.x2;p.x2=p.x1,p.x1=g}if(p.y2<p.y1){var v=p.y2;p.y2=p.y1,p.y1=v}return p}function c(t,e,n,i){return"rgba("+100*t+"%, "+100*e+"%, "+100*n+"%, "+i+")"}function u(t){return.32>t?[1,.5+.5*t/.32,.5]:.64>t?[1,1,.5+.5*(t-.32)/.32]:[.75+.25*(1-t)/.36,.75+.25*(1-t)/.36,1]}function h(t,e,n){var i={x2:0,y2:0,scale:1,angle:0};M(t,function(t,s){var a=e[t.id],c=t.cache;c||(c=t.cache={}),c.scale=i.scale*t.scale,c.angle=i.angle+a.angle;var u=t.attach[0]-t.root[0],h=t.attach[1]-t.root[1];c.x1=i.x2,c.y1=i.y2,c.x2=c.x1+c.scale*(u*r(c.angle)+h*o(c.angle)),c.y2=c.y1+c.scale*(-u*o(c.angle)+h*r(c.angle));var l=i;i=c,n(t,s),i=l})}function l(t){var e=t.cache,n=e.x1-t.root[0],i=e.y1-t.root[1];return a(-e.angle,n+t.root[0],i+t.root[1],n,i,n+e.scale*e.width,i+e.scale*e.height)}function d(t,e,n,i){function s(t,n){return e[t.id][n]}var a=i.width,d=i.height,A=i.debug,f=i.urlPrefix,p="",g=i.imgCache,v=i.el.getContext("2d"),m=Number(i.el.getAttribute("data-density"));m>1&&(p="2x-");var y=[];M(t,function(t,e){t.img&&y.push(f+p+t.img),e()}),g.getAll(y,function(g){function y(t){for(var e=0;e<n.length;e++){var i=n[e];x.save(),x.beginPath(),x.moveTo(i.pos[0],i.pos[1]);var s=i.pos[0]+E*r(i.pointAngle-i.beamAngle/2),a=i.pos[1]+E*o(i.pointAngle-i.beamAngle/2);x.lineTo(s,a),x.arc(i.pos[0],i.pos[1],E,(i.pointAngle-i.beamAngle/2)*O,(i.pointAngle+i.beamAngle/2)*O),x.lineTo(i.pos[0],i.pos[1]),t(i),x.restore()}}v.save(),v.scale(m,-m),v.translate(0,-d),v.clearRect(0,0,a,d);var b;h(t,e,function(t,n){var i=t.cache;if(v.save(),v.rotate(s(t,"angle")*-O),v.scale(t.scale,t.scale),e._selected&&(t.id===e._selected.id&&(v.globalAlpha=.5),t.id===e._selected.circled&&(b=t)),t.img){var o=f+p+t.img,r=g[o];r&&(v.save(),v.scale(1/m,-1/m),v.translate(0,-r.height),v.drawImage(r,-t.root[0]*m,t.root[1]*m),v.restore(),i.width=r.width/m,i.height=r.height/m)}v.translate(t.attach[0]-t.root[0],t.attach[1]-t.root[1]),n(),v.restore()});var w=document.createElement("canvas");w.width=i.el.width,w.height=i.el.height;var x=w.getContext("2d");x.scale(m,m);var E=Math.sqrt(Math.pow(a,2)+Math.pow(d,2));if(y(function(t){var e=x.createRadialGradient(t.pos[0],t.pos[1],0,t.pos[0],t.pos[1],E),n=u(t.temp);e.addColorStop(0,c(n[0],n[1],n[2],1)),e.addColorStop(1,c(n[0],n[1],n[2],0)),x.fillStyle=e,x.globalCompositeOperation="lighten",x.fill()}),y(function(t){x.globalAlpha=.4,x.strokeStyle="black",x.stroke()}),v.save(),v.scale(1/m,1/m),v.globalCompositeOperation="source-atop",v.globalAlpha=.65,v.drawImage(w,0,0),v.globalAlpha=.25,v.globalCompositeOperation="destination-over",v.drawImage(w,0,0),v.restore(),b){b.cache;v.save(),v.beginPath();var C=l(b),D=C.x1+(C.x2-C.x1)/2,S=C.y1+(C.y2-C.y1)/2,k=Math.sqrt(Math.pow(C.x2-C.x1,2)+Math.pow(C.y2-C.y1,2));v.arc(D,S,k/2+6,0,R),v.strokeStyle="red",v.lineWidth=2,v.globalAlpha=.9,v.stroke(),v.restore()}A&&M(t,function(t,e){v.save();var n=t.cache;if(v.beginPath(),v.globalAlpha=.75,v.moveTo(n.x1,n.y1),v.lineTo(n.x2,n.y2),v.strokeStyle=t.proxyFor?"lightgreen":"magenta",v.lineWidth=1,v.stroke(),e(),v.restore(),n.width){var i=l(t);v.beginPath(),v.rect(i.x1,i.y1,i.x2-i.x1,i.y2-i.y1),v.strokeStyle="red",v.stroke()}}),v.restore(),i.onFinish&&i.onFinish()})}function A(t){this.el=t.el,this.opts=t,this.imgCache=new D,this._queued=!1,this._rendering=!1,this._afterNextRender=null,this.resetTree(),this.renderUI()}var f=i(["<div\n      className=","\n      style=","\n      onMouseDown=","\n    />"],["<div\n      className=","\n      style=","\n      onMouseDown=","\n    />"]),p=i(["\n        <div>\n          <div style=",'>\n            <Handle\n              className="rotate"\n              disabled=',"\n              onDrag=","\n              onRelease=","\n            ></Handle>\n          </div>\n          <div style=",'>\n            <Handle\n              className="color"\n              style=',"\n              disabled=","\n              onDrag=","\n              onRelease=","\n            ></Handle>\n          </div>\n        </div>\n      "],["\n        <div>\n          <div style=",'>\n            <Handle\n              className="rotate"\n              disabled=',"\n              onDrag=","\n              onRelease=","\n            ></Handle>\n          </div>\n          <div style=",'>\n            <Handle\n              className="color"\n              style=',"\n              disabled=","\n              onDrag=","\n              onRelease=","\n            ></Handle>\n          </div>\n        </div>\n      "]),g=i(["\n      <div\n        className=","\n        style=","\n        data-light-id=",'\n      >\n        <div>\n          <Handle\n            className="position"\n            disabled=',"\n            onGrab=","\n            onDrag=","\n            onRelease=","\n          ></Handle>\n        </div>\n        ","\n      </div>\n    "],["\n      <div\n        className=","\n        style=","\n        data-light-id=",'\n      >\n        <div>\n          <Handle\n            className="position"\n            disabled=',"\n            onGrab=","\n            onDrag=","\n            onRelease=","\n          ></Handle>\n        </div>\n        ","\n      </div>\n    "]),v=i(['\n        <div className="tools">\n          <button\n            className="add-light"\n            disabled=','\n            title="Create new light"\n            onClick=','\n          >+</button>\n          <button\n            className="remove"\n            disabled=','\n            title="Remove selected (delete key)"\n            onClick=',"\n          >x</button>\n        </div>\n      "],['\n        <div className="tools">\n          <button\n            className="add-light"\n            disabled=','\n            title="Create new light"\n            onClick=','\n          >+</button>\n          <button\n            className="remove"\n            disabled=','\n            title="Remove selected (delete key)"\n            onClick=',"\n          >x</button>\n        </div>\n      "]),m=i(["\n      <div\n        onMouseDown=","\n        onKeyDown=","\n        style=","\n        className=","\n        title=",'\n        tabIndex="1"\n      >\n        ',"\n        ",'\n        <div className="content">\n          <canvas\n            data-density=',"\n            width=","\n            height=","\n            style=","\n            onMouseDown=","\n          ></canvas>\n          ","\n        </div>\n      </div>\n    "],["\n      <div\n        onMouseDown=","\n        onKeyDown=","\n        style=","\n        className=","\n        title=",'\n        tabIndex="1"\n      >\n        ',"\n        ",'\n        <div className="content">\n          <canvas\n            data-density=',"\n            width=","\n            height=","\n            style=","\n            onMouseDown=","\n          ></canvas>\n          ","\n        </div>\n      </div>\n    "]),y=i(['<div><div className="spinner"></div><div className="spinner-text">loading</div>'],['<div><div className="spinner"></div><div className="spinner-text">loading</div>']),b=i(["<Light\n              id=","\n              disabled=","\n              selected=","\n              x=","\n              y=","\n              temp=","\n              pointAngle=","\n              beamAngle=","\n              onInteract=","\n              onMove=","\n              onRotate=","\n              onChangeColor=","\n              onSave=","\n            />"],["<Light\n              id=","\n              disabled=","\n              selected=","\n              x=","\n              y=","\n              temp=","\n              pointAngle=","\n              beamAngle=","\n              onInteract=","\n              onMove=","\n              onRotate=","\n              onChangeColor=","\n              onSave=","\n            />"]),w=n(94),x=n(49),E=n(31),C=n(100),D=n(46),M=n(30),S=Math.PI/2,R=2*Math.PI,O=S/90;w.component("Handle",{componentDidMount:function(){this._handleDragMove=this.handleDragMove.bind(this),this._handleDragUp=this.handleDragUp.bind(this),this.getDOMNode().firstChild.addEventListener("touchstart",this.handleDragDown.bind(this))},_stopListening:function(){window.removeEventListener("mousemove",this._handleDragMove,!1),window.removeEventListener("mouseup",this._handleDragUp,!1),window.removeEventListener("touchmove",this._handleDragMove,!1),window.removeEventListener("touchend",this._handleDragUp,!1)},componentWillUnmount:function(){this._stopListening()},_normalizeEvent:function(t){t.touches&&t.touches[0]&&(t.clientX=t.touches[0].clientX,t.clientY=t.touches[0].clientY)},handleDragDown:function(t){this.props.disabled||(document.documentElement.style.cursor="move",window.addEventListener("mousemove",this._handleDragMove,!1),window.addEventListener("mouseup",this._handleDragUp,!1),window.addEventListener("touchmove",this._handleDragMove,!1),window.addEventListener("touchend",this._handleDragUp,!1),this.props.onGrab&&(this._normalizeEvent(t),this.props.onGrab(t)),t.preventDefault())},handleDragUp:function(t){document.documentElement.style.cursor=null,this._stopListening(),this.props.onRelease&&(this._normalizeEvent(t),this.props.onRelease(t)),t.preventDefault()},handleDragMove:function(t){this.props.onDrag&&(this._normalizeEvent(t),this.props.onDrag(t)),t.preventDefault()},render:function(){return w(f,x("handle",this.props.className),this.props.style||"",this.handleDragDown.bind(this))}}),w.component("Light",{handleRotate:function(t){var e=this.getDOMNode().firstChild.getBoundingClientRect(),n=t.clientX-e.left,i=t.clientY-e.top,o=s(-Math.atan2(i,n)/O,360),r=Math.sqrt(Math.pow(n,2)+Math.pow(i,2));this.props.onRotate(o,E(0,95-3*(r-42),95))},handleRotateColor:function(t){var e=this.getDOMNode().firstChild.getBoundingClientRect(),n=t.clientX-e.left,i=t.clientY-e.top,o=E(90,s(-Math.atan2(i,n)/O-this.props.pointAngle,360),270)-90;this.props.onChangeColor(o/180)},render:function(){var t=C({left:this.props.x+"px",top:this.props.y+"px",transform:"rotate("+(-this.props.pointAngle-90)+"deg)"}),e="";if(this.props.selected){var n=42+E(0,(95-this.props.beamAngle)/3,95),i=C({position:"absolute",top:n+"px"}),s=180*this.props.temp,a=u(this.props.temp),h=C({position:"absolute",left:39*r(-s)+"px",top:39*o(-s)+"px"}),l=C({backgroundColor:c(a[0],a[1],a[2],1)});e=w(p,i,this.props.disabled,this.handleRotate.bind(this),this.props.onSave,h,l,this.props.disabled,this.handleRotateColor.bind(this),this.props.onSave)}return w(g,x("light",this.props.selected&&"selected"),t,this.props.id,this.props.disabled,this.props.onInteract,this.props.onMove,this.props.onSave,e)}}),A.prototype={STIFFNESS:.25,FRICTION:.1,MAX_LIGHTS:3,FPS:15,resetTree:function(){this.l=null,this.lights=[],this.state={_selected:null}},renderUI:function(){var t="";this.opts.readOnly||this.opts.loading||(t=w(v,this.lights.length===this.MAX_LIGHTS,this.handleCreateLight.bind(this),!this.state._selected,this.handleRemoveSelection.bind(this)));var e=C({width:this.opts.width+"px",height:this.opts.height+"px"}),n=window.devicePixelRatio>1?2:1,i=C({transformOrigin:"0 0",transform:"scale("+1/n+", "+1/n+")"}),o=x("frame",this.opts.loading&&"loading");w.render(w(m,this.handleMouseDown.bind(this),this.handleKeyDown.bind(this),e,o,this.opts.title,t,this.opts.loading?w(y):"",n,this.opts.width*n,this.opts.height*n,i,this.handleCanvasMouseDown.bind(this),this.lights.map(function(t){var e=Boolean(this.state._selected&&"light"===this.state._selected.kind&&this.state._selected.id===t.id);return w(b,t.id,this.opts.readOnly,e,t.pos[0],this.opts.height-t.pos[1],t.temp,t.pointAngle,t.beamAngle,this.handleLightInteract.bind(this,t.id),this.handleLightMove.bind(this,t.id),this.handleLightRotate.bind(this,t.id),this.handleLightChangeColor.bind(this,t.id),this.saveLight.bind(this,t.id))}.bind(this))),this.el)},update:function(t){this.l=t.tree;var e=0,n={};M(this.l,function(t,i){e++;var o=t.img||"proxy";n[o]=n[o]||{count:0},n[o].count++;var r=this.state[t.id];r||(r=this.state[t.id]={angle:t.angle,av:0}),r.node=t,i()}.bind(this)),this.lights=t.lights||[];var i=Date.now();this._afterNextRender=function(){if(this.opts.loading=!1,this.renderUI(),this._afterNextRender=null,this.opts.debug){var o=Date.now()-i;console.debug("updated. rendered",e,"nodes in",o,"ms","| supply:",t.supply),console.table(n)}}.bind(this),this.renderCanvas()},addWind:function(t,e,n){M(this.l,function(i,r){if(!i.rigid&&i.cache){var s=this.state[i.id],a=o(t-i.cache.angle),c=1-n+Math.random()*n;s.av+=i.waviness*c*(1/Math.sqrt(i.cache.scale))*a*e}r()}.bind(this)),this.queueStep()},queueStep:function(){this._queued||(setTimeout(this.step.bind(this),1e3/this.FPS),this._queued=Date.now())},step:function(){this.opts.beforeStep&&this.opts.beforeStep(),M(this.l,function(t,e){var n=this.state[t.id];n.av+=(1-t.waviness)*t.scale*this.STIFFNESS*(t.angle-n.angle),n.av+=-n.av*this.FRICTION,n.angle+=n.av,e()}.bind(this)),this.renderCanvas(),this._queued=!1,this.queueStep()},renderCanvas:function(){!this._rendering&&this.l&&(this._rendering=!0,d(this.l,this.state,this.lights,{width:this.opts.width,height:this.opts.height,el:this.el.querySelector("canvas"),urlPrefix:this.opts.urlPrefix,imgCache:this.imgCache,debug:this.opts.debug,onFinish:function(){this._afterNextRender&&this._afterNextRender(),this._rendering=!1}.bind(this)}))},handleMouseDown:function(){this.el.querySelector(".frame").focus()},_deselect:function(){this.state._selected=null,this.renderCanvas(),this.renderUI()},handleCanvasMouseDown:function(t){if(!this.opts.readOnly){this._deselect();var e=this.el.getBoundingClientRect(),n=t.clientX-e.left,i=this.opts.height-(t.clientY-e.top),o=null,r=Number.MAX_VALUE;if(M(this.l,function(t,e){var s=t.cache;if(s&&s.width&&!t.immutable){var a=l(t);if(a.x1<=n&&a.x2>=n&&a.y1<=i&&a.y2>=i){var c=(a.x2-a.x1)*(a.y2-a.y1);r>c&&(o=t,r=c)}}e()}),o){var s=o.proxyFor||o.id;this.state._selected={kind:"node",id:s,circled:o.id}}this.renderCanvas(),this.renderUI()}},handleKeyDown:function(t){46!==t.which&&8!==t.which||(this.handleRemoveSelection(),t.preventDefault())},handleLightInteract:function(t){this.opts.readOnly||(this.state._selected={kind:"light",id:t},this.renderCanvas(),this.renderUI())},handleLightMove:function(t,e){+var n=this.el.getBoundingClientRect(),i=E(0,e.clientX-n.left,this.opts.width),o=E(0,this.opts.height-(e.clientY-n.top),this.opts.height);this.setLight(t,{pos:[i,o]})},handleLightRotate:function(t,e,n){this.setLight(t,{pointAngle:e,beamAngle:n})},handleLightChangeColor:function(t,e){this.setLight(t,{temp:e})},setLight:function(t,e){this.opts.onLightChanging(t);var n=this.lights.findIndex(function(e){return e.id===t});-1!==n&&(Object.assign(this.lights[n],e),this.renderCanvas(),this.renderUI())},saveLight:function(t){var e=this.lights.findIndex(function(e){return e.id===t});-1!==e&&this.opts.onSetLight(t,this.lights[e])},handleCreateLight:function(){var t=this.opts.width*Math.random(),e=(this.opts.height+this.opts.height*Math.random())/2,n=this.opts.width/2,i=0;this.opts.onSetLight(null,{pos:[t,e],pointAngle:Math.atan2(i-e,n-t)/O,beamAngle:45+45*Math.random(),temp:.5})},handleRemoveSelection:function(){this.state._selected&&("node"===this.state._selected.kind?this.opts.onPruneNode(this.state._selected.id):"light"===this.state._selected.kind&&this.opts.onRemoveLight(this.state._selected.id),this._deselect())}},t.exports=A},function(t,e,n){var i=n(31);t.exports=function(t){function e(){var e=2*(Math.random()-.5);Math.random()>.9&&(n+=Math.random()-.5),o=i(60,o+2*(Math.random()-.5),120),n*=.995,t.addWind(o,.025*e+.25*n,.5)}var n=0,o=90;t.opts.beforeStep=e,e()}},function(t,e,n){var i,o;/*!+	  Copyright (c) 2016 Jed Watson.+	  Licensed under the MIT License (MIT), see+	  http://jedwatson.github.io/classnames+	*/+!function(){"use strict";function n(){for(var t=[],e=0;e<arguments.length;e++){var i=arguments[e];if(i){var o=typeof i;if("string"===o||"number"===o)t.push(i);else if(Array.isArray(i))t.push(n.apply(null,i));else if("object"===o)for(var s in i)r.call(i,s)&&i[s]&&t.push(s)}}return t.join(" ")}var r={}.hasOwnProperty;"undefined"!=typeof t&&t.exports?t.exports=n:(i=[],o=function(){return n}.apply(e,i),!(void 0!==o&&(t.exports=o)))}()},function(t,e,n){n(88),n(90),n(91),n(89),t.exports=n(3).Promise},function(t,e,n){n(82),t.exports=n(3).Array.filter},function(t,e,n){n(83),t.exports=n(3).Array.findIndex},function(t,e,n){n(85),t.exports=n(3).Array.map},function(t,e,n){n(86),t.exports=n(3).Date.now},function(t,e,n){n(87),t.exports=n(3).Object.assign},function(t,e){t.exports=function(t,e,n,i){if(!(t instanceof e)||void 0!==i&&i in t)throw TypeError(n+": incorrect invocation!");return t}},function(t,e,n){var i=n(16),o=n(27),r=n(80);t.exports=function(t){return function(e,n,s){var a,c=i(e),u=o(c.length),h=r(s,u);if(t&&n!=n){for(;u>h;)if(a=c[h++],a!=a)return!0}else for(;u>h;h++)if((t||h in c)&&c[h]===n)return t||h;return!t&&-1}}},function(t,e,n){var i=n(10),o=n(62),r=n(1)("species");t.exports=function(t,e){var n;return o(t)&&(n=t.constructor,"function"!=typeof n||n!==Array&&!o(n.prototype)||(n=void 0),i(n)&&(n=n[r],null===n&&(n=void 0))),new(void 0===n?Array:n)(e)}},function(t,e,n){var i=n(6),o=n(63),r=n(61),s=n(4),a=n(27),c=n(81);t.exports=function(t,e,n,u,h){var l,d,A,f=h?function(){return t}:c(t),p=i(n,u,e?2:1),g=0;if("function"!=typeof f)throw TypeError(t+" is not iterable!");if(r(f))for(l=a(t.length);l>g;g++)e?p(s(d=t[g])[0],d[1]):p(t[g]);else for(A=f.call(t);!(d=A.next()).done;)o(A,p,d.value,e)}},function(t,e){t.exports=function(t,e,n){var i=void 0===n;switch(e.length){case 0:return i?t():t.call(n);case 1:return i?t(e[0]):t.call(n,e[0]);case 2:return i?t(e[0],e[1]):t.call(n,e[0],e[1]);case 3:return i?t(e[0],e[1],e[2]):t.call(n,e[0],e[1],e[2]);case 4:return i?t(e[0],e[1],e[2],e[3]):t.call(n,e[0],e[1],e[2],e[3])}return t.apply(n,e)}},function(t,e,n){var i=n(12),o=n(1)("iterator"),r=Array.prototype;t.exports=function(t){return void 0!==t&&(i.Array===t||r[o]===t)}},function(t,e,n){var i=n(11);t.exports=Array.isArray||function(t){return"Array"==i(t)}},function(t,e,n){var i=n(4);t.exports=function(t,e,n,o){try{return o?e(i(n)[0],n[1]):e(n)}catch(r){var s=t["return"];throw void 0!==s&&i(s.call(t)),r}}},function(t,e,n){"use strict";var i=n(69),o=n(23),r=n(24),s={};n(9)(s,n(1)("iterator"),function(){return this}),t.exports=function(t,e,n){t.prototype=i(s,{next:o(1,n)}),r(t,e+" Iterator")}},function(t,e,n){var i=n(1)("iterator"),o=!1;try{var r=[7][i]();r["return"]=function(){o=!0},Array.from(r,function(){throw 2})}catch(s){}t.exports=function(t,e){if(!e&&!o)return!1;var n=!1;try{var r=[7],s=r[i]();s.next=function(){n=!0},r[i]=function(){return s},t(r)}catch(a){}return n}},function(t,e){t.exports=function(t,e){return{value:e,done:!!t}}},function(t,e,n){var i,o,r,s=n(2),a=n(42).set,c=s.MutationObserver||s.WebKitMutationObserver,u=s.process,h=s.Promise,l="process"==n(11)(u),d=function(){var t,e;for(l&&(t=u.domain)&&t.exit();i;)e=i.fn,e(),i=i.next;o=void 0,t&&t.enter()};if(l)r=function(){u.nextTick(d)};else if(c){var A=!0,f=document.createTextNode("");new c(d).observe(f,{characterData:!0}),r=function(){f.data=A=!A}}else r=h&&h.resolve?function(){h.resolve().then(d)}:function(){a.call(s,d)};t.exports=function(t){var e={fn:t,next:void 0};o&&(o.next=e),i||(i=e,r()),o=e}},function(t,e,n){"use strict";var i=n(38),o=n(72),r=n(39),s=n(28),a=n(22),c=Object.assign;t.exports=!c||n(14)(function(){var t={},e={},n=Symbol(),i="abcdefghijklmnopqrst";return t[n]=7,i.split("").forEach(function(t){e[t]=t}),7!=c({},t)[n]||Object.keys(c({},e)).join("")!=i})?function(t,e){for(var n=s(t),c=arguments.length,u=1,h=o.f,l=r.f;c>u;)for(var d,A=a(arguments[u++]),f=h?i(A).concat(h(A)):i(A),p=f.length,g=0;p>g;)l.call(A,d=f[g++])&&(n[d]=A[d]);return n}:c},function(t,e,n){var i=n(4),o=n(70),r=n(33),s=n(25)("IE_PROTO"),a=function(){},c="prototype",u=function(){var t,e=n(21)("iframe"),i=r.length,o=">";for(e.style.display="none",n(34).appendChild(e),e.src="javascript:",t=e.contentWindow.document,t.open(),t.write("<script>document.F=Object</script"+o),t.close(),u=t.F;i--;)delete u[c][r[i]];return u()};t.exports=Object.create||function(t,e){var n;return null!==t?(a[c]=i(t),n=new a,a[c]=null,n[s]=t):n=u(),void 0===e?n:o(n,e)}},function(t,e,n){var i=n(15),o=n(4),r=n(38);t.exports=n(7)?Object.defineProperties:function(t,e){o(t);for(var n,s=r(e),a=s.length,c=0;a>c;)i.f(t,n=s[c++],e[n]);return t}},function(t,e,n){var i=n(39),o=n(23),r=n(16),s=n(43),a=n(8),c=n(35),u=Object.getOwnPropertyDescriptor;e.f=n(7)?u:function(t,e){if(t=r(t),e=s(e,!0),c)try{return u(t,e)}catch(n){}return a(t,e)?o(!i.f.call(t,e),t[e]):void 0}},function(t,e){e.f=Object.getOwnPropertySymbols},function(t,e,n){var i=n(8),o=n(28),r=n(25)("IE_PROTO"),s=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=o(t),i(t,r)?t[r]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?s:null}},function(t,e,n){var i=n(8),o=n(16),r=n(57)(!1),s=n(25)("IE_PROTO");t.exports=function(t,e){var n,a=o(t),c=0,u=[];for(n in a)n!=s&&i(a,n)&&u.push(n);for(;e.length>c;)i(a,n=e[c++])&&(~r(u,n)||u.push(n));return u}},function(t,e,n){var i=n(13);t.exports=function(t,e,n){for(var o in e)i(t,o,e[o],n);return t}},function(t,e,n){var i=n(10),o=n(4),r=function(t,e){if(o(t),!i(e)&&null!==e)throw TypeError(e+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,e,i){try{i=n(6)(Function.call,n(71).f(Object.prototype,"__proto__").set,2),i(t,[]),e=!(t instanceof Array)}catch(o){e=!0}return function(t,n){return r(t,n),e?t.__proto__=n:i(t,n),t}}({},!1):void 0),check:r}},function(t,e,n){"use strict";var i=n(2),o=n(15),r=n(7),s=n(1)("species");t.exports=function(t){var e=i[t];r&&e&&!e[s]&&o.f(e,s,{configurable:!0,get:function(){return this}})}},function(t,e,n){var i=n(4),o=n(17),r=n(1)("species");t.exports=function(t,e){var n,s=i(t).constructor;return void 0===s||void 0==(n=i(s)[r])?e:o(n)}},function(t,e,n){var i=n(26),o=n(20);t.exports=function(t){return function(e,n){var r,s,a=String(o(e)),c=i(n),u=a.length;return 0>c||c>=u?t?"":void 0:(r=a.charCodeAt(c),55296>r||r>56319||c+1===u||(s=a.charCodeAt(c+1))<56320||s>57343?t?a.charAt(c):r:t?a.slice(c,c+2):(r-55296<<10)+(s-56320)+65536)}}},function(t,e,n){var i=n(26),o=Math.max,r=Math.min;t.exports=function(t,e){return t=i(t),0>t?o(t+e,0):r(t,e)}},function(t,e,n){var i=n(19),o=n(1)("iterator"),r=n(12);t.exports=n(3).getIteratorMethod=function(t){return void 0!=t?t[o]||t["@@iterator"]||r[i(t)]:void 0}},function(t,e,n){"use strict";var i=n(5),o=n(18)(2);i(i.P+i.F*!n(41)([].filter,!0),"Array",{filter:function(t){return o(this,t,arguments[1])}})},function(t,e,n){"use strict";var i=n(5),o=n(18)(6),r="findIndex",s=!0;r in[]&&Array(1)[r](function(){s=!1}),i(i.P+i.F*s,"Array",{findIndex:function(t){return o(this,t,arguments.length>1?arguments[1]:void 0)}}),n(32)(r)},function(t,e,n){"use strict";var i=n(32),o=n(66),r=n(12),s=n(16);t.exports=n(36)(Array,"Array",function(t,e){this._t=s(t),this._i=0,this._k=e},function(){var t=this._t,e=this._k,n=this._i++;return!t||n>=t.length?(this._t=void 0,o(1)):"keys"==e?o(0,n):"values"==e?o(0,t[n]):o(0,[n,t[n]])},"values"),r.Arguments=r.Array,i("keys"),i("values"),i("entries")},function(t,e,n){"use strict";var i=n(5),o=n(18)(1);i(i.P+i.F*!n(41)([].map,!0),"Array",{map:function(t){return o(this,t,arguments[1])}})},function(t,e,n){var i=n(5);i(i.S,"Date",{now:function(){return(new Date).getTime()}})},function(t,e,n){var i=n(5);i(i.S+i.F,"Object",{assign:n(68)})},function(t,e,n){"use strict";var i=n(19),o={};o[n(1)("toStringTag")]="z",o+""!="[object z]"&&n(13)(Object.prototype,"toString",function(){return"[object "+i(this)+"]"},!0)},function(t,e,n){"use strict";var i,o,r,s=n(37),a=n(2),c=n(6),u=n(19),h=n(5),l=n(10),d=(n(4),n(17)),A=n(56),f=n(59),p=(n(76).set,n(78)),g=n(42).set,v=n(67),m="Promise",y=a.TypeError,b=a.process,w=a[m],b=a.process,x="process"==u(b),E=function(){},C=!!function(){try{var t=w.resolve(1),e=(t.constructor={})[n(1)("species")]=function(t){t(E,E)};return(x||"function"==typeof PromiseRejectionEvent)&&t.then(E)instanceof e}catch(i){}}(),D=function(t,e){return t===e||t===w&&e===r},M=function(t){var e;return l(t)&&"function"==typeof(e=t.then)?e:!1},S=function(t){return D(w,t)?new R(t):new o(t)},R=o=function(t){var e,n;this.promise=new t(function(t,i){if(void 0!==e||void 0!==n)throw y("Bad Promise constructor");e=t,n=i}),this.resolve=d(e),this.reject=d(n)},O=function(t){try{t()}catch(e){return{error:e}}},k=function(t,e){if(!t._n){t._n=!0;var n=t._c;v(function(){for(var i=t._v,o=1==t._s,r=0,s=function(e){var n,r,s=o?e.ok:e.fail,a=e.resolve,c=e.reject,u=e.domain;try{s?(o||(2==t._h&&L(t),t._h=1),s===!0?n=i:(u&&u.enter(),n=s(i),u&&u.exit()),n===e.promise?c(y("Promise-chain cycle")):(r=M(n))?r.call(n,a,c):a(n)):c(i)}catch(h){c(h)}};n.length>r;)s(n[r++]);t._c=[],t._n=!1,e&&!t._h&&N(t)})}},N=function(t){g.call(a,function(){var e,n,i,o=t._v;if(T(t)&&(e=O(function(){x?b.emit("unhandledRejection",o,t):(n=a.onunhandledrejection)?n({promise:t,reason:o}):(i=a.console)&&i.error&&i.error("Unhandled promise rejection",o)}),t._h=x||T(t)?2:1),t._a=void 0,e)throw e.error})},T=function(t){if(1==t._h)return!1;for(var e,n=t._a||t._c,i=0;n.length>i;)if(e=n[i++],e.fail||!T(e.promise))return!1;return!0},L=function(t){g.call(a,function(){var e;x?b.emit("rejectionHandled",t):(e=a.onrejectionhandled)&&e({promise:t,reason:t._v})})},I=function(t){var e=this;e._d||(e._d=!0,e=e._w||e,e._v=t,e._s=2,e._a||(e._a=e._c.slice()),k(e,!0))},U=function(t){var e,n=this;if(!n._d){n._d=!0,n=n._w||n;try{if(n===t)throw y("Promise can't be resolved itself");(e=M(t))?v(function(){var i={_w:n,_d:!1};try{e.call(t,c(U,i,1),c(I,i,1))}catch(o){I.call(i,o)}}):(n._v=t,n._s=1,k(n,!1))}catch(i){I.call({_w:n,_d:!1},i)}}};C||(w=function(t){A(this,w,m,"_h"),d(t),i.call(this);try{t(c(U,this,1),c(I,this,1))}catch(e){I.call(this,e)}},i=function(t){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1},i.prototype=n(75)(w.prototype,{then:function(t,e){var n=S(p(this,w));return n.ok="function"==typeof t?t:!0,n.fail="function"==typeof e&&e,n.domain=x?b.domain:void 0,this._c.push(n),this._a&&this._a.push(n),this._s&&k(this,!1),n.promise},"catch":function(t){return this.then(void 0,t)}}),R=function(){var t=new i;this.promise=t,this.resolve=c(U,t,1),this.reject=c(I,t,1)}),h(h.G+h.W+h.F*!C,{Promise:w}),n(24)(w,m),n(77)(m),r=n(3)[m],h(h.S+h.F*!C,m,{reject:function(t){var e=S(this),n=e.reject;return n(t),e.promise}}),h(h.S+h.F*(s||!C),m,{resolve:function(t){if(t instanceof w&&D(t.constructor,this))return t;var e=S(this),n=e.resolve;return n(t),e.promise}}),h(h.S+h.F*!(C&&n(65)(function(t){w.all(t)["catch"](E)})),m,{all:function(t){var e=this,n=S(e),i=n.resolve,o=n.reject,r=O(function(){var n=[],r=0,s=1;f(t,!1,function(t){var a=r++,c=!1;n.push(void 0),s++,e.resolve(t).then(function(t){c||(c=!0,n[a]=t,--s||i(n))},o)}),--s||i(n)});return r&&o(r.error),n.promise},race:function(t){var e=this,n=S(e),i=n.reject,o=O(function(){f(t,!1,function(t){e.resolve(t).then(n.resolve,i)})});return o&&i(o.error),n.promise}})},function(t,e,n){"use strict";var i=n(79)(!0);n(36)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,e=this._t,n=this._i;return n>=e.length?{value:void 0,done:!0}:(t=i(e,n),this._i+=t.length,{value:t,done:!1})})},function(t,e,n){for(var i=n(84),o=n(13),r=n(2),s=n(9),a=n(12),c=n(1),u=c("iterator"),h=c("toStringTag"),l=a.Array,d=["NodeList","DOMTokenList","MediaList","StyleSheetList","CSSRuleList"],A=0;5>A;A++){var f,p=d[A],g=r[p],v=g&&g.prototype;if(v){v[u]||s(v,u,l),v[h]||s(v,h,p),a[p]=l;for(f in i)v[f]||o(v,f,i[f],!0)}}},function(t,e,n){e=t.exports=n(93)(),e.push([t.id,"#comic{position:relative;display:inline-block;outline:2px solid #000;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#comic .frame{background:#fff;-webkit-transition:background-color .5s ease;transition:background-color .5s ease}#comic .frame:focus{outline:none}#comic .frame .content{opacity:1;-webkit-transition:opacity .5s ease;transition:opacity .5s ease}#comic .frame.loading{background:#fff}#comic .frame.loading .content{opacity:0}#comic .spinner{position:absolute;left:50%;top:45%;width:50px;height:52px;margin-left:-30px;margin-top:-26px;background-image:url("+n(107)+");background-size:contain;-webkit-animation-name:rotate;animation-name:rotate;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-duration:2s;animation-duration:2s;-webkit-transform-origin:56% 50%;transform-origin:56% 50%}@-webkit-keyframes rotate{0%{-webkit-transform:rotateY(0);transform:rotateY(0)}50%{-webkit-transform:rotateY(1turn);transform:rotateY(1turn)}to{-webkit-transform:rotateY(0);transform:rotateY(0)}}@keyframes rotate{0%{-webkit-transform:rotateY(0);transform:rotateY(0)}50%{-webkit-transform:rotateY(1turn);transform:rotateY(1turn)}to{-webkit-transform:rotateY(0);transform:rotateY(0)}}#comic .spinner-text{font-size:0;position:absolute;left:50%;top:53%;width:91px;height:17px;margin-left:-45.5px;background-image:url("+n(106)+");background-repeat:no-repeat}#comic .light{position:absolute;z-index:200}#comic .light .handle{position:absolute;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}#comic .light .handle.position{width:40px;height:44px;background-image:url("+n(105)+");background-size:contain}#comic .light .handle.rotate{width:0;height:0;background:transparent;border-left:10px solid transparent;border-right:10px solid transparent;border-top:10px solid red}#comic .light .handle.color{width:8px;height:8px;background:#000;border:2px solid #000;border-radius:100%}#comic .light.selected:before{content:'';display:block;position:absolute;left:-40px;right:-40px;top:-40px;bottom:-40px;border:2px solid red;border-radius:100%;opacity:.75}#comic .tools{position:absolute;right:0;z-index:100}#comic .tools button{display:block;width:40px;height:40px;padding:0;margin:10px;box-sizing:content-box;font-size:0;color:transparent;background:#ddd;background-size:contain;border:1px solid #333;border-radius:3px;-webkit-transition:opacity .15s ease;transition:opacity .15s ease;opacity:.75}#comic .tools button:hover{opacity:1}#comic .tools button:disabled{opacity:.25}#comic .tools button.add-light{background-image:url("+n(104)+")}#comic .tools button.remove{background-color:#d22;background-image:url("+n(108)+")}#comic .tools button.remove:disabled{background-color:#777}#comic #data{width:100%;height:10em}",""])},function(t,e){t.exports=function(){var t=[];return t.toString=function(){for(var t=[],e=0;e<this.length;e++){var n=this[e];n[2]?t.push("@media "+n[2]+"{"+n[1]+"}"):t.push(n[1])}return t.join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var i={},o=0;o<this.length;o++){var r=this[o][0];"number"==typeof r&&(i[r]=!0)}for(o=0;o<e.length;o++){var s=e[o];"number"==typeof s[0]&&i[s[0]]||(n&&!s[2]?s[2]=n:n&&(s[2]="("+s[2]+") and ("+n+")"),t.push(s))}},t}},function(t,e,n){function i(){function t(t,e){var n,i=t._co;if(e&&e._co&&e._co.instance){if(n=e._co.instance,n.shouldComponentUpdate&&n.shouldComponentUpdate(i.props,n.state)===!1)return!1}else n=new u[i.component],n.getInitialState&&(n.state=n.getInitialState());i.instance=n,n._node=e||t,n.props=i.props,e&&(e._co=i);var o=n.render();t.appendChild(o)}function e(i,o){if(o._co&&o._co.component)return t(o,i)!==!1&&s(i,o,{onBeforeMorphEl:e,onBeforeNodeDiscarded:n,childrenOnly:!0}),!1;if(o._co&&o._co.events){var r,a=o._co.events;for(r in a)a.hasOwnProperty(r)&&(i[r]=a[r]);if(i._co&&i._co.events){var c=i._co.events;for(r in c)a.hasOwnProperty(r)||(i[r]=void 0)}i._co=i._co||{},i._co.events=a}}function n(t){i(t,function(t){t._co&&t._co.instance&&t._co.instance.componentWillUnmount&&t._co.instance.componentWillUnmount()})}function i(t,e){e(t);for(var n=0;n<t.childNodes.length;n++)i(t.childNodes[n],e)}function a(o,r,a){s(o,r,{onBeforeMorphEl:e,onBeforeNodeDiscarded:n,childrenOnly:a}),i(o,function(e){var n=e._co;n&&n.component&&!n.instance&&(t(e),n.instance.componentDidMount&&n.instance.componentDidMount())})}var c=o(function(t,e,n){var i,o,s;if(u.hasOwnProperty(t)){i=r.createElement("co-"+t.toLowerCase(),{},[]);for(o in e){s=e[o];var a;"string"==typeof s&&("true"===s?e[o]=!0:"false"===s?e[o]=!1:""!==s&&(a=Number(s),isNaN(a)||(e[o]=a)))}i._co={component:t,props:e},n&&n.length&&(i._co.props.children=n)}else{var c={},h={};for(o in e)s=e[o],"on"===o.slice(0,2)&&(o=o.toLowerCase(),h[o]=s),c[o]=s;i=r.createElement(t,c,n),i._co={events:h}}return i}),u=c._components={},h={};return h.setState=function(t){var e,n={};for(e in this.state)this.state.hasOwnProperty(e)&&(n[e]=this.state[e]);for(e in t)t.hasOwnProperty(e)&&(n[e]=t[e]);var i=this.shouldComponentUpdate&&this.shouldComponentUpdate(this.props,n)!==!1;this.state=n,i!==!1&&a(this._node.firstChild,this.render())},h.getDOMNode=function(){return this._node},c.component=function(t,e){e.setState=h.setState,e.getDOMNode=h.getDOMNode;var n=function(){};n.prototype=e,u[t]=n},c.render=function(t,e){var n=r.createElement("div",{},[t]);a(e,n,!0)},c}var o=n(44),r=n(95),s=n(98);t.exports=i(),t.exports.scope=i},function(t,e,n){function i(t,e,n){function i(t){if(Array.isArray(t))for(var e=0;e<t.length;e++){var n=t[e];if(Array.isArray(n))i(n);else{if(("number"==typeof n||"boolean"==typeof n||n instanceof Date||n instanceof RegExp)&&(n=n.toString()),"string"==typeof n){if(r.lastChild&&"#text"===r.lastChild.nodeName){r.lastChild.nodeValue+=n;continue}n=o.createTextNode(n)}n&&n.nodeType&&r.appendChild(n)}}}var r;-1!==c.indexOf(t)&&(e.namespace=s);var u=!1;e.namespace&&(u=e.namespace,delete e.namespace),r=u?o.createElementNS(u,t):o.createElement(t);for(var h in e)if(e.hasOwnProperty(h)){var l=h.toLowerCase(),d=e[h];if("classname"===l&&(l="class",h="class"),a[l])if("true"===d)d=l;else if("false"===d)continue;"on"===l.slice(0,2)?r[h]=d:u?r.setAttributeNS(null,h,d):r.setAttribute(h,d)}return i(n),r}var o=n(96),r=n(44),s="http://www.w3.org/2000/svg",a={autofocus:1,checked:1,defaultchecked:1,disabled:1,formnovalidate:1,indeterminate:1,readonly:1,required:1,willvalidate:1},c=["svg","altGlyph","altGlyphDef","altGlyphItem","animate","animateColor","animateMotion","animateTransform","circle","clipPath","color-profile","cursor","defs","desc","ellipse","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","filter","font","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignObject","glyph","glyphRef","hkern","image","line","linearGradient","marker","mask","metadata","missing-glyph","mpath","path","pattern","polygon","polyline","radialGradient","rect","set","stop","switch","symbol","text","textPath","title","tref","tspan","use","view","vkern"];t.exports=r(i),t.exports.createElement=i},function(t,e,n){(function(e){var i="undefined"!=typeof e?e:"undefined"!=typeof window?window:{},o=n(110);if("undefined"!=typeof document)t.exports=document;else{var r=i["__GLOBAL_DOCUMENT_CACHE@4"];r||(r=i["__GLOBAL_DOCUMENT_CACHE@4"]=o),t.exports=r}}).call(e,function(){return this}())},function(t,e){function n(t){return function(e,n,o){for(var r in n)r in i&&(n[i[r]]=n[r],delete n[r]);return t(e,n,o)}}t.exports=n;var i={"class":"className","for":"htmlFor","http-equiv":"httpEquiv"}},function(t,e){function n(t){for(var e in t)if(t.hasOwnProperty(e))return!1;return!0}function i(t){!u&&document.createRange&&(u=document.createRange(),u.selectNode(document.body));var e;return u&&u.createContextualFragment?e=u.createContextualFragment(t):(e=document.createElement("body"),e.innerHTML=t),e.childNodes[0]}function o(){}function r(t,e){var n,i,o,r,s=e.attributes,a={};for(n=s.length-1;n>=0;n--)i=s[n],i.specified!==!1&&(o=i.name,r=i.value,a[o]=!0,t.getAttribute(o)!==r&&t.setAttribute(o,r));for(s=t.attributes,n=s.length-1;n>=0;n--)i=s[n],i.specified!==!1&&(o=i.name,a.hasOwnProperty(o)||t.removeAttribute(o))}function s(t,e){for(var n=t.firstChild;n;){var i=n.nextSibling;e.appendChild(n),n=i}return e}function a(t){return t.id}function c(t,e,c){function u(t,e){var n=g(t);if(n?f[n]=t:e||v(t),1===t.nodeType)for(var i=t.firstChild;i;)u(i,e||n),i=i.nextSibling}function h(t){if(1===t.nodeType)for(var e=t.firstChild;e;)g(e)||(v(e),h(e)),e=e.nextSibling}function l(t,e,n){b(t)!==!1&&(e.removeChild(t),n?g(t)||(v(t),h(t)):u(t))}function A(t,e,n,i){var o=g(e);if(o&&delete f[o],!i){if(m(t,e)===!1)return;if(r(t,e),y(t,e)===!1)return}if("TEXTAREA"!=t.tagName){var s,a,c,u,h,v=e.firstChild,b=t.firstChild;t:for(;v;){for(c=v.nextSibling,s=g(v);b;){var w=g(b);if(a=b.nextSibling,!n&&w&&(h=p[w]))h.parentNode.replaceChild(b,h),A(b,h,n),b=a;else{var E=b.nodeType;if(E===v.nodeType){var C=!1;if(1===E?(b.tagName===v.tagName&&(w||s?s===w&&(C=!0):C=!0),C&&A(b,v,n)):3===E&&(C=!0,b.nodeValue=v.nodeValue),C){v=c,b=a;continue t}}l(b,t,n),b=a}}s&&((u=f[s])?(A(u,v,!0),v=u):p[s]=v),t.appendChild(v),1===v.nodeType&&(s||v.firstChild)&&x.push(v),v=c,b=a}for(;b;)a=b.nextSibling,l(b,t,n),b=a}var D=d[t.tagName];D&&D(t,e)}c||(c={}),"string"==typeof e&&(e=i(e));var f={},p={},g=c.getNodeKey||a,v=c.onNodeDiscarded||o,m=c.onBeforeMorphEl||o,y=c.onBeforeMorphElChildren||o,b=c.onBeforeNodeDiscarded||o,w=c.childrenOnly===!0,x=[],E=t,C=E.nodeType,D=e.nodeType;if(!w)if(1===C)1===D?t.tagName!==e.tagName&&(v(t),E=s(t,document.createElement(e.tagName))):E=e;else if(3===C){if(3===D)return E.nodeValue=e.nodeValue,E;E=e}if(E===e)v(t);else{A(E,e,!1,w);var M=function(t){for(var e=t.firstChild;e;){var i=e.nextSibling,o=g(e);if(o){var r=f[o];if(r&&e.tagName===r.tagName){if(e.parentNode.replaceChild(r,e),A(r,e,!0),e=i,n(f))return!1;continue}}1===e.nodeType&&M(e),e=i}};if(!n(f))t:for(;x.length;){var S=x;x=[];for(var R=0;R<S.length;R++)if(M(S[R])===!1)break t}for(var O in f)if(f.hasOwnProperty(O)){var k=f[O];v(k),h(k)}}return!w&&E!==t&&t.parentNode&&t.parentNode.replaceChild(E,t),E}var u,h,l="undefined"!=typeof document?document.body||document.createElement("div"):{};h=l.hasAttribute?function(t,e){return t.hasAttribute(e)}:function(t,e){return t.getAttributeNode(e)};var d={OPTION:function(t,e){(t.selected=e.selected)?t.setAttribute("selected",""):t.removeAttribute("selected","")},INPUT:function(t,e){t.checked=e.checked,t.value!=e.value&&(t.value=e.value),h(e,"checked")||t.removeAttribute("checked"),h(e,"value")||t.removeAttribute("value")},TEXTAREA:function(t,e){var n=e.value;t.value!=n&&(t.value=n),t.firstChild&&(t.firstChild.nodeValue=n)}};t.exports=c},function(t,e){/** @license
+	 * eventsource.js
+	 * Available under MIT License (MIT)
+	 * https://github.com/Yaffle/EventSource/
+	 */+!function(t){"use strict";function e(){this.data={}}function n(){this.listeners=new e}function i(t){h(function(){throw t},0)}function o(t){this.type=t,this.target=void 0}function r(t,e){o.call(this,t),this.data=e.data,this.lastEventId=e.lastEventId}function s(t,e){var n=t;return n!==n&&(n=e),M>n?M:n>S?S:n}function a(t,e,n){try{"function"==typeof e&&e.call(t,n)}catch(o){i(o)}}function c(e,i){function c(){G=y,void 0!=_&&(_.abort(),_=void 0),0!==F&&(l(F),F=0),0!==j&&(l(j),j=0),L.readyState=y}function u(t){var n="";if(G===m||G===v)try{n=_.responseText}catch(i){}var u=void 0,d=!1;if(G===v){var A=0,f="",p=void 0;if("contentType"in _)""!==t&&"error"!==t&&(A=200,f="OK",p=_.contentType);else try{A=_.status,f=_.statusText,p=_.getResponseHeader("Content-Type")}catch(i){A=0,f="",p=void 0}if(void 0==p&&(p=""),0===A&&""===f&&"load"===t&&""!==n&&(A=200,f="OK",""===p)){var M=/^data\:([^,]*?)(?:;base64)?,[\S]*$/.exec(e);void 0!=M&&(p=M[1])}if(200===A&&D.test(p)){if(G=m,U=!0,I=k,L.readyState=m,u=new o("open"),L.dispatchEvent(u),a(L,L.onopen,u),G===y)return}else if(0!==A&&(200!==A||""!==p)){var R="";R=200!==A?"EventSource's response has a status "+A+" "+f.replace(/\s+/g," ")+" that is not 200. Aborting the connection.":"EventSource's response has a Content-Type specifying an unsupported type: "+p.replace(/\s+/g," ")+". Aborting the connection.",h(function(){throw new Error(R)},0),d=!0}}if(G===m){n.length>P&&(U=!0);for(var O=P-1,B=n.length,j="\n";++O<B;)if(j=n.charAt(O),K===b&&"\n"===j)K=w;else if(K===b&&(K=w),"\r"===j||"\n"===j){if("data"===z?Q.push(q):"id"===z?Y=q:"event"===z?H=q:"retry"===z?(k=s(Number(q),k),I=k):"heartbeatTimeout"===z&&(N=s(Number(q),N),0!==F&&(l(F),F=h(J,N))),q="",z="",K===w){if(0!==Q.length&&(T=Y,""===H&&(H="message"),u=new r(H,{data:Q.join("\n"),lastEventId:Y}),L.dispatchEvent(u),"message"===H&&a(L,L.onmessage,u),G===y))return;Q.length=0,H=""}K="\r"===j?b:w}else K===w&&(K=x),K===x?":"===j?K=E:z+=j:K===E?(" "!==j&&(q+=j),K=C):K===C&&(q+=j);P=B}G!==m&&G!==v||!("load"===t||"error"===t||d||P>1048576||0===F&&!U)?0===F&&(U=!1,F=h(J,N)):(d?c():(""!==t||0!==F||U||h(function(){throw new Error("No activity within "+N+" milliseconds. Reconnecting.")},0),G=g,_.abort(),0!==F&&(l(F),F=0),I>16*k&&(I=16*k),I>S&&(I=S),F=h(J,I),I=2*I+1,L.readyState=v),u=new o("error"),L.dispatchEvent(u),a(L,L.onerror,u))}function d(){u("progress")}function A(){u("load")}function M(){u("error")}function R(){u(4===_.readyState?0===_.status?"error":"load":"progress")}e=e.toString();var O=f&&void 0!=i&&Boolean(i.withCredentials),k=s(1e3,0),N=s(45e3,0),T="",L=this,I=k,U=!1,B=void 0!=i&&void 0!=i.Transport?i.Transport:p,_=new B,F=0,j=0,P=0,G=g,Q=[],Y="",H="",J=void 0,K=w,z="",q="";"readyState"in _&&void 0!=t.opera&&(j=h(function Z(){3===_.readyState&&u("progress"),j=h(Z,500)},0)),J=function(){if(F=0,G!==g)return void u("");if((!("ontimeout"in _)||"sendAsBinary"in _||"mozAnon"in _)&&void 0!=t.document&&void 0!=t.document.readyState&&"complete"!==t.document.readyState)return void(F=h(J,4));_.onload=A,_.onerror=M,"onabort"in _&&(_.onabort=M),"onprogress"in _&&(_.onprogress=d),"onreadystatechange"in _&&(_.onreadystatechange=R),U=!1,F=h(J,N),P=0,G=v,Q.length=0,H="",Y=T,q="",z="",K=w;var n=e.slice(0,5);n="data:"!==n&&"blob:"!==n?e+((-1===e.indexOf("?",0)?"?":"&")+"lastEventId="+encodeURIComponent(T)+"&r="+(Math.random()+1).toString().slice(2)):e,_.open("GET",n,!0),"withCredentials"in _&&(_.withCredentials=O),"responseType"in _&&(_.responseType="text"),"setRequestHeader"in _&&_.setRequestHeader("Accept","text/event-stream"),_.send(void 0)},n.call(this),this.close=c,this.url=e,this.readyState=v,this.withCredentials=O,this.onopen=void 0,this.onmessage=void 0,this.onerror=void 0,J()}function u(){this.CONNECTING=v,this.OPEN=m,this.CLOSED=y}var h=t.setTimeout,l=t.clearTimeout;e.prototype.get=function(t){return this.data[t+"~"]},e.prototype.set=function(t,e){this.data[t+"~"]=e},e.prototype["delete"]=function(t){delete this.data[t+"~"]},n.prototype.dispatchEvent=function(t){t.target=this;var e=t.type.toString(),n=this.listeners,o=n.get(e);if(void 0!=o)for(var r=o.length,s=-1,a=void 0;++s<r;){a=o[s];try{a.call(this,t)}catch(c){i(c)}}},n.prototype.addEventListener=function(t,e){t=t.toString();var n=this.listeners,i=n.get(t);void 0==i&&(i=[],n.set(t,i));for(var o=i.length;--o>=0;)if(i[o]===e)return;i.push(e)},n.prototype.removeEventListener=function(t,e){t=t.toString();var n=this.listeners,i=n.get(t);if(void 0!=i){for(var o=i.length,r=[],s=-1;++s<o;)i[s]!==e&&r.push(i[s]);0===r.length?n["delete"](t):n.set(t,r)}},r.prototype=o.prototype;var d=t.XMLHttpRequest,A=t.XDomainRequest,f=void 0!=d&&void 0!=(new d).withCredentials,p=f||void 0!=d&&void 0==A?d:A,g=-1,v=0,m=1,y=2,b=3,w=4,x=5,E=6,C=7,D=/^text\/event\-stream;?(\s*charset\=utf\-8)?$/i,M=1e3,S=18e6;u.prototype=n.prototype,c.prototype=new u,u.call(c),f&&(c.prototype.withCredentials=void 0);var R=function(){return void 0!=t.EventSource&&"withCredentials"in t.EventSource.prototype};void 0!=p&&(void 0==t.EventSource||f&&!R())&&(t.NativeEventSource=t.EventSource,t.EventSource=c)}("undefined"!=typeof window?window:this)},function(t,e,n){function i(t){if(!t)return"";var e=Object.keys(t),n=e.map(function(e){var n=o(e),i=n.concat(":").concat(t[e]);return i});return n.join(";")}/*!+	 * Dependencies+	 */+var o=n(101);/*!+	 * Exports+	 */+t.exports=i},function(t,e){/*!+	 * dashify <https://github.com/jonschlinkert/dashify>+	 *+	 * Copyright (c) 2015 Jon Schlinkert.+	 * Licensed under the MIT license.+	 */+"use strict";t.exports=function(t){if("string"!=typeof t)throw new TypeError("dashify expects a string.");return t=t.replace(/[A-Z]/g,"-$&"),t=t.replace(/[ \t]/g,"-"),t.toLowerCase()}},function(t,e,n){function i(t,e){for(var n=0;n<t.length;n++){var i=t[n],o=A[i.id];if(o){o.refs++;for(var r=0;r<o.parts.length;r++)o.parts[r](i.parts[r]);for(;r<i.parts.length;r++)o.parts.push(u(i.parts[r],e))}else{for(var s=[],r=0;r<i.parts.length;r++)s.push(u(i.parts[r],e));A[i.id]={id:i.id,refs:1,parts:s}}}}function o(t){for(var e=[],n={},i=0;i<t.length;i++){var o=t[i],r=o[0],s=o[1],a=o[2],c=o[3],u={css:s,media:a,sourceMap:c};n[r]?n[r].parts.push(u):e.push(n[r]={id:r,parts:[u]})}return e}function r(t,e){var n=g(),i=y[y.length-1];if("top"===t.insertAt)i?i.nextSibling?n.insertBefore(e,i.nextSibling):n.appendChild(e):n.insertBefore(e,n.firstChild),y.push(e);else{if("bottom"!==t.insertAt)throw new Error("Invalid value for parameter 'insertAt'. Must be 'top' or 'bottom'.");n.appendChild(e)}}function s(t){t.parentNode.removeChild(t);var e=y.indexOf(t);e>=0&&y.splice(e,1)}function a(t){var e=document.createElement("style");return e.type="text/css",r(t,e),e}function c(t){var e=document.createElement("link");return e.rel="stylesheet",r(t,e),e}function u(t,e){var n,i,o;if(e.singleton){var r=m++;n=v||(v=a(e)),i=h.bind(null,n,r,!1),o=h.bind(null,n,r,!0)}else t.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=c(e),i=d.bind(null,n),o=function(){s(n),n.href&&URL.revokeObjectURL(n.href)}):(n=a(e),i=l.bind(null,n),o=function(){s(n)});return i(t),function(e){if(e){if(e.css===t.css&&e.media===t.media&&e.sourceMap===t.sourceMap)return;i(t=e)}else o()}}function h(t,e,n,i){var o=n?"":i.css;if(t.styleSheet)t.styleSheet.cssText=b(e,o);else{var r=document.createTextNode(o),s=t.childNodes;s[e]&&t.removeChild(s[e]),s.length?t.insertBefore(r,s[e]):t.appendChild(r)}}function l(t,e){var n=e.css,i=e.media;if(i&&t.setAttribute("media",i),t.styleSheet)t.styleSheet.cssText=n;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(n))}}function d(t,e){var n=e.css,i=e.sourceMap;i&&(n+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(i))))+" */");var o=new Blob([n],{type:"text/css"}),r=t.href;t.href=URL.createObjectURL(o),r&&URL.revokeObjectURL(r)}var A={},f=function(t){var e;return function(){return"undefined"==typeof e&&(e=t.apply(this,arguments)),e}},p=f(function(){return/msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase())}),g=f(function(){return document.head||document.getElementsByTagName("head")[0]}),v=null,m=0,y=[];t.exports=function(t,e){e=e||{},"undefined"==typeof e.singleton&&(e.singleton=p()),"undefined"==typeof e.insertAt&&(e.insertAt="bottom");var n=o(t);return i(n,e),function(t){for(var r=[],s=0;s<n.length;s++){var a=n[s],c=A[a.id];c.refs--,r.push(c)}if(t){var u=o(t);i(u,e)}for(var s=0;s<r.length;s++){var c=r[s];if(0===c.refs){for(var h=0;h<c.parts.length;h++)c.parts[h]();delete A[c.id]}}}};var b=function(){var t=[];return function(e,n){return t[e]=n,t.filter(Boolean).join("\n")}}()},function(t,e,n){var i=n(92);"string"==typeof i&&(i=[[t.id,i,""]]);n(102)(i,{});i.locals&&(t.exports=i.locals)},function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAYUExURUxpcQAAABYWFgAAAAAAAPv7+39/f8fHx/ktK9QAAAAEdFJOUwCK20hF+i26AAABpklEQVRYw+2Xya7DIAxF4wH4/z9+GNI2ScNg4yd1kSt10SyOjhnNtlmCzAEZQ+RTkDZbgDlmVgx8DdiA2eVOUGIVTJmVjoJxwZBEUAo+CMZsvCoY0pEXOaUFwXgWxBDqf5tezVEwiWAuWi9I+LHCU8HywwXcKblgzMLohKszkn9gHLxrsBSsFmzplfUtBTO56FVB7Qx3cGXTKWeYkAeJmoLHOCl7WnAKJyFf3JzgPG4KqMFNnINK3AioxvWBBlwXCMyeQBuuCTRV2wFa9RpAsuPuFzZ4AxdGsHm6mpHgvWzAeZ/0LxQLEobtkC9QLwmLN6eptVYhyftOIY+OwfKacAfCsU/1AOYFmRI6AkUx3L2X7C8yaXhDin5AKTqEoSWoms0JoqZ9xZ3YRcJwMdOJGAuxM5SoAQoRK7JpqQPW87EyG6tSCcxfsTJTS1ILzJZg7GChcx4BOgOLqeSLvAC8PyrXgZcx/QbCK3sx+P4whcT5sxmnbh0n4OcVDV7Al6QfcJ9FcgQWSU/DIkn3G6Bsgn2U3x+2xYCqt3iAD/B/gIQlfsAnP5A/fAAqEID2icEAAAAASUVORK5CYII="},function(t,e){t.exports="data:image/png;base64,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"},function(t,e){t.exports="data:image/gif;base64,R0lGODlhWwARAPfOAPf39wEBAR4eHgICAiIiIpmZmf39/YmJib6+vvb29oKCgkRERLq6uq2trbu7uz4+PgMDA7CwsAYGBvz8/BQUFAsLC/Hx8d3d3Y2NjaqqqoGBgRsbG19fX2hoaLOzs+vr6zQ0NKysrCkpKRcXFw4ODh8fHxwcHMLCwnp6ehMTE7S0tM/Pz4uLi1JSUpOTk/v7+9fX13FxccrKyuPj4woKCrGxsd/f3/X19VpaWu3t7ZWVlWZmZkFBQUBAQHR0dGVlZdnZ2TMzMxYWFjAwMBgYGNXV1d7e3gUFBRAQELKyspubm9PT0/n5+dzc3Ly8vEpKSgcHB1xcXBkZGSUlJUNDQ5aWloCAgMDAwBEREWxsbIiIiM3NzVVVVcjIyEtLS15eXg8PD/r6+qenp6Ghoejo6Gtra+Li4nZ2djIyMmBgYFZWVioqKisrK0ZGRoaGhjs7O/T09Lm5uQ0NDU5OTs7Oznl5eZiYmNLS0n5+fiEhIX19fa+vr8zMzO/v75GRkSMjIygoKGJiYtHR0SwsLLi4uMHBwaurq/Dw8CcnJ9TU1JycnKioqDk5OQgICIqKiubm5gwMDFdXV+Xl5cvLy56eni8vL5qammFhYSAgIFBQUMPDwzc3N8fHx3BwcL29vdDQ0FFRUQkJCYSEhPPz86SkpDY2NmRkZExMTKCgoE1NTerq6p+fn3t7e3Jyctvb2+Tk5EVFRampqYeHh6Ojo1hYWKampsnJyTw8PNbW1kJCQqWlpTo6OsbGxmdnZyYmJj09PX9/f4ODg2lpaeDg4Hd3dzExMezs7Nra2klJSVRUVHV1dTg4OBISEgAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDY3IDc5LjE1Nzc0NywgMjAxNS8wMy8zMC0yMzo0MDo0MiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDA1QzFEMDNGMjY3MTFFNTkwODJDMzUxMTUyRTJDNjMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDA1QzFEMDRGMjY3MTFFNTkwODJDMzUxMTUyRTJDNjMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0MDVDMUQwMUYyNjcxMUU1OTA4MkMzNTExNTJFMkM2MyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0MDVDMUQwMkYyNjcxMUU1OTA4MkMzNTExNTJFMkM2MyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAUyAM4ALAAAAABbABEAAAj/AJ0JdLZI08CDCA8hREhmS4KFHx51MeShUA6ENgwsFAgjBRoLCG8Y20iSJJ1mzQCUXCEiwBNVG+tAaEYhzkE8KHOifEBH4IlmeQS+QpgE5UWBQOYcabbBFK+SUJ3pajagpJEUOYfEQqhEZwUZA3GgDCCAQk4aCJw5aibAWZ1mHQ42QDmQjxydVA9EJcmALslTzYigyPnggkAmAppRmWSi2QiQzno1m3NDIABCif8Y0NEMkLNSzQIIGqjCbxESzUqE6IIBRDMITvYu7NuMpJEAzUI487AAJRKwhEJ/cmZLQjMrAok1W4DwZ7Mrs5phcda7WaCBc2sb2MXUsMAJT5rV/5KNkJTfhRqaSdEoMIKQzs5iNBMxsEwzEiD1NAOx8E8zDCFI58wDKEHgnQd01YBSEggB0Ad5CLHQTCMkUdFMGQhlx8cbcA1kA24NOHNAM0Es1JsWGTQDxYA5xSDQFSiRwUEzjEC4VxbNCEFSCc0UgJABFTRjyQjN6HDQEM384IwWzayxEIEaxIESAL3R0IwERTiTCEp3INKMBgfBIQgud7xgo0Bt7LcRACilhRABzaAwQDNuKKDGLWwg0cwUzlTRDAEL+dJMMA6gFMYmzezQGA/OmNFmYj4KxIlxKJGAwZlsNOPFRsegBMNCqHWCl04D3FDAnwjlMKcKhdYGSzNpjP+Bkg4JoLRHY3oJNANur014JpFcbGSDpwitgBIGKNHQQwcK2IFsMwycCuhBKTbzQavORNEMMs700EwoTQRpRwvNDDLBQEuY4YwIip0JSTNfbFRrM24OlEYzYASYEkKNKUAJTQj90kwxzmDrQzNoODOMnj0QQadzPiCEAEoZnInSDiRh0owoB6mAmxVdpbAQKM2kkqIEKrWHkhgFo2SBhNM2MCdKEePYjAID9QFnEGbaaFYkA0kixhkcBOIHUx84A4cCUDSZAE5DLDTYIAg2s0V7d5XoDG0JnErEQF2h5KIzMzajxhIy8PDaCmc6w+EIX4AQpE57GCeFF2CglIcRZDeDE8VCRwvhBEot6JELSgJ4Vy0DI5ZwkCwoufAdF6PmemZReB1RSRs7zODCUigN8EPSzvzQDAsLeYISKqNOkaXKzSwRwQYtIDTGGUwcVEViqUXatjM1XEILK4qckPJAQGjQgQszHDRBE2EsxEQrwoyyCjA4ZIJDDT0L1IQIjJ7pChC/BwQAIfkEBTIAzgAsTAAIAAYACAAACCIADTkbOPAJwYHKDjorpHCCwoEqoAggiKJZMwIDnaRIdiEgACH5BAUyAM4ALFEADAAGAAUAAAgfAJ0JfPFCoLMVFCooOuQsSrNmUww4O9AMkQuDFiQGBAAh+QQFMgDOACxWAA0ABQADAAAIFABhOGMhZdkiZ4yaMUvkDEELRwEBADs="},function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABoCAMAAAAw/qUdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAYUExURQAAAExpcQAAAAAAAAAAAAAAAAAAAAAAAO1JYJEAAAAIdFJOU/8A4rxAaZMcjWgm3wAABTlJREFUaN69Wtd2hSAQxK3//8dBAWkLCibhKedGGbbNFnTH5hLHr591mxjs3HuUXRB0CyjrIAyAFEDeorj32gG5RXACLi7RXwOJe+qhrlu/BpI21ChJueA7CDtvgaPYsBfFK+wUkLZBLvWgQt6QOxA56HpqG0SiBFkUZ1gFvoG0JgDqMeJv8Gsg1hKJWvsKAjwBeYrMJ5BbO/ws0L4LD3Y8LIGQaQ+E3NKSvYgfoPDg901aITA3s/2ON0AYAThFZB0c7wWZgyQ2KQ4NuOFh7om36oVetgkGrbsw9QLARR9FzIBfJ7j4fClbcQK1tilJA00ugYsciZl3gvFWDZITqkn3ziX+KYSUkFE3vOvWNSJYIBjkLSylG9yFIqw1HZcgyb+L33QVxDsYaONqUsQNJjcXmcTjBMTz3aUubFyNb6kogxfJDBZAzrdUbvk578FlXCQJlcZR7+bVbgahXpCqEKP8xKIkKOmQolnj2DlX3PlyQFkwvBJHFgzRHrgYKMoonPQfBTj0DHrSlTjhTLQhQOi4A1r9bkc2chBMN1j49sigscYvsyk4Poq4CqIlb4lZuGn2V9hJWj5PnV2CaowzYCEdFhhM+Fh9uwEpwiUQIxJhxTI+RLXUJ74o8d1ID2eRHawjHflfHmCnLfAlAT9LItWhapfMBAasJsjlAPQIcpvUG1GbtF1ujO8zvRsl3TOrYpe44V2J9xakoo4NEHZXi9aA4Nnf6gOIvANJPUCEcY0AfChMQN6KUsema7J5ptsGJIqu2FSQVKQXsHrWEkSGjQjGchgDiQc6Fm4rJTtqBiDdcQCx9k1ln8zkBsHHirJTV3DeghtrHZ/qOktFsUqXkcu4vqVSfTYomoJnkb34kjnZHZ0oDP07Lz23sE/4swtGcZ9XUWaGQqyP+HWU2RtoeFfIFiM9DNxUYA6CLYgSkZooeJbTaM07ZvEPhgtfL8Dw8fJ/7N54BuQ2wtXd4ao7Tf9HV/rOIGAo9A3/TUPpHlA5a7iBj+FoG0+k4sabh11LKu08yDauHKbuuCr4ITmx6/gRasfiAQg/GQhCyVzYRDpOsWyTz2+BGPxIQWGurOiIpu8WJSm/mB5hko0qEOxKNmmyA9Yaf0c7UIF4URr9Vl4AWa1wJuJnmPBIAqFmiJVH2hQHG/H8RfKeK4wxWUVu7+Ko6jYnaekYmKmBD30YFnHNwoJ+16s+CAEEhSdjKIdvm51akqqoASMQMZpAEkh0WjgHr74x8G2JhPwZqpJT9ZDUmHWb7RHsSJXPBAk4tTWuaPLP7c72B8MeRhlCPZv6Q3MYohKMupQcbnB4Rost+xV1KKM6Cga1NZtFZykjQVnBw6yQIjea1qDZOd5sHXhV7NoeUmKWZg5Gdk/QtQ4UGlFu5AygkPiG6+J20usSI4/6E2x7JDErBaiK89UrQGxVfDF1b5uix8djC4S6i1coYytqSGeDgSkIZ/GVLs/nm3ax1FG2CK1fyyLUSVKwcdzEWuIWtWUMC7Av3dm8Q6NtkEwZmM8cUGjSSq6BcJE9i6ED9ZdotA9SFqPFtBGtsfouCJU3PXI0E1J+nGytgYTqJ89hqbt1/GCTGyNGIdYMAiuX12MXlpMuKfkrhlQEYKSADyBhApkkOAf1vjtSy/2+f+0xdlRcF8VN702s48rspmQPBCcgf6gu+l8Q+QoiQ73reji6+R2jTMz13vJuepOJ88vH798S+RpZHu5qQb+CPN15LOjrG4j8EYhupOD177tgPQWvg8g/GL6smT7HyeOnLAB/+mGfsqB1zTVcP58nJEXsQyUBAAAAAElFTkSuQmCC"},function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAYUExURUxpcf///////////////////////////95C5tQAAAAHdFJOUwABnpj+bGXDMIZhAAAA4ElEQVRYw+3XwQ6EIAxFUUCZ/v8fz8QYk1Gghd6V6VvTk6gIbUqRSCTynnxK1hflrZq9XXQxb7JXsyeq+PPEKB6eJh6eTTy9sXh6FvHyRuLlGcQioor5b1EyvOyxaFgytXzSUwumPaVkwRsWLXmDskWvW7jsdUodXrPY5TXKnd4DcHt30e/dRMDrieteW/R4LdHnPUWvdxf9Hg7Sj0x/FHrb0Bub/vXow4E+vugDlr4C6EuKvkbpi55uRehmiW7n6IYTb4nxph0fK/DBhx/N+OGRH29TqqYNW2qKRCKR9+QLfbEaz7uhlPAAAAAASUVORK5CYII="},function(t,e){!function(t){"use strict";function e(t){if("string"!=typeof t&&(t=String(t)),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(t))throw new TypeError("Invalid character in header field name");return t.toLowerCase()}function n(t){return"string"!=typeof t&&(t=String(t)),t}function i(t){this.map={},t instanceof i?t.forEach(function(t,e){this.append(e,t)},this):t&&Object.getOwnPropertyNames(t).forEach(function(e){this.append(e,t[e])},this)}function o(t){return t.bodyUsed?Promise.reject(new TypeError("Already read")):void(t.bodyUsed=!0)}function r(t){return new Promise(function(e,n){t.onload=function(){e(t.result)},t.onerror=function(){n(t.error)}})}function s(t){var e=new FileReader;return e.readAsArrayBuffer(t),r(e)}function a(t){var e=new FileReader;return e.readAsText(t),r(e)}function c(){return this.bodyUsed=!1,this._initBody=function(t){if(this._bodyInit=t,"string"==typeof t)this._bodyText=t;else if(f.blob&&Blob.prototype.isPrototypeOf(t))this._bodyBlob=t;else if(f.formData&&FormData.prototype.isPrototypeOf(t))this._bodyFormData=t;else if(t){if(!f.arrayBuffer||!ArrayBuffer.prototype.isPrototypeOf(t))throw new Error("unsupported BodyInit type")}else this._bodyText="";this.headers.get("content-type")||("string"==typeof t?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type&&this.headers.set("content-type",this._bodyBlob.type))},f.blob?(this.blob=function(){var t=o(this);if(t)return t;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this.blob().then(s)},this.text=function(){var t=o(this);if(t)return t;if(this._bodyBlob)return a(this._bodyBlob);if(this._bodyFormData)throw new Error("could not read FormData body as text");return Promise.resolve(this._bodyText)}):this.text=function(){var t=o(this);return t?t:Promise.resolve(this._bodyText)},f.formData&&(this.formData=function(){return this.text().then(l)}),this.json=function(){return this.text().then(JSON.parse)},this}function u(t){var e=t.toUpperCase();return p.indexOf(e)>-1?e:t}function h(t,e){e=e||{};var n=e.body;if(h.prototype.isPrototypeOf(t)){if(t.bodyUsed)throw new TypeError("Already read");this.url=t.url,this.credentials=t.credentials,e.headers||(this.headers=new i(t.headers)),this.method=t.method,this.mode=t.mode,n||(n=t._bodyInit,t.bodyUsed=!0)}else this.url=t;if(this.credentials=e.credentials||this.credentials||"omit",!e.headers&&this.headers||(this.headers=new i(e.headers)),this.method=u(e.method||this.method||"GET"),this.mode=e.mode||this.mode||null,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&n)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(n)}function l(t){var e=new FormData;return t.trim().split("&").forEach(function(t){if(t){var n=t.split("="),i=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");e.append(decodeURIComponent(i),decodeURIComponent(o))}}),e}function d(t){var e=new i,n=t.getAllResponseHeaders().trim().split("\n");return n.forEach(function(t){var n=t.trim().split(":"),i=n.shift().trim(),o=n.join(":").trim();e.append(i,o)}),e}function A(t,e){e||(e={}),this.type="default",this.status=e.status,this.ok=this.status>=200&&this.status<300,this.statusText=e.statusText,this.headers=e.headers instanceof i?e.headers:new i(e.headers),this.url=e.url||"",this._initBody(t)}if(!t.fetch){i.prototype.append=function(t,i){t=e(t),i=n(i);var o=this.map[t];o||(o=[],this.map[t]=o),o.push(i)},i.prototype["delete"]=function(t){delete this.map[e(t)]},i.prototype.get=function(t){var n=this.map[e(t)];return n?n[0]:null},i.prototype.getAll=function(t){return this.map[e(t)]||[]},i.prototype.has=function(t){return this.map.hasOwnProperty(e(t))},i.prototype.set=function(t,i){this.map[e(t)]=[n(i)]},i.prototype.forEach=function(t,e){Object.getOwnPropertyNames(this.map).forEach(function(n){this.map[n].forEach(function(i){t.call(e,i,n,this)},this)},this)};var f={blob:"FileReader"in t&&"Blob"in t&&function(){try{return new Blob,!0}catch(t){return!1}}(),formData:"FormData"in t,arrayBuffer:"ArrayBuffer"in t},p=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];h.prototype.clone=function(){return new h(this)},c.call(h.prototype),c.call(A.prototype),A.prototype.clone=function(){return new A(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new i(this.headers),url:this.url})},A.error=function(){var t=new A(null,{status:0,statusText:""});return t.type="error",t};var g=[301,302,303,307,308];A.redirect=function(t,e){if(-1===g.indexOf(e))throw new RangeError("Invalid status code");return new A(null,{status:e,headers:{location:t}})},t.Headers=i,t.Request=h,t.Response=A,t.fetch=function(t,e){return new Promise(function(n,i){function o(){return"responseURL"in s?s.responseURL:/^X-Request-URL:/m.test(s.getAllResponseHeaders())?s.getResponseHeader("X-Request-URL"):void 0}var r;r=h.prototype.isPrototypeOf(t)&&!e?t:new h(t,e);var s=new XMLHttpRequest;s.onload=function(){var t=1223===s.status?204:s.status;if(100>t||t>599)return void i(new TypeError("Network request failed"));var e={status:t,statusText:s.statusText,headers:d(s),url:o()},r="response"in s?s.response:s.responseText;n(new A(r,e))},s.onerror=function(){i(new TypeError("Network request failed"))},s.open(r.method,r.url,!0),"include"===r.credentials&&(s.withCredentials=!0),"responseType"in s&&f.blob&&(s.responseType="blob"),r.headers.forEach(function(t,e){s.setRequestHeader(e,t)}),s.send("undefined"==typeof r._bodyInit?null:r._bodyInit)})},t.fetch.polyfill=!0}}("undefined"!=typeof self?self:this)},function(t,e){}]);
+ client/package.json view
@@ -0,0 +1,44 @@+{+  "name": "xkcd-linden",+  "version": "0.0.1",+  "description": "Client side of 2016-4-1",+  "keywords": [+    "xkcd"+  ],+  "author": {+    "name": "Max Goodman",+    "email": "c@chromakode.com"+  },+  "license": "MIT",+  "scripts": {+    "lint": "eslint .",+    "start": "webpack-dev-server --devtool inline-source-map --content-base build/",+    "build": "webpack -p --progress"+  },+  "main": "src/index.js",+  "dependencies": {+    "core-js": "~2.2.1",+    "diablo": "~0.0.2",+    "clamp": "~1.0.1",+    "classnames": "~2.2.3",+    "inline-style": "~2.0.0",+    "event-source-polyfill": "~0.0.7",+    "whatwg-fetch": "~0.11.0"+  },+  "devDependencies": {+    "url-loader": "~0.5.7",+    "css-loader": "~0.23.1",+    "style-loader": "~0.13.1",+    "autoprefixer-loader": "~3.2.0",+    "less-loader": "~2.2.3",+    "less": "~2.6.1",+    "babel-loader": "~6.2.4",+    "babel-core": "~6.7.4",+    "babel-plugin-transform-es2015-template-literals": "~6.6.5",+    "webpack": "~1.12.14",+    "webpack-dev-server": "~1.14.1",+    "eslint": "~2.5.3",+    "eslint-config-airbnb": "~6.2.0",+    "babel-eslint": "~6.0.2"+  }+}
+ client/src/GardenClient.js view
@@ -0,0 +1,251 @@+require('event-source-polyfill')++var Linden = require('./Linden')+var walkTree = require('./walkTree')+var randomWind = require('./randomWind')++var GardenClient = function GardenClient(opts) {+  this.opts = opts+  this.data = null+  this.gardenId = null+  this.gardenKey = null+}+GardenClient.prototype = {+  record: function record(ref, name) {+    new Image().src = 'http://xkcd.com/events.gif?re=' + ref + '&ev=' + name+  },++  log: function log() {+    if (this.opts.debug) {+      console.log.apply(console, arguments)  // eslint-disable-line no-console+    }+  },++  _url: function _url() {+    var serverURL = this.opts.endpoint + 'run'+    if (this.gardenId) {+      serverURL += '/' + this.gardenId+    }+    return serverURL+  },++  _connect: function() {+    var serverURL = this._url()++    this.source = new EventSource(serverURL)+    this.log('connecting to event source:', serverURL)++    this.source.addEventListener('open', this.onConnect.bind(this), false)+    this.source.addEventListener('error', this.onConnectionError.bind(this), false)+    this.source.addEventListener('linden-key', this.onLindenKey.bind(this), false)+    this.source.addEventListener('linden', this.onLindenUpdate.bind(this), false)+    this.source.addEventListener('reload', this.onReload.bind(this), false)+  },++  run: function run() {+    try {+      if (this.opts.endpoint) {+        if (location.hash) {+          this.gardenId = location.hash.substr(1)+          this.gardenKey = localStorage['key-' + this.gardenId]+        }+        this._connect()+      }++      this.linden = new Linden({+        el: this.opts.el,+        width: this.opts.width,+        height: this.opts.height,+        title: this.opts.title,+        urlPrefix: this.opts.imgPrefix,+        loading: true,+        readOnly: !this.gardenKey,+        debug: this.opts.debug,+        onLightChanging: this.handleLightChanging.bind(this),+        onSetLight: this.handleSetLight.bind(this),+        onRemoveLight: this.handleRemoveLight.bind(this),+        onPruneNode: this.handlePruneNode.bind(this),+      })++      if (this.opts.data) {+        this.linden.update(this.opts.data)+      }++      randomWind(this.linden)+    } catch (e) {+      this.record(this.opts.name, 'js_error')+      throw e+    }++    return this+  },++  onConnect: function onConnect() {+    this.record(this.opts.name, 'connect_start')+    this.dirtyLights = {}+    this.inFlightRequests = 0+    this.linden.renderUI()+  },++  onConnectionError: function onConnectionError(ev) {+    this.log('connection error', ev)+    this.record(this.opts.name, 'connect_error')+    this.linden.opts.loading = true+    this.linden.resetTree()+    this.linden.renderUI()+    if (this.source.url !== this._url()) {+      // we need to change the url to re-fetch the current tree+      this.source.close()+      this._connect()+    }+  },++  onLindenKey: function onLindenKey(ev) {+    this.log('linden key from EventSource', ev)+    var data = JSON.parse(ev.data)+    this.gardenId = location.hash = data.garden+    this.gardenKey = localStorage['key-' + data.garden] = data.key++    // TODO: this violates encapsulation+    this.linden.opts.readOnly = false+    this.linden.renderUI()+  },++  _acceptUpdate: function _acceptUpdate() {+    if (this.inFlightRequests > 0) {+      this.log('rejecting update, inflight:', this.inFlightRequests)+      return false+    }++    for (var k in this.dirtyLights) {+      if (this.dirtyLights.hasOwnProperty(k) && this.dirtyLights[k] === true) {+        this.log('rejecting update, dirty light:', k)+        return false+      }+    }++    return true+  },++  _update: function _update(data) {+    if (!this._acceptUpdate()) {+      // discard possibly conflicting updates+      return+    }+    this.data = data+    this.linden.update(data)+  },++  onLindenUpdate: function onLindenUpdate(ev) {+    this.log('linden from EventSource', ev)+    this._update(JSON.parse(ev.data))+  },++  onReload: function onReload() {+    var delay = Math.round(Math.random() * this.opts.spread.reload)+    this.log('reloading in', delay + 5, 'seconds')+    setTimeout(function doReload() {+      this.record(this.opts.name, 'reloading')++      // give the record a little time to be sent+      setTimeout(function delayedReload() {+        location.reload()+      }, 5 * 1000)+    }, delay * 1000)+  },++  request: function request(url, options) {+    var onFinished = function onRequestFinished() {+      this.inFlightRequests--+      this.log('inflight--', this.inFlightRequests)+    }.bind(this)++    var fetchOptions = {+      method: 'POST',+    }+    Object.assign(fetchOptions, options)++    fetchOptions.body = JSON.stringify(options.body)++    this.log('request', url, fetchOptions)+    var promise = fetch(url, fetchOptions)+    promise.then(onFinished, onFinished)+    this.inFlightRequests+++    this.log('inflight++', this.inFlightRequests)+    return promise+  },++  handleLightChanging: function handleLightChanging(id) {+    this.dirtyLights[id] = true+  },++  handleSetLight: function handleSetLight(id, data) {+    this.log('set light:', id, data)++    this.request(this.opts.endpoint + 'edit', {+      body: Object.assign({+        tag: 'EditLight',+        garden: this.data.garden,+        editKey: this.gardenKey,+        light: id,+      }, data),+    })+      .then(function onResponse(resp) { return resp.json() })+      .then(function onJSON(newLights) {+        this._update(Object.assign({}, this.data, {+          lights: newLights,+        }))+      }.bind(this))++    delete this.dirtyLights[id]+  },++  _sendRemove: function _sendRemove(id) {+    this.request(this.opts.endpoint + 'edit', {+      body: {+        tag: 'Del',+        garden: this.data.garden,+        editKey: this.gardenKey,+        node: id,+      },+    })+  },++  handleRemoveLight: function handleRemoveLight(id) {+    this.log('remove light:', id)++    var newLights = this.data.lights.filter(function filterLights(light) {+      return light.id !== id+    })++    this._update(Object.assign({}, this.data, {+      lights: newLights,+    }))++    this._sendRemove(id)+  },++  handlePruneNode: function handlePruneNode(id) {+    this.log('prune node:', id)++    var rootNode = { children: [] }+    var parentNode = rootNode+    walkTree(this.data.tree, function pruneVisit(node, descend) {+      var origParentNode = parentNode+      parentNode = { node: node, children: [] }+      if (node.id !== id) {+        origParentNode.children.push(parentNode)+      }+      descend()+      parentNode = origParentNode+    })++    this._update(Object.assign({}, this.data, {+      tree: rootNode.children[0],+    }))++    this._sendRemove(id)+  },+}++module.exports = GardenClient
+ client/src/ImgCache.js view
@@ -0,0 +1,62 @@+function ImgCache() {+  this.imgs = {}+  this.callbacks = {}+}+ImgCache.prototype = {+  get: function get(src, callback) {+    var img = this.imgs[src]++    if (img === undefined) {+      this.imgs[src] = true+      this.callbacks[src] = [callback]+      img = new Image()+      img.onload = this._onDone.bind(this, src, img, img)+      img.onerror = this._onDone.bind(this, src, img, false)+      img.crossOrigin = 'anonymous'+      img.src = src+    } else if (img === true) {+      // loading+      this.callbacks[src].push(callback)+    } else {+      // release zalgo+      if (callback) {+        callback(src, img)+      }+    }+  },++  _onDone: function _onDone(src, img, result) {+    this.imgs[src] = result+    for (var i = 0; i < this.callbacks[src].length; i++) {+      var callback = this.callbacks[src][i]+      if (callback) {+        callback(src, result)+      }+    }+    delete this.callbacks[src]+  },++  getAll: function getAll(srcs, callback) {+    if (!srcs.length) {+      callback([])+      return+    }++    var remaining = srcs.length+    var results = {}++    function loaded(src, img) {+      results[src] = img+      remaining--+      if (remaining === 0) {+        callback(results)+      }+    }++    for (var i = 0; i < srcs.length; i++) {+      this.get(srcs[i], loaded)+    }+  },+}++module.exports = ImgCache
+ client/src/Linden.js view
@@ -0,0 +1,794 @@+var x = require('diablo')+var classNames = require('classnames')+var clamp = require('clamp')+var inlineStyle = require('inline-style')++var ImgCache = require('./ImgCache')+var walkTree = require('./walkTree')++var PI2 = Math.PI / 2+var TWOPI = 2 * Math.PI+var DEG = PI2 / 90++function sin(angle) {+  return Math.sin(angle * DEG)+}++function cos(angle) {+  return Math.cos(angle * DEG)+}++function mod(v, m) {+  return (v % m + m) % m+}++function rotBounds(a, ox, oy, x1, y1, x2, y2) {+  var rx1+  var ry1+  var rx2+  var ry2+  var am = mod(a, 360)+  if (am <= 90) {+    rx1 = x1+    ry1 = y2+    rx2 = x2+    ry2 = y1+  } else if (am <= 180) {+    rx1 = x2+    ry1 = y1+    rx2 = x1+    ry2 = y2+  } else if (am <= 270) {+    rx1 = x2+    ry1 = y2+    rx2 = x1+    ry2 = y1+  } else {+    rx1 = x1+    ry1 = y2+    rx2 = x2+    ry2 = y1+  }+  rx1 -= ox+  ry1 -= oy+  rx2 -= ox+  ry2 -= oy+  var newBounds = {+    x1: ox + rx1 * cos(am) - ry1 * sin(am),+    y1: oy + rx1 * sin(am) + ry1 * cos(am),+    x2: ox + rx2 * cos(am) - ry2 * sin(am),+    y2: oy + rx2 * sin(am) + ry2 * cos(am),+  }++  // FIXME: not sure why these are transposed :(+  if (newBounds.x2 < newBounds.x1) {+    var swapx = newBounds.x2+    newBounds.x2 = newBounds.x1+    newBounds.x1 = swapx+  }+  if (newBounds.y2 < newBounds.y1) {+    var swapy = newBounds.y2+    newBounds.y2 = newBounds.y1+    newBounds.y1 = swapy+  }++  return newBounds+}++function rgba(r, g, b, a) {+  return 'rgba(' + 100 * r + '%, ' + 100 * g + '%, ' + 100 * b + '%, ' + a + ')'+}++function tempToRGB(t) {+  if (t < 0.32) {+    return [1, 0.5 + 0.5 * t / 0.32, 0.5]+  }+  if (t < 0.64) {+    return [1, 1, 0.5 + 0.5 * (t - 0.32) / 0.32]+  }+  return [0.75 + 0.25 * (1 - t) / 0.36, 0.75 + 0.25 * (1 - t) / 0.36, 1]+}++function walkPositions(l, state, visit) {+  // parent cache+  var pc = {+    x2: 0,+    y2: 0,+    scale: 1,+    angle: 0,+  }+  walkTree(l, function visitPosition(node, descend) {+    var nodeState = state[node.id]+    var c = node.cache+    if (!c) {+      c = node.cache = {}  // eslint-disable-line no-param-reassign+    }+    c.scale = pc.scale * node.scale+    c.angle = pc.angle + nodeState.angle+    var dx = node.attach[0] - node.root[0]+    var dy = node.attach[1] - node.root[1]+    c.x1 = pc.x2+    c.y1 = pc.y2+    c.x2 = c.x1 + c.scale * (dx * cos(c.angle) + dy * sin(c.angle))+    c.y2 = c.y1 + c.scale * (-dx * sin(c.angle) + dy * cos(c.angle))++    var origPC = pc+    pc = c+    visit(node, descend)+    pc = origPC+  })+}++function calcBox(node) {+  var c = node.cache+  var bx1 = c.x1 - node.root[0]+  var by1 = c.y1 - node.root[1]+  return rotBounds(+    -c.angle,+    bx1 + node.root[0],+    by1 + node.root[1],+    bx1,+    by1,+    bx1 + c.scale * c.width,+    by1 + c.scale * c.height+  )+}++function renderLinden(l, state, lights, opts) {+  var w = opts.width+  var h = opts.height+  var debug = opts.debug+  var urlPrefix = opts.urlPrefix+  var imgPrefix = ''+  var imgCache = opts.imgCache+  var ctx = opts.el.getContext('2d')+  var density = Number(opts.el.getAttribute('data-density'))++  if (density > 1) {+    imgPrefix = '2x-'+  }++  var imgs = []+  walkTree(l, function visitImg(node, descend) {+    if (node.img) {+      imgs.push(urlPrefix + imgPrefix + node.img)+    }+    descend()+  })++  function getState(node, prop) {+    return state[node.id][prop]+  }++  imgCache.getAll(imgs, function withImgs(loadedImgs) {+    ctx.save()+    ctx.scale(density, -density)+    ctx.translate(0, -h)+    ctx.clearRect(0, 0, w, h)++    var circledNode+    walkPositions(l, state, function visitDraw(node, descend) {+      var c = node.cache+      ctx.save()+      ctx.rotate(getState(node, 'angle') * -DEG)+      ctx.scale(node.scale, node.scale)+      if (state._selected) {+        if (node.id === state._selected.id) {+          ctx.globalAlpha = 0.5+        }+        if (node.id === state._selected.circled) {+          circledNode = node+        }+      }+      if (node.img) {+        var imgUrl = urlPrefix + imgPrefix + node.img+        var img = loadedImgs[imgUrl]+        if (img) {+          ctx.save()+          ctx.scale(1 / density, -1 / density)+          ctx.translate(0, -img.height)+          ctx.drawImage(img, -node.root[0] * density, node.root[1] * density)+          ctx.restore()+          c.width = img.width / density+          c.height = img.height / density+        }+      }+      ctx.translate(node.attach[0] - node.root[0], node.attach[1] - node.root[1])+      descend()+      ctx.restore()+    })++    // lights+    var mixCanvas = document.createElement('canvas')+    mixCanvas.width = opts.el.width+    mixCanvas.height = opts.el.height+    var lctx = mixCanvas.getContext('2d')+    lctx.scale(density, density)+    var lightRadius = Math.sqrt(Math.pow(w, 2) + Math.pow(h, 2))+    function drawLights(callback) {+      for (var i = 0; i < lights.length; i++) {+        var light = lights[i]+        lctx.save()+        lctx.beginPath()+        lctx.moveTo(light.pos[0], light.pos[1])+        var x1 = light.pos[0] + lightRadius * cos(light.pointAngle - light.beamAngle / 2)+        var y1 = light.pos[1] + lightRadius * sin(light.pointAngle - light.beamAngle / 2)+        lctx.lineTo(x1, y1)+        lctx.arc(light.pos[0], light.pos[1], lightRadius,+          (light.pointAngle - light.beamAngle / 2) * DEG,+          (light.pointAngle + light.beamAngle / 2) * DEG+        )+        lctx.lineTo(light.pos[0], light.pos[1])+        callback(light)+        lctx.restore()+      }+    }+    drawLights(function drawLightFill(light) {+      var grad = lctx.createRadialGradient(+        light.pos[0], light.pos[1], 0,+        light.pos[0], light.pos[1], lightRadius+      )+      var color = tempToRGB(light.temp)+      grad.addColorStop(0, rgba(color[0], color[1], color[2], 1))+      grad.addColorStop(1, rgba(color[0], color[1], color[2], 0))+      lctx.fillStyle = grad+      lctx.globalCompositeOperation = 'lighten'+      lctx.fill()+    })+    drawLights(function drawLightStroke(light) {+      lctx.globalAlpha = 0.4+      lctx.strokeStyle = 'black'+      lctx.stroke()+    })+    ctx.save()+    ctx.scale(1 / density, 1 / density)+    ctx.globalCompositeOperation = 'source-atop'+    ctx.globalAlpha = 0.65+    ctx.drawImage(mixCanvas, 0, 0)+    ctx.globalAlpha = 0.25+    ctx.globalCompositeOperation = 'destination-over'+    ctx.drawImage(mixCanvas, 0, 0)+    ctx.restore()++    if (circledNode) {+      var c = circledNode.cache+      ctx.save()+      ctx.beginPath()+      var circledBox = calcBox(circledNode)+      var cx = circledBox.x1 + (circledBox.x2 - circledBox.x1) / 2+      var cy = circledBox.y1 + (circledBox.y2 - circledBox.y1) / 2+      var d = Math.sqrt(Math.pow(circledBox.x2 - circledBox.x1, 2) + Math.pow(circledBox.y2 - circledBox.y1, 2))+      ctx.arc(cx, cy, d / 2 + 6, 0, TWOPI)+      ctx.strokeStyle = 'red'+      ctx.lineWidth = 2+      ctx.globalAlpha = 0.9+      ctx.stroke()+      ctx.restore()+    }++    if (debug) {+      walkTree(l, function visitDrawDebug(node, descend) {+        ctx.save()+        var c2 = node.cache+        ctx.beginPath()+        ctx.globalAlpha = 0.75+        ctx.moveTo(c2.x1, c2.y1)+        ctx.lineTo(c2.x2, c2.y2)+        ctx.strokeStyle = node.proxyFor ? 'lightgreen' : 'magenta'+        ctx.lineWidth = 1+        ctx.stroke()+        descend()+        ctx.restore()+        if (c2.width) {+          var box = calcBox(node)+          ctx.beginPath()+          ctx.rect(box.x1, box.y1, (box.x2 - box.x1), (box.y2 - box.y1))+          ctx.strokeStyle = 'red'+          ctx.stroke()+        }+      })+    }++    ctx.restore()++    if (opts.onFinish) {+      opts.onFinish()+    }+  })+}++x.component('Handle', {+  componentDidMount: function componentDidMount() {+    this._handleDragMove = this.handleDragMove.bind(this)+    this._handleDragUp = this.handleDragUp.bind(this)++    // ontouchstart attribute doesn't seem to work :(+    this.getDOMNode().firstChild.addEventListener('touchstart', this.handleDragDown.bind(this))+  },++  _stopListening: function _stopListening() {+    window.removeEventListener('mousemove', this._handleDragMove, false)+    window.removeEventListener('mouseup', this._handleDragUp, false)+    window.removeEventListener('touchmove', this._handleDragMove, false)+    window.removeEventListener('touchend', this._handleDragUp, false)+  },++  componentWillUnmount: function componentWillUnmount() {+    this._stopListening()+  },++  _normalizeEvent: function _normalizeEvent(ev) {+    if (ev.touches && ev.touches[0]) {+      ev.clientX = ev.touches[0].clientX  // eslint-disable-line no-param-reassign+      ev.clientY = ev.touches[0].clientY  // eslint-disable-line no-param-reassign+    }+  },++  handleDragDown: function handleDragDown(ev) {+    if (this.props.disabled) {+      return+    }+    document.documentElement.style.cursor = 'move'+    window.addEventListener('mousemove', this._handleDragMove, false)+    window.addEventListener('mouseup', this._handleDragUp, false)+    window.addEventListener('touchmove', this._handleDragMove, false)+    window.addEventListener('touchend', this._handleDragUp, false)+    if (this.props.onGrab) {+      this._normalizeEvent(ev)+      this.props.onGrab(ev)+    }+    ev.preventDefault()+  },++  handleDragUp: function handleDragUp(ev) {+    document.documentElement.style.cursor = null+    this._stopListening()+    if (this.props.onRelease) {+      this._normalizeEvent(ev)+      this.props.onRelease(ev)+    }+    ev.preventDefault()+  },++  handleDragMove: function handleDragMove(ev) {+    if (this.props.onDrag) {+      this._normalizeEvent(ev)+      this.props.onDrag(ev)+    }+    ev.preventDefault()+  },++  render: function render() {+    // FIXME: diablo issue: undefined / null gets stringified+    return x`<div+      className=${classNames('handle', this.props.className)}+      style=${this.props.style || ''}+      onMouseDown=${this.handleDragDown.bind(this)}+    />`+  },+})++x.component('Light', {+  handleRotate: function handleRotate(ev) {+    // TODO: key?+    var box = this.getDOMNode().firstChild.getBoundingClientRect()+    var px = ev.clientX - box.left+    var py = ev.clientY - box.top+    var angle = mod(-Math.atan2(py, px) / DEG, 360)+    var distance = Math.sqrt(Math.pow(px, 2) + Math.pow(py, 2))+    this.props.onRotate(angle, clamp(0, 95 - (distance - 42) * 3, 95))+  },++  handleRotateColor: function handleRotateColor(ev) {+    var box = this.getDOMNode().firstChild.getBoundingClientRect()+    var px = ev.clientX - box.left+    var py = ev.clientY - box.top+    var angle = clamp(90, mod(-Math.atan2(py, px) / DEG - this.props.pointAngle, 360), 270) - 90+    this.props.onChangeColor(angle / 180)+  },++  render: function render() {+    var style = inlineStyle({+      left: this.props.x + 'px',+      top: this.props.y + 'px',+      transform: `rotate(${-this.props.pointAngle - 90}deg)`,+    })++    var handles = ''+    if (this.props.selected) {+      var d = 42 + clamp(0, (95 - this.props.beamAngle) / 3, 95)+      var rotHandlePosStyle = inlineStyle({+        position: 'absolute',+        top: d + 'px',+      })++      var colorAngle = 180 * this.props.temp+      var color = tempToRGB(this.props.temp)+      var colorHandlePosStyle = inlineStyle({+        position: 'absolute',+        left: 39 * cos(-colorAngle) + 'px',+        top: 39 * sin(-colorAngle) + 'px',+      })+      var colorHandleStyle = inlineStyle({+        backgroundColor: rgba(color[0], color[1], color[2], 1),+      })++      handles = x`+        <div>+          <div style=${rotHandlePosStyle}>+            <Handle+              className="rotate"+              disabled=${this.props.disabled}+              onDrag=${this.handleRotate.bind(this)}+              onRelease=${this.props.onSave}+            ></Handle>+          </div>+          <div style=${colorHandlePosStyle}>+            <Handle+              className="color"+              style=${colorHandleStyle}+              disabled=${this.props.disabled}+              onDrag=${this.handleRotateColor.bind(this)}+              onRelease=${this.props.onSave}+            ></Handle>+          </div>+        </div>+      `+    }++    return x`+      <div+        className=${classNames('light', this.props.selected && 'selected')}+        style=${style}+        data-light-id=${this.props.id}+      >+        <div>+          <Handle+            className="position"+            disabled=${this.props.disabled}+            onGrab=${this.props.onInteract}+            onDrag=${this.props.onMove}+            onRelease=${this.props.onSave}+          ></Handle>+        </div>+        ${handles}+      </div>+    `+  },+})++function Linden(opts) {+  this.el = opts.el+  this.opts = opts+  this.imgCache = new ImgCache()+  this._queued = false+  this._rendering = false+  this._afterNextRender = null+  this.resetTree()+  this.renderUI()+}+Linden.prototype = {+  STIFFNESS: 0.25,+  FRICTION: 0.1,+  MAX_LIGHTS: 3,+  FPS: 15,++  resetTree: function resetTree() {+    this.l = null+    this.lights = []+    this.state = { _selected: null }+  },++  renderUI: function renderUI() {+    var tools = ''+    if (!this.opts.readOnly && !this.opts.loading) {+      tools = x`+        <div className="tools">+          <button+            className="add-light"+            disabled=${this.lights.length === this.MAX_LIGHTS}+            title="Create new light"+            onClick=${this.handleCreateLight.bind(this)}+          >+</button>+          <button+            className="remove"+            disabled=${!this.state._selected}+            title="Remove selected (delete key)"+            onClick=${this.handleRemoveSelection.bind(this)}+          >x</button>+        </div>+      `+    }++    var style = inlineStyle({+      width: this.opts.width + 'px',+      height: this.opts.height + 'px',+    })++    var canvasMultiplier = window.devicePixelRatio > 1 ? 2 : 1+    var canvasStyle = inlineStyle({+      transformOrigin: '0 0',+      transform: `scale(${1 / canvasMultiplier}, ${1 / canvasMultiplier})`,+    })++    var className = classNames('frame', this.opts.loading && 'loading')+    x.render(x`+      <div+        onMouseDown=${this.handleMouseDown.bind(this)}+        onKeyDown=${this.handleKeyDown.bind(this)}+        style=${style}+        className=${className}+        title=${this.opts.title}+        tabIndex="1"+      >+        ${tools}+        ${this.opts.loading ? x`<div><div className="spinner"></div><div className="spinner-text">loading</div>` : ''}+        <div className="content">+          <canvas+            data-density=${canvasMultiplier}+            width=${this.opts.width * canvasMultiplier}+            height=${this.opts.height * canvasMultiplier}+            style=${canvasStyle}+            onMouseDown=${this.handleCanvasMouseDown.bind(this)}+          ></canvas>+          ${this.lights.map(function renderLight(light) {+            var selected = Boolean(+              this.state._selected &&+              this.state._selected.kind === 'light' &&+              this.state._selected.id === light.id+            )+            return x`<Light+              id=${light.id}+              disabled=${this.opts.readOnly}+              selected=${selected}+              x=${light.pos[0]}+              y=${this.opts.height - light.pos[1]}+              temp=${light.temp}+              pointAngle=${light.pointAngle}+              beamAngle=${light.beamAngle}+              onInteract=${this.handleLightInteract.bind(this, light.id)}+              onMove=${this.handleLightMove.bind(this, light.id)}+              onRotate=${this.handleLightRotate.bind(this, light.id)}+              onChangeColor=${this.handleLightChangeColor.bind(this, light.id)}+              onSave=${this.saveLight.bind(this, light.id)}+            />`+          }.bind(this))}+        </div>+      </div>+    `, this.el)+  },++  update: function update(data) {+    this.l = data.tree++    var nodeCount = 0+    var nodeTypeCount = {}+    walkTree(this.l, function visitInitState(node, descend) {+      nodeCount+++      var nodeType = node.img || 'proxy'+      nodeTypeCount[nodeType] = nodeTypeCount[nodeType] || {count: 0}+      nodeTypeCount[nodeType].count++++      var nodeState = this.state[node.id]+      if (!nodeState) {+        nodeState = this.state[node.id] = {+          angle: node.angle,+          av: 0,+        }+      }+      nodeState.node = node+      descend()+    }.bind(this))++    this.lights = data.lights || []++    var startTime = Date.now()++    this._afterNextRender = function afterRender() {+      this.opts.loading = false+      this.renderUI()+      this._afterNextRender = null++      if (this.opts.debug) {+        var duration = Date.now() - startTime+        console.debug('updated. rendered', nodeCount, 'nodes in', duration, 'ms', '| supply:', data.supply)+        console.table(nodeTypeCount)+      }+    }.bind(this)++    this.renderCanvas()+  },++  addWind: function addWind(angle, magnitude, spread) {+    walkTree(this.l, function visitWind(node, descend) {+      if (!node.rigid && node.cache) {+        var nodeState = this.state[node.id]+        var alignment = sin(angle - node.cache.angle)+        var spreadMag = (1 - spread) + Math.random() * spread+        nodeState.av += node.waviness * spreadMag * (1 / Math.sqrt(node.cache.scale)) * alignment * magnitude+      }+      descend()+    }.bind(this))++    this.queueStep()+  },++  queueStep: function queueStep() {+    if (!this._queued) {+      // note that actual FPS is below this.FPS since we don't adapt sleep time+      setTimeout(this.step.bind(this), 1000 / this.FPS)+      this._queued = Date.now()+    }+  },++  step: function step() {+    if (this.opts.beforeStep) {+      this.opts.beforeStep()+    }++    walkTree(this.l, function visitStep(node, descend) {+      var nodeState = this.state[node.id]+      nodeState.av += (1 - node.waviness) * node.scale * this.STIFFNESS * (node.angle - nodeState.angle)+      nodeState.av += -nodeState.av * this.FRICTION+      nodeState.angle += nodeState.av+      descend()+    }.bind(this))++    this.renderCanvas()++    this._queued = false+    this.queueStep()+  },++  renderCanvas: function renderCanvas() {+    if (this._rendering || !this.l) {+      return+    }+    this._rendering = true+    renderLinden(this.l, this.state, this.lights, {+      width: this.opts.width,+      height: this.opts.height,+      el: this.el.querySelector('canvas'),+      urlPrefix: this.opts.urlPrefix,+      imgCache: this.imgCache,+      debug: this.opts.debug,+      onFinish: function onRenderFinish() {+        if (this._afterNextRender) {+          this._afterNextRender()+        }+        this._rendering = false+      }.bind(this),+    })+  },++  handleMouseDown: function handleMouseDown() {+    this.el.querySelector('.frame').focus()+  },++  _deselect: function _deselect() {+    this.state._selected = null+    this.renderCanvas()+    this.renderUI()+  },++  handleCanvasMouseDown: function handleCanvasMouseDown(ev) {+    if (this.opts.readOnly) {+      return+    }+    this._deselect()++    var box = this.el.getBoundingClientRect()+    var x0 = ev.clientX - box.left+    var y0 = this.opts.height - (ev.clientY - box.top)+    var hit = null+    var hitArea = Number.MAX_VALUE+    walkTree(this.l, function visitCheckProximity(node, descend) {+      var c = node.cache+      if (c && c.width && !node.immutable) {+        var box = calcBox(node)+        if (box.x1 <= x0 && box.x2 >= x0 &&+            box.y1 <= y0 && box.y2 >= y0) {+          var area = (box.x2 - box.x1) * (box.y2 - box.y1)+          if (area < hitArea) {+            hit = node+            hitArea = area+          }+        }+      }+      descend()+    })++    if (hit) {+      var selectedId = hit.proxyFor || hit.id+      this.state._selected = { kind: 'node', id: selectedId, circled: hit.id }+    }+    this.renderCanvas()+    this.renderUI()+  },++  handleKeyDown: function handleKeyDown(ev) {+    if (ev.which === 46 || ev.which === 8) { // backspace or delete+      this.handleRemoveSelection()+      ev.preventDefault()+    }+  },++  handleLightInteract: function handleLightInteract(id) {+    if (this.opts.readOnly) {+      return+    }+    this.state._selected = { kind: 'light', id: id }+    this.renderCanvas()+    this.renderUI()+  },++  handleLightMove: function handleLightMove(id, ev) {+    var box = this.el.getBoundingClientRect()+    var px = clamp(0, ev.clientX - box.left, this.opts.width)+    var py = clamp(0, this.opts.height - (ev.clientY - box.top), this.opts.height)+    this.setLight(id, {+      pos: [px, py],+    })+  },++  handleLightRotate: function handleLightRotate(id, pointAngle, beamAngle) {+    this.setLight(id, {+      pointAngle: pointAngle,+      beamAngle: beamAngle,+    })+  },++  handleLightChangeColor: function handleLightChangeColor(id, temp) {+    this.setLight(id, {+      temp: temp,+    })+  },++  setLight: function setLight(id, data) {+    this.opts.onLightChanging(id)+    var idx = this.lights.findIndex(function matchLight(light) { return light.id === id })+    if (idx === -1) {+      return+    }+    Object.assign(this.lights[idx], data)+    this.renderCanvas()+    this.renderUI()+  },++  saveLight: function saveLight(id) {+    var idx = this.lights.findIndex(function matchLight(light) { return light.id === id })+    if (idx === -1) {+      return+    }+    this.opts.onSetLight(id, this.lights[idx])+  },++  handleCreateLight: function handleCreateLight() {+    var lx = this.opts.width * Math.random()+    var ly = (this.opts.height + this.opts.height * Math.random()) / 2+    var tx = this.opts.width / 2+    var ty = 0+    this.opts.onSetLight(null, {+      pos: [lx, ly],+      pointAngle: Math.atan2(ty - ly, tx - lx) / DEG,+      beamAngle: 45 + 45 * Math.random(),+      temp: 0.5,+    })+  },++  handleRemoveSelection: function handleRemoveSelection() {+    if (!this.state._selected) {+      return+    }++    if (this.state._selected.kind === 'node') {+      this.opts.onPruneNode(this.state._selected.id)+    } else if (this.state._selected.kind === 'light') {+      this.opts.onRemoveLight(this.state._selected.id)+    }++    this._deselect()+  },+}++module.exports = Linden
+ client/src/index.js view
@@ -0,0 +1,34 @@+require('core-js/fn/object/assign')+require('core-js/fn/array/map')+require('core-js/fn/array/find-index')+require('core-js/fn/array/filter')+require('core-js/fn/date/now')+require('core-js/es6/promise')+require('whatwg-fetch')++require('./index.less')+var GardenClient = require('./GardenClient')++var scripts = document.getElementsByTagName('script')+var script = scripts[scripts.length - 1]++window.garden = new GardenClient({+  name: 'garden',+  el: document.getElementById('comic'),+  width: 740,+  height: 462,+  title: script.getAttribute('data-title'),+  endpoint: script.getAttribute('data-endpoint'),+  imgPrefix: script.getAttribute('data-image-endpoint'),+  spread: {+    reload: 60,+  },+  debug: location.search === '?debug',+}).run()++window.addEventListener('keydown', function preventBackNav(ev) {+  // prevent backspace from navigating+  if (ev.which === 8) {+    ev.preventDefault()+  }+}, false)
+ client/src/index.less view
@@ -0,0 +1,166 @@+#comic {+  position: relative;+  display: inline-block;+  outline: 2px solid black;+  overflow: hidden;+  user-select: none;++  .frame {+    background: white;+    transition: background-color .5s ease;++    &:focus {+      outline: none;+    }++    .content {+      opacity: 1;+      transition: opacity .5s ease;+    }++    &.loading {+      background: white;++      .content {+        opacity: 0;+      }+    }+  }++  .spinner {+    position: absolute;+    left: 50%;+    top: 45%;+    width: 50px;+    height: 52px;+    margin-left: -30px;+    margin-top: -26px;+    background-image: url('../../art/loading.png');+    background-size: contain;++    animation-name: rotate;+    animation-iteration-count: infinite;+    animation-timing-function: linear;+    animation-duration: 2s;+    transform-origin: 56% 50%;+    @keyframes rotate {+      0% {+        transform: rotateY(0);+      }+      50% {+        transform: rotateY(360deg);+      }+      100% {+        transform: rotateY(0);+      }+    }+  }++  .spinner-text {+    font-size: 0;+    position: absolute;+    left: 50%;+    top: 53%;+    width: 91px;+    height: 17px;+    margin-left: -91px / 2;+    background-image: url('../../art/loading.gif');+    background-repeat: no-repeat;+  }++  .light {+    position: absolute;+    z-index: 200;++    .handle {+      position: absolute;+      transform: translate(-50%, -50%);++      &.position {+        width: 40px;+        height: 44px;+        background-image: url('../../art/light.png');+        background-size: contain;+      }++      &.rotate {+        width: 0;+        height: 0;+        background: transparent;+        border-left: 10px solid transparent;+        border-right: 10px solid transparent;+        border-top: 10px solid red;+      }++      &.color {+        width: 8px;+        height: 8px;+        background: black;+        border: 2px solid black;+        border-radius: 100%;+      }+    }++    &.selected:before {+      content: '';+      display: block;+      position: absolute;+      left: -40px;+      right: -40px;+      top: -40px;+      bottom: -40px;+      border: 2px solid red;+      border-radius: 100%;+      opacity: .75;+    }+  }++  .tools {+    position: absolute;+    right: 0;+    z-index: 100;++    button {+      display: block;+      width: 40px;+      height: 40px;+      padding: 0;+      margin: 10px;+      box-sizing: content-box;+      font-size: 0;+      color: transparent;+      background: #ddd;+      background-size: contain;+      border: 1px solid #333;+      border-radius: 3px;+      transition: opacity .15s ease;+      opacity: .75;++      &:hover {+        opacity: 1;+      }++      &:disabled {+        opacity: .25;+      }++      &.add-light {+        background-image: url('../../art/add-light.png');+      }++      &.remove {+        background-color: #d22;+        background-image: url('../../art/remove.png');++        &:disabled {+          background-color: #777;+        }+      }+    }+  }++  #data {+    width: 100%;+    height: 10em;+  }+}
+ client/src/randomWind.js view
@@ -0,0 +1,19 @@+var clamp = require('clamp')++module.exports = function randomWind(linden) {+  var gust = 0+  var angle = 90++  function updateWind() {+    var jitter = 2 * (Math.random() - 0.5)+    if (Math.random() > 0.9) {+      gust += Math.random() - 0.5+    }+    angle = clamp(60, angle + 2 * (Math.random() - 0.5), 120)+    gust *= 0.995+    linden.addWind(angle, 0.025 * jitter + 0.25 * gust, 0.5)+  }++  linden.opts.beforeStep = updateWind  // eslint-disable-line no-param-reassign+  updateWind()+}
+ client/src/walkTree.js view
@@ -0,0 +1,12 @@+module.exports = function walk(obj, visit) {+  if (!obj) {+    return+  }++  function descend() {+    for (var i = 0; i < obj.children.length; i++) {+      walk(obj.children[i], visit)+    }+  }+  visit(obj.node, descend)+}
+ client/webpack.config.js view
@@ -0,0 +1,35 @@+var path = require('path')+var webpack = require('webpack')++module.exports = {+  entry: {+    linden: './src/index.js',+  },++  output: {+    filename: '[name].js',+    path: path.join(__dirname, 'build'),+  },++  module: {+    loaders: [+      {+        test: /\.js$/,+        exclude: /node_modules/,+        loader: 'babel',+      },+      {+        test: /\.less$/,+        loaders: ['style', 'css', 'autoprefixer', 'less'],+      },+      {+        test: /\.png$|\.gif$/,+        loader: 'url',+      },+    ],+  },++  plugins: [+    new webpack.BannerPlugin('linden by chromako.de'),+  ],+}
+ extract_grips.py view
@@ -0,0 +1,142 @@+#!/usr/bin/python+from __future__ import print_function+import sys+import gc+import glob, os+import shutil+import hashlib+import json++from PIL import Image++mirror_names = glob.glob("tiff/*-m.tif")+for m in mirror_names:+    im = Image.open(m)+    im_mirrored = im.transpose(Image.FLIP_LEFT_RIGHT)+    im.save(m.replace("-m.tif", "-l.tif"))+    im_mirrored.save(m.replace("-m.tif", "-r.tif"))++FROM_DPI = 1200+TO_DPI_2X = 2 * 80+RATIO_2X = float(TO_DPI_2X) / float(FROM_DPI)+RATIO_1X = RATIO_2X / 2++def print_status(*args):+    print(*args, end=" ")+    sys.stdout.flush()++def pixIn(b, p):+    g = 4+    return (((b['x1']-g) < p[0]) and ((b['x2']+g) > p[0]) and ((b['y1']-g) < p[1]) and ((b['y2']+g) > p[1]))++def boxCenter(b):+    return { "x": (b['x1']+b['x2'])/2+           , "y": ((b['y1']+b['y2'])/2)-1 }++def boxAdd(b, cx, cy):+    if b:+        b['x1'] = min(b['x1'], cx)+        b['x2'] = max(b['x2'], cx)+        b['y1'] = min(b['y1'], cy)+        b['y2'] = max(b['y2'], cy)+    else:+        b = {'x1': cx, 'x2': cx, 'y1':cy, 'y2': cy}+    return b++def floodFill(im, pix, color, cx, cy, to_color=(0, 0, 0)):+    global b+    b = None++    global queue+    queue = [(cx, cy)]++    def visit((px, py)):+        global b+        global queue++        try:+            p = pix[px, py][:3]+        except IndexError:+            return++        if p == color:+            b = boxAdd(b, px, im.size[1] - py)+            pix[px, py] = to_color+            queue.append((px + 1, py))+            queue.append((px - 1, py))+            queue.append((px, py + 1))+            queue.append((px, py - 1))++    while queue:+        visit(queue.pop(0))++    return b and boxCenter(b)++if not os.path.exists("assets"):+    os.makedirs("assets")++art_names = glob.glob("tiff/*.tif")++sources_seen = 0+print("")+print_status("| generating...")+for in_name in art_names:+    sources_seen += 1+    print_status(": [{} of {}]".format(sources_seen, len(art_names)), in_name, "|")+    print_status("h")+    with open(in_name, "r") as im_data:+        im_hash = hashlib.sha1(im_data.read()).hexdigest()[:16]+    print_status("l")+    im = Image.open(in_name)+    grip_name = os.path.join("assets", "{}.json".format(im_hash))+    if os.path.exists(grip_name):+        print_status("=")+    else:+        print_status("g")+        root = []+        attach = []+        pix = im.load()+        for cx in range(im.size[0]):+            for cy in range(im.size[1]):+                p = pix[cx, cy][:3]+                if p == (255, 255, 0) or p == (0, 0, 255): # root+                    to_color = (255, 255, 255, 0) if p[2] == 255 else (0, 0, 0, 255)+                    root.append(floodFill(im, pix, p, cx, cy, to_color))+                    print_status("a")+                elif p == (255, 0, 0) or p == (0, 255, 0): # attach+                    to_color = (255, 255, 255, 0) if p[1] == 255 else (0, 0, 0, 255)+                    attach.append(floodFill(im, pix, p, cx, cy, to_color))+                    print_status("r")++        # scale points to 1x pixel density+        for points in (root, attach):+            for point in points:+                point["x"] = int(point["x"] * RATIO_1X)+                point["y"] = int(point["y"] * RATIO_1X)++        with open(grip_name, "w") as f:+            json.dump({"attach": attach, "root": root and root[0]}, f)++        fname = os.path.basename(in_name)+        fbarename = os.path.splitext(fname)[0]++        for ratio, prefix in ((RATIO_1X, ''), (RATIO_2X, '2x-')):+            resized_name = os.path.join("assets", "{}{}.png".format(prefix, fbarename))+            print_status("r")+            im_resized = im.resize((int(im.size[0] * ratio), int(im.size[1] * ratio)), Image.ANTIALIAS)++            im_resized = im_resized.convert('RGBA')+            pix_resized = im_resized.load()+            # convert white to alpha+            for cx in range(im_resized.size[0]):+                for cy in range(im_resized.size[1]):+                    p = pix_resized[cx, cy]+                    pix_resized[cx, cy] = (0, 0, 0, 255 - (sum(p[:3]) / 3))++            print_status("w")+            if im_resized.info.has_key("icc_profile"):+                del im_resized.info["icc_profile"]+            im_resized.save(resized_name, "PNG", optimize=True)++    print(".")+
+ linden.cabal view
@@ -0,0 +1,87 @@+name:                linden+version:             0+synopsis:            Zen gardening, based on l-systems+description:         A system for defining and running interactive l-systems over the web.+homepage:            http://xkcd.com/1663/+license:             BSD3+license-file:        LICENSE+author:              davean+maintainer:          davean@xkcd.com+copyright:           davean 2016+category:            Web+build-type:          Simple+cabal-version:       >=1.10++data-files:+  art/*.png+  art/*.svg+  client/package.json+  client/webpack.config.js+  client/build/index.html+  client/build/linden.js+  client/src/*.js+  client/src/*.less+  tiff/*.tif+  extract_grips.py+  assets/*.png+  assets/*.json++source-repository head+  type: git+  location: https://code.xkrd.net/xkcd/linden.git++library+  hs-source-dirs:      src+  default-language:    Haskell2010+  exposed-modules:+          Linden.TH+        , Linden.Types+        , Linden.RuleDSL+        , Linden.Store.Memory+        , Linden.Store.Redis+        , Linden.Server+  build-depends:+                base >=4.9 && <4.11+              , bifunctors >= 5.4 && < 5.5+              , deepseq >= 1.4 && < 1.5+              , containers >= 0.5 && < 0.6+              , rosezipper >= 0.2 && < 0.3+              , random-fu >= 0.2.6.2+              , aeson >= 1.2 && < 1.3+              , mtl >= 2.2 && < 2.3+              , monad-supply == 0.6.*+              , bytestring >= 0.10 && < 0.11+              , text >= 1.2 && < 1.3+              , wai >= 3.2 && < 3.3+              , http-types >= 0.9 && < 0.10+              , wai-extra >= 3.0 && < 3.1+              , wai-app-static >= 3.1 && < 3.2+              , wai-cors >= 0.2 && < 0.3+              , delay >= 0 && < 0.1+              , time >= 1.7 && < 1.9+              , filepath >= 1.4 && < 1.5+              , SHA >= 1.6 && < 1.7+              , uuid >= 1.3 && < 1.4+              , unordered-containers >= 0.2 && < 0.3+              , lens >= 4.15 && < 4.16+              , exceptions >= 0.8 && < 0.9+              , hashable >= 1.2 && < 1.3+              , hedis >= 0.9 && < 0.10+              , zlib >= 0.6 && < 0.7++executable linden-example+  default-language:    Haskell2010+  ghc-options: -rtsopts -threaded+  main-is: Linden.hs+  other-modules: Paths_linden+  build-depends:+                base >=4.9 && <4.11+              , linden+              , containers+              , rosezipper+              , mtl+              , random-fu+              , warp >= 3.2 && < 3.3+              , uuid+              , hashable+              , text
+ src/Linden/RuleDSL.hs view
@@ -0,0 +1,261 @@+{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}+module Linden.RuleDSL (+   RuleContext(..), RuleDSL, compileRules+ , addOption+ , doNothing, deleteMe, addBranch, addBranches+ , alreadyExists, noDup, nodeCount, maxNodes+ , curBranch, gaiaPathSyms, angleSum, angleToVertical+ , distance, distanceSq, curChildren+ , lightCones, lightHits, lightAng, lightIntensity+ , colorTemp+ , materialChildrenSyms, directSiblings+ , weighted, readBranchSym, attachLocation, allLights+ -- To suppress warnings about constructors used for TH naming+ , pX, pY, gRoot, gAttach+ , Point(..), Grips(..)+ ) where++import Data.Char+import Data.Maybe+import Data.Tree+import Data.Tree.Zipper+import Data.Bifunctor+import Data.Biapplicative+import Data.Random.RVar+import Data.Random.Distribution.Categorical+import Control.Monad.Identity+import Control.Monad.Reader+import Control.Monad.Writer.Strict+import Control.Monad.Supply (MonadSupply(..))+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BSL+import Data.Aeson ((.:))+import qualified Data.Aeson as JS+import qualified Data.Text as T+import GHC.Generics+import System.FilePath+import Data.Digest.Pure.SHA (sha1, showDigest)+import qualified Data.Map.Lazy as Map+import Data.Hashable (hash)++import Debug.Trace++import Linden.Types++data Point =+    Point { pX :: Int, pY :: Int }+  deriving (Read, Show, Eq, Ord, Generic)++instance JS.ToJSON Point where+  toJSON (Point x y) = JS.object [("x", JS.toJSON x), ("y", JS.toJSON y)]++instance JS.FromJSON Point where+  parseJSON (JS.Object o) =+    Point <$> o .: "x" <*> o .: "y"+  parseJSON _ = mzero++data Grips =+  Grips {+    gRoot :: Point+  , gAttach :: [Point]+  }+  deriving (Read, Show, Eq, Ord, Generic)++instance JS.ToJSON Grips where+  toJSON (Grips r a) = JS.object [("attach", JS.toJSON a), ("root", JS.toJSON r)]++instance JS.FromJSON Grips where+  parseJSON (JS.Object o) =+    Grips <$> o .: "root" <*> o .: "attach"+  parseJSON _ = mzero++unPoint :: Point -> (Int, Int)+unPoint (Point x y) = (x, y)++readBranchSym :: [Int] -> FilePath -> ReaderT FilePath IO BranchSym+readBranchSym = readBranchSym'++readBranchSym' :: [Int] -> FilePath -> ReaderT FilePath IO BranchSym+readBranchSym' cls fp = do+  bp <- ask+  imfl <- liftIO $ BSL.readFile (bp </> "tiff" </> (addExtension fp "tif"))+  let imhash = take 16 . showDigest . sha1 $ imfl+  let gfl = bp </> "assets" </> (take 16 imhash++".json")+  gjson <- liftIO $ BS.readFile gfl+  case JS.eitherDecodeStrict' gjson of+    Left err -> fail $ "Failed to parse json of "++fp++" because "++show err+    Right (Grips (Point rX rY) as) ->+        return $ BranchSym (Just . T.pack $ addExtension fp "png")+                           (rX, rY) (map unPoint as) False False (map hash cls)++weighted :: [(Double, LEnv EditCommand)] -> RVar (LEnv EditCommand)+weighted = weightedCategorical++data RuleContext =+    RC {+      rcLights :: [Light]+    , rcAttachLocs :: BranchPossition+    , rcTreePos :: TreePos Full Branch+    }+  deriving (Read, Show, Eq)++type RuleDSL a = ReaderT RuleContext (WriterT [(Double, LEnv EditCommand)] RVar) a++compileRules :: RuleDSL () -> Rule+compileRules act bps lights tp =+    weighted =<< (execWriterT . runReaderT act $ RC lights bps tp)++curBranch :: RuleDSL Branch+curBranch = label <$> asks rcTreePos++curChildren :: RuleDSL Int+curChildren = (length . offspring) <$> asks rcTreePos++directSiblings :: RuleDSL Int+directSiblings =+  (fromMaybe 0 . fmap ((1-) . length . subForest . tree) . parent) <$> asks rcTreePos++-- | Finds if a command adds an edge that already exists.+alreadyExists :: LEnv EditCommand -> RuleDSL Bool+alreadyExists mc = do+  let c = runLEnv mc (Supply 0) -- We're an ID-independant comparison.+  case fst c of+    DeleteMe -> return False+    DoNothing -> return False+    AddChildren nsf -> do+      esf <- offspring <$> asks rcTreePos+      return . or $ [ ((bAngle nc) == (bAngle ec)) && ((bImg nc) == (bImg ec))+                    | nc <- rootLabel <$> nsf, ec <- fmap label esf]++nodeCount :: RuleDSL Int+nodeCount = (length . flatten . toTree) <$> asks rcTreePos++maxNodes :: Int -> RuleDSL () -> RuleDSL ()+maxNodes mx act = do+  nc <- nodeCount+  case nc of+    l | l >= mx -> doNothing 1+    _ -> act++isMaterial :: RuleDSL Bool+isMaterial = (not . isJust . bProxyFor . label) <$> asks rcTreePos++materialChildrenSyms :: RuleDSL [Branch]+materialChildrenSyms = do+    tp <- asks rcTreePos+    im <- isMaterial+    case im of+      -- We're material, so only our children can be immaterial.+      True -> return $ searchDown . tree $ tp+      False -> return $ maybe [] searchDown . findMaterial $ tp+  where+    findMaterial :: TreePos Full Branch -> Maybe (Tree Branch)+    findMaterial tp =+      case parent tp of+        Nothing -> Nothing+        Just tp' | (isJust . bProxyFor . label $ tp') -> findMaterial tp'+        Just tp' -> Just . tree $ tp'+    searchDown :: Tree Branch -> [Branch]+    searchDown (Node b sf) =+      case isJust . bProxyFor $ b of+        True -> concatMap searchDown sf+        False -> [b]++noDup :: RuleDSL a -> RuleDSL a+noDup act = do+    rc <- ask+    (a, w) <- lift . lift . runWriterT . runReaderT act $ rc+    forM_ w $ \(weight, ec) -> do+      d <- alreadyExists ec+      unless d $ tell [(weight, ec)]+    return a++addOption :: Double -> LEnv EditCommand -> RuleDSL ()+addOption w ec = tell [(w, ec)]++doNothing :: Double -> RuleDSL ()+doNothing w = addOption w . return $ DoNothing++deleteMe :: Double -> RuleDSL ()+deleteMe w = do+  im <- (not . bImmutable) <$> curBranch+  when im $ addOption w . return $ DeleteMe++explodeSym :: BranchSym -> Angle -> Scale -> Double -> LEnv (Tree Branch)+explodeSym (BranchSym img r [attach] im rgd cls) a s wv = do+  i <- supply+  return $ Node (Branch i img r attach a s im rgd cls Nothing wv) []+explodeSym (BranchSym img r attachs im rgd cls) a s wv = do+  i <- supply+  sf <- forM attachs $ \at -> do+     i' <- supply+     return $ Node (Branch i' Nothing (0, 0) at 0 1 im True cls (Just i) 0) []+  return $ Node (Branch i img r (0, 0) a s im rgd cls Nothing wv) sf++addBranch :: Double -> BranchSym -> Angle -> Scale -> Double -> RuleDSL ()+addBranch w bs a s wv =+  addOption w ((AddChildren . pure) <$> explodeSym bs a s wv)++addBranches :: Double -> [(BranchSym, Angle, Scale, Double)] -> RuleDSL ()+addBranches w as = addOption w $ do+  cs <- forM as $ \(bs, a, s, wv) -> do+         explodeSym bs a s wv+  return . AddChildren $ cs++gaiaPathSyms :: RuleDSL [Branch]+gaiaPathSyms = (map (\(_, p, _) -> p) . parents) <$> asks rcTreePos++angleToVertical :: RuleDSL Angle+angleToVertical = negate <$> angleSum++angleSum :: RuleDSL Angle+angleSum = (absAngle . sum . map bAngle) <$> gaiaPathSyms++absAngle :: Angle -> Angle+absAngle d = d + (fromIntegral $ (ceiling ((negate d) / 360.0)::Integer)) * 360.0++lightAng :: (X, Y) -> Light -> Angle+lightAng p l =+  let (dx, dy) = ((-), (-)) <<*>> p <<*>> (bimap fromIntegral fromIntegral $ lPos l)+  in absAngle $ (180/pi) * atan2 dy dx++lightHits :: (X, Y) -> Light -> Bool+lightHits p l =+    let ang = lightAng p l+    in (abs $ ang-(absAngle $ lPointAngle l)) <= (lBeamAngle l)/2++attachLocation :: RuleDSL (X, Y)+attachLocation =+    (fst . fromJust) <$> (Map.lookup <$> (bId <$> curBranch) <*> (asks rcAttachLocs))++allLights :: RuleDSL [Light]+allLights = asks rcLights++lightCones :: RuleDSL [Light]+lightCones = do+  p <- attachLocation+  filter (lightHits p) <$> asks rcLights++distance :: (X, Y) -> (Int, Int) -> Double+distance a b = sqrt . distanceSq a $ b++distanceSq :: (X, Y) -> (Int, Int) -> Double+distanceSq (x0, y0) (x1', y1') =+    ((x0-x1)^(2::Int))+((y0-y1)^(2::Int))+  where+    x1 = fromIntegral x1'+    y1 = fromIntegral y1'++colorTemp :: RuleDSL Double+colorTemp = do+  lcs <- lightCones+  return $ if (null lcs)+           then -10+           else (sum . map lTemp $ lcs) / (fromIntegral $ length lcs)++lightIntensity :: RuleDSL Double+lightIntensity = do+  p <- attachLocation+  hitting <- lightCones+  let intenses = map (\l -> (10^(4::Int))*(360/(lBeamAngle l)) / (distanceSq p (lPos l))) hitting+  return . sum $ 0:intenses
+ src/Linden/Server.hs view
@@ -0,0 +1,152 @@+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}+module Linden.Server (+   lindenApp+ ) where++import Control.Applicative+import Control.Monad+import qualified Data.ByteString.Lazy as BSL+import qualified Data.ByteString.Lazy.Builder as BSLB+import qualified Data.Aeson as JS+import Data.Maybe+import Data.IORef+import Data.Char+import Data.Bits+import Data.Monoid+import Data.Function+import Data.List+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import Data.Random+import Control.Time+import Data.Time+import Data.UUID (UUID)+import qualified Data.UUID as UUID+import qualified Data.UUID.V1 as V1+import qualified Data.Map as Map+import Data.Digest.Pure.SHA (sha512, showDigest)+import System.FilePath++import qualified Network.HTTP.Types.Method as HTTP+import qualified Network.HTTP.Types.Status as HTTP+import qualified Network.Wai as WAI+import Network.Wai.EventSource+import Network.Wai.Middleware.Gzip+import Network.Wai.Application.Static+import Network.Wai.Middleware.Rewrite+import Network.Wai.UrlMap+import Network.Wai.Middleware.Cors+import Network.Wai.Request (requestSizeCheck)++import Linden.Types++lindenApp :: FilePath -> GardenStore -> BSL.ByteString -> NominalDiffTime -> AxiomSource -> IO Rule -> IO T.Text -> WAI.Application+lindenApp bp (GardenStore gsave gcas _) sec upRate axiom iorule upRule =+    gzip def . simpleCors . rewritePure rr . mapUrls $+          mount "run" eventTree+      <|> mount "rule" reRule+      <|> mount "edit" treeEdit+      <|> mount "art" (staticApp $ defaultWebAppSettings (bp </> "assets"))+      <|> mountRoot (staticApp $ defaultWebAppSettings (bp </> "client/build"))+  where+    rr [] _ = ["index.html"]+    rr ts _ = ts++    genEditKey :: UUID -> String+    genEditKey u = showDigest . sha512 $ sec <> UUID.toByteString u++    editKeyCheck :: UUID -> String -> Bool+    editKeyCheck u k =+        (length gk == length k) && 0 == (foldl1 (.|.) . zipWith (xor `on` ord) k $ gk)+      where gk = genEditKey u++    upDiff :: DiffTime+    upDiff = realToFrac upRate++    keyAuth :: UUID -> String -> (WAI.Response -> IO WAI.ResponseReceived) -> IO WAI.ResponseReceived -> IO WAI.ResponseReceived+    keyAuth u k _ app | editKeyCheck u k = app+    keyAuth _ _ respond _ | otherwise =+      respond $ WAI.responseBuilder HTTP.status403 [] "Bad Edit Key"++    reRule :: WAI.Application+    reRule _ respond = do+      errs <- upRule+      respond $ WAI.responseLBS HTTP.status200 [] . BSL.fromStrict . TE.encodeUtf8 $ errs++    treeEdit :: WAI.Application+    treeEdit req respond | (WAI.requestMethod req == HTTP.methodPost) = do+      reqBody <- WAI.strictRequestBody =<< requestSizeCheck 1024 req+      case JS.eitherDecode' reqBody of+        Left err -> respond $ WAI.responseBuilder HTTP.status400 [] .+                   BSLB.byteString . TE.encodeUtf8 . T.pack $ err+        Right (UserDel gdn k tgt) -> keyAuth gdn k respond $ do+          now <- getCurrentTime+          void . gcas gdn $ \(LS i _ s0 ls (Just (PrettyJSTree t0))) -> do+            return ( Just . LS i now s0 (filter (\lt -> (lId lt) /=tgt) ls) .+                       fmap PrettyJSTree . filterTree (\b -> tgt /= (bId b)) $ t0+                   , ())+          respond $ WAI.responseBuilder HTTP.status200 [] "Ok"+        Right (UserEditLight gdn k ml loc p w c) -> keyAuth gdn k respond $ do+          now <- getCurrentTime+          nlight <- gcas gdn $ \(ls@(LS _ _ s lts _)) -> do+            let (tl, ol) = partition (\l' -> (Exists $ lId l')==ml) lts+            let (s', newLights) =+                    case (tl, ml, length ol < 3) of+                      ([_], Exists l, _) -> (s, [Light l loc p w c])+                      ([], IsNull, True) -> (s+1, [Light s loc p w c])+                      _ -> (s, [])+            let allLights = newLights++ol+            return ( Just $ ls { lsSupply = s'+                               , lsLastUpdate = now+                               , lsLights = allLights }+                     , Just allLights)+          respond $ WAI.responseBuilder HTTP.status200 [] .+                  BSLB.lazyByteString . JS.encode $ nlight+    treeEdit _ respond =+        respond $ WAI.responseLBS HTTP.status405 [] "Bad method"++    eventSendGarden :: Maybe LState -> IO ServerEvent+    eventSendGarden mns = return $+      case mns of+        Nothing -> CloseEvent+        Just ns ->+          ServerEvent (Just "linden") Nothing .+          pure . BSLB.lazyByteString . JS.encode $ ns++    eventTree :: WAI.Application+    eventTree req respond = do+      (u, ek) <- case join . fmap UUID.fromText . listToMaybe . WAI.pathInfo $ req of+           Nothing -> do+             Just uuid <- V1.nextUUID+             now <- getCurrentTime+             ax <- runRVar (axiom uuid now) StdRandom+             gsave ax+             return (uuid, Left $ genEditKey uuid)+           Just u' -> return (u', Right False)+      stepR <- newIORef ek+      eventSourceAppIO (do+        stp <- readIORef stepR+        case stp of+          Left k -> do+            writeIORef stepR (Right False)+            return . ServerEvent (Just "linden-key") Nothing .+              pure . BSLB.lazyByteString . JS.encode . Map.fromList $+                [ ("garden", UUID.toText u)+                , ("key"::T.Text, T.pack k) ]+          Right False -> do+            writeIORef stepR (Right True)+            mns <- gcas u (\ls -> return (Nothing, ls))+            eventSendGarden mns+          _ -> do+            delay upDiff+            now <- getCurrentTime+            rule <- iorule+            mns <- gcas u (\ls ->+                    -- make sure we only update if its been long enough.+                    case (now `diffUTCTime` (lsLastUpdate ls)) <= upRate of+                      True -> return (Nothing, ls)+                      False -> do+                        upped <- step rule now ls+                        return (Just upped, upped))+            eventSendGarden mns) req respond+
+ src/Linden/Store/Memory.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE ScopedTypeVariables, ImpredicativeTypes, RankNTypes #-}+module Linden.Store.Memory (+   memStore+ ) where++import Data.UUID (UUID)+import Data.Random+import Control.Concurrent.MVar+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as HMap++import Linden.Types++memStore :: IO GardenStore+memStore = do+    ms <- newMVar HMap.empty+    return $ GardenStore (memSave ms) (memCAS ms) (memExists ms)+  where+    memSave :: MVar (HashMap UUID (MVar LState)) -> GardenSave+    memSave ms lcs = do+      nmv <- newMVar lcs+      modifyMVar_ ms (return . HMap.insert (lsGarden lcs) nmv)++    memCAS :: MVar (HashMap UUID (MVar LState)) -> GardenCAS+    memCAS ms = \u mut -> do+      mm <- (HMap.lookup u) <$> readMVar ms+      case mm of+        Nothing -> return Nothing+        Just m -> modifyMVar m $ \lcs -> do+          (mlcs', a) <- runRVar (mut lcs) StdRandom+          case mlcs' of+            Nothing -> return (lcs, Just a)+            Just lcs' -> return (lcs', Just a)++    memExists :: MVar (HashMap UUID (MVar LState)) -> GardenExists+    memExists ms u =+      (maybe False (const True) . HMap.lookup u) <$> takeMVar ms
+ src/Linden/Store/Redis.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE OverloadedStrings, RankNTypes, ScopedTypeVariables #-}+module Linden.Store.Redis (+   rStore+ ) where++import Control.Monad+import Control.Monad.Trans+import qualified Data.UUID as UUID+import qualified Data.Aeson as JS+import qualified Control.Monad.Catch as E+import qualified Control.Exception.Base as BE+import qualified Data.ByteString.Lazy as BSL+import Data.Random+import Control.Time+import Data.Time+import Database.Redis+import Codec.Compression.Zlib (compress, decompress)++import Linden.Types++backOff :: forall a. IO (Maybe a) -> IO (Maybe a)+backOff act =+    go 0+  where+    go :: Int -> IO (Maybe a)+    go 5 = return Nothing+    go e = E.handle (\(ex::E.SomeException) -> print ex >> delay (secondsToDiffTime $ 2^e) >> act) act++rStore :: Bool -> ConnectInfo -> IO GardenStore+rStore debug cinfo = do+    conn <- connect cinfo+    return $ GardenStore (rSave conn) (rCAS conn) undefined+  where+    rSave :: Connection -> GardenSave+    rSave conn lcs = runRedis conn $ do+      let ubs = UUID.toASCIIBytes . lsGarden $ lcs+      let bdy = BSL.toStrict . compress . JS.encode $ lcs+      void $ set ubs bdy++    rCAS :: Connection -> GardenCAS+    rCAS conn = \u mut -> backOff . runRedis conn $ do+      when debug . liftIO . putStrLn $ "CASing "++show u+      let ubs = UUID.toASCIIBytes u+      watch [ubs]+      eo <- get ubs+      case fmap (join . fmap (JS.decode' . decompress . BSL.fromStrict)) eo of+        Left err -> do+          liftIO . putStrLn $ " - "++show u ++ " got error: "++show err+          return Nothing+        Right Nothing -> do+          liftIO . putStrLn $ " - "++show u ++ " was not pressent or didn't decode!"+          return Nothing+        Right (Just g) -> do+          when debug . liftIO . putStrLn $ " - "++show u ++ " downloaded"          +          (mg', a) <- liftIO $ runRVar (mut g) StdRandom+          case mg' of+            Nothing -> do+              when debug . liftIO . putStrLn $ " - "++show u ++ " no action."+              return $ Just a+            Just g' | g' == g -> do+              when debug . liftIO . putStrLn $ " - "++show u ++ " updated but same"+              return $ Just a+            Just g' -> do+              when debug . liftIO . putStrLn $ " - "++show u ++ " updated"+              let bdy = BSL.toStrict . compress . JS.encode $ g'+              txRes <- multiExec $ do+                (fmap (const ())) <$> set ubs bdy+              case txRes of+                TxSuccess _ -> do+                  when debug . liftIO . putStrLn $ " - "++show u ++ " CAS"+                  return $ Just a+                TxAborted -> liftIO . E.throwM . BE.AssertionFailed $ "Raced"+                TxError err ->+                  -- liftIO . putStrLn $ " - Error on multi: "++err+                  liftIO . E.throwM . BE.AssertionFailed $ "retry"
+ src/Linden/TH.hs view
@@ -0,0 +1,8 @@+module Linden.TH (+   makeCamel+ ) where++import Data.Char++makeCamel :: Int -> String -> String+makeCamel d = (\l -> (toLower . head $ l):(tail l))  . drop d
+ src/Linden/Types.hs view
@@ -0,0 +1,255 @@+{-# LANGUAGE OverloadedStrings, DeriveGeneric, DeriveFunctor, DeriveAnyClass, TemplateHaskell, RankNTypes, FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, ImpredicativeTypes #-}+module Linden.Types (+   BranchSym(..), Branch(..), X, Y, Angle, Scale+ , offspring+ , LState(..), Light(..), BranchPossition+ , step, EditCommand(..)+ , LEnvT, LEnv, runLEnvT, runLEnv, Supply(..)+ , Rule, AxiomSource+ , PrettyJSTree(..)+ , GardenStore(..), GardenSave, GardenCAS, GardenExists+ , UserCommand(..)+ , filterTree+ , Nullable(..)+ , makePossitions+ ) where++import GHC.Generics+import qualified Data.Text as T+import qualified Data.Aeson as JS+import Data.Aeson.TH+import Data.Bifunctor+import Data.Biapplicative+import Data.Word+import Data.Tree+import Data.Maybe+import Data.Time+import Data.UUID (UUID)+import Data.Tree.Zipper+import Data.Random.RVar+import Control.Monad.Identity+import Control.Monad.State.Lazy+import Control.Monad.Supply (MonadSupply(..))+import qualified Data.Map.Lazy as Map+import Control.DeepSeq++import Linden.TH++data BranchSym =+  BranchSym {+      bsImg :: Maybe T.Text+    , bsRoot :: !(Int, Int)+    , bsAttach :: ![(Int, Int)]+    , bsImmutable :: !Bool+    , bsRigid :: !Bool+    , bsClasses :: ![Int]+    }+  deriving (Read, Show, Eq, Ord, Generic, NFData)++type Angle = Double+type Scale = Double++data Branch =+  Branch {+      bId :: {-# UNPACK #-} !Word32+    , bImg :: Maybe T.Text+    , bRoot :: !(Int, Int)+    , bAttach :: !(Int, Int)+    , bAngle :: {-# UNPACK #-} !Angle+    , bScale ::  {-# UNPACK #-}!Scale+    , bImmutable :: !Bool+    , bRigid :: !Bool+    , bClass :: ![Int]+    , bProxyFor :: Maybe Word32+    , bWaviness :: {-# UNPACK #-}!Double+    }+  deriving (Read, Show, Eq, Ord, Generic, NFData)++$(deriveJSON defaultOptions{fieldLabelModifier = makeCamel 1} ''Branch)++data Light =+    Light {+      lId ::  {-# UNPACK #-}!Word32+    , lPos :: !(Int, Int)+    , lPointAngle ::  {-# UNPACK #-}!Angle+    , lBeamAngle ::  {-# UNPACK #-}!Angle+    , lTemp :: {-# UNPACK #-}!Double+    }+  deriving (Read, Show, Eq, Ord, Generic, NFData)++$(deriveJSON defaultOptions{fieldLabelModifier = makeCamel 1} ''Light)++newtype PrettyJSTree a =+    PrettyJSTree (Tree a)+  deriving (Read, Show, Eq, Generic, NFData)++instance JS.ToJSON a => JS.ToJSON (PrettyJSTree a) where+    toJSON (PrettyJSTree (Node a c)) =+        JS.object [ ("node", JS.toJSON a)+                  , ("children", JS.toJSON . map PrettyJSTree $ c)]++instance JS.FromJSON a => JS.FromJSON (PrettyJSTree a) where+    parseJSON (JS.Object v) = do+      n <- v JS..: "node"+      c <- v JS..: "children"+      return . PrettyJSTree . Node n . map (\(PrettyJSTree t) -> t) $ c+    parseJSON _ = mzero++data LState =+  LS {+    lsGarden :: {-# UNPACK #-} !UUID+  , lsLastUpdate :: {-# UNPACK #-} !UTCTime+  , lsSupply ::  {-# UNPACK #-}!Word32+  , lsLights :: ![Light]+  , lsTree :: Maybe (PrettyJSTree Branch)+  }+  deriving (Read, Show, Eq, Generic, NFData)++$(deriveJSON defaultOptions{fieldLabelModifier = makeCamel 2} ''LState)++type GardenSave = LState -> IO ()+-- Returns Nothing if the UUID doesn't exist in the store.+-- Action returns Nothing if there is no update.+type GardenCAS = forall a. UUID -> (LState -> RVar (Maybe LState, a)) -> IO (Maybe a)+type GardenExists = UUID -> IO Bool++data GardenStore =+    GardenStore GardenSave GardenCAS GardenExists++data Nullable a =+    IsNull+  | Exists a+  deriving (Read, Show, Eq, Ord, Generic, NFData)++instance JS.ToJSON a => JS.ToJSON (Nullable a) where+    toJSON (Exists a) = JS.toJSON a+    toJSON IsNull = JS.Null++instance JS.FromJSON a => JS.FromJSON (Nullable a) where+    parseJSON JS.Null = return IsNull+    parseJSON v = Exists <$> JS.parseJSON v++data UserCommand =+    UserDel { ucGarden :: UUID, ucEditKey :: String, ugNode :: Word32 }+  | UserEditLight { ucGarden :: UUID, ucEditKey :: String+                  , ucLight :: Nullable Word32, ucPos :: (Int, Int)+                  , ucPointAngle :: Angle, ucBeamAngle :: Angle+                  , ucTemp :: Double }+  deriving (Read, Show, Eq, Ord, Generic, NFData)++$(deriveJSON defaultOptions {+                   fieldLabelModifier = makeCamel 2+                 , constructorTagModifier = drop 4} ''UserCommand)++filterTree :: (Branch -> Bool) -> Tree Branch -> Maybe (Tree Branch)+filterTree f (Node b sf) =+    if f b+    then Just (Node b (mapMaybe (filterTree f) sf))+    else Nothing++data EditCommand = DoNothing | AddChildren [Tree Branch] | DeleteMe++newtype Supply = Supply Word32++type X = Double+type Y = Double++newtype LEnvT m a =+    LEnvT { runLEnvT :: Supply -> m (a, Supply) }++type LEnv = LEnvT Identity++runLEnv :: LEnv a -> Supply -> (a, Supply)+runLEnv a = runIdentity . runLEnvT a++instance (Functor m) => Functor (LEnvT m) where+    fmap f m = LEnvT $ \s ->+               fmap (\ ~(a, s') -> (f a, s')) $ runLEnvT m s++instance (Functor m, Monad m) => Applicative (LEnvT m) where+    pure a = LEnvT $ \s -> return (a, s)+    LEnvT mf <*> LEnvT mx = LEnvT $ \s -> do+        ~(f, s') <- mf s+        ~(x, s'') <- mx s'+        return (f x, s'')+    {-# INLINE (<*>) #-}++instance (Monad m) => Monad (LEnvT m) where+    m >>= k  = LEnvT $ \s -> do+        ~(a, s') <- runLEnvT m s+        runLEnvT (k a) s'+    fail str = LEnvT $ \_ -> fail str++instance (Monad m) => MonadState Supply (LEnvT m) where+    state f = LEnvT (return . f)++instance MonadTrans LEnvT where+    lift m = LEnvT $ \s -> do+               a <- m+               return (a, s)++instance Monad m => MonadSupply Word32 (LEnvT m) where+    supply = state (\(Supply s) -> (s, Supply $ s+1))+    peek = (\(Supply s) -> s) <$> get+    exhausted = (\(Supply s) -> s == maxBound) <$> get++type BranchPossition = Map.Map Word32 ((X, Y), Angle)++makePossitions :: Tree Branch -> BranchPossition+makePossitions (Node b' sf') =+    go (Map.singleton (bId b') (attachOffset b', bAngle b')) (bId b') sf'+  where+    doublefy :: (Int, Int) -> (Double, Double)+    doublefy = bimap fromIntegral fromIntegral+    attachOffset :: Branch -> (X, Y)+    attachOffset b =+        ((-), (-)) <<*>> (doublefy $ bAttach b) <<*>> (doublefy $ bRoot b)+    go :: BranchPossition -> Word32 -> Forest Branch -> BranchPossition+    go memo pid cl = foldl (addChild pid) memo cl+    addChild :: Word32 -> BranchPossition -> Tree Branch -> BranchPossition+    addChild pid memo (Node c sf) =+      go (Map.insert (bId c) (attachPos pid memo c) memo) (bId c) sf+    attachPos :: Word32 -> BranchPossition -> Branch -> ((X, Y), Angle)+    attachPos pid memo c =+      let ((parX, parY), pAng) = memo Map.! pid+          ang = pAng + (bAngle c)+          (offX, offY) = attachOffset c+          (xosq, yosq) = ((^(2::Int)), (^(2::Int))) <<*>> attachOffset c+          cLen = (bScale c) * sqrt(xosq+yosq)+          rang = ang*(pi/180) + atan2 offY offX+          cX = cLen*(cos rang)+          cY = cLen*(sin rang)+      in ((parX+cX, parY+cY), ang)++type Rule = BranchPossition -> [Light] -> TreePos Full Branch -> RVar (LEnv EditCommand)++-- Axiom generator+type AxiomSource = UUID -> UTCTime -> RVar LState++offspring :: TreePos Full Branch -> [TreePos Full Branch]+offspring tp =+    go . children $ tp+  where+    go :: TreePos Empty Branch -> [TreePos Full Branch]+    go cf =+      case nextTree cf of+        Nothing -> []+        Just c -> c:(go . nextSpace $ c)++step :: Rule -> UTCTime -> LState -> RVar LState+step _ _ l@(LS _ _ _ _ Nothing) = return l+step rule n (LS i _ s0 lights (Just (PrettyJSTree t0))) =+    fmap (\(t1, Supply s1) -> force $ LS i n s1 lights . fmap PrettyJSTree $ t1) .+    (`runLEnvT` (Supply s0)) . applyProductions .+    fromTree $ (t0::Tree Branch)+  where+    bps = makePossitions t0+    applyProductions :: TreePos Full Branch -> LEnvT RVar (Maybe (Tree Branch))+    applyProductions tp = do+      let sym = label tp+      cuped <- fmap catMaybes . forM (offspring tp) $ \c -> applyProductions c+      com <- (lift . rule bps lights $ tp) >>= (\a -> state (runLEnv a))+      case com of+        DoNothing -> return . Just $ Node sym cuped+        AddChildren ts -> return . Just $ Node sym (ts++cuped)+        DeleteMe -> return Nothing
+ tiff/branch.tif view

binary file changed (absent → 687 bytes)