Communitygithub.com

drawn-notebook-ui

一个完整的设计系统和实现指南,用于再现“手绘笔记本”美学风格。

drawn-notebook-ui 是什么?

drawn-notebook-ui 是一个详细的设计系统技能,旨在指导开发者复现 Askar 个人作品集中所采用的“手绘笔记本”美学风格。该技能超越了简单的组件库,提供了一套完整的设计语言,涵盖了从设计哲学、色彩与排版令牌、动画曲线,到全局基础和主题切换等所有方面。它详细说明了如何在 Flutter (Dart) 和纯 Web (HTML/CSS/JS) 两种平台上实现光照和深色两种模式。其核心原则是创造一种安静、自信、手工感强的界面,避免使用常见的 AI 生成的“样板”模式,强调通过缓慢动画讲述故事。使用此技能时,开发者需要严格遵循所有令牌定义和实现步骤,因为每个间距、颜色和运动值都是支撑整个设计的关键。

兼容平台~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/askarthemasss/craftsman-skills/tree/HEAD/skills/drawn-notebook-ui

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

drawn-notebook-ui 是做什么的?


概述

Drawn Notebook 不是一个组件库。它是一种设计语言——一个连贯的哲学体系,其中的每个像素、每条线宽、每个间距节奏和每个动画时间曲线都服务于一个统一的目标。其指导原则是:界面应该像一个活生生的笔记本:用墨水画在奶油色的纸上,段落随着读者向下滚动而缓缓显现,导航线索像实体旅程一样贯穿页面。没有任何元素是喧闹的。也没有任何装饰为装饰本身而存在。UI 中的每个视觉元素都通过将其锚定于意义来赢得其存在的权利。

本技能是一份精确、全面的参考。实现此 UI 时,请按顺序遵循每个部分。不要跳过令牌定义——每个间距、颜色和运动值都是承重的。不要跳过深色模式部分——两种主题是同等重要的,必须始终同时保持正确。


第 1 节 — 设计哲学

1.1 核心原则

安静的自信,而非为极简而极简。 设计是简约的,但保留下来的每个元素都是有目的的。空白空间不是懒惰——它是页面在呼吸。用户的视线由构图、重量和运动引导,而非由色彩饱和度或装饰性点缀引导。

手绘美学,而非字面的拟物化。 设计参考了绘画工艺——细描边粗细、衬线编辑字体、纸质纹理、以线条画形式呈现的 SVG 插图——但它没有假装是纸。它是一个尊重模拟工艺触感的数字表面,而不笨拙地模仿它。

每个部分都是一章。 页面是连续滚动的,每个部分都像翻到笔记本的新一页。部分的背景色在暖中性色之间微妙变化。一条连续的金色线(旅程线 SVG)将它们视觉上缝合在一起。

运动是叙事,而非娱乐。 动画像手写字一样揭示内容——从底部向上出现,柔和地淡入,描摹 SVG 路径。按现代标准,持续时间较慢(显示动画为 1.2s–2s)。这是刻意的。速度意味着噪音。缓慢意味着工艺。

避免 AI 样板模式。 以下模式被明确禁止:紫色到粉红色渐变的英雄背景、高度和圆角半径完全相同的卡片网格、悬停时发光的霓虹效果、大多数元素上过大的圆角(> 14px)、带有百分比的彩色技能条、带有粗体日期和项目符号的时间线,以及任何使用 emoji 作为装饰的行为。


第 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>
    (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);  /* 通用缓出 */
--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 中: 使用位于小部件树顶部的 CustomPaint 小部件(带有基于 shader 的噪声模式)来重现此效果,或者将纹理烘焙为透明度约为 3% 的 PNG 资源,并在深色模式下使用 BlendMode.screen。


第 5 节 — 主题切换(天体开关)

主题切换是一个小的药丸状按钮,固定定位在右上角。它

…

[为翻译而截断]

相关技能