Как создать всплывающее окно при входе на сайт с формой авторизации

Всплывающее окно при входе на сайт состоит из затемняющего слоя, панели и обработчиков событий. HTML задает поля формы, CSS размещает окно, а jQuery управляет открытием. Добавьте закрытие кнопкой, клавишей Escape и нажатием на фон. Адаптивная ширина и управление фокусом сделают форму удобнее на телефоне и с клавиатуры.

Пример собирает интерфейс окна. Проверку учетных данных выполняет серверный обработчик, который вы подключаете отдельно.

Подготовьте файлы и подключите jQuery

Создайте папку примера с файлами index.html, popup.css и popup.js. Разметку, стили и обработчики сохраните в соответствующие файлы. Это один комплект кода: повторно добавлять фрагменты не потребуется.

Пример использует полную сборку jQuery с поддержкой AJAX.

Если библиотека уже подключена в проекте, используйте существующее подключение. Вторую копию добавлять не нужно: она усложняет работу плагинов и обработчиков.

Для отдельного примера скачайте библиотеку с официального сайта jQuery. В разделе загрузки выберите полную сборку ветки 3.x: несжатую для изучения кода или сжатую production-сборку. Slim-сборка для AJAX-варианта не подходит.

  1. Откройте ссылку на выбранную сборку. Если браузер показывает JavaScript текстом, нажмите Ctrl+S в Windows или Linux либо Command+S в macOS.
  2. Создайте папку assets рядом с index.html. Сохраните скачанный файл в эту папку под именем jquery.js. Если браузер уже скачал файл, перенесите его туда и переименуйте.
  3. Проверьте имя файла: должно получиться assets/jquery.js, без дополнительного расширения .txt. Итоговая разметка подключает библиотеку именно по этому локальному пути.

Назначение методов описывает документация jQuery.

Дополнительные веб-шрифты для работы окна не нужны. Пример использует системный шрифт и сохраняет видимую рамку фокуса.

Добавьте HTML-код формы входа

Сохраните разметку в index.html. Ссылка «Войти» открывает окно через JavaScript. Ее адрес login.php остается запасным переходом на отдельную страницу входа.

<!doctype html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Форма входа</title>
  <link rel="stylesheet" href="popup.css">
  <script src="assets/jquery.js" defer></script>
  <script src="popup.js" defer></script>
</head>
<body>
  <main id="pageContent">
    <p>Содержимое страницы</p>
    <a href="login.php" id="loginLink">Войти</a>
  </main>

  <div class="overlay" id="loginOverlay" hidden>
    <div class="login-wrapper" role="dialog" aria-modal="true"
         aria-labelledby="loginTitle" tabindex="-1">
      <div class="login-content">
        <button type="button" class="close" aria-label="Закрыть">×</button>
        <div id="loginTarget">
          <h2 id="loginTitle">Вход</h2>
          <form method="post" action="login.php">
            <label for="username">Имя пользователя</label>
            <p id="usernameHelp">От 8 до 20 символов. Начните с латинской буквы;
              дальше допустимы латинские буквы, цифры, точка, дефис и подчеркивание.</p>
            <input type="text" name="username" id="username"
                   autocomplete="username" aria-describedby="usernameHelp"
                   pattern="[a-zA-Z][a-zA-Z0-9_.\-]{7,19}" required>
            <label for="password">Пароль</label>
            <input type="password" name="password" id="password"
                   autocomplete="current-password" required>
            <button type="submit">Войти</button>
          </form>
        </div>
      </div>
    </div>
  </div>
</body>
</html>

Атрибут hidden скрывает наложение до открытия.

Атрибуты role="dialog" и aria-modal="true" обозначают модальное окно для вспомогательных технологий. Заголовок loginTitle задает его доступное название.

Шаблон логина проверяет 8–20 символов: первая буква плюс еще 7–19 знаков. Обратный слэш экранирует дефис внутри класса символов. Правила синтаксиса разобраны в документации атрибута pattern.

В форме входа пароль проверяется только на заполненность.

Требования к сложности задавайте при регистрации: новые правила не должны блокировать вход с существующим паролем. Если ваш сайт принимает другие логины, согласуйте шаблон с сервером.

Создайте CSS-наложение и адаптивную панель

