├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.sh ├── cat.txt ├── deploy.sh ├── screenshot.png ├── src ├── constants.rs ├── coordinates.rs ├── cursors.rs ├── enclose.rs ├── links.rs ├── main.rs ├── partials │ ├── about.html │ ├── copyright.html │ ├── cv.html │ ├── doctype.html │ ├── footer.html │ ├── header.html │ └── projects │ │ ├── android-drag-and-drop.html │ │ ├── audio-visualizer.html │ │ ├── emoji-picker.html │ │ ├── intel.html │ │ ├── moonmoji.html │ │ └── retrorecord.html ├── project.rs ├── project_slideshows.rs ├── trash.rs ├── util.rs └── work_history.rs └── static ├── .well-known └── dat ├── avi.jpg ├── dat.json ├── detect.js ├── favicon.png ├── gradient.png ├── noscript.css ├── projects.css ├── script.js └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | static/index.html 3 | static/cat.txt 4 | images 5 | ignore 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | dist: trusty 3 | sudo: false 4 | rust: 5 | - nightly 6 | 7 | node_js: "8.10" 8 | 9 | script: 10 | - shellcheck *.sh 11 | - bash build.sh test 12 | 13 | before_install: 14 | - npm i -g npx 15 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "base-x" 5 | version = "0.2.8" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" 8 | 9 | [[package]] 10 | name = "bumpalo" 11 | version = "3.6.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9" 14 | 15 | [[package]] 16 | name = "cfg-if" 17 | version = "1.0.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 20 | 21 | [[package]] 22 | name = "discard" 23 | version = "1.0.4" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" 26 | 27 | [[package]] 28 | name = "getrandom" 29 | version = "0.1.16" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 32 | dependencies = [ 33 | "cfg-if", 34 | "libc", 35 | "stdweb", 36 | "wasi", 37 | ] 38 | 39 | [[package]] 40 | name = "itoa" 41 | version = "0.4.7" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 44 | 45 | [[package]] 46 | name = "lazy_static" 47 | version = "1.4.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 50 | 51 | [[package]] 52 | name = "libc" 53 | version = "0.2.85" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" 56 | 57 | [[package]] 58 | name = "log" 59 | version = "0.4.14" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 62 | dependencies = [ 63 | "cfg-if", 64 | ] 65 | 66 | [[package]] 67 | name = "ppv-lite86" 68 | version = "0.2.10" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 71 | 72 | [[package]] 73 | name = "proc-macro2" 74 | version = "1.0.24" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 77 | dependencies = [ 78 | "unicode-xid", 79 | ] 80 | 81 | [[package]] 82 | name = "quote" 83 | version = "1.0.8" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" 86 | dependencies = [ 87 | "proc-macro2", 88 | ] 89 | 90 | [[package]] 91 | name = "rand" 92 | version = "0.7.3" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 95 | dependencies = [ 96 | "getrandom", 97 | "libc", 98 | "rand_chacha", 99 | "rand_core", 100 | "rand_hc", 101 | ] 102 | 103 | [[package]] 104 | name = "rand_chacha" 105 | version = "0.2.2" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 108 | dependencies = [ 109 | "ppv-lite86", 110 | "rand_core", 111 | ] 112 | 113 | [[package]] 114 | name = "rand_core" 115 | version = "0.5.1" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 118 | dependencies = [ 119 | "getrandom", 120 | ] 121 | 122 | [[package]] 123 | name = "rand_hc" 124 | version = "0.2.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 127 | dependencies = [ 128 | "rand_core", 129 | ] 130 | 131 | [[package]] 132 | name = "rickycodes" 133 | version = "0.0.1" 134 | dependencies = [ 135 | "rand", 136 | "stdweb", 137 | ] 138 | 139 | [[package]] 140 | name = "rustc_version" 141 | version = "0.2.3" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 144 | dependencies = [ 145 | "semver", 146 | ] 147 | 148 | [[package]] 149 | name = "ryu" 150 | version = "1.0.5" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 153 | 154 | [[package]] 155 | name = "semver" 156 | version = "0.9.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 159 | dependencies = [ 160 | "semver-parser", 161 | ] 162 | 163 | [[package]] 164 | name = "semver-parser" 165 | version = "0.7.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 168 | 169 | [[package]] 170 | name = "serde" 171 | version = "1.0.123" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" 174 | 175 | [[package]] 176 | name = "serde_derive" 177 | version = "1.0.123" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" 180 | dependencies = [ 181 | "proc-macro2", 182 | "quote", 183 | "syn", 184 | ] 185 | 186 | [[package]] 187 | name = "serde_json" 188 | version = "1.0.62" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" 191 | dependencies = [ 192 | "itoa", 193 | "ryu", 194 | "serde", 195 | ] 196 | 197 | [[package]] 198 | name = "sha1" 199 | version = "0.6.0" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" 202 | 203 | [[package]] 204 | name = "stdweb" 205 | version = "0.4.20" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" 208 | dependencies = [ 209 | "discard", 210 | "rustc_version", 211 | "serde", 212 | "serde_json", 213 | "stdweb-derive", 214 | "stdweb-internal-macros", 215 | "stdweb-internal-runtime", 216 | "wasm-bindgen", 217 | ] 218 | 219 | [[package]] 220 | name = "stdweb-derive" 221 | version = "0.5.3" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" 224 | dependencies = [ 225 | "proc-macro2", 226 | "quote", 227 | "serde", 228 | "serde_derive", 229 | "syn", 230 | ] 231 | 232 | [[package]] 233 | name = "stdweb-internal-macros" 234 | version = "0.2.9" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" 237 | dependencies = [ 238 | "base-x", 239 | "proc-macro2", 240 | "quote", 241 | "serde", 242 | "serde_derive", 243 | "serde_json", 244 | "sha1", 245 | "syn", 246 | ] 247 | 248 | [[package]] 249 | name = "stdweb-internal-runtime" 250 | version = "0.1.5" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" 253 | 254 | [[package]] 255 | name = "syn" 256 | version = "1.0.60" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" 259 | dependencies = [ 260 | "proc-macro2", 261 | "quote", 262 | "unicode-xid", 263 | ] 264 | 265 | [[package]] 266 | name = "unicode-xid" 267 | version = "0.2.1" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 270 | 271 | [[package]] 272 | name = "wasi" 273 | version = "0.9.0+wasi-snapshot-preview1" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 276 | 277 | [[package]] 278 | name = "wasm-bindgen" 279 | version = "0.2.70" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" 282 | dependencies = [ 283 | "cfg-if", 284 | "wasm-bindgen-macro", 285 | ] 286 | 287 | [[package]] 288 | name = "wasm-bindgen-backend" 289 | version = "0.2.70" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7" 292 | dependencies = [ 293 | "bumpalo", 294 | "lazy_static", 295 | "log", 296 | "proc-macro2", 297 | "quote", 298 | "syn", 299 | "wasm-bindgen-shared", 300 | ] 301 | 302 | [[package]] 303 | name = "wasm-bindgen-macro" 304 | version = "0.2.70" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c" 307 | dependencies = [ 308 | "quote", 309 | "wasm-bindgen-macro-support", 310 | ] 311 | 312 | [[package]] 313 | name = "wasm-bindgen-macro-support" 314 | version = "0.2.70" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385" 317 | dependencies = [ 318 | "proc-macro2", 319 | "quote", 320 | "syn", 321 | "wasm-bindgen-backend", 322 | "wasm-bindgen-shared", 323 | ] 324 | 325 | [[package]] 326 | name = "wasm-bindgen-shared" 327 | version = "0.2.70" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64" 330 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rickycodes" 3 | version = "0.0.1" 4 | authors = ["Ricky Miller"] 5 | 6 | [dependencies] 7 | stdweb = "^0.4.20" 8 | rand = { version = "^0.7.3", features = [ "stdweb" ] } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2018 Ricky Miller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/rickycodes/www.svg?branch=master)](https://travis-ci.org/rickycodes/www) ![Shellcheck Status](https://img.shields.io/badge/shellcheck-passing-brightgreen) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Frickycodes%2Fwww.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Frickycodes%2Fwww?ref=badge_shield) 2 | 3 | # ricky.codes 4 | 5 | ``` 6 | +---------------+ 7 | |.-------------.| 8 | || ricky || 9 | || dot || 10 | || codes || 11 | ||$ ./build.sh || 12 | |+-------------+| 13 | +-..---------..-+ 14 | .---------------. 15 | / /=============\ \ 16 | / /===============\ \ 17 | /_____________________\ 18 | \_____________________/ 19 | ``` 20 | 21 | My personal website built with Rust using cargo-web and stdweb 22 | 23 | 24 | 25 | ## Disclaimer 26 | 27 | stdweb seems to be now defunct (hasn't been updated since 2019). You might be better off exploring alternatives like wasm bingen. That's what everyone else is doing. 28 | 29 | If you still want to play with this I am building it on `nightly-2019-08-01-x86_64-unknown-linux-gnu` re: #8 30 | 31 | ## Build 32 | you will need `cargo web` 33 | 34 | ``` 35 | cargo web build --target=wasm32-unknown-unknown 36 | ``` 37 | or you can use the handy build script: 38 | ``` 39 | bash build.sh 40 | ``` 41 | I haven't tested other targets, but emscripten should work 42 | 43 | You should see something like: 44 | ``` 45 | warning: debug builds on the wasm32-unknown-unknown are currently totally broken 46 | forcing a release build 47 | Finished release [optimized] target(s) in 0.0 secs 48 | ``` 49 | ## Running local web server 50 | ``` 51 | cargo web start --target=wasm32-unknown-unknown 52 | ``` 53 | or you can use the handy build script: 54 | ``` 55 | bash build.sh --serve 56 | ``` 57 | You should see something like: 58 | ``` 59 | warning: debug builds on the wasm32-unknown-unknown are currently totally broken 60 | forcing a release build 61 | Finished release [optimized] target(s) in 0.0 secs 62 | 63 | If you need to serve any extra files put them in the 'static' directory 64 | in the root of your crate; they will be served alongside your application. 65 | You can also put a 'static' directory in your 'src' directory. 66 | 67 | Your application is being served at '/rickycodes.js'. It will be automatically 68 | rebuilt if you make any changes in your code. 69 | 70 | You can access the web server at `http://[::1]:8000`. 71 | ``` 72 | ## License 73 | 74 | Licensed under 75 | 76 | * MIT license ([LICENSE](LICENSE) or http://opensource.org/licenses/MIT) 77 | 78 | 79 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Frickycodes%2Fwww.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Frickycodes%2Fwww?ref=badge_large) 80 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PARTIALS='src/partials' 4 | PROJECTS=$PARTIALS/projects/ 5 | ARG=$1 6 | OUTPUT=static/index.html 7 | E_ASSERT_FAILED=99 8 | 9 | _HELP="help" 10 | BUILD="build" 11 | GEN="gen" 12 | WATCH="watch" 13 | MIN="min" 14 | TEST="test" 15 | SITE_NAME="ricky.codes" 16 | 17 | HELP="$(cat <<-EOF 18 | $SITE_NAME build tool 19 | 20 | USAGE: 21 | ./build.sh [OPTIONS] 22 | 23 | OPTIONS: 24 | --$_HELP Prints help information 25 | --$GEN Generate + minify HTML... 26 | --$BUILD Runs cargo web deploy --target=wasm32-unknown-unknown 27 | (deploys site to ./target/deploy) 28 | --$WATCH Runs cargo web start --target=wasm32-unknown-unknown 29 | (watch the application locally) 30 | --$MIN Minify deployed *.js files with uglify 31 | --$TEST Run tests 32 | 33 | Running "bash build.sh" (with zero options) will --$GEN --$BUILD and --$MIN (in that order) 34 | This is not a sophisticated script, one [OPTION] (singular) at a time or none. 35 | EOF 36 | )" 37 | 38 | _help() { 39 | echo $HELP 40 | } 41 | 42 | msg() { 43 | echo "$1 deployed to $2" 44 | } 45 | 46 | gen() { 47 | echo 'Generate + minify HTML...' 48 | { cat ${PARTIALS}/doctype.html \ 49 | ${PARTIALS}/header.html \ 50 | ${PARTIALS}/about.html \ 51 | ${PARTIALS}/cv.html \ 52 | ${PARTIALS}/copyright.html \ 53 | ${PROJECTS}* \ 54 | ${PARTIALS}/footer.html | npx html-minifier \ 55 | --collapse-whitespace \ 56 | --remove-comments \ 57 | --remove-optional-tags \ 58 | --remove-redundant-attributes \ 59 | --remove-script-type-attributes \ 60 | --use-short-doctype \ 61 | --minify-css; } > $OUTPUT 62 | msg "HTML" "$OUTPUT" 63 | # newline 64 | echo "\n" >> "$OUTPUT" 65 | # begin html comment 66 | echo "" >> "$OUTPUT" 71 | # copy ascii text to static so we can fetch 72 | cp cat.txt static/ 73 | } 74 | 75 | build() { 76 | # build 77 | echo 'Building...' 78 | cargo web deploy --target=wasm32-unknown-unknown 79 | } 80 | 81 | watch() { 82 | # build 83 | echo 'Starting up local server...' 84 | cargo web start --target=wasm32-unknown-unknown 85 | } 86 | 87 | min() { 88 | # replace console.log before minify 89 | sed -i "s/\"Finished loading \Rust wasm module 'rickycodes'\"//g" target/deploy/rickycodes.js 90 | # minify gen and static js files 91 | echo 'Minify...' 92 | jsFiles='target/deploy/*.js' 93 | for f in $jsFiles; do 94 | npx uglifyjs "$f" \ 95 | --compress \ 96 | --mangle \ 97 | --output "$f" 98 | done 99 | msg "JavaScript" "$jsFiles" 100 | } 101 | 102 | fail() { 103 | echo "$1 $2" 104 | exit $E_ASSERT_FAILED 105 | } 106 | 107 | tests() { 108 | echo "warming up $SITE_NAME test suite!" 109 | # test help 110 | HELP=$(./build.sh --help) 111 | CHECK_HELP=$(echo "$HELP" | grep "$SITE_NAME build tool") 112 | if [ -z "$CHECK_HELP" ]; then 113 | fail "build.sh --help test failed (unexpected text)" 114 | fi 115 | # test gen 116 | if [ -f "$OUTPUT" ]; then 117 | rm "$OUTPUT" 118 | fi 119 | bash build.sh --gen 120 | if [ ! -f "$OUTPUT" ]; then 121 | fail "build.sh --$GEN test failed (no HTML file)" 122 | fi 123 | # nothing in life is simple 124 | SIMPLE=$(grep -ir 'simple\|simply' src/) 125 | if [ -n "$SIMPLE" ]; then 126 | fail "'Nothing in life is simple' test failed $SIMPLE" 127 | fi 128 | echo "Running cargo clean and cargo check" 129 | cargo clean && cargo check 130 | # test the full build 131 | # build 132 | # ex=$? 133 | # if [ $ex -gt 0 ]; then 134 | # fail "The build failed :(" 135 | # fi 136 | echo "all tests passed!" 137 | } 138 | 139 | if [ -z "$ARG" ] 140 | then 141 | gen 142 | build 143 | min 144 | echo 'Done.' 145 | else 146 | [ "$ARG" = "--$TEST" ] || [ "$ARG" = "$TEST" ] && tests 147 | [ "$ARG" = "--$_HELP" ] || [ "$ARG" = "$_HELP" ] && _help 148 | [ "$ARG" = "--$GEN" ] || [ "$ARG" = "$GEN" ] && gen 149 | [ "$ARG" = "--$MIN" ] || [ "$ARG" = "$MIN" ] && min 150 | [ "$ARG" = "--$BUILD" ] || [ "$ARG" = "$BUILD" ] && build 151 | [ "$ARG" = "--$WATCH" ] || [ "$ARG" = "$WATCH" ] && watch 152 | exit 0 153 | fi 154 | -------------------------------------------------------------------------------- /cat.txt: -------------------------------------------------------------------------------- 1 | ██ ██ 2 | ██░░██ ██░░██ 3 | ██░░▒▒████████▒▒░░██ ████ 4 | ██▒▒░░░░▒▒▒▒░░▒▒░░░░▒▒██ ██░░░░██ 5 | ██░░░░░░░░░░░░░░░░░░░░██ ██ ░░██ 6 | ██▒▒░░░░░░░░░░░░░░░░░░░░▒▒████████ ██▒▒██ 7 | ██░░ ██ ░░██░░ ██ ░░ ▒▒ ▒▒ ██ ██░░██ 8 | ██░░░░░░░░██░░██░░░░░░░░░░▒▒░░▒▒░░░░██████▒▒██ 9 | ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██░░██ 10 | ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██░░██ 11 | ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██ 12 | ██▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██ 13 | ██▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██ 14 | ██▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒██ 15 | ██▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒██ 16 | ██▒▒░░▒▒▒▒░░▒▒░░░░░░▒▒░░▒▒▒▒░░▒▒██ 17 | ██░░████░░██████████░░████░░██ 18 | ██▓▓░░ ▓▓██░░ ░░██▓▓ ░░▓▓██ 19 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # sync to http 4 | function http() { 5 | rsync -avze "ssh -p $PORT" ./target/deploy/ $USER@$HOST:$DEPLOY_PATH 6 | } 7 | 8 | # sync to dat 9 | function dat() { 10 | rsync -r --size-only target/deploy/* ignore/dat/ 11 | dat sync --dir=ignore/dat/ 12 | } 13 | 14 | function prompt() { 15 | read -p "${1} " -n 1 -r 16 | echo # (optional) move to a new line 17 | if [[ $REPLY =~ ^[Yy]$ ]] 18 | then 19 | $2 20 | fi 21 | } 22 | 23 | # build 24 | echo "Building..." 25 | ./build.sh 26 | # TODO: make blog components optional and re-enable 27 | # hugo --source ~/projects/blog/ 28 | 29 | 30 | 31 | # combine website and blog 32 | # mv ~/projects/blog/public/ ./target/deploy/blog/ 33 | 34 | prompt "Deploy to http?" http 35 | # this seems busted 36 | # prompt "Sync to dat?" dat 37 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickycodes/www/6f51dbbacd04c27fa56989f0fb495c67a645a7eb/screenshot.png -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | pub(crate) const CRIES: [&str; 8] = [ 2 | "U sure?", 3 | "Really?", 4 | "Y u gotta be this way?", 5 | "Come on?", 6 | "Please no", 7 | "(ಥ﹏ಥ)", 8 | "༼ ༎ຶ ෴ ༎ຶ༽", 9 | "(>﹏<)" 10 | ]; 11 | 12 | pub(crate) const TARGET: &str = "target"; 13 | pub(crate) const BLANK: &str = "_blank"; 14 | pub(crate) const REL: &str = "rel"; 15 | pub(crate) const NOOPENER: &str = "noopener"; 16 | pub(crate) const CLOSE: &str = "close"; 17 | pub(crate) const TRASH: &str = "trash"; 18 | pub(crate) const DRAG: &str = "drag"; 19 | pub(crate) const STYLE: &str = "style"; 20 | pub(crate) const ZOOM: &str = "zoom"; 21 | pub(crate) const NEXT: &str = "next"; 22 | pub(crate) const PREV: &str = "prev"; 23 | pub(crate) const ESC: &str = "Escape"; 24 | pub(crate) const DATA_INDEX: &str = "data-index"; 25 | pub(crate) const DATA_PROJECT: &str = "data-project"; 26 | pub(crate) const DATA_SCROLL: &str = "data-scroll"; 27 | pub(crate) const HIDDEN: &str = "hidden"; 28 | pub(crate) const NAME: &str = "name"; 29 | pub(crate) const TITLE: &str = "title"; 30 | pub(crate) const EMPTY: &str = ""; 31 | pub(crate) const ZERO: &str = "0"; 32 | pub(crate) const UNDERSCORE: &str = "_"; 33 | pub(crate) const ARROW_LEFT: &str = "ArrowLeft"; 34 | pub(crate) const ARROW_RIGHT: &str = "ArrowRight"; 35 | pub(crate) const DIV: &str = "div"; 36 | pub(crate) const A: &str = "a"; 37 | pub(crate) const CONTROLS: &str = "controls"; 38 | pub(crate) const LINK: &str = "link"; 39 | pub(crate) const WORK_HISTORY_SELECTOR: &str = ".work-history"; 40 | pub(crate) const POOP: &str = "💩"; 41 | pub(crate) const CLASS: &str = "class"; 42 | pub(crate) const HASH: &str = "#"; 43 | pub(crate) const DISPLAY_NONE: &str = "display: none;"; 44 | pub(crate) const HREF: &str = "href"; 45 | 46 | pub(crate) const CURSOR_SELECTOR: &str = ".cursor"; 47 | pub(crate) const CURSOR_PROJECT_SELECTOR: &str = ".cursor .project"; 48 | pub(crate) const DRAG_ENTER: &str = "dragenter"; 49 | pub(crate) const DRAG_SELECTOR: &str = ".drag"; 50 | pub(crate) const COORDINATE_SELECTOR: &str = ".coord"; 51 | pub(crate) const LINK_SELECTOR: &str = ".project.link, .cv.link"; 52 | pub(crate) const CLOSE_SELECTOR: &str = ".close div"; 53 | pub(crate) const NOT_PROJECT_SELECTOR: &str = ".content a:not(.project)"; 54 | pub(crate) const PROJECT_SELECTOR: &str = "[data-project] > .content"; 55 | pub(crate) const COORDINATES_SELECTOR: &str = ".coord > div"; 56 | pub(crate) const INFO_SELECTOR: &str = ".info"; 57 | pub(crate) const INFO_LINKS_SELECTOR: &str = ".content a[title], .content label[name]"; 58 | pub(crate) const SLIDESHOW_SELECTOR: &str = ".slideshow"; 59 | pub(crate) const YEAR_SELECTOR: &str = ".year"; 60 | pub(crate) const X_SELECTOR: &str = ".x"; 61 | pub(crate) const Y_SELECTOR: &str = ".y"; 62 | 63 | pub(crate) struct CursorAttributes { 64 | pub(crate) selector: &'static str, 65 | pub(crate) classname: &'static str, 66 | } 67 | 68 | pub(crate) const CURSORS_ATTRIBUTES: [CursorAttributes; 5] = [ 69 | CursorAttributes { 70 | selector: ".github.link", 71 | classname: "gh", 72 | }, 73 | CursorAttributes { 74 | selector: ".twitter.link", 75 | classname: "tw", 76 | }, 77 | CursorAttributes { 78 | selector: "._projects .project", 79 | classname: ZOOM, 80 | }, 81 | CursorAttributes { 82 | selector: ".slideshow .prev", 83 | classname: PREV, 84 | }, 85 | CursorAttributes { 86 | selector: ".slideshow .next", 87 | classname: NEXT, 88 | }, 89 | ]; 90 | 91 | const CONSOLE_STYLE: &'static str = "font-family: 'Monaco'; 92 | font-size: 4em; 93 | padding: 20px; 94 | color: white; 95 | text-shadow: 0 0 0.05em #fff, 0 0 0.2em #fe05e1, 0 0 0.3em #fe05e1; 96 | transform: rotate(-7deg);"; 97 | 98 | pub(crate) fn log() { 99 | console!(log, "%c welcome to my homepage", CONSOLE_STYLE); 100 | console!(log, "The original Magic Kitty™ is an oracle that lets anyone seek advice about their future!"); 101 | console!(log, "All you have to do is simply “ask() the kitty” any yes or no question, then wait for your answer to be revealed."); 102 | console!(log, "example: ask('will i be pretty?')") 103 | } 104 | -------------------------------------------------------------------------------- /src/coordinates.rs: -------------------------------------------------------------------------------- 1 | use stdweb::traits::*; 2 | use stdweb::web::window; 3 | use crate::util::query_selector; 4 | 5 | use stdweb::web::event::MouseMoveEvent; 6 | 7 | use crate::constants::COORDINATES_SELECTOR; 8 | 9 | pub(crate) struct Coordinates(); 10 | 11 | impl Coordinates { 12 | pub(crate) fn new() -> Coordinates { 13 | let mouse_move_event = move |event: MouseMoveEvent| { 14 | let x = f64::from(event.client_x()); 15 | let y = f64::from(event.client_y()); 16 | 17 | query_selector(COORDINATES_SELECTOR).set_text_content(&format!("_x: {}, _y: {}", x, y)); 18 | }; 19 | 20 | window().add_event_listener(mouse_move_event); 21 | 22 | Coordinates() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/cursors.rs: -------------------------------------------------------------------------------- 1 | use stdweb::traits::*; 2 | use stdweb::unstable::TryInto; 3 | use stdweb::web::event::{MouseMoveEvent, MouseOutEvent, MouseOverEvent}; 4 | use stdweb::web::window; 5 | use stdweb::web::HtmlElement; 6 | use crate::util::{node_list, query_selector}; 7 | 8 | use crate::constants::{ 9 | CLOSE, CLOSE_SELECTOR, CURSORS_ATTRIBUTES, CURSOR_SELECTOR, STYLE, X_SELECTOR, Y_SELECTOR, ZERO, 10 | }; 11 | 12 | struct Cursor; 13 | 14 | impl Cursor { 15 | pub(crate) fn new(el: HtmlElement, cursor: &HtmlElement, classname: &'static str) -> Self { 16 | el.add_event_listener( 17 | enclose!( (cursor, classname) move |_event: MouseOverEvent| { 18 | cursor.class_list().add( classname ).unwrap(); 19 | }), 20 | ); 21 | 22 | el.add_event_listener(enclose!( (cursor, classname) move |_event: MouseOutEvent| { 23 | cursor.class_list().remove( classname ).unwrap(); 24 | })); 25 | 26 | Cursor 27 | } 28 | } 29 | 30 | fn set_cursor_coordinates(el: HtmlElement, x: &str, y: &str) { 31 | el.set_attribute(STYLE, &format!("transform: translate3d({},{},0);", x, y)) 32 | .unwrap() 33 | } 34 | 35 | pub(crate) struct Cursors(); 36 | 37 | impl Cursors { 38 | pub(crate) fn new() -> Cursors { 39 | let cursor_element = query_selector(CURSOR_SELECTOR); 40 | let close = query_selector(CLOSE_SELECTOR); 41 | 42 | Cursor::new(close, &cursor_element, CLOSE); 43 | 44 | for cursor_attribute in &CURSORS_ATTRIBUTES { 45 | for cursor in node_list(cursor_attribute.selector) { 46 | Cursor::new( 47 | cursor.try_into().unwrap(), 48 | &cursor_element, 49 | cursor_attribute.classname, 50 | ); 51 | } 52 | } 53 | 54 | let mouse_move_event = move |event: MouseMoveEvent| { 55 | let x = f64::from(event.client_x()); 56 | let y = f64::from(event.client_y()); 57 | 58 | self::set_cursor_coordinates(query_selector(X_SELECTOR), &format!("{}px", x), ZERO); 59 | self::set_cursor_coordinates(query_selector(Y_SELECTOR), ZERO, &format!("{}px", y)); 60 | self::set_cursor_coordinates( 61 | query_selector(CURSOR_SELECTOR), 62 | &format!("{}px", x), 63 | &format!("{}px", y), 64 | ); 65 | }; 66 | 67 | window().add_event_listener(mouse_move_event); 68 | 69 | Cursors() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/enclose.rs: -------------------------------------------------------------------------------- 1 | macro_rules! enclose { 2 | ( ($( $x:ident ),*) $y:expr ) => { 3 | { 4 | $(let $x = $x.clone();)* 5 | $y 6 | } 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /src/links.rs: -------------------------------------------------------------------------------- 1 | use stdweb::traits::*; 2 | use stdweb::unstable::TryInto; 3 | use stdweb::web::HtmlElement; 4 | use crate::util::{node_list, query_selector}; 5 | 6 | use stdweb::web::event::{MouseOutEvent, MouseOverEvent}; 7 | 8 | use crate::constants::{ 9 | BLANK, HIDDEN, INFO_LINKS_SELECTOR, INFO_SELECTOR, NAME, NOOPENER, NOT_PROJECT_SELECTOR, REL, 10 | TARGET, TITLE, 11 | }; 12 | 13 | pub(crate) fn show_info(str: &str, el: HtmlElement, info: HtmlElement) { 14 | let attr = el.get_attribute(str); 15 | if attr != None { 16 | let attr_str = attr.unwrap(); 17 | info.set_text_content(&attr_str); 18 | info.class_list().remove(HIDDEN).unwrap(); 19 | } 20 | } 21 | 22 | pub(crate) fn hide_info(info: HtmlElement) { 23 | info.class_list().add(HIDDEN).unwrap(); 24 | } 25 | 26 | pub(crate) struct Links(); 27 | 28 | impl Links { 29 | pub(crate) fn new() -> Links { 30 | let info = query_selector(INFO_SELECTOR); 31 | 32 | for link in node_list(INFO_LINKS_SELECTOR) { 33 | let el: HtmlElement = link.clone().try_into().unwrap(); 34 | link.add_event_listener(enclose!( (el, info) move |_event: MouseOverEvent| { 35 | self::show_info(NAME, el.clone(), info.clone()); 36 | self::show_info(TITLE, el.clone(), info.clone()); 37 | })); 38 | 39 | link.add_event_listener(enclose!( (info) move |_event: MouseOutEvent| { 40 | self::hide_info(info.clone()); 41 | })); 42 | } 43 | 44 | for link in node_list(NOT_PROJECT_SELECTOR) { 45 | // console!(log, &link); 46 | let el: HtmlElement = link.try_into().unwrap(); 47 | el.set_attribute(TARGET, BLANK).unwrap(); 48 | el.set_attribute(REL, NOOPENER).unwrap(); 49 | } 50 | 51 | Links() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![forbid(unsafe_code)] 2 | #![deny(missing_debug_implementations, nonstandard_style)] 3 | #![warn(unreachable_pub, future_incompatible, rust_2018_idioms)] 4 | 5 | #[macro_use] 6 | extern crate stdweb; 7 | 8 | extern crate rand; 9 | 10 | #[macro_use] 11 | mod enclose; 12 | mod constants; 13 | mod coordinates; 14 | mod cursors; 15 | mod links; 16 | mod project_slideshows; 17 | mod project; 18 | mod trash; 19 | mod util; 20 | mod work_history; 21 | 22 | use crate::coordinates::Coordinates; 23 | use crate::cursors::Cursors; 24 | use crate::links::Links; 25 | use crate::project_slideshows::SlideShows; 26 | use crate::project::ToggleProject; 27 | use crate::trash::Trash; 28 | use crate::util::set_date; 29 | use crate::work_history::WorkHistory; 30 | use crate::constants::log; 31 | 32 | struct Website(); 33 | 34 | impl Website { 35 | fn set_date(self) -> Self { 36 | set_date(); 37 | self 38 | } 39 | 40 | fn initialize(self) -> Self { 41 | stdweb::initialize(); 42 | 43 | SlideShows::new(); 44 | Links::new(); 45 | Coordinates::new(); 46 | Cursors::new(); 47 | Trash::new(); 48 | WorkHistory::new(); 49 | ToggleProject::new(); 50 | self 51 | } 52 | 53 | fn event_loop(self) -> Self { 54 | stdweb::event_loop(); 55 | self 56 | } 57 | 58 | fn new() -> Website { 59 | log(); 60 | Website() 61 | } 62 | } 63 | 64 | fn main() { 65 | Website::new().initialize().set_date().event_loop(); 66 | } 67 | -------------------------------------------------------------------------------- /src/partials/about.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

Ricky Miller

5 |

6 | hello@ricky.codesGitHubTwitterBlog 7 |

8 | 9 |

10 | Greetings! I'm Ricky, a polyglot software developer currently living in Toronto, ON. Canada. 11 |

12 |

13 | For the past several years I've been building interactive web applications with React, Redux and Node.js (JavaScript). 14 |

15 |

16 | I've also written programs in Python, Bash, Rust and PHP. I tend to work on the front-end, but I'm no stranger to testing and automation. I've also used Electron to build cross platform desktop applications. 17 |

18 |
19 |

Projects

20 |

21 | intel 22 | 23 | moonmoji 24 | retrorecord 25 | emoji picker 26 | android drag and drop 27 | audio visualizer 28 | 29 | 30 |

31 |
32 |

Open Source

33 |

34 | I've contributed to several open source projects on GitHub, most notably, Hyper, Beaker Browser, winamp2-js, and stdweb. 35 |

36 |

Mentoring

37 |

38 | In my spare time I enjoy teaching others how to write software. Along with some friends, I started the Toronto chapter of Nodeschool, a monthly meetup focused on JavaScript and Node.js. 39 |

40 |
41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/partials/copyright.html: -------------------------------------------------------------------------------- 1 | 19 |
20 | 21 |
22 | -------------------------------------------------------------------------------- /src/partials/cv.html: -------------------------------------------------------------------------------- 1 |
2 | work history 3 | 47 |
48 | -------------------------------------------------------------------------------- /src/partials/doctype.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/partials/footer.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 |
8 |
🗑️
9 |
10 | 15 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/partials/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ricky's WWWebsite 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 119 | 120 | 121 | 124 |
125 |
126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 144 | 145 | 146 | 147 |
148 |
149 |
150 |
151 |
152 | 153 |
154 | -------------------------------------------------------------------------------- /src/partials/projects/android-drag-and-drop.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
android drag and drop
6 |
7 |
8 | 9 | close 10 | 11 |
12 |

Android Drag & Drop

13 |

14 | A proof-of-concept electron app for sending files to your android device's /sdcard/ 15 |

16 |

17 | 18 | View project on github 19 | 20 |

21 |
22 |
23 | -------------------------------------------------------------------------------- /src/partials/projects/audio-visualizer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
audio visualizer 1
6 |
audio visualizer 2
7 |
8 |
9 | 10 | close 11 | 12 |
13 |

Web Audio Visualizer

14 |

15 | Audio visualizer built with HTML5 web audio API 16 |

17 |

18 | 19 | View project on github 20 | 21 | 22 | View live examples 23 | 24 |

25 |
26 |
27 | -------------------------------------------------------------------------------- /src/partials/projects/emoji-picker.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
emoji picker
6 |
7 |
8 | 9 | close 10 | 11 |
12 |

Emoji Picker

13 |

14 | Vanilla JavaScript emoji picker that replaces the basecamp emoji string, e.g. :dog: in a text input with the unicode character equivalant, e.g. 🐶. This uses native (in browser, no bundling!) supported ES6 modules, so you may need to enable that in order for it to work. A similar feature exists in the popular cloud-based team collaboration tool Slack. 15 |

16 |

17 | 18 | View project on github 19 | 20 | 21 | View live example 22 | 23 |

24 |
25 |
26 | -------------------------------------------------------------------------------- /src/partials/projects/intel.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
intel preview
6 |
intel screen 1
7 |
intel screen 2
8 |
intel screen 3
9 |
intel screen 4
10 | 13 |
14 |
15 | 16 | close 17 | 18 |
19 |

Nowsecure INTEL

20 |

21 | “NowSecure INTEL continuously monitors the Apple App Store and Google Play store to automatically perform in-depth static, dynamic and behavioral security analysis of new and updated 3rd-party apps on real iOS and Android devices.” 22 |

23 |

24 | I built/maintained the front-end of this application for NowSecure using react, webpack and ES6. 25 |

26 |
27 |
28 | -------------------------------------------------------------------------------- /src/partials/projects/moonmoji.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
moonmoji
6 |
7 |
8 | 9 | close 10 | 11 |
12 |

Moonmoji

13 |

14 | Return an emoji representing the current moon phase. 15 |

16 |

17 | The main goal of this was to have the emoji display in a shell. This can be accomplished by adding the script to your PS1 export in your .bash_prompt or similar. 18 |

19 |

20 | 21 | View project on github 22 | 23 |

24 |
25 |
26 | -------------------------------------------------------------------------------- /src/partials/projects/retrorecord.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
luigi
6 |
megaman
7 |
street fighter
8 |
strider
9 |
xmen
10 |
11 |
12 | 13 | close 14 | 15 |
16 |

Retrorecord

17 |

18 | An application to post screenshots/record video games from my raspberry pi (RetroPie) to twitter. I originally wrote this app in JavaScript and then ported it using Rust for better performance. 19 |

20 |

21 | Each tweet interacts with various image bot accounts that respond with altered or "glitched" versions of the original content. Here's are some examples: 22 |

23 |
24 |
25 | 26 | 27 |
doom glitch
28 |
megaman glitch
29 |
30 |
31 |

32 | 33 | View project on github 34 | 35 | 38 |

39 |
40 |
41 | -------------------------------------------------------------------------------- /src/project.rs: -------------------------------------------------------------------------------- 1 | use stdweb::traits::*; 2 | use stdweb::web::event::HashChangeEvent; 3 | use stdweb::web::{document, window}; 4 | use crate::util::{get_hash, query_selector}; 5 | 6 | use crate::constants::{DATA_PROJECT, DATA_SCROLL, EMPTY, PROJECT_SELECTOR}; 7 | 8 | fn show(hash: String, scrolls: &mut Vec) { 9 | let body = document().body().unwrap(); 10 | let document_element = document().document_element(); 11 | let selector = &format!(".projects .project.{}", hash); 12 | let query = document().query_selector(selector).unwrap(); 13 | if query.is_some() { 14 | let top = Some(window().page_y_offset()) 15 | .unwrap_or_else(|| document_element.unwrap().scroll_top()); 16 | body.set_attribute(DATA_PROJECT, &hash).unwrap(); 17 | scrolls.push(top); 18 | query_selector(PROJECT_SELECTOR).set_scroll_top(top) 19 | } 20 | } 21 | 22 | fn hide(scrolls: &mut Vec) { 23 | let body = document().body().unwrap(); 24 | let document_element = document().document_element(); 25 | let top = *scrolls.last().unwrap_or(&0.00); 26 | body.remove_attribute(DATA_PROJECT); 27 | document_element.unwrap().set_scroll_top(top); 28 | body.set_scroll_top(top); 29 | body.remove_attribute(DATA_SCROLL) 30 | } 31 | 32 | fn toggle(scrolls: &mut Vec) { 33 | let hash = get_hash(); 34 | if hash != EMPTY { 35 | self::show(hash, scrolls) 36 | } else { 37 | self::hide(scrolls) 38 | } 39 | } 40 | 41 | pub(crate) struct ToggleProject(); 42 | 43 | impl ToggleProject { 44 | pub(crate) fn new() -> ToggleProject { 45 | let mut scrolls = Vec::new(); 46 | self::toggle(&mut scrolls); 47 | let toggle_project_event = move |_event: HashChangeEvent| self::toggle(&mut scrolls); 48 | window().add_event_listener(toggle_project_event); 49 | 50 | ToggleProject() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/project_slideshows.rs: -------------------------------------------------------------------------------- 1 | use stdweb::traits::*; 2 | use stdweb::unstable::TryInto; 3 | use stdweb::web::event::{ClickEvent, KeyUpEvent}; 4 | use stdweb::web::{document, window, HtmlElement, Node}; 5 | use crate::util::{create_element, node_list}; 6 | 7 | use crate::constants::{ 8 | A, ARROW_LEFT, ARROW_RIGHT, CONTROLS, DATA_INDEX, DATA_PROJECT, DIV, EMPTY, ESC, LINK, NEXT, 9 | PREV, SLIDESHOW_SELECTOR, UNDERSCORE, 10 | }; 11 | 12 | fn get_data_index(element: &HtmlElement) -> usize { 13 | element.get_attribute(DATA_INDEX).unwrap().parse().unwrap() 14 | } 15 | 16 | fn set_data_index_attribute(element: &HtmlElement, attribute: &str) { 17 | element.set_attribute(DATA_INDEX, attribute).unwrap(); 18 | } 19 | 20 | fn get_increment(direction: &str, data_index: usize, last: usize) -> usize { 21 | let increment = if direction == PREV { 22 | if data_index == 0 { 23 | last 24 | } else { 25 | data_index - 1 26 | } 27 | } else { 28 | if data_index == last { 29 | 0 30 | } else { 31 | data_index + 1 32 | } 33 | }; 34 | 35 | increment 36 | } 37 | 38 | pub(crate) struct Controls(); 39 | 40 | impl Controls { 41 | pub(crate) fn new(slideshow_el: &HtmlElement, slides: &Vec) -> Controls { 42 | let controls_el = create_element(DIV, CONTROLS); 43 | 44 | let control_setup = |index: usize| { 45 | let control_el = create_element(A, LINK); 46 | control_el.set_text_content(&(index + 1).to_string()); 47 | control_el.add_event_listener(enclose!( (slideshow_el, index) move |event:ClickEvent| { 48 | event.prevent_default(); 49 | slideshow_el.set_attribute(DATA_INDEX, &index.to_string()).unwrap(); 50 | })); 51 | controls_el.append_child(&control_el); 52 | }; 53 | 54 | for (index, _slide) in slides.iter().enumerate() { 55 | control_setup(index) 56 | } 57 | 58 | slideshow_el 59 | .parent_node() 60 | .unwrap() 61 | .append_child(&controls_el); 62 | 63 | Controls() 64 | } 65 | } 66 | 67 | pub(crate) struct Slide(); 68 | 69 | impl Slide { 70 | pub(crate) fn new(node: Node) -> HtmlElement { 71 | node.try_into().unwrap() 72 | } 73 | } 74 | 75 | pub(crate) struct SlideShows(); 76 | 77 | impl SlideShows { 78 | pub(crate) fn new() -> SlideShows { 79 | // setup all slideshows 80 | for slideshow in node_list(SLIDESHOW_SELECTOR) { 81 | // collect slides 82 | let slides: Vec = slideshow 83 | .child_nodes() 84 | .into_iter() 85 | .filter(|item| item.node_name() == DIV.to_uppercase()) 86 | .map(Slide::new) 87 | .collect(); 88 | 89 | // only setup slideshow if there is more than one slide! 90 | if slides.len() > 1 { 91 | let slideshow_el: HtmlElement = slideshow.try_into().unwrap(); 92 | 93 | let slideshow_prev = create_element(A, PREV); 94 | slideshow_el.append_child(&slideshow_prev); 95 | 96 | let slideshow_next = create_element(A, NEXT); 97 | slideshow_el.append_child(&slideshow_next); 98 | 99 | Controls::new(&slideshow_el, &slides); 100 | 101 | let prev_next_click = 102 | |direction: &str, slideshow_el: &HtmlElement, slides: &Vec| { 103 | let increment = self::get_increment( 104 | direction, 105 | self::get_data_index(&slideshow_el), 106 | slides.len() - 1, 107 | ); 108 | self::set_data_index_attribute(&slideshow_el, &increment.to_string()) 109 | }; 110 | 111 | let slideshow_prev_event = enclose!( (slideshow_el, slides) move |event: ClickEvent| { 112 | event.prevent_default(); 113 | prev_next_click(&PREV, &slideshow_el, &slides) 114 | }); 115 | 116 | let slideshow_next_event = enclose!( (slideshow_el, slides) move |event: ClickEvent| { 117 | event.prevent_default(); 118 | prev_next_click(&NEXT, &slideshow_el, &slides) 119 | }); 120 | 121 | slideshow_prev.add_event_listener(slideshow_prev_event); 122 | slideshow_next.add_event_listener(slideshow_next_event); 123 | } 124 | } 125 | 126 | // use keyboard to navigate 127 | let next_prev_click = |selector: &str| { 128 | if document().query_selector(selector).unwrap().is_some() { 129 | js!( document.querySelector(@{selector}).click(); ); 130 | } 131 | }; 132 | 133 | let determine_key = |key: String| match key.as_ref() { 134 | ARROW_LEFT => PREV, 135 | ARROW_RIGHT => NEXT, 136 | _ => UNDERSCORE, 137 | }; 138 | 139 | let keyup_event = move |event: KeyUpEvent| { 140 | let data_project = document().body().unwrap().get_attribute(DATA_PROJECT); 141 | if data_project.is_some() { 142 | let key = event.key(); 143 | if key == ESC { 144 | js!( window.location.hash = @{EMPTY}; ); 145 | } else { 146 | let next_prev_key = determine_key(key); 147 | if next_prev_key != UNDERSCORE { 148 | let selector = 149 | &format!(".project.{} .{}", data_project.unwrap(), next_prev_key); 150 | next_prev_click(selector) 151 | } 152 | } 153 | } 154 | }; 155 | 156 | window().add_event_listener(keyup_event); 157 | 158 | SlideShows() 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/trash.rs: -------------------------------------------------------------------------------- 1 | use stdweb::web::event::{ 2 | DragDropEvent, DragEndEvent, DragEnterEvent, DragEvent, DragLeaveEvent, DragOverEvent, 3 | DragStartEvent, 4 | }; 5 | 6 | use stdweb::traits::*; 7 | use stdweb::web::{confirm, document, CloneKind}; 8 | use crate::util::{get_range, node_list, query_selector}; 9 | 10 | use stdweb::unstable::TryInto; 11 | use stdweb::web::{HtmlElement, Node}; 12 | 13 | use crate::constants::{ 14 | COORDINATE_SELECTOR, CRIES, CURSOR_PROJECT_SELECTOR, CURSOR_SELECTOR, DISPLAY_NONE, DRAG, 15 | DRAG_ENTER, DRAG_SELECTOR, LINK_SELECTOR, STYLE, TRASH, ZOOM, 16 | }; 17 | 18 | fn remove_drag_enter() { 19 | let coord = query_selector(COORDINATE_SELECTOR); 20 | coord.class_list().remove(DRAG_ENTER).unwrap(); 21 | coord.class_list().remove(TRASH).unwrap(); 22 | } 23 | 24 | fn reset() { 25 | self::remove_drag_enter(); 26 | let drag = document().query_selector(DRAG_SELECTOR).unwrap(); 27 | if let Some(drag) = drag { 28 | drag.class_list().remove(DRAG).unwrap(); 29 | } 30 | } 31 | 32 | fn delete() { 33 | let drag = document().query_selector(DRAG_SELECTOR).unwrap(); 34 | if let Some(drag) = drag { 35 | drag.set_attribute(STYLE, DISPLAY_NONE).unwrap() 36 | } 37 | reset() 38 | } 39 | 40 | pub(crate) struct Trash(); 41 | 42 | impl Trash { 43 | pub(crate) fn new() -> Trash { 44 | let coord = query_selector(COORDINATE_SELECTOR); 45 | let cries = CRIES; 46 | 47 | coord.add_event_listener(|event: DragOverEvent| { 48 | event.prevent_default(); 49 | }); 50 | 51 | coord.add_event_listener(|event: DragEnterEvent| { 52 | event.prevent_default(); 53 | let coord = query_selector(COORDINATE_SELECTOR); 54 | coord.class_list().add(DRAG_ENTER).unwrap(); 55 | }); 56 | 57 | coord.add_event_listener(|event: DragLeaveEvent| { 58 | event.prevent_default(); 59 | let coord = query_selector(COORDINATE_SELECTOR); 60 | coord.class_list().remove(DRAG_ENTER).unwrap(); 61 | }); 62 | 63 | coord.add_event_listener(enclose!((cries) move |event: DragDropEvent| { 64 | event.prevent_default(); 65 | let index = get_range(0 as f64, cries.len() as f64) as usize; 66 | let okay = confirm(&cries[index].to_string()); 67 | if okay { 68 | self::delete() 69 | } else { 70 | self::reset() 71 | } 72 | })); 73 | 74 | fn bind_link(link: Node) { 75 | let el: HtmlElement = link.clone().try_into().unwrap(); 76 | let drag_event = |event: DragEvent| { 77 | let cursor = query_selector(CURSOR_SELECTOR); 78 | let x = f64::from(event.client_x()); 79 | let y = f64::from(event.client_y()); 80 | cursor 81 | .set_attribute( 82 | STYLE, 83 | &format!("transform: translate3d({}px,{}px,0);", x, y), 84 | ) 85 | .unwrap(); 86 | }; 87 | 88 | link.add_event_listener(enclose!( (el) move |_event: DragStartEvent| { 89 | let coord = query_selector(COORDINATE_SELECTOR); 90 | coord.class_list().add(TRASH).unwrap(); 91 | let cursor = query_selector(CURSOR_SELECTOR); 92 | cursor.class_list().remove(ZOOM).unwrap(); 93 | let clone = el.clone_node(CloneKind::Deep).unwrap(); 94 | el.class_list().add(DRAG).unwrap(); 95 | cursor.append_child(&clone); 96 | })); 97 | 98 | link.add_event_listener(drag_event); 99 | 100 | link.add_event_listener(|_event: DragEndEvent| { 101 | js! { 102 | /* TODO: fix this hack! */ 103 | var reset = @{reset}; 104 | window.setTimeout(reset, 100); 105 | } 106 | self::remove_drag_enter(); 107 | let cursor = query_selector(CURSOR_SELECTOR); 108 | let project = query_selector(CURSOR_PROJECT_SELECTOR); 109 | cursor.remove_child(&project).unwrap(); 110 | }); 111 | } 112 | 113 | for link in node_list(LINK_SELECTOR) { 114 | bind_link(link) 115 | } 116 | 117 | Trash() 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use stdweb::traits::*; 2 | use stdweb::web::{document, Date, HtmlElement, NodeList}; 3 | 4 | use rand::Rng; 5 | use stdweb::unstable::TryInto; 6 | 7 | use crate::constants::{A, HREF, CLASS, EMPTY, HASH, POOP, YEAR_SELECTOR}; 8 | 9 | pub(crate) fn node_list(selector: &str) -> NodeList { 10 | document().query_selector_all(selector).unwrap() 11 | } 12 | 13 | pub(crate) fn create_element(element_type: &str, class: &str) -> HtmlElement { 14 | let el = document().create_element(element_type).unwrap(); 15 | if element_type == A.to_string() { 16 | el.set_attribute(HREF, HASH).unwrap(); 17 | } 18 | el.set_attribute(CLASS, class).unwrap(); 19 | el.try_into().unwrap() 20 | } 21 | 22 | pub(crate) fn get_hash() -> String { 23 | document() 24 | .location() 25 | .unwrap() 26 | .hash() 27 | .unwrap() 28 | .replace(HASH, EMPTY) 29 | } 30 | 31 | pub(crate) fn query_selector(selector: &str) -> HtmlElement { 32 | let target = document().query_selector(selector).unwrap(); 33 | 34 | if target.is_none() { 35 | panic!(POOP) 36 | } 37 | 38 | target.unwrap().try_into().unwrap() 39 | } 40 | 41 | pub(crate) fn get_range(start: f64, end: f64) -> f64 { 42 | let mut rng = rand::thread_rng(); 43 | rng.gen_range(start, end) as f64 44 | } 45 | 46 | pub(crate) fn set_date() { 47 | query_selector(YEAR_SELECTOR).set_text_content(&Date::new().get_full_year().to_string()); 48 | } 49 | -------------------------------------------------------------------------------- /src/work_history.rs: -------------------------------------------------------------------------------- 1 | use stdweb::traits::*; 2 | use stdweb::unstable::TryInto; 3 | use stdweb::web::{document, Element}; 4 | use stdweb::web::event::ClickEvent; 5 | 6 | use crate::constants::WORK_HISTORY_SELECTOR; 7 | 8 | fn scroll_into_view(el: Element) { 9 | js! { @(no_return) 10 | let el = @{el}; 11 | setTimeout(() => { 12 | el.scrollIntoView(); 13 | }, 10); 14 | } 15 | } 16 | 17 | pub(crate) struct WorkHistory(); 18 | 19 | impl WorkHistory { 20 | pub(crate) fn new() -> WorkHistory { 21 | let details = document() 22 | .query_selector(WORK_HISTORY_SELECTOR) 23 | .unwrap() 24 | .unwrap(); 25 | let click_event = enclose!( (details) move |_: ClickEvent| { 26 | let clone = details.clone(); 27 | let is_open: bool = js!( return @{&details}.open; ) 28 | .try_into() 29 | .unwrap(); 30 | 31 | if !is_open { 32 | self::scroll_into_view(clone) 33 | } 34 | }); 35 | details.add_event_listener(click_event); 36 | WorkHistory() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /static/.well-known/dat: -------------------------------------------------------------------------------- 1 | dat://9e6613e2076dd1f247df437c21057fc59d768f1f0729efacec93d30d98f72b48 2 | TTL=3600 3 | -------------------------------------------------------------------------------- /static/avi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickycodes/www/6f51dbbacd04c27fa56989f0fb495c67a645a7eb/static/avi.jpg -------------------------------------------------------------------------------- /static/dat.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "rickycodes.hashbase.io (draft)", 3 | "description": "online wwwebsite of software developer Ricky Miller", 4 | "url": "dat://924d316b8a73f6b24af2d3d71bc61ff8623233456deb968678ebafe94a685d6e" 5 | } 6 | -------------------------------------------------------------------------------- /static/detect.js: -------------------------------------------------------------------------------- 1 | const featureKey = 'WebAssembly'; 2 | const ASCII = 'cat.txt'; 3 | const src = 'rickycodes.js'; 4 | 5 | const options = [ 6 | "It is certain.", 7 | "It is decidedly so.", 8 | "Without a doubt.", 9 | "Yes - definitely.", 10 | "You may rely on it.", 11 | "As I see it, yes.", 12 | "Most likely.", 13 | "Outlook good.", 14 | "Yes.", 15 | "Signs point to yes.", 16 | "Reply hazy, try again.", 17 | "Ask again later.", 18 | "Better not tell you now.", 19 | "Cannot predict now.", 20 | "Concentrate and ask again.", 21 | "Don't count on it.", 22 | "My reply is no.", 23 | "My sources say no.", 24 | "Outlook not so good.", 25 | "Very doubtful." 26 | ]; 27 | 28 | const ask = q => console.log(options[Math.floor(Math.random() * options.length)]); 29 | 30 | // WebAssembly Feature Detection 31 | (async function () { 32 | if (featureKey in window) { 33 | var script = document.createElement('script') 34 | await script.setAttribute('src', src) 35 | await fetch(ASCII) 36 | .then(response => response.text()) 37 | .then(data => console.log(data)) 38 | await document.body.appendChild(script) 39 | } else { 40 | // womp womp 41 | var els = ['._projects', '.projects', '.coord'] 42 | els.forEach(function (selector) { 43 | var el = document.querySelector(selector) 44 | el.style.display = 'none' 45 | }) 46 | } 47 | })(); 48 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickycodes/www/6f51dbbacd04c27fa56989f0fb495c67a645a7eb/static/favicon.png -------------------------------------------------------------------------------- /static/gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickycodes/www/6f51dbbacd04c27fa56989f0fb495c67a645a7eb/static/gradient.png -------------------------------------------------------------------------------- /static/noscript.css: -------------------------------------------------------------------------------- 1 | ._projects, .projects, .coord { 2 | display: none; 3 | } 4 | 5 | @keyframes wat { 6 | 0% { 7 | transform: translate3d(0,-100%,0); 8 | } 9 | 100% { 10 | transform: translate3d(0,0,0); 11 | } 12 | } 13 | 14 | .wat { 15 | padding: 1em; 16 | background: red; 17 | color: white; 18 | display: block; 19 | width: 100%; 20 | text-align: center; 21 | animation: wat .6s ease-out; 22 | } 23 | -------------------------------------------------------------------------------- /static/projects.css: -------------------------------------------------------------------------------- 1 | [data-project] > .content { 2 | height: 100vh; 3 | overflow-y: hidden; 4 | } 5 | 6 | .projects { 7 | position: fixed; 8 | top: 0; 9 | left: 0; 10 | width: 100%; 11 | height: 100%; 12 | z-index: 3; 13 | pointer-events: none; 14 | display: block; 15 | } 16 | 17 | [data-project] .projects { 18 | pointer-events: auto; 19 | } 20 | 21 | .projects .project { 22 | position: absolute; 23 | width: calc(100% - 4em); 24 | margin-left: 4em; 25 | height: inherit; 26 | top: 0; 27 | z-index: 4; 28 | overflow-y: scroll; 29 | transform: translate3d(100%,0,0) rotateX(0); 30 | transition: all .3s ease-out; 31 | background: white; 32 | } 33 | 34 | [data-project='intel'] .projects .project.intel, 35 | [data-project='moonmoji'] .projects .project.moonmoji, 36 | [data-project='retrorecord'] .projects .project.retrorecord, 37 | [data-project='emoji-picker'] .projects .project.emoji-picker, 38 | [data-project='android-drag-and-drop'] .projects .project.android-drag-and-drop, 39 | [data-project='winamp-skins'] .projects .project.winamp-skins, 40 | [data-project='audio-visualizer'] .projects .project.audio-visualizer { 41 | transform: translate3d(0%,0,0) rotateX(0); 42 | } 43 | 44 | .projects .project .close-link { 45 | display: block; 46 | z-index: 3; 47 | overflow: hidden; 48 | border: none; 49 | position: absolute; 50 | width: 60px; 51 | height: 60px; 52 | top: 0; 53 | right: 0; 54 | transition: background-color .3s ease-out; 55 | background-color: black; 56 | } 57 | 58 | .projects .project .close-link img { 59 | width: 100%; 60 | height: 100%; 61 | } 62 | 63 | .projects .project .close-link:hover { 64 | background-color: dimgrey; 65 | } 66 | 67 | .projects .project .content { 68 | padding-top: 0; 69 | } 70 | 71 | .projects .close { 72 | visibility: hidden; 73 | position: fixed; 74 | top: 0; 75 | left: 0; 76 | width: 100%; 77 | height: 100%; 78 | background: black; 79 | opacity: .4; 80 | } 81 | 82 | .projects .close div { 83 | width: 4em; 84 | height: 100%; 85 | } 86 | 87 | [data-project] .close { 88 | visibility: visible; 89 | cursor: none; 90 | } 91 | 92 | .projects .close.hidden { 93 | display: none; 94 | } 95 | 96 | /* consider putting this in another stylesheet as well? */ 97 | 98 | .slideshow { 99 | position: relative; 100 | height: 520px; 101 | border-top: 2px solid lightgrey; 102 | border-bottom: 2px solid lightgrey; 103 | overflow: hidden; 104 | background: radial-gradient(white, rgb(0,0,0,.1)); 105 | } 106 | 107 | .slideshow .prev, 108 | .slideshow .next { 109 | position: absolute; 110 | top: 0; 111 | width: 40%; 112 | background: transparent; 113 | border: none; 114 | height: 100%; 115 | z-index: 3; 116 | cursor: none; 117 | } 118 | 119 | .slideshow .prev { 120 | left: 0; 121 | } 122 | .slideshow .next { 123 | right: 0; 124 | } 125 | 126 | .slideshow div { 127 | /* slides */ 128 | position: absolute; 129 | left: 0; 130 | top: 0; 131 | width: 100%; 132 | height: 100%; 133 | text-align: center; 134 | z-index: 1; 135 | } 136 | 137 | .slideshow div * { 138 | height: 100%; 139 | transition: opacity .3s ease-out; 140 | opacity: 0; 141 | } 142 | 143 | .slideshow[data-index='0'] div:nth-child(1) *, 144 | .slideshow[data-index='1'] div:nth-child(2) *, 145 | .slideshow[data-index='2'] div:nth-child(3) *, 146 | .slideshow[data-index='3'] div:nth-child(4) *, 147 | .slideshow[data-index='4'] div:nth-child(5) * { 148 | opacity: 1; 149 | } 150 | 151 | .slideshow-container .controls { 152 | margin: 2em 2em 0 0; 153 | } 154 | 155 | .project > .slideshow-container .controls { 156 | margin-left: 2em; 157 | } 158 | 159 | .slideshow-container .controls .link { 160 | display: inline-block; 161 | padding: 0 1em 0 1em; 162 | margin-right: 1em; 163 | font-size: .8em; 164 | cursor: pointer; 165 | } 166 | 167 | .slideshow[data-index='0'] + .controls .link:nth-child(1), 168 | .slideshow[data-index='1'] + .controls .link:nth-child(2), 169 | .slideshow[data-index='2'] + .controls .link:nth-child(3), 170 | .slideshow[data-index='3'] + .controls .link:nth-child(4), 171 | .slideshow[data-index='4'] + .controls .link:nth-child(5) { 172 | background: black; 173 | color: white; 174 | pointer-events: none; 175 | } 176 | 177 | .slideshow-container .slideshow div img { 178 | object-fit: contain; 179 | max-width: 100%; 180 | user-select: none; 181 | } 182 | -------------------------------------------------------------------------------- /static/script.js: -------------------------------------------------------------------------------- 1 | /* global IntersectionObserver */ 2 | (function () { 3 | var loadDeferredStyles = function () { 4 | var addStylesNode = document.querySelector('.deferred') 5 | var replacement = document.createElement('div') 6 | replacement.innerHTML = addStylesNode.textContent 7 | document.body.appendChild(replacement) 8 | addStylesNode.parentElement.removeChild(addStylesNode) 9 | } 10 | 11 | var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || 12 | window.webkitRequestAnimationFrame || window.msRequestAnimationFrame 13 | 14 | var lazyLoadImages = function () { 15 | var lazyImages = [].slice.call(document.querySelectorAll('img[data-src]')) 16 | if ('IntersectionObserver' in window) { 17 | let lazyImageObserver = new IntersectionObserver(function (entries, observer) { 18 | entries.forEach(function (entry) { 19 | if (entry.isIntersecting) { 20 | let lazyImage = entry.target 21 | lazyImage.src = lazyImage.dataset.src 22 | lazyImageObserver.unobserve(lazyImage) 23 | } 24 | }) 25 | }) 26 | lazyImages.forEach(function (img) { 27 | lazyImageObserver.observe(img) 28 | }) 29 | } else { 30 | lazyImages.forEach(function (img) { 31 | img.setAttribute('src', img.getAttribute('data-src')) 32 | img.onload = function () { 33 | this.removeAttribute('data-src') 34 | } 35 | }) 36 | } 37 | } 38 | 39 | var attachScript = function (source) { 40 | var script = document.createElement('script') 41 | script.setAttribute('src', source.src) 42 | 'type' in source && script.setAttribute('type', source.type) 43 | document.body.appendChild(script) 44 | } 45 | 46 | var attachScripts = function () { 47 | var scripts = [ 48 | { src: 'detect.js' }, 49 | { type: 'async', src: 'https://www.googletagmanager.com/gtag/js?id=UA-71959023-1' } 50 | ] 51 | 52 | scripts.map(attachScript) 53 | } 54 | 55 | var setupGTag = function () { 56 | window.dataLayer = window.dataLayer || [] 57 | function gtag () { window.dataLayer.push(arguments) } 58 | gtag('js', new Date()) 59 | gtag('config', 'UA-71959023-1') 60 | } 61 | 62 | var initialize = function () { 63 | if (raf) raf(function () { window.setTimeout(loadDeferredStyles, 0) }) 64 | else window.addEventListener('load', loadDeferredStyles) 65 | setupGTag() 66 | lazyLoadImages() 67 | attachScripts() 68 | } 69 | 70 | document.addEventListener('DOMContentLoaded', initialize) 71 | })() 72 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | @keyframes rgbshift { 2 | 0% { 3 | text-shadow: 3px 5px 0px #bbb; 4 | box-shadow: 3px 5px 0px #bbb; 5 | opacity: 1; 6 | } 7 | 4% { 8 | text-shadow: 1px 1px 0px rgba(255, 0, 0, .5), 9 | -3px -1px 0px rgba(0, 255, 0, .5), 10 | -1px 2px 0px rgba(0, 0, 255, .5); 11 | box-shadow: 1px 1px 0px rgba(255, 0, 0, .5), 12 | -3px -1px 0px rgba(0, 255, 0, .5), 13 | -1px 2px 0px rgba(0, 0, 255, .5); 14 | opacity: 1; 15 | } 16 | 7% { 17 | text-shadow: 4px -1px 0px rgba(255, 0, 0, .5), 18 | -3px -1px 0px rgba(0, 255, 0, .5), 19 | 1px -1px 0px rgba(0, 0, 255, .5); 20 | box-shadow: 4px -1px 0px rgba(255, 0, 0, .5), 21 | -3px -1px 0px rgba(0, 255, 0, .5), 22 | 1px -1px 0px rgba(0, 0, 255, .5); 23 | opacity: 0.6; 24 | } 25 | 9% { 26 | text-shadow: none; 27 | box-shadow: none; 28 | opacity: 1; 29 | } 30 | 58% { 31 | text-shadow: none; 32 | box-shadow: none; 33 | opacity: 1; 34 | } 35 | 59% { 36 | text-shadow: -3px -4px 0px #bbb; 37 | box-shadow: -3px -4px 0px #bbb; 38 | opacity: 0.6; 39 | } 40 | 62% { 41 | text-shadow: 4px -1px 0px rgba(255, 0, 0, .5), 42 | -3px -1px 0px rgba(0, 255, 0, .5), 43 | 1px -1px 0px rgba(0, 0, 255, .5); 44 | box-shadow: 4px -1px 0px rgba(255, 0, 0, .5), 45 | -3px -1px 0px rgba(0, 255, 0, .5), 46 | 1px -1px 0px rgba(0, 0, 255, .5); 47 | opacity: 1; 48 | } 49 | 67% { 50 | text-shadow: none; 51 | box-shadow: none; 52 | } 53 | } 54 | 55 | .canvas { 56 | width: 100%; 57 | height: 100%; 58 | } 59 | 60 | .x, .y { 61 | position: absolute; 62 | border-style: solid; 63 | border-width: 1px; 64 | border-color: lightgrey; 65 | } 66 | 67 | .x { 68 | top: 0; 69 | height: 100%; 70 | border-left: 1px; 71 | margin-left: -1px; 72 | } 73 | 74 | .y { 75 | left: 0; 76 | width: 100%; 77 | border-top: 1px; 78 | } 79 | 80 | .info, .coord { 81 | position: fixed; 82 | z-index: 3; 83 | background: black; 84 | padding: 1em 2em; 85 | color: white; 86 | font-size: .8em; 87 | font-family: 'Roboto Mono', monospace; 88 | text-align: right; 89 | } 90 | 91 | .coord { 92 | bottom: 0; 93 | right: 0; 94 | transition: all .3s ease-out; 95 | } 96 | 97 | /* put me in the trash */ 98 | .coord .can { 99 | display: none; 100 | pointer-events: none; 101 | } 102 | 103 | .coord.trash { 104 | font-size: 60px; 105 | } 106 | 107 | .coord.dragenter { 108 | background: lightgrey; 109 | font-size: 80px; 110 | } 111 | 112 | .coord.trash > div { 113 | display: none; 114 | pointer-events: none; 115 | } 116 | 117 | .coord.trash .can { 118 | display: block; 119 | } 120 | 121 | .info { 122 | display: block; 123 | top: 0; 124 | right: 0; 125 | transform: translate3d(0,0,0); 126 | transition: transform .3s ease-out; 127 | } 128 | 129 | .info.hidden { 130 | transform: translate3d(0,-100%,0); 131 | } 132 | 133 | ul { 134 | margin: 0; 135 | padding: 0 2em; 136 | } 137 | 138 | footer { 139 | font-size: .8em; 140 | margin-top: 4em; 141 | padding-top: 1em; 142 | border-top: 2px solid black; 143 | font-family: 'Roboto Mono', monospace; 144 | } 145 | 146 | .link, .content .meta a { 147 | font-family: 'Roboto Mono', monospace; 148 | display: inline-block; 149 | vertical-align: middle; 150 | transform: perspective(1px) translateZ(0); 151 | box-shadow: 0 0 1px transparent; 152 | position: relative; 153 | transition-property: color; 154 | transition-duration: .3s; 155 | padding: 0 1em; 156 | margin: 0 .5em .5em 0; 157 | border: 2px solid black; 158 | border-radius: 100px; 159 | overflow: hidden; 160 | text-shadow: none; 161 | font-size: .8em; 162 | } 163 | 164 | .link:before { 165 | content: ""; 166 | position: absolute; 167 | z-index: -1; 168 | top: 0; 169 | left: 0; 170 | right: 0; 171 | bottom: 0; 172 | background: black; 173 | transform: scaleX(0); 174 | transform-origin: 0 50%; 175 | transition: transform .3s ease-out; 176 | } 177 | 178 | .link:hover { 179 | color: white; 180 | border: 2px solid black; 181 | animation: visible 0.2s, rgbshift 3s infinite; 182 | } 183 | 184 | .link:hover:before { 185 | transform: scaleX(1); 186 | } 187 | 188 | .link.project { 189 | cursor: none; 190 | } 191 | 192 | .link.cv { 193 | cursor: pointer; 194 | } 195 | 196 | code { 197 | padding: 0.2em 0.4em; 198 | background-color: rgba(27,31,35,0.05); 199 | border-radius: 3px; 200 | text-transform: none; 201 | } 202 | 203 | .work-history ul { 204 | margin: 0; 205 | padding: 0; 206 | list-style: none; 207 | } 208 | 209 | .work-history ul li ul { 210 | list-style-type: disc; 211 | margin-block-start: 1em; 212 | margin-block-end: 1em; 213 | margin-inline-start: 0px; 214 | margin-inline-end: 0px; 215 | padding-inline-start: 40px; 216 | font-size: 80%; 217 | } 218 | 219 | .work-history summary { 220 | list-style: none; 221 | } 222 | 223 | .work-history summary::after { 224 | content: '+'; 225 | margin: 0 0 0 10px; 226 | } 227 | 228 | .work-history[open] summary::after { 229 | content: '-'; 230 | } 231 | 232 | .work-history summary::-webkit-details-marker { 233 | display: none; 234 | } 235 | 236 | .cursor { 237 | position: fixed; 238 | z-index: 4; 239 | pointer-events: none; 240 | mix-blend-mode: difference; 241 | } 242 | 243 | .cursor > div { 244 | transform: scale(0.6); 245 | position: absolute; 246 | } 247 | 248 | .cursor .link.project { 249 | pointer-events: none; 250 | position: relative; 251 | left: -50%; 252 | top: -26px; 253 | } 254 | 255 | .cursor svg { 256 | position: absolute; 257 | top: -150px; 258 | left: -150px; 259 | display: none; 260 | filter: drop-shadow( 0 0 6px rgba(255, 255, 255, 1)) 261 | drop-shadow( 0 0 10px rgba(255, 255, 255, .5)); 262 | } 263 | 264 | .cursor .project { 265 | z-index: 4; 266 | background-color: white; 267 | mix-blend-mode: normal; 268 | } 269 | 270 | .cursor.prev .cursor-prev { 271 | left: -200px; 272 | } 273 | 274 | .cursor.next .cursor-next { 275 | left: -100px; 276 | } 277 | 278 | .cursor.close .cursor-close, 279 | .cursor.prev .cursor-prev, 280 | .cursor.next .cursor-next, 281 | .cursor.zoom .cursor-zoom, 282 | .cursor.tw .cursor-tw, 283 | .cursor.gh .cursor-gh { 284 | display: block; 285 | } 286 | 287 | .content .meta {} 288 | .content .meta a { 289 | display: inline-block; 290 | padding: 0 1em 0 1em; 291 | margin-right: 1em; 292 | font-size: .6em; 293 | cursor: pointer; 294 | display: block; 295 | } 296 | 297 | .content .meta a.github, .content .meta a.twitter { 298 | cursor: none; 299 | } 300 | 301 | @media (max-width: 720px) { 302 | a.webring { 303 | display: block; 304 | float: none; 305 | } 306 | 307 | .content, .projects .project { 308 | width: 100%; 309 | } 310 | 311 | .projects .project { 312 | margin-left: 0; 313 | } 314 | 315 | .slideshow { 316 | height: 260px; 317 | } 318 | 319 | .cursor, .info, .coord, .x, .y { 320 | display: none; 321 | } 322 | 323 | html, body { 324 | font-size: 1em; 325 | } 326 | } 327 | --------------------------------------------------------------------------------