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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,28 @@ compatibility plus every declared payload file's byte length and SHA-256.
registered parts. It is revalidated on every import and apply. `manifest.sig`
is not used for signature verification.

### Import an MP4 video background

You can also choose a video directly from the platform menu without making a ZIP first:

- macOS: open the menu-bar app and choose **一键更换视频背景…**.
- Windows: open the system-tray menu and choose **一键更换视频背景**.
- Select an `.mp4` file in the picker and confirm. Dream Skin creates and applies a video theme.

A video theme still requires a static image for its poster and failure fallback. The video is read only through the controlled local loopback path and is not uploaded to an external service. The file must be a non-empty MP4 no larger than 100 MiB. If playback fails, the page becomes hidden, or the theme changes, Dream Skin pauses and cleans up the video and restores the image.

You can also create or import a video theme manually. In `theme.json`, `video` must be a filename inside the theme directory; absolute paths and traversal are not accepted:

```json
{
"schemaVersion": 1,
"image": "background.jpg",
"video": "background.mp4"
}
```

Place `background.jpg` (or another supported image) and `background.mp4` next to `theme.json` and `theme.css`, either in a theme directory or in a ZIP, then choose **Import Theme ZIP…**. A manually placed theme must also contain both the image and video files. Do not use symbolic links, junctions, or files outside the theme directory. After import, the theme is added to **Saved Themes** and can be selected from the menu or tray.

The local simplified ZIP must contain exactly non-empty `theme.json`, non-empty
`theme.css`, and its referenced image. That format has no official
manifest integrity or compatibility declaration and should come from a trusted
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,39 @@ macOS 菜单栏和 Windows 托盘都有「主题库 Gallery」和「在线 Studi
版本,以及清单中每个负载文件的大小和 SHA-256。`theme.css` 必须通过本机 Safe CSS 校验,导入后只会
作用于 12 个注册部件;每次切换/应用仍会重新校验。`manifest.sig` 当前不参与签名验证。

### 导入 MP4 视频背景

成功率最高的方式是直接从平台菜单选择视频,不要先把大视频打进主题 ZIP:

1. 先从 `Codex Dream Skin` 快捷方式启动 Codex,并保持菜单栏应用(macOS)或系统托盘(Windows)运行。
2. macOS 选择“一键更换视频背景…”;Windows 选择“一键更换视频背景”。
3. 在文件选择器中选择真实的 `.mp4` 文件。文件必须非空、不超过 100 MiB,并包含有效的 MP4 `ftyp`
容器头;只把其他格式改成 `.mp4` 后缀会被拒绝。为兼容 Codex 使用的 Chromium,建议使用
H.264/AVC 视频和 AAC 音频,或直接移除音轨。
4. 如果 Codex 是普通方式启动、还没有 Dream Skin 会话,首次应用可能要求重启一次;确认后等待
Dream Skin 重新启动并验证视频。会话正常后,再切换图片、视频或已保存主题不需要反复重启。

视频背景仍需要静态图片作为 poster 和失败回退。视频只在本机受控回环通道中读取,不会上传到外部服务;
直接选择视频时会沿用当前主题图片作为 poster。只有真实 Codex 渲染器确认视频已经可播放,热切换才会报告
成功;验证失败会恢复切换前的主题。播放失败、页面不可见或切换主题时,会自动暂停或清理旧视频。

也可以手动制作或导入包含视频的主题。`theme.json` 使用主题目录内的文件名,不接受绝对路径或路径穿越:

```json
{
"schemaVersion": 1,
"image": "background.jpg",
"video": "background.mp4"
}
```

将 `background.jpg`(或其他受支持图片)和 `background.mp4` 与 `theme.json`、`theme.css` 放在主题目录或 ZIP 中,
再通过“导入主题 ZIP…”导入。手动主题目录也必须包含图片和视频文件;不要放入符号链接、链接目录或其他
主题目录之外的文件。导入成功后主题会加入“已保存的主题”,再从菜单/托盘切换。

