Skip to content

Repository files navigation

放映室 — 個人網站

作品集兼個人門面。取代 G:\Projects\room。 開場動畫的骨架參考 jasonbergh.com 的 preloader。


⚠️ 唯一的下一步:補上你的臉

名字已經填好(site.ts 裡是「朱 / 亞承」,全站生效)。現在只差影像。

站上的大頭照與生活照目前都是打樣佔位框(site.ts 的 portrait 與 about.photos 仍是 null)—— 開場動畫交棒後的第一眼、關於我那面照片牆,現在都是空框。這是整站唯一沒收尾的一塊,也是只有你能補的:

  1. 大頭照(首頁第一眼)→ 一張 4:5 直式正面照放到 public/media/portrait.webp,路徑填進 site.ts 的 portrait。
  2. 4–6 張生活照 → 放進 public/media/(life-01.webp、life-02.webp…),填進 about.photos。

檔名對就會自動出現,不用改其他程式(見下方「待補素材清單」)。補了素材記得重啟 dev server——media.ts 在載入時就掃完資料夾,之後新增的看不到。


跑起來

cd G:\Projects\personal-site && npm run dev

開 http://localhost:4321 。

⚠️ 補了 public/media/ 的素材要重啟 dev server。 media.ts 在模組載入時就把資料夾掃完了,之後新增的檔案它看不到 —— 而且不會報錯,畫面只是繼續顯示打樣框,看起來像「還沒補」。 npm run build 每次都重掃,所以正式站不受影響。

開場動畫同一個分頁只播一次;要重看:

sessionStorage.clear(); location.reload()

待補素材清單

文字內容全部是真的(從 room 繼承,13 筆網址都驗證過),只有影像留白。 留白處會顯示「打樣佔位框」——1px 規線的空框加對角十字與尺寸標註,不是壞掉的灰方塊。

# 要補什麼 放哪裡 改哪一行 規格
1 你的名字 ✅ 已填(朱/亞承) — src/data/site.ts → name —
2 大頭照 ← 首頁第一眼 public/media/portrait.webp src/data/site.ts → portrait 4:5 直式,正面、乾淨
3 4–6 張生活照 public/media/ src/data/site.ts → about.photos 直橫交錯,ratio 一起改
4 學經歷 — src/data/site.ts → about.path(標 todo: true 的兩列) 只寫真的;沒有就整列刪掉
5 9 段操作錄影 public/media/<slug>.mp4 — 檔名對就好 由 tools/record 自動產生
6 ✅ 縮圖 9 張已抓好(tools/record/covers.mjs)。剩 4 個是沒上線的站(房間/Video-to-3D/LangAlpha/第二大腦),沒網址就抓不了,維持打樣框 public/media/<slug>-cover.webp — 檔名對就好 3:2 橫式
7 社群連結 — src/data/site.ts → contact.links(沒有的整條刪掉) —
8 正式網址 — astro.config.mjs → site —
9 確認年份 — src/data/projects.ts → year(目前一律 2026,依資料夾時間推得,請核對) —

學經歷目前沒有打樣列(2026-09-29:學歷列已填,空的經歷列已刪)。之後要先佔位的列標 todo: true, 站上會用虛線+淡字+[ 待補 ] 呈現,跟圖片打樣框同一套語彙。 填好之後把 todo 那一行刪掉就會轉成正常樣式。沒有的經歷就把整列刪掉 —— 空欄比假資歷好看,而且這站的規矩是不捏造獎項、客戶與數字。

補素材=把檔案丟進 public/media/,檔名對就好

不用改任何資料檔。 build 時會掃 public/media/,照檔名對回作品 (見 src/data/media.ts):

檔名 出現在哪
<slug>.mp4 作品舞台中央的操作錄影
<slug>-still.webp 它的封面(錄影管線會自動抽)
<slug>-cover.webp 開場膠捲與上下偷看格的縮圖
portrait.webp 首頁第一幕的大頭照
life-01.webp、life-02.webp… 關於我的生活照,照編號排序

生活照的張數由檔案決定、比例由圖片本身決定(build 時直接讀檔頭)—— 照片牆是交錯排的,硬套一個比例等於把每張照片都裁一刀。

