Files
nextjs-style-guide/docs/index.md
S.Gromov 5cf0f0f8ba fix: переключатель языка и иконка репозитория в шапке
- восстановлены link для локалей: ru → /ru/, en → /en/ (были сломаны на /)
- добавлена иконка GitHub в socialLinks шапки VitePress для ru и en
  со ссылкой на https://gromlab.ru/docs/nextjs-style-guide
- root-локаль скрыта из переключателя языка через CSS (display: none
  для href="/"): в дропдауне теперь только Русский и English
- pill-кнопка «Репозиторий» добавлена в блок controls лендинга
  с иконкой GitHub, открывается в новой вкладке
- мобильная вёрстка лендинга переработана: контролы стопкой,
  репозиторий на ≤480px сжимается до иконки 36x36, увеличены
  отступы между блоками (hero / controls / cards) для разделения
2026-04-25 21:14:17 +03:00

13 KiB
Raw Blame History

layout
layout
false
<script setup> import { ref, computed, onMounted } from 'vue' const STORAGE_KEY = 'nsg-landing-lang' const THEME_KEY = 'vitepress-theme-appearance' // __BUILD_VERSION__ подставляется Vite-define из ENV `BUILD_VERSION` // (см. .vitepress/config.ts). В dev и build всегда определена. const buildVersion = __BUILD_VERSION__ const dict = { ru: { tagline: 'Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.', langLabel: 'Язык', themeLabel: 'Тема', repoLabel: 'Репозиторий', themes: { auto: 'Авто', light: 'Светлая', dark: 'Тёмная' }, cards: { docs: { title: 'Документация', desc: 'Все разделы: процессы разработки, базовые правила, прикладные руководства.', href: './ru/', cta: 'Открыть', }, ai: { title: 'Ассистенту', desc: 'Карта документации в формате llms.txt для AI-агентов.', buttons: [ { label: 'llms.txt', href: './ru/llms.txt' }, { label: 'llms-full.txt', href: './ru/llms-full.txt' }, ], }, zip: { title: 'Скачать правила', desc: 'Архив всех Markdown-файлов одним ZIP.', href: './nextjs-style-guide-ru.zip', cta: 'Скачать', }, }, }, en: { tagline: 'Conventions for Next.js project development: application architecture and layers, code structure, module organization, styling, typing, and infrastructure.', langLabel: 'Language', themeLabel: 'Theme', repoLabel: 'Repository', themes: { auto: 'Auto', light: 'Light', dark: 'Dark' }, cards: { docs: { title: 'Documentation', desc: 'All sections: development processes, basic rules, applied guides.', href: '#', cta: 'Open', badge: 'in development', }, ai: { title: 'For Assistant', desc: 'Documentation map in llms.txt format for AI agents.', badge: 'in development', buttons: [ { label: 'llms.txt', href: '#' }, { label: 'llms-full.txt', href: '#' }, ], }, zip: { title: 'Download rules', desc: 'Archive of all Markdown files and llms.txt in a single ZIP.', href: '#', cta: 'Download', badge: 'soon', }, }, }, } const lang = ref('ru') const theme = ref('auto') onMounted(() => { const savedLang = localStorage.getItem(STORAGE_KEY) if (savedLang === 'ru' || savedLang === 'en') { lang.value = savedLang } else { const nav = (navigator.language || 'ru').toLowerCase() lang.value = nav.startsWith('ru') ? 'ru' : 'en' } const savedTheme = localStorage.getItem(THEME_KEY) theme.value = savedTheme === 'dark' || savedTheme === 'light' ? savedTheme : 'auto' }) const t = computed(() => dict[lang.value]) function setLang(value) { lang.value = value localStorage.setItem(STORAGE_KEY, value) } function setTheme(value) { theme.value = value if (value === 'auto') { localStorage.removeItem(THEME_KEY) } else { localStorage.setItem(THEME_KEY, value) } const isDark = value === 'dark' || (value === 'auto' && matchMedia('(prefers-color-scheme: dark)').matches) document.documentElement.classList.toggle('dark', isDark) } /** * Клик по кнопке темы: * - по активной → переключение в auto; * - по неактивной → выбор этого варианта. */ function toggleTheme(value) { setTheme(theme.value === value ? 'auto' : value) } </script>

NextJS Style Guide

{{ t.tagline }}

{{ t.repoLabel }}
Русский English

{{ t.cards[key].title }} {{ t.cards[key].badge }}

{{ t.cards[key].desc }}

{{ t.cards[key].title }} {{ t.cards[key].badge }}

{{ t.cards[key].desc }}

{{ t.cards[key].cta }} →

v{{ buildVersion }}

