Merge pull request #1 from brendan-ch/feat/garden

feat/garden
This commit is contained in:
2026-08-08 22:08:52 -04:00
committed by GitHub
59 changed files with 845 additions and 422 deletions

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@ vendor/
# macOS
.DS_Store
/_includes/notes_graph.json

10
Gemfile Normal file
View File

@@ -0,0 +1,10 @@
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
gem "jekyll", "~> 4.4"
gem "jekyll-last-modified-at", git: "https://github.com/maximevaillancourt/jekyll-last-modified-at", branch: "add-support-for-files-in-git-submodules"
gem "webrick", "~> 1.9"
gem "nokogiri"

226
Gemfile.lock Normal file
View File

@@ -0,0 +1,226 @@
GIT
remote: https://github.com/maximevaillancourt/jekyll-last-modified-at
revision: b7f1164861ba589660af3dc096dcbdff75d67145
branch: add-support-for-files-in-git-submodules
specs:
jekyll-last-modified-at (1.3.2)
jekyll (>= 3.7, < 5.0)
GEM
remote: https://rubygems.org/
specs:
addressable (2.9.0)
public_suffix (>= 2.0.2, < 8.0)
base64 (0.3.0)
bigdecimal (4.1.2)
colorator (1.1.0)
concurrent-ruby (1.3.8)
csv (3.3.6)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
eventmachine (1.2.7)
ffi (1.17.4-aarch64-linux-gnu)
ffi (1.17.4-aarch64-linux-musl)
ffi (1.17.4-arm-linux-gnu)
ffi (1.17.4-arm-linux-musl)
ffi (1.17.4-arm64-darwin)
ffi (1.17.4-x86_64-darwin)
ffi (1.17.4-x86_64-linux-gnu)
ffi (1.17.4-x86_64-linux-musl)
forwardable-extended (2.6.0)
google-protobuf (4.35.1)
bigdecimal
rake (~> 13.3)
google-protobuf (4.35.1-aarch64-linux-gnu)
bigdecimal
rake (~> 13.3)
google-protobuf (4.35.1-aarch64-linux-musl)
bigdecimal
rake (~> 13.3)
google-protobuf (4.35.1-arm64-darwin)
bigdecimal
rake (~> 13.3)
google-protobuf (4.35.1-x86_64-darwin)
bigdecimal
rake (~> 13.3)
google-protobuf (4.35.1-x86_64-linux-gnu)
bigdecimal
rake (~> 13.3)
google-protobuf (4.35.1-x86_64-linux-musl)
bigdecimal
rake (~> 13.3)
http_parser.rb (0.8.1)
i18n (1.15.2)
concurrent-ruby (~> 1.0)
jekyll (4.4.1)
addressable (~> 2.4)
base64 (~> 0.2)
colorator (~> 1.0)
csv (~> 3.0)
em-websocket (~> 0.5)
i18n (~> 1.0)
jekyll-sass-converter (>= 2.0, < 4.0)
jekyll-watch (~> 2.0)
json (~> 2.6)
kramdown (~> 2.3, >= 2.3.1)
kramdown-parser-gfm (~> 1.0)
liquid (~> 4.0)
mercenary (~> 0.3, >= 0.3.6)
pathutil (~> 0.9)
rouge (>= 3.0, < 5.0)
safe_yaml (~> 1.0)
terminal-table (>= 1.8, < 4.0)
webrick (~> 1.7)
jekyll-sass-converter (3.1.0)
sass-embedded (~> 1.75)
jekyll-watch (2.2.1)
listen (~> 3.0)
json (2.21.2)
kramdown (2.5.2)
rexml (>= 3.4.4)
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.4)
listen (3.10.0)
logger
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.7.0)
mercenary (0.4.0)
nokogiri (1.19.4-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.19.4-aarch64-linux-musl)
racc (~> 1.4)
nokogiri (1.19.4-arm-linux-gnu)
racc (~> 1.4)
nokogiri (1.19.4-arm-linux-musl)
racc (~> 1.4)
nokogiri (1.19.4-arm64-darwin)
racc (~> 1.4)
nokogiri (1.19.4-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.19.4-x86_64-linux-gnu)
racc (~> 1.4)
nokogiri (1.19.4-x86_64-linux-musl)
racc (~> 1.4)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (7.0.5)
racc (1.8.1)
rake (13.4.2)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
rexml (3.4.4)
rouge (4.7.0)
safe_yaml (1.0.5)
sass-embedded (1.102.0-aarch64-linux-gnu)
google-protobuf (~> 4.31)
sass-embedded (1.102.0-aarch64-linux-musl)
google-protobuf (~> 4.31)
sass-embedded (1.102.0-arm-linux-gnueabihf)
google-protobuf (~> 4.31)
sass-embedded (1.102.0-arm-linux-musleabihf)
google-protobuf (~> 4.31)
sass-embedded (1.102.0-arm64-darwin)
google-protobuf (~> 4.31)
sass-embedded (1.102.0-x86_64-darwin)
google-protobuf (~> 4.31)
sass-embedded (1.102.0-x86_64-linux-gnu)
google-protobuf (~> 4.31)
sass-embedded (1.102.0-x86_64-linux-musl)
google-protobuf (~> 4.31)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
unicode-display_width (2.6.0)
webrick (1.9.2)
PLATFORMS
aarch64-linux-gnu
aarch64-linux-musl
arm-linux-gnu
arm-linux-gnueabihf
arm-linux-musl
arm-linux-musleabihf
arm64-darwin
x86_64-darwin
x86_64-linux-gnu
x86_64-linux-musl
DEPENDENCIES
jekyll (~> 4.4)
jekyll-last-modified-at!
nokogiri
webrick (~> 1.9)
CHECKSUMS
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
bundler (4.0.13) sha256=19f08be7f27022cf0b89f27da0b044ae075e8270a9ef44ad248a932614e1ca3b
colorator (1.1.0) sha256=e2f85daf57af47d740db2a32191d1bdfb0f6503a0dfbc8327d0c9154d5ddfc38
concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
csv (3.3.6) sha256=aba61e7e507a66f03d45cb1f3c4b6359861c3504038b422962875dce099e4456
em-websocket (0.5.3) sha256=f56a92bde4e6cb879256d58ee31f124181f68f8887bd14d53d5d9a292758c6a8
eventmachine (1.2.7) sha256=994016e42aa041477ba9cff45cbe50de2047f25dd418eba003e84f0d16560972
ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39
ffi (1.17.4-arm-linux-gnu) sha256=d6dbddf7cb77bf955411af5f187a65b8cd378cb003c15c05697f5feee1cb1564
ffi (1.17.4-arm-linux-musl) sha256=9d4838ded0465bef6e2426935f6bcc93134b6616785a84ffd2a3d82bc3cf6f95
ffi (1.17.4-arm64-darwin) sha256=19071aaf1419251b0a46852abf960e77330a3b334d13a4ab51d58b31a937001b
ffi (1.17.4-x86_64-darwin) sha256=aa70390523cf3235096cf64962b709b4cfbd5c082a2cb2ae714eb0fe2ccda496
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
forwardable-extended (2.6.0) sha256=1bec948c469bbddfadeb3bd90eb8c85f6e627a412a3e852acfd7eaedbac3ec97
google-protobuf (4.35.1) sha256=a3a6471331d918f58dfa4d014a8f6286f0af2cf4840216bde52fcf2ea3fe3726
google-protobuf (4.35.1-aarch64-linux-gnu) sha256=50ca44d0eeff3f8475e630a1accdd974256f3510694d574e2c9d6119ea8bc9e1
google-protobuf (4.35.1-aarch64-linux-musl) sha256=d5c65cef6bd6498a9e5ed5f88cf6cf7e341c10b0a005e32137d5d1a2b6e8c18a
google-protobuf (4.35.1-arm64-darwin) sha256=d9c957df04fa89c749fa9a72a7b383eb4296efc9b2303dc6fd6fbe39c698ad6b
google-protobuf (4.35.1-x86_64-darwin) sha256=66b62b4df00931018a692806df66393efa960d6d2b7da69735187249f950d3ee
google-protobuf (4.35.1-x86_64-linux-gnu) sha256=c786439087512a3fbd199e9897d265b855f951d4027e218ea55e858d45969edd
google-protobuf (4.35.1-x86_64-linux-musl) sha256=91890eb0002934a339fdb7d77a147c46b7474b6799db27872b747b905837f744
http_parser.rb (0.8.1) sha256=9ae8df145b39aa5398b2f90090d651c67bd8e2ebfe4507c966579f641e11097a
i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
jekyll (4.4.1) sha256=4c1144d857a5b2b80d45b8cf5138289579a9f8136aadfa6dd684b31fe2bc18c1
jekyll-last-modified-at (1.3.2)
jekyll-sass-converter (3.1.0) sha256=83925d84f1d134410c11d0c6643b0093e82e3a3cf127e90757a85294a3862443
jekyll-watch (2.2.1) sha256=bc44ed43f5e0a552836245a54dbff3ea7421ecc2856707e8a1ee203a8387a7e1
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
kramdown (2.5.2) sha256=1ba542204c66b6f9111ff00dcc26075b95b220b07f2905d8261740c82f7f02fa
kramdown-parser-gfm (1.1.0) sha256=fb39745516427d2988543bf01fc4cf0ab1149476382393e0e9c48592f6581729
liquid (4.0.4) sha256=4fcfebb1a045e47918388dbb7a0925e7c3893e58d2bd6c3b3c73ec17a2d8fdb3
listen (3.10.0) sha256=c6e182db62143aeccc2e1960033bebe7445309c7272061979bb098d03760c9d2
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
mercenary (0.4.0) sha256=b25a1e4a59adca88665e08e24acf0af30da5b5d859f7d8f38fba52c28f405138
nokogiri (1.19.4-aarch64-linux-gnu) sha256=1269fb644a6de405057a53dd5c762b1209b43ca7424f839454d3dbc677c31a8f
nokogiri (1.19.4-aarch64-linux-musl) sha256=35c65b9ce72b3bb03207bdbe7067915019dc18c1b9b59139684bd6690fdd01af
nokogiri (1.19.4-arm-linux-gnu) sha256=a301313e38bb065d68239e79734bcd6f56fb6efaacebde29e9abf2a4735340ca
nokogiri (1.19.4-arm-linux-musl) sha256=588923c101bcfa78869734d247d25b598674323e7f22474fc468f6e5647311eb
nokogiri (1.19.4-arm64-darwin) sha256=a46db9853286e6597b36ebc6953817d15acf3a299583eb3f89fdc6f91dd63527
nokogiri (1.19.4-x86_64-darwin) sha256=7fd17057d3e1f00e9954a74b3cd76595d3d4a5ef233b7ed9599047c204f70551
nokogiri (1.19.4-x86_64-linux-gnu) sha256=379fae440b28915e3f19d752ce2dcf8465ed2b2fbefd2a7ca0dd497bc981a06a
nokogiri (1.19.4-x86_64-linux-musl) sha256=17dfb7c1fa194ae02fbf7c51a7afc8d278045ab3fdacfd86f91d02d7b274470b
pathutil (0.16.2) sha256=e43b74365631cab4f6d5e4228f812927efc9cb2c71e62976edcb252ee948d589
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe
rb-inotify (0.11.1) sha256=a0a700441239b0ff18eb65e3866236cd78613d6b9f78fea1f9ac47a85e47be6e
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
rouge (4.7.0) sha256=dba5896715c0325c362e895460a6d350803dbf6427454f49a47500f3193ea739
safe_yaml (1.0.5) sha256=a6ac2d64b7eb027bdeeca1851fe7e7af0d668e133e8a88066a0c6f7087d9f848
sass-embedded (1.102.0-aarch64-linux-gnu) sha256=087f377fa4135a8f67f5f422781b59815e99b8fd3aec335935c50a47c2a205fd
sass-embedded (1.102.0-aarch64-linux-musl) sha256=12bb4091972499241c35304ca11ca2c314764badf1b9933a8d437ba62432b538
sass-embedded (1.102.0-arm-linux-gnueabihf) sha256=566b1188e4f923366f494a5d95e4e5e03864ae55bae5ea52beb2c30b1b9e2915
sass-embedded (1.102.0-arm-linux-musleabihf) sha256=1ca396610bf4f4aa8f849b67a9f2720f67d9f3b42651072eafee786aece314cf
sass-embedded (1.102.0-arm64-darwin) sha256=8e18b2dd94b232c2dd73984c579f77bbc1a066028f4675c8989ac34fa40d14dc
sass-embedded (1.102.0-x86_64-darwin) sha256=3e78c6ac5be0ba4236d075bd044a49d0f515d54799f7d6f253c71d325ffedac0
sass-embedded (1.102.0-x86_64-linux-gnu) sha256=9815b7604123a9a44a65d44d5e58e7af1c7005174b745cda83bad4c9e3760ab3
sass-embedded (1.102.0-x86_64-linux-musl) sha256=9e3c15eb5d83784adf3b946d4fc5d8a32aed25566a2fee939f25dd1e1d217ddf
terminal-table (3.0.2) sha256=f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91
unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a
webrick (1.9.2) sha256=beb4a15fc474defed24a3bda4ffd88a490d517c9e4e6118c3edce59e45864131
BUNDLED WITH
4.0.13