沒有的檔案就繼續顯示打樣佔位框,不會破版。

操作錄影:手錄的流程

現在站上那 9 支是腳本錄的(下一節),站主決定改成自己錄。手錄只負責「拍」, 剪接、對規格、無縫循環、抽封面、接上網站都交給 fromraw.mjs。

錄多久

每支錄 20–30 秒,最後只留 8 秒。

不要對著碼表錄 8 秒 —— 會為了卡時間而趕,而且第 6 秒手一抖整條就得重來。 一鏡到底錄一段長的:兩個動作慢慢做、該停就停,中間搞砸了不要停,原地再做一次, 剪的時候挑乾淨的那一段。九支加起來大概 30–45 分鐘。

成品維持 8 秒的理由:舞台換幕冷卻 900ms,訪客多半只看得到前 2–3 秒, 所以第一個動作必須在 3 秒內做完;但低於 5 秒,循環的接點會一直撞人。

怎麼錄

  1. Win+Alt+R(Xbox Game Bar),瀏覽器按 F11 全螢幕。 影格是 16:9,留著網址列與工作列的話,裁掉的一定是你在意的那一邊。
  2. 一個動作,一次到位:移過去(一次)→ 停 1 秒 → 按下去 → 停 1.5 秒讓結果落定。 最多兩個動作。游標不要飄、不要 hover 到一半又移開、滾輪不要一格一格點。
  3. 不要錄空狀態。錄影用的瀏覽器 profile 常常是乾淨的, Mi Español 的預設首頁會是 0/30 XP、0 連續天數 —— 先切到「對話練習」再開錄。

拍什麼(實測過會有明顯變化的動作)

站 第一個動作 第二個動作
Mi Español 從「對話練習」開場 → 點側欄「歌詞分析」 點歌詞裡有底線的字 → 解析欄長出詞性、中文、變化表
世足 2026 點「跳過開場,直接進入」 捲到四個大數字 + 兩張黑底案例卡
夢想車庫 點右側車列第 2 台 點第 3 台
書櫃 滾輪橫推書櫃兩格 點中央分類卡 → 立體書封通道
唱片架 點下排收藏挑一張(背景跟著封面變色) 按播放 → 唱片從封套滑出
今天穿什麼 點「運動」情境鍵 點右上「我的衣櫃」分頁
我的衣櫃 滾輪把整圈轉半圈 點前排那件 → 放大+資訊卡
CPE 49 捲到九張題型卡填滿一屏 點 TYPE 07 → 跳到題庫、篩選自動套上
五層規劃 點「第三層 任務拆解」 點「完成總覽」

四個坑(都是實測撞到的):

  • 車庫只能點相鄰的車。 跨兩台以上會黑畫面約 1 秒才畫出來(一次只預載下一台)。
  • 穿搭站要等天氣載完再開錄,不然開頭是「抓取台中天氣中…」的骨架。
  • 穿搭站的「出門」與「上課」推出來完全一樣,點下去零反應 —— 那個站的 bug。用「運動」。
  • 書櫃點卡片後有約 0.7 秒整片純色(站自己的轉場),別把結尾停在那裡。

錄完之後

檔名改成 slug(bookshelf.mp4、mi-espanol.mp4…),丟進 tools/record/raw/,然後:

cd G:\Projects\personal-site\tools\record && node fromraw.mjs

列出 raw/ 有什麼。接著逐支挑進點 —— --scan 會每秒抽一格、左上角烙上秒數:

cd G:\Projects\personal-site\tools\record && node fromraw.mjs bookshelf --scan

看 check/raw-bookshelf.png 挑好進點(例如第 4 秒),然後:

cd G:\Projects\personal-site\tools\record && node fromraw.mjs bookshelf 4

它會裁成 16:9、縮到 1280×720、切 8 秒、接成無縫循環、抽封面、 放進 public/media/,並產生一張檢查圖。網站那邊不用改任何東西,檔名對就會出現。

第三個參數可以改長度:node fromraw.mjs bookshelf 4 7 = 從第 4 秒剪 7 秒。


