diff --git a/PubSub.js b/PubSub.js
new file mode 100644
index 0000000..10ebec4
--- /dev/null
+++ b/PubSub.js
@@ -0,0 +1,39 @@
+let PubSub = ()=>{
+ // state:
+ let subscriptions_by_event = {}; // by event name, that is
+ let subscriptions_by_name = {}; // subscriptions can be named, for easy unsubscribing
+
+ // methods:
+ let pub = (e, ...params)=>{
+ if(subscriptions_by_event[e]){
+ subscriptions_by_event[e].forEach(subscription=>subscription.cb(...params))
+ }
+ };
+ let sub = (e, cb, name)=>{ // 'name' is for unsubbing
+ if(typeof subscriptions_by_event[e] === 'undefined'){
+ subscriptions_by_event[e] = [];
+ }
+
+ let subscription = { e, cb, name: name || '' };
+
+ subscriptions_by_event[e].push(subscription);
+ if(subscription.name !== ''){
+ if(typeof subscriptions_by_name[name] !== 'undefined'){ console.warn('Already subscription with name "'+name+'". Overwriting nonetheless.'); }
+ subscriptions_by_name[name] = subscription;
+ }
+ };
+ let unsub = (name)=>{
+ // check if such a named subscription exists:
+ if(typeof subscriptions_by_name[name] !== 'undefined'){
+ // get ref to subscription object for later:
+ let subscription = subscriptions_by_name[name];
+ // delete subscription from both lists:
+ subscriptions_by_event[subscription.e].splice(subscriptions_by_event[subscription.e].indexOf(subscription), 1);
+ delete subscriptions_by_name[name];
+ }
+ };
+
+ return {pub, sub, unsub};
+ }
+
+export default PubSub;
\ No newline at end of file
diff --git a/behaviors.js b/behaviors.js
new file mode 100644
index 0000000..f521e0b
--- /dev/null
+++ b/behaviors.js
@@ -0,0 +1,16 @@
+import {pub, sub} from './pubsub.js';
+import state from './state.js';
+import redraw from './redraw.js';
+
+// "raw" event listeners, which publish meaningful events, which are listened-to further-down:
+// ...
+
+
+// "meaningful" event listeners:
+//sub('set-current-node', (node_vm)=>{
+// state.current_node_vm = node_vm;
+// redraw();
+// });
+
+
+export default null;
\ No newline at end of file
diff --git a/components/App.js b/components/App.js
new file mode 100644
index 0000000..f7456ef
--- /dev/null
+++ b/components/App.js
@@ -0,0 +1,15 @@
+import {elementOpen as o, elementClose as c, text as t} from '../vendor/incremental-dom.js';
+import state from '../state.js';
+import {pub} from '../pubsub.js';
+
+
+const App = ()=>{
+ o('div', null, null,
+ 'id', 'header');
+ o('h1');
+ t('Options Calendar-Spread Optimizer');
+ c('h1');
+ c('div');
+ }
+
+export default App;
\ No newline at end of file
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000..6b6fda9
Binary files /dev/null and b/favicon.ico differ
diff --git a/index.css b/index.css
new file mode 100644
index 0000000..9d88b99
--- /dev/null
+++ b/index.css
@@ -0,0 +1,33 @@
+* {
+ box-sizing: border-box;
+ }
+
+.node, .node > .content, .node > .node-children-container {
+ display: inline-block;
+ width: min-content;
+ }
+
+.node {
+ margin-top: 5px;
+ margin-bottom: 5px;
+ }
+
+.node > .content {
+ box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
+ }
+.node[currently-selected-node] > .content {
+ /* background-color: #7ea6fc; */
+ box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
+ }
+
+.node > .content > textarea {
+ background: transparent;
+ resize: none;
+ width: 80em;
+ outline: none;
+ border: none;
+ }
+
+.node > .node-children-container {
+ margin-left: 5em;
+ }
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..c268352
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Calendar Optimizer
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..6752a5b
--- /dev/null
+++ b/index.js
@@ -0,0 +1,16 @@
+import redraw from './redraw.js';
+import state from './state.js';
+import x from './behaviors.js';
+
+// bootstrap initial state:
+
+
+redraw();
+
+
+/* TODO */
+/*
+*
+
+
+*/
\ No newline at end of file
diff --git a/pubsub.js b/pubsub.js
new file mode 100644
index 0000000..ba4c9d4
--- /dev/null
+++ b/pubsub.js
@@ -0,0 +1,7 @@
+// global PubSub instance:
+import PubSub from './PubSub.js';
+
+const pubsub = PubSub();
+export const {pub, sub, unsub} = pubsub;
+
+export default pubsub;
\ No newline at end of file
diff --git a/redraw.js b/redraw.js
new file mode 100644
index 0000000..b75ccfa
--- /dev/null
+++ b/redraw.js
@@ -0,0 +1,10 @@
+import {patch} from './vendor/incremental-dom.js';
+import App from './components/App.js';
+
+export const redraw = ()=>{
+ const root = document.getElementById('root');
+
+ patch(root, App);
+ }
+
+export default redraw;
\ No newline at end of file
diff --git a/state.js b/state.js
new file mode 100644
index 0000000..6fb71e1
--- /dev/null
+++ b/state.js
@@ -0,0 +1,8 @@
+
+let state = {
+
+ };
+
+
+
+export default state;
\ No newline at end of file
diff --git a/vendor/incremental-dom.js b/vendor/incremental-dom.js
new file mode 100644
index 0000000..971cfd3
--- /dev/null
+++ b/vendor/incremental-dom.js
@@ -0,0 +1,7 @@
+/**
+ * Bundled by jsDelivr using Rollup v2.60.2 and Terser v5.10.0.
+ * Original file: /npm/incremental-dom@0.7.0/dist/incremental-dom-cjs.js
+ *
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
+ */
+var t={};Object.defineProperty(t,"__esModule",{value:!0});let e="key";let n=!1,r=!1,o=!1;function l(t){if(!t)throw new Error("Expected value to be defined");return t}function i(t){if(!o)throw new Error("Cannot call "+t+"() unless in patch.")}function c(t){if(n)throw new Error(t+"() can not be called between elementOpenStart() and elementOpenEnd().")}function s(t){if(r)throw new Error(t+"() may not be called inside an element that has called skip().")}function u(t){if(!n)throw new Error(t+"() can only be called after calling elementOpenStart().")}function a(t){o=null!=t}function f(t){const e=n;return n=t,e}function p(t){const e=r;return r=t,e}const d=Object.prototype.hasOwnProperty;function h(){}function m(){return new h}function g(t,e){for(;t.length>e;)t.pop()}h.prototype=Object.create(null);const y={default:"__default"};function w(t,e,n){if(null==n)t.removeAttribute(e);else{const r=function(t){return 0===t.lastIndexOf("xml:",0)?"http://www.w3.org/XML/1998/namespace":0===t.lastIndexOf("xlink:",0)?"http://www.w3.org/1999/xlink":null}(e);r?t.setAttributeNS(r,e,String(n)):t.setAttribute(e,String(n))}}function b(t,e,n){t[e]=n}function O(t,e,n){e.indexOf("-")>=0?t.setProperty(e,n):t[e]=n}const x=m();function A(t,e,n){(x[e]||x[y.default])(t,e,n)}x[y.default]=function(t,e,n){const r=typeof n;"object"===r||"function"===r?b(t,e,n):w(t,e,n)},x.style=function(t,e,n){l("style"in t);const r=t.style;if("string"==typeof n)r.cssText=n;else{r.cssText="";for(const t in n)o=n,i=t,d.call(o,i)&&O(r,t,n[t])}var o,i};const S={nodesCreated:null,nodesDeleted:null};class E{constructor(){this.created=[],this.deleted=[]}markCreated(t){this.created.push(t)}markDeleted(t){this.deleted.push(t)}notifyChanges(){S.nodesCreated&&this.created.length>0&&S.nodesCreated(this.created),S.nodesDeleted&&this.deleted.length>0&&S.nodesDeleted(this.deleted)}}function C(t){return 1===t.nodeType}const D="undefined"!=typeof Node&&Node.prototype.getRootNode||function(){let t=this,e=t;for(;t;)e=t,t=t.parentNode;return e};function _(t,e){const n=function(t){const e=D.call(t);return function(t){return 11===t.nodeType||9===t.nodeType}(e)?e.activeElement:null}(t);return n&&t.contains(n)?function(t,e){const n=[];let r=t;for(;r!==e;){const t=l(r);n.push(t),r=t.parentNode}return n}(n,e):[]}class k{constructor(t,e,n){this._attrsArr=null,this.staticsApplied=!1,this.nameOrCtor=t,this.key=e,this.text=n}hasEmptyAttrsArr(){const t=this._attrsArr;return!t||!t.length}getAttrsArr(t){return this._attrsArr||(this._attrsArr=function(t){const e=new Array(t);return g(e,0),e}(t))}}function N(t,e,n,r){const o=new k(e,n,r);return t.__incrementalDOMData=o,o}function v(t,n){if(t.__incrementalDOMData)return t.__incrementalDOMData;const r=C(t)?t.localName:t.nodeName,o=e,l=C(t)&&null!=o?t.getAttribute(o):null,i=N(t,r,C(t)?l||n:null);return C(t)&&function(t,e){const n=t.attributes,r=n.length;if(!r)return;const o=e.getAttrsArr(r);for(let t=0,e=0;t=0?function(t,e,n){const r=e.nextSibling;let o=n;for(;null!==o&&o!==e;){const e=o.nextSibling;t.insertBefore(o,r),o=e}}(K,r,j):K.insertBefore(r,j),j=r)}function Z(t,e){return Y(t,e),G(),K}function $(){return p(!1),H(),j}function tt(){return Y("#text",null),j}function et(){return i("currentElement"),c("currentElement"),K}function nt(t,e={}){const{matches:r=I}=e;return(e,o,l)=>{const i=T,c=B,s=L,u=z,d=V,h=j,m=K,g=R;let y=!1,w=!1;B=e.ownerDocument,T=new E,R=r,z=[],V=[],j=null,K=e.parentNode,L=_(e,K),y=f(!1),w=p(!1),a(T);try{const r=t(e,o,l);return function(){if(n)throw new Error("elementOpenEnd() must be called after calling elementOpenStart().")}(),r}finally{T.notifyChanges(),B=c,T=i,R=g,z=u,V=d,j=h,K=m,L=s,f(y),p(w),a(T)}}}function rt(t){return nt(((t,e,n)=>(j=t,G(),e(n),H(),function(t,e){if(t===e)return;let n=t;const r=[];for(;n&&n!==e;)r.push(n.nodeName.toLowerCase()),n=n.parentNode;throw new Error("One or more tags were not closed:\n"+r.join("\n"))}(j,t),t)),t)}function ot(t){return nt(((t,e,n)=>{const r={nextSibling:t};let o=null,i=null;return o=t.nextSibling,i=t.previousSibling,j=r,e(n),K||console.warn("patchOuter requires the node have a parent if there is a key."),function(t,e,n,r){const o=l(t),i=l(e),c=i.nextSibling===n&&i.previousSibling===r,s=i.nextSibling===o.nextSibling&&i.previousSibling===r;if(!c&&!s&&i!==o)throw new Error("There must be exactly one top level call corresponding to the patched element.")}(r,j,o,i),K&&X(K,F(),t.nextSibling),r===j?null:j}),t)}const lt=rt(),it=ot(),ct=[];let st=0;function ut(t,e,n,r){ct.push(t),ct.push(e),ct.push(n),ct.push(r)}const at=m();function ft(t,e,n,r){const o=!t.length;let l=0;for(;lcrypto.getRandomValues(new Uint8Array(t)),r=(t,e,r)=>{let l=(2<{let a="";for(;;){let e=r(n),g=n;for(;g--;)if(a+=t[e[g]&l]||"",a.length===o)return a}}},l=(t,l=21)=>r(t,l,e),n=(t=21)=>{let e="",r=crypto.getRandomValues(new Uint8Array(t));for(;t--;){let l=63&r[t];e+=l<36?l.toString(36):l<62?(l-26).toString(36).toUpperCase():l<63?"_":"-"}return e};export{l as customAlphabet,r as customRandom,n as nanoid,e as random,t as urlAlphabet};export default null;