diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -2,4 +2,37 @@
 
 Hylogen is a tiny language [embedded in Haskell](https://wiki.haskell.org/Embedded_domain_specific_language) for live-coding visuals.
 
+## Setup
+```
+cabal update
+cabal install ghcid hylogen
+```
+## Usage
+
+```haskell
+-- ./Main.hs
+module Main where
+
+import Hylogen
+
+
+main = putStrLn . toGLSL $ Vec4 (a, a, a, 1)
+  where
+    a = cos(X uv * sin(time/ 10) * 10 + X mouse)
+      + sin(Y uv * sin(time / 10) * 10 + Y mouse)
+```
+
+#### run hylogen
+
+```
+hylogen Main.hs
+```
+
+#### play!
+Go to [localhost:5678](http://localhost:5678) in your browser.
+
+Changes in `Main.hs` will now be propagated in realtime to your shader!
+
+## inspiration
+- [The_Force](https://github.com/shawnlawson/The_Force).
 
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -2,18 +2,29 @@
 {-# LANGUAGE ViewPatterns #-}
 {-# LANGUAGE LambdaCase #-}
 
-import Data.Monoid
+
+import Paths_hylogen (getDataFileName)
+import Control.Monad
+import Control.Concurrent
+import qualified Data.ByteString.Char8 as BS8
+import Data.ByteString (ByteString)
+import Network (PortID(..), withSocketsDo, listenOn)
+import Network.Socket (accept, sClose)
+import Network.Socket.ByteString (sendAll)
+
+-- import Data.Monoid
 import qualified Data.Text as T
 import Network.WebSockets
 import System.Environment (getArgs)
 import System.FilePath
 import System.INotify
 import System.Process
-import System.Random
+-- import System.Random
 
 main :: IO ()
 main = getArgs >>= \case
-   [pathToWatch] ->
+   [pathToWatch] -> do
+      _ <- forkIO $ serveIndex
       runServer "127.0.0.1" 8080 $ handleConnection pathToWatch
    _ -> error "Name a file to watch!"
 
@@ -25,7 +36,7 @@
    connection <- acceptRequest pending
    (sendTextData connection . T.pack) =<< getNewSource pathToWatch
    -- withINotify $ \inotify ->
-   addWatch inotify [Modify] dirToWatch $ \case
+   _ <- addWatch inotify [Modify] dirToWatch $ \case
       Modified False (Just f) | f == fileToWatch ->
          (sendTextData connection . T.pack) =<< getNewSource pathToWatch
       _ -> return ()
@@ -38,17 +49,17 @@
 getNewSource :: FilePath -> IO String
 getNewSource pathToWatch = do
    -- TODO: more robust paths!:
-   c <- readFile pathToWatch
-   -- let (dirToWatch, fileToWatch) = splitFileName pathToWatch
-   -- c <- readProcess "runghc" [
-   --      "-i"++dirToWatch
-   --    , pathToWatch
-   --    ] ""
+   -- c <- readFile pathToWatch
+   let (dirToWatch, fileToWatch) = splitFileName pathToWatch
+   c <- readProcess "runghc" [
+        "-i"++dirToWatch
+      , pathToWatch
+      ] ""
    putStrLn c
    return c
 {-
    color <- randomRIO (0::Float, 1)
-   
+
    return $ unlines [
         "precision mediump float;"
       , "uniform float time;"
@@ -61,3 +72,24 @@
       , "}"
       ]
 -}
+
+
+serveIndex :: IO ()
+serveIndex = withSocketsDo $ do
+   htmlString <- readFile =<< getDataFileName "app/index.html"
+   sock <- listenOn $ PortNumber 5678
+   forever $ do
+      (conn, _) <- accept sock
+      _ <- forkIO $ do
+         sendAll conn $ wrapHtml $ BS8.pack htmlString
+         sClose conn
+      return ()
+
+wrapHtml :: ByteString -> ByteString
+wrapHtml bs = mconcat [
+     "HTTP/1.0 200 OK\r\nContent-Length: "
+   , BS8.pack . show $ BS8.length bs
+   , "\r\n\r\n", bs, "\r\n"
+   ]
+
+
diff --git a/app/index.html b/app/index.html
new file mode 100644
--- /dev/null
+++ b/app/index.html
@@ -0,0 +1,264 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>HYLOGEN</title>
+    <style>
+     html, body {
+         width: 100%;
+         height: 100%;
+         margin: 0;
+         padding: 0;
+         overflow: hidden;
+     }
+     body {
+         display: flex;
+         justify-content: center;
+         align-items: center;
+     }
+     #canvas {
+         width: 100vmax;
+         height: 100vmax;
+         background: white;
+     }
+     #landing {
+         width: 100vw;
+         height: 100vh;
+         position: fixed;
+         right: 0;
+         top: 0;
+         color: white;
+         display: flex;
+         justify-content: center;
+         align-items: center;
+         font-family: arial;
+         line-height: 10em;
+         pointer-events: none;
+     }
+     #bg {
+         background: black;
+         width: 100vw;
+         height: 100vh;
+         position: fixed;
+         right: 0;
+         top: 0;
+         pointer-events: none;
+     }
+
+     #title {
+         text-transform: uppercase;
+         font-style: italic;
+         font-weight: bold;
+         font-size: 15vw;
+         letter-spacing: 0.1em;
+         text-shadow: 0 0 0.2em white;
+         pointer-events: auto;
+     }
+     @keyframes fadeOut {
+         0% {opacity: 1;}
+         100% {opacity: 0;} 
+     } 
+     .removing {
+         -webkit-animation: fadeOut 1000ms;
+         -moz-animation: fadeOut 1000ms;
+         animation: fadeOut 1000ms;
+     }
+     a {
+         text-decoration: inherit;
+     }
+
+     a:visited {
+         color: inherit;
+     }
+    </style>
+  </head>
+  <body>
+    <div id="bg"> </div>
+    <div id="landing">
+      <div id="title">
+        <a href="https://github.com/sleexyz/hylogen">
+          Hylogen
+        </a>
+      </div>
+    </div>
+
+    <canvas id="canvas"></canvas>
+    <script>
+const vsSource = `
+attribute vec3 aPosition;
+varying vec3 uv;
+void main() {
+  gl_Position = vec4(aPosition, 1.0);
+  uv = aPosition;
+}
+`;
+
+const initialFsSource = `
+precision mediump float;
+uniform float time;
+uniform vec3 mouse;
+const float PI = 3.141592653589793238462643383;
+varying vec3 uv;
+
+void main() {
+    gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
+}
+`;
+
+
+function loadProgram (gl, state, vsSource, fsSource) {
+
+  // compileShader :: (gl, source, shaderType) -> Shader
+  // throws Error on compilation error
+
+  function compileShader (gl, source, shaderType) {
+    //assert(shaderType === gl.FRAGMENT_SHADER || shaderType === g.VERTEXT_SHADER);
+
+    let shader = gl.createShader(shaderType);
+
+    gl.shaderSource(shader, source);
+    gl.compileShader(shader);
+
+
+    let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
+    if (!success) {
+      throw "could not compile shader:" + gl.getShaderInfoLog(shader);
+    }
+
+    return shader;
+  };
+
+
+
+  let vs = compileShader(gl, vsSource, gl.VERTEX_SHADER);
+  let fs = compileShader(gl, fsSource, gl.FRAGMENT_SHADER);
+
+
+  let program = gl.createProgram();
+
+  gl.attachShader(program, vs);
+  gl.attachShader(program, fs);
+
+  gl.linkProgram(program);
+
+  let success = gl.getProgramParameter(program, gl.LINK_STATUS);
+  if (!success) {
+    throw ("program failed to link:" + gl.getProgramInfoLog(program));
+  }
+
+  gl.useProgram(program);
+
+
+  // Create a square as a strip of two triangles.
+  gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
+  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ -1,1,0, 1,1,0, -1,-1,0, 1,-1,0 ]), gl.STATIC_DRAW);
+
+  // Assign attribute aPosition to each of the square's vertices.
+  gl.aPosition = gl.getAttribLocation(program, "aPosition");
+  gl.enableVertexAttribArray(gl.aPosition);
+  gl.vertexAttribPointer(gl.aPosition, 3, gl.FLOAT, false, 0, 0);
+
+  // remember the address within the fragment shader of each of my uniforms variables
+  gl.time = gl.getUniformLocation(program, "time");
+  gl.mouse = gl.getUniformLocation(program, "mouse");
+
+  draw(gl, state);
+
+  if (state.animationFrameRequest === null) {
+    state.animationFrameRequest = requestAnimationFrame(() => animate(gl, state));
+  }
+}
+
+function draw (gl, state) {
+
+  gl.uniform1f(gl.time, (new Date().getTime() / 1000 - state.time0));
+  gl.uniform3f(gl.mouse, state.mouse.x, state.mouse.y, state.mouse.z);
+
+  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+};
+
+function animate (gl, state) {
+  draw(gl, state);
+  state.animationFrameRequest = requestAnimationFrame(() => animate(gl, state));
+};
+
+
+
+function setupState (state, canvas){
+  function setMouse (event, z) {
+    let r = event.target.getBoundingClientRect();
+    state.mouse.x = (event.clientX - r.left) / (r.right - r.left) * 2 -1;
+    state.mouse.y = (event.clientY - r.bottom) / (r.top - r.bottom) * 2 - 1;
+
+    if (z !== undefined) {
+      state.mouse.z = z;
+    }
+  };
+
+  state.animationFrameRequest = null;
+
+  state.time0 = new Date() / 1000;
+
+
+  canvas.onmousedown = (event) => setMouse(event, 1);
+  canvas.onmousemove = (event) => setMouse(event);
+  canvas.onmouseup = (event) => setMouse(event, 0);
+
+  state.mouse = {x: 0, y: 0, z: 0};
+
+  // TODO: implement Audio
+}
+
+
+
+
+// stateful variables
+
+const canvas = document.getElementById("canvas");
+canvas.width = Math.max(window.innerHeight, window.innerWidth);
+canvas.height = Math.max(window.innerHeight, window.innerWidth);
+
+const gl = canvas.getContext("webgl");
+
+const state = {};
+
+setupState(state, canvas);
+
+
+loadProgram (gl, state,  vsSource, initialFsSource);
+
+
+function fadeOut(elem) {
+  elem.className = "removing";
+  window.setTimeout(function() {
+    elem.remove();
+  }, 1000);
+}
+
+function connectToHylogen() {
+  const wsConn = new WebSocket("ws://localhost:8080/");
+
+  wsConn.onopen = function() {
+    const landing = document.getElementById("landing");
+    if (landing) {
+      fadeOut(document.getElementById("bg"));
+      window.setTimeout(() => {fadeOut(landing);}, 500);
+    }
+    console.log('websocket opened');
+
+  };
+
+  wsConn.onclose = function() {
+    console.log('websocket closed');
+    window.setTimeout(connectToHylogen, 100);
+  };
+
+  wsConn.onmessage = function (m) {
+    console.log(m.data);
+    loadProgram(gl, state,  vsSource, m.data);
+  };
+}
+
+connectToHylogen();
+    </script>
+  </body>
+</html>
diff --git a/hylogen.cabal b/hylogen.cabal
--- a/hylogen.cabal
+++ b/hylogen.cabal
@@ -1,5 +1,5 @@
 name:                hylogen
