Source code + assets for pluie.me

lots of stuff

pluie.me 49cdd265 a96984c6

verified
+742 -230
+8
.envrc
···
··· 1 + # only run some commands if nix is installed 2 + nix="$(command -v nix)" 3 + 4 + if ! has nix_direnv_version || ! nix_direnv_version 2.2.1 && [ -n "$nix" ] ; then 5 + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs=" 6 + fi 7 + 8 + [ -n "$nix" ] && use flake
+2 -1
.gitignore
··· 3 _site 4 .DS_Store 5 src/_cache 6 - deno.lock
··· 3 _site 4 .DS_Store 5 src/_cache 6 + deno.lock 7 + .direnv
+9 -4
_config.ts
··· 5 import pug from "lume/plugins/pug.ts"; 6 import inline from "lume/plugins/inline.ts"; 7 import date from "lume/plugins/date.ts"; 8 9 // remark plugins 10 import remark from "lume/plugins/remark.ts"; ··· 16 import postcss from "lume/plugins/postcss.ts"; 17 import tailwindcss from "lume/plugins/tailwindcss.ts"; 18 import tailwindConfig from "./tailwind.config.ts"; 19 20 const site = lume({ src: "./src" }); 21 - 22 site 23 .use(remark({ remarkPlugins: [emoji, a11yEmoji, smartyPants] })) 24 .use(minify_html()) 25 .use(sass({ includes: "_styles" })) 26 - .use(tailwindcss({ 27 - options: tailwindConfig, 28 - })) 29 .use(imagick()) 30 .use(pug()) 31 .use(inline()) 32 .use(date()) 33 .use(postcss()) 34 .copy("assets", ".") 35 .copy("scripts", "scripts"); 36
··· 5 import pug from "lume/plugins/pug.ts"; 6 import inline from "lume/plugins/inline.ts"; 7 import date from "lume/plugins/date.ts"; 8 + import vento from "./vento-improved.ts"; 9 10 // remark plugins 11 import remark from "lume/plugins/remark.ts"; ··· 17 import postcss from "lume/plugins/postcss.ts"; 18 import tailwindcss from "lume/plugins/tailwindcss.ts"; 19 import tailwindConfig from "./tailwind.config.ts"; 20 + import stripIndent from "npm:strip-indent"; 21 22 const site = lume({ src: "./src" }); 23 site 24 .use(remark({ remarkPlugins: [emoji, a11yEmoji, smartyPants] })) 25 .use(minify_html()) 26 .use(sass({ includes: "_styles" })) 27 + .use( 28 + tailwindcss({ 29 + options: tailwindConfig, 30 + }) 31 + ) 32 .use(imagick()) 33 .use(pug()) 34 .use(inline()) 35 .use(date()) 36 .use(postcss()) 37 + .use(vento()) 38 + .filter("strip_indent", stripIndent) 39 .copy("assets", ".") 40 .copy("scripts", "scripts"); 41
+4 -10
flake.nix
··· 8 nixpkgs, 9 }: let 10 systems = ["x86_64-linux" "x86_64-darwin"]; 11 - forAllSystems = f: nixpkgs.lib.genAttrs systems f; 12 - nixpkgsFor = forAllSystems (system: 13 - import nixpkgs { 14 - inherit system; 15 - }); 16 in { 17 - devShells = forAllSystems (system: { 18 - default = nixpkgsFor.${system}.mkShell { 19 - buildInputs = with nixpkgsFor.${system}; [ 20 - deno 21 - ]; 22 }; 23 }); 24 };
··· 8 nixpkgs, 9 }: let 10 systems = ["x86_64-linux" "x86_64-darwin"]; 11 + forAllSystems = f: nixpkgs.lib.genAttrs systems (s: f (import nixpkgs {system = s;})); 12 in { 13 + devShells = forAllSystems (pkgs: { 14 + default = pkgs.mkShell { 15 + buildInputs = [pkgs.deno]; 16 }; 17 }); 18 };
+1 -1
import_map.json
··· 1 { 2 "imports": { 3 - "lume/": "https://deno.land/x/lume@v1.18.0/" 4 } 5 }
··· 1 { 2 "imports": { 3 + "lume/": "https://deno.land/x/lume@v1.18.4/" 4 } 5 }
-16
src/404.pug
··· 1 - --- 2 - layout: layouts/default.pug 3 - title: "404" 4 - description: Oooops... page not found! 5 - 6 - url: /404.html 7 - centered: true 8 - --- 9 - include _includes/comps/button 10 - 11 - h1.title.is-1 404 🦀 12 - h2.subtitle.is-4 Oops! 13 - p The page you’re looking for doesn’t exist. Sorry ’bout that! :p 14 - 15 - +button("/", "_self").is-primary 16 - span Back to main page
···
+20
src/404.vto
···
··· 1 + --- 2 + layout: layouts/default.vto 3 + title: "404" 4 + doNotRenderTitle: true 5 + description: Oooops... page not found! 6 + 7 + url: /404.html 8 + --- 9 + 10 + <div class="flex flex-col text-center place-content-center place-items-center h-main-screen"> 11 + <h1 class="text-6xl font-bold mb-4">404 🦀</h1> 12 + <h2 class="text-3xl mb-4">Oops!</h2> 13 + {{ filter strip_indent |> md }} 14 + The page you're looking for doesn't exist. Sorry 'bout that! :p 15 + {{ /filter }} 16 + 17 + <a class="button rounded-full mt-8 px-10 py-3 bg-brand" href="/" target="_self"> 18 + Back to main page 19 + </a> 20 + </div>
+14 -8
src/_data.yml
··· 1 - layout: layouts/default.pug 2 3 switches: 4 email: email-modal ··· 16 - text: YouTube 17 link: https://youtube.com/@pluiedev 18 icon: si-youtube 19 - color: white 20 21 - text: pronouns.page 22 link: https://pronouns.page/@pluiedev 23 - icon: 24 - src: /icons/pronouns-page.svg 25 - alt: pronouns.page logo 26 27 - - text: Discord Server 28 link: https://discord.gg/NeNfePzCx8 29 icon: si-discord 30 - color: 7781f6 31 32 - text: Mastodon 33 link: https://blobfox.coffee/@pluie 34 icon: si-mastodon 35 - color: 7781f6 36 37 - text: Bilibili 38 link: https://space.bilibili.com/401096522 39 icon: si-bilibili 40 41 technologies: 42 - title: I use proficiently
··· 1 + layout: layouts/default.vto 2 3 switches: 4 email: email-modal ··· 16 - text: YouTube 17 link: https://youtube.com/@pluiedev 18 icon: si-youtube 19 + color: youtube 20 21 - text: pronouns.page 22 link: https://pronouns.page/@pluiedev 23 + icon: /icons/pronouns-page.svg 24 + color: pronouns-page 25 26 + - text: Discord 27 link: https://discord.gg/NeNfePzCx8 28 icon: si-discord 29 + color: discord 30 31 - text: Mastodon 32 link: https://blobfox.coffee/@pluie 33 icon: si-mastodon 34 + color: mastodon 35 36 - text: Bilibili 37 link: https://space.bilibili.com/401096522 38 icon: si-bilibili 39 + color: bilibili 40 + 41 + - text: GitHub 42 + link: https://github.com/pluiedev 43 + icon: si-github 44 + color: zinc-200 45 + fg: black 46 47 technologies: 48 - title: I use proficiently
+23
src/_includes/comps/icon-grid.vto
···
··· 1 + {{ export function icon_grid(icons) }} 2 + <div class="grid grid-cols-5 gap-2"> 3 + {{ for id, lang of icons }} 4 + {{ if typeof lang === "string" }} 5 + {{ devicon(id, lang) }} 6 + {{ else }} 7 + {{> const { name, light, dark } = lang }} 8 + {{ devicon(id, name, light, dark) }} 9 + {{ /if }} 10 + {{ /for }} 11 + </div> 12 + {{ /export }} 13 + 14 + {{ function devicon(id, name, light, dark) }} 15 + {{ set parts = [id, light, dark].filter(Boolean).join("/") }} 16 + {{ set style = `"--devicon: url('https://cdn.simpleicons.org/${parts}');"` }} 17 + <div 18 + class="devicon" 19 + style={{ style }} 20 + data-tooltip={{ name }} 21 + ></div> 22 + {{ /function }} 23 +
+1 -1
src/_includes/layouts/base.pug
··· 1 doctype html 2 html(lang="en") 3 include ../parts/head 4 - body.pt-16(class="dark:bg-zinc-900 dark:text-zinc-200") 5 //- include ../parts/email-modal 6 include ../parts/navbar 7 block content
··· 1 doctype html 2 html(lang="en") 3 include ../parts/head 4 + body.pt-navbar(class="dark:bg-zinc-900 dark:text-zinc-200") 5 //- include ../parts/email-modal 6 include ../parts/navbar 7 block content
+11
src/_includes/layouts/base.vto
···
··· 1 + <!DOCTYPE html> 2 + 3 + <html lang="en"> 4 + {{ include "parts/head.vto" }} 5 + <body class="pt-navbar bg-bg text-fg"> 6 + {{ include "parts/navbar.vto" }} 7 + {{ content }} 8 + {{ include "parts/footer.vto" }} 9 + </body> 10 + </html> 11 +
+12
src/_includes/layouts/default.vto
···
··· 1 + {{ layout "layouts/base.vto" }} 2 + 3 + <main class="py-20 max-w-screen-lg mx-auto sm:py-10"> 4 + {{ if it.title && !it.doNotRenderTitle }} 5 + <h1 class="text-5xl font-bold my-20">{{ title }}</h1> 6 + {{ /if }} 7 + 8 + {{ content }} 9 + </main> 10 + 11 + {{ /layout }} 12 +
+2 -2
src/_includes/parts/footer.pug
··· 9 footer.bg-brand-darker.p-12.pb-20.text-center.prose.max-w-none.prose-invert(class="prose-a:text-blue-300 dark:prose-a:text-brand dark:bg-zinc-900 dark:border-t-2 dark:border-t-brand-dark") 10 p © #{yearText} Leah Amelia “pluie” Chen 11 :md 12 - Source code is licensed under [the Mozilla Public License](https://www.mozilla.org/MPL) 13 and available on [GitHub](https://github.com/pluiedev/site).<br/> 14 Content is licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). 15 16 - Made with [Bulma](https://bulma.io), 💜, caffeine, and estrogen pills.
··· 9 footer.bg-brand-darker.p-12.pb-20.text-center.prose.max-w-none.prose-invert(class="prose-a:text-blue-300 dark:prose-a:text-brand dark:bg-zinc-900 dark:border-t-2 dark:border-t-brand-dark") 10 p © #{yearText} Leah Amelia “pluie” Chen 11 :md 12 + Source code is licensed under the [Mozilla Public License](https://www.mozilla.org/MPL) 13 and available on [GitHub](https://github.com/pluiedev/site).<br/> 14 Content is licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). 15 16 + Made with [Lume](https://lume.land), [Tailwind CSS](https://tailwindcss.com), 💜, caffeine, and estrogen pills.
+15
src/_includes/parts/footer.vto
···
··· 1 + {{ set currentYear = new Date().getFullYear() }} 2 + {{ set firstYear = 2022 }} 3 + {{ set yearText = currentYear > firstYear ? `${firstYear}–${currentYear}` : `${firstYear}` }} 4 + 5 + <footer class="p-12 pb-20 text-center bg-brand-darker dark:bg-zinc-900 prose prose-invert max-w-none prose-a:text-blue-300 dark:prose-a:text-brand dark:border-t-2 dark:border-t-brand-dark"> 6 + {{ filter strip_indent |> md }} 7 + © {{yearText}} Leah Amelia “pluie” Chen 8 + 9 + Source code is licensed under the [Mozilla Public License](https://www.mozilla.org/MPL) 10 + and available on [GitHub](https://github.com/pluiedev/site).<br> 11 + Content is licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). 12 + 13 + Made with [Lume](https://lume.land), [Tailwind CSS](https://tailwindcss.com), 💜, caffeine, and estrogen pills. 14 + {{ /filter }} 15 + </footer>
+32
src/_includes/parts/head.vto
···
··· 1 + <head> 2 + <meta charset="utf-8" /> 3 + 4 + <title>{{it.title}} | pluie.me</title> 5 + 6 + <meta name="viewport" content="width=device-width, initial-scale=0.75" /> 7 + <meta name="description" content={{it.description}} /> 8 + <meta name="theme-color" content="#d23773" /> 9 + <meta property="og:title" content={{it.title}} /> 10 + <meta property="og:description" content={{it.description}} /> 11 + <meta property="og:url" content={{`https://pluie.me${url}`}} /> 12 + 13 + {{ for prop, val of it.opengraph }} 14 + {{ if Array.isArray(val) }} 15 + {{ for cont of val }} 16 + <meta property={{prop}} content={{cont}} /> 17 + {{ /for }} 18 + {{ else }} 19 + <meta property={{prop}} content={{val}} /> 20 + {{ /if }} 21 + {{ /for }} 22 + 23 + <link rel="preconnect" href="https://fonts.bunny.net" /> 24 + <link 25 + rel="icon" 26 + type="image/png" 27 + sizes="250x250" 28 + href="/img/avatar-small.jpg" 29 + /> 30 + 31 + <link rel="stylesheet" href="/style.css" /> 32 + </head>
+38
src/_includes/parts/navbar.vto
···
··· 1 + <nav class="top-0 fixed z-30 bg-navbar-bg flex h-navbar min-w-full border-b-2 border-b-navbar-border text-navbar-fg"> 2 + <input id="navbar-toggle" class="hidden" type="checkbox" aria-hidden="true" /> 3 + 4 + <a class="navbar-tab navbar-brand" href="/" title="pluie.me"></a> 5 + 6 + {{# 7 + //- label.navbar-burger(for="navbar-toggle" role="button" aria-label="menu") 8 + //- //- Three empty spans here for rendering the burger icon 9 + //- span(aria-hidden="true") 10 + //- span(aria-hidden="true") 11 + //- span(aria-hidden="true") 12 + #}} 13 + 14 + {{ for category of categories }} 15 + <a class="navbar-tab" href={{ category.url}} title={{category.title }}> 16 + {{ category.title }} 17 + </a> 18 + {{ /for }} 19 + 20 + <label class="navbar-dropdown" for={{ switches.contacts }} tabindex="0" role="button"> 21 + <input id={{ switches.contacts }} type="checkbox" class="navbar-dropdown-toggle" /> 22 + 23 + <span class="navbar-dropdown-label navbar-tab">Contact / Links</span> 24 + 25 + <div class="navbar-dropdown-items bg-white border-t-2 rounded-b-lg shadow-md shadow-black/10 dark:bg-brand-darker dark:border-t-brand-darkest"> 26 + {{ for link of links }} 27 + <a class="navbar-item flex items-center px-4 py-3 whitespace-nowrap" href={{ link.link }} target="_blank"> 28 + {{# icon #}} 29 + <span>{{ link.text }}</span> 30 + </a> 31 + {{ /for }} 32 + <label class="navbar-item flex items-center px-4 py-3 whitespace-nowrap" tabindex="0" role="button" for={{ switches.email }}> 33 + {{# icon #}} 34 + <span>Email</span> 35 + </label> 36 + </div> 37 + </label> 38 + </nav>
+2
src/_styles/components/_index.scss
··· 1 @import "devicon"; 2 //@import "display"; 3 //@import "dynamic-button"; 4 //@import "langs"; 5 @import "link"; 6 //@import "message"; 7 @import "navbar"; 8 //@import "post"; 9 //@import "toggleable-modal"; 10 @import "tooltip";
··· 1 @import "devicon"; 2 //@import "display"; 3 //@import "dynamic-button"; 4 + @import "icon"; 5 //@import "langs"; 6 @import "link"; 7 //@import "message"; 8 @import "navbar"; 9 //@import "post"; 10 //@import "toggleable-modal"; 11 + @import "splash"; 12 @import "tooltip";
+1 -2
src/_styles/components/devicon.scss
··· 1 .devicon { 2 - @apply relative rounded-lg border-zinc-300 dark:border-zinc-700 border-2; 3 4 &::before { 5 content: ""; ··· 8 background: no-repeat center/75% var(--devicon); 9 } 10 } 11 -
··· 1 .devicon { 2 + @apply relative rounded-lg border-fg/30 border-2; 3 4 &::before { 5 content: ""; ··· 8 background: no-repeat center/75% var(--devicon); 9 } 10 }
+11
src/_styles/components/icon.scss
···
··· 1 + .has-icon { 2 + display: flex; 3 + align-items: center; 4 + column-gap: 0.5rem; 5 + 6 + &::before { 7 + @apply inline-block h-3/4 aspect-square; 8 + content: ""; 9 + background-image: var(--icon); 10 + } 11 + }
+1 -1
src/_styles/components/link.scss
··· 1 @use "sass:color"; 2 3 - a:not(.navbar-tab, [disabled], .pagination-link) { 4 // adapted from https://stackoverflow.com/a/72459455 5 @extend .bg-transition; 6 @apply text-brand ease-out bg-no-repeat bg-right-bottom bg-gradient-to-r from-transparent to-brand-dark to-0%;
··· 1 @use "sass:color"; 2 3 + a:not(.button, .navbar-tab, [disabled], .pagination-link) { 4 // adapted from https://stackoverflow.com/a/72459455 5 @extend .bg-transition; 6 @apply text-brand ease-out bg-no-repeat bg-right-bottom bg-gradient-to-r from-transparent to-brand-dark to-0%;
+1 -6
src/_styles/components/navbar.scss
··· 13 // } 14 //} 15 16 - .navbar { 17 - @apply top-0 fixed z-30 bg-brand flex justify-center h-navbar min-w-full border-b-2 border-b-brand-dark text-white dark:bg-zinc-900 dark:border-b-brand; 18 - } 19 - 20 .navbar-brand { 21 - @extend .navbar-tab; 22 @apply w-32; 23 24 background: url('/icons/wordmark.svg') no-repeat center, linear-gradient(to top, var(--tw-gradient-stops)) no-repeat bottom !important; ··· 27 28 .navbar-tab { 29 @extend .bg-transition; 30 - @apply inline-flex h-full items-center px-3 ease-out bg-no-repeat bg-bottom bg-gradient-to-t from-transparent to-brand-dark dark:to-brand-darker to-0%; 31 32 background-size: auto calc(var(--bg-transition) * 100%); 33 }
··· 13 // } 14 //} 15 16 .navbar-brand { 17 @apply w-32; 18 19 background: url('/icons/wordmark.svg') no-repeat center, linear-gradient(to top, var(--tw-gradient-stops)) no-repeat bottom !important; ··· 22 23 .navbar-tab { 24 @extend .bg-transition; 25 + @apply inline-flex h-full items-center px-3 ease-out text-navbar-fg bg-no-repeat bg-bottom bg-gradient-to-t from-transparent to-navbar-border to-0%; 26 27 background-size: auto calc(var(--bg-transition) * 100%); 28 }
+44
src/_styles/components/splash.scss
···
··· 1 + // Like that fancy expanding/contracting splash screen I have in the main page? 2 + // Now you can have it too! Just uh... this is not really that adaptable 3 + // Good luck!!! 4 + // JavaScript *is* required. There's no way you can do this in CSS without some 5 + // seriously evil crimes 6 + 7 + .splash { 8 + padding-top: 0; 9 + transition: padding 0.5s ease-out; 10 + 11 + .intro { 12 + padding: calc(50vh - 3 * 4rem) 0; 13 + transition: padding 0.5s ease-out; 14 + } 15 + 16 + &.compact { 17 + padding-top: calc(2.5rem + 3 * 4rem); 18 + .intro { 19 + padding: 0; 20 + } 21 + } 22 + 23 + .scroll-down { 24 + @apply text-fg-dimmed transition absolute; 25 + bottom: 3rem; 26 + animation: 0.75s infinite alternate bob; 27 + 28 + &::after { 29 + @apply mx-auto block h-6 w-6 border-2 border-fg rounded-sm border-l-0 border-t-0 origin-center; 30 + transform: scale(1.5, 1.25) rotate(45deg); 31 + content: ""; 32 + } 33 + } 34 + .bottom-detector { 35 + position: absolute; 36 + bottom: -5rem; 37 + } 38 + } 39 + 40 + @keyframes bob { 41 + from { transform: translateY(0px); } 42 + to { transform: translateY(10px); } 43 + } 44 +
+1 -1
src/_styles/components/tooltip.scss
··· 3 4 &::after { 5 content: attr(data-tooltip); 6 - @apply hidden absolute text-sm text-center inline-block align-middle bg-white/90 dark:bg-zinc-900/80; 7 @apply p-1 rounded-md border-[1px] border-brand min-w-fit -inset-x-2 bottom-[90%] z-20; 8 } 9 &:hover {
··· 3 4 &::after { 5 content: attr(data-tooltip); 6 + @apply hidden absolute text-sm text-center inline-block align-middle bg-bg/80; 7 @apply p-1 rounded-md border-[1px] border-brand min-w-fit -inset-x-2 bottom-[90%] z-20; 8 } 9 &:hover {
+1 -1
src/assets/icons/pronouns-page.svg
··· 1 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 650 650"><path d="M396.52 174.35c1.35-2.4.21-4.35-2.54-4.35l-48.2.03c-2.75 0-6.15 1.94-7.54 4.31l-118.1 199.77c-16.48 27.15-39.48 33.15-61.58 30.47-37.94-4.6-58.34-32.45-58.34-69.54 0-37.25 30.31-67.56 67.56-67.56h75c2.75 0 6.12-1.95 7.48-4.34l27.03-47.2c1.37-2.39.23-4.34-2.52-4.34h-107c-68.06 0-123.44 55.37-123.44 123.44 0 32.89 12.85 68.36 36.22 91.54 23.03 22.84 53.8 31.21 86.64 31.89 18.54.21 69.46-.21 93.33-42.68 26.73-47.57 136-241.44 136-241.44zM571.94 244.44c-23.03-22.84-53.8-31.21-86.64-31.89-18.54-.21-69.46.21-93.33 42.68-26.72 47.55-136 241.42-136 241.42-1.35 2.4-.21 4.35 2.54 4.35l48.2-.03c2.75 0 6.15-1.94 7.54-4.31l118.1-199.77c16.48-27.15 39.48-33.15 61.58-30.47 37.94 4.6 58.34 32.45 58.34 69.54 0 37.25-30.31 67.56-67.56 67.56h-75c-2.75 0-6.12 1.95-7.48 4.34l-27.03 47.2c-1.37 2.39-.23 4.34 2.52 4.34h107c68.06 0 123.44-55.37 123.44-123.44 0-32.87-12.85-68.34-36.22-91.52z" fill="#c71585"/></svg>
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 650 650"><path d="M396.52 174.35c1.35-2.4.21-4.35-2.54-4.35l-48.2.03c-2.75 0-6.15 1.94-7.54 4.31l-118.1 199.77c-16.48 27.15-39.48 33.15-61.58 30.47-37.94-4.6-58.34-32.45-58.34-69.54 0-37.25 30.31-67.56 67.56-67.56h75c2.75 0 6.12-1.95 7.48-4.34l27.03-47.2c1.37-2.39.23-4.34-2.52-4.34h-107c-68.06 0-123.44 55.37-123.44 123.44 0 32.89 12.85 68.36 36.22 91.54 23.03 22.84 53.8 31.21 86.64 31.89 18.54.21 69.46-.21 93.33-42.68 26.73-47.57 136-241.44 136-241.44zM571.94 244.44c-23.03-22.84-53.8-31.21-86.64-31.89-18.54-.21-69.46.21-93.33 42.68-26.72 47.55-136 241.42-136 241.42-1.35 2.4-.21 4.35 2.54 4.35l48.2-.03c2.75 0 6.15-1.94 7.54-4.31l118.1-199.77c16.48-27.15 39.48-33.15 61.58-30.47 37.94 4.6 58.34 32.45 58.34 69.54 0 37.25-30.31 67.56-67.56 67.56h-75c-2.75 0-6.12 1.95-7.48 4.34l-27.03 47.2c-1.37 2.39-.23 4.34 2.52 4.34h107c68.06 0 123.44-55.37 123.44-123.44 0-32.87-12.85-68.34-36.22-91.52z" fill="#fff"/></svg>
-99
src/index.pug
··· 1 - --- 2 - title: Heya~ 3 - description: I'm Leah, also known as pluie or pluiedev! 4 - 5 - date: Git Last Modified 6 - doNotRenderTitle: true 7 - opengraph: 8 - og:type: profile 9 - profile:first_name: Leah 10 - profile:last_name: Chen 11 - profile:username: pluie 12 - profile:gender: female 13 - --- 14 - 15 - include _includes/comps/devicon.pug 16 - 17 - .grid.gap-20.mb-16(class="grid-cols-[20rem_auto_20rem] grid-rows-[calc(100vh-3*4rem)]") 18 - figure.place-self-center 19 - img.rounded-full.border-2.border-brand(src="/img/avatar.webp" alt="Avatar" width="320") 20 - .col-span-2.place-self-center 21 - h1.text-5xl.font-bold.mt-4 22 - | Heya~ I’m #[span.text-brand Leah]! 23 - span.text-xl 24 - span.mx-2 · 25 - a(href="https://pronouns.page/@pluiedev") she/her 🏳️‍⚧️ 26 - 27 - p.text-2xl.mt-4. 28 - You may know me by my online aliases, 29 - #[span.text-brand pluie] or #[span.text-brand pluiedev]. 30 - 31 - .prose.text-justify.col-span-2(class="dark:prose-invert"): :md 32 - I'm a Chinese open-source developer 👩🏼‍💻, 33 - community manager 🔨👷🏼‍♀️, 34 - graphics designer and artist 👩🏼‍🎨, 35 - dedicated to making the world of digital technology and design accessible and 36 - inclusive for everyone. 37 - 38 - I love coding, drawing, painting, discovering more about myself, and helping others discover and express themselves. 39 - Funnily enough, I do most of my productive work in my spare time when I'm supposed to relax—apparently, 40 - once I get fixated on something, I can't stop. Not sure if that's a good quality or not! ;) 41 - 42 - More things I like include dogs, traffic cones, history, politics and linguistics. 43 - Especially <abbr title="constructed languages, or languages intentionally created by people">conlangs</abbr>. 44 - They're _very_ cool. 45 - 46 - I also often think way too hard about narrative video games, to the point 47 - I write full articles and reviews about them. I'm not exactly professional, 48 - but I still try my best to figure out what does and doesn't tick! 49 - 50 - div 51 - 52 - .text-centered.w-full(class="sm:text-left") 53 - h2.text-2xl.font-bold Technologies that... 54 - 55 - .flex.divide-y.gap-4(class="sm:flex-col") 56 - each techs in technologies 57 - div 58 - h3.text-xl.my-3= techs.title 59 - +devicons(techs.icons) 60 - 61 - .prose.text-justify.col-span-2(class="dark:prose-invert") 62 - h2(class="!mb-0") Current endeavors 63 - p Up-to-date as of: #[em= filters.date(date, "HUMAN_DATE")] 64 - 65 - :md 66 - Currently, I spend most of my attention and energy on two things: 67 - community management and moderation, and contributing to and creating various 68 - open-source projects. 69 - 70 - As one of the [community managers](https://quiltmc.org/about/teams/#community-managers) 71 - of [the Quilt Project](https://quiltmc.org), a modding toolchain project primarily designed for Minecraft, 72 - I work on policies, guidelines and tooling that drives Quilt towards its goal of creating 73 - a healthy, diverse, multicultural community striving to accomodate all sorts of minorities, 74 - be they ethnic or racial minorities, sexual and/or gender-nonconfirming minorities, neurodivergent minorities, 75 - or any other group of people facing various challenges and adversities in world society. 76 - 77 - My unique identity as a queer, lesbian Chinese trans woman with an international, 78 - multicultural mindset provides me with ample amounts of lived experience with 79 - diversifying communities beyond the dominant Western, mostly Anglophone, 80 - predominantly white, cis- and heteronormative culture, 81 - which is unfortunately still the sole major cultural environment in most tech-focused communities. 82 - I hope that, through my efforts, heterogeneous people from diverse backgrounds can find 83 - communities that I manage comfortable, and be free from having to mask their cultural heritage, 84 - like when I first ventured into international technical communities. 85 - 86 - As an open-source contributor, I occasionally submit patches and pull requests to 87 - projects I find interesting, mostly in Rust, TypeScript and Kotlin. 88 - I also maintain Quilt's community tooling, mainly [its official website](https://quiltmc.org) 89 - and its [Developer Wiki](https://modder.wiki.quiltmc.org/), and sometimes its own 90 - Discord bot called [Cozy](https://github.com/QuiltMC/cozy-discord). 91 - I also started some other random projects, one of which is a 92 - [Rust rewrite of the `alien` tool](https://github.com/pluiedev/alien), 93 - originally a Perl utility script that converts Linux software packages between various formats 94 - (e.g.: `.deb`, `.rpm`, `.tgz`, ...) for different package managers (e.g.: `dpkg`/`apt`, `rpm`/`dnf`, ...) 95 - 96 - I'm almost always coding something new, for existing projects or otherwise, 97 - and I can't wait to show you more! For now, you'll just have to wait for me to update this 98 - portfolio again sometime in the future... 99 -
···
+177
src/index.vto
···
··· 1 + --- 2 + layout: layouts/base.vto 3 + title: Heya~ 4 + description: I'm Leah, also known as pluie or pluiedev! 5 + 6 + date: Git Last Modified 7 + doNotRenderTitle: true 8 + opengraph: 9 + og:type: profile 10 + profile:first_name: Leah 11 + profile:last_name: Chen 12 + profile:username: pluie 13 + profile:gender: female 14 + --- 15 + {{ import { icon_grid } from "comps/icon-grid.vto" }} 16 + 17 + <link rel="stylesheet" href="main.css" /> 18 + 19 + <div class="top-detector"></div> 20 + <main class="grid splash justify-items-center gap-x-16 gap-y-10 mx-auto max-w-screen-lg mb-16 grid-cols-main-screen"> 21 + <figure class="intro place-self-center"> 22 + <img class="rounded-full border-2 border-brand" src="/img/avatar.webp" alt="Avatar" width="320" height="320" /> 23 + </figure> 24 + 25 + <div class="intro col-span-2 place-self-center flex flex-col gap-y-4"> 26 + <h1 class="text-5xl font-bold"> 27 + Heya~ I’m <span class="text-brand">Leah</span>! 28 + <span class="text-xl"> 29 + <span class="mx-2">·</span> 30 + <a href="https://pronouns.page/@pluiedev">she/her 🏳️‍⚧️</a> 31 + </span> 32 + </h1> 33 + 34 + <p class="text-2xl"> 35 + You may know me by my online aliases, 36 + <span class="text-brand">pluie</span> or 37 + <span class="text-brand">pluiedev</span>. 38 + </p> 39 + <p class="text-xl"> 40 + I'm a Chinese open-source <span class="text-brand">developer 👩🏼‍💻</span>, 41 + community <span class="text-brand">manager 🔨👷🏼‍♀️</span>, 42 + graphics designer and <span class="text-brand">artist 👩🏼‍🎨</span>, 43 + dedicated to making the world of technology and design accessible and 44 + inclusive for <span class="text-brand">everyone</span>. 45 + </p> 46 + </div> 47 + 48 + <div class="scroll-down"> 49 + Scroll down 50 + <div class="bottom-detector"></div> 51 + </div> 52 + 53 + <div id="scroll-indicator-vanish-point" class="prose text-justify col-span-2 dark:prose-invert"> 54 + {{ filter strip_indent |> md }} 55 + I love coding, drawing, painting, discovering more about myself, and helping others discover and express themselves. 56 + Funnily enough, I do most of my productive work in my spare time when I'm supposed to relax—apparently, 57 + once I get fixated on something, I can't stop. Not sure if that's a good quality or not! ;) 58 + 59 + More things I like include dogs, traffic cones, history, politics and linguistics. 60 + Especially <abbr title="constructed languages, or languages intentionally created by people">conlangs</abbr>. 61 + They're _very_ cool. 62 + 63 + I also often think way too hard about narrative video games, to the point 64 + I write full articles and reviews about them. I'm not exactly professional, 65 + but I still try my best to figure out what does and doesn't tick! 66 + {{ /filter }} 67 + </div> 68 + 69 + <div class="flex flex-col w-full place-content-center gap-2"> 70 + {{ for link of links }} 71 + {{ set fg = link.fg ?? "white" }} 72 + {{ if link.icon.startsWith("si-") }} 73 + {{ set icon = `https://cdn.simpleicons.org/${link.icon.substring(3)}/${fg}` }} 74 + {{ else }} 75 + {{ set icon = link.icon }} 76 + {{ /if }} 77 + 78 + <a 79 + href={{ link.link }} 80 + class="button arrow-button has-icon bg-{{ link.color }} py-2 px-3 text-{{ fg }}" 81 + style="--icon: url({{ icon }})" 82 + > 83 + {{ link.text }} 84 + </a> 85 + {{ /for }} 86 + </div> 87 + 88 + <div class="text-centered w-full sm:text-left"> 89 + <h2 class="text-2xl font-bold">Technologies that...</h2> 90 + 91 + <div class="flex divide-y gap-4 sm:flex-col"> 92 + {{ for techs of technologies }} 93 + <div> 94 + <h3 class="text-xl my-3">{{ techs.title }}</h3> 95 + {{ icon_grid(techs.icons) }} 96 + </div> 97 + {{ /for }} 98 + </div> 99 + </div> 100 + 101 + <div class="prose text-justify col-span-2 dark:prose-invert"> 102 + <h2 class="!mb-0">Current endeavors</h2> 103 + <p>Up-to-date as of: <em>{{ it.date |> date("HUMAN_DATE") }}</em></p> 104 + 105 + {{ filter strip_indent |> md }} 106 + Currently, I spend most of my attention and energy on two things: 107 + community management and moderation, and contributing to and creating various 108 + open-source projects. 109 + 110 + As one of the [community managers](https://quiltmc.org/about/teams/#community-managers) 111 + of [the Quilt Project](https://quiltmc.org), a modding toolchain project primarily designed for Minecraft, 112 + I work on policies, guidelines and tooling that drives Quilt towards its goal of creating 113 + a healthy, diverse, multicultural community striving to accomodate all sorts of minorities, 114 + be they ethnic or racial minorities, sexual and/or gender-nonconfirming minorities, neurodivergent minorities, 115 + or any other group of people facing various challenges and adversities in world society. 116 + 117 + My unique identity as a queer, lesbian Chinese trans woman with an international, 118 + multicultural mindset provides me with ample amounts of lived experience with 119 + diversifying communities beyond the dominant Western, mostly Anglophone, 120 + predominantly white, cis- and heteronormative culture, 121 + which is unfortunately still the sole major cultural environment in most tech-focused communities. 122 + I hope that, through my efforts, heterogeneous people from diverse backgrounds can find 123 + communities that I manage comfortable, and be free from having to mask their cultural heritage, 124 + like when I first ventured into international technical communities. 125 + 126 + As an open-source contributor, I occasionally submit patches and pull requests to 127 + projects I find interesting, mostly in Rust, TypeScript and Kotlin. 128 + I also maintain Quilt's community tooling, mainly [its official website](https://quiltmc.org) 129 + and its [Developer Wiki](https://modder.wiki.quiltmc.org/), and sometimes its own 130 + Discord bot called [Cozy](https://github.com/QuiltMC/cozy-discord). 131 + I also started some other random projects, one of which is a 132 + [Rust rewrite of the \`alien\` tool](https://github.com/pluiedev/alien), 133 + originally a Perl utility script that converts Linux software packages between various formats 134 + (e.g.: \`.deb\`, \`.rpm\`, \`.tgz\`, ...) for different package managers (e.g.: \`dpkg\`/\`apt\`, \`rpm\`/\`dnf\`, ...) 135 + 136 + I'm almost always coding something new, for existing projects or otherwise, 137 + and I can't wait to show you more! For now, you'll just have to wait for me to update this 138 + portfolio again sometime in the future... 139 + {{ /filter }} 140 + </div> 141 + </main> 142 + 143 + <script> 144 + const scroll = document.querySelector('.scroll-down'); 145 + const intro = document.querySelector('main'); 146 + 147 + const t = new IntersectionObserver(entries => { 148 + entries.forEach(entry => { 149 + if (entry.isIntersecting) { 150 + scroll.classList.remove('opacity-0'); 151 + intro.classList.remove('compact'); 152 + } 153 + }); 154 + }); 155 + const b = new IntersectionObserver(entries => { 156 + entries.forEach(entry => { 157 + if (entry.isIntersecting) { 158 + scroll.classList.add('opacity-0'); 159 + intro.classList.add('compact'); 160 + } 161 + }); 162 + }); 163 + 164 + function init(ev) { 165 + if (window.innerHeight < 600) { 166 + scroll.classList.add('opacity-0'); 167 + t.disconnect(); 168 + b.disconnect(); 169 + } else { 170 + t.observe(document.querySelector('.top-detector')); 171 + b.observe(document.querySelector('.bottom-detector')); 172 + } 173 + } 174 + 175 + window.addEventListener('resize', init) 176 + window.addEventListener('load', init) 177 + </script>
+14
src/main.scss
···
··· 1 + .arrow-button { 2 + @apply relative; 3 + 4 + &::after { 5 + @apply absolute w-0 h-0 right-0 border-bg border-l-transparent; 6 + content: ""; 7 + transition: border-width 0.25s; 8 + border-width: 1.25rem 1.5rem 1.25rem 1rem; 9 + } 10 + 11 + &:hover::after { 12 + border-right-width: 0rem; 13 + } 14 + }
+29
src/style.scss
··· 2 @tailwind components; 3 @tailwind utilities; 4 5 @layer base { 6 } 7
··· 2 @tailwind components; 3 @tailwind utilities; 4 5 + :root { 6 + // Default dark theme "Neon" 7 + 8 + --brand: 210 55 115; // #d23773 9 + --brand-dark: 186 44 99; // #ba2c63 10 + --brand-darker: 133 30 70; // #851e46 11 + --brand-darkest: 87 18 45; // #57122d 12 + 13 + --bg: 24 24 27; // zinc-900 14 + 15 + --navbar-bg: var(--bg); 16 + --navbar-fg: 255 255 255; // white 17 + --navbar-border: var(--brand); 18 + 19 + --fg: 212 212 216; // zinc-200 20 + } 21 + @media (prefers-color-scheme: light) { 22 + :root { 23 + // Default light theme "Strawberry Marshmellow" 24 + 25 + --bg: 255 255 255; // white 26 + --navbar-bg: var(--brand); 27 + --navbar-border: var(--brand-dark); 28 + 29 + --fg: 24 24 27; // zinc-900 30 + --fg-dimmed: 63 63 70; // zinc-700 31 + } 32 + } 33 + 34 @layer base { 35 } 36
+120 -57
src/works.pug src/works.vto
··· 1 --- 2 title: Works 3 description: Projects, artworks, and other things I've done... 4 --- 5 6 include _includes/comps/icon 7 include _includes/comps/modal 8 9 - h2.title.is-3 Projects 10 11 - :md 12 Under construction! :purple_heart: 13 (Yeah, I know, it's been ages, gimme some more time) 14 15 - h2.title.is-3 Artworks 16 17 - mixin display(id, title, imgid) 18 - -const { height, width, aspectRatio, ...rest } = attributes; 19 20 - figure.display&attributes(rest) 21 - label(for=id role="button" tabindex="0") 22 - img( 23 - src=`https://imagedelivery.net/TLP_u-wyyvTEPKkgbA6Osg/${imgid}/public` 24 - alt=title 25 - width=width 26 - height=height 27 - ) 28 - 29 - figcaption 30 - h3.title.is-4= title 31 - block 32 +modal(id) 33 .image.is-fullwidth(class=aspectRatio) 34 img( ··· 37 fetchpriority="low" 38 loading="lazy" 39 ) 40 41 - mixin header(date, medium) 42 - p.subtitle.is-6 43 - span.icon-text 44 - +icon("far fa-calendar") 45 - span= filters.date(date, "PP") 46 - +icon("fas fa-brush") 47 - span= medium 48 49 - +display("tiger", "Ferality", "558e95a4-f685-4dee-32a6-8c33e49d9600").has-frame( 50 - size="medium" aspectRatio="is-square" width=400 height=408 51 - ) 52 - +header("2018-12-02", "Oil on canvas") 53 - :md 54 One of the older works of mine — I think I'd only spent 2 years dabbling with oil paintings back then, 55 and the technical inexpertise definitely shows — but a brazen and unpolished technique does not 56 necessarily hinder expressiveness. ;) 57 - 58 I really wanted to capture the anger in the tiger, and as I exaggerated its features and started furiously 59 laying down sweeping strokes of striking orange, I too experienced the feral fury in me. 60 Wish I could experience that again... 61 62 - +display("altitudinous_blossoms", "Altitudinous blossoms", "170696fd-d9b4-4652-e168-2cc80250d400").has-frame( 63 - size="medium" aspectRatio="is-3by5" rtl width=400 height=660 64 - ) 65 - +header("2021-02-11", "Oil on canvas") 66 - :md 67 A friend of my mother's is a hobbyist photographer, and I think he took the original photo when he went to Tibet to... 68 well, take pictures of the wild flora and fauna and the majestic landscape. 69 (Y'know, like a photographer? Why else would he be there anyway?) ··· 76 I did take a while to figure it out though. I also used a toothbrush to ~~paint~~ *spray* the white dots on, 77 which are actually specks of mint-scented toothpaste and not white paint! 78 Although, they look like they hold up just fine on the canvas. 79 80 - +display("tropical_fish", "A piscine aura in the depths", "278a69c6-ec0a-4461-c3f3-79aa615ad800").has-frame( 81 - size="large" aspectRatio="is-4by3" width=500 height=380 82 - ) 83 - +header("2021-06-05", "Oil on canvas") 84 - :md 85 Some kind of tropical fish whose name I have completely forgotten. It didn't even occur to past-Leah to write these things down! *sigh* 86 87 It's really pretty, especially for its lake-blue scales and blood-red fins! Sure did take a long time to paint though... It was around this time 88 when my style really departed from the raw, emotional expression like *Ferality*, towards a finer sort of aesthetic. 89 90 - +display("red_cotton", "A flower — what's her name again?", "35db3ba1-542a-46aa-2d10-850d34463e00").has-frame( 91 - size="large" aspectRatio="is-4by3" rtl width=500 height=372 92 - ) 93 - +header("2021-12-11", "Oil on canvas") 94 - :md 95 Turns out it's a flower of the tree [*Bombax ceiba*](https://en.wikipedia.org/wiki/Bombax_ceiba), better known as 96 the red cotton tree, or <ruby>木棉 <rp>(</rp><rt>mù mián</rt><rp>)</rp></ruby> in Chinese. 97 I was going to leave it unidentified, but my mother's a bonafide botanist and identified it for me. ··· 99 Its striking red color is honestly captivating, and I still have it displayed on the wall of my room, 100 facing the entrance for I hope any visitors would appreciate its vividity and life force seeping out of the canvas, 101 as much as I would. 102 103 - +display("laotie", "老铁 (Laotie)", "d35a98e6-9215-4ca7-ac79-133c93d17700").has-frame( 104 - size="medium" aspectRatio="is-3by4" width=400 height=533 105 - ) 106 - +header("2022-03-20", "Oil on canvas") 107 - :md 108 He's a corgi in the studio we used to paint in, and his presence made most of our weekly painting hours turn into communal dog-petting sessions. 109 It was lovely to paint with such an adorable cutie around, and time and stress all went away so swiftly with him. 110 I still have like, *so* many photos of him playing around with us... Too bad that that studio was shut down about 2 months ago, on February 2023. 111 112 My mother insists I painted Santa Claus in disguise of a dog! But really, he's just wearing some festive clothes... 113 114 - +display("mom_and_i", "By the lake, beneath the canopies, in the beforetimes", "34e6b9ba-08a0-4a1a-437c-2e8839591500").has-frame( 115 - size="medium" aspectRatio="is-3by4" width=400 height=515 116 - ) 117 - +header("2022-07-23", "Oil on canvas") 118 - :md 119 One summer afternoon, on the eastern shore of Jinji Lake, Suzhou. It was an awfully nice day, so my mother and I decided 120 to take a selfie on our daily stroll outside. 121 Originally, I decided to paint this as a work of memorabilia, to make me cherish the good times I spent with my mother at home, ··· 123 124 But now, it has taken an additional level of meaning — this was the last painting I finished before I realized I was a trans woman. 125 That tall silhouette with the masculine build was supposed to be me, but now it's representative of the person I used to be. 126 - 127 However, I have to remain truthful to myself — regardless of who I am now, I *was* like that back then, and this fossilized image 128 - of me is one that I can't, and don't want to change. The past is not to be changed — the future, however, is.
··· 1 --- 2 title: Works 3 description: Projects, artworks, and other things I've done... 4 + 5 + artworks: 6 + - id: tiger 7 + name: Ferality 8 + imgid: 558e95a4-f685-4dee-32a6-8c33e49d9600 9 + size: medium 10 + aspect_ratio: square 11 + width: 400 12 + height: 408 13 + 14 + - id: blossoms 15 + name: Altitudinous blossoms 16 + imgid: 170696fd-d9b4-4652-e168-2cc80250d400 17 + size: medium 18 + aspect_ratio: 3by5 19 + width: 400 20 + height: 660 21 + 22 + - id: fish 23 + name: A piscine aura in the depths 24 + imgid: 278a69c6-ec0a-4461-c3f3-79aa615ad800 25 + size: large 26 + aspect_ratio: 4by3 27 + width: 500 28 + height: 380 29 + 30 + - id: red_cotton 31 + name: A flower — what's her name again? 32 + imgid: 35db3ba1-542a-46aa-2d10-850d34463e00" 33 + size: large 34 + aspect_ratio: 4by3 35 + rtl: true 36 + width: 500 37 + height: 372 38 + 39 + - id: laotie 40 + name: 老铁 (Laotie) 41 + imgid: d35a98e6-9215-4ca7-ac79-133c93d17700 42 + size: medium 43 + aspect_ratio: 3by4 44 + width: 400 45 + height: 533 46 + 47 + - id: mom_and_i 48 + name: By the lake, beneath the canopies, in the beforetimes 49 + imgid: 34e6b9ba-08a0-4a1a-437c-2e8839591500 50 + size: medium 51 + aspect_ratio: 3by4 52 + width: 400 53 + height: 515 54 --- 55 56 + {{# 57 include _includes/comps/icon 58 include _includes/comps/modal 59 + #}} 60 61 + <h2 class="text-3xl font-bold">Projects</h2> 62 63 + {{ filter strip_indent |> md }} 64 Under construction! :purple_heart: 65 (Yeah, I know, it's been ages, gimme some more time) 66 + {{ /filter }} 67 68 + <h2 class="text-3xl font-bold">Artworks</h2> 69 70 + {{ function display(attrs) }} 71 + {{> const { id, title, imgid, height, width, aspect_ratio } = attrs }} 72 73 + <figure class="display"> 74 + <label for={{ id }} role="button" tabindex="0"> 75 + <img 76 + src="https://imagedelivery.net/TLP_u-wyyvTEPKkgbA6Osg/{{ imgid }}/public" 77 + alt={{ title }} 78 + width={{ width }} 79 + height={{ height }} 80 + /> 81 + </label> 82 + 83 + <figcaption> 84 + <h3 class="text-2xl font-bold">{{ title }}</h3> 85 + {{ it[id] }} 86 + </figcaption> 87 + {{# 88 +modal(id) 89 .image.is-fullwidth(class=aspectRatio) 90 img( ··· 93 fetchpriority="low" 94 loading="lazy" 95 ) 96 + #}} 97 + {{ /function }} 98 99 + {{ function header(date, medium) }} 100 + <p class="text-xl"> 101 + <i data-lucide="calendar"></i> 102 + {{ date |> date("PP") }} 103 + <i data-lucide="brush"></i> 104 + {{ medium }} 105 + </p> 106 + {{ /function }} 107 108 + {{ set tiger }} 109 + {{ header("2018-12-02", "Oil on canvas") }} 110 + {{ filter strip_indent |> md }} 111 One of the older works of mine — I think I'd only spent 2 years dabbling with oil paintings back then, 112 and the technical inexpertise definitely shows — but a brazen and unpolished technique does not 113 necessarily hinder expressiveness. ;) 114 + 115 I really wanted to capture the anger in the tiger, and as I exaggerated its features and started furiously 116 laying down sweeping strokes of striking orange, I too experienced the feral fury in me. 117 Wish I could experience that again... 118 + {{ /filter }} 119 + {{ /set }} 120 121 + {{ set blossoms }} 122 + {{ header("2021-02-11", "Oil on canvas") }} 123 + {{ filter strip_indent |> md }} 124 A friend of my mother's is a hobbyist photographer, and I think he took the original photo when he went to Tibet to... 125 well, take pictures of the wild flora and fauna and the majestic landscape. 126 (Y'know, like a photographer? Why else would he be there anyway?) ··· 133 I did take a while to figure it out though. I also used a toothbrush to ~~paint~~ *spray* the white dots on, 134 which are actually specks of mint-scented toothpaste and not white paint! 135 Although, they look like they hold up just fine on the canvas. 136 + {{ /filter }} 137 + {{ /set }} 138 139 + {{ set fish }} 140 + {{ header("2021-06-05", "Oil on canvas") }} 141 + {{ filter strip_indent |> md }} 142 Some kind of tropical fish whose name I have completely forgotten. It didn't even occur to past-Leah to write these things down! *sigh* 143 144 It's really pretty, especially for its lake-blue scales and blood-red fins! Sure did take a long time to paint though... It was around this time 145 when my style really departed from the raw, emotional expression like *Ferality*, towards a finer sort of aesthetic. 146 + {{ /filter }} 147 + {{ /set }} 148 149 + {{ set red_cotton }} 150 + {{ header("2021-12-11", "Oil on canvas") }} 151 + {{ filter strip_indent |> md }} 152 Turns out it's a flower of the tree [*Bombax ceiba*](https://en.wikipedia.org/wiki/Bombax_ceiba), better known as 153 the red cotton tree, or <ruby>木棉 <rp>(</rp><rt>mù mián</rt><rp>)</rp></ruby> in Chinese. 154 I was going to leave it unidentified, but my mother's a bonafide botanist and identified it for me. ··· 156 Its striking red color is honestly captivating, and I still have it displayed on the wall of my room, 157 facing the entrance for I hope any visitors would appreciate its vividity and life force seeping out of the canvas, 158 as much as I would. 159 + {{ /filter }} 160 + {{ /set }} 161 162 + {{ set laotie }} 163 + {{ header("2022-03-20", "Oil on canvas") }} 164 + {{ filter strip_indent |> md }} 165 He's a corgi in the studio we used to paint in, and his presence made most of our weekly painting hours turn into communal dog-petting sessions. 166 It was lovely to paint with such an adorable cutie around, and time and stress all went away so swiftly with him. 167 I still have like, *so* many photos of him playing around with us... Too bad that that studio was shut down about 2 months ago, on February 2023. 168 169 My mother insists I painted Santa Claus in disguise of a dog! But really, he's just wearing some festive clothes... 170 + {{ /filter }} 171 + {{ /set }} 172 173 + {{ set mom_and_i }} 174 + {{ header("2022-07-23", "Oil on canvas") }} 175 + {{ filter strip_indent |> md }} 176 One summer afternoon, on the eastern shore of Jinji Lake, Suzhou. It was an awfully nice day, so my mother and I decided 177 to take a selfie on our daily stroll outside. 178 Originally, I decided to paint this as a work of memorabilia, to make me cherish the good times I spent with my mother at home, ··· 180 181 But now, it has taken an additional level of meaning — this was the last painting I finished before I realized I was a trans woman. 182 That tall silhouette with the masculine build was supposed to be me, but now it's representative of the person I used to be. 183 + 184 However, I have to remain truthful to myself — regardless of who I am now, I *was* like that back then, and this fossilized image 185 + of me is one that I can't, and don't want to change. The past is not to be changed — the future, however, is. 186 + {{ /filter }} 187 + {{ /set }} 188 + 189 + {{ for artwork of artworks }} 190 + {{ display(artwork) }} 191 + {{ /for }}
+32 -20
tailwind.config.ts
··· 1 import typography from "npm:@tailwindcss/typography"; 2 3 export default { 4 theme: { 5 extend: { 6 - colors: { 7 - brand: "#d23773", 8 - "brand-dark": "#ba2c63", 9 - "brand-darker": "#851e46", 10 - "brand-darkest": "#57122d", 11 - 12 - youtube: "#d02525", 13 - twitter: "#0077d6", 14 - github: "#fafafa", 15 - "pronouns-page": "#c71585", 16 - blurple: "#5865f2", 17 - }, 18 - backgroundSize: { 19 - wordmark: "75%", 20 }, 21 - backgroundImage: { 22 - wordmark: "url('/icons/wordmark.svg')", 23 }, 24 height: { 25 - navbar: "4rem", 26 - }, 27 - minHeight: { 28 - "main-screen": "calc(100vh - 2 * 4rem)", 29 }, 30 }, 31 fontFamily: {
··· 1 import typography from "npm:@tailwindcss/typography"; 2 3 + const themeColors = [ 4 + "brand", 5 + "brand-dark", 6 + "brand-darker", 7 + "brand-darkest", 8 + "fg", 9 + "fg-dimmed", 10 + "navbar-bg", 11 + "navbar-fg", 12 + "navbar-border", 13 + "bg", 14 + "bg-deemphasized", 15 + ]; 16 + 17 + const colors = { 18 + youtube: "#d02525", 19 + bilibili: "#00a1d6", 20 + github: "#fafafa", 21 + "pronouns-page": "#c71585", 22 + discord: "#5865f2", 23 + mastodon: "#6364ff", 24 + }; 25 + for (const themeColor of themeColors) { 26 + colors[themeColor] = `rgba(var(--${themeColor}) / <alpha-value>)`; 27 + } 28 + 29 export default { 30 theme: { 31 extend: { 32 + colors, 33 + spacing: { 34 + navbar: "4rem", 35 }, 36 + gridTemplateColumns: { 37 + "main-screen": "20rem auto 20rem", 38 }, 39 height: { 40 + "main-screen": "calc(100vh - 4 * 4rem)", 41 }, 42 }, 43 fontFamily: {
+116
vento-improved.ts
···
··· 1 + import { defaults, Options } from "lume/plugins/vento.ts"; 2 + import engine from "https://deno.land/x/vento@v0.7.1/mod.ts"; 3 + import { Environment } from "https://deno.land/x/vento@v0.7.1/src/environment.ts"; 4 + import { FileLoader } from "https://deno.land/x/vento@v0.7.1/src/loader.ts"; 5 + 6 + import { Data, Engine, FS, Helper, Site } from "lume/core.ts"; 7 + import loader from "lume/core/loaders/text.ts"; 8 + import { merge, normalizePath } from "lume/core/utils.ts"; 9 + 10 + export type Tag = ( 11 + env: Environment, 12 + code: string, 13 + output: string, 14 + tokens: Token[] 15 + ) => string | undefined; 16 + 17 + class LumeLoader extends FileLoader { 18 + fs: FS; 19 + constructor(includes: string, fs: FS) { 20 + super(includes); 21 + this.fs = fs; 22 + } 23 + async load(file: string) { 24 + const entry = this.fs.entries.get(normalizePath(file)); 25 + if (!entry) { 26 + throw new Error(`File not found: ${file}`); 27 + } 28 + const data = await entry.getContent(loader); 29 + return { source: data.content as string, data }; 30 + } 31 + } 32 + 33 + /** Template engine to render Vento files */ 34 + export class VentoEngine implements Engine { 35 + engine: Environment; 36 + constructor(engine: Environment) { 37 + this.engine = engine; 38 + } 39 + deleteCache(file: string) { 40 + this.engine.cache.delete(file); 41 + } 42 + render(content: string, data: Data = {}, filename?: string) { 43 + return this.engine 44 + .runString(content, data, filename) 45 + .then((m) => m.content); 46 + } 47 + renderSync(content: string, data: Data = {}, filename?: string): string { 48 + return this.engine.runStringSync(content, data, filename).content; 49 + } 50 + 51 + addHelper(name: string, fn: Helper) { 52 + this.engine.filters[name] = fn; 53 + } 54 + } 55 + 56 + export default function (userOptions?: Partial<Options>) { 57 + const options = merge(defaults, userOptions); 58 + const extensions = Array.isArray(options.extensions) 59 + ? { pages: options.extensions, components: options.extensions } 60 + : options.extensions; 61 + 62 + return (site: Site) => { 63 + const env = engine({ 64 + includes: new LumeLoader(normalizePath(site.options.includes), site.fs), 65 + dataVarname: options.options.dataVarname, 66 + }); 67 + 68 + const patch = { 69 + async load(file: string, from?: string): Promise<Template> { 70 + const path = from ? this.options.loader.resolve(from, file) : file; 71 + 72 + if (!this.cache.has(path)) { 73 + const { source, data } = await this.options.loader.load(path); 74 + const template = this.compile(source, path, data); 75 + 76 + // BUGFIX: use `path`, not `file` 77 + this.cache.set(path, template); 78 + } 79 + 80 + // BUGFIX: use `path`, not `file` 81 + return this.cache.get(path)!; 82 + }, 83 + }; 84 + 85 + const vento = Object.assign(env, patch); 86 + vento.tags.push(filterTag); 87 + 88 + const ventoEngine = new VentoEngine(vento); 89 + 90 + site.loadPages(extensions.pages, loader, ventoEngine); 91 + site.loadComponents(extensions.components, loader, ventoEngine); 92 + }; 93 + } 94 + 95 + const filterTag = (env, code, output, tokens) => { 96 + const match = code.match(/^filter (.*)$/); 97 + if (!match) return; 98 + const [_, filter] = match; 99 + tokens.unshift(["filter", filter]); 100 + 101 + const varname = "__content"; 102 + const filters = env.compileFilters(tokens, varname); 103 + const content = env.compileTokens(tokens, varname, ["/filter"]); 104 + 105 + const tok = tokens.shift(); 106 + if (tok && (tok[0] !== "tag" || tok[1] !== "/filter")) { 107 + throw new Error(`Missing closing tag for filter '${filter.name}': ${code}`); 108 + } 109 + 110 + const res = [ 111 + `{let ${varname} = "";`, 112 + ...content, 113 + `${output} += ${filters};}`, 114 + ].join("\n"); 115 + return res; 116 + };