← HTML & CSS/Паттерны адаптивности#47 из 383← ПредыдущийСледующий →+20 XP
Полезно по теме:Гайд: старт в frontendПрактика: DOM и событияТермин: DOMМаршрут: старт с нуля

Паттерны адаптивности

Медиа-запросы — это инструмент. Но инструмент бесполезен без стратегии. В этом уроке разберём проверенные паттерны, которые используются в реальных проектах: подход mobile-first, fluid-типографику, единицы вьюпорта и адаптивную навигацию.

Mobile-first vs Desktop-first

Mobile-first (рекомендуемый подход)

Пишем базовые стили для мобильного, расширяем через min-width:

/* Базовые стили — мобильный */
.header {
  padding: 12px 16px;
  font-size: 1rem;
}

.nav-links {
  display: none; /* На мобильном скрыта */
}

.hamburger {
  display: block;
}

/* Планшет */
@media (min-width: 768px) {
  .header {
    padding: 16px 24px;
  }
}

/* Десктоп */
@media (min-width: 1024px) {
  .nav-links {
    display: flex;
    gap: 24px;
  }

  .hamburger {
    display: none;
  }
}

Почему mobile-first лучше:

  • Мобильные устройства получают минимальный CSS → быстрая загрузка
  • Сложность добавляется постепенно, а не убирается
  • Заставляет думать о приоритетах контента
  • min-width работает каскадно — каждый следующий брейкпоинт дополняет предыдущий
  • Desktop-first (устаревший подход)

    /* Базовые стили — десктоп */
    .sidebar { width: 300px; float: left; }
    
    /* «Ломаем» на планшетах */
    @media (max-width: 1023px) {
      .sidebar { width: 200px; }
    }
    
    /* «Ломаем» ещё раз на мобильных */
    @media (max-width: 767px) {
      .sidebar { width: 100%; float: none; }
    }

    Desktop-first — это костыли поверх костылей. Каждый брейкпоинт отменяет предыдущие стили. Избегай.

    Fluid Typography — плавная типографика

    Вместо прыжков размера шрифта на брейкпоинтах можно сделать плавное изменение.

    Функция clamp()

    /* clamp(минимум, предпочтительный, максимум) */
    h1 {
      font-size: clamp(1.5rem, 4vw + 0.5rem, 3.5rem);
    }

    Как это работает:

  • На маленьком экране: шрифт не меньше 1.5rem (24px)
  • Растёт плавно по формуле 4vw + 0.5rem
  • На большом экране: не больше 3.5rem (56px)
  • Практическая система типографики

    :root {
      /* Fluid шрифты */
      --text-sm:   clamp(0.75rem,  0.7rem  + 0.25vw, 0.875rem);
      --text-base: clamp(0.875rem, 0.8rem  + 0.4vw,  1rem);
      --text-lg:   clamp(1.125rem, 1rem    + 0.6vw,  1.375rem);
      --text-xl:   clamp(1.25rem,  1rem    + 1.2vw,  2rem);
      --text-2xl:  clamp(1.5rem,   1rem    + 2vw,    3rem);
      --text-3xl:  clamp(2rem,     1.2rem  + 3vw,    4rem);
    
      /* Fluid отступы */
      --space-sm:  clamp(0.5rem,  0.3rem + 1vw,  1rem);
      --space-md:  clamp(1rem,    0.5rem + 2vw,  2rem);
      --space-lg:  clamp(1.5rem,  0.5rem + 4vw,  4rem);
      --space-xl:  clamp(2rem,    1rem   + 5vw,  6rem);
    }
    
    h1 { font-size: var(--text-3xl); }
    h2 { font-size: var(--text-2xl); }
    h3 { font-size: var(--text-xl); }
    p  { font-size: var(--text-base); }
    section { padding: var(--space-lg) var(--space-md); }

    Теперь вся типографика и отступы масштабируются плавно без единого медиа-запроса.

    Единицы вьюпорта

    Классические единицы

    .hero {
      width: 100vw;   /* 100% ширины вьюпорта */
      height: 100vh;  /* 100% высоты вьюпорта */
    }
    
    .sidebar {
      width: 25vw;          /* 25% ширины */
      min-height: 100vh;
    }
    
    .text {
      font-size: 5vmin;  /* 5% от меньшей стороны вьюпорта */
    }

    | Единица | Что измеряет |

    |---------|-------------|

    | vw | 1% ширины вьюпорта |

    | vh | 1% высоты вьюпорта |

    | vmin | 1% меньшей стороны |

    | vmax | 1% большей стороны |

    Проблема 100vh на мобильных

    На мобильных 100vh включает область за адресной строкой браузера. Результат — контент уезжает за пределы видимой области.

    /* Плохо: на мобильном элемент выше видимой области */
    .fullscreen {
      height: 100vh;
    }
    
    /* Хорошо: новые динамические единицы */
    .fullscreen {
      height: 100dvh;  /* Динамическая высота — учитывает состояние браузера */
    }

    Новые единицы вьюпорта (поддерживаются всеми современными браузерами)

    | Единица | Описание |

    |---------|----------|

    | svh / svw | Small viewport — минимальный размер (адресная строка видна) |

    | lvh / lvw | Large viewport — максимальный размер (адресная строка скрыта) |

    | dvh / dvw | Dynamic viewport — текущий размер (меняется при скролле) |

    /* Герой-секция занимает ровно видимую область на любом устройстве */
    .hero {
      min-height: 100svh;  /* Гарантированно помещается на экране */
    }
    
    /* Фиксированная панель внизу */
    .bottom-bar {
      position: fixed;
      bottom: 0;
      height: env(safe-area-inset-bottom, 0px); /* Учитывает «чёлку» iPhone */
    }

    Адаптивная навигация

    Паттерн: гамбургер-меню

    <nav class="nav">
      <a href="/" class="nav-logo">Сайт</a>
      <button class="nav-toggle" aria-label="Меню">☰</button>
      <ul class="nav-links">
        <li><a href="/about">О нас</a></li>
        <li><a href="/services">Услуги</a></li>
        <li><a href="/contacts">Контакты</a></li>
      </ul>
    </nav>
    .nav {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px 16px;
    }
    
    .nav-links {
      display: none;
      list-style: none;
      padding: 0;
      margin: 0;
    }
    
    .nav-links.active {
      display: flex;
      flex-direction: column;
      position: absolute;
      top: 56px;
      left: 0;
      right: 0;
      background: white;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
      padding: 16px;
      gap: 12px;
    }
    
    .nav-toggle {
      display: block;
      background: none;
      border: none;
      font-size: 1.5rem;
      cursor: pointer;
    }
    
    @media (min-width: 768px) {
      .nav-links {
        display: flex;
        flex-direction: row;
        position: static;
        background: none;
        box-shadow: none;
        padding: 0;
        gap: 24px;
      }
    
      .nav-toggle {
        display: none;
      }
    }

    Паттерн: навигация, которая превращается в табы

    .tabs {
      display: flex;
      overflow-x: auto;             /* На мобильном — горизонтальный скролл */
      -webkit-overflow-scrolling: touch;
      scrollbar-width: none;        /* Прячем скроллбар */
      gap: 0;
    }
    
    .tabs::-webkit-scrollbar { display: none; }
    
    .tab {
      flex-shrink: 0;               /* Не сжимаются */
      padding: 12px 20px;
      white-space: nowrap;
    }
    
    @media (min-width: 768px) {
      .tabs {
        overflow-x: visible;        /* На десктопе всё видно */
        justify-content: center;
      }
    }

    Стратегия брейкпоинтов

    Не привязывайся к устройствам

    /* Плохо: «iPhone 14 Pro Max» */
    @media (max-width: 430px) { }
    
    /* Хорошо: контент определяет брейкпоинт */
    /* Смотришь, где макет «ломается» — ставишь брейкпоинт */
    @media (min-width: 600px) { }   /* Контент перестаёт помещаться в одну колонку */
    @media (min-width: 960px) { }   /* Хватает места для сайдбара */

    Рекомендованный набор

    /* Минимальный набор (2-3 брейкпоинта) */
    :root {
      --bp-tablet: 768px;
      --bp-desktop: 1024px;
    }
    
    /* Или используй Container Queries (CSS 2023+) */
    @container (min-width: 400px) {
      .card { flex-direction: row; }
    }

    Container Queries — будущее адаптивности

    Медиа-запросы проверяют размер окна. Container Queries — размер контейнера. Компонент адаптируется к своему контексту, а не к экрану.

    .card-wrapper {
      container-type: inline-size;
      container-name: card;
    }
    
    @container card (min-width: 400px) {
      .card {
        display: flex;
        flex-direction: row;
      }
      .card-image {
        width: 40%;
      }
    }
    
    @container card (max-width: 399px) {
      .card {
        display: flex;
        flex-direction: column;
      }
      .card-image {
        width: 100%;
      }
    }

    Теперь карточка будет горизонтальной в широком контейнере и вертикальной в узком — независимо от размера экрана.

    Типичные ошибки

    Ошибка 1: фиксированные размеры вместо fluid

    /* Плохо */
    h1 { font-size: 48px; }
    @media (max-width: 768px) { h1 { font-size: 24px; } }
    
    /* Хорошо */
    h1 { font-size: clamp(1.5rem, 4vw + 0.5rem, 3rem); }

    Ошибка 2: 100vh на мобильном

    /* Плохо: контент прячется за адресную строку */
    .hero { height: 100vh; }
    
    /* Хорошо */
    .hero { min-height: 100dvh; }

    Ошибка 3: слишком много брейкпоинтов

    Если у тебя больше 4 — значит, дизайн недостаточно гибкий. Fluid-размеры и flexbox/grid решают большинство задач без медиа-запросов.

    Примеры

    Mobile-first страница с fluid-типографикой и адаптивной навигацией

    <!DOCTYPE html>
    <html lang="ru">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <style>
        * { margin: 0; box-sizing: border-box; }
    
        :root {
          --text-base: clamp(0.875rem, 0.8rem + 0.4vw, 1rem);
          --text-xl:   clamp(1.25rem, 1rem + 1.2vw, 2rem);
          --text-3xl:  clamp(2rem, 1.2rem + 3vw, 4rem);
          --space-md:  clamp(1rem, 0.5rem + 2vw, 2rem);
          --space-lg:  clamp(1.5rem, 0.5rem + 4vw, 4rem);
        }
    
        body { font-family: system-ui, sans-serif; font-size: var(--text-base); }
    
        /* Навигация: mobile-first */
        .nav {
          display: flex;
          flex-wrap: wrap;
          justify-content: space-between;
          align-items: center;
          padding: 12px 16px;
          background: #1e293b;
          color: white;
        }
        .nav-logo { color: white; text-decoration: none; font-weight: 700; }
        .nav-toggle {
          display: block; background: none;
          border: 1px solid #475569; color: white;
          padding: 6px 12px; border-radius: 6px; cursor: pointer;
        }
        .nav-links {
          display: none; width: 100%;
          list-style: none; padding: 12px 0 0; margin: 0;
        }
        .nav-links.active { display: flex; flex-direction: column; gap: 8px; }
        .nav-links a { color: #94a3b8; text-decoration: none; }
    
        @media (min-width: 768px) {
          .nav-toggle { display: none; }
          .nav-links {
            display: flex !important; width: auto;
            flex-direction: row; gap: 24px; padding: 0;
          }
        }
    
        /* Hero: fluid */
        .hero {
          min-height: 100dvh;
          display: flex; align-items: center; justify-content: center;
          padding: var(--space-lg);
          background: linear-gradient(135deg, #0f172a, #1e3a5f);
          color: white; text-align: center;
        }
        .hero h1 { font-size: var(--text-3xl); margin-bottom: var(--space-md); }
        .hero p  { font-size: var(--text-xl); opacity: 0.8; max-width: 600px; }
    
        /* Контент */
        .content {
          max-width: 1200px;
          margin: 0 auto;
          padding: var(--space-lg) var(--space-md);
        }
      </style>
    </head>
    <body>
      <nav class="nav">
        <a href="/" class="nav-logo">MySite</a>
        <button class="nav-toggle" onclick="this.nextElementSibling.classList.toggle('active')">☰</button>
        <ul class="nav-links">
          <li><a href="#">Главная</a></li>
          <li><a href="#">О нас</a></li>
          <li><a href="#">Контакты</a></li>
        </ul>
      </nav>
    
      <section class="hero">
        <div>
          <h1>Fluid типографика</h1>
          <p>Размер текста плавно меняется от мобильного к десктопу — без единого медиа-запроса</p>
        </div>
      </section>
    
      <div class="content">
        <h2 style="font-size: var(--text-xl); margin-bottom: var(--space-md);">
          Измени размер окна
        </h2>
        <p>Навигация превращается в гамбургер-меню, типографика масштабируется, герой-секция занимает ровно видимый экран.</p>
      </div>
    </body>
    </html>

    Паттерны адаптивности

    Медиа-запросы — это инструмент. Но инструмент бесполезен без стратегии. В этом уроке разберём проверенные паттерны, которые используются в реальных проектах: подход mobile-first, fluid-типографику, единицы вьюпорта и адаптивную навигацию.

    Mobile-first vs Desktop-first

    Mobile-first (рекомендуемый подход)

    Пишем базовые стили для мобильного, расширяем через min-width:

    /* Базовые стили — мобильный */
    .header {
      padding: 12px 16px;
      font-size: 1rem;
    }
    
    .nav-links {
      display: none; /* На мобильном скрыта */
    }
    
    .hamburger {
      display: block;
    }
    
    /* Планшет */
    @media (min-width: 768px) {
      .header {
        padding: 16px 24px;
      }
    }
    
    /* Десктоп */
    @media (min-width: 1024px) {
      .nav-links {
        display: flex;
        gap: 24px;
      }
    
      .hamburger {
        display: none;
      }
    }

    Почему mobile-first лучше:

  • Мобильные устройства получают минимальный CSS → быстрая загрузка
  • Сложность добавляется постепенно, а не убирается
  • Заставляет думать о приоритетах контента
  • min-width работает каскадно — каждый следующий брейкпоинт дополняет предыдущий
  • Desktop-first (устаревший подход)

    /* Базовые стили — десктоп */
    .sidebar { width: 300px; float: left; }
    
    /* «Ломаем» на планшетах */
    @media (max-width: 1023px) {
      .sidebar { width: 200px; }
    }
    
    /* «Ломаем» ещё раз на мобильных */
    @media (max-width: 767px) {
      .sidebar { width: 100%; float: none; }
    }

    Desktop-first — это костыли поверх костылей. Каждый брейкпоинт отменяет предыдущие стили. Избегай.

    Fluid Typography — плавная типографика

    Вместо прыжков размера шрифта на брейкпоинтах можно сделать плавное изменение.

    Функция clamp()

    /* clamp(минимум, предпочтительный, максимум) */
    h1 {
      font-size: clamp(1.5rem, 4vw + 0.5rem, 3.5rem);
    }

    Как это работает:

  • На маленьком экране: шрифт не меньше 1.5rem (24px)
  • Растёт плавно по формуле 4vw + 0.5rem
  • На большом экране: не больше 3.5rem (56px)
  • Практическая система типографики

    :root {
      /* Fluid шрифты */
      --text-sm:   clamp(0.75rem,  0.7rem  + 0.25vw, 0.875rem);
      --text-base: clamp(0.875rem, 0.8rem  + 0.4vw,  1rem);
      --text-lg:   clamp(1.125rem, 1rem    + 0.6vw,  1.375rem);
      --text-xl:   clamp(1.25rem,  1rem    + 1.2vw,  2rem);
      --text-2xl:  clamp(1.5rem,   1rem    + 2vw,    3rem);
      --text-3xl:  clamp(2rem,     1.2rem  + 3vw,    4rem);
    
      /* Fluid отступы */
      --space-sm:  clamp(0.5rem,  0.3rem + 1vw,  1rem);
      --space-md:  clamp(1rem,    0.5rem + 2vw,  2rem);
      --space-lg:  clamp(1.5rem,  0.5rem + 4vw,  4rem);
      --space-xl:  clamp(2rem,    1rem   + 5vw,  6rem);
    }
    
    h1 { font-size: var(--text-3xl); }
    h2 { font-size: var(--text-2xl); }
    h3 { font-size: var(--text-xl); }
    p  { font-size: var(--text-base); }
    section { padding: var(--space-lg) var(--space-md); }

    Теперь вся типографика и отступы масштабируются плавно без единого медиа-запроса.

    Единицы вьюпорта

    Классические единицы

    .hero {
      width: 100vw;   /* 100% ширины вьюпорта */
      height: 100vh;  /* 100% высоты вьюпорта */
    }
    
    .sidebar {
      width: 25vw;          /* 25% ширины */
      min-height: 100vh;
    }
    
    .text {
      font-size: 5vmin;  /* 5% от меньшей стороны вьюпорта */
    }

    | Единица | Что измеряет |

    |---------|-------------|

    | vw | 1% ширины вьюпорта |

    | vh | 1% высоты вьюпорта |

    | vmin | 1% меньшей стороны |

    | vmax | 1% большей стороны |

    Проблема 100vh на мобильных

    На мобильных 100vh включает область за адресной строкой браузера. Результат — контент уезжает за пределы видимой области.

    /* Плохо: на мобильном элемент выше видимой области */
    .fullscreen {
      height: 100vh;
    }
    
    /* Хорошо: новые динамические единицы */
    .fullscreen {
      height: 100dvh;  /* Динамическая высота — учитывает состояние браузера */
    }

    Новые единицы вьюпорта (поддерживаются всеми современными браузерами)

    | Единица | Описание |

    |---------|----------|

    | svh / svw | Small viewport — минимальный размер (адресная строка видна) |

    | lvh / lvw | Large viewport — максимальный размер (адресная строка скрыта) |

    | dvh / dvw | Dynamic viewport — текущий размер (меняется при скролле) |

    /* Герой-секция занимает ровно видимую область на любом устройстве */
    .hero {
      min-height: 100svh;  /* Гарантированно помещается на экране */
    }
    
    /* Фиксированная панель внизу */
    .bottom-bar {
      position: fixed;
      bottom: 0;
      height: env(safe-area-inset-bottom, 0px); /* Учитывает «чёлку» iPhone */
    }

    Адаптивная навигация

    Паттерн: гамбургер-меню

    <nav class="nav">
      <a href="/" class="nav-logo">Сайт</a>
      <button class="nav-toggle" aria-label="Меню">☰</button>
      <ul class="nav-links">
        <li><a href="/about">О нас</a></li>
        <li><a href="/services">Услуги</a></li>
        <li><a href="/contacts">Контакты</a></li>
      </ul>
    </nav>
    .nav {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px 16px;
    }
    
    .nav-links {
      display: none;
      list-style: none;
      padding: 0;
      margin: 0;
    }
    
    .nav-links.active {
      display: flex;
      flex-direction: column;
      position: absolute;
      top: 56px;
      left: 0;
      right: 0;
      background: white;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
      padding: 16px;
      gap: 12px;
    }
    
    .nav-toggle {
      display: block;
      background: none;
      border: none;
      font-size: 1.5rem;
      cursor: pointer;
    }
    
    @media (min-width: 768px) {
      .nav-links {
        display: flex;
        flex-direction: row;
        position: static;
        background: none;
        box-shadow: none;
        padding: 0;
        gap: 24px;
      }
    
      .nav-toggle {
        display: none;
      }
    }

    Паттерн: навигация, которая превращается в табы

    .tabs {
      display: flex;
      overflow-x: auto;             /* На мобильном — горизонтальный скролл */
      -webkit-overflow-scrolling: touch;
      scrollbar-width: none;        /* Прячем скроллбар */
      gap: 0;
    }
    
    .tabs::-webkit-scrollbar { display: none; }
    
    .tab {
      flex-shrink: 0;               /* Не сжимаются */
      padding: 12px 20px;
      white-space: nowrap;
    }
    
    @media (min-width: 768px) {
      .tabs {
        overflow-x: visible;        /* На десктопе всё видно */
        justify-content: center;
      }
    }

    Стратегия брейкпоинтов

    Не привязывайся к устройствам

    /* Плохо: «iPhone 14 Pro Max» */
    @media (max-width: 430px) { }
    
    /* Хорошо: контент определяет брейкпоинт */
    /* Смотришь, где макет «ломается» — ставишь брейкпоинт */
    @media (min-width: 600px) { }   /* Контент перестаёт помещаться в одну колонку */
    @media (min-width: 960px) { }   /* Хватает места для сайдбара */

    Рекомендованный набор

    /* Минимальный набор (2-3 брейкпоинта) */
    :root {
      --bp-tablet: 768px;
      --bp-desktop: 1024px;
    }
    
    /* Или используй Container Queries (CSS 2023+) */
    @container (min-width: 400px) {
      .card { flex-direction: row; }
    }

    Container Queries — будущее адаптивности

    Медиа-запросы проверяют размер окна. Container Queries — размер контейнера. Компонент адаптируется к своему контексту, а не к экрану.

    .card-wrapper {
      container-type: inline-size;
      container-name: card;
    }
    
    @container card (min-width: 400px) {
      .card {
        display: flex;
        flex-direction: row;
      }
      .card-image {
        width: 40%;
      }
    }
    
    @container card (max-width: 399px) {
      .card {
        display: flex;
        flex-direction: column;
      }
      .card-image {
        width: 100%;
      }
    }

    Теперь карточка будет горизонтальной в широком контейнере и вертикальной в узком — независимо от размера экрана.

    Типичные ошибки

    Ошибка 1: фиксированные размеры вместо fluid

    /* Плохо */
    h1 { font-size: 48px; }
    @media (max-width: 768px) { h1 { font-size: 24px; } }
    
    /* Хорошо */
    h1 { font-size: clamp(1.5rem, 4vw + 0.5rem, 3rem); }

    Ошибка 2: 100vh на мобильном

    /* Плохо: контент прячется за адресную строку */
    .hero { height: 100vh; }
    
    /* Хорошо */
    .hero { min-height: 100dvh; }

    Ошибка 3: слишком много брейкпоинтов

    Если у тебя больше 4 — значит, дизайн недостаточно гибкий. Fluid-размеры и flexbox/grid решают большинство задач без медиа-запросов.

    Примеры

    Mobile-first страница с fluid-типографикой и адаптивной навигацией

    <!DOCTYPE html>
    <html lang="ru">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <style>
        * { margin: 0; box-sizing: border-box; }
    
        :root {
          --text-base: clamp(0.875rem, 0.8rem + 0.4vw, 1rem);
          --text-xl:   clamp(1.25rem, 1rem + 1.2vw, 2rem);
          --text-3xl:  clamp(2rem, 1.2rem + 3vw, 4rem);
          --space-md:  clamp(1rem, 0.5rem + 2vw, 2rem);
          --space-lg:  clamp(1.5rem, 0.5rem + 4vw, 4rem);
        }
    
        body { font-family: system-ui, sans-serif; font-size: var(--text-base); }
    
        /* Навигация: mobile-first */
        .nav {
          display: flex;
          flex-wrap: wrap;
          justify-content: space-between;
          align-items: center;
          padding: 12px 16px;
          background: #1e293b;
          color: white;
        }
        .nav-logo { color: white; text-decoration: none; font-weight: 700; }
        .nav-toggle {
          display: block; background: none;
          border: 1px solid #475569; color: white;
          padding: 6px 12px; border-radius: 6px; cursor: pointer;
        }
        .nav-links {
          display: none; width: 100%;
          list-style: none; padding: 12px 0 0; margin: 0;
        }
        .nav-links.active { display: flex; flex-direction: column; gap: 8px; }
        .nav-links a { color: #94a3b8; text-decoration: none; }
    
        @media (min-width: 768px) {
          .nav-toggle { display: none; }
          .nav-links {
            display: flex !important; width: auto;
            flex-direction: row; gap: 24px; padding: 0;
          }
        }
    
        /* Hero: fluid */
        .hero {
          min-height: 100dvh;
          display: flex; align-items: center; justify-content: center;
          padding: var(--space-lg);
          background: linear-gradient(135deg, #0f172a, #1e3a5f);
          color: white; text-align: center;
        }
        .hero h1 { font-size: var(--text-3xl); margin-bottom: var(--space-md); }
        .hero p  { font-size: var(--text-xl); opacity: 0.8; max-width: 600px; }
    
        /* Контент */
        .content {
          max-width: 1200px;
          margin: 0 auto;
          padding: var(--space-lg) var(--space-md);
        }
      </style>
    </head>
    <body>
      <nav class="nav">
        <a href="/" class="nav-logo">MySite</a>
        <button class="nav-toggle" onclick="this.nextElementSibling.classList.toggle('active')">☰</button>
        <ul class="nav-links">
          <li><a href="#">Главная</a></li>
          <li><a href="#">О нас</a></li>
          <li><a href="#">Контакты</a></li>
        </ul>
      </nav>
    
      <section class="hero">
        <div>
          <h1>Fluid типографика</h1>
          <p>Размер текста плавно меняется от мобильного к десктопу — без единого медиа-запроса</p>
        </div>
      </section>
    
      <div class="content">
        <h2 style="font-size: var(--text-xl); margin-bottom: var(--space-md);">
          Измени размер окна
        </h2>
        <p>Навигация превращается в гамбургер-меню, типографика масштабируется, герой-секция занимает ровно видимый экран.</p>
      </div>
    </body>
    </html>

    Задание

    Создай mobile-first страницу с fluid-типографикой. Заголовок `h1` должен плавно масштабироваться от `1.5rem` до `3.5rem` с помощью `clamp()`. Герой-секция должна занимать всю высоту видимого экрана (используй динамическую единицу вьюпорта). Добавь CSS-переменные для fluid-отступов.

    Подсказка

    Для `--text-heading` используй `clamp(1.5rem, 1.2rem + 3vw, 3.5rem)`. Динамическая единица высоты вьюпорта — `dvh`, то есть `100dvh`. В `clamp()` для отступов средний аргумент содержит сумму `rem` и `vw`. Переменные подставляй через `var(--имя)`.

    Загружаем среду выполнения...
    Загружаем AI-помощника...