LoVisual/frontend
loki5512344 4fce425dcd
Some checks failed
CI / backend (cargo test + clippy) (push) Failing after 1m21s
CI / frontend (lint + test + build) (push) Failing after 24s
CI / mod (gradle test + checkFolderLimit) (push) Successful in 1m57s
feat(frontend): avatar crop dialog (viewport 320, wheel/pinch/keyboard, guides+shade, previews, dirty guard)
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.
2026-10-12 22:51:40 +02:00
..
public feat: frontend perf and a11y pass, plus mod visuals, hero main menu, Simple Voice Chat and vanilla chat autocomplete 2026-10-01 21:29:54 +02:00
scripts chore(history): squash 67 commit(s) from 2026-09-25 2026-09-25 20:22:13 +02:00
src feat(frontend): avatar crop dialog (viewport 320, wheel/pinch/keyboard, guides+shade, previews, dirty guard) 2026-10-12 22:51:40 +02:00
.env.example chore(history): squash 67 commit(s) from 2026-09-25 2026-09-25 20:22:13 +02:00
.gitignore test(frontend): lock the style invariants a QA pass keeps re-finding 2026-09-28 13:27:15 +02:00
.oxlintrc.json chore(history): squash 59 commit(s) from 2026-09-23 2026-09-23 22:38:08 +02:00
ARCHITECTURE.md refactor(frontend): bring shared/ui and pages/public under the 4-file rule 2026-09-28 13:21:18 +02:00
bun.lock feat: frontend perf and a11y pass, plus mod visuals, hero main menu, Simple Voice Chat and vanilla chat autocomplete 2026-10-01 21:29:54 +02:00
index.html feat(frontend): SEO — sitemap, canonical link, structured data 2026-09-30 15:48:44 +02:00
package.json feat: frontend perf and a11y pass, plus mod visuals, hero main menu, Simple Voice Chat and vanilla chat autocomplete 2026-10-01 21:29:54 +02:00
PLAN.md feat(frontend): avatar upload QOL — feedback, re-crop, drop, progress, history 2026-09-28 22:56:01 +02:00
README.md docs: add a real root README and rewrite the frontend one 2026-09-28 17:28:41 +02:00
tsconfig.app.json feat(frontend): avatar upload QOL — feedback, re-crop, drop, progress, history 2026-09-28 22:56:01 +02:00
tsconfig.json test(frontend): lock the style invariants a QA pass keeps re-finding 2026-09-28 13:27:15 +02:00
tsconfig.node.json chore(history): squash 67 commit(s) from 2026-09-25 2026-09-25 20:22:13 +02:00
tsconfig.test.json feat(frontend): avatar upload QOL — feedback, re-crop, drop, progress, history 2026-09-28 22:56:01 +02:00
vite.config.ts perf(frontend): route-level code splitting, main chunk 538 -> 491 KB 2026-09-30 16:06:45 +02:00

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/en resources per feature, compile-time key parity via satisfies
  • 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.