Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/ui-deconstruct-redesign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"dropout": "patch:feat"
"@dropout/ui": "patch:feat"
---

Redesign the launcher as a deconstructed construction drawing with a frameless window, an exposed construction grid, oversized titles with a dropped glyph, overlapping system windows, a dithered sky, and hidden base64 easter eggs. The interface uses fixed pixels like the official Minecraft launcher: it follows only the system display scale, never the window size, and extra window space goes to the grid and whitespace.
150 changes: 150 additions & 0 deletions packages/ui/design/DECONSTRUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# DropOut 界面重设计 · 解构

> 分支 `feat/ui-deconstruct`。粒子方块世界那一版(`feat/ui-redesign-prototype`)原样保留,不在这条分支上。
> 参考:TypeSafe AI 官网(jev)。只借它的语法,不抄它的品牌。

## 0. 一句话

把启动器画成一张还没收拾干净的施工图:网格、轴号、裁切角、尺寸线都露在外面;
超大的字被切开、错位,其中一个字被「丢掉」只剩轮廓;窗口互相压着、微微歪斜;
有些文字表面上是一串 base64,复制下来解码才读得到。

DropOut 这个名字本来就是神经网络里的 dropout:训练时随机丢掉一部分神经元,剩下的更强。
所以「丢掉」是贯穿全局的母题:被丢掉的字、被丢掉的方块、被丢掉的神经元,都只剩一圈虚线。

## 1. 为什么换掉粒子

维护者觉得粒子太花。粒子版的主角是一团会动的东西,界面退到细线和字后面;
这一版反过来:**没有主角,结构本身就是画面**。动的东西只剩两处小部件,都很慢,
而且在减弱动效和测试夹具里完全静止。

## 2. 尺寸规则(照官方启动器)

官方 Minecraft 启动器的界面是**固定尺寸**的:字号、按钮、栏宽都是固定像素,只跟系统显示缩放(125%、150%)走
(参考 Mojang 的 MCL-10940);窗口随便拉,不锁比例,只有最小尺寸;窗口变大时多出来的地方给画面,
界面本身不变大。具体像素数官方没公开,这里照的是规则。

- 界面全部是固定 CSS 像素:不用 `transform: scale()`,不调 webview 的 `setZoom()`,字号不按窗口宽高算(不用 `cqw`/`vw` 字号)。
系统缩放由 webview 的 `devicePixelRatio` 处理,不用写代码。
- 窗口最小 905×575(`src-tauri/tauri.conf.json`),不锁比例、不吸附。无边框窗口的拖动、最小化 / 最大化 / 关闭、
右键菜单在 `src/lib/desktop-window.ts`;`src/components/shell/stage.tsx` 只是一个铺满窗口的根节点。
- 窗口变大,多出来的空间这样用:

| 部分 | 规则 |
| ---- | ---- |
| 施工网格 | 格子固定约 84×89(默认窗口内容区 1006×711 正好 12 × 8 格),铺满窗口,窗口越大格子越多;轴号按格数生成,列号 Z 之后接 AA、AB… |
| 首页 | 左栏固定 551 宽贴左,右边那叠窗口固定 388 宽贴右,中间是留白;抖色天空铺满宽度,高度固定 355.5 |
| 实例页 | 列表伸缩,右边详情栏固定 394;网格视图每张卡至少 260,窗口越宽列越多 |
| 助手页 | 对话区伸缩(气泡最宽 720),右边上下文栏固定 292 |
| 设置页 | 版心最宽 1006(默认窗口里的宽度),贴左,不跟着变宽 |
| 章节大数字 | 02 / 03 / 04 的右边缘钉在默认窗口里的位置,窗口窄时往左让,不压右上角的按钮 |

- 放不下的时候在自己那一栏里滚动,不缩小字。最小窗口下首页右栏变窄(最窄约 268),
两个窗口的错位收小,DAYLIGHT 小部件收起,页面纵向滚动。
- 弹出层(对话框、下拉、菜单)挂在 body 上;对话框「不超过窗口」的约束用 `vh`/`vw`,这不算缩放。
- 状态栏和 F3 的坐标就是窗口坐标;F3 里写窗口尺寸和 devicePixelRatio。

## 3. 解构手法对照

| 手法 | 在界面里的样子 |
| ---- | ---- |
| 暴露结构 | 全局施工网格(默认窗口 12 列 × 8 行,顶上 A–L、左边 1–8 轴号,窗口大了格子更多),四角套准记号 ⊕;首页标题下方一条实时量出来的尺寸线 `W 551` |
| 碎片 / 错位 | 标题除第一行外整体缩进半格;每句标题按固定规则丢掉一个字(只剩描边),并拉一条引线标注 `DROPPED · p=0.50` |
| 叠压 / 倾斜 | 首页右侧 INSTANCE.CFG、READINESS.SYS、两个小部件互相压着,小部件转 −2.2° / 1.6°;一块旋转的虚线框「被丢掉的方块」压在标题后面 |
| 印刷错版 | 当前实例、按钮悬停、输入框聚焦时,身后露出一道偏移的粉色色块 |
| 巨大的章节号 | 实例 02、助手 03、设置 04 的页头后面压一个空心大号数字,被页面切掉一部分 |
| 第二层文本 | 侧边竖排密文、`[B.64]` 块、状态栏中间那串,见第 7 节 |
| 字体冲突 | 挤紧的无衬线大字 + 像素等宽小字 + 等宽说明字,三种声音同时说话 |

