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:
2026-06-06 00:10:16 -04:00
parent b097d58f13
commit a01eb1ed4f
3 changed files with 78 additions and 1 deletions
+6
View File
@@ -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) {
+71
View File
@@ -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>
);
}
+1 -1
View File
@@ -1 +1 @@
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/AnimatedAvatar.tsx","./src/components/BackgroundScene.tsx","./src/components/ChatBubble.tsx","./src/components/Clock.tsx","./src/components/KiraAvatar.tsx","./src/components/Live2DCat.tsx","./src/components/Live2DStage.tsx","./src/components/MusicPlayer.tsx","./src/components/Notes.tsx","./src/components/Particles.tsx","./src/components/PetZone.tsx","./src/components/TaskList.tsx","./src/components/Timer.tsx","./src/components/Toolbar.tsx","./src/components/Wardrobe.tsx","./src/components/WelcomeScreen.tsx","./src/components/WhiteNoise.tsx","./src/components/scenes.ts","./src/hooks/useConversation.ts","./src/types/index.ts"],"version":"6.0.3"}
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/AnimatedAvatar.tsx","./src/components/BackgroundScene.tsx","./src/components/ChatBubble.tsx","./src/components/Clock.tsx","./src/components/KiraAvatar.tsx","./src/components/Live2DCat.tsx","./src/components/Live2DStage.tsx","./src/components/LoginScreen.tsx","./src/components/MusicPlayer.tsx","./src/components/Notes.tsx","./src/components/Particles.tsx","./src/components/PetZone.tsx","./src/components/TaskList.tsx","./src/components/Timer.tsx","./src/components/Toolbar.tsx","./src/components/Wardrobe.tsx","./src/components/WelcomeScreen.tsx","./src/components/WhiteNoise.tsx","./src/components/scenes.ts","./src/hooks/useConversation.ts","./src/types/index.ts"],"version":"6.0.3"}