React 18 · TypeScript · Vite · SCSS · Vercel
Персональный сайт Стаса / S_TN / Stress_TN — чип-музыканта и автора саундтреков к видеоиграм.
Все данные хранятся в одном JSON-файле и управляются через встроенную CMS-панель без пересборки кода.
- О проекте
- Стек технологий
- Архитектура проекта
- Секции сайта
- Структура данных data.json
- Переменные окружения
- Быстрый старт
- Команды
- Деплой на Vercel
- Работа с Админ-панелью /cms
- API-эндпоинты Serverless
- Как добавить новый релиз
- Как добавить концерт
- Как опубликовать новость
- Загрузка изображений
- LSDJ Kits
- FAQ / Частые проблемы
Сайт выполнен в ретро-эстетике Commodore 64 с элементами глитча и пиксельных шрифтов. При первом открытии страницы запускается анимация отрисовки контура Gameboy (библиотека lazy-line-painter), после завершения которой подгружаются данные из data.json и рендерится основной контент.
Вся контентная часть сайта (биография, новости, дискография, события) управляется через CMS-панель по адресу /cms. Изменения сохраняются прямо в GitHub-репозиторий через API — сайт автоматически перестраивается на Vercel в течение ~1 минуты.
| Слой | Технология |
|---|---|
| UI | React 18, TypeScript, SCSS/Sass |
| Сборка | Vite 6, @vitejs/plugin-react, vite-plugin-svgr |
| Роутинг | React Router DOM v6 |
| Медиа | react-player (SoundCloud / YouTube) |
| Анимация | lazy-line-painter (SVG-анимация Gameboy) |
| Флаги стран | flag-icons |
| Backend / API | Vercel Serverless Functions (Node.js) |
| Деплой | Vercel + GitHub |
| Данные | public/data.json (загружается в рантайме) |
| Мультиязычность | Встроенная i18n (RU/EN), localStorage |
Сайт поддерживает русский и английский языки.
В правом верхнем углу страницы (фиксированная кнопка) отображается переключатель RU / EN.
Кнопка всегда видна при прокрутке (position: fixed).
Клик мгновенно переключает язык всех статических надписей на сайте.
Выбор сохраняется в localStorage и применяется при следующем визите.
При первом посещении язык определяется автоматически по языку браузера.
| Элемент | RU | EN |
|---|---|---|
| Заголовки секций | Биография, Новости, Концерты… | Bio, News, Events… |
| Биография | из поля bio_ru в data.json |
из поля bio в data.json |
| Дискография | Альбомы и EP / Синглы и ремиксы | Albums & EPs / Singles and Remixes |
| Футер | Все права принадлежат S_TN | All rights belong to S_TN |
| Страница 404 | Страница не найдена | Page Not Found |
| Лоадер iframe | ЗАГРУЗКА | LOADING |
Остальной контент (новости, названия релизов, события) вводится вручную через админку — автор сам решает на каком языке писать тексты.
Вкладка 👤 Bio в Админ-панели содержит два редактора:
- Bio EN — английская версия (поле
bioвdata.json) - Bio RU — русская версия (поле
bio_ruвdata.json)
Оба поля поддерживают HTML-теги и показывают предпросмотр прямо под редактором.
Если bio_ru не заполнен — на русском языке отобразится английская версия.
Все строки находятся в одном файле:
src/i18n/translations.ts
const translations = {
en: { bio: 'Bio', news: 'News', ... },
ru: { bio: 'Биография', news: 'Новости', ... },
}Чтобы добавить новый язык (например de):
- Добавь объект
de: { ... }вtranslations.ts - Обнови тип
Lang = 'ru' | 'en' | 'de'в том же файле - Добавь кнопку
DEв компонентLangSwitcherвApp.tsx
WEB/
├── api/ # Vercel Serverless Functions
│ ├── save-data.js # Сохранение data.json в GitHub
│ ├── upload-image.js # Загрузка картинок в GitHub
│ ├── resolve-player.js # Резолв URL → ID плеера (BC/SC/YT)
│ └── bandcamp-desc.js # Получение описания с Bandcamp
│
├── public/ # Статика (копируется в build/)
│ ├── data.json # ★ ГЛАВНЫЙ ФАЙЛ ДАННЫХ
│ ├── images/
│ │ ├── albums/ # Обложки релизов
│ │ ├── extra/ # Медиа для новостей
│ │ └── games/ # Обложки игр
│ ├── kits/ # Бесплатные LSDJ-киты
│ └── favicon/ # Анимированная favicon (0–4.gif)
│
├── src/
│ ├── index.js # Точка входа, монтирует Gameboy
│ ├── Gameboy.tsx # Загрузочный экран + загрузка data.json
│ │
│ ├── context/
│ │ └── AppContext.tsx # React Context (передаёт AppData по дереву)
│ │
│ ├── types/
│ │ └── appData.tsx # TypeScript-интерфейсы данных
│ │
│ └── components/
│ ├── App.tsx # Главный компонент, роутер, раскладка
│ ├── Bio.tsx # Секция «Биография»
│ ├── News.tsx # Секция «Новости»
│ ├── Newpost.tsx # Карточка новости
│ ├── Discography.tsx # Секция «Дискография»
│ ├── Disc.tsx # Карточка релиза
│ ├── Popup.tsx # Попап с деталями релиза
│ ├── Events.js # Секция «События» (гиги/выступления)
│ ├── Player.tsx # SoundCloud плейлист
│ ├── Game.tsx # Ссылки на игры (OST)
│ ├── Emulator.tsx # Iframe-эмулятор «Hello World»
│ ├── Mup.tsx # Iframe-виджет «МУП»
│ ├── Kits.tsx # Скачать LSDJ-киты
│ ├── Cotacts.tsx # Ссылки на соцсети
│ ├── Donate.tsx # Кнопка PayPal-доната
│ └── admin/
│ └── AdminPanel.tsx # ★ CMS-панель (/cms)
│
├── vercel.json # Правила роутинга и редиректов Vercel
├── vite.config.ts # Конфигурация сборщика
└── package.json
GitHub (public/data.json)
│
│ fetch() при загрузке страницы
▼
Gameboy.tsx ──► AppContext.Provider ──► App.tsx
│
┌────────────────────────────┤
▼ ▼ ▼ ▼
Bio News Discography Events ...
(читают данные из AppContext)
Данные не кэшируются в localStorage — каждый раз при открытии сайта они загружаются заново по URL:
- На Vercel:
https://raw.githubusercontent.com/{owner}/{repo}/{branch}/public/data.json - Локально:
/data.json
| Секция | Компонент | Данные из JSON | Описание |
|---|---|---|---|
| Bio | Bio.tsx |
bio (HTML-строка) |
Биография, поддерживает HTML-теги |
| News | News.tsx |
news[] |
Посты: изображение / видео / текст |
| Discography | Discography.tsx |
discography[] |
Альбомы и синглы с плеерами |
| Events | Events.js |
events[] |
Концерты, сгруппированы по годам |
| Player | Player.tsx |
hardcoded URL | SoundCloud-плейлист |
| Games OST | Game.tsx |
hardcoded | Ссылки на игры Kinto Games |
| Hello World | Emulator.tsx |
iframe | Встроенный C64-эмулятор |
| MUP | Mup.tsx |
iframe | Интерактивный виджет «МУП» |
| LSDJ Kits | Kits.tsx |
hardcoded | Скачать бесплатные саундкиты |
| Subscribe | Cotacts.tsx |
hardcoded | Соцсети (Spotify, VK, YouTube…) |
| Donate | Donate.tsx |
hardcoded | PayPal-форма доната |
Каждую секцию можно включить/выключить через Settings в Админ-панели (поле
visibleSectionsвdata.json).
{
"visibleSections": {
"bio": true,
"news": true,
"discography": true,
"game": true,
"player": true,
"events": true,
"donate": false,
"contacts": true,
"mup": true,
"emulator": true,
"kits": true
},
"bio": "<p>HTML-текст биографии...</p>",
"news": [
{
"show": true,
"date": 1618207620,
"title": "Название поста",
"description": "Описание",
"link": "https://...",
"type": "image",
"media": "./images/extra/boy.gif"
}
],
"discography": [
{
"show": true,
"type": "album",
"title": "Название",
"author": "S_TN",
"year": "2025",
"bandcampAlbum": 569378756,
"bandcampTrack": null,
"soundcloudPlayer": "123",
"youtubeId": "dQw4w9WgXcQ",
"labelName": "Self-released",
"labelLink": "https://...",
"releaseId": "STN-001",
"coverLink": "../images/albums/cover.jpg",
"downloadLink": "https://band.link/...",
"tracklist": ["Track 1", "Track 2"]
}
],
"events": [
{
"date": "21 February 2026",
"country": "Russia",
"city": "Moscow",
"title": "Название концерта",
"link": "https://..."
}
]
}Создай файл .env.local в корне проекта (не коммитится в git):
# Пароль для входа в админку (фронтенд)
VITE_ADMIN_PASSWORD=your_password
# Для загрузки данных с GitHub Raw (фронтенд)
VITE_GITHUB_OWNER=stanislavche
VITE_GITHUB_REPO=web
VITE_GITHUB_BRANCH=masterНа Vercel нужно добавить ещё серверные переменные (Project Settings → Environment Variables):
# Пароль (бэкенд, должен совпадать с VITE_ADMIN_PASSWORD)
ADMIN_PASSWORD=your_password
# GitHub Personal Access Token (нужны права repo → write contents)
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
# Репозиторий
GITHUB_OWNER=stanislavche
GITHUB_REPO=web
GITHUB_BRANCH=master
# Путь к файлу данных (необязательно, по умолчанию: public/data.json)
DATA_FILE_PATH=public/data.jsonВажно:
VITE_ADMIN_PASSWORDиADMIN_PASSWORDдолжны совпадать.
Переменные с префиксомVITE_доступны в браузере, без префикса — только на сервере.
git clone https://github.com/stanislavche/web.git
cd web
npm install# Создать .env.local и заполнить значениями (см. раздел выше)npm start
# → http://localhost:3000# Запустить в режиме разработки
npm start
# Собрать продакшн-билд (→ папка build/)
npm run build
# Предпросмотр продакшн-билда локально
npm run preview
# Запустить тесты
npm test- Зарегистрируйся на vercel.com и подключи GitHub-репозиторий
- В настройках проекта укажи:
- Build Command:
npm run build - Output Directory:
build
- Build Command:
- Добавь все переменные окружения (см. раздел выше)
- Первый деплой запустится автоматически при пуше в репозиторий
Админ-панель (/cms)
│
│ POST /api/save-data
▼
Vercel Function
│
│ GitHub API (PUT contents/public/data.json)
▼
GitHub коммит
│
│ Vercel webhook → новый деплой
▼
Сайт обновляется за ~1 мин
Данные загружаются из GitHub Raw напрямую в браузер — изменения видны сразу после деплоя.
Перейди по адресу https://твой-домен/cms
Введи пароль из переменной VITE_ADMIN_PASSWORD. По умолчанию: s_tn
Сессия хранится в
sessionStorageи сбрасывается при закрытии вкладки.
┌──────────────────────────────────────────────────────────────────────┐
│ ◆ S_TN ADMIN [🚀 Save & Deploy] [⬇ Download] [Logout] │
├──────────┬────────────┬──────────┬──────────┬────────────────────────┤
│ 🎤 Events│ 💿 Disc │ 📰 News │ 👤 Bio │ ⚙ Settings │
├──────────┴────────────┴──────────┴──────────┴────────────────────────┤
│ │
│ [ Контент вкладки ] │
│ │
└──────────────────────────────────────────────────────────────────────┘
Список выступлений, сгруппированных по годам на главной странице.
Добавить событие:
- Нажми
+ Add Event - Заполни поля:
- Date — текстовая дата:
21 February 2026 - Country — выбери из списка (появится флаг страны)
- City — название города
- Title — название мероприятия
- Link — URL (необязательно; если указан, строка становится кликабельной)
- Date — текстовая дата:
- Нажми
Save
Управление списком: ▲ ▼ — сортировка, ✎ — редактирование, ✕ — удаление.
Список альбомов и синглов с встроенными плеерами.
Добавить релиз:
- Нажми
+ Add - В блоке 🔗 URL-ресолвера вставь ссылку на Bandcamp/SoundCloud/YouTube и нажми
🔍 Resolve:- Автоматически определит платформу и извлечёт метаданные
- Нажми
✓ Применить в форму
- Дозаполни поля вручную при необходимости:
| Поле | Описание |
|---|---|
| Type | album или single |
| Show | Visible — показывать, Hidden — скрыть |
| Title | Название релиза |
| Author | Исполнитель (по умолчанию S_TN) |
| Year | Год выхода |
| Label Name / Link | Лейбл и ссылка на него |
| Release ID | Каталожный номер (напр. STN-001) |
| Bandcamp Album ID | Числовой ID альбома на Bandcamp |
| Bandcamp Track ID | Числовой ID трека (для синглов) |
| SoundCloud Player ID | Числовой ID трека SoundCloud |
| YouTube Video ID | 11-значный ID (напр. dQw4w9WgXcQ) |
| Cover Link | Путь к обложке: ../images/albums/name.jpg |
| Download Link | Ссылка для скачивания/покупки |
| Tracklist | Список треков (по одному) |
- Нажми
Save
Фильтры: All / Album / Single — для навигации по большому списку.
Иконки 👁 / 🚫 — быстро скрыть/показать релиз без удаления.
Добавить пост:
- Нажми
+ Add Post - Заполни поля:
| Поле | Описание |
|---|---|
| Show | Visible / Hidden |
| Date | Unix timestamp (секунды). Текущее время подставляется автоматически |
| Title | Заголовок поста |
| Type | image — с картинкой, video — видео, text — текст |
| Description | Описание (поддерживает HTML) |
| Link | Ссылка при клике на пост |
| Media | Путь к картинке (./images/extra/...) или URL видео |
- Для загрузки картинки используй кнопку 🖼 Загрузить картинку (макс. 5 МБ)
- Нажми
Save
Посты отображаются в обратном хронологическом порядке — новые сверху.
Редактор биографии с поддержкой HTML-тегов:
<p class="container__text">Основной текст</p>
<b>Жирный</b>
<a href="https://...">Ссылка</a>
<br />Предпросмотр обновляется прямо под полем ввода.
Переключатели для каждой секции главной страницы:
| Ключ | Секция на сайте |
|---|---|
bio |
Биография |
news |
Новости |
discography |
Дискография |
game |
Игры (OST) |
player |
Плеер SoundCloud |
events |
Концерты |
donate |
Кнопка доната |
contacts |
Соцсети |
mup |
Виджет МУП |
emulator |
Эмулятор Hello World |
kits |
Скачать LSDJ Kits |
| Кнопка | Действие |
|---|---|
| 🚀 Save & Deploy | Отправляет JSON в GitHub → триггерит перестройку ��айта |
| ⬇ Download | Скачивает data.json локально (резервная копия) |
| Logout | Завершить сессию |
Если
Save & Deployнедоступен (нет env-переменных) — используй Download, затем вручную замениpublic/data.jsonв репозитории.
| Метод | URL | Описание |
|---|---|---|
POST |
/api/save-data |
Обновляет data.json в GitHub репозитории |
POST |
/api/upload-image |
Загружает изображение в GitHub |
POST |
/api/resolve-player |
URL → ID плеера (Bandcamp/SoundCloud/YouTube) |
POST |
/api/bandcamp-desc |
Парсит описание альбома с Bandcamp |
// Запрос:
{ "password": "your_password", "data": { ...AppData } }
// Ответ:
{ "ok": true, "message": "Saved! Site will rebuild in ~1 min." }// Запрос:
{
"password": "your_password",
"filename": "cover.jpg",
"content": "base64...",
"folder": "albums"
}
// Ответ:
{ "ok": true, "path": "../images/albums/cover.jpg", "filename": "cover.jpg" }// Запрос:
{ "url": "https://stresstn.bandcamp.com/album/chiptune-is-not-dead" }
// Ответ:
{
"platform": "bandcamp",
"bandcampAlbum": 569378756,
"title": "Chiptune is not dead",
"artist": "S_TN",
"thumbnail": "https://..."
}- Открой
/cms→ вкладка 💿 Discography →+ Add - В URL-ресолвере вставь ссылку на Bandcamp/SoundCloud/YouTube →
🔍 Resolve→✓ Применить - Загрузи обложку кнопкой 🖼 Загрузить обложку
- Добавь треклист
- Нажми
Save→🚀 Save & Deploy
- Bandcamp: на странице альбома нажми
Share → Embed, из iframe-кода скопируй число послеalbum= - SoundCloud:
Share → Embed, из HTML найдиtracks%2F123456789— число и есть ID - YouTube: из URL
youtube.com/watch?v=XXXXXXXXXXX— 11 символов послеv=
/cms→ 🎤 Events →+ Add Event- Формат даты:
21 February 2026 - Для прошедших концертов ссылку можно не указывать
Save→🚀 Save & Deploy
/cms→ 📰 News →+ Add Post- Тип
image: загрузи картинку через 🖼 Загрузить картинку - Дата задаётся автоматически, её можно исправить (Unix timestamp в секундах)
Save→🚀 Save & Deploy
| Папка | Назначение | Путь в коде |
|---|---|---|
public/images/albums/ |
Обложки релизов | ../images/albums/name.jpg |
public/images/extra/ |
Медиа для новостей | ./images/extra/name.jpg |
public/images/games/ |
Логотипы игр | ../images/games/name.png |
- Максимальный размер файла через Upload API: 5 МБ
- Форматы: JPG, PNG, GIF, WebP
В папке public/kits/ лежат бесплатные саундкиты для LSDJ (Game Boy трекер):
| Kit | Описание |
|---|---|
C64_Sam_Reciter_1–4.kit |
Синтез речи SAM (Commodore 64) |
Reggae.kit |
Регги-инструменты |
Scream.kit |
Скримы и вокальные сэмплы |
Speak_and_Spell_numbers.kit |
Цифры Speak & Spell |
Speak_and_Spell_Voice_1–4.kit |
Голосовые пресеты Speak & Spell |
Доступны для скачивания на сайте в секции FREE LSDJ KITS или напрямую:
https://s-tn.space/kits/C64_Sam_Reciter_1.kit
❓ Не работает вход в админку
→ Убедись что VITE_ADMIN_PASSWORD задан в .env.local (локально) или в переменных Vercel.
❓ Save & Deploy возвращает Missing env vars
→ На Vercel не заданы GITHUB_TOKEN, GITHUB_OWNER или GITHUB_REPO. Добавь их в Project Settings → Environment Variables, затем передеплой проект.
❓ Данные сохранились, но сайт не обновился
→ Подожди 1–2 минуты. Vercel пересобирает автоматически. Если через 5 минут изменений нет — проверь вкладку Deployments в Vercel Dashboard.
❓ Картинка не отображается после загрузки
→ Нажми 🚀 Save & Deploy — это зафиксирует изменения и запустит пересборку с новыми файлами.
❓ URL-ресолвер не находит ID для Bandcamp
→ Получи ID вручную: на странице альбома Share → Embed, скопируй число после album=.
❓ Сайт не загружается локально
→ Проверь, что файл public/data.json существует и является валидным JSON.
Открой в браузере: http://localhost:3000/data.json
❓ Нужно изменить домен
→ Отредактируй файл public/CNAME (одна строка — домен без https://, напр. s-tn.space).
Проект создан в исследовательских и образовательных целях.
Все материалы (код, ассеты, ROM-образы, LSDJ-киты) распространяются на условиях:
Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)
- ✅ Свободное использование в учебных и некоммерческих целях
- ✅ Распространение с донатами
- ✅ Изучение и адаптация кода
- ❌ Коммерческое использование без разрешения
💼 По вопросам коммерческого использования: stress_tn@yahoo.com
Сторонние зависимости (React, Vite и др.) распространяются на условиях собственных лицензий.