## 4. 从 jev 借来的

- 白色标签贴在色场上的导航(当前页反成黑底)。
- 抖色(dither)色场:首页上半截是一块静态的像素云,往下散成纸面。
- 复古系统窗口:黑色标题栏 + 像素字「文件名」(`INSTANCE.CFG`、`SESSION.LOG`…),1px 墨线、硬投影。
- 引导点线 `标签 ……… 数值`、左边一道竖线的文字栏、`∵ x` 分节记号、分段进度条。
- 超大的下划线链接当主操作(`▶▶▶ Launch`)。
- 竖排 base64、`[B.64]` 块、页脚 base64 这类彩蛋。

jev 自己用的 Die Grotesk 和 LisaTerminal 是商业字体,这里换成开源替代(见第 5 节)。

## 5. 配色与字体

| 记号 | 纸面(浅) | 墨面(深) | 用途 |
| ---- | ---- | ---- | ---- |
| `paper` | `#eeece7` | `#171717` | 背景 |
| `sheet` | `#fbfaf7` | `#1f1f1f` | 窗口、卡片 |
| `ink` / `rule` | `#1e1e1e` | `#eceae4` | 文字、1px 墨线 |
| `win` | `#dedede` | `#2c2c2c` | 窗口里的灰色控制区 |
| `signal` | `#f386a1` | 同 | 错版色块、当前项小方块 |
| `go` | `#43b581` | `#4cc48c` | 启动、就绪(DropOut 图标原本的绿) |
| `sky` / `warn` / `fault` | 蓝 / 黄 / 红 | 同 | 天空、警告、错误 |

`go-ink` / `warn-ink` / `fault-ink` 是同色系的深色版,专门给文字用,保证对比度。
圆角全部为 0。shadcn 的 `primary` 映射成墨色,`accent`(菜单高亮)是反白。

字体全部本地打包,不走 CDN(Tauri 的 CSP 也不允许):

- 大标题:Inter Tight(`type-display`,字距 −0.055em,行距 0.86;中文自动放宽)
- 正文:Inter
- 像素小字:Departure Mono(OFL,文件和授权在 `src/assets/fonts/departure-mono/`)
- 等宽:JetBrains Mono
- 中文回落到系统字体(苹方 / 雅黑 / Noto Sans CJK)

## 6. 构件

```text
src/index.css 记号、字体、.win/.win-title/.tag/.chip/.crop/.dots/.leader/.dropped/.misreg 等
src/lib/desktop-window.ts 无边框窗口:拖动、最小化 / 最大化 / 关闭、右键菜单
src/components/shell/
stage.tsx 铺满窗口的根节点
top-nav.tsx 顶栏标签导航 + 账号
construction-grid.tsx 施工网格(固定格子)、按格数生成的标尺、竖排密文
status-strip.tsx 底栏(系统名、版本、路由、base64、鼠标坐标)
easter-eggs.tsx ↑↑↓↓←→←→BA 解构模式(左下角提示窗)、F3 调试信息、控制台招呼
logo.tsx 标志(神经网络,两个节点被丢掉)+ 字标 Drop○ut
src/components/deco/
win.tsx 系统窗口
display-title.tsx 丢一个字的超大标题
dither-sky.tsx 抖色天空(静态,按尺寸和主题重画)
widgets.tsx DROPOUT 神经元小部件、DAYLIGHT 游戏时钟
marks.tsx 引导点线、分段条、尺寸线、引线标注、套准记号、[B.64]、竖排密文
page-header.tsx 二级页面页头
src/lib/eggs.ts 所有彩蛋的内容
```

shadcn 基础组件(按钮、对话框、下拉、选择、选项卡、开关…)直接改了样式,
所以向导、编辑器、弹窗这些没单独重写的地方也跟着换了语言。对话框统一成系统窗口,
标题栏默认写 `DIALOG.SYS`,可以用 `chrome` 换。

## 7. 彩蛋

| 位置 | 内容 |
| ---- | ---- |
| 左侧竖排那串 | base64 套了六层,最后是 *Never dig straight down.* |
| 设置页右上 `[B.64]` | dropout 本身的代码(Srivastava 2014),注释 *drop half. the rest get stronger.* |
| 助手页底部 `[B.64]` | 一张 16×16 灰度 PNG:苦力怕的脸(对应 jev 藏的 MNIST 手写数字) |
| 状态栏中间 | *DropOut, the next-generation Minecraft launcher.* |
| ↑↑↓↓←→←→BA | 解构模式:所有盒子的边界都描出来,背景抽掉,露出层层叠压。左下角(首页日志窗下面那块空白)弹出一扇固定 360×96 的 `DECONSTRUCT.EXE` 小窗,带图例;再输一次、按 Esc 或点 × 退出 |
| F3 | Minecraft 式调试信息(帧率、坐标、区块、路由、实例) |
| DROPOUT 小部件 | 点一下换丢弃率 p(0.1 → 0.9) |
| DAYLIGHT 小部件 | 现实时间换算成游戏时间,第几天从 Minecraft 正式版发布那天算 |
| 浏览器控制台 | 一行像素字 DROP OUT 和上面两个按键提示 |

