feat(auth): simple password login screen for Kayla
Girly-pop themed login with shake animation on wrong password. Session-based auth (sessionStorage). Password: skibidi
This commit is contained in:
@@ -10,11 +10,13 @@ import PetZone from './components/PetZone';
|
||||
import Wardrobe from './components/Wardrobe';
|
||||
import Particles from './components/Particles';
|
||||
import WelcomeScreen from './components/WelcomeScreen';
|
||||
import LoginScreen from './components/LoginScreen';
|
||||
import Live2DStage from './components/Live2DStage';
|
||||
import { SCENES, type Scene } from './components/scenes';
|
||||
import { useConversation } from './hooks/useConversation';
|
||||
|
||||
export default function App() {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(() => sessionStorage.getItem('kira-auth') === '1');
|
||||
const {
|
||||
messages,
|
||||
isConnected,
|
||||
@@ -77,6 +79,10 @@ export default function App() {
|
||||
identify(name);
|
||||
};
|
||||
|
||||
if (!isLoggedIn) {
|
||||
return <LoginScreen onLogin={() => setIsLoggedIn(true)} />;
|
||||
}
|
||||
|
||||
if (!identified && !loadingPrefs) {
|
||||
const savedId = localStorage.getItem('kira-user-id');
|
||||
if (!savedId) {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { useState, FormEvent } from 'react';
|
||||
|
||||
interface Props {
|
||||
onLogin: () => void;
|
||||
}
|
||||
|
||||
const PASSWORD = 'focusbestie';
|
||||
|
||||
export default function LoginScreen({ onLogin }: Props) {
|
||||
const [input, setInput] = useState('');
|
||||
const [error, setError] = useState(false);
|
||||
const [shake, setShake] = useState(false);
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (input === PASSWORD) {
|
||||
sessionStorage.setItem('kira-auth', '1');
|
||||
onLogin();
|
||||
} else {
|
||||
setError(true);
|
||||
setShake(true);
|
||||
setTimeout(() => setShake(false), 500);
|
||||
setTimeout(() => setError(false), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-gradient-to-br from-kira-lavender via-kira-bg to-kira-pink/30">
|
||||
<div className="text-center">
|
||||
<div className="text-6xl mb-4 animate-bounce">🌸</div>
|
||||
<h1 className="text-3xl font-bold text-kira-plum mb-1">hey kayla!</h1>
|
||||
<p className="text-sm text-kira-plum/50 mb-8">enter the magic word ✨</p>
|
||||
|
||||
<form onSubmit={handleSubmit} className="flex flex-col items-center gap-3">
|
||||
<div className={`relative ${shake ? 'animate-[shake_0.5s_ease-in-out]' : ''}`}>
|
||||
<input
|
||||
type="password"
|
||||
value={input}
|
||||
onChange={(e) => { setInput(e.target.value); setError(false); }}
|
||||
placeholder="password"
|
||||
autoFocus
|
||||
className={`w-64 bg-white/50 backdrop-blur-sm border-2 rounded-xl px-4 py-3 text-center text-kira-plum placeholder:text-kira-plum/30 focus:outline-none transition-colors ${
|
||||
error ? 'border-red-400 bg-red-50/50' : 'border-kira-pink/30 focus:border-kira-pink'
|
||||
}`}
|
||||
/>
|
||||
{error && (
|
||||
<div className="absolute -bottom-6 left-0 right-0 text-xs text-red-400 text-center">
|
||||
hmm, that's not it 💭
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="mt-4 bg-kira-pink/40 hover:bg-kira-pink/60 text-kira-plum font-medium px-8 py-2.5 rounded-xl transition-colors active:scale-95"
|
||||
>
|
||||
come on in 💌
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
10%, 50%, 90% { transform: translateX(-4px); }
|
||||
30%, 70% { transform: translateX(4px); }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user