| Путь | Компонент | Описание |
|---|---|---|
/ |
Home.jsx |
Главная — Hero, Features, HowItWorks, Stats, Screenshots, CTA |
/themes |
Themes.jsx |
Каталог тем: поиск (useDeferredValue), фильтр по 10 категориям, 4-колоночная сетка |
/themes/:id |
ThemeDetail.jsx |
Детальная страница: превью VK UI, палитра, похожие темы, og:image, InstallModal |
/theme/:encoded |
ThemePreview/ |
Превью расшаренной темы (base64url config), InstallModal при отсутствии расширения |
/wallpapers |
Wallpapers.jsx |
Коллекция обоев: поиск, фильтры по типу (video/image/web) и категории |
/wallpapers/:id |
WallpaperDetail.jsx |
Видео/изображение/iframe, авто-метаданные, fallback при ошибке, InstallModal |
/changelog |
Changelog.jsx |
История обновлений |
/changelog/:version |
ChangelogVersion.jsx |
Детальная страница конкретной версии |
/news |
News.jsx |
Новости и анонсы (двуязычные посты из src/data/news.js) |
/news/:slug |
NewsPost.jsx |
Страница новости: обложка, блоки контента, CTA, похожие посты |
/welcome |
Welcome.jsx |
Страница после установки расширения: статус подключения, выбор тем, быстрые настройки |
/uninstall |
Uninstall.jsx |
Страница после удаления — форма фидбека (Google Forms, no-cors) |
/privacy |
Privacy.jsx |
Политика конфиденциальности |
/terms |
Terms.jsx |
Условия использования |
* |
NotFound.jsx |
404 с Easter egg (5 кликов на цифры) и быстрыми ссылками |
┌────────────────────────────────────────────────────────────────────┐
│ vkify.ru (SPA) │
│ │
│ React Router 6 ──► страницы загружаются лениво (lazy + Suspense) │
│ hydrateRoot ──► гидрация пре-рендеренного HTML │
│ LanguageProvider ──► ru / en, переключатель в шапке │
│ │
│ Пре-рендер: 85 маршрутов │
│ 8 статических + 72 темы (/themes/:id) + 3 обоя (/wallpapers/:id)│
│ Puppeteer → статический HTML для поисковиков │
└────────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
src/data/*.js src/config/index.js
(72 темы, 3 обоя, (URL, ссылки, аналитика,
changelog, features) Google Forms ID, статистика)
│ │
▼ ▼
scripts/generate-og.js src/i18n/locales/{ru,en}.js
(Puppeteer → 72 × og:image (словари UI; контент тем/changelog/
PNG 1200×630 для /themes/:id) legal остаётся на RU с фолбэком)
Собственный лёгкий i18n без зависимостей: LanguageProvider + хук useTranslation.
import { useTranslation } from './i18n'
const { t, lang, setLang, supported } = useTranslation()
t('hero.subtitle', { features: '50+' }) // строка с интерполяцией {features}
t('features.cards.appearance.details') // массивы интерполируются построчно- Определение языка:
localStorage('vkify-lang')→navigator.language→ru. - Сохранение: при смене языка пишется в
localStorage, обновляется<html lang>. - Фолбэк: пропущенный ключ → язык по умолчанию (
ru) → сам ключ (сconsole.warnв DEV). <LanguageSwitcher>: выпадающий список с самоназваниями всех языков изSUPPORTED_LANGS— добавление языка не требует правок компонента.
Добавление нового языка:
- Создать
src/i18n/locales/<code>.jsсо структурой как уru.js. - Зарегистрировать в
src/i18n/index.jsx(DICTS,SUPPORTED_LANGS). - Готово — переключатель и фолбэк подхватят автоматически.
Сейчас на RU оставлено по решению: 72 описания тем, записи changelog, тело Privacy/Terms (только заголовки/SEO переведены). Фолбэк показывает RU-оригинал в EN-режиме.
frontend/
├── public/
│ ├── og-image.png # OG-картинка для главной (1200×630)
│ ├── og/themes/ # 72 PNG — og:image для каждой темы
│ ├── wallpapers/
│ ├── favicon.svg, sitemap.xml, robots.txt, _redirects
│
├── scripts/
│ └── generate-og.js # Puppeteer: 72 og:image PNG, запускается перед vite build
│
├── src/
│ ├── i18n/
│ │ ├── index.jsx # LanguageProvider, useTranslation, LANG_NAMES
│ │ └── locales/
│ │ ├── ru.js # русский словарь (источник правды)
│ │ └── en.js # английский (зеркало структуры)
│ │
│ ├── components/
│ │ ├── common/
│ │ │ ├── Analytics.jsx # GA4 + Яндекс.Метрика
│ │ │ ├── Badge.jsx, Button.jsx, Card.jsx
│ │ │ ├── DetailNavbar.jsx # /themes/:id, /theme/:encoded, /wallpapers/:id
│ │ │ ├── DonateModal.jsx # Cloudtips, Tribute
│ │ │ ├── ErrorBoundary.jsx # читает язык напрямую (не зависит от i18n-провайдера)
│ │ │ ├── ExtensionHint.jsx # плашка «Расширение подключено» для детальных страниц
│ │ │ ├── Footer.jsx, Header.jsx
│ │ │ ├── LanguageSwitcher.jsx # выпадающий список, расширяется автоматически
│ │ │ ├── Loading.jsx, Logo.jsx
│ │ │ ├── ScrollToTop.jsx
│ │ │ ├── Section.jsx
│ │ │ ├── SEO.jsx # title, og:*, twitter:*, canonical
│ │ │ ├── SocialIcons.jsx, ThemeToggle.jsx
│ │ │ ├── VkLogo.jsx, VkMiniPreview.jsx
│ │ ├── home/
│ │ │ ├── CTA.jsx, Features.jsx
│ │ │ ├── Hero.jsx, Hero3DCard.jsx
│ │ │ ├── HowItWorks.jsx, Screenshots.jsx, Stats.jsx
│ │ ├── changelog/
│ │ │ └── VersionCard.jsx
│ │ └── uninstall/
│ │ └── FeedbackForm.jsx # Google Forms, no-cors POST; в форму всегда уходит RU
│ │
│ ├── pages/
│ │ ├── Home.jsx, Themes.jsx, ThemeDetail.jsx
│ │ ├── ThemePreview/
│ │ │ ├── index.jsx
│ │ │ ├── BackgroundPreview.jsx
│ │ │ ├── ThemeParamTable.jsx # PARAM_META, ColorStrip, FontSample
│ │ │ └── InstallModal.jsx # используется в ThemeDetail/ThemePreview/WallpaperDetail
│ │ ├── Wallpapers.jsx, WallpaperDetail.jsx
│ │ ├── Changelog.jsx, ChangelogVersion.jsx
│ │ ├── Welcome.jsx, Uninstall.jsx
│ │ ├── Privacy.jsx, Terms.jsx, NotFound.jsx
│ │
│ ├── data/
│ │ ├── themes.js # 72 темы, 10 категорий (themes/themeCategories/themeIds)
│ │ ├── wallpapers.js # 3 обоя (IMAGE/VIDEO/WEB), 7 категорий
│ │ ├── features.js # структурные данные топ-фич (id+icon+color)
│ │ ├── changelog.js # 5 версий
│ │ └── news.js # двуязычные новости (slug + translations[lang])
│ │
│ ├── hooks/
│ │ ├── useExtension.js # postMessage-мост; { detected, version, settings, saveSettings }
│ │ ├── useApplyToVK.js # apply + InstallModal-state — общий для детальных страниц
│ │ ├── useCopyToClipboard.js # copy + авто-сброс флага «скопировано»
│ │ ├── useGoogleFont.js
│ │ ├── useVideoMeta.js # resolution, duration, format, size
│ │ └── useWEProperties.js # парсит project.json Wallpaper Engine
│ │
│ ├── utils/
│ │ ├── colors.js # isDarkColor, adjustColor, pluralizeRu
│ │ ├── scroll.js # scrollToElement, scrollWithOffset, scrollToTop, getActiveSection
│ │ ├── themeShare.js # encodeTheme / decodeTheme (base64url, v2)
│ │ └── videoEmbed.js # YouTube, VK, Vimeo, Coub, Twitch, Rutube, MP4
│ │
│ ├── config/
│ │ └── index.js # app, analytics, links, navigation, social, stats, feedback, seo
│ ├── context/
│ │ └── ThemeContext.jsx # dark / light (Tailwind .dark на <html>)
│ ├── styles/
│ │ └── index.css # Tailwind + CSS-переменные
│ ├── App.jsx # роутер + lazy-импорты
│ └── main.jsx # hydrateRoot / createRoot + ErrorBoundary + LanguageProvider
│
├── vite.config.js # Сборка + пре-рендер 85 маршрутов
├── tailwind.config.js
├── postcss.config.js
└── package.json
"dependencies": {
"framer-motion": "^10.16.5",
"lucide-react": "^0.294.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^2.0.4",
"react-router-dom": "^6.20.0",
"react-router-hash-link":"^2.4.3"
}
"devDependencies": {
"@prerenderer/renderer-puppeteer": "^1.2.4",
"@prerenderer/rollup-plugin": "^0.3.12",
"@vitejs/plugin-react": "^4.2.0",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.31",
"puppeteer": "^25.0.4",
"tailwindcss": "^3.3.5",
"vite": "^7.0.0"
}Vite зафиксирован на 7.x (классический Rollup) — последняя ветка, совместимая с
@vitejs/plugin-react@4по peer-зависимостям.manualChunksвvite.config.jsзадан функцией (требование Rollup).puppeteerобъявлен явно — его напрямую импортируетscripts/generate-og.js.
npm install
npm run dev # dev-сервер → http://localhost:5173
npm run build # og:image + продакшн-сборка с пре-рендером → dist/
npm run preview # превью собранной версии
npm run generate-og # только перегенерировать og:image (без сборки)При
npm run buildсначала запускаетсяscripts/generate-og.js— удаляет старые PNG вpublic/og/themes/и создаёт 72 новых (1200×630) через Puppeteer; затемvite buildсобирает фронтенд и делает пре-рендер.
| Категория | Иконка |
|---|---|
| Классика | 📝 |
| Мягкие | 🌸 |
| AMOLED | 🖤 |
| Цветные | 🌈 |
| Неоновые | ⚡ |
| Природа | 🌿 |
| Минимализм | ◾ |
| Ретро | 💾 |
| Тёплые | 🔥 |
| Холодные | ❄️ |
Структура темы:
{
id: 'github-dark',
name: 'GitHub Dark',
category: 'classic',
description: 'Тёмная тема...', // на детальной странице, остаётся на RU
tags: ['dark', 'professional'],
config: {
custom_theme_id: 'github-dark',
custom_theme: '#0d1117', // цвет фона
custom_accent: '#58a6ff', // акцентный цвет
// …остальные параметры расширения
},
preview: {
bg: '#0d1117', card: '#161b22', accent: '#58a6ff',
},
}preview используется для рендера карточек на сайте без установки расширения.
config — настройки, которые расширение применяет к VK.
Только структура (id + иконка + градиент); тексты — в i18n.features.cards.<id>.
6 кураторских блоков: appearance, adblock, privacy, tracking, media, messages.
Пре-рендер через @prerenderer/rollup-plugin + @prerenderer/renderer-puppeteer.
Список маршрутов строится динамически из данных при сборке:
// vite.config.js
import { themeIds } from './src/data/themes.js' // 72 ID
import { wallpaperIds } from './src/data/wallpapers.js' // 3 ID
import { newsSlugs } from './src/data/news.js' // 1 slug
const routes = [
'/', '/welcome', '/uninstall', '/changelog',
'/privacy', '/terms', '/themes', '/wallpapers', '/news', // 9 статических
...themeIds.map(id => `/themes/${id}`), // 72 маршрута
...wallpaperIds.map(id => `/wallpapers/${id}`), // 3 маршрута
...newsSlugs.map(slug => `/news/${slug}`), // 1 маршрут
] // итого: 85 маршрутовКаждая страница темы получает уникальный og:image:
// ThemeDetail.jsx
<SEO
title={t('themeDetail.seoTitle', { name: theme.name })}
description={theme.description}
url={`/themes/${theme.id}`}
image={`/og/themes/${theme.id}.png`}
/>SEO.jsx принимает: title, description, url (canonical), image (og:image;
fallback — /og-image.png). Автоматически добавляет og:locale, og:type,
og:site_name, Twitter Card, theme-color.
На текущий момент пре-рендер запекает мета-теги на языке по умолчанию (RU). Для полноценного мультиязычного SEO нужен отдельный заход (hreflang-теги + отдельные пререндеры для EN).
| Хук | Что делает |
|---|---|
useExtension |
postMessage-мост к расширению: { detected, version, settings, saveSettings } |
useApplyToVK |
Общая логика «применить + InstallModal» для детальных страниц |
useCopyToClipboard |
copy(text) + флаг copied с авто-сбросом, чистит таймер при unmount |
useVideoMeta / useImageMeta |
Авто-извлечение метаданных медиа (resolution, duration, size, format) |
useWEProperties |
Парсит project.json обоев Wallpaper Engine |
useGoogleFont |
Подгружает шрифт Google Fonts по font-family |
useTranslation |
i18n — см. раздел выше |
:root {
--bg-primary: #ffffff; --bg-secondary: #f5f5f7; --bg-tertiary: #e8e8ed;
--text-primary: #1d1d1f; --text-secondary: #6e6e73; --text-tertiary:#9b9b9f;
--border-color: #e0e0e5; --primary-light: #e5f1ff;
}
.dark { /* Tailwind dark mode — класс на <html> */
--bg-primary: #1c1c1e; --bg-secondary: #000000; --bg-tertiary: #2c2c2e;
--text-primary: #f5f5f7; --text-secondary: #a1a1a6; --text-tertiary:#6e6e73;
--border-color: #38383a; --primary-light: #0a3a6b;
}Кастомные утилиты: .glass, .text-gradient, .glow, .card-hover,
.bg-primary/secondary/tertiary, .text-primary/secondary/tertiary, .border-theme.
Также уважается prefers-reduced-motion: все CSS- и Framer Motion-анимации
сокращаются до 0.01мс.
config.app // url, name, description, tagline, email
config.analytics // googleAnalytics (GA4 ID), yandexMetrika
config.links // chromeWebStore, github, githubIssues, githubDiscussions, telegram, vk
config.navigation // main (5 якорных ссылок), footer.{product, resources, community, legal}
// у каждого пункта есть labelKey для i18n
config.social // [{name, href, color, bgColor}] — Telegram, VK, GitHub
config.stats // users, rating, features, themes, fonts
config.feedback // formId, fields.{reasons, comment} — Google Forms
config.seo // title, description, keywords
config.theme // primary (#0077ff), gradientsТемы кодируются в base64url и передаются по адресу /theme/:encoded. v2-схема
с короткими псевдонимами ключей и пропуском дефолтных значений (минимальный
размер URL).
encodeTheme(settings, meta) → base64url string
decodeTheme(encoded) → { settings, meta }
generateShareUrl(settings) → `${window.location.origin}/theme/${encoded}`Пользователь нажимает «Поделиться» на странице темы → формируется ссылка →
получатель видит полноэкранный превью с теми же цветами. Кнопка «Применить
мгновенно» применяет тему через расширение (если не установлено — открывается
InstallModal).
Все user-facing share-ссылки (generateShareUrl, страница обоев) строятся
от window.location.origin — в dev (http://localhost:5173) получается
локальный URL, в проде — https://vkify.ru. Без хардкода домена в коде.
Что намеренно остаётся привязанным к https://vkify.ru (через config.app.url):
| Где | Зачем |
|---|---|
src/components/common/SEO.jsx |
canonical, og:url, og:image — обязаны быть продовским доменом; localhost-canonical навредит SEO |
src/data/wallpapers.js abs() env-aware: в браузере берёт
window.location.origin (на dev — localhost-URL, на проде — vkify.ru),
SSR-фолбэк для prerender'а — config.app.url. Это нужно, чтобы расширение
получало рабочий URL обоев на запущенной версии сайта.
Если расширение вам нравится — можно поддержать разработку:
| Способ | Ссылка |
|---|---|
| 🇷🇺 Visa, MasterCard, МИР | Cloudtips |
| 🌍 Зарубежные карты и крипта | Tribute |

