all repos — h3 @ 0c33bed966605c1dab83f2ea97c4a65272167018

A tiny, extremely minimalist JavaScript microframework.

Merge branch 'dev'
h3rald h3rald@h3rald.com
Tue, 09 Jun 2020 18:11:04 +0200
commit

0c33bed966605c1dab83f2ea97c4a65272167018

parent

4d04306829cad6cc2589180b9447c0e602e99cbe

M README.mdREADME.md

@@ -19,7 +19,7 @@ ### I'm sold! Where can I get it?

Here, look, it's just one file: -<a href="https://raw.githubusercontent.com/h3rald/h3/v0.7.0/h3.js" target="_blank" class="button primary">Download v0.7.0 (Gory Gorn)</a> +<a href="https://raw.githubusercontent.com/h3rald/h3/v0.8.0/h3.js" target="_blank" class="button primary">Download v0.8.0 (Humble Human)</a> Yes there is also a [NPM package](https://www.npmjs.com/package/@h3rald/h3) if you want to use it with WebPack and similar, but let me repeat: _it's just one file_.
M __tests__/h3.js__tests__/h3.js

@@ -32,7 +32,6 @@ classList: [],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, value: undefined,

@@ -91,7 +90,6 @@ classList: [],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, value: "test",

@@ -102,7 +100,6 @@ classList: [],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, value: undefined,

@@ -123,7 +120,6 @@ classList: ["a", "b", "c"],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, type: "div",

@@ -141,7 +137,6 @@ data: {},

attributes: {}, eventListeners: {}, id: "test", - $key: undefined, $html: undefined, style: undefined, type: "div",

@@ -160,7 +155,6 @@ classList: [],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, type: "#text",

@@ -173,7 +167,6 @@ classList: [],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, type: "#text",

@@ -185,7 +178,6 @@ classList: ["test"],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, value: undefined,

@@ -205,7 +197,6 @@ classList: [],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, type: "#text",

@@ -218,7 +209,6 @@ classList: [],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, type: "#text",

@@ -228,7 +218,6 @@ ],

data: {}, eventListeners: {}, id: "test", - $key: undefined, $html: undefined, style: undefined, value: undefined,

@@ -244,7 +233,6 @@ children: [],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, value: "AAA",

@@ -263,7 +251,6 @@ eventListeners: {

click: fn, }, id: undefined, - $key: undefined, $html: undefined, style: undefined, value: undefined,

@@ -289,7 +276,6 @@ classList: [],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, type: "#text",

@@ -305,7 +291,6 @@ classList: [],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, type: "#text",

@@ -317,7 +302,6 @@ classList: [],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, value: undefined,

@@ -332,7 +316,6 @@ classList: [],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, type: "#text",

@@ -344,7 +327,6 @@ classList: [],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, value: undefined,

@@ -354,7 +336,6 @@ classList: ["test"],

data: {}, eventListeners: {}, id: undefined, - $key: undefined, $html: undefined, style: undefined, value: undefined,
M __tests__/router.js__tests__/router.js

@@ -22,15 +22,12 @@ hash = value;

