From e939f68888b391903070a9f02c4a8932a0a79cdb Mon Sep 17 00:00:00 2001 From: Nikolaengel Date: Mon, 29 Dec 2025 21:23:50 +0300 Subject: [PATCH 01/14] docs: selectors --- docs/basic-guides/selectors.mdx | 602 ++++++++++++++++++ .../current/basic-guides/selectors.mdx | 602 ++++++++++++++++++ package-lock.json | 391 +++--------- 3 files changed, 1281 insertions(+), 314 deletions(-) create mode 100644 docs/basic-guides/selectors.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx diff --git a/docs/basic-guides/selectors.mdx b/docs/basic-guides/selectors.mdx new file mode 100644 index 00000000..8ecb8390 --- /dev/null +++ b/docs/basic-guides/selectors.mdx @@ -0,0 +1,602 @@ +# Селекторы + +Testplane предоставляет множество способов для поиска элементов на странице браузера с помощью селекторов. Для этого используются библиотеки `WebDriverIO` и `testing-library`. Селекторы позволяют точно идентифицировать элементы интерфейса, что необходимо для автоматизации тестирования. + +## WebDriverIO + +WebDriverIO — это Node.js-библиотека для автоматизации браузеров, которая реализует протокол WebDriver (W3C стандарт) В Testplane она используется для управления браузерами и взаимодействия с веб-элементами. + +### CSS селекторы + +#### По классу + +Чтобы найти элемент на странице по классу, используйте селектор `".class-name"`. + +```javascript +// Поиск кнопки с классом "btn-primary" +const button = await browser.$(".btn-primary"); +await button.click(); + +// Поиск нескольких элементов +const menuItems = await browser.$$(".nav-item"); +console.log("Количество пунктов меню: ${menuItems.length}"); +``` +Стоит использовать, если: + +- класс является стабильным и не генерируется динамически; +- нужен быстрый и простой способ найти элемент; +- класс семантически описывает элемент (например, `.error-message`, `.success-banner`); +- вы работаете с компонентными библиотеками, где классы являются частью API. + +#### По id + +Чтобы найти элемент по `id`, используйте селектор вида `"#id"`. + +```javascript +// Поиск формы id +const loginForm = await browser.$("#login-form"); +const isDisplayed = await loginForm.isDisplayed(); +``` +Стоит использовать, если: + +- вы работаете с критически важными элементами (формы, модальные окна, главные контейнеры); +- `id` является частью публичного API компонента; +- нужна максимальная производительность селектора (`id` — самый быстрый селектор). + +#### По типу атрибута + +Для поиска элемента по атрибуту, используйте селектор вида `input[type="name"]`. + +```javascript +// Поиск всех чекбоксов +const checkboxes = await browser.$$("input[type="checkbox"]"); +for (const checkbox of checkboxes) { + await checkbox.click(); +} + +// Поиск email input +const emailInput = await browser.$("input[type="email"]"); +await emailInput.setValue("test@example.com"); + +// Поиск скрытых полей +const hiddenField = await browser.$("input[type="hidden"][name="csrf_token"]"); +const csrfValue = await hiddenField.getValue(); +``` + +Стоит использовать, если: + +- нужно найти все элементы определённого типа (все чекбоксы, все радиокнопки); +- вы тестируете формы и валидацию; +- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`); +- вам нужно убедиться, что используется правильный тип поля для `accessibility`; +- вы тестируете различное поведение для разных типов полей. + +#### CSS-селекторы по атрибуту data-testid + +Для поиска элементов, которые помечены дял тестирования, используйте селекторы по атрибуту `data-testid`. + +```javascript +// Поиск элемента по data-testid +const userAvatar = await browser.$("[data-testid="user-avatar"]"); +await userAvatar.waitForDisplayed({ timeout: 5000 }); + +// Клик по кнопке с data-testid +const deleteButton = await browser.$("[data-testid="delete-user-btn"]"); +await deleteButton.click(); + +// Получение текста из элемента +const errorMessage = await browser.$("[data-testid="error-notification"]"); +const text = await errorMessage.getText(); +expect(text).toContain("Ошибка валидации"); +``` +Стоит использовать, если: + +- создаёте селекторы специально для тестирования; +- нужна стабильность селекторов независимо от изменений UI/стилей; +- работаете в команде, где дизайнеры часто меняют классы и структуру; +- хотите явно пометить элементы, доступные для тестирования; + +#### CSS-комбинированные селекторы + +Комбинированные селекторы позволяют находить элементы в определённом контексте. + +```javascript +// Потомок: кнопка внутри модального окна +const modalButton = await browser.$(".modal .close-button"); +await modalButton.click(); + +// Прямой потомок: первый уровень вложенности +const navItem = await browser.$(".navigation > .nav-item"); + +// Соседний элемент +const errorLabel = await browser.$("input.invalid + .error-message"); +const errorText = await errorLabel.getText(); + +// Сложная комбинация +const activeTab = await browser.$("ul.tabs > li.active > a[href^="#"]"); +``` + +Стоит использовать, если: + +- нужно найти элемент в определённом контексте; +- работаете с повторяющейся структурой; +- нужно убедиться в правильной вложенности элементов; +- простые селекторы слишком неспецифичны; +- тестируете связанные элементы. + +#### CSS-псевдоселекторы + +Псевдоселекторы позволяют выбирать элементы на основе их положения или состояния. + +```javascript +// Первый элемент списка +const firstItem = await browser.$("ul.menu > li:first-child"); +await firstItem.click(); + +// Последний элемент +const lastItem = await browser.$(".breadcrumb > li:last-child"); +const currentPage = await lastItem.getText(); + +// N-ый элемент (третий пункт меню) +const thirdItem = await browser.$(".menu-item:nth-child(3)"); + +// Каждый второй элемент (чётные) +const evenRows = await browser.$$("table tr:nth-child(even)"); + +// Отключённые элементы +const disabledButtons = await browser.$$("button:disabled"); +expect(disabledButtons.length).toBe(2); + +// Проверенные чекбоксы +const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); +``` + +Стоит использовать, если: + +- тестируете позиционирование элементов; +- проверяете состояния элементов (`disabled`, `checked`, `focus`); +- работаете с таблицами и нужно выбрать определённую строку или столбец; +- тестируете паттерны (чётные/нечётные строки для `zebra-striping`); +- нужно найти элемент по его позиции, когда нет других идентификаторов. + +### XPath селекторы + +#### По тексту элемента + +Чтобы найти элемент по содержащемуся в нем тексту, используйте селектор `//element[text()="text"]`. + +```javascript +// Точное совпадение текста +const loginButton = await browser.$("//button[text()="Войти в систему"]"); +await loginButton.click(); + +// Частичное совпадение +const successMessage = await browser.$("//div[contains(text(), "успешно")]"); +await successMessage.waitForDisplayed(); + +// Текст с пробелами и переносами +const heading = await browser.$("//h1[normalize-space()="Добро пожаловать"]"); + +// Поиск по тексту в дочернем элементе +const card = await browser.$("//div[@class="card"][.//h3[text()="Premium план"]]"); + +// Case-insensitive поиск (XPath 2.0) +const link = await browser.$("//a[contains(translate(text(), "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"), "поиск")]"); +``` + +Стоит использовать, если: + +- текст элемента уникален и стабилен (названия кнопок, заголовки); +- нет других идентификаторов (нет `data-testid`, `id`, классов); +- тестируете, что правильный текст отображается в правильном месте; +- работаете с динамически генерируемым контентом, где единственный стабильный элемент — текст; +- нужно найти элемент, содержащий определённый текст внутри себя или в дочерних элементах. + +#### По атрибутам + +Для поиска элемента по атрибуту, используйте селектор вида `//element[@type="atribute"]`. + +```javascript +// Поиск по одному атрибуту +const nameInput = await browser.$("//input[@name="username"]"); +await nameInput.setValue("john_doe"); + +// Множественные условия (AND) +const activeModal = await browser.$("//div[@class="modal" and @data-visible="true"]"); + +// Условие OR +const submitBtn = await browser.$("//button[@type="submit" or @class="btn-submit"]"); + +// Поиск по началу значения атрибута +const imageJpg = await browser.$("//img[starts-with(@src, "/images/")]"); + +// Поиск по концу значения атрибута +const pdfLink = await browser.$("//a[substring(@href, string-length(@href) - 3) = ".pdf"]"); + +// Поиск по частичному совпадению атрибута +const dataElement = await browser.$("//div[contains(@data-component, "user-profile")]"); + +// NOT условие +const notDisabledButton = await browser.$("//button[not(@disabled)]"); +``` + +Стоит использовать, если: + +- нужны сложные условия поиска (комбинации атрибутов); +- работаете с динамическими атрибутами (`data`-атрибуты с переменными значениями); +- нужна гибкость в поиске (частичные совпадения, начало/конец строки); +- CSS-селекторы не могут выразить нужную логику; +- нужно найти элемент по отсутствию атрибута. + +#### Навигация по DOM + +Исползуя XPath, вы можете навигировать по DOM-дереву. + +```javascript +// Прямой родитель +const parentDiv = await browser.$("//input[@name="email"]/.."); + +// Предок с условием +const formContainer = await browser.$("//input[@name="email"]/ancestor::form[@id="registration"]"); + +// Следующий сиблинг +const errorLabel = await browser.$("//input[@class="invalid"]/following-sibling::span[@class="error"][1]"); + +// Предыдущий сиблинг +const label = await browser.$("//input[@name="password"]/preceding-sibling::label[1]"); + +// Все потомки +const allInputs = await browser.$$("//form[@id="checkout"]//input"); + +// Прямые дети +const directChildren = await browser.$$("//ul[@class="menu"]/li"); + +// Поиск «дяди» элемента (родитель -> сиблинг родителя) +const siblingSection = await browser.$("//h2[text()="Контакты"]/../following-sibling::section[1]"); +``` + +Стоит использовать, если: + +- нужно найти элемент относительно другого известного элемента; +- структура DOM сложна, но относительные позиции стабильны; +- тестируете связанные элементы (label и input, ошибка рядом с полем); +- нужно подняться вверх по DOM-дереву от найденного элемента; +- работаете с семантической структурой HTML (заголовок и следующая за ним секция). + +#### XPath: индексы и позиции + +XPath позволяет выбирать элементы по их позиции в наборе результатов. + +```javascript +// Первый элемент в результатах XPath +const firstButton = await browser.$("(//button[@class="action"])[1]"); +await firstButton.click(); + +// Последний элемент +const lastItem = await browser.$("(//li[@class="menu-item"])[last()]"); + +// Второй элемент +const secondRow = await browser.$("(//table[@id="results"]//tr)[2]"); + +// Предпоследний +const secondToLast = await browser.$("(//div[@class="card"])[last()-1]"); + +// Диапазон элементов (все кроме первого) +const allButFirst = await browser.$$("(//li[@class="item"])[position() > 1]"); + +// Каждый третий элемент +const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 = 0]"); +``` + +Стоит использовать, если: + +- нужен доступ к элементу по его позиции в наборе результатов; +- тестируете пагинацию или списки с определённым порядком; +- работаете с таблицами и нужна конкретная строка; +- нужен первый или последний элемент среди нескольких одинаковых; +- тестируете сортировку (проверка, что элемент на правильной позиции). + +### Селекторы по Link Text + +Селекторы по содержащемуся внутри тексту позволяют находить ссылки `()` по их тексту. Используйте `="text"`, чтобы найти элемент с точным текстом и `*="text"` для поиска по частичному совпадению текста. + +```javascript +// Полное совпадение текста ссылки +const loginLink = await browser.$("=Войти в систему"); +await loginLink.click(); + +// Частичное совпадение +const docsLink = await browser.$("*=Документация"); +await docsLink.click(); +``` +Стоит использовать, если: + +- работаете с навигационными ссылками с уникальным текстом; +- тестируете меню и навигацию сайта; +- текст ссылки является частью требований и не должен меняться; +- нужна простота и читаемость теста; +- тестируете наличие ссылок с правильным текстом на странице. + +### Селекторы по имени тега + +Чтобы найти элемент по их HTML-тегу, используйте селектор по имени тега. + +```javascript +// Поиск первой кнопки на странице +const button = await browser.$("button"); +await button.click(); + +// Все параграфы +const paragraphs = await browser.$$("p"); +const textsArray = await Promise.all( + paragraphs.map(p => p.getText()) +); + +// Все изображения +const images = await browser.$$("img"); +for (const img of images) { + const alt = await img.getAttribute("alt"); + expect(alt).not.toBe(""); // проверка accessibility +} + +// Форма +const form = await browser.$("form"); +await form.waitForDisplayed(); + +// Таблица +const table = await browser.$("table"); +const rows = await table.$$("tr"); +``` + +Стоит использовать, если: + +- на странице один элемент данного типа (например, единственная форма); +- нужно получить все элементы определённого типа для массовой проверки; +- тестируете семантичность HTML (наличие правильных тегов); +- работаете с базовой HTML-структурой (`form`, `table`, `ul`); +- проводите accessibility-аудит (проверка всех `img` на наличие `alt`). + +### React селекторы + +Для поиска экомпонентов в React-приложении по их имени, используйте react-селекторы, например `react$("MyButton")` найдет компонент `MyButton`, а `react$("Button" { props: { variant: "primary", size: "large"}})` найдет кнопку с определенными параметрами. + +```javascript +// Поиск React-компонента по имени +const myButton = await browser.react$("MyButton"); +await myButton.click(); + +// С фильтрацией по параметрами +const primaryButton = await browser.react$("Button", { + props: { variant: "primary", size: "large" } +}); + +// С фильтрацией по state +const openModal = await browser.react$("Modal", { + state: { isOpen: true, activeTab: 'settings' } +}); + +// Все экземпляры компонента +const allCards = await browser.react$$("ProductCard"); +console.log("Найдено карточек: ${allCards.length}"); + +// Вложенный поиск +const form = await browser.react$("CheckoutForm"); +const submitButton = await form.react$("SubmitButton"); + +// С комплексными параметрами +const userProfile = await browser.react$("UserProfile", { + props: { + user: { id: 123, role: "admin" }, + editable: true + } +}); +``` + +Стоит использовать, если: + +- работаете с React-приложением и имеете доступ к исходному коду; +- нужно найти компонент по его параметрам или `state`; +- тестируете, что компонент рендерится с правильными данными; +- структура `DOM` может меняться, но API компонента стабилен; +- нужна глубокая интеграция с React DevTools. + +### Shadow DOM селекторы + +Shadow DOM селекторы позволяют работать с элементами внутри Shadow DOM — инкапсулированной части DOM-дерева. Например, если у вас есть кастомный элемент `my-custom-element`, вы можете найти кнопку внутри его Shadow DOM с помощью `shadow$("button")`. + +```javascript +// Простой доступ в Shadow DOM +const customElement = await browser.$("my-custom-element"); +const button = await customElement.shadow$("button"); +await button.click(); + +// Множественные элементы в Shadow DOM +const slotElements = await customElement.shadow$$(".slot-item"); +``` + +Стоит использовать, если: + +- работаете с Web Components и Custom Elements; +- приложение использует Shadow DOM для инкапсуляции стилей; +- тестируете компоненты из сторонних библиотек (Lit, Stencil, native Web Components); +- нужен доступ к элементам внутри shadow root; +- работаете с дизайн-системой на базе Web Components. + +## Testing-library + +Testing Library — это адаптер популярной философии Testing Library для Testplane. Она предоставляет селекторы, ориентированные на пользовательский опыт (как пользователи находят элементы). + +### ByRole + +`getByRole` — основной метод в Testing Library, который позволяет находить элементы по их ARIA-ролям. Например, если вы используете метод `screen.getByRole("button", { name: /submit/i })`, то найдете кнопку с текстом, содержащим `submit`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск кнопки +const submitButton = screen.getByRole("button", { name: /submit/i }); +await userEvent.click(submitButton); + +// Поиск текстового поля +const emailInput = screen.getByRole('textbox', { name: /email/i }); +await userEvent.type(emailInput, "test@example.com"); + +// Поиск чекбокса +const agreeCheckbox = screen.getByRole("checkbox", { name: /agree to terms/i }); +await userEvent.click(agreeCheckbox); +``` + +Стоит использовать, если: + +- для любых интерактивных элементов (кнопки, ссылки, поля ввода); +- для структурных элементов (`navigation`, `main`, `header`, `footer`); +- для форм и их элементов (`radio`, `checkbox`, `combobox`); +- для заголовков и важных текстовых элементов; +- для списков и таблиц. + + +### ByLabelText + +Для поиска элементов форм по тексту их меток (`label`), используйте метод `getByLabelText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по тексту label (полное совпадение) +const emailInput = screen.getByLabelText("Email Address"); +await userEvent.type(emailInput, "user@example.com"); + +// Поиск с регулярным выражением (частичное совпадение, case-insensitive) +const passwordInput = screen.getByLabelText(/password/i); +await userEvent.type(passwordInput, "secure123"); +``` + +Стоит использовать, если: + +- работаете с формами; +- нужно найти input, select, textarea по связанной метке; +- занимаетесь тестированием доступности (наличие `label` обязательно для доступности); +- метка уникальна и описательна. + +### ByPlaceholderText + +Чтобы найти поле ввода по тексту `placeholder`, используйте селектор `getByPlaceholderText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по placeholder +const searchInput = screen.getByPlaceholderText("Search..."); +await userEvent.type(searchInput, "testing library"); + +// С регулярным выражением +const emailInput = screen.getByPlaceholderText(/enter.*email/i); +await userEvent.type(emailInput, "test@example.com"); +``` + +Стоит использовать, если: + +- у поля нет `label`, но есть `placeholder`; +- занимаетесь тестированием legacy-кода, где `placeholder` используется вместо `label`; +- `placeholder` достаточно описателен и уникален. + +### ByText + +Чтобы найти текстовый элемент по его содержимому, используйте метод `getByText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по точному тексту +const heading = screen.getByText("Welcome to our application"); +expect(heading).toBeInTheDocument(); + +// С регулярным выражением (частичное совпадение) +const errorMessage = screen.getByText(/error.*occurred/i); +expect(errorMessage).toHaveClass("error"); +``` + +Стоит использовать, если: + +- необходимо найти неинтерактивные текстовые элементов (параграфы, заголовки, уведомления); +- занимаетесь проверкой отображения текста на странице; +- текст уникален и является частью требований. + +### ByDisplayValue + +Для поиска элемента по их текущему значению, используйте метод `getByDisplayValue`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск input с конкретным значением +const emailInput = screen.getByDisplayValue("user@example.com"); +expect(emailInput).toBeInTheDocument(); + +// Поиск с регулярным выражением +const searchInput = screen.getByDisplayValue(/search query/i); +``` + +Стоит использовать, если: + +- необходимо протестировать предзаполненных форм (edit forms, profile pages); +- нужно проверить установку корректного значения после действия; +- необходимо найти элемент по его текущему значению, а не по label. + +### ByAltText + +Для поиска изображений по тексту `alt`, используйте метод `getByAltText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск изображения по alt тексту +const logo = screen.getByAltText("Company Logo"); +expect(logo).toBeInTheDocument(); +expect(logo).toHaveAttribute("src", "/images/logo.png"); +``` + +Стоит использовать, если: + +- нужно работать с изображениями (``, ``, ``); +- необходимо проверить доступность изображений (наличие `alt` обязательно); +- `alt`-текст достаточно описателен и уникален. + +### ByTitle + +Чтобы найти элемент по атрибуту title, используйте метод getByTitle. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск элемента по title атрибуту +const closeButton = screen.getByTitle("Close dialog"); +await userEvent.click(closeButton); +``` + +Стоит использовать, если: + +- необходимо работать с элементами, которые используют `title` для `tooltips`; +- нужно протестировать `iframe`-элементы (`title` обязателен для доступности); +- нужно взаимодействовать с `SVG`-элементами (`title` внутри `SVG` для описания). + +### ByTestId + +`getByTestId` используется как последний вариант, когда другие методы не подходят. Например, если вы используете `screen.getByTestId("submit-button")`, то найдте элемент с атрибутом `data-testid="submit-button"` + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Базовое использование +const submitButton = screen.getByTestId("submit-button"); +await userEvent.click(submitButton); +``` + diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx new file mode 100644 index 00000000..8ecb8390 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx @@ -0,0 +1,602 @@ +# Селекторы + +Testplane предоставляет множество способов для поиска элементов на странице браузера с помощью селекторов. Для этого используются библиотеки `WebDriverIO` и `testing-library`. Селекторы позволяют точно идентифицировать элементы интерфейса, что необходимо для автоматизации тестирования. + +## WebDriverIO + +WebDriverIO — это Node.js-библиотека для автоматизации браузеров, которая реализует протокол WebDriver (W3C стандарт) В Testplane она используется для управления браузерами и взаимодействия с веб-элементами. + +### CSS селекторы + +#### По классу + +Чтобы найти элемент на странице по классу, используйте селектор `".class-name"`. + +```javascript +// Поиск кнопки с классом "btn-primary" +const button = await browser.$(".btn-primary"); +await button.click(); + +// Поиск нескольких элементов +const menuItems = await browser.$$(".nav-item"); +console.log("Количество пунктов меню: ${menuItems.length}"); +``` +Стоит использовать, если: + +- класс является стабильным и не генерируется динамически; +- нужен быстрый и простой способ найти элемент; +- класс семантически описывает элемент (например, `.error-message`, `.success-banner`); +- вы работаете с компонентными библиотеками, где классы являются частью API. + +#### По id + +Чтобы найти элемент по `id`, используйте селектор вида `"#id"`. + +```javascript +// Поиск формы id +const loginForm = await browser.$("#login-form"); +const isDisplayed = await loginForm.isDisplayed(); +``` +Стоит использовать, если: + +- вы работаете с критически важными элементами (формы, модальные окна, главные контейнеры); +- `id` является частью публичного API компонента; +- нужна максимальная производительность селектора (`id` — самый быстрый селектор). + +#### По типу атрибута + +Для поиска элемента по атрибуту, используйте селектор вида `input[type="name"]`. + +```javascript +// Поиск всех чекбоксов +const checkboxes = await browser.$$("input[type="checkbox"]"); +for (const checkbox of checkboxes) { + await checkbox.click(); +} + +// Поиск email input +const emailInput = await browser.$("input[type="email"]"); +await emailInput.setValue("test@example.com"); + +// Поиск скрытых полей +const hiddenField = await browser.$("input[type="hidden"][name="csrf_token"]"); +const csrfValue = await hiddenField.getValue(); +``` + +Стоит использовать, если: + +- нужно найти все элементы определённого типа (все чекбоксы, все радиокнопки); +- вы тестируете формы и валидацию; +- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`); +- вам нужно убедиться, что используется правильный тип поля для `accessibility`; +- вы тестируете различное поведение для разных типов полей. + +#### CSS-селекторы по атрибуту data-testid + +Для поиска элементов, которые помечены дял тестирования, используйте селекторы по атрибуту `data-testid`. + +```javascript +// Поиск элемента по data-testid +const userAvatar = await browser.$("[data-testid="user-avatar"]"); +await userAvatar.waitForDisplayed({ timeout: 5000 }); + +// Клик по кнопке с data-testid +const deleteButton = await browser.$("[data-testid="delete-user-btn"]"); +await deleteButton.click(); + +// Получение текста из элемента +const errorMessage = await browser.$("[data-testid="error-notification"]"); +const text = await errorMessage.getText(); +expect(text).toContain("Ошибка валидации"); +``` +Стоит использовать, если: + +- создаёте селекторы специально для тестирования; +- нужна стабильность селекторов независимо от изменений UI/стилей; +- работаете в команде, где дизайнеры часто меняют классы и структуру; +- хотите явно пометить элементы, доступные для тестирования; + +#### CSS-комбинированные селекторы + +Комбинированные селекторы позволяют находить элементы в определённом контексте. + +```javascript +// Потомок: кнопка внутри модального окна +const modalButton = await browser.$(".modal .close-button"); +await modalButton.click(); + +// Прямой потомок: первый уровень вложенности +const navItem = await browser.$(".navigation > .nav-item"); + +// Соседний элемент +const errorLabel = await browser.$("input.invalid + .error-message"); +const errorText = await errorLabel.getText(); + +// Сложная комбинация +const activeTab = await browser.$("ul.tabs > li.active > a[href^="#"]"); +``` + +Стоит использовать, если: + +- нужно найти элемент в определённом контексте; +- работаете с повторяющейся структурой; +- нужно убедиться в правильной вложенности элементов; +- простые селекторы слишком неспецифичны; +- тестируете связанные элементы. + +#### CSS-псевдоселекторы + +Псевдоселекторы позволяют выбирать элементы на основе их положения или состояния. + +```javascript +// Первый элемент списка +const firstItem = await browser.$("ul.menu > li:first-child"); +await firstItem.click(); + +// Последний элемент +const lastItem = await browser.$(".breadcrumb > li:last-child"); +const currentPage = await lastItem.getText(); + +// N-ый элемент (третий пункт меню) +const thirdItem = await browser.$(".menu-item:nth-child(3)"); + +// Каждый второй элемент (чётные) +const evenRows = await browser.$$("table tr:nth-child(even)"); + +// Отключённые элементы +const disabledButtons = await browser.$$("button:disabled"); +expect(disabledButtons.length).toBe(2); + +// Проверенные чекбоксы +const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); +``` + +Стоит использовать, если: + +- тестируете позиционирование элементов; +- проверяете состояния элементов (`disabled`, `checked`, `focus`); +- работаете с таблицами и нужно выбрать определённую строку или столбец; +- тестируете паттерны (чётные/нечётные строки для `zebra-striping`); +- нужно найти элемент по его позиции, когда нет других идентификаторов. + +### XPath селекторы + +#### По тексту элемента + +Чтобы найти элемент по содержащемуся в нем тексту, используйте селектор `//element[text()="text"]`. + +```javascript +// Точное совпадение текста +const loginButton = await browser.$("//button[text()="Войти в систему"]"); +await loginButton.click(); + +// Частичное совпадение +const successMessage = await browser.$("//div[contains(text(), "успешно")]"); +await successMessage.waitForDisplayed(); + +// Текст с пробелами и переносами +const heading = await browser.$("//h1[normalize-space()="Добро пожаловать"]"); + +// Поиск по тексту в дочернем элементе +const card = await browser.$("//div[@class="card"][.//h3[text()="Premium план"]]"); + +// Case-insensitive поиск (XPath 2.0) +const link = await browser.$("//a[contains(translate(text(), "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"), "поиск")]"); +``` + +Стоит использовать, если: + +- текст элемента уникален и стабилен (названия кнопок, заголовки); +- нет других идентификаторов (нет `data-testid`, `id`, классов); +- тестируете, что правильный текст отображается в правильном месте; +- работаете с динамически генерируемым контентом, где единственный стабильный элемент — текст; +- нужно найти элемент, содержащий определённый текст внутри себя или в дочерних элементах. + +#### По атрибутам + +Для поиска элемента по атрибуту, используйте селектор вида `//element[@type="atribute"]`. + +```javascript +// Поиск по одному атрибуту +const nameInput = await browser.$("//input[@name="username"]"); +await nameInput.setValue("john_doe"); + +// Множественные условия (AND) +const activeModal = await browser.$("//div[@class="modal" and @data-visible="true"]"); + +// Условие OR +const submitBtn = await browser.$("//button[@type="submit" or @class="btn-submit"]"); + +// Поиск по началу значения атрибута +const imageJpg = await browser.$("//img[starts-with(@src, "/images/")]"); + +// Поиск по концу значения атрибута +const pdfLink = await browser.$("//a[substring(@href, string-length(@href) - 3) = ".pdf"]"); + +// Поиск по частичному совпадению атрибута +const dataElement = await browser.$("//div[contains(@data-component, "user-profile")]"); + +// NOT условие +const notDisabledButton = await browser.$("//button[not(@disabled)]"); +``` + +Стоит использовать, если: + +- нужны сложные условия поиска (комбинации атрибутов); +- работаете с динамическими атрибутами (`data`-атрибуты с переменными значениями); +- нужна гибкость в поиске (частичные совпадения, начало/конец строки); +- CSS-селекторы не могут выразить нужную логику; +- нужно найти элемент по отсутствию атрибута. + +#### Навигация по DOM + +Исползуя XPath, вы можете навигировать по DOM-дереву. + +```javascript +// Прямой родитель +const parentDiv = await browser.$("//input[@name="email"]/.."); + +// Предок с условием +const formContainer = await browser.$("//input[@name="email"]/ancestor::form[@id="registration"]"); + +// Следующий сиблинг +const errorLabel = await browser.$("//input[@class="invalid"]/following-sibling::span[@class="error"][1]"); + +// Предыдущий сиблинг +const label = await browser.$("//input[@name="password"]/preceding-sibling::label[1]"); + +// Все потомки +const allInputs = await browser.$$("//form[@id="checkout"]//input"); + +// Прямые дети +const directChildren = await browser.$$("//ul[@class="menu"]/li"); + +// Поиск «дяди» элемента (родитель -> сиблинг родителя) +const siblingSection = await browser.$("//h2[text()="Контакты"]/../following-sibling::section[1]"); +``` + +Стоит использовать, если: + +- нужно найти элемент относительно другого известного элемента; +- структура DOM сложна, но относительные позиции стабильны; +- тестируете связанные элементы (label и input, ошибка рядом с полем); +- нужно подняться вверх по DOM-дереву от найденного элемента; +- работаете с семантической структурой HTML (заголовок и следующая за ним секция). + +#### XPath: индексы и позиции + +XPath позволяет выбирать элементы по их позиции в наборе результатов. + +```javascript +// Первый элемент в результатах XPath +const firstButton = await browser.$("(//button[@class="action"])[1]"); +await firstButton.click(); + +// Последний элемент +const lastItem = await browser.$("(//li[@class="menu-item"])[last()]"); + +// Второй элемент +const secondRow = await browser.$("(//table[@id="results"]//tr)[2]"); + +// Предпоследний +const secondToLast = await browser.$("(//div[@class="card"])[last()-1]"); + +// Диапазон элементов (все кроме первого) +const allButFirst = await browser.$$("(//li[@class="item"])[position() > 1]"); + +// Каждый третий элемент +const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 = 0]"); +``` + +Стоит использовать, если: + +- нужен доступ к элементу по его позиции в наборе результатов; +- тестируете пагинацию или списки с определённым порядком; +- работаете с таблицами и нужна конкретная строка; +- нужен первый или последний элемент среди нескольких одинаковых; +- тестируете сортировку (проверка, что элемент на правильной позиции). + +### Селекторы по Link Text + +Селекторы по содержащемуся внутри тексту позволяют находить ссылки `()` по их тексту. Используйте `="text"`, чтобы найти элемент с точным текстом и `*="text"` для поиска по частичному совпадению текста. + +```javascript +// Полное совпадение текста ссылки +const loginLink = await browser.$("=Войти в систему"); +await loginLink.click(); + +// Частичное совпадение +const docsLink = await browser.$("*=Документация"); +await docsLink.click(); +``` +Стоит использовать, если: + +- работаете с навигационными ссылками с уникальным текстом; +- тестируете меню и навигацию сайта; +- текст ссылки является частью требований и не должен меняться; +- нужна простота и читаемость теста; +- тестируете наличие ссылок с правильным текстом на странице. + +### Селекторы по имени тега + +Чтобы найти элемент по их HTML-тегу, используйте селектор по имени тега. + +```javascript +// Поиск первой кнопки на странице +const button = await browser.$("button"); +await button.click(); + +// Все параграфы +const paragraphs = await browser.$$("p"); +const textsArray = await Promise.all( + paragraphs.map(p => p.getText()) +); + +// Все изображения +const images = await browser.$$("img"); +for (const img of images) { + const alt = await img.getAttribute("alt"); + expect(alt).not.toBe(""); // проверка accessibility +} + +// Форма +const form = await browser.$("form"); +await form.waitForDisplayed(); + +// Таблица +const table = await browser.$("table"); +const rows = await table.$$("tr"); +``` + +Стоит использовать, если: + +- на странице один элемент данного типа (например, единственная форма); +- нужно получить все элементы определённого типа для массовой проверки; +- тестируете семантичность HTML (наличие правильных тегов); +- работаете с базовой HTML-структурой (`form`, `table`, `ul`); +- проводите accessibility-аудит (проверка всех `img` на наличие `alt`). + +### React селекторы + +Для поиска экомпонентов в React-приложении по их имени, используйте react-селекторы, например `react$("MyButton")` найдет компонент `MyButton`, а `react$("Button" { props: { variant: "primary", size: "large"}})` найдет кнопку с определенными параметрами. + +```javascript +// Поиск React-компонента по имени +const myButton = await browser.react$("MyButton"); +await myButton.click(); + +// С фильтрацией по параметрами +const primaryButton = await browser.react$("Button", { + props: { variant: "primary", size: "large" } +}); + +// С фильтрацией по state +const openModal = await browser.react$("Modal", { + state: { isOpen: true, activeTab: 'settings' } +}); + +// Все экземпляры компонента +const allCards = await browser.react$$("ProductCard"); +console.log("Найдено карточек: ${allCards.length}"); + +// Вложенный поиск +const form = await browser.react$("CheckoutForm"); +const submitButton = await form.react$("SubmitButton"); + +// С комплексными параметрами +const userProfile = await browser.react$("UserProfile", { + props: { + user: { id: 123, role: "admin" }, + editable: true + } +}); +``` + +Стоит использовать, если: + +- работаете с React-приложением и имеете доступ к исходному коду; +- нужно найти компонент по его параметрам или `state`; +- тестируете, что компонент рендерится с правильными данными; +- структура `DOM` может меняться, но API компонента стабилен; +- нужна глубокая интеграция с React DevTools. + +### Shadow DOM селекторы + +Shadow DOM селекторы позволяют работать с элементами внутри Shadow DOM — инкапсулированной части DOM-дерева. Например, если у вас есть кастомный элемент `my-custom-element`, вы можете найти кнопку внутри его Shadow DOM с помощью `shadow$("button")`. + +```javascript +// Простой доступ в Shadow DOM +const customElement = await browser.$("my-custom-element"); +const button = await customElement.shadow$("button"); +await button.click(); + +// Множественные элементы в Shadow DOM +const slotElements = await customElement.shadow$$(".slot-item"); +``` + +Стоит использовать, если: + +- работаете с Web Components и Custom Elements; +- приложение использует Shadow DOM для инкапсуляции стилей; +- тестируете компоненты из сторонних библиотек (Lit, Stencil, native Web Components); +- нужен доступ к элементам внутри shadow root; +- работаете с дизайн-системой на базе Web Components. + +## Testing-library + +Testing Library — это адаптер популярной философии Testing Library для Testplane. Она предоставляет селекторы, ориентированные на пользовательский опыт (как пользователи находят элементы). + +### ByRole + +`getByRole` — основной метод в Testing Library, который позволяет находить элементы по их ARIA-ролям. Например, если вы используете метод `screen.getByRole("button", { name: /submit/i })`, то найдете кнопку с текстом, содержащим `submit`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск кнопки +const submitButton = screen.getByRole("button", { name: /submit/i }); +await userEvent.click(submitButton); + +// Поиск текстового поля +const emailInput = screen.getByRole('textbox', { name: /email/i }); +await userEvent.type(emailInput, "test@example.com"); + +// Поиск чекбокса +const agreeCheckbox = screen.getByRole("checkbox", { name: /agree to terms/i }); +await userEvent.click(agreeCheckbox); +``` + +Стоит использовать, если: + +- для любых интерактивных элементов (кнопки, ссылки, поля ввода); +- для структурных элементов (`navigation`, `main`, `header`, `footer`); +- для форм и их элементов (`radio`, `checkbox`, `combobox`); +- для заголовков и важных текстовых элементов; +- для списков и таблиц. + + +### ByLabelText + +Для поиска элементов форм по тексту их меток (`label`), используйте метод `getByLabelText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по тексту label (полное совпадение) +const emailInput = screen.getByLabelText("Email Address"); +await userEvent.type(emailInput, "user@example.com"); + +// Поиск с регулярным выражением (частичное совпадение, case-insensitive) +const passwordInput = screen.getByLabelText(/password/i); +await userEvent.type(passwordInput, "secure123"); +``` + +Стоит использовать, если: + +- работаете с формами; +- нужно найти input, select, textarea по связанной метке; +- занимаетесь тестированием доступности (наличие `label` обязательно для доступности); +- метка уникальна и описательна. + +### ByPlaceholderText + +Чтобы найти поле ввода по тексту `placeholder`, используйте селектор `getByPlaceholderText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по placeholder +const searchInput = screen.getByPlaceholderText("Search..."); +await userEvent.type(searchInput, "testing library"); + +// С регулярным выражением +const emailInput = screen.getByPlaceholderText(/enter.*email/i); +await userEvent.type(emailInput, "test@example.com"); +``` + +Стоит использовать, если: + +- у поля нет `label`, но есть `placeholder`; +- занимаетесь тестированием legacy-кода, где `placeholder` используется вместо `label`; +- `placeholder` достаточно описателен и уникален. + +### ByText + +Чтобы найти текстовый элемент по его содержимому, используйте метод `getByText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по точному тексту +const heading = screen.getByText("Welcome to our application"); +expect(heading).toBeInTheDocument(); + +// С регулярным выражением (частичное совпадение) +const errorMessage = screen.getByText(/error.*occurred/i); +expect(errorMessage).toHaveClass("error"); +``` + +Стоит использовать, если: + +- необходимо найти неинтерактивные текстовые элементов (параграфы, заголовки, уведомления); +- занимаетесь проверкой отображения текста на странице; +- текст уникален и является частью требований. + +### ByDisplayValue + +Для поиска элемента по их текущему значению, используйте метод `getByDisplayValue`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск input с конкретным значением +const emailInput = screen.getByDisplayValue("user@example.com"); +expect(emailInput).toBeInTheDocument(); + +// Поиск с регулярным выражением +const searchInput = screen.getByDisplayValue(/search query/i); +``` + +Стоит использовать, если: + +- необходимо протестировать предзаполненных форм (edit forms, profile pages); +- нужно проверить установку корректного значения после действия; +- необходимо найти элемент по его текущему значению, а не по label. + +### ByAltText + +Для поиска изображений по тексту `alt`, используйте метод `getByAltText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск изображения по alt тексту +const logo = screen.getByAltText("Company Logo"); +expect(logo).toBeInTheDocument(); +expect(logo).toHaveAttribute("src", "/images/logo.png"); +``` + +Стоит использовать, если: + +- нужно работать с изображениями (``, ``, ``); +- необходимо проверить доступность изображений (наличие `alt` обязательно); +- `alt`-текст достаточно описателен и уникален. + +### ByTitle + +Чтобы найти элемент по атрибуту title, используйте метод getByTitle. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск элемента по title атрибуту +const closeButton = screen.getByTitle("Close dialog"); +await userEvent.click(closeButton); +``` + +Стоит использовать, если: + +- необходимо работать с элементами, которые используют `title` для `tooltips`; +- нужно протестировать `iframe`-элементы (`title` обязателен для доступности); +- нужно взаимодействовать с `SVG`-элементами (`title` внутри `SVG` для описания). + +### ByTestId + +`getByTestId` используется как последний вариант, когда другие методы не подходят. Например, если вы используете `screen.getByTestId("submit-button")`, то найдте элемент с атрибутом `data-testid="submit-button"` + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Базовое использование +const submitButton = screen.getByTestId("submit-button"); +await userEvent.click(submitButton); +``` + diff --git a/package-lock.json b/package-lock.json index 3b0f0c20..fe2f27fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -180,6 +180,7 @@ "version": "4.23.3", "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.3.tgz", "integrity": "sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==", + "peer": true, "dependencies": { "@algolia/client-common": "4.23.3", "@algolia/requester-common": "4.23.3", @@ -306,6 +307,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -466,7 +468,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, - "peer": true, "engines": { "node": ">=6.9.0" } @@ -745,7 +746,6 @@ "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", "dev": true, - "peer": true, "dependencies": { "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-plugin-utils": "^7.20.2", @@ -765,7 +765,6 @@ "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", "dev": true, - "peer": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -782,7 +781,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.24.1.tgz", "integrity": "sha512-+0hrgGGV3xyYIjOrD/bUZk/iUwOIGuoANfRfVg1cPhYBxF+TIXSEcc42DqzBICmWsnAQ+SfKedY0bj8QD+LuMg==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-export-default-from": "^7.24.1" @@ -800,7 +798,6 @@ "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" @@ -818,7 +815,6 @@ "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-numeric-separator": "^7.10.4" @@ -836,7 +832,6 @@ "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", "dev": true, - "peer": true, "dependencies": { "@babel/compat-data": "^7.20.5", "@babel/helper-compilation-targets": "^7.20.7", @@ -857,7 +852,6 @@ "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" @@ -875,7 +869,6 @@ "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", @@ -904,7 +897,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -928,7 +920,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.24.1.tgz", "integrity": "sha512-cNXSxv9eTkGUtd0PsNMK8Yx5xeScxfpWOUAxE+ZPAXXEcAMOC3fk7LRdXq5fvpra2pLx2p1YtkAhpUbB2SwaRA==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.0" }, @@ -1004,7 +995,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1017,7 +1007,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1030,7 +1019,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1043,7 +1031,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1056,7 +1043,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1798,7 +1784,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.1.tgz", "integrity": "sha512-kDJgnPujTmAZ/9q2CN4m2/lRsUUPDvsG3+tSHWUJIzMGTt5U/b/fwWd3RO3n+5mjLrsBrVa5eKFRVSQbi3dF1w==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.0" }, @@ -1814,7 +1799,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz", "integrity": "sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.0" }, @@ -2078,6 +2062,7 @@ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/compat-data": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", @@ -2517,6 +2502,7 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.4.0.tgz", "integrity": "sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w==", + "peer": true, "dependencies": { "@babel/core": "^7.23.3", "@babel/generator": "^7.23.3", @@ -3699,7 +3685,6 @@ "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", "dev": true, - "peer": true, "engines": { "node": ">=12" } @@ -3709,7 +3694,6 @@ "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3" }, @@ -3722,7 +3706,6 @@ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, - "peer": true, "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", @@ -3738,7 +3721,6 @@ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", @@ -4023,7 +4005,6 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.6.tgz", "integrity": "sha512-647OSi6xBb8FbwFqX9zsJxOzu685AWtrOUWHfOkbKD+5LOpGORw+GQo0F9rWZnB68rLQyfKUZWJeaD00pGv5fw==", "dev": true, - "peer": true, "dependencies": { "@react-native-community/cli-clean": "12.3.6", "@react-native-community/cli-config": "12.3.6", @@ -4056,7 +4037,6 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.6.tgz", "integrity": "sha512-gUU29ep8xM0BbnZjwz9MyID74KKwutq9x5iv4BCr2im6nly4UMf1B1D+V225wR7VcDGzbgWjaezsJShLLhC5ig==", "dev": true, - "peer": true, "dependencies": { "@react-native-community/cli-tools": "12.3.6", "chalk": "^4.1.2", @@ -4068,7 +4048,6 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.6.tgz", "integrity": "sha512-JGWSYQ9EAK6m2v0abXwFLEfsqJ1zkhzZ4CV261QZF9MoUNB6h57a274h1MLQR9mG6Tsh38wBUuNfEPUvS1vYew==", "dev": true, - "peer": true, "dependencies": { "@react-native-community/cli-tools": "12.3.6", "chalk": "^4.1.2", @@ -4083,7 +4062,6 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -4093,7 +4071,6 @@ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", "dev": true, - "peer": true, "dependencies": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", @@ -4109,7 +4086,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4130,7 +4106,6 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", "dev": true, - "peer": true, "dependencies": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" @@ -4144,7 +4119,6 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, - "peer": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -4158,7 +4132,6 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, - "peer": true, "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -4172,7 +4145,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -4182,7 +4154,6 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.6.tgz", "integrity": "sha512-SjUKKsx5FmcK9G6Pb6UBFT0s9JexVStK5WInmANw75Hm7YokVvHEgtprQDz2Uvy5znX5g2ujzrkIU//T15KQzA==", "dev": true, - "peer": true, "dependencies": { "serve-static": "^1.13.1" } @@ -4192,7 +4163,6 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.6.tgz", "integrity": "sha512-fvBDv2lTthfw4WOQKkdTop2PlE9GtfrlNnpjB818MhcdEnPjfQw5YaTUcnNEGsvGomdCs1MVRMgYXXwPSN6OvQ==", "dev": true, - "peer": true, "dependencies": { "@react-native-community/cli-config": "12.3.6", "@react-native-community/cli-platform-android": "12.3.6", @@ -4217,7 +4187,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -4227,7 +4196,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^4.1.0" }, @@ -4240,7 +4208,6 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", "dev": true, - "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -4253,7 +4220,6 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.6.tgz", "integrity": "sha512-sNGwfOCl8OAIjWCkwuLpP8NZbuO0dhDI/2W7NeOGDzIBsf4/c4MptTrULWtGIH9okVPLSPX0NnRyGQ+mSwWyuQ==", "dev": true, - "peer": true, "dependencies": { "@react-native-community/cli-platform-android": "12.3.6", "@react-native-community/cli-tools": "12.3.6", @@ -4266,7 +4232,6 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.6.tgz", "integrity": "sha512-DeDDAB8lHpuGIAPXeeD9Qu2+/wDTFPo99c8uSW49L0hkmZJixzvvvffbGQAYk32H0TmaI7rzvzH+qzu7z3891g==", "dev": true, - "peer": true, "dependencies": { "@react-native-community/cli-tools": "12.3.6", "chalk": "^4.1.2", @@ -4281,7 +4246,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4302,7 +4266,6 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.6.tgz", "integrity": "sha512-3eZ0jMCkKUO58wzPWlvAPRqezVKm9EPZyaPyHbRPWU8qw7JqkvnRlWIaYDGpjCJgVW4k2hKsEursLtYKb188tg==", "dev": true, - "peer": true, "dependencies": { "@react-native-community/cli-tools": "12.3.6", "chalk": "^4.1.2", @@ -4317,7 +4280,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4337,15 +4299,13 @@ "version": "12.3.6", "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.6.tgz", "integrity": "sha512-3jxSBQt4fkS+KtHCPSyB5auIT+KKIrPCv9Dk14FbvOaEh9erUWEm/5PZWmtboW1z7CYeNbFMeXm9fM2xwtVOpg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@react-native-community/cli-server-api": { "version": "12.3.6", "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.6.tgz", "integrity": "sha512-80NIMzo8b2W+PL0Jd7NjiJW9mgaT8Y8wsIT/lh6mAvYH7mK0ecDJUYUTAAv79Tbo1iCGPAr3T295DlVtS8s4yQ==", "dev": true, - "peer": true, "dependencies": { "@react-native-community/cli-debugger-ui": "12.3.6", "@react-native-community/cli-tools": "12.3.6", @@ -4363,7 +4323,6 @@ "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", "dev": true, - "peer": true, "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -4380,7 +4339,6 @@ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", "dev": true, - "peer": true, "dependencies": { "@types/yargs-parser": "*" } @@ -4390,7 +4348,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", @@ -4406,7 +4363,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "dev": true, - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -4428,7 +4384,6 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.6.tgz", "integrity": "sha512-FPEvZn19UTMMXUp/piwKZSh8cMEfO8G3KDtOwo53O347GTcwNrKjgZGtLSPELBX2gr+YlzEft3CoRv2Qmo83fQ==", "dev": true, - "peer": true, "dependencies": { "appdirsjs": "^1.2.4", "chalk": "^4.1.2", @@ -4447,7 +4402,6 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -4457,7 +4411,6 @@ "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", "dev": true, - "peer": true, "dependencies": { "is-wsl": "^1.1.0" }, @@ -4470,7 +4423,6 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.6.tgz", "integrity": "sha512-xPqTgcUtZowQ8WKOkI9TLGBwH2bGggOC4d2FFaIRST3gTcjrEeGRNeR5aXCzJFIgItIft8sd7p2oKEdy90+01Q==", "dev": true, - "peer": true, "dependencies": { "joi": "^17.2.1" } @@ -4480,7 +4432,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, - "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -4490,7 +4441,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -4504,7 +4454,6 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, - "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -4519,7 +4468,6 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, - "peer": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -4529,7 +4477,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -4542,7 +4489,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -4558,7 +4504,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -4571,7 +4516,6 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, - "peer": true, "engines": { "node": ">= 4.0.0" } @@ -4581,7 +4525,6 @@ "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", "dev": true, - "peer": true, "engines": { "node": ">=18" } @@ -4591,7 +4534,6 @@ "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.4.tgz", "integrity": "sha512-XzRd8MJGo4Zc5KsphDHBYJzS1ryOHg8I2gOZDAUCGcwLFhdyGu1zBNDJYH2GFyDrInn9TzAbRIf3d4O+eltXQQ==", "dev": true, - "peer": true, "dependencies": { "@react-native/codegen": "0.73.3" }, @@ -4604,7 +4546,6 @@ "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.21.tgz", "integrity": "sha512-WlFttNnySKQMeujN09fRmrdWqh46QyJluM5jdtDNrkl/2Hx6N4XeDUGhABvConeK95OidVO7sFFf7sNebVXogA==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.20.0", "@babel/plugin-proposal-async-generator-functions": "^7.0.0", @@ -4661,7 +4602,6 @@ "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", "dev": true, - "peer": true, "dependencies": { "@babel/parser": "^7.20.0", "flow-parser": "^0.206.0", @@ -4683,7 +4623,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4704,7 +4643,6 @@ "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.17.tgz", "integrity": "sha512-F3PXZkcHg+1ARIr6FRQCQiB7ZAA+MQXGmq051metRscoLvgYJwj7dgC8pvgy0kexzUkHu5BNKrZeySzUft3xuQ==", "dev": true, - "peer": true, "dependencies": { "@react-native-community/cli-server-api": "12.3.6", "@react-native-community/cli-tools": "12.3.6", @@ -4727,7 +4665,6 @@ "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", "dev": true, - "peer": true, "engines": { "node": ">=18" } @@ -4737,7 +4674,6 @@ "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.8.tgz", "integrity": "sha512-oph4NamCIxkMfUL/fYtSsE+JbGOnrlawfQ0kKtDQ5xbOjPKotKoXqrs1eGwozNKv7FfQ393stk1by9a6DyASSg==", "dev": true, - "peer": true, "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.73.3", @@ -4760,7 +4696,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -4769,15 +4704,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@react-native/dev-middleware/node_modules/open": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "dev": true, - "peer": true, "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -4794,7 +4727,6 @@ "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz", "integrity": "sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg==", "dev": true, - "peer": true, "engines": { "node": ">=18" } @@ -4804,7 +4736,6 @@ "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", "dev": true, - "peer": true, "engines": { "node": ">=18" } @@ -4814,7 +4745,6 @@ "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.20.0", "@react-native/babel-preset": "0.73.21", @@ -4832,15 +4762,13 @@ "version": "0.73.2", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@react-native/virtualized-lists": { "version": "0.73.4", "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", "dev": true, - "peer": true, "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" @@ -4990,7 +4918,6 @@ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.16.2.tgz", "integrity": "sha512-3Z5FW8mxzomBbrw2iF0lNOAlNBr2OK6HR0NM416PzcTs0UcSoPj/nD4eqmrV5Kut6kvCc/TJua5LyeoPE7vSmw==", "dev": true, - "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/react-reconciler": "^0.26.7", @@ -5040,7 +4967,6 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", "dev": true, - "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -5050,7 +4976,6 @@ "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", "dev": true, - "peer": true, "engines": { "node": ">=12.7.0" }, @@ -5102,7 +5027,6 @@ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, - "peer": true, "dependencies": { "type-detect": "4.0.8" } @@ -5112,7 +5036,6 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, - "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -6461,6 +6384,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -6611,6 +6535,7 @@ "integrity": "sha512-Xaf+UBvW6JNuV131uvSNyMXHn+bh6LyKN4tbv7tOUFQpXyz/t9YWRE04emtlUW9Y0qrm/GKFCbY8n3z6BpZbTA==", "dev": true, "hasInstallScript": true, + "peer": true, "dependencies": { "@swc/counter": "^0.1.2", "@swc/types": "^0.1.5" @@ -7199,6 +7124,7 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -7641,6 +7567,7 @@ "version": "18.2.79", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz", "integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -7651,7 +7578,6 @@ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", "dev": true, - "peer": true, "dependencies": { "@types/react": "*" } @@ -7749,8 +7675,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/unist": { "version": "3.0.2", @@ -7767,8 +7692,7 @@ "version": "0.5.15", "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.15.tgz", "integrity": "sha512-nC9116Gd4N+CqTxqo6gvCfhAMAzgRcfS8ZsciNodHq8uwW4JCVKwhagw8yN0XmC7mHrLnWqniJpoVEiR+72Drw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/ws": { "version": "8.5.10", @@ -7955,6 +7879,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz", "integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.4.0", "@typescript-eslint/types": "7.4.0", @@ -8703,7 +8628,6 @@ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "dev": true, - "peer": true, "dependencies": { "event-target-shim": "^5.0.0" }, @@ -8727,6 +8651,7 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8775,6 +8700,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -8834,6 +8760,7 @@ "version": "4.23.3", "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.3.tgz", "integrity": "sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==", + "peer": true, "dependencies": { "@algolia/cache-browser-local-storage": "4.23.3", "@algolia/cache-common": "4.23.3", @@ -8867,8 +8794,7 @@ "version": "1.4.10", "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", - "dev": true, - "peer": true + "dev": true }, "node_modules/ansi-align": { "version": "3.0.1", @@ -8920,7 +8846,6 @@ "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", "dev": true, - "peer": true, "dependencies": { "colorette": "^1.0.7", "slice-ansi": "^2.0.0", @@ -8932,7 +8857,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -8942,7 +8866,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^4.1.0" }, @@ -9005,8 +8928,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/arg": { "version": "5.0.2", @@ -9183,8 +9105,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/assert": { "version": "2.1.0", @@ -9225,7 +9146,6 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -9248,8 +9168,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/at-least-node": { "version": "1.0.0", @@ -9502,7 +9421,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", "dev": true, - "peer": true, "dependencies": { "@babel/plugin-syntax-flow": "^7.12.1" } @@ -9770,6 +9688,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -9789,7 +9708,6 @@ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, - "peer": true, "dependencies": { "node-int64": "^0.4.0" } @@ -9813,7 +9731,6 @@ "url": "https://feross.org/support" } ], - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -9921,7 +9838,6 @@ "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", "dev": true, - "peer": true, "dependencies": { "callsites": "^2.0.0" }, @@ -9934,7 +9850,6 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -9944,7 +9859,6 @@ "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", "dev": true, - "peer": true, "dependencies": { "caller-callsite": "^2.0.0" }, @@ -10214,7 +10128,6 @@ "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", "dev": true, - "peer": true, "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", @@ -10241,7 +10154,6 @@ "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", "integrity": "sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==", "dev": true, - "peer": true, "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", @@ -10256,7 +10168,6 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, - "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -10411,7 +10322,6 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -10425,15 +10335,13 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/cliui/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -10443,7 +10351,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -10458,7 +10365,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -10471,7 +10377,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -10559,8 +10464,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true, - "peer": true + "dev": true }, "node_modules/combine-promises": { "version": "1.2.0", @@ -10583,8 +10487,7 @@ "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/commander": { "version": "5.1.0", @@ -10746,7 +10649,6 @@ "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "dev": true, - "peer": true, "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", @@ -10770,7 +10672,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -10780,7 +10681,6 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dev": true, - "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -10798,15 +10698,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/connect/node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "dev": true, - "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -10819,7 +10717,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "dev": true, - "peer": true, "engines": { "node": ">= 0.6" } @@ -11365,8 +11262,7 @@ "version": "1.11.10", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/debounce": { "version": "1.2.1", @@ -11395,7 +11291,6 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11627,8 +11522,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/depd": { "version": "2.0.0", @@ -11643,7 +11537,6 @@ "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", "integrity": "sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==", "dev": true, - "peer": true, "dependencies": { "@react-native/normalize-colors": "^0.73.0", "invariant": "^2.2.4", @@ -12171,6 +12064,7 @@ "version": "6.5.1", "resolved": "https://registry.npmjs.org/@svgr/core/-/core-6.5.1.tgz", "integrity": "sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==", + "peer": true, "dependencies": { "@babel/core": "^7.19.6", "@svgr/babel-preset": "^6.5.1", @@ -13267,7 +13161,6 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true, - "peer": true, "engines": { "node": ">= 0.8" } @@ -13341,7 +13234,6 @@ "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", "dev": true, - "peer": true, "dependencies": { "stackframe": "^1.3.4" } @@ -13351,7 +13243,6 @@ "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", "dev": true, - "peer": true, "dependencies": { "accepts": "~1.3.7", "escape-html": "~1.0.3" @@ -13546,6 +13437,7 @@ "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -13668,6 +13560,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "devOptional": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -14111,7 +14004,6 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -14287,7 +14179,6 @@ "url": "https://paypal.me/naturalintelligence" } ], - "peer": true, "dependencies": { "strnum": "^1.0.5" }, @@ -14331,7 +14222,6 @@ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, - "peer": true, "dependencies": { "bser": "2.1.1" } @@ -14645,8 +14535,7 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/flow-parser": { "version": "0.206.0", @@ -14937,7 +14826,6 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "peer": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -15672,15 +15560,13 @@ "version": "0.15.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.15.0.tgz", "integrity": "sha512-lLYvAd+6BnOqWdnNbP/Q8xfl8LOGw4wVjfrNd9Gt8eoFzhNBRVD95n4l2ksfMVOoxuVyegs85g83KS9QOsxbVQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/hermes-parser": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.15.0.tgz", "integrity": "sha512-Q1uks5rjZlE9RjMMjSUCkGrEIPI5pKJILeCtK1VmTj7U4pf3wVPoo+cxfu+s4cBAPy2JzikIIdCZgBoR6x7U1Q==", "dev": true, - "peer": true, "dependencies": { "hermes-estree": "0.15.0" } @@ -15690,7 +15576,6 @@ "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", "dev": true, - "peer": true, "dependencies": { "source-map": "^0.7.3" }, @@ -16437,7 +16322,6 @@ "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -16489,7 +16373,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -16916,7 +16799,6 @@ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.5.tgz", "integrity": "sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==", "dev": true, - "peer": true, "dependencies": { "@types/react-reconciler": "^0.28.0" }, @@ -16929,7 +16811,6 @@ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.8.tgz", "integrity": "sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==", "dev": true, - "peer": true, "dependencies": { "@types/react": "*" } @@ -16975,7 +16856,6 @@ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -16993,7 +16873,6 @@ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -17003,7 +16882,6 @@ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", @@ -17024,7 +16902,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -17037,7 +16914,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -17051,15 +16927,13 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-mock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -17090,7 +16964,6 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -17108,7 +16981,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -17121,7 +16993,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -17135,8 +17006,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jest-worker": { "version": "29.7.0", @@ -17206,22 +17076,19 @@ "version": "250231.0.0", "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", "integrity": "sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jsc-safe-url": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/jscodeshift": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", "integrity": "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.13.16", "@babel/parser": "^7.13.16", @@ -17255,7 +17122,6 @@ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", "dev": true, - "peer": true, "dependencies": { "tslib": "^2.0.1" }, @@ -17268,7 +17134,6 @@ "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", "dev": true, - "peer": true, "dependencies": { "ast-types": "0.15.2", "esprima": "~4.0.0", @@ -17284,7 +17149,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -17310,8 +17174,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -17483,7 +17346,6 @@ "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", "dev": true, - "peer": true, "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" @@ -17494,7 +17356,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -17503,8 +17364,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/lightningcss": { "version": "1.30.1", @@ -17824,8 +17684,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/lodash.uniq": { "version": "4.5.0", @@ -17853,7 +17712,6 @@ "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", "dev": true, - "peer": true, "dependencies": { "ansi-fragments": "^0.2.1", "dayjs": "^1.8.15", @@ -17868,7 +17726,6 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -17878,7 +17735,6 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, - "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -17889,15 +17745,13 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/logkitty/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -17911,7 +17765,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -17921,7 +17774,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -17934,7 +17786,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -17950,7 +17801,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -17963,7 +17813,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -17978,7 +17827,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -17991,7 +17839,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -18005,15 +17852,13 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/logkitty/node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, - "peer": true, "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -18036,7 +17881,6 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, - "peer": true, "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -18149,7 +17993,6 @@ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, - "peer": true, "dependencies": { "tmpl": "1.0.5" } @@ -18196,8 +18039,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/math-intrinsics": { "version": "1.1.0", @@ -18615,8 +18457,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/memoizerific": { "version": "1.11.3", @@ -18662,7 +18503,6 @@ "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.8.tgz", "integrity": "sha512-in7S0W11mg+RNmcXw+2d9S3zBGmCARDxIwoXJAmLUQOQoYsRP3cpGzyJtc7WOw8+FXfpgXvceD0u+PZIHXEL7g==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "@babel/core": "^7.20.0", @@ -18720,7 +18560,6 @@ "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.8.tgz", "integrity": "sha512-TTzNwRZb2xxyv4J/+yqgtDAP2qVqH3sahsnFu6Xv4SkLqzrivtlnyUbaeTdJ9JjtADJUEjCbgbFgUVafrXdR9Q==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.20.0", "hermes-parser": "0.20.1", @@ -18734,15 +18573,13 @@ "version": "0.20.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.20.1.tgz", "integrity": "sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/metro-babel-transformer/node_modules/hermes-parser": { "version": "0.20.1", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.20.1.tgz", "integrity": "sha512-BL5P83cwCogI8D7rrDCgsFY0tdYUtmFP9XaXtl2IQjC+2Xo+4okjfXintlTxcIwl4qeGddEl28Z11kbVIw0aNA==", "dev": true, - "peer": true, "dependencies": { "hermes-estree": "0.20.1" } @@ -18752,7 +18589,6 @@ "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.8.tgz", "integrity": "sha512-5svz+89wSyLo7BxdiPDlwDTgcB9kwhNMfNhiBZPNQQs1vLFXxOkILwQiV5F2EwYT9DEr6OPZ0hnJkZfRQ8lDYQ==", "dev": true, - "peer": true, "dependencies": { "metro-core": "0.80.8", "rimraf": "^3.0.2" @@ -18766,7 +18602,6 @@ "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.8.tgz", "integrity": "sha512-qWKzxrLsRQK5m3oH8ePecqCc+7PEhR03cJE6Z6AxAj0idi99dHOSitTmY0dclXVB9vP2tQIAE8uTd8xkYGk8fA==", "dev": true, - "peer": true, "engines": { "node": ">=18" } @@ -18776,7 +18611,6 @@ "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.8.tgz", "integrity": "sha512-VGQJpfJawtwRzGzGXVUoohpIkB0iPom4DmSbAppKfumdhtLA8uVeEPp2GM61kL9hRvdbMhdWA7T+hZFDlo4mJA==", "dev": true, - "peer": true, "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", @@ -18795,7 +18629,6 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -18805,7 +18638,6 @@ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", "dev": true, - "peer": true, "dependencies": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", @@ -18821,7 +18653,6 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", "dev": true, - "peer": true, "dependencies": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" @@ -18835,7 +18666,6 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, - "peer": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -18849,7 +18679,6 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, - "peer": true, "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -18863,7 +18692,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -18873,7 +18701,6 @@ "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.8.tgz", "integrity": "sha512-g6lud55TXeISRTleW6SHuPFZHtYrpwNqbyFIVd9j9Ofrb5IReiHp9Zl8xkAfZQp8v6ZVgyXD7c130QTsCz+vBw==", "dev": true, - "peer": true, "dependencies": { "lodash.throttle": "^4.1.1", "metro-resolver": "0.80.8" @@ -18887,7 +18714,6 @@ "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.8.tgz", "integrity": "sha512-eQXMFM9ogTfDs2POq7DT2dnG7rayZcoEgRbHPXvhUWkVwiKkro2ngcBE++ck/7A36Cj5Ljo79SOkYwHaWUDYDw==", "dev": true, - "peer": true, "dependencies": { "anymatch": "^3.0.3", "debug": "^2.2.0", @@ -18912,7 +18738,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -18921,15 +18746,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/metro-minify-terser": { "version": "0.80.8", "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.8.tgz", "integrity": "sha512-y8sUFjVvdeUIINDuW1sejnIjkZfEF+7SmQo0EIpYbWmwh+kq/WMj74yVaBWuqNjirmUp1YNfi3alT67wlbBWBQ==", "dev": true, - "peer": true, "dependencies": { "terser": "^5.15.0" }, @@ -18942,7 +18765,6 @@ "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.8.tgz", "integrity": "sha512-JdtoJkP27GGoZ2HJlEsxs+zO7jnDUCRrmwXJozTlIuzLHMRrxgIRRby9fTCbMhaxq+iA9c+wzm3iFb4NhPmLbQ==", "dev": true, - "peer": true, "engines": { "node": ">=18" } @@ -18952,7 +18774,6 @@ "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.8.tgz", "integrity": "sha512-2oScjfv6Yb79PelU1+p8SVrCMW9ZjgEiipxq7jMRn8mbbtWzyv3g8Mkwr+KwOoDFI/61hYPUbY8cUnu278+x1g==", "dev": true, - "peer": true, "dependencies": { "@babel/runtime": "^7.0.0" }, @@ -18965,7 +18786,6 @@ "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.8.tgz", "integrity": "sha512-+OVISBkPNxjD4eEKhblRpBf463nTMk3KMEeYS8Z4xM/z3qujGJGSsWUGRtH27+c6zElaSGtZFiDMshEb8mMKQg==", "dev": true, - "peer": true, "dependencies": { "@babel/traverse": "^7.20.0", "@babel/types": "^7.20.0", @@ -18985,7 +18805,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -18995,7 +18814,6 @@ "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.8.tgz", "integrity": "sha512-nwhYySk79jQhwjL9QmOUo4wS+/0Au9joEryDWw7uj4kz2yvw1uBjwmlql3BprQCBzRdB3fcqOP8kO8Es+vE31g==", "dev": true, - "peer": true, "dependencies": { "invariant": "^2.2.4", "metro-source-map": "0.80.8", @@ -19016,7 +18834,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -19026,7 +18843,6 @@ "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.8.tgz", "integrity": "sha512-sSu8VPL9Od7w98MftCOkQ1UDeySWbsIAS5I54rW22BVpPnI3fQ42srvqMLaJUQPjLehUanq8St6OMBCBgH/UWw==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", @@ -19043,7 +18859,6 @@ "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.8.tgz", "integrity": "sha512-+4FG3TQk3BTbNqGkFb2uCaxYTfsbuFOCKMMURbwu0ehCP8ZJuTUramkaNZoATS49NSAkRgUltgmBa4YaKZ5mqw==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", @@ -19066,15 +18881,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/metro/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -19083,15 +18896,13 @@ "version": "0.20.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.20.1.tgz", "integrity": "sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/metro/node_modules/hermes-parser": { "version": "0.20.1", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.20.1.tgz", "integrity": "sha512-BL5P83cwCogI8D7rrDCgsFY0tdYUtmFP9XaXtl2IQjC+2Xo+4okjfXintlTxcIwl4qeGddEl28Z11kbVIw0aNA==", "dev": true, - "peer": true, "dependencies": { "hermes-estree": "0.20.1" } @@ -19100,15 +18911,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/metro/node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -19118,7 +18927,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -19131,7 +18939,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "dev": true, - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -20847,7 +20654,6 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, - "peer": true, "bin": { "mime": "cli.js" }, @@ -20992,7 +20798,6 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -21083,7 +20888,6 @@ "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", "dev": true, - "peer": true, "engines": { "node": ">=12.0.0" } @@ -21158,8 +20962,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/node-releases": { "version": "2.0.27", @@ -21172,7 +20975,6 @@ "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", "dev": true, - "peer": true, "engines": { "node": ">=0.12.0" }, @@ -21260,8 +21062,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/nypm": { "version": "0.3.8", @@ -21421,7 +21222,6 @@ "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.8.tgz", "integrity": "sha512-QHJQk/lXMmAW8I7AIM3in1MSlwe1umR72Chhi8B7Xnq6mzjhBKkA6Fy/zAhQnGkA4S912EPCEvTij5yh+EQTAA==", "dev": true, - "peer": true, "engines": { "node": ">=18" } @@ -22145,6 +21945,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -22734,6 +22535,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -22922,7 +22724,6 @@ "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", "dev": true, - "peer": true, "dependencies": { "asap": "~2.0.6" } @@ -23151,6 +22952,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -23360,7 +23162,6 @@ "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.28.5.tgz", "integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==", "dev": true, - "peer": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -23371,7 +23172,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "dev": true, - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -23428,6 +23228,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -23519,7 +23320,6 @@ "url": "https://github.com/sponsors/lavrton" } ], - "peer": true, "dependencies": { "@types/react-reconciler": "^0.28.2", "its-fine": "^1.1.1", @@ -23537,7 +23337,6 @@ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.8.tgz", "integrity": "sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==", "dev": true, - "peer": true, "dependencies": { "@types/react": "*" } @@ -23547,7 +23346,6 @@ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.0.tgz", "integrity": "sha512-wa0fGj7Zht1EYMRhKWwoo1H9GApxYLBuhoAuXN0TlltESAjDssB+Apf0T/DngVqaMyPypDmabL37vw/2aRM98Q==", "dev": true, - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -23564,6 +23362,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", + "peer": true, "dependencies": { "@types/react": "*" }, @@ -23647,7 +23446,6 @@ "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", "dev": true, - "peer": true, "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -23664,7 +23462,6 @@ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", "dev": true, - "peer": true, "dependencies": { "@types/yargs-parser": "*" } @@ -23674,7 +23471,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", "dev": true, - "peer": true, "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", @@ -23689,15 +23485,13 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/react-native/node_modules/scheduler": { "version": "0.24.0-canary-efb381bbf-20230505", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz", "integrity": "sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==", "dev": true, - "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -23707,7 +23501,6 @@ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz", "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==", "dev": true, - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.21.0" @@ -23724,7 +23517,6 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", "dev": true, - "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -23734,7 +23526,6 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -23743,6 +23534,7 @@ "version": "5.3.4", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.13", "history": "^4.9.0", @@ -23810,7 +23602,6 @@ "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", "dev": true, - "peer": true, "dependencies": { "object-assign": "^4.1.1", "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" @@ -23842,7 +23633,6 @@ "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==", "dev": true, - "peer": true, "dependencies": { "debounce": "^1.2.1" }, @@ -23998,8 +23788,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/recast": { "version": "0.23.6", @@ -24482,7 +24271,6 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -24507,8 +24295,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/requireindex": { "version": "1.2.0", @@ -24528,8 +24315,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/resolve": { "version": "1.22.11", @@ -24756,6 +24542,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.72.0.tgz", "integrity": "sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==", "dev": true, + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -24859,6 +24646,7 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -25030,7 +24818,6 @@ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -25205,8 +24992,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/set-function-length": { "version": "1.2.2", @@ -25464,7 +25250,6 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^3.2.0", "astral-regex": "^1.0.0", @@ -25479,7 +25264,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -25492,7 +25276,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "peer": true, "dependencies": { "color-name": "1.1.3" } @@ -25501,8 +25284,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/snake-case": { "version": "3.0.4", @@ -25669,7 +25451,6 @@ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, - "peer": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -25682,7 +25463,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -25691,15 +25471,13 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/stacktrace-parser": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", "dev": true, - "peer": true, "dependencies": { "type-fest": "^0.7.1" }, @@ -25712,7 +25490,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -26032,8 +25809,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/style-loader": { "version": "3.3.4", @@ -26078,8 +25854,7 @@ "version": "9.2.1", "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/supports-color": { "version": "7.2.0", @@ -26108,7 +25883,6 @@ "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", "dev": true, - "peer": true, "peerDependencies": { "react": ">=17.0" } @@ -26466,8 +26240,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/through2": { "version": "2.0.5", @@ -26543,8 +26316,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -26807,6 +26579,7 @@ "version": "5.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -27525,15 +27298,13 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, - "peer": true, "dependencies": { "makeerror": "1.0.12" } @@ -27588,6 +27359,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -27906,6 +27678,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -27998,8 +27771,7 @@ "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/whatwg-url": { "version": "5.0.0", @@ -28089,8 +27861,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/which-typed-array": { "version": "1.1.15", @@ -28243,7 +28014,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", "dev": true, - "peer": true, "dependencies": { "async-limiter": "~1.0.0" } @@ -28284,7 +28054,6 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "peer": true, "engines": { "node": ">=10" } @@ -28307,7 +28076,6 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "peer": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -28326,7 +28094,6 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "peer": true, "engines": { "node": ">=12" } @@ -28335,15 +28102,13 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/yargs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -28353,7 +28118,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -28368,7 +28132,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, From a6742e85b358775bd06d8c80c12d5953b56a15e7 Mon Sep 17 00:00:00 2001 From: Nikolaengel Date: Wed, 21 Jan 2026 19:36:45 +0300 Subject: [PATCH 02/14] docs-updatequickstart --- .../current/basic-guides/selectors.mdx | 24 +- .../current/quickstart/index.mdx | 63 ++-- .../current/quickstart/running-tests.mdx | 116 ++++++-- .../current/quickstart/writing-tests.mdx | 281 +++++++++++++++++- 4 files changed, 414 insertions(+), 70 deletions(-) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx index 8ecb8390..08fcc8c7 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx @@ -1,5 +1,7 @@ # Селекторы +## Введение + Testplane предоставляет множество способов для поиска элементов на странице браузера с помощью селекторов. Для этого используются библиотеки `WebDriverIO` и `testing-library`. Селекторы позволяют точно идентифицировать элементы интерфейса, что необходимо для автоматизации тестирования. ## WebDriverIO @@ -45,7 +47,7 @@ const isDisplayed = await loginForm.isDisplayed(); #### По типу атрибута -Для поиска элемента по атрибуту, используйте селектор вида `input[type="name"]`. +Для поиска элемента по атрибуту, используйте селектор вида `"input[type="name"]"`. ```javascript // Поиск всех чекбоксов @@ -73,7 +75,7 @@ const csrfValue = await hiddenField.getValue(); #### CSS-селекторы по атрибуту data-testid -Для поиска элементов, которые помечены дял тестирования, используйте селекторы по атрибуту `data-testid`. +Для поиска элементов, которые помечены дял тестирования, используйте селекторы по атрибуту `"data-testid"`. ```javascript // Поиск элемента по data-testid @@ -126,7 +128,7 @@ const activeTab = await browser.$("ul.tabs > li.active > a[href^="#"]"); #### CSS-псевдоселекторы -Псевдоселекторы позволяют выбирать элементы на основе их положения или состояния. +Для выбора элементов на основе их положения или состояния, используйте псевдоселекторы. ```javascript // Первый элемент списка @@ -163,7 +165,7 @@ const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); #### По тексту элемента -Чтобы найти элемент по содержащемуся в нем тексту, используйте селектор `//element[text()="text"]`. +Чтобы найти элемент по содержащемуся в нем тексту, используйте селектор `"//element[text()="text"]"`. ```javascript // Точное совпадение текста @@ -194,7 +196,7 @@ const link = await browser.$("//a[contains(translate(text(), "АБВГДЕЁЖЗ #### По атрибутам -Для поиска элемента по атрибуту, используйте селектор вида `//element[@type="atribute"]`. +Для поиска элемента по атрибуту, используйте селектор вида `"//element[@type="atribute"]"`. ```javascript // Поиск по одному атрибуту @@ -230,7 +232,7 @@ const notDisabledButton = await browser.$("//button[not(@disabled)]"); #### Навигация по DOM -Исползуя XPath, вы можете навигировать по DOM-дереву. +С помощью XPath вы можете навигировать по DOM-дереву. ```javascript // Прямой родитель @@ -298,7 +300,7 @@ const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 ### Селекторы по Link Text -Селекторы по содержащемуся внутри тексту позволяют находить ссылки `()` по их тексту. Используйте `="text"`, чтобы найти элемент с точным текстом и `*="text"` для поиска по частичному совпадению текста. +Селекторы по содержащемуся внутри тексту позволяют находить ссылки `()` по их тексту. Используйте `"=text"`, чтобы найти элемент с точным текстом и `"*=text"` для поиска по частичному совпадению текста. ```javascript // Полное совпадение текста ссылки @@ -424,7 +426,7 @@ const slotElements = await customElement.shadow$$(".slot-item"); ## Testing-library -Testing Library — это адаптер популярной философии Testing Library для Testplane. Она предоставляет селекторы, ориентированные на пользовательский опыт (как пользователи находят элементы). +Testing Library — это адаптер популярной философии Testing Library для Testplane. Он предоставляет селекторы, ориентированные на пользовательский опыт (как пользователи находят элементы). ### ByRole @@ -458,7 +460,7 @@ await userEvent.click(agreeCheckbox); ### ByLabelText -Для поиска элементов форм по тексту их меток (`label`), используйте метод `getByLabelText`. +Для поиска элементов форм по тексту их меток (`"label"`), используйте метод `getByLabelText`. ```javascript import { screen } from "@testing-library/dom"; @@ -482,7 +484,7 @@ await userEvent.type(passwordInput, "secure123"); ### ByPlaceholderText -Чтобы найти поле ввода по тексту `placeholder`, используйте селектор `getByPlaceholderText`. +Чтобы найти поле ввода по тексту `placeholder`, используйте метод `getByPlaceholderText`. ```javascript import { screen } from "@testing-library/dom"; @@ -570,7 +572,7 @@ expect(logo).toHaveAttribute("src", "/images/logo.png"); ### ByTitle -Чтобы найти элемент по атрибуту title, используйте метод getByTitle. +Чтобы найти элемент по атрибуту `title`, используйте метод `getByTitle`. ```javascript import { screen } from "@testing-library/dom"; diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/index.mdx index de250999..4c060761 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/index.mdx @@ -4,36 +4,59 @@ sidebar_position: 1 import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; +import Admonition from "@theme/Admonition"; -# Установка {#install} +# Установка и настройка -Запустите установщик testplane с помощью `npm`. +## Системные требования + +Чтобы начать работу с testplane, установите `Node.js` версии 18.0 и выше. + +## Установка {#install} + +Для запуска установщика testplane, с помощью `npm` выполните следующую команду: ```bash npm init testplane@latest YOUR_PROJECT_PATH ``` -Если вы не хотите использовать дефолты при инициализации проекта, а настроить его с помощью визарда, укажите опцию `-v`. +Чтобы настроить проект, а не использовать дефолты при его инициализации, укажите опцию `-v`. + +После выполнения команды установки, в директории проекта появится следующий набор файлов и папок: + +```bash +node_modules +testplane-tests + example.testplane.ts + ts.config.json +package-lock.json +package.json +testplane.config.ts +``` ## Настройка {#setup} -После выполнения команды, указанной выше, в корне проекта сгенерится файл `testplane.config.ts` с базовой настройкой. +В файле `testplane.config.ts` содержится базовый набор настроек для запуска тестов: ```typescript export default { - // https://testplane.io/ru/docs/v8/basic-guides/managing-browsers/ gridUrl: "local", baseUrl: "http://localhost", pageLoadTimeout: 0, httpTimeout: 60000, testTimeout: 90000, resetCursor: false, + + // В параметре sets содержится информация о директории, в которой находятся тесты + // и перечень браузеров, в которых они будут запускаться: sets: { desktop: { files: ["testplane-tests/**/*.testplane.(t|j)s"], browsers: ["chrome", "firefox"], }, }, + + // В поле `browsers` описана конфигурация используемых браузеров: browsers: { chrome: { headless: true, @@ -48,9 +71,9 @@ export default { }, }, }, + plugins: { "html-reporter/testplane": { - // https://github.com/gemini-testing/html-reporter enabled: true, path: "testplane-report", defaultView: "all", @@ -60,7 +83,7 @@ export default { }; ``` -Вы можете загрузить браузеры, описанные в конфиге, отдельно от запуска самого Testplane: +Чтобы загрузить браузеры, описанные в конфиге, отдельно от запуска самого Testplane, выполните команду: ```bash npx testplane install-deps @@ -68,31 +91,5 @@ npx testplane install-deps Без предварительного запуска команды, недостающие браузеры будут автоматически загружены с первым запуском Testplane. -## Создание теста {#test_creation} - -Перейдите в файл `tests/example.testplane.js` с тестом. В нем вы можете посмотреть пример теста или написать свой. Например, -```javascript -describe("github", async function () { - it("should find testplane", async function ({ browser }) { - await browser.url("https://github.com/gemini-testing/testplane"); - const elem = await browser.$("#readme h1"); - await expect(elem).toHaveText("Testplane"); - }); -}); -``` - -## Запуск теста {#test_running} - -Теперь вы можете запустить тесты: - -```bash -npx testplane -``` - -или запустить gui-режим и запустить тест через интерфейс в браузере - -```bash -npx testplane gui -``` diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx index 2461ca29..210a3ab1 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx @@ -2,54 +2,120 @@ sidebar_position: 3 --- -# Запуск тестов +# Запуск и отладка -Используйте команду `npx testplane` для запуска всех тестов в вашем проекте. +## Запуск тестов -## Запуск конкретного файла +Для запуска тестов используйте команду: -Если вы хотите запустить всю группу тестов, которые находятся в конкретном файле, то укажите путь к этому файлу в качестве входного параметра для testplane: +```bash +npx testplane +``` + +Для запуска тестов в gui-режиме через интерфейс браузера используйте команду: ```bash -testplane src/features/Reviews/Reviews.test/MyReview/MyReview.a11y@touch-phone.testplane.js +npx testplane gui ``` +После выполнения одной из команд, тесты запустятся во всех браузерах, которые указаны в файле `testplane.config.ts`. -## Опция `--grep` +### Запуск конкретных тестов -Если же вы хотите запустить конкретный тест, то воспользуйтесь опцией `--grep`, указав в качестве ее значения полное имя теста: +`Grep`-фильтрация позволяет запускать тесты по названию, не меняя код. Это удобно для запуска подмножества тестов во время разработки. ```bash -testplane --grep "Доступность Оставление отзыва" +# Запустить тесты, название которых содержит "test name" +# Поиск происходит в describe() и it() блоках +npx testplane --grep "test name" + +# Инверсия grep - запустить все КРОМЕ совпадающих +# Полезно для исключения медленных или нестабильных тестов +npx testplane --grep "name" --invert ``` -## Директива `.only` +### Запуск в определенных браузерах -Ещё вы можете воспользоваться директивой `.only` для набора тестов `describe` и конкретного теста `it`, аналогично тому как это реализовано в `mocha` (см. раздел [exlusive tests](https://mochajs.org/#exclusive-tests)): +По умолчанию Testplane запускает тесты во всех браузерах из конфигурации. Флаг `--browser` позволяет выбрать конкретные браузеры для запуска. -Например: +```bash +# Запустить только в Chrome +# Имя браузера должно совпадать с ключом в конфиге +npx testplane --browser chrome -```javascript -describe.only("Доступность", function () { - // набор тестов... -}); +# Запустить в нескольких браузерах одновременно +# Полезно для кросс-браузерного тестирования +npx testplane --browser chrome --browser firefox ``` -или +### Параллельный запуск + +Testplane может запускать тесты параллельно в нескольких процессах, что значительно ускоряет выполнение больших тестовых наборов. + +```bash +# Запустить с 4 параллельными воркерами +# Каждый воркер - отдельный процесс Node.js +npx testplane --workers 4 + +# Альтернативно - настроить в конфигурации +# Это значение будет использоваться по умолчанию +module.exports = { + system: { + workers: 4 // Количество параллельных процессов + } +}; +``` + + +### Режим пользовательского интерфейса + +В Testplane вы можете работать с тестами в UI формате с помощью Testplane UI. + +![](/img/docs/html-reporter/html-reporter-demo.png) + +О процессах установки и настройки Testplane UI вы можете прочитать вот [здесь.](..//html-reporter//overview.mdx) + +## Отладка + +### Отладка с помощью консоли + +Самым доступным и простым способом отладки является вывод информации в консоль: + +### Browser.debug() + +В Testplane имеется встроенный инструмент для отладки - `browser.debug`. ```javascript -it.only("Оставление отзыва", async function () { - // код теста... +it('отладка с паузой', async ({browser}) => { + // Открываем тестируемую страницу + await browser.url('/page'); + + // browser.debug() останавливает выполнение теста + // и открывает интерактивную консоль (REPL - Read-Eval-Print Loop) + await browser.debug(); + + // После вызова debug() тест приостанавливается + // В консоли можно вводить команды WebdriverIO в реальном времени: + + // Примеры команд, которые можно вводить в REPL: + // > await browser.$('.button').click() - кликнуть по кнопке + // > await browser.getTitle() - получить заголовок страницы + // > await browser.$$('.items') - найти все элементы + // > .exit - выйти из режима отладки + + // Этот код выполнится только после выхода из debug() + await browser.$('.button').click(); }); + ``` -## Запуск тестов несколько раз {#running_tests_multiple_times} -Иногда может быть полезным запустить один и тот же тест несколько раз — например, для проверки стабильности. Плагин [@testplane/test-repeater][testplane-test-repeater] позволяет запустить тесты заданное количество раз. +### Отладка через Testplane UI -После установки и включения плагина вы можете запустить тесты нужное количество раз, используя следующую команду: +Наиболее удобным способом для работы с отладкой тестов является UI режим, в нем вы можете в реальном времени наблюдать выполнения тестов. -```bash -npx testplane --test-repeater-repeat 5 --grep 'Имя теста' -``` +![](/gif/docs/ui/run-debug.gif) + +И находить нестабильные тесты, медленные тесты или другие проблемы с помощью опций "сортировка" и "группировка". + +![](/gif/docs/ui/analytics.gif) -[testplane-test-repeater]: ../../plugins/testplane-test-repeater diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx index 9b4bed96..ac8f8bd4 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx @@ -5,4 +5,283 @@ draft: true # Написание тестов - +## Пример + +Чтобы узнать, как написать тест, ознакомьтесь с примером: + +```javascript +describe("test examples", () => { + it("docs search test", async ({browser}) => { + await browser.openAndWait("https://testplane.io/"); + + // Find by tag name + const navBar = await browser.$("nav"); + + // Find by aria-label + await navBar.$("aria/Search").click(); + + // Find by placeholder + const fileSearchInput = await browser.findByPlaceholderText("Search docs"); + await fileSearchInput.waitForDisplayed(); + await fileSearchInput.setValue("config"); + + // Find by id + const fileSearchResults = await browser.$("#docsearch-list"); + + // Find by role + const fileSearchResultsItems = await fileSearchResults.findAllByRole("option"); + + await expect(fileSearchResultsItems.length).toBeGreaterThan(1); + }); +}); +``` + +Файл с `example.testplane.ts` находится в папке `testplane-tests`. + +## Базовый синтаксис + +### Навигация + +Для перемещения по страницам используйте метод `await browser.url()`. Testplane предоставляет методы для типичных навигационных действий: обновление страницы, переход назад/вперед по истории браузера. Все методы навигации являются асинхронными и должны использоваться с `await`: +```javascript +it('page navigation', async ({browser}) => { + // Открыть URL - базовый метод для загрузки страницы + await browser.url('https://example.com'); +}); +``` +Если на странице имеются элементы, которые отображаются с задержкой, для корректного выполнения тестов укажите явное ожидание: + +```javascript +await browser.url(""); +await browser.$('h1').waitForExist({ timeout: 5000 }); +const title = await browser.$('h1').getText(); +``` + +Либо используйте команду: + +```javascript +await browser.openAndWait(""); +``` +Команда `await browser.openAndWait()` по умолчанию дожидается загрузки всех необходимых элементов на странице. + +### Селекторы + +Testplane поддерживает различные стратегии поиска элементов: `CSS` селекторы (самые распространенные), текстовые селекторы (по содержимому), `XPath` для сложных запросов. Метод `$()` возвращает первый найденный элемент, а `$$()` - массив всех подходящих элементов: + +```javascript +it('different selectors', async ({browser}) => { + // CSS селекторы - стандартный способ поиска элементов + await browser.$('.class-name'); // По классу + await browser.$('#id-name'); // По ID + + // Текстовый селектор - поиск элемента с точным совпадением текста + // Знак = означает точное совпадение + const link = await browser.$('a=Click me'); + + // Частичное совпадение текста - знак * означает "содержит" + const button = await browser.$('button*=Submit'); + + // XPath - мощный язык запросов для сложных поисков + await browser.$('//button[@type="submit"]'); + + // Поиск нескольких элементов - возвращает массив + const items = await browser.$$('.list-item'); + console.log(items.length); // Количество найденных элементов +}); +``` + +### Взаимодействия с элементами + +Для поиска элементов используйте метод `$()` (один элемент) или `$$()` (множество элементов). После нахождения элемента можно выполнить различные действия: клик, ввод текста, двойной клик. Метод `setValue()` очищает поле и вводит новое значение, а `addValue()` добавляет текст к существующему: + +```javascript +it('clicks and text input', async ({browser}) => { + // Найти элемент по CSS селектору и выполнить клик + const button = await browser.$('.submit-button'); + await button.click(); + + // Найти поле ввода и установить значение (предварительно очистив поле) + const input = await browser.$('#username'); + await input.setValue('my-username'); + + // Очистить поле вручную и добавить текст к существующему содержимому + await input.clearValue(); + await input.addValue('new-text'); + + // Двойной клик - полезно для выделения текста или специальных действий + await button.doubleClick(); + + // Клик правой кнопкой мыши - для вызова контекстного меню + await button.click({button: 'right'}); +}); +``` + +### Assertions + +Testplane использует `expect API` из `WebdriverIO` для проверки состояния элементов и страницы. Все `assertions` асинхронные и автоматически ждут выполнения условия в течение заданного таймаута. Можно инвертировать проверку с помощью `.not`: + +```javascript +it('element checks', async ({browser}) => { + const element = await browser.$('.message'); + + // Проверка видимости элемента - элемент должен быть в DOM и visible + await expect(element).toBeDisplayed(); + // Инверсия - элемент не должен быть виден + await expect(element).not.toBeDisplayed(); + + // Проверка существования в DOM - элемент есть в разметке + await expect(element).toExist(); + + // Проверка текстового содержимого элемента + await expect(element).toHaveText('Expected text'); // Точное совпадение + await expect(element).toHaveTextContaining('partial'); // Частичное совпадение + + // Проверка атрибутов элемента + await expect(element).toHaveAttribute('href', '/link'); // Точное значение + await expect(element).toHaveAttributeContaining('class', 'active'); // Частичное + + // Проверка значения input/textarea элементов + const input = await browser.$('input'); + await expect(input).toHaveValue('value'); + + // Проверка URL текущей страницы + await expect(browser).toHaveUrl('https://example.com/page'); // Полный URL + await expect(browser).toHaveUrlContaining('/page'); // Часть URL + + // Проверка заголовка страницы (тег ) + await expect(browser).toHaveTitle('Page Title'); +}); +``` + +### Хуки + +Хуки позволяют выполнять код до или после тестов, что полезно для подготовки тестового окружения и очистки. `before/after` выполняются один раз для всего блока `describe`, а `beforeEach/afterEach` - перед/после каждого теста: + +```javascript +describe('Tests with hooks', () => { + // Выполняется один раз перед всеми тестами в блоке + // Используется для глобальной настройки, например, авторизации + before(async ({browser}) => { + await browser.url('/setup'); + }); + + // Выполняется один раз после всех тестов + // Полезно для финальной очистки ресурсов + after(async ({browser}) => { + // cleanup code + }); + + // Выполняется перед каждым тестом + // Гарантирует чистое начальное состояние для каждого теста + beforeEach(async ({browser}) => { + await browser.url('/'); + }); + + // Выполняется после каждого теста + // Очистка состояния между тестами (cookies, localStorage и т.д.) + afterEach(async ({browser}) => { + await browser.deleteCookies(); + }); + + it('test 1', async ({browser}) => { + // beforeEach -> test 1 -> afterEach + }); + + it('test 2', async ({browser}) => { + // beforeEach -> test 2 -> afterEach + }); +}); +``` + +### Ожидания + +Явные ожидания необходимы для работы с динамическим контентом, который загружается или изменяется асинхронно. Testplane автоматически ждет появления элементов, но для сложных сценариев можно использовать специальные методы ожидания: + +```javascript +it('waiting for elements', async ({browser}) => { + const element = await browser.$('.dynamic-content'); + + // Ждать пока элемент станет видимым (display !== none, visibility !== hidden) + // Таймаут по умолчанию можно переопределить + await element.waitForDisplayed({timeout: 5000}); + + // Ждать появления элемента в DOM (может быть невидимым) + await element.waitForExist(); + + // Ждать пока элемент станет кликабельным (видим и enabled) + // Полезно для кнопок, которые могут быть disabled + await element.waitForClickable(); + + // Кастомное условие ожидания с помощью waitUntil + // Функция проверяется повторно до истечения таймаута + await browser.waitUntil( + async () => { + const text = await element.getText(); + return text === 'Loaded'; + }, + { + timeout: 5000, + timeoutMsg: 'Text did not appear' // Кастомное сообщение об ошибке + } + ); +}); +``` + +### Работа с формами + +Testplane предоставляет специальные методы для работы с различными элементами форм. Чекбоксы и радио-кнопки управляются через клик. Для выпадающих списков `(<select>)` есть удобные методы выбора опций по видимому тексту или значению атрибута: + +```javascript +it('form filling', async ({browser}) => { + // Ввод в текстовое поле - стандартный способ заполнения input элементов + await browser.$('#email').setValue('test@example.com'); + + // Работа с чекбоксом - клик переключает состояние checked/unchecked + const checkbox = await browser.$('#agree'); + await checkbox.click(); + + // Выбор радио-кнопки - поиск по значению value и клик + await browser.$('input[value="option1"]').click(); + + // Работа с выпадающим списком - несколько способов выбора опции + const select = await browser.$('#country'); + // Выбор по видимому тексту в списке + await select.selectByVisibleText('Russia'); + // Выбор по значению атрибута value + await select.selectByAttribute('value', 'ru'); +}); +``` + +### Работа с JavaScript кодом + +Иногда нужно выполнить произвольный JavaScript код в контексте страницы - например, для работы с `localStorage`, вызова функций или манипуляции `DOM` напрямую. Метод `execute()` выполняет код в браузере и может возвращать результат: + +```javascript +it('executing custom JS', async ({browser}) => { + await browser.url('/'); + + // Выполнить JavaScript в контексте браузера + // Код выполняется на странице, а не в Node.js + await browser.execute(() => { + window.localStorage.setItem('key', 'value'); + }); + + // Передача аргументов и получение результата + // Аргументы передаются после функции и доступны внутри нее + const result = await browser.execute((a, b) => { + return a + b; + }, 2, 3); + console.log(result); // 5 - результат выполнения в браузере + + // Выполнение кода на конкретном элементе + // Элемент передается как WebElement и доступен в функции + const element = await browser.$('.element'); + await browser.execute((el) => { + el.style.border = '2px solid red'; // Манипуляция стилями напрямую + }, element); +}); +``` + + + + From f109786bb05a74039fe4c640f5385eac5076d6b0 Mon Sep 17 00:00:00 2001 From: Nikolaengel <bratashick@gmail.com> Date: Fri, 23 Jan 2026 01:30:19 +0300 Subject: [PATCH 03/14] docs: fix-quickstart --- .../current/quickstart/running-tests.mdx | 127 +++- .../current/quickstart/writing-tests.mdx | 614 +++++++++++++----- 2 files changed, 540 insertions(+), 201 deletions(-) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx index 210a3ab1..f43c3bca 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx @@ -12,59 +12,114 @@ sidebar_position: 3 npx testplane ``` -Для запуска тестов в gui-режиме через интерфейс браузера используйте команду: +Также тесты можно запускать в gui—режиме, для этого выполните команду: ```bash npx testplane gui ``` -После выполнения одной из команд, тесты запустятся во всех браузерах, которые указаны в файле `testplane.config.ts`. -### Запуск конкретных тестов +### Запуск конкретного теста -`Grep`-фильтрация позволяет запускать тесты по названию, не меняя код. Это удобно для запуска подмножества тестов во время разработки. +У вас имеется набор тестов и вам нужно запустить только один из них. + +```javascript +const assert = require("assert"); + +describe("tests", () => { + it("Проверка отображения главной страницы", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + const title = await browser.getTitle(); + assert.ok(title.includes("Testplane")); + }); + + it("Проверка наличия логотипа на главной странице", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + const logo = await browser.$("a.navbar__brand"); + const isDisplayed = await logo.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); + + it("Проверка навигационного меню", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + const menuItems = await browser.$$("nav.navbar a.navbar__item"); + assert.ok(menuItems.length > 0); + }); + + it("Проверка наличия поля поиска", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + const searchButton = await browser.$("button.DocSearch"); + const isExisting = await searchButton.isExisting(); + assert.strictEqual(isExisting, true); + }); +}); +``` +В таком случае выполните команду: ```bash -# Запустить тесты, название которых содержит "test name" -# Поиск происходит в describe() и it() блоках -npx testplane --grep "test name" +testplane --grep "Проверка наличия поля поиска" +``` +В кавычках вам необходимо написать содержимое скобок ключевого слова `it`. + +### Запуск тестов в конкретных браузерах -# Инверсия grep - запустить все КРОМЕ совпадающих -# Полезно для исключения медленных или нестабильных тестов -npx testplane --grep "name" --invert +По умолчанию тесты запускаются в тех браузерах, которые указаны в файле `testplane.config.ts`. + +```javascript +browsers: [ + "chrome", + "firefox" +] ``` +При выполнении команды `npx testplane` тесты запустятся в браузерах Google Chrome и Mozila Firefox. -### Запуск в определенных браузерах +```bash +# Запуск во всех браузерах (по умолчанию) +testplane +``` -По умолчанию Testplane запускает тесты во всех браузерах из конфигурации. Флаг `--browser` позволяет выбрать конкретные браузеры для запуска. +Чтобы выполнить тесты в конкретном браузере, используйте команду: ```bash -# Запустить только в Chrome -# Имя браузера должно совпадать с ключом в конфиге -npx testplane --browser chrome +# Запуск только в Chrome +testplane --browser chrome +``` + +Также вы можете указать конкретный браузер для работы в теле теста. -# Запустить в нескольких браузерах одновременно -# Полезно для кросс-браузерного тестирования -npx testplane --browser chrome --browser firefox +```javascript +// tests/browser-specific.test.js +describe("Browser specific tests", () => { + it("should work in all browsers", async ({browser}) => { + await browser.url("https://example.com"); + }); + + // Пропустить тест в Safari + testplane.skip.in("safari", "Feature not supported in Safari"); + it("should work only in Chrome and Firefox", async ({browser}) => { + await browser.url("https://example.com"); + // ... тело теста + }); + + // Запустить только в Chrome + testplane.only.in("chrome"); + it("should work only in Chrome", async ({browser}) => { + await browser.url("https://example.com"); + // ... тело теста + }); +}); ``` -### Параллельный запуск +### Запуск теста из конкретного файла -Testplane может запускать тесты параллельно в нескольких процессах, что значительно ускоряет выполнение больших тестовых наборов. +Чтобы запустить тесты из конкретного файла, выполните команду: ```bash -# Запустить с 4 параллельными воркерами -# Каждый воркер - отдельный процесс Node.js -npx testplane --workers 4 - -# Альтернативно - настроить в конфигурации -# Это значение будет использоваться по умолчанию -module.exports = { - system: { - workers: 4 // Количество параллельных процессов - } -}; +# Запуск конкретного файла +testplane ../testplane-tests/example.testplane.ts ``` +Где `../testplane-tests/example.testplane.ts` это путь к файлу с тестами. + ### Режим пользовательского интерфейса @@ -72,17 +127,19 @@ module.exports = { ![](/img/docs/html-reporter/html-reporter-demo.png) -О процессах установки и настройки Testplane UI вы можете прочитать вот [здесь.](..//html-reporter//overview.mdx) +О процессах установки и настройки Testplane UI вы можете прочитать в разделе [UI.](..//html-reporter//overview.mdx) ## Отладка -### Отладка с помощью консоли +### Отладка в gui—формате + +Отслеживать процесс выполнения тестов очень легко, если запустить их в gui—режиме. В подобном формате работы html-reporter продемонстрирует, какие тесты были успешно выполнены, а в каких присутствуют ошибки и какого они характера. -Самым доступным и простым способом отладки является вывод информации в консоль: +Тут скриншот из gui testplane ### Browser.debug() -В Testplane имеется встроенный инструмент для отладки - `browser.debug`. +В Testplane имеется встроенный инструмент для отладки — `browser.debug`. ```javascript it('отладка с паузой', async ({browser}) => { diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx index ac8f8bd4..a18fe170 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx @@ -5,9 +5,28 @@ draft: true # Написание тестов -## Пример +## Структура теста -Чтобы узнать, как написать тест, ознакомьтесь с примером: +Блок `describe` предназначен для группировки связанных тестов. + +```javascript +describe('Название группы тестов', () => { + it('описание того, что должно произойти', async ({browser}) => { + // Тело теста +}); +}); +``` +В блоке `it` описываются тестовые сценарии. + +```javascript +it('описание того, что должно произойти', async ({browser}) => { + // Тело теста +}); +``` + +### Пример + +После установки testplane, вы можете ознакомиться с примером теста, для этого перейдите в папку `testplane-tests` и откройте файл `example.testplane.ts`. ```javascript describe("test examples", () => { @@ -36,23 +55,19 @@ describe("test examples", () => { }); ``` -Файл с `example.testplane.ts` находится в папке `testplane-tests`. - ## Базовый синтаксис ### Навигация -Для перемещения по страницам используйте метод `await browser.url()`. Testplane предоставляет методы для типичных навигационных действий: обновление страницы, переход назад/вперед по истории браузера. Все методы навигации являются асинхронными и должны использоваться с `await`: +Для перемещения по страницам используйте метод: + ```javascript -it('page navigation', async ({browser}) => { - // Открыть URL - базовый метод для загрузки страницы - await browser.url('https://example.com'); -}); +await browser.url("https://testplane.io/ru/"); ``` Если на странице имеются элементы, которые отображаются с задержкой, для корректного выполнения тестов укажите явное ожидание: ```javascript -await browser.url(""); +await browser.url("https://testplane.io/ru/"); await browser.$('h1').waitForExist({ timeout: 5000 }); const title = await browser.$('h1').getText(); ``` @@ -60,225 +75,492 @@ const title = await browser.$('h1').getText(); Либо используйте команду: ```javascript -await browser.openAndWait(""); +await browser.openAndWait("https://testplane.io/ru/"); ``` Команда `await browser.openAndWait()` по умолчанию дожидается загрузки всех необходимых элементов на странице. ### Селекторы -Testplane поддерживает различные стратегии поиска элементов: `CSS` селекторы (самые распространенные), текстовые селекторы (по содержимому), `XPath` для сложных запросов. Метод `$()` возвращает первый найденный элемент, а `$$()` - массив всех подходящих элементов: +Testplane поддерживает различные стратегии поиска элементов: `CSS` селекторы (самые распространенные), текстовые селекторы (по содержимому), `XPath` для сложных запросов. Метод `$()` возвращает первый найденный элемент, а `$$()` — массив всех подходящих элементов: ```javascript -it('different selectors', async ({browser}) => { - // CSS селекторы - стандартный способ поиска элементов - await browser.$('.class-name'); // По классу - await browser.$('#id-name'); // По ID - - // Текстовый селектор - поиск элемента с точным совпадением текста - // Знак = означает точное совпадение - const link = await browser.$('a=Click me'); - - // Частичное совпадение текста - знак * означает "содержит" - const button = await browser.$('button*=Submit'); - - // XPath - мощный язык запросов для сложных поисков - await browser.$('//button[@type="submit"]'); - - // Поиск нескольких элементов - возвращает массив - const items = await browser.$$('.list-item'); - console.log(items.length); // Количество найденных элементов +const assert = require("assert"); + +describe("tests", () => { + it("Проверка отображения главной страницы", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + const title = await browser.getTitle(); + assert.ok(title.includes("Testplane")); + }); + + it("Проверка наличия логотипа на главной странице", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + const logo = await browser.$("a.navbar__brand"); + const isDisplayed = await logo.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); + + it("Проверка навигационного меню", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + const menuItems = await browser.$$("nav.navbar a.navbar__item"); + assert.ok(menuItems.length > 0); + }); + + it("Проверка наличия поля поиска", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + const searchButton = await browser.$("button.DocSearch"); + const isExisting = await searchButton.isExisting(); + assert.strictEqual(isExisting, true); + }); }); ``` ### Взаимодействия с элементами -Для поиска элементов используйте метод `$()` (один элемент) или `$$()` (множество элементов). После нахождения элемента можно выполнить различные действия: клик, ввод текста, двойной клик. Метод `setValue()` очищает поле и вводит новое значение, а `addValue()` добавляет текст к существующему: +Для поиска элементов используйте метод `$()` (один элемент) или `$$()` (множество элементов). После нахождения элемента можно выполнить различные действия: клик, ввод текста, двойной клик. ```javascript -it('clicks and text input', async ({browser}) => { - // Найти элемент по CSS селектору и выполнить клик - const button = await browser.$('.submit-button'); - await button.click(); - - // Найти поле ввода и установить значение (предварительно очистив поле) - const input = await browser.$('#username'); - await input.setValue('my-username'); - - // Очистить поле вручную и добавить текст к существующему содержимому - await input.clearValue(); - await input.addValue('new-text'); - - // Двойной клик - полезно для выделения текста или специальных действий - await button.doubleClick(); +const assert = require("assert"); + +describe("tests, () => { - // Клик правой кнопкой мыши - для вызова контекстного меню - await button.click({button: 'right'}); + it("Пример клика - открытие поиска", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + // Клик по кнопке поиска + const searchButton = await browser.$("button.DocSearch"); + await searchButton.waitForClickable({timeout: 5000}); + await searchButton.click(); + await browser.pause(1000); + + // Проверяем, что модальное окно поиска появилось + const searchModal = await browser.$(".DocSearch-Modal"); + const isDisplayed = await searchModal.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); + + it("Пример ввода текста - поиск по документации", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + // Открываем поиск + const searchButton = await browser.$("button.DocSearch"); + await searchButton.waitForClickable({timeout: 5000}); + await searchButton.click(); + await browser.pause(500); + + // Вводим текст в поле поиска + const searchInput = await browser.$("input.DocSearch-Input"); + await searchInput.waitForDisplayed({timeout: 5000}); + await searchInput.setValue("browser"); + await browser.pause(1000); + + // Проверяем, что текст введен + const inputValue = await searchInput.getValue(); + assert.strictEqual(inputValue, "browser"); + }); + + it("Пример двойного клика - выделение текста заголовка", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + // Находим заголовок на главной странице + const heading = await browser.$("h1"); + await heading.waitForDisplayed({timeout: 5000}); + await heading.scrollIntoView(); + await browser.pause(500); + + // Двойной клик по заголовку + await heading.doubleClick(); + await browser.pause(500); + + // Проверяем, что элемент существует и отображается + const isDisplayed = await heading.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); }); ``` ### Assertions -Testplane использует `expect API` из `WebdriverIO` для проверки состояния элементов и страницы. Все `assertions` асинхронные и автоматически ждут выполнения условия в течение заданного таймаута. Можно инвертировать проверку с помощью `.not`: +В Testplane для проверки состояния элементов и страниц задействует `expect API` из `WebdriverIO` — это позволяет формулировать утверждения (`assertions`) о том, какими должны быть свойства элементов или страницы в целом. ```javascript -it('element checks', async ({browser}) => { - const element = await browser.$('.message'); - - // Проверка видимости элемента - элемент должен быть в DOM и visible - await expect(element).toBeDisplayed(); - // Инверсия - элемент не должен быть виден - await expect(element).not.toBeDisplayed(); - - // Проверка существования в DOM - элемент есть в разметке - await expect(element).toExist(); - - // Проверка текстового содержимого элемента - await expect(element).toHaveText('Expected text'); // Точное совпадение - await expect(element).toHaveTextContaining('partial'); // Частичное совпадение - - // Проверка атрибутов элемента - await expect(element).toHaveAttribute('href', '/link'); // Точное значение - await expect(element).toHaveAttributeContaining('class', 'active'); // Частичное - - // Проверка значения input/textarea элементов - const input = await browser.$('input'); - await expect(input).toHaveValue('value'); - - // Проверка URL текущей страницы - await expect(browser).toHaveUrl('https://example.com/page'); // Полный URL - await expect(browser).toHaveUrlContaining('/page'); // Часть URL - - // Проверка заголовка страницы (тег <title>) - await expect(browser).toHaveTitle('Page Title'); +const assert = require("assert"); + +describe("tests", () => { + + it("WebdriverIO assert - проверка URL", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + // WebdriverIO expect для browser + await expect(browser).toHaveUrl("https://testplane.io/ru/"); + }); + + it("WebdriverIO assert - проверка существования элемента", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + const logo = await browser.$("a.navbar__brand"); + + // WebdriverIO expect для элемента + await expect(logo).toExist(); + }); + + it("WebdriverIO assert - проверка видимости элемента", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + const searchButton = await browser.$("button.DocSearch"); + + // WebdriverIO expect + await expect(searchButton).toBeDisplayed(); + }); + + // Примеры с Jest ассертами + it("Jest assert - проверка заголовка страницы", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + const title = await browser.getTitle(); + + // Jest expect + expect(title).toContain("Testplane"); + }); + + it("Jest assert - проверка количества элементов", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + const menuItems = await browser.$$("nav.navbar a.navbar__item"); + + // Jest expect + expect(menuItems.length).toBeGreaterThan(0); + }); + + it("Jest assert - проверка атрибута элемента", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + const docsLink = await browser.$("a[href='/ru/docs/v8/']"); + const href = await docsLink.getAttribute("href"); + + // Jest expect + expect(href).toBe("/ru/docs/v8/"); + }); + + it("Jest assert - проверка URL с регулярным выражением", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + const currentUrl = await browser.getUrl(); + + // Jest expect с regex + expect(currentUrl).toMatch(/testplane\.io/); + }); }); ``` ### Хуки -Хуки позволяют выполнять код до или после тестов, что полезно для подготовки тестового окружения и очистки. `before/after` выполняются один раз для всего блока `describe`, а `beforeEach/afterEach` - перед/после каждого теста: +Хуки в Testplane — это специальные функции, которые автоматически выполняются в определенные моменты жизненного цикла тестов. Они позволяют подготовить окружение перед тестами и очистить его после выполнения. По умолчанию доступны два вида хуков — `beforeEach` и `afterEach`, первый выполняется перед каждым тестом, а второй после. + ```javascript -describe('Tests with hooks', () => { - // Выполняется один раз перед всеми тестами в блоке - // Используется для глобальной настройки, например, авторизации - before(async ({browser}) => { - await browser.url('/setup'); - }); +const assert = require("assert"); + +describe("Примеры работы с хуками", () => { - // Выполняется один раз после всех тестов - // Полезно для финальной очистки ресурсов - after(async ({browser}) => { - // cleanup code + // beforeEach - выполняется перед каждым тестом + beforeEach(async ({browser}) => { + console.log("--- Выполняется BEFOREEACH - перед каждым тестом ---"); + await browser.url("https://testplane.io/ru/"); + await browser.pause(500); + }); + + // afterEach - выполняется после каждого теста + afterEach(async ({browser}) => { + console.log("--- Выполняется AFTEREACH - после каждого теста ---"); + const currentUrl = await browser.getUrl(); + console.log("Текущий URL:", currentUrl); + // Можно делать скриншоты, очищать данные и т.д. }); + + it("Тест 1 - проверка заголовка", async ({browser}) => { + const title = await browser.getTitle(); + assert.ok(title.includes("Testplane")); + }); + + it("Тест 2 - проверка логотипа", async ({browser}) => { + const logo = await browser.$("a.navbar__brand"); + const isDisplayed = await logo.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); + + it("Тест 3 - проверка поиска", async ({browser}) => { + const searchButton = await browser.$("button.DocSearch"); + const isExisting = await searchButton.isExisting(); + assert.strictEqual(isExisting, true); + }); +}); + +describe("Пример вложенных describe с хуками", () => { - // Выполняется перед каждым тестом - // Гарантирует чистое начальное состояние для каждого теста beforeEach(async ({browser}) => { - await browser.url('/'); + console.log("--- OUTER BEFOREEACH ---"); + await browser.url("https://testplane.io/ru/"); }); - - // Выполняется после каждого теста - // Очистка состояния между тестами (cookies, localStorage и т.д.) + afterEach(async ({browser}) => { - await browser.deleteCookies(); + console.log("--- OUTER AFTEREACH ---"); }); - - it('test 1', async ({browser}) => { - // beforeEach -> test 1 -> afterEach + + it("Внешний тест", async ({browser}) => { + const title = await browser.getTitle(); + assert.ok(title.length > 0); }); - - it('test 2', async ({browser}) => { - // beforeEach -> test 2 -> afterEach + + describe("Внутренний блок тестов", () => { + + beforeEach(async ({browser}) => { + console.log("--- INNER BEFOREEACH ---"); + // Сначала выполнится outer beforeEach, потом этот + await browser.url("https://testplane.io/ru/docs/v8/"); + }); + + afterEach(async ({browser}) => { + console.log("--- INNER AFTEREACH ---"); + // Сначала выполнится этот afterEach, потом outer + }); + + it("Внутренний тест 1", async ({browser}) => { + const currentUrl = await browser.getUrl(); + assert.ok(currentUrl.includes("docs")); + }); + + it("Внутренний тест 2", async ({browser}) => { + const heading = await browser.$("h1"); + const isDisplayed = await heading.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); }); }); ``` ### Ожидания -Явные ожидания необходимы для работы с динамическим контентом, который загружается или изменяется асинхронно. Testplane автоматически ждет появления элементов, но для сложных сценариев можно использовать специальные методы ожидания: +Явные ожидания необходимы для работы с динамическим контентом, который загружается или изменяется асинхронно. Testplane автоматически ждет появления элементов, но для сложных сценариев можно использовать специальные методы ожидания. ```javascript -it('waiting for elements', async ({browser}) => { - const element = await browser.$('.dynamic-content'); - - // Ждать пока элемент станет видимым (display !== none, visibility !== hidden) - // Таймаут по умолчанию можно переопределить - await element.waitForDisplayed({timeout: 5000}); - - // Ждать появления элемента в DOM (может быть невидимым) - await element.waitForExist(); - - // Ждать пока элемент станет кликабельным (видим и enabled) - // Полезно для кнопок, которые могут быть disabled - await element.waitForClickable(); +const assert = require("assert"); + +describe("Примеры ожиданий в Testplane", () => { - // Кастомное условие ожидания с помощью waitUntil - // Функция проверяется повторно до истечения таймаута - await browser.waitUntil( - async () => { - const text = await element.getText(); - return text === 'Loaded'; - }, - { + it("Ожидание появления и кликабельности элемента", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + // Ожидаем, что кнопка поиска появится на странице + const searchButton = await browser.$("button.DocSearch"); + await searchButton.waitForDisplayed({ timeout: 5000, - timeoutMsg: 'Text did not appear' // Кастомное сообщение об ошибке - } - ); + timeoutMsg: "Кнопка поиска не появилась в течение 5 секунд" + }); + + // Ожидаем, что элемент станет кликабельным + await searchButton.waitForClickable({ + timeout: 3000, + timeoutMsg: "Кнопка поиска не стала кликабельной" + }); + + await searchButton.click(); + + // Ожидаем появления модального окна поиска + const searchModal = await browser.$(".DocSearch-Modal"); + await searchModal.waitForDisplayed({timeout: 3000}); + + const isDisplayed = await searchModal.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); + + it("Ожидание изменения текста элемента", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + const heading = await browser.$("h1"); + + // Ожидаем, что элемент будет существовать + await heading.waitForExist({ + timeout: 5000, + timeoutMsg: "Заголовок не найден на странице" + }); + + // Ожидаем, что у элемента будет определенный текст + await heading.waitUntil( + async function() { + const text = await this.getText(); + return text.length > 0; + }, + { + timeout: 5000, + timeoutMsg: "Текст заголовка не появился" + } + ); + + const text = await heading.getText(); + assert.ok(text.length > 0); + }); + + it("Ожидание с использованием browser.waitUntil для проверки URL", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + const docsLink = await browser.$("a[href='/ru/docs/v8/']"); + await docsLink.waitForExist({timeout: 5000}); + + // Используем JavaScript click для надежности + await browser.execute((el) => el.click(), docsLink); + + // Ожидаем изменения URL с помощью browser.waitUntil + await browser.waitUntil( + async () => { + const currentUrl = await browser.getUrl(); + return currentUrl.includes("docs"); + }, + { + timeout: 5000, + timeoutMsg: "URL не изменился на страницу документации" + } + ); + + const finalUrl = await browser.getUrl(); + assert.ok(finalUrl.includes("docs")); + }); }); ``` ### Работа с формами -Testplane предоставляет специальные методы для работы с различными элементами форм. Чекбоксы и радио-кнопки управляются через клик. Для выпадающих списков `(<select>)` есть удобные методы выбора опций по видимому тексту или значению атрибута: +Testplane предоставляет специальные методы для работы с различными элементами форм. Чекбоксы и радио—кнопки управляются через клик. Для выпадающих списков `(<select>)` есть удобные методы выбора опций по видимому тексту или значению атрибута. ```javascript -it('form filling', async ({browser}) => { - // Ввод в текстовое поле - стандартный способ заполнения input элементов - await browser.$('#email').setValue('test@example.com'); - - // Работа с чекбоксом - клик переключает состояние checked/unchecked - const checkbox = await browser.$('#agree'); - await checkbox.click(); - - // Выбор радио-кнопки - поиск по значению value и клик - await browser.$('input[value="option1"]').click(); - - // Работа с выпадающим списком - несколько способов выбора опции - const select = await browser.$('#country'); - // Выбор по видимому тексту в списке - await select.selectByVisibleText('Russia'); - // Выбор по значению атрибута value - await select.selectByAttribute('value', 'ru'); +const assert = require("assert"); + +describe("Примеры работы с формами в Testplane", () => { + + it("Работа с чекбоксами через клик", async ({browser}) => { + // Для демонстрации используем страницу с формой + await browser.url("https://the-internet.herokuapp.com/checkboxes"); + + const checkbox1 = await browser.$("#checkboxes input:nth-child(1)"); + await checkbox1.waitForDisplayed({timeout: 5000}); + + // Проверяем начальное состояние + let isSelected = await checkbox1.isSelected(); + assert.strictEqual(isSelected, false); + + // Кликаем для выбора + await checkbox1.click(); + isSelected = await checkbox1.isSelected(); + assert.strictEqual(isSelected, true); + + // Кликаем еще раз для снятия выбора + await checkbox1.click(); + isSelected = await checkbox1.isSelected(); + assert.strictEqual(isSelected, false); + }); + + it("Работа с радио—кнопками через клик", async ({browser}) => { + await browser.url("https://the-internet.herokuapp.com/"); + + // Переходим на страницу с примерами + const link = await browser.$("a[href='/forgot_password']"); + await link.click(); + + // Работа с полем email (как пример радио-кнопок) + const emailInput = await browser.$("#email"); + await emailInput.waitForDisplayed({timeout: 5000}); + await emailInput.setValue("test@example.com"); + + const value = await emailInput.getValue(); + assert.ok(value.includes("test@example.com")); + }); + + it("Работа с выпадающим списком (select) — выбор по тексту", async ({browser}) => { + await browser.url("https://the-internet.herokuapp.com/dropdown"); + + const dropdown = await browser.$("#dropdown"); + await dropdown.waitForDisplayed({timeout: 5000}); + + // Выбор опции по видимому тексту + await dropdown.selectByVisibleText("Option 1"); + + // Проверяем выбранное значение + let selectedValue = await dropdown.getValue(); + assert.strictEqual(selectedValue, "1"); + + // Получаем текст выбранной опции + let selectedText = await dropdown.$("option:checked").getText(); + assert.strictEqual(selectedText, "Option 1"); + }); + + it("Отправка формы и проверка результата", async ({browser}) => { + await browser.url("https://the-internet.herokuapp.com/login"); + + // Заполняем форму + const usernameInput = await browser.$("#username"); + const passwordInput = await browser.$("#password"); + const submitButton = await browser.$("button[type='submit']"); + + await usernameInput.setValue("tomsmith"); + await passwordInput.setValue("SuperSecretPassword!"); + + // Отправляем форму + await submitButton.click(); + + // Ждем появления сообщения об успехе + const successMessage = await browser.$("#flash"); + await successMessage.waitForDisplayed({timeout: 5000}); + + const messageText = await successMessage.getText(); + assert.ok(messageText.includes("You logged into a secure area!")); + }); }); ``` ### Работа с JavaScript кодом -Иногда нужно выполнить произвольный JavaScript код в контексте страницы - например, для работы с `localStorage`, вызова функций или манипуляции `DOM` напрямую. Метод `execute()` выполняет код в браузере и может возвращать результат: +Иногда нужно выполнить произвольный JavaScript код в контексте страницы — например, для работы с `localStorage`, вызова функций или манипуляции `DOM` напрямую. Метод `execute()` выполняет код в браузере и может возвращать результат. ```javascript -it('executing custom JS', async ({browser}) => { - await browser.url('/'); +const assert = require("assert"); + +describe("Примеры работы с JavaScript кодом", () => { - // Выполнить JavaScript в контексте браузера - // Код выполняется на странице, а не в Node.js - await browser.execute(() => { - window.localStorage.setItem('key', 'value'); + it("Выполнение JavaScript кода в контексте страницы", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + // Пример 1: Получение данных из localStorage + await browser.execute(() => { + localStorage.setItem("testKey", "testValue"); + localStorage.setItem("userName", "John Doe"); + }); + + const storageValue = await browser.execute(() => { + return localStorage.getItem("testKey"); + }); + assert.strictEqual(storageValue, "testValue"); + + // Пример 2: Манипуляция DOM — изменение текста элемента + const newText = await browser.execute(() => { + const heading = document.querySelector("h1"); + if (heading) { + const originalText = heading.textContent; + heading.textContent = "Измененный заголовок"; + return originalText; + } + return ""; + }); + + const modifiedHeading = await browser.$("h1"); + const currentText = await modifiedHeading.getText(); + assert.strictEqual(currentText, "Измененный заголовок"); + + // Пример 3: Вызов функции с параметрами + const sum = await browser.execute((a, b) => { + return a + b; + }, 5, 10); + assert.strictEqual(sum, 15); }); - - // Передача аргументов и получение результата - // Аргументы передаются после функции и доступны внутри нее - const result = await browser.execute((a, b) => { - return a + b; - }, 2, 3); - console.log(result); // 5 - результат выполнения в браузере - - // Выполнение кода на конкретном элементе - // Элемент передается как WebElement и доступен в функции - const element = await browser.$('.element'); - await browser.execute((el) => { - el.style.border = '2px solid red'; // Манипуляция стилями напрямую - }, element); }); ``` From ec6296bbe946643342fe095c432fa008b646ed06 Mon Sep 17 00:00:00 2001 From: Nikolaengel <bratashick@gmail.com> Date: Fri, 23 Jan 2026 02:18:51 +0300 Subject: [PATCH 04/14] docs: quickstart-fix --- .../current/quickstart/running-tests.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx index f43c3bca..a0ad1b11 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx @@ -165,7 +165,6 @@ it('отладка с паузой', async ({browser}) => { ``` - ### Отладка через Testplane UI Наиболее удобным способом для работы с отладкой тестов является UI режим, в нем вы можете в реальном времени наблюдать выполнения тестов. From bd295dd86a90a3db86c0750a4f84dce6f6d5ec7e Mon Sep 17 00:00:00 2001 From: Nikolaengel <bratashick@gmail.com> Date: Fri, 23 Jan 2026 02:23:58 +0300 Subject: [PATCH 05/14] docs: writingtests-fix --- .../current/quickstart/running-tests.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx index a0ad1b11..34394e0c 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx @@ -12,7 +12,7 @@ sidebar_position: 3 npx testplane ``` -Также тесты можно запускать в gui—режиме, для этого выполните команду: +Также тесты можно запускать в `gui`—режиме, для этого выполните команду: ```bash npx testplane gui @@ -133,7 +133,7 @@ testplane ../testplane-tests/example.testplane.ts ### Отладка в gui—формате -Отслеживать процесс выполнения тестов очень легко, если запустить их в gui—режиме. В подобном формате работы html-reporter продемонстрирует, какие тесты были успешно выполнены, а в каких присутствуют ошибки и какого они характера. +Отслеживать процесс выполнения тестов очень легко, если запустить их в `gui`—режиме. В подобном формате работы html-reporter продемонстрирует, какие тесты были успешно выполнены, а в каких присутствуют ошибки и какого они характера. Тут скриншот из gui testplane From f12bc41eb9e36a47040494725b4f9f9512780636 Mon Sep 17 00:00:00 2001 From: Nikolaengel <bratashick@gmail.com> Date: Fri, 23 Jan 2026 02:31:42 +0300 Subject: [PATCH 06/14] docs: runningtests-fix --- .../current/quickstart/running-tests.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx index 34394e0c..5ec79c85 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx @@ -58,7 +58,7 @@ describe("tests", () => { ```bash testplane --grep "Проверка наличия поля поиска" ``` -В кавычках вам необходимо написать содержимое скобок ключевого слова `it`. +В кавычках вам необходимо передать содержимое скобок ключевого слова `it`. ### Запуск тестов в конкретных браузерах From 9246463b0185fb220c6ce99471d2cb0cf26c4e44 Mon Sep 17 00:00:00 2001 From: Nikolaengel <bratashick@gmail.com> Date: Thu, 22 Jan 2026 16:39:00 +0300 Subject: [PATCH 07/14] docs: selectors --- docs/guides/selectors.mdx | 602 ++++++++++++++++++ .../current/guides/selectors.mdx | 602 ++++++++++++++++++ 2 files changed, 1204 insertions(+) create mode 100644 docs/guides/selectors.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/guides/selectors.mdx diff --git a/docs/guides/selectors.mdx b/docs/guides/selectors.mdx new file mode 100644 index 00000000..8ecb8390 --- /dev/null +++ b/docs/guides/selectors.mdx @@ -0,0 +1,602 @@ +# Селекторы + +Testplane предоставляет множество способов для поиска элементов на странице браузера с помощью селекторов. Для этого используются библиотеки `WebDriverIO` и `testing-library`. Селекторы позволяют точно идентифицировать элементы интерфейса, что необходимо для автоматизации тестирования. + +## WebDriverIO + +WebDriverIO — это Node.js-библиотека для автоматизации браузеров, которая реализует протокол WebDriver (W3C стандарт) В Testplane она используется для управления браузерами и взаимодействия с веб-элементами. + +### CSS селекторы + +#### По классу + +Чтобы найти элемент на странице по классу, используйте селектор `".class-name"`. + +```javascript +// Поиск кнопки с классом "btn-primary" +const button = await browser.$(".btn-primary"); +await button.click(); + +// Поиск нескольких элементов +const menuItems = await browser.$$(".nav-item"); +console.log("Количество пунктов меню: ${menuItems.length}"); +``` +Стоит использовать, если: + +- класс является стабильным и не генерируется динамически; +- нужен быстрый и простой способ найти элемент; +- класс семантически описывает элемент (например, `.error-message`, `.success-banner`); +- вы работаете с компонентными библиотеками, где классы являются частью API. + +#### По id + +Чтобы найти элемент по `id`, используйте селектор вида `"#id"`. + +```javascript +// Поиск формы id +const loginForm = await browser.$("#login-form"); +const isDisplayed = await loginForm.isDisplayed(); +``` +Стоит использовать, если: + +- вы работаете с критически важными элементами (формы, модальные окна, главные контейнеры); +- `id` является частью публичного API компонента; +- нужна максимальная производительность селектора (`id` — самый быстрый селектор). + +#### По типу атрибута + +Для поиска элемента по атрибуту, используйте селектор вида `input[type="name"]`. + +```javascript +// Поиск всех чекбоксов +const checkboxes = await browser.$$("input[type="checkbox"]"); +for (const checkbox of checkboxes) { + await checkbox.click(); +} + +// Поиск email input +const emailInput = await browser.$("input[type="email"]"); +await emailInput.setValue("test@example.com"); + +// Поиск скрытых полей +const hiddenField = await browser.$("input[type="hidden"][name="csrf_token"]"); +const csrfValue = await hiddenField.getValue(); +``` + +Стоит использовать, если: + +- нужно найти все элементы определённого типа (все чекбоксы, все радиокнопки); +- вы тестируете формы и валидацию; +- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`); +- вам нужно убедиться, что используется правильный тип поля для `accessibility`; +- вы тестируете различное поведение для разных типов полей. + +#### CSS-селекторы по атрибуту data-testid + +Для поиска элементов, которые помечены дял тестирования, используйте селекторы по атрибуту `data-testid`. + +```javascript +// Поиск элемента по data-testid +const userAvatar = await browser.$("[data-testid="user-avatar"]"); +await userAvatar.waitForDisplayed({ timeout: 5000 }); + +// Клик по кнопке с data-testid +const deleteButton = await browser.$("[data-testid="delete-user-btn"]"); +await deleteButton.click(); + +// Получение текста из элемента +const errorMessage = await browser.$("[data-testid="error-notification"]"); +const text = await errorMessage.getText(); +expect(text).toContain("Ошибка валидации"); +``` +Стоит использовать, если: + +- создаёте селекторы специально для тестирования; +- нужна стабильность селекторов независимо от изменений UI/стилей; +- работаете в команде, где дизайнеры часто меняют классы и структуру; +- хотите явно пометить элементы, доступные для тестирования; + +#### CSS-комбинированные селекторы + +Комбинированные селекторы позволяют находить элементы в определённом контексте. + +```javascript +// Потомок: кнопка внутри модального окна +const modalButton = await browser.$(".modal .close-button"); +await modalButton.click(); + +// Прямой потомок: первый уровень вложенности +const navItem = await browser.$(".navigation > .nav-item"); + +// Соседний элемент +const errorLabel = await browser.$("input.invalid + .error-message"); +const errorText = await errorLabel.getText(); + +// Сложная комбинация +const activeTab = await browser.$("ul.tabs > li.active > a[href^="#"]"); +``` + +Стоит использовать, если: + +- нужно найти элемент в определённом контексте; +- работаете с повторяющейся структурой; +- нужно убедиться в правильной вложенности элементов; +- простые селекторы слишком неспецифичны; +- тестируете связанные элементы. + +#### CSS-псевдоселекторы + +Псевдоселекторы позволяют выбирать элементы на основе их положения или состояния. + +```javascript +// Первый элемент списка +const firstItem = await browser.$("ul.menu > li:first-child"); +await firstItem.click(); + +// Последний элемент +const lastItem = await browser.$(".breadcrumb > li:last-child"); +const currentPage = await lastItem.getText(); + +// N-ый элемент (третий пункт меню) +const thirdItem = await browser.$(".menu-item:nth-child(3)"); + +// Каждый второй элемент (чётные) +const evenRows = await browser.$$("table tr:nth-child(even)"); + +// Отключённые элементы +const disabledButtons = await browser.$$("button:disabled"); +expect(disabledButtons.length).toBe(2); + +// Проверенные чекбоксы +const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); +``` + +Стоит использовать, если: + +- тестируете позиционирование элементов; +- проверяете состояния элементов (`disabled`, `checked`, `focus`); +- работаете с таблицами и нужно выбрать определённую строку или столбец; +- тестируете паттерны (чётные/нечётные строки для `zebra-striping`); +- нужно найти элемент по его позиции, когда нет других идентификаторов. + +### XPath селекторы + +#### По тексту элемента + +Чтобы найти элемент по содержащемуся в нем тексту, используйте селектор `//element[text()="text"]`. + +```javascript +// Точное совпадение текста +const loginButton = await browser.$("//button[text()="Войти в систему"]"); +await loginButton.click(); + +// Частичное совпадение +const successMessage = await browser.$("//div[contains(text(), "успешно")]"); +await successMessage.waitForDisplayed(); + +// Текст с пробелами и переносами +const heading = await browser.$("//h1[normalize-space()="Добро пожаловать"]"); + +// Поиск по тексту в дочернем элементе +const card = await browser.$("//div[@class="card"][.//h3[text()="Premium план"]]"); + +// Case-insensitive поиск (XPath 2.0) +const link = await browser.$("//a[contains(translate(text(), "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"), "поиск")]"); +``` + +Стоит использовать, если: + +- текст элемента уникален и стабилен (названия кнопок, заголовки); +- нет других идентификаторов (нет `data-testid`, `id`, классов); +- тестируете, что правильный текст отображается в правильном месте; +- работаете с динамически генерируемым контентом, где единственный стабильный элемент — текст; +- нужно найти элемент, содержащий определённый текст внутри себя или в дочерних элементах. + +#### По атрибутам + +Для поиска элемента по атрибуту, используйте селектор вида `//element[@type="atribute"]`. + +```javascript +// Поиск по одному атрибуту +const nameInput = await browser.$("//input[@name="username"]"); +await nameInput.setValue("john_doe"); + +// Множественные условия (AND) +const activeModal = await browser.$("//div[@class="modal" and @data-visible="true"]"); + +// Условие OR +const submitBtn = await browser.$("//button[@type="submit" or @class="btn-submit"]"); + +// Поиск по началу значения атрибута +const imageJpg = await browser.$("//img[starts-with(@src, "/images/")]"); + +// Поиск по концу значения атрибута +const pdfLink = await browser.$("//a[substring(@href, string-length(@href) - 3) = ".pdf"]"); + +// Поиск по частичному совпадению атрибута +const dataElement = await browser.$("//div[contains(@data-component, "user-profile")]"); + +// NOT условие +const notDisabledButton = await browser.$("//button[not(@disabled)]"); +``` + +Стоит использовать, если: + +- нужны сложные условия поиска (комбинации атрибутов); +- работаете с динамическими атрибутами (`data`-атрибуты с переменными значениями); +- нужна гибкость в поиске (частичные совпадения, начало/конец строки); +- CSS-селекторы не могут выразить нужную логику; +- нужно найти элемент по отсутствию атрибута. + +#### Навигация по DOM + +Исползуя XPath, вы можете навигировать по DOM-дереву. + +```javascript +// Прямой родитель +const parentDiv = await browser.$("//input[@name="email"]/.."); + +// Предок с условием +const formContainer = await browser.$("//input[@name="email"]/ancestor::form[@id="registration"]"); + +// Следующий сиблинг +const errorLabel = await browser.$("//input[@class="invalid"]/following-sibling::span[@class="error"][1]"); + +// Предыдущий сиблинг +const label = await browser.$("//input[@name="password"]/preceding-sibling::label[1]"); + +// Все потомки +const allInputs = await browser.$$("//form[@id="checkout"]//input"); + +// Прямые дети +const directChildren = await browser.$$("//ul[@class="menu"]/li"); + +// Поиск «дяди» элемента (родитель -> сиблинг родителя) +const siblingSection = await browser.$("//h2[text()="Контакты"]/../following-sibling::section[1]"); +``` + +Стоит использовать, если: + +- нужно найти элемент относительно другого известного элемента; +- структура DOM сложна, но относительные позиции стабильны; +- тестируете связанные элементы (label и input, ошибка рядом с полем); +- нужно подняться вверх по DOM-дереву от найденного элемента; +- работаете с семантической структурой HTML (заголовок и следующая за ним секция). + +#### XPath: индексы и позиции + +XPath позволяет выбирать элементы по их позиции в наборе результатов. + +```javascript +// Первый элемент в результатах XPath +const firstButton = await browser.$("(//button[@class="action"])[1]"); +await firstButton.click(); + +// Последний элемент +const lastItem = await browser.$("(//li[@class="menu-item"])[last()]"); + +// Второй элемент +const secondRow = await browser.$("(//table[@id="results"]//tr)[2]"); + +// Предпоследний +const secondToLast = await browser.$("(//div[@class="card"])[last()-1]"); + +// Диапазон элементов (все кроме первого) +const allButFirst = await browser.$$("(//li[@class="item"])[position() > 1]"); + +// Каждый третий элемент +const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 = 0]"); +``` + +Стоит использовать, если: + +- нужен доступ к элементу по его позиции в наборе результатов; +- тестируете пагинацию или списки с определённым порядком; +- работаете с таблицами и нужна конкретная строка; +- нужен первый или последний элемент среди нескольких одинаковых; +- тестируете сортировку (проверка, что элемент на правильной позиции). + +### Селекторы по Link Text + +Селекторы по содержащемуся внутри тексту позволяют находить ссылки `(<a>)` по их тексту. Используйте `="text"`, чтобы найти элемент с точным текстом и `*="text"` для поиска по частичному совпадению текста. + +```javascript +// Полное совпадение текста ссылки +const loginLink = await browser.$("=Войти в систему"); +await loginLink.click(); + +// Частичное совпадение +const docsLink = await browser.$("*=Документация"); +await docsLink.click(); +``` +Стоит использовать, если: + +- работаете с навигационными ссылками с уникальным текстом; +- тестируете меню и навигацию сайта; +- текст ссылки является частью требований и не должен меняться; +- нужна простота и читаемость теста; +- тестируете наличие ссылок с правильным текстом на странице. + +### Селекторы по имени тега + +Чтобы найти элемент по их HTML-тегу, используйте селектор по имени тега. + +```javascript +// Поиск первой кнопки на странице +const button = await browser.$("button"); +await button.click(); + +// Все параграфы +const paragraphs = await browser.$$("p"); +const textsArray = await Promise.all( + paragraphs.map(p => p.getText()) +); + +// Все изображения +const images = await browser.$$("img"); +for (const img of images) { + const alt = await img.getAttribute("alt"); + expect(alt).not.toBe(""); // проверка accessibility +} + +// Форма +const form = await browser.$("form"); +await form.waitForDisplayed(); + +// Таблица +const table = await browser.$("table"); +const rows = await table.$$("tr"); +``` + +Стоит использовать, если: + +- на странице один элемент данного типа (например, единственная форма); +- нужно получить все элементы определённого типа для массовой проверки; +- тестируете семантичность HTML (наличие правильных тегов); +- работаете с базовой HTML-структурой (`form`, `table`, `ul`); +- проводите accessibility-аудит (проверка всех `img` на наличие `alt`). + +### React селекторы + +Для поиска экомпонентов в React-приложении по их имени, используйте react-селекторы, например `react$("MyButton")` найдет компонент `MyButton`, а `react$("Button" { props: { variant: "primary", size: "large"}})` найдет кнопку с определенными параметрами. + +```javascript +// Поиск React-компонента по имени +const myButton = await browser.react$("MyButton"); +await myButton.click(); + +// С фильтрацией по параметрами +const primaryButton = await browser.react$("Button", { + props: { variant: "primary", size: "large" } +}); + +// С фильтрацией по state +const openModal = await browser.react$("Modal", { + state: { isOpen: true, activeTab: 'settings' } +}); + +// Все экземпляры компонента +const allCards = await browser.react$$("ProductCard"); +console.log("Найдено карточек: ${allCards.length}"); + +// Вложенный поиск +const form = await browser.react$("CheckoutForm"); +const submitButton = await form.react$("SubmitButton"); + +// С комплексными параметрами +const userProfile = await browser.react$("UserProfile", { + props: { + user: { id: 123, role: "admin" }, + editable: true + } +}); +``` + +Стоит использовать, если: + +- работаете с React-приложением и имеете доступ к исходному коду; +- нужно найти компонент по его параметрам или `state`; +- тестируете, что компонент рендерится с правильными данными; +- структура `DOM` может меняться, но API компонента стабилен; +- нужна глубокая интеграция с React DevTools. + +### Shadow DOM селекторы + +Shadow DOM селекторы позволяют работать с элементами внутри Shadow DOM — инкапсулированной части DOM-дерева. Например, если у вас есть кастомный элемент `my-custom-element`, вы можете найти кнопку внутри его Shadow DOM с помощью `shadow$("button")`. + +```javascript +// Простой доступ в Shadow DOM +const customElement = await browser.$("my-custom-element"); +const button = await customElement.shadow$("button"); +await button.click(); + +// Множественные элементы в Shadow DOM +const slotElements = await customElement.shadow$$(".slot-item"); +``` + +Стоит использовать, если: + +- работаете с Web Components и Custom Elements; +- приложение использует Shadow DOM для инкапсуляции стилей; +- тестируете компоненты из сторонних библиотек (Lit, Stencil, native Web Components); +- нужен доступ к элементам внутри shadow root; +- работаете с дизайн-системой на базе Web Components. + +## Testing-library + +Testing Library — это адаптер популярной философии Testing Library для Testplane. Она предоставляет селекторы, ориентированные на пользовательский опыт (как пользователи находят элементы). + +### ByRole + +`getByRole` — основной метод в Testing Library, который позволяет находить элементы по их ARIA-ролям. Например, если вы используете метод `screen.getByRole("button", { name: /submit/i })`, то найдете кнопку с текстом, содержащим `submit`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск кнопки +const submitButton = screen.getByRole("button", { name: /submit/i }); +await userEvent.click(submitButton); + +// Поиск текстового поля +const emailInput = screen.getByRole('textbox', { name: /email/i }); +await userEvent.type(emailInput, "test@example.com"); + +// Поиск чекбокса +const agreeCheckbox = screen.getByRole("checkbox", { name: /agree to terms/i }); +await userEvent.click(agreeCheckbox); +``` + +Стоит использовать, если: + +- для любых интерактивных элементов (кнопки, ссылки, поля ввода); +- для структурных элементов (`navigation`, `main`, `header`, `footer`); +- для форм и их элементов (`radio`, `checkbox`, `combobox`); +- для заголовков и важных текстовых элементов; +- для списков и таблиц. + + +### ByLabelText + +Для поиска элементов форм по тексту их меток (`label`), используйте метод `getByLabelText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по тексту label (полное совпадение) +const emailInput = screen.getByLabelText("Email Address"); +await userEvent.type(emailInput, "user@example.com"); + +// Поиск с регулярным выражением (частичное совпадение, case-insensitive) +const passwordInput = screen.getByLabelText(/password/i); +await userEvent.type(passwordInput, "secure123"); +``` + +Стоит использовать, если: + +- работаете с формами; +- нужно найти input, select, textarea по связанной метке; +- занимаетесь тестированием доступности (наличие `label` обязательно для доступности); +- метка уникальна и описательна. + +### ByPlaceholderText + +Чтобы найти поле ввода по тексту `placeholder`, используйте селектор `getByPlaceholderText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по placeholder +const searchInput = screen.getByPlaceholderText("Search..."); +await userEvent.type(searchInput, "testing library"); + +// С регулярным выражением +const emailInput = screen.getByPlaceholderText(/enter.*email/i); +await userEvent.type(emailInput, "test@example.com"); +``` + +Стоит использовать, если: + +- у поля нет `label`, но есть `placeholder`; +- занимаетесь тестированием legacy-кода, где `placeholder` используется вместо `label`; +- `placeholder` достаточно описателен и уникален. + +### ByText + +Чтобы найти текстовый элемент по его содержимому, используйте метод `getByText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по точному тексту +const heading = screen.getByText("Welcome to our application"); +expect(heading).toBeInTheDocument(); + +// С регулярным выражением (частичное совпадение) +const errorMessage = screen.getByText(/error.*occurred/i); +expect(errorMessage).toHaveClass("error"); +``` + +Стоит использовать, если: + +- необходимо найти неинтерактивные текстовые элементов (параграфы, заголовки, уведомления); +- занимаетесь проверкой отображения текста на странице; +- текст уникален и является частью требований. + +### ByDisplayValue + +Для поиска элемента по их текущему значению, используйте метод `getByDisplayValue`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск input с конкретным значением +const emailInput = screen.getByDisplayValue("user@example.com"); +expect(emailInput).toBeInTheDocument(); + +// Поиск с регулярным выражением +const searchInput = screen.getByDisplayValue(/search query/i); +``` + +Стоит использовать, если: + +- необходимо протестировать предзаполненных форм (edit forms, profile pages); +- нужно проверить установку корректного значения после действия; +- необходимо найти элемент по его текущему значению, а не по label. + +### ByAltText + +Для поиска изображений по тексту `alt`, используйте метод `getByAltText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск изображения по alt тексту +const logo = screen.getByAltText("Company Logo"); +expect(logo).toBeInTheDocument(); +expect(logo).toHaveAttribute("src", "/images/logo.png"); +``` + +Стоит использовать, если: + +- нужно работать с изображениями (`<img>`, `<area>`, `<input type="image">`); +- необходимо проверить доступность изображений (наличие `alt` обязательно); +- `alt`-текст достаточно описателен и уникален. + +### ByTitle + +Чтобы найти элемент по атрибуту title, используйте метод getByTitle. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск элемента по title атрибуту +const closeButton = screen.getByTitle("Close dialog"); +await userEvent.click(closeButton); +``` + +Стоит использовать, если: + +- необходимо работать с элементами, которые используют `title` для `tooltips`; +- нужно протестировать `iframe`-элементы (`title` обязателен для доступности); +- нужно взаимодействовать с `SVG`-элементами (`title` внутри `SVG` для описания). + +### ByTestId + +`getByTestId` используется как последний вариант, когда другие методы не подходят. Например, если вы используете `screen.getByTestId("submit-button")`, то найдте элемент с атрибутом `data-testid="submit-button"` + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Базовое использование +const submitButton = screen.getByTestId("submit-button"); +await userEvent.click(submitButton); +``` + diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/selectors.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/selectors.mdx new file mode 100644 index 00000000..8ecb8390 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/selectors.mdx @@ -0,0 +1,602 @@ +# Селекторы + +Testplane предоставляет множество способов для поиска элементов на странице браузера с помощью селекторов. Для этого используются библиотеки `WebDriverIO` и `testing-library`. Селекторы позволяют точно идентифицировать элементы интерфейса, что необходимо для автоматизации тестирования. + +## WebDriverIO + +WebDriverIO — это Node.js-библиотека для автоматизации браузеров, которая реализует протокол WebDriver (W3C стандарт) В Testplane она используется для управления браузерами и взаимодействия с веб-элементами. + +### CSS селекторы + +#### По классу + +Чтобы найти элемент на странице по классу, используйте селектор `".class-name"`. + +```javascript +// Поиск кнопки с классом "btn-primary" +const button = await browser.$(".btn-primary"); +await button.click(); + +// Поиск нескольких элементов +const menuItems = await browser.$$(".nav-item"); +console.log("Количество пунктов меню: ${menuItems.length}"); +``` +Стоит использовать, если: + +- класс является стабильным и не генерируется динамически; +- нужен быстрый и простой способ найти элемент; +- класс семантически описывает элемент (например, `.error-message`, `.success-banner`); +- вы работаете с компонентными библиотеками, где классы являются частью API. + +#### По id + +Чтобы найти элемент по `id`, используйте селектор вида `"#id"`. + +```javascript +// Поиск формы id +const loginForm = await browser.$("#login-form"); +const isDisplayed = await loginForm.isDisplayed(); +``` +Стоит использовать, если: + +- вы работаете с критически важными элементами (формы, модальные окна, главные контейнеры); +- `id` является частью публичного API компонента; +- нужна максимальная производительность селектора (`id` — самый быстрый селектор). + +#### По типу атрибута + +Для поиска элемента по атрибуту, используйте селектор вида `input[type="name"]`. + +```javascript +// Поиск всех чекбоксов +const checkboxes = await browser.$$("input[type="checkbox"]"); +for (const checkbox of checkboxes) { + await checkbox.click(); +} + +// Поиск email input +const emailInput = await browser.$("input[type="email"]"); +await emailInput.setValue("test@example.com"); + +// Поиск скрытых полей +const hiddenField = await browser.$("input[type="hidden"][name="csrf_token"]"); +const csrfValue = await hiddenField.getValue(); +``` + +Стоит использовать, если: + +- нужно найти все элементы определённого типа (все чекбоксы, все радиокнопки); +- вы тестируете формы и валидацию; +- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`); +- вам нужно убедиться, что используется правильный тип поля для `accessibility`; +- вы тестируете различное поведение для разных типов полей. + +#### CSS-селекторы по атрибуту data-testid + +Для поиска элементов, которые помечены дял тестирования, используйте селекторы по атрибуту `data-testid`. + +```javascript +// Поиск элемента по data-testid +const userAvatar = await browser.$("[data-testid="user-avatar"]"); +await userAvatar.waitForDisplayed({ timeout: 5000 }); + +// Клик по кнопке с data-testid +const deleteButton = await browser.$("[data-testid="delete-user-btn"]"); +await deleteButton.click(); + +// Получение текста из элемента +const errorMessage = await browser.$("[data-testid="error-notification"]"); +const text = await errorMessage.getText(); +expect(text).toContain("Ошибка валидации"); +``` +Стоит использовать, если: + +- создаёте селекторы специально для тестирования; +- нужна стабильность селекторов независимо от изменений UI/стилей; +- работаете в команде, где дизайнеры часто меняют классы и структуру; +- хотите явно пометить элементы, доступные для тестирования; + +#### CSS-комбинированные селекторы + +Комбинированные селекторы позволяют находить элементы в определённом контексте. + +```javascript +// Потомок: кнопка внутри модального окна +const modalButton = await browser.$(".modal .close-button"); +await modalButton.click(); + +// Прямой потомок: первый уровень вложенности +const navItem = await browser.$(".navigation > .nav-item"); + +// Соседний элемент +const errorLabel = await browser.$("input.invalid + .error-message"); +const errorText = await errorLabel.getText(); + +// Сложная комбинация +const activeTab = await browser.$("ul.tabs > li.active > a[href^="#"]"); +``` + +Стоит использовать, если: + +- нужно найти элемент в определённом контексте; +- работаете с повторяющейся структурой; +- нужно убедиться в правильной вложенности элементов; +- простые селекторы слишком неспецифичны; +- тестируете связанные элементы. + +#### CSS-псевдоселекторы + +Псевдоселекторы позволяют выбирать элементы на основе их положения или состояния. + +```javascript +// Первый элемент списка +const firstItem = await browser.$("ul.menu > li:first-child"); +await firstItem.click(); + +// Последний элемент +const lastItem = await browser.$(".breadcrumb > li:last-child"); +const currentPage = await lastItem.getText(); + +// N-ый элемент (третий пункт меню) +const thirdItem = await browser.$(".menu-item:nth-child(3)"); + +// Каждый второй элемент (чётные) +const evenRows = await browser.$$("table tr:nth-child(even)"); + +// Отключённые элементы +const disabledButtons = await browser.$$("button:disabled"); +expect(disabledButtons.length).toBe(2); + +// Проверенные чекбоксы +const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); +``` + +Стоит использовать, если: + +- тестируете позиционирование элементов; +- проверяете состояния элементов (`disabled`, `checked`, `focus`); +- работаете с таблицами и нужно выбрать определённую строку или столбец; +- тестируете паттерны (чётные/нечётные строки для `zebra-striping`); +- нужно найти элемент по его позиции, когда нет других идентификаторов. + +### XPath селекторы + +#### По тексту элемента + +Чтобы найти элемент по содержащемуся в нем тексту, используйте селектор `//element[text()="text"]`. + +```javascript +// Точное совпадение текста +const loginButton = await browser.$("//button[text()="Войти в систему"]"); +await loginButton.click(); + +// Частичное совпадение +const successMessage = await browser.$("//div[contains(text(), "успешно")]"); +await successMessage.waitForDisplayed(); + +// Текст с пробелами и переносами +const heading = await browser.$("//h1[normalize-space()="Добро пожаловать"]"); + +// Поиск по тексту в дочернем элементе +const card = await browser.$("//div[@class="card"][.//h3[text()="Premium план"]]"); + +// Case-insensitive поиск (XPath 2.0) +const link = await browser.$("//a[contains(translate(text(), "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"), "поиск")]"); +``` + +Стоит использовать, если: + +- текст элемента уникален и стабилен (названия кнопок, заголовки); +- нет других идентификаторов (нет `data-testid`, `id`, классов); +- тестируете, что правильный текст отображается в правильном месте; +- работаете с динамически генерируемым контентом, где единственный стабильный элемент — текст; +- нужно найти элемент, содержащий определённый текст внутри себя или в дочерних элементах. + +#### По атрибутам + +Для поиска элемента по атрибуту, используйте селектор вида `//element[@type="atribute"]`. + +```javascript +// Поиск по одному атрибуту +const nameInput = await browser.$("//input[@name="username"]"); +await nameInput.setValue("john_doe"); + +// Множественные условия (AND) +const activeModal = await browser.$("//div[@class="modal" and @data-visible="true"]"); + +// Условие OR +const submitBtn = await browser.$("//button[@type="submit" or @class="btn-submit"]"); + +// Поиск по началу значения атрибута +const imageJpg = await browser.$("//img[starts-with(@src, "/images/")]"); + +// Поиск по концу значения атрибута +const pdfLink = await browser.$("//a[substring(@href, string-length(@href) - 3) = ".pdf"]"); + +// Поиск по частичному совпадению атрибута +const dataElement = await browser.$("//div[contains(@data-component, "user-profile")]"); + +// NOT условие +const notDisabledButton = await browser.$("//button[not(@disabled)]"); +``` + +Стоит использовать, если: + +- нужны сложные условия поиска (комбинации атрибутов); +- работаете с динамическими атрибутами (`data`-атрибуты с переменными значениями); +- нужна гибкость в поиске (частичные совпадения, начало/конец строки); +- CSS-селекторы не могут выразить нужную логику; +- нужно найти элемент по отсутствию атрибута. + +#### Навигация по DOM + +Исползуя XPath, вы можете навигировать по DOM-дереву. + +```javascript +// Прямой родитель +const parentDiv = await browser.$("//input[@name="email"]/.."); + +// Предок с условием +const formContainer = await browser.$("//input[@name="email"]/ancestor::form[@id="registration"]"); + +// Следующий сиблинг +const errorLabel = await browser.$("//input[@class="invalid"]/following-sibling::span[@class="error"][1]"); + +// Предыдущий сиблинг +const label = await browser.$("//input[@name="password"]/preceding-sibling::label[1]"); + +// Все потомки +const allInputs = await browser.$$("//form[@id="checkout"]//input"); + +// Прямые дети +const directChildren = await browser.$$("//ul[@class="menu"]/li"); + +// Поиск «дяди» элемента (родитель -> сиблинг родителя) +const siblingSection = await browser.$("//h2[text()="Контакты"]/../following-sibling::section[1]"); +``` + +Стоит использовать, если: + +- нужно найти элемент относительно другого известного элемента; +- структура DOM сложна, но относительные позиции стабильны; +- тестируете связанные элементы (label и input, ошибка рядом с полем); +- нужно подняться вверх по DOM-дереву от найденного элемента; +- работаете с семантической структурой HTML (заголовок и следующая за ним секция). + +#### XPath: индексы и позиции + +XPath позволяет выбирать элементы по их позиции в наборе результатов. + +```javascript +// Первый элемент в результатах XPath +const firstButton = await browser.$("(//button[@class="action"])[1]"); +await firstButton.click(); + +// Последний элемент +const lastItem = await browser.$("(//li[@class="menu-item"])[last()]"); + +// Второй элемент +const secondRow = await browser.$("(//table[@id="results"]//tr)[2]"); + +// Предпоследний +const secondToLast = await browser.$("(//div[@class="card"])[last()-1]"); + +// Диапазон элементов (все кроме первого) +const allButFirst = await browser.$$("(//li[@class="item"])[position() > 1]"); + +// Каждый третий элемент +const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 = 0]"); +``` + +Стоит использовать, если: + +- нужен доступ к элементу по его позиции в наборе результатов; +- тестируете пагинацию или списки с определённым порядком; +- работаете с таблицами и нужна конкретная строка; +- нужен первый или последний элемент среди нескольких одинаковых; +- тестируете сортировку (проверка, что элемент на правильной позиции). + +### Селекторы по Link Text + +Селекторы по содержащемуся внутри тексту позволяют находить ссылки `(<a>)` по их тексту. Используйте `="text"`, чтобы найти элемент с точным текстом и `*="text"` для поиска по частичному совпадению текста. + +```javascript +// Полное совпадение текста ссылки +const loginLink = await browser.$("=Войти в систему"); +await loginLink.click(); + +// Частичное совпадение +const docsLink = await browser.$("*=Документация"); +await docsLink.click(); +``` +Стоит использовать, если: + +- работаете с навигационными ссылками с уникальным текстом; +- тестируете меню и навигацию сайта; +- текст ссылки является частью требований и не должен меняться; +- нужна простота и читаемость теста; +- тестируете наличие ссылок с правильным текстом на странице. + +### Селекторы по имени тега + +Чтобы найти элемент по их HTML-тегу, используйте селектор по имени тега. + +```javascript +// Поиск первой кнопки на странице +const button = await browser.$("button"); +await button.click(); + +// Все параграфы +const paragraphs = await browser.$$("p"); +const textsArray = await Promise.all( + paragraphs.map(p => p.getText()) +); + +// Все изображения +const images = await browser.$$("img"); +for (const img of images) { + const alt = await img.getAttribute("alt"); + expect(alt).not.toBe(""); // проверка accessibility +} + +// Форма +const form = await browser.$("form"); +await form.waitForDisplayed(); + +// Таблица +const table = await browser.$("table"); +const rows = await table.$$("tr"); +``` + +Стоит использовать, если: + +- на странице один элемент данного типа (например, единственная форма); +- нужно получить все элементы определённого типа для массовой проверки; +- тестируете семантичность HTML (наличие правильных тегов); +- работаете с базовой HTML-структурой (`form`, `table`, `ul`); +- проводите accessibility-аудит (проверка всех `img` на наличие `alt`). + +### React селекторы + +Для поиска экомпонентов в React-приложении по их имени, используйте react-селекторы, например `react$("MyButton")` найдет компонент `MyButton`, а `react$("Button" { props: { variant: "primary", size: "large"}})` найдет кнопку с определенными параметрами. + +```javascript +// Поиск React-компонента по имени +const myButton = await browser.react$("MyButton"); +await myButton.click(); + +// С фильтрацией по параметрами +const primaryButton = await browser.react$("Button", { + props: { variant: "primary", size: "large" } +}); + +// С фильтрацией по state +const openModal = await browser.react$("Modal", { + state: { isOpen: true, activeTab: 'settings' } +}); + +// Все экземпляры компонента +const allCards = await browser.react$$("ProductCard"); +console.log("Найдено карточек: ${allCards.length}"); + +// Вложенный поиск +const form = await browser.react$("CheckoutForm"); +const submitButton = await form.react$("SubmitButton"); + +// С комплексными параметрами +const userProfile = await browser.react$("UserProfile", { + props: { + user: { id: 123, role: "admin" }, + editable: true + } +}); +``` + +Стоит использовать, если: + +- работаете с React-приложением и имеете доступ к исходному коду; +- нужно найти компонент по его параметрам или `state`; +- тестируете, что компонент рендерится с правильными данными; +- структура `DOM` может меняться, но API компонента стабилен; +- нужна глубокая интеграция с React DevTools. + +### Shadow DOM селекторы + +Shadow DOM селекторы позволяют работать с элементами внутри Shadow DOM — инкапсулированной части DOM-дерева. Например, если у вас есть кастомный элемент `my-custom-element`, вы можете найти кнопку внутри его Shadow DOM с помощью `shadow$("button")`. + +```javascript +// Простой доступ в Shadow DOM +const customElement = await browser.$("my-custom-element"); +const button = await customElement.shadow$("button"); +await button.click(); + +// Множественные элементы в Shadow DOM +const slotElements = await customElement.shadow$$(".slot-item"); +``` + +Стоит использовать, если: + +- работаете с Web Components и Custom Elements; +- приложение использует Shadow DOM для инкапсуляции стилей; +- тестируете компоненты из сторонних библиотек (Lit, Stencil, native Web Components); +- нужен доступ к элементам внутри shadow root; +- работаете с дизайн-системой на базе Web Components. + +## Testing-library + +Testing Library — это адаптер популярной философии Testing Library для Testplane. Она предоставляет селекторы, ориентированные на пользовательский опыт (как пользователи находят элементы). + +### ByRole + +`getByRole` — основной метод в Testing Library, который позволяет находить элементы по их ARIA-ролям. Например, если вы используете метод `screen.getByRole("button", { name: /submit/i })`, то найдете кнопку с текстом, содержащим `submit`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск кнопки +const submitButton = screen.getByRole("button", { name: /submit/i }); +await userEvent.click(submitButton); + +// Поиск текстового поля +const emailInput = screen.getByRole('textbox', { name: /email/i }); +await userEvent.type(emailInput, "test@example.com"); + +// Поиск чекбокса +const agreeCheckbox = screen.getByRole("checkbox", { name: /agree to terms/i }); +await userEvent.click(agreeCheckbox); +``` + +Стоит использовать, если: + +- для любых интерактивных элементов (кнопки, ссылки, поля ввода); +- для структурных элементов (`navigation`, `main`, `header`, `footer`); +- для форм и их элементов (`radio`, `checkbox`, `combobox`); +- для заголовков и важных текстовых элементов; +- для списков и таблиц. + + +### ByLabelText + +Для поиска элементов форм по тексту их меток (`label`), используйте метод `getByLabelText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по тексту label (полное совпадение) +const emailInput = screen.getByLabelText("Email Address"); +await userEvent.type(emailInput, "user@example.com"); + +// Поиск с регулярным выражением (частичное совпадение, case-insensitive) +const passwordInput = screen.getByLabelText(/password/i); +await userEvent.type(passwordInput, "secure123"); +``` + +Стоит использовать, если: + +- работаете с формами; +- нужно найти input, select, textarea по связанной метке; +- занимаетесь тестированием доступности (наличие `label` обязательно для доступности); +- метка уникальна и описательна. + +### ByPlaceholderText + +Чтобы найти поле ввода по тексту `placeholder`, используйте селектор `getByPlaceholderText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по placeholder +const searchInput = screen.getByPlaceholderText("Search..."); +await userEvent.type(searchInput, "testing library"); + +// С регулярным выражением +const emailInput = screen.getByPlaceholderText(/enter.*email/i); +await userEvent.type(emailInput, "test@example.com"); +``` + +Стоит использовать, если: + +- у поля нет `label`, но есть `placeholder`; +- занимаетесь тестированием legacy-кода, где `placeholder` используется вместо `label`; +- `placeholder` достаточно описателен и уникален. + +### ByText + +Чтобы найти текстовый элемент по его содержимому, используйте метод `getByText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по точному тексту +const heading = screen.getByText("Welcome to our application"); +expect(heading).toBeInTheDocument(); + +// С регулярным выражением (частичное совпадение) +const errorMessage = screen.getByText(/error.*occurred/i); +expect(errorMessage).toHaveClass("error"); +``` + +Стоит использовать, если: + +- необходимо найти неинтерактивные текстовые элементов (параграфы, заголовки, уведомления); +- занимаетесь проверкой отображения текста на странице; +- текст уникален и является частью требований. + +### ByDisplayValue + +Для поиска элемента по их текущему значению, используйте метод `getByDisplayValue`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск input с конкретным значением +const emailInput = screen.getByDisplayValue("user@example.com"); +expect(emailInput).toBeInTheDocument(); + +// Поиск с регулярным выражением +const searchInput = screen.getByDisplayValue(/search query/i); +``` + +Стоит использовать, если: + +- необходимо протестировать предзаполненных форм (edit forms, profile pages); +- нужно проверить установку корректного значения после действия; +- необходимо найти элемент по его текущему значению, а не по label. + +### ByAltText + +Для поиска изображений по тексту `alt`, используйте метод `getByAltText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск изображения по alt тексту +const logo = screen.getByAltText("Company Logo"); +expect(logo).toBeInTheDocument(); +expect(logo).toHaveAttribute("src", "/images/logo.png"); +``` + +Стоит использовать, если: + +- нужно работать с изображениями (`<img>`, `<area>`, `<input type="image">`); +- необходимо проверить доступность изображений (наличие `alt` обязательно); +- `alt`-текст достаточно описателен и уникален. + +### ByTitle + +Чтобы найти элемент по атрибуту title, используйте метод getByTitle. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск элемента по title атрибуту +const closeButton = screen.getByTitle("Close dialog"); +await userEvent.click(closeButton); +``` + +Стоит использовать, если: + +- необходимо работать с элементами, которые используют `title` для `tooltips`; +- нужно протестировать `iframe`-элементы (`title` обязателен для доступности); +- нужно взаимодействовать с `SVG`-элементами (`title` внутри `SVG` для описания). + +### ByTestId + +`getByTestId` используется как последний вариант, когда другие методы не подходят. Например, если вы используете `screen.getByTestId("submit-button")`, то найдте элемент с атрибутом `data-testid="submit-button"` + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Базовое использование +const submitButton = screen.getByTestId("submit-button"); +await userEvent.click(submitButton); +``` + From f9688e7ff52e862751523a8de65420067cabbacd Mon Sep 17 00:00:00 2001 From: Nikolaengel <bratashick@gmail.com> Date: Thu, 22 Jan 2026 16:41:28 +0300 Subject: [PATCH 08/14] docs: selectors-fix --- docs/guides/selectors.mdx | 602 ------------------ .../current/basic-guides/selectors.mdx | 24 +- .../current/guides/selectors.mdx | 602 ------------------ 3 files changed, 11 insertions(+), 1217 deletions(-) delete mode 100644 docs/guides/selectors.mdx delete mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/guides/selectors.mdx diff --git a/docs/guides/selectors.mdx b/docs/guides/selectors.mdx deleted file mode 100644 index 8ecb8390..00000000 --- a/docs/guides/selectors.mdx +++ /dev/null @@ -1,602 +0,0 @@ -# Селекторы - -Testplane предоставляет множество способов для поиска элементов на странице браузера с помощью селекторов. Для этого используются библиотеки `WebDriverIO` и `testing-library`. Селекторы позволяют точно идентифицировать элементы интерфейса, что необходимо для автоматизации тестирования. - -## WebDriverIO - -WebDriverIO — это Node.js-библиотека для автоматизации браузеров, которая реализует протокол WebDriver (W3C стандарт) В Testplane она используется для управления браузерами и взаимодействия с веб-элементами. - -### CSS селекторы - -#### По классу - -Чтобы найти элемент на странице по классу, используйте селектор `".class-name"`. - -```javascript -// Поиск кнопки с классом "btn-primary" -const button = await browser.$(".btn-primary"); -await button.click(); - -// Поиск нескольких элементов -const menuItems = await browser.$$(".nav-item"); -console.log("Количество пунктов меню: ${menuItems.length}"); -``` -Стоит использовать, если: - -- класс является стабильным и не генерируется динамически; -- нужен быстрый и простой способ найти элемент; -- класс семантически описывает элемент (например, `.error-message`, `.success-banner`); -- вы работаете с компонентными библиотеками, где классы являются частью API. - -#### По id - -Чтобы найти элемент по `id`, используйте селектор вида `"#id"`. - -```javascript -// Поиск формы id -const loginForm = await browser.$("#login-form"); -const isDisplayed = await loginForm.isDisplayed(); -``` -Стоит использовать, если: - -- вы работаете с критически важными элементами (формы, модальные окна, главные контейнеры); -- `id` является частью публичного API компонента; -- нужна максимальная производительность селектора (`id` — самый быстрый селектор). - -#### По типу атрибута - -Для поиска элемента по атрибуту, используйте селектор вида `input[type="name"]`. - -```javascript -// Поиск всех чекбоксов -const checkboxes = await browser.$$("input[type="checkbox"]"); -for (const checkbox of checkboxes) { - await checkbox.click(); -} - -// Поиск email input -const emailInput = await browser.$("input[type="email"]"); -await emailInput.setValue("test@example.com"); - -// Поиск скрытых полей -const hiddenField = await browser.$("input[type="hidden"][name="csrf_token"]"); -const csrfValue = await hiddenField.getValue(); -``` - -Стоит использовать, если: - -- нужно найти все элементы определённого типа (все чекбоксы, все радиокнопки); -- вы тестируете формы и валидацию; -- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`); -- вам нужно убедиться, что используется правильный тип поля для `accessibility`; -- вы тестируете различное поведение для разных типов полей. - -#### CSS-селекторы по атрибуту data-testid - -Для поиска элементов, которые помечены дял тестирования, используйте селекторы по атрибуту `data-testid`. - -```javascript -// Поиск элемента по data-testid -const userAvatar = await browser.$("[data-testid="user-avatar"]"); -await userAvatar.waitForDisplayed({ timeout: 5000 }); - -// Клик по кнопке с data-testid -const deleteButton = await browser.$("[data-testid="delete-user-btn"]"); -await deleteButton.click(); - -// Получение текста из элемента -const errorMessage = await browser.$("[data-testid="error-notification"]"); -const text = await errorMessage.getText(); -expect(text).toContain("Ошибка валидации"); -``` -Стоит использовать, если: - -- создаёте селекторы специально для тестирования; -- нужна стабильность селекторов независимо от изменений UI/стилей; -- работаете в команде, где дизайнеры часто меняют классы и структуру; -- хотите явно пометить элементы, доступные для тестирования; - -#### CSS-комбинированные селекторы - -Комбинированные селекторы позволяют находить элементы в определённом контексте. - -```javascript -// Потомок: кнопка внутри модального окна -const modalButton = await browser.$(".modal .close-button"); -await modalButton.click(); - -// Прямой потомок: первый уровень вложенности -const navItem = await browser.$(".navigation > .nav-item"); - -// Соседний элемент -const errorLabel = await browser.$("input.invalid + .error-message"); -const errorText = await errorLabel.getText(); - -// Сложная комбинация -const activeTab = await browser.$("ul.tabs > li.active > a[href^="#"]"); -``` - -Стоит использовать, если: - -- нужно найти элемент в определённом контексте; -- работаете с повторяющейся структурой; -- нужно убедиться в правильной вложенности элементов; -- простые селекторы слишком неспецифичны; -- тестируете связанные элементы. - -#### CSS-псевдоселекторы - -Псевдоселекторы позволяют выбирать элементы на основе их положения или состояния. - -```javascript -// Первый элемент списка -const firstItem = await browser.$("ul.menu > li:first-child"); -await firstItem.click(); - -// Последний элемент -const lastItem = await browser.$(".breadcrumb > li:last-child"); -const currentPage = await lastItem.getText(); - -// N-ый элемент (третий пункт меню) -const thirdItem = await browser.$(".menu-item:nth-child(3)"); - -// Каждый второй элемент (чётные) -const evenRows = await browser.$$("table tr:nth-child(even)"); - -// Отключённые элементы -const disabledButtons = await browser.$$("button:disabled"); -expect(disabledButtons.length).toBe(2); - -// Проверенные чекбоксы -const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); -``` - -Стоит использовать, если: - -- тестируете позиционирование элементов; -- проверяете состояния элементов (`disabled`, `checked`, `focus`); -- работаете с таблицами и нужно выбрать определённую строку или столбец; -- тестируете паттерны (чётные/нечётные строки для `zebra-striping`); -- нужно найти элемент по его позиции, когда нет других идентификаторов. - -### XPath селекторы - -#### По тексту элемента - -Чтобы найти элемент по содержащемуся в нем тексту, используйте селектор `//element[text()="text"]`. - -```javascript -// Точное совпадение текста -const loginButton = await browser.$("//button[text()="Войти в систему"]"); -await loginButton.click(); - -// Частичное совпадение -const successMessage = await browser.$("//div[contains(text(), "успешно")]"); -await successMessage.waitForDisplayed(); - -// Текст с пробелами и переносами -const heading = await browser.$("//h1[normalize-space()="Добро пожаловать"]"); - -// Поиск по тексту в дочернем элементе -const card = await browser.$("//div[@class="card"][.//h3[text()="Premium план"]]"); - -// Case-insensitive поиск (XPath 2.0) -const link = await browser.$("//a[contains(translate(text(), "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"), "поиск")]"); -``` - -Стоит использовать, если: - -- текст элемента уникален и стабилен (названия кнопок, заголовки); -- нет других идентификаторов (нет `data-testid`, `id`, классов); -- тестируете, что правильный текст отображается в правильном месте; -- работаете с динамически генерируемым контентом, где единственный стабильный элемент — текст; -- нужно найти элемент, содержащий определённый текст внутри себя или в дочерних элементах. - -#### По атрибутам - -Для поиска элемента по атрибуту, используйте селектор вида `//element[@type="atribute"]`. - -```javascript -// Поиск по одному атрибуту -const nameInput = await browser.$("//input[@name="username"]"); -await nameInput.setValue("john_doe"); - -// Множественные условия (AND) -const activeModal = await browser.$("//div[@class="modal" and @data-visible="true"]"); - -// Условие OR -const submitBtn = await browser.$("//button[@type="submit" or @class="btn-submit"]"); - -// Поиск по началу значения атрибута -const imageJpg = await browser.$("//img[starts-with(@src, "/images/")]"); - -// Поиск по концу значения атрибута -const pdfLink = await browser.$("//a[substring(@href, string-length(@href) - 3) = ".pdf"]"); - -// Поиск по частичному совпадению атрибута -const dataElement = await browser.$("//div[contains(@data-component, "user-profile")]"); - -// NOT условие -const notDisabledButton = await browser.$("//button[not(@disabled)]"); -``` - -Стоит использовать, если: - -- нужны сложные условия поиска (комбинации атрибутов); -- работаете с динамическими атрибутами (`data`-атрибуты с переменными значениями); -- нужна гибкость в поиске (частичные совпадения, начало/конец строки); -- CSS-селекторы не могут выразить нужную логику; -- нужно найти элемент по отсутствию атрибута. - -#### Навигация по DOM - -Исползуя XPath, вы можете навигировать по DOM-дереву. - -```javascript -// Прямой родитель -const parentDiv = await browser.$("//input[@name="email"]/.."); - -// Предок с условием -const formContainer = await browser.$("//input[@name="email"]/ancestor::form[@id="registration"]"); - -// Следующий сиблинг -const errorLabel = await browser.$("//input[@class="invalid"]/following-sibling::span[@class="error"][1]"); - -// Предыдущий сиблинг -const label = await browser.$("//input[@name="password"]/preceding-sibling::label[1]"); - -// Все потомки -const allInputs = await browser.$$("//form[@id="checkout"]//input"); - -// Прямые дети -const directChildren = await browser.$$("//ul[@class="menu"]/li"); - -// Поиск «дяди» элемента (родитель -> сиблинг родителя) -const siblingSection = await browser.$("//h2[text()="Контакты"]/../following-sibling::section[1]"); -``` - -Стоит использовать, если: - -- нужно найти элемент относительно другого известного элемента; -- структура DOM сложна, но относительные позиции стабильны; -- тестируете связанные элементы (label и input, ошибка рядом с полем); -- нужно подняться вверх по DOM-дереву от найденного элемента; -- работаете с семантической структурой HTML (заголовок и следующая за ним секция). - -#### XPath: индексы и позиции - -XPath позволяет выбирать элементы по их позиции в наборе результатов. - -```javascript -// Первый элемент в результатах XPath -const firstButton = await browser.$("(//button[@class="action"])[1]"); -await firstButton.click(); - -// Последний элемент -const lastItem = await browser.$("(//li[@class="menu-item"])[last()]"); - -// Второй элемент -const secondRow = await browser.$("(//table[@id="results"]//tr)[2]"); - -// Предпоследний -const secondToLast = await browser.$("(//div[@class="card"])[last()-1]"); - -// Диапазон элементов (все кроме первого) -const allButFirst = await browser.$$("(//li[@class="item"])[position() > 1]"); - -// Каждый третий элемент -const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 = 0]"); -``` - -Стоит использовать, если: - -- нужен доступ к элементу по его позиции в наборе результатов; -- тестируете пагинацию или списки с определённым порядком; -- работаете с таблицами и нужна конкретная строка; -- нужен первый или последний элемент среди нескольких одинаковых; -- тестируете сортировку (проверка, что элемент на правильной позиции). - -### Селекторы по Link Text - -Селекторы по содержащемуся внутри тексту позволяют находить ссылки `(<a>)` по их тексту. Используйте `="text"`, чтобы найти элемент с точным текстом и `*="text"` для поиска по частичному совпадению текста. - -```javascript -// Полное совпадение текста ссылки -const loginLink = await browser.$("=Войти в систему"); -await loginLink.click(); - -// Частичное совпадение -const docsLink = await browser.$("*=Документация"); -await docsLink.click(); -``` -Стоит использовать, если: - -- работаете с навигационными ссылками с уникальным текстом; -- тестируете меню и навигацию сайта; -- текст ссылки является частью требований и не должен меняться; -- нужна простота и читаемость теста; -- тестируете наличие ссылок с правильным текстом на странице. - -### Селекторы по имени тега - -Чтобы найти элемент по их HTML-тегу, используйте селектор по имени тега. - -```javascript -// Поиск первой кнопки на странице -const button = await browser.$("button"); -await button.click(); - -// Все параграфы -const paragraphs = await browser.$$("p"); -const textsArray = await Promise.all( - paragraphs.map(p => p.getText()) -); - -// Все изображения -const images = await browser.$$("img"); -for (const img of images) { - const alt = await img.getAttribute("alt"); - expect(alt).not.toBe(""); // проверка accessibility -} - -// Форма -const form = await browser.$("form"); -await form.waitForDisplayed(); - -// Таблица -const table = await browser.$("table"); -const rows = await table.$$("tr"); -``` - -Стоит использовать, если: - -- на странице один элемент данного типа (например, единственная форма); -- нужно получить все элементы определённого типа для массовой проверки; -- тестируете семантичность HTML (наличие правильных тегов); -- работаете с базовой HTML-структурой (`form`, `table`, `ul`); -- проводите accessibility-аудит (проверка всех `img` на наличие `alt`). - -### React селекторы - -Для поиска экомпонентов в React-приложении по их имени, используйте react-селекторы, например `react$("MyButton")` найдет компонент `MyButton`, а `react$("Button" { props: { variant: "primary", size: "large"}})` найдет кнопку с определенными параметрами. - -```javascript -// Поиск React-компонента по имени -const myButton = await browser.react$("MyButton"); -await myButton.click(); - -// С фильтрацией по параметрами -const primaryButton = await browser.react$("Button", { - props: { variant: "primary", size: "large" } -}); - -// С фильтрацией по state -const openModal = await browser.react$("Modal", { - state: { isOpen: true, activeTab: 'settings' } -}); - -// Все экземпляры компонента -const allCards = await browser.react$$("ProductCard"); -console.log("Найдено карточек: ${allCards.length}"); - -// Вложенный поиск -const form = await browser.react$("CheckoutForm"); -const submitButton = await form.react$("SubmitButton"); - -// С комплексными параметрами -const userProfile = await browser.react$("UserProfile", { - props: { - user: { id: 123, role: "admin" }, - editable: true - } -}); -``` - -Стоит использовать, если: - -- работаете с React-приложением и имеете доступ к исходному коду; -- нужно найти компонент по его параметрам или `state`; -- тестируете, что компонент рендерится с правильными данными; -- структура `DOM` может меняться, но API компонента стабилен; -- нужна глубокая интеграция с React DevTools. - -### Shadow DOM селекторы - -Shadow DOM селекторы позволяют работать с элементами внутри Shadow DOM — инкапсулированной части DOM-дерева. Например, если у вас есть кастомный элемент `my-custom-element`, вы можете найти кнопку внутри его Shadow DOM с помощью `shadow$("button")`. - -```javascript -// Простой доступ в Shadow DOM -const customElement = await browser.$("my-custom-element"); -const button = await customElement.shadow$("button"); -await button.click(); - -// Множественные элементы в Shadow DOM -const slotElements = await customElement.shadow$$(".slot-item"); -``` - -Стоит использовать, если: - -- работаете с Web Components и Custom Elements; -- приложение использует Shadow DOM для инкапсуляции стилей; -- тестируете компоненты из сторонних библиотек (Lit, Stencil, native Web Components); -- нужен доступ к элементам внутри shadow root; -- работаете с дизайн-системой на базе Web Components. - -## Testing-library - -Testing Library — это адаптер популярной философии Testing Library для Testplane. Она предоставляет селекторы, ориентированные на пользовательский опыт (как пользователи находят элементы). - -### ByRole - -`getByRole` — основной метод в Testing Library, который позволяет находить элементы по их ARIA-ролям. Например, если вы используете метод `screen.getByRole("button", { name: /submit/i })`, то найдете кнопку с текстом, содержащим `submit`. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск кнопки -const submitButton = screen.getByRole("button", { name: /submit/i }); -await userEvent.click(submitButton); - -// Поиск текстового поля -const emailInput = screen.getByRole('textbox', { name: /email/i }); -await userEvent.type(emailInput, "test@example.com"); - -// Поиск чекбокса -const agreeCheckbox = screen.getByRole("checkbox", { name: /agree to terms/i }); -await userEvent.click(agreeCheckbox); -``` - -Стоит использовать, если: - -- для любых интерактивных элементов (кнопки, ссылки, поля ввода); -- для структурных элементов (`navigation`, `main`, `header`, `footer`); -- для форм и их элементов (`radio`, `checkbox`, `combobox`); -- для заголовков и важных текстовых элементов; -- для списков и таблиц. - - -### ByLabelText - -Для поиска элементов форм по тексту их меток (`label`), используйте метод `getByLabelText`. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск по тексту label (полное совпадение) -const emailInput = screen.getByLabelText("Email Address"); -await userEvent.type(emailInput, "user@example.com"); - -// Поиск с регулярным выражением (частичное совпадение, case-insensitive) -const passwordInput = screen.getByLabelText(/password/i); -await userEvent.type(passwordInput, "secure123"); -``` - -Стоит использовать, если: - -- работаете с формами; -- нужно найти input, select, textarea по связанной метке; -- занимаетесь тестированием доступности (наличие `label` обязательно для доступности); -- метка уникальна и описательна. - -### ByPlaceholderText - -Чтобы найти поле ввода по тексту `placeholder`, используйте селектор `getByPlaceholderText`. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск по placeholder -const searchInput = screen.getByPlaceholderText("Search..."); -await userEvent.type(searchInput, "testing library"); - -// С регулярным выражением -const emailInput = screen.getByPlaceholderText(/enter.*email/i); -await userEvent.type(emailInput, "test@example.com"); -``` - -Стоит использовать, если: - -- у поля нет `label`, но есть `placeholder`; -- занимаетесь тестированием legacy-кода, где `placeholder` используется вместо `label`; -- `placeholder` достаточно описателен и уникален. - -### ByText - -Чтобы найти текстовый элемент по его содержимому, используйте метод `getByText`. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск по точному тексту -const heading = screen.getByText("Welcome to our application"); -expect(heading).toBeInTheDocument(); - -// С регулярным выражением (частичное совпадение) -const errorMessage = screen.getByText(/error.*occurred/i); -expect(errorMessage).toHaveClass("error"); -``` - -Стоит использовать, если: - -- необходимо найти неинтерактивные текстовые элементов (параграфы, заголовки, уведомления); -- занимаетесь проверкой отображения текста на странице; -- текст уникален и является частью требований. - -### ByDisplayValue - -Для поиска элемента по их текущему значению, используйте метод `getByDisplayValue`. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск input с конкретным значением -const emailInput = screen.getByDisplayValue("user@example.com"); -expect(emailInput).toBeInTheDocument(); - -// Поиск с регулярным выражением -const searchInput = screen.getByDisplayValue(/search query/i); -``` - -Стоит использовать, если: - -- необходимо протестировать предзаполненных форм (edit forms, profile pages); -- нужно проверить установку корректного значения после действия; -- необходимо найти элемент по его текущему значению, а не по label. - -### ByAltText - -Для поиска изображений по тексту `alt`, используйте метод `getByAltText`. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск изображения по alt тексту -const logo = screen.getByAltText("Company Logo"); -expect(logo).toBeInTheDocument(); -expect(logo).toHaveAttribute("src", "/images/logo.png"); -``` - -Стоит использовать, если: - -- нужно работать с изображениями (`<img>`, `<area>`, `<input type="image">`); -- необходимо проверить доступность изображений (наличие `alt` обязательно); -- `alt`-текст достаточно описателен и уникален. - -### ByTitle - -Чтобы найти элемент по атрибуту title, используйте метод getByTitle. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск элемента по title атрибуту -const closeButton = screen.getByTitle("Close dialog"); -await userEvent.click(closeButton); -``` - -Стоит использовать, если: - -- необходимо работать с элементами, которые используют `title` для `tooltips`; -- нужно протестировать `iframe`-элементы (`title` обязателен для доступности); -- нужно взаимодействовать с `SVG`-элементами (`title` внутри `SVG` для описания). - -### ByTestId - -`getByTestId` используется как последний вариант, когда другие методы не подходят. Например, если вы используете `screen.getByTestId("submit-button")`, то найдте элемент с атрибутом `data-testid="submit-button"` - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Базовое использование -const submitButton = screen.getByTestId("submit-button"); -await userEvent.click(submitButton); -``` - diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx index 08fcc8c7..8ecb8390 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx @@ -1,7 +1,5 @@ # Селекторы -## Введение - Testplane предоставляет множество способов для поиска элементов на странице браузера с помощью селекторов. Для этого используются библиотеки `WebDriverIO` и `testing-library`. Селекторы позволяют точно идентифицировать элементы интерфейса, что необходимо для автоматизации тестирования. ## WebDriverIO @@ -47,7 +45,7 @@ const isDisplayed = await loginForm.isDisplayed(); #### По типу атрибута -Для поиска элемента по атрибуту, используйте селектор вида `"input[type="name"]"`. +Для поиска элемента по атрибуту, используйте селектор вида `input[type="name"]`. ```javascript // Поиск всех чекбоксов @@ -75,7 +73,7 @@ const csrfValue = await hiddenField.getValue(); #### CSS-селекторы по атрибуту data-testid -Для поиска элементов, которые помечены дял тестирования, используйте селекторы по атрибуту `"data-testid"`. +Для поиска элементов, которые помечены дял тестирования, используйте селекторы по атрибуту `data-testid`. ```javascript // Поиск элемента по data-testid @@ -128,7 +126,7 @@ const activeTab = await browser.$("ul.tabs > li.active > a[href^="#"]"); #### CSS-псевдоселекторы -Для выбора элементов на основе их положения или состояния, используйте псевдоселекторы. +Псевдоселекторы позволяют выбирать элементы на основе их положения или состояния. ```javascript // Первый элемент списка @@ -165,7 +163,7 @@ const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); #### По тексту элемента -Чтобы найти элемент по содержащемуся в нем тексту, используйте селектор `"//element[text()="text"]"`. +Чтобы найти элемент по содержащемуся в нем тексту, используйте селектор `//element[text()="text"]`. ```javascript // Точное совпадение текста @@ -196,7 +194,7 @@ const link = await browser.$("//a[contains(translate(text(), "АБВГДЕЁЖЗ #### По атрибутам -Для поиска элемента по атрибуту, используйте селектор вида `"//element[@type="atribute"]"`. +Для поиска элемента по атрибуту, используйте селектор вида `//element[@type="atribute"]`. ```javascript // Поиск по одному атрибуту @@ -232,7 +230,7 @@ const notDisabledButton = await browser.$("//button[not(@disabled)]"); #### Навигация по DOM -С помощью XPath вы можете навигировать по DOM-дереву. +Исползуя XPath, вы можете навигировать по DOM-дереву. ```javascript // Прямой родитель @@ -300,7 +298,7 @@ const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 ### Селекторы по Link Text -Селекторы по содержащемуся внутри тексту позволяют находить ссылки `(<a>)` по их тексту. Используйте `"=text"`, чтобы найти элемент с точным текстом и `"*=text"` для поиска по частичному совпадению текста. +Селекторы по содержащемуся внутри тексту позволяют находить ссылки `(<a>)` по их тексту. Используйте `="text"`, чтобы найти элемент с точным текстом и `*="text"` для поиска по частичному совпадению текста. ```javascript // Полное совпадение текста ссылки @@ -426,7 +424,7 @@ const slotElements = await customElement.shadow$$(".slot-item"); ## Testing-library -Testing Library — это адаптер популярной философии Testing Library для Testplane. Он предоставляет селекторы, ориентированные на пользовательский опыт (как пользователи находят элементы). +Testing Library — это адаптер популярной философии Testing Library для Testplane. Она предоставляет селекторы, ориентированные на пользовательский опыт (как пользователи находят элементы). ### ByRole @@ -460,7 +458,7 @@ await userEvent.click(agreeCheckbox); ### ByLabelText -Для поиска элементов форм по тексту их меток (`"label"`), используйте метод `getByLabelText`. +Для поиска элементов форм по тексту их меток (`label`), используйте метод `getByLabelText`. ```javascript import { screen } from "@testing-library/dom"; @@ -484,7 +482,7 @@ await userEvent.type(passwordInput, "secure123"); ### ByPlaceholderText -Чтобы найти поле ввода по тексту `placeholder`, используйте метод `getByPlaceholderText`. +Чтобы найти поле ввода по тексту `placeholder`, используйте селектор `getByPlaceholderText`. ```javascript import { screen } from "@testing-library/dom"; @@ -572,7 +570,7 @@ expect(logo).toHaveAttribute("src", "/images/logo.png"); ### ByTitle -Чтобы найти элемент по атрибуту `title`, используйте метод `getByTitle`. +Чтобы найти элемент по атрибуту title, используйте метод getByTitle. ```javascript import { screen } from "@testing-library/dom"; diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/selectors.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/selectors.mdx deleted file mode 100644 index 8ecb8390..00000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/selectors.mdx +++ /dev/null @@ -1,602 +0,0 @@ -# Селекторы - -Testplane предоставляет множество способов для поиска элементов на странице браузера с помощью селекторов. Для этого используются библиотеки `WebDriverIO` и `testing-library`. Селекторы позволяют точно идентифицировать элементы интерфейса, что необходимо для автоматизации тестирования. - -## WebDriverIO - -WebDriverIO — это Node.js-библиотека для автоматизации браузеров, которая реализует протокол WebDriver (W3C стандарт) В Testplane она используется для управления браузерами и взаимодействия с веб-элементами. - -### CSS селекторы - -#### По классу - -Чтобы найти элемент на странице по классу, используйте селектор `".class-name"`. - -```javascript -// Поиск кнопки с классом "btn-primary" -const button = await browser.$(".btn-primary"); -await button.click(); - -// Поиск нескольких элементов -const menuItems = await browser.$$(".nav-item"); -console.log("Количество пунктов меню: ${menuItems.length}"); -``` -Стоит использовать, если: - -- класс является стабильным и не генерируется динамически; -- нужен быстрый и простой способ найти элемент; -- класс семантически описывает элемент (например, `.error-message`, `.success-banner`); -- вы работаете с компонентными библиотеками, где классы являются частью API. - -#### По id - -Чтобы найти элемент по `id`, используйте селектор вида `"#id"`. - -```javascript -// Поиск формы id -const loginForm = await browser.$("#login-form"); -const isDisplayed = await loginForm.isDisplayed(); -``` -Стоит использовать, если: - -- вы работаете с критически важными элементами (формы, модальные окна, главные контейнеры); -- `id` является частью публичного API компонента; -- нужна максимальная производительность селектора (`id` — самый быстрый селектор). - -#### По типу атрибута - -Для поиска элемента по атрибуту, используйте селектор вида `input[type="name"]`. - -```javascript -// Поиск всех чекбоксов -const checkboxes = await browser.$$("input[type="checkbox"]"); -for (const checkbox of checkboxes) { - await checkbox.click(); -} - -// Поиск email input -const emailInput = await browser.$("input[type="email"]"); -await emailInput.setValue("test@example.com"); - -// Поиск скрытых полей -const hiddenField = await browser.$("input[type="hidden"][name="csrf_token"]"); -const csrfValue = await hiddenField.getValue(); -``` - -Стоит использовать, если: - -- нужно найти все элементы определённого типа (все чекбоксы, все радиокнопки); -- вы тестируете формы и валидацию; -- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`); -- вам нужно убедиться, что используется правильный тип поля для `accessibility`; -- вы тестируете различное поведение для разных типов полей. - -#### CSS-селекторы по атрибуту data-testid - -Для поиска элементов, которые помечены дял тестирования, используйте селекторы по атрибуту `data-testid`. - -```javascript -// Поиск элемента по data-testid -const userAvatar = await browser.$("[data-testid="user-avatar"]"); -await userAvatar.waitForDisplayed({ timeout: 5000 }); - -// Клик по кнопке с data-testid -const deleteButton = await browser.$("[data-testid="delete-user-btn"]"); -await deleteButton.click(); - -// Получение текста из элемента -const errorMessage = await browser.$("[data-testid="error-notification"]"); -const text = await errorMessage.getText(); -expect(text).toContain("Ошибка валидации"); -``` -Стоит использовать, если: - -- создаёте селекторы специально для тестирования; -- нужна стабильность селекторов независимо от изменений UI/стилей; -- работаете в команде, где дизайнеры часто меняют классы и структуру; -- хотите явно пометить элементы, доступные для тестирования; - -#### CSS-комбинированные селекторы - -Комбинированные селекторы позволяют находить элементы в определённом контексте. - -```javascript -// Потомок: кнопка внутри модального окна -const modalButton = await browser.$(".modal .close-button"); -await modalButton.click(); - -// Прямой потомок: первый уровень вложенности -const navItem = await browser.$(".navigation > .nav-item"); - -// Соседний элемент -const errorLabel = await browser.$("input.invalid + .error-message"); -const errorText = await errorLabel.getText(); - -// Сложная комбинация -const activeTab = await browser.$("ul.tabs > li.active > a[href^="#"]"); -``` - -Стоит использовать, если: - -- нужно найти элемент в определённом контексте; -- работаете с повторяющейся структурой; -- нужно убедиться в правильной вложенности элементов; -- простые селекторы слишком неспецифичны; -- тестируете связанные элементы. - -#### CSS-псевдоселекторы - -Псевдоселекторы позволяют выбирать элементы на основе их положения или состояния. - -```javascript -// Первый элемент списка -const firstItem = await browser.$("ul.menu > li:first-child"); -await firstItem.click(); - -// Последний элемент -const lastItem = await browser.$(".breadcrumb > li:last-child"); -const currentPage = await lastItem.getText(); - -// N-ый элемент (третий пункт меню) -const thirdItem = await browser.$(".menu-item:nth-child(3)"); - -// Каждый второй элемент (чётные) -const evenRows = await browser.$$("table tr:nth-child(even)"); - -// Отключённые элементы -const disabledButtons = await browser.$$("button:disabled"); -expect(disabledButtons.length).toBe(2); - -// Проверенные чекбоксы -const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); -``` - -Стоит использовать, если: - -- тестируете позиционирование элементов; -- проверяете состояния элементов (`disabled`, `checked`, `focus`); -- работаете с таблицами и нужно выбрать определённую строку или столбец; -- тестируете паттерны (чётные/нечётные строки для `zebra-striping`); -- нужно найти элемент по его позиции, когда нет других идентификаторов. - -### XPath селекторы - -#### По тексту элемента - -Чтобы найти элемент по содержащемуся в нем тексту, используйте селектор `//element[text()="text"]`. - -```javascript -// Точное совпадение текста -const loginButton = await browser.$("//button[text()="Войти в систему"]"); -await loginButton.click(); - -// Частичное совпадение -const successMessage = await browser.$("//div[contains(text(), "успешно")]"); -await successMessage.waitForDisplayed(); - -// Текст с пробелами и переносами -const heading = await browser.$("//h1[normalize-space()="Добро пожаловать"]"); - -// Поиск по тексту в дочернем элементе -const card = await browser.$("//div[@class="card"][.//h3[text()="Premium план"]]"); - -// Case-insensitive поиск (XPath 2.0) -const link = await browser.$("//a[contains(translate(text(), "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"), "поиск")]"); -``` - -Стоит использовать, если: - -- текст элемента уникален и стабилен (названия кнопок, заголовки); -- нет других идентификаторов (нет `data-testid`, `id`, классов); -- тестируете, что правильный текст отображается в правильном месте; -- работаете с динамически генерируемым контентом, где единственный стабильный элемент — текст; -- нужно найти элемент, содержащий определённый текст внутри себя или в дочерних элементах. - -#### По атрибутам - -Для поиска элемента по атрибуту, используйте селектор вида `//element[@type="atribute"]`. - -```javascript -// Поиск по одному атрибуту -const nameInput = await browser.$("//input[@name="username"]"); -await nameInput.setValue("john_doe"); - -// Множественные условия (AND) -const activeModal = await browser.$("//div[@class="modal" and @data-visible="true"]"); - -// Условие OR -const submitBtn = await browser.$("//button[@type="submit" or @class="btn-submit"]"); - -// Поиск по началу значения атрибута -const imageJpg = await browser.$("//img[starts-with(@src, "/images/")]"); - -// Поиск по концу значения атрибута -const pdfLink = await browser.$("//a[substring(@href, string-length(@href) - 3) = ".pdf"]"); - -// Поиск по частичному совпадению атрибута -const dataElement = await browser.$("//div[contains(@data-component, "user-profile")]"); - -// NOT условие -const notDisabledButton = await browser.$("//button[not(@disabled)]"); -``` - -Стоит использовать, если: - -- нужны сложные условия поиска (комбинации атрибутов); -- работаете с динамическими атрибутами (`data`-атрибуты с переменными значениями); -- нужна гибкость в поиске (частичные совпадения, начало/конец строки); -- CSS-селекторы не могут выразить нужную логику; -- нужно найти элемент по отсутствию атрибута. - -#### Навигация по DOM - -Исползуя XPath, вы можете навигировать по DOM-дереву. - -```javascript -// Прямой родитель -const parentDiv = await browser.$("//input[@name="email"]/.."); - -// Предок с условием -const formContainer = await browser.$("//input[@name="email"]/ancestor::form[@id="registration"]"); - -// Следующий сиблинг -const errorLabel = await browser.$("//input[@class="invalid"]/following-sibling::span[@class="error"][1]"); - -// Предыдущий сиблинг -const label = await browser.$("//input[@name="password"]/preceding-sibling::label[1]"); - -// Все потомки -const allInputs = await browser.$$("//form[@id="checkout"]//input"); - -// Прямые дети -const directChildren = await browser.$$("//ul[@class="menu"]/li"); - -// Поиск «дяди» элемента (родитель -> сиблинг родителя) -const siblingSection = await browser.$("//h2[text()="Контакты"]/../following-sibling::section[1]"); -``` - -Стоит использовать, если: - -- нужно найти элемент относительно другого известного элемента; -- структура DOM сложна, но относительные позиции стабильны; -- тестируете связанные элементы (label и input, ошибка рядом с полем); -- нужно подняться вверх по DOM-дереву от найденного элемента; -- работаете с семантической структурой HTML (заголовок и следующая за ним секция). - -#### XPath: индексы и позиции - -XPath позволяет выбирать элементы по их позиции в наборе результатов. - -```javascript -// Первый элемент в результатах XPath -const firstButton = await browser.$("(//button[@class="action"])[1]"); -await firstButton.click(); - -// Последний элемент -const lastItem = await browser.$("(//li[@class="menu-item"])[last()]"); - -// Второй элемент -const secondRow = await browser.$("(//table[@id="results"]//tr)[2]"); - -// Предпоследний -const secondToLast = await browser.$("(//div[@class="card"])[last()-1]"); - -// Диапазон элементов (все кроме первого) -const allButFirst = await browser.$$("(//li[@class="item"])[position() > 1]"); - -// Каждый третий элемент -const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 = 0]"); -``` - -Стоит использовать, если: - -- нужен доступ к элементу по его позиции в наборе результатов; -- тестируете пагинацию или списки с определённым порядком; -- работаете с таблицами и нужна конкретная строка; -- нужен первый или последний элемент среди нескольких одинаковых; -- тестируете сортировку (проверка, что элемент на правильной позиции). - -### Селекторы по Link Text - -Селекторы по содержащемуся внутри тексту позволяют находить ссылки `(<a>)` по их тексту. Используйте `="text"`, чтобы найти элемент с точным текстом и `*="text"` для поиска по частичному совпадению текста. - -```javascript -// Полное совпадение текста ссылки -const loginLink = await browser.$("=Войти в систему"); -await loginLink.click(); - -// Частичное совпадение -const docsLink = await browser.$("*=Документация"); -await docsLink.click(); -``` -Стоит использовать, если: - -- работаете с навигационными ссылками с уникальным текстом; -- тестируете меню и навигацию сайта; -- текст ссылки является частью требований и не должен меняться; -- нужна простота и читаемость теста; -- тестируете наличие ссылок с правильным текстом на странице. - -### Селекторы по имени тега - -Чтобы найти элемент по их HTML-тегу, используйте селектор по имени тега. - -```javascript -// Поиск первой кнопки на странице -const button = await browser.$("button"); -await button.click(); - -// Все параграфы -const paragraphs = await browser.$$("p"); -const textsArray = await Promise.all( - paragraphs.map(p => p.getText()) -); - -// Все изображения -const images = await browser.$$("img"); -for (const img of images) { - const alt = await img.getAttribute("alt"); - expect(alt).not.toBe(""); // проверка accessibility -} - -// Форма -const form = await browser.$("form"); -await form.waitForDisplayed(); - -// Таблица -const table = await browser.$("table"); -const rows = await table.$$("tr"); -``` - -Стоит использовать, если: - -- на странице один элемент данного типа (например, единственная форма); -- нужно получить все элементы определённого типа для массовой проверки; -- тестируете семантичность HTML (наличие правильных тегов); -- работаете с базовой HTML-структурой (`form`, `table`, `ul`); -- проводите accessibility-аудит (проверка всех `img` на наличие `alt`). - -### React селекторы - -Для поиска экомпонентов в React-приложении по их имени, используйте react-селекторы, например `react$("MyButton")` найдет компонент `MyButton`, а `react$("Button" { props: { variant: "primary", size: "large"}})` найдет кнопку с определенными параметрами. - -```javascript -// Поиск React-компонента по имени -const myButton = await browser.react$("MyButton"); -await myButton.click(); - -// С фильтрацией по параметрами -const primaryButton = await browser.react$("Button", { - props: { variant: "primary", size: "large" } -}); - -// С фильтрацией по state -const openModal = await browser.react$("Modal", { - state: { isOpen: true, activeTab: 'settings' } -}); - -// Все экземпляры компонента -const allCards = await browser.react$$("ProductCard"); -console.log("Найдено карточек: ${allCards.length}"); - -// Вложенный поиск -const form = await browser.react$("CheckoutForm"); -const submitButton = await form.react$("SubmitButton"); - -// С комплексными параметрами -const userProfile = await browser.react$("UserProfile", { - props: { - user: { id: 123, role: "admin" }, - editable: true - } -}); -``` - -Стоит использовать, если: - -- работаете с React-приложением и имеете доступ к исходному коду; -- нужно найти компонент по его параметрам или `state`; -- тестируете, что компонент рендерится с правильными данными; -- структура `DOM` может меняться, но API компонента стабилен; -- нужна глубокая интеграция с React DevTools. - -### Shadow DOM селекторы - -Shadow DOM селекторы позволяют работать с элементами внутри Shadow DOM — инкапсулированной части DOM-дерева. Например, если у вас есть кастомный элемент `my-custom-element`, вы можете найти кнопку внутри его Shadow DOM с помощью `shadow$("button")`. - -```javascript -// Простой доступ в Shadow DOM -const customElement = await browser.$("my-custom-element"); -const button = await customElement.shadow$("button"); -await button.click(); - -// Множественные элементы в Shadow DOM -const slotElements = await customElement.shadow$$(".slot-item"); -``` - -Стоит использовать, если: - -- работаете с Web Components и Custom Elements; -- приложение использует Shadow DOM для инкапсуляции стилей; -- тестируете компоненты из сторонних библиотек (Lit, Stencil, native Web Components); -- нужен доступ к элементам внутри shadow root; -- работаете с дизайн-системой на базе Web Components. - -## Testing-library - -Testing Library — это адаптер популярной философии Testing Library для Testplane. Она предоставляет селекторы, ориентированные на пользовательский опыт (как пользователи находят элементы). - -### ByRole - -`getByRole` — основной метод в Testing Library, который позволяет находить элементы по их ARIA-ролям. Например, если вы используете метод `screen.getByRole("button", { name: /submit/i })`, то найдете кнопку с текстом, содержащим `submit`. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск кнопки -const submitButton = screen.getByRole("button", { name: /submit/i }); -await userEvent.click(submitButton); - -// Поиск текстового поля -const emailInput = screen.getByRole('textbox', { name: /email/i }); -await userEvent.type(emailInput, "test@example.com"); - -// Поиск чекбокса -const agreeCheckbox = screen.getByRole("checkbox", { name: /agree to terms/i }); -await userEvent.click(agreeCheckbox); -``` - -Стоит использовать, если: - -- для любых интерактивных элементов (кнопки, ссылки, поля ввода); -- для структурных элементов (`navigation`, `main`, `header`, `footer`); -- для форм и их элементов (`radio`, `checkbox`, `combobox`); -- для заголовков и важных текстовых элементов; -- для списков и таблиц. - - -### ByLabelText - -Для поиска элементов форм по тексту их меток (`label`), используйте метод `getByLabelText`. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск по тексту label (полное совпадение) -const emailInput = screen.getByLabelText("Email Address"); -await userEvent.type(emailInput, "user@example.com"); - -// Поиск с регулярным выражением (частичное совпадение, case-insensitive) -const passwordInput = screen.getByLabelText(/password/i); -await userEvent.type(passwordInput, "secure123"); -``` - -Стоит использовать, если: - -- работаете с формами; -- нужно найти input, select, textarea по связанной метке; -- занимаетесь тестированием доступности (наличие `label` обязательно для доступности); -- метка уникальна и описательна. - -### ByPlaceholderText - -Чтобы найти поле ввода по тексту `placeholder`, используйте селектор `getByPlaceholderText`. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск по placeholder -const searchInput = screen.getByPlaceholderText("Search..."); -await userEvent.type(searchInput, "testing library"); - -// С регулярным выражением -const emailInput = screen.getByPlaceholderText(/enter.*email/i); -await userEvent.type(emailInput, "test@example.com"); -``` - -Стоит использовать, если: - -- у поля нет `label`, но есть `placeholder`; -- занимаетесь тестированием legacy-кода, где `placeholder` используется вместо `label`; -- `placeholder` достаточно описателен и уникален. - -### ByText - -Чтобы найти текстовый элемент по его содержимому, используйте метод `getByText`. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск по точному тексту -const heading = screen.getByText("Welcome to our application"); -expect(heading).toBeInTheDocument(); - -// С регулярным выражением (частичное совпадение) -const errorMessage = screen.getByText(/error.*occurred/i); -expect(errorMessage).toHaveClass("error"); -``` - -Стоит использовать, если: - -- необходимо найти неинтерактивные текстовые элементов (параграфы, заголовки, уведомления); -- занимаетесь проверкой отображения текста на странице; -- текст уникален и является частью требований. - -### ByDisplayValue - -Для поиска элемента по их текущему значению, используйте метод `getByDisplayValue`. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск input с конкретным значением -const emailInput = screen.getByDisplayValue("user@example.com"); -expect(emailInput).toBeInTheDocument(); - -// Поиск с регулярным выражением -const searchInput = screen.getByDisplayValue(/search query/i); -``` - -Стоит использовать, если: - -- необходимо протестировать предзаполненных форм (edit forms, profile pages); -- нужно проверить установку корректного значения после действия; -- необходимо найти элемент по его текущему значению, а не по label. - -### ByAltText - -Для поиска изображений по тексту `alt`, используйте метод `getByAltText`. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск изображения по alt тексту -const logo = screen.getByAltText("Company Logo"); -expect(logo).toBeInTheDocument(); -expect(logo).toHaveAttribute("src", "/images/logo.png"); -``` - -Стоит использовать, если: - -- нужно работать с изображениями (`<img>`, `<area>`, `<input type="image">`); -- необходимо проверить доступность изображений (наличие `alt` обязательно); -- `alt`-текст достаточно описателен и уникален. - -### ByTitle - -Чтобы найти элемент по атрибуту title, используйте метод getByTitle. - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск элемента по title атрибуту -const closeButton = screen.getByTitle("Close dialog"); -await userEvent.click(closeButton); -``` - -Стоит использовать, если: - -- необходимо работать с элементами, которые используют `title` для `tooltips`; -- нужно протестировать `iframe`-элементы (`title` обязателен для доступности); -- нужно взаимодействовать с `SVG`-элементами (`title` внутри `SVG` для описания). - -### ByTestId - -`getByTestId` используется как последний вариант, когда другие методы не подходят. Например, если вы используете `screen.getByTestId("submit-button")`, то найдте элемент с атрибутом `data-testid="submit-button"` - -```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Базовое использование -const submitButton = screen.getByTestId("submit-button"); -await userEvent.click(submitButton); -``` - From 9a06f74809bc25f97ea4d249914faa5f7db47bd9 Mon Sep 17 00:00:00 2001 From: Nikolaengel <bratashick@gmail.com> Date: Thu, 22 Jan 2026 17:07:31 +0300 Subject: [PATCH 09/14] docs: fix-prettier --- docs/basic-guides/selectors.mdx | 210 +++++++-------- .../current/basic-guides/selectors.mdx | 210 +++++++-------- .../current/quickstart/index.mdx | 7 +- .../current/quickstart/running-tests.mdx | 39 ++- .../current/quickstart/writing-tests.mdx | 243 +++++++++--------- 5 files changed, 349 insertions(+), 360 deletions(-) diff --git a/docs/basic-guides/selectors.mdx b/docs/basic-guides/selectors.mdx index 8ecb8390..df05d980 100644 --- a/docs/basic-guides/selectors.mdx +++ b/docs/basic-guides/selectors.mdx @@ -21,12 +21,13 @@ await button.click(); const menuItems = await browser.$$(".nav-item"); console.log("Количество пунктов меню: ${menuItems.length}"); ``` + Стоит использовать, если: -- класс является стабильным и не генерируется динамически; -- нужен быстрый и простой способ найти элемент; -- класс семантически описывает элемент (например, `.error-message`, `.success-banner`); -- вы работаете с компонентными библиотеками, где классы являются частью API. +- класс является стабильным и не генерируется динамически; +- нужен быстрый и простой способ найти элемент; +- класс семантически описывает элемент (например, `.error-message`, `.success-banner`); +- вы работаете с компонентными библиотеками, где классы являются частью API. #### По id @@ -37,11 +38,12 @@ console.log("Количество пунктов меню: ${menuItems.length}") const loginForm = await browser.$("#login-form"); const isDisplayed = await loginForm.isDisplayed(); ``` + Стоит использовать, если: -- вы работаете с критически важными элементами (формы, модальные окна, главные контейнеры); -- `id` является частью публичного API компонента; -- нужна максимальная производительность селектора (`id` — самый быстрый селектор). +- вы работаете с критически важными элементами (формы, модальные окна, главные контейнеры); +- `id` является частью публичного API компонента; +- нужна максимальная производительность селектора (`id` — самый быстрый селектор). #### По типу атрибута @@ -65,11 +67,11 @@ const csrfValue = await hiddenField.getValue(); Стоит использовать, если: -- нужно найти все элементы определённого типа (все чекбоксы, все радиокнопки); -- вы тестируете формы и валидацию; -- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`); -- вам нужно убедиться, что используется правильный тип поля для `accessibility`; -- вы тестируете различное поведение для разных типов полей. +- нужно найти все элементы определённого типа (все чекбоксы, все радиокнопки); +- вы тестируете формы и валидацию; +- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`); +- вам нужно убедиться, что используется правильный тип поля для `accessibility`; +- вы тестируете различное поведение для разных типов полей. #### CSS-селекторы по атрибуту data-testid @@ -89,12 +91,13 @@ const errorMessage = await browser.$("[data-testid="error-notification"]"); const text = await errorMessage.getText(); expect(text).toContain("Ошибка валидации"); ``` + Стоит использовать, если: -- создаёте селекторы специально для тестирования; -- нужна стабильность селекторов независимо от изменений UI/стилей; -- работаете в команде, где дизайнеры часто меняют классы и структуру; -- хотите явно пометить элементы, доступные для тестирования; +- создаёте селекторы специально для тестирования; +- нужна стабильность селекторов независимо от изменений UI/стилей; +- работаете в команде, где дизайнеры часто меняют классы и структуру; +- хотите явно пометить элементы, доступные для тестирования; #### CSS-комбинированные селекторы @@ -118,11 +121,11 @@ const activeTab = await browser.$("ul.tabs > li.active > a[href^="#"]"); Стоит использовать, если: -- нужно найти элемент в определённом контексте; -- работаете с повторяющейся структурой; -- нужно убедиться в правильной вложенности элементов; -- простые селекторы слишком неспецифичны; -- тестируете связанные элементы. +- нужно найти элемент в определённом контексте; +- работаете с повторяющейся структурой; +- нужно убедиться в правильной вложенности элементов; +- простые селекторы слишком неспецифичны; +- тестируете связанные элементы. #### CSS-псевдоселекторы @@ -153,11 +156,11 @@ const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); Стоит использовать, если: -- тестируете позиционирование элементов; -- проверяете состояния элементов (`disabled`, `checked`, `focus`); -- работаете с таблицами и нужно выбрать определённую строку или столбец; -- тестируете паттерны (чётные/нечётные строки для `zebra-striping`); -- нужно найти элемент по его позиции, когда нет других идентификаторов. +- тестируете позиционирование элементов; +- проверяете состояния элементов (`disabled`, `checked`, `focus`); +- работаете с таблицами и нужно выбрать определённую строку или столбец; +- тестируете паттерны (чётные/нечётные строки для `zebra-striping`); +- нужно найти элемент по его позиции, когда нет других идентификаторов. ### XPath селекторы @@ -186,11 +189,11 @@ const link = await browser.$("//a[contains(translate(text(), "АБВГДЕЁЖЗ Стоит использовать, если: -- текст элемента уникален и стабилен (названия кнопок, заголовки); -- нет других идентификаторов (нет `data-testid`, `id`, классов); -- тестируете, что правильный текст отображается в правильном месте; -- работаете с динамически генерируемым контентом, где единственный стабильный элемент — текст; -- нужно найти элемент, содержащий определённый текст внутри себя или в дочерних элементах. +- текст элемента уникален и стабилен (названия кнопок, заголовки); +- нет других идентификаторов (нет `data-testid`, `id`, классов); +- тестируете, что правильный текст отображается в правильном месте; +- работаете с динамически генерируемым контентом, где единственный стабильный элемент — текст; +- нужно найти элемент, содержащий определённый текст внутри себя или в дочерних элементах. #### По атрибутам @@ -222,11 +225,11 @@ const notDisabledButton = await browser.$("//button[not(@disabled)]"); Стоит использовать, если: -- нужны сложные условия поиска (комбинации атрибутов); -- работаете с динамическими атрибутами (`data`-атрибуты с переменными значениями); -- нужна гибкость в поиске (частичные совпадения, начало/конец строки); -- CSS-селекторы не могут выразить нужную логику; -- нужно найти элемент по отсутствию атрибута. +- нужны сложные условия поиска (комбинации атрибутов); +- работаете с динамическими атрибутами (`data`-атрибуты с переменными значениями); +- нужна гибкость в поиске (частичные совпадения, начало/конец строки); +- CSS-селекторы не могут выразить нужную логику; +- нужно найти элемент по отсутствию атрибута. #### Навигация по DOM @@ -257,11 +260,11 @@ const siblingSection = await browser.$("//h2[text()="Контакты"]/../follo Стоит использовать, если: -- нужно найти элемент относительно другого известного элемента; -- структура DOM сложна, но относительные позиции стабильны; -- тестируете связанные элементы (label и input, ошибка рядом с полем); -- нужно подняться вверх по DOM-дереву от найденного элемента; -- работаете с семантической структурой HTML (заголовок и следующая за ним секция). +- нужно найти элемент относительно другого известного элемента; +- структура DOM сложна, но относительные позиции стабильны; +- тестируете связанные элементы (label и input, ошибка рядом с полем); +- нужно подняться вверх по DOM-дереву от найденного элемента; +- работаете с семантической структурой HTML (заголовок и следующая за ним секция). #### XPath: индексы и позиции @@ -290,11 +293,11 @@ const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 Стоит использовать, если: -- нужен доступ к элементу по его позиции в наборе результатов; -- тестируете пагинацию или списки с определённым порядком; -- работаете с таблицами и нужна конкретная строка; -- нужен первый или последний элемент среди нескольких одинаковых; -- тестируете сортировку (проверка, что элемент на правильной позиции). +- нужен доступ к элементу по его позиции в наборе результатов; +- тестируете пагинацию или списки с определённым порядком; +- работаете с таблицами и нужна конкретная строка; +- нужен первый или последний элемент среди нескольких одинаковых; +- тестируете сортировку (проверка, что элемент на правильной позиции). ### Селекторы по Link Text @@ -309,13 +312,14 @@ await loginLink.click(); const docsLink = await browser.$("*=Документация"); await docsLink.click(); ``` + Стоит использовать, если: -- работаете с навигационными ссылками с уникальным текстом; -- тестируете меню и навигацию сайта; -- текст ссылки является частью требований и не должен меняться; -- нужна простота и читаемость теста; -- тестируете наличие ссылок с правильным текстом на странице. +- работаете с навигационными ссылками с уникальным текстом; +- тестируете меню и навигацию сайта; +- текст ссылки является частью требований и не должен меняться; +- нужна простота и читаемость теста; +- тестируете наличие ссылок с правильным текстом на странице. ### Селекторы по имени тега @@ -328,15 +332,13 @@ await button.click(); // Все параграфы const paragraphs = await browser.$$("p"); -const textsArray = await Promise.all( - paragraphs.map(p => p.getText()) -); +const textsArray = await Promise.all(paragraphs.map(p => p.getText())); // Все изображения const images = await browser.$$("img"); for (const img of images) { - const alt = await img.getAttribute("alt"); - expect(alt).not.toBe(""); // проверка accessibility + const alt = await img.getAttribute("alt"); + expect(alt).not.toBe(""); // проверка accessibility } // Форма @@ -350,11 +352,11 @@ const rows = await table.$$("tr"); Стоит использовать, если: -- на странице один элемент данного типа (например, единственная форма); -- нужно получить все элементы определённого типа для массовой проверки; -- тестируете семантичность HTML (наличие правильных тегов); -- работаете с базовой HTML-структурой (`form`, `table`, `ul`); -- проводите accessibility-аудит (проверка всех `img` на наличие `alt`). +- на странице один элемент данного типа (например, единственная форма); +- нужно получить все элементы определённого типа для массовой проверки; +- тестируете семантичность HTML (наличие правильных тегов); +- работаете с базовой HTML-структурой (`form`, `table`, `ul`); +- проводите accessibility-аудит (проверка всех `img` на наличие `alt`). ### React селекторы @@ -367,12 +369,12 @@ await myButton.click(); // С фильтрацией по параметрами const primaryButton = await browser.react$("Button", { - props: { variant: "primary", size: "large" } + props: { variant: "primary", size: "large" }, }); // С фильтрацией по state const openModal = await browser.react$("Modal", { - state: { isOpen: true, activeTab: 'settings' } + state: { isOpen: true, activeTab: "settings" }, }); // Все экземпляры компонента @@ -385,20 +387,20 @@ const submitButton = await form.react$("SubmitButton"); // С комплексными параметрами const userProfile = await browser.react$("UserProfile", { - props: { - user: { id: 123, role: "admin" }, - editable: true - } + props: { + user: { id: 123, role: "admin" }, + editable: true, + }, }); ``` Стоит использовать, если: -- работаете с React-приложением и имеете доступ к исходному коду; -- нужно найти компонент по его параметрам или `state`; -- тестируете, что компонент рендерится с правильными данными; -- структура `DOM` может меняться, но API компонента стабилен; -- нужна глубокая интеграция с React DevTools. +- работаете с React-приложением и имеете доступ к исходному коду; +- нужно найти компонент по его параметрам или `state`; +- тестируете, что компонент рендерится с правильными данными; +- структура `DOM` может меняться, но API компонента стабилен; +- нужна глубокая интеграция с React DevTools. ### Shadow DOM селекторы @@ -416,11 +418,11 @@ const slotElements = await customElement.shadow$$(".slot-item"); Стоит использовать, если: -- работаете с Web Components и Custom Elements; -- приложение использует Shadow DOM для инкапсуляции стилей; -- тестируете компоненты из сторонних библиотек (Lit, Stencil, native Web Components); -- нужен доступ к элементам внутри shadow root; -- работаете с дизайн-системой на базе Web Components. +- работаете с Web Components и Custom Elements; +- приложение использует Shadow DOM для инкапсуляции стилей; +- тестируете компоненты из сторонних библиотек (Lit, Stencil, native Web Components); +- нужен доступ к элементам внутри shadow root; +- работаете с дизайн-системой на базе Web Components. ## Testing-library @@ -439,7 +441,7 @@ const submitButton = screen.getByRole("button", { name: /submit/i }); await userEvent.click(submitButton); // Поиск текстового поля -const emailInput = screen.getByRole('textbox', { name: /email/i }); +const emailInput = screen.getByRole("textbox", { name: /email/i }); await userEvent.type(emailInput, "test@example.com"); // Поиск чекбокса @@ -449,12 +451,11 @@ await userEvent.click(agreeCheckbox); Стоит использовать, если: -- для любых интерактивных элементов (кнопки, ссылки, поля ввода); -- для структурных элементов (`navigation`, `main`, `header`, `footer`); -- для форм и их элементов (`radio`, `checkbox`, `combobox`); -- для заголовков и важных текстовых элементов; -- для списков и таблиц. - +- для любых интерактивных элементов (кнопки, ссылки, поля ввода); +- для структурных элементов (`navigation`, `main`, `header`, `footer`); +- для форм и их элементов (`radio`, `checkbox`, `combobox`); +- для заголовков и важных текстовых элементов; +- для списков и таблиц. ### ByLabelText @@ -475,10 +476,10 @@ await userEvent.type(passwordInput, "secure123"); Стоит использовать, если: -- работаете с формами; -- нужно найти input, select, textarea по связанной метке; -- занимаетесь тестированием доступности (наличие `label` обязательно для доступности); -- метка уникальна и описательна. +- работаете с формами; +- нужно найти input, select, textarea по связанной метке; +- занимаетесь тестированием доступности (наличие `label` обязательно для доступности); +- метка уникальна и описательна. ### ByPlaceholderText @@ -499,11 +500,11 @@ await userEvent.type(emailInput, "test@example.com"); Стоит использовать, если: -- у поля нет `label`, но есть `placeholder`; -- занимаетесь тестированием legacy-кода, где `placeholder` используется вместо `label`; -- `placeholder` достаточно описателен и уникален. +- у поля нет `label`, но есть `placeholder`; +- занимаетесь тестированием legacy-кода, где `placeholder` используется вместо `label`; +- `placeholder` достаточно описателен и уникален. -### ByText +### ByText Чтобы найти текстовый элемент по его содержимому, используйте метод `getByText`. @@ -522,9 +523,9 @@ expect(errorMessage).toHaveClass("error"); Стоит использовать, если: -- необходимо найти неинтерактивные текстовые элементов (параграфы, заголовки, уведомления); -- занимаетесь проверкой отображения текста на странице; -- текст уникален и является частью требований. +- необходимо найти неинтерактивные текстовые элементов (параграфы, заголовки, уведомления); +- занимаетесь проверкой отображения текста на странице; +- текст уникален и является частью требований. ### ByDisplayValue @@ -544,9 +545,9 @@ const searchInput = screen.getByDisplayValue(/search query/i); Стоит использовать, если: -- необходимо протестировать предзаполненных форм (edit forms, profile pages); -- нужно проверить установку корректного значения после действия; -- необходимо найти элемент по его текущему значению, а не по label. +- необходимо протестировать предзаполненных форм (edit forms, profile pages); +- нужно проверить установку корректного значения после действия; +- необходимо найти элемент по его текущему значению, а не по label. ### ByAltText @@ -564,9 +565,9 @@ expect(logo).toHaveAttribute("src", "/images/logo.png"); Стоит использовать, если: -- нужно работать с изображениями (`<img>`, `<area>`, `<input type="image">`); -- необходимо проверить доступность изображений (наличие `alt` обязательно); -- `alt`-текст достаточно описателен и уникален. +- нужно работать с изображениями (`<img>`, `<area>`, `<input type="image">`); +- необходимо проверить доступность изображений (наличие `alt` обязательно); +- `alt`-текст достаточно описателен и уникален. ### ByTitle @@ -583,9 +584,9 @@ await userEvent.click(closeButton); Стоит использовать, если: -- необходимо работать с элементами, которые используют `title` для `tooltips`; -- нужно протестировать `iframe`-элементы (`title` обязателен для доступности); -- нужно взаимодействовать с `SVG`-элементами (`title` внутри `SVG` для описания). +- необходимо работать с элементами, которые используют `title` для `tooltips`; +- нужно протестировать `iframe`-элементы (`title` обязателен для доступности); +- нужно взаимодействовать с `SVG`-элементами (`title` внутри `SVG` для описания). ### ByTestId @@ -599,4 +600,3 @@ import userEvent from "@testing-library/user-event"; const submitButton = screen.getByTestId("submit-button"); await userEvent.click(submitButton); ``` - diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx index 8ecb8390..df05d980 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx @@ -21,12 +21,13 @@ await button.click(); const menuItems = await browser.$$(".nav-item"); console.log("Количество пунктов меню: ${menuItems.length}"); ``` + Стоит использовать, если: -- класс является стабильным и не генерируется динамически; -- нужен быстрый и простой способ найти элемент; -- класс семантически описывает элемент (например, `.error-message`, `.success-banner`); -- вы работаете с компонентными библиотеками, где классы являются частью API. +- класс является стабильным и не генерируется динамически; +- нужен быстрый и простой способ найти элемент; +- класс семантически описывает элемент (например, `.error-message`, `.success-banner`); +- вы работаете с компонентными библиотеками, где классы являются частью API. #### По id @@ -37,11 +38,12 @@ console.log("Количество пунктов меню: ${menuItems.length}") const loginForm = await browser.$("#login-form"); const isDisplayed = await loginForm.isDisplayed(); ``` + Стоит использовать, если: -- вы работаете с критически важными элементами (формы, модальные окна, главные контейнеры); -- `id` является частью публичного API компонента; -- нужна максимальная производительность селектора (`id` — самый быстрый селектор). +- вы работаете с критически важными элементами (формы, модальные окна, главные контейнеры); +- `id` является частью публичного API компонента; +- нужна максимальная производительность селектора (`id` — самый быстрый селектор). #### По типу атрибута @@ -65,11 +67,11 @@ const csrfValue = await hiddenField.getValue(); Стоит использовать, если: -- нужно найти все элементы определённого типа (все чекбоксы, все радиокнопки); -- вы тестируете формы и валидацию; -- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`); -- вам нужно убедиться, что используется правильный тип поля для `accessibility`; -- вы тестируете различное поведение для разных типов полей. +- нужно найти все элементы определённого типа (все чекбоксы, все радиокнопки); +- вы тестируете формы и валидацию; +- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`); +- вам нужно убедиться, что используется правильный тип поля для `accessibility`; +- вы тестируете различное поведение для разных типов полей. #### CSS-селекторы по атрибуту data-testid @@ -89,12 +91,13 @@ const errorMessage = await browser.$("[data-testid="error-notification"]"); const text = await errorMessage.getText(); expect(text).toContain("Ошибка валидации"); ``` + Стоит использовать, если: -- создаёте селекторы специально для тестирования; -- нужна стабильность селекторов независимо от изменений UI/стилей; -- работаете в команде, где дизайнеры часто меняют классы и структуру; -- хотите явно пометить элементы, доступные для тестирования; +- создаёте селекторы специально для тестирования; +- нужна стабильность селекторов независимо от изменений UI/стилей; +- работаете в команде, где дизайнеры часто меняют классы и структуру; +- хотите явно пометить элементы, доступные для тестирования; #### CSS-комбинированные селекторы @@ -118,11 +121,11 @@ const activeTab = await browser.$("ul.tabs > li.active > a[href^="#"]"); Стоит использовать, если: -- нужно найти элемент в определённом контексте; -- работаете с повторяющейся структурой; -- нужно убедиться в правильной вложенности элементов; -- простые селекторы слишком неспецифичны; -- тестируете связанные элементы. +- нужно найти элемент в определённом контексте; +- работаете с повторяющейся структурой; +- нужно убедиться в правильной вложенности элементов; +- простые селекторы слишком неспецифичны; +- тестируете связанные элементы. #### CSS-псевдоселекторы @@ -153,11 +156,11 @@ const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); Стоит использовать, если: -- тестируете позиционирование элементов; -- проверяете состояния элементов (`disabled`, `checked`, `focus`); -- работаете с таблицами и нужно выбрать определённую строку или столбец; -- тестируете паттерны (чётные/нечётные строки для `zebra-striping`); -- нужно найти элемент по его позиции, когда нет других идентификаторов. +- тестируете позиционирование элементов; +- проверяете состояния элементов (`disabled`, `checked`, `focus`); +- работаете с таблицами и нужно выбрать определённую строку или столбец; +- тестируете паттерны (чётные/нечётные строки для `zebra-striping`); +- нужно найти элемент по его позиции, когда нет других идентификаторов. ### XPath селекторы @@ -186,11 +189,11 @@ const link = await browser.$("//a[contains(translate(text(), "АБВГДЕЁЖЗ Стоит использовать, если: -- текст элемента уникален и стабилен (названия кнопок, заголовки); -- нет других идентификаторов (нет `data-testid`, `id`, классов); -- тестируете, что правильный текст отображается в правильном месте; -- работаете с динамически генерируемым контентом, где единственный стабильный элемент — текст; -- нужно найти элемент, содержащий определённый текст внутри себя или в дочерних элементах. +- текст элемента уникален и стабилен (названия кнопок, заголовки); +- нет других идентификаторов (нет `data-testid`, `id`, классов); +- тестируете, что правильный текст отображается в правильном месте; +- работаете с динамически генерируемым контентом, где единственный стабильный элемент — текст; +- нужно найти элемент, содержащий определённый текст внутри себя или в дочерних элементах. #### По атрибутам @@ -222,11 +225,11 @@ const notDisabledButton = await browser.$("//button[not(@disabled)]"); Стоит использовать, если: -- нужны сложные условия поиска (комбинации атрибутов); -- работаете с динамическими атрибутами (`data`-атрибуты с переменными значениями); -- нужна гибкость в поиске (частичные совпадения, начало/конец строки); -- CSS-селекторы не могут выразить нужную логику; -- нужно найти элемент по отсутствию атрибута. +- нужны сложные условия поиска (комбинации атрибутов); +- работаете с динамическими атрибутами (`data`-атрибуты с переменными значениями); +- нужна гибкость в поиске (частичные совпадения, начало/конец строки); +- CSS-селекторы не могут выразить нужную логику; +- нужно найти элемент по отсутствию атрибута. #### Навигация по DOM @@ -257,11 +260,11 @@ const siblingSection = await browser.$("//h2[text()="Контакты"]/../follo Стоит использовать, если: -- нужно найти элемент относительно другого известного элемента; -- структура DOM сложна, но относительные позиции стабильны; -- тестируете связанные элементы (label и input, ошибка рядом с полем); -- нужно подняться вверх по DOM-дереву от найденного элемента; -- работаете с семантической структурой HTML (заголовок и следующая за ним секция). +- нужно найти элемент относительно другого известного элемента; +- структура DOM сложна, но относительные позиции стабильны; +- тестируете связанные элементы (label и input, ошибка рядом с полем); +- нужно подняться вверх по DOM-дереву от найденного элемента; +- работаете с семантической структурой HTML (заголовок и следующая за ним секция). #### XPath: индексы и позиции @@ -290,11 +293,11 @@ const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 Стоит использовать, если: -- нужен доступ к элементу по его позиции в наборе результатов; -- тестируете пагинацию или списки с определённым порядком; -- работаете с таблицами и нужна конкретная строка; -- нужен первый или последний элемент среди нескольких одинаковых; -- тестируете сортировку (проверка, что элемент на правильной позиции). +- нужен доступ к элементу по его позиции в наборе результатов; +- тестируете пагинацию или списки с определённым порядком; +- работаете с таблицами и нужна конкретная строка; +- нужен первый или последний элемент среди нескольких одинаковых; +- тестируете сортировку (проверка, что элемент на правильной позиции). ### Селекторы по Link Text @@ -309,13 +312,14 @@ await loginLink.click(); const docsLink = await browser.$("*=Документация"); await docsLink.click(); ``` + Стоит использовать, если: -- работаете с навигационными ссылками с уникальным текстом; -- тестируете меню и навигацию сайта; -- текст ссылки является частью требований и не должен меняться; -- нужна простота и читаемость теста; -- тестируете наличие ссылок с правильным текстом на странице. +- работаете с навигационными ссылками с уникальным текстом; +- тестируете меню и навигацию сайта; +- текст ссылки является частью требований и не должен меняться; +- нужна простота и читаемость теста; +- тестируете наличие ссылок с правильным текстом на странице. ### Селекторы по имени тега @@ -328,15 +332,13 @@ await button.click(); // Все параграфы const paragraphs = await browser.$$("p"); -const textsArray = await Promise.all( - paragraphs.map(p => p.getText()) -); +const textsArray = await Promise.all(paragraphs.map(p => p.getText())); // Все изображения const images = await browser.$$("img"); for (const img of images) { - const alt = await img.getAttribute("alt"); - expect(alt).not.toBe(""); // проверка accessibility + const alt = await img.getAttribute("alt"); + expect(alt).not.toBe(""); // проверка accessibility } // Форма @@ -350,11 +352,11 @@ const rows = await table.$$("tr"); Стоит использовать, если: -- на странице один элемент данного типа (например, единственная форма); -- нужно получить все элементы определённого типа для массовой проверки; -- тестируете семантичность HTML (наличие правильных тегов); -- работаете с базовой HTML-структурой (`form`, `table`, `ul`); -- проводите accessibility-аудит (проверка всех `img` на наличие `alt`). +- на странице один элемент данного типа (например, единственная форма); +- нужно получить все элементы определённого типа для массовой проверки; +- тестируете семантичность HTML (наличие правильных тегов); +- работаете с базовой HTML-структурой (`form`, `table`, `ul`); +- проводите accessibility-аудит (проверка всех `img` на наличие `alt`). ### React селекторы @@ -367,12 +369,12 @@ await myButton.click(); // С фильтрацией по параметрами const primaryButton = await browser.react$("Button", { - props: { variant: "primary", size: "large" } + props: { variant: "primary", size: "large" }, }); // С фильтрацией по state const openModal = await browser.react$("Modal", { - state: { isOpen: true, activeTab: 'settings' } + state: { isOpen: true, activeTab: "settings" }, }); // Все экземпляры компонента @@ -385,20 +387,20 @@ const submitButton = await form.react$("SubmitButton"); // С комплексными параметрами const userProfile = await browser.react$("UserProfile", { - props: { - user: { id: 123, role: "admin" }, - editable: true - } + props: { + user: { id: 123, role: "admin" }, + editable: true, + }, }); ``` Стоит использовать, если: -- работаете с React-приложением и имеете доступ к исходному коду; -- нужно найти компонент по его параметрам или `state`; -- тестируете, что компонент рендерится с правильными данными; -- структура `DOM` может меняться, но API компонента стабилен; -- нужна глубокая интеграция с React DevTools. +- работаете с React-приложением и имеете доступ к исходному коду; +- нужно найти компонент по его параметрам или `state`; +- тестируете, что компонент рендерится с правильными данными; +- структура `DOM` может меняться, но API компонента стабилен; +- нужна глубокая интеграция с React DevTools. ### Shadow DOM селекторы @@ -416,11 +418,11 @@ const slotElements = await customElement.shadow$$(".slot-item"); Стоит использовать, если: -- работаете с Web Components и Custom Elements; -- приложение использует Shadow DOM для инкапсуляции стилей; -- тестируете компоненты из сторонних библиотек (Lit, Stencil, native Web Components); -- нужен доступ к элементам внутри shadow root; -- работаете с дизайн-системой на базе Web Components. +- работаете с Web Components и Custom Elements; +- приложение использует Shadow DOM для инкапсуляции стилей; +- тестируете компоненты из сторонних библиотек (Lit, Stencil, native Web Components); +- нужен доступ к элементам внутри shadow root; +- работаете с дизайн-системой на базе Web Components. ## Testing-library @@ -439,7 +441,7 @@ const submitButton = screen.getByRole("button", { name: /submit/i }); await userEvent.click(submitButton); // Поиск текстового поля -const emailInput = screen.getByRole('textbox', { name: /email/i }); +const emailInput = screen.getByRole("textbox", { name: /email/i }); await userEvent.type(emailInput, "test@example.com"); // Поиск чекбокса @@ -449,12 +451,11 @@ await userEvent.click(agreeCheckbox); Стоит использовать, если: -- для любых интерактивных элементов (кнопки, ссылки, поля ввода); -- для структурных элементов (`navigation`, `main`, `header`, `footer`); -- для форм и их элементов (`radio`, `checkbox`, `combobox`); -- для заголовков и важных текстовых элементов; -- для списков и таблиц. - +- для любых интерактивных элементов (кнопки, ссылки, поля ввода); +- для структурных элементов (`navigation`, `main`, `header`, `footer`); +- для форм и их элементов (`radio`, `checkbox`, `combobox`); +- для заголовков и важных текстовых элементов; +- для списков и таблиц. ### ByLabelText @@ -475,10 +476,10 @@ await userEvent.type(passwordInput, "secure123"); Стоит использовать, если: -- работаете с формами; -- нужно найти input, select, textarea по связанной метке; -- занимаетесь тестированием доступности (наличие `label` обязательно для доступности); -- метка уникальна и описательна. +- работаете с формами; +- нужно найти input, select, textarea по связанной метке; +- занимаетесь тестированием доступности (наличие `label` обязательно для доступности); +- метка уникальна и описательна. ### ByPlaceholderText @@ -499,11 +500,11 @@ await userEvent.type(emailInput, "test@example.com"); Стоит использовать, если: -- у поля нет `label`, но есть `placeholder`; -- занимаетесь тестированием legacy-кода, где `placeholder` используется вместо `label`; -- `placeholder` достаточно описателен и уникален. +- у поля нет `label`, но есть `placeholder`; +- занимаетесь тестированием legacy-кода, где `placeholder` используется вместо `label`; +- `placeholder` достаточно описателен и уникален. -### ByText +### ByText Чтобы найти текстовый элемент по его содержимому, используйте метод `getByText`. @@ -522,9 +523,9 @@ expect(errorMessage).toHaveClass("error"); Стоит использовать, если: -- необходимо найти неинтерактивные текстовые элементов (параграфы, заголовки, уведомления); -- занимаетесь проверкой отображения текста на странице; -- текст уникален и является частью требований. +- необходимо найти неинтерактивные текстовые элементов (параграфы, заголовки, уведомления); +- занимаетесь проверкой отображения текста на странице; +- текст уникален и является частью требований. ### ByDisplayValue @@ -544,9 +545,9 @@ const searchInput = screen.getByDisplayValue(/search query/i); Стоит использовать, если: -- необходимо протестировать предзаполненных форм (edit forms, profile pages); -- нужно проверить установку корректного значения после действия; -- необходимо найти элемент по его текущему значению, а не по label. +- необходимо протестировать предзаполненных форм (edit forms, profile pages); +- нужно проверить установку корректного значения после действия; +- необходимо найти элемент по его текущему значению, а не по label. ### ByAltText @@ -564,9 +565,9 @@ expect(logo).toHaveAttribute("src", "/images/logo.png"); Стоит использовать, если: -- нужно работать с изображениями (`<img>`, `<area>`, `<input type="image">`); -- необходимо проверить доступность изображений (наличие `alt` обязательно); -- `alt`-текст достаточно описателен и уникален. +- нужно работать с изображениями (`<img>`, `<area>`, `<input type="image">`); +- необходимо проверить доступность изображений (наличие `alt` обязательно); +- `alt`-текст достаточно описателен и уникален. ### ByTitle @@ -583,9 +584,9 @@ await userEvent.click(closeButton); Стоит использовать, если: -- необходимо работать с элементами, которые используют `title` для `tooltips`; -- нужно протестировать `iframe`-элементы (`title` обязателен для доступности); -- нужно взаимодействовать с `SVG`-элементами (`title` внутри `SVG` для описания). +- необходимо работать с элементами, которые используют `title` для `tooltips`; +- нужно протестировать `iframe`-элементы (`title` обязателен для доступности); +- нужно взаимодействовать с `SVG`-элементами (`title` внутри `SVG` для описания). ### ByTestId @@ -599,4 +600,3 @@ import userEvent from "@testing-library/user-event"; const submitButton = screen.getByTestId("submit-button"); await userEvent.click(submitButton); ``` - diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/index.mdx index 4c060761..035e2ea5 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/index.mdx @@ -27,11 +27,11 @@ npm init testplane@latest YOUR_PROJECT_PATH ```bash node_modules testplane-tests - example.testplane.ts + example.testplane.ts ts.config.json package-lock.json package.json -testplane.config.ts +testplane.config.ts ``` ## Настройка {#setup} @@ -90,6 +90,3 @@ npx testplane install-deps ``` Без предварительного запуска команды, недостающие браузеры будут автоматически загружены с первым запуском Testplane. - - - diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx index 5ec79c85..f50d45e7 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx @@ -26,26 +26,26 @@ npx testplane gui const assert = require("assert"); describe("tests", () => { - it("Проверка отображения главной страницы", async ({browser}) => { + it("Проверка отображения главной страницы", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); const title = await browser.getTitle(); assert.ok(title.includes("Testplane")); }); - it("Проверка наличия логотипа на главной странице", async ({browser}) => { + it("Проверка наличия логотипа на главной странице", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); const logo = await browser.$("a.navbar__brand"); const isDisplayed = await logo.isDisplayed(); assert.strictEqual(isDisplayed, true); }); - it("Проверка навигационного меню", async ({browser}) => { + it("Проверка навигационного меню", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); const menuItems = await browser.$$("nav.navbar a.navbar__item"); assert.ok(menuItems.length > 0); }); - it("Проверка наличия поля поиска", async ({browser}) => { + it("Проверка наличия поля поиска", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); const searchButton = await browser.$("button.DocSearch"); const isExisting = await searchButton.isExisting(); @@ -53,11 +53,13 @@ describe("tests", () => { }); }); ``` + В таком случае выполните команду: ```bash testplane --grep "Проверка наличия поля поиска" ``` + В кавычках вам необходимо передать содержимое скобок ключевого слова `it`. ### Запуск тестов в конкретных браузерах @@ -65,11 +67,9 @@ testplane --grep "Проверка наличия поля поиска" По умолчанию тесты запускаются в тех браузерах, которые указаны в файле `testplane.config.ts`. ```javascript -browsers: [ - "chrome", - "firefox" -] +browsers: ["chrome", "firefox"]; ``` + При выполнении команды `npx testplane` тесты запустятся в браузерах Google Chrome и Mozila Firefox. ```bash @@ -89,20 +89,20 @@ testplane --browser chrome ```javascript // tests/browser-specific.test.js describe("Browser specific tests", () => { - it("should work in all browsers", async ({browser}) => { + it("should work in all browsers", async ({ browser }) => { await browser.url("https://example.com"); }); // Пропустить тест в Safari testplane.skip.in("safari", "Feature not supported in Safari"); - it("should work only in Chrome and Firefox", async ({browser}) => { + it("should work only in Chrome and Firefox", async ({ browser }) => { await browser.url("https://example.com"); // ... тело теста }); // Запустить только в Chrome testplane.only.in("chrome"); - it("should work only in Chrome", async ({browser}) => { + it("should work only in Chrome", async ({ browser }) => { await browser.url("https://example.com"); // ... тело теста }); @@ -120,7 +120,6 @@ testplane ../testplane-tests/example.testplane.ts Где `../testplane-tests/example.testplane.ts` это путь к файлу с тестами. - ### Режим пользовательского интерфейса В Testplane вы можете работать с тестами в UI формате с помощью Testplane UI. @@ -142,27 +141,26 @@ testplane ../testplane-tests/example.testplane.ts В Testplane имеется встроенный инструмент для отладки — `browser.debug`. ```javascript -it('отладка с паузой', async ({browser}) => { +it("отладка с паузой", async ({ browser }) => { // Открываем тестируемую страницу - await browser.url('/page'); - + await browser.url("/page"); + // browser.debug() останавливает выполнение теста // и открывает интерактивную консоль (REPL - Read-Eval-Print Loop) await browser.debug(); - + // После вызова debug() тест приостанавливается // В консоли можно вводить команды WebdriverIO в реальном времени: - + // Примеры команд, которые можно вводить в REPL: // > await browser.$('.button').click() - кликнуть по кнопке // > await browser.getTitle() - получить заголовок страницы // > await browser.$$('.items') - найти все элементы // > .exit - выйти из режима отладки - + // Этот код выполнится только после выхода из debug() - await browser.$('.button').click(); + await browser.$(".button").click(); }); - ``` ### Отладка через Testplane UI @@ -174,4 +172,3 @@ it('отладка с паузой', async ({browser}) => { И находить нестабильные тесты, медленные тесты или другие проблемы с помощью опций "сортировка" и "группировка". ![](/gif/docs/ui/analytics.gif) - diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx index a18fe170..d948a12a 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx @@ -10,16 +10,17 @@ draft: true Блок `describe` предназначен для группировки связанных тестов. ```javascript -describe('Название группы тестов', () => { - it('описание того, что должно произойти', async ({browser}) => { +describe("Название группы тестов", () => { + it("описание того, что должно произойти", async ({ browser }) => { // Тело теста -}); + }); }); ``` + В блоке `it` описываются тестовые сценарии. ```javascript -it('описание того, что должно произойти', async ({browser}) => { +it("описание того, что должно произойти", async ({ browser }) => { // Тело теста }); ``` @@ -30,7 +31,7 @@ it('описание того, что должно произойти', async ({ ```javascript describe("test examples", () => { - it("docs search test", async ({browser}) => { + it("docs search test", async ({ browser }) => { await browser.openAndWait("https://testplane.io/"); // Find by tag name @@ -64,12 +65,13 @@ describe("test examples", () => { ```javascript await browser.url("https://testplane.io/ru/"); ``` + Если на странице имеются элементы, которые отображаются с задержкой, для корректного выполнения тестов укажите явное ожидание: ```javascript await browser.url("https://testplane.io/ru/"); -await browser.$('h1').waitForExist({ timeout: 5000 }); -const title = await browser.$('h1').getText(); +await browser.$("h1").waitForExist({ timeout: 5000 }); +const title = await browser.$("h1").getText(); ``` Либо используйте команду: @@ -77,6 +79,7 @@ const title = await browser.$('h1').getText(); ```javascript await browser.openAndWait("https://testplane.io/ru/"); ``` + Команда `await browser.openAndWait()` по умолчанию дожидается загрузки всех необходимых элементов на странице. ### Селекторы @@ -87,26 +90,26 @@ Testplane поддерживает различные стратегии пои const assert = require("assert"); describe("tests", () => { - it("Проверка отображения главной страницы", async ({browser}) => { + it("Проверка отображения главной страницы", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); const title = await browser.getTitle(); assert.ok(title.includes("Testplane")); }); - it("Проверка наличия логотипа на главной странице", async ({browser}) => { + it("Проверка наличия логотипа на главной странице", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); const logo = await browser.$("a.navbar__brand"); const isDisplayed = await logo.isDisplayed(); assert.strictEqual(isDisplayed, true); }); - it("Проверка навигационного меню", async ({browser}) => { + it("Проверка навигационного меню", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); const menuItems = await browser.$$("nav.navbar a.navbar__item"); assert.ok(menuItems.length > 0); }); - it("Проверка наличия поля поиска", async ({browser}) => { + it("Проверка наличия поля поиска", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); const searchButton = await browser.$("button.DocSearch"); const isExisting = await searchButton.isExisting(); @@ -123,16 +126,16 @@ describe("tests", () => { const assert = require("assert"); describe("tests, () => { - + it("Пример клика - открытие поиска", async ({browser}) => { await browser.url("https://testplane.io/ru/"); - + // Клик по кнопке поиска const searchButton = await browser.$("button.DocSearch"); await searchButton.waitForClickable({timeout: 5000}); await searchButton.click(); await browser.pause(1000); - + // Проверяем, что модальное окно поиска появилось const searchModal = await browser.$(".DocSearch-Modal"); const isDisplayed = await searchModal.isDisplayed(); @@ -141,19 +144,19 @@ describe("tests, () => { it("Пример ввода текста - поиск по документации", async ({browser}) => { await browser.url("https://testplane.io/ru/"); - + // Открываем поиск const searchButton = await browser.$("button.DocSearch"); await searchButton.waitForClickable({timeout: 5000}); await searchButton.click(); await browser.pause(500); - + // Вводим текст в поле поиска const searchInput = await browser.$("input.DocSearch-Input"); await searchInput.waitForDisplayed({timeout: 5000}); await searchInput.setValue("browser"); await browser.pause(1000); - + // Проверяем, что текст введен const inputValue = await searchInput.getValue(); assert.strictEqual(inputValue, "browser"); @@ -161,17 +164,17 @@ describe("tests, () => { it("Пример двойного клика - выделение текста заголовка", async ({browser}) => { await browser.url("https://testplane.io/ru/"); - + // Находим заголовок на главной странице const heading = await browser.$("h1"); await heading.waitForDisplayed({timeout: 5000}); await heading.scrollIntoView(); await browser.pause(500); - + // Двойной клик по заголовку await heading.doubleClick(); await browser.pause(500); - + // Проверяем, что элемент существует и отображается const isDisplayed = await heading.isDisplayed(); assert.strictEqual(isDisplayed, true); @@ -187,66 +190,65 @@ describe("tests, () => { const assert = require("assert"); describe("tests", () => { - - it("WebdriverIO assert - проверка URL", async ({browser}) => { + it("WebdriverIO assert - проверка URL", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); - + // WebdriverIO expect для browser await expect(browser).toHaveUrl("https://testplane.io/ru/"); }); - it("WebdriverIO assert - проверка существования элемента", async ({browser}) => { + it("WebdriverIO assert - проверка существования элемента", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); - + const logo = await browser.$("a.navbar__brand"); - + // WebdriverIO expect для элемента await expect(logo).toExist(); }); - it("WebdriverIO assert - проверка видимости элемента", async ({browser}) => { + it("WebdriverIO assert - проверка видимости элемента", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); - + const searchButton = await browser.$("button.DocSearch"); - + // WebdriverIO expect await expect(searchButton).toBeDisplayed(); }); // Примеры с Jest ассертами - it("Jest assert - проверка заголовка страницы", async ({browser}) => { + it("Jest assert - проверка заголовка страницы", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); - + const title = await browser.getTitle(); - + // Jest expect expect(title).toContain("Testplane"); }); - it("Jest assert - проверка количества элементов", async ({browser}) => { + it("Jest assert - проверка количества элементов", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); - + const menuItems = await browser.$$("nav.navbar a.navbar__item"); - + // Jest expect expect(menuItems.length).toBeGreaterThan(0); }); - it("Jest assert - проверка атрибута элемента", async ({browser}) => { + it("Jest assert - проверка атрибута элемента", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); - + const docsLink = await browser.$("a[href='/ru/docs/v8/']"); const href = await docsLink.getAttribute("href"); - + // Jest expect expect(href).toBe("/ru/docs/v8/"); }); - it("Jest assert - проверка URL с регулярным выражением", async ({browser}) => { + it("Jest assert - проверка URL с регулярным выражением", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); - + const currentUrl = await browser.getUrl(); - + // Jest expect с regex expect(currentUrl).toMatch(/testplane\.io/); }); @@ -257,39 +259,37 @@ describe("tests", () => { Хуки в Testplane — это специальные функции, которые автоматически выполняются в определенные моменты жизненного цикла тестов. Они позволяют подготовить окружение перед тестами и очистить его после выполнения. По умолчанию доступны два вида хуков — `beforeEach` и `afterEach`, первый выполняется перед каждым тестом, а второй после. - ```javascript const assert = require("assert"); describe("Примеры работы с хуками", () => { - // beforeEach - выполняется перед каждым тестом - beforeEach(async ({browser}) => { + beforeEach(async ({ browser }) => { console.log("--- Выполняется BEFOREEACH - перед каждым тестом ---"); await browser.url("https://testplane.io/ru/"); await browser.pause(500); }); // afterEach - выполняется после каждого теста - afterEach(async ({browser}) => { + afterEach(async ({ browser }) => { console.log("--- Выполняется AFTEREACH - после каждого теста ---"); const currentUrl = await browser.getUrl(); console.log("Текущий URL:", currentUrl); // Можно делать скриншоты, очищать данные и т.д. }); - it("Тест 1 - проверка заголовка", async ({browser}) => { + it("Тест 1 - проверка заголовка", async ({ browser }) => { const title = await browser.getTitle(); assert.ok(title.includes("Testplane")); }); - it("Тест 2 - проверка логотипа", async ({browser}) => { + it("Тест 2 - проверка логотипа", async ({ browser }) => { const logo = await browser.$("a.navbar__brand"); const isDisplayed = await logo.isDisplayed(); assert.strictEqual(isDisplayed, true); }); - it("Тест 3 - проверка поиска", async ({browser}) => { + it("Тест 3 - проверка поиска", async ({ browser }) => { const searchButton = await browser.$("button.DocSearch"); const isExisting = await searchButton.isExisting(); assert.strictEqual(isExisting, true); @@ -297,40 +297,38 @@ describe("Примеры работы с хуками", () => { }); describe("Пример вложенных describe с хуками", () => { - - beforeEach(async ({browser}) => { + beforeEach(async ({ browser }) => { console.log("--- OUTER BEFOREEACH ---"); await browser.url("https://testplane.io/ru/"); }); - afterEach(async ({browser}) => { + afterEach(async ({ browser }) => { console.log("--- OUTER AFTEREACH ---"); }); - it("Внешний тест", async ({browser}) => { + it("Внешний тест", async ({ browser }) => { const title = await browser.getTitle(); assert.ok(title.length > 0); }); describe("Внутренний блок тестов", () => { - - beforeEach(async ({browser}) => { + beforeEach(async ({ browser }) => { console.log("--- INNER BEFOREEACH ---"); // Сначала выполнится outer beforeEach, потом этот await browser.url("https://testplane.io/ru/docs/v8/"); }); - afterEach(async ({browser}) => { + afterEach(async ({ browser }) => { console.log("--- INNER AFTEREACH ---"); // Сначала выполнится этот afterEach, потом outer }); - it("Внутренний тест 1", async ({browser}) => { + it("Внутренний тест 1", async ({ browser }) => { const currentUrl = await browser.getUrl(); assert.ok(currentUrl.includes("docs")); }); - it("Внутренний тест 2", async ({browser}) => { + it("Внутренний тест 2", async ({ browser }) => { const heading = await browser.$("h1"); const isDisplayed = await heading.isDisplayed(); assert.strictEqual(isDisplayed, true); @@ -347,69 +345,68 @@ describe("Пример вложенных describe с хуками", () => { const assert = require("assert"); describe("Примеры ожиданий в Testplane", () => { - - it("Ожидание появления и кликабельности элемента", async ({browser}) => { + it("Ожидание появления и кликабельности элемента", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); - + // Ожидаем, что кнопка поиска появится на странице const searchButton = await browser.$("button.DocSearch"); await searchButton.waitForDisplayed({ timeout: 5000, - timeoutMsg: "Кнопка поиска не появилась в течение 5 секунд" + timeoutMsg: "Кнопка поиска не появилась в течение 5 секунд", }); - + // Ожидаем, что элемент станет кликабельным await searchButton.waitForClickable({ timeout: 3000, - timeoutMsg: "Кнопка поиска не стала кликабельной" + timeoutMsg: "Кнопка поиска не стала кликабельной", }); - + await searchButton.click(); - + // Ожидаем появления модального окна поиска const searchModal = await browser.$(".DocSearch-Modal"); - await searchModal.waitForDisplayed({timeout: 3000}); - + await searchModal.waitForDisplayed({ timeout: 3000 }); + const isDisplayed = await searchModal.isDisplayed(); assert.strictEqual(isDisplayed, true); }); - it("Ожидание изменения текста элемента", async ({browser}) => { + it("Ожидание изменения текста элемента", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); - + const heading = await browser.$("h1"); - + // Ожидаем, что элемент будет существовать await heading.waitForExist({ timeout: 5000, - timeoutMsg: "Заголовок не найден на странице" + timeoutMsg: "Заголовок не найден на странице", }); - + // Ожидаем, что у элемента будет определенный текст await heading.waitUntil( - async function() { + async function () { const text = await this.getText(); return text.length > 0; }, { timeout: 5000, - timeoutMsg: "Текст заголовка не появился" - } + timeoutMsg: "Текст заголовка не появился", + }, ); - + const text = await heading.getText(); assert.ok(text.length > 0); }); - it("Ожидание с использованием browser.waitUntil для проверки URL", async ({browser}) => { + it("Ожидание с использованием browser.waitUntil для проверки URL", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); - + const docsLink = await browser.$("a[href='/ru/docs/v8/']"); - await docsLink.waitForExist({timeout: 5000}); - + await docsLink.waitForExist({ timeout: 5000 }); + // Используем JavaScript click для надежности - await browser.execute((el) => el.click(), docsLink); - + await browser.execute(el => el.click(), docsLink); + // Ожидаем изменения URL с помощью browser.waitUntil await browser.waitUntil( async () => { @@ -418,10 +415,10 @@ describe("Примеры ожиданий в Testplane", () => { }, { timeout: 5000, - timeoutMsg: "URL не изменился на страницу документации" - } + timeoutMsg: "URL не изменился на страницу документации", + }, ); - + const finalUrl = await browser.getUrl(); assert.ok(finalUrl.includes("docs")); }); @@ -436,81 +433,80 @@ Testplane предоставляет специальные методы для const assert = require("assert"); describe("Примеры работы с формами в Testplane", () => { - - it("Работа с чекбоксами через клик", async ({browser}) => { + it("Работа с чекбоксами через клик", async ({ browser }) => { // Для демонстрации используем страницу с формой await browser.url("https://the-internet.herokuapp.com/checkboxes"); - + const checkbox1 = await browser.$("#checkboxes input:nth-child(1)"); - await checkbox1.waitForDisplayed({timeout: 5000}); - + await checkbox1.waitForDisplayed({ timeout: 5000 }); + // Проверяем начальное состояние let isSelected = await checkbox1.isSelected(); assert.strictEqual(isSelected, false); - + // Кликаем для выбора await checkbox1.click(); isSelected = await checkbox1.isSelected(); assert.strictEqual(isSelected, true); - + // Кликаем еще раз для снятия выбора await checkbox1.click(); isSelected = await checkbox1.isSelected(); assert.strictEqual(isSelected, false); }); - it("Работа с радио—кнопками через клик", async ({browser}) => { + it("Работа с радио—кнопками через клик", async ({ browser }) => { await browser.url("https://the-internet.herokuapp.com/"); - + // Переходим на страницу с примерами const link = await browser.$("a[href='/forgot_password']"); await link.click(); - + // Работа с полем email (как пример радио-кнопок) const emailInput = await browser.$("#email"); - await emailInput.waitForDisplayed({timeout: 5000}); + await emailInput.waitForDisplayed({ timeout: 5000 }); await emailInput.setValue("test@example.com"); - + const value = await emailInput.getValue(); assert.ok(value.includes("test@example.com")); }); - it("Работа с выпадающим списком (select) — выбор по тексту", async ({browser}) => { + it("Работа с выпадающим списком (select) — выбор по тексту", async ({ browser }) => { await browser.url("https://the-internet.herokuapp.com/dropdown"); - + const dropdown = await browser.$("#dropdown"); - await dropdown.waitForDisplayed({timeout: 5000}); - + await dropdown.waitForDisplayed({ timeout: 5000 }); + // Выбор опции по видимому тексту await dropdown.selectByVisibleText("Option 1"); - + // Проверяем выбранное значение let selectedValue = await dropdown.getValue(); assert.strictEqual(selectedValue, "1"); - + // Получаем текст выбранной опции let selectedText = await dropdown.$("option:checked").getText(); assert.strictEqual(selectedText, "Option 1"); }); - it("Отправка формы и проверка результата", async ({browser}) => { + it("Отправка формы и проверка результата", async ({ browser }) => { await browser.url("https://the-internet.herokuapp.com/login"); - + // Заполняем форму const usernameInput = await browser.$("#username"); const passwordInput = await browser.$("#password"); const submitButton = await browser.$("button[type='submit']"); - + await usernameInput.setValue("tomsmith"); await passwordInput.setValue("SuperSecretPassword!"); - + // Отправляем форму await submitButton.click(); - + // Ждем появления сообщения об успехе const successMessage = await browser.$("#flash"); - await successMessage.waitForDisplayed({timeout: 5000}); - + await successMessage.waitForDisplayed({ timeout: 5000 }); + const messageText = await successMessage.getText(); assert.ok(messageText.includes("You logged into a secure area!")); }); @@ -525,21 +521,20 @@ describe("Примеры работы с формами в Testplane", () => { const assert = require("assert"); describe("Примеры работы с JavaScript кодом", () => { - - it("Выполнение JavaScript кода в контексте страницы", async ({browser}) => { + it("Выполнение JavaScript кода в контексте страницы", async ({ browser }) => { await browser.url("https://testplane.io/ru/"); - + // Пример 1: Получение данных из localStorage await browser.execute(() => { localStorage.setItem("testKey", "testValue"); localStorage.setItem("userName", "John Doe"); }); - + const storageValue = await browser.execute(() => { return localStorage.getItem("testKey"); }); assert.strictEqual(storageValue, "testValue"); - + // Пример 2: Манипуляция DOM — изменение текста элемента const newText = await browser.execute(() => { const heading = document.querySelector("h1"); @@ -550,20 +545,20 @@ describe("Примеры работы с JavaScript кодом", () => { } return ""; }); - + const modifiedHeading = await browser.$("h1"); const currentText = await modifiedHeading.getText(); assert.strictEqual(currentText, "Измененный заголовок"); - + // Пример 3: Вызов функции с параметрами - const sum = await browser.execute((a, b) => { - return a + b; - }, 5, 10); + const sum = await browser.execute( + (a, b) => { + return a + b; + }, + 5, + 10, + ); assert.strictEqual(sum, 15); }); }); ``` - - - - From 90cfc1ce8c27b402e593aa9f3d1e1a8c26d40669 Mon Sep 17 00:00:00 2001 From: Nikolaengel <bratashick@gmail.com> Date: Thu, 22 Jan 2026 17:09:54 +0300 Subject: [PATCH 10/14] docs: tabs-fix --- .../current/quickstart/running-tests.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx index f50d45e7..b66797e4 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx @@ -109,6 +109,7 @@ describe("Browser specific tests", () => { }); ``` + ### Запуск теста из конкретного файла Чтобы запустить тесты из конкретного файла, выполните команду: From c5b9411fc2abadc195aa5a4b56a6ba27a076ad8d Mon Sep 17 00:00:00 2001 From: Nikolaengel <bratashick@gmail.com> Date: Fri, 23 Jan 2026 13:20:26 +0300 Subject: [PATCH 11/14] docs: runningtests-fix --- .../current/quickstart/running-tests.mdx | 8 ++++---- .../current/quickstart/writing-tests.mdx | 10 ++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx index b66797e4..1283c37c 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx @@ -12,7 +12,7 @@ sidebar_position: 3 npx testplane ``` -Также тесты можно запускать в `gui`—режиме, для этого выполните команду: +Также тесты можно запускать в `gui`-режиме, для этого выполните команду: ```bash npx testplane gui @@ -131,9 +131,9 @@ testplane ../testplane-tests/example.testplane.ts ## Отладка -### Отладка в gui—формате +### Отладка в gui-формате -Отслеживать процесс выполнения тестов очень легко, если запустить их в `gui`—режиме. В подобном формате работы html-reporter продемонстрирует, какие тесты были успешно выполнены, а в каких присутствуют ошибки и какого они характера. +Отслеживать процесс выполнения тестов очень легко, если запустить их в `gui`-режиме. В подобном формате работы html-reporter продемонстрирует, какие тесты были успешно выполнены, а в каких присутствуют ошибки и какого они характера. Тут скриншот из gui testplane @@ -170,6 +170,6 @@ it("отладка с паузой", async ({ browser }) => { ![](/gif/docs/ui/run-debug.gif) -И находить нестабильные тесты, медленные тесты или другие проблемы с помощью опций "сортировка" и "группировка". +И находить нестабильные тесты, медленные тесты или другие проблемы с помощью опций «сортировка» и «группировка». ![](/gif/docs/ui/analytics.gif) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx index d948a12a..e3b2874b 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx @@ -25,8 +25,6 @@ it("описание того, что должно произойти", async ({ }); ``` -### Пример - После установки testplane, вы можете ознакомиться с примером теста, для этого перейдите в папку `testplane-tests` и откройте файл `example.testplane.ts`. ```javascript @@ -120,7 +118,7 @@ describe("tests", () => { ### Взаимодействия с элементами -Для поиска элементов используйте метод `$()` (один элемент) или `$$()` (множество элементов). После нахождения элемента можно выполнить различные действия: клик, ввод текста, двойной клик. +После знакомства с селекторами и нахождения элемента можно выполнить различные действия: клик, ввод текста, двойной клик. ```javascript const assert = require("assert"); @@ -257,7 +255,7 @@ describe("tests", () => { ### Хуки -Хуки в Testplane — это специальные функции, которые автоматически выполняются в определенные моменты жизненного цикла тестов. Они позволяют подготовить окружение перед тестами и очистить его после выполнения. По умолчанию доступны два вида хуков — `beforeEach` и `afterEach`, первый выполняется перед каждым тестом, а второй после. +Хуки — это специальные функции, которые автоматически выполняются в определенные моменты жизненного цикла тестов. Они позволяют подготовить окружение перед тестами и очистить его после выполнения. По умолчанию доступны два вида хуков — `beforeEach` и `afterEach`, первый выполняется перед каждым тестом, а второй после. ```javascript const assert = require("assert"); @@ -427,7 +425,7 @@ describe("Примеры ожиданий в Testplane", () => { ### Работа с формами -Testplane предоставляет специальные методы для работы с различными элементами форм. Чекбоксы и радио—кнопки управляются через клик. Для выпадающих списков `(<select>)` есть удобные методы выбора опций по видимому тексту или значению атрибута. +Testplane предоставляет специальные методы для работы с различными элементами форм. Чекбоксы и радио-кнопки управляются через клик. Для выпадающих списков `(<select>)` есть удобные методы выбора опций по видимому тексту или значению атрибута. ```javascript const assert = require("assert"); @@ -455,7 +453,7 @@ describe("Примеры работы с формами в Testplane", () => { assert.strictEqual(isSelected, false); }); - it("Работа с радио—кнопками через клик", async ({ browser }) => { + it("Работа с радио-кнопками через клик", async ({ browser }) => { await browser.url("https://the-internet.herokuapp.com/"); // Переходим на страницу с примерами From 7f4f18dd27e1f42ea4fe3ebb6a2a2ff809c4b543 Mon Sep 17 00:00:00 2001 From: Nikolaengel <bratashick@gmail.com> Date: Fri, 23 Jan 2026 13:30:22 +0300 Subject: [PATCH 12/14] docs: margins-fix --- .../current/quickstart/writing-tests.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx index e3b2874b..8446aa62 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx @@ -182,7 +182,7 @@ describe("tests, () => { ### Assertions -В Testplane для проверки состояния элементов и страниц задействует `expect API` из `WebdriverIO` — это позволяет формулировать утверждения (`assertions`) о том, какими должны быть свойства элементов или страницы в целом. +Testplane задействует `expect API` из `WebdriverIO` для проверки состояния элементов и страниц — это позволяет формулировать утверждения (`assertions`) о том, какими должны быть свойства элементов или страницы в целом. ```javascript const assert = require("assert"); From 66416c2939a8d6271e0b7a72b514d11f921c15b9 Mon Sep 17 00:00:00 2001 From: Nikolaengel <bratashick@gmail.com> Date: Mon, 26 Jan 2026 17:04:40 +0300 Subject: [PATCH 13/14] docs:screenshot-fix --- .../current/quickstart/running-tests.mdx | 2 +- static/img/docs/guides/testplane-gui.png | Bin 0 -> 250274 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 static/img/docs/guides/testplane-gui.png diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx index 1283c37c..c0b74b3f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx @@ -135,7 +135,7 @@ testplane ../testplane-tests/example.testplane.ts Отслеживать процесс выполнения тестов очень легко, если запустить их в `gui`-режиме. В подобном формате работы html-reporter продемонстрирует, какие тесты были успешно выполнены, а в каких присутствуют ошибки и какого они характера. -Тут скриншот из gui testplane +![Интерфейс Testplane GUI](/img/docs/guides/testplane-gui.png) ### Browser.debug() diff --git a/static/img/docs/guides/testplane-gui.png b/static/img/docs/guides/testplane-gui.png new file mode 100644 index 0000000000000000000000000000000000000000..7aaa7c02a07c530f3cece4b4b2a3e8e0c0dd647a GIT binary patch literal 250274 zcmeFZWmH_t)-H@R?hr@_?hqV8a0%`nXlNu@2u^T!Cj^J!5+uRhEl6;8cXxNVi|l>Q zoBQ4|zVVHH{+=GI>BW*-HKm?<=A6|XtSB#mhWrv43JMBMN>WrA3JSRj3JNX+5gs^F zz1VpS1%)baA|j$FB_cwqXk+!k#M}r9N-{WB9RaM;jh6~hCIcb*kyD*f2T3B4Qu*Pz zNMOcBfDoyE`K9YiJXg0a3_^^<)rM2r^9n+<ACDC)t&i*Q>FKGfVRBfPLrm{&I%&La zsy$uw9JsR`sAa!gfc9GGh?hQ9l!H2<f-9?ufSSmPQN{a#py*9Z3XS~!6wZc@hMF3t zg>CMkv7reX)5vvP>Bsrt!=q+}A9*bl6eau$YKB$>#xWVxk(#%v8WfZgvU6wxoH7+t zAXoyP$gf5%xLmd7T`;@q)k(UXFyZr+PN<0SYc~x%sF&^$#<<93>F7k_D9Kd9r67IJ zAotVLTof`L#8SozZ`+aBo4wD)c4ZGa39Omjo5o@9-!b_56Vk@XoL-eb4%*!vMy2Jx zNNs~xJ2%((n9TO!YjtPsXj?3pXr(&p^Y@QImC!<;O09=<f)l1ghD2leP3a?S^#-7D zR)S9&oE9)iXx0z{M9e{-awwT!>38LDsSp-acwdXqcfd7(g3if`aJzk10?p-nayrol zT`5$4t)m_fUX{HiiMdI`tqmrO705|pTtVA5kQbXq>c&(dLuYtKBlflc`cn)ar+`}* zbs=pM0f#w-N;{oFsUMg+$`ADYtTaSc!+I{_zR2y`dT*-RoR!%WQJ1bu9`W5evMa+9 z#tnwS&l^!}`IV9{k^*%4TzDC@ERwjq@_16%tMB-Vm>Cg+RKhy;vz=K*E}Xn*nMJ#k zM3PCFKa&tcz);U~rAe40@!BcB-?iu4T<NjC@;q`Ug|U81`W_mt0RKHt7xc=Pg&ZBH z+#Mu%H&R#)ArN8U*JZ43Uw&+-Fa)tB#~@K50XC-hCF&q#vPY9=`D^VI$vZoUY=Jj1 zuWFZ&+OJ6o*M7Qoq(>RH`%)ggU|aEYY7cw|WzgmOZu;@?TJHj-&0Dq|^V^HmNHLNs zN_Z^T;EeY7nM9UBw<`-ypoW{a9iQ?~gUeh^Q#h%ZJV+QwlStZrv!m@DWlB2YK1|<9 z_+rvN7oYk<MC3U6pf0_46!DE$0$Q*f{ZFl}1-Z%z3~NO^=*wGQX5UvMHn|9kI6N~( zmLC3grD84-K2|bmO`xQ-akG2(-L3ycH>YEk@!8#6aT&9p#nnfdJ;)xZ13It2c8g{y zbxW)K`+hL5gKV&b+T7|-Tuwg7T)JSbqVZYc^@(9@LJaRD?<JR~>N#%E2V7cC3dcIT zD%s>%?Ut1m=76A%XvE}SWm?JFb5M#No~p&w>ZQJmxPH?Q{*XI*C;=LHc=#_K%dPTI zC5y04*k9b1&8Lq>GvQmVDPa5y6g!rCP@tQ*x2!Mmp>W<qo9pp0wGcVMdSZfFg<eZS z(Y=3lCO}YRi~kjA!$&L~6yyEwJ<)d<NXr{VlnXD|bfgL&l_d#lEG_uSpF}k-bw6!~ z5OjPo5Pc#rU$zRNVyd(we-hHde6tb?BEym-jPl8%&<Fx4k=jOfNaB~l{}8Ssx9{>; zL$pDz^|B+cjzo~eIH5Tq?ohN>Mc@!G9R5U0u%5pB5rzk@U)VTZYzx~CPBVBSy?$8y z?3oKz;}?gF!C?>7_ogp!*xLwM-~8&xELCN|iPk$URW5+Z|M{{6?<%=iUmMJPfljl6 zn(WWpic1(mvjXD=FF>OEMSZ!_iNXWEIYhAK_Gl3F@@LSJ&=L6&Vl<+LUxFXDFq|I3 zGRrc<oXou6q$qi$R0Ww!OXFAJKC%HaCK^lf1FBR?2W%3oTPeyY=x*;dkr3)*DYz`` z5%>|y5u_1ZWzKCzjWBa*2Qq|cm2PYs)>@y2P`6|^|GStw?@X!EOedvGI^iDk9_bzj zX_R&B?4%R#Jh94*8%3P&++$*6(x$NI4ChSev5AR^wTZc!1&Lsb49)Brfyz@;jIo6u zX8E)Ew&SB?xI6S?j(KUS&4mvk;@>nz?@WGFz|0_P679d<7uYX^7ip97%_SQHrz;f> zfBz)53YQ{WooTzJvMqLoeDn5(%B7fEY*aHkzk4#dSfkKc^fc!~GoFZ9gIa}J(}Yc- zw&wGSRFmk6!b;x?{~4KzYLm1HkM@=qc&JPwE#SVX0j}Py39_B3ts^@dyB@oXExWC= zZPVSl9qR3Yk-`c7oY{`{uM@Jw?BTUh&R)*G*E)mX@NYCDketbMN3p}OhZpXi?vWlp z9(3+^Zw}GG@NeO3;q%cFQ4jD}0xkkd1K80x2~s)sh`zD^a{c^XcKa)=fd{K_*k0Jm z=ZzN@?53PMoNrjsS>s+>u?SeqnYF$?=Imyr<K(s+G3a4A=fF*hOG;(A56kXdDLDbh z7O)oxuv3&Pvinb~m7|wqY`obB-ssqH?LX8|WSqiFz+1<&z;lnwiHnT0W!z=-EX@R$ z7M2xGKvK$G3@i*%%p1q*x^EJ~n$QMwx^fmaiWvJR1|0${!uC0MGl^!ICM~1qja<hT z`UPg2W*(woca{!F9j4ZXxZ_wRYzqv{`=^q=?0g~Y6z+8CM39%wMa<RDrOti6%Fvnp z1;T$YS2H+ZIp91nn{xPhl9=R8lapKTm7w;XHi&7QX_9(~8l(Vb$#8C!QAbdZ-2E^K zu|%zwtj@NyuuPldpEEi@JK!gdBi<$^;I}NZpJLrx-#bkO4MGjVr52_hHq135H=sM+ zFYj-q>^An^PKzc5X9myG2%NB9F5jA;Z=V=l=iGW6&aNA8bYA8kyKY%6GoLLkKgJkz zb}#R3G$2}|N5B!HJGGs!46LT2w!?_P$iVXYRQO{1=J^~TBq12XO(I<S*ZB7#apOxP zqG4zgxkMZH;T-Kax$6<@owg46<p*{Ks{0FmE(wnis}RkM)QdEuI26|qlgdoYeEXW& z#p0~-MbcM%RtbaPQr6P(QiYXYo&90F&j*9j@S5o#?1hC|d;EBVtAaz?t0-NRHI=nT zlGa+*IJ&i?f5|0sZtI8Wg>Yqa$HpYa)KOG=+6=APN7s+ejYyAJ%9bXunf3Vp2v9lA zAx;uc`<3Rvk`j@c+(ds*A(*J!b~w0XWZt)hV-2?pH|YP+YNHq4B~c-M_Ii}YR&Jx5 zA+4ZNgMZp%8h5%;zo<s~j*aHEvCKfy+}2!5Ev$35v+HU1_4c&{<xG@TcfvOHsIlB( z43DHtmNt9Chk~SG8kG3`2#OQBlMNW6t8a@%CGXt6IVo;Iz}i<1A4d9o%PT%eeW)C) zN^3N~7|mSDB-vQ91=-GQB=&2+zWm8I&`@|9d5wQMiw9-^m!-Svb+yijjfvHUdN4UK z>8RXIuB>%1%6yJz&^p;lim=*|c`a>7#ToCyJb%LYP<S=_#Rw<P0(?=c3cgWEu+<Qo z?p*g-7j)-*sP8$1S2HZy0&h3joW5Fl)tyI>f3G%KG_0vou`xW;RT;Kka58ezJRt{h z%xJCD?{(R6>9{HC3F_HEiz868xB|y&+c)y0yBvyt3+sK;G)*~YXH@>U5y1Eh(o@y& zsB?E;SV#sTFe@;7RfScJYw23XZ*p5pG9@^+&oZY}wK^)_Jy0}LGsA9AY;Rxx*`csN zVi)(Ezkbv~cLAp+xAteL-mcEZLYe#fH2p=Z+tJHKE=-T~ebU?aZDjc(1Q_h-@o&h( z$_Z{0%aZC6RT6o_{fJD=jxD*}#<sTx)XFDz4C*@7h;xZ`ZQPbbhl3xYN1_{Z%jEe@ zlO`NC&$31*2ijA&xd=Qi?!tcxrV4a&JFYYgUQeLtJpXdn{_tZv^S0xcL-j?QVEw&Y zO<Uu)<EYN;jo#FF2oH?|ar27f`q+3&zM#&kcD)n-j>~3PJ3+N&&iq~-Rm1t7!@_=i z^RtJO<$>0EzxwRU*N5-sIO}vz^XH3Q>*wmf@FThnJ+M41r%3on43hMcc<$+(_+4bJ z{Ma6?GiDmpboFs%dVF(TeXu#QN#Y{oF?r*@iZc5=@Zsq3Tcee-?xoChueV@)Gn-T8 zDf49W%#Fd#Ve^3J{j%IP#J9TgaO!ZNQNiQ!tgSCRD?B!BPoUBb{n5>l)bqViPy8{0 zCmc&U-;5#5(WX4qM>;5$rP@dy4SNAXhM*l;-k=uG);#vL_n#2bp&v#&FIt<ElA!ix z(Y-jva&w_EMPNyP;-uqHe;!*dpIfR`(Fi&+KXM<+{d|XdWEHX_dlS3(4U(>E5BF`> z6Pn1*7rROO?FS&h2r&XneUOubdIjtwLV=)PLcsxh(7;;&n&992;?T5Eu)mMPKtTnX zK!N_gMjrTldPM;5r+fbTgpCS-LID252HsBTF#oz5xhfs@U;A(&z&WV5Dk4%+z^96V zjggV1?MEv+EgfiF-~f`fq=qdN6du*n8(K=4@(5^u%0v}x2bPoNF|e{=(lfNuH)3+K zuzqR>iqDA$*tIaS(<61VFt@bjapEWceFYD&|8$s{ob>l4c4qwKU^zun5i1)b(l<;j zOf2LA$fTsCd^Uz3c$7uO|GpjgkDvUbot-rgGqa<kBa<ULla-AzGb=YYH!}+xGaDNt za0R2Sv!$J$6QiXq#b1s5s~u4zTLT*tYdaGwOVX!y_4KXm?fA*bpC0t@*I)fKax(d! zCt2G5Efydk^V1n-Rwfqaf42?X%J+1XN72N|$Xr9z!~)P6@C*Ug*Q{K8zi;?|PW{g# z|F{+WKew`RzGnOL);~`D?_1y58rg_gSpW~U6Zjv4{eAPFC;z^YkNHXVKXmbzqJJL+ zG%bM4$NX=r2_OfX6V(8QBs39KPz62#H+y=)bO3*7|M~>>VLpUC!=MA4P6$d$^sTBB z^j-==@-q&CU%C+st`a<NghaC`B+I?wluDr9U}E9o(yIx<p<INZm8=FL(~@S=%H?@| zCl#ynI$O9t>5JU!+cU4NZ=9cA?>=<o?52!1d`Rf-`;gkuU1feeV~{!5{Z2+-L;{9V z=$~&$WD#Ml6Mojl8UINuOfb)?>bzJa|9L+sBVGV)PzUhJP(fpp{?pAGE4^eK&hPC% zZ-Lai9W%XzgLO{h6;cG$Ki#}Sq5KP(NKvEyaSKpLlmS2+!OH{6QeGg*f4XUbXwB`R z^&<Xx3zDR)KpS$Xj&aiefpwS2h4k`K5Pkm>CkrMM(lgVqDOddutPA}d8X2N3^n&uA zdKloElBhr%IBhA}|A}>L5EZ9(U|LE4sfPiMC8Q6uk@(_(A@)D8Zi|>#tJ-r+`2WPo zutmIDC)%xzfBYxb{V#<4->&dq2>D;L+J7PBf6Yh#g^>S+H~)o@|3b+BCr9}&g#14c zl3da{0muHOI|{DT@Vw06WU-mt)v>;dWs4UEqq(x%(O%#GFY-BpxYiTfd$Kl=0$G|| zR^7BcT6)Xvc2#ojc($eVlY|H<i_Lp)x*X-fX01!_{#Zw0ePW|F5(>#Mt|fTNn3&5+ z5?$j^Tw*Qg+|rJr{Dm_gQiK<RcN@Zm%icN@z~}_AyW$v=&S&Y22a<hQqxqaqu1XVG z3vm_^^h2T(I=rCLl32aE7~B+=MBNsynv}qQl6-Gj9Igz%!a$7R&yRQCW$_(93s1=E z@*EEW#N>+IIQR)DBJg40Q1D`4;E)ZeQ5K2otri8it(Tzu3z{s*)EIO?q%*CZ(+$q2 zzw*0k;@VvKi)u7pAZ77+s~)gl?#(jaVi2kxia#sMx%2PCIfH{nwsRXsEdU`I20{5r zT%T@M|B_i!pz6HzRyFM(<9RN?sglp{{AL(Sa(z?I4)itT4~|BPi<r#PI^oYA-rZSD zn4QiMTs))R%uN?;HkcRvMC?p)yt0+SO#X1A_qUh586u`{x<AEB!>A`urt-VeejE?$ zWke#Rfhxrl7-&USdd0P{`(r4C4*~m#*;{po9fO#Mq5fhoJAJ%4p1>?F6I3CTYd&2@ z=Muojh%HG3lkrQ2l<SppmZUV;di_>tN2~aU3$>eNj%n{n!yZW9Z!_%PZJXu~g3QH= zn2gyvVI|0&$51(9Dp{sG0)%R=C}j_TUTLtQ5w#o-7c4Y5I-GNTYH;HSjwOJY6f+Tu zp_;n>Ty0@DV>X@@xU!TgV$XeJ!5sk<H@yWo)rx`_Ov-QTVA8`J9Ag*Q`LaeE+PjfR zPQ>w+?(}bN1dlK9&Ax{DanYbx|0mf_{t8monc_J~`F7G<{j4&lbzM%+>{SK%pf^Zz zNtB|*@la>F)F9A)rklaC!?^{UkqA(#A&m3MTBO_ciJ1IW+p~sA_bJ)}Y@{qMZ^M1S z!1K(mmn4$M6U*C?UY<yb>8p!Xdi|25nlKl~*moYUv}&v?0&l2^77^S=pA|@gVB9Rl zq1v}%s3VXElVU7MQwjfoULi0H9iw@6_C$$Anq4HID(&df)+ZNykJYkwR0yf=DK+Sf z8nK}-<UA3k_CCXr6sPxVm>gXk;PU}VlD+{&6%J+@RZUg!+LC$qVWtbwu%9_GAYBD| zxi?TwGgP0rw+K#RnPDdYV<HltRm>Dea${4@v>J8r%b#9`?Hz+##gyzU8zA8RdgPj2 zAowX>5Dn|dd(tJqFX0EP%%}DD8Fd<Jp2l`;YuZndK=ke+?fN?n?uihPn>>_SqU}cC z_NivhyYNdF<<~g$QZRIKq#8GUzJchyU$kA7>lmGEC?ilov_nz>YWhS{q&$GeqB6xo zkqc^X=a1c64jJ2TYLSj=x2ao?f+b1u(>Q7Ld5@F-j5m@&%F0tNR}qrU1Yf!q<_DsU zXls#xuFmau&OD6=FP#g-Otm0L^iPY$Chaf8>!JyUAL%ECJwav0gG^!}&yICF_)Obf z=V6f@0mNyK6)HPRfK(j<<F#|8C%_*#2EqZf_!a3gLWm7#Xq9TZ%qV&!TLz`d!@-T@ z$ac5HQU`Q3cb3`ePDAc(=(>;`WYKO*{l^fn#+H=Q+noyk(&HC-H?(5DvxcEGm3U{- z#r~A5@P4wXH!K23t-GWzHH|2q>hh`0ZeddVxXF#iR0egNT`8C!3N4538>#NMaJ}fG zK%Xd6q2PFetzaj-K>Bh*;qmVaKnBzvfd3zv^_J)?BOcY_e?ecBe+|=p(!f1D<G(5M z$K<voM@r>ih@mB6R=CigGUrNXE_ZNYu>Yi1DA1k^aV#TbVp8$r&*91bwjL@ZZ^IIy zE?0gjCTg{4kUSKO$055(%08$D(K$p)1Q>={Ca=_DK$+C(oigtWD`gYdlt`W*`niz= zaej(>jskf5FCX$#N!+KdNZ~H<Vf+z-d-q{Ua_jB-;CHUgztv*nvW@o_49_VUPup+R z1f+}*azQd*XwX0>;-TyaeIBn>Ym<F>RFl;SwRkXJzp9LfLguYX0~IRLFsafQ>+JCa z6y!^j0;erMMapb17r-ghr)1FBU6cUgvLQtTMtdnBZT<Ez=~O;vH+7jNjH@Cka0V^l zWB63G)Vz9#SNG1gt*=1}F@KH*a_<<NDv#p{r3AJEa668%&l&}gPB^K=H<Nu*D}etm z<}1zOsqB1f#=|WS7aGevA3X%s!tBYtvnaeU%C1k=PmLO#i*L&`z;xJL8Aw-^gL93J zX?wGtM}C$BseDLR!GJwIOwlI%LHZJaJ(n(3mRtP|;y-=1S-BPZEX0;ngR^AIe$b_n zK8OVLan;tXfcQ_?AVkVaE;+w$k}w<};legl`t;PpImL3)CvA5r!?@H^fUVE+OYAoX zQf#h{mKoMt`lOab$s#Bu(CJjaM=&eIU)C`v`Nxub<0N|N-uCVDI`(F?Nyu+iEq6zI zr6B^amOJrYTawfPbmk;Akj$Me8jM}q%O%cz>`p39njkm#q(sLxhM`T+-9y7Mw}4vs zpE~>kQkI{(X|b0uWL8K2vP$)=Ig{2rjKnNX^ht#pQcGLI-^xC%it9qm=bPN?(^Kt$ z!wEDI{H_jkz<jg8-s3~)RBQ~b`aueqkLh4M_Svh=7k?~-)$qvYyo^VP7D5G}7f0l4 z9{@h0fC>$J<K%i`=vKcaX20`4gl#gzR-=`E!d|41Zuu859-a%yENDM*{!AGLNs_Mn z6*_8(qmW}k_@!_F9LYlKH2sYu2;QEL_meY_DszqSsrJK}I(xGf`kyZ&gpjdl)e62c z4+i{Of2m$1w|$RQ2VBdu{!o%F3IM<ztC*EAQDQ=1JcyZ>Z-vZf6Kgxd0SG=e=#PpD z^h!(P>=udS__Hrvra(^f1Bc!Q2|KX!3zdqU{0S}maE@+U@H;G#nnl6h&nT88BJnU! zbrecb!g57m=!9Fs-KqT<=xjb6zg7+aT#9Zqh7SuHk$h?M7`q7r#PQsb5(!#>Gde2+ z++Txzbb3l%L<)ZKha-6v1L(Q4yP>zf2c+&QN7K`uj2M;VwTLi4>mYNm8(Miu0xcL) zAryRB_gI8G5d@MHYXAMz1~#b{-6vVqT}$3jV#@q2D+46axg+{X)~x=rDF5HGwnm)) znMLbzd6JdiAP$q_NmfM4a$wYwLX%+rC96P<B-lTO{7F_QK-M3<pJ@PDMgEr6*7;ae z+3!i#kqACC>?6{@WOaJ|!jTH@N!H8+a5opuldJ=@;6LYDuka^XSAXe^CqJb+Pu>Hv z0J0|hE$c>psQo3yldLU(tR%&F6Tu+KzaHAa^j}f`e_9oKm5{s5*k!GxtJGk5l;zD= zp-lfmi7$koRCltX<!$`h;J*BT7j!w>I;Nan%Ze`Jjgkm)q`W}n3cr-ZJTF?R6LY7< zc_JM`Jcw8BOMT(`oM-2*mj|)F2pB!fZGlt(=Z~USXX=h;*5&D9rvq}|UL<0gq|2SI z+SQl`&h@=OXuvNi2sD6-G<C=+IA`Xu$A16HaJs@de{Zf<#$kVf9$<HI!f+^(+ff}< z5?{KSCyxoV9V-tOw^$n*zTzSoB6*7fGYGo?{ELg##$OZU9mYtCAEaRfevd1`1`I;( z%+9IN$&b_J#?K$Jq)E#AvgB`dA}^BOxL%ltxa)fk{#va~aIoO<?3Cyo+I4=hUn(mB zgHN@zxhW?;7~J_Z*ZphDA_h{#26H)#%!6bWyyB<OVrxflBXK>6V$;M)BHzidzahn0 z-MQtd7e$6V-t01OPaVHPNK0V%j0M6It_y!;xASrJlP-#I&0*sd`#86=VMK`$$>T5W z#NJ3a)Ls0SbGum4P1@sm@1lplHA^kNm6dx!V0_Z0OK*0;;Q5QgMdsdk=J+rIPE8** z&I2HtlAJDE&oUi<Wx67|=7@QDj2bP(hj^uw<$roH+GuHo5L8I5kh)j1Imo?9cuX(h zI=@ZhZAejKwdj$+w*FjYKaLUdO34r%N8Yb992iM{40Vj?-=zc$Iu+t-Q64a_=FwYY z6USfm;>K6RVRwYqeUrQTNuwJfVp@hdCpW20XPm4#-##n?<3WRo;r-<~tZbRtOhp0^ zF?Nc29{s#kZuW4;LEqG<G>z4AJEo%Uo~yNuq2&)pk_h2M+9vaUMh!qhlxQ<XpKb+M z+%2`VbXR8Pb$r|=hwL(ar*7`WL+7`(@D7<m>G5K92_EfFO2YIn!Z$bGscSQOK3p5u z4YGSa+_9NW^x2Vg!b*~sgYNDcy7y|2o&KaUV^1Z9&$DF~orEJNCugD3c#jUM0nJX5 z8t0!s`76^t*-O`H8P56f9)s|8n)3#J?b*o17xxSpU61Q*zK1IuwX+1{RJt^`lTr$k zEGG&5@D|5Kcbdz2$7TuH8;5sAS{hCF4(2!LAtF2HyOX^u9ifF$A<vna-b(^8Q^XwT zPQZHOdFgDG#q3)3qK9qJtZ=2<Pan8!%lWz}PAi>3O3Z`3naa_S-W1;IXG~gC?Aot@ z0brXegozp`Mcb`EjyLh+iPeu2Ovp;dF_>JdET;iOX=x|kb$<TW<Nalt{o(8-FwS%a zxK>ebnQjaup)OOaFx4wQD)qi{KrJ^`6QG7N+fvDu=iuObygS8NcmT#44x`~PT7}WB zgY#;mf?<8x$jk1xOEZP1>&m+oFH@xsUGKQd%X-izwa6+JinkLEL>FtFd*5hOIyCxc zl#~TQ8cnmaglv{t-fGxsDf*gaSQd^d_bBV~fp~P#?oS5=Wr}sGc{6$Jwn$;&&m8wq zaKWYe{uNf9KY~-)jjL8KWwM-XN%<)wT0wVwI#FAf-p(kGG2s$lx)pbIr}7diOoq8c zI#1Hia9;4ZWijZ~welRz)m3Jn#y-y#jDA6k6~0NG8YmNYmy)6Rc-)zZO4R8qNy`4) z2l||k!T*;J0HrFx2iU*0s;L_6hZvm982p?N*VpRE>St1FRdZXi9Q$6{_7aZt&Br03 zVC}`w5M#<^)2u5=|E#)>2?fD>Rd{r_o>uFNY2)6b<u-CMy{A+M!1w^*!_cqhFq=SR z2&gAwc=`4_QpQe`yDQOf2@68+5~fjY3cp}`n(IOB0VN%dPRV<3qh&lA)k01Cb9D~7 z0gK5ZZ8n2ns49~ag%A|K7eAKnXh&ew1?P5k_Uo;eg{MpOL=;u?)e1i*a0f}7JGcSH zrl!#B<e-&D#PBa1=CIB5XZXIplKt4_O5$$P{=II)Nez!#Q}kkAkhI=Hjndw27Ef-? zM@`Ot$ehlcgSOR$T0WI&l}4^~B!)ppmUwTJ-`u5Hv`FW+xZ&=?S|#OY_~pBaucLxk z^M*_83QxAobvtZyH@dqzeydb&JQ!lirtw=f={~i-Ams?Jv`!i9dw~diJ$lBfUzZ;; zN;q5=DyClY)8~9<gccOE(s#}23;UCBCIFNB%hd^66i8arYWm)cM_-Ihl;&{BjLCGi z`2L=9qnZPq3TslZuW?^c8=FNdR;K{`2qLBQ6p#Cj-S$Fb;5#H#wyAhX64$XSoqCa0 zJ9P-9R@gI=A6^EXU*O#5%1I2KZwTBSiHy|R8oO5Xr^y9V-O;3Hl#EY^o<#|TJdqcT z%nr+X$3r|m&%J|+QTgaHqh6jkiG^ybwD+|G<N4}IS(r_4EM{IyVoK@dzg>d>x<0l_ z6kXiaF?QP;BD>zsO_O~F+6@e6j@8bUC!$ly`5gR>#sOd}VMM)#y}_*$Tbc~R!g0QN zR&IOS>T5&C515rj?wWG7dn;<n6_RAO*uYd7t^1)bHaI#r!To5?eHKkQL(8O702xBW zB@9n%juuRvffbBdrT-4}nc2jcgaYs(<MowL3dm~clf1>f8*w1zgunH_gjXeSy+jnH z9_B5&%7P`%_*wWl4Gc|P$$1>aGLHAKf!%b5CMAX^+A34Pgj$yC$uijJfw+HG<&V&Q zPjJsH^>$SnQ(Dy=KdCP!s_bx}AR;+h;yB-YfjuxprH2W|t`ayK5@B<(o57agTy%@> zOKTR|7)UXz?4EbLjD%R$Qt97{Vi$>rgoB0rK<B%hEIxA*B<_@-OE`o9lAlw;@pegr z0m+aIx)~#xN^|dGGtIMSrqWCf<+b^gA~G&=&>PwIPlF-2k6mR(GW`eagM2E|@eU!J zwB{U6Zo7boJ;+6Gxz6u?o-BmWc-|cc;`-k<8Fnj|xm~l-Yc)81@;-N|o5BG~{ui>* zR1(OLZ)QJ4Q8aS;@8rb8(7<X<HRsMFdCuP=Vjs=AZJgCL9LI|+3stgZa(nHDW|XIP zTCmsvypVj26ej6cVn6o1cx_mMq;lZ)Y*ZmWj&U%4Qpa^3nbUGkG60?EWu(;a@el;Z z0*#af1uYmJPkH0s!e~x3RUD38wIT1VrqHOuHSAwFU+)+%;C>+87_g3?{p`sC%E+%3 z0{$EuDu4&|KWkd@9i=xHL%(c{kO_%LmwoQB_6yeLN|E$XSgc&iyVYcw2qJp1hYa>Y z%aT4_@1s&K)6&#IxC}L4SdvYmNs}y9HaWf>-|2vGW_N6M(J8Nw?I}0eyqT_ttX*nI z7%mb!9t5M&2u~r0I-|XO{hH=sIgq4hrNgs3{jG1Idn~<r9FWZVKE2?5!5(apD%m<N zd0bV%w$@)zf~(egMDg77NIg%ryX@uH$n{7JLN3xP#Zu?f58T&l(b>ZO$Yk+j^A#r1 z_Dv+7ccl%t2d;6=vI!$F8Tk(WD9_{OYDF;KlZPAi#%ai`yerh?C}5G@>}CXL;KWl1 z^)xcsjR#_gcpboKN_nbuKOohXQ{vn%XWwO0`KH14qMquFF7~@G&g!j8c5Vg*AHg1A zdyAoT@Ab~+WNuq{4JVGs9$1v9+sni1hxh#0ovX|vrx?y#Cy{T|na(q9<}wDH28j8b zhI;6&78=OX5N^I&L)HRaTJJArtzbfxt=D_wIT<j~FZ+b}c2Hz$x`4~vcCI5i83+hh z*Wb$#>C@eL<1%RayI5v2jFH%j0~v;if&^~(3Emcgn6^`VzHmZrayaB?K~AX|C*67X z9U-{Ac?ImTR+)$P#XW<m{1AW$d=Og}U{cETySv#frrvhmOflA0`5+NPtVM7m@QbWw z)7m1Sj;YPa>=0+jlazUGarg}B!tGPql7~p+(qKGu+PmD3C&6NiKDY2%OCA6fQmF(6 zP-xSKdG~m!82Rv&gvFa-a0RUO)UA32-3*>P0cfU1o^3^coaS5Md1ZhZaWk1+nzWvo z9b@!Ivfk&q;n4}U&oyJk2+&>jQiBfBm>W5?CJJsRd2+zR<Rgcv5F#1^>8U!iOXA=3 ze5SIq>_ih-zux(282*BGw=}8RJlbN`F|UcS`Wdq(IOI7!cGcp1scn3n)yOZClYGYr zt=fje6xf}6lS3VjE!#O<IxWfbpFC@GD{kl{^1Hi=;UD3IW*xpe(!Gm4^h~5K-^eZ< zEWEk7J*b&}79rT2XYlpb^)j{51XWeX9|bq2EL%ogyNPbC27z$0XfgdJQA@c<mX0n$ z7tPw4%e^Uoq97hi*Udng&syEF+xbrWRd^4D)xr?o!+SEP`{pFWded+=DjCTiQ5~&S zNpzH7QbiFV^$z<Vs9hF6Nm!Xo?D5s!?Bw@rJYKD4rFYv?pVZJqS#u~i@CpV<k>Vm< zIX6%aAHtSCtC~rx<+j~Gyw>{35wO{s%75FpuJ--C$0@OXcpLkxwEb%zNAGd7$4U>| zCHKs%o8BNJ`P<WH*Q>R<b%vi*(iBru>RH6C$3mD?OT1q3_P0ngEZ83rz#MWXRa?fp z@60zF9_#Nk{`l-n0sx<FtJ>$oB}Zhf3}OF5N2#k))6t&T{4uL7_X%mAtNpzBiNe`2 zUfr3R7r(HM20mXr-X2nwdGHstwfO|oT@kOYAI)AZdUAFJpgn3fr?BV|(@WqofQRU7 z4RpnKcDsb&Sf*XKN3thsw8rzs$9To)9I*ooWLpLYlU}5~DyMh~{{P-2^`9~VHOFk^ z=8bt0^4zx#UgHz$H$*Y#`6B{`=`I*eF_4fL$^5Mzs?N1SXatdon^t-5EV0`PBbhNy zQRkZc(VP5vVFuB%!8v;&9tVZ;m-!x>JcNFwqcp^k$7v)EdkU(gSgjMR2ETfIF$6xQ zuz~1Ca}}Zit|bi^3N-*=Wuhev=3LLEYF^R_Xe|u48>vnqd-ofsxfh9MAHEDNzBu8r z-=Vzvs#Nju3d8-ymd}gx{Ng4&FqvKb&G}$ufrn5~HWBE@X4;}Mf{5kqrKfUH!-=%S z-Av^!sCTLV>#><)V`%={Dkkl^E;-Kmm<(@GiMOZ`Ldl4CC%w$3LpIr}YWgmSiTc|C z7$lZxA#bb}_%@sa^K*fm3^t>KuOzOILd~mpd6Ho-Ia-%1R!IHEJB0`XHt|4j9ZJ={ z%RaGn8K|8vi$Gq@et$a{{=z9}%34leNGsHL@A&9&x?CbhF6o0qwZ-hj%dff>A0tVv zs@wb!){siIUFLs<5f}kc1Qv$bNLFyiuT;!II@AS`t9s+`#N}0+k|^gMx?Hf2lDeuQ z=H$T1dT$uZa4M;8)7`7I#2y%9sawkgHlw_oj}aF?Te0*|mJEFEec>{XhM|2Q9`CEU zx11`Zekf-bp0K^5EyqnWDaJ$k!1L{eaRe~&y7OQz$R)6K>;+VQa1(8HFE~6*<S^5` z43>KK(>GiXvubs)SFd%Xn?@a({%E*UYzJpZ_pK~(p2E<z&|0wLPAvT+tB)H|palSp zjBj1On{leS_v(7yXYZ8$(y@ym*&Hb|T5}vphYddrYG;VN|Hv@=*0%Nf;r?>*t-IU9 z9o(42Fq^*TB1Q*8oI6{Q=h=RV-=4$Ptl4;;(Rub$lrs!a3XcH)8tQ0Sm%AxrYR#^b zff8oJ?y?Fb9<kW3tC3CBVp$D(qaUVAK9$E7d11d-E7t7(^cgaxnWKtyy)#Q9%gw{u z9>FnII%#LViJ8CUbcuJK%wm#Ppjp2_WQ0UNXy#UwyjbC(*o;EPwUae}Ek__(kD|Gi zZg9K4g~VoX;Z}Y)K&+jm++jmqz`uhlLA>+mL#VuBS#xu~BbiIjG%`$9jTwpVw%i|a z%#(Q!w<&{S&t!=ZTDwXu7is@!X(u7&!GXVW;!v2alQQbNGRI5G^58b+Sf+^-^Rx3{ zh)M3{ShUQ{8^<8|TQoPR3zD=cBL%C=$?uvPE(=rLO)_#Nm<e3YiG9iCE!Y)2<nPi| zgI8?s$K$21%N{T_+96XR<CIJ_OTxZJYhBE1V=4-<c#N7ry%th<?|yp`OJLRTGwb-| zH<7#SiN2NBg89Lv<}-12>Lis36maRx-EEF`%T$69mPw)9z;a$cc?05Ifk@Kmx~Jvv zM^p78rBQFzeNh^pecb1SqiB0@Y&5;(;lX70-Qx4!@2rcx#OJpI2S<CR6I+BP^O=$F z%Otr1H;JjwKPO$uC<<_)532AXKsc;koLt7=Yv(EqTKWVNkuBQiy^Av|ZlK({Z`YQP zZAoA=_#%7bEUwtta-6BwpSA9c=HEKehXgrneuTaTrgX-;aJ~@*&1y@=%SF%TzC@1M z*WLclb!eFzjvA0=o1xv$$7#(@PTMd9&F?QJf{9)CShvTvO^GV1>SLf@4xVEO-VO1M z?jy}qSy0T^*`x4o3s9Vk;lF@&-p-C6|JrFN!H*UAsUL_Ix?nNp4p9uKeq8k(pPs@` zLID-Ze<hEA9WP+%Cu<Wr0GJjwKcz;RWLjV*OUMG(ik-coUF%Ei#}_--`X7doAcYt& zw3AyW<oRuTG~~)5#*HfYJ~MZRtUp{_5X*@>r|+)Lv92F_+e0BZWab@FtfRSuC6abq zLvP_xx2&$28_(EeDtub(C$(vSzDHCFjy3;ga2Y3I4$bb0s{oaa97y4n;Cr?bNX+j7 zLZhw0XorHW(X##tcR)*zy8P21oS$hG$OwI|J|ukoF`~jMjr!$6%_kLAa+vB8T>S{w z!+iqEkCpMTg6$O|0jR;WJ@zD>EJ&0-End*DvZa`$_WLvAe(bBmnQAG4I%>*+<u<#Q z-sPg9^Luy%WG8;>10NY))Xg%mx2hp}lO}#%BUQ^&KV&#Rrm#{}uD(K58v=?Ut8|8= z2qJ{`1KgWgu0t+u0cd5`dWjsYDe;pNjOPAa&pH7(@YLxhx@`^qOxCXX1GF{F;_LNX zSYtj1AFE2Z;C$BVl;6MRLVESayF(W&E1PxtO%jC<+r7Acw%ubfWB<%BfJ!C~9E}-4 z46_XYZ4Jgbs?~H^2UzV?_Wi6g5bLA(d1NFvT4s#A7i1t>B}O+w!k9dmt3CB;pE}?h zelBE(LC<RWJim!5ABe@-WJCpD?m7s$rZ{gTU=>$e-_y=;tGTnCwBGX@4-h(u@W_s| z*ng;wlhe!RECS*Y($=pxyILx>2nP*f8pEjb#tD9Ud=90ZR2GT77}Dv`GT$=@86xNB z^C6OC$iLqIdZpL9W>PL}q<8ULvH6?pqD^58gHqp~@8-FqU?gt-2<Vdb@Tb5$(J>G3 zB@Rx)mEJe~1ik)+N!uUG{Y&h+2uKo-!X_OeG;_yUmK9SAXC1m!L>7>7EiUT)3*R60 zJ4PG!6fYn08!DE%Y1C%RbVlHv;4x@2kc1K9P=4g_C%F`|p4r_n%X;s9H<9`o5poIS z7)adsddM_w;=i@2YmJB}zA4D{MVkEunF6vtiZV_<IKQNM1n-V~&zWxb$_IN&^x7EF zzZM+tR~xjk1^<rP-`I+$T7#576@bFF{z?Y6D^DT!E)+Mgx6W~D#+W(~UiN7p1-hLW zrCXUz91+&uMX6CwE2w<vnL4C_Ecf8{LIyR6-UMqAgQJ?uJI^<!*F*5Y(eBHY7(RaA z4(t%s3ssdY>wlCRL<PzldrsCK<XxPsbK<l%?JgzEY*rNkkb+LkXNaVBULV5L*~p)3 z$><$B-65k6R@$5>WK1b;m`Y(NT6W^&Jih<f9o1ZT+Ul4M#Bn`G+%x`}U%A*!MkqV2 z@C*roG8#Y&epsp9`!q$?_!oZNnl9h8m~c|d>RE6-Z0rGdg{5}0DeJm<?#$n!pQiM) z8&u*P5*0Vy<QZAKQB8E(Q|K`M0+gJNKdQvN{i7}%K?sT|u@8IcfX7{%C&++qh&M|V zpUqetH|@se;wJ7_nBuw~Qx`Vco<n<?4P@T~D%Hxt<6W=x@^1<CGS1USer~AO1)4Jz z?sipU^v=-*8xdIP(K9qxn&ZF7a#;=I%yK?GWiSVl1gbh7*G9b<dYfZER0w<N>!-z^ z;&{i+_yubh|H3Oow`Kq5aA<BVnOJfA6_CYIdwli}6}fOZ#Qc_M(ilogDLj>oy;9#K z!iP1$v%xsc)p5f=WtTfiJm+RO^;$s^FYAM<Y&Yelvl2g$!INYaH#db+Fv1HQeR?^8 zLZk37&8leA&ylhoDM9=x%h(HaC)pxk9dOejJhy{aNv?|wc}vz0Bn&=0FU=4y(Lu~F z#6-{T)Z1Pce7I`k0S$80mxp#lG;2H6=ywQ~nJ1`>g*=e>@GzJK7>z_$J7B_RSUEz} z?Lr4h$J$QDtTAogng?eOwaliH4B%W($5&1m`OmcKGx}v)FAw0xW!p7jeglA#Q*sVb zPv>o=Y+Aie7r|hw&AJa`rZdu%wFf4$@b^lOdzVgbVW^y0sC_i1rMyC$Oa`!ne+Ld% z1;z{#dck|T)_&A&GM0smxZC+Mqw-k1$Cf&voGLD9M!vFDs;m2n;f=mhaMR>GZbfMF zK8z6fXEFypX^aomq@=!)mI7w?EK$*ZYY#G3k^3C`jmMbD0RN@dpp+o~HqJ{<OTsgc z&$|S1toxHpJ@ODdf!x))aeHB1uS<*i(07Y4^r=alx%2cHH)v;O;3C#WCW^k5@$e7t zc&G4}oxMHmsx@KeEszNASwIh#&wB;%W%T6J1m=}MsqerhF!!%+x*J_D!N*P;Q^it1 zUTR8~bXhnK&a<ae7v6(6j#f?8vZ2VgrN>k}MSD+g5O!j$C!Vpepk_a^a5folgY4Jd z=%{Pd^wuZAp25+SdnShFw_f#*iypl$KjwF6d%V}DVXjXTR&pPB6f~5RKDNRV*Qqpm za?@bE-5b$Gg6tP*B`Dh*?*A0_eToQiJW#=u#CKPXN{tGcO_(mNwH@@BLmwSI%HeHp z=*I07gEc*4bU$$6FQw*xkE33=T8tx=$;+Mi{%XEn$LPx>F}Tq`R;S+Ah&QW~ZP4>o zXi5DzrRM$s->T~N43IdwI_@eh)1F^7i-}dY_<@J@k(VVfU!$=M$U{$^tS+Jydy?*Q znzQLSwLF8#X54u>G~r|K6L@om$v~GP`5dT!2ne1HKG(L3Ot^CEW#sIgcU-g(8%*L< zC=9`=p=r8WLE+FNJ+o#19Yz-6R8vCzCRC><<wLZ~hp;H)w_b^7>GLff?vz%kb39)U z3U8;~!^*(ON;wXg#^`Pja;JQB%HPwCrq6)_-H22>lX*vZkAM`s@UXrZO0PV}z`n=8 z5)rhT{<)P$3fYa8tn{bc&Uyut^M!JYTXgStF1NgJ1?eI+I}Z5HlRwK3(W&arZ5&W; zc!+U60RcOC{g`X?yfW{A%w%M%njHO2p*HP~K%RaF@=moH8AryMxl#WaG>*k1yrBc} zrD}fMMU%s<MfDG3mqa*+&-hvn(?;m@g;)DwpS*>4G<B=YoriW@FBe?HE3s2~AI~P9 zFM4QJnyz0x_?1B_K4$!CdewE<%xvSPB7CpsKjCp$yBjgStm?LLb?qEIzTbWK=W3#; zz$fx#nGei5w~cWSWIQC*EZu5YX(%j2<H{2IQ+Tj9Kc%ZfPOypXk8w|{Cc${sJbcCK zBF(%JKKvOQsq`w1%}{{5$Q1Kk&Yf=fZS*IZ`LTfYU#LMUU4gP(tLF2tzmlrmTN47` zvBEmkE`>x77pn8AOnLB!aYQi%@6V#o_FdC{V1sxl#6K(gqCiK!@#7)l(J~pa+nsoK z&~Pf>i9LD{ju)@%QOkEeCS;4sm*#Qv1C{V9rV?YK9S9LOf+oG|59(wz&=L&cSTRq4 ztQ(gfB2aUU%}w)=7P#HBbr1SX#H+@5sy*$uO$IEV^z!O&Om_RwUZ|5f0rZ)m*X#H_ zP!4>eQtwT0qY?Ln2@q{wsTXCoM`ZIAeiJw41nYU(3v(g5vMBDa&*_uKpoFH0g@AO_ z6q&))mCVc9Rp#73w0V!95uNKhjO!Z82Y7n1Ep}M^(!g~ysU)IlPF05OjNp=%AV~hZ zd?uh6dEN(~`G)aM%ttv}S_4L(F|!RJJB8O#Q;oG^X-vj0)Xs@%=K_YF1(tUo6QS3; z3j-adjau#{uYmskOr^|+zONY=rE!V2o@>Hcjf_}DD!E^%qQ>#wmttH_0Tdm*cwHFD z#@<cMiq1St_J#0h0=sddACQzR0i{iOo1qRuyA!S8L*+MT5FY&4HXZi{LX$+w8u*?# zMl;Yr7tUliuq;jXC8DfS(Y)9!&C<OS(B%!qdKUG=c8(vno_lTg!QpB*I&T~G_@^!1 zYps{b<Fegv&r@*Sdja}boNuC$+~3DRz*sJN{!L>V9aHOD_#_S@6mF*8S?`RqwsBCX zQ5vceCun(QS%kJoYCbnd`;lPmoMel}UK1I@fwcK%SQzgvj^ONMh%|%F5Bq%Z2SrKQ zMr!&lpYKuxWY2r|o5W;(ws@s9HETib+jc$OnWkp!qz%VlxygcAQsze7s}yAD_5Mjm zyR}M%U@?hTGA1YwVDl5fY!}?^;eAt_fTQ(pV_nYWU7o6V7h@hVpXLD_QVNN1PLqnw zZHYtlvJ!Z`&v*%{`EF%K)2g}Q4!oaE?C=FmLSp%naWy!7`D$%@<)q!TvX$X%4=tu@ zrm<;fJ9xBtwW*CKc*d1d)EBQM)rvHX_eJW@>aI1%f33?Uelx{i!%Fj{KzY>Czc}A7 zjPqyG9`REzzE{`raIHbY@MtK;K8N>{<Y~1iE!Ih4&ssIIAYz!jua^q`EHD%ZhJi4@ zEt0}SP~h_~bh5NNa0}zkR}i?KYRFw(01A+J;?I0Pkuby(_SANIZS*DVUs>SL38*&B zdFo?Jc9ddQWO?ZRs;^(vdI~g6N^#AH1Nl!^X3ON{g1p*JZ;x-S)bFEigfiz4(%h&* zA8uw(R&~=-=f^~Y*{D&&wWsnZj!+x4H9mAwBJEe1&s#RCm@m}H9Y&!*IBaDrgxYIJ za%kyKypc4&n!|?fiiLvI^t_iY)~hAB7D7|qj?g;|@z-L99f_EK9vyl<Fp_xO;FeVq ziZ)cw+lBddR_*hy7h(wD+;Wq%`WKq?jhrstyTf;`%y#0cYaP5$DBm=BgwMKcJ{t)& z&9q%j@6HTzjQ_M>Ihhws@6G^AcH{H{yY_q{6&-p9D4#F_<)%rgf<j*AJydnw+e_js zcc9Qyhzq^n`p&J|dC40A%YxgDy|wzAXzkzV_}$?{EwD1+-`fL41hx4)mtWEOa2|%Y z#fig{v<VDVE8k=eA8NDQaM!sHs_tpnt`1;%K3bG3PlcSM9_~IH*=Z$lWv^bN|B5$L z4O#0>9dOyUJDvf!JM!`?fuT0%V8(r!-U`e49<cTK3f5DSZGnF9Y_A-{R1~|42~s87 zKZb^ispWZpeljHD4GnUwC2>2-;Cplvy5lwMLYaW_lRpLKz;5?TIDwB0S~V@UYK#R{ z0*`mL6Uwu4NpIN81MCGZyuVnts`Z`Bmyq?7>wTYY!(7*ofLF^RrEM)RWbSpXGq;p# zVFu}QIBg&((SN;hUt$<;Rb%m~>1}$=c42~mlAJ9a8-q<H4fau~Uy{lFWVNr~1+<9R z^TvGYJ$kWvaq7D-)1T&Gm4*s5%45gty{x+|G=^o|@CKAg7?g$Qa<xZu<cMH*Ts*TZ zUbm`^Ei#Gw4<YyF&LRxkb6c7vlq9>%nB;DM?A@U{P}<+~5pBeP9C=w|-RX5#0_%>y zoMhrwJzQ_5l{xHd((kTx$aGB`yD|r&3?IBPGAc0a#x~uqViTt{DOPE?OTa?<U_^9p zUA%i*4lxa|U;WUZh>!JZWNMYH#Bw;(9}9V#?+kAep$KQcK{SU4{k~Kiv|lyz(jv|4 zZf}yh)roWTG6u4T6q>Sy(Lh+hEV5UWGT+L2VI;PQEu&{v(T6=8{=tZMX<D`Uq4w=W z{qTo_Hx~1Cm8&;lMrnHo<$C=u^&!j%pI8|zXNmj^9bQ6vK9!HV#f?%>$<gbnUTcdW zD#<HN@R_BliI}G7h>+3-ug&R)FU%J^2GN`!_o@{{amGrD)I;jDsB*{c<P)7w?*|R1 z1))DR$AfV?U6Jf=7m7d$Mle2o#yW1Dh{AhulJ?h!VOPozcf1w_t(N%cnem!Cca^ZY zfd~!XeMd61Atoa&w>m7re0;;mkVU6M6^$mF%2?R;@0S;KN<A^?Ro_uUEfpG%;z~UN zt6X*>1-?WnS<Me@a$1n?GkshRl7Kq))gjEImRO5XdFT4t%&Dp^#I&My0>6BtH{O8y z>s^}biGajo7DdhjkNPk9uMFdP$qc}H20FCC?kNyMitdN%<7ZVs00@?2A=Z0`pT8oT z%r*RTYuY$1R<A7pa{QGPNbRT&8bf3MPK8w?FAHLqU`bY%Vkam5Rr>~NuM-7o+^=NT z#@{W@Hw0g<>0GY)|DEQG@>DC1o(%9XEUHo*q+r(D-8U5$AMe5KFP$j|R#V{L_0<20 zqFc~Sb>7qITuW|#&>?f$xNCIFex{_RP12BGe<K&GZLC-IXt7`_cQed8cL+U|20)W+ z-On|Gde?`_%k{;u9fOfij<aDY*)HSKJRhqd)%y{~Ixd~b6y)-vdS>fAu_9l_Uw}LL z-EZa!QA3{D?>V4|0rfFvb)D&-k-vyaJYj|Bx*~PCK6^}!)}vJ~U5=FKkt8l2UQ`G^ z-i4m+jQ8%DE;coyzC8Pk)E2#PBXHavo##PO$j1_QHNb1ddot?XN$e_HSn*l`-{518 z+KYxE*@EHY<At0V_D-O9;8e}n`)Bb0_)dd)?}O$y!$P2jkQY3M2Wh@tE7_AzIx@qJ z)W#3|p6*w$o^KdGvR;l<@NH&((~3B5F^8!1Fsk5LmB+;|y#&E|jZ~(Faa``K{@Bh! zjqmwudPY(gTS`O(j~2k3Q}S`LboJc^<)X(eqj$}(g8(!F>Oq0q<Y^B3oo9pDkVw+s zES!$gm-seaoKkOh)~cDYxZx!4opKhHPD%36`J^K&-dD@obv!e*;-Zb3PpgD#+7e+m zy5LW1n$G|FEr4D98?MVYntJr`oD&67IKYpk5!Cm-{|buo7W#lNct>suX9kqGuf_JN zfNw9DT{G4@jky;JFTrO}!S|OMI?z53E5V(>rjbiDEJLK>6CoeN=nXLg%A06EG&zor z+ZY8<Xj;#DF!1Q~64)A<Uh3t$COCi@s~|7z;%n`;*{D1;_4XYg1npT!b7?dF*oQk; zinAYq8CT)0YpWPGSHLU!g<CpvGzG##)o2=pH)@`Ze&4}7?i+o&a^jY~oz%k4?}@;d z2EbQnDhf(WWnwSvjzkoL%<_c1Vd9oFAq?VLF1MgL6kw&E`t`GNhWpt=I>dJ@-pJ@M zT|o7L+pk`&{n*p;XKs>&+8JXhc2!QiM#JrJfq9^{`<ntapmY|VlR&^>niV(5ClIQ< z51<gL(rdp8>n)07UU>*ymBY-#@<N**>{;`S(o~)lS9LI6+1AY7J2iz;(D6#P$)I85 zVq>9FWm!dYiGNFv2CeaP*Y^oG0{ry>(yw4sT}7%6W$Wc<Dn4d16u|vLc;8>S*b?7Y zY9D((%+!NyLW|&iP+r_*oSDQapw86zJsT-J=_>T@s23H;a=v0HHO;LvcmDo}BL2)p zY8LNg|1!GpBO+sQ-JWoYs>WAH9z%Z^+cFLD79$+W8t3vy{$WH22B9MPj5jovMQ$&1 z)Q#~Zl7`>x&TLEIA=|{UHKvm(eeH6bD7uDMNRZ|f4+IG!fdH&qzSts<eHDiiU&*|( zLS@cxw-u52Y;Fu}y1UR=f3|HK?+abf?6@;Fz32LOE=G3h;CFV+YB1CTdkCa#QUKaq z`zt#J<dtq-232&pz8xgs^_P94qal^@u{_G<(t@)~LicmrW0iU37rH!94#vEDes0Sc zqq)U!R+9??x1gcM|6%Vf!=moK_fbVuLb?=CN<uoMLqJNpVdzF$x&)*IMCooxiGiUT zWF(}K?hfhhI(vMc=lgxmb)D<{&i~bU^LvL5!|d66ueI*A)_t$f4k|(QoI|Rr+Gy_# z=5BuwCRt>IyQsowX>+&J+c60_1ABRo%8tA>-42^Kgwe`rkFS#QjZ`=jmp{p9CBvpT ztae1tuQu(?PD20_gO)7Nd~2L-bOZM!+vO{rx4!ZHc&X^g?YR#Pt$8O`a~_*a?#Cm- z$zf!#`iQkF@=7lsVv>GSA3>KrO5%47GgfOl8m;!Zy?b!JW>L6d_{gX$Mjsy5Ik6e_ z)`wou5it4QIPsUi|8K{U{O&DcibK220%27y?d7YrXR!+`&(QduGXz4zXB>nvcd+OO zCzY~XTx!pKKM1eJN@8|h#KvJxUVdPlB~dWkjG%bE+;}=$-G}n3C{H1s@Z}}R=$2L` z+M{`ai{Dw67F@e`%l=f#6UD8_hJ&A52P&#*qil^rlap*b^~V?W0f*5Yu>PPgVWcM) zo8T5y3!fn<A(GG5&6yHOEy&yT-d4cX8SOwf;jRc_QI($ah+0_q>Islu`I<bB_|*b< zoLVh2yZ6ksuvg=)kkw_!O7!jQhq`AxM=;dIC>)L*z#Xf$p2%dR=-~{i50s>Vua<^N zh2pnFEgZ`gX}>2d_P!uzB*XB<)8S$8<ZB!o)uUx})`Mz|WSGF@zcQ7-)(LdPSOc9% z#>3TKjyIDP7E5Q}42ar9q@3HIm}?$mQPaG9GJy!T8YOy6p+qcPl13rYjQ8RJ5wX1A z?irf)k7__rbo}ON*A<grW;dtDP!3czo>~m{=nPr4yP_k^R9>Rr8Vn*KEErfVd}0v` zDKk;I^(;R>HMMzT{qS&<D4F=oT7C+F%pkx>K$$vp>dE4^e;1rGWeUxX@p2h-`qW%@ zWRrE#tVWzS0tm;xGGpt!mdXK;Bq_i#`7K3gITvMeOl+A2$6Du>doxLQ+RjUqF(GKK zj*s(Y#@u0Wd8nW$#PBs=jq67*3YoZT<8!Ng0lS71wC->p+q>(!)h?jd6vMf%mj)tl z5n~IqGE<rkT^DO+=ErmG(_M+mt?M8f>+0WiW-|}m*P({$q;cFk4#zohE8JUj*)((l zH{Z8cYx-|aQ+v{G>j{|ik#Vq2)@>IDmqH}QH_Wi#h@tx)WGOUT7yQx=@hBJ<6D(;u zj4aYA?`zHS(&DnpU_6(+Eyja_rcToFK^H%jEJPfQAjYcL+8kom8`ryal1i%wSFet( zVreE_*Q~u`z)G=_8OqF#_l2cq9;xM%kRJ1&0Hu*OEr~=PL2pdWL-ZH1h`cZWDjEJP z+B}w8jmboue|)B~dXCO*$Ah;T#yRNuy~RIh`Rm=+vg$ji95j@lAF6i~F=|n!`y8jf z8IhNB;nf#p*k;b!<+BalKlIUOq-r{AH}O&^A%V+3b5OVdB)N>kBY^KcpftZ@Bd<vY zR1`2O)G3angS-IgDV0rqu*!zSeZTEHGIfbDtNY$31|&k`IiFiWD<r7A6;YLh${fh^ z#L_%AEWB!jgFQQ*HbP^+^vIKWDz79it*w0tPfl0+HxvlwK#<NJ3A~1@=Mba{tOkF+ zU4)t2lu3_!Xj;4_tKI)#o;VJXhQu_QzhlO`y;uq|={?AzA+=Lz2!LYWn>SvKTjD4y zwf1WE#_~+t0d0C~$eD-<RI75Bap-%I+U4EIfWpYdZFkdFyFW%PxZ7~pQ=7%8DT1-q z9!_SLS^7M6W{^zC_UQMo*QowOMZLcRq=Kl;$cevcR~LV$G~gkEf~HpyP|)2s4GiDH zAReyn;ydiAv*fb>owy_N(D5Pa84yH)cu!+_Ul|ea<;DwGULO@=*NXBS?>3Xfv<(aL zby>d-8P5cLDFO6QnaPQGoUzO4sg-UGI^W;yR@`w49gbrB)n|;vr3o{{7J%K64ZtT~ z{kLkDNKHD!%}jA0y3%*TtatZSc!C^DwU2(dFTStTE?p#}4$~^|A0<Q@1FU0h!Wjy6 z{|{W#tziHKz*0C(yYP)?RER!ZDLw0g>(ikJ@_7kVKOsOhbAEd5P%;%?45&oSWvmme zhr<*n1L_Kq-(^0mRox-(^;&}@OeRJOQK;&<C{YcH)vd9H$kx043G!wE5CKGxe^|1q zR83I@(kBhTsMfA0=c?4dSGpCwy@vA3XQZGdnq=!3&fmG)oELHndo}1j9^Ome3<Nl! z^93*ci0$+%UB>h#vrT%hj+)M*vWu31!6tQNa9ut&Q+LaLMs52~+dFK8-+-FXh`46R zN<BKc-f38A)S*S+>(U?f2b`V}`q5D;-1OtZv^u1;-?sE9b9C*^=BfqWyLN>ndOTW% z*SNoora`9hwzV;QW=sZ+!H{O!SOKV^PqxdiY?8R+(6zbCuhy>-IBd<x{LX6R^g(Q) zTvXULON7MnX3`#lSUa$wp8z|Fd7&n1sa}{FCnN;1{I(MAxYCuM<yQs#=*VVe!(N@E zyQ#@Xm;BtgVxb%lQ>%Q_m~C1j0QVbDPY48e{0S%lMZAdiBjA5aLT?T~h5SIaFLIc7 z`ExmE&e>#<T#)iv)%_0l|7x#K!$(7>zQt<51<;R<mFi4T^Oc}cm>#y=H`Q&p)pb-< zg0E-bqi<*LZS|l3ooP4_Sp{M-ghIP-AWWO1b`i%UTo63ZD%&Z;qNFLTPtUF8`s*=i z9?Tddw;mo=Ot5=gh3D2F=)Tb(<=G8c!mqEF#qg^Zrz8QpreOei+iED+c|gpxUa`Ww zUpw~qVOYk}H2>DzhmV+ElF+EZ@zmA;5bSN68{-iPLaV#`$?hw1K)$I8AqGuj+^h$E zERDx)*<)2UEUbhJ&h2?A%0m~ns?|FXhf#<#hXBx<$Hz{oy8S8m#@ci={ND<`h+#`? zwk*{!HK;Y6q_u~drsvNEV%MNpbxkXb72qDwWT3Ed;;z(h@X__5Jekv-yH+p2?5zm1 z-oC@ufW#aZZnK`Z{aAT_nZKzC!u(Natpy#H--?AwM0TaWy5;Ur9w*_pjjG>mn(Dc8 z!uKprhu>xE+fA{m4BvgMPvU@sIh*ct6L_>cH&^($e7B2%|0f3b^!$W64UXXRc}JvQ zm}%!q!)O)WWITtV<4l95C=}0;3$4a-Ay<RzhMWX&iTY59aRf;brRX_)r8ikY@HT0( zeDBC^y5`rf4g{|!8}OiKj1;2>R!hh5;If+c6iUEb_*{1csIg~LW{c!59Qy^waEmsc zYW5LlPCIXouIq|u2Ook0C@mmrItVa0!-26&g@@lcT<hl^#d3aj!c?RK_p=MZ4IwfD zVvzgl-Zf(ssyBeMyd_8EFxU?ynophE`Beka2uM2?CqJmkZzpkUka;?qTPrI_Tp;Tf zd!Z}cw1(hq*AH&nf%*Y%j%jsR_opUmb@=FK)=-UCNh90X7VKg?hE_!vCa1i0Og}ty z-N4qVMicmOD&bcV<6|m-$^sD9q6%-Zd5zi~3=JoSscIox&RGyu&A#g;P`*4$I(ZxP zDh1}e70{X6AW+ckg-Gs=8<9BH7-LwPP#J$+zQ27qyc{nqU0*pOP0-buTJ2IwssG+F zp-63!kjG}_=!Zg6)ZFdG)a2OuL1^~PgflW}8u39~CD!ehx_)mT9Rjd#Kg#SGjVyxv za9lo+(c(RkyI9dUw_WTH^V<2oo~KI07_ZZ04I^j-d-xNtVfJdCqjF}486E)2JEdBS z=U2JMy@x-=XujiN88Q^<b6YOsw;3E%$NxYsfs~sif1^!*4<}w@;}7$sMAe{bt3NbO zK%Pd9@&bT;=|6z|-sN#*1KUdj*2P1+q7e3z&5c^xU92Qh<M3*9?JO@Wn6~Y!N(r^~ z*<*UoxH984=9Y4TXcyK|>@T5ztTt?l?>b@sRp+vWMKjxBVXV=`tM}%u^b>OsM0$U^ zGn1o`UQg+O7E9hJs<do;`v6~leB<T;GQ%dU>RD&YovtKKi>2^?NIj%ckrzfI!bRWn zFnSVttkYI|`=hOjYbCxYm|6S9F%>fI(_H?^+8HfaftKn&RmSHLF$jLUeR*vlRN+{N zuD*)QF=jB+3$qrcBHU2@`U$@KYViP)3YzxNUR$@3RkQc?@@C0BnN1#I7SZ{tDGzBN z!1Y%aM|702x>!^BxBQ70faEL&cHHB%x+5#(x<eXtpT8B3wv)hSyqs*;WdHv6yJs&f z33ng|?Ib1Wj7Jl?9sy7uOh3O!D#@;R{Tr6Iqa}iKE&dP11sx~7Nw1&J6Cp)D=)a=3 zy#ERjh()&I(l)ID5M~F=JV&WY%MoNB7jvS|fas)3-s8kyPtK4sB6Vq>@EvB*^1i$f zR3+Q#^R?f9^!?qiK_$gg?Qv`gX>cozMdTPjG;fcB{9~k(0W*%?;OWS=fgBMb$uZ|n znB?QNz$HV&k)1&*>o$9AG11`$+Zhjq%|<>uO^H{>!z#|K6xwLouZ>-`&mN_+^W-AB z5NaNtfNq9n&r@9cx#sf`s@_UX+ebYM%I)${n(|D$PBg<pz~L>jedtNzUTOZ1sY%dU z&lsvN2sgg4KDHE$1hKTiiogSaj9a{52^bI$g&2vie69E7!3>UtaZ(wD^ENX@DM)%* zZ5+P8z3jcjbGbP_MRRv;mBPE-!t)w&*rmi0IB%%L67|uP@h8yo)N{5<rPt-trQt9G z;6PFVHYmYR#uivB0BlJz{8Gs~?HM*_U0LvBYvtxNr?+!7bWe+hu3wtQ;htO(sE-f* zsp!o!cI>a?R4obz3*`1E<=w1$YTb+^E@{<ebsQ^``v^5MP)xa@XgA+nSXKJ{qI$yj z>ZlpWtLz6#|9ESE_GL_<7mervu=nY;b{QCS@ytAaTsOp<?5DILLwia3vx6#YjZEcw z&qd!^Wtgz{C3Damv-NQqd`IfNz+TrBmC4BDvHDLY>zM}AWMTY*Im79WrKcHgzbKVK z8w6<bBHe{9<i`1yzhK>0KVASL(`uvTcM<`aCw9W%PWIUe0~%GP0V7LP{c@B`nZt@S z!vshjskmpHqIsWF9k;bSZa$m4y`jyL@G?E%N}Lo(&^+AxfyFx;Z}SWFnC9@00R?c; zqyKy6#vZ{RfdwhpN~LAI-Q_reDz?2yJF>W1l`ywa8bSD|O#O01J5hFs+5WGkugvl- z)_G~XD@C~$aba&uIUjQiMH$~A&nkA?H|km(H8FL5`K7e)FIsXRZI(8uWbqg>98|Ns z_D^int#|oSr}<vhQ5(gHtifiA3sSS^D)ZqO@Z69wG`}YmOf6ahN=KdSBPpZW**WiI z7VK&7Q{C^BVSlRj(x9yF6rqhP#fS9uLH|2SAVG3S!74ggcy{W42w3`MsM^1BVY9Us zLo|i%jRA$=E6pJB*9W#Lj$sH1ZRY%%5mg0nHs<kg8h24zX7Rg(-vdC)<mse(^Omvd z=H`;}xP^85HE!ISw&hPpHGsJP@u^}52a0+zs}xpg;boHFJQT1bFhlj%392SH>lK(d z8;m<JTO0{b#}<B6QQuRA1HwClRcXj2tF&QZV`a)s586c%mks^-!F_Mdt(kg_T=`T> zG<$6#E*vE{_CeZ!E|-Jl&UW~_SbDX=<C|EyM7CDJJl($vSN~wzpdJ)$;|bcS{!*>V z4a-bPHLrCy+Zh`P!$R8JY`m&P#->|~B-t~z0kXRI2Vt?W<M?&Q<z)NlM*v986~?Xg z8p8F`?3#d?Bu3I8_vQ~`DtC-OouzuV^{O>k&0}@|(&!p&s)oNVpRXtG;Z#4#X976w z2WJ@?OwEL)O{c$Zm;0%^i@X-%c&c#4&HyewCZe{ymIwiD>a8X{!?|cDzDDsz$3TcQ z@#w}VXdohL^3Cxj%H8bzK5OLZmmYJBrqU0KhVwMW3pV|>i)TEGa?<0O^<RYE=+Ju( zd&bSQC~x(S>08VZxcUeLK)Gk#jK@1WBjqQp8mJLD=lg8-CkjYO!ewEx88<)`|Hjm? z@Oq-Cgvz=wMMuQLwYjgUdjZ5PoV(rIZpDF)*AJRZ0t$E5jt~Tigz>MszE|ft7C$_c zD;>mNXcq)rY!f9gUd7-pT3RhZURYndPg*|_6xvJiS^eb+7phh|1wA|w%%^1W88EeK zv!kicDB=KC!cn!A&@Z_&fO6@zZR7DMESBGGFN2u7(0owfu!qB8J!e~vhw7N~A=#<H z;t4zueZ)1Two9sOoZO$<{{cqS$@Ww&)}c5LB%RmWd-g2T5Q6&eN|OGcN>VR26n4C2 zM%$@Y{XocxRMB`j+|~7yqVvd(Cq+Cy3$)3AuKIk=_~6Rxp|k2h)oFBXwrFKV1%B0o zg^(KKt?|->@At5hoIp1`s?PZbx^DZIOL-v1Aad+^NY+r&T6!$^_%HEHSPwfk>`&j! zWn&luqK`?eUkmCZWs}$+9r+eRxt%Hsf~Ik$A`lR&2ri>)pAb~lZDNFe+-*EfdqgG# z7dzhZs-FgI3mRE0)aO)pOIkQi-Ot_(f2unN0z@WJr^B8um@YD^45?b7`WNZPu=nQ< zEo7vv_)OYWPO2QBa6ENjwbcG42NZV0-cR3s#Ai1iQT4od$|GC*{e!}<uxG@5y(zph zs4s`&e3>9+CUy7oe)1C=Zi31W6Z)LgF5$9~e$i90LNIEfn%ZH+J>9X7*NEu7nZ$U( zVJo50<7s1{$IYAArgq_qwBH-==fG?e*A=7ObymgoTL$X5gbTo4P$<wSF$4B-uj_)m zn0k8}E*PBTbH%pzYadAP;y)2E{9az4G|N^%QTyJL$icMFxd;l+d99<BqSRk%UNKz6 zX`*U#z!}r7->O%=HVc4)ToW`OHLQ&8GG~K@q8B~*=y*~V83tFk(+a^av!1S)FvO@B zasQI-R!TWbP`Q0=ywC1_yL6i<czRU#tJK7+C;*Cnt4aAo=yEb<D9@ZbU$f~ms;B$x z-)$%YHCc%6rmF%hVLsJR|7Voxt)@kvT@u&b)4Y5R=(`RXkR`5-azb7mF7lVTZ*nnQ zNzF|y*J{j0-;@4uXRL8^#KH0lecUUmTafrOE6Pw~X>@a2ja8q@DOWkYdHVTWo#oe~ zZm&sB8@DPQ`!`lkV}SdSqv^UQGk~s25`N{;fI~rXlh_gSt%GYF?ky{D1!1z*t+q|A zu<`WeC1{YD_V%>HJ2|*IouhGI2&ACt`rC#ZI#ZtE2Kx!KhsDlUAjGvlPw&Dy=|iEn zMS6LRjXnXCjAPj%f{9!lFnY{17Pme(P(7-2J>86bT-BhVY&8ew-mEP6vLu~xF(v&3 zWjQS%=nsFqxi#(^f<MEjU)=5{&%VB$hDfc`P7zkUecHU7#UhcMxm#BW`~<~O>ja>V zb!PQ97!9h7TPliecLpmC-(b*1!A5Q{e#6mORQq?=evD$^O|sN)IYh6{xCTjQNqmw0 zW7M8*K<sV@y>r%zr;wy68%HMeJ^}TzH~MowDNxN_jW!iw{fk;Hya;9>5gVU?TwH1E zN6BiOsQ5jCGs9t=bHkXSIzprRnP?UH!uT2K$DLi4x300Vp#m1}%Hv7x6oR*&M>Qk3 zT8ob#O@@<jGiN5CEc$^2l7mBv|8LncKFOcXcO>Ki(uv3yDflR$_dsUd&A+KhC$i_< zCp%Z*>-5}v6aqu8ycjn2dRc|(aQ%ZEIe-(i^{W|oYcrwq8v0(;;Q|NWLM~R5OQ1&{ zk^saeOrUSNRa&}-Qj?!pOjSW~4Re?R@q532DSlY}eBK;s0iak=PLc#DmRcDQ9y9#Z z8a<A)#X>$Dzt}o-aG?l~&r$Jg`YIM?E26W~rIu~hORU!hxg6+F00chGhtB9*(~BH2 z=xnq_EFQHpU3{ZQ>qD}`FY;2Nm<z)$zP|;<GwdH|0WP+M@gB{yi|2Q7jOf0yh+zR$ zY*kvK%Vy$*oApfGOCYjw%2%Wy=aCJE9S?kc?RT_*oaKTZtY>8IIa4-CR-6XWb*Z_+ zb1%u#1H$rfVb#2+MY+qJj#s97yGP?UfPjAh%7YL#sktHuM~4?s?=Wt9p`aQ>K8`LH zopF`&_k3`kQQ~{-b-fcmD|A;~hkQ`XlZZu6wDFSQ<qA;V$foY1;hV><6qgeiUIw{Z zPiyE2lx5n50C?Njv-qdRY$7pE49JV{U62rKY2aNn(a0t7(DY6)b95809<6hz-Zpe> zf$2pV@7!=-m_RDc8XFG!P*l<%n2$vkvNZn;c<nB9YnjrMR6QVu<@Q@XD7GB9n$w*r zmT;l#vKJjT?!989r`8j7x(^x!hBb|y(b=6ZfL^YPNIwtCr<^a!YgMgRbp~iD@nBfR z?`(D#xXG`~u~4ma+9mI^wogBv=Zl2TkW>_W=4C}wcnKdBl>UB@AotzjybYQS$#R6R zHYtY$)5kF2DHoNmH!~qwRlpS2R#=rzBJ=dP{L(CA72@=`^^K!)CCAZlV8sgF$<zYT zi3c^%!C9aiF1X3rezUmIZ)wOtb06yoF1DUr(vg1*LT-+62izL==w`Oum-dTd9ccIe z;Oix{#K0V6UlS6ByH1Fz@`2YK>oU=CkG*+{Cudj5?R>6pDk-P#xjG{ffC&Ay9?8-; zPsMEpNM-_N5)>pYO4dOxU*nx!wLO}zSFL)>`<P%D+>i%&K51UZf-2#lNehoU$Me_c zFctS8@~3kf9uJ`r41_)q9|pW}M0lq})z<gic6LAP<%V7?YpDBne17uu;1UxT;|aUi zlSl4z!#+S$>9<tTE#+0Zi0xe;NK)eV`)iQN(Qh_&Hae`8iwWu}1jZ(LRn4&}@vkHm zTOXlIOK=4?#<^({pS**`nC~uTp=DTkyhmZ4lceVsRZ%ZwFynj#`@!zDh+|GkPzO`N zwD=`NE}RYHL{S=liv4yHI^_<DLy2txdWPllej)B(K>6;#i29c=Fi8;!_fn*a0(3%Q z*Dbf$7wdrbT^&$&M!xETeXav~K4m7IbIP?2rnNU0o6AX7NMuo4q6_av^$qt4$zD|? zvl=$M{(X49rZ07SV_~$gX!&j*=#{uAQN11=e6O{cC>xnvA?(<28VCKOk@ioi@z@h- zao)I6G)Q{>=HB>~e)$N2*$bB+4hOJrc~6mzutB2wgAc<E;ObqzZ#3qrF+=kFy#WiL zL;<}z)<{w-d`Bp~ovBE=Rz?5G>m7EA$YV?Mm8!iB!t{k2gmkO_(b+cUB69G)%g(fg zizIGxQUG)K%!OgC!-@bce}({~1_lnX4p5sx9V=N}ZTnsJ7u(7}a@<|Vz}T5)-~NQg z<NSc2OKXRn;ZbNZm{7tF7mk*4CzrSeRi~a}!26V`b-2qwi`_0tSCl$-zN@mB=zh4G zkIGpOHTfn9=5QTTgh6M}3~RVtNn^x8n$*}LpUT&ZvLTAv*BBNMYofC@nF<u}eoK|* z=o6(_c%KcfC%Q$*(Z3eC*s^(5cp-)9mzjhVX;n&_ufX1~6WYczXz0yI<~?j2S}-_b z!QNa-0CXJ&-^)(*=3I`xPfhpLZJm8Speq07XWz&)jSKdE$uLF10Uy;o=}cJuxEA7q z4H+wRja#c+Z8Ece1Y%%{W3m)Q=#<Bk*^p=Igz}+;%*#dDOvr3oR=eKTty!A5k7e#n z=>kac^RV(OS@)~Lv7{}#+NP`&7imts(KJAc??f+YW+91rEMBjeNl>q;+Wc@)uf_cb zec{YRTXU`B3cFFiij}Gwq4vGz-R%ys&Ge)0l7pGnbm%f~tmf4d>X_rCtRs-zilN&n z&COQp(8khxoaQ)sYd=p9yBO^%cW66s=#7n+3R$%1XVVP41Zpv}^Tl^agLnHIDW+U^ z$_{uN%{XgoWh5Q7tVV4j;%AA?;HNY8<uOV!JoTwwoogxu8j;Utid|?+;s11pS_R*& zF)0_A*yDv&(*m7Kyv|^sEUr{|V1@lsy9|&nVqj{j|Ja}WQ`Z?ge<y>qP7xssGIy4L zxt5EG+otG=!+T3FKr`G(u=%7g#6{teN3TG*bSxdSTbxOM8ipEMACtAXSU(A$%ood8 zS>kPk()f{jw04DzXYs7@tjbJ3bNz2MGJmF~%SRfci$piIty_Jd9)RX9AM$p4m&HwQ zv3A`Bp+s&eX|S4Y!B#LVOjiCUt&ao|FGkN^{Gm$Z0}Nu6BG`tjJl<D<GaF~nbWhaP z`zSi7G6%-D$n>XGC#vP`*+5?hG05Jy^oO!EecOnonQwQcI9l^|?L0!LyV`fRdoKPx z9jG-NQLr{keP+n;sp)h=2T1Y_SWWu+C0Dw?%9~o1Molc=5IH6$8O<9_n+L7L+YPls z@1vPVb7`bK$IyBp29^5kgyqa@5ayq!(x2j>KD0I3BsHop6WdR7`Uo@T^6$o1*wx|_ zw_}pE^R~JzfXTY&Tl$m0)8=j@3e>>B9#h<L0qhf}?aAJ)CjxE}6;@+?0aS>(Wb$YB z-=%*NkW3UtmWQR`E<nnr=8qxxlu&~$3n-n^IXTQCT?R5l2AjO*8`8Z_c%N`F?opuX zFgKkUaR`I?uS$@L->$W5X`Y|&tMxRbvKiOC#(wF%p^9KK*71!-ahzaL1&;&W{{_m- zV74zo1E79$f9nIft@y96@7)Xjwa1z`_!{Yr@OL4%RShKuR>0-!VIGwgD>oi^3@6<t z-}1ZD!>sd~l#$qomz9yg($&`Y#o~0gaed&s3pW%Q3c4XoKdGZyY;Vh;5DYK~iJPpx zw7H_gp<i#2BU^B@s2q7waXW(S!3ahBP_b&hvU0Vp?o-*ZQRd$Pl|q^yk8q1bKRi^5 zc7drv>Rq08CUYCG;lVkwUtd@FM+`GtJEPwNol6s$?^d_ucdVe;9hpxnew5j`6x+&# zb6O6=#J=0gBy*Vcz>t0T$Xq@BX*W!Wggh$4g@m4g+MfT5`d!ZI`*ZVDNS*`j(KK@G z{08Sl9aceBsFW6(_eL<gOPBay{IXkGFmi)HlrdwlFTFu(+0J7es_8I?6?&?@c6(5n zz}3{P;;+wvq(1P|zPv`gdj=SV!;VIcycI6nlgd@r!pLa^Ug$LV21a*DFKGA5=6>X| zE4cyx_09Wb;Uw^1>IPrb(m#89juuT62;S>g(lI|Pug>>W4z~RdT{%w4n5L@&;!ExO zD-QeJOB>Dp{V5zHMNQP?y(QHSRGsPE+6umsug3a}fgoYfvO=|RzKdNj?J~rPX-He- z=gqnLFvu;}GUt=SCDYi(55KDZ=98wYdbVEC>M((lU$qVsZN76&<Kde(UvFN2K9k7X zc4})=Zq~DT+B4v?Kqh07fZPA}@9fBcqWd7$YWd5l(67#-OXwO#DkFo_iR+W#D52}Q zYKgt5S9W^tvXG2k`}c_}HvBGHafeP4;xWg&^`0~2_Yc;F>XGA?K({ZGah-|v(jdRe z`A~wU!S431>91pIJX=70si@BgTzV4pQ=#cfeU(|$WYGETm^$%TiPfOU=LXT#sY|lk zew2^)*BIVcdXniz?7`8rpxBF;Dx%}%OzTLaAh2J<nLn|Gvebh1a4p5L_>KLD+S8Q~ z+BDXMKWq^iDi100ROBerBILZ4|J3{u{YL0s1b;!{-F~yndU8yyt5dWmd`8*4Hz{?M zMV*8mLG=xzK%gg%5a?NLQd_q@kLLH#VKJ3?*e(dVb`*Brdx_RFgl>|J8Kx>0p5A!T zWUUp~4<4lHu5`te(dghaV#7X%puQptCtbp>r1UQ$tYX%z(ERB0EMeZ)v6Wx;yv%nl zVajzYihJ%>mCJ6%tPeE}PicG-s?A+{*vC&wD*CYOzHY}l&Zmv{e_=BF9$nWN>Q`6T zk2Pvm;*wXYrczGvKW9vPY(m=;P{J+`x+6uR*N0T}8>;o(S7PHz@s}U2aVh(iK9YR| zCQvn1AaC5>C(_Aa>}WE8d!Ib-{u`pP{69mqK<Em+c{KmS2d!Bf+^LVYoE1;Dl%FMg z&bLh~zhuQUY9nVVYFI^i>{Z!XH0khQ@r8Az$mjXSqrvVT#$PJ(`7c1i`01S(IkCD5 z;JFyLCc=izh!p-v*05q?*wO(=Bc`Ag%LGCMjnVlN9<%@hB)A_yCtYAf+0%XO7hfK; zO~1ct@s%1YGi8u`hF#2MF*tq^%xT<S5+)D<({B!1nY2nJJDJW@%YIF=(vv7t=Ttiz z91L_FPtF0m4abI*(H6`w5l3x~BVmB@;g-Lrl}*S%mUTa7wgv6XMX~RI>k~p)1<!nT z0kIJrm}Lw^H%{E!iW~1?coxmxKw~D1j2=4X@(_naXiGTQg6NBgb>ORde^wkBzoo>7 zLYfXb{K1ljy7+$F^^T$a5%ObCy0c!x3bP+ues~{B^=cVsJ8J9Xpn|Ob3m8|unNb-? z_gLo9DAY3-!i?d+K5tEgL&ertu35Ucp%boY?_!@ZR!f<_Zu!E>MqG{P5rZ!Pc=)mp zO4oeP6yY<V62*UTcL<ED)AY$p^C*brOe=w*Sh&LQwX>xRH%Y6lQG?B$iGvBy3D9gN z<ny`<m;#|KCNpuAK+uEZgms^cV7&b8FS0UkFX7=GLV~(@myXEuLn79(yJ9IPD`Plh zU80VFQK(amZM&vUh`Wt>Jy2mWB>O63>77lc!gnvfOYu{@;GlU@8N5kEWp?L8*uBP< zg7V{W(ox+J`{9PRqM3S%m3pQBC9xAn`Kg`r#xSbZARkcKb~$V3wh0|=i-#JWj~j11 zsp~$fbR(SDy$?i#4CB;VKg%>AjAT~_^_CVMYj&!`6(26XKx!24`UbKFOU_`b^X)c4 zlX2x&--)#WhWzK5MeQwr&=e3yFZA9HF48LRqY!B0AJ6JKu>9(GKVo=DE=6Dl_uV9w zXFhdkp<40VIn(pGTeq02Qn!5L+YdpzQSYlz`NMwF-Lw7r0+S(oR&Gzo!@K<BV0H_H zhL&+kz_{x0>7z9W$Q?Ti10)f`GD7uZ5pKOH5zAEd*AJoF!TplmSK>arCoRfMjM+x( zrZzchv>!CrJ0I>U_CH;#|Gm^Tn0B2cQkS{mg(~`$IhDB*Gicy<K84+SQ4zG5<05Bd z^M;dBC-o<j*x=<-Z7)DdG;GGoFc+2FD<eTXXP&_-{botfqHE^r1ml~Gk6Xt7V)>g$ zW1(iV{%4TUOSyj2cPELJ%adX839n(qT_4uwUhZPTc5cS68<G9w>OD98`%TwB#hS%W z4c5iO)M!8c#>-TVIrcf}Lz)g-^uEP&ZA8T(l}2f!c9SBnQJK!+!ME~Ve078WLUGba z&K^b4&k7Y8HE{loJwpUG6{>yh{&Mr~Y|kvO=jm_3vbB_cf&NAY4*r8g!TNHJ-S-_@ zoYme*HTDy`3U=gG7|s31@>;G?&a2Z}ntIt(t{I6)7*hpIEo8=hx48$)xPv;o(6wNa z9la5i+PJYQ%z5#}*J2$`G;O9J*=j1urX`=t$-N1XmU8YqR-^&Sx8T{1@_}MI`OTMl zW!qI+f#34EM>!|bS>Ik)$W?V2R-Hc;dSAl}XhbvL8r8JaIg+i*^5?Mfy!~OGSNmU4 zqkDebkeVxy_w6Qc$^0O|{%{AG{LWnz;X6pN(svB4(yX|&4GW`QEp^42^$H!U1M0JN zZS8KmSRpjcyI*R0vPtFOQ8&^iNk0>(=QBNSO@A0CZy`*<ZJmHA*<~?=Ho!keplQ2m zs%O6C_7S{czkSnJt;h*J;&daD&AU^RN}PzI9oqw1s0~l*%+F*r@?_NGu54=-E`CyL z12)5b$4=__>`?gWt<UYQrB9Wu>O9D+I<AgOjyXnc5`d__AED{E`5K^3r2oE@g}Lme zvkFx2Kb8cD^PmW4oFOmH+N)a{7FvoK$%Js|-nFPpisIyB;!W#WpJ2AiH5@C{u(Ml) zOSD*EEXu^>_^vp~`$;qH7loi35)qobd6~yvn_0QXiJSVnkunkt)cmHDCD>y9+Bc68 z+O=5V+|><o;T>(B%C#sP>Q95n;jk5*!1%y+P>JdxffdNPxWDrTioH`Nh~)?RE!Eu} z+{saW(H2m#ya*~FtJF1_VND%!_E_VLGp}<RJg2gjd!Ee~bBuGT{d-hSCdV%FF<q~c zW(_el{%2fw+EDd%1~Mh)WOF}n+6!p4jkX=6|BFh0;IFLP<X?8;&6QucCV5&ul`Yb@ zmW#&$84BTX&x~QvQ_6IE+gkYL1S><EV+Ho{fTm&L<JHZ?q8fNymoxL$(dXgDT`hXa zapGK6R~<`=PzSatJitcp&q9ss%XHNCaXD7=JO@9vrk)d4ZMfbJqnB`DyNs^odMg-( zmn-n{wM1z*+Fj4a=;tPrG_Jgds!T2PYh1VBwG5w9K$^pEF9XlVhFB(E>o=TVmdig1 zBPl<G!?=5cBgwdeMP;z9UKnk*&L_s}9YR+p9rLRU849_!Qfd!-bg_hHziUB{U8`s6 zT-fr`odP2FIJcUvy+4yFoHZa9bGs30Kt0#4mAsp_?F+WOJ-4hM>?H6kP^z7Yo)buq z7p(1_OzmO^>r@=HfD8Du;BgtT512IbU~KyRVXs+9<H=F@5UR$|`NPPvd%kEi*lRaY z%GD2vsjO#=qA_}Z1?`Yb^BUf@U=LmPpnY?v@J*9L(lo-9(P?^W;G@O5e4cnhRje$J zJgKB;EhRHY9p(8)lM{x%`c}7QxC-vM-J`JP$(ZE1_?WmQcNW3$5vGP0d}BXnY}UZN zP8B=l5i0k-W#^J?OwL7QJn55q>Kvf<Z_81x<0AK{(-ol4uw}>wK<j9d*)o9kaY%oJ zrGSND>dKP384wOSwG-`v%OVHgSQnOZLks-6S$uh#?GCh+jr{*(#MpG^NGRHeI-d2U z1JhgHZ_CL?t@_@+_2S&qsU6Dk#B<zdN^=+qlW~hvFA*?}j8q(Pwh?IjxldH_vT@AX zNNcntg2A6-HK;@*;p?rY(dhcrFsZ$IDr|&<-HchV$yUAS%i4Ujc7pf4O)>mX!pDxW z@$&2%Ls=oUdo0HsF?NmCrHMz;@tPsiHvJ=xy{?4t%r}sI9h*vzUotf7Z|IzWV(h+% zZe%W_+E1gvlvX}0!6O4~dH(e)JfyXE^DdSq<pC*TeBpvz!va@(5dkY^)`T7>TMI1W zjz1}aUMA?;{&>r82p2oddv)FU&7WrLdBC9Zkk*Anw>7QfhQRy%Hljw%@MOG$%)|ze z#Go^))X=864Nlv4Y@;q%f2bVQ>Lyd<y*?W9$CTRaAVk8Zd*)l?n$jjRUN9MvBh0ib zXfc=_L&*x!9yydF)KuXO!D!XXl+tLRR#LmY;peJRh1Y`Va~}Oe9|=XC3+p=eRti{^ z6w)$M?MUDfroTCQF5`<EHA&q(vxl1^osx_tGij8Ec>&|m$Q;Dr8viags^6rc1LO&7 z=&E=EjrU^J+@s&tK+2c5adPrcK5TTO5zf?@*l0B9Y}OPv&&1cLjZ*#i{NT~!2!p#S zaZJq@8=K!ZG8*-?Ln=+!T&|BVO-%0(f1yghMA62x_;|kuS2nMn1=skSw#y_z2Y3!H zp3BA44OansqsC)W?$6$MfJ$SgOi@6>HN#;DG=C?P1BKLHG~+cd@9=wHA@6dZs=&*C zUdWIZn)`aY?i`GaHY@fMLX<Cd>g}%89$ITnN`!pWy;Z$jPl!F>ogDZ028D8hSR=1^ z<pduEIdy+^b^-cJx(QHEn%8peXeuPWlvPgwjl;vadu3esE}Wd4Y41F!Zl8b|#K$u4 zcFMY$sc+?jmpICYMG3Wi>Rw5BvraY~?{vJlUtIQ#%iz-6tgv^Jc4wSt>m*#`mKv6E zF=dR&)~x5FyE$TTS(2y6PUfRrt#jQ@a>PbUs&>0ghIw_aF_)+@>qe^_^Md00iME}s z3rS6*xdoR8k}yvbLrMKe&uN@%{AT?u-)hVfCdR}4@U<~!Rt-HLtNH5FV*W&tdsXPb zWf|8tpLGR)NL-8-KAr6Dx57Twn7c91^Kvv%UI=BZd1k*SlqRkXQ3`XBKKxSsiuFgM zTb!FwH_U1c+EBJG;nnMV`AB*~D7xl2#60K;B+Yrolqh9Kd#G|PS3cSBnfH}D$Nt)j z6IGa6W9PxH-N{UEQRAv*%enFnKJa93-h!!=vWgWXwlytV)%uOtjC)e0SzP;|&q@|- zd^%xH=D765=cHty+zYrh%wiX4?S={8?YyW9xnc|Q;<Uc<#>w|QFl6o-@r90j<?bg2 zpT``1)9!Q4er{ZIZucB}44vZ|{G<E&F3Ef>)7*-iZr!WzP9+PvZtn)6plh?#&%f;7 zIOAU3H`l`~YMK0I&2P{za_uDp1OsmM@7-ri60szG2vIC&SfzccQ(alBXE|GC6I93g z1Pw|=%<{IY9sCn@rudTGcgKveUXfEUdy<PRlGrudsqVmX-pi3r%yf@2^V?XZdE2|O z{xVHw;${naS`+^z^ZAr$BlirP)cXu)HI!vzGIZx$_GHe>)dU`}+|69yL&8x|KNbyn zgV9kK8=k9>zL4H@dvi%#^d1+}E_~$GJ{V%{F*-^1Xm(~)L0is&cOhU&P^NgQE)Va- zv9pBo>~~gpFXx!fkiiJu`I~`)H@WPhS$5nyH>j=WUeULMxK#fjBJ2;swYb7y=oaT# zl^0(5#-v=&6>plb=`@-`gi`MPPRgl3Fn~VtOTnW8kq}UCOwL!yJCkWGpb~)x(rv!a zzw_c*2>nY%3d0C7d|X2@R6^yptiCv)AwL)K0Hess59~{#Sv5p41Jxvhk>B7-&y(Nr z9mE5)bcjv>)K>0|aJFe3wOG`Uqw1nKlM0zBQ%fV4rT)IAePKX${$j*qH4BCY_&OwM zhdAd6wNN=+F^6R#cNXK5!st^($`d;xks3`WdLB10yec^2DY;USLj886UOuJV*(mqG z3NF4mYSZ90X?ER&N79zXQ<3e-E`*kM`PxuT0yQN%u(yiz9F0seGOmlTW6q-~A!zBB zoq;dy=Rq$@bDxeA?kAY(8=Cet{m6n?NAVlg1O(~c=_j)zUeh33y{*oi)1%k;Z610+ ztv*T5a&gT$8kh*V$xmyPE*~&eBd#6wFJ`Lf9^-)Q-W|!WI+;oAZq)yMFnP9g*27pC z(N<<(T{ch8VCuNmcRGpJEf*Ts>&0Lx=;_q0`EsK@e)k$uzLsQ>R%5Vpr8P8dRo(eX zJA6l1hB?L7ns==)c{ik85dC;|c`K<nRjwO<n>+B@^g`BdaX_GCdvf?_ul4JURh`Lo zQ^Z@>vg)e?QkEw9r<2KQo~LhNs!nwdOj;G=JqJ69U;tARb1Hw5i_MgnUZ%?!OWIGh zQHSHtaT%5E<r$-`40b6xY{-1}^epba!kjq4jy`Wlr|#u6ac2fVxfDFouU>jbS!^`k zGd0xNjfrF8*gf47l59Br{m5sq4NYOI;Ph?3nx(S*Z<qy<M`2a`q+wV3ByXdK)9KXx zKzZ)5Gk3dO+C00Q{iTjeyhXKUK;<}~O{USFp!WKl7&PFT-u^jPHgWM(?JX&-Wo34c z@pBUz<i)F|&gZuQzW;>;-!K6rD9lP@$c#bJX2>LdfsDA|>B7_eeFbl})PHKT$C4LD ztmC>~p|mPxQ%hfsF1ePNL}gieRH>>!IPxzoQSmy=X++~JYcJ`{Yq5nhRG2x%g(xBf zeR)pf2cEsvOwSL1V!U?DRYi8Qi~A(7-kvC&GRJMdDlz+aZh=TMKFNUm!rnbviwcF@ z+6GyGIS?!nn*+&ym*@3XTsp}E5p{1Kbx}rpe2Wl<=Hr8g`hxWdWjRx({`ykZ+OQ=K zJPH}ZjU-Kq2!^}Je6>+ad?>By!YP+BZ?RrB_+VFYnc#MCG;l9zGA!p0BevlMC&^&Y zf43nR>uvyaKOfxzJc=7W`trlxNAQ}W1R`PhIL`En8_%!k>RnNQ<|5r7u7>yKio2`Y zP<c*LmsXmqv7y7$KRW;)<}4K@*FZvXa-i;T#twe<_YSz-g0J-Rd6>DIU3;nePnCRx zFHy1N%EtPWTK&ngu*twNEU6GJKgR$IvADB<0ba}ajMCb;^r%$Z+m;3ya=xI6T8cP> z;;Yf_!<~X$a~aOqN`c*W_%dHuzia?wV7`@6pl~rV_U~osuuXDoA0+!7mal>Xdp9EE zlM!!7`}bx)8$%=Vl<|TOFLQ^jrO1%}li`sIA!Apy=Q00$Lkt!&bO%lXUK4(T1N_Ys zqw1?^S(LJaAEmfPAz4x~I_J>%wHY<J{y0l|EBDzift}8z>4_*1G?lSdG+!N5@`27+ z(lHV88$_TH^~_kf_J>tcMDwIAG(PFjqEN^I$of<+e<DKYU%vD`IQK6Kx)ISY!9sHH z25^JdeAy|nPs2D`ZO>~H30d^4z0VdNmV==#L)mZehN|uKeUj71Lvm97Tug7UDeKGy z{Z^cU+6DPV$DBQSe->54LV?W!j$~1dZ21Aw`Evm3aVPI#SY6bUwI@ZAU5%ao{?*vN z)i@=OanC+?8F5D&(0+pFI)F36d#z)F`rkdu5JOJ7+B-fYkoG)w)_9>rc#yoci>Vnm zhZ4fXh1kAa`kyF>WlJF3G}tx?4K^HMa$jLmajh=$=Hmd%#XCsY!Kl7GLL4|{t;?(A zB-om7?)YL>BO%kr|8-eacf2&#sh1id>zuOZ4it!uVqRGwMx@|>ot1Cb^AE5}_RUI4 znLt=2WnrwS$Q>j#Y4V+?=D{-sNA20;_o=XfUq<V;Ycx=kZw04Fh0?m87!r|#L(r9j zO?L;_$Ndk|F@F|<u+ZR6IZ>`Mn(v+*Rwg=%_YuaCk938P^WQH{{u<l~4rFoIARY*K z7kuOo5o<XR|La2!)P`E``SKwF(|lomHCp?c;*Kv5E<kO-BuRJi4^(@>jZ}5qRymQT z{cHDQy-(F_&)w|QZD*9rrvF4%&-@zP|9)V`Ac`t@^w45feBz{gth!u`B-maDGQ=i) zdxV5&ps3`^vH^zJO8^;58Hd4qgwF>dAkiUK$N-hVTo4$(gnzrkwx`W<M>R75{J%JP zaevQx`Kp^u7Qq*K5~A04kj|+-2-Es7hM*$ZQyN^#B)zmS0?EXayrOgWuY%b1^c1h3 z;LbNDQqcLSVffZG+ZE(e($W3v#%duE&{{X1V|Di^+{$R%BE?Vu%Qe-sFnI#Lh%mA* zc)C<Fkau(dH7%H-SMIbv7(DHWhJ^hyP8dL5xsI@><&8yj3+GUx`y3(Qt3j}XH#r<f zQ{2ru2TdU{0U6-0Pyl0k@mzQDqwqS)_)Qd`-z9)2FT^ray;05;j6i;rXRPqy)NHFT z_z_{E@bnzFc)N(he{A{=5*v-8J$$s|Aq%%%^Jjv{$8(On<`{-neZOn45o;_$=}G49 z?@8q!10xt)D!>y6)!oYz^8so{Ct|J$vIYk!f&CV!C;-jJhRTR;I_GK>y(2CG{Qopc z>v1A)iHk1;_RiimaFHHGn0y!Ed7i$&5hW)?w)P2WN(Uos@k0e_6Eg(o2MwB2Q1)gS z^Cl@v+Q_j_PvzJiM?Cn~Zn|TC<1yUK+l8{8PJbV4S+Tz~jh6|4PV7^@M3__!ZUUE0 z5}@~m<mcz_L#x`^(vvESF@;Ex$p_q!aA81rTDFfvK>omc^Uvk}Ae@C*VeQ*}Tn=+R zo2;((Ht>aSP=IeDlQHrGb8x|S0*cOKIhT;+EJ5sQVv1LZh~s|o0<MPs=eTp8(b4{E zfgXtCR*Pg2@<_}V4~=R%>1ITRkFpoCmZ1YzP44$WcU4LcP*y&n;H0aRoAt7H$8$hv zgg8azyCQ{w(o7BvhBQuf_TDTle#ZXLw*a^{^VK7WV?z}V!?Nm#+#5q<nLEDn)`cHG zi;$ZmRW)b7iKfXl?@#0HQ2C8+frX#Mx=-YHXSY{Wk-E=;T9J7-*<nSdiQ=#N+#iFK z$)F5`vIsRyHyj}QGo3f8Nt2PlM+e95@*)a(1DZ&n2gn*q$i)7X&uRCEp2<J609ebU z2+gYve5;p>PlJFJInWN|%8_I>^YD9b+7~}Pc;LJ7XSG-N(dD-N#7ER##1JB}Q3LbI zC3sy$K7ayNE9OPL!4XmjwsI_qLO&mzwXFEVW=kr>IxP!{x)7W6w-3XXL#$IK{;}f( zs;14C;)4UhP*~;r&9D?;mDt$6lapE;;2Ae$64TJiN#Y+-S=23rNE3L$^(!LPVppTO z#9QF^a>x~#`(=wNVi$MF<xpNe{~(-4f!8OTk%BsYgA1|R{*@K<i>oi}L^#DNs6iZO zzVV58pyP;C#`G-rh^Ax=!bbm&{^XIu;%M4xt^GophC5AREB<(e^$p?JRdKG@k)9w3 z*P)J6hlzb9Evf_y0~aun-?doK1biP85#i=z1S8g7eBJLro8_q|cs>^L21-mfhvR-! zj@ildxhe@j5k?^VNbB5vj^!(W`|c9-Vb+bnOxM(oadbR_ZD061>QOjYAB3l>RYwT9 zYu`&n$f@(izi!@F6xAY_+IKw<r`ji6PU2t3aqJ>d$*q~#-$fy6I}x-<8!v}&B^lI< z=opj=nS{Dyv~jH|B6xjkSn{5#;d*Z>sq#F$i-PYjTr0?7Ma0Oi_3<(IcTCtFq}0Ju zT2qwe=nqr+pnLAw-2Xg%pNO4#@?K!J%=9@TCd`S2luV<<0KhP<8Lj`HGrZ$_h5PNT zVWFa+{g}gHdOUXZ>S@&rIcpScnIEaDh)enM;|JYYDf^h{oY8lQYGHxsIPU^#-#zlB zLn>n(24m#uO2*Tds-IobQ~CqbM_Re)Nztz#7-rT{wLcc|1*Z$VNsNqKqyoxw+WC|C z-<KeNlJC6NF&uOvEX(lGnv?vyXZc-Rc~u-=X}395C1)kof2|JN_Y+Z7L4&3(#)wDa zGfHcwp-)s(zyqk^4lwQk`c9&F=ms*@3ZrEuK`^JJj2{zn6doOSp}!LKLiICnQxLuv zCFYU?PsS#&h9MXaDG^6FBl-!gs0eu&GW?o)#Vnt*C+0*r0j0zKIoPJKO3^elxs1VC z?!Z;?tN+}sJ4pM_6$#+#UvIbk305Pu<C7=d_lr;*V$%T1Jo=1s7lHCrk>gnOZR41A zEr$(hx^rdX828J;7-^X~<-)4idDUXj9wUh*0w*?%_ja-(-(svd)#6vC_;n}y>{7K} zd#vOrh2jHb`t`(Gw@zgaS8wPTmr+g%d(H7rqx)dhDk8COl<3oh%Wk3oxiA5Yg(QL4 zvA9ZlW=vbb4i5W0%MUgaaEY6n`3iiRqGq{=IsSF`j&Z&L3U+}@eqJfKd$sPDD+DMy z-Fte?PJ6@bJ)Dp~Fu4(#zD3%qY%*G05|Sl8pY$1ftuXLID+DN+*~|DeL&D}fs7L8d z{Xh^?J>YP>@e4Ft$ZSK=k#Y5mbcn@)A*IqYaJ2;ri(^N8mx@L*%~!jOU4DzDs%)dK z4goMh-Ag&C2fX!?h65?U?{Bk3U;$(#HEL^ThOMDf^#r?Dx`ypu&G4&(a*^K~@Q`&O z_-KOqqY1?C&*QvfDw7`Ac&5Ql4*51B>vsKS`O`pUlvQ$B$OkdVh1G0B?QMr$>Ln5) zEIJ^Kob~8bxP?F#tG~o0ntD&6$P{<(V!tPk;724E<xA2i_{kpv#4o%DHwcsPHBkw* zhS;(NBf_w6+JMcJkGH-l!AC9tmW<Yyjrw25r>KLZ#$e6FY^MCLn(RMr=x^uvUw8hm z?C75#r6fU?#3y=&@RR>1@B90A@dLrF%QT?D`LCh;cX9u(AN=PJ_&On=IaR=`|K5-P zp2h#X9APB@@tM%faQ~Hk{?G6F?<f54m;c|B|KDr>k1P29YB~RF^Z)Dc{sG?qvy1;% zIQU<0|G%N`|J2$1xoH2t$9drqG;Ja#J)<~AT}yzf&El8<x@OT=!k>SR<XeNSS@(^L z8A3t!2SH&|4j_t&4epLZnd0a}1!@dKb<UfL!CeB`Uh0X*()kCkKU8UZ^WdTb(BmtL z{jI%f3P?74!RX$eC~BF8rpg?%HnfWoG<!RozpDiq1IPuXPgUOLwqp4@&}N)mmzmC< z)t=_L+&I{g>dN<rzN>$=p;HD^T8q{#fz{T?E_J`uVRj$j{G>l!@x{U7x-pdf94<{1 zF2_IguJ&W~m_?zd*!l!?GrqH_vCo#HmWKXw<E(AtqkN-Gzg)q!gYx|eo5|2}6oXfV zy53w*>w78diu~xkH`(R%d6KzoEP+a5_0~eRFp6|ZEmk!-<~~v_B9REOSQXq%;<hu> za_g^EXVPAN_C#Y<EMp@8dPv0h5}k-i;%Cy(He5Vb?2f${^$3kAz-{!YANX*Adf9vu zJa4dqqbTLcfDpslB-L&krSKF~GyBa$1b|*bRkfUwyQd4vTpVDshaGFijGJZaKi)@} z6h+mNp9a&q^B(e|tgwxjoYqh}!#l^rqC*dfnm)n!^`_$L{pa-(hC0o%;RN!1U*-fy zn-(kkKFYYvepIXZb|uz^7beruIoP>+p}J1nnsvZ?Y|?G;<1LBWv(0qo_dH~E%p1fc z=~WZCYkE`j7mDZB<4*3d1>)8<51va%+nR1(y>v6C4~cy<kJ7{XKIr3=ZWCJir`jwF zHmdI@Vwg5A_IS0MaeX-LX5ke_V){FRWSC^9#_ffRdaswUkaDOq%yl=_ndIuZMhs&L zDk?UWOMu{Xky@sA`n(cFYw6usWRNB{Y%v(rgW4!xnIN(XP#8dc%T;aG&Z5^a{*yS4 z4}WF&$v~~++NaWa>C2*~z6pO}KBS77K^!=uERRT)UQfx{3<4d@{d;j*5aB|!3hRkM zeG6fAt#|)`{}?h~%eNgd`p^ht6#V!P7il9{+ggvE+w8l(5ER#Hr&c5Jo;C>5`fM(< zm|t7FZSLCYHh<fkcAMUu_Tt7&#Uh)61q$uvE*(^^ZF50{;5X~=+69#yqJTq}qMotQ z+GCkYH6iwpSENA%CL8a$);WV|JC1(rj~mBZMU*^8((;t!nz}gKygw+yHz5{dsF|Fm zt-#f|$Mu7*94$A!UY%04S%jCjy|634>L&xTw1P0w80Pk5QXCU~x0dsICs&V%8j3ce zB9R2~1u4-6DtXv9^Ok1_Q>(#K?8q!Kwy4$Mh1hNb`QCJSD!Wfbzw%-W9ufx1fQ!|K z06fG#7|CP|PTGQs@9|V7QgO?RN^$D9ZJ;LpXO9%!k?EmNRopqnLdYZZkv8n&U?XS? z)8o)PeS&K{d55k)EMGy+_Ria%O*zfDRj}kafUDEslS=ecYgU#>@5WZ(<~V;a<p1L7 zz2m8V|NrrlWP}ikP<ApZds89VS#gYPagM!bvJ;Y(Enc=`pL6WPv9hy{y|XtV`*)q* z-_P%#^M_lvIM3&GJ+8-ff856%<=na%m6vD8M$A$tyMahCQ|U1ur~50Z?2^6GgD!f2 zX6spXTSrgB3iqq$-?sg5u#;HZ&)!{I(7acZ@4+8|-ad7?GHkXt8yrF;C3N;`N)5R$ z!sf#phC|iKqV@`%^4azY13W0dE@6?du0=gswNGo#+Itx62?9WF7%__1UJrsk${gO2 zd7vTbs-g%G*se$F5Qwd1fBF_}K5P=13(ye@J&_433q6>5Z7aTYU3@j(Yew8sBE#qU zv6YL=>w1)j%$vvrOt}9&9j*)+wkk6&bOPrid^&|D!^hkYG~>RinP9SN9;Qm@ex~1W z+X#v(I9jqYj4M<16Ov3(y)&cOogOg}QW`;=?VJQQr0-v!+P{M*lYgI+=ItgC@=QA< zg`hk;h3ZQ*FX0xW1|{~&ez3+CND6r!>6!4dM~<IZbq4gJX0pAN$ra9{Pyrhj43?nn zQvkI>85AqxB=hV=%lg6r!f-IwP|JjE=;RtsVzv8!s9)pKAM5J*{9;<D6+(o6M?vca z&szcjpU@HgbZY-{pZ`3n(5Y$)>?OJ#{jOAUu$T5_V?=OA*xd{nQPgyCjt(gX#QRV` zfw!T3U;n@PhWcB)rKzaLk3}d$W$xc8OVHP}5*(=guwM4}#WlQ<H!`MJtrClFnsUTc zmxp#?atk>C@I$U8Kq`O48;*UZM)Dd}4ELmqON-M5g~(7-fcGO8uc86o&u5m*fM9_C zpDnEq=0tp$u9dJ17?3KehQ#1e$6SM<m@({+ac{ztT|EjWtY@Dr>YS^Vm77bTVVk}~ zl=c4*f;f@@Cum0;31pur;4Q^F06)f*2}1P-7`@$ul;Q;k!I-q`y9`%=t!;m@34nG@ zwZ+<tOtl%z3Gk?8Gc`lqaxEg3sWlQrsIyFi;X_uvd&-k}=?s*4pC9tw;S{>wO3}uy z>V?}Y`$|o`L>wggl3ssy6H%S^#3#{_M|zh@lxBx_GPiX8Z`HA?tHOjy<#8EmOsm_& z1+lo<fQ1fm&i~;u4_WY*hNC+3W$-`AP*bTxjL{#S$T%myRx@)c|6HKq0<dxjo>zM{ z$eWlgF`RrCY4*%09tNibOZBw^nBK61@lA(I^KSn)94x!S_s-f}=m7w~SUEn5FpJXr z;9e+rDj>b(Z{@}$%HAI2_1xKj4oOA09)7KP{t%o)x4FW(!P~p@`emlUztSLx;}|6p zgQRNP<hTDutGFE?BY1~1(M?%!_@+gWj2Yq2w?*7YeUqX{B|HIcggzZ^{LxNHC~bDv z7>M~B{6Z2UBgObe`HkXfzr4b`PWbAM^<-}RhtXUK9sS6ke%xx>|CPm$Q<v9|{?zIr zd>9p`7yOk0_l8C9lyx^3ESBT2{8sgPCF>08Xu-$Y?AIsk;`K`7z(^>c7jJ3MEk(k! zLi(4Xrsg8Kgtjh^hW`I;)GWd{C2@AHoz#CdvL{1&7P9B!c|!>}wQk%m2hT<zU1rR* zD9WO<4CkE^EkXY5sZso`uvY+Hd_~!0W6PQ#pH>XUD_LzyJ*l3E$WNHIJNX27VJw9| z5*`Cai#jPj2$5Ruuh{vwRK?Qyo2)amThd<NCs6_I&krIh9Oe_Sa3{=r5;l(swgkra zxpEOSxgg-QE#L*$|9>9c45&t#b>T551<4Q*aU0eS=Ql9Xq&~9YG0FKVfY0?^CT;{P zluq3OEEN)~hQH{>-<1x3naM@y0-eQH*^Fm#b?JY3p#z+?jJEJ9-_I|Yma&i$#tDLD zYGLSW{;!(J$aJ^b)QkZHIT^?-Z>}s>y*%SqMaUX;#e-et1Q*@Mjm`n6KQ(Cwq_08< z1S0okC|NG>kP1&)A^hLDa4n#a{m!wQvbHW+aHtFcK0xt*)4#U9SEbmZnTo@wzFOf? z)*}A|0eezJmvhg!T!)9YTRkE8ug%pmRW=fGeJ6#+jek<d;B%>tiP`o^RGP0<{KPnc zA!a^Us9EVZy>SnvE9s*lF>jv^!a5`VfI)s{@dbFC1~#93a3$FK0eD)|#casrWIFq% z0*giB?vej{l3I6hPm+sRC^$goI-xb*B#3!d2HGsVBDiZOfYGkmWd+HMP<{O>2z{il z<btvtE%^_6uaxVtAE;y6cdQ5Mlh&pv0<WJGLXF4P8#H_9(E4n~jtK(kU<mqssk;FW zMBfF(fin7EYap(14XAj8v)<qbzKiIW6=12B_XuxQzSAK|;^GITw5u5NlNa~TSOF#C ze=Bu&I9q{y2#6q9bLMa}x_@`ak=GBmx4zpHQ+3@AfD-;(uQ15^^+m{E=r$2<V4@(? zPd}>o4k#}_1jE1R)(Bz%1wfq;k8*F+W&2%Ne$n$j_E~?y8UGZm&%mJOeq~Vw^J4-0 z3O2CyA*Mk%z6KCU17q*Xj2c{T^*pw!-&Bvsys!r}1Wzi7S?X}l@9mv1=SiMEPo;pn zeE+wFKEL{id=VoPdmEov<~_(kBf{!$;Fl0WNIoWWf_)y3kHP4qZU6dFrE~j!>B12h z8Gn||^d41hd!5TQ2!`I&Bg&5+{rY-sAuL2$<OcYvnFd6yzUl!{pPU*hf4ZlF-yfj~ z`$~)ZeUU78R*;a?1FXop0d6h+t2I)DzjS`yl3VyF^MUYP1U$SzSW)6PZZ*m<60yDu zJ6eculMI4;;wx)`A3?!*r`LD{eEb_$>fw$Roe?=p++5{9);q_tH-q4hi^$ao&B51# z$bToq$NfdzgsnAOG%NM-a!cP7|KG0-rpkn?&r>V!aV38Yf@8h15^i*u+REt)2N&JJ z0~(~~fQu<K(FLnnO1M0V(^TC<IbK<1h}ua}AZrE2zys%s2bl5s2sijBXJsdhqsHw; zZ9{Xwj{PX}2G5a6OJ*#&;&eAiMjrGW7D3@M|JFU2uuj@J_CfTtc$2`x#H|i3k?Rx0 z)JDyBOS6b@Gw0X8l2<Vu;f)VKvzGJ70k{*VfP|SR77IB2PIz6DV6*AQ8(H8i6*ux3 ztWfnw0DWFAWu`zbN3%VX%`65bma}M`x$@sDrwlvG#KZm5P!9a(;4Z?$qKuC_k<@7b z3B@hKd8RANsgw18t27fs^h+mympIphT15t<Q{!a|r$jJA4}a{E<2_^k8lFF5>ApMH z96+t>&8rIGMz$9S{RG!Pb!EVUI8dNBvAujpNrm6_87zj1Lf+^9#=mR<)#O|cp(=X5 z9xuQNST+3|^Xeh)9azXd?1Q01R^Z%RiORS`2`o^q)N8O{;?nn@bqDZ4C0t458~9{8 z^qH4GI8a8E@~8ZmKA8@r#q}4+R^2VgV&pf{)|z4^2q<LxSSIMN9SuHktOW}>a4*x- zrcJ_8rM`ZXnYu;2%4z-1_h5N6IY%imiEL{!*|IlT@OIgL&u8lPo^<|E0oX(<$>dCB z{#OA`Mru{Cc7k=Ewt-Wn@bmwBU)s~Gcaf|A>LG;BcD%x3V2E87_dS+y8%T77#xq!k z`MMs|6^_6X8BieE-!2g1<#4DgVz3~5`poS|gGp4A>*m;_F}^mkMeUqS)#N+*Qn&}F zy8=Esp2L*A9Q%_cN0n2a4Gr_Yv#uR=c_vvq`W-xnyoQ8pCyz3ttCqT_b2VE$*d7do z8s^m5+*PRVX3%=cpfKISd*ej+NhqgKDtJ&-;FZ595GUR|^9O(D<+^@vGCQ&2A@}f^ zH0^PB)&1oWVTEMl+<t)z)Z`$vW-UZ9TSI&|d?h65nY0QRgY%JLw#s^GmW9u@X&%_F zgg#sBQ7Ub;0E)~_gki#&A8^zYRA8tt9ruYPrNlU?t2JQ}eOme{C0Z~P=<8BCWAnb> z=g&Qx<pCs=T;0+caLgPX&c$wyj3`WB6fqfBS_a?ePI_vRl_cexoFr(Q09vKA0uTkH zW9;h+;D!iR(Zzq{hCxbDu;BMRN&FM#M+jyMHMFw&gFP|4$(qA@2nN7k_2!37xKu*N z-%7s*%z|DT2-HqT&mulwiR)+z_zm@rm76sPxoB2m^=(tYp{N7ZoBpH%vLIGV%{rni z0hL<_9Ai!m5sm#jHLE`rpWec2vwrgXD-SO?>YhJ*&x2$q=!L5ZJd66H;D?5uzW?wt zAO4ni%MjJmRk@#Dpy^)*qc!)2<$h=tG%)Mlc8Y$^IWYD&q8a`z2!15xR%0<(x*5`c z>{rlUWZv2ub(pX0+F!Y;r0Y&TYN&hc!`bI7ah7W7CnvNBP3q83dvuuM?C)Ny@F%ga z<~Y|q;6rk=;MzhQdjGj^J2$_*5nQjzy!^u`627)@C=8U~?6KYa#Id$X{GaP70sxl} z&uH~+5d6`@U=a{&``HuLTDN>qijMfwXqX~o+1)Gj-23)Ka}}@Sik>+EaAx{tWhBHD zb;%72*Y_}81f)-2$1W_y&U6B0n{t%wM54~+`0BAJDU;L`x6xzP3Ck3i3d&9}r$<$B z@bY)8+BumwTjPwEuX2XeMm#GMEAe$g;Oq%@+nW)M_m8diFGuT)8Mvl(8n1*WJ#MJA zCUSm6?A1&{wtbqE&pqEbtbaRD6`Zc`R5B5En|j6n_p+nsTaN3E<&vxPz|)jz0z0Nu zx|3$>gFg{1foAS2Q$tbww#C?P$+pIqbBzk=LdJdv(pNoL2-K8J+M2HeRqq}#?dH9l zWYuF9O)3bS-Q`%TuitQc4x|j<)<*4%Ui+tMJpfie!8_=a<&>0ACdO|9P$VOu^avbJ zIY(!N1ffhuOAN&;C++Hza6*zl+tcEkz0Yu``?r59@s}qATr_|MzV6U|Vs)8H3U<ak z*v@i9+dGBVkPcZoDF2r2`rp2Ocov)G-8#-uw7<Xc>b)i1@D+i-(msD}LF)n+{w3fr z*Yt$|-IWv?)XSm}b^CK!JDu(KNZ}a~5#Ba3^vYXxj$SCy-CFopm>(gaLYk{K5kuIn z#r({%OQ{)qeh^0&BC*?aBe1+6q%kR@W-T}6-0jV>ZXEk>V0vo{q6DWN?tu{4@4&}& z5M=#`i6q*r^oj2X;IeX=%v(r>1;{0KWeVr0m)y^`P${>Ve17o_<)+xJA75GMHvyVh zQo~W<4tXOGCiJqWK)*S7nk}e><$t_oVzM#U9N4=$kR5n;r*@;{{S`<U7R*a^dgJf_ z#UN>gDbWvx&t5#mo*xAKxE}SAd~66>H<H`z$S$tbu$QA$a$@`NtD$`wqGyeRF}*XV zoGf-t1X4^w5!j<BbNVNu6!?d%+^SjI)O#(pw>pd8F`ou{>6F9}w1pp?^yB7@&Ddz0 zxcRRZ-3cjI^=IWLn4zCM@dH1;#?sj4ZJj0|u+~#!D!dkC7CG-jK~W8*!5v9LVTx&= zUWQ0zX#JvVl=<{o(rtX;_LL)i_?+X-hr9pNLQqPIWN?d)beD%Ry%*X%D>9eu+V?4M zqj3Bv03CZG%W;8)*u!ewcNjyVIlV1>=yOT|bzrGHkuTrse|2We|KY7+%WqxuX}lhg z*$w5YeOzbT9a57{Ld|$}-+KDK>00G-vaJxYeB+|6mL=Eb0yq=n^v9CB&x|WA+>a*A zy|*T$mp*PYm0l`+dUv?`@`xV-J>4L1AIq@23{4XCRt-E5b6h*gJE5|vl194))a;)^ z5R*+G@P-kI8KZL&9P30=7r&^MQ?4#nGgeXK?aN_-$0f(y2Mwl(Y1VrU;J5bx&znSw z@(AAAUk}`;9AnT_u4BfoBUdMT`7Ea(Ke&^#&n4hbk2sLS_X2mTc;0;B15_Gtf<ULh zP}4%NcX+sdjguxo1T{^ZhEfiO&)0rsvm;%V*P9{=g(DZ=T=nT*6%ynxu{Qk-WgF2t z{_!deSp7L`Ps<-P$d*#D$g{@jQ3dHEp-B!UgJkT=-``6CNm1rIK)QM=zE!2^VDLHZ z&6Q;0r^g?bGh}{D5}))xdj>j#gpa^3a<LZX2m-jK-GKqoo0jm-a8E^l7qFJU%LWqU zkiT2FV~@T-*35k(p>%RQ-|I*L-&TCypyGzE&p05&@|(ZDWRKub;^$qtx;#dE$1XA* zRmQ^eK|)7?C2+<d`pg`#3hEm;@A97QalF!M3M?x-%#M~Kehacr;{HjW%!1(8KYww5 zBWT3-W7fj|)b?q5mHtlp@;QH0zsM}{-`Re&5V`cFk3+A?txXH$IG08}gGa)EYVIP` zLhHXQtHXoRM*h-<L!&?!aZe03_8TFDLnYlN-FNLUh?*USZv}psAX*Z}QNBsl->?98 z5p9uAbg5Iig7xImAF8Zpbg?8p&-=_b>xb*M?&6j<dn>C3Mtb`!@x-%>k7#8pp!J&u zP2F$wq7WoG@MwC3z#>HfuZ9o;-A9Wr-Lr3RDm*IN1ZLw3twdTi#1LbzwVZ3&ukP#P ziRdca@+|pXcu*V4==JmU7n`&orJBzW>ga_^)jdur13yC0)Bn(7xS3#QHgjcLf}KqD znfj3nw|d6iMe2RQ!0+=aEuM);t@Ltn{L*R5ZcNm9C8@~1o<-!a2r(*fwkg$wsu#08 zG8n4S3~f@Qa)>kDAb`&H|CzOWBHc6Pd7#p9JHa0{s<Q~T?4pg~joVfk7j>_j^MeW~ z699zO{`!2b7MQhm8@NxqlCsa<eKA(pH_6{8QYp*G`d(WB&24`HtXl1k&M%}d_oA1_ z%%VRyr2m6K-~8({U##%NXFfGF#MzodnEfJLnhX2TgXn{mo+LBY+hwXW-z)UmM~qhN zIMmXi!?|Uuv3|M`n4tgt)3%62kI60?mkFo3$!Mw5G37LU4xyN@643E3Qpn@83b#MY zDfR?K;sp&wXPI#&gZ}+F=1LPMEb6Ps7ne?Nl)-J_d(%{n^M&RiR-es=C#noF!HtwB zg4{Iz)T?~E?;%ufY+c_1d`Q>sAC<-}YR`|{EUjHFE^hZR-_uSohSzO>K2Rwl{{HoQ zr$`D9|KuyYh=%wWBfl-_<&JVHN=Y3?IU<OK;z^4}MvA-isr_$3v819h*UR&p%P}0r zyODpZV^D`W&+JE$kx5d{I`4n=p4ubELoP+~B~?+qIX!C&+(FXd8lxdMmMon>^laK~ zw8e7sZ#dCP)r0jW29!{dM|VaybCI6|6}zesU*mJkxxkOUPgxCfO%1m!4i^!l<ti#o z)X1NkM>({^{nmwbPQgi%R_RHn-7W|F8}=zgvFx`fj@AdJ&*wK|KP@`V%8uzv(LgAW z31_8np93ZuWFc!!7^Edjr!BBd`|keAf>geGR}`Lx+w2v?BuY5(YW@rUUjzxwJtE~i z<uuEgllN8krT13wuYj~PMX67mZSC6W-O}l&?%O_<efmXmA@>j05|;-!jBhV!dB52D z%z6BiiZ6K}!*|X51M~2quF;(jMEvXeefI(X-qF4JChhr5{eIUISyrGIt+8@Ed;?DZ z4KJGg^@LoYLpvNb7Di1uMckKeY^?l!b-Bak3~b8?Mr}A|F<5Xw-Pa>e+3xuxdA{2s z`Q&CE+dKOqnO;zTY=J<#pXkIBME^x{V;BG7bIG{tL~tME-PlB_F`A&LVAZg)$u>g; z82kaLEvmWeNg-+rH9G@H(K*A~_lo{JF(lL_G$|@>w5^CrE&jF*j@_ynNt((AYIfj^ zYieFor9ZX5)O``IQ8ApdHrNzwuDOO@c~QZ#y+Fa~0{P*=_K1y8i|P7dBI{@UwoDO> zPJ?rH0;?`ry5H#spoJ5*8FdGlosHA5vJrfa_($8kNoCrx6-*v;0aZ^laEnu45u<Zl zlf5DnN=d)Nx#TfYO(dOyzN(xd{54ZY`ZUn33!3vxuepA>@~_Qwi0`P9P54mA<2+cc zBhraWseE6og(_U?a`P#Gt+F0=F83yT7L6ee1=L()^#HbT*e!X8u}u$XFK8B=(97|T zERCt)aHd8!o-WrdciUfDW$!Is^h(tw3BmMFR{q?+cQAy1@F_1wbF!&+F<Ro6%hpsU zBm4C7+vWdasK;)As(6hBpab0xbIGMHdn7KWx|_=_6;oaJ2P{X7ittx3;`6q@{Rc!j zQW$4$;3rDsm6YWu<wcPO@yy4C-1hDB*>4kWUXwOHJR{H<!|Q)ODLeMLOdAuWY#EX; z4p(tIc>VCr*TU6G3e+-7lmtzIY4&Gu+j=ZcthF}Yc-7%e6-_XE^<8f@e6@Jevjf)C z=zY5?R1Y?Lwn}yI`@q#y7C|ByA(Z4jH(=GXV(h=-edTpwoHWbm>`;-J8vPJlWbxjF z<gu%!TD#5qMnC-HceZ79`6tiz8`3w=gMmqb!S%l+iJHEOQGHvug^^c*Q4>6Dp9ks- zB)q+Cv{%o5E`q>HlPHme-8qqZ7Fz<2R0mUs)BNnH=ZS`H9~^qfaNX=%AiA1_|MDau zfuLr@4pE*k7~EcC06TKJaeT=BxY(9_+zX%U-^$wn%6&VW@^)5w#566ANeS@L|I9jj z4&&7*D`ftH1-q;*f*$p^YLh+`@obprl@cE%@K8S8C_S#sq4t#ja?hml9dgnfqeH#= ziG=s50;8vSSH+|Mtt;$quTd{Xr@Z>K-~GGj!ScyFbFHNc5>*nG&k~pK<ML#^kl?i3 zFAq&7JuRlXpAb7D`~HuWaS}@2@9tfsw~DUwoK#>>Xtp$ehTk6CUy2)=CS;ZnL(nA_ zskfdCBG8gnLK{rfR-omFJ68`V33Y61-Gn+G)EfUMo0-9JWy<KiJS*!3v8(+t{^1P% zY0+G~CQek(AXo|qB?RFBl^mv_!JO_Mw$Uld^LK*Io46~BVcmbaiRjCW<6d+qn)<hO z2Mi@}Ypc@Mp_oC>GycR<lqbR>u`PbV5A)w+%b9xpp$cu1%9D2bzb09iy`_flb5$}v zSoS3^_P(;Wu&J+K`|-{0KA)Pzjvjx?qC!D5gZubYzRPlNsURS{9yW+o*-yS#H|XBy zh3MpAbPe1>ruUbg^!zSB-|OxA6$qXJ?a&Y3x_s6OsYG~L4%W=I?GQz<mg<I?&+V_Z zJ?ZGG>w2G-K!8jr-mJ-oufMT~)4Yhc@vMAH2obzt*640LjO{yyKU}?mND{VXh2#Z1 z9RX8(k_(H4yK2t;g@$6npal-dNpuOc5-JHMp*>A7jms?-Ugev%Z!@Bw;o`mE0h8N) zcl!R{wcLTPM0rAUrA77RD(xzJh4=*pT|c=mv5X%I`FNzL+^Q+g8oyt4>Wkb@7*k!Y zfKvr0QZtH9-vH9V*EFsUM)R&|2DRt-wB<wAc##yjLdz5KR`p%XI6$L@2;zFylO{Yn z<+9OW*=WVnQybBssE~|U6+^+WS#r)pGbu!m^EO6!<LKA)0(=kB$j^N*>dud?@Xu_- zxrd5dYjXo?c4G2+{_Aw*XB#zuqePE(Z+t4Vo8%Kovb1W(+nHYM{T)B_<tO$@#*$6T z7Y5-0l-8tKJY6N0u1KC&FWVtmPQn=rtqe)b81Xvp$!}P+RI21XESG2jMidP<1UD*D zMvtHyJmTNrL*9y8@7hpJ-g)aU4LvJD=qf-Ddjwz5qOb8r@y@hX*csI^pB*V8EjopK zeoWe}JAg3iQ}2ULj*O@D&@$OBDB1nztj%2OK+e31ir<t2T0zPJpR+CZnH}?RJ>W;F z0+JVokSmv`qv#}@*+;a|7TSH5A?YX?9A4u0?hNc#fOaLUml1+Pso4HOuRjiQ?Q21E zFZe#A6LkWDYRz!Uv8!Ytok@Zr$Jwi;#-q+63or5XOoxwskR`(x_mX}KkGnTE{#fbH z*g1ZgfAymQ<rJ{x-OXx5->&~Fp4!lGw$_!t)NouZ?=ltYQMmJJ>owy3RF1IwjOR+G zvAk=FqW{;ftjl{FAPbxSpqmf<Hxx|Lp3=jrVh;Bgm8kP3QQ1=W%T-2<O<8pGwAe+3 zXV<-xHV`$5+<Ke*h8FR^LnxpI6U&1N%$INa#g-;#sO+TUPOYD>l~R#R)1Ee13|cK8 z@#nm0;Yry~?)p~Ppmw)!sc=y_)w`j)#VDo6c!jNgRENgSmFag2+nMAOyS;>mimusz zo-6X}HLQ<kN|)<M0F9_>@&$Cq6LksXPU{y^S!Yu5nciyu6-pl;XK%cnrrIBse5_Ne z+TZ9h@2WCZ@Sqlb$gvDesMPdmyc<$<QOY%!Ko{&dK~oFAlUMPLmQaf>!1GD4mJL-t zsX7X#zi+zC3?1=S-Z8C-Vh|_TDhKjZ`beeK3*aftS2-U-Pq3pNtX*@c64SRP`P8ia z%foj7i{Q6m8YeL@DJq$^DS}fr2Bv#&i=z2@Ldfl%DUj<8(w_U~S88Yaf@-j7Ewf~O zwwF7+qKRfA(nto={)itig$ZXjaqpwe%;}=bIw-p365M?N?g4Pr#~2-N<Lb#H&q3d6 zN6Sj%^}Rx%<nO2#Gd2IZq#QY2^F-bjf!-*oZaVW-P*b(;saA*!QJ6?@oFN__vg{G) zmW=$;9*Z%vkPWD3So>P_yrS`@*RcWHJz?jT>a))F@u{ct7cZ>SbG;W;(aI?%30^zb z;s>IRgNLHJ%b)B|*;JB7{;}{9Lhl`&-6w+R8YA+?-H!uij^92xX!*VuNrSYJaN51` z@`i*s3>7~<*sc5gxc?TTu)c<-9W44d;1SGV*cB9Ou5{DXm!NI!!eOX`(QB1_XhCwR zBj}xtOYdz{g3VuiBC3dN^02D|OY5Ekx<#kA9md`#o<yWA6gxhvfWkg>Z0sF5(vyUi z9F+Jo<{V(b=b0biH8etdYBJ?N>nGj5Dt8ilJV+$oaPBtIm@)3z0dMFl#+ujzkMRif zl%xL~hpkzc*>m?1Qq(OA(VzT$4U0$!D}KXnValiW{hB<66(XYA)5;rh{bdl}5hF|K z{x=h~wxtwDUF#yiG>F;#U=6#Xw#bnL*`cHS?<&RDonIUp`>wsod8zM9Qe`g!b(>6e zN66t;<w_<);G*gFUHu312^PQ{Z5anWU=%VBM-?JpQJq%5S@{!?)66aYcP^x#H62{g zPkB^RMLLx>yw$jk_>7QiYk^y%=u205rOL-8Ktq+p<o{riw7mzZfj<e><O$Z&>nM&h zuW|3Bjn?=t)2wV$SQ@~|QzVazQN=VA|1}qAG5ofhWAZ;A@VPh~Z7u+zL-eE~A%-Qc z`+FVPJ#mj?FII<ZB&>gBIt-IWq5m!@MR$(PozHmhq&0ExzUSD%ccRE&c2b$)PiMtk zov%n+0>6Dr0GL0=UH`wS*QEJ@#`=7A8(8%2GrAW(XakqdVTPME^;d0Y{?g~aeS;)v zGMTDdu$E{^cd{tNBC>aC9FcgjW=}+8OvUR&Hf&d!7TUM=bJ`VaFrC46e=X_v@42(k z<f{u2?6G#;OISl_{rLCU^PP^MC&RhoM`ahi1RJFr85t55$u(9#sVtYf6jkizc!b7( zJ#hUg^&z7fF{7|aKhp|#pJB2{C{8V&Z_h?<GS+QcV<GM;$1cAc#Z+hfcFYb=Av<hg zk<{;QAJYxbZdME-*)>gyzN63P{d;#Qc5ZHI3(p%#)~2+OPfV`1Nb#2Zf}yO^J=+cZ zPN-!aTbM_hTE=63{YGD1v`S~IRx!AZJ^E28@n-B43%S}$OdN6^a}`W~t%udNxG6N= zbikHqV44nmpRS%0sfUokuY7AAYtAa#g)DpCU1?dVY4JEdAp>9y6JGq79o5DE5F6l* z;FZvg$xRIks@p=QpqVc7cI=m~0A_e_6Jp|jNN??+>t8_N?rLao`V3<cCPwDJV(Xd! zE(bIIS3D3MVH2Iv0&-a@i?bs`>FmqfL^lDsWYJ$-E~oBj!uD)dE9th@3I@18&)#k& z)>zv}Wp9Tzo-B9P2OiLcxpR=-Vx$nKl!2DZnO+&yHl_hMKFJlU*FmdR?JI8g2P7FB zZ!h+rYp&mTOSgVjo?x~VqaqzW@z&Vy#NulZK6-8a#f4_Aaq*;M2SemO@5+qA1{{?R z<_*!L31!p~2B$qP)95Qh`$)O&-I6-HX_UeXyqRW@&9*Vya^!P$dExsbSkevE5=u@U zMD<y1&vJ39@kRJ@%M033<BMCATwMP8TTQNBLIw7k7s14_Ma~<|B~@C--i%anL+w1W zaLlUv*Pfa^#j)%&??0<oUcOaBflB+n{TnrVBf7>n_jVAYMlt;cM0PkA#fJvbjFOTA z{Rz$Gb^In6`~zT_OppgU`eTCF-)u62ufl+6OUT1*6$Q$_8rpAAr6f`P%|+I>{SbSx z$LSxv{?!xR;@Mgian;EeDCz|*oqfE3H!Kea(CNlre2_dv!W=h%>mWY&mzlJj7aO|_ z$jQTgfWvj_3!NYrXH+A7N!_;C0kGQt>#KofNz2!oH}c*R%=Y8l7~aXS!>cAOf<J+d z>>cz=+aJyP`){4v0Vr6Q(t+K1oDec1eGGuzBmfLQ-4|b-3nV{cugsWv&m1w62B2}+ z*I;nDoqPc7G;!a~WB0u?$h08HWAy!_ku794wl*^X;>`m0-nZp>R5}tuB5TgJ>r`?v zSRT~WV7}kN`qWC~^v>#ip(i;{4n0PS$DMlxg=g<S45M==meAe)nK`8`JVlc_(>S}l zJa^$v9<`+^)h=lO`ywFm{3)~Kw>r%Hzk6r>-n~(kL;N2vGcHm0h4)qn%!~$^6%{J` zw{I|`Nxwx_*6dAQ_}HWum%MS5y1+OXSKAH^AR9)(7%dll)PB=_ZziKeP2z}c|G~+n zwR6)<phSy6!zX16X6P1rIsrK7taF?!zZ~NVls>-g`TS=`fKemX$Qc3dRR?23d?^_X z@z<9GXxx8ZuDSAoqbSv`JPLPZ0ABFt<$ug-kLUAxbZ7l`b~dj2Uc}R~Zv75>qc8o6 zbPVFY?-&XkGQ=ic9xP8XeWx{Mm}e?QoEA357e-4JY)qj7HgAkEZk+v=Q@bs0`aASU zYY@)ZXO-a-$M>A{R`TBiE$8mE<6C*9byHNx-0zNQoY3i!j7N72Hn*zz+Nh#WBRv;t zbeV&1ZVP(-Jv@%PsHuep^JwO(@(a)C%&JRY1hG)I(&`$y+deG$1&y7}a5e2JCiUKe z@t?!p1D_@uI(W%reF91Lt2M11gDMIM3dQh73cmNjmxy|XPg06C)iLi#Rdbw+iU(Vu zfJ{m%ChVFvc|Wa(>x<Oy;bw8y^*9&B6NcRau^#HgRz{tsB(4{(ezs5B^;<b}$^<1u z1`)QA95!3Dzk3t1pSfm{{Tj$tn9TnowNU4>M)@GrefW<5;mCYIn<43|S{iN4yl9_v z05qs@JgC=Sb*RGw_|V`7xbQIm7`xfg%&j<or2R)9!n6j5s@G;eoIFND?A@mh@=x~i z_AVbIVoNT~Ochcmg(B+8v?~RrPtH@~w%{cG$B=%f%c`K9*9MGk{@J9b->IS`p00UA z5{`GO&tfvu%u6FCwg>$o=$9;AYAe8JVSz*kCdo>Y7wzD>`)<Gv-76%d@Sxs3a4tfE z1bJ0`^^AXJ<Gtbva@>i^dG)$?Th>t4FTh}X5nDD~`#(dX*~X)%d9Zh5rzx`!%u8z5 zuimz^&98wk!Z^<Rw?q2U#14HCHFv6Obl*}3!sFTT`VNjMl?f%%GObgIrvU7U9|s8$ zW!k3f;mN<hsno`?0ZZls_NL-g?(JKu`)bU*zWxe_(yeeN7DWn5)jEt|I9xzh*ycr9 z!)rVV-$13+l%BOya%tt+XwI0#AZwFnnUSbx5{t~x-duZy<AjOlNc#?_Us!5urKZt7 z$eOILWeHWkGRM=<w%UTAEFn_FG3#lp<^}l!kzcc;(#KL2pSnP!qH1>5GfZxt@vOoC zIqfzX9J?D0QrgpkI_KB<h8q~%YDYnIG<?n_?y2Ceaz$)O>951Y9KplUaNp;N#pFDI zXzGb#WF6MlH=%(2O%}wyYuRCm@X7_%_6y4sJASJ^<uGP$C~Q-b&oQ!90;~AcOgE!q zrD~*MRo@I-V;(y;?Ua9|-v4xTZTxDlgGqso)1l=9UfTKqajl{Bmcr8G2l5>ictyuy z+>^nEjs6?+e-|B~{vEaMfG{lMNl_MJFf3n*H~Qud5;WK~maSnFM6nOG_;tn#k?@&k z*c@OA`V<_^JCW@AT_0}CE{x^7!%3NDLW1Eo5T)XL>5Y<xBmL}7It^ZM=yMNWZo8wE zCXLi5)iATbd{^+1Xt+DuLi|u&FSTav^=QeXv7qu9P<m%mQvKrSTW3e{6I;l{xkdat z7~Egc^%CK|+SCF|bpc8g1CO9A>6)c>R2nOEaCGsS2{H^N+iJ=C>}9aN$3=Zqdgbv8 zWnFmTHbYE&wKM2kl``D9+EIDsC%KEB>!Io?Y@mz`S+bpK8QFbslH%L*%gWg9p%y6# zMEYWZ{$Q7iPk@lt++(SaYud)A#dm%rITn=7QQ8*=tcD|wGlk*g=NtEh3kc32_T}ML zgchX)D@wjEu&z0Zwp7mzz5~B4YLt~Xm0_)`%CN=nmt%#qXR`#-ci#_uPs6X$P&Kh{ z*qEP7A2)9~qz}u<F6qwc(b&7Y_yC7B%j2+SKjrN}BW!V$RFz|3^ZaHF`C(ZGV||*A z5I*l!%l&}k13t9LjMs7Y+&gi~?k<UH?hh(paJ<{}HG|QwRtl<L_Z6eV6JcxR$?2<S zt?g-^HI=SA^E*m%^IGM2`*?3-Y2Dy=Q2AbFc#ivs)b=HRS|EfJkdkO4svHvgNX4t5 zr@G!Wo-4&w*9zN1=ooUGf@u`;4Ju0LXb@d5KDgT#N4IueyC!o@@s+GrxUuUukbC~< z4i{H_vGyz1FgYU5k@4AH+jHxX0z<o3jz1OW?pQ>2Z+(fPM6TaFZN9~HPw$ay8n@9; zVhkXIQSBE!eZ%oa{YUkmA6!MOB}4C{#WWCKv_%-)KdLfiyFKi3qnYsFX3@Jh=CK&L zwe|&Rr{FLw6FhlRNI0U$tz%+K6$y_Gc`<^hSN;1tcQp@q7`dxYzUX2$D6|-aiXcze zzJ|XSjD332t9?{ngGXeTM($|)Zn)5c0{Qtx+7nj=S>%p?iv9mVk)`-~ypmS}nnMV` zBzY8UnHa+4xgf`Hn}ZlCd#IV93%4D}szE6)y!E~pX1zruyjd#IeduleVQm@#wncn8 z+NrCk_?znfL_mo)R#10iiv&UoH(*C(3%^Iz3J+;oXrv6do^6#^-e1-GNGjgjRe!`6 zNL5_L1w;5wcrlG7iwM~EO$Bwk%{bCmmRhArcTFG8EwV!g&~;YRnunQHQ$95oU%;ch z@HyBzKCy5adJCxv9Tj5SK}hnjZ1^`B1=h!tI%T}GSp2Yw1a+3wVW-;c>S0Pn=IM&` zm1x1-L&EQ-9n{E>zz>7UJ2OS6TWKI-lHJ30zY7fZ24(It2Xv2HUGWkYksWW8)M2fk zG6BY@7JI-OD5@ZLQ#kzs8KJpFEoMJX3_0B5ww=Z9TgR~}Hq5d`Rm}cM$ZNEkMD5HG zwa|Fb1X>a}{xoJ54PV0W+Fjh6KbK>un>_(NHGf!tPf{giSE(P7UdERSgM2n!HcGd& zQ@P?h%CL~KGQM&1k@nrVHiew*Mk=0TyEJwIVOt$5J>`YhDsieS=J)q2``6EoH+}6& zo&6(f?Tf=I%6-L!{9r8?JS7>Y)f1%#QJOkh{H{#Ce;l7a6X(_<?Rd0@qUQOj#;l;1 zgy?IC&N_`~%_{e`q_p<iS4>0r=g?cY@~5f=JD+vCKn*=i-JW-63i%8R85;lC*fHV@ z@JQq}#NmIvz&pl(uiwemsXLqKIt$s9*Dqg_3}50xZ#1BN`x)ma{B<>aZi}$<vGds4 zaG$RCBvEsj5cFVN-bhajHa|;!ogjSC&gSQ6I9&P4(`zYS_Xdscncdud`h2we;h7&G z-`wzT<8YJoDqcv`D4seFd{y9e8QiAmg{=q1barm%zSI6*1i?uW84E-t<0N|!`>zF* z8D;(I$K!en+%Xb>+w~~m4PuOr_p-34WIcMVU>1ik?>4l&&_2{-bGF&3{iI1`6)n2_ z-MpElKQtJ=el{q+kr_xvT%1&R!M;(BOxu!=3op|?(ejz^^sN{y>7<<(YFGe`ZAT!o zBno%)a0Y)jn2^}l@5u*_F#Pc?>tGDm`6D2^1_%L&#G8;d{~;COn;l%J36=3`8(L(D zToI0?C%P1;NqHxPr&c~3+)Fv<116lDCMJ$(F*;^afB*6y{B`ELCFp$AsgPF%EqE50 zk$1ijy0fWQ->`t;541(FG@j5MI~=MKg?rq3e#wj)ExG7LLSXAVwwv;1A|*q_0luS< z4&iUdBPeOsdysLLmd5337BYuGj(f+>9ph6n!GjVtA6}btlaBBWS+W1tGpRbA+ZNxM zBZ%Je<1(?WP+kGWy4s>gosLg*`c}t-M)X>B1-eDLW`8zctx@1=4}TK-!F5wmcLzpZ zIRl0(#Z;a9ctLPIJSB<;Cbq;P<6Z@_UV9_{M7-iy&0~9{h8<qd!*9Q$o}`x<QA(U! zqNkPnwQIde@jzI$xZi@g8uitChqqY+V9@dU)5yTh^lEML?`r5`RAJ3!DOM<t(O5eB z4aMEJV>bMpN~V;G0`@aK(g@&}SW($e?%hJL$MjLx$R#)C+9OC}L7ANCL`V85>g0T5 z=M!s|rD>2#+s-!6UW&5Ab307`^3GLp#*fS;7;1)DT*LHhlEfy!OenTphMPXpVd1G> zL-xyHmP}s!H2<Y~x4TlDX_{2kR`KgJ&3nFI)?fAueLNh<T^gNI?ee=g^<-!)8nZr! zPu^!>gEFjeljM;MQ}9I4x;e;NytP7bUmKI9c8w!@Ad=bqt!7?TnG^{+V#}E?NkX#e z0y24kD|t>AzmsU>^|+cnDL(L_Ofm!NrMQIpGm{q!6-Ja%T@!NehLcOh9L3Ap909#S zDc>8#S)v{75-7Osy19}e!!=*{Hnxd>)0{$kVW3(>Qq!P_w@cTo6|Rw`BiSv`q`^&l zEF0_u0JEdn5n(JOOyb}_mfqU*reT1KCu{~zQg`I0_?|Uo_v3Fx_X^K6J#Zeo6e>tX zmXv8z<nws+edwUmOGa(OW0>xv;1>O%JR?Os&Xd!J{G4aSbqS(dK`pS|_w=foTk|i; zr}%+jrjLwAapCjpe(6R0O8uQyz4i?MLqiNy&zCS`ek34)t=w+8Sk$u2D!?_F<DDar z$cz{c%9|kYFpq9+&PVvT79&@p%{sh2y_xSU7aBUPfstu(R%Th7Td-YSfrS6XhH)?8 zFr}yq@G!Xn<J-SVv2x+Ylz5?Jz#p)vv@tnmN&CsY0XDA?LyvhmBBezmuuqqPeWh}k z232;H<WV&Njfkq)+AII0N-gXa74rg)w?h993OWBD6ao!`)8&);`|ng~UJ-P|yVsXS zjr|#>Mkyb`DmvMTX4l^6#;O@!*MFx)+u`>mQ~j~q)Q%6ma_oD6ogZe-zDj@qFng#; zKMx~!D7|V}G@B+K@Q%uhjjYV~>B4`PH*`7a*mk>U`^yeH3|b=+BBnut<Vy4NMap)Y zUFj~1K@^J)rR};OLiIS~ZyoQ0#B)~SjUlUx%-K%Tj7~of%CcAG8W_ShLAT9!=$)Q+ zae$}=&4zh11)oK@s7L{%gQ@Xo!Xf46tW&PeQ>STQh(tLNgjItLmVH!5f0(4@Oh|s3 z0aqH;T|IqbcW4Z&?8Oolk@Os~Uyovx<eGP9*;A;N=83JZIegy|5wrbR_ionNRv@{Q z@*QWJZ~7el--9=@79fDiba6Jcm5mq;+8N5f>`~tKMD?q+<7J&HWEDR@2GC1$MLX9< z-43?&V{Y}`JlA<|$VGcC(<Z;tJSJ<s?8m2kt64zFTs!1(C*KyIMw_*tuhKh}eNYH@ zYq?F0EJwSh7#Sdg?Vhyer2f8v7ylYJL$uKJhEsAQC-zf4my>4JU!KX8us_9Au6oS} z(@tbs{`~sr@~^Q2W?aKRf!!N&yyJg0nPF`f%|8yhak^;r6uliZ;5c!Hy*P0V&cU)! znH1f&y-hFFV`Qo-z9rzi+kETR)?Jn78k3j6MVAD#Dn1W-exmk;Kk2S=_5LT_#&q>4 z@Rmn{M4;we+3Y<vs=x$o21(;=c%^()q&G%KQ~X(X4AWqdzau&-NA%wC>h0EsD3u!k zI$}&>luA!*KeF3!w|Ipda(a`k+v7}h506kM501kqk#9yT_Davb3&H8vo$U^a?V=c@ z2l_+X-}aqvX;^Lv>X7c|JxhG_GJ`+E_Ymf=O>UKO_e<xhIhdrsut|?=Y{gi4E-V1f z!`d%*lfClWw`$vO!m35=oGCn)mBz$8y95qO^EMDXJd!VAwN`>hc_U3$9*dq8yJwv% zs?tU(iSp6Gpq9TFWdI#G#lH^Rvb|?~c>+Rk>sh+<dU@pA_*)dB;@d9`^8|<HQ5_E+ z2*{*mUT#Zod-wQAFXzowDSxo1dxSJZ0(<-U9d~bw{0x)J(8_w+H~rhqh+rDzn|BUJ zk2^e=G-SUu_hqX!n#=p1$~pMV0dS>qWE|4x6b$z&|D3VGTJrtR!EjSy;o;=((Ty{H zg(<RU8}1v@q2DvumG{XlMm7s0`YVixCOahgx0t>ZR+9awDs4EI$iC%SVDO`jf)Zw9 znc=FzzrB=4zUDr2e~nLZq_?t(D*|GtfOul9wc-eTr{#GQIervK-G<L;F@*stTBXX& zY;D?riHWU8liZE#$&RbbZSpkR>hTX%6WWS34JtmF9Ax<@gd0;EG34R=nai{i5|+Pt zjJUSlL5AES6Z1-NMNPY<yx!nBDWuX9Qy+&%&}qdQd(82VQ@RK-B7-@;Do@eHJ59ZU z3q_w$Dz?fF7e2><*r}lTa6f5@KV(Q!V5-O%Hy!fYFtVF0eh>uLP#RuktUZ|VPhx71 z8Y<Z#6L;XN>`h1+Yk7Z`;Vv!I-*8^*&mB`xJYJ`Y)wHiU2$+jY;IH{R70WVNnBj97 zd4K<}v+Nk>k798uz3crVdxQr?u5`ppBV_1Hdv`WMi<UH5o0Sig$<#xIEDIQCzYVoy z;^*Z~D>$D=f2pSeA`F5NBD>s!s$dMs6~b+v=GdOpC|Htj`r)HrC<F3%!t8=x`Qc|` zCUE|V{V5M}i)~Pa#oQn-Mm>Mjtzk16JUL6pDYe&1)nK8OH>MN_FB!K;luch6XE<L{ z9ARt)DuyLBAVq~0#{94P?j9SCmIz*c+uTm$+WX4AQj<W;wmRtU6KAbv!d7*DveKD8 zR?u)oMu?Wa>tCJ0To3gelP0^k$D3$=W-^~lP?TMq6bh%4C;Ex`RLnV$&>O0=8Y8E( zfidm#+2aNok|~W0;RNzuJf2irxuR{7bdRNOSB|Vpn=XfxbMsFZ8lJ5ewFJ6Y1ZXF` z%T7HtE^brQyWAY*D>oi#h{j;BK1WH4tC@a+H4gsCeTq-v+FyRN#+B(vx1e<o^J-p@ zsjw$r6Ne-gma03}T#Bi(Gm;%svwQn-MrrlIZv`t^7>aU3v65`fxf__hVE;V3F`t=( z<W@csG&VSRn6l%|7|RN_@-|CY+QyW2_FWyvnm+16j>_$#-CC5nqiV<<Rsn5&Y_NX@ zCE{pZTRLelgo;I_`n#BGq2$tv!bt`116?V<4WaF7D9N-=n38DcEqtyR88!&C;e5Zl z_xh~|U#lhrj5x06A?&<1%R7=mtS`s&b0Ut^JIaKELB!#bm98d$P?XgDxp3-aB_rdh zklRX{`)O?7l)xYnM6hND5b4NyMHj+Yz7!A2*Mu_pjlJf!nFcJV9hCOg9+*%+P-d3? z^Xny55Q;cEUJJ}2Jf*AIr}9aeY5PzUuyy__)zJ@5Z;?1QFiuHRfnBfFG9Ag^4ZKBu z6scU}80TfYl~?w1Okbf@(1|&amCiOMgmu%%dqeVI-}Z_~)p!l~)cq!U2Jb$uR(LnY zbrJPM3$Zo<$FFs}*<}9Ypz|sOJ+oCRGl1$B%SBslmZWf4R`E`YKpqLIRlM+?-%+(G zOGfM;4HntoCt`=69ccR#-u^T0>^QZ|yx#-TOe7jl?7n!O9G-d$QL}`HlaJezm)e)9 z6u$p(;q=&f;x+w!U?;_LT`Z9W>iS;5&Aq2OeA%u4bG$1K8vRr_n?9<uv%BMe-pOrT zJ|#K*-WXdYCX$;p9a@VQ+LmXBUm@l-o^eT&`VGOY7o)OV#l0Dcnx^+49|HqK|5C$G zi!Z>H@>}D2Rwc9Xy{;zH{)%>l|B2Q6eQyvUk#wLc&HEFQD_&A2IAlw<d+*O|iSd7P zI*wmX|0o@-#_|yi$m!8*2EO1sWJ?czTk~&N<-6M0ESNL)xJras;^U(Y+KNvxktFVK zyZ1kdF~*AOefWwX*uY>ZrutUP$^`3&jjCHl+CPBd$Qk?u<V#+>BrUE{Y70&*d6Q`G z8p5@+?i0@zR3;btf&_viME<#Mo>t8GdYIlKo@drIOV-9U_h*4!8`q{l=Dhx=fjRX! z?eJaK2eb(?><ifH&-y$`?Vni}m~FH@aZJ8GjLzOgHb+R+&aSUJleOD6I#Ttl50=tq zI0k%zH#dw{exlR*Vr+RuJ1Uuf?Y?}@Z9wXtC2=;mvAlJ>kw{*%ni{J@ZoigbzWt*k zUFn^mOqwudPggJCf}GE0*pu_dTU-SmjSGs9fswAtL%MYp)(T#3=*4m!Q}1*-o6ZLB z)y4f>rIl9^^1#|0ZoPZi;i{C=ZX#p{Djsq(dG~0y<z)}x-<zw+*`a1?Gep<^i=`AR zZn@rq!GcgNRP&(TwqV7_N8NC&<mKOAuSsm2C6&5_ryK%3LChZP*Uv4`4le2yiHtxB zU8Z0As{+#U(#y<m6R<4Ce~>K=4bswy)<%#5er=IBG^<2&HsTU9J8bt3W&DskD8fuS zB;^=a+a_?MQZ4Sa&l=gSk^0(J?O)dX-mcTD8!HSqCF6x5TE88oNJ(f2GfBj&0z%06 z(bg&|JG=>7U@Ye=femi6yFqZfXZ&qZq$Wh~NkW+{?;G`^gM)gvLx_FkuaccV-s|K1 z8dYF)297|fvmOJIZVl!(5IJ~@s*P<h!CHftyMl~_!-Ap*VZCk~*DQUUbZ7g?@KTBY zuzkDA0^lVQ>Ev|fFljl~9A+-M;OXfO7la?qk9?i_+p3WItp?ONFDdH}W*eA(2&&W` z<Sd*fsKW!Ug-1<EDe{;=;V^}8)X=js&({do3||nckRVLKH}Sc0fe_t+p*kcvgs8<| znVwPC!qqqTzcS(%hRfvWL$y<ssEh_x?hU??1B8}*<E2{p3w4u4A-ZFc-(O#Tnp^<V zh5g&3w{J)BPb;!PN1R=*E{@5w27jk^FnV*(dvm0f9X6hAALzb%%jzXvXfR(fhX2PX z!D9qj#5x5s2EABRK$3IV$>i+%AAHIZkd1ip9rY@lGOtjQ(|s)`F&ThEeY2yj&U&2) z)x}D?dYjQT2ronzj?oF%pDv%!%F^i;@QIu_p+vQJK7y(k(d&<Fm)v3^$4m7Xw(pUr zFQ$DknPzJb_IIisVn(v}#Oi^4!#S+6L(;&+HtyNCpw@;PYcc;ReT#imH=E}51)b50 z)>?!HV<onh;Xmua*q@~m0;y{<aJ+yA7(cpAPY|=>$Uz%pB^3<kdLz=9zW`r|WMFh_ zN}oA4KH%Ib?pp#EcFLcRo?@^MGTCbIO>8Nj)};1<L}ekqbZ$w~8QVLvV#hy9lIVpE z4!v=S5N)GZ{KwL#E)JLfQ`saK+|B=pn!O0S&u{n1Kav}2=yX}od<cyDOB&j3(h=C~ zz^W{_bk8-xgTAxFg*vo>v!hm7+=>$gk_&@{>nAxkSa=^gkk;Y!c1k5RY-N`SUvzJ8 z^Vv@Noy~6hM&V=0eJuxGGQI}WFct&ZTrpkYX&0*O=q&R<=Ifd9;^S^_yH9^UDXph` zMTN|obCVAhFG8Pfi?fhibLvd^Nz8oY#wzcc=<jeVYN}dm8{8LL8U!|c6SIqJY|1!w zSKO>^Ghbkdrktvdi^*(v*~Z_?G;WwftsmDrtRTA9^bdfDB@d-#c)|Znm&_oM39q0P z?nsN!$EyK_i`7P34_CN=cJJbNs_pjJ55FWP6pY5*WJ4Yon8E1!wc&yax#RmM+;jh= z);)hhvVxmw2}jw}opylCxt4PnxK1kAaVW8G)j3l63He{Gyp;9fVDK#)FWV_SUo4Xw zHOE?Rwat@70hz%XZAeNC6E>Xmt=>3l?}KPuXg!-#aox$4VH`4WDukyMc@HKmJWCmq zZ+Hk~S4n)0e=&z|#uA&f6BscIgg1#vAQ_&^AM9(UrHDrBZUH^hn=-E54rwE(xhzW( zNG8q#LGmjXE?L~w6C38uFC5uyXU80HDt-^D{5ZE1&W@Tboz?o_o3i6mu3)Hc)OGy2 zuv$RTd41UUd<g@q=IqYhK6Za_`V4BM37g(I*TNE0hdyB^i6CyF(&MLC4o0uFbhim7 z<n0>(aETX;^U<KcJ>1(KJ(X3|-Aqt@pYLR~&dQBF7^p3?&`fjZF_I5wqHf1^TRMqy zV0gruAEWKJVGvLXp_%U1XXw;Fyq&i}?8T<6VXVX32d{K6{;}YEadxzo;)EFnx~2s_ z&(e*o;(>J^u@&q~cHhq?&upssh9mj9vWileQ0e@NkkMKn%yE4R5?`DWrN2@_z_wSC zu5udd9Go@pF0X~Q{ww-l71r3_7dQ~NgeMF6y3_j|!4M-lepd*3_WB)t8M0Dt?fh9U z5%L@fUO=RdvtWS{YKdKMuw}b+3AR*87U1zL2cdx<(DsV=Kdk8W(LXbNH6dZ>*hby> zUHkg+q`fo_Jj`v7hh=3)iE$CSgXzxSq8Ioa^$hNhJ(lDal-)7G^g<&5kSi*o7jwz4 z0jjh>P7L#a3r1(OYaM8EV143zwS1%OnH5q%@O5c2M!W?wz<NMdm1W@R1=ObdXJ=D? zi~{4@PEms^U;ukR;bmB5+BoYW4t8At2&Lf5klT9~LbE>3&ZCCzCuifAMe-&^-m`i} z+hcG^T7>pwr)v_S>GW>!jC+Rg|3}qZM@89v@BfORfD(d$0uoXJ64FY8BHbaWbPe4t zr6MKL4I<6Z%uoZ;-Q7rc4<j+~Jv`6z{`}U@we%m?nz`>e=j?ON-q(Izd~7dZ<57eZ zArjE4kq;>CiObVTPeX)+LMmU&<&>_=RBa3Xf#&p#6|vWb-LdfAA6#8-?YC#JIx0Tn z4y6Z2^o7tm_jbdd4{|a6B?R-^r*NSjvM||<zMs~A8NW5+k>%R3KN~0|L>5Iyc3UDX z6>{8<&a}eAR!o+X_^r)c7d-A&TzOiX6A4)+=yj*?lnZ%)MsH$i4)P%Anek>{OoB}@ zBHNc#p5@R;BnaBoN(_@G@|S&Jm1=#vC+)tS{-K4t#}qhPxlNkgKL$do`{qo4{su;h zaz~14iH=+($qn$@%#3mzM-vKk`LjY|EtV%+PVERCBh1?tL6@s{Xl=z>FWw~y*qhPg z&U>S@0_JX1URw)mSxO=(f;I&sLN~KT1c9h&!#S+K7I-`0QrOYL+X&xn1ip(?(9ew+ z($LJWkY`cJj+?jFt}z6@k%TY&GLQ2CA$c3pkraXaGg{|nw;ek;MZQ;bbuyxa&c1K6 z=xmu!H?PAm#9OyhE;nen)lO0s6sJHp0u>~DH0GadDTKT3%2LQaoBeb(y`S8wVf)Sa z<I$Hu(<`s`uFhp|*yQ!PtL+RYMDAS%Q4sWJJXHFb?N80vCXtI_;krk#N5wa{Prbh_ zvHW)@BMztWlkMuo?Nhb_XYM+_&B`yzVX3dX3>#MQ40j0Jr?$F6Ex#B2wtH@4hCe0h zSstAHjt$|6q#lTn=L3dT=*|TPih<^>hAy@u7C>^3|4SSMF|r~k(0`5^?~Lp{Zn+Zm zGQVy2&f)Y=$Q(t@5QSqjAyGi^q9)O7<|Ed<II%lCgQ}I%{!4pp+0~EMTM%v8kMKw_ zXleZg9MGqY@l%54`&5W`rzyw1`|(A65keJi13QA(9_tKVT_)wdp>;b7?<YM+o)RRf zeBkx0Q2p7%F(WE1Xti!WV8PRJfq?&sF@BeDPBph<I0SFosADLMVLYivj?Xk;H|ZaZ zwa07udU&|RACFdn$P0||<PJm(_ub$`tzl1R1cvrXTRva(fD&l*t~K)t|GfK1{mF1> z+P!PH<hN?IWctog>xiExcli2nJ8%F?!x$rW&`!;CdDe~9d8+vtLrA1vN!(I}`ytUl z%L+$>7EOrC-2Qm17S#?;Pn=q1C(WH&p^Hr~eX0h)kyz#6FR<&e2Wg@9zKDnHPNQWP zsnn}|gNP3OGKLq$5B&THo^Xwx>}c=L&qG9C6gb(?eVbKHlg(0Z9@m2i+BKY+E%sR@ zn#R4<Ii0(!wZk}|aU-flj4nZ0%R`ActTuJo@Cv{0JaI_RDlHu|&Ey&Oamy)B-1H66 z*j@P5TBs>NtI}qk?vwMp{hRb$c^w}sj=_#<v!P*Ty4dIIn&#e8sIpA;#0U<RhU=Jk zrr-6I)P4daa0Wkd^3hnak%{D_?OG0*t(I5hT<va(%zQ1!7`LeJrTYSYzI(a2Eqo5< zpRfgNsPE730>3uIb=`Br(&pd(cfzOD|8!6@VG<L?NKMi~mpWm8hvhi#ZEmInv<?zU z7qZXQp*X9mc*DsguD>k!=p+_`X6?-PAzx16s^%>=`kjr)R@Ioj&T-MXx(sIqdUG%m zPi}9QX`Cvu+Cwo7w-5fbB(wcV3-bqHhc1QpgoSoE9;&GVWh!A=UE>{;PV(iyRMV80 zAY-k#u@|6C6K^<5THQz`7{p!ZdrL$fi!V#bPVHVqOE98*tG2aZxt$kxgx;Xaz#0gh z44goEgZbO5^Np-<%A}p@<%rvK3g6%hPPOlhrEtNK6B6)sQZ(_8-xwn#oT!SCI9G=N zy658IG3LSFX<b+kZ|5C-RY(&5Gc22`#BqDa%E?)zXx(G$Y@#=`1o32+sG<1&85PPt z82V+Wu29=2b|r!(<~pyW0ogsc;-R_z$P=|?sj1_ep>0@mW!AQO*Ikimn%Cg#U(qoE z-ArC3Ka?iN&Z9EVOt!TrWO~F!EYL&V@QOC!m4B|j$eQ>C&h!W?XGSLA-813Y57Fe2 zLm@R;Q0-JqmpoJ6>o}Q>N1FU@EkHkO;)#UlAlngfUB2ut{q%Ejt-vpZ$TI}>45F|y z^pnmbRH#e0VEpqP)O1@>)p_?Rry(mxy4IY#d2^8fh2dpT`c84<tQA)zbTbI5By#c1 z<4n}<!ZQ1)p2TErH>fQu{t`{Uznt?@iZ&RUc!?b(?3ua|B2Q6I-3kd{g($#(6`}Ok z`xxEZbpuZQ#7T+7D0%B0%yjIBXK><o3GVHi-i2}wMsnHBdpmW%ioZAH2S?@r-4-8q zgj|){1BW0HjMYCq{ne2Lg+f7{8|*Ef?&DdQxH^m+6Eu+@PwA8`tJ^n9%XH*SE-#M< zYRpXan(~u1i>b(G1Av#<)|w%zMJ{ma*fn{dPo=gv{`0|b@E%QqH70!3Q1>-P{u?Mv zeRZiyW3^A6{^2{7!ft9%h*71$Ek8q(De(i>(v}pbO_!#8Q7TxYwyjw7Ezz-ia6}hL zxMq2&#<LjW{_0N68C{8AXFGoue^xBBYgCNoP$VqaROI^s?=u6wlKQKN#`K6}g}tNg zD@)(UqD`U?fWsK#lyCX8VpllPmO#*6iQ7GUMIuRlq>I<!<+*KQi~<IR$5idfuw3{y zgT_)Ag<U-@EsoY?Z7&xZLx~(GO5z@uw<t<)`4`!fu>oOJigMV=7h<NX6}IHlAmGEZ z6}Vlz9B~o)gb(um9P=ySB()}q)u3iD`TKp~67g&=Ab5U2K`w&c12I+C|GCn{s-biM z1Ap&v9AX5ug<6QTVYGEzuDMWY9<QEyIeWx7GaywRCp!r&FVy|o9wiTG>e|G*O31!f z&%OFoH)|p1QSc(02)1T46NT4{zxD;OPO7@ImcwRNygqARCxu3Dq^S=?^+-5W;1&1D zqiW|h8H&DtR2MhM=gE|9@}zQ!PyAq>>f)_p^S8zc(QwXhe)xe!H}i*#+gu+D=+7(t zINaAW=hpK$jWex4e~zbeALf*E+N(a`?aozaqoyF@4y4TJ9WSKS0b^OodgC%wOl$sd z=f{BUxnW6(?HMwq5%j?~bdCw{@1a2RD4B}&_U8jojA4sLQweVgM>a_Y=It#QH@w{u zXJnXXcK(prI!5P(t<H0MLOT>{Q3YBv3u_OW@7o~J4!5^gb)$t=aK`S2yyksLAOBr( zvVf<B%B0Gu8Hgj76tFFgiyO|ZvdXZr6#kZ_DZ3Pj!0J!n^cLA~+~miRu-YlJWUP4; z)7%ky7OYrvz+zdu>8S#4kl+0jFV1ZT{}yP%D#cTFR~Wq5M{K}%b0e$CNq^TajUDiY z=(q^u-;RXl>l!vIMfbL8EfBq#c7s3d^|US_rA&AEQ{{ggu5lOo@iTR)mwn&fiq|Y1 z5U6(_E?L_p=BD|Lkup0L9$mW@D)bKf5M3;POR-8}Q%uSgc0CG$Zqq@+As_cm-c!zG zd1?8jluCY?HiLT~BH{Dg$2#@~{dXcC!y+wh+AN!5dtC`(X6^ZU#<Y+=r=A*)^D0dW z2>z^~AcwBgS}$*Is+*~DLE!0pTTzWpv;g0joN)E#M5<ovt$j3#*lEAznv8yxy+%jc zuy8w-wNt2)d+owuZ7X>Xx1Ur8)Q@YAuR8j^q<imC!))iK^_<sm{3j>chfa4wuMLa7 zOz>(xF~Oflp{`7EjH$hM5^1&)nZkKe<%Ec3f|+v!Bzh*~W(yL}y8?uQiyE&8N2;vj zX}+;qJy0|&qf;rYn}u3$;1Q%*X2_>WIpXc9+THY>tE7^wG|5wEvEj5mf)%6y^Wczu z8UBLaGh5=-O!50}GI(wWy~w39kf6PwK2TX*(sRHud1h6`P(bfst@jpc^z`2n?$t#f zsy9oC*W>pq^xrI^dbs&8e<79Owvp1LXBqmK-)sL|u;S`<;k}M7*DKd~4YTo-S68NN zY8A__$rl$jC#D=19lK3LOd!Ae;0@QKRdW0}JN}C4MG_|jq76k-u^%U6$otswsLPM{ zD9*G6NWY^O=Q>GXr+ahX7ZtkaiRXcc_hni~wv-YA^oQFdE%*wyQ+ljC^uPDs<2c@2 z8w1dOt}ur~beCuCfUX=hhYLzU!O$h%j%}vNg}YC`VCB~q+j(x^R&|)okoi&0+iAU! z@NxmGdBXnnp0GoiakZ%|EIr(wjRFkbQ*Q0~q+WFMeTBkO3mFFo{4aVQEUBtEx=v-g zoy+6krgOPPrZ!1_Rp+S1>DV)xEvGfGR#En$;lM9HEz5hlH^y#qq~r}D`GN2GW-i*O zhB$f7-1YY-(>~BFvo%AeVSwc^K5G=pc8S)zk~3vh_uMS5^H|fJBtJdhUR&L(b8U$O zZ6oO3!Z2`{id$DG9-3#N4Wpz8qY|Db<KM2`F4I~d8NXjrg;(n;j;a|){Gp+R?hKrp zkKl9+mH9;M@OsomuTSYoPS&x3GntGdHTZcRV7Nng&CX9YueyWdP#){4WbT5oiyE*O zSmE1qpqul`m;D+@j5TVnf#9<jpcP0>9DoCK5=dciXLo3Nc#XAY#$m=PMSOvLzmo*0 z36ql7a1}H+a-W3f@Xdk=In%^`MR$Jbgn?Hgv7$gz@~^9uSIAK{v{O>p5in^yzo@xf z2fUDoDw`iNI!9?GTvIenxs>PCO$v?KyE{3_J-+Y17x2s;s`B@hnva*dk9j&(p{fn^ zcWYE34`DgEzE`XKNiu3V=MVLntcek2VWE^m+W=``c$Yw@K|A2)Y=Trwbca-7{ZNaF zzeY!t6h_?=)t<EJut8{Cj4M{(#D#c=pAwd7gfK=P-HJOn8Vv6>2KU=KTTYLeta(EJ zZif(vK3NS-+C`#W7lxc537)Go6#VsXC&?LAwL8F=%%;KT(e{fvHTpBKhB|xnkQw>O z7vE)3YV4oamaXDBevKkc4h8X^gdi|X%)1W=ojOMoE;n;WaN8ibvT-AYkv}|k``WVw zHLmX0dqFxiC$~Hord_^3!?!&&Eo();NlxhY?9}wQ@)dSMS#ec@DwNKmHzzamN1l49 zZJ4lA%`}{ydL2jV?&GPx_Fdr|$u-PhoZPEl>3eh5_`&>VUf+NynuBVeZ|&RmKBsX9 zdgGn0;o>c)U;HC9_?eHEuzet#Sq|SOUM2HvuA51y`aJG(3PLAG(H`9}CG0Jg-}Ypg zkn<F(TtlQy!^R!8ToduwUNQW!<`d_pu$h|8u4}G1t&kaMcflJq<RIfy%+YnrkU&Yl zV7XINPd=Fusa+K)s5@fN<Cgqf-)FQQ>@WUWSam@uX(WQg>W4gXmBq!1bwNZ#(u`KI zpER2KpSgy<$63-G>+qZ}a~a{zZ;TZPlDJQ(qtgMDBehgwS@j4IYn(1PH-#eSeFx~) zmIoS7qDm0wADw<1Oey@pg^wZObNg(YFWW_-45}*(qAb;>MaRC81Ft<y)OqZ>VyD)^ z?ib@1r#kl<=1iz6P$w~Y6_UL;(0r*p=^VI_pzjMeCFHkas$cS-G51Cy-TIaX>L1PK zG-wp8T0I)-6MX^Epq}wnFJy~+;_&giE2E%G>g&5cG4f06f{M)NwwqftcDi0WW#)vy zCf!zHL$an>^DXZE4}lb^Pdy$}2%g@%26+)YR6M&AyE&rJbQW-M>K7XT^M<3zo?7oC z;4eQH#OP{;7D%UD*sy#^xv&jp*M0wqEX|okM5PHy|M+xz-CzW9ZaaD_PiVEPf6;fQ zTP~v>i{%#)?EGQ<wX97?=_*KEHy)3$@$P-Y<ZYVNFISh~bZhRuO93H#!J72qx|Bv9 zqeWfL-MmNb44iqALTx%o4uju@1e4a)xUy|yshyI#b)mr7qhP&Lp)Q+`yUMa!Pm_~& zzz}zDq57h~^qJ-qu4%fe#U}lRI!q!veXn+D^1Gd3;rpH*%K}2utDfcf?I<3Ny3Zqh zDDILI2KI+!Sgfb63sc2#edsj9zzptDf1z8oV7}pu4P3!J{o-NtWo`(*=fw6?WDCJ^ z01h5sZJm+E-?`ichOz;k&WPQJnn&k$!Xu6(Tn3pJxWO+3EhRIiao$Hl$$N(P9hMU` z*NlOS84A<{lW!EuN(N{*xKh%K0dQ>+e*eVc@4=tGI3p$3^!{|mZsxVKbqXf)u7(ng zkkNJJg-OK-<&+x%v<4S*i75*tmg(H*bc*S5y86$go#Wqk2rvUCN;@>$rflnWFBe|z zQhRP>nt>jfF<U9|M*Tkvucs_{?^<tj43vG#tt8=jxLH)ue}oznouGe&#cCuBNHu`; zB)pamI^F{NI-`bQrlJx_*Yqc`ixYQ4%(55<rndJ^LA~-pcmA=8fg4b{g9}*>aaImd z$>-pbg2wGJjgD+=A_2w*r!Y8%oMnfnoUj3j?H8@9;|PzXsrbN=^;Fj)VEcB6&~ErU zoqTmBH8=F3zOiR*f5$u0tC~Cw2#H`AR>x(_j0q)(?6t<rHNj&fd|^ti=`<*+MPdJQ zPHduDctxzvv2#1tg!{5l#br4i1TTrxaXm-yilkJ>cFNZ*ewV_J^%<QPlX+iSbVm4$ zbIU@4%Y+_-_DRe-2*|B!r(~e7qy|UyYDdoL0uhOsZ@bSX*yuvq(_d$Fm%%ZDX?|z% zE590IG*I4e?rdX(qgQm_I_V*DoerO`=f#{LCEK6B)bLsBh9}1ezv6c&+qKcgFzn(l z=E0Z90Y6UR?PM>*K|I5uKvvaK91tAuoFf%j+1)n|*Lx}U`TE<n$m7#|ekiak+psr} zgAVY-=>A&zPUD3+VX^~jC@H9J*r%?JS)ci%`z+TD46_pSd@lo&kL!u=fh(9V{nyfY z1&y~_)lW`WHpvB@nxeWN?~*IE1PV^At>!Rr5I3T2D!)D-alX$CnFFcKOC#^I)%dG& zCYU@IU(aLPw%SwghMCr!ZLW$m{D#XT))M*frD9qA+Me-7Js7x!xoRiMFSQ~XRGx*{ zCoY5J{nSnJ-bzxOz43a9d~d?0;!J8@l{w^2XyQqq-jGH1ecgeYmDk)Z=MVmH@9h{} zziIUo4NJFRZ`?aw{I*SkY^3(H-Sb>LT-0%I*0TRIkvcX1?qGBM14#=0eO+qz6U*b% z3+l6p7UJrFWTSeu&W7Z$39ubQ;W1N0>A%}4>{d{BhW`wf4{R4z*b?r7S$h&x?1k9@ z_*p<?^^wP)L3{A`rRO%9vD*8niz)FA0pt;l^{2m?U#RelcPj?wuT(3WLg)pLgm@v7 z2a8+8N<w>H-*6R60betQ*WYmmj^qAM{7Pn3B@P9Ro;o>d3s795;sVj)jN8Q9t12Bt z#NBr?QaKBqYzI{*$b~ee8IGLx9Z(=3*&i$Za{`Mk)j<1$><?dOJM!1#ln1-j80`AP zm?Uli4$nO+TAYPk0+*y8e{4CO0YG~nn-phVH;)Sw*4=8?Z}K*R#dFN{-@f-05b{NU zPSRvhNoLKfZN)cQ_xvf(@IW3(S@S~2in{{KxviYY`uTI};8Jr%9D^#o`T$_+oHx_B zERQ*P6)tn-H1lH1CuXlU8PX68mDPRrqtvFqPmNCl>1OktaGa#zm(_pirATvw&Hj{E zJ@w~XtbF%B1Q{=IYvw^XY6lKJ-R|+kh_jU%2$Asd@##<7q|b}zzJs2d83Y*CEc17? zmwUR#;k=g%L=^lpHksGs6>LhhOJubr%3ryn8G-$uG+<p`)Tsgx1u`-MsD&5H+yUVY z9rE~wRbj8VfTv%}1Lb_8*J;lL+B<$kFF)b8!?EI3S^Z35d*BpG1fz27q?JlLBheem z_j}fH-7m2G;21Q}ow>;+6qv7W6;eE<OaXl*Qa&;i_<}o}Su!tiYu{l^h7dEIcc>Vs zqVGSsLiKVFJjRK)ss6hZ+6RC_wghUnH>9d$0Iz%Ym3S<|;UUcG!8qRL8|b%W#d>nm zp3jKd+A|XC5Bo{>05mF1U-WXyF7m0c!$Lv(Q)`*kO8SLms^ZT~)YEaB+`cbwvM*<g zS?xG>1qjL|8Br6-0tcA4by~U0+LzKXnCdx{5^IXnWRAD@^LB|1L9}g2{U5~zST1&; zmQFuK;2gc>tU4Jmx%#PgH34X&HFyjGG2e$g3sjGosDx*o5@&gqX|&_)8jds1CWsa8 zu86Zf;PDf1FA3VnH`u~?hzOi=T1y%ksN)hqT)nn-mut4A27O+PN{MFd)kistZ4a-+ zhd%oIc0#0)lBA>ozldR0zyDM&M{w%bIm5b`AnP7-IH_J;$R@5T^!X`Y2bG7tM2b8< zs5b^-k$s`AjMw&P^N*UUF&G-xHYVZj49yiHaXeh4NR&xaBvqv}49)8=5Q*W<%hZlj zp%r4CfBGJmz&$vA<eRKrEmMxl7Xd3m*4UTl0CvK6^!Q_-$d4bf+72csF@JwLt*;G0 zCi5sy<31jj>aIdp3B8ay-6uO!71|BKM6lX4y-`h_c?r`pS|RMLV{qe3j*t5tb@=;k z--9t#H&@G)IPzGVT8I<V_wbLr$<;OSd8KYSKpLhn%0dJnvCrQbZpY-0^Z~+%)#$yl zKckJ5hMvw!C$m3f7K)OBpai0a^&aR{0&kOZw(&Q4oZLNXGZ)kz)Ah&VS(EA=>nY$i zlIa6=?c<oI*+?WaSl-_j^}*gpUaa^`O)Omf7P>SUQLP5`{%XIZ;z*GRVjIOIp}rS$ zxlP<FciWy%DbilpZmknMtrcI46_<#+*l*;p(FgiRUZ+u&cUXrj3WgPZNkZB{J^$^Z zLzJ(L!1mJRT~AD^=ayxYWvsZV3Yf4Qy<h{U(u=;0o;1Fuq-2_@*(vYo1sM%;J>P98 zKi!3$IHoJW&Xm(B+oJhyM>s7&2Tkw$sY&5<{iKp$3%Bf9!%b;s_32zz9d)Z6mN)e) z^#D0>!=6~HWN=pmk;<bQLn36EZ`vYD?2?c@@I7+_^3yhM8)P=_Nc}ZPADLOt;3#3b zGT9bVfwNXfRlTlGQ!XPlAeD}NIMqL^Gf;M+#Q*ow2x&O?8egcAkdzE;tTOrvh0nKl zN=D%wEm^)T*`n|T@%V}%gabZfJM(KG4<S0n7YkKJW!LTH=wlmhf2Wo7*3C?gtlRh0 zCW!=>H^wH$B#>5eu1w!IpU>ea@fyEK-f7VM_!=-CgJkUX-e4g*kcv@0GU7d$azSY? zdQlBC=I(daguTX@LYG2KcCGXc{}gM64ey6YH2R{$AcOe<JFz;4-N;d^WwLX-xiz(J zC|Y=LBv*+!RD2$^tKnl7b!ei-HK96nUl^u1U>%wYp5Gs<ry)q_Q%3D_G|?)ZUzB;k z{rbpRIBo0wyfIX3>bYSd#Qq3UkkJrRpPiHRG3P4fJha%{?b>Wz6k7BbKmo=?jhTq1 z9qLLa)S<KHZ%dd`oqyDTTLt=pr<moXWkI=sw$xtt@`ZsH?<O6O!#kTt$hvLD47;Zu zja6v;SH@8`T$D>R5WM@!8dBjyW<<@X&uxW<CB=WX6IY@J1U&be#h3qPKL9E3Zv}PR z-nOx^ar||A)#0yD<=cUIU^#!?oPZzJF$<h+D*|BzSl3r4$0iGhbB;ai#O!H)MGaTr zXz?Tr9-R8~7rA*4d=1CY+JrdKuHk8NX}}US!OXy@J1eO<aWUeIihJSsQ*j1KDQarh zjoB_<WH;i1`u<X9%_1X35e21rMPMl}h0ZD&nW6RV=^eziuhE`QwMuW^3WEiuL+8BN z;wN7jCogc&>b(;woNBNCQ!_+&?)z5uUVFvG#H=&??DV2NTgYXv^v)?~;5{}wj(fTu z=Cvo&x_v?Ej9BYGh3XCL+Z5K;yTYHM=hNP!FqJAP@QH5gT}ApO$;>+*^MUBblGa+# z%`GcHW2b*KZQ8;<V`sVY?e)xeXsHG@f4rVk@>$oh=f{=>MFE!lTNAqid2_2}E~8pm zXkC7)r9PplGRw6Pg%b4{iEni3OcTl9%oeTfnYSbvh4QKZ>(S$GYxbQNN%<CdGhDB& z_<XeN&KC0)@dtT$PENzp3f-w91u<3Bo@9+>dhXS%2WID+h-nz?{RkG9n2#`%hyc7j zU80)yYm1nJ;kz!wbJ_Fiwrm~i<f}G%tA+kF)<R>yRooL_!e<k|df7~?%&6bBdE$>O zv$-ekd=2_M$sTY{o*Ydv@+sKNG0;}Ur0Dy~Ppnwd&%G;LdWb7YLzZd9j(U`HQj<&0 z@Z)P%o(6B&4X_vwYwGGM@_b9z=|Y%*NnU;0FuAWcE*anERLm5l!59s<QKFX?#r1z8 zK1H90c0^K=zM8z8B3nq%-Lv9i{~Z2Ac4ev1PG%!r3K>aaH@uR+)?8?d&uFo!_kOYf z$HGteqMSyNY+z>Q@Erkn>T5nEiE}z0>^fg$?pqIs_2=D2FB8$Rc8(L+pbwlbLV&A@ zVp@TJ&SD1`61gNVA(5+d5<d4S47?AqMnDnQYl9+kKw!BSv?jVO_{0wlr~k=dkU)Cb zN&5H@_-(!t)RysSuNt>X`ok)D7Of-_HB3_ydmCTm@Auys2#|~qFx`TP+-NYmX_OVw zd=Df44T>|jxE_KgBA4RFvVq4|eiZbrG5o!GG%r&~WHxIB%QQP2Dvk5>HYE!7FAosK zbM)QEIXjf&$I*NGmf5jkj0h>L`1oQudkd|j!7?udgKMTeMQ-xfqWY0wc_EmY@k|ui z1t8!ulrneWKO)s5mE2k5ivR9%w9@4t&Xg*~HRcqfl$>Zofzqcjzy+=uCy4ORyGBW^ zSLxS0p1ZLLgVRv1hcwfdKWy}qbw@9r&$;l=Nnq<D-B<*JV1E{`gtv8K++W??PkT_@ zb|Y@WO<Ve$z_R#S_0c{^qc2q4Q939o>@U|~p}(k$AG!UCrPIWyUN_?4g5z>ej<?Cc zL-tKE%^`F?%2EBJ<V%3mF~|l;of72&&nOPe0)IdP)hU67yRf4ndq($lHSE}ie0$j7 zf3fLJVzWqCoe_YKcUe{@B~pV<2TLis#uZSAet%wFBL~7T%!51l0yJM?BZ`8F;4hnD zKqw-w%oh6fSHsWqZLfU<d<uXIc2_?Za&-ze1h`z%MddLj%--beJTB6c8-uD`DQ`p^ zJV%$)!-S;fwtx-a@RxroVfXLha=1|1wBz$@a8!R;p5_3KNK4Q$Q}VT}T55n;@{Mse z0Gq_OGQbt%?xyw?>YhCl%lfT!YVQH{-a6AlC)asI{82=mFn39S=vV#_Cfn$A-J*nL zY?+T)ZT{hsfV(d_EQjWB@8Av!yCB9kg)Kda3GhfygHplq-x2p@Zu0P-S|BTni@fx5 z!<TeEQN>GU0jAtgA1@Sc#N1N^MVj{>$uV+cKX614QkSURMG@*GS#UY)j;*Ut9t)2w ztrD}@>MtnOE)sEP_&`WoqGU5&d1@y0gxj}SX-(7)k-nHOdpKd>Q~K&-N$M`NiEmC$ z`0L;8ejpS3SX^_qw^(_1Es_#e@}yLLVKGH0O8BVt{@NUYD1JX1|HmhEB)$T(s6jQH zLrr~;w-qJ1mHUI4h!5!@X`H9&DB&a;ObzlxY+Vv110%PqT~hyBsvMPmjb{xWJDX%K zQv?MqPng0V)O$=FCqncMLto!!y4ox?gZCx#FWeN`ysul_k9$L}UlMIim+o-MC1+g^ zo@9O;0G20++!P;=gJqim>*Rjc1Pb`$3((}du`G@uO;H&%%2N11@o(M_TD3cJ#qom* zd{gv>9t0j&-xr7OKB@bH|8$9>b}rIx6}52f`}o1LjVw7M9oEl-=*_U9B&_Fjm3VV? zZm4)sr?xUrMiM0n9E!@(Ou*GRD;0On&yG7XznB>3^UPGE#Phb`Y(ozY_rB61{OMji z@yP%|-V0EU(aIccT|B?IExsV;zM=qZnbNzJCEXhqqPtgS+>sP1Vs$iaUb1SO(VWx9 z=Y_%cFb}pezayuUAgCAh1{4koJOFNci5{}MqTnuo{7aWa^|(YdC)*&$BQP6ia(Ir; zUzu)I=4{WWo!g$%L?(Gh|9h&$a6^W>CB8K7z<N++_31o-mv=Y7$+&^qytr|<AxLE8 zrJn$2W@JI<^@WLgb6B5_oxPfmdQ}MCNY1rUaUF6#NJgU;h+4O1xj8Opgs!(Jrje2Z zRC9fQ?(JH7j=RBg>l<319q-7MB$Ivc;ue5+XOQ$B+;Js{kSkN$Xh@DT6x=CoAuiy9 zOlGF3xCiayEf^#Sx}~j)FFqZp*ve0P|I?v4x!7{^j~oZ0Rq7aS7zuqnRp%B6BvQJ) zzx{hW--R3gG~W?8oiq+)=yg5?x2=~KyYYX89RVAH9FKi6<7yrxes-Q*xydHnqLZwT zdkXqLj&0QaF5r#mKJIyb4{zz}&f6ege~Q_iy}&r=uvbS?3n@-ecyWSPv~SA4QK!4m zLsSw@!9Nd_Ato!DLKC)x1`;}!mKedT7|~Fvh4bP(jm@B7=uJdU*_5c=_eS|@VUv-% zGN-wBvqqEPZ_U2mE;4R5IODM|^0xUtsk|V!7c}a9_yB&GAs!gU*iy?!s~aH?2h%=Q zlM(i`?8k(>M_ncDi=(kKqpx!&=3XEVV)HTpI{nV*lX~y4@Vm3h#Hj!T`g8_o)>%YW z9_kh#^i?}53-Q6g^5VoYN!@h7yh6d8RUXtn3kjPwvpIAZ0VXn$^UMHN15nh0rGYGI zm`zEX`=6b6x7x>iMbbTzPe=3B&@$-LNn0eE&^6!yjWQP2wft$L?Bk208;ym&(#?!p z3KDkPzEIq!L#1ZBCC%ILZNN841}cg!!ZzV=4)tcrkIoKSu07@rb7yqX<=TglilobS z!kzVR1cUcjWAb!N?)w+eWS|nx@0{CIWs(cS3ii(V0Hc>tC3clIYHc}EH0|IDK2bhp z#tPAxJ{t7wX3pj5VFY{uX%KJqn(0NShCUNe&qKnHM?D<;O2SC>?|Tc^S8f&P)1gA6 zaLQN1d68?M=^}-OZ(6;CR|WBv$u%E9IJ%t*&r{j(Knymd8LhOm{5j5Ex3i>_H)2=m zyZkPaYB#cC-o6D7$8;zt9aO^v4Iv{rYF38UlgkCHvQ?(?aJ*2h76a{;=81zZ-DQ#g zV*wzYH`kAAFB-}=m&(R|SWItuh@9;!D85Q6c6W7CTB+?@qo~bI+Ff^=rK2$1R=3mJ zO_595K@KladX3WN6stuqs<0jlA1^ib>F%RBo}*56?W|z)kibO&(8M6ZtAwq4X9q_R z%nQ-;llykFJJ7|3IiZ2(;ywA@lMOY6TK2cI-dV`Lf<P_o`o^u)jF*U91NXkpkrF(e zE|vag`gLz>FLw~r&mb<A>hr+v8>4D7<&{$($V}S#K3))L5l9JdO}sB;?4Z7}%9iLf zOHF_bYqCkwS464Wxdys5>l2L|Xi@2qYH|xS90%RA)ix;t1xY%Ecx?Uy)rAi(%c*>c zA6X-{oP;EclOuFHfN*9t-~TQa4r$zOW979WuW$1}+N6o*_ej^%8RK^qdr5_iDLp5E z5p3oe8LqYdWGF(!`x^64AY#q73bXjlOIyPI=x1*#ZBvZ)$#f_)sTrP-?uL2jisEru zN~Map&iWJS$H$K$$F27a^ti7ZuFkEKgBoKO9rLq2&n>&00<D$RgPtor0%4h@;>}a8 z>RRzTw~{<c%z5-|W9hmn3Wpud-KS4ZREo|A(i?wYo{YTlnqIJa^)_|}-87Ch!D(x! ztcSN3gm^!>-33d0T-ky#@_K(?*T8P}c7b<Qh;IMEty~1-K+Vx7vF9TG-q>o#QBsH2 z2EMP_eUxbxo5vx#ZoGCz!$33h*xNaHf8dv$rcJY^PmEO}<qVvd8+ku|cVU1V7k<P< zSTld%q92_+`XLNgg}9xkN6kkg<c0o2n~Uk`kE*IMr5c5g^xRh!`redo`B}MOA;UW& zE?&MTdIx%`_#;^CGW$tt_j4TOif;5r+%0pS(qXkAC;B+Vu;DN+0PmiM76Nq7Cx;jS zYT$4JXJ-CBoyw1MZlX+`$aj2m8k*W|vu+EBzPq)4XQnw*e%ENa9vXi+U_2zePyB&# z#dt)WdK<u7<p^I-S%c##@Q6zm`?pq7zTY4}^ATtc%)q%v?&}rMr!sy0fJ~hq9PGJ1 z9k=U<VOIF;h-wE7CK0X=;PqXn+-Lo8R1Uc87`VT3OpVt6jl0G}%Lmzh!ewGn!SMUI zOU+1^Un#_Kd+W7V;AyH4fhvnJAFUdY5W`Y|wgQbaWjLc))@qyh?QKgv2!BRV9Lb4& zf-ioKo|zcLxA?7pOO71#8J@x&(bswfvEOw=U!YKJ$EP~Le(R~;evW4ea;qw4K{QR+ zOR)l#wB0q`*P~z;Orpt2^f?#z%zMp7M0P}fN_M^Kf#alFVt?H{P+WX3o)x_kX!1U2 zx(JW*Lx0#=LFhKVDwf2eV3x(CJjJ(N-b#Mktl?1<O=iO1sax1OY5RP9p*GpP40V4_ z>$$5>CjrLTo8PCoC$=z*2`ds<!5jhD?Sf>+Uz0H`jh;ay*}-bb3|Vk=UH%NPxM<g| z;{xBT*^IwO<ptYBVzm=hEWd(c|1=aJt^q@Y=SFlWd;&ijFF0xbQ-9pg4Vo3Cn=KW} zhvAYU66)Gq3Ml`3R0oV>OvT@<_t~mSd0)JaMSa9k0)iPaz84V+Ox}HbCEl&@{ioR) zS`*CFGHWSP`5lEnNG{X-(CHr6e(5}QDOS9O6tB{ksQ~>e_Bs_MYAbU2tBW?F-vy<) zuv?{$h*A^U&em8b;n8#+?|Mh!mOUfiK6RAsAu%~UiT~%C`ymW)T3XD`OAOpx8s9*P z;I)53%s(g|G+UL(R2RU~H+=?bjla|yEL)np&R9~6<kC7Uc`=?%ojio;5XII2%Jgs7 zG3pg`q>#=0Md=xVayjHnF+AtGgE|cgSvEgXg*)tOJ`Mho=H4%@9>p&GfODAG?(+xo zJ)d;HSJSiJ>w$auW*1(&<%pYPS#LEp#l4ad99`6;hv0F_`V;H+-o(CPN2Q(8-8Hiw zh0oQw<<kQiKovFY(+ivuj)X^Ydh7#Gcc7+Da~WjkQj$q#{&f?wRUd$e?5>~)s4smC z$yaw7J0c@`SzWPc<1O5x^Gp)Z_f*neI)7knw+MM_dV=1}W#r%NI*M7;s6d%6hT+P} zAtySFYiXKFMD%`MovyNp1qu{T+)ADYP6KIU_r!AU+PHmx=MgOYWTj96BD|w%pm@V$ z^|CPeIzM~d{e6c{@N1`{ICix`3FPj*r_RdFM?X%z3v6PlHT;fcL%&nsxJ*5-%A|>3 z0E#glYv<+6e7{ES&18+_QVpdlO@`{zH7|@P%K|2!W!mkeh5b^EVK;;(S@qudg?E!> zf%D1}P4rjzy!Fm8;L*EWL+mi7@t7GiYCNP5JQwHd5}H^Ua4lv9+NL?A78JN!21G6( z$BTj2-PJlh>~%#oN8PNKx7QC8ic4?W29D~PY|K`8c{P;%lrct4(5!fjfJ*YR?=F>U zc78)E7Mb4nMGrm=@8TN|(&s$>|EfoT0)rvo$72@ClU=q*k)zIdmXo7;Dyw`aR(a6Z z-~u+k$N5{vC8p=8C4X=32OK*#8tXrTO?H3&JzGlbT{e!Hm@-*)9;o9ZK$`x%yJd3; zfe3oGkAredP)HMBs!_;X^SpCxBcn-A!w(jdBg)kgvz+oE;a=JL8i1;j@+GStDBg>G zsvtK7=lNAZl2f<0mJTYeX>w@18dolU(Ylh@h{_-8F}p9KU8^=HZ5q-%lG9*@ULUw- zpi2!)9*Qnl0s!unIaHw26#$9R(Uvm^9>GFz1NlAT*QV+5UbMbkG+Q%l)(8#FEwrXB zxv4yKmPhkp7NmX>f_;|c1??N&M>0<1+U5*2QE<)hFI7A%ERZol(3VN8<*dL6(RjkE zvN7OyRRJGvtFC+tNPZv*o>P#1{uKCvS$~gXbhD7~9G!%|DFW33BSI}jD}WX&SQMZZ zyixeUpN)9n_ynqNS`Bx-T2C2)aQ*U9$_MrxUBdef7sa~uMW+lOHNFzHf5xh0>tZUD z2!2*rLW=JW!6UX5x;h2gs-)yS+3Lv>J3Bkxra<C;2EopW>UXf_@h9^^OBB~<pWv82 zp29(zgMvnrA`J&)(07c>5YM=yOb{Kme%MV9yqWY|Xi;LQJZ1@u10|Rm2eoJO^F}49 zX-LgBD&*Kk&~X=jt=qQI>FgPN-lQ47@2AO+bOerJA%jYyZV>dXg;tHS@0sZSUyO;; zbG@lT?&@)?z&?=7vpz@3&1w7YhNF6omH2Dz47!iQoR89p<L@hIq(+Kf4C`tlMR7PN zC_b1@+M}y{_}7Z9?Y)HSW2=!>{PVnyOuL?zu{5q7yD(1H5`jTImzGq}G}wLtm#waC zvKyC`X1vU*UOKwdyNeRCY9JS8q)o%0bxzjrU244(DPlA4d!1G3W&snsr&O7zeb*ZL za<Ztbo2DdHCmS&!C2)O2K`i8->NZ`5Q=tf{SG5hLjZj9LJcxB0C^Pnxeoxgiu3xdv z`!&Oqb{Gp!B%BIu!>yEiD&ip-`}kR6nL&^Hdxs5R@EeWGIQnw<-6DJ;!g38q)1zx} zzVVvtD$IK#G@tEDorX@q#m3OW8(;U$EM;n)BTiD&PcX|aWB;(qaeIq_ZAo>M8M&UM z<LHqs-%~{4y!$fV!YA7SnN|wh+&EZ>!i;T8n&FC!MA{Q<;5G-&+w?Cc5<~OG;)Gt< zzMZt{S~0g?z{?L)J?mnItIM)M6i^o>zRLHMI&bW*M+(geDz0j+6Jq12HxTh76a3K) zQ-THy%f2N9KWR8n<|!u~zi<{ju8rq+##OG6psw9_tllU&T%D*_y%}*Ykf2lDKxIsn zh$&dzG{!AsWWbI~In8kgTWujr*F>eVwf7iQqLhliZQbDjH48{xo0M5q6RXtxewtpT zMOkCa*9VkDwYB1CzU+$C9LezQSAJh7BD~PPUi?<2B}bhtSF8rq!a~OHqafGbs8sk% zqE{WXj|%s5YORd=LH>TeH@cw5a4;jb7l0(Z0N3(@9vJ|RR*{|tAC->nMpUx^x+Nsa z@EX+TAZr>LK0hT1gj>^3GSKScySYtY0NPSC`?m&FBS4^X(5bN9zH$Fc0e*w~nj7U^ zQU^N~17@R?=dcOU5N$8i-mU_lt?dd7XLIHPz&cMFml+K3?oHvEc)0^*c|b_fktV(P zdx6$2&<~(H>X5T=Ry#pr69UDI1ad@AOG_o_77<CDc21+{$CUmV#RhlTS)hDNXPjhK zFNVUVyDgR+`2K)oBeKDz<1d{4*m*N2z8^+cqJBhQw9cGp^X}R0s@MR{?4jR&v;mAP z!q=Dg{qYo|gH7(pk#YG%99eR!B^o%q30ul-?8ze}ZObK+lA253(R;0HSi5aoTRyXC zCuIl(b_i2PEf)@Rprs8YS+FK4ENQ%Av`|a_e%@Q2_!iLtq2x!XyNXdP*d8DD&Qw<7 z-H+x+F{UwWA4dW@>HAD^zDW}!bAF%Q9;q{MOQ&~tgE`P!#6H%M7@g)>a3dO%rDqk? zRNolRE;RR}N*OKID{4gMm6L4i0f|am!M(E^Z)536s|yy-3I09vkGI%3i-7RHmyC{u z0xgo~_nlq(JIl=X=C2Rwy!Q(?_i#Q;2!3dk;j^ep2TiB#R4|Le!zxkqrjj^$fm-=O z1I=)W;1QoEXrQ{6JH}KEgw27=2N9Z*K#uZ7NN~#Msv!E$Ho4$aK1hOHM|2;zKDfFH z2P`xJZsSmjL;I5``123QHXDJnmFT{`x_w)MV^}QRv#G}nuNC<cRl;1SEtx6+;E%pa zZ5l%#1aBz=3zAV(J!tnrqm9rvVm;~Dj&TW0e(G$Hh6`~7r?Vv4Vefq)sFHc#_r8DS za}iwX_^V9+it+67N(DRKiD(PKWQ%lt0{aI4Dudn5+FASuf<2f0&6O#qaXlN;g(ewy zql6_>u70x->-;t5onL$PBf?K3QHbv^Qf`F4P9ov_mb1)z;>!lt=<1=+;-IWAJ2O0n zwAxc0-u(SSN2B+fR-?Y`?n8q{`2@xzGzGmcH{<)f?M%ZIp9&@b3$6Z>jd5Pu2;skm zmv4^y?@@aceu;$V62XoZYeBL{X#JBlfAR|pI<x2x>@KjgM^<&}krsTx-6cOpz*%o) zeC*aWsCptyUDlG4rpf*IBqrEhqPSCWHF($v1TBvq9rqMX<=7<i<p8xxj$uAk)I25^ zbKn-;vJwA&cEVORwIVBMO7)42y?jo@8g^**>n026d>r>EI{Aodu4g;)3dnsQex=t$ z#c_(5G@gi(8j1vqu;&OgkDCIQ(}rp_DObFV4Z42cH!}i>5C+BAL8tk8k1Mq7yE}R{ zzhc=%SF;?qpJ=_43nbZmkfCB(Aemcvq7?WVAm5LlJcmNMq0TEq%`@(P6*lEm^&u>} zBK@m{3Mc+szIr#Fq0IL<j#XTTr4_^sbUWNDeEDY}6-B~TpVh`QKGhSjuFxUu&&Rwe zf$Ud+0=M9-hFNnCS;s$=yuk}7E_NYN3{uf~aa&V!&d76*sk=COrtW{IwtEVw3%iuM zf~pd8ppFVjTw|yemWS30^Kz@|F!|#iUIU<$+k$r*UybIE)L#u!UMIhmc%V>Vng87& z`fCjAT#Ks|wJ-X6d%}d{+4wD(?}r?Bo9DFA=bA?w^ii^a`{vlz=X2x3i7QJ3<o*Kj zY$H-b{1tB!&wb+%G+Hx(N38DzrE*fZ!gCvDUkrQ}u$$MrjssjXs*#9SQxA`u)c6>t z0xM%PG)`lUf=g}QMW>oZ{s=E5mu!HTjdilmyxyLQek{|A4oQ4!qP3e4tMGD0h@_D+ z-2*{EuReSswLpFY;%BuFG0Jj2@p`s~d#*dNFT)s+k$1wfsmE|a!jdnXcOSEy_9MW^ zG+OHwI3ycvBq&g=KGI4X0tv3|jL!8mui`1!8Joam$C)9!c*ge@Br<6H?$5bhKC#1E zu^*KRi91R2%(VyX2GWGlA>X?+eZLt~d=}2X`a|Hy`Gq>IhOR<>%NbDJ5<&08%da}p zw{5=5{CHZWJ93e8h!|n`;^0r;weDQ2(_SdcyZ5rkDT1r%c>Z<E$Xl0PJ@dG|neVZm zpN~lO!Pl5aybFcXfJ=ig2V&U4l;~uJXKW<5t9WJ3%1WpVvR)hUq@+0B#y2r0|NTwH z;9qhuefA3+dSaMaqWsR!NGk4eJ+B^N@tO!B=MNRlj_APU>YW$~%7^i_Y7u{lzUOHy zX$E_<v63xJt8!tlMBcbB7PKjUZzN@4z~*5RTY$bI?*GTPO$!5LINNF<tJCu_6#tm5 z(kRPdZm_DWTC2ue(Peafzzq-341$7U7QANZ8>=^CQvCtg^jg~!!#+H}DwM{!aS4j5 z9>Y6(1poXN+Rc={S-0A6@?k&Oeq$%(soWD_vdrLf^jQo^2=pk8uz&DEzhW5tWl3NL zj(yOzcXp(JLy4jV7f8AadbN~|uXNv967{&4GEA|mghKI>#Pe1ealrQJd2_A#t^B<K zFXXnrGv-EJHeIXBKUS-f|4vJ&w@$*3|5?Vy-|`9Ww$ZiHJi}aB9mXR{sj{7G7uMjt zFv`r{UR~7%%)--`H(A87(e!U|ablC!g=`AN80sOW3>F%L4HS+FBMMmz7n+k+SV-CX zhSqXl9XUW+nEfA9-RzrTVPH0u$pF5Z0HDl&G_0dF(N+I<E7|PC&G-({#ePk+wt2Wl zz_P$Da5h)7R=3k$N!92Hu(T{u$&yMuJ6^xwtaaYB;+uER3gF1sM6Bd{WIz1Zlvmbk zDNl(Rd(e#)%jM|C3|RT`f1?y^iKlwwUVGymO+$fkLQ<ajRu8*16kHf;m&)6RtG;Q` z-hZD2jw`*r2`^h-=v5ESf;{F`LQLOYes0>4*2BsU2(7Zkjo`Ig%SfF9TrQsaY4v}4 zr1xhFaJ?6)>((!&8?SP`iW{#yQR+h|Xz5trW5YtZKp(&xR{h6jxd%{zspV9=e~4vc z>!!P}nI3m<@ADM;l%yfoeu+LE-|^N{1n(>-r*8M^#wVO(m+0+BaA3`8Xb{;VS_gs; z?0;>L$%iab0>t9LNSfk=ZQKMHBl-UVOuK^Y(0>lu8j_zHB*u7nNuXjFAm3kLRHzAM zEzKFyAb;if%j3qWQa|Rm{f*$X-w6XFn30B~5Hx5Si<dK3<!b6O2SMX7fz?UIH1>@} zbm)x<)c>CSKeb<2(Dcn2lG1lDEvI6=jkox=!y7%_9M5hfP_`z!&0DhLPVBUZ_?)W~ zU|Zq(3laW1N<utbGU~#&&uulXv2TH^YaY#igG6$ItxR$=FTtFhBFZRHhAF&jqWxG@ zTBT8_8L>XTbuh_dSEUPx_Z7K|gOUWibW&jlTBH!yC}P6r7`H=oNsbuoW8=qL2t;S! z6O_Eolh}F@_aZ6)lq?_nk<5<rlbo_FYXq;#ZGkVKyA7J4y@>ud9~ZBH0cyp1FLM8U zH;y7%1QFlO73}`T5A4#t8~eTof+nEu$93o43HLU%D#>#0^*nXpSGuLcOLZsa%9S2K zKj^zWJ62Fo2;ZEpD%>0^jNcq(ZU{KAvgO#Kvio-d67H{m8IHXN-h6dPF$Ng8e-dK^ zrqOPpPE%Zla+LqRIoK@Zz_=Nn{dkRuD+82ttX5TL>Y*K)IJzYOip&lAK}Q;Vytpj} zX+Hdg18suNBQP;lK`E<R=b?2GCbV(5)!15wnw1N9YRgIk?#|WXzhf65_OHEn-&@1~ zpS^c-WGG?h*IPdDFwTcKHxH!^Y|R(g`b}9w+q1vl%q}qVaGAtDWHwVIY-#Y^)&fhr zc<dPjy74*NkwoS<uD(XijTfr+QwFm!+yq?B$>|rWVT#ol6aA^%vN~KKuzySMrB&wr zCj{i*Fx?rL|L3c{_OtZnD^&mS)&3k1)7SC##%#O+n2mq^KeO?D()IG#x67*1|IH3y zHeRCq2TvZ!k_1e{zu6kF-1C1=^uFHRVUc+ncMDL@^@hv23^Y_PP4N8-Xm8xC6~e(k zFJ%7bX3a|${QtRGb3Xb75n;cv(>}Se)26@qmK!^5A2<#%<6{q1OA8B5s4krSe%hm( zQTyiySt9%{V7k;A(jb=Ywm;w26{81!rTT{;RP)Esg9kT}{*4?h(ABBp$|=T=gg#lK zBM0%Q{hy!UkVO&D<)$Bi#^xWF51#=)x#z&_oMgIG{XSR@uQ66R^Te);wy5M-^lpJi z!o3wse=}k+pTuS_Y@-<!xbBm_;6x&kIp7awQ0M<WQ#Db7s9!^(kVm=YL~-ioRE4=- zs7wubE_zI}{*=uR4#8$notKGhIP8j&w=g_t2u~*q=mp)_p{22h1wuS8WWakf;{c1> z?oqexA6)ok+)T<2&EuBbX2AQ4%vAAH0%4vi##g$6%0Kyh;B6c*yA757ue95QUDfk2 zK{$%N^;-L8A9;SuzZx}{qm*uIHjuW9$K%3YLl%p%$n>9C{4Go!F|nTl9^SyNBnzw; z>-(xpnjq9X!W<p=P?_iOV2&LJ*g8I96Hg<|qrxcuZct@Al2Zj3>kPNSWRgj*r&WH6 zIbCIAT<gLN{o43LS}Ud0Wo_q`S^w-=?5NZz76umWCxM?d?`0m{Oz0t;n3fC-bj43m zQj_=o;5juuW~nlOFJ!@KMeOsr?dfrYZc3ptjnxsE?3-U2{0{jz?+1V%iyQy$3|#4H zVj=jL9qis@{sD`hsR1M~Pp>QA9b7qo@Z**<5t3FFNPv>Byp4^G%yzrGhRk8~E6job zu`j2*ImO0b3i7O9vS@=}(&Ly6IC-0s1wQo<%P7JDzVh2^SqC>W3gh2Fe__GyO7oo? zgosyJ%ThPbQT9w3|KE9tCpI=Q;T5RJTekGh@V^*O_#E%{wu^yL^*q8WK8J7xSVmWV z{oNdVzYo->FlY<UFVB<Xs$*|nDT5Gm$E)3T242EzGXrCLqxmGa%Uz>A?<+QNo0HCv z(|@P7jdO-`Mhk{q(=buU=CgLayNoY(6fP!93~->wwRvBF1$fRR{*L788Gf1?k&Lly zx4@HPeUklg^9tYOGxW}Fb&TGpnXbOg2F1X)Id5G5T`i#NHR^W^vszfa@c7tRVt=Y& zN7>gH5NtX>eL6oQN?^EK4`V!9#Ks`BVD?|zPn1#`a&bx3Npd#WGp}dicr1g7LHLzs zu-1hY=(6QnNfcLQGoA%Q0bPO@#O$6}9F%wl3pPze-Z!%x@GN4%#LsiZvChp&t~gZ@ z0mK>mn)a<G|NIDxpgO&ub35-wD|}~Ozs{BSsi5l%0Sc~vR`t#4paB!iSsO5S_q<#o z78cm`!tL<Zm<Jey(o$m4yC(xyx)j1N#?U^Go6!Nbq{|*0VLb($VOpyt`XIazO4ARP zNkTVAGkun1B6Qx|Jm<6K7onuWWV3o$n1t`t#p%@Eg5mgy6SI6XDZlyB=j-|7fI|!C z#~&bO(<pz$SRHpu7IZ641dKYRY_R+D1u9|LGXC?wH*Z1n=C^0c#3q=Y$)~$de&NwB z5n`CW6^ot(t2|cOEH(N!Nt87PDL$B-l-Tc;pr;5UxUB5F&hkl`VLi*2yKAK2GZ=2y zSLL)uAGduYf%ShV9hARjC^JsuX0Qzs!y*!+RPhg&jRhZ2K&xCFh<h?nu=X(fCl(Dj z0pC)V`DgEn!j7?C*ahmR!@CIc^E*nwg-mQ#=hu2L<iE-3K${^FCe$w%qpGE4{@QJi zGXAFuK6rC1Owt$cKd$vfPmD{67sB{jWt`1=T1i3JAi&tGqt{zH-L+N#ZqUuSa-O~a zEhd&9VqiCsSEz%LXv0-C3ygxH0k6IKh3NQ~Fu_N@0`api(f;nuYJWxe9Cb7Js|PVE z?E*v@`=lM3nZP#o7UnSVa}*FuJdJ~E2N&ztB>|JBoD!aw|F@RBFve%<S$=}#{~c`b zL92ub{B~)HqJ97p#cGc?fgfd^fZYN9BjFQC#WYHK6b6H}kB*OjwhM22xvWuUaToWt z|2LY!d`Yj7GD)EYmsR}gpTN=}MZ01>Y07Cm)=N;u7kecYET(!o2JxHugT<uG{Qubd z?r^x*cHKk}BuEH?Ad(;ml8D|VBBJ-0Q6hTpM2{W>5xo=LWb_%GAbRhe=*%e5JLjEM zzP<K7>pOez_4oP5HP?*!&D);med>K<R+r#B=d&0N7^~VJ%N_(iaOXi+s1jz?KXf*S z<>uxd?x%bIE>2#i4^pN0wny3f60njXur21)?jiBFzzsL_D#J2W`J6LEmLt(*(^IiE zwWGGXl-yxh7e%3Qu0ORqW$V{e#@5A=P>gtx508<(pOfNrPs_b=Pu^*bE8`OGy2(!I z0fPW?c^#=aI=)>Ta@ET*dAVVKeWE4(vDEAO_o^h16a6n?KV|Ig+j%{et5-eaK&j5? zf0I>vlx3;2^y6%VWcX{jOExds5<dJ!aNQa!%0R@@E7DPZ6o_{@<&rxOE!eQ%@Z3mF zEU9*rxh>*FZs$e!l6X@ke)*07x+%*6J-j#|8i~=}zumPTzRxuUcLA*?)DyBJo8AiV z^GSc9HdW~mTyvWt2TuTOi5#|O4?LIT^9{Qqrq9>0V{eMLsT^ci8sbF9St@6$;*WgA z&WNsprwAdj#(s{P#K<_4^Cz>FbTJL+v9`*R&m<dB>tF#D{tlhXB@?oh_xNTgs3nxa z|C!6BtW?ngBhZEY>O3$^JbP+})Tn;5lL!vTr8&H0AdQ!>wip!HFuOQ+a$TwT@FqBM zb=+w$FoS>OWx85ra#x9V5)glp;c2p6LH<4`ph$5q2d0BA{smXlAz={a%yGj4*ZQyO zXFc*O^Ht()53!E9TaB!DO0Zh>p$P>wmSH7hOI4?13$l}I_$$k;?hD^iN3!%&Qrze! z+`eR5^^{m_<9(}RlXZ*AQ6+uX!TeZTRSqtpL9gI~1&o)cNdgYfxRQ-b#dYyjLJEi8 zC2|?%$Dtj%f!%n|92WI~Oj5<{ESCpk?AhMC+xUus80;Erc6NAibPzBOl>qfIX0BtT zIlHw9A6wHCss&7LXlhU2zTJJ!Hw)zYH@p1~Ue3(DO*Bagkc+uAE}{@|%DW0!Sg0OM zoxtYIa*0)n2}JF-G0ZY-r)cj%xOTB@gFr$+d)BT69^J$arhu~(Z)LNM05Vkv@7xHL zf7A?{8z7Iw#<F>ESIq2Woa(+j+;<LGNxHBme)%N3o?o@rv-e6`p{6)#Fos4X1al2U zM`tBtGyzGkq$d~V^s%;NM?Iwo2J?6q&PeyiJcetb`x)g|{y1g7?c|M*0+(eqp{1?V zA_wPOp(z|vzoIEsJ={qv?hVU?SqLI!8=$O(T_eL*)jAVQK_wPF`bT~R0RtV!#|1p2 zr}0xg4k@C!N288%!@|>~=+!2u>$C>+h!!$YYBC>YGIkhMXi%L;0tx3AO<ly^O*Wf% z{%Sda3^7@j#rw7=s6-khooea_Z)%dU{PUdrwyqd4_<ewX-cy*>{=0Wqfx9Lv`4Rp1 zpZ>S+N&sUS@i4XxT)2Px`2XhJyEMS9TE(-W`Db5GeF*GhavaXM|K+6r9971BVCvJ* z=L75Z&$s?JPrBs?yos-G8A*RX%zwAM|F6e_*b3<A`6IUS|N6(j-$}2pAkYK3)v1s5 z_n-fF?|u#iqqp}hC;IRH@$XNP!Y!<jhGB;0U7aFz#itMc;Tl}VdvVivZ!cR<NQ1Ru z_#tni&;NE6e;@rrT5yn>^kn7!_v`rE1DG*la5+HwAAY?F93;g1Z*3TV_#aH*E)Ezy z^H1x4`1REH7%>Rr;=^S2|H1EPU}&##?y&#Euipv-2WhT|k?9|b!7Yp!d}ss?);}4( z*Bo%7A%epT81MfdJm9l882v5l@_+bs1wkx4XmCq>;{R0P{+A6>!bEGah+_WX*MlTM z#AfSF1nK`+;s2-K7bO6rSN=Xr@(;hh1;mDp*38!X9~^qHSFgmt=m~f=|KZpFztI1G zq5r2sFFHg3UG7dYTk1+M!nl{@?3%{Wk2Jnuk^WT_pVIf{sj?0rEuaJWS^~;YsM&a# zRZ5%153m3Effx451I!cE{B}z+oMyw1e7~Ju{+FKex_9GqFG4w6E-OF3+qh2YN<<b5 z!XTI8Z^coR8$0$enUHPnkJQT_Dx(b#*ypP6d`t$iaJ<!=Z$?}#ijSw7fxSTw!y}%| z0iLEmFK|(RY`~(p=GvG7!hVVu($iaH`n?;&aHxc{wL!m%KQYdqxq$zqBraj+MT;V2 z+zQ5Vv*!N#$-^67c{h;fpd`FDTkXl<m-6k~jAE>q5{y4qC$$j!fpt{Nsq@a#5oIpj zaE0A+gCWFsFz%TucB?)BH~ZH@{KrcLN9;V4IoB~RA{^j2{>RuUZ|1z$MenW-bb;32 zGsfS-=A%V=lw8CTe`abfVMU3Fo;}&?xHU+pT=z{AtY$l6@gc@X`H$!S`BJn9Uj_B! z;GNwAk-rW@h1x5K90}?3iY^C1`m5Ry*!pT#?Qb(u7{&&tWQD_|&baShI>XE!hn(2i zwKKDUtPqn4B>aLyZZTL-L;pweQ-h$H=tWdTl$5fKTk*rc*Uh4n_$vKB4$4ci-2Sy* zKqcm2W08RlmhD1tDE20DYHMi~%@_pXgTJq8p7)0zVaF6a^k)2rBo)JhgT2eIuDrGo z06gR(Y6lxrvH+rYVDe^4hfI8sJXC??uQU7CT6=BZSHLHo=?LRz_)jbBOp*gIy?h^& zGRf`YJb^5!bxe``xpE(Q0N5x6wX95dz_2aR_D_}~LvXx0DyVSSs`k3?KCuSvyqibJ zRLUiK8%Ej1%l9y`;lIx7k0YTX<Yk+DsJ$0*_0nI*b}L*|f&dEVfsc<v2GS+N?#(@5 z{qu8M`nyliAUxULEp1j@`MPi{?fKRF;jHxWm<VBKweyaQmR8d0WQCo5Mr<ND#xhji z@BYn?V|&>q9%|qGnT!3`Bhmg5^Wy+4_UAP{6dJ8TRQ`$fWe^CBf~O2gi6%n~S|i^V ze)0YR-=+#%9$ryHBvby#1yTII@OQS+Qp!(E@Phu$S$=}u4$g9uX!gCouEoRqSY%A* zh>jmfvypC0C;O*(@2`Idbza1W7x3*-NT3)kl);kW1!?RvlP?;DZ@pHhYpP5@owz*! zivY8{PUUs)-^}usmu>vvw9>CU%&OIpeOsbh=Xg`tTN23eh}wwQR<1Wo7NgFfGr1}j z7OqxJ#rwRmrmH^nXTSx1aCtf|{0%QYm^_GftAH>$K+>um;bAj@Wm<sb@b41&Bj$s! z9O8~ULjc&X>uyq1@0U8`UQR18oP+WVvoCKlg}O3w`?r~#{@lYqkI`@6_iATQU?-ii zGr2rgyZ%~Xyx@m|vhsXbi<zeO$#1cq3X-2majbkB7+*kP3>#!WOhrC`ommxC-!G9~ z|LtL^Sa`2S%k|s75adX0vlw#=L|A-1YE^gwB*7}n(@kid(}bmRo%h=RE%=xzctw*u z3<%sD8@2(M7KVV!{iOpe>n`>CGRrAJ`FxtZ-0?v&5L~7Qt5MAfJ;bvS03(_GZ6q;4 z7Qd%=@_#L*yZpW)UfT?X*0bVPN11$!P_IX?+4Zz1kQOE7>HJ^WwW~*E5_$VxolaCa zZN5<h$X-R}<OCox{x`d)a2aGs=3KL4DcB(T`qGI{^j>C_spwa;YgNXABKml4i*XhG zvz%LidXEmPnv<GuySuYz=LHMcxJ8m2UR`vU$oWc{T8gkcOW9Bk<?n|atdw!!0nRmY z6;auKbun7032=o8zawk@=sFW3rL20Wpk(lOgSiA8Ow1z1efaF8W+vJBz$3GzDD?W9 zwr~B=_+zoVJYWQ*6<ggh3-vcKzZ7!`fQjRt{CP%hg?tALhM%s-4Bc3es0G?=NMYz3 z`nHp<*<?oym$l`ecf@~D6QZB+BCF11wISJZN%7IfEul2BEul%D)DH4;bD!Ayzg4G; zN&;!`g?cquVpp<I#|e*sMZ8T|Z;h+dj4ODw)DhU-xtrhsC30<cE|`}C?^uNcxK~3H z`-69WS4klK86RE&&d!(Uv^Q~PNQRPubd#1TYnp>?3FXB{f7K0tIk@dUq7~ya`V+~# zHeW$wn9djP=ntymnV7&Z(qe)kgu+30wgCr&JXdS1<~?Fd2x@7#2?+^ba@%5H`)aV? z8yODoS95aCzjy?s%LVV0#a!#%alxp<O$7T$SW3_bd4@lJPUFTagI^u@;)(<F!W6~r zuxvJ~nrhs-0&Ot?>%Uxz|9T17F7B*bkr?MrLZTkA0JhWSU4WHIirD{HnYdR0xXxn% zQhM7XNa;hg>%6s(8Yg-nn|N-R_ZhCi2T^>yU)`NV{F|L??{9+AlpJ%c<ib7I8C9x= z;dV=7Q$WgzIjSG2!6Mq2h4)7?ZTO?hV+x+ZU*`Y)Xl}F;d)b!O$;LjW&&?hNhc~{~ z{UjdP8Ov9D3vwR;iTFe(G7SnRgbG&4S)cHEtboK>QSp5cXuJCovrIF$t2s-InrXnf zHXs4sUEKO>_#aDC!~}fReOZ-AtmfY~j4U&}_1^`baPBEs?w0|4oH(!oFZ+#w-0tlO z-7i=~FD$f0bp8k=-HFmv9KdK+ko+uk3xd!;1v;l6qkhO0`|+wdBfidJ_D%wLsA`IO z%0vA(dW-L(3LVx)`yx-C4gypNo}J~MgJ`)`%W+^NQl(-tYAV;kN|6mjvgN&+Bpk8f z&<Xx&$N6s#)oYp5#5F6xu>1^Jt!pmLG1X(gKF;M<dAEhCfSP9`z&k^0<;BVFsvZi3 zB1j@!1(B*JcfSjiEQVnIT$ci7!9y$H!%2^NK%Ty9`@-OX4?&nl9WXGze#2F~k0*G3 zNV)_N>bXJprRDc@r%hZ85<R7ZC{WmX0DFLT2h_>Ob@9wht|3OWvp|ANY_fa}1IN1* z#G1d`ev}pWIL!oiMNyK*;QX*9eNIWH9o%)1^k9kxqsBN7i@C7TWluusHx`5MbGH8* zCmICCBZJRmL(OYJ@8nza5xCA12{`wREm@hVjN8xeUReMr1j&L4<`O*kF2E@g(hmOI zk-`C!)A?<3ju=)#_pS7xyI!fj-oD#RsdD2CF`Dl-%ICWcqgDrVBb;}-cuvC*6P8s| zVMh10Y}=X)LCH#4K+!0M`wv4YZMeO_+|_P3Zjv4EelS%w<}8Of)~%7EPyFEXEnz$) zC^JW6Z~>g+<AT&wYiW*ip!$Y()eaW@z3_7C{A*gm0&UD#vg686jmvc>%Waag{cW<O zW7unL%8|(7n#8yE#4`yuim-?@^P{v!2#neD{xmiJC8pp!laW3wJ!8``W|B)ZHtdY; zdsN}?kE^URQ!;k+E-_Zn`HNyjJ6o#<vn3`?Ws46aQ)dEbc~DCOoaA;)q=f`I7RZ#W zf4+Hf-osIUQ}WYeNji6XeCg-INB00J(#6Cbn;+rfZR>zybIHmR(Ed51a<c*LnftSk zPJn$c4$~sf8`{_nxNh`>LCI|ER{&qQQ~UVQ_B|#{GFx9X^>)KBz+U<>>3$%z1Q2Pg z)V|J~8VYf`9a@)k^P`_tseL5HgQn*K3DjEIK0BGY*t$M9kUpEi3tKh;m3xHmWD>Z$ zs?U$n^?(NMtP2GzpuN)*&(%>4I#%kptUOO9g7$(GWE}{pA_2Ynf4Kp`YAIx46Igh5 zTqTD$9D?ecXVQ7nG5J|Kbsk;a6g$4{QNiv-u?C;or6>9AS8h1u{#*wArHRXfMqk2Z z5qd3h$&V`nGz{YmUe^{c1<-k{gcwa)n3|dbZmu;eTw+>HYjS-BH!RyP2G28H*q4$_ zpuC<FO0}G-*ATfL1sFT9j+F~{2&T>vk*jx;?|xs<a7bDdkMPj($XWZ?f?3msTc$Ff zey$XP6zHY~CJ@8GpQU(=2DFE*r#mT5hkUKi(HcfcCfpO|oH~L}J<r|r{A7=LLAT!( z?=OPKEB6hX$keZgO4o22wBIUeW0dudn>n*|wL6>*NQhE{v>mvV4=PDVReoF`2BOC2 z1t=AUR_joCuucF{nh@QOLy>5OoV4vI-GWfn*Iq#5<ua$}oEja{Iu6Fo3gR@+$$_>w zJ@<2`)&G7D+H{SG&(8f>-J;6o0DmS|IW2>El$b?-hLp!0%KQ3}4_iJ<Y0YbLIL9Dy zX`Q1jNAk52gQ+8B{n^lnjI69?`^{;g@@AsHQ(&)NruOca=^q)|X&Xd#wGonJz{}@e zgM3_nke$BdF$%^J!WwPYK{?_+#sid?a_L-GlEBEM+fLWZ?#GNW#v6-jdX=CuH>9rJ z6qvTbYU~N&=4J^N$cnxUcRz#ZRik7VRPGlM9i*H2j-;rgYZU|q0E6E7U#I+1_e1Xs z$!6y%&x@j&xV(CAoNJxeII4{;>(2Hwx_fs|+XH7nA7x5$zso_gBP9JwInZwtlMd=T zLt)B{3b+(Ih<8r4{mfVNZ{5VA#MI&3%xZWcJ_@;=pDE`62-%*@xb1}Kd`fbfZcFq$ zKavX0sK3B4zkCC&J=34Qhf2*y%{74b_yy;i!i=rQppJewnoFCT*AO6Geh`($U%0o_ z@j#yP*Y1Y<=@t{oSLqC%x1ydq0FU3fIc*Go1h8SMl<ag1FeP>zTy7XP{%XUcNm1zr zV1FBFp%fq*fuU7RmwHA3O6Co{%Rb8dNJho9?o<;3EPr<mDml(v7+mj8nWMzjC<{>Z zm)!F4n3udnBd+-qrJnHFuJT1Sb_KP0p;!rT;@_GKx>$b5I=`Kc{WtqaOdryi1&M*< z=5+elky2tTzeCWbRKYFhvIjkvui(C)mNJ8UdGYc9MJccpD{`cFW!3WY_dUS+yt{6z zXwwo#XZ13KYEkFQl$_(J8#bK33@}MZzj}=vrxhaLbk>}Iuw9)~*g(K~+gayZ^Ifs6 z1gqLr^%~g(<TQ&(g8rJ<Q*~{0fyjd!k7w)$LJS)Tc{;gr-%Pg%lvsBueRIk({RPae zc+g$`)b^gJ3L($9@%{T^O8|!8r55+a@km`Kr4guB8gShKOroKm)VrB>GB4WI`4>CC z6hHsY@S$)hQ*psv`;`Q)cE;MmWs9}Z=EYa+<v+1X0162?^YW?T4xj=_0A29)@}=O0 z+yRJMOFUPUfGLodESlyECn-$^Bz~85-0vpYb&qAGrPYgMQ?Aju{qo|Q^(7_2&Ie|9 z>?*KftaWOgZXjQhhE&Z~_Ac7_!9kNrHRUs7g=aY*OJ*6gcg=a&Q-=CVt!9L0L&1jP zK}kPHtpWYf)*JEUZv>3tu7dFiR2udScs1#&L{oR4ED^B&Fd};ywT>>RBVeqlG6>nJ zWt60FQ91y6dNZ&jl)%?FIsp3{U~Ve5l1C5)uW?z(3m@s*{q&M$wHh}AATR|y)7JT( z<p8NAUSQ)e8}L=E(_QfPV|23zf7LoNf$wCku8g-!AO8-(dtItnPCnoWyK^L|^F8{J zB|}Qgdn;f;T+0K>>VoZ%020M+zL-QQDCa!G+$^B%=dm|5(g~~uGL7@&V_L(FN$0NV z+@Nn}py1ClgU8Yr9?pAT{_FkV9}R6RjKGv54&msr#x(pUz<;IpeWMM07p?8GT1v|s zQS-U<mbak+vuxLXL~ELC!eGZ6Og)uMitrR7L?h)-EPw)8025NPXoNm>XJdkOm%kud zpK$sW{n!`+5!Q4Fq}<)gS)@Rr_>cA!v`Qmi<`wF<k$9T8P`9u{S=rlN2Xi<+2@|P7 zOeCzvd=1P?-?9?xrAvCEyN(YVI?B4g6*b56bqOn<aY=7vr+9D!+^__L7_H$Rp4J>u z0o!_E2h`x*JvO8<q#Hsf1VCRctl?%O`B}gs5OJ)KiY*3;9A~REB8<B$Ixrbzcx7oJ z95uVesWKdOr%Hu0NQ<gSi^z_W47vPdN}MMoF#URibY9dCP}jh%Vc(50_ZSo&>Y@Qj z;3iwmE<(qQGAqAogD$-8YP>-mD7MG3fBEUQE&yjBzq9OmRoCoU-*2``ZDFBmk2yV7 z!|>qQnE2x!E?HlA#g}>EzqxJO!$s59t=J6i2^7-^l$f+(U>xOLZwIgcVI%7)M$25I z_bL>;LC#_<Xalq}ezd!~Fb1!u&Kyb8Gt3vF3NE$uB>;s#sC$Ffbxm<5!Nj^@f;j_F zySoAFYkJfT>Zv&etjrTUD0LtxYd(lDa3db6=G^H~WhOsP$`!;w3Y2uP*8z|ZKv2NN zi2)`VHZJ*|8)W|wBJm4oa^l3=Ra}#V4L~I)TA)uVHfk!BF~c|zo>>=h`$mooFcYD@ z`E{pH2Lum)UReUDner<)CLRE~SZcdg`hW*o-gi&M`A>f}b^%Z+{f+9Kd|yf;62J2$ zp1LQV=Q}B#L98!(&Rs#rQRmFLqkjrwyJq2wbSF6d%RL_p7Zv$|0gV*8j@*VgfnBC% z6QU!ESG8G5YZRRAp{-qc=BCwxVRIa_X-V>y2F_L>regZcV9f~BaU_m}3X{tTZed?s zmDg@MKk5-UEoOYNP4wV(4PYGN+=lFfuBCXWWd)b+s%AoRKhP@kzkyK7FS?(A4z~EZ z)5Y>n{X%EE16_dWaJ_}l&J}R%d53<uSDGPfXE5iIAgh`tE+`%EeAq;xb3Dd!1~|GJ z%ITskokImS05T6}4|ev01{z-Lv7fQP;z||7yCpuF7q$pU7av@*?w4YUpK_WJ0tAkC zK*#X8Rdlyi&ED`*^$wL0Bw&7Vo|g`A?cGI8mU+x@Pdz)*kJ4;+1ttB5xL4g{m%u%? z82FpQ>&OO&uI^Z!ApXIei@hv^IpV<NUC{9jZu6oha$prSx2eX9elaBpWXv}9jyX3` zQpuKc(5CF?xSzERxgV1d@TnVR#_{Iyj68=%%J=A@sjNKM_CS^7s?$nueagpZbCMWT z2EGizI};|R3TM9qE*mM6i5r$zU4^TMUT~Z#Th}ZUAci4cBLoPEyv(u4GK(ui%B0N; z6(jCM4M&UumJ81YU>%yC??gdZAtEbX%9+50M`M$`U0s@ZEY)_xs4ZE#46*7zR^>4G zBFAxkJX1Pv%mIO38?!q-Uae4eR&BAbKZid|o<Nou_3SfI<`M3$Ij#2deTkV{-IQ^w zazIBbCeJ=yJT4=b1C>H2nz=%|pMs*!9iPr_2#*9=T@3Gr<ap)<_mc$6_>3ZUlV*{1 z<BBu1xzko{?Tp-WUyYPZP?Y^MNoNUmGxQrLY#J{7baL`0S;rHKwe2mq2>IgF?aEE2 zGFf~1;$`STZq4p#P|WzFp{j=(=ZTnNz`wX0pFd&bx^25_p}IEefjMCS#<V<%?wn@4 zDaVx30%+=f#e{Yn#W%fzeU>&eJN=arAxD%q8#;i?lteT3l9+Fy_h%;n-!hUD+KU7* zYk*yvQD5!*6<>IWHK@rxpr22Q(@9p$CEHG77MKVBXoc}6?xJ-YiT3PYx40munv1^v zY$y;YzKj+^oAMCxuZV6=O5rQsL^A$-<$Pl}oW^~(k9pqiCRUUjxC2Ak1OeZ=757N^ z`6bKB2M=CF8FSs4r054A3~N0e7@CmAK5xB1naxTx`#_!N#m~ek8&5odd%!Bv4U9rV zj+2SP+lp6IcxK#6Fda0ca}qShhJJ$6ZKM{w7)pV)(t+;!RSb&u>0W^+VQ8ZOiDP?2 zZnW|%Rh_7VZh_5LES9QAR^_v}cHk28Eira{qzn7;I=A`*cNKFZsl3S9C%*H;dCs79 zkH>x$z`d$EzndoeaD>|eKSI+Fe)K;2*%g~Gq5aY!9ui|6kE6x<qN(spQ1NHfp~5a1 z2W9Vlh4eu@GV-REI3Cm!G>-bVk>`3y`l61}R*ymV6inV?9Wk=)sZQYcn66<MG{Xu! zOcvHA=3hzuS<WD!HfTD~yQx|vo=7<IPGMzxQF-(j2(}AW_Dh=u0E{U}yf3SLUnx+C z3#R6ssgh<jQ)>vGXGt#cj%m-NbX=78O%K+Ut3=m*wP$%}JVl1xPPZh_P2jf*flqh% zl9Wd%b7x}4lA@Mt)kZ~&fgHGX=P^GGW*G0L9olxV#q<&nAf6;E>;U9(MBgXh+SMPR zN%xsar$104yy@rJ*CiBv9&poGMA&>Tls)h`+v5>CsXADl0NuS}l=yur%b@A{*N>PG zR<ZKc-4#VEH63?^Z=2;Dioj;-aN`^}iL>r0kLHtKS8O8F;APRL)Lf=2CESsp6=oi? zek&Aje>S9}deZs^zRV543epbp&KXEaDn0c~q4-y3JTH6<t4rfQyl5l2#PVDC0{D&B zvE#U{0<WlR7(}&NJ)&;0gdUd}4z$^``eY=tp1DKmb6pG0wfCF`K=AOuzTw>S;zY+& zf-p(bVMz6C=oNsSsGHsniST(3NqgaW5a%CV%~>k$`(wm3%v#}um{xGKbzZJA)yM<{ z1I`az4>kIC_>FsCPiItlotNwnsp}lI5#BERreT3ly&qvIV^yt=EC@+Hv+k|Z?2%Ft znFAZV!xTU63NTs5UR<hC7db!FqaI56Fj1gW_jA;}mS;?#^uj&cDFQ`BRO7<+KEhJH z^>ySOIUl|Ma*Ib}1f4rY6FHFG^GhvAX4jkLq_?<(sFR_>dxq1nv&>}O4Ts}JH026) ziP;Fd#&nd%F0f1+=&q@oyPwo;HKR?K%1lp>qtvyxIN#WHadiPdbA8inO-w8q4^VTE zd)5K7SZ4)A_x^$bTA@s;ZNQ=QnMQ4&Ig^Qc5Om02aZOmm8kU;?Y@^r#ug<$}UR1!0 zO^Y37YNcIazWbj_t=dA3)#+7*4Ad1yLGY!sHT=HMAfi1dshm1drYJqI)X_j=tZMCn zp)Bq>u)f_U{LI-|3j}#>B@~)SN%{OBE~_53giYf^O8N=bQjV|TYk8h?aQ;^Icyn8% zR@3fIk0isR3ibdsP?C9Cy8d9v_fz7P#oU9K^S~JM$YKYEj)CdQ^W*1OCkx>EH|CzS zetz+#{Q+S+NFljb1)Fa#lD{`1uWceY>IT3wSG(ejlF3xWsTld-(iFRve5joCYNxdX z50x4#9nUine9vq7`%Ug;uVq*hHp4_h57xt7<V&q|64*NoM+|z9o?+uLNjjckZRTfP zB-U~{GDn(46H@%6Q6UgVT^v5&oPq-4{0jc!`=PgIP4ocBFopmM4mr;3dA*5O11)jZ znUL9~ex}=ieZeDECtrMku8Di}F>npd-Kv&^5@KuNvRir<BEVRp!CK>nO!5eOmX#LO zwMTvTq|BjQx1$?*is`{h>l&P$0JbVgbt!7`o7e+~+gv&30^Zz5^PvLWPCXW32nU^4 z8_w$;jeTDjf@>}xhIxmEbi;g3TknUxKYHN!Yl;r5$<C)e%Ztko14~?PKOe#q2hiVd zA=*QLMj+h9HS#?2+~2sO9fYzzIaJL1n40Q+fgP2T+LxnP_#*HowD6r6J^7>2q90UM z@2;!`eSOD7AC32rTj9Ri3kJncI87uI*Kn~_6mBgf8RZ*ojFoUgH>RcpTsJq}gbS-U zOO$Il1)Kmf_p&dWV+953*0-d`y5hr4&Plf?-@>vI1P)+77bfZ-s-$fLBgw!#V8fc4 z(@=(24xPQa%b+gu(6B4-%qz!vrRDh~-5nfW&?WD0O}j55z>(DHpg7IuezHMU`pz|1 zAm>33*%yV566DIY0;nUPms`n%)J_2;)y9!pX#QKXXFe$@57q~c<FC^R3vTxyXe=i3 zjX?~81g@4Fnu)R7MzcpGcZPOo99L*+hQmrQ#?<lJD%R%Ag)rl0Sg$dzpx7lZ75vEi zqj`k(&*}MfmX5_e0e5vmcCrh<{VHg(08ZBeH76>Vg5njkW3pwT>L!~+`4BO}v=$qh zsi3_3{EdXjhYrPdGV>|GsPojrw^B^ZBEZtHNMs2`Wj;i#Q|34FfDVV4ENHK;&eVm+ zcqmtSIn}#D_Aq@QDF)C^G};OslsN#f&Zn5ZF!PmxZpn^OFvq90%g1S5fL&f>?PW_X z)5aE1T*2&)E2rrRFal-=LL{o{7{dds`kskRdZMh2k(!CNC66~&*xlvLR>U)60ZlJi z*OCM){p>9|G`0B@2<|kj?=GWjheO_vOWH_8Q7UK2v>ghO@Fe-KmgLWKZCC|RpPqDC zd61qixiW2)_w(t@s^c)LK1qS)-0LB{eLMgt9G7}HN6fTD`)n4^$z|fT$wJWIc3`7@ zU|rG_p^8g#k2brwSs6zm)ZLGk64O4I)Sjj8DK-59RMcM-S5f%|kWZ?w#?CFQTPk`~ zO|ph%cW|Jp^f`qEp5cQ@G48ZZpU@>3%i5o<#*`m1nioCdaJhx8QgmUz_iK>S8Lm^9 z-NE57FV4x-rpm_Y+KyXGep09;_6^ZXm;d~8H|E1xh-&_EnPx{fk6c}w#oHdaDiEV% zm{u{)Hp!8MZzl4M@z`?@9^Wd~^b@ng(I$wK?0;tZadkF;$v54BaI!`&d6#xb-D9_} zWp*V>-I8wlHp|F*SwW3<-0n2}bM_#Y&pWOgZmcU?yTJV;c6nOElHP4_dT65<2Dfzl z2|y6q@Dh$YI!ErqsHVn&KbGG6ZMwj$&Tm&dPn*jE`CZ3y(yWX^X(%A{w$1a=HzU1i zjm3;22a|T4A1+^Mn9XzQMYvXlO$Is)tOLr%cj(BeRTX(VAmuN;_?;3_Mpv(|SP=>S z`u<Jt+vnzF(9~UKO*83?s%XeYd8yMcmp`@sNxzipLqs6nyj8CvGMa}L1TB`WwhD6Z zs@BhM=)dl6MGXsCR+!vw<|{dMO-o$v^daIO&5=_F7llsz)_e=at-_IAekK|>YYRO= zz(B2ReR{5)-Y~ZmXI`jU!9;UnDg25Q=5gNp{Hni@#c;L2k>{f1M$JL(FGm0J<FzZ^ zw3$N+kbGRKwMnw(M+RY2mUoBPpNFAjp+eRZUw44Z8CJ@{X{%3wL^b;;rdwX?hNY(` zeoA^1@(9psDT1yYk#VuWFzJ|ceOk}cqQ?^p%VWEYXGxJ&+p-?Q(q7}!HD(t?rgWjS zLj2|3<!-~<Dim;;u&ILfazH@LT*+uMqmbvG{h<(|OvhvCrY0xfG5#(%dDg*-Yfvm# z?y@)BVbp3P=5>u0eV53^(ZVP4#9fBhx@sr7L-su)7n=4w(b^|FeT5)&{P^^Xi4U%6 z+{&a~51~&QRl(fF&ECfc#9rF472ouTMG)DIvZ;SP55o3m)_gAcUpZb9+(d~Z*p8n% z12Yeo$!q-a8VG>t4&H-7Pd{WeuWp0@Q;iLhzrlN3@X+^@VFy=I?K6~g?vyqH*B(Tm zU#6+cl8$s$v)|ZXuPS>PAmRCjtX)M~)KS}1cLgwlb{LbXdEVF!NK0%wyXQ9LFv56V zzc)pIk6aqh&g6DJ-D>fPHa6S%t|4IDwg%AX0u5Lfi!!cNS?=ufv(tgt(TZfu#*O|Y zab&s3{@P>On;_|l4HOugil~V7Zj;MwL%cw<3SS}co5_#&zh<*a6spp{!g({PlwxhB znWO3zJ9AOH-;!1J%~nv}t_guGn?%R>zlC{xXGzVeV%yW|B*&><3sagZ%H6zzG#&@7 zMPs#}XdNW8muA%u(Ho95>ie$o?mpD!h{m<_hS<uumlBuj#e-`bD^p0jT9Ep%&u|ux zTeu0FMwZVscZTgf4;vjR$+()ZRVg^m>G9e{DB)S&Zhq$qY6dyA9I#Qn?(SCY!M>z( z0_E|qi_DScWnDxg`?&_#0wMyMVaIMA-#G}L91oc3pqe@T?wpZRt9qqX*;w3F+mVJ? zg$y{i*UZ0Zv?cSTs~pG^hb$}0rh8iUSzSKh9pY6refVMBDutzZ9I3_3yI*$=DrQZi zYEwX+b2^86F**O5LPtNp>cgH!vBU&?<^eKiXaZUPM9A~p`<t3}#J+5<u$d#xwh(Fu z))VH+-itK7Kp6K;=GgXF_3<$8uH!<_7Ey_*?9_YiBHiOD)3x~>eD~Q0^DH0^L!tHi zZV_bH)mz_?+1W_c?VR=agfh^?JXKz-VdH1~rl%PU=jR65c&cRAb=Td8)HL(we6Q!{ zyfgAR6|5(s%jZWfK27u#x`)wD%|p>|p&$DkjW1ZSVbRamtWTyj;XlSr-oN+{y$N2~ z;*av<K(Br=8-E>!(Zb&3EwvqQ%wyQin)ma<pD13b>D`<J?pB6Zw02ojUMTYjVVy&Q zSy$D|&^wgAS(cv+pV-WiIzRyAQJ~?9kc|)g+~Fs+)t#IBhNv0_ywIkr&@;8;wG)*T z1>>-9l^*%DX!GyIz1pvW36Yh2IN&On0WfNM>nuh>EWLXPGv@lZAQLsD5G7EA`UDaP z$Wdz;76gR*Ty76L%#a>v?)jmndXVujlS%ZE&1PR=`CBxI{NM6#{W3jIRJV>)RP<5b zu;v+csmECk5W=sFBXNQVf&4*N7jR6!C|a${M?{Y#a(2`)wL5gHn6(J3m)ezHHeHZ` z^HD(TE%|}p?BjCCHw%qqMa|TC?m8X_rtx=c*C4xiMI=f2<t@u+k3T)m<YaNntDM7- znJ+uPAkEv@p9>D~J!1Bx-FL`!U6bPvVEy3S#()UfVz$q%Q#CF8PR=8lm2eC6Cq|LE zl?{wb;=J$STVDHSX6;yA+mWMgF)<oJLwk@T({CVVHM+rX!jJQD3O9C=nr!muVVOhO zz~i5QJ&KZ>WK~WXXgRYpK+hqjP<G}ka5B35Jb;Lh%6wAD@y?Y2Hqk;v>sdOv)BdDg z!HQNT^d|^t1qq3*+*<;{N^)6O48a~?USn}tPyYN2&>C<&K0DqEZk}MOd|wWbd0seK zt%hRNp&fv_o6c5S-d!9cCLVQ01y2K@A4?s7r%OL@gvp)_CXty&4U&r(qdcF4oqWkY z0L43;#;eGJB-v4k^bMIEXha{5ZPKH4it=*OPJqKeo|L#7u;O)fgP0>$lJnvyaHcZ9 z_B(~G($2DSr`bWUHM0-=Xx}7PmVgi$z5V7(iKieTmHmqwcP{MSJ4w`eoJdy9MJk`0 zhw{#ST;Y<xA)0!OO~pT&*B)`QSwPq!vfF$3WB9ss8Z_uFY&czIcKDeu1R-RSU!MQt zIFo)fZ(XHtDjeu}x31uy7Xa8me<~_B{MaLB&#_S!#8I8CM@~%poaFu3MUGb*3lXmc z8q0@q=fzDcKlZ_)2M-5LXLMHdlpgjI>#Pc7S0bH!0#KF`WPZpTzcx_qIToY)U9G;a z{OeK9qAGOvZ0cgxs%6o|ZMRP{y$==ii1`L802joh$nD|9(I!KA28SN&*`Uc=(&>#^ zs&^i0yxk(RZ)TIA$pK{e#$#Mktf(kXPY&n98p&z7YFGj-IB;CF%(r5)56a9&gmX?; z!gcd@0yePABu<)%#n4rY0Mc2H2_O0tK$?YcE~V#xxpk^++!GB_W_++AIiADp)zT#X z{IyL=#8R#ryMtzxM4kFV8o8i_BCis?6tq-MtJ1!Rqp?NFWB93I;=G@yCiCZt^W&*> zi$UlfwHazq{i*m6LH>#7@~QJ`4L`CpDc{eZ0um%-v{L2Yt&V(Hm^<@}Uu|t!G)iaO z%JOhbL}%rUs$l*HxqB^F(gYO(a&_24wRMcmre4jVXm2TDAiZr63ohP3AOg#I`lclV zl8C&lfn$4isWxkfkiZnbnSAe(Ue%d$X6e2kn@vl6^HEEg|7!JDyRw?+I_QjRw@hcp z$oU?|T8LbA<PF2S`{5GSpMONtuuSslR63HRqfcXtm@&*#NuDEyojKPzcB^)Mhz+B4 zyB8BOE#20~9oCB<iSqctW*ML=OsR#TE)eFd704L?XuMI&pdJ=;c0zZf^m>^DZ<1%t zfKD=`8Cv+(<F*o_UYkeY^R1vfX^!~$7Li+ndT<i$&AOB2LdE=bUZ6+4slC<r)cLj$ z3CapWu@+p4Uj1D@$+0T(+vdGhFt+%Y71*v_+VG<`**%1%hu8qqA`wX2kv;2;8soG8 z#iO67s1JeM`W#29b3(XPUJb9~sMNC>+&o>q)rr_s%L^`rcbICaNOD^LOmm?kGav?T zbb2A}atM!h*fyb~H=tM&NUzmsoO%mOk_#iiM&DL_+}mYnoH#5!yHZNg9k)31e39P> zbkVY>^RyVHBxw#h-<^rBTE_Q;;AlC}yhx$nyS^U%MDLt@0?*5G|6-o*<i#@e32hQo z_22}A1;)kt=6&&rTex4&83pa)CS{wVJkY~>PLq6-JiW_WX%0ggRZVBmpUKKxA>BBp zvTjSA#82}jO%l6?Rw|(OQ*!`UuG@6%*r{%UzdV10xmpf}dwcS$-EM0*kA%Yt6$K9+ z*Z$meY)0kQ6=K1M@k8_hcg(v5cRE)P$&t1*G!BD(8Yd8kd1|ZEghDTZI*DIQDT%kH z*20UFs$U^4)eqlu>H$d(bAU#<FAV~Qg2@h-`;#)`!_POWBxZ5+5rqr5vQwmAra9e@ zsVV2Djx<56rJiD9qEXC7NAolyl}^}UIrD~iPcoNI9+xItH%m>5iv3ohe2P|jA7F%0 zvR-Zs?=!myJ_0^k)cZ1@6A4)--Q3KH@;QHoA(aaaqceth87I=+l|D`v_bx7VYOVKj zEayqe%GtGdG#_<Z=CZ7wH4$;B7jlgVvjs@e*AFYvNe@GGVY-d}6EQD!OMurA>dkO0 z{*~j&#xiq!EEC>c1uiT?N^{l6P4M1+8u#M{o$k4-9;lK>e)f~(%v=TUAeOSyIjZO+ zz{L6bTgras*`t+~6qb{1GDI@PfmSmiL+(8VHp}t3t2le^O&0>Ft9K&2;U0xDX~SH@ zE9MkrLM)du+l9+;o{Oxl_DDyS6KIWn*LYTPFZpmHudErdkUI;JRJAT=>qJ!{S+1$< zK6mfWU`oG~0(q;tXsqcx>qi0f-&0Yt6bEKMZ0K2GawxThp5~TM5>T)jFC~2FTqYbw zwDitIPS$qr-aU34wTKC2o9Oq^GUW4t^G}nts=^Q+M?ZateLmP)SPzL<4$b9yzrWbL z=pyir;o1olsNHoRU(eIhDmo!M+%S?!d5&;d^$fM|6a?uQ#T$xfG~IzCwTc+rL0NHB zxuq6*+9T;MY_{PRk$jRugLsiAYMB@#JYtGJ60LJOM?qQRX<SZcE=!;??tE)|C1F9p z=;5LHMfby7<E+N`-#u!l7MXcbNEKN7kM**d>7)ndnUIzUV2BwkP~e=TxU!cFL4l`b z*YhJV;;UUS3)EnpPCmYjMQtx(nEb3}WRt$^AvT<vbj|O6mps{fs{G*h+_zyp`8D5{ zYIS;mg;qdTy55)0y;7iyJQN!*kJeu=6}<NsMFv0$u-7)Zd2WYq3Pn*BymZ~A?q&no zW-<FKsRDw8J*phD!x0&}N!xHA6wNTO58NV#OsYL7P6&BTUV>_yH>2;e8$xaNXe-E< zkjQN61v1S~tmk`nvg!QQAn5pLt%h*}SCL2H21O7{DMeR;B^BkfPSq-H8(k(YmJAp9 zsCu6;9)Kojept<Zam0tyH4i)=e2C{Hb9|B|i!k6VNUyvmAWn&6KxVG^bM*(Qe2`_o z>gYg?iN?UH|HS1hrZef*tA)CaPaCXE`&8)-5s!^Q)MGNyLHQK;$P~M_&<U1BbisuH zl1RxR0KDb&vg0#AnUv~1o=FyS@7|uhXJ4IbdGM{o)Q%#rvEpF5&ghPjrM9@0idIyE zg=J4=rJ~Ns%7DBXiim3}l9|I`@-1A~gcW1oNdo)M9$G$Jp-9*fRs17~Y4F|}s01S0 zC(1U+y-cvD4Zl>!X0yx@V%HPn0^b?EEC$O&8W`?<%W_eicwUsHNV5dG4$^zR`R5+) z^z-G`3OSxE{{pMZJ3pD~(6=nS0ZFM7hDd4!m-8As409W%>Q>fTFN8}PB&uI*g<UPq z*FD-lp5U@yS)?3!Z+|AqR#{2-K+|oj$uPy;)U|K;>0Xj-2<5noF;7)i0_iikksuX& zOIjXF%~H=suX@k({UL{$uGlB{-_(sKzqv{z_~7uw=H1J5#`BqXT#Pw(Jeif)cu4Y! zx<T2>n&*a~Oy(&;;xM9|?v0jxe;{jaxznqad<LyY4#BQZgHF7c<zSWsR|)e7vqGtP zxXlBblA1j?JSv8gu7G;J6=RYM{h!&}>G(cOmMh9^i+ig(l1}y@qs5y4%xTI&{m=zK zhvCcjJ7(0KgUTE6X8cJxBf2{LjjzgP>d2g^Q9_<mguBMJq!YFD&^xoDlhRv^7!}~I zIHT9Las6zR7aW$acVRS^biwObjkRGQfb%Nm1L2OU3w-dn(lY-|xwT@su>(Dv<JiGO zqbSya!=O;L18!E%C7Y&ZAsNq_&syXGLQWm587tkY2UQ{NVbrf#j0zpvBV&97Sj-x< zJl=HV2fM3C{K$}sSnK#jr0%|CzV+O6!r~wx2O%yXTNLKO%c1^RdS{sUS6c*=WUyjp z2!svML}<<2Sq|c`hAJ;iE3N9ZZu`zLPL%mN&BFFZDkH_c;g%z)yh8=EIH{;#m4{yy z5-maot@uWWktjLMGMsktvb4l{HP4iC^a7op6uRJ=z%hkW-}mmGv+KF{G<0Rl%IEDs z>?Bs{^7A?}4FUOsLv<V^i`S%5dr!Q;OvdttO{U4MwcSl}rx_S|&=h&t+4pK{^C6>_ zn%LRy?AYb}xak;lVp=dha*<fJDvkm*{J89ptvHY3rfW4Q-{oD&TFo`KMF=LKzb1%q zT)gj-pgM~#W?()w2e{^P6u2s1LYdv0$p*_L#NKJy_j#x)7V_jH)CFo>-OR=@K~h(G z$pLgp#$9-K8Ot92P5|dmkw4>V7*El-A7f0F*5yvp?4osAW;EC220r&-^6UPZZb{KK z0BLY&)!naJ$^pM}$=H>^VJ012P~*CJV{poxbXp||M*E|-AisVXAfCj5aw(yGMhyWg z9)X_$U!R3cJdV@`u32m0qO#CIhaZLZZR~;bn}li|8K{Oy@`G{DTpzep%^-^EOoD>2 z2=;mOFnV8uF1rs7>WB>o2TV=@g{bl1!${jprhY}Bti8`|opdNTk9}n01UM^<cy)xx z^u`u(A2_k#FVWtU4T(NiJFcHxx()YrMLXrz%EnPwp=5Ve&a!nPr4+fF$PJiK<FVdJ zk`;7(M$yo6!}CR6qrC8<lMNWY_h$J#(NZP)DfC7DW^{!!W0x67FXCbO#~Av_aBalu zg@(9L0D|@8C6K%v{F!qoBymExC_#!VKX@a}Rtk9-vKs|!u{^l5v1D0Fdt40`&?0a| zcd`yAUi;`V&Wri{i9@!ClX}^qa9HvT(ieulLCPqD*Byr#SuMRzws$F~u$B;xvRdHm zk?9_n1VysMRC0o4OS1JB1oy1~9kq}DYBTo9Zn5#<%=!Mvr|-2dfte%(NMY({nmE5M zGH^Y}Rf85(ZwLL_TtrUnzwXSD;GB~}{iZ^s?fXVORzT$xuid0g3mIR)c-s_dKcHZx za$Avu(X0x&g0j^R$ZOTWYkZZSc##`)oJ&^IcT9DTovz3v>_es>hq(s$(|y|m-ap0s zID-)`bgxCh^Va<9{I{^W^U4qL1?3da3d-M=D@v;@vMd6%RV6R2J9q9iMB^JKeEf<$ zu8src`l#aCA*lXpT$-QKb@5n=Gm3pq#P@@)(+vAYj<}aU9`c-HrC|}62L$5IJNJqN zHp+!yizUYT_$iUV5maU6o{UE-z>xG^WFt{;>P)X8kHv~;g<O89QMGrImoX@dq~^SC z1u1FH#8Q=d9+A=bK?qDsM2O{WR%%=jUGFxuIK-??8O~6Baqc>MLWVa$XpKZ~Siik_ zK}KS(s-Ozz&3ouet-iI&z74Vpgq3M0cke7%&3NPJBv5F-7mlyis@kvTvuR9>TI>7K z9bt<Ljes<l273Y?#dLD!9Y2b7*GWm55s;1+*(vap4aS9)5!ONZfpeB{cB*E!p5P#Z z9#so8(;AS=4!Vah{WeE;k$L`Y%wWky<zYWA;0+p1ny7faZ=?rHjhYDUgM!oxL~Y0_ z{y6$wHzjJRdtF?r9>T{&Hl?`X*qAL^ES|x#QQMCOEaB|?mM$U6eSV~>-^S*yLLK^k zsO_Rpyik<3WOaU%8PQQwN5?x8`Vq2S9G-EM!;aZDVVmOnb;CLxB@$X!Kx6b@f<|7I zco`KQz2}a0Wx;P@Fva-gMLd%Vv#ek9ApIE9z*`aIF+uCTtN4<~pDC&m7aDM#^4%c6 zZvLF%)k%fOX=QVc_+!nG+ctWm03r`+5!Y80v!ver7R}jn%DJ{D)nOBiQW!$zvQ>Cj zSRQib(lZa$<uI`Wt5m@1WX0cWvEb}yG>~Zm$-3>=vYQeF%rn4n2;7sYq<_=VX9K5o z8N3z?owTrn3fN~C^_=WY%;e1CM1i`U&Y#D_^^PhmK)Tfo%Od9!og46(UJAwUZF+v- z12sjjE1y^<OL2A&-JSyZ;F$16CDtyafqu<FG}Q9Rh>oXEvj4IfS1tJnX>>Ds=Y6l( zRml*F<FsAdy-|yvS<7x|box$!cO`OKqw#d^oUNCai$4c_Q%WMu)o)YvmZs|c_sD}P zQ1;vPFxW7dZVl16o{nmf=UY@cd1^enpIlh~(fE6he5i;=Vce;m&lA%))%p{V{beO+ zqP6)kTkj^fHKW$=zPuDynp&hscipYZh%Mss5C;hOe00xIdoY9wWIGbxb*#*gJn+?* zH<ucyWU4;Kg*xzz0w0Bx{WTLM&u&~PAe-4wxu~W@5U8%cg;kwPp%csw@tLj@ZEW*0 zjy%|-8o5zFY#UqdiTA}DzF{kE^XTYTW%_d`$MU9&xw+7JJ}f+y7|~20IAj_$GnfdU z{z}Jt?ZmcOX_m6$z?Xu_tL?Epn~ZN(fDMcMCW<0Fqj}#|HOuZph|6-mTKW)jTVM6- znNm=yfS9Y|<ZWC+NQl(#AoSlJ6iuh6regUZ%jvl+VqBi&SZ(>hZNyYODJ?r92psdO zY|F||+K5+ju~fB2VP@21sw&OvbWR=>$rL2!4nB$T>b3dDlkTa*xkLOJ&|Bxqc)e`# za`34<G_6BfK0&x<W@7y?CM1&gF%Wvyf!+!l+DXQ%$`I|HaO|3TjS8^*U1x>ZYo0LZ z6b@Z{s@Qb$fme99SiVt8@hp=VQ1Tp}+>>_MpjAi1N7z<|?mL+=dmIj1Ee;bR^Be}0 zR9MLDx(`7R^(%q2g<Wk=V3v&t>qgNos7J=7KV6M~@JOm&NkBVn!)Em=bZE|hxF^~T zoi#skHl2~5J*lR)=4Ss1ZHVOr3E8=_?&xyRcSilSeamvHvLd}Cx#KmQbYc!^Z5FWg z@jpTx;MJ{rZ#{z-Pb^egt6vKdPMO9)p1FD~qSORjr5F#{&Yedqv$52WWIg4pM0k}Y zY9AH$xIA*r3iq~J=sGF`$5Wj0*F?2k`gWH$YuVKchpg^)Xf1(mF>jYPnsl)|<q$J2 z1=1YJ5lT)|%bc3i?GB&sgS34o1kPVwU`{hhx}k&+6GTWfbGe|~Vd1ATuo0?Zyr600 z)3g)lX^90T%P$Io-RE$jfT`*hrmUW+TYRb%h7gPLI6KCHH*0_9wV--w-Tml7`{4** zNq_zDh|Y!6yQB-yC1LJ3<9>iQu=xZcraB}9yMBlF@yhu5uJL#|(}<=WyaFh;21Sq9 zYE2<W)(gZ|bAmp$y~BVnRw@nuH<GgWzQ+evUip|#he2b_$q03AX9nZA>V3gEe}73( zsQEIqn$Im^iOT`iq1er8sa-jZSd4~BTpr43rXA87!|?dZE_X|h-9zL7yed{F>%;2E zlMQfu0KM7>2gIN1L*J1`Tx|v|QF@@Wk}4K9G<a0&7%L+pj14fL%QiPj3RL$3QB-Mr zi_TF@??j9YKch*$^=7wBY>GYEzQ3*St=3=(TtSr|llx6a4ecHkwa`-Ab6UHVvSoVo z|0agFNX*&xiWWAmA!47s{hso*s&|bS@i5C82j?<&p3Zk-tw_7zTf75dPjRHo6=CQA zTK?YJM<;p}-f+m4+w5?uKLoMydl0n3OX@5~%kyjxG0IQI6S)wuaax#NTX5)9cns&* zUmajNt~FAB1^3NE^j1a_(eUFs<yw)|)Wm*<>}CndpIXQge5fd~c^>ESU11=%%E3>^ z!sg746jA*qrN_1QEsTYHGWza1KiSU8CN$}Owrm19VwjSL1IJ@TvqbvMWjb}{pHwW@ zVZ(SR$f^5^OH0=P;f+e&7Xt9gaP?pW0TXCV`$5!wG}>FQc2VA5QT*ioSr4T10j1CB zd|q#~6`6fm{ac*MDe{Myps#!Wa1a`DYpoPcm!sqX%Is&q*<bO7n0Kj9t{?co^UPHi zb+kIb<wCZ*pftgsFOX8MCponNy0NsvS899Cs7Vg^Y_o^#F0A}n{C04Do;m-b`d2m? zbJcwojYnT4%hxDgX!%5g07Y3#XCYBr{sgr-xmV&e>KTST6}uA3@LV21s?IrCY>D!C z-mhjd*At@UCE08$OBCD(K^B)2f<Jf&E<n4z_oguktO5!#+RxVA=4m^~fQ$%933VLT z4}&m1dv&g`<rxTj=gCRWs`$M%>UNu1;e=IcuItnda*d)6p1Mf(UQ4A?&M^b&6;R;S zkR!*Ciy?Od>B$xAQW^Hz_pkaha&LEo%x+bh=5i+p9dRE_Iq8&NMmcEIaKsdsvmrr0 z0h_Uwo%!6_P?dIpHbO{KHu3O9FX<3R`s$Q6l1pJ3t=VL;(nCB`wey+)5S^3>bPEN* zlI>?3{5Don>3!S8!i->s*MnVWyCwHZO^dkH6|Hn8tY!q_e^?NnB-(X{Kv+5Swd339 z1Fl~$pjdU*lpdkVpW_LA!#Rk}2DuPQ_o4==x%@Ki5u=&aQ%}ID?dRFS#Q`l18~PaP zlBlA0$D_y1k)IYO`Qfcu_(}U&YSQ~Td@(0%Ns8YsQY8i-ILc6r=`r;DY}2!8+QzU2 zy+xkQXI0lU`uZxgC4`#d!Ma(3@Ce<9#Tcule9P`-Q`JWs8y^y_Sl(sG)uwnr29D*F zM)2abgn16aT=VHJ$E)+nfGleC@tc0+=p3z`V=S!`QRRLOfZ`_)n3@%oyyS%<%~2^- z6+7bW=DlxRzt)6j^8LfPuDU?q2Li!d`Cj<vA!&j#R83y1O)wVUv0Q}zm!%-Hqe$1Y zJhst10vrU9!pPSI<w1Bg+YpKQ*=zvaCrC+Y6QO$rye1C^>Y$^Yq7?WRR$FjfZ>Wle zDbb!i7%ixcPbkM94-j(Q@2pJVhu5nia()492gzA!&&GHzGg|wT0rqMe)O^?lHI5E7 z|0o0nDwC(q(Q9wfOJdiulg`?TwdIoq`SMbgAM0Fp2bQAI^U_h2zamCLa};0}2oMJs zzAj$8>1!2st*)hOgb)lsjV8iwnbEU*ZfTjQV~7M%nSBDnw3BU(sTEku6+zVId(fE> zSB$}Xbq1as_VT8A$iw&Ef4z+)2qLS_;jKe$v6?j&IhOToT9w~43KHU{oTH8_P?I8O zyAet~$ZtE!VGk_d=6<^-9ecDkrV}qo8+QW;2~)6{Y)KkO)UQemSV~UnA%LYiCF#|J zDS4`7rz=a^C<NjJ%Dx;x55-l95|^boPu!aMZ?rY+`+?NvG+e0RPsg^N+C${#i;1h7 z_5F41BYbuXt>K{Okh<~zu=k!}O=jEsup(GM#0HEY#V#NqT{@_MfE0m*-b8wnE?rPS zMT#K3cSuO+p@WDN=`EoXk)F^Y^uT}P%$(z#@|&6W+k0K-o05m{?6US=>t6S|7d5ms zib~|Ux2$?i^t?QXOt7uay3igJ4ZaipQPjB!0Z17S+8N#ZP!H(^g8a(`3y=BIeCZUg z<)@EWXWXo^pUa4f2Gz6v;fw%%o^GJEM26spw#sKP(_g}-oxa|PA{DS3p$}dJ3er!M z`E#tw#-%T%eeWo8CczG$;hgSX6y9Yd5;0hwQbRW34l&$|G22?AulKHrE^Sfoa<q+4 ztO33-S-Z<7p{F{N45?f0y?6P!#6fn+P2ICKI2ap+PSO0=kqbY}Aa)t7%v-40Aq7RA zw_9TAE6A~#%qN`oYUb9L=uLo_%&rNgRf>LZp;p(q#9xoF+aHwy>8~om>1d<0Qqk4s zYn1l4^ovQx!rQE?_y-z?IH*Tt*UG8anyN1!LO1fBcQl!F-!Jro5<zaOR3f1BywmH? zXni}|en_C|JE?Yc0LI}8ocr^|Hd`->M{zaxb<Yh%Hl>|2_EK3ifJ4POMsT*!*!u{r zQhRTgwbI&w{wck~unc!bm2A-JNozj21C7>txg}^e!RvYhAkp}e?#~IgZ2DI$Vv5!C z=AXkVMo>+!7BJc~(X!!*ZI}EVX;iy<J?fzCUlzFaB=46-wB4skHPAz*GA{uM?;zID zzA#^JQ#;VU;q#)-h-*4HJ^d+4YAmcB5G5FHK%KH5sCWjvywrAOvJjMZM#?J$2(YB+ zV<z6Z>y#rRZdOY*)q3#^6n?!KcbqoW%TV8cRMHbPLm9_|@Ds)tG}oIYgHOX6+aV}r zF1%2>xQ{ye+zGPPNS`O8IuAP1X^t<NO?E2jC;?oU;wpI<eD*SIO^r*c$$A8J`wqM- zS6{2th5^Nm2mR*EM+_{l?8@(AoVPPv7YbWBF3q@j?^j9x6lxtM4I{PkY^Afz{lF{x zic+1;4CqWYYwUw^FKC{!c4M2|-hI5Vsp6>nNtPAdMP6Yd2U6)#ezM=idmpsoL_qYP zn*2-$?@UQ;g#f9la{kKLR|p{YJrM7;Gbd?TdGL7TAz2K_9n~k>%mS5db%kGO%Tz4$ zKtQF$%OuX5aSnE%yg7&x*&Hc)D>^q|ZZBGKw7BumykaanqfB(lJ7SEQf_Y{V)ajGs zMYb;$4Oze=Ofp`TUkvyB%3#$#Z(Pa2cF%gqQu4tI#4Ef|%o;0T2Fc?2oabL<f+j$+ zY4zha!FSXHbDVEl6hBQ1@-ot=1Ic>;qe`>KGQRlcGY$OdcbW2T_S;hrUO}H8&;>(V zvj9U|em;F?9|dqb<M+MT_QQPF23^%s!b&5F{($F<B63?X`fi=bde?&j_WWV<5*xs1 zN;{O+X82+haq^D9ns^dv0n``ax5zn~kz7fw*5|Pf@>y|;31JWFcEWN3t1QZ8N69L5 zvny|<%N^IQ-JTwPt~<}q1R}BNkE)6VoJEXIT<)Eso9{Txs2DvwmNRPjHDWoF=Q{54 ztZSAUz$-*X-0(w@H6s^_D(`G=A0}uW?K-NTF9dT=t+iR_KF%-s-O$X%XPcTqF%7+! z&AJ;Xo)$Q4qYkpK_*dewX}q`Z0Nrrj+AXOCKL_D>0Gwts`pA)L=T+cv+4$36v-t`R zv3zovnhN}U9n@M0`P^~{7#)^ZT}S=tk6TE199{i}9rW-hc+y;e%m7b8o`RAJz8TgE zA8C3m0yf#Eo;s{p>I!ljs9fi$T0VH@_j+`GH=L2;6bd(e)%}SSHlVNKY56-=sp17t zj{=p3XL;_df6b16WR#Dl(!*(tnxzGCV}_mF0hj*OQYtp!74XvzZ>7ovS&-_GqrGt0 z_9b1metbt5=@8BC#g<Ar`V`LEqwh(8RJuv#uTe?nyEe=^aVddv__!5KEbxghRJQbb z5TS`g13c(ROYihn-HalC8>8OT2I6yihI)$y>4;CjoXN3tw2xe7=8kJano^Ll_SCvt zuAnF<P1e!w%^$9(lYLUm?Sxl2#P?QCQ&X!*H-8=Xg%Kx(OSakH0s;PYdxt@zt^s5D z1Qw`jD`d`M)cF3?eaR>(_(Xsi9qWJ=aNyE{-ngo?*Fj4OTGp}L^oLm%4H+2Yi$2Ss z>ML9R^fo88=yOjQqxlA4Li1vt&OsNQMuqgNWqIw|!W%CG!?{#cFE5g<GxJzH9e}m8 z{s<kKik*t!=YYd?qW}E#YuiEhrJ89a?;X|k4HYtNz6YgI)?@;p1SHf<a69o%?Vi11 z8m+UCi{7os{3e@rZ$Rm<b2Y!f^J4jwvt6dcjG_sUW*4M?Z|Cr=BGC@($%n|2=2=O+ z-B|+I9nufbpJ7^B5YYV2p>1o0%CEC@AT_HT)V|iwl<yyQqE3C8jXjy-nS`u9P3QPb z*;EXD<{cxttPju>Q~MV$^jFsGb|mC^irJ1;ee?Gh2%>|z0&p<3?E{1UBjo|bRwK6` zkRs<^rL*J8B5GK=LGLm_iFwB$5j6JeK0o^7`qDbO*$UIcI`t<QMy0wf((s)?5S*1B zc~Rs=oIzO-74MCIndP|Ssfng-rsN;^q)V7BSJVJmSNkn3_AiO9(=pQrpyobx<wukR zwSE9tBd1Un<ryN8ytQy-Mazht%mP!N_uhohb1cp`*J`GNC>KtKpd*|Xb1ywHUOZLb zzZU=$g>ES?8sI^0&(NkkV}pZx)WCi!LOt4pojOv!P^Auv@YX5g3rc|^EPu^E0KX#Q zExniOb2-HJ4C^6;`(o2lN8Tp_+znS%lf1QK=JMbeCBwVg>j~UQGBrrsta}EDC2PgD zbPCt-luHgtCb(Hm+oBEo+AH2I-Z>Ph|Gr^PM~~?%wv%!xX1xtxQ|k#b@WMyIUOw-% zG(U~9h;ytO`kq(BfRw5A>+=V*lOVUP*}Jz&Es#BstGV{gBy{2P1lyb^z^{;?^E6dz zw^v{SC4&_a#P_dgB5q0Hvm78bNJ_|dO^=*)krx)8+F_lb$-d7z65F>^>HBTx@c=*s ziOE$qRozQN>8V|?iW*08hy7xU1Za{f^+N4fbt#p2zYVkg3<9XX@7hx8SJpwRy>}N5 z41m4`wJ-u;k9=QH)47pOZX5HLgxQas`vY9)@q>*a+678Fu(=A#O1IkM`Zq|z`6Q2> z)+n5mNyBcSp?yV)g1C9e66h{!bS-$FNOjk*m^|0=J20<@>oun&uc_PU8a@DQF(oyt zklb$RFCun%OFKbY4UZEYlv8~8#D!OYPR5O~d%1Vf?=SH+6=B1(hD%U%d|gh-XBePw zb(=sz`rTO-BGY&oa3LWFDR*J)Aoi*e{PuDvQD$NJJ`=q|euRTbij3rF7F)lds%3iQ zTWcx>aswFk<I%P!k6aInX%8WB1)M({Uw+rx>&$laQ+Q)#wbc^P?vQ*X9cB4E;hiw2 z@e?HLlaNuZn9{Kwr7}gp!aYf+R3)e-?x$#5wbJ+{J}~ZDZ`_!NgWFTl;zp$y?i;6L zaOEw;O)OKf1`)r!4T|GU`+iA#hfC6Ty&?RDF4%G3q4P=8aYXcCoy#TdwLYSD_LbO< z(Z)l!dHCCVye_ydN>s*<py%LE;s6_t*W#^i_#R%r_dLG3T63BE2EZop`PUSM=F-2V zZuE8wpP&@Fx|PgGtFsQTo=u~<uOz&%{ytE%Q)I34J@O)OF#)^>j(HT2Nf_eP0W$k1 zmCS64`YVngFTm)z`x;u{c{Qv<&Z6dTz&#hZfUV`9$_vjYY4oCajAK>r*zg?&iDHJ4 z4|h56zSWDEo%P<%&kMMosVK=u#T&@C?}OOC`R#ujbk%jyHP*61TSN?{cNti+gk%8V zr2X{e7iQ_XoZ^7=r?sxNyS6o9dGI{Zu5qqz{aD({q4SA5f7>*laM%&J-k&l=;-bmn zUD-y?mV1^Gz8<b)_|ZN$klseYG8;1dD;GjP_RzZwl$B)jE4esSctahIAb&c@k#g}a znqdtb7%)$G&cl2Ng|;`5vP}rT$Et<&R9UOvxUoj)FVSteVO=^laa@l28RTC3aoy4p zd$_@{(2!>Tb~k0z4YHMsHSXhnGik=RbF+wNX~!VU>PLAO^gT^6n})<9sT2Y006FAy zBz9*CRKg}b$;k*5IoMWGkW%o}PF}BX-0`xmd|irP5Mzzb-OSDwzT|!(&igTZ_%hk~ ziFcUMYUK!jFg4<qcEj^js+?zqpjLCB^+tD4yj@cScFI$SA`e(Q+Ie=f0jQVNE2wCw z8uq0_4IJ%^ftJ=dV0}5AVLKREqTH#@aEN60(m<a%04`NK@CE>zFJ!;iOBsqiS8O9& zXHWkTZ3<WIiiL?S*{p~s;Ve46I`rMGwG8gE#fzvLphY*$zUvr5D#e@`?1>Edy~}?D zBCnrHul7<VkfHXQY+iZAIn8TmHyH;AOilnjxK!*X%5uhrJKD7l)IF>+PejK+@hpA2 z7W)iwyOWRdiqtWqL>*58dU_+d`?R*6m#bW3P^L|^j6f)?b*fCIjo`4muQTZL)V*76 zwB0-_fo5d6wq(#G4oenvk|=cTAq0fTXeXOV$8t~5GU#}W&wk|Hi6_0dp*PmNIY<yN z^il4ygxoc0!`fri6YX1a#@ic!K8?ZvTiEcom-KY>d^qS@C6>rxR6R-yHWfv1LHBra zdgO)Cx!&b?2b1lM`+}V@T9yJHuzt{eI8+dyTRUssQTSrzsY4rA(?f&@PUs7;ykAC+ z^oY163ES#!cA4JJnWRAJf%N$krCRcDFuPYe{ZuBM``j9Rg#(FYY~4nsbf=UPA77Ez z?t6f;VSU54h$RUEni<2~Zj&3>2~-Q~F0H=D6`1M9gS;Pve7(JD@xWOEaDGfdEpgc_ z0f3R`NOw!$=;6Qq4wsrmXR7BzX1;=cP}eRa<Ie2wZ*pB~dU==)H1}#Lt_ELkxHP&j z)KI!FW;1fn0f4mTtNFlD-g@q~8{4!_an=e^FV^FS1s#D!$>l9*$o+AFpkq2H<l{`G zc<Q`yEB@|1k(l9rH%FyMhqv|iqO4XPq>nt4gH*+r!122l0RBYm;-ez{bza3=b)Xfm z2)-ldX-%}LzgQlRg(M$R$F3nllC99iFD<Mly<GRifU6k;VXDe{IO2VUOP_2TJ9#b6 zH${JhGn7!x!|XuMMr(!UkLUr%1Uypvosq2J>*|f6*0xxP!?@;ABPaN{S9gEmNN129 zp6W@d4StU05FdRk;ge?n5!7FE&uWd2m|+g(JC={l(1fr1@yFhM@qNG9J|OD5D>&>j zG-l-5t}Rd>MyR}G+C*fhQ+=-xJ~y1&LY3`J#o!l27tq5{sHdSC!B7w`0Tflh-d~!H z+6rBeiy@`%E0l^jRdK1El83bolh!ot9G2U3NF^v<6cV(2aRrfSQq5xca#${_f_ey0 zk)JL#q8Yku)L#INC(2j#Hg^?wA7KN;5BAo~%!ky;^M_JbP-soc-WNp1d_!r-k<KJ> z@%f4d)E?~l2uJ1oWreiOZFXgxaOvkZCzcFy)K1g71sp+Rlf-(&`pGBd<BN95cIGDm zjkd_7Q~;Ys@7q@_bmDgCBJ?z?212-U)Zr7z0Jo68&jh?!qkhEQzJf|LRNG%ybkIm= zIA<AOdqAwvtZ;m*=U?lI{+#Eos7kf~^3K7T-HxEg10~x-0>gtODze8;<IS92`H%@h zxr28evap5K@a`~WFK#Vs&JkG%>Pk2+)j=0d_*E|Kn*m9g<+vAdvuim7x1G5mdIu*x zpz{@wpu0YMLYwbC`&k6&-`rhQEGCpRKrW|{)*0@*IJI{sV@mnw++*^}4}7V{ekUO- zG)Q}0+icXS*U8#qx@|KJ>%Ul$xm}J6b~LgUe20VZkFJ+?=Q>epvNx9#UUfGoCCzS@ z(tRtSyg8>e1qgv3xCTM_qwQhs*cnwZiQ2EDLdS*excWOmmew-aZt$qUd)Vf6LNm;+ zK0yE6`U@@k3P|%~r{k!2H=KDlim*_rK9HKgpgwPu;!~nhGJw<T??j&rb4O?oU!=A2 z1o4Lgw#Hp1ZV#|{FX^hT2GW~h%F5;gJUe+hMZ&uYTYv~<g&K9%78bH1q8W{XxCUAT zd4|w8yj40JGNI7^$NTG9$I_19;e9*~_3RkgF{CV&asmP@o&3*J$B&(0JYO|)T3B{V zW7hTlg&BaV=u~69h^gC~3&Z_@Wfc0pLPuC-xDOPt=98K6kv<3M%2>e2qVIyMg^aFE zw^LwTYj0De5h_<2&q-4vwg6i**=|RB4fv2v{6pg#dI)o3ZaB&tkMO1?>dLox%vJBq zZ_E{0HWsU9R(&j3A{sr!^V@y@l5fc3U3=Mk&+gI{P4@bvOtIY#>uONF%-%{Po+}Kh z16<vmi<YLi7!JLVGA2hmSr)fpZd2so^^64&4va*px+Gd$fwDKiW+#)+zSe%X{lEa5 z6<B8R0nmhP3tpXPA~fhK$ha(2IAeGlJh(bfdtx|2lCfWC4(WSeu7^Q;pQ#bBCQU{R zm7A+Z&ACep3IUrk#>{<$b9d+30RYO%;$l{+E;fY?Q@=Jr3SV~0O4F~|u}J(-K>UoY zb{B8}9fu<`;?8>y0`$eFhx{;y$?*ap=(R$5v%oQ<EqM-|iMd|&!N5}F@emubwc5*^ z9A!b3a}|T~$mxfr+tQER?L&^rN#gzJEtEC(&ZaRS-<c8bVzB1oBN@-*@2l7x`WL!W zhq8Y<xvaL-4^{730?NE*1hLwEvtzh$4O|}2)J~1lSVy7qE;dlbS|r*-9`z!QKs2mZ zCrx?oJIA5`y+D#oW|#R{>AjH?{MlJ{<Q?1WN48P~6tb4F-~gbz7t{4MA)v7mA&D!I z8}V=)Q@yL4qAl=Pdo6hKKpq@c=QEYOdA8c2!!4O8glZPLUN{on)1a<z9B`IU!%M1N z!kDp>Yp&<_v1~cB<CulHCapPuC3f!=rB8@c0&JmZxTlaWzp2dk8#%55!EZ@dq6W9# z2*h&3#K&r&1G-59y8_oa24HKUi_x2%x7_j~5m=2K1gtZXUCY+*k#-4;()0kYfAB6c zZSG<-q1M4;^=p=46vSDqcDuc?PAZmT!+2}<xOU0q-SYmOfx!2NaMu=nionKf_cc_> ztM@T$B!9@$G+#0B7N+h|@CY6R+ATKM*KOaRKNr`)b5%;?+~lhZ*QPxqR)+KY);Rn3 zS_U&vm_gfU8A7D@cB}s9Zz1llGTjQEL=8vjMh+%8=|rtnun-Sn)kk4SQyK^2NXH$0 z=j1LuUJzS#JRb(BoBJkSn|%O{c~7Y@=sM|khc`<ZW&j^{gK1e_7PNmRU0sCJDHiV( z!q2})2UDP6jM|c#+KOV^*B!o`lX*rqxpzF4mk5&a#?E8q8`IwG>H@S$jw;rzpDKdc znKq}*1)_(4d4-7QS?E^VX|hk^DQN`CMv~8iN7#&}oGvqYZH-IHCwZU5F<9p10eQ2| z*W-^%3q%xz-PUK9g+2&}-|xKO7LtU0OK}wzh5agd><(&HBnxUZDjlr0B;>KR%zRO( zPSM2w41>kSKpLv#Nl-R83-$$dyu$(1wx1i>I$BHS+(7;EFwe`4QmXD^@Y*`}#74T; z!pnjk&3*`n*yxqrp-Y!O2kxPs_dB*|clcC)lwRz41=^^*3IgVdAsp^}RXiTgZ;)-? zX936VYUFGrcpnR}*8>N<rDKh5#d;QVIx$ml>Z59^L_r%dA2~|H*vkf342-+dh}yjc zTcgjF30BP=3A%ZD)osa=GOuafM#eYFhp!jM#z2qo=gg*CRT_j;3O{Do9>UDoJGq|~ z@Yap*r`6fKZk*M7VmH734BrXH7Y2>8J;D8HB&XyShq(a+sfn-}va2LxABD*)P#;{U zJ+rr63p@hM2&kqUXB(BY1N`N(G@eBGZXZCKZcR`{3Hy+cn<l!NgjVEP$FwjU0O^|( z!!F9avoWUoF>5x}W}jc*TI4^h=zKBGWz*wfLm12=@OAINzHWdD!;jC=1hQuUjIoRR zz?F+^>H&h;uY`^EFSfJk7i|Ev>EVE<zh^zDZ#O(6IC*wA>=4>J?_*D@y%4okaekHp zrIp1dv-05Pl!i242hXq0Nimr=;eoz)C$Xo{SpHBn%lI^uA^<mC8JbECN{a`WCpr|X z^8mu<Cz<uwRgm1g0g-oK$D)BNC(dnC6p%PIr7ivF)E_Zp5@e7wASPNYhTXFmc5W~W zWmN-M9Qt+{JwUipE6dSz@L{_2g!!UqWbeYg11^rUrdct9Yp<9eZXrD{iL&;=jA^yx z03DrK1G{PMvM!5HhTfr`Eb^Hz1_wz^##ZhPYnPGbr@?RnMm4(<C8gxzWe(uPR9`J@ zSMB|G72&OD%GA#ZL#Q&LBklS}-H12UZ{$}mUjm+9v5*)aC*<-p8+kO&;n{=CSUp4* zZVM&FZVmT?)HWw)%0vz^m$E(p(x@~713SameN0=tVDIEEl4et|U~S^u9}bDzYRMx6 zTR)@>a;`Xdr+S{IL@x{5U}MnEpr12S!L<rbcnTa}B-G-=4b5O1F;16gO#wbUU8z^v zu2lcgKFbgDR<>b_ycKicK9Vuv%T2>BgS_W(Lf@>A1wbF$K_GgOFHtSn08WDoaN=v* zXok^`g1L-H_JX7Q4EdrnBx0~C2rYFHAW=rx$j%mfuzkXbdb9*T)V=JXL{6C-!8))5 zO8rUu_YcAfv|uwDMnN6p3g2NA!SK?z6ex?E<SXS(Z`v%}xNs&NV!OF!f?iQ$bSZQE zBl$NUX421FpZQ$Ff}~h!WVAw~96E|+B<2{|>^h1qcL6GL;!5|{d>HP+gqXgYZO^v! z1i7e^*$^PM3ef_wIYv!<s&W?Y4kQa{d26u(*5R-|>t3ie2%R|JvQ^e=p^ajF5Hk8J z$nTQK&vQp8V8ne18$@ST&_Ib|r8lA1{#eazcG#v$-(y9p3qAzjU>`RcQ8-PP@^Z#7 zfFTl%FGE_goXlO@e|GD7?&j=L3qafwR~VXXt(^`zl)j&11YT!<I9~GEqF)oA#~3|| zSIHQ18EUF_H{CePagDHySdzB$rsx3ma><bILpouoh%hfu&wFD)reZ6X=KJ~yty^RX zla4D;_F^^F?o-;gJ^>t?RKF%J4ggjG3U}F>l_{kpPa_V12zNa}-=V$4m+89Yvv{is zR6S)V4c)i*aY}RnXmonbu*yP{if3uk=Czj+h9ujc&-=CCaA5V!Vf@9BTCM-Qi>Z$8 z%v5cN0i081P3VV3ikA*=m(eIppuYzkYVJItNY@4;Bhp*4;?1K>nHEHm9Q-ey-Y@Wo zQeWt4+Glsn0vMc|D_mvQAWFyis{p6qo1YdHk&q|QtU>ESCvYA1Eu$hY&(^LK;)537 zw~NZZe}A0IJFfVS6CS}Ft!)=-$*{M$fG$jBt&rCQmvd#lJdOL_pey{l6!oIblTwS- zey^^j^R>56XBo*4U+QO%P0&LO;iivxhO7l%7IxIQk)6Jj*lipmp77a$d}vN*9vNtb zZh4l{FdE>dgq(m(jK175iL;J$dh!sT!Qm&iGdpp&3bt7$6`_kuk&v{8z_P7CeoS+; zdvfP}`Py!$_Za!c*QW7geIicXFngH6Hpx;Grlb1RR1Mo1t80=HGUN?=bFf?=^0fU> zPb|$k&=TFp@n95<=Rt<`!Qcs~uBM%Mo{L(T4Ug@q`GE$Y<Hk}ISKZ~{Go2*|^>lW8 zpL1UMz>%XDKqiUv<y<TSFzRl1LV5SfT<K-_%FbSn6Xa3_;_IyMRJf8D^#W4@AjCSy zed#R6sOpXnrrvb{{mtiFlXvUhc2}VWo(ATNO@P{<)80lh7l?ZgcXel|t=T}l_*0~j zo%a%PaNDK1Kn$N#LAOiapAg~|NL-DobvyytE61=cZa!;JPj4n`X{W5XxV_<ftAELb z>>fnviP@7!Du|sN<IiQOjWK<X!u3HJW=OJBb!?~-;N&!{r{-V|Rg;kUdR_YJXqoJ( zK@j41Gf_Lvc@_XY%IYHjTa>qX^P#}4z~~~5>w!p97Jd<wHZOq$q2V!2y3mVOkm>?0 zA3`8E6U@a?UWa*rVM$E=uGSUaT3$Z`wwcG?dHn}Y867ds3g}0lPTh(>OLlQ#IbFu@ zdgr}~^?|jZF;x&T^wONSLOV9B7b3pZ+>hQTP`AruM7yF(n-&33dfQBs;1f%B;sC$X z1cYeLq45=UP%<v3YMZ^SfYiB(#M_VEK<dJzkEycP^TNoi9Z34n`$eu%6Dco?d1y$d zoy5%{^lOP}aYrsPD_G2F_luknUC%e;I(B?r00`yTtZON~Tj?lS>Z4oIG)0fS3@tdj z)DU`wmaMvF5l=Z~%y$x?5dPI)gd!oItQnj^ibIkuqh$2hN2j{B%V43*zLNkFac0a% z1IxWJS@#B~p?*%i65a>Yu{|LWQP#^j9F^hkGM|{qQA1f_A2C$J{x$`l_4WM-j+;5} zY{q(oriVBVh;9hi+uzLObq4x^;Y(?(=9c#zZf1v?MN;2!p2;%@x}O)FwfB3K<m(rD zbw_N@b|i?+mFs3k!kKg5<Lp03DvIwd8<j=#TSFhU7bGvJP_PXtTF32Ak?-9U<m+3O z^Ts$9eDOjVXLbhqCLsV+e%sfLQ@ts(10qyg-t;0T<dwQUM7iST!{HLk{#=ga@BF~v zDu<{w`rA+JoK|lcqC&A&<)#`b3Xqvqua`>f>W6^#Og};E$!a8Mm4nR4x<2D-eaEzC zO~LS;S8S_piz9-r?m0Ql?=F^~9sPy1RE`>Ln;p>!lDeFxpCF?=pnLtkNfQH)_RF>Y z-j1afw!Aho2K0gxpMGb;z6hZrdQ5MPkm?EuI{w2~@#(2TOeM`OYwe}a9{1E99KFO& zZu0)U>p1__j3YgOp31-zamx0#i+1H<p8ULfm(aJD`{N4rvE4vdm#Q*q!&K-nMvPl2 zqj3RTT+VqfEzvNTrBy_Y=O%f@L``3k!j{%JCgnkZRBQ1Cmgb?HUK|Jlvb$D6J(Dq` z$K9IJ-eYI(F`smzJZ+<<$1~x%(EyZ{cW28EvsS*5=SuU_3K>Ujs`;>Moq5)CT$bNI zQhtlqtYA=?bI8<nKvUp&lE=acmsSFC7<AVcuBOWgrA;W{DQo#7$E}LmQQhAYy>@c) zpVXMWTzlvp<zxdQ6d9Q$HdDvt8b!%B2WCYe(=$~qoieqLAQ5$j>+OU-IBpVe>&Vjp z1Qr>Q6}zS1d?`ApNHU0@$B;5X73^scO@-X!!oec;p@Wur)vJ0lUHg4rheam!((F37 zMAh*H0gP^Dck@Jx=o-N=Dt(u&+soB@0ajR2hizdX@S<jqpIV^RUaUQ+Np`|hF0?Ke zsO>6>p4YSgdZQpkmmv*Dwg5EZ-f8uBXq?hh^8*EsumOeKXM$voQHlG^@Gnj$lsk-h zY{7m>W&x0%b&Gqg^Kh@8UBU(d9x<tzL8@k#Ln9063;PblZ=(s<YE|%`?ZLx^B;=d8 zC0~x38zpYm{ms_nE`Y9kQoF2VqGv1mqLso#MWN#Fb+(7ZpRO`eSjN7B&)Jf@nOJ|X z?FLS-n4&0p?;VvYjKZ5FZ@pesIhJYl9M{=KmC+-yOD<037#a35SU?2eIMHtCq|(cw zDc~3u^^?5jo)=%o3gk^oyAiHy<<P4w?37iTp{xe4&O@N==V=^@Dt#+J3Mu~E^t4}{ zLOXl?SZFMt_87-i0?>E@o{Zx2YipZ^TR`)(gkmwF6?<>gQN*+(e%XH(L|mdXTq!hX zLE&NVbGP;^)vOLl%ZLc`w*&f1yfxGDnz7YAT{Dj>smYC4b8|6jC5pH!917Kfb1!x$ z!<Fj&k2}8d0y>5DYZbmh4K#f3LmL?@(e7I(nA%ReAM{{j`7m3xcO~@ax3y+G2+Wz# zVzlbWJ2n*MG+J<PH^ozXP6gELZ<|#*8ojMC$_2WJ<JcnqflN7i{*9?~R@U?G(F?6? z$(hyS#Nz1yEa(~Fi!p_j5>l$@TS>ueOS}U))l4%}-fyt<>hD4e`2LrbNLdao#BHE1 zES|Pl;Utjj9c3Q=+A!^$841bG7&l1H=QkYD&E8tD6;P-;xd~iUQd+gKAT^MoGk4^= z-tK2$8M}D~@BdHomc#^2cIJ)%fyAi4AIlbTx)ciXCnlD;1=0f#-YzwPA`CMRl6W3` zt&*TA1{*2}Ud!naEM6ow4fU%^xI|i^2B*h$+z2>o$CA5+i4KzsKQ@XV`B@+{0<%*x zW%DIWhy>kOz(+|JHKG(PoFsjqC5>E0jqZ|B4iu>hBfwjn4HNG9c8VlO+hAv1;1&H< z@wfacF1u_4V>O=cy@kfD%a;qDkEbTnXN7!@)SrJ1+)n-<01EZTgqjq_Qz-tABCS$C zF=%`&;NGg1!2s_H9JRxZrx`UtjG7`WzJZ*?XXu(7U#(n+40{@~!ot;)wOsTL#Zl5# zisv0|wjX)PW&r!tANYZ)=a~nFK5p<2$KFWhAOT{<zR$Oil^a|Dfj7eNp8jzl-wpv5 zxLO7s*7?oGP(<AzyfE-&vV~b(h@kS{1c>{Kx}5C<$}8t6vf0vDR5O1mc{dRSn#;<t z3sz3>R)MzLbyAhVi{I45ZVkk?jDD88^3Ph(uIr?*f_;V2&6{XjAOSFz&9*fa#c8d+ zh!ffh<g95fo2!UD6AEM{S>|un#A0HcqyUh0640v0J~)`Hm{c7*N=lan-m$S<e~|Up zTSSF3RBa_{hdOV994B-8hBh<sXU+9%t)BRQR51YU3rxO$d8=8ZW18Uf7*wRIVo8iJ z?|8t};%)$lP&^vQ7jvdbVbU+br2Ih1QK^X^HS|&Jbx8dwRHlVXKKD<j1TTw^MXtAM zr<m4~9|djWA{-1l1{mdNZlPSjz)HiJc!4FhH6Sc~TmZtszJbVz)~im;ZbAY&HU~An zUJh(EW3qduG&AjjMfW%}0Y=w&D05-Aq>noD!)^2FBliFruZE@7Il6V{k<qO4hb<sJ z3-TvwX8dX}FM?k?+Pw8e9Lj5J7Mp$OCOhRMI96cP^ey!Z>zs}uhWscg>(%5q2auO? zSOWnHDf^U%bq%Nl0=rJW2naIA0mjUw(KZ!p+IdIhF37Gws72~}Q+wEI7F><IL;6Se z5wjyeRszI65(ezy8@XO=Fh*G?IlvgcZDGo(xoR|E*$kQ`y|*dO#iU}JL5l8bpB6$m zlpvk_5SSRntI2rH=nJY>UP|<y1AEVLgHQY24?~rJkva-gRw!AJON}bdospMANdP#q zx5jHQ^|a(qyZ#?915|!s)%(@}X_OxC^~eCd9YB8v>iR^{i!ZFZQ~jP)7reuLbt+0B z$puV%-RI$i&&$$ykG03UFZ|rpb)C?~c{lNZC_#PkTYSv>c0ry&e=cz5JCdD5@sE0T zMqc}|LA#PkEJoW<r!mQFDn5SjutLxTnp#7Jz<%}D9fcd7$mRNnI>SFtoFgP9Bta?r z3f1B5v3&5a>&no|&F@J$CyGGS)*GlPLiOOB*(#V19;%}qF)@r)_rVBte57fuO2p~< zGJL9A+eZx&e|?3Q5<;YX4W)}>w&ccOAD*m;wHUj|Uy^ODCQph(w(gs8;@YWWqn9G7 zeNDg!{%Kdg|4TtJnGSb?Vw44BE%+?nE`us-j&=4R`wFn?eu!dzOHA$)8zy?cBP1E; zfOj#`ELg<bbin~6qZ`?}W_=Mmb;%g4kn_A*U(m7NLx~=?<Vx-+cJ0V9>N|C@EX9MM zH_iFBoR#+bx|TqEUtr#u4WX;DGFrI8;9_;|KTHr96fDInUxS5j`Yz~PkXEsjsit<K z25d%pzEz3C1t+Ke7XMYSsLRN24qV>m<h&II_(S>qRH?u=IX1+@H|ge#y;gjO-z`CX zD*L<r%J&9HabrWC1ssYf8@%bG5K(z3t`8(kf(|=ceO99s2D3zo@9g;liQ~5oMVBYR z*GGkc=sRIyyM6pPSED-?&*Xfdx=~qmIgMojgEvW8Z+#Lhvd7#x1%4&_=HY4)fnr)a z*zPK$tLl6V%(-m8j&zEqi;@LrpUy?&RGQv(^M>9hlz&AFD?A!Br)g5^`}APjX7H2n zOVG~s=N<Fw+VN`nSY$KZA*&(L2K&~ttGRyDu$j=r6h-RvT^^Pxz(bJ39Fk*p+}G}Y z8iGOa$;KGWYfX8(=Xa?{&o-Y2yIJb<kwfR`>*18aHzq;F%V#DMz;u|Ta0(YK9(;ay zM*3Ks+nUQt`i#MVRfMv`b-LVOg-R(Ha8lJI&&3TrghfXSyC6TPg6@T?)!QDWj|q#0 zQ$M@tzuP2CIXM=00v;L#`H9|{j3c8n&PO}rlab!Sj<d=H;kT5(y7$Qi6xj`!!?Vg_ zQMt2J{31rj(2v@;av*LhuJ~*40T5{Qr6H%0(S>AO@?DBF9}~`{|4;jek6cZaXu!8q z*zwbzSF4mrJB@-hlmh*kfbr;!h2dVZ=Os)Tx#B;zN%&diRs?QC_e#G72YC_7W;8ZZ z%VGYDR3J?ZXk7IgQQ{23bB{JI*H<CmD~(?*kwAYR<j(_7K6gq@n`m$?)l~JTA7|6~ zNM8c3zc1He0?Oqf@TnA9BiZ35S^25It*r4;C&>(N@(WPaCv1qFnmU)@5%+dyuFM=L zkdD1e8o$dh_e-lwtK<9$%pX$$zs+0)8LY%BzXGl4{_8FG<FCV%lDk1xfz7J4fIQZ5 zw#z43mcWqo>!BoZfkvb7+1qaMl5en3%OPLnj#rAeMM?>1lsH~{v#OGO$PuCi59C^C z1?C$IZrGgU%?#tKHQJfRO8JaG&Feo-74&W81;Q(|S%=V1dsi7j5(Fw&5>jn!&<TEW zpgABojIBS<9P-e9`x0frqr=tFk=aW|J9^L{zokfYS-*zqPs8|qCVl*A%<d3gQ7$yE z{$!<QtRz8V126`2?f#xMC_>AR{dKoOKJV?{Us1x9tdG3D6zgRPI!|8a$fnb_Aoy+B z%mw~S?aOZwrq2tXMh)>_YBNs%!Jkqg=LO{opAK(E9`F6VZ2AcrcCAC3r6<!tdma`k z=*y{3gLCq?^_2Db(e6sK@Jse*Eo`k9pgDFKmQO|U9I~;uh;Xh?%cF%{^HQjz|G2dN zxu25zNE9->^80MssDA#Gn=JNmJ|wQ%YOpZBAs@ID(ntFrpV9lLyYx{-!UqG0?_5?~ z`knNu<CVrnLvdCVzfbf}CyIn1DV+n+K(H$M*3X~zo#f2{#gV<}mdJ`P5VoflkjHhT zph53%Yb-$|ReOTKETe_Iz&T2%K!Mm++p>vKdIOs9{`BR)O@e}Cat?-y-_Ns^|0i`1 zWm%t_^j3)L+^vWA+V4yI*Z$QEiaz;j&#C^0KlYzq;HRIdNr2)SvQSOZe~jh#mHM;q zk~oB1w}Ybplhys-14t$TnP0Y-ZCC%^AP@K9Du^)JZNFUnX#{`vp1+NU`YiK+*3oA) zK7Vz5|9CD`A54+gwank_;IFO7kB75g0uP&<x#;BITuyM)?13p-qvs;~`^!QGVngVW z7^%NFFF(!XBrWqmEcOk{-(MD1X5fC4G~fCAdx5zYOwpe7<G;Tw%16Qeqn0o^_tOae z;+VIAzd9}Ol>F~63;7`sd4xOg@9%~GndrYCg#WT~|1;5loH_q(uKs7De`a|;|1;75 z`}Y0cE&8Wc27=@N-J<{P2>Qnz`~SCFl$jjWaj@S?ekb_<gKxxq_Rz&1^Go@cJL@m} zmzw5{F9L5hknuUt#O^OV|F4hq0l*wl^6k>!cLk4u-?WzSpb7d5JN}Q4C?E2#Futb$ z`Y!)i{Qvp^<!$hrM^`j?|NiSDfGem)k@(NB?a%+(udl(IETNF^G{{}lA#KV3%_86I z?>lyx9Qh+{0tc0kPm*fK?WBC#_$gHXQ%m^q7L<omW=PZ&`r8Bl)Crt@!Ha+2?N+%0 z_9S(sPsE>HivJ$7Fb}kF{k3uZywaiMz~{~xpv?XI;o^}xXz;(k#-F{oj~Z~e6#zHu z#^0ZvlPLSse{~1_^SSi<AlhSfKF0R<BNB=H2%|r}d;e`5%+z4@RSW1fPW|m&3l`(@ zv7guHudQef2!&(cjeY`ols{Vipa1u))zmM4wr2mn%8w57(Q5O@p?^O<d}+Ye^mktp zT;HsR3DTzm`RD)s>;O|d{%?!)r_1_3Q~bZC=zpg8Urg&aQ&b*`&{*v~zgBBo!%N|< z^Gr*?)*5P6o@zb}Lzs8xO4U)s7CjFx_iVAAUeL6Sxgj4FO@L~5o*r4f+goDP?>kfX z`tNRZ2`+H#1a`}f7q8K@^Mvfp7_N29yR^;Qn#6IwlE72Rd%w|DCDZ9&Dz2F;pQbU{ zwW)4zVavTNJbIwL>?ioNZQNw8Fhg@9V9CjEaFRC8gwj+3@rgdE)&`m2p|EgdX7C#} z$&7!gu$<?07I--cJCb-K?rJ^Psfgz<J=-qRA6Nvkqwem={Kq`yf7ncqlE5Ldz7Xz< znQiCHj61RKSsSy1%^_1@5_pp5<Q6mGM#;}G<k<X+=gyXD`?|rtomB5Wv1|9cG}pYT z&y4ts4jt~5?;0U#D8Zrm%CBAp6TckRD_*?%Nib8|95kVy2F3cfU*EBdy`Xrn?lhyw zJv)yFLCTLx2fiUFH)wWi{D-6?1OM7tbo~^#wZddqmOpTgJZn^9QV-*+%I}mi!6R#1 zqx+H94t^Qdf+x)S)HCreXq6Umx~`L+OmJncdU(VqjGUL?@SY@|ym*M&)YFcd6!M9W z-A7p<|B%!_Kqx#uv~m4}btjM@yi_C3J`BZrHthZc`v3RCIobRWyDB7#c<pHLtonVp zp^dK~)owan)$gcNVDuG}QqZ2A6VO37y#LJo?4vjdRq>|FpyAqE!;XqYHZpPQs|pY1 z#;V<m>Gs_QB`j5<`P_`!!i!Og^hL1Ke+jYnxyb=Mq<3dtvM^OY<M7z?4MW=L<P!#V zgZY^{h<BX@*@`vl-ixn}57?c~J(s=FHJHjo6LVqT&SQRY#5CNq-G+XVQ5eZZS>-bH z%i84oTSfdi)^ODHs;GYqKl|{rAfqEB5$Cfv93Fedk6<1zcwF39Gb`$P`XI%EfMz#8 z<<&^0jlW>#RNAdI_u-E+5dZZeOosq(Kx^}Tsh&6ov3uSL&v>}&$D-$>*ibs%#ri4L z=Dn#w+dI2??IV5;7qGOT(#svAJ>6-f)RAa+cd~daD=q<p6$G6)EJ<Oaj+sW8Sccuk z#STWx4c@3lFU|R~M&4zdDU$mF*7d4$xXpriOD@`Q0ZhTe<nttGui%`J5A-{I9=HqA zK@?v*qPfz<;Ob$U6~kt)vMn<Ba|S|jhg(5`8&FH)5kr_JcCC}38ODFGpMU$A4>#DQ z>=&$ZOm|1xOxrWUIb`D(fQeh38GM>NSJJVhP>ES6%+#)WR<MmN)sJ;Ct+i~d<^s%m z9-jD{psMGbEwQ$=&8bt*Xnz;&txz--8xzdt%qwH<;CSyKH-?A#LGsW8Q&R1qD+951 zkJhdl{}>NjmKM9yvBZp%rWRf(GIc~hP}L5ie1~xo#qWv<48-zs(!XdgMcR$)3U{h( z4Z8k|yy1Ucs*fsQsaW`0@8#id>RU3p?{*K|a5U?!8}ayrFl0j%T=;T&@7dU`>2U!j zZsEMzz&Vs#X*7$)adw{8&$n7*rp{*>KFpLT3FtV9I&al3lIj}4?_(j})Q9e+GLO6( zJr`gMzXB9LoYoiS#j;zg=G|TY*g5CFPI}7?u<dmUZzL`WhQwRz?M|Wx${7pL%tZ$# zC%y#B4}Y?y^|mze<{9?5w5K{(ta7VJdY7+_oPB6g2W{LM8FRqCmlQ)F+~M)=7AYE% zgU6K!?}65S!W|Z@#EC7Q)7G;Ju@_mmA5ot>E_KoA94QNl@5hukS4of>-WyH@_^Y!o z4N}f1WXOhpC(lk!KB>Y0Ti45|zgruh|F*pR>w;?Z_7~c*$;tTea2M{)yd`}36ft;B zFsELYdQ#LLmyc?EWk1<=jwLKBUyT*2S*iH`f$dD@qkxX!1VPplB^H&pIub)2Xgg(n zqJ32~;9c5<5-eXMM5ETOel}AGG{DDDm=>FsD+MQra%6QJ+;Fv@{_t5#Lc5x3+hK~z zh@kEdBX7Wo3NQD2J?U?`l%VQu+Ct00DVXZ8l^~v`W(8TA*uStlnZ41Gbk9Yn4=X9T zuuc0_RZY&kQ=FA`;zUP?xg|DFG~hwj8QEZ~!IIj|9u;nH*{=!LO0hF%<xbQXF-AC- z44;sS!eF^1**kg$89UxTyxq%a74%R8Q#cvwJj#kXu9bmaNX6-0IDcLQ6tdKOc<xwv z^H;jspWdqyx4}jF;E8+y7qJ${7Dr|6TxH)`mbhv#+7Uc+-+XUvHfgo5_-OEh-O(}q zMdT_~h{HAS9i8diq=oLxGe|c31dlG~H)Ayxo2;@rTu{uC{t{Y=Z$t-~=!l@@6!=(H zH&I3Ips3FS$wVfy(uQ%qh*v#9W>0&Tdfr+K5d&4l2u^Occ2KWw_?A{R!u(H--kM8+ zP<zD}m{d3;%u{_6HWYNT<W@B1ptW&dX!0z?7wj&-HNb!9mASqWWw+Y9VQjBM;ay=3 zkyk?}$pqpyWgSswr8Y-PK=<wBJrrJ`Jl<ZjLr(-Rbknw@DtKn!X~|M$cZi;$Gp8}| zEA3P(B;2?=Q>qZZ#nJDTQSxnKA!0GqSpe0alxHb3{5JiSck^tL%-0=HJOh?G&HW1d zMGw`%3VVG*q)N4#Q<^3}!^m~cb}z{=&Rh7S-zPslTZ?y+@ry5#GA`nu+l8&J2qVVU zET}RJCwzid8(L%w?fd2W-QNbk!yMQw!n;*)QF%)*E<qw(+g7`SXZm(mpHz~Hn&Ac{ zEiV_o$oHr<e%o)(=WNygP&Cv^G{^T#Aiw7=apHDw#Mz1v7j?fN%Sa@P=y%3dO+Kym zPM7x(%}EPl@JdzSw-BH(O>|+TrOi6Fv4j;jYu?&=5%L^P-UfTl_Mhk#|G_s1eF6fJ z3X!)fwbyZV_nm}l^aMm5=X&@QVm%>N5XnzUjC+ss<SxB!$ZpBUxhckl`;pgr78xSd zU8s-5uQWg2<~L8NI>3G+@mAvZl8c2@swc^lQ76Uh7hC3cy49^}5ikeEm(t0uP-w*w zJoT^SbLlp=2qG$G)_9WEE-{xV?noq@=~1PaC?}Kk)Xj5wfJI+~_$g9L?@czH`%aTv zjBFTCH*7au@=~M{qO-2^61xjaFv=F_a@Dh4$+Y}rPe?>-m2I`Z3_|lko)^iyO6*Aq zUb6TiT+F<YytIE!_Cz>|E^IK*ZmMzhvnc0W)0#lX1xOBGAHpi7aMg9@Ny?f2rW}ct zTk>{S*C~G20+6}4FS;Fj4@#jcj-8Zxf+fRPW8YTy7~OZV3~%ykNTr^d2)=gt!8m>c z5s{T8KGluAX9g)MS+`twNmeAw7uVf*JC8=vwq%kF9c+fsR$*-08eHCAv71`^v2qj# zGO*9tL+?AMTBG82VY(3Y5?j{voPD6)r9iEnu*kx~vb7|-D++hb8N#K@|Fxw|j{*qB z30KQ(^u2|Pv%DP{4KoJp&jpky#>%Z(yEZI}`qS@sliTQ$*n|LiNZn<$$w8RU+CIZ_ z2}FiEv@J=v_H^`4XL4ghge~(i?W#q-M)i~PUxkyxLTJ2yQG27NoBYftY_*0aojoz{ zg>wHIXeR1vl~q;pjdIZIS!I8`55I_KKaaf<I@8B_b2f^u4oWCmW@6t~j<nL8w?i15 ziT%R=A#2GC!{w2~`dUn=ytI?Ns0L|FnU?92PcTKa%(-K0*s?g|H%gxMJHSYAq0UUQ z8BZfbc09I+X}MQtIz@+aPbe;>!?cFk3*eW<JO_p#SoJHoFE$rMeAhWGCAaj8B|PqF z7gRModp6S@CM;u<XFlSKfL+6KCF&@4_RfA7!*VqW?RgHd746`c9QFM4j^&tGP`Mr# z>X?iDU{R#5M&Pr(T?ge$BG!PJmydDy)5G=9q(U*^fVKXvm;z0cck|btvEGwD&bjkR z;~hSWTxU#oifKp*wB3Wqk1!7w8M`dtov(%gIWEgB3-zO=@==^*kkQ^~CBG3mXjS{Y zK5<VnZBe1)uF7yncF_?FrfIyJO$rYrr>J#6@k#(4!tzBaVb^}5L_jN!WJoE<01kUy zX|V}=iClvBz7lW>OQd96#ih2ze~!{)(52&+%XjLnQ&`>l=*0m)U8U)#eakT^9m2t3 za7Ip6_Jd6Ai-*paM-5SfE^1TXRU$3&E_f+?v}0==tek6FD%}t1t3aWe${&$xqhH&M zJL=a#L;Ofzd~$jGQki4)J~7xXro#$q*!uc4X$UNL`s-O|H4RlVh$rNE$q5mj`}yda zo2A-RE{KgI>=5nIbaR=%<n<NPFb`Oq+oZPOtH@9<gOD)|dth2Lq(3F0<)Go($Vv!K zpY~QibvZ|}-5i4zrP&9L1Y89CmhH%9WRs@8q-sd;mCL8C_A9>V_J1-;&%>94c7dLo zOq$D09wDyxMQ?{?z0rHZGob8{{t6K&z!_Fb>n0Ycyd)c>iWX_T$ZDimV1w0ib=y&$ zF6dPpi+8<oQ|7(SqZ;NJj>KMZ1d;oGJ_I7Oz*Q4>S;VQUqBwMODcd8U>P2TO<Ae*t z@C_=Z)icNG6If~CcOD3P<f=D}dfV4JGBMf4uWqTK9g@m-1w;6q&H}bhEPmq5wJKPL zG@w}_FW?$o-apF5a_zS*NXmN?j|nd2ozD?{NN1zL8_H+=QEbC~(=_4<rd{a2`f)#h zz`O>CC`2reGkFOe<W&Fs_UMGdqrB(%Vr*8+EqR$2xi_MZ5iWcm#koXf3h=IJe&yo> zmU92;sJ(!E0=Ls-o`d(-!Ug$b$jKbcaQ^)VqRC~>Sh|?yR#I6-S?4MzCk9ACPsKKQ ztky0|(llD@QN(x1q<xF=;0uE^k7^Tt^<R3#q;)&#t`eL?vp`RUmzm_IXUz7}d9$Af z?yoJePKsELKEAXazT4R<?$BS<la3R`WbBMGl|Idlzvr&gp)16Ry~*x4pZDIT2=awb z2$h3|w$O0EnG%J(<%ELfW_uN@0}&Re$i4DaJhdCzeRHgIsRD|y)+sNjdfqADIQ517 z^koKWb|ozFJul3$a?$uqmveBu*j39-<cB<{CyVwO+jEv+DHRg(Akxs`cMNPguQE#^ zR@eQWlH-WE2ddOc!`Q8tB`nFrhwYwwc@w(3(A!#)iCu}_Rx27`X(AMY69rk5*mu=x zhS&A`xh_;Fe0;i%T)8JMS?TZAXfvEfx%+5%XkjwP{Ma+%K0$A^&pmt0iTWbScEiBZ zygl`4x7^OIGV-|~%ZJ+ZeU5n{l&T4msO8LnHBS00;<eh5l|D$h8QWxWE7X7u%cb7T zz3TG5=|Ks@X$B#+JdA}kKPyse*kb{noH?Y1R>x1X<yug=?BFaDc#7tm8XIFvfd!@; zocMRR^WP$^qoh5WF9ECc$jE&J?p7&!W4^!hk@Tt3<L8%qXcs2DS(7oM$be$xU}J$g zuo`yD;;{Wv@kJ*@3ij6QQH8t<k^WZ}#KP$1Mhwi)X{M1~NJ>~KmWd-6*d)20PhcOi zTBYXY6B>0T>bkw)1q4ay@O{_$Hm>dd5%ueH24OOlgk+`qrpf&w>-L-a{Y3#CeVt!i z7<ZEyp}e>9=d+pTqLD@=cMTck@e!j-rFT9E&{mD3k#x971e<M%N7G7tZDaV1;yc(F zu6Q&T^2M_kdageQTZyfi;CUgKTmSqC<o?3&xN1OpYgxyJ3Gtq(qhidUJL;j7)LfI2 zqWgXRdF(fIsRCkms$h7>tz?*9E!1hkhcejv1QXB>@Y9(Y3;>PE#I|BSQswUyOpr!4 zdfwWQ3pq8)&YoClH$~t4i6+mTe}AdDpKf6(hA#E<Q1#7?kJUqb3H;P27qvL`8j-(D zIBEfx#lMe}Y37BZ=n_-uKJzBj5*ZMIh;T|co-J3&+O<$z(o@5^Gg!m9=ee}PuvVH* z95ns16kV&nyP3#mkfT+9Dq6_zS$J4{ZGDkjK3eZafBW3htp>)wmeC2_A<+~Yz;X6j zi1@xgZv(lKL2E+P|AbZyA_#jr!-i$uzPKKUxLyZ(<X25=rHKg~Ks~^N_^~RFSvRU6 zVRCt{K6niV3xm0(6#&bJi2;W^f@=82FQe6BXr%#oa@LNZdlmvZr?u)8CppdXV<puf z14mU4%!5moPWO97RzEF}+b~(X=cPm2)RM}Yes8A0c0DOlK=?%21)F@nZTrIFy>vfI zh;ABp_OShN%ak5hj+CB|p<8AV@^IXPU2n8HKhw}WUl7b{M{@WIBgC;<Xxn_5nl9*B zK3bg`qED@YUR8=0y}}?}>QWVsy8*`^-v}QLv5FGYUq=b-irQ!tq*Q?~==Z|eKB#uU zNR(TeE_?y5|5VQ0vCEZeY?=zkONm_*@p*?f$Eaq{=h5^6sR0kog=j&J${SJK>a%Dw zq_Vns^X5C)BMYp`#L$N2v&jjG1nyPQeEz?bA!+s?(G(h>Gs{N5n7?#m1coWH+Eux< zU*8m*B5hRd<001l;w5h$=5v}QSc+>xUaYvRz68`U?}aFvx84CUxt_R><+5Yu1Z2rq zBYohdM`$PT?{?<zf+bn0!I-s)6F1GdpUyp>=$hzdJUoEiXRX>+pKzN`T@TrQe+<&C z2H_`^P{wb@-Wf&p6||~TDSRL)rw~e9L;qp}rcp}Qn2-1&?}qw@4pKh4(lFSg?8Tdj zZQ9tX+d+A?s^VEY*)@vVJ|kq4NLCj-+N+g@<5mVmBnCFtc+bhnN*-<i*S@FDj;o6k zlI=V`R_B2C9rhZNR@A>|ZQZj8$=4>UN<eY7%2+OqJj_xJwuY%Jeqjv1;f>r2o_S!t zP6&?RRM8XjTy_XC2hNZuk+{lmE$m#akq9Hw*&r{U4$yf;^X2l<NUx~@o2BVVkyz+o zns*6iC(=Tze7*r?cwN$m&j^;V)Ps@Td8eYw-eIE}MYWxho8HWPa%kSl3`joVTh6Og z)?gv)eXrI12F=da7G1Ukf4RGLn0HcaEz7}4_d1NM)u>q@)GDP5t%<7X25k{%&B4br zreZTiJ4o_pJ40vUMZRfWa(eBoPhGrA;k-sIw7#j+S5gCd0>9t;$`fs-`>NJmCE`)S z=U9v2u~ISC1)D0BjP=^MnuyIBCn|C6yqpclKoO>-WXgtp8Mav89|^6)OxMSii-}W{ za6CzVooC!KcEO#Y6JFrJ6bH@~NFQ)av_GY6(Jr~lE@qDm*m39w!BSU!!gr^-avs<( z=e-)5krcFBoILPPZw_OZzZ@XLJ<tL?59z(&0`aZBlj=jS5H^OIyCtJLgZ=SZO^t49 zPMXx*3JdLJiFORW2!GO~zvdJtl4x=d&{=>@u6&7Q#3FZYFJnzhVpRYUhUeZkZXy?S zo6fdtSuz!6pWJNpw2dJas^k-bTU?q0H><41D)SQflS;yhXgh_Rx2t+@vuKy)dT~S> zST22~Y7I^k{(tPfXIN9));7E?D1v|ul&+#8C?F!e3R_VSkP;9=6QqS+LN7MNLQ$$x zRZ1WbLQCi%D!oZbLPw<6&_aNa@XnmG&pG>C*M82!`M&q(`!5$|Wv#i!9OWMOJtoie z(bAP?l(0J_J>0X@OGB3P2u<~--ciJ%pkb9mUe)Ef-wDKg9}>ICy0p;;RhtagbedkN zlUZZF!*M6bzMd(8L|Jp?6A^0^QLhx+)-jQ8Vr!#mC%0g1CsbrqoRu1Eu3dgr*@b!& z%~-kEBvngIk$Z%t`vh&B{$dfyuW_FaL3`x;IJvnUe`VZil9F6#=iXziJ;tbewv_?; zjRnKi!6^=wMk|O<-t!)FxoF&Mtxk+tqmQwU>{4g2SN_Dyj!$W=8g+PWxcuEHnNel3 z%VR}_m2*a#jxTS3#c1qeYg#F8jL`gj-4m2NpZ6!%xr`lxj#4*xtE1ft2rL;P$|B(d zNL$C^`8JG2YXSd@#KyX%D#CV9PS9e6`M2)#eg5~D)0Abh(g_%Wu46F*w`~=!_e>kq zAo;it2$DBdnWlxTc(!+Tg6GNCA9Wqh$9V^_Vw2mP>I`eFud)$rq`^`=0TW;dkXB6% z4G&|Rfg8|gC<k4Pq>_?(Y+PX`naVx?^W>hAkRq%~JQMb|a4)RtIwvT8@;oQ=<5pW1 z5==u@O-bB?lDZlkmvh*f!Gd(aEANoY__l*6lr;1ll7C3~L2jgpTH5*agHXgqqUe^# z<eCrEeEJ&;wx)R5*|=m&9_=U4wd~Pjl%bnBsF=k?UAz2YXYfK4R)y_z`e&O*)4F;{ zONXd)z&;c`wqJUI^rI0j0Kczb;eK~Pst#U32k^+>ebYvdz?PY{%wrn}qqA~R?pAgD zYc@Z8cEwr0ZU22)c4}ES9y?q>W`V6Fd;ux^kqfG-`c(a+J_d0s)L?wuCC!2NhSvO9 z)L>9g<X>Pif6<D2z))mRfSGO7$|H+n@=+qhQdY6>$x|*Q(#fBE%y+&$1Hel&fK)uI zKTE#<5cd=naYZ`*^pamQuHOH1yB%s`p}rV@$#b_@TOiYP%wyaATAiT<#In6vZRiL| z@u+m4+)iVZO+(`Bx%>fg0eSFfv`MC;AABGvurz#o%hR~RIfoz{!m<b}jTvE~_<Csq z+%&T_A*23ffMPy(&a-)Rx%msF<-&Qdf233XBQozK<Pi5}lysRUMD1_*1ue-J8u{Ow zS;DzEUyd(*AB+F`t$x)^puA{0x>YE_hSuPR7o7^WWyY@ZS}U0lQc3_YWxf+4*!@`! zSu2^n2D0W0sx>YIOr_m(OZ1@09?QwRi*Ie7q24iKxX`Z^hqz!Lf4moyRMOYeTR&#z zp;0ux?m9w_r=yv{qF2BmMTXy4u7;fZF6JM65flQm!_SUjl@zH$#-*+(ch0Cz^HPPY zY(sr@=_2o+WoKtwRwM-h6Vo=4)or-S@7!TGoTHx=Sv9<B=BR(4LR-4a5EU|7Y!W=Y zm76zOsts!Gu~K0N+zH6!H}4~S^LM~`WEGZcW9_B{vU}KZy+Zq2QE-O9kBZxu7nKpt z9ozk@UD0eQ+OpXCxTu&XA|q}J2(D4{<*chCMSUxQp`Y0&K2)Z~wA2d+BrAu^R+Vh8 zD@93-pDkv9Zbgcx=1~xemYlDyA*!u%DY#qVu>3iRYH3P^zJ!}23Xfb$vUK!U4$6<( zlMTM0@6E{Fi@GO5I8VRRKppzHk~2jMA`<l~y_{!F#768R`=%QXaA$fw>DIf}Q0?)# z_6-IkbD(to+2>rw)Wx2gLHA{b@Nd=?;iPRLfB9&1nu(FJoid_R+UmI|SZP+u!|XOw z3Wf?S6cdem<Tm)_z{|*(=RZJRLYh_%i4JKG!mYHlz^2lRF9u*2BB%A!-N`FvMUPrf zfZEiz@mP*Y&rqX6Zdewi0D~}`^+Fr2^g7HN)>e9zm*@86yd7jsle;i{Vw{&ZP-|PS z$7o_hqg3A8oq4)p;^n)Q;Y%7drdw4F&b`Qb)*r|QIp+`HOq8W}#4`v%st?rv5LCkI zy@Uu7JJ7B#p+X_Y*#4W$=g$TBze632_s(iEKo1BszpoxK-oyr?L)R+m;R$$2sOG&$ zg_|N?-)N-OiIRQ!);0e2ueUtMjluT{<B+d0E=frIfWtj?-6uK-lz;QwDoA^$ax9;s zu6SQfFq*1z0-1G{H?ejyo~d*oNuSgQj>Ho3vbP9FCJsy$8-irJEN+9g;n+K>zzVmc zymQvWx|KgxEBZU~A*Vcz=1W9Y?=rMyZZctOaI3sIP;7p`baF~m$eBwP=#(*;TOBjW z*Na;1g;LRF<6HN+(z<I7;2lR9Hnc(m(u;3Z9va1&T-tbl6|_spXZ$yQaV{E{ZrDq( zy5L3zLmY_co~)Wq4rf}&Y^+SRwHgOoysK0jd_np1IO4PLH#z6SI=r!)?q<o_v+T$q z>Ts>hyq|HoW9E3@5fjVO2Q`juo;9>7h+vKNJ>f>)fTrt`!dT=_TT|2I;Gp#Dxhu0} z*<Ma1MxZ(H>iH{oP3<+i?~l=i=1O~NIiH;e9mZ;n)N)0}ELH2>m(J%UH3_SX6aB7& zpcL6DTRiONGbW!8;aZz&yVAo)lQ&JkJw>(TsR7Ko_@)>D0otG4XCRN5FUMac?9X<A zq7SiYh9$`*B7#DP{G|g825>H}8<2B9ywgUB+fD{Qm#d|Snh|=E?cy%-!f%nmw&sw> zRG&3MrpAh}i&+qs!kjgr_Ax<Uv~^WEWP|bN#x4@=q*T&Rk_+M{#TZJOBtcw;<&gIa znKHDEsc}R4n4A*SaK{4jX;^^v!n^6EU~^;w(gj<&c!76EjV3$k-q<s$AT>JP&qh_c zX^Tpl?IVxC+HF+E1r<9M<*=H1CA%#SHwP`Tm;gePghZ-0uU>_vgI_5wKu;m$2tldg z2;_tJ08!s&7Mpp!d^+JoCe?0u%6StYoU|_kkCtkGOx!BJ%?wkL9A2wwV;zXpzYk9{ zbjF4m!toBdnL*Okqqc7eLKdAj;T_>%?^^^mUS^mU5qXL-g1YsXV<e;5A^M&!(}MT@ zP}%-($>p(Rz_R#R)r~4u?1P!zd8cqI1JsmyH(Ygvc=R7A!*>?MWtR#2jEn6e9PPW` zr&A-?ngmKWq*yOrIRwSECkPqb&V4a>8%=v39eqBD%{9LHieR}R3}W5eiPBkG$(-nT z2j!iEg7_%;v)CrhzOG%!=uqX(?zUq-fdHl)2~|I}yVs%MvGJ8tHcN4ug(77icQGW+ z!kcCl8H~VH>LJ>+rrZnUU6jSpQ#lAo+2V16zNq7E=#6x-;H0bSI{b0QQ61u9>B45n zpE+pM{d?yWkC_6;uV`R=UtTF>C3y!<6YKY|f0H*-Y=YJcJh$~!DT1nqG3+&Xc!xa@ z5+{xZEjfaX58t5BE^W%9h_dN|_MGGF@cl0F9r?5bAX2@#XxROS<A^1H6E;?&e4);6 zz=xbrOMd6RMgiY>nZ8ejou{j9DDM_o?Px9)>8rdR(9FTi-)X-RA?_#*=aOWl5EFx2 z-M}PTbOsX~I<4NwTAGhC@YUf35K%L6nlbv(?Me;Wi;j-10jS+{{@{{FFF7J<6j`pI z@XnB7N+O4wvpvsirVeTTt~LcM5rCtb9s!y1ZNIH?i8N7m2%TFy+AVo?hBn`P!l^9f z9`_1xgY`K`BvUE!u}gNGEiU=Ukt3g#P*6R+XwuuDJyv(aKsA*`5ny!MSMwD0r2XfR zCiVN~OulvIgAps+uskj&$$d}z{A!w+J*G)Me&LK#z{R4xIR~3bc0Uy_u`Qnk=iBI@ zn5NB9Ty`Im{G-4PN-m}8Z)<kfH8ErbGC)!D26a7E<*OQXLky&;lhfLpM+HYAlP+7e zA-*r%AotfLMHso;IA8_v_SDhIestQADZO9{$O1cIkcW`$-Ws7?(5zU$UnS>5>-L+K z%P-DNVA>Z&l+s<DY_ZdxB%0|GZy!PZS{UmIy9GJ0QED><pwj>!xisTi>pU`y%^koF zFL*|7Oku)o(Ad|gIi&@%<q-5T*-Dz#PSpD9-b%B_Dlj_zN2H&zGYPb5*kv?*E#Di3 zYJx>mtOB`2YXR%GYPrMQPLPuzY3Z2m{1RaHT}Q2~4u4#kHDT5f3@vUMMWj4MfyVkV zc7KJSvGq~jZ+*-+`ncLI8WiX&eq*|I#rLRuPUu5^p5ZP#8{MdV12?n5YIaV<owQkB z?b;AhQ^FpCp576+I27cQc|JGjYjDK2W^N)%F%j~cA;ZIA?Cm(N=2k5WNgXz0g$FhL z*T6(BAB0z3_w|a_2T=}zX4bzq>Qtwui9djpMDP>pW!3?>9yD4-6p50We}Xw*fH7Ot z`SG<HWSG`cl<g#G`Ot!x4`3B}HhyFx?;yXT#+)|p+h6!vG?pjWTev|f=s0=j3#4BT z;s9AYczwiQabr&r<hLg!VA@KeVfRCGyJ(I;?vr&tlbF_79LVozo^u<SHkp==(-meD z6qF`U$mfwIE5(i^3G|W?Kz$vHYlv4S@Reif-TB@DXmRAj`pC)8``g|zK7GOMqzI?Q zdY!>3{j@~r7k~!Q(s<=W#1^`BLtqg1@IjJd-}YRx(W9-%v~P)PBo0>%>MDtM`ph3S zO{1rF#2Dz=1PTwyxzv|EZco8mmT=bLt?eRVxX1GH9kU#X2A)UFZ`_%3PPV%@O_D9x znm^%VcA(m<`ik(EJYlxO6*=Tv5ZmEE`(nZ|h@!u$j}AFkmJSpU1iYPIMnsV8*#y02 zPaV?Icx_6uj|$DId(uOFAeQ^|i@UqKZO`gtD=CN_1|R&o0#AzZ-s1+=1AR1siyscH zWox|t2GWngNGIlb7(irVM_2+homP;MYmYK5bD0vQPy_e)(z<mvQv2p-9=cYgdszFe zb_jw_4If2_D${G3%as?*WX|v&K~=)JFR5@bPl%r7$7&vfY+X-wUYWYJ3c3YyFU~Kn zAsvHBZz+lNp52QeWdqR*76oi9GY=RFEnwS4H}ZUw`YjlQ!>HH(2J#n+yic)P@;6L0 zsBVNe^}n$rwPb3KX(DdYraZZ<UFE&z-tq~Xf#ij1@THMp;aPn&=%;n{E~v|5Kz$|~ z34!PigYh<iiV|pbnot0{7kTDGUr^CRv)`oa+1a1`s}sHZ_Elej<uTzp&80Qz;Tj_a z4{i`hT{HrReGt!})ff7Ho*FS2)5PTbwEdW*5ASX}@yqKvQ*ZqY;%c1wwXB8*d&_+O zX}8C#I?~j4W6S5Kn_A3j51Bd`Ft9;!_7~f8M+I-KagfRrEddA#_#kxDYCKs$J}Ult zXp2lLWq!w^JNOZ_(UTT@X=jnawdI${8{S6kMM*QdGfQQ@V2@Odwl)fXCp&Lj`C_d% zNFlIQz(X?>+FqXG9Sn-1RLZt7Lf_<LAE-{?U-GRh*WKo`p567w^Z<&*yh9@ul%RJt z7ddm6?Ev4SQK?R9spo6WfobZX-FrE#Zyo@6E|FTcSkr{Yg>q|SNfdl5{u}fF@*Wg> z-`L?(b+PbcSWp(8B9jM3%c4JAQg)%}Zln#lDo$e1G}ZyU3qroU-Htc*ywG(x=@cia z=MI6a3gLGKjeM`Fob*#oW<TT<Ca4zP`EOl!A6!mib!NKkhQhn$ZkS_{{kK7$m2ctm z2Xe$i_)#6qHXG=9y3zA?I-2}|G|NfQ=gS-HpAl{B<V(<1j*8t<60Vj)<5fe9UQY;} z2dVWre#e_Zlqmyh0`ZK_MT^cU7$vfB#5gdpY7==Kw#YqG!{i1W{-FWIZ)E*3L&!VH z2ZH-ioV2bYl|5~!0>G!lSeiyuBEL?jsh76a)v~teF4{GUU9^ZElJ#)J{cfG{lEV_R z?>b5B?Xgju@9vX4*|(Ot;!()U6<a?q_3sjb%YA1*KZ@!Qg}gR;R}|WE*T5jQOQc;H z;;Ro~9S-n7o5rBac;w*snI?qKZh3<Ia7%Mec|KJ{7>Yz`hU!T>o@;duE4|yi|01cS zXs*}*)P^!TV{h|wr-qQBVvzgLYV_$CvDX!c*_9wfr+TZn3jlS#*&ehybYyF-Da~VM z{hTa7V_Y&)AFk|N%qW{ORHYo9l+DV>Xv!a#pNu*XVjp55?y=J*-u@|e1O42Pv~JRz zDiUyi{%BTl#RhPjHmzgr1D?tm7UcIErK!qB!PKpUE2i2Zho63skffxRyWK7w@bf~@ zQZk;d6JVc~=U>PApgfWj=kLBL-->({Uc}@m2Qg)VZ?y4D85qu`q2K1}#>FwAKGoa| zSNr!W?=Sjlpd@^<PLB}gtESTJ2cI5E$?X4Jc5FZ6Bd&cl*5@ggArL)wHx@hnhaEI3 zB}}3oB8fMz!!csaA<}hLdkjIlAe>J^n%4I}@3>ACm^yU)juE)HUf5{12G`8O?e{&K zN4<Ec|8sTv@ArK@9%R_zB!vfB!i>k{G#L!C0FO}rG4V{yQ{Fw<9sP1&Im%8rX&uio zC^qej5#P6;Q}^)?<J*D4c}JeK2>=4YlwbVNk-fF89;<9K|L$yDto8;2F1f2-$E2qT zE(UA9F1DZXGI&s5w!J>uv6uAfp~?8V{vr0WUhy~ikG86wjbU8ojk(Krp>(f;nz!cw zXUg_v<CpV#`j)@}tahTeH35`Fu-TTH6GS;;S6~SGa1cg=$Hez4nKS<ynDQ6M-k<5B zk3)c9h88ii*BklQ<=!3wp|j_=51N0z+@4?^#!WC(VQ%#0Uk--(&&8Mn7^)!2^0xfH z*INC*2mN17|Np(we_8kc^y&YZ?*Fth|8xTmXOCgv?UAS8DX%44GwUYH{@ARuR|>4^ znS%B;*qzPIv8b)d4srU=oBhwfeOm#%jXoivAH=68Zy7`{S;VF^dTZn_lurz(5;S5r z_@OiV)xucgo<D#7L-5CKCEDhAcWuaS!PK3II^hg%r)50j#vyr8NqlGWpRfBLpXLns zPL?A|RHlqOubt$pwD3z*Gu9NjtAZVT2{F^OXeV9W6EPA|r`pW!3v=XNj(ZKkE2pUX zj=ECK_4GsoOmlr{Cp^?%aVrd5AFONk6tuxDjaIo_!TLgkj6Y@=$+IwC-L$SbYJB5C zw0<-^{Jh`mCeR6t2c_)ogVUgWQe@TWfiAy3E}yeMS@;L+plr2^T+<;Sf7wR}Kf#DO z<bCqwNnV1@nQSQB^FH|`Amv7NUM6!)q<@nCUpM>TA6(@UUH5o|&k8pB6(lZ?hF%u} z@^jjR>T2j?(bJ$cXM(pEa)x2zKKs2%v`!2j`aUG>{i{X#?Gq7mX;z8J=@-=b$!3QG zRZA#~bXqs)`k}qKim*T`JebjWI?DeXNNH7SX13xuE_F`9Mf+alt!p<lHdelneFm4< z&i#pCdT0AwL`n*8`Xb2e_D7sZ8;sSfuQ?m%z#Fe|a?cMz&vst%*=D#-_HOB4pQ$9< z^l#Xl(=h|xS5(V{{FLug)TPiGwE|qvv-B(b0pYOs>%=&Z>}{_dgWLg=x)sxF5ylt$ zU?^MIX7)0I$@x$P=coG+60X7~_9rNYC6iThNqj6<NX>tm1^b^vor(!vq{}>+yUazW zf=t&0-_}l*=fuw)!J3b?3ZMI0V9s$UX}@)RRQ%m5f{Bgzl!t7ASL4v-BLk<pgAM%z zQJ-`e*tO5#gf1k-#-54}xp3H+*v0r6DGcEG<9ZQq-bhqlwWt}ggABS2wY@2L_MdO( zU%yrP4H)$Sl@^%UMoQn6>Pt!&nuVGlj>^^HuRJ%t`1_d+=9uP7eV_@qQQF%xpI?C^ ziki7B&EZ^b<PDY@rl!(j-R*;?!aIa|__s`o>{?y0?IzOtFM^52cMVc_x;NCyKRuEx zP74yeQWb-Q<w*YIeC3vv*qQwkc@J1?J>%<vh@QDVE+=?p)^T4_sUc!HU)=7(PI&p| z9;GA4RK92d;#kr>4rk?nl{|$!a5g}i(t_~)%UN$t(U9<~787!d7JhWO1<&UEV;-4u zi;a{aLBPR_V{SgZz5-4KzGfSG?9)7ROsGy719W^I9+OWRx&jj}Dr{|R`W|=ko+3Le z2(_i68={YQo*F>g^s%X-w6<wKv1%D6&2#39x77@N1sBh?-#I9;_^=;HV7OYi7QGt+ zPPUltxfrG=Yrg|^&RGrP>`uE@r8rPfdQ^|AyDW-;mS;kC_GN-nf~Z9_KQ>rQ-!SMR zfgN?WbA+1QUkX~XZ9&$8`GE&G`+mX4ujvPa$9b3)==N~LKnCF5vs9A@fzyS&1Lm<D zdZ<hEwzSFdIzzD&#dUv{d6>Grly_l=9zb=^c^kvg0R7}+he1Z~$W)l^6+Hn-HzRhH z_5FTCM_M#%`T#v6?YCVx)e60LXju6H=$t#;2wmsAQ$Sj%moWKAe9WWhX~5)&d)RtP z^a|3%CL6js`KUiAFhOcmTqw6abLrxVB&Ox1u3#7qFh`>Gb4{Q!Sp~5|1`XotNxIJf zd*-~lHvhY4CiTNWWRdxl<9E@wm6lBkE!)epY9^$S10KdnmA}{g(o+qvE4pRF?57R6 z<qP^x9zt8urZGe=q14*vzuUcG^;1GTnWK@D`_ikq31v!Z2zT1RR~GDcl0}LR(b5st z&)`9%Z3AVX>xugM`dJE<LY5xfKl~~xip*Gw{F291TD0eZ3~#yUflE#Di)`a=^yOEz z;XMI|S9>P*X>)s&HrHxJcPU4C@a($GlP4P-UA+`e-%HmoAjQwSbcI+2QETAWR}q~y zbSkGl@)|5{kT2$ToC{fQy0>8E%k2;A^S}4|`@Z2QQCR4@C?{;f=7zs&rdiQQdi$P7 zoNs!V)qv4!X<N3)ckMjt^^~@3@J5+6q3>7Wc4r$wEac*yEI9@M`}wRSuT$p46}@29 zcSJ>0lKPxgRc?-gj#->ed}IMD;aM}C5C#MSpHs<nE-wgVpjjM+yol5oy-m$99b4s_ zUN}tAUWg+~g$UC5E3EcoKYC7U4_3}JhL${DrDB#6@B_6fRZ$M-CdQOm>By8HN+&cq z34GwInLMt`7gRHYGLpiWZ`M_$OaKy>F*ukV^4<riSBl(s5B28`KT~Wo16`sV4=wgZ z>WmOfxv#yz=;kQc(N!;UeXiMgcNSoocC`b1U-XwwKVSOHlLrO(<Yaw-IX-Cwa8tG% zDA&u~W}gzaA%`a)vHeE43{e{?BZ>qC77#O(?ocdX;2e-6llqV|kffFJz%36?3ry}D z5;Pod;A&M$b91b6dlWBTyeKP4aVj1yp1X%LOna2PSC_@826p-x-?5{Z7bX#Y-SR`* z-+LSBNWz&`ab=v&M>ZGACqJH!NPRFZqyB}5D`skSbs=F#+YE7!*RK*+Ilxw|F1rE2 zDXQ;m7Pbw_bgooY7pg4nH~B%-dL?3x3vL;gp@Vq<VuZz6_ym+t<X&DGRXK$9H6xyI zdbx}OSXs2lo9e8P?R>k5RHRn*Knhp#h&a1<^45%#pY_Q6>Nu7^`GsEz3AS9I;HH0A zooHvLAmX@@zcNE4sxZjy4qJWfy)w;r!4z=XIg9KItRM8q?UD$lV#HF;3(iW8HqLl* zz~9@CL|p#YX5`mdLFLG0`C6_#JmvTCeckIXY|TBlU~WZZ+^CFnEm76lD3Q%|3Uix8 ztL>Rntd1HS&6R!fEt1JBq&GM__)?$Gb<_e+{9<Xd`3D^Fw=h=Wb*?*-`T<Q6KipXp z!XLy`e@3ETsU5*0w-(4+I75q;<UL^{bjPa>UWbhTxkwVbzb0U)|3NF^CC)sf9M*)R zx~(rr0R}l}mhI1Z;-ZKVPGrL`F5t`{Y>Q-w1p)cV{J>QfDi<liP!b9?p^flX28Q1= zl^r9#_>D{_;eV4mN(eZ7^85}l%awEt@b@ExC8HCPYGAJ3irxX8bdsF-jd>|uYt$|? z**7c_P!%s-aAfD;?Sbc=>a_SeAMN6}F}GR!f)0DWh)CK0WsWz*s%5<BR_-tE1rDKr z5JG!)9loSw&F+yR`fEJhcSDR()6a&D9%`{0kRWw9zU4N?Izvk0(31QRX5c`HfipZN zi+4|ysADQ;c>pLdHWPYH>g`W+|L0I29;&{-PVo1YNWj#R_?Q;u!{g-W#e^NGIOruq z=_-#FkPA~ErI}>I*FLrLIX~gcE|`op@42AUp}?nxCql(^wD^B^am?HVLKpUqMah+O zXpNLxtEb*|nKfuE@suB^t~$7y&mh--n9SDowH>Ee5ys^NiFt4(j0@xtWq{ax12CNe zKQ+JU-rSZ~oH|n+SaQ!qwu{b)@qyNgNt*{)tEj1BI{&sP%#52MDGN7SEgLFGB%@co zeCJuDug+m3!~0xneL@U5SNI<6IkRk_kAHm8*(9`JI6pA3&4dT42J4Fsye<$z$i}># z0{$awuMVNp-z(B6Ae#mB0EJDFUt{J*>nGl<4(5+O8hqy*nV?~>d=GgFZIzl-9E=St znNVBntLQ1PQga9yKcIKjRlMKFBQ3CE{bVCDMX^MqAmwVDC624&qIX=nU8D&@g4}vq zNDSUGbYYSy;UuKXqjN>kg!IF#$e#>Te9z@n0BK)o$}lf7VlEvh9pk-y2OydY?MhK8 zBhvG~O;V$fS{cz<_FkNP(<i>OWBj;B3q>0K@4+L>3cUtb^0tHY~`hXSS6>xxk1 zXXO4e;L2mlxu@tfzjpD4$cl~R#+rA49b^rHlyzk1trRgp%iuE*0X58mOobZ9j`*lu z+-Sm*pRo%%Fiae?pnz2?4(e0DF2#x%82U>uq@$@5<uTVi7mpghFZnTWYZR@E%sN`J zdjRc?cwyl~J3s2Qd7Cv&=|Tw4=C|B8{-<-}%j(h<<)Bi2y3o=>N&b<xD?d%?z7T;6 z@K4CeMKryja+hpL!w9lYM<oC>yvfzxUXf!ryfr22um2J9yl0@J!rX!a3PM~i#)NW) z-3aAevjMrU?~I(wg|D$2V#4HuNSKZf#v9hw*6q-9eK5T}=g`M)g6(nq`?_SDe$)L> z6HxB)2fb&C%d#sYQA9K++8JDZho3UtKx%w?Hvq(7!ls)y2kZ3C`rM4t3<9)fzQ}d^ zURIi4<H_#N+d=+opsABFJndYq`grcqA7qX>YWQyEo!9GUMz!eegB!DH;<8qcVCY$( zY%o#g`@6fTOOKMf|EaB!xQQ5oqRI)fIJdbSA>B{k0GqP~^f7UigD1}OYzIAUGm-9b z?5K&@zB`}jTs1(Il&SV?I5SB#Y%R?M73%GntmyM6LwLo;4Z|e{^W@P|l!U*vq1HQv zsF?;&^t!X~R1dXgm~AC|T6L9SR<@?ndf6j<!#u8gL=59BS9TXP#yaJChg^-*<JQX; z#Q_>#+3yYTbU(ih&1kP{%}&KjeSqVO3j_`8MMI+hK_{F7zX<1fPkg}P3gw_>2_cZQ z&u0cMRYE?oTqs)->w}s4{*ie{sI7Ur4W83OW!S};tz}?OHp%B~BN9^JC~XS<ai$!< z_Lp9Iku<U6Vnedbt=7m-is_L^XW65#`|XJeNMa9tK4r^~c>GDS7j`c#bqv47J>7ix z%^^-rapjcY>mv%BUqNh!gxaZtiqXTSb5e2~Ih;GO^`X-b1AX>{uJ5t4RanF!fZTDr z%3QGIP$>mN8>)hS>@@GkIRpnJ)gBuI^qP31=^88kOYl}aZhtn>7jwA#=lTfe_=)kx zfafL=-lVNO+w$8I7+1ED-n;%%zD*+w<UWzJJH)92nRW2enek}{oYrBnjr<o#WNq+> zU#!ux0gGJV+*>bBLjTG(-g162w$+2UoMlcmP}yn?%eq6SmUZxUPc6+|^9hsV%^-+G z`I3Ab%K$-O)?wNfIiN3K3j6;jEdY2-I5=rSGA=DjzwC!lFYKy7^;eX#v&AKJ(3+Sv z=ad~F%skSI5^EcG^GSY+`a`=SuU$=AHk_r{@S{D!jS~iR8bfxzT|+C8T5vI!AjB1z z5ergoX@OK+-JuBNT2t@i(@Ks(+<Pl>koo%lT5H*SFhwGk$l<i;;b~Nk`HG^G*~|$o zpxGVXvp-wAPP26W!C=aWBE04niNmCRa34OOkK%&U4^kAIKZsnNUy*2>*z7-P;`w@~ zjC7b24S9-j#WNKke`O;TV58=h<WY||Q{&ib%ug-jBAY^jgJS`Fx-exGI9v0e*OMZb zs=1ZsfFwvyPj6x|)YUJ=@ed)G`JxMpQ9yyD;^NRfNM-d`m<-M&V!rP{E<lqwJ9R3n zlVYYXNA9_Sz-S7xm7j(;$zDtx+dx})`VJ6WLzneX4uBvN$u1E=I<)qIs}8@rLskU- z_1b%zvW-P}>$mPt+Ul~OLa)n*yAW~cze!}ci7i-jUT2n?Kxeb|Z|9tqW4Sd9C8C}U zyZi6<@&MiN^d!(%RB9tsQ`%ny9DWM183rNRKSCZ9+HJ$v)X`4^HhUk+291>%@goj7 z7Lb}9bm*?Qsv=@B@$yCH*mamR*Cm?VO&~t9=1EKgFZNga8y+>J?etmix!4>5Vu`=l z&*OhzoWOI4+hyH+PH^Vd0rVoildvt|*YO|<DVnRAl~c`0kjMvs&D%ZlxIxnL_g20` zQlpT;nLF9I_m@!LP!ye|gr)KEh}M8W?HQ%J2JqH9PC3V&f)?JW+kn{o?m?=;-<2<_ zhD2kLT{5ZI+;apt)1yE)XjEDI0yLt9eUbaK$DFi@9}|h`8%It+tB{>J{AY050MD82 z5(H(}l#*1ZlV(lw)wHX;=ZXpK(V&@JtoVc1u>jNaKGI{oO&9&ib31UnSZ--2Vr}Bt zt&yA7z?LR-(wY-;Yt|FxV_&>r=WfYuH)r;p!SOk1>Bw6&<s8pHD%qDfQrFSf_aH`C zL=~I!&~@ZD`8|V|_Vl`QOwx_%)S;6w$@p0O7#_$QPC$ZsY83sZ^}82d1T40n<3-#q z!2ChYI)`dhn!yC@ij*3ZK@1mZTB&JUAsanlDgk9;MLu;IMkX8sS{9&W<2<cw4p{mN zt}w!yHu2M3@{i#6214fb@MO6$aO|`yiaxu*(`3?<&q5FdgvYN@)+J1ZnI#&9F3_{= zP-W=Gt9!<Z_0F$94$vF_RF@EipW=VAitC^yOs#-L+`W6ZCO&a)8l)&C*#lhs{9-LF zeccpV1bq3`zaXvu`01@5aPPM_jpag*s2#y@wQ97w&42MjH+MU0MrbCb`FT#oH7nMh zV#gl!(TuR1A7mbht%q_1h@b-Bt?FIS(M5Y(umB=toJVMDt2oMk=9vk*E`?)3r;B_1 z84DKaP8X!ng@~6IYwt@Te01B_RAWwOu8@2xv_G5|>RuQRsOm_#dS9=>yLwfBx)|Rc z3j~b5BwZHw$?=Tavx)w3qo6T9PgGRjSM`rs0KKJmnX9)=o}pl(l8B&y^4Tlh+1-nP zSGx%cIl6?V&Dy%rq!2#F&pe?#TJ4&AU75N5R5ZYi6C0l_+^GEYK}Vj#^Y`^!h{sz8 ze7SJ~qQ3}|?-LOA`w=4N&eaM0$QkJQtcr9GD(x&`P0WJ7#V^+EjouCdYf}|4E`c4V z#kzJkRa8TF@>kRv#s4rv1npCBFq+}uXz$af+E7ALrA8oiNgMGLR!qsR`-kK2DE_D8 zUm;c8CHSxSQzk|)HG_c|exzA61n-ms={pK{+`7wL{x-1Ax{PT{dsb2l06J^-WS^fL zPJrB66xv#0cxti#=Q+RSENxnrj{*n;)wPDuS<gZ1NNa=f$n+2mVsn&lN~sb;+hm5E z%|XMsbm?|o&->PB{;(^@1k<a7LFR0<b2+8_AvddjbE?zGw4p&z&Y!4UZc!0cyUN;k z7`%lfR+s2T#CoyMJ0JzWW$vi-BUdr{4-X%x7zRWG+Z8o(2^=_GD(YrR>0zkA@l|+b z{;;CJ!GK}B#$K`bFE}tQQ}SzP2iH*e@7cHqsft61fGY=d`15@o9-HeA$BX#VqJ{>b zfZ;2qtNR3UY#8p8Ku@;mGXCVoIqMrH2V!m~tpLCczLlojn(JvH{^EUFCMK<5H?QZ; z-?B1}whn6x1z|vQTI1?2)m3&dD~>R#L?0@(bOiFtOh`L^weIuH>mlO`jY1~|*kXk1 zb7g=?Zw9RTzFsg!<?fh5C~sJ;+%&s!&A1)fQ`j>1DVB~?atJJu<VG)GUnzK!J<S(& z?gg3BWR}B>Dkb|W09M6$jg)2{WT2L=5iv1anHmQGs;Mm?Z)w&nw)ml1Cf_DCA1T@A zzw}GifoiNd?+|1m8$K&%_<CAw3@)n2;!=m-kXAZ7M{FkG1AQfcAbexymx1^{azw@L zoM%2J)mK23Gy#z6%>Y42?Rtp>es<k~?ZNHyZ4T(ZIWM#H_UtL7QcplJT3+%%DISKJ zs{!Y5k$a}M_vTbprcRyh;~JZWJwGDA30!LSKeY(OCOPh+dTE;2tMR2ARkLzt{i-&T zTbIP{WI76K#%`qbY*12hpYav8L@qkly$W#Ni=nWCP{`zc$jx2{dg7oEL@tg7>Xjac z>P4Ufygp8L042jdzXBJ#;?ZS(<#<lVtfa<<NT%Owx)&hjnO{-2V;T`m!NFiM3Lww6 zBzIc8T((-h<!7(Trt4Vf3X~)c``X<3>a+v+5!T~oUUEj5-ywe0_1puBr`Kf^?Wj&$ z_RV2@&J9nFb3p2*MXjrIn+*S^MJ4fo@eZ!5J8ioXS<9OxfWv&G^lYIVLKW3g`G^(` zhT3QwW1T-3&FGUPq^wa5g(hfyOK51?VSxDpo{^OoX<Gp#3><Q_+d$=fR%~Rgh~Q_z z;sUY3jdo3^?B-w38~HG97o`ctX0*-jXTerLDxLM`gZ&n;HB*7~(Mieuo{7YFwLaZv z-kW<jkW!R-B>EDMe@g}$FPuwi;yx3Eo%Y^Flz<_7cyK;gB0LrT#b<k|l-T5xM4K<I z-Ttt-daFef%v!M7wV>Pz-87KttjN3trj)p@ccwpDb&bhOkRn5}F_3n{0TXnLV~M1? z-J5v*>D_2l7`6#r87!ImN-D4KzPa;8qU;hK-+o+NoLu8J0BW6HVKYtiOXDmz<MQ`` zH#hi31(2%KMT@NyPrXgcAJ}l>AC@Uyczf&<(uLBL5r#^*MxW=P{^OGdn06sq=;2_N z8<Ficz4c~#ve8+&r6S0&-O<FyVA(UU^i{?1A1OY*XR3xNx512{A_}-wX}ddjM<^R+ zgx`>geHCkc+KaS+51ygL)@)d00m;)sd0uYP46+o@l?j_6s;4H4n_S*1S&%JMHzb;i z)GqDZsy$3f)dA?~Jdd{E5ONS%gFJyk8V3H(%+zTjtqH~;AdHM9r;gm{s{oUR{M_+t z4cFH68494iWja=G8e2fW&!&Q7WQm@3E;VfUr>TtG#feuhv~j!l6^32aPq)fta%liq zx{<VSF;^Nmm5p#I0B4AKf9L%f>B$=*w~uae%}?%4vUg17=Ny>V;SX1?H88Gb8qY}! z9SyYi>{~-X3LvQR0IMo%NQa8+S3W1mxZ%PBQFPmpd<}$qHOS>)LwRrsn;G(vhEsfL z^mO+Y!?gp$J3uCrqE0H_&7*!h$=@xESYoneLf42%2i;=!8CmL5vfZ-(P~!1fJqdk) zy$r%0usEnWFjP7?#d@Q_IjvQO^<p2g0ZXllaFiK%4-LW#>8pDNcRDfM=<`=$j`Qgm zp{hu+J1yRy)_{+zfSVXuFY>HJfUsfiT&ER)!{0G9&}uIqP8Hwdbgkd^8ykU=03WoW zL^Nf%6?S!7h2xUQdVl9=a|sg`33uuZ7N*V4>XaKY(i?1mnYspi`hZSX=bH4;d?0<= z4BhSa<~|<~G+>ue_u&<|xSCLs=Aq!tjEx6s1@zT<nWj%GQy!#=cFM@AjD&Uj4Nls4 z9IS*-@TrU&^PK38^%FXfRXvA7&jMN_kgiO|fEh0SaPA@Rmq5`zoK6Lu*ltHF9W?SQ zUrHd)a}Z~j9Y13m+?8x=JcQ|VS?WZI;G<_7G|HeV3pOhuWsrKzi4^aSL;|L?@JDV~ zrm>_Q5HV)!%*V&ZK2m(dzvqhnC7AIYaoKJvX(0?X+KSLre)kN)`ohaokz3&cD6N}$ z2G_?gP1O{p2S)~3z9B@YMRlUWN;O8sD83~*&o=#sGaMC(!f|0bywP=U4$B`>tG-ky zNyk{|WdVxRG6p0Hv-=2O{)1eq4=nEwFpFw%4JQtHSpc;%Y7|bJd=r~+oL4!{Nw+^) zbhc}X!-)!F@ZyKaw?Mb?t7~-jBYpP6J7|FBhEspF_CGX8MwOVGq3)xu5_eJ)mA(w* z5y_6d7A82SMNnu)rYVX7sm1SL&o1hQN`pK+$EYUNe3sNPPmK2As1bQ16*4W^NJn0Q zu-$wSupC<+q0TR4#Kk;iRXGikn-i5<%ZWV}-?lWTGaENuBI9gO*5E8kWNl1i?c6ju z6}$)}JS!{(*I42De~bAKvh#!IL1J_4;`U1>@8G0$RNeqJ$@*uvMn*6njlWPU!=~Nv z^Vi4t_StD15$REyJ(r?ASaUKUv+?<oIbz|7aX=WI5X<Rk+Z3;s6l6GKP}gcgz(?aH z2b)CLT`)T1?sUb<1sLvT$k0YQkBr|J$=|b=%7ao4XE@+&=|h=<BDD@nH;?OY`?Voh zQ>!Vm!!5y9pu1*rrdRgS{FP&{ltz(0xuP|DA;`yAy884(xp$VeO)sFIw#4`-UBqeP z3bbbn(a7x%AQcR!`NfX&AWdA;2DdfJzfNY1&m68wea6&=tzFp{C7Z(93y(~by)-SF z5_5f^B770dsCfnS0s@=Jj-2KBJoV`_!#v7Ssj^50JLNdZfM7D0EC0u@-$jt4g2J15 zQ>-@fpe1*dN)sS3HMLN>2Dk52dyv{^Q)q@mNT%GIhiL<1=uYm-Nf@6I2mLr7%8kH# zEgUsgXBOlg+CJdD8{nw4|I3O4u=5yi0he<bW*iuf96<`Fr^*DVio!m9V>Y>^L6TSm zv-e_O*9&Xhi+q3dVurA-i+qumH~-*8;R`wV1ZAInLh+^992HkV+NygFMSg3$Jl(bE zVaP0kq|{=6Vm!2J952X0z3!~t@_&oAbOhr0_0}BWWRRueo>hRchj;FRSp-kB>xQ0P z9}cjJTIde!G?htj*VSXziH`x`_0x)rlC+8PlS<!8@JAp`zF>3?XxP^<z4{wSR*8OL zZSn(-=J;&5+e^8x5}?e~a4L2+6fN<sxu)T;KTvKLEhyD!2Ds8rVJyyv+!cZp#AaXf z&A@_L04*KX@<8XFvONHKX{<;HEML7fcY<#%BQB-!7UmhK&7-p1R1>rx{>aDxqb|@{ z2N0CPd<%vr<G+5jWr1O9^AzFUIc(_oyp-Uc-8-WUVAsO+KCwKxclOww0!e}4sirig z1TmwaPY@ds0kVNJ64H$l0eqv?>}Vvi#6`Xeaw$LgtY)bQ+>{m*krr)>TY@X7TTdN# zV{%0<XAT&+N&INfk#n#;;x5O@FlV){AKxNyMt{%p9k7X?(>L)c$%eCT?2M+MsF!#4 zGANm7OVvla`Gf68EopS0w2HHN5H`l=%=5_n2^b8x0<pOfD{r)~<NNpT_g~mHi3<-Q zg^Lc&|Nc=@O-Y;Mp9kRmlREa7dHACv0B$$_CGHtxtb4ZjAoQ7^H;@E$3)jT6*fIv8 zt@g3wy^v*Gb0RiV*APW|yMdvm5y(6PdlnLl70+HNL>K5Nf$BTiV@;3u8w>TB&{OZZ z4g_7|YWqd7E?N(&0=WoyS8h`5QwyWDAu{@=5bH&D(}?@?5ctWf9D9sSXZ1m7YLAVI zOHx&tfH7nF03qV`u7U;)xWx;I3y0Sktv$Zqi7Mmsu!t~>R)lUmZulXz#&ia9_dvCp zK$}D0A-Txlzg(riT*J5ibP%XB{sW+AKd~81Xn7j3riecVIBWDsHqnyWnyx6oYkIHd zG8iiu!$Ki615Lyv*hn6r5PQ|ZAwuWZ1C!U>`Z}3uk~8N)l_Z?BZ@<G9h55_x`ybeJ z55YsToP0aB_Z$Dmh2DYOor_c5cCQ1m|3xcj)Cd0R{o|v18UXiiZ|`L|ov<x){jZKf z{Etx6W0m({xxXEJB(q1W``6V}9v!5ecG7wW7?bL@xBovpuFBbi`$YP-?Ot=K7f2pd zeDUABp1;4A-|vA1`+oTY<G(G&zZUd={`5z?SbVRW;9m-cvsQE*>2~w)|Gj050e&5e zdQ{<m|DVTC0-F-oxU{DM-T$^2fA9vv|9sfzzx~1gxJEDE1DkrOd;PCYF#eYn{M9EF z8M;k%SzrC{ZR$JSr-etq+H<GjpSS;&zU?CUtoJ4c`STK-zXYO$RqVazfAuW?@n^>( z!4kQxpV{L$`9B%>F>!i7HvQ50JxfLYu_900z!JSkZ+!6I|L02(q9e{+Jh^8g*gsqv z<8uM9L_CTOe>&&%pTGOlQ~kxy{_kv25aF&E=KU~&)CN<JmdVBcH?OJ;jCh2Xj@>(S z`a!iFhCVjY!UpshGhO;CI0NgivOVc#zyAaehI5KPMa{vYlxbz_U`jAttZm`H*rzJb z>4wV~?a*!hkvtJ*)7Zs36*^5evtKHH_63~H?)BWb*mgFpq41o3rUtw2ZZ8Lzh_u8# zjmGM*+eSo1`IvcIS$)1=hl>k4x%aPqd-LGBrQ>Vtdkx7}ySEn$j~?7lcNFjS4d*hy z=DOU(tMDvU5|teKi)_xCNtmX6QVW~InFNW1&!K9(WcDn$`E5Vv&9ku9L}f^RoTcM! zU#`4!mx1}-bhp{=cy@^8L;wVT-*p>yv<!(X#%qN>0OPmww6>MT0DqI~A7=aK4foeS zJ9_|_XS)j9v&@5nz}jTcZ39q<eTrg$w@RrIShQ-3kNf&^FaE3mjC??Alx7KEMph5c zSSCwHZta!SM)wxiUxQ5>zIx$-`VZiyo?UA7IuHot%;)g&BG5##({q#dL(b|9rZLgc z>z7L17I4R0Foz#3)<M|)Awhtlw@tbzF5WfpUFpAYZf}>sH}z1D7d*ZX#P=_R6J#68 zLro%7a`oTh2r0QOXqYEc8L~Vzc-z6^TMsF@)-u;*=*Oqs&XMXyC<MQCVAq31mholw z9oe68;`*8_8Yv42)+6nX4mWdbvQ*<EjJ37(Bl=@)4~<-DEa>7AGt0Y&@VKhweqGYl zS6b{D6Jyi9-$iTJ797z1cEz1ar#oy=K_jiF7cls>JUhkC07V+vFX#iigk6WJU8>Mf zqG~?t{)3Fi&Z<2AZQp<XFmV&SDmIHcp6gE-+L?d#-|B4OK2&?I!MV&hoUcarsQnfi z-(_9^pLNt6uQ##?ztwuW{*pjL!=|olc-?`+1TLgLIli{0>c-M{@soo=F1SR<%KBG? zb-!b~xcm4kagV-i@3dL_uohATr_v=U{&K&g+}HEk8+%O6{YjbKLrE{B>N&Wbk8>aG z|JY7jl&tEtqU`*dhSv0y^#%5BZdCX<2B7Xc8XZ7kF5&T;en(2P&N`OwpEvUvhM1^s z$(b1#If<YPQErY<YHR1##sB?@>8qmRdV~?X5%0xcJ&mliYh0+TweY8ul!nM;)dZRF zw`XiPS{P%m<(JLB_MNB?8!tCXeS>o}Lif10MwEuLElNpl3~dulLQNeRpoBcLleK{} zr`_B6(UA%9H6<MnPBz?osZZYaS}53JoiEAc&L!kU3e*HVz<Y0xq(vxV2a3I@L!YOA zMUPsX^4XnS>Lx|$Y&x`C+Ru@z<!euRS|oF?B~9B|XU3empyk=O+kW_a@E~y%iQ4{k zcvn%LHm<X)TTwmxBB{YfJp|!Jvxpky8cp-G$sil>>tCu8P%50hD{^jRmpPF(8E1EX z@!78bPA#&sF-t%yqGy*D-siBRJzoN7*I4DM>BLRy$=Wvv4h7sCl!fbBc1eEo$*k`s z&HPL8zqykrtptoJ;xMn*efk+BlcJI#{6Xto-PCf=>4uEEF`uhn=jS|igp=?!c|(a$ zb8~*dY#w8#RG}_-W-#Rfn>nTQzu4a&HtKf_xQ=f|GoNG)UYN9^oAk>_(`+MWPLD|C zLR5+rI!$UTT?!ooNk7pxT5)ywQ+ZVz>4e7V5bxWXcQ)ST=|BY=wPi&k`$^B{Zy+q^ z_a!VO3@f`w>3koP=htQO3MO_CZOJ{&Ec}$aNBQLxDoec=2jYW`5}sD{x628JRQ8WH zC@%#V3YnVZ4rZ2GoFI()nB@qeh^GaIPZ35a{O()43Ank`VQx8ldriiTRXui`d}@Va z&3bD{$H0J`Y|h1_0W;cRr8piT9!kB%j(%0aW~dY;mK%Gf`c~lL+mMJt0V$!;Iqay( z!=4RQ6ed+jC;Lh#mVD1|VQG8u-bc(%hkqTuS1>}>d7$DPc_Z~o6fxm{+q{1)p5n!` zeFOSMK?*Klr}$|E@w#a3`_bdFX`pa}_-Qp(ZoRU-&-vF^vPCip#fo*2^v?BNm~1TC z*w7Z;`eO4AS!c+HtHYCP7Y`I#YfAPM2h*bBhBc<aIvcdF$MUC~(h>FbNC_snWPF*E zW>qZBe7XR|&l++%X+@+BPOq(CpdH2t3lw+r+(fN>X8l$VCXrB{FttKuvtYMQYRK=w zcey!DJ`*yPU(05;{`LM<Tq$9`%1{Mk-gE<EuhkiQ1%oB68iYzh0-ST7I-<zIQxa(o z#UW7Q>;g6aph+un#$H7-6q<YGi*~AltZiqi8DERo-VE!YKLnimVN@P4Z9`H;O>(Z= zl#wbZ`4O!ywIvF1-NYG|+l}Ys`A&oscRly*nUSu44MErMmKYq?ic<}t{p_@Eetl6_ ztj|RD0h2?GD?-h>wAL>m=kr|5TG~rx1-Saq+JYhq`E99Ya%rWWT5GUnZ*UmubEZ@+ zXaB++CKpQ5JguxWhA6Ci%c4OJ@#<t7uk;B<K+?p&HGY36&(AmLQe<|6#rZYDL>29$ z=xH}Zn^>G*Poj?TfB*U98FU!UiZzs~acxwyZu#!>{e^4aHQLTss{7!{U5s|-z{LGj zALM8M_58-zr&(xBAMMvCi|-*F)tMemGIP$vsHp0-=Z19ud^K{bG~Ie@z7;yGY#;W_ zYW2C0xc}zNgTAV3OZF?(Tkk9|bNa2NkT7lYWZz$}P^sCcVOqSM@?hqc8ftQ%ezs&N zWKa&-2t$WJ$Gz5rq3n>>PQ<bU6FN&E1>Gs`h|ha3>uH;N?l~0dUbct015EehX@-N) zfa8_Y=8!AB6+PTOqSf29X`-WKXboejsJ;2Io|@fHSKU5b@&Y@nUVU_{@D4=DkvHe7 zBa$m|KeFdi$c1@5<3dcJ1j8dS<tcf3tXV}Zlvb?Hl)W4O)slm{qHlN@S{w$|?(VB} zEmyB9zIcp+m#&sfBDQK;L`Jox;$d18ms*Q^A%yTeok<zb)bcAe9z6-xlXG$Xi`L#$ zOCI^jvIV)6UN91lN2<sG<K1&S2|Jf0szmOQ_@&c1T--r5fNyO21m8GRvSZ|Dx<h&M ztC2kVb~v&7gaLK+$!@e6B8J(8Y6zUhbbs|ajXCl?=eT-A=Te*wU(jsVd-p}5$<}V7 zWFzge6jzOIZh%>NHbj!ttqBcFztV`p)tY{QFAbGv=1Y(GT2Bh-id{}Z)?~RkPIl=- zppFb$Jpru`@g$Za!~9Qy=JP2zFh`xL+#T)Oy+!|H*}!#}Xl8yS4=`bZ$AhNh4ZXH? zXy>-&h7$Lu)P}73d~icuv%&Xu+=UlNGFXerZ?E=M-JU5@=>3$y9}VHxV%G|B@79{k z>UM?J;d3grB64DstF&cqD6UT?(aIt0cxUN;DO+B+ZmxT-7*Y+JLrt^}={vEXHwF%T zQP!5ZIBC?Xn5EGsNLa_kZ(>S@EIpcC#ZDZV8eZ8V)2wp?Cf}U8PWu_Bo*<_A5O+n% z%f|a}!83h#M9xWmt%=#XQmeFe+aPUcIo0u3fKRY=Wzap%xISwHPnwwzDWe!?BHvwW zxQUT?*q@p`FkqO8A7T7p7zTwnFV4A;c=qN-<^|67RVew(`eUnsi$ww#t2h^+{ymyq zv!&<Nsf;`bRbu}BbcS5tcKf%EC|;=$%@o1&+S_BVRwkYb7Aw_me%BJW|M_vD!mxlO z5EB)_wzTmnPlt4ulql-Id$Ewzhsv!)c~YAl5sgC$v{HY2*v>TYCdfn3r3buD)XgQq zhU2b^MMG5`!~?r))0=H92ph8WnWCGf!zbdkYMps0<|R7#SY#osafM(*sP*5>+MIIT zMQhBO4=%9V)sUHu15hy_JuyHwL0tk`r{o*_!h(te`!h%9euD)(>7JWIl7tJhg!6Jj zw(QYNj&_uU#>E*Ti18-y#b#j?%6y&h#-S(c784;p$^$;Qim~|5iRB-$=^*2J3BY)C zn8{x^d08;=h;E}XJi)M<Bh1wMz8_a1t^rzK&u>pji|LfZy6b^w#Wk0fbgk{Wr1=kr zgX~Mah<lL%+E`+6pIWD$ANl0KA=^%a)I8Jb>#Q~FUlt6RW(^G$+qq!LJJUnZU);4c zfBfoLGyf|#jKK>Leh}Bz*me!8{DxR3r{KZ-iDb*b?QI<!GTZQMfs8|EO!2&lpu>^_ z%WO6)*8Cs4SJ8W}Qkk7N;E!IYK*jtmbz9Hsa(zt<UVgn$aYYbZ)&C~$MzGzsQJCc* zGuDvoD)wmb1If_no>u3EcR`8VgTX-}F+8DWZ`<%j7FeRXT1PwJYbQ5um628w_}}Q$ z#&@5_2RLEWcY1<%O;hy{Aprt?+oefVXH&QDZBo-4n>&r;AUwxnbUs|WSQGfO_7Q=k zJyo-QCvbIu(>MVZMPkA7)vO(N|HYTq)PRQTiS_w!M=75q*SdwE8s!4fa#sh(4quV; z#%jRen<eHCFcRnbZe(_BpdZ|>MBqMk_ZQo#Bl`xp)sfwDo>OgxI1{_J_s4q>I>ZQ| zHQC$FIe2{^sMbFF3YvkpZ!|DChKKT%%XU!PrCZQZ=_4Do%|3aZrhNH;=rBu1Qeve2 zk(l7Blxq%tDVi2gt~i`lV+7A>BkdG3m&h7cZ1B(5=~i8<xB3GsJoKuw^22p`4V=*$ zFz_Z0L*_#*dxmn=u#1qW#TozHT+D`T_Ki>zgjN!^!yrK7L1jnl@E$|cC=(-=W4#CM znvJPi7&%%>n;T2|l~k*U-kFo}Pq6+~=0o=`8yDE>@JakzkYsX_XK@by7C!yg^I_#w zGlIM^ZM#TjeU)ruCdFIm&_-KkE1C!Q&qt~jumULs{r<bn>jxtv`}<X9UkRr6!DdYM z_0HXQ^#9qdDdtQvoxEj7F;W^F@q=UVAlt^0@8l?k>h<W-M9fNd;)JWcGX@;tAF(@u zu7@03iR+sGYo+(M4f*5(@b_PWewcm2uXYDq8QNZc!`6t%r##xi6cVHY>OT;vs@}u> zF$xEv{C|X}{$9jAU-iJPFKD`X4Fq7(&rk?+X@UR4-g`zxnRRW0ZNPwHBq#{jC?X)x z269vzQ4z2u2}KfALKPrcvS5~^qJTsbAfd=rWDq4tRzi^}B|{M;XPA9}?*5*6XT9ro z=Er=q=Kb@mUfs`4-S;_r@3Z%HU3(u6$I)kmt@@H(wBuw`f_>XF{~u=`mJ}<oIlRZW z$<(V~Rqb`ESI6JKtePRr&iiL@b8XN|1>#Tq4QGW<DK8f#o%GlR&4tvo+wv*S&1{FH zYbj#vj%12t+aZiKoWqtaWc!DPLVn33^7Cm<oywN2;(4Qa%W<sDvB`H?K%Jd0F&nT$ zrY+f`p6OzG+x3<pyE=Jx=ZtfS@Ohd!d0g!dvjt6q?Wg9HM%#AkJ%eqV9Wu&)oY-cq zOzA!ghuKb*rStY}4Uh=yimEpWa4x4&S&aOMQ^)kD<}B^%DMRFz1V#&UJXif<%Ru08 zx~K;6H6eE(VX|GlO@sXpbN;K^ZHZ2yWi)c}(dkzr=FJl7da5mhO{tP<{Uj-0tRd7k z9pZz7*VL!mkdqH@fhelOZZR|rV{NP97~WH^udnBJL&tS+r%}bv<4tzP%45(6J}$i= z`S9F$Sr1_#kaN6kCd6myNVWy7Yl74L$gum5rT9z<v1a%*+HkgrVD@4S!EOFhGtbhs zk?NZbxvp2@%<#E<aWU>N2DE-$`3>dnPMHiw3!;T06okadb{yW@X*za8oK0_)dA(~@ zsQ5F_4`JfX^yH}In^mEi${*~;x=JH2<Bi+jV9wOfK&H;4=6t&%ZaADhb!5rS*7yTS zs{7QGt57S8q#sp0DlgaOTGrUUNjKT)+Unwn@Of3r4eHReSM3tjIE7Xf+Qr>Ccb~1= zJA2vML1Z$WkY^O?&?t&xyt~VkpH)w_Ohat{X3nMQeUgU$gL?X#asQkM;}>|S5(}P< zZml7>Hd>`u!o5p)Y1MEW=XLekt96?1l7^-VaXVS#p&oz9rE7JzmtLKSoCsMT*Zoc- ziA20ZAm(*jE$qP<VbW3Zi_3|~9}!Ai;8xC<dq-qcZtdJ_)mZhg<W9hHvjoRXgltJi zw-++FiY)YL2sC)N?4~1<Xlq^9(#~0;Brdaw;XvJhTh{HL?i}8zawEp(aHMq3cf80C zX^*vpFRZ;Dd-uzshd$o!;(dcbY5i1kFn+R#Gd$~%VCG`S%tt|ml696cNm-7$2`Jw` zUw@U~&-b2E?MdEK5~elpYu?3N!RNly_#iAj{;Jwm;87bTyKMZz(94&qE%c<QgJuJo zkjM4Lo{g)&R_|n_6w&TPjT}9I?cm$h#}B7N-t&=?k=(&4#SW;<@Z3qLTK$-QfN`%# z@UG5t795Hi#~!mV%W_s9b2;KR`&FnTGEzu>z~;xiP*r%jVv?{@-xJAvmWEavJCStT z6K-`gSCVW(l*m5U?mjF^6?oGwE4BD=rv9@iX`ta$8t>BLp@zbw-#=@&Ri90oj08-} zG&PzWteF*b;s^WaGI`!TQrOqL`EZYD{sEOrcfN3)pVt*9o5cmF!}n`W@+`OUj^?rN z)a{*}%>9eMwORK~E#n=fH<W>&1P34e+$rq>iuUO`#_IwPwc|=Yun>sC8cul<3)<3A zc&rK2eeibu57XlgdE^0-*wW?_^1a`C9WHWJ7f{sk8bwx`uJ0K?4%NNYmT>R>m})e3 zZ+X$Un?IuRY?5$gZ?No=vyN2*(@Ry_Pe&CxT<u1)`v6_4<qETJ=5)E$pZ?CB!~{k6 zwAu$@A6@=NZAYQWnE0;XggCze1HAY~NjT`Q2saEYr5aaTsE?Ae+JrG4^m7-%e-pdr zXPx!+7oA)Haw(pKOFL-EkbQ-8E_@dHlGPPsh!3tStLN_=YZ3B^koj3l@SRk?BH!TN zJmQtyU5u+A3<xy92e)!YEE>5C1tbRW^;1F#J|QuCh8lLvIuX+Q?S-|odo^O?ojQF) zDF=?-|Eo5FagS1dy}}K7iZ+vW`J(fQy5RQX=FMrxT!PobnDR@H8P5}?daA;3j#}e6 zF!79C+k(vU)SM<hz4f0Y^}qdxuekM|#9ZznX<@qX(B(f@io9gV(7F?a5}SDF<fzN! zorP4DW}XMzS)WgePiE~O{o>{KB4IPbKE`~p{-}5&(ZAMx;Z?@+{rS`$eV2}?eWz|+ zw$B9&s&S-a`euWq50y91s?V{>PC=O?eEPDfl3)J9?RdRxr!a+L5g(sLs*9Y<t@NEm zAJ=VYFcqQ(N%xjz4CE*B=@(ZV5{hTr<2n(kU{p+szP`AVI{QcrfUoNfBxRH!UpXiW z-ExMtL4YXK4QONqdS+|3&gNzejV{OH%pE=jZMFJ|+wpjjOwF&KHnaIYN%+8GLFC9f zaErK|q%j?nqg<~qiJK*j&H9F>vz#9?v8x9ngt+$+(Pi0t|4GBSiT;=Ap?k3fy@L+0 zkeTy`7ah)&YX~CHasRde0dLXmtgIpF8?{9N9ZtM=$oZn{QcYmz=&YXteT4=2$IVeu zfj89vHc!{loXlI(!ddiPkB4i`*yf}xyxb>DP(wiVC4v@p)7b~p%~E{&b+7K=yKP8S z&RG}n-I6pqoFv;hb8yurE56>|P>i5jE-!#;A<gabcjWxG*$RKxPUs2dOADxWc1aNs zGU4}@a2)G854EN9`_US!23?1jZUB<_bYokHJ94`>!mZywUsgQ(%|m&c)Lh@Y;|{~E zH8%ZHi!oAVKk6B;D=ExorKnhjR=+yQbETd#aOl1^-IX|X9c)YewGUR>XX!rC?>CRu zZEBk4O6nTXDsW&rh8)D`&RIBc=WKZ&g*(4_2-3eFAjYQI@2)n&7T8bSgBW(!<(wDB z^;p>yMt%6j8R}PhOw7fa%QE~9`?M?K8`rNqRKx-|E-#6ut;ZOW(QnC}g?v+_&^T?& zuRJsg$Gq1v0})$#zPdGEcfsZ{jP4ik#AvH%PcFN_N!sClmj72){C~D-)tu$lw#k`5 zw~a;6n_Ds9Pa7MXc%94>lz6RLqmdJTV00R^5&8z-KYARu?fYtCX7=RSA%)^|Z03v` zHf{f~e7Qh-nU$c+h8qz}Hc%C93*QqYYN?kq)8UnDRv#Pgbl(c&i3Amr$0XxEsoS>* z<ffa)Em|`s5Ae$RrEkPVXoo}{Y7CC>tI<5EMJ8xTjaQs}=2m;$u<+59V9CWDY$C_5 z)U=$&ZjKW^a&v(Vn(<e6D+UVsXzDpa%6s#K%goQ9KdznWPq(~DakT`YxlH*PzqIS6 z&jCE<;WD2~D=6{f<a~F|hi@YIQaENaK9%yZqsXiCK8xN_rnE~%{ORpZw0+{%*WUyl zYt-(mZjkm@Vbbn}qnw^(kuzF++^VB+cz@$jY%yuYiT$=oEl$)X8x(lf<X6Z*etSmK z0$zV~==m~>)WXltk!j@1_@(@p`!VM6uo2qpyKmv=G+WZCZu8o@^j0cKq+Jm)y+_0? z+jDL&&Gstx4{}*{h3`Fa0<#eveIS?lwVRDPQ@l^qLg)AqU17xn3g6Qudq=A4bWyB& zoMDJL`SD-x&P}Fw<FM9OTXvc3^C<FSDe3f+<+!_fr*D`;d*)Nx9)duZF)jRpkWR)Q z;Z6fF`;Hs_c`5i*jPoX8ddx3;@T>89C<T2Y6$6U{a2ETGioN?&9c?FzJc+t5@ppQG z8SlTEWN@00T*RE*9~C0<@yRg{%w{oR=@F>s#2fB9)zo;q)5oZz@bUf65W!n{Xn9*@ zXTIM}HY|G5=r%uOH1qREn`qKA>}P?8%<ikmO<?Pf6B?5muRUa+yS{@qex-mi`Hg=j z)qSou!mu(}c$A!;E3j$DfvZ33gD`TM$SR*&d+>4wq5rv>b~cG*q94!JJ6|HZlzgf= zA$z#l=N;=4%&V}gPtk*`cLN=6-nw^UG}|l3YCfn6ZuY<!@Z~J%o`mjXBvcNO2WB5L zId)Fv_=;kE_%{zjdPZ^1qm9}X(hUNsf@8%)%)i~m{`4lsqt-H)lSu1CuPX$qb@{Y> z6CQxX0;7G+g~&Y{_wXbxnx|{Tr+;BfH{7p``MZDfpkG}!ewpj;k^=DNmeevQ%#X_{ ztS;M7pQgn}H~IDqh8gi=kFmb_7F(~?S8q$9dm?`RnW)9_qRL>*Pfz5mJ}sRuYd!yf z%d)lgZSX#fi?(G~pV6AV+#a9krXhQ&sm#Xy;o)ocGF~<CBNN%nA3i^@KYeZWX@Nm9 zZuv2bV`swhfDblc53LTX>gzAFas`y)R)2PFby&8C%pdk*|L}+F<*PQ+lczp2;PkBF zX-5V(%y0gi2W9nX@>0O!KrC3BzUxshjKUIl_pn{7roNXm>suDHW2Z=SdJ+fbl<}+X z>W|wu2p;_|2IfZBC2|A%)BI~6UdCM7tN0|xYMj?h1M}(ldSuPi=ND^@PaXwNIeALx z2<A-_XIc4h;oguqo@h<*aeu*L>`%)xuP)mcNlWQoy)K{ZaK&jc%sXy!b=B0_>~ZA$ zByf}|&1kFy1vpB?+Okzk<FxI-USyXD0m_)e)4*D2oURqIH-e{)-1o;}1hK(hbXS*+ zM5E^ZQ~`To=Af>}e16I8+`1B$o6m;4-SQbdUC?j<v#HDJZ&`hsMe@j%4H*)@{l-0X z;m^}*JR`@(iYvFiF~uI>TUXyBID4qhc`x5sF;PuB*-$=bsu`bb-CcGy*U2tkD>W*m zN!(+{w(SSR{@BF8$_SH^d_;48Frb5Xhiu#-jJrL?b-ByswE<qw{x8mocVE~XhMGyq zw*56G78cCchwkRq>g&DoOi)r%LP_twKMdsDwa9kZ(z(mGR?TQz-Yms<PQEQ8<_ztv zyjO}GJ~rVMA3lm&aCfr;{UM1hw(<wn=XFVD>M?Vc&BihGw<K!9S*4xQ=o55LPj1Kd zA-RH=>s$;x_Yj6Ahn`2UFk5xz%?-poTY7)!zLqL?C8LSmuD-1vj0B~Z9;#o2+1V^I zuJx;^<dg?tbm%@o53ZgK<#-l?fa1hw>Ra<xNd6R{uIX2d9C-El;m(nV`jyY;&lrHZ zG?K5NzYQ&!S>1=cpa%}vmP_vS+4b_}n>7Rn-0u?c4aF2mb1g@zqwKDuHN^s&W=hM4 z7lMu(shJLKo<C`+c&Q%8BB-+uq#}Q13R`lPxL~t#6Yk6JT@2zLr|WFLy^zP#5EbG{ ztUu-BuKU6#WNH5l<Izhcjmbv$wb|V9J@)r`TP56=7HSr*d~|9|x|SIh_jos^{OGGY z-IM1#<0tQL#NlHm{GH_+Gw`_Bt@%)!Me@CRr{L%N<MYF5|3sr#j!Y<FJf@v?^vdM- zN|Baho3`(J+7J`Wf^F9q)n0Fz{YaMaF~H+aEPi*}EixRJn8(_$66^hn@4Cd6uG0tA zcPYQTC)#@Jb4`R)Md<--%XIKE^U*UhNm301`UMoTXKuE-_k1Lw0=Yt>Bpa0!e|>Xy z{#!$Gozyl|)%H|J=*KooK;0;%Qs0X02m0Vp{j!-nskIT;^1OfZ^mv|?s(gjo{*!dt zs!8>y52~!^Z46$Y5cB+2?9o9?YYv!--o~-;o$<Yg8?{xU3tGnVtXPEf4pdfF9=GoL zf(ntl?hNJp!5?cP6^%-tbAPL=V?;G&^F$>!ZJfs@<}c1NNgTNbc<HcBmgV!suCv{N zos$jvLh5)WbsYP~vBOVeNl~%Hvk$gERuXACtfG371>12umnqji?lCFj_s`!BgJK?{ z+>v4*HkJ*WcMyIiUUQV+d*1ov(6=<vS`Ism(e$9uddKR`6q`K+Vs!0yb4O~1P|nXY z{oA}bW%CO1?{zb&f8-Dt2s~+t={<b}o=&?MNV|e$L4u(@VA_ZORO5HHq;&ztej*<g z(l%oHBMe7Z{)qMehJU05_ohJHrl+v0)L(LvRC}_&Z_p;p>2XyiOCQ{_<-BF@VN40k zLCuv#|NQTw=k8_=6~=2+go>w5L>hzU@Z^W!Gx6Or-E#Oz%K3E-ysA+z!ddKz6w_8( zd*eHwIO7}G0`tU+6~mzj{A2XahfawB6fwvjEoVlb4*HtcB;p_VOnoAC|H+$$6aBS# z;miiP^|{FkY>&#XV02e*kDT5OwBuXIlv>lD<Q&V^_ALw)dUArwYm#;O`_}~ej43s` zhWa>7&Af!3+1ID<nzcKmI&X9`^0TkL<Z}j7_9N%M2l-cfI?OFzvsGBOcpwB14y2jZ zHZP2IKOK0%tC}L?&}OGu7dOzXT94Z)yYTb&&w|aVj&%~I0FhOSQl;#n@D}@iXM<t2 z^t3k|vJC?RdMqpNa!s4#cT79@I$~RwH0RZk@&EW@jZL_gxmI>!j>MWovGSS|SF6*P z-yasXX4>HM5{V(a-<<%ZDY>_GZ}UKnz;vLHK8HiAm4a!!ny`Ih?$ncY8#dXNH#)Mu z7o9Q*r{D##?K0!pgCnyaot}6SCv91C1V-4>MbF#?B@<W@dp94Ey={j%olf=%ja&zN z?#AF^MRgrZK(zMBy1C^3XowS_v#4DtE7`D9Y8ON%UVV`YOUuD;4+>ItVe;3FPD(4k zzUx1J-5i(@?MM<);Dm8i=#@#z1kTacID>Seac<PlT}oMUD6LGh=sCAy$2SE|0d+g2 z5!bo?=+vn;`xXJWpLaR0H?+BOH8!`sEykGK%9YTe=$^jkpRmyadV{gvh!Zz1_S*t; z)Qw|b3KKEcR(pU`H4wRfDZBH-c5*mCh_sD3erocqAD_gg8jTcxZx~yQSR6D?hBtXB zls&1LM8b(PoWo?HK(A~?W?>1<L4R+A&|M-)#+4p#KXv{M1vCv(Ss->W_9XT%y3dyD z8%DTJl}1^^r<at-OeGIyQA!aeIoliIhd`)eUv@T}MdpoD!ou|f+F9u)-`-x$w7wxV z@%8MpPvq{-hWsVF?d%e1w&H-1q9d%|1@hC;v2vlns?Ct}vRUR>^}C>yYnO%7$enc? z45!;1grxoFqKs>je>`9--o!?@ERi^^8LN{GsMeLhYo8w!eR+0>W;ddS0~=6QI&X~4 z2DYSaS#gL%TmRP(prNTooG#6VD~3ANAL6pnm%}~gg1iT_028%kRgG5mNv)EIv(;PF z3?A<e)aOWVv&Y$~UJ4aEUM1G)eY@JZJyp4dy1Ym&flid{s>L!k#4Cq74`)W=RAgqa z59Ti~Y1y2}>j}np(Dav`PKk;dPb1rIG>ap43|YE6+b@4P<mU}fOVVm7rFrwiY$~#g zwlU(BvmIVS7IFCKk?CTNp^5iWnEd{ZTt3T65Ze47u{(Z87<saEO|NDdm)SO7zpi;0 z1fVYsXb)v3-pq_FFOAf=sJ@=SQRg(`GNa8t@Xk0E(6OXt>C?FG|Cv8UN-X;D*SnOS zs!g~Eut^l2QhIB%RV4b~4`#VM_awGhWShHm^Xos3OUR0KksQvQZqHK3ao+y!A=dHe zP|8wogu7eZ^bH1GseT{n=Asx!Z%n|Chubl9+UBYp`saXP<2`ki{q>M<EHMnmmV6xm z>@ZkeO6gjQkjsy4MEhBJ7Tq-E52TnF-u6*nhr)M$GFi&Pl+QaP;~6akc3M}7_ZH?j zj(0#WSry;sC^DlbyM(Pt3{KmbzA@^ZM5A_=)@XyT<nTT(W`QUC2^n1EMnkU;J7V4D zTd2Nj&q%~Wbe6TL{pV>JoxYNWkXM`AjZ7?_Ykn7|m$Mn$arA|_a_D{Eh@<tNx{YfW zwRm@b6OU5xi=hvBTgWoVdptjMciKeE>l}dS+g*NpFuA1T_1zt1393&ga>lE?(sQ~6 zj+=aaU9)H$?sz0`p<AeA#C>@_o{Efg#Hk@Wg=TvKNYwL?M?PC0vl2FM=94^$dwG<- z6WteS$*>-U-wW=h=5_l+VEu!`ePOhwyWM$&o!1*jy9&kJ;ohE}o+~!QgO<cz37f=k zQzpig1dQ<dEE6)q@X&v<LG!e)2J{+HWyJ17_hlP;`z{qx7!N9q=mQEqRTg5ICw}Bo z&{JLa0o8~n`b%Shku<foi3e;JCiQy5GiCgVf6@mAlw4fbxV%ukd`vXZEt{2~^n=Qu zm~%p;+2ox@r@EfY@;ucKc@d1r4RfFjy0@687j5MUeL9)FG)Ggs)--rdH1t$6$Izxd zXR)Q8PdBXO47>jg80WSzfP@}pKe0!f>&>C(qwPkZZb!w*YJxCaznjPECo^XvHTLDu zGf0ifB4bqTB5Pp)MG5#6jIFkGpKnPX$(z5~P|p)%NK=rV`!;Cbta#|0_m>3pJs$Ok z{G?8>MN>)_X2*^Eq^G_F=S@Tk6~XxvrW>fDEW0={)34&Loz;3X#Jr<dcG)pArp1lT z+|QoP+XI{3^{K9W(vN?W0{En=U}1hxsl6JC9o|m9dDs5?_0|llQU8dA!{_`s!Y1>; z$~(Z+%M(VjXbBoaTA%e*u9(<EAS}1g)I2+6mPE>WLYVT5_3y8ZE)hs?y^<BOK2vt# z$4=bj)ht`nL3pYu7osSClyX?Gu0z9xcSGNXt&i`(Rw26buO!>-|DKw)Zwjox+Jd0& z*_kM5*B4YxHx>PjxM`~U@_8m@1s_)R@N@msz$#dUS{3ha%B@Sx?F`IcxIyVgesgz+ z#L?_wv!qF|bRPzh#)E9pM1ShiRQl*+6&Egi!j8hIi$u`n%+7q~T-pW14X?g^i>1oQ zH;a%DSQmLUd7j_*?-?6?_OAaCrLw%AAQItHUpG|xF8VG}axm#^Tt6aghzSl&Hv%&N z@4&V3>+879c3TZ+_67(^E;<jyL>HIxM1)E=)PmkAPD7slu&AhLVEZgsLYDg1$(c?+ zzZT2<+&VJwZAEk}I!`N!k}6J4e)hdOKJu870=ebgD<?+aziF#9J8S%Q^3=I{3W(f` zLd9)TbYFgopxGR-{_^`@9G!)GRlIAJC5iR7@C^w*5ML#(Fx8EXrba9tBf~K;c4Nx9 zlsm8xk_03R?s}oB{8+_p%)iM<nw_8|bn$d6;}@k8W*lL6daJeK_I-aG?5PZ4aULRR z_PfpTPdYYax4+bN=)C}|J{%gCjM21ZS4x)oJ^!)lZS1GaEwgPMo|2*H4X$o;pt8f6 zUKuqt?2s=hm^)DeXP0%GT|?0;<8ZCKW9pLZVz&(J+uBQi{`hPJp<OGz9pW*hT~=RD zZ@w{-1EJ)QIx%;wjK4EZf6=UeshG|3K>mE&h~d}Qr?v8jrTY7&o5O5`nXwu7qQXjI zs(&Vl6s~+Xz1y{O%GM-FqEW(mYOqGulDap-6)5-PAVMSbtrb71w1iOG!;m8@)$3U1 zPErP$NEF9^VqGS%iH79@=@lR$uVE{ooQg&<Woe;THZUqs%Aa<;<iOtPRvVT6b}*^~ z(cw;J{W&~K1Mt#&ZA6Y>dubn`l>=D_w9{)ag|oydF-W4%|DcQ=M?#=C)!|}%aipkG z=aY@v>?f4em7sEH{t*8q3vuFbjD`y=hT2p;{=8FGDm%e$?CaT`MPN8=a7t-KXU>J~ zl=>-G>82tz`saaLAJ;XFPq#Z;5|RowHQ71yK-Bc>h)=EJvulAcc(Go4y3xpQFnN-p z@8$-QRZXpPnomQP5+=j{%_D2YW<qlQIi`kuz8YAnnG+Kz&ARKFeKiP5Ei>p82Kb4g z$ya81VH`(yQT1%B#T+DaS#{BnV<cn^#NHkcp@vGUI$DociUxkl>~EKuAKVKE^G!f0 zWN2gXhDE10v%k=dUfO%)(wBsC9vT~gxTDK$p_9|ETrfNS)Pi94;2uqVSG!qt>9H0z z=QM>Q7MHOlwyfGKHZ=3E-9u?rua2wbQBlXK&ZJ5DYIiy)kSM3pt3VI|iD{$rNNy^( z51Xi<MP_%vKFEFSN0#PC{NDLW@MSCiprDk;zSXKkqh8k?zN=7n;j=6ffW(XxKHl32 z22X%XX-Sa=rn6#0X|)~q_9~kQE1Th{26)y^2myqJE*{wn(L`VO#Rhkuvpc2J>T`$l z`XJ|Pvk~F=zindtr>^RfNZEX`QA4%<Mh-_BgL_sox%|bJ{K(oGD~gtJQC*B`d0-@U zn_$L4!K}Ww8q-%k+MyD9_n~uz;0Jql%FGzVO)<K)xlXs?+7wwfbKEwNf@|uU7JWq9 z#mFG92s&kY1(HbpqirL;1~ks4pAS*Zl{)=G1p;#Td7F{acj;3mDhUOR*y`R>;VX$= zA>BWTp5^^bcy)E99~3n$Qrt^iZ4k7ndqeG65hj^FW}RCl$wtu4xOFXOJZz+*{neQW z5Pjgi4~U=0PQO_hobmJ1HY11#O}S1y`U@Q_op(7G3@QbAuR2v6FNv=WSf7Au^?o-8 zQ1mD2v5z=bI~%s=I5uVbLRKO+KbV?I3zz68N;j*Hiwa`g3f`6T*Y4l*H{?wLW7nnA zbs)mSfr?JgHG9|&g#J#i`HkwG|Dv~Cnyq#hw7Xz)sWaU{$7Qr=lxXS7Pn&{&n|G5` zn?0Nrr`9$pnm?;VNgumDk~{5tvRdk7`$Uwedb?h))O}q3Tz%rr@na4d&`)5PS}Ed% zGTiq0_UQo25-2Lz*a%6d?c2WjqkJk-&LVTUUQ?evdoV?DHl@VP*~V|Gk!$-UOaVad z^GY?mQ|I6CYj2VS-Sf?x*tbTK)gwvos(Y=nQMeyn7_TltdD+kJ1xRRvpb21Zl2mJT z^IRNr7RJg)oAHS+*Ari=8%hdaW(&(7^|35;$Quv4Li=A%y4ODKZ4TM#(<alEt>-bC zP|Au44gPCF`u8@`K*ekyB;{xgs(bF1{V^$~5?4Nui0=-)X(Wf+<-2;^C=VzKcOKpk z=y7CKbcCCe%b*b_e!0M+?;DGA8e5b(M%^B(&PsGy_&2%;odw`VM>0kD$`+CP+O>)A zdN-njbOsdSuX4XT<j?C8vihT%>{P-hx;chQ?e>RYtMd$304o)MGyXmb?M(i-k5iSW zIMrXpoA36ZX!Er>SWXVqTh%Iw7R`)yc2aPbK8k{whuIcT8g#Ns>`?Y_zkgo)JLz1R zPHfW9%a>L%;u!jWP=TC#o8Z2|#TXWA0nHogZ%Dn%(ct>d9YIFETD|hX3XQf<mqgC_ zu#%K<Ds5lNIfh~;_hSU$4e1dEp)#3RKp`bhD&y*D(ZqFh0Q8Tuvc@NT*WN)A`OaM0 z{k%$cc|k*6yY_H8l%6Y2U-SM#u0(In@HajApC_;#4Awj=o|_b4K--H~-3$(V$){-* z@(!A}aMiSf!>R-V-#)}JfJr0CP-pIlt0jaRYuAUv6XR1R(WO{n2cZ>s#Sb*AYuiW9 zATqOwaHqTJ9mCJv0+{4YavN6G$M0W%{NLw~RE3BNLF9!4(8tST!)B40hNPUgTi@rt znlfn)G4+$cmZcti%Ccf_;{O(vf%<OZdhUyp`y}lN2r4Vcf6&dOWYAsq@;GpJ`%YXx zPe>^}A|H}Bi;af-`f(+xc?ZoFYap~2i;ypr@<=-9z3=$jwIArOiA;uc49PM<I>%%_ z*MqHVPHw}Bqx$Lnv(pJzlew4$b*{!zf@8_R*<CoeLMN4Y@8A3R0YiChUB!KDA;3*= zC9|wvgL<AMS@l9;08sT~MgqcU?z-<T=YJwcpB806`{=R32<lY%S7b+w>!Pyp$<v(# zn(7uZnY(u9Fw3>S8RjQJF(;z#%5iMG!u><-uiKyDvZ}?>&!H8VY!<%kh6h*IhH@L? z5)*;Q+^^7p*ixvASoB?z)kt;|ecz4)_~|xr|Cq4f`tkq)FL^KKm$4)Zq*XQect=XP zHWZuj^p&h~j*e@bqgRTR>WvDu-%qj|mH7I=G^DA!iAIkd84&_o5h0s@illa0Onr|% zHn>CXpyhh`(#;g>y#!)pjmj{S>Du}rfx?y7morEagqi~aC+};kesls2AcAKDmby%w zqOh?ezj<7GeSY>Mp~hW$_u4wOWUQHH4GYKxY`1gD<Z!wQC=MyE9A6+cxTv~jo|E0S zIoQsTb?fcuo&@ZS*YP&MN1l_F6zTx--9jmXZ}H?$fLbo+k|Ks$+I1{qYqCHkaTR14 z{UkopR8*b=MrvGrRY$<IrX#RuWjt}I{$#D})(BtNf`Bs#{SGKcxb^*`+l;V4rsT$& zaBV=--pk>ejr&E}O;tc$z<X&3D<|Ae_au1dr36o+BUM*gEFZ)?Bz!{vpQ#7{$?F<8 z-k-3QWjgOciHeH4a^pLYGZU(G<=AvauT55UKFsFncAYLDh~wXX<wj+Lo-<KR*9MMR z&xDs^Rg&CKJ>y|27N7b$x6f?tug~D4Cv36(ePC`^bZ0#`SSAakKz=oUr<@<V8t3<o zD*{b{LT1{l3!vqh@_zIe_NPbJ1b0hIv@v@4?d%+ro!H_8!0aBI)>KFZqQ<%1Aoio+ z0!6SLy5PxQBi4jtC6Y*;$>}bl$b${Lhgbr}i)5+9c~iFRWOzC`-@6{0VWRg=*tV)* znjF!a;ej39=4S~P#A5XS*AG`$eCUnRU-z!T(_Y{4zSy4@T6@}y-QV8<_N(XK*Hc)3 z`i|W2>eH2N_5-<Fk-ReexhVGUHeA(PX~*YMh^a`_s<GuR_NO=P0kPfB<qg06w!G|e z<oLkfm%t3RIWIJfx5@tOO+O~_<xS<DZ=q+Z7~dsi9yoWD<#=2dL5u!SDn9<k7m>3X zc%AJ6XIad=6*IneCn@Rt>3uZ%fb|xyzJ~)1^EN}{?bGAZ?s=}VRGE>H=_UK}-btri z{gH<G$*g?+#p#UP*r4f&1*YV&W|xPR&p9>u_sk$psnraK;s%X0GezuzO99-$^4X8{ z1(bkXahkSnfT)Eo&Dsys`HKp|w3{Bskg6ItT2(n#Y+e*mS%cl_!q#gcp($n76gO3Q z%(69L@kYEa=AS;L2XBs4uI|$nvah;*LL6PTVT_XS8{B)otZ1tL4B^`L`KDv%&0mrJ zeH_@e^0b}ZV9*IiV_39TyMi}iE0DoMj!LdBTDgU!F!f$aj#Y?CX$W@PCvQwyk)l)d z@HI{F>LR@N3a~%DZSBM9%nJ<zpLhG<2DK-##~Y&j>Y`Ip6!BNlhclFa`x8^~yt1$H zjH|28WcdU3TVtW=SQzI0uH${$jg*UF5#$@x3NW@N{o(=a-`z>KvTEiF4%G|(TJ0IH zO48wCb&Stn=33n`#WdcOKM3&I%r2GBFu!^H4)pZ$Zi4RakddU>fu!-O`lC;~eX?1b za<I$UUy$Si#y$A&62~<TXL%BD<d$4KkqN8#{La1?m|rFBw>5wLxQQ^h2TYGGyQjy= zVrgL(b@g~Q*!6w=9kd0iUo{9fU~<yvREs5x6eZhXqz`0*r$aFJw4~{+{mw|954@xG zG6BIf9RqQR2_lvA!`VV2P1pU+h8RpiA}G{>NMz6iet10(zk(nWI&(g(Tl3XvZDVui zVUWN*Y985uHd?~TiGW|$$sQm(q}_m=(Qvk}2s(}$%glyYG48tDYde@^LarJKpH(+@ z><o}}GcTZAZ-*hNw{2l@K5!q?&y$*)9^p*Pn=>QzZp6hsGr<^v<l>mY6TUts5^vMM zg#+e|<M<orIyslce5I!v-v;~v82&yPm)kzor!<o$W8)H@0PcG%>@&!ji~zk?qS0er zpUWJpQ8u82Lt7yA%UH3lfPs8YT7m)89J-wzn>ikl&09on7lLl6#9u{5%V@cWo(u$6 zzRzf9Nwf{j!r*1ZxL%hv)_$s9!>qmZIz*;KwNXp>uwd^gz`rGSfC^v}!$O^^N)g~b zZQAn?`!9B4jhot$$o@TvMC<Y;>f6*DtMbKEbq4Y<Fae{$FukvBH;oQ69H@&i%}x09 zX!oHG0I5~75A);+LT?t41#r@w+g3S%%_W1bx+E&ZqWQbOQOH;!<@%0o%*P%<^VK<M zjsvkO$LPyM7j9ku^;iS?|M?`s4bQ0GC0;l>F;87~D@gM<!0)+5qy@{myB)CodfGH_ zxGlS+U^C|d=m)&fOnGWR17hO<Qi@0c<eDjGI`^~e%!!9xDkt*o8g$dgh&qm4U!ENY z#$*O?D<tLYUli2MQrq^<o<<R{0Q4+PB@ZNFoQ0qo>!@~LvdID5HyN}w3&e?Zpr5TK zzA2PIZ<Yr1e`EiuI{wvon;VhdZ4Y)MFr9b>F8nHCcPIo2Xv8*$p5FF+_hp1^=4p41 z0O-Yrz-Ols@MvFAs>{hE%ArBzdM3hRm%j?vD@CPiwYGQM8K3(b_*<cFKTq6+r}lt0 z)0vahpMM{6U!HbnY2^S^<8FI|`_cV}v}}}qZ*k$oI*je$<?L1aAh|a%yYrN@iEcGO zOk6TV8wKmPCaQV>E_gxvJ<_GHO4}=eEBk`b^jOdU5{j{zr~P#rmp4`=IZ~LvP@dlv z_bl`&n%k5y5F5wJxB{{tu&ev)*#ff-H+g;}-+k^23>fqGzv`7c)npujm;g03HOBc5 zOBg-VWOCJ8$Q?r=a14tdYXWpU2-IEvE~PFXgk>8+VmDs&)(uZjK4K#?+vVT+<{`($ z5)jb%cMPUhE7M+XQ!hEMY$@FnWMug1-c|;BGHMh&4kUEh*&a{Qh0DU?VRiu9Aj`=B z#2HzRX?|B>9S}OA&1t6o_z)N`oC9oG=3ZkY(n+K4bf5X$c->#A#_5U)HP3Zos3rY1 zaV%Y8FzNG3(<D+GKA%2pgSru0p_y>B@0}mB)HK++42zDSi5fPsAygh~L7O-|PHx=+ zr}4|r&r4a%p4`IMU&f!XP~D7M9t+Gbv?^f_+#aS^>d*&NjkXFsD*xMaoukd!CGEW* za=M8<r#^sLOO9R7#2^IJ_WGYOKm83v6-SlDe%!ka6im%hgN}{T1g$}c$a5dj_Q%~8 zX41^zgi8jtAZoIie)EA_atECH-W<RE^ZQjGNX=->#-Uc{T7mNEUmUNJ6nbvi!G*CL z^XOR3%mjvU=^xtRt3W(cK_|uU{#1QyI)%}UUD!+wz|xd4epa0Xr{)26ht}N}_Bj+i zVaj}J-2HJ7k=|l3&0t%xvS(zVj`f9R`)1SVqL}FL;5gC4jalF{-O6Vwcm4s=^L?jG zF6U_vO@rwPWaPshmZ)p$s((#PE}(dx9{6y$pc%2HL!)6ME<A!6-#yb3Fn+|k^$dGg zleBBp4Ed?Dv0^2e^Q!l8Arf`L6-2TD-g*QCR8J5I!`qXPpqpKc+{e6yTv;C|sLKiw zOa+eeK2B+_rMZUusOT^X3&9m=o41zKy#7>ar-?_vDe@2-G|nq)xK7q<+NAsYUyE=V zEA>o>gmInHk@nD~>vO^X2vfyrY&al<=>eA2S6&)vF?5E5r$@Fd_5n_Dx0zCp2ArSe zLiyf@(=tI4xp2bz_qN4ps;p){7(fS-eK?IEHh^=nPr)=d&wvIRbS^E;B~}1Zy*Z>M z^of(x@BNkewxm|UEQw|kK5$CNF3Aogh$|K}viDcc#}}&+O5SxKpkBP3EqyG|gpff} zdqcTyVMmAapNF=EvpTm5=q?<07)mJ@M8t*sp5aT%%DUihOGr(9QUs{wG(G*K$(|5P z--xA&Xw&6lW)2uDJTst~A1sp>TR?GWcjSpuiQquO_ru)~lj9DyhUE2e(iKo*xdyOC zr=Zn$WtcA(NYR9hw|?gpc-I%`T_@>qfycD=z%X#HE>ZV3>vDln5XTiGJGFdr$XARD zv8id{j04-qzCarvQGqEymyGyeCXVmaP)oF*h)82_=q2aTA{M}WPu3)}UfD%3fAm}i z<EwoTTs@d3N1DvEA%(@yoo8T34_L9!JOg}9FR1VyfdO$BwF(1ZdK-+)2xe@}MrFWJ zU<3@IBJIK(_&-|jAy{(_#_B=qwZOi&#|tQj>zwNTQmA)4fiF<xQwJ68wWAm@px0Yg z(_OEbuL49otd=?TZQd=s%{^`ZOyR?gbD$oQhj@y|T|^3nQ6vUvUYzqJq5(VGgmV6H zwE)=H>buA0sL2P6&17@7wk$jTzUCkU8YIM>kgM?RGlsbc-{8D$ouMUUTR<NByMsHj z!V(VW_pWp}ReCy%)eIx|>hk>+k#)na3YyJmignBuO5v|SuV3&EIz-Ue1?Dti`N-c3 zC|%NUoIH+Eji$_$;Vg57k!;9p8a5V*4z0#Yd7`Vstjl;ufml9B6eV9KBu$Q2%O;8} z!%GTIwc1p(M*oBwq68Koto{FyujC2+rJ6w+I2lP#Ix=Evl4J)p3sTvrhiMOY)s*`4 zXyR2^;l=VG!=vL#n;NL+fBuvZB4TcKAn}b)(I%WekS2}p%Zs%?@RA-P)E~k4gvW>t z)MqH5yf}juyqDj4eWjDTF4{sX43bc`G428AI)JfB6~s3Ml=mZlXP!^`?G`5+N`Sbo zOw-G}3?Azxl=lb}MbZJsw4Td|*bg?M5fs{qiAkVT{_x?$(LN2hK05SNHb|wICSr6P zA+0b1nUJaK^3tNw!px{?uJyGapY9`mJkA=DN{<Iv%Q4n9kNKQ$<3$6gq~)t0TFg4+ z%tae<#+AWL=+Mi0-*vAZbcqr~O@TVN+-8mnu5H!s*vlk^hMDPHO?#bukz0Idg^jlz zsN;S92cbJ|;BhP>j0IodGF7_A0EI_w2=T3qNRJZ_IvzT6q5K7Fml0>G7u_~y#x%)f z(w(zdBRc~slc4pzAR@Ed4vKz(9=zI)bP<IQ!mn`Pv4)iA6XtMvE;xc!*E5A;n`&vz zKNc*qhnP`&BvNUhcsVmst8!>-YE^`+Y`A03E9j~_m$3ZfAtwh=E>;DpNZ=F-C#@+C zRYcDf1s{Vd@dPvUQXv6I&hpayai@vCv}(}YT)AaEh_pI~6nwoQ1us+-%4I~d?MvZO znXgrrt`$&n?qQuv$jGzmhgQ@q`AP6Yf$5#eS$Bz#t3f{p3TR&wMs>(h9jLCNbCWd9 z-rI}w(|)KVb6ug&{g!9^()2Wc-&)`XaWvDBXK`+7hz&{02Am3|F@}O&A5O2rIjk}! zcvCp)1*R?4b`($w`-ti7Pr#97LFlQ)*u<o%vm1$DNXAIi%)C)E&i(_8Vga@0cGgoz zL5-(L!k^9s35gZb#`(ETyaO5cX6tE`z643wnVptp!u&@1t8X-yZ|ODk4D#Y(&Kp2x zKf8B!yaA~>LM*aO^Rh1HgD~pR<GnmlMsR{0d9Z^$n1bhn0{>9drB{2ZWtUx!X{Ef6 z68r{d&OVUn9e@-uxk`do*Jupc)^XRl3B;1)5A;HW@-gxikNV!!<$B@Z6yOtrCMtX= zy#x_ZR}aMOc{D@>_E)??=v6gjY<~^yXTW$+9lV^rdWhP&w=lbk!htXE9<)bj7!;6C zK)fXko)vcf(|Z~*U5}9V?OZab1x05n5#`sQ?=Gzex=siSkC6KM8<eFwmr3N)V0@{q zCsDmhqG<DHIgD_imWVV+zsL${*dz?lICy9^Pc<*xY{}~f=@R*cr2#wEkCJ;Gvxc|~ zv^{+@3J37XLP9L|r?u9erVy{_36+D*30+9VS~sj)-+gG+Yi0-E-Hf;ZCvB>c<L_7t z^<7^ct!~Kvrt6VGH{oesDenuI;Y)4L)u#onZ5FX+fc)M^C#3`<HUg{ewU(q7Synf* z1c7PK$F^esa3TWg0q)kwae!k7H)@<}i`AzNUXEU~L$~Gt*OOp}H-4$f-e>K_N2$`| zDhMn8r6&8wYemtws4t(AV9~UG3M*iK^CXGYr*+vSqb{|>(S<v5FADnrrZVN#MN4T> zt`vO(KxB{V*b&S>{!{>Ebv6Z*Cs%ie3?<2v>niPE(t*KiI*x9_2=oCmE1(81@$t#5 zN`4yGte_`Rz}A7191kxe5zviM_zT`;QvWwxKm@p;<%w)aBvya_%|l|<=ZNFA<iQZw z1A+PStL&J)#v})s&oT?(*U3%3xKnx`{a-NxB2;-e;{x_~xVg%ikoJq5&&=ZjBh*o< z^ui2W$6KJi;P0t+GwpUHDid_uLQL#(WXstc>~1^qVE&XZYc8zSc#S*Q2m-+4^V_lM zSkRlE5LyjBJ?Z+LECArQ%&0p*$NULtgf&cjsy*Ju4tBv#utEv5F@Alrx`!kyBN&)y zUPK?MHHQR-1)g;M-yt=(vmI&{CW^jKx;@%)8xr%{1XL#srqwE=ym5A{``Ln)k&iH+ zC_mOMljCQEy6iq@F8481thO$?PsZsNfqF7bc^qX)AZtS93fw5duE1`m)@+CRJ@JXT zBE;Tp2noCt^vCZqAeTj&BveYfmt*V(0$TA5TB@+dW_nSATPZBntndq(r47``s=Ch8 z!@3`DAql&Ei@D}kta6fd>xl}h!GMyg5=T4*U({?^7XK#Glc*v+spc}BO%=Ghm|@k4 zr1`(ZKdc?5((`G}7a=sq2}D(}2J<&&5t>r^8WQ!<umh&skeznm_2;#<Ez8cN7(dv) z|M?E_n-xJwPOYtpHI{hPuNok2fGQhjz6sVPj)CYQWC39Qi7*7YTI4TIp(YwsWqym( zOx#qJGGl=f@zYNyQ9BE?Lo&%e<-vMpy}rH%=o4@+mOsMJbtklh7EpT9z%G={M=;`I za-C}{)GBJZTjd5~J2oGkHlRU=9|!a|Ss(;bybLwhWZ4g~LMlV_C&%0+M!<bw2(HhZ zp6dH{h@y=rN>KGHDywDYkEItYiMFK$l}updx#fCReW!9^3cs)(v0nKDJ&q~jcZsO% zk(#Vi&Ae|1^~^Bz9<j2_LTOM8Naq1morCzQzA+kX?pvaMJ_6kc0MwgCATtMC;{d1o zf)%8ihs{TzP)F5{skDAZcL0uSYdOW1`$r>WXU9>^fdJFffdb@4E~m)>CaBp7!|C5| zMwX@AC`T=`7jOtN>1#>1OjrV7rx=v;Mu5;}&cU?v&Y^UwFHD`28H%`fd#~4%Cr{Fd z37>!&V1X>rwYeMBv8Z4n@?#Zlgs$#~Ky+ALg-t0isW)Ta>Nmi~9^MF$-I8!vCqU1& z)_J!O)(-$<SfWVZG-8T;M4FE_Oas0zxW%3#`my5GNh73w>kHUuC<Kb?43K!yH{;_# zHOTc~C#PN~Z^DHnJ49D;vO^Lr=pAxT9gT#G8oKcN*2N<{-`t>I;MkV#Bd!2h^?K$B zs|P^)`0`xhGAj&{tY}mIgn&&;U^SwmhMO@OZBp~<Uh59E7*8r41Rg;A**KsiUa0gw z=X;>({iFXR0>tV5L6E&f%*qUC*&}&u3LK8tS$CNCU5CnVHwtDX)oXD8_kSJUAvw6K zG(FCVyMVHTUE<IBTf=`TurtZdH&w^lCu*oD2O;HQvs=$A<DT{DrgyQDNM)B+gVTwY zOO@hbkQ{_dJ4dNOq4F3;CA^<h=j}R6oIP$Pv3SZoF@b7LPu{^HB^C~_scn|?V6s9e z{8KV?-kpn94(ouz%@2WqEE2BJo8JjS^U9a}n{oQkGIbk@#ZI6{{QxkDHez?1`H>6` z^?uUj4ZySCpxSW}e*Q-v+z<DX;{OQ<G)`3Zp)r}yLZ3*$K$P>!0f03sY@6ezc0oYl zH;H2<NJDQg*Vdm$qCaWm!eI~=QjU;zG2jg7EqX_oPHDuT{^rbE=6SO~Qh8gnWp&44 zohWwMek}sE*_-8Uh1j97gM7qLX}1ND)l8|_Jl24Q+8slTPTV}32y_~^VE)2b+Pt3g z(93OMQ4GsR55VG#BAYf(km^Mfd`8-HYn@BCen58kRyW(;6jy&{537<guTnU^HcF{r zC^kCqh(~R(@&)3;WhI{CBL!4Y$*Bzo@bj!|0356s{CwLm!aOtgYK{8<&o?{O<=`bi zezZ8%BD5zl%)U9sfQWddnJDOZH$^RQFFA#dx<gBRGVlq}<|Nm<Y1qv|Zp|zj<>K)4 z5P?xY?$*6W7{3xPW{{#T(}2t}1a^>a#Ka_W;4zWGH6g)`bf1q#PtJpKGSmOB@y;o@ z(6iW&Ga`|Bu_Xw&$P)-no=dQ)66ZlYNt&MOk2S<JG9BT2o~o+&ZR8`-aYv{rZ{Kpc zlC6J07mqiB1I#ZfClX!On>%%K-aoui3c(_?091Ry{RU##<IjOr4;3h3ufJr22!=<& ze67tZXjJze!Xm^M!|WllV$_Rl&|L08!!P2&1!tq)x_L2>^8k;@^j94h>IX2s6{xIa z=#}pYF<a10xqd<LX*STF6H5>_-?`3DWjX<Z0)$v04DhL45@wQtL6zq}#lK7ht3HT2 zfy<OkGp$WBKo5(UtSFoFlDt@hXvJC(O|7g8t%V?3DM>WTATc6H4L(At=Zc-rg^W|C zEdWUw#SzI*geg}K^;2HQH5$;+2vG%2S6hiGfXrE=!|b)S6XR|nI2;kH_L*QwMgR&_ z)kDOA&c6L{XN(_^^ihj=e$=CC1v3w#)BWeB^OqOX!i92XdqR*Ccf}zN3(3MUClqp- z^s8uQLU!!_o|jNvy^2o$L5cOKzFq+$Z!|f9e<$^Wbv;<klLLPk$o*xM3xW;0(`9hd z@h8Clm>H?bsGrIdht_dV8So!3MB@sX#OXAdr?(kGXSJ`4>s;5T1Uoui*gNGg3E}7d zYU2$;Fe^d~5ajWC33oKzKnyzNYGnd-?X}QNiJ4CI1~fF;0GePg&QsKH&-{4U2_($L z?8B$2FfZW`L|&4)@1uV16^9qlDu)3aFDOhH&xp$jYf*)kVD`WfMU`$|kyh<8;2zU~ zKtZT5npSGpY)3&8M^>_1xP^{#{m$WC^IrN3T<me=Gr)EketG)4RT*z|-R<Xi_|I0I z#YHelr?!FnT0MkNVPb>&?Y=h;JAnUI&PInK>dDq|Rre&8rXunLa*IeKDzplI1A$Fi z9vZ_6_%zDsGvS_jr$T_X-hu-Ty~9Y|c~~>8qNO?x>L)AsA;Ucq{s3q&c0_|&!0Rgm zGOGif*6xp>?N%U$;>gYO+80_5!ILz<CW2+c?BIQn^;{^)0Hd;r{v5?%$H?(Fy~=T+ zacZ;d%;|(YKC~3j_xJYuy@tQ}o(Ww9gdUC_>`DAPvbfY^P58^SSHoONyLM!l4&qNv zOakS22%+i+?h+kS(P&(Bs+|860-XHg5>EG=?-MmH`CVysfQ|LWWl|0A5=Gl=RlV6I zMcCg3`QvF$_7I#CbH*+n05GyY=lPkAZ!pVCrv>0&q!Q_0L<F)(qrqLw)ToE`8X6ty zpz*jj0NjN|$@~YU>lLU903wI!Z>VSDG)`5mOuSLs&b2&Hs5-3235I5s0OS|kJ`7hv zL8BzQsWdMm8^IOLXn-!E3neRkk)VoEqS-OIje6-9k{a|v?BT{6?FnEpH1)y#PMo*q z!tc3Rsh<y{8#<u@)(7Rm2w!!v83bI^s$N|)(he&H^kPja(VdrlQ7j*ljk&8ZI!VJd zO6?6WK3eqT9^L0jeW+yu^ck~Jt)>ZKk`Br=AW?vuZ9_%PoQofabO$1WQCu^Ri+Ora z(SNTxjHnB0cY`MMw}8KAI;n2NEzgw57K`_W`ukGX<PEKWdZPBr$RUK`qOLqsU99X& z_ZpFrIg+raN(c=6_hWYVaf<tqfeRK;(0pbz?d&dth{+Z$gbbq@0#i!L1SMsQN`0^g z;|N##P}`advn};*Xe>ACB1Z*Ut|;&^t)?-iXBqBy-K#@ltK8HgW?a|$<k<&NTgZZt z<J&{i{le2sDw3}~pY2vj7=l~?w!{6gaDz_j>j<oDOqtfIv0Jxmafjo;*w31x!Bs2# zrH&^N(WHBo#eR2N8jmPJDHszI6RE}(jt4ZrBS_N0q8S7o;@^OJPYd^U0LC(Ma)CD* zy*Xmm(Ilb?<vq+wXX4;+bZFMdd>Y#41nH=T#S=*Sp!67gMi!P&65c<Ez;8tAu}>p| zLP|LzC(4(Q3=Q*XesJ4rEWLT{;Y$-=$WV%&{=?8fYCV3sPA}J~2-HW+a3K4jDRIDW zh8SH^R!0MpI#FXM8vny<zUQ#g`pB1wFqlEsVLiT^jG{1N5YK;#LIwJz2pQ?;do}s> zVfLcmtCQ$+Q0PILvOWj_hi%X__`^JfRYAw!y{7)n?tn7_Xpp}F4K&%8ZG8hyj%OK} z7>|jk+^c|1YFcgZ?F9HVrAyzsKEE_dA`Nh5LuR5sTc&9UjGVe<_dsb(ESls)az?42 zSUt(?Vb`A&o(Pwe*Rw!FJ?hGYvG|N>4Vo8vZo{cqJV#5^|4NGSBwlzeB3^0_A)l_3 z=OyI)J)Y!x3(P9QSKC(4@1Bsj7qtjWk5p9k)&);M(P1T&K%uNwmx2@^GgS_Gs+}iO zZ&p457@+>PkZC;398BYh&RrTbmi0om6GL(4=E(|&`27uTO*3;1;cE=u?gIRIuW}=C z?s){XY9lR~rnv@2#jsq{+6zv##M$me{9V|^T5l;J6EixV@YfWe0<aMJ2m>i>SCC$( z<LBp{D%nSPyp&;L62M5%f%6EoR<fEz0kt`}+N}t+SZ6>{X!Px^{H#LU;*o1LU1888 zj3+t2WJ5?45G4tt?`uJml4LOSGD5NJ<jsmlh#i)!7tRc<$On=NwO651yGE^(<T5+f z2y+2yod0ImCJpIJACq7JOM0yI7z|Ajf4<j97bCgxC3qEP*J?3W5~D&`J#yVd{VWmM z-B82c?fIefP8ho5jJW&ebjyfsw=AGyP6lK{xbe__{;l^BK?&sW9@MdHJJK$l2a;J4 z6Ys;JbC7=*!ql7nrB{)F=K2l@2@Fnoypmy{+8Tv<Q18zkSx5B}%sPtQZ57wt)-+mp zR0m+Ikf2Yuh*u3pJBm=3na+99>RZTAVIKXf#5t@uOVnyoTv?}qZ!SgBbmF~l+Fo+j z7ZDL((+!MPAX<ITT5ZT-sYo-|tbjH4Ypu$Xgz?$Lt-S~WzqkE?nf!X>brZvFl6k0s z8nhI<5S@!4873rIJ{(O#B1v;qj1Zd~W$g=^E%2Xa(HH!F_l@?mk6@!Lz`A{~D=NJ} zEPYHA#E9EoUyJp}Msn0codZON`2^cKiE6Krj_L{x=CAB3eVl;#kLj)+9PuN0>Ot=4 zj~#1y2YUv*rxn^Om_Em~H5FliXxLj3WEw2`^UV#~$=(>Lg0Ih4zux?UWbzjh5QEH_ zYHeE?x5R@9^YQV~H>+WcoL{f3{UP4$q}?>23d{?wr7`yX0nV;{f7OwQhF+AMFW^YQ zn(tp?#@W|+2Bs0@g8%>OF`&a~miY4eJAmN^{L%#Klr_!YAX#->=_n*tvoCV6&*{Ie zxN4Y$A<~gBbI54?&+NlCt_=}4X$RMMk+0{D+4G_dB0=39qklpBoc5Zj31p`|u!9e$ zBppz~7_cEIe7v~!<D<-=!T6sBCQR_h|JNIsKDxuelfs*l7ERiDfp$x00VQrwz&7f3 zMJ!;3>cCq!k|v9oN>gCw^-_jFrZ6}Sw>t|S<9JVqW^r6AY$7ezW!7634zv&}r&^48 z6+F$o=CRjX-Y#rL@*#KKlh{8YZjB48Pc<&<AcD``SZA^av)8=dzJ}m^W~kRu1q9Dr z@X!c$DZ~HONsf_Af)5wCxEgGjNlae2OAu_V`0KIudPA_`So8O486vp`4hS=^Zy4bf z6sx+{f=y9~80<IGul4G#fo;9f=1rmza9Pk*7zHy3DEhZ3rI3K!tG{sV0a5N?DczCT zkB3=;x;CWRZzbOx@)G{xg2pu=`i}r2*Kg2We&j&YWz3s;2f7-OQf_5fD^eE$Q$>=9 zO1d}#-4EP(1la{pM-_v<?=7%l$pxF(t~(#Use)NNv!xL6DoD8m!nr>IJvhO`)fk+q zLb2Vu^!@J6j+dHxj{zT)uMU?Eu`oD<xg8h$NZtUtu|{D+S)nLofmb2d)+|4u(H-eY zjC<R#K76IlL=tdBzX9+N#+<~Zh%p}odn2lTM_GFX8t8G=$fps~s*{foHU^hgQ*8b9 z(Cj6^%Ft?~LL&IrUcHj<CmIw4`bk#QfC3agNsHR1CbXAe&R4qK&!74QM0Sq>3%q`t zI|}=q8rsnx(RfK1NLsdVIuKf>Y?hhhB`z3Ohe?VeitQ7T?iA(*JGs|`bYpKQaR2h3 zus1e8C@B%P0(+t7oGywaEToqs2J`SEwx!!cRg+~$W2VKYD&vik^e`XbPxpHOkUcUA z(|P2rKi=45*5)!^`3^+cuU@$;?S&Z`7b0Jhx!L9c6e~CXdG9q0<V%LpTitFAKnb@- zSw@};z&O7R99Sb;ZcHduNH^;g>ZqB4a?>Wi^!@V<0)QRXUW%v-E@2{6D$Xd;!GMx| zBRS*BE>N<VhG1p!AU(dUw;ms#e9mQ&rtb$3O+Z7T#x)VJ#5)Z&oQq)ShEQRlK`Jw_ z@rN!rWn;{!Ax6Q#&#w?EOhBb-JJ@(!!eKacUbBgxk58le`o#&20?M8O)&MYA%pU(* z4BdK|Gl6YB!<wfbeFm3PJ~m*$u9`uM5(*Dl={aGQo~(Sui}NrY=+C4F7>m!<FF&db z4~YroV(vmmk6lC;yK`Orj8W~sx)O{$@K9)A`#;Mqr>#@^_bh;a{^fu358Qj;7Pj5- z*M8|$pX`N)suZzZU$FM$9(%TKAqQ-sB>gup^I!i{5j@1)8!7q=26ad7G2G0mxDkI9 zyAgt2ccANB$7`@3;*g}i{u8eG<n|Ok_Dip41rHTD_G6D1$acBFOa8~TU~Iwvd#?3g zt@FSB8}2<b3Q2bI1ahYax6}P!^QtX%LP;kYB^=~yl#)np$E2|bWfVP7AdtWv`laB; z2siI)ZwyHIuix?C{F_MR{9(jrzZNle!9(j6HGjz^Yf!|fqI~&pUgp34CwaKzm)Rqd z|Cd~{A4Lpf{PkaYJuVb6KGysKCIk_~7MwpaRT%q4qL3rX?S_Zs4f-!)&vPMSNI=A> zX<~i#ONa3mc!(>>_Sa6kFgREH2|h5t(qq%M3Yg5@kggIT!{BxftHe6(6#Q(!l>Etz zaHeFga96x?g}ZxunXFJri6TqM1DdT@Wfp;X-g0rslgTx6q1*)bO=JZMEYD0aDSdl; zYDj!^tc;X9RS@^hs#5uzYn#uzk)B1vPP^>D@w!Ul{*BM&XmC3S7bihUPkfCcJ(<(S zkI}^-o$@4hPfBaI5!#pLaaKR1SqgJrzM;fwW|N0HR6dWC2@B~bO9^H!2ad`LReIaw z)4cZPJ6_q|Xkk#VP!=1$(LP$*6}K>4-B6gqR32M7<m}AgcUqF;FJi8+D7rm3vwIna zW0x)V&>a1V<YvC-vZYWJSEq&MN06w8D<t54HSK8W8)YwUdE$vu7MK8%gJ0LvZN%Ze zD^!*}@5z^yJ-5)yOHxj^PMZ7{$oV*;QcQLzB(zy#v8%<XQkZh7rHuH;RDN-xzTToD zv6eNjf!Aur_pVtkRomk5kNRfMc!uL*T~E&%e!R1ep<mnH6?N825=jY<#dAr5?9<qm z(!>;2QeepIrinB)d-nb9p}P(AH$yjS<ddfwIyxgcOIXVD&Z`h|dxSJg1XLD{PgCRa zob4vA<|~}WWvYeWUK|yhuHw`TsmdB^@Hw0`Kg=`ozyMzu8K}pqu5yF;fb#Ldu}wPu zs>b~K7arL`L;A}Q>l;5PW`{A3+c0^9-f!Y_z<kP*aeKu+hY@+Y+J%C4<6@7NDU}vx zA>9LE4FjQZ`6+!b#cm8t*EnC0vmF>se?Mu>zdcM!tX+T1O@bKCHsKvHB`cd{JNO(w zZ#^5B`Q2hZF->JYE$WQ*85-Q$+w_{-l2ose>BhkD@h10obhXVs5yDlor#llh%&Q%~ zP^%jjF(!hqxzT)(!o*+?;l-XGkF82VTWVkJXtFaokUei@nj7`$q^0!UojJGN<4MKZ zqn45fm~|x`h%qf@mh*#N)g9CXj-aYT;%_FSI-|}-{~zqVcU)6hw>LhHeJofW#X`|R zWo7^wCjwHE=rD?k5ET`qMPVce1QevDj+JpxP#Hm5Vxy>3F$pyhX(A;e5Fi1fv=9Rc zkVg7@!i>*-?!EK8FZg@!``q9C@Hzj$$ysObv(8>?eb?UKwU32u1~RwgaL2c~fck(p z{VfdAmSUM>x@ArWuoyR#60oGsZ*#Z1#Y=aK;*=}i36Q(_0Jo+Z_(0HiR^LuD_T1kK z_;qqNJvqH5O;uO)_2Q>P`2(W%fxj^l&o!Y18N0-ZE4fD{yb^p`g+Qkj6}Y`^Z9vmP z_rR~7GY0RbDN{Tx=6fb+E2(ly4dA)jLeOmqsk_em09@|}OuK}0fvZBpr<uCn&(QYe zg?Y+t@u$}pOh%e8Yw=k8zM@9u&G_D5!<qT31M^EFDg++o7tWWL4&UuQ@GLctZNwy> z^$>s##3Y-8*XgpYwvVE_`E_;gy<#2%3wVYY`JQq~WniIqjQF9Vv}dDTj8AWiCpPda zlBMNa_tvz)jWo|!xd(1p;J@SnUrtE$_>oFD_LR_>0`~>@985WfD4b?`bI}wS0e<7p z&Rl5D$4|PaMewq_N1%$nqD#kLXbvhap^jPyBndJvzCF{BRCAxLpD5`<CiL``&#iqd zemoXqoLOxK>$wxiW0nMZMz`Sy-ZZ$H#@iBAE}n`G_TGu!i>zfgTr`84q1(K}T)Yk) z{|*0M7#eWVI8zzsK!)wg`g^R1(KT#VSk!iZ_j6%q26Z(m;bemezPaMjM8%e3R`AUT zzz_$y#%zN%CYom$?Q&!$6l7))HLmu&FBYC5x>EHwUSMt_^T^n=;=1eO?apwM;-q7D zII!aJ;>odAomRAcg#HG0*I@PwZ_=33mR?6=pLb?@c5O#`*8J%7pw)i4^%memeup`) z&8d?<d^JEHegqJwj<B!i)i2IF4h!*0H>PZ>Y#*p@e=~={T<ht2_;@kN(qi;KV~D>x zGZmwj;#xKu5LdxyVj%fN*6k{l7HspX?4rcSA$l=3*Tj&_`Z_i16{l}uYqS3tCccyV zdRH`EFeo>!WL(a(`yLy?%Ma0aUHYBUn3<0fy}2h5Axj{mOmZxA8O=m@OmCc+^;77? zS+$Kn>vF%SVhLxwUZdEu9X;kcKHLyLRMsv}y>iZc*W{~CDr@<CtTPRHtbD9_$$Vt^ zalaeIluey-c}#xYRjJZc*FTq6GT^-`5u72A`o5RMoZ#7r>r=xv_r9^pGr%|nJ@3ub zzLPDx&?CQ}o)6L0|6zLs>v1HBdJ~!bZIC-Wo0N%|5QH=qiNOhmWlwOii(d?}Pr4RZ za1(A=2f2x90x>wrkUd>?)$>ijbk)`TV`t(P9%4d~U8f)E_usL{-(|RF+|nTp)fFSE zu`-_o<=Txw;82dB`T*O`TFM6qRrU6f>7kJ~N(bAbqFWl~3-f`Z&*G@lX+Mb3?auCI zQQ6nt3}c()R1|dt3ZbR79mw|;sL$G*uwPOZ!M3PfHW|G~I?%@W-~!ve=yilgmWML< zb$CnWL#I7#6Ti54jhy9mmNkc^w+^A^oji*@zQkhwG2U0n+l*gqqYuCWaz|D5-{#bx z%_pV5cW*`00k`9>=#UV6SQ}USH_;wFnA5(c+g&`b^G`=OVK18|eA?{?#AnbX_q&B2 z30$ka#iW32i}e(s>cElawgHbDY}x<gQyS|1oq;taCe~i!7a{3{{7f1PS?m>5{KKte z&zp<Md{URU1>q&C+Swt;ZgE7EoKTcWTu-Nhje)&J(ye;8%0pOCOE(2?Ttijj$45NE z`f82UD;iFiIx510I(7uxo<{*Y#tblrsEQxU)s?()QEE{?wM4UYzM!a`TG;Sr{5!Zl zGyK;IOI~WA90(XbV`7KnOBno)FimHMVtXN14n9G!Q|xI(hj@2SwBc*-(36fCeAmoG zT)?1tbH8G)#T0LHF6CaiM6N7be!V{e3PGk;gc3O2xCnPRtxA4g)k2H6=x_reJ=$N5 zeb8KRu6=6KmeNfVy>sL(BW+XNF<DI7cDkLJ_tdw&gGN5Tf$7b<{#3?E7(2Vl+bQpH z`F9=KQUxH`&$nMhS$i&fdg$oZ5dd^9#T9D}+QmSFt!#AN+U@R=9quOZ?HlfG*K`Rl zCFT7%_&RW5O<zWAn9-IwEoY5Yh0QG+fZb;8-bZW<rv0$L)D=@yXVqE|6sq|XRnbB_ zCcF0NR`bD<Z~Ps<GsMYH-zY!xQ#oxwCF+e{sN5n)s?7oRUAOdA#r(>iEicgL^6kHp zAIm!iwy+qV?5foL=?Bi!jw~X+G;{>Mc3wonsj-`NJ$l*@v`3n%k=7RT3wd8Lw_!PN zI+f2-^Ov^ZHrN~Me7mS#E&p|KOW5VyCGz}(8JR9F`M_r$++%W{;MG4fwBegj29Up( z5b{mFNllZdT=6i0XV{QPnfK35^kVLXAHSS)x8s(1ho~8_mZRk+8t3V5d-M$n$N-*P zb=-*q_Gobh>f$$_%{#?gkZ$>F>_WAjr8fnA6W%u#sLtKgZH>uy=^N8u*xqS%_K<Ug zlXFxP+yjSfxED3>7-J#zI8-3k#@y8*09bum|3#9Ll~HI<(f_J8$MfV$lfElZJ>3wM zD))u#K2j&HRSGcz#`zkP6V>%r>Fbt2+;5SM4LbC}*4t$Qcy(hms8u51|FA2L{b=k; zhse)pNezL)yrs6d3Xfgfkl5G8U^)jN<8Ga8KljYVV`Irl@bo@Ox4R2SG83D!zQKL{ zY(ub@*8|4S8BHsLkea)a^Hn>nv@ojuS4FR}pr(apc&C?Feu7gxc!l*2g}nMEECfhi z)I0^~tJ1Z=IATV2@XKn8=RnQZ<!2sj{{`6BwzMYP-q@9=_2isaxpzahU`=9tth^)p zi&0~OhfaG_-}Ib#;%V`VJN*8OkKCu>>D4J|uTn&GxH$cldyqz1yi7F;kUaNv93{MS zAA5*Dc5T_lwFinV@q;i*)3jmbCPx4XpOm{>G?D-|c00gsOgv(($1L=!yBG1!Fwm_B znwn?^U%SvN`V_N3+(C&mS>Uvvci_WQhx{97VH(gql!OYsp+fQDZ?i(rFanGEj3d)i zEyb*Icv_(p3iEXeIri<1QgPd*Q^Uk->@`3TbVMIt_KYCOZS8;W9rIXfqML^qpzKg3 zpFP{!spbH?SQH|(9QC$vhgt_B_V<Y79eusoZZdc~PY2e$f<`<J7NrQJWt%$~sE0@@ ztdIt9EyL#@PbiG#!Y$@`zzawL8u^dpnjF%{>rd*7n)FR`H{>AsQr-TH=|-`StF!NE zMBM{$%lM{<?x$2LKk3PCt$qM{^SV@{PL%9Jzb`AVB0v@3LM*q=Q}F~q2K9C$-YxpO z5m#HR*{C-#!eaoykj2K>U?nM)9s8u>(kyZ5R|qF4=uU!%utRJD3+g#HcsA#F{-8-B z#~F$3e`NKv_cwu5g;TPY$G8T<+pqb+4&-it<eKETA6jI2(xFfldlKD#m<s^$udNG7 z)_DWJGqe>NX^c;d;IQ&u+v(+Kn+~r6JmZ}LOU-@lmNxd;7A@b#oZ`_1WxEV8+>6K6 zZg@MH#9woFId>PZamO=4DXQb!$hr+k#{Rme&(6s%C)M8IARc?+nE0R(+0BY~sKHmU z0_wW@14Vc9!&&(oUE*mOp)Lt`pDj4fv)xL%r#^o9Y53Js3G<IH>taNrg6vm51<W+< z!MiPO+Qc`&1=)NGNlFTw=5pEY)r*wyg7CrJsiW_!8QaD@bM4!7gUyl-2VT<dD{)@x zy-ouX;7`a>dq92EOFMpBOg<5@e}OFD#qTGUC2~pav)s^{#_@4DzRaWoJoJ)S>zyig z0(Lq}%3WI7>+<cqR9a-$=_H5zWt0ZjR1u|fHy8Nw(vn`d%NN=R9{nEJDe=3gywHqJ z^<83azNh$w*7-7|`}w$42c7biA$iXy@Bm6#W<5#@xNoV!ge?dZ3PjTd`<ZKj!pDG? zb~|q%P59ZJeoSsGNRSuw)>XGuJR{2Qb(@HfTfgo4(@=Ss!RLbwefLdt4k?0E;@tk< z_N;z~I$Hd(#l)A{O36Sb8A2fBQKkv7-O4eNjTU*$@z4nMk1INk!G6r{VQ^2Jdj@O) zbh4Fx1HmRFKs11T)ztEW(-^$(wCP)4GKFxUtW7eK)8SX6L2%T*qvgCW<M8=NX2G{z zejv;BwTaQT9fos~NY}Dc-!>NJz5Y4dx2%1u`V>FW>ENl0_gB_!y5l?^oW1S_Z*;Bx zPe%~h_Ej(aJwabS*m$mts9u!r%kv4J%lf@v)q8$9hd@B1i2|wcWyPR)CksmmBhWc% zic$?#SgsKCwwL8=wm?bYp^EggHP6?m%wy7YAXhES(*@k8RbRL1QHG2h9?MkaMVfVM zX%ZUkmK#~4zDm%k%`pD$;6j0+#M+p78!^Nzy47`>$hDZ}e4wf2oYOoXMeIBbvGP<{ z=NrM#Z7DRm&ms>fNcDmtb%v5iXwL{W<3d%9RfKsg-g2=AR0N}B(Rwwx*-~(l%9*LO zhm!BpsUPV$h(1bv7^o2I=t<qTZy%~dHZazqlQo*q@i<>!#mwnYm~V$iMl;X1EK`|Z zP1k{t!Y^F7uq`;9H+O-6$fGLd*CUoKIm1Dh@-Re;$=*|^Y+T)JqS)^VYF<gi1;P4_ z8*TaD=F2MT>+9!(xumi>*mR%LPy3W=-Z2bC*2fTBXiLH8)~p~y)Q$bzCj9s_@Zj;R zyn|>mJKR%`S$6uOvC}I?1zh=)yh0H?io~^lbj@I+Mnj}|92@Jgwu>+NeLJMR5MB); z!!|E@#48fkE2CIt(A9N_vLvpYxDM8kWCk-V7f8|hg9%=)-6;%W#c4+1O5G{~(}wMG zLa3@DG}=shg|?Lf^}5N$;bRpTuxUWLAS`C|o`6(&Dm)+{fHA6&j5=EXgbZz515^~O zUcI_*Z!W+CO}Xx#in)fK<|KAO=lm<Vu`2Up<Em>lxv`rAnrQEU*BftOn}QpvV!bPD zE+kBlQuCc?=PFZC9mc@^h|U|5)gAbqi#>?DL_f(%P|aQSxM-oP^J5Z%V8tU<#_A0W zeNRD`qeK>5eUIRe-p$HxMF!Vj;==yTkB+X7=3WlDcP~1G1Fx_EN1ek9)UDnZ+?imq zajqxzxzxSwz=5A_f-)6Y=f7nyPX)Ieij@9{Uk(A2ph5aKycLl{>nsdYtwIR0<jd{+ z9i~C)Cb#q>LL%ddf0f++1!8u4^W`f>hO)!sWuze9L)Q@)(GuX1h0@7R4m3qQ0EeW( z&~+?Ixlx7R1xEp@v%a5hm#Cg3YQ9M??L)VgnX8&kQh?_dzBiNhra@G%v(UDoKg+2| z0W9!DMY+^nI`F#Ch%2EVdRzf#vasKpZ9JJMQTBOpZ@4rMS4iL*;m@TJbaU~;9t@p7 ziav__4hbPd^OJXy6(4&QGEy#&oJsM#;0sv>jTmL#ooIDa#9yt>jNaz(2-sFOeSH%Q z7z^vxl7^Cj!jWbx#$*LwT7Ovub51u2Xxbb=&EH5KDyj4j|NcaJNXO)4l=p{e2AG!z zj5h^=x$4J`{kra^r+HR3?0bvth&Vp?<T*}SgmdY{nvg^0>FQ`nEZv!I7#%#c_=)V> zCpre00_ls^CHS#d7leb9#uVfRvVmIu;euG-HA62~V(1#A>=HdPFWXcRB)8R5)2g^H zT^7Fc(&<iK4#sC|$QOFz;Yr?5Y>0IDf(^GrfD1zf*-%FfY~d>Mh5Be{Peq!{dJ-&* zEz2vm$c3V-aug4@1LO5b%h~0p!rE-s(f5rS?p(K)ynM-#u-D9+?h7Lub;-+FcR36D zJ1W!jrfUpsfH63#%!_}bX?zxYfY|DM%;t*G{xdMq4PbyoPR2ZeW$GyV1w*a->Pnt9 zJ5f~KG4EcBX#De>xd~^b)SjLO(VH;u<X%nbhq4fPDkY1q;&4#Xp{yFd0W0Cec6b-s zY~p9P?i48}i!$WPA|pXiT{*4f62p#tqAev-XNiXb-XIl0>n-3q2A59<T3t%N2N+8V z@4YG^A)$ycX){R;oTgh#TM7*Hgb(?}^YAe>e<S-*FKbkHML@~7vkqSTVP6Vu+$F>* zN&Pwy(X<q@SZt=-bBmmZ-rSuQ-v6@3excz(D~$>rhyI%P5?RskY<rZ;hO%>LcL}%w zl@Fc}8FA%h`8=<DH}cm`!8Fj|sqoJ65g|Pj@aK&v_mMN45GN~7On(8(-n#*Iq5}A6 z(>4B^9RX@{hleuG`xebljMFgx(O$M+TME=SC@4plIJ7BTIMnp*!Y8V_wzgxM!M8#+ z*goVQZv3Z<Bc5U2(<JY`Jo>j4<n1*y=7F*Fx(IkWLC((d!zH-5rlkO%vV}qfA?@4? z?=tXF2Lyzh(1IP<_u?`j7X2K`);~+1i$>);c}Al*fQ6I0o<~;QN|^HV!yEx&xtC6+ zZ@RBNf4|m@_j@pc4}?V3Y>Zj(DC2aXy5Lb=Tqac25&u|Wpl*Bn(jw;RBSfB4KeWUs zSFLWZ!_xFfL&YR?x$sV8Mr?CMG1^*$Q0GXo6Nx7UqXwym%w^OfzlycdJK)_mlh!>i zNzefO@+^%FR7K(@z!e<bP~5{!;SXT06~NA#aZ7v@P<&mS{#gvBT(gxkP0zRM2{68m z#l#MCSEE}*Ke>Re_CQUHC2;qeI3LJ@LB*nstaQ+7GGoMRc;vRP!@JdVyW3P7{V(!8 zsXtU;+Do`17iakJ<elRD1Mg7};n|~EFC=(1=5%1|y70VW5|^MBaMGr^Ea!N9cW3D_ zO(@msV#5CWJI{?mnk=9XNNDxwsbrIVJul9>ynzKGQ3F_6F;J6?zzt<v2txnDo&S|< zdtc_XyHnmH(UmGzxmAC;aJk-o7Z(>0`+FisBaaU*PqAlUF61i&vWPSRi()>Q1=Ran z2dsI#{Auwt**Y&@0jgX=3Dxe6gZlC^4A}%bsc&c~s1RJ4=JU@>S+d9@H8okg3<}!> zt!Q^~N9#FHH<8)8DJZuOG`$b&{n?=vfXvN1FJh+KKW$g;AKcyPUW2$d6PZnb%!aLJ zb!N0qefO6F(r@(dXC(9cF9j9{-x*BP8%_<Ki{H&T9k_;gdPXwe05ZQn22az-{uqzP z0oVH-q-sVoM*x{`4xpzw1pc7aKAm?Z_c-R-Gy~ipG7kZnBX=azw530m=8r92<ne5B z=`<I@{*d_zka@Iabo$I?KwH_XR*+AOub-wB{xK|n1!VReVA{`!L@;YWzwDkhpfBgm z8qlH@vjzmj;MoW?=gT8svX^?;o^7fn9kHF6-`@1r?UYTBD%2RZ*kdDW{a=Byf9jqW z0f2phkNe(#dcj}Z?eO+ZL1q-t^mi8pqUwroHU%xKe(~E(z|4J5000FeroRm#5LGuF z1_02nXV=UK%#;8CsJnT~jAZTu0BA+)cQXPrp#YdENk1?nncaZQrp)6r0yBdEm<hE_ zn32pyKxX%$XEOpoZvY%V7_n_e0O(6V=Hk5*(`3^8F)V)v(0aq@p=nZ={*amX)gq6% zJ94LAd<AIh8{HM;L{{?jYs~<eSpx$6_pAZU`Eu5PW(`Q|J!c~j5aVYf(7Z*n5eS&W znuTy*T>O6r;f&&O9~#oz4rsH8i-9u(KbzhjdVjSb9Oc)v;B+9!CUR!eO#s?5vKXBi z+FHAUytZ}yjL_DvK)SLofH|{t#S}<aGC})ilH#oR7MQPXdole@+yTdb-wjAt4k2#M zB*ocu6i8Q|oYkFC>QM-!EBBf=%}C}kkggnUo$hu#Q`3_deSp+sNBW)_r7QaZna7#a z-B#+4<gx}xJ$!5~&nR8F4an?wnn8&%psnA4)T8FYf*GYNUjs6?QzB-RuKW#1S7NzK zXOyn|G;2V!2BfWopEaO)i)IZ7m@xbA8i7dQu7FQ_0RaAc_iQwp^W|(b0&@RZIJf)1 zH=Mi3EeL;_aMU=rtAHGJeMY!$&X*(raJlah&IqJ!24q(3bDYtfa=?7*dUECqzbgOH z?K5lbe;~eF7kxo<%ovt`66#)FJtM&X#YJ}jiRa#ro)LOq0mv+Rv~@-=`TyO(xROh{ zG&Cdh{v*J)*nA^#DxH~by3__B@xb{_GfIE2EL!AYzs_uCY>PE3$c(R2rpc`PBdJ`p zas~O?_wLi*ZWJ)R!zKV-Y0b_VY-a|5kFSB%y3;SFO{daY`27<=`fK7(m{I!s6OjJC z@?SjNccXogyK{l`_rzPf8Oi(#q`y~MR?Wy5W)0}ed9wyIYd}**H)}v&&I<tMf@UMo zYy_H(KtPQDJZwK}K(i2T7Q#(Y!T;wOP~(+cqt<69@(q`roEfD5;^L}{x9cRZ@QM2c zq$~8LGlKTBMCw^0wKlJtB~k;9b(To2B~s6p^30a<07Sh1akX$jkTgj9_}Ka2vyS+E zqcf`}{AIWA#oG>9RgBx*!gk)3+?(R1Gny8?vKR>O(X0)p1J^y>JEIBWCLnEYYC1Hd z)?RGd6oj19esD$;c0+*S5BY7I(Q<?PiWOw?F9BO;1UbThY4R}8>KSRl445MJ*iD*I zF~fZ=uev08@r<;v7MSxkyYD`u>WsGlO^?~#IB!N;SOTbf?cv-R@wfbe(h+2^-=&#M z65pQ>sC!BFi5c-}b^=s>ODoWxnUKETUINrT|Cq^)mKgr6l6d;h<i7_{_m@xa&S(J5 znvwQh&YF?d8O)l|lon>qNV^_38;!JD_`GQJ{lu{SLYpV82Lc&4Es2&h3KzRK?Vln{ zcclpNyZkdOX5`KPVF!xYxbfcte!bUp+3K1e$m>72%B_CIrkH^dyophI3&P};sOeI8 zk3{nN*Kf`&-gNeS+#7s{>bM7*+L(Ft_jy}3%~`yE_m`_~{qk>rzQlfsD-=UOwtw~G zKV0|E{)Alo1s<fDmmT-}KfB_8f8%1<OLKBB!Y}6Ex-^p;y)<4i7Mn}_qW$*2d#dT* zLb`aehM{O2`A3~hUwYN#Jgxdx-?usX@6|fpTfB$Np>?8&$@-t!ed2CFeYh`=IUM{C z?{%6T=r(?@3vA(DWdE7nU%d!WAMBTp`~TCxnpOucD&{GDcv!!P|2QnCB|T>`=1V|* z^2LYszx~f6dpfr`M+8Q11-)_kXLcX92~Zz&&QqhUe_>kF=m38;FnXoQU7J6z`(ul> z>NDOQ{`fPkXV&;<jepkoXN~`JH1Plb$gdnDY9>Nx$`46U<%cYdkfZ4N+(5Md{oZ-K z)K2-CP8ePon``d#aoy+WbjzlTI$A(u_={icXS#Hr7SMEn!XJL7{6cL+*V$dP_cIZJ zSR0af$u>tmQ+}>ie&PMDuRk*+qO^cad?@{!&y=5|m7n`I==L<i_g_HUD6kfhpfa65 z6Aw>*p_QNGPyS3qR89a84HoQT{F#6d^NSYGf3ef!FP?2W7};Bk=c$Azt3MMFL8buh z7b#|+35fHxfEG?~yZ)K-hEs^{i>9@oX}%p=M3;~F_|s>~&zk?N`OljFtoeUt^q!6W zv(f)w8vWnLXp|jz%~oH$oJaJA_Iz#}URAvReWa>}0msP(b)d?zsv77U-Wh^9UP|v^ z^Fk5>S6AT6r;faywfU~mny9IxT<uq4*`lbMIyg+^IO?u6e(IoqrqEhroaIH$ZF91G z!H11kLRL`sU?gWEcCtUUF^roWDgV?4f(>#Ie(G2pKZf3n{<C8k2{`QYgftW?J`QIt zBs#pehX<WGQjk9>Mx9Wwf<AUSg;{kJG1)_h>w;6tN(k+0%NU!8*6t>8j)Y>}R+u^z zoQELMnjXaU?2HTpKT{!>hmT~Ir1DOVhJ<DNS~c}}(gX0R_WEUK`9s=U>doGAR+s-n z2hrJ|E6YFa1@O;@eOf!kAWTy<4<f2zAZTCXxt&JHkI@8df+nceGSy%t^FXt3kO)4f ziPM-LX!f@MU}cCZC~_X*(L3M~l^w)<DF4b?I{(%a*dd*W*88#Hnr<4xb|MH$hDer| zoxS4-rWeT&AcMKJI;et2N(&K2hFISX70~jNUY%>^-`b47B06Q|6xs{zg?7yq7z&6Z zm6Eic&2&^VO6ymi1Lu38EbyJ~F8$}?mdiam8{_^f+R(U3=@G{F6hz~(=jLRB*8oxF zAjD{R&BTfW#dxCQOYNh=@pXppAkubep=N8N3Emj4p{lVeg&aSa9*Ri(<P+0v`|r8~ zPoh1DuD#zz`h9ZN-6bIJCt~&Z)DiuE6jG9<^mLjDi3ocyip>?yPuNAj>xc>9<TNI4 z2yG;a*E`@vhmbx)??4vylwby;F|ElNN#6^d{clziv!h_v9{#WU0=-c|>`Sh$2OZbr zeF<bHcHJJs!E4TV?J_(mgKI)1EA1!vM1>X2lpd^i5{{>;pKpwm`mv0)j<5_ppz5DG z(9+&96$kw#RU=oUrQrjLd;#r`zk4UuJsg&11@2jh^`8zDkkqL0RlgH8Dkc8kU%X3h zijR!@<aK+{RSMvyIJJhOR%%rH?#aL_ij_4C6Z2D$?+0}lNq$mid~363AK$SYOwdR! zngtDL)Q0Y2{vo`tMcDN+^KnTro^Oc$0E9NfvbT|SqDRSPKm7577Hb#ihGqSVj_=eB z;P!+Lc+^=VxlVZ`;h})x>#dZ**OcugvbU+2K(Iz-NKB}y4H#M#3GW-dO_S<qZ%K!a z{K(dCjrZffY23=ER<pFasPb-1|C$f>foEq#Hrx);=e)?UDq%E(o;)yiW+xi4O9zJR zLnd;?7^tQ|Crp>@m0VKYlN5yp;wM~k3G3Kh22!<UMf}myc}+J|;=}*jdBIA>k7gAf zaTwmNEVGJwQUu|^GVpmhSx?5;3PkUOkX(*JO@}mkbw-@|BwCK|S|Tw!FK8CI5Shu2 z-Pr4tIu-D}-`EnD>I#REB_vU>^+axe8d<chgBiWeLR8IV0ZK^f#tb`ANd$ocs`x(g zH0$msQK9;bc_*d1!nPHL4M1!^FWuTpV52l-c71n<{d-^to!n+h^QjwkiSL>4SdI)b z-z6LHgV&8_#UL6&N`@2Y{W5{3p>ymPUCR<XeTwD;R#7rY(G&U%nrkSd(TaNNsVCvx z5OJp&ZSZYvDV`6P{1St$Y{vARWbLGfHvS4=1Zc$7ir3DK`n+OQuGF;7x)2zfl+lLn z!Z;QiMGt*~*k>T6tI(#e@}Zq(rldGlmO&ZN1`|L6ss@9x0`tyfm|bA%(8;*<TG6_^ z0^P3bCBFI;nwT)S%a{S^hm1|OWt>-d2u^MnH@@yym*C^~PKp<nUCjW_w)s+&iS`?L zz}c?329o)<!s`fRt6jqmBduuX8p+qTJ0f%XC6wMGJx(?73~aJ_Se;EUo(FEoAgqt> zhDrs6i)iXol!7*!U?OTHksa#q+XI!FqhD50Paz=VImi@*wVzhhu<Vuh&&ES>;oWKm zI<Y^^O)5~UE!gq$NcD0ks<w#wNJtbbdjr|nLa`D4wP+yOe5ra2yn6IDmDCfjKB>P9 z+sPkSQfSXia#9TCe%3ty*S=*A>IsB7S@AG(zP@cyMZJSPSl!iV%WQ10j|c32^D|j> zl7?a!_kON!1ox|F{u6Jt8t9rI!A08kN{z;IwswF=-mO9Ij2;FCC#^SwG8)B>ujUh_ zQ1r#o^bgxho}2W<QxQ!z8zz9(F&b5+cUsT!;m-AJ4FJ8$e&##$6{Y(m<@s|kz&i=k zD5dmrUw-$ukuQ3oA@fcRsd#@Licx%Rr7CI=G^(E~fKoMAzOe28<W_jvKQNZV(wNCG ztLy|$#lUZtDR!bswUDk*SE<X9)f6ZiO3fW<0bv8^&CG3()JRv&Z6O7O<8?>EgPP1Q z4C+KaKKnZNEMENpk=rlfI&P;&wCJ^yt-<+8Pe`MbUg4lXjn(Yq@Iq**Wb|XfY5`HI z!)tn~8XEkwhqi*4YF5wp;oVyci-55q+LENTuOXb?Xl+}e<Nx?`bX4gze4C@{wKo@i z*H59J*Vrq^!Ae4a6~q?k+mY09$-=m8u<gbKGl$XSbSwd{&K1;~Vkq4pew!arImpfr zlsq2oPHrQ6O7&z#+QHCGlS<o1!$ELROHfH}<dEMKK&PrPswVWq{%FKw@?a_FH4&@L zAGC*qbnk?)dDk0o>K<hC#GfGi1FmzV9Q3d&)I-qN1o<=O5Tjui>;yMHwe|Oj59)qg z?|O$41Sq@d<fprK>!hYf?%U}R%u6rn;`|xmK!WRWob=nwL>|EiFNxM*lxk~qkAX@b zvjwUl_$hBEN1}$JK~xY=nu)~}THx;!H4=^hM;Q>Nsm{!umui49Epam#B42Y4nuvaA z`|h#xGKFc<hIkIGLblVYvw9oRLCt5k#QhA-K201v+CMJl@XX8JqN{>$#cT5mYvxMH z&-_Iw(^5ABZSWoWQLw0N4<v$rBJi!#GEm`gX@}MYEs1y_H7ZL$0H^zkd^-^({J};( zuawgORlHZrWW3?H*SNz~kcNy}AX+Klz}44!HW~7Orwn^xG*aKaQsj1_&`zt0uB*K( zBQ9e`x^o%#Q#bhew1np?IO;-OpkapyV>)oU8c|n35AA74?&%1pC*dWPDxt8WMgO+y z{xxi(<4s@!OG*Q-UIuiN0cf1#@6ef0Vl-Sh>=Uh>_Ikdg!f0fHth2-;caN1tWA#Ix zw^SfNXygK<T&eF{)>|?e9T$<I=%e5mHbYBCPB`XqNMzNc-ewJWQtS1Gb&{b=ErK|c zRYz@~4>QS@(cWX@6lP+qB`m<MVz=mUpyN$%I=<}OD}Udj!S{=+sweQ8-Cu!rkunz8 z0^Y|LO77_l&8?FHs?Lwh5Uh#VLbkZ>L1`y@$|oMzYCT_tNrmgk{-D5Bl;CH`64Bs+ zn_3%3xuWtrZ+9}b1oxa^g>oAbHpBy}5$B1#3ylhM4|JF2k_X=<8Cz(B5zmWT;p5=r zkx(4&fo#Yy2f7ACo{qA2=$g1{^0xClGBeQ;PH2k)Vgt|#>2LbikxqRKB*`p^YWHLu zz%OvYLBSf^RjiciTvmX`c5L*~{wd#kx7Xxf+F|b!d?P<Otu>LI8-~D1UqBVvrxD=s zB4#ELY{-SCC_IJ?wZ3+&6|NCYg5%Vu9ArDgN1tkTjWooUeB&zT?%kNmX*Tui=3XZo z((#IXOd?H@)KQb(u`2(OEtsk<$9JZ>3E%=aKYnB`L^@B=Qg+EOmE)`v#<uB!PRi8+ zg{B9%crzR?(0p*XH<p4Ls#>0_&FZ_Z+vabL7<P^q;Bjid5T6RXQrV=sjeW@>oPwfU z$9#p|+|zuIEX6`NTZ7fm6z+W%0|2IDcN=wGH-VI0){a<X+w+|5r(^g)WD(Gy+P)Y9 z0qy^M7d-x4{LjXDlOxNulLo|*>h!WJ`Y6WbZHZnyJ$2EBl%{tvH#1dy>o-sySKWlS z3~SYZ2dN4l6Pg`<xYG`kZ4@8O%!BgMOT7#Dflx%a;rS7X0?Zq=o<t_gg069BD(uUz z3{;WhISd<(nhkv~Y9>KttDwHhoNXG4=6*I@I<mQYK%-RSYdJYXz)0>sUCu9{s(Yc? zRS!?r(8DAfV*RMi!>O{yI~tba`)<PcRU`MlAvtivVqdNh!^U@BErC}%h!JH~f$8f< z^b?cmes5zFcY(%;?e29YZgM-e3ZdKit>oHdwmK-XTSJke)=iXFy%MdrN42aHN`*PD z>Inh(w4h+5tw`U8{dSeK8$n{6-<}AeOI0ym_Y`|E$M9s^UOaF>1sdn~y)_dlD9MHi zF$b>}20@zxw66br%sIg+;Pugoob95#YI;~i-A^FFdbuB@XegOJ1mbk9rw0%Os=DQc zk#3S&m5{A<ItP_@bV+~CkQS0JKQB0yK*R<1bF5i~{zHTo%AiX_LWyfL-bDZ<s)Bne zPkQh_-rxft(kx4--=Mjo<u!~^9mb}yjXnwy-Lr1|<U?<=DEK_D<@<Qy>(wsMR7yMO zkpjpI1WTI7E?PzVeB8($D$$<vpj)?D$J^}?BXM-r$K)1OE=TWUngijU+>*vYiW`U3 zjIib5%|@_8*i^QVjPkpH=UK<taBdrCQkw+P9$-)@O`K+*u5E@rBrVS+tnXo?njbVr z4_to|B1HF|px@w!fH{Xbb`#ZY6d5?=8DgK5LI}$aGXT25QWB=RHrtI^ZOo31SxfmB z_zgp_Omvu2?&kM^r-wqZg5?8IgmgCIA@n&I5s=4tTQgJ=rnW2c9g5&LYWCB+8six{ zw9Yp#jZP^y7YrAQR!aNcg3R0}t|wQ)*PRal657mE_aUk2PKPjZ2>}RpKcK43WOhvl z)ok33$(wH#8S{t(T}Pw>m8LW!+Udl`tl=J-wBx(R&fKB(L>t^lEh+h7&jk^Pf()?h zVc|Z4w$<fSx5DC#Rc)-J@uMIn(E^3?3vH}%&}F$_C{&4(M_Ub9U~mF+#1R4u1hW-M z*eF&Ug;-%bS_CcRvlW4BuK|gm)zc8D2Twb-1Wc4{*`c^-ST?!_RLa1~qud*mI5UTi zicvb#5@bEp_b3*a@WB@tA~|teGUE(wrMJ7=f=i!d7?X*7BvIYTzkcmxn;(24+T9UG z|8cdWKAy3P!%e<Up$-UNtWD=Aq#Ck*2oxR3Lg0PS2vk;fllB(|0JwD&V}Ja_1bzY@ z+;+*#`vxT8PtV=aD7@apRliy|)pq*9a`9GT7HN1uJ3AAl=!*1KjKG2fhXj8kbtPd4 zj8Z&RQ=}8Z8kZDBW??13+zFaibgDCliGL)Q6p^%%yHF?0VO5dgWFgCY$D_c}toSTa zqo5=7LJv!wg9Ih`Zike*(WOEwMN0&Kzt6|X!jb+)lBES<Kw-_K5m+e9&>?+}PYWgE zT85?XghNDYLY0Ad8`J`XW0Z+0YJLu1bx4zHP992Xtb%_{6TJ}Xq8L`3rh7$q5X)1d z`8f_rh@mp`@aEke=M^mzTi9X0mc`M#tA`5)d|ER0b*qhJJRNz~UVFnZ8#XO+!XJ!h z3+h++HQlDgv`xI+3Gu9tG%R7X#O4K#3(xhw2suuVIUV&{qjJKqblobS^h>&OJFR2Z zEyG?<-e{<w=xJ($T1_!rrlV?&&?lB$w^G?X>X#%_noe)uAYX;1Dmu`W-bK12Z`RV8 zEqYSHsL)MV858KU>vVn1!@!|Yhbnu`3$HpvaGw*DK3t3)LWbRoy+*ADz)dUqHg*_$ z+O;zwj)mA~s%m}sz<ftoe+cTuuuU*jWf!*me8+fl>N;&FO51*A;kV#bdo(Znhkczn zVau`gkHo-KjD_?xzqZ9d3=!X$99a_$un^#askt+{dh9I}*)iS&1qSI77F9dJhV`p% zwyyRcb{x^BLAq3pOx+=!A30z8Q{8BheW3`Z?OEeZ8n@*kFdHWlHd<F}yY6$rEwpg> zs8O)ywo<%M+s}CGbh@=@Ydk}!lG3|lN;W#6+Z1h}z9QFw9ssCUOihqM6@NT?q(-T% z-yWp+K|70<ywaAl;?V>1LTz&8J*nm>;B4LQHQlTADfHzv@1SOYRzX(kOo!OKen~4G z#90xkReOt8osKux5qBkeiW<*u1e7`SgOMCjiVzc8IDODmR>f$T0iJ;vAL;iTDMEEH zv!q2MI+}5Fjl@xX4yqEv*l8`apA;*%5@4Ddih<b|*IV&z3Cy%TP*Ue8H~C4JfvWn+ zgPX*dV&sH!J2HVy$h9gR5JssL{Ffi+3^nNVk-z78f*$L#Y*4-lf_t9ykKXD$ZZfH^ zV9<d#d{c~(Ng)NR`E04QQnKNrCum6>&OOP_T|2*yGVCid85gdgI1!|m$n_y~#zwD+ z=+3%H|9;Nq;8YCJ(M&e_z#uh7=$E#EIT`&cYnwpE*332bBqMxvflDV9zs6hQ4Eo6R z4RCeE>I(a=c<s%Z1GaJTk|WvoFwzTg$s!WcYr-GB7WD$OjSWus-G)Va0M`(%8jRkk zkA!jV9e}eKTjR-y2M)Hr6Fsfge(yTS;q{M3PY$;<&)#BI7qjDH<~A}3PM~)3>ywA` z)7TY_X`p*aHlUVT$qhbo8AR1%zl;T9W*Ic3O$w#FU1Hp@ZL8fgL~o0r9icj*?l32- z$IYR5Rl|p*dc&oQ27D;cbG&S+VY#Yt69G%_6OD@m4BW@+wA-FaL%j05O8A%%L!}HW z?kU?0#L;Ss?hR!R4TqKT&QdeU#bQ-TAWeZ$$>a^e1LJC`iY_%|<1w0O4RlMC=M8cH zF!se3cn^bYIHyp%mglECF{z%Kch|1#@f3poWv6A8oCOC#+YDJGN!bub2a5mMYGP7W zMK{E$$AmFNs8Tr|W$%X5!^XKi#;LP4J!31apm>?Afey#18&zCR2o$f7vFL1^S|XH6 zqHRhZ(Ql_3Ao0}Q9VJciarDfJ_{I@yFM%Cotv+WfeSNE-*rJ}3Z0$!o&ffKb(j%Y5 z`sFI}<Oq;oMZYAUAZ53xfO;6Y0N$(|Ox4sfWV(%W1`6w8=aSoH4r7B0@!x;+x7o)M z)|03_2b}Z+h0Hlp=iJj6sd}B2g$zs}?qC*SI^r>YBqVYIv=5yqkl&`~kaL}Iwf^)8 zFX%*aJDF5ZUpIOZkcSijQ;xFrkH8IpIN}70u?_0`0wK#Ylr;vhm;;C#5>vL=h<Fm| zk*d7WLKrN=4S=4_HZrTO+0pecvJ(VE=mQlhVpvM0x9S{@GscPBBm<>)iNcl(llM~T z_pAd7T@|~s&|GM$CI`Y%MfT@wZstxQLggSQDM}J^Z=uk!b#C@>DFZ9K(N$%N`qjh1 zEHQe#<&Lp#L#tfkVjMSkI+G*NxGwyDiMd-%%!dJnT*Bq9uU|>Gelo~i?_XcnOl44D zF#3J`$2%OZyL20|PDtxX9cst9t*ly(8(2-x3IrA^Xlt)M&;sAUOt|@|gMA}^OI=_9 z|42ae@b&mayAn47gF>`9Z>j?VYgpiGI*qHUzJn|^rcB*>e_T}a0W%nVw<9D%LZV_( zs0AvQBs*9~sg=x*pc(DmnJ=mOZC%8e5ittll|U2FMwT-GIvkA(l2iSm8+;;+t@*!~ zI6N7AU<`4e81RX9X9KaN(5_LW43VybeDApQ+2r?W-R^7%7;I$SXKhTf1J5;+b#`@3 zJ*)`kHDTw)>(ydn3c9tNsbk{PSs9P%lU;C9<J6}jfthHGN}S#z-K%I>QDj?xKC2OY z&`byXAN7bYs~fv`(&zK_C>1U&yDZi&@Ca^D&6)An?)4mqvpw@)q(>sOQ-1{7z{`g( zjJ}zsl9f4!uw*u=dYtv5cO62CUU)!xMBzZ8E1mX(bofSE_%R+vp~TTq^Z;-_tshG5 zOqm1KjMCZV<Bc@E<(qY0C@Z5%YYit)rc>Qe^-a5?{c1I@Wn5cesqwmoM^p27)?A)& zbqLs>xeqM~DzbyCyOtP4{1(MpzZ2O4En=BYEwTfX=ppB_R)j<@NFfgsyO5uFjXKf= zkzb8RAnu=yRVVlSozr;kUwg5NATadSm4!`=A?``Cogs+^p&){+O^6X;P}U0->*RwA z$3YD@a%1$+1vPD%>CBVH`#K^Re%(yE2tP^3tEA=#&BUZn;PO7*$~*l@Ux<zzQolA3 z8xv!~3I_~o%%O4e7T=cDzVY&wx_t?{;&`Zfa(`A5Cz~j96IFLU!ift1An6kUCQ?YR z&O~ozm`GizSh5zpLy~QX&C&7^Ce{6lc<mnps!alYI|qSbIqlS+H<f?-O?wgSUvUH6 zfGSHtlsRyKhi0#K<R4K^^k)<0eq{)G3%%%Vb%ef`DIMfQe~?6tV`=t-!HqAQtwAu= z>)v*{bv%HazL@NX;|L@{-*8k_L#5s7Qxi`(J|pk~^usRs9lS*3=?Bp7cPw%838){W zuWqov8O6-&2W?C>2oQyk2DIaFE}_I~pb(f7pcJ$meH8L+D+icJm_0jFJ*ZTphHR5G zJVhv22W0opSF7&VMIeQb8mQmvE>m?5gsm+ds0XK+Lv1>+ZLEh*aTR>%64h{albxzC zw}u$re2|b8hFu;W#Cv_Zi~$iNBu~{mye|)iSGD?jYc?lDNpzEK#`;2Z!f~7XoKp3~ zopuNg^e3A7{nAkVevqM)DQU8;@JZqQC!7b8wH2BX>-vZrH1SbKo$#OlfOR*YJ61n8 zl`^bCgdHOLDsz+EQQ5vO2{dKVLc5U#JIK;+O5(;T-T~YPaZXLZ;`#I0)BhiIGTNnn z;3$wB<}3x?mFGuQ8dfv*raTRM0Vkyw3DJ7##0?<dXjE+vO8P_m`s&oZm-n^;ytP8Z z+!k6_K2R1d3&G=lVJ`ombI5Ehlr$O5<P@|G`2<^_&}(f>5#B99;Ix&gS)6fO?_})W zep$%8ltLxj+k_8Lae%dKHgzLb6RoJLx+`UvY+&QDn)JvVZKhY5ZW~#p=ulXdogb_i z{H<1-O%Cb*=^~XAVyNA<y*X2fq|O91dXoOypfVqdf;aCJg~v<trRIuTs+UtdmwJN% z{6v%nqoBQCmVm2B+i?&KlSXM!n&&!UR^|8#e9=Z3(XN-)ON-x|ZP=~v8gYfpA6L~$ z4JDg_DR`dNDR*5~!VK7WHKZ3`+-itF+YpaK(nCA$6{%E3OP-LnL(cnA*a-c>GUF7x zG|eKMs)b}78x=4tU?+FCJv@*uOiu026ksp}!rktoD8}2mP(S5p7s^8_j?GnCkt23M z@Gl?j8SoVj-3nR9R%P7VLFeyEu?w@Tz~Xq>&=kCCM;Is=sceh3)3BE4L=-AjF>BJY ztIQ*Y{JPym1NR&ch2#LlAN8vR&SMu{M;e=oNyGI!lJ51o$r_+iyn*D3>J;1u5nP+Q z7Zz7iEz;5<i3)%a6ggKqPdZn+_a8-o*D_U?(7mjCb+=BpZXw93pYhPR&eI3w`>BpA z)ao4U9P}K#y1Dy?M=U`v>^k@6r>ceeo|oWC)l2M3TcFcssL;l(nO0i%gZ41~zulk= z+zkzL=sIp{i~d_ed{fRaLMNs9Q3fNCdAsLD{R4rGtTT-swl{WLVwPs<Xrw+(amSu* zDSP4=w{Y~xA?oM|mpKGg`2_0BeQi1LO%lOWuC)I_Hx&o4t2(=g-I5FD;h~)a<Anej zG+M2Q-DDR>7`lE<&oUY_Qsfyh0kD+bk8rU+IxbJOgGf$pw*c@_gFza&0_I<TI-#x( zAVz7)5?z;zvZRa6tb-;d0SZ+cK+Q2npOc6n54OfQ85jb*trP&6pV4V1^cueyD5&rE z1sGde`r1fkSF7Sm;-}_-ca{X+g+>wI-l^Ia9}keC{&0W@m|(U0r*4*LnXdoRPK_F# z)=Y5Tf1uw49*$KqdHNPJ-5AW08lv~nYw8|4rp2&xkrNJb6R<-O_Y7{t!2s96-x|{s zP>&QdAVU0PJYGgISJ9Lo0A5P;{NLT7y9W;UF%CP%sWGWgoH|RzpZf#0k#9$Go51$w zgh7h@;64UJTm6P!wE46=I<TGIlAIpW^N8D^GjyasGT6~<Z3Ty>p;1yXDbb4DSmHv$ z_-Pw6lAFl?PD3k4PnL_`iIr8_1zb<QnXxvO8d$`ag-CTxjVaZ3V~3Wuo_$jD&>_K- zU^Mif?y8U@bcb*NS%zIY89j8Px2(4i**tQ_JCFrS--RPsR|zXLMMf11yH-oCVfQ4s z6M)&APpx5ti7g<HhBV-msAV+|U^CBIP@aVh9{X`_1JIPfTC^iR1+VFaj;u}W`SlFE z)RI>bHcHu!GZ?Bte0o$EF>HXsZxIbH6lQPrvrKe1mFG2;aufoch>XYw(k(ax$+r+& zi~s$@@}Xt#0BYbWsE*=q#Pk&4Ca9hDgWRw<B?Fv7Rh}{+RX5fijor&8os?pvL@%z_ zF!(w#2KyV`*$GfCIAvf$WbJ_C892dl-^aWA`1t}F6EJgtB`SKJXuF6|O|OE~AhI$6 z?HV|RM$6NTXyNgh=Foc%W;==sftMDEWPkzsk1I+S1?%ISCiWG%v;qyrZ!59GHs5BK zI<zJkHa<$c*X0&aI9z7QBM8(*CKXF6jK_rqu<@=ZpI#A!^ZL_^cWewY1zxdNYB)Vo z({;D^YHz}1bZ6G&NZX&I<e#XnOfzy&@T1^>(r&~B6A|DJG}y8wDT{7B;UV>xPh5^} zTLsz(JT<n%Perkz;VDXIReF`grGGQ5kqE32>EZqQ7>A%t0vJ!Rj!o_)ScU1=(Zj0C zg;Otn57fdhlHfklXq*%SrF)2w<?Rn`Q)+IU<)od&14K`I&14eMkfg3MhAD$qSOhKj zKyjiXYK`lxQXd)XvQ)$MM;zD{{>)&TPyj5kb6871G_aGEZ1Xt3%H<Yiy|$A`n^3?} zSV^lph*C|t1Il=;#J(J0MA;q+PFU7hpbd4sG=P1q5H9odYgTYZRo88Q?%mOZWDHlL zv{ek|BR`E`40(FEk&$#PpG_eyJ9SN)@`g~aJ>Mt{Z?jf*cG*tN^|B;a(tw<XUTzQk zV8H!DLQD2!5pD?ql4)|}{s`Y*qx7oy^ok-n;QtY<)l+qR!sYeBjp)Z=lZ8c*0Q)sc z6NLV;S^InM=B}I3@fVguBP7jQvMmt_dkwH-N7RdHxZKL3FjnAdeaWx*kJ0y}Yq5%i z4P<dTFRxiD=%`2!!P2`h@`7BhC&pl?Zs-9+mdrwBT~qtVV}|ZuKeL>~-zsf{P&+qN zYO!i$sdY}jUfP-0Rpu>X$@(-s6eb{f2l}lDBlo4C0>`Z%i6`5%dB^qM^$}OP?-r9L zN8F_SF0qy+{5|&kgWc|!=3yKrpk-SJ*#EWHb&pv3`kr{08FQd?;j$8w^V&No#-Hp> z^_1S<9Jw{v41%}-g^>Iccm_KM@~U%dI5+rpS!<D@J(sUd-94>EHQDyiDG&YjR@o}b zS|X^_km&xRK3r!*sB0giFKpjz<Fjg?9dNUz_Me@M6LO;E0=0iY_Jb*?)H%3_o6e!c z$%~@>hk5IuWW(miy`(2jx#H^FR2usR8UoocVi_3_?=z9A8j8);yXIuCVEyuBk0DFv zd#hM}c8pDhejVo!<|C5ZV`@WZYntEBEsN`K(5Svlvu(?Hztb$Gt86^j3Gl%&n*DKx zjZD*bgmgXkj~;0$0JDhLb4u5sd9sopwDyUDqiWb*{oG`LyvsuC%tscKjh(C2i9i(X z*Jcu(SKT76cCXFSvT76lK@12fI%O*w@@}#E+NPNO8%JJMt%r7|H+`*5GEVFIbgc;m zS_7_wMO^lZ+SR|Xtmx!pZQvRC(X{Nx;8+)J^r;UnSfYbao{F@LZ3ALUjMuMcTwf#4 z2TWBXEQ|Q?;fCQzv)}Ehn75>G<f{@h=O*Af<Fd-_c4_06d%8@IYBQmT&52%{DK$@2 zM!is3Q8ScfRHz(=LI;~k_;9}q8wHJhT=wnm+h-VlTy9rAX>F4qhiIMx@TUV=<*ox% zVDl9K&&tLZaxcrS7PQIVtE-@`;V_K+b!#eyx>(g84~4uxObg(2KcYSnDk(Y(Cn7Z% zl{P5K)d|w-GCBqh{z$aZCdW<A|9(C6gLaivAB(elL#N+$4accP!Ym?Gr5vR}mC_iE zdW>zTR#f5flL8%$>byu0^Qc^-j#9JtEuhrb<MA?qxkfceqba3H8oP7!uRiSspq^mE z)e0U(Db*$JwQS(%ni74PnPe4fbIPNbqP%Xre<3)5{U|II8s7Ldt*h~R9??jD!;nus zj3t(2ZQlXxs1Xf78CthS&W2)D&(HQ*-%l+gC?zpG`~DD}lt;La9w8h*C`pFC6F(UF zo1bJgTFr_X9U{xdUOhaJwvaJktGf)_`Ub8ZISdpWa(WMEb*L0-_0<FGoN(3Jx&HAD zeHNsXlKHXMyd!P`$(sLg>Qu?xgWhy5z2bqhdD%WlqUX*cVyCu>MSHf$-0h?2dEkI= z{a$<a3jiV2E!P%y@|T69x-2FN2OcJ?fV#6k&JwYeN3)W=j0_zCrd8%tp<s3-LLL^9 z-P(|MJASKf(};0|wtP@~c4SGSO~uFtnOkVfTdQ0kkt?(ZiW-S%`}9A%<_Fa>j;78c z;00~mGaaw%?f`VBvprC0y#B|YWOet(PhA5ku75fUQC!OS^c*55ZH?gHmGMW{Qo3MT zGS$@CE?v`4&+#C{9mqm`V^EQqQ0cX)uAN0d&tDjAwc<SNG6h#Os1%Q5ATK)kn<*#d zzQT;LPP<NsAcqLzp0>m`9=eg}&LMBG@iXQ}w-wU;?k6V_>U2F;D@7eprzWGJQ@Hi& z`>lCRcYlHf)DBp&+?8i#bCD{KICZXN7%)S~5axTmD!6Jm4$}Sv6l0E5x5ubT9)_5y zwOUU~FP0ZTT}9d}wX=7tT@59i->9U?R&9x9MBm3MwPy=uEnfQ!4bGm}PNIpN(FdC< zk-Yv)M-jG{4cr#ck-6=8@o9YCutzLZtMoI*d5{tM8f@TrmUi=p|N2Hp**x_)XII}X z6hSGQxKETE(Gw4p>NLIZzr!hKoLd|2MyU}{CFokqEYhe@F=o5Up(?rUPQTs-|D(33 zx2NJS&?kzdF~Gu{MlfzA5wg`vvH8V0$~z!}eRf?0>;gc^fb3Mk!8f)SO)ML*e$?+U z{(fN*anC+&;cGr(KqJ}4Rm<dBPd6&v*lQjIvyS3LKh8=-lhALqD+M}bFA0BNRsf0_ z)jBx-em0)2vCpahO}Yk(FJJx3Z6Zyntl2(hGcHWh{yE4o*HS8C>8a%oD`@yTlf9|# zy{_)pYqczlQ@Wk2q4<AvL6hZ`=y~VyiTBiP6V!J$JGD%Wh1zC6(KhO!6P$QuS14P* zLUZ2u)utU%-_Yn#*u#Eoa@4a(sEzy~{sddZ?IJvJ>c%(oxzM??WL_~IE<#${mLRah zDDTkfB1k`^NCk{R(16yH210hsrEb$nQ*X_@V(s>^(V39NIi$+$3I!X9tz$|#c*WKJ zf=f-cW+isH(G8f?)NmPIVRH!<WFTdCXe2f%`KeAtgPrC9K;eXzvlvmW4Gv|O$}U#C zkGpTc+AxtG2n`n)vNb>{x6V|$!UF#wxcLu4bZFN~ZD`<s#a|$~WP1q}6!<zaPt{g| zS1Mc4)wrqTr;C3(IwUB?tPXc1H7vUfk5kvjX{*G~xt_Bs(CuE?=Idck4I9nktNJ;_ zUL(onsZ=T=z7g%)8e}brWv1Ep0EArFKkFY<BQhv794N=D5dtEvX+mB-5&67EgiNHh z=GO8IW3LfpQlh*_58HUXC$!C4*O6c_(b?9|xt?jbv+c<alk*Dt$43514|5Qq>k(Bz z?X<b7M+o5?Vp$X&h#~_GVKz2l-k&jma3bNv4v6se4x@3)g%z9zwn`|r62wx&m>gVk zqT-4&EeS*4BLIT9_5cZDqg}_=UsIfM=dYd!^PULvo6?~+>I$j%BCks63jB%$bQq+U zrogXT<vh~d{iF?}nVNI`gE9o4SLst`6^00&S`ZU?7ueS$(bd$y6NZ`<KDZN{<y_lL zEPr+AUo{c0k-7Am#{rYJN-0cGTMj##8hP-4wfF5|O<w7~o!zNUM}@Y`XaOZ{tz>(w zA_feXkc?G93naBTG9U)8L_rA`m4py7EmfofQK>>8ByFvvsa&iPF_4gQ5kx{#4Hvm3 zKtf`;B!t|^^*di;XWD(9y`Qtse)e<DJahC9AM$<Qde^(&wZ669_x=6W`m%oJ5jnXi z%7XOMd9jafm^!vb%&23A7uqfj*X`a}3QN3<IWwX;vzb%2(~F6UQ>kK<TVD-7S44}g zQ;9<BSm(c7FAEb@+>lM`9u5LBDgT|l_mg(t91QjPO~jwZES?JIJAVJta%@M_fj?7N z{6lXx-v0fOAH~1tpp!jwsMRg;UFajk%2Q6IHsAcX=d#ZKoiZVefg}&Ha=vGKiXLBv zv_3TRlkivWJGOOqS0ikPD32|xrtT*zmmZYi1hbe>!HzLg?^;OC;#`hnqY8d$G@mAy zqZy8R)U<tB=4e26-dGPuW@OQ2@1)yKlv2ZqrWWUydf(170--JwD$XBbX5|+=m@f}S zY81m-Yn%xGM$^m6UYhac0Qu*8&rq4fl@u$x7jbNDzTL}mV)HS-IC=G@%dB!E`yAi4 z*rsSkUNU1fi5WzXlmIhvYVnS?Po=ah1>7Gwyl`)?3Cfs$?{pa9AUu8WR5pc?8uIbF z@8M|5f#kHoQ-n4)uDINI5Rzl%oClgZ)&tn_@*!Wt;uN<1mpdk>)*(*l;XX<T?JJ@^ z^i{mZDbLiDjn&F$JAQczt&MhEUs!BkfsQ!FtCd+FNSKW_tttSwLG>PPItXgB*!0?X z&4#m{zIr4SJ{x4snDLgMa13@=J8LDf+ty8!(-+pzbGrx$0%Il%m1dUdp?@n(a?V+f zMShj=*-S|&uBR`o1o_K@vq3{M4WE~<!Tw6#I7KX@U#DNDUzgD5EG?=A?@v=+{~oTS zZ}?v;PP#343yb}?hKQhKAIRF0bpV}xSEXeLQhr;x;X7$X_KmSx$@5>gIJ?gI5rH`Q z8jBH#9mJEz)Z!6`+1~fFsd^X!lC$#7lQ+J~Jtj)X;-GJO{_<d0U*o^mVE8W$)>zX1 ziw!ndszwZoS9Sg<%al%ay}3x0jI16w7l!<J9cAG@Y6}H{E$G`@-^!QDn@a~V$Y@mN zjJk;B^vlWROG%XZ-D!ZqHVq=fMlHo=#bkMI?0Z<W^4*TfytCX#ird4{m_x{=KcZ(a zKag8N%(->Gj2|bwd2WVn`90LgdWKBa)x^q7J<^TJCt=3^ybER4xYNuAf}{p-JFfkM z{$zD-YHRym$Hx2Oc-|>V6Zvk=7sPw$h~VVOZ=HV_Rc(^qQ;#Bt!yh6NS&Yhtj87<E z%<KP%A2FO^&Q%}vtBb?*#fPv_4^5UY;AgD&F(z~%b&$<cZld=ykJ|DcR&6JbLP@NO zsM-EF{Yy1pTJs-sY+@?ev6R8*S&iTTt{g?qT%qRul(lM{q)j||O0jT2?CdlhfSf(U z92RhatBylENy^~!0s3N@<hb^n7ywVqn%`|>B|BVhXm3d->>6V2tSPcZYT3)Ak5=vR zpi(DdSxB41t``e<+HqiB;Pad`n}5!+QK0TOJGJ(C6<*8?U7j)zPLEoIZ!Qsg_QgH$ z<P|2_Ij6@lQ8s%ut|uROriIuX#{?9k`6JmH&=r-Pov}Hx1r!Y!;XAo9l-Z_2M4N1? zd|*t=v^h9}cx&r3Bg<YBCv*M&)sw1(yY9!`PdfNd%@0AR7hY`zMT@!lL)Wys>)wRU z#_8?!X8Rum+mo@+1y-lp(k5ZBVAW1RD!`St;+iVWri47lnuEA9%aJtM+f_|$Uzo8- z#hkkJ=bP*L<8F$Z|ANm;x2qF6ngo1w<D@>iuGwMiUw%kZlsz)LKUz?P(`Aq22eb|K zIRv=qR72dEB+Kv-GoUYc#P6{+nklZ02r{wGUQ8EN#%)Gn4DJPJi}17Ik0EraYj;9U z<yH2)S<s}FA=k4=*O;s#9G7|4B{Pm0%=1;<8)9!q+Wi^e8fZafXH;$0-_<uU1*2|> zee_j{E{QNsMpaz|#biP0e{dOnOms=YQSQ$ctxUAphgD)>`<eSDSAWo`t{zdHkEAn# zS(Vu-KaHrwNJ`4;bWWqIoG{PbT}`klLv82)9ab=i1ckb#yL(b84{+E|q4eddCv&8O z2FV&Ix*EFQ_g)q%kU6QaOIG?v(L>hSK5X2P7R0&M!J2L8#0T`e39Z@Ahl+wL6)70< z9X^Jqv@doH%A6>Q-nvZx;oEQYRFye2Pk4nv1)MH%m=XDjyew&m=In?rC1b4Oe)QNl zV1F%B3ye~4D3OZC#3#0sXH?qmR|9dUQGiT5L@VRL!@(VvKSrU_pwb6tBlOyqIfu+L zs+Zx`qt>J@7--D{=yS*d?s?~Vr!Z1<!3WXTUw8g{y|=JY-=xQF&)U*IajGW0F|JyC zxV~CYjpAI*{(2b(cJ6ldOy08WdHcMbRzhD9OsHfZoZqj4Rivr#D)DJW(syl=nDJCr z7_%)YoDxn+S+DwV6(eQlK=^=VrE<hFYJvHNf(iu1HI1-ExUEAXnTR?3i2HE?>%UK> zjls?ITMQW8R7H8aN05Y}lG>FE1wl=DGu)+Bq8ViR;<aOtO97bv0!c6B?bMk#oA4uw z_EStU2jLhvnGM8g_sd!4UeAM$%XOzp`-A<rNK1OG8_&PG^kfvb$O>s>YYB@9rn#lY zgn$R>wP`=F@*m&55r`h#Hi+qm&>L+4VWI`aB5iMMFkrAGAjg@q#pD{Kh`4R%(~obK zQ%$;M>g71M6^m$v#Zcz;#$s8sn9MWyFcVyM3yt#@cs%Nj9^B?U3Bg~<&lq$WidI2< z1lF9;yHsZz>yB>F(OkV%4!f?K{VR|6Pbe6uR*6X$h>nIe!2B&)@0WV=6DFZ!08#=G zpvNnnI0~>Q{1FiF1FFHqM98wu#%qOT3~!vwCNN5JOpkZNDtP9pemF*32)X}1{F|>x zx}cKQy&b3Kdtl~2kR*smI0gOc4SN3u%F&_EP$5RiN03Zw<Fe&wl%)lb0<=}pJh0zT zu&WiSOCjPi1-kfE)(ty2vDj(DcRJm3m;YP(wQs73*T#x6C;G1mRAL4S=6i!6W=2qi zyN)u}Pa!bZ!UH|%iHm0YupXDv@s`%YQBi4ogxS03s=Zmzu218loeUJoDLnm`DVOL# zuOedUB|T!VrZXfXwMVAMf%6ooiA!|Kdx7FNGa1mH>bIApH(OrJbw`R^qPt_>SBAbP z(!bC3lK;rwKyhi1vi$>{OLRq1CnBno$ap{J=-QA*g-Zjm@ZtukUWL-yX{2gqcPUH8 zjHW;!lcm!3!Z=8PAbyT~u~6^B<9&PoSW=kJCC?Tl(X?bZw+eG6<3Q_jUD|pqVP~B% z+z+i8Alonz+=ia;0F+}C<<LzJeIu|A*e};{-e#`kP|F*Ovbh!Par-q?rWIUj6-DFk zAkJAP*ym@O;<IYXDO|+Cn~agT`F?zZV$SJ$U-F%kF*NZmN(!3U2rd%`t@GFEAa?U& z8(UTO;lX*~$6}6rf4I?Xx9BLpRYi>GbHuU;irGG{&tanUNeeOaxSCvKIJz-Tz`yqE zl^2};5o-`}R@&DLap}*MzF3pNJMQUS{F?a7{FLuh?g~aXg}Rpv*B!SQVRjqr7Snsi zG8Zy{c9=dA{d@=DgUkMk#kFsmX<vJ&mlbYWBU@z47ku`|S0x>afm>=7t$)tXn@6IM z)&z6H+ojF?2dw;5nxsvm-*a|doEHmQ?QaIpUpRZmrals&E%SF=1SA579Gr)eZ$R#& zi$Cx?9+KHG_AQB0glH&a`s^=^V@@~Ac2;`TU`+EXIPlDwjPz$CLJ4cu!nh0mO1FLA zv|VpdTQz{hlX<-rRi=u_68Odq>7<~aBj>>}=B?U;+2I&jsRT;G__5Q{RqD}9>v3(* z`g48r2y!@@ITu0vww_zf!gs67_)uzpaFgCJA40xsR$OgXo@cbKV_{4T#3w^zB1x0n z&?7C2nyDNmTF=vw!=%MMhh7SQX_kNX(Gu;6niD#c-!qNY=^j@!jT{abf*Xsga(ap2 z-VnKzhdwt{Z)S9>tpm$cPH|Qp0&`O4HKk|Jamf&mZ!e(icdqvYk&gXKg@Xc^DU|8i z4I_l5p4q%06O(~3KVkZLNGEE&AmvCfNLeezjz*R4gonzCbYG3U<2SGH_U!%!FKn&Q zDxmq=tHdrl8kv{*++XI9<>wKb@u@`SSc}6ZvxpNayno1(`fviIu5G}=_EXs0u5IFq z?!&f6uIEA5te3lx1Y46b{bi0;$pN<sL!KxcZ?-g)wO~Fz^G1M{*wyjFb6-w?GTc%m zBOPZ13d%f_*EaX$01=d|Eh&CkSm76;J&0p|1%{H4vt|0~rOxh$y$Jb}gY)afE(Hmo z7<}$xaq?vu_PKB7^QGft)7MpD8Vz6y?Xz9|f%Kd)O%~eNRBh|`EHqf9Sx5-E;Q&?s zcd7)kx14u^Ng;iE!X*6>;utJwkL;4$Fqpg~o)G58A3!e7UACZ0mk%t{)j-8djZ5<v zTHh=z#SG#g&1|{GX|rpI&xarM?Gwf2#+<J2&jG`aa{W`-4PqAzwVHg)^e~)ab4Wg* zD{tXG&=(U|-lwiDOkV`uVFCRz**UKd5U;>p$bL^>-1pNv<n<ooiaaP%m#Nz(CKwLb z9=X&G_1o{;S;aueho+j=SR>blArqM7=QX7*HZ9;(|C`zYs7NNUEj_Y{Ny9n?#Nv3j zdY0=rd7eOK+MN0cwf5AarEEwt)`R0WiGAfZ8AT(>)Ru!;BAdvjo$V~t6d*El9UVih z+w;u0MIO@m2IM?=5(&7S-D(y%9u^<I1Am0|oncG9?@)`MVq!%7I9+sSnj+YEaLk9e zIDczdCcc7q(EjE;*dfzDMtt5xVlj9Ji$^Q$e1HVt{Di-qqp1<ZrU$``3NX&K_aQ1? zKY1YiMH$dPl?kcdpEV8d!$>!Gor!U}uN2V!8Ud?@YF9T!GVCtP(U!x0)Ax8)S<YqD zH{;--tFi>{T-Y8y)6??sK}nBm7*-Ltdfma*=oRBrgOADtpoa_BANKz$fqP^zT^DuF z<sQRODZi7ZL`4vr-fjxCxUYDo{EJC=7Sz|(mK-pwNdff@+60f0ZgyTe4@M_=W%tUq z(YfzHUF6&S!AKOm(iZb0gC+uXIk+D4cYRZ)FM`TIo@iW6v{$@Ks9I-Kj2|6~%nKoB z%FE6#aZ*ZgJy20)C^NUd7No0unwE7^AI92FPe>j|Ip(_c152a5MXVb<^HF=}<Y-eP zD{MX^oRNra$)Czdxs}s}A8#5mJta*8vnZzy1N)aI$o=;`rQ3g0=PSKgL;aia3pZ|i z`orZ41;ZN(l~;L+`U>i8G@P*EDrRUJ?%Axtfp1?V@=&=9@w}M{zVtqpvxoBaU(JF7 z^l1voIm^oeFRk=)X#AtS4-CJazPEF>TFN}dPYbO2hO#eY@|np}OoM^(Opp|YhzMyM zwN*%KeVfinaf`70Y@2?_oOi7+xuo9wDDNoBqpNVf%+h(YZ@@M?osa4RLxV3HP^Z^@ zc=-yZ!ajE!vw2A3J9!lm%&>^3QUc^-ads>vmpcc1e$!j@fs@ytT=k?J`t-Yq`P-;G z+nf^*T)03|w?1HLsEDhPeGYQ_<Y*x(;2gucxXY)JMK%`VAz&%uOt06e{w37p!h8p_ zJPbo7zE?6LX|;AVc~Y=52;+27P^z7ov1FPQM_032K~eLkk{!Pi73{=><k=Qfw?uKt zj6F9^_ZpnP66FIx*S3<E^q;8M0hn+1kw)SvzY<jfczMW>b|PEf|BLE>7u5y4{%ai> zrUA&WM9G2Af2||NHskp#Q98=Y=DzIRmxKBL&btfG-vwvrZ79^07%CK)%tf88rrcvR z7vUVZIXr~4(q!wVr9CI44yvu!sPLuzAW*|WI~z&}d&j>FkNR_=cAyZ}>nz~pTZd5g zUi*6s@dAqjpS(hU?fv%TN|}{*`Ou@o&R6~nS!5!PkqNW0{PNjvKkbP=q%Vw;RQM4N zr3pr{Y~K?8Qp#o$I~s2s2M`n4ltt--<QXFMVF{Z?&;s2^ie?ndj&RhBl}a3Wkr8e& zWV!OR(});4Gcfx?SwwIpJuvfFs=pL;RcC3Byhkl08&>5v&EMyHmU)*M%<Slo;V}gc zyD^5I@;fLgwIDRDtg5L?J?%<=_Yx~YaKfa?<Cv1J+So1WF{`jcS=4a~6Ge=iKCKw5 zD4WCet*uE>;RO`MX<CV7C5Y<&BEey{ROz?sL^3nrTP40u`qCWP7okt6N*b&1;nh$3 zwE+3RBUl)2y{e0T9F?Zr8te*T*5i6l(^H=J$N35qs>XQ1=zt-OK!(clb2UN>fh{MV zk3nw2$?FzP%__0e*})U;y5yUkve0}3mQtT_m$m&;xucd_+8={3VxgY&Irb_gwrsQ^ zGnacF-~*waUaDf+Ch+o;ipCz$l5Mkz8r@Y#6*HeX&K&AlpLWzqtoHWA<m;8Mfd^GN zyi9&UC3OSZT<fatT<r+qNlheXRgJlSY1Xsm0Pt<JA0JF^L78)X`FPZ6GTTiF>-cmW z|K-Pyvp)=Th{;LHsZME>M>dsRGWb}ELquUlv5PwxUY_7tgC@!!ld$Ij6o9%o%|F*L zo>Q^bd`FknOZivHzn*WkY3*7SFrU1eA#Tq@=J{g_@WJMP-3LU>i4Ur7PKW9=0<$b; zrDz+h1V3(0K6NLIX%&I-GHy#s8}0R8VbF}AFes!NLw3gxM^}%zPSu+IQ(o2UxLw~k zNzYWVZYf|9V4GI~M;IeNXm-T=)QVWG`X;e}b~zy<r5zM*)xNt5t%`HUJKT!bT%o5q z><w8;yZ6=a06!B^VmV1fgUM%^K-f9)0Zg(qNCvJiyRBz~ftMbyJi3}ryCaOb=_Ktb zkXXRT6^wU44XeRn$M@)LqGd$B4=PHQo<$}yyuc#n09arMsD0}>8Rb^nOx_GQEbLWa z#W;UZI9TJ;iL*!}@Z7HZ+~hNtsCqC{z)9CaPz~OwM!Pln)9PG6PM2RO_cKl18nR7R zUpb%!LzFKL-t@pY*j%`!wQ^G-VmYql`~fAAYDnIz<t^;yPS0><C$}+|SQJM$%Bh~7 z!BU=ueV)hJ%aGQxryoAXG*9CAMzt((9kngNMCxr-Eqx66z6mSPb+NRqYZuFQ<~n?( zONR0Covm4W8YmsU%vM|0qj(Rs5l@gya*{nPkKg4xSK;%ln%;MlL%$soOqO6|<WR+? zLW32~)2Ukm3iGC~V1bKpB-uQDrD5&US=qu1;n!aZe`%JNqvb#U5()Hd%$fZCVW=zg z;KbXOYWb>}9gRDNBsvAh5EpJ_<%O(H|HiMs%o9DoUP~d|n7uGlz+K)~6s3RSAbKtq z@O<=%HBjv=2(i5<tJ1Jg;BFt_RHeY_z-Ll+L<;_Z{j4??k!E1B1z)tP(Sk2Dmmfk9 z^V!x58H<T1qIQ!1etdCws7ztD;r+;Im2k_cCm6h$Vr$rL#Wn|Mm}{GJtQxj+;>_e? z1;VoEi{S^u1+5-dUy2#f+0{0MbKU_U<wT4SBd%WCq)juaq*B5R2{1cV!ZGFR%PX0_ z6e~C}T3Y&wtS;EhK${wSH2mYIN)Ul_$RI4D8zJhu0Nd$Nz0NB7gQIP<efo%WVH(cJ z)m#nE%`bV-0x&2nVlb*Hbf3&(b9#$OQ%<7_UE1RE2tWV}&<e=dM|joeRu_xS1e-p~ znD~?44J^@>CtTGCl<l;43q>Ae+!yY)Y{f5uZHX{H@`(^cj)4R`frMAd9HoD!wUdEj zRYWFW&4zZ5|9rM1c%}tlMcBfHVtrI@pGt23E5Xw8r(oO4sgAiKR(UTY&P%eBZ#JoV zJN&9|doC8{^6oN_);P{xe+G6YV{*Yhai5<!WH}iJW2F9sERi%V)vO^ezHqu$sBGZ* z;jc)GYbdj^<99dK+rJgBxXDTrm*uCbGbQ)qRVKeYZ9taQk}vH&)El)VKQ`~g3&ym9 z^_D1&D#Pp)M7)8@5(uReE;xJnJ?<m##{_Frp6hhVDliipCAvY#Z~>PlyTwun?7PDJ zx>%*p>UG0<De~<QSU9JKnf7<2j{!Nrc-I>P+|arZJeY5j<<an4o|=CH=go4jh_4j0 zD7|J|cRo<@w^{_0IWyJa{6U5S9nXAVA?XorPnSe6?Iz?1Pmd|r<DwA{+;@RaEkwDt zQf*#T8?(|uiV8t%yvxkgqWDSVRZZgLRP@kx2iw+?UB{;=`8lL#oM6D)Ho9K6Q==VO zXl4Nhr+OJ)3*0Xx<>mquQ^$dCgE&Wgipt8@Z0t&-JL@7>U~D*iUdnj=^Z`h<FSQJ7 z`YVxdy4i6`7$Ke24LbzDA%>>h7Q`=t;hhQ+AP^H|Odeb#cjs7pggYB(tFk{UBkG}I z@7a9Q(wr-GNeBN|Y0qLqpu#=MJ|;q3p}|o>3>ie4LGC7q0IPW!=!&btDio0sI4<^c zo1GR4g4ANBgg8mxx}FB7EFz9IGb500pc!Osg2h?-_Ru4d+dw^tU1w3GJd+F7Rvvft z8d`NByS@0h)ZU{nR=d=2<IFv-D(RvXs?WW9c$PAix5P20bJs#EbM<s+^%~LZuxl>m zVKug4Ij9+kp+hNbePKEGu{W<TS-Tn18allmUz(qqKE^8rSw_7feC%EsVJ5COL|>1= z52`EgZt+{|DTr6(JXJNyOPu%6*2!B@GYbQXC4Mp%wo2&duwNk@ZS5G4N1U7tR+U5C zY4L{+m?ZG1b%pxiSB#L&zF#aa1IeU-^qVuK)(QKqmcYJVdgNw(F(=^stJ6zGl3w#U z$-QocdM7+MI#!wo@^EY@a$XCL><QJ{*cVCqkH*144zha1-&nkZHl@-ozl+p}WK^J_ zoBR=-)aaT7`eG_B3{8w)PT1F*w6YNPgZm=w!j9}~Ax4)uGSkOI#k8`76sWwO|5h?K z9lONEbs4yYK4(nL7eU~_f?JK&UxStF_TC)iDCdIG@-8+ECi*=<NZq;}3t(u8w>-c@ zUHfs9ttU{*+&3U*<poqonhZcvvQ`VlU%9K+Wr9wp9lj!^BjHt+)GaJBJS5F}qmsTo zGZ)dhRPf-1{u+TEzgl=Pp&(AX^zFXcgq!Q=!Q|*zbX7=%-%<OHq(<SxHy}A?nZ+fm zwvC;W_w+ks?Gq1CW$P_}RxI7mGtl`?eWLQ{B`T=Bz3Mg2k->@naOA1}+`*H}aj~*b z$|U}1YT?_IH{v&E2X=OkJ93<TpABzK%a~cm3`&hiyH~)e!Lxf#d9k{87NaiRzQKKa z3dC>1ft0q>6~Q6KQjAX{BY31OLDJN(xK|{RzuN`r{4;1?u#G@9g>!hj(1+d}+7tTC zY@&)#1V;~M1O4dQ@=IlY-Z*BVPYe<t5n@cjR2q5+2;|K5=KkZ=%vtaKq*bka`b^$Q zHG0h8bI2LV(SwP*xsvo74zFY)U@fE&*$#EQrDHID_a%jPlJJ-9)reVZMsEJ6^x)jY zP|+%kon3+qH0H*6?d0{~ZbfBn^YhD3^VeQa4D6h4vN3@zcypR2Sqd}50vnHB0!adf zkI-Em9r!*4|C3D@AK1?W4jbGzBjx>L+~FSDzUY{`x2Ix}oA}qtbPEdyVZ?_1FW%iE z7^kRYr+=hfEuKz+;vzTyN1{MW{00}f?M78K-Mg_9EGboRo^A^D_Vg44mzh5TMK6xJ zOuvlo-Se09^I0kVZ^do8NGKD3)Sx(yK-dRkq?<r3d-1NmeyP-d!ctm_)#+$eIwJT@ zUfpD3yG{hz$5<l99=(LJ#II&{={}j2p;E*QliA$h^t<oh=-pB}22Y#8kV;r5v_07Y z<i@r<z;t2B{=DB8r0y@F9KA9?l4hzB*v13kq{^7Lir%I#vW-5i@SifX_@A<ktppZ) zKDRjvRuN(QX?Rm;-Q<OA$;1YaAsfmf<;`8krh*>~f9PU2RSTHO;x8R`2Vu3y@t}Hx z$VatD+EV4ub+eG%6LjeE#g#q!Bce(qJ`6L@K;eO?Qb|Ns@}$do(YOe%41vwwgFA_Y zm@F+al(l7-Dm2{YNE1-1MJsV1P5sPV{Tox2CVLmLK3%o}Tyo7o!>gL>q^uSkp~z+* zS80(Hv!z8WBRfSP!KM>7W%}S0HWlx~0PnipE~gnuSsZ)AGuJN`(Mxm%#H4|^GS9?3 zV@<l#`az|%|6-M$og-~ji@%}7dRY1;o6ZFl)bHCI%<_To&0G1okRfVzyD7cTa7b)W z93`Q*<Zs~f?!{}VLkupH5YmuOu@_SE`CkZ}qp~Et^ODpy%~??(s1Q8wUxwqJ)TQaB zcT=(|72i)nXW9%Q19wrG+M73cRhaCPy2+A4t<xj9E;ze7ffS3y&Rrd;ZQMeX2Rg<9 z8Kt5?5-~d>1)9RIa8ej|2hA?Jrg#bDyMl`+NL&hCZatl+T|4d~ASpc*#WCrnZ{ZPc zqFG=-$~U+at6S)KXDd){$i|Fyce0rpFOVi>(&MngU1Fh2QV*?yFsM%INfh@PQrMmv z_u{)l+(pVZgzX9l=n0HZfZ-B43S4lh%ge2TwbQm%;3App8#<?A`qpkhNAMRWT#I$Z z8dzWjjN{5aWc&LwuEVx*LqFo_7Th#5UPTdu=31!S^)XO33T2+N9`}v7MBVoZWX#os z+>0rZ(yE(kaW3^{Ij=NZM(?Y~IIr$r9#<BepJ1DDs^3Km)84?yvY5ygil*%t0;+*r zL9a_*IUgPh8S7M|>J)mPl4sbCstZ6Rk2k@FPi>frIWv{)d^ng4IGY3uS5AL*)smsy zN)Oi6&f)AM_CaQaz`ANw4({g@=69(ZkQKeb)1<b6aNWSQ^~wv!Fl1vS*cbL_D>qql zl~m3pY#jm<eaR=sF7r@F%^(`A!l$$$DyVJsC${(rPbdwX@*iGc!k-aC`Yt7Ab%J!+ zzSIroS6=aL+PiDVe$eDh;#NqgulMW=LftRVz3_?J@)W0!*S??xu24<}txxTF(^EO( z=JPGrU*%l=;RWySgNi~)@pl`d5EW@|(w?6%N)*{;eL?O!?1zSZb7QUUaJ|dswExBm zC&4UNxHYxl!SK!AL`HlZMm%W?+#YKhm;*Q7=!#gTdxCt@B4jSOQsRl$ebSM9CQCO# zau{w#w}ueCF-#F-aWZzZmXeIgvUq&oQ9G$??{O6BvGryN1X#dOtdd@BoW8Jbd&t*) z6B+*b_zi1huYK!5m1T5xgiqU_vgEqwN2Xo_S21%KDie*A4zbPHI`eEE$QKvflsLw0 zfa6byapu7~Rh`~N9gxHr5N-}249Ywdaj@EL^;7=2BDc9+rn4mx9@|c<^IQ!3(7s*b zfxol%4BUk;&BISEt_#_fMU2~jXX2}~CpT(2xAblzc#u~_{NL$*5><Pg6c?>>UjXZ= z|Cs@3A3{>3Hph4#Rs|cmjT&Xq?*sXI7*kgwBViae9ln8T)qRARr!^`cXZka*550eJ zobpE=@2AHY!bkuaQ+co?eyZel>8PbYdpfv?VwIzK_B+N4#vzKnDp^{qoH%s?1>eWw zs!!d;g!3&MGAM5lJf@^uxkr;MUyV%E^6HB?On&@gQ#p0<<Wfw!Ok-?atcrv%2>k9M z`N&d`Gzc;YMqW8TMzoGc8>ym!D55LO6|Z{+#-tz9vp1EKBr00o184~np_@BK^@^fm z<gxlUr5fP;@-g`+e?LSTrJ4Pu)Tn+aVXG>73+IWd^#7!oipn`GDo;weDPCvE!1#~} zQ^VbU&{98USCw)-58OTB@u0hj#76R{V?)$M*M>M|_#H4&3c<C}1GvISX87VHBgneY z$)QG|*G3>q;C)eq7GQK;1{H7qgiL^N8z9s34w(&=b6ACEqC}sCCTGz~kU5Huh*O5i zA{>N756R8*!B<ijz>Rp}6MizyE$ad@D;f28)Fv=fG(U=DNDBS6>{i7#U$W*=A?fM! z4lgm%6q0Fh*%6A(>R!`*E@;LR4s7^IspAvBXP!;%n7p|?#AwmqqogH;3!R(KS7#8o z6u=ose86&Cgs0`Mu9Nl;Wb`kK8nr<K<OZR2GrDy)r%qCZ!U%vE?Ig<+7=qmQ&^5F= z?ga0h3VIw8@VlnWB?7$ea>uz$_3qn+;d?0UirBnF=(xJ_lr9`KP^f1N8Pq;}U{cW@ zSJqLB;n^s_l@@rB=Ac{(=qYMBl3SQnVJvbbgN2>1_r=Ox>jND2q?i6(?~y^M6r6Y< zu*?s$B@f&YmKR@G)Vwx@{4?no8U!zKPdWdu3)nyJAZqQC_`$cLdb6hzmb~QX)9kiE z`G%?Y(ahyBv0dzr3m`!U>d3(Rb)bwE9=M%lK~bcREe{z}e0xL7W;QBrA?1_L;Im$= zc+*HZQa7J`nrykCNe1Teakia^&n$nOul>U)VF7hlDiP;%GMt~-^gXkt{JYzA85qG7 zI=`z9HO&>6QyP?7r;Cv6bcj7>?7|acOlDjwQ_uuFJ)-Gjpk&Ns*c!gcMLKrnBy6W2 z;m}>=eEOnmGU?7<BcdCM+4JB+j1fmNLcN)ys(RNiYWDK5G&IK@PLrJWCBZ5^crYmn z#maL@scy*-%N)*8TrKUH%Pk^pU!;}k)I>?+3#q>@J7)$EzToZ5taP1gYLjzc#T~E~ z?;CY5!`l~yJxk}6$H`(QfK*|eeEO8V<t_Nr6XaCU4YsPicVuHtxJ4m6VP{aKQKqJx ztcNA?%XsiB<JE?x;i^i+vEc9EghWCSK{KzAXm{V#VxdnboQ41jKy5%pJNmQ~bMG6^ zez$SA{;xlLYJHtJ@p!as`|K7%DnHr!6XOl-J<6gkr6FOlNMDKOj=VEo=7|XzNvLL~ z!bKT5;MdPteG!jsftgZxVBUr`ssm|#3pLx*v@Yg-0b_Y1omxb?5r!Y%81EYc2s7^r zY+8qwILlKhzr4LlnRlYJ%;8!V7)>HlIw=0IC|(&d>_Sv#|7jiq!a=X((W04bwRL-U zrq%Im)vTdTRw!CjhZjtW{1(Tk+ig8wqXCVnh>Xm9r$v>l*_{o=w<ZNN6gl@tA6jf} zMhPTX*2n@&dRg4BuDJp1=-3+_)nPUk)|J@@WOilaja~0Qn_}NyWy*T@W5^ITPE~Nz zU-cxUc{KU=o<3O9CxzDP+srFN?C#}L_iAj_lIlDBYaPpH8ejhX(he`jz(3>|c=BC` z8iPm+;by$0`(!=CFWq{thRDuYm0BO}XueoQFFkeb_TbRY@G-@AIh!NmU_5M3A0sSQ zdDK3s&<23t-kg;Ohc#*>-;c^a4{35xtSUfXh+5U-dGOFHlM^4TDI<(*HJ&Y)m+-3s z#_d^f{KRw2Z8O9}t9zXMioCR;c_ahhENM0M_)*zJ2%^k2b<04a1j}RwiRG_konI+} z=&!1xyEWBe)E+3KQ3WnhLm{rD!mNH8rr^LrD28OzuJSdBXZ*^*mbegt-h>L;GnW&i zx(w?a4g{aTVbd=kV0MEaa&bph^b52iSCo|58nTnVu0S6}QBL1KX(YxVb+zt@^&dol z3IKOr8E8xaV1ePj5KwSY49|wX@-<^LFOULA*PM$*hO9u5Nr2+MR9eLDX6D;sG%*hg zdhmy=s#ey6Mmq=DEam|cWSqPXPwhDbE0y-+PDG?N^x+-ccw+UimjvyUpa2KNX}G-x zupGI)irV@1+BIRkVs-y=;z)nh$t_ZLx~E^>G<>rk21&boBv;48CYv{^t~L+eFI}c_ z;tNG%R34QqLvu+L&Fl^EWA;w<LA9hgF8^ulX0Ep!ik6Llz(o8q@DYo;_3zg}uUhtj zVT_(NMz8vhih-%K55;{OyZgAb#4K1BhTeJ|Sr<MH9q+$Z6_`I|sB>jBUkdrJ+hm?g zYfcmmk@9?OfAT@fH!-mKg~=%E1K!ux!?p((>)~qx_)k%`nXSQu;a(pwIUiIcfoSZs z+=hB)UR;N~QvwG079u$TqEV>zIEQ7;L5eZ!hGRf0&42a<y}iD%sP%IAncDI(11(p> z9<qCh0|cj9?^3YI)|m0&2d&^2Y$>)6!-nYCkOJ<t+ajD9a5g+|hP#|;*C*(bTZOpx zVMT34B4eZIngV@#13b-3f3FZslJS9*ho>v9vsE$kAj0|dIhLD4Zs;F}nS;)GX<b_Y z-M1MVIYH-=MwH)67GlObT^X(aZ!ZIflno8N=SUDKb1fy~fwcE4AI(=j!bH8l>h*z) zZrp0rciStpJD46~M|>eyn}NB_4T=-<qb^tRi!t;$TyZ#fUgU*_hq+&~Xr@k#78Su~ znSKm48k0(A@1SB$6f4VeWVZKXhf(G<l?x_<-fH?wwfAX8?lCN#k(i4s#(CP?5@rZ@ zh%*K3wq9pvQ<zlZBosl#ac6?SgTTiDhk#<<s5^_nG(H)dvAe_kgh!mt(_`R9aMs9k z{5h$s17Rz`P3x4P8BS)01UIwa*HI@Txsl<7XD)P`{yK&b7Kd)saR=sqa7VB!T=A+r zhR`mdWqn^Fu1lx7b7TqbMFQ9E#SncNcP20W9O(rR+3w11hIla+&eqUV<=MAKkGa!r zt6k}%ZrZ}<EZFpZU*^GazZaReJFYrJpF<JV8(AYuV>2d~SSIa)dxi0*{}g(aPxp4P z&(Ry7<RI;au*|t$U)C%{SK}QcsJ(n>G>UN3^4w`Z+zLn-g!<e20FI4Hbs%Skd8P-4 zRvf|}%Q8|bDL<Usqwg5p;rq_kpJuI!T!%7@^=DNb1DX0UJ}WoknNd^)XP^MPK43^T zQ~)F>^j95)$DZ^EzQ_YPZm2mW1EfBb*~Yk5+*K+$u>su2`|{71c6d1m{x>f}{1arI zIJc|GQ3k`(au8(Xa8>hlBM9_6SC$UdjykO&1&69Y2U>nAR_2%5Coc=5?=adO@_roo zJRoUY2sI{Avi$GoQjAc0o0;j9Q3%5}kR1~?)#LSg)=oOR54F3B9=F+n?USG2MZzf8 z%1OU<6fW@jHQnK4Z$my!GS#k#Srw2uV~VBc1~ju-IfHR!Yb(dr+YpDXmpD03r`i-T zCCJPfa%>rM^|;|__$)ixBUT%1te*#SQrV@Qp}5e43Ek^rN_n_5GlxdO#77nP`St7@ zoa@H^_i;0XB9LB6vZ1enA0v+rFrS$r;yH{;#ZL;qVbyOWQFMOUYEOS$s-Y-On^tMR z_q?WDnt(Z!buzbsopZ)4voW*Z7lrl?KMvCz&Qe6A#c4;}Y?Iv$bPm0)mgOHB*Bwm< z$+1XEw!b?AFS>wDIX>ehe$0_Kz7fzv+eV)1m+ZvP@bAKPwf&5xkOD)IHDS~uvoN{O zPej&$UU{`ZD;5p;z7;^E*33CU1WNn0w4ZbWKb63a&}A-Ddl@<RnSjGCPBLs;AIBts z$6(|;v$tRir$(bikG@=Zi^YI_3pZkis|W{DF?sYI)?JR;c}tT@CfpqW+h#0s9kO95 z&(?+gL59tWtogh=#7KXa&o)1nc5*%;P03e2h0fs4hy@Wq%9dQ(RW=xEfamvs`#Xoe zUNT*SZQH=>WUE%HWBKtW5C|Lbg5a(o_o32&Q8CJV{HGaPoyRzbN6iH>e@aF02>Z!w zhr|ir0!dtwI(b=4Lr-(XMN}(I2(oC}isQ-|_X<L8_KrpDr<`f!EijYl6@l8xn&hD< z7-fMFvt^L7flQ)}sI>qT<vu0M{L!BE%ZjJB%$)r?6$-H$dRN@6t~@syO}PT;pSACY zh{oO)B;ALAygMOdiSLKTmOGZ?_^hD<`&+VE5CM<@8;1A*%5SB%kLnkMDbgAjTqnMy zpi|s9ishk#uixnzhH*db#tVDgfzq&EHIIs+i#tdvmfza^26OHMS7;WVz3mt7mFUw1 zZ3N0Nar-N*boOgE=L!bp{0x!&?UW-`8%YK+Oe~Q2LmlJp(~gwK2Y)&E$W6ft6c?CV zQP0~Z&AAPQAczct!@{0gfx+wv>l#HiHG|oG$06};Z&d_28k64+tVDrSKCaks2sij} zD-qFBPMWygR<HjA(s?1F>Y4SC^MU$$vl=5!OVvBNWfYTo($HCOqcU#+Snd~faF1lJ Th4~8jZ|`pOuKEuTfA&8C<tNvY literal 0 HcmV?d00001 From 5b180c7095b1d6911524650c115e7074db8aeccc Mon Sep 17 00:00:00 2001 From: Nikolaengel <bratashick@gmail.com> Date: Thu, 5 Feb 2026 12:06:24 +0300 Subject: [PATCH 14/14] docs: selectors-fix --- .../current/basic-guides/selectors.mdx | 463 ++++++------------ .../current/quickstart/writing-tests.mdx | 2 + 2 files changed, 140 insertions(+), 325 deletions(-) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx index df05d980..3e2fe151 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx @@ -1,10 +1,10 @@ # Селекторы -Testplane предоставляет множество способов для поиска элементов на странице браузера с помощью селекторов. Для этого используются библиотеки `WebDriverIO` и `testing-library`. Селекторы позволяют точно идентифицировать элементы интерфейса, что необходимо для автоматизации тестирования. +Testplane предоставляет множество способов для поиска элементов на странице браузера. ## WebDriverIO -WebDriverIO — это Node.js-библиотека для автоматизации браузеров, которая реализует протокол WebDriver (W3C стандарт) В Testplane она используется для управления браузерами и взаимодействия с веб-элементами. +В Testplane поддерживается поиск элементов, совместимый с синтаксисом WebdriverIO: по CSS селекторам, по XPath, по тексту элементов и по другим признакам, которые описаны ниже. ### CSS селекторы @@ -13,13 +13,18 @@ WebDriverIO — это Node.js-библиотека для автоматиза Чтобы найти элемент на странице по классу, используйте селектор `".class-name"`. ```javascript -// Поиск кнопки с классом "btn-primary" -const button = await browser.$(".btn-primary"); -await button.click(); - -// Поиск нескольких элементов -const menuItems = await browser.$$(".nav-item"); -console.log("Количество пунктов меню: ${menuItems.length}"); +describe("CSS-селектор по классу", () => { + it("Поиск элемента на главной странице", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Ищем элемент по классу "navbar" + const navbar = await browser.$(".navbar"); + + // Проверяем, отображается ли элемент на странице + const isDisplayed = await navbar.isDisplayed(); + console.log("Навбар отображается:", isDisplayed); + }); +}); ``` Стоит использовать, если: @@ -34,14 +39,23 @@ console.log("Количество пунктов меню: ${menuItems.length}") Чтобы найти элемент по `id`, используйте селектор вида `"#id"`. ```javascript -// Поиск формы id -const loginForm = await browser.$("#login-form"); -const isDisplayed = await loginForm.isDisplayed(); +describe("CSS-селектор по id", () => { + it("Поиск элемента по id на главной странице", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Ищем элемент по id "__docusaurus" + const main = await browser.$("#__docusaurus"); + + // Проверяем, отображается ли элемент на странице + const isDisplayed = await main.isDisplayed(); + console.log("Элемент отображается:", isDisplayed); + }); +}); ``` Стоит использовать, если: -- вы работаете с критически важными элементами (формы, модальные окна, главные контейнеры); +- вам необходим быстрый и простой способ поиска элементов; - `id` является частью публичного API компонента; - нужна максимальная производительность селектора (`id` — самый быстрый селектор). @@ -50,117 +64,50 @@ const isDisplayed = await loginForm.isDisplayed(); Для поиска элемента по атрибуту, используйте селектор вида `input[type="name"]`. ```javascript -// Поиск всех чекбоксов -const checkboxes = await browser.$$("input[type="checkbox"]"); -for (const checkbox of checkboxes) { - await checkbox.click(); -} - -// Поиск email input -const emailInput = await browser.$("input[type="email"]"); -await emailInput.setValue("test@example.com"); - -// Поиск скрытых полей -const hiddenField = await browser.$("input[type="hidden"][name="csrf_token"]"); -const csrfValue = await hiddenField.getValue(); +describe("CSS-селектор по типу атрибута", () => { + it("Поиск элемента по типу атрибута", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Ищем кнопку по атрибуту type="button" + // Формат селектора: element[type="value"] + const button = await browser.$('button[type="button"]'); + + // Проверяем существование элемента в DOM + const isExisting = await button.isExisting(); + console.log("Кнопка существует:", isExisting); + }); +}); ``` Стоит использовать, если: - нужно найти все элементы определённого типа (все чекбоксы, все радиокнопки); -- вы тестируете формы и валидацию; -- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`); -- вам нужно убедиться, что используется правильный тип поля для `accessibility`; -- вы тестируете различное поведение для разных типов полей. +- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`). #### CSS-селекторы по атрибуту data-testid -Для поиска элементов, которые помечены дял тестирования, используйте селекторы по атрибуту `data-testid`. +Для поиска элементов, которые помечены для тестирования, используйте селекторы по атрибуту `data-testid`. ```javascript -// Поиск элемента по data-testid -const userAvatar = await browser.$("[data-testid="user-avatar"]"); -await userAvatar.waitForDisplayed({ timeout: 5000 }); - -// Клик по кнопке с data-testid -const deleteButton = await browser.$("[data-testid="delete-user-btn"]"); -await deleteButton.click(); - -// Получение текста из элемента -const errorMessage = await browser.$("[data-testid="error-notification"]"); -const text = await errorMessage.getText(); -expect(text).toContain("Ошибка валидации"); +describe("CSS-селектор по атрибуту data-testid", () => { + it("Поиск элемента по data-testid", async ({ browser }) => { + // Открываем страницу и ждем её загрузки + await browser.openAndWait("https://testplane.io/"); + + // Ищем элемент по атрибуту data-testid + const element = await browser.$('[data-testid="main-content"]'); + + // Проверяем существование элемента в DOM + const isExisting = await element.isExisting(); + console.log("Элемент с data-testid существует:", isExisting); + }); +}); ``` Стоит использовать, если: - создаёте селекторы специально для тестирования; -- нужна стабильность селекторов независимо от изменений UI/стилей; -- работаете в команде, где дизайнеры часто меняют классы и структуру; -- хотите явно пометить элементы, доступные для тестирования; - -#### CSS-комбинированные селекторы - -Комбинированные селекторы позволяют находить элементы в определённом контексте. - -```javascript -// Потомок: кнопка внутри модального окна -const modalButton = await browser.$(".modal .close-button"); -await modalButton.click(); - -// Прямой потомок: первый уровень вложенности -const navItem = await browser.$(".navigation > .nav-item"); - -// Соседний элемент -const errorLabel = await browser.$("input.invalid + .error-message"); -const errorText = await errorLabel.getText(); - -// Сложная комбинация -const activeTab = await browser.$("ul.tabs > li.active > a[href^="#"]"); -``` - -Стоит использовать, если: - -- нужно найти элемент в определённом контексте; -- работаете с повторяющейся структурой; -- нужно убедиться в правильной вложенности элементов; -- простые селекторы слишком неспецифичны; -- тестируете связанные элементы. - -#### CSS-псевдоселекторы - -Псевдоселекторы позволяют выбирать элементы на основе их положения или состояния. - -```javascript -// Первый элемент списка -const firstItem = await browser.$("ul.menu > li:first-child"); -await firstItem.click(); - -// Последний элемент -const lastItem = await browser.$(".breadcrumb > li:last-child"); -const currentPage = await lastItem.getText(); - -// N-ый элемент (третий пункт меню) -const thirdItem = await browser.$(".menu-item:nth-child(3)"); - -// Каждый второй элемент (чётные) -const evenRows = await browser.$$("table tr:nth-child(even)"); - -// Отключённые элементы -const disabledButtons = await browser.$$("button:disabled"); -expect(disabledButtons.length).toBe(2); - -// Проверенные чекбоксы -const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); -``` - -Стоит использовать, если: - -- тестируете позиционирование элементов; -- проверяете состояния элементов (`disabled`, `checked`, `focus`); -- работаете с таблицами и нужно выбрать определённую строку или столбец; -- тестируете паттерны (чётные/нечётные строки для `zebra-striping`); -- нужно найти элемент по его позиции, когда нет других идентификаторов. +- нужна стабильность селекторов независимо от изменений UI/стилей. ### XPath селекторы @@ -170,57 +117,42 @@ const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); ```javascript // Точное совпадение текста -const loginButton = await browser.$("//button[text()="Войти в систему"]"); -await loginButton.click(); - -// Частичное совпадение -const successMessage = await browser.$("//div[contains(text(), "успешно")]"); -await successMessage.waitForDisplayed(); - -// Текст с пробелами и переносами -const heading = await browser.$("//h1[normalize-space()="Добро пожаловать"]"); - -// Поиск по тексту в дочернем элементе -const card = await browser.$("//div[@class="card"][.//h3[text()="Premium план"]]"); - -// Case-insensitive поиск (XPath 2.0) -const link = await browser.$("//a[contains(translate(text(), "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"), "поиск")]"); +describe("XPath-селектор по тексту элемента", () => { + it("Поиск элемента по тексту", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Ищем элемент по тексту внутри него + const link = await browser.$('//a[text()="Docs"]'); + + // Проверяем существование элемента в DOM + const isExisting = await link.isExisting(); + console.log("Элемент с текстом существует:", isExisting); + }); +}); ``` Стоит использовать, если: - текст элемента уникален и стабилен (названия кнопок, заголовки); -- нет других идентификаторов (нет `data-testid`, `id`, классов); -- тестируете, что правильный текст отображается в правильном месте; -- работаете с динамически генерируемым контентом, где единственный стабильный элемент — текст; -- нужно найти элемент, содержащий определённый текст внутри себя или в дочерних элементах. +- другие стратегии поиска элементов оказались неприменимы. #### По атрибутам Для поиска элемента по атрибуту, используйте селектор вида `//element[@type="atribute"]`. ```javascript -// Поиск по одному атрибуту -const nameInput = await browser.$("//input[@name="username"]"); -await nameInput.setValue("john_doe"); - -// Множественные условия (AND) -const activeModal = await browser.$("//div[@class="modal" and @data-visible="true"]"); - -// Условие OR -const submitBtn = await browser.$("//button[@type="submit" or @class="btn-submit"]"); - -// Поиск по началу значения атрибута -const imageJpg = await browser.$("//img[starts-with(@src, "/images/")]"); - -// Поиск по концу значения атрибута -const pdfLink = await browser.$("//a[substring(@href, string-length(@href) - 3) = ".pdf"]"); - -// Поиск по частичному совпадению атрибута -const dataElement = await browser.$("//div[contains(@data-component, "user-profile")]"); - -// NOT условие -const notDisabledButton = await browser.$("//button[not(@disabled)]"); +describe("XPath-селектор по атрибуту", () => { + it("Поиск элемента по атрибуту", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Ищем элемент по атрибуту type + const button = await browser.$('//button[@type="button"]'); + + // Проверяем существование элемента в DOM + const isExisting = await button.isExisting(); + console.log("Элемент с атрибутом существует:", isExisting); + }); +}); ``` Стоит использовать, если: @@ -258,37 +190,33 @@ const directChildren = await browser.$$("//ul[@class="menu"]/li"); const siblingSection = await browser.$("//h2[text()="Контакты"]/../following-sibling::section[1]"); ``` -Стоит использовать, если: - -- нужно найти элемент относительно другого известного элемента; -- структура DOM сложна, но относительные позиции стабильны; -- тестируете связанные элементы (label и input, ошибка рядом с полем); -- нужно подняться вверх по DOM-дереву от найденного элемента; -- работаете с семантической структурой HTML (заголовок и следующая за ним секция). +Подобный вид навигации по дереву не рекомендуется использовать из-за своей хрупкости, но он возможен. #### XPath: индексы и позиции XPath позволяет выбирать элементы по их позиции в наборе результатов. ```javascript -// Первый элемент в результатах XPath -const firstButton = await browser.$("(//button[@class="action"])[1]"); -await firstButton.click(); - -// Последний элемент -const lastItem = await browser.$("(//li[@class="menu-item"])[last()]"); - -// Второй элемент -const secondRow = await browser.$("(//table[@id="results"]//tr)[2]"); - -// Предпоследний -const secondToLast = await browser.$("(//div[@class="card"])[last()-1]"); - -// Диапазон элементов (все кроме первого) -const allButFirst = await browser.$$("(//li[@class="item"])[position() > 1]"); - -// Каждый третий элемент -const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 = 0]"); +describe("XPath-селектор: индексы и позиции", () => { + it("Поиск элемента по индексу", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Ищем третий элемент ссылки в навигации (индекс начинается с 1) + // Формат селектора: (//element)[index] + const thirdLink = await browser.$('(//a)[3]'); + + // Ждем появления элемента и его отображения + await thirdLink.waitForDisplayed({ timeout: 5000 }); + + // Проверяем существование элемента в DOM + const isExisting = await thirdLink.isExisting(); + console.log("Третий элемент существует:", isExisting); + + // Получаем текст элемента + const text = await thirdLink.getText(); + console.log("Текст третьего элемента:", text); + }); +}); ``` Стоит использовать, если: @@ -304,103 +232,38 @@ const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 Селекторы по содержащемуся внутри тексту позволяют находить ссылки `(<a>)` по их тексту. Используйте `="text"`, чтобы найти элемент с точным текстом и `*="text"` для поиска по частичному совпадению текста. ```javascript -// Полное совпадение текста ссылки -const loginLink = await browser.$("=Войти в систему"); -await loginLink.click(); - -// Частичное совпадение -const docsLink = await browser.$("*=Документация"); -await docsLink.click(); -``` - -Стоит использовать, если: - -- работаете с навигационными ссылками с уникальным текстом; -- тестируете меню и навигацию сайта; -- текст ссылки является частью требований и не должен меняться; -- нужна простота и читаемость теста; -- тестируете наличие ссылок с правильным текстом на странице. - -### Селекторы по имени тега - -Чтобы найти элемент по их HTML-тегу, используйте селектор по имени тега. - -```javascript -// Поиск первой кнопки на странице -const button = await browser.$("button"); -await button.click(); - -// Все параграфы -const paragraphs = await browser.$$("p"); -const textsArray = await Promise.all(paragraphs.map(p => p.getText())); - -// Все изображения -const images = await browser.$$("img"); -for (const img of images) { - const alt = await img.getAttribute("alt"); - expect(alt).not.toBe(""); // проверка accessibility -} - -// Форма -const form = await browser.$("form"); -await form.waitForDisplayed(); - -// Таблица -const table = await browser.$("table"); -const rows = await table.$$("tr"); -``` - -Стоит использовать, если: - -- на странице один элемент данного типа (например, единственная форма); -- нужно получить все элементы определённого типа для массовой проверки; -- тестируете семантичность HTML (наличие правильных тегов); -- работаете с базовой HTML-структурой (`form`, `table`, `ul`); -- проводите accessibility-аудит (проверка всех `img` на наличие `alt`). - -### React селекторы - -Для поиска экомпонентов в React-приложении по их имени, используйте react-селекторы, например `react$("MyButton")` найдет компонент `MyButton`, а `react$("Button" { props: { variant: "primary", size: "large"}})` найдет кнопку с определенными параметрами. - -```javascript -// Поиск React-компонента по имени -const myButton = await browser.react$("MyButton"); -await myButton.click(); - -// С фильтрацией по параметрами -const primaryButton = await browser.react$("Button", { - props: { variant: "primary", size: "large" }, -}); - -// С фильтрацией по state -const openModal = await browser.react$("Modal", { - state: { isOpen: true, activeTab: "settings" }, -}); - -// Все экземпляры компонента -const allCards = await browser.react$$("ProductCard"); -console.log("Найдено карточек: ${allCards.length}"); - -// Вложенный поиск -const form = await browser.react$("CheckoutForm"); -const submitButton = await form.react$("SubmitButton"); - -// С комплексными параметрами -const userProfile = await browser.react$("UserProfile", { - props: { - user: { id: 123, role: "admin" }, - editable: true, - }, +describe('Селектор Link Text', () => { + it('Поиск элемента по совпадающему тексту', async ({ browser }) => { + await browser.url('https://testplane.io/ru/'); + + // Полное совпадение текста ссылки + const docsLink = await browser.$('=Документация'); + const isDocsLinkFound = await docsLink.isExisting(); + console.log(`Элемент с полным текстом "Документация" найден: ${isDocsLinkFound}`); + + // Частичное совпадение текста ссылки + const partialLink = await browser.$('*=Докум'); + const isPartialLinkFound = await partialLink.isExisting(); + console.log(`Элемент с частичным текстом "Докум" найден: ${isPartialLinkFound}`); + + // Частичное совпадение с указанием тега <a> + const tagPartialLink = await browser.$('a*=Документ'); + const isTagPartialLinkFound = await tagPartialLink.isExisting(); + console.log(`Элемент <a> с частичным текстом "Документ" найден: ${isTagPartialLinkFound}`); + + // Case-insensitive поиск с тегом div + const divCaseInsensitive = await browser.$('div.=testplane'); + const isDivCaseInsensitiveFound = await divCaseInsensitive.isExisting(); + console.log(`Элемент <div> с case-insensitive текстом "testplane" найден: ${isDivCaseInsensitiveFound}`); + }); }); ``` Стоит использовать, если: -- работаете с React-приложением и имеете доступ к исходному коду; -- нужно найти компонент по его параметрам или `state`; -- тестируете, что компонент рендерится с правильными данными; -- структура `DOM` может меняться, но API компонента стабилен; -- нужна глубокая интеграция с React DevTools. +- текст элементов стабилен; +- вам необходимо, чтобы тест был максимально приближен к реальным пользовательским сценариям. + ### Shadow DOM селекторы @@ -426,37 +289,24 @@ const slotElements = await customElement.shadow$$(".slot-item"); ## Testing-library -Testing Library — это адаптер популярной философии Testing Library для Testplane. Она предоставляет селекторы, ориентированные на пользовательский опыт (как пользователи находят элементы). +Testing Library позволяет искать элементы так, как их ищут на странице пользователи — по тексту, типу элемента или другим атрибутам, которые не зависят от деталей вашей верстки. ### ByRole `getByRole` — основной метод в Testing Library, который позволяет находить элементы по их ARIA-ролям. Например, если вы используете метод `screen.getByRole("button", { name: /submit/i })`, то найдете кнопку с текстом, содержащим `submit`. ```javascript -import { screen } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; - -// Поиск кнопки -const submitButton = screen.getByRole("button", { name: /submit/i }); -await userEvent.click(submitButton); +describe("getByRole", () => { + it("Поиск кнопки с помощью метода getByRole", async ({ browser }) => { + await browser.url("https://testplane.io/"); -// Поиск текстового поля -const emailInput = screen.getByRole("textbox", { name: /email/i }); -await userEvent.type(emailInput, "test@example.com"); + const button = await browser.getByRole("button", { name: "Get started" }); -// Поиск чекбокса -const agreeCheckbox = screen.getByRole("checkbox", { name: /agree to terms/i }); -await userEvent.click(agreeCheckbox); + await button.click(); + }); +}); ``` -Стоит использовать, если: - -- для любых интерактивных элементов (кнопки, ссылки, поля ввода); -- для структурных элементов (`navigation`, `main`, `header`, `footer`); -- для форм и их элементов (`radio`, `checkbox`, `combobox`); -- для заголовков и важных текстовых элементов; -- для списков и таблиц. - ### ByLabelText Для поиска элементов форм по тексту их меток (`label`), используйте метод `getByLabelText`. @@ -474,13 +324,6 @@ const passwordInput = screen.getByLabelText(/password/i); await userEvent.type(passwordInput, "secure123"); ``` -Стоит использовать, если: - -- работаете с формами; -- нужно найти input, select, textarea по связанной метке; -- занимаетесь тестированием доступности (наличие `label` обязательно для доступности); -- метка уникальна и описательна. - ### ByPlaceholderText Чтобы найти поле ввода по тексту `placeholder`, используйте селектор `getByPlaceholderText`. @@ -498,12 +341,6 @@ const emailInput = screen.getByPlaceholderText(/enter.*email/i); await userEvent.type(emailInput, "test@example.com"); ``` -Стоит использовать, если: - -- у поля нет `label`, но есть `placeholder`; -- занимаетесь тестированием legacy-кода, где `placeholder` используется вместо `label`; -- `placeholder` достаточно описателен и уникален. - ### ByText Чтобы найти текстовый элемент по его содержимому, используйте метод `getByText`. @@ -521,12 +358,6 @@ const errorMessage = screen.getByText(/error.*occurred/i); expect(errorMessage).toHaveClass("error"); ``` -Стоит использовать, если: - -- необходимо найти неинтерактивные текстовые элементов (параграфы, заголовки, уведомления); -- занимаетесь проверкой отображения текста на странице; -- текст уникален и является частью требований. - ### ByDisplayValue Для поиска элемента по их текущему значению, используйте метод `getByDisplayValue`. @@ -543,12 +374,6 @@ expect(emailInput).toBeInTheDocument(); const searchInput = screen.getByDisplayValue(/search query/i); ``` -Стоит использовать, если: - -- необходимо протестировать предзаполненных форм (edit forms, profile pages); -- нужно проверить установку корректного значения после действия; -- необходимо найти элемент по его текущему значению, а не по label. - ### ByAltText Для поиска изображений по тексту `alt`, используйте метод `getByAltText`. @@ -563,15 +388,9 @@ expect(logo).toBeInTheDocument(); expect(logo).toHaveAttribute("src", "/images/logo.png"); ``` -Стоит использовать, если: - -- нужно работать с изображениями (`<img>`, `<area>`, `<input type="image">`); -- необходимо проверить доступность изображений (наличие `alt` обязательно); -- `alt`-текст достаточно описателен и уникален. - ### ByTitle -Чтобы найти элемент по атрибуту title, используйте метод getByTitle. +Чтобы найти элемент по атрибуту title, используйте метод `getByTitle`. ```javascript import { screen } from "@testing-library/dom"; @@ -582,12 +401,6 @@ const closeButton = screen.getByTitle("Close dialog"); await userEvent.click(closeButton); ``` -Стоит использовать, если: - -- необходимо работать с элементами, которые используют `title` для `tooltips`; -- нужно протестировать `iframe`-элементы (`title` обязателен для доступности); -- нужно взаимодействовать с `SVG`-элементами (`title` внутри `SVG` для описания). - ### ByTestId `getByTestId` используется как последний вариант, когда другие методы не подходят. Например, если вы используете `screen.getByTestId("submit-button")`, то найдте элемент с атрибутом `data-testid="submit-button"` diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx index 8446aa62..b4f5dd7b 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx @@ -28,6 +28,8 @@ it("описание того, что должно произойти", async ({ После установки testplane, вы можете ознакомиться с примером теста, для этого перейдите в папку `testplane-tests` и откройте файл `example.testplane.ts`. ```javascript +const assert = require("assert"); + describe("test examples", () => { it("docs search test", async ({ browser }) => { await browser.openAndWait("https://testplane.io/");