Rewrite profile content as generalist; fix scrollbars, footer padding & link colour

Content (A/B):
- Headline no longer Web3-specific: "Full Stack Engineer · AI & Automation · DevOps"
- Resume restructured: summary, grouped core skills, selected projects,
  three roles with outcomes, education, languages, "Currently learning"
- Web3 demoted from a top-level section to a single learning bullet
- Thanks panel rewritten to generalist; contact links updated to Gitea
- SEO/OG metadata rewritten for a personal profile (was template copy)

Bug fixes:
- App.vue: progressivelyShowStyle(1) was called three times but is a no-op
  once its cumulative-length guard is satisfied, so stylesheet stages 2-3
  were never typed and the entire .thankEditor stylesheet (padding, link
  colours, box layout) was missing. Now every stage is typed in order.
- App.vue: enableHtml flipped before the text passes completed, rendering
  both panels as empty markdown. HTML swap now happens after typing.
- App.vue: removed html{min-height:100vh} which added a phantom 7px of
  vertical page scroll.
- profile.js: .styleEditor was fixed+left:0 with a left margin, pushing the
  panel 4px off-screen and creating page-level horizontal scroll. Widths now
  calc(50vw - 1em) with left/right offsets inside the viewport.
- profile.js: .thankEditor padding was being reset by a later margin:0 auto
  shorthand; final stage now re-declares padding/margin so the footer has
  real breathing room. Footer height fits inside #foot (no clipping).