window.dispatchEvent(event); }, }; -const $onrender = (node) => { - node.classList.add("test"); -}; const C1 = () => { const parts = h3.route.parts; const content = Object.keys(parts).map((key) => h3("li", `${key}: ${parts[key]}`) ); - return h3("ul.c1", { $onrender }, content); + return h3("ul.c1", content); }; const C2 = () => {

@@ -38,7 +35,7 @@ const params = h3.route.params;

const content = Object.keys(params).map((key) => h3("li", `${key}: ${params[key]}`) ); - return h3("ul.c2", { $onrender }, content); + return h3("ul.c2", content); }; describe("h3 (Router)", () => {

@@ -69,9 +66,6 @@ it("should support the capturing of parts within the current route", (done) => {

const sub = h3.on("$redraw", () => { expect(document.body.childNodes[0].childNodes[1].textContent).toEqual( "b: 2" - ); - expect(document.body.childNodes[0].classList.contains("test")).toEqual( - true ); sub(); done();

@@ -102,34 +96,6 @@ });

} catch (e) { expect(e.message).toMatch(/No route matches/); } - }); - - it("should execute $onrender callback after each navigation", async () => { - let executions = []; - let count = 0; - const c = () => { - return h3( - "div", - { - $onrender: (node) => { - executions.push("test1"); - }, - }, - [ - h3("span", { - $onrender: (node) => { - executions.push("test2"); - }, - }), - ] - ); - }; - expect(executions).toEqual([]); - await h3.init({ - element: document.body, - routes: { "/": c }, - }); - expect(executions).toEqual(["test2", "test1"]); }); it("should execute setup and teardown methods", (done) => {
M __tests__/vnode.js__tests__/vnode.js

@@ -7,7 +7,6 @@ const obj = {

id: "test", type: "input", value: "AAA", - $key: "123", $html: "", data: { a: "1", b: "2" }, eventListeners: { click: fn },

@@ -20,7 +19,6 @@ const vnode1 = h3("br");

vnode1.from(obj); const vnode2 = h3("input#test.a1.a2", { value: "AAA", - $key: "123", $html: "", data: { a: "1", b: "2" }, onclick: fn,

@@ -321,5 +319,26 @@ expect(n1.classList[0]).toEqual("test");

expect(v1).toEqual(v2); v3.redraw({ node: n3, vnode: v4 }); expect(v3).toEqual(v4); + }); + + it("should execute $onrender callbacks whenever a child node is added to the DOM", async () => { + let n; + const $onrender = (node) => { + n = node; + }; + const vn1 = h3("ul", [h3("li")]); + const vn2 = h3("ul", [h3("li"), h3("li.vn2", { $onrender })]); + const n1 = vn1.render(); + vn1.redraw({ node: n1, vnode: vn2 }); + expect(n.classList.value).toEqual("vn2"); + const vn3 = h3("ul", [h3("span.vn3", { $onrender })]); + vn1.redraw({ node: n1, vnode: vn3 }); + expect(n.classList.value).toEqual("vn3"); + const rc = () => h3("div.rc", { $onrender }); + await h3.init(rc); + expect(n.classList.value).toEqual("rc"); + const rc2 = () => vn2; + await h3.init(rc2); + expect(n.classList.value).toEqual("vn2"); }); });
M docs/H3_DeveloperGuide.htmdocs/H3_DeveloperGuide.htm

@@ -7336,7 +7336,7 @@ <h3>I&rsquo;m sold! Where can I get it?<a href="#document-top" title="Go to top"></a></h3>

<p>Here, look, it&rsquo;s just one file:</p> -<p><a href="https://raw.githubusercontent.com/h3rald/h3/v0.7.0/h3.js" target="_blank" class="button primary">Download v0.7.0 (Gory Gorn)</a></p> +<p><a href="https://raw.githubusercontent.com/h3rald/h3/v0.8.0/h3.js" target="_blank" class="button primary">Download v0.8.0 (Humble Human)</a></p> <p>Yes there is also a <a href="https://www.npmjs.com/package/@h3rald/h3">NPM package</a> if you want to use it with WebPack and similar, but let me repeat: <em>it&rsquo;s just one file</em>.</p>

@@ -7517,7 +7517,7 @@ <p>A route component is a top-level component that handles a route. Unlike ordinary components, route components:</p>

<ul> <li>may have a dedicated <em>setup</em> (after the route component is added to the DOM) and <em>teardown</em> phase (after the route component is removed from the DOM and before the new route component is loaded).</li> -<li>may have built-in local state, initialized during setup and (typically) destroyed during teardown.</li> +<li>may have built-in local state, initialized during setup and (typically) destroyed during teardown. Such state is passed as the first (and only) parameter of the route component when executed.</li> </ul>

@@ -7533,7 +7533,7 @@ <h3>How everything works&hellip;<a href="#document-top" title="Go to top"></a></h3>

<p>The following sequence diagram summarizes how H3 works, from its initialization to the redraw and navigation phases.</p> -<p><img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjb250ZW50U2NyaXB0VHlwZT0iYXBwbGljYXRpb24vZWNtYXNjcmlwdCIgY29udGVudFN0eWxlVHlwZT0idGV4dC9jc3MiIGhlaWdodD0iMTAyNnB4IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIiBzdHlsZT0id2lkdGg6NzkwcHg7aGVpZ2h0OjEwMjZweDsiIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDc5MCAxMDI2IiB3aWR0aD0iNzkwcHgiIHpvb21BbmRQYW49Im1hZ25pZnkiPjxkZWZzPjxmaWx0ZXIgaGVpZ2h0PSIzMDAlIiBpZD0iZnVlMzQ4OXNoMWZhcCIgd2lkdGg9IjMwMCUiIHg9Ii0xIiB5PSItMSI+PGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgc3RkRGV2aWF0aW9uPSIyLjAiLz48ZmVDb2xvck1hdHJpeCBpbj0iYmx1ck91dCIgcmVzdWx0PSJibHVyT3V0MiIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIC40IDAiLz48ZmVPZmZzZXQgZHg9IjQuMCIgZHk9IjQuMCIgaW49ImJsdXJPdXQyIiByZXN1bHQ9ImJsdXJPdXQzIi8+PGZlQmxlbmQgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iYmx1ck91dDMiIG1vZGU9Im5vcm1hbCIvPjwvZmlsdGVyPjwvZGVmcz48Zz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxOCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIyMDUiIHg9IjI5MS43NSIgeT0iMjYuNzA4Ij5IMyBTZXF1ZW5jZSBEaWFncmFtPC90ZXh0PjxyZWN0IGZpbGw9IiNGRkZGRkYiIGZpbHRlcj0idXJsKCNmdWUzNDg5c2gxZmFwKSIgaGVpZ2h0PSIxMDQuNTMxMyIgc3R5bGU9InN0cm9rZTogIzAwMDAwMDsgc3Ryb2tlLXdpZHRoOiAyLjA7IiB3aWR0aD0iNTA5IiB4PSIxODMuNSIgeT0iNTA4Ljk3NjYiLz48cmVjdCBmaWxsPSIjRkZGRkZGIiBmaWx0ZXI9InVybCgjZnVlMzQ4OXNoMWZhcCkiIGhlaWdodD0iMzA4LjQ2MDkiIHN0eWxlPSJzdHJva2U6ICMwMDAwMDA7IHN0cm9rZS13aWR0aDogMi4wOyIgd2lkdGg9IjY3OS41IiB4PSIxMyIgeT0iNjI3LjUwNzgiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsgc3Ryb2tlLWRhc2hhcnJheTogNS4wLDUuMDsiIHgxPSI2NyIgeDI9IjY3IiB5MT0iMTAwLjI1IiB5Mj0iOTUyLjk2ODgiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsgc3Ryb2tlLWRhc2hhcnJheTogNS4wLDUuMDsiIHgxPSIyMzkuNSIgeDI9IjIzOS41IiB5MT0iMTAwLjI1IiB5Mj0iOTUyLjk2ODgiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsgc3Ryb2tlLWRhc2hhcnJheTogNS4wLDUuMDsiIHgxPSIzODMuNSIgeDI9IjM4My41IiB5MT0iMTAwLjI1IiB5Mj0iOTUyLjk2ODgiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsgc3Ryb2tlLWRhc2hhcnJheTogNS4wLDUuMDsiIHgxPSI0OTYiIHgyPSI0OTYiIHkxPSIxMDAuMjUiIHkyPSI5NTIuOTY4OCIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyBzdHJva2UtZGFzaGFycmF5OiA1LjAsNS4wOyIgeDE9IjY1OS41IiB4Mj0iNjU5LjUiIHkxPSIxMDAuMjUiIHkyPSI5NTIuOTY4OCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjgzIiB4PSIyMyIgeT0iOTYuOTQ4MiI+QXBwbGljYXRpb248L3RleHQ+PGVsbGlwc2UgY3g9IjY3LjUiIGN5PSI2Ny45NTMxIiBmaWxsPSIjRkVGRUNFIiBmaWx0ZXI9InVybCgjZnVlMzQ4OXNoMWZhcCkiIHJ4PSIxMiIgcnk9IjEyIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiIHgxPSI1NS41IiB4Mj0iNzkuNSIgeTE9IjgxLjk1MzEiIHkyPSI4MS45NTMxIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iODMiIHg9IjIzIiB5PSI5NjQuOTYzOSI+QXBwbGljYXRpb248L3RleHQ+PGVsbGlwc2UgY3g9IjY3LjUiIGN5PSI5ODQuMjY1NiIgZmlsbD0iI0ZFRkVDRSIgZmlsdGVyPSJ1cmwoI2Z1ZTM0ODlzaDFmYXApIiByeD0iMTIiIHJ5PSIxMiIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAyLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAyLjA7IiB4MT0iNTUuNSIgeDI9Ijc5LjUiIHkxPSI5OTguMjY1NiIgeTI9Ijk5OC4yNjU2Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iODYiIHg9IjE5My41IiB5PSI5Ni45NDgyIj5Db21wb25lbnQ8L3RleHQ+PGVsbGlwc2UgY3g9IjIzOS41IiBjeT0iNjcuOTUzMSIgZmlsbD0iI0ZFRkVDRSIgZmlsdGVyPSJ1cmwoI2Z1ZTM0ODlzaDFmYXApIiByeD0iMTIiIHJ5PSIxMiIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAyLjA7Ii8+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIyMzUuNSw1NS45NTMxLDI0MS41LDUwLjk1MzEsMjM5LjUsNTUuOTUzMSwyNDEuNSw2MC45NTMxLDIzNS41LDU1Ljk1MzEiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9Ijg2IiB4PSIxOTMuNSIgeT0iOTY0Ljk2MzkiPkNvbXBvbmVudDwvdGV4dD48ZWxsaXBzZSBjeD0iMjM5LjUiIGN5PSI5ODQuMjY1NiIgZmlsbD0iI0ZFRkVDRSIgZmlsdGVyPSJ1cmwoI2Z1ZTM0ODlzaDFmYXApIiByeD0iMTIiIHJ5PSIxMiIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAyLjA7Ii8+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIyMzUuNSw5NzIuMjY1NiwyNDEuNSw5NjcuMjY1NiwyMzkuNSw5NzIuMjY1NiwyNDEuNSw5NzcuMjY1NiwyMzUuNSw5NzIuMjY1NiIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTkiIHg9IjM3MSIgeT0iOTYuOTQ4MiI+SDM8L3RleHQ+PGVsbGlwc2UgY3g9IjM4My41IiBjeT0iNjcuOTUzMSIgZmlsbD0iI0ZFRkVDRSIgZmlsdGVyPSJ1cmwoI2Z1ZTM0ODlzaDFmYXApIiByeD0iMTIiIHJ5PSIxMiIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAyLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAyLjA7IiB4MT0iMzcxLjUiIHgyPSIzOTUuNSIgeTE9IjgxLjk1MzEiIHkyPSI4MS45NTMxIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTkiIHg9IjM3MSIgeT0iOTY0Ljk2MzkiPkgzPC90ZXh0PjxlbGxpcHNlIGN4PSIzODMuNSIgY3k9Ijk4NC4yNjU2IiBmaWxsPSIjRkVGRUNFIiBmaWx0ZXI9InVybCgjZnVlMzQ4OXNoMWZhcCkiIHJ4PSIxMiIgcnk9IjEyIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiIHgxPSIzNzEuNSIgeDI9IjM5NS41IiB5MT0iOTk4LjI2NTYiIHkyPSI5OTguMjY1NiIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjQ5IiB4PSI0NjkiIHk9Ijk2Ljk0ODIiPlJvdXRlcjwvdGV4dD48ZWxsaXBzZSBjeD0iNDk2LjUiIGN5PSI2Ny45NTMxIiBmaWxsPSIjRkVGRUNFIiBmaWx0ZXI9InVybCgjZnVlMzQ4OXNoMWZhcCkiIHJ4PSIxMiIgcnk9IjEyIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiIHgxPSI0ODQuNSIgeDI9IjUwOC41IiB5MT0iODEuOTUzMSIgeTI9IjgxLjk1MzEiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI0OSIgeD0iNDY5IiB5PSI5NjQuOTYzOSI+Um91dGVyPC90ZXh0PjxlbGxpcHNlIGN4PSI0OTYuNSIgY3k9Ijk4NC4yNjU2IiBmaWxsPSIjRkVGRUNFIiBmaWx0ZXI9InVybCgjZnVlMzQ4OXNoMWZhcCkiIHJ4PSIxMiIgcnk9IjEyIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiIHgxPSI0ODQuNSIgeDI9IjUwOC41IiB5MT0iOTk4LjI2NTYiIHkyPSI5OTguMjY1NiIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjQwIiB4PSI2MzYuNSIgeT0iOTYuOTQ4MiI+U3RvcmU8L3RleHQ+PHBhdGggZD0iTTY0MS41LDQ3Ljk1MzEgQzY0MS41LDM3Ljk1MzEgNjU5LjUsMzcuOTUzMSA2NTkuNSwzNy45NTMxIEM2NTkuNSwzNy45NTMxIDY3Ny41LDM3Ljk1MzEgNjc3LjUsNDcuOTUzMSBMNjc3LjUsNzMuOTUzMSBDNjc3LjUsODMuOTUzMSA2NTkuNSw4My45NTMxIDY1OS41LDgzLjk1MzEgQzY1OS41LDgzLjk1MzEgNjQxLjUsODMuOTUzMSA2NDEuNSw3My45NTMxIEw2NDEuNSw0Ny45NTMxICIgZmlsbD0iI0ZFRkVDRSIgZmlsdGVyPSJ1cmwoI2Z1ZTM0ODlzaDFmYXApIiBzdHlsZT0ic3Ryb2tlOiAjMDAwMDAwOyBzdHJva2Utd2lkdGg6IDEuNTsiLz48cGF0aCBkPSJNNjQxLjUsNDcuOTUzMSBDNjQxLjUsNTcuOTUzMSA2NTkuNSw1Ny45NTMxIDY1OS41LDU3Ljk1MzEgQzY1OS41LDU3Ljk1MzEgNjc3LjUsNTcuOTUzMSA2NzcuNSw0Ny45NTMxICIgZmlsbD0ibm9uZSIgc3R5bGU9InN0cm9rZTogIzAwMDAwMDsgc3Ryb2tlLXdpZHRoOiAxLjU7Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNDAiIHg9IjYzNi41IiB5PSI5NjQuOTYzOSI+U3RvcmU8L3RleHQ+PHBhdGggZD0iTTY0MS41LDk3OC4yNjU2IEM2NDEuNSw5NjguMjY1NiA2NTkuNSw5NjguMjY1NiA2NTkuNSw5NjguMjY1NiBDNjU5LjUsOTY4LjI2NTYgNjc3LjUsOTY4LjI2NTYgNjc3LjUsOTc4LjI2NTYgTDY3Ny41LDEwMDQuMjY1NiBDNjc3LjUsMTAxNC4yNjU2IDY1OS41LDEwMTQuMjY1NiA2NTkuNSwxMDE0LjI2NTYgQzY1OS41LDEwMTQuMjY1NiA2NDEuNSwxMDE0LjI2NTYgNjQxLjUsMTAwNC4yNjU2IEw2NDEuNSw5NzguMjY1NiAiIGZpbGw9IiNGRUZFQ0UiIGZpbHRlcj0idXJsKCNmdWUzNDg5c2gxZmFwKSIgc3R5bGU9InN0cm9rZTogIzAwMDAwMDsgc3Ryb2tlLXdpZHRoOiAxLjU7Ii8+PHBhdGggZD0iTTY0MS41LDk3OC4yNjU2IEM2NDEuNSw5ODguMjY1NiA2NTkuNSw5ODguMjY1NiA2NTkuNSw5ODguMjY1NiBDNjU5LjUsOTg4LjI2NTYgNjc3LjUsOTg4LjI2NTYgNjc3LjUsOTc4LjI2NTYgIiBmaWxsPSJub25lIiBzdHlsZT0ic3Ryb2tlOiAjMDAwMDAwOyBzdHJva2Utd2lkdGg6IDEuNTsiLz48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjM3MS41LDEyNy4zODI4LDM4MS41LDEzMS4zODI4LDM3MS41LDEzNS4zODI4LDM3NS41LDEzMS4zODI4IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsgc3Ryb2tlLWRhc2hhcnJheTogMi4wLDIuMDsiIHgxPSI2Ny41IiB4Mj0iMzc3LjUiIHkxPSIxMzEuMzgyOCIgeTI9IjEzMS4zODI4Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGZvbnQtd2VpZ2h0PSJib2xkIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjU5IiB4PSI3NC41IiB5PSIxMjYuMzE2OSI+aDMuaW5pdCgpPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iNjQ3LjUsMTU2LjUxNTYsNjU3LjUsMTYwLjUxNTYsNjQ3LjUsMTY0LjUxNTYsNjUxLjUsMTYwLjUxNTYiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjM4My41IiB4Mj0iNjUzLjUiIHkxPSIxNjAuNTE1NiIgeTI9IjE2MC41MTU2Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGZvbnQtc3R5bGU9Iml0YWxpYyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI1NiIgeD0iMzkwLjUiIHk9IjE1NS40NDk3Ij5pbml0aWFsaXplPC90ZXh0PjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjY1OS41IiB4Mj0iNzAxLjUiIHkxPSIxODkuNjQ4NCIgeTI9IjE4OS42NDg0Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iNzAxLjUiIHgyPSI3MDEuNSIgeTE9IjE4OS42NDg0IiB5Mj0iMjAyLjY0ODQiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSI2NjAuNSIgeDI9IjcwMS41IiB5MT0iMjAyLjY0ODQiIHkyPSIyMDIuNjQ4NCIvPjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iNjcwLjUsMTk4LjY0ODQsNjYwLjUsMjAyLjY0ODQsNjcwLjUsMjA2LjY0ODQsNjY2LjUsMjAyLjY0ODQiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBmb250LXN0eWxlPSJpdGFsaWMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTEyIiB4PSI2NjYuNSIgeT0iMTg0LjU4MjUiPmV4ZWN1dGUgbW9kdWxlczwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjY0Ny41LDIyNy43ODEzLDY1Ny41LDIzMS43ODEzLDY0Ny41LDIzNS43ODEzLDY1MS41LDIzMS43ODEzIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSIzODMuNSIgeDI9IjY1My41IiB5MT0iMjMxLjc4MTMiIHkyPSIyMzEuNzgxMyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjkxIiB4PSIzOTAuNSIgeT0iMjI2LjcxNTMiPmRpc3BhdGNoKCRpbml0KTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9Ijc4LjUsMjU2LjkxNDEsNjguNSwyNjAuOTE0MSw3OC41LDI2NC45MTQxLDc0LjUsMjYwLjkxNDEiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjcyLjUiIHgyPSIzODIuNSIgeTE9IjI2MC45MTQxIiB5Mj0iMjYwLjkxNDEiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI2MiIgeD0iODQuNSIgeT0iMjU1Ljg0ODEiPnByZVN0YXJ0KCk8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSI0ODQuNSwyODYuMDQ2OSw0OTQuNSwyOTAuMDQ2OSw0ODQuNSwyOTQuMDQ2OSw0ODguNSwyOTAuMDQ2OSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iMzgzLjUiIHgyPSI0OTAuNSIgeTE9IjI5MC4wNDY5IiB5Mj0iMjkwLjA0NjkiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgZm9udC1zdHlsZT0iaXRhbGljIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjU2IiB4PSIzOTAuNSIgeT0iMjg0Ljk4MSI+aW5pdGlhbGl6ZTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjQ4NC41LDMxNS4xNzk3LDQ5NC41LDMxOS4xNzk3LDQ4NC41LDMyMy4xNzk3LDQ4OC41LDMxOS4xNzk3IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSIzODMuNSIgeDI9IjQ5MC41IiB5MT0iMzE5LjE3OTciIHkyPSIzMTkuMTc5NyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjQwIiB4PSIzOTAuNSIgeT0iMzE0LjExMzgiPnN0YXJ0KCk8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIyNTAuNSwzNDQuMzEyNSwyNDAuNSwzNDguMzEyNSwyNTAuNSwzNTIuMzEyNSwyNDYuNSwzNDguMzEyNSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IHN0cm9rZS1kYXNoYXJyYXk6IDIuMCwyLjA7IiB4MT0iMjQ0LjUiIHgyPSI0OTUuNSIgeTE9IjM0OC4zMTI1IiB5Mj0iMzQ4LjMxMjUiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI0NiIgeD0iMjU2LjUiIHk9IjM0My4yNDY2Ij5zZXR1cCgpPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iNjQ3LjUsMzczLjQ0NTMsNjU3LjUsMzc3LjQ0NTMsNjQ3LjUsMzgxLjQ0NTMsNjUxLjUsMzc3LjQ0NTMiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjQ5Ni41IiB4Mj0iNjUzLjUiIHkxPSIzNzcuNDQ1MyIgeTI9IjM3Ny40NDUzIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTM5IiB4PSI1MDMuNSIgeT0iMzcyLjM3OTQiPmRpc3BhdGNoKCRuYXZpZ2F0aW9uKTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjI1MC41LDQwMi41NzgxLDI0MC41LDQwNi41NzgxLDI1MC41LDQxMC41NzgxLDI0Ni41LDQwNi41NzgxIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSIyNDQuNSIgeDI9IjQ5NS41IiB5MT0iNDA2LjU3ODEiIHkyPSI0MDYuNTc4MSIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjUyIiB4PSIyNTYuNSIgeT0iNDAxLjUxMjIiPnJlbmRlcigpPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iMjUwLjUsNDMxLjcxMDksMjQwLjUsNDM1LjcxMDksMjUwLjUsNDM5LjcxMDksMjQ2LjUsNDM1LjcxMDkiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjI0NC41IiB4Mj0iNDk1LjUiIHkxPSI0MzUuNzEwOSIgeTI9IjQzNS43MTA5Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNzYiIHg9IjI1Ni41IiB5PSI0MzAuNjQ1Ij4kb25yZW5kZXIoKTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjY0Ny41LDQ2MC44NDM4LDY1Ny41LDQ2NC44NDM4LDY0Ny41LDQ2OC44NDM4LDY1MS41LDQ2NC44NDM4IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSI0OTYuNSIgeDI9IjY1My41IiB5MT0iNDY0Ljg0MzgiIHkyPSI0NjQuODQzOCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjExNyIgeD0iNTAzLjUiIHk9IjQ1OS43Nzc4Ij5kaXNwYXRjaCgkcmVkcmF3KTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9Ijc4LjUsNDg5Ljk3NjYsNjguNSw0OTMuOTc2Niw3OC41LDQ5Ny45NzY2LDc0LjUsNDkzLjk3NjYiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjcyLjUiIHgyPSIzODIuNSIgeTE9IjQ5My45NzY2IiB5Mj0iNDkzLjk3NjYiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI2OSIgeD0iODQuNSIgeT0iNDg4LjkxMDYiPnBvc3RTdGFydCgpPC90ZXh0PjxwYXRoIGQ9Ik0xODMuNSw1MDguOTc2NiBMMjgxLjUsNTA4Ljk3NjYgTDI4MS41LDUxNS45NzY2IEwyNzEuNSw1MjUuOTc2NiBMMTgzLjUsNTI1Ljk3NjYgTDE4My41LDUwOC45NzY2ICIgZmlsbD0iI0VFRUVFRSIgc3R5bGU9InN0cm9rZTogIzAwMDAwMDsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PHJlY3QgZmlsbD0ibm9uZSIgaGVpZ2h0PSIxMDQuNTMxMyIgc3R5bGU9InN0cm9rZTogIzAwMDAwMDsgc3Ryb2tlLXdpZHRoOiAyLjA7IiB3aWR0aD0iNTA5IiB4PSIxODMuNSIgeT0iNTA4Ljk3NjYiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgZm9udC13ZWlnaHQ9ImJvbGQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNTMiIHg9IjE5OC41IiB5PSI1MjIuMDQzNSI+cmVkcmF3PC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iMzcxLjUsNTQzLjI0MjIsMzgxLjUsNTQ3LjI0MjIsMzcxLjUsNTUxLjI0MjIsMzc1LjUsNTQ3LjI0MjIiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjIzOS41IiB4Mj0iMzc3LjUiIHkxPSI1NDcuMjQyMiIgeTI9IjU0Ny4yNDIyIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGZvbnQtd2VpZ2h0PSJib2xkIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9Ijg3IiB4PSIyNDYuNSIgeT0iNTQyLjE3NjMiPmgzLnJlZHJhdygpPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iMjUwLjUsNTcyLjM3NSwyNDAuNSw1NzYuMzc1LDI1MC41LDU4MC4zNzUsMjQ2LjUsNTc2LjM3NSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iMjQ0LjUiIHgyPSIzODIuNSIgeTE9IjU3Ni4zNzUiIHkyPSI1NzYuMzc1Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNTUiIHg9IjI1Ni41IiB5PSI1NzEuMzA5MSI+cmVkcmF3KCk8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSI2NDcuNSw2MDEuNTA3OCw2NTcuNSw2MDUuNTA3OCw2NDcuNSw2MDkuNTA3OCw2NTEuNSw2MDUuNTA3OCIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iMzgzLjUiIHgyPSI2NTMuNSIgeTE9IjYwNS41MDc4IiB5Mj0iNjA1LjUwNzgiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxMTciIHg9IjM5MC41IiB5PSI2MDAuNDQxOSI+ZGlzcGF0Y2goJHJlZHJhdyk8L3RleHQ+PHBhdGggZD0iTTEzLDYyNy41MDc4IEwxMzcsNjI3LjUwNzggTDEzNyw2MzQuNTA3OCBMMTI3LDY0NC41MDc4IEwxMyw2NDQuNTA3OCBMMTMsNjI3LjUwNzggIiBmaWxsPSIjRUVFRUVFIiBzdHlsZT0ic3Ryb2tlOiAjMDAwMDAwOyBzdHJva2Utd2lkdGg6IDEuMDsiLz48cmVjdCBmaWxsPSJub25lIiBoZWlnaHQ9IjMwOC40NjA5IiBzdHlsZT0ic3Ryb2tlOiAjMDAwMDAwOyBzdHJva2Utd2lkdGg6IDIuMDsiIHdpZHRoPSI2NzkuNSIgeD0iMTMiIHk9IjYyNy41MDc4Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGZvbnQtd2VpZ2h0PSJib2xkIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9Ijc5IiB4PSIyOCIgeT0iNjQwLjU3NDciPm5hdmlnYXRpb248L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIzNzEuNSw2NjEuNzczNCwzODEuNSw2NjUuNzczNCwzNzEuNSw2NjkuNzczNCwzNzUuNSw2NjUuNzczNCIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iMjM5LjUiIHgyPSIzNzcuNSIgeTE9IjY2NS43NzM0IiB5Mj0iNjY1Ljc3MzQiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgZm9udC13ZWlnaHQ9ImJvbGQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTIwIiB4PSIyNDYuNSIgeT0iNjYwLjcwNzUiPmgzLm5hdmlnYXRlVG8oKTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjQ4NC41LDY5MC45MDYzLDQ5NC41LDY5NC45MDYzLDQ4NC41LDY5OC45MDYzLDQ4OC41LDY5NC45MDYzIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSIzODMuNSIgeDI9IjQ5MC41IiB5MT0iNjk0LjkwNjMiIHkyPSI2OTQuOTA2MyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9Ijg5IiB4PSIzOTAuNSIgeT0iNjg5Ljg0MDMiPnByb2Nlc3NQYXRoKCk8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIyNTAuNSw3MjAuMDM5MSwyNDAuNSw3MjQuMDM5MSwyNTAuNSw3MjguMDM5MSwyNDYuNSw3MjQuMDM5MSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IHN0cm9rZS1kYXNoYXJyYXk6IDIuMCwyLjA7IiB4MT0iMjQ0LjUiIHgyPSI0OTUuNSIgeTE9IjcyNC4wMzkxIiB5Mj0iNzI0LjAzOTEiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI3MSIgeD0iMjU2LjUiIHk9IjcxOC45NzMxIj50ZWFyZG93bigpPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iMjUwLjUsNzQ5LjE3MTksMjQwLjUsNzUzLjE3MTksMjUwLjUsNzU3LjE3MTksMjQ2LjUsNzUzLjE3MTkiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyBzdHJva2UtZGFzaGFycmF5OiAyLjAsMi4wOyIgeDE9IjI0NC41IiB4Mj0iNDk1LjUiIHkxPSI3NTMuMTcxOSIgeTI9Ijc1My4xNzE5Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNDYiIHg9IjI1Ni41IiB5PSI3NDguMTA2Ij5zZXR1cCgpPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iNjQ3LjUsNzc4LjMwNDcsNjU3LjUsNzgyLjMwNDcsNjQ3LjUsNzg2LjMwNDcsNjUxLjUsNzgyLjMwNDciIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjQ5Ni41IiB4Mj0iNjUzLjUiIHkxPSI3ODIuMzA0NyIgeTI9Ijc4Mi4zMDQ3Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTM5IiB4PSI1MDMuNSIgeT0iNzc3LjIzODgiPmRpc3BhdGNoKCRuYXZpZ2F0aW9uKTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9Ijc4LjUsODA3LjQzNzUsNjguNSw4MTEuNDM3NSw3OC41LDgxNS40Mzc1LDc0LjUsODExLjQzNzUiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjcyLjUiIHgyPSI0OTUuNSIgeTE9IjgxMS40Mzc1IiB5Mj0iODExLjQzNzUiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgZm9udC1zdHlsZT0iaXRhbGljIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjE0OCIgeD0iODQuNSIgeT0iODA2LjM3MTYiPnJlbW92ZSBhbGwgRE9NIG5vZGVzPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iMjI3LjUsODM2LjU3MDMsMjM3LjUsODQwLjU3MDMsMjI3LjUsODQ0LjU3MDMsMjMxLjUsODQwLjU3MDMiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjY3LjUiIHgyPSIyMzMuNSIgeTE9Ijg0MC41NzAzIiB5Mj0iODQwLjU3MDMiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgZm9udC1zdHlsZT0iaXRhbGljIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjE0OCIgeD0iNzQuNSIgeT0iODM1LjUwNDQiPnJlbW92ZSBhbGwgRE9NIG5vZGVzPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iMjUwLjUsODY1LjcwMzEsMjQwLjUsODY5LjcwMzEsMjUwLjUsODczLjcwMzEsMjQ2LjUsODY5LjcwMzEiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjI0NC41IiB4Mj0iNDk1LjUiIHkxPSI4NjkuNzAzMSIgeTI9Ijg2OS43MDMxIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNTIiIHg9IjI1Ni41IiB5PSI4NjQuNjM3MiI+cmVuZGVyKCk8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIyNTAuNSw4OTQuODM1OSwyNDAuNSw4OTguODM1OSwyNTAuNSw5MDIuODM1OSwyNDYuNSw4OTguODM1OSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iMjQ0LjUiIHgyPSI0OTUuNSIgeTE9Ijg5OC44MzU5IiB5Mj0iODk4LjgzNTkiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI3NiIgeD0iMjU2LjUiIHk9Ijg5My43NyI+JG9ucmVuZGVyKCk8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSI2NDcuNSw5MjMuOTY4OCw2NTcuNSw5MjcuOTY4OCw2NDcuNSw5MzEuOTY4OCw2NTEuNSw5MjcuOTY4OCIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iNDk2LjUiIHgyPSI2NTMuNSIgeTE9IjkyNy45Njg4IiB5Mj0iOTI3Ljk2ODgiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxMTciIHg9IjUwMy41IiB5PSI5MjIuOTAyOCI+ZGlzcGF0Y2goJHJlZHJhdyk8L3RleHQ+PCEtLU1ENT1bNTU5ZjU3MGMwZGZkNTc0MWUxYzNmZjYxYTBhMDgyN2FdCkBzdGFydHVtbA0KdGl0bGUgSDMgU2VxdWVuY2UgRGlhZ3JhbQ0KDQplbnRpdHkgQXBwbGljYXRpb24NCmNvbnRyb2wgQ29tcG9uZW50DQplbnRpdHkgSDMNCmVudGl0eSBSb3V0ZXINCmRhdGFiYXNlIFN0b3JlDQoNCkFwcGxpY2F0aW9uIC0gLT4gSDMgOiA8Yj5oMy5pbml0KCk8L2I+DQpIMyAtPiBTdG9yZSA6IC8vaW5pdGlhbGl6ZS8vDQpTdG9yZSAtPiBTdG9yZSA6IC8vZXhlY3V0ZSBtb2R1bGVzLy8NCkgzIC0+IFN0b3JlIDogZGlzcGF0Y2goJGluaXQpDQpIMyAtPiBBcHBsaWNhdGlvbiA6IHByZVN0YXJ0KCkNCkgzIC0+IFJvdXRlciA6IC8vaW5pdGlhbGl6ZS8vDQpIMyAtPiBSb3V0ZXIgOiBzdGFydCgpDQpSb3V0ZXIgLSAtPiBDb21wb25lbnQgOiBzZXR1cCgpIA0KUm91dGVyIC0+IFN0b3JlOiBkaXNwYXRjaCgkbmF2aWdhdGlvbikNClJvdXRlciAtPiBDb21wb25lbnQgOiByZW5kZXIoKQ0KUm91dGVyIC0+IENvbXBvbmVudCA6ICRvbnJlbmRlcigpDQpSb3V0ZXIgLT4gU3RvcmU6IGRpc3BhdGNoKCRyZWRyYXcpDQoNCkgzIC0+IEFwcGxpY2F0aW9uIDogcG9zdFN0YXJ0KCkNCg0KZ3JvdXAgcmVkcmF3DQogICAgQ29tcG9uZW50IC0+IEgzIDogPGI+aDMucmVkcmF3KCk8L2I+DQogICAgSDMgLT4gQ29tcG9uZW50IDogcmVkcmF3KCkNCiAgICBIMyAtPiBTdG9yZTogZGlzcGF0Y2goJHJlZHJhdykNCmVuZA0KDQpncm91cCBuYXZpZ2F0aW9uDQogICAgQ29tcG9uZW50IC0+IEgzIDogPGI+aDMubmF2aWdhdGVUbygpPC9iPg0KICAgIEgzIC0+IFJvdXRlciA6IHByb2Nlc3NQYXRoKCkNCiAgICBSb3V0ZXIgLSAtPiBDb21wb25lbnQgOiB0ZWFyZG93bigpIA0KICAgIFJvdXRlciAtIC0+IENvbXBvbmVudCA6IHNldHVwKCkgDQogICAgUm91dGVyIC0+IFN0b3JlOiBkaXNwYXRjaCgkbmF2aWdhdGlvbikNCiAgICBSb3V0ZXIgLT4gQXBwbGljYXRpb24gOiAvL3JlbW92ZSBhbGwgRE9NIG5vZGVzLy8NCiAgICBBcHBsaWNhdGlvbiAtPiBDb21wb25lbnQgOiAvL3JlbW92ZSBhbGwgRE9NIG5vZGVzLy8NCiAgICBSb3V0ZXIgLT4gQ29tcG9uZW50IDogcmVuZGVyKCkNCiAgICBSb3V0ZXIgLT4gQ29tcG9uZW50IDogJG9ucmVuZGVyKCkNCiAgICBSb3V0ZXIgLT4gU3RvcmU6IGRpc3BhdGNoKCRyZWRyYXcpDQogICAgDQplbmQNCkBlbmR1bWwNCgpQbGFudFVNTCB2ZXJzaW9uIDEuMjAyMC4xMChTdW4gTWF5IDE3IDA5OjQ4OjQ5IFVUQyAyMDIwKQooR1BMIHNvdXJjZSBkaXN0cmlidXRpb24pCkphdmEgUnVudGltZTogSmF2YShUTSkgU0UgUnVudGltZSBFbnZpcm9ubWVudApKVk06IEphdmEgSG90U3BvdChUTSkgNjQtQml0IFNlcnZlciBWTQpPcGVyYXRpbmcgU3lzdGVtOiBMaW51eApEZWZhdWx0IEVuY29kaW5nOiBVVEYtOApMYW5ndWFnZTogZW4KQ291bnRyeTogVVMKLS0+PC9nPjwvc3ZnPg==" alt="Sequence Diagram" /></p> +<p><img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjb250ZW50U2NyaXB0VHlwZT0iYXBwbGljYXRpb24vZWNtYXNjcmlwdCIgY29udGVudFN0eWxlVHlwZT0idGV4dC9jc3MiIGhlaWdodD0iOTY4cHgiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiIHN0eWxlPSJ3aWR0aDo3OTBweDtoZWlnaHQ6OTY4cHg7IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA3OTAgOTY4IiB3aWR0aD0iNzkwcHgiIHpvb21BbmRQYW49Im1hZ25pZnkiPjxkZWZzPjxmaWx0ZXIgaGVpZ2h0PSIzMDAlIiBpZD0iZm8zbHluM2Vlajk2eiIgd2lkdGg9IjMwMCUiIHg9Ii0xIiB5PSItMSI+PGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgc3RkRGV2aWF0aW9uPSIyLjAiLz48ZmVDb2xvck1hdHJpeCBpbj0iYmx1ck91dCIgcmVzdWx0PSJibHVyT3V0MiIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIC40IDAiLz48ZmVPZmZzZXQgZHg9IjQuMCIgZHk9IjQuMCIgaW49ImJsdXJPdXQyIiByZXN1bHQ9ImJsdXJPdXQzIi8+PGZlQmxlbmQgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iYmx1ck91dDMiIG1vZGU9Im5vcm1hbCIvPjwvZmlsdGVyPjwvZGVmcz48Zz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxOCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIyMDUiIHg9IjI5MS43NSIgeT0iMjYuNzA4Ij5IMyBTZXF1ZW5jZSBEaWFncmFtPC90ZXh0PjxyZWN0IGZpbGw9IiNGRkZGRkYiIGZpbHRlcj0idXJsKCNmbzNseW4zZWVqOTZ6KSIgaGVpZ2h0PSIxMDQuNTMxMyIgc3R5bGU9InN0cm9rZTogIzAwMDAwMDsgc3Ryb2tlLXdpZHRoOiAyLjA7IiB3aWR0aD0iNTA5IiB4PSIxODMuNSIgeT0iNDc5Ljg0MzgiLz48cmVjdCBmaWxsPSIjRkZGRkZGIiBmaWx0ZXI9InVybCgjZm8zbHluM2Vlajk2eikiIGhlaWdodD0iMjc5LjMyODEiIHN0eWxlPSJzdHJva2U6ICMwMDAwMDA7IHN0cm9rZS13aWR0aDogMi4wOyIgd2lkdGg9IjY3OS41IiB4PSIxMyIgeT0iNTk4LjM3NSIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyBzdHJva2UtZGFzaGFycmF5OiA1LjAsNS4wOyIgeDE9IjY3IiB4Mj0iNjciIHkxPSIxMDAuMjUiIHkyPSI4OTQuNzAzMSIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyBzdHJva2UtZGFzaGFycmF5OiA1LjAsNS4wOyIgeDE9IjIzOS41IiB4Mj0iMjM5LjUiIHkxPSIxMDAuMjUiIHkyPSI4OTQuNzAzMSIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyBzdHJva2UtZGFzaGFycmF5OiA1LjAsNS4wOyIgeDE9IjM4My41IiB4Mj0iMzgzLjUiIHkxPSIxMDAuMjUiIHkyPSI4OTQuNzAzMSIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyBzdHJva2UtZGFzaGFycmF5OiA1LjAsNS4wOyIgeDE9IjQ5NiIgeDI9IjQ5NiIgeTE9IjEwMC4yNSIgeTI9Ijg5NC43MDMxIi8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IHN0cm9rZS1kYXNoYXJyYXk6IDUuMCw1LjA7IiB4MT0iNjU5LjUiIHgyPSI2NTkuNSIgeTE9IjEwMC4yNSIgeTI9Ijg5NC43MDMxIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iODMiIHg9IjIzIiB5PSI5Ni45NDgyIj5BcHBsaWNhdGlvbjwvdGV4dD48ZWxsaXBzZSBjeD0iNjcuNSIgY3k9IjY3Ljk1MzEiIGZpbGw9IiNGRUZFQ0UiIGZpbHRlcj0idXJsKCNmbzNseW4zZWVqOTZ6KSIgcng9IjEyIiByeT0iMTIiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMi4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMi4wOyIgeDE9IjU1LjUiIHgyPSI3OS41IiB5MT0iODEuOTUzMSIgeTI9IjgxLjk1MzEiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI4MyIgeD0iMjMiIHk9IjkwNi42OTgyIj5BcHBsaWNhdGlvbjwvdGV4dD48ZWxsaXBzZSBjeD0iNjcuNSIgY3k9IjkyNiIgZmlsbD0iI0ZFRkVDRSIgZmlsdGVyPSJ1cmwoI2ZvM2x5bjNlZWo5NnopIiByeD0iMTIiIHJ5PSIxMiIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAyLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAyLjA7IiB4MT0iNTUuNSIgeDI9Ijc5LjUiIHkxPSI5NDAiIHkyPSI5NDAiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI4NiIgeD0iMTkzLjUiIHk9Ijk2Ljk0ODIiPkNvbXBvbmVudDwvdGV4dD48ZWxsaXBzZSBjeD0iMjM5LjUiIGN5PSI2Ny45NTMxIiBmaWxsPSIjRkVGRUNFIiBmaWx0ZXI9InVybCgjZm8zbHluM2Vlajk2eikiIHJ4PSIxMiIgcnk9IjEyIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiLz48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjIzNS41LDU1Ljk1MzEsMjQxLjUsNTAuOTUzMSwyMzkuNSw1NS45NTMxLDI0MS41LDYwLjk1MzEsMjM1LjUsNTUuOTUzMSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iODYiIHg9IjE5My41IiB5PSI5MDYuNjk4MiI+Q29tcG9uZW50PC90ZXh0PjxlbGxpcHNlIGN4PSIyMzkuNSIgY3k9IjkyNiIgZmlsbD0iI0ZFRkVDRSIgZmlsdGVyPSJ1cmwoI2ZvM2x5bjNlZWo5NnopIiByeD0iMTIiIHJ5PSIxMiIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAyLjA7Ii8+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIyMzUuNSw5MTQsMjQxLjUsOTA5LDIzOS41LDkxNCwyNDEuNSw5MTksMjM1LjUsOTE0IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxOSIgeD0iMzcxIiB5PSI5Ni45NDgyIj5IMzwvdGV4dD48ZWxsaXBzZSBjeD0iMzgzLjUiIGN5PSI2Ny45NTMxIiBmaWxsPSIjRkVGRUNFIiBmaWx0ZXI9InVybCgjZm8zbHluM2Vlajk2eikiIHJ4PSIxMiIgcnk9IjEyIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiIHgxPSIzNzEuNSIgeDI9IjM5NS41IiB5MT0iODEuOTUzMSIgeTI9IjgxLjk1MzEiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxOSIgeD0iMzcxIiB5PSI5MDYuNjk4MiI+SDM8L3RleHQ+PGVsbGlwc2UgY3g9IjM4My41IiBjeT0iOTI2IiBmaWxsPSIjRkVGRUNFIiBmaWx0ZXI9InVybCgjZm8zbHluM2Vlajk2eikiIHJ4PSIxMiIgcnk9IjEyIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiIHgxPSIzNzEuNSIgeDI9IjM5NS41IiB5MT0iOTQwIiB5Mj0iOTQwIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNDkiIHg9IjQ2OSIgeT0iOTYuOTQ4MiI+Um91dGVyPC90ZXh0PjxlbGxpcHNlIGN4PSI0OTYuNSIgY3k9IjY3Ljk1MzEiIGZpbGw9IiNGRUZFQ0UiIGZpbHRlcj0idXJsKCNmbzNseW4zZWVqOTZ6KSIgcng9IjEyIiByeT0iMTIiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMi4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMi4wOyIgeDE9IjQ4NC41IiB4Mj0iNTA4LjUiIHkxPSI4MS45NTMxIiB5Mj0iODEuOTUzMSIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjQ5IiB4PSI0NjkiIHk9IjkwNi42OTgyIj5Sb3V0ZXI8L3RleHQ+PGVsbGlwc2UgY3g9IjQ5Ni41IiBjeT0iOTI2IiBmaWxsPSIjRkVGRUNFIiBmaWx0ZXI9InVybCgjZm8zbHluM2Vlajk2eikiIHJ4PSIxMiIgcnk9IjEyIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDIuMDsiIHgxPSI0ODQuNSIgeDI9IjUwOC41IiB5MT0iOTQwIiB5Mj0iOTQwIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNDAiIHg9IjYzNi41IiB5PSI5Ni45NDgyIj5TdG9yZTwvdGV4dD48cGF0aCBkPSJNNjQxLjUsNDcuOTUzMSBDNjQxLjUsMzcuOTUzMSA2NTkuNSwzNy45NTMxIDY1OS41LDM3Ljk1MzEgQzY1OS41LDM3Ljk1MzEgNjc3LjUsMzcuOTUzMSA2NzcuNSw0Ny45NTMxIEw2NzcuNSw3My45NTMxIEM2NzcuNSw4My45NTMxIDY1OS41LDgzLjk1MzEgNjU5LjUsODMuOTUzMSBDNjU5LjUsODMuOTUzMSA2NDEuNSw4My45NTMxIDY0MS41LDczLjk1MzEgTDY0MS41LDQ3Ljk1MzEgIiBmaWxsPSIjRkVGRUNFIiBmaWx0ZXI9InVybCgjZm8zbHluM2Vlajk2eikiIHN0eWxlPSJzdHJva2U6ICMwMDAwMDA7IHN0cm9rZS13aWR0aDogMS41OyIvPjxwYXRoIGQ9Ik02NDEuNSw0Ny45NTMxIEM2NDEuNSw1Ny45NTMxIDY1OS41LDU3Ljk1MzEgNjU5LjUsNTcuOTUzMSBDNjU5LjUsNTcuOTUzMSA2NzcuNSw1Ny45NTMxIDY3Ny41LDQ3Ljk1MzEgIiBmaWxsPSJub25lIiBzdHlsZT0ic3Ryb2tlOiAjMDAwMDAwOyBzdHJva2Utd2lkdGg6IDEuNTsiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI0MCIgeD0iNjM2LjUiIHk9IjkwNi42OTgyIj5TdG9yZTwvdGV4dD48cGF0aCBkPSJNNjQxLjUsOTIwIEM2NDEuNSw5MTAgNjU5LjUsOTEwIDY1OS41LDkxMCBDNjU5LjUsOTEwIDY3Ny41LDkxMCA2NzcuNSw5MjAgTDY3Ny41LDk0NiBDNjc3LjUsOTU2IDY1OS41LDk1NiA2NTkuNSw5NTYgQzY1OS41LDk1NiA2NDEuNSw5NTYgNjQxLjUsOTQ2IEw2NDEuNSw5MjAgIiBmaWxsPSIjRkVGRUNFIiBmaWx0ZXI9InVybCgjZm8zbHluM2Vlajk2eikiIHN0eWxlPSJzdHJva2U6ICMwMDAwMDA7IHN0cm9rZS13aWR0aDogMS41OyIvPjxwYXRoIGQ9Ik02NDEuNSw5MjAgQzY0MS41LDkzMCA2NTkuNSw5MzAgNjU5LjUsOTMwIEM2NTkuNSw5MzAgNjc3LjUsOTMwIDY3Ny41LDkyMCAiIGZpbGw9Im5vbmUiIHN0eWxlPSJzdHJva2U6ICMwMDAwMDA7IHN0cm9rZS13aWR0aDogMS41OyIvPjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iMzcxLjUsMTI3LjM4MjgsMzgxLjUsMTMxLjM4MjgsMzcxLjUsMTM1LjM4MjgsMzc1LjUsMTMxLjM4MjgiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyBzdHJva2UtZGFzaGFycmF5OiAyLjAsMi4wOyIgeDE9IjY3LjUiIHgyPSIzNzcuNSIgeTE9IjEzMS4zODI4IiB5Mj0iMTMxLjM4MjgiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgZm9udC13ZWlnaHQ9ImJvbGQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNTkiIHg9Ijc0LjUiIHk9IjEyNi4zMTY5Ij5oMy5pbml0KCk8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSI2NDcuNSwxNTYuNTE1Niw2NTcuNSwxNjAuNTE1Niw2NDcuNSwxNjQuNTE1Niw2NTEuNSwxNjAuNTE1NiIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iMzgzLjUiIHgyPSI2NTMuNSIgeTE9IjE2MC41MTU2IiB5Mj0iMTYwLjUxNTYiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgZm9udC1zdHlsZT0iaXRhbGljIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjU2IiB4PSIzOTAuNSIgeT0iMTU1LjQ0OTciPmluaXRpYWxpemU8L3RleHQ+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iNjU5LjUiIHgyPSI3MDEuNSIgeTE9IjE4OS42NDg0IiB5Mj0iMTg5LjY0ODQiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSI3MDEuNSIgeDI9IjcwMS41IiB5MT0iMTg5LjY0ODQiIHkyPSIyMDIuNjQ4NCIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjY2MC41IiB4Mj0iNzAxLjUiIHkxPSIyMDIuNjQ4NCIgeTI9IjIwMi42NDg0Ii8+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSI2NzAuNSwxOTguNjQ4NCw2NjAuNSwyMDIuNjQ4NCw2NzAuNSwyMDYuNjQ4NCw2NjYuNSwyMDIuNjQ4NCIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGZvbnQtc3R5bGU9Iml0YWxpYyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxMTIiIHg9IjY2Ni41IiB5PSIxODQuNTgyNSI+ZXhlY3V0ZSBtb2R1bGVzPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iNjQ3LjUsMjI3Ljc4MTMsNjU3LjUsMjMxLjc4MTMsNjQ3LjUsMjM1Ljc4MTMsNjUxLjUsMjMxLjc4MTMiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjM4My41IiB4Mj0iNjUzLjUiIHkxPSIyMzEuNzgxMyIgeTI9IjIzMS43ODEzIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iOTEiIHg9IjM5MC41IiB5PSIyMjYuNzE1MyI+ZGlzcGF0Y2goJGluaXQpPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iNzguNSwyNTYuOTE0MSw2OC41LDI2MC45MTQxLDc4LjUsMjY0LjkxNDEsNzQuNSwyNjAuOTE0MSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iNzIuNSIgeDI9IjM4Mi41IiB5MT0iMjYwLjkxNDEiIHkyPSIyNjAuOTE0MSIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjYyIiB4PSI4NC41IiB5PSIyNTUuODQ4MSI+cHJlU3RhcnQoKTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjQ4NC41LDI4Ni4wNDY5LDQ5NC41LDI5MC4wNDY5LDQ4NC41LDI5NC4wNDY5LDQ4OC41LDI5MC4wNDY5IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSIzODMuNSIgeDI9IjQ5MC41IiB5MT0iMjkwLjA0NjkiIHkyPSIyOTAuMDQ2OSIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBmb250LXN0eWxlPSJpdGFsaWMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNTYiIHg9IjM5MC41IiB5PSIyODQuOTgxIj5pbml0aWFsaXplPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iNDg0LjUsMzE1LjE3OTcsNDk0LjUsMzE5LjE3OTcsNDg0LjUsMzIzLjE3OTcsNDg4LjUsMzE5LjE3OTciIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjM4My41IiB4Mj0iNDkwLjUiIHkxPSIzMTkuMTc5NyIgeTI9IjMxOS4xNzk3Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNDAiIHg9IjM5MC41IiB5PSIzMTQuMTEzOCI+c3RhcnQoKTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjI1MC41LDM0NC4zMTI1LDI0MC41LDM0OC4zMTI1LDI1MC41LDM1Mi4zMTI1LDI0Ni41LDM0OC4zMTI1IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsgc3Ryb2tlLWRhc2hhcnJheTogMi4wLDIuMDsiIHgxPSIyNDQuNSIgeDI9IjQ5NS41IiB5MT0iMzQ4LjMxMjUiIHkyPSIzNDguMzEyNSIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjQ2IiB4PSIyNTYuNSIgeT0iMzQzLjI0NjYiPnNldHVwKCk8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSI2NDcuNSwzNzMuNDQ1Myw2NTcuNSwzNzcuNDQ1Myw2NDcuNSwzODEuNDQ1Myw2NTEuNSwzNzcuNDQ1MyIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iNDk2LjUiIHgyPSI2NTMuNSIgeTE9IjM3Ny40NDUzIiB5Mj0iMzc3LjQ0NTMiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxMzkiIHg9IjUwMy41IiB5PSIzNzIuMzc5NCI+ZGlzcGF0Y2goJG5hdmlnYXRpb24pPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iMjUwLjUsNDAyLjU3ODEsMjQwLjUsNDA2LjU3ODEsMjUwLjUsNDEwLjU3ODEsMjQ2LjUsNDA2LjU3ODEiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjI0NC41IiB4Mj0iNDk1LjUiIHkxPSI0MDYuNTc4MSIgeTI9IjQwNi41NzgxIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNTIiIHg9IjI1Ni41IiB5PSI0MDEuNTEyMiI+cmVuZGVyKCk8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSI2NDcuNSw0MzEuNzEwOSw2NTcuNSw0MzUuNzEwOSw2NDcuNSw0MzkuNzEwOSw2NTEuNSw0MzUuNzEwOSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iNDk2LjUiIHgyPSI2NTMuNSIgeTE9IjQzNS43MTA5IiB5Mj0iNDM1LjcxMDkiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxMTciIHg9IjUwMy41IiB5PSI0MzAuNjQ1Ij5kaXNwYXRjaCgkcmVkcmF3KTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9Ijc4LjUsNDYwLjg0MzgsNjguNSw0NjQuODQzOCw3OC41LDQ2OC44NDM4LDc0LjUsNDY0Ljg0MzgiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjcyLjUiIHgyPSIzODIuNSIgeTE9IjQ2NC44NDM4IiB5Mj0iNDY0Ljg0MzgiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI2OSIgeD0iODQuNSIgeT0iNDU5Ljc3NzgiPnBvc3RTdGFydCgpPC90ZXh0PjxwYXRoIGQ9Ik0xODMuNSw0NzkuODQzOCBMMjgxLjUsNDc5Ljg0MzggTDI4MS41LDQ4Ni44NDM4IEwyNzEuNSw0OTYuODQzOCBMMTgzLjUsNDk2Ljg0MzggTDE4My41LDQ3OS44NDM4ICIgZmlsbD0iI0VFRUVFRSIgc3R5bGU9InN0cm9rZTogIzAwMDAwMDsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PHJlY3QgZmlsbD0ibm9uZSIgaGVpZ2h0PSIxMDQuNTMxMyIgc3R5bGU9InN0cm9rZTogIzAwMDAwMDsgc3Ryb2tlLXdpZHRoOiAyLjA7IiB3aWR0aD0iNTA5IiB4PSIxODMuNSIgeT0iNDc5Ljg0MzgiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgZm9udC13ZWlnaHQ9ImJvbGQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNTMiIHg9IjE5OC41IiB5PSI0OTIuOTEwNiI+cmVkcmF3PC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iMzcxLjUsNTE0LjEwOTQsMzgxLjUsNTE4LjEwOTQsMzcxLjUsNTIyLjEwOTQsMzc1LjUsNTE4LjEwOTQiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjIzOS41IiB4Mj0iMzc3LjUiIHkxPSI1MTguMTA5NCIgeTI9IjUxOC4xMDk0Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGZvbnQtd2VpZ2h0PSJib2xkIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9Ijg3IiB4PSIyNDYuNSIgeT0iNTEzLjA0MzUiPmgzLnJlZHJhdygpPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iMjUwLjUsNTQzLjI0MjIsMjQwLjUsNTQ3LjI0MjIsMjUwLjUsNTUxLjI0MjIsMjQ2LjUsNTQ3LjI0MjIiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjI0NC41IiB4Mj0iMzgyLjUiIHkxPSI1NDcuMjQyMiIgeTI9IjU0Ny4yNDIyIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNTUiIHg9IjI1Ni41IiB5PSI1NDIuMTc2MyI+cmVkcmF3KCk8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSI2NDcuNSw1NzIuMzc1LDY1Ny41LDU3Ni4zNzUsNjQ3LjUsNTgwLjM3NSw2NTEuNSw1NzYuMzc1IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSIzODMuNSIgeDI9IjY1My41IiB5MT0iNTc2LjM3NSIgeTI9IjU3Ni4zNzUiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxMTciIHg9IjM5MC41IiB5PSI1NzEuMzA5MSI+ZGlzcGF0Y2goJHJlZHJhdyk8L3RleHQ+PHBhdGggZD0iTTEzLDU5OC4zNzUgTDEzNyw1OTguMzc1IEwxMzcsNjA1LjM3NSBMMTI3LDYxNS4zNzUgTDEzLDYxNS4zNzUgTDEzLDU5OC4zNzUgIiBmaWxsPSIjRUVFRUVFIiBzdHlsZT0ic3Ryb2tlOiAjMDAwMDAwOyBzdHJva2Utd2lkdGg6IDEuMDsiLz48cmVjdCBmaWxsPSJub25lIiBoZWlnaHQ9IjI3OS4zMjgxIiBzdHlsZT0ic3Ryb2tlOiAjMDAwMDAwOyBzdHJva2Utd2lkdGg6IDIuMDsiIHdpZHRoPSI2NzkuNSIgeD0iMTMiIHk9IjU5OC4zNzUiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgZm9udC13ZWlnaHQ9ImJvbGQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNzkiIHg9IjI4IiB5PSI2MTEuNDQxOSI+bmF2aWdhdGlvbjwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjM3MS41LDYzMi42NDA2LDM4MS41LDYzNi42NDA2LDM3MS41LDY0MC42NDA2LDM3NS41LDYzNi42NDA2IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSIyMzkuNSIgeDI9IjM3Ny41IiB5MT0iNjM2LjY0MDYiIHkyPSI2MzYuNjQwNiIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBmb250LXdlaWdodD0iYm9sZCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxMjAiIHg9IjI0Ni41IiB5PSI2MzEuNTc0NyI+aDMubmF2aWdhdGVUbygpPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iNDg0LjUsNjYxLjc3MzQsNDk0LjUsNjY1Ljc3MzQsNDg0LjUsNjY5Ljc3MzQsNDg4LjUsNjY1Ljc3MzQiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjM4My41IiB4Mj0iNDkwLjUiIHkxPSI2NjUuNzczNCIgeTI9IjY2NS43NzM0Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iODkiIHg9IjM5MC41IiB5PSI2NjAuNzA3NSI+cHJvY2Vzc1BhdGgoKTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjI1MC41LDY5MC45MDYzLDI0MC41LDY5NC45MDYzLDI1MC41LDY5OC45MDYzLDI0Ni41LDY5NC45MDYzIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsgc3Ryb2tlLWRhc2hhcnJheTogMi4wLDIuMDsiIHgxPSIyNDQuNSIgeDI9IjQ5NS41IiB5MT0iNjk0LjkwNjMiIHkyPSI2OTQuOTA2MyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjcxIiB4PSIyNTYuNSIgeT0iNjg5Ljg0MDMiPnRlYXJkb3duKCk8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIyNTAuNSw3MjAuMDM5MSwyNDAuNSw3MjQuMDM5MSwyNTAuNSw3MjguMDM5MSwyNDYuNSw3MjQuMDM5MSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IHN0cm9rZS1kYXNoYXJyYXk6IDIuMCwyLjA7IiB4MT0iMjQ0LjUiIHgyPSI0OTUuNSIgeTE9IjcyNC4wMzkxIiB5Mj0iNzI0LjAzOTEiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI0NiIgeD0iMjU2LjUiIHk9IjcxOC45NzMxIj5zZXR1cCgpPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iNjQ3LjUsNzQ5LjE3MTksNjU3LjUsNzUzLjE3MTksNjQ3LjUsNzU3LjE3MTksNjUxLjUsNzUzLjE3MTkiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjQ5Ni41IiB4Mj0iNjUzLjUiIHkxPSI3NTMuMTcxOSIgeTI9Ijc1My4xNzE5Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTM5IiB4PSI1MDMuNSIgeT0iNzQ4LjEwNiI+ZGlzcGF0Y2goJG5hdmlnYXRpb24pPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iNzguNSw3NzguMzA0Nyw2OC41LDc4Mi4zMDQ3LDc4LjUsNzg2LjMwNDcsNzQuNSw3ODIuMzA0NyIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iNzIuNSIgeDI9IjQ5NS41IiB5MT0iNzgyLjMwNDciIHkyPSI3ODIuMzA0NyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBmb250LXN0eWxlPSJpdGFsaWMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTQ4IiB4PSI4NC41IiB5PSI3NzcuMjM4OCI+cmVtb3ZlIGFsbCBET00gbm9kZXM8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIyMjcuNSw4MDcuNDM3NSwyMzcuNSw4MTEuNDM3NSwyMjcuNSw4MTUuNDM3NSwyMzEuNSw4MTEuNDM3NSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iNjcuNSIgeDI9IjIzMy41IiB5MT0iODExLjQzNzUiIHkyPSI4MTEuNDM3NSIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBmb250LXN0eWxlPSJpdGFsaWMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTQ4IiB4PSI3NC41IiB5PSI4MDYuMzcxNiI+cmVtb3ZlIGFsbCBET00gbm9kZXM8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIyNTAuNSw4MzYuNTcwMywyNDAuNSw4NDAuNTcwMywyNTAuNSw4NDQuNTcwMywyNDYuNSw4NDAuNTcwMyIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iMjQ0LjUiIHgyPSI0OTUuNSIgeTE9Ijg0MC41NzAzIiB5Mj0iODQwLjU3MDMiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI1MiIgeD0iMjU2LjUiIHk9IjgzNS41MDQ0Ij5yZW5kZXIoKTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjY0Ny41LDg2NS43MDMxLDY1Ny41LDg2OS43MDMxLDY0Ny41LDg3My43MDMxLDY1MS41LDg2OS43MDMxIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSI0OTYuNSIgeDI9IjY1My41IiB5MT0iODY5LjcwMzEiIHkyPSI4NjkuNzAzMSIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjExNyIgeD0iNTAzLjUiIHk9Ijg2NC42MzcyIj5kaXNwYXRjaCgkcmVkcmF3KTwvdGV4dD48IS0tTUQ1PVs2MTRjOTJiMmQ1MDZhMTk5ZGJkNDU1ZDZiNDdjOWUzOV0KQHN0YXJ0dW1sDQp0aXRsZSBIMyBTZXF1ZW5jZSBEaWFncmFtDQoNCmVudGl0eSBBcHBsaWNhdGlvbg0KY29udHJvbCBDb21wb25lbnQNCmVudGl0eSBIMw0KZW50aXR5IFJvdXRlcg0KZGF0YWJhc2UgU3RvcmUNCg0KQXBwbGljYXRpb24gLSAtPiBIMyA6IDxiPmgzLmluaXQoKTwvYj4NCkgzIC0+IFN0b3JlIDogLy9pbml0aWFsaXplLy8NClN0b3JlIC0+IFN0b3JlIDogLy9leGVjdXRlIG1vZHVsZXMvLw0KSDMgLT4gU3RvcmUgOiBkaXNwYXRjaCgkaW5pdCkNCkgzIC0+IEFwcGxpY2F0aW9uIDogcHJlU3RhcnQoKQ0KSDMgLT4gUm91dGVyIDogLy9pbml0aWFsaXplLy8NCkgzIC0+IFJvdXRlciA6IHN0YXJ0KCkNClJvdXRlciAtIC0+IENvbXBvbmVudCA6IHNldHVwKCkgDQpSb3V0ZXIgLT4gU3RvcmU6IGRpc3BhdGNoKCRuYXZpZ2F0aW9uKQ0KUm91dGVyIC0+IENvbXBvbmVudCA6IHJlbmRlcigpDQpSb3V0ZXIgLT4gU3RvcmU6IGRpc3BhdGNoKCRyZWRyYXcpDQoNCkgzIC0+IEFwcGxpY2F0aW9uIDogcG9zdFN0YXJ0KCkNCg0KZ3JvdXAgcmVkcmF3DQogICAgQ29tcG9uZW50IC0+IEgzIDogPGI+aDMucmVkcmF3KCk8L2I+DQogICAgSDMgLT4gQ29tcG9uZW50IDogcmVkcmF3KCkNCiAgICBIMyAtPiBTdG9yZTogZGlzcGF0Y2goJHJlZHJhdykNCmVuZA0KDQpncm91cCBuYXZpZ2F0aW9uDQogICAgQ29tcG9uZW50IC0+IEgzIDogPGI+aDMubmF2aWdhdGVUbygpPC9iPg0KICAgIEgzIC0+IFJvdXRlciA6IHByb2Nlc3NQYXRoKCkNCiAgICBSb3V0ZXIgLSAtPiBDb21wb25lbnQgOiB0ZWFyZG93bigpIA0KICAgIFJvdXRlciAtIC0+IENvbXBvbmVudCA6IHNldHVwKCkgDQogICAgUm91dGVyIC0+IFN0b3JlOiBkaXNwYXRjaCgkbmF2aWdhdGlvbikNCiAgICBSb3V0ZXIgLT4gQXBwbGljYXRpb24gOiAvL3JlbW92ZSBhbGwgRE9NIG5vZGVzLy8NCiAgICBBcHBsaWNhdGlvbiAtPiBDb21wb25lbnQgOiAvL3JlbW92ZSBhbGwgRE9NIG5vZGVzLy8NCiAgICBSb3V0ZXIgLT4gQ29tcG9uZW50IDogcmVuZGVyKCkNCiAgICBSb3V0ZXIgLT4gU3RvcmU6IGRpc3BhdGNoKCRyZWRyYXcpDQogICAgDQplbmQNCkBlbmR1bWwNCgpQbGFudFVNTCB2ZXJzaW9uIDEuMjAyMC4xMShTYXQgTWF5IDMwIDEwOjEzOjQzIFVUQyAyMDIwKQooR1BMIHNvdXJjZSBkaXN0cmlidXRpb24pCkphdmEgUnVudGltZTogSmF2YShUTSkgU0UgUnVudGltZSBFbnZpcm9ubWVudApKVk06IEphdmEgSG90U3BvdChUTSkgNjQtQml0IFNlcnZlciBWTQpEZWZhdWx0IEVuY29kaW5nOiBVVEYtOApMYW5ndWFnZTogZW4KQ291bnRyeTogVVMKLS0+PC9nPjwvc3ZnPg==" alt="Sequence Diagram" /></p> <p>When the <code>h3.init()</code> method is called at application level, the following operations are performed in sequence:</p>

@@ -7546,7 +7546,6 @@ <li>The <em>Router</em> is initialized and started.</li>

<li>The <strong>setup()</strong> method of the matching Route Component is called (if any).</li> <li>The <strong>$navigation</strong> event is dispatched.</li> <li>The <em>Route Component</em> matching the current route and all its child components are rendered for the first time.</li> -<li>Any callback specified via the <strong>$onrender</strong> special attributes in the loaded components is executed once all components are rendered.</li> <li>The <strong>$redraw</strong> event is dispatched.</li> </ol>

@@ -7566,11 +7565,9 @@ <li>The <em>Router</em> processes the new path and determine which component to render based on the routing configuration.</li>

<li>The <strong>teardow()</strong> method of the current Route Component is called (if any).</li> <li>The <strong>setup()</strong> method of the new matching Route Component is called (if any).</li> <li>All DOM nodes within the scope of the routing are removed, all components are removed.</li> -<li>Any <strong>$onrender</strong> callback defined in the added components is executed once all components are rendered.</li> <li>The <strong>$navigation</strong> event is dispatched.</li> <li>All DOM nodes are removed.</li> <li>The <em>Route Component</em> matching the new route and all its child components are rendered.</li> -<li>Any callback specified via the <strong>$onrender</strong> special attributes in the loaded components is executed once all components are rendered.</li> <li>The <strong>$redraw</strong> event is dispatched.</li> </ol>

@@ -7699,8 +7696,8 @@ h3("a.logo.col-sm-1", { href: "#/" }, [

h3("img", { alt: "H3", src: "images/h3.svg" }), ]), h3("div.version.col-sm.col-md", [ - h3("div.version-number", "v0.7.0"), - h3("div.version-label", "“Gory Gorn“"), + h3("div.version-number", "v0.8.0"), + h3("div.version-label", "“Humble Human“"), ]), h3("label.drawer-toggle.button.col-sm-last", { for: "drawer-control" }), ]);