操作錄影:腳本錄的那條路(目前站上的 9 支)

中央影格是這站的主角。 它不是靜態截圖,是「操作你的網站」的錄影。

錄影管線在 tools/record/:無頭 Chrome 開你的站、照腳本操作、錄成 1280×720 的 mp4,尾巴跟開頭交叉溶接成無縫循環,順便抽一張封面。

cd G:\Projects\personal-site\tools\record && node record.mjs bookshelf

不帶參數就把 choreo/ 底下全部重錄一遍。

  • probe.mjs — 掃 9 個已上線的站,量出結構,產生 probe.json
  • inspect.mjs <slug> --shots — 印出可點擊元素與座標,並存 5 張不同捲動位置的截圖
  • choreo/<slug>.mjs — 每個站的編排(要點哪裡、捲多少、停多久)
  • lib.mjs — 共用動作:tap / moveTo / scrollToPct / sleep,游標帶 ease

為什麼不手錄:手錄的游標會晃、節奏會抖,9 支的手感不會一致;站改版之後手錄要全部重來, 腳本重跑一次就好;而且尺寸、長度、檔案大小可以精準控制。

錄影的規矩

  • 8 秒。多數訪客只會看到前 2–3 秒(滾一下就換下一個作品了), 所以前三秒要能單獨成立;但太短(<5 秒)循環的接點會很明顯。
  • 最多兩個動作。 第三個動作只會讓前兩個都看不清楚。
  • 每個動作前後都要留白。沒有停頓的操作看起來像故障,不像示範。
  • 游標用 ease 移動,不走等速直線 —— 等速的游標一眼就知道是機器。
  • 只有 9 件上線的作品錄得到。 房間、Video-to-3D、LangAlpha、第二大腦 (進行中或不公開)沒有可以錄的網址,維持打樣框。

三條踩過的坑,改管線前先讀

① 溶接那一塊一定要放尾巴。 無縫循環是把尾 0.4 秒跟頭 0.4 秒交叉溶接。 接好的那塊放在開頭的話,影片第 0 格是「結尾淡出中」的疊影 —— 一播先閃一下自己的結局再溶回開頭。9 支全中,抽第 0 格才看出來。 正確順序是 [body][join]:body 從第 0.4 秒開始(乾淨),join 結束在第 0.4 秒(=body 的起點)。

② 游標要在深底與淺底上都看得見。 這 9 個站有黑底(唱片架)也有米白紙感 (五層規劃、Mi Español)。單純的白圈移動途中看得到,一停到白色按鈕上就整個消失 —— 而消失的那一刻正好是「按下去」,因果就斷了。所以是雙層環:外圈深、內圈亮。 (不用 mix-blend-mode: difference,它會把紅底翻成青色。)

③ load 不等於「畫面可以看了」。 夢想車庫要讀 8 個 glb,今天穿什麼要等天氣 API 回來重畫一次 —— 只給固定寬限的話,影片開頭會是「載入車庫 94%」或骨架。 record.mjs 會等 networkidle;還不夠的站在編排檔 export const ready, 那一段跑在裁切點之前,不會被錄進去。

空狀態不要當開場

錄影每次都是全新的瀏覽器 profile,localStorage 是空的。Mi Español 的預設首頁 因此是 0/30 XP、0 連續天數、0 已學會單字 —— 誠實,但等於在作品集上寫 「這個站沒人用過」,而那是唯一打算拿出去賣的產品。

解法不是去塞假的使用數據(那才真的不能做),是用 ready() 先切到一頁真的有內容的分區再開錄。Mi Español 現在從「對話練習」開場: 真的西語句子、可點的底線字、一個 0 都沒有。

素材資料夾的位置:兩種定位法各自只對一半

src/data/media.ts 要找到 public/media/,而 process.cwd() 與 import.meta.url 都只對一半:

  • process.cwd() build 時對(在專案根目錄跑),但 dev server 是 astro dev --root G:\... 從別的資料夾啟動的,cwd 不是專案根。
  • import.meta.url dev 時對(Vite 直接服務原始檔),但 build 時模組被打包到別的位置。

