mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-01 15:30:52 +08:00
Fix release runtime preparation
This commit is contained in:
@@ -271,6 +271,13 @@ jobs:
|
||||
echo "LIME_WINDOWS_SIGNING_CERTIFICATE_FILE=$CERTIFICATE_PATH" >> "$GITHUB_ENV"
|
||||
echo "LIME_WINDOWS_SIGNING_CERTIFICATE_PASSWORD=$WINDOWS_SIGNING_CERTIFICATE_PASSWORD" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Prepare sherpa-onnx runtime
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node scripts/prepare-sherpa-onnx-runtime.mjs \
|
||||
--target "${{ matrix.target }}"
|
||||
|
||||
- name: Build Electron app
|
||||
shell: bash
|
||||
env:
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
- Fixed inconsistent loading, error, disconnect, close, and retry states in Settings and Browser Runtime, and removed the retired Chrome Relay guide path from the current product chain.
|
||||
- Fixed re-entry boundaries in Electron updater, App Server host, and Windows startup paths, with deterministic configuration for unsigned macOS development packaging.
|
||||
- Fixed a Windows Squirrel N-1 smoke navigation race that treated the preload-enabled startup page as the final renderer; packaged launches now also remove unsupported `NODE_OPTIONS` and the development-server URL.
|
||||
- Fixed Electron Release workflow builds when the Rust cache omits sherpa-onnx prebuilt libraries; shared runtime libraries are now prepared explicitly for each target before packaging.
|
||||
|
||||
### Improvements and Refactors
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
- 修复设置页和 Browser Runtime 在加载、错误、断开、关闭与重试状态下的文案和控件不一致问题,并移除已脱离 current 主链的旧 Chrome Relay guide 双轨。
|
||||
- 修复 Electron updater、App Server host 和 Windows 启动路径的并发/重入边界,补齐 macOS 未签名开发打包的确定性配置。
|
||||
- 修复 Windows Squirrel N-1 smoke 将带 preload 的临时启动页误判为最终 renderer 的导航竞态;打包启动环境同时移除不受支持的 `NODE_OPTIONS` 与开发服务器地址。
|
||||
- 修复 Electron Release workflow 在 Rust cache 未恢复 sherpa-onnx 预编译库时的构建失败;构建前会按目标平台显式准备共享运行库。
|
||||
|
||||
### 优化与重构
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Lime v1.107.0 发布执行计划
|
||||
|
||||
状态:validated-ready-to-cover-release
|
||||
状态:validated-ready-to-rerun-release
|
||||
日期:2026-07-18
|
||||
目标版本:`1.107.0`
|
||||
目标 tag:`v1.107.0`
|
||||
@@ -12,7 +12,7 @@
|
||||
## Release Candidate
|
||||
|
||||
- 基线:`v1.106.0`;任务开始时 `main`、`origin/main` 与该 tag 指向同一提交,目标 tag 在本地不存在。
|
||||
- 初始候选盘点包含 213 个已跟踪改动和 72 个未跟踪文件,共 285 个候选路径;本轮追加 Windows Squirrel N-1 导航竞态修复、回归测试及首页皮肤资源,仍无排除项。
|
||||
- 初始候选盘点包含 213 个已跟踪改动和 72 个未跟踪文件,共 285 个候选路径;本轮追加 Windows Squirrel N-1 导航竞态修复、Release sherpa-onnx 预编译库预热、回归测试及首页皮肤资源,仍无排除项。
|
||||
- `release metadata`:`package.json`、`packages/lime-cli-npm/package.json`、`lime-rs/Cargo.toml`、`lime-rs/Cargo.lock`、`RELEASE_NOTES.md`、`RELEASE_NOTES.en.md` 与本计划。
|
||||
- `candidate changes`:当前工作树全部产品、测试、脚本、schema、资源、治理和文档改动,无排除项。
|
||||
- `excluded changes`:无。
|
||||
@@ -34,7 +34,8 @@
|
||||
|
||||
- Release candidate 已冻结,纳入当前全部候选路径,无排除项;用户已明确要求覆盖 `v1.107.0` 并递交全部当前改动。
|
||||
- 版本事实源与双语 release notes 已更新,并补记 Windows startup-page navigation race hotfix。
|
||||
- 版本事实源、合同、Rust、Agent fixture、GUI smoke 与 Windows Squirrel 定向回归均已通过;Git staging、commit、tag 与 push 待执行。
|
||||
- 首轮覆盖提交 `7fb666415` 已完成并推送;Release workflow `29631194668` 因三平台构建时缺少 sherpa-onnx 预编译库失败,未进入 Windows N-1 smoke。
|
||||
- 已加入 Release 构建前按目标显式准备 sherpa-onnx 运行库的 workflow guard;本轮定向回归通过,待重新覆盖提交、tag 和 workflow。
|
||||
|
||||
## 验证记录
|
||||
|
||||
@@ -46,8 +47,10 @@
|
||||
- `npm run verify:gui-smoke`:通过;renderer、Desktop Host、preload、App Server sidecar、Workbench、Memory Settings 与 reload 主路径可用,报告 `appserver.v0 / 1.107.0`。
|
||||
- `git diff --check`:通过。
|
||||
- `npx vitest run scripts/electron/windows-squirrel-rc-smoke.test.mjs scripts/electron/release-workflow-guard.test.mjs scripts/electron/current-entrypoints.test.mjs`:通过,3 files / 52 tests。
|
||||
- 更新 workflow 后 `npx vitest run scripts/electron/release-workflow-guard.test.mjs scripts/electron/windows-squirrel-rc-smoke.test.mjs scripts/electron/current-entrypoints.test.mjs src/components/agent/chat/components/EmptyStateLayout.test.tsx src/components/agent/chat/components/homeSkinPresentation.test.ts`:通过,5 files / 62 tests。
|
||||
- 两个 Windows Squirrel 修改脚本 `node --check`:通过。
|
||||
- 真实 Windows packaged L8/N-1 验证:待新的远端 Release workflow 执行;本机为 macOS,无法代替 Windows 证据。
|
||||
- 首轮远端 Release `29631194668`:失败于三平台 `app-server` 链接;Windows `sherpa-onnx-c-api.lib` 缺失,macOS `sherpa-onnx-c-api` 共享库目录缺失,未执行 N-1。
|
||||
- 真实 Windows packaged L8/N-1 验证:待修复后的远端 Release workflow 执行;本机为 macOS,无法代替 Windows 证据。
|
||||
|
||||
## 架构确认
|
||||
|
||||
@@ -56,4 +59,4 @@
|
||||
- 责任人:release owner(v1.107.0)。
|
||||
- 日期:2026-07-18。
|
||||
|
||||
当前完成度:`97%`。下一刀:`git add -A` 后复核 staged 摘要,创建覆盖提交,移动并强推 `v1.107.0`,再监控远端 Windows Release workflow 与资产。
|
||||
当前完成度:`94%`。下一刀:纳入提交后新增的首页皮肤改动与 sherpa workflow 修复,覆盖提交和 `v1.107.0`,再监控 Release workflow 至 Windows N-1 终态。
|
||||
|
||||
@@ -318,6 +318,14 @@ function assertBuildSteps(buildJob) {
|
||||
|
||||
const buildStep = stepByName(steps, "Build Electron app");
|
||||
const buildRun = buildStep?.run || "";
|
||||
const sherpaStep = stepByName(steps, "Prepare sherpa-onnx runtime");
|
||||
const sherpaRun = sherpaStep?.run || "";
|
||||
for (const required of [
|
||||
"scripts/prepare-sherpa-onnx-runtime.mjs",
|
||||
'--target "${{ matrix.target }}"',
|
||||
]) {
|
||||
assertIncludes(sherpaRun, required, "sherpa-onnx runtime preparation");
|
||||
}
|
||||
for (const required of [
|
||||
"npm run electron:build",
|
||||
"npx electron-forge package",
|
||||
|
||||
@@ -132,6 +132,20 @@ describe("Electron release workflow guard", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects release workflow without sherpa-onnx runtime preparation", () => {
|
||||
const current = fs.readFileSync(".github/workflows/release.yml", "utf8");
|
||||
const workflowPath = tempWorkflowPath(
|
||||
current.replace(
|
||||
/ - name: Prepare sherpa-onnx runtime[\s\S]*? --target "\$\{\{ matrix\.target \}\}"\n\n/,
|
||||
"",
|
||||
),
|
||||
);
|
||||
|
||||
expect(() => validateReleaseWorkflow({ workflowPath })).toThrow(
|
||||
/sherpa-onnx runtime preparation.*scripts\/prepare-sherpa-onnx-runtime\.mjs/,
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects missing macOS transient package retry in Forge package step", () => {
|
||||
const current = fs.readFileSync(".github/workflows/release.yml", "utf8");
|
||||
const workflowPath = tempWorkflowPath(
|
||||
|
||||
@@ -15,55 +15,209 @@ const heroReveal = keyframes`
|
||||
}
|
||||
`;
|
||||
|
||||
const foregroundFloat = keyframes`
|
||||
const portraitBreathe = keyframes`
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) rotate(0deg);
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(0, calc(var(--home-hero-motion-distance, 12px) * -1), 0)
|
||||
rotate(var(--home-hero-motion-rotate, 0deg));
|
||||
transform: translate3d(
|
||||
0,
|
||||
calc(var(--home-hero-motion-distance, 8px) * -0.35),
|
||||
0
|
||||
)
|
||||
scale(var(--home-hero-motion-scale, 1.02));
|
||||
}
|
||||
`;
|
||||
|
||||
const foregroundSway = keyframes`
|
||||
const foliageSway = keyframes`
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) rotate(0deg);
|
||||
transform: rotate(calc(var(--home-hero-motion-rotate, 2deg) * -0.35));
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(calc(var(--home-hero-motion-distance, 12px) * -1), 0, 0)
|
||||
rotate(var(--home-hero-motion-rotate, 0deg));
|
||||
transform: translate3d(
|
||||
calc(var(--home-hero-motion-distance, 8px) * -0.25),
|
||||
0,
|
||||
0
|
||||
)
|
||||
rotate(var(--home-hero-motion-rotate, 2deg));
|
||||
}
|
||||
`;
|
||||
|
||||
const foregroundPulse = keyframes`
|
||||
const cheerBob = keyframes`
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
}
|
||||
42% {
|
||||
transform: translate3d(
|
||||
0,
|
||||
calc(var(--home-hero-motion-distance, 10px) * -1),
|
||||
0
|
||||
)
|
||||
scale(1.01, 0.985);
|
||||
}
|
||||
54% {
|
||||
transform: translate3d(
|
||||
0,
|
||||
calc(var(--home-hero-motion-distance, 10px) * -0.55),
|
||||
0
|
||||
)
|
||||
scale(0.995, var(--home-hero-motion-scale, 1.02));
|
||||
}
|
||||
`;
|
||||
|
||||
const powerPulse = keyframes`
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
filter: brightness(1) drop-shadow(0 0 0 transparent);
|
||||
}
|
||||
50% {
|
||||
transform: scale(var(--home-hero-motion-scale, 1.04));
|
||||
opacity: 0.94;
|
||||
transform: scale(var(--home-hero-motion-scale, 1.035));
|
||||
filter: brightness(1.08)
|
||||
drop-shadow(
|
||||
var(--home-hero-motion-glow, 0 0 16px rgba(73, 222, 255, 0.42))
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
||||
const foregroundDrift = keyframes`
|
||||
const silkDrift = keyframes`
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(
|
||||
calc(var(--home-hero-motion-distance, 12px) * -1),
|
||||
calc(var(--home-hero-motion-distance, 12px) * -0.55),
|
||||
calc(var(--home-hero-motion-distance, 10px) * -1),
|
||||
calc(var(--home-hero-motion-distance, 10px) * -0.3),
|
||||
0
|
||||
)
|
||||
rotate(var(--home-hero-motion-rotate, 0deg));
|
||||
}
|
||||
`;
|
||||
|
||||
const stageBeat = keyframes`
|
||||
0%,
|
||||
58%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) rotate(0deg);
|
||||
}
|
||||
45% {
|
||||
transform: translate3d(
|
||||
0,
|
||||
calc(var(--home-hero-motion-distance, 12px) * -1),
|
||||
0
|
||||
)
|
||||
rotate(calc(var(--home-hero-motion-rotate, 0.8deg) * -1));
|
||||
}
|
||||
52% {
|
||||
transform: translate3d(
|
||||
0,
|
||||
calc(var(--home-hero-motion-distance, 12px) * -0.4),
|
||||
0
|
||||
)
|
||||
rotate(calc(var(--home-hero-motion-rotate, 0.8deg) * 0.35));
|
||||
}
|
||||
`;
|
||||
|
||||
const creativeTilt = keyframes`
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(calc(var(--home-hero-motion-rotate, 1.4deg) * -0.3));
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(
|
||||
calc(var(--home-hero-motion-distance, 9px) * -0.5),
|
||||
calc(var(--home-hero-motion-distance, 9px) * -0.6),
|
||||
0
|
||||
)
|
||||
rotate(var(--home-hero-motion-rotate, 1.4deg));
|
||||
}
|
||||
`;
|
||||
|
||||
const streetBeat = keyframes`
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) rotate(0deg);
|
||||
}
|
||||
32% {
|
||||
transform: translate3d(
|
||||
0,
|
||||
calc(var(--home-hero-motion-distance, 11px) * -1),
|
||||
0
|
||||
)
|
||||
rotate(calc(var(--home-hero-motion-rotate, 1.8deg) * -0.8));
|
||||
}
|
||||
42% {
|
||||
transform: translate3d(
|
||||
0,
|
||||
calc(var(--home-hero-motion-distance, 11px) * -0.3),
|
||||
0
|
||||
)
|
||||
rotate(calc(var(--home-hero-motion-rotate, 1.8deg) * 0.35));
|
||||
}
|
||||
68% {
|
||||
transform: translate3d(
|
||||
calc(var(--home-hero-motion-distance, 11px) * -0.45),
|
||||
calc(var(--home-hero-motion-distance, 11px) * -0.7),
|
||||
0
|
||||
)
|
||||
rotate(var(--home-hero-motion-rotate, 1.8deg));
|
||||
}
|
||||
`;
|
||||
|
||||
const spotlightBreathe = keyframes`
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
filter: brightness(1) drop-shadow(0 0 0 transparent);
|
||||
}
|
||||
50% {
|
||||
transform: scale(var(--home-hero-motion-scale, 1.018));
|
||||
filter: brightness(1.06)
|
||||
drop-shadow(
|
||||
var(--home-hero-motion-glow, 0 0 14px rgba(255, 220, 176, 0.26))
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
||||
const portalPulse = keyframes`
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
filter: brightness(0.96) drop-shadow(0 0 0 transparent);
|
||||
}
|
||||
50% {
|
||||
transform: scale(var(--home-hero-motion-scale, 1.04));
|
||||
filter: brightness(1.16)
|
||||
drop-shadow(
|
||||
var(--home-hero-motion-glow, 0 0 20px rgba(146, 194, 255, 0.46))
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
||||
const cityGlide = keyframes`
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
filter: brightness(1) drop-shadow(0 0 0 transparent);
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(
|
||||
calc(var(--home-hero-motion-distance, 14px) * -1),
|
||||
0,
|
||||
0
|
||||
)
|
||||
scale(var(--home-hero-motion-scale, 1.012));
|
||||
filter: brightness(1.06)
|
||||
drop-shadow(
|
||||
var(--home-hero-motion-glow, 0 0 16px rgba(229, 53, 69, 0.3))
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
||||
const HeroSection = styled.section`
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -291,32 +445,69 @@ const ArtworkForeground = styled.div`
|
||||
width: var(--home-hero-foreground-width, 0);
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
transform-origin: 70% 82%;
|
||||
will-change: transform;
|
||||
transform-origin: var(--home-hero-motion-origin, 50% 85%);
|
||||
will-change: transform, filter;
|
||||
|
||||
&[data-home-hero-motion="float"] {
|
||||
animation: ${foregroundFloat} var(--home-hero-motion-duration, 8s)
|
||||
ease-in-out var(--home-hero-motion-delay, 0s) infinite;
|
||||
&:not([data-home-hero-motion="none"]) {
|
||||
animation-duration: var(--home-hero-motion-duration, 8s);
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-delay: var(--home-hero-motion-delay, 0s);
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: var(--home-hero-motion-direction, normal);
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
&[data-home-hero-motion="sway"] {
|
||||
animation: ${foregroundSway} var(--home-hero-motion-duration, 8s)
|
||||
ease-in-out var(--home-hero-motion-delay, 0s) infinite;
|
||||
&[data-home-hero-motion="portrait-breathe"] {
|
||||
animation-name: ${portraitBreathe};
|
||||
}
|
||||
|
||||
&[data-home-hero-motion="pulse"] {
|
||||
animation: ${foregroundPulse} var(--home-hero-motion-duration, 8s)
|
||||
ease-in-out var(--home-hero-motion-delay, 0s) infinite;
|
||||
&[data-home-hero-motion="foliage-sway"] {
|
||||
animation-name: ${foliageSway};
|
||||
}
|
||||
|
||||
&[data-home-hero-motion="drift"] {
|
||||
animation: ${foregroundDrift} var(--home-hero-motion-duration, 8s)
|
||||
ease-in-out var(--home-hero-motion-delay, 0s) infinite;
|
||||
&[data-home-hero-motion="cheer-bob"] {
|
||||
animation-name: ${cheerBob};
|
||||
animation-timing-function: cubic-bezier(0.34, 1.28, 0.64, 1);
|
||||
}
|
||||
|
||||
&[data-home-hero-motion="power-pulse"] {
|
||||
animation-name: ${powerPulse};
|
||||
}
|
||||
|
||||
&[data-home-hero-motion="silk-drift"] {
|
||||
animation-name: ${silkDrift};
|
||||
}
|
||||
|
||||
&[data-home-hero-motion="stage-beat"] {
|
||||
animation-name: ${stageBeat};
|
||||
animation-timing-function: cubic-bezier(0.45, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
&[data-home-hero-motion="creative-tilt"] {
|
||||
animation-name: ${creativeTilt};
|
||||
}
|
||||
|
||||
&[data-home-hero-motion="street-beat"] {
|
||||
animation-name: ${streetBeat};
|
||||
animation-timing-function: cubic-bezier(0.34, 1.12, 0.64, 1);
|
||||
}
|
||||
|
||||
&[data-home-hero-motion="spotlight-breathe"] {
|
||||
animation-name: ${spotlightBreathe};
|
||||
}
|
||||
|
||||
&[data-home-hero-motion="portal-pulse"] {
|
||||
animation-name: ${portalPulse};
|
||||
}
|
||||
|
||||
&[data-home-hero-motion="city-glide"] {
|
||||
animation-name: ${cityGlide};
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
animation: none !important;
|
||||
will-change: auto;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
img {
|
||||
@@ -552,6 +743,9 @@ export function EmptyStateHero({
|
||||
"--home-hero-motion-distance": foregroundMotion?.distance ?? "12px",
|
||||
"--home-hero-motion-rotate": foregroundMotion?.rotate ?? "0deg",
|
||||
"--home-hero-motion-scale": foregroundMotion?.scale ?? "1.04",
|
||||
"--home-hero-motion-origin": foregroundMotion?.origin ?? "50% 85%",
|
||||
"--home-hero-motion-direction": foregroundMotion?.direction ?? "normal",
|
||||
"--home-hero-motion-glow": foregroundMotion?.glow ?? "0 0 0 transparent",
|
||||
"--home-hero-breakout-space": presentation.breakoutSpace ?? "0",
|
||||
"--home-hero-breakout-space-mobile":
|
||||
presentation.breakoutSpaceMobile ?? "0",
|
||||
|
||||
@@ -100,7 +100,7 @@ describe("EmptyStateLayout", () => {
|
||||
artworkStage
|
||||
?.querySelector('[data-testid="home-hero-foreground"]')
|
||||
?.getAttribute("data-home-hero-motion"),
|
||||
).toBe("float");
|
||||
).toBe("portrait-breathe");
|
||||
expect(
|
||||
artworkStage?.style.getPropertyValue("--home-hero-motion-duration"),
|
||||
).toBe("4.8s");
|
||||
@@ -135,7 +135,7 @@ describe("EmptyStateLayout", () => {
|
||||
artworkStage
|
||||
?.querySelector('[data-testid="home-hero-foreground"]')
|
||||
?.getAttribute("data-home-hero-motion"),
|
||||
).toBe("pulse");
|
||||
).toBe("power-pulse");
|
||||
expect(
|
||||
artworkStage?.style.getPropertyValue("--home-hero-art-blend-width"),
|
||||
).toBe("68%");
|
||||
|
||||
@@ -24,7 +24,7 @@ describe("homeSkinPresentation", () => {
|
||||
expect(presentation.stageHeightWide).toBe("430px");
|
||||
expect(presentation.foreground?.top).toBe("-56px");
|
||||
expect(presentation.breakoutSpace).toBe("68px");
|
||||
expect(presentation.foreground?.motion?.kind).toBe("float");
|
||||
expect(presentation.foreground?.motion?.kind).toBe("portrait-breathe");
|
||||
expect(presentation.foreground?.motion?.duration).toBe("4.8s");
|
||||
});
|
||||
|
||||
@@ -42,6 +42,12 @@ describe("homeSkinPresentation", () => {
|
||||
"100% center",
|
||||
);
|
||||
expect(getHomeSkinPresentation("lime-forest").artFit).toBe("contain");
|
||||
expect(getHomeSkinPresentation("lime-forest").foreground?.width).toBe(
|
||||
"43%",
|
||||
);
|
||||
expect(getHomeSkinPresentation("lime-forest").foreground?.bottom).toBe(
|
||||
"-2px",
|
||||
);
|
||||
expect(getHomeSkinPresentation("lime-forest").artBlendWidth).toBe("68%");
|
||||
expect(getHomeSkinPresentation("lime-luxury").artBlendWidth).toBe("68%");
|
||||
expect(getHomeSkinPresentation("lime-ocean").image).toContain(
|
||||
@@ -83,6 +89,12 @@ describe("homeSkinPresentation", () => {
|
||||
expect(getHomeSkinPresentation("lime-luxury").image).toContain(
|
||||
"lime-luxury-hero.png",
|
||||
);
|
||||
expect(getHomeSkinPresentation("lime-luxury").foreground?.width).toBe(
|
||||
"43%",
|
||||
);
|
||||
expect(getHomeSkinPresentation("lime-luxury").foreground?.bottom).toBe(
|
||||
"-1px",
|
||||
);
|
||||
});
|
||||
|
||||
it("每套皮肤都应使用独立 Hero 素材", () => {
|
||||
@@ -153,7 +165,7 @@ describe("homeSkinPresentation", () => {
|
||||
).toBe(true);
|
||||
expect(
|
||||
new Set(foregrounds.map((foreground) => foreground?.motion?.kind)).size,
|
||||
).toBe(4);
|
||||
).toBe(11);
|
||||
});
|
||||
|
||||
it("hero 外框和阴影应跟随皮肤色调", () => {
|
||||
|
||||
@@ -61,7 +61,18 @@ export type HomeKeepsakeVariant =
|
||||
| "medallion"
|
||||
| "bookmark";
|
||||
|
||||
export type HomeHeroMotionKind = "float" | "sway" | "pulse" | "drift";
|
||||
export type HomeHeroMotionKind =
|
||||
| "portrait-breathe"
|
||||
| "foliage-sway"
|
||||
| "cheer-bob"
|
||||
| "power-pulse"
|
||||
| "silk-drift"
|
||||
| "stage-beat"
|
||||
| "creative-tilt"
|
||||
| "street-beat"
|
||||
| "spotlight-breathe"
|
||||
| "portal-pulse"
|
||||
| "city-glide";
|
||||
|
||||
export interface HomeHeroMotion {
|
||||
kind: HomeHeroMotionKind;
|
||||
@@ -70,6 +81,9 @@ export interface HomeHeroMotion {
|
||||
distance?: string;
|
||||
rotate?: string;
|
||||
scale?: string;
|
||||
origin?: string;
|
||||
direction?: "normal" | "reverse";
|
||||
glow?: string;
|
||||
}
|
||||
|
||||
export interface HomeHeroForeground {
|
||||
@@ -177,11 +191,12 @@ const HOME_SKIN_PRESENTATIONS: Record<
|
||||
right: "4%",
|
||||
rightMobile: "1%",
|
||||
motion: {
|
||||
kind: "float",
|
||||
kind: "portrait-breathe",
|
||||
duration: "4.8s",
|
||||
delay: "-1.2s",
|
||||
distance: "13px",
|
||||
rotate: "1.2deg",
|
||||
distance: "8px",
|
||||
scale: "1.025",
|
||||
origin: "65% 88%",
|
||||
},
|
||||
},
|
||||
heroDecorations: {
|
||||
@@ -225,10 +240,11 @@ const HOME_SKIN_PRESENTATIONS: Record<
|
||||
transform: "translate(2%, 2%) scale(0.94)",
|
||||
transformMobile: "translate(2%, 2%) scale(1.02)",
|
||||
motion: {
|
||||
kind: "sway",
|
||||
duration: "5.6s",
|
||||
distance: "12px",
|
||||
rotate: "2.2deg",
|
||||
kind: "foliage-sway",
|
||||
duration: "5.8s",
|
||||
distance: "8px",
|
||||
rotate: "2.4deg",
|
||||
origin: "44% 100%",
|
||||
},
|
||||
},
|
||||
heroDecorations: { trailingIcon: "leaf" },
|
||||
@@ -259,20 +275,23 @@ const HOME_SKIN_PRESENTATIONS: Record<
|
||||
image: limeForestHero,
|
||||
foreground: {
|
||||
image: limeForestForeground,
|
||||
width: "39%",
|
||||
widthMobile: "54%",
|
||||
top: "-28px",
|
||||
topMobile: "-14px",
|
||||
right: "1%",
|
||||
rightMobile: "-4%",
|
||||
transform: "scale(1.08)",
|
||||
transformMobile: "scale(1.16)",
|
||||
width: "43%",
|
||||
widthMobile: "58%",
|
||||
top: "auto",
|
||||
topMobile: "auto",
|
||||
right: "-2%",
|
||||
rightMobile: "-9%",
|
||||
bottom: "-2px",
|
||||
bottomMobile: "-1px",
|
||||
transform: "scale(1.04)",
|
||||
transformMobile: "scale(1.08)",
|
||||
motion: {
|
||||
kind: "sway",
|
||||
duration: "4.4s",
|
||||
kind: "cheer-bob",
|
||||
duration: "3.6s",
|
||||
delay: "-2.4s",
|
||||
distance: "10px",
|
||||
rotate: "1.3deg",
|
||||
distance: "11px",
|
||||
scale: "1.025",
|
||||
origin: "55% 100%",
|
||||
},
|
||||
},
|
||||
heroDecorations: { trailingIcon: "coins" },
|
||||
@@ -304,18 +323,22 @@ const HOME_SKIN_PRESENTATIONS: Record<
|
||||
image: limeOceanHero,
|
||||
foreground: {
|
||||
image: limeOceanForeground,
|
||||
width: "34%",
|
||||
widthMobile: "47%",
|
||||
top: "-58px",
|
||||
topMobile: "-32px",
|
||||
right: "-1%",
|
||||
rightMobile: "-6%",
|
||||
transform: "translate(2%, 2%) scale(0.98)",
|
||||
transformMobile: "translate(2%, 2%) scale(1.06)",
|
||||
width: "40%",
|
||||
widthMobile: "56%",
|
||||
top: "auto",
|
||||
topMobile: "auto",
|
||||
right: "-3%",
|
||||
rightMobile: "-10%",
|
||||
bottom: "-2px",
|
||||
bottomMobile: "-1px",
|
||||
transform: "translate(2%, 0) scale(0.98)",
|
||||
transformMobile: "translate(2%, 0) scale(1.02)",
|
||||
motion: {
|
||||
kind: "pulse",
|
||||
duration: "4.2s",
|
||||
scale: "1.04",
|
||||
kind: "power-pulse",
|
||||
duration: "4.6s",
|
||||
scale: "1.035",
|
||||
origin: "50% 86%",
|
||||
glow: "0 0 18px rgba(50, 238, 255, 0.48)",
|
||||
},
|
||||
},
|
||||
heroDecorations: { trailingIcon: "shield" },
|
||||
@@ -356,11 +379,12 @@ const HOME_SKIN_PRESENTATIONS: Record<
|
||||
transform: "scale(1.04)",
|
||||
transformMobile: "scale(1.1)",
|
||||
motion: {
|
||||
kind: "float",
|
||||
duration: "5.6s",
|
||||
kind: "silk-drift",
|
||||
duration: "6.8s",
|
||||
delay: "-2.1s",
|
||||
distance: "12px",
|
||||
rotate: "1deg",
|
||||
distance: "10px",
|
||||
rotate: "0.7deg",
|
||||
origin: "60% 92%",
|
||||
},
|
||||
},
|
||||
heroDecorations: { trailingIcon: "sun" },
|
||||
@@ -401,11 +425,12 @@ const HOME_SKIN_PRESENTATIONS: Record<
|
||||
transform: "scale(1.05)",
|
||||
transformMobile: "scale(1.14)",
|
||||
motion: {
|
||||
kind: "sway",
|
||||
duration: "3.8s",
|
||||
kind: "stage-beat",
|
||||
duration: "3.2s",
|
||||
delay: "-0.8s",
|
||||
distance: "15px",
|
||||
rotate: "2deg",
|
||||
distance: "13px",
|
||||
rotate: "0.7deg",
|
||||
origin: "50% 100%",
|
||||
},
|
||||
},
|
||||
heroDecorations: { trailingIcon: "music" },
|
||||
@@ -446,11 +471,12 @@ const HOME_SKIN_PRESENTATIONS: Record<
|
||||
transform: "scale(1.04)",
|
||||
transformMobile: "scale(1.12)",
|
||||
motion: {
|
||||
kind: "float",
|
||||
duration: "4.2s",
|
||||
kind: "creative-tilt",
|
||||
duration: "4.6s",
|
||||
delay: "-1.7s",
|
||||
distance: "14px",
|
||||
distance: "9px",
|
||||
rotate: "1.5deg",
|
||||
origin: "55% 95%",
|
||||
},
|
||||
},
|
||||
heroDecorations: { trailingIcon: "lightbulb" },
|
||||
@@ -491,11 +517,12 @@ const HOME_SKIN_PRESENTATIONS: Record<
|
||||
transform: "scale(1.08)",
|
||||
transformMobile: "scale(1.14)",
|
||||
motion: {
|
||||
kind: "drift",
|
||||
duration: "5.2s",
|
||||
kind: "spotlight-breathe",
|
||||
duration: "6s",
|
||||
delay: "-3.1s",
|
||||
distance: "10px",
|
||||
rotate: "1.2deg",
|
||||
scale: "1.018",
|
||||
origin: "50% 90%",
|
||||
glow: "0 0 16px rgba(232, 185, 116, 0.3)",
|
||||
},
|
||||
},
|
||||
heroDecorations: { trailingIcon: "mic" },
|
||||
@@ -536,10 +563,12 @@ const HOME_SKIN_PRESENTATIONS: Record<
|
||||
transform: "translate(3%, 2%) scale(0.94)",
|
||||
transformMobile: "translate(3%, 2%) scale(1.04)",
|
||||
motion: {
|
||||
kind: "pulse",
|
||||
duration: "5.8s",
|
||||
kind: "portal-pulse",
|
||||
duration: "5.4s",
|
||||
delay: "-2.8s",
|
||||
scale: "1.055",
|
||||
scale: "1.04",
|
||||
origin: "50% 58%",
|
||||
glow: "0 0 20px rgba(146, 194, 255, 0.48)",
|
||||
},
|
||||
},
|
||||
heroDecorations: { trailingIcon: "circle" },
|
||||
@@ -579,11 +608,12 @@ const HOME_SKIN_PRESENTATIONS: Record<
|
||||
transform: "scale(1.05)",
|
||||
transformMobile: "scale(1.14)",
|
||||
motion: {
|
||||
kind: "sway",
|
||||
duration: "4.2s",
|
||||
kind: "street-beat",
|
||||
duration: "3.7s",
|
||||
delay: "-1.5s",
|
||||
distance: "14px",
|
||||
distance: "11px",
|
||||
rotate: "1.8deg",
|
||||
origin: "50% 100%",
|
||||
},
|
||||
},
|
||||
heroDecorations: { trailingIcon: "palette" },
|
||||
@@ -624,11 +654,12 @@ const HOME_SKIN_PRESENTATIONS: Record<
|
||||
transform: "scale(1.06)",
|
||||
transformMobile: "scale(1.12)",
|
||||
motion: {
|
||||
kind: "float",
|
||||
duration: "6s",
|
||||
kind: "portrait-breathe",
|
||||
duration: "6.2s",
|
||||
delay: "-4s",
|
||||
distance: "10px",
|
||||
rotate: "0.8deg",
|
||||
distance: "5px",
|
||||
scale: "1.012",
|
||||
origin: "55% 100%",
|
||||
},
|
||||
},
|
||||
heroDecorations: { trailingIcon: "book-open" },
|
||||
@@ -660,20 +691,23 @@ const HOME_SKIN_PRESENTATIONS: Record<
|
||||
image: limeLuxuryHero,
|
||||
foreground: {
|
||||
image: limeLuxuryForeground,
|
||||
width: "31%",
|
||||
widthMobile: "46%",
|
||||
top: "-36px",
|
||||
topMobile: "-22px",
|
||||
right: "2%",
|
||||
rightMobile: "-6%",
|
||||
transform: "translate(2%, 0) scale(1)",
|
||||
transformMobile: "translate(2%, 0) scale(1.04)",
|
||||
width: "43%",
|
||||
widthMobile: "58%",
|
||||
top: "auto",
|
||||
topMobile: "auto",
|
||||
right: "-2%",
|
||||
rightMobile: "-10%",
|
||||
bottom: "-1px",
|
||||
bottomMobile: "-1px",
|
||||
transform: "translate(1%, 0) scale(1)",
|
||||
transformMobile: "translate(1%, 0) scale(1.02)",
|
||||
motion: {
|
||||
kind: "drift",
|
||||
duration: "6.2s",
|
||||
kind: "spotlight-breathe",
|
||||
duration: "6.6s",
|
||||
delay: "-2.6s",
|
||||
distance: "9px",
|
||||
rotate: "0.8deg",
|
||||
scale: "1.02",
|
||||
origin: "50% 100%",
|
||||
glow: "0 0 18px rgba(131, 117, 255, 0.34)",
|
||||
},
|
||||
},
|
||||
heroDecorations: { trailingIcon: "gem" },
|
||||
@@ -714,10 +748,13 @@ const HOME_SKIN_PRESENTATIONS: Record<
|
||||
transform: "translate(-1%, 3%) scale(0.92)",
|
||||
transformMobile: "translate(-1%, 3%) scale(1.02)",
|
||||
motion: {
|
||||
kind: "pulse",
|
||||
duration: "4.8s",
|
||||
kind: "city-glide",
|
||||
duration: "8s",
|
||||
delay: "-1.9s",
|
||||
scale: "1.04",
|
||||
distance: "14px",
|
||||
scale: "1.012",
|
||||
origin: "50% 80%",
|
||||
glow: "0 0 16px rgba(229, 53, 69, 0.3)",
|
||||
},
|
||||
},
|
||||
heroDecorations: { trailingIcon: "orbit" },
|
||||
|
||||
Reference in New Issue
Block a user