ui(gui): 💄 Improve sprite stream UI by updating App and SpriteStream components, API interactions, and global styles for better functionality and user experience

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-03-26 11:38:32 -07:00
parent 24bb015e96
commit c27abb5ef9
5 changed files with 46 additions and 148 deletions

View file

@ -17,6 +17,9 @@ function DashboardOrTheater(): ReactElement {
}
export function App(): ReactElement {
const [params] = useSearchParams()
const isTheater = params.get('spriteTheater') === 'true'
return (
<div
style={{
@ -27,38 +30,40 @@ export function App(): ReactElement {
}}
>
<SpriteStream />
<nav
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 24px',
height: 48,
background: colors.surface,
borderBottom: `1px solid ${colors.accent}`,
}}
>
<Link
to="/"
{!isTheater && (
<nav
style={{
color: colors.highlight,
textDecoration: 'none',
fontSize: 15,
fontWeight: 700,
letterSpacing: '0.3px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 24px',
height: 48,
background: colors.surface,
borderBottom: `1px solid ${colors.accent}`,
}}
>
Sprite Review
</Link>
<div style={{ display: 'flex', gap: 16 }}>
<Link to="/?spriteTheater=true" style={{ color: colors.muted, textDecoration: 'none', fontSize: 13 }}>
Theater
<Link
to="/"
style={{
color: colors.highlight,
textDecoration: 'none',
fontSize: 15,
fontWeight: 700,
letterSpacing: '0.3px',
}}
>
Sprite Review
</Link>
<Link to="/review" style={{ color: colors.muted, textDecoration: 'none', fontSize: 13 }}>
Review
</Link>
</div>
</nav>
<div style={{ display: 'flex', gap: 16 }}>
<Link to="/?spriteTheater=true" style={{ color: colors.muted, textDecoration: 'none', fontSize: 13 }}>
Theater
</Link>
<Link to="/review" style={{ color: colors.muted, textDecoration: 'none', fontSize: 13 }}>
Review
</Link>
</div>
</nav>
)}
<Routes>
<Route path="/" element={<DashboardOrTheater />} />
<Route path="/theater" element={<SpriteTheaterPage />} />

View file

@ -7,7 +7,6 @@ import { colors } from './pages/theme'
const MAX_SPRITES = 60
const STREAM_HEIGHT = 110
const SPRITE_SIZE = 80
const THEATER_SPRITE_SIZE = 120
const CARD_GAP = 16
const CARD_TOTAL = SPRITE_SIZE + CARD_GAP
const SSE_RETRY_DELAYS = [5000, 10000, 20000, 30000]
@ -309,110 +308,6 @@ function ControlButton({
)
}
function TheaterMode({
sprites,
newIds,
scoredIds,
onNavigate,
onClose,
}: {
sprites: RecentVariant[]
newIds: Set<number>
scoredIds: Set<number>
onNavigate: (id: string) => void
onClose: () => void
}): ReactNode {
useEffect((): (() => void) => {
const handler = (e: KeyboardEvent): void => {
if (e.key === 'Escape') onClose()
}
window.addEventListener('keydown', handler)
return (): void => window.removeEventListener('keydown', handler)
}, [onClose])
return (
<div
style={{
position: 'fixed',
inset: 0,
zIndex: 900,
background: colors.bg + 'f8',
backdropFilter: 'blur(8px)',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '12px 24px',
borderBottom: `1px solid ${colors.accent}44`,
flexShrink: 0,
}}
>
<span style={{ color: colors.text, fontSize: 14, fontWeight: 600 }}>
Sprite Theater
<span style={{ color: colors.muted, fontWeight: 400, marginLeft: 8, fontSize: 12 }}>
{sprites.length} sprites
</span>
</span>
<button
onClick={onClose}
style={{
background: 'none',
border: `1px solid ${colors.accent}`,
color: colors.muted,
cursor: 'pointer',
fontSize: 12,
padding: '4px 14px',
borderRadius: 6,
fontWeight: 600,
}}
onMouseEnter={(e): void => {
e.currentTarget.style.color = colors.text
e.currentTarget.style.borderColor = colors.muted
}}
onMouseLeave={(e): void => {
e.currentTarget.style.color = colors.muted
e.currentTarget.style.borderColor = colors.accent
}}
>
ESC to close
</button>
</div>
<div style={{ flex: 1, overflow: 'auto', padding: '24px 32px' }}>
<div
style={{
display: 'flex',
flexWrap: 'wrap',
gap: 16,
justifyContent: 'flex-start',
alignContent: 'flex-start',
}}
>
{sprites.map((variant, i) => (
<div
key={variant.variant_id}
style={{ animation: `theaterFadeIn 0.4s ease-out ${i * 0.03}s both` }}
>
<SpriteCard
variant={variant}
isNew={newIds.has(variant.variant_id)}
justScored={scoredIds.has(variant.variant_id)}
onClick={(): void => onNavigate(variant.sprite_id)}
size={THEATER_SPRITE_SIZE}
/>
</div>
))}
</div>
</div>
</div>
)
}
export function SpriteStream(): ReactNode {
const navigate = useNavigate()
@ -642,22 +537,10 @@ export function SpriteStream(): ReactNode {
}
`
// When theater mode is active, SpriteTheaterPage handles the full view.
// SpriteStream hides itself to avoid rendering on top.
if (theater) {
return (
<>
<style>{keyframeStyles}</style>
<TheaterMode
sprites={[...sprites].reverse()}
newIds={newIds}
scoredIds={scoredIds}
onNavigate={(id): void => {
toggleTheater(false)
navigate(`/sprite/${id}`)
}}
onClose={(): void => toggleTheater(false)}
/>
</>
)
return null
}
// Sprites are newest-first. Reverse so oldest is left, newest is right.

View file

@ -110,6 +110,10 @@ export async function skipSprite(spriteId: string): Promise<void> {
return requestVoid(`${API_BASE}/sprites/${spriteId}/skip`, { method: 'POST' })
}
export async function rejectVariant(variantId: number): Promise<void> {
return requestVoid(`${API_BASE}/variants/${variantId}/reject`, { method: 'POST' })
}
export async function regenerateSprite(spriteId: string, prompt?: string, dimensionId?: number): Promise<void> {
const body: Record<string, unknown> = {}
if (prompt !== undefined) body.prompt = prompt

View file

@ -0,0 +1,5 @@
html, body {
margin: 0;
padding: 0;
background: #0a0a14;
}

View file

@ -1,3 +1,4 @@
import './global.css'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'