({
positions,
electionName,
isOnline,
userEmail,
isSubmitting,
onSubmit,
abstainedPositions,
onAbstain,
}: VotingLayoutProps)
| 26 | * Features ambient background orbs, deep glassmorphism via .glass-panel, |
| 27 | * and high-fidelity hover states. |
| 28 | */ |
| 29 | export function VotingLayout({ |
| 30 | positions, |
| 31 | electionName, |
| 32 | isOnline, |
| 33 | userEmail, |
| 34 | isSubmitting, |
| 35 | onSubmit, |
| 36 | abstainedPositions, |
| 37 | onAbstain, |
| 38 | }: VotingLayoutProps) { |
| 39 | const time = useSystemClock(); |
| 40 | const activeIndex = useBallotStore((s) => s.activePositionIndex); |
| 41 | const setActiveIndex = useBallotStore((s) => s.setActivePositionIndex); |
| 42 | const getSelectionCount = useBallotStore((s) => s.getSelectionCount); |
| 43 | |
| 44 | const handlePositionClick = (index: number) => { |
| 45 | sounds.playClick(); |
| 46 | setActiveIndex(index); |
| 47 | }; |
| 48 | |
| 49 | return ( |
| 50 | <div className="relative flex h-screen w-screen overflow-hidden bg-zinc-50 dark:bg-zinc-950 font-sans selection:bg-maroon-500/30"> |
| 51 | {/* ─── Premium Grid Background ─── */} |
| 52 | <div className="pointer-events-none absolute inset-0 z-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_0%,#000_70%,transparent_100%)]" /> |
| 53 | |
| 54 | {/* ─── Ambient Background Orbs ─── */} |
| 55 | <div className="pointer-events-none absolute -left-[10%] -top-[10%] h-[40vw] w-[40vw] rounded-full bg-maroon-400/10 blur-[120px] dark:bg-maroon-600/20 mix-blend-multiply dark:mix-blend-screen" /> |
| 56 | <div className="pointer-events-none absolute -bottom-[10%] -right-[5%] h-[50vw] w-[50vw] rounded-full bg-gold-400/10 blur-[150px] dark:bg-maroon-900/30 mix-blend-multiply dark:mix-blend-screen" /> |
| 57 | |
| 58 | {/* ─── App Container ─── */} |
| 59 | <div className="z-10 flex h-full w-full flex-col overflow-hidden"> |
| 60 | {/* ─── Native App Title Bar (Glass) ─── */} |
| 61 | <header className="relative z-30 flex h-12 shrink-0 items-center justify-between glass-panel rounded-none border-t-0 border-x-0 border-b-white/50 px-5 text-sm dark:border-b-white/5 [-webkit-app-region:drag]"> |
| 62 | {/* Left: Branding + Clock */} |
| 63 | <div className="flex items-center gap-4 [-webkit-app-region:no-drag]"> |
| 64 | <span className="font-extrabold tracking-widest text-zinc-900 dark:text-zinc-50"> |
| 65 | VOTE 2026 |
| 66 | </span> |
| 67 | <div className="h-4 w-px bg-zinc-300 dark:bg-zinc-700"></div> |
| 68 | <span className="font-mono tabular-nums text-zinc-500 dark:text-zinc-400 font-medium"> |
| 69 | {time} |
| 70 | </span> |
| 71 | </div> |
| 72 | |
| 73 | {/* Center: Election Name */} |
| 74 | <div className="pointer-events-none absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center gap-2.5"> |
| 75 | <div className="h-2 w-2 rounded-full bg-green-500 shadow-[0_0_12px_rgba(34,197,94,0.6)] animate-pulse-dot" /> |
| 76 | <span className="font-semibold tracking-tight text-zinc-800 dark:text-zinc-200"> |
| 77 | {electionName} |
| 78 | </span> |
| 79 | </div> |
| 80 | |
| 81 | {/* Right: Controls & macOS Windows */} |
| 82 | <div className="flex items-center gap-3 [-webkit-app-region:no-drag]"> |
| 83 | {userEmail && ( |
| 84 | <> |
| 85 | <span className="font-mono text-zinc-500 dark:text-zinc-400 font-medium bg-zinc-100 dark:bg-zinc-800/50 px-2 py-0.5 rounded-md"> |
nothing calls this directly
no test coverage detected