- Enhanced tests to ensure consistent messageId generation for legacy inbox rows lacking a messageId. - Updated test descriptions for better clarity regarding the new messageId handling. - Adjusted test expectations to align with the updated behavior of relaying legacy inbox rows with generated messageIds.
40 KiB
План разработки лендинга для Voice-to-Text
Обзор проекта
Voice-to-Text — десктопное приложение для преобразования голоса в текст с фокусом на приватность и офлайн-поддержку. Построено на Tauri, Rust и Vue 3.
Ключевые особенности приложения:
- 🎤 Real-time транскрипция с поддержкой нескольких провайдеров (Deepgram, AssemblyAI, Whisper)
- 🔒 Privacy-focused — API ключи хранятся локально, нет облачного хранилища
- 🌍 Кроссплатформенность — macOS, Windows, Linux
- ⚡ Глобальные хоткеи — быстрый доступ через горячие клавиши
- 📋 Автоматическое копирование в буфер обмена
- 🎨 Современный UI с glass morphism эффектами
- 🌐 Мультиязычность — поддержка 6 языков (en, ru, es, fr, de, uk)
Технологический стек
Основные технологии:
- Nuxt 3 — фреймворк для Vue.js с SSR/SSG
- Vuetify 3 — Material Design компоненты
- Vue I18n — локализация (совместимо с текущим
vue-i18n) - TypeScript — типизация
- Vite — сборщик (встроен в Nuxt)
- Pinia — управление общим состоянием (через
@pinia/nuxt)
Дополнительные библиотеки:
- @nuxtjs/i18n — интеграция i18n с Nuxt
- @nuxtjs/seo — SEO оптимизация
- @vueuse/nuxt — композаблы для Vue (например
usePreferredDark) - nuxt-icon — иконки
- @nuxtjs/ipx — обработка изображений (опционально)
- swiper — карусель скриншотов (Vue интеграция)
- @nuxt/eslint + Prettier — линтинг и форматирование кода
Примечания по зависимостям:
- Google Fonts лучше не подключать как внешнюю зависимость. Либо системный шрифт, либо self-host (проще с приватностью и стабильностью).
Архитектура проекта
landing/
├── data/ # статические данные/конфиги секций (без Nuxt контекста)
├── types/ # TS типы (без Nuxt контекста)
├── utils/ # чистые функции (без Nuxt контекста, легко тестировать)
├── nuxt.config.ts # Конфигурация Nuxt
├── package.json
├── tsconfig.json
├── .env # Переменные окружения
│
├── locales/ # Файлы локализации
│ ├── en.json
│ ├── ru.json
│ ├── es.json
│ ├── fr.json
│ ├── de.json
│ └── uk.json
│
├── assets/ # Статические ресурсы
│ ├── images/
│ │ ├── hero-bg.jpg
│ │ ├── screenshot-dark.png
│ │ ├── screenshot-light.png
│ │ ├── features/
│ │ └── platforms/
│ ├── videos/ # Демо-видео (опционально)
│ └── styles/
│ └── main.scss # Глобальные стили
│
├── components/ # Vue компоненты
│ ├── layout/
│ │ ├── AppHeader.vue # Шапка с навигацией
│ │ ├── AppFooter.vue # Подвал
│ │ └── LanguageSwitcher.vue # Переключатель языков
│ │
│ ├── sections/
│ │ ├── HeroSection.vue # Главный экран
│ │ ├── FeaturesSection.vue # Особенности
│ │ ├── ProvidersSection.vue # STT провайдеры
│ │ ├── ScreenshotsSection.vue # Скриншоты
│ │ ├── DownloadSection.vue # Секция загрузки
│ │ ├── PrivacySection.vue # Приватность
│ │ └── FAQSection.vue # Частые вопросы
│ │
│ ├── ui/
│ │ ├── DownloadButton.vue # Кнопка загрузки
│ │ ├── FeatureCard.vue # Карточка фичи
│ │ ├── PlatformBadge.vue # Бейдж платформы
│ │ └── ScreenshotCarousel.vue # Карусель скриншотов
│ │
│ └── common/
│ ├── AppLogo.vue
│ └── ThemeToggle.vue # Переключатель темы (если нужен)
│
├── composables/ # Композаблы
│ ├── useDownload.ts # Логика загрузки
│ ├── useAnalytics.ts # Аналитика (опционально)
│ ├── usePlatform.ts # Определение платформы
│ ├── useBrowserTheme.ts # Определение темы браузера
│ └── useLocation.ts # Определение локации пользователя
│
├── layouts/
│ └── default.vue # Основной layout
│
├── plugins/ # Плагины Nuxt
│ ├── vuetify.ts # Инициализация Vuetify
│ └── init-theme-locale.client.ts # Автоинициализация темы и локали
│
├── pages/
│ ├── index.vue # Главная страница
│ ├── download.vue # Страница загрузки
│ └── privacy.vue # Политика приватности (опционально)
│
├── public/ # Публичные файлы
│ ├── favicon.ico
│ ├── robots.txt
│ └── sitemap.xml
│
└── server/ # Server API (если нужен)
Правила разделения логики
- composables/: завязаны на Nuxt/Vue (реактивность,
useCookie,navigateTo,useRoute,useFetch,useHeadи т.д.) - utils/: чистые функции без Nuxt контекста (легко тестировать, переиспользовать)
Структура страниц
1. Главная страница (/)
Hero Section
- Заголовок: "Voice to Text — Privacy-Focused Transcription"
- Подзаголовок: Краткое описание приложения
- CTA кнопки:
- "Download for [Platform]" (определяется автоматически)
- "View Features" (скролл к секции)
- Фоновое изображение/видео: Демонстрация приложения
Features Section
6 основных фич в виде карточек:
-
Real-time Transcription
- Иконка: 🎤
- Описание: Мгновенная транскрипция с частичными результатами
-
Privacy-Focused
- Иконка: 🔒
- Описание: API ключи хранятся локально, нет облачного хранилища
-
Multiple Providers
- Иконка: 🌐
- Описание: Deepgram, AssemblyAI, Whisper (офлайн)
-
Cross-Platform
- Иконка: 💻
- Описание: macOS, Windows, Linux
-
Global Hotkeys
- Иконка: ⌨️
- Описание: Быстрый доступ через горячие клавиши
-
Auto-Copy
- Иконка: 📋
- Описание: Автоматическое копирование в буфер обмена
Providers Section
Детальное описание STT провайдеров:
-
Deepgram (Nova-2/3)
- Низкая задержка
- Высокое качество
- Автоматический выбор модели (Nova-3 для английского, Nova-2 для русского)
-
AssemblyAI (Universal-Streaming v3)
- Высокое качество
- Облачный сервис
-
Whisper Local (офлайн)
- Полностью офлайн
- Требует cmake и загрузки модели
Screenshots Section
Карусель скриншотов (Swiper):
- Используем Swiper для Vue:
https://swiperjs.com/vue - На десктопе должно быть видно сразу несколько скриншотов на экране (не один с обязательным “далее”)
- Пример:
slidesPerView: 2-4на широких экранах сbreakpoints - Допускается режим с “частичным” превью следующего слайда
- Пример:
- На мобильных: 1 скрин, свайп, пагинация
- Набор скринов:
- Темная тема
- Светлая тема
- Настройки
- Процесс записи
Download Section
Платформо-специфичные кнопки загрузки с автоопределением ОС:
- Определяем текущую ОС пользователя и показываем приоритетно релевантную загрузку
- Если ОС определить не удалось — показываем все ОС
- Для macOS учитывать архитектуру (Apple Silicon vs Intel):
- Если получилось определить — выбираем нужную сборку по умолчанию
- Если нет — показываем оба варианта и даём выбрать вручную
Дополнительно:
- Версия приложения
- Размер файла
- Системные требования
- Changelog ссылка
Privacy Section
Ключевые моменты приватности:
- Локальное хранение API ключей
- Нет облачного хранилища транскрипций
- Опциональное использование собственных API ключей
Open Source:
- Да: часть компонентов/модулей планируем сделать open-source (для маркетинга и доверия)
FAQ Section
Частые вопросы:
- Какие платформы поддерживаются?
- Нужен ли интернет для работы?
- Как настроить API ключи?
- Можно ли использовать офлайн?
- Как изменить горячие клавиши?
- Безопасны ли мои данные?
2. Страница загрузки (/download)
- Определение платформы автоматически
- Кнопки загрузки для всех платформ
- Инструкции по установке для каждой ОС
- Системные требования
- Changelog (последние версии)
Локализация (i18n)
Поддерживаемые языки:
- 🇺🇸 English (
en) - 🇷🇺 Русский (
ru) - 🇪🇸 Español (
es) - 🇫🇷 Français (
fr) - 🇩🇪 Deutsch (
de) - 🇺🇦 Українська (
uk)
Структура файлов локализации:
// locales/en.json
{
"meta": {
"title": "Voice to Text - Privacy-Focused Transcription",
"description": "..."
},
"nav": {
"features": "Features",
"download": "Download",
"privacy": "Privacy"
},
"hero": {
"title": "Voice to Text",
"subtitle": "Privacy-focused voice-to-text application with offline support",
"download": "Download for {platform}",
"viewFeatures": "View Features"
},
"features": {
"realtime": {
"title": "Real-time Transcription",
"description": "..."
},
"privacy": {
"title": "Privacy-Focused",
"description": "..."
}
// ... остальные фичи
},
"providers": {
"title": "Multiple STT Providers",
"deepgram": {
"name": "Deepgram",
"description": "..."
}
// ... остальные провайдеры
},
"download": {
"title": "Download",
"forPlatform": "Download for {platform}",
"systemRequirements": "System Requirements",
"version": "Version {version}"
},
"faq": {
"title": "Frequently Asked Questions",
"items": [
{
"question": "...",
"answer": "..."
}
]
}
}
Модель контента (чтобы i18n не превращалась в ад)
Проблема классическая: если хранить “структуру секций” в data/*, а весь контент раскидать по ключам i18n, то любая правка превращается в квест “найди 20 ключей в 6 языках”.
Решение — разделить два слоя:
- Микрокопирайт (кнопки, лейблы, мелкие подписи) — остаётся в
landing/locales/*. - Контент секций (FAQ, список фич, провайдеры, тексты блоков) — лежит в локализованных контент-файлах с одинаковой структурой по всем языкам.
Рекомендуемая схема:
landing/content/en.ts,landing/content/ru.ts, ... (или.json, если удобнее).- Внутри — один типизированный объект
LandingContent, где:- элементы имеют стабильные
id(напримерfaq.items[].id,features.items[].id) - порядок можно менять без рефакторинга компонентов
- длинные тексты редактируются “в одном месте” для каждой локали
- элементы имеют стабильные
Минимальные правила дисциплины:
idнеизменяемы (меняем текст, но не идентификатор).- Если добавили/удалили элемент — правим все локали (это легко проверяется автоматикой).
- Для контента не используем “вложенные ключи на 10 уровней”, держим структуру простой и читаемой.
Проверка качества:
- Добавляем маленькую проверку (скрипт/тест), которая сравнивает структуру контента между локалями и падает, если где-то не хватает ключей/элементов.
Минимальный “контракт” этой проверки:
- сравниваем структуру (ключи/массивы по
id), а не тексты - ошибка должна показывать, какой
id/ключ отсутствует и в какой локали - проверка запускается локально и в CI (чтобы не ловить это уже на проде)
Настройка i18n в Nuxt:
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@nuxtjs/i18n',
'vuetify/nuxt'
],
i18n: {
locales: [
{ code: 'en', iso: 'en-US', file: 'en.json', name: 'English' },
{ code: 'ru', iso: 'ru-RU', file: 'ru.json', name: 'Русский' },
{ code: 'es', iso: 'es-ES', file: 'es.json', name: 'Español' },
{ code: 'fr', iso: 'fr-FR', file: 'fr.json', name: 'Français' },
{ code: 'de', iso: 'de-DE', file: 'de.json', name: 'Deutsch' },
{ code: 'uk', iso: 'uk-UA', file: 'uk.json', name: 'Українська' }
],
defaultLocale: 'en',
strategy: 'prefix_except_default',
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'i18n_redirected',
redirectOn: 'root',
// Определение языка по локации (через composable)
alwaysRedirect: false,
fallbackLocale: 'en'
}
}
})
Автоматическое определение языка по локации:
Логика определения языка:
- Проверка cookie — если пользователь уже выбирал язык, использовать его
- Определение по браузеру —
navigator.languageилиnavigator.languages - Fallback — английский язык по умолчанию
Важно:
- Лендинг планируется как SSG (статический). Значит IP-геолокация на сервере здесь неуместна: негде “серверу” исполняться на каждый запрос.
- Если когда-то понадобится geo-редирект — это отдельная задача (edge/runtime), не часть текущего лендинга.
Маппинг стран к языкам:
- 🇷🇺 Россия, Беларусь, Казахстан →
ru - 🇺🇦 Украина →
uk - 🇪🇸 Испания, Латинская Америка →
es - 🇫🇷 Франция, Бельгия, Швейцария (французский) →
fr - 🇩🇪 Германия, Австрия, Швейцария (немецкий) →
de - 🇺🇸 Остальные →
en
Компоненты Vuetify
Используемые компоненты:
- v-app-bar — шапка сайта
- v-container, v-row, v-col — сетка
- v-card — карточки фич
- v-btn — кнопки
- v-carousel — карусель скриншотов
- v-expansion-panels — FAQ аккордеон
- v-chip — бейджи платформ
- v-select — выбор языка
- v-icon — иконки
- v-divider — разделители
Кастомизация темы:
// plugins/vuetify.ts
import { createVuetify } from 'vuetify'
export default defineNuxtPlugin((nuxtApp) => {
// Определение темы браузера при инициализации
const getInitialTheme = (): 'dark' | 'light' => {
if (process.client) {
// Проверка сохраненной темы в localStorage
const savedTheme = localStorage.getItem('vuetify-theme')
if (savedTheme === 'dark' || savedTheme === 'light') {
return savedTheme
}
// Определение по системным настройкам браузера
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark'
}
}
return 'light' // Fallback для SSR
}
const vuetify = createVuetify({
theme: {
defaultTheme: getInitialTheme(),
themes: {
dark: {
colors: {
primary: '#6366f1', // indigo
secondary: '#8b5cf6', // purple
accent: '#ec4899', // pink
background: '#0f172a', // slate-900
surface: '#1e293b', // slate-800
}
},
light: {
colors: {
primary: '#6366f1',
secondary: '#8b5cf6',
accent: '#ec4899',
background: '#ffffff',
surface: '#f8fafc', // slate-50
}
}
}
}
})
// Слушатель изменений системной темы
if (process.client) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleThemeChange = (e: MediaQueryListEvent) => {
const savedTheme = localStorage.getItem('vuetify-theme')
// Автоматически менять только если пользователь не выбирал тему вручную
if (!savedTheme) {
vuetify.theme.global.name.value = e.matches ? 'dark' : 'light'
}
}
mediaQuery.addEventListener('change', handleThemeChange)
}
nuxtApp.vueApp.use(vuetify)
})
SEO оптимизация
Meta теги:
<!-- layouts/default.vue -->
<template>
<Html :lang="$i18n.locale">
<Head>
<Title>{{ $t('meta.title') }}</Title>
<Meta name="description" :content="$t('meta.description')" />
<Meta property="og:title" :content="$t('meta.title')" />
<Meta property="og:description" :content="$t('meta.description')" />
<Meta property="og:image" content="/og-image.png" />
<Meta name="twitter:card" content="summary_large_image" />
</Head>
<!-- ... -->
</Html>
</template>
Структурированные данные (JSON-LD):
// composables/useStructuredData.ts
export const useStructuredData = () => {
const { $i18n } = useNuxtApp()
const softwareApplication = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Voice to Text",
"applicationCategory": "UtilityApplication",
"operatingSystem": ["macOS", "Windows", "Linux"],
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
}
}
return { softwareApplication }
}
Функциональность
1. Определение платформы
// composables/usePlatform.ts
export const usePlatform = () => {
const platform = ref<'macos' | 'windows' | 'linux' | 'unknown'>('unknown')
if (process.client) {
const userAgent = navigator.userAgent.toLowerCase()
if (userAgent.includes('mac')) platform.value = 'macos'
else if (userAgent.includes('win')) platform.value = 'windows'
else if (userAgent.includes('linux')) platform.value = 'linux'
}
const downloadUrl = computed(() => {
// Логика формирования URL для загрузки
const baseUrl = 'https://github.com/777genius/voice-to-text/releases'
// ...
})
return { platform, downloadUrl }
}
2. Определение темы браузера
// composables/useBrowserTheme.ts
import { usePreferredDark } from '@vueuse/core'
import { useTheme } from 'vuetify'
export const useBrowserTheme = () => {
const { $vuetify } = useNuxtApp()
const preferredDark = usePreferredDark()
const theme = useTheme()
// Инициализация темы при первом посещении
const initTheme = () => {
if (process.client) {
const savedTheme = localStorage.getItem('vuetify-theme')
if (savedTheme) {
// Использовать сохраненную тему
theme.global.name.value = savedTheme as 'dark' | 'light'
} else {
// Использовать системную тему браузера
theme.global.name.value = preferredDark.value ? 'dark' : 'light'
localStorage.setItem('vuetify-theme', theme.global.name.value)
}
}
}
// Переключение темы
const toggleTheme = () => {
const newTheme = theme.global.name.value === 'dark' ? 'light' : 'dark'
theme.global.name.value = newTheme
localStorage.setItem('vuetify-theme', newTheme)
}
// Установка конкретной темы
const setTheme = (themeName: 'dark' | 'light') => {
theme.global.name.value = themeName
localStorage.setItem('vuetify-theme', themeName)
}
// Слушатель изменений системной темы (только если пользователь не выбирал вручную)
if (process.client) {
watch(preferredDark, (isDark) => {
const savedTheme = localStorage.getItem('vuetify-theme')
if (!savedTheme) {
theme.global.name.value = isDark ? 'dark' : 'light'
}
})
}
return {
currentTheme: computed(() => theme.global.name.value),
isDark: computed(() => theme.global.name.value === 'dark'),
initTheme,
toggleTheme,
setTheme
}
}
3. Определение локации пользователя
// composables/useLocation.ts
export const useLocation = () => {
const { $i18n } = useNuxtApp()
const locale = useCookie('i18n_redirected', { default: () => 'en' })
// Маппинг стран к языкам
const countryToLocale: Record<string, string> = {
'RU': 'ru', // Россия
'BY': 'ru', // Беларусь
'KZ': 'ru', // Казахстан
'UA': 'uk', // Украина
'ES': 'es', // Испания
'MX': 'es', // Мексика
'AR': 'es', // Аргентина
'CO': 'es', // Колумбия
'CL': 'es', // Чили
'PE': 'es', // Перу
'FR': 'fr', // Франция
'BE': 'fr', // Бельгия (французский)
'CH': 'de', // Швейцария (по умолчанию немецкий, можно улучшить)
'DE': 'de', // Германия
'AT': 'de', // Австрия
}
// Определение языка по браузеру
const getBrowserLocale = (): string => {
if (process.client) {
const browserLang = navigator.language || (navigator as any).userLanguage
const langCode = browserLang.split('-')[0].toLowerCase()
// Проверка поддерживаемых языков
const supportedLocales = ['en', 'ru', 'es', 'fr', 'de', 'uk']
if (supportedLocales.includes(langCode)) {
return langCode
}
// Проверка полного кода (например, ru-RU)
const fullCode = browserLang.toLowerCase()
if (fullCode.startsWith('ru')) return 'ru'
if (fullCode.startsWith('uk')) return 'uk'
if (fullCode.startsWith('es')) return 'es'
if (fullCode.startsWith('fr')) return 'fr'
if (fullCode.startsWith('de')) return 'de'
}
return 'en'
}
// Важно: лендинг статический (SSG), поэтому IP-геолокацию не используем.
// Инициализация языка при первом посещении
const initLocale = async () => {
// Если язык уже выбран пользователем, не менять
if (locale.value && locale.value !== 'en') {
return
}
// Приоритет: cookie > браузер > fallback
let detectedLocale = locale.value || 'en'
// На клиенте определяем по браузеру
detectedLocale = getBrowserLocale()
// Устанавливаем язык только если он отличается от текущего
if (detectedLocale !== $i18n.locale.value) {
const { switchLocalePath } = useI18n()
await navigateTo(switchLocalePath(detectedLocale))
}
}
return {
currentLocale: computed(() => $i18n.locale.value),
initLocale,
getBrowserLocale
}
}
4. Плагин для автоматической инициализации
// plugins/init-theme-locale.client.ts
export default defineNuxtPlugin(async () => {
const { initTheme } = useBrowserTheme()
const { initLocale } = useLocation()
// Инициализация темы
initTheme()
// Инициализация локали (только при первом посещении)
const hasVisited = useCookie('has_visited', { default: () => false })
if (!hasVisited.value) {
await initLocale()
hasVisited.value = true
}
})
5. Статистика загрузок (опционально)
Не делаем.
6. Аналитика (опционально)
// composables/useAnalytics.ts
export const useAnalytics = () => {
const trackDownload = (platform: string) => {
if (process.client) {
// Google Analytics, Plausible, или другая аналитика
gtag('event', 'download', { platform })
}
}
return { trackDownload }
}
Решение по аналитике:
- Делаем GA4 (Google Analytics) + события:
download_click(platform, arch, version, locale)download_page_viewlanguage_changetheme_changefaq_opencta_view_features_click
- Для подключения нужен
GA4 Measurement ID(например,G-XXXXXXXXXX) и/или доступы к аккаунту. В коде предусматриваем конфиг через env, а фактическое подключение выполняется владельцем аккаунта.
Дизайн и стилизация
Цветовая схема:
Темная тема:
- Фон:
#0f172a(slate-900) - Поверхности:
#1e293b(slate-800) - Акцент:
#6366f1(indigo-500) - Текст:
#f1f5f9(slate-100)
Светлая тема:
- Фон:
#ffffff - Поверхности:
#f8fafc(slate-50) - Акцент:
#6366f1(indigo-500) - Текст:
#0f172a(slate-900)
Типографика:
- Заголовки: Inter или System Font Stack
- Текст: Inter или System Font Stack
- Моноширинный: JetBrains Mono (для кода)
Анимации:
- Плавные переходы при скролле
- Hover эффекты на карточках
- Параллакс для hero секции (опционально)
Деплой
Рекомендуемые платформы:
-
Vercel (рекомендуется)
- Автоматический деплой из Git
- SSR/SSG поддержка
- CDN по умолчанию
-
Netlify
- Аналогично Vercel
- Хорошая поддержка Nuxt
-
GitHub Pages (только SSG)
- Бесплатный хостинг
- Требует
nuxt generate
Target деплой:
- Render
- Режим: Static Site Generation (SSG) (статическая генерация)
Конфигурация для деплоя:
// nuxt.config.ts
export default defineNuxtConfig({
// Для SSG (пререндер страниц, а не SPA):
ssr: true,
nitro: { preset: 'static' },
routeRules: {
'/': { prerender: true },
'/download': { prerender: true },
},
})
Важно про i18n + SSG (критично для SEO)
routeRules выше — это только базовый пример. Для i18n нужно гарантировать, что пререндерятся все локали.
Правило:
- Источник правды — список локалей + список страниц.
- На их основе формируем список путей для пререндера и для sitemap.
Пример логики (идея, не финальный код):
- Локали:
['en', 'ru', 'es', 'fr', 'de', 'uk'], default =en - Страницы:
['/', '/download'] - Генерация путей:
- для
en:['/', '/download'] - для остальных:
['/ru', '/ru/download'],['/es', '/es/download'], ...
- для
Это решает сразу две проблемы:
- не теряем локали при деплое (все страницы реально существуют как статик)
- sitemap/alternate можно генерировать из той же таблицы
См. также: landing/docs/ARCHITECTURE_GUARDRAILS.md (раздел про “источник правды по URL”).
Чеклист разработки
Фаза 1: Настройка проекта
- Инициализация Nuxt 3 проекта
- Установка Vuetify 3
- Настройка TypeScript
- Настройка Pinia (
@pinia/nuxt) и базовых stores (общие состояния не держим “размазанными” по компонентам) - Настройка i18n с определением локации
- Настройка Vuetify с определением темы браузера
- Установка @vueuse/nuxt для usePreferredDark
- ESLint (
@nuxt/eslint) + Prettier (единый стиль форматирования) - Базовая структура папок
Фаза 2: Layout и навигация
- Создание
default.vuelayout - Компонент
AppHeader.vueс навигацией - Компонент
AppFooter.vue - Компонент
LanguageSwitcher.vue - Адаптивная навигация (мобильное меню)
Фаза 3: Главная страница
- Hero Section
- Features Section (6 карточек)
- Providers Section
- Screenshots Section (карусель)
- Download Section
- Privacy Section
- FAQ Section
Фаза 4: Локализация и тема
- Переводы для всех 6 языков
- Композабл
useBrowserTheme.tsдля определения темы - Композабл
useLocation.tsдля определения локации - Плагин
init-theme-locale.client.tsдля автоинициализации - Маппинг стран к языкам
- Определение языка по браузеру (client-side)
- Определение темы по системным настройкам
- Сохранение выбора пользователя в cookies/localStorage
- Тестирование переключения языков и темы
- SEO мета-теги для каждого языка
- Правильные URL для каждого языка
Фаза 5: Функциональность
- Определение платформы пользователя
- Кнопки загрузки с правильными ссылками
- Страница
/downloadс инструкциями - Аналитика (GA4) + события (скачивание, смена языка/темы, FAQ, CTA)
Фаза 6: Оптимизация
- Оптимизация изображений
- Lazy loading для секций
- SEO оптимизация
- Структурированные данные
- Sitemap и robots.txt
Фаза 7: Тестирование
- Тестирование на разных устройствах
- Тестирование всех языков
- Тестирование автоматического определения языка по браузеру
- Тестирование автоматического определения темы
- Тестирование переключения темы вручную
- Тестирование сохранения выбора пользователя
- Проверка производительности
- Проверка доступности (a11y)
Фаза 8: Деплой
- Настройка CI/CD
- Деплой на выбранную платформу
- Настройка домена
- SSL сертификат
Дополнительные рекомендации
Определение темы и локации:
-
Приоритет определения языка:
- Cookie (если пользователь уже выбирал)
- Браузерные настройки (
navigator.language) - Fallback на английский
-
Приоритет определения темы:
- localStorage (если пользователь выбирал вручную)
- Системные настройки браузера (
prefers-color-scheme) - Fallback на светлую тему
-
Альтернативные API для геолокации:
- Не используем в рамках текущего статического лендинга (SSG).
-
Обработка ошибок:
- Таймаут для API запросов (3 секунды)
- Graceful fallback на браузерные настройки
- Логирование ошибок для отладки
Производительность:
- Использовать
nuxt/imageдля оптимизации изображений - Lazy loading для компонентов ниже fold
- Code splitting для больших компонентов
- Никаких внешних geo-запросов: меньше точек отказа и проще с приватностью.
Доступность:
- Семантический HTML
- ARIA атрибуты
- Keyboard navigation
- Контрастность цветов (WCAG AA)
Аналитика:
- Google Analytics 4
- Plausible (privacy-focused)
- Yandex Metrika (для русскоязычной аудитории)
Мониторинг:
- Sentry для отслеживания ошибок
- Uptime monitoring
Примеры реализации компонентов
Hero Section
<template>
<v-container class="hero-section">
<v-row align="center" justify="center" class="fill-height">
<v-col cols="12" md="8" class="text-center">
<h1 class="text-h2 font-weight-bold mb-4">
{{ $t('hero.title') }}
</h1>
<p class="text-h6 text-medium-emphasis mb-8">
{{ $t('hero.subtitle') }}
</p>
<div class="d-flex gap-4 justify-center flex-wrap">
<DownloadButton :platform="platform" />
<v-btn
size="large"
variant="outlined"
@click="scrollToFeatures"
>
{{ $t('hero.viewFeatures') }}
</v-btn>
</div>
</v-col>
</v-row>
</v-container>
</template>
Feature Card
<template>
<v-card class="feature-card" elevation="2">
<v-card-text class="text-center pa-6">
<v-icon
:icon="icon"
size="64"
color="primary"
class="mb-4"
/>
<h3 class="text-h6 mb-2">{{ title }}</h3>
<p class="text-body-2 text-medium-emphasis">
{{ description }}
</p>
</v-card-text>
</v-card>
</template>
Контакты и ресурсы
- GitHub: https://github.com/777genius/voice-to-text
- Документация: (если есть)
- Поддержка: (если есть)
Версия плана: 1.0
Дата создания: 2025-01-17
Статус: Готов к реализации
План итераций (сначала планируем, затем перепроверяем, потом реализуем)
Процесс:
- Сначала готовим максимально подробные планы итераций.
- Затем несколько раз перепроверяем планы (полнота, несостыковки, риски, критерии готовности).
- Только после этого начинаем реализацию пошагово.
- После реализации — сверяемся с планами и ещё раз перепроверяем соответствие.
Файлы итераций:
landing/docs/iterations/ITERATION_00_REQUIREMENTS.mdlanding/docs/iterations/ITERATION_01_SCAFFOLDING.mdlanding/docs/iterations/ITERATION_02_UI_SECTIONS.mdlanding/docs/iterations/ITERATION_03_SWIPER_AND_DOWNLOAD.mdlanding/docs/iterations/ITERATION_04_ANALYTICS_SEO_SSG_RENDER.md