diff --git a/.cursor/rules/frontend.mdc b/.cursor/rules/frontend.mdc
index c4c209b..f2ef0f2 100644
--- a/.cursor/rules/frontend.mdc
+++ b/.cursor/rules/frontend.mdc
@@ -1,93 +1,53 @@
---
-description: Rules for working with the Telegram Mini App frontend (webapp/)
+description: Rules for working with the Telegram Mini App frontend (webapp-next/)
globs:
- - webapp/**
+ - webapp-next/**
---
-# Frontend — Telegram Mini App
+# Frontend — Telegram Mini App (Next.js)
-## Module structure
+The Mini App lives in `webapp-next/`. It is built as a static export and served by FastAPI at `/app`.
-All source lives in `webapp/js/`. Each module has a single responsibility:
+## Stack
-| Module | Responsibility |
-|--------|---------------|
-| `main.js` | Entry point: theme init, auth gate, `loadMonth`, navigation, swipe gestures, sticky scroll |
-| `dom.js` | Lazy DOM getters (`getCalendarEl()`, `getDutyListEl()`, etc.) and shared mutable `state` |
-| `i18n.js` | `MESSAGES` dictionary (en/ru), `getLang()`, `t()`, `monthName()`, `weekdayLabels()` |
-| `auth.js` | Telegram `initData` extraction (`getInitData`), `isLocalhost`, hash/query fallback |
-| `theme.js` | Theme detection (`getTheme`), CSS variable injection (`applyThemeParamsToCss`), `initTheme` |
-| `api.js` | `apiGet`, `fetchDuties`, `fetchCalendarEvents`; timeout, auth header, `Accept-Language` |
-| `calendar.js` | `dutiesByDate`, `calendarEventsByDate`, `renderCalendar` (6-week grid with indicators) |
-| `dutyList.js` | `dutyTimelineCardHtml`, `dutyItemHtml`, `renderDutyList` (monthly duty timeline cards) |
-| `dayDetail.js` | Day detail panel — popover (desktop) or bottom sheet (mobile), `buildDayDetailContent` |
-| `hints.js` | Tooltip positioning, duty marker hint content, info-button tooltips |
-| `dateUtils.js` | Date helpers: `localDateString`, `dutyOverlapsLocalDay/Range`, `getMonday`, `formatHHMM`, etc. |
-| `utils.js` | `escapeHtml` utility |
-| `constants.js` | `FETCH_TIMEOUT_MS`, `RETRY_DELAY_MS`, `RETRY_AFTER_ACCESS_DENIED_MS` |
+- **Next.js** (App Router, `output: 'export'`, `basePath: '/app'`)
+- **TypeScript**
+- **Tailwind CSS** — theme extended with custom tokens (surface, muted, accent, duty, today, etc.)
+- **shadcn/ui** — Button, Card, Sheet, Popover, Tooltip, Skeleton, Badge
+- **Zustand** — app store (month, lang, duties, calendar events, loading, view state)
+- **@telegram-apps/sdk-react** — SDKProvider, useThemeParams, useLaunchParams, useMiniApp, useBackButton
-## State management
+## Structure
-A single mutable `state` object is exported from `dom.js`:
+| Area | Location |
+|------|----------|
+| App entry, layout | `src/app/layout.tsx`, `src/app/page.tsx` |
+| Providers | `src/components/providers/TelegramProvider.tsx` |
+| Calendar | `src/components/calendar/` — CalendarHeader, CalendarGrid, CalendarDay, DayIndicators |
+| Duty list | `src/components/duty/` — DutyList, DutyTimelineCard, DutyItem |
+| Day detail | `src/components/day-detail/` — DayDetail (Sheet/Popover), DayDetailContent |
+| Current duty view | `src/components/current-duty/CurrentDutyView.tsx` |
+| Contact links | `src/components/contact/ContactLinks.tsx` |
+| State views | `src/components/states/` — LoadingState, ErrorState, AccessDenied |
+| Hooks | `src/hooks/` — use-telegram-theme, use-telegram-auth, use-month-data, use-swipe, use-media-query, use-sticky-scroll, use-auto-refresh |
+| Lib | `src/lib/` — api, calendar-data, date-utils, phone-format, constants, utils |
+| i18n | `src/i18n/` — messages.ts, use-translation.ts |
+| Store | `src/store/app-store.ts` |
+| Types | `src/types/index.ts` |
-```js
-export const state = {
- current: new Date(), // currently displayed month
- lastDutiesForList: [], // duties array for the duty list
- todayRefreshInterval: null, // interval handle
- lang: "en" // 'ru' | 'en'
-};
-```
+## Conventions
-- **No store / pub-sub / reactivity.** `main.js` mutates `state`, then calls
- render functions (`renderCalendar`, `renderDutyList`) imperatively.
-- Other modules read `state` but should not mutate it directly.
+- **Client components:** Use `'use client'` where hooks or browser APIs are used (layout loads config script; page and most UI are client).
+- **Theme:** CSS variables in `globals.css`; `useTelegramTheme` maps Telegram theme params to `--tg-theme-*` and sets `data-theme` on ``.
+- **Auth:** `useTelegramAuth` provides initData for API; access gated for non-Telegram except localhost.
+- **i18n:** `useTranslation()` from store lang; `window.__DT_LANG` set by `/app/config.js` (backend).
+- **API:** `fetchDuties`, `fetchCalendarEvents` in `src/lib/api.ts`; pass initData, lang, AbortSignal; handle ACCESS_DENIED.
-## HTML rendering
+## Testing
-- Rendering functions build HTML strings (concatenation + `escapeHtml`) or use
- `createElement` + `setAttribute`.
-- Always escape user-controlled text with `escapeHtml()` before inserting via `innerHTML`.
-- `setAttribute()` handles attribute quoting automatically — do not manually escape
- quotes for data attributes.
+- **Runner:** Vitest in `webapp-next/`; environment: jsdom; React Testing Library.
+- **Config:** `webapp-next/vitest.config.ts`; setup in `src/test/setup.ts`.
+- **Run:** `cd webapp-next && npm test` (or `npm run test`). Build: `npm run build`.
+- **Coverage:** Unit tests for lib (api, date-utils, calendar-data, i18n, etc.) and component tests for calendar, duty list, day detail, current duty, states.
-## i18n
-
-```js
-t(lang, key, params?) // → translated string
-```
-
-- `lang` is `'ru'` or `'en'`, stored in `state.lang`.
-- `getLang()` reads **backend config** only: `window.__DT_LANG` (set by `/app/config.js` from `DEFAULT_LANGUAGE`). If missing or invalid, falls back to `"en"`. No Telegram or navigator language detection.
-- Params use named placeholders: `t(lang, "duty.until", { time: "14:00" })`.
-- Fallback chain: `MESSAGES[lang][key]` → `MESSAGES.en[key]` → raw key string.
-- All user-visible text must go through `t()` — never hardcode Russian strings in JS.
-
-## Telegram WebApp SDK
-
-- Loaded via `
-
-
-
-
-
-
-