兩個都踩過:先是 dev 全部退回打樣框(build 卻是對的),改成相對模組之後換 build 全部退回。 兩次都沒有任何錯誤訊息,素材只是安靜地消失。現在是兩個候選路徑都試, 找不到會 console.warn 叫出來。改這一段之前先想清楚 dev 跟 build 會不會不一致。

看網站跑起來的樣子(面板叫不出來的時候)

開發用的瀏覽器面板如果沒有顯示,渲染管線是凍結的 —— 截不到圖、動畫也不會跑。 tour.mjs 用無頭 Chrome 把網站自己錄下來:

cd G:\Projects\personal-site\tools\record && node tour.mjs work

home 是開場動畫 → 往下捲=關於我;work 是作品舞台逐一翻過去(影格裡的錄影會播)。 產出在 check/tour-<which>.mp4,只給人看,不上站。

兩個必要的細節:無頭模式預設擋自動播放,要開 --autoplay-policy=no-user-gesture-required; 滾輪監聽掛在 .stage 上不是 window,游標停在預設的 (0,0) 會被固定導覽列吃掉,要先移到畫面中央。

錄影一次只能跑一支

Playwright 走 CDP screencast 收合成器吐出來的每一格,機器一忙就會少畫幾格, 少掉的格會被補成停格 —— 成品會頓,而且 log 上看不出來。 record.mjs 有檔案鎖會自動排隊;卡住的話刪掉 tools/record/.record.lock。


架構

src/
  data/
    site.ts        全站設定(名字、聯絡、導覽)── 標了 TODO 的是待補
    projects.ts    作品清單 ── 全站唯一真相來源,餵給開場膠捲/首頁舞台/作品頁
  styles/global.css  設計 token 與共用元件語彙(契約見 DESIGN.md)
  lib/
    motion.ts      ease 家族、頁面轉場布幕
    intro.ts       開場動畫時間軸(4.24s)
    stage.ts       首頁作品輪播
    rails.ts       邊緣呎數標尺的慣性迴圈
  components/
    Intro.astro    開場:膠捲+里程表+名字展開
    Stage.astro    首頁舞台
    Rails.astro    邊緣標尺
    Grain.astro    底片顆粒
    Placeholder.astro  打樣佔位框
    Nav / Foot
  layouts/Base.astro
  pages/
    index.astro       首頁:開場 → 關於我
    work/index.astro  作品輪播
    work/list.astro   作品列表
    journal.astro     遊記:去過的國家與地方

三個網址

網址 是什麼
/ 開場動畫 → 往下捲 = 關於我(臉 → 照片 → 自我介紹 → 學經歷 → 信箱)
/work 作品輪播。「Work」與「Projects」是同一件事,只有這一個入口
/work/list 同一批 13 件的列表看法,兩者由角落的「輪播 / 列表」互通

沒有獨立的 /about:首頁往下捲就是關於我,頁首的「關於」是錨點 /#about。 同一份自我介紹放兩個網址,訪客不是讀兩次就是兩次都不讀。

新增一個作品:只改 src/data/projects.ts 加一筆。開場膠捲、首頁舞台、作品頁三處會一起更新。


開場動畫

4.24 秒,四拍:

時間 發生什麼
0.0s 兩列作品縮圖反向轉起(膠捲上片),中央浮出「不做工具/做展覽」
1.1s 字卡升出鏡,里程表接位
1.2–2.7s 數字滾 00 → 99,到底閃一格紅(academy leader 的那一下)
2.7–3.6s 名字左右兩半展開,實體把膠捲推出畫面
3.7–4.24s 舞台從中央拉開,開場溶掉

可跳過:點任何地方、按任意鍵、滾一下、或右下角的跳過鍵。 同一 session 只播一次(sessionStorage),回訪直接進門。 prefers-reduced-motion 完全不播,直接呈現最終狀態。

交棒到大頭照

開場結束時,名字的兩半在視窗中線相接 —— 那個「口」就是首頁第一幕影格的位置。 影格用 clip-path 從同一條中線往兩側拉開,裡面是你的臉。 兩拍是同一個動作的前後半,不是兩段各自播完的接力(實測:影格開到 48.3%→51.7% 時, 開場還在 0.80 的不透明度)。

