feat(ui): center avatar as hero, ~1/3 viewport height; tools grid below

- Avatar now centered in its own row above the tools grid (was crammed in column 1)
- KiraAvatar container: min-height 33vh, canvas up to 500px wide
- Tools reorganized into 4 columns below: Chat, Timer+Music, Notes+Noise, Clock+Pets+Wardrobe
- WelcomeScreen restored to full (not compact) for first-time users
This commit is contained in:
2026-06-05 09:03:32 -04:00
parent 92250a668b
commit baaa89756f
2 changed files with 36 additions and 30 deletions
+31 -26
View File
@@ -80,7 +80,7 @@ export default function App() {
if (!identified && !loadingPrefs) { if (!identified && !loadingPrefs) {
const savedId = localStorage.getItem('kira-user-id'); const savedId = localStorage.getItem('kira-user-id');
if (!savedId) { if (!savedId) {
return <WelcomeScreen onComplete={handleWelcome} isCompact />; return <WelcomeScreen onComplete={handleWelcome} />;
} }
// Has saved ID but not identified yet — show welcome with their name // Has saved ID but not identified yet — show welcome with their name
return ( return (
@@ -121,37 +121,29 @@ export default function App() {
<Toolbar currentScene={currentSceneId} onSceneChange={handleSceneChange} /> <Toolbar currentScene={currentSceneId} onSceneChange={handleSceneChange} />
</div> </div>
{/* Main grid */} {/* Hero: Avatar centered, ~1/3 of viewport */}
<div className="flex-1 overflow-y-auto px-4 py-4 scrollbar-thin"> <div className="flex-none flex justify-center py-4 px-4">
<div className="w-full max-w-md">
<KiraAvatar
isSpeaking={isKiraSpeaking}
isListening={isRecording}
outfit={currentOutfit}
accessory={currentAcc}
onTalkToggle={handleTalkToggle}
/>
</div>
</div>
{/* Tools grid below the avatar */}
<div className="flex-1 overflow-y-auto px-4 pb-4 scrollbar-thin">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 max-w-7xl mx-auto"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 max-w-7xl mx-auto">
{/* Column 1: Kira + Clock */} {/* Column 1: Chat + Text Input */}
<div className="space-y-4">
<Clock />
<KiraAvatar
isSpeaking={isKiraSpeaking}
isListening={isRecording}
outfit={currentOutfit}
accessory={currentAcc}
onTalkToggle={handleTalkToggle}
/>
</div>
{/* Column 2: Timer + Music + Notes + WhiteNoise */}
<div className="space-y-4">
<Timer />
<MusicPlayer />
<Notes />
<WhiteNoise />
</div>
{/* Column 3: Chat + Text Input */}
<div className="space-y-4"> <div className="space-y-4">
<ChatBubble messages={messages} isKiraSpeaking={isKiraSpeaking} userName={userName} livePartial={livePartial} /> <ChatBubble messages={messages} isKiraSpeaking={isKiraSpeaking} userName={userName} livePartial={livePartial} />
{/* Text input fallback */} {/* Text input fallback */}
<div className="glass-card p-3"> <div className="glass-card p-3">
{/* Subtle greeting */}
<div className="text-[10px] text-kira-plum/30 mb-2"> <div className="text-[10px] text-kira-plum/30 mb-2">
hey {userName} hey {userName}
</div> </div>
@@ -174,8 +166,21 @@ export default function App() {
</div> </div>
</div> </div>
{/* Column 4: Cats + Wardrobe */} {/* Column 2: Timer + Music */}
<div className="space-y-4"> <div className="space-y-4">
<Timer />
<MusicPlayer />
</div>
{/* Column 3: Notes + White Noise */}
<div className="space-y-4">
<Notes />
<WhiteNoise />
</div>
{/* Column 4: Cats + Wardrobe + Clock */}
<div className="space-y-4">
<Clock />
<PetZone /> <PetZone />
<Wardrobe onOutfitChange={handleOutfitChange} onAccessoryChange={handleAccessoryChange} /> <Wardrobe onOutfitChange={handleOutfitChange} onAccessoryChange={handleAccessoryChange} />
</div> </div>
+5 -4
View File
@@ -55,8 +55,9 @@ export default function KiraAvatar(props: Props) {
// Cast needed due to pixi-live2d-display expecting older Ticker type // Cast needed due to pixi-live2d-display expecting older Ticker type
(Live2DModel as any).registerTicker(Ticker as any); (Live2DModel as any).registerTicker(Ticker as any);
// Responsive sizing // Responsive sizing — fill the container, target ~1/3 viewport
const size = Math.min(container.clientWidth || 260, 260); const containerW = container.clientWidth || 400;
const size = Math.min(containerW, 500);
const app = new Application({ const app = new Application({
width: size, width: size,
height: size * 1.25, height: size * 1.25,
@@ -198,12 +199,12 @@ export default function KiraAvatar(props: Props) {
}, [props.accessory, live2dReady]); }, [props.accessory, live2dReady]);
return ( return (
<div className="glass-card p-4 flex flex-col items-center" style={{ minHeight: 360 }}> <div className="glass-card p-6 flex flex-col items-center w-full" style={{ minHeight: '33vh' }}>
{/* Live2D canvas */} {/* Live2D canvas */}
<div <div
ref={canvasRef} ref={canvasRef}
className={`relative w-full ${live2dReady ? 'block' : 'hidden'}`} className={`relative w-full ${live2dReady ? 'block' : 'hidden'}`}
style={{ maxWidth: 260, height: 325 }} style={{ maxWidth: 500, height: '30vh', minHeight: 250 }}
/> />
{/* SVG fallback */} {/* SVG fallback */}