## 8. 测试

- 功能测试全部通过(可访问性、键盘、各种启动状态、迁移、助手…)。
- 新增「界面不随窗口缩放」:905×575、1024×768、1280×720、1920×1080、2560×1440 五种窗口下,
首页标题、启动按钮、导航的 computed font-size 和尺寸完全一样,左栏和右栏不重叠,没有横向滚动。
- 助手页图片彩蛋那条:最小窗口里它在页面折线以下,测试先把它滚到中间,再检查完整可见、能复制。
- 截图基线全部重画:只重新生成了 Linux 的(CI 跑在 Ubuntu 上),macOS 的旧基线没动。
- default-window(1024×768)基本不变,只有字号取整带来的亚像素差异(标题 73.7 → 74 这类);网格和天空逐像素一致。
- minimum-window 改回 905×575,从「整张画板缩小」变成 1:1 排版,放不下的部分要滚动才看得到。
- 截图要逐像素一致,所以夹具模式下:时钟固定在一个时刻、DROPOUT 小部件不跳、抖色天空用固定种子。
- 系统缩放:用 `deviceScaleFactor: 1.5` 截图,整个界面按 1.5 倍放大,排版和 1 倍一样。

## 9. 没做 / 下一步

1. Tauri 窗口里的 WebKitGTK 还没实测(抖色天空是 2D canvas,风险比粒子小得多);拉窗口、最大化也只在浏览器里按尺寸验过。
2. 浏览页(Modrinth)只换了页头和列表行,卡片可以再做得更像「目录卡」。
3. 实例编辑弹窗、导入/转换向导只换了基础组件和颜色,没有逐屏打磨。
4. 设置里可以加一个「施工网格」开关,给觉得线太多的人关掉。
3 changes: 3 additions & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
},
"dependencies": {
"@base-ui/react": "^1.8.0",
"@fontsource-variable/inter": "^5.3.0",
"@fontsource-variable/inter-tight": "^5.3.0",
"@fontsource-variable/jetbrains-mono": "^5.3.0",
"@hookform/resolvers": "^5.9.1",
"@stepperize/react": "^8.0.0",
"@tauri-apps/api": "^2.11.1",
Expand Down
Binary file not shown.
93 changes: 93 additions & 0 deletions packages/ui/src/assets/fonts/departure-mono/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
Copyright 2022–2024 Helena Zhang (helenazhang.com).

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
5 changes: 1 addition & 4 deletions packages/ui/src/components/bottom-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,7 @@ export function BottomBar() {

return (
<Button
className={cn(
"px-4 py-2 shadow-xl",
"bg-emerald-700! hover:bg-emerald-800!",
)}
className={cn("px-4 py-2 shadow-xl", "bg-go! hover:bg-go!")}
size="lg"
onClick={handleStartGame}
disabled={launchingInstanceId === activeInstance?.id}
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/src/components/config-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export function ConfigEditor({ onOpenChange, ...props }: ConfigEditorProps) {
return (
<>
<Dialog onOpenChange={handleOpenChange} {...props}>
<DialogContent className="grid h-[min(92vh,760px)] max-h-[calc(100vh-1.5rem)] w-[min(94vw,1120px)] max-w-none grid-rows-[auto_minmax(0,1fr)_auto] gap-0 overflow-hidden p-0 sm:max-w-[1120px]">
<DialogContent className="grid h-[min(92vh,760px)] max-h-[calc(100vh-1.5rem)] w-[min(94vw,1120px)] max-w-none grid-rows-[auto_minmax(0,1fr)_auto] gap-0 overflow-hidden p-0 pt-[18px] sm:max-w-[1120px]">
<DialogHeader className="border-border/70 border-b px-4 py-3 text-left">
<div className="flex items-start justify-between gap-4 pr-8">
<div className="min-w-0">
Expand Down Expand Up @@ -238,7 +238,7 @@ export function ConfigEditor({ onOpenChange, ...props }: ConfigEditorProps) {
</span>
{isDirty && (
<span
className="bg-amber-500 size-1.5 rounded-full"
className="bg-warn size-1.5 rounded-none"
aria-hidden="true"
/>
)}
Expand Down Expand Up @@ -299,7 +299,7 @@ export function ConfigEditor({ onOpenChange, ...props }: ConfigEditorProps) {
>
<div className="flex min-w-0 items-center gap-2">
{validation.valid ? (
<CheckCircle2Icon className="size-3 shrink-0 text-emerald-600 dark:text-emerald-300" />
<CheckCircle2Icon className="size-3 shrink-0 text-go-ink" />
) : (
<CircleAlertIcon className="text-destructive size-3 shrink-0" />
)}
Expand Down
Loading
Loading