auto-save 2026-05-15 15:54 (~4)
This commit is contained in:
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -941,6 +941,17 @@ SubjectAsset {
|
||||
<h2>变更记录</h2>
|
||||
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
|
||||
<div class="changelog">
|
||||
<article class="change">
|
||||
<header>
|
||||
<h3>2026-05-15 · 登录页动画角色改回风格库原版几何结构</h3>
|
||||
<span class="tag rose">UI</span>
|
||||
</header>
|
||||
<div class="body">
|
||||
<p><strong>问题:</strong>首版登录页把风格库的动画角色理解成圆润小球,视觉上偏幼稚,和 <code>14 动画角色登录</code> 的原始气质不一致。</p>
|
||||
<p><strong>改动:</strong>对照风格库 <code>AnimatedCharacters.vue</code> 和 demo,把角色重写为紫色高矩形、黑色矩形、橙色半圆、黄色圆角柱的贴底几何组合;保留鼠标视线跟随,并让输入、显示密码、错误、成功状态驱动 skew、眼睛和嘴型变化。</p>
|
||||
<p><strong>影响:</strong><code>web/app/login/page.tsx</code>、<code>web/app/globals.css</code>、<code>docs/source-analysis.html</code>。</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="change">
|
||||
<header>
|
||||
<h3>2026-05-15 · 生产站点增加应用内登录页</h3>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
<div className="login-character-stage" aria-hidden="true">
|
||||
<div className="login-stage-grid" />
|
||||
{CHARACTER_IDS.map((id, index) => (
|
||||
<div className={`login-character login-character--${id}`} key={id} style={{ "--i": index } as CSSProperties}>
|
||||
<span className="login-character__gloss" />
|
||||
<span className="login-character__eye login-character__eye--left" />
|
||||
<span className="login-character__eye login-character__eye--right" />
|
||||
<span className="login-character__mouth" />
|
||||
<span className="login-character__badge" />
|
||||
<div className="login-characters-container">
|
||||
<div className="login-figure login-figure--purple">
|
||||
<span className="login-eyes login-eyes--purple">
|
||||
<span className="login-eye" />
|
||||
<span className="login-eye" />
|
||||
</span>
|
||||
<span className="login-mouth login-mouth--purple" />
|
||||
</div>
|
||||
))}
|
||||
<div className="login-figure login-figure--black">
|
||||
<span className="login-eyes login-eyes--black">
|
||||
<span className="login-eye login-eye--small" />
|
||||
<span className="login-eye login-eye--small" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="login-figure login-figure--orange">
|
||||
<span className="login-eyes login-eyes--orange">
|
||||
<span className="login-pupil" />
|
||||
<span className="login-pupil" />
|
||||
</span>
|
||||
<span className="login-mouth login-mouth--orange" />
|
||||
</div>
|
||||
<div className="login-figure login-figure--yellow">
|
||||
<span className="login-eyes login-eyes--yellow">
|
||||
<span className="login-pupil" />
|
||||
<span className="login-pupil" />
|
||||
</span>
|
||||
<span className="login-yellow-mouth">
|
||||
<svg width="80" height="20" viewBox="0 0 80 20">
|
||||
<path d="M0 10 Q10 10, 20 10 Q30 10, 40 10 Q50 10, 60 10 Q70 10, 80 10" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-3">
|
||||
|
||||
Reference in New Issue
Block a user