本地简化 ZIP 也必须恰好包含非空 `theme.json`、非空 `theme.css` 和其引用图片;该格式没有正式清单的
完整性与兼容性声明,只应从可信来源使用。压缩包最大 32 MiB、最多 32 个条目、解压后最多 64 MiB。
这些限制包含 `background.mp4`;例如 76 MiB 视频不能通过 ZIP 导入,应使用上面的菜单直接选择方式。
导入成功后主题只会加入“已保存的主题”,不会自动替换当前主题;相同内容不会重复写入,同 ID 的不同
主题会使用新的安全标识保存。

Expand Down
10 changes: 10 additions & 0 deletions macos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 未发布

### 修复

- 收窄首页/主壳 CSS 干预:不再清空原生 token surface 背景,不再改写 composer 圆角/边框/阴影/backdrop,不再抬升 utility bar z-index 或 translateY;把侧栏与输入区外观交回 Codex,仅保留视频舞台等必要层。

### 功能

- 支持将本机 MP4 设为动态视频背景:菜单栏新增「一键更换视频背景…」,主题包 `theme.json` 可选 `video` 字段;视频经本机回环媒体服务传输(`127.0.0.1`、一次性 token、Range/`206`),图片仍作 poster 与失败回退。导入校验要求真实 `ftyp` 容器头、非空且不超过 100 MiB;热切换仅在渲染器确认可播放后提交,失败则恢复切换前主题。

## 1.5.6 — 2026-07-26

### 安全
Expand Down
258 changes: 137 additions & 121 deletions macos/assets/dream-skin.css
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,6 @@ html[data-dream-skin="active"] aside.app-shell-left-panel [aria-current="page"]

html[data-dream-skin="active"] main.main-surface {
position: relative !important;
isolation: isolate;
overflow: hidden !important;
background: var(--ds-bg) !important;
border: 1px solid var(--ds-line) !important;
border-right: 0 !important;
Expand Down Expand Up @@ -497,22 +495,8 @@ html[data-dream-skin="active"][data-dream-shell="light"] main.main-surface:not(:
text-shadow: none;
}

/* Newer Codex builds paint the list/detail panes (pull requests, the chat
review/terminal/browser/files side panel) with token surfaces instead of
[role="main"]. The side panel can coexist with a visible home shell, so this
transparency is deliberately not gated on the home-route selector. The nested
surfaces all carry the same token, so they are cleared everywhere and the
translucent pane tint is painted once, on the bordered outer wrapper. */
html[data-dream-skin="active"] main.main-surface
:is(div, section, aside)[class~="bg-token-main-surface-primary"] {
background: transparent !important;
}

html[data-dream-skin="active"] main.main-surface
div[class~="bg-token-main-surface-primary"][class~="border-l"] {
background: rgb(var(--ds-panel-rgb) / .62) !important;
backdrop-filter: blur(10px) saturate(106%) !important;
}
/* Keep Codex token surfaces intact so native side panels retain their own
background, border, radius, and stacking behavior. */

html[data-dream-skin="active"] main.main-surface:not(:has([role="main"])) article {
border: 1px solid rgb(var(--ds-muted-rgb) / .12);
Expand Down Expand Up @@ -888,16 +872,7 @@ html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .gro
html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .group\/home-suggestions button > span:first-child { justify-content: center !important; }
html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .group\/home-suggestions button > span:last-child { align-items: center !important; text-align: center !important; }

html[data-dream-skin="active"] .composer-surface-chrome {
overflow: visible !important;
border: 1px solid rgb(var(--ds-muted-rgb) / .18) !important;
border-radius: 22px !important;
background: rgb(var(--ds-panel-rgb) / .94) !important;
box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24) !important;
backdrop-filter: blur(16px) saturate(108%) !important;
}

html[data-dream-skin="active"] .composer-surface-chrome::before { content: none !important; }
/* Let Codex own the composer surface geometry and stacking order. */

/* A wide image can carry the home screen too. The native hero remains live,
but the artwork is painted once on the window instead of repeated in a card. */
Expand Down Expand Up @@ -979,85 +954,15 @@ html[data-dream-skin="active"][data-dream-art-wide="true"] main.main-surface:has
backdrop-filter: blur(8px) saturate(104%) !important;
}

html[data-dream-skin="active"][data-dream-art-wide="true"] .composer-surface-chrome {
background: var(--ds-immersive-composer-solid) !important;
border: 0 !important;
box-shadow:
0 10px 30px rgb(var(--ds-bg-rgb) / .20),
inset 0 0 0 1px var(--ds-immersive-line),
inset 0 1px rgb(var(--ds-text-rgb) / .12) !important;
backdrop-filter: none !important;
}

