diff --git a/public/images/lee-teong-hoe-128.jpg b/public/images/lee-teong-hoe-128.jpg new file mode 100644 index 0000000..ba89992 Binary files /dev/null and b/public/images/lee-teong-hoe-128.jpg differ diff --git a/public/images/lee-teong-hoe-128.webp b/public/images/lee-teong-hoe-128.webp new file mode 100644 index 0000000..36beb31 Binary files /dev/null and b/public/images/lee-teong-hoe-128.webp differ diff --git a/public/images/lee-teong-hoe-640.jpg b/public/images/lee-teong-hoe-640.jpg new file mode 100644 index 0000000..da4d270 Binary files /dev/null and b/public/images/lee-teong-hoe-640.jpg differ diff --git a/public/images/lee-teong-hoe-640.webp b/public/images/lee-teong-hoe-640.webp new file mode 100644 index 0000000..3bf9819 Binary files /dev/null and b/public/images/lee-teong-hoe-640.webp differ diff --git a/src/components/AuthorCard.astro b/src/components/AuthorCard.astro index 43665b0..95d3917 100644 --- a/src/components/AuthorCard.astro +++ b/src/components/AuthorCard.astro @@ -3,21 +3,62 @@ import { SITE } from '../config'; interface Props { bio?: string; + lang?: 'en' | 'zh'; } -const { bio = 'Full-stack developer & DevOps engineer. I build web apps, self-host infrastructure, and automate things — this blog is my living portfolio.' } = Astro.props; +const { + bio = 'Full-stack developer & DevOps engineer based in Malaysia. I run my own production infrastructure and build web systems for small businesses — this blog is the living record.', + lang = 'en', +} = Astro.props; +const isZh = lang === 'zh'; + +const t = isZh + ? { + hiring: '招聘方:技术栈与履历', + hiringHref: '/zh/about/', + sme: '马来西亚中小企业:网站设计与开发', + smeHref: SITE.website, + stack: '全部技术文章', + stackHref: '/zh/posts/', + } + : { + hiring: 'Hiring? Profile & stack', + hiringHref: '/about/', + sme: 'Malaysian SME? Website design & development', + smeHref: SITE.website, + stack: 'All posts', + stackHref: '/posts/', + }; ---
- -
-

{SITE.author}

+ + + {SITE.author} + +
+

{SITE.author} · {SITE.handle}

{bio}

+

+ {t.hiring} + · + {t.sme} ↗ + · + {t.stack} +