- profile.js: footer links were UA-default blue (#0000EE) on a light panel;
  now #0b4a6f, weight 600, with a hover state.

Repo hygiene (C):
- Removed GitHub remote and the GitHub Pages workflow; Gitea only
- README rewritten as a personal profile repo (was template marketing copy)
- Dropped force-tracked build artifacts (dist/index.html, public/static/*)
  that were dead Vue2 leftovers and contradicted .gitignore
This commit is contained in:
2026-09-20 06:29:34 +08:00
parent 0bf4551f19
commit de7c44b87d
15 changed files with 223 additions and 305 deletions
-34
View File
@@ -1,34 +0,0 @@
name: Deploy to GitHub Pages
on:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- name: Configure GitHub Pages
id: configure-pages
uses: actions/configure-pages@v4
- name: Build with Pages base path
run: npm run build -- --base=${{ steps.configure-pages.outputs.base_path }}
- uses: actions/upload-pages-artifact@v3
with:
path: dist
- id: deployment
uses: actions/deploy-pages@v4
+80 -74
View File
@@ -1,91 +1,97 @@
# Animated Profile
# hoelee-profile
> Type your way to a stunning developer showcase.
Personal profile and animated resume for **Lee Teong Hoe (Mr Hoelee)** — Full Stack Engineer · AI & Automation · DevOps.
## Demo
**Live:** https://me.hoelee.com
See the live animated resume in action:
- **Primary site** → [**me.hoelee.com**](https://me.hoelee.com)
- **GitHub Pages mirror** → [**hoelee.github.io/hoelee-profile**](https://hoelee.github.io/hoelee-profile/)
[![Live demo](https://img.shields.io/badge/demo-me.hoelee.com-0066cc)](https://me.hoelee.com)
[![GitHub Pages](https://img.shields.io/badge/demo-github.io-2ea44f)](https://hoelee.github.io/hoelee-profile/)
[![Live](https://img.shields.io/badge/live-me.hoelee.com-0b4a6f)](https://me.hoelee.com)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
An interactive animated resume template built with Vue 3 and Vite.
## What this is
## Quick Start
An animated developer profile: the page types its own stylesheet in a code editor on the left while
rendering the resume on the right, then finishes with a thank-you / contact panel.
1. Click **Use this template** on GitHub, or clone the repository:
It's a personal site — the content is my resume, not a reusable template. The *technique* is adapted
from [jirengu-inc/animating-resume](https://github.com/jirengu-inc/animating-resume) (Apache-2.0);
see Credits.
```bash
git clone https://github.com/hoelee/hoelee-profile.git
cd hoelee-profile
```
2. Install dependencies:
```bash
npm install
```
3. Edit `src/config/profile.js` and fill in your name, bio, skills, and contact information.
4. Start a local preview at <http://localhost:8080>:
```bash
npm run dev
```
5. Create a production build in `dist/`:
```bash
npm run build
```
6. Deploy `dist/` to Netlify, Vercel, GitHub Pages, or any static host.
## Typing Speed
Set `typingSpeed` in `src/config/profile.js` to control the milliseconds per character. `1` is fastest; `20` creates a slow, dramatic effect.
## Project Structure
| Path | Purpose |
| --- | --- |
| `src/App.vue` | Desktop animated profile |
| `src/Mobile.vue` | Mobile animated profile |
| `src/config/profile.js` | Personal details, Markdown, animation speed, and typed CSS |
| `src/main.js` | Responsive application entry point |
| `src/components/` | Resume, style, and thank-you editors |
| `src/assets/` | Reset stylesheet and static assets |
| `public/robots.txt` | SEO crawler directives |
## Deployment
| Platform | Approach |
| --- | --- |
| GitHub Pages | [hoelee.github.io/hoelee-profile](https://hoelee.github.io/hoelee-profile/) — auto-deploys via GitHub Actions on every push to `main`. |
| Netlify | Build with `npm run build` and publish `dist/`. |
| Vercel | Import the repository; Vite settings are detected automatically. |
| Static server | Upload the contents of `dist/` to any static web server. |
`public/robots.txt` is pre-included for SEO and is copied into the production build.
## Tech Stack
## Tech stack
| Technology | Role |
| --- | --- |
| Vue 3 | UI framework |
| Vite | Development server and build tool |
| Prism.js | Syntax highlighting |
| marked.js | Markdown rendering |
| Vite 6 | Dev server and build tool |
| Prism.js | Syntax highlighting in the code panel |
| marked | Markdown → HTML rendering |
## License
## Project structure
Licensed under [Apache-2.0](LICENSE).
| Path | Purpose |
| --- | --- |
| `src/App.vue` | Desktop layout — orchestrates the typewriter sequence |
| `src/Mobile.vue` | Mobile layout (used when viewport ≤ 500px) |
| `src/config/profile.js` | **All content lives here** — details, Markdown, animation speed, typed CSS |
| `src/components/StyleEditor.vue` | Left panel: highlighted, self-typing CSS |
| `src/components/ResumeEditor.vue` | Right panel: resume markdown → HTML |
| `src/components/ThankEditor.vue` | Footer: thank-you / contact panel |
| `src/main.js` | Entry point, picks desktop vs mobile |
| `src/assets/reset.css` | Reset + border-box baseline |
| `public/robots.txt` | Crawler directives (copied into the build) |
## Editing the content
Everything you'd want to change is in `src/config/profile.js`:
- `name`, `title` — identity and headline
- `typingSpeed` — ms per character (`1` = near-instant, `20` = slow and dramatic)
- `resumeMarkdown` — the resume body
- `thanksMarkdown` — the thank-you / contact panel
- `desktopStyles` / `mobileStyles` — **arrays** of CSS strings, typed in sequence
> **Note on the CSS arrays.** Each array is typed in order and every stage is *appended to the same
> injected `<style>` tag*. Because they all end up in one stylesheet, a later stage's shorthand
> (e.g. `margin: 0 auto`) overrides an earlier stage's longhand (`margin-top: 1em`). Keep the final
> visual values in the **last** stage of each array — that is what wins.
## Development
```bash
npm install
npm run dev # http://localhost:8080
npm run build # outputs to dist/
npm run preview # serve the production build locally
```
Requires Node ≥ 18 (built and tested on Node 24).
## Deployment
Pushed to **Gitea only** — `git.hoelee.com/hoelee/hoelee-profile`. There is no GitHub mirror.
`me.hoelee.com` is served by an `nginx:alpine` container (`nginx-me.hoelee.com`) on the VPS at
`41.216.187.102`, behind Traefik. The container mounts the built site read-only:
| Host path | Container path |
| --- | --- |
| `/docker/web/me.hoelee.com/public` | `/var/www/html` |
| `/docker/web/me.hoelee.com/nginx/default.conf` | `/etc/nginx/conf.d/default.conf` |
To deploy a new build:
```bash
npm run build
rsync -av --delete dist/ [email protected]:/docker/web/me.hoelee.com/public/
```
The site is static — no PHP or database is used. The `php:9000` upstream in the nginx config is a
leftover from the shared web template and is not needed by this site.
## Credits
This project is inspired by and credits [jirengu-inc/animating-resume](https://github.com/jirengu-inc/animating-resume) — the original animated resume project by jirengu-inc.
Adapted from [jirengu-inc/animating-resume](https://github.com/jirengu-inc/animating-resume) by
jirengu-inc — the original animated resume project.
## License
[Apache-2.0](LICENSE).
-20
View File
@@ -1,20 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta name="description" content="Animated developer profile — customize and showcase your own resume with typewriter effects.">
<meta property="og:title" content="Animated Profile | me.hoelee.com">
<meta property="og:description" content="An interactive animated resume with typewriter effects. Built with Vue 3 + Vite.">
<meta property="og:url" content="https://me.hoelee.com">
<meta property="og:type" content="website">
<title>Animated Profile | me.hoelee.com</title>
<link rel="icon" type="image/x-icon" href="./favicon.ico">
<link rel="shortcut icon" type="image/x-icon" href="./favicon.ico">
<script type="module" crossorigin src="/static/js/index-M1D7qx2s.js"></script>
<link rel="stylesheet" crossorigin href="/static/css/index-DXZ8y7TX.css">
</head>
<body>
<div id="app"></div>␍
</body>
</html>
+12 -5
View File
@@ -3,12 +3,19 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta name="description" content="Animated developer profile — customize and showcase your own resume with typewriter effects.">
<meta property="og:title" content="Animated Profile | me.hoelee.com">
<meta property="og:description" content="An interactive animated resume with typewriter effects. Built with Vue 3 + Vite.">
<meta name="description" content="Lee Teong Hoe (Mr Hoelee) — Full Stack Engineer with ~10 years building production web systems. PHP/CodeIgniter, Java/Spring Boot, AI & automation, DevOps. Based in Perak, Malaysia.">
<meta name="author" content="Lee Teong Hoe (Mr Hoelee)">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://me.hoelee.com/">
<meta property="og:title" content="Mr Hoelee — Full Stack Engineer · AI & Automation · DevOps">
<meta property="og:description" content="Interactive animated resume — Full Stack Engineer with ~10 years building production web systems: PHP/CodeIgniter, Java/Spring Boot, AI automation and Docker infrastructure.">
<meta property="og:url" content="https://me.hoelee.com">
<meta property="og:type" content="website">
<title>Animated Profile | me.hoelee.com</title>
<meta property="og:type" content="profile">
<meta property="og:site_name" content="me.hoelee.com">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Mr Hoelee — Full Stack Engineer · AI & Automation · DevOps">
<meta name="twitter:description" content="Interactive animated resume of Lee Teong Hoe — full stack engineering, AI automation and self-hosted infrastructure.">
<title>Mr Hoelee — Full Stack Engineer · AI & Automation · DevOps</title>
<link rel="icon" type="image/x-icon" href="./favicon.ico">
<link rel="shortcut icon" type="image/x-icon" href="./favicon.ico">
</head>
-1
View File
@@ -1 +0,0 @@
<!DOCTYPE html><html><head><meta charset=utf-8><title>个人简历</title><meta name=viewport content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link href=/anires/public/static/css/app.97f24077373dbd5f86744ecd100255e7.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/anires/public/static/js/manifest.22a4ae013e2ceb48b7dd.js></script><script type=text/javascript src=/anires/public/static/js/vendor.045b08f12500b7884e7e.js></script><script type=text/javascript src=/anires/public/static/js/app.c4b2e14682ed119846b5.js></script></body></html>
@@ -1,74 +0,0 @@
#app[data-v-22ce9519] {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
html[data-v-22ce9519] {
min-height: 100vh;
}
*[data-v-22ce9519] {
box-sizing: border-box;
}
pre[data-v-35c047fd]{
}
@media (max-width:500px){
pre[data-v-35c047fd]{
}
}
.code[data-v-35c047fd] {
display: none;
}
@media (max-width:500px){
.resumeEditor[data-v-647e008b]{
}
}
.htmlMode[data-v-647e008b] {
animation: flip 2s;
}
@keyframes flip {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@media (max-width:500px){
.thankEditor[data-v-29ad1df6]{
}
}
.htmlMode[data-v-29ad1df6] {
animation: flip 2s;
}
@keyframes flip {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
*{margin: 0; padding: 0;}
*{box-sizing: border-box;}
*::before{box-sizing: border-box;}
*::after{box-sizing: border-box;}
#app[data-v-53c8d54c] {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
min-height: 100vh; position: relative;
}
html[data-v-53c8d54c] {
min-height: 100vh;
}
*[data-v-53c8d54c]{
box-sizing: border-box;
}
/*# sourceMappingURL=app.97f24077373dbd5f86744ecd100255e7.css.map*/
@@ -1 +0,0 @@
{"version":3,"sources":["webpack:///./src/App.vue","webpack:///./src/components/StyleEditor.vue","webpack:///./src/components/ResumeEditor.vue","webpack:///./src/components/ThankEditor.vue","webpack:///./src/assets/reset.css","webpack:///./src/Mobile.vue"],"names":[],"mappings":";AACA;EACE,oDAAoD;EACpD,oCAAoC;EACpC,mCAAmC;CACpC;AACD;EACE,kBAAkB;CACnB;AACD;EACE,uBAAuB;CACxB;;ACVD;CACC;AACD;AACA;CACC;CACA;AACD;EACE,cAAc;CACf;;ACRD;AACA;CACC;CACA;AACD;EACE,mBAAmB;CACpB;AACD;AACA;IACI,WAAW;CACd;AACD;IACI,WAAW;CACd;CACA;;ACdD;AACA;CACC;CACA;AACD;EACE,mBAAmB;CACpB;AACD;AACA;IACI,WAAW;CACd;AACD;IACI,WAAW;CACd;CACA;ACfD,EAAE,UAAU,CAAC,WAAW,CAAC;AACzB,EAAE,uBAAuB,CAAC;AAC1B,UAAU,uBAAuB,CAAC;AAClC,SAAS,uBAAuB,CAAC;;ACFjC;EACE,oDAAoD;EACpD,oCAAoC;EACpC,mCAAmC;EACnC,kBAAkB,CAAC,mBAAmB;CACvC;AACD;EACE,kBAAkB;CACnB;AACD;EACE,uBAAuB;CACxB","file":"static/css/app.97f24077373dbd5f86744ecd100255e7.css","sourcesContent":["\n#app[data-v-22ce9519] {\n font-family: 'Avenir', Helvetica, Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\nhtml[data-v-22ce9519] {\n min-height: 100vh;\n}\n*[data-v-22ce9519] {\n box-sizing: border-box;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/App.vue","\npre[data-v-35c047fd]{\n}\n@media (max-width:500px){\npre[data-v-35c047fd]{\n}\n}\n.code[data-v-35c047fd] {\n display: none;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/components/StyleEditor.vue","\n@media (max-width:500px){\n.resumeEditor[data-v-647e008b]{\n}\n}\n.htmlMode[data-v-647e008b] {\n animation: flip 2s;\n}\n@keyframes flip {\nfrom {\n opacity: 0;\n}\nto {\n opacity: 1;\n}\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/components/ResumeEditor.vue","\n@media (max-width:500px){\n.thankEditor[data-v-29ad1df6]{\n}\n}\n.htmlMode[data-v-29ad1df6] {\n animation: flip 2s;\n}\n@keyframes flip {\nfrom {\n opacity: 0;\n}\nto {\n opacity: 1;\n}\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/components/ThankEditor.vue","*{margin: 0; padding: 0;}\n*{box-sizing: border-box;}\n*::before{box-sizing: border-box;}\n*::after{box-sizing: border-box;}\n\n\n\n// WEBPACK FOOTER //\n// ./src/assets/reset.css","\n#app[data-v-53c8d54c] {\n font-family: 'Avenir', Helvetica, Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n min-height: 100vh; position: relative;\n}\nhtml[data-v-53c8d54c] {\n min-height: 100vh;\n}\n*[data-v-53c8d54c]{\n box-sizing: border-box;\n}\n\n\n\n\n// WEBPACK FOOTER //\n// ./src/Mobile.vue"],"sourceRoot":""}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,i){for(var u,a,f,s=0,l=[];s<t.length;s++)a=t[s],o[a]&&l.push(o[a][0]),o[a]=0;for(u in c)Object.prototype.hasOwnProperty.call(c,u)&&(e[u]=c[u]);for(r&&r(t,c,i);l.length;)l.shift()();if(i)for(s=0;s<i.length;s++)f=n(n.s=i[s]);return f};var t={},o={2:0};n.e=function(e){function r(){u.onerror=u.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var i=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,n.nc&&u.setAttribute("nonce",n.nc),u.src=n.p+"static/js/"+e+"."+{0:"045b08f12500b7884e7e",1:"c4b2e14682ed119846b5"}[e]+".js";var a=setTimeout(r,12e4);return u.onerror=u.onload=r,i.appendChild(u),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="/anires/public/",n.oe=function(e){throw console.error(e),e}}([]);
//# sourceMappingURL=manifest.22a4ae013e2ceb48b7dd.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+16 -9
View File
@@ -37,17 +37,28 @@
},
methods: {
makeResume: async function () {
await this.progressivelyShowStyle(0)
// Type every stage of the stylesheet in order. The original code called
// progressivelyShowStyle(1) three times between steps, but that call is
// idempotent once the cumulative-length guard is satisfied, so stages 2
// and 3 were never typed and the whole .thankEditor stylesheet (padding,
// link colours, box layout) was missing from the injected <style> tag.
for (let n = 0; n < this.fullStyle.length; n++) {
await this.progressivelyShowStyle(n)
}
// Type the resume body while it is still plain text, then swap to
// rendered HTML. The swap must happen AFTER both text passes, otherwise
// `enableHtml` flips early and both panels render empty markdown.
await this.progressivelyShowResume()
await this.progressivelyShowStyle(1)
await this.showHtml()
await this.progressivelyShowStyle(1)
await this.progressivelyShowThanks()
await this.progressivelyShowStyle(1)
await this.showHtml()
},
showHtml() {
return new Promise((resolve) => {
this.enableHtml = true
this.$nextTick(() => {
if (this.$refs.resumeEditor) this.$refs.resumeEditor.goTop()
if (this.$refs.thankEditor) this.$refs.thankEditor.goTop()
})
resolve()
})
},
@@ -122,10 +133,6 @@
-moz-osx-font-smoothing: grayscale;
}
html {
min-height: 100vh;
}
* {
box-sizing: border-box;
}
+115 -73
View File
@@ -1,72 +1,80 @@
export default {
// Personal info
name: 'Mr. Hoelee',
title: 'Web3 Developer & Full Stack Engineer',
githubUrl: 'https://github.com/hoelee/',
title: 'Full Stack Engineer · AI & Automation · DevOps',
githubUrl: 'https://git.hoelee.com/hoelee',
// Animation speed (ms per character typed)
// Lower = faster. Recommended range: 1 (instant) to 20 (dramatic slow)
typingSpeed: 1,
// Resume content in Markdown
resumeMarkdown: `Mr. Hoelee
resumeMarkdown: `Mr. Hoelee (Lee Teong Hoe)
====
Hi, I'm Mr. Hoelee — a Web3 Developer & Experience Full Stack Engineer.
I design and build bridges between Web2 and Web3 — crafting scalable backend systems, gas-optimized smart contracts, and immersive decentralized frontends.
My passion lies at the intersection of blockchain innovation, AI automation, and full-stack engineering — shaping how the next internet will work.
Full Stack Engineer with ~10 years building production web systems — PHP/CodeIgniter and Java/Spring backends, relational data layers, and the Docker infrastructure that runs them.
I work end to end: gather the requirements, design the schema and APIs, build the app, deploy it, and keep it running in production.
Recent focus is AI and automation — LLM-backed assistants, OCR document verification, and workflow automation that removes manual work from real business processes.
About Me
----
I'm a passionate Web3 developer with hands-on experience creating secure, upgradeable, and gas-efficient ERC-20 / ERC-721 smart contracts using Solidity, Hardhat, and Foundry.
Beyond the blockchain, I bring an enterprise-level backend mindset — leveraging Java Spring Boot, CodeIgniter, and Dockerized infrastructure to scale decentralized apps with the reliability of traditional systems.
When I'm not building, I explore the evolving Web3 landscape — from Layer 2 protocols and DAO governance to DeFi innovation and on-chain analytics.
My mission: to merge creativity, code, and cryptography into meaningful digital experiences.
I'm a hands-on engineer who likes owning a system from the first requirement to production support.
Day to day that means CodeIgniter and Spring Boot services, WordPress builds for clients, and a self-hosted Docker stack of 30+ services (reverse proxy, SSO, databases, monitoring, backups, automation) that I run and maintain myself.
I'm comfortable being the only technical person on a project: talking to the client, making the architecture call, writing the code, and handling the production incident.
I use AI as a working tool — coding assistants, local LLMs, and automation pipelines — to move faster without lowering the quality bar.
Web3 & Blockchain
Core Skills
----
- Solidity smart contracts (ERC-20 / ERC-721 / upgradeable patterns)
- Hardhat, Foundry testing, OpenZeppelin & Chainlink Oracles
- Alchemy SDK, Polygon, IPFS integration
- Languages: PHP, Java, JavaScript, SQL, Python (scripting), Bash
- Backend & Frameworks: CodeIgniter 4, Spring Boot, Spring Data JPA / Hibernate, REST API design, WordPress plugin development
- Databases: MySQL, PostgreSQL, SQL Server, SQLite, Redis, Elasticsearch, Memcached
- Frontend: HTML5, CSS3, JavaScript, Vue 3, jQuery, Bootstrap, responsive UI
- DevOps & Infrastructure: Docker, Docker Compose, Portainer, Traefik, Nginx, IIS, Cloudflare, Linux (Ubuntu/Debian), Windows Server, Synology DSM, unRAID
- AI & Automation: OpenAI API, LLM integration, prompt engineering, OCR pipelines, IBM Watson Assistant, n8n, local LLM inference (LM Studio, llama.cpp)
- Tooling: Git (Gitea/GitHub/GitLab), VS Code, NetBeans, Postman, Chrome extension development
Frontend Development
Selected Projects
----
- Web3.js / Ethers.js integration
- JavaScript, jQuery, HTML5, CSS3
- UI/UX prototyping with Photoshop, Illustrator, Canva
- DigiKedai AI customer-support bot — multi-channel (Telegram + WhatsApp) support bot in TypeScript, LiteLLM + PostgreSQL, containerised on NAS behind a Cloudflare tunnel webhook.
- OCR invoice verification + AI customer service — replaces manual data entry by extracting and validating receipt fields, with an LLM layer drafting replies.
- Real-time GPS delivery tracking platform — live order and driver tracking with automated dispatch and scheduling; included the rider-side Android app.
- Self-hosted production infrastructure — 30+ Docker services across NAS and VPS: Traefik reverse proxy with custom bot-filter middleware, authentik SSO, PostgreSQL/MySQL, backups, monitoring and automation.
- Digital product catalogue + delivery system — NocoDB catalogue, n8n-backed single-use access links, and per-download logging for digital goods sales.
- Numerology report engine (CodeIgniter 4) — print-ready A4 report generation staged for token-gated PDF delivery.
- Client web builds — 30+ delivered SME, NGO and e-commerce sites on WordPress (Kadence, Elementor, JetEngine) plus custom PHP applications.
Backend & DevOps
Experience
----
- PHP CodeIgniter (Web2↔Web3 bridge)
- Java Spring Boot + Hibernate for scalable data layers
- Databases: MySQL, MongoDB, Memcached, Elasticsearch
- Docker, Nginx, IIS, OpenLiteSpeed, Apache Tomcat, WordPress
- Server management (Windows, Ubuntu, unRaid, DSM)
Full Stack Software Engineer — Joywood, Perak, Malaysia (2020 – Present)
- Delivered 30+ production websites and business systems for SME, NGO and e-commerce clients, owning each from requirements through deployment and support.
- Built AI features into client products: OpenAI-backed assistants, OCR data capture, chatbots and workflow automation.
- Developed and maintained PHP (CodeIgniter) applications, WordPress plugins and Java Spring Boot services.
- Designed and operated the self-hosted Docker infrastructure (30+ services) covering auth, monitoring, backup, databases, automation and reverse proxying.
Work Ethic
----
- Effective Communicator: Strong written & verbal clarity in remote teams
- Problem-Solver: Adaptive mindset for complex, high-speed development
- Continuous Learner: Always exploring blockchain advancements
- AI-Driven: Using AI to accelerate workflows and creative tasks
- Client-Focused: Prioritizing real solutions and transparent delivery
Project Manager / Software Engineer — Edufly (2019 – 2020)
- Led development of an MLM e-commerce platform and integrated its payment gateway.
- Coordinated an external development team and managed delivery scope and timelines.
- Built Java Spring automation tools for Shopee bulk listing, product scraping and internal workflows.
- Integrated Telegram, WhatsApp, Facebook Messenger and web chat bots, plus email marketing automation.
Education - Bachelor of Information System (Hons)
Software Engineer — Superceed (M) Sdn. Bhd., Remote (2016 – 2019)
- Enterprise Java Spring Boot web applications and Android apps.
- Real-time GPS delivery tracking platform with live map and automated customer assignment.
- IBM Watson Assistant chatbot, speech synthesis/recognition and face-recognition customer verification.
Education — Bachelor of Information System (Hons)
----
- Universiti Tunku Abdul Rahman (UTAR), Kampar, Malaysia
- March 2016 | CGPA: 3.06 / 4.00 - Degree In Merit
- Download Certificate → [hoelee.com/cert](https://hoelee.com/cert)
- March 2016 | CGPA: 3.06 / 4.00 — Degree with Merit
- Certificate: [hoelee.com/cert](https://hoelee.com/cert)
Languages
----
English | Malay | Mandarin | Cantonese | Hokkien
Interests
Currently Learning
----
- DeFi Innovation
- DAO Governance
- AI-assisted Development
- Cryptography
- On-Chain Analytics
- Solidity and smart-contract fundamentals (ERC-20 / ERC-721 with Foundry and Hardhat) — a side interest, not my day job.
- Local LLM inference and self-hosted AI tooling.
`,
@@ -76,31 +84,28 @@ Interests
Thank you for reading!
----
I'm open to remote collaborations and Web3-driven projects.
I'm open to full-time, contract and remote engineering roles — building and running web systems end to end.
Let's build something that connects the decentralized future with real-world impact.
The best fit is a team that needs one person who can own the whole stack: backend, frontend, deployment and production support.
* WhatsApp Mr Hoelee Now → [+6012-7972969](https://wa.me/60127972969)
* Email Mr Hoelee Now → [[email protected]](mailto:[email protected])
* Passionate about bridging the gap between traditional systems and decentralized technologies
* Ready to contribute to innovative Web3 projects that solve real-world problems
* Available for remote collaboration with forward-thinking teams and startups
* Let's discuss how we can leverage blockchain technology to create meaningful digital experiences
* WhatsApp → [+6012-7972969](https://wa.me/60127972969)
* Email → [[email protected]](mailto:[email protected])
* GitHub → [git.hoelee.com/hoelee](https://git.hoelee.com/hoelee)
* Based in Perak, Malaysia — remote-friendly, open to hybrid in Malaysia
Get in touch to explore opportunities in:
* DeFi protocol development
* Smart contract architecture and optimization
* Web3 frontend integration
* Scalable backend systems for dApps
* Cross-chain solutions and Layer 2 implementations
Where I can contribute:
* Full stack application development (PHP/CodeIgniter, Java/Spring Boot, JavaScript)
* AI and automation features — LLM assistants, OCR pipelines, workflow automation
* DevOps and self-hosted infrastructure — Docker, reverse proxy, CI/CD, monitoring, backups
* Taking existing systems in hand: maintenance, upgrades, performance and reliability
`,
// CSS animation stages — array of CSS strings typed in sequence
desktopStyles: [
`/*
* GitHub: https://github.com/hoelee/
* Hi, I'm Mr. Hoelee — Web3 Developer & Full Stack Engineer
* Gitea: https://git.hoelee.com/hoelee
* Hi, I'm Mr. Hoelee — Full Stack Engineer, AI & Automation, DevOps
* Let me create an animated resume!
*/
@@ -117,8 +122,9 @@ html {
margin:0;
}
#foot{
height:29vh;
height:30vh;
margin:0;
overflow: hidden;
}
/* Set border */
@@ -127,7 +133,7 @@ html {
border: 1px solid;
margin: .5em;
overflow: auto;
width: 50vw; height: 70vh;
width: calc(50vw - 1em); height: 70vh;
background: rgb(20,20,20);
}
/* Code highlighting */
@@ -141,7 +147,7 @@ html{
perspective: 1000px;
}
.styleEditor {
position: fixed; left: 0; top: 0;
position: fixed; left: .5em; top: 0;
-webkit-transition: none;
transition: none;
-webkit-transform: rotateY(10deg) translateZ(-100px);
@@ -150,9 +156,9 @@ html{
/* Prepare an editor */
.resumeEditor{
position: fixed; right: 0; top: 0;
position: fixed; right: .5em; top: 0;
padding: .5em; margin: .5em;
width: 50vw; height: 70vh;
width: calc(50vw - 1em); height: 70vh;
border: 1px solid;
background: rgb(200,200,200); color: #222;
overflow: auto;
@@ -167,7 +173,7 @@ html{
* then add some styles to HTML
*/
.resumeEditor{
padding: 0.5em;
padding: 1em;
}
.resumeEditor h1{
font-size: 1.3em;
@@ -202,14 +208,14 @@ html{
* Thanks for everyone's attention.
*/
.styleEditor{
width:49vw;
width: calc(49vw - 1em);
height:70vh;
margin-left:1.2vw;
margin-left:1vw;
}
.resumeEditor{
width:49vw;
width: calc(49vw - 1em);
height:70vh;
margin-right:1.2em;
margin-right:1vw;
}
.thankEditor{
@@ -217,23 +223,47 @@ html{
background: #E9D9BB;
color: #001C42;
overflow: auto;
padding: 1.25em 1.75em;
margin: .75em auto;
box-sizing: border-box;
}
.thankEditor h2{
margin-top: 0;
padding-bottom: .35em;
border-bottom: 2px solid rgba(0,28,66,.35);
}
/* readable link colour on the light background */
.thankEditor a{
color: #0b4a6f;
font-weight: 600;
text-decoration-color: rgba(11,74,111,.5);
}
.thankEditor a:hover{
color: #06304a;
text-decoration-color: #06304a;
}
.thankEditor li::before{
color: #0b4a6f !important;
}
.thankEditor {
width: 99vw; height: 45vh;
width: 99vw; height: calc(30vh - 1.5em);
border: 1px solid #ccc;
font-size: .9em;
}
`,`
.thankEditor{
padding: .5em; margin: .5em; margin-top:1em;
padding: 1.25em 1.75em; margin: .5em auto;
}
.thankEditor ul,.thankEditor ol{
list-style: none;
}
.thankEditor ul> li::before{
content: '☞'; color: red;
content: '☞'; color: #0b4a6f;
margin-right: .5em;
}
.thankEditor ol {
@@ -247,20 +277,27 @@ html{
.thankEditor{
width: 95vw;
height: 45vh;
margin: 0 auto;
height: calc(30vh - 1.5em);
margin: .5em auto;
padding: 1.25em 1.75em;
box-sizing: border-box;
-webkit-transition: none;
transition: none;
-webkit-transform: rotateX(-10deg) translateZ(-200px);
transform: rotateX(-10deg) translateZ(-200px);
}
/* keep the page itself from ever scrolling */
body{
overflow-x: hidden;
}
`],
mobileStyles: [
`/*
* GitHub: https://github.com/hoelee/
* Hi, I'm Mr. HoeleeWeb3 Developer & Full Stack Engineer
* Gitea: https://git.hoelee.com/hoelee
* Hi, I'm Mr. Hoelee — Full Stack Engineer, AI & Automation, DevOps
* Let me create an animated resume!
*/
@@ -377,5 +414,10 @@ html{
.resumeEditor a:hover {
text-decoration: underline;
}
/* keep the page itself from ever scrolling */
html, body{
overflow-x: hidden;
max-width: 100%;
}
`]
}