Разместите панель входа внутри наложения, которое затемняет остальную страницу.

Сохраните стили в popup.css. Правило position: fixed закрепляет фон относительно области просмотра. Свойство z-index поднимает окно над обычными блоками страницы.

*, *::before, *::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: #f3f3f3;
  color: #222;
  font-family: Arial, sans-serif;
}

#pageContent { padding: 24px; }
body.modal-open { overflow: hidden; }

.overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 16px;
  background: rgba(0, 0, 0, .5);
}

.overlay[hidden] { display: none; }

.login-wrapper {
  width: 100%;
  max-width: 450px;
  max-height: 100%;
  overflow-y: auto;
  padding: 6px;
  border-radius: 8px;
  background: rgba(0, 0, 0, .25);
}

.login-content {
  position: relative;
  padding: 24px;
  border-radius: 4px;
  background: #fff;
}

#loginTitle {
  margin: 0 48px 20px 0;
  font-size: 1.5rem;
}

.close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 40px;
  height: 40px;
  border: 0;
  border-radius: 50%;
  background: #e5e5e5;
  color: #222;
  font-size: 24px;
  cursor: pointer;
}

#loginTarget label {
  display: block;
  margin-top: 16px;
}

#loginTarget p {
  font-size: .875rem;
  line-height: 1.5;
}

#loginTarget input {
  display: block;
  width: 100%;
  min-width: 0;
  margin: 6px 0 16px;
  padding: 12px;
  border: 1px solid #777;
  border-radius: 4px;
  font: inherit;
  font-size: 16px;
}

#loginTarget button[type="submit"] {
  padding: 12px 20px;
  border: 0;
  border-radius: 4px;
  background: #1459a6;
  color: #fff;
  font: inherit;
  cursor: pointer;
}