@@ -7771,6 +7768,10 @@ <a name="Next-steps"></a>

<h3>Next steps<a href="#document-top" title="Go to top"></a></h3> <p>Made it this far? Good. Wanna know more? Have a look at the code of the <a href="https://github.com/h3rald/h3/tree/master/docs/example">todo list example</a> and try it out <a href="https://h3.js.org/example/index.html">here</a>.</p> + +<p>Once you feel more comfortable and you are ready to dive into a more complex application, featuring different routes, route components, forms, confirmation messages, plenty of third-party components etc., have a look at <a href="https://github.com/h3rald/litepad">LitePad</a>. You can see it in action here: <a href="https://litepad.h3rald.com/">litepad.h3rald.com</a>.</p> + +<p>Note: the LitePad online demo will store all its data in localStorage.</p> <a name="API"></a> <h2>API<a href="#document-top" title="Go to top"></a></h2>

@@ -7883,9 +7884,8 @@ <ul>

<li>Any attribute starting with <em>on</em> (e.g. onclick, onkeydown, etc.) will be treated as an event listener.</li> <li>The <code>classList</code> attribute can be set to a list of classes to apply to the element (as an alternative to using the element selector shorthand).</li> <li>The <code>data</code> attribute can be set to a simple object containing <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes">data attributes</a>.</li> -<li>The special <code>$key</code> attribute can be used to guarantee the uniqueness of two VNodes and it will not be translated into an HTML attribute.</li> <li>The special <code>$html</code> attribute can be used to set the <code>innerHTML</code> property of the resulting HTML element. Use only if you know what you are doing!</li> -<li>The special <code>$onrender</code> attribute can be set to a function that will executed after the VNode is rendered for the first time.</li> +<li>The special <code>$onrender</code> attribute can be set to a function that will executed every time the VNode is rendered and added to the DOM.</li> </ul>

