Update Uses page with my Linux desktop setup

This commit is contained in:
2025-06-24 21:17:19 +08:00
parent 5a8bdbc706
commit f84246947b
115 changed files with 4923 additions and 4925 deletions

356
.gitignore vendored
View File

@@ -1,178 +1,178 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Eleventy site output folder
_site/
# Logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Eleventy site output folder
_site/
# Logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

View File

@@ -1,22 +1,22 @@
# Helen Chong's Developer Portfolio and Blog
Source code of my developer portfolio and blog website. Built with [Eleventy](https://www.11ty.dev/).
More technical information can be found on my website's [colophon page](https://helenchong.dev/colophon).
## Run Locally
It is recommended to use [Bun](https://bun.sh) as the JavaScript runtime for this project. If you want to use Node.js to run this project, for each terminal command and `package.json` script, replace `bun` with `npm` and `bunx` with `npx`.
1. Clone this repository locally
```
git clone https://git.helenchong.dev/helenchong/helenchong.dev.git
```
1. Install dependencies
```
bun install
```
1. Run the project
```
bun start
```
1. Open http://localhost:8080/ in your browser
# Helen Chong's Developer Portfolio and Blog
Source code of my developer portfolio and blog website. Built with [Eleventy](https://www.11ty.dev/).
More technical information can be found on my website's [colophon page](https://helenchong.dev/colophon).
## Run Locally
It is recommended to use [Bun](https://bun.sh) as the JavaScript runtime for this project. If you want to use Node.js to run this project, for each terminal command and `package.json` script, replace `bun` with `npm` and `bunx` with `npx`.
1. Clone this repository locally
```
git clone https://git.helenchong.dev/helenchong/helenchong.dev.git
```
1. Install dependencies
```
bun install
```
1. Run the project
```
bun start
```
1. Open http://localhost:8080/ in your browser

View File

@@ -1,57 +1,57 @@
// nstalled Plugins
import { InputPathToUrlTransformPlugin } from "@11ty/eleventy";
import pluginEleventyNavigation from "@11ty/eleventy-navigation";
import pluginSyntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight";
import pluginEmbedEverything from "eleventy-plugin-embed-everything";
import pluginWordcount from "eleventy-plugin-wordcount-extended";
import { VentoPlugin } from 'eleventy-plugin-vento';
// Custom configurations
import markdownItConfig from "./src/_config/markdown-it.js";
import feedsConfig from "./src/_config/feeds.js";
import filesConfig from "./src/_config/files.js";
import topicsConfig from "./src/_config/topics.js";
import filtersConfig from "./src/_config/filters.js";
import shortCodesConfig from "./src/_config/shortcodes.js";
export default function(eleventyConfig) {
// ----- Installed Plugins
eleventyConfig.addPlugin(InputPathToUrlTransformPlugin);
eleventyConfig.addPlugin(pluginEleventyNavigation);
eleventyConfig.addPlugin(pluginEmbedEverything, {
youtube: {
options: {
lazy: true,
recommendSelfOnly: true,
}
}
});
eleventyConfig.addPlugin(pluginSyntaxHighlight, { preAttributes: { tabindex: 0 } });
eleventyConfig.addPlugin(pluginWordcount);
// ----- Custom configurations
eleventyConfig.addPlugin(markdownItConfig);
eleventyConfig.addPlugin(feedsConfig);
eleventyConfig.addPlugin(filesConfig);
eleventyConfig.addPlugin(topicsConfig);
eleventyConfig.addPlugin(filtersConfig);
eleventyConfig.addPlugin(shortCodesConfig);
// ----- Eleventy bundle plugin
eleventyConfig.addBundle("css");
eleventyConfig.addBundle("js", { toFileDirectory: "assets/js" });
// ----- Vento plugin for Eleventy
// Must be loaded after plugins that modify filters
eleventyConfig.addPlugin(VentoPlugin, {
autotrim: true,
});
return {
markdownTemplateEngine: "vto",
htmlTemplateEngine: "vto",
dir: {
input: "src"
}
};
};
// nstalled Plugins
import { InputPathToUrlTransformPlugin } from "@11ty/eleventy";
import pluginEleventyNavigation from "@11ty/eleventy-navigation";
import pluginSyntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight";
import pluginEmbedEverything from "eleventy-plugin-embed-everything";
import pluginWordcount from "eleventy-plugin-wordcount-extended";
import { VentoPlugin } from 'eleventy-plugin-vento';
// Custom configurations
import markdownItConfig from "./src/_config/markdown-it.js";
import feedsConfig from "./src/_config/feeds.js";
import filesConfig from "./src/_config/files.js";
import topicsConfig from "./src/_config/topics.js";
import filtersConfig from "./src/_config/filters.js";
import shortCodesConfig from "./src/_config/shortcodes.js";
export default function(eleventyConfig) {
// ----- Installed Plugins
eleventyConfig.addPlugin(InputPathToUrlTransformPlugin);
eleventyConfig.addPlugin(pluginEleventyNavigation);
eleventyConfig.addPlugin(pluginEmbedEverything, {
youtube: {
options: {
lazy: true,
recommendSelfOnly: true,
}
}
});
eleventyConfig.addPlugin(pluginSyntaxHighlight, { preAttributes: { tabindex: 0 } });
eleventyConfig.addPlugin(pluginWordcount);
// ----- Custom configurations
eleventyConfig.addPlugin(markdownItConfig);
eleventyConfig.addPlugin(feedsConfig);
eleventyConfig.addPlugin(filesConfig);
eleventyConfig.addPlugin(topicsConfig);
eleventyConfig.addPlugin(filtersConfig);
eleventyConfig.addPlugin(shortCodesConfig);
// ----- Eleventy bundle plugin
eleventyConfig.addBundle("css");
eleventyConfig.addBundle("js", { toFileDirectory: "assets/js" });
// ----- Vento plugin for Eleventy
// Must be loaded after plugins that modify filters
eleventyConfig.addPlugin(VentoPlugin, {
autotrim: true,
});
return {
markdownTemplateEngine: "vto",
htmlTemplateEngine: "vto",
dir: {
input: "src"
}
};
};

View File

@@ -7,9 +7,9 @@
"url": "https://git.helenchong.dev/helenchong/helenchong.dev.git"
},
"scripts": {
"start": "bunx eleventy --serve --quiet",
"build": "bunx eleventy",
"build-action": "npx eleventy"
"start": "bunx @11ty/eleventy --serve --quiet",
"build": "bunx @11ty/eleventy",
"build-action": "npx @11ty/eleventy"
},
"type": "module",
"devDependencies": {

View File

@@ -1,11 +1,11 @@
---
title: Page Not Found
layout: layouts/content
permalink: "404.html"
eleventyExcludeFromCollections: true
---
<p>Sorry! Either the page you are looking for does not exist, or it has been moved to a diffrent part of this website.</p>
<p>You may return to the <a href="/">home page</a> and try again.</p>
{{ css }}main { min-height: 59vh; }{{ /css }}
---
title: Page Not Found
layout: layouts/content
permalink: "404.html"
eleventyExcludeFromCollections: true
---
<p>Sorry! Either the page you are looking for does not exist, or it has been moved to a diffrent part of this website.</p>
<p>You may return to the <a href="/">home page</a> and try again.</p>
{{ css }}main { min-height: 59vh; }{{ /css }}

View File

@@ -1,8 +1,8 @@
---
permalink: "{{ page.filePathStem }}"
eleventyExcludeFromCollections: true
eleventyAllowMissingExtension: true
---
# Redirect obfuscated email links to mailto link
@email_path path_regexp ^.*{{ sitemeta.siteAuthor.emailDecoyUrl }}.*
redir @email_path mailto:{{ sitemeta.siteAuthor.email }}
---
permalink: "{{ page.filePathStem }}"
eleventyExcludeFromCollections: true
eleventyAllowMissingExtension: true
---
# Redirect obfuscated email links to mailto link
@email_path path_regexp ^.*{{ sitemeta.siteAuthor.emailDecoyUrl }}.*
redir @email_path mailto:{{ sitemeta.siteAuthor.email }}

View File

@@ -1,30 +1,30 @@
import { feedPlugin } from "@11ty/eleventy-plugin-rss";
import { siteLang, feedPath, siteAuthor, siteUrl, siteBlog } from "../_data/sitemeta.js";
export default function(eleventyConfig) {
eleventyConfig.addPlugin(feedPlugin, {
type: "atom",
outputPath: feedPath,
collection: {
name: "posts",
limit: 10,
},
metadata: {
language: siteLang,
title: siteBlog.title,
subtitle: siteBlog.desc,
base: siteUrl + "/blog/",
author: {
name: siteAuthor.name,
email: siteAuthor.email,
}
},
templateData: {
eleventyNavigation: {
key: "RSS Feed",
parent: "Blog",
order: 4
}
},
});
}
import { feedPlugin } from "@11ty/eleventy-plugin-rss";
import { siteLang, feedPath, siteAuthor, siteUrl, siteBlog } from "../_data/sitemeta.js";
export default function(eleventyConfig) {
eleventyConfig.addPlugin(feedPlugin, {
type: "atom",
outputPath: feedPath,
collection: {
name: "posts",
limit: 10,
},
metadata: {
language: siteLang,
title: siteBlog.title,
subtitle: siteBlog.desc,
base: siteUrl + "/blog/",
author: {
name: siteAuthor.name,
email: siteAuthor.email,
}
},
templateData: {
eleventyNavigation: {
key: "RSS Feed",
parent: "Blog",
order: 4
}
},
});
}

View File

@@ -1,10 +1,10 @@
// Passthrough File Copy
export default function(eleventyConfig) {
eleventyConfig.addPassthroughCopy("./src/assets/");
eleventyConfig.addWatchTarget("./src/assets/");
eleventyConfig.addPassthroughCopy({
"./src/assets/favicon/favicon.ico": "/favicon.ico",
"./src/assets/favicon/apple-touch-icon.png": "/apple-touch-icon.png",
"node_modules/@zachleat/details-utils/details-utils.js": "assets/js/details-utils.js",
});
}
// Passthrough File Copy
export default function(eleventyConfig) {
eleventyConfig.addPassthroughCopy("./src/assets/");
eleventyConfig.addWatchTarget("./src/assets/");
eleventyConfig.addPassthroughCopy({
"./src/assets/favicon/favicon.ico": "/favicon.ico",
"./src/assets/favicon/apple-touch-icon.png": "/apple-touch-icon.png",
"node_modules/@zachleat/details-utils/details-utils.js": "assets/js/details-utils.js",
});
}

View File

@@ -1,25 +1,25 @@
import { DateTime } from "luxon";
export default function(eleventyConfig) {
// Filter: Format dates
eleventyConfig.addFilter("formatDate", (date) => {
const dateFormat = "d LLLL yyyy";
if (typeof date === "object") {
return DateTime.fromJSDate(date).toFormat(dateFormat);
}
return DateTime.fromISO(date, { setZone: true }).toFormat(dateFormat);
});
// Filter: Limit the number of collection items displayed
eleventyConfig.addFilter("itemLimit", (array, maximum) => {
return array.slice(0, maximum);
});
// Filter: Collection item count
eleventyConfig.addFilter("itemCount", (array) => array.length );
// Filter: Thousands separator
eleventyConfig.addFilter("thousands", (num) => {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
});
}
import { DateTime } from "luxon";
export default function(eleventyConfig) {
// Filter: Format dates
eleventyConfig.addFilter("formatDate", (date) => {
const dateFormat = "d LLLL yyyy";
if (typeof date === "object") {
return DateTime.fromJSDate(date).toFormat(dateFormat);
}
return DateTime.fromISO(date, { setZone: true }).toFormat(dateFormat);
});
// Filter: Limit the number of collection items displayed
eleventyConfig.addFilter("itemLimit", (array, maximum) => {
return array.slice(0, maximum);
});
// Filter: Collection item count
eleventyConfig.addFilter("itemCount", (array) => array.length );
// Filter: Thousands separator
eleventyConfig.addFilter("thousands", (num) => {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
});
}

View File

@@ -1,72 +1,72 @@
/* CONFIGURATION FOR MARKDOWN TEMPLATES */
// Installed plugins
import pluginTOC from '@uncenter/eleventy-plugin-toc';
import embedEverything from "eleventy-plugin-embed-everything";
// Configure slug filter
import slugify from "slugify";
// markdown-it plugins
import markdownIt from "markdown-it";
import markdownItAnchor from "markdown-it-anchor";
import markdownItAttrs from "markdown-it-attrs";
import markdownItBracketedSpans from "markdown-it-bracketed-spans";
export default function(eleventyConfig) {
// Installed plugins
eleventyConfig.addPlugin(pluginTOC, {
tags: ['h2', 'h3', 'h4', 'h5', 'h6'],
wrapper: (toc) => {
return `<nav class="toc" aria-labelledby="toc-heading">${toc}</nav>`;
},
});
eleventyConfig.addPlugin(embedEverything, { add: ['soundcloud'] });
// Configure markdown-it-anchor plugins
eleventyConfig.setLibrary('md', markdownIt().use(markdownItAnchor))
const linkAfterHeader = markdownItAnchor.permalink.linkAfterHeader({
class: "heading-anchor",
assistiveText: title => `Permalink to section '${title}'`,
visuallyHiddenClass: 'visually-hidden',
});
const markdownItAnchorOptions = {
level: [2, 3, 4, 5],
slugify: (str) =>
slugify(str, {
lower: true,
strict: true,
remove: /["]/g,
}),
tabIndex: false,
permalink(slug, opts, state, idx) {
state.tokens.splice(idx, 0,
Object.assign(new state.Token("div_open", "div", 1), {
// Add class "header-wrapper [h1 or h2 or h3]"
attrs: [["class", `heading-wrapper ${state.tokens[idx].tag}`]],
block: true,
})
);
state.tokens.splice(idx + 4, 0,
Object.assign(new state.Token("div_close", "div", -1), {
block: true,
})
);
linkAfterHeader(slug, opts, state, idx + 1);
},
};
/* Markdown Overrides */
let markdownLibrary = markdownIt({
html: true,
linkify: true,
})
.set({ fuzzyLink: false })
.use(markdownItAnchor, markdownItAnchorOptions)
.use(markdownItAttrs)
.use(markdownItBracketedSpans)
/* This is the part that tells 11ty to swap to our custom config */
eleventyConfig.setLibrary("md", markdownLibrary);
}
/* CONFIGURATION FOR MARKDOWN TEMPLATES */
// Installed plugins
import pluginTOC from '@uncenter/eleventy-plugin-toc';
import embedEverything from "eleventy-plugin-embed-everything";
// Configure slug filter
import slugify from "slugify";
// markdown-it plugins
import markdownIt from "markdown-it";
import markdownItAnchor from "markdown-it-anchor";
import markdownItAttrs from "markdown-it-attrs";
import markdownItBracketedSpans from "markdown-it-bracketed-spans";
export default function(eleventyConfig) {
// Installed plugins
eleventyConfig.addPlugin(pluginTOC, {
tags: ['h2', 'h3', 'h4', 'h5', 'h6'],
wrapper: (toc) => {
return `<nav class="toc" aria-labelledby="toc-heading">${toc}</nav>`;
},
});
eleventyConfig.addPlugin(embedEverything, { add: ['soundcloud'] });
// Configure markdown-it-anchor plugins
eleventyConfig.setLibrary('md', markdownIt().use(markdownItAnchor))
const linkAfterHeader = markdownItAnchor.permalink.linkAfterHeader({
class: "heading-anchor",
assistiveText: title => `Permalink to section '${title}'`,
visuallyHiddenClass: 'visually-hidden',
});
const markdownItAnchorOptions = {
level: [2, 3, 4, 5],
slugify: (str) =>
slugify(str, {
lower: true,
strict: true,
remove: /["]/g,
}),
tabIndex: false,
permalink(slug, opts, state, idx) {
state.tokens.splice(idx, 0,
Object.assign(new state.Token("div_open", "div", 1), {
// Add class "header-wrapper [h1 or h2 or h3]"
attrs: [["class", `heading-wrapper ${state.tokens[idx].tag}`]],
block: true,
})
);
state.tokens.splice(idx + 4, 0,
Object.assign(new state.Token("div_close", "div", -1), {
block: true,
})
);
linkAfterHeader(slug, opts, state, idx + 1);
},
};
/* Markdown Overrides */
let markdownLibrary = markdownIt({
html: true,
linkify: true,
})
.set({ fuzzyLink: false })
.use(markdownItAnchor, markdownItAnchorOptions)
.use(markdownItAttrs)
.use(markdownItBracketedSpans)
/* This is the part that tells 11ty to swap to our custom config */
eleventyConfig.setLibrary("md", markdownLibrary);
}

View File

@@ -1,49 +1,49 @@
import slugify from "slugify";
export default function(eleventyConfig) {
// Shortcode: Current year
eleventyConfig.addShortcode("currentYear", () => `${new Date().getFullYear()}`);
// Shortcode: <cite> tag
eleventyConfig.addShortcode('cite', (str) => `<cite>${str}</cite>`);
// Shortcode: Manual heading anchor
eleventyConfig.addPairedShortcode('headingAnchor', (title, hLevel, id=slugify(title)) => {
return `<div class="heading-wrapper h${hLevel}">
<h${hLevel} id="${id}">${title}</h${hLevel}>
<a class="heading-anchor" href="#${id}">
<span class="visually-hidden">Permalink to '${title}'</span>
<span aria-hidden="true">#</span>
</a>
</div>`;
});
/*
Cloudinary responsive image shortcodes by Sia Karamalegos:
https://sia.codes/posts/eleventy-and-cloudinary-images/
*/
// Set constants for the Cloudinary URL and fallback widths for images when not supplied by the shorcode params
const CLOUDNAME = "helenchong";
const FOLDER = "v1742915994/";
const BASE_URL = `https://res.cloudinary.com/${CLOUDNAME}/image/upload/`;
const FALLBACK_WIDTHS = [ 300, 600, 680, 1360 ];
const FALLBACK_WIDTH = 680;
// Generate srcset attribute using the fallback widths or a supplied array of widths
function getSrcset(file, widths) {
const widthSet = widths ? widths : FALLBACK_WIDTHS;
return widthSet.map(width => {
return `${getSrc(file, width)} ${width}w`;
}).join(", ");
}
// Generate the src attribute using the fallback width or a width supplied
// by the shortcode params
function getSrc(file, width) {
return `${BASE_URL}q_auto,f_auto,w_${width ? width : FALLBACK_WIDTH}/${FOLDER}${file}`;
}
// Shortcode: Responsive image
eleventyConfig.addShortcode('src', (file, width) => getSrc(file, width));
eleventyConfig.addShortcode('srcset', (file, widths) => getSrcset(file, widths));
}
import slugify from "slugify";
export default function(eleventyConfig) {
// Shortcode: Current year
eleventyConfig.addShortcode("currentYear", () => `${new Date().getFullYear()}`);
// Shortcode: <cite> tag
eleventyConfig.addShortcode('cite', (str) => `<cite>${str}</cite>`);
// Shortcode: Manual heading anchor
eleventyConfig.addPairedShortcode('headingAnchor', (title, hLevel, id=slugify(title)) => {
return `<div class="heading-wrapper h${hLevel}">
<h${hLevel} id="${id}">${title}</h${hLevel}>
<a class="heading-anchor" href="#${id}">
<span class="visually-hidden">Permalink to '${title}'</span>
<span aria-hidden="true">#</span>
</a>
</div>`;
});
/*
Cloudinary responsive image shortcodes by Sia Karamalegos:
https://sia.codes/posts/eleventy-and-cloudinary-images/
*/
// Set constants for the Cloudinary URL and fallback widths for images when not supplied by the shorcode params
const CLOUDNAME = "helenchong";
const FOLDER = "v1742915994/";
const BASE_URL = `https://res.cloudinary.com/${CLOUDNAME}/image/upload/`;
const FALLBACK_WIDTHS = [ 300, 600, 680, 1360 ];
const FALLBACK_WIDTH = 680;
// Generate srcset attribute using the fallback widths or a supplied array of widths
function getSrcset(file, widths) {
const widthSet = widths ? widths : FALLBACK_WIDTHS;
return widthSet.map(width => {
return `${getSrc(file, width)} ${width}w`;
}).join(", ");
}
// Generate the src attribute using the fallback width or a width supplied
// by the shortcode params
function getSrc(file, width) {
return `${BASE_URL}q_auto,f_auto,w_${width ? width : FALLBACK_WIDTH}/${FOLDER}${file}`;
}
// Shortcode: Responsive image
eleventyConfig.addShortcode('src', (file, width) => getSrc(file, width));
eleventyConfig.addShortcode('srcset', (file, widths) => getSrcset(file, widths));
}

View File

@@ -1,26 +1,26 @@
export default function(eleventyConfig) {
// Add blog post topics to collections
eleventyConfig.addCollection("topics", (collectionApi) => {
let topics = new Set();
let posts = collectionApi.getFilteredByTag("posts");
posts.forEach(p => {
let tops = p.data.topics;
if (tops) {
tops.forEach(t => topics.add(t));
}
});
return Array.from(topics).sort();
});
// Filter: Filter blog posts by topic
eleventyConfig.addFilter("filterByTopic", (posts, topic) => {
topic = topic.toLowerCase();
let result = posts.filter(p => {
let tops = p.data.topics.map(t => t.toLowerCase());
if (tops) {
return tops.includes(topic);
}
});
return result;
});
}
export default function(eleventyConfig) {
// Add blog post topics to collections
eleventyConfig.addCollection("topics", (collectionApi) => {
let topics = new Set();
let posts = collectionApi.getFilteredByTag("posts");
posts.forEach(p => {
let tops = p.data.topics;
if (tops) {
tops.forEach(t => topics.add(t));
}
});
return Array.from(topics).sort();
});
// Filter: Filter blog posts by topic
eleventyConfig.addFilter("filterByTopic", (posts, topic) => {
topic = topic.toLowerCase();
let result = posts.filter(p => {
let tops = p.data.topics.map(t => t.toLowerCase());
if (tops) {
return tops.includes(topic);
}
});
return result;
});
}

View File

@@ -1,23 +1,23 @@
export const siteName = "Helen Chong, Web Developer";
export const siteDomain = "helenchong.dev";
export const siteUrl = "https://" + siteDomain || "http://localhost:8080";
export const siteAuthor = {
name: "Helen Chong",
email: "contact@helenchong.dev",
emailEncoded: '<span class="email-encoded">contact@helenchong<b>.mail</b>.dev</span>',
emailDecoyUrl: "emailme/",
url: siteUrl + "/about",
fediverse: {
handle: "@helenchong@social.lol",
url: "https://social.lol/@helenchong"
},
signal: "helenchong.08"
};
export const siteDescription = siteAuthor.name + "'s developer portfolio and blog website.";
export const siteLang = "en";
export const siteLocale = "en_MY";
export const siteBlog = {
title: siteAuthor.name + "'s Developer Blog",
desc: siteAuthor.name + " talks about tech, coding and development.",
};
export const feedPath = "/blog/feed.xml"
export const siteName = "Helen Chong, Web Developer";
export const siteDomain = "helenchong.dev";
export const siteUrl = "https://" + siteDomain || "http://localhost:8080";
export const siteAuthor = {
name: "Helen Chong",
email: "contact@helenchong.dev",
emailEncoded: '<span class="email-encoded">contact@helenchong<b>.mail</b>.dev</span>',
emailDecoyUrl: "emailme/",
url: siteUrl + "/about",
fediverse: {
handle: "@helenchong@social.lol",
url: "https://social.lol/@helenchong"
},
signal: "helenchong.08"
};
export const siteDescription = siteAuthor.name + "'s developer portfolio and blog website.";
export const siteLang = "en";
export const siteLocale = "en_MY";
export const siteBlog = {
title: siteAuthor.name + "'s Developer Blog",
desc: siteAuthor.name + " talks about tech, coding and development.",
};
export const feedPath = "/blog/feed.xml"

View File

@@ -1,9 +1,9 @@
{{ if isArticle || hasBreadcrumbs }}
<nav class="breadcrumbs" aria-labelledby="breadcrumbs-title">
<h2 class="visually-hidden" id="breadcrumbs-title">Breadcrumbs</h2>
{{ set breadcrumbNavPages = collections.all |> eleventyNavigationBreadcrumb(eleventyNavigation.key || pageTitle || title) }}
{{ for entry of breadcrumbNavPages }}
<a href="{{ entry.url }}">{{ entry.title }}</a> <span aria-hidden="true">➔</span>
{{ /for }}
</nav>
{{ /if }}
{{ if isArticle || hasBreadcrumbs }}
<nav class="breadcrumbs" aria-labelledby="breadcrumbs-title">
<h2 class="visually-hidden" id="breadcrumbs-title">Breadcrumbs</h2>
{{ set breadcrumbNavPages = collections.all |> eleventyNavigationBreadcrumb(eleventyNavigation.key || pageTitle || title) }}
{{ for entry of breadcrumbNavPages }}
<a href="{{ entry.url }}">{{ entry.title }}</a> <span aria-hidden="true">➔</span>
{{ /for }}
</nav>
{{ /if }}

View File

@@ -1,9 +1,9 @@
<div class="h-card hidden">
<span class="p-name">Helen Chong</span>
<span class="p-given-name">Helen</span>
<span class="p-family-name">Chong</span>
<a href="/" class="u-url">Home page</a>
<img src="/assets/graphics/profile/avatar-helen-chong.avif" alt="Helen Chong, Front-End Developer" class="u-photo">
<span class="p-country-name">Malaysia</span>
<span class="p-note">They/she. Graphic designer turned front-end web developer from Malaysia.</span>
<div class="h-card hidden">
<span class="p-name">Helen Chong</span>
<span class="p-given-name">Helen</span>
<span class="p-family-name">Chong</span>
<a href="/" class="u-url">Home page</a>
<img src="/assets/graphics/profile/avatar-helen-chong.avif" alt="Helen Chong, Front-End Developer" class="u-photo">
<span class="p-country-name">Malaysia</span>
<span class="p-note">They/she. Graphic designer turned front-end web developer from Malaysia.</span>
</div>

View File

@@ -1,88 +1,88 @@
{{ set firstLabel }}
<svg class="inline-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160zm352-160l-160 160c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L301.3 256 438.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0z"></svg>
Newest{{ /set }}
{{ set prevLabel }}
<svg class="inline-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>
Newer{{ /set }}
{{ set nextLabel }}Older
<svg class="inline-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg>
{{ /set }}
{{ set lastLabel }}Oldest
<svg class="inline-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M470.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L402.7 256 265.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160zm-352 160l160-160c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L210.7 256 73.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0z"/></svg>
{{ /set }}
<nav aria-labelledby="pagination-title" class="pagination__wrapper">
<h2 class="visually-hidden" id="pagination-title">Pagination</h2>
<ul class="pagination">
<li>
{{ if pagination.href.first }}
<a href="{{ pagination.href.first }}">
{{ firstLabel }}
</a>
{{ else }}
{{ firstLabel }}
{{ /if }}
</li>
<li>
{{ if pagination.href.previous }}
<a href=" {{ pagination.href.previous }}">
<i class="fa-solid fa-angle-left"></i>
{{ prevLabel }}
</a>
{{ else }}
{{ prevLabel }}
{{ /if }}
</li>
<li>
{{ if pagination.href.next }}
<a href=" {{ pagination.href.next }}">
{{ nextLabel }}
</a>
{{ else }}
{{ nextLabel }}
{{ /if }}
</li>
<li>
{{ if pagination.href.last }}
<a href=" {{ pagination.href.last }}">
{{ lastLabel }}
</a>
{{ else }}
{{ lastLabel }}
{{ /if }}
</li>
</ul>
</nav>
{{ css }}
.pagination__wrapper {
display: grid;
place-content: center;
margin-top: 3em;
}
.pagination {
list-style-type: "";
padding: 0;
margin: 0;
display: flex;
gap: 0.5em;
flex-wrap: wrap;
justify-content: center;
}
.pagination li {
text-align: center;
padding: 0.3em 0.7em;
}
.pagination li:has(a) { border: 0.15em solid var(--clr-accent-700); }
.pagination li:has(a):hover { border-color: var(--clr-link-hover); }
.pagination li:has(a):hover > a { color: var(--clr-link-hover); }
.pagination li:has(a):focus-within { outline: 0.2em solid var(--clr-accent-700); }
.pagination li a { text-decoration: none; }
.pagination li a:focus { outline: none; }
{{ /css }}
{{ set firstLabel }}
<svg class="inline-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160zm352-160l-160 160c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L301.3 256 438.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0z"></svg>
Newest{{ /set }}
{{ set prevLabel }}
<svg class="inline-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>
Newer{{ /set }}
{{ set nextLabel }}Older
<svg class="inline-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg>
{{ /set }}
{{ set lastLabel }}Oldest
<svg class="inline-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M470.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L402.7 256 265.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160zm-352 160l160-160c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L210.7 256 73.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0z"/></svg>
{{ /set }}
<nav aria-labelledby="pagination-title" class="pagination__wrapper">
<h2 class="visually-hidden" id="pagination-title">Pagination</h2>
<ul class="pagination">
<li>
{{ if pagination.href.first }}
<a href="{{ pagination.href.first }}">
{{ firstLabel }}
</a>
{{ else }}
{{ firstLabel }}
{{ /if }}
</li>
<li>
{{ if pagination.href.previous }}
<a href=" {{ pagination.href.previous }}">
<i class="fa-solid fa-angle-left"></i>
{{ prevLabel }}
</a>
{{ else }}
{{ prevLabel }}
{{ /if }}
</li>
<li>
{{ if pagination.href.next }}
<a href=" {{ pagination.href.next }}">
{{ nextLabel }}
</a>
{{ else }}
{{ nextLabel }}
{{ /if }}
</li>
<li>
{{ if pagination.href.last }}
<a href=" {{ pagination.href.last }}">
{{ lastLabel }}
</a>
{{ else }}
{{ lastLabel }}
{{ /if }}
</li>
</ul>
</nav>
{{ css }}
.pagination__wrapper {
display: grid;
place-content: center;
margin-top: 3em;
}
.pagination {
list-style-type: "";
padding: 0;
margin: 0;
display: flex;
gap: 0.5em;
flex-wrap: wrap;
justify-content: center;
}
.pagination li {
text-align: center;
padding: 0.3em 0.7em;
}
.pagination li:has(a) { border: 0.15em solid var(--clr-accent-700); }
.pagination li:has(a):hover { border-color: var(--clr-link-hover); }
.pagination li:has(a):hover > a { color: var(--clr-link-hover); }
.pagination li:has(a):focus-within { outline: 0.2em solid var(--clr-accent-700); }
.pagination li a { text-decoration: none; }
.pagination li a:focus { outline: none; }
{{ /css }}

View File

@@ -1,76 +1,76 @@
<nav class="socials" aria-labelledby="social-link-title">
<h2 class="visually-hidden" id="social-link-title">Social Links</h2>
<ul class="socials__links">
<li>
<a href="https://helenchong.omg.lol/" rel="me">
<?xml version="1.0" encoding="utf-8"?>
<svg class="socials__icon" aria-hidden="true" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
<path d="M 250 500 C 211.611 500 173.225 485.355 143.934 456.066 L 43.934 356.066 C -14.645 297.487 -14.645 202.514 43.934 143.935 C 100.532 87.337 191.104 85.422 250 138.191 C 308.898 85.423 399.47 87.339 456.066 143.935 C 514.645 202.514 514.645 297.487 456.066 356.066 L 356.066 456.066 C 326.777 485.355 288.389 500 250 500 Z M 179.245 262.013 C 179.245 229.449 152.847 203.051 120.283 203.051 C 87.719 203.051 61.321 229.449 61.321 262.013 C 61.321 294.577 87.719 320.975 120.283 320.975 C 152.847 320.975 179.245 294.577 179.245 262.013 Z M 438.679 262.013 C 438.679 229.449 412.281 203.051 379.717 203.051 C 347.153 203.051 320.755 229.449 320.755 262.013 C 320.755 294.577 347.153 320.975 379.717 320.975 C 412.281 320.975 438.679 294.577 438.679 262.013 Z M 208.843 209 C 198.899 209 190.841 217.058 190.841 226.998 C 190.841 236.942 198.899 245 208.843 245 C 218.783 245 226.841 236.942 226.841 226.998 C 226.841 217.058 218.783 209 208.843 209 Z M 291.161 209.037 C 281.217 209.037 273.159 217.095 273.159 227.035 C 273.159 236.979 281.217 245.037 291.161 245.037 C 301.101 245.037 309.159 236.979 309.159 227.035 C 309.159 217.095 301.101 209.037 291.161 209.037 Z M 203.661 265.526 C 199.217 268.249 197.823 274.056 200.545 278.5 C 211.949 295.681 231.878 306.105 249.84 306.105 C 258.902 306.105 269.168 303.619 277.366 299.2 C 281.46 296.994 285.838 293.868 289.368 290.545 C 292.874 287.243 297.559 280.437 297.559 280.437 C 297.972 279.996 297.559 280.437 297.559 280.437 C 301.351 276.866 301.53 270.896 297.959 267.102 C 294.387 263.309 288.417 263.13 284.623 266.702 C 284.623 266.702 284.209 267.143 284.623 266.702 C 284.623 266.702 279.06 274.334 276.434 276.807 C 273.83 279.26 271.466 280.947 268.415 282.59 C 262.325 285.873 256.683 287.237 249.84 287.237 C 235.989 287.237 224.104 282.26 216.632 268.641 C 213.91 264.198 208.102 262.803 203.659 265.526 L 203.661 265.526 Z" style="fill-opacity: 1; fill-rule: nonzero; stroke: none;" id="path26">
<title>omg.lol</title>
</path>
</svg>
<span class="visually-hidden">omg.lol</span>
</a>
</li>
<li>
<a href="https://git.helenchong.dev/helenchong/" rel="me">
<svg class="socials__icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
<path d="M439.6 236.1L244 40.5a28.9 28.9 0 0 0 -40.8 0l-40.7 40.6 51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.2 199v121.9c25.3 12.5 22.3 41.9 9.1 55a34.3 34.3 0 0 1 -48.6 0c-17.6-17.6-11.1-46.9 11.3-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1a28.9 28.9 0 0 0 0 40.8l195.6 195.6a28.9 28.9 0 0 0 40.8 0l194.7-194.7a28.9 28.9 0 0 0 0-40.8z"/>
<title>Git</title>
</svg>
<span class="visually-hidden">Gitea</span>
</a>
</li>
<li>
<a href="https://github.com/helenclx" rel="me">
<svg class="socials__icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
<!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/>
<title>GitHub</title>
</svg>
<span class="visually-hidden">GitHub</span>
</a>
</li>
<li>
<a href="https://codepen.io/helenclx" rel="me">
<svg class="socials__icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<!--!Font Awesome Free 6.7.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<path d="M502.3 159.7l-234-156c-8-4.9-16.5-5-24.6 0l-234 156C3.7 163.7 0 170.8 0 178v156c0 7.1 3.7 14.3 9.7 18.3l234 156c8 4.9 16.5 5 24.6 0l234-156c6-4 9.7-11.1 9.7-18.3V178c0-7.1-3.7-14.3-9.7-18.3zM278 63.1l172.3 114.9-76.9 51.4L278 165.7V63.1zm-44 0v102.6l-95.4 63.7-76.9-51.4L234 63.1zM44 219.1l55.1 36.9L44 292.8v-73.7zm190 229.7L61.7 334l76.9-51.4L234 346.3v102.6zm22-140.9l-77.7-52 77.7-52 77.7 52-77.7 52zm22 140.9V346.3l95.4-63.7 76.9 51.4L278 448.8zm190-156l-55.1-36.9L468 219.1v73.7z"/>
<title>CodePen</title>
</svg>
<span class="visually-hidden">CodePen</span>
</a>
</li>
<li>
<a href="{{ sitemeta.siteAuthor.fediverse.url }}" rel="me">
<svg class="socials__icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<path d="M433 179.1c0-97.2-63.7-125.7-63.7-125.7-62.5-28.7-228.6-28.4-290.5 0 0 0-63.7 28.5-63.7 125.7 0 115.7-6.6 259.4 105.6 289.1 40.5 10.7 75.3 13 103.3 11.4 50.8-2.8 79.3-18.1 79.3-18.1l-1.7-36.9s-36.3 11.4-77.1 10.1c-40.4-1.4-83-4.4-89.6-54a102.5 102.5 0 0 1 -.9-13.9c85.6 20.9 158.7 9.1 178.8 6.7 56.1-6.7 105-41.3 111.2-72.9 9.8-49.8 9-121.5 9-121.5zm-75.1 125.2h-46.6v-114.2c0-49.7-64-51.6-64 6.9v62.5h-46.3V197c0-58.5-64-56.6-64-6.9v114.2H90.2c0-122.1-5.2-147.9 18.4-175 25.9-28.9 79.8-30.8 103.8 6.1l11.6 19.5 11.6-19.5c24.1-37.1 78.1-34.8 103.8-6.1 23.7 27.3 18.4 53 18.4 175z"/>
<title>Mastodon</title>
</svg>
<span class="visually-hidden">Mastodon</span>
</a>
</li>
<li>
<a href="https://bsky.app/profile/helenchong.omg.lol" rel="me">
<svg class="socials__icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<path d="M407.8 294.7c-3.3-.4-6.7-.8-10-1.3c3.4 .4 6.7 .9 10 1.3zM288 227.1C261.9 176.4 190.9 81.9 124.9 35.3C61.6-9.4 37.5-1.7 21.6 5.5C3.3 13.8 0 41.9 0 58.4S9.1 194 15 213.9c19.5 65.7 89.1 87.9 153.2 80.7c3.3-.5 6.6-.9 10-1.4c-3.3 .5-6.6 1-10 1.4C74.3 308.6-9.1 342.8 100.3 464.5C220.6 589.1 265.1 437.8 288 361.1c22.9 76.7 49.2 222.5 185.6 103.4c102.4-103.4 28.1-156-65.8-169.9c-3.3-.4-6.7-.8-10-1.3c3.4 .4 6.7 .9 10 1.3c64.1 7.1 133.6-15.1 153.2-80.7C566.9 194 576 75 576 58.4s-3.3-44.7-21.6-52.9c-15.8-7.1-40-14.9-103.2 29.8C385.1 81.9 314.1 176.4 288 227.1z"/>
<title>Bluesky</title>
</svg>
<span class="visually-hidden">Bluesky</span>
</a>
</li>
<li>
<a href="https://www.linkedin.com/in/helenclx/" rel="me">
<svg class="socials__icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<path d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"/>
<title>LinkedIn</title>
</svg>
<span class="visually-hidden">LinkedIn</span>
</a>
</li>
</ul>
</nav>
<nav class="socials" aria-labelledby="social-link-title">
<h2 class="visually-hidden" id="social-link-title">Social Links</h2>
<ul class="socials__links">
<li>
<a href="https://helenchong.omg.lol/" rel="me">
<?xml version="1.0" encoding="utf-8"?>
<svg class="socials__icon" aria-hidden="true" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
<path d="M 250 500 C 211.611 500 173.225 485.355 143.934 456.066 L 43.934 356.066 C -14.645 297.487 -14.645 202.514 43.934 143.935 C 100.532 87.337 191.104 85.422 250 138.191 C 308.898 85.423 399.47 87.339 456.066 143.935 C 514.645 202.514 514.645 297.487 456.066 356.066 L 356.066 456.066 C 326.777 485.355 288.389 500 250 500 Z M 179.245 262.013 C 179.245 229.449 152.847 203.051 120.283 203.051 C 87.719 203.051 61.321 229.449 61.321 262.013 C 61.321 294.577 87.719 320.975 120.283 320.975 C 152.847 320.975 179.245 294.577 179.245 262.013 Z M 438.679 262.013 C 438.679 229.449 412.281 203.051 379.717 203.051 C 347.153 203.051 320.755 229.449 320.755 262.013 C 320.755 294.577 347.153 320.975 379.717 320.975 C 412.281 320.975 438.679 294.577 438.679 262.013 Z M 208.843 209 C 198.899 209 190.841 217.058 190.841 226.998 C 190.841 236.942 198.899 245 208.843 245 C 218.783 245 226.841 236.942 226.841 226.998 C 226.841 217.058 218.783 209 208.843 209 Z M 291.161 209.037 C 281.217 209.037 273.159 217.095 273.159 227.035 C 273.159 236.979 281.217 245.037 291.161 245.037 C 301.101 245.037 309.159 236.979 309.159 227.035 C 309.159 217.095 301.101 209.037 291.161 209.037 Z M 203.661 265.526 C 199.217 268.249 197.823 274.056 200.545 278.5 C 211.949 295.681 231.878 306.105 249.84 306.105 C 258.902 306.105 269.168 303.619 277.366 299.2 C 281.46 296.994 285.838 293.868 289.368 290.545 C 292.874 287.243 297.559 280.437 297.559 280.437 C 297.972 279.996 297.559 280.437 297.559 280.437 C 301.351 276.866 301.53 270.896 297.959 267.102 C 294.387 263.309 288.417 263.13 284.623 266.702 C 284.623 266.702 284.209 267.143 284.623 266.702 C 284.623 266.702 279.06 274.334 276.434 276.807 C 273.83 279.26 271.466 280.947 268.415 282.59 C 262.325 285.873 256.683 287.237 249.84 287.237 C 235.989 287.237 224.104 282.26 216.632 268.641 C 213.91 264.198 208.102 262.803 203.659 265.526 L 203.661 265.526 Z" style="fill-opacity: 1; fill-rule: nonzero; stroke: none;" id="path26">
<title>omg.lol</title>
</path>
</svg>
<span class="visually-hidden">omg.lol</span>
</a>
</li>
<li>
<a href="https://git.helenchong.dev/helenchong/" rel="me">
<svg class="socials__icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
<path d="M439.6 236.1L244 40.5a28.9 28.9 0 0 0 -40.8 0l-40.7 40.6 51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.2 199v121.9c25.3 12.5 22.3 41.9 9.1 55a34.3 34.3 0 0 1 -48.6 0c-17.6-17.6-11.1-46.9 11.3-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1a28.9 28.9 0 0 0 0 40.8l195.6 195.6a28.9 28.9 0 0 0 40.8 0l194.7-194.7a28.9 28.9 0 0 0 0-40.8z"/>
<title>Git</title>
</svg>
<span class="visually-hidden">Gitea</span>
</a>
</li>
<li>
<a href="https://github.com/helenclx" rel="me">
<svg class="socials__icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
<!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/>
<title>GitHub</title>
</svg>
<span class="visually-hidden">GitHub</span>
</a>
</li>
<li>
<a href="https://codepen.io/helenclx" rel="me">
<svg class="socials__icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<!--!Font Awesome Free 6.7.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<path d="M502.3 159.7l-234-156c-8-4.9-16.5-5-24.6 0l-234 156C3.7 163.7 0 170.8 0 178v156c0 7.1 3.7 14.3 9.7 18.3l234 156c8 4.9 16.5 5 24.6 0l234-156c6-4 9.7-11.1 9.7-18.3V178c0-7.1-3.7-14.3-9.7-18.3zM278 63.1l172.3 114.9-76.9 51.4L278 165.7V63.1zm-44 0v102.6l-95.4 63.7-76.9-51.4L234 63.1zM44 219.1l55.1 36.9L44 292.8v-73.7zm190 229.7L61.7 334l76.9-51.4L234 346.3v102.6zm22-140.9l-77.7-52 77.7-52 77.7 52-77.7 52zm22 140.9V346.3l95.4-63.7 76.9 51.4L278 448.8zm190-156l-55.1-36.9L468 219.1v73.7z"/>
<title>CodePen</title>
</svg>
<span class="visually-hidden">CodePen</span>
</a>
</li>
<li>
<a href="{{ sitemeta.siteAuthor.fediverse.url }}" rel="me">
<svg class="socials__icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<path d="M433 179.1c0-97.2-63.7-125.7-63.7-125.7-62.5-28.7-228.6-28.4-290.5 0 0 0-63.7 28.5-63.7 125.7 0 115.7-6.6 259.4 105.6 289.1 40.5 10.7 75.3 13 103.3 11.4 50.8-2.8 79.3-18.1 79.3-18.1l-1.7-36.9s-36.3 11.4-77.1 10.1c-40.4-1.4-83-4.4-89.6-54a102.5 102.5 0 0 1 -.9-13.9c85.6 20.9 158.7 9.1 178.8 6.7 56.1-6.7 105-41.3 111.2-72.9 9.8-49.8 9-121.5 9-121.5zm-75.1 125.2h-46.6v-114.2c0-49.7-64-51.6-64 6.9v62.5h-46.3V197c0-58.5-64-56.6-64-6.9v114.2H90.2c0-122.1-5.2-147.9 18.4-175 25.9-28.9 79.8-30.8 103.8 6.1l11.6 19.5 11.6-19.5c24.1-37.1 78.1-34.8 103.8-6.1 23.7 27.3 18.4 53 18.4 175z"/>
<title>Mastodon</title>
</svg>
<span class="visually-hidden">Mastodon</span>
</a>
</li>
<li>
<a href="https://bsky.app/profile/helenchong.omg.lol" rel="me">
<svg class="socials__icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<path d="M407.8 294.7c-3.3-.4-6.7-.8-10-1.3c3.4 .4 6.7 .9 10 1.3zM288 227.1C261.9 176.4 190.9 81.9 124.9 35.3C61.6-9.4 37.5-1.7 21.6 5.5C3.3 13.8 0 41.9 0 58.4S9.1 194 15 213.9c19.5 65.7 89.1 87.9 153.2 80.7c3.3-.5 6.6-.9 10-1.4c-3.3 .5-6.6 1-10 1.4C74.3 308.6-9.1 342.8 100.3 464.5C220.6 589.1 265.1 437.8 288 361.1c22.9 76.7 49.2 222.5 185.6 103.4c102.4-103.4 28.1-156-65.8-169.9c-3.3-.4-6.7-.8-10-1.3c3.4 .4 6.7 .9 10 1.3c64.1 7.1 133.6-15.1 153.2-80.7C566.9 194 576 75 576 58.4s-3.3-44.7-21.6-52.9c-15.8-7.1-40-14.9-103.2 29.8C385.1 81.9 314.1 176.4 288 227.1z"/>
<title>Bluesky</title>
</svg>
<span class="visually-hidden">Bluesky</span>
</a>
</li>
<li>
<a href="https://www.linkedin.com/in/helenclx/" rel="me">
<svg class="socials__icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<path d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"/>
<title>LinkedIn</title>
</svg>
<span class="visually-hidden">LinkedIn</span>
</a>
</li>
</ul>
</nav>

View File

@@ -1,60 +1,60 @@
<aside class="left-sidebar" aria-label="Left sidebar">
<details-utils force-open="(min-width: 60rem)" force-restore>
<details class="toc__wrapper sidebar--sticky">
<summary class="toc__heading" id="toc-heading">
Table of Contents
</summary>
{{ content |> toc }}
</details>
</details-utils>
</aside>
{{ css }}
.toc__wrapper {
max-height: 80vh;
overflow-x: auto;
padding: 0.3em 1.3rem 1.5em;
}
.toc__heading {
font-size: 1.4rem;
font-weight: 700;
}
.toc__wrapper[open] > .toc__heading {
margin-bottom: 0.5em;
}
.toc ol, .toc ol ol {
display: grid;
gap: 0.7em;
}
.toc ol {
border-top: 0.1em solid var(--clr-txt-default);
padding-left: 1.3em;
padding-top: 1em;
}
.toc ol ol {
border-top: none;
list-style-type: disc;
padding-left: 1em;
padding-top: 0.5em;
margin: 0;
}
.toc ol a {
font-size: 1.1em;
padding-left: 0.3em;
}
.toc ol ol a {
padding: 0;
font-size: 1em;
}
@media only screen and (min-width: 60rem) {
.toc__wrapper { padding-bottom: var(--sz-content-bottom); }
}
{{ /css }}
<aside class="left-sidebar" aria-label="Left sidebar">
<details-utils force-open="(min-width: 60rem)" force-restore>
<details class="toc__wrapper sidebar--sticky">
<summary class="toc__heading" id="toc-heading">
Table of Contents
</summary>
{{ content |> toc }}
</details>
</details-utils>
</aside>
{{ css }}
.toc__wrapper {
max-height: 80vh;
overflow-x: auto;
padding: 0.3em 1.3rem 1.5em;
}
.toc__heading {
font-size: 1.4rem;
font-weight: 700;
}
.toc__wrapper[open] > .toc__heading {
margin-bottom: 0.5em;
}
.toc ol, .toc ol ol {
display: grid;
gap: 0.7em;
}
.toc ol {
border-top: 0.1em solid var(--clr-txt-default);
padding-left: 1.3em;
padding-top: 1em;
}
.toc ol ol {
border-top: none;
list-style-type: disc;
padding-left: 1em;
padding-top: 0.5em;
margin: 0;
}
.toc ol a {
font-size: 1.1em;
padding-left: 0.3em;
}
.toc ol ol a {
padding: 0;
font-size: 1em;
}
@media only screen and (min-width: 60rem) {
.toc__wrapper { padding-bottom: var(--sz-content-bottom); }
}
{{ /css }}

View File

@@ -1,74 +1,74 @@
{{ set topLinkTitle = "Back to Top" }}
<a href="#top" class="top-btn" aria-label="{{ topLinkTitle }}">Top</a>
<noscript>
<p><a class="top-link" href="#top">{{ topLinkTitle }}</a></p>
</noscript>
{{ css }}
.top-btn, .top-btn:hover { color: var(--clr-top-btn-txt); }
.top-btn {
position: fixed;
bottom: 0.5rem;
right: 0.5rem;
z-index: 999;
text-decoration: none;
text-transform: uppercase;
font-size: 1rem;
font-weight: 700;
background-color: var(--clr-top-btn-bg);
border-radius: 50em;
height: 3.25rem;
aspect-ratio: 1 / 1;
display: none;
gap: 0.1em;
justify-items: center;
}
.top-btn.show { display: grid; }
.top-btn::before {
content: "";
display: block;
border-bottom: 0.6rem solid currentColor;
border-left: 0.6rem solid transparent;
border-right: 0.6rem solid transparent;
}
.top-btn:focus {
outline: 0.25em solid var(--clr-top-btn-bg);
outline-offset: 0.15em;
}
.top-link::before,
.top-link::after {
--arrow-margin: 0.3em;
content: "";
display: inline-block;
border-bottom: 0.7rem solid currentColor;
border-left: 0.5rem solid transparent;
border-right: 0.5rem solid transparent;
}
.top-link::before { margin: 0 var(--arrow-margin) 0 0; }
.top-link::after { margin: 0 0 0 var(--arrow-margin); }
{{ /css }}
{{ js }}
const topButton = document.querySelector(".top-btn");
const scrollOffset = 400;
window.onscroll = () => {
if (document.body.scrollTop > scrollOffset || document.documentElement.scrollTop > scrollOffset) {
topButton.classList.add("show");
} else {
topButton.classList.remove("show");
}
};
topButton.addEventListener('click', () => {
window.scrollTo({ top: 0, left: 0 });
});
{{ /js }}
{{ set topLinkTitle = "Back to Top" }}
<a href="#top" class="top-btn" aria-label="{{ topLinkTitle }}">Top</a>
<noscript>
<p><a class="top-link" href="#top">{{ topLinkTitle }}</a></p>
</noscript>
{{ css }}
.top-btn, .top-btn:hover { color: var(--clr-top-btn-txt); }
.top-btn {
position: fixed;
bottom: 0.5rem;
right: 0.5rem;
z-index: 999;
text-decoration: none;
text-transform: uppercase;
font-size: 1rem;
font-weight: 700;
background-color: var(--clr-top-btn-bg);
border-radius: 50em;
height: 3.25rem;
aspect-ratio: 1 / 1;
display: none;
gap: 0.1em;
justify-items: center;
}
.top-btn.show { display: grid; }
.top-btn::before {
content: "";
display: block;
border-bottom: 0.6rem solid currentColor;
border-left: 0.6rem solid transparent;
border-right: 0.6rem solid transparent;
}
.top-btn:focus {
outline: 0.25em solid var(--clr-top-btn-bg);
outline-offset: 0.15em;
}
.top-link::before,
.top-link::after {
--arrow-margin: 0.3em;
content: "";
display: inline-block;
border-bottom: 0.7rem solid currentColor;
border-left: 0.5rem solid transparent;
border-right: 0.5rem solid transparent;
}
.top-link::before { margin: 0 var(--arrow-margin) 0 0; }
.top-link::after { margin: 0 0 0 var(--arrow-margin); }
{{ /css }}
{{ js }}
const topButton = document.querySelector(".top-btn");
const scrollOffset = 400;
window.onscroll = () => {
if (document.body.scrollTop > scrollOffset || document.documentElement.scrollTop > scrollOffset) {
topButton.classList.add("show");
} else {
topButton.classList.remove("show");
}
};
topButton.addEventListener('click', () => {
window.scrollTo({ top: 0, left: 0 });
});
{{ /js }}

View File

@@ -1,33 +1,33 @@
<section class="section--home grid-center">
<h2 class="heading--home">Badges</h2>
<div class="widgets home__badges">
<a href="https://validator.w3.org/feed/check.cgi?url=https%3A//helenchong.dev/blog/feed.xml"><img src="/assets/graphics/badges/valid-atom.png" alt="Valid Atom 1.0" width="88" height="31" loading="lazy"></a>
<a href="https://ko-fi.com/s/798b83fbcc"><img src="/assets/graphics/badges/coffee-powered.svg" alt="Coffee powered" width="88" height="31" loading="lazy"></a>
<a href="https://people.pledge.party/"><img src="/assets/graphics/badges/people-pledge.svg" alt="The People Pledge" width="88" height="31" loading="lazy"></a>
<a href="https://fightfascism.glitch.me/"><img src="/assets/graphics/badges/fight-fascism.jpg" alt="Fight fascism" width="220" height="37" loading="lazy"></a>
<a href="https://internet.nl/site/helenchong.dev/3290581/"><img src="/assets/graphics/badges/internet.nl-websitetest.svg" alt="Badge with a green check mark and text: 100% score in website test, internet.nl" width="204" height="38" loading="lazy"></a>
</div>
<div id="wcb" class="carbonbadge">
<noscript>
<p>Looks like you have JavaScript disabled. JavaScript is required to display the <a href="https://www.websitecarbon.com/badge/">Website Carbon badge</a>.</p>
</noscript>
</div>
<script src="https://unpkg.com/website-carbon-badges@1.1.3/b.min.js" defer></script>
</section>
{{ js }}
const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
const websiteCarbonBadge = document.getElementById("wcb");
if (window.matchMedia && darkModePreference.matches) {
websiteCarbonBadge.classList.add("wcb-d");
}
darkModePreference.addEventListener("change", (event) => {
if (event.matches) {
websiteCarbonBadge.classList.add("wcb-d");
} else {
websiteCarbonBadge.classList.remove("wcb-d");
}
});
{{ /js }}
<section class="section--home grid-center">
<h2 class="heading--home">Badges</h2>
<div class="widgets home__badges">
<a href="https://validator.w3.org/feed/check.cgi?url=https%3A//helenchong.dev/blog/feed.xml"><img src="/assets/graphics/badges/valid-atom.png" alt="Valid Atom 1.0" width="88" height="31" loading="lazy"></a>
<a href="https://ko-fi.com/s/798b83fbcc"><img src="/assets/graphics/badges/coffee-powered.svg" alt="Coffee powered" width="88" height="31" loading="lazy"></a>
<a href="https://people.pledge.party/"><img src="/assets/graphics/badges/people-pledge.svg" alt="The People Pledge" width="88" height="31" loading="lazy"></a>
<a href="https://fightfascism.glitch.me/"><img src="/assets/graphics/badges/fight-fascism.jpg" alt="Fight fascism" width="220" height="37" loading="lazy"></a>
<a href="https://internet.nl/site/helenchong.dev/3290581/"><img src="/assets/graphics/badges/internet.nl-websitetest.svg" alt="Badge with a green check mark and text: 100% score in website test, internet.nl" width="204" height="38" loading="lazy"></a>
</div>
<div id="wcb" class="carbonbadge">
<noscript>
<p>Looks like you have JavaScript disabled. JavaScript is required to display the <a href="https://www.websitecarbon.com/badge/">Website Carbon badge</a>.</p>
</noscript>
</div>
<script src="https://unpkg.com/website-carbon-badges@1.1.3/b.min.js" defer></script>
</section>
{{ js }}
const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
const websiteCarbonBadge = document.getElementById("wcb");
if (window.matchMedia && darkModePreference.matches) {
websiteCarbonBadge.classList.add("wcb-d");
}
darkModePreference.addEventListener("change", (event) => {
if (event.matches) {
websiteCarbonBadge.classList.add("wcb-d");
} else {
websiteCarbonBadge.classList.remove("wcb-d");
}
});
{{ /js }}

View File

@@ -1,6 +1,6 @@
<section class="section--home grid-center">
<h2 class="heading--home">Latest Blog Posts</h2>
{{ set postList = collections.posts |> toReversed |> itemLimit(3) }}
{{ include "partials/postslist.vto" }}
<a class="home__btn-link" href="/blog">More Blog Posts</a>
</section>
<section class="section--home grid-center">
<h2 class="heading--home">Latest Blog Posts</h2>
{{ set postList = collections.posts |> toReversed |> itemLimit(3) }}
{{ include "partials/postslist.vto" }}
<a class="home__btn-link" href="/blog">More Blog Posts</a>
</section>

View File

@@ -1,26 +1,26 @@
<section class="section--home grid-center">
<h2 class="heading--home">Notice</h2>
<div class="notice">
<h3 class="notice__heading">I Am Open for Hire</h3>
<p>I am available for new work opportunities and looking for a remote web developer position.</p>
<p>If you would like to get in touch, please <a href="/contact">contact me</a> or check out <a href="/resume">my résumé</a>.</p>
</div>
</section>
{{ css }}
.notice {
border: 0.2em solid var(--clr-accent-700);
background-color: var(--clr-accent-100);
padding: 1em;
max-width: 45rem;
display: grid;
gap: 1em;
}
.notice__heading { text-align: center; }
.notice-list { padding: 0; }
.notice-list__item {
display: grid;
gap: 0.3em;
}
{{ /css }}
<section class="section--home grid-center">
<h2 class="heading--home">Notice</h2>
<div class="notice">
<h3 class="notice__heading">I Am Open for Hire</h3>
<p>I am available for new work opportunities and looking for a remote web developer position.</p>
<p>If you would like to get in touch, please <a href="/contact">contact me</a> or check out <a href="/resume">my résumé</a>.</p>
</div>
</section>
{{ css }}
.notice {
border: 0.2em solid var(--clr-accent-700);
background-color: var(--clr-accent-100);
padding: 1em;
max-width: 45rem;
display: grid;
gap: 1em;
}
.notice__heading { text-align: center; }
.notice-list { padding: 0; }
.notice-list__item {
display: grid;
gap: 0.3em;
}
{{ /css }}

View File

@@ -1,10 +1,10 @@
<section class="profile">
<div class="profile__image-wrapper">
<img fetchpriority="high" src="/assets/graphics/profile/avatar-helen-chong.avif" alt="Avatar of Helen Chong">
</div>
<div class="profile__card">
<p class="profile__text">Hi! I am <span class="profile__text--highlight">Helen</span>, a graphic designer turned web developer from Malaysia. As a <span class="profile__text--highlight">Web Developer</span>, I love building the web to solve problems and express myself.</p>
<p class="profile__text">I focus on accessibility, inclusive design and progressive enhancement to create a user experience that puts people first. You can <a class="profile__link" href="/about">learn more about me</a> and check out <a class="profile__link" href="{{sitemeta.siteUrl}}/resume">my résumé</a> for my professional history.</p>
<p class="profile__text">I am always open to connect with more people, so do not hesitate to <a class="profile__link" href="/contact">get in touch with me</a>.<p>
</div>
<section class="profile">
<div class="profile__image-wrapper">
<img fetchpriority="high" src="/assets/graphics/profile/avatar-helen-chong.avif" alt="Avatar of Helen Chong">
</div>
<div class="profile__card">
<p class="profile__text">Hi! I am <span class="profile__text--highlight">Helen</span>, a graphic designer turned web developer from Malaysia. As a <span class="profile__text--highlight">Web Developer</span>, I love building the web to solve problems and express myself.</p>
<p class="profile__text">I focus on accessibility, inclusive design and progressive enhancement to create a user experience that puts people first. You can <a class="profile__link" href="/about">learn more about me</a> and check out <a class="profile__link" href="{{sitemeta.siteUrl}}/resume">my résumé</a> for my professional history.</p>
<p class="profile__text">I am always open to connect with more people, so do not hesitate to <a class="profile__link" href="/contact">get in touch with me</a>.<p>
</div>
</section>

View File

@@ -1,8 +1,8 @@
<section class="section--home">
<h2 class="heading--home">Featured Projects</h2>
<div class="project-grid">
{{ set projectList = collections.projects |> itemLimit(3) }}
{{ include "partials/project-card.vto" }}
</div>
<a class="home__btn-link" href="/projects">More Projects</a>
</section>
<section class="section--home">
<h2 class="heading--home">Featured Projects</h2>
<div class="project-grid">
{{ set projectList = collections.projects |> itemLimit(3) }}
{{ include "partials/project-card.vto" }}
</div>
<a class="home__btn-link" href="/projects">More Projects</a>
</section>

View File

@@ -1,61 +1,61 @@
<section class="section--home grid-center">
<h2 class="heading--home">Webrings</h2>
<p class="text-center">This site is a member of the following webrings:</p>
<div class="widgets webrings">
<nav class="a11y-webring-club" aria-labelledby="a11y-webring-club">
<h3 id="a11y-webring-club"><a rel="external" href="https://a11y-webring.club/">a11y-webring.club</a></h3>
<ul>
<li><a rel="external" referrerpolicy="strict-origin" href="https://a11y-webring.club/prev">Previous website</a></li>
<li><a rel="external" referrerpolicy="strict-origin" href="https://a11y-webring.club/random">Random website</a></li>
<li><a rel="external" referrerpolicy="strict-origin" href="https://a11y-webring.club/next">Next website</a></li>
</ul>
</nav>
<nav aria-labelledby="css-joy">
<h3 id="css-joy"><a href="https://cs.sjoy.lol/">CSS JOY Webring</a></h3>
<ul>
<li><a class="external-link" href="https://webri.ng/webring/cssjoy/previous?via=https://helenchong.dev/">Previous website</a></li>
<li><a class="external-link" href="https://webri.ng/webring/cssjoy/random?via=https://helenchong.dev/">Random website</a></li>
<li><a class="external-link" href="https://webri.ng/webring/cssjoy/next?via=https://helenchong.dev/">Next website</a></li>
</ul>
</nav>
<nav aria-labelledby="fediring">
<h3 id="fediring"><a href="https://fediring.net/">Fediring</a></h3>
<ul>
<li><a class="external-link" href="https://fediring.net/previous?host=helenchong.dev">Previous website</a></li>
<li><a class="external-link" href="https://fediring.net/random">Random website</a></li>
<li><a class="external-link" href="https://fediring.net/next?host=helenchong.dev">Next website</a></li>
</ul>
</nav>
<nav aria-labelledby="indieweb-webring">
<h3 id="indieweb-webring">An <a href="https://xn--sr8hvo.ws">IndieWeb Webring</a> 🕸💍</h3>
<ul>
<li><a href="https://xn--sr8hvo.ws/previous">Previous website</a></li>
<li><a href="https://xn--sr8hvo.ws/next">Next website</a></li>
</ul>
</nav>
<nav aria-labelledby="staticquest-webring">
<h3 id="staticquest-webring"><a href="https://static.quest">Static.Quest</a></h3>
<ul>
<li><a href="https://static.quest/previous/?host=helenchong.dev">Previous website</a></li>
<li><a href="https://static.quest/members">View members</a></li>
<li><a href="https://static.quest/next/?host=helenchong.dev">Next website</a></li>
<li><a href="https://static.quest/random">Random website</a></li>
</ul>
</nav>
</div>
</section>
<section class="section--home grid-center">
<h2 class="heading--home">Directories</h2>
<p class="text-center">{{ sitemeta.siteDomain }} is listed at the following directories:</p>
<ul class="widgets directories">
<li><a href="https://bukmark.club/">BUKMARK.CLUB</a></li>
<li><a href="https://darktheme.club/">The Darktheme Club</a></li>
<li><a href="https://www.11ty.dev/authors/">Eleventy Authors</a></li>
<li><a href="https://personalsit.es/">PersonalSit.es</a></li>
</ul>
<section class="section--home grid-center">
<h2 class="heading--home">Webrings</h2>
<p class="text-center">This site is a member of the following webrings:</p>
<div class="widgets webrings">
<nav class="a11y-webring-club" aria-labelledby="a11y-webring-club">
<h3 id="a11y-webring-club"><a rel="external" href="https://a11y-webring.club/">a11y-webring.club</a></h3>
<ul>
<li><a rel="external" referrerpolicy="strict-origin" href="https://a11y-webring.club/prev">Previous website</a></li>
<li><a rel="external" referrerpolicy="strict-origin" href="https://a11y-webring.club/random">Random website</a></li>
<li><a rel="external" referrerpolicy="strict-origin" href="https://a11y-webring.club/next">Next website</a></li>
</ul>
</nav>
<nav aria-labelledby="css-joy">
<h3 id="css-joy"><a href="https://cs.sjoy.lol/">CSS JOY Webring</a></h3>
<ul>
<li><a class="external-link" href="https://webri.ng/webring/cssjoy/previous?via=https://helenchong.dev/">Previous website</a></li>
<li><a class="external-link" href="https://webri.ng/webring/cssjoy/random?via=https://helenchong.dev/">Random website</a></li>
<li><a class="external-link" href="https://webri.ng/webring/cssjoy/next?via=https://helenchong.dev/">Next website</a></li>
</ul>
</nav>
<nav aria-labelledby="fediring">
<h3 id="fediring"><a href="https://fediring.net/">Fediring</a></h3>
<ul>
<li><a class="external-link" href="https://fediring.net/previous?host=helenchong.dev">Previous website</a></li>
<li><a class="external-link" href="https://fediring.net/random">Random website</a></li>
<li><a class="external-link" href="https://fediring.net/next?host=helenchong.dev">Next website</a></li>
</ul>
</nav>
<nav aria-labelledby="indieweb-webring">
<h3 id="indieweb-webring">An <a href="https://xn--sr8hvo.ws">IndieWeb Webring</a> 🕸💍</h3>
<ul>
<li><a href="https://xn--sr8hvo.ws/previous">Previous website</a></li>
<li><a href="https://xn--sr8hvo.ws/next">Next website</a></li>
</ul>
</nav>
<nav aria-labelledby="staticquest-webring">
<h3 id="staticquest-webring"><a href="https://static.quest">Static.Quest</a></h3>
<ul>
<li><a href="https://static.quest/previous/?host=helenchong.dev">Previous website</a></li>
<li><a href="https://static.quest/members">View members</a></li>
<li><a href="https://static.quest/next/?host=helenchong.dev">Next website</a></li>
<li><a href="https://static.quest/random">Random website</a></li>
</ul>
</nav>
</div>
</section>
<section class="section--home grid-center">
<h2 class="heading--home">Directories</h2>
<p class="text-center">{{ sitemeta.siteDomain }} is listed at the following directories:</p>
<ul class="widgets directories">
<li><a href="https://bukmark.club/">BUKMARK.CLUB</a></li>
<li><a href="https://darktheme.club/">The Darktheme Club</a></li>
<li><a href="https://www.11ty.dev/authors/">Eleventy Authors</a></li>
<li><a href="https://personalsit.es/">PersonalSit.es</a></li>
</ul>
</section>

View File

@@ -1,37 +1,37 @@
<!DOCTYPE html>
<html lang="{{ sitemeta.siteLang }}" id="top">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Open Graph meta data -->
<meta name="generator" content="{{ eleventy.generator }}">
<meta name="author" content="{{ sitemeta.siteAuthor.name }}">
<meta name="fediverse:creator" content="{{ sitemeta.siteAuthor.fediverse.handle }}">
<meta property="og:site_name" content="{{ sitemeta.siteName }}" />
<meta property="og:type" content="{{ tags && tags.includes("posts") ? "article" : "website" }}">
<meta name="description" content="{{ desc || sitemeta.siteDescription }}">
<meta property="og:url" content="{{ sitemeta.siteUrl }}{{ page.url ? page.url : '' }}">
<meta property="og:image" content="{{ sitemeta.siteUrl }}/assets/graphics/helenchong-meta-img.png">
<meta name="theme-color" content="#8200cd">
<!-- Fonts -->
{{ include "partials/fonts.vto" }}
<!-- Feeds -->
<link rel="alternate" type="application/rss+xml" title="{{ sitemeta.siteBlog.title }}" href="{{ sitemeta.feedPath }}">
<!-- CSS -->
{{ include "partials/css.vto" }}
<!-- Verifications -->
<link rel="authorization_/point" href="https://indieauth.com/auth">
<!-- JavaScript -->
{{ include "partials/js.vto" }}
<title>{{ if title }} {{ title }} | {{ /if }} {{ sitemeta.siteName }}</title>
</head>
<body>
<div class="skip-btn"><a href="#content">Skip to content</a></div>
{{ include "partials/header.vto" }}
<main id="content">
{{ content }}
</main>
{{ include "partials/footer.vto" }}
</body>
</html>
<!DOCTYPE html>
<html lang="{{ sitemeta.siteLang }}" id="top">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Open Graph meta data -->
<meta name="generator" content="{{ eleventy.generator }}">
<meta name="author" content="{{ sitemeta.siteAuthor.name }}">
<meta name="fediverse:creator" content="{{ sitemeta.siteAuthor.fediverse.handle }}">
<meta property="og:site_name" content="{{ sitemeta.siteName }}" />
<meta property="og:type" content="{{ tags && tags.includes("posts") ? "article" : "website" }}">
<meta name="description" content="{{ desc || sitemeta.siteDescription }}">
<meta property="og:url" content="{{ sitemeta.siteUrl }}{{ page.url ? page.url : '' }}">
<meta property="og:image" content="{{ sitemeta.siteUrl }}/assets/graphics/helenchong-meta-img.png">
<meta name="theme-color" content="#8200cd">
<!-- Fonts -->
{{ include "partials/fonts.vto" }}
<!-- Feeds -->
<link rel="alternate" type="application/rss+xml" title="{{ sitemeta.siteBlog.title }}" href="{{ sitemeta.feedPath }}">
<!-- CSS -->
{{ include "partials/css.vto" }}
<!-- Verifications -->
<link rel="authorization_/point" href="https://indieauth.com/auth">
<!-- JavaScript -->
{{ include "partials/js.vto" }}
<title>{{ if title }} {{ title }} | {{ /if }} {{ sitemeta.siteName }}</title>
</head>
<body>
<div class="skip-btn"><a href="#content">Skip to content</a></div>
{{ include "partials/header.vto" }}
<main id="content">
{{ content }}
</main>
{{ include "partials/footer.vto" }}
</body>
</html>

View File

@@ -1,67 +1,67 @@
---
layout: layouts/base
---
<header class="main__header">
{{ include "components/breadcrumbs.vto" }}
<h1 class="heading--main">{{ pageTitle || title }}</h1>
{{ if tags && tags.includes("posts") }}
<div class="blog__post--info">
<p>
<span class="text-bold">{{ content |> wordcount |> thousands }} words</span>.
<span class="text-bold">Posted:</span> <time datetime="{{ date }}">{{ date |> formatDate }}</time> by {{ sitemeta.siteAuthor.name }}
</p>
{{ if updated }}
<p><span class="text-bold">Last Updated:</span> <time datetime="{{ updated }}">{{ updated |> formatDate }}</time></p>
{{ /if }}
<p><span class="text-bold">Topics:</span>
{{ for topic of topics }}
<a href="/blog/topics/{{ topic |> slugify }}">{{ topic }}</a>{{ if topics.indexOf(topic) !== topics.length - 1 }}, {{ /if }}
{{ /for }}
</p>
</div>
{{ /if }}
</header>
<content-wrapper>
{{ if toc }}
{{ include "components/toc.vto" }}
{{ /if }}
{{ set contentEl = isArticle ? "article" : "div" }}
<{{contentEl}} class="prose">
{{ content }}
{{ if tags && tags.includes("posts") }}
{{ if collections.posts.length > 1 }}
<nav class="blog__post--pagination" aria-labelledby="nextprev-title">
<h2 class="visually-hidden" id="nextprev-title">Next and Previous Blog Posts</h2>
{{ set previousPost = collections.posts |> getPreviousCollectionItem }}
{{ set nextPost = collections.posts |> getNextCollectionItem }}
{{ if nextPost || previousPost }}
<ul class="blog__post--nextprev">
{{ if previousPost }}
<li class="blog__post--prev">
<p>Previous Post:</p>
<a href="{{ previousPost.url }}">{{ previousPost.data.title }}</a>
</li>
{{ /if }}
{{ if nextPost }}
<li class="blog__post--next">
<p>Next Post:</p>
<a href="{{ nextPost.url }}">{{ nextPost.data.title }}</a>
</li>
{{ /if }}
</ul>
{{ /if }}
</nav>
{{ /if }}
{{ /if }}
</{{contentEl}}>
{{ if tags && tags.includes("blog pages") }}
{{ include "partials/blognav.vto" }}
{{ /if }}
</content-wrapper>
---
layout: layouts/base
---
<header class="main__header">
{{ include "components/breadcrumbs.vto" }}
<h1 class="heading--main">{{ pageTitle || title }}</h1>
{{ if tags && tags.includes("posts") }}
<div class="blog__post--info">
<p>
<span class="text-bold">{{ content |> wordcount |> thousands }} words</span>.
<span class="text-bold">Posted:</span> <time datetime="{{ date }}">{{ date |> formatDate }}</time> by {{ sitemeta.siteAuthor.name }}
</p>
{{ if updated }}
<p><span class="text-bold">Last Updated:</span> <time datetime="{{ updated }}">{{ updated |> formatDate }}</time></p>
{{ /if }}
<p><span class="text-bold">Topics:</span>
{{ for topic of topics }}
<a href="/blog/topics/{{ topic |> slugify }}">{{ topic }}</a>{{ if topics.indexOf(topic) !== topics.length - 1 }}, {{ /if }}
{{ /for }}
</p>
</div>
{{ /if }}
</header>
<content-wrapper>
{{ if toc }}
{{ include "components/toc.vto" }}
{{ /if }}
{{ set contentEl = isArticle ? "article" : "div" }}
<{{contentEl}} class="prose">
{{ content }}
{{ if tags && tags.includes("posts") }}
{{ if collections.posts.length > 1 }}
<nav class="blog__post--pagination" aria-labelledby="nextprev-title">
<h2 class="visually-hidden" id="nextprev-title">Next and Previous Blog Posts</h2>
{{ set previousPost = collections.posts |> getPreviousCollectionItem }}
{{ set nextPost = collections.posts |> getNextCollectionItem }}
{{ if nextPost || previousPost }}
<ul class="blog__post--nextprev">
{{ if previousPost }}
<li class="blog__post--prev">
<p>Previous Post:</p>
<a href="{{ previousPost.url }}">{{ previousPost.data.title }}</a>
</li>
{{ /if }}
{{ if nextPost }}
<li class="blog__post--next">
<p>Next Post:</p>
<a href="{{ nextPost.url }}">{{ nextPost.data.title }}</a>
</li>
{{ /if }}
</ul>
{{ /if }}
</nav>
{{ /if }}
{{ /if }}
</{{contentEl}}>
{{ if tags && tags.includes("blog pages") }}
{{ include "partials/blognav.vto" }}
{{ /if }}
</content-wrapper>

View File

@@ -1,28 +1,28 @@
---
layout: layouts/base
hasBreadcrumbs: true
---
<div class="project--page">
<header class="main__header">
{{ include "components/breadcrumbs.vto" }}
<h1 class="heading--main">{{ title }}</h1>
</header>
<article class="project">
<div class="project__image-wrapper">
{{# <img src="{{ image }}" alt="{{ imageAlt }}"> #}}
<img src="{{ src image }}"
srcset="{{ srcset image }}"
sizes="(min-width: 1080px) 1020px, calc(94.74vw + 16px)"
alt="{{ imageAlt }}"
width="{{ imageWidth }}"
height="{{ imageHeight }}"
loading="lazy"
>
</div>
<div class="project__content prose">
{{ content }}
</div>
</article>
</div>
---
layout: layouts/base
hasBreadcrumbs: true
---
<div class="project--page">
<header class="main__header">
{{ include "components/breadcrumbs.vto" }}
<h1 class="heading--main">{{ title }}</h1>
</header>
<article class="project">
<div class="project__image-wrapper">
{{# <img src="{{ image }}" alt="{{ imageAlt }}"> #}}
<img src="{{ src image }}"
srcset="{{ srcset image }}"
sizes="(min-width: 1080px) 1020px, calc(94.74vw + 16px)"
alt="{{ imageAlt }}"
width="{{ imageWidth }}"
height="{{ imageHeight }}"
loading="lazy"
>
</div>
<div class="project__content prose">
{{ content }}
</div>
</article>
</div>

View File

@@ -1,9 +1,9 @@
---
layout: layouts/content
---
{{ content }}
{{ if updated }}
<p class="update-info">(This {{ keyword ? keyword : "page" }} was last updated on <time datetime="{{ updated }}">{{ updated |> formatDate }}</time>)</p>
{{ /if }}
---
layout: layouts/content
---
{{ content }}
{{ if updated }}
<p class="update-info">(This {{ keyword ? keyword : "page" }} was last updated on <time datetime="{{ updated }}">{{ updated |> formatDate }}</time>)</p>
{{ /if }}

View File

@@ -1,17 +1,17 @@
{{ set currentUrl }}{{ page.url }}{{ /set }}
<aside class="right-sidebar">
<nav class="blog__nav sidebar--sticky" aria-labelledby="blog-nav-title">
<h2 class="blog__nav--title" id="blog-nav-title">Blog Navigation</h2>
<ul class="blog__nav--links">
<li><a {{ if page.url == "/blog/" }} aria-current="page"{{ /if }} href="/blog/">Index</a></li>
{{ set navPages = collections.all |> eleventyNavigation("Blog") }}
{{ for entry of navPages }}
<li><a
{{ if entry.url == page.url }}aria-current="page"{{ /if }}
href="{{ entry.url }}">
{{ entry.title }}
</a></li>
{{ /for }}
</ul>
</nav>
</aside>
{{ set currentUrl }}{{ page.url }}{{ /set }}
<aside class="right-sidebar">
<nav class="blog__nav sidebar--sticky" aria-labelledby="blog-nav-title">
<h2 class="blog__nav--title" id="blog-nav-title">Blog Navigation</h2>
<ul class="blog__nav--links">
<li><a {{ if page.url == "/blog/" }} aria-current="page"{{ /if }} href="/blog/">Index</a></li>
{{ set navPages = collections.all |> eleventyNavigation("Blog") }}
{{ for entry of navPages }}
<li><a
{{ if entry.url == page.url }}aria-current="page"{{ /if }}
href="{{ entry.url }}">
{{ entry.title }}
</a></li>
{{ /for }}
</ul>
</nav>
</aside>

View File

@@ -1,16 +1,16 @@
{{ set cssFiles = [
"global",
"a11y-syntax-highlighting",
"general",
"home",
"components",
"projects",
"content",
"blog",
"socials",
"utility"
] }}
{{ for file of cssFiles }}
<link rel="stylesheet" href="{{'/assets/css/' + file + '.css'}}">
{{ /for }}
<style>{{ getBundle "css" }}</style>
{{ set cssFiles = [
"global",
"a11y-syntax-highlighting",
"general",
"home",
"components",
"projects",
"content",
"blog",
"socials",
"utility"
] }}
{{ for file of cssFiles }}
<link rel="stylesheet" href="{{'/assets/css/' + file + '.css'}}">
{{ /for }}
<style>{{ getBundle "css" }}</style>

View File

@@ -1,28 +1,28 @@
<link rel="preload" href="/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-regular.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-regular.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-700.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/assets/fonts/work-sans/work-sans-v19-latin-700.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<style>
@font-face {
font-display: swap;
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 400;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-regular.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 700;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-700.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Work Sans';
font-style: normal;
font-weight: 700;
src: url('/assets/fonts/work-sans/work-sans-v19-latin-700.woff2') format('woff2');
}
</style>
<link rel="stylesheet" href="/assets/fonts/fonts.css">
<link rel="preload" href="/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-regular.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-regular.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-700.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/assets/fonts/work-sans/work-sans-v19-latin-700.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<style>
@font-face {
font-display: swap;
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 400;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-regular.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 700;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-700.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Work Sans';
font-style: normal;
font-weight: 700;
src: url('/assets/fonts/work-sans/work-sans-v19-latin-700.woff2') format('woff2');
}
</style>
<link rel="stylesheet" href="/assets/fonts/fonts.css">

View File

@@ -1,49 +1,49 @@
<footer class="footer">
<h2 class="visually-hidden">Footer Navigation:</h2>
<ul class="inline-list">
<li>
<a href="{{ sitemeta.feedPath }}">
<svg class="inline-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M0 64C0 46.3 14.3 32 32 32c229.8 0 416 186.2 416 416c0 17.7-14.3 32-32 32s-32-14.3-32-32C384 253.6 226.4 96 32 96C14.3 96 0 81.7 0 64zM0 416a64 64 0 1 1 128 0A64 64 0 1 1 0 416zM32 160c159.1 0 288 128.9 288 288c0 17.7-14.3 32-32 32s-32-14.3-32-32c0-123.7-100.3-224-224-224c-17.7 0-32-14.3-32-32s14.3-32 32-32z"/></svg>RSS Feed</a>
</li>
{{ for link of collections["footer links"] }}
<li><a
{{ if link.url == page.url }}aria-current="page"{{ /if }}
href="{{ link.url }}"
>{{ link.data.title }}</a></li>
{{ /for }}
{{ for statement of collections.statements }}
<li><a
{{ if statement.url == page.url }}aria-current="page"{{ /if }}
href="{{ statement.url }}"
>{{ statement.data.title }}</a></li>
{{ /for }}
</ul>
<p>Made with 💜 by {{ sitemeta.siteAuthor.name }} • 2023{{ currentYear }}</p>
<p>Built with <a href="https://www.11ty.dev/">Eleventy</a> v{{ eleventy.version }} • <a href="https://git.helenchong.dev/helenchong/helenchong.dev" class="external-link">Source Code</a></p>
<div class="widgets site-btns">
<img src="/assets/graphics/helenchong-88x31.svg" alt="88-by-31 badge: my favicon, the white letters HC, next to the word Helen Chong on a purple background." width="88" height="31" loading="lazy">
</div>
{{ include "components/socials.vto" }}
<div class="widgets"><a href="https://buymeacoffee.com/helenchong"><img src="/assets/graphics/buymeacoffee.png" alt="Buy Me a Coffee" width="200" height="56" loading="lazy"></a></div>
{{ include "components/h-card.vto" }}
{{ include "components/top-btn.vto" }}
</footer>
{{ css }}
.footer {
width: 100%;
padding: 2em 1em clamp(2em, calc(100% - 3.5rem), 4em);
text-align: center;
display: flex;
gap: 0.7em;
flex-direction: column;
justify-content: center;
align-tems: center;
box-shadow: var(--bs-footer);
}
{{ /css }}
<footer class="footer">
<h2 class="visually-hidden">Footer Navigation:</h2>
<ul class="inline-list">
<li>
<a href="{{ sitemeta.feedPath }}">
<svg class="inline-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M0 64C0 46.3 14.3 32 32 32c229.8 0 416 186.2 416 416c0 17.7-14.3 32-32 32s-32-14.3-32-32C384 253.6 226.4 96 32 96C14.3 96 0 81.7 0 64zM0 416a64 64 0 1 1 128 0A64 64 0 1 1 0 416zM32 160c159.1 0 288 128.9 288 288c0 17.7-14.3 32-32 32s-32-14.3-32-32c0-123.7-100.3-224-224-224c-17.7 0-32-14.3-32-32s14.3-32 32-32z"/></svg>RSS Feed</a>
</li>
{{ for link of collections["footer links"] }}
<li><a
{{ if link.url == page.url }}aria-current="page"{{ /if }}
href="{{ link.url }}"
>{{ link.data.title }}</a></li>
{{ /for }}
{{ for statement of collections.statements }}
<li><a
{{ if statement.url == page.url }}aria-current="page"{{ /if }}
href="{{ statement.url }}"
>{{ statement.data.title }}</a></li>
{{ /for }}
</ul>
<p>Made with 💜 by {{ sitemeta.siteAuthor.name }} • 2023{{ currentYear }}</p>
<p>Built with <a href="https://www.11ty.dev/">Eleventy</a> v{{ eleventy.version }} • <a href="https://git.helenchong.dev/helenchong/helenchong.dev" class="external-link">Source Code</a></p>
<div class="widgets site-btns">
<img src="/assets/graphics/helenchong-88x31.svg" alt="88-by-31 badge: my favicon, the white letters HC, next to the word Helen Chong on a purple background." width="88" height="31" loading="lazy">
</div>
{{ include "components/socials.vto" }}
<div class="widgets"><a href="https://buymeacoffee.com/helenchong"><img src="/assets/graphics/buymeacoffee.png" alt="Buy Me a Coffee" width="200" height="56" loading="lazy"></a></div>
{{ include "components/h-card.vto" }}
{{ include "components/top-btn.vto" }}
</footer>
{{ css }}
.footer {
width: 100%;
padding: 2em 1em clamp(2em, calc(100% - 3.5rem), 4em);
text-align: center;
display: flex;
gap: 0.7em;
flex-direction: column;
justify-content: center;
align-tems: center;
box-shadow: var(--bs-footer);
}
{{ /css }}

View File

@@ -1,164 +1,164 @@
{{ set currentUrl }}{{ page.url }}{{ /set }}
{{ set navLinks }}
{{ set navPages = collections["header links"] |> eleventyNavigation }}
{{ for entry of navPages }}
<li><a
{{ if entry.url == page.url }} aria-current="page"{{ /if }}
class="header__link {{ if (currentUrl.includes('/blog') && entry.url.includes('/blog'))
|| (currentUrl.includes('/projects') && entry.url.includes('/projects'))
}} link--active{{ /if }}"
href="{{ entry.url }}">
{{ entry.title }}
</a></li>
{{ /for }}
{{ /set }}
<header class="header">
<a class="header__title" {{ if currentUrl === '/' }}aria-current="page"{{ /if }} href="/">
<img class="header__logo" src="/assets/favicon/HC-favicon.svg" alt="Helen Chong initials logo, to the home page">
</a>
<nav aria-labelledby="top-nav-title">
<p class="visually-hidden" id="top-nav-title">Main</p>
<ul class="header__navmenu header__links">{{ navLinks }}</ul>
<button class="header__toggle" popovertarget="nav-menu" aria-label="Toggle navigation menu">
<svg aria-hidden="true" focusable="false" width="1em" height="1em" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm306.7 69.1L162.4 380.6c-19.4 7.5-38.5-11.6-31-31l55.5-144.3c3.3-8.5 9.9-15.1 18.4-18.4l144.3-55.5c19.4-7.5 38.5 11.6 31 31L325.1 306.7c-3.2 8.5-9.9 15.1-18.4 18.4zM288 256a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
Explore
</button>
</nav>
<div popover id="nav-menu" class="header__popover">
<ul class="header__navmenu">{{ navLinks }}</ul>
</div>
</header>
{{ css }}
.header {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.5em 1em;
padding: 1rem 1.5rem;
background-color: var(--clr-body-bg);
box-shadow: var(--bs-main);
z-index: 998;
position: static;
top: 0;
}
.header__title {
color: var(--clr-accent-600);
font-size: 2rem;
margin: 0;
}
.header__logo {
aspect-ratio: 1 / 1;
height: 1.3em;
}
.header__title:hover > *,
.header__title:focus ,
.header__title:focus > * { outline: 0; }
.header__title > * { transition: transform 0.2s ease-out; }
.header__title:hover > *,
.header__title:focus > * { transform: scale(1.25); }
.header__toggle {
border: 0.125em solid var(--clr-accent-700);
border-radius: 0.2em;
background-color: var(--clr-body-bg);
color: var(--clr-accent-700);
font-size: 1.5rem;
font-weight: 600;
box-shadow: -4px 4px 0px var(--clr-accent-700);
display: none;
align-items: center;
gap: 0.15em;
padding-left: 0.25em;
padding-right: 0.26em;
}
.header__toggle svg { fill: var(--clr-accent-700); }
.header__toggle:focus {
outline: 0.15em solid var(--clr-accent-700);
outline-offset: 0.1em;
}
.header__navmenu {
list-style-type: "";
padding: 0;
margin: 0;
font-weight: 600;
color: var(--clr-accent-600);
text-align: center;
display: flex;
flex-wrap: wrap;
gap: 0.5em 1em;
}
.header__links { display: flex; }
.header__popover {
background-color: var(--clr-body-bg);
border: 0.15em solid var(--clr-accent-600);
padding: 1.5em;
box-shadow: var(--bs-main);
max-width: 80%;
}
.header__popover::backdrop {
background-color: black;
opacity: 0.5;
}
.header__navmenu [aria-current="page"],
.link--active { color: var(--clr-link-hover); }
.header__link {
position: relative;
font-size: 1.15em;
text-decoration: none;
}
.header__link::after {
content: "";
left: 0px;
bottom: -4px;
height: 2px;
width: 100%;
position: absolute;
background-color: var(--clr-link-hover);
transform: scaleX(0);
transition: transform 0.3s ease 0s;
}
.header__link:hover::after,
.header__navmenu [aria-current="page"]::after,
.link--active::after { transform: scaleX(1); }
.header__link:focus {
outline: 0.2em solid var(--clr-accent-600);
outline-offset: 0.1em;
}
@supports selector([popover]) {
.header__toggle { display: flex; }
.header__links { display: none; }
.header__navmenu { margin: 0; }
.header { position: sticky; }
}
/* For larger screen sizes */
@media (min-width: 640px) {
.header__title { margin-bottom: 0; }
}
@media only screen and (min-width: 530px) {
.header { position: sticky; }
.header__navmenu { margin: 0; }
.header__toggle, .header__popover { display: none; }
.header__links { display: flex; }
}
{{ /css }}
{{ set currentUrl }}{{ page.url }}{{ /set }}
{{ set navLinks }}
{{ set navPages = collections["header links"] |> eleventyNavigation }}
{{ for entry of navPages }}
<li><a
{{ if entry.url == page.url }} aria-current="page"{{ /if }}
class="header__link {{ if (currentUrl.includes('/blog') && entry.url.includes('/blog'))
|| (currentUrl.includes('/projects') && entry.url.includes('/projects'))
}} link--active{{ /if }}"
href="{{ entry.url }}">
{{ entry.title }}
</a></li>
{{ /for }}
{{ /set }}
<header class="header">
<a class="header__title" {{ if currentUrl === '/' }}aria-current="page"{{ /if }} href="/">
<img class="header__logo" src="/assets/favicon/HC-favicon.svg" alt="Helen Chong initials logo, to the home page">
</a>
<nav aria-labelledby="top-nav-title">
<p class="visually-hidden" id="top-nav-title">Main</p>
<ul class="header__navmenu header__links">{{ navLinks }}</ul>
<button class="header__toggle" popovertarget="nav-menu" aria-label="Toggle navigation menu">
<svg aria-hidden="true" focusable="false" width="1em" height="1em" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm306.7 69.1L162.4 380.6c-19.4 7.5-38.5-11.6-31-31l55.5-144.3c3.3-8.5 9.9-15.1 18.4-18.4l144.3-55.5c19.4-7.5 38.5 11.6 31 31L325.1 306.7c-3.2 8.5-9.9 15.1-18.4 18.4zM288 256a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
Explore
</button>
</nav>
<div popover id="nav-menu" class="header__popover">
<ul class="header__navmenu">{{ navLinks }}</ul>
</div>
</header>
{{ css }}
.header {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.5em 1em;
padding: 1rem 1.5rem;
background-color: var(--clr-body-bg);
box-shadow: var(--bs-main);
z-index: 998;
position: static;
top: 0;
}
.header__title {
color: var(--clr-accent-600);
font-size: 2rem;
margin: 0;
}
.header__logo {
aspect-ratio: 1 / 1;
height: 1.3em;
}
.header__title:hover > *,
.header__title:focus ,
.header__title:focus > * { outline: 0; }
.header__title > * { transition: transform 0.2s ease-out; }
.header__title:hover > *,
.header__title:focus > * { transform: scale(1.25); }
.header__toggle {
border: 0.125em solid var(--clr-accent-700);
border-radius: 0.2em;
background-color: var(--clr-body-bg);
color: var(--clr-accent-700);
font-size: 1.5rem;
font-weight: 600;
box-shadow: -4px 4px 0px var(--clr-accent-700);
display: none;
align-items: center;
gap: 0.15em;
padding-left: 0.25em;
padding-right: 0.26em;
}
.header__toggle svg { fill: var(--clr-accent-700); }
.header__toggle:focus {
outline: 0.15em solid var(--clr-accent-700);
outline-offset: 0.1em;
}
.header__navmenu {
list-style-type: "";
padding: 0;
margin: 0;
font-weight: 600;
color: var(--clr-accent-600);
text-align: center;
display: flex;
flex-wrap: wrap;
gap: 0.5em 1em;
}
.header__links { display: flex; }
.header__popover {
background-color: var(--clr-body-bg);
border: 0.15em solid var(--clr-accent-600);
padding: 1.5em;
box-shadow: var(--bs-main);
max-width: 80%;
}
.header__popover::backdrop {
background-color: black;
opacity: 0.5;
}
.header__navmenu [aria-current="page"],
.link--active { color: var(--clr-link-hover); }
.header__link {
position: relative;
font-size: 1.15em;
text-decoration: none;
}
.header__link::after {
content: "";
left: 0px;
bottom: -4px;
height: 2px;
width: 100%;
position: absolute;
background-color: var(--clr-link-hover);
transform: scaleX(0);
transition: transform 0.3s ease 0s;
}
.header__link:hover::after,
.header__navmenu [aria-current="page"]::after,
.link--active::after { transform: scaleX(1); }
.header__link:focus {
outline: 0.2em solid var(--clr-accent-600);
outline-offset: 0.1em;
}
@supports selector([popover]) {
.header__toggle { display: flex; }
.header__links { display: none; }
.header__navmenu { margin: 0; }
.header { position: sticky; }
}
/* For larger screen sizes */
@media (min-width: 640px) {
.header__title { margin-bottom: 0; }
}
@media only screen and (min-width: 530px) {
.header { position: sticky; }
.header__navmenu { margin: 0; }
.header__toggle, .header__popover { display: none; }
.header__links { display: flex; }
}
{{ /css }}

View File

@@ -1,7 +1,7 @@
{{ if toc }}
<script src="/assets/js/details-utils.js" defer></script>
{{ /if }}
{{ if hasCodeBlock }}
<script src="/assets/js/copycode.js" defer></script>
{{ /if }}
<script src="{{ getBundleFileUrl 'js' }}" defer></script>
{{ if toc }}
<script src="/assets/js/details-utils.js" defer></script>
{{ /if }}
{{ if hasCodeBlock }}
<script src="/assets/js/copycode.js" defer></script>
{{ /if }}
<script src="{{ getBundleFileUrl 'js' }}" defer></script>

View File

@@ -1,15 +1,15 @@
<ul class="blog__postlist">
{{ for post of postList }}
<li>
<p class="blog__postlist--title">
<a href="{{ post.url }}">
{{ if post.data.title }}{{ post.data.title }}
{{ else }}
<code>{{ post.url }}</code>
{{ /if }}
</a>
</p>
<p><time datetime="{{ post.date }}">{{ post.date |> formatDate }}</time></p>
</li>
{{ /for }}
</ul>
<ul class="blog__postlist">
{{ for post of postList }}
<li>
<p class="blog__postlist--title">
<a href="{{ post.url }}">
{{ if post.data.title }}{{ post.data.title }}
{{ else }}
<code>{{ post.url }}</code>
{{ /if }}
</a>
</p>
<p><time datetime="{{ post.date }}">{{ post.date |> formatDate }}</time></p>
</li>
{{ /for }}
</ul>

View File

@@ -1,35 +1,35 @@
{{ set currentUrl }}{{ page.url }}{{ /set }}
{{ set headingLevel = currentUrl === '/' ? 3 : 2 }}
{{ set homePageImgSizes = "(min-width: 1180px) 327px, (min-width: 880px) calc(26.79vw + 15px), (min-width: 580px) calc(50vw - 43px), calc(99.62vw - 44px)" }}
{{ set projectPageImgSizes = "(min-width: 1540px) 707px, (min-width: 1020px) calc(43.8vw + 41px), (min-width: 820px) 709px, calc(90.8vw - 17px)" }}
{{ set resProjectImgSizes }}{{ currentUrl === '/' ? homePageImgSizes : projectPageImgSizes }}{{ /set }}
{{ for project of projectList }}
<article class="project-card">
<div class="project-card__image-wrapper">
{{# <img src="{{ project.data.image }}" alt="{{ project.data.imageAlt }}"> #}}
<img src="{{ src project.data.image }}"
srcset="{{ srcset project.data.image }}"
sizes="{{ resProjectImgSizes }}"
alt="{{ project.data.imageAlt }}"
width="{{ project.data.imageWidth }}"
height="{{ project.data.imageHeight }}"
loading="lazy"
>
</div>
<div class="project-card__body">
<div class="project-card__tags">
{{ for tag of project.data.tech }}
<span class="project-card__tag">{{ tag }}</span>
{{ /for }}
</div>
<h{{headingLevel}} class="project-card__title">{{ project.data.title }}</h{{headingLevel}}>
<p class="project-card__summary">{{ project.data.summary }}</p>
<p>
<svg aria-hidden="true" class="inline-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"/></svg>
<a class="project-card__link" href="{{ project.url }}">Project case study of {{ project.data.title }}</a>
</p>
</div>
</article>
{{ /for }}
{{ set currentUrl }}{{ page.url }}{{ /set }}
{{ set headingLevel = currentUrl === '/' ? 3 : 2 }}
{{ set homePageImgSizes = "(min-width: 1180px) 327px, (min-width: 880px) calc(26.79vw + 15px), (min-width: 580px) calc(50vw - 43px), calc(99.62vw - 44px)" }}
{{ set projectPageImgSizes = "(min-width: 1540px) 707px, (min-width: 1020px) calc(43.8vw + 41px), (min-width: 820px) 709px, calc(90.8vw - 17px)" }}
{{ set resProjectImgSizes }}{{ currentUrl === '/' ? homePageImgSizes : projectPageImgSizes }}{{ /set }}
{{ for project of projectList }}
<article class="project-card">
<div class="project-card__image-wrapper">
{{# <img src="{{ project.data.image }}" alt="{{ project.data.imageAlt }}"> #}}
<img src="{{ src project.data.image }}"
srcset="{{ srcset project.data.image }}"
sizes="{{ resProjectImgSizes }}"
alt="{{ project.data.imageAlt }}"
width="{{ project.data.imageWidth }}"
height="{{ project.data.imageHeight }}"
loading="lazy"
>
</div>
<div class="project-card__body">
<div class="project-card__tags">
{{ for tag of project.data.tech }}
<span class="project-card__tag">{{ tag }}</span>
{{ /for }}
</div>
<h{{headingLevel}} class="project-card__title">{{ project.data.title }}</h{{headingLevel}}>
<p class="project-card__summary">{{ project.data.summary }}</p>
<p>
<svg aria-hidden="true" class="inline-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"/></svg>
<a class="project-card__link" href="{{ project.url }}">Project case study of {{ project.data.title }}</a>
</p>
</div>
</article>
{{ /for }}

View File

@@ -1,234 +1,234 @@
/*
* a11y theme for JavaScript, CSS, and HTML
* Based on the okaidia theme: https://github.com/PrismJS/prism/blob/gh-pages/themes/prism-okaidia.css
* @author ericwbailey
*/
/*
* MARK: Setup
*/
@layer vendor-prism {
@media (forced-colors: none), (forced-colors: active) and (prefers-color-scheme: light), (prefers-color-scheme: dark) {
:root {
--prism-a11y-border-radius: 0.3em;
--prism-a11y-font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
--prism-a11y-line-height: 1.5;
--prism-a11y-code-block-margin: 0.5em 0;
--prism-a11y-code-block-padding: 1em;
--prism-a11y-code-inline-padding: 0.1em;
--prism-a11y-width-border: 1px;
--prism-a11y-width-tab: 2;
}
}
@media (forced-colors: none) and (prefers-color-scheme: light) {
:root {
--prism-a11y-color-background: hsla(0, 0%, 100%, 1);
--prism-a11y-color-text-no-token: hsla(0, 0%, 33%, 1);
--prism-a11y-color-text-comment: hsla(16, 100%, 25%, 1);
--prism-a11y-color-text-blue: hsla(195, 100%, 30%, 1);
--prism-a11y-color-text-green: hsla(120, 100%, 25%, 1);
--prism-a11y-color-text-gray: hsla(0, 0%, 41%, 1);
--prism-a11y-color-text-purple: hsla(282, 100%, 41%, 1);
--prism-a11y-color-text-red: hsla(2, 80%, 47%, 1);
--prism-a11y-color-text-yellow: hsla(43, 74%, 30%, 1);
--prism-a11y-plugin-color-border: hsla(43, 74%, 30%, 0.55);
--prism-a11y-plugin-color-background: hsla(43, 74%, 30%, 0.1);
}
}
@media (forced-colors: none) and (prefers-color-scheme: dark) {
:root {
--prism-a11y-color-background: hsla(0, 0%, 17%, 1);
--prism-a11y-color-text-no-token: hsla(60, 30%, 96%, 1);
--prism-a11y-color-text-comment: hsla(54, 32%, 75%, 1);
--prism-a11y-color-text-blue: hsla(180, 100%, 44%, 1);
--prism-a11y-color-text-green: hsla(80, 75%, 55%, 1);
--prism-a11y-color-text-gray: hsla(60, 30%, 96%, 1);
--prism-a11y-color-text-purple: hsla(291, 30%, 83%, 1);
--prism-a11y-color-text-red: hsla(17, 100%, 74%, 1);
--prism-a11y-color-text-yellow: hsla(51, 100%, 50%, 1);
--prism-a11y-plugin-color-border: hsla(51, 100%, 50%, 0.55);
--prism-a11y-plugin-color-background: hsla(51, 100%, 50%, 0.1);
}
}
@media (forced-colors: none), (forced-colors: active) and (prefers-color-scheme: light), (prefers-color-scheme: dark) {
/*
* MARK: Theme
*/
:where(
code[class*="language-"],
pre[class*="language-"]
) {
color: var(--prism-a11y-color-text-no-token);
background: var(--prism-a11y-color-background);
font-family: var(--prism-a11y-font-family);
text-align: start;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: var(--prism-a11y-line-height);
-moz-tab-size: var(--prism-a11y-width-tab);
-o-tab-size: var(--prism-a11y-width-tab);
tab-size: var(--prism-a11y-width-tab);
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: var(--prism-a11y-code-block-padding);
margin: var(--prism-a11y-code-block-margin);
overflow: auto;
border-radius: var(--prism-a11y-border-radius);
}
:where(
:not(pre) > code[class*="language-"],
pre[class*="language-"]
) {
background: var(--prism-a11y-color-background);
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: var(--prism-a11y-code-inline-padding);
border-radius: var(--prism-a11y-border-radius);
white-space: normal;
}
:where(
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata
) {
color: var(--prism-a11y-color-text-comment);
}
.token.punctuation {
color: var(--prism-a11y-color-text-gray);
}
:where(
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted
) {
color: var(--prism-a11y-color-text-red);
}
:where(
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable,
.token.keyword
) {
color: var(--prism-a11y-color-text-blue);
}
:where(
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted
) {
color: var(--prism-a11y-color-text-green);
}
:where(
.token.atrule,
.token.attr-value,
.token.function,
.token.regex,
.token.important
) {
color: var(--prism-a11y-color-text-yellow);
}
:where(
.token.boolean,
.token.number,
.token.keyword
) {
color: var(--prism-a11y-color-text-purple);
}
:where(
.token.important,
.token.bold
) {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
/*
* MARK: Plugin support
*/
/* Line highlight */
.line-highlight {
background: var(--prism-a11y-plugin-color-background);
border-top: var(--prism-a11y-width-border) solid var(--prism-a11y-plugin-color-border);
border-bottom: var(--prism-a11y-width-border) solid var(--prism-a11y-plugin-color-border);
}
/* Line numbers */
.line-numbers .line-numbers-rows {
border-right: var(--prism-a11y-width-border) solid var(--prism-a11y-color-text-no-token);
}
.line-numbers-rows > span:before {
color: var(--prism-a11y-color-text-comment);
}
}
/*
* MARK: Forced color mode support
*/
@media (forced-colors: active) {
:root {
--prism-a11y-color-background: Canvas;
--prism-a11y-color-text-no-token: CanvasText;
--prism-a11y-color-text-comment: GrayText;
--prism-a11y-color-text-blue: LinkText;
--prism-a11y-color-text-gray: LinkText;
--prism-a11y-color-text-green: CanvasText;
--prism-a11y-color-text-purple: CanvasText;
--prism-a11y-color-text-red: CanvasText;
--prism-a11y-color-text-yellow: GrayText;
--prism-a11y-plugin-color-border: LinkText;
--prism-a11y-plugin-color-background: Canvas;
}
:where(
.token.boolean,
.token.number,
.token.keyword
) {
font-weight: bold;
}
}
/*
* a11y theme for JavaScript, CSS, and HTML
* Based on the okaidia theme: https://github.com/PrismJS/prism/blob/gh-pages/themes/prism-okaidia.css
* @author ericwbailey
*/
/*
* MARK: Setup
*/
@layer vendor-prism {
@media (forced-colors: none), (forced-colors: active) and (prefers-color-scheme: light), (prefers-color-scheme: dark) {
:root {
--prism-a11y-border-radius: 0.3em;
--prism-a11y-font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
--prism-a11y-line-height: 1.5;
--prism-a11y-code-block-margin: 0.5em 0;
--prism-a11y-code-block-padding: 1em;
--prism-a11y-code-inline-padding: 0.1em;
--prism-a11y-width-border: 1px;
--prism-a11y-width-tab: 2;
}
}
@media (forced-colors: none) and (prefers-color-scheme: light) {
:root {
--prism-a11y-color-background: hsla(0, 0%, 100%, 1);
--prism-a11y-color-text-no-token: hsla(0, 0%, 33%, 1);
--prism-a11y-color-text-comment: hsla(16, 100%, 25%, 1);
--prism-a11y-color-text-blue: hsla(195, 100%, 30%, 1);
--prism-a11y-color-text-green: hsla(120, 100%, 25%, 1);
--prism-a11y-color-text-gray: hsla(0, 0%, 41%, 1);
--prism-a11y-color-text-purple: hsla(282, 100%, 41%, 1);
--prism-a11y-color-text-red: hsla(2, 80%, 47%, 1);
--prism-a11y-color-text-yellow: hsla(43, 74%, 30%, 1);
--prism-a11y-plugin-color-border: hsla(43, 74%, 30%, 0.55);
--prism-a11y-plugin-color-background: hsla(43, 74%, 30%, 0.1);
}
}
@media (forced-colors: none) and (prefers-color-scheme: dark) {
:root {
--prism-a11y-color-background: hsla(0, 0%, 17%, 1);
--prism-a11y-color-text-no-token: hsla(60, 30%, 96%, 1);
--prism-a11y-color-text-comment: hsla(54, 32%, 75%, 1);
--prism-a11y-color-text-blue: hsla(180, 100%, 44%, 1);
--prism-a11y-color-text-green: hsla(80, 75%, 55%, 1);
--prism-a11y-color-text-gray: hsla(60, 30%, 96%, 1);
--prism-a11y-color-text-purple: hsla(291, 30%, 83%, 1);
--prism-a11y-color-text-red: hsla(17, 100%, 74%, 1);
--prism-a11y-color-text-yellow: hsla(51, 100%, 50%, 1);
--prism-a11y-plugin-color-border: hsla(51, 100%, 50%, 0.55);
--prism-a11y-plugin-color-background: hsla(51, 100%, 50%, 0.1);
}
}
@media (forced-colors: none), (forced-colors: active) and (prefers-color-scheme: light), (prefers-color-scheme: dark) {
/*
* MARK: Theme
*/
:where(
code[class*="language-"],
pre[class*="language-"]
) {
color: var(--prism-a11y-color-text-no-token);
background: var(--prism-a11y-color-background);
font-family: var(--prism-a11y-font-family);
text-align: start;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: var(--prism-a11y-line-height);
-moz-tab-size: var(--prism-a11y-width-tab);
-o-tab-size: var(--prism-a11y-width-tab);
tab-size: var(--prism-a11y-width-tab);
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: var(--prism-a11y-code-block-padding);
margin: var(--prism-a11y-code-block-margin);
overflow: auto;
border-radius: var(--prism-a11y-border-radius);
}
:where(
:not(pre) > code[class*="language-"],
pre[class*="language-"]
) {
background: var(--prism-a11y-color-background);
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: var(--prism-a11y-code-inline-padding);
border-radius: var(--prism-a11y-border-radius);
white-space: normal;
}
:where(
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata
) {
color: var(--prism-a11y-color-text-comment);
}
.token.punctuation {
color: var(--prism-a11y-color-text-gray);
}
:where(
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted
) {
color: var(--prism-a11y-color-text-red);
}
:where(
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable,
.token.keyword
) {
color: var(--prism-a11y-color-text-blue);
}
:where(
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted
) {
color: var(--prism-a11y-color-text-green);
}
:where(
.token.atrule,
.token.attr-value,
.token.function,
.token.regex,
.token.important
) {
color: var(--prism-a11y-color-text-yellow);
}
:where(
.token.boolean,
.token.number,
.token.keyword
) {
color: var(--prism-a11y-color-text-purple);
}
:where(
.token.important,
.token.bold
) {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
/*
* MARK: Plugin support
*/
/* Line highlight */
.line-highlight {
background: var(--prism-a11y-plugin-color-background);
border-top: var(--prism-a11y-width-border) solid var(--prism-a11y-plugin-color-border);
border-bottom: var(--prism-a11y-width-border) solid var(--prism-a11y-plugin-color-border);
}
/* Line numbers */
.line-numbers .line-numbers-rows {
border-right: var(--prism-a11y-width-border) solid var(--prism-a11y-color-text-no-token);
}
.line-numbers-rows > span:before {
color: var(--prism-a11y-color-text-comment);
}
}
/*
* MARK: Forced color mode support
*/
@media (forced-colors: active) {
:root {
--prism-a11y-color-background: Canvas;
--prism-a11y-color-text-no-token: CanvasText;
--prism-a11y-color-text-comment: GrayText;
--prism-a11y-color-text-blue: LinkText;
--prism-a11y-color-text-gray: LinkText;
--prism-a11y-color-text-green: CanvasText;
--prism-a11y-color-text-purple: CanvasText;
--prism-a11y-color-text-red: CanvasText;
--prism-a11y-color-text-yellow: GrayText;
--prism-a11y-plugin-color-border: LinkText;
--prism-a11y-plugin-color-background: Canvas;
}
:where(
.token.boolean,
.token.number,
.token.keyword
) {
font-weight: bold;
}
}
}

View File

@@ -1,40 +1,40 @@
/* Blog Styles */
.blog__postlist,
.blog__post--info { display: grid; }
.blog__postlist { gap: 0.5em; }
.blog__cat--title { margin-bottom: 1em; }
.blog__nav--title {
font-size: var(--fs-h3);
margin-bottom: 0.3em;
}
.blog__postlist--title {
font-size: var(--fs-bloglist-title);
font-weight: 700;
}
.blog__post--info {
gap: 0.3em;
font-size: var(--fs-main-txt);
}
.blog__post--desc { margin-bottom: 1em; }
.blog__post--pagination {
padding-top: 0.8em;
margin-top: 2.5em;
border-top: 0.1em solid var(--clr-slate-600);
}
.blog__post--nextprev {
list-style-type: "";
padding: 0;
margin: 0;
display: grid;
gap: 1em;
grid-template-columns: repeat(2, 1fr);
grid-template-areas: 'prev next';
}
.blog__post--prev { grid-area: prev; }
/* Blog Styles */
.blog__postlist,
.blog__post--info { display: grid; }
.blog__postlist { gap: 0.5em; }
.blog__cat--title { margin-bottom: 1em; }
.blog__nav--title {
font-size: var(--fs-h3);
margin-bottom: 0.3em;
}
.blog__postlist--title {
font-size: var(--fs-bloglist-title);
font-weight: 700;
}
.blog__post--info {
gap: 0.3em;
font-size: var(--fs-main-txt);
}
.blog__post--desc { margin-bottom: 1em; }
.blog__post--pagination {
padding-top: 0.8em;
margin-top: 2.5em;
border-top: 0.1em solid var(--clr-slate-600);
}
.blog__post--nextprev {
list-style-type: "";
padding: 0;
margin: 0;
display: grid;
gap: 1em;
grid-template-columns: repeat(2, 1fr);
grid-template-areas: 'prev next';
}
.blog__post--prev { grid-area: prev; }
.blog__post--next { grid-area: next; }

View File

@@ -1,74 +1,74 @@
/* Skip to content button */
.skip-btn a {
position: absolute;
display: inline-block;
top: -1000px;
left: 50%;
transform: translate(-50%, 0);
overflow: hidden;
transition: top 0.5s ease;
background: var(--clr-body-bg);
z-index: 1000;
font-size: 1.25rem;
padding: 0.3em 0.7em;
}
.skip-btn a:focus {
top: 0;
transition: top 0.3s ease;
outline: 0.2em solid var(--clr-accent-600);
}
/* Inline lists */
.inline-list {
justify-self: center;
list-style-type: "";
text-align: center;
margin: 0;
padding: 0;
display: flex;
gap: 0 var(--sz-list-gap);
flex-wrap: wrap;
justify-content: center;
}
.inline-list li:not(:last-child)::after {
content: '•';
padding-left: 0.5em;
}
/* Web widgets */
.widgets {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
* + .widgets { margin-top: var(--sz-list-gap); }
/* Site buttons */
.site-btns {
align-items: center;
gap: var(--sz-list-gap);
}
/* Breadcumbs */
.breadcrumbs {
display: flex;
gap: 0.5em;
flex-wrap: wrap;
justify-content: center;
font-size: var(--fs-main-txt);
}
/* Remove external link icon from Website Carbon badge */
#wcb_a::after { all: revert; }
/* Button to copy code snippets */
.cc-btn {
font-size: 0.8em;
display: flex;
align-items: center;
gap: 0.2em;
border: 0.115em solid var(--clr-code-border);
border-radius: 0.2em;
/* Skip to content button */
.skip-btn a {
position: absolute;
display: inline-block;
top: -1000px;
left: 50%;
transform: translate(-50%, 0);
overflow: hidden;
transition: top 0.5s ease;
background: var(--clr-body-bg);
z-index: 1000;
font-size: 1.25rem;
padding: 0.3em 0.7em;
}
.skip-btn a:focus {
top: 0;
transition: top 0.3s ease;
outline: 0.2em solid var(--clr-accent-600);
}
/* Inline lists */
.inline-list {
justify-self: center;
list-style-type: "";
text-align: center;
margin: 0;
padding: 0;
display: flex;
gap: 0 var(--sz-list-gap);
flex-wrap: wrap;
justify-content: center;
}
.inline-list li:not(:last-child)::after {
content: '•';
padding-left: 0.5em;
}
/* Web widgets */
.widgets {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
* + .widgets { margin-top: var(--sz-list-gap); }
/* Site buttons */
.site-btns {
align-items: center;
gap: var(--sz-list-gap);
}
/* Breadcumbs */
.breadcrumbs {
display: flex;
gap: 0.5em;
flex-wrap: wrap;
justify-content: center;
font-size: var(--fs-main-txt);
}
/* Remove external link icon from Website Carbon badge */
#wcb_a::after { all: revert; }
/* Button to copy code snippets */
.cc-btn {
font-size: 0.8em;
display: flex;
align-items: center;
gap: 0.2em;
border: 0.115em solid var(--clr-code-border);
border-radius: 0.2em;
}

View File

@@ -1,60 +1,60 @@
.main__header, content-wrapper { display: grid; }
content-wrapper { gap: 1rem; }
.main__header {
padding: 3em var(--sz-main-padding);
text-align: center;
}
.prose {
font-size: var(--fs-main-txt);
color: var(--clr-slate-600);
padding: 0 var(--sz-main-padding) var(--sz-content-bottom);
}
.prose > * + :not([class]:not([class*="language-"])) {
margin-block-start: var(--sz-paragraph-margin);
}
.prose > * + h2, .prose > * + .h2,
.prose > h3, .prose > * + .h3,
.prose > h4, .prose > h5, .prose > h6 { margin-block-start: 2em; }
.bio { max-width: 65ch; }
.right-sidebar { padding: 0 clamp(1em, 5%, 1.5em) var(--sz-content-bottom); }
.sidebar--sticky {
position: sticky;
top: 6em;
}
/* Heading Wrapper and Anchor */
.heading-wrapper {
display: flex;
gap: 0.3em;
align-items: baseline;
}
* + .heading-wrapper { margin-block-start: 1.8em; }
.heading-anchor {
order: -1;
text-underline-offset: 0.1em;
}
.heading-anchor:focus {
outline: 0.13em solid currentColor;
outline-offset: 0.05em;
}
/* Desktop main content layout */
@media only screen and (min-width: 60rem) {
content-wrapper {
grid-template-areas: 'leftbar article rightbar';
grid-template-columns: 1fr minmax(min(60ch, 40vw), 2.5fr) 1fr;
}
.prose { grid-area: article; }
.left-sidebar { grid-area: leftbar; }
.right-sidebar { grid-area: rightbar; }
}
.main__header, content-wrapper { display: grid; }
content-wrapper { gap: 1rem; }
.main__header {
padding: 3em var(--sz-main-padding);
text-align: center;
}
.prose {
font-size: var(--fs-main-txt);
color: var(--clr-slate-600);
padding: 0 var(--sz-main-padding) var(--sz-content-bottom);
}
.prose > * + :not([class]:not([class*="language-"])) {
margin-block-start: var(--sz-paragraph-margin);
}
.prose > * + h2, .prose > * + .h2,
.prose > h3, .prose > * + .h3,
.prose > h4, .prose > h5, .prose > h6 { margin-block-start: 2em; }
.bio { max-width: 65ch; }
.right-sidebar { padding: 0 clamp(1em, 5%, 1.5em) var(--sz-content-bottom); }
.sidebar--sticky {
position: sticky;
top: 6em;
}
/* Heading Wrapper and Anchor */
.heading-wrapper {
display: flex;
gap: 0.3em;
align-items: baseline;
}
* + .heading-wrapper { margin-block-start: 1.8em; }
.heading-anchor {
order: -1;
text-underline-offset: 0.1em;
}
.heading-anchor:focus {
outline: 0.13em solid currentColor;
outline-offset: 0.05em;
}
/* Desktop main content layout */
@media only screen and (min-width: 60rem) {
content-wrapper {
grid-template-areas: 'leftbar article rightbar';
grid-template-columns: 1fr minmax(min(60ch, 40vw), 2.5fr) 1fr;
}
.prose { grid-area: article; }
.left-sidebar { grid-area: leftbar; }
.right-sidebar { grid-area: rightbar; }
}

View File

@@ -1,87 +1,87 @@
/* General Styles */
body {
color: var(--clr-txt-default);
background-color: var(--clr-body-bg);
display: grid;
font-size: var(--fs-base);
font-family: var(--ff-regular);
}
main { display: grid; }
h1, h2, h3 { line-height: 1.3; }
h1, h2, h3, h4, h5, h6 {
font-weight: 700;
color: var(--clr-accent-600);
font-family: var(--ff-heading);
}
h1 { font-size: var(--fs-h1); }
h2, .h2 { font-size: var(--fs-h2); }
h3, .h3 { font-size: var(--fs-h3); }
h4 { font-size: var(--fs-h4); }
:focus { outline: 0.15em solid currentColor; }
a { color: var(--clr-accent-700); }
a:hover {
text-decoration: none;
color: var(--clr-link-hover);
}
a:focus, a:hover > img { outline: 0.15em solid currentColor; }
a[href^="http"]:not([href*="helenchong.dev"]):not(:has(img, svg, picture)),
.external-link { padding-right: 1.1em; }
a[href^="http"]:not([href*="helenchong.dev"]):not(:has(img, svg, picture))::after,
.external-link::after {
position: absolute;
content: "";
display: inline-block;
width: 1em;
height: 1em;
margin: 0 0.2em;
background-color: currentColor;
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3C!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--%3E%3Cpath d='M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z'/%3E%3C/svg%3E");
mask-repeat: no-repeat;
mask-size: 80%;
transform: translateY(0.28em);
}
ul:not([class]), ol:not([class]) {
display: grid;
gap: var(--sz-list-gap);
}
ul:not([class]) ul, ol:not([class]) ol,
ul:not([class]) ol, ol:not([class]) ul { margin-top: var(--sz-list-gap); }
blockquote {
padding: 0.8em 1em;
background-color: var(--clr-code-bg);
border-inline-start: 0.15em solid var(--clr-code-border);
margin-bottom: 1em;
}
blockquote > * + :not([class]) { margin-block-start: var(--sz-paragraph-margin); }
code {
font-family: var(--ff-monospace);
word-break: break-word;
}
:not(pre) > code {
padding: 0.125em 0.25em;
background-color: var(--clr-code-bg);
font-size: 0.85em;
}
pre {
border: 0.1em solid var(--clr-code-border);
max-width: 90vw;
overflow: auto;
padding: 1rem;
}
pre > code { white-space: pre; }
details:focus { outline-offset: 0.2em; }
/* General Styles */
body {
color: var(--clr-txt-default);
background-color: var(--clr-body-bg);
display: grid;
font-size: var(--fs-base);
font-family: var(--ff-regular);
}
main { display: grid; }
h1, h2, h3 { line-height: 1.3; }
h1, h2, h3, h4, h5, h6 {
font-weight: 700;
color: var(--clr-accent-600);
font-family: var(--ff-heading);
}
h1 { font-size: var(--fs-h1); }
h2, .h2 { font-size: var(--fs-h2); }
h3, .h3 { font-size: var(--fs-h3); }
h4 { font-size: var(--fs-h4); }
:focus { outline: 0.15em solid currentColor; }
a { color: var(--clr-accent-700); }
a:hover {
text-decoration: none;
color: var(--clr-link-hover);
}
a:focus, a:hover > img { outline: 0.15em solid currentColor; }
a[href^="http"]:not([href*="helenchong.dev"]):not(:has(img, svg, picture)),
.external-link { padding-right: 1.1em; }
a[href^="http"]:not([href*="helenchong.dev"]):not(:has(img, svg, picture))::after,
.external-link::after {
position: absolute;
content: "";
display: inline-block;
width: 1em;
height: 1em;
margin: 0 0.2em;
background-color: currentColor;
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3C!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--%3E%3Cpath d='M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z'/%3E%3C/svg%3E");
mask-repeat: no-repeat;
mask-size: 80%;
transform: translateY(0.28em);
}
ul:not([class]), ol:not([class]) {
display: grid;
gap: var(--sz-list-gap);
}
ul:not([class]) ul, ol:not([class]) ol,
ul:not([class]) ol, ol:not([class]) ul { margin-top: var(--sz-list-gap); }
blockquote {
padding: 0.8em 1em;
background-color: var(--clr-code-bg);
border-inline-start: 0.15em solid var(--clr-code-border);
margin-bottom: 1em;
}
blockquote > * + :not([class]) { margin-block-start: var(--sz-paragraph-margin); }
code {
font-family: var(--ff-monospace);
word-break: break-word;
}
:not(pre) > code {
padding: 0.125em 0.25em;
background-color: var(--clr-code-bg);
font-size: 0.85em;
}
pre {
border: 0.1em solid var(--clr-code-border);
max-width: 90vw;
overflow: auto;
padding: 1rem;
}
pre > code { white-space: pre; }
details:focus { outline-offset: 0.2em; }

View File

@@ -1,98 +1,98 @@
/* ------------------- */
/* Custom Properties */
/* ------------------- */
:root {
color-scheme: light dark;
--clr-body-bg: light-dark(#fefefe, #121212);
--clr-txt-default: light-dark(#000, #fff);
--clr-slate-300: light-dark(#5d5d5d, #f8f8f8);
--clr-slate-600: light-dark(#383838, #f6f6f6);
--clr-accent-100: light-dark(#f1d9ff, #4c004c);
--clr-accent-600: light-dark(#a200ff, #da85ff);
--clr-accent-700: light-dark(#8200cd, #cb7aff);
--clr-accent-800: light-dark(#5a008e, #d09dff);
--clr-link-hover: light-dark(#c83c00, #fe6625);
--clr-code-bg: light-dark(#ededed, #3a3a3a);
--clr-code-border: light-dark(#616161, #868686);
--clr-top-btn-bg: #4153b6;
--clr-top-btn-txt: #fff;
--ff-regular: 'IBM Plex Sans', Avenir, Montserrat, Corbel, 'URW Gothic', source-sans-pro, sans-serif;
--ff-heading: 'Work Sans', Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif;
--ff-monospace: 'Intel One Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
--fs-base: clamp(1.05rem, 1.013rem + 0.186vw, 1.18rem);
--fs-h1: clamp(2.2rem, 2.057rem + 0.714vw, 2.7rem);
--fs-h2: clamp(1.9rem, 1.786rem + 0.571vw, 2.3rem);
--fs-h3: clamp(1.7rem, 1.614rem + 0.429vw, 2rem);
--fs-h4: clamp(1.6rem, 1.543rem + 0.286vw, 1.8rem);
--fs-main-txt: 1.12em;
--fs-profile: 1.2em;
--fs-bloglist-title: 1.2em;
--sz-main-padding: clamp(1em, 5%, 1.5em);
--sz-paragraph-margin: 1.15em;
--sz-list-gap: 0.5em;
--sz-content-bottom: 4em;
--bs-main: 0 10px 15px -3px light-dark(rgba(0, 0, 0, 0.3), rgba(162, 162, 162, 0.5)),
0 4px 6px -4px light-dark(rgb(0 0 0 / 0.2), rgba(129, 129, 129, 0.5));
--bs-footer: 0 -10px 15px -3px light-dark(rgba(0, 0, 0, 0.3), rgba(215, 215, 215, 0.5));
}
/* ------------------- */
/* CSS Reset */
/* ------------------- */
@layer reset {
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
}
body {
min-height: 100vh;
line-height: 1.5;
}
h1, h2, h3, h4, h5, h6,
button, input, label {
line-height: 1.1;
}
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
h1, h2, h3, h4, h5, h6 {
text-wrap: balance;
}
img, picture, video, canvas {
max-width: 100%;
height: auto;
display: block;
}
input, button, textarea, select {
font: inherit;
}
textarea:not([rows]) {
min-height: 10em;
}
:target {
scroll-margin-block: 5ex;
}
[popover] {
margin: auto;
}
}
/* ------------------- */
/* Custom Properties */
/* ------------------- */
:root {
color-scheme: light dark;
--clr-body-bg: light-dark(#fefefe, #121212);
--clr-txt-default: light-dark(#000, #fff);
--clr-slate-300: light-dark(#5d5d5d, #f8f8f8);
--clr-slate-600: light-dark(#383838, #f6f6f6);
--clr-accent-100: light-dark(#f1d9ff, #4c004c);
--clr-accent-600: light-dark(#a200ff, #da85ff);
--clr-accent-700: light-dark(#8200cd, #cb7aff);
--clr-accent-800: light-dark(#5a008e, #d09dff);
--clr-link-hover: light-dark(#c83c00, #fe6625);
--clr-code-bg: light-dark(#ededed, #3a3a3a);
--clr-code-border: light-dark(#616161, #868686);
--clr-top-btn-bg: #4153b6;
--clr-top-btn-txt: #fff;
--ff-regular: 'IBM Plex Sans', Avenir, Montserrat, Corbel, 'URW Gothic', source-sans-pro, sans-serif;
--ff-heading: 'Work Sans', Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif;
--ff-monospace: 'Intel One Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
--fs-base: clamp(1.05rem, 1.013rem + 0.186vw, 1.18rem);
--fs-h1: clamp(2.2rem, 2.057rem + 0.714vw, 2.7rem);
--fs-h2: clamp(1.9rem, 1.786rem + 0.571vw, 2.3rem);
--fs-h3: clamp(1.7rem, 1.614rem + 0.429vw, 2rem);
--fs-h4: clamp(1.6rem, 1.543rem + 0.286vw, 1.8rem);
--fs-main-txt: 1.12em;
--fs-profile: 1.2em;
--fs-bloglist-title: 1.2em;
--sz-main-padding: clamp(1em, 5%, 1.5em);
--sz-paragraph-margin: 1.15em;
--sz-list-gap: 0.5em;
--sz-content-bottom: 4em;
--bs-main: 0 10px 15px -3px light-dark(rgba(0, 0, 0, 0.3), rgba(162, 162, 162, 0.5)),
0 4px 6px -4px light-dark(rgb(0 0 0 / 0.2), rgba(129, 129, 129, 0.5));
--bs-footer: 0 -10px 15px -3px light-dark(rgba(0, 0, 0, 0.3), rgba(215, 215, 215, 0.5));
}
/* ------------------- */
/* CSS Reset */
/* ------------------- */
@layer reset {
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
}
body {
min-height: 100vh;
line-height: 1.5;
}
h1, h2, h3, h4, h5, h6,
button, input, label {
line-height: 1.1;
}
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
h1, h2, h3, h4, h5, h6 {
text-wrap: balance;
}
img, picture, video, canvas {
max-width: 100%;
height: auto;
display: block;
}
input, button, textarea, select {
font: inherit;
}
textarea:not([rows]) {
min-height: 10em;
}
:target {
scroll-margin-block: 5ex;
}
[popover] {
margin: auto;
}
}

View File

@@ -1,125 +1,125 @@
/* Home page sections */
.home--container { margin: 0 auto 2em; }
.section--home {
display: grid;
font-size: var(--fs-main-txt);
padding: 2em 1em;
}
.section--home,
.profile {
max-width: 65rem;
font-size: var(--fs-main-txt);
justify-self: center;
}
.profile {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2em;
margin-bottom: 3em;
padding: 0 1em;
}
.profile__image-wrapper {
display: grid;
place-content: center;
}
.profile__image-wrapper img {
width: 250px;
height: 250px;
border-radius: 9999px;
}
.profile__card {
display: flex;
flex-direction: column;
flex: 1;
}
.profile__text {
margin-bottom: 1rem;
color: var(--clr-slate-600);
}
.profile__text--lead {
font-weight: 500;
font-size: var(--fs-profile);
}
.profile__text--highlight {
font-weight: bold;
font-size: var(--fs-profile);
color: var(--clr-accent-600);
}
.profile__text--emphasis {
font-weight: bold;
font-size: 1.125rem;
}
.profile__link { font-weight: bold; }
/* For larger screen sizes */
@media (min-width: 640px) {
.profile {
flex-direction: row;
padding: 0 1em;
}
}
/* Home headings */
.home__title {
text-align: center;
margin-top: 1em;
margin-bottom: 0.7em;
padding: 0 1em;
}
.heading--home {
margin-bottom: 0.8em;
text-align: center;
}
/* Home button links */
.home__btn-link {
display: block;
margin: 1.3em auto 0;
border: 0.15em solid currentColor;
text-decoration: none;
padding: 0.5em 1em;
border-radius: 0.5em;
font-weight: 700;
}
.home__btn-link:focus {
outline: 0.2em solid var(--clr-accent-700);
outline-offset: 0.15em;
}
/* Webrings and Badges */
.widgets.webrings {
gap: 2em;
margin-top: 1.5em;
}
.widgets.webrings h3 {
font-size: 1.5rem;
margin-bottom: 0.7em;
}
.widgets.directories {
padding: 0;
gap: var(--sz-list-gap) 1.2em;
}
.home__badges {
--badge-gap: 0.5em;
gap: var(--badge-gap);
margin-bottom: var(--badge-gap);
align-items: center;
/* Home page sections */
.home--container { margin: 0 auto 2em; }
.section--home {
display: grid;
font-size: var(--fs-main-txt);
padding: 2em 1em;
}
.section--home,
.profile {
max-width: 65rem;
font-size: var(--fs-main-txt);
justify-self: center;
}
.profile {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2em;
margin-bottom: 3em;
padding: 0 1em;
}
.profile__image-wrapper {
display: grid;
place-content: center;
}
.profile__image-wrapper img {
width: 250px;
height: 250px;
border-radius: 9999px;
}
.profile__card {
display: flex;
flex-direction: column;
flex: 1;
}
.profile__text {
margin-bottom: 1rem;
color: var(--clr-slate-600);
}
.profile__text--lead {
font-weight: 500;
font-size: var(--fs-profile);
}
.profile__text--highlight {
font-weight: bold;
font-size: var(--fs-profile);
color: var(--clr-accent-600);
}
.profile__text--emphasis {
font-weight: bold;
font-size: 1.125rem;
}
.profile__link { font-weight: bold; }
/* For larger screen sizes */
@media (min-width: 640px) {
.profile {
flex-direction: row;
padding: 0 1em;
}
}
/* Home headings */
.home__title {
text-align: center;
margin-top: 1em;
margin-bottom: 0.7em;
padding: 0 1em;
}
.heading--home {
margin-bottom: 0.8em;
text-align: center;
}
/* Home button links */
.home__btn-link {
display: block;
margin: 1.3em auto 0;
border: 0.15em solid currentColor;
text-decoration: none;
padding: 0.5em 1em;
border-radius: 0.5em;
font-weight: 700;
}
.home__btn-link:focus {
outline: 0.2em solid var(--clr-accent-700);
outline-offset: 0.15em;
}
/* Webrings and Badges */
.widgets.webrings {
gap: 2em;
margin-top: 1.5em;
}
.widgets.webrings h3 {
font-size: 1.5rem;
margin-bottom: 0.7em;
}
.widgets.directories {
padding: 0;
gap: var(--sz-list-gap) 1.2em;
}
.home__badges {
--badge-gap: 0.5em;
gap: var(--badge-gap);
margin-bottom: var(--badge-gap);
align-items: center;
}

View File

@@ -1,97 +1,97 @@
/* Project Styles */
.project__heading {
padding: 1rem 0;
font-size: 2.5rem;
}
.project-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
}
.project-card {
box-shadow: var(--bs-main);
border-radius: 0.5em;
border: 0.15em solid var(--clr-accent-700);
}
.project-card__image-wrapper img {
width: 100%;
height: auto;
border-top-left-radius: 0.375em;
border-top-right-radius: 0.375em;
transition: transform 0.3s ease 0s;
}
.project-card__image-wrapper:hover img {
transform: scale(1.05);
}
.project-card__body { padding: 1em; }
.project-card__title {
margin-bottom: 1rem;
font-size: 1.7rem;
line-height: 1.3;
}
.project-card__title a:focus { outline: 0.15em solid var(--clr-txt-default); }
.project-card__tags {
margin-bottom: 1rem;
display: flex;
flex-wrap: wrap;
gap: 0.5em;
}
.project-card__tag {
display: flex;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.7em;
background-color: var(--clr-accent-100);
color: var(--clr-accent-800);
}
.project-card__summary {
margin-bottom: 1rem;
color: var(--clr-slate-600);
}
.project-card__link {
font-weight: 600;
color: var(--clr-accent-600);
margin-bottom: 0.6em;
}
.project-card__link:focus { outline: 0.15em solid var(--clr-accent-700); }
.project-card__link-icon {
display: inline-block;
vertical-align: text-bottom;
width: auto;
height: 1.2em;
margin-left: -0.2em;
}
/* Project Page Styles */
.project-list {
display: flex;
flex-direction: column;
gap: 3rem;
max-width: 42rem;
margin: 0 auto 4rem auto;
}
.project--page {
margin: 0 auto;
max-width: 60rem;
}
.project__image-wrapper { margin-bottom: 1.5rem; }
.project__image-wrapper img {
margin-bottom: 2rem;
border-radius: 0.25rem;
/* Project Styles */
.project__heading {
padding: 1rem 0;
font-size: 2.5rem;
}
.project-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
}
.project-card {
box-shadow: var(--bs-main);
border-radius: 0.5em;
border: 0.15em solid var(--clr-accent-700);
}
.project-card__image-wrapper img {
width: 100%;
height: auto;
border-top-left-radius: 0.375em;
border-top-right-radius: 0.375em;
transition: transform 0.3s ease 0s;
}
.project-card__image-wrapper:hover img {
transform: scale(1.05);
}
.project-card__body { padding: 1em; }
.project-card__title {
margin-bottom: 1rem;
font-size: 1.7rem;
line-height: 1.3;
}
.project-card__title a:focus { outline: 0.15em solid var(--clr-txt-default); }
.project-card__tags {
margin-bottom: 1rem;
display: flex;
flex-wrap: wrap;
gap: 0.5em;
}
.project-card__tag {
display: flex;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.7em;
background-color: var(--clr-accent-100);
color: var(--clr-accent-800);
}
.project-card__summary {
margin-bottom: 1rem;
color: var(--clr-slate-600);
}
.project-card__link {
font-weight: 600;
color: var(--clr-accent-600);
margin-bottom: 0.6em;
}
.project-card__link:focus { outline: 0.15em solid var(--clr-accent-700); }
.project-card__link-icon {
display: inline-block;
vertical-align: text-bottom;
width: auto;
height: 1.2em;
margin-left: -0.2em;
}
/* Project Page Styles */
.project-list {
display: flex;
flex-direction: column;
gap: 3rem;
max-width: 42rem;
margin: 0 auto 4rem auto;
}
.project--page {
margin: 0 auto;
max-width: 60rem;
}
.project__image-wrapper { margin-bottom: 1.5rem; }
.project__image-wrapper img {
margin-bottom: 2rem;
border-radius: 0.25rem;
}

View File

@@ -1,29 +1,29 @@
/* Socials Style */
.socials {
grid-area: scial;
display: grid;
place-content: center;
}
* + .socials { margin-top: 0.7em; }
.socials__links {
list-style-type: "";
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 0.5em;
}
.socials a { display: inline-flex; }
.socials a:hover,
.socials a:focus {
outline: 0.15em solid var(--clr-accent-700);
outline-offset: 0.15em;
}
.socials__icon {
height: 2rem;
fill: var(--clr-accent-700);
/* Socials Style */
.socials {
grid-area: scial;
display: grid;
place-content: center;
}
* + .socials { margin-top: 0.7em; }
.socials__links {
list-style-type: "";
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 0.5em;
}
.socials a { display: inline-flex; }
.socials a:hover,
.socials a:focus {
outline: 0.15em solid var(--clr-accent-700);
outline-offset: 0.15em;
}
.socials__icon {
height: 2rem;
fill: var(--clr-accent-700);
}

View File

@@ -1,42 +1,42 @@
/* Utility Classes */
.heading--main {
padding: 1rem 0;
font-size: var(--fs-h1);
font-weight: 700;
color: var(--clr-accent-600);
}
.text-center { text-align: center; }
.text-bold { font-weight: 700; }
.el-top-margin { margin-top: 1.5em; }
.email-encoded b { display: none; }
.update-info { margin-top: 3em; }
.inline-icon {
display: inline-block;
vertical-align: -10%;
height: 0.9em;
fill: currentColor;
}
a > .inline-icon { padding-inline-end: .25em; }
.grid-center {
display: grid;
place-content: center;
}
.hidden { display: none; }
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border: 0;
outline: 0;
outline-offset: 0;
}
/* Utility Classes */
.heading--main {
padding: 1rem 0;
font-size: var(--fs-h1);
font-weight: 700;
color: var(--clr-accent-600);
}
.text-center { text-align: center; }
.text-bold { font-weight: 700; }
.el-top-margin { margin-top: 1.5em; }
.email-encoded b { display: none; }
.update-info { margin-top: 3em; }
.inline-icon {
display: inline-block;
vertical-align: -10%;
height: 0.9em;
fill: currentColor;
}
a > .inline-icon { padding-inline-end: .25em; }
.grid-center {
display: grid;
place-content: center;
}
.hidden { display: none; }
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border: 0;
outline: 0;
outline-offset: 0;
}

View File

@@ -1,97 +1,97 @@
/* IBM Plex Sans */
@font-face {
font-display: swap;
font-family: 'IBM Plex Sans';
font-style: italic;
font-weight: 400;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-italic.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 600;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-600.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'IBM Plex Sans';
font-style: italic;
font-weight: 600;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-600italic.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'IBM Plex Sans';
font-style: italic;
font-weight: 700;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-700italic.woff2') format('woff2');
}
/* Work Sans */
@font-face {
font-display: swap;
font-family: 'Work Sans';
font-style: normal;
font-weight: 400;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-regular.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Work Sans';
font-style: italic;
font-weight: 400;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-italic.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Work Sans';
font-style: italic;
font-weight: 700;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-700italic.woff2') format('woff2');
}
/* Intel One Mono */
@font-face {
font-display: swap;
font-family: 'Intel One Mono';
font-style: normal;
font-weight: 400;
src: url('/assets/fonts/intel-one-mono/IntelOneMono-Regular.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Intel One Mono';
font-style: italic;
font-weight: 400;
src: url('/assets/fonts/intel-one-mono/IntelOneMono-Italic.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Intel One Mono';
font-style: normal;
font-weight: 600;
src: url('/assets/fonts/intel-one-mono/IntelOneMono-Medium.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Intel One Mono';
font-style: italic;
font-weight: 600;
src: url('/assets/fonts/intel-one-mono/IntelOneMono-MediumItalic.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Intel One Mono';
font-style: normal;
font-weight: 700;
src: url('/assets/fonts/intel-one-mono/IntelOneMono-Bold.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Intel One Mono';
font-style: italic;
font-weight: 700;
src: url('/assets/fonts/intel-one-mono/IntelOneMono-BoldItalic.woff2') format('woff2');
}
/* IBM Plex Sans */
@font-face {
font-display: swap;
font-family: 'IBM Plex Sans';
font-style: italic;
font-weight: 400;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-italic.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 600;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-600.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'IBM Plex Sans';
font-style: italic;
font-weight: 600;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-600italic.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'IBM Plex Sans';
font-style: italic;
font-weight: 700;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-700italic.woff2') format('woff2');
}
/* Work Sans */
@font-face {
font-display: swap;
font-family: 'Work Sans';
font-style: normal;
font-weight: 400;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-regular.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Work Sans';
font-style: italic;
font-weight: 400;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-italic.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Work Sans';
font-style: italic;
font-weight: 700;
src: url('/assets/fonts/ibm-plex-sans/ibm-plex-sans-v19-latin-700italic.woff2') format('woff2');
}
/* Intel One Mono */
@font-face {
font-display: swap;
font-family: 'Intel One Mono';
font-style: normal;
font-weight: 400;
src: url('/assets/fonts/intel-one-mono/IntelOneMono-Regular.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Intel One Mono';
font-style: italic;
font-weight: 400;
src: url('/assets/fonts/intel-one-mono/IntelOneMono-Italic.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Intel One Mono';
font-style: normal;
font-weight: 600;
src: url('/assets/fonts/intel-one-mono/IntelOneMono-Medium.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Intel One Mono';
font-style: italic;
font-weight: 600;
src: url('/assets/fonts/intel-one-mono/IntelOneMono-MediumItalic.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Intel One Mono';
font-style: normal;
font-weight: 700;
src: url('/assets/fonts/intel-one-mono/IntelOneMono-Bold.woff2') format('woff2');
}
@font-face {
font-display: swap;
font-family: 'Intel One Mono';
font-style: italic;
font-weight: 700;
src: url('/assets/fonts/intel-one-mono/IntelOneMono-BoldItalic.woff2') format('woff2');
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,63 +1,63 @@
/*
Based on ttntm's code to add a button to copy code snippets:
https://ttntm.me/blog/adding-a-copy-button-to-code-blocks/
*/
function createCopyBtn(blockIndex) {
return `<div class="cc-wrapper d-none d-sm-block">
<button class="cc-btn btn-muted shadow" data-target="${blockIndex}">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none"d="M0 0h24v24H0z"fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2"/><path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z"/></svg>
Copy Code
</button>
</div>`;
}
async function copyCode(block) {
const code = document.querySelector(`[data-block-id="${block}"]`);
const doCopy = async() => await navigator.clipboard.writeText(code?.innerText ?? '');
if (!navigator.userAgent.includes('Firefox')) {
const result = await navigator.permissions.query({ name: 'clipboard-write' });
if (result.state === 'granted' || result.state === 'prompt') {
doCopy();
}
} else {
doCopy();
}
}
async function handleCopyBtnClick(event) {
const btn = event?.target;
const btnTarget = btn?.getAttribute('data-target');
if (btn && btnTarget) {
const originalText = btn.innerHTML;
await copyCode(btnTarget);
btn.innerHTML = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" /><path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M9 14l2 2l4 -4" /></svg> Copied!';
setTimeout(() => {
btn.innerHTML = originalText;
}, 1500);
}
}
document.addEventListener('DOMContentLoaded', () => {
const allCodeBlocks = Array.from(document.querySelectorAll('pre[class^="language-"]'));
allCodeBlocks.forEach((b, i) => {
const code = b.childNodes[0];
const codeBlockIndex = `cb-${i}`;
b.insertAdjacentHTML('afterend', createCopyBtn(codeBlockIndex));
code.setAttribute('data-block-id', codeBlockIndex);
})
const allCopyBtns = Array.from(document.querySelectorAll('.cc-btn'));
allCopyBtns.forEach((btn) => {
btn.addEventListener('click', handleCopyBtnClick);
})
/*
Based on ttntm's code to add a button to copy code snippets:
https://ttntm.me/blog/adding-a-copy-button-to-code-blocks/
*/
function createCopyBtn(blockIndex) {
return `<div class="cc-wrapper d-none d-sm-block">
<button class="cc-btn btn-muted shadow" data-target="${blockIndex}">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none"d="M0 0h24v24H0z"fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2"/><path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z"/></svg>
Copy Code
</button>
</div>`;
}
async function copyCode(block) {
const code = document.querySelector(`[data-block-id="${block}"]`);
const doCopy = async() => await navigator.clipboard.writeText(code?.innerText ?? '');
if (!navigator.userAgent.includes('Firefox')) {
const result = await navigator.permissions.query({ name: 'clipboard-write' });
if (result.state === 'granted' || result.state === 'prompt') {
doCopy();
}
} else {
doCopy();
}
}
async function handleCopyBtnClick(event) {
const btn = event?.target;
const btnTarget = btn?.getAttribute('data-target');
if (btn && btnTarget) {
const originalText = btn.innerHTML;
await copyCode(btnTarget);
btn.innerHTML = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" /><path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M9 14l2 2l4 -4" /></svg> Copied!';
setTimeout(() => {
btn.innerHTML = originalText;
}, 1500);
}
}
document.addEventListener('DOMContentLoaded', () => {
const allCodeBlocks = Array.from(document.querySelectorAll('pre[class^="language-"]'));
allCodeBlocks.forEach((b, i) => {
const code = b.childNodes[0];
const codeBlockIndex = `cb-${i}`;
b.insertAdjacentHTML('afterend', createCopyBtn(codeBlockIndex));
code.setAttribute('data-block-id', codeBlockIndex);
})
const allCopyBtns = Array.from(document.querySelectorAll('.cc-btn'));
allCopyBtns.forEach((btn) => {
btn.addEventListener('click', handleCopyBtnClick);
})
})

View File

@@ -1,3 +1,3 @@
export default {
tags: "blog pages"
export default {
tags: "blog pages"
}

View File

@@ -1,13 +1,13 @@
---
title: Blog Archive
layout: layouts/content
tags: blog pages
hasBreadcrumbs: true
eleventyNavigation:
key: Blog Posts
title: Archive
parent: Blog
order: 3
---
{{ set postList = collections.posts |> toReversed }}
{{ include "partials/postslist.vto" }}
---
title: Blog Archive
layout: layouts/content
tags: blog pages
hasBreadcrumbs: true
eleventyNavigation:
key: Blog Posts
title: Archive
parent: Blog
order: 3
---
{{ set postList = collections.posts |> toReversed }}
{{ include "partials/postslist.vto" }}

View File

@@ -1,14 +1,14 @@
---
title: Welcome to Helen Codes
desc: Welcome to Helen Chong's coding and tech blog.
date: 2023-08-21
topics: ["about this blog"]
---
My name is Helen Chong. I am a self-taught developer with 8 years of working experience as a graphic designer.
I created this blog to document my coding learnings and talk about various tech topics.
You can check out [my portfolio website](https://helenclx.github.io).
Enjoy!
---
title: Welcome to Helen Codes
desc: Welcome to Helen Chong's coding and tech blog.
date: 2023-08-21
topics: ["about this blog"]
---
My name is Helen Chong. I am a self-taught developer with 8 years of working experience as a graphic designer.
I created this blog to document my coding learnings and talk about various tech topics.
You can check out [my portfolio website](https://helenclx.github.io).
Enjoy!

View File

@@ -1,16 +1,16 @@
---
title: Built A TickyBot Clone for Chingu's Solo Projects
desc: I built a TickyBot Clone for Chingu's Solo Project.
date: 2023-08-24
topics: ["chingu"]
---
On 11 August 2023, I decided to join [Chingu](https://www.chingu.io/) after discovering its existence through the [Scrimba Podcast](https://www.chingu.io/), specifically [Episode 127](https://scrimba.com/podcast/are-you-a-new-developer-follow-this-one-tip-with-scrimba-student-danny/). I have learned coding since December 2022, and one of the challenges aspiring self-taught developers face is gaining practical experience that can prepare them for developer jobs, so when I discovered Chingu, I thought it could be an opportunity for me to gain experience in working in group developer projects, thus I registered on the platform and joined the Chingu community on Discord.
The first step needed to start participating in Chingu's projects is to submit a solo project for approval. I chose to build a [TickyBot Clone](https://github.com/chingu-voyages/soloproject-tier1-tickybot-clone) using HTML, CSS and vanilla JavaScript. On 21 August, I finished coding [my TickyBot Clone](https://github.com/helenclx/TickyBot-Clone) and submitted the project to Chingu. On the next day, my solo project was approved by Chingu, to my great delight.
![Screenshot of my TickyBot clone](https://raw.githubusercontent.com/helenclx/TickyBot-Clone/home/screenshot-tickybot-clone.png)
You can view my TickyBot Clone's [live site](http://helenclx.github.io/TickyBot-Clone/) and [GitHub repository](https://github.com/helenclx/TickyBot-Clone).
My next step was to sign up for the next Chingu Voyage. I am looking forward to learning to work in a group developer project. Wish me luck!
---
title: Built A TickyBot Clone for Chingu's Solo Projects
desc: I built a TickyBot Clone for Chingu's Solo Project.
date: 2023-08-24
topics: ["chingu"]
---
On 11 August 2023, I decided to join [Chingu](https://www.chingu.io/) after discovering its existence through the [Scrimba Podcast](https://www.chingu.io/), specifically [Episode 127](https://scrimba.com/podcast/are-you-a-new-developer-follow-this-one-tip-with-scrimba-student-danny/). I have learned coding since December 2022, and one of the challenges aspiring self-taught developers face is gaining practical experience that can prepare them for developer jobs, so when I discovered Chingu, I thought it could be an opportunity for me to gain experience in working in group developer projects, thus I registered on the platform and joined the Chingu community on Discord.
The first step needed to start participating in Chingu's projects is to submit a solo project for approval. I chose to build a [TickyBot Clone](https://github.com/chingu-voyages/soloproject-tier1-tickybot-clone) using HTML, CSS and vanilla JavaScript. On 21 August, I finished coding [my TickyBot Clone](https://github.com/helenclx/TickyBot-Clone) and submitted the project to Chingu. On the next day, my solo project was approved by Chingu, to my great delight.
![Screenshot of my TickyBot clone](https://raw.githubusercontent.com/helenclx/TickyBot-Clone/home/screenshot-tickybot-clone.png)
You can view my TickyBot Clone's [live site](http://helenclx.github.io/TickyBot-Clone/) and [GitHub repository](https://github.com/helenclx/TickyBot-Clone).
My next step was to sign up for the next Chingu Voyage. I am looking forward to learning to work in a group developer project. Wish me luck!

View File

@@ -1,14 +1,14 @@
---
title: CS50x Week 7 Completed
desc: I have completed and submitted the Week 7 assignment of the CS50's Introduction to Computer Science (CS50x) course.
date: 2023-09-10
topics: ["cs50", "cs50x", "sql"]
---
On 8 September 2023, I completed [Week 7](https://cs50.harvard.edu/x/2023/weeks/7/) of [CS50s Introduction to Computer Science](https://cs50.harvard.edu/x/2023/) (CS50x) course, by submitting the required assignments, [Lab 7](https://cs50.harvard.edu/x/2023/labs/7/) and [Problem Set 7](https://cs50.harvard.edu/x/2023/psets/7/).
Week 7 of the CS50x course revolved around SQL. Since I was focusing on learning about front-end web development, this was the first time I learned SQL and relational database. At first, I was worried about tackling this week's assignments due to my lack of familiarity to SQL.
However, it turned out SQL and working on databases was more fun than I expected. I had a good time in writing out the SQL queries to solve this week's assigned problems. In particular, I found my new favourite assignment from CS50x in the [Fiftyville](https://cs50.harvard.edu/x/2023/psets/7/fiftyville/) problem, which involved using SQL to solve a mystery of a theft of the CS50 Duck. I had a great deal of fun with the Fiftyville problem from start to finish, from writing my SQL queries and comments about my thought process in solving the mysteries, to narrowing down the suspects and eventually identifying the thief and their accomplice. Kudos to those who designed this fun and challenging assignment!
Now that I am done with Week 7 of CS50x, I am ready to move on to next week, which will be about HTML, CSS and JavaScript — the languages I had spent the past eight months in learning and practicing through various courses outside CS50. CS50x Week 8 here I come!
---
title: CS50x Week 7 Completed
desc: I have completed and submitted the Week 7 assignment of the CS50's Introduction to Computer Science (CS50x) course.
date: 2023-09-10
topics: ["cs50", "cs50x", "sql"]
---
On 8 September 2023, I completed [Week 7](https://cs50.harvard.edu/x/2023/weeks/7/) of [CS50s Introduction to Computer Science](https://cs50.harvard.edu/x/2023/) (CS50x) course, by submitting the required assignments, [Lab 7](https://cs50.harvard.edu/x/2023/labs/7/) and [Problem Set 7](https://cs50.harvard.edu/x/2023/psets/7/).
Week 7 of the CS50x course revolved around SQL. Since I was focusing on learning about front-end web development, this was the first time I learned SQL and relational database. At first, I was worried about tackling this week's assignments due to my lack of familiarity to SQL.
However, it turned out SQL and working on databases was more fun than I expected. I had a good time in writing out the SQL queries to solve this week's assigned problems. In particular, I found my new favourite assignment from CS50x in the [Fiftyville](https://cs50.harvard.edu/x/2023/psets/7/fiftyville/) problem, which involved using SQL to solve a mystery of a theft of the CS50 Duck. I had a great deal of fun with the Fiftyville problem from start to finish, from writing my SQL queries and comments about my thought process in solving the mysteries, to narrowing down the suspects and eventually identifying the thief and their accomplice. Kudos to those who designed this fun and challenging assignment!
Now that I am done with Week 7 of CS50x, I am ready to move on to next week, which will be about HTML, CSS and JavaScript — the languages I had spent the past eight months in learning and practicing through various courses outside CS50. CS50x Week 8 here I come!

View File

@@ -1,12 +1,12 @@
---
title: CS50x Week 8 Assignments Completed
desc: I have completed and submitted the Week 8 assignment of the CS50's Introduction to Computer Science (CS50x) course.
date: 2023-09-26
topics: ["cs50", "cs50x", "html", "css", "javascript"]
---
At last, I completed and submitted [Week 8](https://cs50.harvard.edu/x/2023/weeks/8/) assignments of the CS50's Introduction to Computer Science (CS50x) course, including [Lab 8](https://cs50.harvard.edu/x/2023/labs/8/) and [Problem Set 8](https://cs50.harvard.edu/x/2023/psets/8/).
The eighth week of the CS50x is about HTML, CSS, JavaScript and how the internet works. I already have a good foundation in HTML, CSS and JavaScript since I started taking front-end web development courses on other platforms like freeCodeCamp and Scrimba since December 2022. Even then, I learned new things about how the internet works from the CS50x course.
As for the assignments, I already completed and submitted Lab 8 on September 13, but it took almost two weeks after submitting Lab 8 for me to finally finish Problem Set 8, because I was struggling to come out of ideas about what to put on my pages to meet CS50x's requirement. I was relieved when I finally finished Problem Set 8.
---
title: CS50x Week 8 Assignments Completed
desc: I have completed and submitted the Week 8 assignment of the CS50's Introduction to Computer Science (CS50x) course.
date: 2023-09-26
topics: ["cs50", "cs50x", "html", "css", "javascript"]
---
At last, I completed and submitted [Week 8](https://cs50.harvard.edu/x/2023/weeks/8/) assignments of the CS50's Introduction to Computer Science (CS50x) course, including [Lab 8](https://cs50.harvard.edu/x/2023/labs/8/) and [Problem Set 8](https://cs50.harvard.edu/x/2023/psets/8/).
The eighth week of the CS50x is about HTML, CSS, JavaScript and how the internet works. I already have a good foundation in HTML, CSS and JavaScript since I started taking front-end web development courses on other platforms like freeCodeCamp and Scrimba since December 2022. Even then, I learned new things about how the internet works from the CS50x course.
As for the assignments, I already completed and submitted Lab 8 on September 13, but it took almost two weeks after submitting Lab 8 for me to finally finish Problem Set 8, because I was struggling to come out of ideas about what to put on my pages to meet CS50x's requirement. I was relieved when I finally finished Problem Set 8.

View File

@@ -1,29 +1,29 @@
---
title: cbpickaxe and My First (Merged) Pull Request in Python
desc: About my first pull request in Python that got approved and merged.
date: 2023-11-13
topics: ["python", "github", "cbpickaxe", "cassette beasts"]
---
Recently, on 11 November 2023, I created my first pull request in Python that got merged later. Specifically, the pull request was for [cbpickaxe](https://github.com/ExcaliburZero/cbpickaxe), a Python library and set of scripts for data mining the video game [Cassette Beasts](https://www.cassettebeasts.com/), titled ["extract\_translation Script: Add support for finding strings of IDs that require pronoun identifiers"](https://github.com/ExcaliburZero/cbpickaxe/pull/3).
While my current focus in my developer journey is front-end web development, I have also been learning Python at the same time, due to the programming language's popularity and the vast amount of resources and tutorials for learning the language. In addition, as an open source enthusiast, I have always wanted to be able to contribute to open source projects, particularly ones that I use.
Cassette Beasts, the independent monster collecting role-playing game developed by Bytten Studio, has become one of my all-time favourite video games since the first time I played it. My interest in the game drives me to learn as much about the game as possible, which led me to start using cbpickaxe.
When I started using the `extract_translation` script to data mine in-game dialogues, I discovered the script's issue of not being able to extract strings with gender identifiers. After discussing the issue with ExcaliburZero, the developer of cbpickaxe, I started investigating the script's code and tried to figure out any solution.
I have had little experience with writing Python scripts, so I saw this as a great learning opportunity for me to put my Python knowledge and skills into practice. After forking the cbpickaxe repository, looking into the code and a lot of web searching, I discovered that in the original code, printing the output of the IDs with missing strings only has one key (`id`) in its list, and does not have any locale key to display the corresponding strings. Therefore, I started thinking of possible solutions from there:
* Check if the string ID does not have the locale key, by using this condition: `if locale not in row.keys()`
* Check if the string ID only has one key (id), by using this condition: `if len(row.keys()) == 1`
After trying both possible methods, I eventually settled for the second one, because it allowed me to write cleaner and more succinct code, while the first required me to write more bloated code, mainly due to having to repeat the block of code for searching and reading strings from in-game data, as refactoring it into a function meant losing the definition of the locale variable.
After that, I spent time in figuring out how to append the pronoun identifiers to the input IDs, so their corresponding strings can be written in the output CSV. At first, I considered trying to dynamically reading the `analysis.tres` file from the decompiled Cassette Beasts game, but the text structure of the `.tres` files might be too complicated to be doable in a clean code, so I chose to create a list for the pronoun identifiers (`.f`, `.m` and `.n`), then the script will iterate through the pronouns list to append the pronoun identifiers to the end of the ID if it requires a pronoun idntifier to find the corresponding string.
Voilà! At last, I solved the problem!
After testing my code multiple times to ensure it works, I made a pull request on cbpickaxe's GitHub repository. ExcaliburZero approved and merged my pull request, which has become my first merged pull request on Python! This pull request has been included in the release of cbpickaxe starting from [version 0.1.2](https://github.com/ExcaliburZero/cbpickaxe/releases/tag/v0.1.2).
I am immensely grateful of this opportunity to not only contribute to an open source project that is realated to one of my favourite video games, but also practicing my Python skills.
---
title: cbpickaxe and My First (Merged) Pull Request in Python
desc: About my first pull request in Python that got approved and merged.
date: 2023-11-13
topics: ["python", "github", "cbpickaxe", "cassette beasts"]
---
Recently, on 11 November 2023, I created my first pull request in Python that got merged later. Specifically, the pull request was for [cbpickaxe](https://github.com/ExcaliburZero/cbpickaxe), a Python library and set of scripts for data mining the video game [Cassette Beasts](https://www.cassettebeasts.com/), titled ["extract\_translation Script: Add support for finding strings of IDs that require pronoun identifiers"](https://github.com/ExcaliburZero/cbpickaxe/pull/3).
While my current focus in my developer journey is front-end web development, I have also been learning Python at the same time, due to the programming language's popularity and the vast amount of resources and tutorials for learning the language. In addition, as an open source enthusiast, I have always wanted to be able to contribute to open source projects, particularly ones that I use.
Cassette Beasts, the independent monster collecting role-playing game developed by Bytten Studio, has become one of my all-time favourite video games since the first time I played it. My interest in the game drives me to learn as much about the game as possible, which led me to start using cbpickaxe.
When I started using the `extract_translation` script to data mine in-game dialogues, I discovered the script's issue of not being able to extract strings with gender identifiers. After discussing the issue with ExcaliburZero, the developer of cbpickaxe, I started investigating the script's code and tried to figure out any solution.
I have had little experience with writing Python scripts, so I saw this as a great learning opportunity for me to put my Python knowledge and skills into practice. After forking the cbpickaxe repository, looking into the code and a lot of web searching, I discovered that in the original code, printing the output of the IDs with missing strings only has one key (`id`) in its list, and does not have any locale key to display the corresponding strings. Therefore, I started thinking of possible solutions from there:
* Check if the string ID does not have the locale key, by using this condition: `if locale not in row.keys()`
* Check if the string ID only has one key (id), by using this condition: `if len(row.keys()) == 1`
After trying both possible methods, I eventually settled for the second one, because it allowed me to write cleaner and more succinct code, while the first required me to write more bloated code, mainly due to having to repeat the block of code for searching and reading strings from in-game data, as refactoring it into a function meant losing the definition of the locale variable.
After that, I spent time in figuring out how to append the pronoun identifiers to the input IDs, so their corresponding strings can be written in the output CSV. At first, I considered trying to dynamically reading the `analysis.tres` file from the decompiled Cassette Beasts game, but the text structure of the `.tres` files might be too complicated to be doable in a clean code, so I chose to create a list for the pronoun identifiers (`.f`, `.m` and `.n`), then the script will iterate through the pronouns list to append the pronoun identifiers to the end of the ID if it requires a pronoun idntifier to find the corresponding string.
Voilà! At last, I solved the problem!
After testing my code multiple times to ensure it works, I made a pull request on cbpickaxe's GitHub repository. ExcaliburZero approved and merged my pull request, which has become my first merged pull request on Python! This pull request has been included in the release of cbpickaxe starting from [version 0.1.2](https://github.com/ExcaliburZero/cbpickaxe/releases/tag/v0.1.2).
I am immensely grateful of this opportunity to not only contribute to an open source project that is realated to one of my favourite video games, but also practicing my Python skills.

View File

@@ -1,20 +1,20 @@
---
title: Chingu Voyage 46 Completed
desc: My participation and winning of Scrimba's JavaScriptmas 2023 challenge.
date: 2023-11-25
topics: ["chingu"]
---
On 12 November 2023, I, along with two other teammates had officially completed [Chingu](https://www.chingu.io/) Voyage 46. I received my [Certificate of Completion](/assets/documents/Chingu-Voyage46-Completion-Cert.pdf) (PDF file) from Chingu on 19 November 2023.
Back in August, I had created a [TickyBot clone](./2023-08-24-Built-a-TickyBot-Clone-for-Chingus-Solo-Project) as my [solo project](https://github.com/chingu-voyages/Handbook/blob/home/docs/guides/soloproject/soloproject.md) to submit to Chingu in order to join the next Chingu Voyage, specifically Tier 1, which is for web development beginners. My submission was approved by Chingu, and I was assigned to Tier 1 Team 1.
We were instructed to build a [recipe app](https://github.com/chingu-voyages/voyage-project-tier1-recipes) that allows users to search for recipes by inputting ingredients.
![Screenshot of Recipe Hub](https://raw.githubusercontent.com/chingu-voyages/v46-tier1-team-01/home/Recipe-Hub-screenshot.png)
You can view our Recipe Hub app's [live site](https://chingu-voyages.github.io/v46-tier1-team-01/Food-Recipe/) and [GitHub repository](https://github.com/chingu-voyages/v46-tier1-team-01).
Chingu Voyage was a big help to me for gaining practical experience in working on group projects, and learning both hard and soft skills. Particupating in the Chingu Voyage allowed me to practice not only my HTML, CSS and JavaScript skills, but also my teamwork and communication skills that are needed in group projects.
Before joining Chingu, I already practice my front-end web development skills with my own projects and Frontend Mentor's challenges, but since those were all solo projects, participating in my first Chingu Voyage, which was Voyage 46, was a new experience for me.
---
title: Chingu Voyage 46 Completed
desc: My participation and winning of Scrimba's JavaScriptmas 2023 challenge.
date: 2023-11-25
topics: ["chingu"]
---
On 12 November 2023, I, along with two other teammates had officially completed [Chingu](https://www.chingu.io/) Voyage 46. I received my [Certificate of Completion](/assets/documents/Chingu-Voyage46-Completion-Cert.pdf) (PDF file) from Chingu on 19 November 2023.
Back in August, I had created a [TickyBot clone](./2023-08-24-Built-a-TickyBot-Clone-for-Chingus-Solo-Project) as my [solo project](https://github.com/chingu-voyages/Handbook/blob/home/docs/guides/soloproject/soloproject.md) to submit to Chingu in order to join the next Chingu Voyage, specifically Tier 1, which is for web development beginners. My submission was approved by Chingu, and I was assigned to Tier 1 Team 1.
We were instructed to build a [recipe app](https://github.com/chingu-voyages/voyage-project-tier1-recipes) that allows users to search for recipes by inputting ingredients.
![Screenshot of Recipe Hub](https://raw.githubusercontent.com/chingu-voyages/v46-tier1-team-01/home/Recipe-Hub-screenshot.png)
You can view our Recipe Hub app's [live site](https://chingu-voyages.github.io/v46-tier1-team-01/Food-Recipe/) and [GitHub repository](https://github.com/chingu-voyages/v46-tier1-team-01).
Chingu Voyage was a big help to me for gaining practical experience in working on group projects, and learning both hard and soft skills. Particupating in the Chingu Voyage allowed me to practice not only my HTML, CSS and JavaScript skills, but also my teamwork and communication skills that are needed in group projects.
Before joining Chingu, I already practice my front-end web development skills with my own projects and Frontend Mentor's challenges, but since those were all solo projects, participating in my first Chingu Voyage, which was Voyage 46, was a new experience for me.

View File

@@ -1,16 +1,16 @@
---
title: JavaScriptmas 2023 Challenge Completed — and Won
desc: My participation and winning of Scrimba's JavaScriptmas 2023 challenge.
date: 2023-12-27T21:47:00+0800
topics: ["scrimba", "javascriptmas"]
---
Starting from 1 December 2023, I had participated in [Scrimba](https://scrimba.com/)'s [JavaScriptmas](https://scrimba.com/learn/javascriptmas) challenge. After 24 days, I finally completed the entire challenge! I have compiled a [scrim playlist](https://scrimba.com/playlist/pdpB3JZfE) for all my JavaScriptmas solutions on Scrimba.
In addition, when Scrimba announced the winners of JavaScriptmas in their [JavaScriptmas PrizeFest](https://www.youtube.com/live/Y0tc4DTS0e8) livestream on 24 December, it was revealed that [I (under my Twitter handle helen\_clx) was one of the 24 winners of a one-year Scrimba Pro plan](https://www.youtube.com/live/Y0tc4DTS0e8?t=2190) for [my solution to the Day 18 challenge](https://scrimba.com/scrim/co79e43dcbacb329c01a1744f?pl=pdpB3JZfE): AI alt text generator!
<img src="{{ src 'JavaScriptmas-PrizeFest-2023-Day-18-winner_xjdpzm.avif' }}" srcset="{{ srcset 'JavaScriptmas-PrizeFest-2023-Day-18-winner_xjdpzm.avif', [260, 649, 980, 1320, 1580, 1830, 2048] }}" sizes="(min-width: 1020px) calc(54.9vw - 67px), calc(96.57vw - 30px)" alt="Screenshot of JavaScriptmas PrizeFest announcing helen_clx as the Day 18 winner" width="1920" height="1080" loading="lazy">
JavaScriptmas has been a great opportunity to practice and sharpen my CSS and JavaScript knowledge and skills. The AI challenges, including Day 18, which I won, were also the first time I learned to incorporate AI in coding projects. Therefore, it was a great surprise that I managed to win for an AI challenge.
Thank you Scrimba for the Christmas gift! With a one-year Scrimba Pro plan, I will be able to take Scirmba courses that are only available to subscribers.
---
title: JavaScriptmas 2023 Challenge Completed — and Won
desc: My participation and winning of Scrimba's JavaScriptmas 2023 challenge.
date: 2023-12-27T21:47:00+0800
topics: ["scrimba", "javascriptmas"]
---
Starting from 1 December 2023, I had participated in [Scrimba](https://scrimba.com/)'s [JavaScriptmas](https://scrimba.com/learn/javascriptmas) challenge. After 24 days, I finally completed the entire challenge! I have compiled a [scrim playlist](https://scrimba.com/playlist/pdpB3JZfE) for all my JavaScriptmas solutions on Scrimba.
In addition, when Scrimba announced the winners of JavaScriptmas in their [JavaScriptmas PrizeFest](https://www.youtube.com/live/Y0tc4DTS0e8) livestream on 24 December, it was revealed that [I (under my Twitter handle helen\_clx) was one of the 24 winners of a one-year Scrimba Pro plan](https://www.youtube.com/live/Y0tc4DTS0e8?t=2190) for [my solution to the Day 18 challenge](https://scrimba.com/scrim/co79e43dcbacb329c01a1744f?pl=pdpB3JZfE): AI alt text generator!
<img src="{{ src 'JavaScriptmas-PrizeFest-2023-Day-18-winner_xjdpzm.avif' }}" srcset="{{ srcset 'JavaScriptmas-PrizeFest-2023-Day-18-winner_xjdpzm.avif', [260, 649, 980, 1320, 1580, 1830, 2048] }}" sizes="(min-width: 1020px) calc(54.9vw - 67px), calc(96.57vw - 30px)" alt="Screenshot of JavaScriptmas PrizeFest announcing helen_clx as the Day 18 winner" width="1920" height="1080" loading="lazy">
JavaScriptmas has been a great opportunity to practice and sharpen my CSS and JavaScript knowledge and skills. The AI challenges, including Day 18, which I won, were also the first time I learned to incorporate AI in coding projects. Therefore, it was a great surprise that I managed to win for an AI challenge.
Thank you Scrimba for the Christmas gift! With a one-year Scrimba Pro plan, I will be able to take Scirmba courses that are only available to subscribers.

View File

@@ -1,32 +1,32 @@
---
title: Rebuilding My Developer Portfolio with Eleventy
desc: I recreated my developer portfolio and blog from scratch with Eleventy.
date: 2024-04-11T18:03:00+0800
topics: ["eleventy", "about this website", "about this blog"]
---
I have been taking courses to learn to code, starting from HTML, CSS and JavaScript, since December 2022. As I honed my front-end web development skills further by building projects, I quickly realised that I needed a portfolio website to showcase myself and my projects. Eventually, I launched my first developer portfolio website in February 2023, based on Kolade Chris tutorial ["How to Build Your Own Developer Portfolio Website with HTML, CSS, and JavaScript"](https://www.freecodecamp.org/news/how-to-build-a-developer-portfolio-website/) on freeCodeCamp, and deployed it to GitHub Pages.
<img src="{{ src 'Helen-Chong-portfolio-old_va6mq2.avif' }}" srcset="{{ srcset 'Helen-Chong-portfolio-old_va6mq2.avif', [260, 649, 980, 1320, 1580, 1830, 2048] }}" sizes="(min-width: 1020px) calc(54.9vw - 67px), calc(96.57vw - 30px)" alt="First version of Helen Chong's developer portfolio" width="1898" height="888" loading="lazy">
When I [shared the first version of my developer portfolio website on Twitter](https://twitter.com/helen_clx/status/1621696817795854336), my tweet was liked and replied to by both [Quincy Larson](https://twitter.com/ossia/status/1621729431009071104), the founder of freeCodeCamp, and [Kolade Chris](https://twitter.com/Ksound22/status/1621739428057079808) themselves, despite me not even tagging freeCodeCamp nor Kolade.
<img src="{{ src 'Quincy-Larson-Kolade-Chris-tweet-replies_ckctpn.avif' }}" srcset="{{ srcset 'Quincy-Larson-Kolade-Chris-tweet-replies_ckctpn.avif', [260, 649, 980, 1320, 1580, 1830, 2048] }}" sizes="(min-width: 1860px) 886px, (min-width: 1020px) 47.93vw, 88vw" alt="Quincy Larson and Kolade Chris' replies to my tweet" width="886" height="1196" loading="lazy">
As my front-end web development skills grew, I made my own improvements to the code of my portfolio website, most notably revamping the CSS to a mobile-first approach. In August 2023, I also [launched my tech and coding blog](2023-08-21-Welcome-to-Helen-Codes.md) to document my coding learnings and talk about various tech topics. At first, I tried to build my blog with a Jekyll template in a separate repository, but switched to [Zonelets](https://zonelets.net/), so I can write and manage my blog in HTML, CSS and JavaScript, languages I was already familiar with, and moved my blog to my portfolio website's own repository.
However, as my portfolio website grew, I began to realise that writing all the code by hand makes a website tedious to maintain and update, especially if the website also includes a blog, since making new blog posts would require copy and pasting the same HTML heading over and over.
I started to research for ways to dynamically build a website, and as a result, I discovered more about static site generators (SSGs). I first heard of static site generators through GitHub and Jekyll, but I abandoned Jekyll after my brief attempt in building my blog with it, because Ruby is not a language I am familiar with, not to mention inconvenient to set up on Windows. Fortunately, I eventually discovered the ultimate solution to my problem: [Eleventy](https://www.11ty.dev/).
Eleventy stands out among other SSGs in its flexibility. It not only supports many template languages including HTML, Markdown, JavaScript, Nunjucks, Liquid, and even having its own web component template called [WebC](https://www.11ty.dev/docs/languages/webc/), but also allows users to mix and match different template languages to build their own static websites. I like to be able to have control over how I build my own projects, which makes Eleventy an attractive option.
Thus, my Eleventy journey began. After reading the official Eleventy documentation and community Eleventy tutorials, as well as following along the tutorials, I created a new branch on my portfolio website repository to recreate my website and blog from scratch, based on Gerard Hynes' tutorial ["Learn the Eleventy Static Site Generator by Building and Deploying a Portfolio Website"](https://www.freecodecamp.org/news/learn-eleventy/) on freeCodeCamp and Raymond Camden's ["A Complete Guide to Building a Blog with Eleventy"](https://cfjedimaster.github.io/eleventy-blog-guide/guide.html), then migrating my website's content and blog posts to Eleventy. I also look at Eleventy's [official blog starter project](https://github.com/11ty/eleventy-base-blog) for inspiration.
Finally, on 9 April 2024, I succeed in rebuilding my developer portfolio website in Eleventy!
<img src="{{ src 'Helen-Chong-portfolio-eleventy_yzgrgh.avif' }}" srcset="{{ srcset 'Helen-Chong-portfolio-eleventy_yzgrgh.avif', [260, 649, 980, 1320, 1580, 1830, 2048] }}" sizes="(min-width: 1020px) calc(54.9vw - 67px), calc(96.57vw - 30px)" alt="Helen Chong's new developer portfolio" width="1898" height="878" loading="lazy">
Using a static site generator to build my own websites has been a great learning experience, from creating layouts, templates, installing plug-ins, to setting up GitHub Actions. I am glad that I have learned a great tool to build and maintain my static website projects in a dynamic, flexible and efficient manner.
Welcome to Helen Chong's brand new Eleventy portfolio website!🎉
---
title: Rebuilding My Developer Portfolio with Eleventy
desc: I recreated my developer portfolio and blog from scratch with Eleventy.
date: 2024-04-11T18:03:00+0800
topics: ["eleventy", "about this website", "about this blog"]
---
I have been taking courses to learn to code, starting from HTML, CSS and JavaScript, since December 2022. As I honed my front-end web development skills further by building projects, I quickly realised that I needed a portfolio website to showcase myself and my projects. Eventually, I launched my first developer portfolio website in February 2023, based on Kolade Chris tutorial ["How to Build Your Own Developer Portfolio Website with HTML, CSS, and JavaScript"](https://www.freecodecamp.org/news/how-to-build-a-developer-portfolio-website/) on freeCodeCamp, and deployed it to GitHub Pages.
<img src="{{ src 'Helen-Chong-portfolio-old_va6mq2.avif' }}" srcset="{{ srcset 'Helen-Chong-portfolio-old_va6mq2.avif', [260, 649, 980, 1320, 1580, 1830, 2048] }}" sizes="(min-width: 1020px) calc(54.9vw - 67px), calc(96.57vw - 30px)" alt="First version of Helen Chong's developer portfolio" width="1898" height="888" loading="lazy">
When I [shared the first version of my developer portfolio website on Twitter](https://twitter.com/helen_clx/status/1621696817795854336), my tweet was liked and replied to by both [Quincy Larson](https://twitter.com/ossia/status/1621729431009071104), the founder of freeCodeCamp, and [Kolade Chris](https://twitter.com/Ksound22/status/1621739428057079808) themselves, despite me not even tagging freeCodeCamp nor Kolade.
<img src="{{ src 'Quincy-Larson-Kolade-Chris-tweet-replies_ckctpn.avif' }}" srcset="{{ srcset 'Quincy-Larson-Kolade-Chris-tweet-replies_ckctpn.avif', [260, 649, 980, 1320, 1580, 1830, 2048] }}" sizes="(min-width: 1860px) 886px, (min-width: 1020px) 47.93vw, 88vw" alt="Quincy Larson and Kolade Chris' replies to my tweet" width="886" height="1196" loading="lazy">
As my front-end web development skills grew, I made my own improvements to the code of my portfolio website, most notably revamping the CSS to a mobile-first approach. In August 2023, I also [launched my tech and coding blog](2023-08-21-Welcome-to-Helen-Codes.md) to document my coding learnings and talk about various tech topics. At first, I tried to build my blog with a Jekyll template in a separate repository, but switched to [Zonelets](https://zonelets.net/), so I can write and manage my blog in HTML, CSS and JavaScript, languages I was already familiar with, and moved my blog to my portfolio website's own repository.
However, as my portfolio website grew, I began to realise that writing all the code by hand makes a website tedious to maintain and update, especially if the website also includes a blog, since making new blog posts would require copy and pasting the same HTML heading over and over.
I started to research for ways to dynamically build a website, and as a result, I discovered more about static site generators (SSGs). I first heard of static site generators through GitHub and Jekyll, but I abandoned Jekyll after my brief attempt in building my blog with it, because Ruby is not a language I am familiar with, not to mention inconvenient to set up on Windows. Fortunately, I eventually discovered the ultimate solution to my problem: [Eleventy](https://www.11ty.dev/).
Eleventy stands out among other SSGs in its flexibility. It not only supports many template languages including HTML, Markdown, JavaScript, Nunjucks, Liquid, and even having its own web component template called [WebC](https://www.11ty.dev/docs/languages/webc/), but also allows users to mix and match different template languages to build their own static websites. I like to be able to have control over how I build my own projects, which makes Eleventy an attractive option.
Thus, my Eleventy journey began. After reading the official Eleventy documentation and community Eleventy tutorials, as well as following along the tutorials, I created a new branch on my portfolio website repository to recreate my website and blog from scratch, based on Gerard Hynes' tutorial ["Learn the Eleventy Static Site Generator by Building and Deploying a Portfolio Website"](https://www.freecodecamp.org/news/learn-eleventy/) on freeCodeCamp and Raymond Camden's ["A Complete Guide to Building a Blog with Eleventy"](https://cfjedimaster.github.io/eleventy-blog-guide/guide.html), then migrating my website's content and blog posts to Eleventy. I also look at Eleventy's [official blog starter project](https://github.com/11ty/eleventy-base-blog) for inspiration.
Finally, on 9 April 2024, I succeed in rebuilding my developer portfolio website in Eleventy!
<img src="{{ src 'Helen-Chong-portfolio-eleventy_yzgrgh.avif' }}" srcset="{{ srcset 'Helen-Chong-portfolio-eleventy_yzgrgh.avif', [260, 649, 980, 1320, 1580, 1830, 2048] }}" sizes="(min-width: 1020px) calc(54.9vw - 67px), calc(96.57vw - 30px)" alt="Helen Chong's new developer portfolio" width="1898" height="878" loading="lazy">
Using a static site generator to build my own websites has been a great learning experience, from creating layouts, templates, installing plug-ins, to setting up GitHub Actions. I am glad that I have learned a great tool to build and maintain my static website projects in a dynamic, flexible and efficient manner.
Welcome to Helen Chong's brand new Eleventy portfolio website!🎉

View File

@@ -1,24 +1,24 @@
---
title: Round 1 of 100 Days of Code Challenge Completed
desc: I completed my first round of the 100 Days of Code challenge.
date: 2024-04-13T12:20:00+0800
topics: ["100 days of code", "freecodecamp"]
---
On 3 January 2024, I started the [#100DaysOfCode](https://www.100daysofcode.com/) challenge for the first time. Came 11 April 2024, I finally completed my first round of the challenge!
I had heard of the 100 Days of Code challenge after I started my journey to learn to code. However, I did not participate in it until reading freeCodeCamp's article ["#100DaysOfCode Challenge for 2024 Discord Edition"](https://www.freecodecamp.org/news/100daysofcode-challenge-2024-discord/). This was also how I found out about the [history of the challenge](https://www.freecodecamp.org/news/the-crazy-history-of-the-100daysofcode-challenge-and-why-you-should-try-it-for-2018-6c89a76e298d/), and the fact that the challenge was created by a freeCodeCamp alumni, which I found interesting as an aspiring developer to start to properly learn to code with freeCodeCamp.
By the time 2024 came around, I had been taking coding courses for more than a year, so I figured the 100 Days of Code challenge would be a good motivator for me to continue my coding journey every day.
It helped that there were still coding courses for me to take. At the time, freeCodeCamp just recently upgraded their [JavaScript](https://www.freecodecamp.org/news/learn-javascript-with-new-data-structures-and-algorithms-certification-projects/) and [Python](https://www.freecodecamp.org/news/python-curriculum-upgrade/) curriculum to be project-based. I already earned the [Legacy JavaScript Algorithms and Data Structures Certification](https://www.freecodecamp.org/certification/helenclx/javascript-algorithms-and-data-structures), but since I prefer to learn to code by building projects, I decided to take freeCodeCamp's upgraded curriculum, and eventually earned the [new freeCodeCamp JavaScript certification](https://www.freecodecamp.org/certification/helenclx/javascript-algorithms-and-data-structures-v8) on 16 March 2024 as well.
Meanwhile, I also started to enrol [Scrimba's Frontend Developer Career Path](https://scrimba.com/learn/frontend) after earning a one-year Scrimba Pro plan from the [JavaScriptmas challenge](2023-12-27-JavaScriptmas-2023-Challenge-Completed-and-Won.md) in December 2023. By the time I completed my first round of the #100DaysOfCode challenge, I had also completed more than half of the curriculum.
I committed to the 100 Days of Code challenge by sharing updates every day on Twitter, Mastodon, Bluesky and the [official freeCodeCamp Discord server](https://discord.gg/freecodecamp). In addition, I made the habit of recording my progress in the 100 Days of Code challenge every day, so I can reflect on my coding journey. You can view [my fork of the #100DaysOfCode challenge repository](https://github.com/helenclx/100-days-of-code) to read the log of my [Round 1](https://github.com/helenclx/100-days-of-code/blob/master/r1-log.md) of the challenge. You can fork the [official repository of the #100DaysOfCode challenge](https://github.com/kallaway/100-days-of-code) to log your own progress in the challenge.
To learn more about the #100DaysOfCode challenge, visit [its official website](https://www.100daysofcode.com/).
The #100DaysOfCode challenge has been a wonderful journey. I am glad I managed to motivate myself to code every day. Through various courses and projects, I have honed my front-end development skills a lot.
Thank you to everyone who has supported me throughout my 100 Days of Code journey, and has interacted with my posts on Twitter, Mastodon, Bluesky and the freeCodeCamp Discord serve, by replying to, liking or reposting.
---
title: Round 1 of 100 Days of Code Challenge Completed
desc: I completed my first round of the 100 Days of Code challenge.
date: 2024-04-13T12:20:00+0800
topics: ["100 days of code", "freecodecamp"]
---
On 3 January 2024, I started the [#100DaysOfCode](https://www.100daysofcode.com/) challenge for the first time. Came 11 April 2024, I finally completed my first round of the challenge!
I had heard of the 100 Days of Code challenge after I started my journey to learn to code. However, I did not participate in it until reading freeCodeCamp's article ["#100DaysOfCode Challenge for 2024 Discord Edition"](https://www.freecodecamp.org/news/100daysofcode-challenge-2024-discord/). This was also how I found out about the [history of the challenge](https://www.freecodecamp.org/news/the-crazy-history-of-the-100daysofcode-challenge-and-why-you-should-try-it-for-2018-6c89a76e298d/), and the fact that the challenge was created by a freeCodeCamp alumni, which I found interesting as an aspiring developer to start to properly learn to code with freeCodeCamp.
By the time 2024 came around, I had been taking coding courses for more than a year, so I figured the 100 Days of Code challenge would be a good motivator for me to continue my coding journey every day.
It helped that there were still coding courses for me to take. At the time, freeCodeCamp just recently upgraded their [JavaScript](https://www.freecodecamp.org/news/learn-javascript-with-new-data-structures-and-algorithms-certification-projects/) and [Python](https://www.freecodecamp.org/news/python-curriculum-upgrade/) curriculum to be project-based. I already earned the [Legacy JavaScript Algorithms and Data Structures Certification](https://www.freecodecamp.org/certification/helenclx/javascript-algorithms-and-data-structures), but since I prefer to learn to code by building projects, I decided to take freeCodeCamp's upgraded curriculum, and eventually earned the [new freeCodeCamp JavaScript certification](https://www.freecodecamp.org/certification/helenclx/javascript-algorithms-and-data-structures-v8) on 16 March 2024 as well.
Meanwhile, I also started to enrol [Scrimba's Frontend Developer Career Path](https://scrimba.com/learn/frontend) after earning a one-year Scrimba Pro plan from the [JavaScriptmas challenge](2023-12-27-JavaScriptmas-2023-Challenge-Completed-and-Won.md) in December 2023. By the time I completed my first round of the #100DaysOfCode challenge, I had also completed more than half of the curriculum.
I committed to the 100 Days of Code challenge by sharing updates every day on Twitter, Mastodon, Bluesky and the [official freeCodeCamp Discord server](https://discord.gg/freecodecamp). In addition, I made the habit of recording my progress in the 100 Days of Code challenge every day, so I can reflect on my coding journey. You can view [my fork of the #100DaysOfCode challenge repository](https://github.com/helenclx/100-days-of-code) to read the log of my [Round 1](https://github.com/helenclx/100-days-of-code/blob/master/r1-log.md) of the challenge. You can fork the [official repository of the #100DaysOfCode challenge](https://github.com/kallaway/100-days-of-code) to log your own progress in the challenge.
To learn more about the #100DaysOfCode challenge, visit [its official website](https://www.100daysofcode.com/).
The #100DaysOfCode challenge has been a wonderful journey. I am glad I managed to motivate myself to code every day. Through various courses and projects, I have honed my front-end development skills a lot.
Thank you to everyone who has supported me throughout my 100 Days of Code journey, and has interacted with my posts on Twitter, Mastodon, Bluesky and the freeCodeCamp Discord serve, by replying to, liking or reposting.

View File

@@ -1,30 +1,30 @@
---
title: CS50x Week 9 Completed
desc: In March 2024, I have completed and submitted the Week 9 problem set of the CS50's Introduction to Computer Science (CS50x) course.
date: 2024-04-15T16:37:00+0800
topics: ["cs50", "cs50x", "python", "flask", "sql"]
---
At last, on 5 March 2024, I completed [Week 9](https://cs50.harvard.edu/x/2024/weeks/9/) of the CS50's Introduction to Computer Science (CS50x) course, by submitting my solutions to [Problem Set 9](https://cs50.harvard.edu/x/2024/psets/9/) . In other words, I have finished all assignments for the CS50x course, with the only thing left for me to do to complete the course is building and submitting my own [final project](https://cs50.harvard.edu/x/2024/project/).
Problem Set 9 consisted of two problems, [Birthdays](https://cs50.harvard.edu/x/2024/psets/9/birthdays/) and [Finance](https://cs50.harvard.edu/x/2024/psets/9/finance/), which required Python, Flask and SQL to solve. I did and submitted my solution to the Birthdays problem on 6 January 2024. At the time, I already took a break from the CS50x course for a few months to pursue other front-end web development courses. It was rather challenging to go back to CS50x and do its assignments after taking a break from the course for a while. My SQL and Flask knowledge got rusty due to rarely using them.
On 2 March 2024, I began tackling the Finance problem. As the final assignment for the course before the final project, Finance was by far the largest assignment of CS50x. As I expected, this project challenged me to apply everything I learned from the CS50x course. I needed to refer to my solutions to the previous problems to refresh my skills of Python, Flask and SQL. Thankfully, I had a good start; on my first day doing the Finance problem, I successfully implemented the Register and Quote features.
The next day, I started implementing the feature to buy stocks by setting up and testing input elements. After a lot of research and trial and errors, I learned to use `try...except` to display an error message on the web page if an incorrect input is entered. I also learned to make use of the phpLiteAdmin installed in the CS50 Codespace to work on the projects database and get the SQL commands I want to use, and modify the database in a graphical user interface.
On the third day of me doing the Finance problem, I successfully implemented the features to buy stocks and displaying the users owned stocks on the index page. My work on this project allowed me to refresh my knowledge and skills of SQL, by creating new tables, inserting new rows into tables, and updating data of existing tables. I also got more practice in working with Flask by dynamically render HTML elements via Python.
On 5 March 2024, I successfully implemented not only the last required features to sell stocks and display transaction history, but also additional features, namely to add cash to the users account and change the users password. On my last day working on the Finance assignment specifically, I learned about:
- Set the timestamp based on local time zone in SQL
- The flash message function in Flask
- Use both the `round()` and `float()` functions to round a number to a certain amount of decimals
- The `steps` attribute of HTMLs `<select>` element
Finally, I completed and submitted the Finance problem from Week 9 of CS50s Introduction to Computer Science!
Here is a video demonstration of my completed Finance project:
https://www.youtube.com/watch?v=AYkO59_Ojb4
Working on this assignment allowed me to practice and learn SQL, Python and Flask a lot. By finishing Week 9 of the CS50x, I reached another milestone.
---
title: CS50x Week 9 Completed
desc: In March 2024, I have completed and submitted the Week 9 problem set of the CS50's Introduction to Computer Science (CS50x) course.
date: 2024-04-15T16:37:00+0800
topics: ["cs50", "cs50x", "python", "flask", "sql"]
---
At last, on 5 March 2024, I completed [Week 9](https://cs50.harvard.edu/x/2024/weeks/9/) of the CS50's Introduction to Computer Science (CS50x) course, by submitting my solutions to [Problem Set 9](https://cs50.harvard.edu/x/2024/psets/9/) . In other words, I have finished all assignments for the CS50x course, with the only thing left for me to do to complete the course is building and submitting my own [final project](https://cs50.harvard.edu/x/2024/project/).
Problem Set 9 consisted of two problems, [Birthdays](https://cs50.harvard.edu/x/2024/psets/9/birthdays/) and [Finance](https://cs50.harvard.edu/x/2024/psets/9/finance/), which required Python, Flask and SQL to solve. I did and submitted my solution to the Birthdays problem on 6 January 2024. At the time, I already took a break from the CS50x course for a few months to pursue other front-end web development courses. It was rather challenging to go back to CS50x and do its assignments after taking a break from the course for a while. My SQL and Flask knowledge got rusty due to rarely using them.
On 2 March 2024, I began tackling the Finance problem. As the final assignment for the course before the final project, Finance was by far the largest assignment of CS50x. As I expected, this project challenged me to apply everything I learned from the CS50x course. I needed to refer to my solutions to the previous problems to refresh my skills of Python, Flask and SQL. Thankfully, I had a good start; on my first day doing the Finance problem, I successfully implemented the Register and Quote features.
The next day, I started implementing the feature to buy stocks by setting up and testing input elements. After a lot of research and trial and errors, I learned to use `try...except` to display an error message on the web page if an incorrect input is entered. I also learned to make use of the phpLiteAdmin installed in the CS50 Codespace to work on the projects database and get the SQL commands I want to use, and modify the database in a graphical user interface.
On the third day of me doing the Finance problem, I successfully implemented the features to buy stocks and displaying the users owned stocks on the index page. My work on this project allowed me to refresh my knowledge and skills of SQL, by creating new tables, inserting new rows into tables, and updating data of existing tables. I also got more practice in working with Flask by dynamically render HTML elements via Python.
On 5 March 2024, I successfully implemented not only the last required features to sell stocks and display transaction history, but also additional features, namely to add cash to the users account and change the users password. On my last day working on the Finance assignment specifically, I learned about:
- Set the timestamp based on local time zone in SQL
- The flash message function in Flask
- Use both the `round()` and `float()` functions to round a number to a certain amount of decimals
- The `steps` attribute of HTMLs `<select>` element
Finally, I completed and submitted the Finance problem from Week 9 of CS50s Introduction to Computer Science!
Here is a video demonstration of my completed Finance project:
https://www.youtube.com/watch?v=AYkO59_Ojb4
Working on this assignment allowed me to practice and learn SQL, Python and Flask a lot. By finishing Week 9 of the CS50x, I reached another milestone.

View File

@@ -1,44 +1,44 @@
---
title: CS50x Course Completed
desc: About me completing the CS50s Introduction to Computer Science course.
date: 2024-05-27T21:47:00+0800
topics: ["cs50", "cs50x", "python", "flask", "sql"]
---
On 23 May 2024, at long last, I completed [CS50s Introduction to Computer Science](https://cs50.harvard.edu/x/2024/), also known as CS50x, course!
After finishing [Week 9 of CS50x](2024-04-15-cs50x-week-9-completed.md), I began to brainstorm ideas for the [final project](https://cs50.harvard.edu/x/2024/project/), in terms of both project type and which language or tech stack to build. Eventually, I chose to build a blogging application with Flask, SQL and Bootstrap, and name the project CS50 Flask Blog.
You can watch a demonstration of CS50 Flask Blog in this video:
https://www.youtube.com/watch?v=eH8Tq57KzXs
CS50 Flask Blog includes the following features:
- Create a user account with a password
- Write, edit and delete blog posts
- Rich text editor for writing and editing blog posts
- Change username and password
The concept of CS50 Flask Blog was a blogging application that can be run and used on a local machine. In addition, CS50 Flask Blog was designed to prioritise user privacy, as it requires users to create an account and log in to write blog posts, thus making it a good fit for private blog posts.
Another reason I decided to create a blogging application as my final project for CS50x was because I wanted to make use of and apply the Flask skills and knowledge I learned from Week 9 of CS50x. While my (current) speciality is front-end web development, I find Flask in conjunction with SQL useful for projects that need a database, like a blogging platform.
The idea of requiring users to log in to use the application's main functions was inspired by the [Finance problem set](https://cs50.harvard.edu/x/2024/psets/9/finance/) from Week 9 of CS50x. I based the code of the user account registration, logging in and logging out functions on the Finance problem set, but the rest of CS50 Flask Blog's code was written from scratch.
To make further good use of the user account feature and the SQL database of CS50 Flask Blog, I also added the ability for users to change their username and password, and use the SQL database to store users' blog posts.
During my research into building Flask apps, I learned about [Flask Blueprints](https://flask.palletsprojects.com/en/2.3.x/blueprints/), which are great for making a Flask project modular, scalable and maintainable. I discovered an excellent [Real Python tutorial for building a scalable Flask web project from scratch](https://realpython.com/flask-project/). Using Flask Blueprints was new to me, since this was not taught in CS50x's Flask lessons.
When I was working on CS50 Flask Blog, I was also looking for other tutorials about building a blogging project with Flask for reference and inspiration. I ended up referring to Noran Saber Abdelfattah's guide, ["Building a Flask Blog: A Step-by-Step Guide for Beginners"](https://medium.com/@noransaber685/building-a-flask-blog-a-step-by-step-guide-for-beginners-8bffe925cd0e) for how to write the functions to edit and delete posts, but I wrote different code than Abdelfattah's guide to accommodate CS50 Flask Blog's user account features.
After finishing the basic features of creating, editing and deleting posts, I wanted to incorporate a proper text editor into CS50 Flask Blog, so users have actual options to format the text of their blog posts, instead of being stuck with a basic text area input field. After searching the web for adding a text editor into a Flask application, I discovered [Flask-CKEditor](https://flask-ckeditor.readthedocs.io/en/latest/index.html), which allowed integration of the rick text editor CKEditor into a Flask project. Flask-CKEditor was another new thing I learned from building CS50 Flask Blog, so it also trained me to read and understand technical documentations more.
Per CS50x's instructions, after the process of writing the code and testing the project was done, I started writing a Read Me for CS50 Flask Blog, by introducing and describing the project and its features, explaining the file structure and the purposes of the files used in the project, listing development dependencies, as well as laying out the steps required to run the project locally.
For the demo video, which was required to submit the final project, I used Bandicam to record a demo video and Kdenlive to edit the video, which allowed me to practice video editing. I searched for a stock clip and a stock music on Pixabay to add flavour to my demo video.
After submitting CS50 Flask Blog as my final project, I successfully claimed my CS50x certificate! You can also [view the PDF version of my CS50x certificate](https://cs50.harvard.edu/certificates/8cb0f5a4-4107-4df6-8abc-cfab3a437367).
<img src="{{ src 'CS50x-certificate_bercpp.avif' }}" srcset="{{ srcset 'CS50x-certificate_bercpp.avif', [260, 649, 980, 1320, 1580, 1830, 2048] }}" sizes="(min-width: 1020px) calc(54.9vw - 67px), calc(96.57vw - 30px)" alt="Helen Chong's CS50x certificate" width="2746" height="1588" loading="lazy">
Taking the CS50x course had been a great opportunity for me to learn about computer science, how to learn different programming languages and practice problem-solving skills with its problem sets. I am continuing to focus on front-end web development, but I hope I could carry what I learned from CS50x in my developer journey.
---
title: CS50x Course Completed
desc: About me completing the CS50s Introduction to Computer Science course.
date: 2024-05-27T21:47:00+0800
topics: ["cs50", "cs50x", "python", "flask", "sql"]
---
On 23 May 2024, at long last, I completed [CS50s Introduction to Computer Science](https://cs50.harvard.edu/x/2024/), also known as CS50x, course!
After finishing [Week 9 of CS50x](2024-04-15-cs50x-week-9-completed.md), I began to brainstorm ideas for the [final project](https://cs50.harvard.edu/x/2024/project/), in terms of both project type and which language or tech stack to build. Eventually, I chose to build a blogging application with Flask, SQL and Bootstrap, and name the project CS50 Flask Blog.
You can watch a demonstration of CS50 Flask Blog in this video:
https://www.youtube.com/watch?v=eH8Tq57KzXs
CS50 Flask Blog includes the following features:
- Create a user account with a password
- Write, edit and delete blog posts
- Rich text editor for writing and editing blog posts
- Change username and password
The concept of CS50 Flask Blog was a blogging application that can be run and used on a local machine. In addition, CS50 Flask Blog was designed to prioritise user privacy, as it requires users to create an account and log in to write blog posts, thus making it a good fit for private blog posts.
Another reason I decided to create a blogging application as my final project for CS50x was because I wanted to make use of and apply the Flask skills and knowledge I learned from Week 9 of CS50x. While my (current) speciality is front-end web development, I find Flask in conjunction with SQL useful for projects that need a database, like a blogging platform.
The idea of requiring users to log in to use the application's main functions was inspired by the [Finance problem set](https://cs50.harvard.edu/x/2024/psets/9/finance/) from Week 9 of CS50x. I based the code of the user account registration, logging in and logging out functions on the Finance problem set, but the rest of CS50 Flask Blog's code was written from scratch.
To make further good use of the user account feature and the SQL database of CS50 Flask Blog, I also added the ability for users to change their username and password, and use the SQL database to store users' blog posts.
During my research into building Flask apps, I learned about [Flask Blueprints](https://flask.palletsprojects.com/en/2.3.x/blueprints/), which are great for making a Flask project modular, scalable and maintainable. I discovered an excellent [Real Python tutorial for building a scalable Flask web project from scratch](https://realpython.com/flask-project/). Using Flask Blueprints was new to me, since this was not taught in CS50x's Flask lessons.
When I was working on CS50 Flask Blog, I was also looking for other tutorials about building a blogging project with Flask for reference and inspiration. I ended up referring to Noran Saber Abdelfattah's guide, ["Building a Flask Blog: A Step-by-Step Guide for Beginners"](https://medium.com/@noransaber685/building-a-flask-blog-a-step-by-step-guide-for-beginners-8bffe925cd0e) for how to write the functions to edit and delete posts, but I wrote different code than Abdelfattah's guide to accommodate CS50 Flask Blog's user account features.
After finishing the basic features of creating, editing and deleting posts, I wanted to incorporate a proper text editor into CS50 Flask Blog, so users have actual options to format the text of their blog posts, instead of being stuck with a basic text area input field. After searching the web for adding a text editor into a Flask application, I discovered [Flask-CKEditor](https://flask-ckeditor.readthedocs.io/en/latest/index.html), which allowed integration of the rick text editor CKEditor into a Flask project. Flask-CKEditor was another new thing I learned from building CS50 Flask Blog, so it also trained me to read and understand technical documentations more.
Per CS50x's instructions, after the process of writing the code and testing the project was done, I started writing a Read Me for CS50 Flask Blog, by introducing and describing the project and its features, explaining the file structure and the purposes of the files used in the project, listing development dependencies, as well as laying out the steps required to run the project locally.
For the demo video, which was required to submit the final project, I used Bandicam to record a demo video and Kdenlive to edit the video, which allowed me to practice video editing. I searched for a stock clip and a stock music on Pixabay to add flavour to my demo video.
After submitting CS50 Flask Blog as my final project, I successfully claimed my CS50x certificate! You can also [view the PDF version of my CS50x certificate](https://cs50.harvard.edu/certificates/8cb0f5a4-4107-4df6-8abc-cfab3a437367).
<img src="{{ src 'CS50x-certificate_bercpp.avif' }}" srcset="{{ srcset 'CS50x-certificate_bercpp.avif', [260, 649, 980, 1320, 1580, 1830, 2048] }}" sizes="(min-width: 1020px) calc(54.9vw - 67px), calc(96.57vw - 30px)" alt="Helen Chong's CS50x certificate" width="2746" height="1588" loading="lazy">
Taking the CS50x course had been a great opportunity for me to learn about computer science, how to learn different programming languages and practice problem-solving skills with its problem sets. I am continuing to focus on front-end web development, but I hope I could carry what I learned from CS50x in my developer journey.

View File

@@ -1,22 +1,22 @@
---
title: Custom Domain Name for My Developer Website
desc: My developer website now has a custom domain name.
date: 2024-05-29T23:18:00+0800
topics: ["about this website"]
---
This website — my developer portfolio and blog website — now has a custom domain: [helenchong.dev](https://helenchong.dev/)!
As an aspiring developer, I have been putting effort in building, updating and maintaining my portfolio website, and later starting a blog on this site as well to write about coding and tech. The only thing missing for my website is a custom domain name to give the site and myself a unique identity on the web, so I have been considering buying a custom domain name for this website.
After doing research and comparing various domain name registrars, I settled for Porkbun for their very competitive prices for domain names, and registered an account there. Through searching for available domain names by using my own name, I found out that helenchong.com has been taken, so I ended up purchasing helenchong.dev as my domain name, since the .dev top-level domain (TLD) name is intended for developers, and its renewal price on Porkbun is close to the .com TLD.
helenchong.dev is also the very first domain name I ever owned, so configuring DNS to point a domain name to my website was also a new experience. Fortunately, my developer website is hosted on GitHub Pages, thus I could refer to GitHub's documentation on [configuring a custom domain name for a GitHub Pages site](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site) for how to do it.
There was a setback, however: Soon after I succeeded in verifying helenchong.dev and pointing to my website on GitHub, I realised that my domain name got reset after pushing new commits to GitHub. While GitHub's documentation mentioned that manually creating a `CNAME` file in the repository may be needed for websites that are built with static site generators, I did not understand the exact actions to perform for my Eleventy website, until I searched the web for how to do it with an Eleventy site and discovered Justus Grunow's guide ["How To Deploy an Eleventy Site to Github Pages with a Custom Domain"](https://www.justus.ws/tech/deploying-eleventy-to-github-pages/). Manually creating a `CNAME` file and configure Eleventy to copy the file into the output folder every time it builds my website saves myself the trouble of having to reconfigure my domain name on GitHub again and again when I make changes to my website.
Since Porkbun offers free email forwarding, I also take advantage of the feature by creating an email address with my domain name — contact@helenchong.dev, and pointing it to my Outlook email address. This means now you can contact me by sending an email to contact@helenchong.dev.
Another benefit of using a custom domain name for my GitHub user site is that all my other projects that are hosted on GitHub pages also share the same custom domain. For example, the live site of my [React Tenzies Game](https://github.com/helenclx/Tenzies-Game) can now be viewed at https://helenchong.dev/Tenzies-Game/.
Welcome to helenchong.dev! 🥳
---
title: Custom Domain Name for My Developer Website
desc: My developer website now has a custom domain name.
date: 2024-05-29T23:18:00+0800
topics: ["about this website"]
---
This website — my developer portfolio and blog website — now has a custom domain: [helenchong.dev](https://helenchong.dev/)!
As an aspiring developer, I have been putting effort in building, updating and maintaining my portfolio website, and later starting a blog on this site as well to write about coding and tech. The only thing missing for my website is a custom domain name to give the site and myself a unique identity on the web, so I have been considering buying a custom domain name for this website.
After doing research and comparing various domain name registrars, I settled for Porkbun for their very competitive prices for domain names, and registered an account there. Through searching for available domain names by using my own name, I found out that helenchong.com has been taken, so I ended up purchasing helenchong.dev as my domain name, since the .dev top-level domain (TLD) name is intended for developers, and its renewal price on Porkbun is close to the .com TLD.
helenchong.dev is also the very first domain name I ever owned, so configuring DNS to point a domain name to my website was also a new experience. Fortunately, my developer website is hosted on GitHub Pages, thus I could refer to GitHub's documentation on [configuring a custom domain name for a GitHub Pages site](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site) for how to do it.
There was a setback, however: Soon after I succeeded in verifying helenchong.dev and pointing to my website on GitHub, I realised that my domain name got reset after pushing new commits to GitHub. While GitHub's documentation mentioned that manually creating a `CNAME` file in the repository may be needed for websites that are built with static site generators, I did not understand the exact actions to perform for my Eleventy website, until I searched the web for how to do it with an Eleventy site and discovered Justus Grunow's guide ["How To Deploy an Eleventy Site to Github Pages with a Custom Domain"](https://www.justus.ws/tech/deploying-eleventy-to-github-pages/). Manually creating a `CNAME` file and configure Eleventy to copy the file into the output folder every time it builds my website saves myself the trouble of having to reconfigure my domain name on GitHub again and again when I make changes to my website.
Since Porkbun offers free email forwarding, I also take advantage of the feature by creating an email address with my domain name — contact@helenchong.dev, and pointing it to my Outlook email address. This means now you can contact me by sending an email to contact@helenchong.dev.
Another benefit of using a custom domain name for my GitHub user site is that all my other projects that are hosted on GitHub pages also share the same custom domain. For example, the live site of my [React Tenzies Game](https://github.com/helenclx/Tenzies-Game) can now be viewed at https://helenchong.dev/Tenzies-Game/.
Welcome to helenchong.dev! 🥳

View File

@@ -1,112 +1,112 @@
---
title: Finally Deployed My Scrimba React Solo Projects to GitHub Pages
desc: At long last, I found a way to deploy my solo projects for Scirmba's React course form my repository's subdirectories.
date: 2024-06-03T11:01:00+0800
topics: ["scrimba", "react", "github pages"]
hasCodeBlock: true
---
I had completed [Scrimba's Learn React course](https://scrimba.com/learn/learnreact) and its solo projects back in July 2023, even before [winning a one-year Scrimba Pro plan from JavaScriptmas 2023](2023-12-27-JavaScriptmas-2023-Challenge-Completed-and-Won.md) for free, since the course, including the instructions and Figma files for the solo projects, is also available on freeCodeCamp's YouTube channel.
However, I always struggled to figure out how to deploy React project from a GitHub repository's subfolders or subdirectories, and frustrated by the lack of guides for that. Scrimba's course does teach about using GitHub, but they often recommend deploying projects from their course to Netlify by doing it manually on [Netlify Drop](https://app.netlify.com/drop). To be fair, Netlify Drop is great for beginner developers, especially when they are new to source control such as Git. However, when I finished Scirmba's Learn React course, I already had a handful of projects deployed to GitHub Pages. Later, I even managed to deploy my React Tenzies Game, which was based on the tutorial from the Learn React course, to GitHub Pages by creating a [dedicated repository for my Tenzies Game](https://github.com/helenclx/Tenzies-Game) and searching for a guide for how to deploy a React project to GitHub Pages.
Unfortunately, I could not find any guide for how to deploy React projects from multiple subfolders or subdirectories of a repository, and I did not have enough experience with using GitHub actions to feel comfortable trying to figure it out myself, so I had to manually deploy my Scrimba React solo projects to Netlify by using Netlify Drop, which I do not find ideal because I had to repeat the process every time I made a change to those projects.
Lately, as I have been busy with maintaining and improving my projects hosted on GitHub, I suddenly thought of trying to search for ways to deploy React projects to GitHub Pages from the repository's subdirectories again. That was when I, at long last, found a guide that helped me to solve my months-long dilemma once and for all: ["Deploy a react projects that are inside a subdirectories to GitHub Pages using GitHub Actions (CI/CD)" by DEVLOKER](https://dev.to/devloker/deploy-a-react-projects-that-are-inside-a-subdirectories-to-github-pages-using-github-actions-cicd-3n9l). It is worth noting that the guide was posted only recently on 11 April 2024. The guide also uses Vite for React projects, which was perfect for me since Vite is also what I use to create React projects, including the ones from Scrimba courses. The guide's Vite configuration file is in TypeScript format (`vite.config.ts`), but it totally works with JavaScript format (`vite.config.js`) too.
This was my set-up in the YAML file of my GitHub action when I succeeded in deploying my Scrimba React solo projects to GitHub Pages:
```yaml
name: Deploy to GitHub Pages
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: latest
# digital-business-card project
- name: Build digital-business-card
run: |
cd 1-digital-business-card
npm install && npm run build
cd ..
mkdir -p deploy/1-digital-business-card
cp -r 1-digital-business-card/dist/* deploy/1-digital-business-card/
# travel-journal project
- name: Build travel-journal
run: |
cd 2-travel-journal
npm install && npm run build
cd ..
mkdir -p deploy/2-travel-journal
cp -r 2-travel-journal/dist/* deploy/2-travel-journal/
# quizzical project
- name: Build quizzical
run: |
cd 3-quizzical
npm install && npm run build
cd ..
mkdir -p deploy/3-quizzical
cp -r 3-quizzical/dist/* deploy/3-quizzical/
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: "./deploy"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
```
This set-up made my Scrimba React solo projects live at the following links:
- [Digital Business Card](https://helenclx.github.io/Scrimba-React-Solo-Projects/1-digital-business-card/)
- [Travel Journal](https://helenclx.github.io/Scrimba-React-Solo-Projects/2-travel-journal/)
- [Quizzical](https://helenclx.github.io/Scrimba-React-Solo-Projects/3-quizzical/)
I was beyond relief to finally deployed my Scrimba React solo projects to GitHub Pages, so I decided to do a bit more by creating a HTML home page to list those solo projects when the main link of the deployed repository, https://helenclx.github.io/Scrimba-React-Solo-Projects/, is visited.
I do not need a fancy home page for my deployed Scrimba React solo projects, so I based the home page's CSS on the [Smol Document Styles by SmolCSS](https://SmolCSS.dev/#smol-document-styles).
After finished creating the HTML and CSS of the home page, I wrote an extra step in the YAML file of my GitHub actions to deploy my React solo projects to GitHub Pages, after building each project and before setting up GitHub Pages.
```yaml
# index page and assets
- name: Deploy index page and assets
run: |
mkdir -p deploy
cp -r index.html style.css deploy/
cp -r 1-digital-business-card/screenshot.png deploy/1-digital-business-card/
cp -r 2-travel-journal/screenshot.png deploy/2-travel-journal/
cp -r 3-quizzical/screenshot-start.png 3-quizzical/screenshot-questions.png 3-quizzical/screenshot-answers.png deploy/3-quizzical/
```
What this step does is creating a "deploy" folder and then copy the home page's HTML, CSS to the "deploy" folder, and copy the project screenshots to the folder of each project.
Here is the final code of my GitHub action TAML file to deploy my Scrimba React solo projects in [my Scrimba React solo projects repository](https://github.com/helenclx/Scrimba-React-Solo-Projects/blob/home/.github/workflows/gh-pages.yml).
Now that I have successfully deployed my Scrimba React solo projects to GitHub Pages, I can finally delete those projects from Netlify and free up more monthly bandwidth for my other projects that I actually want to deploy to Netlify.
---
title: Finally Deployed My Scrimba React Solo Projects to GitHub Pages
desc: At long last, I found a way to deploy my solo projects for Scirmba's React course form my repository's subdirectories.
date: 2024-06-03T11:01:00+0800
topics: ["scrimba", "react", "github pages"]
hasCodeBlock: true
---
I had completed [Scrimba's Learn React course](https://scrimba.com/learn/learnreact) and its solo projects back in July 2023, even before [winning a one-year Scrimba Pro plan from JavaScriptmas 2023](2023-12-27-JavaScriptmas-2023-Challenge-Completed-and-Won.md) for free, since the course, including the instructions and Figma files for the solo projects, is also available on freeCodeCamp's YouTube channel.
However, I always struggled to figure out how to deploy React project from a GitHub repository's subfolders or subdirectories, and frustrated by the lack of guides for that. Scrimba's course does teach about using GitHub, but they often recommend deploying projects from their course to Netlify by doing it manually on [Netlify Drop](https://app.netlify.com/drop). To be fair, Netlify Drop is great for beginner developers, especially when they are new to source control such as Git. However, when I finished Scirmba's Learn React course, I already had a handful of projects deployed to GitHub Pages. Later, I even managed to deploy my React Tenzies Game, which was based on the tutorial from the Learn React course, to GitHub Pages by creating a [dedicated repository for my Tenzies Game](https://github.com/helenclx/Tenzies-Game) and searching for a guide for how to deploy a React project to GitHub Pages.
Unfortunately, I could not find any guide for how to deploy React projects from multiple subfolders or subdirectories of a repository, and I did not have enough experience with using GitHub actions to feel comfortable trying to figure it out myself, so I had to manually deploy my Scrimba React solo projects to Netlify by using Netlify Drop, which I do not find ideal because I had to repeat the process every time I made a change to those projects.
Lately, as I have been busy with maintaining and improving my projects hosted on GitHub, I suddenly thought of trying to search for ways to deploy React projects to GitHub Pages from the repository's subdirectories again. That was when I, at long last, found a guide that helped me to solve my months-long dilemma once and for all: ["Deploy a react projects that are inside a subdirectories to GitHub Pages using GitHub Actions (CI/CD)" by DEVLOKER](https://dev.to/devloker/deploy-a-react-projects-that-are-inside-a-subdirectories-to-github-pages-using-github-actions-cicd-3n9l). It is worth noting that the guide was posted only recently on 11 April 2024. The guide also uses Vite for React projects, which was perfect for me since Vite is also what I use to create React projects, including the ones from Scrimba courses. The guide's Vite configuration file is in TypeScript format (`vite.config.ts`), but it totally works with JavaScript format (`vite.config.js`) too.
This was my set-up in the YAML file of my GitHub action when I succeeded in deploying my Scrimba React solo projects to GitHub Pages:
```yaml
name: Deploy to GitHub Pages
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: latest
# digital-business-card project
- name: Build digital-business-card
run: |
cd 1-digital-business-card
npm install && npm run build
cd ..
mkdir -p deploy/1-digital-business-card
cp -r 1-digital-business-card/dist/* deploy/1-digital-business-card/
# travel-journal project
- name: Build travel-journal
run: |
cd 2-travel-journal
npm install && npm run build
cd ..
mkdir -p deploy/2-travel-journal
cp -r 2-travel-journal/dist/* deploy/2-travel-journal/
# quizzical project
- name: Build quizzical
run: |
cd 3-quizzical
npm install && npm run build
cd ..
mkdir -p deploy/3-quizzical
cp -r 3-quizzical/dist/* deploy/3-quizzical/
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: "./deploy"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
```
This set-up made my Scrimba React solo projects live at the following links:
- [Digital Business Card](https://helenclx.github.io/Scrimba-React-Solo-Projects/1-digital-business-card/)
- [Travel Journal](https://helenclx.github.io/Scrimba-React-Solo-Projects/2-travel-journal/)
- [Quizzical](https://helenclx.github.io/Scrimba-React-Solo-Projects/3-quizzical/)
I was beyond relief to finally deployed my Scrimba React solo projects to GitHub Pages, so I decided to do a bit more by creating a HTML home page to list those solo projects when the main link of the deployed repository, https://helenclx.github.io/Scrimba-React-Solo-Projects/, is visited.
I do not need a fancy home page for my deployed Scrimba React solo projects, so I based the home page's CSS on the [Smol Document Styles by SmolCSS](https://SmolCSS.dev/#smol-document-styles).
After finished creating the HTML and CSS of the home page, I wrote an extra step in the YAML file of my GitHub actions to deploy my React solo projects to GitHub Pages, after building each project and before setting up GitHub Pages.
```yaml
# index page and assets
- name: Deploy index page and assets
run: |
mkdir -p deploy
cp -r index.html style.css deploy/
cp -r 1-digital-business-card/screenshot.png deploy/1-digital-business-card/
cp -r 2-travel-journal/screenshot.png deploy/2-travel-journal/
cp -r 3-quizzical/screenshot-start.png 3-quizzical/screenshot-questions.png 3-quizzical/screenshot-answers.png deploy/3-quizzical/
```
What this step does is creating a "deploy" folder and then copy the home page's HTML, CSS to the "deploy" folder, and copy the project screenshots to the folder of each project.
Here is the final code of my GitHub action TAML file to deploy my Scrimba React solo projects in [my Scrimba React solo projects repository](https://github.com/helenclx/Scrimba-React-Solo-Projects/blob/home/.github/workflows/gh-pages.yml).
Now that I have successfully deployed my Scrimba React solo projects to GitHub Pages, I can finally delete those projects from Netlify and free up more monthly bandwidth for my other projects that I actually want to deploy to Netlify.

View File

@@ -1,32 +1,32 @@
---
title: Dipping My Toes in PHP for My Hobby Project
desc: I started to learn to code in PHP for my hobby project.
date: 2024-07-08T13:21:00+0800
topics: ["php", "bellabuffs"]
---
Hobbies are a great motivator to learn new things, and web development is no exception. I had dabbled in HTML and CSS since my teenage years, but it was not until December 2022 when I decided to take online web development courses, and then aspire to become a web developer.
Recently, I took a particular interest in fanlistings. For those who are unfamiliar with it, a fanlisting is an online listing of fans for a particular subject open for fans around the world to join. I find fanlistings a neat concept to gather and discover other people who share your interests. You can find fanlistings through [The Fanlistings Network](https://thefanlistings.org/) and [The Anime Fanlistings Network](https://thefanlistings.org/).
As an aspiring web developer, I became curious about how a fanlisting is built, and even wondering if I could do it myself. After some searching, I discovered the two most used fanlisting scripts are [Enthusiast](https://scripts.indisguise.org/enthusiast/) and [BellaBuffs](https://www.jemjabella.co.uk/scripts/bellabuffs/), both written in PHP. My focus has been on front end web development, so studying how fanlistings are built was my first foray in learning to code in PHP.
In order to learn to code web pages in PHP, I installed PHP in my computer, and registered an account on [InfinityFree](https://www.infinityfree.com/), one of the few hosting servers that provide PHP and MySQL support for free.
Initially, I tried out [Enthusiast](https://scripts.indisguise.org/enthusiast/), specifically [Erin's fork of Enthusiast](https://github.com/DudeThatsErin/enth), since InfinityFree uses version 8.2 of PHP Enthusiast also has extra features that BellaBuffs does not have, including allowing fanlisting members to set passwords and even managing the entire fanlisting collective.
Unfortunately, I encountered a critical issue with Enthusiast when I tried it: whenever I added a fanlisting to my joined fanlisting list in the admin panel, the home page of the fanlisting collective would go completely blank. I do not know the cause of the issue, and I was not familiar enough with PHP to be confident in attempting to investigate the entire fanlisting collective code just to identify the culprit, especially when it uses MySQL database to store data, adding another complexity in troubleshooting it.
Therefore, I later tried BellaBuffs, and deiced BellaBuffs to be my personal choice of building and managing a fanlisting.
There were a few setbacks I encountered when I was learning to build a fanlisting with BellaBuffs on InfinityFree — I learned that InfinityFree's free hosting plan [does not support PHP `mail()` function](https://forum.infinityfree.com/t/sending-email-from-your-website-php-mail/49242), which is used by BellaBuffs.
BellaBuffs includes the optional features to email new members after submitting the join form, email the fanlisting admin when a new member joins or email new members when their applications were approved. The original BellaBuffs script achieved this by using PHP's built-in `mail()` feature, which unfortunately the free hosting of InfinityFree does not support, so I had to figure out an alternative way send emails from a fanlisting powered by BellaBuffs.
Thankfully, InfinityFree suggested using PHPMailer, and even providing a [contact form powered by PHPMailer](https://github.com/InfinityFreeHosting/contactform) as a sample. As a result, I spent time in trying to incorporate PHPMailer into BellaBuffs based on the InfinityFree's PHPMailer contact form code, and voilà, I did it!
I am glad that despite my inexperience in PHP, I was able to apply the programming knowledge I learned from the coding courses, including CS50's Introduction to Computer Science, to solve an issue with a PHP script.
I decided to fork BellaBuffs and release my version of the fanlisting script with PHPMailer integration, in case there are others who want to use BellaBuffs to build fanlistings, but cannot or do not want to use the PHP `mail()` function.
You can download my BellaBuffs fork and learn about how to use it from [its GitHub repository](https://github.com/helenclx/BellaBuffs-PHPMailer).
---
title: Dipping My Toes in PHP for My Hobby Project
desc: I started to learn to code in PHP for my hobby project.
date: 2024-07-08T13:21:00+0800
topics: ["php", "bellabuffs"]
---
Hobbies are a great motivator to learn new things, and web development is no exception. I had dabbled in HTML and CSS since my teenage years, but it was not until December 2022 when I decided to take online web development courses, and then aspire to become a web developer.
Recently, I took a particular interest in fanlistings. For those who are unfamiliar with it, a fanlisting is an online listing of fans for a particular subject open for fans around the world to join. I find fanlistings a neat concept to gather and discover other people who share your interests. You can find fanlistings through [The Fanlistings Network](https://thefanlistings.org/) and [The Anime Fanlistings Network](https://thefanlistings.org/).
As an aspiring web developer, I became curious about how a fanlisting is built, and even wondering if I could do it myself. After some searching, I discovered the two most used fanlisting scripts are [Enthusiast](https://scripts.indisguise.org/enthusiast/) and [BellaBuffs](https://www.jemjabella.co.uk/scripts/bellabuffs/), both written in PHP. My focus has been on front end web development, so studying how fanlistings are built was my first foray in learning to code in PHP.
In order to learn to code web pages in PHP, I installed PHP in my computer, and registered an account on [InfinityFree](https://www.infinityfree.com/), one of the few hosting servers that provide PHP and MySQL support for free.
Initially, I tried out [Enthusiast](https://scripts.indisguise.org/enthusiast/), specifically [Erin's fork of Enthusiast](https://github.com/DudeThatsErin/enth), since InfinityFree uses version 8.2 of PHP Enthusiast also has extra features that BellaBuffs does not have, including allowing fanlisting members to set passwords and even managing the entire fanlisting collective.
Unfortunately, I encountered a critical issue with Enthusiast when I tried it: whenever I added a fanlisting to my joined fanlisting list in the admin panel, the home page of the fanlisting collective would go completely blank. I do not know the cause of the issue, and I was not familiar enough with PHP to be confident in attempting to investigate the entire fanlisting collective code just to identify the culprit, especially when it uses MySQL database to store data, adding another complexity in troubleshooting it.
Therefore, I later tried BellaBuffs, and deiced BellaBuffs to be my personal choice of building and managing a fanlisting.
There were a few setbacks I encountered when I was learning to build a fanlisting with BellaBuffs on InfinityFree — I learned that InfinityFree's free hosting plan [does not support PHP `mail()` function](https://forum.infinityfree.com/t/sending-email-from-your-website-php-mail/49242), which is used by BellaBuffs.
BellaBuffs includes the optional features to email new members after submitting the join form, email the fanlisting admin when a new member joins or email new members when their applications were approved. The original BellaBuffs script achieved this by using PHP's built-in `mail()` feature, which unfortunately the free hosting of InfinityFree does not support, so I had to figure out an alternative way send emails from a fanlisting powered by BellaBuffs.
Thankfully, InfinityFree suggested using PHPMailer, and even providing a [contact form powered by PHPMailer](https://github.com/InfinityFreeHosting/contactform) as a sample. As a result, I spent time in trying to incorporate PHPMailer into BellaBuffs based on the InfinityFree's PHPMailer contact form code, and voilà, I did it!
I am glad that despite my inexperience in PHP, I was able to apply the programming knowledge I learned from the coding courses, including CS50's Introduction to Computer Science, to solve an issue with a PHP script.
I decided to fork BellaBuffs and release my version of the fanlisting script with PHPMailer integration, in case there are others who want to use BellaBuffs to build fanlistings, but cannot or do not want to use the PHP `mail()` function.
You can download my BellaBuffs fork and learn about how to use it from [its GitHub repository](https://github.com/helenclx/BellaBuffs-PHPMailer).

View File

@@ -1,94 +1,94 @@
---
title: Aboard the Eleventy 3.0 Train
desc: My developer portfolio and blog website has been officially upgraded to Eleventy 3.0.
date: 2024-07-19T15:31:00+0800
updated: 2024-09-27T11:41:53+0800
templateEngineOverride: md
topics: ["about this website", "eleventy", "javascript"]
toc: true
hasCodeBlock: true
---
On 9 April 2024, I [rebuilt this developer portfolio and blog website of mine](2024-04-11-rebuilding-my-developer-portfolio-with-eleventy.md) with Eleventy 2.0.1. Three months after using Eleventy and loving it, now I have upgraded Eleventy to 3.0! 🥳
Version 3.0 of Eleventy is still in alpha, but as I have been following Eleventy's development and community after starting to use it, and learning about other Eleventy users upgrading to version 3.0, I began to consider hopping on the Eleventy 3.0 train. Now that I have done it, I guess I am one of Eleventy's "courageous canary testers", [as the official Eleventy blog put it](https://www.11ty.dev/blog/canary-eleventy-v3/), haha. 😆
## Why Upgrading to 3.0
### ESM Support
The first major factor that made me consider upgrading Eleventy to 3.0 is 3.0's support for ECMAScript Modules (ESM).
Since I began to learn web development more seriously starting from December 2022, I have been using ESM in my JavaScript projects, including vanilla JavaScript and React, due to that being what is taught in Scrimba's JavaScript and React courses. As a consequence, Eleventy 2.0 was my first time using CommonJS, so I had to learn the syntax differences between the CommonJS and ESM. I learned about 3.0's ESM support soon after I began to use Eleventy, but I decided to stick to 2.0 for a bit because I wanted to familiarise myself with Eleventy first.
As I learned to use Eleventy more and becoming more satisfied with my decision to build my website with it, I felt more confident in my ability of trying out an alpha release of Eleventy.
### Built-In Bundle Plugin
Another major push for me to upgrade to Eleventy 3.0 was its [built-in bundle plugin](https://github.com/11ty/eleventy-plugin-bundle). I have been considering to use the plugin to bundle my CSS and JavaScript for specific components, including the header, footer, scroll-to-top button link, and so on, so the CSS and JavaScript code blocks I wrote in the components' templates would be bundled into the `<head>` element. Without bundling the CSS and JavaScript, the `<style>` and `<script>` tags would scatter across the `<body>` element in the HTML output, which is not a good practice, as the [W3C Markdown Validation Service](https://validator.w3.org/) would output errors when I checked the HTML output.
Previously, Eleventy's bundle plugin was a separate plugin that needed to be installed manually, as seen in the [Eleventy base blog](https://github.com/11ty/eleventy-base-blog) starter project, which uses Eleventy 2.0.1, though starting with Eleventy v3.0.0-alpha.10, the bundle plugin is now bundled with Eleventy.
Therefore, I finally made the jump, by keying in `bun install @11ty/eleventy@canary --save-exact` in my website's folder on the terminal, and voilà! Eleventy 3.0 here I come!
(Yes, I use [Bun](https://bun.sh/) as the JavaScript runtime for developing my Eleventy website and deploying it to GitHub Pages, and it works since Bun is meant to be a drop-in replacement for Node.js)
## Step 1: Changing My `package.json` Scripts
A change I had to made immediately after installing Eleventy 3.0 was to edit my `package.json` scripts for Eleventy, including "start" and "build" scripts, by replacing `@11ty/eleventy` to just `eleventy`, since otherwise the terminal will output an error saying "Script not found '@11ty/eleventy'".
This happens when I run the Eleventy commands with either `npm` or `bun`.
## Step 2: Bundling CSS and JavaScript
With problems with my `package.json` scripts solved, I could finally begin my process to transform my website to an Eleventy 3.0 site, by starting with bundling my component-specific CSS and JavaScript.
I followed the instructions from [Eleventy bundle plugin's GitHub repository](https://github.com/11ty/eleventy-plugin-bundle), by adding `addBundle("css")` and `addBundle("js")` to my Eleventy configuration file.
I want to write my bundled JavaScript into files with the `getBundleFileUrl` shortcode, so I can add the `defer` HTML attribute to the `<script>` tags that load the bundled file. For this purpose, I use the `toFileDirectory` to configure my JavaScript bundle to write the bundled JavaScript files into the output's assets folder and its JavaScript subfolder, so the setup to bundle my CSS and JavaScript in my Eleventy configuration file becomes this:
```js
eleventyConfig.addBundle("css");
eleventyConfig.addBundle("js", { toFileDirectory: "assets/js" });
```
Next, I opened the base layout template for my entire website, and added the `getBundle` shortcode for CSS and `getBundleFileUrl` shortcode for JavaScript in the `<head>` element, then I replaced the `<style>` and `<script>` tags in my component Nunjucks templates with `{{ css }}` and `{{ js }}` shortcodes.
Now my component-specific CSS and JavaScript are bundled in the output.
## Step 3: Converting CommonJS to ESM
It was finally time to go all way in using ESM in my projects that use JavaScript, including Eleventy, by converting all CommonJS syntax in my Eleventy configuration and data files to ESM.
The first step was to add `"type": "module"` to my `package.json`, to make my Eleventy project environment support ESM.
Fun fact: When you create a `package.json` file with Bun, ``"type": "module"`` will be added by default. This actually tripped me up when I created my Eleventy 2.0 website with Bun, and made me learn about the differences between CommonJS and ESM the hard way, so I had to remove `"type": "module"`. Therefore, it feels good to have `"type": "module"` back to my `package.json` file.
Fortunately, Visual Studio Code, my go-to code editor, can automatically convert the CommonJS syntax of the imports in my Eleventy configuration files and the exports of the Eleventy configuration functions to ESM. Visual Studio Code also converts the `module.exports` syntax in my website metadata file, by assigning `const` variables and adding the `export` declaration, which allows me to use the variables and their values in other variables in the same file, without using the `this` keyword. For example:
```js
export const siteDomain = "helenchong.dev";
export const siteUrl = "https://" + siteDomain || "http://localhost:8080";
export const siteAuthor = {
    name: "Helen Chong",
    email: "contact@helenchong.dev",
    url: siteUrl + "/about"
};
export const siteDescription = siteAuthor.name + "'s developer portfolio and blog website.";
```
However, I needed to manually change the `require()` functions inside the Eleventy configuration function, by using the `import` declaration at the top of the file, and assign them variables.
The process of converting CommonJS to ESM in my Eleventy website was smoother than I expected. It might be because my website is not that complex, and my methods of organising my website files were relatively compatible with ESM.
## Conclusion
My website's transition to an Eleventy 3.0 site is a success, and I could not be happier with my decision to make the upgrade. If you like Eleventy, I highly recommend giving 3.0 a try.
Hello, Eleventy 3.0! 👋
## Update, 27 September 2024: THE Eleventy Meetup Episode 19
I was invited to speak at [THE Eleventy Meetup Episode 19](https://11tymeetup.dev/events/ep-19-migrating-to-3-0-and-blogging-with-storyblok/) on 26 September 2024, by giving a talk based on this blog post! You can watch my talk at THE Eleventy Meetup's YouTube Channel.
https://www.youtube.com/watch?v=qgFNl_oAyQY
---
title: Aboard the Eleventy 3.0 Train
desc: My developer portfolio and blog website has been officially upgraded to Eleventy 3.0.
date: 2024-07-19T15:31:00+0800
updated: 2024-09-27T11:41:53+0800
templateEngineOverride: md
topics: ["about this website", "eleventy", "javascript"]
toc: true
hasCodeBlock: true
---
On 9 April 2024, I [rebuilt this developer portfolio and blog website of mine](2024-04-11-rebuilding-my-developer-portfolio-with-eleventy.md) with Eleventy 2.0.1. Three months after using Eleventy and loving it, now I have upgraded Eleventy to 3.0! 🥳
Version 3.0 of Eleventy is still in alpha, but as I have been following Eleventy's development and community after starting to use it, and learning about other Eleventy users upgrading to version 3.0, I began to consider hopping on the Eleventy 3.0 train. Now that I have done it, I guess I am one of Eleventy's "courageous canary testers", [as the official Eleventy blog put it](https://www.11ty.dev/blog/canary-eleventy-v3/), haha. 😆
## Why Upgrading to 3.0
### ESM Support
The first major factor that made me consider upgrading Eleventy to 3.0 is 3.0's support for ECMAScript Modules (ESM).
Since I began to learn web development more seriously starting from December 2022, I have been using ESM in my JavaScript projects, including vanilla JavaScript and React, due to that being what is taught in Scrimba's JavaScript and React courses. As a consequence, Eleventy 2.0 was my first time using CommonJS, so I had to learn the syntax differences between the CommonJS and ESM. I learned about 3.0's ESM support soon after I began to use Eleventy, but I decided to stick to 2.0 for a bit because I wanted to familiarise myself with Eleventy first.
As I learned to use Eleventy more and becoming more satisfied with my decision to build my website with it, I felt more confident in my ability of trying out an alpha release of Eleventy.
### Built-In Bundle Plugin
Another major push for me to upgrade to Eleventy 3.0 was its [built-in bundle plugin](https://github.com/11ty/eleventy-plugin-bundle). I have been considering to use the plugin to bundle my CSS and JavaScript for specific components, including the header, footer, scroll-to-top button link, and so on, so the CSS and JavaScript code blocks I wrote in the components' templates would be bundled into the `<head>` element. Without bundling the CSS and JavaScript, the `<style>` and `<script>` tags would scatter across the `<body>` element in the HTML output, which is not a good practice, as the [W3C Markdown Validation Service](https://validator.w3.org/) would output errors when I checked the HTML output.
Previously, Eleventy's bundle plugin was a separate plugin that needed to be installed manually, as seen in the [Eleventy base blog](https://github.com/11ty/eleventy-base-blog) starter project, which uses Eleventy 2.0.1, though starting with Eleventy v3.0.0-alpha.10, the bundle plugin is now bundled with Eleventy.
Therefore, I finally made the jump, by keying in `bun install @11ty/eleventy@canary --save-exact` in my website's folder on the terminal, and voilà! Eleventy 3.0 here I come!
(Yes, I use [Bun](https://bun.sh/) as the JavaScript runtime for developing my Eleventy website and deploying it to GitHub Pages, and it works since Bun is meant to be a drop-in replacement for Node.js)
## Step 1: Changing My `package.json` Scripts
A change I had to made immediately after installing Eleventy 3.0 was to edit my `package.json` scripts for Eleventy, including "start" and "build" scripts, by replacing `@11ty/eleventy` to just `eleventy`, since otherwise the terminal will output an error saying "Script not found '@11ty/eleventy'".
This happens when I run the Eleventy commands with either `npm` or `bun`.
## Step 2: Bundling CSS and JavaScript
With problems with my `package.json` scripts solved, I could finally begin my process to transform my website to an Eleventy 3.0 site, by starting with bundling my component-specific CSS and JavaScript.
I followed the instructions from [Eleventy bundle plugin's GitHub repository](https://github.com/11ty/eleventy-plugin-bundle), by adding `addBundle("css")` and `addBundle("js")` to my Eleventy configuration file.
I want to write my bundled JavaScript into files with the `getBundleFileUrl` shortcode, so I can add the `defer` HTML attribute to the `<script>` tags that load the bundled file. For this purpose, I use the `toFileDirectory` to configure my JavaScript bundle to write the bundled JavaScript files into the output's assets folder and its JavaScript subfolder, so the setup to bundle my CSS and JavaScript in my Eleventy configuration file becomes this:
```js
eleventyConfig.addBundle("css");
eleventyConfig.addBundle("js", { toFileDirectory: "assets/js" });
```
Next, I opened the base layout template for my entire website, and added the `getBundle` shortcode for CSS and `getBundleFileUrl` shortcode for JavaScript in the `<head>` element, then I replaced the `<style>` and `<script>` tags in my component Nunjucks templates with `{{ css }}` and `{{ js }}` shortcodes.
Now my component-specific CSS and JavaScript are bundled in the output.
## Step 3: Converting CommonJS to ESM
It was finally time to go all way in using ESM in my projects that use JavaScript, including Eleventy, by converting all CommonJS syntax in my Eleventy configuration and data files to ESM.
The first step was to add `"type": "module"` to my `package.json`, to make my Eleventy project environment support ESM.
Fun fact: When you create a `package.json` file with Bun, ``"type": "module"`` will be added by default. This actually tripped me up when I created my Eleventy 2.0 website with Bun, and made me learn about the differences between CommonJS and ESM the hard way, so I had to remove `"type": "module"`. Therefore, it feels good to have `"type": "module"` back to my `package.json` file.
Fortunately, Visual Studio Code, my go-to code editor, can automatically convert the CommonJS syntax of the imports in my Eleventy configuration files and the exports of the Eleventy configuration functions to ESM. Visual Studio Code also converts the `module.exports` syntax in my website metadata file, by assigning `const` variables and adding the `export` declaration, which allows me to use the variables and their values in other variables in the same file, without using the `this` keyword. For example:
```js
export const siteDomain = "helenchong.dev";
export const siteUrl = "https://" + siteDomain || "http://localhost:8080";
export const siteAuthor = {
    name: "Helen Chong",
    email: "contact@helenchong.dev",
    url: siteUrl + "/about"
};
export const siteDescription = siteAuthor.name + "'s developer portfolio and blog website.";
```
However, I needed to manually change the `require()` functions inside the Eleventy configuration function, by using the `import` declaration at the top of the file, and assign them variables.
The process of converting CommonJS to ESM in my Eleventy website was smoother than I expected. It might be because my website is not that complex, and my methods of organising my website files were relatively compatible with ESM.
## Conclusion
My website's transition to an Eleventy 3.0 site is a success, and I could not be happier with my decision to make the upgrade. If you like Eleventy, I highly recommend giving 3.0 a try.
Hello, Eleventy 3.0! 👋
## Update, 27 September 2024: THE Eleventy Meetup Episode 19
I was invited to speak at [THE Eleventy Meetup Episode 19](https://11tymeetup.dev/events/ep-19-migrating-to-3-0-and-blogging-with-storyblok/) on 26 September 2024, by giving a talk based on this blog post! You can watch my talk at THE Eleventy Meetup's YouTube Channel.
https://www.youtube.com/watch?v=qgFNl_oAyQY

View File

@@ -1,60 +1,60 @@
---
title: Responsive Disability Pride Flag CSS Background
desc: I coded the Disability Pride Flag in CSS to celebrate Disability Pride Month.
date: 2024-07-23T20:35:00+0800
updated: 2024-12-15T18:43:47+0800
topics: ["css", "disability pride"]
hasCodeBlock: true
---
{{ set demoHeight = "23rem" }}
{{ set disabilityPrideCss }}.disability-pride-flag {
background:
linear-gradient(
37deg,
#595959 0 35%,
#CF7280 30% 41%,
#EEDE77 30% 47%,
#E8E8E8 30% 53%,
#7bc2e0 30% 59%,
#3BB07D 30% 65%,
#595959 0
);
}{{ /set }}
{{ css }}
{{ disabilityPrideCss }}
.disability-pride-flag.demo { height: {{ demoHeight }}; }
{{ /css }}
<div class="disability-pride-flag demo" role="img" aria-label="Demo of the Disability Pride flag coded in CSS"></div>
Happy [Disability Pride Month](https://en.wikipedia.org/wiki/Disability_Pride_Month)! To celebrate the occasion, I did a small project: a responsive Disability Pride flag CSS background! For those who do not know, this [Disability Pride flag](https://www.womansday.com/life/a43964487/disability-pride-flag/) was created by [Ann Magill](https://capri0mni.dreamwidth.org/837596.html) and released in 2021.
My Disability Pride flag CSS background is done with a combination of the CSS [`background` property](https://developer.mozilla.org/en-US/docs/Web/CSS/background) and [`linuear-gradient()` function](https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient), and designed to be responsive, so it can adapt to different screen sizes and devices.
Here is the CSS snippet for a responsive Disability Pride Flag background:
```css
{{ disabilityPrideCss }}
```
After copying the above snippet in a CSS stylesheet or the HTML `<style>` tags, to apply the background to an HTML element, simply add the CSS class name `disability-pride-flag` (you can rename the class name if you want) to the HTML element's `class` attribute.
Note that you want to apply the Pride flag to an empty element, you also need to set the element's [`height`](https://developer.mozilla.org/en-US/docs/Web/CSS/height) or [`aspect-ratio`](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio) property to make the Pride flag background visible. For example, the demo above sets the `height` property to {{ demoHeight }}.
I was inspired to code a Disbility Pride flag background in CSS by other people's CSS code snippets of LGBTQ+ pride flag backgrounds, such as [Alvaro Montoro's LGBTQ+ Flags Coded in CSS demo](https://codepen.io/alvaromontoro/full/NWyBrZJ).
Since Ann Magill released the Disability Pride flag to the public domain under [CC0 1.0 Universal](https://creativecommons.org/publicdohome/zero/1.0/), I am doing the same to my responsive Disability Pride Flag CSS background. Therefore, you are free to use my code and do what you want with it without asking for permission or crediting me.
You can also check out [my CodePen demo](https://codepen.io/helenclx/pen/VwJjBmB) for my responsive Disability Pride flag CSS background and play with the code.
<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="VwJjBmB" data-pen-title="Disability Pride Flag CSS Background" data-editable="true" data-user="helenclx" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
<span>See the Pen <a href="https://codepen.io/helenclx/pen/VwJjBmB">
Disability Pride Flag CSS Background</a> by Helen Chong (<a href="https://codepen.io/helenclx">@helenclx</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
**Update, 15 December 2024:** Previously, I coded the Disability Pride flag background in CSS by using an inline SVG, but I eventually figured out how to create the stripes by using the CSS `linuear-gradient()` function, making my code snippet much cleaner.
---
title: Responsive Disability Pride Flag CSS Background
desc: I coded the Disability Pride Flag in CSS to celebrate Disability Pride Month.
date: 2024-07-23T20:35:00+0800
updated: 2024-12-15T18:43:47+0800
topics: ["css", "disability pride"]
hasCodeBlock: true
---
{{ set demoHeight = "23rem" }}
{{ set disabilityPrideCss }}.disability-pride-flag {
background:
linear-gradient(
37deg,
#595959 0 35%,
#CF7280 30% 41%,
#EEDE77 30% 47%,
#E8E8E8 30% 53%,
#7bc2e0 30% 59%,
#3BB07D 30% 65%,
#595959 0
);
}{{ /set }}
{{ css }}
{{ disabilityPrideCss }}
.disability-pride-flag.demo { height: {{ demoHeight }}; }
{{ /css }}
<div class="disability-pride-flag demo" role="img" aria-label="Demo of the Disability Pride flag coded in CSS"></div>
Happy [Disability Pride Month](https://en.wikipedia.org/wiki/Disability_Pride_Month)! To celebrate the occasion, I did a small project: a responsive Disability Pride flag CSS background! For those who do not know, this [Disability Pride flag](https://www.womansday.com/life/a43964487/disability-pride-flag/) was created by [Ann Magill](https://capri0mni.dreamwidth.org/837596.html) and released in 2021.
My Disability Pride flag CSS background is done with a combination of the CSS [`background` property](https://developer.mozilla.org/en-US/docs/Web/CSS/background) and [`linuear-gradient()` function](https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient), and designed to be responsive, so it can adapt to different screen sizes and devices.
Here is the CSS snippet for a responsive Disability Pride Flag background:
```css
{{ disabilityPrideCss }}
```
After copying the above snippet in a CSS stylesheet or the HTML `<style>` tags, to apply the background to an HTML element, simply add the CSS class name `disability-pride-flag` (you can rename the class name if you want) to the HTML element's `class` attribute.
Note that you want to apply the Pride flag to an empty element, you also need to set the element's [`height`](https://developer.mozilla.org/en-US/docs/Web/CSS/height) or [`aspect-ratio`](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio) property to make the Pride flag background visible. For example, the demo above sets the `height` property to {{ demoHeight }}.
I was inspired to code a Disbility Pride flag background in CSS by other people's CSS code snippets of LGBTQ+ pride flag backgrounds, such as [Alvaro Montoro's LGBTQ+ Flags Coded in CSS demo](https://codepen.io/alvaromontoro/full/NWyBrZJ).
Since Ann Magill released the Disability Pride flag to the public domain under [CC0 1.0 Universal](https://creativecommons.org/publicdohome/zero/1.0/), I am doing the same to my responsive Disability Pride Flag CSS background. Therefore, you are free to use my code and do what you want with it without asking for permission or crediting me.
You can also check out [my CodePen demo](https://codepen.io/helenclx/pen/VwJjBmB) for my responsive Disability Pride flag CSS background and play with the code.
<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="VwJjBmB" data-pen-title="Disability Pride Flag CSS Background" data-editable="true" data-user="helenclx" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
<span>See the Pen <a href="https://codepen.io/helenclx/pen/VwJjBmB">
Disability Pride Flag CSS Background</a> by Helen Chong (<a href="https://codepen.io/helenclx">@helenclx</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
**Update, 15 December 2024:** Previously, I coded the Disability Pride flag background in CSS by using an inline SVG, but I eventually figured out how to create the stripes by using the CSS `linuear-gradient()` function, making my code snippet much cleaner.

View File

@@ -1,80 +1,80 @@
---
title: Migrating My Website to Hostinger
desc: I have migrated my developer portfolio and blog website to Hostinger.
date: 2024-07-31T09:31:00+0800
topics: ["about this website", "hostinger"]
toc: true
---
Starting from 30 July 2024, helenchong.dev, my developer portfolio and blog website, has migrated from GitHub Pages to Hostinger as its web host.
This website was hosted on GitHub Pages since its creation, and has come a long way since its launch in February 2023, from [adding a blog](2023-08-21-Welcome-to-Helen-Codes.md), [being rebuilt with Eleventy](2024-04-11-rebuilding-my-developer-portfolio-with-eleventy.md) to [getting its own domain name](2024-05-29-custom-domain-name-helenchong-dev.md). This website started out as a standard developer portfolio, but as I grew as an aspiring web developer, so did my website.
Therefore, I decided that it was time to take my website to another level to allow both my website and myself as a developer more room to grow, starting with migrating from GitHub Pages.
## Why Moving Hosts
GitHub Pages is decent for hosting static sites. You can host a static site on GitHub Pages by directly deploying a Git repository. It certainly helps that you can do so for free, and if you already have a custom domain name, you can use it on your GitHub Pages website without additional costs. I still think GitHub Pages is a good platform if you are just starting out building your website or blog, especially if you are unsure if you are going to commit to it.
However, as I grew as a web developer and began to explore more advance topics about web development, I started to desire for more control over my own website. A notable disadvantage of GitHub Pages compared to web hosting services that support dynamic websites is the lack of `.htaccess` support. Learning about the things `.htaccess` can do has tempted me to consider migrating away form GitHub Pages.
Furthermore, in addition to this developer portfolio and blog website, I have also built various hobby websites, so it would be nice if I could consolidate the management of my websites into one platform.
Last but not least, I am invested in web development and building my own website enough to be willing to spend money on them, as evident in my purchase and registration of a custom domain name helenchong.dev for this website. That said, I do need to consider carefully about how much money I am going to spend, which factors in why I eventually chose Hostinger to host my websites.
Eventually, on 25 July 2024, I registered an account on [Hostinger](https://www.hostinger.my/), and purchased their 48-month Premium Shared Hosting plan, then I migrated my websites starting from my hobby websites on the same day. Later, on 30 July 2024, I completed the migration of helenchong.dev from GitHub Pages to Hostinger, and renamed the [website's GitHub repository](https://github.com/helenclx/helenchong.dev) from `helenclx.github.io` to helenchong.dev.
## Why Hostinger
### Affordable Price with Regional Pricing
The biggest draw about Hostinger is its pricing — it is not only cheap, but also offering regional pricing. This is a huge deal for people who live in countries with weaker currencies than US Dollars and Euro, as the pricing scales to the level we are more comfortable spending, without worrying about currency conversion rates.
For reference, as of this writing, 1 USD equals approximately 4.6 Malaysian Ringgit (MYR), while the minimum wage in Malaysia is 1,500 MYR, which approximately equals to 325.7 USD, per month. Therefore, if a Malaysian like me is going to spend money, every USD counts.
Compare the pricing of the 48-month Premium Shared Hosting plan of [Hostinger.my](https://www.hostinger.my/) to [Hostinger.com](https://www.hostinger.com/): At the time of my purchase, the plan cost 7.99 per month in MYR (approximately 1.71 USD), while 2.99 per month in USD (approximately 13.97 MYR). This means I ended up saving almost 6 MYR per month by purchasing the plan on the Malaysian branch of Hostinger. This is just comparing Hostinger's own hosting plan with different currencies, let alone comparing Hostinger's hosting plan to its competitors.
As a bonus, being able to purchase Hostinger's hosting plans in Malaysian Ringgit means I can pay by directly transferring money from a Malaysian bank account.
Being cheap is not even the only reason I chose Hostinger; what is better than being cheap is to provide many, many features at the same time.
### Many, Many Features at a Cheap Price
The cheap price of the Premium Shared Hosting Plan of Hostinger is jam-packed with many features, including but not limited to:
- Ability to create up to 100 websites (which is significantly more than enough for average users)
- 100 GB SSD storage
- Unlimited bandwidth
- Unlimited MySQL databases
- Email accounts included for free
- Built-in Git support, which allows me to continue deploying my website from its Git repository
- And more!
On the subject of email accounts, it is worth pointing out that Hostinger's Premium Shared Hosting plan allows you to create an email account for each website you made, and you can have up to 100 addresses and forwarders for each email account.
Hostinger's Git support allows you to even deploy multiple repositories to the same website, which was what I ended up doing to deploy [my résumé](/resume) into my website's directory, despite it having a [separate Git repository](https://github.com/helenclx/resume) from my website.
## Excellent Customer Support
I am pleased to report that my experience with Hostinger's support team has been excellent.
After migrating this website to Hostinger, I was trying to deploy my résumé to this website's directory. At first, I had an issue with my résumé not being deployed to this website as I intended, so I sought for help by contacting Hostinger's support team via live chat. The support team answered to my message quickly. The specialist who handled my case were not only kind and polite, but also willing to walk me through the process of figuring out possible causes of my issue.
Turned out, I accidentally deployed my résumé to a different website of mine, so I double-checked and reconfigured my set-up to deploy both my website and résumé from their GitHub repositories to the correct directories, and my problem was finally solved.
## Domain Name Remaining at Porkbun
While Hostinger provides domain registration and transfer service, I chose to have the helenchong.dev domain name stay with [Porkbun](https://porkbun.com/), because I already spent money on the domain name, so Hostinger's hosting plan providing domain name registration for free for the first year does not benefit me.
In addition, Porkbun's renewal rate for domain names is cheaper than Hostinger. For example, Porkbun renews the `.com` domain extension at 10.37 USD per year, while Hostinger renews at 15.99 USD per year. Even the Malaysian Ringgit pricing of Hostinger's domain renewal rate for `.com`, 55.99 MYR per year, is still more expansive than Porkbun's approximately 47.76 MYR per year rate.
## What will Happen to My GitHub Pages Subdomain
Now that my website has migrated to Hostinger, what happens to `helenclx.github.io`, my GitHub Pages subdomain now?
At the moment, I am still deploying projects to GitHub Pages, so the `helenclx.github.io` subdomain is still being used by my projects on GitHub, but if you just visit `helenclx.github.io` on its own, you will now get a 404 error page, indicating there is no longer a GitHub Pages site at this address.
## Wrapping Up
I am glad to have made the decision to move hosts for this website. By migrating my developer portfolio and blog website to Hostinger, I felt like opening up new opportunities for my website to grow, and for myself to keep learning as a web developer.
As of this writing, I had created a `.htaccess` for my website to set a custom 404 page, configure the cache policy of this website's static assets, and remove `.html` file extension from website addresses. I am looking forward to exploring this new web hosting experience more.
---
title: Migrating My Website to Hostinger
desc: I have migrated my developer portfolio and blog website to Hostinger.
date: 2024-07-31T09:31:00+0800
topics: ["about this website", "hostinger"]
toc: true
---
Starting from 30 July 2024, helenchong.dev, my developer portfolio and blog website, has migrated from GitHub Pages to Hostinger as its web host.
This website was hosted on GitHub Pages since its creation, and has come a long way since its launch in February 2023, from [adding a blog](2023-08-21-Welcome-to-Helen-Codes.md), [being rebuilt with Eleventy](2024-04-11-rebuilding-my-developer-portfolio-with-eleventy.md) to [getting its own domain name](2024-05-29-custom-domain-name-helenchong-dev.md). This website started out as a standard developer portfolio, but as I grew as an aspiring web developer, so did my website.
Therefore, I decided that it was time to take my website to another level to allow both my website and myself as a developer more room to grow, starting with migrating from GitHub Pages.
## Why Moving Hosts
GitHub Pages is decent for hosting static sites. You can host a static site on GitHub Pages by directly deploying a Git repository. It certainly helps that you can do so for free, and if you already have a custom domain name, you can use it on your GitHub Pages website without additional costs. I still think GitHub Pages is a good platform if you are just starting out building your website or blog, especially if you are unsure if you are going to commit to it.
However, as I grew as a web developer and began to explore more advance topics about web development, I started to desire for more control over my own website. A notable disadvantage of GitHub Pages compared to web hosting services that support dynamic websites is the lack of `.htaccess` support. Learning about the things `.htaccess` can do has tempted me to consider migrating away form GitHub Pages.
Furthermore, in addition to this developer portfolio and blog website, I have also built various hobby websites, so it would be nice if I could consolidate the management of my websites into one platform.
Last but not least, I am invested in web development and building my own website enough to be willing to spend money on them, as evident in my purchase and registration of a custom domain name helenchong.dev for this website. That said, I do need to consider carefully about how much money I am going to spend, which factors in why I eventually chose Hostinger to host my websites.
Eventually, on 25 July 2024, I registered an account on [Hostinger](https://www.hostinger.my/), and purchased their 48-month Premium Shared Hosting plan, then I migrated my websites starting from my hobby websites on the same day. Later, on 30 July 2024, I completed the migration of helenchong.dev from GitHub Pages to Hostinger, and renamed the [website's GitHub repository](https://github.com/helenclx/helenchong.dev) from `helenclx.github.io` to helenchong.dev.
## Why Hostinger
### Affordable Price with Regional Pricing
The biggest draw about Hostinger is its pricing — it is not only cheap, but also offering regional pricing. This is a huge deal for people who live in countries with weaker currencies than US Dollars and Euro, as the pricing scales to the level we are more comfortable spending, without worrying about currency conversion rates.
For reference, as of this writing, 1 USD equals approximately 4.6 Malaysian Ringgit (MYR), while the minimum wage in Malaysia is 1,500 MYR, which approximately equals to 325.7 USD, per month. Therefore, if a Malaysian like me is going to spend money, every USD counts.
Compare the pricing of the 48-month Premium Shared Hosting plan of [Hostinger.my](https://www.hostinger.my/) to [Hostinger.com](https://www.hostinger.com/): At the time of my purchase, the plan cost 7.99 per month in MYR (approximately 1.71 USD), while 2.99 per month in USD (approximately 13.97 MYR). This means I ended up saving almost 6 MYR per month by purchasing the plan on the Malaysian branch of Hostinger. This is just comparing Hostinger's own hosting plan with different currencies, let alone comparing Hostinger's hosting plan to its competitors.
As a bonus, being able to purchase Hostinger's hosting plans in Malaysian Ringgit means I can pay by directly transferring money from a Malaysian bank account.
Being cheap is not even the only reason I chose Hostinger; what is better than being cheap is to provide many, many features at the same time.
### Many, Many Features at a Cheap Price
The cheap price of the Premium Shared Hosting Plan of Hostinger is jam-packed with many features, including but not limited to:
- Ability to create up to 100 websites (which is significantly more than enough for average users)
- 100 GB SSD storage
- Unlimited bandwidth
- Unlimited MySQL databases
- Email accounts included for free
- Built-in Git support, which allows me to continue deploying my website from its Git repository
- And more!
On the subject of email accounts, it is worth pointing out that Hostinger's Premium Shared Hosting plan allows you to create an email account for each website you made, and you can have up to 100 addresses and forwarders for each email account.
Hostinger's Git support allows you to even deploy multiple repositories to the same website, which was what I ended up doing to deploy [my résumé](/resume) into my website's directory, despite it having a [separate Git repository](https://github.com/helenclx/resume) from my website.
## Excellent Customer Support
I am pleased to report that my experience with Hostinger's support team has been excellent.
After migrating this website to Hostinger, I was trying to deploy my résumé to this website's directory. At first, I had an issue with my résumé not being deployed to this website as I intended, so I sought for help by contacting Hostinger's support team via live chat. The support team answered to my message quickly. The specialist who handled my case were not only kind and polite, but also willing to walk me through the process of figuring out possible causes of my issue.
Turned out, I accidentally deployed my résumé to a different website of mine, so I double-checked and reconfigured my set-up to deploy both my website and résumé from their GitHub repositories to the correct directories, and my problem was finally solved.
## Domain Name Remaining at Porkbun
While Hostinger provides domain registration and transfer service, I chose to have the helenchong.dev domain name stay with [Porkbun](https://porkbun.com/), because I already spent money on the domain name, so Hostinger's hosting plan providing domain name registration for free for the first year does not benefit me.
In addition, Porkbun's renewal rate for domain names is cheaper than Hostinger. For example, Porkbun renews the `.com` domain extension at 10.37 USD per year, while Hostinger renews at 15.99 USD per year. Even the Malaysian Ringgit pricing of Hostinger's domain renewal rate for `.com`, 55.99 MYR per year, is still more expansive than Porkbun's approximately 47.76 MYR per year rate.
## What will Happen to My GitHub Pages Subdomain
Now that my website has migrated to Hostinger, what happens to `helenclx.github.io`, my GitHub Pages subdomain now?
At the moment, I am still deploying projects to GitHub Pages, so the `helenclx.github.io` subdomain is still being used by my projects on GitHub, but if you just visit `helenclx.github.io` on its own, you will now get a 404 error page, indicating there is no longer a GitHub Pages site at this address.
## Wrapping Up
I am glad to have made the decision to move hosts for this website. By migrating my developer portfolio and blog website to Hostinger, I felt like opening up new opportunities for my website to grow, and for myself to keep learning as a web developer.
As of this writing, I had created a `.htaccess` for my website to set a custom 404 page, configure the cache policy of this website's static assets, and remove `.html` file extension from website addresses. I am looking forward to exploring this new web hosting experience more.

View File

@@ -1,46 +1,46 @@
---
title: Got My First Developer Job
desc: At last, I have officially switched my career path to web development.
date: 2024-08-16T20:35:51+0800
updated: 2025-01-02T21:51:14+0800
topics: ["life updates"]
toc: true
---
After working as a graphic designer for 8 years, and later taking online courses to learn to code for nearly two years without a computer science degree, I finally got my first developer job!
Starting from 16 August 2024, I am working for a local traditional Chinese medicine (TCM) healthcare company, with my role involves web development, UX design and graphic design. My official position in the company is marketing assistant, but if you were to ask me what my job is, I would describe myself as a developer and designer instead.
## How I Got the Job
After taking online courses, including freeCodeCamp, Scirmba and CS50's Introduction to Computer Science, for more than a year, I began to hunt for web developer jobs in recent months. I tried to apply for many companies on LinkedIn, but none of my attempts succeed. There was a recruiter on LinkedIn inviting me to apply for a contract developer job, but since the office's location is far from where I live, and it is a hybrid position that requires reporting to the office occasionally, I was not qualified for the position.
In late July 2024, I received a WhatsApp message and was invited by the managing director of a local traditional Chinese medicine company to attend an interview with him. The company was looking for someone with web development expertise to work on and improve their website, which is built with WordPress. The managing director discovered my résumé on a Malaysian job hunting website. I registered on account on said job hunting website when I tried to apply for a web developer position for a Malaysian company that I discovered through LinkedIn.
During my interview, I explained to the managing director that I have been looking to switch career from graphic design to web development. Turned out, the company was also interested in hiring a web developer who knew design to improve the front end of their website, so my 8 years worth of graphic design experience was a bonus. A week after the interview, the managing director sent me a job offer, and I accepted and got hired, with my responsibilities include web development, UX design and graphic design.
## WordPress Developer by Day, Eleventy Developer by Night
Starting my professional web development journey with WordPress is going to be a new experience for me. I had brief experience with working on WordPress websites as a website administrator in one of my previous jobs, but never as a developer. Furthermore, prior to receiving this job offer, I never studied WordPress development, nor built my own website with WordPress.
That said, I hope to apply the programming knowledge and skills I learned from the coding courses I had taken to adapt to learning WordPress development. My confidence in my ability to learn new tech skills was bolstered by [the time I managed to solve a problem with a hobby PHP project](2024-07-08-dipping-my-toes-in-php-for-my-hobby-project.md) despite my inexperience with PHP. I am looking forward to learning more about PHP with my new job as a WordPress developer as well.
Although I work on WordPress in my day job, my own website will continue to be built with Eleventy, and I will continue to follow and support Eleventy's development. One of the reasons I wanted to pursue web development as a profession is the freedom to learn and use different web development tools outside day job while continuing to grow as a developer.
None of the coding courses I took taught about Eleventy or any static site generator, but building my website with Eleventy allowed me to learn more about web development, not to mention I do have a lot of fun with using Eleventy. Therefore, I am content to be a WordPress developer by day, Eleventy developer by night.
## Wrapping Up
I am happy to be able to switch my career path to web development despite not having a computer science degree, and that my prior working experience as a graphic designer helped with getting this new job opportunity.
Since I began to seriously consider pursuing web development as a profession, I have been introducing myself as a graphic designer turned aspiring web developer, on this website and my other online presence, because I hoped to transfer my creativity and design expertise into building the web. I am glad that my such aspirations have come true.
---
## Major Update, 2 January 2025: Leaving the Job
Unfortunately, my first web development ended up lasting only four months, as it turned out, despite stating looking for someone with web development skills as the reason for inviting me for an interview and later hiring me, the company website was considered merely one part of its branding, and what my employer really wanted is a digital marketer who could wear many hats, which I should have seen coming in hindsight because my official title in the company was actually Marketing Assistant, and the company does not have a tech or IT department.
I am a web developer, not a marketer, so I decided to leave the company to continue to focus on web development, thus 3 January 2025 will be the final day of my employment in the company.
On the plus side, at least I actually got some professional web development experience, even if it lasted only a few months, since I had successfully developed a WordPress plugin for the first time ever during my first web developer job.
---
title: Got My First Developer Job
desc: At last, I have officially switched my career path to web development.
date: 2024-08-16T20:35:51+0800
updated: 2025-01-02T21:51:14+0800
topics: ["life updates"]
toc: true
---
After working as a graphic designer for 8 years, and later taking online courses to learn to code for nearly two years without a computer science degree, I finally got my first developer job!
Starting from 16 August 2024, I am working for a local traditional Chinese medicine (TCM) healthcare company, with my role involves web development, UX design and graphic design. My official position in the company is marketing assistant, but if you were to ask me what my job is, I would describe myself as a developer and designer instead.
## How I Got the Job
After taking online courses, including freeCodeCamp, Scirmba and CS50's Introduction to Computer Science, for more than a year, I began to hunt for web developer jobs in recent months. I tried to apply for many companies on LinkedIn, but none of my attempts succeed. There was a recruiter on LinkedIn inviting me to apply for a contract developer job, but since the office's location is far from where I live, and it is a hybrid position that requires reporting to the office occasionally, I was not qualified for the position.
In late July 2024, I received a WhatsApp message and was invited by the managing director of a local traditional Chinese medicine company to attend an interview with him. The company was looking for someone with web development expertise to work on and improve their website, which is built with WordPress. The managing director discovered my résumé on a Malaysian job hunting website. I registered on account on said job hunting website when I tried to apply for a web developer position for a Malaysian company that I discovered through LinkedIn.
During my interview, I explained to the managing director that I have been looking to switch career from graphic design to web development. Turned out, the company was also interested in hiring a web developer who knew design to improve the front end of their website, so my 8 years worth of graphic design experience was a bonus. A week after the interview, the managing director sent me a job offer, and I accepted and got hired, with my responsibilities include web development, UX design and graphic design.
## WordPress Developer by Day, Eleventy Developer by Night
Starting my professional web development journey with WordPress is going to be a new experience for me. I had brief experience with working on WordPress websites as a website administrator in one of my previous jobs, but never as a developer. Furthermore, prior to receiving this job offer, I never studied WordPress development, nor built my own website with WordPress.
That said, I hope to apply the programming knowledge and skills I learned from the coding courses I had taken to adapt to learning WordPress development. My confidence in my ability to learn new tech skills was bolstered by [the time I managed to solve a problem with a hobby PHP project](2024-07-08-dipping-my-toes-in-php-for-my-hobby-project.md) despite my inexperience with PHP. I am looking forward to learning more about PHP with my new job as a WordPress developer as well.
Although I work on WordPress in my day job, my own website will continue to be built with Eleventy, and I will continue to follow and support Eleventy's development. One of the reasons I wanted to pursue web development as a profession is the freedom to learn and use different web development tools outside day job while continuing to grow as a developer.
None of the coding courses I took taught about Eleventy or any static site generator, but building my website with Eleventy allowed me to learn more about web development, not to mention I do have a lot of fun with using Eleventy. Therefore, I am content to be a WordPress developer by day, Eleventy developer by night.
## Wrapping Up
I am happy to be able to switch my career path to web development despite not having a computer science degree, and that my prior working experience as a graphic designer helped with getting this new job opportunity.
Since I began to seriously consider pursuing web development as a profession, I have been introducing myself as a graphic designer turned aspiring web developer, on this website and my other online presence, because I hoped to transfer my creativity and design expertise into building the web. I am glad that my such aspirations have come true.
---
## Major Update, 2 January 2025: Leaving the Job
Unfortunately, my first web development ended up lasting only four months, as it turned out, despite stating looking for someone with web development skills as the reason for inviting me for an interview and later hiring me, the company website was considered merely one part of its branding, and what my employer really wanted is a digital marketer who could wear many hats, which I should have seen coming in hindsight because my official title in the company was actually Marketing Assistant, and the company does not have a tech or IT department.
I am a web developer, not a marketer, so I decided to leave the company to continue to focus on web development, thus 3 January 2025 will be the final day of my employment in the company.
On the plus side, at least I actually got some professional web development experience, even if it lasted only a few months, since I had successfully developed a WordPress plugin for the first time ever during my first web developer job.

View File

@@ -1,16 +1,16 @@
---
title: "Scheduled: Speaking at THE Eleventy Meetup Episode 19"
desc: My first opportunity to speak at a tech event will be in Episode 19 of THE Eleventy Meetup.
date: 2024-09-10T23:35:17+0800
topics: ["eleventy", "speaking"]
---
I am pleased to announce that I have been invited to speak at Episode 19 of [THE Eleventy Meetup](https://11tymeetup.dev/)!
This will also be my first time speaking at a tech event, another major milestone since I [got my first developer job in August 2024](2024-08-16-got-my-first-developer-job.md).
Episode 19 of THE Eleventy Meetup is scheduled to be held on 26 September 2024, 7pm Malaysia Time. My talk, titled "Eleventy Journey From 2.0 to 3.0", will be about my experience with using Eleventy, including the decision and process of upgrading to Eleventy 3.0.
I have been using Eleventy since [April 2024](2024-04-11-rebuilding-my-developer-portfolio-with-eleventy.md), and loving it and its wonderful community, so I am immensely grateful that my first time speaking at a tech event will be dedicated to Eleventy.
You can find more details about [THE Eleventy Meetup Ep. 19: Migrating to 3.0 and Blogging with Storyblok](https://11tymeetup.dev/events/ep-19-migrating-to-3-0-and-blogging-with-storyblok/) on THE Eleventy Meetup website. See you there!
---
title: "Scheduled: Speaking at THE Eleventy Meetup Episode 19"
desc: My first opportunity to speak at a tech event will be in Episode 19 of THE Eleventy Meetup.
date: 2024-09-10T23:35:17+0800
topics: ["eleventy", "speaking"]
---
I am pleased to announce that I have been invited to speak at Episode 19 of [THE Eleventy Meetup](https://11tymeetup.dev/)!
This will also be my first time speaking at a tech event, another major milestone since I [got my first developer job in August 2024](2024-08-16-got-my-first-developer-job.md).
Episode 19 of THE Eleventy Meetup is scheduled to be held on 26 September 2024, 7pm Malaysia Time. My talk, titled "Eleventy Journey From 2.0 to 3.0", will be about my experience with using Eleventy, including the decision and process of upgrading to Eleventy 3.0.
I have been using Eleventy since [April 2024](2024-04-11-rebuilding-my-developer-portfolio-with-eleventy.md), and loving it and its wonderful community, so I am immensely grateful that my first time speaking at a tech event will be dedicated to Eleventy.
You can find more details about [THE Eleventy Meetup Ep. 19: Migrating to 3.0 and Blogging with Storyblok](https://11tymeetup.dev/events/ep-19-migrating-to-3-0-and-blogging-with-storyblok/) on THE Eleventy Meetup website. See you there!

View File

@@ -1,20 +1,20 @@
---
title: "THE Eleventy Meetup Episode 19: My First Talk at a Tech Event"
desc: I gave my first talk at a tech event at Episode 19 of THE Eleventy Meetup
date: 2024-09-27T11:47:53+0800
topics: ["eleventy", "speaking"]
---
On 26 September 2024, I gave my first talk at a tech event at Episode 19 of THE Eleventy Meetup!
After I published a blog post, ["Aboard the Eleventy 3.0 Train"](2024-07-19-eleventy-3-0-upgrade.md), about my process of upgrading Eleventy 2.0 to Eleventy 3.0 for this website in July 2024, I received an invitation that asked me if I wanted to speak at the next [Eleventy Meetup](https://11tymeetup.dev/) about migrating to Eleventy 3.0. This was a great surprise, since I was not even a professional developer yet at the time, and thus had not been a speaker at any tech event before.
That said, as I was already aspiring to become a web developer back then, I figured that speaking at a tech event would be a great opportunity in my developer journey, especially since it would be about Eleventy, which had become my favourite tool for building websites since I discovered it and [rebuilt my website with it in April 2024](2024-04-11-rebuilding-my-developer-portfolio-with-eleventy.md), so I gladly accepted the invitation to be a speaker at THE Eleventy Meetup. I did end up getting my first developer job in August as well.
Eventually, on September 10, [THE Eleventy Meetup Episode 19](https://11tymeetup.dev/events/ep-19-migrating-to-3-0-and-blogging-with-storyblok/) was announced to take place on September 26, 6:00 AM CDT. It was decided that this time, the meetup would be switching time zones for a time slot that is more Asia-Pacific-, Europe-, and Africa-friendly. Here in Malaysia, the meetup would be at 7 pm.
Thank you [Sia Karamalegos](https://sia.codes/) for organising Episode 19 of THE Eleventy Meetup. Thank you [Estela Franco](https://estelafranco.com/) for your talk about and presentation of [using Storyblok to build an Eleventy blog site](https://www.youtube.com/watch?v=G3Pz1AH6SCY). Thank you to everyone who attended the meetup. Thank you everyone who has left supportive and encouraging messages for my talk in the chat.
You can watch my talk, "Eleventy Journey From 2.0 to 3.0", at THE Eleventy Meetup Episode 19 at THE Eleventy Meetup's YouTube channel:
https://www.youtube.com/watch?v=qgFNl_oAyQY
---
title: "THE Eleventy Meetup Episode 19: My First Talk at a Tech Event"
desc: I gave my first talk at a tech event at Episode 19 of THE Eleventy Meetup
date: 2024-09-27T11:47:53+0800
topics: ["eleventy", "speaking"]
---
On 26 September 2024, I gave my first talk at a tech event at Episode 19 of THE Eleventy Meetup!
After I published a blog post, ["Aboard the Eleventy 3.0 Train"](2024-07-19-eleventy-3-0-upgrade.md), about my process of upgrading Eleventy 2.0 to Eleventy 3.0 for this website in July 2024, I received an invitation that asked me if I wanted to speak at the next [Eleventy Meetup](https://11tymeetup.dev/) about migrating to Eleventy 3.0. This was a great surprise, since I was not even a professional developer yet at the time, and thus had not been a speaker at any tech event before.
That said, as I was already aspiring to become a web developer back then, I figured that speaking at a tech event would be a great opportunity in my developer journey, especially since it would be about Eleventy, which had become my favourite tool for building websites since I discovered it and [rebuilt my website with it in April 2024](2024-04-11-rebuilding-my-developer-portfolio-with-eleventy.md), so I gladly accepted the invitation to be a speaker at THE Eleventy Meetup. I did end up getting my first developer job in August as well.
Eventually, on September 10, [THE Eleventy Meetup Episode 19](https://11tymeetup.dev/events/ep-19-migrating-to-3-0-and-blogging-with-storyblok/) was announced to take place on September 26, 6:00 AM CDT. It was decided that this time, the meetup would be switching time zones for a time slot that is more Asia-Pacific-, Europe-, and Africa-friendly. Here in Malaysia, the meetup would be at 7 pm.
Thank you [Sia Karamalegos](https://sia.codes/) for organising Episode 19 of THE Eleventy Meetup. Thank you [Estela Franco](https://estelafranco.com/) for your talk about and presentation of [using Storyblok to build an Eleventy blog site](https://www.youtube.com/watch?v=G3Pz1AH6SCY). Thank you to everyone who attended the meetup. Thank you everyone who has left supportive and encouraging messages for my talk in the chat.
You can watch my talk, "Eleventy Journey From 2.0 to 3.0", at THE Eleventy Meetup Episode 19 at THE Eleventy Meetup's YouTube channel:
https://www.youtube.com/watch?v=qgFNl_oAyQY

View File

@@ -1,47 +1,47 @@
---
title: Happy One-Month Anniversary of My omg.lol Membership
desc: It has been a month since I joined omg.lol, and I am glad to be part of it.
date: 2024-10-19T00:14:19+0800
updated: 2024-11-04T23:57:10+0800
topics: ["omg.lol"]
---
19 October 2024 marks my one-month anniversary of joining [omg.lol](https://home.omg.lol/).
I discovered the existence of omg.lol for the first time when I became more active in exploring personal websites during the first half of 2024, after I learned about the resurgence of personal websites in 2022. I was quickly intrigued by omg.lol for its fun name (OMG LOL!), cute logo (named [Prami](https://prami.omg.lol/)), and the offer of various web services including a web address, a profile page, status update log, a Mastodon account, PURLs, pastebin, community chat, and more, for an affordable price of US $20 per year.
Furthermore, since I already started using the static site generator [Eleventy](https://www.11ty.dev/) to build my own websites at the time, another fact about omg.lol that caught my attention was the fact that a few Eleventy users are also omg.lol members, including [Robb Knight](https://rknight.me/), who is also part of [omg.lol's staff](https://home.omg.lol/staff).
At first, however, I did not feel that I had a use of an omg.lol membership yet, since I already had my own websites and at least one Mastodon account. Therefore, I did not immediately join omg.lol, though I still checked out the platform from time to time, because I did find what omg.lol does interesting, by offering people to claim their own space on the internet. Learning that omg.lol also offered [membership sponsorships](https://home.omg.lol/info/sponsorships) for members of marginalised groups made me respect omg.lol even more.
Then, in September 2024, I learned that omg.lol was hosting a [fundraising campaign for the St. Jude Childrens Research Hospital](https://omglol.news/2024/08/28/supporting-st-jude-with-a-month-of-awesomeness), which includes discounts for omg.lol membership fee and daily auction for lifetime omg.lol address. This time, I began to seriously consider joining omg.lol for real, as this was an opportunity to not only obtain an omg.lol membership, but also support a good cause for children. At last, I joined omg.lol after successfully bidding a lifetime address in the auction and registering my `helenchong.omg.lol` address!
After setting up my [omg.lol profile page](https://helenchong.omg.lol/), I began to connect with the omg.lol community by joining the community chat, Discourse forum and the [social.lol](https://social.lol/) Mastodon instance, and I have been enjoying my time in omg.lol's wonderful community since. [Adam Newbold](https://adam.omg.lol/), the founder of omg.lol himself, is also a really nice guy who is pleasant to talk to.
To make more good use out of my omg.lol address, and the fact that you can configure the DNS of your omg.lol address, I have also created a [blog.helenchong.omg.lol](https://blog.helenchong.omg.lol/) subdomain to use it as the address of my new personal blog, Galaxy Garden. It is worth noting that I was inspired to have a personal blog again by other bloggers in the omg.lol community.
I am incredibly grateful that services like omg.lol exist to bring back the fun of carving your own corner on the web, and I am proud to be a member of omg.lol and its delightful community. I am following omg.lol's development and looking forward to what it has in store for the future.
---
**UPDATE, 4 November 2023:**
After waiting for the items to ship from the U.S. to Malaysia, my lifetime omg.lol membership goodies have arrived at my home at last!
![Photo of omg.lol lifetime membership goodies, in which the double-sided Prami coaster shows regular Prami](https://cdn.some.pics/helenchong/6728b7f709104.jpg)
![Photo of omg.lol lifetime membership goodies, in which the double-sided Prami coaster shows distressed Prami](https://cdn.some.pics/helenchong/6728b8092e3ad.jpg)
These goodies include a double-sided Prami coaster printed with regular and distressed Prami, an enamel Prami pin, two 3D printed Prami tokens made of polylactic acid (PLA) filament, and 10 pieces of stickers featuring hearts Prami, sick Prami, distressed Prami, annoyed Prami, and regular Prami.
![Photo of a piece of light yellow paper containing a message from the Adam Newbold, the founder of omg.lol](https://cdn.some.pics/helenchong/6728b81b68b5a.jpg)
The package of these goodies also included a message from the founder Adam Newbold:
> Thank you so much for being an amazing lifetime member of omg.lol!
>
> Enjoy these goodies, and let me know if there's ever anything I can do for you.
>
> — Adam
Thank you, Adam, for these adorable Prami goodies, and again for all your work on omg.lol and more!
---
title: Happy One-Month Anniversary of My omg.lol Membership
desc: It has been a month since I joined omg.lol, and I am glad to be part of it.
date: 2024-10-19T00:14:19+0800
updated: 2024-11-04T23:57:10+0800
topics: ["omg.lol"]
---
19 October 2024 marks my one-month anniversary of joining [omg.lol](https://home.omg.lol/).
I discovered the existence of omg.lol for the first time when I became more active in exploring personal websites during the first half of 2024, after I learned about the resurgence of personal websites in 2022. I was quickly intrigued by omg.lol for its fun name (OMG LOL!), cute logo (named [Prami](https://prami.omg.lol/)), and the offer of various web services including a web address, a profile page, status update log, a Mastodon account, PURLs, pastebin, community chat, and more, for an affordable price of US $20 per year.
Furthermore, since I already started using the static site generator [Eleventy](https://www.11ty.dev/) to build my own websites at the time, another fact about omg.lol that caught my attention was the fact that a few Eleventy users are also omg.lol members, including [Robb Knight](https://rknight.me/), who is also part of [omg.lol's staff](https://home.omg.lol/staff).
At first, however, I did not feel that I had a use of an omg.lol membership yet, since I already had my own websites and at least one Mastodon account. Therefore, I did not immediately join omg.lol, though I still checked out the platform from time to time, because I did find what omg.lol does interesting, by offering people to claim their own space on the internet. Learning that omg.lol also offered [membership sponsorships](https://home.omg.lol/info/sponsorships) for members of marginalised groups made me respect omg.lol even more.
Then, in September 2024, I learned that omg.lol was hosting a [fundraising campaign for the St. Jude Childrens Research Hospital](https://omglol.news/2024/08/28/supporting-st-jude-with-a-month-of-awesomeness), which includes discounts for omg.lol membership fee and daily auction for lifetime omg.lol address. This time, I began to seriously consider joining omg.lol for real, as this was an opportunity to not only obtain an omg.lol membership, but also support a good cause for children. At last, I joined omg.lol after successfully bidding a lifetime address in the auction and registering my `helenchong.omg.lol` address!
After setting up my [omg.lol profile page](https://helenchong.omg.lol/), I began to connect with the omg.lol community by joining the community chat, Discourse forum and the [social.lol](https://social.lol/) Mastodon instance, and I have been enjoying my time in omg.lol's wonderful community since. [Adam Newbold](https://adam.omg.lol/), the founder of omg.lol himself, is also a really nice guy who is pleasant to talk to.
To make more good use out of my omg.lol address, and the fact that you can configure the DNS of your omg.lol address, I have also created a [blog.helenchong.omg.lol](https://blog.helenchong.omg.lol/) subdomain to use it as the address of my new personal blog, Galaxy Garden. It is worth noting that I was inspired to have a personal blog again by other bloggers in the omg.lol community.
I am incredibly grateful that services like omg.lol exist to bring back the fun of carving your own corner on the web, and I am proud to be a member of omg.lol and its delightful community. I am following omg.lol's development and looking forward to what it has in store for the future.
---
**UPDATE, 4 November 2023:**
After waiting for the items to ship from the U.S. to Malaysia, my lifetime omg.lol membership goodies have arrived at my home at last!
![Photo of omg.lol lifetime membership goodies, in which the double-sided Prami coaster shows regular Prami](https://cdn.some.pics/helenchong/6728b7f709104.jpg)
![Photo of omg.lol lifetime membership goodies, in which the double-sided Prami coaster shows distressed Prami](https://cdn.some.pics/helenchong/6728b8092e3ad.jpg)
These goodies include a double-sided Prami coaster printed with regular and distressed Prami, an enamel Prami pin, two 3D printed Prami tokens made of polylactic acid (PLA) filament, and 10 pieces of stickers featuring hearts Prami, sick Prami, distressed Prami, annoyed Prami, and regular Prami.
![Photo of a piece of light yellow paper containing a message from the Adam Newbold, the founder of omg.lol](https://cdn.some.pics/helenchong/6728b81b68b5a.jpg)
The package of these goodies also included a message from the founder Adam Newbold:
> Thank you so much for being an amazing lifetime member of omg.lol!
>
> Enjoy these goodies, and let me know if there's ever anything I can do for you.
>
> — Adam
Thank you, Adam, for these adorable Prami goodies, and again for all your work on omg.lol and more!

View File

@@ -1,34 +1,34 @@
---
title: My Web Development and Web Design Origin Story
desc: sailorhg's comic "Home Sweet Homepage" inspired me to share my web development and web design origin story.
date: 2024-11-05T18:05:46+0800
topics: ["web development"]
---
Recently, I discovered [sailorhg](https://sailorhg.com/)'s comic, ["Home Sweet Homepage"](https://sailorhg.com/home_sweet_homepage/) (which also has a [text transcript](https://sailorhg.com/home_sweet_homepage/transcript.html) available), telling the author's story about growing up online in the era of GeoCities. While I never got to code my own website on GeoCities, this comic reminded me of why I love web development and web design and thus eventually decided to changed my career path to web development.
This line in the comic in particular summed it up:
> Working on my website combines my love for words, art, and technology, and it's a form of self-expression for me. Computers are logical and code is logical but you can use them to express your emotions, make art, and communicate to other people.
Therefore, sailorhg's comic has inspired me to share my web development and web design origin story on this blog.
I had a GeoCities account, but by the time I started to participate on the web in the 2000s when I was around 14, blogs were on the rise, so blogging was my first foray into creating on the internet, so I did not experience creating and coding a personal website from scratch like other GeoCities users did. That said, I got my start on HTML and CSS through blogging, by editing the codes of blog themes and widgets, while being inspired by many other blogs.
Almost as soon as I began to learn HTML and CSS during my teenage blogging era in the 2000s, I was enamoured by the two languages and web design. Similar to sailorhg, I found web design and development a fascinating combination of words, art and technology.
I never consider art and science at odds with each other, because since childhood I was interested in both: drawing has been a life-long hobby of mine, but I had a childhood phase where science was my main special interest; then while I chose to study science stream subjects in secondary school, at the same time I aspired to become a graphic designer, and thus pursuing a diploma in graphic design after graduating from secondary school.
In fact, I was inspired to pursue graphic design as a profession by a Taiwanese blogger I followed during my 2000s teenage blogging era. Said Taiwanese blogger was a professional designer whose blog included a gorgeous header made with Flash. Resources for learning to code to become a professional web developer were not as accessible and easy to find as they are today, and I did not know anyone working in tech to know how to break into web design or web development field either, so I mistakenly assumed that pursuing graphic design would allow me to get involved in web design, but that ended up not being the case. Years in the graphic design field, I got burned out; there were multiple factors contributing to my burnout from the graphic design field, but the short version is that the reality of graphic design work, at least in my experience, did not turn out as great as what my younger self had expected and hoped for.
Fast-forward to 2022, my interest in web design was rekindled when I discovered Neocities and the resurgence of creating personal websites, so I decided to build and code my own website from scratch for the first time. For this, I re-learned HTML and CSS, and began to truly experience the joy of carving one's own space on the web, and the more I worked on my website, the more I learned about not only web design, but also web development, by dabbling in JavaScript.
Over time, my interest in web development evolved enough to take online coding courses, starting with [freeCodeCamp](https://www.freecodecamp.org/), then [Scrimba](https://scrimba.com/) shortly after. Taking these courses made me realise that these languages have evolved a lot since more than a decade ago. I practised the HTML, CSS and JavaScript skills and knowledge I learned from these courses by building projects, including the [Frontend Mentor](https://www.frontendmentor.io/) challenges and my hobby websites.
Meanwhile, learning that there were people who became developers without a computer science degree inspired me to consider switching my career path from graphic design to web development. In other words, web design and development had evolved to more than a hobby to me.
I did not expect career switch to be easy, so I did put as much as I could in learning and practising web development. The process made me realise that my true passion lies in web development and web design, not graphic design. Although the design skills I learned from my experience in graphic design were helpful in improving my front-end web development skills, I would be lying if I said I do not have any regret for my past decision to pursue graphic design as a profession.
After teaching myself to code for almost two years, and received multiple code and tech-related certifications including [CS50's Introduction to Computer Science](2024-05-27-cs50x-course-completed.md), I updated my résumé with information about my interest in switching career from graphic design to web development, and hope for transferring my creativity and design expertise into building the web. I started to try hunting for front end web developer jobs, until I [finally got my first developer job in August 2024](2024-08-16-got-my-first-developer-job.md).
Looking back, even I am still amazed by the fact that I managed to transition from a hobbyist to a professional web developer, especially since I did not come from a tech educational or industrial background, but I am glad that it happened.
---
title: My Web Development and Web Design Origin Story
desc: sailorhg's comic "Home Sweet Homepage" inspired me to share my web development and web design origin story.
date: 2024-11-05T18:05:46+0800
topics: ["web development"]
---
Recently, I discovered [sailorhg](https://sailorhg.com/)'s comic, ["Home Sweet Homepage"](https://sailorhg.com/home_sweet_homepage/) (which also has a [text transcript](https://sailorhg.com/home_sweet_homepage/transcript.html) available), telling the author's story about growing up online in the era of GeoCities. While I never got to code my own website on GeoCities, this comic reminded me of why I love web development and web design and thus eventually decided to changed my career path to web development.
This line in the comic in particular summed it up:
> Working on my website combines my love for words, art, and technology, and it's a form of self-expression for me. Computers are logical and code is logical but you can use them to express your emotions, make art, and communicate to other people.
Therefore, sailorhg's comic has inspired me to share my web development and web design origin story on this blog.
I had a GeoCities account, but by the time I started to participate on the web in the 2000s when I was around 14, blogs were on the rise, so blogging was my first foray into creating on the internet, so I did not experience creating and coding a personal website from scratch like other GeoCities users did. That said, I got my start on HTML and CSS through blogging, by editing the codes of blog themes and widgets, while being inspired by many other blogs.
Almost as soon as I began to learn HTML and CSS during my teenage blogging era in the 2000s, I was enamoured by the two languages and web design. Similar to sailorhg, I found web design and development a fascinating combination of words, art and technology.
I never consider art and science at odds with each other, because since childhood I was interested in both: drawing has been a life-long hobby of mine, but I had a childhood phase where science was my main special interest; then while I chose to study science stream subjects in secondary school, at the same time I aspired to become a graphic designer, and thus pursuing a diploma in graphic design after graduating from secondary school.
In fact, I was inspired to pursue graphic design as a profession by a Taiwanese blogger I followed during my 2000s teenage blogging era. Said Taiwanese blogger was a professional designer whose blog included a gorgeous header made with Flash. Resources for learning to code to become a professional web developer were not as accessible and easy to find as they are today, and I did not know anyone working in tech to know how to break into web design or web development field either, so I mistakenly assumed that pursuing graphic design would allow me to get involved in web design, but that ended up not being the case. Years in the graphic design field, I got burned out; there were multiple factors contributing to my burnout from the graphic design field, but the short version is that the reality of graphic design work, at least in my experience, did not turn out as great as what my younger self had expected and hoped for.
Fast-forward to 2022, my interest in web design was rekindled when I discovered Neocities and the resurgence of creating personal websites, so I decided to build and code my own website from scratch for the first time. For this, I re-learned HTML and CSS, and began to truly experience the joy of carving one's own space on the web, and the more I worked on my website, the more I learned about not only web design, but also web development, by dabbling in JavaScript.
Over time, my interest in web development evolved enough to take online coding courses, starting with [freeCodeCamp](https://www.freecodecamp.org/), then [Scrimba](https://scrimba.com/) shortly after. Taking these courses made me realise that these languages have evolved a lot since more than a decade ago. I practised the HTML, CSS and JavaScript skills and knowledge I learned from these courses by building projects, including the [Frontend Mentor](https://www.frontendmentor.io/) challenges and my hobby websites.
Meanwhile, learning that there were people who became developers without a computer science degree inspired me to consider switching my career path from graphic design to web development. In other words, web design and development had evolved to more than a hobby to me.
I did not expect career switch to be easy, so I did put as much as I could in learning and practising web development. The process made me realise that my true passion lies in web development and web design, not graphic design. Although the design skills I learned from my experience in graphic design were helpful in improving my front-end web development skills, I would be lying if I said I do not have any regret for my past decision to pursue graphic design as a profession.
After teaching myself to code for almost two years, and received multiple code and tech-related certifications including [CS50's Introduction to Computer Science](2024-05-27-cs50x-course-completed.md), I updated my résumé with information about my interest in switching career from graphic design to web development, and hope for transferring my creativity and design expertise into building the web. I started to try hunting for front end web developer jobs, until I [finally got my first developer job in August 2024](2024-08-16-got-my-first-developer-job.md).
Looking back, even I am still amazed by the fact that I managed to transition from a hobbyist to a professional web developer, especially since I did not come from a tech educational or industrial background, but I am glad that it happened.

View File

@@ -1,60 +1,60 @@
---
title: My 2024 Year in Web Development
desc: Reflecting on my 2024 as a web developer.
date: 2024-12-28T19:47:18+0800
topics: ["year in review", "web development"]
toc: true
---
2024 was an eventful year for my developer journey, so as 2024 is getting closer to the end, it is time to reflect on my 2024 as a web developer.
## Discovering Eleventy
The main event that changed my web developer life for the better in 2024 was my discovery of the static site generator [Eleventy](https://www.11ty.dev/) in April.
Since I built this website in December 2022, this site has grown from the original simple purpose of a developer portfolio, to including a blog that serves as my online journal as a developer. As a consequence, I started to look for ways to build and maintain my websites in a more elegant manner, ideally to eliminate the need to duplicate the same HTML code of my website's header and footer over and over on every page.
As learning more about static site generators, I eventually discovered Eleventy through Neocities, as some websites hosted on Neocities were built with it. After digging into and trying out Eleventy, I quickly realised that it was the solution I had been looking for, so I decided to [rebuild my developer portfolio and blog with Eleventy](2024-04-11-rebuilding-my-developer-portfolio-with-eleventy.md), and launched my rebuilt developer website on 9 April 2024, and I could not be happier with how my website and my new development workflow turned out.
In addition to becoming my favourite tool to build websites, Eleventy has a wonderful community of web developers who share my interest in making the web better. Therefore, by talking about Eleventy on both this blog and Mastodon, I got to connect with other like-minded developers. Connecting with the Eleventy community provided me my first opportunity to speak at a tech event, specifically [Episode 19 of THE Eleventy Meetup](2024-09-27-eleventy-meetup-19-first-talk.md).
Thank you, Eleventy, for not only making me enjoy building websites, including my own, even more, but also for giving me the opportunity to connect with other like-minded web developers.
## Custom Domain Name and New Host for My Website
Rebuilding my entire website with a static site generator was only the first step of taking my site to the next level.
As both my website and my web development skills grew, I began to consider renting a custom domain name for my website to give my site a unique identity on the web.
Near the end of May, I rented helenchong.dev from Porkbun, making it my first time spending money on a domain name, and [set helenchong.dev as the domain name of my developer website](2024-05-29-custom-domain-name-helenchong-dev.md).
Initially, this website was hosted on GitHub pages, but as I learned more advanced concepts about web development and hosting, I started to desire more control over how I run my website, and thus looked for alternative web hosting options. Meanwhile, I had started [learning PHP for my hobby projects](2024-07-08-dipping-my-toes-in-php-for-my-hobby-project.md), so I wanted web hosting that supported PHP too.
Eventually, after researching alternative web hosting options, I finally [migrated all my static and PHP websites to Hostinger's shared hosting plan](2024-07-31-migrating-to-hostinger.md) in July. I have used Hostinger since then, and I am satisfied with it.
## Completed a CS50 Course
On 23 May 2024, I [completed CS50s Introduction to Computer Science](2024-05-27-cs50x-course-completed.md), also known as CS50x, course. The course is not specific to web development, but it has taught me the knowledge I need to become a better developer and programmer.
While CS50x was not the first online course for coding I had taken, it might be the most challenging, especially with how the assignments test your problem-solving skills and not just your computer science knowledge and coding skills.
I am grateful of CS50 for teaching me the computer science knowledge and theories that allow me to adapt to different programming languages, such as when [I learned PHP for the first time for a hobby project](2024-07-08-dipping-my-toes-in-php-for-my-hobby-project.md), and understand computers and programming better.
## My First Web Developer Job
My [interest in web design and development](2024-11-05-my-web-dev-design-origin-story.md) went as far back as my teenage blogging years in the 2000s, when I learned basic HTML and CSS through editing blog themes and widgets, but I did not know how to pursue web development as a profession at the time.
Fast-forward to 2022, when my interest in HTML and CSS was rekindled by coding my own website from scratch for the first time after discovering Neocities. This time, my interest in web development grew enough for me to take online courses, starting with freeCodeCamp, and decided to switch my career path from graphic design to web development after being inspired by the stories of other people, including career switchers, becoming a professional developer without a computer science degree.
After teaching myself web development for almost two years, my dream of [getting my first developer job came true in August](2024-08-16-got-my-first-developer-job.md). In my first web development job, I succeed in developing a custom WordPress plugin, something I never did before this job.
Unfortunately, my first web development ended up lasting only four months, as it turned out, despite stating looking for someone with web development skills as the reason for inviting me for an interview and later hiring me, the company website was considered merely one part of its branding, and what my employer really wanted is a digital marketer who could wear many hats, which I should have seen coming in hindsight because my official title in the company was actually Marketing Assistant, and the company does not have a tech or IT department.
I am a web developer, not a marketer, so I decided to leave the company to continue to focus on web development, thus 3 January 2025 will be the final day of my employment in the company.
## Wrapping Up
2024 has been a huge year for my journey as a web developer, as it was a year when I achieved multiple milestones in those regards, including rebuilding my entire website with a static site generator for the first time, completing a CS50 course, speaking at a tech event for the first time, and getting my first developer job.
For 2025, I intend to continue to keep learning to hone my web development knowledge and skills.
---
title: My 2024 Year in Web Development
desc: Reflecting on my 2024 as a web developer.
date: 2024-12-28T19:47:18+0800
topics: ["year in review", "web development"]
toc: true
---
2024 was an eventful year for my developer journey, so as 2024 is getting closer to the end, it is time to reflect on my 2024 as a web developer.
## Discovering Eleventy
The main event that changed my web developer life for the better in 2024 was my discovery of the static site generator [Eleventy](https://www.11ty.dev/) in April.
Since I built this website in December 2022, this site has grown from the original simple purpose of a developer portfolio, to including a blog that serves as my online journal as a developer. As a consequence, I started to look for ways to build and maintain my websites in a more elegant manner, ideally to eliminate the need to duplicate the same HTML code of my website's header and footer over and over on every page.
As learning more about static site generators, I eventually discovered Eleventy through Neocities, as some websites hosted on Neocities were built with it. After digging into and trying out Eleventy, I quickly realised that it was the solution I had been looking for, so I decided to [rebuild my developer portfolio and blog with Eleventy](2024-04-11-rebuilding-my-developer-portfolio-with-eleventy.md), and launched my rebuilt developer website on 9 April 2024, and I could not be happier with how my website and my new development workflow turned out.
In addition to becoming my favourite tool to build websites, Eleventy has a wonderful community of web developers who share my interest in making the web better. Therefore, by talking about Eleventy on both this blog and Mastodon, I got to connect with other like-minded developers. Connecting with the Eleventy community provided me my first opportunity to speak at a tech event, specifically [Episode 19 of THE Eleventy Meetup](2024-09-27-eleventy-meetup-19-first-talk.md).
Thank you, Eleventy, for not only making me enjoy building websites, including my own, even more, but also for giving me the opportunity to connect with other like-minded web developers.
## Custom Domain Name and New Host for My Website
Rebuilding my entire website with a static site generator was only the first step of taking my site to the next level.
As both my website and my web development skills grew, I began to consider renting a custom domain name for my website to give my site a unique identity on the web.
Near the end of May, I rented helenchong.dev from Porkbun, making it my first time spending money on a domain name, and [set helenchong.dev as the domain name of my developer website](2024-05-29-custom-domain-name-helenchong-dev.md).
Initially, this website was hosted on GitHub pages, but as I learned more advanced concepts about web development and hosting, I started to desire more control over how I run my website, and thus looked for alternative web hosting options. Meanwhile, I had started [learning PHP for my hobby projects](2024-07-08-dipping-my-toes-in-php-for-my-hobby-project.md), so I wanted web hosting that supported PHP too.
Eventually, after researching alternative web hosting options, I finally [migrated all my static and PHP websites to Hostinger's shared hosting plan](2024-07-31-migrating-to-hostinger.md) in July. I have used Hostinger since then, and I am satisfied with it.
## Completed a CS50 Course
On 23 May 2024, I [completed CS50s Introduction to Computer Science](2024-05-27-cs50x-course-completed.md), also known as CS50x, course. The course is not specific to web development, but it has taught me the knowledge I need to become a better developer and programmer.
While CS50x was not the first online course for coding I had taken, it might be the most challenging, especially with how the assignments test your problem-solving skills and not just your computer science knowledge and coding skills.
I am grateful of CS50 for teaching me the computer science knowledge and theories that allow me to adapt to different programming languages, such as when [I learned PHP for the first time for a hobby project](2024-07-08-dipping-my-toes-in-php-for-my-hobby-project.md), and understand computers and programming better.
## My First Web Developer Job
My [interest in web design and development](2024-11-05-my-web-dev-design-origin-story.md) went as far back as my teenage blogging years in the 2000s, when I learned basic HTML and CSS through editing blog themes and widgets, but I did not know how to pursue web development as a profession at the time.
Fast-forward to 2022, when my interest in HTML and CSS was rekindled by coding my own website from scratch for the first time after discovering Neocities. This time, my interest in web development grew enough for me to take online courses, starting with freeCodeCamp, and decided to switch my career path from graphic design to web development after being inspired by the stories of other people, including career switchers, becoming a professional developer without a computer science degree.
After teaching myself web development for almost two years, my dream of [getting my first developer job came true in August](2024-08-16-got-my-first-developer-job.md). In my first web development job, I succeed in developing a custom WordPress plugin, something I never did before this job.
Unfortunately, my first web development ended up lasting only four months, as it turned out, despite stating looking for someone with web development skills as the reason for inviting me for an interview and later hiring me, the company website was considered merely one part of its branding, and what my employer really wanted is a digital marketer who could wear many hats, which I should have seen coming in hindsight because my official title in the company was actually Marketing Assistant, and the company does not have a tech or IT department.
I am a web developer, not a marketer, so I decided to leave the company to continue to focus on web development, thus 3 January 2025 will be the final day of my employment in the company.
## Wrapping Up
2024 has been a huge year for my journey as a web developer, as it was a year when I achieved multiple milestones in those regards, including rebuilding my entire website with a static site generator for the first time, completing a CS50 course, speaking at a tech event for the first time, and getting my first developer job.
For 2025, I intend to continue to keep learning to hone my web development knowledge and skills.

View File

@@ -1,24 +1,24 @@
---
title: Developed My First WordPress Plugin
desc: I successfully developed a WordPress plugin during my first web developer job.
date: 2025-01-03T16:30:08+0800
topics: ["wordpress", "php"]
---
3 January 2025 was the final day of [my first web developer job](2024-08-16-got-my-first-developer-job.md).
The best thing that came out from my first web developer job was my accomplishment of learning and successfully developing a WordPress plugin for the first time. Specifically, my plugin is an add-on to [Long Watch Studio](https://plugins.longwatchstudio.com/)'s [WooRewards](https://plugins.longwatchstudio.com/product/woorewards/), which is a plugin for WooCommerce.
What my plugin does is to add one-time redeemable vouchers as a reward option in a WooCommerce website's points and rewards system powered by WooRewards for customers to unlock, with the vouchers being issued by using the [QR Planet API](https://qrplanet.com/qr-code-api). When a customer unlocks a one-time redeemable voucher reward, the plugin will automatically send the customer an email containing a link to redeem the voucher with instructions. Like with other types of rewards in WooRewards, the WordPress or WooCommerce website's administrator can enter how many points are required to unlock the voucher in the settings. The website administrator also needs a QR Planet account and API secret key to take full advantage of the API.
As soon as my first web developer job started, I was tasked by my employer to figure out how to incorporate [QR Planet's one-time redeemable coupon feature](https://qrplanet.com/help/article/one-time-redeemable-qr-code-coupons) into my former company's WooCommerce website that used WooRewards to power their customer points and rewards system, since QR Planet does not provide any official WordPress plugin (at least at the moment). After researching both WooRewards and QR Planet, I suggested to my employer that the best solution was to develop our own WordPress plugin as an add-on to WooRewards.
Before getting my first developer job, I had some basic PHP knowledge and skills, notably by [creating my own fork of a PHP website script as a hobby project](2024-07-08-dipping-my-toes-in-php-for-my-hobby-project.md), so I developed my first WordPress plugin by applying my knowledge and skills of how to study the source code of an existing project to learn how to add extra features to it.
The PHP features I learned the most from developing my one-time redeemable voucher plugin were [classes and objects](https://www.php.net/manual/en/language.oop5.php), since WooRewards' functions are mostly powered by classes and objects, so my first objective in developing the plugin was to write my own classes and objects for calling the QR Planet API, generating a voucher and a link to redeem the voucher, setting up an email template for my plugin, and setting up my plugin to work with WooRewards. I learned how to write the PHP code to execute the things I want my plugin to do, by doing a lot of research on the web about classes and objects. I wrote and test my plugin one step at a time to gradually ensure my plugin does the things I want it to do.
After I completed and tested my plugin, I proudly showed it to my employer and manager. I still remember the "wow" my employer exclaimed when I told him I succeed in completing the task he assigned to me by developing a WordPress plugin.
My success in developing my first WordPress plugin was a huge milestone in my web developer journey, by showcasing not only my technical and programming knowledge and skills, but also my learning and problem-solving skills.
You can view the source code of my one-time redeemable voucher add-on WooRewards by checking out [its GitHub repository](https://github.com/helenclx/woorewards-addon-one-time-voucher).
---
title: Developed My First WordPress Plugin
desc: I successfully developed a WordPress plugin during my first web developer job.
date: 2025-01-03T16:30:08+0800
topics: ["wordpress", "php"]
---
3 January 2025 was the final day of [my first web developer job](2024-08-16-got-my-first-developer-job.md).
The best thing that came out from my first web developer job was my accomplishment of learning and successfully developing a WordPress plugin for the first time. Specifically, my plugin is an add-on to [Long Watch Studio](https://plugins.longwatchstudio.com/)'s [WooRewards](https://plugins.longwatchstudio.com/product/woorewards/), which is a plugin for WooCommerce.
What my plugin does is to add one-time redeemable vouchers as a reward option in a WooCommerce website's points and rewards system powered by WooRewards for customers to unlock, with the vouchers being issued by using the [QR Planet API](https://qrplanet.com/qr-code-api). When a customer unlocks a one-time redeemable voucher reward, the plugin will automatically send the customer an email containing a link to redeem the voucher with instructions. Like with other types of rewards in WooRewards, the WordPress or WooCommerce website's administrator can enter how many points are required to unlock the voucher in the settings. The website administrator also needs a QR Planet account and API secret key to take full advantage of the API.
As soon as my first web developer job started, I was tasked by my employer to figure out how to incorporate [QR Planet's one-time redeemable coupon feature](https://qrplanet.com/help/article/one-time-redeemable-qr-code-coupons) into my former company's WooCommerce website that used WooRewards to power their customer points and rewards system, since QR Planet does not provide any official WordPress plugin (at least at the moment). After researching both WooRewards and QR Planet, I suggested to my employer that the best solution was to develop our own WordPress plugin as an add-on to WooRewards.
Before getting my first developer job, I had some basic PHP knowledge and skills, notably by [creating my own fork of a PHP website script as a hobby project](2024-07-08-dipping-my-toes-in-php-for-my-hobby-project.md), so I developed my first WordPress plugin by applying my knowledge and skills of how to study the source code of an existing project to learn how to add extra features to it.
The PHP features I learned the most from developing my one-time redeemable voucher plugin were [classes and objects](https://www.php.net/manual/en/language.oop5.php), since WooRewards' functions are mostly powered by classes and objects, so my first objective in developing the plugin was to write my own classes and objects for calling the QR Planet API, generating a voucher and a link to redeem the voucher, setting up an email template for my plugin, and setting up my plugin to work with WooRewards. I learned how to write the PHP code to execute the things I want my plugin to do, by doing a lot of research on the web about classes and objects. I wrote and test my plugin one step at a time to gradually ensure my plugin does the things I want it to do.
After I completed and tested my plugin, I proudly showed it to my employer and manager. I still remember the "wow" my employer exclaimed when I told him I succeed in completing the task he assigned to me by developing a WordPress plugin.
My success in developing my first WordPress plugin was a huge milestone in my web developer journey, by showcasing not only my technical and programming knowledge and skills, but also my learning and problem-solving skills.
You can view the source code of my one-time redeemable voucher add-on WooRewards by checking out [its GitHub repository](https://github.com/helenclx/woorewards-addon-one-time-voucher).

View File

@@ -1,29 +1,29 @@
---
title: Attending 42 the Computer Science School
desc: I have applied to attend one of the Malaysian campuses of 42, an international computer science school.
date: 2025-01-19T23:56:32+0800
updated: 2025-02-17T18:08:02+0800
topics: ["42 the school", "life updates"]
---
After [my first web developer job](2024-08-16-got-my-first-developer-job.md) ended, I had been considering how to proceed to the next phase of my web development journey.
One day in December 2024, I suddenly remembered that my mother told me once that there was now a coding school somewhere relatively near to our house. We even talked about how my younger self would have appreciated the existence of such a school so I could become a developer much earlier. Out of curiosity, I searched the web to look for the school in question.
Turned out, the school is named [42](https://www.42network.org/), and is an international school network that provides free computer science education, with many campuses around the world, including Malaysia. The main thing that differentiates 42 from traditional universities is its peer-to-peer and project-based learning models that do not involve any teachers.
According to the [official website of 42's Paris campus](https://42.fr/en/what-is-42/42-program-explained/) — where 42 originated — the name is a reference to Douglas Adams book {{ cite "The Hitchhikers Guide to the Galaxy" }}:
> In Douglas Adams book The Hitchhikers Guide to the Galaxy H2G2 for short an advanced alien species builds a super computer. It has only one job: to find the answer to the Ultimate Question of Life, the Universe and Everything. After 7 million years of calculation, the computer finds the answer: 42. In geek culture, 42 often represents the answer to a seemingly insuperable problem. This is why 42 is called 42. This is our answer to what IT education should be today and in the future.
As a web developer without a computer science degree, a physical school that offers computer science education for free is certainly an attractive notion. Furthermore, as a developer who learned to code through online courses and learning resources, I have got used to learning new things without a teacher holding my hand. My years of working experience had also taught me to not expecting anyone to hold your hand if you want to upskill yourself. I have also found myself to prefer to learn to code by building projects.
Therefore, in late December 2024, I applied for the Malaysian campus of 42 near me, spent two hours in their application test consisted of a memory game and a logic game. On the next day, I was informed that I passed my application test, so I chose to enrol in their trial bootcamp, referred to as the Piscine, that will begin on 7 April 2025. Participants need to pass the 4-week trial bootcamp before being able to take 42's core curriculum.
Meanwhile, through their official website, I learned that the campus would host an open day on 18 January 2025, so I decided to take the opportunity to visit the campus for myself to see what I can expect from the environment when I started my study there. When I attended the open day on January 18, I learned that the computers in their workstations use Linux, specifically the Ubuntu distribution with GNOME as its desktop environment.
However, to my surprise, the computers did not have any setting to adjust screen brightness, which was not a good thing for me due to my [retinitis pigmentosa](https://blog.helenchong.omg.lol/en/posts/2024-12-21-living-with-retinitis-pigmentosa/) making my eyes sensitive to bright light, including artificial lights. I would prefer not to rely on my clip-on sunglasses just to see a computer screen, since the surroundings including the keyboard would appear too dark for me. Therefore, I approached one of the staff members to request methods of dimming the computer screen when I attend my class there. The staff member suggested that they should be able to arrange that to accommodate my needs.
After visiting the campus on its open day, I have been looking forward to attending the school more. Wish me luck in passing my Piscine so I can become an official student of 42!
**Update, 17 February 2025:** Originally, I registered to join the Piscine scheduled for 24 February 2025, but I received an email from the school announcing that due to unforeseen circumstances, the Piscine scheduled late February 2025 will no longer take place as planned. Therefore, I registered for the Piscine beginning on 7 April 2025.
---
title: Attending 42 the Computer Science School
desc: I have applied to attend one of the Malaysian campuses of 42, an international computer science school.
date: 2025-01-19T23:56:32+0800
updated: 2025-02-17T18:08:02+0800
topics: ["42 the school", "life updates"]
---
After [my first web developer job](2024-08-16-got-my-first-developer-job.md) ended, I had been considering how to proceed to the next phase of my web development journey.
One day in December 2024, I suddenly remembered that my mother told me once that there was now a coding school somewhere relatively near to our house. We even talked about how my younger self would have appreciated the existence of such a school so I could become a developer much earlier. Out of curiosity, I searched the web to look for the school in question.
Turned out, the school is named [42](https://www.42network.org/), and is an international school network that provides free computer science education, with many campuses around the world, including Malaysia. The main thing that differentiates 42 from traditional universities is its peer-to-peer and project-based learning models that do not involve any teachers.
According to the [official website of 42's Paris campus](https://42.fr/en/what-is-42/42-program-explained/) — where 42 originated — the name is a reference to Douglas Adams book {{ cite "The Hitchhikers Guide to the Galaxy" }}:
> In Douglas Adams book The Hitchhikers Guide to the Galaxy H2G2 for short an advanced alien species builds a super computer. It has only one job: to find the answer to the Ultimate Question of Life, the Universe and Everything. After 7 million years of calculation, the computer finds the answer: 42. In geek culture, 42 often represents the answer to a seemingly insuperable problem. This is why 42 is called 42. This is our answer to what IT education should be today and in the future.
As a web developer without a computer science degree, a physical school that offers computer science education for free is certainly an attractive notion. Furthermore, as a developer who learned to code through online courses and learning resources, I have got used to learning new things without a teacher holding my hand. My years of working experience had also taught me to not expecting anyone to hold your hand if you want to upskill yourself. I have also found myself to prefer to learn to code by building projects.
Therefore, in late December 2024, I applied for the Malaysian campus of 42 near me, spent two hours in their application test consisted of a memory game and a logic game. On the next day, I was informed that I passed my application test, so I chose to enrol in their trial bootcamp, referred to as the Piscine, that will begin on 7 April 2025. Participants need to pass the 4-week trial bootcamp before being able to take 42's core curriculum.
Meanwhile, through their official website, I learned that the campus would host an open day on 18 January 2025, so I decided to take the opportunity to visit the campus for myself to see what I can expect from the environment when I started my study there. When I attended the open day on January 18, I learned that the computers in their workstations use Linux, specifically the Ubuntu distribution with GNOME as its desktop environment.
However, to my surprise, the computers did not have any setting to adjust screen brightness, which was not a good thing for me due to my [retinitis pigmentosa](https://blog.helenchong.omg.lol/en/posts/2024-12-21-living-with-retinitis-pigmentosa/) making my eyes sensitive to bright light, including artificial lights. I would prefer not to rely on my clip-on sunglasses just to see a computer screen, since the surroundings including the keyboard would appear too dark for me. Therefore, I approached one of the staff members to request methods of dimming the computer screen when I attend my class there. The staff member suggested that they should be able to arrange that to accommodate my needs.
After visiting the campus on its open day, I have been looking forward to attending the school more. Wish me luck in passing my Piscine so I can become an official student of 42!
**Update, 17 February 2025:** Originally, I registered to join the Piscine scheduled for 24 February 2025, but I received an email from the school announcing that due to unforeseen circumstances, the Piscine scheduled late February 2025 will no longer take place as planned. Therefore, I registered for the Piscine beginning on 7 April 2025.

View File

@@ -1,18 +1,18 @@
---
title: I Have Joined the light-dark() Side
desc: I am applying the CSS light-dark() function to add light and dark modes to my websites.
date: 2025-02-15T11:18:07+0800
topics: ["css"]
---
After building and working on this developer portfolio and blog website of mine for a while, I began to learn to apply light and dark modes to my website. Initially, I used the [`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) CSS `@media` feature to achieve that. Specifically, I used a `prefers-color-scheme: dark` media query to set the values of my website layout in dark mode.
That was, until I learned about CSS' [`light-dark()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/light-dark) colour function through [Sara Joy](https://sarajoy.dev/)'s article on CSS-Tricks, ["Come to the light-dark() Side"](https://css-tricks.com/come-to-the-light-dark-side/). As soon as I tried the `light-dark()` function out myself, I fell in love with it. In other words, I have indeed joined the `light-dark()` side, LOL.
`light-dark()` is a game-changer, because compared to the `prefers-color-scheme` `@media` query, it saves me the trouble of duplicating lines of CSS properties, both native and custom, just to change the colour value. Additionally, I got to learn about using the `color-scheme` CSS property as another method to set a web page's light and dark mode.
Therefore, I decided to apply `color-scheme` and `light-dark()` to my websites.
helenchong.dev is also a member of the [Darktheme Club](https://darktheme.club/), a directory of websites with dark modes. Part of the process to join the directory is to specify how you add dark mode to your website, with multiple options include dark mode by default, `@media` query or JavaScript, but no option for the `color-scheme` CSS property.
Therefore, I made a suggestion to add `color-scheme` CSS property as a possible method to set dark mode by [creating a GitHub issue](https://github.com/garritfra/darktheme.club/issues/194). [Garrit Franke](https://garrit.xyz/), the creator of Darktheme Club, replied by asking if I could create a pull request for it, so [I happily did](https://github.com/garritfra/darktheme.club/pull/196). I also changed my website's dark mode method from "media query" to "color-scheme". My pull request eventually got merged! I am happy to be able to make good use of my new CSS knowledge.
---
title: I Have Joined the light-dark() Side
desc: I am applying the CSS light-dark() function to add light and dark modes to my websites.
date: 2025-02-15T11:18:07+0800
topics: ["css"]
---
After building and working on this developer portfolio and blog website of mine for a while, I began to learn to apply light and dark modes to my website. Initially, I used the [`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) CSS `@media` feature to achieve that. Specifically, I used a `prefers-color-scheme: dark` media query to set the values of my website layout in dark mode.
That was, until I learned about CSS' [`light-dark()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/light-dark) colour function through [Sara Joy](https://sarajoy.dev/)'s article on CSS-Tricks, ["Come to the light-dark() Side"](https://css-tricks.com/come-to-the-light-dark-side/). As soon as I tried the `light-dark()` function out myself, I fell in love with it. In other words, I have indeed joined the `light-dark()` side, LOL.
`light-dark()` is a game-changer, because compared to the `prefers-color-scheme` `@media` query, it saves me the trouble of duplicating lines of CSS properties, both native and custom, just to change the colour value. Additionally, I got to learn about using the `color-scheme` CSS property as another method to set a web page's light and dark mode.
Therefore, I decided to apply `color-scheme` and `light-dark()` to my websites.
helenchong.dev is also a member of the [Darktheme Club](https://darktheme.club/), a directory of websites with dark modes. Part of the process to join the directory is to specify how you add dark mode to your website, with multiple options include dark mode by default, `@media` query or JavaScript, but no option for the `color-scheme` CSS property.
Therefore, I made a suggestion to add `color-scheme` CSS property as a possible method to set dark mode by [creating a GitHub issue](https://github.com/garritfra/darktheme.club/issues/194). [Garrit Franke](https://garrit.xyz/), the creator of Darktheme Club, replied by asking if I could create a pull request for it, so [I happily did](https://github.com/garritfra/darktheme.club/pull/196). I also changed my website's dark mode method from "media query" to "color-scheme". My pull request eventually got merged! I am happy to be able to make good use of my new CSS knowledge.

View File

@@ -1,92 +1,92 @@
---
title: Adding a Button to Copy Code Snippets
desc: How I added a button to copy code snippets to my website with JavaScript.
date: 2025-02-19T11:05:03+0800
topics: ["javascript", "accessibility"]
hasCodeBlock: true
---
Recently, I discovered [Salma Alam-Naylor](https://whitep4nth3r.com/)'s article [“How to build a copy code snippet button and why it matters”](https://whitep4nth3r.com/blog/how-to-build-a-copy-code-snippet-button/) through [Frontend Dogma](https://frontenddogma.com/).
This being a developer website and blog, adding a button to copy code snippets was something I had considered before, as I had thought it would be convenient for readers to copy a code snippet with one click, but I did not consider how such a button could improve a website's accessibility too, until I read Salma's post. As she wrote:
> It is impossible to highlight and copy code blocks when you are unable to use your hands.
>
> […] it is absolutely imperative that you include a way for people with limited use of their hands to copy that code in a single action
Therefore, I began to research how to add a button to copy code snippets to my website. Like Salma, I also use Eleventy to build my website, and use [Eleventy's official syntax highlighting plugin](https://www.11ty.dev/docs/plugins/syntaxhighlight/), but I was unable to figure out how to make use of Salma's code to add such button to my website. Therefore, I visited [The 11ty Bundle](https://11tybundle.dev/), and searched if there were other guides for adding a button to copy code snippets to an Eleventy website. Eventually, I found [ttntm](https://ttntm.me/)'s article ["Adding a Copy Button to Code Blocks"](https://ttntm.me/blog/adding-a-copy-button-to-code-blocks/).
When I tried ttntm's code by adding it to a JavaScript file to be linked to my website, I was pleased to found that it worked. Since it worked on my website, I decided to modify ttntm's code to suit my preferences.
Firstly, instead of just an icon with a HTML `title` attribute as a tooltip, I added text to the button, namely "Copy Code" and then "Copied!" when the button is clicked, so both sighted readers and screen reader users can better understand the purpose of the button.
Secondly, I chose to place the button after my code blocks, rather than before them. This approach was inspired by [David Bushell](https://dbushell.com/)'s article ["Copy Code Button"](https://dbushell.com/2025/02/14/copy-code-button/). I would like readers to look at a code snippet first before deciding if they want to copy that snippet. To achieve this, I change the `insertAdjacentHTML()` method's position parameter from `"beforebegin"` to `"afterend"`.
I have configured Eleventy's official syntax highlighting plugin to make my code blocks focusable, so readers who use keyboard to navigate my website can easily skip through the code snippets and go to my "Copy Code" button by pressing the Tab button.
Thirdly, I decided to make the button to copy code appear on mobile as well. While coding is not typically done on mobile devices, it still would not hurt to give mobile device users a convenient way to copy code snippet, so they can save the snippet somewhere else for future references, such as saving in a note app to reference it later when they return to a desktop computer. As instructed by ttntm, I removed the `if` statement (`if (notAPhone.matches) {...}`) to achieve that.
Here is my final JavaScript code for adding a button to copy code snippets:
```js
function createCopyBtn(blockIndex) {
return `<div class="cc-wrapper d-none d-sm-block">
<button class="cc-btn btn-muted shadow" data-target="${blockIndex}">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none"d="M0 0h24v24H0z"fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2"/><path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z"/></svg>
Copy Code
</button>
</div>`;
}
async function copyCode(block) {
const code = document.querySelector(`[data-block-id="${block}"]`);
const doCopy = async() => await navigator.clipboard.writeText(code?.innerText ?? '');
if (!navigator.userAgent.includes('Firefox')) {
const result = await navigator.permissions.query({ name: 'clipboard-write' });
if (result.state === 'granted' || result.state === 'prompt') {
doCopy();
}
} else {
doCopy();
}
}
async function handleCopyBtnClick(event) {
const btn = event?.target;
const btnTarget = btn?.getAttribute('data-target');
if (btn && btnTarget) {
const originalText = btn.innerHTML;
await copyCode(btnTarget);
btn.innerHTML = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" /><path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M9 14l2 2l4 -4" /></svg> Copied!';
setTimeout(() => {
btn.innerHTML = originalText;
}, 1500);
}
}
document.addEventListener('DOMContentLoaded', () => {
const allCodeBlocks = Array.from(document.querySelectorAll('pre[class^="language-"]'));
allCodeBlocks.forEach((b, i) => {
const code = b.childNodes[0];
const codeBlockIndex = `cb-${i}`;
b.insertAdjacentHTML('afterend', createCopyBtn(codeBlockIndex));
code.setAttribute('data-block-id', codeBlockIndex);
})
const allCopyBtns = Array.from(document.querySelectorAll('.cc-btn'));
allCopyBtns.forEach((btn) => {
btn.addEventListener('click', handleCopyBtnClick);
})
})
```
Finally, after styling the button a bit with CSS, I was satisfied with how my button to copy code snippets turn out, so as long as your browser has JavaScript enabled, you should be able to see a "Copy Code" button below the code snippet blocks on this website, including my JavaScript code for the button above.
---
title: Adding a Button to Copy Code Snippets
desc: How I added a button to copy code snippets to my website with JavaScript.
date: 2025-02-19T11:05:03+0800
topics: ["javascript", "accessibility"]
hasCodeBlock: true
---
Recently, I discovered [Salma Alam-Naylor](https://whitep4nth3r.com/)'s article [“How to build a copy code snippet button and why it matters”](https://whitep4nth3r.com/blog/how-to-build-a-copy-code-snippet-button/) through [Frontend Dogma](https://frontenddogma.com/).
This being a developer website and blog, adding a button to copy code snippets was something I had considered before, as I had thought it would be convenient for readers to copy a code snippet with one click, but I did not consider how such a button could improve a website's accessibility too, until I read Salma's post. As she wrote:
> It is impossible to highlight and copy code blocks when you are unable to use your hands.
>
> […] it is absolutely imperative that you include a way for people with limited use of their hands to copy that code in a single action
Therefore, I began to research how to add a button to copy code snippets to my website. Like Salma, I also use Eleventy to build my website, and use [Eleventy's official syntax highlighting plugin](https://www.11ty.dev/docs/plugins/syntaxhighlight/), but I was unable to figure out how to make use of Salma's code to add such button to my website. Therefore, I visited [The 11ty Bundle](https://11tybundle.dev/), and searched if there were other guides for adding a button to copy code snippets to an Eleventy website. Eventually, I found [ttntm](https://ttntm.me/)'s article ["Adding a Copy Button to Code Blocks"](https://ttntm.me/blog/adding-a-copy-button-to-code-blocks/).
When I tried ttntm's code by adding it to a JavaScript file to be linked to my website, I was pleased to found that it worked. Since it worked on my website, I decided to modify ttntm's code to suit my preferences.
Firstly, instead of just an icon with a HTML `title` attribute as a tooltip, I added text to the button, namely "Copy Code" and then "Copied!" when the button is clicked, so both sighted readers and screen reader users can better understand the purpose of the button.
Secondly, I chose to place the button after my code blocks, rather than before them. This approach was inspired by [David Bushell](https://dbushell.com/)'s article ["Copy Code Button"](https://dbushell.com/2025/02/14/copy-code-button/). I would like readers to look at a code snippet first before deciding if they want to copy that snippet. To achieve this, I change the `insertAdjacentHTML()` method's position parameter from `"beforebegin"` to `"afterend"`.
I have configured Eleventy's official syntax highlighting plugin to make my code blocks focusable, so readers who use keyboard to navigate my website can easily skip through the code snippets and go to my "Copy Code" button by pressing the Tab button.
Thirdly, I decided to make the button to copy code appear on mobile as well. While coding is not typically done on mobile devices, it still would not hurt to give mobile device users a convenient way to copy code snippet, so they can save the snippet somewhere else for future references, such as saving in a note app to reference it later when they return to a desktop computer. As instructed by ttntm, I removed the `if` statement (`if (notAPhone.matches) {...}`) to achieve that.
Here is my final JavaScript code for adding a button to copy code snippets:
```js
function createCopyBtn(blockIndex) {
return `<div class="cc-wrapper d-none d-sm-block">
<button class="cc-btn btn-muted shadow" data-target="${blockIndex}">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none"d="M0 0h24v24H0z"fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2"/><path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z"/></svg>
Copy Code
</button>
</div>`;
}
async function copyCode(block) {
const code = document.querySelector(`[data-block-id="${block}"]`);
const doCopy = async() => await navigator.clipboard.writeText(code?.innerText ?? '');
if (!navigator.userAgent.includes('Firefox')) {
const result = await navigator.permissions.query({ name: 'clipboard-write' });
if (result.state === 'granted' || result.state === 'prompt') {
doCopy();
}
} else {
doCopy();
}
}
async function handleCopyBtnClick(event) {
const btn = event?.target;
const btnTarget = btn?.getAttribute('data-target');
if (btn && btnTarget) {
const originalText = btn.innerHTML;
await copyCode(btnTarget);
btn.innerHTML = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" /><path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M9 14l2 2l4 -4" /></svg> Copied!';
setTimeout(() => {
btn.innerHTML = originalText;
}, 1500);
}
}
document.addEventListener('DOMContentLoaded', () => {
const allCodeBlocks = Array.from(document.querySelectorAll('pre[class^="language-"]'));
allCodeBlocks.forEach((b, i) => {
const code = b.childNodes[0];
const codeBlockIndex = `cb-${i}`;
b.insertAdjacentHTML('afterend', createCopyBtn(codeBlockIndex));
code.setAttribute('data-block-id', codeBlockIndex);
})
const allCopyBtns = Array.from(document.querySelectorAll('.cc-btn'));
allCopyBtns.forEach((btn) => {
btn.addEventListener('click', handleCopyBtnClick);
})
})
```
Finally, after styling the button a bit with CSS, I was satisfied with how my button to copy code snippets turn out, so as long as your browser has JavaScript enabled, you should be able to see a "Copy Code" button below the code snippet blocks on this website, including my JavaScript code for the button above.

View File

@@ -1,30 +1,30 @@
---
title: Attended 42 the School's 5-Day Coding Bootcamp
desc: I joined 42 the school's 5-day bite-sized coding bootcamp in late February.
date: 2025-03-06T22:20:37+0800
topics: ["42 the school", "python", "life updates"]
---
From 24 to 28 February 2025, I attended [42 the computer science school](2025-01-20-attending-42-school.md)'s bite-sized coding bootcamp that lasted for 5 days, called the Discovery Piscine.
Originally, when I applied for 42 and passed their online assessment in December 2024, I registered to join their trial bootcamp, named Piscine, on 24 February 2025. However, on 17 February 2025, a week before my Piscine was supposed to start, I received an email from 42 announcing that due to unforeseen circumstances, the Piscine scheduled late February 2025 will no longer take place as planned. Therefore, I registered for the Piscine beginning on 7 April 2025.
On the next day, I received another email from 42 that instead of a full Piscine, a five-day bite-sized coding bootcamp named Discovery Piscine would take place from February 24 to 28 instead. I decided to register to join Discovery Piscine, so I could have a taste of what the Piscine is like, and allowing myself to be better prepared for the full Piscine in April.
Discovery Piscine cadets, or Discovery Pisciners, will learn Python and shell, since the computers in their workstations use Linux, specifically the Ubuntu distribution with GNOME as its desktop environment.
I had experience with Linux desktop environments before 42 founded its first Malaysian campus, as I used to dual boot Windows and Linux in my computer, but that happened years ago. Currently, my main method of using Linux is through Windows Subsystem for Linux (WSL) without setting up any desktop environment. Therefore, Discovery Piscine provided a great opportunity to practice using Linux and shell commands.
I had learned Python before during my journey of teaching myself to code, and I even [contributed to a Python library for datamining my favourite video game {{ cite "Cassette Beasts" }}](2023-11-13-My-First-PR-in-Python.md). However, Discovery Piscine allowed me to refresh my Python knowledge and practice writing Python scripts, while also learning Python's built-in functions that I was unfamiliar with. There was an exercise where I was stuck in for a longer time than expect, but then I solved the exercise after discovering the Python function I needed by searching the web with the right keyword.
Speaking of the computers in 42's workstations, during my last visit to my local campus of 42 on their open day in January, I requested a way to change the computer screen brightness to the campus staff, because my eyes are sensitive to bright light due to me having [retinitis pigmentosa](https://blog.helenchong.omg.lol/en/posts/2024-12-21-living-with-retinitis-pigmentosa/), and to my surprise the computers lacked the option to adjust screen brightness. The school staff promised to accommodate my request. On my first day attending the Discovery Piscine, I reminded the staff of my request, and a staff member soon installed a screen brightness setting in all the workstation computers, so I could adjust the computer screen brightness to the level I felt comfortable with.
Seeing the school staff fulfilling their promise to meet my request that accommodated my visual impairment has certainly raised my opinion on the school even higher.
42's education model is peer-to-peer learning without teachers or lectures, so cadets obtain curriculum materials through 42's intranet. After we completed a curriculum module, we still book someone else's time slot to let another cadet evaluate our work. Even if we failed a module, we could retry the module immediately. Cadets also need to set available time slots, so others can book our time slots for evaluation. When I evaluated other Discovery Pisciners' work, I often provided tips for improving code and alternate ways to solve the exercises.
The fact that 42's curriculum has no teacher encouraged us to learn through searching the web, and it certainly helped that there have been a lot of resources, including free ones, to learn to code. 42's curriculum reminded me of how I started to teach myself to code for this reason, with the added bonus of learning in a physical coding school and meeting other people who were also interested in learning to code.
There were 10 modules of Python exercises in Discovery Piscine's curriculum, and I managed to do all the exercises, though not all of them got evaluated because of time constraints, and 42 only considers you having completed a module after you pass peer evaluations, so I ended up completing 5 out of 10 modules. However, this also earned me a certificate of completion after I graduated from the Discovery Piscine.
Despite already having programming experience — including [professional experience](2024-08-16-got-my-first-developer-job.md) albeit a short one — I find 42 a good school for me. I am looking forward to the full Piscine in April.
---
title: Attended 42 the School's 5-Day Coding Bootcamp
desc: I joined 42 the school's 5-day bite-sized coding bootcamp in late February.
date: 2025-03-06T22:20:37+0800
topics: ["42 the school", "python", "life updates"]
---
From 24 to 28 February 2025, I attended [42 the computer science school](2025-01-20-attending-42-school.md)'s bite-sized coding bootcamp that lasted for 5 days, called the Discovery Piscine.
Originally, when I applied for 42 and passed their online assessment in December 2024, I registered to join their trial bootcamp, named Piscine, on 24 February 2025. However, on 17 February 2025, a week before my Piscine was supposed to start, I received an email from 42 announcing that due to unforeseen circumstances, the Piscine scheduled late February 2025 will no longer take place as planned. Therefore, I registered for the Piscine beginning on 7 April 2025.
On the next day, I received another email from 42 that instead of a full Piscine, a five-day bite-sized coding bootcamp named Discovery Piscine would take place from February 24 to 28 instead. I decided to register to join Discovery Piscine, so I could have a taste of what the Piscine is like, and allowing myself to be better prepared for the full Piscine in April.
Discovery Piscine cadets, or Discovery Pisciners, will learn Python and shell, since the computers in their workstations use Linux, specifically the Ubuntu distribution with GNOME as its desktop environment.
I had experience with Linux desktop environments before 42 founded its first Malaysian campus, as I used to dual boot Windows and Linux in my computer, but that happened years ago. Currently, my main method of using Linux is through Windows Subsystem for Linux (WSL) without setting up any desktop environment. Therefore, Discovery Piscine provided a great opportunity to practice using Linux and shell commands.
I had learned Python before during my journey of teaching myself to code, and I even [contributed to a Python library for datamining my favourite video game {{ cite "Cassette Beasts" }}](2023-11-13-My-First-PR-in-Python.md). However, Discovery Piscine allowed me to refresh my Python knowledge and practice writing Python scripts, while also learning Python's built-in functions that I was unfamiliar with. There was an exercise where I was stuck in for a longer time than expect, but then I solved the exercise after discovering the Python function I needed by searching the web with the right keyword.
Speaking of the computers in 42's workstations, during my last visit to my local campus of 42 on their open day in January, I requested a way to change the computer screen brightness to the campus staff, because my eyes are sensitive to bright light due to me having [retinitis pigmentosa](https://blog.helenchong.omg.lol/en/posts/2024-12-21-living-with-retinitis-pigmentosa/), and to my surprise the computers lacked the option to adjust screen brightness. The school staff promised to accommodate my request. On my first day attending the Discovery Piscine, I reminded the staff of my request, and a staff member soon installed a screen brightness setting in all the workstation computers, so I could adjust the computer screen brightness to the level I felt comfortable with.
Seeing the school staff fulfilling their promise to meet my request that accommodated my visual impairment has certainly raised my opinion on the school even higher.
42's education model is peer-to-peer learning without teachers or lectures, so cadets obtain curriculum materials through 42's intranet. After we completed a curriculum module, we still book someone else's time slot to let another cadet evaluate our work. Even if we failed a module, we could retry the module immediately. Cadets also need to set available time slots, so others can book our time slots for evaluation. When I evaluated other Discovery Pisciners' work, I often provided tips for improving code and alternate ways to solve the exercises.
The fact that 42's curriculum has no teacher encouraged us to learn through searching the web, and it certainly helped that there have been a lot of resources, including free ones, to learn to code. 42's curriculum reminded me of how I started to teach myself to code for this reason, with the added bonus of learning in a physical coding school and meeting other people who were also interested in learning to code.
There were 10 modules of Python exercises in Discovery Piscine's curriculum, and I managed to do all the exercises, though not all of them got evaluated because of time constraints, and 42 only considers you having completed a module after you pass peer evaluations, so I ended up completing 5 out of 10 modules. However, this also earned me a certificate of completion after I graduated from the Discovery Piscine.
Despite already having programming experience — including [professional experience](2024-08-16-got-my-first-developer-job.md) albeit a short one — I find 42 a good school for me. I am looking forward to the full Piscine in April.

View File

@@ -1,24 +1,24 @@
---
title: Purelymail and Online Account Spring Cleaning
desc: I started to use Purelymail as my email provider, and took the opportunity to clean up my online accounts.
date: 2025-03-21T21:42:18+0800
topics: ["emails", "online life"]
---
I have switched to [Purelymail](https://purelymail.com/) as my main email service provider.
I have been looking for an alternative email service provider to Gmail and Outlook for my personal use for a while, to reduce my reliance on Big Tech services as much as possible. However, price has always been the main obstacle: as an internet user who lives in a country with cheaper currency than both U.S. Dollars and Euro, I need to be conscious about how much money to spend on Western-based products, and I am honestly not comfortable spending on most of the alternative email service providers, especially for the ability to use custom domain names and create extra mailboxes or users for different purposes.
Purelymail, on the other hand, is really cheap: its standard pricing is already as low as 10 U.S. Dollars per year, while offering no hard limits on storage, and no extra charges for using custom domain names and creating extra mailboxes or users. After trying out Purelymail for a bit, I decided to officially activate my account with [advanced pricing](https://purelymail.com/advancedpricing), which is its pay-as-you-go model, because I calculated that my usage would require spending less than 10 U.S. Dollars per year.
I created multiple mailboxes on my Purelymail account with my various domain names for different purposes: for personal communication or registering accounts on online service. This means I changed the email addresses of most of my online accounts as well.
Speaking of my online accounts, I also took this opportunity of changing the email addresses of my online accounts to close and delete the online accounts that I do not use or need any more. Thanks to a password manager like [BitWarden](https://bitwarden.com/), I am able to not only save passwords and generate unique passwords without having to memorise every one of them myself, but also keep track of how many online accounts I have, including ones that I no longer use or need so I can get rid of those accounts. In the end of this round of online account spring cleaning, I closed and deleted at least 60 accounts.
After inspired by the resurgence of personal websites to code my own website for the first time in 2022, I have learned more about the importance of taking control and ownership of my online life and presence. It is telling that many of the online accounts I just deleted were related to services that I had settled for alternatives that I have been satisfied with.
For example, [Obsidian](https://obsidian.md/) has become my go-to note-taking software for pretty much everything, including projects, so I do not need other online project management services for personal use any more; [Affinity Photo](https://affinity.serif.com/en-gb/photo/) and [Designer](https://affinity.serif.com/en-gb/designer/) have replaced Adobe Photoshop and Illustrator for my photo editing and vector graphics needs, especially given the fact that I do not work as a graphic designer any more, so I do not need an Adobe account any more.
In conclusion, I am satisfied with my switch to Purelymail, and I have found online spring cleaning a good practice in declutter my online life.
Shout-out to [Adam Newbold](https://adam.omg.lol/) and [Faisal Misle](https://faisal.fm/) of [omg.lol](https://home.omg.lol/) staff for helping me to troubleshoot an issue with using my omg.lol email forwarding address to forward emails to my Purelymail mailbox using my omg.lol address as the custom domain, as well as Porkbun's support team for helping me to resolve a DNS issue with one of my domain names after switching from Porkbun's email forwarding service to Purelymail that was caused by the DNS records related to Porkbun's email forwarding being cached in my domain's DNS records longer than expected.
---
title: Purelymail and Online Account Spring Cleaning
desc: I started to use Purelymail as my email provider, and took the opportunity to clean up my online accounts.
date: 2025-03-21T21:42:18+0800
topics: ["emails", "online life"]
---
I have switched to [Purelymail](https://purelymail.com/) as my main email service provider.
I have been looking for an alternative email service provider to Gmail and Outlook for my personal use for a while, to reduce my reliance on Big Tech services as much as possible. However, price has always been the main obstacle: as an internet user who lives in a country with cheaper currency than both U.S. Dollars and Euro, I need to be conscious about how much money to spend on Western-based products, and I am honestly not comfortable spending on most of the alternative email service providers, especially for the ability to use custom domain names and create extra mailboxes or users for different purposes.
Purelymail, on the other hand, is really cheap: its standard pricing is already as low as 10 U.S. Dollars per year, while offering no hard limits on storage, and no extra charges for using custom domain names and creating extra mailboxes or users. After trying out Purelymail for a bit, I decided to officially activate my account with [advanced pricing](https://purelymail.com/advancedpricing), which is its pay-as-you-go model, because I calculated that my usage would require spending less than 10 U.S. Dollars per year.
I created multiple mailboxes on my Purelymail account with my various domain names for different purposes: for personal communication or registering accounts on online service. This means I changed the email addresses of most of my online accounts as well.
Speaking of my online accounts, I also took this opportunity of changing the email addresses of my online accounts to close and delete the online accounts that I do not use or need any more. Thanks to a password manager like [BitWarden](https://bitwarden.com/), I am able to not only save passwords and generate unique passwords without having to memorise every one of them myself, but also keep track of how many online accounts I have, including ones that I no longer use or need so I can get rid of those accounts. In the end of this round of online account spring cleaning, I closed and deleted at least 60 accounts.
After inspired by the resurgence of personal websites to code my own website for the first time in 2022, I have learned more about the importance of taking control and ownership of my online life and presence. It is telling that many of the online accounts I just deleted were related to services that I had settled for alternatives that I have been satisfied with.
For example, [Obsidian](https://obsidian.md/) has become my go-to note-taking software for pretty much everything, including projects, so I do not need other online project management services for personal use any more; [Affinity Photo](https://affinity.serif.com/en-gb/photo/) and [Designer](https://affinity.serif.com/en-gb/designer/) have replaced Adobe Photoshop and Illustrator for my photo editing and vector graphics needs, especially given the fact that I do not work as a graphic designer any more, so I do not need an Adobe account any more.
In conclusion, I am satisfied with my switch to Purelymail, and I have found online spring cleaning a good practice in declutter my online life.
Shout-out to [Adam Newbold](https://adam.omg.lol/) and [Faisal Misle](https://faisal.fm/) of [omg.lol](https://home.omg.lol/) staff for helping me to troubleshoot an issue with using my omg.lol email forwarding address to forward emails to my Purelymail mailbox using my omg.lol address as the custom domain, as well as Porkbun's support team for helping me to resolve a DNS issue with one of my domain names after switching from Porkbun's email forwarding service to Purelymail that was caused by the DNS records related to Porkbun's email forwarding being cached in my domain's DNS records longer than expected.

View File

@@ -1,61 +1,61 @@
---
title: I Use (Neo)Vim BTW
desc: I have officially joined the Vim ecosystem and switched to Neovim as my main code editor.
date: 2025-04-03T20:22:21+0800
topics: ["vim", "neovim", "42 the school", "vs code"]
toc: true
---
## Introduction
It is official: I have switched from [Visual Studio Code](https://code.visualstudio.com/) to [Neovim](https://neovim.io/) as my main code editor.
Since I started to teach myself web development, Visual Studio Code had been my integrated development environment (IDE) of choice for two years. I started out with VS Code because an IDE with a graphic user interface (GUI) were easier for a newbie web developer to get used to, especially given the fact that I came from a graphic design background, GUI ticked my graphic brain. It helped that VS Code's vast theme and extension ecosystems allowed me to customise my VS Code workspace to suit my needs.
For two years, I was content with VS Code and my workspace in it. Occasionally, I discovered other code editors and IDEs, and I even briefly considered using [VSCodium](https://vscodium.com/), a community-driven binary releases of VS Code without Microsoft's branding, but I ended up still preferring VS Code for being easier to install extensions. I knew the existence of [Vim](https://www.vim.org/), but with the first thing I learned about them was jokes and memes about how to exit Vim, it was not exactly the best first impression. 😅 The idea of using a terminal-based code editor had not crossed my mind either.
That changed when I attended [42 the computer science school's 5-day bite-sized coding bootcamp](2025-03-06-attended-42-discovery-piscine.md), called Discovery Piscine, in late February this year.
## The Seed of Using Vim
The computers of my local campus of 42 use Linux, specifically the Ubuntu distribution. Discovery Piscine's curriculum focused on Python, but the assignments also included using shell commands, specifically the Bash shell, thus encouraged students to use the terminal to do the assignments.
When I was attending the Discovery Piscine, the only terminal-based text editor I had used was GNU nano, which I had used to edit some configuration files, so GNU nano was what I used for the Discovery Piscine assignments. This also marked my first time coding in GNU nano.
However, as I made progressed in the assignments, I quickly realised GNU nano's simplicity would make it inefficient to do more advanced coding work on it. Meanwhile, evaluating my peers' assignments allowed me to discover that some of my peers were using Vim. I did not have the time to learn to use Vim when I attended Discovery Piscine, but it planted the seed of the idea of using Vim in my mind.
The computers in my 42 campus allow us to install Visual Studio Code or VSCodium — in fact, I used VS Code briefly during the Discovery Piscine to check my assignment files The curriculum also does not impose restriction on which text or code editor we should use for our coding assignments. That said, I figured that with their shell command assignments, and the fact that I already interact with the terminal in my own computer a lot due to building my websites with a static site generator, it would be a good idea to incorporate the terminal in my coding workflow more, by familiarising myself with a powerful terminal-based code editor like Vim, especially as I was planning on attending 42's full Piscine trial bootcamp in April.
In late March, as the April Piscine was approaching, and I wanted to get prepared for the Piscine as much as possible, I decided to finally learn to use Vim for real.
## Side Note: GNU Emacs
As a side note, it is worth pointing out that the computers in my 42 campus also had [GNU Emacs](https://www.gnu.org/s/emacs/) pre-installed. I was already aware of the jokes and memes about the Vim vs. Emacs [editor war](https://en.wikipedia.org/wiki/Editor_war), and I did consider trying out GNU Emacs, but I eventually chose to learn to use Vim first, because I appreciate Vim for being lightweight and ubiquitous as it is pre-isntalled in most Linux distributions, which means even if I use a different Linux machine, I can still count on being able to use a powerful editor for coding. In fact, Vim came pre-installed in my Windows Subsystem for Linux (WSL) with Ubuntu as well.
That said, I am still not completely ruling out the possibility of me using GNU Emacs one day, just not anytime soon.
## Started to Learn to Use Vim
I started learning the keyboard shortcuts and commands in Vim by typing the `vimtutor` tutorial in the terminal of my Windows Subsystem for Linux (WSL) with Ubuntu. I had realised that learning by doing is the best way for me to learn programming, so I appreciate tutorials like vimtutor that allows me to learn and practice by doing.
Even when just trying out Vim's keyboard shortcuts and commands in the tutorial, I already had a much with Vim. Finally trying out Vim myself has certainly made me appreciate it a lot, and see why would developers choose Vim as their code editor of choice. Doing all the coding in keyboard reduces the context switch that would come with using a mouse, combined with Vim's minimal interface, it allows for a coding environment with fewer distractions.
Even after completing vimtutor, sometimes I still revisit the tutorial to refresh my memory of certain Vim shortcuts and commands.
After completing vimtutor, I downloaded and installed gVim in my Windows system. One of the first things I did after installing gVim was adding [Reuben L. Lillie](https://reubenlillie.com/)'s [a11y dark theme](https://github.com/ericwbailey/a11y-syntax-highlighting/blob/home/dist/vim/a11y-dark.vim), which was based on [Eric Bailey](https://ericwbailey.website/)'s [a11y syntax highlighting](https://github.com/ericwbailey/a11y-syntax-highlighting), as my default theme in Vim.
## Hello, Neovim and LazyVim
As I explored the Vim ecosystem further, I discovered Neovim, a fork of Vim. Vim itself has a vast plugin ecosystem, but one particular Neovim setup that attracted my attention was [LazyVim](https://www.lazyvim.org/), as it not only turns Neovim into a full-fledged IDE, but also makes it easy to customise and extend your Neovim configuration.
The plugin ecosystem of the Vim family made me consider making Vim and Neovim replace VS Code as my main code editor. LazyVim provided what I was looking for as a great replacement for VS Code: it includes plugins that replicates the various features I use VS Code for, mainly file tree, searching files by file name or file content and code snippets. The more I explored LazyVim's features, the happier I am with my decision to switch from VS Code to Neovim as my code editor of choice, and LazyVim certainly helped with the transition.
With LazyVim turning Neovim into an IDE that serves my needs, I decided to keep Vim as my backup text editor in the terminal, so I keep my Vim setup simple, with no plugins other than themes and syntax highlighting.
## From VS Code to the Vim Ecosystem
As part of my efforts to transition to the Vim ecosystem as my main code editor, I'm building the habit of navigating the directories containing my code files with the terminal, and open the code files with Vim or Neovim in the terminal.
Finally, as I settled in Vim and Neovim, I uninstalled most extensions in Visual Studio Code, officially marking my switch from VS Code to Neovim as my main code editor.
Hello, Vim and Neovim!
---
title: I Use (Neo)Vim BTW
desc: I have officially joined the Vim ecosystem and switched to Neovim as my main code editor.
date: 2025-04-03T20:22:21+0800
topics: ["vim", "neovim", "42 the school", "vs code"]
toc: true
---
## Introduction
It is official: I have switched from [Visual Studio Code](https://code.visualstudio.com/) to [Neovim](https://neovim.io/) as my main code editor.
Since I started to teach myself web development, Visual Studio Code had been my integrated development environment (IDE) of choice for two years. I started out with VS Code because an IDE with a graphic user interface (GUI) were easier for a newbie web developer to get used to, especially given the fact that I came from a graphic design background, GUI ticked my graphic brain. It helped that VS Code's vast theme and extension ecosystems allowed me to customise my VS Code workspace to suit my needs.
For two years, I was content with VS Code and my workspace in it. Occasionally, I discovered other code editors and IDEs, and I even briefly considered using [VSCodium](https://vscodium.com/), a community-driven binary releases of VS Code without Microsoft's branding, but I ended up still preferring VS Code for being easier to install extensions. I knew the existence of [Vim](https://www.vim.org/), but with the first thing I learned about them was jokes and memes about how to exit Vim, it was not exactly the best first impression. 😅 The idea of using a terminal-based code editor had not crossed my mind either.
That changed when I attended [42 the computer science school's 5-day bite-sized coding bootcamp](2025-03-06-attended-42-discovery-piscine.md), called Discovery Piscine, in late February this year.
## The Seed of Using Vim
The computers of my local campus of 42 use Linux, specifically the Ubuntu distribution. Discovery Piscine's curriculum focused on Python, but the assignments also included using shell commands, specifically the Bash shell, thus encouraged students to use the terminal to do the assignments.
When I was attending the Discovery Piscine, the only terminal-based text editor I had used was GNU nano, which I had used to edit some configuration files, so GNU nano was what I used for the Discovery Piscine assignments. This also marked my first time coding in GNU nano.
However, as I made progressed in the assignments, I quickly realised GNU nano's simplicity would make it inefficient to do more advanced coding work on it. Meanwhile, evaluating my peers' assignments allowed me to discover that some of my peers were using Vim. I did not have the time to learn to use Vim when I attended Discovery Piscine, but it planted the seed of the idea of using Vim in my mind.
The computers in my 42 campus allow us to install Visual Studio Code or VSCodium — in fact, I used VS Code briefly during the Discovery Piscine to check my assignment files The curriculum also does not impose restriction on which text or code editor we should use for our coding assignments. That said, I figured that with their shell command assignments, and the fact that I already interact with the terminal in my own computer a lot due to building my websites with a static site generator, it would be a good idea to incorporate the terminal in my coding workflow more, by familiarising myself with a powerful terminal-based code editor like Vim, especially as I was planning on attending 42's full Piscine trial bootcamp in April.
In late March, as the April Piscine was approaching, and I wanted to get prepared for the Piscine as much as possible, I decided to finally learn to use Vim for real.
## Side Note: GNU Emacs
As a side note, it is worth pointing out that the computers in my 42 campus also had [GNU Emacs](https://www.gnu.org/s/emacs/) pre-installed. I was already aware of the jokes and memes about the Vim vs. Emacs [editor war](https://en.wikipedia.org/wiki/Editor_war), and I did consider trying out GNU Emacs, but I eventually chose to learn to use Vim first, because I appreciate Vim for being lightweight and ubiquitous as it is pre-isntalled in most Linux distributions, which means even if I use a different Linux machine, I can still count on being able to use a powerful editor for coding. In fact, Vim came pre-installed in my Windows Subsystem for Linux (WSL) with Ubuntu as well.
That said, I am still not completely ruling out the possibility of me using GNU Emacs one day, just not anytime soon.
## Started to Learn to Use Vim
I started learning the keyboard shortcuts and commands in Vim by typing the `vimtutor` tutorial in the terminal of my Windows Subsystem for Linux (WSL) with Ubuntu. I had realised that learning by doing is the best way for me to learn programming, so I appreciate tutorials like vimtutor that allows me to learn and practice by doing.
Even when just trying out Vim's keyboard shortcuts and commands in the tutorial, I already had a much with Vim. Finally trying out Vim myself has certainly made me appreciate it a lot, and see why would developers choose Vim as their code editor of choice. Doing all the coding in keyboard reduces the context switch that would come with using a mouse, combined with Vim's minimal interface, it allows for a coding environment with fewer distractions.
Even after completing vimtutor, sometimes I still revisit the tutorial to refresh my memory of certain Vim shortcuts and commands.
After completing vimtutor, I downloaded and installed gVim in my Windows system. One of the first things I did after installing gVim was adding [Reuben L. Lillie](https://reubenlillie.com/)'s [a11y dark theme](https://github.com/ericwbailey/a11y-syntax-highlighting/blob/home/dist/vim/a11y-dark.vim), which was based on [Eric Bailey](https://ericwbailey.website/)'s [a11y syntax highlighting](https://github.com/ericwbailey/a11y-syntax-highlighting), as my default theme in Vim.
## Hello, Neovim and LazyVim
As I explored the Vim ecosystem further, I discovered Neovim, a fork of Vim. Vim itself has a vast plugin ecosystem, but one particular Neovim setup that attracted my attention was [LazyVim](https://www.lazyvim.org/), as it not only turns Neovim into a full-fledged IDE, but also makes it easy to customise and extend your Neovim configuration.
The plugin ecosystem of the Vim family made me consider making Vim and Neovim replace VS Code as my main code editor. LazyVim provided what I was looking for as a great replacement for VS Code: it includes plugins that replicates the various features I use VS Code for, mainly file tree, searching files by file name or file content and code snippets. The more I explored LazyVim's features, the happier I am with my decision to switch from VS Code to Neovim as my code editor of choice, and LazyVim certainly helped with the transition.
With LazyVim turning Neovim into an IDE that serves my needs, I decided to keep Vim as my backup text editor in the terminal, so I keep my Vim setup simple, with no plugins other than themes and syntax highlighting.
## From VS Code to the Vim Ecosystem
As part of my efforts to transition to the Vim ecosystem as my main code editor, I'm building the habit of navigating the directories containing my code files with the terminal, and open the code files with Vim or Neovim in the terminal.
Finally, as I settled in Vim and Neovim, I uninstalled most extensions in Visual Studio Code, officially marking my switch from VS Code to Neovim as my main code editor.
Hello, Vim and Neovim!

View File

@@ -1,20 +1,20 @@
---
title: First Week of 42's Piscine Bootcamp
desc: My first week attending 42 the computer science school's 26-day bootcamp named Piscine.
date: 2025-04-13T07:40:46+0800
topics: ["42 the school", "c", "life updates"]
---
On 7 April 2025, my attendance to [42 the computer science school](2025-01-19-attending-42-school.md)'s 26-day bootcamp named Piscine has finally begun. I have been looking forward to this day for nearly three months, after visiting my local campus of the school on its open day in January, applying to join the Piscine that was originally scheduled on February 24 but was later replaced by a 5-day bite-sized version of the bootcamp called [Discovery Piscine](2025-03-06-attended-42-discovery-piscine.md), and applying to join the full Piscine that was scheduled for April 7.
Despite having attended the Discovery Piscine to get a taste of what the Piscine would be like, the full Piscine is still quite a different experience, because not only the full Piscine is longer, but also its curriculum was different: Discovery Piscine focused on Python, but the Piscine focuses on C the programming language, with the first two projects being about shell commands. In addition, during Discovery Piscine, we did not use Git for storing our projects, but in the Piscine, the school's intranet creates Git repositories for all our projects, so we would submit our projects by pushing them to their own Git repositories, and during peer evaluations, evaluators would clone the Git repositories into their own computer to review their peer's work. Furthermore, Discovery Piscine did not have automatic evaluations, but in the Piscine, after getting through peer evaluations, our projects need to pass the automatic evaluation to succeed.
A key to succeed the projects is to pay close attention to the details of the project exercise requirements and examples. A good thing is, one of the ways 42 is not a traditional school is that we can retry projects even if we failed. During my first try of the Piscine's very first project, which was about shell commands, I failed due to not paying enough attention to the details of the exercise requirements, but after learning my mistakes and retrying the project, I succeeded by passing both peer and automatic evaluations.
I had learned C when I took [CS50's Introduction to Computer Science course](/blog/topics/cs50x), but it has been a while since I wrote C, so the Piscine served as a good opportunity to refresh my C skills and knowledge. I started the Piscine's first C project on April 10, and despite already having some background in C, I still got to learn some things about C that I was not aware of before, such as the `write()` function. That said, refreshing my C knowledge and skills by starting the C projects in the Piscine's curriculum certainly helped me to pass the Piscine's first exam on April 11.
After the weekly exam happened on Fridays, we got assigned weekly group projects to do during the weekends, with our team members being generated randomly. The first group project was easy enough for my team to complete it on Saturday, including alternate versions of the project as an opportunity to earn bonus points. Therefore, I got to take one day break on Sunday, but I still need to prepare for the possibility of needing to visit the campus for group projects in Sundays in the next few weeks.
Attending the Piscine made me even happier with my decision to [learn to use Vim](2025-04-03-i-use-neovim-btw.md) to get prepared for the Piscine, because not only the campus computers have Vim installed, but also 42 has its own Vim plugin to add its custom header, which is a requirement for doing its coding projects like C. The plugin is available for Visual Studio Code as well, but the video resource for the first C project showcases C code with Vim. The staff of my local 42 campus also mentioned having Vim installed in the campus computers when they briefed us on the first day of the Piscine. In conclusion, my hunch that 42 encourages students to use Vim was right.
On average, I stayed in the campus around 8 hours per day. I had prepared for April being a busy month for me because of the Piscine, so I am doing my best in the Piscine projects, and I hope I can pass the Piscine and enter 42's core curriculum. I chose to attend 42 because computer science and programming are such vast fields that there is always something to learn, and I am a firm believer that learning is a lifelong process and journey.
---
title: First Week of 42's Piscine Bootcamp
desc: My first week attending 42 the computer science school's 26-day bootcamp named Piscine.
date: 2025-04-13T07:40:46+0800
topics: ["42 the school", "c", "life updates"]
---
On 7 April 2025, my attendance to [42 the computer science school](2025-01-19-attending-42-school.md)'s 26-day bootcamp named Piscine has finally begun. I have been looking forward to this day for nearly three months, after visiting my local campus of the school on its open day in January, applying to join the Piscine that was originally scheduled on February 24 but was later replaced by a 5-day bite-sized version of the bootcamp called [Discovery Piscine](2025-03-06-attended-42-discovery-piscine.md), and applying to join the full Piscine that was scheduled for April 7.
Despite having attended the Discovery Piscine to get a taste of what the Piscine would be like, the full Piscine is still quite a different experience, because not only the full Piscine is longer, but also its curriculum was different: Discovery Piscine focused on Python, but the Piscine focuses on C the programming language, with the first two projects being about shell commands. In addition, during Discovery Piscine, we did not use Git for storing our projects, but in the Piscine, the school's intranet creates Git repositories for all our projects, so we would submit our projects by pushing them to their own Git repositories, and during peer evaluations, evaluators would clone the Git repositories into their own computer to review their peer's work. Furthermore, Discovery Piscine did not have automatic evaluations, but in the Piscine, after getting through peer evaluations, our projects need to pass the automatic evaluation to succeed.
A key to succeed the projects is to pay close attention to the details of the project exercise requirements and examples. A good thing is, one of the ways 42 is not a traditional school is that we can retry projects even if we failed. During my first try of the Piscine's very first project, which was about shell commands, I failed due to not paying enough attention to the details of the exercise requirements, but after learning my mistakes and retrying the project, I succeeded by passing both peer and automatic evaluations.
I had learned C when I took [CS50's Introduction to Computer Science course](/blog/topics/cs50x), but it has been a while since I wrote C, so the Piscine served as a good opportunity to refresh my C skills and knowledge. I started the Piscine's first C project on April 10, and despite already having some background in C, I still got to learn some things about C that I was not aware of before, such as the `write()` function. That said, refreshing my C knowledge and skills by starting the C projects in the Piscine's curriculum certainly helped me to pass the Piscine's first exam on April 11.
After the weekly exam happened on Fridays, we got assigned weekly group projects to do during the weekends, with our team members being generated randomly. The first group project was easy enough for my team to complete it on Saturday, including alternate versions of the project as an opportunity to earn bonus points. Therefore, I got to take one day break on Sunday, but I still need to prepare for the possibility of needing to visit the campus for group projects in Sundays in the next few weeks.
Attending the Piscine made me even happier with my decision to [learn to use Vim](2025-04-03-i-use-neovim-btw.md) to get prepared for the Piscine, because not only the campus computers have Vim installed, but also 42 has its own Vim plugin to add its custom header, which is a requirement for doing its coding projects like C. The plugin is available for Visual Studio Code as well, but the video resource for the first C project showcases C code with Vim. The staff of my local 42 campus also mentioned having Vim installed in the campus computers when they briefed us on the first day of the Piscine. In conclusion, my hunch that 42 encourages students to use Vim was right.
On average, I stayed in the campus around 8 hours per day. I had prepared for April being a busy month for me because of the Piscine, so I am doing my best in the Piscine projects, and I hope I can pass the Piscine and enter 42's core curriculum. I chose to attend 42 because computer science and programming are such vast fields that there is always something to learn, and I am a firm believer that learning is a lifelong process and journey.

View File

@@ -1,27 +1,27 @@
---
title: 42's Piscine Bootcamp Completed
desc: At last, I finished 42 the computer science school's 26-day bootcamp.
date: 2025-05-04T11:05:16+0800
updated: 2025-05-16T22:49:36+0800
topics: ["42 the school", "c", "life updates"]
---
2 May 2025 marked the final day of [42 the computer science school](/blog/topics/42-the-school/)'s 26-day bootcamp, named the Piscine, that I was attending. The final day of the Piscine was also when the final exam took place. I am happy to announce that I have completed the Piscine, by taking and passing the final exam!
This intake of bootcamp started on April 7, and the intense and challenging curriculum meant April had been my busiest month since forever. Despite already having learned C before attending the Piscine, I still got to learn things about C that I did not before, such as using the `write()` function instead of `printf()` to print out something in the terminal, creating a Makefile for the first time ever as part of one of the solo projects, and having fun with it.
I had been enjoying the challenge the Piscine brought though. Attending the bootcamp further cemented my decision to pursue web or software development instead of my previous profession of graphic design that I was burned out from. Despite the curriculum of the Piscine being free, you are still encouraged to spend a great deal of time in your study if you hope to succeed. I pushed myself to spend 6 to 8 hours per day in average in the campus in my work for the Piscine, and I did not regret attending the Piscine for even a second.
That said, I would be lying if I said everything in my study in the Piscine went smoothly. My biggest regret of my days in the Piscine was not being able to pass any of the two group projects, which took place in weekends, that I participated in.
In the first group project, my team made promising progress, but due to including an unused C header in one of the files, our evaluated marked us as failed, as unused headers are forbidden.
In the second group project, my team's leader (team leaders of the group projects in the Piscine were chosen at random) could not go to the campus due to urgent work commitment. While said team leader was still able to contribute to the project through Discord and phone communications, his absence still meant our team coordination was not the best. It did not help that the second group project was considerably more challenging, with it being a Skyscraper puzzle solver. Unsurprisingly, we failed the project as well. I learned it the hard way that no matter how hard the rest of the team tried to carry on, an absent team member would hinder the outcome of a group project and teamwork one way or another.
Failing both group projects also motivated me to work hard in solo projects to hopefully make up for it. We cannot retry group projects, but we can retry solo projects even if we failed. When the final week of the Piscine came, I looked forward to having more free time after this week, but I was still doing my best in my projects and preparing for the final exam.
By the end of the Piscine, I made more progress in the solo projects than most of my peers, by succeeding in more solo projects than most of my peers. As for the weekly exams, I passed all four exams, and except the first exam, where I scored 30%, the minimum score to pass an exam, I succeeded the exams with the same score of 60% out of 100%. It is worth noting that the final exam has more assignments, which was why it lasted for 8 hours instead of the usual 4, which meant I needed to succeed in completing more assignments in the final exam to get the same score.
Despite my intake of the Piscine was finished, it was not completely over for us Pisciners yet, since we still need to wait for another two weeks after the final exam for news and announcements about if we are qualified for 42's core curriculum. I attended 42 because I wanted to continue to learn to become a better developer, and connect with my local tech spaces, so naturally I hope to pass the Piscine and join the school's core programme. Meanwhile, I am taking the opportunity of having more free time to enjoy my personal projects, including my websites, and other hobbies. Fingers crossed!
**Update, 16 May 2025:** Great news: [I have passed the Piscine](2025-05-16-i-have-passed-42-piscine.md) and is eligible for 42's core programme!
---
title: 42's Piscine Bootcamp Completed
desc: At last, I finished 42 the computer science school's 26-day bootcamp.
date: 2025-05-04T11:05:16+0800
updated: 2025-05-16T22:49:36+0800
topics: ["42 the school", "c", "life updates"]
---
2 May 2025 marked the final day of [42 the computer science school](/blog/topics/42-the-school/)'s 26-day bootcamp, named the Piscine, that I was attending. The final day of the Piscine was also when the final exam took place. I am happy to announce that I have completed the Piscine, by taking and passing the final exam!
This intake of bootcamp started on April 7, and the intense and challenging curriculum meant April had been my busiest month since forever. Despite already having learned C before attending the Piscine, I still got to learn things about C that I did not before, such as using the `write()` function instead of `printf()` to print out something in the terminal, creating a Makefile for the first time ever as part of one of the solo projects, and having fun with it.
I had been enjoying the challenge the Piscine brought though. Attending the bootcamp further cemented my decision to pursue web or software development instead of my previous profession of graphic design that I was burned out from. Despite the curriculum of the Piscine being free, you are still encouraged to spend a great deal of time in your study if you hope to succeed. I pushed myself to spend 6 to 8 hours per day in average in the campus in my work for the Piscine, and I did not regret attending the Piscine for even a second.
That said, I would be lying if I said everything in my study in the Piscine went smoothly. My biggest regret of my days in the Piscine was not being able to pass any of the two group projects, which took place in weekends, that I participated in.
In the first group project, my team made promising progress, but due to including an unused C header in one of the files, our evaluated marked us as failed, as unused headers are forbidden.
In the second group project, my team's leader (team leaders of the group projects in the Piscine were chosen at random) could not go to the campus due to urgent work commitment. While said team leader was still able to contribute to the project through Discord and phone communications, his absence still meant our team coordination was not the best. It did not help that the second group project was considerably more challenging, with it being a Skyscraper puzzle solver. Unsurprisingly, we failed the project as well. I learned it the hard way that no matter how hard the rest of the team tried to carry on, an absent team member would hinder the outcome of a group project and teamwork one way or another.
Failing both group projects also motivated me to work hard in solo projects to hopefully make up for it. We cannot retry group projects, but we can retry solo projects even if we failed. When the final week of the Piscine came, I looked forward to having more free time after this week, but I was still doing my best in my projects and preparing for the final exam.
By the end of the Piscine, I made more progress in the solo projects than most of my peers, by succeeding in more solo projects than most of my peers. As for the weekly exams, I passed all four exams, and except the first exam, where I scored 30%, the minimum score to pass an exam, I succeeded the exams with the same score of 60% out of 100%. It is worth noting that the final exam has more assignments, which was why it lasted for 8 hours instead of the usual 4, which meant I needed to succeed in completing more assignments in the final exam to get the same score.
Despite my intake of the Piscine was finished, it was not completely over for us Pisciners yet, since we still need to wait for another two weeks after the final exam for news and announcements about if we are qualified for 42's core curriculum. I attended 42 because I wanted to continue to learn to become a better developer, and connect with my local tech spaces, so naturally I hope to pass the Piscine and join the school's core programme. Meanwhile, I am taking the opportunity of having more free time to enjoy my personal projects, including my websites, and other hobbies. Fingers crossed!
**Update, 16 May 2025:** Great news: [I have passed the Piscine](2025-05-16-i-have-passed-42-piscine.md) and is eligible for 42's core programme!

View File

@@ -1,18 +1,18 @@
---
title: I Have Passed 42's Piscine Bootcamp
desc: After studying hard in 42 the computer science school's 26-day bootcamp, I am officially eligible for 42's core curriculum.
date: 2025-05-16T22:20:23+0800
topics: ["42 the school", "life updates"]
---
Finally, after spending 8 hours in average per day studying hard in [42 the computer science school](/blog/topics/42-the-school/)'s 26-day bootcamp, named the Piscine, and two weeks after finishing the Piscine, I succeed in passing the bootcamp, and is qualified for 42's core curriculum! 🎉
This was another major milestone for me as a developer, since despite already having experience in programming when attending the bootcamp, the Piscine's curriculum was still no walk in the park. I registered and attending the Piscine because I want to continue to improve myself as a developer, so naturally I hope I can pass the bootcamp and be qualified for 42's core programme. It was a massive relief to realise that my hard work had paid off and got recognised by the school.
At the moment, it is still uncertain when the core programme will start, because my local campus still needs a certain amount of qualified participants from multiple intakes of the Piscine, so there are enough students to start taking the core curriculum together. The campus have planned to hold a new intake of the Piscine in the coming months, so hopefully there will be more people joining us in the core programme.
Interestingly, the email the campus staff sent us to inform us of the results of our Piscine arrived at our mailbox at 4.20 pm (Malaysia Time) sharp. I am almost certain that is intentional, given that the school is literally named 42.
I had decided that regardless of my results of the Piscine, I will continue to make good use of the resources I currently have to keep my computer science and programming knowledge and skills sharp, but passing the Piscine is certainly a great motivator.
Looking forward to 42's core programme!
---
title: I Have Passed 42's Piscine Bootcamp
desc: After studying hard in 42 the computer science school's 26-day bootcamp, I am officially eligible for 42's core curriculum.
date: 2025-05-16T22:20:23+0800
topics: ["42 the school", "life updates"]
---
Finally, after spending 8 hours in average per day studying hard in [42 the computer science school](/blog/topics/42-the-school/)'s 26-day bootcamp, named the Piscine, and two weeks after finishing the Piscine, I succeed in passing the bootcamp, and is qualified for 42's core curriculum! 🎉
This was another major milestone for me as a developer, since despite already having experience in programming when attending the bootcamp, the Piscine's curriculum was still no walk in the park. I registered and attending the Piscine because I want to continue to improve myself as a developer, so naturally I hope I can pass the bootcamp and be qualified for 42's core programme. It was a massive relief to realise that my hard work had paid off and got recognised by the school.
At the moment, it is still uncertain when the core programme will start, because my local campus still needs a certain amount of qualified participants from multiple intakes of the Piscine, so there are enough students to start taking the core curriculum together. The campus have planned to hold a new intake of the Piscine in the coming months, so hopefully there will be more people joining us in the core programme.
Interestingly, the email the campus staff sent us to inform us of the results of our Piscine arrived at our mailbox at 4.20 pm (Malaysia Time) sharp. I am almost certain that is intentional, given that the school is literally named 42.
I had decided that regardless of my results of the Piscine, I will continue to make good use of the resources I currently have to keep my computer science and programming knowledge and skills sharp, but passing the Piscine is certainly a great motivator.
Looking forward to 42's core programme!

View File

@@ -1,32 +1,32 @@
---
title: Attended WordCamp Johor Bahru 2025
desc: I attended my city's first ever WordCamp conference.
date: 2025-05-18T10:43:00+0800
topics: ["wordcamp", "wordpress"]
---
On 17 May 2025, my city, [Johor Bahru, held its first ever WordCamp conference](https://johorbahru.wordcamp.org/2025/), and I had the privilege of attending WordPress Johor Bahru 2025, including its afterparty.
![Photo of an attendee card of WordCamp Johor Bahru 2025, written with the name Helen Chong and printed with Johor version of Wapuu, the WordPress mascot.](https://cdn.some.pics/helenchong/6829402d8e6db.jpg)
I first heard of WordCamp Johor Bahru (WCJB) in late 2024. The conference piqued my interest, because not only would be the first WordCamp in my city, but also I was working as a WordPress developer at the time, so I subscribed to WordCamp Johor Bahru's RSS feed.
Came 2025, when WCJB's blog started to announce more information about the conference, I realised that the venue where the conference was held was none other than 42 Iskandar Puteri (42IP), the local campus of [42 the computer science school](/blog/topics/42-the-school/) that I am attending.
A week before WCJB, the campus staff of 42IP gave away two tickets to WCJB to those of us who attended 42IP's bootcamp, named the Piscine, and I was lucky to be able to win the ticket giveaway. This allowed me to attend WCJB at no cost. While I had left my previous job, which was also my first developer job and where I worked in WordPress development, I still would not refuse the chance to connect with the WordPress community, and wanting the chance to connect with my local tech scene was one of the reasons I attended 42IP, so I participated in 42IP's ticket giveaway to attend WCJB.
Coincidentally, WCJB 2025 took place one day after the day when those of us who attended 42 Iskandar Puteri's Piscine got informed if we passed the bootcamp and are qualified for 42's core curriculum. [I succeed in passing the Piscine](2025-05-16-i-have-passed-42-piscine.md), which certainly helped giving me a morale boost to attend a tech event held at 42ip's campus. During WCJB 2025, it turned out two other fellow participants of 42 Iskandar Puteri's bootcamp had also attended, with one of them being another person who won the campus staff's ticket giveaway, and they both passed the bootcamp too.
Both attendees and speakers of WCJB 2025 include not only Malaysians, but also people from other countries. It was quite the experience to witness how the WordPress community expands across the world. I got to meet fellow students of 42 Iskandar Puteri and new people at WCJB, and I appreciate the experience of attending a conference, and the opportunity to learn what is going on with the WordPress community in my area.
WCJB 2025 was also when I started to actually learn about Wapuu, the WordPress mascot. WordPress Johor Bahru had published a blog post about t[he design of the Johor-inspired Wapuu](https://johorbahru.wordcamp.org/2025/meet-our-johor-inspired-wapuu/). I am a sucker for adorable mascots, so I also appreciate being able to collect Wapuu goodies, including a tote bag and stickers, along with WordPress and WCJB 2025 goodies, including a T-shirt, stickers and pins.
![Photo of a tote bag of WCJB 2025, which stands for WordCamp Johor Bahru 2025, with blue handles and the head and hands of the Johor version of Wapuu, the WordPress mascot.](https://cdn.some.pics/helenchong/6829405424ca2.jpg)
![Photo of goodies of WordCamp Johor Bahru 2025, including WordPress pins in blue and black, WordPress stickers in blue, black and rainbow colours, Johor version of Wapuu the WordPress mascot, and heart shaped-sticker of the flag of the Johor state.](https://cdn.some.pics/helenchong/6829407bf1def.jpg)
Just a few days before WCJB 2025, [Salma Alam-Naylor](https://whitep4nth3r.com/) published a new blog post about treating attending conferences as an experience, ["The experience is enough"](https://whitep4nth3r.com/blog/the-experience-is-enough/), which was a good advice that came timely, as WCJB 2025 would be the first in-person tech conference I attended.
During WCJB 2025, the talks I attended included Mayuko Moriyama's ["The Real WordCamp Happens Off the Stage: How to Build Connections and Seize Opportunities"](https://johorbahru.wordcamp.org/2025/session/the-real-wordcamp-happens-off-the-stage-how-to-build-connections-and-seize-opportunities/) and Dan Gabriel Tabifranca's ["Growing in a Community That Builds You"](https://johorbahru.wordcamp.org/2025/session/growing-in-a-community-that-builds-you/), both of which made me reflect that just being present at a community event can open up doors for growth and connections, both for yourself and your community. While those talks focused on the WordPress community, the sentiment could be applied to other communities too.
All in all, I am glad that I got to experience what is like to attend a tech conference through WordPress Johor Bahru 2025.
---
title: Attended WordCamp Johor Bahru 2025
desc: I attended my city's first ever WordCamp conference.
date: 2025-05-18T10:43:00+0800
topics: ["wordcamp", "wordpress"]
---
On 17 May 2025, my city, [Johor Bahru, held its first ever WordCamp conference](https://johorbahru.wordcamp.org/2025/), and I had the privilege of attending WordPress Johor Bahru 2025, including its afterparty.
![Photo of an attendee card of WordCamp Johor Bahru 2025, written with the name Helen Chong and printed with Johor version of Wapuu, the WordPress mascot.](https://cdn.some.pics/helenchong/6829402d8e6db.jpg)
I first heard of WordCamp Johor Bahru (WCJB) in late 2024. The conference piqued my interest, because not only would be the first WordCamp in my city, but also I was working as a WordPress developer at the time, so I subscribed to WordCamp Johor Bahru's RSS feed.
Came 2025, when WCJB's blog started to announce more information about the conference, I realised that the venue where the conference was held was none other than 42 Iskandar Puteri (42IP), the local campus of [42 the computer science school](/blog/topics/42-the-school/) that I am attending.
A week before WCJB, the campus staff of 42IP gave away two tickets to WCJB to those of us who attended 42IP's bootcamp, named the Piscine, and I was lucky to be able to win the ticket giveaway. This allowed me to attend WCJB at no cost. While I had left my previous job, which was also my first developer job and where I worked in WordPress development, I still would not refuse the chance to connect with the WordPress community, and wanting the chance to connect with my local tech scene was one of the reasons I attended 42IP, so I participated in 42IP's ticket giveaway to attend WCJB.
Coincidentally, WCJB 2025 took place one day after the day when those of us who attended 42 Iskandar Puteri's Piscine got informed if we passed the bootcamp and are qualified for 42's core curriculum. [I succeed in passing the Piscine](2025-05-16-i-have-passed-42-piscine.md), which certainly helped giving me a morale boost to attend a tech event held at 42ip's campus. During WCJB 2025, it turned out two other fellow participants of 42 Iskandar Puteri's bootcamp had also attended, with one of them being another person who won the campus staff's ticket giveaway, and they both passed the bootcamp too.
Both attendees and speakers of WCJB 2025 include not only Malaysians, but also people from other countries. It was quite the experience to witness how the WordPress community expands across the world. I got to meet fellow students of 42 Iskandar Puteri and new people at WCJB, and I appreciate the experience of attending a conference, and the opportunity to learn what is going on with the WordPress community in my area.
WCJB 2025 was also when I started to actually learn about Wapuu, the WordPress mascot. WordPress Johor Bahru had published a blog post about t[he design of the Johor-inspired Wapuu](https://johorbahru.wordcamp.org/2025/meet-our-johor-inspired-wapuu/). I am a sucker for adorable mascots, so I also appreciate being able to collect Wapuu goodies, including a tote bag and stickers, along with WordPress and WCJB 2025 goodies, including a T-shirt, stickers and pins.
![Photo of a tote bag of WCJB 2025, which stands for WordCamp Johor Bahru 2025, with blue handles and the head and hands of the Johor version of Wapuu, the WordPress mascot.](https://cdn.some.pics/helenchong/6829405424ca2.jpg)
![Photo of goodies of WordCamp Johor Bahru 2025, including WordPress pins in blue and black, WordPress stickers in blue, black and rainbow colours, Johor version of Wapuu the WordPress mascot, and heart shaped-sticker of the flag of the Johor state.](https://cdn.some.pics/helenchong/6829407bf1def.jpg)
Just a few days before WCJB 2025, [Salma Alam-Naylor](https://whitep4nth3r.com/) published a new blog post about treating attending conferences as an experience, ["The experience is enough"](https://whitep4nth3r.com/blog/the-experience-is-enough/), which was a good advice that came timely, as WCJB 2025 would be the first in-person tech conference I attended.
During WCJB 2025, the talks I attended included Mayuko Moriyama's ["The Real WordCamp Happens Off the Stage: How to Build Connections and Seize Opportunities"](https://johorbahru.wordcamp.org/2025/session/the-real-wordcamp-happens-off-the-stage-how-to-build-connections-and-seize-opportunities/) and Dan Gabriel Tabifranca's ["Growing in a Community That Builds You"](https://johorbahru.wordcamp.org/2025/session/growing-in-a-community-that-builds-you/), both of which made me reflect that just being present at a community event can open up doors for growth and connections, both for yourself and your community. While those talks focused on the WordPress community, the sentiment could be applied to other communities too.
All in all, I am glad that I got to experience what is like to attend a tech conference through WordPress Johor Bahru 2025.

View File

@@ -1,89 +1,89 @@
---
title: My Virtual Private Server Arc Has Officially Begun
desc: I have officially joined the "self-hosting on a VPS" club.
date: 2025-05-19T23:46:33+0800
updated: 2025-05-28T06:38:31+0800
topics: ["vps", "self-hosting"]
toc: true
---
It is official — not only I have a virtual private server (VPS), but also I have started self-hosting things on a VPS, including all my websites.
I had heard of the concept of self-hosting even before making my first website from scratch in 2022. When I became more aware of digital privacy, I had learned that people can host alternatives to mainstream tech services, but while I learned to switch to using more privacy-friendly software as much as possible, I did not have enough tech proficiently to self-host yet.
As I started to code my own website and learn web development, I have found myself enjoying being able to control my stuff and the tools I use more and more, so I finally started to take the step to learn self-hosting, and here I am, with my slice of a computer on the internet up and running.
## Before VPS: From Static Hosting to Shared Hosting