feat: use webgl shaders instead of wasm
This commit is contained in:
parent
38efffa9b9
commit
51dc6c9ee6
24 changed files with 1712 additions and 1607 deletions
20
README.md
20
README.md
|
|
@ -2,22 +2,22 @@
|
||||||
|
|
||||||
Personal site for Jet Pham.
|
Personal site for Jet Pham.
|
||||||
|
|
||||||
The site is a small Vite app with a terminal-style UI, ANSI-rendered text, and a Rust/WebAssembly Conway's Game of Life background.
|
The site is a small Vite app with a terminal-style UI, ANSI-rendered text, and a WebGL2 Conway's Game of Life background.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- ASCII/ANSI-inspired visual style with the IBM VGA font
|
- ASCII/ANSI-inspired visual style with the IBM VGA font
|
||||||
- Conway's Game of Life running in the background via Rust + WebAssembly
|
- Conway's Game of Life running in the background via WebGL2
|
||||||
- Q+A page backed by the site API
|
- Q+A page backed by the site API
|
||||||
- Single-file oriented frontend build with Vite
|
- Single-file oriented frontend build with Vite
|
||||||
- Reduced-motion aware background controls with a static fallback mode
|
- Fullscreen GPU blur/composite for the frosted panel effect
|
||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
- Vite
|
- Vite
|
||||||
- TypeScript
|
- TypeScript
|
||||||
- Tailwind CSS v4
|
- Tailwind CSS v4
|
||||||
- Rust + WebAssembly
|
- WebGL2
|
||||||
- npm
|
- npm
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
@ -25,9 +25,6 @@ The site is a small Vite app with a terminal-style UI, ANSI-rendered text, and a
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Node.js + npm
|
- Node.js + npm
|
||||||
- Rust
|
|
||||||
- `wasm-pack`
|
|
||||||
- `wasm-opt` (used by `build:wasm`)
|
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
|
|
||||||
|
|
@ -35,12 +32,6 @@ The site is a small Vite app with a terminal-style UI, ANSI-rendered text, and a
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build the WASM package
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build:wasm
|
|
||||||
```
|
|
||||||
|
|
||||||
### Start the dev server
|
### Start the dev server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -63,10 +54,9 @@ npm run build
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/ frontend app
|
src/ frontend app
|
||||||
cgol/ Rust + WASM Conway's Game of Life module
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- The homepage and Q+A page are the intended public pages.
|
- The homepage and Q+A page are the intended public pages.
|
||||||
- If WebAssembly fails or motion is disabled, the site falls back to a static background treatment.
|
- The background renderer targets WebGL2 and falls back to the site shell background if WebGL2 is unavailable.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
[build]
|
|
||||||
target = "wasm32-unknown-unknown"
|
|
||||||
|
|
||||||
[target.wasm32-unknown-unknown]
|
|
||||||
rustflags = ["-C", "link-arg=--strip-all"]
|
|
||||||
5
cgol/.gitignore
vendored
5
cgol/.gitignore
vendored
|
|
@ -1,5 +0,0 @@
|
||||||
/target
|
|
||||||
**/*.rs.bk
|
|
||||||
bin/
|
|
||||||
pkg/
|
|
||||||
wasm-pack.log
|
|
||||||
3
cgol/.vscode/settings.json
vendored
3
cgol/.vscode/settings.json
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"rust-analyzer.cargo.target": "wasm32-unknown-unknown",
|
|
||||||
}
|
|
||||||
427
cgol/Cargo.lock
generated
427
cgol/Cargo.lock
generated
|
|
@ -1,427 +0,0 @@
|
||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "async-trait"
|
|
||||||
version = "0.1.89"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bumpalo"
|
|
||||||
version = "3.20.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cast"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cc"
|
|
||||||
version = "1.2.56"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
|
||||||
dependencies = [
|
|
||||||
"find-msvc-tools",
|
|
||||||
"shlex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cgol"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"console_error_panic_hook",
|
|
||||||
"js-sys",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-test",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "console_error_panic_hook"
|
|
||||||
version = "0.1.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "find-msvc-tools"
|
|
||||||
version = "0.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-core"
|
|
||||||
version = "0.3.32"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-task"
|
|
||||||
version = "0.3.32"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-util"
|
|
||||||
version = "0.3.32"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"futures-task",
|
|
||||||
"pin-project-lite",
|
|
||||||
"slab",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "js-sys"
|
|
||||||
version = "0.3.91"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
|
|
||||||
dependencies = [
|
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libm"
|
|
||||||
version = "0.2.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "minicov"
|
|
||||||
version = "0.3.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"walkdir",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nu-ansi-term"
|
|
||||||
version = "0.50.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-traits"
|
|
||||||
version = "0.2.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"libm",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell"
|
|
||||||
version = "1.21.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "oorandom"
|
|
||||||
version = "11.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-project-lite"
|
|
||||||
version = "0.2.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.106"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.45"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustversion"
|
|
||||||
version = "1.0.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "same-file"
|
|
||||||
version = "1.0.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
||||||
dependencies = [
|
|
||||||
"serde_core",
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_core"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.149"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"memchr",
|
|
||||||
"serde",
|
|
||||||
"serde_core",
|
|
||||||
"zmij",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "shlex"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "slab"
|
|
||||||
version = "0.4.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.117"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.24"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "walkdir"
|
|
||||||
version = "2.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
|
||||||
dependencies = [
|
|
||||||
"same-file",
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen"
|
|
||||||
version = "0.2.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"once_cell",
|
|
||||||
"rustversion",
|
|
||||||
"wasm-bindgen-macro",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-futures"
|
|
||||||
version = "0.4.64"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"futures-util",
|
|
||||||
"js-sys",
|
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro"
|
|
||||||
version = "0.2.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
|
|
||||||
dependencies = [
|
|
||||||
"quote",
|
|
||||||
"wasm-bindgen-macro-support",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro-support"
|
|
||||||
version = "0.2.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
|
|
||||||
dependencies = [
|
|
||||||
"bumpalo",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-shared"
|
|
||||||
version = "0.2.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-test"
|
|
||||||
version = "0.3.64"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6311c867385cc7d5602463b31825d454d0837a3aba7cdb5e56d5201792a3f7fe"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"cast",
|
|
||||||
"js-sys",
|
|
||||||
"libm",
|
|
||||||
"minicov",
|
|
||||||
"nu-ansi-term",
|
|
||||||
"num-traits",
|
|
||||||
"oorandom",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"wasm-bindgen-test-macro",
|
|
||||||
"wasm-bindgen-test-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-test-macro"
|
|
||||||
version = "0.3.64"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "67008cdde4769831958536b0f11b3bdd0380bde882be17fff9c2f34bb4549abd"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-test-shared"
|
|
||||||
version = "0.2.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cfe29135b180b72b04c74aa97b2b4a2ef275161eff9a6c7955ea9eaedc7e1d4e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "web-sys"
|
|
||||||
version = "0.3.91"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
|
|
||||||
dependencies = [
|
|
||||||
"js-sys",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-util"
|
|
||||||
version = "0.1.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-link"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.61.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zmij"
|
|
||||||
version = "1.0.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "cgol"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["jet"]
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
wasm-bindgen = "0.2"
|
|
||||||
js-sys = "0.3"
|
|
||||||
web-sys = { version = "0.3", features = [
|
|
||||||
"CanvasRenderingContext2d",
|
|
||||||
"Document",
|
|
||||||
"HtmlCanvasElement",
|
|
||||||
"Window",
|
|
||||||
"MouseEvent",
|
|
||||||
"Element",
|
|
||||||
"EventTarget",
|
|
||||||
"Performance",
|
|
||||||
"TouchEvent",
|
|
||||||
"Touch",
|
|
||||||
"TouchList",
|
|
||||||
"ImageData",
|
|
||||||
] }
|
|
||||||
console_error_panic_hook = { version = "0.1", optional = true }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["console_error_panic_hook"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
wasm-bindgen-test = "0.3"
|
|
||||||
|
|
||||||
[package.metadata.wasm-pack.profile.release]
|
|
||||||
wasm-opt = false
|
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
debug = "line-tables-only"
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
debug = 0
|
|
||||||
opt-level = 3
|
|
||||||
lto = true
|
|
||||||
codegen-units = 1
|
|
||||||
panic = "abort"
|
|
||||||
strip = true
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
<h1><code>wasm-pack-template</code></h1>
|
|
||||||
|
|
||||||
<strong>A template for kick starting a Rust and WebAssembly project using <a href="https://github.com/rustwasm/wasm-pack">wasm-pack</a>.</strong>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://travis-ci.org/rustwasm/wasm-pack-template"><img src="https://img.shields.io/travis/rustwasm/wasm-pack-template.svg?style=flat-square" alt="Build Status" /></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>
|
|
||||||
<a href="https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html">Tutorial</a>
|
|
||||||
<span> | </span>
|
|
||||||
<a href="https://discordapp.com/channels/442252698964721669/443151097398296587">Chat</a>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<sub>Built with 🦀🕸 by <a href="https://rustwasm.github.io/">The Rust and WebAssembly Working Group</a></sub>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## About
|
|
||||||
|
|
||||||
[**📚 Read this template tutorial! 📚**][template-docs]
|
|
||||||
|
|
||||||
This template is designed for compiling Rust libraries into WebAssembly and
|
|
||||||
publishing the resulting package to NPM.
|
|
||||||
|
|
||||||
Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other
|
|
||||||
templates and usages of `wasm-pack`.
|
|
||||||
|
|
||||||
[tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html
|
|
||||||
[template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html
|
|
||||||
|
|
||||||
## 🚴 Usage
|
|
||||||
|
|
||||||
### 🐑 Use `cargo generate` to Clone this Template
|
|
||||||
|
|
||||||
[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate)
|
|
||||||
|
|
||||||
```
|
|
||||||
cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project
|
|
||||||
cd my-project
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🛠️ Build with `wasm-pack build`
|
|
||||||
|
|
||||||
```
|
|
||||||
wasm-pack build
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔬 Test in Headless Browsers with `wasm-pack test`
|
|
||||||
|
|
||||||
```
|
|
||||||
wasm-pack test --headless --firefox
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🎁 Publish to NPM with `wasm-pack publish`
|
|
||||||
|
|
||||||
```
|
|
||||||
wasm-pack publish
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔋 Batteries Included
|
|
||||||
|
|
||||||
* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating
|
|
||||||
between WebAssembly and JavaScript.
|
|
||||||
* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook)
|
|
||||||
for logging panic messages to the developer console.
|
|
||||||
* `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Licensed under either of
|
|
||||||
|
|
||||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
|
||||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
|
||||||
|
|
||||||
at your option.
|
|
||||||
|
|
||||||
### Contribution
|
|
||||||
|
|
||||||
Unless you explicitly state otherwise, any contribution intentionally
|
|
||||||
submitted for inclusion in the work by you, as defined in the Apache-2.0
|
|
||||||
license, shall be dual licensed as above, without any additional terms or
|
|
||||||
conditions.
|
|
||||||
483
cgol/src/lib.rs
483
cgol/src/lib.rs
|
|
@ -1,483 +0,0 @@
|
||||||
mod utils;
|
|
||||||
|
|
||||||
use std::cell::Cell;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
use wasm_bindgen::Clamped;
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
|
|
||||||
use js_sys::Math;
|
|
||||||
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData, Window};
|
|
||||||
|
|
||||||
const CELL_SIZE: u32 = 10;
|
|
||||||
const TICK_MS: f64 = 1000.0 / 60.0;
|
|
||||||
const HUE_PERIOD_MS: f64 = 3000.0;
|
|
||||||
const STILL_STEPS: u32 = 5;
|
|
||||||
|
|
||||||
/// Cells: 0 = dead, 1-255 = alive with that hue value.
|
|
||||||
const DEAD: u8 = 0;
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn safe_hue(h: u8) -> u8 {
|
|
||||||
if h == DEAD {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Lookup tables (computed once) ──────────────────────────────────
|
|
||||||
|
|
||||||
struct Luts {
|
|
||||||
/// RGBA for each hue. Index 0 = black (dead cell).
|
|
||||||
rgb: [[u8; 4]; 256],
|
|
||||||
sin: [f32; 256],
|
|
||||||
cos: [f32; 256],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Luts {
|
|
||||||
fn new() -> Self {
|
|
||||||
let mut t = Self {
|
|
||||||
rgb: [[0, 0, 0, 255]; 256],
|
|
||||||
sin: [0.0; 256],
|
|
||||||
cos: [0.0; 256],
|
|
||||||
};
|
|
||||||
for i in 1..256u16 {
|
|
||||||
let h = i as f32 / 255.0 * 6.0;
|
|
||||||
let x = 1.0 - (h % 2.0 - 1.0).abs();
|
|
||||||
let (r, g, b) = match h as u32 {
|
|
||||||
0 => (1.0f32, x, 0.0),
|
|
||||||
1 => (x, 1.0, 0.0),
|
|
||||||
2 => (0.0, 1.0, x),
|
|
||||||
3 => (0.0, x, 1.0),
|
|
||||||
4 => (x, 0.0, 1.0),
|
|
||||||
_ => (1.0, 0.0, x),
|
|
||||||
};
|
|
||||||
t.rgb[i as usize] = [(r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8, 255];
|
|
||||||
}
|
|
||||||
for i in 0..256 {
|
|
||||||
let rad = i as f32 * std::f32::consts::TAU / 256.0;
|
|
||||||
t.sin[i] = rad.sin();
|
|
||||||
t.cos[i] = rad.cos();
|
|
||||||
}
|
|
||||||
t
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn mix(&self, hues: &[u8]) -> u8 {
|
|
||||||
let mut s = 0.0f32;
|
|
||||||
let mut c = 0.0f32;
|
|
||||||
for &h in hues {
|
|
||||||
s += self.sin[h as usize];
|
|
||||||
c += self.cos[h as usize];
|
|
||||||
}
|
|
||||||
safe_hue(
|
|
||||||
(s.atan2(c).rem_euclid(std::f32::consts::TAU) * 256.0 / std::f32::consts::TAU) as u8,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Grid ───────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
struct Grid {
|
|
||||||
w: u32,
|
|
||||||
h: u32,
|
|
||||||
cells: Vec<u8>,
|
|
||||||
buf: Vec<u8>,
|
|
||||||
hues: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Grid {
|
|
||||||
fn new(w: u32, h: u32) -> Self {
|
|
||||||
let n = (w * h) as usize;
|
|
||||||
let mut g = Self {
|
|
||||||
w,
|
|
||||||
h,
|
|
||||||
cells: vec![DEAD; n],
|
|
||||||
buf: vec![DEAD; n],
|
|
||||||
hues: Vec::with_capacity(8),
|
|
||||||
};
|
|
||||||
g.randomize();
|
|
||||||
g
|
|
||||||
}
|
|
||||||
|
|
||||||
fn randomize(&mut self) {
|
|
||||||
for c in &mut self.cells {
|
|
||||||
*c = if Math::random() < 0.5 {
|
|
||||||
safe_hue((Math::random() * 255.0) as u8)
|
|
||||||
} else {
|
|
||||||
DEAD
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tick(&mut self, luts: &Luts) {
|
|
||||||
let (w, h) = (self.w, self.h);
|
|
||||||
|
|
||||||
for row in 0..h {
|
|
||||||
let n_off = (if row == 0 { h - 1 } else { row - 1 }) * w;
|
|
||||||
let r_off = row * w;
|
|
||||||
let s_off = (if row == h - 1 { 0 } else { row + 1 }) * w;
|
|
||||||
|
|
||||||
for col in 0..w {
|
|
||||||
let we = if col == 0 { w - 1 } else { col - 1 };
|
|
||||||
let ea = if col == w - 1 { 0 } else { col + 1 };
|
|
||||||
|
|
||||||
// Read all 8 neighbors
|
|
||||||
let ns = [
|
|
||||||
self.cells[(n_off + we) as usize],
|
|
||||||
self.cells[(n_off + col) as usize],
|
|
||||||
self.cells[(n_off + ea) as usize],
|
|
||||||
self.cells[(r_off + we) as usize],
|
|
||||||
self.cells[(r_off + ea) as usize],
|
|
||||||
self.cells[(s_off + we) as usize],
|
|
||||||
self.cells[(s_off + col) as usize],
|
|
||||||
self.cells[(s_off + ea) as usize],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Count alive neighbors (branchless)
|
|
||||||
let count = ns.iter().fold(0u8, |a, &v| a + (v != DEAD) as u8);
|
|
||||||
|
|
||||||
let i = (r_off + col) as usize;
|
|
||||||
let cell = self.cells[i];
|
|
||||||
let alive = cell != DEAD;
|
|
||||||
|
|
||||||
self.buf[i] = if alive {
|
|
||||||
if count == 2 || count == 3 {
|
|
||||||
cell
|
|
||||||
} else {
|
|
||||||
DEAD
|
|
||||||
}
|
|
||||||
} else if count == 3 {
|
|
||||||
// Only collect hues for births
|
|
||||||
self.hues.clear();
|
|
||||||
for &v in &ns {
|
|
||||||
if v != DEAD {
|
|
||||||
self.hues.push(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
luts.mix(&self.hues)
|
|
||||||
} else {
|
|
||||||
DEAD
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::mem::swap(&mut self.cells, &mut self.buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stamp(&mut self, cr: i32, cc: i32, half: i32, hue: u8) {
|
|
||||||
let (h, w) = (self.h as i32, self.w as i32);
|
|
||||||
let hue = safe_hue(hue);
|
|
||||||
for dr in -half..=half {
|
|
||||||
for dc in -half..=half {
|
|
||||||
let r = (cr + dr).rem_euclid(h) as u32;
|
|
||||||
let c = (cc + dc).rem_euclid(w) as u32;
|
|
||||||
self.cells[(r * self.w + c) as usize] = hue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── App state ──────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
struct App {
|
|
||||||
win: Window,
|
|
||||||
canvas: HtmlCanvasElement,
|
|
||||||
ctx: CanvasRenderingContext2d,
|
|
||||||
luts: Luts,
|
|
||||||
grid: Grid,
|
|
||||||
pixels: Vec<u8>,
|
|
||||||
cw: u32,
|
|
||||||
ch: u32,
|
|
||||||
crow: i32,
|
|
||||||
ccol: i32,
|
|
||||||
cursor_on: bool,
|
|
||||||
last_tick: f64,
|
|
||||||
dirty: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
static APP_STATE: RefCell<Option<Rc<RefCell<App>>>> = const { RefCell::new(None) };
|
|
||||||
static RUNNING: Cell<bool> = const { Cell::new(false) };
|
|
||||||
static LISTENERS_READY: Cell<bool> = const { Cell::new(false) };
|
|
||||||
static RAF_CLOSURE: RefCell<Option<Closure<dyn FnMut()>>> = const { RefCell::new(None) };
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
fn new() -> Result<Self, JsValue> {
|
|
||||||
utils::set_panic_hook();
|
|
||||||
let win = web_sys::window().ok_or("no window")?;
|
|
||||||
let doc = win.document().ok_or("no document")?;
|
|
||||||
let canvas: HtmlCanvasElement = doc
|
|
||||||
.get_element_by_id("canvas")
|
|
||||||
.ok_or("no canvas")?
|
|
||||||
.dyn_into()?;
|
|
||||||
let ctx: CanvasRenderingContext2d =
|
|
||||||
canvas.get_context("2d")?.ok_or("no 2d ctx")?.dyn_into()?;
|
|
||||||
ctx.set_image_smoothing_enabled(false);
|
|
||||||
|
|
||||||
let mut app = Self {
|
|
||||||
win,
|
|
||||||
canvas,
|
|
||||||
ctx,
|
|
||||||
luts: Luts::new(),
|
|
||||||
grid: Grid::new(1, 1),
|
|
||||||
pixels: Vec::new(),
|
|
||||||
cw: 0,
|
|
||||||
ch: 0,
|
|
||||||
crow: 0,
|
|
||||||
ccol: 0,
|
|
||||||
cursor_on: false,
|
|
||||||
last_tick: 0.0,
|
|
||||||
dirty: true,
|
|
||||||
};
|
|
||||||
app.resize();
|
|
||||||
Ok(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn now(&self) -> f64 {
|
|
||||||
self.win.performance().unwrap().now()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resize(&mut self) {
|
|
||||||
let vw = self.win.inner_width().unwrap().as_f64().unwrap();
|
|
||||||
let vh = self.win.inner_height().unwrap().as_f64().unwrap();
|
|
||||||
let cols = (vw as u32 / CELL_SIZE).max(1);
|
|
||||||
let rows = (vh as u32 / CELL_SIZE).max(1);
|
|
||||||
|
|
||||||
self.cw = cols;
|
|
||||||
self.ch = rows;
|
|
||||||
self.canvas.set_width(cols);
|
|
||||||
self.canvas.set_height(rows);
|
|
||||||
self.canvas
|
|
||||||
.dyn_ref::<web_sys::Element>()
|
|
||||||
.unwrap()
|
|
||||||
.set_attribute(
|
|
||||||
"style",
|
|
||||||
&format!(
|
|
||||||
"position:fixed;inset:0;width:{vw}px;height:{vh}px;image-rendering:pixelated"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
self.ctx.set_image_smoothing_enabled(false);
|
|
||||||
|
|
||||||
self.grid = Grid::new(cols, rows);
|
|
||||||
self.pixels = vec![0u8; (cols * rows * 4) as usize];
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(&mut self) {
|
|
||||||
if !self.dirty {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.dirty = false;
|
|
||||||
|
|
||||||
let cells = &self.grid.cells;
|
|
||||||
let rgb = &self.luts.rgb;
|
|
||||||
let px = &mut self.pixels;
|
|
||||||
|
|
||||||
for (i, &cell) in cells.iter().enumerate() {
|
|
||||||
px[i * 4..i * 4 + 4].copy_from_slice(&rgb[cell as usize]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(img) = ImageData::new_with_u8_clamped_array_and_sh(Clamped(px), self.cw, self.ch)
|
|
||||||
{
|
|
||||||
self.ctx.put_image_data(&img, 0.0, 0.0).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_steps(&mut self, steps: u32) {
|
|
||||||
for _ in 0..steps {
|
|
||||||
let luts = &self.luts as *const Luts;
|
|
||||||
// SAFETY: luts is not mutated during tick()
|
|
||||||
self.grid.tick(unsafe { &*luts });
|
|
||||||
}
|
|
||||||
self.dirty = true;
|
|
||||||
self.draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn app() -> Result<Rc<RefCell<App>>, JsValue> {
|
|
||||||
APP_STATE.with(|state| {
|
|
||||||
if let Some(app) = state.borrow().as_ref() {
|
|
||||||
return Ok(app.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let app = Rc::new(RefCell::new(App::new()?));
|
|
||||||
state.borrow_mut().replace(app.clone());
|
|
||||||
Ok(app)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_listeners(app: Rc<RefCell<App>>) -> Result<(), JsValue> {
|
|
||||||
let already_ready = LISTENERS_READY.with(|ready| {
|
|
||||||
if ready.get() {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
ready.set(true);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if already_ready {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let win = app.borrow().win.clone();
|
|
||||||
let doc = win.document().unwrap();
|
|
||||||
|
|
||||||
let s = app.clone();
|
|
||||||
let cb = Closure::wrap(Box::new(move |e: web_sys::MouseEvent| {
|
|
||||||
let mut a = s.borrow_mut();
|
|
||||||
a.ccol = e.client_x() / CELL_SIZE as i32;
|
|
||||||
a.crow = e.client_y() / CELL_SIZE as i32;
|
|
||||||
a.cursor_on = true;
|
|
||||||
}) as Box<dyn FnMut(_)>);
|
|
||||||
doc.add_event_listener_with_callback("mousemove", cb.as_ref().unchecked_ref())?;
|
|
||||||
cb.forget();
|
|
||||||
|
|
||||||
let s = app.clone();
|
|
||||||
let cb = Closure::wrap(Box::new(move |e: web_sys::TouchEvent| {
|
|
||||||
e.prevent_default();
|
|
||||||
if let Some(t) = e.touches().get(0) {
|
|
||||||
let mut a = s.borrow_mut();
|
|
||||||
a.ccol = t.client_x() / CELL_SIZE as i32;
|
|
||||||
a.crow = t.client_y() / CELL_SIZE as i32;
|
|
||||||
a.cursor_on = true;
|
|
||||||
}
|
|
||||||
}) as Box<dyn FnMut(_)>);
|
|
||||||
doc.add_event_listener_with_callback("touchmove", cb.as_ref().unchecked_ref())?;
|
|
||||||
cb.forget();
|
|
||||||
|
|
||||||
let s = app.clone();
|
|
||||||
let cb = Closure::wrap(Box::new(move |e: web_sys::TouchEvent| {
|
|
||||||
e.prevent_default();
|
|
||||||
if let Some(t) = e.touches().get(0) {
|
|
||||||
let mut a = s.borrow_mut();
|
|
||||||
a.ccol = t.client_x() / CELL_SIZE as i32;
|
|
||||||
a.crow = t.client_y() / CELL_SIZE as i32;
|
|
||||||
a.cursor_on = true;
|
|
||||||
}
|
|
||||||
}) as Box<dyn FnMut(_)>);
|
|
||||||
doc.add_event_listener_with_callback("touchstart", cb.as_ref().unchecked_ref())?;
|
|
||||||
cb.forget();
|
|
||||||
|
|
||||||
let s = app.clone();
|
|
||||||
let cb = Closure::wrap(Box::new(move |_: web_sys::TouchEvent| {
|
|
||||||
s.borrow_mut().cursor_on = false;
|
|
||||||
}) as Box<dyn FnMut(_)>);
|
|
||||||
doc.add_event_listener_with_callback("touchend", cb.as_ref().unchecked_ref())?;
|
|
||||||
cb.forget();
|
|
||||||
|
|
||||||
let s = app;
|
|
||||||
let cb = Closure::wrap(Box::new(move || {
|
|
||||||
let mut a = s.borrow_mut();
|
|
||||||
a.resize();
|
|
||||||
if RUNNING.with(|running| running.get()) {
|
|
||||||
a.draw();
|
|
||||||
} else {
|
|
||||||
a.render_steps(STILL_STEPS);
|
|
||||||
}
|
|
||||||
}) as Box<dyn FnMut()>);
|
|
||||||
win.add_event_listener_with_callback("resize", cb.as_ref().unchecked_ref())?;
|
|
||||||
cb.forget();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_animation_loop(app: Rc<RefCell<App>>) {
|
|
||||||
RAF_CLOSURE.with(|slot| {
|
|
||||||
if slot.borrow().is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let win = app.borrow().win.clone();
|
|
||||||
let f: Rc<RefCell<Option<Closure<dyn FnMut()>>>> = Rc::new(RefCell::new(None));
|
|
||||||
let g = f.clone();
|
|
||||||
let s = app.clone();
|
|
||||||
let w = win.clone();
|
|
||||||
|
|
||||||
*g.borrow_mut() = Some(Closure::wrap(Box::new(move || {
|
|
||||||
if !RUNNING.with(|running| running.get()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let now = s.borrow().now();
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut a = s.borrow_mut();
|
|
||||||
|
|
||||||
if now - a.last_tick >= TICK_MS {
|
|
||||||
a.last_tick = now;
|
|
||||||
let luts = &a.luts as *const Luts;
|
|
||||||
// SAFETY: luts is not mutated during tick()
|
|
||||||
a.grid.tick(unsafe { &*luts });
|
|
||||||
a.dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.cursor_on {
|
|
||||||
let (cr, cc) = (a.crow, a.ccol);
|
|
||||||
let hue = ((now % HUE_PERIOD_MS) / HUE_PERIOD_MS * 256.0) as u8;
|
|
||||||
a.grid.stamp(cr, cc, 2, hue);
|
|
||||||
a.dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
if RUNNING.with(|running| running.get()) {
|
|
||||||
w.request_animation_frame(f.borrow().as_ref().unwrap().as_ref().unchecked_ref())
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}) as Box<dyn FnMut()>));
|
|
||||||
|
|
||||||
slot.borrow_mut().replace(g.borrow_mut().take().unwrap());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Entry point ────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn start() -> Result<(), JsValue> {
|
|
||||||
let app = app()?;
|
|
||||||
init_listeners(app.clone())?;
|
|
||||||
|
|
||||||
if RUNNING.with(|running| running.get()) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
RUNNING.with(|running| running.set(true));
|
|
||||||
ensure_animation_loop(app.clone());
|
|
||||||
|
|
||||||
RAF_CLOSURE.with(|slot| {
|
|
||||||
if let Some(cb) = slot.borrow().as_ref() {
|
|
||||||
app.borrow()
|
|
||||||
.win
|
|
||||||
.request_animation_frame(cb.as_ref().unchecked_ref())
|
|
||||||
} else {
|
|
||||||
Err(JsValue::from_str("no animation closure"))
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn stop() -> Result<(), JsValue> {
|
|
||||||
let _ = app()?;
|
|
||||||
RUNNING.with(|running| running.set(false));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn render_still(steps: u32) -> Result<(), JsValue> {
|
|
||||||
let app = app()?;
|
|
||||||
init_listeners(app.clone())?;
|
|
||||||
RUNNING.with(|running| running.set(false));
|
|
||||||
let steps = steps.max(1);
|
|
||||||
let mut app = app.borrow_mut();
|
|
||||||
app.resize();
|
|
||||||
app.render_steps(steps);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
pub fn set_panic_hook() {
|
|
||||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
|
||||||
// `set_panic_hook` function at least once during initialization, and then
|
|
||||||
// we will get better error messages if our code ever panics.
|
|
||||||
//
|
|
||||||
// For more details see
|
|
||||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
|
||||||
#[cfg(feature = "console_error_panic_hook")]
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +1,42 @@
|
||||||
import tseslint from 'typescript-eslint';
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{
|
{
|
||||||
ignores: ['dist', 'cgol/pkg/**/*']
|
ignores: ["dist"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.ts'],
|
files: ["**/*.ts"],
|
||||||
extends: [
|
extends: [
|
||||||
...tseslint.configs.recommended,
|
...tseslint.configs.recommended,
|
||||||
...tseslint.configs.recommendedTypeChecked,
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
...tseslint.configs.stylisticTypeChecked
|
...tseslint.configs.stylisticTypeChecked,
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/array-type": "off",
|
|
||||||
"@typescript-eslint/consistent-type-definitions": "off",
|
|
||||||
"@typescript-eslint/consistent-type-imports": [
|
|
||||||
"warn",
|
|
||||||
{ prefer: "type-imports", fixStyle: "inline-type-imports" },
|
|
||||||
],
|
],
|
||||||
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
rules: {
|
||||||
"@typescript-eslint/require-await": "off",
|
"@typescript-eslint/array-type": "off",
|
||||||
"@typescript-eslint/no-misused-promises": [
|
"@typescript-eslint/consistent-type-definitions": "off",
|
||||||
"error",
|
"@typescript-eslint/consistent-type-imports": [
|
||||||
{ checksVoidReturn: { attributes: false } },
|
"warn",
|
||||||
],
|
{ prefer: "type-imports", fixStyle: "inline-type-imports" },
|
||||||
},
|
],
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"warn",
|
||||||
|
{ argsIgnorePattern: "^_" },
|
||||||
|
],
|
||||||
|
"@typescript-eslint/require-await": "off",
|
||||||
|
"@typescript-eslint/no-misused-promises": [
|
||||||
|
"error",
|
||||||
|
{ checksVoidReturn: { attributes: false } },
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
linterOptions: {
|
linterOptions: {
|
||||||
reportUnusedDisableDirectives: true
|
reportUnusedDisableDirectives: true,
|
||||||
},
|
},
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
projectService: true
|
projectService: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
|
||||||
10
index.html
10
index.html
|
|
@ -103,13 +103,15 @@
|
||||||
<a class="skip-link" href="#outlet">Skip to content</a>
|
<a class="skip-link" href="#outlet">Skip to content</a>
|
||||||
<canvas
|
<canvas
|
||||||
id="canvas"
|
id="canvas"
|
||||||
class="fixed top-0 left-0 -z-10 h-screen w-screen"
|
class="pointer-events-none fixed inset-0 z-0 h-screen w-screen"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></canvas>
|
></canvas>
|
||||||
<div class="page-frame">
|
<div class="page-frame relative z-10">
|
||||||
<nav aria-label="Main navigation" class="site-region">
|
<nav aria-label="Main navigation" class="site-region">
|
||||||
<div class="site-shell site-panel px-[2ch] py-[1ch]">
|
<div class="site-shell site-panel-frame px-[2ch] py-[1ch]">
|
||||||
<div class="flex justify-center gap-[2ch]">
|
<div class="site-panel-frost" aria-hidden="true"></div>
|
||||||
|
<div class="site-panel-border" aria-hidden="true"></div>
|
||||||
|
<div class="site-panel-content flex justify-center gap-[2ch]">
|
||||||
<a href="/" data-nav-link>[HOME]</a>
|
<a href="/" data-nav-link>[HOME]</a>
|
||||||
<a href="/qa" data-nav-link>[Q&A]</a>
|
<a href="/qa" data-nav-link>[Q&A]</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
306
package-lock.json
generated
306
package-lock.json
generated
|
|
@ -7,9 +7,6 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "website",
|
"name": "website",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
|
||||||
"cgol": "file:./cgol/pkg"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.3.3",
|
||||||
|
|
@ -22,15 +19,9 @@
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.56.1",
|
"typescript-eslint": "^8.56.1",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vite-plugin-singlefile": "^2.3.0",
|
"vite-plugin-singlefile": "^2.3.0"
|
||||||
"vite-plugin-top-level-await": "^1.6.0",
|
|
||||||
"vite-plugin-wasm": "^3.5.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cgol/pkg": {
|
|
||||||
"name": "cgol",
|
|
||||||
"version": "0.1.0"
|
|
||||||
},
|
|
||||||
"node_modules/@emnapi/runtime": {
|
"node_modules/@emnapi/runtime": {
|
||||||
"version": "1.8.1",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
|
||||||
|
|
@ -693,24 +684,6 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/plugin-virtual": {
|
|
||||||
"version": "3.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz",
|
|
||||||
"integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"rollup": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.59.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
||||||
|
|
@ -1061,239 +1034,6 @@
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core": {
|
|
||||||
"version": "1.15.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.18.tgz",
|
|
||||||
"integrity": "sha512-z87aF9GphWp//fnkRsqvtY+inMVPgYW3zSlXH1kJFvRT5H/wiAn+G32qW5l3oEk63KSF1x3Ov0BfHCObAmT8RA==",
|
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@swc/counter": "^0.1.3",
|
|
||||||
"@swc/types": "^0.1.25"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/swc"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@swc/core-darwin-arm64": "1.15.18",
|
|
||||||
"@swc/core-darwin-x64": "1.15.18",
|
|
||||||
"@swc/core-linux-arm-gnueabihf": "1.15.18",
|
|
||||||
"@swc/core-linux-arm64-gnu": "1.15.18",
|
|
||||||
"@swc/core-linux-arm64-musl": "1.15.18",
|
|
||||||
"@swc/core-linux-x64-gnu": "1.15.18",
|
|
||||||
"@swc/core-linux-x64-musl": "1.15.18",
|
|
||||||
"@swc/core-win32-arm64-msvc": "1.15.18",
|
|
||||||
"@swc/core-win32-ia32-msvc": "1.15.18",
|
|
||||||
"@swc/core-win32-x64-msvc": "1.15.18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@swc/helpers": ">=0.5.17"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@swc/helpers": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@swc/core-darwin-arm64": {
|
|
||||||
"version": "1.15.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.18.tgz",
|
|
||||||
"integrity": "sha512-+mIv7uBuSaywN3C9LNuWaX1jJJ3SKfiJuE6Lr3bd+/1Iv8oMU7oLBjYMluX1UrEPzwN2qCdY6Io0yVicABoCwQ==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0 AND MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@swc/core-darwin-x64": {
|
|
||||||
"version": "1.15.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.18.tgz",
|
|
||||||
"integrity": "sha512-wZle0eaQhnzxWX5V/2kEOI6Z9vl/lTFEC6V4EWcn+5pDjhemCpQv9e/TDJ0GIoiClX8EDWRvuZwh+Z3dhL1NAg==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0 AND MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
|
||||||
"version": "1.15.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.18.tgz",
|
|
||||||
"integrity": "sha512-ao61HGXVqrJFHAcPtF4/DegmwEkVCo4HApnotLU8ognfmU8x589z7+tcf3hU+qBiU1WOXV5fQX6W9Nzs6hjxDw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
|
||||||
"version": "1.15.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.18.tgz",
|
|
||||||
"integrity": "sha512-3xnctOBLIq3kj8PxOCgPrGjBLP/kNOddr6f5gukYt/1IZxsITQaU9TDyjeX6jG+FiCIHjCuWuffsyQDL5Ew1bg==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0 AND MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@swc/core-linux-arm64-musl": {
|
|
||||||
"version": "1.15.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.18.tgz",
|
|
||||||
"integrity": "sha512-0a+Lix+FSSHBSBOA0XznCcHo5/1nA6oLLjcnocvzXeqtdjnPb+SvchItHI+lfeiuj1sClYPDvPMLSLyXFaiIKw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0 AND MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@swc/core-linux-x64-gnu": {
|
|
||||||
"version": "1.15.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.18.tgz",
|
|
||||||
"integrity": "sha512-wG9J8vReUlpaHz4KOD/5UE1AUgirimU4UFT9oZmupUDEofxJKYb1mTA/DrMj0s78bkBiNI+7Fo2EgPuvOJfuAA==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0 AND MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@swc/core-linux-x64-musl": {
|
|
||||||
"version": "1.15.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.18.tgz",
|
|
||||||
"integrity": "sha512-4nwbVvCphKzicwNWRmvD5iBaZj8JYsRGa4xOxJmOyHlMDpsvvJ2OR2cODlvWyGFH6BYL1MfIAK3qph3hp0Az6g==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0 AND MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@swc/core-win32-arm64-msvc": {
|
|
||||||
"version": "1.15.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.18.tgz",
|
|
||||||
"integrity": "sha512-zk0RYO+LjiBCat2RTMHzAWaMky0cra9loH4oRrLKLLNuL+jarxKLFDA8xTZWEkCPLjUTwlRN7d28eDLLMgtUcQ==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0 AND MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@swc/core-win32-ia32-msvc": {
|
|
||||||
"version": "1.15.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.18.tgz",
|
|
||||||
"integrity": "sha512-yVuTrZ0RccD5+PEkpcLOBAuPbYBXS6rslENvIXfvJGXSdX5QGi1ehC4BjAMl5FkKLiam4kJECUI0l7Hq7T1vwg==",
|
|
||||||
"cpu": [
|
|
||||||
"ia32"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0 AND MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@swc/core-win32-x64-msvc": {
|
|
||||||
"version": "1.15.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.18.tgz",
|
|
||||||
"integrity": "sha512-7NRmE4hmUQNCbYU3Hn9Tz57mK9Qq4c97ZS+YlamlK6qG9Fb5g/BB3gPDe0iLlJkns/sYv2VWSkm8c3NmbEGjbg==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0 AND MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@swc/counter": {
|
|
||||||
"version": "0.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
|
||||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0"
|
|
||||||
},
|
|
||||||
"node_modules/@swc/types": {
|
|
||||||
"version": "0.1.25",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
|
|
||||||
"integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@swc/counter": "^0.1.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@swc/wasm": {
|
|
||||||
"version": "1.15.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.15.18.tgz",
|
|
||||||
"integrity": "sha512-zeSORFArxqUwfVMTRHu8AN9k9LlfSn0CKDSzLhJDITpgLoS0xpnocxsgMjQjUcVYDgO47r9zLP49HEjH/iGsFg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0"
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/node": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz",
|
||||||
|
|
@ -1910,10 +1650,6 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cgol": {
|
|
||||||
"resolved": "cgol/pkg",
|
|
||||||
"link": true
|
|
||||||
},
|
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
|
|
@ -3273,20 +3009,6 @@
|
||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
|
||||||
"version": "10.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
|
||||||
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
|
||||||
"https://github.com/sponsors/broofa",
|
|
||||||
"https://github.com/sponsors/ctavan"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"uuid": "dist/bin/uuid"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.3.1",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||||
|
|
@ -3379,32 +3101,6 @@
|
||||||
"vite": "^5.4.11 || ^6.0.0 || ^7.0.0"
|
"vite": "^5.4.11 || ^6.0.0 || ^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite-plugin-top-level-await": {
|
|
||||||
"version": "1.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.6.0.tgz",
|
|
||||||
"integrity": "sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@rollup/plugin-virtual": "^3.0.2",
|
|
||||||
"@swc/core": "^1.12.14",
|
|
||||||
"@swc/wasm": "^1.12.14",
|
|
||||||
"uuid": "10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"vite": ">=2.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vite-plugin-wasm": {
|
|
||||||
"version": "3.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.5.0.tgz",
|
|
||||||
"integrity": "sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|
|
||||||
14
package.json
14
package.json
|
|
@ -5,7 +5,6 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"build:wasm": "cd cgol && wasm-pack build --release --target web && wasm-opt pkg/cgol_bg.wasm -o pkg/cgol_bg.wasm -O4 --enable-bulk-memory --enable-nontrapping-float-to-int --enable-sign-ext --low-memory-unused --converge",
|
|
||||||
"check": "npm run lint && tsc --noEmit",
|
"check": "npm run lint && tsc --noEmit",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"format:check": "prettier --check \"**/*.{ts,js,jsx,mdx}\" --cache",
|
"format:check": "prettier --check \"**/*.{ts,js,jsx,mdx}\" --cache",
|
||||||
|
|
@ -15,9 +14,6 @@
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
|
||||||
"cgol": "file:./cgol/pkg"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.3.3",
|
||||||
|
|
@ -30,13 +26,7 @@
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.56.1",
|
"typescript-eslint": "^8.56.1",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vite-plugin-singlefile": "^2.3.0",
|
"vite-plugin-singlefile": "^2.3.0"
|
||||||
"vite-plugin-top-level-await": "^1.6.0",
|
|
||||||
"vite-plugin-wasm": "^3.5.0"
|
|
||||||
},
|
},
|
||||||
"knip": {
|
"knip": {}
|
||||||
"ignore": [
|
|
||||||
"cgol/pkg/**"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
export function frostedBox(content: string, extraClasses?: string): string {
|
export function frostedBox(content: string, extraClasses?: string): string {
|
||||||
return `
|
return `
|
||||||
<div class="site-shell relative my-[2ch] overflow-hidden px-[2ch] py-[2ch] ${extraClasses ?? ""}">
|
<div class="site-shell site-panel-frame relative my-[2ch] overflow-hidden px-[2ch] py-[2ch] ${extraClasses ?? ""}">
|
||||||
<div
|
<div class="site-panel-frost pointer-events-none" aria-hidden="true"></div>
|
||||||
class="pointer-events-none absolute inset-0 h-[200%]"
|
<div class="site-panel-border" aria-hidden="true"></div>
|
||||||
style="background-color: rgba(0, 0, 0, 0.75); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); mask-image: linear-gradient(to bottom, black 0% 50%, transparent 50% 100%); -webkit-mask-image: linear-gradient(to bottom, black 0% 50%, transparent 50% 100%);"
|
<div class="site-panel-content h-full min-h-0">
|
||||||
aria-hidden="true"
|
|
||||||
></div>
|
|
||||||
<div class="absolute inset-0 border-2 border-white" aria-hidden="true"></div>
|
|
||||||
<div class="relative z-10 h-full min-h-0">
|
|
||||||
${content}
|
${content}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
const MOTION_QUERY = "(prefers-reduced-motion: reduce)";
|
|
||||||
const STILL_STEPS = 5;
|
|
||||||
|
|
||||||
type BackgroundMode = "animated" | "still" | "failed";
|
|
||||||
|
|
||||||
interface BackgroundActions {
|
|
||||||
start: () => void;
|
|
||||||
stop: () => void;
|
|
||||||
renderStill: (steps: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMode(reducedMotion: boolean): BackgroundMode {
|
|
||||||
return reducedMotion ? "still" : "animated";
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyCanvasState(mode: BackgroundMode) {
|
|
||||||
const canvas = document.getElementById("canvas");
|
|
||||||
document.body.dataset.backgroundMode = mode;
|
|
||||||
if (canvas) {
|
|
||||||
canvas.toggleAttribute("hidden", mode === "failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initBackgroundControls(actions: BackgroundActions) {
|
|
||||||
const media = window.matchMedia(MOTION_QUERY);
|
|
||||||
let mode: BackgroundMode = getMode(media.matches);
|
|
||||||
let failed = false;
|
|
||||||
|
|
||||||
const applyMode = (restartAnimation = false) => {
|
|
||||||
if (failed) return;
|
|
||||||
|
|
||||||
mode = getMode(media.matches);
|
|
||||||
applyCanvasState(mode);
|
|
||||||
|
|
||||||
if (mode === "animated") {
|
|
||||||
if (restartAnimation) {
|
|
||||||
actions.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.start();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.stop();
|
|
||||||
actions.renderStill(STILL_STEPS);
|
|
||||||
};
|
|
||||||
|
|
||||||
const restartAnimation = () => {
|
|
||||||
if (document.visibilityState === "hidden" || media.matches) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
applyMode(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
media.addEventListener("change", () => {
|
|
||||||
applyMode();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("visibilitychange", () => {
|
|
||||||
restartAnimation();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("pageshow", () => {
|
|
||||||
restartAnimation();
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
applyInitialMode() {
|
|
||||||
applyMode();
|
|
||||||
},
|
|
||||||
setFailed() {
|
|
||||||
failed = true;
|
|
||||||
mode = "failed";
|
|
||||||
applyCanvasState(mode);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -28,8 +28,10 @@ export function renderFooter() {
|
||||||
const mirror = getMirrorLink();
|
const mirror = getMirrorLink();
|
||||||
|
|
||||||
footer.innerHTML = `
|
footer.innerHTML = `
|
||||||
<div class="site-panel px-[2ch] py-[1ch]">
|
<div class="site-panel-frame px-[2ch] py-[1ch]">
|
||||||
<div class="site-footer-inner">
|
<div class="site-panel-frost" aria-hidden="true"></div>
|
||||||
|
<div class="site-panel-border" aria-hidden="true"></div>
|
||||||
|
<div class="site-panel-content site-footer-inner">
|
||||||
<a href="${REPO_URL}">src</a>
|
<a href="${REPO_URL}">src</a>
|
||||||
<span aria-hidden="true">|</span>
|
<span aria-hidden="true">|</span>
|
||||||
<a href="/qa/rss.xml" data-native-link>rss</a>
|
<a href="/qa/rss.xml" data-native-link>rss</a>
|
||||||
|
|
|
||||||
1515
src/lib/webgl-background.ts
Normal file
1515
src/lib/webgl-background.ts
Normal file
File diff suppressed because it is too large
Load diff
24
src/main.ts
24
src/main.ts
|
|
@ -1,7 +1,6 @@
|
||||||
import "~/styles/globals.css";
|
import "~/styles/globals.css";
|
||||||
import init, { render_still, start, stop } from "cgol";
|
|
||||||
import { route, initRouter } from "~/router";
|
import { route, initRouter } from "~/router";
|
||||||
import { initBackgroundControls } from "~/lib/background";
|
import { initWebGLBackground } from "~/lib/webgl-background";
|
||||||
import { renderFooter } from "~/lib/site";
|
import { renderFooter } from "~/lib/site";
|
||||||
import { homePage } from "~/pages/home";
|
import { homePage } from "~/pages/home";
|
||||||
import { qaPage } from "~/pages/qa";
|
import { qaPage } from "~/pages/qa";
|
||||||
|
|
@ -12,24 +11,5 @@ route("/qa", "Jet Pham - Q+A", qaPage);
|
||||||
route("*", "404 - Jet Pham", notFoundPage);
|
route("*", "404 - Jet Pham", notFoundPage);
|
||||||
|
|
||||||
renderFooter();
|
renderFooter();
|
||||||
const background = initBackgroundControls({
|
initWebGLBackground();
|
||||||
start() {
|
|
||||||
start();
|
|
||||||
},
|
|
||||||
stop() {
|
|
||||||
stop();
|
|
||||||
},
|
|
||||||
renderStill(steps) {
|
|
||||||
render_still(steps);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await init();
|
|
||||||
background.applyInitialMode();
|
|
||||||
} catch (e) {
|
|
||||||
background.setFailed();
|
|
||||||
console.error("WASM init failed:", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
initRouter();
|
initRouter();
|
||||||
|
|
|
||||||
|
|
@ -6,30 +6,31 @@ export function homePage(outlet: HTMLElement) {
|
||||||
outlet.innerHTML = `
|
outlet.innerHTML = `
|
||||||
<div class="flex flex-col items-center justify-start">
|
<div class="flex flex-col items-center justify-start">
|
||||||
${frostedBox(`
|
${frostedBox(`
|
||||||
<div class="flex flex-col items-center justify-center gap-[2ch] md:flex-row">
|
<div class="flex flex-col items-center justify-center gap-[1.25ch] md:gap-[2ch] md:flex-row">
|
||||||
<div class="order-1 flex flex-col items-center md:order-2">
|
<div class="order-1 flex flex-col items-center md:order-2">
|
||||||
<h1 class="sr-only">Jet Pham</h1>
|
<h1 class="sr-only">Jet Pham</h1>
|
||||||
<div aria-hidden="true">${Jet}</div>
|
<div aria-hidden="true" data-emitter-ansi>${Jet}</div>
|
||||||
<p class="mt-[2ch]">Software Extremist</p>
|
<p class="mt-[2ch]">Software Extremist</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="order-2 shrink-0 md:order-1">
|
<div class="order-2 shrink-0 md:order-1">
|
||||||
<img
|
<img
|
||||||
|
data-emitter-image
|
||||||
src="/jet.svg"
|
src="/jet.svg"
|
||||||
alt="A picture of Jet wearing a beanie in purple and blue lighting"
|
alt="A picture of Jet wearing a beanie in purple and blue lighting"
|
||||||
width="250"
|
width="250"
|
||||||
height="250"
|
height="250"
|
||||||
class="aspect-square w-full max-w-[250px] object-cover md:h-[263px] md:w-[175px] md:max-w-none"
|
class="aspect-square w-full max-w-[220px] object-cover md:h-[263px] md:w-[175px] md:max-w-none"
|
||||||
style="background-color: #a80055; color: transparent"
|
style="background-color: #a80055; color: transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<fieldset class="section-block mt-[2ch] border-2 border-white px-[calc(1.5ch-0.5px)] pb-[1ch] pt-0">
|
<fieldset class="section-block mt-[2ch] border-2 px-[calc(1.5ch-0.5px)] pb-[1ch] pt-0" style="border-color: var(--white)">
|
||||||
<legend class="-mx-[0.5ch] px-[0.5ch] text-white">Contact</legend>
|
<legend class="-mx-[0.5ch] px-[0.5ch]" style="color: var(--white)">Contact</legend>
|
||||||
<button type="button" id="copy-email" class="qa-inline-action">jet@extremist.software</button>
|
<button type="button" id="copy-email" class="qa-inline-action">jet@extremist.software</button>
|
||||||
<span id="copy-email-status" class="qa-meta ml-[1ch]" aria-live="polite"></span>
|
<span id="copy-email-status" class="qa-meta ml-[1ch]" aria-live="polite"></span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="section-block mt-[2ch] border-2 border-white px-[calc(1.5ch-0.5px)] pb-[1ch] pt-0">
|
<fieldset class="section-block mt-[2ch] border-2 px-[calc(1.5ch-0.5px)] pb-[1ch] pt-0" style="border-color: var(--white)">
|
||||||
<legend class="-mx-[0.5ch] px-[0.5ch] text-white">Links</legend>
|
<legend class="-mx-[0.5ch] px-[0.5ch]" style="color: var(--white)">Links</legend>
|
||||||
<ol>
|
<ol>
|
||||||
<li><a href="https://git.extremist.software" class="inline-flex items-center">Forgejo</a></li>
|
<li><a href="https://git.extremist.software" class="inline-flex items-center">Forgejo</a></li>
|
||||||
<li><a href="https://github.com/jetpham" class="inline-flex items-center">GitHub</a></li>
|
<li><a href="https://github.com/jetpham" class="inline-flex items-center">GitHub</a></li>
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ async function render() {
|
||||||
outlet.innerHTML = "";
|
outlet.innerHTML = "";
|
||||||
await r.handler(outlet, params);
|
await r.handler(outlet, params);
|
||||||
document.title = r.title;
|
document.title = r.title;
|
||||||
|
document.dispatchEvent(new Event("site:render"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -70,6 +71,7 @@ async function render() {
|
||||||
await notFoundHandler(outlet, {});
|
await notFoundHandler(outlet, {});
|
||||||
}
|
}
|
||||||
document.title = notFoundTitle;
|
document.title = notFoundTitle;
|
||||||
|
document.dispatchEvent(new Event("site:render"));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initRouter() {
|
export function initRouter() {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,14 @@
|
||||||
--light-magenta: #ff55ff;
|
--light-magenta: #ff55ff;
|
||||||
--yellow: #ffff55;
|
--yellow: #ffff55;
|
||||||
--white: #ffffff;
|
--white: #ffffff;
|
||||||
|
--panel-bg-alpha: 0.2;
|
||||||
|
--panel-gloss-alpha: 0.05;
|
||||||
|
--panel-border-inset: 0.6rem;
|
||||||
|
--panel-bg: rgba(0, 0, 0, var(--panel-bg-alpha));
|
||||||
|
--panel-blur: 10px;
|
||||||
|
--fallback-glow-a: rgba(85, 255, 255, 0.18);
|
||||||
|
--fallback-glow-b: rgba(85, 85, 255, 0.16);
|
||||||
|
--fallback-glow-c: rgba(255, 85, 85, 0.14);
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
|
@ -43,6 +51,8 @@ html {
|
||||||
body {
|
body {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
|
position: relative;
|
||||||
|
isolation: isolate;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: var(--black);
|
background-color: var(--black);
|
||||||
|
|
@ -53,6 +63,38 @@ body {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
inset: -20vmax;
|
||||||
|
z-index: -30;
|
||||||
|
background: var(--black);
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-background-mode="failed"]::before {
|
||||||
|
opacity: 1;
|
||||||
|
animation: fallback-drift 18s linear infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
body[data-background-mode="failed"]::before {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fallback-drift {
|
||||||
|
from {
|
||||||
|
transform: translate3d(-3%, -2%, 0) scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate3d(3%, 2%, 0) scale(1.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
background-color: var(--light-blue);
|
background-color: var(--light-blue);
|
||||||
color: var(--black);
|
color: var(--black);
|
||||||
|
|
@ -98,20 +140,49 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-shell {
|
.site-shell {
|
||||||
width: min(100%, 60%);
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-panel {
|
@media (min-width: 768px) {
|
||||||
border: 2px solid var(--white);
|
.site-shell {
|
||||||
background-color: rgba(0, 0, 0, 0.75);
|
width: min(100%, 60%);
|
||||||
backdrop-filter: blur(10px);
|
}
|
||||||
-webkit-backdrop-filter: blur(10px);
|
}
|
||||||
|
|
||||||
|
.site-panel-frame {
|
||||||
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.site-panel-frost {
|
||||||
|
position: absolute;
|
||||||
|
inset: var(--panel-border-inset);
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
background:
|
||||||
|
linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(255, 255, 255, var(--panel-gloss-alpha)),
|
||||||
|
transparent 24%
|
||||||
|
),
|
||||||
|
var(--panel-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-panel-border {
|
||||||
|
position: absolute;
|
||||||
|
inset: var(--panel-border-inset);
|
||||||
|
border: 2px solid var(--white);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-panel-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.site-region {
|
.site-region {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,5 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src", "vite.config.ts", "vite-plugin-ansi.ts"],
|
"include": ["src", "vite.config.ts", "vite-plugin-ansi.ts"],
|
||||||
"exclude": ["node_modules", "dist", "cgol/pkg"]
|
"exclude": ["node_modules", "dist"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,51 +4,51 @@ import fs from "node:fs";
|
||||||
import type { Plugin } from "vite";
|
import type { Plugin } from "vite";
|
||||||
|
|
||||||
const colorMap: Record<string, string> = {
|
const colorMap: Record<string, string> = {
|
||||||
"ansi-black": "text-[var(--black)]",
|
"ansi-black": "color: var(--black)",
|
||||||
"ansi-red": "text-[var(--red)]",
|
"ansi-red": "color: var(--red)",
|
||||||
"ansi-green": "text-[var(--green)]",
|
"ansi-green": "color: var(--green)",
|
||||||
"ansi-yellow": "text-[var(--brown)]",
|
"ansi-yellow": "color: var(--brown)",
|
||||||
"ansi-blue": "text-[var(--blue)]",
|
"ansi-blue": "color: var(--blue)",
|
||||||
"ansi-magenta": "text-[var(--magenta)]",
|
"ansi-magenta": "color: var(--magenta)",
|
||||||
"ansi-cyan": "text-[var(--cyan)]",
|
"ansi-cyan": "color: var(--cyan)",
|
||||||
"ansi-white": "text-[var(--light-gray)]",
|
"ansi-white": "color: var(--light-gray)",
|
||||||
"ansi-bright-black": "text-[var(--dark-gray)]",
|
"ansi-bright-black": "color: var(--dark-gray)",
|
||||||
"ansi-bright-red": "text-[var(--light-red)]",
|
"ansi-bright-red": "color: var(--light-red)",
|
||||||
"ansi-bright-green": "text-[var(--light-green)]",
|
"ansi-bright-green": "color: var(--light-green)",
|
||||||
"ansi-bright-yellow": "text-[var(--yellow)]",
|
"ansi-bright-yellow": "color: var(--yellow)",
|
||||||
"ansi-bright-blue": "text-[var(--light-blue)]",
|
"ansi-bright-blue": "color: var(--light-blue)",
|
||||||
"ansi-bright-magenta": "text-[var(--light-magenta)]",
|
"ansi-bright-magenta": "color: var(--light-magenta)",
|
||||||
"ansi-bright-cyan": "text-[var(--light-cyan)]",
|
"ansi-bright-cyan": "color: var(--light-cyan)",
|
||||||
"ansi-bright-white": "text-[var(--white)]",
|
"ansi-bright-white": "color: var(--white)",
|
||||||
};
|
};
|
||||||
|
|
||||||
const bgColorMap: Record<string, string> = {
|
const bgColorMap: Record<string, string> = {
|
||||||
"ansi-black": "bg-transparent",
|
"ansi-black": "background-color: transparent",
|
||||||
"ansi-red": "bg-[var(--red)]",
|
"ansi-red": "background-color: var(--red)",
|
||||||
"ansi-green": "bg-[var(--green)]",
|
"ansi-green": "background-color: var(--green)",
|
||||||
"ansi-yellow": "bg-[var(--brown)]",
|
"ansi-yellow": "background-color: var(--brown)",
|
||||||
"ansi-blue": "bg-[var(--blue)]",
|
"ansi-blue": "background-color: var(--blue)",
|
||||||
"ansi-magenta": "bg-[var(--magenta)]",
|
"ansi-magenta": "background-color: var(--magenta)",
|
||||||
"ansi-cyan": "bg-[var(--cyan)]",
|
"ansi-cyan": "background-color: var(--cyan)",
|
||||||
"ansi-white": "bg-[var(--light-gray)]",
|
"ansi-white": "background-color: var(--light-gray)",
|
||||||
"ansi-bright-black": "bg-[var(--dark-gray)]",
|
"ansi-bright-black": "background-color: var(--dark-gray)",
|
||||||
"ansi-bright-red": "bg-[var(--light-red)]",
|
"ansi-bright-red": "background-color: var(--light-red)",
|
||||||
"ansi-bright-green": "bg-[var(--light-green)]",
|
"ansi-bright-green": "background-color: var(--light-green)",
|
||||||
"ansi-bright-yellow": "bg-[var(--yellow)]",
|
"ansi-bright-yellow": "background-color: var(--yellow)",
|
||||||
"ansi-bright-blue": "bg-[var(--light-blue)]",
|
"ansi-bright-blue": "background-color: var(--light-blue)",
|
||||||
"ansi-bright-magenta": "bg-[var(--light-magenta)]",
|
"ansi-bright-magenta": "background-color: var(--light-magenta)",
|
||||||
"ansi-bright-cyan": "bg-[var(--light-cyan)]",
|
"ansi-bright-cyan": "background-color: var(--light-cyan)",
|
||||||
"ansi-bright-white": "bg-[var(--white)]",
|
"ansi-bright-white": "background-color: var(--white)",
|
||||||
};
|
};
|
||||||
|
|
||||||
const decorationMap: Record<string, string> = {
|
const decorationMap: Record<string, string> = {
|
||||||
bold: "font-bold",
|
bold: "font-weight: 700",
|
||||||
dim: "opacity-50",
|
dim: "opacity: 0.5",
|
||||||
italic: "italic",
|
italic: "font-style: italic",
|
||||||
hidden: "invisible",
|
hidden: "visibility: hidden",
|
||||||
strikethrough: "line-through",
|
strikethrough: "text-decoration: line-through",
|
||||||
underline: "underline",
|
underline: "text-decoration: underline",
|
||||||
blink: "animate-pulse",
|
blink: "animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
|
||||||
};
|
};
|
||||||
|
|
||||||
function fixBackspace(txt: string): string {
|
function fixBackspace(txt: string): string {
|
||||||
|
|
@ -60,18 +60,18 @@ function fixBackspace(txt: string): string {
|
||||||
return txt;
|
return txt;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createClass(bundle: AnserJsonEntry): string | null {
|
function createStyle(bundle: AnserJsonEntry): string | null {
|
||||||
const classes: string[] = [];
|
const declarations: string[] = [];
|
||||||
if (bundle.bg && bgColorMap[bundle.bg]) {
|
if (bundle.bg && bgColorMap[bundle.bg]) {
|
||||||
classes.push(bgColorMap[bundle.bg]!);
|
declarations.push(bgColorMap[bundle.bg]!);
|
||||||
}
|
}
|
||||||
if (bundle.fg && colorMap[bundle.fg]) {
|
if (bundle.fg && colorMap[bundle.fg]) {
|
||||||
classes.push(colorMap[bundle.fg]!);
|
declarations.push(colorMap[bundle.fg]!);
|
||||||
}
|
}
|
||||||
if (bundle.decoration && decorationMap[bundle.decoration]) {
|
if (bundle.decoration && decorationMap[bundle.decoration]) {
|
||||||
classes.push(decorationMap[bundle.decoration]!);
|
declarations.push(decorationMap[bundle.decoration]!);
|
||||||
}
|
}
|
||||||
return classes.length ? classes.join(" ") : null;
|
return declarations.length ? declarations.join("; ") : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(str: string): string {
|
function escapeHtml(str: string): string {
|
||||||
|
|
@ -92,10 +92,10 @@ function renderAnsiToHtml(raw: string): string {
|
||||||
|
|
||||||
const spans = bundles
|
const spans = bundles
|
||||||
.map((bundle) => {
|
.map((bundle) => {
|
||||||
const cls = createClass(bundle);
|
const style = createStyle(bundle);
|
||||||
const content = escapeHtml(bundle.content);
|
const content = escapeHtml(bundle.content);
|
||||||
if (cls) {
|
if (style) {
|
||||||
return `<span class="${cls}">${content}</span>`;
|
return `<span style="${style}">${content}</span>`;
|
||||||
}
|
}
|
||||||
return `<span>${content}</span>`;
|
return `<span>${content}</span>`;
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import wasm from "vite-plugin-wasm";
|
|
||||||
import topLevelAwait from "vite-plugin-top-level-await";
|
|
||||||
import { viteSingleFile } from "vite-plugin-singlefile";
|
import { viteSingleFile } from "vite-plugin-singlefile";
|
||||||
import ansi from "./vite-plugin-ansi";
|
import ansi from "./vite-plugin-ansi";
|
||||||
|
|
||||||
|
|
@ -9,8 +7,6 @@ export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
ansi(),
|
ansi(),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
wasm(),
|
|
||||||
topLevelAwait(),
|
|
||||||
viteSingleFile({ useRecommendedBuildConfig: false }),
|
viteSingleFile({ useRecommendedBuildConfig: false }),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|
@ -20,12 +16,14 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
headers: {
|
headers: {
|
||||||
|
"Cache-Control": "no-store",
|
||||||
"Cross-Origin-Opener-Policy": "same-origin",
|
"Cross-Origin-Opener-Policy": "same-origin",
|
||||||
"Cross-Origin-Embedder-Policy": "require-corp",
|
"Cross-Origin-Embedder-Policy": "require-corp",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
headers: {
|
headers: {
|
||||||
|
"Cache-Control": "no-store",
|
||||||
"Cross-Origin-Opener-Policy": "same-origin",
|
"Cross-Origin-Opener-Policy": "same-origin",
|
||||||
"Cross-Origin-Embedder-Policy": "require-corp",
|
"Cross-Origin-Embedder-Policy": "require-corp",
|
||||||
},
|
},
|
||||||
|
|
@ -34,5 +32,6 @@ export default defineConfig({
|
||||||
target: "esnext",
|
target: "esnext",
|
||||||
assetsInlineLimit: 0,
|
assetsInlineLimit: 0,
|
||||||
cssCodeSplit: false,
|
cssCodeSplit: false,
|
||||||
|
cssMinify: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue