diff --git a/.memory/worklog.json b/.memory/worklog.json index c03fec0..44b4820 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -1,12 +1,5 @@ { "entries": [ - { - "files_changed": 8, - "hash": "8c6ee1d", - "message": "auto-save 2026-05-14 01:45 (+6, ~2)", - "ts": "2026-05-14T01:46:08+08:00", - "type": "commit" - }, { "files_changed": 2, "message": "启动 Codex 接力会话 · 已载入 Claude / Codex 最近会话,等待下一条指令 · 分支 HEAD · 2 项未提交变更 · 最近提交:auto-save 2026-05-14 01:45 (+6, ~2)", @@ -3251,6 +3244,13 @@ "type": "session-heartbeat", "message": "Codex 会话活跃 · 最近命令:codex · 4 项未提交变更 · 最近提交:auto-save 2026-05-15 17:00 (~3)", "files_changed": 4 + }, + { + "ts": "2026-05-15T17:06:22+08:00", + "type": "commit", + "message": "auto-save 2026-05-15 17:06 (+1, ~4)", + "hash": "f3230ff", + "files_changed": 5 } ] } diff --git a/RULES.md b/RULES.md index 74048b3..d5930f1 100644 --- a/RULES.md +++ b/RULES.md @@ -21,7 +21,7 @@ - 管理后台:待定 - 服务器目录:`/opt/skg-marketing-studio` - 生产启动:`docker compose -f docker-compose.prod.yml --env-file deploy/.env.production up -d --build` -- 生产架构:`web` 容器用 Nginx 承载 Next 静态导出;未登录访问工作台跳转 `/login/`,`/api/` 通过 Nginx `auth_request` 校验 FastAPI 会话 Cookie 后反代到 `skg-marketing-api:4291`;Traefik 通过 `coolify` 外部网络接入 80/443 +- 生产架构:`web` 容器用 Nginx 承载 Next 静态导出;`/login/`、`/_next/`、`/assets/` 等登录页必需静态资源公开访问;未登录访问工作台跳转 `/login/`,`/api/` 通过 Nginx `auth_request` 校验 FastAPI 会话 Cookie 后反代到 `skg-marketing-api:4291`;Traefik 通过 `coolify` 外部网络接入 80/443 - 持久化目录:服务器 `./data/jobs` 挂载到后端 `/data/jobs` - 登录凭证:用户名写下方快捷登录;密码明文备份只放服务器 `/root/skg-marketing-studio-login.txt`,生产环境变量 `WEB_AUTH_PASSWORD` / `WEB_AUTH_SESSION_SECRET` 只放服务器 `deploy/.env.production` diff --git a/deploy/nginx.conf b/deploy/nginx.conf index 3925306..221bba3 100644 --- a/deploy/nginx.conf +++ b/deploy/nginx.conf @@ -87,6 +87,11 @@ server { try_files $uri =404; } + location /assets/ { + root /usr/share/nginx/html; + try_files $uri =404; + } + location ~* ^/(icon|apple-icon|favicon|manifest|placeholder).* { root /usr/share/nginx/html; try_files $uri =404; diff --git a/docs/source-analysis.html b/docs/source-analysis.html index b70d59d..4e8fd2a 100644 --- a/docs/source-analysis.html +++ b/docs/source-analysis.html @@ -536,7 +536,7 @@ 生产站点 https://marketing.skg.com - 公司域名已解析到 VPS 76.13.31.179。线上由既有 Coolify / Traefik 负责 HTTPS 入口,项目 web 容器用 Nginx 承载静态前端;未登录访问工作台跳转 /login//api/ 通过 auth_request 校验 FastAPI 会话 Cookie 后再反代。 + 公司域名已解析到 VPS 76.13.31.179。线上由既有 Coolify / Traefik 负责 HTTPS 入口,项目 web 容器用 Nginx 承载静态前端;/login//_next//assets/ 为公开登录页资源,未登录访问工作台跳转 /login//api/ 通过 auth_request 校验 FastAPI 会话 Cookie 后再反代。 生产部署 @@ -591,8 +591,8 @@ - - + + @@ -949,8 +949,8 @@ SubjectAsset {

问题:上一版虽然吸收了官网元素,但仍偏浅灰卡片拼接,卡通角色舞台削弱了公司级工具和高端健康硬件的质感。

-

改动:生产登录页主视觉改为真实 SKG 颈部按摩仪产品摄影卡,采用黑白/香槟金主轴、超大标题、克制胶囊状态、素材/声音/成片三段式状态栏;几何角色组件不再作为当前登录页主视觉挂载。新增本地静态图 web/public/assets/skg-g7-pro-neck-massager.png,避免远程官网图在生产登录页破图。

-

影响:web/app/login/page.tsxweb/app/globals.cssweb/public/assets/skg-g7-pro-neck-massager.pngdocs/source-analysis.html

+

改动:生产登录页主视觉改为真实 SKG 颈部按摩仪产品摄影卡,采用黑白/香槟金主轴、超大标题、克制胶囊状态、素材/声音/成片三段式状态栏;几何角色组件不再抢占主视觉,但作为 Live Studio Modules 小组件保留,继续响应鼠标眼神跟随、输入、显示密码、错误和成功状态。新增本地静态图 web/public/assets/skg-g7-pro-neck-massager.png,并在生产 Nginx 放行 /assets/,避免登录页产品图被登录保护拦截。

+

影响:web/app/login/page.tsxweb/app/globals.cssweb/public/assets/skg-g7-pro-neck-massager.pngdeploy/nginx.confRULES.mddocs/source-analysis.html

diff --git a/web/app/globals.css b/web/app/globals.css index 12de8b5..46395b4 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -296,6 +296,50 @@ font-weight: 600; line-height: 1.05; } +.login-dynamic-dock { + position: absolute; + left: 0; + bottom: 118px; + width: 252px; + border: 1px solid rgba(255, 255, 255, 0.72); + border-radius: 8px; + background: rgba(255, 255, 255, 0.64); + padding: 12px; + box-shadow: 0 22px 52px rgba(40, 40, 40, 0.12); + backdrop-filter: blur(18px); +} +.login-dynamic-dock__label { + display: block; + margin-bottom: 8px; + color: rgba(40, 40, 40, 0.46); + font-size: 11px; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} +.login-dynamic-dock .login-character-stage { + min-height: 116px; + border-color: rgba(40, 40, 40, 0.08); + background: + linear-gradient(90deg, rgba(40, 40, 40, 0.04) 1px, transparent 1px), + linear-gradient(180deg, rgba(40, 40, 40, 0.04) 1px, transparent 1px), + rgba(255, 255, 255, 0.5); + background-size: 22px 22px, 22px 22px, auto; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.84); +} +.login-dynamic-dock .login-character-stage::after { + height: 48%; + background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.7)); +} +.login-dynamic-dock .login-stage-grid { + inset: 12px; + border-color: rgba(40, 40, 40, 0.08); +} +.login-dynamic-dock .login-characters-container { + left: 50%; + bottom: 0; + transform: translateX(-50%) scale(0.28); +} .login-studio-chip { position: absolute; display: inline-flex; @@ -816,6 +860,18 @@ .login-product-caption b { font-size: 22px; } + .login-dynamic-dock { + left: 0; + bottom: 132px; + width: 210px; + padding: 10px; + } + .login-dynamic-dock .login-character-stage { + min-height: 94px; + } + .login-dynamic-dock .login-characters-container { + transform: translateX(-50%) scale(0.22); + } .login-studio-chip { font-size: 12px; } diff --git a/web/app/login/page.tsx b/web/app/login/page.tsx index 8076723..c8349e4 100644 --- a/web/app/login/page.tsx +++ b/web/app/login/page.tsx @@ -1,7 +1,7 @@ "use client" import type { FormEvent } from "react" -import { useState } from "react" +import { useEffect, useMemo, useState } from "react" import { AlertCircle, ArrowRight, @@ -14,6 +14,7 @@ import { Sparkles, UserRound, } from "lucide-react" +import { AnimatedLoginCharacters, type LoginCharacterMood } from "@/components/login/animated-login-characters" type LoginStatus = "idle" | "loading" | "success" @@ -25,11 +26,33 @@ export default function LoginPage() { const [password, setPassword] = useState("") const [remember, setRemember] = useState(true) const [showPassword, setShowPassword] = useState(false) + const [activeField, setActiveField] = useState<"username" | "password" | null>(null) const [error, setError] = useState("") const [status, setStatus] = useState("idle") + const [eyeOffset, setEyeOffset] = useState({ x: 0, y: 0 }) + + useEffect(() => { + const onPointerMove = (event: PointerEvent) => { + const centerX = window.innerWidth / 2 + const centerY = window.innerHeight / 2 + const nextX = Math.max(-1, Math.min(1, (event.clientX - centerX) / centerX)) + const nextY = Math.max(-1, Math.min(1, (event.clientY - centerY) / centerY)) + setEyeOffset({ x: nextX * 8, y: nextY * 5.5 }) + } + window.addEventListener("pointermove", onPointerMove) + return () => window.removeEventListener("pointermove", onPointerMove) + }, []) const disabled = status === "loading" || status === "success" + const mood: LoginCharacterMood = useMemo(() => { + if (status === "success") return "success" + if (error) return "error" + if (showPassword && activeField === "password") return "peek" + if (activeField || username || password) return "typing" + return "idle" + }, [activeField, error, password, showPassword, status, username]) + async function onSubmit(event: FormEvent) { event.preventDefault() setError("") @@ -95,6 +118,10 @@ export default function LoginPage() { G7 Pro Fold Neck Massager +
+ Live Studio Modules + +
Visual Asset Flow @@ -141,6 +168,8 @@ export default function LoginPage() { disabled={disabled} autoComplete="username" placeholder="请输入账号" + onFocus={() => setActiveField("username")} + onBlur={() => setActiveField(null)} onChange={(event) => { setUsername(event.target.value) if (error) setError("") @@ -160,6 +189,8 @@ export default function LoginPage() { type={showPassword ? "text" : "password"} autoComplete="current-password" placeholder="请输入密码" + onFocus={() => setActiveField("password")} + onBlur={() => setActiveField(null)} onChange={(event) => { setPassword(event.target.value) if (error) setError("")
web/app/page.tsx产品工作台主状态:jobs、activeJobId、按 job 隔离的 selectedFrames/详情面板状态、clipboard、ReactFlow 节点和边;负责打开/找回画布工作面板。
web/app/login/page.tsx生产登录页:账号密码表单、保持登录、错误/成功状态;左侧展示区改为高级产品入口页结构,使用本地 SKG 颈部按摩仪产品图、黑白/香槟金视觉、Secure Studio 胶囊和素材/声音/成片状态栏。
web/components/login/animated-login-characters.tsx登录页旧版四个几何角色组件:仍保留在代码中作为历史/备用组件,但当前生产登录页主视觉已不再挂载。
web/app/login/page.tsx生产登录页:账号密码表单、保持登录、错误/成功状态;左侧展示区改为高级产品入口页结构,使用本地 SKG 颈部按摩仪产品图、黑白/香槟金视觉、Secure Studio 胶囊和素材/声音/成片状态栏;同时保留动态角色作为 Live Studio Modules 小组件。
web/components/login/animated-login-characters.tsx登录页四个几何动态角色组件:当前以小型 Live Studio Modules 方式挂在产品区,保留鼠标眼神跟随、输入、显示密码、错误和成功状态反馈。
web/components/nodes/index.tsxDAG 节点定义:Input、VisualLab、Audio、Compose,以及画布工作面板 KeyframePanel / VideoFramePanel;旧 Keyframe/Storyboard/VideoGen 组件保留但不再挂主画布。
web/components/audio-strip.tsx底部吸附音频条:可拖拽调整高度;播放原音频时移动指针,逐个高亮英文/中文字幕节点和对应波形,并在右侧固定显示按原音频时长生成的 SKG 英文产品口播和 MiniMax 随机英文配音。
web/components/lightbox.tsx关键帧素材准备面板:清洗、统一主体候选、参考帧网格、六张主体重绘图、每帧去主体场景图、纵向 6 行产品融合镜头工作表和审核。