auto-save 2026-05-15 16:10 (+1, ~4)
This commit is contained in:
@@ -362,84 +362,84 @@
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
.login-page[data-mood="typing"] .login-figure--purple {
|
||||
.login-character-stage[data-mood="typing"] .login-figure--purple {
|
||||
height: 430px;
|
||||
transform: skewX(-10deg) translateX(36px);
|
||||
}
|
||||
.login-page[data-mood="typing"] .login-figure--black {
|
||||
.login-character-stage[data-mood="typing"] .login-figure--black {
|
||||
transform: skewX(7deg) translateX(6px);
|
||||
}
|
||||
.login-page[data-mood="typing"] .login-figure--orange {
|
||||
.login-character-stage[data-mood="typing"] .login-figure--orange {
|
||||
transform: skewX(-5deg);
|
||||
}
|
||||
.login-page[data-mood="typing"] .login-figure--yellow {
|
||||
.login-character-stage[data-mood="typing"] .login-figure--yellow {
|
||||
transform: skewX(4deg);
|
||||
}
|
||||
.login-page[data-mood="typing"] .login-mouth--purple {
|
||||
.login-character-stage[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 {
|
||||
.login-character-stage[data-mood="typing"] .login-mouth--orange {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
transform: translateX(6px);
|
||||
}
|
||||
.login-page[data-mood="peek"] .login-figure--purple,
|
||||
.login-page[data-mood="peek"] .login-figure--black {
|
||||
.login-character-stage[data-mood="peek"] .login-figure--purple,
|
||||
.login-character-stage[data-mood="peek"] .login-figure--black {
|
||||
transform: skewX(0deg) translateY(-10px);
|
||||
}
|
||||
.login-page[data-mood="peek"] .login-eyes--purple {
|
||||
.login-character-stage[data-mood="peek"] .login-eyes--purple {
|
||||
left: 40px;
|
||||
top: 14px;
|
||||
}
|
||||
.login-page[data-mood="peek"] .login-eyes--black {
|
||||
.login-character-stage[data-mood="peek"] .login-eyes--black {
|
||||
left: 2px;
|
||||
top: 20px;
|
||||
}
|
||||
.login-page[data-mood="peek"] .login-eyes--orange {
|
||||
.login-character-stage[data-mood="peek"] .login-eyes--orange {
|
||||
left: 68px;
|
||||
top: 48px;
|
||||
}
|
||||
.login-page[data-mood="peek"] .login-eyes--yellow {
|
||||
.login-character-stage[data-mood="peek"] .login-eyes--yellow {
|
||||
left: 10px;
|
||||
top: 28px;
|
||||
}
|
||||
.login-page[data-mood="peek"] .login-eye::after,
|
||||
.login-page[data-mood="peek"] .login-pupil::after {
|
||||
.login-character-stage[data-mood="peek"] .login-eye::after,
|
||||
.login-character-stage[data-mood="peek"] .login-pupil::after {
|
||||
transform: translate(-8px, -6px);
|
||||
}
|
||||
.login-page[data-mood="error"] .login-characters-container {
|
||||
.login-character-stage[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 {
|
||||
.login-character-stage[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 {
|
||||
.login-character-stage[data-mood="error"] .login-mouth--purple,
|
||||
.login-character-stage[data-mood="error"] .login-mouth--orange {
|
||||
border-radius: 12px 12px 0 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
.login-page[data-mood="error"] .login-yellow-mouth path {
|
||||
.login-character-stage[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 {
|
||||
.login-character-stage[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 {
|
||||
.login-character-stage[data-mood="success"] .login-mouth--purple {
|
||||
width: 30px;
|
||||
height: 16px;
|
||||
border-radius: 0 0 15px 15px;
|
||||
}
|
||||
.login-page[data-mood="success"] .login-mouth--orange {
|
||||
.login-character-stage[data-mood="success"] .login-mouth--orange {
|
||||
width: 32px;
|
||||
height: 18px;
|
||||
border-radius: 0 0 16px 16px;
|
||||
}
|
||||
.login-page[data-mood="success"] .login-yellow-mouth path {
|
||||
.login-character-stage[data-mood="success"] .login-yellow-mouth path {
|
||||
d: path("M0 6 Q20 18, 40 18 Q60 18, 80 6");
|
||||
}
|
||||
@keyframes login-stage-breathe {
|
||||
@@ -466,8 +466,8 @@
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.login-characters-container,
|
||||
.login-page[data-mood="error"] .login-characters-container,
|
||||
.login-page[data-mood="success"] .login-characters-container {
|
||||
.login-character-stage[data-mood="error"] .login-characters-container,
|
||||
.login-character-stage[data-mood="success"] .login-characters-container {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import type { CSSProperties, FormEvent } from "react"
|
||||
import type { FormEvent } from "react"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import {
|
||||
AlertCircle,
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
Sparkles,
|
||||
UserRound,
|
||||
} from "lucide-react"
|
||||
import { AnimatedLoginCharacters, type LoginCharacterMood } from "@/components/login/animated-login-characters"
|
||||
|
||||
type LoginStatus = "idle" | "loading" | "success"
|
||||
type LoginMood = "idle" | "typing" | "peek" | "error" | "success"
|
||||
|
||||
export default function LoginPage() {
|
||||
const [username, setUsername] = useState("")
|
||||
@@ -39,7 +39,7 @@ export default function LoginPage() {
|
||||
return () => window.removeEventListener("pointermove", onPointerMove)
|
||||
}, [])
|
||||
|
||||
const mood: LoginMood = useMemo(() => {
|
||||
const mood: LoginCharacterMood = useMemo(() => {
|
||||
if (status === "success") return "success"
|
||||
if (error) return "error"
|
||||
if (showPassword && activeField === "password") return "peek"
|
||||
@@ -87,13 +87,6 @@ export default function LoginPage() {
|
||||
return (
|
||||
<main
|
||||
className="login-page relative min-h-screen overflow-hidden px-5 py-6 text-white sm:px-8 lg:px-10"
|
||||
data-mood={mood}
|
||||
style={
|
||||
{
|
||||
"--eye-x": `${eyeOffset.x}px`,
|
||||
"--eye-y": `${eyeOffset.y}px`,
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
<div className="relative z-10 mx-auto flex min-h-[calc(100vh-3rem)] w-full max-w-7xl items-center">
|
||||
<div className="grid w-full gap-5 lg:grid-cols-[minmax(0,1.08fr)_minmax(380px,460px)] lg:items-stretch">
|
||||
@@ -115,42 +108,7 @@ export default function LoginPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="login-character-stage" aria-hidden="true">
|
||||
<div className="login-stage-grid" />
|
||||
<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>
|
||||
<AnimatedLoginCharacters mood={mood} eyeOffset={eyeOffset} />
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-3">
|
||||
{[
|
||||
|
||||
Reference in New Issue
Block a user