@@ -8050,7 +8050,7 @@ </ul>

</div> <div id="footer"> - <p><span class="copy"></span> Fabio Cevasco &ndash; May 30, 2020</p> + <p><span class="copy"></span> Fabio Cevasco &ndash; June 9, 2020</p> <p><span>Powered by</span> <a href="https://h3rald.com/hastyscribe"><span class="hastyscribe"></span></a></p> </div> </div>
M docs/example/assets/js/components/Todo.jsdocs/example/assets/js/components/Todo.js

@@ -2,18 +2,18 @@ import h3 from "../h3.js";

export default function Todo(data) { const todoStateClass = data.done ? ".done" : ".todo"; - const toggleTodo = (todo) => { - h3.dispatch("todos/toggle", data); - h3.redraw() + const toggleTodo = (key) => { + h3.dispatch("todos/toggle", key); + h3.redraw(); }; - const removeTodo = (todo) => { - h3.dispatch("todos/remove", data); - h3.redraw() + const removeTodo = (key) => { + h3.dispatch("todos/remove", key); + h3.redraw(); }; - return h3(`div.todo-item`, {$key: data.key}, [ + return h3(`div.todo-item`, { data: { key: data.key } }, [ h3(`div.todo-content${todoStateClass}`, [ - h3("span.todo-text", { onclick: () => toggleTodo(data) }, data.text), + h3("span.todo-text", { onclick: (e) => toggleTodo(e.currentTarget.parentNode.parentNode.dataset.key) }, data.text), ]), - h3("span.delete-todo", { onclick: () => removeTodo(data) }, "✘"), + h3("span.delete-todo", { onclick: (e) => removeTodo(e.currentTarget.parentNode.dataset.key) }, "✘"), ]); }
M docs/example/assets/js/h3.jsdocs/example/assets/js/h3.js

@@ -1,5 +1,5 @@

/** - * H3 v0.7.0 "Gory Gorn" + * H3 v0.8.0 "Humble Human" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * This source code is licensed under the MIT license found in the

@@ -59,8 +59,9 @@ }

return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1); }; +const selectorRegex = /^([a-z][a-z0-9:_=-]*)(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i; + let $onrenderCallbacks = []; -const selectorRegex = /^([a-z][a-z0-9:_=-]*)(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i; // Virtual Node Implementation with HyperScript-like syntax class VNode {

@@ -69,7 +70,6 @@ this.type = undefined;

this.attributes = {}; this.data = {}; this.id = undefined; - this.$key = undefined; this.$html = undefined; this.$onrender = undefined; this.style = undefined;

@@ -159,7 +159,6 @@ from(data) {

this.value = data.value; this.type = data.type; this.id = data.id; - this.$key = data.$key; this.$html = data.$html; this.$onrender = data.$onrender; this.style = data.style;

@@ -177,7 +176,6 @@ }

processProperties(attrs) { this.id = this.id || attrs.id; - this.$key = attrs.$key; this.$html = attrs.$html; this.$onrender = attrs.$onrender; this.style = attrs.style;

@@ -189,7 +187,7 @@ ? attrs.classList

: this.classList; this.attributes = attrs; Object.keys(attrs) - .filter((a) => a.startsWith("on")) + .filter((a) => a.startsWith("on") && attrs[a]) .forEach((key) => { if (typeof attrs[key] !== "function") { throw new Error(

@@ -200,7 +198,6 @@ this.eventListeners[key.slice(2)] = attrs[key];

delete this.attributes[key]; }); delete this.attributes.value; - delete this.attributes.$key; delete this.attributes.$html; delete this.attributes.$onrender; delete this.attributes.id;

@@ -302,11 +299,11 @@ // Children

this.children.forEach((c) => { const cnode = c.render(); node.appendChild(cnode); + c.$onrender && $onrenderCallbacks.push(() => c.$onrender(cnode)); }); if (this.$html) { node.innerHTML = this.$html; } - this.$onrender && $onrenderCallbacks.push(() => this.$onrender(node)); return node; }

@@ -324,11 +321,10 @@ oldvnode !== newvnode)

) { const renderedNode = newvnode.render(); node.parentNode.replaceChild(renderedNode, node); + newvnode.$onrender && newvnode.$onrender(renderedNode); oldvnode.from(newvnode); return; } - // $key - oldvnode.$key = newvnode.$key; // ID if (oldvnode.id !== newvnode.id) { node.id = newvnode.id || "";

@@ -416,104 +412,113 @@ });

oldvnode.eventListeners = newvnode.eventListeners; } // Children - function mapChildren(parent1, parent2) { - const map = []; - for (let j = 0; j < parent1.children.length; j++) { - let found = false; - for (let k = 0; k < parent2.children.length; k++) { + function mapChildren(oldvnode, newvnode) { + let map = []; + let oldNodesFound = 0; + let newNodesFound = 0; + // First look for existing nodes + for (let oldIndex = 0; oldIndex < oldvnode.children.length; oldIndex++) { + let found = -1; + for (let index = 0; index < newvnode.children.length; index++) { if ( - parent1.children[j].equal(parent2.children[k]) && - !map.includes(k) + equal(oldvnode.children[oldIndex], newvnode.children[index]) && + !map.includes(index) ) { - map.push(k); - found = true; + found = index; + newNodesFound++; + oldNodesFound++; break; } } - if (!found) { - map.push(-1); + map.push(found); + } + if ( + newNodesFound === oldNodesFound && + newvnode.children.length === oldvnode.children.length + ) { + // something changed but everything else is the same + return map; + } + if (newNodesFound === newvnode.children.length) { + // All children in newvnode exist in oldvnode + // All nodes that are not found must be removed + for (let i = 0; i < map.length; i++) { + if (map[i] === -1) { + map[i] = -3; + } } } - return map; - } - let newmap, oldmap, notFoundInNew, notFoundInOld; - const remap = () => { - // Map positions of newvnode children in relation to oldvnode children - newmap = mapChildren(newvnode, oldvnode); - // Map positions of oldvnode children in relation to newvnode children - oldmap = mapChildren(oldvnode, newvnode); - notFoundInOld = newmap.indexOf(-1); - notFoundInNew = oldmap.indexOf(-1); - }; - remap(); - if (newmap.length === oldmap.length) { - if (equal(newmap, oldmap) && notFoundInNew >= 0) { - // Something changed (some nodes are different at the same position) - for (let i = 0; i < newmap.length; i++) { - if (newmap[i] === -1 || oldmap[i] === -1) { - oldvnode.children[i].redraw({ - node: node.childNodes[i], - vnode: newvnode.children[i], - }); + if (oldNodesFound === oldvnode.children.length) { + // All children in oldvnode exist in newvnode + // Check where the missing newvnodes children need to be added + for ( + let newIndex = 0; + newIndex < newvnode.children.length; + newIndex++ + ) { + if (!map.includes(newIndex)) { + map.splice(newIndex, 0, -2); } } - } else { - // Nodes in different position (maps have same nodes) - let index = 0; - while (!equal(oldmap, [...Array(oldmap.length).keys()])) { - if (newmap[index] !== index) { - const child = node.childNodes[newmap[index]]; - node.removeChild(child); - node.insertBefore(child, node.childNodes[index]); - const cnode = oldvnode.children[newmap[index]]; - oldvnode.children = oldvnode.children.filter( - (c) => !equal(c, cnode) - ); - oldvnode.children.splice(index, 0, cnode); - remap(); - index = 0; - } else { - index++; + } + // Check if nodes needs to be removed (if there are fewer children) + if (newvnode.children.length < oldvnode.children.length) { + for (let i = 0; i < map.length; i++) { + if (map[i] === -1 && !newvnode.children[i]) { + map[i] = -3; } } } - } else { - while (notFoundInOld >= 0 || notFoundInNew >= 0) { - // First remove children not found in new map, then add the missing ones. - if (notFoundInNew >= 0) { - const childOfNew = - newvnode.children.length > notFoundInNew && - newvnode.children[notFoundInNew]; - const childOfOld = oldvnode.children[notFoundInNew]; - if ( - childOfNew && - childOfOld && - childOfOld.type === childOfNew.type && - childOfNew.children.length === 0 && - childOfNew.children.length === 0 - ) { - // Optimization to avoid removing simple nodes of the same type - oldvnode.children[notFoundInNew].redraw({ - node: node.childNodes[notFoundInNew], - vnode: newvnode.children[notFoundInNew], + return map; + } + let childMap = mapChildren(oldvnode, newvnode); + let resultMap = [...Array(childMap.filter((i) => i !== -3).length).keys()]; + while (!equal(childMap, resultMap)) { + let count = -1; + for (let i of childMap) { + count++; + let breakFor = false; + if (i === count) { + // Matching nodes; + continue; + } + switch (i) { + case -1: + // different node, check + oldvnode.children[count].redraw({ + node: node.childNodes[count], + vnode: newvnode.children[count], }); - } else { - // While there are children not found in newvnode, remove them and re-check - node.removeChild(node.childNodes[notFoundInNew]); - oldvnode.children.splice(notFoundInNew, 1); - } - } else { - // While there are children not found in oldvnode, add them and re-check - const cnode = newvnode.children[notFoundInOld].render(); - node.insertBefore(cnode, node.childNodes[notFoundInOld]); - oldvnode.children.splice( - notFoundInOld, - 0, - newvnode.children[notFoundInOld] - ); + break; + case -2: + // add node + oldvnode.children.splice(count, 0, newvnode.children[count]); + const renderedNode = newvnode.children[count].render(); + node.insertBefore(renderedNode, node.childNodes[count]); + newvnode.children[count].$onrender && + newvnode.children[count].$onrender(renderedNode); + breakFor = true; + break; + case -3: + // remove node + oldvnode.children.splice(count, 1); + node.removeChild(node.childNodes[count]); + breakFor = true; + break; + default: + // Node found, move nodes and remap + const vtarget = oldvnode.children.splice(i, 1)[0]; + oldvnode.children.splice(count, 0, vtarget); + node.insertBefore(node.childNodes[i], node.childNodes[count]); + breakFor = true; + break; } - remap(); + if (breakFor) { + break; + } } + childMap = mapChildren(oldvnode, newvnode); + resultMap = [...Array(childMap.length).keys()]; } // $onrender if (!equal(oldvnode.$onrender, newvnode.$onrender)) {

@@ -601,7 +606,6 @@ }

async start() { const processPath = async (data) => { - $onrenderCallbacks = []; const oldRoute = this.route; const fragment = (data &&

@@ -659,6 +663,7 @@ }

const vnode = newRouteComponent(newRouteComponent.state); const node = vnode.render(); this.element.appendChild(node); + vnode.$onrender && vnode.$onrender(node); $onrenderCallbacks.forEach((cbk) => cbk()); $onrenderCallbacks = []; this.setRedraw(vnode, newRouteComponent.state);
M docs/example/assets/js/modules.jsdocs/example/assets/js/modules.js

@@ -3,7 +3,9 @@

const app = () => { h3.on("app/load", () => { const storedData = localStorage.getItem("h3_todo_list"); - const { todos, settings } = storedData ? JSON.parse(storedData) : {todos: [], settings: {}}; + const { todos, settings } = storedData + ? JSON.parse(storedData) + : { todos: [], settings: {} }; return { todos, settings }; }); h3.on("app/save", (state, data) => {

@@ -37,13 +39,13 @@ text: data.text,

}); return { todos }; }); - h3.on("todos/remove", (state, data) => { - const todos = state.todos.filter(({ key }) => key !== data.key); + h3.on("todos/remove", (state, k) => { + const todos = state.todos.filter(({ key }) => key !== k); return { todos }; }); - h3.on("todos/toggle", (state, data) => { + h3.on("todos/toggle", (state, k) => { const todos = state.todos; - const todo = state.todos.find((t) => t.key === data.key); + const todo = state.todos.find(({ key }) => key === k); todo.done = !todo.done; return { todos }; });
M docs/images/h3.sequence.svgdocs/images/h3.sequence.svg

@@ -1,4 +1,4 @@

-<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="1026px" preserveAspectRatio="none" style="width:790px;height:1026px;" version="1.1" viewBox="0 0 790 1026" width="790px" zoomAndPan="magnify"><defs><filter height="300%" id="fue3489sh1fap" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><text fill="#000000" font-family="sans-serif" font-size="18" lengthAdjust="spacingAndGlyphs" textLength="205" x="291.75" y="26.708">H3 Sequence Diagram</text><rect fill="#FFFFFF" filter="url(#fue3489sh1fap)" height="104.5313" style="stroke: #000000; stroke-width: 2.0;" width="509" x="183.5" y="508.9766"/><rect fill="#FFFFFF" filter="url(#fue3489sh1fap)" height="308.4609" style="stroke: #000000; stroke-width: 2.0;" width="679.5" x="13" y="627.5078"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="67" x2="67" y1="100.25" y2="952.9688"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="239.5" x2="239.5" y1="100.25" y2="952.9688"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="383.5" x2="383.5" y1="100.25" y2="952.9688"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="496" x2="496" y1="100.25" y2="952.9688"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="659.5" x2="659.5" y1="100.25" y2="952.9688"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="83" x="23" y="96.9482">Application</text><ellipse cx="67.5" cy="67.9531" fill="#FEFECE" filter="url(#fue3489sh1fap)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><line style="stroke: #A80036; stroke-width: 2.0;" x1="55.5" x2="79.5" y1="81.9531" y2="81.9531"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="83" x="23" y="964.9639">Application</text><ellipse cx="67.5" cy="984.2656" fill="#FEFECE" filter="url(#fue3489sh1fap)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><line style="stroke: #A80036; stroke-width: 2.0;" x1="55.5" x2="79.5" y1="998.2656" y2="998.2656"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="86" x="193.5" y="96.9482">Component</text><ellipse cx="239.5" cy="67.9531" fill="#FEFECE" filter="url(#fue3489sh1fap)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><polygon fill="#A80036" points="235.5,55.9531,241.5,50.9531,239.5,55.9531,241.5,60.9531,235.5,55.9531" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="86" x="193.5" y="964.9639">Component</text><ellipse cx="239.5" cy="984.2656" fill="#FEFECE" filter="url(#fue3489sh1fap)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><polygon fill="#A80036" points="235.5,972.2656,241.5,967.2656,239.5,972.2656,241.5,977.2656,235.5,972.2656" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="19" x="371" y="96.9482">H3</text><ellipse cx="383.5" cy="67.9531" fill="#FEFECE" filter="url(#fue3489sh1fap)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><line style="stroke: #A80036; stroke-width: 2.0;" x1="371.5" x2="395.5" y1="81.9531" y2="81.9531"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="19" x="371" y="964.9639">H3</text><ellipse cx="383.5" cy="984.2656" fill="#FEFECE" filter="url(#fue3489sh1fap)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><line style="stroke: #A80036; stroke-width: 2.0;" x1="371.5" x2="395.5" y1="998.2656" y2="998.2656"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="49" x="469" y="96.9482">Router</text><ellipse cx="496.5" cy="67.9531" fill="#FEFECE" filter="url(#fue3489sh1fap)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><line style="stroke: #A80036; stroke-width: 2.0;" x1="484.5" x2="508.5" y1="81.9531" y2="81.9531"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="49" x="469" y="964.9639">Router</text><ellipse cx="496.5" cy="984.2656" fill="#FEFECE" filter="url(#fue3489sh1fap)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><line style="stroke: #A80036; stroke-width: 2.0;" x1="484.5" x2="508.5" y1="998.2656" y2="998.2656"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="40" x="636.5" y="96.9482">Store</text><path d="M641.5,47.9531 C641.5,37.9531 659.5,37.9531 659.5,37.9531 C659.5,37.9531 677.5,37.9531 677.5,47.9531 L677.5,73.9531 C677.5,83.9531 659.5,83.9531 659.5,83.9531 C659.5,83.9531 641.5,83.9531 641.5,73.9531 L641.5,47.9531 " fill="#FEFECE" filter="url(#fue3489sh1fap)" style="stroke: #000000; stroke-width: 1.5;"/><path d="M641.5,47.9531 C641.5,57.9531 659.5,57.9531 659.5,57.9531 C659.5,57.9531 677.5,57.9531 677.5,47.9531 " fill="none" style="stroke: #000000; stroke-width: 1.5;"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="40" x="636.5" y="964.9639">Store</text><path d="M641.5,978.2656 C641.5,968.2656 659.5,968.2656 659.5,968.2656 C659.5,968.2656 677.5,968.2656 677.5,978.2656 L677.5,1004.2656 C677.5,1014.2656 659.5,1014.2656 659.5,1014.2656 C659.5,1014.2656 641.5,1014.2656 641.5,1004.2656 L641.5,978.2656 " fill="#FEFECE" filter="url(#fue3489sh1fap)" style="stroke: #000000; stroke-width: 1.5;"/><path d="M641.5,978.2656 C641.5,988.2656 659.5,988.2656 659.5,988.2656 C659.5,988.2656 677.5,988.2656 677.5,978.2656 " fill="none" style="stroke: #000000; stroke-width: 1.5;"/><polygon fill="#A80036" points="371.5,127.3828,381.5,131.3828,371.5,135.3828,375.5,131.3828" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 2.0,2.0;" x1="67.5" x2="377.5" y1="131.3828" y2="131.3828"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="59" x="74.5" y="126.3169">h3.init()</text><polygon fill="#A80036" points="647.5,156.5156,657.5,160.5156,647.5,164.5156,651.5,160.5156" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="383.5" x2="653.5" y1="160.5156" y2="160.5156"/><text fill="#000000" font-family="sans-serif" font-size="13" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="56" x="390.5" y="155.4497">initialize</text><line style="stroke: #A80036; stroke-width: 1.0;" x1="659.5" x2="701.5" y1="189.6484" y2="189.6484"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="701.5" x2="701.5" y1="189.6484" y2="202.6484"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="660.5" x2="701.5" y1="202.6484" y2="202.6484"/><polygon fill="#A80036" points="670.5,198.6484,660.5,202.6484,670.5,206.6484,666.5,202.6484" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="112" x="666.5" y="184.5825">execute modules</text><polygon fill="#A80036" points="647.5,227.7813,657.5,231.7813,647.5,235.7813,651.5,231.7813" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="383.5" x2="653.5" y1="231.7813" y2="231.7813"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="91" x="390.5" y="226.7153">dispatch($init)</text><polygon fill="#A80036" points="78.5,256.9141,68.5,260.9141,78.5,264.9141,74.5,260.9141" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="72.5" x2="382.5" y1="260.9141" y2="260.9141"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="62" x="84.5" y="255.8481">preStart()</text><polygon fill="#A80036" points="484.5,286.0469,494.5,290.0469,484.5,294.0469,488.5,290.0469" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="383.5" x2="490.5" y1="290.0469" y2="290.0469"/><text fill="#000000" font-family="sans-serif" font-size="13" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="56" x="390.5" y="284.981">initialize</text><polygon fill="#A80036" points="484.5,315.1797,494.5,319.1797,484.5,323.1797,488.5,319.1797" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="383.5" x2="490.5" y1="319.1797" y2="319.1797"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="40" x="390.5" y="314.1138">start()</text><polygon fill="#A80036" points="250.5,344.3125,240.5,348.3125,250.5,352.3125,246.5,348.3125" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 2.0,2.0;" x1="244.5" x2="495.5" y1="348.3125" y2="348.3125"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="46" x="256.5" y="343.2466">setup()</text><polygon fill="#A80036" points="647.5,373.4453,657.5,377.4453,647.5,381.4453,651.5,377.4453" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="496.5" x2="653.5" y1="377.4453" y2="377.4453"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="139" x="503.5" y="372.3794">dispatch($navigation)</text><polygon fill="#A80036" points="250.5,402.5781,240.5,406.5781,250.5,410.5781,246.5,406.5781" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="244.5" x2="495.5" y1="406.5781" y2="406.5781"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="52" x="256.5" y="401.5122">render()</text><polygon fill="#A80036" points="250.5,431.7109,240.5,435.7109,250.5,439.7109,246.5,435.7109" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="244.5" x2="495.5" y1="435.7109" y2="435.7109"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="76" x="256.5" y="430.645">$onrender()</text><polygon fill="#A80036" points="647.5,460.8438,657.5,464.8438,647.5,468.8438,651.5,464.8438" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="496.5" x2="653.5" y1="464.8438" y2="464.8438"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="117" x="503.5" y="459.7778">dispatch($redraw)</text><polygon fill="#A80036" points="78.5,489.9766,68.5,493.9766,78.5,497.9766,74.5,493.9766" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="72.5" x2="382.5" y1="493.9766" y2="493.9766"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="69" x="84.5" y="488.9106">postStart()</text><path d="M183.5,508.9766 L281.5,508.9766 L281.5,515.9766 L271.5,525.9766 L183.5,525.9766 L183.5,508.9766 " fill="#EEEEEE" style="stroke: #000000; stroke-width: 1.0;"/><rect fill="none" height="104.5313" style="stroke: #000000; stroke-width: 2.0;" width="509" x="183.5" y="508.9766"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="53" x="198.5" y="522.0435">redraw</text><polygon fill="#A80036" points="371.5,543.2422,381.5,547.2422,371.5,551.2422,375.5,547.2422" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="239.5" x2="377.5" y1="547.2422" y2="547.2422"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="87" x="246.5" y="542.1763">h3.redraw()</text><polygon fill="#A80036" points="250.5,572.375,240.5,576.375,250.5,580.375,246.5,576.375" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="244.5" x2="382.5" y1="576.375" y2="576.375"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="55" x="256.5" y="571.3091">redraw()</text><polygon fill="#A80036" points="647.5,601.5078,657.5,605.5078,647.5,609.5078,651.5,605.5078" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="383.5" x2="653.5" y1="605.5078" y2="605.5078"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="117" x="390.5" y="600.4419">dispatch($redraw)</text><path d="M13,627.5078 L137,627.5078 L137,634.5078 L127,644.5078 L13,644.5078 L13,627.5078 " fill="#EEEEEE" style="stroke: #000000; stroke-width: 1.0;"/><rect fill="none" height="308.4609" style="stroke: #000000; stroke-width: 2.0;" width="679.5" x="13" y="627.5078"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="79" x="28" y="640.5747">navigation</text><polygon fill="#A80036" points="371.5,661.7734,381.5,665.7734,371.5,669.7734,375.5,665.7734" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="239.5" x2="377.5" y1="665.7734" y2="665.7734"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="120" x="246.5" y="660.7075">h3.navigateTo()</text><polygon fill="#A80036" points="484.5,690.9063,494.5,694.9063,484.5,698.9063,488.5,694.9063" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="383.5" x2="490.5" y1="694.9063" y2="694.9063"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="89" x="390.5" y="689.8403">processPath()</text><polygon fill="#A80036" points="250.5,720.0391,240.5,724.0391,250.5,728.0391,246.5,724.0391" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 2.0,2.0;" x1="244.5" x2="495.5" y1="724.0391" y2="724.0391"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="71" x="256.5" y="718.9731">teardown()</text><polygon fill="#A80036" points="250.5,749.1719,240.5,753.1719,250.5,757.1719,246.5,753.1719" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 2.0,2.0;" x1="244.5" x2="495.5" y1="753.1719" y2="753.1719"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="46" x="256.5" y="748.106">setup()</text><polygon fill="#A80036" points="647.5,778.3047,657.5,782.3047,647.5,786.3047,651.5,782.3047" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="496.5" x2="653.5" y1="782.3047" y2="782.3047"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="139" x="503.5" y="777.2388">dispatch($navigation)</text><polygon fill="#A80036" points="78.5,807.4375,68.5,811.4375,78.5,815.4375,74.5,811.4375" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="72.5" x2="495.5" y1="811.4375" y2="811.4375"/><text fill="#000000" font-family="sans-serif" font-size="13" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="148" x="84.5" y="806.3716">remove all DOM nodes</text><polygon fill="#A80036" points="227.5,836.5703,237.5,840.5703,227.5,844.5703,231.5,840.5703" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="67.5" x2="233.5" y1="840.5703" y2="840.5703"/><text fill="#000000" font-family="sans-serif" font-size="13" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="148" x="74.5" y="835.5044">remove all DOM nodes</text><polygon fill="#A80036" points="250.5,865.7031,240.5,869.7031,250.5,873.7031,246.5,869.7031" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="244.5" x2="495.5" y1="869.7031" y2="869.7031"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="52" x="256.5" y="864.6372">render()</text><polygon fill="#A80036" points="250.5,894.8359,240.5,898.8359,250.5,902.8359,246.5,898.8359" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="244.5" x2="495.5" y1="898.8359" y2="898.8359"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="76" x="256.5" y="893.77">$onrender()</text><polygon fill="#A80036" points="647.5,923.9688,657.5,927.9688,647.5,931.9688,651.5,927.9688" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="496.5" x2="653.5" y1="927.9688" y2="927.9688"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="117" x="503.5" y="922.9028">dispatch($redraw)</text><!--MD5=[559f570c0dfd5741e1c3ff61a0a0827a] +<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="968px" preserveAspectRatio="none" style="width:790px;height:968px;" version="1.1" viewBox="0 0 790 968" width="790px" zoomAndPan="magnify"><defs><filter height="300%" id="fo3lyn3eej96z" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><text fill="#000000" font-family="sans-serif" font-size="18" lengthAdjust="spacingAndGlyphs" textLength="205" x="291.75" y="26.708">H3 Sequence Diagram</text><rect fill="#FFFFFF" filter="url(#fo3lyn3eej96z)" height="104.5313" style="stroke: #000000; stroke-width: 2.0;" width="509" x="183.5" y="479.8438"/><rect fill="#FFFFFF" filter="url(#fo3lyn3eej96z)" height="279.3281" style="stroke: #000000; stroke-width: 2.0;" width="679.5" x="13" y="598.375"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="67" x2="67" y1="100.25" y2="894.7031"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="239.5" x2="239.5" y1="100.25" y2="894.7031"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="383.5" x2="383.5" y1="100.25" y2="894.7031"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="496" x2="496" y1="100.25" y2="894.7031"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="659.5" x2="659.5" y1="100.25" y2="894.7031"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="83" x="23" y="96.9482">Application</text><ellipse cx="67.5" cy="67.9531" fill="#FEFECE" filter="url(#fo3lyn3eej96z)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><line style="stroke: #A80036; stroke-width: 2.0;" x1="55.5" x2="79.5" y1="81.9531" y2="81.9531"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="83" x="23" y="906.6982">Application</text><ellipse cx="67.5" cy="926" fill="#FEFECE" filter="url(#fo3lyn3eej96z)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><line style="stroke: #A80036; stroke-width: 2.0;" x1="55.5" x2="79.5" y1="940" y2="940"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="86" x="193.5" y="96.9482">Component</text><ellipse cx="239.5" cy="67.9531" fill="#FEFECE" filter="url(#fo3lyn3eej96z)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><polygon fill="#A80036" points="235.5,55.9531,241.5,50.9531,239.5,55.9531,241.5,60.9531,235.5,55.9531" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="86" x="193.5" y="906.6982">Component</text><ellipse cx="239.5" cy="926" fill="#FEFECE" filter="url(#fo3lyn3eej96z)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><polygon fill="#A80036" points="235.5,914,241.5,909,239.5,914,241.5,919,235.5,914" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="19" x="371" y="96.9482">H3</text><ellipse cx="383.5" cy="67.9531" fill="#FEFECE" filter="url(#fo3lyn3eej96z)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><line style="stroke: #A80036; stroke-width: 2.0;" x1="371.5" x2="395.5" y1="81.9531" y2="81.9531"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="19" x="371" y="906.6982">H3</text><ellipse cx="383.5" cy="926" fill="#FEFECE" filter="url(#fo3lyn3eej96z)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><line style="stroke: #A80036; stroke-width: 2.0;" x1="371.5" x2="395.5" y1="940" y2="940"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="49" x="469" y="96.9482">Router</text><ellipse cx="496.5" cy="67.9531" fill="#FEFECE" filter="url(#fo3lyn3eej96z)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><line style="stroke: #A80036; stroke-width: 2.0;" x1="484.5" x2="508.5" y1="81.9531" y2="81.9531"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="49" x="469" y="906.6982">Router</text><ellipse cx="496.5" cy="926" fill="#FEFECE" filter="url(#fo3lyn3eej96z)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><line style="stroke: #A80036; stroke-width: 2.0;" x1="484.5" x2="508.5" y1="940" y2="940"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="40" x="636.5" y="96.9482">Store</text><path d="M641.5,47.9531 C641.5,37.9531 659.5,37.9531 659.5,37.9531 C659.5,37.9531 677.5,37.9531 677.5,47.9531 L677.5,73.9531 C677.5,83.9531 659.5,83.9531 659.5,83.9531 C659.5,83.9531 641.5,83.9531 641.5,73.9531 L641.5,47.9531 " fill="#FEFECE" filter="url(#fo3lyn3eej96z)" style="stroke: #000000; stroke-width: 1.5;"/><path d="M641.5,47.9531 C641.5,57.9531 659.5,57.9531 659.5,57.9531 C659.5,57.9531 677.5,57.9531 677.5,47.9531 " fill="none" style="stroke: #000000; stroke-width: 1.5;"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="40" x="636.5" y="906.6982">Store</text><path d="M641.5,920 C641.5,910 659.5,910 659.5,910 C659.5,910 677.5,910 677.5,920 L677.5,946 C677.5,956 659.5,956 659.5,956 C659.5,956 641.5,956 641.5,946 L641.5,920 " fill="#FEFECE" filter="url(#fo3lyn3eej96z)" style="stroke: #000000; stroke-width: 1.5;"/><path d="M641.5,920 C641.5,930 659.5,930 659.5,930 C659.5,930 677.5,930 677.5,920 " fill="none" style="stroke: #000000; stroke-width: 1.5;"/><polygon fill="#A80036" points="371.5,127.3828,381.5,131.3828,371.5,135.3828,375.5,131.3828" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 2.0,2.0;" x1="67.5" x2="377.5" y1="131.3828" y2="131.3828"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="59" x="74.5" y="126.3169">h3.init()</text><polygon fill="#A80036" points="647.5,156.5156,657.5,160.5156,647.5,164.5156,651.5,160.5156" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="383.5" x2="653.5" y1="160.5156" y2="160.5156"/><text fill="#000000" font-family="sans-serif" font-size="13" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="56" x="390.5" y="155.4497">initialize</text><line style="stroke: #A80036; stroke-width: 1.0;" x1="659.5" x2="701.5" y1="189.6484" y2="189.6484"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="701.5" x2="701.5" y1="189.6484" y2="202.6484"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="660.5" x2="701.5" y1="202.6484" y2="202.6484"/><polygon fill="#A80036" points="670.5,198.6484,660.5,202.6484,670.5,206.6484,666.5,202.6484" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="112" x="666.5" y="184.5825">execute modules</text><polygon fill="#A80036" points="647.5,227.7813,657.5,231.7813,647.5,235.7813,651.5,231.7813" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="383.5" x2="653.5" y1="231.7813" y2="231.7813"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="91" x="390.5" y="226.7153">dispatch($init)</text><polygon fill="#A80036" points="78.5,256.9141,68.5,260.9141,78.5,264.9141,74.5,260.9141" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="72.5" x2="382.5" y1="260.9141" y2="260.9141"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="62" x="84.5" y="255.8481">preStart()</text><polygon fill="#A80036" points="484.5,286.0469,494.5,290.0469,484.5,294.0469,488.5,290.0469" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="383.5" x2="490.5" y1="290.0469" y2="290.0469"/><text fill="#000000" font-family="sans-serif" font-size="13" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="56" x="390.5" y="284.981">initialize</text><polygon fill="#A80036" points="484.5,315.1797,494.5,319.1797,484.5,323.1797,488.5,319.1797" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="383.5" x2="490.5" y1="319.1797" y2="319.1797"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="40" x="390.5" y="314.1138">start()</text><polygon fill="#A80036" points="250.5,344.3125,240.5,348.3125,250.5,352.3125,246.5,348.3125" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 2.0,2.0;" x1="244.5" x2="495.5" y1="348.3125" y2="348.3125"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="46" x="256.5" y="343.2466">setup()</text><polygon fill="#A80036" points="647.5,373.4453,657.5,377.4453,647.5,381.4453,651.5,377.4453" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="496.5" x2="653.5" y1="377.4453" y2="377.4453"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="139" x="503.5" y="372.3794">dispatch($navigation)</text><polygon fill="#A80036" points="250.5,402.5781,240.5,406.5781,250.5,410.5781,246.5,406.5781" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="244.5" x2="495.5" y1="406.5781" y2="406.5781"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="52" x="256.5" y="401.5122">render()</text><polygon fill="#A80036" points="647.5,431.7109,657.5,435.7109,647.5,439.7109,651.5,435.7109" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="496.5" x2="653.5" y1="435.7109" y2="435.7109"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="117" x="503.5" y="430.645">dispatch($redraw)</text><polygon fill="#A80036" points="78.5,460.8438,68.5,464.8438,78.5,468.8438,74.5,464.8438" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="72.5" x2="382.5" y1="464.8438" y2="464.8438"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="69" x="84.5" y="459.7778">postStart()</text><path d="M183.5,479.8438 L281.5,479.8438 L281.5,486.8438 L271.5,496.8438 L183.5,496.8438 L183.5,479.8438 " fill="#EEEEEE" style="stroke: #000000; stroke-width: 1.0;"/><rect fill="none" height="104.5313" style="stroke: #000000; stroke-width: 2.0;" width="509" x="183.5" y="479.8438"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="53" x="198.5" y="492.9106">redraw</text><polygon fill="#A80036" points="371.5,514.1094,381.5,518.1094,371.5,522.1094,375.5,518.1094" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="239.5" x2="377.5" y1="518.1094" y2="518.1094"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="87" x="246.5" y="513.0435">h3.redraw()</text><polygon fill="#A80036" points="250.5,543.2422,240.5,547.2422,250.5,551.2422,246.5,547.2422" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="244.5" x2="382.5" y1="547.2422" y2="547.2422"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="55" x="256.5" y="542.1763">redraw()</text><polygon fill="#A80036" points="647.5,572.375,657.5,576.375,647.5,580.375,651.5,576.375" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="383.5" x2="653.5" y1="576.375" y2="576.375"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="117" x="390.5" y="571.3091">dispatch($redraw)</text><path d="M13,598.375 L137,598.375 L137,605.375 L127,615.375 L13,615.375 L13,598.375 " fill="#EEEEEE" style="stroke: #000000; stroke-width: 1.0;"/><rect fill="none" height="279.3281" style="stroke: #000000; stroke-width: 2.0;" width="679.5" x="13" y="598.375"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="79" x="28" y="611.4419">navigation</text><polygon fill="#A80036" points="371.5,632.6406,381.5,636.6406,371.5,640.6406,375.5,636.6406" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="239.5" x2="377.5" y1="636.6406" y2="636.6406"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="120" x="246.5" y="631.5747">h3.navigateTo()</text><polygon fill="#A80036" points="484.5,661.7734,494.5,665.7734,484.5,669.7734,488.5,665.7734" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="383.5" x2="490.5" y1="665.7734" y2="665.7734"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="89" x="390.5" y="660.7075">processPath()</text><polygon fill="#A80036" points="250.5,690.9063,240.5,694.9063,250.5,698.9063,246.5,694.9063" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 2.0,2.0;" x1="244.5" x2="495.5" y1="694.9063" y2="694.9063"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="71" x="256.5" y="689.8403">teardown()</text><polygon fill="#A80036" points="250.5,720.0391,240.5,724.0391,250.5,728.0391,246.5,724.0391" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 2.0,2.0;" x1="244.5" x2="495.5" y1="724.0391" y2="724.0391"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="46" x="256.5" y="718.9731">setup()</text><polygon fill="#A80036" points="647.5,749.1719,657.5,753.1719,647.5,757.1719,651.5,753.1719" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="496.5" x2="653.5" y1="753.1719" y2="753.1719"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="139" x="503.5" y="748.106">dispatch($navigation)</text><polygon fill="#A80036" points="78.5,778.3047,68.5,782.3047,78.5,786.3047,74.5,782.3047" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="72.5" x2="495.5" y1="782.3047" y2="782.3047"/><text fill="#000000" font-family="sans-serif" font-size="13" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="148" x="84.5" y="777.2388">remove all DOM nodes</text><polygon fill="#A80036" points="227.5,807.4375,237.5,811.4375,227.5,815.4375,231.5,811.4375" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="67.5" x2="233.5" y1="811.4375" y2="811.4375"/><text fill="#000000" font-family="sans-serif" font-size="13" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="148" x="74.5" y="806.3716">remove all DOM nodes</text><polygon fill="#A80036" points="250.5,836.5703,240.5,840.5703,250.5,844.5703,246.5,840.5703" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="244.5" x2="495.5" y1="840.5703" y2="840.5703"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="52" x="256.5" y="835.5044">render()</text><polygon fill="#A80036" points="647.5,865.7031,657.5,869.7031,647.5,873.7031,651.5,869.7031" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="496.5" x2="653.5" y1="869.7031" y2="869.7031"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="117" x="503.5" y="864.6372">dispatch($redraw)</text><!--MD5=[614c92b2d506a199dbd455d6b47c9e39] @startuml title H3 Sequence Diagram

@@ -18,7 +18,6 @@ H3 -> Router : start()

Router - -> Component : setup() Router -> Store: dispatch($navigation) Router -> Component : render() -Router -> Component : $onrender() Router -> Store: dispatch($redraw) H3 -> Application : postStart()

@@ -38,17 +37,15 @@ Router -> Store: dispatch($navigation)

Router -> Application : //remove all DOM nodes// Application -> Component : //remove all DOM nodes// Router -> Component : render() - Router -> Component : $onrender() Router -> Store: dispatch($redraw) end @enduml -PlantUML version 1.2020.10(Sun May 17 09:48:49 UTC 2020) +PlantUML version 1.2020.11(Sat May 30 10:13:43 UTC 2020) (GPL source distribution) Java Runtime: Java(TM) SE Runtime Environment JVM: Java HotSpot(TM) 64-Bit Server VM -Operating System: Linux Default Encoding: UTF-8 Language: en Country: US
M docs/js/app.jsdocs/js/app.js

@@ -44,8 +44,8 @@ h3("a.logo.col-sm-1", { href: "#/" }, [

h3("img", { alt: "H3", src: "images/h3.svg" }), ]), h3("div.version.col-sm.col-md", [ - h3("div.version-number", "v0.7.0"), - h3("div.version-label", "“Gory Gorn“"), + h3("div.version-number", "v0.8.0"), + h3("div.version-label", "“Humble Human“"), ]), h3("label.drawer-toggle.button.col-sm-last", { for: "drawer-control" }), ]);
M docs/js/h3.jsdocs/js/h3.js

@@ -1,5 +1,5 @@

/** - * H3 v0.7.0 "Gory Gorn" + * H3 v0.8.0 "Humble Human" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * This source code is licensed under the MIT license found in the

@@ -59,8 +59,9 @@ }

return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1); }; +const selectorRegex = /^([a-z][a-z0-9:_=-]*)(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i; + let $onrenderCallbacks = []; -const selectorRegex = /^([a-z][a-z0-9:_=-]*)(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i; // Virtual Node Implementation with HyperScript-like syntax class VNode {

@@ -69,7 +70,6 @@ this.type = undefined;

this.attributes = {}; this.data = {}; this.id = undefined; - this.$key = undefined; this.$html = undefined; this.$onrender = undefined; this.style = undefined;

@@ -159,7 +159,6 @@ from(data) {

this.value = data.value; this.type = data.type; this.id = data.id; - this.$key = data.$key; this.$html = data.$html; this.$onrender = data.$onrender; this.style = data.style;

@@ -177,7 +176,6 @@ }

processProperties(attrs) { this.id = this.id || attrs.id; - this.$key = attrs.$key; this.$html = attrs.$html; this.$onrender = attrs.$onrender; this.style = attrs.style;

@@ -189,7 +187,7 @@ ? attrs.classList

: this.classList; this.attributes = attrs; Object.keys(attrs) - .filter((a) => a.startsWith("on")) + .filter((a) => a.startsWith("on") && attrs[a]) .forEach((key) => { if (typeof attrs[key] !== "function") { throw new Error(

@@ -200,7 +198,6 @@ this.eventListeners[key.slice(2)] = attrs[key];

delete this.attributes[key]; }); delete this.attributes.value; - delete this.attributes.$key; delete this.attributes.$html; delete this.attributes.$onrender; delete this.attributes.id;

@@ -302,11 +299,11 @@ // Children

this.children.forEach((c) => { const cnode = c.render(); node.appendChild(cnode); + c.$onrender && $onrenderCallbacks.push(() => c.$onrender(cnode)); }); if (this.$html) { node.innerHTML = this.$html; } - this.$onrender && $onrenderCallbacks.push(() => this.$onrender(node)); return node; }

@@ -324,11 +321,10 @@ oldvnode !== newvnode)

) { const renderedNode = newvnode.render(); node.parentNode.replaceChild(renderedNode, node); + newvnode.$onrender && newvnode.$onrender(renderedNode); oldvnode.from(newvnode); return; } - // $key - oldvnode.$key = newvnode.$key; // ID if (oldvnode.id !== newvnode.id) { node.id = newvnode.id || "";

@@ -416,104 +412,113 @@ });

oldvnode.eventListeners = newvnode.eventListeners; } // Children - function mapChildren(parent1, parent2) { - const map = []; - for (let j = 0; j < parent1.children.length; j++) { - let found = false; - for (let k = 0; k < parent2.children.length; k++) { + function mapChildren(oldvnode, newvnode) { + let map = []; + let oldNodesFound = 0; + let newNodesFound = 0; + // First look for existing nodes + for (let oldIndex = 0; oldIndex < oldvnode.children.length; oldIndex++) { + let found = -1; + for (let index = 0; index < newvnode.children.length; index++) { if ( - parent1.children[j].equal(parent2.children[k]) && - !map.includes(k) + equal(oldvnode.children[oldIndex], newvnode.children[index]) && + !map.includes(index) ) { - map.push(k); - found = true; + found = index; + newNodesFound++; + oldNodesFound++; break; } } - if (!found) { - map.push(-1); + map.push(found); + } + if ( + newNodesFound === oldNodesFound && + newvnode.children.length === oldvnode.children.length + ) { + // something changed but everything else is the same + return map; + } + if (newNodesFound === newvnode.children.length) { + // All children in newvnode exist in oldvnode + // All nodes that are not found must be removed + for (let i = 0; i < map.length; i++) { + if (map[i] === -1) { + map[i] = -3; + } } } - return map; - } - let newmap, oldmap, notFoundInNew, notFoundInOld; - const remap = () => { - // Map positions of newvnode children in relation to oldvnode children - newmap = mapChildren(newvnode, oldvnode); - // Map positions of oldvnode children in relation to newvnode children - oldmap = mapChildren(oldvnode, newvnode); - notFoundInOld = newmap.indexOf(-1); - notFoundInNew = oldmap.indexOf(-1); - }; - remap(); - if (newmap.length === oldmap.length) { - if (equal(newmap, oldmap) && notFoundInNew >= 0) { - // Something changed (some nodes are different at the same position) - for (let i = 0; i < newmap.length; i++) { - if (newmap[i] === -1 || oldmap[i] === -1) { - oldvnode.children[i].redraw({ - node: node.childNodes[i], - vnode: newvnode.children[i], - }); + if (oldNodesFound === oldvnode.children.length) { + // All children in oldvnode exist in newvnode + // Check where the missing newvnodes children need to be added + for ( + let newIndex = 0; + newIndex < newvnode.children.length; + newIndex++ + ) { + if (!map.includes(newIndex)) { + map.splice(newIndex, 0, -2); } } - } else { - // Nodes in different position (maps have same nodes) - let index = 0; - while (!equal(oldmap, [...Array(oldmap.length).keys()])) { - if (newmap[index] !== index) { - const child = node.childNodes[newmap[index]]; - node.removeChild(child); - node.insertBefore(child, node.childNodes[index]); - const cnode = oldvnode.children[newmap[index]]; - oldvnode.children = oldvnode.children.filter( - (c) => !equal(c, cnode) - ); - oldvnode.children.splice(index, 0, cnode); - remap(); - index = 0; - } else { - index++; + } + // Check if nodes needs to be removed (if there are fewer children) + if (newvnode.children.length < oldvnode.children.length) { + for (let i = 0; i < map.length; i++) { + if (map[i] === -1 && !newvnode.children[i]) { + map[i] = -3; } } } - } else { - while (notFoundInOld >= 0 || notFoundInNew >= 0) { - // First remove children not found in new map, then add the missing ones. - if (notFoundInNew >= 0) { - const childOfNew = - newvnode.children.length > notFoundInNew && - newvnode.children[notFoundInNew]; - const childOfOld = oldvnode.children[notFoundInNew]; - if ( - childOfNew && - childOfOld && - childOfOld.type === childOfNew.type && - childOfNew.children.length === 0 && - childOfNew.children.length === 0 - ) { - // Optimization to avoid removing simple nodes of the same type - oldvnode.children[notFoundInNew].redraw({ - node: node.childNodes[notFoundInNew], - vnode: newvnode.children[notFoundInNew], + return map; + } + let childMap = mapChildren(oldvnode, newvnode); + let resultMap = [...Array(childMap.filter((i) => i !== -3).length).keys()]; + while (!equal(childMap, resultMap)) { + let count = -1; + for (let i of childMap) { + count++; + let breakFor = false; + if (i === count) { + // Matching nodes; + continue; + } + switch (i) { + case -1: + // different node, check + oldvnode.children[count].redraw({ + node: node.childNodes[count], + vnode: newvnode.children[count], }); - } else { - // While there are children not found in newvnode, remove them and re-check - node.removeChild(node.childNodes[notFoundInNew]); - oldvnode.children.splice(notFoundInNew, 1); - } - } else { - // While there are children not found in oldvnode, add them and re-check - const cnode = newvnode.children[notFoundInOld].render(); - node.insertBefore(cnode, node.childNodes[notFoundInOld]); - oldvnode.children.splice( - notFoundInOld, - 0, - newvnode.children[notFoundInOld] - ); + break; + case -2: + // add node + oldvnode.children.splice(count, 0, newvnode.children[count]); + const renderedNode = newvnode.children[count].render(); + node.insertBefore(renderedNode, node.childNodes[count]); + newvnode.children[count].$onrender && + newvnode.children[count].$onrender(renderedNode); + breakFor = true; + break; + case -3: + // remove node + oldvnode.children.splice(count, 1); + node.removeChild(node.childNodes[count]); + breakFor = true; + break; + default: + // Node found, move nodes and remap + const vtarget = oldvnode.children.splice(i, 1)[0]; + oldvnode.children.splice(count, 0, vtarget); + node.insertBefore(node.childNodes[i], node.childNodes[count]); + breakFor = true; + break; } - remap(); + if (breakFor) { + break; + } } + childMap = mapChildren(oldvnode, newvnode); + resultMap = [...Array(childMap.length).keys()]; } // $onrender if (!equal(oldvnode.$onrender, newvnode.$onrender)) {

@@ -601,7 +606,6 @@ }

async start() { const processPath = async (data) => { - $onrenderCallbacks = []; const oldRoute = this.route; const fragment = (data &&

@@ -659,6 +663,7 @@ }

const vnode = newRouteComponent(newRouteComponent.state); const node = vnode.render(); this.element.appendChild(node); + vnode.$onrender && vnode.$onrender(node); $onrenderCallbacks.forEach((cbk) => cbk()); $onrenderCallbacks = []; this.setRedraw(vnode, newRouteComponent.state);
M docs/md/api.mddocs/md/api.md

@@ -112,9 +112,8 @@

* Any attribute starting with *on* (e.g. onclick, onkeydown, etc.) will be treated as an event listener. * The `classList` attribute can be set to a list of classes to apply to the element (as an alternative to using the element selector shorthand). * The `data` attribute can be set to a simple object containing [data attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes). -* The special `$key` attribute can be used to guarantee the uniqueness of two VNodes and it will not be translated into an HTML attribute. * The special `$html` attribute can be used to set the `innerHTML` property of the resulting HTML element. Use only if you know what you are doing! -* The special `$onrender` attribute can be set to a function that will executed after the VNode is rendered for the first time. +* The special `$onrender` attribute can be set to a function that will executed every time the VNode is rendered and added to the DOM. The `$html` and the `$onrender` special attributes should be used sparingly, and typically only when interfacing with third-party libraries that need access to the real DOM.
M docs/md/key-concepts.mddocs/md/key-concepts.md

@@ -83,7 +83,7 @@

A route component is a top-level component that handles a route. Unlike ordinary components, route components: * may have a dedicated *setup* (after the route component is added to the DOM) and *teardown* phase (after the route component is removed from the DOM and before the new route component is loaded). -* may have built-in local state, initialized during setup and (typically) destroyed during teardown. +* may have built-in local state, initialized during setup and (typically) destroyed during teardown. Such state is passed as the first (and only) parameter of the route component when executed. Route components are stll created using ordinary function returning a VNode, but you can optionally define a **setup** and a **teardown** async methods on them (functions are objects in JavaScript after all...) to be executed during each corresponding phase.

@@ -108,8 +108,7 @@ 5. The *Router* is initialized and started.

6. The **setup()** method of the matching Route Component is called (if any). 8. The **$navigation** event is dispatched. 9. The *Route Component* matching the current route and all its child components are rendered for the first time. -10. Any callback specified via the **$onrender** special attributes in the loaded components is executed once all components are rendered. -11. The **$redraw** event is dispatched. +10. The **$redraw** event is dispatched. Then, whenever the `h3.redraw()` method is called (typically within a component):

@@ -122,11 +121,9 @@ 1. The *Router* processes the new path and determine which component to render based on the routing configuration.

2. The **teardow()** method of the current Route Component is called (if any). 3. The **setup()** method of the new matching Route Component is called (if any). 4. All DOM nodes within the scope of the routing are removed, all components are removed. -5. Any **$onrender** callback defined in the added components is executed once all components are rendered. 6. The **$navigation** event is dispatched. 7. All DOM nodes are removed. 8. The *Route Component* matching the new route and all its child components are rendered. -9. Any callback specified via the **$onrender** special attributes in the loaded components is executed once all components are rendered. 10. The **$redraw** event is dispatched. And that's it. The whole idea is to make the system extremely *simple* and *predictable* &mdash; which means everything should be very easy to debug, too.
M docs/md/overview.mddocs/md/overview.md

@@ -12,7 +12,7 @@ ### I'm sold! Where can I get it?

Here, look, it's just one file: -<a href="https://raw.githubusercontent.com/h3rald/h3/v0.7.0/h3.js" target="_blank" class="button primary">Download v0.7.0 (Gory Gorn)</a> +<a href="https://raw.githubusercontent.com/h3rald/h3/v0.8.0/h3.js" target="_blank" class="button primary">Download v0.8.0 (Humble Human)</a> Yes there is also a [NPM package](https://www.npmjs.com/package/@h3rald/h3) if you want to use it with WebPack and similar, but let me repeat: _it's just one file_.
M docs/md/tutorial.mddocs/md/tutorial.md

@@ -118,8 +118,8 @@ h3("a.logo.col-sm-1", { href: "#/" }, [

h3("img", { alt: "H3", src: "images/h3.svg" }), ]), h3("div.version.col-sm.col-md", [ - h3("div.version-number", "v0.7.0"), - h3("div.version-label", "“Gory Gorn“"), + h3("div.version-number", "v0.8.0"), + h3("div.version-label", "“Humble Human“"), ]), h3("label.drawer-toggle.button.col-sm-last", { for: "drawer-control" }), ]);

@@ -188,4 +188,8 @@ ```

### Next steps -Made it this far? Good. Wanna know more? Have a look at the code of the [todo list example](https://github.com/h3rald/h3/tree/master/docs/example) and try it out [here](https://h3.js.org/example/index.html).+Made it this far? Good. Wanna know more? Have a look at the code of the [todo list example](https://github.com/h3rald/h3/tree/master/docs/example) and try it out [here](https://h3.js.org/example/index.html). + +Once you feel more comfortable and you are ready to dive into a more complex application, featuring different routes, route components, forms, confirmation messages, plenty of third-party components etc., have a look at [LitePad](https://github.com/h3rald/litepad). You can see it in action here: [litepad.h3rald.com](https://litepad.h3rald.com/). + +Note: the LitePad online demo will store all its data in localStorage.
M h3.jsh3.js

@@ -1,5 +1,5 @@

/** - * H3 v0.7.0 "Gory Gorn" + * H3 v0.8.0 "Humble Human" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * This source code is licensed under the MIT license found in the

@@ -59,8 +59,9 @@ }

return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1); }; +const selectorRegex = /^([a-z][a-z0-9:_=-]*)(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i; + let $onrenderCallbacks = []; -const selectorRegex = /^([a-z][a-z0-9:_=-]*)(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i; // Virtual Node Implementation with HyperScript-like syntax class VNode {

@@ -69,7 +70,6 @@ this.type = undefined;

this.attributes = {}; this.data = {}; this.id = undefined; - this.$key = undefined; this.$html = undefined; this.$onrender = undefined; this.style = undefined;

@@ -159,7 +159,6 @@ from(data) {

this.value = data.value; this.type = data.type; this.id = data.id; - this.$key = data.$key; this.$html = data.$html; this.$onrender = data.$onrender; this.style = data.style;

@@ -177,7 +176,6 @@ }

processProperties(attrs) { this.id = this.id || attrs.id; - this.$key = attrs.$key; this.$html = attrs.$html; this.$onrender = attrs.$onrender; this.style = attrs.style;

@@ -189,7 +187,7 @@ ? attrs.classList

: this.classList; this.attributes = attrs; Object.keys(attrs) - .filter((a) => a.startsWith("on")) + .filter((a) => a.startsWith("on") && attrs[a]) .forEach((key) => { if (typeof attrs[key] !== "function") { throw new Error(

@@ -200,7 +198,6 @@ this.eventListeners[key.slice(2)] = attrs[key];

delete this.attributes[key]; }); delete this.attributes.value; - delete this.attributes.$key; delete this.attributes.$html; delete this.attributes.$onrender; delete this.attributes.id;

@@ -302,11 +299,11 @@ // Children

this.children.forEach((c) => { const cnode = c.render(); node.appendChild(cnode); + c.$onrender && $onrenderCallbacks.push(() => c.$onrender(cnode)); }); if (this.$html) { node.innerHTML = this.$html; } - this.$onrender && $onrenderCallbacks.push(() => this.$onrender(node)); return node; }

@@ -324,11 +321,10 @@ oldvnode !== newvnode)

) { const renderedNode = newvnode.render(); node.parentNode.replaceChild(renderedNode, node); + newvnode.$onrender && newvnode.$onrender(renderedNode); oldvnode.from(newvnode); return; } - // $key - oldvnode.$key = newvnode.$key; // ID if (oldvnode.id !== newvnode.id) { node.id = newvnode.id || "";

@@ -416,104 +412,113 @@ });

oldvnode.eventListeners = newvnode.eventListeners; } // Children - function mapChildren(parent1, parent2) { - const map = []; - for (let j = 0; j < parent1.children.length; j++) { - let found = false; - for (let k = 0; k < parent2.children.length; k++) { + function mapChildren(oldvnode, newvnode) { + let map = []; + let oldNodesFound = 0; + let newNodesFound = 0; + // First look for existing nodes + for (let oldIndex = 0; oldIndex < oldvnode.children.length; oldIndex++) { + let found = -1; + for (let index = 0; index < newvnode.children.length; index++) { if ( - parent1.children[j].equal(parent2.children[k]) && - !map.includes(k) + equal(oldvnode.children[oldIndex], newvnode.children[index]) && + !map.includes(index) ) { - map.push(k); - found = true; + found = index; + newNodesFound++; + oldNodesFound++; break; } } - if (!found) { - map.push(-1); + map.push(found); + } + if ( + newNodesFound === oldNodesFound && + newvnode.children.length === oldvnode.children.length + ) { + // something changed but everything else is the same + return map; + } + if (newNodesFound === newvnode.children.length) { + // All children in newvnode exist in oldvnode + // All nodes that are not found must be removed + for (let i = 0; i < map.length; i++) { + if (map[i] === -1) { + map[i] = -3; + } } } - return map; - } - let newmap, oldmap, notFoundInNew, notFoundInOld; - const remap = () => { - // Map positions of newvnode children in relation to oldvnode children - newmap = mapChildren(newvnode, oldvnode); - // Map positions of oldvnode children in relation to newvnode children - oldmap = mapChildren(oldvnode, newvnode); - notFoundInOld = newmap.indexOf(-1); - notFoundInNew = oldmap.indexOf(-1); - }; - remap(); - if (newmap.length === oldmap.length) { - if (equal(newmap, oldmap) && notFoundInNew >= 0) { - // Something changed (some nodes are different at the same position) - for (let i = 0; i < newmap.length; i++) { - if (newmap[i] === -1 || oldmap[i] === -1) { - oldvnode.children[i].redraw({ - node: node.childNodes[i], - vnode: newvnode.children[i], - }); + if (oldNodesFound === oldvnode.children.length) { + // All children in oldvnode exist in newvnode + // Check where the missing newvnodes children need to be added + for ( + let newIndex = 0; + newIndex < newvnode.children.length; + newIndex++ + ) { + if (!map.includes(newIndex)) { + map.splice(newIndex, 0, -2); } } - } else { - // Nodes in different position (maps have same nodes) - let index = 0; - while (!equal(oldmap, [...Array(oldmap.length).keys()])) { - if (newmap[index] !== index) { - const child = node.childNodes[newmap[index]]; - node.removeChild(child); - node.insertBefore(child, node.childNodes[index]); - const cnode = oldvnode.children[newmap[index]]; - oldvnode.children = oldvnode.children.filter( - (c) => !equal(c, cnode) - ); - oldvnode.children.splice(index, 0, cnode); - remap(); - index = 0; - } else { - index++; + } + // Check if nodes needs to be removed (if there are fewer children) + if (newvnode.children.length < oldvnode.children.length) { + for (let i = 0; i < map.length; i++) { + if (map[i] === -1 && !newvnode.children[i]) { + map[i] = -3; } } } - } else { - while (notFoundInOld >= 0 || notFoundInNew >= 0) { - // First remove children not found in new map, then add the missing ones. - if (notFoundInNew >= 0) { - const childOfNew = - newvnode.children.length > notFoundInNew && - newvnode.children[notFoundInNew]; - const childOfOld = oldvnode.children[notFoundInNew]; - if ( - childOfNew && - childOfOld && - childOfOld.type === childOfNew.type && - childOfNew.children.length === 0 && - childOfNew.children.length === 0 - ) { - // Optimization to avoid removing simple nodes of the same type - oldvnode.children[notFoundInNew].redraw({ - node: node.childNodes[notFoundInNew], - vnode: newvnode.children[notFoundInNew], + return map; + } + let childMap = mapChildren(oldvnode, newvnode); + let resultMap = [...Array(childMap.filter((i) => i !== -3).length).keys()]; + while (!equal(childMap, resultMap)) { + let count = -1; + for (let i of childMap) { + count++; + let breakFor = false; + if (i === count) { + // Matching nodes; + continue; + } + switch (i) { + case -1: + // different node, check + oldvnode.children[count].redraw({ + node: node.childNodes[count], + vnode: newvnode.children[count], }); - } else { - // While there are children not found in newvnode, remove them and re-check - node.removeChild(node.childNodes[notFoundInNew]); - oldvnode.children.splice(notFoundInNew, 1); - } - } else { - // While there are children not found in oldvnode, add them and re-check - const cnode = newvnode.children[notFoundInOld].render(); - node.insertBefore(cnode, node.childNodes[notFoundInOld]); - oldvnode.children.splice( - notFoundInOld, - 0, - newvnode.children[notFoundInOld] - ); + break; + case -2: + // add node + oldvnode.children.splice(count, 0, newvnode.children[count]); + const renderedNode = newvnode.children[count].render(); + node.insertBefore(renderedNode, node.childNodes[count]); + newvnode.children[count].$onrender && + newvnode.children[count].$onrender(renderedNode); + breakFor = true; + break; + case -3: + // remove node + oldvnode.children.splice(count, 1); + node.removeChild(node.childNodes[count]); + breakFor = true; + break; + default: + // Node found, move nodes and remap + const vtarget = oldvnode.children.splice(i, 1)[0]; + oldvnode.children.splice(count, 0, vtarget); + node.insertBefore(node.childNodes[i], node.childNodes[count]); + breakFor = true; + break; } - remap(); + if (breakFor) { + break; + } } + childMap = mapChildren(oldvnode, newvnode); + resultMap = [...Array(childMap.length).keys()]; } // $onrender if (!equal(oldvnode.$onrender, newvnode.$onrender)) {

@@ -601,7 +606,6 @@ }

async start() { const processPath = async (data) => { - $onrenderCallbacks = []; const oldRoute = this.route; const fragment = (data &&

@@ -659,6 +663,7 @@ }

const vnode = newRouteComponent(newRouteComponent.state); const node = vnode.render(); this.element.appendChild(node); + vnode.$onrender && vnode.$onrender(node); $onrenderCallbacks.forEach((cbk) => cbk()); $onrenderCallbacks = []; this.setRedraw(vnode, newRouteComponent.state);
M package.jsonpackage.json

@@ -1,7 +1,7 @@

{ "name": "@h3rald/h3", - "version": "0.7.0", - "versionName": "Gory Gorn", + "version": "0.8.0", + "versionName": "Humble Human", "description": "A tiny, extremely minimalist JavaScript microframework.", "main": "h3.js", "scripts": {