#loginTarget button[type="submit"]:hover { background: #10477f; }
.close:hover { background: #ccc; }

:focus-visible {
  outline: 3px solid #1459a6;
  outline-offset: 3px;
}

#loginTarget input:invalid:focus { border-color: #b42318; }

@media (max-width: 480px) {
  .overlay { padding: 8px; }
  .login-content { padding: 20px 16px; }
}

Панель занимает 100% доступной ширины, но ограничена значением 450px. На узком экране внешние отступы уменьшаются до 8px. Ограничение высоты и внутренняя прокрутка помогают добраться до кнопки при открытой экранной клавиатуре.

Flexbox центрирует панель по двум осям.

При стандартном flex-direction: row свойство justify-content управляет горизонтальным выравниванием. Свойства align-items и align-self работают по поперечной, то есть вертикальной оси.

Сделайте кнопки и поля доступными с клавиатуры

Кнопка закрытия использует элемент button, поэтому получает фокус клавишей Tab. Атрибут type="button" исключает случайную отправку формы. Название «Закрыть» озвучивается благодаря aria-label.

Свойство right: 12px отсчитывает отступ от правого края .login-content. Именно этот контейнер задает ближайшего позиционированного предка кнопки через position: relative.

Каждая подпись связывается с полем через for и id. Нажатие на подпись переводит курсор в соответствующее поле. Подсказки остаются видимыми после ввода, поскольку не спрятаны в placeholder.

Не удаляйте рамку фокуса глобальным правилом outline: none.

Селектор :focus-visible показывает текущее положение при навигации с клавиатуры.

Открывайте окно и управляйте фокусом через jQuery

Сохраните обработчики в popup.js. Сокращенная запись $(function () { ... }) запускает код после построения DOM. К этому моменту элементы страницы уже доступны скрипту.

$(function () {
  const overlay = document.getElementById("loginOverlay");
  const dialog = overlay.querySelector(".login-wrapper");
  const page = document.getElementById("pageContent");
  let returnFocus = null;
  let pageWasInert = false;
  let bodyWasLocked = false;

  function focusables() {
    return $(dialog).find(
      'a[href], button, input, select, textarea, [tabindex]'
    ).filter(':visible').filter(function () {
      return !this.disabled && this.tabIndex >= 0;
    }).get();
  }

  function focusFirst() {
    const input = $(dialog).find('input:visible:enabled').get(0);
    (input || focusables()[0] || dialog).focus();
  }

  function openModal(trigger) {
    if (!overlay.hidden) return;
    returnFocus = trigger || document.activeElement;
    pageWasInert = page.inert;
    bodyWasLocked = document.body.classList.contains("modal-open");
    overlay.hidden = false;
    document.body.classList.add("modal-open");
    focusFirst();
    page.inert = true;
  }

  function closeModal() {
    if (overlay.hidden) return;
    overlay.hidden = true;
    page.inert = pageWasInert;
    if (!bodyWasLocked) document.body.classList.remove("modal-open");
    if (returnFocus && returnFocus.isConnected) returnFocus.focus();
  }

  $("#loginLink").on("click", function (e) {
    e.preventDefault();
    openModal(this);
  });

  $(document).on("click", "#loginOverlay .close", closeModal);

  $(overlay).on("click", function (e) {
    if (e.target === e.currentTarget) closeModal();
  });

  $(document).on("keydown", function (e) {
    if (overlay.hidden) return;
    if (e.key === "Escape") {
      e.preventDefault();
      closeModal();
      return;
    }
    if (e.key !== "Tab") return;
    const items = focusables();
    const first = items[0];
    const last = items[items.length - 1];
    if (!first) {
      e.preventDefault();
      dialog.focus();
    } else if (e.shiftKey &&
               (document.activeElement === first || document.activeElement === dialog)) {
      e.preventDefault();
      last.focus();
    } else if (!e.shiftKey &&
               (document.activeElement === last || document.activeElement === dialog)) {
      e.preventDefault();
      first.focus();
    }
  });

  $(document).on("focusin", function (e) {
    if (!overlay.hidden && !dialog.contains(e.target)) focusFirst();
  });

  // Дополнительные обработчики AJAX и автопоказа вставляйте здесь.
});

Метод события preventDefault() отменяет обычный переход по ссылке.

Проверка e.key === "Escape" закрывает только открытое окно. Нажатие внутри формы не закрывает панель: обработчик фона сравнивает цель события с самим наложением.

Функция openModal() запоминает элемент, вызвавший окно. Она переводит фокус в первое поле и блокирует прокрутку страницы. Атрибут inert исключает основное содержимое из взаимодействия, пока форма открыта.

Обработчик Tab удерживает обход внутри панели, включая Shift+Tab в обратном направлении. При закрытии скрипт восстанавливает прежнее состояние страницы и возвращает фокус.

В реальном проекте поместите в pageContent весь фон, включая шапку и подвал.

Подгружайте вход и регистрацию через AJAX

AJAX позволяет менять содержимое формы без перезагрузки страницы. Общая оболочка, затемнение и кнопка закрытия остаются на месте. Ответ сервера попадает только в #loginTarget.

Подготовьте HTML-фрагменты

Создайте папку ajax рядом с index.html. Скопируйте заголовок h2 и всю форму входа из #loginTarget в ajax/login-form.html. Файл должен содержать только эти два элемента, без оболочки документа и контейнера #loginTarget.

В index.html удалите из #loginTarget только форму входа. Заголовок «Вход» оставьте как начальное доступное название окна. Контейнер и соседняя кнопка закрытия тоже остаются в странице:

<div class="login-content">
  <button type="button" class="close" aria-label="Закрыть">×</button>
  <div id="loginTarget">
    <h2 id="loginTitle">Вход</h2>
  </div>
</div>

При загрузке скрипт заменит содержимое контейнера целиком, включая начальный заголовок. Поэтому после вставки фрагмента в документе останется один loginTitle.

Замените ссылку loginLink внутри pageContent двумя ссылками:

<a href="login.php" class="overlayLink" data-action="login">Войти</a>
<a href="register.php" class="overlayLink" data-action="register">Зарегистрироваться</a>

В файл ajax/registration-form.html сохраните форму регистрации:

<h2 id="loginTitle">Регистрация</h2>
<form id="registrationForm" method="post" action="register.php">
  <label for="regUsername">Имя пользователя</label>
  <p id="regUsernameHelp">От 8 до 20 символов, первый – латинская буква.
    Затем допустимы латинские буквы, цифры, точка, дефис и подчеркивание.</p>
  <input id="regUsername" name="username" type="text" autocomplete="username"
         aria-describedby="regUsernameHelp"
         pattern="[a-zA-Z][a-zA-Z0-9_.\-]{7,19}" required>
  <label for="regEmail">Электронная почта</label>
  <input id="regEmail" name="email" type="email" autocomplete="email" required>
  <label for="regPassword">Пароль</label>
  <p id="regPasswordHelp">Минимум 8 символов, включая заглавную и строчную
    латинские буквы, а также цифру или спецсимвол.</p>
  <input id="regPassword" name="password" type="password"
         autocomplete="new-password" aria-describedby="regPasswordHelp"
         pattern="(?=.{8,}$)(?=.*[A-Z])(?=.*[a-z])(?=.*[\d\W]).*" required>
  <label for="regConfirm">Повторите пароль</label>
  <input id="regConfirm" name="password_confirmation" type="password"
         autocomplete="new-password" required>
  <button type="submit">Зарегистрироваться</button>
</form>

Это учебная политика сложности, которую нужно согласовать с серверной проверкой.

В HTML-атрибуте обратные слэши записываются один раз. Класс [\d\W] допускает цифру или символ вне набора букв ASCII, цифр и подчеркивания.

Пароль Abc12345 соответствует этому шаблону. Сам атрибут pattern проверяет значение целиком. Пустую строку отдельно запрещает required.

Загрузите выбранную форму и обработайте ошибку

Вставьте обработчик внутрь $(function () { ... }), перед его закрытием. Объект forms ограничивает выбор двумя известными файлами. Атрибут ссылки не превращается в произвольный адрес запроса.

const forms = {
  login: "ajax/login-form.html",
  register: "ajax/registration-form.html"
};
let pending = null;

$(document).on("click", ".overlayLink", function (e) {
  const kind = this.dataset.action;
  if (!Object.prototype.hasOwnProperty.call(forms, kind)) return;
  e.preventDefault();
  const trigger = this;
  if (pending) pending.abort();
  $("#loginTarget").html(
    '<h2 id="loginTitle">Загрузка формы</h2><p role="status">Подождите…</p>'
  );
  openModal(trigger);
  dialog.focus();
  pending = $.ajax({ url: forms[kind], dataType: "html" })
    .done(function (html) {
      $("#loginTarget").html(html);
      if (!overlay.hidden) focusFirst();
    })
    .fail(function (xhr, status) {
      if (status === "abort") return;
      const fallback = kind === "register" ? "register.php" : "login.php";
      $("#loginTarget").html(
        '<h2 id="loginTitle">Форма недоступна</h2>' +
        '<p role="alert">Не удалось загрузить форму.</p>' +
        '<a href="' + fallback + '">Открыть отдельную страницу</a>'
      );
      if (!overlay.hidden) focusFirst();
    });
});

Загружайте таким способом только собственные доверенные HTML-фрагменты. Вставка чужого HTML способна добавить опасную разметку или скрипты.

После загрузки функция focusFirst() переводит курсор в новое поле.

Делегированный обработчик закрытия закреплен на document. Он продолжит работать, даже если кнопка появится динамически. В этом примере кнопка дополнительно вынесена за пределы заменяемого контейнера.

Сравните пароль с подтверждением

Добавьте эти обработчики в тот же блок готовности DOM. Делегирование охватывает форму, загруженную после открытия страницы.

function validateConfirmation(form) {
  const password = form.querySelector('[name="password"]');
  const confirmation = form.querySelector('[name="password_confirmation"]');
  confirmation.setCustomValidity(
    confirmation.value === password.value ? "" : "Пароли не совпадают"
  );
}

$(document).on("input", "#registrationForm input", function () {
  validateConfirmation(this.form);
});

$(document).on("submit", "#registrationForm", function (e) {
  validateConfirmation(this);
  if (!this.reportValidity()) e.preventDefault();
});

Пустая строка в setCustomValidity() снимает ошибку после исправления. Клиентская валидация формы регистрации дополняет проверку на сервере.

Серверный обработчик register.php тоже должен сравнивать пароли и проверять остальные поля. Клиентскую проверку пользователь может обойти.

Покажите всплывающее окно при входе на сайт

Если вы создаете всплывающее окно при входе на сайт, ограничьте повторные показы. Запись в localStorage сохранит отметку между посещениями в этом браузере. Не сохраняйте там введенные пароли.

Для варианта с формой прямо в странице вставьте таймер в блок готовности DOM. Задержка в примере составляет 1500 мс. Отметка записывается только после открытия окна.

const storageKey = "login-popup-shown";
let alreadyShown = true;
try {
  alreadyShown = localStorage.getItem(storageKey) === "yes";
} catch (error) {
  // При запрете хранилища автоматический показ отключен.
}

if (!alreadyShown) {
  setTimeout(function () {
    if (!overlay.hidden) return;
    openModal(document.getElementById("loginLink"));
    try {
      localStorage.setItem(storageKey, "yes");
    } catch (error) {
      // Окно продолжает работать без сохранения отметки.
    }
  }, 1500);
}

Этот фрагмент рассчитан на базовую разметку с loginLink.

В AJAX-варианте вместо вызова openModal() используйте $('.overlayLink[data-action="login"]').trigger('click'). Этот обработчик сначала подготовит загрузку формы.

Для повторной проверки удалите отметку командой localStorage.removeItem('login-popup-shown') в консоли браузера. Очистка данных сайта тоже удалит ее. Для авторизованного посетителя сервер должен отключать автоматический показ.

Когда можно обойтись без jQuery

Для новой страницы с единственным модальным окном используйте нативный <dialog>. Метод showModal() открывает модальный режим и блокирует взаимодействие с фоном. Браузер поддерживает закрытие клавишей Escape и удерживает клавиатурный фокус внутри.

Вариант работает независимо от предыдущих файлов. Сохраните его отдельной HTML-страницей:

<!doctype html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Нативное окно входа</title>
  <style>
    dialog { box-sizing: border-box; width: 92%; max-width: 450px; }
    dialog::backdrop { background: rgba(0, 0, 0, .5); }
  </style>
</head>
<body>
  <button type="button" id="openLogin">Войти</button>
  <dialog id="nativeLogin" aria-labelledby="nativeTitle">
    <button type="button" id="closeLogin" aria-label="Закрыть">×</button>
    <h2 id="nativeTitle">Вход</h2>
    <form action="login.php" method="post">
      <p><label>Имя пользователя
        <input name="username" autocomplete="username" required autofocus>
      </label></p>
      <p><label>Пароль
        <input name="password" type="password" autocomplete="current-password" required>
      </label></p>
      <button type="submit">Войти</button>
    </form>
  </dialog>
  <script>
    const modal = document.getElementById("nativeLogin");
    const opener = document.getElementById("openLogin");
    opener.addEventListener("click", () => modal.showModal());
    document.getElementById("closeLogin").addEventListener("click", () => modal.close());
    modal.addEventListener("close", () => opener.focus());
  </script>
</body>
</html>

Псевдоэлемент ::backdrop оформляет затемнение.

В этом минимальном варианте окно закрывается кнопкой и Escape; закрытие по фону отдельно не добавлено. Поведение элемента описывает документация dialog.

Проверьте окно перед подключением к проекту

Откройте базовый index.html в браузере. Для AJAX запустите локальный веб-сервер: открытие через file:// не подходит для такого запроса. Встроенный сервер Python раздаст HTML-фрагменты из папки проекта.

Для этого способа понадобится установленный Python 3.

Откройте PowerShell в Windows или приложение терминала в macOS и Linux. В Windows окно PowerShell можно открыть через Win+R: введите powershell и нажмите Enter.

  1. Проверьте Python командой py --version в Windows или python3 --version в macOS и Linux. Терминал должен вывести название Python и номер версии. Если команда не найдена, попробуйте python --version; используйте этот вариант дальше, только если он выводит Python 3.
  2. Перейдите в папку, содержащую index.html. Введите cd, пробел и полный путь к папке в кавычках, затем нажмите Enter. Например, для папки C:\login-popup команда выглядит так: cd "C:\login-popup". Подставьте путь к своему проекту.
  3. Запустите сервер командой py -m http.server 8000 --bind 127.0.0.1 в Windows. В macOS и Linux используйте python3 -m http.server 8000 --bind 127.0.0.1. Если проверка сработала только с python, замените первое слово команды на python. Терминал выведет сообщение о запуске сервера на порту 8000.
  4. Введите в адресной строке браузера http://127.0.0.1:8000/index.html. Должна открыться страница со ссылками входа и регистрации. Оставьте терминал с сервером открытым.
  5. Откройте http://127.0.0.1:8000/ajax/login-form.html в отдельной вкладке. Браузер должен показать заголовок и поля входа без затемнения. Аналогично откройте http://127.0.0.1:8000/ajax/registration-form.html: появится форма регистрации.

Если Python отсутствует, используйте уже работающий веб-сервер вашего проекта. Скопируйте файлы примера в отдельную папку внутри его каталога сайта. Откройте index.html по HTTP-адресу этой папки и проверьте оба HTML-фрагмента по относительным путям ajax/login-form.html и ajax/registration-form.html.

Если сервер сообщает, что порт занят, замените 8000 на 8001. В адресах браузера тоже укажите 8001. Ошибка 404 при открытии фрагмента означает неверный путь или имя файла: папка ajax должна находиться рядом с index.html.

Вернитесь на страницу index.html и проверьте поведение окна:

  1. Нажмите «Войти». Должно появиться затемнение, а курсор – оказаться в логине.
  2. Пройдите форму клавишами Tab и Shift+Tab. Фокус должен оставаться внутри панели, включая кнопку закрытия.
  3. Закройте окно кнопкой. Фокус должен вернуться на ссылку, прокрутка страницы – восстановиться.
  4. Откройте окно снова и нажмите Escape. Панель должна исчезнуть без перехода по ссылке.
  5. Нажмите на затемненный фон. Окно должно закрыться; нажатие внутри формы должно оставить его открытым.
  6. Уменьшите ширину области просмотра до 320px. Панель должна помещаться по ширине, а содержимое – прокручиваться по высоте.
  7. Введите логин Abc12345. Проверка должна принять 8 символов; варианты длиной 7 и 21 символ должны отклоняться.
  8. В регистрации введите пароль Abc12345 и другое подтверждение. Браузер должен запретить отправку до совпадения значений.
  9. Переключите вход и регистрацию через AJAX. Новое поле должно получить фокус, а кнопка закрытия – сохранить работу.
  10. Временно переименуйте файл AJAX-формы. При открытии должна появиться ошибка со ссылкой на отдельную страницу.

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

Если окно не открывается, посмотрите консоль браузера: сообщение $ is not defined указывает на проблему подключения jQuery. Проверьте наличие assets/jquery.js и порядок подключения скриптов.

После проверки остановите локальный сервер сочетанием Ctrl+C в его терминале. Сервер Python раздает статические файлы и не выполняет PHP. Не используйте его для публикации сайта или проверки серверной авторизации.

Файлы login.php и register.php в примере обозначают ваши серверные обработчики. Пока их нет, отправка формы не выполнит вход или регистрацию. Перед публикацией добавьте серверную валидацию, защиту от CSRF и перебора паролей, безопасное хранение паролей и HTTPS.

Какой вариант окна выбрать для проекта

Используйте jQuery-вариант, если библиотека уже обслуживает интерфейс вашего сайта. AJAX-подгрузка пригодится для переключения форм внутри общей оболочки. Для отдельного нового окна нативный dialog сократит объем собственного кода управления фокусом.

Совет от нашей редакции: перед публикацией пройдите весь вход без мыши. Если фокус теряется или кнопка недоступна, исправьте это до добавления анимации.

Комментарии

Оставьте свой комментарий
В
Валерий

Мне, как новичку в этих технологиях, статья показалась очень интересной и полезной, спасибо!
Но...

1. Если на главной странице разместить не одну, как представлено, а две разные формы, то не понятно как в ul-li-ссылках они различаются (на самом деле, они отображаются одновременно обе при клике на любую ссылку).

2. Если же не размещать их на странице вовсе (для ajax-загрузки), то как должно быть подготовлено для них место? В виде пустого элемента div class="login-content" ? Что-то не получается и так. При клике на любую ссылку отображается пустое окно без формы.
Правильно ли я понял, что в этом варианте нужно в корне сервера создать папку ajax и разместить в ней html-файлы этих двух форм? Тогда почему на эти файлы из главной страницы показаны ссылки без упоминания этой папки ajax?