drawn-notebook-ui 是做什麼的?
name: drawn-notebook-ui description: > 一個完整的設計系統與實作指南,用於重現「繪本筆記本」美學 (Drawn Notebook) — 一種安靜、編輯風格、手工打造的 UI 語言,最初為 Askar 的個人作品集所創作。詳細涵蓋每個標記、元件、動畫和互動模式,針對 Flutter (Dart) 和純網頁 (HTML/CSS/JS) 兩種平台,並同時支援淺色與深色模式。每當使用者想要在任何情境下建置、改編或擴展此特定視覺語言時,請使用此技能。
概述
Drawn Notebook 不是一個元件庫。它是一種設計語言 — 一個連貫的哲學,服務於每個像素、每條線條粗細、每個間距節奏和每個動畫時序曲線。其指導原則是,介面應該感覺像一本活生生的筆記本:用墨水繪製在奶油色紙上,段落隨著讀者向下捲動而緩慢揭示,像實體旅程一樣橫跨頁面的導航線索。沒有什麼是喧囂的。沒有什麼是為了裝飾而裝飾。UI 透過將每個視覺元素錨定在意義上來贏得它們。
這個技能是一份精確、全面的參考文件。在實作此 UI 時,請依序遵循每個章節。不要跳過標記定義 — 每個間距、顏色和動畫值都是承重的。不要跳過深色模式章節 — 兩個主題都是平等的第一等公民,必須始終同時保持正確。
第 1 節 — 設計理念
1.1 核心原則
沈靜的信心,而非為了極簡而極簡。 設計雖然簡潔,但留下的每個元素都是有意義的。空白空間並非懶惰 — 而是頁面在呼吸。使用者的視線由構圖、重量和動畫引導,而非由色彩飽和度或裝飾性花邊引導。
手繪美學,但非字面上的擬物化。 UI 參考了繪畫的工藝 — 細線條粗細、襯線編輯字體、紙張紋理、以線條畫呈現的 SVG 插圖 — 但它並未假裝自己是紙張。它是一個尊重類比工藝觸感但又不笨拙模仿的數位表面。
每個區塊都是一個章節。 頁面是一個連續的捲動頁面,每個區塊感覺就像翻開筆記本的新一頁。區塊背景在暖色中性色調之間微妙地轉換。一條連續的金色線條(旅程線 SVG)將它們在視覺上縫合在一起。
動畫是敘事,而非娛樂。 動畫揭示內容的方式就像手寫文字 — 從下往上出現、柔和淡入、追蹤 SVG 路徑。持續時間以現代標準來看是慢的(揭示動畫為 1.2 秒至 2 秒)。這是刻意的。速度意味著噪音。緩慢意味著工藝。
避免 AI 生成的垃圾模式。 以下模式被明確禁止:帶有紫色到粉紅色漸層的英雄背景、具有相同高度和相同圓角的卡片網格、發出霓虹光的懸停狀態、大多數元素上過大的圓角(> 14px)、帶有百分比數字的顏色編碼技能條、帶有項目符號和粗體日期的時間線,以及任何將表情符號用作裝飾的行為。
第 2 節 — 設計標記
2.1 色彩調色盤 — 淺色模式(日光筆記本)
淺色模式中的每種顏色都是源自紙張、奶油色和天然墨水的暖色中性色。只有一個重點色系列:金色/陶土色,範圍從柔和的琥珀色到暖磚色。
/* 淺色模式(應用於 :root) */
--cream: #F7F5F0; /* 頁面背景 — 主要表面 */
--sage: #EEF3ED; /* 關於區塊背景 — 最淺的灰綠色 */
--parchment: #F1ECE3; /* 經歷 + 履歷區塊背景 */
--warm-white: #FAF8F5; /* 技能區塊背景 — 最淺的表面 */
--linen: #F3EFE8; /* 專案區塊背景 */
--sage-tint: #EFF3EE; /* 學習區塊背景 */
--charcoal: #1A1A1A; /* 主要文字,SVG 線條,邊框 */
--soft-black: #2C2C2C; /* 次要文字,強標籤 */
--warm-grey: #8A8580; /* 淡化標籤,時間戳,說明文字 */
--light-grey: #C5BFB8; /* 捲動提示,裝飾性分隔線 */
--terracotta: #B87A5A; /* 懸停狀態,項目符號,活動路徑點 */
--indigo: #6B6EA0; /* 保留 — 未在主要模式中使用 */
--gold: #C4956A; /* 主要重點色 — 旅程線,技能圖示,標籤 */
--gold-dim: rgba(196, 149, 106, 0.3); /* 淡金色用於陰影,邊框 */
--blush: #F0EBE5; /* 懸停狀態下的標籤/徽章背景 */
--sage-dark: #8B9E8B; /* 區塊邊框分隔線色調 */
2.2 色彩調色盤 — 深色模式(夜間筆記本)
深色模式不是簡單的反色。背景從暖奶油色轉變為深暖黑色 — 一種幾乎像墨水漬的質感。重點色稍微變亮以維持在深色背景上的可讀性。
/* 深色模式(應用於 :root[data-theme="dark"]) */
--cream: #141210; /* 頁面背景 — 深暖黑色 */
--sage: #171816; /* 關於區塊背景 */
--parchment: #1A1815; /* 經歷 + 履歷背景 */
--warm-white: #1E1B18; /* 技能區塊背景 */
--linen: #181614; /* 專案區塊背景 */
--sage-tint: #161815; /* 學習區塊背景 */
--charcoal: #EDE8E1; /* 主要文字 — 暖米白色 */
--soft-black: #D8D2C8; /* 次要文字 */
--warm-grey: #9E978F; /* 淡化標籤 */
--light-grey: #38342E; /* 裝飾性分隔線 */
--terracotta: #D98A62; /* 懸停重點色 — 比淺色模式稍亮 */
--indigo: #8C8FB8;
--gold: #D4A373; /* 主要重點色 — 比淺色模式稍暖 */
--gold-dim: rgba(212, 163, 115, 0.3);
--blush: #241E1A;
--sage-dark: #9EAF9E;
/* 表面標記 */
--card-glass: rgba(30, 27, 24, 0.72);
--card-glass-hover: rgba(42, 38, 34, 0.95);
--card-item-bg: rgba(24, 21, 19, 0.78);
--card-item-hover: rgba(38, 34, 30, 0.96);
--card-border: rgba(212, 163, 115, 0.22);
--card-border-hover: rgba(212, 163, 115, 0.48);
--card-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
--card-shadow-hover: 0 6px 20px rgba(0, 0, 0, 0.45);
--card-inset: inset 0 1px 0 rgba(255, 255, 255, 0.05);
--card-inset-hover: inset 0 1px 0 rgba(255, 255, 255, 0.08);
--badge-bg: rgba(24, 21, 19, 0.88);
--grain-opacity: 0.022; /* 深色模式中較低以避免混濁 */
2.3 色彩調色盤 — 淺色模式表面標記
/* 淺色模式表面標記 */
--card-glass: rgba(255, 255, 255, 0.65);
--card-glass-hover: #ffffff;
--card-item-bg: rgba(247, 245, 240, 0.8);
--card-item-hover: rgba(255, 255, 255, 0.98);
--card-border: rgba(196, 149, 106, 0.22);
--card-border-hover: rgba(196, 149, 106, 0.45);
--card-shadow: 0 4px 16px rgba(26, 26, 26, 0.04);
--card-shadow-hover: 0 6px 20px rgba(196, 149, 106, 0.12);
--card-inset: inset 0 1px 0 rgba(255, 255, 255, 0.9);
--card-inset-hover: inset 0 1px 0 rgba(255, 255, 255, 0.95);
--badge-bg: rgba(247, 245, 240, 0.82);
--grain-opacity: 0.035;
2.4 主題切換 — 無閃爍實作
主題檢測必須在頁面渲染前同步執行於 <head> 中,以防止錯誤主題閃爍 (FOUT)。這是一個關鍵的實作需求:
<head>
<!-- 必須是 <head> 中的第一個 script,在任何樣式表連結之前 -->
<script>
(function () {
try {
var stored = localStorage.getItem('color-scheme');
var prefersDark = window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches;
var theme = (stored === 'dark' || (!stored && prefersDark)) ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', theme);
var meta = document.querySelector('meta[name="color-scheme"]');
if (meta && (stored === 'dark' || stored === 'light')) meta.content = stored;
var themeColor = document.querySelector('meta[name="theme-color"]');
if (themeColor) themeColor.content = theme === 'dark' ? '#141210' : '#F8F4F0';
} catch (e) {}
})();
</script>
</head>
在 Flutter/Dart 中,使用 ThemeMode.system 作為預設值,並透過 SharedPreferences 進行持久化覆寫。鍵為 color-scheme,值為 'dark' 或 'light'。
2.5 字體標記
--font-serif: 'Cormorant Garamond', 'Georgia', serif;
--font-sans: 'Inter', 'Helvetica Neue', sans-serif;
Cormorant Garamond 對此設計的編輯語調是不可妥協的。它載入了 300, 400, 500, 600 粗細,並包含 300 和 400 粗細的斜體變體。它用於所有標題、區塊標題、英雄名稱、標籤、側欄標籤、路徑點訊息,以及任何需要「書寫」質感的地方。它絕不能在 600 粗細以上使用 — 這不是一個粗體字體。
Inter 是功能性輔助字體。它用於說明文字、技能標籤、時間戳、大寫引言,以及密集資訊上下文中的正文。僅載入 300, 400, 500 粗細。
/* Google Fonts 導入 */
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;0,600;1,300;1,400&family=Inter:wght@300;400;500&display=swap');
2.6 間距標記
所有間距均使用 clamp() 實現流體排版。請勿對區塊內距或內容寬度限制使用硬編碼的像素值。
--section-pad: clamp(80px, 12vh, 160px); /* 主要區塊的垂直內距 */
--content-max: 800px; /* 窄版散文列 */
--content-wide: 1100px; /* 用於網格的寬版面 */
--side-pad: clamp(24px, 5vw, 80px); /* 頁面水平邊距 */
2.7 動畫標記
--ease-out: cubic-bezier(0.25, 0.46, 0.45, 0.94); /* 一般的 easing-out */
--ease-smooth: cubic-bezier(0.4, 0, 0.2, 1); /* 類 Material 平滑 */
--duration-slow: 1.2s; /* 揭示動畫 */
--duration-med: 0.8s; /* 中等過渡 */
--duration-fast: 0.4s; /* 快速微互動 */
第 3 節 — 全域基礎
3.1 CSS 重置與基礎
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 16px;
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
background-color: var(--cream);
color: var(--charcoal);
font-family: var(--font-sans);
font-weight: 300;
line-height: 1.7;
overflow-x: hidden;
position: relative;
}
3.2 文字選取
選取反白是柔和的金色洗滌 — 它反映了重點色調色盤,而不會過於強烈:
::selection {
background: rgba(196, 149, 106, 0.2);
color: var(--charcoal);
}
::-moz-selection {
background: rgba(196, 149, 106, 0.2);
color: var(--charcoal);
}
3.3 SVG 線條適應
所有內聯 SVG 都使用淺色模式調色盤的硬編碼十六進制線條顏色(#1A1A1A、#2C2C2C)。若要將它們適應深色模式而無需修改 SVG 標記,請使用 CSS 屬性選擇器覆寫:
svg [stroke="#1A1A1A"], svg [stroke="#1a1a1a"] { stroke: var(--charcoal); }
svg [fill="#1A1A1A"], svg [fill="#1a1a1a"] { fill: var(--charcoal); }
svg [stroke="#2C2C2C"], svg [stroke="#2c2c2c"] { stroke: var(--soft-black); }
svg [fill="#2C2C2C"], svg [fill="#2c2c2c"] { fill: var(--soft-black); }
svg text[fill="#1A1A1A"] { fill: var(--charcoal); }
svg text[fill="#8A8580"] { fill: var(--warm-grey); }
SVG 中的金色重點色 (#C4956A) 特意不被覆寫。 金色重點色在兩個主題中都是固定的參考顏色;在深色模式中,標記 --gold 會變亮為 #D4A373,但內聯 SVG 金色不需要重新映射,因為它與深色背景的對比度仍然可以接受。
3.4 平滑主題過渡
對每個在主題間變更的表面套用協調的過渡。這可以防止突兀的顏色跳躍:
body, section,
.about, .experience, .resume-section, .skills, .projects, .learning, .closing,
.exp-card, .exp-item, .skill-cell, .compact-card, .compact-card__icon,
.about__point, .about__visual-card, .about__chip, .closing__link,
.journey-waypoint__message, .rail-node__label, .resume-modal__panel,
.resume-modal__header {
transition:
background-color 0.35s var(--ease-out),
color 0.35s var(--ease-out),
border-color 0.35s var(--ease-out);
}
/* 始終尊重使用者動作偏好 */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { transition: none !important; }
}
第 4 節 — 紙張紋理疊加層
一個微妙的紙張紋理圖層會作為整個視口固定的疊加層套用。它位於 z-index: 9999 — 在所有元素之上 — 但 pointer-events: none 確保它永遠不會攔截互動。
.grain-overlay {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 9999;
opacity: var(--grain-opacity, 0.035);
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
background-repeat: repeat;
background-size: 200px 200px;
transform: translateZ(0);
will-change: transform;
transition: opacity 0.35s var(--ease-out);
}
/* 在深色模式中,螢幕混合模式能更自然地呈現紋理 */
:root[data-theme="dark"] .grain-overlay {
mix-blend-mode: screen;
}
在 Flutter 中: 在元件樹頂部使用一個帶有基於 shader 噪點模式的 CustomPaint widget 來重現此效果,或將紋理作為 ~3% 透明度的 PNG 資產烘焙,並在深色模式中使用 BlendMode.screen。
第 5 節 — 主題切換(天體切換器)
主題切換按鈕是一個位於右上角固定定位的小藥丸按鈕。它
…
[此處內容因原文截斷而省略]