改開場尾段的時間時要記得:名字必須先完全落定並停留 ≥0.2s,交棒才開始。 一邊淡出一邊還在升上來,等於沒有高潮。

帶 #錨點 進站不播開場:訪客指名要去某一段,不該先繳四秒的過路費。

首頁:關於我

[ GET TO KNOW ME ]                 ← 幕① 開場的口拉開,裡面是一張臉
     PORTRAIT ⊙⊙⊙ 4:5
    ┃   大頭照 4:5   ┃
    01.  YOUR NAME  .05
          往下 ↓
────────────────────────────
[ 照片 ]              01 — 04      ← 幕② 兩軌交錯的生活照,不是卡片牆
[ 自我介紹 ]                        ← 幕③ 「我是 ⟨名字⟩」+兩段話+四條原則
[ 學經歷 · 現在 ]      03 列        ← 幕④ 編號橫列,紅點=正在做的,虛線=待補
[ 聯絡 ]              GMAIL        ← 幕⑤ 巨型信箱,這一頁唯一的行動

巨型名字只出現一次,在幕 ③。開場剛把名字拆成兩半展開過, 幕 ① 再放一次等於把高潮花掉兩次 —— 那裡的名字降級成影格底下的一行演職員字幕。

作品舞台(/work)

        ┌ 上一個作品(被視窗上緣切掉)┐
   分類 ─────── ⊙⊙⊙ ─────── 一句分類
  巨型作品名  ┃ 操作錄影 ┃ 內容簡介
              ┃         ┃ ● 上線中 · 2026 · 學習
        01.   MI ESPANOL   .13   [ 進站 ↗ ]
        └ 下一個作品(被下緣切掉)┘
                                          • 全部 (13)
                                            建檔 (4)
                                            展示 (3)
  © 2026    輪播 / 列表                     工具 (2)   00:12:40   關於
  • 滾一下換一幕(900ms 冷卻、deltaY ≥ 20)、上下鍵、觸控滑動、點上下偷看格、點刻度
  • 點影格=進站(桌面另有跟著游標跑的「進站 ↗」標籤),右欄也有明確的按鈕
  • 底部篩選吃 cat 欄位(建檔/展示/學習/分析/工具),是真的會篩
  • 輪播與列表是同一批 13 件,不是兩份不同的清單
  • 右下角的時間碼是放映機的讀數:純裝飾、aria-hidden、reduced-motion 下靜止

巨型名字不與影格重疊。 參考站讓字被切斷是成立的,因為他只有一個剛好長到要被切的名字; 你有 13 筆長短不一的作品,同一個裝置只會變成缺角。字級上限由「最長的一筆要放得進名字欄」 反推 —— 改字級或欄寬之後要重新量 13 筆。

遊記(/journal)

去過的國家與地方。長相是帳本不是相簿牆 —— 編號、國碼、年份、地名,一列一趟, 照年份由早到晚(「從小到大」就是這份帳的讀法)。同一個國家去兩次就開兩筆, 統計會自動去重(三次日本 = 一個國家、三趟)。

加一筆:改 src/data/journal.ts 的 TRIPS, 統計、年份分節、照片全部自動接上。照片丟 public/media/journal/<id>-01.webp, 檔名對就會出現,不用改程式。

⚠️ 不得代填。 沒去過的地方寫上去,是這個網站唯一不能犯的錯。

全螢幕選單

頁首右上角的「選單」。巨型義大利體的編號清單,五項:

01. 首頁    INDEX
02. 作品    WORK
03. 關於    ABOUT
04. 聯絡    CONTACT     ← mailto
05. 重播開場 REPLAY

Esc 或右上角的「關閉」收起來。開啟時選單以外的區塊會被設成 inert, 鍵盤與螢幕閱讀器都出不去。

回去看開場

點頁首的名字,或選單裡的「05 REPLAY」。首頁就地重播(會先自動捲回頂端, 不然交棒的那個口在畫面外),其他頁會導回首頁再播。 滑鼠移到名字上,右邊會浮出「↺ 重播開場」。

