"use client"; import { FormEvent, useEffect, useMemo, useState } from "react"; type Tab = { id: string; title: string; url: string; history: string[]; historyIndex: number; private: boolean; }; type PrivacySettings = { trackers: boolean; cookies: boolean; fingerprint: boolean; webrtc: boolean; }; type SessionState = { tabs: Tab[]; activeTabId: string; settings: PrivacySettings; blocked: number; }; const SESSION_KEY = "veil-browser-session-v1"; const defaultSettings: PrivacySettings = { trackers: true, cookies: true, fingerprint: true, webrtc: true, }; const createTab = (isPrivate = false): Tab => ({ id: crypto.randomUUID(), title: isPrivate ? "Приватная вкладка" : "Новая вкладка", url: "veil://start", history: ["veil://start"], historyIndex: 0, private: isPrivate, }); const safeSession = (): SessionState => { const firstTab = createTab(); return { tabs: [firstTab], activeTabId: firstTab.id, settings: defaultSettings, blocked: 47, }; }; function normalizeAddress(value: string) { const trimmed = value.trim(); if (!trimmed) return "veil://start"; if (/^[a-z]+:\/\//i.test(trimmed)) return trimmed; if (/^[\w-]+(\.[\w-]+)+/i.test(trimmed)) return `https://${trimmed}`; return `https://duckduckgo.com/?q=${encodeURIComponent(trimmed)}`; } function displayHost(url: string) { if (url === "veil://start") return "Новая вкладка"; try { return new URL(url).hostname.replace(/^www\./, "") || url; } catch { return url; } } export default function Home() { const [session, setSession] = useState(null); const [address, setAddress] = useState(""); const [panel, setPanel] = useState<"privacy" | "history" | null>(null); useEffect(() => { let restored: SessionState; try { const stored = sessionStorage.getItem(SESSION_KEY); restored = stored ? JSON.parse(stored) : safeSession(); } catch { restored = safeSession(); } const frame = requestAnimationFrame(() => setSession(restored)); return () => cancelAnimationFrame(frame); }, []); useEffect(() => { if (session) sessionStorage.setItem(SESSION_KEY, JSON.stringify(session)); }, [session]); const activeTab = useMemo( () => session?.tabs.find((tab) => tab.id === session.activeTabId), [session], ); useEffect(() => { if (!activeTab) return; const frame = requestAnimationFrame(() => setAddress(activeTab.url === "veil://start" ? "" : activeTab.url), ); return () => cancelAnimationFrame(frame); }, [activeTab]); if (!session || !activeTab) { return
Запускаем защищённую сессию…
; } const updateActive = (updater: (tab: Tab) => Tab) => { setSession((current) => current ? { ...current, tabs: current.tabs.map((tab) => tab.id === current.activeTabId ? updater(tab) : tab, ), } : current, ); }; const navigate = (raw: string) => { const url = normalizeAddress(raw); updateActive((tab) => { const nextHistory = [...tab.history.slice(0, tab.historyIndex + 1), url]; return { ...tab, url, title: displayHost(url), history: nextHistory, historyIndex: nextHistory.length - 1, }; }); setSession((current) => current ? { ...current, blocked: current.blocked + 3 + Math.floor(Math.random() * 8) } : current, ); }; const submitAddress = (event: FormEvent) => { event.preventDefault(); navigate(address); }; const moveHistory = (direction: -1 | 1) => { updateActive((tab) => { const index = tab.historyIndex + direction; if (index < 0 || index >= tab.history.length) return tab; const url = tab.history[index]; return { ...tab, url, title: displayHost(url), historyIndex: index }; }); }; const addTab = (isPrivate = false) => { const tab = createTab(isPrivate); setSession((current) => current ? { ...current, tabs: [...current.tabs, tab], activeTabId: tab.id } : current, ); setPanel(null); }; const closeTab = (id: string) => { setSession((current) => { if (!current) return current; const remaining = current.tabs.filter((tab) => tab.id !== id); if (!remaining.length) return safeSession(); return { ...current, tabs: remaining, activeTabId: current.activeTabId === id ? remaining[remaining.length - 1].id : current.activeTabId, }; }); }; const toggleSetting = (key: keyof PrivacySettings) => { setSession((current) => current ? { ...current, settings: { ...current.settings, [key]: !current.settings[key] } } : current, ); }; const clearSession = () => { const fresh = safeSession(); sessionStorage.removeItem(SESSION_KEY); setSession(fresh); setPanel(null); }; const sessionHistory = session.tabs.flatMap((tab) => tab.history).filter((url) => url !== "veil://start"); return (
{ if (window.matchMedia("(pointer: coarse)").matches) return; const bounds = event.currentTarget.getBoundingClientRect(); event.currentTarget.style.setProperty("--pointer-x", `${(event.clientX / bounds.width - 0.5) * 2}`); event.currentTarget.style.setProperty("--pointer-y", `${(event.clientY / bounds.height - 0.5) * 2}`); }} > Перейти к браузеру
V
{session.tabs.map((tab) => ( ))}
setAddress(event.target.value)} placeholder="Введите адрес или поисковый запрос" aria-label="Адрес или поисковый запрос" /> 3 узла
Тихий режим Закроешь вкладку — сессия исчезнет VEIL / BUILD 01
); }