feat(landing): refresh landing page with ember background and bilingual i18n

- Add glyph-embers.js canvas background and i18n.js language switching
- Refresh index.html, main.js, and styles.css layout and content
- Bundle Fusion Pixel fonts for CJK display type
- Add landing test suite (node --test meldwork-landing/test/landing.test.cjs)
- Update landing docs and switch README license notice to Apache 2.0

Checks: node --test meldwork-landing/test/landing.test.cjs (11 passed)
This commit is contained in:
Ryder Sun committed 2026-10-21 12:46:35 +08:00
1 parent a97fd91975
commit 5a938f60d2
15 files changed
+1437 -676

No files matched your search

+1 -1
View File
@@ -127,4 +127,4 @@ Release verification includes 357/357 frontend tests, 1,603/1,603 desktop tests,
## License
Meldwork is source-available under the [Meldwork Non-Commercial Source License 1.0](LICENSE). You may inspect, run, modify, and share it for non-commercial purposes. Commercial use requires prior written permission; see [COMMERCIAL_USE.md](COMMERCIAL_USE.md).
Meldwork is open source under the [Apache License 2.0](LICENSE). Commercial use, private use, modification, and redistribution are permitted under the license terms. The license does not grant rights to the Meldwork name, logo, or other brand assets; see [COMMERCIAL_USE.md](COMMERCIAL_USE.md).
+1 -1
View File
@@ -127,4 +127,4 @@ V1.0.6 改进了长驻 Agent 的恢复能力,让群组讨论在个别成员失
## 许可证
Meldwork 采用 [Meldwork 非商用源码许可 1.0](LICENSE)。你可以出于非商业目的查看、运行、修改和分享。商业使用需要事先书面许可,详见 [COMMERCIAL_USE.md](COMMERCIAL_USE.md)。
Meldwork 以 [Apache License 2.0](LICENSE) 开源。在许可证条款范围内,可以商用、私用、修改和再分发。许可证不授予 Meldwork 名称、标志和其他品牌素材的使用权,详见 [COMMERCIAL_USE.md](COMMERCIAL_USE.md)。
+8 -5
View File
@@ -4,7 +4,9 @@ Meldwork's own design system, extracted from the shipped app (`frontend/src/styl
## 1. Visual Theme & Atmosphere
Dark, quiet, instrument-panel calm. Meldwork is a local-first work cell where multiple AI agents are reviewed by a human — the visual mood is "traceability made visible": a near-black field, a faint dot-matrix of evidence points, one continuous coral work line, and generous negative space. Density is low; motion is slow and deliberate; nothing loops aggressively. Retro dot-matrix display type signals the "run state / terminal" heritage without becoming a gimmick.
Dark, quiet, instrument-panel calm. Meldwork is a local-first workspace where selected Agents collaborate to advance a shared task. The visual language connects their contributions: a near-black field, a faint dot-matrix of participants, one continuous coral work line, and generous negative space. Density is low; motion is slow and deliberate; nothing loops aggressively. Retro dot-matrix display type signals the "run state / terminal" heritage without becoming a gimmick.
Marketing copy leads with "Set the goal. Let Agents work together." Emphasize shared context, discussion, authorized execution, peer review, and less manual coordination. Users set goals and permissions and step in when information or key decisions are needed. Local history supports revisiting the work; do not promise complete evidence traceability, guaranteed correctness, or tasks completing without human input.
## 2. Color Palette & Roles
@@ -19,19 +21,20 @@ Dark, quiet, instrument-panel calm. Meldwork is a local-first work cell where mu
| `ink-soft` | `#c3cdd1` | Secondary text |
| `muted` | `#93a0a5` | Labels, captions (app dark muted) |
| `faint` | `#707b80` | Disabled / tertiary |
| `coral` | `#ef5a45` | Brand accent — the "acceptance point"; active indicators, glyphs, focus |
| `coral-deep` | `#d45f52` | Accent on light surfaces (app light accent) |
| `coral` | `#d98568` | Product dark-theme accent; active indicators, glyphs, focus |
| `coral-deep` | `#d45f52` | Product light-theme accent |
| `line` | `rgba(255,255,255,0.10)` | Hairline borders on dark |
| `pill-white` | `#ffffff` | Nav pill, primary CTA, logo badge |
| `nav-ink` | `#26343c` | Text on white pills (app light text) |
Light/dark mapping: marketing pages are dark-only. App light-theme tokens (`#f3f6f8` bg, `#d45f52` accent) appear only inside product screenshots.
Light/dark mapping: marketing defaults to dark and supports a light-theme toggle. Accent RGB and translucent effects follow the selected product accent; do not reintroduce the older saturated `#ef5a45` into interface effects. Brand assets retain their original colors.
## 3. Typography Rules
Families:
- UI: `"Inter", "Segoe UI", system-ui, sans-serif` (weights 400/500/600)
- Display: `"BubbledotICG-FinePos", "Geist Pixel Circle", monospace` — retro dot-matrix, Latin only
- Chinese display fallback: local `Meldwork Pixel CJK` (Fusion Pixel 12px proportional, OFL-1.1). Use it for headings and short state labels; keep body text in the system Chinese sans-serif stack. Chinese headings allow character wrapping; preserve Latin words as units.
- Mono accents: same display stack for stat glyphs
| Level | Size | Weight | Tracking | Line-height |
@@ -76,7 +79,7 @@ Do:
- Use coral only for acceptance/active/focus moments — it must stay rare.
- Keep the trace-line metaphor consistent: continuous rounded line, round terminals, one bright end point.
- Respect `prefers-reduced-motion`: static frame, final states, no marquee.
- Ground every claim in README facts (12 CLIs, 3 modes, local-first, human gate, V1.0.3).
- Ground every claim in README facts (12 CLIs, 3 modes, local-first collaboration, opt-in writes, user decisions when needed). Label historical examples with their version.
Don't:
- No gradient/shimmer headlines; no glassmorphism everywhere; no neon multi-color.
+27 -1
View File
@@ -37,7 +37,33 @@ npx serve . # 或 python3 -m http.server
## 语言
当前官网正文为英文。仓库 README 与桌面产品宣传文案同时维护中英文;桌面端沿用现有 i18n。
右上角可切换中文 / EN,默认跟随浏览器语言,选择保存在 `meldwork-landing-locale`。正文与富文本翻译使用 `index.html` 的 `data-zh` 属性,`i18n.js` 统一更新语言、辅助标签、页面标题、分享摘要和中文 FAQ 结构化数据。即使本地存储不可用,也可在当前页面切换。官网不依赖 Vue 或额外构建步骤。
英文标题保留 BubbledotICG-FinePos,中文标题与状态标签搭配本地 Fusion Pixel 12px 等比例像素字体;正文使用 Inter 与系统中文无衬线字体。字体版本为 2026.10.16,来源为 [Fusion Pixel](https://github.com/TakWolf/fusion-pixel-font/releases/tag/2026.10.16),字体及上游 OFL 声明保留在 `assets/fonts/fusion-pixel/`。
## 案例卡片与颜色
案例区继续使用项目已有的原生 Stepper。五条记录始终可读;自动播放只推进当前节点与轻量位移动画,按文本长度留出至少 7 秒阅读时间,到最后一条停止。悬停、键盘焦点、页面隐藏或离开案例区时暂停,手动翻页后可主动继续。减少动态效果模式默认展示完整记录,禁用自动播放。
官网强调色对齐产品:暗色 `#d98568`、浅色 `#d45f52`。透明色从同一组 RGB 变量派生,降低发光与背景视频饱和度;现有品牌标志不改色。
案例卡片通过背景明度和阴影区分当前贡献,不使用左侧描边。Agent Logo 采用白色底座,便于识别深色或透明图标。
## 全页字符余烬背景
`glyph-embers.js` 是原生 Canvas 实现,视觉方向参考 [React Bits Pro Glyph Ember](https://pro.reactbits.dev/docs/components/glyph-ember) 的公开描述:字符火焰、亮色底床与上升余烬。当前官网没有 React/shadcn 环境,且未配置 `REACTBITS_LICENSE_KEY`,因此没有安装 Pro 注册表组件,也没有复制其付费源码或新增 `components.json`。
背景固定覆盖整个视口。多层连续噪声向上平流并扭曲火舌,混合字母与符号按热度映射为字符密度;底床由暖橙过渡至象牙白,独立的模糊辉光层与漂浮火星提供层次。卡片保持实色以保护阅读,首页原视频降低为次要背景。绘制上限为 20fps、280 列、110 行、1.5 倍像素比;页面隐藏时取消动画帧,减少动态效果模式仅绘制静态版本。背景不接收鼠标事件,并从辅助技术中隐藏。
## 验证
先安装现有前端开发依赖,再运行专项测试:
```bash
node --test meldwork-landing/test/landing.test.cjs
```
该命令从仓库根目录执行,使用现有前端依赖中的 JSDOM,覆盖双语、语言记忆、完整文案、播放节奏、键盘控制与减少动态效果。视觉检查使用本地 HTTP 服务,同时检查中英文、明暗主题与窄屏。更新样式或脚本时同步刷新 `index.html` 中的资源版本参数。
## 升级为视频背景(可选)
@@ -0,0 +1,96 @@
Ark Pixel Font
https://github.com/TakWolf/ark-pixel-font
Copyright (c) 2021, TakWolf (https://takwolf.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.
@@ -0,0 +1,108 @@
[Cubic 11]
These fonts are free software.
Unlimited permission is granted to use, copy, and distribute them, with or without modification, either commercially or noncommercially.
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
此字型是免費的。
無論您是否進行對本字型進行商業或非商業性修改,均可無限制地使用,複製和分發它們。
本字型的衍生品之授權必須與此字型相同,且不作任何擔保。
[JF Dot M+H 12]
Copyright(c) 2005 M+ FONTS PROJECT
[M+ BITMAP FONTS]
Copyright (C) 2002-2004 COZ
These fonts are free software.
Unlimited permission is granted to use, copy, and distribute it, with or without modification, either commercially and noncommercially.
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
これらのフォントはフリー(自由な)ソフトウエアです。
あらゆる改変の有無に関わらず、また商業的な利用であっても、自由にご利用、複製、再配布することができますが、全て無保証とさせていただきます。
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://scripts.sil.org/OFL
-----------------------------------------------------------
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.
@@ -0,0 +1,93 @@
Copyright (c) 2019–2025 Lee Minseo ([email protected])
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.
@@ -0,0 +1,96 @@
Fusion Pixel Font
https://github.com/TakWolf/fusion-pixel-font
Copyright (c) 2022, TakWolf (https://takwolf.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.
+186
View File
@@ -0,0 +1,186 @@
/* Original Canvas background inspired by the public Glyph Ember visual brief.
This is not the licensed React Bits Pro component or a port of its source. */
(function () {
"use strict";
var canvas = document.getElementById("emberBackground");
if (!canvas) return;
var ctx = canvas.getContext("2d");
if (!ctx) return;
var motion = window.matchMedia("(prefers-reduced-motion: reduce)");
var glyphs = " .`'\",:;^~-/\\()|<>+?il!L7ctfjrvxz1CYuJXkhdwO0QZmUaobqW8B$%#@";
var field = document.createElement("canvas");
var ink = field.getContext("2d");
if (!ink) return;
var width = 0;
var height = 0;
var columns = 0;
var rows = 0;
var cellWidth = 0;
var cellHeight = 12;
var palette = [];
var sparks = [];
var light = false;
var frame = null;
var lastPaint = -Infinity;
var elapsed = 0;
var previous = null;
function updatePalette() {
light = document.documentElement.dataset.theme === "light";
var stops = light ? [[212, 95, 82], [190, 93, 67], [173, 72, 64], [139, 61, 51], [113, 46, 39]]
: [[55, 29, 22], [105, 54, 36], [217, 133, 104], [250, 194, 150], [255, 250, 232]];
palette = Array.from({ length: 64 }, function (_, index) {
var position = index / 63 * (stops.length - 1);
var from = stops[Math.min(stops.length - 2, Math.floor(position))];
var to = stops[Math.min(stops.length - 1, Math.floor(position) + 1)];
var fraction = position - Math.min(stops.length - 2, Math.floor(position));
return "rgb(" + from.map(function (channel, i) {
return Math.round(channel + (to[i] - channel) * fraction);
}).join(",") + ")";
});
}
function hash(x, y) {
var value = Math.imul(x, 374761393) + Math.imul(y, 668265263);
value = Math.imul(value ^ (value >>> 13), 1274126177);
return ((value ^ (value >>> 16)) >>> 0) / 4294967295;
}
function noise(x, y) {
var ix = Math.floor(x);
var iy = Math.floor(y);
var fx = x - ix;
var fy = y - iy;
fx = fx * fx * (3 - 2 * fx);
fy = fy * fy * (3 - 2 * fy);
var lower = hash(ix, iy) * (1 - fx) + hash(ix + 1, iy) * fx;
var upper = hash(ix, iy + 1) * (1 - fx) + hash(ix + 1, iy + 1) * fx;
return lower * (1 - fy) + upper * fy;
}
function turbulence(x, y) {
return noise(x, y) * 0.58 + noise(x * 2.07 + 31, y * 2.07) * 0.28
+ noise(x * 4.13, y * 4.13 + 17) * 0.14;
}
function draw(time) {
ctx.clearRect(0, 0, width, height);
ink.clearRect(0, 0, width, height);
var fontSize = Math.min(13, cellWidth * 1.65, cellHeight);
ink.font = fontSize + "px ui-monospace, SFMono-Regular, Consolas, monospace";
ink.textAlign = "center";
ink.textBaseline = "middle";
// Advect a continuous noise field upward; smaller octaves tear and curl tips.
for (var row = 0; row < rows; row++) {
var rise = (rows - 1 - row) / rows;
var flowY = rise * 2.7 - time * 0.55;
for (var col = 0; col < columns; col++) {
var x = col / columns;
var warp = noise(x * 4.5 + 9, flowY * 0.75) - 0.5;
var billow = turbulence(x * 11 + warp * rise * 3, flowY);
var heat = Math.max(0, Math.min(1, 1 - rise * 1.75 + (billow - 0.48) * 2.3 * Math.sqrt(rise)));
if (heat < 0.035) continue;
var flicker = hash(col + Math.floor(time * 5), row);
var density = Math.max(0, Math.min(1, heat + (flicker - 0.5) * 0.08));
ink.globalAlpha = (light ? 0.12 : 1) * Math.min(1, 0.2 + heat * 1.2);
ink.fillStyle = palette[Math.round(Math.pow(heat, 1.45) * 63)];
var character = Math.min(glyphs.length - 1, Math.floor(density * (glyphs.length - 1)));
ink.fillText(glyphs[character], (col + 0.5) * cellWidth, (row + 0.5) * cellHeight);
}
}
ink.globalAlpha = 1;
// Bloom is a separate pass, leaving the original glyphs sharp and legible.
if (!light) {
ctx.filter = "blur(9px)";
ctx.globalAlpha = 0.65;
ctx.drawImage(field, 0, 0, width, height);
}
ctx.filter = "none";
ctx.globalAlpha = 1;
ctx.drawImage(field, 0, 0, width, height);
ctx.textAlign = "center";
ctx.textBaseline = "middle";
sparks.forEach(function (spark) {
var age = (time * spark.speed + spark.phase) % 1;
var px = spark.x * width + Math.sin(age * 9 + spark.phase * 25) * width * 0.025 * age;
var py = height * (1 - age * 0.92);
ctx.font = spark.size + "px ui-monospace, monospace";
ctx.globalAlpha = Math.sin(age * Math.PI) * (light ? 0.16 : 0.95);
if (!light) {
var radius = spark.size * 2.5;
var halo = ctx.createRadialGradient(px, py, 0, px, py, radius);
halo.addColorStop(0, "rgba(250,194,150,0.5)");
halo.addColorStop(0.25, "rgba(217,133,104,0.16)");
halo.addColorStop(1, "rgba(217,133,104,0)");
ctx.fillStyle = halo;
ctx.fillRect(px - radius, py - radius, radius * 2, radius * 2);
}
ctx.fillStyle = light ? "#ad4840" : "#fff1df";
ctx.fillText(spark.glyph, px, py);
});
ctx.globalAlpha = 1;
}
function resize() {
width = window.innerWidth;
height = window.innerHeight;
var ratio = Math.min(window.devicePixelRatio || 1, 1.5);
canvas.width = Math.round(width * ratio);
canvas.height = Math.round(height * ratio);
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
field.width = width;
field.height = height;
columns = Math.min(280, Math.ceil(width / 7));
rows = Math.min(110, Math.ceil(height / 12));
cellWidth = width / columns;
cellHeight = height / rows;
sparks = Array.from({ length: Math.min(64, Math.ceil(width / 25)) }, function (_, index) {
return { x: hash(index, 21), phase: hash(index, 43), speed: 0.045 + hash(index, 11) * 0.035,
size: 10 + hash(index, 7) * 5, glyph: index % 3 === 0 ? "*" : "+" };
});
draw(elapsed);
}
function stop() {
if (frame !== null) window.cancelAnimationFrame(frame);
frame = null;
previous = null;
canvas.dataset.state = "paused";
}
function animate(now) {
if (now - lastPaint >= 50) {
if (previous !== null) elapsed += Math.min((now - previous) / 1000, 0.1);
previous = now;
lastPaint = now;
draw(elapsed);
}
frame = window.requestAnimationFrame(animate);
}
function sync() {
stop();
if (document.hidden) return;
if (motion.matches) {
draw(0);
canvas.dataset.state = "static";
return;
}
lastPaint = -Infinity;
canvas.dataset.state = "running";
frame = window.requestAnimationFrame(animate);
}
updatePalette();
resize();
sync();
window.addEventListener("resize", resize);
document.addEventListener("visibilitychange", sync);
document.addEventListener("meldwork:themechange", function () {
updatePalette();
draw(motion.matches ? 0 : elapsed);
});
if (motion.addEventListener) motion.addEventListener("change", sync);
window.addEventListener("pagehide", stop);
window.addEventListener("pageshow", sync);
})();
+99
View File
@@ -0,0 +1,99 @@
/* Static landing copy uses the same en/zh locale convention as the desktop app.
Translated HTML is authored in index.html, never fetched or supplied by users. */
(function () {
"use strict";
var doc = document;
var storageKey = "meldwork-landing-locale";
var locale;
try { locale = localStorage.getItem(storageKey); } catch (error) { /* Storage is optional. */ }
if (locale !== "zh" && locale !== "en") {
locale = navigator.language.toLowerCase().startsWith("zh") ? "zh" : "en";
}
var copies = Array.from(doc.querySelectorAll("[data-zh]")).map(function (el) {
return { el: el, en: el.innerHTML, zh: el.dataset.zh };
});
var labels = {
"Meldwork home": "Meldwork 首页", "Primary": "主导航", "Mobile": "移动端导航",
"Toggle theme": "切换明暗主题", "Menu": "菜单", "Playback mode": "播放模式",
"Previous step": "上一步", "Next step": "下一步", "Scroll to workflow": "查看工作流程",
"Meldwork detecting local Agent CLIs": "Meldwork 检测本地 Agent CLI",
"Agents discussing and reviewing a shared task": "Agent 讨论并检查同一个任务",
"Auto Discussion V4 group collaboration": "自动讨论 V4 群组协作",
"Direct multimodal session with local files": "引用本地文件的多模态直接会话"
};
var attributes = [];
doc.querySelectorAll("[aria-label], img[alt]").forEach(function (el) {
["aria-label", "alt"].forEach(function (name) {
var original = el.getAttribute(name);
if (labels[original]) attributes.push({ el: el, name: name, en: original, zh: labels[original] });
});
});
var meta = Array.from(doc.querySelectorAll('meta[name="description"], meta[name="keywords"], meta[property="og:title"], meta[property="og:description"], meta[name="twitter:title"], meta[name="twitter:description"]'))
.map(function (el) { return { el: el, en: el.content }; });
var englishTitle = doc.title;
var readmeLink = doc.querySelector('a[href="https://github.com/Ryder-Sun/Meldwork#readme"]');
var chineseTitle = "Meldwork — 提出目标,让 Agent 们协作完成。";
var chineseDescription = "Meldwork 是本地优先的多 Agent 协作工作空间。让你已有的 Agent 共享上下文、讨论方案、执行授权工作并检查成果,减少人工协调;需要补充信息或关键决策时再由你介入。";
var schemaEl = doc.querySelector('script[type="application/ld+json"]');
var englishSchema = JSON.parse(schemaEl.textContent);
var faq = Array.from(doc.querySelectorAll(".acc-item"));
var runtimeCopy = {
"Round 1 · Propose": "第 1 轮 · 提案", "Round 1 · Critique": "第 1 轮 · 质询",
"Round 2 · Review": "第 2 轮 · 评审", "Round 2 · Reconcile": "第 2 轮 · 协调",
"Adoption · Human Gate": "用户决策 · 人工确认",
"Pause": "暂停播放", "Resume": "继续播放", "Replay": "重新播放",
"Auto": "自动播放", "Sequence": "完整记录"
};
function t(english) { return locale === "zh" ? runtimeCopy[english] || english : english; }
function setLocale(value, remember) {
locale = value === "zh" ? "zh" : "en";
doc.documentElement.lang = locale === "zh" ? "zh-CN" : "en";
copies.forEach(function (copy) {
copy.el.innerHTML = copy[locale];
if (copy.el.matches("[data-decrypt], .eyebrow")) {
copy.el.dataset.text = copy.el.textContent;
copy.el.classList.remove("is-scrambling");
}
});
attributes.forEach(function (copy) { copy.el.setAttribute(copy.name, copy[locale]); });
doc.title = locale === "zh" ? chineseTitle : englishTitle;
meta.forEach(function (copy) {
var key = copy.el.getAttribute("name") || copy.el.getAttribute("property");
copy.el.content = locale === "en" ? copy.en : key.includes("title") ? chineseTitle
: key === "keywords" ? "多 Agent 协作,自主协作,本地优先,共享上下文,授权执行,Meldwork" : chineseDescription;
});
var schema = JSON.parse(JSON.stringify(englishSchema));
if (locale === "zh") {
var app = schema["@graph"].find(function (entry) { return entry["@type"] === "SoftwareApplication"; });
app.description = chineseDescription;
app.featureList = "自主协作、共享任务上下文、同伴贡献与评审、授权执行、并发回复、自动讨论 V4、本地协作记录";
app.inLanguage = "zh-CN";
var questions = schema["@graph"].find(function (entry) { return entry["@type"] === "FAQPage"; });
questions.mainEntity = faq.map(function (el) {
return { "@type": "Question", name: el.querySelector(".acc-trigger").textContent.trim(),
acceptedAnswer: { "@type": "Answer", text: el.querySelector(".acc-panel").textContent.trim() } };
});
}
schemaEl.textContent = JSON.stringify(schema);
var toggle = doc.getElementById("languageToggle");
toggle.textContent = locale === "zh" ? "EN" : "中文";
toggle.setAttribute("aria-label", locale === "zh" ? "Switch to English" : "切换为中文");
toggle.setAttribute("lang", locale === "zh" ? "en" : "zh-CN");
readmeLink.href = locale === "zh"
? "https://github.com/Ryder-Sun/Meldwork/blob/main/README.zh-CN.md"
: "https://github.com/Ryder-Sun/Meldwork#readme";
window.meldworkI18n.locale = locale;
if (remember !== false) {
try { localStorage.setItem(storageKey, locale); } catch (error) { /* Storage is optional. */ }
}
doc.dispatchEvent(new CustomEvent("meldwork:localechange"));
}
window.meldworkI18n = { locale: locale, t: t, setLocale: setLocale };
doc.getElementById("languageToggle").addEventListener("click", function () {
setLocale(locale === "zh" ? "en" : "zh");
});
setLocale(locale, false);
})();
+209 -203
View File
@@ -72,7 +72,7 @@
{
"@type": "Question",
"name": "Is Meldwork free? What's the license?",
"acceptedAnswer": { "@type": "Answer", "text": "Source-available under the Meldwork Non-Commercial Source License 1.0. You can inspect, run, modify, and share it for non-commercial purposes. Commercial use requires prior written permission." }
"acceptedAnswer": { "@type": "Answer", "text": "Open source under the Apache License 2.0. Commercial use, private use, modification, and redistribution are permitted under the license terms. The license does not grant rights to the Meldwork name or logo." }
},
{
"@type": "Question",
@@ -104,10 +104,12 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link href="https://db.onlinewebfonts.com/c/8cb707a9b8a73f8a7403336b861c3074?family=BubbledotICG-FinePos" rel="stylesheet" />
<link rel="stylesheet" href="styles.css" />
<link rel="stylesheet" href="styles.css?v=20261021.6" />
</head>
<body>
<canvas class="ember-background" id="emberBackground" aria-hidden="true"></canvas>
<!-- Scroll progress bar -->
<div class="scroll-progress" aria-hidden="true"><span></span></div>
@@ -117,16 +119,17 @@
<img src="assets/meldwork-mark-v3-dark.svg" alt="" width="52" height="52" data-theme-src="assets/meldwork-mark-v3.svg" />
</a>
<nav class="nav-pill" aria-label="Primary">
<a href="#workflow" class="nav-link" data-spy="workflow"><span>Workflow</span></a>
<a href="#modes" class="nav-link" data-spy="modes"><span>Modes</span></a>
<a href="#capabilities" class="nav-link" data-spy="capabilities"><span>Capabilities</span></a>
<a href="#scenario" class="nav-link" data-spy="scenario"><span>In a run</span></a>
<a href="#compare" class="nav-link" data-spy="compare"><span>How it differs</span></a>
<a href="#faq" class="nav-link" data-spy="faq"><span>FAQ</span></a>
<a href="#workflow" class="nav-link" data-spy="workflow"><span data-zh="工作流程">Workflow</span></a>
<a href="#modes" class="nav-link" data-spy="modes"><span data-zh="协作模式">Modes</span></a>
<a href="#capabilities" class="nav-link" data-spy="capabilities"><span data-zh="产品能力">Capabilities</span></a>
<a href="#scenario" class="nav-link" data-spy="scenario"><span data-zh="真实协作">In a run</span></a>
<a href="#compare" class="nav-link" data-spy="compare"><span data-zh="产品特点">How it differs</span></a>
<a href="#faq" class="nav-link" data-spy="faq"><span data-zh="常见问题">FAQ</span></a>
</nav>
<a class="cta-dark magnet" href="https://github.com/Ryder-Sun/Meldwork/releases/tag/Meldwork-V1.0.6" target="_blank" rel="noopener">
<a class="cta-dark magnet" href="https://github.com/Ryder-Sun/Meldwork/releases/tag/Meldwork-V1.0.6" target="_blank" rel="noopener" data-zh="&lt;span class=&quot;cta-status&quot;&gt;&lt;/span&gt; 下载 V1.0.6">
<span class="cta-status"></span> Download V1.0.6
</a>
<button type="button" class="language-toggle" id="languageToggle" aria-label="Switch to Chinese">中 / EN</button>
<button type="button" class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
<svg class="sun-icon" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"></path></svg>
<svg class="moon-icon" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
@@ -138,13 +141,13 @@
<div class="overlay" id="overlay" hidden></div>
<nav class="mobile-menu" id="mobileMenu" hidden aria-label="Mobile">
<a href="#workflow" class="m-link"><span>Workflow</span></a>
<a href="#modes" class="m-link"><span>Modes</span></a>
<a href="#capabilities" class="m-link"><span>Capabilities</span></a>
<a href="#scenario" class="m-link"><span>In a run</span></a>
<a href="#compare" class="m-link"><span>How it differs</span></a>
<a href="#faq" class="m-link"><span>FAQ</span></a>
<a href="https://github.com/Ryder-Sun/Meldwork/releases/tag/Meldwork-V1.0.6" target="_blank" rel="noopener" class="m-link m-signin"><span>Download V1.0.6</span></a>
<a href="#workflow" class="m-link"><span data-zh="工作流程">Workflow</span></a>
<a href="#modes" class="m-link"><span data-zh="协作模式">Modes</span></a>
<a href="#capabilities" class="m-link"><span data-zh="产品能力">Capabilities</span></a>
<a href="#scenario" class="m-link"><span data-zh="真实协作">In a run</span></a>
<a href="#compare" class="m-link"><span data-zh="产品特点">How it differs</span></a>
<a href="#faq" class="m-link"><span data-zh="常见问题">FAQ</span></a>
<a href="https://github.com/Ryder-Sun/Meldwork/releases/tag/Meldwork-V1.0.6" target="_blank" rel="noopener" class="m-link m-signin"><span data-zh="下载 V1.0.6">Download V1.0.6</span></a>
</nav>
<!-- ================= HERO ================= -->
@@ -163,21 +166,21 @@
</div>
<div class="hero-inner">
<p class="eyebrow" data-decrypt data-text="LOCAL-FIRST · MULTI-AGENT COLLABORATION">LOCAL-FIRST · MULTI-AGENT COLLABORATION</p>
<p class="eyebrow" data-decrypt data-text="LOCAL-FIRST · MULTI-AGENT COLLABORATION" data-zh="本地优先 · 多 AGENT 协作">LOCAL-FIRST · MULTI-AGENT COLLABORATION</p>
<h1 class="headline" id="headline" data-split>
<span class="hl-line">Set the Goal.</span>
<span class="hl-line">Let Agents Work Together.</span>
<span class="hl-line" data-zh="提出目标,">Set the Goal.</span>
<span class="hl-line" data-zh="让 Agent 们协作完成。">Let Agents Work Together.</span>
</h1>
<p class="subhead">Meldwork is a <strong>local-first multi-Agent collaboration workspace</strong>. Your existing Agents share context, discuss approaches, carry out authorized work, and check results — with less relaying and coordinating from you. Set the goal and permissions, then step in when they need information or a key decision.</p>
<p class="subhead" data-zh="Meldwork 是一个&lt;strong&gt;本地优先的多 Agent 协作工作空间&lt;/strong&gt;。让你已有的 Agent 共享上下文、讨论方案、在授权范围内执行并检查成果,减少你在它们之间转述和协调的工作。设定目标和权限,在需要补充信息或关键决策时介入。">Meldwork is a <strong>local-first multi-Agent collaboration workspace</strong>. Your existing Agents share context, discuss approaches, carry out authorized work, and check results — with less relaying and coordinating from you. Set the goal and permissions, then step in when they need information or a key decision.</p>
<div class="cta-row">
<a class="cta-primary magnet" href="https://github.com/Ryder-Sun/Meldwork/releases/download/Meldwork-V1.0.6/Meldwork-0.1.6-arm64.dmg">
<span class="cta-label">Download for macOS</span>
<span class="cta-meta">V1.0.6 · Apple silicon</span>
<span class="cta-label" data-zh="下载 macOS 版">Download for macOS</span>
<span class="cta-meta" data-zh="V1.0.6 · Apple 芯片">V1.0.6 · Apple silicon</span>
</a>
<a class="cta-ghost" href="https://github.com/Ryder-Sun/Meldwork" target="_blank" rel="noopener">View on GitHub <span aria-hidden="true">↗</span></a>
<a class="cta-ghost" href="https://github.com/Ryder-Sun/Meldwork" target="_blank" rel="noopener" data-zh="在 GitHub 查看 &lt;span aria-hidden=&quot;true&quot;&gt;↗&lt;/span&gt;">View on GitHub <span aria-hidden="true">↗</span></a>
</div>
<div class="trust">
@@ -189,7 +192,7 @@
<span class="avatar-ring"><span class="avatar-dot"><img src="assets/agents/kimi.png" alt="Kimi Code" /></span></span>
<span class="avatar-ring"><span class="avatar-dot"><img src="assets/agents/hermes.svg" alt="Hermes" /></span></span>
</div>
<span class="trust-pill">One task — across the Agent CLIs you already run.</span>
<span class="trust-pill" data-zh="用你已有的 Agent CLI,共同推进一个任务。">One task — across the Agent CLIs you already run.</span>
</div>
</div>
@@ -197,22 +200,22 @@
<div class="stat">
<span class="stat-glyph">&gt;</span>
<span class="stat-value"><span class="count" data-target="12" data-decimals="0">0</span><span class="stat-suffix">+</span></span>
<span class="stat-label">Supported Agent CLIs</span>
<span class="stat-label" data-zh="已支持的 Agent CLI">Supported Agent CLIs</span>
</div>
<div class="stat">
<span class="stat-glyph">*</span>
<span class="stat-value"><span class="count" data-target="3" data-decimals="0">0</span></span>
<span class="stat-label">Collaboration Modes</span>
<span class="stat-label" data-zh="协作模式">Collaboration Modes</span>
</div>
<div class="stat">
<span class="stat-glyph">%</span>
<span class="stat-value"><span class="count" data-target="100" data-decimals="0">0</span><span class="stat-suffix">%</span></span>
<span class="stat-label">Local-first work cell</span>
<span class="stat-glyph">*</span>
<span class="stat-value" data-zh="本地">Local</span>
<span class="stat-label" data-zh="对话与运行记录">Conversations &amp; run history</span>
</div>
<div class="stat">
<span class="stat-glyph">#</span>
<span class="stat-value"><span class="count" data-target="1" data-decimals="0">0</span></span>
<span class="stat-label">Shared task</span>
<span class="stat-label" data-zh="共享任务">Shared task</span>
</div>
</div>
@@ -221,9 +224,9 @@
<!-- ================= AGENT LOGO LOOP ================= -->
<section class="section agents" id="agents">
<p class="eyebrow reveal" data-reveal>DETECTED ON YOUR MACHINE</p>
<h2 class="section-title reveal" data-reveal>Works with the agents you already run.</h2>
<p class="section-sub reveal" data-reveal>Meldwork detects a compatible local Agent command and exposes its readiness, capabilities, and session behavior. Approved Agent Connectors can be added through the Agent Connector SDK.</p>
<p class="eyebrow reveal" data-reveal data-zh="发现本机 AGENT">DETECTED ON YOUR MACHINE</p>
<h2 class="section-title reveal" data-reveal data-zh="让你已有的 Agent 一起工作。">Works with the agents you already run.</h2>
<p class="section-sub reveal" data-reveal data-zh="Meldwork 检测兼容的本地 Agent 命令,显示可用状态、能力与会话支持情况。也可通过 Agent Connector SDK 接入已批准的连接器。">Meldwork detects a compatible local Agent command and exposes its readiness, capabilities, and session behavior. Approved Agent Connectors can be added through the Agent Connector SDK.</p>
<div class="marquee" data-marquee aria-hidden="true">
<div class="marquee-track">
<span class="logo-chip"><img src="assets/agents/codex.svg" alt="" />Codex</span>
@@ -244,157 +247,157 @@
<!-- ================= DEFINITION (AI-answer-ready) ================= -->
<section class="section definition" id="definition">
<p class="eyebrow reveal" data-reveal>THE SHORT ANSWER</p>
<h2 class="section-title reveal" data-reveal>What is Meldwork?</h2>
<p class="eyebrow reveal" data-reveal data-zh="认识 MELDWORK">THE SHORT ANSWER</p>
<h2 class="section-title reveal" data-reveal data-zh="Meldwork 是什么?">What is Meldwork?</h2>
<div class="geo-bento">
<article class="geo-tile geo-tile--def spotlight reveal" data-reveal style="--d:0s">
<p class="geo-def-text">Meldwork is a <span class="gradient-text">local-first multi-Agent collaboration workspace</span> for research, analysis, writing, planning, review, and implementation. Selected Agents advance the same task through discussion, authorized execution, and peer review. You set the boundaries and make <strong>key decisions when needed</strong>.</p>
<p class="geo-def-text" data-zh="Meldwork 是一个&lt;span class=&quot;gradient-text&quot;&gt;本地优先的多 Agent 协作工作空间&lt;/span&gt;,用于研究、分析、写作、规划、评审和实施。选定的 Agent 通过讨论、授权执行和相互检查推进同一任务。你设定边界,并在需要时作出&lt;strong&gt;关键决策&lt;/strong&gt;。">Meldwork is a <span class="gradient-text">local-first multi-Agent collaboration workspace</span> for research, analysis, writing, planning, review, and implementation. Selected Agents advance the same task through discussion, authorized execution, and peer review. You set the boundaries and make <strong>key decisions when needed</strong>.</p>
</article>
<article class="geo-tile geo-tile--wide spotlight reveal" data-reveal style="--d:0.08s">
<span class="geo-kicker">Supported agents</span>
<span class="geo-kicker" data-zh="支持的 Agent">Supported agents</span>
<span class="geo-num">12<em>+</em></span>
<p class="geo-fact-value">Codex, Claude Code, Gemini CLI, Qwen Code, Kimi Code, MiMo Code, OpenCode, OpenCodeReview, Hermes, Pi Agent, OpenClaw, WorkBuddy — detected natively on your machine.</p>
<p class="geo-fact-value" data-zh="Codex、Claude Code、Gemini CLI、Qwen Code、Kimi Code、MiMo Code、OpenCode、OpenCodeReview、Hermes、Pi Agent、OpenClaw、WorkBuddy,直接检测本机安装。">Codex, Claude Code, Gemini CLI, Qwen Code, Kimi Code, MiMo Code, OpenCode, OpenCodeReview, Hermes, Pi Agent, OpenClaw, WorkBuddy — detected natively on your machine.</p>
</article>
<article class="geo-tile spotlight reveal" data-reveal style="--d:0.16s">
<span class="geo-kicker">Modes</span>
<span class="geo-kicker" data-zh="协作模式">Modes</span>
<span class="geo-num">3</span>
<p class="geo-fact-value">Direct · Concurrent Responses · Auto Discussion V4</p>
<p class="geo-fact-value" data-zh="直接会话 · 并发回复 · 自动讨论 V4">Direct · Concurrent Responses · Auto Discussion V4</p>
</article>
<article class="geo-tile geo-tile--wide spotlight reveal" data-reveal style="--d:0.24s">
<span class="geo-kicker">Working together</span>
<span class="geo-kicker" data-zh="共同推进">Working together</span>
<div class="geo-chips">
<span class="geo-chip">Discuss</span>
<span class="geo-chip"><i>→</i> Execute</span>
<span class="geo-chip"><i>→</i> Check</span>
<span class="geo-chip"><i>→</i> Refine</span>
<span class="geo-chip" data-zh="讨论">Discuss</span>
<span class="geo-chip" data-zh="&lt;i&gt;→&lt;/i&gt; 执行"><i>→</i> Execute</span>
<span class="geo-chip" data-zh="&lt;i&gt;→&lt;/i&gt; 检查"><i>→</i> Check</span>
<span class="geo-chip" data-zh="&lt;i&gt;→&lt;/i&gt; 完善"><i>→</i> Refine</span>
</div>
<p class="geo-fact-value">Agents build on peer contributions and request the next steps, reducing manual coordination.</p>
<p class="geo-fact-value" data-zh="Agent 接续同伴的贡献,请求下一步工作,减少人工协调。">Agents build on peer contributions and request the next steps, reducing manual coordination.</p>
</article>
<article class="geo-tile spotlight reveal" data-reveal style="--d:0.32s">
<span class="geo-kicker">Runs on</span>
<p class="geo-fact-value">macOS · Apple silicon · Electron</p>
<p class="geo-fact-value">100% local-first work cell</p>
<span class="geo-kicker" data-zh="运行环境">Runs on</span>
<p class="geo-fact-value" data-zh="macOS · Apple 芯片 · Electron">macOS · Apple silicon · Electron</p>
<p class="geo-fact-value" data-zh="对话与运行记录保存在本地,模型仍可能使用外部服务。">Local conversations and run history; models may use external providers.</p>
</article>
</div>
</section>
<!-- ================= WORKFLOW ================= -->
<section class="section workflow" id="workflow">
<p class="eyebrow reveal" data-reveal>THE WORK CELL</p>
<h2 class="section-title reveal" data-reveal>Set up the task. Let the group move it forward.</h2>
<p class="eyebrow reveal" data-reveal data-zh="从目标到协作">THE WORK CELL</p>
<h2 class="section-title reveal" data-reveal data-zh="设定任务,让工作小组继续推进。">Set up the task. Let the group move it forward.</h2>
<div class="steps" data-steps>
<svg class="step-line" viewBox="0 0 1200 8" preserveAspectRatio="none" aria-hidden="true">
<defs>
<linearGradient id="stepGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#ef5a45" stop-opacity="0" />
<stop offset="0.1" stop-color="#ef5a45" />
<stop offset="0.85" stop-color="#ff8a6e" />
<stop offset="1" stop-color="#ff8a6e" stop-opacity="0" />
<stop offset="0" stop-color="var(--coral)" stop-opacity="0" />
<stop offset="0.1" stop-color="var(--coral)" />
<stop offset="0.85" stop-color="var(--coral-highlight)" />
<stop offset="1" stop-color="var(--coral-highlight)" stop-opacity="0" />
</linearGradient>
</defs>
<path d="M0 4 H1200" pathLength="1" />
</svg>
<div class="step reveal" data-reveal style="--d:0s">
<span class="step-num">01</span>
<h3>Select</h3>
<p>Choose the local Agents and participants. Participants are always selected by you — never auto-assigned from a roster.</p>
<h3 data-zh="选择成员">Select</h3>
<p data-zh="选择参与任务的本地 Agent。参与者由你指定,不会从候选池中自动分配。">Choose the local Agents and participants. Participants are always selected by you — never auto-assigned from a roster.</p>
</div>
<div class="step reveal" data-reveal style="--d:0.1s">
<span class="step-num">02</span>
<h3>Scope</h3>
<p>Bound the goal, working directory, context, and permissions. Every run starts from a frozen task snapshot.</p>
<h3 data-zh="设定边界">Scope</h3>
<p data-zh="明确目标、工作目录、上下文和权限。每次运行从冻结的任务快照开始。">Bound the goal, working directory, context, and permissions. Every run starts from a frozen task snapshot.</p>
</div>
<div class="step reveal" data-reveal style="--d:0.2s">
<span class="step-num">03</span>
<h3>Collaborate</h3>
<p>Start Auto Discussion so Agents can discuss, request contributions, carry out authorized work, and check deliverables.</p>
<h3 data-zh="开始协作">Collaborate</h3>
<p data-zh="开启自动讨论,让 Agent 讨论方案、请求同伴贡献、执行授权工作并检查成果。">Start Auto Discussion so Agents can discuss, request contributions, carry out authorized work, and check deliverables.</p>
</div>
<div class="step reveal" data-reveal style="--d:0.3s">
<span class="step-num">04</span>
<h3>Decide &amp; check</h3>
<p>Step in for missing information, permissions, or key decisions. Check the deliverables and request changes when needed.</p>
<h3 data-zh="决策与检查">Decide &amp; check</h3>
<p data-zh="在需要补充信息、权限或关键决策时介入。检查交付物,必要时要求修改。">Step in for missing information, permissions, or key decisions. Check the deliverables and request changes when needed.</p>
</div>
</div>
</section>
<!-- ================= MODES ================= -->
<section class="section modes" id="modes">
<p class="eyebrow reveal" data-reveal>COLLABORATION MODES</p>
<h2 class="section-title reveal" data-reveal>Three ways agents work together.</h2>
<p class="section-sub reveal" data-reveal>Work with one Agent, compare independent replies, or let a group advance a task together.</p>
<p class="eyebrow reveal" data-reveal data-zh="协作模式">COLLABORATION MODES</p>
<h2 class="section-title reveal" data-reveal data-zh="三种方式,推进你的工作。">Three ways agents work together.</h2>
<p class="section-sub reveal" data-reveal data-zh="与一个 Agent 深入工作,比较多份独立回答,或让整个小组一起推进任务。">Work with one Agent, compare independent replies, or let a group advance a task together.</p>
<div class="mode-grid">
<article class="mode-card spotlight reveal" data-reveal style="--d:0s">
<span class="mode-glyph-wrap"><span class="mode-glyph">&gt;</span></span>
<h3>Direct</h3>
<p>One selected Agent keeps its conversation and native session when supported.</p>
<span class="mode-best">Best for focused work with one Agent.</span>
<h3 data-zh="直接会话">Direct</h3>
<p data-zh="与选定的一个 Agent 持续对话,支持时保持其原生会话。">One selected Agent keeps its conversation and native session when supported.</p>
<span class="mode-best" data-zh="适合与一个 Agent 专注完成工作。">Best for focused work with one Agent.</span>
</article>
<article class="mode-card spotlight reveal" data-reveal style="--d:0.12s">
<span class="mode-glyph-wrap"><span class="mode-glyph">*</span></span>
<h3>Concurrent Responses</h3>
<p>Selected Agents receive the same frozen task snapshot and return independent replies in stable order.</p>
<span class="mode-best">Best for comparing approaches before choosing one.</span>
<h3 data-zh="并发回复">Concurrent Responses</h3>
<p data-zh="选定的 Agent 收到同一份冻结任务快照,各自独立回复,并按稳定顺序展示。">Selected Agents receive the same frozen task snapshot and return independent replies in stable order.</p>
<span class="mode-best" data-zh="适合比较不同思路,再选择方向。">Best for comparing approaches before choosing one.</span>
</article>
<article class="mode-card spotlight reveal" data-reveal style="--d:0.24s">
<span class="mode-glyph-wrap"><span class="mode-glyph">#</span></span>
<h3>Auto Discussion V4</h3>
<p>Agents propose, discuss differences, request peer contributions, carry out authorized work, and check results.</p>
<span class="mode-best">Best for multi-step work with less manual coordination.</span>
<h3 data-zh="自动讨论 V4">Auto Discussion V4</h3>
<p data-zh="Agent 提出方案、讨论分歧、请求同伴贡献,执行授权工作并检查成果。">Agents propose, discuss differences, request peer contributions, carry out authorized work, and check results.</p>
<span class="mode-best" data-zh="适合希望减少人工协调的多步骤任务。">Best for multi-step work with less manual coordination.</span>
</article>
</div>
</section>
<!-- ================= CAPABILITIES ================= -->
<section class="section capabilities" id="capabilities">
<p class="eyebrow reveal" data-reveal>WHAT SUPPORTS THE WORK</p>
<h2 class="section-title reveal" data-reveal>Less coordinating. More work moving forward.</h2>
<p class="section-sub reveal" data-reveal>Shared context and peer contributions help Agents advance the task. You keep control of the boundaries and key decisions.</p>
<p class="eyebrow reveal" data-reveal data-zh="让协作持续推进">WHAT SUPPORTS THE WORK</p>
<h2 class="section-title reveal" data-reveal data-zh="少一些协调,让工作继续向前。">Less coordinating. More work moving forward.</h2>
<p class="section-sub reveal" data-reveal data-zh="共享上下文与同伴贡献帮助 Agent 推进任务,边界和关键决策仍由你掌握。">Shared context and peer contributions help Agents advance the task. You keep control of the boundaries and key decisions.</p>
<div class="cap-grid">
<article class="cap-card spotlight tilt reveal" data-reveal style="--d:0s">
<div class="cap-icon"><span class="cap-gate"></span></div>
<h3>You Set the Boundaries</h3>
<p>Choose the goal and permissions. Workspace writes are opt-in; requests for information or key decisions come back to you.</p>
<span class="cap-tag">Decide when needed</span>
<h3 data-zh="由你设定边界">You Set the Boundaries</h3>
<p data-zh="选择目标与权限。工作区写入需主动授权;信息缺失或需要关键决策时,任务会交回给你。">Choose the goal and permissions. Workspace writes are opt-in; requests for information or key decisions come back to you.</p>
<span class="cap-tag" data-zh="需要时作出决策">Decide when needed</span>
</article>
<article class="cap-card spotlight tilt reveal" data-reveal style="--d:0.08s">
<div class="cap-icon"><span class="cap-snap"></span></div>
<h3>Shared Context</h3>
<p>Agents start from the same task snapshot and receive peer contributions as discussion progresses.</p>
<span class="cap-tag">Less context relaying</span>
<h3 data-zh="共享上下文">Shared Context</h3>
<p data-zh="Agent 从同一份任务快照开始,并在讨论推进时接收同伴的贡献。">Agents start from the same task snapshot and receive peer contributions as discussion progresses.</p>
<span class="cap-tag" data-zh="减少上下文转述">Less context relaying</span>
</article>
<article class="cap-card spotlight tilt reveal" data-reveal style="--d:0.16s">
<div class="cap-icon"><span class="cap-trail"></span></div>
<h3>Agents Advance the Work</h3>
<p>Agents request peer input, continue authorized work, and review deliverables. Revisit replies and run status when you need to.</p>
<span class="cap-tag">Contribute, check, refine</span>
<h3 data-zh="Agent 自主推进">Agents Advance the Work</h3>
<p data-zh="Agent 请求同伴补充、接续授权工作并检查交付物。需要时可回看回复与运行状态。">Agents request peer input, continue authorized work, and review deliverables. Revisit replies and run status when you need to.</p>
<span class="cap-tag" data-zh="贡献、检查、完善">Contribute, check, refine</span>
</article>
<article class="cap-card spotlight tilt reveal" data-reveal style="--d:0.24s">
<div class="cap-icon"><span class="cap-lock"></span></div>
<h3>Local-first Boundary</h3>
<p>Electron work cell on your machine. No remote Agent fleet. Conversations stay local.</p>
<span class="cap-tag">Your CLI, your data</span>
<h3 data-zh="本地优先">Local-first Boundary</h3>
<p data-zh="在你的电脑上运行 Electron 工作空间,对话保存在本地,无需远程 Agent 集群。">Electron work cell on your machine. No remote Agent fleet. Conversations stay local.</p>
<span class="cap-tag" data-zh="已有 CLI,本地记录">Your CLI, your data</span>
</article>
</div>
</section>
<!-- ================= SCENARIO (animated stepper) ================= -->
<section class="section scenario" id="scenario">
<p class="eyebrow reveal" data-reveal>INSIDE A REAL RUN</p>
<h2 class="section-title reveal" data-reveal>A release review, from proposals to a user decision.</h2>
<p class="section-sub reveal" data-reveal>Historical V1.0.3 session excerpts: Codex, Hermes, and OpenCodeReview discussed a private-beta release, with the user steering a key tradeoff. This example shows discussion and human input, rather than an end-to-end execution task.</p>
<p class="eyebrow reveal" data-reveal data-zh="一次真实协作">INSIDE A REAL RUN</p>
<h2 class="section-title reveal" data-reveal data-zh="一次发布评审,从提案到用户决策。">A release review, from proposals to a user decision.</h2>
<p class="section-sub reveal" data-reveal data-zh="V1.0.3 历史会话摘录:Codex、Hermes 和 OpenCodeReview 讨论内测发布,用户介入关键取舍。这段案例展示讨论与人工决策,不代表完整的自动执行任务。">Historical V1.0.3 session excerpts: Codex, Hermes, and OpenCodeReview discussed a private-beta release, with the user steering a key tradeoff. This example shows discussion and human input, rather than an end-to-end execution task.</p>
<div class="scenario-stage reveal" data-reveal data-scenario>
<div class="mac-titlebar">
<span class="mac-lights" aria-hidden="true"><i class="light-r"></i><i class="light-y"></i><i class="light-g"></i></span>
<div class="scenario-meta">
<span class="meta-pill"><span class="meta-dot"></span> Auto Discussion V4</span>
<span class="meta-name">Launch Readiness Council</span>
<span class="meta-pill" data-zh="&lt;span class=&quot;meta-dot&quot;&gt;&lt;/span&gt; 自动讨论 V4"><span class="meta-dot"></span> Auto Discussion V4</span>
<span class="meta-name" data-zh="发布准备评审小组">Launch Readiness Council</span>
</div>
<div class="titlebar-right">
<span class="meta-round" data-round>Round 1 · Propose</span>
<span class="meta-round" data-round data-zh="第 1 轮 · 提案">Round 1 · Propose</span>
<div class="seg-control" role="group" aria-label="Playback mode">
<button type="button" class="seg-btn is-on" data-mode="auto">Auto</button>
<button type="button" class="seg-btn" data-mode="order">Sequence</button>
<button type="button" class="seg-btn is-on" data-mode="auto" aria-pressed="true" data-zh="自动播放">Auto</button>
<button type="button" class="seg-btn" data-mode="order" aria-pressed="false" data-zh="完整记录">Sequence</button>
</div>
</div>
</div>
@@ -408,42 +411,42 @@
<ol class="timeline" data-timeline-list>
<li class="t-step is-active" data-step="0">
<span class="t-node" data-node></span>
<span class="t-time">R1 · 05:34</span>
<span class="t-time" data-zh="第 1 轮 · 05:34">R1 · 05:34</span>
<div class="t-bubble t-bubble-codex">
<header><img src="assets/agents/codex.svg" alt="" /><strong>Codex</strong><span class="t-role">Proposal</span></header>
<p>Ship a <strong>private, invitation-only preview</strong> with one promise: keep a real task coherent while users move between Agents. Don't market workflow automation, universal compatibility, or public distribution readiness yet.</p>
<header><img src="assets/agents/codex.svg" alt="" /><strong>Codex</strong><span class="t-role" data-zh="方案提议">Proposal</span></header>
<p data-zh="先发布&lt;strong&gt;仅限受邀用户的内测版&lt;/strong&gt;,只承诺一件事:用户在不同 Agent 之间切换时,同一个任务仍能连贯推进。暂不宣传工作流自动化、通用兼容或已具备公开发行条件。">Ship a <strong>private, invitation-only preview</strong> with one promise: keep a real task coherent while users move between Agents. Don't market workflow automation, universal compatibility, or public distribution readiness yet.</p>
</div>
</li>
<li class="t-step" data-step="1">
<span class="t-node" data-node></span>
<span class="t-time">R1 · 05:38</span>
<span class="t-time" data-zh="第 1 轮 · 05:38">R1 · 05:38</span>
<div class="t-bubble t-bubble-hermes">
<header><img src="assets/agents/hermes.svg" alt="" /><strong>Hermes</strong><span class="t-role">Evidence challenge</span></header>
<p>The beta should <strong>test behavior</strong>, not enthusiasm. Measure whether users resume the same task, bring in a second Agent for a clear reason, and use the trace before accepting a result. Without those signals, the category story is still unproven.</p>
<header><img src="assets/agents/hermes.svg" alt="" /><strong>Hermes</strong><span class="t-role" data-zh="依据质询">Evidence challenge</span></header>
<p data-zh="内测应当&lt;strong&gt;验证行为&lt;/strong&gt;,而非热情。观察用户是否继续同一任务、是否有明确理由引入第二个 Agent,以及是否在接受结果前回看过程。没有这些信号,产品定位仍有待验证。">The beta should <strong>test behavior</strong>, not enthusiasm. Measure whether users resume the same task, bring in a second Agent for a clear reason, and use the trace before accepting a result. Without those signals, the category story is still unproven.</p>
</div>
</li>
<li class="t-step" data-step="2">
<span class="t-node" data-node></span>
<span class="t-time">R2 · 05:44</span>
<span class="t-time" data-zh="第 2 轮 · 05:44">R2 · 05:44</span>
<div class="t-bubble t-bubble-ocr">
<header><img src="assets/agents/opencodereview.svg" alt="" /><strong>OpenCodeReview</strong><span class="t-role">Engineering review</span></header>
<p>The MVP <strong>is</strong> stable enough for a controlled evaluation, but not for a public download. Tests and packaging pass; the blocker is trusted macOS signing and notarization. Keep the first release private and label unsupported platforms explicitly.</p>
<header><img src="assets/agents/opencodereview.svg" alt="" /><strong>OpenCodeReview</strong><span class="t-role" data-zh="工程评审">Engineering review</span></header>
<p data-zh="MVP &lt;strong&gt;已能支持受控评估&lt;/strong&gt;,但还不适合公开下载。测试和打包通过,阻碍在于可信的 macOS 签名与公证。首版继续保持内测,并明确标出不支持的平台。">The MVP <strong>is</strong> stable enough for a controlled evaluation, but not for a public download. Tests and packaging pass; the blocker is trusted macOS signing and notarization. Keep the first release private and label unsupported platforms explicitly.</p>
</div>
</li>
<li class="t-step" data-step="3">
<span class="t-node" data-node></span>
<span class="t-time">R2 · 05:52</span>
<span class="t-time" data-zh="第 2 轮 · 05:52">R2 · 05:52</span>
<div class="t-bubble t-bubble-you">
<header><span class="you-dot">You</span><strong>You</strong><span class="t-role">Reconciliation</span></header>
<p>Round 2: reconcile the disagreement. Keep only decisions we can verify this week and give me <strong>one acceptance gate</strong>.</p>
<header><span class="you-dot" aria-hidden="true">Y</span><strong data-zh="你">You</strong><span class="t-role" data-zh="协调分歧">Reconciliation</span></header>
<p data-zh="第 2 轮:协调分歧。只保留本周能验证的决定,并给出&lt;strong&gt;一项验收条件&lt;/strong&gt;。">Round 2: reconcile the disagreement. Keep only decisions we can verify this week and give me <strong>one acceptance gate</strong>.</p>
</div>
</li>
<li class="t-step" data-step="4">
<span class="t-node" data-node></span>
<span class="t-time">Adoption</span>
<span class="t-time" data-zh="用户决策">Adoption</span>
<div class="t-bubble t-bubble-gate">
<header><span class="gate-dot"></span><strong>Human Gate</strong><span class="t-role">Decision</span></header>
<p>Ship V1.0.3 to the private preview only. <strong>No-Go for public release.</strong> Reopen after Developer ID signing and notarization pass on a clean Mac.</p>
<header><span class="gate-dot"></span><strong data-zh="人工决策">Human Gate</strong><span class="t-role" data-zh="最终决定">Decision</span></header>
<p data-zh="V1.0.3 仅向内测用户发布,&lt;strong&gt;暂不公开发行。&lt;/strong&gt;待 Developer ID 签名与公证在干净的 Mac 环境通过后重新评估。">Ship V1.0.3 to the private preview only. <strong>No-Go for public release.</strong> Reopen after Developer ID signing and notarization pass on a clean Mac.</p>
</div>
</li>
</ol>
@@ -453,6 +456,7 @@
<button type="button" class="rail-btn" data-prev aria-label="Previous step">‹</button>
<span class="rail-status" data-status>1 / 5</span>
<button type="button" class="rail-btn" data-next aria-label="Next step">›</button>
<button type="button" class="playback-toggle" data-pause aria-pressed="false">Pause</button>
</div>
</div>
</div>
@@ -460,75 +464,75 @@
<!-- ================= PRODUCT SCREENSHOTS (CardSwap) ================= -->
<section class="section product" id="product">
<p class="eyebrow reveal" data-reveal>INSIDE THE WORK CELL</p>
<h2 class="section-title reveal" data-reveal>One workspace for the group and its work.</h2>
<p class="eyebrow reveal" data-reveal data-zh="走进工作空间">INSIDE THE WORK CELL</p>
<h2 class="section-title reveal" data-reveal data-zh="一个工作空间,承接整个小组的工作。">One workspace for the group and its work.</h2>
<div class="card-swap-stage reveal" data-reveal>
<div class="card-swap-info">
<div class="cs-info-item is-active" data-cs-info="0">
<h3>Local Agent discovery</h3>
<p>Pick the participants before any work runs. Meldwork detects installed CLIs and shows their adapter status.</p>
<h3 data-zh="发现本地 Agent">Local Agent discovery</h3>
<p data-zh="在工作开始前选择参与者。Meldwork 检测已安装的 CLI,并显示适配器状态。">Pick the participants before any work runs. Meldwork detects installed CLIs and shows their adapter status.</p>
</div>
<div class="cs-info-item" data-cs-info="1">
<h3>Multi-agent review</h3>
<p>Agents compare approaches and check each other's contributions in the same task context.</p>
<h3 data-zh="多 Agent 评审">Multi-agent review</h3>
<p data-zh="Agent 在同一个任务上下文中比较思路,检查彼此的贡献。">Agents compare approaches and check each other's contributions in the same task context.</p>
</div>
<div class="cs-info-item" data-cs-info="2">
<h3>Auto Discussion V4</h3>
<p>Agents discuss, request contributions, and advance authorized work across rounds. Step in when a decision needs you.</p>
<h3 data-zh="自动讨论 V4">Auto Discussion V4</h3>
<p data-zh="Agent 分轮讨论、请求同伴贡献并推进授权工作。需要你作出决策时再介入。">Agents discuss, request contributions, and advance authorized work across rounds. Step in when a decision needs you.</p>
</div>
<div class="cs-info-item" data-cs-info="3">
<h3>Direct mode</h3>
<p>Work with one Agent and continue its native session when supported. Keep the conversation and results together.</p>
<h3 data-zh="直接会话">Direct mode</h3>
<p data-zh="与一个 Agent 工作,支持时延续原生会话。对话与结果保存在一起。">Work with one Agent and continue its native session when supported. Keep the conversation and results together.</p>
</div>
</div>
<div class="card-swap-3d" data-card-swap>
<div class="card-swap-clip">
<div class="cs-card" data-card-idx="0">
<img src="assets/screenshots/meldwork-agent-discovery.png" alt="Meldwork detecting local Agent CLIs" loading="lazy" />
<div class="cs-caption">Local Agent discovery</div>
<div class="cs-caption" data-zh="发现本地 Agent">Local Agent discovery</div>
</div>
<div class="cs-card" data-card-idx="1">
<img src="assets/screenshots/meldwork-multi-agent-review.png" alt="Agents discussing and reviewing a shared task" loading="lazy" />
<div class="cs-caption">Multi-agent review</div>
<div class="cs-caption" data-zh="多 Agent 评审">Multi-agent review</div>
</div>
<div class="cs-card" data-card-idx="2">
<img src="assets/screenshots/meldwork-group-collaboration.png" alt="Auto Discussion V4 group collaboration" loading="lazy" />
<div class="cs-caption">Auto Discussion V4</div>
<div class="cs-caption" data-zh="自动讨论 V4">Auto Discussion V4</div>
</div>
<div class="cs-card" data-card-idx="3">
<img src="assets/screenshots/meldwork-direct-multimodal.png" alt="Direct multimodal session with local files" loading="lazy" />
<div class="cs-caption">Direct mode</div>
<div class="cs-caption" data-zh="直接会话">Direct mode</div>
</div>
</div>
</div>
</div>
<p class="product-foot reveal" data-reveal>Try a task such as reviewing a PR, analyzing a bug, or drafting a plan. Choose compatible Agents, give them shared context, and start Auto Discussion. They can compare approaches, request further work, and check deliverables. Authorize workspace writes when implementation is needed, and respond when they need a key decision.</p>
<p class="product-foot reveal" data-reveal data-zh="试试评审 PR、分析问题或起草方案。选择兼容的 Agent,提供共享上下文,开启自动讨论。它们可以比较思路、请求后续工作并检查成果。需要实施时授权工作区写入,需要关键决策时再由你处理。">Try a task such as reviewing a PR, analyzing a bug, or drafting a plan. Choose compatible Agents, give them shared context, and start Auto Discussion. They can compare approaches, request further work, and check deliverables. Authorize workspace writes when implementation is needed, and respond when they need a key decision.</p>
</section>
<!-- ================= WHY / DIFFER ================= -->
<section class="section why" id="why">
<div class="why-grid">
<div class="why-left">
<p class="eyebrow reveal" data-reveal>HOW MELDWORK DIFFERS</p>
<h2 class="section-title reveal" data-reveal>Spend less time managing the handoffs.</h2>
<p class="section-sub reveal" data-reveal>Meldwork brings the Agent tools you already run into a <strong>local-first collaboration workspace</strong>. Let the group discuss and advance the task, while you focus on the goal, boundaries, and key decisions.</p>
<p class="eyebrow reveal" data-reveal data-zh="MELDWORK 的特点">HOW MELDWORK DIFFERS</p>
<h2 class="section-title reveal" data-reveal data-zh="少花些时间,协调每一次交接。">Spend less time managing the handoffs.</h2>
<p class="section-sub reveal" data-reveal data-zh="Meldwork 把你已有的 Agent 工具汇聚到&lt;strong&gt;本地优先的协作工作空间&lt;/strong&gt;。让小组讨论并推进任务,你专注目标、边界和关键决策。">Meldwork brings the Agent tools you already run into a <strong>local-first collaboration workspace</strong>. Let the group discuss and advance the task, while you focus on the goal, boundaries, and key decisions.</p>
</div>
<ul class="why-list">
<li class="reveal" data-reveal style="--d:0s">
<h3>Agents coordinate the next contribution</h3>
<p>Agents can request a peer's input or review, and the delivery owner judges whether more work or a user decision is needed.</p>
<h3 data-zh="Agent 协调下一步贡献">Agents coordinate the next contribution</h3>
<p data-zh="Agent 可请求同伴补充或评审,由交付负责人判断是否还需工作或用户决策。">Agents can request a peer's input or review, and the delivery owner judges whether more work or a user decision is needed.</p>
</li>
<li class="reveal" data-reveal style="--d:0.08s">
<h3>Authorized execution and peer review</h3>
<p>Enable workspace writes for implementation tasks. Agents can carry out work within that scope and ask peers to check the deliverables.</p>
<h3 data-zh="授权执行,相互检查">Authorized execution and peer review</h3>
<p data-zh="为实施任务开启工作区写入。Agent 可在授权范围内执行,并请求同伴检查交付物。">Enable workspace writes for implementation tasks. Agents can carry out work within that scope and ask peers to check the deliverables.</p>
</li>
<li class="reveal" data-reveal style="--d:0.16s">
<h3>Local-first boundaries</h3>
<p>Runs in your Electron work cell with existing CLIs. No cloud Agent fleet, no required server, conversations stay local.</p>
<h3 data-zh="本地优先的边界">Local-first boundaries</h3>
<p data-zh="用已有 CLI 在本地 Electron 工作空间运行,无需云端 Agent 集群或必选服务器,对话保存在本地。">Runs in your Electron work cell with existing CLIs. No cloud Agent fleet, no required server, conversations stay local.</p>
</li>
<li class="reveal" data-reveal style="--d:0.24s">
<h3>Shared context and local history</h3>
<p>Keep the brief, peer replies, run state, and deliverables together so you can revisit progress and continue the work.</p>
<h3 data-zh="共享上下文,本地记录">Shared context and local history</h3>
<p data-zh="任务说明、同伴回复、运行状态和交付物放在一起,方便回看进度并继续工作。">Keep the brief, peer replies, run state, and deliverables together so you can revisit progress and continue the work.</p>
</li>
</ul>
</div>
@@ -536,67 +540,67 @@
<!-- ================= COMPARISON TABLE ================= -->
<section class="section compare" id="compare">
<p class="eyebrow reveal" data-reveal>WHERE MELDWORK FITS</p>
<h2 class="section-title reveal" data-reveal>Where task collaboration fits in your workflow.</h2>
<p class="section-sub reveal" data-reveal>Meldwork focuses on shared tasks across the local Agent tools you choose. Here is how that focus relates to other ways of organizing Agent work.</p>
<p class="eyebrow reveal" data-reveal data-zh="它在工作流中的位置">WHERE MELDWORK FITS</p>
<h2 class="section-title reveal" data-reveal data-zh="把任务协作,接入你的工作流程。">Where task collaboration fits in your workflow.</h2>
<p class="section-sub reveal" data-reveal data-zh="Meldwork 聚焦你选定的本地 Agent 之间的共享任务。以下展示它与其他 Agent 工作组织方式的关系。">Meldwork focuses on shared tasks across the local Agent tools you choose. Here is how that focus relates to other ways of organizing Agent work.</p>
<div class="cmp-table reveal" data-reveal role="table">
<div class="cmp-row cmp-head" role="row">
<div role="columnheader">Category</div>
<div role="columnheader">What it focuses on</div>
<div role="columnheader">Meldwork's difference</div>
<div role="columnheader" data-zh="工作方式">Category</div>
<div role="columnheader" data-zh="关注重点">What it focuses on</div>
<div role="columnheader" data-zh="Meldwork 的侧重">Meldwork's difference</div>
</div>
<div class="cmp-row" role="row">
<div role="cell" class="cmp-cat">Individual Agent sessions</div>
<div role="cell">Work with an Agent through its own interface and tools.</div>
<div role="cell">Bring selected CLIs into a shared task where Agents can discuss and request peer contributions.</div>
<div role="cell" class="cmp-cat" data-zh="独立 Agent 会话">Individual Agent sessions</div>
<div role="cell" data-zh="通过 Agent 自身的界面与工具开展工作。">Work with an Agent through its own interface and tools.</div>
<div role="cell" data-zh="把选定的 CLI 带入同一任务,让 Agent 讨论并请求同伴贡献。">Bring selected CLIs into a shared task where Agents can discuss and request peer contributions.</div>
</div>
<div class="cmp-row" role="row">
<div role="cell" class="cmp-cat">Hosted Agent execution</div>
<div role="cell">Run work in a remotely managed environment.</div>
<div role="cell">Local Electron desktop app using the CLIs already installed on your machine.</div>
<div role="cell" class="cmp-cat" data-zh="托管式 Agent 执行">Hosted Agent execution</div>
<div role="cell" data-zh="在远程管理的环境中执行工作。">Run work in a remotely managed environment.</div>
<div role="cell" data-zh="本地 Electron 桌面应用,调用你电脑上已经安装的 CLI。">Local Electron desktop app using the CLIs already installed on your machine.</div>
</div>
<div class="cmp-row" role="row">
<div role="cell" class="cmp-cat">Agent messaging</div>
<div role="cell">Exchange messages and events between participants.</div>
<div role="cell">Connect discussion to authorized execution, deliverable review, and task progress decisions.</div>
<div role="cell" class="cmp-cat" data-zh="Agent 消息通信">Agent messaging</div>
<div role="cell" data-zh="在参与者之间交换消息与事件。">Exchange messages and events between participants.</div>
<div role="cell" data-zh="把讨论连接到授权执行、交付物检查和任务进度决策。">Connect discussion to authorized execution, deliverable review, and task progress decisions.</div>
</div>
<div class="cmp-row" role="row">
<div role="cell" class="cmp-cat">Parallel workspaces</div>
<div role="cell">Organize separate tasks or branches running at the same time.</div>
<div role="cell">Let different Agent CLIs build on each other's contributions to the same task.</div>
<div role="cell" class="cmp-cat" data-zh="并行工作空间">Parallel workspaces</div>
<div role="cell" data-zh="组织同时运行的独立任务或分支。">Organize separate tasks or branches running at the same time.</div>
<div role="cell" data-zh="让不同的 Agent CLI 围绕同一任务,接续彼此的贡献。">Let different Agent CLIs build on each other's contributions to the same task.</div>
</div>
<div class="cmp-row" role="row">
<div role="cell" class="cmp-cat">Custom orchestration</div>
<div role="cell">Build and maintain your own task routing and collaboration logic.</div>
<div role="cell">A ready-to-use desktop workflow across 12+ heterogeneous CLIs — no orchestration framework to assemble.</div>
<div role="cell" class="cmp-cat" data-zh="自建编排流程">Custom orchestration</div>
<div role="cell" data-zh="自行构建与维护任务路由和协作逻辑。">Build and maintain your own task routing and collaboration logic.</div>
<div role="cell" data-zh="开箱即用的桌面协作流程,支持 12 种以上不同的 CLI,无需组装编排框架。">A ready-to-use desktop workflow across 12+ heterogeneous CLIs — no orchestration framework to assemble.</div>
</div>
</div>
</section>
<!-- ================= TECH SPECS / BOUNDARY ================= -->
<section class="section boundary" id="boundary">
<p class="eyebrow reveal" data-reveal>THE BOUNDARY</p>
<h2 class="section-title reveal" data-reveal>What Meldwork is, and isn't.</h2>
<p class="eyebrow reveal" data-reveal data-zh="产品边界">THE BOUNDARY</p>
<h2 class="section-title reveal" data-reveal data-zh="它能做什么,边界又在哪里。">What Meldwork is, and isn't.</h2>
<div class="bnd-grid">
<div class="bnd-col reveal" data-reveal style="--d:0s">
<h3><span class="bnd-glyph bnd-yes"></span> Is</h3>
<h3 data-zh="&lt;span class=&quot;bnd-glyph bnd-yes&quot;&gt;&lt;/span&gt; 已有能力"><span class="bnd-glyph bnd-yes"></span> Is</h3>
<ul>
<li>A local-first desktop workspace for multi-Agent collaboration.</li>
<li>A working group built from the Agent CLIs you select.</li>
<li>Shared context, authorized execution, peer review, and local run history.</li>
<li>Workspace writes as opt-in workflow controls.</li>
<li>Source-available under the Meldwork Non-Commercial Source License 1.0.</li>
<li data-zh="本地优先的多 Agent 桌面协作工作空间。">A local-first desktop workspace for multi-Agent collaboration.</li>
<li data-zh="由你选定的 Agent CLI 组成的工作小组。">A working group built from the Agent CLIs you select.</li>
<li data-zh="共享上下文、授权执行、同伴检查与本地运行记录。">Shared context, authorized execution, peer review, and local run history.</li>
<li data-zh="工作区写入需要主动授权。">Workspace writes as opt-in workflow controls.</li>
<li data-zh="按 Apache License 2.0 开源。">Open source under the Apache License 2.0.</li>
</ul>
</div>
<div class="bnd-col reveal" data-reveal style="--d:0.12s">
<h3><span class="bnd-glyph bnd-no"></span> Isn't</h3>
<h3 data-zh="&lt;span class=&quot;bnd-glyph bnd-no&quot;&gt;&lt;/span&gt; 能力边界"><span class="bnd-glyph bnd-no"></span> Isn't</h3>
<ul>
<li>Not a hosted Agent fleet or general Agent framework.</li>
<li>Not automatic participant selection from a larger roster.</li>
<li>Not a guarantee that every task finishes without your input.</li>
<li>Not an operating-system sandbox — writes happen through your shell.</li>
<li>Not fully offline — a selected Agent may send prompts to its own CLI provider.</li>
<li data-zh="不提供托管式 Agent 集群或通用 Agent 框架。">Not a hosted Agent fleet or general Agent framework.</li>
<li data-zh="不会从更大的候选池中自动选择参与者。">Not automatic participant selection from a larger roster.</li>
<li data-zh="不保证每项任务都能在没有你参与的情况下完成。">Not a guarantee that every task finishes without your input.</li>
<li data-zh="不是操作系统沙箱,写入仍通过本机 Shell 执行。">Not an operating-system sandbox — writes happen through your shell.</li>
<li data-zh="并非完全离线,选定的 Agent 可能向自己的 CLI 服务商发送提示。">Not fully offline — a selected Agent may send prompts to its own CLI provider.</li>
</ul>
</div>
</div>
@@ -604,81 +608,81 @@
<!-- ================= FAQ ================= -->
<section class="section faq" id="faq">
<p class="eyebrow reveal" data-reveal>ANSWERS</p>
<h2 class="section-title reveal" data-reveal>Questions teams ask before adopting.</h2>
<p class="section-sub reveal" data-reveal>Short, honest answers — the long versions live in <a href="https://github.com/Ryder-Sun/Meldwork/blob/main/architecture.md" target="_blank" rel="noopener">architecture.md ↗</a>.</p>
<p class="eyebrow reveal" data-reveal data-zh="常见问题">ANSWERS</p>
<h2 class="section-title reveal" data-reveal data-zh="开始使用前,你可能想了解。">Questions teams ask before adopting.</h2>
<p class="section-sub reveal" data-reveal data-zh="简明回答,详细说明请查看 &lt;a href=&quot;https://github.com/Ryder-Sun/Meldwork/blob/main/architecture.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;架构文档 ↗&lt;/a&gt;。">Short, honest answers — the long versions live in <a href="https://github.com/Ryder-Sun/Meldwork/blob/main/architecture.md" target="_blank" rel="noopener">architecture.md ↗</a>.</p>
<ul class="acc-list">
<li class="acc-item reveal" data-reveal style="--d:0s">
<button type="button" class="acc-trigger" aria-expanded="false">
<span>What is Meldwork?</span>
<span data-zh="Meldwork 是什么?">What is Meldwork?</span>
<span class="acc-icon" aria-hidden="true"></span>
</button>
<div class="acc-panel">
<p>Meldwork is a <strong>local-first multi-Agent collaboration workspace</strong> for macOS. It brings your existing Agents into one working group to share context, discuss approaches, carry out authorized work, and check results. You choose the participants, set goals and permissions, and step in when information or key decisions are needed.</p>
<p data-zh="Meldwork 是面向 macOS 的&lt;strong&gt;本地优先多 Agent 协作工作空间&lt;/strong&gt;。它把你已有的 Agent 组织成工作小组,共享上下文、讨论方案、执行授权工作并检查成果。你选择成员、设定目标和权限,在需要补充信息或关键决策时介入。">Meldwork is a <strong>local-first multi-Agent collaboration workspace</strong> for macOS. It brings your existing Agents into one working group to share context, discuss approaches, carry out authorized work, and check results. You choose the participants, set goals and permissions, and step in when information or key decisions are needed.</p>
</div>
</li>
<li class="acc-item reveal" data-reveal style="--d:0.06s">
<button type="button" class="acc-trigger" aria-expanded="false">
<span>Does Meldwork send my code to a remote service?</span>
<span data-zh="Meldwork 会把代码发送到远程服务吗?">Does Meldwork send my code to a remote service?</span>
<span class="acc-icon" aria-hidden="true"></span>
</button>
<div class="acc-panel">
<p>Meldwork stores conversations and coordinates work locally. Selected Agents may send prompts, attachments, or Skills to the model provider configured in <em>their own</em> CLI. Local-first describes where Meldwork stores and coordinates the work; it does not mean the model runs offline.</p>
<p data-zh="Meldwork 在本地保存对话、协调工作。选定的 Agent 可能把提示、附件或 Skill 发送到&lt;em&gt;自身 CLI&lt;/em&gt; 配置的模型服务商。本地优先描述的是 Meldwork 存储与协调工作的位置,不代表模型离线运行。">Meldwork stores conversations and coordinates work locally. Selected Agents may send prompts, attachments, or Skills to the model provider configured in <em>their own</em> CLI. Local-first describes where Meldwork stores and coordinates the work; it does not mean the model runs offline.</p>
</div>
</li>
<li class="acc-item reveal" data-reveal style="--d:0.06s">
<button type="button" class="acc-trigger" aria-expanded="false">
<span>Which General Agents does Meldwork support?</span>
<span data-zh="支持哪些 General Agent?">Which General Agents does Meldwork support?</span>
<span class="acc-icon" aria-hidden="true"></span>
</button>
<div class="acc-panel">
<p>Twelve today — Codex, Claude Code, Gemini CLI, Qwen Code, Kimi Code, MiMo Code, OpenCode, OpenCodeReview, Hermes, Pi Agent, OpenClaw, and WorkBuddy. Approved Agent Connectors can be added through the Agent Connector SDK; custom executables use the desktop custom-Agent path.</p>
<p data-zh="目前包括 Codex、Claude Code、Gemini CLI、Qwen Code、Kimi Code、MiMo Code、OpenCode、OpenCodeReview、Hermes、Pi Agent、OpenClaw 和 WorkBuddy。可通过 Agent Connector SDK 添加已批准的连接器,也可使用桌面端的自定义 Agent 入口。">Twelve today — Codex, Claude Code, Gemini CLI, Qwen Code, Kimi Code, MiMo Code, OpenCode, OpenCodeReview, Hermes, Pi Agent, OpenClaw, and WorkBuddy. Approved Agent Connectors can be added through the Agent Connector SDK; custom executables use the desktop custom-Agent path.</p>
</div>
</li>
<li class="acc-item reveal" data-reveal style="--d:0.12s">
<button type="button" class="acc-trigger" aria-expanded="false">
<span>What does Auto Discussion V4 actually do?</span>
<span data-zh="自动讨论 V4 如何工作?">What does Auto Discussion V4 actually do?</span>
<span class="acc-icon" aria-hidden="true"></span>
</button>
<div class="acc-panel">
<p>Selected Agents start with independent proposals, then discuss differences, request peer contributions, carry out authorized work, and check deliverables. A delivery owner judges progress and whether more work or a user decision is needed. You do not need to direct every round, but missing information, permissions, or key decisions can still require your input.</p>
<p data-zh="选定的 Agent 先独立提案,再讨论分歧、请求同伴贡献、执行授权工作并检查交付物。交付负责人判断进度,以及是否需要继续工作或请求用户决策。你不必安排每一轮,但信息缺失、权限或关键取舍仍可能需要你介入。">Selected Agents start with independent proposals, then discuss differences, request peer contributions, carry out authorized work, and check deliverables. A delivery owner judges progress and whether more work or a user decision is needed. You do not need to direct every round, but missing information, permissions, or key decisions can still require your input.</p>
</div>
</li>
<li class="acc-item reveal" data-reveal style="--d:0.18s">
<button type="button" class="acc-trigger" aria-expanded="false">
<span>How is Meldwork different from a terminal or IDE?</span>
<span data-zh="它与终端或 IDE 有什么不同?">How is Meldwork different from a terminal or IDE?</span>
<span class="acc-icon" aria-hidden="true"></span>
</button>
<div class="acc-panel">
<p>Meldwork adds task-level collaboration across selected Agent CLIs: shared context, peer discussion, requests for further contributions, authorized execution, and review of deliverables. This reduces the work of relaying replies and coordinating next steps yourself. Collaboration history and run state stay local so you can revisit progress and blockers.</p>
<p data-zh="Meldwork 在选定的 Agent CLI 之间增加任务层面的协作:共享上下文、同伴讨论、请求进一步贡献、授权执行和成果检查,减少你转述回复和协调下一步的工作。协作记录与运行状态保存在本地,方便回看进度和阻塞。">Meldwork adds task-level collaboration across selected Agent CLIs: shared context, peer discussion, requests for further contributions, authorized execution, and review of deliverables. This reduces the work of relaying replies and coordinating next steps yourself. Collaboration history and run state stay local so you can revisit progress and blockers.</p>
</div>
</li>
<li class="acc-item reveal" data-reveal style="--d:0.21s">
<button type="button" class="acc-trigger" aria-expanded="false">
<span>Do I still need to intervene?</span>
<span data-zh="我还需要介入吗?">Do I still need to intervene?</span>
<span class="acc-icon" aria-hidden="true"></span>
</button>
<div class="acc-panel">
<p>You choose the Agents, goal, context, and permissions. Auto Discussion lets Agents advance the work without you directing each turn, but missing information, permission requests, or key tradeoffs may require your input. Workspace writes are opt-in. Check important deliverables before using them; peer review does not guarantee correctness.</p>
<p data-zh="你选择 Agent、目标、上下文和权限。自动讨论让 Agent 无需你逐轮指挥也能推进工作,但信息缺失、权限请求或关键取舍可能需要你介入。工作区写入需主动授权。重要交付物在使用前仍需检查,同伴评审不保证结果正确。">You choose the Agents, goal, context, and permissions. Auto Discussion lets Agents advance the work without you directing each turn, but missing information, permission requests, or key tradeoffs may require your input. Workspace writes are opt-in. Check important deliverables before using them; peer review does not guarantee correctness.</p>
</div>
</li>
<li class="acc-item reveal" data-reveal style="--d:0.24s">
<button type="button" class="acc-trigger" aria-expanded="false">
<span>Is Meldwork free? What's the license?</span>
<span data-zh="Meldwork 免费吗?使用什么许可证?">Is Meldwork free? What's the license?</span>
<span class="acc-icon" aria-hidden="true"></span>
</button>
<div class="acc-panel">
<p>Source-available under the Meldwork Non-Commercial Source License 1.0. You can inspect, run, modify, and share it for non-commercial purposes. Commercial use requires prior written permission — see <a href="https://github.com/Ryder-Sun/Meldwork/blob/main/COMMERCIAL_USE.md" target="_blank" rel="noopener">COMMERCIAL_USE.md ↗</a>.</p>
<p data-zh="按 Apache License 2.0 开源。商业使用、私人使用、修改和再分发均依照许可证条款进行。许可证不授予 Meldwork 名称或标志的使用权,详见 &lt;a href=&quot;https://github.com/Ryder-Sun/Meldwork/blob/main/COMMERCIAL_USE.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;商业使用说明 ↗&lt;/a&gt;。">Open source under the Apache License 2.0. Commercial use, private use, modification, and redistribution are permitted under the license terms. The license does not grant rights to the Meldwork name or logo — see <a href="https://github.com/Ryder-Sun/Meldwork/blob/main/COMMERCIAL_USE.md" target="_blank" rel="noopener">COMMERCIAL_USE.md ↗</a>.</p>
</div>
</li>
<li class="acc-item reveal" data-reveal style="--d:0.3s">
<button type="button" class="acc-trigger" aria-expanded="false">
<span>Does it work on Intel macOS, Windows, or Linux?</span>
<span data-zh="支持 Intel Mac、Windows 或 Linux 吗?">Does it work on Intel macOS, Windows, or Linux?</span>
<span class="acc-icon" aria-hidden="true"></span>
</button>
<div class="acc-panel">
<p>The current V1.0.6 preview targets Apple silicon macOS (ad-hoc signed, not notarized — open <em>Open Anyway</em> in <em>System Settings → Privacy &amp; Security</em> on first launch). Cross-platform builds are on the roadmap; the app is Electron so the same source compiles everywhere once those targets land.</p>
<p data-zh="当前 V1.0.6 预览版面向 Apple 芯片 macOS,采用临时签名且未公证,首次启动可能需在&lt;em&gt;系统设置 → 隐私与安全性&lt;/em&gt;选择&lt;em&gt;仍要打开&lt;/em&gt;。跨平台构建已列入计划,应用基于 Electron,后续将逐步提供相应目标。">The current V1.0.6 preview targets Apple silicon macOS (ad-hoc signed, not notarized — open <em>Open Anyway</em> in <em>System Settings → Privacy &amp; Security</em> on first launch). Cross-platform builds are on the roadmap; the app is Electron so the same source compiles everywhere once those targets land.</p>
</div>
</li>
</ul>
@@ -687,30 +691,32 @@
<!-- ================= VELOCITY MARQUEE ================= -->
<div class="velocity" data-velocity aria-hidden="true">
<div class="velocity-track">
<span>DISCUSS</span><i>·</i><span>EXECUTE</span><i>·</i><span>REFINE</span><i>·</i>
<span>DISCUSS</span><i>·</i><span>EXECUTE</span><i>·</i><span>REFINE</span><i>·</i>
<span data-zh="讨论">DISCUSS</span><i>·</i><span data-zh="执行">EXECUTE</span><i>·</i><span data-zh="完善">REFINE</span><i>·</i>
<span data-zh="讨论">DISCUSS</span><i>·</i><span data-zh="执行">EXECUTE</span><i>·</i><span data-zh="完善">REFINE</span><i>·</i>
</div>
</div>
<!-- ================= CTA / FOOTER ================= -->
<footer class="cta-final" id="download">
<h2 class="final-title" data-split>Set the goal. Let Agents work together.</h2>
<p class="final-sub">V1.0.6 improves Agent recovery, keeps group discussions going through individual failures, delivers group attachments and knowledge sources, and highlights conversations awaiting your decision. <a href="https://github.com/Ryder-Sun/Meldwork/releases/tag/Meldwork-V1.0.6" target="_blank" rel="noopener">Read the release notes ↗</a></p>
<h2 class="final-title" data-split data-zh="提出目标,让 Agent 们协作完成。">Set the goal. Let Agents work together.</h2>
<p class="final-sub" data-zh="V1.0.6 改进 Agent 恢复能力,让群组在个别成员失败时继续讨论,完善附件和知识源传递,并标记等待你决策的会话。&lt;a href=&quot;https://github.com/Ryder-Sun/Meldwork/releases/tag/Meldwork-V1.0.6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;查看发布说明 ↗&lt;/a&gt;">V1.0.6 improves Agent recovery, keeps group discussions going through individual failures, delivers group attachments and knowledge sources, and highlights conversations awaiting your decision. <a href="https://github.com/Ryder-Sun/Meldwork/releases/tag/Meldwork-V1.0.6" target="_blank" rel="noopener">Read the release notes ↗</a></p>
<div class="cta-row">
<a class="cta-primary magnet" href="https://github.com/Ryder-Sun/Meldwork/releases/download/Meldwork-V1.0.6/Meldwork-0.1.6-arm64.dmg">
<span class="cta-label">Download V1.0.6</span>
<span class="cta-meta">Apple silicon · 123 MB</span>
<span class="cta-label" data-zh="下载 V1.0.6">Download V1.0.6</span>
<span class="cta-meta" data-zh="Apple 芯片 · 123 MB">Apple silicon · 123 MB</span>
</a>
<a class="cta-ghost" href="https://github.com/Ryder-Sun/Meldwork#readme" target="_blank" rel="noopener">Read the README <span aria-hidden="true">↗</span></a>
<a class="cta-ghost" href="https://github.com/Ryder-Sun/Meldwork#readme" target="_blank" rel="noopener" data-zh="阅读中文说明 &lt;span aria-hidden=&quot;true&quot;&gt;↗&lt;/span&gt;">Read the README <span aria-hidden="true">↗</span></a>
</div>
<div class="foot-meta">
<img class="foot-wordmark" src="assets/meldwork-wordmark-v3-dark.svg" alt="Meldwork" data-theme-src="assets/meldwork-wordmark-v3.svg" />
<span>Source-available · Meldwork Non-Commercial Source License 1.0</span>
<span>Local-first · Electron · macOS preview</span>
<span data-zh="开源 · Apache License 2.0">Open source · Apache License 2.0</span>
<span data-zh="本地优先 · Electron · macOS 预览版">Local-first · Electron · macOS preview</span>
<a class="foot-link" href="https://github.com/Ryder-Sun/Meldwork" target="_blank" rel="noopener">github.com/Ryder-Sun/Meldwork</a>
</div>
</footer>
<script src="main.js" defer></script>
<script src="i18n.js?v=20261021.3" defer></script>
<script src="glyph-embers.js?v=20261021.5" defer></script>
<script src="main.js?v=20261021.3" defer></script>
</body>
</html>
+95 -70
View File
@@ -29,6 +29,7 @@
if (!el || el.dataset.splitDone) return 1;
el.dataset.splitDone = "1";
var lines = el.querySelectorAll(".hl-line, [data-split-line]");
if (!lines.length) lines = [el];
var globalIndex = 0;
var baseDelay = parseFloat(el.dataset.splitDelay || "0.04");
lines.forEach(function (line) {
@@ -36,7 +37,9 @@
var raw = line.textContent;
line.textContent = "";
// Preserve words so spacing stays right; per-char inside each word.
var words = raw.split(/(\s+)/);
var words = doc.documentElement.lang === "zh-CN"
? raw.match(/[A-Za-z0-9]+|[^\sA-Za-z0-9]|\s+/g) || []
: raw.split(/(\s+)/);
words.forEach(function (token) {
if (/^\s+$/.test(token)) {
line.appendChild(document.createTextNode(token));
@@ -94,6 +97,15 @@
}
}
doc.addEventListener("meldwork:localechange", function () {
[headline, finalTitle].forEach(function (el) {
if (!el) return;
delete el.dataset.splitDone;
splitText(el);
el.classList.add("play");
});
});
/* =========================================================
2. DECRYPTED TEXT — scramble the eyebrow on entrance
========================================================= */
@@ -109,6 +121,7 @@
var duration = 900;
var start = performance.now();
function frame(now) {
if (el.dataset.text && el.dataset.text !== target) return;
var p = Math.min((now - start) / duration, 1);
var revealCount = Math.floor(target.length * p);
var out = "";
@@ -308,92 +321,103 @@
/* =========================================================
9. SCENARIO STEPPER — auto-cycle the timeline
========================================================= */
var scenario = doc.querySelector("[data-scenario]");
var scenario = doc.querySelector("[data-scenario]");
if (scenario) {
var steps = scenario.querySelectorAll(".t-step");
var total = steps.length;
var current = 0;
var isInView = false;
var hovered = false;
var focused = false;
var paused = false;
var autoTimer = null;
var autoDelay = 1600;
var scnMode = "auto"; // "auto" = step-by-step playback, "order" = full transcript
var scnMode = reduced ? "order" : "auto";
var railFill = scenario.querySelector("[data-rail]");
var railStatus = scenario.querySelector("[data-status]");
var roundLabel = scenario.querySelector("[data-round]");
var roundNames = [
"Round 1 · Propose",
"Round 1 · Critique",
"Round 2 · Propose",
"Round 2 · Reconcile",
"Adoption · Human Gate"
];
function setStep(idx, revealAll) {
current = (idx + total) % total;
steps.forEach(function (s, i) {
if (revealAll || i <= current) s.classList.add("is-visible");
else s.classList.remove("is-visible");
s.classList.toggle("is-active", i === current);
});
if (railFill) railFill.style.height = (((current + 1) / total) * 100) + "%";
if (railStatus) railStatus.textContent = (current + 1) + " / " + total;
if (roundLabel) roundLabel.textContent = roundNames[current];
}
function startAuto() {
stopAuto();
autoTimer = setInterval(function () {
setStep(current + 1);
}, autoDelay);
}
function stopAuto() { if (autoTimer) { clearInterval(autoTimer); autoTimer = null; } }
scenario.querySelector("[data-prev]").addEventListener("click", function () {
setStep(current - 1); startAuto();
});
scenario.querySelector("[data-next]").addEventListener("click", function () {
setStep(current + 1); startAuto();
});
scenario.addEventListener("mouseenter", stopAuto);
scenario.addEventListener("mouseleave", function () {
if (isInView && scnMode === "auto") startAuto();
});
// macOS segmented control — Auto vs Sequence
var pauseButton = scenario.querySelector("[data-pause]");
var segBtns = scenario.querySelectorAll(".seg-btn");
var roundNames = ["Round 1 · Propose", "Round 1 · Critique", "Round 2 · Review",
"Round 2 · Reconcile", "Adoption · Human Gate"];
function translate(text) { return win.meldworkI18n.t(text); }
function setStep(idx) {
current = Math.max(0, Math.min(total - 1, idx));
steps.forEach(function (step, i) {
step.classList.toggle("is-visible", i <= current);
step.classList.toggle("is-active", i === current);
if (i === current) step.setAttribute("aria-current", "step");
else step.removeAttribute("aria-current");
});
var node = steps[current].querySelector(".t-node");
if (railFill) railFill.style.height = (steps[current].offsetTop + node.offsetTop + node.offsetHeight / 2) + "px";
if (railStatus) railStatus.textContent = (current + 1) + " / " + total;
if (roundLabel) roundLabel.textContent = translate(roundNames[current]);
scenario.querySelector("[data-prev]").disabled = current === 0;
scenario.querySelector("[data-next]").disabled = current === total - 1;
pauseButton.textContent = translate(current === total - 1 ? "Replay" : paused ? "Resume" : "Pause");
pauseButton.setAttribute("aria-pressed", String(paused));
}
function stopAuto() {
clearTimeout(autoTimer);
autoTimer = null;
scenario.dataset.playback = "paused";
}
function scheduleNext() {
stopAuto();
if (!isInView || hovered || focused || paused || reduced || doc.hidden || scnMode !== "auto" || current === total - 1) return;
scenario.dataset.playback = "playing";
// Allow time to read each contribution. Language switches preserve the step.
var length = steps[current].querySelector("p").textContent.length;
var delay = doc.documentElement.lang === "zh-CN" ? Math.max(7000, length * 90) : Math.max(7000, length * 32);
autoTimer = setTimeout(function () { setStep(current + 1); scheduleNext(); }, delay);
}
function manualStep(idx) { paused = true; setStep(idx); scheduleNext(); }
scenario.querySelector("[data-prev]").addEventListener("click", function () { manualStep(current - 1); });
scenario.querySelector("[data-next]").addEventListener("click", function () { manualStep(current + 1); });
pauseButton.addEventListener("click", function () {
if (current === total - 1) { paused = false; setStep(0); }
else { paused = !paused; setStep(current); }
scheduleNext();
});
scenario.addEventListener("mouseenter", function () { hovered = true; stopAuto(); });
scenario.addEventListener("mouseleave", function () { hovered = false; scheduleNext(); });
scenario.addEventListener("focusin", function () { focused = true; stopAuto(); });
scenario.addEventListener("focusout", function (event) {
focused = scenario.contains(event.relatedTarget);
scheduleNext();
});
scenario.addEventListener("keydown", function (event) {
if (scnMode !== "auto" || !["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
event.preventDefault();
manualStep(event.key === "Home" ? 0 : event.key === "End" ? total - 1 : current + (event.key === "ArrowRight" ? 1 : -1));
});
function setMode(mode) {
scnMode = mode;
scenario.classList.toggle("is-order", mode === "order");
segBtns.forEach(function (b) {
b.classList.toggle("is-on", b.dataset.mode === mode);
segBtns.forEach(function (button) {
var selected = button.dataset.mode === mode;
button.classList.toggle("is-on", selected);
button.setAttribute("aria-pressed", String(selected));
});
if (mode === "order") {
stopAuto();
setStep(total - 1, true); // full transcript, rail at 100%, gate highlighted
} else {
setStep(0);
if (isInView && !reduced) startAuto();
}
setStep(current);
scheduleNext();
}
segBtns.forEach(function (b) {
b.addEventListener("click", function () { setMode(b.dataset.mode); });
segBtns.forEach(function (button) {
button.addEventListener("click", function () { setMode(button.dataset.mode); });
});
doc.addEventListener("visibilitychange", scheduleNext);
doc.addEventListener("meldwork:localechange", function () { setStep(current); scheduleNext(); });
if ("ResizeObserver" in win) {
new ResizeObserver(function () { setStep(current); }).observe(scenario.querySelector(".timeline"));
}
if ("IntersectionObserver" in win) {
var scnObs = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
isInView = entry.isIntersecting;
if (isInView) {
if (scnMode === "auto") {
setStep(0);
if (!reduced) startAuto();
} else {
setStep(total - 1, true);
}
} else stopAuto();
});
}, { threshold: 0.25 });
scnObs.observe(scenario);
} else setStep(0);
new IntersectionObserver(function (entries) {
isInView = entries[0].isIntersecting;
scheduleNext();
}, { threshold: 0.15 }).observe(scenario);
} else isInView = true;
setMode(scnMode);
}
/* =========================================================
@@ -868,6 +892,7 @@
var darkSrc = img.getAttribute("data-dark-src");
img.setAttribute("src", t === "light" ? lightSrc : darkSrc);
});
doc.dispatchEvent(new CustomEvent("meldwork:themechange"));
}
// Dark is the default theme — always start dark; the toggle
+160 -395
View File
@@ -7,6 +7,17 @@
ScrollProgress, ScrollSpy, CardSwap.
============================================================ */
/* Chinese pixel glyphs complement the existing Latin dot-matrix display face.
Fusion Pixel 12px proportional, 2026.10.16, OFL-1.1; licenses in assets/fonts/. */
@font-face {
font-family: "Meldwork Pixel CJK";
src: url("assets/fonts/fusion-pixel/fusion-pixel-12px-proportional-zh_hans.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+3000-303F, U+3400-9FFF, U+FF00-FFEF;
}
/* ================= DESIGN TOKENS ================= */
:root {
@@ -20,9 +31,12 @@
--ink-soft: #c0c8cc;
--muted: #8a9499;
--faint: #6a7378;
--coral: #ef5a45;
--coral-soft: rgba(239, 90, 69, 0.35);
--coral-glow: rgba(239, 90, 69, 0.18);
--coral: #d98568;
--coral-text: #e2906f;
--coral-rgb: 217, 133, 104;
--coral-highlight: #efa080;
--coral-soft: rgba(var(--coral-rgb), 0.24);
--coral-glow: rgba(var(--coral-rgb), 0.10);
--line: rgba(255, 255, 255, 0.06);
--line-strong: rgba(255, 255, 255, 0.12);
--nav-ink: #26343c;
@@ -31,8 +45,8 @@
--shadow-3: 0 30px 90px rgba(0, 0, 0, 0.6);
--glass-bg: rgba(12, 14, 17, 0.72);
--glass-border: rgba(255, 255, 255, 0.06);
--font-sans: "Inter", "Segoe UI", system-ui, sans-serif;
--font-display: "BubbledotICG-FinePos", "Geist Pixel Circle", monospace;
--font-sans: "Inter", "PingFang SC", "Microsoft YaHei", "Segoe UI", system-ui, sans-serif;
--font-display: "BubbledotICG-FinePos", "Meldwork Pixel CJK", "Geist Pixel Circle", monospace;
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--r-pill: 999px;
@@ -50,9 +64,12 @@
--ink-soft: #4a5054;
--muted: #7a8084;
--faint: #9aa0a4;
--coral: #e8553f;
--coral-soft: rgba(232, 85, 63, 0.3);
--coral-glow: rgba(232, 85, 63, 0.12);
--coral: #d45f52;
--coral-text: #ad4840;
--coral-rgb: 212, 95, 82;
--coral-highlight: #bd5047;
--coral-soft: rgba(var(--coral-rgb), 0.3);
--coral-glow: rgba(var(--coral-rgb), 0.12);
--line: rgba(0, 0, 0, 0.05);
--line-strong: rgba(0, 0, 0, 0.1);
--nav-ink: #1a1d1f;
@@ -68,6 +85,7 @@
html { scroll-behavior: smooth; background: var(--bg); }
body {
isolation: isolate;
margin: 0;
background: var(--bg);
color: var(--ink);
@@ -91,6 +109,37 @@ a:focus-visible, button:focus-visible {
body.menu-open { overflow: hidden; }
.ember-background {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
z-index: -1;
pointer-events: none;
opacity: 0.32;
}
.language-toggle {
display: grid;
place-items: center;
flex-shrink: 0;
min-width: 40px;
height: 40px;
padding: 0 9px;
border-radius: 50%;
background: var(--surface);
color: var(--ink-soft);
font-size: 12px;
font-weight: 500;
transition: color 0.2s, background 0.2s;
}
.language-toggle:hover { color: var(--coral-text); background: var(--surface-hover); }
html:lang(zh-CN) .headline { font-size: clamp(30px, 4.9vw, 64px); letter-spacing: 0.03em; line-height: 1.3; }
html:lang(zh-CN) .section-title, html:lang(zh-CN) .final-title { letter-spacing: 0.03em; line-height: 1.4; }
html:lang(zh-CN) .eyebrow { letter-spacing: 0.13em; }
html:lang(zh-CN) .subhead, html:lang(zh-CN) .section-sub { line-height: 1.9; }
html:lang(zh-CN) .t-bubble p { line-height: 1.9; }
/* ================= SCROLL PROGRESS ================= */
.scroll-progress {
position: fixed;
@@ -106,7 +155,7 @@ body.menu-open { overflow: hidden; }
display: block;
height: 100%;
width: 0;
background: linear-gradient(90deg, var(--coral), #ffb29b);
background: linear-gradient(90deg, var(--coral), var(--coral-highlight));
box-shadow: 0 0 12px var(--coral-soft);
transition: width 0.06s linear;
}
@@ -150,7 +199,7 @@ body.menu-open { overflow: hidden; }
position: absolute;
inset: -2px;
border-radius: 14px;
background: linear-gradient(135deg, rgba(239,90,69,0.5), transparent 40%, transparent 60%, rgba(239,90,69,0.3));
background: linear-gradient(135deg, rgba(var(--coral-rgb), 0.5), transparent 40%, transparent 60%, rgba(var(--coral-rgb), 0.3));
opacity: 0;
transition: opacity 0.3s ease;
z-index: -1;
@@ -220,8 +269,8 @@ body.menu-open { overflow: hidden; }
gap: 8px;
height: 42px;
padding: 0 18px;
background: rgba(239, 90, 69, 0.12);
color: var(--coral);
background: rgba(var(--coral-rgb), 0.12);
color: var(--coral-text);
border-radius: var(--r-pill);
font-size: 13px;
font-weight: 600;
@@ -232,17 +281,17 @@ body.menu-open { overflow: hidden; }
transition: background 0.25s ease, color 0.25s ease, transform 0.25s ease, box-shadow 0.3s ease;
}
.cta-dark:hover {
background: rgba(239, 90, 69, 0.2);
background: rgba(var(--coral-rgb), 0.2);
color: var(--ink);
transform: translateY(-1px);
box-shadow: 0 4px 20px rgba(239, 90, 69, 0.25);
box-shadow: 0 4px 20px rgba(var(--coral-rgb), 0.25);
}
.cta-status {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--coral);
box-shadow: 0 0 0 3px rgba(239, 90, 69, 0.22);
box-shadow: 0 0 0 3px rgba(var(--coral-rgb), 0.22);
animation: statusPulse 1.6s ease-in-out infinite;
}
@keyframes statusPulse {
@@ -406,6 +455,7 @@ body.menu-open { overflow: hidden; }
opacity: 0;
transition: opacity 1.1s ease;
}
.bg-still, .bg-video { filter: saturate(0.62); }
.bg-veil {
position: absolute;
@@ -450,7 +500,7 @@ body.menu-open { overflow: hidden; }
font-size: 12px;
font-weight: 600;
letter-spacing: 0.18em;
color: var(--coral);
color: var(--coral-text);
text-transform: uppercase;
font-variant-numeric: tabular-nums;
}
@@ -536,7 +586,7 @@ body.menu-open { overflow: hidden; }
bottom: -20%;
left: -75%;
width: 45%;
background: linear-gradient(105deg, transparent 0%, rgba(239, 90, 69, 0.12) 45%, rgba(239, 90, 69, 0.2) 50%, rgba(239, 90, 69, 0.12) 55%, transparent 100%);
background: linear-gradient(105deg, transparent 0%, rgba(var(--coral-rgb), 0.12) 45%, rgba(var(--coral-rgb), 0.2) 50%, rgba(var(--coral-rgb), 0.12) 55%, transparent 100%);
transform: skewX(-18deg);
animation: ctaShine 3.6s var(--ease-out) infinite;
pointer-events: none;
@@ -551,13 +601,13 @@ body.menu-open { overflow: hidden; }
box-shadow:
0 0 0 1px var(--line-strong),
0 0 30px rgba(255,255,255,0.3),
0 0 60px rgba(239,90,69,0.25);
0 0 60px rgba(var(--coral-rgb), 0.25);
}
[data-theme="light"] .cta-primary:hover {
box-shadow:
0 0 0 1px rgba(0,0,0,0.1),
0 6px 28px rgba(0,0,0,0.18),
0 0 50px rgba(239,90,69,0.2);
0 0 50px rgba(var(--coral-rgb), 0.2);
}
.spark {
@@ -571,7 +621,7 @@ body.menu-open { overflow: hidden; }
z-index: 999;
animation: sparkFly 0.55s var(--ease-out) forwards;
}
.spark:nth-child(odd) { background: #ffb29b; }
.spark:nth-child(odd) { background: var(--coral-highlight); }
@keyframes sparkFly {
from { transform: translate(0, 0) scale(1); opacity: 1; }
to { transform: translate(var(--tx), var(--ty)) scale(0.15); opacity: 0; }
@@ -639,7 +689,7 @@ body.menu-open { overflow: hidden; }
width: min(940px, calc(100% - 28px));
}
.stat { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 2px; min-width: 0; }
.stat-glyph { font-family: var(--font-display); color: var(--coral); font-size: clamp(20px, 2.6vw, 30px); line-height: 1.2; }
.stat-glyph { font-family: var(--font-display); color: var(--coral-text); font-size: clamp(20px, 2.6vw, 30px); line-height: 1.2; }
.stat-value { display: inline-flex; align-items: baseline; color: var(--ink); font-size: clamp(18px, 2.1vw, 26px); font-weight: 600; letter-spacing: -0.025em; font-variant-numeric: tabular-nums; }
.stat-suffix { color: var(--muted); font-size: 0.62em; font-weight: 500; margin-left: 2px; }
.stat-label { color: var(--muted); font-size: clamp(10.5px, 1.1vw, 12.5px); }
@@ -728,7 +778,7 @@ body.menu-open { overflow: hidden; }
font-size: 15px;
line-height: 1.65;
}
.section-sub a { color: var(--coral); }
.section-sub a { color: var(--coral-text); }
.section-sub a:hover { text-decoration: underline; }
.section-sub.centered,
@@ -858,7 +908,7 @@ body.menu-open { overflow: hidden; }
/* GradientText port — animated gradient sweep on live text */
.gradient-text {
background: linear-gradient(90deg, var(--coral) 25%, #ff9d7e 45%, var(--coral) 65%);
background: linear-gradient(90deg, var(--coral) 25%, var(--coral-highlight) 45%, var(--coral) 65%);
background-size: 220% 100%;
-webkit-background-clip: text;
background-clip: text;
@@ -877,7 +927,7 @@ body.menu-open { overflow: hidden; }
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--coral);
color: var(--coral-text);
}
.geo-kicker::before {
content: "";
@@ -894,7 +944,7 @@ body.menu-open { overflow: hidden; }
color: var(--ink);
margin: 4px 0 2px;
}
.geo-num em { font-style: normal; color: var(--coral); }
.geo-num em { font-style: normal; color: var(--coral-text); }
.geo-tile--wide { grid-column: span 2; }
.geo-fact-label { font-size: 11px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); }
.geo-fact-value { margin: 0; font-size: 13.5px; line-height: 1.65; color: var(--ink-soft); }
@@ -910,7 +960,7 @@ body.menu-open { overflow: hidden; }
transition: box-shadow 0.3s ease, color 0.3s ease, transform 0.3s var(--ease-out);
}
.geo-chip:hover { box-shadow: inset 0 0 0 1px var(--coral-soft); color: var(--ink); transform: translateY(-1px); }
.geo-chip i { font-style: normal; color: var(--coral); margin-right: 3px; }
.geo-chip i { font-style: normal; color: var(--coral-text); margin-right: 3px; }
/* ---- Workflow steps ---- */
.steps {
@@ -949,7 +999,7 @@ body.menu-open { overflow: hidden; }
width: 52px;
height: 52px;
border-radius: 50%;
background: linear-gradient(135deg, var(--coral), #ff8a6e);
background: linear-gradient(135deg, var(--coral), var(--coral-highlight));
color: #fff;
font-family: var(--font-display);
font-size: 17px;
@@ -987,7 +1037,7 @@ body.menu-open { overflow: hidden; }
content: "";
position: absolute;
inset: 0;
background: radial-gradient(360px circle at var(--sx, 50%) var(--sy, 50%), rgba(239,90,69,0.12), transparent 65%);
background: radial-gradient(360px circle at var(--sx, 50%) var(--sy, 50%), rgba(var(--coral-rgb), 0.12), transparent 65%);
opacity: 0;
transition: opacity 0.35s ease;
pointer-events: none;
@@ -1010,7 +1060,7 @@ body.menu-open { overflow: hidden; }
from var(--beam),
transparent 0turn,
transparent 0.6turn,
rgba(239, 90, 69, 0.55) 0.8turn,
rgba(var(--coral-rgb), 0.55) 0.8turn,
#ffb49e 0.88turn,
transparent 1turn
);
@@ -1038,7 +1088,7 @@ body.menu-open { overflow: hidden; }
transition: box-shadow 0.35s ease, transform 0.35s var(--ease-out);
}
.mode-card:hover .mode-glyph-wrap { box-shadow: inset 0 0 0 1px var(--coral-soft), 0 8px 20px var(--coral-glow); transform: translateY(-2px); }
.mode-glyph { font-family: var(--font-display); font-size: 26px; color: var(--coral); }
.mode-glyph { font-family: var(--font-display); font-size: 26px; color: var(--coral-text); }
.mode-card h3 { margin: 16px 0 10px; font-size: 19px; font-weight: 600; letter-spacing: -0.01em; }
.mode-card p { margin: 0; color: var(--ink-soft); font-size: 14.5px; line-height: 1.65; }
.mode-best {
@@ -1078,7 +1128,7 @@ body.menu-open { overflow: hidden; }
content: "";
position: absolute;
inset: 0;
background: radial-gradient(360px circle at var(--sx, 50%) var(--sy, 50%), rgba(239,90,69,0.14), transparent 65%);
background: radial-gradient(360px circle at var(--sx, 50%) var(--sy, 50%), rgba(var(--coral-rgb), 0.14), transparent 65%);
opacity: 0;
transition: opacity 0.35s ease;
pointer-events: none;
@@ -1093,8 +1143,8 @@ body.menu-open { overflow: hidden; }
padding: 1px;
background: conic-gradient(
at var(--sx, 50%) var(--sy, 50%),
rgba(239, 90, 69, 0.55),
rgba(239, 90, 69, 0.12) 18%,
rgba(var(--coral-rgb), 0.55),
rgba(var(--coral-rgb), 0.12) 18%,
transparent 38%
);
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
@@ -1147,8 +1197,8 @@ body.menu-open { overflow: hidden; }
animation: gatePulse 1.8s ease-in-out infinite;
}
@keyframes gatePulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(239,90,69,0.5); }
50% { box-shadow: 0 0 0 6px rgba(239,90,69,0); }
0%, 100% { box-shadow: 0 0 0 0 rgba(var(--coral-rgb), 0.5); }
50% { box-shadow: 0 0 0 6px rgba(var(--coral-rgb), 0); }
}
.cap-snap {
position: relative;
@@ -1160,7 +1210,7 @@ body.menu-open { overflow: hidden; }
content: "";
position: absolute;
inset: 4px;
border: 1px dashed rgba(239,90,69,0.5);
border: 1px dashed rgba(var(--coral-rgb), 0.5);
border-radius: 2px;
}
.cap-trail {
@@ -1213,369 +1263,79 @@ body.menu-open { overflow: hidden; }
letter-spacing: 0.02em;
}
/* ---- Scenario (redesigned, no borders) ---- */
/* Scenario transcript: every contribution remains readable during playback. */
.scenario-stage {
margin-top: clamp(40px, 6vh, 60px);
margin-top: clamp(32px, 5vh, 56px);
position: relative;
padding: 0;
background: var(--surface);
border-radius: 18px;
overflow: hidden;
box-shadow: var(--shadow-3);
box-shadow: var(--shadow-2), inset 0 1px 0 var(--line-strong);
}
.scenario-stage::before {
content: "";
position: absolute;
top: -40%;
right: -20%;
width: 60%;
height: 80%;
background: radial-gradient(circle, rgba(239,90,69,0.06) 0%, transparent 70%);
pointer-events: none;
}
/* macOS window chrome */
.mac-titlebar {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
padding: 13px 20px;
background: linear-gradient(180deg, var(--surface-raised), var(--surface));
border-bottom: 1px solid var(--line);
position: relative;
z-index: 2;
}
.mac-lights { display: flex; gap: 8px; flex-shrink: 0; }
.mac-lights i {
width: 12px;
height: 12px;
border-radius: 50%;
transition: filter 0.25s ease, transform 0.25s ease;
}
.light-r { background: #ff5f57; box-shadow: inset 0 0 2px rgba(0,0,0,0.18); }
.light-y { background: #febc2e; box-shadow: inset 0 0 2px rgba(0,0,0,0.18); }
.light-g { background: #28c840; box-shadow: inset 0 0 2px rgba(0,0,0,0.18); }
.scenario-stage:hover .mac-lights i { filter: saturate(1.25) brightness(1.08); }
.titlebar-right {
margin-left: auto;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
/* macOS segmented control */
.seg-control {
display: inline-flex;
padding: 3px;
border-radius: 9px;
background: var(--bg-deep);
box-shadow: inset 0 0 0 1px var(--line);
}
.seg-btn {
padding: 5px 14px;
border-radius: 7px;
font-size: 12px;
font-weight: 600;
color: var(--muted);
letter-spacing: 0.01em;
transition: background 0.25s ease, color 0.25s ease, box-shadow 0.25s ease;
}
.seg-btn:hover { color: var(--ink-soft); }
.seg-btn.is-on {
padding: 16px 22px;
background: var(--surface-raised);
color: var(--ink);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.28), inset 0 0 0 1px var(--line-strong);
border-bottom: 1px solid var(--line);
}
[data-theme="light"] .seg-btn.is-on { box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1), inset 0 0 0 1px var(--line-strong); }
.scenario-meta {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.meta-pill {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 5px 12px;
background: rgba(239,90,69,0.1);
border-radius: var(--r-pill);
color: var(--coral);
font-size: 11.5px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.meta-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--coral);
animation: statusPulse 1.4s ease-in-out infinite;
}
.meta-name { font-size: 14px; font-weight: 600; color: var(--ink); white-space: nowrap; }
.meta-round {
font-family: var(--font-display);
font-size: 13px;
color: var(--coral);
padding: 5px 12px;
background: rgba(239,90,69,0.06);
border-radius: var(--r-pill);
transition: opacity 0.3s ease;
white-space: nowrap;
}
.scenario-body { position: relative; z-index: 1; padding: clamp(20px, 3vw, 32px); }
/* Sequence mode — full transcript, auto playback disabled */
.scenario-stage.is-order .t-step { opacity: 1; transform: translateX(0); }
.scenario-stage.is-order .t-node { background: var(--surface-hover); }
.mac-lights { display: flex; gap: 7px; flex-shrink: 0; }
.mac-lights i { width: 10px; height: 10px; border-radius: 50%; }
.light-r { background: #ff5f57; }
.light-y { background: #febc2e; }
.light-g { background: #28c840; }
.scenario-meta { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; min-width: 0; }
.meta-pill { display: inline-flex; align-items: center; gap: 7px; color: var(--coral-text); font-size: 11px; letter-spacing: 0.04em; font-weight: 600; }
.meta-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--coral); }
.meta-name { font-size: 13px; font-weight: 600; color: var(--ink); }
.titlebar-right { margin-left: auto; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.meta-round { font-family: var(--font-display); font-size: 12px; color: var(--muted); white-space: nowrap; }
.seg-control { display: inline-flex; padding: 3px; border-radius: 8px; background: var(--bg-deep); }
.seg-btn { padding: 6px 12px; border-radius: 5px; font-size: 11px; font-weight: 500; color: var(--muted); transition: background 0.2s, color 0.2s; }
.seg-btn:hover { color: var(--ink); }
.seg-btn.is-on { background: var(--surface-hover); color: var(--ink); box-shadow: 0 1px 3px rgb(0 0 0 / 0.12); }
.scenario-body { padding: clamp(18px, 3vw, 32px); }
.timeline-track { position: relative; padding-left: 24px; }
.timeline-rail { position: absolute; left: 5px; top: 0; bottom: 0; width: 1px; }
.rail-line { position: absolute; inset: 20px 0; background: var(--line-strong); }
.rail-progress { position: absolute; top: 0; left: 0; width: 1px; height: 0; background: var(--coral); transform-origin: top; transition: height 0.65s var(--ease-out); }
.timeline { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.t-step { display: flex; align-items: flex-start; gap: 16px; position: relative; opacity: 1; min-width: 0; }
.t-node { position: absolute; left: -23px; top: 23px; width: 9px; height: 9px; border-radius: 50%; background: var(--surface-hover); box-shadow: 0 0 0 4px var(--surface); transition: background 0.3s, box-shadow 0.3s; z-index: 1; }
.t-step.is-visible .t-node { background: var(--muted); }
.t-step.is-active .t-node { background: var(--coral); box-shadow: 0 0 0 4px var(--surface), 0 0 0 5px var(--coral-soft); }
.t-time { flex-shrink: 0; width: 80px; padding-top: 20px; font-family: var(--font-display); font-size: 11px; color: var(--muted); line-height: 1.4; transition: color 0.3s; }
.t-step.is-active .t-time { color: var(--coral-text); }
.t-bubble { --agent-color: var(--coral-text); position: relative; flex: 1; min-width: 0; padding: 20px 24px; border-radius: 12px; background: var(--bg-deep); transition: background 0.4s, box-shadow 0.4s, transform 0.4s var(--ease-out); }
.t-bubble:hover { background: var(--surface-raised); }
.t-step.is-active .t-bubble { background: var(--surface-raised); box-shadow: 0 6px 18px rgb(0 0 0 / 0.1); transform: translateX(3px); animation: contributionEnter 0.55s var(--ease-out); }
@keyframes contributionEnter { from { transform: translateX(0) translateY(4px); } to { transform: translateX(3px) translateY(0); } }
.t-bubble header { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; }
.t-bubble header img { width: 30px; height: 30px; object-fit: contain; padding: 5px; border-radius: 8px; background: #ffffff; }
.t-bubble header strong { font-size: 13px; font-weight: 600; color: var(--ink); }
.t-bubble p { margin: 0; color: var(--ink-soft); font-size: 14px; line-height: 1.75; overflow-wrap: anywhere; }
.t-bubble p strong { color: var(--ink); font-weight: 600; }
.t-role { margin-left: auto; color: var(--agent-color); font-size: 10px; font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase; }
.t-bubble-codex { --agent-color: #91a4d6; }
.t-bubble-hermes { --agent-color: #c99c7b; }
.t-bubble-ocr { --agent-color: #86bba6; }
.you-dot, .gate-dot { width: 22px; height: 22px; border-radius: 5px; display: grid; place-items: center; flex-shrink: 0; background: var(--coral-glow); color: var(--coral-text); font-size: 11px; font-weight: 600; }
.gate-dot::after { content: "✓"; }
.scenario-controls { display: flex; align-items: center; justify-content: center; gap: 12px; margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line); }
.rail-status { font-family: var(--font-display); font-size: 12px; color: var(--muted); min-width: 50px; text-align: center; }
.rail-btn { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 6px; color: var(--ink-soft); background: var(--bg-deep); font-size: 22px; transition: color 0.2s, background 0.2s; }
.rail-btn:hover, .playback-toggle:hover { color: var(--coral-text); background: var(--surface-hover); }
.rail-btn:disabled { opacity: 0.35; cursor: default; color: var(--muted); background: var(--bg-deep); }
.playback-toggle { padding: 8px 12px; border-radius: 6px; min-width: 92px; font-size: 12px; color: var(--ink-soft); transition: color 0.2s, background 0.2s; }
.scenario-stage.is-order .scenario-controls { display: none; }
.timeline-track {
position: relative;
padding-left: 28px;
}
.timeline-rail {
position: absolute;
left: 7px;
top: 8px;
bottom: 8px;
width: 2px;
display: flex;
flex-direction: column;
}
.rail-line {
position: absolute;
inset: 0;
background: var(--line);
border-radius: 2px;
}
.rail-progress {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 0;
background: linear-gradient(180deg, var(--coral), #ffb29b);
border-radius: 2px;
transition: height 0.35s var(--ease-out);
box-shadow: 0 0 12px var(--coral-glow);
}
.timeline {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 14px;
position: relative;
min-height: 200px;
}
.t-step {
display: flex;
align-items: flex-start;
gap: 16px;
opacity: 0;
transform: translateX(-16px);
transition: opacity 0.35s var(--ease-out), transform 0.35s var(--ease-out);
position: relative;
}
.t-step.is-visible { opacity: 0.42; transform: translateX(0); }
.t-step.is-active { opacity: 1; transform: translateX(0); }
.t-node {
position: absolute;
left: -28px;
top: 18px;
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--bg);
z-index: 2;
transition: box-shadow 0.35s ease, transform 0.35s var(--ease-out), background 0.35s ease;
}
.t-step.is-visible .t-node { background: var(--surface-hover); }
.t-step.is-active .t-node {
background: var(--coral);
box-shadow: 0 0 0 4px rgba(239,90,69,0.15), 0 0 16px var(--coral-glow);
transform: scale(1.15);
}
.t-step.is-active .t-node::after {
content: "";
position: absolute;
inset: -4px;
border: 1.5px solid var(--coral);
border-radius: 50%;
animation: nodePulseRing 1.8s ease-in-out infinite;
}
@keyframes nodePulseRing {
0%, 100% { transform: scale(1); opacity: 0.5; }
50% { transform: scale(1.4); opacity: 0; }
}
.t-time {
flex-shrink: 0;
width: 72px;
font-family: var(--font-display);
font-size: 11.5px;
color: var(--muted);
padding-top: 14px;
letter-spacing: 0.02em;
transition: color 0.35s ease;
}
.t-step.is-active .t-time { color: var(--coral); }
.t-bubble {
flex: 1;
background: var(--bg-deep);
border-radius: 14px;
padding: 16px 20px;
position: relative;
transition: box-shadow 0.28s ease, transform 0.28s var(--ease-out), opacity 0.25s ease;
overflow: hidden;
}
.t-step.is-active .t-bubble {
box-shadow:
0 0 0 1px rgba(239, 90, 69, 0.08),
0 8px 28px rgba(0, 0, 0, 0.2),
0 0 40px rgba(239, 90, 69, 0.03);
transform: translateY(-1px);
}
.t-step.is-active .t-bubble::after {
content: "";
position: absolute;
top: -50%;
left: -20%;
width: 40%;
height: 200%;
background: linear-gradient(105deg, transparent 40%, rgba(239,90,69,0.06) 50%, transparent 60%);
animation: bubbleSweep 2s ease-in-out infinite;
pointer-events: none;
}
@keyframes bubbleSweep {
0%, 100% { transform: translateX(-30%); opacity: 0; }
50% { transform: translateX(250%); opacity: 1; }
}
.t-bubble header {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--muted);
margin-bottom: 8px;
}
.t-bubble header img {
width: 18px;
height: 18px;
object-fit: contain;
border-radius: 4px;
background: var(--surface);
padding: 1px;
transition: box-shadow 0.35s ease;
}
.t-step.is-active .t-bubble header img {
box-shadow: 0 0 0 2px rgba(239, 90, 69, 0.3), 0 0 12px var(--coral-glow);
}
.t-bubble header strong { color: var(--ink); font-weight: 600; font-size: 13.5px; }
.t-bubble p { margin: 0; color: var(--ink-soft); font-size: 13.5px; line-height: 1.6; }
.t-bubble strong { color: var(--ink); font-weight: 600; }
.you-dot, .gate-dot {
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--coral);
color: #fff;
font-size: 9px;
font-weight: 700;
display: grid;
place-items: center;
flex-shrink: 0;
}
.you-dot::after { content: "Y"; color: #fff; }
.gate-dot { background: var(--coral); box-shadow: 0 0 10px var(--coral-glow); position: relative; }
.gate-dot::after { content: ""; position: absolute; inset: -3px; border: 1px solid var(--coral); border-radius: 50%; animation: gatePulseRing 1.6s ease-in-out infinite; }
@keyframes gatePulseRing {
0%, 100% { transform: scale(1); opacity: 0.6; }
50% { transform: scale(1.15); opacity: 0; }
}
/* Per-agent accent system: tinted bubble wash + matching role badge */
.t-bubble-codex {
background: linear-gradient(135deg, rgba(108, 140, 255, 0.07), var(--bg-deep) 60%);
box-shadow: inset 3px 0 0 rgba(108, 140, 255, 0.55);
}
.t-bubble-hermes {
background: linear-gradient(135deg, rgba(255, 157, 82, 0.07), var(--bg-deep) 60%);
box-shadow: inset 3px 0 0 rgba(255, 157, 82, 0.55);
}
.t-bubble-ocr {
background: linear-gradient(135deg, rgba(106, 212, 139, 0.07), var(--bg-deep) 60%);
box-shadow: inset 3px 0 0 rgba(106, 212, 139, 0.55);
}
.t-bubble-you {
background: linear-gradient(135deg, rgba(239, 90, 69, 0.06), var(--bg-deep) 60%);
box-shadow: inset 3px 0 0 rgba(239, 90, 69, 0.45);
}
.t-bubble-codex .t-role { color: #93a9ff; background: rgba(108, 140, 255, 0.1); }
.t-bubble-hermes .t-role { color: #ffb27e; background: rgba(255, 157, 82, 0.1); }
.t-bubble-ocr .t-role { color: #8ce0ac; background: rgba(106, 212, 139, 0.1); }
.t-bubble-you .t-role, .t-bubble-gate .t-role { color: var(--coral); background: rgba(239, 90, 69, 0.1); }
.t-role {
margin-left: auto;
padding: 3px 9px;
border-radius: 6px;
font-size: 10.5px;
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 600;
white-space: nowrap;
}
.t-step.is-active .t-role { filter: brightness(1.12); }
.t-bubble-gate {
background: linear-gradient(135deg, rgba(239,90,69,0.08), rgba(239,90,69,0.02));
box-shadow: inset 3px 0 0 var(--coral), 0 0 24px rgba(239,90,69,0.06);
}
.t-step.is-active .t-bubble-gate {
box-shadow:
inset 3px 0 0 var(--coral),
0 8px 28px rgba(0, 0, 0, 0.2),
0 0 50px rgba(239, 90, 69, 0.08);
}
.scenario-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
margin-top: 24px;
font-family: var(--font-display);
font-size: 12px;
color: var(--muted);
}
.rail-btn {
width: 34px;
height: 34px;
border-radius: 50%;
background: var(--bg-deep);
color: var(--ink-soft);
font-size: 18px;
line-height: 1;
display: grid;
place-items: center;
transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.rail-btn:hover {
background: rgba(239,90,69,0.1);
color: var(--coral);
transform: scale(1.06);
}
.rail-btn:active { transform: scale(0.95); }
.scenario-stage.is-order .t-bubble { transform: none; animation: none; box-shadow: none; background: var(--bg-deep); }
.scenario-stage.is-order .t-node { background: var(--muted); box-shadow: 0 0 0 4px var(--surface); }
.scenario-stage.is-order .rail-progress { height: 100% !important; opacity: 0.3; }
[data-theme="light"] .t-bubble-codex { --agent-color: #53658e; }
[data-theme="light"] .t-bubble-hermes { --agent-color: #936743; }
[data-theme="light"] .t-bubble-ocr { --agent-color: #427b62; }
/* ---- CardSwap screenshots (React Bits port) ---- */
.card-swap-stage {
@@ -1611,7 +1371,7 @@ body.menu-open { overflow: hidden; }
font-size: 13.5px;
line-height: 1.55;
}
.cs-info-item.is-active h3 { color: var(--coral); }
.cs-info-item.is-active h3 { color: var(--coral-text); }
/* React Bits CardSwap — layout box (visual bounds only) */
.card-swap-3d {
@@ -1702,7 +1462,7 @@ body.menu-open { overflow: hidden; }
}
.why-list li:hover { transform: translateX(6px); box-shadow: var(--shadow-1); }
.why-list h3 { margin: 0 0 8px; font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
.why-list h3::before { content: "● "; color: var(--coral); font-size: 9px; vertical-align: 2px; }
.why-list h3::before { content: "● "; color: var(--coral-text); font-size: 9px; vertical-align: 2px; }
.why-list p { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.65; }
/* ---- Comparison table (no borders, seamless) ---- */
@@ -1723,7 +1483,7 @@ body.menu-open { overflow: hidden; }
transition: background 0.25s ease;
}
.cmp-row + .cmp-row { border-top: 1px solid var(--line); }
.cmp-row:not(.cmp-head):hover { background: rgba(239,90,69,0.04); }
.cmp-row:not(.cmp-head):hover { background: rgba(var(--coral-rgb), 0.04); }
.cmp-row:not(.cmp-head)::before {
content: "";
position: absolute;
@@ -1791,7 +1551,7 @@ body.menu-open { overflow: hidden; }
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.bnd-no { background: rgba(239,90,69,0.12); }
.bnd-no { background: rgba(var(--coral-rgb), 0.12); }
.bnd-no::before, .bnd-no::after {
content: "";
position: absolute;
@@ -1862,7 +1622,7 @@ body.menu-open { overflow: hidden; }
text-align: left;
transition: color 0.2s ease;
}
.acc-trigger:hover { color: var(--coral); }
.acc-trigger:hover { color: var(--coral-text); }
.acc-icon {
position: relative;
flex-shrink: 0;
@@ -1895,7 +1655,7 @@ body.menu-open { overflow: hidden; }
font-size: 14.5px;
line-height: 1.65;
}
.acc-panel p a { color: var(--coral); }
.acc-panel p a { color: var(--coral-text); }
.acc-panel p a:hover { text-decoration: underline; }
/* ---- ScrollVelocity marquee (no borders) ---- */
@@ -1914,7 +1674,7 @@ body.menu-open { overflow: hidden; }
-webkit-text-stroke: 1px var(--line-strong);
will-change: transform;
}
.velocity-track i { font-style: normal; color: var(--coral); -webkit-text-stroke: 0; }
.velocity-track i { font-style: normal; color: var(--coral-text); -webkit-text-stroke: 0; }
/* ---- Final CTA ---- */
.cta-final {
@@ -1957,7 +1717,7 @@ body.menu-open { overflow: hidden; }
}
.foot-wordmark { height: 22px; width: auto; opacity: 0.85; }
.foot-link { color: var(--muted); }
.foot-link:hover { color: var(--coral); }
.foot-link:hover { color: var(--coral-text); }
/* ================= RESPONSIVE ================= */
@@ -2000,7 +1760,12 @@ body.menu-open { overflow: hidden; }
.t-step { flex-direction: column; gap: 4px; }
.t-time { width: auto; padding-top: 0; }
.scenario-controls { font-size: 11px; }
.t-role { display: none; }
.t-role { font-size: 10px; letter-spacing: 0.02em; }
.t-step { gap: 8px; }
.t-node { top: 5px; }
.t-bubble { width: 100%; padding: 16px; }
.scenario-meta { gap: 8px; }
.meta-name { font-size: 12px; }
.meta-round { font-size: 11.5px; }
.mac-titlebar { gap: 10px; padding: 12px 16px; }
.titlebar-right { margin-left: 0; width: 100%; justify-content: space-between; }
@@ -2033,4 +1798,4 @@ body.menu-open { overflow: hidden; }
.cs-card { transition: none !important; }
.t-bubble::after, .logo-badge::before { display: none; }
.logo-badge, .nav-pill, .cta-dark, .theme-toggle { opacity: 1 !important; transform: none !important; }
}
}
+258
View File
@@ -0,0 +1,258 @@
const { test } = require('node:test')
const assert = require('node:assert/strict')
const { readFileSync } = require('node:fs')
const path = require('node:path')
const { JSDOM } = require('../../frontend/node_modules/jsdom')
const root = path.resolve(__dirname, '..')
const html = readFileSync(path.join(root, 'index.html'), 'utf8')
const scripts = ['i18n.js', 'glyph-embers.js', 'main.js'].map(file => readFileSync(path.join(root, file), 'utf8'))
function page({ language = 'en', saved, reduced = false, blockedStorage = false } = {}) {
const dom = new JSDOM(html, { url: 'https://meldwork.test/', runScripts: 'outside-only', pretendToBeVisual: true })
const { window } = dom
const observers = []
const timers = new Map()
const frames = new Map()
const mediaQueries = []
let frameId = 0
let emberDraws = 0
let glyphSamples = []
let timerId = 0
Object.defineProperty(window.navigator, 'language', { value: language })
if (saved) window.localStorage.setItem('meldwork-landing-locale', saved)
if (blockedStorage) Object.defineProperty(window, 'localStorage', { get() { throw new Error('unavailable') } })
window.matchMedia = () => {
const query = { matches: reduced, addEventListener(name, callback) { this.onChange = callback } }
mediaQueries.push(query)
return query
}
window.requestAnimationFrame = callback => { frames.set(++frameId, callback); return frameId }
window.cancelAnimationFrame = id => frames.delete(id)
window.setTimeout = (callback, delay) => { timers.set(++timerId, { callback, delay }); return timerId }
window.clearTimeout = id => timers.delete(id)
window.setInterval = window.setTimeout
window.clearInterval = window.clearTimeout
window.HTMLCanvasElement.prototype.getContext = function () {
const canvas = this
return { scale() {}, setTransform() {}, drawImage() {}, fillRect() {},
createRadialGradient() { return { addColorStop() {} } },
clearRect() { if (!canvas.id) glyphSamples = [] }, beginPath() {}, arc() {}, fill() {},
fillText(glyph, x, y) {
if (canvas.id !== 'bgCanvas') emberDraws++
if (!canvas.id) glyphSamples.push({ glyph, x, y, color: this.fillStyle })
} }
}
window.HTMLMediaElement.prototype.pause = () => {}
window.IntersectionObserver = class {
constructor(callback) { this.callback = callback; this.targets = []; observers.push(this) }
observe(target) { this.targets.push(target) }
unobserve() {}
disconnect() {}
}
window.ResizeObserver = class { observe() {} }
scripts.forEach(source => window.eval(source))
const doc = window.document
const scenario = doc.querySelector('[data-scenario]')
function inView(value) {
const observer = observers.findLast(entry => entry.targets.includes(scenario))
observer.callback([{ target: scenario, isIntersecting: value }])
}
function tick() {
const timer = [...timers.entries()].find(([, value]) => value.delay >= 7000)
assert.ok(timer, 'a reading-paced playback timer should exist')
timers.delete(timer[0])
timer[1].callback()
}
function runFrame(now) {
const pending = [...frames.values()]
frames.clear()
pending.forEach(callback => callback(now))
}
function setMotion(value) {
mediaQueries.forEach(query => { query.matches = value; query.onChange?.() })
}
return { dom, window, doc, scenario, inView, tick, timers, runFrame, setMotion,
emberDraws: () => emberDraws, glyphSamples: () => glyphSamples }
}
test('browser language, saved preference, repeated switching and metadata stay synchronized', () => {
const p = page({ language: 'zh-CN' })
try {
assert.equal(p.doc.documentElement.lang, 'zh-CN')
assert.match(p.doc.querySelector('h1').textContent, /提出目标/)
assert.match(p.doc.title, /协作完成/)
assert.equal(p.doc.querySelector('[data-pause]').textContent, '暂停播放')
const toggle = p.doc.getElementById('languageToggle')
toggle.click()
assert.equal(p.doc.documentElement.lang, 'en')
assert.equal(p.window.localStorage.getItem('meldwork-landing-locale'), 'en')
assert.match(p.doc.querySelector('h1').textContent, /Set the Goal/)
toggle.click()
toggle.click()
toggle.click()
assert.equal(p.doc.querySelectorAll('#headline .hl-line').length, 2)
assert.match(p.doc.querySelector('a[href*="README.zh-CN.md"]').textContent, /中文/)
assert.equal(p.doc.querySelector('[data-next]').getAttribute('aria-label'), '下一步')
const faq = JSON.parse(p.doc.querySelector('script[type="application/ld+json"]').textContent)['@graph'][1]
assert.ok(faq.mainEntity.some(item => item.name === '我还需要介入吗?'))
const savedPage = page({ language: 'zh', saved: 'en' })
assert.equal(savedPage.doc.documentElement.lang, 'en')
savedPage.dom.window.close()
} finally { p.dom.window.close() }
})
test('unavailable storage still permits language switching', () => {
const p = page({ blockedStorage: true, language: 'zh' })
try {
p.doc.getElementById('languageToggle').click()
assert.equal(p.doc.documentElement.lang, 'en')
} finally { p.dom.window.close() }
})
test('full-page copy has Chinese alternatives while links and emphasis survive switching', () => {
const p = page()
try {
for (const el of p.doc.querySelectorAll('h1 .hl-line,h2,h3,p,.cta-ghost,.nav-link span,.m-link span,.stat-label,.meta-pill,.meta-name,.seg-btn,.t-time,.t-role,.cs-caption,.cmp-row > div,.bnd-col li,.acc-trigger > span:first-child,.foot-meta > span')) {
assert.ok(el.hasAttribute('data-zh') || el.closest('[data-zh]'), el.outerHTML)
}
const linkCount = p.doc.querySelectorAll('a').length
p.window.meldworkI18n.setLocale('zh')
assert.equal(p.doc.querySelectorAll('a').length, linkCount)
assert.ok(p.doc.querySelector('.t-bubble p strong'))
p.doc.querySelector('.acc-trigger').click()
assert.equal(p.doc.querySelector('.acc-trigger').getAttribute('aria-expanded'), 'true')
} finally { p.dom.window.close() }
})
test('manual playback pauses and language switches preserve the current contribution', () => {
const p = page()
try {
p.inView(true)
assert.equal(p.scenario.dataset.playback, 'playing')
p.tick()
assert.equal(p.scenario.querySelector('[aria-current="step"]').dataset.step, '1')
p.doc.querySelector('[data-next]').click()
assert.equal(p.scenario.dataset.playback, 'paused')
p.window.meldworkI18n.setLocale('zh')
assert.equal(p.scenario.querySelector('[aria-current="step"]').dataset.step, '2')
assert.equal(p.doc.querySelector('[data-round]').textContent, '第 2 轮 · 评审')
assert.equal(p.doc.querySelector('[data-pause]').textContent, '继续播放')
p.doc.querySelector('[data-pause]').click()
assert.equal(p.scenario.dataset.playback, 'playing')
p.inView(false)
assert.equal(p.scenario.dataset.playback, 'paused')
} finally { p.dom.window.close() }
})
test('autoplay stops at the last contribution and replay starts from the beginning', () => {
const p = page()
try {
p.inView(true)
for (let i = 0; i < 4; i++) p.tick()
assert.equal(p.doc.querySelector('[data-status]').textContent, '5 / 5')
assert.equal(p.scenario.dataset.playback, 'paused')
assert.equal(p.doc.querySelector('[data-pause]').textContent, 'Replay')
p.doc.querySelector('[data-pause]').click()
assert.equal(p.doc.querySelector('[data-status]').textContent, '1 / 5')
} finally { p.dom.window.close() }
})
test('hover and document visibility suspend playback without losing progress', () => {
const p = page()
try {
p.inView(true)
p.tick()
p.scenario.dispatchEvent(new p.window.MouseEvent('mouseenter'))
assert.equal(p.scenario.dataset.playback, 'paused')
p.scenario.dispatchEvent(new p.window.MouseEvent('mouseleave'))
assert.equal(p.scenario.dataset.playback, 'playing')
Object.defineProperty(p.doc, 'hidden', { value: true, configurable: true })
p.doc.dispatchEvent(new p.window.Event('visibilitychange'))
assert.equal(p.scenario.dataset.playback, 'paused')
Object.defineProperty(p.doc, 'hidden', { value: false, configurable: true })
p.doc.dispatchEvent(new p.window.Event('visibilitychange'))
assert.equal(p.scenario.dataset.playback, 'playing')
assert.equal(p.doc.querySelector('[data-status]').textContent, '2 / 5')
} finally { p.dom.window.close() }
})
test('keyboard navigation, focus pause and transcript mode keep playback under user control', () => {
const p = page()
try {
p.inView(true)
p.doc.querySelector('[data-next]').focus()
assert.equal(p.scenario.dataset.playback, 'paused')
p.scenario.dispatchEvent(new p.window.KeyboardEvent('keydown', { key: 'End', bubbles: true }))
assert.equal(p.doc.querySelector('[data-status]').textContent, '5 / 5')
p.doc.querySelector('[data-mode="order"]').click()
assert.ok(p.scenario.classList.contains('is-order'))
assert.equal(p.doc.querySelector('[data-mode="order"]').getAttribute('aria-pressed'), 'true')
assert.equal(p.scenario.dataset.playback, 'paused')
} finally { p.dom.window.close() }
})
test('reduced motion defaults to a readable transcript without automatic playback', () => {
const p = page({ reduced: true })
try {
p.inView(true)
assert.ok(p.scenario.classList.contains('is-order'))
p.doc.querySelector('[data-mode="auto"]').click()
assert.equal(p.scenario.dataset.playback, 'paused')
assert.equal([...p.timers.values()].some(value => value.delay >= 7000), false)
} finally { p.dom.window.close() }
})
test('glyph background renders, caps frame rate, and stops drawing in a hidden page', () => {
const p = page()
try {
const canvas = p.doc.getElementById('emberBackground')
assert.equal(canvas.getAttribute('aria-hidden'), 'true')
assert.equal(canvas.dataset.state, 'running')
assert.ok(p.emberDraws() > 0)
p.runFrame(100)
const draws = p.emberDraws()
p.runFrame(110)
assert.equal(p.emberDraws(), draws, 'frames within 50ms should not repaint the glyph field')
Object.defineProperty(p.doc, 'hidden', { value: true, configurable: true })
p.doc.dispatchEvent(new p.window.Event('visibilitychange'))
p.runFrame(200)
assert.equal(canvas.dataset.state, 'paused')
assert.equal(p.emberDraws(), draws)
Object.defineProperty(p.doc, 'hidden', { value: false, configurable: true })
p.doc.dispatchEvent(new p.window.Event('visibilitychange'))
assert.equal(canvas.dataset.state, 'running')
} finally { p.dom.window.close() }
})
test('glyph background switches to a static frame when reduced motion is enabled', () => {
const p = page()
try {
p.setMotion(true)
assert.equal(p.doc.getElementById('emberBackground').dataset.state, 'static')
const draws = p.emberDraws()
p.runFrame(200)
assert.equal(p.emberDraws(), draws)
p.setMotion(false)
assert.equal(p.doc.getElementById('emberBackground').dataset.state, 'running')
} finally { p.dom.window.close() }
})
test('fire has a dense bright bed, mixed letter glyphs, and uneven flame tips', () => {
const p = page()
try {
const samples = p.glyphSamples()
const height = p.window.innerHeight
const bed = samples.filter(sample => sample.y > height - 12)
assert.ok(bed.length > 100, 'the bed should be a continuous dense band')
assert.ok(bed.every(sample => sample.color === 'rgb(255,250,232)'), 'the bed should burn ivory-white')
assert.ok(new Set(samples.map(sample => sample.glyph)).size > 20, 'fire should use a varied ASCII density ramp')
const tips = new Map()
samples.forEach(sample => tips.set(sample.x, Math.min(tips.get(sample.x) ?? Infinity, sample.y)))
assert.ok(Math.max(...tips.values()) - Math.min(...tips.values()) > height * 0.15, 'flame tongues should reach different heights')
const first = samples.map(sample => sample.color + sample.glyph).join('')
p.runFrame(100)
p.runFrame(1100)
assert.notEqual(p.glyphSamples().map(sample => sample.color + sample.glyph).join(''), first)
} finally { p.dom.window.close() }
})