diff --git a/src/config.ts b/src/config.ts index 73a46ec..9dcd841 100644 --- a/src/config.ts +++ b/src/config.ts @@ -17,4 +17,14 @@ export const SITE = { gitea: 'https://git.hoelee.com/hoelee', website: 'https://www.hoelee.com', twitter: '', // add if you have one + // Author photo (B4, 2026-10-16). One real headshot, two sizes, WebP first with the + // JPEG as fallback — continuous-tone images are the case where JPEG/WebP beat PNG. + // Used by: the About page, the author card on every post, the home hero, and the + // JSON-LD `Person.image` (the identity signal search engines read). + photo: { + large: '/images/lee-teong-hoe-640.jpg', // 640x640 — About page (320 CSS px @2x) + largeWebp: '/images/lee-teong-hoe-640.webp', + small: '/images/lee-teong-hoe-128.jpg', // 128x128 — author card avatar (64 CSS px @2x) + smallWebp: '/images/lee-teong-hoe-128.webp', + }, } as const; diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index cee842a..e8ecffe 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -130,6 +130,7 @@ const nav = isZh alternateName: SITE.handle, url: SITE.url, email: SITE.email, + image: new URL(SITE.photo.large, SITE.url).href, sameAs: [SITE.linkedin, SITE.github, SITE.gitea, SITE.website].filter(Boolean), jobTitle: 'Full-stack Developer & DevOps Engineer', // The consultancy that does the client work — ties the blog's author node to diff --git a/src/pages/about.astro b/src/pages/about.astro index 08b1bd8..eafbeab 100644 --- a/src/pages/about.astro +++ b/src/pages/about.astro @@ -6,36 +6,130 @@ import { SITE } from '../config'; -

About

+
+ + + {`${SITE.author} + +
+

{SITE.author} ({SITE.handle})

+

+ Full-stack developer and DevOps engineer in Malaysia. I run the production + infrastructure behind my own and my clients' systems — + 162 containers across three self-managed hosts — and I build + web systems for Malaysian small businesses. +

+ +

Open to remote / hybrid engineering roles.

+
+
+ +

By the numbers

+
+
162containers in production
+
3self-managed hosts (78 compose stacks)
+
71write-ups, each in English and 中文
+
0hosting companies between me and the servers
+

- I'm {SITE.author} ({SITE.handle}), a full-stack developer and DevOps engineer - based in Malaysia. I build web applications — PHP/CodeIgniter, Java/Spring, - JavaScript/TypeScript/React — and self-host a ~140-container homelab - (Docker, Traefik, nginx, Cloudflare tunnels). I run Hoelee Enterprise, a - website design & development business for Malaysian SMEs (email hosting is - a side offering, not the focus). -

-

- This blog is my living portfolio: I document what I build, from self-hosted - CI/CD and Docker orchestration to Web3 experiments and AI automation. If - you're a recruiter, a client, or a fellow builder, the posts here are the - most honest picture of what I can do. + That footprint is client websites, a mail platform, CI/CD, workflow automation, + analytics and this blog — held together by hand. It is also why the posts here + are specific: when something breaks in production, I am the one who finds out, + and the write-up is the record of what the evidence actually said.

- +

What I actually work with

+
+
+

Infrastructure & DevOps

+
    +
  • Docker & Compose, Traefik, nginx, Cloudflare tunnels
  • +
  • Gitea Actions (self-hosted CI/CD), act_runner
  • +
  • Prometheus, Grafana, cAdvisor, Uptime Kuma
  • +
  • Unraid, Synology DSM, Linux (Debian/Ubuntu/Alpine)
  • +
+
+
+

Backend & automation

+
    +
  • PHP / CodeIgniter, Java / Spring
  • +
  • Node.js / TypeScript, Python
  • +
  • n8n, NocoDB, PostgreSQL / MySQL
  • +
  • REST APIs, webhooks, browser automation (CDP/Playwright)
  • +
+
+
+

Frontend & web

+
    +
  • Astro, JavaScript / TypeScript, React
  • +
  • WordPress (Kadence, Elementor, custom plugins)
  • +
  • Core Web Vitals, accessibility, responsive layout
  • +
+
+
+

Self-hosted services I run

+
    +
  • Authentik SSO, Mailcow, AList, Mem0
  • +
  • Umami analytics, Tor onion services
  • +
  • Whisper/TTS inference on a local GPU
  • +
+
+
-

Contact

-

- {SITE.email} - · - LinkedIn - · - git.hoelee.com - · - www.hoelee.com -

+

For Malaysian SMEs — Hoelee Enterprise

+
+

+ Website design and development is my main business — fast, + maintainable sites for Malaysian small and medium businesses, from the design + through to the hosting, DNS, backups and monitoring underneath it. + Email hosting is a secondary offering, not the focus. +

+

+ Because I run the infrastructure myself, you get one person who can answer for + the whole stack instead of a developer who cannot see the server and a hosting + company that cannot read the code. +

+

+ www.hoelee.com + · + WhatsApp +60 12-797 2969 +

+
+ +

Get in touch

+
+
+

For hiring & engineering roles

+

{SITE.email}

+

LinkedIn · GitHub

+

Remote or hybrid; available for a technical conversation any time.

+
+
+

For business & web projects

+

WhatsApp +60 12-797 2969

+

www.hoelee.com

+

Malaysian SMEs: website design & development, hosting and maintenance.

+
+
+ +
diff --git a/src/pages/index.astro b/src/pages/index.astro index 2e2db06..3c44190 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -25,7 +25,20 @@ const featuredPosts = featured.length === 4 ? featured : posts.slice(0, 4);
-

{SITE.author} — full-stack developer & DevOps engineer

+
+ + + {`${SITE.author} + +

{SITE.author} — full-stack developer & DevOps engineer

+

I run 162 containers across three self-managed hosts — client sites, a mail platform, CI/CD, workflow automation and this blog — and I build web systems for diff --git a/src/pages/zh/about.astro b/src/pages/zh/about.astro index e65fc0f..9d6643e 100644 --- a/src/pages/zh/about.astro +++ b/src/pages/zh/about.astro @@ -6,35 +6,125 @@ import { SITE } from '../../config'; -

关于

+
+ + + {`${SITE.author}(${SITE.handle})`} + +
+

{SITE.author} ({SITE.handle})

+

+ 在马来西亚的全栈开发者兼 DevOps 工程师。我亲手运维自己和客户系统背后的生产环境—— + 三台自管服务器上的 162 个容器——并为马来西亚中小企业构建 Web 系统。 +

+ +

接受 远程 / 混合 工程岗位机会。

+
+
+ +

几个数字

+
+
162个生产环境容器
+
3台自管服务器(78 个 compose 栈)
+
71篇长文,每篇中英双版
+
0夹在我和服务器之间的托管商
+

- 我是 {SITE.author}({SITE.handle}),一名base在马来西亚的全栈开发者兼 DevOps - 工程师。我做 Web 应用——PHP/CodeIgniter、Java/Spring、JavaScript/TypeScript/React—— - 并自托管一个约 140 个容器的 homelab(Docker、Traefik、nginx、Cloudflare tunnel)。 - 我运营 Hoelee Enterprise,一家面向马来西亚中小企业的网站设计开发公司 - (邮箱托管只是副业,并非主业)。 -

-

- 这个博客就是我的动态作品集:我把自己构建的东西记录下来,从自托管 CI/CD、 - Docker 编排,到 Web3 实验与 AI 自动化。无论你是招聘方、客户,还是同路的 - 开发者,这里的文章都是我能做什么最诚实的呈现。 + 这套环境里有客户网站、邮件平台、CI/CD、流程自动化、统计和这个博客,全靠手工拴在一起。 + 这也是为什么这里的文章都很具体:生产环境出问题时,第一个知道的人是我, + 文章就是「证据到底说了什么」的记录。

- +

我实际在用的东西

+
+
+

基建与 DevOps

+
    +
  • Docker 与 Compose、Traefik、nginx、Cloudflare tunnel
  • +
  • Gitea Actions(自托管 CI/CD)、act_runner
  • +
  • Prometheus、Grafana、cAdvisor、Uptime Kuma
  • +
  • Unraid、Synology DSM、Linux(Debian/Ubuntu/Alpine)
  • +
+
+
+

后端与自动化

+
    +
  • PHP / CodeIgniter、Java / Spring
  • +
  • Node.js / TypeScript、Python
  • +
  • n8n、NocoDB、PostgreSQL / MySQL
  • +
  • REST API、webhook、浏览器自动化(CDP/Playwright)
  • +
+
+
+

前端与 Web

+
    +
  • Astro、JavaScript / TypeScript、React
  • +
  • WordPress(Kadence、Elementor、自写插件)
  • +
  • Core Web Vitals、可访问性、响应式布局
  • +
+
+
+

我自托管的服务

+
    +
  • Authentik SSO、Mailcow、AList、Mem0
  • +
  • Umami 统计、Tor onion service
  • +
  • 本地 GPU 上的 Whisper / TTS 推理
  • +
+
+
-

联系方式

-

- {SITE.email} - · - LinkedIn - · - git.hoelee.com - · - www.hoelee.com -

+

面向马来西亚中小企业 —— Hoelee Enterprise

+
+

+ 网站设计与开发是我的主业——为马来西亚中小企业做快速、好维护的网站, + 从设计一直到它底下的托管、DNS、备份与监控。邮箱托管是次要副业,并非主业。 +

+

+ 因为基建是我自己运维的,你面对的是一个人而不是两方——不会出现「写代码的人看不到服务器、 + 托管商读不懂代码」的情况。 +

+

+ www.hoelee.com + · + WhatsApp +60 12-797 2969 +

+
+ +

联系

+
+
+

招聘 / 工程岗位

+

{SITE.email}

+

LinkedIn · GitHub

+

远程或混合;随时可以聊技术。

+
+
+

商业项目 / 网站

+

WhatsApp +60 12-797 2969

+

www.hoelee.com

+

马来西亚中小企业:网站设计开发、托管与维护。

+
+
+ + diff --git a/src/pages/zh/index.astro b/src/pages/zh/index.astro index 41f5817..a211c6d 100644 --- a/src/pages/zh/index.astro +++ b/src/pages/zh/index.astro @@ -29,7 +29,20 @@ const featuredPosts = featured.length === 4 ? featured : posts.slice(0, 4); altLocaleUrl={`${SITE.url}/`} >
-

{SITE.author} —— 全栈开发者兼 DevOps 工程师

+
+ + + {`${SITE.author}(${SITE.handle})`} + +

{SITE.author} —— 全栈开发者兼 DevOps 工程师

+

我独自运维 三台自管服务器上的 162 个容器(客户站点、邮件平台、 CI/CD、流程自动化与这个博客),并为马来西亚中小企业构建 Web 系统。这里写的是 diff --git a/src/styles/global.css b/src/styles/global.css index d655078..ac5ac93 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -284,20 +284,88 @@ pre code { border-radius: var(--radius); } .author-card .avatar { - width: 56px; - height: 56px; + width: 64px; + height: 64px; border-radius: 50%; - background: var(--brand); - color: #fff; - display: grid; - place-items: center; - font-weight: 700; - font-size: 1.3rem; + object-fit: cover; + border: 1px solid var(--border); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--brand) 25%, transparent); flex-shrink: 0; + display: block; } +.author-card .author-body { min-width: 0; } .author-card .who { font-weight: 600; margin: 0; } .author-card .bio { color: var(--muted); font-size: 0.9rem; margin: 0.25rem 0 0.5rem; } -.author-card .links { display: flex; gap: 0.75rem; font-size: 0.85rem; } +.author-card .links { display: flex; gap: 0.75rem; font-size: 0.85rem; flex-wrap: wrap; } +.author-card .author-split { font-size: 0.82rem; color: var(--muted); margin: 0.6rem 0 0; } +.author-card .author-split a { color: var(--muted); } +.author-card .author-split a:hover { color: var(--brand); } + +/* ---------- About ---------- */ +.about-hero { + display: flex; + gap: 1.75rem; + align-items: flex-start; + margin: 0.5rem 0 2.25rem; + padding-bottom: 1.75rem; + border-bottom: 1px solid var(--border); +} +.about-photo { + width: 190px; + height: 190px; + border-radius: var(--radius); + border: 1px solid var(--border); + /* The headshot's own dark-blue background sits close to the page background, so a + faint brand ring + shadow is what actually separates it from the UI. */ + box-shadow: 0 0 0 1px color-mix(in srgb, var(--brand) 30%, transparent), + 0 10px 28px rgba(0, 0, 0, 0.35); + object-fit: cover; + flex-shrink: 0; + display: block; +} +.about-hero h1 { margin-bottom: 0.6rem; } +.about-hero h1 .handle { color: var(--muted); font-weight: 500; } +.about-lede { font-size: 1.02rem; color: var(--muted); margin-bottom: 0.75rem; } +.about-links { font-size: 0.9rem; margin-bottom: 0.5rem; } +.about-open { font-size: 0.9rem; color: var(--muted); margin: 0; } + +.proof-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0.85rem; + margin: 1rem 0 1.25rem; +} +.proof-card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 0.85rem 0.9rem; +} +.proof-card .n { display: block; font-size: 1.45rem; font-weight: 700; line-height: 1.15; } +.proof-card .l { display: block; font-size: 0.78rem; color: var(--muted); margin-top: 0.25rem; } + +.stack-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; + margin: 1rem 0 1.5rem; +} +.stack-grid h3 { font-size: 0.95rem; margin: 0 0 0.4rem; } +.stack-grid ul { margin: 0; padding-left: 1.1rem; } +.stack-grid li { font-size: 0.88rem; color: var(--muted); margin: 0.15rem 0; } + +.sme-block { + background: var(--surface); + border: 1px solid var(--border); + border-left: 3px solid var(--brand); + border-radius: var(--radius); + padding: 1rem 1.1rem; + margin: 1rem 0 1.5rem; +} +.sme-block p { margin: 0.4rem 0; } +.contact-split { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.25rem; margin-top: 1rem; } +.contact-split h3 { font-size: 0.95rem; margin: 0 0 0.35rem; } +.contact-split p { margin: 0.2rem 0; font-size: 0.9rem; color: var(--muted); } /* ---------- Related posts ---------- */ .related { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--border); } @@ -346,7 +414,18 @@ pre code { padding-bottom: 1.5rem; border-bottom: 1px solid var(--border); } -.hero h1 { margin-bottom: 0.75rem; } +.hero-id { display: flex; align-items: center; gap: 1rem; margin-bottom: 0.75rem; } +.hero-photo { + width: 72px; + height: 72px; + border-radius: 50%; + border: 1px solid var(--border); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--brand) 30%, transparent); + object-fit: cover; + flex-shrink: 0; + display: block; +} +.hero h1 { margin: 0; } .hero-sub { font-size: 1.05rem; color: var(--muted); max-width: 62ch; } .hero-cta { display: flex; @@ -642,6 +721,10 @@ a.category-card:hover { border-color: var(--brand); } h2 { font-size: 1.25rem; } .related-grid { grid-template-columns: 1fr; } .featured-grid { grid-template-columns: 1fr; } + .proof-grid { grid-template-columns: repeat(2, 1fr); } + .stack-grid, .contact-split { grid-template-columns: 1fr; } + .about-hero { flex-direction: column; gap: 1rem; } + .about-photo { width: 140px; height: 140px; } .search-hint, .search-key { display: none; } .author-card { flex-direction: column; align-items: flex-start; } }