View File

@@ -1,3 +1,16 @@
# brendan-ch.github.io
This is my personal webpage built using Jekyll and GitHub Pages.
It is based on the [digital-garden-jekyll-template](https://github.com/maximevaillancourt/digital-garden-jekyll-template) by Maxime
Vaillancourt. The MIT license of the project:
```
Copyright (c) 2020 Maxime Vaillancourt.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```

20
_config.yml Normal file
View File

@@ -0,0 +1,20 @@
exclude: [ '_includes/notes_graph.json' ]
plugins:
- jekyll-last-modified-at
open_external_links_in_new_tab: true
use_html_extension: false
permalink: pretty
relative_permalinks: false
collections:
notes:
output: true
permalink: /:slug/
defaults:
- scope:
path: "_notes/**/*.md"
values:
layout: "note"

View File

@@ -1,20 +1,9 @@
<nav class="navbar">
<a href="/" class="nav-logo">
<img src="/assets/web-app-manifest-512x512.png" alt="Brendan Chen home" class="nav-logo-icon">
<a href="/" class="nav-title">
<p>
<b>
Brendan Chen
</b>
</p>
</a>
<input type="checkbox" id="hamburger-toggle" class="hamburger-toggle">
<label for="hamburger-toggle" class="hamburger">
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
</label>
<label for="hamburger-toggle" class="menu-overlay"></label>
<ul class="nav-menu">
<li class="nav-item">
<a href="/" class="nav-link">Home</a>
</li>
<li class="nav-item">
<a href="/projects" class="nav-link">Projects</a>
</li>
</ul>
</nav>

View File

@@ -4,14 +4,15 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script defer src="https://sassy-fulmar.pikapod.net/script.js" data-website-id="479f6086-eaaf-46c8-a365-78a53a066682"></script>
<script defer src="https://sassy-fulmar.pikapod.net/script.js"
data-website-id="479f6086-eaaf-46c8-a365-78a53a066682"></script>
<link rel="icon" type="image/png" href="/assets/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg" />
<link rel="shortcut icon" href="/assets/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-title" content="Brendan Chen" />
<link rel="manifest" href="/assets/site.webmanifest" />
<link rel="icon" type="image/png" href="/assets/favicon-96x96.png" sizes="96x96"/>
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg"/>
<link rel="shortcut icon" href="/assets/favicon.ico"/>
<link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png"/>
<meta name="apple-mobile-web-app-title" content="Brendan Chen"/>
<link rel="manifest" href="/assets/site.webmanifest"/>
<link rel="stylesheet" href="/assets/css/global.css">
<meta name="theme-color" content="#f4f4f4">
@@ -19,16 +20,19 @@
<title>{{ page.title }}</title>
</head>
<body>
<header>
{% include navigation.html %}
</header>
<main>
{{ content }}
</main>
<footer>
copyright 2025 Brendan Chen |
<header>
{% include navigation.html %}
</header>
<main>
{{ content }}
</main>
<footer>
<p>
<!-- see https://stackoverflow.com/questions/12464656/get-todays-date-in-jekyll-with-liquid-markup -->
copyright {{ site.time | date: '%Y' }} Brendan Chen |
<a target="_blank" href="https://github.com/brendan-ch">GitHub</a> |
<a target="_blank" href="https://linkedin.com/in/brendan-ch">LinkedIn</a> | e: me [at] bchen.dev
</footer>
</p>
</footer>
</body>
</html>

26
_layouts/note.html Normal file
View File

@@ -0,0 +1,26 @@
---
layout: default
---
<h1>{{ page.title }}</h1>
{{ content }}
<br />
<hr class="backlink-divider">
<div class="backlink-box">
{% if page.backlinks.size > 0 %}
<h3>Notes mentioning this note</h3>
<div>
{% for backlink in page.backlinks %}
<div class="backlinks">
<a class="internal-link" href="{{ site.baseurl }}{{ backlink.url }}{%- if site.use_html_extension -%}.html{%- endif -%}">{{ backlink.title }}</a><br>
</div>
{% endfor %}
</div>
{% endif %}
</div>
<br />

6
_layouts/page_moved.html Normal file
View File

@@ -0,0 +1,6 @@
---
layout: default
---
<h1>Page moved</h1>
<p>This page has been moved. {{ content }}</p>

26
_notes/cmes-mobile.md Normal file
View File

@@ -0,0 +1,26 @@
---
title: CMES Mobile App
---
![cmes-mobile-app.png](../assets/project/cmes-mobile-app.png "Screenshots of the CMES app, showing the My Library screen and a web view.")
**App development project for volunteer organization Techies Without Borders.**
I helped implement the CMES mobile app, facilitating the download, viewing and
playback of medical education content in resource-constrained countries.
In the early stages, I built the download screen and business logic, the PDF
viewer integration, and background audio playback using native code. Later, I
built the native web view to integrate with
the [CMES-Pi](https://cmesworld.org/cmes_product-Pi.html) solution, simplifying
the business logic while adding support for future edge platforms.
The app's front-end uses Capacitor and Angular, with several Capacitor plugins
in Swift + Java to bundle native functionality.
This app is actively used in medical facilities in Uganda and Nepal, to name a
few.
- [App Store](https://apps.apple.com/us/app/cmes/id1276335909)
- [Google Play](https://play.google.com/store/apps/details?id=org.cmesworld.www.cmes_piandroid_app)
- [Techies Without Borders](https://techieswithoutborders.us/)
- [CMES Project](https://cmesworld.org/)

View File

@@ -1,6 +1,5 @@
---
title: Don't Wake Sandie
layout: project
---
<iframe style="width: 100%; height: 400px; border-radius: 1px; pointer-events: auto; background-color: white;" src="https://www.linkedin.com/embed/feed/update/urn:li:activity:7274629268624551936?compact=true" frameborder="0" sandbox="allow-scripts allow-popups allow-top-navigation-by-user-activation allow-forms allow-same-origin allow-storage-access-by-user-activation allow-popups-to-escape-sandbox" allowfullscreen=""></iframe>
@@ -17,5 +16,5 @@ UI development, artwork integration, and version control. There were so many
moving parts, and I really aimed to get as much input for the gameplay design
as I could from the team.
[itch.io (play the game!)](https://bchendev.itch.io/dont-wake-sandie){: .primary-action }
{: .horizontal-wrapper }
[Play the game on itch.io!](https://bchendev.itch.io/dont-wake-sandie)

24
_notes/index.md Normal file
View File

@@ -0,0 +1,24 @@
---
title: "Brendan Chen"
layout: default
isIndexPage: 1
permalink: /
---
I'm Brendan, a recently graduated Software Engineering student who attended
Chapman University. I'm currently an associate software engineer at Cargill. I'm
also the creator of [OpenAppLock](https://github.com/brendan-ch/OpenAppLock), an
open-source time blocker for iOS,
and [Interchange](https://interchange.bchen.dev), a free shuttle app for Chapman
students.
I believe that no matter which path an app's development takes, it should be
grounded in real experiences.
Feel free to [say hi](mailto:brendan@bchen.dev)!
- [[Projects]]
- [View my resume](/assets/resume.pdf)
![Picture of me taken at the Senso-ji Buddhist Temple in Asakusa, Tokyo, Japan](../assets/me-1.png "Picture of me taken at the Senso-ji Buddhist Temple in Asakusa, Tokyo, Japan")

15
_notes/interchange.md Normal file
View File

@@ -0,0 +1,15 @@
---
title: Interchange
---
![interchange-screenshots.png](../assets/project/interchange-screenshots.png "Screenshots of Interchange.")
**An iOS app for college transit, currently available for Chapman University.**
View routes, shuttle locations, and estimated arrival times in a sleek,
easy-to-use interface. I built this app using SwiftUI, Node.js and Apollo
GraphQL, with Redis as a primary storage mechanism.
- [Download on the App Store](https://apps.apple.com/us/app/interchange-college-commute/id6739968742)
- [Join the TestFlight](https://testflight.apple.com/join/HbuHfUgW)
- [Website](https://interchange.bchen.dev/)

View File

@@ -0,0 +1,26 @@
---
title: Power to the People
---
![power-to-the-people.png](../assets/project/power-to-the-people.png "Screenshots of Power to the People, an iOS app designed to make traffic stops safer.")
**An iPhone app designed to make traffic stops safer for marginalized
communities.** Record interactions with police, assign and text emergency
contacts, and be informed of your rights. I worked in a student team of 4,
including [David Sohn](https://www.linkedin.com/in/sohn-david/), [Jacob Zamore](https://www.linkedin.com/in/jacob-zamore/),
and [Patrick Smith](https://www.linkedin.com/in/patrick-smith-ld/), to prototype
and build a proof of concept in 36 hours. The app was selected for the *
*President's Pick award** at [CruzHacks 2024](https://cruzhacks.com/), the
premier hackathon of UC Santa Cruz.
- [GitHub](https://github.com/brendan-ch/PowerToThePeople)
- [Devpost](https://devpost.com/software/power-to-the-people-lbhxyd)
My contribution was the iOS app prototype, of which a demo is shown below. I
built the primary user flow of recording a police interaction, simultaneously
using the front and back iPhone cameras to capture all angles. Activating the
recording also displays the "Know Your Rights" screen, displaying basic dos and
don'ts. Finally, I built a way to view previous recordings and share the files
using the iOS share sheet.
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/Un8JWXkpcW4?si=CfQ0qOuUjSpas0ny&amp;start=89" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

View File

@@ -1,42 +1,44 @@
---
title: Projects
layout: default
---
# Projects
## Games
- [Resonance](/projects/resonance)
- [[Resonance]]
- **IEEE GameSig Finalist**, 2026
- A cyberpunk-themed game show shooter.
- [Don't Wake Sandie](/projects/dont-wake-sandie)
- [[Don't Wake Sandie]]
- A game about not waking up a beloved hungover corgi
## Personal
- [Interchange](/projects/interchange)
- [[Interchange]]
- An iOS app for college transit, now on the App Store!
- [Timestamp](/projects/timestamp)
- [[Timestamp]]
- An unintrusive, open source Pomodoro timer for macOS.
- [Sourcebook](/projects/sourcebook)
- [[Sourcebook]]
- A lightweight content management system (CMS) for class websites.
## Hackathons
- [teech.ai](/projects/teech-ai-cal-hacks-12)
- [[teech.ai]]
- Cal Hacks 12.0
- A tutoring app to make education more accessible, built with SwiftUI and Gemini Vision API.
- [Power to the People](/projects/power-to-the-people)
- A tutoring app to make education more accessible, built with SwiftUI and
Gemini Vision API.
- [[Power to the People]]
- **President's Pick**, CruzHacks 2024
- An iOS app designed to make traffic stops safer for marginalized communities.
- An iOS app designed to make traffic stops safer for marginalized
communities.
## Work
- [The Farmer's Dog Trivia Game](/projects/the-farmers-dog-trivia)
- A Windows kiosk application designed to connect customers with the Farmer's Dog experience.
- [[The Farmer's Dog Trivia Game]]
- A Windows kiosk application designed to connect customers with the
Farmer's Dog experience.
## Community
- [CMES Mobile App](/projects/cmes-mobile/)
- One of my earliest projects. A cross-platform mobile app to deliver educational medical content to end users, part of [Techies Without Borders](https://techieswithoutborders.us/).
- [[CMES Mobile App]]
- One of my earliest projects. A cross-platform mobile app to deliver
educational medical content to end users, part
of [Techies Without Borders](https://techieswithoutborders.us/).

40
_notes/resonance.md Normal file
View File

@@ -0,0 +1,40 @@
---
title: Resonance
---
![resonance-screenshot.png](../assets/project/resonance-screenshot.png "Screenshot of Resonance")
Resonance is a cyberpunk-themed game show shooter. Enter the arena, blast your
opponents, and become the star of the show. I worked with a student team of 12
to create this game over multiple months. The game was selected as an **IEEE
GameSig finalist**.
*Steam link coming soon!*
My core role was to implement the game's networking logic using PurrNet, an
open-source library for Unity.
My first project was to implement networking for **Arena**, a free-for-all PVP
game mode, where the player with the most eliminations wins. I learned how to
isolate game logic on the server and synchronize state on each client. The
networked API for Arena is used by every aspect of the game, including the
scoreboard UI and spotlight mechanic. Following a pivot in the games core
direction, I successfully handed this system to another engineer, who
implemented a rating system for style points.
Another project was the **build system**. Having a server dependency and Steam
integration meant that the environment differed from development to production.
We found it painful to prepare a build for each class-mandated playtest. With
the help of Claude, I created a two-step system: an editor script to build and
configure dependencies in one step, and a nightly GitHub Actions pipeline.
Besides simplifying the build process, this created another positive side effect
by ensuring we fixed compiler issues proactively.
I'm currently working on a client-side prediction model to match other FPS games
on the market.
Even with our team size, the most important thing I learned was how to manage
scope and scale. Each one of us pushed ourselves to the limits of our knowledge,
going from bare theoretical understanding to practical working implementations.
The goal was never to get things right on the first try; it was to learn and
realize new ideas in multiple passes.

View File

@@ -1,9 +1,8 @@
---
title: Sourcebook
layout: project
---
![sourcebook.png](assets/sourcebook.png "Screenshot of Sourcebook, a lightweight CMS for class websites.")
![sourcebook.png](../assets/project/sourcebook.png "Screenshot of Sourcebook, a lightweight CMS for class websites.")
**A lightweight content management system (CMS) designed to build class
websites.** I designed it to be a self-hosted alternative for popular apps like
@@ -13,5 +12,5 @@ I designed an entity-relationship diagram, MySQL database schema, and HTML/CSS
high-fidelity prototype to support the project and show to professors. I then
built a lightweight full-stack application using Python, Flask, and MySQL.
[GitHub](https://github.com/brendan-ch/sourcebook){: .primary-action }
{: .horizontal-wrapper }
[GitHub](https://github.com/brendan-ch/sourcebook)

24
_notes/tad-pool.md Normal file
View File

@@ -0,0 +1,24 @@
---
title: Tad-Pool
---
![tad-pool.png](../assets/project/tad-pool.png "Screenshots of Tad-Pool, a carpooling app for college students.")
**A mobile app designed to make carpooling easier for college students.** Find
carpools based on time and location, and get rewarded for helping to reduce CO2
emissions. I worked
with [Jazmine Stanek](https://www.linkedin.com/in/vjazminestanek/)
and [Isabelle Woo](https://www.linkedin.com/in/isabellewoo/) to create the UI/UX
design using Figma and FigJam, in a period of 24 hours for
[FullyBeyond 2024](https://fullybeyond.acmcsuf.com/).
The project reached the finalist judging stage.
- [High-fidelity design](https://www.figma.
com/design/nTKCcwftKrg27wlP5mmy9e/Tad-Pool-Design?node-id=13-1837&node-type=canvas&t=YKuVvM6RAPs4hTIo-11)
- [Low-fidelity design](https://www.figma.
com/design/nTKCcwftKrg27wlP5mmy9e/Tad-Pool-Design?node-id=0-1&node-type=canvas&t=YKuVvM6RAPs4hTIo-11)
- [FigJam board](https://www.figma.
com/board/Dct7GuhKFYq8maw0KoAVBV/Product-Brainstorm-FullyBeyond?node-id=0-1&node-type=canvas&t=g7RKLLo5IoU84ndk-11)
- [Devpost](https://devpost.com/software/tad-pool)

View File

@@ -0,0 +1,29 @@
---
title: teech.ai
---
<iframe width="100%" height="480" src="https://www.youtube-nocookie.com/embed/luVIdLB9id0?si=1sTE9Z3MeyWntRmd" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
**A proof-of-concept tutoring app for students learning math.** Using the phone
camera, the app sees and understands math problems, guiding students through
problems with hints and step-by-step questions. We added text-to-speech (TTS) to
give our app a voice, further engaging students and making it feel like a more
personal experience.
[Tri Vu](https://www.linkedin.com/in/tri-q-vu/) built a web interface prototype
and Python-based backend utilizing the Gemini Vision API and Fish Audio for TTS.
I built a native proof-of-concept using SwiftUI, AVFoundation, and other native
frameworks. In the app, I built a chat and camera interface, integrating with
Tri's backend by sending image and prompt data. Swiping to the right reveals the
user's chat history, which is also continuously synced with the backend.
Design-wise, I decided to use this gesture-based approach in order to bring the
camera interface front and center.
The app was built in 48 hours for **Cal Hacks 12.0**, the world's largest
collegiate hackathon.
- [GitHub](https://github.com/brendan-ch/teecher-ios)
- [Devpost](https://devpost.com/software/teech-ai)
- [Cal Hacks](https://www.calhacks.io)

View File

@@ -1,19 +1,18 @@
---
title: The Farmer's Dog Trivia Game
layout: project
---
![farmers-dog.png](assets/farmers-dog.png)
![farmers-dog.png](../assets/project/farmers-dog.png)
**A Windows kiosk application designed to connect customers with the Farmer's
Dog experience.** Play the interactive trivia game, score points, and win swag!
I worked with [Walter Cheng](https://www.linkedin.com/in/walter-j-cheng/) to
plan out dependencies and requirements, set up the codebase, and implement the
screens for the app using React Native for Windows.
screens for the app using React Native for Windows.
"Ive worked with Brendan for many years, and he is the first person I turn to
for web/app development. Hes knowledgeable and efficient, and his task
management skills make him an ideal project member." — Walter Cheng, project overseer
management skills make him an ideal project member." — Walter Cheng, project
overseer
[The Farmers Dog](https://thefarmersdog.com/){: .primary-action }
{: .horizontal-wrapper }
[The Farmers Dog](https://thefarmersdog.com/)

11
_notes/timestamp.md Normal file
View File

@@ -0,0 +1,11 @@
---
title: Timestamp
---
![Screenshot of the Timestamp app and the settings window.](../assets/project/timestamp-screenshot.png)
A simple, unintrusive Pomodoro timer for macOS which lives in the menu bar. I
built it with SwiftUI and made it open source.
[Download the app](https://github.com/brendan-ch/timestamp/releases)
[View the code](https://github.com/brendan-ch/timestamp)

View File

@@ -0,0 +1,111 @@
# frozen_string_literal: true
class BidirectionalLinksGenerator < Jekyll::Generator
def generate(site)
graph_nodes = []
graph_edges = []
all_notes = site.collections['notes'].docs
all_pages = site.pages
all_docs = all_notes + all_pages
link_extension = !!site.config["use_html_extension"] ? '.html' : ''
# Convert all Wiki/Roam-style double-bracket link syntax to plain HTML
# anchor tag elements (<a>) with "internal-link" CSS class
all_docs.each do |current_note|
all_docs.each do |note_potentially_linked_to|
note_title_regexp_pattern = Regexp.escape(
File.basename(
note_potentially_linked_to.basename,
File.extname(note_potentially_linked_to.basename)
)
).gsub('\_', '[ _]').gsub('\-', '[ -]').capitalize
title_from_data = note_potentially_linked_to.data['title']
if title_from_data
title_from_data = Regexp.escape(title_from_data)
end
new_href = "#{site.baseurl}#{note_potentially_linked_to.url}#{link_extension}"
anchor_tag = "<a class='internal-link' href='#{new_href}'>\\1</a>"
# Replace double-bracketed links with label using note title
# [[A note about cats|this is a link to the note about cats]]
current_note.content.gsub!(
/\[\[#{note_title_regexp_pattern}\|(.+?)(?=\])\]\]/i,
anchor_tag
)
# Replace double-bracketed links with label using note filename
# [[cats|this is a link to the note about cats]]
current_note.content.gsub!(
/\[\[#{title_from_data}\|(.+?)(?=\])\]\]/i,
anchor_tag
)
# Replace double-bracketed links using note title
# [[a note about cats]]
current_note.content.gsub!(
/\[\[(#{title_from_data})\]\]/i,
anchor_tag
)
# Replace double-bracketed links using note filename
# [[cats]]
current_note.content.gsub!(
/\[\[(#{note_title_regexp_pattern})\]\]/i,
anchor_tag
)
end
# At this point, all remaining double-bracket-wrapped words are
# pointing to non-existing pages, so let's turn them into disabled
# links by greying them out and changing the cursor
current_note.content = current_note.content.gsub(
/\[\[([^\]]+)\]\]/i, # match on the remaining double-bracket links
<<~HTML.delete("\n") # replace with this HTML (\\1 is what was inside the brackets)
<span title='There is no note that matches this link.' class='invalid-link'>
<span class='invalid-link-brackets'>[[</span>
\\1
<span class='invalid-link-brackets'>]]</span></span>
HTML
)
end
# Identify note backlinks and add them to each note
all_notes.each do |current_note|
# Nodes: Jekyll
notes_linking_to_current_note = all_notes.filter do |e|
e.url != current_note.url && e.content.include?(current_note.url)
end
# Nodes: Graph
graph_nodes << {
id: note_id_from_note(current_note),
path: "#{site.baseurl}#{current_note.url}#{link_extension}",
label: current_note.data['title'],
} unless current_note.path.include?('_notes/index.html')
# Edges: Jekyll
current_note.data['backlinks'] = notes_linking_to_current_note
# Edges: Graph
notes_linking_to_current_note.each do |n|
graph_edges << {
source: note_id_from_note(n),
target: note_id_from_note(current_note),
}
end
end
File.write('_includes/notes_graph.json', JSON.dump({
edges: graph_edges,
nodes: graph_nodes,
}))
end
def note_id_from_note(note)
note.data['title'].bytes.join
end
end

View File

@@ -0,0 +1,18 @@
# frozen_string_literal: true
EMPTY_FRONT_MATTER = <<~JEKYLL
---
---
JEKYLL
# Inject empty front matter in notes that don't have any
Jekyll::Hooks.register :site, :after_init do |site|
Dir.glob(site.collections['notes'].relative_directory + '/**/*.md').each do |filename|
raw_note_content = File.read(filename)
unless raw_note_content.start_with?('---')
raw_note_content.prepend(EMPTY_FRONT_MATTER)
File.write(filename, raw_note_content)
end
end
end

View File

@@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'fileutils'
require 'pathname'
require 'jekyll-last-modified-at'
module Recents
# Generate change information for all markdown pages
class Generator < Jekyll::Generator
def generate(site)
items = site.collections['notes'].docs
items.each do |page|
timestamp = Jekyll::LastModifiedAt::Determinator.new(site.source, page.path, '%FT%T%:z').to_s
page.data['last_modified_at_timestamp'] = timestamp
end
end
end
end

View File

@@ -0,0 +1,18 @@
# frozen_string_literal: true
# Turns ==something== in Markdown to <mark>something</mark> in output HTML
Jekyll::Hooks.register [:notes], :pre_render do |doc|
replace(doc)
end
Jekyll::Hooks.register [:pages], :pre_render do |doc|
# jekyll considers anything at the root as a page,
# we only want to consider actual pages
next unless doc.path.start_with?('_pages/')
replace(doc)
end
def replace(doc)
doc.content.gsub!(/==+([^ ](.*?)?[^ .=])==+/, "<mark>\\1</mark>")
end

View File

@@ -0,0 +1,28 @@
# If the configuration sets `open_external_links_in_new_tab` to a truthy value,
# add 'target=_blank' to anchor tags that don't have `internal-link` class
# frozen_string_literal: true
require 'nokogiri'
Jekyll::Hooks.register [:notes], :post_convert do |doc|
convert_links(doc)
end
Jekyll::Hooks.register [:pages], :post_convert do |doc|
# jekyll considers anything at the root as a page,
# we only want to consider actual pages
next unless doc.path.start_with?('_pages/')
convert_links(doc)
end
def convert_links(doc)
open_external_links_in_new_tab = !!doc.site.config["open_external_links_in_new_tab"]
if open_external_links_in_new_tab
parsed_doc = Nokogiri::HTML::DocumentFragment.parse(doc.content)
parsed_doc.css("a:not(.internal-link):not(.footnote):not(.reversefootnote)").each do |link|
link.set_attribute('target', '_blank')
end
doc.content = parsed_doc.inner_html
end
end

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,3 +1,18 @@
:root {
--background-color: #f4f4f4;
--color-background-1: #f4f4f4;
--color-background-2: #e4e4e4;
--color-primary-1: #000000;
--color-accent: #008891;
--color-accent-hover: #539397;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background-1: #171616;
--color-background-2: #222222;
--color-primary-1: #eeeeee;
--color-accent: #00B7C3;
--color-accent-hover: #70c1c6;
}
}

View File

@@ -1,36 +0,0 @@
/* Custom classes for styling elements on a page, such as links */
.horizontal-wrapper {
display: flex;
flex-direction: row;
gap: 8px;
align-items: flex-start;
}
.primary-action {
background: black;
padding: 0.8em;
color: white;
text-decoration-line: unset;
border-radius: 4px;
border-style: solid;
border-width: 1px;
border-color: black;
box-sizing: border-box;
}
.secondary-action {
border-width: 1px;
border-style: solid;
border-color: black;
padding: 0.8em;
text-decoration-line: unset;
border-radius: 4px;
box-sizing: border-box;
}
@media screen and (max-width: 768px) {
.horizontal-wrapper {
flex-direction: column;
}
}

View File

@@ -1,15 +1,15 @@
@import url('fonts.css');
@import url('navigation.css');
@import url('colors.css');
@import url('content.css');
@import url('note.css');
html {
background-color: var(--background-color);
background-color: var(--color-background-1);
}
body {
font-family: 'FormaDJRText', sans-serif;
color: black;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, Ubuntu, sans-serif;
color: var(--color-primary-1);
display: flex;
flex-direction: column;
@@ -35,12 +35,14 @@ footer {
width: 100%;
padding-bottom: 32px;
padding-top: 64px;
color: dimgray;
}
a {
color: unset;
color: var(--color-accent);
}
a:hover {
color: var(--color-accent-hover);
}
/* Improve readability for smaller text, especially in long paragraphs */
@@ -48,8 +50,8 @@ p,
a,
ul,
ol {
letter-spacing: 0.2px;
line-height: 24px;
line-height: 1.6em;
font-size: 18px;
}
h1,
@@ -58,23 +60,23 @@ h3,
h4,
h5,
h6 {
margin-top: 2em;
margin-top: 1em;
margin-bottom: 0.5em;
font-family: 'FormaDJRDisplay', sans-serif;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, Ubuntu, sans-serif;
}
h1 {
font-size: 3em;
}
h2 {
font-size: 2em;
}
h3 {
h2 {
font-size: 1.5em;
}
h3 {
font-size: 1.2em;
}
img {
max-width: 100%;
}

View File

@@ -10,16 +10,13 @@
height: 64px;
}
.nav-logo {
display: flex;
align-items: center;
.nav-title {
text-decoration: none;
color: black;
color: unset;
}
.nav-logo-icon {
height: 48px;
width: 48px;
.nav-title:hover {
color: unset;
}
.nav-menu {
@@ -29,134 +26,3 @@
padding: 0;
gap: 32px;
}
.nav-item {
display: flex;
}
.nav-link {
text-decoration: none;
color: black;
}
.nav-link:hover {
color: #666;
}
/* Hamburger Menu */
.hamburger-toggle {
display: none;
}
.hamburger {
display: none;
flex-direction: column;
cursor: pointer;
width: 64px;
height: 64px;
justify-content: center;
align-items: center;
background-color: black;
gap: 4px;
}
.hamburger-line {
width: 20px;
height: 2px;
background-color: white;
}
.menu-overlay {
position: fixed;
top: 80px;
left: 0;
width: 100%;
height: calc(100% - 80px);
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
opacity: 0;
visibility: hidden;
cursor: pointer;
}
/* Mobile Navigation */
@media screen and (max-width: 768px) {
.hamburger {
display: flex;
}
.nav-menu {
position: fixed;
left: -100%;
top: 80px;
flex-direction: column;
background-color: var(--background-color);
width: 100%;
z-index: 1000;
gap: 0;
}
.hamburger-toggle:checked ~ .nav-menu {
left: 0;
}
.hamburger-toggle:checked ~ .menu-overlay {
opacity: 1;
visibility: visible;
}
.nav-menu .nav-link {
padding: 16px;
width: 100%;
}
}
.project-header {
position: relative;
display: inline-block;
margin-top: 96px;
}
.project-header h1 {
margin-top: 0;
}
.project-header .breadcrumb-wrapper {
position: absolute;
left: 0;
bottom: 100%;
margin-bottom: 0.5rem;
display: flex;
flex-direction: row;
gap: 8px;
}
.project-header .breadcrumb-wrapper a {
display: inline-block;
}
.project-header .breadcrumb-wrapper p {
margin: 0;
display: inline-block;
}
.project-footer {
box-sizing: border-box;
display: flex;
flex-direction: row;
justify-content: space-between;
height: 64px;
border-top: 1px solid black;
border-bottom: 1px solid black;
padding-top: 16px;
padding-bottom: 16px;
margin-top: 96px;
}
.project-footer div {
display: flex;
flex-direction: row;
align-items: center;
}

12
assets/css/note.css Normal file
View File

@@ -0,0 +1,12 @@
.backlink-divider {
color: var(--color-background-2);
}
.backlink-box {
display: flex;
flex-direction: column;
}
.backlinks {
}

View File

Before

Width:  |  Height:  |  Size: 461 KiB

After

Width:  |  Height:  |  Size: 461 KiB

View File

Before

Width:  |  Height:  |  Size: 843 KiB

After

Width:  |  Height:  |  Size: 843 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 2.8 MiB

View File

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 182 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

Before

Width:  |  Height:  |  Size: 760 KiB

After

Width:  |  Height:  |  Size: 760 KiB

View File

Before

Width:  |  Height:  |  Size: 941 KiB

After

Width:  |  Height:  |  Size: 941 KiB

View File

Before

Width:  |  Height:  |  Size: 9.4 MiB

After

Width:  |  Height:  |  Size: 9.4 MiB

View File

@@ -1,21 +0,0 @@
---
title: "Brendan Chen"
layout: default
isIndexPage: 1
---
# Brendan Chen
I'm Brendan, a recently graduated Software Engineering student who attended Chapman University. I'm currently an associate software engineer at Cargill. I'm also the creator of [OpenAppLock](https://github.com/brendan-ch/OpenAppLock), an open-source time blocker for iOS, and [Interchange](https://interchange.bchen.dev), a free shuttle app for Chapman students.
I believe that no matter which path an app's development takes, it should be grounded in real experiences.
Feel free to [say hi](mailto:brendan@bchen.dev)!
<div class="horizontal-wrapper">
<a class="primary-action" href="/projects">View projects</a>
<a class="secondary-action" href="/assets/resume.pdf" target="_blank">View resume</a>
</div>
![Picture of me taken at the Senso-ji Buddhist Temple in Asakusa, Tokyo, Japan](assets/me-1.png "Picture of me taken at the Senso-ji Buddhist Temple in Asakusa, Tokyo, Japan")

View File

@@ -1,23 +0,0 @@
---
title: "Notes"
layout: default
---
<h1>Notes</h1>
<p>I've moved publishing to my [Substack](https://bchendev.substack.com?utm_source=bchen.dev). For reference, old notes remain accessible here.</p>
<p></p>
{% assign posts_by_month = site.posts | group_by_exp: "post", "post.date | date: '%B %Y'" %}
{% for month in posts_by_month %}
<h2>{{ month.name }}</h2>
<ul>
{% for post in month.items %}
<li>
<time>{{ post.date | date: "%Y.%m.%d" }}</time>
<a href="{{ post.url }}">{{ post.title }}</a>
</li>
{% endfor %}
</ul>
{% endfor %}

5
projects/cmes-mobile.md Normal file
View File

@@ -0,0 +1,5 @@
---
title: Page moved
layout: page_moved
---
[[CMES Mobile App]]

View File

@@ -1,20 +0,0 @@
---
title: CMES Mobile App
layout: project
---
![cmes-mobile-app.png](assets/cmes-mobile-app.png "Screenshots of the CMES app, showing the My Library screen and a web view.")
**App development project for volunteer organization Techies Without Borders.** I helped implement the CMES mobile app, facilitating the download, viewing and playback of medical education content in resource-constrained countries.
In the early stages, I built the download screen and business logic, the PDF viewer integration, and background audio playback using native code. Later, I built the native web view to integrate with the [CMES-Pi](https://cmesworld.org/cmes_product-Pi.html) solution, simplifying the business logic while adding support for future edge platforms.
The app's front-end uses Capacitor and Angular, with several Capacitor plugins in Swift + Java to bundle native functionality.
This app is actively used in medical facilities in Uganda and Nepal, to name a few.
[App Store](https://apps.apple.com/us/app/cmes/id1276335909){: .primary-action }
[Google Play](https://play.google.com/store/apps/details?id=org.cmesworld.www.cmes_piandroid_app){: .primary-action }
[Techies Without Borders](https://techieswithoutborders.us/){: .secondary-action }
[CMES Project](https://cmesworld.org/){: .secondary-action }
{: .horizontal-wrapper }

View File

@@ -0,0 +1,5 @@
---
title: Page moved
layout: page_moved
---
[[Don't Wake Sandie]]

5
projects/interchange.md Normal file
View File

@@ -0,0 +1,5 @@
---
title: Page moved
layout: page_moved
---
[[Interchange]]

View File

@@ -1,16 +0,0 @@
---
title: Interchange
layout: project
---
![interchange-screenshots.png](assets/interchange-screenshots.png "Screenshots of Interchange.")
**An iOS app for college transit, currently available for Chapman University.**
View routes, shuttle locations, and estimated arrival times in a sleek,
easy-to-use interface. I built this app using SwiftUI, Node.js and Apollo
GraphQL, with Redis as a primary storage mechanism.
[Download on the App Store](https://apps.apple.com/us/app/interchange-college-commute/id6739968742){: .primary-action }
[Join the TestFlight](https://testflight.apple.com/join/HbuHfUgW){: .secondary-action }
[Website](https://interchange.bchen.dev/){: .secondary-action }
{: .horizontal-wrapper }

View File

@@ -0,0 +1,5 @@
---
title: Page moved
layout: page_moved
---
[[Power to the People]]

View File

@@ -1,17 +0,0 @@
---
title: Power to the People
layout: project
---
![power-to-the-people.png](assets/power-to-the-people.png "Screenshots of Power to the People, an iOS app designed to make traffic stops safer.")
**An iPhone app designed to make traffic stops safer for marginalized communities.** Record interactions with police, assign and text emergency contacts, and be informed of your rights. I worked in a student team of 4, including [David Sohn](https://www.linkedin.com/in/sohn-david/), [Jacob Zamore](https://www.linkedin.com/in/jacob-zamore/), and [Patrick Smith](https://www.linkedin.com/in/patrick-smith-ld/), to prototype and build a proof of concept in 36 hours. The app was selected for the **President's Pick award** at [CruzHacks 2024](https://cruzhacks.com/), the premier hackathon of UC Santa Cruz.
[GitHub](https://github.com/brendan-ch/PowerToThePeople){: .primary-action }
[Devpost](https://devpost.com/software/power-to-the-people-lbhxyd){: .secondary-action }
{: .horizontal-wrapper }
My contribution was the iOS app prototype, of which a demo is shown below. I built the primary user flow of recording a police interaction, simultaneously using the front and back iPhone cameras to capture all angles. Activating the recording also displays the "Know Your Rights" screen, displaying basic dos and don'ts. Finally, I built a way to view previous recordings and share the files using the iOS share sheet.
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/Un8JWXkpcW4?si=CfQ0qOuUjSpas0ny&amp;start=89" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

5
projects/resonance.md Normal file
View File

@@ -0,0 +1,5 @@
---
title: Page moved
layout: page_moved
---
[[Resonance]]

View File

@@ -1,20 +0,0 @@
---
title: Resonance
layout: project
---
![resonance-screenshot.png](assets/resonance-screenshot.png "Screenshot of Resonance")
Resonance is a cyberpunk-themed game show shooter. Enter the arena, blast your opponents, and become the star of the show. I worked with a student team of 12 to create this game over multiple months. The game was selected as an **IEEE GameSig finalist**.
*Steam link coming soon!*
My core role was to implement the game's networking logic using PurrNet, an open-source library for Unity.
My first project was to implement networking for **Arena**, a free-for-all PVP game mode, where the player with the most eliminations wins. I learned how to isolate game logic on the server and synchronize state on each client. The networked API for Arena is used by every aspect of the game, including the scoreboard UI and spotlight mechanic. Following a pivot in the games core direction, I successfully handed this system to another engineer, who implemented a rating system for style points.
Another project was the **build system**. Having a server dependency and Steam integration meant that the environment differed from development to production. We found it painful to prepare a build for each class-mandated playtest. With the help of Claude, I created a two-step system: an editor script to build and configure dependencies in one step, and a nightly GitHub Actions pipeline. Besides simplifying the build process, this created another positive side effect by ensuring we fixed compiler issues proactively.
I'm currently working on a client-side prediction model to match other FPS games on the market.
Even with our team size, the most important thing I learned was how to manage scope and scale. Each one of us pushed ourselves to the limits of our knowledge, going from bare theoretical understanding to practical working implementations. The goal was never to get things right on the first try; it was to learn and realize new ideas in multiple passes.

5
projects/sourcebook.md Normal file
View File

@@ -0,0 +1,5 @@
---
title: Page moved
layout: page_moved
---
[[Sourcebook]]

5
projects/tad-pool.md Normal file
View File

@@ -0,0 +1,5 @@
---
title: Page moved
layout: page_moved
---
[[Tad-Pool]]

View File

@@ -1,21 +0,0 @@
---
title: Tad-Pool
layout: project
---
![tad-pool.png](assets/tad-pool.png "Screenshots of Tad-Pool, a carpooling app for college students.")
**A mobile app designed to make carpooling easier for college students.** Find
carpools based on time and location, and get rewarded for helping to reduce CO2
emissions. I worked with [Jazmine Stanek](https://www.linkedin.com/in/vjazminestanek/)
and [Isabelle Woo](https://www.linkedin.com/in/isabellewoo/) to create the UI/UX
design using Figma and FigJam, in a period of 24 hours for
[FullyBeyond 2024](https://fullybeyond.acmcsuf.com/).
The project reached the finalist judging stage.
[High-fidelity design](https://www.figma.com/design/nTKCcwftKrg27wlP5mmy9e/Tad-Pool-Design?node-id=13-1837&node-type=canvas&t=YKuVvM6RAPs4hTIo-11){: .primary-action }
[Low-fidelity design](https://www.figma.com/design/nTKCcwftKrg27wlP5mmy9e/Tad-Pool-Design?node-id=0-1&node-type=canvas&t=YKuVvM6RAPs4hTIo-11){: .secondary-action }
[FigJam board](https://www.figma.com/board/Dct7GuhKFYq8maw0KoAVBV/Product-Brainstorm-FullyBeyond?node-id=0-1&node-type=canvas&t=g7RKLLo5IoU84ndk-11){: .secondary-action }
[Devpost](https://devpost.com/software/tad-pool){: .secondary-action }
{: .horizontal-wrapper }

View File

@@ -0,0 +1,5 @@
---
title: Page moved
layout: page_moved
---
[[teech.ai]]

View File

@@ -1,19 +0,0 @@
---
title: teech.ai
layout: project
---
<iframe width="100%" height="480" src="https://www.youtube-nocookie.com/embed/luVIdLB9id0?si=1sTE9Z3MeyWntRmd" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
**A proof-of-concept tutoring app for students learning math.** Using the phone camera, the app sees and understands math problems, guiding students through problems with hints and step-by-step questions. We added text-to-speech (TTS) to give our app a voice, further engaging students and making it feel like a more personal experience.
[Tri Vu](https://www.linkedin.com/in/tri-q-vu/) built a web interface prototype and Python-based backend utilizing the Gemini Vision API and Fish Audio for TTS.
I built a native proof-of-concept using SwiftUI, AVFoundation, and other native frameworks. In the app, I built a chat and camera interface, integrating with Tri's backend by sending image and prompt data. Swiping to the right reveals the user's chat history, which is also continuously synced with the backend. Design-wise, I decided to use this gesture-based approach in order to bring the camera interface front and center.
The app was built in 48 hours for **Cal Hacks 12.0**, the world's largest collegiate hackathon.
[GitHub](https://github.com/brendan-ch/teecher-ios){: .primary-action }
[Devpost](https://devpost.com/software/teech-ai){: .secondary-action }
[Cal Hacks](https://www.calhacks.io){: .secondary-action }
{: .horizontal-wrapper }

View File

@@ -0,0 +1,5 @@
---
title: Page moved
layout: page_moved
---
[[The Farmer's Dog Trivia Game]]

6
projects/timestamp.md Normal file
View File

@@ -0,0 +1,6 @@
---
title: Timestamp
layout: page_moved
---
[[Timestamp]]

View File

@@ -1,12 +0,0 @@
---
title: Timestamp
layout: project
---
![Screenshot of the Timestamp app and the settings window.](assets/timestamp-screenshot.png)
A simple, unintrusive Pomodoro timer for macOS which lives in the menu bar. I built it with SwiftUI and made it open source.
[Download the app](https://github.com/brendan-ch/timestamp/releases){: .primary-action }
[View the code](https://github.com/brendan-ch/timestamp){: .secondary-action }
{: .horizontal-wrapper }