Compare commits
613 commits
erynofwale
...
main
Author | SHA1 | Date | |
---|---|---|---|
26dc35e0d0 | |||
f95940c387 | |||
588d88a0b2 | |||
1d15db8aa5 | |||
2688e4e339 | |||
69760a802d | |||
294226cfcb | |||
ea6eea2b03 | |||
459c55d344 | |||
c9d7c6409e | |||
33da073783 | |||
ee9dc5c9ea | |||
c2d860a64d | |||
c878785635 | |||
0e2b3eb249 | |||
9633b85411 | |||
24871ecf49 | |||
eb9d763638 | |||
2016028811 | |||
493947028c | |||
24c61c8cdf | |||
a8c30e1c90 | |||
b57fd9c8d0 | |||
1f2de30c60 | |||
cc405e6ca9 | |||
724f2a37d4 | |||
86259d788c | |||
2bad4f9b08 | |||
85de90bcd3 | |||
5a0dcb8a7d | |||
bef5ab71ce | |||
1c78ce3627 | |||
a777b1e726 | |||
64e8aa20d5 | |||
49d8e069db | |||
f2e835cea5 | |||
65410d9de5 | |||
fd890b7486 | |||
741163dac8 | |||
89abc0b8ee | |||
b732238c20 | |||
a761f45afd | |||
9e2e49d944 | |||
5989af5e64 | |||
264a828089 | |||
9d50a93313 | |||
b463f8df64 | |||
471a63463b | |||
e123b3c38e | |||
c25987e3c5 | |||
7fd193d723 | |||
78628c2243 | |||
dc09f14cc1 | |||
a3b33970ed | |||
6f3a30cf4b | |||
712d40177d | |||
fd51d2405c | |||
fa19112a4b | |||
bf0c8a583a | |||
7807e901f7 | |||
01d2e7f471 | |||
55b89dfc93 | |||
534c5fa984 | |||
b0f7385daf | |||
6db1174a21 | |||
22dbdceab3 | |||
9db2f3a49b | |||
d37d924219 | |||
0877f504b3 | |||
3f436085a3 | |||
eb0a6b0da9 | |||
a1c22b3491 | |||
734fe626a3 | |||
6ae0590721 | |||
74d4c1a508 | |||
4d066582b8 | |||
2061992be1 | |||
ec13456966 | |||
807927a0de | |||
e7440684d7 | |||
b9796ab096 | |||
8ec88e6c80 | |||
cc98666b2d | |||
f6955df4f9 | |||
6b8cb0fb22 | |||
a90ebf15f6 | |||
b2eb00a0a8 | |||
11d8ac0b07 | |||
bb2e56cb47 | |||
80d1f8106a | |||
052152193d | |||
6edd649fb6 | |||
9e0df5797b | |||
622c09c472 | |||
7ab4ae1864 | |||
e3b93e5e0f | |||
73aaf24f91 | |||
3b4297c335 | |||
84fce9d862 | |||
ace538d953 | |||
a9f52aad98 | |||
f83c6ebbe5 | |||
b7fb364862 | |||
2d6912aed2 | |||
9ce6362402 | |||
a30903c8cf | |||
8afadd8379 | |||
dd2eec20bc | |||
f82a864f5e | |||
a5093be13a | |||
96410c903c | |||
a968c9cb78 | |||
8105b0e16b | |||
122e55b1fa | |||
cb16a35020 | |||
11d4dcd590 | |||
1f53428932 | |||
d87438bf6a | |||
f47c41a80d | |||
0cebee344a | |||
d59f2e0a69 | |||
2104b5354b | |||
0b67da2557 | |||
8483704e73 | |||
15bec870da | |||
8cb1187aaa | |||
7d50f86bcf | |||
32c41daea1 | |||
54782cc1f7 | |||
0a0f007a11 | |||
8010f10396 | |||
126dc58e7c | |||
0cef7a7903 | |||
602f5fa26c | |||
855c5b719e | |||
aa14214f73 | |||
d42176c918 | |||
f15644810c | |||
d1c47448ab | |||
fd6fd1c3ca | |||
cd789e9dfe | |||
d1c2a3b4c8 | |||
8bfa3fc8bf | |||
d679ae6c39 | |||
9b682a5d96 | |||
3fba9450ee | |||
0d4a5ea214 | |||
6343452a62 | |||
e72f8136ab | |||
d6e56e2ab7 | |||
612a0425a2 | |||
5e58416bc6 | |||
d5296995de | |||
2b613de769 | |||
b72441b9b1 | |||
e65a680b49 | |||
0e2c7c2dac | |||
796371e10f | |||
b163b64e47 | |||
1d4969a06b | |||
300829b9eb | |||
71cb4e9d2f | |||
5c2942bc56 | |||
f55b445c1f | |||
7274f9ce74 | |||
f19de6f8fc | |||
310d4177c1 | |||
093571e1cd | |||
9e3f7d1a69 | |||
45449c3b20 | |||
f5b2b1e67a | |||
750dd6179b | |||
7b353e2fa8 | |||
f95ac34c7c | |||
9f2eacfaeb | |||
4da0aeca1a | |||
830f22d6ba | |||
3766168bea | |||
187bc15a6f | |||
3bb8917116 | |||
3369b52735 | |||
d496238184 | |||
8e8881c52e | |||
5510a738fa | |||
e0b2683ef9 | |||
3102dc1e56 | |||
20d53e0df2 | |||
0f200e3d09 | |||
47d1bf9ffe | |||
66adceaf24 | |||
de8101efed | |||
6398a4d0df | |||
ae9aa17e37 | |||
3e4e292a23 | |||
fa82e4f9a7 | |||
f0d0d5a136 | |||
8d7b2364db | |||
00d6181bd8 | |||
bac1ff6ea9 | |||
fd75e27423 | |||
afc5433304 | |||
9628145f56 | |||
c4f5170a09 | |||
4c44baab1b | |||
2a780a08ae | |||
bee8800070 | |||
1c897e4518 | |||
2f5b413d1e | |||
b241986a5d | |||
3174b44fbe | |||
7843ee9cdd | |||
e2d64f82f8 | |||
4166a83fcb | |||
cfe80aaeef | |||
851f676ca0 | |||
7ff50445f5 | |||
452fec15ad | |||
d7ec4ae8b6 | |||
84f8a6697f | |||
6f42d2b7e8 | |||
de3bf86764 | |||
e18453dac0 | |||
85ed759a3a | |||
dabda17c06 | |||
84382e4576 | |||
d1688c14b5 | |||
5973cd882b | |||
ac8f8781cf | |||
2098413e87 | |||
ee9382b7a9 | |||
8b7e70dc81 | |||
48df8771ab | |||
11bacea783 | |||
14f4246154 | |||
7ebf3da3bd | |||
9584896363 | |||
5f14f1cea2 | |||
34d4bee382 | |||
74ed7ba8dc | |||
ff72f94b8f | |||
f7e35417a9 | |||
5444c10dba | |||
1660f63578 | |||
087e3461d6 | |||
778416a43a | |||
992e9119f3 | |||
383234e983 | |||
e4c62f02d3 | |||
b876f5a0cd | |||
b9265b5793 | |||
d540126bfa | |||
63ddb21964 | |||
a353c4a549 | |||
ee98566283 | |||
a4a0561bc8 | |||
bcae9f135c | |||
ca8cbf8f78 | |||
4a4a941e5d | |||
5cccfb9887 | |||
3783801365 | |||
b3a38f4c7d | |||
14cf9750d4 | |||
cb3d538a54 | |||
73320e6fb3 | |||
703d6933f3 | |||
f905b16940 | |||
bfee27e7c5 | |||
2a83017324 | |||
3d25927c65 | |||
03fee87f16 | |||
d45ad449a5 | |||
bf37618430 | |||
59f5b195a5 | |||
8022a8f84a | |||
abb85f701d | |||
c56f91da2c | |||
ff2dbdb1fd | |||
a4648e7a39 | |||
2bd5fd03bb | |||
48a28a7f7a | |||
8cc207b84c | |||
864c1b673a | |||
20e76897ff | |||
17f576406f | |||
090eef5bee | |||
fe176e4dfd | |||
3a1597acec | |||
fb7fd04fe9 | |||
c642a23ec3 | |||
cf0fd27172 | |||
a9cfddec80 | |||
b7070ccfd5 | |||
6f132cc04e | |||
1fac613c37 | |||
d18edef261 | |||
1ce9e2b417 | |||
0262f79509 | |||
42dc062147 | |||
d6e5c2a978 | |||
162e17339c | |||
2331e37e62 | |||
dbb202d7f9 | |||
b95120c602 | |||
16f96558cc | |||
4ea71e2219 | |||
4723da8289 | |||
77f6e16d9a | |||
755de7d938 | |||
931afe5b68 | |||
59da399fac | |||
345d5175c9 | |||
30ec64dd66 | |||
01d27411d7 | |||
f4ed16c706 | |||
4576de0c8d | |||
c94c4a6c95 | |||
851c74b2c9 | |||
f950cdbbd9 | |||
1dc243c95f | |||
8aed72a94d | |||
ea9fb02aaa | |||
3bcdd7edb7 | |||
013d707f50 | |||
61047d1c26 | |||
43e3cc33b3 | |||
ead226cccc | |||
629bbe0c5c | |||
041000ce37 | |||
9372bf3093 | |||
751f1c7d81 | |||
d407d9d15a | |||
30b18bd969 | |||
d685f2e647 | |||
a4f54d34f6 | |||
c33d82c0b7 | |||
2fec92c904 | |||
57ce171455 | |||
afc6e81b70 | |||
cce2a1670e | |||
64f6161d72 | |||
4604717794 | |||
f93752e805 | |||
c21398f449 | |||
bf23ba41a9 | |||
4d14ea9ab2 | |||
6740e7b61d | |||
da64dd30d5 | |||
68618884e8 | |||
f70768ecbf | |||
6a0193c002 | |||
1e851f7061 | |||
039efa3c23 | |||
5fe0139b93 | |||
ff615a757c | |||
4f63fab916 | |||
1713d5f849 | |||
52a79500c0 | |||
82db8ebfd1 | |||
8029f97f9c | |||
273c380fe6 | |||
f2800b0619 | |||
fc35499d6c | |||
0e81f019a7 | |||
862b4ce77e | |||
ba2f868e06 | |||
b3a353c2a8 | |||
6a6c638b76 | |||
ca1bb982ca | |||
d6e44d66ef | |||
ecd6fbb155 | |||
fc195c6008 | |||
e346a905fa | |||
37fecd7dfb | |||
174e5bc784 | |||
a5f5909bef | |||
a990aa335a | |||
0b754a7d27 | |||
727a6ffd59 | |||
8296cc6e43 | |||
cec1576d75 | |||
ac6b5125eb | |||
40274f037d | |||
347c00a054 | |||
af76a72b57 | |||
fb71dde50b | |||
89323983cc | |||
6a1acdf9bc | |||
cb81836279 | |||
70cce64fc2 | |||
9c02e5fa12 | |||
f640f8cd28 | |||
53be965773 | |||
143c148f7a | |||
66d50f773b | |||
f21ac49b08 | |||
6eeeb11ab4 | |||
3b3e38928b | |||
b8434f4b2a | |||
8ce8089474 | |||
29c402c3ec | |||
2501cac13a | |||
01ca8d8cfb | |||
1b02d081e3 | |||
ec96e17acd | |||
51d9a5b61f | |||
15e0caa603 | |||
b9869c11f0 | |||
3eb3217092 | |||
b1103ab027 | |||
50c181f2fd | |||
7f48047610 | |||
59bb6d28d0 | |||
5f6ae6c603 | |||
d0757067ed | |||
cb025398b3 | |||
5fd6f473dd | |||
17ec17b1e7 | |||
c256179803 | |||
84bf9b3a32 | |||
3014a7c694 | |||
dabe54e581 | |||
0010e65428 | |||
9f7f0f84c3 | |||
3920d8a415 | |||
78bd1e4a4e | |||
533bc1138c | |||
15165db3c6 | |||
a2653e185c | |||
74ae4174e0 | |||
d2709de6d7 | |||
f9749738f3 | |||
01fb022322 | |||
02b0224247 | |||
4254726189 | |||
61be4cad61 | |||
9f1c6e1345 | |||
4f4312b944 | |||
94a09d0c81 | |||
3c25f2041a | |||
6f0f1fd337 | |||
c384c07051 | |||
78caa686c2 | |||
aa50054108 | |||
89902ccc03 | |||
e4a7550abc | |||
5517fd0110 | |||
a283cae416 | |||
b8ac3126da | |||
8a5fc9c820 | |||
2033e0c3a1 | |||
186929921b | |||
d524a7dd9e | |||
b20da51037 | |||
7f61437f92 | |||
16cf918e8e | |||
18b7ad38b5 | |||
6affc7b454 | |||
5deda1c5ee | |||
12a3fc6e59 | |||
4eb5119b46 | |||
eebfdab03f | |||
5bf157ca41 | |||
e18e207530 | |||
61b6f361d2 | |||
547e196616 | |||
c6531e618d | |||
8aaeef4d45 | |||
42a71c1ac3 | |||
d1df92aa03 | |||
e8e0a35064 | |||
97c92d209d | |||
4d4ba7bfa8 | |||
cbc1cd16f0 | |||
d68e76478d | |||
4ca71181c7 | |||
87e60d53f6 | |||
c13e5a62c3 | |||
39467b7a43 | |||
aaa690b777 | |||
d67ec5f99b | |||
2a4838e8a3 | |||
d88d34a00a | |||
3fe3599bc8 | |||
e263b3cb41 | |||
ab172e1e9e | |||
b7221075a3 | |||
fe8e12a2ba | |||
4231c6c5d8 | |||
b423f89809 | |||
0ca9cf45c9 | |||
4f865015e1 | |||
f3ce03ea8d | |||
75a2e3a8bf | |||
aa8c85c902 | |||
f0931bd9d1 | |||
88476a707e | |||
f1b60f443c | |||
d174ae952d | |||
2011ccdd78 | |||
828bcb0f94 | |||
5606feeca1 | |||
9e11021ae3 | |||
d2ab14f649 | |||
566c915f43 | |||
cb8742042f | |||
7c3a65542a | |||
ff65c704bd | |||
0d8f36817f | |||
3a55fbe945 | |||
2ca095e617 | |||
df62cc0374 | |||
0be0081319 | |||
f74f0c5e9d | |||
f6000d0197 | |||
e83eb2a3e5 | |||
766557433b | |||
93a92fad86 | |||
188c5fb876 | |||
2c23862cc6 | |||
1d825755ff | |||
7bbd9a3adf | |||
cc4b1df2bf | |||
2c1f64cbca | |||
19f153e706 | |||
6b800dcbf6 | |||
04c4c31f96 | |||
2c5cec4f87 | |||
8fadc54a43 | |||
19d8209cb2 | |||
4da32bb6d0 | |||
567363336d | |||
c52ac00231 | |||
41f506bb1e | |||
c0a2dd9f28 | |||
19145efa33 | |||
03899a54dc | |||
f078614848 | |||
5dbec4ec08 | |||
e0b7aae3db | |||
da88d389e9 | |||
6842511b10 | |||
fb1b4ccab3 | |||
bdf5065f17 | |||
9601e1e283 | |||
f8174ab27c | |||
963444b6b6 | |||
02dabcd008 | |||
1a10555640 | |||
ad646c89c0 | |||
81c04c7b5e | |||
877dcedbcd | |||
d3df159add | |||
e15c8488a5 | |||
2f3d133dda | |||
b2d24aad54 | |||
24122c7427 | |||
2cff513898 | |||
ce5f0ea678 | |||
543112fc51 | |||
7dcfc12ec7 | |||
c4db9126b5 | |||
9051fce9aa | |||
92854f7a0a | |||
1e6711b8f3 | |||
0f47ca61a6 | |||
3f1bd948eb | |||
aea3489ddb | |||
3ef4c7eb1b | |||
e83ba90c62 | |||
4c3a61ad5d | |||
aeafd0b530 | |||
244220b914 | |||
48056e5be8 | |||
cf5d8755ac | |||
d8e842604d | |||
8d0048bd25 | |||
55f0526f6e | |||
76e3da0de1 | |||
ecb39a9298 | |||
c5427866c6 | |||
30ba875393 | |||
fd4ab996c5 | |||
acb1cf3c35 | |||
5920625753 | |||
16af88e08a | |||
57947282ce | |||
e7b2cb493a | |||
8b4273fcae | |||
ac991083a0 | |||
0a410b0ad8 | |||
e1e63b1499 | |||
d45ad6e1ae | |||
4b3abd2a17 | |||
83f3385bf8 | |||
1e8b80481e | |||
660a1a79b5 | |||
3ec529525a | |||
1200833ef3 | |||
397c2262be | |||
27f88ab3b5 | |||
b5a22374bf | |||
edb9c49f21 | |||
35d55c1cf7 | |||
782f613d5a | |||
77d677afed | |||
d738354e41 | |||
5bfa439700 | |||
f23b047a0f | |||
b8b614e68d | |||
ff14784468 | |||
23fc76b1f5 | |||
84c7c714ed | |||
c9c5889bed |
3
.gitattributes
vendored
|
@ -1,2 +1,5 @@
|
|||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
||||
*.pxm filter=lfs diff=lfs merge=lfs -text
|
||||
*.mov filter=lfs diff=lfs merge=lfs -text
|
||||
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
||||
|
|
9
.gitignore
vendored
|
@ -1,4 +1,11 @@
|
|||
node_modules/
|
||||
public/
|
||||
resources/
|
||||
/documentation/mirrors/
|
||||
/resources/
|
||||
.hugo_build.lock
|
||||
*.log
|
||||
*.orig
|
||||
*~
|
||||
|
||||
# Backup files for Markdown files processed in-place with sed
|
||||
*.md-e
|
||||
|
|
21
.gitmodules
vendored
|
@ -1,3 +1,18 @@
|
|||
[submodule "themes/paper"]
|
||||
path = themes/paper
|
||||
url = ssh://git@github.com:erynofwales/hugo-paper.git
|
||||
[submodule "themes/platters"]
|
||||
path = themes/platters
|
||||
url = nutmeg:git/hugo-theme-platters.git
|
||||
[submodule "themes/termlite"]
|
||||
path = themes/termlite
|
||||
url = git@github.com:erynofwales/hugo-theme-termlite.git
|
||||
[submodule "themes/resource-builders"]
|
||||
path = themes/resource-builders
|
||||
url = git@github.com:erynofwales/hugo-resource-builders.git
|
||||
[submodule "themes/image-utils"]
|
||||
path = themes/image-utils
|
||||
url = git@github.com:erynofwales/hugo-image-utilities.git
|
||||
[submodule "themes/photostream"]
|
||||
path = themes/photostream
|
||||
url = git@github.com:erynofwales/hugo-theme-photostream.git
|
||||
[submodule "themes/feeds"]
|
||||
path = themes/feeds
|
||||
url = git@github.com:erynofwales/hugo-theme-feeds.git
|
||||
|
|
15
.nvim/UltiSnips/markdown.snippets
Normal file
|
@ -0,0 +1,15 @@
|
|||
snippet jp "lang jp shortcode" w
|
||||
{{< lang jp >}}$1{{< /lang >}}
|
||||
endsnippet
|
||||
|
||||
snippet jpp "lang jp shortcode with expansion" w
|
||||
{{% lang jp %}}$1{{% /lang %}}
|
||||
endsnippet
|
||||
|
||||
snippet tess "tess shortcode" w
|
||||
{{< tess >}}
|
||||
endsnippet
|
||||
|
||||
snippet ruby "ruby shortcode" w
|
||||
{{< ruby "$1" >}}$2{{< /ruby >}}
|
||||
endsnippet
|
4
.nvim/after/ftplugin/css.lua
Normal file
|
@ -0,0 +1,4 @@
|
|||
-- Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
vim.bo.shiftwidth = 2
|
||||
vim.bo.softtabstop = 2
|
6
.nvim/after/ftplugin/html.lua
Normal file
|
@ -0,0 +1,6 @@
|
|||
-- Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
local root = gitTopLevelDirectory()
|
||||
vim.opt_local.path:prepend(root .. "/assets/scripts/**")
|
||||
vim.opt_local.path:prepend(root .. "/assets/styles/**")
|
||||
vim.opt_local.path:prepend(root .. "/layouts/**")
|
4
.nvim/after/ftplugin/javascript.lua
Normal file
|
@ -0,0 +1,4 @@
|
|||
-- Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
local root = gitTopLevelDirectory()
|
||||
vim.opt_local.path:prepend(root .. "/assets/scripts/**")
|
11
.nvim/after/ftplugin/markdown.lua
Normal file
|
@ -0,0 +1,11 @@
|
|||
-- Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
vim.bo.textwidth = 80
|
||||
|
||||
vim.cmd [[
|
||||
iabbrev tokyo Tōkyō
|
||||
iabbrev Tokyo Tōkyō
|
||||
iabbrev kyoto Kyōto
|
||||
iabbrev Kyoto Kyōto
|
||||
iabbrev xx ×
|
||||
]]
|
8
.nvim/ftdetect/html.lua
Normal file
|
@ -0,0 +1,8 @@
|
|||
-- Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
local filetypedetectGroup = vim.api.nvim_create_augroup("HugoHTMLTemplates", {clear = true})
|
||||
vim.api.nvim_create_autocmd({"BufRead", "BufNewFile"}, {
|
||||
pattern = {"**/layouts/**/*.html"},
|
||||
group = filetypedetectGroup,
|
||||
command = "set ft=gohtmltmpl",
|
||||
})
|
43
Makefile
|
@ -1,9 +1,42 @@
|
|||
# Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
DEPLOY_LOCATION=eryn@nutmeg.erynwells.me:/srv/www/erynwells.me/html
|
||||
BUILD_DIR=public
|
||||
|
||||
.PHONY: deploy
|
||||
deploy:
|
||||
hugo
|
||||
rsync -avz --no-times --no-perms --delete public/ $(DEPLOY_LOCATION)
|
||||
CONTENT_PATH=content
|
||||
|
||||
DEPLOY_USER=eryn
|
||||
DEPLOY_HOSTNAME=nutmeg.erynwells.me
|
||||
DEPLOY_PATH=/srv/www/erynwells.me/html
|
||||
DEPLOY_LOCATION=$(DEPLOY_USER)@$(DEPLOY_HOSTNAME):$(DEPLOY_PATH)
|
||||
|
||||
HOSTNAME=$(shell hostname -s)
|
||||
|
||||
NETHACK_LOGFILE=$(shell command nethack --showpaths | grep scoredir | sed 's/.*"\(.*\)".*/\1/g')/logfile
|
||||
NETHACK_LOGFILE_DATA_FILE=data/nethack/logfile/$(HOSTNAME).json
|
||||
|
||||
.PHONY: site deploy clean
|
||||
|
||||
site:
|
||||
@echo "Building site"
|
||||
hugo --buildFuture --enableGitInfo --destination "$(BUILD_DIR)"
|
||||
|
||||
deploy: site
|
||||
@echo "Deploying to $(DEPLOY_LOCATION)"
|
||||
rsync -avz --no-times --no-perms --delete "$(BUILD_DIR)/" "$(DEPLOY_LOCATION)"
|
||||
git tag -f deploy-$(shell date +%Y-%m-%d)
|
||||
|
||||
deployall: nethack deploy
|
||||
|
||||
nethack: nethack-logfile nethack-commit
|
||||
|
||||
nethack-logfile: $(NETHACK_LOGFILE)
|
||||
ifeq (,$(wildcard $<))
|
||||
@echo "Importing Nethack logfile from $(NETHACK_LOGFILE)"
|
||||
scripts/import-nethack-logfile.py -o $(NETHACK_LOGFILE_DATA_FILE) $<
|
||||
endif
|
||||
|
||||
nethack-commit: $(NETHACK_LOGFILE_DATA_FILE)
|
||||
if ! git diff --quiet $<; then git commit -m "Update Nethack logfile for $(HOSTNAME)" -- $<; fi
|
||||
|
||||
clean:
|
||||
rm -rf "$(BUILD_DIR)/"
|
||||
|
|
9
archetypes/link.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
slug: link-{{ .Name }}
|
||||
date: {{ .Date }}
|
||||
categories: links
|
||||
draft: true
|
||||
tags: []
|
||||
---
|
||||
|
7
archetypes/sketch/index.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
---
|
||||
|
||||
{{< figures/p5 id="sketch" >}}
|
20
archetypes/sketch/sketch.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
const sketch = p => {
|
||||
p.setup = () => {
|
||||
const sketchContainer = document.querySelector('#sketch');
|
||||
const canvasWidth = parseFloat(getComputedStyle(sketchContainer).width);
|
||||
let canvas = p.createCanvas(canvasWidth, canvasWidth);
|
||||
canvas.canvas.removeAttribute('style');
|
||||
sketchContainer.appendChild(canvas.canvas);
|
||||
|
||||
p.pixelDensity(p.displayDensity());
|
||||
};
|
||||
|
||||
p.draw = () => {
|
||||
p.background(255);
|
||||
p.strokeWeight(4);
|
||||
p.stroke(0, 0, 255);
|
||||
p.circle(100, 100, 100);
|
||||
};
|
||||
};
|
||||
|
||||
new p5(sketch, 'sketch');
|
10
archetypes/weeknotes.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: "Notes on {{ time.Now.Format "2006" }}W%%WEEK_NUMBER%%"
|
||||
slug: weeknotes-{{ time.Now.Format "2006" }}w%%WEEK_NUMBER%%
|
||||
date: {{ .Date | time.Format "2006-01-02" }}
|
||||
categories: weeknotes
|
||||
tags:
|
||||
- Weeknotes
|
||||
draft: true
|
||||
---
|
||||
|
18
assets/css/099_format_tweaks.css
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
/************************
|
||||
* PARAGRAPH-SPACED LIST
|
||||
************************/
|
||||
|
||||
p + .paragraph-spaced-list {
|
||||
margin-block-start: var(--space-paragraph);
|
||||
}
|
||||
|
||||
.paragraph-spaced-list {
|
||||
li + li {
|
||||
margin-block-start: var(--space-paragraph);
|
||||
}
|
||||
}
|
42
assets/css/099_home_latest.css
Normal file
|
@ -0,0 +1,42 @@
|
|||
.home-latest {
|
||||
display: grid;
|
||||
grid-column: main-start / main-end;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
grid-template-rows: min-content min-content;
|
||||
grid-template-areas:
|
||||
"blog1 blog1 blog2 blog2"
|
||||
"photo1 photo2 photo3 photo4";
|
||||
|
||||
.home-latest__blog {
|
||||
margin-block-end: var(--space-m);
|
||||
}
|
||||
|
||||
.home-latest__blog:nth-of-type(1) {
|
||||
grid-area: blog1;
|
||||
border-right: 2px dashed var(--gray6);
|
||||
padding-inline-end: var(--space-s);
|
||||
}
|
||||
|
||||
.home-latest__blog:nth-of-type(2) {
|
||||
grid-area: blog2;
|
||||
padding-inline-start: var(--space-s);
|
||||
}
|
||||
|
||||
.home-latest__photo {
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.home-latest {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: repeat(min-content, 4);
|
||||
grid-template-areas:
|
||||
"blog1 blog2"
|
||||
"photo1 photo2"
|
||||
"photo3 photo4";
|
||||
}
|
||||
}
|
||||
|
||||
p + .home-latest {
|
||||
margin-block-start: var(--space-paragraph);
|
||||
}
|
100
assets/css/099_nethack.css
Normal file
|
@ -0,0 +1,100 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
/******************
|
||||
* NETHACK LOGFILE
|
||||
******************/
|
||||
|
||||
#dungeon-background {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
filter: brightness(0.3);
|
||||
}
|
||||
|
||||
.nethack-logfile {
|
||||
margin-inline-start: 0;
|
||||
padding-inline-start: 0;
|
||||
|
||||
.nethack-logentry {
|
||||
align-items: first baseline;
|
||||
display: grid;
|
||||
grid-template-columns: min-content min-content auto min-content;
|
||||
grid-template-areas:
|
||||
"list-marker entry-marker entry-date entry-character-descriptor"
|
||||
". . entry-description entry-description"
|
||||
". . entry-stats entry-stats";
|
||||
gap: var(--space-xs);
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.nethack-logentry {
|
||||
&:not(:last-child) {
|
||||
margin-block-end: var(--space-l);
|
||||
}
|
||||
|
||||
&::before {
|
||||
grid-area: list-marker;
|
||||
}
|
||||
|
||||
.nethack-logentry__marker {
|
||||
grid-area: entry-marker;
|
||||
}
|
||||
|
||||
.nethack-logentry__date {
|
||||
grid-area: entry-date;
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nethack-logentry__character-descriptor {
|
||||
font-family: var(--font-family-monospace);
|
||||
font-size: var(--text-s);
|
||||
grid-area: entry-character-descriptor;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nethack-logentry__description {
|
||||
grid-area: entry-description;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nethack-logentry__stats {
|
||||
border: 0;
|
||||
color: var(--text-color-secondary);
|
||||
font-family: var(--font-family-monospace);
|
||||
font-size: var(--text-s);
|
||||
grid-area: entry-stats;
|
||||
margin-block: 0;
|
||||
width: 100%;
|
||||
-webkit-border-horizontal-spacing: 0;
|
||||
-webkit-border-vertical-spacing: 0;
|
||||
}
|
||||
|
||||
.nethack-logentry__stats {
|
||||
padding: 0;
|
||||
text-transform: uppercase;
|
||||
vertical-align: bottom;
|
||||
white-space: nowrap;
|
||||
|
||||
thead {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.nethack-logentry__score,
|
||||
.nethack-logentry__hp,
|
||||
.nethack-logentry__level {
|
||||
width: 16rem;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
BIN
assets/images/memes/not-doing-it.png
Normal file
After Width: | Height: | Size: 464 KiB |
106247
assets/scripts/lib/p5-1.5.0.js
Normal file
758
assets/scripts/nethack/dungeon.js
Normal file
|
@ -0,0 +1,758 @@
|
|||
const NUMBER_OF_ROOMS = 12;
|
||||
const TUNNEL_PASSES = 3;
|
||||
|
||||
class Cell {
|
||||
static CORRIDOR = "#";
|
||||
static DOOR_CLOSED = "+";
|
||||
|
||||
character;
|
||||
characterColor;
|
||||
backgroundColor;
|
||||
|
||||
constructor(char, charColor) {
|
||||
this.character = char;
|
||||
}
|
||||
|
||||
empty() { this.character = " "; }
|
||||
floor() { this.character = "."; }
|
||||
upStair() { this.character = "<"; }
|
||||
downStair() { this.character = ">"; }
|
||||
corridor() { this.character = Cell.CORRIDOR; }
|
||||
topLeftWall() { this.character = "┌"; }
|
||||
topRightWall() { this.character = "┐"; }
|
||||
bottomLeftWall() { this.character = "└"; }
|
||||
bottomRightWall() { this.character = "┘"; }
|
||||
horizontalWall() { this.character = "─"; }
|
||||
verticalWall() { this.character = "│"; }
|
||||
doorClosed() { this.character = Cell.DOOR_CLOSED; }
|
||||
|
||||
isEmpty() { return !this.character || this.character === " "; }
|
||||
isDoor() { return this.character === Cell.DOOR_CLOSED; }
|
||||
isCorridor() { return this.character === Cell.CORRIDOR; }
|
||||
|
||||
canBecomeDoor() { return this.character === "─" || this.character === "│" }
|
||||
}
|
||||
|
||||
class Point {
|
||||
x = 0;
|
||||
y = 0;
|
||||
|
||||
constructor(x, y) {
|
||||
if (x) { this.x = x; }
|
||||
if (y) { this.y = y; }
|
||||
}
|
||||
|
||||
*neighbors() {
|
||||
const x = this.x;
|
||||
const y = this.y;
|
||||
|
||||
yield new Point(x - 1, y - 1);
|
||||
yield new Point(x, y - 1);
|
||||
yield new Point(x + 1, y - 1);
|
||||
yield new Point(x - 1, y);
|
||||
yield new Point(x + 1, y);
|
||||
yield new Point(x - 1, y + 1);
|
||||
yield new Point(x, y + 1);
|
||||
yield new Point(x + 1, y + 1);
|
||||
}
|
||||
|
||||
equalsPoint(other) { return this.x === other.x && this.y === other.y; }
|
||||
}
|
||||
|
||||
class Size {
|
||||
width = 0;
|
||||
height = 0;
|
||||
|
||||
constructor(width, height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
class Rect {
|
||||
origin = new Point();
|
||||
size = new Size();
|
||||
|
||||
static fromCoordinates(x, y, w, h) {
|
||||
return new Rect(new Point(x, y), new Size(w, h));
|
||||
}
|
||||
|
||||
constructor(origin, size) {
|
||||
this.origin = origin;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
get minX() { return this.origin.x; }
|
||||
get minY() { return this.origin.y; }
|
||||
get maxX() { return this.origin.x + this.size.width; }
|
||||
get maxY() { return this.origin.y + this.size.height; }
|
||||
|
||||
get width() { return this.size.width; }
|
||||
get height() { return this.size.height; }
|
||||
get area() { return this.size.width * this.size.height; }
|
||||
|
||||
*xCoordinates() {
|
||||
for (let x = this.minX; x <= this.maxX; x++) {
|
||||
yield x;
|
||||
}
|
||||
}
|
||||
|
||||
*yCoordinates() {
|
||||
for (let y = this.minY; y <= this.maxY; y++) {
|
||||
yield y;
|
||||
}
|
||||
}
|
||||
|
||||
insetRect(inset) {
|
||||
const twiceInset = 2 * inset;
|
||||
|
||||
return Rect.fromCoordinates(
|
||||
this.origin.x + inset,
|
||||
this.origin.y + inset,
|
||||
this.size.width - twiceInset,
|
||||
this.size.height - twiceInset
|
||||
);
|
||||
}
|
||||
|
||||
intersects(otherRect) {
|
||||
if (otherRect.minX > this.maxX)
|
||||
return false;
|
||||
|
||||
if (otherRect.maxX < this.minX)
|
||||
return false;
|
||||
|
||||
if (otherRect.minY > this.maxY)
|
||||
return false;
|
||||
|
||||
if (otherRect.maxY < this.minY)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class Grid {
|
||||
#size;
|
||||
#cells = [];
|
||||
#rooms = [];
|
||||
|
||||
constructor(width, height) {
|
||||
this.#size = new Size(width, height);
|
||||
|
||||
this.#cells = new Array(width * height);
|
||||
for (let i = 0; i < this.#cells.length; i++) {
|
||||
this.#cells[i] = new Cell(" ");
|
||||
}
|
||||
}
|
||||
|
||||
get bounds() { return new Rect(new Point(), this.#size); }
|
||||
get width() { return this.#size.width; }
|
||||
get height() { return this.#size.height; }
|
||||
get rooms() { return this.#rooms; }
|
||||
|
||||
generate(p, roomGeneratorClass, tunnelGeneratorClass) {
|
||||
this.#generateRooms(p, new roomGeneratorClass(this, NUMBER_OF_ROOMS));
|
||||
this.#placeStairs();
|
||||
this.#digCorridors(p, new tunnelGeneratorClass(this, TUNNEL_PASSES));
|
||||
}
|
||||
|
||||
#generateRooms(p, generator) {
|
||||
generator.generate(p);
|
||||
this.#rooms = generator.rooms;
|
||||
}
|
||||
|
||||
#placeStairs() {
|
||||
const indexOfRoomWithUpStairs = randomInt(this.#rooms.length);
|
||||
const coordinateOfUpStair = this.#rooms[indexOfRoomWithUpStairs].randomPoint();
|
||||
this.cellAt(coordinateOfUpStair.x, coordinateOfUpStair.y).upStair();
|
||||
|
||||
while (true) {
|
||||
let indexOfRoomForDownStair = randomInt(this.#rooms.length);
|
||||
if (indexOfRoomForDownStair == indexOfRoomWithUpStairs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const coordinateOfDownStair = this.#rooms[indexOfRoomForDownStair].randomPoint();
|
||||
this.cellAt(coordinateOfDownStair.x, coordinateOfDownStair.y).downStair();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#digCorridors(p, generator) {
|
||||
generator.generate(p);
|
||||
}
|
||||
|
||||
pointIsInBounds(pt) { return pt.x >= 0 && pt.x < this.width && pt.y >= 0 && pt.y < this.height; }
|
||||
|
||||
cellAt(x, y) { return this.#cells[y * this.width + x]; }
|
||||
}
|
||||
|
||||
class Room {
|
||||
#rect;
|
||||
|
||||
constructor(rect) {
|
||||
this.#rect = rect;
|
||||
}
|
||||
|
||||
get bounds() { return this.#rect; }
|
||||
|
||||
get minX() { return this.#rect.minX; }
|
||||
get minY() { return this.#rect.minY; }
|
||||
get maxX() { return this.#rect.maxX; }
|
||||
get maxY() { return this.#rect.maxY; }
|
||||
|
||||
randomPoint() {
|
||||
return new Point(
|
||||
this.#rect.minX + 1 + randomInt(this.#rect.size.width - 2),
|
||||
this.#rect.minY + 1 + randomInt(this.#rect.size.height - 2)
|
||||
);
|
||||
}
|
||||
|
||||
transformCellAt(pt, cell) {
|
||||
const minX = this.minX;
|
||||
const minY = this.minY;
|
||||
const maxX = this.maxX;
|
||||
const maxY = this.maxY;
|
||||
|
||||
const x = pt.x;
|
||||
const y = pt.y;
|
||||
|
||||
if (y === minY && x === minX) { cell.topLeftWall(); }
|
||||
else if (y === minY && x === maxX) { cell.topRightWall(); }
|
||||
else if (y === maxY && x === minX) { cell.bottomLeftWall(); }
|
||||
else if (y === maxY && x === maxX) { cell.bottomRightWall(); }
|
||||
else if (y === minY || y === maxY) { cell.horizontalWall(); }
|
||||
else if (x === minX || x === maxX) { cell.verticalWall(); }
|
||||
else if ((x > minX && x < maxX) && (y > minY && y < maxY)) { return cell.floor(); }
|
||||
}
|
||||
}
|
||||
|
||||
class NRandomRoomsGenerator {
|
||||
static MIN_ROOM_DIMENSION = 7;
|
||||
static MAX_ROOM_DIMENSION = 12;
|
||||
|
||||
#numberOfRooms = 12;
|
||||
#rooms;
|
||||
#grid;
|
||||
|
||||
constructor(grid, numberOfRooms) {
|
||||
this.#grid = grid;
|
||||
|
||||
if (numberOfRooms) {
|
||||
this.#numberOfRooms = numberOfRooms;
|
||||
}
|
||||
}
|
||||
|
||||
get rooms() {
|
||||
if (!this.#rooms) {
|
||||
this.#generateRooms();
|
||||
}
|
||||
|
||||
return this.#rooms;
|
||||
}
|
||||
|
||||
get #bounds() { return this.#grid.bounds; }
|
||||
|
||||
generate(p) {
|
||||
this.#generateRooms();
|
||||
|
||||
for (let i = 0; i < this.#rooms.length; i++) {
|
||||
let room = this.#rooms[i];
|
||||
for (let y = room.minY; y <= room.maxY; y++) {
|
||||
for (let x = room.minX; x <= room.maxX; x++) {
|
||||
let point = new Point(x, y);
|
||||
let cell = this.#grid.cellAt(x, y);
|
||||
|
||||
room.transformCellAt(point, cell);
|
||||
cell.characterColor = p.color(255);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#generateRooms() {
|
||||
let rects = new Array();
|
||||
|
||||
const sizeRange = NRandomRoomsGenerator.MAX_ROOM_DIMENSION - NRandomRoomsGenerator.MIN_ROOM_DIMENSION;
|
||||
|
||||
while (rects.length < this.#numberOfRooms) {
|
||||
const randomSize = new Size(
|
||||
NRandomRoomsGenerator.MIN_ROOM_DIMENSION + randomInt(sizeRange),
|
||||
NRandomRoomsGenerator.MIN_ROOM_DIMENSION + randomInt(sizeRange)
|
||||
);
|
||||
|
||||
const randomOrigin = new Point(
|
||||
this.#bounds.minX + randomInt(this.#bounds.maxX - randomSize.width),
|
||||
this.#bounds.minY + randomInt(this.#bounds.maxY - randomSize.height)
|
||||
);
|
||||
|
||||
const proposedRoomRect = new Rect(randomOrigin, randomSize);
|
||||
|
||||
// Check that the rect doesn't intersect with any other rects.
|
||||
if (rects.some(e => e.intersects(proposedRoomRect))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rects.push(proposedRoomRect);
|
||||
}
|
||||
|
||||
rects.sort((a, b) => {
|
||||
if (a.origin.x < b.origin.x) { return -1; }
|
||||
if (a.origin.x > b.origin.x) { return 1; }
|
||||
if (a.origin.y < b.origin.y) { return -1; }
|
||||
if (a.origin.y > b.origin.y) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
this.#rooms = rects.map(r => new Room(r.insetRect(1)));
|
||||
}
|
||||
}
|
||||
|
||||
class TunnelGenerator {
|
||||
static MAX_DOORS = 100;
|
||||
|
||||
#grid;
|
||||
#passes = 3;
|
||||
#numberOfDoorsPlaced = 0;
|
||||
|
||||
constructor(grid, passes) {
|
||||
this.#grid = grid;
|
||||
if (passes) { this.#passes = passes; }
|
||||
}
|
||||
|
||||
generate(p) {
|
||||
console.group("Digging tunnels");
|
||||
|
||||
if (this.#passes >= 1) {
|
||||
this.#doPassOne(p);
|
||||
}
|
||||
|
||||
if (this.#passes >= 2) {
|
||||
this.#doPassTwo(p);
|
||||
}
|
||||
|
||||
if (this.#passes >= 3) {
|
||||
this.#doPassThree(p);
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
#doPassOne(p) {
|
||||
console.group("Pass 1");
|
||||
this.#iterateAndConnectRoomsWithOffset(p, 1);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
#doPassTwo(p) {
|
||||
console.group("Pass 2");
|
||||
this.#iterateAndConnectRoomsWithOffset(p, 2);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
#doPassThree(p) {
|
||||
console.group("Pass 3");
|
||||
this.#iterateAndConnectRoomsWithOffset(p, 3);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
#iterateAndConnectRoomsWithOffset(p, offset) {
|
||||
let rooms = this.#grid.rooms;
|
||||
const numberOfRooms = rooms.length;
|
||||
for (let i = 0; i < numberOfRooms - offset; i++) {
|
||||
let fromRoom = rooms[i];
|
||||
let toRoom = rooms[i + offset];
|
||||
|
||||
let [fromPoint, toPoint] = this.#findPointFromRoomToRoom(fromRoom, toRoom);
|
||||
if (!fromPoint || !toPoint) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let neighbor of fromPoint.neighbors()) {
|
||||
if (!this.#grid.pointIsInBounds(neighbor)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cell = this.#grid.cellAt(neighbor.x, neighbor.y);
|
||||
if ((neighbor.x === fromPoint.x || neighbor.y === fromPoint.y) && cell.canBecomeDoor()) {
|
||||
cell.doorClosed();
|
||||
this.#numberOfDoorsPlaced++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const successfullyFoundTargetPoint = this.#digCorridorFromPointToPoint(p, fromPoint, toPoint);
|
||||
|
||||
if (successfullyFoundTargetPoint) {
|
||||
for (let neighbor of toPoint.neighbors()) {
|
||||
if (!this.#grid.pointIsInBounds(neighbor)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cell = this.#grid.cellAt(neighbor.x, neighbor.y);
|
||||
if ((neighbor.x === toPoint.x || neighbor.y === toPoint.y) && cell.canBecomeDoor()) {
|
||||
cell.doorClosed();
|
||||
this.#numberOfDoorsPlaced++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dig a corridor from fromPoint to toPoint, assuming that both points are
|
||||
* adjacent to valid locations for doors on the map.
|
||||
*
|
||||
* This is as close a copy of dig_corridor in the Nethack source as I could
|
||||
* muster. It's not exactly pretty. This method assumed
|
||||
*/
|
||||
#digCorridorFromPointToPoint(p, fromPoint, toPoint) {
|
||||
const MAX_STEPS = 500;
|
||||
|
||||
if (!fromPoint || !toPoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fromX = fromPoint.x;
|
||||
const toX = toPoint.x;
|
||||
const fromY = fromPoint.y;
|
||||
const toY = toPoint.y;
|
||||
|
||||
let curX = fromX;
|
||||
let curY = fromY;
|
||||
|
||||
let dx = 0;
|
||||
let dy = 0;
|
||||
|
||||
// Set up the initial direction of the dig.
|
||||
if (toX > curX) {
|
||||
dx = 1;
|
||||
} else if (toY > curY) {
|
||||
dy = 1;
|
||||
} else if (toX < curX) {
|
||||
dx = -1;
|
||||
} else {
|
||||
dy = -1;
|
||||
}
|
||||
|
||||
curX -= dx;
|
||||
curY -= dy;
|
||||
|
||||
let steps = 0;
|
||||
while (curX !== toX || curY !== toY) {
|
||||
if (steps++ > MAX_STEPS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
curX += dx;
|
||||
curY += dy;
|
||||
|
||||
if (curX >= this.#grid.width - 1 || curX <= 0 || curY <= 0 || curY >= this.#grid.height - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let cell = this.#grid.cellAt(curX, curY);
|
||||
if (cell.isEmpty()) {
|
||||
cell.corridor();
|
||||
} else if (!cell.isCorridor()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let dix = Math.abs(curX - toX);
|
||||
let diy = Math.abs(curY - toY);
|
||||
|
||||
if (dix > diy && diy) {
|
||||
const random = randomInt(dix - diy + 1);
|
||||
if (!random) {
|
||||
dix = 0;
|
||||
}
|
||||
} else if (diy > dix && dix) {
|
||||
const random = randomInt(dix - diy + 1);
|
||||
if (!random) {
|
||||
diy = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (dy && dix > diy) {
|
||||
const ddx = curX > toX ? -1 : 1;
|
||||
|
||||
let cell = this.#grid.cellAt(curX + ddx, curY);
|
||||
if (cell.isEmpty() || cell.isCorridor()) {
|
||||
dx = ddx;
|
||||
dy = 0;
|
||||
continue;
|
||||
}
|
||||
} else if (dx && diy > dix) {
|
||||
const ddy = curY > toY ? -1 : 1;
|
||||
|
||||
let cell = this.#grid.cellAt(curX, curY + ddy);
|
||||
if (cell.isEmpty() || cell.isCorridor()) {
|
||||
dy = ddy;
|
||||
dx = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
cell = this.#grid.cellAt(curX + dx, curY + dy);
|
||||
if (cell.isEmpty() || cell.isCorridor()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dx) {
|
||||
dx = 0;
|
||||
dy = toY < curY ? -1 : 1;
|
||||
} else {
|
||||
dy = 0;
|
||||
dx = toX < curX ? -1 : 1;
|
||||
}
|
||||
|
||||
cell = this.#grid.cellAt(curX + dx, curY + dy);
|
||||
if (cell.isEmpty() || cell.isCorridor()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dy = -dy;
|
||||
dx = -dx;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#findPointFromRoomToRoom(fromRoom, toRoom) {
|
||||
const fromRoomBounds = fromRoom.bounds;
|
||||
const toRoomBounds = toRoom.bounds;
|
||||
|
||||
let foundFromPoint = false;
|
||||
let foundToPoint = false;
|
||||
|
||||
let fromPoint;
|
||||
let toPoint;
|
||||
if (fromRoomBounds.maxX < toRoomBounds.minX) {
|
||||
// fromRoom is farther left than toRoom
|
||||
|
||||
fromPoint = new Point(fromRoomBounds.maxX, fromRoomBounds.minY + 1 + randomInt(fromRoomBounds.height - 2));
|
||||
foundFromPoint = this.#canPlaceDoorAt(fromPoint);
|
||||
|
||||
for (let y of fromRoomBounds.yCoordinates()) {
|
||||
fromPoint.y = y;
|
||||
foundFromPoint = this.#canPlaceDoorAt(fromPoint);
|
||||
if (foundFromPoint) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundFromPoint) {
|
||||
return [];
|
||||
}
|
||||
|
||||
toPoint = new Point(toRoomBounds.minX, toRoomBounds.minY + 1 + randomInt(toRoomBounds.height - 2));
|
||||
foundToPoint = this.#canPlaceDoorAt(toPoint);
|
||||
|
||||
for (let y of toRoomBounds.yCoordinates()) {
|
||||
toPoint.y = y;
|
||||
foundToPoint = this.#canPlaceDoorAt(toPoint);
|
||||
if (foundToPoint) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundToPoint) {
|
||||
return [];
|
||||
}
|
||||
|
||||
fromPoint.x += 1;
|
||||
toPoint.x -= 1;
|
||||
} else if (fromRoomBounds.minX > toRoomBounds.maxX) {
|
||||
// fromRoom is farther right than toRoomBounds.
|
||||
|
||||
fromPoint = new Point(toRoomBounds.maxX, toRoomBounds.minY + 1 + randomInt(toRoomBounds.height - 2));
|
||||
foundFromPoint = this.#canPlaceDoorAt(fromPoint);
|
||||
|
||||
for (let y of toRoomBounds.yCoordinates()) {
|
||||
fromPoint.y = y;
|
||||
foundFromPoint = this.#canPlaceDoorAt(fromPoint);
|
||||
if (foundFromPoint) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundFromPoint) {
|
||||
return [];
|
||||
}
|
||||
|
||||
toPoint = new Point(fromRoomBounds.minX, fromRoomBounds.minY + 1 + randomInt(fromRoomBounds.height - 2));
|
||||
foundToPoint = this.#canPlaceDoorAt(toPoint);
|
||||
|
||||
for (let y of fromRoomBounds.yCoordinates()) {
|
||||
toPoint.y = y;
|
||||
foundToPoint = this.#canPlaceDoorAt(toPoint);
|
||||
if (foundToPoint) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundToPoint) {
|
||||
return [];
|
||||
}
|
||||
|
||||
fromPoint.x -= 1;
|
||||
toPoint.x += 1;
|
||||
} else if (fromRoomBounds.maxY < (toRoomBounds.minY - 1)) {
|
||||
// fromRoom is above toRoom
|
||||
|
||||
fromPoint = new Point(fromRoomBounds.minX + 1 + randomInt(fromRoomBounds.width - 2), fromRoomBounds.maxY);
|
||||
foundFromPoint = this.#canPlaceDoorAt(fromPoint);
|
||||
|
||||
for (let x of fromRoomBounds.xCoordinates()) {
|
||||
fromPoint.x = x;
|
||||
foundFromPoint = this.#canPlaceDoorAt(fromPoint);
|
||||
if (foundFromPoint) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundFromPoint) {
|
||||
return [];
|
||||
}
|
||||
|
||||
toPoint = new Point(toRoomBounds.minX + 1 + randomInt(toRoomBounds.width - 2), toRoomBounds.minY);
|
||||
foundToPoint = this.#canPlaceDoorAt(toPoint);
|
||||
|
||||
for (let x of toRoomBounds.xCoordinates()) {
|
||||
toPoint.x = x;
|
||||
foundToPoint = this.#canPlaceDoorAt(toPoint);
|
||||
if (foundToPoint) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundToPoint) {
|
||||
return [];
|
||||
}
|
||||
|
||||
fromPoint.y += 1;
|
||||
toPoint.y -= 1;
|
||||
} else if (fromRoomBounds.minY > (toRoomBounds.maxY + 1)) {
|
||||
// fromRoom is below toRoom
|
||||
|
||||
fromPoint = new Point(toRoomBounds.minX + 1 + randomInt(toRoomBounds.width - 2), toRoomBounds.maxY);
|
||||
foundFromPoint = this.#canPlaceDoorAt(fromPoint);
|
||||
|
||||
for (let x of toRoomBounds.xCoordinates()) {
|
||||
fromPoint.x = x;
|
||||
foundFromPoint = this.#canPlaceDoorAt(fromPoint);
|
||||
if (foundFromPoint) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundFromPoint) {
|
||||
return [];
|
||||
}
|
||||
|
||||
toPoint = new Point(fromRoomBounds.minX + 1 + randomInt(fromRoomBounds.width - 2), fromRoomBounds.minY);
|
||||
foundToPoint = this.#canPlaceDoorAt(toPoint);
|
||||
|
||||
for (let x of fromRoomBounds.xCoordinates()) {
|
||||
toPoint.x = x;
|
||||
foundToPoint = this.#canPlaceDoorAt(toPoint);
|
||||
if (foundToPoint) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundToPoint) {
|
||||
return [];
|
||||
}
|
||||
|
||||
fromPoint.y += 1;
|
||||
toPoint.y -= 1;
|
||||
}
|
||||
|
||||
return [fromPoint, toPoint];
|
||||
}
|
||||
|
||||
#canPlaceDoorAt(pt) {
|
||||
if (this.#numberOfDoorsPlaced > TunnelGenerator.MAX_DOORS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.#grid.cellAt(pt.x, pt.y).canBecomeDoor()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let neighbor of pt.neighbors()) {
|
||||
if (!this.#grid.pointIsInBounds(neighbor)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cell = this.#grid.cellAt(neighbor.x, neighbor.y);
|
||||
if (cell.isDoor()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function randomInt(n) {
|
||||
max = Math.floor(n);
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
|
||||
let grid;
|
||||
|
||||
new p5(p => {
|
||||
const CELL_WIDTH = 20;
|
||||
const CELL_HEIGHT = Math.floor(CELL_WIDTH * 1.3);
|
||||
|
||||
p.setup = () => {
|
||||
const container = document.querySelector('#dungeon-background');
|
||||
console.assert(container, "Missing #dungeon-background element");
|
||||
|
||||
canvasWidth = parseFloat(getComputedStyle(container).width);
|
||||
canvasHeight = parseFloat(getComputedStyle(container).height);
|
||||
|
||||
let canvas = p.createCanvas(canvasWidth, canvasHeight);
|
||||
canvas.canvas.removeAttribute('style');
|
||||
container.appendChild(canvas.canvas);
|
||||
|
||||
p.pixelDensity(p.displayDensity());
|
||||
p.textFont("Courier");
|
||||
|
||||
const gridBounds = Rect.fromCoordinates(
|
||||
0, 0,
|
||||
Math.max(80, Math.ceil(canvasWidth / CELL_WIDTH) - 1),
|
||||
Math.max(24, Math.ceil(canvasHeight / CELL_HEIGHT) - 1)
|
||||
);
|
||||
|
||||
console.log(`Generating grid with size ${gridBounds.size.width} x ${gridBounds.size.height}`);
|
||||
|
||||
grid = new Grid(gridBounds.size.width, gridBounds.size.height);
|
||||
grid.generate(p, NRandomRoomsGenerator, TunnelGenerator);
|
||||
};
|
||||
|
||||
p.draw = () => {
|
||||
console.log("Drawing");
|
||||
|
||||
p.textSize(CELL_HEIGHT);
|
||||
|
||||
for (let y = 0; y < grid.height; y++) {
|
||||
for (let x = 0; x < grid.width; x++) {
|
||||
let cell = grid.cellAt(x, y);
|
||||
|
||||
let fillColor = cell.characterColor ? cell.characterColor : p.color(255);
|
||||
p.fill(fillColor);
|
||||
|
||||
p.textAlign(p.CENTER, p.CENTER);
|
||||
p.text(cell.character, x * CELL_WIDTH, y * CELL_HEIGHT, CELL_WIDTH, CELL_HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
p.noLoop();
|
||||
};
|
||||
|
||||
}, '#dungeon-background');
|
63
assets/scripts/railroad-utils.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
import rr from "scripts/lib/railroad.js";
|
||||
|
||||
class RailroadDiagramManager {
|
||||
constructor() {
|
||||
this.figures = new Map();
|
||||
this.isCurrentlyNarrow = undefined;
|
||||
|
||||
this.diagramBreakpoint = window.matchMedia("(max-width: 450px)");
|
||||
this.diagramBreakpoint.addEventListener("change", () => {
|
||||
this.updateVisiblity();
|
||||
});
|
||||
}
|
||||
|
||||
add(svg) {
|
||||
const parent = svg.parentElement;
|
||||
if (!this.figures.has(parent)) {
|
||||
this.figures.set(parent, []);
|
||||
}
|
||||
|
||||
this.figures.get(parent).push(svg);
|
||||
|
||||
parent.removeChild(svg);
|
||||
}
|
||||
|
||||
updateVisiblity() {
|
||||
const isNarrow = this.diagramBreakpoint.matches;
|
||||
|
||||
if (isNarrow === this.isCurrentlyNarrow) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isCurrentlyNarrow = isNarrow;
|
||||
|
||||
for (let [figure, svgs] of this.figures.entries()) {
|
||||
for (let svg of svgs) {
|
||||
const svgHasNarrowClass = svg.classList.contains("narrow");
|
||||
if (isNarrow && svgHasNarrowClass)
|
||||
figure.appendChild(svg);
|
||||
else if (!isNarrow && !svgHasNarrowClass)
|
||||
figure.appendChild(svg);
|
||||
else if (svg.parentElement === figure)
|
||||
figure.removeChild(svg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let railroadDiagramManager = new RailroadDiagramManager();
|
||||
|
||||
export function railroadDiagram(builder, elementID, isNarrow) {
|
||||
const diagram = builder(rr);
|
||||
const svg = diagram.addTo(document.getElementById(elementID));
|
||||
|
||||
if (isNarrow) {
|
||||
svg.classList.add("narrow");
|
||||
}
|
||||
|
||||
railroadDiagramManager.add(svg);
|
||||
}
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
railroadDiagramManager.updateVisiblity();
|
||||
});
|
123
assets/scripts/rubiks/scrambler.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
class RubiksCubeScrambler extends HTMLElement {
|
||||
static #RandomMoveHysteresisMaxLength = 2;
|
||||
|
||||
#shadowRoot;
|
||||
#movesListElement;
|
||||
|
||||
#numberOfMovesToGenerate = 25;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#shadowRoot = this.attachShadow({ mode: "open" });
|
||||
}
|
||||
|
||||
scramble() {
|
||||
console.log("Randomizing Rubik's cube...");
|
||||
|
||||
const movesList = this.#movesListElement;
|
||||
|
||||
while (movesList.childElementCount > this.#numberOfMovesToGenerate) {
|
||||
movesList.removeChild(movesList.lastChild);
|
||||
}
|
||||
|
||||
let randomMoveHysteresis = [];
|
||||
|
||||
for (let i = 0; i < this.#numberOfMovesToGenerate; i++) {
|
||||
const randomMove = this.#randomMove(randomMoveHysteresis);
|
||||
|
||||
let moveItem;
|
||||
if (i < movesList.childElementCount) {
|
||||
moveItem = movesList.children[i];
|
||||
} else {
|
||||
moveItem = document.createElement("li");
|
||||
movesList.appendChild(moveItem);
|
||||
}
|
||||
|
||||
moveItem.classList.add("scrambler__move");
|
||||
moveItem.classList.remove("scrambler__move--start", "scrambler__move--end");
|
||||
|
||||
if (randomMove.includes("2")) {
|
||||
moveItem.classList.add("scrambler__move--start");
|
||||
} else if (randomMove.includes("'")) {
|
||||
moveItem.classList.add("scrambler__move--end");
|
||||
}
|
||||
|
||||
moveItem.innerText = randomMove;
|
||||
}
|
||||
}
|
||||
|
||||
#randomMove(hysteresis) {
|
||||
const faces = "FBLRUD";
|
||||
|
||||
let move;
|
||||
do {
|
||||
move = faces.charAt(Math.floor(Math.random() * faces.length));
|
||||
} while (hysteresis && hysteresis.includes(move));
|
||||
|
||||
if (hysteresis) {
|
||||
hysteresis.unshift(move);
|
||||
while (hysteresis.length > RubiksCubeScrambler.#RandomMoveHysteresisMaxLength) {
|
||||
hysteresis.pop();
|
||||
}
|
||||
}
|
||||
|
||||
const modifierFactor = Math.random();
|
||||
if (modifierFactor < 0.33333) {
|
||||
move = "2" + move;
|
||||
} else if (modifierFactor < 0.666666) {
|
||||
move = move + "'";
|
||||
}
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
#removeAllMoves() {
|
||||
const element = this.#movesListElement;
|
||||
while (element.hasChildNodes()) {
|
||||
element.removeChild(element.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Custom Element
|
||||
|
||||
connectedCallback() {
|
||||
let template = document.getElementById("rubiks-cube-scrambler-template");
|
||||
console.assert(template, "Couldn't find RubiksCubeScrambler component template in the document");
|
||||
|
||||
const shadowRoot = this.#shadowRoot;
|
||||
shadowRoot.appendChild(template.content.cloneNode(true));
|
||||
|
||||
this.#movesListElement = shadowRoot.querySelector(".scrambler__move-list");
|
||||
|
||||
shadowRoot
|
||||
.querySelector("button[name='scramble']")
|
||||
.addEventListener("click", () => this.scramble());
|
||||
|
||||
const patternLengthInputElement = shadowRoot.querySelector(".scrambler__pattern-length > input");
|
||||
patternLengthInputElement.value = this.#numberOfMovesToGenerate;
|
||||
patternLengthInputElement.addEventListener("input", event => {
|
||||
try {
|
||||
const integerValue = parseInt(event.target.value);
|
||||
this.#numberOfMovesToGenerate = integerValue;
|
||||
} catch (e) {
|
||||
console.error("Non-integer value of pattern length field", e);
|
||||
}
|
||||
});
|
||||
|
||||
this.scramble();
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
console.debug("RubiksCubeScrambler attribute changed", name, oldValue, newValue);
|
||||
if (name === "count") {
|
||||
try {
|
||||
let newIntValue = parseInt(newValue);
|
||||
this.#numberOfMovesToGenerate = newIntValue;
|
||||
} catch (e) {
|
||||
console.error("`count` attribute should have an integer value.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define("rubiks-cube-scrambler", RubiksCubeScrambler);
|
169
assets/scripts/ruby_switch.js
Normal file
|
@ -0,0 +1,169 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
class RubySwitch extends HTMLElement {
|
||||
static controlSizeInPixels = 32;
|
||||
static thumbTransitionDuration = 0.1;
|
||||
|
||||
static settings = [
|
||||
{
|
||||
id: "ruby-switch-none",
|
||||
value: "none",
|
||||
label: "あ"
|
||||
},
|
||||
{
|
||||
id: "ruby-switch-both",
|
||||
value: "both",
|
||||
label: "<ruby>あ<rt>a</rt></ruby>",
|
||||
default: true
|
||||
},
|
||||
{
|
||||
id: "ruby-switch-hidden",
|
||||
value: "hidden",
|
||||
label: "ab"
|
||||
},
|
||||
];
|
||||
|
||||
#root;
|
||||
#thumb;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.#updateValue(RubySwitch.settings.find(obj => obj.default).value);
|
||||
this.addEventListener("RubyStyleChanged", event => {
|
||||
this.#updateValue(event.detail.style);
|
||||
});
|
||||
|
||||
this.#root = this.attachShadow({ mode: "closed" });
|
||||
this.#buildShadowDOM();
|
||||
|
||||
this.#updateThumbPosition(this.#root.querySelector(".control[data-default]"));
|
||||
}
|
||||
|
||||
#updateValue(style) {
|
||||
this.setAttribute("value", style);
|
||||
}
|
||||
|
||||
get #stylesheet() {
|
||||
const controlSize = RubySwitch.controlSizeInPixels;
|
||||
const halfControlSize = controlSize / 2;
|
||||
|
||||
return `
|
||||
#ruby-controls {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#controls {
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
display: inline grid;
|
||||
grid-template-columns: repeat(3, ${controlSize}px);
|
||||
margin: 0;
|
||||
overflow: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#thumb {
|
||||
box-sizing: border-box;
|
||||
box-shadow: 2px 2px 6px #ccc;
|
||||
border: 0.5px solid #aaa;
|
||||
border-radius: ${halfControlSize}px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: ${controlSize}px;
|
||||
width: ${controlSize}px;
|
||||
z-index: 50;
|
||||
transition: left ${RubySwitch.thumbTransitionDuration}s;
|
||||
}
|
||||
|
||||
.control {
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: ${controlSize}px;
|
||||
width: ${controlSize}px;
|
||||
}
|
||||
|
||||
b {
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
label {
|
||||
z-index: 2;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#ruby-switch-both {
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
#buildShadowDOM() {
|
||||
const root = this.#root;
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.textContent = this.#stylesheet;
|
||||
root.appendChild(style);
|
||||
|
||||
let container = document.createElement("div");
|
||||
container.id = "ruby-controls";
|
||||
root.appendChild(container);
|
||||
|
||||
let controls = document.createElement("div");
|
||||
controls.id = "controls";
|
||||
container.appendChild(controls);
|
||||
|
||||
for (const desc of RubySwitch.settings) {
|
||||
let control = document.createElement("div");
|
||||
control.classList.add("control")
|
||||
control.id = desc.id;
|
||||
|
||||
control.dataset.value = desc.value;
|
||||
if (desc.default) {
|
||||
control.dataset.default = "";
|
||||
}
|
||||
|
||||
controls.appendChild(control);
|
||||
|
||||
control.addEventListener("click", event => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
this.#updateThumbPosition(event.currentTarget);
|
||||
|
||||
this.#root.dispatchEvent(new CustomEvent("RubyStyleChanged", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
style: control.dataset.value,
|
||||
},
|
||||
}));
|
||||
}, { capture: true });
|
||||
|
||||
const label = document.createElement("b");
|
||||
label.innerHTML = desc.label;
|
||||
control.appendChild(label);
|
||||
}
|
||||
|
||||
const thumb = document.createElement("div");
|
||||
this.#thumb = thumb;
|
||||
thumb.id = "thumb";
|
||||
container.appendChild(thumb);
|
||||
}
|
||||
|
||||
#updateThumbPosition(selectedControl) {
|
||||
const controls = this.#root.querySelector("#controls");
|
||||
|
||||
const trackBoundingRect = controls.getBoundingClientRect();
|
||||
const controlBoundingRect = selectedControl.getBoundingClientRect();
|
||||
|
||||
const offset = controlBoundingRect.left - trackBoundingRect.left;
|
||||
this.#thumb.style.left = `${offset}px`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ruby-switch", RubySwitch);
|
6
assets/scripts/site.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
/* site.js
|
||||
* Eryn Wells <eryn@erynwells.me>
|
||||
*/
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
6
config/_default/config.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
baseURL: https://erynwells.me/
|
||||
languageCode: en-US
|
||||
title: ~eryn
|
||||
copyright: Copyright © 2020—2024 Eryn Wells
|
||||
defaultContentLanguage: en
|
||||
enableEmoji: true
|
12
config/_default/languages.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
en:
|
||||
languageName: English
|
||||
weight: 1
|
||||
es:
|
||||
languageName: Español
|
||||
weight: 2
|
||||
jp:
|
||||
languageName: 日本語
|
||||
weight: 3
|
||||
tok:
|
||||
languageName: toki pona
|
||||
weight: 4
|
12
config/_default/markup.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
goldmark:
|
||||
renderer:
|
||||
unsafe: true
|
||||
parser:
|
||||
attribute:
|
||||
block: true
|
||||
title: true
|
||||
highlight:
|
||||
anchorLineNos: true
|
||||
lineNos: false
|
||||
lineNumbersInTable: false
|
||||
noClasses: false
|
6
config/_default/mediatypes.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
application/rss+xml:
|
||||
delimiter: .
|
||||
suffixes: [rss]
|
||||
application/atom+xml:
|
||||
delimiter: .
|
||||
suffixes: [atom, xml]
|
52
config/_default/menu.yaml
Normal file
|
@ -0,0 +1,52 @@
|
|||
main:
|
||||
- identifier: blog
|
||||
name: Blog
|
||||
url: /blog/
|
||||
weight: 10
|
||||
- identifier: photos
|
||||
name: Photos
|
||||
url: /photos/
|
||||
weight: 20
|
||||
- identifier: about
|
||||
name: About
|
||||
url: /about/
|
||||
weight: 30
|
||||
- identifier: feed
|
||||
name: feed
|
||||
url: /feed.atom
|
||||
weight: 40
|
||||
params:
|
||||
style: file
|
||||
social:
|
||||
- identifier: mastodon
|
||||
name: Mastodon
|
||||
url: https://mastodon.social/@erynofwales
|
||||
weight: 10
|
||||
params:
|
||||
shortName: mst
|
||||
- identifier: github
|
||||
name: Github
|
||||
url: https://github.com/erynofwales
|
||||
weight: 20
|
||||
params:
|
||||
shortName: gh
|
||||
- identifier: instagram
|
||||
name: Instagram
|
||||
url: https://instagram.com/erynofwales
|
||||
weight: 30
|
||||
params:
|
||||
shortName: ig
|
||||
- identifier: feed
|
||||
name: feed
|
||||
url: /feed.atom
|
||||
weight: 40
|
||||
params:
|
||||
shortName: feed
|
||||
targetBlank: false
|
||||
about:
|
||||
- identifier: resume
|
||||
name: Résumé
|
||||
url: /resume/
|
||||
- identifier: whereAmI
|
||||
name: Where Am I
|
||||
url: /where-am-i/
|
15
config/_default/module.yaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
hugoVersion:
|
||||
extended: false
|
||||
min: "0.116.0"
|
||||
replacements: >-
|
||||
github.com/erynofwales/hugo-theme-feeds/v2 -> feeds,
|
||||
github.com/erynofwales/hugo-theme-termlite/v2 -> termlite,
|
||||
github.com/erynofwales/hugo-theme-photostream/v2 -> photostream,
|
||||
github.com/erynofwales/hugo-resource-builders/v2 -> resource-builders,
|
||||
github.com/erynofwales/hugo-image-utilities/v2 -> image-utils
|
||||
imports:
|
||||
- path: github.com/erynofwales/hugo-theme-termlite/v2
|
||||
- path: github.com/erynofwales/hugo-theme-feeds/v2
|
||||
- path: github.com/erynofwales/hugo-theme-photostream/v2
|
||||
- path: github.com/erynofwales/hugo-resource-builders/v2
|
||||
- path: github.com/erynofwales/hugo-image-utilities/v2
|
8
config/_default/outputformats.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
RSS:
|
||||
mediatype: application/rss+xml
|
||||
baseName: feed
|
||||
suffixes: [rss]
|
||||
Atom:
|
||||
mediatype: application/atom+xml
|
||||
baseName: feed
|
||||
suffixes: [atom, xml]
|
4
config/_default/outputs.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
home: [HTML, Atom]
|
||||
section: [HTML, Atom]
|
||||
taxonomy: [HTML]
|
||||
term: [HTML]
|
20
config/_default/params.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
author:
|
||||
name: Eryn Wells
|
||||
email: eryn@erynwells.me
|
||||
|
||||
shortTitle: Eryn Wells
|
||||
|
||||
twitter: erynofwales
|
||||
github: erynofwales
|
||||
instagram: erynofwales
|
||||
description: Home page of Eryn Rachel Wells
|
||||
|
||||
blog:
|
||||
yearLimit: 3
|
||||
|
||||
photostream:
|
||||
yearLimit: 3
|
||||
|
||||
photos:
|
||||
gridSize: 200
|
||||
thumbnailSize: 600
|
2
config/_default/permalinks.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
blog: blog/:year/:month/:slug/
|
||||
photos: photos/:year/:month/:slug/
|
2
config/_default/privacy.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
x:
|
||||
enableDNT: true
|
2
config/_default/services.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
x:
|
||||
disableInlineCSS: true
|
4
config/_default/taxonomies.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
category: categories
|
||||
location: locations
|
||||
series: series
|
||||
tag: tags
|
|
@ -1,5 +1,8 @@
|
|||
---
|
||||
title: Eryn Rachel Wells
|
||||
layout: single
|
||||
---
|
||||
|
||||
Este es un poco de texto sobre Eryn: quién es, qué le gusta, y por qué existe este sitio.
|
||||
{{< nobreak >}}Ingeniera de software,{{< /nobreak >}}
|
||||
alfarera, música, y
|
||||
{{< nobreak >}}nerd en general.{{< /nobreak >}}
|
||||
|
|
|
@ -1,5 +1,58 @@
|
|||
---
|
||||
title: Eryn Rachel Wells
|
||||
layout: single
|
||||
params:
|
||||
renderHeadingAnchors: false
|
||||
---
|
||||
|
||||
Software engineer, potter, musician, and overall nerd. This is my website.
|
||||
Hi, I'm Eryn Wells. This is my website. Welcome!
|
||||
|
||||
## Latest
|
||||
|
||||
Here are some of my most recent posts.
|
||||
|
||||
{{< home/latest >}}
|
||||
|
||||
## Personal
|
||||
|
||||
I'm a queer trans woman, {{< tess >}}' partner, and mom of [two cats][cats]. I
|
||||
was born in Seattle, {{< abbr Washington >}}WA{{< /abbr >}} and grew up in
|
||||
Phoenix, {{< abbr Arizona >}}AZ{{< /abbr >}}. I attended [Oberlin College][ob]
|
||||
where I got a degree in Computer Science. My pronouns are [she/her][pronouns].
|
||||
|
||||
You can read more about me on my [about][ab] page, or [get in touch][where-am-i].
|
||||
|
||||
## Professional
|
||||
|
||||
I've worked as a software engineer since 2011 for a variety of companies around
|
||||
the San Francisco Bay Area. I joined [Apple][a] in 2016, where I currently work
|
||||
on password management and authentication technologies.
|
||||
|
||||
My [résumé][r] has all the details.
|
||||
|
||||
## Hobbies
|
||||
|
||||
When I'm not working, you can reliably find me hacking on this website or [some
|
||||
coding other project][gh]. I'm also a musician, and play piano, Irish tin
|
||||
whistle, and modular synthesizer. Occasionally I [record][bc] [things][sc]. I
|
||||
love outer space and astronomy; I will always get excited to look at the moon
|
||||
with you, or check out anything through a telescope. I enjoy [photograhy][p],
|
||||
mostly as a travel hobby. And I've been practicing iaido, a traditional Japanese
|
||||
sword art, since early 2024. Other things I've been into include: bread baking,
|
||||
bicycling, calligraphy, ceramics, and knitting.
|
||||
|
||||
[a]: https://apple.com
|
||||
[ab]: {{< ref "/about" >}}
|
||||
[b]: {{< ref "/blog" >}}
|
||||
[bc]: https://erynwells.bandcamp.com/releases
|
||||
[cats]: {{< ref "/cats" >}}
|
||||
[eml]: mailto:Eryn%20Wells<eryn@erynwells.me>
|
||||
[gh]: https://github.com/erynofwales
|
||||
[ig]: https://www.instagram.com/erynofwales
|
||||
[m]: https://mastodon.social/@erynofwales
|
||||
[n]: {{< ref "/now" >}}
|
||||
[ob]: https://www.oberlin.edu
|
||||
[p]: {{< ref "/photos" >}}
|
||||
[pronouns]: http://pronoun.is/she
|
||||
[r]: {{< ref "/resume" >}}
|
||||
[sc]: https://soundcloud.com/purlsnbeeps
|
||||
[where-am-i]: {{< ref "/about/where-am-i" >}}
|
||||
|
|
|
@ -1,12 +1,24 @@
|
|||
---
|
||||
title: "Hola! 👋🏻"
|
||||
draft: false
|
||||
slug: sobre
|
||||
resources:
|
||||
- name: me
|
||||
src: me.jpeg
|
||||
---
|
||||
|
||||
Me llamo Eryn. Mis pronombres son [ella/ella][p]. Esta es me página personal. Bienvenide.
|
||||
{{< circular_image id=me name=me class="float-right" width=200
|
||||
alt="Una foto de me, con sombrero, sentando en frente de un fondo de piedra">}}
|
||||
|
||||
Soy una mujer trans y queer. Vivo en San Francisco con mis [dos gatos][cats]. Nací en Seattle, WA, y crecía en Phoenix, AZ. Asistí [Oberlin College][ob] donde obtuve un títolo en informática. {{< tess >}} es mi novia.
|
||||
Me llamo Eryn. Mis pronombres son [ella/ella][p]. Esta es me página personal.
|
||||
Bienvenide.
|
||||
|
||||
Mi lengua nativa es inglés, y también hablo español pero siempre necesito practicar más.
|
||||
Soy una mujer trans y queer. Vivo en San Francisco con mis [dos gatos][cats].
|
||||
Nací en Seattle, WA, y crecía en Phoenix, AZ. Asistí [Oberlin College][ob] donde
|
||||
obtuve un títolo en informática. {{< tess >}} es mi novia.
|
||||
|
||||
Mi lengua nativa es inglés, y también hablo español pero siempre necesito
|
||||
practicar más.
|
||||
|
||||
## Pasatiempos
|
||||
|
||||
|
@ -35,14 +47,15 @@ Echa un vistazo a mi [resumen][r] para más detalles.
|
|||
|
||||
## Decirme Hola
|
||||
|
||||
Puedes encontrarme en muchos rincones del Internet. Estoy más activa en
|
||||
[Twitter][t] y [Instagram][i]. Publico música en [SoundCloud][sc] y
|
||||
Puedes [encontrarme en muchos rincones del Internet][where-am-i]. Estoy más
|
||||
activa en [Twitter][t] y [Instagram][i]. Publico música en [SoundCloud][sc] y
|
||||
[Bandcamp][bc]. Y para los proyectos de software, estoy en [GitHub][gh].
|
||||
|
||||
[p]: http://pronoun.is/she
|
||||
[cats]: {{< ref "/cats" >}}
|
||||
[ob]: https://www.oberlin.edu
|
||||
[r]: {{< ref "/resume" >}}
|
||||
[r]: {{< ref path="/resume" >}}
|
||||
[where-am-i]: {{< ref path="about/where-am-i" lang="es" >}}
|
||||
[t]: https://twitter.com/erynofwales
|
||||
[i]: https://www.instagram.com/erynofwales/
|
||||
[sc]: https://soundcloud.com/purlsnbeeps
|
|
@ -1,14 +1,16 @@
|
|||
---
|
||||
title: "Hi! 👋🏻"
|
||||
date: 2022-09-03T12:14:32-07:00
|
||||
draft: true
|
||||
layout: single
|
||||
resources:
|
||||
- name: me
|
||||
src: me.jpeg
|
||||
params:
|
||||
alt: >
|
||||
Me, wearing a hat and smiling slightly, standing in front of a stone
|
||||
background.
|
||||
---
|
||||
|
||||
{{< circular_image id=me name=me class="float-right" width=200
|
||||
alt="A photo of me, wearing a hat, standing in front of a stone background">}}
|
||||
{{% section class=content--small-right-column %}}
|
||||
|
||||
I'm Eryn. My pronouns are [she/her][p]. I'm a queer trans woman. I live in San
|
||||
Francisco, CA, on the unceded ancestral lands of the Ramaytush Ohlone people,
|
||||
|
@ -18,6 +20,10 @@ I attended [Oberlin College][ob] where I got a degree in Computer Science.
|
|||
|
||||
I speak English natively, and Spanish too, though I always need more practice.
|
||||
|
||||
{{< circular_image id=me name=me class="content--right-column" width=200 >}}
|
||||
|
||||
{{% /section %}}
|
||||
|
||||
## Hobbies
|
||||
|
||||
I've been a musician for most of my life. I started on the piano at age seven,
|
||||
|
@ -45,8 +51,8 @@ Check out my [résumé][r] for more details.
|
|||
|
||||
## Say Hello
|
||||
|
||||
You can find me in lots of other corners of the Internet. I'm most active on
|
||||
[Twitter][t] and [Instagram][i]. I post music on [SoundCloud][sc] and
|
||||
You can find me in [lots of other corners of the Internet][where-am-i]. I'm most
|
||||
active on [Twitter][t] and [Instagram][i]. I post music on [SoundCloud][sc] and
|
||||
[Bandcamp][bc]. I'm on [GitHub][gh] for coding projects. You can also send me an
|
||||
[email][eml].
|
||||
|
||||
|
@ -60,3 +66,4 @@ You can find me in lots of other corners of the Internet. I'm most active on
|
|||
[bc]: https://erynwells.bandcamp.com/releases
|
||||
[gh]: https://github.com/erynofwales
|
||||
[eml]: mailto:Eryn%20Wells<eryn@erynwells.me>
|
||||
[where-am-i]: {{< ref "/about/where-am-i" >}}
|
|
@ -1,4 +1,17 @@
|
|||
@layer page {
|
||||
main > section > p:not(:last-child) {
|
||||
margin-bottom: var(--body-item-spacing);
|
||||
}
|
||||
|
||||
img#me {
|
||||
p:has(img#me) {
|
||||
display: inline;
|
||||
grid-column: unset;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
img#me {
|
||||
margin: 0;
|
||||
shape-outside: circle(55%);
|
||||
width: min(200px, 25%);
|
||||
}
|
||||
}
|
||||
|
|
25
content/about/where-am-i.es.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
title: "Dónde encontrarme"
|
||||
date: 2022-11-11T08:35:26-08:00
|
||||
slug: donde-encontrarme
|
||||
---
|
||||
|
||||
Aquí está una lista de dónde se puede encontrarme en línea.
|
||||
|
||||
## Redes Sociales
|
||||
|
||||
- Cohost: [@eryn](https://cohost.org/eryn)
|
||||
- Instagram: [@erynofwales](https://instagram.com/erynofwales)
|
||||
- Mastodon: [@erynofwales](https://mastodon.social/@erynofwales)
|
||||
- Twitter: [@erynofwales](https://twitter.com/erynofwales)
|
||||
|
||||
## Contenido
|
||||
|
||||
- Bandcamp: [erynwells](https://erynwells.bandcamp.com/releases)
|
||||
- Soundcloud: [purlsnbeeps](https://soundcloud.com/purlsnbeeps)
|
||||
- YouTube: [Eryn Wells](https://www.youtube.com/channel/UCWb2pTDlC27R1PucyUPrypA)
|
||||
- GitHub: [erynofwales](https://github.com/erynofwales)
|
||||
|
||||
## La Manera Antigua
|
||||
|
||||
- Email: [eryn@erynwells.me](mailto:Eryn%20Wells<eryn@erynwells.me>)
|
30
content/about/where-am-i.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
title: "Where to Find Me"
|
||||
date: 2022-11-11T08:35:26-08:00
|
||||
---
|
||||
|
||||
Here's a list of places you can find me online. You can often find me on
|
||||
services not listed here with the `erynofwales` or `erynrwells` handles.
|
||||
|
||||
## Social Media
|
||||
|
||||
I'm really only on Instagram and Mastodon these days. My Twitter account is
|
||||
still live, as an archive, but I don't post on it or look at it. Ditto for
|
||||
Facebook.
|
||||
|
||||
- Facebook: [erynofwales](https://www.facebook.com/erynofwales)
|
||||
- Instagram: [@erynofwales](https://instagram.com/erynofwales)
|
||||
- Mastodon: [@erynofwales](https://mastodon.social/@erynofwales)
|
||||
- Twitter: [@erynofwales](https://twitter.com/erynofwales)
|
||||
|
||||
## Content
|
||||
|
||||
- Bandcamp: [erynwells](https://erynwells.bandcamp.com/releases)
|
||||
- GitHub: [erynofwales](https://github.com/erynofwales)
|
||||
- Soundcloud: [purlsnbeeps](https://soundcloud.com/purlsnbeeps)
|
||||
- StoryGraph: [erynrwells](https://app.thestorygraph.com/profile/erynrwells)
|
||||
- YouTube: [Eryn Wells](https://www.youtube.com/channel/UCWb2pTDlC27R1PucyUPrypA)
|
||||
|
||||
## The Old Fashioned Way
|
||||
|
||||
- Email: [eryn@erynwells.me](mailto:Eryn%20Wells<eryn@erynwells.me>)
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: "Booting a Raspberry Pi Over TFTP"
|
||||
date: 2020-10-13T08:31:52-07:00
|
||||
draft: false
|
||||
description: A writeup of how I set up a Raspberry Pi to boot over TFTP to facilitate an operating system development project.
|
||||
series: ["Raspberry Pi OS Development"]
|
||||
categories: ["Tech"]
|
||||
tags: ["Raspberry Pi", "Networking"]
|
||||
|
|
4
content/blog/2020/_index.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: 2020
|
||||
date: 2020-01-01
|
||||
---
|
|
@ -7,7 +7,7 @@ categories: ["Music"]
|
|||
tags: ["Synthesizers", "Electronics", "DIY", "Compositions"]
|
||||
---
|
||||
|
||||
{{< figures/youtube id="gCSwWsxzy_c" title="A timelapse video of me building an Oskitone Scout, set to music produced using the Scout itself">}}
|
||||
{{< youtube id="gCSwWsxzy_c" title="A timelapse video of me building an Oskitone Scout, set to music produced using the Scout itself" >}}
|
||||
|
||||
[Oskitone][oskitone] recently released a new synthesizer: the [Scout][scout].
|
||||
It's a small monophonic keyboard synth built around an Arduino. It was a quick
|
||||
|
|
|
@ -41,4 +41,4 @@ I'm so grateful for every one of these people. We've been friends for years and
|
|||
even though our lives have taken us in so many different directions, we've found
|
||||
each other again and that is so wonderful.
|
||||
|
||||
{{< twitter erynofwales 1447951049076056071 >}}
|
||||
{{< twitter user=erynofwales id=1447951049076056071 >}}
|
||||
|
|
4
content/blog/2021/_index.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: 2021
|
||||
date: 2021-01-01
|
||||
---
|
|
@ -7,7 +7,7 @@ categories: ["Music"]
|
|||
tags: ["Eurorack", "Synthesizers", "Recordings", "Performances", "Compositions"]
|
||||
---
|
||||
|
||||
{{< figures/youtube id="sqr7g4P85aM" title="A top-down video of me operating a small Eurorack system made of only three modules. Lights flash, an incorporeal hand turns knobs to sculpt the sound." >}}
|
||||
{{< youtube id="sqr7g4P85aM" title="A top-down video of me operating a small Eurorack system made of only three modules. Lights flash, an incorporeal hand turns knobs to sculpt the sound." >}}
|
||||
|
||||
This is my submission to the [Three Module Challenge][3mc] show put on by
|
||||
Colorado Modular Synth Society in late January 2022. This is my first time
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
title: Ay, Ella Se Ha Vuelto Adicta a Nethack
|
||||
date: 2022-04-24T17:36:33-07:00
|
||||
description: In which I get hooked on that one roguelike (God help me)
|
||||
draft: false
|
||||
categories: ["Games"]
|
||||
tags: ["Nethack", "Video Games"]
|
||||
---
|
||||
|
||||
Hace unas semanas que conecté a mi VPS para averiguar algo. Hay [un
|
||||
parte][list-tmux-sessions] de [mis dotfiles][zprofile] que ejecuta durante la
|
||||
iniciación de las sesiones de inicio de ZSH que imprime las sesiones existidos
|
||||
de `tmux` asociados con mi cuenta. Realizé que había una sesión y me la adjunté.
|
||||
de `tmux` asociados con mi cuenta. Me dio cuenta que había una sesión y me la
|
||||
adjunté.
|
||||
|
||||
En esa sesión encontré un juego de [Nethack][nethack] que empezaba en enero y
|
||||
nunca lo completé. Lo completé, me mató un duende, y, pues, el daño ya se me
|
||||
|
@ -17,7 +18,7 @@ estuve hecho.
|
|||
|
||||
Es posible que me entusiasme un poco.
|
||||
|
||||
{{< twitter erynofwales 1510763278691016705 >}}
|
||||
{{< twitter user=erynofwales id=1510763278691016705 >}}
|
||||
|
||||
He mejorado mucho en las últimas semanas. Mis puntajes han crecidos desde 1,000
|
||||
hasta el mejor juego hasta ahora en que [obtuve 9401 puntos][over9000]. Quién
|
||||
|
@ -34,4 +35,4 @@ web. Echa un vistazo a mi [logfile](/nethack) de Nethack para esa.
|
|||
[nethack]: https://www.nethack.org
|
||||
[nethackwiki]: https://nethackwiki.com/wiki/Main_Page
|
||||
[priceid]: https://nethackwiki.com/wiki/Price_identification
|
||||
[over9000]: https://www.youtube.com/watch?v=ITWMoS2L1oo
|
||||
[over9000]: https://www.youtube.com/watch?v=ITWMoS2L1oo
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
title: Oh Dear, She Got Hooked on Nethack Again
|
||||
date: 2022-04-24T17:36:33-07:00
|
||||
description: In which I get hooked on that one roguelike (God help me)
|
||||
draft: false
|
||||
categories: ["Games"]
|
||||
tags: ["Nethack", "Roguelikes"]
|
||||
---
|
||||
|
@ -24,7 +23,7 @@ packed with [NetHackWiki][nethackwiki] tabs too, including a pinned one for the
|
|||
|
||||
I may have gotten a little carried away a time or two.
|
||||
|
||||
{{< twitter erynofwales 1510763278691016705 >}}
|
||||
{{< twitter user=erynofwales id=1510763278691016705 >}}
|
||||
|
||||
I've gotten much better in that time. My scores have increased from the
|
||||
1000-2000 range to my best game so far in which [I scored 9401
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
title: "Roguelikes I Like"
|
||||
date: 2022-05-09T08:37:23-07:00
|
||||
description: Some roguelikes I’ve enjoyed recently.
|
||||
draft: false
|
||||
resources:
|
||||
- name: nethack
|
||||
|
|
36
content/blog/2022/08/audio-scope-with-p5js/diagram.svg
Normal file
|
@ -0,0 +1,36 @@
|
|||
<svg class="railroad-diagram" width="408" height="62" viewBox="0 0 408 62">
|
||||
<g transform="translate(.5 .5)">
|
||||
<g>
|
||||
<path d="M20 21v20m10 -20v20m-10 -10h20"></path>
|
||||
</g>
|
||||
<path d="M40 31h10"></path>
|
||||
<g>
|
||||
<path d="M50 31h0"></path>
|
||||
<path d="M358 31h0"></path>
|
||||
<g class="terminal ">
|
||||
<path d="M50 31h0"></path>
|
||||
<path d="M126 31h0"></path>
|
||||
<rect x="50" y="20" width="76" height="22" rx="10" ry="10"></rect>
|
||||
<text x="88" y="35"><audio></text>
|
||||
</g>
|
||||
<path d="M126 31h10"></path>
|
||||
<path d="M136 31h10"></path>
|
||||
<g class="terminal ">
|
||||
<path d="M146 31h0"></path>
|
||||
<path d="M230 31h0"></path>
|
||||
<rect x="146" y="20" width="84" height="22" rx="10" ry="10"></rect>
|
||||
<text x="188" y="35">Analyzer</text>
|
||||
</g>
|
||||
<path d="M230 31h10"></path>
|
||||
<path d="M240 31h10"></path>
|
||||
<g class="terminal ">
|
||||
<path d="M250 31h0"></path>
|
||||
<path d="M358 31h0"></path>
|
||||
<rect x="250" y="20" width="108" height="22" rx="10" ry="10"></rect>
|
||||
<text x="304" y="35">destination</text>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M358 31h10"></path>
|
||||
<path d="M 368 31 h 20 m -10 -10 v 20 m 10 -20 v 20"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: "Making an Audio Scope with P5.js"
|
||||
date: 2022-08-18T20:48:37-07:00
|
||||
draft: false
|
||||
description: A writeup of a small JavaScript waveform visualizer I made with P5.js.
|
||||
categories: ["Tech"]
|
||||
tags: ["P5.js", "Programming", "Web", "Art"]
|
||||
resources:
|
||||
|
@ -23,11 +23,13 @@ visualizations. By the end, we'll have something like this:
|
|||
HTML has the ability to [embed audio][mdn-audio-tag] in a page with the
|
||||
`<audio>` tag. This one declares a single MP3 file as a source.
|
||||
|
||||
{{< figures/code >}}
|
||||
```html
|
||||
<audio id="amen">
|
||||
<source src="amen.mp3" type="audio/mpeg">
|
||||
</audio>
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
In this form, the `<audio>` element doesn't do anything except declare some
|
||||
audio that can be played. It's invisible and the user can't interact with it or
|
||||
|
@ -47,6 +49,7 @@ destinations could be your computer's speakers or a file.
|
|||
Here's the entire code snippet that sets up the audio processing I need for the
|
||||
sketch:
|
||||
|
||||
{{< figures/code >}}
|
||||
```js {linenostart=2}
|
||||
let analyzerNode = null;
|
||||
let samples = null;
|
||||
|
@ -67,6 +70,7 @@ let audioContext = (() => {
|
|||
return audioContext;
|
||||
})();
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
The [`AudioContext`][mdn-audio-context] is the object that encapsulates the
|
||||
entire node graph. On line 10, I create a new `AudioContext`.
|
||||
|
@ -84,21 +88,7 @@ node to the input of the analyzer node, and the output of the analyzer node to
|
|||
the audio context's `destination` node that routes to the computer's speakers.
|
||||
Our audio processing graph looks like this:
|
||||
|
||||
{{< figures/railroad id="audioContextDiagram" >}}
|
||||
return rr.Diagram(
|
||||
rr.Sequence(
|
||||
rr.Terminal("<audio>"),
|
||||
rr.Terminal("Analyzer"),
|
||||
rr.Terminal("destination")));
|
||||
{{< /figures/railroad >}}
|
||||
|
||||
{{< figures/railroad id="audioContextDiagram" class="narrow-only" >}}
|
||||
return rr.Diagram(
|
||||
rr.Stack(
|
||||
rr.Terminal("<audio>"),
|
||||
rr.Terminal("Analyzer"),
|
||||
rr.Terminal("destination")));
|
||||
{{< /figures/railroad >}}
|
||||

|
||||
|
||||
By itself the AudioContext doesn't actually play any audio. I'll tackle that
|
||||
next.
|
||||
|
@ -109,6 +99,7 @@ Next up is starting playback. The following snippet creates a Play button using
|
|||
P5.js's DOM manipulation API, and hooks up the button's `click` event to start
|
||||
and stop playback.
|
||||
|
||||
{{< figures/code >}}
|
||||
```js {linenostart=29}
|
||||
const playPauseButton = p.createButton('Play');
|
||||
playPauseButton.position(10, 10);
|
||||
|
@ -131,6 +122,7 @@ playPauseButtonElement.addEventListener('click', function() {
|
|||
}
|
||||
});
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
Something I found odd while working with these audio components is there isn't a
|
||||
way to ask any of them if audio is playing back at any given moment. Instead it
|
||||
|
@ -147,12 +139,14 @@ The last bit of playback state tracking to do is to listen for when playback
|
|||
ends because it reached the end of the audio file. I did that with the `ended`
|
||||
event:
|
||||
|
||||
{{< figures/code >}}
|
||||
```js {linenostart=53}
|
||||
audioElement.addEventListener('ended', function() {
|
||||
playPauseButtonElement.dataset.playing = 'false';
|
||||
playPauseButtonElement.innerHTML = '<span>Play</span>';
|
||||
}, false);
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
This handler resets the `playing` flag and the label of the button.
|
||||
|
||||
|
@ -160,6 +154,7 @@ This handler resets the `playing` flag and the label of the button.
|
|||
|
||||
Now it's time to draw some waveforms! The main part of a P5 sketch is the `draw` method. Here's mine:
|
||||
|
||||
{{< figures/code >}}
|
||||
```js {linenostart=57}
|
||||
const amplitude = p.height / 2;
|
||||
const axis = p.height / 2;
|
||||
|
@ -184,12 +179,15 @@ for (let i = 0; i < samples.length; i++) {
|
|||
p.point(i, axis + amplitude * sampleValue);
|
||||
}
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
The most interesting part of this function starts at line 66 where we get an array of samples from the analyzer node. The `samples` variable is a JavaScript `Float32Array`, with one element for each pixel of width.
|
||||
|
||||
{{< figures/code >}}
|
||||
```js {linenostart=30}
|
||||
samples = new Float32Array(p.width);
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
Once the sample data is populated from the analyzer, we can render them by
|
||||
plotting them along the X axis, scaling them to the height of the sketch.
|
||||
|
|
152
content/blog/2022/10/hugo-dictionary-api/index.md
Normal file
|
@ -0,0 +1,152 @@
|
|||
---
|
||||
title: "Hugo's Dictionary API"
|
||||
date: 2022-10-13T10:19:02-07:00
|
||||
description: I’ve found Hugo’s API for collections to be difficult to understand. Here’s my attempt to summarize it’s quirks.
|
||||
categories: ["Tech"]
|
||||
tags: ["Hugo", "Web", "API Design"]
|
||||
series: "Erynwells.me Development"
|
||||
---
|
||||
|
||||
Hugo's templating system has support for dictionaries. Unfortunately the API for
|
||||
working with them is, frankly, awful. While working on developing some new
|
||||
templates for this site, I had to figure out how to build up dictionary data
|
||||
structures and it took me a _long_ time to figure out how to do some basic
|
||||
operations with them.
|
||||
|
||||
Here's a quick summary of what I found.
|
||||
|
||||
## Creating Dictionaries
|
||||
|
||||
The function to create a dictionary is called [`dict`][dict] and it takes a
|
||||
variable number of arguments that alternate between keys and values. It reminds
|
||||
me of this [bizarre and backwards NSDictionary API][nsdictionary-init] in Apple's
|
||||
Foundation framework. Keys must be strings (or string slices) and values can be
|
||||
anything. So this:
|
||||
|
||||
{{< figures/code >}}
|
||||
```go-html-template
|
||||
{{ $d := dict "a" 1 "b" 2 "c" 3 }}
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
creates a structure that looks like this JSON object:
|
||||
|
||||
{{< figures/code >}}
|
||||
```json
|
||||
{ "a": 1, "b": 2, "c": 3 }
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
You can also create an empty dictionary by calling `dict` with no arguments.
|
||||
|
||||
{{< figures/code >}}
|
||||
```go-html-template
|
||||
{{ $d := dict }}
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
## Accessing Keys and Values
|
||||
|
||||
Statically, you can get a single item in a dictionary with dot syntax. Below,
|
||||
`$item` will get the value 1.
|
||||
|
||||
{{< figures/code >}}
|
||||
```go-html-template
|
||||
{{ $item := (dict "a" 1 "b" 2 "c" 3).a }}
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
If you want to get a value with a key you get at render time, you can use the
|
||||
[`index`][index] function. In the snippet below, `$item` will get the value of
|
||||
`"b"`, which is 2.
|
||||
|
||||
{{< figures/code >}}
|
||||
```go-html-template
|
||||
{{ $key := "b" }}
|
||||
{{ $item := index $key (dict "a" 1 "b" 2 "c" 3) }}
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
`index` doesn't make much sense to me as a verb for accessing values in a
|
||||
dictionary. It sounds more like an array function, and indeed it's the function
|
||||
that gives you access to items in arrays. I would like to see another function
|
||||
with a more dictionary-sounding name, like `get` or `value` or `item`, even if
|
||||
it were just an alias for `index` underneath.
|
||||
|
||||
## Adding Items to a Dictionary
|
||||
|
||||
This is a bit complex because, as far as I can tell, dictionaries are immutable.
|
||||
So, if you want to update a dictionary, you need to combine two dictionaries and
|
||||
then save it back to the original variable. The [`merge`][merge] function does
|
||||
that. Here's a snippet:
|
||||
|
||||
{{< figures/code >}}
|
||||
```go-html-template
|
||||
{{ $d := dict "a" 1 "b" 2 "c" 3 }}
|
||||
{{ $d = merge $d (dict "b" 4) }}
|
||||
{{ $item = index "b" $d }}
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
`merge` takes a variable number of arguments, and merges dictionaries left to
|
||||
right. So, items in dictionaries later in the argument list will override items
|
||||
in dictionaries earlier in the list.
|
||||
|
||||
Just to underscore, you have to set the update dictionary back to the original
|
||||
variable to complete the update, hence the `$d = ...`.
|
||||
|
||||
All that is to say: at the end of that snippet, `$item` will get the value 4.
|
||||
|
||||
## A Complex Example: A Dictionary of Arrays
|
||||
|
||||
For the previously mentioned template changes I was making, I was updating the
|
||||
`terms` template for my category taxonomy. For each category, I wanted to show
|
||||
one section per tag, and a list of all the posts with that tag underneath.
|
||||
|
||||
My categories are high level groups like "Tech," "Music," and "Travel." Tags are
|
||||
more specific topics for the post like "Web" or "Compositions." Pages only ever
|
||||
have one category but they can have multiple tags.
|
||||
|
||||
A `terms` template lets you access an array of terms, and the pages associated
|
||||
with those terms. You can access the tags attached to a page with the
|
||||
`.GetTerms` function. Here's what I did, and then I'll talk through it:
|
||||
|
||||
{{< figures/code >}}
|
||||
```go-html-template
|
||||
{{- $pagesByTag := dict -}}
|
||||
{{- range $page := .Pages -}}
|
||||
{{- range $tag := .GetTerms "tags" -}}
|
||||
{{- $tagName := $tag.Name -}}
|
||||
{{- if not (in $pagesByTag $tagName) -}}
|
||||
{{- $pagesByTag = merge $pagesByTag
|
||||
(dict $tagName (slice $page)) -}}
|
||||
{{- else -}}
|
||||
{{- $pagesForTag := index $pagesByTag $tagName -}}
|
||||
{{- $pagesForTag = $pagesForTag | append $page -}}
|
||||
{{- $pagesByTag = merge $pagesByTag
|
||||
(dict $tagName $pagesForTag) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
`$pagesByTag` is my empty dictionary. It will hold tag names as keys, each
|
||||
pointing to a slice (array) of page objects. For each page, I get its list of
|
||||
tags. For each tag, I check `$pagesByTag` to see if it already has a key/value
|
||||
pair for that tag. If not, I create a new entry in `$pagesByTag` with `merge`.
|
||||
If it does already, I get the slice for that tag with `index`, add the Page to
|
||||
the slice with `append`, and then merge the updated slice back into
|
||||
`$pagesByTag` with `merge`.
|
||||
|
||||
It's not too bad once it's all spelled out, but it does feel like more work than
|
||||
it should take for such simple operations.
|
||||
|
||||
I think this API could be improved substantially with some new functions that
|
||||
operate specifically on dictionaries and that have clear names that describe
|
||||
what they do.
|
||||
|
||||
[dict]: https://gohugo.io/functions/dict/
|
||||
[index]: https://gohugo.io/functions/index-function/
|
||||
[merge]: https://gohugo.io/functions/merge/
|
||||
[nsdictionary-init]: https://developer.apple.com/documentation/foundation/nsdictionary/1574181-dictionarywithobjectsandkeys?language=objc
|
34
content/blog/2022/11/lunar-eclipse.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
title: "Lunar Eclipse 🌝"
|
||||
date: 2022-11-07T08:37:45-08:00
|
||||
description: A quick note about the upcoming lunar eclipse in the morning of 2022-11-08.
|
||||
categories: Space
|
||||
tags: [Moon, Lunar Eclipse]
|
||||
---
|
||||
|
||||
I shouldn't be surprised (but I am) that the lunar eclipse happening tomorrow
|
||||
morning has [its own Wikipedia page][wp]. It won't be visible at all from most
|
||||
of Europe and Africa, but it will be from most of North America, and on the west
|
||||
coast of North America, we'll be able to see it all. Yay!
|
||||
|
||||
All the times on that page are in UTC. Here are some handy conversions (in 24-hr
|
||||
form) to PST for those of us on the US west coast:
|
||||
|
||||
| Contact | Time (PST) |
|
||||
|:--------:|:----------:|
|
||||
| P1 | 00:02 |
|
||||
| U1 | 01:09 |
|
||||
| U2 | 02:16 |
|
||||
| Max | 02:59 |
|
||||
| U3 | 03:41 |
|
||||
| U4 | 04:49 |
|
||||
| P4 | 04:56 |
|
||||
|
||||
If these times make no sense to you, the eclipse starts at roughly midnight
|
||||
tonight, the total eclipse is between 02:16 and 03:41, and it ends at 04:56.
|
||||
|
||||
More information about contact points for lunar eclipses can be found in the
|
||||
Timing section on the Wikipedia page for [Lunar Eclipse][wp-le].
|
||||
|
||||
[wp]: https://en.wikipedia.org/wiki/November_2022_lunar_eclipse
|
||||
[wp-le]: https://en.wikipedia.org/wiki/Lunar_eclipse#Timing
|
66
content/blog/2022/11/my-best-nethack-game-so-far/index.md
Normal file
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
title: "My Best Nethack Game (So Far)"
|
||||
date: 2022-11-24T09:13:15-05:00
|
||||
description: A summary of my best-to-date game of Nethack.
|
||||
categories: ["Games"]
|
||||
tags: ["Nethack", "Roguelikes", "Video Games"]
|
||||
resources:
|
||||
- name: wishing
|
||||
src: wishing.png
|
||||
title:
|
||||
- name: oracle
|
||||
src: oracle.png
|
||||
title:
|
||||
---
|
||||
|
||||
I just finished my best ever game of Nethack. I earned 31,118 points as a level
|
||||
9 Valkyrie.
|
||||
|
||||
Some highlights:
|
||||
|
||||
I got all the way to the bottom of the Mines, fought several vampires and
|
||||
trolls, but somehow completely missed the luckstone. I did pick up a grey stone,
|
||||
but it ended up being a touchstone.
|
||||
|
||||
I got a Wand of Wishing! I was zap testing wands in Minetown and the game asked
|
||||
me for a wish! I had no idea what to wish for -- it was my first time getting a
|
||||
wish 😱 -- but I had been watching [Adeon's Nethack speedrun][adeon] at the 2017
|
||||
Roguelike Celebration and remembered him wishing for some absurdly qualified
|
||||
dragon scale mail, so I ended up with a *+2 uncursed silver dragon scale mail*
|
||||
that brought my AC down to -10. It saved my butt later on when I ran into a
|
||||
bunch of winter wolf pups because it reflected their cold beams.
|
||||
|
||||
{{< figures/image name=wishing >}}
|
||||
|
||||
Later on in the mines, I stepped on a polymorph trap that transformed me into an
|
||||
ice dragon. That transformation caused my dragon scale mail to fuse into my
|
||||
body. When the transformation ended, I was left with simple dragon scales,
|
||||
which were still silver, but no longer had the big defense bonus. Boo. :( I did
|
||||
get to lay two eggs that hatched into baby ice dragons. Baby dragons are
|
||||
ravenous and indiscrimate.
|
||||
|
||||
The baby dragons killed the Oracle. Whoops.
|
||||
|
||||
{{< figures/image name=oracle >}}
|
||||
|
||||
In the oracle level, I tried to dip my long sword into the fountains around the
|
||||
Oracle, hoping to find Excalibur. Just a few turns before, I'd fought a
|
||||
gelatinous cube that corroded my sword. I didn't notice at the time though, so
|
||||
dipping went horribly wrong... Not only did my corroded long sword become
|
||||
*thoroughly rusted*, it also eventually became cursed. Oddly enough, that sword
|
||||
was still the best weapon I had, and I used it to the very end. I need to figure
|
||||
out how to replace weapons that get degraded like that.
|
||||
|
||||
I solved Sokoban with some help from the Nethack wiki for the first time. The
|
||||
puzzles are hard but I enjoyed thinking through them. The monsters in there,
|
||||
especially in the upper levels are *hard*: multiple elementals, several packs of
|
||||
winter wolf pups, yetis, apes, and a zruty. I got through the last level and
|
||||
discovered a mimic blocking the hallway to the zoo. It killed me.
|
||||
|
||||
Near the end, I was testing amulets and put on an Amulet of Changing that caused
|
||||
me to switch genders. Boo.
|
||||
|
||||
It's in my [logfile][logfile] now too.
|
||||
|
||||
[adeon]: https://www.youtube.com/watch?v=rIB0y_kwFuY
|
||||
[logfile]: {{< ref "nethack" >}}
|
BIN
content/blog/2022/11/my-best-nethack-game-so-far/oracle.png
Normal file
After Width: | Height: | Size: 1 MiB |
BIN
content/blog/2022/11/my-best-nethack-game-so-far/wishing.png
Normal file
After Width: | Height: | Size: 1.5 MiB |
20
content/blog/2022/11/week-of-soup.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: "Week of Soup"
|
||||
date: 2022-11-11T12:42:33-08:00
|
||||
categories: ["Food"]
|
||||
tags: ["Soup", "San Francisco", "Restaurants"]
|
||||
---
|
||||
|
||||
{{< tess >}} and I were both sick this week and had a hankering for soup. We
|
||||
ended up eating soup for lunch every day. (What can I say, we like soup.) All of
|
||||
these places are excellent.
|
||||
|
||||
- Matzo Ball Soup from [Wise Sons][ws]
|
||||
- Pozole and caldo de pollo from [La Espiga de Oro][edo]
|
||||
- Ramen from [Coco's][cocos]
|
||||
- Chicken Noodle Soup and dumplings from [Leleka][leleka]
|
||||
|
||||
[ws]: https://www.wisesonsdeli.com
|
||||
[edo]: https://www.yelp.com/biz/la-espiga-de-oro-san-francisco
|
||||
[cocos]: http://www.cocoramen.com
|
||||
[leleka]: https://lelekasf.com
|
17
content/blog/2022/11/where-am-i.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
title: "Where Am I"
|
||||
date: 2022-11-20T07:42:27-08:00
|
||||
categories: "Tech"
|
||||
tags: ["Twitter", "Me", "News"]
|
||||
---
|
||||
|
||||
In the wake of Elon Musk taking control of Twitter, a lot of folks have decided
|
||||
it's not as welcoming a place as it once was. In my circles, there's been a huge
|
||||
movement of people to [Mastodon][m] and [cohost][c] mainly. I have accounts on
|
||||
all of those places, though I haven't quite figured out where I'll land yet. If
|
||||
you're interested in following me anywhere else on the internet, I made
|
||||
[a handy list][where].
|
||||
|
||||
[m]: https://mastodon.social/
|
||||
[c]: https://cohost.org
|
||||
[where]: {{< ref "about/where-am-i" >}}
|
4
content/blog/2022/_index.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: 2022
|
||||
date: 2022-01-01
|
||||
---
|
4
content/blog/2023/_index.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: 2023
|
||||
date: 2023-01-01
|
||||
---
|
17
content/blog/2023/atom-feed-bug-fixes/index.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
title: "Atom Feed Bug Fixes"
|
||||
date: 2023-08-09T08:43:26-07:00
|
||||
categories: ["Tech"]
|
||||
tags: ["Erynwells.me", "Meta", "Atom"]
|
||||
---
|
||||
|
||||
A kind reader pointed out to me that my Atom feed was incorrect. There were two
|
||||
problems. First, I was specifying an incorrect URL in the feed's `<link
|
||||
rel="self">` -- it was pointing to a nonexistant feed.xml file. Second, I was
|
||||
omitting a `<link>` tag from the entries entirely.
|
||||
|
||||
Thunderbird didn't like this. With no `<link>` for an entry, it would show the
|
||||
feed's `<link>` in it's UI. And that link left users at a 404 page.
|
||||
|
||||
I pushed a fix this morning. You might have to refresh or resubscribe to pick up
|
||||
the changes.
|
22
content/blog/2023/chess/index.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
title: "Chess"
|
||||
date: 2023-11-20T14:58:56-08:00
|
||||
categories: Chess
|
||||
tags: ["Games", "Hobbies"]
|
||||
---
|
||||
|
||||
I've been playing a lot of chess lately. {{< tess >}} and I have been watching
|
||||
[Slow Horses][slow-horses] on Apple TV+, and there was a recent episode in which
|
||||
a chess game between two characters is a key plot beat. That got me thinking
|
||||
about playing again.
|
||||
|
||||
I learned chess as a kid. My dad taught me. I played in chess clubs in
|
||||
elementary and middle school. I was really into it for a while!
|
||||
|
||||
I have a [Chess.com](chess.com) account now: [erynrwells][chess-com-profile].
|
||||
I'm also on [lichess.org](lichess.org): I'm [erynrwells][lichess-profile] there too.
|
||||
Send me a friend request or challenge? :)
|
||||
|
||||
[slow-horses]: https://tv.apple.com/us/show/slow-horses/umc.cmc.2szz3fdt71tl1ulnbp8utgq5o
|
||||
[chess-com-profile]: https://www.chess.com/member/erynrwells
|
||||
[lichess-profile]: https://lichess.org/@/erynrwells
|
13
content/blog/2023/guide-to-computing/index.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
title: "Guide to Computing"
|
||||
date: 2023-09-23T10:24:35-07:00
|
||||
categories: "Tech"
|
||||
tags: ["Retro Computing", "Design"]
|
||||
---
|
||||
|
||||
I really enjoyed looking through the images on [Docubyte's Guide to
|
||||
Computing][link]. It depicts machines from the early days of modern computing --
|
||||
think IBM mainframes, PDP-1's, and lots of midcentury modern design -- in a way
|
||||
I found really intriguing.
|
||||
|
||||
[link]: https://www.docubyte.com/projects/guide-to-computing/
|
38
content/blog/2023/hello-chess-friend.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
title: "Hello Chess Friend"
|
||||
description: I started building a chess engine in Rust. Here it is.
|
||||
date: 2023-12-29T08:29:00-08:00
|
||||
series: chess-friend
|
||||
categories: Tech
|
||||
tags: [Programming, Chess]
|
||||
---
|
||||
|
||||
I started [playing a lot of chess][chess-post] recently. As often happens with
|
||||
me, it wasn't very long until I started wondering how I could Do Programming To
|
||||
It.
|
||||
|
||||
I found the mostly excellent, occasionally vague and confusing [Chess
|
||||
Programming Wiki][cpwiki] and have been using that as a guide. It helpfully says
|
||||
this on it's [Getting Started][cpgs] page:
|
||||
|
||||
> The **very first step** to writing a chess engine is to write a complete, bug
|
||||
> free board representation that knows every rule of chess.
|
||||
|
||||
As a software engineer, the "bug free" bit cracks me up.
|
||||
|
||||
My engine is called ChessFriend. It uses [bitboards][cpbb] for its board
|
||||
representation. As of this post, I've managed to write a board representation
|
||||
that allows me to place pieces of both colors on any square, and I'm hacking
|
||||
away at the move generator. I've also written a small command line "board
|
||||
explorer" utility that can interact with my board representation. Of course, it
|
||||
has a pile of unit tests, helping me inch ever-so-slowly toward that blissful
|
||||
bug-free state.
|
||||
|
||||
It's written in Rust. I've [_mostly_][rust-bc-toot] avoided fighting with the
|
||||
borrow checker.
|
||||
|
||||
[chess-post]: {{< ref "chess" >}}
|
||||
[cpwiki]: https://www.chessprogramming.org/Main_Page
|
||||
[cpgs]: https://www.chessprogramming.org/Getting_Started
|
||||
[cpbb]: https://www.chessprogramming.org/Bitboards
|
||||
[rust-bc-toot]: https://mastodon.social/@erynofwales/111637122773195611
|
36
content/blog/2023/less-instagram-more-blog.md
Normal file
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
title: "Less Instagram, More Blog"
|
||||
description: Resolving, yet again, to blog more and social media less.
|
||||
date: 2023-12-27T08:56:44-07:00
|
||||
categories: Meta
|
||||
tags: [Writing, Resolutions, Habits]
|
||||
---
|
||||
|
||||
I've been thinking the last few days about how to make use of my blog in 2024. I
|
||||
made some vague noises in this general direction a few days ago in my [What
|
||||
Should I Blog About?][what-to-blog-about] post too.
|
||||
|
||||
My vision since I started posting more here has been to use it as a place to
|
||||
share all sorts of things: stuff I'm working on or thinking about; photos; and
|
||||
stories from travel and life.
|
||||
|
||||
I often fall into a trap when I sit down to write something in which I feel like
|
||||
I must first invent the Universe. The need to explain everything from first
|
||||
principles seriously hampers my ability (and frankly, desire) to write anything.
|
||||
I don't want to only post carefully thought out, highly edited and polished
|
||||
pieces, though I certainly hope _some_ of my posts reach that bar. I hope to
|
||||
also post quick notes and sketches of ideas. I've enjoyed reading some quicker
|
||||
posts from {{< tess >}} and [Elaine][e] this past year, and I'd like to follow
|
||||
their example.
|
||||
|
||||
{{< youtube zSgiXGELjbc >}}
|
||||
|
||||
I'm not setting myself a specific goal here. The idea is just "more" in a
|
||||
certain general direction. I don't want to commit to a specific frequency or
|
||||
quality. Instead, I'm hoping this post sets a foundation on which to build a
|
||||
sustainable thinking-writing-sharing habit.
|
||||
|
||||
Thanks for coming along. :)
|
||||
|
||||
[what-to-blog-about]: {{< ref "what-to-blog-about" >}}
|
||||
[e]: https://diplograph.net
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:562f6c157c43ef11ae55274b0bd1403c2cbb9a64b82443a2d9f4fb58b32606fd
|
||||
size 148873
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: "The Long Way to a Small, Angry Planet by Becky Chambers"
|
||||
slug: long-way-to-a-small-angry-planet-book
|
||||
date: 2023-02-01T09:16:48-08:00
|
||||
draft: true
|
||||
categories: Books
|
||||
tags: ["Science Fiction"]
|
||||
series: 2023-books
|
||||
---
|
||||
|
15
content/blog/2023/mastodon-icon/index.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: "Mastodon Icon"
|
||||
date: 2023-08-11T08:23:25-07:00
|
||||
categories: ["Tech"]
|
||||
tags: ["Meta", "Erynwells.me", "Web"]
|
||||
---
|
||||
|
||||
I finally got around to replacing the Twitter icon in the site's header with a
|
||||
link to my Mastodon page. It was surprisingly tricky because of how I styled and
|
||||
layed out those icons. I was able to clean up the SVGs a little bit too.
|
||||
|
||||
These days I have [way too many social media accounts][where-to-find-me]. I'm
|
||||
mostly on Mastodon and Instagram.
|
||||
|
||||
[where-to-find-me]: {{< ref "/about/where-am-i" >}}
|
14
content/blog/2023/nethack-illustrated-guide.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
title: "Nethack Illustrated Guide"
|
||||
date: 2023-01-07T08:52:53-08:00
|
||||
link: https://thinkmoult.com/nethack-illustrated-guide-mazes-of-menace.html
|
||||
categories: Games
|
||||
tags: [Nethack, Art, AI]
|
||||
---
|
||||
|
||||
While browsing {{< r nethack >}}, I came across a [post][post] from someone sharing a [collection of AI-generated
|
||||
images][guide] that illustrate the story arc of a game of Nethack. Between the images and the prose they added around
|
||||
it, I thought they did a fantastic job of capturing the mood of the game.
|
||||
|
||||
[post]: https://www.reddit.com/r/nethack/comments/zx965y/nethack_an_illustrated_guide_to_the_mazes_of/
|
||||
[guide]: https://thinkmoult.com/nethack-illustrated-guide-mazes-of-menace.html
|
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 197 KiB |
After Width: | Height: | Size: 429 KiB |
47
content/blog/2023/netscape-meteors-retrospective/index.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: "Netscape Meteors: Retrospective"
|
||||
date: 2023-08-05T17:14:40-07:00
|
||||
description: Someone shared my Netscape Meteors post on the Orange Website, causing it to be moderately viral for a few days. Here’s an update on the web traffic my server received.
|
||||
categories: ["Tech"]
|
||||
tags:
|
||||
- History
|
||||
- Meta
|
||||
- Netscape
|
||||
- Web
|
||||
- Web Browsers
|
||||
---
|
||||
|
||||
Last week, I published a small blog post about trying to find the original
|
||||
[Netscape "meteors"][meteors] loading animation. At some point that night,
|
||||
someone posted it on the orange website, and ... it did some numbers.
|
||||
|
||||
As of today, five days later, my server has registered just over 200,000 page
|
||||
accesses.
|
||||
|
||||
{{< figures/code >}}
|
||||
```txt
|
||||
% grep "GET /blog/2023/08/netscape-meteors/" \
|
||||
/var/log/nginx/access.log | wc -l
|
||||
200201
|
||||
```
|
||||
{{< /figures/code >}}
|
||||
|
||||
Bandwidth saw a bit of a spike too.
|
||||
|
||||
{{< figures/image name=bandwidth-2023-08-05.png shouldShowTitle=false >}}
|
||||
|
||||
To my knowledge this is the first time anything I've published on the internet
|
||||
has been picked up by Hacker News. It's jarring to realize so many people have
|
||||
visited my website in the last several days, reading this and other things I've
|
||||
written, listening to music I've published, and looking through photos I've
|
||||
posted. It's a little like having surprise house guests and realizing you
|
||||
haven't tidied up in a little while.
|
||||
|
||||
I only took a brief look at the comments. I was pleased to see they were civil,
|
||||
and mostly reminiscing about the days of Netscape and the early web. I had a few
|
||||
people reach out to tell me they enjoyed my post too.
|
||||
|
||||
Thanks, y'all, for reading my little corner of the web, and for your kind
|
||||
words.
|
||||
|
||||
[meteors]: {{< ref "blog/2023/netscape-meteors" >}}
|
97
content/blog/2023/netscape-meteors/index.md
Normal file
|
@ -0,0 +1,97 @@
|
|||
---
|
||||
title: "Netscape Meteors"
|
||||
date: 2023-08-01T18:23:33-07:00
|
||||
description: I went on a hunt to find the "Meteors" loading animation from Netscape back in the 90s, and wrote up my adventure.
|
||||
resources:
|
||||
- name: netscape60
|
||||
title: Netscape Meteor Loading Animation
|
||||
src: netscape-meteors.gif
|
||||
- name: netscape-modified60
|
||||
title: Modified Netscape Meteor Loading Animation, Small
|
||||
src: netscape-meteors-modified-60.gif
|
||||
- name: netscape-modified240
|
||||
title: Modified Netscape Meteor Loading Animation, Large
|
||||
src: netscape-meteors-modified-240.gif
|
||||
- name: rectangular-pixels
|
||||
title: Rectangular Pixels
|
||||
src: rectangular-pixels.png
|
||||
alt: "A zoomed in screenshot of an animation frame with pixel grid enabled,
|
||||
showing rectangular pixels"
|
||||
categories: Tech
|
||||
tags: ["Netscape", "History", "Web Browsers", "Web"]
|
||||
---
|
||||
|
||||
I went on a small journey the last couple days to find the original Netscape
|
||||
Navigator "meteors" animation. This one has a special place in my head and
|
||||
heart because it is so clearly connected to my memories of discovering the
|
||||
web as a kid. Here it is in its original 60×60 px glory:
|
||||
|
||||
{{< figures/image name=netscape60 shouldShowTitle=false size=small >}}
|
||||
|
||||
I started out doing some web searches that turned up several versions. One was
|
||||
promising but far too big: 400×400 px. Worse, after some shoddy resize
|
||||
attempts, the "pixels" had become rectangular.
|
||||
|
||||
{{< figures/image name=rectangular-pixels shouldShowTitle=false size=small >}}
|
||||
|
||||
This would not do.
|
||||
|
||||
I continued searching, hoping to find the original animations. I found someone's
|
||||
[mirror of Netscape 5.0 on Github][gh-netscape]. Then I found some [very old
|
||||
versions of Mozilla][moz-netscape] on a Mozilla FTP server. Sadly, the
|
||||
animations had been stripped out of these archives. :(
|
||||
|
||||
Frustrated with hitting several deadends, I complained to {{< tess >}} and
|
||||
wondered aloud if anyone might have the original images stashed away somewhere.
|
||||
She quipped that if anyone did, it would be Jamie Zawinski.
|
||||
|
||||
A little later, I posted about it on Mastodon.
|
||||
|
||||
<iframe
|
||||
src="https://mastodon.social/@erynofwales/110817133916254596/embed"
|
||||
class="mastodon-embed"
|
||||
style="max-width: 100%; border: 0"
|
||||
width="400"
|
||||
allowfullscreen="allowfullscreen">
|
||||
</iframe>
|
||||
|
||||
And wouldn't you know it, a friend tagged [`@jwz`][masto-jwz] asking if he had
|
||||
it, and a few moments later I got a reply from [Jamie][jwz] himself.
|
||||
|
||||
<iframe
|
||||
src="https://mastodon.social/@jwz/110817331045294426/embed"
|
||||
class="mastodon-embed"
|
||||
style="max-width: 100%; border: 0"
|
||||
width="400"
|
||||
allowfullscreen="allowfullscreen">
|
||||
</iframe>
|
||||
|
||||
If you don't know, Jamie Zawinski is well-know for working on several important
|
||||
software projects in the '90s. He worked on Netscape Navigator, built and
|
||||
maintains [Xscreensaver][xscreensaver], and several other things. Nowadays, he
|
||||
owns and runs [DNA Lounge][dna] in San Francisco.
|
||||
|
||||
There are a lot of neat bits of web browser history on the page he linked --
|
||||
totally worth a quick look over -- but most important to the quest at hand, it
|
||||
had that Netscape meteors loading animation.
|
||||
|
||||
The original one has some small artifacts on the left side of frame 10 that
|
||||
render as red and orange pixels. These bothered me enough that I made a version
|
||||
that replaces those pixels with ones that match the surrounding pixels. Here's
|
||||
the modified 60×60 one and a bigger 240×240 px one, for good
|
||||
measure:
|
||||
|
||||
{{< content-grid columns=2 >}}
|
||||
{{< figures/image name="netscape-modified60" shouldShowTitle=false shouldResize=false size=small >}}
|
||||
{{< figures/image name="netscape-modified240" shouldShowTitle=false shouldResize=false size=small >}}
|
||||
{{< /content-grid >}}
|
||||
|
||||
<script src="https://mastodon.social/embed.js" async="async"></script>
|
||||
|
||||
[gh-netscape]: https://github.com/zii/netscape
|
||||
[moz-netscape]: https://ftp.mozilla.org/pub/mozilla/source/
|
||||
[masto-jwz]: https://mastodon.social/@jwz
|
||||
[jwz]: https://www.jwz.org
|
||||
[xscreensaver]: https://www.jwz.org/xscreensaver/
|
||||
[dna]: https://www.jwz.org
|
||||
[about-jwz]: https://www.jwz.org/doc/about-jwz.html
|
After Width: | Height: | Size: 248 KiB |
After Width: | Height: | Size: 40 KiB |
BIN
content/blog/2023/netscape-meteors/netscape-meteors.gif
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
content/blog/2023/netscape-meteors/rectangular-pixels.png
Normal file
After Width: | Height: | Size: 781 KiB |
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fa3b6749eb23bff608c4b7233746c6d0577c689786cc69d7354758959e56a9fc
|
||||
size 159168
|
32
content/blog/2023/once-upon-a-time-on-mars-book/index.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
title: Once Upon a Time I Lived on Mars by Kate Greene
|
||||
slug: once-upon-a-time-i-lived-on-mars-book
|
||||
description: A brief book report.
|
||||
date: 2023-02-20T09:16:48-08:00
|
||||
date_finished: 2023-02-20T00:00:00-08:00
|
||||
categories: Books
|
||||
tags: [Memoirs, Space]
|
||||
series: 2023-books
|
||||
resources:
|
||||
- name: cover
|
||||
src: cover.jpeg
|
||||
alt: "The cover of Once Upon a Time I Lived on Mars by Kate Greene, with a subtitle that reads 'Space, Exploration, and Life on Earth'"
|
||||
title:
|
||||
---
|
||||
|
||||
{{< figures/image name=cover >}}
|
||||
|
||||
{{< tess >}} got me this book for Christmas 2022 on a whim at a local bookshop. It's a series of essays -- reflections
|
||||
and examinations -- of the author's time parcipating in one of NASA's Mars analog missions on Mauna Kea, Hawai'i. She is
|
||||
a lesbian and, as luck would have it, lived in the same part of town that Tess and I do! It was fun to read little
|
||||
anecdotes about her and her (ex) wife stopping in at shops that we frequent ourselves.
|
||||
|
||||
I enjoyed reading about her experiences working with NASA in the context of an analog mission. It sounds like they went
|
||||
above and beyond to make the mission as close to a real Mars experience as possible, despite being firmly on Earth.
|
||||
Communication with mission control was artificially delayed 20 minutes, as it would be on Mars. Going outside required
|
||||
putting on bulky spacesuits. And participants were isolated together for six months.
|
||||
|
||||
She also has several essays in which she reflected on the politics and cost of spaceflight, and what it means for humans
|
||||
to explore and exist in space.
|
||||
|
||||
Support a local bookshop and get it from [Folio Books](https://www.foliosf.com/book/9781250796660). 🙂
|
17
content/blog/2023/pajaro-dunes/index.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
title: "Pajaro Dunes"
|
||||
date: 2023-05-30T08:31:34-07:00
|
||||
tags: [Travel, Beaches, Tess, EJ, Vacations]
|
||||
---
|
||||
|
||||
{{< tess >}}, EJ, and I took a weekend trip down the coast over Memorial Day
|
||||
weekend this year to stay in a beachside condo in Pajaro Dunes, just west of
|
||||
Watsonville. We enjoyed hanging out on the beach, playing music and games,
|
||||
building [Kiwi Crates][kiwi], and just generally being together. I took a couple
|
||||
photos too. :)
|
||||
|
||||
{{< photo "2023/pajaro-dunes" >}}
|
||||
|
||||
{{< photo "2023/sunset-over-pajaro-dunes" >}}
|
||||
|
||||
[kiwi]: https://www.kiwico.com
|
3
content/blog/2023/tahoe-ski-trip/cabin.jpg
Normal file
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ded6e76e0903c82d1db25e8df3f5b68ac72bd07774648ee2dab2c68c0ddbc77f
|
||||
size 2616121
|
56
content/blog/2023/tahoe-ski-trip/index.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
title: "Tahoe Ski Trip"
|
||||
date: 2023-01-30T12:40:15-08:00
|
||||
draft: true
|
||||
categories: Travel
|
||||
tags: [Friends, Snowboarding, Snowshoeing]
|
||||
resources:
|
||||
- name: cabin
|
||||
src: cabin.jpg
|
||||
title:
|
||||
params:
|
||||
alt: A cozy cabin living room, with wood panel walls, lots of fuzzy, furry pillows, and a high steeply pitched roof. A fireplace is off to the right, and a large couch occupies the middle of the room. The windows fill the wall, floor to ceiling. Outside, you can see many tall pine trees and snow falling.
|
||||
- name: snowshoeing
|
||||
src: snowshoeing.jpg
|
||||
title:
|
||||
params:
|
||||
alt: "A selfie of three people: me and two friends, wearing cold weather gear and standing in the snow. A well-traveled path in the snow meanders through the snow-covered trees."
|
||||
---
|
||||
|
||||
This weekend I took a trip to the north side of Lake Tahoe with a group of coworkers and friends to ski and snowboard,
|
||||
and enjoy the mountains and each other's company. We stayed in an AirBnb in Truckee, and spent a couple days up at
|
||||
Northstar.
|
||||
|
||||
{{< figures/image name=cabin >}}
|
||||
|
||||
We all spent Friday on the mountain. I took an all-day group snowboarding lesson, while the rest did runs all over the
|
||||
moutain. My lesson was a small group, just five of us, and it was really great. We were all newbies, and very
|
||||
encouraging of each other. Our instructor pushed us quickly through standing and short glides on the board, to longer J
|
||||
turns, and traversing the bunny hill. Before lunch, we were doing the Big Easy. I felt like I was starting to get the
|
||||
hang of it, and the instructor agreed. After lunch, we went up the Arrow Express lift and took Lumberjack all the way
|
||||
down. It was harrowing--I have bad memories of the first drop from the last time I was at Northstart--but we all made
|
||||
it! By the end, I was feeling much more confident on the board, though I was also pretty beat up from several rough
|
||||
falls throughout the day.
|
||||
|
||||
We all went to bed pretty sore that night, but the next day we got up and did it all again. There were a _lot_ more
|
||||
people on the mountain on Saturday, so wait times for lifts were longer. Despite that I got a bunch of good runs in,
|
||||
including one from the top of the moutain with the rest of my group! I ended the day with two runs from the top of the
|
||||
moutain, and a sore tail bone, but with much more confidence in my ability to turn and stop on a board. 🤙🏻
|
||||
|
||||
The third day was a rest day for me. While some went back up the mountain for another day of skiing, a few others of us
|
||||
decided to go on a snowshoe hike. This was my first time with snowshoes. They're a bit awkward, but pretty easy to get
|
||||
the hang of. We took a loop around a small lake near Donner Lake. I always enjoy the peace that being out in nature
|
||||
brings, and it was great to catch up with two of my friends in a smaller group.
|
||||
|
||||
{{< figures/image name=snowshoeing >}}
|
||||
|
||||
At the end of the day, we scrambled to amass a stockpile of snowballs for an ambush! The guys in our group had decided
|
||||
to go skiing a third day, and were on their way back. When they pulled into the driveway, we attacked! Honestly, it
|
||||
wasn't anywhere near a fair fight. 😅 They fought back though, survived the onslaught, and we all had a great time.
|
||||
|
||||
Spending quality time with friends, going on trips like this, means a lot to me. I've gotten to do trips with friends
|
||||
like this a few times over the last couple years, but it's been a bit since I went with _this_ group. I had so much fun
|
||||
being up in the moutains, hanging out, playing games, and having long conversations. We're such a good group, and I'm
|
||||
grateful to have all of them in my life.
|
||||
|
||||
Thanks y'all. <3
|
3
content/blog/2023/tahoe-ski-trip/snowshoeing.jpg
Normal file
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:00a65714e64f92a4ae67d2d21c8be81341a433f9eabb23c032f835d8cc6a0580
|
||||
size 3079324
|
3
content/blog/2023/the-storyteller-book/cover.jpg
Normal file
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6d82f2760711b2c4b7148bf22a141002a937890b8abb8568b4fd3746d8edf0d0
|
||||
size 55408
|
26
content/blog/2023/the-storyteller-book/index.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
title: "The Storyteller by Dave Grohl"
|
||||
slug: the-storyteller-book
|
||||
date: 2023-01-31T09:17:09-08:00
|
||||
date_finished: 2023-01-31T09:17:09-08:00
|
||||
series: 2023-books
|
||||
categories: Books
|
||||
tags: [Memoirs]
|
||||
resources:
|
||||
- name: cover
|
||||
src: cover.jpg
|
||||
alt: "The cover of The Storyteller by Dave Grohl: a profile photo of Dave Grohl with the title of the book overlaid."
|
||||
title:
|
||||
---
|
||||
|
||||
{{< figures/image name=cover >}}
|
||||
|
||||
I checked out [Dave Grohl's new memoir](https://www.davegrohlstoryteller.com) from the San Francisco Public Library as
|
||||
an audiobook after a friend recommended it to me. Broadly, it's a series of anecdotes from his life, many of which
|
||||
include famous celebrities and musicians.
|
||||
|
||||
Dave seems like a really genuine person. Throughout the book he expresses his gratitude for the people who've supported
|
||||
him along the way. He's been through many challenging experiences too, and reflects on them with a positive attitude. I
|
||||
enjoyed his humor and humility, and the pearls of wisdom he'd earned from those experiences.
|
||||
|
||||
[SFPL](https://sfpl.bibliocommons.com/v2/record/S93C4875170)
|
22
content/blog/2023/trip-to-japan/index.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
title: Trip to Japan
|
||||
date: 2023-04-14T21:40:21+09:00
|
||||
categories: Travel
|
||||
tags:
|
||||
- Travel
|
||||
- japan
|
||||
---
|
||||
|
||||
At the beginning of April, {{< tess >}} and I took a trip to Japan for two
|
||||
weeks. She had a work meeting to attend in Tōkyō, and we were lucky to be able
|
||||
to extend the trip to take some vacation before her meeting.
|
||||
|
||||
This was my first trip to Japan. I had been wanting to travel there since I was
|
||||
a kid playing Pokémon Red on my OG Game Boy. To say I was excited is a bit of an
|
||||
understatement.
|
||||
|
||||
You can read all about our trip [on my travel log page][series-page]. Tess also
|
||||
wrote about it [on her website][tess-post].
|
||||
|
||||
[series-page]: {{< ref "/series/2023-japan" >}}
|
||||
[tess-post]: https://tess.oconnor.cx/2023/04/japan
|
13
content/blog/2023/what-to-blog-about/index.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
title: What Should I Blog About?
|
||||
date: 2023-12-20T08:06:34-08:00
|
||||
link: https://css-irl.info/what-to-blog-about-when-you-dont-know-what-to-blog-about/
|
||||
categories: Tech
|
||||
tags: [Writing]
|
||||
---
|
||||
|
||||
I came across this handy list of [things to blog about when you don't know what
|
||||
to blog about][link]. As someone who often doesn't know what to blog about, it's
|
||||
nice to have a list of ideas for what to blog about.
|
||||
|
||||
[link]: https://css-irl.info/what-to-blog-about-when-you-dont-know-what-to-blog-about/
|
3
content/blog/2023/yerba-buena-book/cover.jpg
Normal file
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7bc98cce772e0c075daed5a6dd9b1c18e6fd19e94ac68197893fd14cb58cf790
|
||||
size 83946
|
27
content/blog/2023/yerba-buena-book/index.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
title: "Yerba Buena by Nina LaCour"
|
||||
date: 2023-11-23T09:50:06-07:00
|
||||
series: 2023-books
|
||||
categories: Books
|
||||
tags: [Romance, Lesbians, Queerness]
|
||||
resources:
|
||||
- name: cover
|
||||
src: cover.jpg
|
||||
title:
|
||||
params:
|
||||
alt: An orange book cover with green leafy sprigs around the edges. Profiles of two women, overlapping and facing opposite directions. "Yerba Buena a novel" is written across the top half.
|
||||
---
|
||||
|
||||
I thought this book was a lesbian romance -- and it is -- but it's so much more
|
||||
too. It's the story of two women and the difficult pasts they emerge out of. I
|
||||
really enjoyed how LaCour wove together their processing of that trauma with
|
||||
growing into young women, making questionable choices, finding themselves, and
|
||||
ultimately each other. It's heavy at times, but also beautiful. I really enjoyed
|
||||
it. Thank you, {{< tess >}}, for the gift. ❤️
|
||||
|
||||
Get it at [Folio][folio] in San Francisco, or on [Bookshop.org][bookshop].
|
||||
|
||||
{{< figures/image name=cover class=content-width >}}
|
||||
|
||||
[folio]: https://www.foliosf.com/book/9781250810519
|
||||
[bookshop]: https://bookshop.org/p/books/yerba-buena-nina-lacour/18721506?ean=9781250810519
|
28
content/blog/2023/you-deserve-a-tech-union-book/index.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
title: "You Deserve a Tech Union by Ethan Marcotte"
|
||||
date: 2023-12-16T08:19:41-08:00
|
||||
draft: true
|
||||
series: 2023-books
|
||||
categories: Books
|
||||
tags: [Unions, Tech]
|
||||
---
|
||||
|
||||
Ethan's book came out in August 2023, and I've been eager to read it since he
|
||||
announced it. Unionization has been a hot topic in the US over the last several
|
||||
years. I've rooted for workers at Amazon, Google, Starbucks, and Apple to form
|
||||
unions and advocate for their rights vis á vis their employers. I think that
|
||||
work is so important.
|
||||
|
||||
This book provides an overview of the history of unions in the US, and in the US
|
||||
tech industry, plus some helpful thoughts on how to form unions in your
|
||||
workplace.
|
||||
|
||||
I found his arguments about why unions are important, even in an industrial
|
||||
sector considered to be rather plush compared to many others. Many people think
|
||||
of unions as organizations that advocate for _more_ privileges and protections
|
||||
for workers. However, at least as important as that work, they also ensure that
|
||||
workers retain the privileges they already have.
|
||||
|
||||
Get it from [A Book Apart][aba].
|
||||
|
||||
[aba]: https://abookapart.com/products/you-deserve-a-tech-union
|
4
content/blog/2024/_index.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: 2024
|
||||
date: 2024-01-01
|
||||
---
|