diff --git a/app-proto/inversive-display/.gitignore b/app-proto/inversive-display/.gitignore
new file mode 100644
index 0000000..238273d
--- /dev/null
+++ b/app-proto/inversive-display/.gitignore
@@ -0,0 +1,3 @@
+target
+dist
+Cargo.lock
\ No newline at end of file
diff --git a/app-proto/inversive-display/Cargo.toml b/app-proto/inversive-display/Cargo.toml
new file mode 100644
index 0000000..42d4e17
--- /dev/null
+++ b/app-proto/inversive-display/Cargo.toml
@@ -0,0 +1,37 @@
+[package]
+name = "inversive-display"
+version = "0.1.0"
+authors = ["Aaron"]
+edition = "2021"
+
+[features]
+default = ["console_error_panic_hook"]
+
+[dependencies]
+js-sys = "0.3.70"
+sycamore = "0.9.0-beta.2"
+
+# The `console_error_panic_hook` crate provides better debugging of panics by
+# logging them with `console.error`. This is great for development, but requires
+# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
+# code size when deploying.
+console_error_panic_hook = { version = "0.1.7", optional = true }
+
+[dependencies.web-sys]
+version = "0.3.69"
+features = [
+ 'HtmlCanvasElement',
+ 'WebGl2RenderingContext',
+ 'WebGlBuffer',
+ 'WebGlProgram',
+ 'WebGlShader',
+ 'WebGlVertexArrayObject',
+ 'Window'
+]
+
+[dev-dependencies]
+wasm-bindgen-test = "0.3.34"
+
+[profile.release]
+opt-level = "s" # optimize for small code size
+debug = true # include debug symbols
diff --git a/app-proto/inversive-display/index.html b/app-proto/inversive-display/index.html
new file mode 100644
index 0000000..9c45180
--- /dev/null
+++ b/app-proto/inversive-display/index.html
@@ -0,0 +1,9 @@
+
+
+
+
+ Inversive display
+
+
+
+
diff --git a/app-proto/inversive-display/main.css b/app-proto/inversive-display/main.css
new file mode 100644
index 0000000..f79e62c
--- /dev/null
+++ b/app-proto/inversive-display/main.css
@@ -0,0 +1,23 @@
+body {
+ margin-left: 20px;
+ margin-top: 20px;
+ color: #fcfcfc;
+ background-color: #202020;
+}
+
+#app {
+ display: flex;
+ flex-direction: column;
+ width: 600px;
+}
+
+canvas {
+ float: left;
+ background-color: #020202;
+ border-radius: 10px;
+ margin-top: 5px;
+}
+
+input {
+ margin-top: 5px;
+}
\ No newline at end of file
diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs
new file mode 100644
index 0000000..6730a39
--- /dev/null
+++ b/app-proto/inversive-display/src/main.rs
@@ -0,0 +1,152 @@
+// based on the WebGL example in the `wasm-bindgen` guide
+//
+// https://rustwasm.github.io/wasm-bindgen/examples/webgl.html
+//
+
+extern crate js_sys;
+use std::f64::consts::PI as PI;
+use sycamore::{prelude::*, rt::{JsCast, JsValue}};
+use web_sys::{console, WebGl2RenderingContext, WebGlProgram, WebGlShader};
+
+fn compile_shader(
+ context: &WebGl2RenderingContext,
+ shader_type: u32,
+ source: &str,
+) -> WebGlShader {
+ let shader = context.create_shader(shader_type).unwrap();
+ context.shader_source(&shader, source);
+ context.compile_shader(&shader);
+ shader
+}
+
+fn main() {
+ // set up a config option that forwards panic messages to `console.error`
+ #[cfg(feature = "console_error_panic_hook")]
+ console_error_panic_hook::set_once();
+
+ sycamore::render(|| {
+ let tip = create_signal(0.0);
+ let display = create_node_ref();
+
+ on_mount(move || {
+ // get the display canvas
+ let canvas = display
+ .get::()
+ .unchecked_into::();
+ let ctx = canvas
+ .get_context("webgl2")
+ .unwrap()
+ .unwrap()
+ .dyn_into::()
+ .unwrap();
+
+ // load the vertex and fragment shaders
+ let vertex_shader = compile_shader(
+ &ctx,
+ WebGl2RenderingContext::VERTEX_SHADER,
+ r##"#version 300 es
+
+ in vec4 position;
+
+ void main() {
+ gl_Position = position;
+ }
+ "##,
+ );
+ let fragment_shader = compile_shader(
+ &ctx,
+ WebGl2RenderingContext::FRAGMENT_SHADER,
+ r##"#version 300 es
+
+ precision highp float;
+ out vec4 outColor;
+
+ void main() {
+ outColor = vec4(gl_FragCoord.xy / 600., 1., 1.);
+ }
+ "##,
+ );
+ let program = ctx.create_program().unwrap();
+ ctx.attach_shader(&program, &vertex_shader);
+ ctx.attach_shader(&program, &fragment_shader);
+ ctx.link_program(&program);
+ let link_status = ctx
+ .get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS)
+ .as_bool()
+ .unwrap();
+ let link_msg = if link_status {
+ "Linked successfully"
+ } else {
+ "Linking failed"
+ };
+ console::log_1(&JsValue::from(link_msg));
+ ctx.use_program(Some(&program));
+
+ // set up a repainting routine
+ create_effect(move || {
+ // list the vertices
+ let vertices: [f32; 9] = [
+ -0.9, 0.9, 0.0,
+ -0.9, -0.9, 0.0,
+ 0.9*(tip.get() as f32), 0.0, 0.0
+ ];
+
+ // create a vertex buffer and bind it to ARRAY_BUFFER
+ let position_attribute_location = ctx.get_attrib_location(&program, "position");
+ let buffer = ctx.create_buffer().unwrap();
+ ctx.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&buffer));
+
+ // load the vertex list into the vertex buffer. the function
+ // `Float32Array::view` creates a raw view into our module's
+ // `WebAssembly.Memory` buffer. allocating more memory will
+ // change the buffer, invalidating the view. that means we have
+ // to make sure we don't allocate any memory until the view is
+ // dropped
+ unsafe {
+ ctx.buffer_data_with_array_buffer_view(
+ WebGl2RenderingContext::ARRAY_BUFFER,
+ &js_sys::Float32Array::view(&vertices),
+ WebGl2RenderingContext::STATIC_DRAW,
+ );
+ }
+
+ // create a vertex array and bind it to the graphics context
+ let vtx_array_obj = ctx.create_vertex_array().unwrap();
+ ctx.bind_vertex_array(Some(&vtx_array_obj));
+
+ // use whatever is bound to ARRAY_BUFFER---here, the vertex
+ // buffer created above---as the vertex attribute array
+ //
+ // https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer
+ //
+ ctx.vertex_attrib_pointer_with_i32(
+ position_attribute_location as u32,
+ 3, // dimension
+ WebGl2RenderingContext::FLOAT, // type
+ false, // normalized?
+ 0, // stride
+ 0, // offset
+ );
+ ctx.enable_vertex_attrib_array(position_attribute_location as u32);
+
+ // clear the screen and draw the scene
+ let vert_count = (vertices.len() / 3) as i32;
+ ctx.clear_color(0.0, 0.0, 0.0, 1.0);
+ ctx.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);
+ ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, vert_count);
+ });
+ });
+
+ view! {
+ div(id="app") {
+ canvas(ref=display, width="600", height="600")
+ input(
+ type="range",
+ max=1.0,
+ step=0.01,
+ bind:valueAsNumber=tip
+ )
+ }
+ }
+ });
+}
\ No newline at end of file