# Portfolio Site `06 · portfolio-site · Open source` The site you're reading. React · Vite · TypeScript with token-first CSS modules, typed EN/RU/AR content, text mirrors, privacy-first telemetry and route-aware navigation. **Scope:** Solo · ongoing **Role:** Engineering portfolio · 2025–2026 --- ## Context > Six cases the recruiter cannot click into. One site they can. Of the six showcased projects, five sit behind closed/NDA scope — client work, internal tooling, R&D. Recruiters cannot open them, cannot pull the repo, cannot diff the commit history. The site itself is the only public artifact a senior reviewer can audit on the spot: open DevTools, watch the network, read the CSS, view-source the dot-grid. Every detail visible to the user is also a decision visible to the engineer. The constraint is dual. The page must scan for a recruiter in five seconds — fixed-shape cards, mono spec-sheets, ASCII diagrams that read like terminal output. It must also hold under a senior eye who scrolls slow — token-first design, hand-rolled i18n with compile-time parity, view transitions for the home → case morph, and a Hero ember field that does not cook iPhones. The site has to be its own case study, because nothing else here can be opened. ## Facts | | | |---|---| | **Scope** | Solo · ongoing | | **Surfaces** | Home + 6 cases · EN/RU/AR · dark/light · 3 palettes | | **Source** | Public GitHub repository · CV PDF · copy-as-Markdown actions | | **Stack** | React 18 · Vite · TypeScript · CSS variables | | **Content** | Typed EN/RU/AR dictionaries · public/private content trees · generated text mirrors | | **Telemetry** | Cookieless same-origin events · admin-only analytics | | **Motion** | View transitions (card → case hero FLIP-morph) · prefers-reduced-motion respected | | **Status** | Open source · live | ## Architecture ### Provider tree + routing ```text // src/theme/ThemeContext.tsx // src/i18n/LangContext.tsx ``` **Provider order matters.** ThemeProvider outermost so the palette is set on `` before any child reads color variables. LangProvider next so `useT()` is available everywhere. Loader, scroll restoration and analytics all sit inside the router because they need routed content or `useLocation()`. **Two functional routes + 404 fallback.** `/` renders the home page; `/cases/:slug` renders the case-study page; `*` falls back to home for unknown URLs. Slug whitelist (`CASE_SLUGS = [ai-crm, roblox-game, ai-video-editor, ai-warehouse, macos-vpn, portfolio-site]`) lives in `src/config/cases.ts` — `Nav.tsx` and `ProjectDetailPage.tsx` consume it; server analytics keeps its own allowlists. **No code-splitting yet.** Both routes ship in one bundle. Lighthouse stays green; revisit when a third route lands. ### Token resolution chain ```text 1 ThemeContext theme = 'dark' → palette = 'ochre' │ ▼ writes data-attrs on 2 │ ▼ tokens.css matches via attribute selectors 3 [data-theme="dark"] → --bg --fg --line [data-palette="ochre"][data-theme="dark"] → --accent --accent-soft │ ▼ CSS modules imported by styles.css consume via var() 4 .hero h1 { color: var(--fg) } .chip { background: color-mix(in oklab, var(--bg) 10%, …) } ``` **Theme determines palette.** Derived deterministically — `dark → ochre` (warm gold accent), `light → electric` (cool blue accent). Both `data-theme` and `data-palette` written to ``. CSS handles the swap via attribute selectors; zero JS branching per element. **Token file + CSS modules, no Tailwind.** `tokens.css` (153 LOC) is the single source of truth for colors / spacing / type / radii. `styles.css` is now a tiny entry file importing `base`, `home`, `media`, `case-study` and `runtime` modules. The token system does what Tailwind config would; component selectors do what utility classes would. **oklch + color-mix for backdrops.** Heavy use of `color-mix(in oklab, var(--bg) X%, transparent)` for tinted overlays — the frosted Hero chips, the case-study glows, the diagram backdrops. Avoids opacity tricks on the wrong color. ### Loader + interface entrance phases ```text T=0 T=400 ms T=900 ms │ │ │ ▼ ▼ ▼ pulse → rush → gone hairline two whiteish blurred loader unmounted opacity pulses run from 15% / 85% hero/about reveal 0.22-0.7 toward center (600 ms) (staggered) at T+400 ms of rush: html.is-loading removed (was set synchronously by inline