html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="true"]
.composer-surface-chrome {
box-shadow:
0 10px 28px rgb(var(--ds-bg-rgb) / .14),
inset 0 0 0 1px var(--ds-immersive-line),
inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important;
backdrop-filter: blur(8px) saturate(102%) !important;
}
/* Codex owns the native utility/composer geometry in wide-art mode. */

html[data-dream-skin="active"][data-dream-shell="light"]
.composer-surface-chrome p.placeholder::after {
color: rgb(var(--ds-muted-rgb) / .78) !important;
opacity: 1 !important;
}

/* Current Codex builds render the home project picker as a separate opaque
cap above the composer. Join both native controls into one continuous
surface while leaving task and utility-route inputs untouched. */
html[data-dream-skin="active"] [class*="_homeUtilityBar_"] {
top: 0 !important;
width: 100% !important;
margin-inline: 0 !important;
padding-inline: 18px !important;
border-radius: 22px 22px 0 0 !important;
background: rgb(var(--ds-panel-rgb) / .94) !important;
box-shadow:
inset 1px 0 rgb(var(--ds-muted-rgb) / .18),
inset -1px 0 rgb(var(--ds-muted-rgb) / .18),
inset 0 1px rgb(var(--ds-muted-rgb) / .18) !important;
backdrop-filter: blur(16px) saturate(108%) !important;
}

html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]):has([class*="_homeUtilityBar_"])
.composer-surface-chrome {
border: 0 !important;
border-radius: 0 0 22px 22px !important;
box-shadow:
0 10px 28px rgb(var(--ds-bg-rgb) / .24),
inset 1px 0 rgb(var(--ds-muted-rgb) / .18),
inset -1px 0 rgb(var(--ds-muted-rgb) / .18),
inset 0 -1px rgb(var(--ds-muted-rgb) / .18) !important;
}

html[data-dream-skin="active"][data-dream-art-wide="true"] [class*="_homeUtilityBar_"] {
background: var(--ds-immersive-composer-solid) !important;
box-shadow:
inset 1px 0 var(--ds-immersive-line),
inset -1px 0 var(--ds-immersive-line),
inset 0 1px rgb(var(--ds-text-rgb) / .12) !important;
backdrop-filter: none !important;
}

html[data-dream-skin="active"][data-dream-art-wide="true"]
[role="main"]:has([data-testid="home-icon"]):has([class*="_homeUtilityBar_"]) .composer-surface-chrome {
box-shadow:
0 10px 30px rgb(var(--ds-bg-rgb) / .20),
inset 1px 0 var(--ds-immersive-line),
inset -1px 0 var(--ds-immersive-line),
inset 0 -1px var(--ds-immersive-line) !important;
}

html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="true"]
[class*="_homeUtilityBar_"] {
box-shadow:
inset 1px 0 var(--ds-immersive-line),
inset -1px 0 var(--ds-immersive-line),
inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important;
backdrop-filter: blur(8px) saturate(102%) !important;
}
/* Wide artwork does not change Codex's native utility/composer surfaces. */

html[data-dream-skin="active"] [class*="_homeUtilityBar_"] button,
html[data-dream-skin="active"] .composer-surface-chrome button:not([class~="bg-token-foreground"]) {
Expand Down Expand Up @@ -1134,27 +1039,6 @@ html[data-dream-skin="active"][data-dream-art-wide="true"] main.main-surface
background: transparent !important;
}

html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) div:has(> .horizontal-scroll-fade-mask .group\/project-selector) {
position: relative;
padding-top: 28px !important;
border: 1px solid rgb(var(--ds-muted-rgb) / .16);
border-bottom: 0;
background: rgb(var(--ds-panel-rgb) / .92) !important;
}

html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) div:has(> .horizontal-scroll-fade-mask .group\/project-selector)::before {
content: var(--dream-skin-project-label, "选择项目");
position: absolute;
left: 13px;
top: 6px;
z-index: 2;
color: var(--ds-muted);
font-size: 12px;
font-weight: 720;
letter-spacing: 0;
white-space: nowrap;
}

