Last active
September 25, 2017 01:33
-
-
Save osvein/934e55e7ffd42d4a9b24241a8c17688d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?xml version="1.0" encoding="UTF-8" ?> | |
| <?xml-stylesheet type="text/css" href="ui.css" ?> | |
| <?xml-stylesheet type="text/css" href="despic.css" ?> | |
| <?xml-stylesheet type="text/css" href="surreal.css" ?> | |
| <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 2 1.35" width="920" height="621" overflow="hidden"> | |
| <set id="all_anims" begin="indefinite" /> | |
| <defs> | |
| <filter id="glow"> | |
| <feGaussianBlur stdDeviation="0.005" result="blur" /> | |
| <feMerge> | |
| <feMergeNode in="blur" /> | |
| <feMergeNode in="SourceGraphic" /> | |
| </feMerge> | |
| </filter> | |
| </defs> | |
| <symbol id="btnsym" class="btnsym" viewBox="0 0 62.5 13.5"> | |
| <polygon points="3,3 8.1,7.5 3,12.5" /> | |
| </symbol> | |
| <rect class="bg" width="100%" height="100%" /> | |
| <g id="grid" class="grid" transform="translate(1 0.5)"> | |
| <line id="grid_deep" x2="0.1" y2="0.5" /> | |
| <use href="#grid_deep" transform="scale(-1 1)" /> | |
| <line id="grid_wide" x1="-1.1" x2="1.1" y1="0.5" y2="0.5" /> | |
| </g> | |
| <svg viewBox="-128 -256 256 512" x="0.725" y="0.14" width="0.55" height="1.1" overflow="visible"> | |
| <animateMotion id="jump" by="0 -0.5" dur="1s" fill="freeze" calcMode="spline" keySplines="0 0 0.5 1" begin="jump_release.end" /> | |
| <animateMotion id="fall" by="0 0.5" dur="1s" fill="freeze" calcMode="spline" keySplines="0.5 0 1 1" begin="jump.end" /> | |
| <defs> | |
| <clipPath id="mouthclip"> | |
| <use href="#mouth" /> | |
| <rect x="-50%" y="-20" width="100%" height="100%" /> | |
| </clipPath> | |
| <mask id="mouthmask"> | |
| <use href="#mouth" fill="white" /> | |
| <rect x="-50" y="-20" width="100" height="100" fill="white" /> | |
| </mask> | |
| <g id="arm"> | |
| <!-- l 0,78 l -12,0 l 0,-78 --> | |
| <!-- straight arms: <path d="M 0,0 a 16,16 0 0,1 16,16 A 1000,1000 0 0,1 16,94 L 4,94 A 1000,1000 0 0,0 4,16 a 8,8 0 0,0 -4,-4" />--> | |
| <path d="M 0,0 a 16,16 0 0,1 16,16 A 40,40 0 0,1 -40,48 l 8.5,-8.5 A 28,28 0 0,0 4,16 a 8,8 0 0,0 -4,-4"> | |
| <animateTransform attributeName="transform" type="rotate" by="55" dur="2s" fill="freeze" begin="banana_eat.begin" /> | |
| <animateTransform attributeName="transform" type="rotate" by="-55" dur="1s" fill="freeze" begin="banana_eat.end" /> | |
| </path> | |
| </g> | |
| </defs> | |
| <g class="outline denim"> | |
| <g id="leg" transform="translate(8 96)"> | |
| <rect width="24" height="24" /> | |
| <g class="shoe" transform="translate(0 24)"> | |
| <rect width="24" height="24" /> | |
| <rect width="12" height="21" x="24" y="3" /> | |
| <circle cx="36" cy="12" r="12" /> | |
| </g> | |
| </g> | |
| <use href="#leg" transform="scale(-1 1)"/> | |
| </g> | |
| <g class="outline"> | |
| <animateMotion id="jump_charge" by="0 20" dur="2s" fill="freeze" begin="all_anims.begin+15s"/> | |
| <animateMotion id="jump_release" by="0 -20" dur="0.1s" fill="freeze" begin="jump_charge.end+1s"/> | |
| <animateMotion id="fall_suspend" by="0 20" dur="0.1s" fill="freeze" calcMode="spline" keySplines="0 0 0.1 1" begin="fall.end"/> | |
| <animateMotion id="fall_release" by="0 -20" dur="1s" fill="freeze" begin="fall_suspend.end"/> | |
| <g class="skin"> | |
| <rect x="-64" y="-96" width="128" height="192" /> | |
| <path d="M -64,-96 A 64,64 0 0,1 64,-96" /> | |
| </g> | |
| <ellipse id="mouth" cy="-20" rx="15" ry="5"> | |
| <animate id="mouth_hng" attributeName="ry" to="2" dur="0.5s" begin="jump_charge.begin" fill="freeze" /> | |
| <animate id="mouth_o" attributeName="ry" to="15" dur="0.5s" begin="banana_in.begin;jump.begin" fill="freeze" /> | |
| <animate id="mouth_eat" attributeName="rx" to="35" dur="2.7s" begin="banana_eat.begin" fill="freeze" /> | |
| <animate id="mouth_normalX" attributeName="rx" to="15" dur="2.5s" begin="mouth_eat.end;fall_suspend.begin" fill="freeze" /> | |
| <animate id="mouth_normalY" attributeName="ry" to="5" dur="2.5s" begin="mouth_eat.end;fall_suspend.begin" fill="freeze" /> | |
| </ellipse> | |
| <g transform="translate(0 -84)"> | |
| <rect x="-64" y="-12" width="128" height="24" /> | |
| <g class="goggle"> | |
| <circle cx="-28" r="28" /> | |
| <circle cx="28" r="28" /> | |
| <animate id="redeye_in" attributeName="fill" to="#ff7f7f" dur="5s" fill="freeze" begin="all_anims.begin" /> | |
| <animate id="redeye_out" attributeName="fill" to="#ffffff" dur="5s" fill="freeze" begin="scronch.end" /> | |
| </g> | |
| <g class="eye"> | |
| <circle id="eyeL" r="8" transform="translate(-28 0)" /> | |
| <circle id="eyeR" r="8" transform="translate(28 0)" /> | |
| </g> | |
| </g> | |
| <g class="denim"> | |
| <rect x="-64" y="64" width="128" height="32" /> | |
| <rect x="-48" y="8" width="96" height="64" /> | |
| </g> | |
| <g> | |
| <svg viewBox="0 0 1 1" x="-35" y="-450" width="100" height="100"> | |
| <defs> | |
| <linearGradient id="banana_stemgrad"> | |
| <stop offset="0%" stop-color="#ffff3f" /> | |
| <stop offset="60%" stop-color="yellowgreen" /> | |
| </linearGradient> | |
| <path id="peelpath" class="peelpath" d="M 0.45,0.05 A 0.59,0.59 0 0,0 0.342,0.44" /> | |
| <rect id="peelclip" y="-100%" width="100%" height="200%" /> | |
| <clipPath id="peelclipL"> | |
| <use href="#peelclip" /> | |
| <animateMotion dur="3s" rotate="auto" fill="freeze" begin="banana_peelL.begin"><mpath href="#peelpath" /></animateMotion> | |
| </clipPath> | |
| <clipPath id="peelclipR"> | |
| <use href="#peelclip" /> | |
| <animateMotion dur="3s" rotate="auto" fill="freeze" begin="banana_peelR.begin"><mpath href="#peelpath" /></animateMotion> | |
| </clipPath> | |
| </defs> | |
| <symbol id="banana_shape" viewBox="0 0 1 1"> | |
| <path d="M 0.5,0 a 0.45,0.45 0 0,0 0,0.8 m 0,-0.8 a 0.6,0.6 0 0,0 0,0.8 a 1.2,1.2 0 0,1 0,-0.8" /> | |
| </symbol> | |
| <g class="banana"> | |
| <path d="M 0.45,0.05 l -0.05,0 A 0.45,0.45 0 0,0 0.5,0.8 A 1.2,1.2 0 0,1 0.48,0.06 Z" /> | |
| <path d="M 0.40,0.05 A 0.45,0.45 0 0,0 0.5,0.8 A 0.59,0.59 0 0,1 0.45,0.05 Z" clip-path="url(#peelclipL)" class="banana_peel" /> | |
| <path d="M 0.48,0.06 A 1.20,1.20 0 0,0 0.5,0.8 A 0.59,0.59 0 0,1 0.45,0.05 Z" clip-path="url(#peelclipR)" class="banana_peel_shade" /> | |
| <rect class="banana_stem" x="0.44" y="0.7675" width="0.12" height="0.05" transform="rotate(45 0.5 0.8)" /> | |
| <polygon class="banana_end" points="0.40,0.05 0.48,0.06 0.45,0.05" clip-path="url(#peelclipL)" /> | |
| <path d="M 0,0 a 0.15,0.15 0 0,0 0,0.25 a 0.13,0.13 0 0,1 0,-0.18" class="banana_peel"> | |
| <animateMotion id="banana_peelL" dur="3s" rotate="auto" fill="freeze" begin="banana_in.end+3s"><mpath href="#peelpath" /></animateMotion> | |
| <animateTransform attributeName="transform" type="scale" from="0.7 0.7" to="1.4 1.4" dur="3s" fill="freeze" begin="banana_peelL.begin" /> | |
| </path> | |
| <path d="M 0,0 a 0.15,0.15 0 0,0 0,0.25 a 0.13,0.13 0 0,1 0,-0.18" class="banana_peel_shade"> | |
| <animateMotion id="banana_peelR" dur="3s" rotate="auto" fill="freeze" begin="banana_peelL.end"><mpath href="#peelpath" /></animateMotion> | |
| <animateTransform attributeName="transform" type="scale" from="0.7 -0.7" to="1.3 -1.3" dur="3s" fill="freeze" begin="banana_peelR.begin" /> | |
| </path> | |
| </g> | |
| <animate attributeName="y" id="banana_in" to="5" dur="0.5s" fill="freeze" begin="all_anims.begin+25s" /> | |
| <animate attributeName="y" id="banana_eat" to="-120" dur="5s" fill="freeze" begin="banana_peelR.end" /> | |
| </svg> | |
| <set attributeName="clip-path" to="url(#mouthclip)" begin="banana_in.end" /> | |
| </g> | |
| <g class="skin"> | |
| <use href="#arm" transform="translate(64 2)"> | |
| <animateTransform attributeName="transform" type="rotate" by="44" dur="2s" fill="freeze" begin="banana_in.begin" /> | |
| <animateTransform attributeName="transform" type="rotate" by="-44" dur="3s" fill="freeze" begin="banana_peelR.begin" /> | |
| <animateTransform attributeName="transform" type="rotate" by="40" dur="2s" fill="freeze" begin="jump_charge.begin" /> | |
| <animateTransform attributeName="transform" type="rotate" by="-40" dur="1.1s" fill="freeze" begin="jump_release.begin" /> | |
| <animateTransform attributeName="transform" type="rotate" by="40" dur="1.1s" fill="freeze" begin="fall.begin" /> | |
| <animateTransform attributeName="transform" type="rotate" by="-40" dur="1s" fill="freeze" begin="fall_release.begin" /> | |
| </use> | |
| <use href="#arm" transform="scale(-1 1) translate(64 2)"> | |
| <animateTransform attributeName="transform" type="rotate" by="55" dur="2s" fill="freeze" begin="banana_in.begin" /> | |
| <animateTransform attributeName="transform" type="rotate" by="-55" dur="2.5s" fill="freeze" begin="banana_peelL.begin+0.5s" /> | |
| <animateTransform attributeName="transform" type="rotate" by="40" dur="2s" fill="freeze" begin="jump_charge.begin" /> | |
| <animateTransform attributeName="transform" type="rotate" by="-40" dur="1.1s" fill="freeze" begin="jump_release.begin" /> | |
| <animateTransform attributeName="transform" type="rotate" by="40" dur="1.1s" fill="freeze" begin="fall.begin" /> | |
| <animateTransform attributeName="transform" type="rotate" by="-40" dur="1s" fill="freeze" begin="fall_release.begin" /> | |
| </use> | |
| </g> | |
| <g class="denim"> | |
| <g transform="translate(0 8)"> | |
| <rect id="overall_strap" width="32" height="8" transform="translate(40 0) rotate(-30 4 4)" /> | |
| <use href="#overall_strap" transform="scale(-1 1)" /> | |
| </g> | |
| <g class="overall_btn" transform="translate(0 40)"> | |
| <circle id="overall_btn" cx="44" cy="28" r="2" /> | |
| <use href="#overall_btn" transform="scale(-1 1)" /> | |
| <use href="#overall_btn" transform="scale(1 -1)" /> | |
| <use href="#overall_btn" transform="scale(-1 -1)" /> | |
| </g> | |
| </g> | |
| </g> | |
| </svg> | |
| <svg class="ui" y="1"> | |
| <rect class="uibg" width="100%" height="0.35" /> | |
| <rect x="0.02" y="0.02" width="1.27" height="0.31" /> | |
| <rect x="1.32" y="0.02" width="0.66" height="0.31" /> | |
| <text x="0.15" y="0.1025">ŠCRONÇH</text> | |
| <rect id="btnUL" class="btnbox" x="0.03" y="0.03" width="0.625" height="0.145" onclick="runAnimation(1)" /> | |
| <text x="0.15" y="0.2475">VEGETAL</text> | |
| <rect id="btnLL" class="btnbox" x="0.03" y="0.175" width="0.625" height="0.145" onclick="runAnimation(3)" /> | |
| <text x="0.785" y="0.1025">花の専門™</text> | |
| <rect id="btnUR" class="btnbox" x="0.655" y="0.03" width="0.625" height="0.145" onclick="runAnimation(2)" /> | |
| <text x="0.785" y="0.2475">ALL</text> | |
| <rect id="btnLR" class="btnbox" x="0.655" y="0.175" width="0.625" height="0.145" onclick="runAnimation(0)" /> | |
| <use href="#btnsym" x="0.05" y="0.03" width="0.625" height="0.145"> | |
| <set attributeName="x" to="0.675" begin="btnUR.mousemove;btnLR.mousemove" end="btnUL.mousemove;btnLL.mousemove" /> | |
| <set attributeName="y" to="0.175" begin="btnLL.mousemove;btnLR.mousemove" end="btnUL.mousemove;btnUR.mousemove" /> | |
| </use> | |
| <g class="text_hidden"> | |
| <text x="0.785" y="0.19">exterminate</text> | |
| <text x="0.785" y="0.3">thots</text> | |
| <set attributeName="visibility" to="visible" begin="btnLR.mousemove" end="btnUL.mousemove;btnUR.mousemove;btnLL.mousemove" /> | |
| </g> | |
| <text x="1.35" y="0.1025">PP 10/44</text> | |
| <text x="1.35" y="0.2475">TYPÊ/¿??</text> | |
| </svg> | |
| <g class="scronch"> | |
| <circle id="scronch_circle" r="0.05" /> | |
| <path id="scronch_path" d="M 0,0 L 0,0 L 0,0 L 0,0 Z" /> | |
| <set id="scronch" attributeName="visibility" to="visible" dur="500s" begin="redeye_in.end" /> | |
| </g> | |
| <script> | |
| //<![CDATA[ | |
| function reset() { | |
| /*document.rootElement.setCurrentTime(100); | |
| var anims = document.querySelectorAll("set, animate, animateColor, animateMotion, animateTransform"); | |
| for (i = 0; i < anims.length; i++) anims[i].endElement(); | |
| alert(anims.length); | |
| var evt = document.createEvent("HTMLEvent"); | |
| evt.initEvent("load", true, true); | |
| document.rootElement.dispatchEvent(evt);*/ | |
| } | |
| var anims = [ | |
| document.getElementById("all_anims"), | |
| document.getElementById("redeye_in"), | |
| document.getElementById("jump_charge"), | |
| document.getElementById("banana_in"), | |
| ] | |
| var animi = parseInt(window.location.hash.substring(1)) | |
| if (animi <= 3) anims[animi].beginElement(); | |
| function runAnimation(id) { | |
| window.location.hash = "#" + id; | |
| window.location.reload(); | |
| } | |
| // grid | |
| const spacingDeep = 0.2; | |
| const spacingWide = 0.8; | |
| const grid = document.getElementById("grid"); | |
| const gridDeep = document.getElementById("grid_deep"); | |
| const gridWide = document.getElementById("grid_wide"); | |
| // x2(n) = spacingDeep * n + 0.1 | |
| var x2 = 0.1; | |
| while ((x2 += spacingDeep) < 1.1) { | |
| var node = gridDeep.cloneNode(); | |
| node.x2.baseVal.value = x2; | |
| grid.appendChild(node); | |
| node = gridDeep.cloneNode(); | |
| node.x2.baseVal.value = -x2; | |
| grid.appendChild(node); | |
| } | |
| // y(n) = (spacingWide ^ n) / 2 | |
| // x(n) = y(n) * x2(5) / 0.5 | |
| var y = 0.5; | |
| while ((y *= spacingWide) > 0.01) { | |
| var node = gridWide.cloneNode(); | |
| node.y1.baseVal.value = node.y2.baseVal.value = y; | |
| node.x1.baseVal.value = -(node.x2.baseVal.value = y * 1.1 / 0.5); | |
| grid.appendChild(node); | |
| } | |
| // eye tracking | |
| const scale = 0.012; | |
| const eyeL = document.getElementById("eyeL"); | |
| const eyeR = document.getElementById("eyeR"); | |
| function eyeTrack(pt) { | |
| var newL = pt.matrixTransform(eyeL.getScreenCTM().inverse()); | |
| eyeL.cx.baseVal.value = newL.x * scale; | |
| eyeL.cy.baseVal.value = newL.y * scale; | |
| var newR = pt.matrixTransform(eyeR.getScreenCTM().inverse()); | |
| eyeR.cx.baseVal.value = newR.x * scale; | |
| eyeR.cy.baseVal.value = newR.y * scale; | |
| //alert(newL.x + " " + newL.y + " " + newR.x + " " + newR.y); | |
| } | |
| document.rootElement.addEventListener("mousemove", function(evt) { | |
| var pt = document.rootElement.createSVGPoint(); | |
| pt.x = evt.clientX; | |
| pt.y = evt.clientY; | |
| eyeTrack(pt); | |
| }); | |
| // scronch | |
| const radius = 0.05; | |
| const scronch = document.getElementById("scronch"); | |
| const scronch_circle = document.getElementById("scronch_circle"); | |
| const scronch_path = document.getElementById("scronch_path"); | |
| scronch.addEventListener("begin", function() { | |
| document.rootElement.addEventListener("mousemove", scronchMove); | |
| }); | |
| scronch.addEventListener("end", function() { | |
| document.rootElement.removeEventListener("mousemove", scronchMove); | |
| }); | |
| function scronchMove(evt) { | |
| var pt = document.rootElement.createSVGPoint(); | |
| pt.x = evt.clientX; | |
| pt.y = evt.clientY; | |
| pt = pt.matrixTransform(scronch_circle.getScreenCTM().inverse()); | |
| scronch_circle.cx.baseVal.value = pt.x; | |
| scronch_circle.cy.baseVal.value = pt.y; | |
| var ptL = document.rootElement.createSVGPoint(); | |
| ptL.x = eyeL.cx.baseVal.value; | |
| ptL.y = eyeL.cy.baseVal.value; | |
| ptL = ptL.matrixTransform(eyeL.getTransformToElement(scronch_circle)); | |
| var ptR = document.rootElement.createSVGPoint(); | |
| ptR.x = eyeR.cx.baseVal.value; | |
| ptR.y = eyeR.cy.baseVal.value; | |
| ptR = ptR.matrixTransform(eyeR.getTransformToElement(scronch_circle)); | |
| var xfromL = ptL.x - pt.x; | |
| var yfromL = ptL.y - pt.y; | |
| var xfromR = ptR.x - pt.x; | |
| var yfromR = ptR.y - pt.y; | |
| var fromL = Math.hypot(xfromL, yfromL) / radius; | |
| var fromR = Math.hypot(xfromR, yfromR) / radius; | |
| var tanL = document.rootElement.createSVGPoint(); | |
| tanL.x = pt.x - yfromL / fromL; | |
| tanL.y = pt.y + xfromL / fromL; | |
| var tanR = document.rootElement.createSVGPoint(); | |
| tanR.x = pt.x + yfromR / fromR; | |
| tanR.y = pt.y - xfromR / fromR; | |
| var seg; | |
| seg = scronch_path.pathSegList.getItem(0); | |
| seg.x = ptL.x; | |
| seg.y = ptL.y; | |
| seg = scronch_path.pathSegList.getItem(1); | |
| seg.x = tanR.x; | |
| seg.y = tanR.y; | |
| seg = scronch_path.pathSegList.getItem(2); | |
| seg.x = ptR.x; | |
| seg.y = ptR.y; | |
| seg = scronch_path.pathSegList.getItem(3); | |
| seg.x = tanL.x; | |
| seg.y = tanL.y; | |
| //alert(pt.x + " " + pt.y + " " + tanL.x + " " + tanL.y + " " + tanR.x + " " + tanR.y); | |
| } | |
| //]]> | |
| </script> | |
| </svg> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| .outline { | |
| stroke: #000000; | |
| stroke-width: 1; | |
| } | |
| .skin { | |
| fill: #ffff00; | |
| } | |
| .denim { | |
| fill: #80b3ff; | |
| } | |
| .shoe { | |
| fill: black; | |
| } | |
| .overall_btn { | |
| fill: #000000; | |
| stroke: #1a1a1a; | |
| stroke-width: 1; | |
| } | |
| #goggle_strap { | |
| fill: #000000; | |
| } | |
| .mouth { | |
| stroke: none; | |
| fill: #1f1f00; | |
| } | |
| .goggle { | |
| fill: #ffffff; | |
| stroke: #666666; | |
| stroke-width: 8; | |
| } | |
| .eye { | |
| fill: #000000; | |
| stroke: #784421; | |
| stroke-width: 4; | |
| } | |
| .banana { | |
| fill: #ffff7f; | |
| stroke: black; | |
| stroke-width: 0.005; | |
| } | |
| .banana_peel { | |
| fill: #ffff3f; | |
| } | |
| .banana_peel_shade { | |
| fill: #ffff1f; | |
| } | |
| .banana_stem { | |
| fill: url(#banana_stemgrad); | |
| } | |
| .banana_end { | |
| fill: #531313; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| .bg { | |
| stroke: none; | |
| fill: #21002D; | |
| } | |
| #sung { | |
| filter: url(#sunglow); | |
| } | |
| .sun { | |
| stroke: none; | |
| fill: url(#sungrad); | |
| mask: url(#sunmask); | |
| } | |
| .grid { | |
| stroke-width: 0.005; | |
| stroke: #AE3CD7; | |
| fill: none; | |
| filter: url(#glow); | |
| } | |
| .orang { | |
| stroke: none; | |
| fill: orangered; | |
| opacity: 0; | |
| } | |
| .orang_stroke { | |
| stroke: orangered; | |
| stroke-width: 0.05; | |
| fill: none; | |
| } | |
| .orang_eye { | |
| fill: url(#orang_eye); | |
| } | |
| .orang_mouth { | |
| fill: #7F2300; | |
| } | |
| .scronch { | |
| stroke: none; | |
| fill: #ff3f3f; | |
| fill-rule: evenodd; | |
| visibility: hidden; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| .ui { | |
| font-size: 0.6%; | |
| font-family: fixedsys,terminal,system,"lucida console","misc fixed",monospace; | |
| fill: white; | |
| stroke: #744CD3; | |
| stroke-width: 0.02; | |
| } | |
| .ui text { | |
| stroke: none; | |
| fill: #744CD3; | |
| dominant-baseline: central; | |
| } | |
| .text_hidden { | |
| visibility: hidden; | |
| font-size: 50%; | |
| } | |
| .btnbox { | |
| fill-opacity: 0; | |
| stroke: none; | |
| } | |
| .btnsym { | |
| fill: #744CD3; | |
| } | |
| .uibg { | |
| fill: grey; | |
| stroke: none; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment