Rebuilds the "pick a photo" flow around an Edit Image modal, matching every product surveyed in the 2026-10 avatar-settings research (Discord, Google, Slack, Figma, Cropper.js playground). Before: 240 px circular viewport with no visible crop frame, a single range slider, and no way to close with the keyboard — the direct reason the previous version felt unfinished. cropMath: VIEWPORT=320, OUTPUT_SIZE=512 (matches Slack min), plus wheelZoom (exp so scroll up then down returns to the same zoom, unlike linear), pinchZoom (ratio of two-pointer distance), PAN_STEP/ZOOM_STEP for keyboard, PREVIEW_SIZES for the "how others see it" column. AvatarCropper is now a pure canvas: pointer capture for pan, two-pointer tracking for pinch, wheel handler with exponential zoom, tabIndex=0 + onKeyDown for arrows/+/-/0, a Reset button, and a visual frame with rule-of-thirds guides (40 % opacity) and an inset box-shadow that dims the corners outside the round target — so the user actually sees what falls in the file. Cancel/Confirm live in the dialog; dirty signal and current CropState bubble up via props. AvatarEditDialog is a portal: bg-black/60 backdrop with click-to-close, role=dialog + aria-modal, initial focus into the card, ESC closes, X button in the header. If the user has panned/zoomed (dirty), closing prompts for confirmation. Header shows file.name + size + the accepted-format hint previously only surfaced after a failed upload. Right column renders 3 live previews at 128/64/32 px so the small HUD nameplate treatment is visible before upload. Progress bar and error notice moved inside so the user does not have to look away during a slow upload. AvatarUpload picks the dialog instead of the raw cropper; two-step "Remove avatar" from the previous commit is unchanged. Tests: 5 new cropMath cases (wheel symmetric, wheel clamps both ends, pinch ratio, pinch guards zero baseline). Existing 140 stay green (145 total), tsc clean, oxlint 0/0. Not yet: no dep on react-easy-crop — own code as planned. No focus trap beyond initial focus + ESC (dialog traps implicitly via portal + backdrop covering). Rotate is still missing — deliberately, since Discord does not ship it either and extending sourceRect for theta would complicate the preview pass. |
||
|---|---|---|
| .. | ||
| public | ||
| scripts | ||
| src | ||
| .env.example | ||
| .gitignore | ||
| .oxlintrc.json | ||
| ARCHITECTURE.md | ||
| bun.lock | ||
| index.html | ||
| package.json | ||
| PLAN.md | ||
| README.md | ||
| tsconfig.app.json | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| tsconfig.test.json | ||
| vite.config.ts | ||
LoVisual Website
The frontend for LoVisual - a marketing site with a live in-game GUI demo, download page, documentation, themes/showcase pages, and an account dashboard with a cloud config manager. UI is fully bilingual (Russian default, English toggle, persisted in the URL).
Stack
- React 19 + TypeScript + Vite, package manager: bun
- Tailwind CSS v4 with custom design tokens (radii, glass panels, control sizing)
- TanStack Query for backend calls, react-router for pages
- react-i18next -
ru/enresources per feature, compile-time key parity viasatisfies - motion/react for animation, vitest + Testing Library for tests
- oxlint for linting
Layout
src/
├── app/ router, providers, App shell
├── features/ one folder per domain (account, cloud, demo, themes, ...)
│ └── <f>/ api.ts · i18n/{ru,en}.ts · components/ · tests/
├── pages/ route-level pages composing features
├── shared/ ui primitives, i18n bootstrap, easter eggs, helpers
└── test/ vitest setup and utilities
Feature-folder rules: API calls, translations and components live together; no cross-feature
imports except through shared/.
Scripts
bun install # install dependencies
bun run dev # dev server on http://localhost:5173 (proxies /api to the backend)
bun run test # vitest run
bun run build # tsc -b + vite build → dist/
bun run lint # oxlint
bun run gen:themes # regenerate theme tokens from the mod palette (scripts/extract-themes.ts)
The dev server expects the Rust gateway on 127.0.0.1:8080 (see backend/);
without it, API-backed pages degrade to demo/offline states by design.
Testing
Unit and component tests are colocated in features/*/tests/ and shared/**/tests/;
run a single file with bunx vitest run src/features/account/tests/account.test.tsx.
Translation parity (ru ↔ en key sets) is enforced at compile time and in tests.