html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .group\/project-selector > button {
border-color: rgb(var(--ds-accent-rgb) / .22) !important;
background: rgb(var(--ds-accent-rgb) / .08) !important;
Expand Down Expand Up @@ -1212,3 +1096,135 @@ html[data-dream-skin="active"] [data-message-author-role] { border-radius: 16px;
animation-iteration-count: 1 !important;
}
}

/* Optional media layer. It is inert for image themes and spans the complete
Codex viewport when a validated MP4 URL is supplied by the injector. */
#codex-dream-skin-background-stage {
position: fixed;
inset: 0;
z-index: 0;
overflow: hidden;
pointer-events: none;
background: transparent !important;
visibility: hidden;
}

#codex-dream-skin-background-stage::before {
content: "";
position: absolute;
inset: 0;
z-index: 3;
pointer-events: none;
background-image: var(--ds-task-fade), var(--ds-task-shade);
background-repeat: no-repeat;
background-position: center, center;
background-size: 100% 100%, 100% 100%;
}

#codex-dream-skin-background-stage img,
#codex-dream-skin-background-stage video {
position: absolute;
inset: 0;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
object-position: var(--ds-art-position, 50% 50%);
pointer-events: none;
}

#codex-dream-skin-background-stage img { z-index: 1; }
#codex-dream-skin-background-stage video {
z-index: 1;
opacity: 0;
}

html[data-dream-skin="active"][data-dream-media="video"]
#codex-dream-skin-background-stage {
visibility: visible;
}

html[data-dream-skin="active"][data-dream-media="video"]
#codex-dream-skin-background-stage video {
opacity: 1;
}

html[data-dream-skin="active"][data-dream-media="video"]
#codex-dream-skin-background-stage::before {
opacity: .46;
}

html[data-dream-skin="active"][data-dream-media="video"]
#codex-dream-skin-background-stage video {
filter: brightness(1.15) saturate(1.04);
}

html[data-dream-skin="active"][data-dream-media="video"]
#codex-dream-skin-background-stage img {
display: none !important;
}

html[data-dream-skin="active"][data-dream-media="video"] body {
background: transparent !important;
background-image: none !important;
}

/* The image rules above are intentionally kept for image themes. Once the
video has its first frame, close every static-art entry point explicitly so
a Codex route cannot leave a second copy of the imported image visible. */
html[data-dream-skin="active"][data-dream-media="video"]
main.main-surface::before {
content: none !important;
background-image: none !important;
}

html[data-dream-skin="active"][data-dream-media="video"]
[role="main"]:has([data-testid="home-icon"]) > div:first-child > div:first-child > div:first-child {
background-image: none !important;
}

html[data-dream-skin="active"][data-dream-media="video"] body > #root {
position: relative !important;
/* Keep every native task/composer surface above the full-window media stage.
The explicit stacking context also covers Codex's new-task route, whose
transparent wrappers can otherwise participate in the body's auto layer. */
z-index: 10 !important;
isolation: isolate;
background: transparent !important;
}

/* Some Codex builds mount dialogs and popovers directly under body instead of
inside #root. Keep those native surfaces above the media stage as well. */
html[data-dream-skin="active"][data-dream-media="video"] body >
:is([role="dialog"], [aria-modal="true"], [data-radix-portal],
[data-radix-popper-content-wrapper]) {
z-index: 50 !important;
}

html[data-dream-skin="active"][data-dream-media="video"]
body > #root aside.app-shell-left-panel {
background:
linear-gradient(180deg,
rgb(var(--ds-panel-rgb) / .38),
rgb(var(--ds-bg-rgb) / .28)) !important;
backdrop-filter: blur(8px) saturate(112%) !important;
}

html[data-dream-skin="active"][data-dream-media="video"] main.main-surface {
background: transparent !important;
}

html[data-dream-skin="active"][data-dream-media="video"]
main.main-surface::before {
content: none !important;
}

/* Wide image themes also paint artwork directly on body. In video mode the
video stage is the sole artwork source; keep the old body image only for
the image fallback path. */
html[data-dream-skin="active"][data-dream-media="video"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"]))
body,
html[data-dream-skin="active"][data-dream-media="video"][data-dream-art-wide="true"]:has(main.main-surface [role="main"])
body {
background-image: none !important;
}
Loading