Анализ сайта studio80.ru
Основное Готовность: 100%
Домен
studio80.ru
Состояние доменного имени
?
Проверяем корректность доменного имени и наличие технических проблем на уровне домена.
Домен второго уровня идеален для продвижения.
Отличный запоминающийся домен.
Регистратор домена - REGRU-RU
Дата регистрации домена 05.07.2022 (более 3 лет назад)
Ответ сервера
200 Успешный ответ
HTTP-код ответа и цепочка редиректов
?
Код 200 — страница доступна. Коды 3xx — редиректы (цепочки замедляют загрузку и размывают ссылочный вес). Коды 4xx/5xx — ошибки, поисковик не сможет проиндексировать страницу.
Сервер настроен корректно.
Цепочка редиректов:
http://studio80.ru
301 MovedPermanently
https://studio80.ru/
200 OK
Безопасность
Сайт безопасен
Использование HTTPS и SSL-сертификат
?
HTTPS — обязательный стандарт. Google и Яндекс отдают предпочтение защищённым сайтам. Отсутствие SSL или просроченный сертификат ведут к предупреждениям в браузере и снижению позиций.
Не настроен HSTS (Strict-Transport-Security) — рекомендуется включить.
На сайте работает защищенный протокол ssl и сайт открывается по https.
Ssl-сертификат действителен до 12.09.2026 13:56:57.
HTTP автоматически перенаправляется на HTTPS.
Поздравляем! Сайт не содержится в реестре РКН.
Кодировка
utf-8
Кодировка символов страницы
?
Стандарт — UTF-8. Неправильная кодировка вызывает нечитаемые символы и мешает поисковику корректно распознать текст страницы.
Указана кодировка на странице utf-8.
Язык
ru
Атрибут lang в HTML-теге
?
Атрибут lang (<html lang="ru">) сообщает поисковикам и браузерам, на каком языке написана страница. Помогает при ранжировании в региональном поиске.
Указан язык документа ru.
Скорость загрузки
~0,13сек
Время отклика сервера (TTFB)
?
Time To First Byte — время до получения первого байта от сервера. Норма до 200 мс. Медленный отклик ухудшает пользовательский опыт и ранжирование: Яндекс и Google учитывают скорость страниц.
Скорость загрузки сайта 0,13сек оптимальна.
Объем документа
87Кб
Размер HTML-кода страницы
?
Слишком большой HTML замедляет парсинг браузером и сканирование поисковым роботом. Рекомендуется не более 200 Кб.
Объем html-документа 87Кб оптимален.
Структура html-документа корректна.
Ресурсы
Ресурсы: 1
Внешние ресурсы страницы (CSS, JS, изображения)
?
Количество и тип подключённых ресурсов влияют на скорость загрузки. Большое число запросов увеличивает время рендеринга страницы.
Кол-во файлов ресурсов 1 достаточно.
Показать полный список ресурсов
| Тип | Название | Значение |
|---|---|---|
| stylesheet | https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500;600&display=swap |
Серверные заголовки
Кол-во: 4
HTTP-заголовки ответа сервера
?
Заголовки сервера передают браузеру и поисковику служебную информацию: кеширование, безопасность (CSP, HSTS), сжатие (gzip). Правильная настройка ускоряет загрузку и повышает защищённость.
Найдены серверные заголовки 4шт. Подробнее про серверные заголовки.
Показать полный список серверных заголовков
| Ключ | Значение |
|---|---|
| Server | nginx/1.24.0 |
| Date | Thu, 20 Aug 2026 18:37:56 GMT |
| ETag | "6a628202-19882" |
| Accept-Ranges | bytes |
CMS
Не определена
Система управления сайтом (движок)
?
CMS — это движок, на котором работает сайт (WordPress, 1C-Bitrix, Tilda и др.). Знание CMS помогает понять возможности SEO-оптимизации и подобрать подходящие инструменты. «Не определена» — вероятно, самописный сайт или нестандартная сборка.
CMS не определена. Вероятно, сайт самописный либо движок надёжно скрыт. Это не ошибка.
Веб-сервер
Программное обеспечение сервера
?
Веб-сервер — это ПО, которое отдаёт страницы посетителям (nginx, Apache, IIS, LiteSpeed и др.). Определяется по серверным заголовкам ответа (Server, X-Powered-By и т.п.). «Не определён» — сервер намеренно скрывает эти заголовки, это нормальная практика безопасности.
Сайт работает на веб-сервере nginx.
Мета-теги Готовность: 32%
Title
STUDIO80 — веб-разработка для тех, кто принимает гостей
Заголовок страницы в браузере и поисковой выдаче
?
Title — главный SEO-заголовок страницы. Влияет на CTR в поиске и ранжирование. Оптимальная длина: 50–70 символов. Ключевые слова — ближе к началу.
Необходимо уменьшить число символов в title (текущее значение: 55, оптимально: от 40 до 45)
Дублей словоформ в title не найдено.
Description
Веб-разработка для отелей и ресторанов с 2017 года: сайты, бронирование, электронные меню, личные кабинеты, интеграции с PMS, POS и CRM. Поддержка 24/7.
Описание страницы в поисковой выдаче (сниппет)
?
Meta Description — текст под заголовком в выдаче. Напрямую на позиции не влияет, но влияет на CTR. Оптимальная длина: 120–160 символов.
Необходимо уменьшить число символов в description (текущее значение: 152, оптимально: от 120 до 130)
Keywords
Список ключевых слов страницы (устаревший тег)
?
Meta Keywords не учитывается Яндексом и Google для ранжирования с 2009–2012 годов. Заполнение не обязательно, но не вредит. Конкурент может использовать содержимое для анализа.
Установите мета-тег keywords!
Канонический Url
Указывает поисковику основную версию страницы
?
Canonical (rel=canonical) предотвращает проблему дублей страниц. Должен точно совпадать с URL проверяемой страницы. Неправильный canonical может передать ссылочный вес на другую страницу.
Рекомендуем прописать канонический Url.
Robots
Ошибок нет
Директивы для поисковых роботов на уровне страницы
?
Meta Robots управляет индексацией конкретной страницы: index/noindex — индексировать ли, follow/nofollow — следовать ли по ссылкам. Noindex полностью исключает страницу из поиска.
Meta-тег robots не указан. Страница свободна для индексации.
Адаптивность
width=device-width, initial-scale=1
Настройка масштабирования на мобильных устройствах
?
Тег viewport (<meta name="viewport">) сообщает браузеру, как масштабировать страницу на мобильных. Стандарт: width=device-width, initial-scale=1. Отсутствие — признак отсутствия мобильной версии.
Meta-тег viewport со значением-константой width=device-width задаёт ширину страницы в соответствии с размером экрана.
Meta-тег viewport со значением initial-scale=1.0 определяет масштаб 1:1, т.е. «не масштабировать».
Разметка OpenGraph
Не найдено
Мета-теги для красивых превью в соцсетях
?
OpenGraph (og:title, og:description, og:image) управляет тем, как страница выглядит при репосте в социальных сетях и мессенджерах. Отсутствие OG-тегов — невзрачный превью при шеринге.
Разметка OpenGraph не задана. Страница не оптимизирована под социальные сети. Мета-теги с разметкой Og помогают социальным роботам лучше структурировать Ваш сайт.
Все мета-теги
Кол-во: 4
Полный список мета-тегов страницы
?
Таблица всех meta-тегов, включая нестандартные. Позволяет найти опечатки, дубли и лишние теги.
Найдены мета-теги 4шт. Мета-теги не видимы для человека и предназначены для обмена информацией между веб-страницей и поисковыми системами, браузерами и другими веб-службами. С ними роботы 🤖 и устройства ведут себя более ожидаемо.
Показать полный список мета-тегов
| Тип | Название | Значение |
|---|---|---|
| name | viewport | width=device-width, initial-scale=1 |
| name | description | Веб-разработка для отелей и ресторанов с 2017 года: сайты, бронирование, электронные меню, личные кабинеты, интеграции с PMS, POS и CRM. Поддержка 24/7. |
| name | theme-color | #F7F7F5 |
| name | theme-color | #0E1013 |
Оптимизация Готовность: 100%
Структура
Ошибок нет
Семантические HTML-элементы страницы
?
Проверяет наличие основных структурных элементов: nav, header, footer, main. Корректная семантическая структура помогает поисковику понять архитектуру страницы.
Структура документа корректна (теги <html> и <body> присутствуют в одном экземпляре).
Контент
Ошибок нет
Объём и качество текстового содержимого
?
Анализирует объём полезного текста на странице. Слишком мало — страница может считаться малополезной. Слишком много — ухудшается читаемость и восприятие.
Слова из title 6 встречаются в тексте достаточно.
Абзацев с текстом 117 достаточно.
Среднее число слов в абзаце 11 достаточно.
Кол-во знаков контента 6715 на странице оптимально.
Кол-во слов 1020 на странице оптимально.
Заголовки
Ошибок нет
Иерархия заголовков H1–H6
?
H1 должен быть один и содержать ключевой запрос. H2–H6 описывают подразделы. Пропуск уровней (H1 → H3) и несколько H1 — типичные ошибки, снижающие понятность страницы для поисковика.
На странице присутствуют заголовки <h1> 1. Это прекрасно.
На странице присутствуют заголовки <h2> 7. Это хорошо.
На странице присутствуют заголовки <h3> 12.
Тошнота
2,83
Насколько одно слово доминирует в тексте
?
Классическая тошнота = √(частота самого повторяющегося слова). Норма до 7–8: текст воспринимается естественно. Выше — поисковик может счесть страницу переспамленной.
Тошнота страницы в пределах нормы 3.
Академич. тошнота
7,45%
Насколько текст перенасыщен ключевыми словами
?
Академическая тошнота = (частота слова / общее количество слов) × 100%. Показывает долю конкретного слова в тексте. Норма 5–15%.
Академическая тошнота страницы в пределах нормы.
Семантическое ядро
20
Наиболее часто встречающиеся слова на странице
?
Топ слов по частоте использования. Показывает, какие слова доминируют в тексте с точки зрения поисковика.
Контент страницы содержит осмысленный текст и слова.
Показать список слов
| Слово | Кол-во | Частота |
|---|---|---|
| интеграции | 8 | 0,78% |
| поддержка | 7 | 0,69% |
| личный | 6 | 0,59% |
| кабинет | 6 | 0,59% |
| работаем | 5 | 0,49% |
| бронирование | 5 | 0,49% |
| эквайринг | 5 | 0,49% |
| системы | 5 | 0,49% |
| выходе | 5 | 0,49% |
| клиенты | 4 | 0,39% |
| info@studio80 | 4 | 0,39% |
| studio80 | 4 | 0,39% |
| делаем | 4 | 0,39% |
| тарифы | 4 | 0,39% |
| гостеприимства | 4 | 0,39% |
| релизы | 4 | 0,39% |
| услуги | 3 | 0,29% |
| контакты | 3 | 0,29% |
| обсудить | 3 | 0,29% |
| проект | 3 | 0,29% |
Индексация Готовность: 80%
Индексирование
Ошибок нет
Разрешено ли индексирование страницы
?
Проверяет, не закрыта ли страница от индексации через robots.txt, meta robots или X-Robots-Tag. Страница, закрытая от индексации, не появится в поисковой выдаче.
Анкоров на странице 30 оптимально. Поисковые роботы обязательно проиндексируют сайт.
Robots.txt
Найден корректный robots.txt
Файл управления сканированием сайта роботами
?
Robots.txt указывает поисковым роботам, какие страницы сканировать, а какие — нет. Ошибки в файле могут случайно закрыть важные разделы от индексации.
Robots.txt настроен корректно. Размер файла: 89483 байт. Загружен за: 0сек.
Проверяемая страница не запрещена в robots.txt.
Robots.txt доступен по постоянному адресу
Цепочка редиректов для файла robots.txt:
http://studio80.ru/robots.txt
301 MovedPermanently
https://studio80.ru/robots.txt
200 OK
Показать содержимое robots.txt
<!DOCTYPE html>
<html lang="ru" data-theme-pref="auto">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>STUDIO80 — веб-разработка для тех, кто принимает гостей</title>
<meta name="description" content="Веб-разработка для отелей и ресторанов с 2017 года: сайты, бронирование, электронные меню, личные кабинеты, интеграции с PMS, POS и CRM. Поддержка 24/7.">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#F7F7F5">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0E1013">
<!-- Анти-мигание: тема ставится до первой отрисовки, три состояния (auto/light/dark) различаются. -->
<script>
(function () {
var r = document.documentElement;
r.classList.add('js');
var pref = 'auto';
try {
var s = localStorage.getItem('s80-theme');
if (s === 'light' || s === 'dark' || s === 'auto') pref = s;
} catch (e) {}
var eff = pref;
if (pref === 'auto') {
eff = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
}
r.setAttribute('data-theme', eff);
r.setAttribute('data-theme-pref', pref);
})();
</script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500;600&display=swap">
<style>
/* ============================================================
STUDIO80 — СПЕЦИФИКАЦИЯ / Datasheet
Все контрасты просчитаны, а не заявлены. Ключевые инварианты:
1.4.3 — весь текст >= 4.5:1 на КАЖДОЙ поверхности, где он лежит
(включая --surface-2 — фон полей формы).
1.4.11 — --rule-strong (функциональные границы полей) >= 3:1
против всех соседних заливок. Отделён от декоративной --line.
Элевация действительно инвертирована:
светлая = бумага, панель УТОПЛЕНА (--surface темнее --bg);
тёмная = прибор, панель ВСПЛЫВАЕТ к лампе (--surface светлее --bg).
Тёмные линейки взяты ШИРЕ математического зеркала: в области низкой
светимости шаг читается слабее (light 1.33:1 -> dark 1.61:1).
============================================================ */
/* ---------- 1. ТОКЕНЫ: СВЕТЛАЯ ТЕМА (бумага) ---------- */
:root {
color-scheme: light;
--bg: #F7F7F5; /* бумага, фон страницы */
--bg-tint: #F1F1EC; /* чередующиеся секции */
--surface: #EEEEE8; /* панель УТОПЛЕНА: чернила западают в бумагу */
--surface-2: #E6E6DF; /* поля формы — глубже всего */
--ink: #16171A; /* 16.71 / 15.82 / 15.39 / 14.30 */
--ink-2: #494C52; /* 8.03 / 7.60 / 7.39 / 6.87 */
--ink-3: #5C6066; /* 5.90 / 5.58 / 5.43 / 5.04 — AA и на --surface-2 */
--line: #D0D0C7; /* декоративный хайрлайн, 1.33:1 на --surface */
--rule-strong: #787365; /* ФУНКЦИОНАЛЬНАЯ граница: 4.41 / 4.06 / 3.77 — 1.4.11 c запасом */
--tick: #C6C6BF; /* тики линейки — печатный чертёж */
--signal: #D9501B; /* только графика: 3.83:1 на --bg */
--signal-ink: #AF3A15; /* текст/ссылки/фокус: 5.69 / 5.39 / 5.24 / 4.87 */
--signal-wash: rgba(217, 80, 27, .08); /* только подложка невалидного поля */
--sel: rgba(217, 80, 27, .28); /* выделение: #EFC8B8, --ink на нём 11.63:1 */
--btn-bg: #16171A;
--btn-fg: #F7F7F5; /* 16.71:1 */
--btn-tick-on: #D9501B; /* тик считается ОТ ЗАЛИВКИ КНОПКИ, не от фона: 4.36:1 на #16171A */
--focus: #AF3A15;
--dot-glow: none; /* след маркера — плоский, без свечения */
/* Оптическая компенсация: на бумаге дисплей держит полный вес */
--w-display: 600;
--track-display: -0.035em;
--track-h2: -0.025em;
--header-bg: rgba(247, 247, 245, .82);
}
/* ---------- 2. ТОКЕНЫ: ТЁМНАЯ ТЕМА (прибор) ---------- */
/* Блок повторён дважды намеренно: [data-theme] — выбор пользователя,
media-версия — честный запасной путь при полностью отключённом JS. */
[data-theme="dark"] {
color-scheme: dark;
--bg: #0E1013; /* графит с синим уклоном, не #000 */
--bg-tint: #121519;
--surface: #171A20; /* панель ВСПЛЫВАЕТ: элевация светлотой */
--surface-2: #1F242B; /* поля формы всплывают ещё выше */
--ink: #E8EAEC; /* 15.80 / 15.18 / 14.45 / 12.94 */
--ink-2: #A6ADB4; /* 8.40 / 8.07 / 7.68 / 6.88 */
--ink-3: #8A939B; /* 6.10 / 5.87 / 5.58 / 5.00 — держит моно-лейблы на полях */
--line: #363E48; /* 1.61:1 на --surface — ГРОМЧЕ светлой (1.33) */
--rule-strong: #767F8B; /* 4.70 / 4.30 / 3.85 — 1.4.11 */
--tick: #3A424B; /* та же геометрия — но пиксельная сетка дисплея */
--signal: #FF7A33; /* 7.33:1 — служит и графикой, и текстом */
--signal-ink: #FF7A33;
--signal-wash: rgba(255, 122, 51, .10); /* только подложка невалидного поля */
--sel: rgba(255, 122, 51, .32); /* выделение: #5B321D, --ink на нём 9.07:1 */
--btn-bg: #E8EAEC;
--btn-fg: #0E1013;
--btn-tick-on: #AF3A15; /* --signal здесь мёртв: 2.15:1 на светлой заливке. Этот — 5.06:1 */
--focus: #FF7A33;
--dot-glow: drop-shadow(0 0 5px rgba(255, 122, 51, .55)); /* LED — только здесь */
/* Светлый текст на графите оптически полнеет от halation — сбрасываем вес */
--w-display: 500;
--track-display: -0.030em;
--track-h2: -0.020em;
--header-bg: rgba(14, 16, 19, .82);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
color-scheme: dark;
--bg: #0E1013; --bg-tint: #121519; --surface: #171A20; --surface-2: #1F242B;
--ink: #E8EAEC; --ink-2: #A6ADB4; --ink-3: #8A939B;
--line: #363E48; --rule-strong: #767F8B; --tick: #3A424B;
--signal: #FF7A33; --signal-ink: #FF7A33; --signal-wash: rgba(255, 122, 51, .10);
--sel: rgba(255, 122, 51, .32);
--btn-bg: #E8EAEC; --btn-fg: #0E1013; --btn-tick-on: #AF3A15; --focus: #FF7A33;
--dot-glow: drop-shadow(0 0 5px rgba(255, 122, 51, .55));
--w-display: 500; --track-display: -0.030em; --track-h2: -0.020em;
--header-bg: rgba(14, 16, 19, .82);
}
}
/* ---------- 3. КАРКАС ---------- */
:root {
--ease: cubic-bezier(.2, 0, 0, 1);
--t1: 160ms; --t2: 240ms; --t3: 400ms;
--col: 1280px;
--pad: clamp(12px, 3.2vw, 56px);
--inset: clamp(16px, 2.6vw, 32px);
--gutter: clamp(24px, 3vw, 40px);
--sec-y: clamp(72px, 10vw, 144px);
--head-h: 64px;
--ctl-h: 36px; /* единая высота контролов шапки */
--gap-head: 20px; /* заголовок раздела -> лид */
/* Шаг хайрлайн-стеков футера. Задан в px, а не множителем: у навигации 15px,
у колофона 11px — общий шаг возможен только при общем лидинге. Шаг = 22 + 9 + 9 + 1 = 41px. */
--stack-lh: 22px;
--stack-pad: 9px;
/* Ступени шкалы. Середина сведена к трём: мелкий акцент / body / h3.
Промежуточные 17 и 18 убраны — они не читались как иерархия, но ломали ритм.
Литералов кегля в CSS больше нет — только эти токены. Два исключения, оба намеренные:
1. Дисплейный диапазон (.h1/.h2/.lead/.nums__v/.client__nm/.mailto) — текучие clamp():
это не ступени, а кривая, и в шкалу она не сворачивается.
2. .c-lab/.c-tag внутри SVG — единицы viewBox, а не экранные px (см. §8).
rem там масштабируется вместе со схемой и ломает подписи. Не токенизировать. */
--fs-1: .6875rem; /* 11 — micro / моно-лейблы */
--fs-2: .75rem; /* 12 — mono */
--fs-3: .9375rem; /* 15 — мелкий акцент */
--fs-4: 1rem; /* 16 — body */
--fs-5: 1.25rem; /* 20 — h3 */
--font-sans: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
*, *::before, *::after { box-sizing: border-box; }
/* Авторский origin всегда бьёт UA-правило [hidden]{display:none}, поэтому любое
авторское display-правило молча ломает hidden. Закрываем один раз и глобально. */
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--font-sans);
font-size: var(--fs-4);
line-height: 1.6;
font-weight: 400;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-variant-numeric: tabular-nums;
font-feature-settings: "tnum" 1;
overflow-x: hidden;
}
/* Рамка чертежа: две вертикальные хайрлайн-линии идут СКВОЗЬ весь документ.
Достигается тем, что каждая секция несёт свой отрезок — линии непрерывны,
потому что секции стыкуются без зазоров. Никакого fixed-оверлея. */
.wrap {
width: min(var(--col), 100% - var(--pad) * 2);
margin-inline: auto;
border-inline: 1px solid var(--line);
}
.inset { padding-inline: var(--inset); }
h1, h2, h3, h4, p, figure, ul, ol, dl, dd, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, svg { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: var(--signal-ink); text-decoration-thickness: 1px; text-underline-offset: 3px; }
:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 3px;
border-radius: 2px;
}
:focus:not(:focus-visible) { outline: none; }
::selection { background: var(--sel); color: var(--ink); }
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
.skip {
position: fixed; top: 8px; left: 8px; z-index: 100;
transform: translateY(-200%);
background: var(--btn-bg); color: var(--btn-fg);
padding: 10px 16px; border-radius: 2px;
font-family: var(--font-mono); font-size: var(--fs-2); letter-spacing: .08em; text-transform: uppercase;
text-decoration: none;
}
.skip:focus { transform: translateY(0); }
/* ---------- 4. ТИПОГРАФИКА ---------- */
/* Кегль подобран измерением, а не на глаз: вторая строка — самая длинная
(30 знаков). При 4.5rem она укладывается в одну строку на всех ширинах
от 900px и выше, поэтому H1 держит заявленную двухстрочную композицию,
а не разваливается на четыре. Ниже 900px перенос естественный. */
.h1 {
font-size: clamp(2.25rem, 1rem + 4vw, 4.5rem);
line-height: .98;
font-weight: var(--w-display);
letter-spacing: var(--track-display);
margin: 0;
}
.h1 span { display: block; }
.h1 .h1__b { color: var(--ink-2); }
.h2 {
font-size: clamp(1.75rem, 1.2rem + 2vw, 2.75rem);
line-height: 1.05;
font-weight: var(--w-display);
letter-spacing: var(--track-h2);
text-wrap: balance;
}
.h3 { font-size: var(--fs-5); line-height: 1.25; font-weight: 600; letter-spacing: -.015em; }
.lead {
font-size: clamp(1.0625rem, 1rem + .4vw, 1.25rem);
line-height: 1.55;
color: var(--ink-2);
max-width: 34em;
}
.body { max-width: 34em; color: var(--ink-2); }
.body + .body { margin-top: 1.1em; }
.body strong { color: var(--ink); font-weight: 500; }
/* Отбивки заголовочного блока — токеном, а не россыпью инлайновых style по секциям.
Порядок важен: .lead + .body должно идти ПОСЛЕ .body + .body — специфичность равная. */
.h2 + .lead, .h2 + .body { margin-top: var(--gap-head); }
.lead + .body { margin-top: 28px; }
/* Мера строки 62–68 знаков — обязательна, а не желательна: она и есть «воздух»
на плотной странице. */
.mono {
font-family: var(--font-mono);
font-size: var(--fs-2);
line-height: 1.35; /* не 1: кикер героя переносится на мобильном, и строки налезали друг на друга */
font-weight: 500;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--ink-3);
}
.micro {
font-family: var(--font-mono);
font-size: var(--fs-1);
line-height: 1.4;
color: var(--ink-3);
letter-spacing: .02em;
}
/* Вордмарк: система из двух голосов видна прямо в подписи */
.wordmark {
font-family: var(--font-sans);
font-weight: 600;
letter-spacing: -.02em;
font-size: var(--fs-4);
color: var(--ink);
text-decoration: none;
display: inline-flex;
align-items: baseline;
white-space: nowrap;
}
.wordmark i {
font-family: var(--font-mono);
font-weight: 500;
font-style: normal;
letter-spacing: 0;
}
/* Линейка тиков — чистый CSS, один и тот же чертёж на двух носителях */
.ruler {
height: 4px;
background-image: repeating-linear-gradient(to right, var(--tick) 0 1px, transparent 1px 8px);
transform-origin: left center;
}
.js .ruler--draw { animation: s80-draw 600ms var(--ease) both; }
@keyframes s80-draw { from { transform: scaleX(0); } to { transform: scaleX(1); } }
/* ---------- 5. КНОПКИ ---------- */
.btn {
--btn-tick: transparent;
position: relative;
display: inline-flex; align-items: center; gap: .5rem;
padding: 12px 20px;
border: 1px solid transparent;
border-radius: 2px;
background: var(--btn-bg); color: var(--btn-fg);
font-size: var(--fs-3); font-weight: 500; letter-spacing: -.01em;
text-decoration: none; cursor: pointer;
white-space: nowrap; /* иначе главный CTA разрывается на две строки и вываливается из шапки */
transition: background-color var(--t1) var(--ease), color var(--t1) var(--ease), border-color var(--t1) var(--ease);
}
.btn::after {
content: ""; position: absolute; left: 20px; right: 20px; bottom: 8px;
height: 2px; background: var(--btn-tick);
transition: background-color var(--t1) var(--ease);
}
/* Тик лежит НА заливке кнопки, поэтому берёт свой токен, а не --signal (он подобран под фон страницы) */
.btn:hover { --btn-tick: var(--btn-tick-on); }
.btn--ghost {
background: transparent; color: var(--ink);
border-color: var(--rule-strong);
}
.btn--ghost:hover { --btn-tick: var(--signal); }
.btn--text {
background: transparent; color: var(--ink);
border-color: transparent; padding-inline: 0;
}
.btn--text::after { left: 0; right: 0; }
.btn--text:hover { --btn-tick: var(--signal); }
/* ---------- 6. ШАПКА ---------- */
.header {
position: sticky; top: 0; z-index: 50;
background: var(--header-bg);
backdrop-filter: blur(8px) saturate(1.2);
-webkit-backdrop-filter: blur(8px) saturate(1.2);
border-bottom: 1px solid var(--line);
}
.header__in {
height: var(--head-h);
display: flex; align-items: center; gap: var(--inset);
}
/* min-width:0 здесь намеренно НЕТ. С ним бокс ul отдавал ширину (320px при своих 488px
min-content), а сами пункты вылезали наружу и печатались ПОВЕРХ переключателя темы:
замер на 900px — перехлёст 144px, «Поддержка» и «Контакты» нечитаемы и некликабельны.
Ужать ряд из шести моно-капс пунктов ниже min-content нечем — поэтому его не ужимают,
а прячут в меню (см. трёхступенчатую шапку ниже). */
.header__nav { display: none; margin-inline-start: auto; flex: 0 1 auto; }
.header__nav ul { display: flex; gap: clamp(12px, 1.6vw, 24px); }
.header__nav a {
font-family: var(--font-mono); font-size: var(--fs-2); font-weight: 500;
text-transform: uppercase; letter-spacing: .08em;
color: var(--ink-2); text-decoration: none;
padding: 6px 2px; display: block; position: relative;
}
.header__nav a::after {
content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
background: transparent; transition: background-color var(--t1) var(--ease);
}
.header__nav a:hover { color: var(--ink); }
.header__nav a:hover::after { background: var(--signal); }
.header__end { display: flex; align-items: center; gap: 10px; margin-inline-start: auto; flex: none; }
.header__cta { display: none; }
/* Переключатель темы: типографский, без иконок — правило «ноль иконок» держится.
Ниже 900px он живёт в панели меню: три контрола физически не помещаются
в полосу шапки на 320px, а обрезанный контрол — отказ 1.4.10 Reflow. */
.theme {
display: none;
border: 1px solid var(--rule-strong);
border-radius: 2px;
overflow: hidden;
background: var(--surface);
height: var(--ctl-h);
}
.theme__opt {
appearance: none; -webkit-appearance: none;
border: 0; background: transparent; cursor: pointer;
display: inline-flex; align-items: center;
padding: 0 10px;
font-family: var(--font-mono); font-size: var(--fs-1); font-weight: 500;
letter-spacing: .06em; text-transform: uppercase;
color: var(--ink-3);
transition: background-color var(--t1) var(--ease), color var(--t1) var(--ease), box-shadow var(--t1) var(--ease);
}
.theme__opt + .theme__opt { border-left: 1px solid var(--rule-strong); }
.theme__opt:hover { color: var(--ink); }
/* Выбранное состояние читается тиком акцента — тот же язык, что .btn::after и .row::before.
Сплошная заливка --ink остаётся уникальным признаком главного CTA. */
.theme__opt[aria-checked="true"] {
background: var(--surface-2); color: var(--ink);
box-shadow: inset 0 -2px 0 var(--signal);
}
.theme__opt:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.theme__long { display: none; }
/* Мобильное меню — нативные details/summary.
Ноль focus-trap, ноль Esc-хендлера, ноль ручного управления фокусом. */
.mnav { position: relative; }
.mnav__btn {
list-style: none; cursor: pointer;
display: inline-flex; align-items: center; justify-content: center;
height: var(--ctl-h);
min-width: 74px; /* ширина зарезервирована под самую длинную подпись — «Закрыть» не двигает ряд */
padding: 0 10px;
border: 1px solid var(--rule-strong); border-radius: 2px;
font-family: var(--font-mono); font-size: var(--fs-1); font-weight: 500;
letter-spacing: .06em; text-transform: uppercase; color: var(--ink);
background: var(--surface);
}
.mnav__btn::-webkit-details-marker { display: none; }
.mnav__btn .is-open { display: none; }
.mnav[open] .mnav__btn .is-shut { display: none; }
.mnav[open] .mnav__btn .is-open { display: inline; }
/* Панель привязана к ВЬЮПОРТУ, а не к кнопке: правый край .mnav отстоит от края
экрана ровно на --pad + бордер .wrap + --inset. Верх — на нижнюю линейку шапки. */
.mnav__panel {
position: absolute;
top: calc(100% + (var(--head-h) - var(--ctl-h)) / 2);
right: calc((var(--pad) + var(--inset) + 1px) * -1);
width: 100vw; max-width: 100vw;
background: var(--bg);
border-block: 1px solid var(--line);
padding: 8px 0 4px;
}
/* Пункты меню встают на ту же вертикаль, что и весь контент страницы */
.mnav__panel a, .mnav__foot { padding-inline: calc(var(--pad) + var(--inset) + 1px); }
.mnav__panel a {
display: flex; align-items: baseline; gap: 16px;
padding-block: 16px;
border-bottom: 1px solid var(--line);
color: var(--ink); text-decoration: none;
font-size: var(--fs-5); font-weight: 500; letter-spacing: -.015em;
}
.mnav__panel li:last-child a { border-bottom: 0; }
.mnav__panel .idx {
font-family: var(--font-mono); font-size: var(--fs-1); font-weight: 500;
color: var(--ink-3); letter-spacing: .06em;
}
.mnav__foot {
padding-block: 14px 16px;
display: flex; flex-wrap: wrap; align-items: center; gap: 14px 20px;
justify-content: space-between;
border-top: 1px solid var(--line);
}
/* Только ниже 900px: с 900px переключатель поднимается в полосу шапки, а два видимых
контрола одной радиогруппы одновременно — это две «выбранные» опции на экране. */
@media (max-width: 899px) { .mnav__foot .theme { display: inline-flex; } }
/* Шапка раскрывается в ТРИ ступени — рубежи взяты замером, а не круглым числом.
Полосе нужно: вордмарк 76 + навигация 488–571 + тема 187 + CTA 153 + гэпы.
<900px — вордмарк + «Меню». Тема и CTA не помещаются, живут в панели.
900px — появляется место под тему и главный CTA (запас ~269px),
но НЕ под навигацию: она остаётся в панели меню.
1200px — полоса вмещает всё: замер need 1033px при avail 1059px.
Раньше навигация включалась с 900px и до ~1170px не помещалась: пункты
печатались поверх переключателя темы, а «Как работаем» ломался на две строки
внутри 64-пиксельной полосы. */
@media (min-width: 900px) {
.header__cta { display: inline-flex; height: var(--ctl-h); padding-block: 0; padding-inline: 16px; }
.header__end > .theme { display: inline-flex; }
.theme__short { display: none; }
.theme__long { display: inline; }
}
@media (min-width: 1200px) {
.mnav { display: none; }
.header__nav { display: block; }
}
html:not(.js) .theme { display: none !important; }
/* ---------- 7. СЕКЦИИ ---------- */
.sec { border-top: 1px solid var(--line); }
.sec--tint { background: var(--bg-tint); }
.sec__in { padding-block: var(--sec-y); }
[id] { scroll-margin-top: calc(var(--head-h) + 16px); }
/* Асимметричный сплит: левый рельс = номер + метка + одна строка спецификации */
.grid { display: grid; gap: var(--gutter); }
@media (min-width: 900px) { .grid { grid-template-columns: 3fr 9fr; } }
@media (min-width: 1200px) { .grid { grid-template-columns: 4fr 8fr; } }
.rail { display: flex; flex-direction: column; gap: 6px; }
@media (min-width: 900px) { .rail { position: sticky; top: calc(var(--head-h) + 40px); align-self: start; } }
.rail__no {
font-family: var(--font-mono); font-size: var(--fs-2); font-weight: 500;
letter-spacing: .08em; color: var(--signal-ink);
}
.rail__label {
font-family: var(--font-mono); font-size: var(--fs-2); font-weight: 500;
text-transform: uppercase; letter-spacing: .08em; color: var(--ink);
}
.rail__spec { font-family: var(--font-mono); font-size: var(--fs-1); color: var(--ink-3); }
.figcap { margin-top: 20px; color: var(--ink-3); max-width: 46em; }
/* ---------- 8. ГЕРОЙ ---------- */
.hero__in { padding-block: clamp(40px, 6vw, 72px) clamp(48px, 6vw, 80px); }
.hero__kicker { margin: 24px 0 clamp(28px, 4vw, 44px); color: var(--ink-3); }
.hero__lead { margin-top: clamp(24px, 3vw, 32px); }
.hero__acts { display: flex; flex-wrap: wrap; gap: 12px 20px; margin-top: clamp(28px, 3.5vw, 40px); }
/* Схема «контур гостя» — единственная предметная графика на странице */
.contour { margin-top: clamp(48px, 7vw, 88px); }
.contour__svg { width: 100%; height: auto; }
.contour__d { display: none; }
/* Вертикальная схема упирается в потолок: без него она масштабируется вместе с колонкой
и на планшете превращается в двухметровое полотно с подписями крупнее .h3.
При 340px scale = 1.13, подписи ~14.7px. Левый край — по той же вертикали, что текст. */
.contour__m { display: block; max-width: 340px; }
/* Горизонтальный контур включается только там, где колонка близка к его viewBox (1200):
ниже 1200px scale падал до 0.66 и подписи рендерились по 8.6px. */
@media (min-width: 1200px) { .contour__d { display: block; } .contour__m { display: none; } }
.c-rail { fill: none; stroke: var(--rule-strong); stroke-width: 1.25; }
.c-return { fill: none; stroke: var(--rule-strong); stroke-width: 1.25; stroke-dasharray: 1 5; stroke-linecap: round; }
.c-tie { fill: none; stroke: var(--rule-strong); stroke-width: 1; stroke-dasharray: 1 4; stroke-linecap: round; }
.c-node-out { fill: var(--bg); stroke: var(--rule-strong); stroke-width: 1.25; }
.c-node-ours { fill: var(--signal); stroke: none; }
.c-ext { fill: none; stroke: var(--rule-strong); stroke-width: 1.25; }
.c-lab {
font-family: var(--font-sans); font-size: 13px; font-weight: 500;
fill: var(--ink); letter-spacing: -.01em;
}
.c-lab--ours { fill: var(--ink); }
.c-tag {
font-family: var(--font-mono); font-size: 10px; font-weight: 500;
fill: var(--ink-3); letter-spacing: .06em; text-transform: uppercase;
}
/* Компенсация масштаба: десктопный viewBox (1200) шире своей колонки (1059–1214),
поэтому кегль в единицах viewBox взят с запасом — на экране выходит 12.3–14.2 / 9.7–11.1px,
вровень с мобильной раскладкой (14.7 / 11.3). Скачка на брейкпоинте больше нет. */
.contour__d .c-lab { font-size: 14px; }
.contour__d .c-tag { font-size: 11px; }
.c-arrow { fill: var(--rule-strong); }
.c-dot {
fill: var(--signal);
filter: var(--dot-glow);
transform-box: view-box;
transform-origin: 0 0;
transform: translate(369px, 100px); /* припаркована на «Бронировании», если offset-path не поддержан */
}
.c-dot--m { transform: translate(44px, 200px); }
@supports (offset-path: path("M 0 0 L 1 1")) {
.c-dot {
transform: none;
offset-rotate: 0deg;
offset-path: path("M 60 100 L 1140 100 A 50 50 0 0 1 1140 200 L 60 200 A 50 50 0 0 1 60 100");
animation: s80-travel 14s linear infinite;
}
.c-dot--m {
offset-path: path("M 44 40 L 44 680 Q 12 680 12 648 L 12 72 Q 12 40 44 40");
animation-duration: 11s;
}
}
@keyframes s80-travel { from { offset-distance: 0%; } to { offset-distance: 100%; } }
/* Спец-строка героя: ТОЛЬКО проверяемые факты — и только те, которых на этом экране
больше нигде нет. «2017» уже несёт кикер, «24/7» — трио чисел §00; держать их ещё и
здесь значило печатать один факт в трёх кеглях на протяжении одного экрана.
Осталось два столбца — ряд один на всех ширинах, брейкпоинт на 4 колонки не нужен. */
.spec {
margin-top: clamp(40px, 5vw, 64px);
border-top: 1px solid var(--rule-strong);
display: grid; grid-template-columns: 1fr;
}
@media (min-width: 640px) { .spec { grid-template-columns: 1fr 1fr; } }
/* dt идёт первым в разметке (валидный <dl>), а показывается вторым:
значение сверху, метка снизу — порядок задаёт column-reverse, а не порядок тегов. */
.spec__i {
padding: 18px 0 16px; border-bottom: 1px solid var(--line);
display: flex; flex-direction: column-reverse; align-items: flex-start; gap: 4px;
}
/* С 640px ряд единственный: нижняя линейка снимается, ячейки делит вертикаль —
та же механика, что у .nums. Первой ячейке снимается И бордер, И отступ,
иначе левый край текста гуляет на 20px относительно колонки. */
@media (min-width: 640px) {
.spec__i { padding-inline: 20px; border-bottom: 0; border-left: 1px solid var(--line); }
.spec__i:first-child { padding-left: 0; border-left: 0; }
}
.spec__v { font-size: var(--fs-4); font-weight: 500; letter-spacing: -.015em; color: var(--ink); }
/* ---------- 9. ПОЛОСА КЛИЕНТОВ (ранний соцпруф) ---------- */
.strip { border-top: 1px solid var(--line); background: var(--bg-tint); }
.strip__in { padding-block: 20px; display: grid; gap: 14px; align-items: center; }
@media (min-width: 900px) { .strip__in { grid-template-columns: auto 1fr; gap: 28px; } }
/* Разделители-бордеры несовместимы с переносом: :first-child не спасает первый элемент
ВТОРОЙ строки — там повисала линейка в пустоте, а текст съезжал на 14px. Гэпы детерминированы. */
.strip__list { display: flex; flex-wrap: wrap; gap: 10px 24px; }
.strip__list li {
font-size: var(--fs-3); font-weight: 500; letter-spacing: -.01em; color: var(--ink-2);
line-height: 1.3;
}
/* ---------- 10. ХАЙРЛАЙН-ТАБЛИЦЫ ---------- */
.rows { border-top: 1px solid var(--rule-strong); }
.row {
position: relative;
display: grid; gap: 4px var(--gutter);
padding: 22px 0;
border-bottom: 1px solid var(--line);
grid-template-columns: auto 1fr;
grid-template-areas: "idx head" ". desc" ". tag";
}
@media (min-width: 720px) {
.row {
grid-template-columns: 40px minmax(0, 1fr) 96px;
grid-template-areas: "idx head tag" "idx desc tag";
padding: 26px 0;
align-items: start;
}
}
/* Тик загорается в паре пикселей от .row__idx — берут один и тот же оранжевый,
иначе в светлой теме рядом читаются два разных (L* 52 против 40). Тик декоративен,
1.4.11 к нему не применяется, запас по контрасту не теряется. */
.row::before {
content: ""; position: absolute; left: -1px; top: -1px; width: 0; height: 1px;
background: var(--signal-ink);
transition: width var(--t1) var(--ease);
}
.row:hover::before, .row:focus-within::before { width: 24px; }
.row__idx {
grid-area: idx;
font-family: var(--font-mono); font-size: var(--fs-2); font-weight: 500;
letter-spacing: .06em; color: var(--ink-3); padding-top: 4px;
transition: color var(--t1) var(--ease);
}
.row:hover .row__idx, .row:focus-within .row__idx { color: var(--signal-ink); }
.row__head { grid-area: head; }
.row__desc { grid-area: desc; color: var(--ink-2); max-width: 34em; margin-top: 4px; }
.row__tag {
grid-area: tag;
font-family: var(--font-mono); font-size: var(--fs-1); font-weight: 500;
letter-spacing: .06em; color: var(--ink-3); padding-top: 5px;
}
@media (min-width: 720px) { .row__tag { text-align: right; } }
.rows__note { margin-top: 20px; color: var(--ink-3); }
/* Таблица интеграций — 3 колонки */
.tbl { border-top: 1px solid var(--rule-strong); }
.tbl__head { display: none; }
@media (min-width: 800px) {
.tbl__head {
display: grid; grid-template-columns: 4fr 5fr 5fr; gap: var(--gutter);
padding: 12px 0; border-bottom: 1px solid var(--line);
}
}
.tbl__row {
display: grid; gap: 4px;
padding: 20px 0; border-bottom: 1px solid var(--line);
position: relative;
}
@media (min-width: 800px) {
.tbl__row { grid-template-columns: 4fr 5fr 5fr; gap: var(--gutter); padding: 18px 0; align-items: baseline; }
}
.tbl__row::before {
content: ""; position: absolute; left: -1px; top: -1px; width: 0; height: 1px;
background: var(--signal-ink); transition: width var(--t1) var(--ease);
}
.tbl__row:hover::before { width: 24px; }
.tbl__sys { font-weight: 500; letter-spacing: -.01em; color: var(--ink); }
.tbl__gives, .tbl__use { color: var(--ink-2); font-size: var(--fs-3); }
.tbl__use { color: var(--ink-2); }
@media (max-width: 799px) {
.tbl__gives::before { content: "даёт — "; color: var(--ink-3); font-family: var(--font-mono); font-size: var(--fs-1); text-transform: uppercase; letter-spacing: .06em; }
.tbl__use::before { content: "применяем — "; color: var(--ink-3); font-family: var(--font-mono); font-size: var(--fs-1); text-transform: uppercase; letter-spacing: .06em; }
}
/* ---------- 11. §00 ФОКУС ---------- */
.segs { border-top: 1px solid var(--line); margin-top: 4px; }
.segs li {
display: flex; gap: 14px; align-items: baseline;
padding: 14px 0; border-bottom: 1px solid var(--line);
}
.segs .idx { font-family: var(--font-mono); font-size: var(--fs-1); font-weight: 500; color: var(--ink-3); letter-spacing: .06em; }
.segs .nm { font-size: var(--fs-3); font-weight: 500; letter-spacing: -.01em; }
.focus__cols { display: grid; gap: var(--gutter); }
@media (min-width: 1000px) { .focus__cols { grid-template-columns: 1fr 300px; } }
/* Трио проверяемых чисел — ритмическая пауза и честный аргумент */
.nums {
margin-top: clamp(48px, 6vw, 72px);
border-top: 1px solid var(--rule-strong);
display: grid; grid-template-columns: 1fr;
}
@media (min-width: 720px) { .nums { grid-template-columns: repeat(3, 1fr); } }
.nums__i {
padding: 24px 0; border-bottom: 1px solid var(--line);
display: flex; flex-direction: column-reverse; align-items: flex-start; gap: 12px;
}
@media (min-width: 720px) {
.nums__i { padding: 28px 20px 24px; border-bottom: 0; border-left: 1px solid var(--line); }
.nums__i:first-child { padding-left: 0; border-left: 0; }
}
/* Числа встают ВРОВЕНЬ с .h2, а не над ним: статистика не может весить больше
навигационной иерархии документа. */
.nums__v {
font-size: clamp(2rem, 1.4rem + 1.6vw, 2.5rem);
line-height: 1; font-weight: var(--w-display); letter-spacing: var(--track-h2);
color: var(--ink);
}
.nums__k { color: var(--ink-2); font-size: var(--fs-3); max-width: 26em; }
/* ---------- 12. §03 ПРОЦЕСС ---------- */
.steps { border-top: 1px solid var(--rule-strong); }
.step {
display: grid; gap: 6px var(--gutter);
padding: 26px 0; border-bottom: 1px solid var(--line);
grid-template-columns: auto 1fr;
grid-template-areas: "idx head" ". desc" ". out";
}
@media (min-width: 720px) {
/* Описание — ведущая колонка, артефакт — сопровождающая.
Пропорции именно в этом порядке: иначе текст сжимается в ленту. */
.step { grid-template-columns: 40px minmax(0, 1.7fr) minmax(0, 1fr); grid-template-areas: "idx head out" "idx desc out"; align-items: start; }
}
/* Акцент закреплён за .rail__no (маркер раздела) — порядковые номера в списках везде серые.
Иначе в §03 рядом горели пять оранжевых, а в §01 те же по смыслу номера были серыми. */
.step__idx { grid-area: idx; font-family: var(--font-mono); font-size: var(--fs-2); font-weight: 500; color: var(--ink-3); letter-spacing: .06em; padding-top: 4px; }
.step__head { grid-area: head; }
.step__desc { grid-area: desc; color: var(--ink-2); max-width: 34em; margin-top: 4px; }
.step__out { grid-area: out; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line); }
@media (min-width: 720px) { .step__out { margin-top: 0; padding-top: 4px; border-top: 0; border-left: 1px solid var(--line); padding-left: var(--gutter); } }
.step__out b { display: block; font-family: var(--font-mono); font-size: var(--fs-1); font-weight: 500; text-transform: uppercase; letter-spacing: .08em; color: var(--ink-3); margin-bottom: 6px; }
.step__out span { color: var(--ink-2); font-size: var(--fs-3); }
/* ---------- 13. §04 КЛИЕНТЫ ---------- */
.clients { border-top: 1px solid var(--rule-strong); display: grid; grid-template-columns: 1fr; }
@media (min-width: 640px) { .clients { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .clients { grid-template-columns: repeat(3, 1fr); } }
.client {
position: relative;
padding: 28px 20px 26px 0;
border-bottom: 1px solid var(--line);
display: flex; flex-direction: column; gap: 10px;
}
@media (min-width: 640px) {
.client { padding-inline: 20px; border-left: 1px solid var(--line); }
.client:nth-child(2n+1) { border-left: 0; padding-left: 0; }
}
@media (min-width: 1000px) {
.client:nth-child(2n+1) { border-left: 1px solid var(--line); padding-left: 20px; }
.client:nth-child(3n+1) { border-left: 0; padding-left: 0; }
}
.client__nm { font-size: clamp(1.25rem, 1.1rem + .5vw, 1.5rem); font-weight: 500; letter-spacing: -.02em; color: var(--ink); }
.client__idx { font-family: var(--font-mono); font-size: var(--fs-1); font-weight: 500; color: var(--ink-3); letter-spacing: .06em; }
/* Заготовка под подтверждённый род деятельности: включится без переверстки */
.client__note:empty { display: none; }
/* Один тик на весь макет: слева, 1px, 24px. Толщина 2px оставлена только за .btn::after,
где тик лежит внутри заливки и 1px там бы потерялся. */
.client::after {
content: ""; position: absolute; top: 0; left: 0; width: 0; height: 1px;
background: var(--signal-ink); transition: width var(--t1) var(--ease);
}
.client:hover::after, .client:focus-within::after { width: 24px; }
/* ---------- 14. §05 ПОДДЕРЖКА ---------- */
.sup__list { border-top: 1px solid var(--line); margin-top: clamp(28px, 3vw, 36px); }
.sup__list li { padding: 18px 0; border-bottom: 1px solid var(--line); display: flex; gap: 16px; align-items: baseline; }
.sup__list .idx { font-family: var(--font-mono); font-size: var(--fs-1); font-weight: 500; color: var(--ink-3); letter-spacing: .06em; flex: none; }
.sup__list p { max-width: 34em; }
.sup__list b { font-weight: 500; color: var(--ink); }
.sup__list span { color: var(--ink-2); }
.clock {
margin-top: clamp(28px, 3vw, 36px);
display: inline-flex; align-items: center; gap: 10px;
padding: 10px 14px;
border: 1px solid var(--rule-strong); border-radius: 2px;
background: var(--surface);
}
.clock__led {
width: 7px; height: 7px; border-radius: 50%;
background: var(--signal); filter: var(--dot-glow); flex: none;
}
.js .clock__led { animation: s80-pulse 2.4s var(--ease) infinite; }
@keyframes s80-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
.clock__t { font-family: var(--font-mono); font-size: var(--fs-2); font-weight: 500; letter-spacing: .06em; color: var(--ink); text-transform: uppercase; }
/* ---------- 15. §06 ФОРМА ---------- */
.contact__cols { display: grid; gap: clamp(40px, 5vw, 64px) var(--gutter); }
@media (min-width: 1000px) { .contact__cols { grid-template-columns: 1fr 1.15fr; } }
.mailto {
display: inline-block; margin-top: clamp(24px, 3vw, 32px);
font-family: var(--font-mono); font-weight: 500;
font-size: clamp(1.0625rem, .9rem + 1vw, 1.5rem);
letter-spacing: -.02em; color: var(--signal-ink);
text-decoration: none; border-bottom: 1px solid var(--rule-strong);
padding-bottom: 4px;
transition: border-color var(--t1) var(--ease);
}
/* Черта под ржавым словом — того же оранжевого, что и само слово, а не ярче его */
.mailto:hover { border-color: var(--signal-ink); }
.form { display: grid; gap: 18px; }
.f2 { display: grid; gap: 18px; }
@media (min-width: 560px) { .f2 { grid-template-columns: 1fr 1fr; } }
.field { display: flex; flex-direction: column; gap: 7px; }
.field > label {
font-family: var(--font-mono); font-size: var(--fs-2); font-weight: 500;
text-transform: uppercase; letter-spacing: .08em; color: var(--ink-3);
}
.field .req { color: var(--signal-ink); }
.inp {
width: 100%;
padding: 11px 12px;
background: var(--surface-2);
border: 1px solid var(--rule-strong); /* 1.4.11: >=3:1 против заливки поля И против фона секции */
border-radius: 2px;
color: var(--ink);
font-size: var(--fs-3); line-height: 1.5;
transition: border-color var(--t1) var(--ease), background-color var(--t1) var(--ease);
}
.inp::placeholder { color: var(--ink-3); opacity: 1; }
.inp:hover { border-color: var(--ink-3); }
.inp:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; border-color: var(--focus); }
textarea.inp { resize: vertical; min-height: 108px; }
select.inp {
appearance: none; -webkit-appearance: none;
padding-right: 34px;
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
background-position: calc(100% - 18px) calc(50% + 1px), calc(100% - 13px) calc(50% + 1px);
background-size: 5px 5px, 5px 5px;
background-repeat: no-repeat;
}
/* Wash кладётся СЛОЕМ поверх заливки, а не вместо неё: шорткат background сбрасывал
--surface-2 и поле-ошибка нарушало инвариант элевации (в светлой всплывало, в тёмной тонуло).
background-image, а не background — иначе у select.inp снесло бы стрелку. */
.inp[aria-invalid="true"] {
border-color: var(--signal-ink);
background-color: var(--surface-2);
background-image: linear-gradient(var(--signal-wash), var(--signal-wash));
}
select.inp[aria-invalid="true"] {
background-image:
linear-gradient(var(--signal-wash), var(--signal-wash)),
linear-gradient(45deg, transparent 50%, currentColor 50%),
linear-gradient(135deg, currentColor 50%, transparent 50%);
background-position: 0 0, calc(100% - 18px) calc(50% + 1px), calc(100% - 13px) calc(50% + 1px);
background-size: auto, 5px 5px, 5px 5px;
background-repeat: no-repeat;
}
.err { font-family: var(--font-mono); font-size: var(--fs-1); color: var(--signal-ink); letter-spacing: .02em; min-height: 0; }
.err:empty { display: none; }
.check { display: grid; grid-template-columns: auto 1fr; gap: 12px; align-items: start; }
.check input {
appearance: none; -webkit-appearance: none;
width: 18px; height: 18px; margin: 2px 0 0;
border: 1px solid var(--rule-strong); border-radius: 2px;
background: var(--surface-2); cursor: pointer; flex: none;
position: relative;
transition: background-color var(--t1) var(--ease), border-color var(--t1) var(--ease);
}
.check input:checked { background: var(--ink); border-color: var(--ink); }
.check input:checked::after {
content: ""; position: absolute; left: 5px; top: 1px;
width: 5px; height: 10px;
border: solid var(--bg); border-width: 0 1.5px 1.5px 0;
transform: rotate(43deg);
}
.check input[aria-invalid="true"] { border-color: var(--signal-ink); }
/* Был единственный 14px на всю страницу — шестая ступень в санс-диапазоне, которая
не читалась как иерархия. Сведён к --fs-3: тот же мелкий акцент, что и везде. */
.check label { font-size: var(--fs-3); color: var(--ink-2); line-height: 1.45; }
.form__foot { display: flex; flex-wrap: wrap; gap: 16px 24px; align-items: center; justify-content: space-between; margin-top: 4px; }
.done {
border: 1px solid var(--rule-strong); border-radius: 2px;
background: var(--surface); padding: clamp(24px, 4vw, 36px);
}
.done__t { font-family: var(--font-mono); font-size: var(--fs-2); font-weight: 500; text-transform: uppercase; letter-spacing: .08em; color: var(--signal-ink); }
.done__m { margin-top: 14px; font-size: var(--fs-5); line-height: 1.25; font-weight: 500; letter-spacing: -.015em; }
.done__s { margin-top: 10px; color: var(--ink-2); font-size: var(--fs-3); max-width: 34em; }
.done__ruler { margin-bottom: 20px; }
/* ---------- 16. ФУТЕР — КОЛОФОН ---------- */
.footer { border-top: 1px solid var(--line); background: var(--bg-tint); }
.footer__ruler { margin-bottom: clamp(40px, 5vw, 56px); }
.footer__in { padding-block: clamp(48px, 6vw, 72px) 0; }
.footer__cols { display: grid; gap: clamp(32px, 4vw, 48px) var(--gutter); }
@media (min-width: 720px) { .footer__cols { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1000px) { .footer__cols { grid-template-columns: 1.2fr 1fr 1.4fr; } }
.footer__tag { margin-top: 12px; color: var(--ink-2); font-size: var(--fs-3); max-width: 26em; }
/* Общий шаг для обоих стеков футера: линейки навигации и колофона обязаны совпадать —
на макете, вся риторика которого в совмещённых линейках, это самое заметное место. */
.footer__nav li { padding-block: var(--stack-pad); border-bottom: 1px solid var(--line); }
.footer__nav li:first-child { border-top: 1px solid var(--line); }
.footer__nav a { color: var(--ink-2); text-decoration: none; font-size: var(--fs-3); line-height: var(--stack-lh); display: block; }
.footer__nav a:hover { color: var(--ink); }
/* Колофон: реквизиты в даташите — родной жанр, а не мелкий серый балласт */
.colophon dl { display: grid; grid-template-columns: 84px 1fr; }
.colophon dt, .colophon dd {
padding-block: var(--stack-pad); border-bottom: 1px solid var(--line);
font-family: var(--font-mono); font-size: var(--fs-1); line-height: var(--stack-lh); letter-spacing: .02em;
}
.colophon dt { color: var(--ink-3); text-transform: uppercase; letter-spacing: .06em; }
.colophon dd { color: var(--ink-2); margin: 0; }
.colophon dd a { color: var(--ink-2); text-decoration: none; border-bottom: 1px solid var(--line); }
.colophon dd a:hover { color: var(--signal-ink); border-color: var(--signal); }
.colophon dt:first-of-type, .colophon dd:first-of-type { border-top: 1px solid var(--line); }
.footer__end {
margin-top: clamp(40px, 5vw, 56px);
padding-block: 20px 24px;
border-top: 1px solid var(--line);
display: flex; flex-wrap: wrap; gap: 12px 24px; justify-content: space-between; align-items: center;
}
.totop { display: inline-flex; align-items: center; gap: 8px; color: var(--ink-3); text-decoration: none; }
.totop:hover { color: var(--signal-ink); }
/* ---------- 17. ДВИЖЕНИЕ ---------- */
.js .reveal { opacity: 0; transform: translateY(8px); }
.js .reveal.in { opacity: 1; transform: none; transition: opacity var(--t3) var(--ease), transform var(--t3) var(--ease); }
.js .reveal.in { transition-delay: calc(var(--i, 0) * 40ms); }
/* Смена темы: класс живёт на <html> только на время переключения,
поэтому при загрузке нет вспышки перехода. */
.theme-anim, .theme-anim *, .theme-anim *::before, .theme-anim *::after {
transition: background-color var(--t2) var(--ease),
border-color var(--t2) var(--ease),
color var(--t2) var(--ease),
fill var(--t2) var(--ease),
stroke var(--t2) var(--ease),
outline-color var(--t2) var(--ease) !important;
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
*, *::before, *::after {
animation-duration: .01ms !important;
animation-iteration-count: 1 !important;
transition-duration: .01ms !important;
}
/* Сигнальная точка паркуется на узле «Бронирование» */
.c-dot { animation: none !important; offset-distance: 12.49%; }
.c-dot--m { offset-distance: 12.16%; }
.js .reveal, .js .reveal.in { opacity: 1; transform: none; }
.js .ruler--draw { animation: none; transform: none; }
.clock__led { animation: none !important; opacity: 1; }
/* Смена темы остаётся плавной СОЗНАТЕЛЬНО: reduced-motion — про вестибулярный
дискомфорт от движения. Скачок всей палитры физически неприятнее перехода,
который он якобы предотвращает. */
.theme-anim, .theme-anim *, .theme-anim *::before, .theme-anim *::after {
transition-property: background-color, border-color, color, fill, stroke !important;
transition-duration: 320ms !important;
transition-timing-function: var(--ease) !important;
}
}
@media print {
.header, .mnav, .theme, .skip, .totop { display: none !important; }
body { background: #fff; color: #000; }
/* IntersectionObserver при печати не срабатывает: без сброса Ctrl+P сразу после
загрузки печатает герой и 52 пустые страницы. */
.js .reveal, .js .reveal.in { opacity: 1 !important; transform: none !important; }
}
</style>
</head>
<body>
<a class="skip" href="#main">К содержанию</a>
<!-- ============ ШАПКА ============ -->
<header class="header">
<div class="wrap">
<div class="inset header__in">
<a class="wordmark" href="#top" aria-label="STUDIO80, на главную">STUDIO<i>80</i></a>
<nav class="header__nav" aria-label="Основная навигация">
<ul>
<li><a href="#services">Услуги</a></li>
<li><a href="#integrations">Интеграции</a></li>
<li><a href="#process">Как работаем</a></li>
<li><a href="#clients">Клиенты</a></li>
<li><a href="#support">Поддержка</a></li>
<li><a href="#contact">Контакты</a></li>
</ul>
</nav>
<div class="header__end">
<!-- aria-label намеренно нет: доступное имя считается из содержимого и совпадает
с видимой подписью (SC 2.5.3). Контекст даёт aria-label группы. -->
<div class="theme" role="radiogroup" aria-label="Тема оформления">
<button type="button" class="theme__opt" role="radio" data-theme-set="auto"
aria-checked="true" tabindex="0">
<span class="theme__short">Авто</span><span class="theme__long">Авто</span>
</button>
<button type="button" class="theme__opt" role="radio" data-theme-set="light"
aria-checked="false" tabindex="-1">
<span class="theme__short">День</span><span class="theme__long">Светлая</span>
</button>
<button type="button" class="theme__opt" role="radio" data-theme-set="dark"
aria-checked="false" tabindex="-1">
<span class="theme__short">Ночь</span><span class="theme__long">Тёмная</span>
</button>
</div>
<a class="btn header__cta" href="#contact">Обсудить проект</a>
<details class="mnav" id="mnav">
<summary class="mnav__btn">
<span class="is-shut">Меню</span><span class="is-open">Закрыть</span>
</summary>
<div class="mnav__panel">
<nav aria-label="Основная навигация">
<ul>
<li><a href="#services"><span class="idx">01</span> Услуги</a></li>
<li><a href="#integrations"><span class="idx">02</span> Интеграции</a></li>
<li><a href="#process"><span class="idx">03</span> Как работаем</a></li>
<li><a href="#clients"><span class="idx">04</span> Клиенты</a></li>
<li><a href="#support"><span class="idx">05</span> Поддержка</a></li>
<li><a href="#contact"><span class="idx">06</span> Контакты</a></li>
</ul>
</nav>
<div class="mnav__foot">
<a class="micro" href="mailto:info@studio80.ru">info@studio80.ru</a>
<!-- Переключатель темы живёт здесь: в полосе шапки на 320px он не помещается -->
<div class="theme" role="radiogroup" aria-label="Тема оформления">
<button type="button" class="theme__opt" role="radio" data-theme-set="auto"
aria-checked="true" tabindex="0">
<span class="theme__short">Авто</span><span class="theme__long">Авто</span>
</button>
<button type="button" class="theme__opt" role="radio" data-theme-set="light"
aria-checked="false" tabindex="-1">
<span class="theme__short">День</span><span class="theme__long">Светлая</span>
</button>
<button type="button" class="theme__opt" role="radio" data-theme-set="dark"
aria-checked="false" tabindex="-1">
<span class="theme__short">Ночь</span><span class="theme__long">Тёмная</span>
</button>
</div>
</div>
</div>
</details>
</div>
</div>
</div>
</header>
<main id="main">
<!-- ============ ГЕРОЙ ============ -->
<section id="top" class="hero">
<div class="wrap">
<div class="inset hero__in">
<div class="ruler ruler--draw" aria-hidden="true"></div>
<p class="mono hero__kicker">STUDIO80 · веб-разработка для тех, кто принимает гостей · с 2017</p>
<!-- H1 не анимируется вовсе — он LCP -->
<h1 class="h1">
<span>Гостеприимство — это система.</span>
<span class="h1__b">Мы собираем её цифровую часть.</span>
</h1>
<p class="lead hero__lead">
Сайты, бронирование, электронные меню, личные кабинеты, B2B-порталы
и интеграции с PMS, POS и CRM — для отелей, ресторанов,
курортов и санаториев.
</p>
<div class="hero__acts">
<a class="btn" href="#contact">Обсудить проект</a>
<a class="btn btn--text" href="#services">Что мы делаем ↓</a>
</div>
<!-- Единственная предметная графика: карта зоны ответственности -->
<figure class="contour">
<!-- Схема продублирована для двух раскладок и переключается ТОЛЬКО через CSS-медиа,
без JS. Для скринридера обе версии скрыты, описание даётся текстом ниже. -->
<!-- Десктоп: горизонтальный контур -->
<svg class="contour__svg contour__d" viewBox="0 0 1200 250" aria-hidden="true" focusable="false">
<!-- внешние системы -->
<g>
<text class="c-tag" x="523" y="22" text-anchor="middle">эквайринг</text>
<rect class="c-ext" x="517" y="32" width="12" height="12"></rect>
<path class="c-tie" d="M 523 48 L 523 90"></path>
<text class="c-tag" x="677" y="22" text-anchor="middle">PMS</text>
<rect class="c-ext" x="671" y="32" width="12" height="12"></rect>
<path class="c-tie" d="M 677 48 L 677 90"></path>
<text class="c-tag" x="986" y="22" text-anchor="middle">POS</text>
<rect class="c-ext" x="980" y="32" width="12" height="12"></rect>
<path class="c-tie" d="M 986 48 L 986 90"></path>
</g>
<!-- рельс и замыкание контура -->
<path class="c-rail" d="M 60 100 L 1140 100"></path>
<path class="c-return" d="M 1140 100 A 50 50 0 0 1 1140 200 L 60 200 A 50 50 0 0 1 60 100"></path>
<path class="c-arrow" d="M 604 200 L 616 194 L 616 206 Z"></path>
<!-- узлы: закрашенные — наша зона, контурные — чужие системы -->
<circle class="c-node-out" cx="60" cy="100" r="6"></circle>
<circle class="c-node-ours" cx="214" cy="100" r="6"></circle>
<circle class="c-node-ours" cx="369" cy="100" r="6"></circle>
<circle class="c-node-out" cx="523" cy="100" r="6"></circle>
<circle class="c-node-out" cx="677" cy="100" r="6"></circle>
<circle class="c-node-ours" cx="831" cy="100" r="6"></circle>
<circle class="c-node-out" cx="986" cy="100" r="6"></circle>
<circle class="c-node-out" cx="1140" cy="100" r="6"></circle>
<circle class="c-node-ours" cx="700" cy="200" r="6"></circle>
<text class="c-lab" x="60" y="126" text-anchor="middle">Поиск</text>
<text class="c-lab" x="214" y="126" text-anchor="middle">Сайт</text>
<text class="c-lab" x="369" y="126" text-anchor="middle">Бронирование</text>
<text class="c-lab" x="523" y="126" text-anchor="middle">Оплата</text>
<text class="c-lab" x="677" y="126" text-anchor="middle">Заезд</text>
<text class="c-lab" x="831" y="126" text-anchor="middle">Меню / QR</text>
<text class="c-lab" x="986" y="126" text-anchor="middle">Счёт</text>
<text class="c-lab" x="1140" y="126" text-anchor="middle">Отзыв</text>
<text class="c-lab" x="700" y="224" text-anchor="middle">Личный кабинет</text>
<text class="c-tag" x="430" y="190" text-anchor="middle">повторный визит</text>
<circle class="c-dot" cx="0" cy="0" r="4.5"></circle>
</svg>
<!-- Мобильный: тот же контур, вертикальный рельс -->
<svg class="contour__svg contour__m" viewBox="0 0 300 740" aria-hidden="true" focusable="false">
<path class="c-rail" d="M 44 40 L 44 680"></path>
<path class="c-return" d="M 44 680 Q 12 680 12 648 L 12 72 Q 12 40 44 40"></path>
<path class="c-arrow" d="M 12 348 L 6 360 L 18 360 Z"></path>
<circle class="c-node-out" cx="44" cy="40" r="6"></circle>
<circle class="c-node-ours" cx="44" cy="120" r="6"></circle>
<circle class="c-node-ours" cx="44" cy="200" r="6"></circle>
<circle class="c-node-out" cx="44" cy="280" r="6"></circle>
<circle class="c-node-out" cx="44" cy="360" r="6"></circle>
<circle class="c-node-ours" cx="44" cy="440" r="6"></circle>
<circle class="c-node-out" cx="44" cy="520" r="6"></circle>
<circle class="c-node-out" cx="44" cy="600" r="6"></circle>
<circle class="c-node-ours" cx="44" cy="680" r="6"></circle>
<text class="c-lab" x="64" y="45">Поиск</text>
<text class="c-lab" x="64" y="125">Сайт</text>
<text class="c-lab" x="64" y="196">Бронирование</text>
<text class="c-lab" x="64" y="276">Оплата</text>
<text class="c-tag" x="64" y="293">эквайринг</text>
<text class="c-lab" x="64" y="356">Заезд</text>
<text class="c-tag" x="64" y="373">PMS</text>
<text class="c-lab" x="64" y="445">Меню / QR</text>
<text class="c-lab" x="64" y="516">Счёт</text>
<text class="c-tag" x="64" y="533">POS</text>
<text class="c-lab" x="64" y="605">Отзыв</text>
<text class="c-lab" x="64" y="685">Личный кабинет</text>
<circle class="c-dot c-dot--m" cx="0" cy="0" r="4.5"></circle>
</svg>
<p class="sr-only">
Схема «контур гостя». Путь гостя замкнут: Поиск, Сайт, Бронирование, Оплата,
Заезд, Меню и QR, Счёт, Отзыв — и возврат через Личный кабинет обратно к Поиску.
Зона ответственности STUDIO80: Сайт, Бронирование, Меню и QR, Личный кабинет.
Внешние системы объекта, с которыми мы стыкуемся: эквайринг на Оплате,
PMS на Заезде, POS на Счёте.
</p>
<figcaption class="micro figcap">
рис. 01 — контур гостя. Закрашенные узлы — зона ответственности STUDIO80.
Контурные — этапы вне нашей зоны. Квадраты — системы, которые уже стоят у вас:
эквайринг, PMS, POS; мы к ним подключаемся.
Контур замкнут: гость возвращается через личный кабинет.
</figcaption>
</figure>
<!-- Спец-строка: только проверяемые факты. Придуманных счётчиков проектов
и процентов доступности здесь нет и быть не может.
Ячейки «на рынке 2017» и «поддержка 24/7» сняты сознательно: 2017 стоит
в кикере двумя экранами выше, 24/7 — в трио чисел §00. Повтор одного факта
в трёх кеглях на одном экране читался как заполнение сетки, а не как
спецификация. Остались два факта, которых нет больше нигде в герое. -->
<dl class="spec">
<div class="spec__i">
<dt class="mono spec__k">интеграции</dt>
<dd class="spec__v">PMS · POS · RMS · CRM</dd>
</div>
<div class="spec__i">
<dt class="mono spec__k">сегменты</dt>
<dd class="spec__v">Отели · Рестораны · Курорты · Санатории</dd>
</div>
</dl>
</div>
</div>
</section>
<!-- ============ РАННИЙ СОЦПРУФ ============ -->
<aside class="strip" aria-label="Клиенты">
<div class="wrap">
<div class="inset strip__in">
<p class="mono">Нам доверяют с 2017</p>
<ul class="strip__list">
<li>Kaspersky</li>
<li>Отель Романтик</li>
<li>SportGorod55</li>
<li>SanpolRest</li>
<li>Отель Грегори</li>
<li>BigWood</li>
</ul>
</div>
</div>
</aside>
<!-- ============ §00 ФОКУС ============ -->
<section class="sec" id="focus">
<div class="wrap">
<div class="inset sec__in">
<div class="grid">
<div class="rail reveal">
<p class="rail__no">§00</p>
<p class="rail__label">Только гостеприимство</p>
<p class="rail__spec">квалификация</p>
</div>
<div>
<div class="focus__cols">
<div class="reveal">
<h2 class="h2">Мы не делаем сайты для всех</h2>
<p class="body lead">
Гость выбирает вас по сайту, бронирует ночью с телефона и заказывает
завтрак по QR-коду. Всё это — тоже гостеприимство, просто оно случается
до и после того, как вы встретите его на стойке.
</p>
<p class="body">
Профильно работаем с теми, у кого есть номера, столы, стоп-листы, тарифы, сезон
и загрузка. Отель, ресторан, курорт, санаторий, сеть — это работающая
операционная система, в которой уже стоят PMS, POS и учётные системы.
Наша работа — не поставить рядом ещё один сервис, а достроить недостающую
часть контура и связать её с тем, что уже работает.
</p>
<p class="body">
Нам не нужно объяснять, чем тариф отличается от цены, почему стоп-лист
нельзя вести вручную в высокий сезон и что происходит с загрузкой, когда
модуль брони отваливается в пятницу вечером.
<strong>Вы говорите как есть — мы понимаем с первого раза.</strong>
</p>
</div>
<div class="reveal" style="--i:1">
<p class="mono" style="margin-bottom:12px">Сегменты</p>
<ul class="segs">
<li><span class="idx">01</span><span class="nm">Отели и гостиничные сети</span></li>
<li><span class="idx">02</span><span class="nm">Рестораны, кафе, бары</span></li>
<li><span class="idx">03</span><span class="nm">Курорты и глэмпинги</span></li>
<li><span class="idx">04</span><span class="nm">Санатории и SPA</span></li>
</ul>
</div>
</div>
<!-- Трио проверяемых чисел. «1» превращает узость в преимущество. -->
<dl class="nums reveal">
<div class="nums__i">
<dt class="nums__k">Год, с которого делаем веб для гостеприимства.</dt>
<dd class="nums__v">2017</dd>
</div>
<div class="nums__i">
<dt class="nums__k">Дежурство, а не приём заявок в рабочее время.</dt>
<dd class="nums__v">24/7</dd>
</div>
<div class="nums__i">
<dt class="nums__k">Одна отрасль в фокусе: веб для гостеприимства.</dt>
<dd class="nums__v">1</dd>
</div>
</dl>
</div>
</div>
</div>
</div>
</section>
<!-- ============ §01 ЧТО ДЕЛАЕМ ============ -->
<section class="sec sec--tint" id="services">
<div class="wrap">
<div class="inset sec__in">
<div class="grid">
<div class="rail reveal">
<p class="rail__no">§01</p>
<p class="rail__label">Что делаем</p>
<p class="rail__spec">8 направлений</p>
</div>
<div>
<h2 class="h2 reveal" style="margin-bottom:clamp(32px,4vw,48px)">
Любая веб-разработка в сфере гостеприимства
</h2>
<div class="rows">
<article class="row reveal" style="--i:0">
<p class="row__idx">01</p>
<h3 class="h3 row__head">Сайты отелей и ресторанов</h3>
<p class="row__desc">Сайт, который продаёт номер и стол: скорость, карточки номеров, меню, спецпредложения, мультиязычность, SEO под гео-запросы.</p>
<p class="row__tag">WEB</p>
</article>
<article class="row reveal" style="--i:1">
<p class="row__idx">02</p>
<h3 class="h3 row__head">Системы бронирования</h3>
<p class="row__desc">Модуль брони на сайте: тарифы, доступность, депозиты и онлайн-оплата, промокоды, отмены. Прямые продажи вместо комиссии агрегаторов.</p>
<p class="row__tag">PMS</p>
</article>
<article class="row reveal" style="--i:2">
<p class="row__idx">03</p>
<h3 class="h3 row__head">Электронные меню</h3>
<p class="row__desc">QR-меню зала и доставки: стоп-листы, модификаторы, аллергены, фото, языки. Обновляется из POS, а не вручную.</p>
<p class="row__tag">POS</p>
</article>
<article class="row reveal" style="--i:3">
<p class="row__idx">04</p>
<h3 class="h3 row__head">Личные кабинеты гостей</h3>
<p class="row__desc">Профиль, история броней и заказов, лояльность, бонусы, сертификаты, повторный заказ в два тапа.</p>
<p class="row__tag">CRM</p>
</article>
<article class="row reveal" style="--i:4">
<p class="row__idx">05</p>
<h3 class="h3 row__head">B2B-порталы</h3>
<p class="row__desc">Корпоративные и агентские кабинеты: договорные тарифы, заявки, счета и закрывающие документы, доступ по ролям.</p>
<p class="row__tag">B2B</p>
</article>
<article class="row reveal" style="--i:5">
<p class="row__idx">06</p>
<h3 class="h3 row__head">Интеграции</h3>
<p class="row__desc">PMS, POS, RMS, платежи, CRM, аналитика. Связываем то, что уже стоит в отеле, а не заставляем менять.</p>
<p class="row__tag">API</p>
</article>
<article class="row reveal" style="--i:6">
<p class="row__idx">07</p>
<h3 class="h3 row__head">Аналитика</h3>
<p class="row__desc">Воронка от визита до заезда: источники, конверсия по тарифам, средний чек, загрузка. Дашборд, который смотрят на планёрке, а не открывают раз в квартал.</p>
<p class="row__tag">BI</p>
</article>
<article class="row reveal" style="--i:7">
<p class="row__idx">08</p>
<h3 class="h3 row__head">Поддержка и развитие</h3>
<p class="row__desc">Мониторинг, релизы, доработки под сезон. Высокий сезон — не время для сюрпризов.</p>
<p class="row__tag">24/7</p>
</article>
</div>
<p class="micro rows__note reveal">
Список не закрытый. Если задача про веб и про гостей — это к нам.
</p>
</div>
</div>
</div>
</div>
</section>
<!-- ============ §02 СТЕК И ИНТЕГРАЦИИ ============ -->
<section class="sec" id="integrations">
<div class="wrap">
<div class="inset sec__in">
<div class="grid">
<div class="rail reveal">
<p class="rail__no">§02</p>
<p class="rail__label">Стек и интеграции</p>
<p class="rail__spec">6 классов систем</p>
</div>
<div>
<h2 class="h2 reveal">Не заставляем менять то, что уже работает</h2>
<p class="lead reveal" style="margin-bottom:clamp(32px,4vw,48px)">
Подключаемся к существующему контуру объекта.
</p>
<!-- Роли проставлены явно: display:grid стирает нативную семантику таблицы,
а шапка колонок — единственный носитель структуры, скрывать её от AT нельзя. -->
<div class="tbl" role="table" aria-label="Классы систем и где мы их применяем">
<div class="tbl__head" role="row">
<p class="mono" role="columnheader">Класс системы</p>
<p class="mono" role="columnheader">Что даёт</p>
<p class="mono" role="columnheader">Где применяем</p>
</div>
<div class="tbl__row reveal" role="row" style="--i:0">
<p class="tbl__sys" role="cell">PMS</p>
<p class="tbl__gives" role="cell">номерной фонд, тарифы, доступность, брони</p>
<p class="tbl__use" role="cell">сайт и модуль бронирования</p>
</div>
<div class="tbl__row reveal" role="row" style="--i:1">
<p class="tbl__sys" role="cell">POS</p>
<p class="tbl__gives" role="cell">меню, стоп-листы, модификаторы, счета</p>
<p class="tbl__use" role="cell">электронное меню и доставка</p>
</div>
<div class="tbl__row reveal" role="row" style="--i:2">
<p class="tbl__sys" role="cell">RMS</p>
<p class="tbl__gives" role="cell">динамические цены и правила</p>
<p class="tbl__use" role="cell">тарифная витрина на сайте</p>
</div>
<div class="tbl__row reveal" role="row" style="--i:3">
<p class="tbl__sys" role="cell">Платежи и эквайринг</p>
<p class="tbl__gives" role="cell">депозиты, предоплата, возвраты</p>
<p class="tbl__use" role="cell">бронь и заказ</p>
</div>
<div class="tbl__row reveal" role="row" style="--i:4">
<p class="tbl__sys" role="cell">CRM и лояльность</p>
<p class="tbl__gives" role="cell">профиль гостя, бонусы, сегменты</p>
<p class="tbl__use" role="cell">личный кабинет</p>
</div>
<div class="tbl__row reveal" role="row" style="--i:5">
<p class="tbl__sys" role="cell">Аналитика</p>
<p class="tbl__gives" role="cell">события от визита до заезда</p>
<p class="tbl__use" role="cell">дашборд загрузки и конверсии</p>
</div>
</div>
<p class="micro rows__note reveal">
табл. 01 — классы систем, с которыми мы работаем. Конкретный вендор
определяется на этапе разбора.
</p>
</div>
</div>
</div>
</div>
</section>
<!-- ============ §03 КАК УСТРОЕНА РАБОТА ============ -->
<section class="sec sec--tint" id="process">
<div class="wrap">
<div class="inset sec__in">
<div class="grid">
<div class="rail reveal">
<p class="rail__no">§03</p>
<p class="rail__label">Как устроена работа</p>
<p class="rail__spec">4 этапа</p>
</div>
<div>
<h2 class="h2 reveal">Каждый этап заканчивается артефактом</h2>
<p class="lead reveal" style="margin-bottom:clamp(32px,4vw,48px)">
Не обещанием, а тем, что можно открыть, прочитать и проверить.
</p>
<div class="steps">
<article class="step reveal" style="--i:0">
<p class="step__idx">01</p>
<h3 class="h3 step__head">Разбор</h3>
<p class="step__desc">Смотрим, что уже стоит: PMS, POS, сайт, кто и как обновляет контент. Считаем, где теряются прямые продажи.</p>
<p class="step__out"><b>На выходе</b><span>Карта гостевого пути и список узких мест.</span></p>
</article>
<article class="step reveal" style="--i:1">
<p class="step__idx">02</p>
<h3 class="h3 step__head">Прототип</h3>
<p class="step__desc">Собираем кликабельный прототип ключевых сценариев: найти → выбрать → забронировать → оплатить.</p>
<p class="step__out"><b>На выходе</b><span>Прототип и техническое задание.</span></p>
</article>
<article class="step reveal" style="--i:2">
<p class="step__idx">03</p>
<h3 class="h3 step__head">Сборка</h3>
<p class="step__desc">Разработка, интеграции, проверка под нагрузкой до начала сезона, контент и мультиязычность.</p>
<p class="step__out"><b>На выходе</b><span>Рабочая система на тестовом контуре.</span></p>
</article>
<article class="step reveal" style="--i:3">
<p class="step__idx">04</p>
<h3 class="h3 step__head">Запуск и развитие</h3>
<p class="step__desc">Запускаем без простоя: старый сайт принимает брони, пока новый не готов их принять. Переносим, наблюдаем, дежурим. Дальше — релизы под сезон и доработки.</p>
<p class="step__out"><b>На выходе</b><span>Мониторинг, регулярные релизы, поддержка 24/7.</span></p>
</article>
</div>
<p class="micro rows__note reveal">табл. 02 — этапы и артефакты на выходе.</p>
</div>
</div>
</div>
</div>
</section>
<!-- ============ §04 НАМ ДОВЕРЯЮТ ============ -->
<section class="sec" id="clients">
<div class="wrap">
<div class="inset sec__in">
<div class="grid">
<div class="rail reveal">
<p class="rail__no">§04</p>
<p class="rail__label">Нам доверяют</p>
<p class="rail__spec">6 клиентов</p>
</div>
<div>
<h2 class="h2 reveal" style="margin-bottom:clamp(32px,4vw,48px)">
Имена, а не обещания
</h2>
<!-- Только названия. Логотипы не рисуем, кейсы и метрики не выдумываем.
Ячейки равные — «главных» клиентов нет. -->
<div class="clients">
<div class="client reveal" style="--i:0">
<p class="client__idx">01</p>
<p class="client__nm">Kaspersky</p>
<p class="micro client__note"></p>
</div>
<div class="client reveal" style="--i:1">
<p class="client__idx">02</p>
<p class="client__nm">Отель Романтик</p>
<p class="micro client__note"></p>
</div>
<div class="client reveal" style="--i:2">
<p class="client__idx">03</p>
<p class="client__nm">SportGorod55</p>
<p class="micro client__note"></p>
</div>
<div class="client reveal" style="--i:3">
<p class="client__idx">04</p>
<p class="client__nm">SanpolRest</p>
<p class="micro client__note"></p>
</div>
<div class="client reveal" style="--i:4">
<p class="client__idx">05</p>
<p class="client__nm">Отель Грегори</p>
<p class="micro client__note"></p>
</div>
<div class="client reveal" style="--i:5">
<p class="client__idx">06</p>
<p class="client__nm">BigWood</p>
<p class="micro client__note"></p>
</div>
</div>
<p class="micro rows__note reveal">клиенты STUDIO80, 2017–2026.</p>
</div>
</div>
</div>
</div>
</section>
<!-- ============ §05 ПОДДЕРЖКА 24/7 ============ -->
<section class="sec sec--tint" id="support">
<div class="wrap">
<div class="inset sec__in">
<div class="grid">
<div class="rail reveal">
<p class="rail__no">§05</p>
<p class="rail__label">Поддержка 24/7</p>
<p class="rail__spec">круглосуточно</p>
</div>
<div>
<h2 class="h2 reveal">Отель не спит. Ресторан — тоже.</h2>
<p class="lead reveal">
Сезон не спрашивает, рабочий ли сегодня день. Самое дорогое время простоя —
то, о котором вы узнаёте утром от гостя. Форма брони, упавшая в 02:40,
стоит вам ночи прямых броней.
</p>
<ul class="sup__list reveal">
<li>
<span class="idx">01</span>
<p><b>Дежурство 24/7.</b> <span>На связи люди, а не автоответчик.</span></p>
</li>
<li>
<span class="idx">02</span>
<p><b>Мониторинг.</b> <span>Доступность, ошибки оплаты и работа модуля брони — под наблюдением.</span></p>
</li>
<li>
<span class="idx">03</span>
<p><b>Релизы по ходу сезона.</b> <span>Доработки без остановки продаж.</span></p>
</li>
<li>
<span class="idx">04</span>
<p><b>Один ответственный человек,</b> <span>а не общий ящик поддержки.</span></p>
</li>
</ul>
<!-- Честные часы: время ЗАВЕДЕНИЯ и дежурной смены (МСК),
а не утверждение о моменте посетителя. Статический дефолт осмыслен. -->
<p class="clock reveal">
<span class="clock__led" aria-hidden="true"></span>
<span class="clock__t" id="clock">Поддержка на связи · круглосуточно</span>
</p>
<p class="micro rows__note reveal">
24/7 · <a href="mailto:info@studio80.ru">info@studio80.ru</a>
</p>
</div>
</div>
</div>
</div>
</section>
<!-- ============ §06 ОБСУДИТЬ ПРОЕКТ ============ -->
<section class="sec" id="contact">
<div class="wrap">
<div class="inset sec__in">
<div class="grid">
<div class="rail reveal">
<p class="rail__no">§06</p>
<p class="rail__label">Обсудить проект</p>
<p class="rail__spec">форма заявки</p>
</div>
<div class="contact__cols">
<div class="reveal">
<h2 class="h2">Напишите, что нужно</h2>
<p class="lead">
Ответим по делу: что реально сделать, в какой последовательности
и что для этого нужно с вашей стороны.
</p>
<a class="mailto" href="mailto:info@studio80.ru">info@studio80.ru</a>
</div>
<div class="reveal" style="--i:1">
<form class="form" id="form" novalidate>
<div class="f2">
<div class="field">
<label for="f-name">Имя <span class="req" aria-hidden="true">*</span></label>
<input class="inp" id="f-name" name="name" type="text" required
autocomplete="name" aria-describedby="e-name">
<p class="err" id="e-name"></p>
</div>
<div class="field">
<label for="f-org">Компания / объект <span class="req" aria-hidden="true">*</span></label>
<input class="inp" id="f-org" name="org" type="text" required
autocomplete="organization" aria-describedby="e-org">
<p class="err" id="e-org"></p>
</div>
</div>
<div class="f2">
<div class="field">
<label for="f-email">Email <span class="req" aria-hidden="true">*</span></label>
<input class="inp" id="f-email" name="email" type="email" required
autocomplete="email" inputmode="email" aria-describedby="e-email">
<p class="err" id="e-email"></p>
</div>
<div class="field">
<label for="f-tel">Телефон</label>
<input class="inp" id="f-tel" name="tel" type="tel"
autocomplete="tel" inputmode="tel" aria-describedby="e-tel">
<p class="err" id="e-tel"></p>
</div>
</div>
<div class="field">
<label for="f-task">Задача</label>
<select class="inp" id="f-task" name="task">
<option value="">Выберите направление</option>
<option>Сайт отеля или ресторана</option>
<option>Система бронирования</option>
<option>Электронное меню</option>
<option>Личный кабинет гостя</option>
<option>B2B-портал</option>
<option>Интеграции (PMS, POS, RMS, CRM)</option>
<option>Аналитика</option>
<option>Поддержка и развитие</option>
<option>Другое</option>
</select>
</div>
<div class="field">
<label for="f-msg">Сообщение</label>
<textarea class="inp" id="f-msg" name="msg" rows="4"
placeholder="Что за объект, что уже стоит, что хочется получить."
aria-describedby="e-msg"></textarea>
<p class="err" id="e-msg"></p>
</div>
<div class="field">
<div class="check">
<input id="f-pd" name="pd" type="checkbox" required aria-describedby="e-pd">
<label for="f-pd">Даю согласие на обработку <a href="#">персональных данных</a> <span class="req" aria-hidden="true">*</span></label>
</div>
<p class="err" id="e-pd"></p>
</div>
<div class="form__foot">
<button class="btn" type="submit">Отправить →</button>
<p class="micro">поля со звёздочкой обязательны</p>
</div>
</form>
<div id="done" hidden></div>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
<!-- ============ ФУТЕР — БЛОК СПЕЦИФИКАЦИИ ============ -->
<footer class="footer">
<div class="wrap">
<div class="inset footer__in">
<div class="ruler footer__ruler" aria-hidden="true"></div>
<div class="footer__cols">
<div>
<a class="wordmark" href="#top" style="font-size:var(--fs-5)">STUDIO<i>80</i></a>
<p class="footer__tag">Веб-разработка для гостеприимства с 2017 года.</p>
</div>
<!-- Лейбл не украшение: он даёт стеку тот же старт, что у «Реквизитов»,
и линейки двух колонок наконец совпадают. -->
<nav aria-label="Навигация в подвале">
<p class="mono" style="margin-bottom:12px">Навигация</p>
<ul class="footer__nav">
<li><a href="#services">Услуги</a></li>
<li><a href="#integrations">Интеграции</a></li>
<li><a href="#process">Как работаем</a></li>
<li><a href="#clients">Клиенты</a></li>
<li><a href="#support">Поддержка</a></li>
<li><a href="#contact">Контакты</a></li>
</ul>
</nav>
<!-- Колофон: реквизиты оформлены как блок даташита и работают
на достоверность, а не прячутся мелким серым. -->
<div class="colophon">
<p class="mono" style="margin-bottom:12px">Реквизиты</p>
<dl>
<dt>ИП</dt>
<dd>Озернюк Инна Станиславовна</dd>
<dt>ИНН</dt>
<dd>773433789987</dd>
<dt>ОГРНИП</dt>
<dd>326508100021015</dd>
<dt>Почта</dt>
<dd><a href="mailto:info@studio80.ru">info@studio80.ru</a></dd>
</dl>
</div>
</div>
<div class="footer__end">
<p class="micro">© 2017–2026 STUDIO80</p>
<a class="micro totop" href="#top">↑ наверх</a>
</div>
</div>
</div>
</footer>
<script>
(function () {
"use strict";
var root = document.documentElement;
/* ---------- Тема: три состояния, живая подписка только в «Авто» ---------- */
var KEY = 's80-theme';
/* Контрол продублирован: в полосе шапки (>=900px) и в панели меню (<900px).
Видим всегда ровно один — второй лежит под display:none и в дерево доступности не попадает. */
var groups = Array.prototype.slice.call(document.querySelectorAll('.theme'));
var opts = Array.prototype.slice.call(document.querySelectorAll('.theme__opt'));
var mq = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
var animTimer = null;
function readPref() {
var p = root.getAttribute('data-theme-pref');
return (p === 'light' || p === 'dark' || p === 'auto') ? p : 'auto';
}
function effective(pref) {
if (pref !== 'auto') return pref;
return (mq && mq.matches) ? 'dark' : 'light';
}
function paintMeta(eff) {
var color = eff === 'dark' ? '#0E1013' : '#F7F7F5';
var metas = document.querySelectorAll('meta[name="theme-color"]');
for (var i = 0; i < metas.length; i++) metas[i].setAttribute('content', color);
}
function syncControl(pref) {
opts.forEach(function (b) {
var on = b.getAttribute('data-theme-set') === pref;
b.setAttribute('aria-checked', on ? 'true' : 'false');
b.tabIndex = on ? 0 : -1;
});
}
function apply(pref, animate) {
var eff = effective(pref);
if (animate) {
root.classList.add('theme-anim');
window.clearTimeout(animTimer);
animTimer = window.setTimeout(function () { root.classList.remove('theme-anim'); }, 420);
}
root.setAttribute('data-theme', eff);
root.setAttribute('data-theme-pref', pref);
paintMeta(eff);
syncControl(pref);
}
function setPref(pref, animate) {
try { localStorage.setItem(KEY, pref); } catch (e) {}
apply(pref, animate);
}
/* Стрелки ходят внутри СВОЕЙ группы: сквозной индекс по всем шести кнопкам
уводил бы фокус в невидимый дубль, где focus() молча ничего не делает. */
groups.forEach(function (group) {
var items = Array.prototype.slice.call(group.querySelectorAll('.theme__opt'));
items.forEach(function (b, i) {
b.addEventListener('click', function () {
setPref(b.getAttribute('data-theme-set'), true);
b.focus();
});
b.addEventListener('keydown', function (ev) {
var k = ev.key, next = null;
if (k === 'ArrowRight' || k === 'ArrowDown') next = (i + 1) % items.length;
else if (k === 'ArrowLeft' || k === 'ArrowUp') next = (i - 1 + items.length) % items.length;
else if (k === 'Home') next = 0;
else if (k === 'End') next = items.length - 1;
else return;
ev.preventDefault();
var t = items[next];
setPref(t.getAttribute('data-theme-set'), true);
t.focus();
});
});
});
// Подписка живёт всегда, но перекрашивает только когда выбран «Авто»
if (mq) {
var onSystem = function () { if (readPref() === 'auto') apply('auto', true); };
if (mq.addEventListener) mq.addEventListener('change', onSystem);
else if (mq.addListener) mq.addListener(onSystem);
}
apply(readPref(), false);
/* ---------- Мобильное меню: закрыть после перехода ---------- */
var mnav = document.getElementById('mnav');
if (mnav) {
mnav.addEventListener('click', function (e) {
if (e.target.closest('a')) mnav.removeAttribute('open');
});
}
/* ---------- Появление секций ---------- */
var reveals = document.querySelectorAll('.reveal');
if ('IntersectionObserver' in window) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (en) {
if (en.isIntersecting) { en.target.classList.add('in'); io.unobserve(en.target); }
});
}, { rootMargin: '0px 0px -8% 0px', threshold: 0.05 });
Array.prototype.forEach.call(reveals, function (el) { io.observe(el); });
} else {
Array.prototype.forEach.call(reveals, function (el) { el.classList.add('in'); });
}
/* ---------- Часы дежурной смены (МСК) ---------- */
var clock = document.getElementById('clock');
if (clock) {
var fmt = null;
try {
fmt = new Intl.DateTimeFormat('ru-RU', {
timeZone: 'Europe/Moscow', hour: '2-digit', minute: '2-digit', hour12: false
});
} catch (e) { fmt = null; }
if (fmt) {
var tick = function () {
clock.textContent = 'Поддержка на связи · ' + fmt.format(new Date()) + ' МСК';
};
tick();
window.setInterval(tick, 20000);
}
}
/* ---------- Форма: клиентская валидация, состояние успеха ---------- */
var form = document.getElementById('form');
var done = document.getElementById('done');
if (!form || !done) return;
var RULES = {
'f-name': { msg: 'Укажите, как к вам обращаться.' },
'f-org': { msg: 'Укажите компанию или название объекта.' },
'f-email': { msg: 'Нужен email — на него мы ответим.', bad: 'Похоже, в адресе опечатка.' },
'f-pd': { msg: 'Без согласия мы не сможем ответить.' }
};
function errNode(id) { return document.getElementById('e-' + id.replace('f-', '')); }
function setErr(el, text) {
var box = errNode(el.id);
if (text) {
el.setAttribute('aria-invalid', 'true');
if (box) box.textContent = text;
} else {
el.removeAttribute('aria-invalid');
if (box) box.textContent = '';
}
}
function validate(el) {
var rule = RULES[el.id];
if (!rule) return true;
if (el.type === 'checkbox') {
if (!el.checked) { setErr(el, rule.msg); return false; }
setErr(el, ''); return true;
}
var v = el.value.trim();
if (!v) { setErr(el, rule.msg); return false; }
if (el.type === 'email' && !/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v)) {
setErr(el, rule.bad); return false;
}
setErr(el, ''); return true;
}
/* Поле краснеет только если его ТРОГАЛИ или был сабмит. Иначе простой проход табом
по форме — нормальный сценарий — сразу заливает её ошибками. */
var submitted = false;
function touched(el) {
if (submitted || el.dataset.touched) return true;
return el.type === 'checkbox' ? el.checked : !!el.value.trim();
}
Object.keys(RULES).forEach(function (id) {
var el = document.getElementById(id);
if (!el) return;
el.addEventListener('input', function () {
el.dataset.touched = '1';
if (el.getAttribute('aria-invalid')) validate(el);
});
el.addEventListener('blur', function () { if (touched(el)) validate(el); });
el.addEventListener('change', function () {
if (el.type !== 'checkbox') return;
el.dataset.touched = '1';
validate(el);
});
});
form.addEventListener('submit', function (e) {
e.preventDefault();
submitted = true;
var first = null, ok = true;
Object.keys(RULES).forEach(function (id) {
var el = document.getElementById(id);
if (!el) return;
if (!validate(el)) { ok = false; if (!first) first = el; }
});
if (!ok) { if (first) first.focus(); return; }
var val = function (id) {
var el = document.getElementById(id);
return el ? String(el.value).trim() : '';
};
var esc = function (s) { return String(s).replace(/[<>&"]/g, ''); };
var email = val('f-email');
/* ВРЕМЕННО: серверного обработчика ещё нет, поэтому заявка уходит письмом
через почтовый клиент гостя — так ни один лид не теряется молча.
Когда появится серверная отправка, весь блок ниже заменяется на fetch()
к своему эндпоинту, разметка успеха остаётся прежней. */
var body = [
'Имя: ' + val('f-name'),
'Компания: ' + val('f-org'),
'E-mail: ' + email,
'Телефон: ' + (val('f-tel') || 'не указан'),
'Задача: ' + val('f-task'),
'',
'Описание:',
val('f-msg') || '—'
].join('\n');
var href = 'mailto:info@studio80.ru' +
'?subject=' + encodeURIComponent('Заявка с сайта — ' + (val('f-org') || val('f-name'))) +
'&body=' + encodeURIComponent(body);
done.innerHTML =
'<div class="done" role="status" aria-live="polite">' +
'<div class="ruler done__ruler" aria-hidden="true"></div>' +
'<p class="done__t">почти всё</p>' +
'<p class="done__m">Осталось отправить письмо</p>' +
'<p class="done__s">Мы открыли ваш почтовый клиент с готовым письмом на ' +
'<a class="mailto" href="' + href + '">info@studio80.ru</a> — отправьте его, ' +
'и мы ответим на ' + esc(email) + ' в течение рабочего дня. ' +
'Если окно не открылось, напишите нам напрямую на этот адрес.</p>' +
'</div>';
form.hidden = true;
done.hidden = false;
var d = done.querySelector('.done');
if (d) { d.setAttribute('tabindex', '-1'); d.focus(); }
try { window.location.href = href; } catch (err) {}
});
})();
</script>
</body>
</html>
Sitemap
Кол-во: 0
XML-карта сайта для поисковиков
?
Sitemap.xml помогает поисковику быстрее находить и индексировать страницы. Особенно важен для крупных сайтов и новых страниц, на которые ещё нет входящих ссылок.
Robots.txt не содержит ссылку на карту сайта. Рекомендуется добавить карту сайта и указать ссылку на нее в robots.txt.
Внутренние ссылки
Не найдено
Ссылки на другие страницы своего сайта
?
Внутренние ссылки распределяют ссылочный вес между страницами и помогают поисковику обходить сайт. Пустые анкоры и ссылки на запрещённые robots.txt страницы — типичные ошибки.
Внутренних ссылок на странице 0 оптимально.
Внешние ссылки
Не найдено
Ссылки на сторонние сайты
?
Исходящие внешние ссылки передают часть ссылочного веса на чужие сайты. Ссылки на авторитетные ресурсы безопасны; ссылки на мусорные сайты могут навредить репутации страницы.
Внешних ссылок на странице не найдено.
Конкуренты Готовность: 0%
Конкуренты в Яндексе
Кол-во: 0
Топ сайтов-конкурентов в Яндексе
?
Сайты, чаще всего появляющиеся в ТОПе Яндекса по запросам из семантического ядра этой страницы.
Мы не нашли у вас конкурентов в Яндексе. Сайт или очень молодой или плохо продвигается.
Конкурентов в ТОП-10 Яндекса не нашлось.
Конкуренты в Google
Кол-во: 0
Топ сайтов-конкурентов в Google
?
Сайты, чаще всего появляющиеся в ТОПе Google по запросам из семантического ядра этой страницы.
Конкуренты в Google тоже не найдены. Займитесь продвижением сайта!
Конкурентов в ТОП-10 Google не нашлось.
ФЗ-152: ПД Готовность: 80%
РКН
ОК
Сайт не заблокирован
ОГРН
Есть
ОГРН найден
ИНН
Есть
ИНН найден
Контакты
https://studio80.ru/contacts/
Страница с контактами найдена
Политика ПД
https://studio80.ru/policy/
Страница политики обработки ПД найдена
Согласие
https://studio80.ru/personal/
Страница согласия на обработку ПД найдена
Страница с формой
https://studio80.ru/
Страница с формой найдена
Чекбокс согласия
Найден
Чекбокс согласия на обработку ПД в формах сайта присутствует
Счётчики до согласия
ОК
Счётчики аналитики не собирают данные до согласия пользователя
ЗоЗПП: права потребителей Готовность: 100%
Нарушения
Не выявлены
Признаков дистанционной продажи товаров (интернет-магазина) не обнаружено — требования ЗоЗПП о раскрытии информации продавца к сайту не применяются. Нарушений нет.
ФЗ-149: рекомендательные технологии Готовность: 100%
Нарушения
Не выявлены
Рекомендательные блоки («с этим покупают», «похожие товары» и т.п.) на сайте не обнаружены — требования ст. 10.7 ФЗ-149 к сайту не применяются. Нарушений нет.
ФЗ-38: реклама Готовность: 100%
Нарушения
Не выявлены
Рекламных тематик с обязательными оговорками (медицина, БАД, кредиты и займы, новостройки) на сайте не обнаружено. Нарушений нет.
ФЗ-436: защита детей Готовность: 100%
Нарушения
Не выявлены
Признаков информационной продукции (новости, видео, книги, игры, курсы) не обнаружено — обязательная возрастная маркировка по ФЗ-436 сайту не требуется. Нарушений нет.
Вердикт
Сайт studio80.ru проверен - готовность высокая и составляет 78%.
Исправьте ошибки в мета-тегах.
Поделитесь с друзьями: