e89295
TIL,《我的部落格》的網站名稱「e89295」,是用三個十六進位的 UTF-8 位元組來表示的:0xE8、0x92、0x95。
new TextDecoder().decode(new Uint8Array([0xE8, 0x92, 0x95]))
TIL,《我的部落格》的網站名稱「e89295」,是用三個十六進位的 UTF-8 位元組來表示的:0xE8、0x92、0x95。
new TextDecoder().decode(new Uint8Array([0xE8, 0x92, 0x95]))
前幾天看到 Eddie 在這篇文章中提到,戒了 IG 後「會想一直開自己網站刷留言,看看有沒有新留言!」。
其實我的這套「留言系統」有接「通知功能」耶!只要有人留言,就會觸發 Telegram 的機器人通知我,這樣我就能快快審核,不用定時一直上去檢查。
不過,更簡單的方法應該是:Google Apps Script 內建直接用 Gmail 發信,這樣還能順便備份留言,一舉兩得,簡單又好用!(但我還沒試過,講得我自己都想用了)
有時候會希望程式跑完某件事之後,自動發個通知給自己,像是排程結束、伺服器異常、或是有人填了表單之類的。
這件事其實用 Telegram 來做超簡單,只要申請一個 BOT、拿到 Chat ID,然後用 POST 打一下 Telegram 的 API 就搞定了!
BTW,其實我早期一直都是用 Line Notify 來做通知,但是 Line 又複雜又難用,而且 2025 年 Notify 功能就收掉了,於是就改成用 Telegram 來做通知了。
今天看到 I'm Marcus 的文章:「来玩玩新开发的小游戏吧」。
Marcus 在自己的部落格實作了一套基於「閱讀軌跡」的文章推薦系統,這個設計還滿有意思的。
AVG(喜歡文章向量) - AVG(不喜歡文章向量)Cosine Similarity(使用者向量, 未反饋文章)今天跟 GPT 學到了用 FFMPEG 製作 Nightcore 風格音樂的方法。
Nightcore 的核心特徵是「音調變高、速度變快」,聽起來像花栗鼠在唱歌的那種感覺。
ffmpeg -i input.mp3 -af "asetrate=44100*1.35,aresample=44100" output.mp3
asetrate=44100*1.35:改變音訊的播放速率,將採樣率強制改為原來的 1.35 倍,音調與速度都會同步提高。
aresample=44100:將採樣率重採樣回標準的 44100Hz,確保檔案在所有設備上都能正常播放。
atempo=1.05:純速度濾波器,不影響音調,用來微調最終節奏。例如可以這樣搭配:
ffmpeg -i input.mp3 -af "asetrate=44100*1.3,aresample=44100,atempo=1.05" output.mp3
最近幫部落格做了一個「相關文章」功能,用 AI 的語義向量(Embedding)來計算文章之間的相似度。
原文:《DIY 系列:來做個「相關文章」功能》
三個步驟:
把文章餵給 Embedding 模型,得到一組向量(一串浮點數)。這組向量代表文章的「語意」,語意越接近的文章,向量在空間中的距離也越近。
計算餘弦相似度,也就是兩個向量之間的夾角,越接近 1 代表越相似。
排序取前 K 名,就是「相關文章」了。
方案一:Gemini API(免費)
到 Google AI Studio 申請 API Key,呼叫 gemini-embedding-001 模型。
免費方案有速率限制,我的做法是每篇截取前 2000 字,每隔兩秒呼叫一次。
方案二:BGE-M3 本地端(也免費)
用 Ollama 在本機跑 BGE-M3,CPU 就能跑,完全離線。
ollama pull bge-m3
pip install ollama
import ollama
res = ollama.embeddings(model="bge-m3", prompt="文章內容...")
vector = res["embedding"]
每次重新計算 Embedding 很耗時,所以用 Hash(標題 + 內文) 來判斷文章是否有改變,沒變就直接讀快取。
相似度計算目前是全部重跑(讓新舊文章可以互相連結),一百多篇大概三秒,還可以接受。
related.json 或直接生成靜態 HTML。我自己在目前部落格上有三種實際應用場景:
Preview 網頁自動 reload:這個場景是高頻檢查,例如每秒看一次檔案有沒有變。就算偶爾誤判,最多只是多 reload 一次,所以我用檔案修改時間來判斷。這邊我用 Polling 的方式來做,雖然 I/O 比較重一點,但實現起來簡單,也不用去安裝其他套件。
Build 建立靜態網站:這邊的重點是正確性,要精準知道內容是否真的改變,避免不必要重建或漏建,所以我會直接比對全文內容,也就是比對「渲染後的文章」與「輸出資料夾的文章」。
檔案變動後要打 API:打 API 通常有副作用,不能亂觸發。這時候不一定要知道改了哪裡,只要知道內容真的不同即可,所以我會把 Hash 存起來,如果有比對到 Hash 變更再去打 API。這種做法很優雅,不需要多留一份原始檔案,只要保留原檔案的 Hash 值就好了。
| 方法 | 核心概念 | 速度 | 準確度 | 代價 |
|---|---|---|---|---|
| 時間戳 | 比對檔案最後修改時間 | 很快 | 中 | 可能會誤判 |
| 全文比對 | 逐字比較內容 | 慢 | 高 | 需要讀兩份完整檔案 |
| Hash 比對 | 比較最終內容 Hash | 快 | 高 | 需額外保存 Hash 值 |
只要看檔案最後修改時間有沒有變,就能快速判斷「可能有變更」。
直接把新舊檔案內容拿來比,這是最準確的方法。
做法是先計算檔案 Hash,把它存起來,下次再算一次做比對。
實務上,若是有副作用的操作(例如打 API、觸發部署、通知、計費),Hash 很適合。
今天想嘗試幫網站做一個 theme。
以前我也曾經做過幾次修改,比如說「聖誕節主題」、「抹茶主題」等等。
原本我都是直接去修改 styles.css,後來發現這樣要做「期間限定」的 Theme 不是很方便。
所以我就把一些常見的顏色改成參數,可以直接用參數改變顏色,直接幫網站換 Theme。但有時候是連結構都會改變,這時候只改變數就不太夠用了(搞得太複雜也不好維護)。
所以呢,以下整理介紹三種常見的 Theme 做法:
先把顏色、字體、間距等抽成 CSS 變數(Custom Properties),切換 Theme 時只要替換一組變數值。
/* 預設主題 */
:root {
--color-bg: #fff;
--color-text: #333;
}
/* 另一個 Theme */
:root[data-theme="retro"] {
--color-bg: #f2f2f2;
--color-text: #111;
}
box-shadow、border-radius、order 這類結構型樣式,通常不夠用。直接維護兩份完整 CSS(例如 default.css、theme.css),切換時整份替換。
保留原本的 styles.css,再額外新增一份 theme.css,只覆蓋需要改動的規則。
.post-card {
background: #f2f2f2;
border-radius: 0;
}
實作上,只要在 HTML 的 <head> 多載入一行:
<link rel="stylesheet" href="/assets/css/styles.css">
<link rel="stylesheet" href="/assets/css/theme.css"> <!-- 加這行 -->
theme.css 直接用相同選擇器覆蓋 styles.css。由於它在後面載入,在相同優先權下會覆蓋前者(CSS cascade)。
可以用 JS 搭配 body class 做 runtime 切換:
document.body.classList.toggle('theme-alt');
這時候,Theme 規則就要加上 body.theme-alt 前綴:
body.theme-alt .post-card { ... }
body.theme-alt .post-title { ... }
如果你要的是「快速上線、可恢復、又能改結構樣式」,通常 CSS 覆蓋會是最平衡的做法。
今天做了一個很白爛的圖層魔術。
按下按鈕後,帽子先往下蓋住,再掀開,裡面會冒出兔子、青蛙,或者乾脆什麼都沒有。
position: relative:讓舞台變成絕對定位元素的參考點。position: absolute:把每張圖放到指定座標。z-index:控制遮擋順序。帽子要蓋住角色,所以層級要最高。transition:當 top 被改變時,自動產生移動動畫,做出「蓋住 -> 掀開」的節奏。display: none 雖然簡單,但不能直接做淡入淡出;如果需要更順的切換,可以改用 opacity 搭配 visibility。圖片請自備~(或者去文章內右鍵下載)
<div class="magic-stage">
<style>
.magic-stage { position: relative; width: 300px; height: 400px; margin: 0px auto; text-align: center; }
.magic-stage img { position: absolute; transition: top 0.6s ease-in-out; }
.magic-stage .magic-table { width: 280px; left: 10px; top: 250px; z-index: 1; }
.magic-stage .magic-hat { width: 160px; left: 70px; top: 0; z-index: 3; }
.magic-stage .magic-rabbit { width: 80px; left: 110px; top: 170px; z-index: 2; display: none; }
.magic-stage .magic-frog { width: 80px; left: 110px; top: 195px; z-index: 2; display: none; }
.magic-stage .active { display: block; }
.magic-stage button { margin-top: 340px; padding: 10px 30px; cursor: pointer; border: 2px solid #6200ee; border-radius: 4px; background: #6200ee; color: white; font-size: 16px; font-weight: bold; transition: 0.2s; }
.magic-stage button:disabled { background: #ccc; border-color: #ccc; cursor: not-allowed; }
</style>
<img src="table.png" class="magic-table" alt="magic-table">
<img src="hat.png" class="magic-hat" alt="magic-hat">
<img src="rabbit.png" class="magic-rabbit" alt="magic-rabbit">
<img src="frog.png" class="magic-frog" alt="magic-frog">
<button class="magic-button" onclick="performMagic()">Magic</button>
<script>
let magicStep = 0;
function performMagic() {
const magicStage = document.querySelector('.magic-stage');
const magicHat = magicStage.querySelector('.magic-hat');
const magicRabbit = magicStage.querySelector('.magic-rabbit');
const magicFrog = magicStage.querySelector('.magic-frog');
const magicButton = magicStage.querySelector('.magic-button');
magicButton.disabled = true;
magicHat.style.top = "140px";
setTimeout(() => {
magicRabbit.classList.remove('active');
magicFrog.classList.remove('active');
if (magicStep === 0) {
magicRabbit.classList.add('active');
} else if (magicStep === 1) {
magicFrog.classList.add('active');
}
magicStep = (magicStep + 1) % 3;
magicHat.style.top = "0px";
setTimeout(() => magicButton.disabled = false, 600);
}, 1250);
}
</script>
</div>
上週遇到一個狀況:
Cloudflare Pages 已經綁定 GitHub,但 git push 之後,沒有自動觸發新的部署(至今仍然不知道為啥)。
後來我又嘗試推了一個空的 commit 上去,結果還是沒觸發(很蠢,我知道)。
查了一下資料,改成使用 Cloudflare Pages 的 Deploy Hook,結果就成功觸發了。
改成 Deploy Hook 後,部署有成功觸發,網站也有順利更新。
昨天看到了「How to make a website in 5 minutes」這篇文章。
突然覺得可以重新設計一版架構,選擇用我喜歡的方式,設計一個非常、非常簡單,功能也完整的部落格架構。
最基本的頁面就只有:
架構如下:
blog/
├── index.html # 首頁
├── feed.xml # RSS Feed
├── assets/ # 放全站通用的資源
│ └── style.css # 網站樣式
└── posts/ # 文章目錄
├── index.html # 文章列表頁面
└── 2026-01-01-first-post/
├── index.html # 文章頁面
└── img.webp # 文章圖片
我最早的部落格更是離譜,就只有一個頁面:
blog/
├── index.html # 首頁
└── style.css # 網站樣式
我的「貼文」很短,通常就一兩行而已,感覺沒必要做成單獨頁面。而且如果首頁和內頁都要顯示全文,就等於要維護兩份一模一樣的內容。
而且如果共用區塊有改動,所有文章檔案都要跟著改,這樣維護起來也不太實際。再加上要取英文 slug、要管理檔案命名、要按日期排序...
種種理由加起來,我的藉口就是:「一開始只想做最簡單的可行方案,不想搞得太複雜」。
那麼...沒有做獨立頁面會有什麼問題?
最明顯的就是文章數量越來越多,首頁的長度真的會太長。我原本想用「年度分頁」來解決,但這樣一來 RSS 的文章連結就要每年換一次,不太合理。
而且沒有獨立頁面的話,要分享或引用單篇文章也很不方便,對讀者和搜尋引擎(SEO)都不太友善。
現在回頭想想,首頁根本不需要顯示全文,只要有文章列表就好,一般人都是用 RSS 訂閱,會來網站上的人也會從列表上挑感興趣的文章來讀。
我當初應該是想模仿社群平台一篇一篇貼文可以直接看的感覺才這樣做的。(原本讀者只有我自己)
至於「共用區塊」的部分,其實有很多方法可以解決:
那我這次是怎麼解決這個問題的呢......?當然是「只放返回按鈕」啊,夠簡單吧!
我可能會再重新寫一篇(或多篇)文章,詳細講一下我是怎麼一步步建立出來的(有需要嗎),以及這樣設計的想法是什麼。
我甚至已經在示範部落格裡面寫了幾篇教學文章,直接把專案抓下來,邊用邊參考也沒問題。