<style scoped> .landing { min-height: 100vh; padding: 48px 32px; background: var(--vp-c-bg); color: var(--vp-c-text-1); font-family: var(--vp-font-family-base); display: flex; flex-direction: column; justify-content: center; gap: 64px; } .landing__controls { display: flex; justify-content: center; align-items: center; gap: 12px; margin-top: 28px; flex-wrap: wrap; } .landing__controls > * { height: 36px; box-sizing: border-box; } .landing__repo { display: inline-flex; align-items: center; gap: 8px; padding: 0 14px; border: 1px solid var(--vp-c-divider); border-radius: 999px; background: var(--vp-c-bg-soft); color: var(--vp-c-text-2); font-size: 13px; font-weight: 500; text-decoration: none; transition: color 0.15s, border-color 0.15s; } .landing__repo:hover { color: var(--vp-c-text-1); border-color: var(--vp-c-brand-1); } .landing__repo svg { flex-shrink: 0; } .seg { display: inline-flex; align-items: stretch; padding: 4px; background: var(--vp-c-bg-soft); border: 1px solid var(--vp-c-divider); border-radius: 999px; gap: 2px; } .seg__btn { appearance: none; display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 6px 14px; font-size: 13px; font-weight: 500; font-family: inherit; line-height: 1; color: var(--vp-c-text-2); background: transparent; border: none; border-radius: 999px; cursor: pointer; transition: color 0.15s, background-color 0.15s; } .seg__btn:hover { color: var(--vp-c-text-1); } .seg__btn--active { background: var(--vp-c-bg); color: var(--vp-c-text-1); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); } .seg--icons .seg__btn { padding: 6px 10px; } .seg__btn svg { display: block; } .landing__hero { text-align: center; } .landing__title { font-size: 56px; font-weight: 700; line-height: 1; margin: 0 0 16px; letter-spacing: -0.02em; background: linear-gradient(120deg, var(--vp-c-brand-1), var(--vp-c-brand-2)); -webkit-background-clip: text; background-clip: text; color: transparent; } .landing__tagline { font-size: 18px; line-height: 1.55; color: var(--vp-c-text-2); margin: 0 auto; max-width: 720px; } .landing__cards { max-width: 1100px; width: 100%; margin: 0 auto; display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; } .landing__card { display: flex; flex-direction: column; gap: 8px; padding: 24px; border-radius: 12px; background: var(--vp-c-bg-soft); border: 1px solid var(--vp-c-divider); text-decoration: none; color: inherit; transition: border-color 0.2s, transform 0.2s; } .landing__card:hover { border-color: var(--vp-c-brand-1); transform: translateY(-2px); } .landing__card--soon { opacity: 0.55; cursor: not-allowed; pointer-events: none; } .landing__card h3 { margin: 0; font-size: 18px; font-weight: 600; color: var(--vp-c-text-1); } .landing__card p { margin: 0; font-size: 14px; line-height: 1.5; color: var(--vp-c-text-2); } .landing__cta { margin-top: auto; font-size: 14px; font-weight: 500; color: var(--vp-c-brand-1); } .landing__buttons { margin-top: auto; display: flex; flex-wrap: wrap; gap: 8px; } .landing__button { display: inline-flex; align-items: center; padding: 6px 12px; font-size: 13px; font-weight: 500; font-family: var(--vp-font-family-mono, monospace); color: var(--vp-c-text-1); background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 8px; text-decoration: none; transition: border-color 0.15s, color 0.15s; } .landing__button:hover { border-color: var(--vp-c-brand-1); color: var(--vp-c-brand-1); } .landing__version { text-align: center; margin: 24px 0 0; font-size: 12px; color: var(--vp-c-text-3); font-family: var(--vp-font-family-mono, monospace); } .landing__badge { display: inline-block; margin-left: 8px; padding: 2px 8px; font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.04em; border-radius: 999px; background: var(--vp-c-bg-mute); color: var(--vp-c-text-3); vertical-align: middle; } @media (max-width: 768px) { .landing { padding: 48px 20px 56px; gap: 40px; justify-content: flex-start; } .landing__title { font-size: 36px; } .landing__tagline { font-size: 16px; } .landing__cards { grid-template-columns: 1fr; gap: 16px; } .landing__controls { gap: 8px; margin-top: 36px; } } @media (max-width: 480px) { .landing { padding: 44px 16px 48px; gap: 36px; } .landing__title { font-size: 30px; } .landing__tagline { font-size: 15px; line-height: 1.5; } .landing__controls { margin-top: 32px; } /* Репозиторий — только иконка, без текста, чтобы все контролы влезли в строку */ .landing__repo { width: 36px; padding: 0; justify-content: center; } .landing__repo span { display: none; } .seg__btn { padding: 6px 12px; font-size: 12px; } .landing__card { padding: 20px; } } </style>