diff --git a/_config.ts b/_config.ts index b0c6c3a..47cefd2 100644 --- a/_config.ts +++ b/_config.ts @@ -1,6 +1,7 @@ import lume from "lume/mod.ts"; import codeHighlight from "lume/plugins/code_highlight.ts"; +import esbuild from "lume/plugins/esbuild.ts"; import feed from "lume/plugins/feed.ts"; import jsx from "lume/plugins/jsx.ts"; import minifyHTML from "lume/plugins/minify_html.ts"; @@ -10,6 +11,7 @@ const site = lume({ }); site.use(codeHighlight()); +site.use(esbuild()); site.use(jsx()); site.use(minifyHTML()); diff --git a/deno.lock b/deno.lock index 912fe92..3fca2b0 100644 --- a/deno.lock +++ b/deno.lock @@ -2838,6 +2838,10 @@ "https://deno.land/x/deno_dom@v0.1.43/src/dom/utils-types.ts": "96db30e3e4a75b194201bb9fa30988215da7f91b380fca6a5143e51ece2a8436", "https://deno.land/x/deno_dom@v0.1.43/src/dom/utils.ts": "4c6206516fb8f61f37a209c829e812c4f5a183e46d082934dd14c91bde939263", "https://deno.land/x/deno_dom@v0.1.43/src/parser.ts": "e06b2300d693e6ae7564e53dfa5c9a9e97fdb8c044c39c52c8b93b5d60860be3", + "https://deno.land/x/denoflate@1.2.1/mod.ts": "f5628e44b80b3d80ed525afa2ba0f12408e3849db817d47a883b801f9ce69dd6", + "https://deno.land/x/denoflate@1.2.1/pkg/denoflate.js": "b9f9ad9457d3f12f28b1fb35c555f57443427f74decb403113d67364e4f2caf4", + "https://deno.land/x/denoflate@1.2.1/pkg/denoflate_bg.wasm.js": "d581956245407a2115a3d7e8d85a9641c032940a8e810acbd59ca86afd34d44d", + "https://deno.land/x/esbuild@v0.19.11/mod.js": "bd4916647799a0cd046dcf4eafd6ace09d22d5898870bd062206b8b6343d5e6a", "https://deno.land/x/lume@v2.0.3/cli.ts": "ec79a3b06406f163de3c9228ffa3ab2381409fd3441056d7d67093a3259f477c", "https://deno.land/x/lume@v2.0.3/cli/build.ts": "f8c1068fd2b06df526d6d6c6e63ff7022049ce5ce1b133f2e4d74c0b3788c099", "https://deno.land/x/lume@v2.0.3/cli/create.ts": "85b0cf567e452616ea55ab9742f84957c385bb38d3f8b3fbcfd8c86dbcbc326c", @@ -2890,6 +2894,7 @@ "https://deno.land/x/lume@v2.0.3/deps/colors.ts": "659aa4bc7885d2d5f4f4c47da14b6bf04b3595535c491f27e3ce9c802934818e", "https://deno.land/x/lume@v2.0.3/deps/crypto.ts": "fb72af775aae9fb4d64b6e502a221668961a63ec7bab5678827e0cea46ded4e2", "https://deno.land/x/lume@v2.0.3/deps/dom.ts": "7eef681d9eabb49c2fb230faa75e33371ab7bbe30a03f4f4c7d6d723742f775a", + "https://deno.land/x/lume@v2.0.3/deps/esbuild.ts": "e36187b1f1d5872631d4d9a846b99478fc31efe983d4d8709106f367e4dd2874", "https://deno.land/x/lume@v2.0.3/deps/front_matter.ts": "5d023497eace3b327d4c1037a837ac8e0b6adf2b40310edbd881008176ddfaa2", "https://deno.land/x/lume@v2.0.3/deps/fs.ts": "86601d766967917078c94225d625dfb83a0f3c14dabc32ed97fe9aecb01f7b12", "https://deno.land/x/lume@v2.0.3/deps/hex.ts": "13a69c4cd15a1727b878a8c64d592301dec2ee928ca4b372c4fdac32bb92af2a", @@ -2925,6 +2930,7 @@ "https://deno.land/x/lume@v2.0.3/plugins/attributes.ts": "663afd522016ca597571820088af2e853d1341d44f11694cfc05265ea5efeda7", "https://deno.land/x/lume@v2.0.3/plugins/base_path.ts": "ab451b1c6911fbf4f3babf1e41e31500e3402e31b6a0b810418a2f3e1052ccdb", "https://deno.land/x/lume@v2.0.3/plugins/code_highlight.ts": "bd4ef987c4fd05c1c585238a490f9f56d48daefd4748b4647219575b8e1d42c0", + "https://deno.land/x/lume@v2.0.3/plugins/esbuild.ts": "a1b5bc51acdbe82b8ec179d922bc1cab898a834a38d3b75b8fdfa0920264a9e8", "https://deno.land/x/lume@v2.0.3/plugins/feed.ts": "d6573ac801319cd842e0b260fd1645450c1aeb983fe1877fcdc243ad4c66f7fc", "https://deno.land/x/lume@v2.0.3/plugins/inline.ts": "44ab46c70ff3461d33c727c571deb592126f12eb08790467ffdce2438f8c9471", "https://deno.land/x/lume@v2.0.3/plugins/json.ts": "f6429bbd865e3666ef3385fd205fcc92df02ca2c0f74f20baa5c0798a81e1642", diff --git a/site/404.tsx b/site/404.tsx index 0d5e37a..8e0d77e 100644 --- a/site/404.tsx +++ b/site/404.tsx @@ -18,13 +18,51 @@ export default (_data: Lume.Data, helpers: Lume.Helpers) => { ); return ( - + <> + + + ); }; diff --git a/site/_includes/base.vto b/site/_includes/base.vto index 5684615..bd80e8d 100644 --- a/site/_includes/base.vto +++ b/site/_includes/base.vto @@ -51,6 +51,28 @@ + {{ content }} diff --git a/site/_includes/cube.tsx b/site/_includes/cube.tsx index 860b099..dc8a9f0 100644 --- a/site/_includes/cube.tsx +++ b/site/_includes/cube.tsx @@ -12,16 +12,110 @@ interface CubeProps { } const Cube = (props: CubeProps) => ( -
-
-
{props.front || empty}
-
{props.back || empty}
-
{props.left || empty}
-
{props.right || empty}
-
{props.top || empty}
-
{props.bottom || empty}
+ <> + + +
+
+
{props.front || empty}
+
{props.back || empty}
+
{props.left || empty}
+
{props.right || empty}
+
{props.top || empty}
+
{props.bottom || empty}
+
-
+ + + ); export default Cube; diff --git a/site/_includes/text.vto b/site/_includes/text.vto index 3be7236..d36288f 100644 --- a/site/_includes/text.vto +++ b/site/_includes/text.vto @@ -2,13 +2,92 @@ layout: base.vto --- - + + +
+ + +
{{ content }}
+ +
Copyright © RGBCube
+
diff --git a/site/assets/cube.ts b/site/assets/cube.ts new file mode 100644 index 0000000..7a640b2 --- /dev/null +++ b/site/assets/cube.ts @@ -0,0 +1,241 @@ +"use strict"; + +class Vec3 { + x: number; + y: number; + z: number; + + constructor(x: number, y: number, z: number) { + this.x = x; + this.y = y; + this.z = z; + } + + static zero() { + return new Vec3(0, 0, 0); + } + + length() { + return Math.sqrt(this.x ** 2 + this.y ** 2 + this.z ** 2); + } + + scale(factor: number) { + this.x *= factor; + this.y *= factor; + this.z *= factor; + + return this; + } + + normalize() { + const length = this.length(); + + if (length != 0) { + this.x /= length; + this.y /= length; + this.z /= length; + } + + return this; + } + + static sub(v: Vec3, t: Vec3) { + return new Vec3( + v.x - t.x, + v.y - t.y, + v.z - t.z, + ); + } + + static sum(v: Vec3, t: Vec3) { + return new Vec3( + v.x + t.x, + v.y + t.y, + v.z + t.z, + ); + } +} + +class Quat { + x: number; + y: number; + z: number; + w: number; + + constructor(x: number, y: number, z: number, w: number) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + static fromAxis(axis: Vec3) { + const angle = axis.length(); + + axis.normalize(); + + const half = angle / 2; + + const sinHalf = Math.sin(half); + const cosHalf = Math.cos(half); + + const x = axis.x * sinHalf; + const y = axis.y * sinHalf; + const z = axis.z * sinHalf; + const w = cosHalf; + + return new Quat(x, y, z, w); + } + + static mul(q: Quat, r: Quat) { + return new Quat( + q.w * r.x + q.x * r.w + q.y * r.z - q.z * r.y, + q.w * r.y - q.x * r.z + q.y * r.w + q.z * r.x, + q.w * r.z + q.x * r.y - q.y * r.x + q.z * r.w, + q.w * r.w - q.x * r.x - q.y * r.y - q.z * r.z, + ); + } +} + +const friction = 3; +const sensitivity = 0.01; + +const mouse = { + down: false, + lastMove: -10000, + previous: Vec3.zero(), +}; + +const orient = { + __cube: document.querySelector(".cube"), + __value: new Quat(0, 0, 0, 1), + + set(value: Quat) { + this.__value = value; + + const q = this.__value; + + // @ts-ignore: Style will never be null. + this.__cube.style.transform = `rotate3d(${q.x}, ${q.y}, ${q.z}, ${ + Math.acos(q.w) * 2 + }rad)`; + }, + + get(): Quat { + return this.__value; + }, +}; + +let velocity = Vec3.zero(); +let impulseThisFrame = Vec3.zero(); + +const handleUp = () => { + mouse.down = false; +}; + +document.addEventListener("mouseup", handleUp); +document.addEventListener("touchend", handleUp); + +const handleDown = (event: MouseEvent | TouchEvent) => { + // Disables link dragging that occurs when spinning. + event.preventDefault(); + + mouse.down = true; + + velocity = Vec3.zero(); +}; + +document.addEventListener("mousedown", handleDown); +document.addEventListener("touchstart", handleDown); + +const handleMove = (event: MouseEvent) => { + // Disables scrolling. + event.preventDefault(); + + if (!mouse.down) return; + + const newMouse = new Vec3(event.clientX, event.clientY, 0); + + const timeDelta = (window.performance.now() - mouse.lastMove) / 1000; + + if (timeDelta > 0.1) { + // This is a fresh scroll. + mouse.previous = newMouse; + } + + const delta = Vec3.sub(newMouse, mouse.previous); + + mouse.previous = newMouse; + mouse.lastMove = window.performance.now(); + + const axis = new Vec3(-delta.y, delta.x, 0) + .normalize() + .scale(delta.length() * sensitivity); + + impulseThisFrame = Vec3.sum(impulseThisFrame, axis); + + const rotation = Quat.fromAxis(axis); + + orient.set(Quat.mul(rotation, orient.get())); +}; + +document.addEventListener("mousemove", handleMove); +document.addEventListener("touchmove", (event) => { + const delta = event.changedTouches[0]; + + // @ts-ignore: We are overriding this for it to work as a MouseEvent. + event.clientX = delta.clientX; + // @ts-ignore: + event.clientY = delta.clientY; + + handleMove(event as object as MouseEvent); +}); + +let lastUpdate = 0; + +const updateFrame = (timestamp: number) => { + if (lastUpdate == 0) lastUpdate = timestamp; + + const delta = (timestamp - lastUpdate) / 1000; + lastUpdate = timestamp; + + if (mouse.down) { + velocity = impulseThisFrame.scale(1 / delta); + impulseThisFrame = Vec3.zero(); + } else { + const decay = Math.exp(-delta * friction); + + const effectiveDelta = friction > 0 ? (1 - decay) / friction : delta; + + let theta = effectiveDelta * velocity.length(); + + velocity.x *= decay; + velocity.y *= decay; + velocity.z *= decay; + + if (friction > 0 && velocity.length() < 0.00001) { + theta += velocity.length() / friction; + + velocity.x = 0; + velocity.y = 0; + velocity.z = 0; + } + + if (window.performance.now() - mouse.lastMove > 10000) { + const impulse = new Vec3(0.7, 0.7, -0.7); + velocity = Vec3.sum(impulse.scale(effectiveDelta * 3), velocity); + } + + const axis = new Vec3(velocity.x, velocity.y, velocity.z) + .normalize() + .scale(theta); + + const rotation = Quat.fromAxis(axis); + + orient.set(Quat.mul(rotation, orient.get())); + } + + requestAnimationFrame(updateFrame); +}; + +updateFrame(0); diff --git a/site/index.tsx b/site/index.tsx index 62f27f6..43b2914 100644 --- a/site/index.tsx +++ b/site/index.tsx @@ -3,26 +3,101 @@ import Cube from "./_includes/cube.tsx"; export const title = "RGBCube"; export default ( - -
about
- - } - top={ - -
github
-
- } - right={ - -
contact
-
- } - left={ - -
blog
-
- } - /> + <> + + +
about
+ + } + top={ + +
github
+
+ } + right={ + +
contact
+
+ } + left={ + +
blog
+
+ } + /> + );