diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index b3cef50..3d374af 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -23,12 +23,11 @@ jobs: - name: Cache dependencies uses: Swatinem/rust-cache@v2 - - name: Install `tailwindcss` + - name: Install `typst` run: > - curl - -L https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 - -o /usr/local/bin/tailwindcss - && chmod +x /usr/local/bin/tailwindcss + curl -L https://github.com/typst/typst/releases/latest/download/typst-x86_64-unknown-linux-musl.tar.xz + | tar -xJf - + && mv typst-x86_64-unknown-linux-musl/typst /usr/local/bin/typst - name: Execute tests uses: actions-rs/cargo@v1 @@ -54,12 +53,11 @@ jobs: - name: Cache dependencies uses: Swatinem/rust-cache@v2 - - name: Install `tailwindcss` + - name: Install `typst` run: > - curl - -L https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 - -o /usr/local/bin/tailwindcss - && chmod +x /usr/local/bin/tailwindcss + curl -L https://github.com/typst/typst/releases/latest/download/typst-x86_64-unknown-linux-musl.tar.xz + | tar -xJf - + && mv typst-x86_64-unknown-linux-musl/typst /usr/local/bin/typst - name: Install `clippy` run: rustup component add clippy diff --git a/.gitignore b/.gitignore index e77a141..bbe74af 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ .LSOverride # Icon must end with two \r -Icon +Icon # Thumbnails ._* @@ -75,4 +75,4 @@ Cargo.lock # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,rust,macos,dotenv # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) - +/content/resume/resume.pdf diff --git a/Cargo.lock b/Cargo.lock index fd9f7c4..666d96b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,6 +43,15 @@ dependencies = [ "syn", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -201,6 +210,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" @@ -228,6 +243,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807" +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + [[package]] name = "color-eyre" version = "0.6.2" @@ -273,6 +294,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + [[package]] name = "crypto-common" version = "0.1.6" @@ -285,9 +312,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" dependencies = [ "powerfmt", "serde", @@ -332,19 +359,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "eyre" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" +checksum = "8bbb8258be8305fb0237d7b295f47bb24ff1b136a535f473baf40e70468515aa" dependencies = [ "indenter", "once_cell", @@ -352,9 +379,9 @@ dependencies = [ [[package]] name = "faster-hex" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239f7bfb930f820ab16a9cd95afc26f88264cf6905c960b340a615384aa3338a" +checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" dependencies = [ "serde", ] @@ -448,6 +475,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.28.1" @@ -646,9 +684,9 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c1e554a87759e672c7d2e37211e761aa390c61ffcd3753a57c51173143f3cb" +checksum = "1f8cf8c2266f63e582b7eb206799b63aa5fa68ee510ad349f637dfe2d0653de0" dependencies = [ "faster-hex", "thiserror", @@ -667,9 +705,9 @@ dependencies = [ [[package]] name = "gix-lock" -version = "11.0.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4feb1dcd304fe384ddc22edba9dd56a42b0800032de6537728cea2f033a4f37" +checksum = "7e5c65e6a29830a435664891ced3f3c1af010f14900226019590ee0971a22f37" dependencies = [ "gix-tempfile", "gix-utils", @@ -849,9 +887,9 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "11.0.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cc2205cf10d99f70b96e04e16c55d4c7cf33efc151df1f793e29fd12a931f8" +checksum = "388dd29114a86ec69b28d1e26d6d63a662300ecf61ab3f4cc578f7d7dc9e7e23" dependencies = [ "gix-fs", "libc", @@ -934,6 +972,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -964,6 +1011,20 @@ dependencies = [ "http", ] +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -982,7 +1043,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1126,9 +1187,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "lazy_static" @@ -1138,15 +1199,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" @@ -1233,13 +1294,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1291,9 +1352,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "overload" @@ -1327,7 +1388,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1368,12 +1429,29 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "postcard" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" +dependencies = [ + "cobs", + "heapless", + "serde", +] + [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1432,6 +1510,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1491,17 +1599,26 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" -version = "0.38.25" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1512,9 +1629,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "same-file" @@ -1531,6 +1648,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + [[package]] name = "serde" version = "1.0.193" @@ -1645,14 +1768,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "syn" -version = "2.0.39" +version = "2.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "13fa70a4ee923979ffb522cacce59d34421ebdea5625e1073c4326ef9d2dd42e" dependencies = [ "proc-macro2", "quote", @@ -1675,7 +1813,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1756,9 +1894,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" dependencies = [ "backtrace", "bytes", @@ -1768,7 +1906,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1977,9 +2115,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-bom" @@ -2058,7 +2196,9 @@ dependencies = [ "include_dir", "maud", "mime", + "postcard", "pulldown-cmark", + "rand", "serde", "serde_yaml", "thiserror", @@ -2127,7 +2267,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -2136,7 +2276,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -2145,13 +2294,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -2160,47 +2324,89 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" -version = "0.5.25" +version = "0.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e87b8dfbe3baffbe687eef2e164e32286eff31a5ee16463ce03d991643ec94" +checksum = "b67b5f0a4e7a27a64c651977932b9dc5667ca7fc31ac44b03ed37a0cf42fdfff" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index e479bae..8497fb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,16 +6,6 @@ name = "vidhan-site" publish = false version = "0.1.0" -[lints] - [lints.rust] - missing_copy_implementations = "warn" - missing_debug_implementations = "warn" - # missing_docs = "warn" - unsafe_code = "forbid" - - [lints.clippy] - nursery = "warn" - pedantic = "warn" [dependencies] axum = { version = "0.7", features = ["macros", "tracing"] } @@ -31,7 +21,9 @@ maud = { git = "https://github.com/vidhanio/maud", branch = "patch-1", features "axum", ] } mime = "0.3" +postcard = "1.0.8" pulldown-cmark = { git = "https://github.com/raphlinus/pulldown-cmark" } +rand = "0.8" serde = { version = "1", features = ["derive"] } serde_yaml = "0.9" thiserror = "1" @@ -48,3 +40,14 @@ tree-sitter-rust = "0.20" [build-dependencies] gix = { version = "0.56", default-features = false } + +[lints] + [lints.rust] + missing_copy_implementations = "warn" + missing_debug_implementations = "warn" + # missing_docs = "warn" + unsafe_code = "forbid" + + [lints.clippy] + nursery = "warn" + pedantic = "warn" diff --git a/Dockerfile b/Dockerfile index 1822ae7..63774e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,9 +10,9 @@ FROM chef AS builder COPY --from=planner /app/recipe.json . RUN cargo chef cook --release COPY . . -ADD --chmod=755 \ - https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 \ - /usr/local/bin/tailwindcss +RUN curl -L https://github.com/typst/typst/releases/latest/download/typst-x86_64-unknown-linux-musl.tar.xz \ + | tar xJf - \ + && mv typst-x86_64-unknown-linux-musl/typst /usr/local/bin/typst RUN cargo build --release RUN mv ./target/release/vidhan-site ./site diff --git a/build.rs b/build.rs index c4994a6..4b24a59 100644 --- a/build.rs +++ b/build.rs @@ -1,54 +1,51 @@ -use std::{env, error::Error, path::PathBuf, process::Command}; +use std::{ + env, + error::Error, + path::{Path, PathBuf}, + process::Command, +}; fn main() -> Result<(), Box> { - build_tailwind()?; - set_git_hash()?; - - Ok(()) -} - -fn build_tailwind() -> Result<(), Box> { - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?); - - let tailwind_config_file = manifest_dir.join("tailwind.config.js"); - let input_file = manifest_dir.join("static").join("styles.input.css"); - let src_dir = manifest_dir.join("src"); - - for path in [&tailwind_config_file, &input_file, &src_dir] { - println!("cargo:rerun-if-changed={}", path.display()); - } - + let cargo_manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?); let out_dir = PathBuf::from(env::var("OUT_DIR")?); - let output_file = out_dir.join("styles.css"); + compile_resume(&cargo_manifest_dir, &out_dir)?; + set_git_hash(&cargo_manifest_dir)?; - let mut command = Command::new("tailwindcss"); - - command.args([ - "build", - "-c", - &tailwind_config_file.to_string_lossy(), - "-i", - &input_file.to_string_lossy(), - "-o", - &output_file.to_string_lossy(), - ]); - - if env::var("PROFILE")? == "release" { - command.arg("--minify"); - } - - let status = command.spawn()?.wait()?; + Ok(()) +} - if !status.success() { - return Err("tailwindcss failed".into()); +fn compile_resume(manifest_dir: &Path, out_dir: &Path) -> Result<(), Box> { + let resume_dir = manifest_dir.join("content/resume"); + println!("cargo:rerun-if-changed={}", resume_dir.display()); + + let resume_path = resume_dir.join("resume.typ"); + + let output = Command::new("typst") + .arg("compile") + .arg(resume_path) + .arg(out_dir.join("resume.pdf")) + .output()?; + + if !output.status.success() { + return Err(format!( + "typst failed with status code {}\n {}", + output.status, + String::from_utf8_lossy(&output.stderr) + ) + .into()); } Ok(()) } -fn set_git_hash() -> Result<(), Box> { - let repo = gix::open(".")?; +fn set_git_hash(manifest_dir: &Path) -> Result<(), Box> { + println!( + "cargo:rerun-if-changed={}", + manifest_dir.join(".git/HEAD").display() + ); + + let repo = gix::open(manifest_dir)?; let commit_id = repo.head()?.id().ok_or("head commit should have id")?; diff --git a/content/blog/hello-world.md b/content/blog/hello-world.md deleted file mode 100644 index cdcc0c9..0000000 --- a/content/blog/hello-world.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: hello, world! -date: 2023-08-04 -description: welcome to my new website! ---- - -hi, i'm vidhan! welcome to my new site and blog, rewritten from the ground up using [rust](https://www.rust-lang.org/). -this site mainly uses two libraries, namely the http server, [axum](https://docs.rs/axum) and the html library -[maud](https://docs.rs/maud). one of the coolest features of this site, the code block syntax highlighting -is powered by [tree-sitter](https://tree-sitter.github.io/tree-sitter/). - -it also uses [tailwind css](https://tailwindcss.com/) for styling. - -take a look at a cool code block example below! - -```rust -#[cfg(test)] -mod tests { - use claims::assert_ok; - - use super::*; - - #[ignore = "don't want to spam the api"] - #[tokio::test] - async fn player_works() { - println!( - "{:#?}", - assert_ok!(AssistLeaders::::default().send().await) - ); - } -} -``` diff --git a/content/posts/hello-world.md b/content/posts/hello-world.md new file mode 100644 index 0000000..2b68e41 --- /dev/null +++ b/content/posts/hello-world.md @@ -0,0 +1,46 @@ +--- +title: hello, world! +date: 2023-12-10 +description: welcome to my new website! +--- + +hi, i'm vidhan! welcome to my new site and blog, rewritten from the ground up using [rust](https://www.rust-lang.org/). +this site mainly uses two libraries, namely the http server, [axum](https://docs.rs/axum) and the html library +[maud](https://docs.rs/maud). one of the coolest features of this site, the code block syntax highlighting, +is powered by [tree-sitter](https://tree-sitter.github.io/tree-sitter/). + +take a look at the code which powers this site's syntax highlighting: + +```rust +pub fn highlight(&self, language: &str, code: &str) -> crate::Result { + let Some(config) = self.0.get(language) else { + return Ok(html_escape::encode_text_minimal(code).into()); + }; + + let mut highlighter = Highlighter::new(); + + let mut highlights = + highlighter.highlight(config, code.as_bytes(), None, |lang| self.0.get(lang))?; + + highlights.try_fold(String::new(), |mut buf, event| { + match event? { + HighlightEvent::Source { start, end } => { + html_escape::encode_text_minimal_to_string(&code[start..end], &mut buf); + } + HighlightEvent::HighlightStart(Highlight(idx)) => { + write!( + buf, + "", + HIGHLIGHT_NAMES[idx].replace('.', " ") + ) + .expect("writing to a string should be infallible"); + } + HighlightEvent::HighlightEnd => { + buf.push_str(""); + } + } + + Ok(buf) + }) +} +``` diff --git a/content/projects.yml b/content/projects.yml deleted file mode 100644 index 3dc7801..0000000 --- a/content/projects.yml +++ /dev/null @@ -1,19 +0,0 @@ -- name: site - description: this website! - href: https://github.com/vidhanio/site - -- name: html-node - description: an html macro for rust. - href: https://github.com/vidhanio/html-node - -- name: fncli - description: an attribute macro to simplify writing simple clis in rust. - href: https://github.com/vidhanio/fncli - -- name: diswordle - description: a discord bot to play wordle right in your discord server. - href: https://github.com/vidhanio/diswordle - -- name: checkpoint - description: a discord bot to provide easy verification for discord servers in my school board. - href: https://github.com/vidhanio/checkpoint diff --git a/content/resume/icons/envelope.svg b/content/resume/icons/envelope.svg new file mode 100644 index 0000000..1347d46 --- /dev/null +++ b/content/resume/icons/envelope.svg @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/content/resume/icons/github.svg b/content/resume/icons/github.svg new file mode 100644 index 0000000..b54c0e9 --- /dev/null +++ b/content/resume/icons/github.svg @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/content/resume/icons/globe.svg b/content/resume/icons/globe.svg new file mode 100644 index 0000000..cd4cda7 --- /dev/null +++ b/content/resume/icons/globe.svg @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/content/resume/icons/link.svg b/content/resume/icons/link.svg new file mode 100644 index 0000000..8ce9852 --- /dev/null +++ b/content/resume/icons/link.svg @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/resume/icons/linkedin.svg b/content/resume/icons/linkedin.svg new file mode 100644 index 0000000..99aeb2f --- /dev/null +++ b/content/resume/icons/linkedin.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/content/resume/icons/phone.svg b/content/resume/icons/phone.svg new file mode 100644 index 0000000..8acb593 --- /dev/null +++ b/content/resume/icons/phone.svg @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/content/resume/resume.typ b/content/resume/resume.typ new file mode 100644 index 0000000..429ae6f --- /dev/null +++ b/content/resume/resume.typ @@ -0,0 +1,192 @@ +#set page(margin: 0.5in) + +#set list(indent: 1em) + +#show heading.where(level: 1): it => { + set align(center) + set text(weight: "bold", size: 2em) + it +} +#show heading: set text(weight: "semibold") +#show heading: smallcaps + +#let subtitle(body) = { + set align(center) + set text(weight: "semibold", size: 1.5em) + smallcaps(body) +} + +#let underlink(dest, body) = underline(link(dest, body)) + +#let icon(width: 0.75em, name) = box(width: width, image("icons/" + name + ".svg")) + +#let infos(..infos) = align( + center, +)[ + #infos.pos().map(info => link(info.href)[#icon(info.icon) #info.body]).join([ | ]) +] + +#let section(title, body) = [ + #block(below: 0.5em)[== #title] + #line(length: 100%) + #block(above: 0.75em)[#body] +] + +#let section-item( + title, + href: none, + post-title: none, + subtitle: none, + start: none, + end: none, + body, +) = { + let title = if href == none { + title + } else { + [#link(href)[#icon(width: 0.7em, "link") #title]] + } + + let post-title = if post-title == none { + none + } else { + [ | _ #post-title _ ] + } + + let date = if start == none and end == none { + none + } else if start == none { + panic("cannot specify only end") + } else { + let end = if end == none { + "Present" + } else { + end.display("[month repr:short]. [year]") + } + + [#start.display("[month repr:short]. [year]") - #end] + } + + [ + #block(below: 0.6em)[#box[=== #title] #post-title #h(1fr) #date] + _ #subtitle _ + + #body + ] +} + += Vidhan Bhatt + +#subtitle[Software Engineer] + +#infos( + (body: "me@vidhan.io", icon: "envelope", href: "mailto:me@vidhan.io"), + ( + body: "/in/vidhanio", + icon: "linkedin", + href: "https://www.linkedin.com/in/vidhanio", + ), + (body: "vidhanio", icon: "github", href: "https://github.com/vidhanio"), + (body: "vidhan.io", icon: "globe", href: "https://vidhan.io"), +) + +#section( + "Education", +)[ + #section-item( + "McMaster University", + post-title: "Hamilton, Ontario", + subtitle: "Bachelor of Applied Science in Computer Science", + start: datetime(year: 2022, month: 9, day: 1), + )[ + - Achieved a 3.8 GPA in first year + - Achieved an A+ in each computer science course + - Relevant skills: *Python*, *Haskell*, *Java*, *Shell Scripting*, *C*, *Elm*, + *GitHub*, *GitHub Actions* + ] +] + +#section( + "Experience", +)[ + #section-item( + "Tailered Sports", + subtitle: "Discord Bot Developer", + start: datetime(year: 2023, month: 5, day: 7), + )[ + - Made a discord bot in *Rust* for users to make fake "bets" on baseball games + - Accessed the FanDuel API to retrieve live betting odds + - Created a live feed of the game using the MLB API + - Used *PostgreSQL* to store user data and betting history + - Also allowed the bettors to track their bets and append them to a spreadsheet + nightly + ] +] + +#section( + "Projects", +)[ + #section-item( + "html-node", + href: "https://github.com/vidhanio/html-node", + post-title: "Rust, GitHub Actions", + subtitle: "A library allowing for a jsx-like syntax in Rust for making server-rendered websites.", + start: datetime(year: 2023, month: 7, day: 26), + )[ + - Implements industry-standard testing and continuous integration using *GitHub + Actions* + ] + + #section-item( + "MacEats", + href: "https://github.com/vidhanio/maceats", + post-title: "Rust, React, Next.js, Docker, Fly.io, Vercel", + subtitle: "A full-stack modern reimplementation of MacEats, the McMaster University campus dining site.", + start: datetime(year: 2022, month: 10, day: 17), + end: datetime(year: 2022, month: 10, day: 31), + )[ + - Created for the 2022 McMaster CSS Hacktober event + - Made a #underlink("https://crates.io/crates/maceats")[*Rust* library] to scrape + the original website and provide a programmatic interface to the data + - Made a *Rust* backend to serve the data for general use by anyone + - Made a *React* and *Next.js* frontend to access the modern site + - Received 100% in judge voting, placing second overall in the competition + ] + + #section-item( + "Checkpoint", + href: "https://github.com/vidhanio/checkpoint", + post-title: "Go, Docker, GitHub Actions, Google Cloud Platform", + subtitle: "A Discord verification bot for servers in the Peel District School Board.", + start: datetime(year: 2021, month: 10, day: 6), + end: datetime(year: 2022, month: 3, day: 1), + )[ + - Made to combat rampant spam bots which would join servers in our school + - Used *Go* for the Discord bot, which is deployed to *Google Compute Engine* + using *GitHub Actions* + - Was implemented in 15+ school club Discord servers, and received outstanding + testimonials from the club executives + ] +] + +#section( + "Technical Skills", +)[ + #section-item( + "Programming Languages", + )[ + Rust, Go, JavaScript, TypeScript, Python, Haskell, Java, Shell Scripting, HTML, + CSS, C++, C, Elm + ] + + #section-item("Frameworks/Databases")[ + React, Next.js, Node.js, Svelte, Tailwind CSS, SQL (PostgreSQL, SQLite, + Supabase), MongoDB, Sled + ] + + #section-item("Continuous Integration/Continuous Development")[ + Docker, GitHub, GitHub Actions, Kubernetes, Fly.io, Vercel, Google Cloud + Platform + ] +] + diff --git a/src/app.rs b/src/app.rs index 2d94c46..e62153a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,23 +1,16 @@ use std::{ - cmp::Reverse, - collections::HashMap, fmt::{self, Debug, Formatter}, sync::Arc, }; use axum::extract::FromRef; -use crate::{ - blog_post::{BlogPost, BlogPostMetadata}, - project::Project, - Error, -}; +use crate::{post::Post, Error}; /// The application. #[derive(Clone, FromRef)] pub struct App { - pub(crate) projects: Arc<[Project]>, - pub(crate) blog_posts: Arc>>, + pub(crate) blog_posts: Arc<[Post<'static>]>, } impl Debug for App { @@ -27,21 +20,10 @@ impl Debug for App { } impl App { - pub(crate) fn get_blog_post(&self, slug: &str) -> crate::Result<&BlogPost> { + pub(crate) fn get_blog_post(&self, slug: &str) -> crate::Result<&Post> { self.blog_posts - .get(slug) - .ok_or_else(|| Error::BlogPostNotFound(slug.into())) - } - - pub(crate) fn blog_posts_metadatas(&self) -> Box<[(&str, &BlogPostMetadata)]> { - let mut metadatas = self - .blog_posts .iter() - .map(|(&slug, blog_post)| (slug, &blog_post.metadata)) - .collect::>(); - - metadatas.sort_by_key(|(_, metadata)| Reverse(metadata.date)); - - metadatas.into_boxed_slice() + .find(|blog_post| blog_post.slug == slug) + .ok_or_else(|| Error::BlogPostNotFound(slug.into())) } } diff --git a/src/config.rs b/src/config.rs index 53b88e1..41a26ef 100644 --- a/src/config.rs +++ b/src/config.rs @@ -22,6 +22,10 @@ pub struct Config { impl Config { /// Load the configuration from the environment. + /// + /// # Errors + /// + /// Returns an error if the environment variables are not valid. pub fn from_env() -> Result { envy::from_env() } diff --git a/src/error.rs b/src/error.rs index 763c31f..b283f35 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,7 +7,7 @@ use axum::{ use maud::{html, Markup, Render}; use thiserror::Error; -use crate::layout::document; +use crate::layout::Document; /// An enum encompassing all possible errors from this crate. #[derive(Error, Debug)] @@ -33,8 +33,14 @@ pub enum Error { NoPostMetadata(String), /// A blog post's metadata could not be deserialized. - #[error("failed to deserialize metadata for blog post: `{0}`")] - DeserializePostMetadata(String, #[source] serde_yaml::Error), + #[error("failed to deserialize metadata for blog post: `{slug}`")] + DeserializePostMetadata { + /// The slug of the blog post. + slug: String, + + /// The source of the error. + source: serde_yaml::Error, + }, /// Unexpected markdown tag. #[error("unexpected markdown tag")] @@ -44,10 +50,6 @@ pub enum Error { #[error("blog post not found: `{0}`")] BlogPostNotFound(String), - /// The projects file could not be deserialized. - #[error("failed to deserialize projects file")] - DeserializeProjects(#[source] serde_yaml::Error), - /// Invalid font path. #[error( "invalid font extension (must be `woff` or `woff2`): `{}`", @@ -71,8 +73,9 @@ impl Error { | Self::TreeSitterHighlight(_) | Self::NoPostMetadata(_) | Self::UnexpectedMarkdownTag - | Self::DeserializePostMetadata(_, _) - | Self::DeserializeProjects(_) => StatusCode::INTERNAL_SERVER_ERROR, + | Self::DeserializePostMetadata { slug: _, source: _ } => { + StatusCode::INTERNAL_SERVER_ERROR + } Self::BlogPostNotFound(_) | Self::FontNotFound(_) => StatusCode::NOT_FOUND, Self::InvalidFontExtension(_) => StatusCode::BAD_REQUEST, } @@ -82,7 +85,7 @@ impl Error { impl Render for Error { fn render(&self) -> Markup { html! { - pre."bg-stone-200"."dark:bg-stone-800".overflow-x-auto."p-4" { + pre { code { (maud::display(self)); @@ -90,7 +93,7 @@ impl Render for Error { .skip(1) .enumerate() { - "\n" (" ".repeat(i * 2)) span."text-violet-500".font-bold { "└" } " " (e) + "\n" (" ".repeat(i * 2)) "└ " (e) } } } @@ -102,20 +105,21 @@ impl IntoResponse for Error { fn into_response(self) -> Response { let status_code = self.status_code(); - let body = document( - None, - &html! { - h1 { (status_code.as_u16()) " error" } - - (self) + ( + status_code, + Document { + path: None, + title: status_code.to_string().to_lowercase(), + subheader: None, + content: self.render(), }, - ); - - (status_code, body).into_response() + ) + .into_response() } } #[derive(Clone, Debug)] +#[allow(clippy::module_name_repetitions)] pub struct ErrorSourceIter<'a> { current: Option<&'a (dyn std::error::Error + 'static)>, } diff --git a/src/highlighter_configs.rs b/src/highlighter_configs.rs index 765d919..12095d4 100644 --- a/src/highlighter_configs.rs +++ b/src/highlighter_configs.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, - fmt::{self, Debug, Formatter}, + fmt::{self, Debug, Formatter, Write}, }; use tree_sitter_highlight::{Highlight, HighlightConfiguration, HighlightEvent, Highlighter}; @@ -117,9 +117,7 @@ impl HighlighterConfigurations { pub fn highlight(&self, language: &str, code: &str) -> crate::Result { let Some(config) = self.0.get(language) else { - let mut buf = String::new(); - encoded_with_line_starts(&mut buf, code, (true, true)); - return Ok(buf); + return Ok(html_escape::encode_text_minimal(code).into()); }; let mut highlighter = Highlighter::new(); @@ -130,14 +128,9 @@ impl HighlighterConfigurations { highlights.try_fold(String::new(), |mut buf, event| { match event? { HighlightEvent::Source { start, end } => { - encoded_with_line_starts( - &mut buf, - &code[start..end], - (start == 0, end == code.len()), - ); + html_escape::encode_text_minimal_to_string(&code[start..end], &mut buf); } HighlightEvent::HighlightStart(Highlight(idx)) => { - use std::fmt::Write; write!( buf, "", @@ -166,19 +159,3 @@ impl Debug for HighlighterConfigurations { .finish() } } - -fn encoded_with_line_starts(buf: &mut String, s: &str, (is_true_start, is_true_end): (bool, bool)) { - if is_true_start { - buf.push_str(""); - } - - let s = is_true_end - .then(|| s.strip_suffix('\n')) - .flatten() - .unwrap_or(s); - - let escaped = - html_escape::encode_text_minimal(s).replace('\n', "\n"); - - buf.push_str(&escaped); -} diff --git a/src/icon.rs b/src/icon.rs deleted file mode 100644 index eace17e..0000000 --- a/src/icon.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! icons sourced from heroicons. - -use maud::{html, Markup, Render}; - -pub struct Icon { - pub d: &'static str, - pub class: &'static str, -} - -impl Render for Icon { - fn render(&self) -> Markup { - html! { - svg.(self.class) xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" { - path fill-rule="evenodd" d=(self.d) clip-rule="evenodd"; - } - } - } -} - -pub const CHEVRON: Icon = Icon { - d: "M16.28 11.47a.75.75 0 010 1.06l-7.5 7.5a.75.75 0 01-1.06-1.06L14.69 12 7.72 5.03a.75.75 0 011.06-1.06l7.5 7.5z", - class: "h-8", -}; diff --git a/src/layout.rs b/src/layout.rs new file mode 100644 index 0000000..8aed7c2 --- /dev/null +++ b/src/layout.rs @@ -0,0 +1,98 @@ +use axum::response::{IntoResponse, Response}; +use maud::{html, Markup, Render, DOCTYPE}; + +const NAV_LINKS: &[(&str, &str)] = &[ + ("home", "/"), + ("contact", "/contact"), + ("resume", "/resume.pdf"), +]; + +pub struct Document { + pub path: Option, + pub title: String, + pub subheader: Option, + pub content: Markup, +} + +impl Render for Document { + fn render(&self) -> Markup { + html! { + (DOCTYPE) + html lang="en" { + head { + (seo(self.path.as_deref(), &self.title)) + link rel="stylesheet" href=(concat!("/static/styles.css?v=", env!("GIT_COMMIT_HASH"))); + } + + body { + nav { + @for (text, href) in NAV_LINKS { + a href=(href) { (text) } + } + } + + main { + header { + h1 { (self.title) } + @if let Some(subheader) = &self.subheader { + (subheader) + } + hr; + } + + (self.content) + } + + footer { + div { + a href="https://github.com/vidhanio/site" { "made with with rust" } " and <3 by vidhan." + } + + a href="/static/LICENSE.txt" { "site licensed under agpl-3.0." } + + div #ring { + @for (icon, action) in [('←', "prev"), ('🎲', "random"), ('→', "next")] { + a href={"https://ring.simonwu.dev/" (action) "/vidhan"} { (icon) } + } + } + } + } + } + } + } +} + +impl IntoResponse for Document { + fn into_response(self) -> Response { + self.render().into_response() + } +} + +fn seo(path: Option<&str>, title: &str) -> Markup { + let title = format!("{title} | vidhan.io"); + let url = path.map_or_else( + || "https://vidhan.io".into(), + |path| format!("https://vidhan.io/{path}"), + ); + + html! { + meta name="viewport" content="width=device-width, initial-scale=1.0"; + meta charset="utf-8"; + + title { (title) } + meta name="description" content="vidhan's home on the internet."; + meta name="theme-color" content="#1b1917"; + // link rel="icon" href="/static/favicon.ico"; + + meta name="og:title" content=(title); + meta name="og:description" content="vidhan's home on the internet."; + meta name="og:url" content=(url); + meta name="og:type" content="website"; + + meta name="twitter:card" content="summary_large_image"; + meta name="twitter:site" content="@vidhanio"; + meta name="twitter:creator" content="@vidhanio"; + meta name="twitter:title" content=(title); + meta name="twitter:description" content="vidhan's home on the internet."; + } +} diff --git a/src/layout/footer/link.rs b/src/layout/footer/link.rs deleted file mode 100644 index cd123af..0000000 --- a/src/layout/footer/link.rs +++ /dev/null @@ -1,19 +0,0 @@ -use maud::{html, Markup, Render}; - -pub struct FooterLink<'a> { - pub name: &'a str, - pub url: &'a str, - pub rel: Option<&'a str>, -} - -impl Render for FooterLink<'_> { - fn render(&self) -> Markup { - html! { - li { - a.text-sm.font-light."hover:text-stone-500".transition-colors href=(self.url) rel=[self.rel] { - (self.name) - } - } - } - } -} diff --git a/src/layout/footer/mod.rs b/src/layout/footer/mod.rs deleted file mode 100644 index cdd3f6b..0000000 --- a/src/layout/footer/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -mod link; -mod ring; - -use maud::{html, Markup}; - -pub use self::link::FooterLink; -use self::ring::ring; - -pub fn footer<'a>(links: impl IntoIterator>) -> Markup { - html! { - footer.text-center.w-full."p-8".flex.flex-col.items-center."gap-4"."border-t-2"."border-stone-300"."dark:border-stone-700" { - p.font-bold.text-lg."text-stone-400"."dark:text-stone-600" { - "made with <3 by vidhan." - } - ul.flex.flex-row.flex-wrap.items-center.justify-center."gap-4"."text-stone-400"."dark:text-stone-600" { - @for link in links { - (link) - } - } - a.text-xs.font-thin."text-stone-400"."dark:text-stone-600"."hover:text-stone-500" href="/static/LICENSE.txt" { - "site licensed under agpl-3.0." - } - - (ring()) - } - } -} diff --git a/src/layout/footer/ring.rs b/src/layout/footer/ring.rs deleted file mode 100644 index fc29c01..0000000 --- a/src/layout/footer/ring.rs +++ /dev/null @@ -1,39 +0,0 @@ -use maud::{html, Markup}; - -use crate::icon::Icon; - -#[rustfmt::skip] -const LEFT_ARROW: Icon = Icon { - d: "M20.25 12a.75.75 0 01-.75.75H6.31l5.47 5.47a.75.75 0 11-1.06 1.06l-6.75-6.75a.75.75 0 010-1.06l6.75-6.75a.75.75 0 111.06 1.06l-5.47 5.47H19.5a.75.75 0 01.75.75z", - class: "h-6", -}; - -#[rustfmt::skip] -const QUESTION_MARK: Icon = Icon { - d: "M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm11.378-3.917c-.89-.777-2.366-.777-3.255 0a.75.75 0 01-.988-1.129c1.454-1.272 3.776-1.272 5.23 0 1.513 1.324 1.513 3.518 0 4.842a3.75 3.75 0 01-.837.552c-.676.328-1.028.774-1.028 1.152v.75a.75.75 0 01-1.5 0v-.75c0-1.279 1.06-2.107 1.875-2.502.182-.088.351-.199.503-.331.83-.727.83-1.857 0-2.584zM12 18a.75.75 0 100-1.5.75.75 0 000 1.5z", - class: "h-6", -}; - -#[rustfmt::skip] -const RIGHT_ARROW: Icon = Icon { - d: "M3.75 12a.75.75 0 01.75-.75h13.19l-5.47-5.47a.75.75 0 011.06-1.06l6.75 6.75a.75.75 0 010 1.06l-6.75 6.75a.75.75 0 11-1.06-1.06l5.47-5.47H4.5a.75.75 0 01-.75-.75z", - class: "h-6", -}; - -const LINKS: [(Icon, &str); 3] = [ - (LEFT_ARROW, "https://ring.simonwu.dev/prev/vidhan"), - (QUESTION_MARK, "https://ring.simonwu.dev/random/vidhan"), - (RIGHT_ARROW, "https://ring.simonwu.dev/next/vidhan"), -]; - -pub fn ring() -> Markup { - html! { - div.flex.flex-row.flex-wrap.items-center.justify-center."gap-4"."text-stone-400"."dark:text-stone-600" { - @for (icon, href) in LINKS { - a."text-stone-400"."dark:text-stone-600"."hover:text-stone-500".transition-colors href=(href) { - (icon) - } - } - } - } -} diff --git a/src/layout/mod.rs b/src/layout/mod.rs deleted file mode 100644 index 160f0ad..0000000 --- a/src/layout/mod.rs +++ /dev/null @@ -1,79 +0,0 @@ -mod footer; -mod nav; -mod seo; - -use maud::{html, Markup, DOCTYPE}; - -use self::{ - footer::{footer, FooterLink}, - nav::{nav, NavLink}, - seo::seo, -}; - -const NAV_LINKS: [NavLink<'static>; 3] = [ - NavLink { - name: "home", - link: "/", - }, - NavLink { - name: "projects", - link: "/projects", - }, - NavLink { - name: "blog", - link: "/blog", - }, -]; - -const FOOTER_LINKS: [FooterLink<'static>; 6] = [ - FooterLink { - name: "source code", - url: "https://github.com/vidhanio/site", - rel: None, - }, - FooterLink { - name: "github", - url: "https://github.com/vidhanio", - rel: None, - }, - FooterLink { - name: "twitter", - url: "https://twitter.com/vidhanio", - rel: None, - }, - FooterLink { - name: "linkedin", - url: "https://linkedin.com/in/vidhanio", - rel: None, - }, - FooterLink { - name: "mastodon", - url: "https://fosstodon.org/@vidhan", - rel: Some("me"), - }, - FooterLink { - name: "email", - url: "mailto:me@vidhan.io", - rel: None, - }, -]; - -pub fn document(path: Option<&str>, content: &Markup) -> Markup { - html! { - (DOCTYPE) - html lang="en" { - head { - (seo(path)) - link rel="stylesheet" href=(concat!("/static/styles.css?v=", env!("GIT_COMMIT_HASH"))); - } - - body.font-sans.min-h-screen."px-[10%]"."lg:px-[25%]".flex.flex-col.items-center."bg-stone-100"."dark:bg-stone-900"."dark:text-stone-300"."text-stone-700" { - (nav(NAV_LINKS)) - main.w-full."py-8"."flex-1"."border-stone-600".flex.flex-col."gap-8" { - (content) - } - (footer(FOOTER_LINKS)) - } - } - } -} diff --git a/src/layout/nav/link.rs b/src/layout/nav/link.rs deleted file mode 100644 index af28b17..0000000 --- a/src/layout/nav/link.rs +++ /dev/null @@ -1,20 +0,0 @@ -use maud::{html, Markup, Render}; - -#[derive(Copy, Clone, Debug)] -pub struct NavLink<'a> { - pub name: &'a str, - pub link: &'a str, -} - -impl Render for NavLink<'_> { - fn render(&self) -> Markup { - html! { - li { - a.text-xl.font-extrabold."text-stone-500".transition-colors."hover:text-stone-700"."dark:hover:text-stone-300" - href=(self.link) { - (self.name) - } - } - } - } -} diff --git a/src/layout/nav/mod.rs b/src/layout/nav/mod.rs deleted file mode 100644 index 01196c3..0000000 --- a/src/layout/nav/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -mod link; - -use maud::{html, Markup}; - -pub use self::link::NavLink; - -pub fn nav<'a>(links: impl IntoIterator>) -> Markup { - html! { - nav.w-full."p-8"."border-b-2"."border-stone-300"."dark:border-stone-700" { - ul.flex.text-center."gap-4".flex-col."sm:flex-row"."sm:gap-16".justify-center { - @for link in links { - (link) - } - } - } - } -} diff --git a/src/layout/seo.rs b/src/layout/seo.rs deleted file mode 100644 index c84cd71..0000000 --- a/src/layout/seo.rs +++ /dev/null @@ -1,33 +0,0 @@ -use maud::{html, Markup}; - -pub fn seo(path: Option<&str>) -> Markup { - let title = path.map_or_else( - || "vidhan".into(), - |path| format!("vidhan{}", path.replace('/', " / ")), - ); - let url = path.map_or_else( - || "https://vidhan.io".into(), - |path| format!("https://vidhan.io/{path}"), - ); - - html! { - meta name="viewport" content="width=device-width, initial-scale=1.0"; - meta charset="utf-8"; - - title { (title) } - meta name="description" content="vidhan's home on the internet."; - meta name="theme-color" content="#1b1917"; - // link rel="icon" href="/static/favicon.ico"; - - meta name="og:title" content=(title); - meta name="og:description" content="vidhan's home on the internet."; - meta name="og:url" content=(url); - meta name="og:type" content="website"; - - meta name="twitter:card" content="summary_large_image"; - meta name="twitter:site" content="@vidhanio"; - meta name="twitter:creator" content="@vidhanio"; - meta name="twitter:title" content=(title); - meta name="twitter:description" content="vidhan's home on the internet."; - } -} diff --git a/src/lib.rs b/src/lib.rs index 85211e9..27c69f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,67 +1,58 @@ //! vidhan's site. -#![warn(clippy::cargo)] -#![warn(clippy::nursery)] -#![warn(clippy::pedantic)] -#![warn(missing_copy_implementations)] -#![warn(missing_debug_implementations)] -#![warn(missing_docs)] -#![allow(clippy::missing_errors_doc)] -#![allow(clippy::module_name_repetitions)] - mod app; -mod blog_post; mod config; mod error; mod highlighter_configs; -mod icon; mod layout; mod pages; -mod project; +mod post; mod r#static; -use std::{collections::HashMap, ffi::OsStr}; +use std::{cmp::Reverse, ffi::OsStr}; use axum::Router; -use blog_post::BlogPost; use highlighter_configs::HighlighterConfigurations; use include_dir::{include_dir, Dir}; +use self::post::Post; pub use self::{app::App, config::Config, error::Error}; type Result = std::result::Result; -const PROJECTS_YAML: &str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/content/projects.yml")); -static BLOG_POSTS_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/content/blog"); +static BLOG_POSTS_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/content/posts"); /// Serve the application. +/// +/// # Errors +/// +/// Returns an error if the application fails to serve. pub async fn serve(config: Config) -> crate::Result<()> { let highlighter_configs = HighlighterConfigurations::new()?; + let mut blog_posts = BLOG_POSTS_DIR + .files() + .filter_map(|file| { + let path = file.path(); + + let slug = if path.extension() == Some(OsStr::new("md")) { + path.file_stem()?.to_str() + } else { + None + }?; + + let markdown = file.contents_utf8()?; + + let blog_post = Post::new(&highlighter_configs, slug, markdown); + + Some(blog_post) + }) + .collect::>>()?; + + blog_posts.sort_by_key(|blog_post| Reverse(blog_post.metadata.date)); + let app = App { - projects: serde_yaml::from_str::>(PROJECTS_YAML) - .map_err(Error::DeserializeProjects)? - .into(), - blog_posts: BLOG_POSTS_DIR - .files() - .filter_map(|file| { - let path = file.path(); - - let slug = if path.extension() == Some(OsStr::new("md")) { - path.file_stem()?.to_str() - } else { - None - }?; - - let markdown = file.contents_utf8()?; - - let blog_post = BlogPost::new(&highlighter_configs, slug, markdown); - - Some(blog_post.map(|blog_post| (slug, blog_post))) - }) - .collect::>>()? - .into(), + blog_posts: blog_posts.into(), }; let tcp_listener = config.tcp_listener().await?; diff --git a/src/pages.rs b/src/pages.rs new file mode 100644 index 0000000..152e6b6 --- /dev/null +++ b/src/pages.rs @@ -0,0 +1,205 @@ +use axum::{ + extract::{Path, State}, + Router, +}; +use axum_extra::{ + headers::{ContentDisposition, ContentType}, + TypedHeader, +}; +use maud::{html, Render}; +use tracing::instrument; + +use crate::{layout::Document, App}; + +pub fn router() -> Router { + Router::new() + .route("/", axum::routing::get(home)) + .route("/contact", axum::routing::get(contact)) + .route("/resume.pdf", axum::routing::get(resume)) + .route("/post/:slug", axum::routing::get(post)) +} + +struct Link<'a> { + pub name: &'a str, + pub description: &'a str, + pub href: &'a str, +} + +impl Render for Link<'_> { + fn render(&self) -> maud::Markup { + html! { + a href=(self.href) { + b { + (self.name) + } + " - " + (self.description) + } + } + } +} + +const PROJECTS: &[Link<'static>] = &[ + Link { + name: "site", + description: "this website!", + href: "https://github.com/vidhanio/site", + }, + Link { + name: "html-node", + description: "an html macro for rust.", + href: "https://github.com/vidhanio/html-node", + }, + Link { + name: "fncli", + description: "an attribute macro to simplify writing simple clis in rust.", + href: "https://github.com/vidhanio/fncli", + }, + Link { + name: "diswordle", + description: "a discord bot to play wordle right in your discord server.", + href: "https://github.com/vidhanio/diswordle", + }, + Link { + name: "checkpoint", + description: + "a discord bot to provide easy verification for discord servers in my school board.", + href: "https://github.com/vidhanio/checkpoint", + }, + Link { + name: "serenity-commands", + description: "a library for creating/parsing serenity slash commands.", + href: "https://github.com/vidhanio/serenity-commands", + }, +]; + +#[instrument(level = "debug")] +pub async fn home(State(app): State) -> Document { + Document { + path: None, + title: "home".into(), + subheader: None, + content: html! { + section #intro { + p { + "hi, i'm vidhan. \ + welcome to my personal website!" + br; + br; + "i'm a software engineer and a computer science student at mcmaster. \ + my favourite programming language is rust, but i also enjoy writing python. \ + i also love basketball! \ + my favourite player is lebron james and i'm a huge fan of the los angeles lakers." + } + } + + section #blog-posts { + h2 { "blog posts" } + hr; + ul { + @for post in &*app.blog_posts { + li { + a href={"/post/" (post.slug)} { + time datetime=(post.metadata.date) { + (post.metadata.date_text()) + } + " - " + b { + (post.metadata.title) + } + } + } + } + } + } + + section #projects { + h2 { "projects" } + hr; + ul { + @for project in PROJECTS { + li { (project) } + } + } + } + }, + } +} + +#[instrument(level = "debug", err(Debug))] +pub async fn post(State(app): State, Path(slug): Path) -> crate::Result { + let blog_post = app.get_blog_post(&slug)?; + + Ok(Document { + path: Some(format!("/post/{slug}")), + title: blog_post.metadata.title.clone(), + subheader: Some(html! { + time datetime=(blog_post.metadata.date) { + (blog_post.metadata.date_text()) + } + p { (blog_post.metadata.description) } + }), + content: html! { + article { + (blog_post) + } + }, + }) +} + +const CONTACTS: &[Link<'static>] = &[ + Link { + name: "github", + description: "vidhanio", + href: "https://github.com/vidhanio", + }, + Link { + name: "twitter", + description: "@vidhanio", + href: "https://twitter.com/vidhanio", + }, + Link { + name: "linkedin", + description: "/in/vidhanio", + href: "https://www.linkedin.com/in/vidhanio", + }, + Link { + name: "email", + description: "me@vidhan.io", + href: "mailto:me@vidhan.io", + }, +]; + +#[instrument(level = "debug")] +pub async fn contact() -> Document { + Document { + path: Some("/contact".into()), + title: "contact".into(), + subheader: None, + content: html! { + section #contact { + ul { + @for contact in CONTACTS { + li { (contact) } + } + } + } + + }, + } +} + +#[instrument(level = "debug")] +async fn resume() -> ( + TypedHeader, + TypedHeader, + &'static [u8], +) { + const RESUME_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/resume.pdf")); + + ( + TypedHeader(ContentDisposition::inline()), + TypedHeader(mime::APPLICATION_PDF.into()), + RESUME_BYTES, + ) +} diff --git a/src/pages/blog/components.rs b/src/pages/blog/components.rs deleted file mode 100644 index 3d64fb4..0000000 --- a/src/pages/blog/components.rs +++ /dev/null @@ -1,39 +0,0 @@ -use maud::{html, Markup, Render}; - -use crate::{blog_post::BlogPostMetadata, icon::CHEVRON}; - -pub struct Link<'a> { - slug: &'a str, - metadata: &'a BlogPostMetadata, -} - -impl<'a> Link<'a> { - pub const fn new(slug: &'a str, metadata: &'a BlogPostMetadata) -> Self { - Self { slug, metadata } - } -} - -impl Render for Link<'_> { - fn render(&self) -> Markup { - html! { - li { - a.group.w-full.flex.flex-row.justify-between."bg-stone-200"."dark:bg-stone-800" href={"/blog/" (self.slug)} { - div."p-4" { - h2.text-lg."text-stone-700"."dark:text-stone-300" { - (&self.metadata.title) - } - time."text-stone-600"."dark:text-stone-400" datetime=(self.metadata.date) { - (self.metadata.date_text()) - } - p."text-stone-600"."dark:text-stone-400" { - (&self.metadata.description) - } - } - div.grid.place-items-center."p-4"."group-hover:translate-x-1".transition-transform."fill-stone-600"."dark:fill-stone-400" { - (CHEVRON) - } - } - } - } - } -} diff --git a/src/pages/blog/mod.rs b/src/pages/blog/mod.rs deleted file mode 100644 index 231c254..0000000 --- a/src/pages/blog/mod.rs +++ /dev/null @@ -1,68 +0,0 @@ -mod components; - -use axum::{ - extract::{Path, State}, - Router, -}; -use maud::{html, Markup}; -use tracing::instrument; - -use self::components::Link; -use crate::{icon::Icon, layout::document, App}; - -pub fn router() -> Router { - Router::new() - .route("/", axum::routing::get(get)) - .route("/:slug", axum::routing::get(get_post)) -} - -#[instrument(level = "debug")] -pub async fn get(State(app): State) -> Markup { - document( - Some("/blog"), - &html! { - h1 { "blog" } - ul.flex.flex-col."gap-4" { - @for (slug, metadata) in &*app.blog_posts_metadatas() { - (Link::new(slug, metadata)) - } - } - }, - ) -} - -#[instrument(level = "debug", err(Debug))] -pub async fn get_post(State(app): State, Path(slug): Path) -> crate::Result { - const CALENDAR_ICON: Icon = Icon { - d: "M6.75 2.25A.75.75 0 017.5 3v1.5h9V3A.75.75 0 0118 3v1.5h.75a3 3 0 013 3v11.25a3 3 0 01-3 3H5.25a3 3 0 01-3-3V7.5a3 3 0 013-3H6V3a.75.75 0 01.75-.75zm13.5 9a1.5 1.5 0 00-1.5-1.5H5.25a1.5 1.5 0 00-1.5 1.5v7.5a1.5 1.5 0 001.5 1.5h13.5a1.5 1.5 0 001.5-1.5v-7.5z", - class: "h-4", - }; - - let blog_post = app.get_blog_post(&slug)?; - - Ok(document( - Some(&format!("/blog/{slug}")), - &html! { - header { - h1 { (blog_post.metadata.title) } - time.flex.flex-row."gap-2".items-center."text-stone-600"."dark:text-stone-400"."mt-2" datetime=(blog_post.metadata.date) { - (CALENDAR_ICON) - (blog_post.metadata.date_text()) - } - p.text-lg."text-stone-500"."mt-4" { - (blog_post.metadata.description) - } - } - - hr."w-3/4"."border-stone-500"; - - article.prose.prose-slate."dark:prose-invert" - ."prose-pre:bg-stone-200"."dark:prose-pre:bg-stone-800"."prose-pre:rounded-none" - ."prose-code:font-normal"."prose-code:bg-stone-200"."dark:prose-code:bg-stone-800"."prose-code:text-stone-800"."dark:prose-code:text-stone-200" - ."prose-code:before:content-none"."prose-code:after:content-none" - .max-w-none { - (blog_post) - } - }, - )) -} diff --git a/src/pages/mod.rs b/src/pages/mod.rs deleted file mode 100644 index 5f8aee8..0000000 --- a/src/pages/mod.rs +++ /dev/null @@ -1,36 +0,0 @@ -mod blog; -mod projects; - -use axum::Router; -use maud::{html, Markup}; -use tracing::instrument; - -use crate::{layout::document, App}; - -pub fn router() -> Router { - Router::new() - .route("/", axum::routing::get(get)) - .route("/projects", axum::routing::get(projects::get)) - .nest("/blog", blog::router()) -} - -#[instrument(level = "debug")] -pub async fn get() -> Markup { - document( - None, - &html! { - h1 { "hey, i'm vidhan!" } - p."text-2xl" { - "i'm a software engineer, fullstack developer, discord \ - bot developer, and a cs student at mcmaster. i'm currently working \ - on a ton of cool projects, which you can find on " - a.underline href="https://github.com/vidhanio" { "my github" } "." - } - div.grid.place-items-center."flex-1" { - a.font-bold."text-4xl".font-mono href="https://github.com/vidhanio/vidhanio/releases/latest/download/resume.pdf" { - "[resume.pdf]" - } - } - }, - ) -} diff --git a/src/pages/projects.rs b/src/pages/projects.rs deleted file mode 100644 index 3bdeb13..0000000 --- a/src/pages/projects.rs +++ /dev/null @@ -1,20 +0,0 @@ -use axum::extract::State; -use maud::{html, Markup}; -use tracing::instrument; - -use crate::{layout::document, App}; - -#[instrument(level = "debug")] -pub async fn get(State(app): State) -> Markup { - document( - Some("/projects"), - &html! { - h1 { "projects" } - ul.flex.flex-col."gap-4" { - @for project in &*app.projects { - (project.link()) - } - } - }, - ) -} diff --git a/src/blog_post.rs b/src/post.rs similarity index 84% rename from src/blog_post.rs rename to src/post.rs index b4447a4..3c80c1d 100644 --- a/src/blog_post.rs +++ b/src/post.rs @@ -7,16 +7,16 @@ use crate::{highlighter_configs::HighlighterConfigurations, Error}; #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "kebab-case")] -pub struct BlogPostMetadata { +pub struct Metadata { pub title: String, pub date: Date, pub description: String, } -impl BlogPostMetadata { +impl Metadata { pub fn date_text(&self) -> String { static FORMAT_DESCRIPTION: &[FormatItem<'static>] = - format_description!("[month repr:long] [day padding:none], [year]"); + format_description!("[year]/[month]/[day]"); self.date .format(FORMAT_DESCRIPTION) @@ -26,12 +26,13 @@ impl BlogPostMetadata { } #[derive(Clone, Debug)] -pub struct BlogPost<'a> { - pub metadata: BlogPostMetadata, +pub struct Post<'a> { + pub slug: &'a str, + pub metadata: Metadata, pub events: Box<[Event<'a>]>, } -impl<'a> BlogPost<'a> { +impl<'a> Post<'a> { pub fn new( highlighter_configs: &HighlighterConfigurations, slug: &'a str, @@ -64,9 +65,7 @@ impl<'a> BlogPost<'a> { let event = Event::Html( html! { pre { - code.highlighted-code { - (PreEscaped(highlighted_code)) - } + code.highlighted { (PreEscaped(highlighted_code)) } } } .into_string() @@ -85,8 +84,12 @@ impl<'a> BlogPost<'a> { }) .collect::>()?; - let parsed_metadata = serde_yaml::from_str(&metadata_string) - .map_err(|e| Error::DeserializePostMetadata(slug.into(), e))?; + let parsed_metadata = serde_yaml::from_str(&metadata_string).map_err(|e| { + Error::DeserializePostMetadata { + slug: slug.into(), + source: e, + } + })?; metadata = Some(parsed_metadata); } @@ -97,11 +100,15 @@ impl<'a> BlogPost<'a> { let metadata = metadata.ok_or_else(|| Error::NoPostMetadata(slug.into()))?; let events = events.into(); - Ok(Self { metadata, events }) + Ok(Self { + slug, + metadata, + events, + }) } } -impl Render for BlogPost<'_> { +impl Render for Post<'_> { fn render(&self) -> Markup { let mut buf = String::new(); pulldown_cmark::html::push_html(&mut buf, self.events.iter().cloned()); diff --git a/src/project.rs b/src/project.rs deleted file mode 100644 index 49ed3d8..0000000 --- a/src/project.rs +++ /dev/null @@ -1,34 +0,0 @@ -use maud::{html, Markup}; -use serde::Deserialize; - -use crate::icon::CHEVRON; - -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct Project { - pub name: String, - pub description: String, - pub href: String, -} - -impl Project { - pub fn link(&self) -> Markup { - html! { - li { - a.group.w-full.flex.flex-row.justify-between."bg-stone-200"."dark:bg-stone-800" href=(self.href) { - div."p-4" { - h2.text-lg."text-stone-700"."dark:text-stone-300" { - (self.name) - } - p."text-stone-600"."dark:text-stone-400" { - (self.description) - } - } - div.grid.place-items-center."p-4"."group-hover:translate-x-1".transition-transform."fill-stone-600"."dark:fill-stone-400" { - (CHEVRON) - } - } - } - } - } -} diff --git a/src/static.rs b/src/static.rs index 696236a..84be397 100644 --- a/src/static.rs +++ b/src/static.rs @@ -2,7 +2,7 @@ use std::{ffi::OsStr, future, path::PathBuf, time::Duration}; use axum::{extract::Path, Router}; use axum_extra::{ - headers::{CacheControl, ContentType}, + headers::{CacheControl, ContentDisposition, ContentType}, response::Css, TypedHeader, }; @@ -38,13 +38,23 @@ macro_rules! static_path { // } #[instrument(level = "debug")] -async fn license() -> &'static [u8] { - include_bytes!(static_path!("LICENSE.txt")) +async fn license() -> ( + TypedHeader, + TypedHeader, + &'static [u8], +) { + const LICENSE: &[u8] = include_bytes!(static_path!("LICENSE.txt")); + + ( + TypedHeader(ContentDisposition::inline()), + TypedHeader(mime::TEXT_PLAIN.into()), + LICENSE, + ) } #[instrument(level = "debug")] async fn styles() -> Css<&'static str> { - const STYLES_CSS: &str = include_str!(concat!(env!("OUT_DIR"), "/styles.css")); + const STYLES_CSS: &str = include_str!(static_path!("styles.css")); Css(STYLES_CSS) } diff --git a/static/fonts/Inter-Black.woff2 b/static/fonts/Inter-Black.woff2 deleted file mode 100644 index 18b35db..0000000 Binary files a/static/fonts/Inter-Black.woff2 and /dev/null differ diff --git a/static/fonts/Inter-BlackItalic.woff2 b/static/fonts/Inter-BlackItalic.woff2 deleted file mode 100644 index 02c9d8e..0000000 Binary files a/static/fonts/Inter-BlackItalic.woff2 and /dev/null differ diff --git a/static/fonts/Inter-Bold.woff2 b/static/fonts/Inter-Bold.woff2 deleted file mode 100644 index 0f1b157..0000000 Binary files a/static/fonts/Inter-Bold.woff2 and /dev/null differ diff --git a/static/fonts/Inter-BoldItalic.woff2 b/static/fonts/Inter-BoldItalic.woff2 deleted file mode 100644 index bc50f24..0000000 Binary files a/static/fonts/Inter-BoldItalic.woff2 and /dev/null differ diff --git a/static/fonts/Inter-ExtraBold.woff2 b/static/fonts/Inter-ExtraBold.woff2 deleted file mode 100644 index b113368..0000000 Binary files a/static/fonts/Inter-ExtraBold.woff2 and /dev/null differ diff --git a/static/fonts/Inter-ExtraBoldItalic.woff2 b/static/fonts/Inter-ExtraBoldItalic.woff2 deleted file mode 100644 index a5b76ca..0000000 Binary files a/static/fonts/Inter-ExtraBoldItalic.woff2 and /dev/null differ diff --git a/static/fonts/Inter-ExtraLight.woff2 b/static/fonts/Inter-ExtraLight.woff2 deleted file mode 100644 index 1d77ae8..0000000 Binary files a/static/fonts/Inter-ExtraLight.woff2 and /dev/null differ diff --git a/static/fonts/Inter-ExtraLightItalic.woff2 b/static/fonts/Inter-ExtraLightItalic.woff2 deleted file mode 100644 index 8c68492..0000000 Binary files a/static/fonts/Inter-ExtraLightItalic.woff2 and /dev/null differ diff --git a/static/fonts/Inter-Italic.woff2 b/static/fonts/Inter-Italic.woff2 deleted file mode 100644 index 4c24ce2..0000000 Binary files a/static/fonts/Inter-Italic.woff2 and /dev/null differ diff --git a/static/fonts/Inter-Light.woff2 b/static/fonts/Inter-Light.woff2 deleted file mode 100644 index dbe6143..0000000 Binary files a/static/fonts/Inter-Light.woff2 and /dev/null differ diff --git a/static/fonts/Inter-LightItalic.woff2 b/static/fonts/Inter-LightItalic.woff2 deleted file mode 100644 index a40d042..0000000 Binary files a/static/fonts/Inter-LightItalic.woff2 and /dev/null differ diff --git a/static/fonts/Inter-Medium.woff2 b/static/fonts/Inter-Medium.woff2 deleted file mode 100644 index 0fd2ee7..0000000 Binary files a/static/fonts/Inter-Medium.woff2 and /dev/null differ diff --git a/static/fonts/Inter-MediumItalic.woff2 b/static/fonts/Inter-MediumItalic.woff2 deleted file mode 100644 index 9676715..0000000 Binary files a/static/fonts/Inter-MediumItalic.woff2 and /dev/null differ diff --git a/static/fonts/Inter-Regular.woff2 b/static/fonts/Inter-Regular.woff2 deleted file mode 100644 index b8699af..0000000 Binary files a/static/fonts/Inter-Regular.woff2 and /dev/null differ diff --git a/static/fonts/Inter-SemiBold.woff2 b/static/fonts/Inter-SemiBold.woff2 deleted file mode 100644 index 95c48b1..0000000 Binary files a/static/fonts/Inter-SemiBold.woff2 and /dev/null differ diff --git a/static/fonts/Inter-SemiBoldItalic.woff2 b/static/fonts/Inter-SemiBoldItalic.woff2 deleted file mode 100644 index ddfe19e..0000000 Binary files a/static/fonts/Inter-SemiBoldItalic.woff2 and /dev/null differ diff --git a/static/fonts/Inter-Thin.woff2 b/static/fonts/Inter-Thin.woff2 deleted file mode 100644 index 0790960..0000000 Binary files a/static/fonts/Inter-Thin.woff2 and /dev/null differ diff --git a/static/fonts/Inter-ThinItalic.woff2 b/static/fonts/Inter-ThinItalic.woff2 deleted file mode 100644 index a7bf213..0000000 Binary files a/static/fonts/Inter-ThinItalic.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-Black.woff2 b/static/fonts/InterDisplay-Black.woff2 deleted file mode 100644 index 8138123..0000000 Binary files a/static/fonts/InterDisplay-Black.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-BlackItalic.woff2 b/static/fonts/InterDisplay-BlackItalic.woff2 deleted file mode 100644 index 735ba21..0000000 Binary files a/static/fonts/InterDisplay-BlackItalic.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-Bold.woff2 b/static/fonts/InterDisplay-Bold.woff2 deleted file mode 100644 index 11c6719..0000000 Binary files a/static/fonts/InterDisplay-Bold.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-BoldItalic.woff2 b/static/fonts/InterDisplay-BoldItalic.woff2 deleted file mode 100644 index 5b6a1fb..0000000 Binary files a/static/fonts/InterDisplay-BoldItalic.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-ExtraBold.woff2 b/static/fonts/InterDisplay-ExtraBold.woff2 deleted file mode 100644 index 9058e98..0000000 Binary files a/static/fonts/InterDisplay-ExtraBold.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-ExtraBoldItalic.woff2 b/static/fonts/InterDisplay-ExtraBoldItalic.woff2 deleted file mode 100644 index 4cd61c0..0000000 Binary files a/static/fonts/InterDisplay-ExtraBoldItalic.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-ExtraLight.woff2 b/static/fonts/InterDisplay-ExtraLight.woff2 deleted file mode 100644 index 8621b29..0000000 Binary files a/static/fonts/InterDisplay-ExtraLight.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-ExtraLightItalic.woff2 b/static/fonts/InterDisplay-ExtraLightItalic.woff2 deleted file mode 100644 index 689c8d9..0000000 Binary files a/static/fonts/InterDisplay-ExtraLightItalic.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-Italic.woff2 b/static/fonts/InterDisplay-Italic.woff2 deleted file mode 100644 index 11f20bc..0000000 Binary files a/static/fonts/InterDisplay-Italic.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-Light.woff2 b/static/fonts/InterDisplay-Light.woff2 deleted file mode 100644 index 446301c..0000000 Binary files a/static/fonts/InterDisplay-Light.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-LightItalic.woff2 b/static/fonts/InterDisplay-LightItalic.woff2 deleted file mode 100644 index f688196..0000000 Binary files a/static/fonts/InterDisplay-LightItalic.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-Medium.woff2 b/static/fonts/InterDisplay-Medium.woff2 deleted file mode 100644 index 29160b2..0000000 Binary files a/static/fonts/InterDisplay-Medium.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-MediumItalic.woff2 b/static/fonts/InterDisplay-MediumItalic.woff2 deleted file mode 100644 index ef1bcbe..0000000 Binary files a/static/fonts/InterDisplay-MediumItalic.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-Regular.woff2 b/static/fonts/InterDisplay-Regular.woff2 deleted file mode 100644 index a6c04f6..0000000 Binary files a/static/fonts/InterDisplay-Regular.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-SemiBold.woff2 b/static/fonts/InterDisplay-SemiBold.woff2 deleted file mode 100644 index 2b4db23..0000000 Binary files a/static/fonts/InterDisplay-SemiBold.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-SemiBoldItalic.woff2 b/static/fonts/InterDisplay-SemiBoldItalic.woff2 deleted file mode 100644 index 59091db..0000000 Binary files a/static/fonts/InterDisplay-SemiBoldItalic.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-Thin.woff2 b/static/fonts/InterDisplay-Thin.woff2 deleted file mode 100644 index dc0b948..0000000 Binary files a/static/fonts/InterDisplay-Thin.woff2 and /dev/null differ diff --git a/static/fonts/InterDisplay-ThinItalic.woff2 b/static/fonts/InterDisplay-ThinItalic.woff2 deleted file mode 100644 index 96439c0..0000000 Binary files a/static/fonts/InterDisplay-ThinItalic.woff2 and /dev/null differ diff --git a/static/fonts/InterVariable-Italic.woff2 b/static/fonts/InterVariable-Italic.woff2 deleted file mode 100644 index f22ec25..0000000 Binary files a/static/fonts/InterVariable-Italic.woff2 and /dev/null differ diff --git a/static/fonts/InterVariable.woff2 b/static/fonts/InterVariable.woff2 deleted file mode 100644 index 22a12b0..0000000 Binary files a/static/fonts/InterVariable.woff2 and /dev/null differ diff --git a/static/styles.css b/static/styles.css new file mode 100644 index 0000000..fc5b595 --- /dev/null +++ b/static/styles.css @@ -0,0 +1,320 @@ +@font-face { + font-family: "Berkeley Mono"; + font-weight: 400; + font-style: normal; + src: url("/static/fonts/BerkeleyMono-Regular.woff2") format("woff2"), + url("/static/fonts/BerkeleyMono-Regular.woff") format("woff"); +} + +@font-face { + font-family: "Berkeley Mono"; + font-weight: 700; + font-style: normal; + src: url("/static/fonts/BerkeleyMono-Bold.woff2") format("woff2"), + url("/static/fonts/BerkeleyMono-Bold.woff") format("woff"); +} + +@font-face { + font-family: "Berkeley Mono"; + font-weight: 400; + font-style: italic; + src: url("/static/fonts/BerkeleyMono-Italic.woff2") format("woff2"), + url("/static/fonts/BerkeleyMono-Italic.woff") format("woff"); +} + +@font-face { + font-family: "Berkeley Mono"; + font-weight: 700; + font-style: italic; + src: url("/static/fonts/BerkeleyMono-BoldItalic.woff2") format("woff2"), + url("/static/fonts/BerkeleyMono-BoldItalic.woff") format("woff"); +} + +:root { + --light-color: #efefef; + --dark-color: #101010; +} + +:root { + --text-color: var(--dark-color); + --background-color: var(--light-color); + + --code-text-color: #8a9199; + --code-background-color: #e7e7e7; +} + +@media (prefers-color-scheme: dark) { + :root { + --text-color: var(--light-color); + --background-color: var(--dark-color); + + --code-text-color: #bfbdb6; + --code-background-color: #181818; + } +} + +:root { + background-color: var(--background-color); + color: var(--text-color); + + font-family: "Berkeley Mono", monospace; +} + +* { + box-sizing: border-box; +} + +body { + min-height: 100vh; + + margin: 0; + padding: 0 10%; + + display: flex; + flex-direction: column; + gap: 1rem; +} + +@media only screen and (min-width: 768px) { + body { + padding: 0 25%; + } +} + +main { + flex: 1; +} + +nav { + padding: 2rem 0; + + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 2rem; +} + +nav a, +footer a, +section ul > li > a { + text-decoration: inherit; +} + +nav a:hover, +footer a:hover, +section ul > li > a:hover { + text-decoration: underline; +} + +footer { + padding: 2rem 0; + + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + + text-align: center; +} + +footer > div#ring { + font-size: 2rem; + display: flex; + gap: 0.75rem; +} + +hr { + border: 0.0625rem solid var(--text-color); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: 1.125rem; +} + +a { + color: inherit; +} + +ul { + list-style: none; + padding-left: 0; + margin: 0; +} + +:not(pre) > code { + padding: 0 0.125rem; + + background-color: var(--code-background-color); +} + +pre { + padding: 1rem; + + background-color: var(--code-background-color); + overflow-x: auto; +} + +code { + font-family: "Berkeley Mono", monospace; +} + +code { + color: var(--code-text-color); +} + +/* syntax highlighting */ + +code.highlighted .comment { + color: #787b8099; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .comment { + color: #acb6bf8c; + } +} + +code.highlighted .constant { + color: #a37acc; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .constant { + color: #d2a6ff; + } +} + +code.highlighted .error { + color: #e65050; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .error { + color: #d95757; + } +} + +code.highlighted .function { + color: #f2ae49; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .function { + color: #ffb454; + } +} + +code.highlighted .keyword, +code.highlighted .builtin { + color: #fa8d3e; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .keyword, + code.highlighted .builtin { + color: #ff8f40; + } +} + +code.highlighted .markup { + color: #f07171; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .markup { + color: #f07178; + } +} + +code.highlighted .markup.bold { + font-weight: bold; +} + +code.highlighted .markup.italic { + font-style: italic; +} + +code.highlighted .module { + color: #399ee6; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .module { + color: #399ee6; + } +} + +code.highlighted .number { + color: #a37acc; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .number { + color: #d2a6ff; + } +} + +code.highlighted .operator { + color: #ed9366; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .operator { + color: #f29668; + } +} + +code.highlighted .string { + color: #86b300; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .string { + color: #aad94c; + } +} + +code.highlighted .string.regexp { + color: #4cbf99; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .string.regexp { + color: #95e6cb; + } +} + +code.highlighted .string.special { + color: #e6ba7e; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .string.special { + color: #e6b673; + } +} + +code.highlighted .tag { + color: #55b4d4; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .tag { + color: #39bae6; + } +} + +code.highlighted .type { + color: #399ee6; +} + +@media (prefers-color-scheme: dark) { + code.highlighted .type { + color: #59c2ff; + } +} diff --git a/static/styles.input.css b/static/styles.input.css deleted file mode 100644 index f1b3612..0000000 --- a/static/styles.input.css +++ /dev/null @@ -1,412 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - h1 { - @apply text-4xl font-extrabold text-stone-500; - } - - h1, - h2, - h3, - h4, - h5, - h6 { - @apply [font-family:InterDisplay]; - } - - @font-face { - font-family: InterVariable; - font-style: normal; - font-weight: 100 900; - font-display: swap; - src: url("/static/fonts/InterVariable.woff2") format("woff2"); - } - @font-face { - font-family: InterVariable; - font-style: italic; - font-weight: 100 900; - font-display: swap; - src: url("/static/fonts/InterVariable-Italic.woff2") format("woff2"); - } - - /* static fonts */ - @font-face { - font-family: "Inter"; - font-style: normal; - font-weight: 100; - font-display: swap; - src: url("/static/fonts/Inter-Thin.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: italic; - font-weight: 100; - font-display: swap; - src: url("/static/fonts/Inter-ThinItalic.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: normal; - font-weight: 200; - font-display: swap; - src: url("/static/fonts/Inter-ExtraLight.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: italic; - font-weight: 200; - font-display: swap; - src: url("/static/fonts/Inter-ExtraLightItalic.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: normal; - font-weight: 300; - font-display: swap; - src: url("/static/fonts/Inter-Light.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: italic; - font-weight: 300; - font-display: swap; - src: url("/static/fonts/Inter-LightItalic.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/static/fonts/Inter-Regular.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: italic; - font-weight: 400; - font-display: swap; - src: url("/static/fonts/Inter-Italic.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: normal; - font-weight: 500; - font-display: swap; - src: url("/static/fonts/Inter-Medium.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: italic; - font-weight: 500; - font-display: swap; - src: url("/static/fonts/Inter-MediumItalic.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: normal; - font-weight: 600; - font-display: swap; - src: url("/static/fonts/Inter-SemiBold.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: italic; - font-weight: 600; - font-display: swap; - src: url("/static/fonts/Inter-SemiBoldItalic.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: normal; - font-weight: 700; - font-display: swap; - src: url("/static/fonts/Inter-Bold.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: italic; - font-weight: 700; - font-display: swap; - src: url("/static/fonts/Inter-BoldItalic.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: normal; - font-weight: 800; - font-display: swap; - src: url("/static/fonts/Inter-ExtraBold.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: italic; - font-weight: 800; - font-display: swap; - src: url("/static/fonts/Inter-ExtraBoldItalic.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: normal; - font-weight: 900; - font-display: swap; - src: url("/static/fonts/Inter-Black.woff2") format("woff2"); - } - @font-face { - font-family: "Inter"; - font-style: italic; - font-weight: 900; - font-display: swap; - src: url("/static/fonts/Inter-BlackItalic.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: normal; - font-weight: 100; - font-display: swap; - src: url("/static/fonts/InterDisplay-Thin.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: italic; - font-weight: 100; - font-display: swap; - src: url("/static/fonts/InterDisplay-ThinItalic.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: normal; - font-weight: 200; - font-display: swap; - src: url("/static/fonts/InterDisplay-ExtraLight.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: italic; - font-weight: 200; - font-display: swap; - src: url("/static/fonts/InterDisplay-ExtraLightItalic.woff2") - format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: normal; - font-weight: 300; - font-display: swap; - src: url("/static/fonts/InterDisplay-Light.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: italic; - font-weight: 300; - font-display: swap; - src: url("/static/fonts/InterDisplay-LightItalic.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/static/fonts/InterDisplay-Regular.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: italic; - font-weight: 400; - font-display: swap; - src: url("/static/fonts/InterDisplay-Italic.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: normal; - font-weight: 500; - font-display: swap; - src: url("/static/fonts/InterDisplay-Medium.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: italic; - font-weight: 500; - font-display: swap; - src: url("/static/fonts/InterDisplay-MediumItalic.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: normal; - font-weight: 600; - font-display: swap; - src: url("/static/fonts/InterDisplay-SemiBold.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: italic; - font-weight: 600; - font-display: swap; - src: url("/static/fonts/InterDisplay-SemiBoldItalic.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: normal; - font-weight: 700; - font-display: swap; - src: url("/static/fonts/InterDisplay-Bold.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: italic; - font-weight: 700; - font-display: swap; - src: url("/static/fonts/InterDisplay-BoldItalic.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: normal; - font-weight: 800; - font-display: swap; - src: url("/static/fonts/InterDisplay-ExtraBold.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: italic; - font-weight: 800; - font-display: swap; - src: url("/static/fonts/InterDisplay-ExtraBoldItalic.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: normal; - font-weight: 900; - font-display: swap; - src: url("/static/fonts/InterDisplay-Black.woff2") format("woff2"); - } - @font-face { - font-family: "InterDisplay"; - font-style: italic; - font-weight: 900; - font-display: swap; - src: url("/static/fonts/InterDisplay-BlackItalic.woff2") format("woff2"); - } - - @font-face { - font-family: "Berkeley Mono"; - font-weight: 400; - font-style: normal; - src: url("/static/fonts/BerkeleyMono-Regular.woff2") format("woff2"), - url("/static/fonts/BerkeleyMono-Regular.woff") format("woff"); - } - - @font-face { - font-family: "Berkeley Mono"; - font-weight: 700; - font-style: normal; - src: url("/static/fonts/BerkeleyMono-Bold.woff2") format("woff2"), - url("/static/fonts/BerkeleyMono-Bold.woff") format("woff"); - } - - @font-face { - font-family: "Berkeley Mono"; - font-weight: 400; - font-style: italic; - src: url("/static/fonts/BerkeleyMono-Italic.woff2") format("woff2"), - url("/static/fonts/BerkeleyMono-Italic.woff") format("woff"); - } - - @font-face { - font-family: "Berkeley Mono"; - font-weight: 700; - font-style: italic; - src: url("/static/fonts/BerkeleyMono-BoldItalic.woff2") format("woff2"), - url("/static/fonts/BerkeleyMono-BoldItalic.woff") format("woff"); - } -} - -@layer base { - /* basic code block styles */ - - :not(pre) > code { - @apply px-1; - } - - code.highlighted-code { - counter-reset: linenumber; - } - - code.highlighted-code span.line-start { - @apply inline-block text-stone-400 dark:text-stone-600 text-right w-[2ch] pr-4 mr-4 border-r-2 border-solid border-stone-300 dark:border-stone-700 box-content; - counter-increment: linenumber; - } - - code.highlighted-code span.line-start:before { - @apply content-[counter(linenumber)]; - } - - /* syntax highlighting */ - - code.highlighted-code .variable { - @apply text-violet-700 dark:text-violet-300; - } - - code.highlighted-code .variable.builtin { - @apply text-violet-600 dark:text-violet-400; - } - - code.highlighted-code .punctuation, - code.highlighted-code .operator { - @apply text-stone-600 dark:text-stone-400; - } - - code.highlighted-code .punctuation.delimiter { - @apply text-stone-500; - } - - code.highlighted-code .comment { - @apply text-stone-400 dark:text-stone-600; - } - - code.highlighted-code .constructor { - @apply text-rose-600 dark:text-rose-400; - } - - code.highlighted-code .type { - @apply text-blue-600 dark:text-blue-400; - } - - code.highlighted-code .attribute { - @apply text-sky-700 dark:text-sky-300; - } - - code.highlighted-code .constant { - @apply text-cyan-600 dark:text-cyan-400; - } - - code.highlighted-code .constant.builtin { - @apply text-purple-600 dark:text-purple-400; - } - - code.highlighted-code .number { - @apply text-purple-600 dark:text-purple-400; - } - - code.highlighted-code .keyword { - @apply text-orange-600 dark:text-orange-400; - } - - code.highlighted-code .function { - @apply text-amber-600 dark:text-amber-400; - } - - code.highlighted-code .type { - @apply text-blue-600 dark:text-blue-400; - } - - code.highlighted-code .module { - @apply text-pink-600 dark:text-pink-400; - } - - code.highlighted-code .string { - @apply text-green-600 dark:text-green-400; - } - - code.highlighted-code .tag { - @apply text-fuchsia-600 dark:text-fuchsia-400; - } -} diff --git a/tailwind.config.js b/tailwind.config.js deleted file mode 100644 index 4699a3f..0000000 --- a/tailwind.config.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - content: ["./src/**/*.rs"], - theme: { - extend: { - fontFamily: { - sans: ["Inter", "sans-serif"], - mono: ["'Berkeley Mono'", "monospace"], - }, - }, - }, - plugins: [require("@tailwindcss/typography")], -};