Офлайн-first PWA — интерактивный конструктор скалолазных трасс. «Красишь» зацепы на доске с фиксированной сеткой по ролям (старт / рука / нога / топ), сохраняешь трассы локально и делишься ссылкой — вся трасса закодирована прямо в URL. Бэкенда нет, всё работает на статическом хостинге.
Демо: https://arbaev.github.io/crux/
Android: скачать APK
Стек: Vue 3 + Quasar + TypeScript (strict)
архитектура с чётким разделением состояния и рендеринга, PWA с офлайн-режимом, шаринг без сервера, i18n, сборка под Android.
- Конструктор трасс. Тап или свайп по зацепам — «покраска» ролями: старт, рука, нога, топ (топ — всегда единственная точка). Кисть-ластик и снятие роли повторным касанием.
- Локальное хранилище. Трассы сохраняются в браузере (IndexedDB): создание, открытие, дублирование, удаление, имя и категория сложности.
- Системы сложности. Font, V-scale, French, цветовая и произвольная метки.
- Шаринг без сервера. Трасса целиком кодируется в ссылку (
?r=…) — получатель открывает её в режиме просмотра и может сохранить себе. Ссылка копируется в буфер одной кнопкой. - PWA. Устанавливается как приложение, работает офлайн (service worker), обновляется по запросу.
- Мультиязычность. Русский и английский с автоопределением по языку браузера и запоминанием выбора.
- Адаптивный интерфейс. Раскладка под десктоп (сайдбар) и мобильный (нижняя панель инструментов).
- Сборка под Android (Capacitor) — опционально, помимо веба/PWA.
| Сфера | Стек |
|---|---|
| Фреймворк | Vue 3 (Composition API, <script setup>), Quasar v2 |
| Сборка | @quasar/app-vite v2 (Vite), TypeScript (strict) |
| Состояние | Pinia (setup-стиль) |
| Хранилище | IndexedDB через idb |
| Рендеринг доски | 2D SVG (без зависимостей) |
| i18n | vue-i18n + lang-паки Quasar |
| PWA | Workbox (InjectManifest), service worker с prompt-on-update |
| Мобильное | Capacitor (Android) |
| Тесты | Vitest (unit-тесты чистых функций) |
Архитектурный принцип — рендерер не владеет состоянием, он его отражает:
Трасса хранится как чистые данные (роли по зацепам), не зависящие от способа отрисовки. Благодаря этому рендерер был заменён с Three.js на 2D-SVG без единой правки бизнес-логики, кодека и тестов.
Формат ссылки (контракт шаринга, v1):
v1.7x10.12S,19H,23H,67T
└┬┘ └─┬─┘ └──────┬──────┘
вер. сетка токены: <индекс ячейки><роль S/H/F/T>
Кодирование/декодирование — чистые функции (src/lib/routeCodec.ts), round-trip покрыт unit-тестами.
Требуется pnpm (другие пакетные менеджеры не использовать — есть pnpm-lock.yaml).
pnpm install # установка зависимостей
pnpm dev # dev-сервер (SPA) → http://localhost:9000/
pnpm exec quasar dev -m pwa # dev в PWA-режиме (проверка service worker)pnpm build # прод-сборка SPA → dist/spa
pnpm exec quasar build -m pwa # прод-сборка PWA → dist/pwa (цель деплоя)
pnpm test # unit-тесты (Vitest)
pnpm lint # ESLint
pnpm format # PrettierДеплой на GitHub Pages автоматизирован через GitHub Actions (.github/workflows/deploy.yml).
Сборка Android-APK описана в docs/android-build.md.
src/
├── pages/ экраны: конструктор (/), мои трассы (/routes),
│ просмотр ссылки (/view), настройки (/settings), 404
├── layouts/ MainLayout — каркас с навигацией
├── components/ BoardSvg (рендерер доски), палитра кистей, тулбары, карточка трассы…
├── composables/ useLocale (язык), useInstallPrompt (PWA)
├── stores/ Pinia: board (конфиг доски), route (трасса + персистентность)
├── lib/ чистые модули: routeCodec, db, board, grades, roles
├── router/ маршруты приложения
├── i18n/ локали en-US (мастер-схема) и ru-RU
└── types/ модель данных
MIT © 2026 Timur Arbaev