My website lesbian.skin
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Stuff

+537 -661
+3 -6
gleam.toml
··· 2 2 version = "1.0.0" 3 3 4 4 [dependencies] 5 - gleam_stdlib = ">= 0.34.0 and < 0.50.0" 6 - lustre = ">= 4.2.0 and < 5.0.0" 7 - tom = ">= 1.0.0 and < 2.0.0" 5 + gleam_stdlib = ">= 0.60.0 and < 0.70.0" 6 + lustre = ">= 5.0.0 and < 6.0.0" 8 7 simplifile = ">= 2.0.0 and < 3.0.0" 9 - commonmark = ">= 0.1.8 and < 1.0.0" 10 - glailglind = ">= 1.1.2 and < 2.0.0" 11 8 lustre_ssg = ">= 0.8.3 and < 1.0.0" 12 - gsv = ">= 3.0.1 and < 4.0.0" 9 + tom = ">= 1.1.1 and < 2.0.0" 13 10 14 11 [dev-dependencies] 15 12 gleeunit = ">= 1.0.0 and < 2.0.0"
-20
index.html
··· 1 - <!doctype html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8" /> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 - 7 - <link rel="preconnect" href="https://fonts.googleapis.com"> 8 - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 9 - <link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet"> 10 - 11 - <title>🐈 naomieow</title> 12 - 13 - <script type="module" src="/priv/static/website.mjs"></script> 14 - <link rel="stylesheet" href="/priv/static/website.css"> 15 - </head> 16 - 17 - <body style="margin: 0;"> 18 - <div id="app"></div> 19 - </body> 20 - </html>
+18 -25
manifest.toml
··· 2 2 # You typically do not need to edit this file 3 3 4 4 packages = [ 5 - { name = "commonmark", version = "0.1.8", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "commonmark", source = "hex", outer_checksum = "D20FD8F1B147C3F9288FDB84CC790B5136FFD9D4BD04E121BE851B7EB02A6566" }, 6 5 { name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" }, 7 6 { name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" }, 8 - { name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" }, 9 - { name = "glailglind", version = "1.1.3", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_httpc", "gleam_stdlib", "shellout", "simplifile", "tom"], otp_app = "glailglind", source = "hex", outer_checksum = "4617C93C84172CF99EC05B4706720098A0BF299539DD58DD3D90DB2BF2B472F8" }, 10 - { name = "gleam_crypto", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "8AE56026B3E05EBB1F076778478A762E9EB62B31AEEB4285755452F397029D22" }, 11 - { name = "gleam_erlang", version = "0.33.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "A1D26B80F01901B59AABEE3475DD4C18D27D58FA5C897D922FCB9B099749C064" }, 12 - { name = "gleam_http", version = "3.7.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8A70D2F70BB7CFEB5DF048A2183FFBA91AF6D4CF5798504841744A16999E33D2" }, 13 - { name = "gleam_httpc", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "CF6CDD88830CC9853F7638ECC0BE7D7CD9522640DA5FAB4C08CFAC8DEBD08028" }, 14 - { name = "gleam_json", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "0A57FB5666E695FD2BEE74C0428A98B0FC11A395D2C7B4CDF5E22C5DD32C74C6" }, 15 - { name = "gleam_otp", version = "0.16.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "FA0EB761339749B4E82D63016C6A18C4E6662DA05BAB6F1346F9AF2E679E301A" }, 16 - { name = "gleam_stdlib", version = "0.49.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "A7DB09F15738198A87255425FBCE049B4B84C77CC522786DC923DABA73911F13" }, 17 - { name = "glearray", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glearray", source = "hex", outer_checksum = "B99767A9BC63EF9CC8809F66C7276042E5EFEACAA5B25188B552D3691B91AC6D" }, 18 - { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, 19 - { name = "gsv", version = "3.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glearray"], otp_app = "gsv", source = "hex", outer_checksum = "31D3C88CECA73DE800FCE66A8F77354FDDA8EB627E2040AA4A27577AD43A68D4" }, 20 - { name = "jot", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "jot", source = "hex", outer_checksum = "A0A52BD8D079AB0ABF80BE11DC63B85CF5791125991A029FAD7D17820D9419D8" }, 21 - { name = "lustre", version = "4.6.3", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "BDF833368F6C8F152F948D5B6A79866E9881CB80CB66C0685B3327E7DCBFB12F" }, 22 - { name = "lustre_ssg", version = "0.8.3", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib", "jot", "lustre", "simplifile", "temporary", "tom"], otp_app = "lustre_ssg", source = "hex", outer_checksum = "5F1DF752AE6AAD0BD332CCB52E1CF2086997CF55EB59894080F4E3ADF691B95C" }, 23 - { name = "shellout", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "shellout", source = "hex", outer_checksum = "E2FCD18957F0E9F67E1F497FC9FF57393392F8A9BAEAEA4779541DE7A68DD7E0" }, 24 - { name = "simplifile", version = "2.2.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0DFABEF7DC7A9E2FF4BB27B108034E60C81BEBFCB7AB816B9E7E18ED4503ACD8" }, 7 + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 8 + { name = "gleam_crypto", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "917BC8B87DBD584830E3B389CBCAB140FFE7CB27866D27C6D0FB87A9ECF35602" }, 9 + { name = "gleam_erlang", version = "0.34.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" }, 10 + { name = "gleam_json", version = "3.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "5BA154440B22D9800955B1AB854282FA37B97F30F409D76B0824D0A60C934188" }, 11 + { name = "gleam_otp", version = "0.16.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "50DA1539FC8E8FA09924EB36A67A2BBB0AD6B27BCDED5A7EF627057CF69D035E" }, 12 + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, 13 + { name = "gleam_stdlib", version = "0.60.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "621D600BB134BC239CB2537630899817B1A42E60A1D46C5E9F3FAE39F88C800B" }, 14 + { name = "gleeunit", version = "1.3.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "A7DD6C07B7DA49A6E28796058AA89E651D233B357D5607006D70619CD89DAAAB" }, 15 + { name = "houdini", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "houdini", source = "hex", outer_checksum = "5BA517E5179F132F0471CB314F27FE210A10407387DA1EA4F6FD084F74469FC2" }, 16 + { name = "jot", version = "4.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "houdini", "splitter"], otp_app = "jot", source = "hex", outer_checksum = "E9E266D2768EA1238283D2CF125AA68095F17BAA4DDF3598360FD19F38593C59" }, 17 + { name = "lustre", version = "5.1.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "A0ADD4D936A49EE2CEBB8070F39058009122F0321D4B5445843D56E54875ECD8" }, 18 + { name = "lustre_ssg", version = "0.11.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_regexp", "gleam_stdlib", "jot", "lustre", "simplifile", "temporary", "tom"], otp_app = "lustre_ssg", source = "hex", outer_checksum = "D1F2B47EBE27C2B1DE6552A0883BC676D8EB076359D152FFA323FADDC74FFC41" }, 19 + { name = "simplifile", version = "2.2.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "C88E0EE2D509F6D86EB55161D631657675AA7684DAB83822F7E59EB93D9A60E3" }, 20 + { name = "splitter", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "splitter", source = "hex", outer_checksum = "128FC521EE33B0012E3E64D5B55168586BC1B9C8D7B0D0CA223B68B0D770A547" }, 25 21 { name = "temporary", version = "1.0.0", build_tools = ["gleam"], requirements = ["envoy", "exception", "filepath", "gleam_crypto", "gleam_stdlib", "simplifile"], otp_app = "temporary", source = "hex", outer_checksum = "51C0FEF4D72CE7CA507BD188B21C1F00695B3D5B09D7DFE38240BFD3A8E1E9B3" }, 26 - { name = "tom", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "228E667239504B57AD05EC3C332C930391592F6C974D0EFECF32FFD0F3629A27" }, 22 + { name = "tom", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "0910EE688A713994515ACAF1F486A4F05752E585B9E3209D8F35A85B234C2719" }, 27 23 ] 28 24 29 25 [requirements] 30 - commonmark = { version = ">= 0.1.8 and < 1.0.0" } 31 - glailglind = { version = ">= 1.1.2 and < 2.0.0" } 32 - gleam_stdlib = { version = ">= 0.34.0 and < 0.50.0" } 26 + gleam_stdlib = { version = ">= 0.60.0 and < 0.70.0" } 33 27 gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 34 - gsv = { version = ">= 3.0.1 and < 4.0.0" } 35 - lustre = { version = ">= 4.2.0 and < 5.0.0" } 28 + lustre = { version = ">= 5.0.0 and < 6.0.0" } 36 29 lustre_ssg = { version = ">= 0.8.3 and < 1.0.0" } 37 30 simplifile = { version = ">= 2.0.0 and < 3.0.0" } 38 - tom = { version = ">= 1.0.0 and < 2.0.0" } 31 + tom = { version = ">= 1.1.1 and < 2.0.0" }
+1 -34
src/build.gleam
··· 1 1 import gleam/dict 2 2 import gleam/io 3 3 import gleam/list 4 - import gleam/option 5 - import gleam/result 6 4 import gleam/string 7 - import gsv 8 5 import lustre/ssg 9 - import simplifile 10 - import tailwind 11 6 import website/common 12 7 import website/data/posts 13 8 import website/data/projects 14 - import website/page/anime 15 9 import website/page/index 16 10 import website/page/post 17 11 import website/page/project ··· 27 21 posts.all() 28 22 |> list.map(fn(post) { #(post.id, post) }), 29 23 ) 30 - let assert Ok(anime_csv) = simplifile.read("./src/website/data/anime.csv") 31 - let assert Ok(anime_rows) = gsv.to_lists(anime_csv) 32 - let anime = 33 - anime_rows 34 - |> list.map(fn(row) { 35 - case row { 36 - [title] -> Ok(common.Anime(title, option.None, option.None)) 37 - [title, link] -> Ok(common.Anime(title, option.Some(link), option.None)) 38 - [title, link, thoughts] -> 39 - Ok(common.Anime(title, option.Some(link), option.Some(thoughts))) 40 - _ -> Error(Nil) 41 - } 42 - }) 43 - |> result.values() 44 24 45 25 let build = 46 26 ssg.new("./priv") ··· 59 39 |> ssg.add_dynamic_route("/posts", posts, fn(p) { 60 40 post.view(p) |> common.wrapper(p.title, _) 61 41 }) 62 - |> ssg.add_static_route( 63 - "/anime", 64 - anime.view(anime) |> common.wrapper("Anime", _), 65 - ) 66 42 |> ssg.use_index_routes() 67 43 |> ssg.add_static_dir("./src/static/") 68 44 |> ssg.build() 69 - |> result.map_error(string.inspect) 70 - |> result.try(fn(_) { 71 - [ 72 - "--config=tailwind.config.js", "--input=./src/input.css", 73 - "--output=./priv/style.css", 74 - ] 75 - |> tailwind.run() 76 - }) 77 45 78 46 case build { 79 47 Ok(_) -> io.println("Build succeeded!") 80 48 Error(e) -> { 81 - io.debug(e) 82 - io.println("Build failed!") 49 + io.println_error("Build failed!\n" <> string.inspect(e)) 83 50 } 84 51 } 85 52 }
-15
src/input.css
··· 1 - @tailwind base; 2 - @tailwind components; 3 - @tailwind utilities; 4 - 5 - * { 6 - font-family: "Recursive", sans-serif; 7 - font-optical-sizing: auto; 8 - font-style: normal; 9 - font-variation-settings: 10 - "slnt" 0, 11 - "CASL" 0, 12 - "CRSV" 0.5, 13 - "MONO" 0; 14 - transition-duration: 750ms; 15 - }
src/static/assets/ages.png

This is a binary file and will not be displayed.

+7
src/static/assets/bsky.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" ?> 2 + <svg width="600" height="530" version="1.1" xmlns="http://www.w3.org/2000/svg"> 3 + <path 4 + d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z" 5 + fill="currentColor" 6 + /> 7 + </svg>
src/static/assets/caramel.png

This is a binary file and will not be displayed.

src/static/assets/cat_pfp.webp

This is a binary file and will not be displayed.

src/static/assets/cfs.png

This is a binary file and will not be displayed.

+7
src/static/assets/curseforge.svg
··· 1 + <?xml version="1.0" encoding="utf-8" ?> 2 + <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> 3 + <svg fill="currentColor" width="800px" height="800px" viewBox="0 0 24 24" role="img" xmlns="http://www.w3.org/2000/svg"> 4 + <path 5 + d="m6.307 5.581.391 1.675H0s.112.502.167.558c.168.279.335.614.559.837 1.06 1.228 2.902 1.73 4.409 2.009 1.06.224 2.121.28 3.181.335l1.228 3.293h.67l.391 1.061h-.558l-.949 3.07h9.321l-.949-3.07h-.558l.39-1.061h.67s.558-3.404 2.288-4.967C21.935 7.758 24 7.535 24 7.535V5.581H6.307zm9.377 8.428c-.447.279-.949.279-1.284.503-.223.111-.335.446-.335.446-.223-.502-.502-.67-.837-.781-.335-.112-.949-.056-1.786-.782-.558-.502-.614-1.172-.558-1.507v-.167c0-.056 0-.112.056-.168.111-.334.39-.669.948-.893 0 0-.39.559 0 1.117.224.335.67.502 1.061.279.167-.112.279-.335.335-.503.111-.39.111-.781-.224-1.06-.502-.446-.613-1.06-.279-1.451 0 0 .112.502.614.446.335 0 .335-.111.224-.223-.056-.167-.782-1.228.279-2.009 0 0 .669-.447 1.451-.391-.447.056-.949.335-1.116.782v.055c-.168.447-.056.949.279 1.396.223.335.502.614.614 1.06-.168-.056-.279 0-.391.112a.533.533 0 0 0-.112.502c.056.112.168.223.279.223h.168c.167-.055.279-.279.223-.446.112.111.167.391.112.558 0 .167-.112.335-.168.446-.056.112-.167.224-.223.335-.056.112-.112.224-.112.335 0 .112 0 .279.056.391.223.335.67 0 .782-.279.167-.335.111-.726-.112-1.061 0 0 .391.224.67 1.005.223.67-.168 1.451-.614 1.73z" 6 + /> 7 + </svg>
src/static/assets/curseforge_64h.png

This is a binary file and will not be displayed.

+15
src/static/assets/git.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" ?> 2 + <svg xmlns="http://www.w3.org/2000/svg" width="92pt" height="92pt" viewBox="0 0 92 92"> 3 + <defs> 4 + <clipPath id="a"> 5 + <path d="M0 .113h91.887V92H0Zm0 0" /> 6 + </clipPath> 7 + </defs> 8 + <g clip-path="url(#a)"> 9 + <path 10 + fill="currentColor" 11 + style="stroke:none;fill-rule:nonzero;fill-opacity:1" 12 + d="M90.156 41.965 50.036 1.848a5.913 5.913 0 0 0-8.368 0l-8.332 8.332 10.566 10.566a7.03 7.03 0 0 1 7.23 1.684 7.043 7.043 0 0 1 1.673 7.277l10.183 10.184a7.026 7.026 0 0 1 7.278 1.672 7.04 7.04 0 0 1 0 9.957 7.045 7.045 0 0 1-9.961 0 7.038 7.038 0 0 1-1.532-7.66l-9.5-9.497V59.36a7.04 7.04 0 0 1 1.86 11.29 7.04 7.04 0 0 1-9.957 0 7.04 7.04 0 0 1 0-9.958 7.034 7.034 0 0 1 2.308-1.539V33.926a7.001 7.001 0 0 1-2.308-1.535 7.049 7.049 0 0 1-1.516-7.7L29.242 14.273 1.734 41.777a5.918 5.918 0 0 0 0 8.371L41.855 90.27a5.92 5.92 0 0 0 8.368 0l39.933-39.934a5.925 5.925 0 0 0 0-8.371" 13 + /> 14 + </g> 15 + </svg>
src/static/assets/git_64h.png

This is a binary file and will not be displayed.

src/static/assets/lilypad.png

This is a binary file and will not be displayed.

+13
src/static/assets/modrinth.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" ?> 2 + <svg width="512" height="514" viewBox="0 0 512 514" xmlns="http://www.w3.org/2000/svg"> 3 + <path 4 + fill-rule="evenodd" 5 + clip-rule="evenodd" 6 + d="M503.16 323.56C514.55 281.47 515.32 235.91 503.2 190.76C466.57 54.2299 326.04 -26.8001 189.33 9.77991C83.8101 38.0199 11.3899 128.07 0.689941 230.47H43.99C54.29 147.33 113.74 74.7298 199.75 51.7098C306.05 23.2598 415.13 80.6699 453.17 181.38L411.03 192.65C391.64 145.8 352.57 111.45 306.3 96.8198L298.56 140.66C335.09 154.13 364.72 184.5 375.56 224.91C391.36 283.8 361.94 344.14 308.56 369.17L320.09 412.16C390.25 383.21 432.4 310.3 422.43 235.14L464.41 223.91C468.91 252.62 467.35 281.16 460.55 308.07L503.16 323.56Z" 7 + fill="currentColor" 8 + /> 9 + <path 10 + d="M321.99 504.22C185.27 540.8 44.7501 459.77 8.11011 323.24C3.84011 307.31 1.17 291.33 0 275.46H43.27C44.36 287.37 46.4699 299.35 49.6799 311.29C53.0399 323.8 57.45 335.75 62.79 347.07L101.38 323.92C98.1299 316.42 95.39 308.6 93.21 300.47C69.17 210.87 122.41 118.77 212.13 94.7601C229.13 90.2101 246.23 88.4401 262.93 89.1501L255.19 133C244.73 133.05 234.11 134.42 223.53 137.25C157.31 154.98 118.01 222.95 135.75 289.09C136.85 293.16 138.13 297.13 139.59 300.99L188.94 271.38L174.07 231.95L220.67 184.08L279.57 171.39L296.62 192.38L269.47 219.88L245.79 227.33L228.87 244.72L237.16 267.79C237.16 267.79 253.95 285.63 253.98 285.64L277.7 279.33L294.58 260.79L331.44 249.12L342.42 273.82L304.39 320.45L240.66 340.63L212.08 308.81L162.26 338.7C187.8 367.78 226.2 383.93 266.01 380.56L277.54 423.55C218.13 431.41 160.1 406.82 124.05 361.64L85.6399 384.68C136.25 451.17 223.84 484.11 309.61 461.16C371.35 444.64 419.4 402.56 445.42 349.38L488.06 364.88C457.17 431.16 398.22 483.82 321.99 504.22Z" 11 + fill="currentColor" 12 + /> 13 + </svg>
src/static/assets/modrinth_64h.png

This is a binary file and will not be displayed.

src/static/assets/pricked.png

This is a binary file and will not be displayed.

src/static/assets/titles.png

This is a binary file and will not be displayed.

+52
src/static/gpg-public-key.txt
··· 1 + -----BEGIN PGP PUBLIC KEY BLOCK----- 2 + 3 + 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 50 + 7WLb7X66AU5QHvu5lmfe1YGk8Q== 51 + =5cRP 52 + -----END PGP PUBLIC KEY BLOCK-----
+1
src/static/ssh-public-key.txt
··· 1 + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDTDutMLPJlc3F1uZxr7apR2ivJZU75/iVEX34c/hlco
+88 -100
src/website/common.gleam
··· 3 3 import lustre/attribute as attr 4 4 import lustre/element 5 5 import lustre/element/html 6 + import lustre/ssg/djot 6 7 import tom 7 - import website/md 8 + import website/style 8 9 9 10 pub type Post(a) { 10 11 Post( ··· 42 43 page: String, 43 44 elements: List(element.Element(a)), 44 45 ) -> element.Element(a) { 45 - html.html([attr.attribute("lang", "en")], [ 46 + html.html([attr.attribute("lang", "en"), style.background_color()], [ 46 47 html.head([], [ 47 48 html.meta([attr.attribute("charset", "UTF-8")]), 48 - html.script([attr.src("/script.js")], ""), 49 - html.link([attr.rel("stylesheet"), attr.href("/style.css")]), 50 - html.link([ 51 - attr.rel("preconnect"), 52 - attr.href("https://fonts.googleapis.com"), 53 - attr.attribute("crossorigin", ""), 54 - ]), 55 - html.link([ 56 - attr.rel("preconnect"), 57 - attr.href("https://fonts.gstatic.com"), 58 - attr.attribute("crossorigin", ""), 59 - ]), 60 - html.link([ 61 - attr.rel("stylesheet"), 62 - attr.href( 63 - "https://fonts.googleapis.com/css2?family=Recursive:wght@300..1000&display=swap", 64 - ), 65 - ]), 66 49 html.title([], page <> " | Naomi Roberts"), 67 50 ]), 68 - html.body( 69 - [ 70 - attr.class( 71 - "my-0 sm:my-5 mx-0 sm:mx-6 md:mx-10 lg:mx-20 xl:mx-48 2xl:mx-64 dark:bg-neutral-700", 72 - ), 73 - ], 74 - [navbar(), ..elements], 75 - ), 76 - html.footer( 77 - [ 78 - attr.class( 79 - "rounded-xl bg-gray-200 dark:bg-neutral-800 border-0 p-4 text-sm text-neutral-500 items-center flex flex-col", 80 - ), 81 - ], 82 - " 83 - The source code for this website is available [on Codeberg](https://codeberg.org/naomi/website) under the [BSD 3 with Attribution](https://spdx.org/licenses/BSD-3-Clause-Attribution) license. 51 + style.body_query(), 52 + html.body([attr.class("body-margin"), style.body_styles()], [ 53 + navbar(), 54 + html.div([attr.style("margin", "1em")], elements), 55 + ]), 56 + html.footer([style.footer()], [ 57 + html.hr([attr.style("color", "lightgrey")]), 58 + .." 59 + The source code for this website is available [here](https://codeberg.org/naomi/website) or [here](https://tangled.sh/@lesbian.skin/website) under the [BSD 3 with Attribution](https://spdx.org/licenses/BSD-3-Clause-Attribution) license. 84 60 85 61 If you wish to get in contact, please send an email to [mia@naomieow.xyz](mailto:mia@naomieow.xyz), or contact me on [Discord](https://chat.lesbian.skin). 86 62 " 87 - |> md.render(), 88 - ), 63 + |> djot.render(djot.default_renderer()) 64 + ]), 89 65 ]) 90 66 } 91 67 92 68 pub fn navbar() -> element.Element(a) { 93 - html.nav( 94 - [ 95 - attr.class( 96 - "drop-shadow-md flex gap-4 w-full mb-5 rounded-xl p-4 items-center border-0 grow bg-gray-200 dark:bg-neutral-800", 97 - ), 98 - ], 99 - [ 100 - html.h1([attr.class("hidden md:block text-3xl m-0 text-pink-400")], [ 101 - element.text("Naomi Roberts"), 102 - ]), 103 - navitem(name: "Home", href: ""), 104 - navitem(name: "Projects", href: "projects"), 105 - navitem(name: "Posts", href: "posts"), 106 - navitem(name: "Anime", href: "anime"), 107 - darkmode(), 108 - ], 109 - ) 69 + html.div([style.navbar()], [ 70 + navitem(name: "home", href: "/", external: False), 71 + navitem(name: "projects", href: "/projects", external: False), 72 + navitem(name: "posts", href: "/posts", external: False), 73 + navitem( 74 + name: "anime", 75 + href: "https://anilist.co/user/naomieow/", 76 + external: True, 77 + ), 78 + html.div([style.navbar_right()], [icon_link(Bluesky), icon_link(Tangled)]), 79 + ]) 110 80 } 111 81 112 - fn darkmode() -> element.Element(e) { 113 - html.button( 114 - [ 115 - attr.id("button-dark-mode"), 116 - attr.class( 117 - "drop-shadow-md no-underline bg-gray-300 dark:bg-neutral-900 p-3 rounded-xl text-black dark:text-neutral-200 118 - before:content-['☀️'] dark:before:content-['🌕️']", 119 - ), 120 - ], 121 - [], 122 - ) 82 + fn navitem( 83 + name text: String, 84 + href href: String, 85 + external external: Bool, 86 + ) -> element.Element(a) { 87 + link([style.navitem()], text:, href:, external:) 123 88 } 124 89 125 - fn navitem(name name: String, href href: String) -> element.Element(a) { 126 - html.a( 127 - [ 128 - attr.href("/" <> href), 129 - attr.class( 130 - "drop-shadow-md no-underline bg-gray-300 dark:bg-neutral-900 p-3 rounded-xl text-black dark:text-neutral-200", 131 - ), 132 - ], 133 - [element.text(name)], 134 - ) 90 + pub type Icon { 91 + Bluesky 92 + Tangled 135 93 } 136 94 137 - pub fn link(text text: String, link link: String) -> element.Element(a) { 138 - html.a([attr.href(link), attr.class("text-pink-400 underline")], [ 139 - element.text(text), 95 + pub fn icon_link(icon icon: Icon) -> element.Element(a) { 96 + let #(href, img) = case icon { 97 + Bluesky -> #("https://bsky.app/profile/lesbian.skin", "/assets/bsky.svg") 98 + Tangled -> #("https://tangled.sh/@lesbian.skin", "/assets/git.svg") 99 + } 100 + 101 + html.a([attr.href(href), style.link_icon()], [ 102 + html.img([attr.src(img), style.icon_size()]), 140 103 ]) 141 104 } 142 105 143 - pub fn warning(title: String, contents: String) -> element.Element(a) { 144 - html.div( 145 - [ 146 - attr.class( 147 - "bg-red-300 dark:bg-red-700 rounded-xl border-2 border-red-950 mb-4", 148 - ), 149 - ], 106 + pub fn link( 107 + attributes attributes: List(attr.Attribute(a)), 108 + text text: String, 109 + href href: String, 110 + external external: Bool, 111 + ) -> element.Element(a) { 112 + let attrs = [attr.href(href), style.link(), ..attributes] 113 + html.a( 114 + case external { 115 + False -> attrs 116 + True -> [ 117 + attr.target("_blank"), 118 + attr.rel("noopener noreferrer"), 119 + attr.style("display", "inline-block"), 120 + ..attrs 121 + ] 122 + }, 150 123 [ 151 - html.h3( 152 - [ 153 - attr.class( 154 - "bg-red-400 dark:bg-red-800 rounded-t-xl text-2xl underline p-1 dark:text-neutral-200 border-b-2 border-red-950 ", 155 - ), 156 - ], 157 - [element.text(title)], 158 - ), 159 - html.p([attr.class("text-xl p-1 dark:text-neutral-100")], [ 160 - element.text(contents), 161 - ]), 124 + element.text(text), 125 + // Seems to cause some DOM issues? 126 + // in some places it appears `<a href>text<p style>↗</p></a> 127 + // but in other places it's `<p><a href>text<a></p><p style><a href>↗</a></p><p><p/>` 128 + // case external { 129 + // True -> 130 + // html.p( 131 + // [ 132 + // attr.styles([ 133 + // #("display", "inline-block"), 134 + // #("font-size", "0.75em"), 135 + // #("margin", "0"), 136 + // #("vertical-align", "top"), 137 + // ]), 138 + // ], 139 + // [element.text("↗")], 140 + // ) 141 + // False -> element.none() 142 + // }, 162 143 ], 163 144 ) 164 145 } 165 146 147 + pub fn warning(title: String, contents: String) -> element.Element(a) { 148 + html.div([], [ 149 + html.h3([], [element.text(title)]), 150 + html.p([], [element.text(contents)]), 151 + ]) 152 + } 153 + 166 154 pub fn date_to_string(date: tom.Date) -> String { 167 - date.day |> int.to_string 155 + date.day |> int.to_string() 168 156 <> "/" 169 - <> date.month |> int.to_string 157 + <> date.month |> int.to_string() 170 158 <> "/" 171 - <> date.year |> int.to_string 159 + <> date.year |> int.to_string() 172 160 }
-52
src/website/data/anime.csv
··· 1 - Frieren: Beyond Journey’s End,https://anilist.co/anime/154587/Frieren-Beyond-Journeys-End/ 2 - DAN DA DAN,https://anilist.co/anime/171018/DAN-DA-DAN/ 3 - Kaiju No.8,https://anilist.co/anime/153288/Kaiju-No8/,"I'm a huge kaiju fan, so this one is quite biased!" 4 - Fullmetal Alchemist: Brotherhood,https://anilist.co/anime/5114/Fullmetal-Alchemist-Brotherhood/ 5 - Gundam: TWFM,https://anilist.co/anime/139274/Mobile-Suit-Gundam-The-Witch-from-Mercury/ 6 - Jujutsu Kaisen S2,https://anilist.co/anime/145064/JUJUTSU-KAISEN-Season-2/ 7 - Chainsaw Man,https://anilist.co/anime/127230/Chainsaw-Man/ 8 - Love is War S3,https://anilist.co/anime/125367/Kaguyasama-Love-is-War-Ultra-Romantic/ 9 - Love is War,https://anilist.co/anime/101921/Kaguyasama-Love-is-War/ 10 - Love is War S4,https://anilist.co/anime/151384/Kaguyasama-Love-is-War-The-First-Kiss-That-Never-Ends/ 11 - Love is War S2,https://anilist.co/anime/112641/Kaguyasama-Love-is-War/ 12 - Evangelion 1.0,https://anilist.co/anime/2759/Evangelion-10-You-Are-Not-Alone/,All the Eva movies are basically the same tier 13 - Evangelion 2.0,https://anilist.co/anime/3784/Evangelion-20-You-Can-Not-Advance/ 14 - Evangelion 3.0,https://anilist.co/anime/3785/Evangelion-30-You-Can-Not-Redo/ 15 - Evangelion 3.0+1.0,https://anilist.co/anime/3786/Evangelion-3010-Thrice-Upon-a-Time/ 16 - JoJo's P3,https://anilist.co/anime/20474/JoJos-Bizarre-Adventure-Stardust-Crusaders/ 17 - JoJo's P3P2,https://anilist.co/anime/20799/JoJos-Bizarre-Adventure-Stardust-Crusaders--Battle-in-Egypt/ 18 - JoJo's P4,https://anilist.co/anime/21450/JoJos-Bizarre-Adventure-Diamond-is-Unbreakable/ 19 - Attack on Titan,https://anilist.co/anime/16498/Attack-on-Titan/ 20 - Attack on Titan S4,https://anilist.co/anime/110277/Attack-on-Titan-Final-Season/ 21 - Attack on Titan S4P2,https://anilist.co/anime/131681/Attack-on-Titan-Final-Season-Part-2/ 22 - Attack on Titan S3,https://anilist.co/anime/99147/Attack-on-Titan-Season-3/ 23 - Attack on Titan S2,https://anilist.co/anime/20958/Attack-on-Titan-Season-2/ 24 - Attack on Titan S3P2,https://anilist.co/anime/104578/Attack-on-Titan-Season-3-Part-2/ 25 - Hell's Paradise,https://anilist.co/anime/128893/Hells-Paradise/ 26 - Death Note,https://anilist.co/anime/1535/Death-Note/ 27 - Attack on Titan S4P3S1,https://anilist.co/anime/146984/Attack-on-Titan-Final-Season-THE-FINAL-CHAPTERS-Special-1/ 28 - Attack on Titan S4P3S2,https://anilist.co/anime/162314/Attack-on-Titan-Final-Season-THE-FINAL-CHAPTERS-Special-2/ 29 - Cyberpunk: Edgerunners,https://anilist.co/anime/120377/Cyberpunk-Edgerunners/ 30 - My Hero Academia S7,https://anilist.co/anime/163139/My-Hero-Academia-Season-7/ 31 - Mushoku Tensei S2P2,https://anilist.co/anime/166873/Mushoku-Tensei-Jobless-Reincarnation-Season-2-Part-2/ 32 - Demon Slayer S4,https://anilist.co/anime/166240/Demon-Slayer-Kimetsu-no-Yaiba-Hashira-Training-Arc/ 33 - Demon Slayer S2,https://anilist.co/anime/142329/Demon-Slayer-Kimetsu-no-Yaiba-Entertainment-District-Arc/ 34 - Fate/Zero,https://anilist.co/anime/10087/FateZero/ 35 - Bocchi the Rock,https://anilist.co/anime/130003/BOCCHI-THE-ROCK/ 36 - Demon Slayer S3,https://anilist.co/anime/145139/Demon-Slayer-Kimetsu-no-Yaiba-Swordsmith-Village-Arc/ 37 - Demon Slayer: Mugen Train,https://anilist.co/anime/112151/Demon-Slayer-Kimetsu-no-Yaiba-The-Movie-Mugen-Train/ 38 - Demon Slayer,https://anilist.co/anime/101922/Demon-Slayer-Kimetsu-no-Yaiba/ 39 - Blue Exorcist S3,https://anilist.co/anime/158931/Blue-Exorcist-Shimane-Illuminati-Saga/ 40 - Blue Exorcist S4,https://anilist.co/anime/176311/Blue-Exorcist-Beyond-the-Snow-Saga/ 41 - Jujutsu Kaisen 0,https://anilist.co/anime/131573/JUJUTSU-KAISEN-0/ 42 - Attack on Titan: No Regrets,https://anilist.co/anime/20811/Attack-on-Titan-No-Regrets/ 43 - Blue Exorcist S2,https://anilist.co/anime/21861/Blue-Exorcist-Kyoto-Saga/ 44 - Blue Exorcist,https://anilist.co/anime/9919/Blue-Exorcist/,If episodes 17-25 didn't exist this would be higher than Season 2 45 - 7th Time Loop,https://anilist.co/anime/168374/7th-Time-Loop-The-Villainess-Enjoys-a-Carefree-Life-Married-to-Her-Worst-Enemy/ 46 - Campfire Cooking,https://anilist.co/anime/156067/Campfire-Cooking-in-Another-World-with-my-Absurd-Skill/ 47 - Mashle,https://anilist.co/anime/151801/MASHLE-MAGIC-AND-MUSCLES/ 48 - Mashle S2,https://anilist.co/anime/166610/MASHLE-MAGIC-AND-MUSCLES-Season-2/ 49 - 86,https://anilist.co/anime/116589/86-EIGHTYSIX/ 50 - 86 S2,https://anilist.co/anime/131586/86-EIGHTYSIX-Part-2/ 51 - AMAIM,https://anilist.co/anime/143203/AMAIM-Warrior-at-the-Borderline-Part-2/ 52 - Aldnoah.Zero,https://anilist.co/anime/20632/ALDNOAHZERO/
+8 -4
src/website/data/posts.gleam
··· 7 7 import simplifile 8 8 import tom 9 9 import website/common.{type Post, Post} 10 - import website/md 11 10 12 11 pub fn all() -> List(Post(a)) { 13 12 let path = "./src/website/data/posts/" ··· 22 21 let assert Ok(summary) = tom.get_string(meta, ["summary"]) 23 22 Post( 24 23 id: post 25 - |> string.drop_right(3) 24 + |> string.drop_end(3) 26 25 |> string.lowercase 27 26 |> string.replace(" ", "-"), 28 27 title:, 29 28 author:, 30 29 date_posted:, 31 - summary: html.p([], summary |> md.render), 32 - content: html.div([], post_md |> djot.content |> md.render), 30 + summary: html.p([], summary |> djot.render(djot.default_renderer())), 31 + content: html.div( 32 + [], 33 + post_md 34 + |> djot.content() 35 + |> djot.render(djot.default_renderer()), 36 + ), 33 37 ) 34 38 }) 35 39 }
+18 -14
src/website/data/projects.gleam
··· 1 - import gleam/bool 2 1 import gleam/list 3 2 import gleam/order 4 3 import gleam/result ··· 7 6 import lustre/ssg/djot 8 7 import simplifile 9 8 import tom 10 - import website/common.{type Link, type Project, Link, Project} 11 - import website/md 9 + import website/common.{type Project, Link, Project} 12 10 13 11 pub fn all() -> List(Project(a)) { 14 12 let path = "./src/website/data/projects/" ··· 26 24 [ 27 25 tom.get_string(meta, ["source"]) 28 26 |> result.try(fn(link) { 29 - Ok(Link(title: "Source Code", url: link, img: "/assets/git_64h.png")) 27 + Ok(Link(title: "Source Code", url: link, img: "/assets/git.svg")) 30 28 }), 31 29 tom.get_string(meta, ["modrinth"]) 32 30 |> result.try(fn(link) { 33 - Ok(Link( 34 - title: "Modrinth", 35 - url: link, 36 - img: "/assets/modrinth_64h.png", 37 - )) 31 + Ok(Link(title: "Modrinth", url: link, img: "/assets/modrinth.svg")) 38 32 }), 39 33 tom.get_string(meta, ["curseforge"]) 40 34 |> result.try(fn(link) { 41 35 Ok(Link( 42 36 title: "CurseForge", 43 37 url: link, 44 - img: "/assets/curseforge_64h.png", 38 + img: "/assets/curseforge.svg", 45 39 )) 46 40 }), 47 41 ] ··· 51 45 archived:, 52 46 author:, 53 47 id: project 54 - |> string.drop_right(3) 48 + |> string.drop_end(3) 55 49 |> string.lowercase 56 50 |> string.replace(" ", "-"), 57 51 img:, 58 52 links:, 59 - summary: html.p([], summary |> md.render), 53 + summary: html.p([], summary |> djot.render(djot.default_renderer())), 60 54 title:, 61 - content: html.div([], project_md |> djot.content |> md.render), 55 + content: html.div( 56 + [], 57 + project_md 58 + |> djot.content() 59 + |> djot.render(djot.default_renderer()), 60 + ), 62 61 ) 63 62 }) 64 63 } ··· 70 69 } 71 70 72 71 fn compare_archived(a: Project(a), with b: Project(b)) -> order.Order { 73 - bool.compare(a.archived, b.archived) 72 + case a.archived, b.archived { 73 + True, True -> order.Eq 74 + True, False -> order.Gt 75 + False, False -> order.Eq 76 + False, True -> order.Lt 77 + } 74 78 }
-152
src/website/md.gleam
··· 1 - import commonmark 2 - import commonmark/ast 3 - import gleam/int 4 - import gleam/list 5 - import gleam/option.{None, Some} 6 - import lustre/attribute 7 - import lustre/element 8 - import lustre/element/html 9 - 10 - pub fn render(document doc: String) -> List(element.Element(a)) { 11 - doc 12 - |> commonmark.parse 13 - |> render_commonmark 14 - } 15 - 16 - pub fn render_commonmark(document doc: ast.Document) -> List(element.Element(a)) { 17 - doc.blocks 18 - |> list.map(render_block_node) 19 - } 20 - 21 - fn render_block_node(blocknode: ast.BlockNode) -> element.Element(a) { 22 - case blocknode { 23 - ast.HorizontalBreak -> html.hr([]) 24 - ast.Heading(level:, contents:) -> { 25 - let contents = contents |> list.map(render_inline_node) 26 - case level { 27 - 1 -> 28 - html.h1( 29 - [attribute.class("text-3xl font-semibold my-3 text-pink-400")], 30 - contents, 31 - ) 32 - 2 -> html.h2([attribute.class("text-2xl font-semibold my-2")], contents) 33 - 3 -> html.h3([attribute.class("text-xl font-semibold my-1")], contents) 34 - 4 -> html.h4([attribute.class("text-lg font-semibold")], contents) 35 - 5 -> html.h5([attribute.class("font-bold")], contents) 36 - 6 -> html.h6([attribute.class("font-semibold")], contents) 37 - _ -> html.p([], contents) 38 - } 39 - } 40 - ast.CodeBlock(info:, full_info:, contents:) -> 41 - html.pre([], [html.code([], [element.text(contents)])]) 42 - ast.HtmlBlock(html:) -> html.html([], [element.text(html)]) 43 - ast.Paragraph(contents:) -> 44 - html.p( 45 - [attribute.class("my-2.5")], 46 - contents |> list.map(render_inline_node), 47 - ) 48 - ast.BlockQuote(contents:) -> 49 - html.blockquote( 50 - [attribute.class("border-l-4 m-2 p-2")], 51 - contents |> list.map(render_block_node), 52 - ) 53 - ast.AlertBlock(level:, contents:) -> { 54 - let contents = contents |> list.map(render_block_node) 55 - case level { 56 - ast.NoteAlert -> 57 - html.blockquote([attribute.class("alert alert-note")], contents) 58 - ast.CautionAlert -> 59 - html.blockquote([attribute.class("alert alert-caution")], contents) 60 - ast.ImportantAlert -> 61 - html.blockquote([attribute.class("alert alert-important")], contents) 62 - ast.TipAlert -> 63 - html.blockquote([attribute.class("alert alert-tip")], contents) 64 - ast.WarningAlert -> 65 - html.blockquote([attribute.class("alert alert-warning")], contents) 66 - } 67 - } 68 - ast.OrderedList(contents:, start:, marker:) -> 69 - html.ol( 70 - [ 71 - attribute.attribute("start", start |> int.to_string), 72 - attribute.class("list-decimal ml-8"), 73 - ], 74 - contents |> list.map(render_list_item), 75 - ) 76 - ast.UnorderedList(contents:, marker:) -> 77 - html.ul( 78 - [attribute.class("list-disc ml-8")], 79 - contents |> list.map(render_list_item), 80 - ) 81 - } 82 - } 83 - 84 - fn render_list_item(list_item: ast.ListItem) { 85 - case list_item { 86 - ast.ListItem(contents:) -> 87 - html.li( 88 - [], 89 - contents 90 - |> list.map(fn(item) { html.p([], [item |> render_block_node]) }), 91 - ) 92 - ast.TightListItem(contents:) -> 93 - html.li([], contents |> list.map(render_block_node)) 94 - } 95 - } 96 - 97 - fn render_inline_node(inline_node: ast.InlineNode) { 98 - case inline_node { 99 - ast.CodeSpan(contents:) -> 100 - html.code( 101 - [ 102 - attribute.class( 103 - "text-pink-400 rounded px-1 py-px bg-gray-100 dark:bg-neutral-900", 104 - ), 105 - ], 106 - [element.text(contents)], 107 - ) 108 - ast.Emphasis(contents:, marker:) -> 109 - html.em([attribute.class("italic")], contents |> list.map(render_inline_node)) 110 - ast.StrongEmphasis(contents:, marker:) -> 111 - html.strong([attribute.class("font-bold")], contents |> list.map(render_inline_node)) 112 - ast.StrikeThrough(contents:) -> 113 - html.s([attribute.class("line-through")], contents |> list.map(render_inline_node)) 114 - ast.Link(contents:, title:, href:) -> 115 - html.a( 116 - [ 117 - attribute.href(href), 118 - attribute.class("text-pink-400 underline"), 119 - case title { 120 - Some(t) -> attribute.attribute("title", t) 121 - None -> attribute.none() 122 - }, 123 - ], 124 - contents |> list.map(render_inline_node), 125 - ) 126 - ast.ReferenceLink(contents:, ref:) -> 127 - html.a([attribute.href(ref)], contents |> list.map(render_inline_node)) 128 - ast.Image(alt:, title:, href:) -> 129 - html.img([ 130 - attribute.alt(alt), 131 - attribute.href(href), 132 - case title { 133 - Some(t) -> attribute.attribute("title", t) 134 - None -> attribute.none() 135 - }, 136 - ]) 137 - ast.ReferenceImage(alt:, ref:) -> 138 - html.img([attribute.alt(alt), attribute.href(ref)]) 139 - ast.UriAutolink(href:) -> 140 - html.a([attribute.href(href)], [element.text(href)]) 141 - ast.EmailAutolink(href:) -> 142 - html.a([attribute.href(href)], [element.text(href)]) 143 - ast.HtmlInline(html:) -> 144 - html.p([], [ 145 - element.text("Unimplemented, dump of inline HTML:"), 146 - element.text(html), 147 - ]) 148 - ast.PlainText(contents:) -> html.text(contents) 149 - ast.HardLineBreak -> html.br([]) 150 - ast.SoftLineBreak -> html.text("") 151 - } 152 - }
-86
src/website/page/anime.gleam
··· 1 - import gleam/float 2 - import gleam/int 3 - import gleam/list 4 - import gleam/option.{None, Some} 5 - import lustre/attribute as attr 6 - import lustre/element.{type Element} 7 - import lustre/element/html 8 - import lustre/ssg/djot 9 - import website/common.{type Anime} 10 - import website/md 11 - 12 - fn score(index: Int, length: Int) -> Int { 13 - float.truncate(int.to_float(length - index) /. int.to_float(length) *. 100.0) 14 - } 15 - 16 - pub fn view(anime: List(Anime)) -> List(Element(a)) { 17 - let length = list.length(anime) 18 - [ 19 - html.div( 20 - [ 21 - attr.class( 22 - "drop-shadow-md text-xl p-4 mb-4 rounded-xl bg-gray-200 dark:bg-neutral-800 dark:text-neutral-200", 23 - ), 24 - ], 25 - { 26 - "This is a non-comprehensive list of anime I have watched, sorted by whether or not I liked an anime more than the one below it. The \"score\" displayed on the left of each anime isn't technically decided by me and is actually dynamically created using the very basic formula: 27 - `(list_length - anime_index) / list_length * 100` 28 - where `anime_index` starts at `0` for the top-most anime. The (scoreless) list is [stored as a CSV](https://codeberg.org/naomi/website/src/branch/main/src/website/data/anime.csv) in the source code for the website, and is updated whenever I feel like it. 29 - I've also included some notes on some of the animes where I feel that I have something to comment on. 30 - Currently `" 31 - <> int.to_string(length) 32 - <> "` out of my (educated guess) `300+` watched anime are listed." 33 - } 34 - |> djot.content() 35 - |> md.render(), 36 - ), 37 - html.div( 38 - [attr.class("grid grid-cols-[min-content_33%_auto] gap-x-4")], 39 - list.index_map(anime, fn(a, i) { anime_entry(a, i, length) }) 40 - |> list.flatten(), 41 - ), 42 - ] 43 - } 44 - 45 - fn anime_entry( 46 - anime a: common.Anime, 47 - index i: Int, 48 - length length: Int, 49 - ) -> List(Element(a)) { 50 - [ 51 - html.p( 52 - [ 53 - attr.class( 54 - "drop-shadow-md text-xl p-4 mb-4 rounded-xl bg-gray-200 dark:bg-neutral-800 dark:text-neutral-200", 55 - ), 56 - ], 57 - [element.text(int.to_string(score(i, length)) <> "/100")], 58 - ), 59 - html.p( 60 - [ 61 - attr.class( 62 - "drop-shadow-md text-xl p-4 mb-4 rounded-xl bg-gray-200 dark:bg-neutral-800 dark:text-neutral-200", 63 - ), 64 - ], 65 - [ 66 - case a.link { 67 - None -> element.text(a.title) 68 - Some(link) -> common.link(a.title, link) 69 - }, 70 - ], 71 - ), 72 - html.p( 73 - [ 74 - attr.class( 75 - "drop-shadow-md text-xl p-4 mb-4 rounded-xl bg-gray-200 dark:bg-neutral-800 dark:text-neutral-200", 76 - ), 77 - ], 78 - [ 79 - case a.thoughts { 80 - None -> element.text("Nothing to add") 81 - Some(thoughts) -> element.text(thoughts) 82 - }, 83 - ], 84 - ), 85 - ] 86 - }
+37 -52
src/website/page/index.gleam
··· 1 - import gleam/list 2 1 import lustre/attribute as attr 3 2 import lustre/element.{type Element} 4 3 import lustre/element/html 5 4 import website/common 6 - import website/md 7 - 8 - fn lang_list(title: String, langs: List(String)) { 9 - html.ul([], [ 10 - html.li([], [ 11 - html.h3([attr.class("text-2xl underline")], [element.text(title)]), 12 - ]), 13 - html.li([], [ 14 - html.ul( 15 - [attr.class("list-disc pl-8")], 16 - langs 17 - |> list.map(fn(lang) { html.li([], [element.text(lang)]) }), 18 - ), 19 - ]), 20 - ]) 21 - } 5 + import website/style 22 6 23 7 pub fn view() -> List(Element(a)) { 24 - let proficient = [ 25 - "⭐ Gleam", "☕ Java", "📜 JavaScript", "🐍 Python", "❓mcfunction", "♻️ Kotlin", 26 - "🎮 C#", 27 - ] 28 - let learning = ["🦀 Rust", "😃 Odin", "⚡ Zig"] 29 - let want = ["🐪 OCaml"] 30 8 [ 31 - html.div( 32 - [ 33 - attr.class( 34 - "drop-shadow-md text-xl p-4 mb-4 rounded-xl bg-gray-200 dark:bg-neutral-800 dark:text-neutral-200", 35 - ), 36 - ], 37 - [ 38 - html.div( 39 - [], 40 - "# About Me 41 - Hi! I'm Naomi (or Mia), a trans girl from the UK who loves to code! I mostly make [Minecraft mods](https://modrinth.com/user/naomi), but have started to begin other projects like [Sheetr](https://sheetr.app/) and [Lilypad](https://codeberg.org/naomi/lilypad). Both projects are in their infancy, but I hope that they can become useful to someone out there eventually!" 42 - |> md.render, 43 - ), 44 - html.div([attr.class("my-4")], [ 9 + style.index_layout_query(), 10 + html.div([attr.class("index-layout"), style.index_layout()], [ 11 + html.div([style.infobox()], [ 12 + html.img([style.avatar_size(), attr.src("/assets/cat_pfp.webp")]), 13 + html.div([style.infobox_inner()], [ 14 + html.p([], [html.text("Naomi Roberts")]), 15 + html.p([], [html.text("she/her")]), 16 + html.p([], [html.text("United Kingdom")]), 45 17 html.p([], [ 46 - element.text( 47 - "I absolutely love learning new languages, so here is what I know:", 18 + common.link( 19 + attributes: [], 20 + text: "mia@naomieow.xyz", 21 + href: "mailto:mia@naomieow.xyz", 22 + external: True, 48 23 ), 49 24 ]), 50 - html.div([attr.class("gap-4 sm:gap-20 flex flex-col md:flex-row")], [ 51 - lang_list("Proficient", proficient), 52 - lang_list("Learning", learning), 53 - lang_list("Want to learn", want), 25 + html.p([], [ 26 + common.link( 27 + attributes: [], 28 + text: "SSH", 29 + href: "/ssh-public-key.txt", 30 + external: False, 31 + ), 32 + html.text("🔑"), 33 + common.link( 34 + attributes: [attr.style("margin-left", "0.5em")], 35 + text: "GPG", 36 + href: "/gpg-public-key.txt", 37 + external: False, 38 + ), 39 + html.text("🔑"), 54 40 ]), 55 41 ]), 56 - html.p([attr.class("text-sm")], [ 57 - element.text("Currently studying "), 58 - common.link( 59 - "Computer Games Programming", 60 - "https://www.staffs.ac.uk/course/computer-games-programming-bsc", 42 + ]), 43 + html.div([], [ 44 + html.h1([], [html.text("Hi, I'm Naomi!")]), 45 + html.p([], [ 46 + html.text( 47 + "I'm a Gleam developer and Minecraft modder, currently studying a BSc in Games Programming. I love learning new languages and skills, and applying the knowledge I gain into my work and problem solving.", 61 48 ), 62 - element.text(" at the "), 63 - common.link("University of Staffordshire", "https://staffs.ac.uk/"), 64 49 ]), 65 - ], 66 - ), 50 + ]), 51 + ]), 67 52 ] 68 53 }
+31 -39
src/website/page/post.gleam
··· 1 1 import gleam/list 2 - import lustre/attribute as attr 3 2 import lustre/element.{type Element} 4 3 import lustre/element/html 5 4 import website/common.{type Post} 6 5 import website/data/posts 6 + import website/style 7 7 8 8 pub fn view_all(posts: List(Post(a))) -> List(Element(a)) { 9 9 posts 10 - |> posts.sorted 11 - |> list.reverse 10 + |> posts.sorted() 11 + |> list.reverse() 12 12 |> list.map(fn(post: Post(a)) { 13 - html.ul( 14 - [ 15 - attr.class( 16 - "drop-shadow-md text-xl rounded-xl p-4 mb-4 bg-gray-200 dark:bg-neutral-800 dark:text-neutral-200", 17 - ), 18 - ], 19 - [ 20 - html.li([], [ 21 - html.div([attr.class("flex items-center gap-4")], [ 22 - html.h1([attr.class("text-2xl")], [ 23 - common.link(post.title, "/posts/" <> post.id), 24 - ]), 25 - html.p([attr.class("text-neutral-500")], [ 13 + html.div([style.post_card()], [ 14 + html.div([], [ 15 + html.div([], [ 16 + html.h1([style.post_header()], [ 17 + common.link([], post.title, "/posts/" <> post.id, False), 18 + ]), 19 + html.span([style.post_info()], [ 20 + html.p([style.post_header()], [ 26 21 element.text("Author: " <> post.author), 27 22 ]), 28 - html.p([attr.class("text-neutral-500")], [ 23 + html.p([style.post_header()], [ 29 24 element.text( 30 - "Date Posted: " <> post.date_posted |> common.date_to_string, 25 + "Date Posted: " 26 + <> post.date_posted 27 + |> common.date_to_string(), 31 28 ), 32 29 ]), 33 30 ]), 34 31 ]), 35 - html.li([], [html.p([], [post.summary])]), 36 - ], 37 - ) 32 + ]), 33 + html.div([], [html.p([], [post.summary])]), 34 + ]) 38 35 }) 39 36 } 40 37 41 38 pub fn view(post: Post(a)) -> List(Element(a)) { 42 39 [ 43 - html.div( 44 - [ 45 - attr.class( 46 - "drop-shadow-md text-xl rounded-xl p-4 mb-4 bg-gray-200 dark:bg-neutral-800 dark:text-neutral-200", 47 - ), 48 - ], 49 - [ 50 - html.div([attr.class("flex items-center align-middle gap-4")], [ 51 - html.h1([attr.class("text-3xl m-0 text-pink-400")], [ 52 - element.text(post.title), 53 - ]), 54 - html.p([attr.class("text-neutral-500")], [ 40 + html.div([], [ 41 + html.div([], [ 42 + html.h1([style.post_header()], [element.text(post.title)]), 43 + html.span([style.post_info()], [ 44 + html.p([style.post_header()], [ 55 45 element.text("Author: " <> post.author), 56 46 ]), 57 - html.p([attr.class("text-neutral-500")], [ 47 + html.p([style.post_header()], [ 58 48 element.text( 59 - "Date Posted: " <> post.date_posted |> common.date_to_string, 49 + "Date Posted: " 50 + <> post.date_posted 51 + |> common.date_to_string(), 60 52 ), 61 53 ]), 62 54 ]), 63 - html.hr([attr.class("h-px my-4 dark:bg-gray-200 border-0 bg-gray-400")]), 64 - html.div([], [post.content]), 65 - ], 66 - ), 55 + ]), 56 + html.hr([]), 57 + html.div([], [post.content]), 58 + ]), 67 59 ] 68 60 }
+49 -62
src/website/page/project.gleam
··· 4 4 import lustre/element/html 5 5 import website/common.{type Project, Project} 6 6 import website/data/projects 7 + import website/style 7 8 8 9 pub fn view_all(projects: List(Project(a))) -> List(Element(a)) { 9 10 projects 10 - |> projects.sorted 11 + |> projects.sorted() 11 12 |> list.map(fn(project: Project(a)) { 12 - html.ul( 13 - [ 14 - attr.id("project-" <> project.id), 15 - attr.class( 16 - "drop-shadow-md text-xl rounded-xl p-4 mb-4 dark:text-neutral-200 " 17 - <> case project.archived { 18 - True -> "bg-red-300 dark:bg-red-700" 19 - False -> "bg-gray-200 dark:bg-neutral-800" 20 - }, 13 + html.div([attr.id("project-" <> project.id), style.project_card()], [ 14 + html.div([style.project_header()], [ 15 + html.img([attr.src(project.img), style.project_icon()]), 16 + html.h1([style.project_header()], [ 17 + common.link([], project.title, "/projects/" <> project.id, False), 18 + ]), 19 + html.div( 20 + [style.project_icon_link_holder()], 21 + project.links 22 + |> list.map(fn(link) { 23 + html.a( 24 + [ 25 + attr.target("_blank"), 26 + attr.rel("noopener noreferrer"), 27 + attr.href(link.url), 28 + attr.alt(link.title), 29 + style.link_icon(), 30 + ], 31 + [html.img([attr.src(link.img), style.project_icon_link_size()])], 32 + ) 33 + }), 21 34 ), 22 - ], 23 - [ 24 - html.li([attr.class("flex items-center gap-4 pb-1.5")], [ 25 - html.img([ 26 - attr.src(project.img), 27 - attr.class("rounded-xl max-w-16 max-h-16"), 28 - ]), 29 - html.h1( 30 - [ 31 - attr.class( 32 - "text-2xl " 33 - <> case project.archived { 34 - True -> "before:content-['🗂️_']" 35 - False -> "" 36 - }, 37 - ), 38 - ], 39 - [common.link(project.title, "/projects/" <> project.id)], 40 - ), 41 - html.div( 42 - [attr.class("flex items-center gap-2")], 43 - project.links 44 - |> list.map(fn(link) { 45 - html.a([attr.href(link.url), attr.alt(link.title)], [ 46 - html.img([ 47 - attr.src(link.img), 48 - attr.class("rounded-xl max-w-14 max-h-14"), 49 - ]), 50 - ]) 51 - }), 52 - ), 53 - ]), 54 - html.li([], [html.p([], [project.summary])]), 55 - ], 56 - ) 35 + ]), 36 + html.div([], [html.p([], [project.summary])]), 37 + ]) 57 38 }) 58 39 } 59 40 60 41 pub fn view(project: Project(a)) -> List(Element(a)) { 61 42 [ 62 - html.div( 63 - [ 64 - attr.class( 65 - "drop-shadow-md text-xl rounded-xl p-4 mb-4 bg-gray-200 dark:bg-neutral-800 dark:text-neutral-200", 43 + html.div([], [ 44 + html.div([style.project_header()], [ 45 + html.img([attr.src(project.img), style.project_icon()]), 46 + html.h1([], [element.text(project.title)]), 47 + html.div( 48 + [style.project_icon_link_holder()], 49 + project.links 50 + |> list.map(fn(link) { 51 + html.a( 52 + [ 53 + attr.target("_blank"), 54 + attr.rel("noopener noreferrer"), 55 + attr.href(link.url), 56 + attr.alt(link.title), 57 + style.link_icon(), 58 + ], 59 + [html.img([attr.src(link.img), style.project_icon_link_size()])], 60 + ) 61 + }), 66 62 ), 67 - ], 68 - [ 69 - html.div([attr.class("flex items-center gap-4 pb-1.5")], [ 70 - html.img([ 71 - attr.src(project.img), 72 - attr.class("rounded-xl max-w-16 max-h-16"), 73 - ]), 74 - html.h1([attr.class("text-2xl")], [element.text(project.title)]), 75 - ]), 76 - html.p([], [project.summary]), 77 - project.content, 78 - ], 79 - ), 63 + ]), 64 + html.p([], [project.summary]), 65 + project.content, 66 + ]), 80 67 ] 81 68 }
+189
src/website/style.gleam
··· 1 + import lustre/attribute as attr 2 + import lustre/element/html 3 + 4 + pub fn background_color() { 5 + attr.style("background-color", "#f7f1e9") 6 + } 7 + 8 + pub fn body_query() { 9 + html.style( 10 + [attr.attribute("scoped", "")], 11 + " 12 + @media (width > 980px) { 13 + .body-margin { 14 + margin-left: 15vw; 15 + margin-right: 15vw; 16 + } 17 + } 18 + ", 19 + ) 20 + } 21 + 22 + pub fn body_styles() { 23 + attr.styles([#("color", "black"), #("container-type", "size")]) 24 + } 25 + 26 + pub fn navbar() { 27 + attr.styles([ 28 + #("border", "2px solid black"), 29 + #("display", "flex"), 30 + #("border-radius", "0.4em"), 31 + #("padding", "1em 1.5em 1em 1.5em"), 32 + #("gap", "1.5em"), 33 + #("margin-left", "1em"), 34 + #("margin-right", "1em"), 35 + #("align-items", "center"), 36 + ]) 37 + } 38 + 39 + pub fn navbar_right() { 40 + attr.styles([ 41 + #("margin-left", "auto"), 42 + #("display", "flex"), 43 + #("gap", "1.5em"), 44 + ]) 45 + } 46 + 47 + pub fn navitem() { 48 + attr.styles([ 49 + #("text-decoration", "none"), 50 + #("color", "black"), 51 + #("font-weight", "700"), 52 + ]) 53 + } 54 + 55 + pub fn link_icon() { 56 + attr.styles([#("display", "flex"), #("align-items", "center")]) 57 + } 58 + 59 + pub fn icon_size() { 60 + attr.styles([#("max-width", "1.5em")]) 61 + } 62 + 63 + pub fn index_layout_query() { 64 + html.style( 65 + [attr.attribute("scoped", "")], 66 + " 67 + .index-layout { 68 + grid-template-columns: auto auto; 69 + } 70 + 71 + @container (width < 750px) { 72 + .index-layout { 73 + grid-template-columns: auto; 74 + } 75 + } 76 + ", 77 + ) 78 + } 79 + 80 + pub fn index_layout() { 81 + attr.styles([ 82 + #("display", "grid"), 83 + #("justify-items", "center"), 84 + #("gap", "2em"), 85 + ]) 86 + } 87 + 88 + pub fn infobox() { 89 + attr.styles([ 90 + #("display", "grid"), 91 + #("width", "fit-content"), 92 + #("grid-template-columns", "auto auto"), 93 + #("border", "2px solid black"), 94 + #("border-radius", "0.4em"), 95 + #("align-items", "center"), 96 + ]) 97 + } 98 + 99 + pub fn rounded_border() { 100 + attr.styles([#("border", "2px solid black"), #("border-radius", "0.4em")]) 101 + } 102 + 103 + pub fn avatar_size() { 104 + attr.styles([ 105 + #("max-width", "10rem"), 106 + #("max-height", "10rem"), 107 + #("margin", "1em"), 108 + ]) 109 + } 110 + 111 + pub fn infobox_inner() { 112 + attr.styles([#("padding", "1em"), #("border-left", "1px solid black")]) 113 + } 114 + 115 + pub fn link() { 116 + attr.styles([ 117 + #("text-decoration", "underline"), 118 + #("color", "black"), 119 + #("font-variant-emoji", "text"), 120 + ]) 121 + } 122 + 123 + pub fn post_card() { 124 + attr.styles([ 125 + #("border-radius", "0.4em"), 126 + #("border", "2px solid black"), 127 + #("padding", "0.5em 1em 0.5em 1em"), 128 + #("margin-bottom", "1em"), 129 + ]) 130 + } 131 + 132 + pub fn post_header() { 133 + attr.styles([#("margin", "0")]) 134 + } 135 + 136 + pub fn post_info() { 137 + attr.styles([ 138 + #("display", "inline-flex"), 139 + #("gap", "1em"), 140 + #("margin-left", "1em"), 141 + #("font-weight", "700"), 142 + ]) 143 + } 144 + 145 + pub fn footer() { 146 + attr.styles([ 147 + #("margin-left", "1em"), 148 + #("margin-right", "1em"), 149 + #("text-align", "center"), 150 + ]) 151 + } 152 + 153 + pub fn project_card() { 154 + attr.styles([ 155 + #("border-radius", "0.4em"), 156 + #("border", "2px solid black"), 157 + #("padding", "1em 1em 1em 1em"), 158 + #("margin-bottom", "1em"), 159 + ]) 160 + } 161 + 162 + pub fn project_icon() { 163 + attr.styles([ 164 + #("max-width", "4em"), 165 + #("max-height", "4em"), 166 + #("border-radius", "0.4em"), 167 + ]) 168 + } 169 + 170 + pub fn project_header() { 171 + attr.styles([ 172 + #("display", "inline-flex"), 173 + #("gap", "1em"), 174 + #("align-items", "center"), 175 + #("margin", "0"), 176 + ]) 177 + } 178 + 179 + pub fn project_icon_link_size() { 180 + attr.styles([#("max-width", "2em")]) 181 + } 182 + 183 + pub fn project_icon_link_holder() { 184 + attr.styles([ 185 + #("display", "inline-flex"), 186 + #("align-items", "center"), 187 + #("gap", "0.5em"), 188 + ]) 189 + }