-version:             0.1.0.2
+version:             0.1.0.3
 synopsis:            a tiny EDSL for live-coding fragment shaders
 description:         a tiny EDSL for live-coding fragment shaders
 homepage:            https://github.com/sleexyz/hylogen
@@ -9,6 +9,7 @@
 category:            Graphics
 build-type:          Simple
 extra-source-files:  README.md
+data-files:          app/index.html
 cabal-version:       >=1.10
 stability:           experimental
 
@@ -24,11 +25,13 @@
 executable hylogen
   main-is:             Main.hs
   build-depends:       base
-                     , websockets
-                     , hinotify
-                     , text
+                     , bytestring
                      , filepath
-                     , random
+                     , hinotify
+                     , network
                      , process
+                     , random
+                     , text
+                     , websockets
   hs-source-dirs:      app
   default-language:    Haskell2010
diff --git a/src/Hylogen/Globals.hs b/src/Hylogen/Globals.hs
--- a/src/Hylogen/Globals.hs
+++ b/src/Hylogen/Globals.hs
@@ -7,7 +7,7 @@
 
 -- | Vec1:
 
-pi = V1u "pi"
+time :: Vec1
 time = V1u "time"
 -- rand_ = V1uop "rand"
 
@@ -20,6 +20,7 @@
 
 -- | Vec2:
 
+uv, mouse :: Vec2
 uv = V2u "uv"
 mouse = V2u "mouse"
 
