From 9afce297f6f520bca11703637495795fd2642073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TG=20=C3=97=20=E2=8A=99?= <*@tg-x.net> Date: Sat, 9 Nov 2019 21:38:48 +0100 Subject: [PATCH 1/8] build fix --- coffee/manager.coffee | 2 +- coffee/rendering.coffee | 2 +- package.json | 4 ++-- run | 5 +---- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/coffee/manager.coffee b/coffee/manager.coffee index 7d2dd99..1b53127 100644 --- a/coffee/manager.coffee +++ b/coffee/manager.coffee @@ -22,7 +22,7 @@ class AppManager systemManager: null constructor: (@container, @controls) -> - @joystick = new Joystick(container) + @joystick = new Joystick(@container) @keystate = new KeyState @inputHandler = new InputHandler(@keystate, @joystick) diff --git a/coffee/rendering.coffee b/coffee/rendering.coffee index d035815..c042ad2 100644 --- a/coffee/rendering.coffee +++ b/coffee/rendering.coffee @@ -46,7 +46,7 @@ class Renderer stack:[] isDrawing:false constructor: (@container) -> - @context = new RenderingContext(container) + @context = new RenderingContext(@container) prepare: (system) => colors = getColors(system) diff --git a/package.json b/package.json index 30d06cb..98bd420 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,9 @@ "version": "0.1.0", "dependencies": { "coffee-script": ">= 1.3.0", + "http-server": ">= 0.5.0", "jitter": ">= 1.1.1", - "node-sass": "3.4.2", "json-minify": ">= 1.0.0", - "http-server": ">= 0.5.0" + "sass": "^1.23.3" } } diff --git a/run b/run index 8a942ef..ca61d08 100755 --- a/run +++ b/run @@ -18,10 +18,7 @@ function handle_sigint() trap handle_sigint SIGINT (node_modules/http-server/bin/http-server . -p 8000 &> /dev/null) & - -(node_modules/node-sass/bin/node-sass sass/test.scss sass/test.css) & - +(node_modules/sass/sass.js css/ui.scss css/ui.css) & (node_modules/jitter/bin/jitter -b coffee js/generated) & wait - From 8e4d156ad8bc08d951c47fa676a9ff3b87c8dcfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TG=20=C3=97=20=E2=8A=99?= <*@tg-x.net> Date: Sat, 9 Nov 2019 22:56:22 +0100 Subject: [PATCH 2/8] minimize ui --- css/forkme.css | 133 ---------------------------------- sass/test.scss => css/ui.scss | 130 ++++++++++++++++----------------- index.html | 120 ++++++++++++------------------ js/lsys-intro.js | 6 +- 4 files changed, 111 insertions(+), 278 deletions(-) delete mode 100644 css/forkme.css rename sass/test.scss => css/ui.scss (84%) diff --git a/css/forkme.css b/css/forkme.css deleted file mode 100644 index b544963..0000000 --- a/css/forkme.css +++ /dev/null @@ -1,133 +0,0 @@ -/* Left will inherit from right (so we don't need to duplicate code */ -.github-fork-ribbon { - /* The right and left lasses determine the side we attach our banner to */ - position: absolute; - - /* Add a bit of padding to give some substance outside the "stitching" */ - padding: 2px 0; - - /* Set the base colour */ - background-color: #a00; - - /* Set a gradient: transparent black at the top to almost-transparent black at the bottom */ - background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0.00)), to(rgba(0, 0, 0, 0.15))); - background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.00), rgba(0, 0, 0, 0.15)); - background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0.00), rgba(0, 0, 0, 0.15)); - background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0.00), rgba(0, 0, 0, 0.15)); - background-image: -ms-linear-gradient(top, rgba(0, 0, 0, 0.00), rgba(0, 0, 0, 0.15)); - background-image: linear-gradient(top, rgba(0, 0, 0, 0.00), rgba(0, 0, 0, 0.15)); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#000000', EndColorStr='#000000'); - - /* Add a drop shadow */ - -webkit-box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.5); - box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.5); - - z-index: 9999; -} - -.github-fork-ribbon a, -.github-fork-ribbon a:hover { - /* Set the font */ - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - font-weight: 700; - color: white; - - /* Set the text properties */ - text-decoration: none; - text-shadow: 0 -1px rgba(0,0,0,0.5); - text-align: center; - - /* Set the geometry. If you fiddle with these you'll also need to tweak the top and right values in #github-fork-ribbon. */ - width: 200px; - line-height: 20px; - - /* Set the layout properties */ - display: inline-block; - padding: 2px 0; - - /* Add "stitching" effect */ - border-width: 1px 0; - border-style: dotted; - border-color: rgba(255,255,255,0.7); -} - -.github-fork-ribbon-wrapper { - width: 150px; - height: 150px; - position: absolute; - overflow: hidden; - top: 0; - z-index: 9999; -} - -.github-fork-ribbon-wrapper.fixed { - position: fixed; -} - -.github-fork-ribbon-wrapper.fm-left { - left: 0; -} - -.github-fork-ribbon-wrapper.right { - right: 0; -} - -.github-fork-ribbon-wrapper.left-bottom { - position: fixed; - top: inherit; - bottom: 0; - left: 0; -} - -.github-fork-ribbon-wrapper.right-bottom { - position: fixed; - top: inherit; - bottom: 0; - right: 0; -} - -.github-fork-ribbon-wrapper.right .github-fork-ribbon { - top: 42px; - right: -43px; - - /* Rotate the banner 45 degrees */ - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); -} - -.github-fork-ribbon-wrapper.fm-left .github-fork-ribbon { - top: 42px; - left: -43px; - - /* Rotate the banner -45 degrees */ - -webkit-transform: rotate(-45deg); - -moz-transform: rotate(-45deg); - -o-transform: rotate(-45deg); - transform: rotate(-45deg); -} - - -.github-fork-ribbon-wrapper.left-bottom .github-fork-ribbon { - top: 80px; - left: -43px; - - /* Rotate the banner -45 degrees */ - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); -} - -.github-fork-ribbon-wrapper.right-bottom .github-fork-ribbon { - top: 80px; - right: -43px; - - /* Rotate the banner -45 degrees */ - -webkit-transform: rotate(-45deg); - -moz-transform: rotate(-45deg); - -o-transform: rotate(-45deg); - transform: rotate(-45deg); -} diff --git a/sass/test.scss b/css/ui.scss similarity index 84% rename from sass/test.scss rename to css/ui.scss index 346baec..6e1e0f6 100644 --- a/sass/test.scss +++ b/css/ui.scss @@ -1,15 +1,4 @@ -$menu-color: #333; -$top:0; -$left:228px; -$bottom:240px; - -@mixin scrollbar{ -&::-webkit-scrollbar{width:4px;height:4px;} -&::-webkit-scrollbar-button:start:decrement, &::-webkit-scrollbar-button:end:increment{ display:block;height:2px;background-color: #232323; } -&::-webkit-scrollbar-track-piece{ background-color: #282828;-webkit-border-radius:0;-webkit-border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px; } -&::-webkit-scrollbar-thumb:vertical{ height:5px;background-color: #6d6d6d;-webkit-border-radius:4px; } -&::-webkit-scrollbar-thumb:horizontal{ width:5px;background-color:#999;-webkit-border-radius:4px; } -} +$sideWidth: 230px; div { -webkit-touch-callout: none; @@ -19,15 +8,22 @@ div { -ms-user-select: none; user-select: none; } - +/* #helpTrigger{ background-color: rgb(163, 7, 7); color: rgb(236, 236, 236); - border-color: rgb(202, 202, 202); text-shadow: 1px 1px black; } - -.introjs-tooltip{ max-width: 350px; } +#helpTrigger:hover { + border-color: rgb(202, 202, 202); +} +*/ +.introjs-tooltip{ + max-height: 150px; + max-width: 350px; + overflow-x: hidden; + overflow-y: auto; +} .introjs-tooltip, .introjs-helperLayer{ color: #e8e8e8; background-color: rgb(70, 71, 77); @@ -40,7 +36,7 @@ div { color: #e8e8e8; background-color: rgba(94, 97, 111, 0.51) } -.introjs-overlay.hidden { +.hidden { display: none; } .introjs-overlay{ @@ -100,12 +96,12 @@ li{ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1); .actions{ padding-top:13px; - padding-right:11px; + padding-right:0px; a{ padding:2px 1px 1px 1px; } } a.site{ font-family:monospace; - padding-left:10px; + padding-left:0px; padding-top:4px; color: #dbdbdb; font-size:12pt; @@ -124,35 +120,48 @@ li{ } } -.container-left{ +.show-side.actions{ + padding-top: 13px; +} +.show-side.actions a{ + border: 0; +} + +.container-side{ + position: absolute; + top: 0; + right: 0; bottom: 0; + width: $sideWidth; cursor: default; - background-color:$menu-color; - left: 0; + background-color:#333; padding: 0 0 0 0px; - position: absolute; - border-right:1px solid #555; - top: $top; - width: $left; + border-left:1px solid #555; + scrollbar-width: thin; + overflow-x: hidden; + overflow-y: auto; + opacity: 0.7; + /* + &::-webkit-scrollbar{width:4px;height:4px;} + &::-webkit-scrollbar-button:start:decrement, &::-webkit-scrollbar-button:end:increment{ display:block;height:2px;background-color: #232323; } + &::-webkit-scrollbar-track-piece{ background-color: #282828;-webkit-border-radius:0;-webkit-border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px; } + &::-webkit-scrollbar-thumb:vertical{ height:5px;background-color: #6d6d6d;-webkit-border-radius:4px; } + &::-webkit-scrollbar-thumb:horizontal{ width:5px;background-color:#999;-webkit-border-radius:4px; } + */ } -.container-right{ - bottom: $bottom; - left: $left+1; +.container-main{ + bottom: 0; + left: 0; margin: 0; padding: 0; position: absolute; right: 0; - top: $top; + top: 0; background-color: #1d1f20; } -.container-bottom{ - position:absolute; +.container-ticker{ padding:0; - bottom:0; - left:$left+1; - right:0; - height:$bottom - 1; border-top:1px solid #666; background-color:#222; .fade{ @@ -162,23 +171,9 @@ li{ background: linear-gradient(to bottom, rgba(34, 34, 34, 0) 0%, rgb(34,34,34) 70%) } } -.top{ - position:absolute; - top:0; - right:0; - left:0; - height:$top - 1; - background-color: #111115; - border-bottom:1px solid #3f3f3f; -} .controls{ - @include scrollbar; font-size:9pt; - position:absolute; - top:51px; - bottom:0; - right:1px; overflow:hidden; overflow-y:auto; @@ -244,7 +239,7 @@ li{ margin:0; } textarea{ - width:132px; + width:204px; } &.half input{ margin-left:72px; @@ -256,6 +251,10 @@ li{ width:64px; padding-left:8px; } + li .label { + display:inline-block; + width:68px; + } } ul.labels{ li{ @@ -311,13 +310,8 @@ li{ } .system-info { - position: absolute; - top:7px; - left:0; - right:0; text-align: center; background-color:rgba(0,0,0,0.3); - color: #d6d6d6; border:1px solid #363636; border-left:none; @@ -391,6 +385,11 @@ li{ font-size:12px; padding:0px 0px 0px 0px; } + i.icon-toggle{ + font-weight: bold; + letter-spacing: -2px; + padding: 0 3px; + } text-decoration: none; line-height:14px; color: #808080; @@ -439,15 +438,17 @@ li{ &.resizing{ cursor: ne-resize; } } } +.help-pad { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: $sideWidth+10; +} .ticker{ - @include scrollbar; - overflow-y: scroll; - position:absolute; - top:0; bottom:0;left:0;right:0; margin:1px; padding:0px; - text-align:center; .elem{ border:1px solid #6f6f6f; padding:2px; @@ -455,10 +456,8 @@ li{ border-radius:1px; position:relative; background-color: #2c2e33; - - width:130px; + width:103px; height:125px; - //position:relative; font-size:8pt; &:hover{ @@ -492,6 +491,3 @@ li{ } } } - - - diff --git a/index.html b/index.html index 4bd49b7..ade899b 100644 --- a/index.html +++ b/index.html @@ -5,26 +5,10 @@ lsys - - - - - + @@ -41,10 +25,10 @@ -
  • +
  • Click and drag!

  • -
  • +
  • As you drag your mouse, you change the parameters of the system, which then gets redrawn.

  • @@ -76,7 +60,7 @@

    The system's parameters are also sync'd automatically with the url - meaning you can share systems by pasting the url somewhere.

    Also - since systems are just urls, your browsers back button works the way you would expect it to :)

  • -
  • +
  • Take a look at some of the examples here (you can scroll down!)

  • @@ -138,17 +122,37 @@
  • That's it! Whew!
  • - -
    +
    +
    +
    +
    + + + + +
    +
    +
    + +
    <<  +
    +
    - lsys + lsys
    - - - + >> +
    +
    +
    +
    +
    @@ -188,13 +192,12 @@
  • -
      -
    • levels
    • +
        +
      • rules
      • +
      • levels
        -
      • rules
      • +
      - -
  • -
    -
    -
    -
      -
      -
      -
      -
      - -
      -
      - - - - - - -
      -
      -
      - -
      -
      - - - -
      -
      -
      - -
      - +
      +
      • gallery
      +
      +
        +
        -
        - - @@ -354,6 +318,12 @@ return false; }); + // show/hide sidebar button + $('a.toggle-side').click(function(){ + $('.container-side').toggleClass('hidden'); + return false; + }) + setTimeout(function(){ manager.start().always(function(){ //this is kinda naughty... but I'd rather refactor Controls first than fudge this in otherwise diff --git a/js/lsys-intro.js b/js/lsys-intro.js index 4067a49..08749f2 100644 --- a/js/lsys-intro.js +++ b/js/lsys-intro.js @@ -26,7 +26,7 @@ function mkIntro(){ return { "element" : step.data('element'), "intro" : step.html(), - "position" : step.data('position') || "right", + "position" : step.data('position') || "left", "data" : step.data() } }); @@ -46,7 +46,7 @@ function mkIntro(){ if (data.overlay == 'off') $('.introjs-overlay').addClass('hidden'); }).setOptions({ exitOnOverlayClick: true, - tooltipPosition:'right', + tooltipPosition:'left', showStepNumbers: false, steps: steps }); @@ -61,4 +61,4 @@ $('#helpTrigger').click(function(){ $('#syntaxTrigger').click(function(){ mkIntro().start().goToStep(13); return false; -}); \ No newline at end of file +}); From 1d070064a6bc3ef66586c1f5530c4dae9ae7d6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TG=20=C3=97=20=E2=8A=99?= <*@tg-x.net> Date: Sat, 9 Nov 2019 23:30:19 +0100 Subject: [PATCH 3/8] export fix --- coffee/manager.coffee | 2 +- coffee/util.coffee | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/coffee/manager.coffee b/coffee/manager.coffee index 1b53127..0f17e62 100644 --- a/coffee/manager.coffee +++ b/coffee/manager.coffee @@ -94,7 +94,7 @@ class AppManager r.context.state.y = (y-b.y1+15) @draw(r) - filename = "lsys_"+system.name.replace(/[\ \/]/g,"_") + filename = "lsys_"+system.name.replace(/[\ \/]/g,"_")+".png" rootCanvas = container.children[0] rootContext = rootCanvas.getContext('2d') [].slice.call(container.children, 1).forEach( (c) -> diff --git a/coffee/util.coffee b/coffee/util.coffee index 166ef67..9abc42e 100644 --- a/coffee/util.coffee +++ b/coffee/util.coffee @@ -24,8 +24,7 @@ class Util a = document.createElement("a") a.href = data a.download=filename - evt = document.createEvent("MouseEvents") - evt.initMouseEvent("click", true, true,window,0,0,0,0,0,true,false,false,false,0,null) + evt = new MouseEvent('click', { view: window }) a.dispatchEvent(evt) # thanks Brian Nickel http://stackoverflow.com/questions/11163344/update-non-retina-canvas-app-to-retina-display From ea02fb616449f897ffc328aa7ccd5a7b64918557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TG=20=C3=97=20=E2=8A=99?= <*@tg-x.net> Date: Sun, 10 Nov 2019 08:30:15 +0100 Subject: [PATCH 4/8] animation --- coffee/compilation.coffee | 17 +++++++---- coffee/controls.coffee | 59 +++++++++++++++++++++++++++++++++++++++ coffee/lsystem.coffee | 39 +++++++++++++++++++++++--- coffee/manager.coffee | 55 +++++++++++++++++++++++++++++++++--- css/ui.scss | 57 +++++++++++++++++++++++++++---------- index.html | 57 +++++++++++++++++++++++++++++++------ 6 files changed, 247 insertions(+), 37 deletions(-) diff --git a/coffee/compilation.coffee b/coffee/compilation.coffee index fb86cff..b20d0db 100644 --- a/coffee/compilation.coffee +++ b/coffee/compilation.coffee @@ -1,8 +1,14 @@ -NullSystem = new LSystem({},{},{},"",1,"no system") -DefaultSystem = new LSystem({ - size: {value:12.27} - angle: {value:4187.5} -},{},{ size: {value:9} } ,"L : SS\nS : F->[F-Y[S(L]]\nY : [-|F-F+)Y]\n" ,12 ,"click-and-drag-me!" ) +NullSystem = new LSystem({},{},{},0,"","",1,"no system") +DefaultSystem = new LSystem( + { size: {value:12.27}, angle: {value:4187.5} }, + {}, + { size: {value:9} }, + false, + "", + "L : SS\nS : F->[F-Y[S(L]]\nY : [-|F-F+)Y]", + 12, + "click-and-drag-me!" +) # ========================================= class CompiledSystem @@ -92,4 +98,3 @@ class SystemManager ) getInstructions: -> @compiledElements - diff --git a/coffee/controls.coffee b/coffee/controls.coffee index a828631..b912160 100644 --- a/coffee/controls.coffee +++ b/coffee/controls.coffee @@ -87,6 +87,65 @@ class Joystick # =============================== +class Animation + active: false + now: 0 + f: (t) -> {} + + constructor: (f) -> + @setF(f) + + setF: (f, el) -> + if el + $(el).removeClass('error') + $(el).attr('title', '') + try + f = Function ['t'], f + r = f.call {}, 0 + unless r instanceof Object + throw 'Return value is not Object' + @f = f + catch err + if el + $(el).addClass 'error' + $(el).attr 'title', err.toString() + + enable: -> + @active = true + @onActivate() + disable: -> + @active = false + @onRelease() + toggle: (active = not @active) -> + if active + @enable() + else + @disable() + + onActivate: -> # noop unless overriden + onRelease: -> # noop unless overriden + + state: () -> + d = + try + @f(@now) + catch e + {} + if d instanceof Object + d + else + {} + + clear: -> #noop for now + draw: -> + if @active + @now++ + + center: -> + @now = 0 + +# =============================== + # ui binding for a single system variable class Control constructor: (@controlkey) -> diff --git a/coffee/lsystem.coffee b/coffee/lsystem.coffee index 287f69e..3577348 100644 --- a/coffee/lsystem.coffee +++ b/coffee/lsystem.coffee @@ -36,13 +36,34 @@ class Defaults @_sensitivites: -> size: {value: 7.7, growth:7.53} angle: {value: 7.6, growth:4} + @play: 0 + @animation: " + return {\n + angleX: t/100,\n + angleY: t/100,\n + sizeX: null,\n + sizeY: null,\n + offsetX: null,\n + offsetY: null,\n + rotation: null\n + }" # ========================================= class LSystem - constructor: (params, offsets, sensitivities, @rules, @iterations, @name) -> + constructor: (params, offsets, sensitivities, play, animation, @rules, @iterations, @name) -> @params = Util.map(Defaults.params(params), (c) -> Param.fromJson(c)) @offsets = Defaults.offsets(offsets) @sensitivities = Util.map(Defaults.sensitivities(sensitivities), (s) -> Sensitivity.fromJson(s)) + @play = + if (typeof play == 'number' && Number.isFinite play) || (typeof play == 'boolean') + if play then 1 else 0 + else + Defaults.play + @animation = + if typeof animation == 'string' and 0 < animation.length + animation + else + Defaults.animation # this is not the most efficient of methods... clone: -> return LSystem.fromUrl(@toUrl()) @@ -51,10 +72,12 @@ class LSystem base = "#?i=#{@iterations}&r=#{encodeURIComponent(@rules)}" mkQueryString = (params) -> _.reduce(params, ((acc,v) -> "#{acc}&#{v.toUrlComponent()}"), "") params = mkQueryString(@params) - sensitivities = mkQueryString(@sensitivities) offsets = "&offsets=#{@offsets.x},#{@offsets.y},#{@offsets.rot}" + sensitivities = mkQueryString(@sensitivities) + play = "&play="+@play + animation = "&anim="+encodeURIComponent(@animation) name = "&name=#{encodeURIComponent(@name)}" - return base+params+sensitivities+offsets+name + return base+params+offsets+sensitivities+play+animation+name merge: (system) -> _.extend(@, system) if system @@ -81,7 +104,15 @@ class LSystem y: parseFloat(o[1]) rot: parseFloat(o[2]) - return new LSystem(params, offsets, sensitivities, decodeURIComponent(config.r), config.i, decodeURIComponent(config.name) or "unnamed") + anim = + if 'anim' of config + decodeURIComponent(config.anim) + else + null + + return new LSystem(params, offsets, sensitivities, parseInt(config.play), + anim, decodeURIComponent(config.r), config.i, + decodeURIComponent(config.name) or "unnamed") isIsomorphicTo: (system) -> if (!system) then false else @rules == system.rules and @iterations == system.iterations diff --git a/coffee/manager.coffee b/coffee/manager.coffee index 0f17e62..f4ea169 100644 --- a/coffee/manager.coffee +++ b/coffee/manager.coffee @@ -13,22 +13,51 @@ class InputHandler system.params.angle.value = Util.round(system.params.angle.value + @joystick.dx(system.sensitivities.angle.value), 4) system.params.angle.growth = Util.round(system.params.angle.growth + @joystick.dy(system.sensitivities.angle.growth),9) +class AnimationHandler + snapshot: null # lsystem params as they were was when joystick activated + constructor: (@animation) -> + + sensitivity: (value) -> + if (value) then Math.pow(10,value-10) else 1 + + update: (system) => + return if not @animation.active + d = @animation.state() + if (typeof d.angleX == 'number' && Number.isFinite d.angleX) + system.params.angle.value = Util.round(system.params.angle.value + d.angleX * @sensitivity(system.sensitivities.angle.value), 4) + if (typeof d.angleY == 'number' && Number.isFinite d.angleY) + system.params.angle.growth = Util.round(system.params.angle.growth + d.angleY * @sensitivity(system.sensitivities.angle.growth), 9) + if (typeof d.sizeX == 'number' && Number.isFinite d.sizeX) + system.params.size.value = Util.round(@snapshot.params.size.value + d.sizeX * @sensitivity(system.sensitivities.size.value), 2) + if (typeof d.sizeY == 'number' && Number.isFinite d.sizeY) + system.params.size.growth = Util.round(@snapshot.params.size.growth + d.sizeY * @sensitivity(system.sensitivities.size.growth), 6) + if (typeof d.offsetX == 'number' && Number.isFinite d.offsetX) + system.offsets.x = @snapshot.offsets.x + d.offsetX + if (typeof d.offsetY == 'number' && Number.isFinite d.offsetY) + system.offsets.y = @snapshot.offsets.y + d.offsetY + if (typeof d.rotation == 'number' && Number.isFinite d.rotation) + system.offsets.rot = @snapshot.offsets.rot + d.rotation class AppManager joystick:null + animation:null keystate: null inputHandler: null renderer:null systemManager: null - constructor: (@container, @controls) -> + constructor: (@container, @controls, @animation) -> @joystick = new Joystick(@container) @keystate = new KeyState @inputHandler = new InputHandler(@keystate, @joystick) + @animationHandler = new AnimationHandler(@animation) @joystick.onRelease = => @syncLocationQuiet() @joystick.onActivate = => @inputHandler.snapshot = @systemManager.activeSystem.clone() + @animation.onRelease = => @syncLocationQuiet() + @animation.onActivate = => @animationHandler.snapshot = @systemManager.activeSystem.clone() + @renderer = new Renderer(@container) @systemManager = new SystemManager @@ -56,6 +85,8 @@ class AppManager @recalculationPromise = @systemManager.activate(system).progress(@onRecalculateProgress) @recalculationPromise.done( => @joystick.enable() + @animation.setF(system.animation, @controls.animation) + @animation.toggle(system.play) @renderer.prepare(system) @syncAll(); @draw() @@ -65,10 +96,16 @@ class AppManager @recalculationPromise lsystemFromControls: -> + play = $(@controls.play).hasClass('play') + animation = $(@controls.animation).val() + Defaults.play = play + Defaults.animation = animation return new LSystem( @paramControls.toJson(), @offsetControls.toJson(), @sensitivityControls.toJson(), + play, + animation, $(@controls.rules).val(), parseInt($(@controls.iterations).val()), $(@controls.name).val() @@ -110,12 +147,17 @@ class AppManager .always(@run) run: => - requestAnimationFrame(@run, @container) - @inputHandler.update(@systemManager.activeSystem) if @joystick.active and not @renderer.isDrawing - @draw() @joystick.draw() + @inputHandler.update(@systemManager.activeSystem) + @syncControls() + @draw() + if @animation.active and not @renderer.isDrawing + @animation.draw() + @animationHandler.update(@systemManager.activeSystem) @syncControls() + @draw() + requestAnimationFrame(@run, @container) draw: (renderer = @renderer) => elems = @systemManager.getInstructions() @@ -138,6 +180,11 @@ class AppManager syncRulesAndIterations: (system = @systemManager.activeSystem) -> $(@controls.iterations).val(system.iterations) $(@controls.rules).val(system.rules) + $(@controls.animation).val(system.animation) + if (system.play) + $(@controls.play).addClass('play') + else + $(@controls.play).removeClass('play') syncControls: (system = @systemManager.activeSystem) -> @paramControls.sync(system.params) diff --git a/css/ui.scss b/css/ui.scss index 6e1e0f6..03ab442 100644 --- a/css/ui.scss +++ b/css/ui.scss @@ -88,7 +88,7 @@ li{ } .header{ - height:50px; + height:40px; background-color: #1b1b1b; border-bottom:1px solid #545454; background-image:-webkit-linear-gradient(top, #222222, #111111); @@ -97,7 +97,12 @@ li{ .actions{ padding-top:13px; padding-right:0px; - a{ padding:2px 1px 1px 1px; } + } + img{ + float:left; + width:40px; + height:40px; + padding-top:4px; } a.site{ font-family:monospace; @@ -108,11 +113,6 @@ li{ font-weight:bold; text-decoration: none; display:block; - img{ - width:40px; - height:40px; - float:left; - } span{ margin-left:10px; line-height:42px; @@ -189,7 +189,13 @@ li{ input[type=text]{ text-align: right; } - + textarea.error{ + border-color: rgb(219, 43, 43); + } + pre{ + padding: 0; + margin: 0; + } &.validation-enabled{ input:invalid{ @@ -202,6 +208,7 @@ li{ li{ display:inline-block; &.section{ + position: relative; background-color: #27292c; width:100%; padding:4px 0px; @@ -210,7 +217,7 @@ li{ border-bottom:1px solid #464646; border-top:1px solid #191919; text-align: center; - i{ + i.icon-right{ position:absolute; right:0; &:hover{ @@ -251,6 +258,12 @@ li{ width:64px; padding-left:8px; } + li.label pre { + margin-left:-8px; + } + .right { + text-align:right; + } li .label { display:inline-block; width:68px; @@ -349,11 +362,14 @@ li{ padding-top:7px; height:30px; a{ - font-size:8pt; - padding:2px 0px 1px 1px; - margin-right:5px; - display:inline-block; position: relative; + display:inline-block; + width: 16px; + height: 16px; + padding: 2px 1px 1px 1px; + margin-right:5px; + text-align:center; + font-size:8pt; background-color: #1f1f20; &.facebook,&.twitter,&.gplus{ @@ -385,11 +401,21 @@ li{ font-size:12px; padding:0px 0px 0px 0px; } - i.icon-toggle{ + i.icon-side{ font-weight: bold; letter-spacing: -2px; padding: 0 3px; } + i.icon-animation{ + padding: 2px; + letter-spacing: -1px; + } + i.icon-animation::after{ + content: "▶"; + } + i.icon-animation.play::after{ + content: "▮▮"; + } text-decoration: none; line-height:14px; color: #808080; @@ -418,6 +444,9 @@ li{ } } +li.section .actions { + padding: 2px 0 0 0; +} .drawing-pad{ position: absolute; diff --git a/index.html b/index.html index ade899b..2b05a84 100644 --- a/index.html +++ b/index.html @@ -136,18 +136,19 @@
        -
        <<  +
        + << 
        - lsys + lsys
        >> + -->>>
        @@ -186,18 +187,30 @@ +
      • + animation +
      • + +
      • +
        function (t) = 
        +
          +
        • +
        +
      • +
      • system - +
        • -
        • rules
        • -
        • levels
        • +
        • rules =
        • +
        • iterations +
          -
        • +
      • -
        + @@ -352,6 +352,7 @@ // show/hide sidebar button $('a.toggle-side').click(function(){ $('.container-side').toggleClass('hidden'); + $('.show-side').toggleClass('hidden'); return false; }) From dc2bc56f66bc2389da0fd0c4320909799f39672d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TG=20=C3=97=20=E2=8A=99?= <*@tg-x.net> Date: Thu, 14 Nov 2019 19:55:35 +0100 Subject: [PATCH 6/8] color ui --- coffee/compilation.coffee | 16 +++++--- coffee/lsystem.coffee | 26 +++++++++++-- coffee/manager.coffee | 19 ++++++++- coffee/rendering.coffee | 11 +++++- coffee/util.coffee | 11 +++++- css/ui.scss | 25 +++++++----- index.html | 82 ++++++++++++++++++++++++++++++++------- 7 files changed, 150 insertions(+), 40 deletions(-) diff --git a/coffee/compilation.coffee b/coffee/compilation.coffee index b20d0db..68537b3 100644 --- a/coffee/compilation.coffee +++ b/coffee/compilation.coffee @@ -1,12 +1,16 @@ NullSystem = new LSystem({},{},{},0,"","",1,"no system") DefaultSystem = new LSystem( - { size: {value:12.27}, angle: {value:4187.5} }, - {}, - { size: {value:9} }, - false, - "", - "L : SS\nS : F->[F-Y[S(L]]\nY : [-|F-F+)Y]", + { size: {value: 20}, angle: {value: 9840} }, + { x: 0, y: 50, rot: 0}, + null, + "L : SS\nS : F-[F-Y[S>(L]]\nY : [-|F-F+)Y]" 12, + 0.218, + [ "black", "white", "cyan", "#e8cc00", "#007272", "#ff4c00" ], + false, + "return {\n + angleX: t/100,\n + }", "click-and-drag-me!" ) diff --git a/coffee/lsystem.coffee b/coffee/lsystem.coffee index 3577348..ffc9544 100644 --- a/coffee/lsystem.coffee +++ b/coffee/lsystem.coffee @@ -36,6 +36,8 @@ class Defaults @_sensitivites: -> size: {value: 7.7, growth:7.53} angle: {value: 7.6, growth:4} + @lineWidth: 0.218 + @colors: [ "black", "white", "cyan", "#e8cc00", "#007272", "#ff4c00" ] @play: 0 @animation: " return {\n @@ -50,7 +52,7 @@ class Defaults # ========================================= class LSystem - constructor: (params, offsets, sensitivities, play, animation, @rules, @iterations, @name) -> + constructor: (params, offsets, sensitivities, @rules, @iterations, lineWidth, colors, play, animation, @name) -> @params = Util.map(Defaults.params(params), (c) -> Param.fromJson(c)) @offsets = Defaults.offsets(offsets) @sensitivities = Util.map(Defaults.sensitivities(sensitivities), (s) -> Sensitivity.fromJson(s)) @@ -64,6 +66,14 @@ class LSystem animation else Defaults.animation + @lineWidth = + if typeof lineWidth == 'number' && Number.isFinite lineWidth + then lineWidth + else Defaults.lineWidth + @colors = + if typeof colors == 'object' + then colors + else Defaults.colors # this is not the most efficient of methods... clone: -> return LSystem.fromUrl(@toUrl()) @@ -74,10 +84,12 @@ class LSystem params = mkQueryString(@params) offsets = "&offsets=#{@offsets.x},#{@offsets.y},#{@offsets.rot}" sensitivities = mkQueryString(@sensitivities) + lineWidth = "&l="+@lineWidth + colors = "&c="+@colors.join(',') play = "&play="+@play animation = "&anim="+encodeURIComponent(@animation) name = "&name=#{encodeURIComponent(@name)}" - return base+params+offsets+sensitivities+play+animation+name + return base+params+offsets+sensitivities+lineWidth+colors+play+animation+name merge: (system) -> _.extend(@, system) if system @@ -110,8 +122,14 @@ class LSystem else null - return new LSystem(params, offsets, sensitivities, parseInt(config.play), - anim, decodeURIComponent(config.r), config.i, + colors = undefined + if (config.c) + colors = config.c.split(',') + + return new LSystem(params, offsets, sensitivities, + decodeURIComponent(config.r), parseInt(config.i), + parseFloat(config.l), colors, + parseInt(config.play), anim, decodeURIComponent(config.name) or "unnamed") isIsomorphicTo: (system) -> if (!system) then false else @rules == system.rules and @iterations == system.iterations diff --git a/coffee/manager.coffee b/coffee/manager.coffee index f4ea169..f839274 100644 --- a/coffee/manager.coffee +++ b/coffee/manager.coffee @@ -104,10 +104,12 @@ class AppManager @paramControls.toJson(), @offsetControls.toJson(), @sensitivityControls.toJson(), - play, - animation, $(@controls.rules).val(), parseInt($(@controls.iterations).val()), + parseFloat($(@controls.lineWidth).val()), + Util.mapArray(@controls.colors, (el) -> el.value), + play, + animation, $(@controls.name).val() ) @@ -176,6 +178,7 @@ class AppManager $(@controls.name).val(system.name) @syncControls(system) @syncRulesAndIterations(system) + @syncLineStyle(system) syncRulesAndIterations: (system = @systemManager.activeSystem) -> $(@controls.iterations).val(system.iterations) @@ -186,6 +189,18 @@ class AppManager else $(@controls.play).removeClass('play') + syncFloat: (input, value) -> + if (parseFloat(input.val()) != value and not isNaN(parseFloat(value))) then input.val(value) + + syncColor: (input, value) -> + $(input).val(value) + input.style.backgroundColor = value + + syncLineStyle: (system = @systemManager.activeSystem) -> + @syncFloat($(@controls.lineWidth), system.lineWidth) + @syncColor(@controls.colors[i], col) for col, i in system.colors + @container.style.backgroundColor = @controls.colors[0].value + syncControls: (system = @systemManager.activeSystem) -> @paramControls.sync(system.params) @offsetControls.sync(system.offsets) diff --git a/coffee/rendering.coffee b/coffee/rendering.coffee index c042ad2..78c8e38 100644 --- a/coffee/rendering.coffee +++ b/coffee/rendering.coffee @@ -38,7 +38,8 @@ class Bounding #================================================================ getG = (c) -> c.getContext('2d') -getColors = (system) -> ["#ffffff","#0044DD","#00DD44","#DD4400"] +getColors = (system) -> system.colors.slice(1) +getLineWidth = (system) -> system.lineWidth class Renderer context:null @@ -78,7 +79,7 @@ class Renderer this.reset(system) @context.gs.forEach (g,i) => - g.lineWidth = 0.218 + g.lineWidth = getLineWidth(system) g.beginPath() g.moveTo(@context.state.x, @context.state.y) @@ -147,5 +148,11 @@ class Renderer "1": (state, params, context, a) -> state.color = a; if (context.g != context.gs[1]) then ( context.g = context.gs[1]; context.g.moveTo(state.x,state.y)) "2": (state, params, context, a) -> state.color = a; if (context.g != context.gs[2]) then ( context.g = context.gs[2]; context.g.moveTo(state.x,state.y)) "3": (state, params, context, a) -> state.color = a; if (context.g != context.gs[3]) then ( context.g = context.gs[3]; context.g.moveTo(state.x,state.y)) + "4": (state, params, context, a) -> state.color = a; if (context.g != context.gs[4]) then ( context.g = context.gs[4]; context.g.moveTo(state.x,state.y)) + "5": (state, params, context, a) -> state.color = a; if (context.g != context.gs[5]) then ( context.g = context.gs[5]; context.g.moveTo(state.x,state.y)) + "6": (state, params, context, a) -> state.color = a; if (context.g != context.gs[6]) then ( context.g = context.gs[6]; context.g.moveTo(state.x,state.y)) + "7": (state, params, context, a) -> state.color = a; if (context.g != context.gs[7]) then ( context.g = context.gs[7]; context.g.moveTo(state.x,state.y)) + "8": (state, params, context, a) -> state.color = a; if (context.g != context.gs[8]) then ( context.g = context.gs[8]; context.g.moveTo(state.x,state.y)) + "9": (state, params, context, a) -> state.color = a; if (context.g != context.gs[9]) then ( context.g = context.gs[9]; context.g.moveTo(state.x,state.y)) } )() diff --git a/coffee/util.coffee b/coffee/util.coffee index 9abc42e..08de09c 100644 --- a/coffee/util.coffee +++ b/coffee/util.coffee @@ -1,8 +1,10 @@ class Util @log:(x) -> console.log(x) - @control:(name) -> document.getElementById(name) + @control: (name) -> + document.getElementById(name) || document.getElementsByClassName(name) @value: (name) => parseFloat(Util.stringvalue(name)) @stringvalue: (name) -> Util.control(name).value + @arrayvalue: (name) -> Util.mapArray(Util.control(name), (el) -> el.value) @clone:(x) -> JSON.parse(JSON.stringify(x)) @toObj:(kvPairs) -> obj = {} @@ -10,9 +12,14 @@ class Util return obj @map: (obj, fn) -> result = {} - for key of obj then do -> + for own key of obj then do -> result[key] = fn(obj[key], key) return result + @mapArray: (obj, fn) -> + result = [] + for own key of obj then do -> + result.push(fn(obj[key], key)) + return result @merge: (a,b,c) -> $.extend(true, a,b,c) @round: (n,d) -> pow = Math.pow(10,d) diff --git a/css/ui.scss b/css/ui.scss index 4c3aa96..74b2eaa 100644 --- a/css/ui.scss +++ b/css/ui.scss @@ -1,3 +1,4 @@ +$opacity: 0.7; $sideWidth: 230px; div { @@ -89,7 +90,7 @@ li{ .header{ height:40px; - background-color: #1b1b1b; + background-color: rgba(27,27,27,$opacity); border-bottom:1px solid #545454; background-image:-webkit-linear-gradient(top, #222222, #111111); box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1); @@ -123,8 +124,10 @@ li{ .show-side.actions{ padding-top: 13px; } -.show-side.actions a{ +.show-side.actions a { border: 0; + color: white; + text-shadow: 1px 1px black; } .container-side{ @@ -134,13 +137,13 @@ li{ bottom: 0; width: $sideWidth; cursor: default; - background-color:#333; + background-color: rgba(0,0,0,$opacity); + text-shadow: 1px 1px black; padding: 0 0 0 0px; border-left:1px solid #555; scrollbar-width: thin; overflow-x: hidden; overflow-y: auto; - opacity: 0.7; /* &::-webkit-scrollbar{width:4px;height:4px;} &::-webkit-scrollbar-button:start:decrement, &::-webkit-scrollbar-button:end:increment{ display:block;height:2px;background-color: #232323; } @@ -163,7 +166,7 @@ li{ .container-ticker{ padding:0; border-top:1px solid #666; - background-color:#222; + background-color: rgba(34,34,34,$opacity); .fade{ position:absolute; right:0; bottom:0; left:0; @@ -182,13 +185,17 @@ li{ color:#bbb; border-radius:2px; border:1px solid #1d1d1d; - background-color: #42474a; + background-color: rgba(66,71,74,$opacity); + text-shadow: 1px 1px black; font-size:9pt; padding:3px 5px; } input[type=text]{ text-align: right; } + input[type=text].color{ + text-shadow: 1px 1px black; + } textarea.error{ border-color: rgb(219, 43, 43); } @@ -209,7 +216,7 @@ li{ display:inline-block; &.section{ position: relative; - background-color: #27292c; + background-color: rgba(39,41,44,$opacity); width:100%; padding:4px 0px; margin-top:6px; @@ -483,13 +490,13 @@ li.section .actions { margin:1px; border-radius:1px; position:relative; - background-color: #2c2e33; + background-color: rgba(44,46,51,$opacity); width:103px; height:125px; font-size:8pt; &:hover{ - background-color: #3f444a; + background-color: rgba(63,68,74,$opacity); } .img-holder{ text-align: center; diff --git a/index.html b/index.html index 16ffef7..ca7e967 100644 --- a/index.html +++ b/index.html @@ -128,10 +128,16 @@
        + - + + + + + +
        @@ -187,17 +193,6 @@ -
      • - animation -
      • - -
      • -
        function (t) = 
        -
          -
        • -
        -
      • -
      • system @@ -223,6 +218,59 @@
      • + +
      • + animation +
      • + +
      • +
        function (t) = 
        + +
      • + +
      • + style +
      • + +
      • + + + + + + + +
      • @@ -255,10 +303,12 @@ params: 'controls-container', offsets: 'offsets-container', sensitivities: 'sensitivities-container', - play: 'play', - animation: 'animation', iterations: 'iterations', rules: 'rules', + play: 'play', + animation: 'animation', + lineWidth: 'line-width', + colors: 'color', name: 'system-name' }, Util.control), new Animation() @@ -372,9 +422,11 @@ $("#controls-container,#offsets-container").find("[data-param]").on("input", syncIfNotEmpty ); $("#sensitivities-container").find("[data-param]").on("input", syncIfNotEmpty ); $("#system-name").on("input", sync ); - $("#animation").on("input", syncAnimation); $("#rules").on("input", sync); $("#iterations").on("input", syncIfNotEmpty); + $("#line-width").on("input", syncIfNotEmpty); + $(".color").on("input", syncIfNotEmpty); + $("#animation").on("input", syncAnimation); $(".controls").addClass('validation-enabled'); }); },0); From 653222b3a070411c898f222f0590d89bd97c9c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TG=20=C3=97=20=E2=8A=99?= <*@tg-x.net> Date: Thu, 14 Nov 2019 21:01:24 +0100 Subject: [PATCH 7/8] anim --- coffee/compilation.coffee | 10 ++++++++-- coffee/lsystem.coffee | 8 ++++---- coffee/manager.coffee | 16 ++++++++-------- index.html | 22 +++++++++++----------- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/coffee/compilation.coffee b/coffee/compilation.coffee index 68537b3..b1831c3 100644 --- a/coffee/compilation.coffee +++ b/coffee/compilation.coffee @@ -9,8 +9,14 @@ DefaultSystem = new LSystem( [ "black", "white", "cyan", "#e8cc00", "#007272", "#ff4c00" ], false, "return {\n - angleX: t/100,\n - }", + angle: t/100,\n + angleG: t/100,\n + size: null,\n + sizeG: null,\n + offsetX: null,\n + offsetY: null,\n + rotation: null\n + }", "click-and-drag-me!" ) diff --git a/coffee/lsystem.coffee b/coffee/lsystem.coffee index ffc9544..3bf9fc6 100644 --- a/coffee/lsystem.coffee +++ b/coffee/lsystem.coffee @@ -41,10 +41,10 @@ class Defaults @play: 0 @animation: " return {\n - angleX: t/100,\n - angleY: t/100,\n - sizeX: null,\n - sizeY: null,\n + angle: t/100,\n + angleG: t/100,\n + size: null,\n + sizeG: null,\n offsetX: null,\n offsetY: null,\n rotation: null\n diff --git a/coffee/manager.coffee b/coffee/manager.coffee index f839274..4ba1a09 100644 --- a/coffee/manager.coffee +++ b/coffee/manager.coffee @@ -23,14 +23,14 @@ class AnimationHandler update: (system) => return if not @animation.active d = @animation.state() - if (typeof d.angleX == 'number' && Number.isFinite d.angleX) - system.params.angle.value = Util.round(system.params.angle.value + d.angleX * @sensitivity(system.sensitivities.angle.value), 4) - if (typeof d.angleY == 'number' && Number.isFinite d.angleY) - system.params.angle.growth = Util.round(system.params.angle.growth + d.angleY * @sensitivity(system.sensitivities.angle.growth), 9) - if (typeof d.sizeX == 'number' && Number.isFinite d.sizeX) - system.params.size.value = Util.round(@snapshot.params.size.value + d.sizeX * @sensitivity(system.sensitivities.size.value), 2) - if (typeof d.sizeY == 'number' && Number.isFinite d.sizeY) - system.params.size.growth = Util.round(@snapshot.params.size.growth + d.sizeY * @sensitivity(system.sensitivities.size.growth), 6) + if (typeof d.angle == 'number' && Number.isFinite d.angle) + system.params.angle.value = Util.round(system.params.angle.value + d.angle * @sensitivity(system.sensitivities.angle.value), 4) + if (typeof d.angleG == 'number' && Number.isFinite d.angleG) + system.params.angle.growth = Util.round(system.params.angle.growth + d.angleG * @sensitivity(system.sensitivities.angle.growth), 9) + if (typeof d.size == 'number' && Number.isFinite d.size) + system.params.size.value = Util.round(@snapshot.params.size.value + d.size * @sensitivity(system.sensitivities.size.value), 2) + if (typeof d.sizeG == 'number' && Number.isFinite d.sizeG) + system.params.size.growth = Util.round(@snapshot.params.size.growth + d.sizeG * @sensitivity(system.sensitivities.size.growth), 6) if (typeof d.offsetX == 'number' && Number.isFinite d.offsetX) system.offsets.x = @snapshot.offsets.x + d.offsetX if (typeof d.offsetY == 'number' && Number.isFinite d.offsetY) diff --git a/index.html b/index.html index ca7e967..64ed92c 100644 --- a/index.html +++ b/index.html @@ -219,17 +219,6 @@ -
      • - animation -
      • - -
      • -
        function (t) = 
        - -
      • -
      • style
      • @@ -271,6 +260,17 @@ -->
      • + +
      • + animation +
      • + +
      • +
        function (t) = 
        + +
      • From f3e46e3dbd6489efa8d666c968df657cbf71f7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TG=20=C3=97=20=E2=8A=99?= <*@tg-x.net> Date: Thu, 14 Nov 2019 21:02:32 +0100 Subject: [PATCH 8/8] fix ticker url --- ticker.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ticker.json b/ticker.json index 756d321..6022831 100644 --- a/ticker.json +++ b/ticker.json @@ -97,7 +97,7 @@ "img": "lsys_spirocopter.png", "author": "benvan" }, { - "url": "http://localhost:8000/#i=33&r=C%20%3A%20S!%7CVFFC%0AS%20%3A%20F!%2B%3E%2B%5BF-YS%5D%0AY%20%3A%20%5BF-FA%2BY%5D%0AA%20%3A%20F-%3E--A%7C%2B%0AV%20%3A%20XVY%0AX%20%3A%20-%0AY%20%3A%20!&p.size=23.82,0.01&p.angle=-2040,0.05&s.size=9,7&s.angle=7.6,4&offsets=0,0,0&name=kinetica", + "url": "#i=33&r=C%20%3A%20S!%7CVFFC%0AS%20%3A%20F!%2B%3E%2B%5BF-YS%5D%0AY%20%3A%20%5BF-FA%2BY%5D%0AA%20%3A%20F-%3E--A%7C%2B%0AV%20%3A%20XVY%0AX%20%3A%20-%0AY%20%3A%20!&p.size=23.82,0.01&p.angle=-2040,0.05&s.size=9,7&s.angle=7.6,4&offsets=0,0,0&name=kinetica", "img": "lsys_kinetica.png", "author": "benvan" }, {