diff --git a/.memory/worklog.json b/.memory/worklog.json index ee25b31..778f2c0 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -1,12 +1,5 @@ { "entries": [ - { - "files_changed": 11, - "hash": "abeff42", - "message": "auto-save 2026-05-14 00:25 (+6, ~5)", - "ts": "2026-05-14T00:26:10+08:00", - "type": "commit" - }, { "files_changed": 1, "hash": "5c9c80e", @@ -3251,6 +3244,13 @@ "type": "session-heartbeat", "message": "Codex 会话活跃 · 最近命令:codex · 1 项未提交变更 · 最近提交:auto-save 2026-05-15 15:43 (~1)", "files_changed": 1 + }, + { + "ts": "2026-05-15T15:49:01+08:00", + "type": "commit", + "message": "auto-save 2026-05-15 15:48 (~1)", + "hash": "beeed42", + "files_changed": 1 } ] } diff --git a/docs/source-analysis.html b/docs/source-analysis.html index 68856b1..e06becb 100644 --- a/docs/source-analysis.html +++ b/docs/source-analysis.html @@ -941,6 +941,17 @@ SubjectAsset {

变更记录

这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。

+
+
+

2026-05-15 · 登录页动画角色改回风格库原版几何结构

+ UI +
+
+

问题:首版登录页把风格库的动画角色理解成圆润小球,视觉上偏幼稚,和 14 动画角色登录 的原始气质不一致。

+

改动:对照风格库 AnimatedCharacters.vue 和 demo,把角色重写为紫色高矩形、黑色矩形、橙色半圆、黄色圆角柱的贴底几何组合;保留鼠标视线跟随,并让输入、显示密码、错误、成功状态驱动 skew、眼睛和嘴型变化。

+

影响:web/app/login/page.tsxweb/app/globals.cssdocs/source-analysis.html

+
+

2026-05-15 · 生产站点增加应用内登录页

diff --git a/web/app/globals.css b/web/app/globals.css index 3acc648..b35b201 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -179,186 +179,295 @@ background-size: 34px 34px, 34px 34px, auto; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 30px 70px rgba(0, 0, 0, 0.35); } +.login-character-stage::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 40%; + background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.28)); + pointer-events: none; +} .login-stage-grid { position: absolute; inset: 26px; border: 1px dashed rgba(255, 255, 255, 0.1); border-radius: 8px; } -.login-character { - --character-fill: #6c3ff5; - --character-shadow: rgba(108, 63, 245, 0.34); +.login-characters-container { position: absolute; - width: 128px; - height: 128px; - border: 2px solid rgba(255, 255, 255, 0.22); - background: var(--character-fill); - box-shadow: 0 26px 45px var(--character-shadow), inset 0 14px 24px rgba(255, 255, 255, 0.18); + z-index: 1; + left: 50%; + bottom: 0; + width: 550px; + height: 400px; + transform: translateX(-50%) scale(0.74); + transform-origin: bottom center; + animation: login-stage-breathe 7s ease-in-out infinite; +} +.login-figure { + position: absolute; + bottom: 0; + transform-origin: bottom center; transition: - transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), - border-radius 0.35s cubic-bezier(0.22, 1, 0.36, 1), - background 0.25s ease; - animation: login-float 5.8s ease-in-out infinite; - animation-delay: calc(var(--i) * -0.55s); + transform 0.7s cubic-bezier(0.4, 0, 0.2, 1), + height 0.55s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.2s ease; + will-change: transform; + filter: drop-shadow(0 28px 34px rgba(0, 0, 0, 0.32)); } -.login-character--pilot { - left: 10%; - top: 18%; - border-radius: 34% 54% 46% 42%; -} -.login-character--lens { - --character-fill: #ff9b6b; - --character-shadow: rgba(255, 155, 107, 0.28); - right: 13%; - top: 10%; - width: 112px; - height: 112px; - border-radius: 50%; -} -.login-character--spark { - --character-fill: #e8d754; - --character-shadow: rgba(232, 215, 84, 0.22); - left: 28%; - bottom: 12%; - width: 106px; - height: 106px; - border-radius: 28% 50% 32% 52%; - color: #111; -} -.login-character--keeper { - --character-fill: #2d2d2d; - --character-shadow: rgba(0, 0, 0, 0.5); - right: 28%; - bottom: 16%; - width: 138px; - height: 138px; - border-radius: 46% 38% 54% 34%; -} -.login-character__gloss { - position: absolute; - left: 20%; - top: 16%; - width: 34%; - height: 18%; - border-radius: 999px; - background: rgba(255, 255, 255, 0.22); - transform: rotate(-18deg); -} -.login-character__eye { - position: absolute; - top: 39%; - width: 20px; - height: 20px; - border-radius: 999px; - background: rgba(255, 255, 255, 0.95); - box-shadow: inset 0 -2px 4px rgba(0, 0, 0, 0.15); -} -.login-character--spark .login-character__eye { - background: rgba(17, 17, 17, 0.92); -} -.login-character__eye::after { +.login-figure::after { content: ""; position: absolute; - left: 6px; - top: 6px; - width: 8px; - height: 8px; + inset: 0; + background: linear-gradient(105deg, rgba(255, 255, 255, 0.16), transparent 34%, rgba(0, 0, 0, 0.12)); + pointer-events: none; +} +.login-figure--purple { + left: 70px; + width: 180px; + height: 400px; + z-index: 1; + background: #6c3ff5; + border-radius: 0; +} +.login-figure--black { + left: 240px; + width: 120px; + height: 310px; + z-index: 2; + background: #2d2d2d; + border-radius: 0; +} +.login-figure--orange { + left: 0; + width: 240px; + height: 150px; + z-index: 3; + background: #ff9b6b; + border-radius: 120px 120px 0 0; +} +.login-figure--yellow { + left: 310px; + width: 140px; + height: 230px; + z-index: 4; + background: #e8d754; + border-radius: 70px 70px 0 0; +} +.login-eyes { + position: absolute; + z-index: 2; + display: flex; + transition: + left 0.35s cubic-bezier(0.4, 0, 0.2, 1), + top 0.35s cubic-bezier(0.4, 0, 0.2, 1); +} +.login-eyes--purple { + left: 75px; + top: 25px; + gap: 32px; +} +.login-eyes--black { + left: 26px; + top: 32px; + gap: 24px; +} +.login-eyes--orange { + left: 112px; + top: 60px; + gap: 32px; +} +.login-eyes--yellow { + left: 52px; + top: 40px; + gap: 24px; +} +.login-eye { + position: relative; + width: 18px; + height: 18px; border-radius: 999px; - background: #111; + background: white; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); +} +.login-eye--small { + width: 16px; + height: 16px; +} +.login-eye::after, +.login-pupil::after { + content: ""; + position: absolute; + left: 50%; + top: 50%; + width: 7px; + height: 7px; + border-radius: 50%; + background: #2d2d2d; transform: translate(var(--eye-x), var(--eye-y)); transition: transform 0.08s linear, opacity 0.2s ease; } -.login-character--spark .login-character__eye::after { - background: #fff; +.login-eye::after { + margin: -3.5px 0 0 -3.5px; } -.login-character__eye--left { - left: 28%; -} -.login-character__eye--right { - right: 28%; -} -.login-character__mouth { - position: absolute; - left: 50%; - top: 62%; - width: 30px; +.login-pupil { + position: relative; + width: 12px; height: 12px; - border-bottom: 3px solid rgba(255, 255, 255, 0.9); - border-radius: 0 0 999px 999px; - transform: translateX(-50%); - transition: transform 0.2s ease, height 0.2s ease, border-radius 0.2s ease; + border-radius: 50%; } -.login-character--spark .login-character__mouth { - border-bottom-color: #111; +.login-pupil::after { + width: 12px; + height: 12px; + margin: -6px 0 0 -6px; } -.login-character__badge { +.login-mouth { position: absolute; - right: 15%; - bottom: 14%; - width: 16px; - height: 16px; - border-radius: 4px; - background: rgba(255, 255, 255, 0.35); - transform: rotate(12deg); + z-index: 2; + background: #2d2d2d; + transition: + width 0.35s cubic-bezier(0.4, 0, 0.2, 1), + height 0.35s cubic-bezier(0.4, 0, 0.2, 1), + border-radius 0.35s cubic-bezier(0.4, 0, 0.2, 1), + transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), + left 0.35s cubic-bezier(0.4, 0, 0.2, 1), + top 0.35s cubic-bezier(0.4, 0, 0.2, 1); } -.login-page[data-mood="typing"] .login-character { - transform: translateY(-8px) scale(1.02); +.login-mouth--purple { + left: 97px; + top: 57px; + width: 24px; + height: 8px; + border-radius: 0 0 12px 12px; } -.login-page[data-mood="peek"] .login-character__eye::after { - opacity: 0.25; +.login-mouth--orange { + left: 126px; + top: 92px; + width: 26px; + height: 13px; + border-radius: 0 0 13px 13px; } -.login-page[data-mood="peek"] .login-character--keeper { - border-radius: 50% 50% 40% 40%; - transform: translateY(-10px) scale(1.05); +.login-yellow-mouth { + position: absolute; + z-index: 2; + left: 40px; + top: 88px; } -.login-page[data-mood="error"] .login-character { - animation: login-shake 0.28s ease-in-out 2; +.login-yellow-mouth path { + stroke: #2d2d2d; + stroke-width: 3; + fill: none; + stroke-linecap: round; } -.login-page[data-mood="error"] .login-character__mouth { +.login-page[data-mood="typing"] .login-figure--purple { + height: 430px; + transform: skewX(-10deg) translateX(36px); +} +.login-page[data-mood="typing"] .login-figure--black { + transform: skewX(7deg) translateX(6px); +} +.login-page[data-mood="typing"] .login-figure--orange { + transform: skewX(-5deg); +} +.login-page[data-mood="typing"] .login-figure--yellow { + transform: skewX(4deg); +} +.login-page[data-mood="typing"] .login-mouth--purple { + width: 7px; + height: 32px; + border-radius: 0; + transform: translate(14px, -28px) skewX(10deg); +} +.login-page[data-mood="typing"] .login-mouth--orange { + width: 14px; height: 14px; - border-top: 3px solid rgba(255, 255, 255, 0.9); - border-bottom: 0; - border-radius: 999px 999px 0 0; - transform: translate(-50%, 5px); + border-radius: 50%; + transform: translateX(6px); } -.login-page[data-mood="success"] .login-character { - transform: translateY(-14px) scale(1.06); +.login-page[data-mood="peek"] .login-figure--purple, +.login-page[data-mood="peek"] .login-figure--black { + transform: skewX(0deg) translateY(-10px); } -.login-page[data-mood="success"] .login-character__mouth { +.login-page[data-mood="peek"] .login-eyes--purple { + left: 50px; + top: 20px; +} +.login-page[data-mood="peek"] .login-eyes--black { + left: 10px; + top: 28px; +} +.login-page[data-mood="peek"] .login-eyes--orange { + left: 80px; + top: 55px; +} +.login-page[data-mood="peek"] .login-eyes--yellow { + left: 20px; + top: 35px; +} +.login-page[data-mood="peek"] .login-eye::after, +.login-page[data-mood="peek"] .login-pupil::after { + transform: translate(-5px, -4px); +} +.login-page[data-mood="error"] .login-characters-container { + animation: login-stage-breathe 7s ease-in-out infinite, login-shake 0.28s ease-in-out 2; +} +.login-page[data-mood="error"] .login-eye { + height: 10px; + margin-top: 4px; +} +.login-page[data-mood="error"] .login-mouth--purple, +.login-page[data-mood="error"] .login-mouth--orange { + border-radius: 12px 12px 0 0; + transform: translateY(10px); +} +.login-page[data-mood="error"] .login-yellow-mouth path { + d: path("M0 10 Q10 2, 20 10 Q30 18, 40 10 Q50 2, 60 10 Q70 18, 80 10"); +} +.login-page[data-mood="success"] .login-characters-container { + animation: login-stage-success 0.75s cubic-bezier(0.34, 1.56, 0.64, 1) both; +} +.login-page[data-mood="success"] .login-mouth--purple { + width: 30px; + height: 16px; + border-radius: 0 0 15px 15px; +} +.login-page[data-mood="success"] .login-mouth--orange { + width: 32px; height: 18px; + border-radius: 0 0 16px 16px; } -@keyframes login-float { +.login-page[data-mood="success"] .login-yellow-mouth path { + d: path("M0 6 Q20 18, 40 18 Q60 18, 80 6"); +} +@keyframes login-stage-breathe { 0%, 100% { translate: 0 0; } - 50% { translate: 0 -12px; } + 50% { translate: 0 -8px; } } @keyframes login-shake { 0%, 100% { translate: 0 0; } 33% { translate: -5px 0; } 66% { translate: 5px 0; } } +@keyframes login-stage-success { + 0% { translate: 0 0; } + 45% { translate: 0 -22px; } + 100% { translate: 0 -8px; } +} @media (max-width: 720px) { .login-character-stage { - min-height: 260px; + min-height: 270px; } - .login-character { - width: 96px; - height: 96px; - } - .login-character--lens, - .login-character--spark { - width: 84px; - height: 84px; - } - .login-character--keeper { - width: 102px; - height: 102px; + .login-characters-container { + transform: translateX(-50%) scale(0.58); } } @media (prefers-reduced-motion: reduce) { - .login-character { - animation: none; - } - .login-page[data-mood="error"] .login-character { + .login-characters-container, + .login-page[data-mood="error"] .login-characters-container, + .login-page[data-mood="success"] .login-characters-container { animation: none; } } diff --git a/web/app/login/page.tsx b/web/app/login/page.tsx index 393a226..412580a 100644 --- a/web/app/login/page.tsx +++ b/web/app/login/page.tsx @@ -17,8 +17,6 @@ import { type LoginStatus = "idle" | "loading" | "success" type LoginMood = "idle" | "typing" | "peek" | "error" | "success" -const CHARACTER_IDS = ["pilot", "lens", "spark", "keeper"] as const - export default function LoginPage() { const [username, setUsername] = useState("") const [password, setPassword] = useState("") @@ -119,15 +117,39 @@ export default function LoginPage() {