简历是一串自我声明,操作系统是一个能上手用的东西。这就是 KM/OS 的全部出发点——它不是一个告诉你"我会做东西"的页面,而是一台跑在浏览器标签页里的桌面系统:窗口、程序坞、菜单栏,还有一个 Terminal 应用背后真正的虚拟文件系统。作品集里本该出现的每一个板块,在这里都是一个能实际运行的应用程序。
打开站点,你面对的不是滚动长页,而是一套可以拖拽、缩放、最小化的窗口管理界面。想了解他的经历?去文件管理器里翻。想看技术栈?终端里敲命令。
刻意不用框架
整个项目用原生 JavaScript 写成 ES 模块,由 esbuild 打包成单个文件。CSS 全部手写,建立在一套设计令牌之上。依赖总共两个,而且都只存在于构建阶段。
对于这样一个几乎全定制的界面——自定义窗口外框、自定义拖拽与缩放物理、自定义终端模拟器——引入框架意味着换来一个运行时、一个构建步骤和一套约定,而项目真正需要的东西少得可怜。几个由此产生的决定:
- 窗口从 克隆,由状态类驱动,所以打开、关闭、最小化的全部动效都活在 CSS 里,不在 JS 里。
- 所有手势——拖拽、缩放、滑动——统一用 Pointer Events,鼠标、手写笔、触摸的行为完全一致,不需要写多套代码路径。
- 终端、Finder 风格的文件浏览器和一个笔记应用共享同一个虚拟文件系统。在其中一个里改文件,另外两个立刻看到变化。
- Service Worker 配合带内容哈希的打包地址(
app.js?b=,每次构建重新生成)。
最后那条来自一个真实的 bug:一个过期的缓存包曾经对着刚部署的 HTML 文档运行,悄悄弄坏了移动端界面,因为旧 JS 在找的 DOM 节点,新外壳里已经没有了。给打包文件的地址加上构建 ID,而不只是给缓存头做文章,让这类 bug 在结构上变得不可能——一个过期缓存面对它从没见过的 URL,literally 没有任何东西可以拿出手。
真正离开浏览器的只有两件事
这一点值得说清楚,因为它是作品集,不是玩具。整个站点只有两处会向外部发起请求。
第一处,在内置 AI 助手里输入的问题会先发到站点自己的服务器,再转给语言模型提供商——API 密钥永远不会到达浏览器。第二处,联系表单提交到一个转发服务。除此之外全部同源,由真正的 Content-Security-Policy HTTP 头强制执行(不是 meta 标签——meta 标签表达不了 frame-ancestors),这也是站点没有分析脚本、没有字体 CDN 的原因。
两个确实需要执行的内联
热门跟贴