Deepfake Authenticator
feat: replace vanilla frontend with React + Vite UI
70348ce
import { useEffect, useState } from 'react';
interface RadioNavProps {
active: string;
onNavigate: (id: string) => void;
}
const items = [
{
id: 'dashboard',
label: 'Dashboard',
icon: (
<path d="M4 13h6a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1zm-1 7a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v4zm10 0a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-7a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v7zm1-10h6a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1z" />
),
},
{
id: 'analyze',
label: 'Analyze',
icon: (
<path d="M12 2a5 5 0 1 0 5 5 5 5 0 0 0-5-5zm0 8a3 3 0 1 1 3-3 3 3 0 0 1-3 3zm9 11v-1a7 7 0 0 0-7-7h-4a7 7 0 0 0-7 7v1h2v-1a5 5 0 0 1 5-5h4a5 5 0 0 1 5 5v1z" />
),
},
{
id: 'pricing',
label: 'Pricing',
icon: (
<path d="M5 18v3.766l1.515-.909L11.277 18H16c1.103 0 2-.897 2-2V8c0-1.103-.897-2-2-2H4c-1.103 0-2 .897-2 2v8c0 1.103.897 2 2 2h1zM4 8h12v8h-5.277L7 18.234V16H4V8zm16-6H8c-1.103 0-2 .897-2 2h12c1.103 0 2 .897 2 2v8c1.103 0 2-.897 2-2V4c0-1.103-.897-2-2-2z" />
),
},
{
id: 'agents',
label: 'Agents',
icon: (
<path d="M11.953 2C6.465 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.493 2 11.953 2zM12 20c-4.411 0-8-3.589-8-8s3.567-8 7.953-8C16.391 4 20 7.589 20 12s-3.589 8-8 8zm-1-5h2v2h-2zm0-8h2v6h-2z" />
),
},
{
id: 'settings',
label: 'Settings',
icon: (
<>
<path d="M12 16c2.206 0 4-1.794 4-4s-1.794-4-4-4-4 1.794-4 4 1.794 4 4 4zm0-6c1.084 0 2 .916 2 2s-.916 2-2 2-2-.916-2-2 .916-2 2-2z" />
<path d="m2.845 16.136 1 1.73c.531.917 1.809 1.261 2.73.73l.529-.306A8.1 8.1 0 0 0 9 19.402V20c0 1.103.897 2 2 2h2c1.103 0 2-.897 2-2v-.598a8.132 8.132 0 0 0 1.896-1.111l.529.306c.923.53 2.198.188 2.731-.731l.999-1.729a2.001 2.001 0 0 0-.731-2.732l-.505-.292a7.718 7.718 0 0 0 0-2.224l.505-.292a2.002 2.002 0 0 0 .731-2.732l-.999-1.729c-.531-.92-1.808-1.265-2.731-.732l-.529.306A8.1 8.1 0 0 0 15 4.598V4c0-1.103-.897-2-2-2h-2c-1.103 0-2 .897-2 2v.598a8.132 8.132 0 0 0-1.896 1.111l-.529-.306c-.924-.531-2.2-.187-2.731.732l-.999 1.729a2.001 2.001 0 0 0 .731 2.732l.505.292a7.683 7.683 0 0 0 0 2.223l-.505.292a2.003 2.003 0 0 0-.731 2.733zm3.326-2.758A5.703 5.703 0 0 1 6 12c0-.462.058-.926.17-1.378a.999.999 0 0 0-.47-1.108l-1.123-.65.998-1.729 1.145.662a.997.997 0 0 0 1.188-.142 6.071 6.071 0 0 1 2.384-1.399A1 1 0 0 0 11 5.3V4h2v1.3a1 1 0 0 0 .708.956 6.083 6.083 0 0 1 2.384 1.399.999.999 0 0 0 1.188.142l1.144-.661 1 1.729-1.124.649a1 1 0 0 0-.47 1.108c.112.452.17.916.17 1.378 0 .461-.058.925-.171 1.378a1 1 0 0 0 .471 1.108l1.123.649-.998 1.729-1.145-.661a.996.996 0 0 0-1.188.142 6.071 6.071 0 0 1-2.384 1.399A1 1 0 0 0 13 18.7l.002 1.3H11v-1.3a1 1 0 0 0-.708-.956 6.083 6.083 0 0 1-2.384-1.399.992.992 0 0 0-1.188-.141l-1.144.662-1-1.729 1.124-.651a1 1 0 0 0 .471-1.108z" />
</>
),
},
];
export default function RadioNav({ active, onNavigate }: RadioNavProps) {
const [hovered, setHovered] = useState<string | null>(null);
// Sync radio inputs with active state
useEffect(() => {
const el = document.getElementById(active) as HTMLInputElement | null;
if (el) el.checked = true;
}, [active]);
return (
<div className="fixed top-4 left-1/2 z-50 -translate-x-1/2 transition-all duration-450 ease-in-out w-auto px-4">
<article
className="flex rounded-2xl overflow-hidden"
style={{
background: '#0d0720',
border: '1px solid rgba(168,85,247,0.18)',
boxShadow: '0 8px 32px rgba(0,0,0,0.5), 0 0 0 1px rgba(88,28,135,0.15)',
}}
>
{items.map(item => {
const isActive = active === item.id;
const isHovered = hovered === item.id;
return (
<label
key={item.id}
htmlFor={item.id}
className="relative flex flex-col items-center justify-center cursor-pointer select-none transition-all duration-300"
style={{
width: 72,
height: 64,
padding: '8px 4px 4px',
gap: 4,
borderRadius: 12,
background: isActive
? 'rgba(124,58,237,0.25)'
: isHovered
? 'rgba(88,28,135,0.15)'
: 'transparent',
boxShadow: isActive
? 'inset 0 1px 0 rgba(192,132,252,0.15), 0 0 16px rgba(124,58,237,0.2)'
: 'none',
borderBottom: isActive ? '2px solid rgba(168,85,247,0.7)' : '2px solid transparent',
}}
onMouseEnter={() => setHovered(item.id)}
onMouseLeave={() => setHovered(null)}
onClick={() => onNavigate(item.id)}
>
<input
id={item.id}
name="nav"
type="radio"
className="hidden"
defaultChecked={isActive}
/>
<svg
viewBox="0 0 24 24"
width={20}
height={20}
xmlns="http://www.w3.org/2000/svg"
style={{
fill: isActive ? '#c084fc' : isHovered ? '#a855f7' : 'rgba(168,85,247,0.35)',
filter: isActive ? 'drop-shadow(0 0 6px rgba(192,132,252,0.7))' : 'none',
transform: isActive || isHovered ? 'scale(1.2)' : 'scale(1)',
transition: 'all 0.3s ease',
}}
>
{item.icon}
</svg>
<span
className="font-bold uppercase tracking-wider"
style={{
fontSize: 8,
letterSpacing: '0.08em',
color: isActive ? '#c084fc' : isHovered ? '#a855f7' : 'rgba(168,85,247,0.3)',
transition: 'color 0.3s ease',
}}
>
{item.label}
</span>
</label>
);
})}
</article>
</div>
);
}