diff --git a/spec/migration.md b/spec/migration.md
new file mode 100644
index 0000000..d03bd32
--- /dev/null
+++ b/spec/migration.md
@@ -0,0 +1,291 @@
+# План миграции сайта vakhrushev.me с Sculpin на Astro
+
+## Обзор текущего состояния
+
+### Стек
+- **Генератор**: Sculpin 3 (PHP 8)
+- **Шаблоны**: Twig (base → internal → article, три уровня наследования)
+- **Фронтенд**: Webpack 4, SCSS, Vue 2 (интерактивная демка гадалки)
+- **Сборка**: Docker (PHP + Node контейнеры), Taskfile
+- **Деплой**: Docker-образ nginx, Ansible на личный сервер
+- **Аналитика**: Яндекс.Метрика
+- **Шрифты**: Google Fonts (PT Serif, Source Code Pro), Font Awesome 5
+- **Кастомные бандлы Sculpin**: HtmlPrettier, SiteMap, TwigExtension (hashed_asset)
+
+### Контент
+- 8 статей в markdown (2019-2020), русский язык
+- 1 статья с интерактивным Vue-компонентом (гадалка Шеннона)
+- Страницы: главная, список статей, 404
+- Atom-лента (ручной шаблон)
+- Sitemap (кастомный бандл + шаблон)
+- robots.txt
+
+### Структура стилей
+- Переменные SCSS: шрифт PT Serif 20px, ширина 740px, цвета GitHub
+- Базовые стили, стили главной, стили внутренних страниц, навигация
+- BEM-подобная методология
+
+---
+
+## Целевое состояние
+
+### Стек
+- **Генератор**: Astro
+- **Стилизация**: Tailwind CSS
+- **Интерактивность**: Vue 3 (islands, для гадалки и будущих компонентов)
+- **Сборка**: Docker (один Node-контейнер), Taskfile
+- **Деплой**: Docker-образ nginx, Ansible (без изменений)
+
+### Разделы сайта
+1. **Главная** — краткое описание, ссылки
+2. **Блог** — статьи и эссе по программированию
+3. **Галерея** — фотографии (новый раздел, реализуется позже)
+
+---
+
+## Задачи
+
+### Этап 1. Инициализация проекта Astro
+
+#### 1.1. Создать проект Astro
+- Инициализировать Astro в корне проекта (или в отдельной ветке)
+- Настроить `astro.config.mjs`: site URL `https://vakhrushev.me`, язык `ru`
+- Установить интеграции: `@astrojs/sitemap`, `@astrojs/rss`, `@astrojs/vue`, `@astrojs/tailwind`
+- Настроить TypeScript (strict или relaxed — на усмотрение)
+
+#### 1.2. Настроить Tailwind CSS
+- Установить и настроить Tailwind
+- Перенести дизайн-токены из SCSS-переменных в Tailwind-конфигурацию:
+ - Шрифты: PT Serif (serif), Source Code Pro (mono)
+ - Цвета: `#24292e` (текст), `#0366d6` (ссылки), `#e6e6e6` (линии)
+ - Ширина контента: 740px
+ - Размер шрифта: 20px базовый
+
+#### 1.3. Настроить Docker для сборки
+- Заменить два Docker-образа (PHP + Node) одним Node-образом
+ - Использовать актуальную LTS-версию Node (22)
+- Удалить PHP Dockerfile — он больше не нужен
+- Сохранить `Dockerfile.nginx.prod`, обновить путь: `dist/` вместо `output_prod/`
+
+#### 1.4. Обновить Taskfile
+- Убрать задачи: `composer`, `sculpin`, `shell-node`, `format-php`
+- Обновить задачи сборки:
+ - `build-dev`: `astro build` (dev-режим — через `astro dev`)
+ - `build-prod`: `astro build`
+- Обновить задачу `deploy`: путь к собранным файлам `dist/`
+- Добавить задачу `dev`: запуск `astro dev` для локальной разработки
+- Сохранить структуру Docker-команд через Taskfile
+
+---
+
+### Этап 2. Лейауты и компоненты
+
+#### 2.1. Создать базовый лейаут `BaseLayout.astro`
+Аналог текущего `base.html.twig`. Должен включать:
+- ``, мета-теги (charset, viewport, description, keywords)
+- Open Graph мета-теги (og:site_name, og:title, og:description, og:url, og:locale)
+- Подключение шрифтов Google Fonts (PT Serif, Source Code Pro)
+- Яндекс.Метрика (вынести в отдельный компонент `YandexMetrika.astro`)
+- Yandex verification мета-тег
+- Слот для контента
+
+Параметры (props): `title`, `description`, `keywords`
+
+#### 2.2. Создать лейаут для внутренних страниц `InternalLayout.astro`
+Аналог `internal.html.twig`:
+- Использует `BaseLayout`
+- Навигация (компонент `Navigation.astro`)
+- Контейнер `.page` с ограничением ширины
+
+#### 2.3. Создать лейаут для статей `ArticleLayout.astro`
+Аналог `article.html.twig`:
+- Использует `InternalLayout`
+- Заголовок статьи `
`
+- Дата публикации и email внизу
+- Слот для содержимого статьи
+
+#### 2.4. Создать общие компоненты
+- `Navigation.astro` — навигация (Главная, Блог, Галерея)
+- `YandexMetrika.astro` — код счетчика аналитики
+- `ArticleList.astro` — список статей, сгруппированных по годам
+
+Особенность: Font Awesome больше не нужен (социальные ссылки удалены в коммите `63a324c`).
+Если понадобятся иконки — использовать `astro-icon` или inline SVG.
+
+---
+
+### Этап 3. Content Collections и перенос статей
+
+#### 3.1. Настроить Content Collection для статей
+- Создать `src/content/articles/` для markdown-файлов
+- Определить схему в `src/content.config.ts`:
+ ```ts
+ {
+ title: z.string(),
+ description: z.string().optional(),
+ date: z.date(), // извлечь из имени файла или добавить в frontmatter
+ keywords: z.array(z.string()).optional(),
+ tags: z.array(z.string()).optional(),
+ draft: z.boolean().default(false),
+ }
+ ```
+
+#### 3.2. Перенести статьи
+Перенести 8 markdown-файлов из `source/_articles/` в `src/content/articles/`.
+
+Необходимые изменения в каждом файле:
+- Удалить Sculpin-специфичные поля frontmatter (`layout`, `styles`, `scripts`, `use`)
+- Добавить поле `date` (сейчас дата в имени файла, нужно продублировать в frontmatter)
+- Оставить `title`, `description`, `keywords`
+- Опционально: добавить `tags` для будущей фильтрации
+
+Список статей:
+1. `2019-05-01-predictor.md` — **ОСОБЫЙ СЛУЧАЙ**, см. задачу 3.3
+2. `2019-06-01-php-serialization.md`
+3. `2019-06-28-storytelling.md`
+4. `2019-08-08-yandex-disk-image-hosting.md`
+5. `2019-09-26-highload-videos.md`
+6. `2020-06-27-interesting-programming-blogs.md`
+7. `2020-06-27-type-discriminant.md`
+8. `2020-11-08-nullable-fields.md`
+
+#### 3.3. Перенести статью с интерактивным компонентом (гадалка)
+Статья `predictor.md` содержит встроенный Vue-компонент `
`.
+
+Шаги:
+- Переименовать файл в `.mdx`
+- Обновить Vue-компонент `PredictorDemo.vue` до Vue 3 (Composition API или Options API)
+ - `@anwinged/predictor` — проверить совместимость с Vue 3, при необходимости обновить
+ - Перенести scoped-стили компонента на Tailwind или оставить scoped CSS
+- В MDX-файле: импортировать компонент и использовать ``
+- Удалить ручное подключение JS/CSS через frontmatter (`styles`, `scripts`)
+
+---
+
+### Этап 4. Страницы
+
+#### 4.1. Главная страница `src/pages/index.astro`
+- Использовать `BaseLayout`
+- Имя, краткое описание (обновить текст — актуализировать информацию о себе)
+- Ссылки: email, git, другие
+- Список последних статей (через `getCollection('articles')`)
+
+#### 4.2. Страница списка статей `src/pages/articles/index.astro`
+- Использовать `InternalLayout`
+- Список всех статей, сгруппированных по годам (как сейчас в `article_list.twig`)
+- Сортировка по дате, от новых к старым
+
+#### 4.3. Динамические страницы статей `src/pages/articles/[...slug].astro`
+- Использовать `ArticleLayout`
+- Рендеринг markdown/MDX содержимого через `render()`
+- SEO мета-теги из frontmatter
+
+#### 4.4. Страница 404 `src/pages/404.astro`
+- Использовать `InternalLayout`
+- Текст "Страница не найдена" и ссылка на главную
+
+#### 4.5. Страница галереи `src/pages/gallery/index.astro` (заглушка)
+- Использовать `InternalLayout`
+- Placeholder-страница "Скоро здесь будут фотографии"
+- Полноценная реализация — отдельная задача в будущем
+
+---
+
+### Этап 5. RSS, Sitemap, SEO
+
+#### 5.1. Настроить RSS-ленту
+- Создать `src/pages/rss.xml.ts` с использованием `@astrojs/rss`
+- Включить все статьи (title, description, date, link)
+- URL: `/rss.xml`
+- Для обратной совместимости: добавить редирект `/atom.xml` → `/rss.xml` (в nginx или через Astro)
+
+#### 5.2. Настроить Sitemap
+- Интеграция `@astrojs/sitemap` уже генерирует sitemap автоматически
+- Убедиться, что 404 и служебные страницы исключены
+
+#### 5.3. Настроить robots.txt
+- Создать `public/robots.txt` со ссылкой на sitemap
+
+#### 5.4. Open Graph и мета-теги
+- Уже реализовано в BaseLayout (задача 2.1)
+- Проверить корректность на каждом типе страниц
+
+---
+
+### Этап 6. Сборка и деплой
+
+#### 6.1. Обновить Docker-сборку
+- Новый `docker/node/Dockerfile` на Node 22 LTS
+- PHP Dockerfile удалить
+- Обновить `Dockerfile.nginx.prod`:
+ ```dockerfile
+ FROM nginx:stable
+ COPY dist /usr/share/nginx/html
+ ```
+- Добавить конфиг nginx для SPA fallback (404.html) и кэширования статики
+
+#### 6.2. Обновить Taskfile
+Финальная версия задач:
+```yaml
+tasks:
+ dev: # astro dev (локальная разработка)
+ build-prod: # astro build
+ deploy: # build-prod → docker build → ansible
+```
+
+#### 6.3. Проверить деплой
+- Собрать Docker-образ
+- Проверить, что все страницы отдаются корректно
+- Проверить 404
+- Проверить RSS и sitemap
+
+---
+
+### Этап 7. Очистка
+
+#### 7.1. Удалить файлы Sculpin
+- `app/` — SculpinKernel, конфиги
+- `bundle/` — кастомные бандлы (HtmlPrettier, SiteMap, TwigExtension)
+- `source/` — старые шаблоны, ассеты, контент (после проверки, что всё перенесено)
+- `composer.json`, `composer.lock`
+- `docker/php/Dockerfile`
+- `webpack.config.js`
+- `.php-cs-fixer.php`
+
+#### 7.2. Обновить package.json
+- Удалить старые зависимости (webpack, babel, vue 2, node-sass, и т.д.)
+- Удалить старые npm-скрипты
+- Оставить только Astro и его зависимости
+
+#### 7.3. Обновить .gitignore
+- Убрать `output_dev/`, `output_prod/`, `vendor/`
+- Добавить `dist/`, `.astro/`, `node_modules/`
+
+#### 7.4. Обновить README.md
+- Описание нового стека
+- Инструкции по локальной разработке и деплою
+
+---
+
+## Сохранение URL-совместимости
+
+Текущие URL статей имеют формат: `/articles/YYYY/MM/DD/slug/`
+(например, `/articles/2019/05/01/predictor/`).
+
+Нужно сохранить эти URL, чтобы не сломать внешние ссылки. Варианты:
+1. **Настроить slug в Astro** — использовать `getStaticPaths()` с кастомными slug, повторяющими текущую структуру
+2. **Редиректы в nginx** — если хочется упростить URL-структуру (например, `/articles/predictor/`), настроить 301-редиректы со старых URL
+
+Рекомендация: упростить URL до `/articles/slug/` и настроить редиректы в nginx для старых URL.
+
+---
+
+## Порядок выполнения
+
+```
+Этап 1 (инициализация) → Этап 2 (лейауты) → Этап 3 (контент)
+ → Этап 4 (страницы) → Этап 5 (RSS/sitemap) → Этап 6 (деплой) → Этап 7 (очистка)
+```
+
+Этапы 2 и 3 можно частично выполнять параллельно.
+Этап 7 выполняется только после полной проверки нового сайта.