diff --git a/backend/app/services/xp.py b/backend/app/services/xp.py
index 6dfd4ef..3450a5f 100644
--- a/backend/app/services/xp.py
+++ b/backend/app/services/xp.py
@@ -161,8 +161,25 @@
return level
+def coin_balance(db: Session, user_id: str) -> int:
+ """Баланс монет — сумма журнала coin_events (та же арифметика, что в саду).
+
+ Считается здесь, а не через services.garden.balance(): garden импортирует
+ уровни отсюда, обратный импорт был бы циклическим. Нужно странице «Достижения»
+ (ТЗ 3.13, сад отключён в 0.58), где монеты показываются без вызова /api/garden.
+ """
+ return int(
+ db.scalar(
+ select(func.coalesce(func.sum(CoinEvent.amount), 0)).where(
+ CoinEvent.user_id == user_id
+ )
+ )
+ or 0
+ )
+
+
def xp_summary(db: Session, user_id: str) -> dict[str, Any]:
- """Сводка для UI: XP, уровень, прогресс до следующего уровня."""
+ """Сводка для UI: XP, уровень, прогресс до следующего уровня, монеты."""
xp = total_xp(db, user_id)
level = level_from_xp(xp)
current_floor = LEVEL_FACTOR * (level - 1) * level // 2
@@ -183,6 +200,7 @@
)
or 0
),
+ "coins": coin_balance(db, user_id),
}
diff --git a/backend/tests/test_xp.py b/backend/tests/test_xp.py
index bb3be54..3cbb705 100644
--- a/backend/tests/test_xp.py
+++ b/backend/tests/test_xp.py
@@ -3,8 +3,11 @@
from typing import Any
from fastapi.testclient import TestClient
+from sqlalchemy import select
+from app.models import GardenItem
from app.services.xp import compute_rarity
+from tests.conftest import _test_session_factory
def _create_task(client: TestClient, **patch: Any) -> int:
@@ -32,6 +35,8 @@
assert summary["total_xp"] == 12
assert summary["level"] == 1
assert summary["completed_count"] == 1
+ # Монеты: столько же, сколько XP (2 за создание + 10 за закрытие)
+ assert summary["coins"] == 12
def test_xp_weighted_by_priority_and_estimate(client: TestClient) -> None:
@@ -163,6 +168,7 @@
summary = client.get("/api/xp").json()
assert summary["total_xp"] == 5
assert summary["completed_count"] == 0
+ assert summary["coins"] == 4
events = client.get("/api/xp/events").json()
assert [e["kind"] for e in events] == ["create_project"]
@@ -177,6 +183,7 @@
summary = client.get("/api/xp").json()
assert summary["total_xp"] == 2
assert summary["completed_count"] == 0
+ assert summary["coins"] == 2
state = client.get("/api/garden").json()
assert state["balance"] == 2
@@ -195,3 +202,34 @@
second = client.post("/api/xp/daily")
assert second.json() == {"granted": False, "coins": 0}
assert client.get("/api/garden").json()["balance"] == 50
+
+
+# --- Монеты в /api/xp и отключённый сад (ТЗ 0.58) ---
+
+
+def test_xp_summary_reports_coins(client: TestClient) -> None:
+ """Баланс монет виден из /api/xp — страница «Достижения» не зовёт /api/garden."""
+ task_id = _create_task(client)
+ client.patch(f"/api/tasks/{task_id}", json={"status": "done"})
+ client.post("/api/xp/daily")
+
+ summary = client.get("/api/xp").json()
+ assert summary["coins"] == 12 + 50
+ # Паритет двух источников баланса (garden.balance и монеты сводки XP)
+ assert summary["coins"] == client.get("/api/garden").json()["balance"]
+
+
+def test_achievements_flow_does_not_create_plants(client: TestClient) -> None:
+ """Регресс (0.58): без вызова /api/garden растения не создаются.
+
+ Растения рождаются лениво — в ensure_garden(), которую зовёт только
+ GET /api/garden. Страница «Достижения» ходит лишь в /api/xp и /api/xp/events.
+ """
+ task_id = _create_task(client)
+ client.patch(f"/api/tasks/{task_id}", json={"status": "done"})
+ client.get("/api/xp")
+ client.get("/api/xp/events")
+
+ with _test_session_factory() as session:
+ plants = session.scalars(select(GardenItem)).all()
+ assert plants == []
diff --git a/docs/TZ.md b/docs/TZ.md
index cbc58bd..ce629c6 100644
--- a/docs/TZ.md
+++ b/docs/TZ.md
@@ -4,7 +4,7 @@
| | |
|---|---|
-| Версия ТЗ | 0.57 |
+| Версия ТЗ | 0.58 |
| Дата | 2026-10-09 |
| Статус | На обсуждении |
@@ -20,6 +20,7 @@
- Несколько пользователей: данные (задачи, проекты, теги, документы, сад, XP/монеты,
настройки) полностью изолированы по `user_id` — один пользователь не видит чужого.
+ Сад (0.58) в интерфейсе отключён, но его данные по-прежнему изолированы.
- Авторизация — через централизованный SSO-сервер **auth.gnexus.space** с клиентской библиотекой **gnexus-gauth** (см. 6.4). Пользователь создаётся в БД при первом входе (email, avatar, locale — из SSO).
- **Claim при первом логине** (миграция на мультиюзерность): все строки с
`user_id = NULL` (наследие однопользовательской версии) и глобальные настройки
@@ -246,8 +247,9 @@
пока владелец не создаст токен в «Настройках». С 0.42 Bearer-токен принимается
и на REST `/api/*` — им ходит браузерное расширение (3.18).
- Закрытие задачи агентом (`update_task`/`complete_task`) идёт по тому же общему
- пути, что и в UI: спавн регулярной, XP, монеты, растение (3.5, 3.13); в открытых
- вкладках — скромный тост о награде (3.14).
+ пути, что и в UI: спавн регулярной, XP, монеты (3.5, 3.13); в открытых
+ вкладках — скромный тост о награде (3.14). Растение больше не выдаётся —
+ сад отключён в 0.58.
### 3.11. Локальные AI-функции
@@ -291,7 +293,8 @@
- **Микронаграды за создание** (без конфетти — скромный тост «+N XP • +M монет»):
создание задачи — +2 XP и +2 монеты, создание проекта — +5 XP и +4 монеты
(0.48: монеты ×2).
- В суммарный опыт (уровень) входят, растений в саду не рождают, счётчик
+ В суммарный опыт (уровень) входят, растений в саду не рождают (сад отключён
+ в 0.58 — выдача и так остановлена), счётчик
закрытых задач не увеличивают; хранятся с `task_id = NULL` — удаление задачи
или проекта награду не отнимает.
- **Ежедневная награда за визит**: при открытии приложения фронт запрашивает
@@ -299,14 +302,76 @@
(только монеты, XP нет), повторные визиты в те же сутки молча пропускаются.
Тост «+50 монет» только в день первого визита.
- **Уровень**: растёт из суммарного XP (уровень не падает); прогресс до следующего
- уровня и **название звания** показываются в «Саду». Звания ботанические
- (Росток, Сеянцев, Садовник, … Легенда сада — 13 званий; порог уровня
- L = 100·L·(L−1)/2 XP). **Дерево уровней** — модалка со списком всех уровней:
- звание, порог XP, что открывается (виды растений с ценой семени и
- premium-пометкой, расширения карты); пройденные отмечены, текущий подсвечен.
+ уровня и **название звания** показываются на странице «Достижения» (0.58; ранее —
+ в саду). Звания ботанические (Росток, Сеянцев, Садовник, … Легенда сада —
+ 13 званий; порог уровня L = 100·L·(L−1)/2 XP). **Дерево званий** — модалка со
+ списком всех уровней: звание и порог XP; пройденные отмечены, текущий подсвечен.
+ В саду (код сохранён) та же модалка дополнительно показывает, что открывается
+ на уровне — виды растений с ценой семени и расширения карты.
- **Момент награды**: при закрытии задачи — короткая конфетти-анимация и тост
- «+N XP» с похвалой. Особые закрытия (редкое растение, крупная задача) —
- усиленное «золотое» конфетти.
+ «+N XP • +N монет» с похвалой. Особые закрытия (крупная задача — от 25 XP,
+ «не хочется делать») — усиленное «золотое» конфетти. Редкость закрытия
+ (см. ниже) в 0.58 в UI не показывается, но по-прежнему пишется в `xp_events` —
+ как история для возврата сада.
+- **Страница «Достижения»** (`/achievements`, 0.58): весь видимый прогресс без
+ сада. Бейджи шапки — уровень с названием звания, суммарный XP и **монеты**;
+ полоса прогресса до следующего уровня; кнопка «Дерево званий»; справка
+ «Как это работает»; **ачивки с тирами** (см. ниже) — тот же блок, что и в саду
+ (общий компонент, логика не дублируется). Данные — `GET /api/tasks`,
+ `GET /api/xp`, `GET /api/xp/events`; `GET /api/garden` **не вызывается** (это и
+ есть механизм отключения выдачи растений, см. ниже). Страница слушает SSE
+ `xp.changed` и `task.changed`. Пункт меню — между «Проектами» и «Статистикой»
+ (место прежнего «Сада»), путь `/garden` редиректит на `/achievements`
+ (старые ссылки и закладки PWA не ломаются).
+- **Монеты**: начисляются при закрытии задачи — xp целиком (0.48: было
+ `floor(xp / 2)`; заголовок `X-Earned-Coins` рядом с XP в тосте), за создание
+ задачи/проекта и бонусом **50 × L** при достижении уровня L (0.48: было
+ 25 × L). Ежедневная награда не менялась (50 монет).
+ Хранение — таблица `coin_events` (журнал заработка и трат; баланс =
+ сумма amount). Траты — отрицательные события (`source = shop`); продажи нет
+ («только позитив»). Баланс виден на «Достижениях» — `GET /api/xp` отдаёт поле
+ `coins` (0.58). Пока сад отключён, тратить монеты негде: они копятся и
+ пригодятся, когда сад вернётся.
+- **Ачивки с тирами**: у каждой ачивки ступени I/II/III (например, 10/50/100
+ задач), прогресс к следующей ступени виден всегда. Набор: вехи закрытий,
+ уровни, макс. задач за день, разные проекты, суммарные часы, строгие
+ дедлайны, закрытия через выдачу задачи (см. 3.9), регулярная, проект.
+ Ачивки считаются на фронте из задач и `xp_events` и показываются на
+ «Достижениях» (0.58) — общий блок с садом (`components/AchievementsPanel.vue`).
+ **Описание по клику (0.49)**: карточка-ачивка — триггер GnPopover кита,
+ клик открывает панель с кратким описанием условия (`progress.achHint`,
+ i18n ×3; тултип по hover не годится — не работает на тач-экранах).
+- **Связка с выдачей задачи** (3.9): закрытие задачи, открытой из выдачи, помечается
+ (`earned_via`) и празднуется меткой «выбрал и сделал» + счётчик-ачивкой;
+ XP при этом не меняется.
+- **Страница «Статистика»**: закрываемость за выбранный **день / неделю / месяц**
+ (период листается стрелками назад/вперёд). Метрики — закрытые задачи, XP,
+ созданные задачи, часы работы, «дни с результатом» — каждая со сравнением
+ с **предыдущим аналогичным периодом** (дельта и проценты). Анимированный
+ **сравнительный** график закрытых задач по бакетам периода (часы / дни недели /
+ дни месяца): текущий и предыдущий период — пары столбиков. **Сетка активности**
+ «как в гитхабе» за полгода или год (переключатель диапазона; цвет по закрытым
+ задачам или XP, переключается).
+ Данные считаются из `xp_events` и дат задач, без новых эндпоинтов.
+- **Метка «ментально сложная»**: у незакрытой задачи есть кнопка-переключатель
+ **«Не хочется делать»** (warning-стиль) — она ставит метку «ментально сложная»:
+ трудность не по объёму, а из-за требуемых волевых усилий. Метку можно снять до
+ закрытия, после закрытия она не меняется. Закрытие такой задачи даёт **+10 XP**
+ (сила воли) и всегда празднуется золотым конфетти; ачивка **«Преодоление»**
+ (5/25/50 закрытых «ментально сложных») — на «Достижениях».
+- **Справка «Как это работает»**: кнопка на «Достижениях» (и, при возврате сада,
+ на странице сада) открывает модалку с объяснением механик (XP, уровни,
+ монеты, ачивки, принцип «только позитив»); параграфы — `progress.about`.
+- **Сад отключён (0.58)**: сад не готов к использованию и недостаточно проработан,
+ а сервис расширяется — приоритет отдан основному функционалу. Страница убрана из
+ интерфейса, `/garden` редиректит на `/achievements`, код сохранён для возврата
+ (по прецеденту представления «дерево», 3.6). **Растения больше не выдаются**:
+ `GardenItem` создаётся лениво — в `ensure_garden()`, которую зовёт только
+ `GET /api/garden`; UI этот эндпоинт не вызывает, поэтому выдача прекращается
+ сама. Модель `GardenItem`, журнал `coin_events` и накопленная редкость
+ (`xp_events.rarity`) сохраняются — сад вернётся с прежним состоянием. Всё
+ описанное ниже — **исторический блок** (как сад устроен), актуальны только
+ XP, уровни, монеты и ачивки.
- **Сад** (отдельная страница): живая **сцена** — графическое представление сада
с домиком в центре; вокруг сад на мелкой сетке (ячейка ~20px, привязка при
перемещении, наложения разрешены). Каждая закрытая задача — растение в саду;
@@ -331,18 +396,11 @@
плавная доводка к центру клетки. Бросок мимо сада или на запрещённую
клетку возвращает объект на исходную позицию анимацией **без запроса**
к API (валидность фронт проверяет сам; бэк остаётся источником истины).
-- **Две награды: опыт и монеты.** Опыт — «ранг садовника»: только растёт, ничего
- не открывает в магазине напрямую. Монеты — «валюта сада»: тратятся. Общий
- принцип: **контент открывается за опыт, покупается за монеты** — опыт (уровень)
- открывает виды растений и землю, монеты покупают рост растений, декорации и
- расширения карты.
-- **Монеты**: начисляются при закрытии задачи — xp целиком (0.48: было
- `floor(xp / 2)` — экономика сада ускорена примерно вдвое; заголовок
- `X-Earned-Coins` рядом с XP в тосте), и бонусом **50 × L** при достижении
- уровня L (0.48: было 25 × L). Ежедневная награда не менялась (50 монет).
- Хранение — таблица `coin_events` (журнал заработка и трат; баланс =
- сумма amount). Траты — отрицательные события (`source = shop`); продажи нет
- («только позитив»).
+- **Две награды: опыт и монеты.** (Исторический блок сада.) Опыт — «ранг
+ садовника»: только растёт, ничего не открывает в магазине напрямую. Монеты —
+ «валюта сада»: тратятся. Общий принцип: **контент открывается за опыт,
+ покупается за монеты** — опыт (уровень) открывает виды растений и землю,
+ монеты покупают рост растений, декорации и расширения карты.
- **Редкие растения**: редкость решается в момент закрытия по весу задачи —
чем больше XP, тем выше шанс редкого; эпик-растения — только у крупных
закрытий. Редкость хранится в `xp_events`, визуально — золото + бейдж.
@@ -393,33 +451,6 @@
(ур. 3, 100 монет) возвращает размер прежнего старта 32×20, второе
(ур. 12, 250 монет) — до 40×28; у существующих садов предметы, оказавшиеся
за новой границей, при первом заходе переставляются по спирали к дому.
-- **Ачивки с тирами**: у каждой ачивки ступени I/II/III (например, 10/50/100
- задач), прогресс к следующей ступени виден всегда. Набор: вехи закрытий,
- уровни, макс. задач за день, разные проекты, суммарные часы, строгие
- дедлайны, закрытия через выдачу задачи (см. 3.9), регулярная, проект.
- **Описание по клику (0.49)**: карточка-ачивка — триггер GnPopover кита,
- клик открывает панель с кратким описанием условия (`garden.achHint`,
- i18n ×3; тултип по hover не годится — не работает на тач-экранах).
-- **Связка с выдачей задачи** (3.9): закрытие задачи, открытой из выдачи, помечается
- (`earned_via`) и празднуется меткой «выбрал и сделал» + счётчик-ачивкой;
- XP при этом не меняется.
-- **Страница «Статистика»**: закрываемость за выбранный **день / неделю / месяц**
- (период листается стрелками назад/вперёд). Метрики — закрытые задачи, XP,
- созданные задачи, часы работы, «дни с результатом» — каждая со сравнением
- с **предыдущим аналогичным периодом** (дельта и проценты). Анимированный
- **сравнительный** график закрытых задач по бакетам периода (часы / дни недели /
- дни месяца): текущий и предыдущий период — пары столбиков. **Сетка активности**
- «как в гитхабе» за полгода или год (переключатель диапазона; цвет по закрытым
- задачам или XP, переключается).
- Данные считаются из `xp_events` и дат задач, без новых эндпоинтов.
-- **Метка «ментально сложная»**: у незакрытой задачи есть кнопка-переключатель
- **«Не хочется делать»** (warning-стиль) — она ставит метку «ментально сложная»:
- трудность не по объёму, а из-за требуемых волевых усилий. Метку можно снять до
- закрытия, после закрытия она не меняется. Закрытие такой задачи даёт **+10 XP**
- (сила воли) и всегда празднуется золотым конфетти; ачивка **«Преодоление»**
- (5/25/50 закрытых «ментально сложных») — в саду.
-- **Справка «О саде»**: кнопка на странице сада открывает модалку с объяснением
- механик (XP, уровни, растения и редкости, ачивки, принцип «только позитив»).
### 3.14. Реактивность: серверные события (SSE)
@@ -436,17 +467,20 @@
Трейлинг-debounce 300 мс коалесцирует бёрсты (закрытие задачи = 2–3 события).
Виды подписываются на свои kind'ы: стек/список — `task.changed`,
`task.deleted`, `detail.changed`, `project.changed`, `project.deleted`;
- статистика — `xp.changed`, `task.changed`; сад — `garden.changed`, `xp.changed`,
- `task.changed` (рефetch подавляется на время drag по сцене); страница задачи —
+ статистика и «Достижения» — `xp.changed`, `task.changed`; сад (код сохранён,
+ страница отключена в 0.58) — `garden.changed`, `xp.changed`, `task.changed`
+ (рефetch подавляется на время drag по сцене); страница задачи —
перечитывается при `task.changed`/`detail.changed` своего `id`.
+ `garden.changed` продолжает публиковаться, но слушателей в UI больше нет —
+ событие безвредно (0.58).
- **Точки публикации** (строго после коммита транзакции): HTTP-хендлеры задач,
проектов, сада, XP-дейли и настроек; MCP-инструменты и фоновая детализация —
broadcast (всем вкладкам). Список kind'ов: `task.changed {id}`,
`task.deleted {id}`, `detail.changed {id}`, `project.changed {id}`,
`project.deleted {id}`, `garden.changed {reason}`, `xp.changed {amount?,
celebrate}`, `settings.changed`, `ready` (первый кадр), `ping`.
- Упрощение: закрытие задачи через MCP не шлёт `garden.changed` — растение
- создаётся лениво, refetch по `task.changed` покрывает.
+ Упрощение: закрытие задачи через MCP не шлёт `garden.changed` — refetch по
+ `task.changed` покрывает (сад отключён в 0.58, слушателей у события нет).
- **Дедуп XP-тостов**: действие своей вкладки празднуется по заголовкам ответа
(`X-Earned-XP`, ТЗ 3.13); SSE-эхо и эхо соседней вкладки молчат — окно
активности 3 с (localStorage). Соседние вкладки показывают скромный тост
@@ -487,7 +521,8 @@
манифест не читается), `black-translucent` статус-бар, короткий тайтл;
- тап-подсветка WebKit отключена (у кита есть `:active`-состояния);
- кнопка fullscreen сада скрыта, если API не поддерживается (iPhone Safari
- умеет fullscreen только для video).
+ умеет fullscreen только для video). *(Сад отключён в 0.58 — правило
+ историческое, вернётся вместе с садом.)*
### 3.16. Вебхуки системы авторизации (0.35, профиль 0.36)
@@ -571,12 +606,13 @@
мелкие контролы/инсеты — 3px; темы Verdant/Ember/Mono и дизайн-токены `--gn-*`
доступны, но не используются пока в gntodo).
- Кастомные (сырые) блоки интерфейса оформлены в том же стиле, что и компоненты
- кита — те же бордеры/радиусы: дерево уровней и маркет сада, ачивки, график
- и heatmap статистики, оверлеи сцены сада (кнопки зума, подписи объектов,
- инвентарь), блок «свежий токен» настроек, полнотекстовые превью и миниатюры
- картинок, tags-fieldset формы задачи.
+ кита — те же бордеры/радиусы: дерево званий и ачивки на «Достижениях», график
+ и heatmap статистики, блок «свежий токен» настроек, полнотекстовые превью
+ и миниатюры картинок, tags-fieldset формы задачи; при возврате сада — маркет,
+ дерево уровней с unlocks и оверлеи сцены (кнопки зума, подписи объектов,
+ инвентарь).
- Смысловые состояния сырых блоков подчёркиваются подкрашенным бордером и лёгкой
- подложкой в цвет состояния (текущий уровень сада, разблокированные ачивки) —
+ подложкой в цвет состояния (текущий уровень, разблокированные ачивки) —
без `!important`-войн с китом: только каскадное переопределение поверх
публичных классов.
- Иконки в заголовках карточек — в цвете текста заголовка (раньше затемнялись).
@@ -626,7 +662,9 @@
Мультиюзерность (с 0.34): на задачах, проектах, тегах, документах, XP/монетах,
саде и настройках есть `user_id` (nullable — NULL забирается первым вошедшим,
-см. 1.2); все запросы API/сервисов/MCP-тулов скоупятся по нему.
+см. 1.2); все запросы API/сервисов/MCP-тулов скоупятся по нему. Модель сада
+(0.58) сохранена без изменений — раздел отключён, данные ждут возврата;
+XP/монеты и `rarity` продолжают начисляться, как раньше.
```
User — пользователь SSO: id (= user_id от SSO), email, name (display_name),
@@ -674,15 +712,19 @@
XpEvent — начисление XP за закрытую задачу (идемпотентно по task_id):
amount, rarity (common | rare | epic), via_options
-CoinEvent — журнал монет сада (заработок и траты; баланс = Σ amount):
+CoinEvent — журнал монет (заработок и траты; баланс = Σ amount):
task_id (nullable), level (nullable — бонус уровня),
- source (task | level | shop), amount (+/−), item_key (ключ
- покупки для трат), created_at
+ source (task | level | daily | shop), amount (+/−), item_key
+ (ключ покупки для трат), created_at. В 0.58 источник баланса
+ для UI — `GET /api/xp` (поле coins); траты (source=shop)
+ возможны только из сада
GardenItem — элемент сцены сада: kind (plant | decoration),
ref_id (→ xp_events для растений; уникально в паре с kind),
item_key (вид растения / ключ декорации), x, y (ячейки сетки),
- stage (0 росток, 1 куст, 2 цветение)
+ stage (0 росток, 1 куст, 2 цветение). С 0.58 новые записи
+ не создаются: ленивая ensure_garden вызывается только из
+ `GET /api/garden`, который UI не зовёт
AppSetting — per-user настройки (валюта, язык): PK (user_id, key), value
AppSettingGlobal — таблица-наследие однопользовательской версии
@@ -735,6 +777,7 @@
5. ~~**Модель для Ollama**~~ — **решено 2026-09-21**: по умолчанию `qwen3.5:2b-q4_K_M` (проверена на M2); меняется конфигом (`OLLAMA_MODEL`) без правок кода. Ollama — внешний сервер, адрес — `OLLAMA_BASE_URL`.
6. **Практическое различие `cancelled` / `deferred`**: определения зафиксированы (3.4), но поведение в интерфейсе почти одинаковое — скрыть из активных списков и оставить доступным для возврата. Уточнить при проектировании представлений, нужны ли оба статуса или их поведение сольётся.
7. ~~**Анти-дюп награды за статус**~~ — **решено 2026-09-21**: закрытие (HTTP и MCP) идёт через общий путь; XP — один раз на задачу (grant_task_xp), спавн регулярной — один раз на цепочку (метка `spawned_at`), выход из done сбрасывает `done_at`. Повторные закрытия и обновления закрытой не дублируют награды и экземпляры — покрыто тестами.
-8. ~~**Пиксель-арт сцена сада**~~ — **решено 2026-09-20**: сцена переведена на пиксель-арт (PixiJS 8, WebGL); спрайты генерируются из пиксель-матриц в коде (без внешних ассетов и лицензий), см. 3.13.
-9. **Premium-виды в семенах**: premium-виды (сейчас кактус) рождаются только из rare/epic закрытий — «отпечаток крупной работы». Семена же продаются за монеты и дают common-растение; сейчас семя premium-вида купить можно. Оставить ли покупку семян premium-видов (простой доступ к виду) или запретить (эксклюзив rare/epic сохраняется) — решить.
-10. **Самосоединяющийся забор**: секции изгороди автоматически соединяются с соседними секциями (маска соседей N/E/S/W → текстура); ручного поворота нет. Соединяются только соседние секции забора — диагонали не считаются. Подтвердить, что поведение устраивает.
\ No newline at end of file
+8. ~~**Пиксель-арт сцена сада**~~ — **решено 2026-09-20**: сцена переведена на пиксель-арт (PixiJS 8, WebGL); спрайты генерируются из пиксель-матриц в коде (без внешних ассетов и лицензий), см. 3.13. **Отложено вместе с садом (0.58)** — код сохранён, вернётся при доработке сада.
+9. **Premium-виды в семенах** — **отложено вместе с садом (0.58)**: premium-виды (сейчас кактус) рождаются только из rare/epic закрытий — «отпечаток крупной работы». Семена же продаются за монеты и дают common-растение; сейчас семя premium-вида купить можно. Оставить ли покупку семян premium-видов (простой доступ к виду) или запретить (эксклюзив rare/epic сохраняется) — решить при возврате сада.
+10. **Самосоединяющийся забор** — **отложено вместе с садом (0.58)**: секции изгороди автоматически соединяются с соседними секциями (маска соседей N/E/S/W → текстура); ручного поворота нет. Соединяются только соседние секции забора — диагонали не считаются. Подтвердить при возврате сада.
+11. **Возврат сада** (0.58): когда основной функционал будет в порядке, вернуть сад к доработке — выдача растений включится сама, если UI снова начнёт звать `GET /api/garden`; вопросы — что переделать в саду (сцена, маркет, прогрессия) — решать тогда, на накопленных данных.
\ No newline at end of file
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index e9f77e5..757df00 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -24,7 +24,7 @@
{ id: 'options', label: t('nav.options'), icon: 'ph-shuffle', to: '/options' },
{ id: 'list', label: t('nav.list'), icon: 'ph-list-bullets', to: '/list' },
{ id: 'projects', label: t('nav.projects'), icon: 'ph-folders', to: '/projects' },
- { id: 'garden', label: t('nav.garden'), icon: 'ph-leaf', to: '/garden' },
+ { id: 'achievements', label: t('nav.achievements'), icon: 'ph-trophy', to: '/achievements' },
{ id: 'stats', label: t('nav.stats'), icon: 'ph-chart-bar', to: '/stats' },
// архив — редко используемый раздел, предпоследний пункт
{ id: 'archive', label: t('nav.archive'), icon: 'ph-archive', to: '/archive' },
diff --git a/frontend/src/api.ts b/frontend/src/api.ts
index 1d81895..3abf5c3 100644
--- a/frontend/src/api.ts
+++ b/frontend/src/api.ts
@@ -101,6 +101,8 @@
level_span: number
level_percent: number
completed_count: number
+ /** Баланс монет (ТЗ 3.13): отдаётся здесь, т.к. /api/garden UI больше не зовёт (0.58) */
+ coins: number
}
export interface XpEvent {
@@ -226,8 +228,8 @@
// Геймификация последнего закрывающего запроса (забирается через takeEarnedXp)
export interface EarnedReward {
xp: number
- coins: number // монеты сада — вторая награда за закрытие
- rarity: string // common | rare | epic
+ coins: number // монеты — вторая награда за закрытие (копятся на «Достижениях»)
+ rarity: string // common | rare | epic — история для возврата сада (0.58), в UI не показывается
options: boolean // закрыто из режима «3 вариантов»
hard: boolean // «ментально сложная» — награда за силу воли
}
diff --git a/frontend/src/components/AchievementsPanel.vue b/frontend/src/components/AchievementsPanel.vue
new file mode 100644
index 0000000..dd09279
--- /dev/null
+++ b/frontend/src/components/AchievementsPanel.vue
@@ -0,0 +1,198 @@
+
+
+
+
+
+
+ {{ t('progress.achievements') }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/gamification.ts b/frontend/src/gamification.ts
index f28cb79..f86bc74 100644
--- a/frontend/src/gamification.ts
+++ b/frontend/src/gamification.ts
@@ -1,6 +1,6 @@
// Геймификация (ТЗ 3.13): микронаграда за закрытие задачи — конфетти
-// и тост «+N XP» с похвалой. Только позитив: ничего не сгорает, серий нет.
-// Особые закрытия (редкое растение, крупная задача, режим «3 вариантов»)
+// и тост «+N XP • +N монет» с похвалой. Только позитив: ничего не сгорает,
+// серий нет. Особые закрытия (крупная задача, «не хочется делать»)
// празднуются «золотым» конфетти и расширенным тостом.
import { takeCreatedReward, takeEarnedXp } from './api'
@@ -49,11 +49,10 @@
window.setTimeout(() => layer.remove(), 2600)
}
-// Текст тоста: похвала + приписки за редкое растение, режим «3 вариантов» и силу воли
+// Текст тоста: похвала + приписка за силу воли.
+// rarity награды (история для возврата сада, 0.58) в тексте не участвует.
function rewardText(r: EarnedReward): string {
const parts: string[] = []
- if (r.rarity === 'epic') parts.push(i18n.global.t('gamify.epicPlant'))
- else if (r.rarity === 'rare') parts.push(i18n.global.t('gamify.rarePlant'))
if (r.hard) parts.push(i18n.global.t('gamify.willpowerText'))
parts.push(randomPraise())
return parts.filter(Boolean).join(' ')
@@ -64,8 +63,9 @@
export function celebrateEarned(): void {
const reward = takeEarnedXp()
if (!reward || !reward.xp) return
- // «Ментально сложная» празднуется золотом всегда — закрыть через силу само по себе повод
- const special = reward.rarity !== 'common' || reward.xp >= 25 || reward.hard
+ // «Ментально сложная» празднуется золотом всегда — закрыть через силу само по себе повод;
+ // крупное закрытие (xp >= 25) — тоже
+ const special = reward.xp >= 25 || reward.hard
confetti(special)
const titleParts = [`+${reward.xp} XP`, `+${reward.coins} ${i18n.global.t('gamify.coins')}`]
if (reward.options) titleParts.push(i18n.global.t('gamify.chosen'))
diff --git a/frontend/src/locales/en.ts b/frontend/src/locales/en.ts
index e374db9..5dcbfe0 100644
--- a/frontend/src/locales/en.ts
+++ b/frontend/src/locales/en.ts
@@ -43,7 +43,7 @@
home: 'Home',
stack: 'Inbox stack',
list: 'Task list',
- garden: 'Garden',
+ achievements: 'Achievements',
stats: 'Statistics',
projects: 'Projects',
archive: 'Archive',
@@ -51,34 +51,32 @@
settings: 'Settings',
extension: 'Extension',
},
- garden: {
- title: 'Garden',
+ // Progress: shared by the "Achievements" page and the garden (the garden is
+ // disabled in 0.58 and will return — wording stays neutral, no plants)
+ progress: {
+ title: 'Achievements',
level: 'Level {n}',
xpTotal: '{n} XP',
- plants: 'plants: {n}',
levelProgress: 'To next level: {xp} / {span} XP',
aboutOpen: 'How it works',
- aboutTitle: 'How the garden works',
- levelsTitle: 'Level tree',
+ aboutTitle: 'How it works',
+ levelsTitle: 'Rank tree',
levelsHint:
- 'XP thresholds and rewards of each level: new plant species (seed price) and map expansions. Levels grow from task XP — nothing expires.',
+ 'XP thresholds and ranks of each level. Levels grow from task XP — nothing expires and nothing is taken away.',
levelCurrent: 'current',
+ achievements: 'Achievements',
+ coins: 'coins: {n}',
+ coinsHint: 'Coins for completed tasks, created tasks and projects, new levels — they accumulate',
achDone: 'All steps completed (highest — {r})',
achStep: 'Step: roman numeral, fraction — progress to the next',
- rewardCreateTask: 'task created',
- rewardCreateProject: 'project created',
about: [
- 'Every completed task leaves a plant on the bed — the garden only grows and never shrinks.',
+ 'Every completed task earns experience and coins — progress only grows and never shrinks.',
'Completing a task earns XP: base 10 + a priority bonus (up to 15) + a size bonus from the time estimate (up to 15) + 10 for a “mentally hard” task — an honest reward for willpower. Bigger and more urgent work is worth more.',
'Earned XP raises your level — from Sprout to Garden Legend. Levels never drop: XP does not expire and is not taken away, even if a task is deleted.',
- 'Sometimes a task yields a rare plant — the chance grows with the task weight. Epic plants only come from the biggest completions.',
- 'The general rule: experience unlocks, coins buy. Experience (level) unlocks new plant species and land; coins are spent on plant growth, decorations and map expansion. Coins come from completed tasks (half the XP) and from reaching new levels.',
+ 'Coins come from completed tasks (as much as the XP), from creating tasks and projects, and from new levels. For now they just accumulate — they will be useful when the garden returns.',
'Achievements mark milestones: tasks completed, levels, different projects, hours of work, strict deadlines, “mentally hard” tasks. Each has steps I, II, III with always-visible progress.',
'A task completed from the “3 options” mode earns the “chose and did” mark. Closing a “don’t want to do it” task is always celebrated in gold — the willpower was worth it. Nothing expires, no streaks or penalties — only pleasant rewards.',
],
- lastReward: 'Latest reward for: {title} — {date}',
- emptyTitle: 'The garden is empty',
- emptyText: 'Complete your first task — a plant will grow on the bed.',
levels: [
'Sprout',
'Seedling',
@@ -94,7 +92,43 @@
'Keeper of legends',
'Garden legend',
],
- achievements: 'Achievements',
+ ach: {
+ firstDone: 'First sprout',
+ done10: 'Dozen',
+ level5: 'Heights',
+ sprinter: 'Sprinter',
+ explorer: 'All-rounder',
+ marathon: 'Marathoner',
+ deadlineBoss: 'Deadline boss',
+ chooser: 'Chose and did',
+ willpower: 'Willpower',
+ recurring: 'Rhythm of life',
+ projectDone: 'Project delivered',
+ },
+ achHint: {
+ firstDone: 'Complete your first task — the milestone is yours.',
+ done10: 'Complete 10, 50 and 100 tasks — one tier per milestone.',
+ level5: 'Reach level 5, 10 and 15.',
+ sprinter: 'Complete 3, 10 and 25 tasks in a single day.',
+ explorer: 'Have completed tasks in 3, 5 and 8 different projects.',
+ marathon: 'Accumulate 10, 50 and 100 hours on tasks — actual or estimated time.',
+ deadlineBoss: 'Complete 5, 15 and 30 tasks with a strict deadline date.',
+ chooser: 'Complete 5, 25 and 50 tasks picked from the “3 options” mode.',
+ willpower: 'Complete 5, 25 and 50 tasks marked “don’t feel like doing it”.',
+ recurring: 'Complete a recurring task.',
+ projectDone: 'Finish a project: all of its tasks (three or more) completed.',
+ },
+ },
+ garden: {
+ title: 'Garden',
+ plants: 'plants: {n}',
+ levelsHint:
+ 'XP thresholds and rewards of each level: new plant species (seed price) and map expansions. Levels grow from task XP — nothing expires.',
+ rewardCreateTask: 'task created',
+ rewardCreateProject: 'project created',
+ lastReward: 'Latest reward for: {title} — {date}',
+ emptyTitle: 'The garden is empty',
+ emptyText: 'Complete your first task — a plant will grow on the bed.',
coins: 'coins: {n}',
coinsHint: 'Garden currency: spent on plant growth and decorations',
shop: 'Market',
@@ -157,32 +191,6 @@
greenhouse: 'Greenhouse',
gazebo: 'Gazebo',
},
- ach: {
- firstDone: 'First sprout',
- done10: 'Dozen',
- level5: 'Heights',
- sprinter: 'Sprinter',
- explorer: 'All-rounder',
- marathon: 'Marathoner',
- deadlineBoss: 'Deadline boss',
- chooser: 'Chose and did',
- willpower: 'Willpower',
- recurring: 'Rhythm of life',
- projectDone: 'Project delivered',
- },
- achHint: {
- firstDone: 'Complete your first task — the first plant grows in your garden.',
- done10: 'Complete 10, 50 and 100 tasks — one tier per milestone.',
- level5: 'Reach gardener level 5, 10 and 15.',
- sprinter: 'Complete 3, 10 and 25 tasks in a single day.',
- explorer: 'Have completed tasks in 3, 5 and 8 different projects.',
- marathon: 'Accumulate 10, 50 and 100 hours on tasks — actual or estimated time.',
- deadlineBoss: 'Complete 5, 15 and 30 tasks with a strict deadline date.',
- chooser: 'Complete 5, 25 and 50 tasks picked from the “3 options” mode.',
- willpower: 'Complete 5, 25 and 50 tasks marked “don’t feel like doing it”.',
- recurring: 'Complete a recurring task.',
- projectDone: 'Finish a project: all of its tasks (three or more) completed.',
- },
},
gamify: {
praise: [
@@ -193,8 +201,6 @@
'Keep it up!',
'One task down, mood up.',
],
- rarePlant: 'A rare plant has grown on your bed!',
- epicPlant: 'A legendary plant in your garden!',
chosen: 'chose and did',
willpower: 'willpower',
willpowerText: 'Willpower! It was worth it.',
diff --git a/frontend/src/locales/ru.ts b/frontend/src/locales/ru.ts
index 2da7fec..2554523 100644
--- a/frontend/src/locales/ru.ts
+++ b/frontend/src/locales/ru.ts
@@ -44,7 +44,7 @@
home: 'Главная',
stack: 'Стек входящих',
list: 'Список задач',
- garden: 'Сад',
+ achievements: 'Достижения',
stats: 'Статистика',
projects: 'Проекты',
archive: 'Архив',
@@ -52,34 +52,32 @@
settings: 'Настройки',
extension: 'Расширение',
},
- garden: {
- title: 'Сад',
+ // Прогресс: общий для страницы «Достижения» и сада (сад отключён в 0.58,
+ // вернётся позже — тексты нейтральные, без растений и «садовника»)
+ progress: {
+ title: 'Достижения',
level: 'Уровень {n}',
xpTotal: '{n} XP',
- plants: 'растений: {n}',
levelProgress: 'До уровня: {xp} / {span} XP',
aboutOpen: 'Как это работает',
- aboutTitle: 'Как устроен сад',
- levelsTitle: 'Дерево уровней',
+ aboutTitle: 'Как это работает',
+ levelsTitle: 'Дерево званий',
levelsHint:
- 'Пороги XP и награды каждого уровня: новые виды растений (цена семени) и расширения карты. Уровень растёт из опыта за задачи — ничего не сгорает.',
+ 'Пороги XP и звания каждого уровня. Уровень растёт из опыта за задачи — ничего не сгорает и не отнимается.',
levelCurrent: 'текущий',
+ achievements: 'Достижения',
+ coins: 'монет: {n}',
+ coinsHint: 'Монеты за закрытые задачи, создание задач и проектов, новые уровни — копятся',
achDone: 'Все ступени пройдены (высшая — {r})',
achStep: 'Ступень: римская — номер, дробь — прогресс к следующей',
- rewardCreateTask: 'создание задачи',
- rewardCreateProject: 'создание проекта',
about: [
- 'Каждая закрытая задача оставляет на грядке растение — сад только растёт и никогда не уменьшается.',
+ 'Каждая закрытая задача приносит опыт и монеты — прогресс только растёт и никогда не уменьшается.',
'За закрытие начисляется XP: база 10 + бонус за приоритет задачи (до 15) + бонус за объём работы по оценке времени (до 15) + 10 за «ментально сложную» — честная награда за силу воли. Крупные и срочные дела ценнее.',
'Накопленный XP повышает уровень — от «Ростка» до «Легенды сада». Уровень не падает: XP не сгорает и не отнимается, даже если задачу удалить.',
- 'Иногда задача приносит редкое растение — шанс тем выше, чем «весомее» задача. Эпические растения достаются только крупным закрытиям.',
- 'Общий принцип: опыт открывает, монеты покупают. Опыт (уровень) открывает новые виды растений и землю; монеты тратятся на рост растений, декорации и расширение карты. Монеты приходят за закрытые задачи (половина XP) и за новые уровни.',
+ 'Монеты приходят за закрытые задачи (столько же, сколько XP), за создание задач и проектов и за новые уровни. Сейчас они копятся — пригодятся, когда сад вернётся.',
'Достижения отмечают вехи: закрытые задачи, уровни, разные проекты, часы работы, строгие дедлайны, «ментально сложные» задачи. У каждой ступени I, II, III — прогресс виден всегда.',
'Задача, закрытая из режима «3 вариантов», отмечается меткой «выбрал и сделал». Закрытие «не хочется делать» празднуется золотом — сила воли того стоила. Ничего не сгорает, серий и наказаний нет — только приятные награды.',
],
- lastReward: 'Последняя награда за: {title} — {date}',
- emptyTitle: 'Сад пока пуст',
- emptyText: 'Закройте первую задачу — на грядке появится растение.',
levels: [
'Росток',
'Сеянцев',
@@ -95,7 +93,43 @@
'Хранитель легенд',
'Легенда сада',
],
- achievements: 'Достижения',
+ ach: {
+ firstDone: 'Первый росток',
+ done10: 'Десяток',
+ level5: 'Высоты',
+ sprinter: 'Спринтер',
+ explorer: 'Разносторонний',
+ marathon: 'Марафонец',
+ deadlineBoss: 'Дедлайн-босс',
+ chooser: 'Выбрал и сделал',
+ willpower: 'Преодоление',
+ recurring: 'Ритм жизни',
+ projectDone: 'Проект под ключ',
+ },
+ achHint: {
+ firstDone: 'Закройте первую задачу — веха открыта.',
+ done10: 'Закройте 10, 50 и 100 задач — по ступени за каждую веху.',
+ level5: 'Достигните 5, 10 и 15 уровня.',
+ sprinter: 'Закройте 3, 10 и 25 задач за один день.',
+ explorer: 'Имейте закрытые задачи в 3, 5 и 8 разных проектах.',
+ marathon: 'Накопите 10, 50 и 100 часов работы над задачами — по факту или оценке.',
+ deadlineBoss: 'Закройте 5, 15 и 30 задач со строгим дедлайном-датой.',
+ chooser: 'Закройте 5, 25 и 50 задач, взятых из режима «3 вариантов».',
+ willpower: 'Закройте 5, 25 и 50 задач с меткой «не хочется делать».',
+ recurring: 'Закройте регулярную задачу.',
+ projectDone: 'Завершите проект целиком: все его задачи (от трёх) закрыты.',
+ },
+ },
+ garden: {
+ title: 'Сад',
+ plants: 'растений: {n}',
+ levelsHint:
+ 'Пороги XP и награды каждого уровня: новые виды растений (цена семени) и расширения карты. Уровень растёт из опыта за задачи — ничего не сгорает.',
+ rewardCreateTask: 'создание задачи',
+ rewardCreateProject: 'создание проекта',
+ lastReward: 'Последняя награда за: {title} — {date}',
+ emptyTitle: 'Сад пока пуст',
+ emptyText: 'Закройте первую задачу — на грядке появится растение.',
coins: 'монет: {n}',
coinsHint: 'Валюта сада: тратится на рост растений и декорации',
shop: 'Маркет',
@@ -157,32 +191,6 @@
greenhouse: 'Теплица',
gazebo: 'Беседка',
},
- ach: {
- firstDone: 'Первый росток',
- done10: 'Десяток',
- level5: 'Высоты',
- sprinter: 'Спринтер',
- explorer: 'Разносторонний',
- marathon: 'Марафонец',
- deadlineBoss: 'Дедлайн-босс',
- chooser: 'Выбрал и сделал',
- willpower: 'Преодоление',
- recurring: 'Ритм жизни',
- projectDone: 'Проект под ключ',
- },
- achHint: {
- firstDone: 'Закройте первую задачу — в саду вырастет первое растение.',
- done10: 'Закройте 10, 50 и 100 задач — по ступени за каждую веху.',
- level5: 'Достигните 5, 10 и 15 уровня садовника.',
- sprinter: 'Закройте 3, 10 и 25 задач за один день.',
- explorer: 'Имейте закрытые задачи в 3, 5 и 8 разных проектах.',
- marathon: 'Накопите 10, 50 и 100 часов работы над задачами — по факту или оценке.',
- deadlineBoss: 'Закройте 5, 15 и 30 задач со строгим дедлайном-датой.',
- chooser: 'Закройте 5, 25 и 50 задач, взятых из режима «3 вариантов».',
- willpower: 'Закройте 5, 25 и 50 задач с меткой «не хочется делать».',
- recurring: 'Закройте регулярную задачу.',
- projectDone: 'Завершите проект целиком: все его задачи (от трёх) закрыты.',
- },
},
gamify: {
praise: [
@@ -193,8 +201,6 @@
'Так держать!',
'Минус одна задача, плюс настроение.',
],
- rarePlant: 'На грядке редкое растение!',
- epicPlant: 'Легендарное растение в саду!',
chosen: 'выбрал и сделал',
willpower: 'сила воли',
willpowerText: 'Сила воли! Это того стоило.',
diff --git a/frontend/src/locales/uk.ts b/frontend/src/locales/uk.ts
index 689b38a..a562988 100644
--- a/frontend/src/locales/uk.ts
+++ b/frontend/src/locales/uk.ts
@@ -43,7 +43,7 @@
home: 'Головна',
stack: 'Вхідний стек',
list: 'Список задач',
- garden: 'Сад',
+ achievements: 'Досягнення',
stats: 'Статистика',
projects: 'Проєкти',
archive: 'Архів',
@@ -51,34 +51,32 @@
settings: 'Налаштування',
extension: 'Розширення',
},
- garden: {
- title: 'Сад',
+ // Прогрес: спільний для сторінки «Досягнення» та саду (сад вимкнено в 0.58,
+ // повернеться пізніше — тексти нейтральні, без рослин і «садівника»)
+ progress: {
+ title: 'Досягнення',
level: 'Рівень {n}',
xpTotal: '{n} XP',
- plants: 'рослин: {n}',
levelProgress: 'До наступного рівня: {xp} / {span} XP',
aboutOpen: 'Як це працює',
- aboutTitle: 'Як влаштований сад',
- levelsTitle: 'Дерево рівнів',
+ aboutTitle: 'Як це працює',
+ levelsTitle: 'Дерево звань',
levelsHint:
- 'Пороги XP та нагороди кожного рівня: нові види рослин (ціна насіння) й розширення мапи. Рівень росте з досвіду за завдання — нічого не згорає.',
+ 'Пороги XP та звання кожного рівня. Рівень росте з досвіду за завдання — нічого не згорає й не віднімається.',
levelCurrent: 'поточний',
+ achievements: 'Досягнення',
+ coins: 'монет: {n}',
+ coinsHint: 'Монети за виконані завдання, створення завдань і проєктів, нові рівні — накопичуються',
achDone: 'Усі щаблі пройдено (вищий — {r})',
achStep: 'Щабель: римська — номер, дріб — прогрес до наступного',
- rewardCreateTask: 'створення завдання',
- rewardCreateProject: 'створення проєкту',
about: [
- 'Кожне виконане завдання залишає на грядці рослину — сад лише зростає і ніколи не зменшується.',
+ 'Кожне виконане завдання приносить досвід і монети — прогрес лише зростає і ніколи не зменшується.',
'За виконання нараховується XP: база 10 + бонус за пріоритет (до 15) + бонус за обсяг за оцінкою часу (до 15) + 10 за «ментально складне» — чесна нагорода за силу волі. Великі й термінові справи цінніші.',
'Накопичений XP підвищує рівень — від «Паростка» до «Легенди саду». Рівень не падає: XP не згорає і не віднімається, навіть якщо завдання видалити.',
- 'Іноді завдання приносить рідкісну рослину — шанс тим вищий, що «важче» завдання. Епічні рослини дістаються лише великим завершенням.',
- 'Загальний принцип: досвід відкриває, монети купують. Досвід (рівень) відкриває нові види рослин і землю; монети витрачаються на ріст рослин, декорації та розширення карти. Монети надходять за виконані завдання (половина XP) та за нові рівні.',
+ 'Монети надходять за виконані завдання (стільки ж, скільки XP), за створення завдань і проєктів та за нові рівні. Поки що вони накопичуються — стануть у пригоді, коли сад повернеться.',
'Досягнення відзначають віхи: виконані завдання, рівні, різні проєкти, години роботи, строгі дедлайни, «ментально складні» завдання. У кожного ступені I, II, III — прогрес видно завжди.',
'Завдання, виконане з режиму «3 варіанти», відзначається меткою «обрав і зробив». Закриття «не хочеться робити» завжди святкується золотом — сила волі того варта. Нічого не згорає, серій і покарань немає — лише приємні нагороди.',
],
- lastReward: 'Остання нагорода за: {title} — {date}',
- emptyTitle: 'Сад поки порожній',
- emptyText: 'Закрийте перше завдання — на грядці зросте рослина.',
levels: [
'Паросток',
'Сіянець',
@@ -94,7 +92,43 @@
'Хранитель легенд',
'Легенда саду',
],
- achievements: 'Досягнення',
+ ach: {
+ firstDone: 'Перший паросток',
+ done10: 'Десятка',
+ level5: 'Висоти',
+ sprinter: 'Спринтер',
+ explorer: 'Різнобічний',
+ marathon: 'Марафонець',
+ deadlineBoss: 'Дедлайн-бос',
+ chooser: 'Обрав і зробив',
+ willpower: 'Подолання',
+ recurring: 'Ритм життя',
+ projectDone: 'Проєкт під ключ',
+ },
+ achHint: {
+ firstDone: 'Закрийте перше завдання — віху відкрито.',
+ done10: 'Закрийте 10, 50 і 100 завдань — по ступені за кожну віху.',
+ level5: 'Досягніть 5, 10 і 15 рівня.',
+ sprinter: 'Закрийте 3, 10 і 25 завдань за один день.',
+ explorer: 'Майте закриті завдання в 3, 5 і 8 різних проєктах.',
+ marathon: 'Накопичите 10, 50 і 100 годин роботи над завданнями — за фактом або оцінкою.',
+ deadlineBoss: 'Закрийте 5, 15 і 30 завдань із суворим дедлайном-датою.',
+ chooser: 'Закрийте 5, 25 і 50 завдань, узятих із режиму «3 варіанти».',
+ willpower: 'Закрийте 5, 25 і 50 завдань із міткою «не хочеться робити».',
+ recurring: 'Закрийте регулярне завдання.',
+ projectDone: 'Завершіть проєкт повністю: усі його завдання (від трьох) закриті.',
+ },
+ },
+ garden: {
+ title: 'Сад',
+ plants: 'рослин: {n}',
+ levelsHint:
+ 'Пороги XP та нагороди кожного рівня: нові види рослин (ціна насіння) й розширення мапи. Рівень росте з досвіду за завдання — нічого не згорає.',
+ rewardCreateTask: 'створення завдання',
+ rewardCreateProject: 'створення проєкту',
+ lastReward: 'Остання нагорода за: {title} — {date}',
+ emptyTitle: 'Сад поки порожній',
+ emptyText: 'Закрийте перше завдання — на грядці зросте рослина.',
coins: 'монет: {n}',
coinsHint: 'Валюта саду: витрачається на ріст рослин і декорації',
shop: 'Маркет',
@@ -157,32 +191,6 @@
greenhouse: 'Теплиця',
gazebo: 'Альтанка',
},
- ach: {
- firstDone: 'Перший паросток',
- done10: 'Десятка',
- level5: 'Висоти',
- sprinter: 'Спринтер',
- explorer: 'Різнобічний',
- marathon: 'Марафонець',
- deadlineBoss: 'Дедлайн-бос',
- chooser: 'Обрав і зробив',
- willpower: 'Подолання',
- recurring: 'Ритм життя',
- projectDone: 'Проєкт під ключ',
- },
- achHint: {
- firstDone: 'Закрийте перше завдання — у саду виросте перша рослина.',
- done10: 'Закрийте 10, 50 і 100 завдань — по ступені за кожну віху.',
- level5: 'Досягніть 5, 10 і 15 рівня садівника.',
- sprinter: 'Закрийте 3, 10 і 25 завдань за один день.',
- explorer: 'Майте закриті завдання в 3, 5 і 8 різних проєктах.',
- marathon: 'Накопичите 10, 50 і 100 годин роботи над завданнями — за фактом або оцінкою.',
- deadlineBoss: 'Закрийте 5, 15 і 30 завдань із суворим дедлайном-датою.',
- chooser: 'Закрийте 5, 25 і 50 завдань, узятих із режиму «3 варіанти».',
- willpower: 'Закрийте 5, 25 і 50 завдань із міткою «не хочеться робити».',
- recurring: 'Закрийте регулярне завдання.',
- projectDone: 'Завершіть проєкт повністю: усі його завдання (від трьох) закриті.',
- },
},
gamify: {
praise: [
@@ -193,8 +201,6 @@
'Так тримати!',
'Мінус одне завдання, плюс настрій.',
],
- rarePlant: 'На грядці зросла рідкісна рослина!',
- epicPlant: 'Легендарна рослина в саду!',
chosen: 'обрав і зробив',
willpower: 'сила волі',
willpowerText: 'Сила волі! Того варто було.',
diff --git a/frontend/src/main.ts b/frontend/src/main.ts
index c06e424..b381376 100644
--- a/frontend/src/main.ts
+++ b/frontend/src/main.ts
@@ -13,7 +13,7 @@
import { setPageTitle } from './taskui'
import ListView from './views/ListView.vue'
import ArchiveView from './views/ArchiveView.vue'
-import GardenView from './views/GardenView.vue'
+import AchievementsView from './views/AchievementsView.vue'
import StatsView from './views/StatsView.vue'
import OptionsView from './views/OptionsView.vue'
import ProjectView from './views/ProjectView.vue'
@@ -34,7 +34,10 @@
// слаг stack устарел (страница теперь главная) — старые ссылки ведём на /
{ path: '/stack', redirect: '/' },
{ path: '/list', component: ListView, meta: { title: 'nav.list' } },
- { path: '/garden', component: GardenView, meta: { title: 'nav.garden' } },
+ // сад отключён в 0.58 (не готов): страница убрана, прогресс и ачивки
+ // переехали на «Достижения»; старые ссылки ведём туда же
+ { path: '/garden', redirect: '/achievements' },
+ { path: '/achievements', component: AchievementsView, meta: { title: 'nav.achievements' } },
{ path: '/stats', component: StatsView, meta: { title: 'nav.stats' } },
{ path: '/projects', component: ProjectsView, meta: { title: 'nav.projects' } },
// название проекта/задачи в тайтл подставляет вид после загрузки данных
diff --git a/frontend/src/views/AchievementsView.vue b/frontend/src/views/AchievementsView.vue
new file mode 100644
index 0000000..9a02c89
--- /dev/null
+++ b/frontend/src/views/AchievementsView.vue
@@ -0,0 +1,262 @@
+
+
+
+
+
+