Новости

Журнал обновлений сайта: что было добавлено, починено и улучшено по версиям.

  • Единый фирменный акцент вместо случайного цикла. Кнопка в шапке, циклившая CSS-переменную --accent по 10 случайным цветам, убрана из публичного интерфейса — на первом впечатлении бренд теперь всегда в своём постоянном оранжевом (--accent зашит в globals.css как источник правды для светлой и тёмной тем). Сам циклер никуда не делся, а стал скрытой пасхалкой: команда живёт только в палитре ⌘K, срабатывает по требованию и сбрасывается на фирменный цвет при перезагрузке. Заодно вычищен мёртвый UI-компонент AccentToggle — фича теперь экспортирует только переиспользуемый хелпер cycleAccent.

    v1.16.1
  • Раздел /websites стал продающим: тарифы, отзывы и прямой контакт. Вместо одной размытой цены «от 50 000 ₽» появилась сетка из трёх тарифов — Лендинг (от 50 000 ₽), Магазин на Tilda (от 100 000 ₽) и Кастом (по запросу) — у каждого свой состав работ и общий блок «входит в любой тариф». Добавлена секция отзывов на отдельном источнике данных (пока пустая, включается автоматически с появлением реальных отзывов, тогда же в разметку подтянется Review-schema) и блок прямой связи рядом с формой заявки — равновеликие кнопки Telegram и WhatsApp с фирменными логотипами. Заодно ширина контента выровнена по главной, из текстов убрано неверное «моя админка» (редактирование контента идёт через встроенный редактор Tilda), FAQ переписан под новые тарифы и способы связи, а meta-description укорочен до размера сниппета выдачи.

    v1.16.0
  • Пет-проекты отделены от коммерческих кейсов. Пункт «Работы» в основной навигации переименован в «Кейсы» и пока показан серым неактивным — раздел ждёт реальных клиентских кейсов с задачей, решением и результатом. Существующие учебные и «для себя» проекты (включая сайт кафе) переехали в раздел «Проекты», доступный со страницы Контакты по аналогии с другими второстепенными страницами. С витрины услуги и с главной убран блок «Примеры работ» с пет-проектами — покупатель видит только релевантный контент.

    v1.15.0
  • Шапка сфокусирована на клиенте: в основной навигации остались только Услуги, Работы, Блог и Контакты вместо прежних восьми пунктов. Служебные и «для себя» страницы — Новости, Лента, UI Kit и Курс ИИ — убраны из шапки и футера, но никуда не делись: доступны по прямой ссылке, в командной палитре ⌘K и новым блоком на странице Контакты. Сама страница Контакты обзавелась карточкой автора с био и разделом «Ещё на сайте», собирающим все второстепенные ссылки в одном месте.

    v1.14.1
  • Английская версия получила собственные индексируемые URL. Раньше RU и EN жили на одном адресе (localePrefix «never», язык выбирался кукой) — из-за этого весь en.json был невидим для поиска, а hreflang указывал сам на себя. Теперь next-intl работает в режиме «as-needed»: русский остаётся на адресах без префикса, английский переехал на /en/…, у каждого языкового варианта свои canonical и взаимные hreflang, а sitemap перечисляет обе версии. Навигация, переключатель языка, командная палитра и серверные редиректы стали локале-зависимыми, чтобы англоязычный посетитель не проваливался на русские страницы.

    v1.14.0
  • Единый источник правды для домена и канонический адрес на sandwor.online. Раньше весь SEO был прибит к sandwor.ru хардкодами в семи местах (metadataBase, sitemap, robots, feed, JSON-LD, OG), из-за чего canonical, Open Graph и карта сайта указывали на домен, недоступный из России из-за размещения на Vercel. Теперь адрес сайта задаётся одной константой SITE_URL из переменной NEXT_PUBLIC_SITE_URL с дефолтом на живой sandwor.online, а все ссылки — canonical, OG, sitemap, robots, feed и JSON-LD — сходятся на неё; sandwor.ru остаётся живым для связки с телеграм-ботом, но больше не заявляется как основной.

    v1.13.1
  • Веб-аналитика: Яндекс.Метрика и подтверждение в Google Search Console. Раньше на сайте не было никакой аналитики — теперь в корневой layout добавлен счётчик Яндекс.Метрики с вебвизором, картой кликов и точным показателем отказов; скрипт грузится через next/script со стратегией afterInteractive, номер счётчика вынесен в переменную NEXT_PUBLIC_YM_ID, а без неё счётчик не рендерится, чтобы локальная разработка не засоряла статистику. Успешная отправка трёх форм засчитывает JS-цели через reachGoal: заявка с главной (order_home), заявка со страницы /websites (order_websites) и сообщение из формы контактов (contact) — идентификаторы централизованы в YM_GOAL. Для Google Search Console добавлен мета-тег google-site-verification, подтверждающий владение доменом без доступа к DNS.

    v1.13.0
  • Витрина услуги «создание сайтов» и раздел /websites. Главная сместила акцент с курса на разработку сайтов на заказ: hero стал полноэкранной витриной со слоганом «Делаю сайты для…» и typewriter-перебором аудиторий (кофейни, магазина, личного бренда и т.д.), двумя CTA — акцентная «Заказать» открывает модалку заявки, вторичная ведёт на /websites — и сохранённым машущим 3D-персонажем; баннер курса ужат, приглушён и опущен под витрину. Новая страница /websites раскрывает подход к клиенту, подбор инструментов (Tilda с админкой / лендинги / кастом), процесс, кейсы, цены от 50 000 ₽ и FAQ, а внизу — форма заявки по якорю; заявки уходят в тот же Telegram-бот, что и контакты. Под капотом появились переиспользуемые Modal, Typewriter, FormSuccess и общий хук useFormSubmit; поля форм переведены на переменную --field-bg, чтобы не сливаться с подложкой; для поиска добавлены FAQPage- и Service-разметка JSON-LD и keyword-rich заголовок с описанием.

    v1.12.0
  • Дефолтная OG-картинка. Страницы без собственной обложки получили брендовую карточку 1200×630 в og:image и twitter:card summary_large_image — ссылки на сайт в соцсетях и мессенджерах разворачиваются в полноценное превью. Картинка задана в корневом layout и продублирована в семи страницах со своим openGraph, потому что в Next дочерний openGraph затирает родительский целиком; статьи блога сохранили динамические обложки через /api/og.

    v1.11.1
  • Анимированный персонаж на главной. В hero поселился 3D-аватар автора: исходная картинка программно разрезана на два слоя — тело и ладонь (flood fill по связности альфа-канала с границей по запястью), и ладонь машет CSS-анимацией вокруг точки запястья, уважая prefers-reduced-motion. В слое тела дорисована внутренняя стенка рукава, чтобы при взмахе не было просветов на светлом фоне; на мобиле персонаж крупнее и обрезается границей секции, текст уступает ему место и подсвечен ореолом цвета фона. Источники — PNG: оптимизатор next/image сам отдаёт webp/avif современным браузерам и png древним.

    v1.11.0
  • SEO-добивка. Canonical и hreflang теперь в ‹head› каждой страницы — раньше альтернативы локалей декларировались только в sitemap; у блога и проектов появились meta-описания, /uikit впервые получил metadata. JSON-LD расширен: CreativeWork на страницах проектов со ссылками на репозиторий и live-сайт в sameAs, BreadcrumbList на статьях и проектах, Person на «Контактах». Попутно исправлен баг: BlogPosting и Course строили URL с префиксом локали (/ru/blog/…), которых при localePrefix: never не существует.

    v1.10.1
  • Лента активности /timeline. Новая страница объединяет статьи, проекты и релизы в одну хронологию: вертикальный рельс с точками, типизированные карточки, у релизов акцентный бейдж версии и первое предложение changelog-текста заголовком. Соседние релизы одного дня схлопываются в запись с диапазоном версий (v1.4.0 – v1.5.2) и счётчиком «и ещё N релизов». Разделы «Лента» и UI Kit добавлены в шапку и палитру ⌘K; навигация стала трёхступенчатой: бургер до 880px, полное меню без поиска до 1160px, дальше — с окошком «Поиск ⌘K», и пункты больше не переносятся на вторую строку.

    v1.10.0
  • Песочница компонентов в UI Kit. Вместо статичной витрины — интерактивный playground: табы по шести компонентам (Button, Badge, Input, Textarea, ProgressBar, DotIcon), контролы пропсов по типам и живой JSX-сниппет, который собирается из текущих значений и копируется одной кнопкой; настройки компонентов переживают переключение табов. В дизайн-системе появился собственный Select — кастомный выпадающий список с акцентной точкой-маркером выбранного пункта, закрытием по Escape и клику мимо. Высоты карточки зафиксированы под самый объёмный набор контролов, grid-колонки получили min-width: 0 — песочница не дёргается при переключении и не распирается на мобиле.

    v1.9.0
  • Точечная иконографика. Все иконки сайта переведены на единую систему DotIcon — круги радиуса 5 на сетке с шагом 10, инлайновый SVG через currentColor: поиск в шапке, все пункты командной палитры, каналы на «Контактах», баннер 404 и переключатели темы/акцента. Логотип «sandwor» пересобран из 92 точек, извлечённых парсером path-данных из исходного SVG, и анимирован: клик рассыпает и собирает буквы заново со случайными задержками точек (рандом генерируется только после гидрации, prefers-reduced-motion уважается). Пять svg-файлов из public/img удалены — иконки больше не грузятся отдельными запросами, а иконка темы перестала мигать после гидрации: обе версии рендерятся на сервере, видимую выбирает CSS по data-theme.

    v1.8.1
  • Страница «Контакты» полностью переработана. Hero с акцентным CTA «Написать на почту», иконки-каналы (Telegram, GitHub, Instagram, VK) на инлайновых SVG через currentColor и сплит-раскладка: слева интро, справа форма на подложке-карточке фиксированной высоты с экраном успеха (галочка + «отправить ещё»). Форма ожила — сообщения уходят в Telegram-бота через серверный роут /api/contact (Bot API sendMessage, экранирование HTML, валидация полей). Антиспам без CAPTCHA: honeypot-поле, тайм-трап на слишком быструю отправку и лимит ссылок, все срабатывания тихо отбрасываются. Попутно починен matcher middleware: next-intl перехватывал /api/* и ронял все API-роуты в 404.

    v1.8.0
  • Раздел News вместо Notes. Журнал релизов с semver-версионированием. Сущность Note расширена опциональным полем version, NoteItem рендерит бейдж справа от даты. 301-редирект /notes → /news сохраняет SEO-вес старой страницы. Раздел добавлен в основную навигацию через NAV_LINKS — раньше /notes был только по прямой ссылке.

    v1.7.0
  • Bundle analyzer и бюджет JS в CI. @next/bundle-analyzer даёт интерактивный treemap через `npm run analyze`. Свой 50-строчный скрипт scripts/check-bundle-size.mjs суммирует gzipped-размер всех чанков в .next/static/chunks и фейлит CI при превышении 350 KB. Текущее использование — 273 KB (78% бюджета).

    v1.6.2
  • Engineering hygiene. Pre-commit hook через husky + lint-staged: ESLint --fix и Prettier --write на staged-файлах, плюс полный tsc --noEmit. GitHub Actions CI: lint, typecheck, jest, build на каждый push в main и каждый PR. concurrency cancel-in-progress, кеш npm, HUSKY=0 для CI. Удалены мёртвые MOCK_POSTS из BlogPage/HomePage.

    v1.6.1
  • View Transitions API и анимированный бургер. Плавный crossfade со slide-up между страницами через next-view-transitions — нативный браузерный API, ~2 KB gzip, ноль JS-overhead. Все next/link заменены на патченый Link библиотеки. Бургер на мобиле: 3 полосы морфят в крест × за 240ms с cubic-bezier easing, сама кнопка обособлена фоном/бордером. prefers-reduced-motion respect.

    v1.6.0
  • Палитра ⌘K расширена. Поиск по статьям через новый эндпоинт /api/posts/list — slim-проекция (slug, title, tag) с кешем s-maxage=3600. Lazy fetch на первом открытии — нулевые запросы до клика, потом кешируется в state. Видимая «Поиск ⌘K» кнопка в шапке для discoverability. Декаплинг через CustomEvent('palette:open') — любой компонент может открыть палитру одной строкой.

    v1.5.3
  • Мобильная вёрстка курса. CourseTopbar стыкуется под Header'ом (top: 56px), не торчит из-под него. На мобиле — две flex-строки: название курса + Reset сверху, progress bar снизу. Сайдбар уроков переведён в колонку — раньше 2-колоночная сетка ломала «Документы и Excel» переносом на 2 строки. Убран дубль «Урок N из M» — остался только в LessonContent.

    v1.5.2
  • Sticky reading progress bar. Полоса сверху статьи появляется после 240px скролла: «N мин чтения · дата» + 2px полоска прогресса в --accent цвете, scaleX-анимация по скроллу документа. Scroll-handler throttled через requestAnimationFrame — батчится react'ом, ~60fps без long tasks. Высота 56px ровно под Header'ом, prefers-reduced-motion отключает анимацию.

    v1.5.1
  • Командная палитра ⌘K. Своя реализация без cmdk (~140 строк): substring-фильтр по label + keywords, ↑↓ Enter навигация, фокус-менеджмент, click-outside, Esc. Команды: 7 навигационных пунктов, тема (light/dark/system), смена локали, цикл accent. По пути вынесены общие хелперы useLocaleSwitch и cycleAccent — теперь и тогглы шапки, и палитра используют одну логику.

    v1.5.0
  • Курс: reset прогресса и deep-link на урок. Кнопка «Начать заново» в топбаре с подтверждением через window.confirm, чистит localStorage и query-параметр. URL `?lesson=N` (1-индексация) открывает указанный урок при заходе — игнорирует lock, чтобы можно было расшарить любую страницу. «Поделиться уроком» — кнопка копирования URL в буфер через navigator.clipboard, ✓-подсветка на 2 сек.

    v1.4.5
  • Похожие посты под статьёй. Алгоритм getRelatedPosts(slug, limit): сначала посты с тем же тегом (новейшие первыми), потом добиваем самыми свежими из остальных, пока не наберём limit. Реюзит существующий PostList виджет с разделителем-заголовком. Скрывается, если на блоге всего один пост.

    v1.4.4
  • SEO: реальный sitemap + hreflang. До правки sitemap.xml отдавал MOCK_POSTS — Google ходил на несуществующие URL, индексация ломалась. Теперь — реальные посты через getTelegramPosts, проекты из projects.json, курсы из COURSES_REGISTRY. Hreflang-alternates для ru/en на каждой записи декларируют мультиязычность. Правильные priority и changeFrequency.

    v1.4.3
  • RSS/Atom фид. Эндпоинт /feed.xml отдаёт валидный Atom 1.0 — переиспользует getTelegramPosts, XML-эскейп вручную (без либ). В ‹head› каждой страницы — тег ‹link rel="alternate" type="application/atom+xml"› для автодискавери (Feedly, Inoreader, Chrome-расширения подсвечиваются). Ссылка «RSS» в футере. Edge-кеш s-maxage=3600, stale-while-revalidate=86400.

    v1.4.2
  • Прогресс курса в баннере на главной. Возвращающийся пользователь видит «Продолжить (3 из 6) →» вместо «Начать курс», точки уроков подсвечены ✓ по выполненным. localStorage-ключ и loadCourseState вынесены в общий lib, хук useCourseProgress читает их в client-overlay поверх server-баннера. SSR рендерит дефолт, чтобы не было hydration mismatch.

    v1.4.1
  • SEO-фундамент: OG-картинки и JSON-LD. Edge-роут /api/og рендерит 1200×630 PNG через next/og (он же @vercel/og) — динамическая обложка с заголовком, тегом и брендом. Подключено через generateMetadata в openGraph.images + twitter card summary_large_image. JSON-LD структурированные данные: Person + WebSite на главной, BlogPosting на каждой статье, Course на курсе — даёт rich-сниппеты в Google и контекст для ИИ-краулеров.

    v1.4.0
  • AI-курс «ИИ — твой новый помощник». 6 интерактивных уроков с теорией, чат-примером и практическим заданием. State-машина из трёх экранов (landing → course → completion) + персистенция в localStorage. Регистрация по имени для персонализации завершения. PDF-шпаргалка по результатам — через window.print() с print-styles. CourseSidebar с unlocking-логикой по выполненным урокам.

    v1.3.0
  • Раздел «Проекты». Карточки с локализованными названиями и описаниями (ru/en), тегами, опциональными ссылками на репозиторий и live-сайт. Источник — entities/project с JSON-данными. Отдельная страница каждого проекта (/projects/[slug]) с подробным описанием и обложкой.

    v1.2.0
  • Блог через Telegram-канал. Посты автоматически парсятся с публичной страницы t.me/s/‹channel› через node-html-parser. Извлечение: жирный заголовок из тегов ‹b›...‹/b›, обложка из background-image-стиля .tgme_widget_message_photo_wrap, теги из хештегов, время чтения из количества слов. React.cache() дедуплицирует fetch в рамках одного рендера. Next Data Cache (revalidate=3600) + webhook /api/telegram/webhook с revalidateTag при новом посте.

    v1.1.0
  • Динамический акцентный цвет. Кнопка в шапке циклит CSS-переменную --accent по 10-цветной палитре. Логика выбора исключает повтор подряд через module-level lastColor memo. После реализации палитры ⌘K — вынесена как cycleAccent() в features/accent-toggle/lib, переиспользуется и в тоггле, и в команде палитры.

    v1.0.2
  • UI Kit и дизайн-система. Витрина /uikit с токенами (цвета, типографика, отступы, радиусы) и базовыми компонентами: Button с тремя вариантами (primary/secondary/ghost), Input, Textarea, Badge, Card, ProgressBar. Кастомный SVG-набор иконок с CSS-mask для перекрашивания через currentColor. Mobile-first медиа-запросы, dark/light темы через data-theme атрибут.

    v1.0.1
  • Запуск sandwor.ru. Стек: Next.js 14 + App Router, TypeScript strict, Feature-Sliced Design (app → views → widgets → features → entities → shared). i18n на next-intl с переключением локали через куку (localePrefix: 'never'). Тёмная/светлая тема через next-themes. Шрифт Inter Variable. Деплой на Vercel.

    v1.0.0