參考站的完整版是 7.95 秒(加上延遲約 8.95 秒才進得了內容)。那是導演的招牌片頭,他負擔得起;一個要讓陌生人點進作品的作品集不該收這個過路費,所以這裡是重新配速的短版,四拍全保留。


線上

https://personal-site-tan-alpha.vercel.app (正式版)

這台機器的 build 特例

Smart App Control 從 2026-08-05 起擋掉 Astro compiler 的原生 .node 檔(SAC 沒有 白名單,不關就繞不開)。解法已裝好:WASM 後備 binding + 專案層 .npmrc 的 force=true(讓 npm 放行 cpu=wasm32 的套件)。兩個檔案都不要刪 —— 細節見 .npmrc 裡的註解。症狀長這樣就是它: Cannot find native binding … An Application Control policy has blocked this file。

第二大腦:數字怎麼保持誠實

/method 頁上的每個數字(102 篇筆記、各資料夾篇數)都是 G:\Vault 的實掃結果, 不是手寫的。掃描器在 tools/scan-vault.mjs,產物是 src/data/brain.json,BrainGraph.astro 與 method.astro 都讀它。

改完筆記,要讓網站跟上:

cd G:\Projects\personal-site && npm run sync

然後照常 npm run build + vercel --prod。

為什麼是獨立的一步、不塞進 build: 跟 media.ts 只能掃 repo 內的 public/media 同一個道理 —— G:\Vault 不在 repo 裡,Vercel 的雲端 build 機器看不到它。在 build 時讀它, 本機對、雲端直接爆。所以 sync 在本機跑、把數字快照進 brain.json(進 repo), build 只讀那份快照。沒跑 sync 也不會壞,只是數字停在上次 sync 的日期。

手排的部分不會被蓋掉: 圖上「畫哪幾個點、點放哪裡」是策展,寫在 BrainGraph.astro 的 NODES(座標有語意:左=輸入、右=產出、下=手藝)。sync 只更新資料夾後面的篇數, 不會把新筆記自動灑成亂點 —— 新筆記值不值得畫進圖,由人決定。這是刻意的:全自動 force-layout 會變成一張沒有語意的亂點圖。輸入管線數(6)是概念數不是檔案數,掃不出來,寫死在 src/data/brain.ts 的 PIPELINES。

真正的「即時同步到線上」做不到也不該做:網站在雲端、給全世界看,不可能即時讀你本機硬碟。 npm run sync 就是誠實可達的最好版本 —— 一個指令,數字對齊當下的 vault。

部署(Vercel)

靜態輸出,不需要 adapter。

cd G:\Projects\personal-site && npm run build
cd G:\Projects\personal-site && vercel --prod

vercel.cmd 要先把 node 補進 PATH。 推之前檢查 .vercelignore —— 原始素材(raw/、footage/、*.MOV)已經擋掉了,只有壓過、放在 public/media/ 的成品會上去。


交付前必跑

  • 375px 無水平捲軸(三頁都測過)
  • console 零錯誤
  • 觸控目標 ≥ 24×24(WCAG 2.2 SC 2.5.8)
  • aria-hidden 內沒有可聚焦元素;透明的連結不會被 tab 到
  • 裝飾性無限捲動由使用者滾動驅動,不自動跑(避開 WCAG 2.2 SC 2.2.2)
  • prefers-reduced-motion 有完整靜態替代
  • 頁面轉場即使動畫被凍結也不會卡住(rAF 逾時保險)
  • 站上寫的數字跟線上實際對得起來(作品數:tools/record/counts.mjs 逐站數;/method 的第二大腦篇數:npm run sync 重掃 vault)
  • 動畫手感實際看過 ← 這一項只有你能簽
  • 9 支操作影片實際看過 ← 同上。畫格我逐張驗過,但「順不順」要你看

文件

  • PRODUCT.md —— 這站要做什麼、給誰、什麼不能捏造
  • DESIGN.md —— 視覺契約:世界觀、色彩、字體、動態規則。改樣式前先讀

About

個人網站「放映室」——開場動畫、作品輪播、方法頁。Astro + GSAP

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages