layout
<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: 'Тема',
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',
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.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;
}
.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: 16px 16px 48px;
gap: 32px;
}
.landing__title {
font-size: 36px;
}
.landing__cards {
grid-template-columns: 1fr;
}
.landing__controls {
gap: 8px;
}
}
</style>