From a0dda98b830039067edaad0e1a70c3ad003250d3 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 15 Sep 2026 22:27:51 +0200 Subject: [PATCH] i18n: build-time ru/en localization + EN homepage & cookie-policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build-pages.mjs: per-locale page/partial overrides (*.en.html), i18n token resolution with ru fallback, + assetPrefix per locale - UI dictionaries src/i18n/{ru,en}.json; lang-switch partial - headers/footer/partials converted to {{i18n:…}} tokens - root-absolute static asset refs so /en/ subpath loads css/js/vendor - EN content: homepage (index.en.html + partials) and cookie-policy.en.html (cookie-policy body translated via apinex.bond /v1/tools/translator) --- public/_lab/bake.html | 81 + public/_lab/index.html | 108 ++ public/_lab/lab.css | 404 +++++ public/_lab/lab.js | 59 + public/_lab/live-reload.js | 20 + public/_lab/neu-card.html | 662 ++++++++ public/_lab/noise-test.html | 3 + public/_lab/paper-bundle.js | 961 ++++++++++++ public/_lab/render.html | 56 + public/_lab/shader-demo.js | 83 + public/_lab/template.html | 74 + public/assets/lab/grain-l.webp | Bin 0 -> 42476 bytes public/assets/lab/grain-m.webp | Bin 0 -> 32060 bytes public/assets/lab/grain-s.webp | Bin 0 -> 20998 bytes public/assets/lab/grain-xl.webp | Bin 0 -> 29274 bytes public/assets/lab/grain-xs.webp | Bin 0 -> 13112 bytes public/cases/ai-assistant-figma/index.html | 53 +- .../cases/ai-platform-primo-rpa-2/index.html | 53 +- .../cases/ai-platform-primo-rpa-v2/index.html | 53 +- public/cases/ai-platform-primo-rpa/index.html | 53 +- public/cases/card-columns-obsidian/index.html | 53 +- public/cases/design-system/index.html | 51 +- public/cases/lunacy/index.html | 51 +- public/cases/manu-game-maker/index.html | 51 +- .../cases/mini-projects-showcase/index.html | 51 +- public/cases/mobile-clinic-app/index.html | 51 +- .../obsidian-plugin-columns-view/index.html | 53 +- public/cases/orc-automation-panel/index.html | 51 +- public/cases/site-with-ai-ide/index.html | 53 +- .../task-distribution-concept/index.html | 51 +- public/cases/task-tracker/index.html | 53 +- .../cases/template-elements-demo/index.html | 53 +- public/cookie-policy/index.html | 51 +- public/css/main.css | 46 + public/en/cases/ai-assistant-figma/index.html | 423 +++++ .../cases/ai-platform-primo-rpa-2/index.html | 1062 +++++++++++++ .../cases/ai-platform-primo-rpa-v2/index.html | 1191 ++++++++++++++ .../en/cases/ai-platform-primo-rpa/index.html | 1233 +++++++++++++++ .../en/cases/card-columns-obsidian/index.html | 815 ++++++++++ public/en/cases/design-system/index.html | 1121 ++++++++++++++ public/en/cases/lunacy/index.html | 1021 ++++++++++++ public/en/cases/manu-game-maker/index.html | 763 +++++++++ .../cases/mini-projects-showcase/index.html | 1277 +++++++++++++++ public/en/cases/mobile-clinic-app/index.html | 763 +++++++++ .../obsidian-plugin-columns-view/index.html | 790 ++++++++++ .../en/cases/orc-automation-panel/index.html | 901 +++++++++++ public/en/cases/site-with-ai-ide/index.html | 423 +++++ .../task-distribution-concept/index.html | 763 +++++++++ public/en/cases/task-tracker/index.html | 1194 ++++++++++++++ .../cases/template-elements-demo/index.html | 1152 ++++++++++++++ public/en/cookie-policy/index.html | 365 +++++ public/en/index.html | 1375 +++++++++++++++++ public/en/resume/index.html | 1020 ++++++++++++ public/index.html | 49 +- public/resume/index.html | 51 +- scripts/build-pages.mjs | 140 +- src/i18n/en.json | 19 + src/i18n/ru.json | 19 + src/pages/cases/card-columns-obsidian.html | 436 +++++- src/pages/cookie-policy.en.html | 139 ++ src/pages/index.en.html | 564 +++++++ src/pages/index.html | 6 +- src/partials/contact-section.en.html | 11 + src/partials/cookie-notice.en.html | 12 + src/partials/fab-stack.en.html | 62 + src/partials/footer.html | 6 +- src/partials/head-links.html | 18 +- src/partials/head-plyr.html | 2 +- src/partials/head-scripts.html | 4 +- src/partials/header-case.html | 9 +- src/partials/header-home.html | 19 +- src/partials/header-minimal.html | 9 +- src/partials/lang-switch.html | 18 + src/partials/mini-projects-section.en.html | 158 ++ src/partials/mini-projects-section.html | 6 +- src/partials/page-scripts.html | 4 +- src/partials/projects-section-mp.en.html | 167 ++ src/partials/projects-section.en.html | 235 +++ src/partials/theme-init-script.html | 6 +- 79 files changed, 22841 insertions(+), 422 deletions(-) create mode 100644 public/_lab/bake.html create mode 100644 public/_lab/index.html create mode 100644 public/_lab/lab.css create mode 100644 public/_lab/lab.js create mode 100644 public/_lab/live-reload.js create mode 100644 public/_lab/neu-card.html create mode 100644 public/_lab/noise-test.html create mode 100644 public/_lab/paper-bundle.js create mode 100644 public/_lab/render.html create mode 100644 public/_lab/shader-demo.js create mode 100644 public/_lab/template.html create mode 100644 public/assets/lab/grain-l.webp create mode 100644 public/assets/lab/grain-m.webp create mode 100644 public/assets/lab/grain-s.webp create mode 100644 public/assets/lab/grain-xl.webp create mode 100644 public/assets/lab/grain-xs.webp create mode 100644 public/en/cases/ai-assistant-figma/index.html create mode 100644 public/en/cases/ai-platform-primo-rpa-2/index.html create mode 100644 public/en/cases/ai-platform-primo-rpa-v2/index.html create mode 100644 public/en/cases/ai-platform-primo-rpa/index.html create mode 100644 public/en/cases/card-columns-obsidian/index.html create mode 100644 public/en/cases/design-system/index.html create mode 100644 public/en/cases/lunacy/index.html create mode 100644 public/en/cases/manu-game-maker/index.html create mode 100644 public/en/cases/mini-projects-showcase/index.html create mode 100644 public/en/cases/mobile-clinic-app/index.html create mode 100644 public/en/cases/obsidian-plugin-columns-view/index.html create mode 100644 public/en/cases/orc-automation-panel/index.html create mode 100644 public/en/cases/site-with-ai-ide/index.html create mode 100644 public/en/cases/task-distribution-concept/index.html create mode 100644 public/en/cases/task-tracker/index.html create mode 100644 public/en/cases/template-elements-demo/index.html create mode 100644 public/en/cookie-policy/index.html create mode 100644 public/en/index.html create mode 100644 public/en/resume/index.html create mode 100644 src/i18n/en.json create mode 100644 src/i18n/ru.json create mode 100644 src/pages/cookie-policy.en.html create mode 100644 src/pages/index.en.html create mode 100644 src/partials/contact-section.en.html create mode 100644 src/partials/cookie-notice.en.html create mode 100644 src/partials/fab-stack.en.html create mode 100644 src/partials/lang-switch.html create mode 100644 src/partials/mini-projects-section.en.html create mode 100644 src/partials/projects-section-mp.en.html create mode 100644 src/partials/projects-section.en.html diff --git a/public/_lab/bake.html b/public/_lab/bake.html new file mode 100644 index 0000000..73d52ed --- /dev/null +++ b/public/_lab/bake.html @@ -0,0 +1,81 @@ + + + bake paper texture + +
+ + + diff --git a/public/_lab/index.html b/public/_lab/index.html new file mode 100644 index 0000000..e949b55 --- /dev/null +++ b/public/_lab/index.html @@ -0,0 +1,108 @@ + + + + + + + Песочница _lab + + + + + + +
+

Песочница _lab

+

+ Стенды для настройки видa элементов без сборки сайта. Правишь + public/css/main.css или файл стенда — страница перезагружается сама (live-reload + сервера npm run serve). Когда вид готов, переносишь компонент в + src/pages/** и собираешь npm run build:pages. +

+ + + +
+ Как добавить стенд: скопируй /_lab/template.html в + /_lab/<имя>.html, поправь заголовок и секции. Стили компонента — в + public/css/main.css, обвязка стенда — в /_lab/lab.css. + Переключатель темы подключается автоматически через lab.js. +
+
+ + diff --git a/public/_lab/lab.css b/public/_lab/lab.css new file mode 100644 index 0000000..9a208e4 --- /dev/null +++ b/public/_lab/lab.css @@ -0,0 +1,404 @@ +/* Обвязка стенда. Сюда не пишем стили компонента — только рамки, сетки, + подписи и подложки. Стили neu-card живут в public/css/main.css. */ + +.lab { + max-width: 62rem; + margin: 0 auto; + padding: clamp(2rem, 5vw, 3.5rem) var(--space) 8rem; +} + +.lab__head { + margin-bottom: clamp(2.5rem, 6vw, 4rem); +} + +.lab__h1 { + margin: 0 0 0.5rem; + font-family: var(--font-accent); + font-size: clamp(1.8rem, 4vw, 2.5rem); + font-weight: 700; + line-height: 1.1; +} + +.lab__sub { + margin: 0 0 1.25rem; + max-width: 42rem; + color: var(--text-muted); + line-height: 1.6; +} + +.lab__nav { + display: flex; + flex-wrap: wrap; + gap: 0.4rem 0.75rem; + font-size: 0.85rem; +} + +.lab__nav a { + color: var(--accent-text); + text-decoration: none; + border-bottom: 1px solid transparent; +} + +.lab__nav a:hover { + border-bottom-color: currentColor; +} + +.lab__sec { + padding-top: clamp(2rem, 5vw, 3rem); + margin-bottom: clamp(2rem, 5vw, 3rem); + scroll-margin-top: 1.5rem; +} + +.lab__h2 { + margin: 0 0 0.5rem; + font-family: var(--font-accent); + font-size: clamp(1.25rem, 2.5vw, 1.6rem); + font-weight: 700; + line-height: 1.2; +} + +.lab__h3 { + margin: 2.5rem 0 0.5rem; + font-family: var(--font-accent); + font-size: 1rem; + font-weight: 700; + line-height: 1.2; + color: var(--text); +} + +.lab__note { + margin: 0 0 1.5rem; + max-width: 46rem; + font-size: 0.9rem; + line-height: 1.6; + color: var(--text-muted); +} + +.lab__caption { + margin: 0.75rem 0 0; + font-size: 0.78rem; + line-height: 1.4; + color: var(--text-muted); +} + +.lab code { + font-family: var(--font); + font-size: 0.92em; + padding: 0.1em 0.35em; + border-radius: var(--radius-sm); + background: color-mix(in srgb, var(--text) 8%, transparent); +} + +.lab__row { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + align-items: flex-start; +} + +.lab__row--wrap .neu-card { + flex: 0 0 auto; +} + +/* ── Токены ─────────────────────────────────────────────────────── */ + +.lab__tokens { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); + gap: 0.75rem; +} + +.lab__token { + margin: 0; + padding: 0.9rem 1rem; + border-radius: var(--radius-md); + background: var(--bg-elevated); + border: 1px solid var(--line); +} + +.lab__token dt { + font-size: 0.82rem; + margin-bottom: 0.3rem; +} + +.lab__token dt span { + color: var(--text-muted); + margin-left: 0.35rem; +} + +.lab__token dd { + margin: 0; + font-size: 0.82rem; + line-height: 1.5; + color: var(--text-muted); +} + +/* ── Фоны ───────────────────────────────────────────────────────── */ + +.lab__bg-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); + gap: 1.5rem; +} + +.lab__bg-cell { + padding: 1.75rem; + border-radius: var(--radius-md); + border: 1px solid var(--line); +} + +.lab__bg-cell[data-bg="page"] { + background: var(--bg); +} + +.lab__bg-cell[data-bg="elevated"] { + background: var(--bg-elevated); +} + +.lab__bg-cell[data-bg="surface"] { + background: var(--surface); +} + +.lab__bg-cell[data-bg="accent"] { + background: var(--accent); + color: var(--on-accent, var(--text)); +} + +.lab__bg-cell[data-bg="accent"] .lab__caption { + color: color-mix(in srgb, var(--on-accent, var(--text)) 78%, transparent); +} + +.lab__bg-cell[data-bg="media"] { + background-image: url("/assets/template-elements-demo/img-long.webp"); + background-size: cover; + background-position: center; + color: #fff; +} + +.lab__bg-cell[data-bg="media"] .lab__caption { + color: rgba(255, 255, 255, 0.85); + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6); +} + +.lab__bg-cell[data-bg="media"] .neu-card { + background: var(--surface); + color: var(--text); +} + +/* ── Состояния ──────────────────────────────────────────────────── */ + +.lab__state { + display: flex; + flex-direction: column; +} + +.lab__depth { + gap: 1.25rem; +} + +.lab__depth .neu-card { + min-width: 150px; +} + +/* ── Сетка ──────────────────────────────────────────────────────── */ + +.lab__grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); + gap: 1rem; + margin-bottom: 1.5rem; +} + +.lab__grid--3 { + grid-template-columns: repeat(3, 1fr); +} + +.lab__grid--tight { + gap: 0.5rem; +} + +@media (max-width: 640px) { + .lab__grid--3 { + grid-template-columns: 1fr; + } +} + +/* ── Переключатель стенда ───────────────────────────────────────── */ + +.lab-switch { + position: fixed; + right: max(1rem, env(safe-area-inset-right, 0px)); + bottom: max(1rem, env(safe-area-inset-bottom, 0px)); + z-index: 200; + display: flex; + align-items: center; + gap: 0.25rem; + padding: 0.35rem; + border-radius: 999px; + background: var(--surface); + border: 1px solid var(--line); + box-shadow: 0 8px 24px hsla(0, 0%, 0%, 0.25); + font-size: 0.78rem; +} + +.lab-switch__btn { + appearance: none; + border: 0; + padding: 0.35rem 0.7rem; + border-radius: 999px; + background: transparent; + color: var(--text-muted); + font: inherit; + font-family: var(--font-accent); + cursor: pointer; +} + +.lab-switch__btn:hover { + color: var(--text); + background: color-mix(in srgb, var(--text) 8%, transparent); +} + +.lab-switch__btn[aria-pressed="true"] { + background: var(--accent); + color: var(--on-accent, #fff); +} + +.lab-switch__sep { + width: 1px; + height: 1.25rem; + margin: 0 0.15rem; + background: var(--line); +} + +/* ── Карточка в карточке ────────────────────────────────────────── */ + +.lab__nest { + margin-bottom: 1.5rem; +} + +/* Внешняя карточка: увеличенные поля и минимум, чтобы вместить вложенные */ +.neu-card--nest { + --neu-pad: 1.75rem; + --neu-min-w: 0; + --neu-min-h: 0; + gap: 1rem; + justify-content: flex-start; +} + +.neu-card__nest-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} + +.neu-card__nest-row { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} + +@media (max-width: 640px) { + .neu-card__nest-grid { + grid-template-columns: 1fr; + } +} + +/* ── Шейдер-карточка (прототип Paper Shaders) ───────────────────── */ +/* canvas шейдера — фоновый слой ВНУТРИ карточки, под контентом. + pointer-events:none — чтобы клик/hover доходили до самой карточки. */ +.contact-card--shader { + overflow: hidden; + isolation: isolate; +} + +.card-shader { + position: absolute; + inset: 0; + z-index: 0; + border-radius: inherit; + overflow: hidden; + pointer-events: none; + /* нейтральный фон-запас: если WebGL не отрисовал, карточка выглядит как + обычная neu-card (--neu-bg). Когда шейдер рисует — он перекрывает этот фон. */ + background: var(--neu-bg); +} + +/* ── neu-card + живой шейдер-слой под бликом ─────────────────────────────── + Обычная неуморфная карточка (тени снаружи + блик) с анимированным шейдером + ВНУТРИ, под бликом. Слои (снизу вверх): + 0 — .card-shader (canvas шейдера) + 1 — ::before (блик --neu-glow) И ::after (внутренний объём inset-теней) + 2 — контент карточки + Важно: box-shadow родителя рисуется НИЖЕ дочерних слоёв, поэтому шейдер (z:0) + перекрыл бы внутренние тени. Чтобы объём был виден ПОВЕРХ шейдера, внешние + тени (drop) оставляем на карточке, а внутренний объём (inset) выносим на + ::after с z-index:1 — он рисуется поверх шейдера. */ +.neu-card--shader { + background: transparent; /* фон уходит, шейдер снизу */ + isolation: isolate; + /* только внешний объём (приподнятость) */ + box-shadow: var(--neu-drop-1), var(--neu-drop-2), var(--neu-drop-3); +} +.neu-card--shader::before { + content: ""; + position: absolute; + inset: 0; + z-index: 1; /* поверх шейдера (z:0) */ + border-radius: inherit; + pointer-events: none; + background: var(--neu-glow); /* блик из левого верхнего угла */ +} +.neu-card--shader::after { + content: ""; + position: absolute; + inset: 0; + z-index: 1; /* поверх шейдера, рядом с бликом */ + border-radius: inherit; + pointer-events: none; + /* внутренний объём (выпуклость): белая кайма сверху-слева + тёмная снизу-справа */ + box-shadow: + var(--neu-in-light-1), var(--neu-in-light-2), + var(--neu-in-dark-1), var(--neu-in-dark-2); +} +.neu-card--shader > *:not(.card-shader) { + position: relative; + z-index: 2; /* контент над бликом/объёмом и шейдером */ +} + +.contact-card--shader .contact-card__icon, +.contact-card--shader .contact-card__body, +.contact-card--shader .contact-card__corner { + position: relative; + z-index: 1; +} + +/* отдельная демо-карточка шейдера */ +.shader-demo-card { + position: relative; + overflow: hidden; + isolation: isolate; + width: 100%; + max-width: 360px; + min-height: 220px; + border-radius: var(--radius-card); + background: var(--neu-bg); + box-shadow: var(--neu-shadow); +} + +.shader-demo-card--wide { + max-width: 420px; + min-height: 260px; +} + +.shader-demo-card__label { + position: absolute; + left: 1rem; + bottom: 0.85rem; + z-index: 1; + font-family: var(--font-accent); + font-size: 0.95rem; + font-weight: 600; + color: #fff; + text-shadow: 0 1px 6px rgba(0, 0, 0, 0.55); + pointer-events: none; +} diff --git a/public/_lab/lab.js b/public/_lab/lab.js new file mode 100644 index 0000000..bc7eded --- /dev/null +++ b/public/_lab/lab.js @@ -0,0 +1,59 @@ +/** + * Стенд: переключение темы и скругления. + * Пишет в localStorage под тем же ключом, что и панель темы сайта, + * поэтому выбранный режим сохранится при переходе на сам сайт. + */ +import { STORAGE_KEY, readThemeState, writeThemeState } from "/js/modules/theme/theme-core.js"; +import { ensureThemeFontLoaded } from "/js/modules/theme/theme-fonts.js"; + +function apply(patch) { + const state = { ...readThemeState(), ...patch }; + writeThemeState(state); + + const root = document.documentElement; + + // Тема: только светлая/тёмная. Режим «system» на стенде не нужен — + // важно видеть обе темы принудительно. + if (state.mode === "system") state.mode = prefersDark() ? "dark" : "light"; + root.dataset.theme = state.mode; + + if (state.radius) root.dataset.radius = state.radius; + if (state.font) root.dataset.font = state.font; + + root.style.setProperty("--theme-h", String(state.h)); + root.style.setProperty("--theme-s", String(state.s)); + root.style.setProperty("--theme-l", String(state.l)); + + ensureThemeFontLoaded(state.font); + syncButtons(); +} + +function prefersDark() { + return window.matchMedia("(prefers-color-scheme: dark)").matches; +} + +function bind(attr, key) { + const btns = document.querySelectorAll(`[${attr}]`); + btns.forEach((btn) => { + btn.addEventListener("click", () => { + apply({ [key]: btn.getAttribute(attr) }); + }); + }); +} + +function syncButtons() { + const state = readThemeState(); + const theme = state.mode === "system" ? (prefersDark() ? "dark" : "light") : state.mode; + + document.querySelectorAll("[data-lab-theme]").forEach((btn) => { + btn.setAttribute("aria-pressed", btn.getAttribute("data-lab-theme") === theme ? "true" : "false"); + }); + + document.querySelectorAll("[data-lab-radius]").forEach((btn) => { + btn.setAttribute("aria-pressed", btn.getAttribute("data-lab-radius") === state.radius ? "true" : "false"); + }); +} + +bind("data-lab-theme", "mode"); +bind("data-lab-radius", "radius"); +syncButtons(); diff --git a/public/_lab/live-reload.js b/public/_lab/live-reload.js new file mode 100644 index 0000000..3973e1b --- /dev/null +++ b/public/_lab/live-reload.js @@ -0,0 +1,20 @@ +/** + * Live-reload-клиент для стендов public/_lab. + * Подключается к SSE /_lab/events сервера serve-dev.mjs и перезагружает + * страницу при правке любого css/js/mjs/html в public/. + * + * Вставка в стенд: + * Работает только при запущенном `npm run serve` (нет на проде). + */ +(function () { + if (!window.EventSource) return; + const es = new EventSource("/_lab/events"); + es.onmessage = (e) => { + if (e.data === "reload") { + es.close(); + window.location.reload(); + } + }; + // Если сервер не поднят — молча уходим, F5 работает как раньше. + es.onerror = () => es.close(); +})(); diff --git a/public/_lab/neu-card.html b/public/_lab/neu-card.html new file mode 100644 index 0000000..c070b52 --- /dev/null +++ b/public/_lab/neu-card.html @@ -0,0 +1,662 @@ + + + + + + + Стенд: neu-card + + + + + + +
+
+

neu-card

+

+ Стенд для настройки вида. Сборка не нужна: правим public/css/main.css → + страница перезагружается сама (live-reload). Переключатель темы в правом нижнем углу. +

+ +
+ + +
+

Финальный вид

+

+ Принятые значения: сила объёма 0.5, блик 0.2, без текстуры. + Обычная неуморфная карточка на нейтральном фоне. +

+
+
+ Проект + Neutral card + Нейтральный фон, выпуклая +
+
+ Accent + --accent +
+ +
+
+ + +
+

Обычная карточка · mp-card

+

+ Твоя базовая карточка с сайта — для сравнения с неуморфной рядом. Разметка и стили + взяты из public/css/main.css без изменений. +

+ +
+ + +
+

Токены

+

Числа, которыми управляется вид. Значение по умолчанию — в скобках.

+
+
+
--neu-bg --bg-elevated
+
+ Нейтральная заливка: в тёмной теме тёмная, в светлой — светлая. Отличается от + --bg на 3%, поэтому объём читается тенями, а не контрастом. +
+
+
+
--neu-spread 0.5
+
Сила объёма. Масштабирует все семь теней разом. 0 — плоская карточка.
+
+
+
--neu-hover-spread 1.5
+
Множитель объёма при наведении.
+
+
+
--neu-glow-a 0.2
+
Сила блика из левого верхнего угла. 0 — выключен.
+
+
+
--neu-in-light-mult 0.55 / 0.4
+
+ Сила внутреннего блика (тёмная / светлая тема). На светлом фоне белый блик «горит», + поэтому там он слабее. +
+
+
+
--neu-in-dark-mult 0.75 / 0.5
+
Сила внутренней тени (тёмная / светлая тема).
+
+
+
--neu-press 1px
+
Смещение вниз при нажатии.
+
+
+
--neu-press-fade 0.5
+
+ Во сколько слабеют внутренние блики при нажатии. Блики НЕ инвертируются — карточка + остаётся выпуклой. +
+
+
+
--neu-lift -3px
+
Подъём при наведении.
+
+
+
--neu-radius --radius-card
+
Скругление. По умолчанию следует настройке темы.
+
+
+
--neu-pad 1.25rem
+
Внутренние отступы.
+
+
+
+ + +
+

Фоны

+

+ Неуморфизм читается только в паре с подложкой. Карточка построена от --bg, + поэтому на каждом фоне ведёт себя по-разному. +

+
+
+
+
+ Neutral + --surface +
+
+ Accent + --accent +
+
+

Страница · --bg

+
+ +
+
+
+ Neutral + --surface +
+
+ Accent + --accent +
+
+

Поверхность · --bg-elevated

+
+ +
+
+
+ Neutral + --surface +
+
+ Accent + --accent +
+
+

Карточка на карточке · --surface — худший случай

+
+ +
+
+
+ Neutral + --surface +
+
+ Accent + --accent +
+
+

Акцентная подложка · --accent

+
+
+
+ + +
+

Состояния

+

+ Наведи курсор и нажми — остальное показано принудительно, чтобы видеть их одновременно. +

+
+
+
+ Rest + Покой +
+

rest

+
+ +
+
+ Hover + Наведи курсор +
+

:hover

+
+ +
+
+ Pressed + Вдавлена +
+

:active / .is-pressed

+
+ + + +
+
+ Disabled + Недоступна +
+

.is-disabled

+
+ +
+
+ Flat + Без объёма +
+

.neu-card--flat

+
+
+
+ + +
+

Содержимое

+

Проверка наполнения: от пустой до переполненной.

+
+
+ Только мета +
+ +
+ Только заголовок +
+ +
+ Eyebrow + Полный набор + Подпись под заголовком +
+ +
+ Длинный заголовок в несколько строк + + И подпись тоже длинная, чтобы проверить, как карточка растёт по высоте + +
+ +
+ + С иконкой + Иконка сверху +
+ +
+ + С обложкой + Картинка на всю ширину +
+ +
+ Акцентная + --accent +
+
+
+ + +
+

Размеры

+

+ Базовый размер задаётся --neu-min-w / --neu-min-h, дальше + карточка растёт от содержимого. В сетке — .neu-card--fluid. +

+
+
+ Компактная + 160 × 96 +
+ +
+ Базовая + 200 × 128 +
+ +
+ Крупная + 280 × 180 +
+ +
+ Широкие поля + --neu-pad: 2rem +
+ +
+ Сильное скругление + --neu-radius: 28px +
+
+
+ + +
+

Сетка

+

+ Рядом друг с другом тени соседних карточек накладываются. Здесь видно, не слипаются ли. +

+ +

Три в ряд, gap 1rem

+
+
+ Проект один + Продуктовый дизайн +
+
+ Проект два + Исследование +
+
+ Проект три + Дизайн-система +
+
+ +

Смешанный рост

+

Разная высота содержимого — проверка на растяжение по сетке.

+
+
+ Короткая +
+
+ Средняя по объёму + + Здесь подпись занимает две строки, и карточка должна потянуться выше соседней + +
+
+ С обложкой + Ещё выше +
+
+
+ + +
+

Карточка в карточке

+

+ Нижняя карточка больше и включает в себя верхние. Оценка того, как объём читается, когда + карточка лежит поверх той же самой поверхности, а не на фоне страницы. Это худший + случай для неуморфизма: обе поверхности одинаковые по цвету. +

+ +
+
+ Внешняя + Карточка-контейнер + Поверхность второго уровня, вложенные карточки внутри + +
+
+ Вложенная + Тот же --neu-bg +
+
+ Вложенная + Тот же --neu-bg +
+
+ Вложенная + Тот же --neu-bg +
+
+ +
+ + +
+
+
+ +

То же в светлой теме

+

+ Внутренние блики настроены раздельно для тёмной и светлой темы — здесь видна разница. +

+
+ + +
+

Светлая тема · тень

+

+ На светлом фоне чистый чёрный 0.25 выглядит грязно. Варианты: цвет тени смещён в + холодный тёмно-серый, альфа внешних теней снижена через --neu-drop-mult. + Эта секция принудительно светлая. +

+
+
+
+ Текущий + 47,53,75 · drop 0.5 +
+

как в main.css

+
+
+
+ Мягче + 70,75,95 · drop 0.4 +
+

светлее тень

+
+
+
+ Глубже + 30,35,55 · drop 0.35 +
+

темнее, но меньше альфа

+
+
+
+ Чёрный + 0,0,0 · drop 0.5 +
+

было грязно

+
+
+
+ + +
+

Контактные карточки

+

+ Оригинальная .contact-card с сайта, теперь с неуморфной основой + (объём как у .neu-card). Бренд-цвета через модификаторы + --brand-tg / --brand-vk / --brand-ya / --brand-email. + Цветное наложение — радиальный градиент ::before из --contact-brand. +

+ +
+ + +
+

Матрица · neu-card + шейдер под бликом

+

+ Обычная неуморфная карточка (тени снаружи, блик из угла) с живым шейдером + внутри, под бликом. Слева — чистая neu-card для сравнения, + справа — та же карточка с анимированным mesh-слоем. Объём и блик сохранены, + шейдер только подсвечивает поверхность. +

+ +

Сетка 3 × 2

+
+ +
+ Проект + Обычная + только тени и блик +
+
+ + Проект + Шейдер · demo + mesh под бликом +
+
+ + Проект + Шейдер · email + mesh под бликом +
+ + +
+ + Проект + Шейдер · tg + mesh под бликом +
+
+ + Проект + Шейдер · vk + mesh под бликом +
+
+ + Проект + Шейдер · ya + mesh под бликом +
+
+ +

Одиночная крупная

+
+
+ + Кейс + Neu + shader + объём снаружи, анимация внутри +
+
+ + Кейс + Static + shader + блик и анимация, без hover-отклика +
+
+
+ + +
+

Mesh gradient

+

+ Единственный используемый шейдер — анимированный mesh gradient (цветные пятна + плавно перетекают). Ниже — карточка-пример только с ним. Контактные и матричные + карточки выше используют тот же шейдер с брендовыми цветами. +

+ +

Пример · mesh gradient

+
+
+ + mesh gradient +
+
+
+
+ +
+ + + + + + +
+ + + + + + diff --git a/public/_lab/noise-test.html b/public/_lab/noise-test.html new file mode 100644 index 0000000..d44965c --- /dev/null +++ b/public/_lab/noise-test.html @@ -0,0 +1,3 @@ + +
+ \ No newline at end of file diff --git a/public/_lab/paper-bundle.js b/public/_lab/paper-bundle.js new file mode 100644 index 0000000..6d8c788 --- /dev/null +++ b/public/_lab/paper-bundle.js @@ -0,0 +1,961 @@ +// node_modules/@paper-design/shaders/dist/vertex-shader.js +var vertexShaderSource = `#version 300 es +precision mediump float; + +layout(location = 0) in vec4 a_position; + +uniform vec2 u_resolution; +uniform float u_pixelRatio; +uniform float u_imageAspectRatio; +uniform float u_originX; +uniform float u_originY; +uniform float u_worldWidth; +uniform float u_worldHeight; +uniform float u_fit; +uniform float u_scale; +uniform float u_rotation; +uniform float u_offsetX; +uniform float u_offsetY; + +out vec2 v_objectUV; +out vec2 v_objectBoxSize; +out vec2 v_responsiveUV; +out vec2 v_responsiveBoxGivenSize; +out vec2 v_patternUV; +out vec2 v_patternBoxSize; +out vec2 v_imageUV; + +vec3 getBoxSize(float boxRatio, vec2 givenBoxSize) { + vec2 box = vec2(0.); + // fit = none + box.x = boxRatio * min(givenBoxSize.x / boxRatio, givenBoxSize.y); + float noFitBoxWidth = box.x; + if (u_fit == 1.) { // fit = contain + box.x = boxRatio * min(u_resolution.x / boxRatio, u_resolution.y); + } else if (u_fit == 2.) { // fit = cover + box.x = boxRatio * max(u_resolution.x / boxRatio, u_resolution.y); + } + box.y = box.x / boxRatio; + return vec3(box, noFitBoxWidth); +} + +void main() { + gl_Position = a_position; + + vec2 uv = gl_Position.xy * .5; + vec2 boxOrigin = vec2(.5 - u_originX, u_originY - .5); + vec2 givenBoxSize = vec2(u_worldWidth, u_worldHeight); + givenBoxSize = max(givenBoxSize, vec2(1.)) * u_pixelRatio; + float r = u_rotation * 3.14159265358979323846 / 180.; + mat2 graphicRotation = mat2(cos(r), sin(r), -sin(r), cos(r)); + vec2 graphicOffset = vec2(-u_offsetX, u_offsetY); + + + // =================================================== + + float fixedRatio = 1.; + vec2 fixedRatioBoxGivenSize = vec2( + (u_worldWidth == 0.) ? u_resolution.x : givenBoxSize.x, + (u_worldHeight == 0.) ? u_resolution.y : givenBoxSize.y + ); + + v_objectBoxSize = getBoxSize(fixedRatio, fixedRatioBoxGivenSize).xy; + vec2 objectWorldScale = u_resolution.xy / v_objectBoxSize; + + v_objectUV = uv; + v_objectUV *= objectWorldScale; + v_objectUV += boxOrigin * (objectWorldScale - 1.); + v_objectUV += graphicOffset; + v_objectUV /= u_scale; + v_objectUV = graphicRotation * v_objectUV; + + // =================================================== + + v_responsiveBoxGivenSize = vec2( + (u_worldWidth == 0.) ? u_resolution.x : givenBoxSize.x, + (u_worldHeight == 0.) ? u_resolution.y : givenBoxSize.y + ); + float responsiveRatio = v_responsiveBoxGivenSize.x / v_responsiveBoxGivenSize.y; + vec2 responsiveBoxSize = getBoxSize(responsiveRatio, v_responsiveBoxGivenSize).xy; + vec2 responsiveBoxScale = u_resolution.xy / responsiveBoxSize; + + #ifdef ADD_HELPERS + v_responsiveHelperBox = uv; + v_responsiveHelperBox *= responsiveBoxScale; + v_responsiveHelperBox += boxOrigin * (responsiveBoxScale - 1.); + #endif + + v_responsiveUV = uv; + v_responsiveUV *= responsiveBoxScale; + v_responsiveUV += boxOrigin * (responsiveBoxScale - 1.); + v_responsiveUV += graphicOffset; + v_responsiveUV /= u_scale; + v_responsiveUV.x *= responsiveRatio; + v_responsiveUV = graphicRotation * v_responsiveUV; + v_responsiveUV.x /= responsiveRatio; + + // =================================================== + + float patternBoxRatio = givenBoxSize.x / givenBoxSize.y; + vec2 patternBoxGivenSize = vec2( + (u_worldWidth == 0.) ? u_resolution.x : givenBoxSize.x, + (u_worldHeight == 0.) ? u_resolution.y : givenBoxSize.y + ); + patternBoxRatio = patternBoxGivenSize.x / patternBoxGivenSize.y; + + vec3 boxSizeData = getBoxSize(patternBoxRatio, patternBoxGivenSize); + v_patternBoxSize = boxSizeData.xy; + float patternBoxNoFitBoxWidth = boxSizeData.z; + vec2 patternBoxScale = u_resolution.xy / v_patternBoxSize; + + v_patternUV = uv; + v_patternUV += graphicOffset / patternBoxScale; + v_patternUV += boxOrigin; + v_patternUV -= boxOrigin / patternBoxScale; + v_patternUV *= u_resolution.xy; + v_patternUV /= u_pixelRatio; + if (u_fit > 0.) { + v_patternUV *= (patternBoxNoFitBoxWidth / v_patternBoxSize.x); + } + v_patternUV /= u_scale; + v_patternUV = graphicRotation * v_patternUV; + v_patternUV += boxOrigin / patternBoxScale; + v_patternUV -= boxOrigin; + // x100 is a default multiplier between vertex and fragmant shaders + // we use it to avoid UV presision issues + v_patternUV *= .01; + + // =================================================== + + vec2 imageBoxSize; + if (u_fit == 1.) { // contain + imageBoxSize.x = min(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio; + } else if (u_fit == 2.) { // cover + imageBoxSize.x = max(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio; + } else { + imageBoxSize.x = min(10.0, 10.0 / u_imageAspectRatio * u_imageAspectRatio); + } + imageBoxSize.y = imageBoxSize.x / u_imageAspectRatio; + vec2 imageBoxScale = u_resolution.xy / imageBoxSize; + + v_imageUV = uv; + v_imageUV *= imageBoxScale; + v_imageUV += boxOrigin * (imageBoxScale - 1.); + v_imageUV += graphicOffset; + v_imageUV /= u_scale; + v_imageUV.x *= u_imageAspectRatio; + v_imageUV = graphicRotation * v_imageUV; + v_imageUV.x /= u_imageAspectRatio; + + v_imageUV += .5; + v_imageUV.y = 1. - v_imageUV.y; +}`; + +// node_modules/@paper-design/shaders/dist/shader-mount.js +var DEFAULT_MAX_PIXEL_COUNT = 1920 * 1080 * 4; +var ShaderMount = class { + parentElement; + canvasElement; + gl; + program = null; + uniformLocations = {}; + /** The fragment shader that we are using */ + fragmentShader; + /** Stores the RAF for the render loop */ + rafId = null; + /** Time of the last rendered frame */ + lastRenderTime = 0; + /** Total time that we have played any animation, passed as a uniform to the shader for time-based VFX */ + currentFrame = 0; + /** The speed that we progress through animation time (multiplies by delta time every update). Allows negatives to play in reverse. If set to 0, rAF will stop entirely so static shaders have no recurring performance costs */ + speed = 0; + /** Actual speed used that accounts for visibility: we pause the shader if the tab is hidden or the element out of the viewport */ + currentSpeed = 0; + /** Uniforms that are provided by the user for the specific shader being mounted (not including uniforms that this Mount adds, like time and resolution) */ + providedUniforms; + /** Names of the uniforms that should have mipmaps generated for them */ + mipmaps = []; + /** Just a sanity check to make sure frames don't run after we're disposed */ + hasBeenDisposed = false; + /** If the resolution of the canvas has changed since the last render */ + resolutionChanged = true; + /** Store textures that are provided by the user */ + textures = /* @__PURE__ */ new Map(); + minPixelRatio; + maxPixelCount; + isSafari = isSafari(); + uniformCache = {}; + textureUnitMap = /* @__PURE__ */ new Map(); + ownerDocument; + constructor(parentElement, fragmentShader, uniforms, webGlContextAttributes, speed = 0, frame = 0, minPixelRatio = 2, maxPixelCount = DEFAULT_MAX_PIXEL_COUNT, mipmaps = []) { + if (parentElement?.nodeType === 1) { + this.parentElement = parentElement; + } else { + throw new Error("Paper Shaders: parent element must be an HTMLElement"); + } + this.ownerDocument = parentElement.ownerDocument; + if (!this.ownerDocument.querySelector("style[data-paper-shader]")) { + const styleElement = this.ownerDocument.createElement("style"); + styleElement.innerHTML = defaultStyle; + styleElement.setAttribute("data-paper-shader", ""); + this.ownerDocument.head.prepend(styleElement); + } + const canvasElement = this.ownerDocument.createElement("canvas"); + this.canvasElement = canvasElement; + this.parentElement.prepend(canvasElement); + this.fragmentShader = fragmentShader; + this.providedUniforms = uniforms; + this.mipmaps = mipmaps; + this.currentFrame = frame; + this.minPixelRatio = minPixelRatio; + this.maxPixelCount = maxPixelCount; + const gl = canvasElement.getContext("webgl2", webGlContextAttributes); + if (!gl) { + throw new Error("Paper Shaders: WebGL is not supported in this browser"); + } + this.gl = gl; + this.initProgram(); + this.setupPositionAttribute(); + this.setupUniforms(); + this.setUniformValues(this.providedUniforms); + this.setupResizeObserver(); + visualViewport?.addEventListener("resize", this.handleVisualViewportChange); + this.setupIntersectionObserver(); + this.setSpeed(speed); + this.parentElement.setAttribute("data-paper-shader", ""); + this.parentElement.paperShaderMount = this; + this.ownerDocument.addEventListener("visibilitychange", this.handleDocumentVisibilityChange); + } + initProgram = () => { + const program = createProgram(this.gl, vertexShaderSource, this.fragmentShader); + if (!program) return; + this.program = program; + }; + setupPositionAttribute = () => { + const positionAttributeLocation = this.gl.getAttribLocation(this.program, "a_position"); + const positionBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer); + const positions = [-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]; + this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(positions), this.gl.STATIC_DRAW); + this.gl.enableVertexAttribArray(positionAttributeLocation); + this.gl.vertexAttribPointer(positionAttributeLocation, 2, this.gl.FLOAT, false, 0, 0); + }; + setupUniforms = () => { + const uniformLocations = { + u_time: this.gl.getUniformLocation(this.program, "u_time"), + u_pixelRatio: this.gl.getUniformLocation(this.program, "u_pixelRatio"), + u_resolution: this.gl.getUniformLocation(this.program, "u_resolution") + }; + Object.entries(this.providedUniforms).forEach(([key, value]) => { + uniformLocations[key] = this.gl.getUniformLocation(this.program, key); + if (value instanceof HTMLImageElement) { + const aspectRatioUniformName = `${key}AspectRatio`; + uniformLocations[aspectRatioUniformName] = this.gl.getUniformLocation(this.program, aspectRatioUniformName); + } + }); + this.uniformLocations = uniformLocations; + }; + /** + * The scale that we should render at. + * - Used to target 2x rendering even on 1x screens for better antialiasing + * - Prevents the virtual resolution from going beyond the maximum resolution + * - Accounts for the page zoom level so we render in physical device pixels rather than CSS pixels + */ + renderScale = 1; + parentWidth = 0; + parentHeight = 0; + parentDevicePixelWidth = 0; + parentDevicePixelHeight = 0; + devicePixelsSupported = false; + intersectionObserver = null; + isInViewport = true; + resizeObserver = null; + setupResizeObserver = () => { + this.resizeObserver = new ResizeObserver(([entry]) => { + if (entry?.borderBoxSize[0]) { + const physicalPixelSize = entry.devicePixelContentBoxSize?.[0]; + if (physicalPixelSize !== void 0) { + this.devicePixelsSupported = true; + this.parentDevicePixelWidth = physicalPixelSize.inlineSize; + this.parentDevicePixelHeight = physicalPixelSize.blockSize; + } + this.parentWidth = entry.borderBoxSize[0].inlineSize; + this.parentHeight = entry.borderBoxSize[0].blockSize; + } + this.handleResize(); + }); + this.resizeObserver.observe(this.parentElement); + }; + setupIntersectionObserver = () => { + const view = this.ownerDocument.defaultView; + if (!view?.IntersectionObserver) return; + this.intersectionObserver = new view.IntersectionObserver(([entry]) => { + this.isInViewport = entry?.isIntersecting ?? true; + this.updateCurrentSpeed(); + }); + this.intersectionObserver.observe(this.parentElement); + }; + // Visual viewport resize handler, mainly used to react to browser zoom changes. + // Resize observer by itself does not react to pinch zoom, and although it usually + // reacts to classic browser zoom, it's not guaranteed in edge cases. + // Since timing between visual viewport changes and resize observer is complex + // and because we'd like to know the device pixel sizes of elements, we just restart + // the observer to get a guaranteed fresh callback regardless if it would have triggered or not. + handleVisualViewportChange = () => { + this.resizeObserver?.disconnect(); + this.setupResizeObserver(); + }; + /** Resize handler for when the container div changes size or the max pixel count changes and we want to resize our canvas to match */ + handleResize = () => { + let targetPixelWidth = 0; + let targetPixelHeight = 0; + const dpr = Math.max(1, window.devicePixelRatio); + const pinchZoom = visualViewport?.scale ?? 1; + if (this.devicePixelsSupported) { + const scaleToMeetMinPixelRatio = Math.max(1, this.minPixelRatio / dpr); + targetPixelWidth = this.parentDevicePixelWidth * scaleToMeetMinPixelRatio * pinchZoom; + targetPixelHeight = this.parentDevicePixelHeight * scaleToMeetMinPixelRatio * pinchZoom; + } else { + let targetRenderScale = Math.max(dpr, this.minPixelRatio) * pinchZoom; + if (this.isSafari) { + const zoomLevel = bestGuessBrowserZoom(this.ownerDocument); + targetRenderScale *= Math.max(1, zoomLevel); + } + targetPixelWidth = Math.round(this.parentWidth) * targetRenderScale; + targetPixelHeight = Math.round(this.parentHeight) * targetRenderScale; + } + const maxPixelCountHeadroom = Math.sqrt(this.maxPixelCount) / Math.sqrt(targetPixelWidth * targetPixelHeight); + const scaleToMeetMaxPixelCount = Math.min(1, maxPixelCountHeadroom); + const newWidth = Math.round(targetPixelWidth * scaleToMeetMaxPixelCount); + const newHeight = Math.round(targetPixelHeight * scaleToMeetMaxPixelCount); + const newRenderScale = newWidth / Math.round(this.parentWidth); + if (this.canvasElement.width !== newWidth || this.canvasElement.height !== newHeight || this.renderScale !== newRenderScale) { + this.renderScale = newRenderScale; + this.canvasElement.width = newWidth; + this.canvasElement.height = newHeight; + this.resolutionChanged = true; + this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height); + this.render(performance.now()); + } + }; + render = (currentTime) => { + if (this.hasBeenDisposed) return; + if (this.program === null) { + console.warn("Tried to render before program or gl was initialized"); + return; + } + const dt = currentTime - this.lastRenderTime; + this.lastRenderTime = currentTime; + if (this.currentSpeed !== 0) { + this.currentFrame += dt * this.currentSpeed; + } + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + this.gl.useProgram(this.program); + this.gl.uniform1f(this.uniformLocations.u_time, this.currentFrame * 1e-3); + if (this.resolutionChanged) { + this.gl.uniform2f(this.uniformLocations.u_resolution, this.gl.canvas.width, this.gl.canvas.height); + this.gl.uniform1f(this.uniformLocations.u_pixelRatio, this.renderScale); + this.resolutionChanged = false; + } + this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); + if (this.currentSpeed !== 0) { + this.requestRender(); + } else { + this.rafId = null; + } + }; + requestRender = () => { + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + } + this.rafId = requestAnimationFrame(this.render); + }; + /** Creates a texture from an image and sets it into a uniform value */ + setTextureUniform = (uniformName, image) => { + if (!image.complete || image.naturalWidth === 0) { + throw new Error(`Paper Shaders: image for uniform ${uniformName} must be fully loaded`); + } + const existingTexture = this.textures.get(uniformName); + if (existingTexture) { + this.gl.deleteTexture(existingTexture); + } + if (!this.textureUnitMap.has(uniformName)) { + this.textureUnitMap.set(uniformName, this.textureUnitMap.size); + } + const textureUnit = this.textureUnitMap.get(uniformName); + this.gl.activeTexture(this.gl.TEXTURE0 + textureUnit); + const texture = this.gl.createTexture(); + this.gl.bindTexture(this.gl.TEXTURE_2D, texture); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR); + this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, image); + if (this.mipmaps.includes(uniformName)) { + this.gl.generateMipmap(this.gl.TEXTURE_2D); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR_MIPMAP_LINEAR); + } + const error = this.gl.getError(); + if (error !== this.gl.NO_ERROR || texture === null) { + console.error("Paper Shaders: WebGL error when uploading texture:", error); + return; + } + this.textures.set(uniformName, texture); + const location = this.uniformLocations[uniformName]; + if (location) { + this.gl.uniform1i(location, textureUnit); + const aspectRatioUniformName = `${uniformName}AspectRatio`; + const aspectRatioLocation = this.uniformLocations[aspectRatioUniformName]; + if (aspectRatioLocation) { + const aspectRatio = image.naturalWidth / image.naturalHeight; + this.gl.uniform1f(aspectRatioLocation, aspectRatio); + } + } + }; + /** Utility: recursive equality test for all the uniforms */ + areUniformValuesEqual = (a, b) => { + if (a === b) return true; + if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) { + return a.every((val, i) => this.areUniformValuesEqual(val, b[i])); + } + return false; + }; + /** Sets the provided uniform values into the WebGL program, can be a partial list of uniforms that have changed */ + setUniformValues = (updatedUniforms) => { + this.gl.useProgram(this.program); + Object.entries(updatedUniforms).forEach(([key, value]) => { + let cacheValue = value; + if (value instanceof HTMLImageElement) { + cacheValue = `${value.src.slice(0, 200)}|${value.naturalWidth}x${value.naturalHeight}`; + } + if (this.areUniformValuesEqual(this.uniformCache[key], cacheValue)) return; + this.uniformCache[key] = cacheValue; + const location = this.uniformLocations[key]; + if (!location) { + console.warn(`Uniform location for ${key} not found`); + return; + } + if (value instanceof HTMLImageElement) { + this.setTextureUniform(key, value); + } else if (Array.isArray(value)) { + let flatArray = null; + let valueLength = null; + if (value[0] !== void 0 && Array.isArray(value[0])) { + const firstChildLength = value[0].length; + if (value.every((arr) => arr.length === firstChildLength)) { + flatArray = value.flat(); + valueLength = firstChildLength; + } else { + console.warn(`All child arrays must be the same length for ${key}`); + return; + } + } else { + flatArray = value; + valueLength = flatArray.length; + } + switch (valueLength) { + case 2: + this.gl.uniform2fv(location, flatArray); + break; + case 3: + this.gl.uniform3fv(location, flatArray); + break; + case 4: + this.gl.uniform4fv(location, flatArray); + break; + case 9: + this.gl.uniformMatrix3fv(location, false, flatArray); + break; + case 16: + this.gl.uniformMatrix4fv(location, false, flatArray); + break; + default: + console.warn(`Unsupported uniform array length: ${valueLength}`); + } + } else if (typeof value === "number") { + this.gl.uniform1f(location, value); + } else if (typeof value === "boolean") { + this.gl.uniform1i(location, value ? 1 : 0); + } else { + console.warn(`Unsupported uniform type for ${key}: ${typeof value}`); + } + }); + }; + /** Gets the current total animation time from 0ms */ + getCurrentFrame = () => { + return this.currentFrame; + }; + /** Set a frame to get a deterministic result, frames are literally just milliseconds from zero since the animation started */ + setFrame = (newFrame) => { + this.currentFrame = newFrame; + this.lastRenderTime = performance.now(); + this.render(performance.now()); + }; + /** Set an animation speed (or 0 to stop animation) */ + setSpeed = (newSpeed = 1) => { + this.speed = newSpeed; + this.updateCurrentSpeed(); + }; + /** Apply the target speed, pausing (0) while the tab is hidden or the element is out of the viewport */ + updateCurrentSpeed = () => { + this.setCurrentSpeed(this.ownerDocument.hidden || !this.isInViewport ? 0 : this.speed); + }; + setCurrentSpeed = (newSpeed) => { + this.currentSpeed = newSpeed; + if (this.rafId === null && newSpeed !== 0) { + this.lastRenderTime = performance.now(); + this.rafId = requestAnimationFrame(this.render); + } + if (this.rafId !== null && newSpeed === 0) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + }; + /** Set the maximum pixel count for the shader, this will limit the number of pixels that will be rendered */ + setMaxPixelCount = (newMaxPixelCount = DEFAULT_MAX_PIXEL_COUNT) => { + this.maxPixelCount = newMaxPixelCount; + this.handleResize(); + }; + /** Set the minimum pixel ratio for the shader */ + setMinPixelRatio = (newMinPixelRatio = 2) => { + this.minPixelRatio = newMinPixelRatio; + this.handleResize(); + }; + /** Update the uniforms that are provided by the outside shader, can be a partial set with only the uniforms that have changed */ + setUniforms = (newUniforms) => { + this.setUniformValues(newUniforms); + this.providedUniforms = { ...this.providedUniforms, ...newUniforms }; + this.render(performance.now()); + }; + handleDocumentVisibilityChange = () => { + this.updateCurrentSpeed(); + }; + /** Dispose of the shader mount, cleaning up all of the WebGL resources */ + dispose = () => { + this.hasBeenDisposed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + if (this.gl && this.program) { + this.textures.forEach((texture) => { + this.gl.deleteTexture(texture); + }); + this.textures.clear(); + this.gl.deleteProgram(this.program); + this.program = null; + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null); + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null); + this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, null); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); + this.gl.getError(); + } + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + this.resizeObserver = null; + } + if (this.intersectionObserver) { + this.intersectionObserver.disconnect(); + this.intersectionObserver = null; + } + visualViewport?.removeEventListener("resize", this.handleVisualViewportChange); + this.ownerDocument.removeEventListener("visibilitychange", this.handleDocumentVisibilityChange); + this.uniformLocations = {}; + this.canvasElement.remove(); + delete this.parentElement.paperShaderMount; + }; +}; +function createShader(gl, type, source) { + const shader = gl.createShader(type); + if (!shader) return null; + gl.shaderSource(shader, source); + gl.compileShader(shader); + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader)); + gl.deleteShader(shader); + return null; + } + return shader; +} +function createProgram(gl, vertexShaderSource2, fragmentShaderSource) { + const format = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT); + const precision = format ? format.precision : null; + if (precision && precision < 23) { + vertexShaderSource2 = vertexShaderSource2.replace(/precision\s+(lowp|mediump)\s+float;/g, "precision highp float;"); + fragmentShaderSource = fragmentShaderSource.replace(/precision\s+(lowp|mediump)\s+float/g, "precision highp float").replace(/\b(uniform|varying|attribute)\s+(lowp|mediump)\s+(\w+)/g, "$1 highp $3"); + } + const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource2); + const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource); + if (!vertexShader || !fragmentShader) return null; + const program = gl.createProgram(); + if (!program) return null; + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + gl.linkProgram(program); + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + console.error("Unable to initialize the shader program: " + gl.getProgramInfoLog(program)); + gl.deleteProgram(program); + gl.deleteShader(vertexShader); + gl.deleteShader(fragmentShader); + return null; + } + gl.detachShader(program, vertexShader); + gl.detachShader(program, fragmentShader); + gl.deleteShader(vertexShader); + gl.deleteShader(fragmentShader); + return program; +} +var defaultStyle = `@layer paper-shaders { + :where([data-paper-shader]) { + isolation: isolate; + position: relative; + + & canvas { + contain: strict; + display: block; + position: absolute; + inset: 0; + z-index: -1; + width: 100%; + height: 100%; + border-radius: inherit; + corner-shape: inherit; + } + } +}`; +function isSafari() { + const ua = navigator.userAgent.toLowerCase(); + return ua.includes("safari") && !ua.includes("chrome") && !ua.includes("android"); +} +function bestGuessBrowserZoom(doc) { + const viewportScale = visualViewport?.scale ?? 1; + const viewportWidth = visualViewport?.width ?? window.innerWidth; + const scrollbarWidth = window.innerWidth - doc.documentElement.clientWidth; + const innerWidth = viewportScale * viewportWidth + scrollbarWidth; + const ratio = outerWidth / innerWidth; + const zoomPercentageRounded = Math.round(100 * ratio); + if (zoomPercentageRounded % 5 === 0) { + return zoomPercentageRounded / 100; + } + if (zoomPercentageRounded === 33) { + return 1 / 3; + } + if (zoomPercentageRounded === 67) { + return 2 / 3; + } + if (zoomPercentageRounded === 133) { + return 4 / 3; + } + return ratio; +} + +// node_modules/@paper-design/shaders/dist/shader-utils.js +var declarePI = ` +#define TWO_PI 6.28318530718 +#define PI 3.14159265358979323846 +`; +var rotation2 = ` +vec2 rotate(vec2 uv, float th) { + return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv; +} +`; +var textureRandomizerR = ` + float randomR(vec2 p) { + vec2 uv = floor(p) / 100. + .5; + return texture(u_noiseTexture, fract(uv)).r; + } +`; +var fiberNoise = ` +float fiberRandom(vec2 p) { + vec2 uv = floor(p) / 100.; + return texture(u_noiseTexture, fract(uv)).b; +} + +float fiberValueNoise(vec2 st) { + vec2 i = floor(st); + vec2 f = fract(st); + float a = fiberRandom(i); + float b = fiberRandom(i + vec2(1.0, 0.0)); + float c = fiberRandom(i + vec2(0.0, 1.0)); + float d = fiberRandom(i + vec2(1.0, 1.0)); + vec2 u = f * f * (3.0 - 2.0 * f); + float x1 = mix(a, b, u.x); + float x2 = mix(c, d, u.x); + return mix(x1, x2, u.y); +} + +float fiberNoiseFbm(in vec2 n, vec2 seedOffset) { + float total = 0.0, amplitude = 1.; + for (int i = 0; i < 4; i++) { + n = rotate(n, .7); + total += fiberValueNoise(n + seedOffset) * amplitude; + n *= 2.; + amplitude *= 0.6; + } + return total; +} + +float fiberNoise(vec2 uv, vec2 seedOffset) { + float epsilon = 0.001; + float n1 = fiberNoiseFbm(uv + vec2(epsilon, 0.0), seedOffset); + float n2 = fiberNoiseFbm(uv - vec2(epsilon, 0.0), seedOffset); + float n3 = fiberNoiseFbm(uv + vec2(0.0, epsilon), seedOffset); + float n4 = fiberNoiseFbm(uv - vec2(0.0, epsilon), seedOffset); + return length(vec2(n1 - n2, n3 - n4)) / (2.0 * epsilon); +} +`; + +// node_modules/@paper-design/shaders/dist/shaders/paper-texture.js +var paperTextureFragmentShader = `#version 300 es +precision mediump float; + +uniform vec2 u_resolution; +uniform float u_pixelRatio; + +uniform vec4 u_colorFront; +uniform vec4 u_colorBack; + +uniform sampler2D u_image; +uniform float u_imageAspectRatio; + +uniform float u_contrast; +uniform float u_roughness; +uniform float u_fiber; +uniform float u_fiberSize; +uniform float u_crumples; +uniform float u_crumpleSize; +uniform float u_folds; +uniform float u_foldCount; +uniform float u_drops; +uniform float u_seed; +uniform float u_fade; + +uniform sampler2D u_noiseTexture; + +in vec2 v_imageUV; + +out vec4 fragColor; + +float getUvFrame(vec2 uv) { + float aax = 2. * fwidth(uv.x); + float aay = 2. * fwidth(uv.y); + + float left = smoothstep(0., aax, uv.x); + float right = 1. - smoothstep(1. - aax, 1., uv.x); + float bottom = smoothstep(0., aay, uv.y); + float top = 1. - smoothstep(1. - aay, 1., uv.y); + + return left * right * bottom * top; +} + +${declarePI} +${rotation2} +${textureRandomizerR} +float valueNoise(vec2 st) { + vec2 i = floor(st); + vec2 f = fract(st); + float a = randomR(i); + float b = randomR(i + vec2(1.0, 0.0)); + float c = randomR(i + vec2(0.0, 1.0)); + float d = randomR(i + vec2(1.0, 1.0)); + vec2 u = f * f * (3.0 - 2.0 * f); + float x1 = mix(a, b, u.x); + float x2 = mix(c, d, u.x); + return mix(x1, x2, u.y); +} +float fbm(vec2 n) { + float total = 0.0, amplitude = .4; + for (int i = 0; i < 3; i++) { + total += valueNoise(n) * amplitude; + n *= 1.99; + amplitude *= 0.65; + } + return total; +} + + +float randomG(vec2 p) { + vec2 uv = floor(p) / 50. + .5; + return texture(u_noiseTexture, fract(uv)).g; +} +float roughness(vec2 p) { + p *= .1; + float o = 0.; + for (float i = 0.; ++i < 4.; p *= 2.1) { + vec4 w = vec4(floor(p), ceil(p)); + vec2 f = fract(p); + o += mix( + mix(randomG(w.xy), randomG(w.xw), f.y), + mix(randomG(w.zy), randomG(w.zw), f.y), + f.x); + o += .2 / exp(2. * abs(sin(.2 * p.x + .5 * p.y))); + } + return o / 3.; +} + +${fiberNoise} + +vec2 randomGB(vec2 p) { + vec2 uv = floor(p) / 50. + .5; + return texture(u_noiseTexture, fract(uv)).gb; +} +float crumpledNoise(vec2 t, float pw) { + vec2 p = floor(t); + float wsum = 0.; + float cl = 0.; + for (int y = -1; y < 2; y += 1) { + for (int x = -1; x < 2; x += 1) { + vec2 b = vec2(float(x), float(y)); + vec2 q = b + p; + vec2 q2 = q - floor(q / 8.) * 8.; + vec2 c = q + randomGB(q2); + vec2 r = c - t; + float w = pow(smoothstep(0., 1., 1. - abs(r.x)), pw) * pow(smoothstep(0., 1., 1. - abs(r.y)), pw); + cl += (.5 + .5 * sin((q2.x + q2.y * 5.) * 8.)) * w; + wsum += w; + } + } + return pow(wsum != 0.0 ? cl / wsum : 0.0, .5) * 2.; +} +float crumplesShape(vec2 uv) { + return crumpledNoise(uv * .25, 16.) * crumpledNoise(uv * .5, 2.); +} + + +vec2 folds(vec2 uv) { + vec3 pp = vec3(0.); + float l = 9.; + for (float i = 0.; i < 15.; i++) { + if (i >= u_foldCount) break; + vec2 rand = randomGB(vec2(i, i * u_seed)); + float an = rand.x * TWO_PI; + vec2 p = vec2(cos(an), sin(an)) * rand.y; + float dist = distance(uv, p); + l = min(l, dist); + + if (l == dist) { + pp.xy = (uv - p.xy); + pp.z = dist; + } + } + return mix(pp.xy, vec2(0.), pow(pp.z, .25)); +} + +float drops(vec2 uv) { + vec2 iDropsUV = floor(uv); + vec2 fDropsUV = fract(uv); + float dropsMinDist = 1.; + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec2 neighbor = vec2(float(i), float(j)); + vec2 offset = randomGB(iDropsUV + neighbor); + offset = .5 + .5 * sin(10. * u_seed + TWO_PI * offset); + vec2 pos = neighbor + offset - fDropsUV; + float dist = length(pos); + dropsMinDist = min(dropsMinDist, dropsMinDist*dist); + } + } + return 1. - smoothstep(.05, .09, pow(dropsMinDist, .5)); +} + +float lst(float edge0, float edge1, float x) { + return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); +} + +void main() { + + vec2 imageUV = v_imageUV; + vec2 patternUV = v_imageUV - .5; + patternUV = 5. * (patternUV * vec2(u_imageAspectRatio, 1.)); + + vec2 roughnessUv = 1.5 * (gl_FragCoord.xy - .5 * u_resolution) / u_pixelRatio; + float roughness = roughness(roughnessUv + vec2(1., 0.)) - roughness(roughnessUv - vec2(1., 0.)); + + vec2 crumplesUV = fract(patternUV * .02 / u_crumpleSize - u_seed) * 32.; + float crumples = u_crumples * (crumplesShape(crumplesUV + vec2(.05, 0.)) - crumplesShape(crumplesUV)); + + vec2 fiberUV = 2. / u_fiberSize * patternUV; + float fiber = fiberNoise(fiberUV, vec2(0.)); + fiber = .5 * u_fiber * (fiber - 1.); + + vec2 normal = vec2(0.); + vec2 normalImage = vec2(0.); + + vec2 foldsUV = patternUV * .12; + foldsUV = rotate(foldsUV, 4. * u_seed); + vec2 w = folds(foldsUV); + foldsUV = rotate(foldsUV + .007 * cos(u_seed), .01 * sin(u_seed)); + vec2 w2 = folds(foldsUV); + + float drops = u_drops * drops(patternUV * 2.); + + float fade = u_fade * fbm(.17 * patternUV + 10. * u_seed); + fade = clamp(8. * fade * fade * fade, 0., 1.); + + w = mix(w, vec2(0.), fade); + w2 = mix(w2, vec2(0.), fade); + crumples = mix(crumples, 0., fade); + drops = mix(drops, 0., fade); + fiber *= mix(1., .5, fade); + roughness *= mix(1., .5, fade); + + normal.xy += u_folds * min(5. * u_contrast, 1.) * 4. * max(vec2(0.), w + w2); + normalImage.xy += u_folds * 2. * w; + + normal.xy += crumples; + normalImage.xy += 1.5 * crumples; + + normal.xy += 3. * drops; + normalImage.xy += .2 * drops; + + normal.xy += u_roughness * 1.5 * roughness; + normal.xy += fiber; + + normalImage += u_roughness * .75 * roughness; + normalImage += .2 * fiber; + + vec3 lightPos = vec3(1., 2., 1.); + float res = dot(normalize(vec3(normal, 9.5 - 9. * pow(u_contrast, .1))), normalize(lightPos)); + + vec3 fgColor = u_colorFront.rgb * u_colorFront.a; + float fgOpacity = u_colorFront.a; + vec3 bgColor = u_colorBack.rgb * u_colorBack.a; + float bgOpacity = u_colorBack.a; + + imageUV += .02 * normalImage; + float frame = getUvFrame(imageUV); + vec4 image = texture(u_image, imageUV); + image.rgb += .6 * pow(u_contrast, .4) * (res - .7); + + frame *= image.a; + + vec3 color = fgColor * res; + float opacity = fgOpacity * res; + + color += bgColor * (1. - opacity); + opacity += bgOpacity * (1. - opacity); + opacity = mix(opacity, 1., frame); + + color -= .007 * drops; + + color.rgb = mix(color, image.rgb, frame); + + fragColor = vec4(color, opacity); +} +`; + +// node_modules/@paper-design/shaders/dist/get-shader-noise-texture.js +function getShaderNoiseTexture() { + if (typeof window === "undefined") { + return void 0; + } + const img = new Image(); + img.src = noiseSrc; + return img; +} +var noiseSrc = "data:image/png;base64,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"; +export { + ShaderMount, + getShaderNoiseTexture, + paperTextureFragmentShader +}; diff --git a/public/_lab/render.html b/public/_lab/render.html new file mode 100644 index 0000000..43e6607 --- /dev/null +++ b/public/_lab/render.html @@ -0,0 +1,56 @@ + + + bake + +
+ + + diff --git a/public/_lab/shader-demo.js b/public/_lab/shader-demo.js new file mode 100644 index 0000000..46bcb50 --- /dev/null +++ b/public/_lab/shader-demo.js @@ -0,0 +1,83 @@ +// Живой прототип Paper Shaders на стенде _lab (без правки прод-кода). +// Пакет скопирован локально в /vendor/paper-shaders (zero-dep, ESM). +import { + ShaderMount, + meshGradientFragmentShader, + getShaderColorFromString, + defaultObjectSizing, + ShaderFitOptions, +} from '/vendor/paper-shaders/index.js'; + +// Цвета по бренду (как у .contact-card / .neu-card--shader) +const BRAND = { + tg: ['#0088cc', '#00bcd4', '#0a66c2'], + vk: ['#0077ff', '#4a90ff', '#0033aa'], + ya: ['#f8604a', '#ffae00', '#ff3d00'], + email: ['#7c5cff', '#b06cff', '#5a3fd6'], + demo: ['#7c5cff', '#00c2c7', '#ff5c8a', '#ffb347'], +}; + +// Официальная конвертация hex/HSL/RGB -> [r,g,b,a] (0..1), как ожидает u_colors (vec4[]). +const c4 = (arr) => arr.map(getShaderColorFromString); + +// Sizing uniforms обязательны (vertex-шейдер делит на u_scale — при 0 будет NaN +// и весь шейдер чёрный). Берём дефолты из пакета, как в React-обёртке. +const sizing = () => ({ + u_fit: ShaderFitOptions[defaultObjectSizing.fit], + u_scale: defaultObjectSizing.scale, + u_rotation: defaultObjectSizing.rotation, + u_offsetX: defaultObjectSizing.offsetX, + u_offsetY: defaultObjectSizing.offsetY, + u_originX: defaultObjectSizing.originX, + u_originY: defaultObjectSizing.originY, + u_worldWidth: defaultObjectSizing.worldWidth, + u_worldHeight: defaultObjectSizing.worldHeight, +}); + +// Единственный используемый шейдер: mesh gradient, анимированный. +const MESH = { + frag: meshGradientFragmentShader, + speed: 1.0, + uniforms: (c) => ({ + ...sizing(), + u_colors: c4(c), u_colorsCount: c.length, + u_distortion: 1.2, u_swirl: 0.8, u_grainMixer: 0.08, u_grainOverlay: 0.04, + }), +}; + +const mounts = new WeakMap(); + +function mountShader(host, colors) { + const wrap = host.querySelector('.card-shader'); + if (!wrap) return; + if (mounts.has(host)) { try { mounts.get(host).dispose(); } catch {} mounts.delete(host); } + while (wrap.firstChild) wrap.removeChild(wrap.firstChild); + try { + const m = new ShaderMount(wrap, MESH.frag, MESH.uniforms(colors), + { preserveDrawingBuffer: true, antialias: true }, MESH.speed); + // отключаем IntersectionObserver: иначе вне вьюпорта ставит currentSpeed=0 + // и рендер замирает (у пользователя карточка внизу страницы -> пауза). + try { m.intersectionObserver?.disconnect(); } catch {} + mounts.set(host, m); + } catch (e) { + console.error('ShaderMount failed', e); + } +} + +function mountAll(root = document) { + // любые карточки с шейдером: у .card-shader есть data-shader (бренд), + // сама карточка — его родитель (contact-card, neu-card--shader, shader-cell...) + root.querySelectorAll('.card-shader[data-shader]').forEach((wrap) => { + const host = wrap.parentElement; + const brand = wrap.dataset.shader; + mountShader(host, BRAND[brand] || BRAND.demo); + }); +} + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', mountAll); +} else { + mountAll(); +} + +window.__mountLabShaders = mountAll; diff --git a/public/_lab/template.html b/public/_lab/template.html new file mode 100644 index 0000000..313ac00 --- /dev/null +++ b/public/_lab/template.html @@ -0,0 +1,74 @@ + + + + + + + Стенд: <название элемента> + + + + + + + +
+
+

<название элемента>

+

+ Стенд для настройки вида. Сборка не нужна: правим public/css/main.css → + страница перезагрузится сама. Переключатель темы в правом нижнем углу. +

+ +
+ + +
+

Демо

+

Опиши, что смотрим. Здесь — сам элемент во всех состояниях.

+
+ +
+ Состояние + Подпись +
+
+
+ +
+

Токены

+

Числа, которыми управляется вид. Значение по умолчанию — в скобках.

+
+
+
--neu-bg --bg-elevated
+
Нейтральная заливка карточки.
+
+
+
+
+ +
+ + + + + + +
+ + + + + diff --git a/public/assets/lab/grain-l.webp b/public/assets/lab/grain-l.webp new file mode 100644 index 0000000000000000000000000000000000000000..01d36c951af24eaebd73c7c01ff6a5266c2286be GIT binary patch literal 42476 zcmV(vK}X%cw7gekJ{v@+Ztc#Xdp%ANDWuAMby(d{2Ct{#W^K@jhYtul&#OexUy$`)B&U z?ce7=W<8MqbN%D;_s~DeKS%#}{nPTx=FGd&i?v(1b>6{bN_4qr~FU$f4qM9KW+b!|G)jWz?V(bS_Zx& zbYJ9Lb6UzQz+oIGa0}w8EVYBYRU-xl_oHw-lD7}(rvUmfCXP24Q-AgP+s+k6!A3MFI4OINmNs&q2AED zQjQIr%jm@AwtwDiP5pw98ldt=Yb2&-aV{~(t_UTh>gB+bkogC`z;3NMiENm8bK^nn zpiYyO!&O7}riZ%ofD>CIE%pEDcmA8J%wbFb4YQeWxP_wP1416U)3=4dXq@%xO6y|{ zRLOthAv;vlJkRxJ=hru3HS-McXunM)4@ZGr}ljGBA8fY(3j z=${l7cTAe*c_y2Pl*cWjlyiX<`RB8>XCCe-T|PaKEuqGe?=|cnQ%9LI5e4*i?8hs7 z)(o{Lc4){DMoM=7<-L}{M75f!^uW}~PLyFS;GH$nU+7_o50W^xo7L3TF#4}CS39)`IRz=8dn!SR zi}ImA+li`AINgA7ZM`a{N-Z=f)+)nSFU@;aeNAvY9Rs+&XN392Ib%ni2eEh!B~ZA6 z8{wBQMWzD$nrR=GZ|ML|{VgXIm=ktcDNbA>wBhR2Li>1^!~U-JnAQ6VP;K~BP)R4- z^f;BFnz#M4%Cv*T{Y6v)Y(y9(MiT+Vd6Uy>Ld?st&dTamP^-_|Z1$;YwL?E+tfY%ERlp-YHpf`X^xkCS?HEVmmUZeax zEc*)#icW0q2oN6E>T|H7Ql8IGJVWhIH9GyC2aLI$W^qY`p1{DrXgf(~{&+a$U6P2?1HV833%%e}CS4(Rcy(>-DmnRQM_N)x6{l-~B?HssIEDZRY+ zEC}6hk&!DkoEY!a}Y%5bUU9WT;Z9Cxln||33_Xp zfON}vG&dThvkNuNEyQPrfyU3^c+XG1MfL$`@&4uNqJ#Dv1O2nT;t=^l;myi2dHYh$ z@>kdGKg^R&=v;(@S}>Uk1aW#cW@Z6mhVk^-Htpcxp`yk6{KV1L?o@ZUV7;)c7K`bR zI-(4RzgQBARKziIS@zi%MfOMsM16xu3wiDphOU?tjEt$WYv|1Diq3|LHum#w@~`4Y z;-00pCM?8P51tH?Fa~W^>XtilG3gTVdlqK=B0YOlq)lpL56)cS=Qp;@*;gC-lOTK; z584|hwNdMi=zmIa5Q@-Rts>rsBiDn<^mk}WT^O%pOFyI6`Qq16R|hkJHHwVKo+yTJ z88*$bHzPeAWBimnF!IXa$hd()flRkI3huAdKBHl-5PV=Q_LM?m($#sHUDV7q?nvk_ zGQcy6ho7iZhtQZ#9*%ExNt&@n(A)quD}in-A5t()#<2|abG6Au&OnJumeQ({7uPZG z48{-Gc`JeekI)(3&M`xS8{)_H>0L1iwz#o`&%mbH3=yR#`(IA}3d1yLKEl3f6HR$0 z8Vzg9MgpF0u?yCJ@!oUv?9iA^c6u(3pEp#4A+OFqnfI0ByouY0Qk{+P;b2&Sw9j{6 zYnkJr-rp8Q@nL$ zJ)iT3y#VW_?l_8R+g^oKY&$I(`@sSX*R9PTqBxnJ`I+z&+$ZWPiTgNko|T8?d`*of z(T?_5XS%L!-0LJMINVmf$>hA?bE$ zfsbDQqQ%tr6%^Z95E?wG{FSeI*36UWw@vgcA{+wt=~C6IzM+SS6(_+dW8}X2Uw0|X z>@*fSj49o81DDr5KOADmJQ!&B{T`;YG2u&1@$00AYw<*`JpY7El*Z@0{5S58frOXq zWhUf3;a$O6@`IPW)V^;Hqo0%IEx=0ugo%N;3E%9#p@~keG%*t^?&r?Pieaj6!r{<{ zF)RL#TxGM6l5GEt)V|VVwXJ|1qTMpR_1oNGP`q!9_9%LAI~3VUe*6-7OAcEIg|WuY z;oq|WC3W?H;s5Py%kdq7jay)^mk%f3MHp?soe{}e^2Gf?V`0x~QCHg6yXBvPpSUpvExoN->($#PwEyp3@i=m<^H09QVNH>% zyX65f4(JjV7_tlPzX)i^fBZxmjP`ESUA}fiy~?fT?;njh(+?;Drj?htm=+t#QPuJy z))j|sgljN+7|^(r53j480w%_Zs>dw%4Kr4tQH~ue{ipT(V>^&FdrpQ1D=|9)16kaM z=pvlZeXzlX1TviFs#FR>OV@Wb%`sQb+fpZA$9a8^1t>@!kSddei*`#n5>|W2O8VJ_ zB+T(wR3Q@RYPBD;4xoiBsasfCUE{rHeaK!1x))B+SNAhpU9g@L`?@> z>{PM5>m~>k#eO`QTTa>IN9+RJ<^hnC%v2u*?xS0{KFt$QsA>s0*n8Mov$Q;^_2~Aw za=ho~5nfp{dL_tujb~U&30%B5MSq#1_Kaz!-O16M_31b3{B4Sz^{X|Pu; z)_4v-kVlU(Nq})gC%Rf46d^<<|wq5BhlwFBTT$J~~L5(4{3PG4N2Fxt8A4 z{Zzf_KM~d`?;a9Sms5-C?Wx2NL9K2I>jDm3TnoHq6>gEJF_1xgzBAGZj^(WQJ`hDt z^!%87QJQNAvtK&yfraJARe6YmCV#7#Y1FhfHbNcP!u{uVG2Ki%**sadki<(_60?9u z)~D~O7^H(jBM^u;nYX1#4{^+mFIc+dt29qFH+z^GM~-g)HodO$yx;+3WNPxplCoyd zyk|b1YW>}G?T3pee{R)kp|dgJ=ab#*5lw#?9Hox5=1T2g zyguiJOy$;ssgfpYGGRS$C8)oRwD?E?#Sop>s`O#=(~t=AqWP>?A!G>mNWk`TR}%U2^^&T zK6;6-Mcs+YuCvKrQ_@c@wb4S`K`bL+LI2bua>co-`c6icFuQ)TeA-Y(Q+Grq45!kV zUf!H58*nncBg=C^kH zMJE_Q;-P|-Q5S5Vmt-?g=z;4CsB^V4a2MEkqZy}W~k;GZfRpG6f9rS{09 zQzsh3d$}h}Ua{2KjVOlv#P_1~!}u`5HLj8=@20*mCD@Yr%sBQ7du-Q zckxzcRW?6~D_H0Q)YbE74uY~guIYv+kq;03perF6WK~c4WB*g_pf1G=i}3AxEj@3} zms3#mpf2W!;ZTdQUUN$OUXDHOX)Sm-$&5kpYzuOWeF{h;`WUeA87c4o?mX!>T@_vp z801v7D(*-UTtMC+10Y^9BPzTY?#tO;@;&OZSgGZ|%W)jY3j%gkN64p2ON6T2UogxL zUzbDl6wLz=&x+C+tytkVeR0mU)^_9j;Y0NN$=I+HCI|pl3e_`XS#7sDl);QkPg76K z=IJ`qnYOq*4!$!|OJJD0m_DFWBJ1hlL!cd{IEVGyJ$ZLGUWPGOb8|B3rq8thT;b;3&) z;}g1bwNesF!D=I#Iuvm$ol{Wdn%ac@bp`iCa9*LrF9%!9dLDc;0+HVNc$hZ~9CTlcp4W6DtC z_bu!8!%6S!mH(%9vPVl35AJ2z8>s9)W-_vn(GX=@kdy{<7U5*ahWWTX;V!R&6IIsG z{mE%g`@!^7i0uO*vEkh`6}i;C3>EL>>&#z?C&&Mqbr2P|%5K{8q+okxQ=2TPwtU^( zCE+!$=1qp(jarL<>)T*N3}{Kd9;Nv*3IKM$+a>NcEt0DX^G9YWly`M$u%@679boMO_j#f2+ z9A4B0wOU7r(O=Prya0Fxc9IT_kNJvbS77o$jSpnl8viNMTx#lf*ws!g6ZAoAbdXn< zj-@AC5fhqdz09>6i^Dt$8-nAv7BCGqR>MPJxQcV=J8k)^+bB#ti_+@!<@TwGluu3} z>GMMeV&ti_gqS}%6}vB)W^3Y95=>){UZO}$lX(H*<7HXP>&~hx9(K4b?R#O1w(3XJ zp8K6DLNE(Sa=S4PI$*h2#i@K-aUanvXdY{_n6@ z1Tn1T7oW-c-J)JGRM)j{$a@bhPOHV0lA&9UC<;4izqqpp{IIWQPvWzD@K8PU0WRA< z8YfO+&Rg<~BJ3atxUD+%&WYGk*5}}z4~Zo}AYg#;#cJfVL>i5G@>-uQkF>hRIk8K9 zl(8@mN1e_kPWd#C-Xe&(z)kSj7OVt(Bw{o$oMv%|0z1Hjx=JVpSm&`3O>987sfF#m z>CnFfZb;CB2P$xs_!dm2s>Oz}s}Z+XUC}>Ea{kv~x(LoBMSX)&$>p??4dj^sv}DuM zPM~Z!TsYO>3H=Nz&rJ&BbZY_MS&Z)h{{ac;E3~{ZdTdo|Qzcp_UDAYf=m8;$X8_UC zWZ=7o5?NIoC$5<2!U!eBLd-Ae)B<-ja?ZWBFie9`kth@Eat* zqa~ks{Ak`P#ZYe5NBZ{GGz*L~0#|#ST%f?6A)px^T_c<0EoHWHErAGaW&0+)VU%3c zP&>=|DhOYyE%0jXuqs=#Sp^sU0s^b4g+iJMJd(4l)JYpuTECfDEeUOtQ{E#!wgCSF z*(6#T@jukIiH}DMQ>aiB8L2iVR`C%upnee7YX{}Z7WTQe(pM0VZoO{pY|3ZZONjO} zPT(GBjVt0l$Td=ImF0uO8nuPSS8vm^1crDOIU3I*JYv5Hg?D6N1a(cu=EvnV^nVNJ z(*?Y+CRDmN`LqQ5vc15@QpijQr%xCqiG{r}YxE1bTJg!tHNR6z_`eCowzrJ0_o>7R z9fpv zFGU+@pR{`s@lM<+-cjOcgs4*nhOCl^Fm$Yiw(>}12qW;l65P160y@rI?bFrC+tSZe z#vB1``}XtPUq^;f2lL^+03sBWuoj$xm}7aAfr^uIJ#6~BW%I2wcvnK@L)rcBMpxf) zGSCW>JR^jyGnIESDSNsivVI8+CBJ$8dGWs^ECP@t!`)b;-;$cfW|iyjPV;!@Sj9B3 z0#$$yzjd_?{S8c+3mV;{mPH9D4b!p#lE-|yA5X*P5NbhM0W%P~y`063CDPz2VV__y z3E4#b@j5kSg9L+ZnJ=orn5^5~&=@i>_);YLe@td=#62+^Aw&`Vnt}w*9H-Y+cgU0< zgzrI?iIy*Lbij^tsET~)TWnKV>>&L{x1#bGjsf8%NHFPh)|9Q2mN*LNEh5{TcUb%3 zxdZ0AUgrn8vK3hoKkQosPEW%en!AFcD&JIz;eZ0_SQs{B;j)W`KVhN2h`f;Ja2*Ss zQ*POYT-j+9Dd8pNig+7*{h|L9f;RGuMIm(iAwmY;I(dTVHl1^4%aFe6s6shIGD`r)c`4w~k#mVII87P(|K#dU~H zOe};ZfQ^krO~1HekQHCV!Qd@ul`|bDJMKxyToCK&rf8>?zl)%8kiT!fa{dZ0obfrm z;8m2CNOu%t(SB1uU;BRpHgdkNSrMQB(+Aj4Cc6lsLpK!eKwB`=?#&>;{ygqEB)iTn zJWsx_UBOZPv)wd558ArDMk2jP!U43%8D(|WOLKvli^6(_CCPFv)LJftsC?>g5)PxE zjhy=Fj2XtlS2$&Dv(_S*GeSt0attC;dHUUyifOK+ry*z$r?(PyOaBy{!S`p zZ1ebBd38qaAQ&!QQ=_RuZ&@OA=|K3}ntSl+f-Me@>w29UD0V*X7aGED=CLVk6Z5`E zmFm|cDV#0g(E+*N(*PvsmoOdyJ?CcGK~zn#RMND=#7hO^(?Zt}TX{5bytdwk0jAEf zo%?I)tk|rtp)DBDp}5CvD=|xJ!MvCva_uBa_unfOb+nX~_8_<7P|H8V;#`?;W<)3* zA)r|1o-u~2rGg2z6IZ&V7C1JUzSdqfHrXE-dh6;)ROUML6Rx{)pJPsUd5G{vf( z!p+46BF5meYv4>+Fa%{20kFc|+0v3eGDM)$&QuPH1H1sz&L}H~ZT5 zb$8??${f(!%bAGkl2{q1L^qFEeiN#*(#$0-p9K_ijPVDV<@17vC#w^-=?%A=tCl^4 zqD`YV6|&1f4M)aGnhisk4-=U+KT`a?vKTrLVv>Sh-^*^#uR1u~{z@OshVj?}d}Y&t zyty?qb2n+_YO|eZJ1j82r)PcvglzM2FoembSQ+`PZ$ z1lpfE=x>jfuxN+%rk7YFBqH)klwN3Xq}F>6u7vd)&-+Q2uCL|AhQ5d`Bh%=Pm}(=Lc z{hg9BXb(l$_dr#zpd|ZI*9?!MEfRQy2E8n6-I)ZXBPuYO{d25^J?motf*9lY6?{0X5EBNdL`I^@BtgZtzXq6tWIiPs-GAiV@{ z5jHt{gjZldGoixbi=iY!Ku5{ndJUzhX9%g%c>KOz#$n-Cu(>=k5Jf29@ElrB4A_g- zUlDnl43;*in|84*PvTn6j7LwEda7IvUPzkrKe$GZ zn2p)1CAT2Gy9okLWEMrRX42jW8w*6;TDC#O2#Nc;t>g+uYn@tTUN3dwVU#DTsSH2mu1&N-!g)W-fHOOFJn7 zb0wSevMkHjG)4*rn0WiM4jyNDOeHla=652RC*e8Y4(NGuvHQ{r_>3*ZhkuSA+IgfB&{$&0T5W zWSZOH7AVK#6d}iB^-3_p+X3XoIGXxB5P?|QK*>0y1gIBB|m%p}U7h}4ScEb}Cf{@T1 z^-`Xl#)@=uohc~+mk5-jq1?yJ4><_?oqpCt0PDq2K$R zzy6j6!>2&(49QJ1mey{JV*rfQ4Vso!EiR!rjs^$&PU~KCEqf-kl&EY#o~?Jw= zAEmYGy~v2Ez>BpEhSUDh|ql+#vtJ#=>z+S@h|l18DG9BBAGxR zn8MX{0i7QN{+0^ER4`AUe}w5Mms@r%S;=UT{BskR1a7&PR#t*=K95Nxf8(XV$G|@| znn>xA533cqX4=-Brhq6iWbwQIe4nG8X=ww6&-FOan=l;i@8yNsQ?p^6J`o#A6%X~Hj}NE8ZqUrFURSymQ|k^k#hjes1gB|RAb_cf z2N8x z2Cup?!WOM=;Xv)p$X*m^ZcXZpQF|5eUPs#T76Zkv%iNPXzm$Zosgo)v*+KdhS#RQk zzq-|68ojmJZC~nXm#sf^`~B9j3~{zQ3e+Jq^uO)|r+1G!ZBm&1h8(JT$iU8`*@IL$ z`Y@SkaIlM=p6xNm8eH^ulT;@Jv4fFtYmg19_?t#NW9!@y>>`e(CciKCIjcd zZg$|kHk>K);jUQM&nMD$tmt?WF6%v7&YWGlV^{?H-prXQ-q)@wUTS~gGmil^iuma> zxu)j!-$}Syt(Oj?Rf{Lh-L79L+elGkmxkNwyk?kD8}B@V5XfA|7I^ozA^6M6nrpK6gj5OT!y)T78K zXn;CMe?wv4gQs2%FgOVXd|h{HkTI)CMJ|&N$UOitsV}G%WrRvpyGFzC5F1 zmT8mA?MaK zF87!1+?f$x5GcAtfdvnQ^bCCv*|Zcg!IoI>w}d|nyMZKO0C*i$@j|;sQpt$f}{ z00|vF29nK)I=3*4wLhjEioXPYDu5)Mywz{e=fNoioel8XTyCbp;#rRcik{FVSF;Fk zuL&SA47UdRo^Lp5R}d{+-`b}oab{s_f>Q~ztJ ziRv(#HMXtfmewfBz0G*tC)EAy47RIhy;upq#XZDkILbG@8EQ1#FW;yH!x3!oTmE9; zT$%8D5tksi*Cy)+nCk!qEQAM^e}D@Y%0+S;4-~UIvJ{ThZIhbVwiZY@zPoXWBU?8o zmCT+ixtjj)l@SYK4)qLC+tNBn7IdNlV6f=)-A^gg`e_OX*!8E)bDQfvU|g!U*DZqt zTGDK;(-~O!xf?OdOOD;Ab_>Ah2Ii~=XCd|Y+7q`FAHQFZ;J|MC(&`&Y)_Rj(7xPc& zk11KLGj0&eVDhI>lZIKxGI3}?mFY-m$oc}#$9O}D_C4~UbRuajw0&TiqLhl}m5XbT zz~5!eLtoTr;VSFbuALX7Y2QY%V;U9(H6lAgoLQ_wBPUE`^6ujmFDl_-v5MyxcvApt z`tq!GlLQ6fSMjFH*c6>ZFQSR{EA{9lU`)1gC2i4IlfPP2@p}A4M`~Zhv?52uSn=xb z=@oGq6L+d-@%7EBsHhY9+e$C>c8<7fYFwMyF%iW3-!@Ob1M5# zK?dM+=$o_BP9#=afr&l@p!TRjb1s?;IC`ioMgTPo_DLAPr_$O4x6=Ju4{HVpGfLEf zR9J3X*7(hxL~K#o_oN3{lWRFCu*^*_aRrm3q5FTx7qSb`w1}{G`b+d+Yj(;)FK&z- zX>FSzU@a+6LDrFMN?1Lp57=Ir+qv$LZ0~GEi#_1KE1-)kvcvnE?ZvQ+)ZZ|KMF2p9d zFsJw`oU114@%N2$NCwW?7=#mWv8zp0afO6${Z(CJbRqcEf#PY;t0HZaq5@2{lqUy}YJd|@0eRG7lO%+xZCW~tI4EQHOjl^FU7bTAknpT+|cJDTy5b!@L-~X{c z<%!vV54bKQJ_n?4a9=paw>7OyudNwTjgN_mx3WN6FEUnD!v@7Nd57`};Bd8w5cQG4 zCG`soNZ{MIo*i)sWDkh`vJomiASQH$mBZV3R1w9%-$T4Erb0GPUZOiLcc^$Ag`h5q zmswFNo;2StFjf=Cb8P8=31Az(`|S3O)A_tHOH|w$j2fM`XLiN(kJ4&|fzi`TB8FM6 zj=NTTAj%`LTEQJ%qxpKd^p%LQqtRR{c_k1OJ?Q;{z9HQfy8TjFoj)6C0zp>Yk5Z8u;6{sdb0qXZ|DTfpbETO z7qSirqnU?y4&JV24D>S^#!`}j`j7mH(qQ5(1Ec^_@Cy|MXaam|l2(B+fHfxmvf;EG z0_eoI@u5mJY6N=5(<2w~5qhPTDzC|rDvh&1Y?p#XL~;$!(zMh{?v ztLUA53ksL-GmwE)>xu9_8-z^`2*pX^RldM>RRG_R!~F+b0&gPOI+S}%=%2wrWnxAo z5eteTFRE^qd9nB&nFOp$hPp&`XG>a1Foox89ddk_Wdyl@PkzW>y>Nt?&*(xWhny!T5M2+!TQZ;PK7rY4P9qgTs7)i>Yiu5a51Ui! zCcn9RrE;uw;85ojcJ4xzeaf#(*{&U%luMxFkt;_BXuP;Z>b zcB1DyD-Y*B-g2cxM(jP0&WC1}s>>tx7Tt-qN&7o@B9wqU&1S6uv6T{+5A0i^!h@N* zr;u8__q6p&-w(w9q(&VX$O^>;7$gs4yrl5C$11SPw!KPHHm+0+?IcM1fh(lVzS5sJ zqK6L1hHM$NY-$DfKIQ4XY5#5jd1*P-C}S01gq32|=@mBqx-O?}gSy|lXr%+f1Ri^j zh?N<$5y~D)R-CUE9oMq>AZu=HoA=tRB3_iWr9^ul4bQM;zWl`X0S^4qUC5c*3@C3m zT13i>%d$V4z8PA>t#>X3yC>lwJ^n;|vyR)+_o9LOPplLkq84(907Ih?P3{)*(-)9Z z2?T`a7gXPI#gni#pzucnPj&MhNCq^BYGN}uFJBMW(o-4vSh|DKPj27Sa^}`K1d6V!=r1qj?Tsm-_Z6M z=H>8}?`R7|ZYcJ9eyT70D3)q?6hoKDDs=PWv&P3=Px#+5?^@I)G>Yne&Yh`1=xxsLm!T_b1Hr(Eky1wYy2a8U(Hx!BV4`Co81j+kyZ>#-hRl6 zR~;z3JArDRC_b(ymrUHq;kD8Xd;H5dB z%2VgYd}hJ9vDp=`X^Gy3_FSY4X)vguZ>+qGatz>^I@9g^%vVILF*Tq4uxA81K6Gr|;_?jdY_V~F321l!`{+)>8@fctg* zD!kFLTX^n>ne6#wP?-70<8%(vM?H!od073O>gKmxs1K^pY1x9=stecDXE4iXU7y#~ z3j`B+CgnY47j}o8;JqjFzqoz#2Me|tn&w9oO!)URDcp67G;BXknAR5HW8O0QUx}4? z-MxDq%uSTZQ*mUICIB$g_D}lya`D(>MHks0l;k+r;Zvjr(QRp-v}j;iv;D>#_kxki z+9Dl!o2J#}W${dgA4i=T4}GZNhzX=>pvnZP7G+WHPLV&yAUS`cj@pD#Ta z2a{Zu0m4X^|FEJGGr84{7B`zsgxu|-yRBW$blZGH`-VgnttFJ?-wi36Z>p!u+sAsU!-*dvTZk~#W@tQcwZL=jHgeuvSBjVE(d6!v=S9o>0Z#V# zgUM{KC;@R=wuIsokYS7(c0G|9J2C(u3MwH980z>xf~dPir@IvvlKXmjw$5>coeDIu zi<4ReOa6z>P0vM4W3(H#JP4@AJ9K{N%5}+&PMI)(Oh7m{c3!wy^C^Uxqa8)<%-n;o zU_Fdmu@6d|FBs;5{bZQX)P!wk?(2%IAYFGC%1imR9TzIzizK2Xz3ES;=4s)nL9yJM z{)R#0i7N+gQRWT4x;F|M9p%M(+q3c{IIQ`=(LLnkZc@Px2y)h0XPfF=WcTH|ua5&A_cU3_Ch>PS!jp`zP zA6IB(g{*3leetXN%u9!lg5E0BYrxwaE5QD4rUAG1b^HE} zHU?q4jVuK&aD(S07|>Fu?PB&FSm&RktE?oB4!e1IPp5kj1~5Yb4I_{Yv7<5nr+Im`x9wa~SGyY6Fm zn|o(WUeP)iJ)wT^pbi!=Ae>T!y`oISh_3Vm1CCQ4STy0j#;T0OM3sTQz@OkGH3Da~ zUTAGjEt=FIN-laPOaw_%zHa{Jg_Lj*Epf;3#Z-%}`8M&k+j9SHIs?;zuqJ zR_%T6G9yYEmg}3y#=cMLI&IQS#x&!&(Gp8Z|F=Wuo)UEzcSUREmfAwN``&{0VOILu z`S~X`h^=l=hRtbA4G~FCY>s$q$-M-`_2sLGO;!XGCJ)w|0G`&}wHOt~{598|wR_f8 zPfHgW`#4%YrI~>VZZ$TlPO`>(whHzCIuM9r*-WJ45+<7YWi=98B4-UGE3eyf}ZWiWo9XyI4-ck#rnkrEpy0-@$e6DWwhYpa} zULAU9@T6xl$LaN6KM3JYyUDmLBI^4Wi&A(e`oa zcDy^tlwH_o&3~5~YfyG2454HqyThMKyhvWTg#A2)u(rSt0a^i!`&OlZgeZ6ob)FllstJ?cYDrsM`&v4=~; zP!l!VZPdx5h22cu>R)Sn(>q+ua+_#!YO8~WGsfTk)`TY=i>_J@kQs0-W)kR1Y4Q_C zuC?Iye1_3;?HP*iK-E7l2%EoT^B~9BmIrPn;@uo-&YdvJY;HFiuyHE)>c?8}lKlG%&8cw|eq_8{ zObt9_)f=zX+S=XTi3oS zLV%++s#tokx)*Iz``Kwuf_PE^bN>5ZDJYz5FI~Tl*;Xl`2*(}+>;n8BLM9oW#^dGb{&&`91>b>$4(L5LEupf09UA5DebQx#B05# zh>RRpc4*@0tAyBneSL7BWE}k%*-PPo`TgU()W5J?8TZn5vtst+q0+jenHO z^(YE-^|QiQWGGN|nt;*$w6eCxq`r=e=x?fm*E$J2H4&CTsL()vzJa@RBXs?|$+6%( z%Sga}3=2SBtPajL54f(HvMpCr>+t$ zxtW8lGutBl|8~*>8bF<4-%U%S1X6_2k5RbfgeTJ4=WE!{K!Sunp@7e4|exbyKCO8qO*^Ynls5`I)L@7C92bgvy8mYBkEA5IoT{jhaO zu>v28BK7+a`x3y7Ix7jCHh87L(Vh|OUAvN=qDn!Rdd~=RA$2${2wDLL6xr4bMp3;F za5AsAXR_=YafCbhFJY*io44Jto$^8Ti-qrWd$f?+jV_E|R2aOH4E{OyC@3?0G(0>6 zFWc95Hz?xd44hD@45Z`@$rJBDjC*GQvp(s&jGeBs5NZg-pD}dC9DBt2$T6D4?Y!(AV>-h zkR~kHD0{1wJe6d3A-6g&G=b>1Hct1U1ye4f&Ded~G%P2oM8mIsj90&1^ycLOEt7WY zOdBWC)(|7ELr?leaKJ$%Y+_X@C+#I*s5oVm-a`UaP)x7Eb@>q8X}XvB4s}q@xFN7| zQ7nt%anb`<(w(VV_O(gHmC(#*bTP5}EKlksAN0;6#;any?NoF$YehAo+-`_3A$3}6Q#Bl6ncqLbxq&-lp|zjfEz{A>h-Sp7WNgX~G1 zf95}xW2rovaVj>!AR!?vux;@z&FRO*9Gumcil@{M?JBI4rY66M8b?tQ`I?mFul7XU z$g@%9%`s z&pCaI52w%I$4z+8Aj_HU=XG-2eWWS=dnW!o3pkvrpAbU*z_p>&%0o{PSyV z6j1K+&@`TC)2E9%Z)cZoE>fQ+2?*}c{)*-(g#YMl-0cvci1ouAhYHiU1;bhvHn3s| z)9N6k`i+^$XI9r+TBkoGI|=whBAKb(~VeuP3qDr({eCLWP?g~D~{QbGE;Nagp2NrWyhE$ zDmDT*N-3_;BZ|g`BV?-6@gH0^B1~n{oBsF9ba8KKxEUw)l*mJc)gZ=%sL5Z-bc|fD zOSoOPq2+`)vWD-5fMzCFYe;D&LSpUfTqVJfeJ+txwMetWAKxv~S>cfap1J(77(iW( zXjtuy#lWkEV~(XA2rOvRSlE(sqK3>%8+c6ZHw90-i(3lEJVaz@m@A>)H>xgI(iScC z`%d#*VlTQ_bUlKMb%lYSfER_Zv+wOg8*JaH8{fUv(%uW0vu?M6)0g1F_!6X`N*O;1 z1B%`PpFlB!n%NELvd2+`(%^2dl+2TpOR^?SB~Ji!U+24oO~X8QqVpT>ub)1R>lJUw zb^KTBl0wtIYnl$}@9mb|COri=&T=%XNlJ% z9DjSem3&o`?bj%wXu|P8>V`^2U6+E1mRoCnU-uk@KppG*LNQ@8JkW%aB}pciwM=a3 zOte|B7mQ4?@gO}s#UAu0Y1^D2h^jmSSMr*AGMBiU+t~Kn%nO6>hAk_KHb7VQyJ%7B z2-dEP{bLqa$y4xNo(;D^ugg~YgF+yyKcv+UwsPo5xy*E<9MohnGdF;_WJ>Vr-+DV7 zQzY!O^nT4R-*n3&LPq^_vCv2VK{DzrJ+9g)47Y3B)UsT< zuqPjwu@f&DoCl@}rv3+eu(1U#Cv$PvFTw(C(dV)rRVP0AEV!M3USfiVzKq(ZzifPY z%Mrv|UCA)sggYk4-4R=sK1LIfjt#Dmgr89yP%aDJmxoFir=J2+2}Z#>J$=xenby}t zOGQr=lR>*9in&`kfq{S|`L~auc2ciZKG52PQzo_YqFo~VEQp->; zP-OsZm>;t(2cOh&AKaxwN)G_n5n|{vqRe4JbOFt6AO>cjFqZbmgQ~3ND?xqgj_C3tI5oDCcUDdDAHxLH`9V@DpQ2GZdRX~6&IP0%GTH8R}qMgettj@ z?s7{JpTr{|R%gvWK!0Y%P2uwUF1G<^Z7y7@@3O)(*Z7gjCZC52M-UMg6a`Y*vg6AX zJkmaykRQ$#RK!wvNbT0g`a?i z2Ta8)u=OeD?Yyq1xjYXpJcLI7;yoYs$+eTeiMQ~{O0eGl(ctd!a8=Kqv1#*!!JEx& zX#%s(T3Z$CLm(feu1k;m+TYp=iJ~I5<3S*fMelAZ1#q=fr7K4;&V@OHRBtR3gs8t~E@ zp{hI3-ytvaFs1IU!7xa*Dn!ZwhYY20-?IT>Pt$QJ11OS zQ=F&%d@6guEzb62_N^3S=Buf=l*ij*J+*_kU1=kBmNTIsvuQHwl(ecSEqOIKM=V&v zOp*h3tP(t*!}6DJSuhPXba7qhB{mh!vHMDv#rBUj?)fmh;pbBp9RMq;jlr|{hYlRk z5O;oT5tU43?_tjxQKwFj!UVbkMeiz(>05r=-~f6h8+L;TU1RhbcJp|bH|-tJ7+;bX*JO6y=d=>SO5y$Ug&ZUNqh?)N zmQ!p~sFsE(Qr7GxG6FpF(*wHz5;y@MI5(l^qAHMuoV4Tq z1Z|EiC(FO-O~JvM|4K=z0(GRqC$%@*nQh-c_USU1bbl2gH&|cdMwJ;e{B|97=Ga3- zGMfFM_?FP*o%`eUgPyrh`;60Lirw@RBwdtO(BL%5DSo3r2V|CL%_V1)i*^~t`zp7T zh*CO%6^L)ou#IF8;>{A-I}!rF6Mlj+g=Y9h0_(eZ@+EHfr@M>&=+YZd2%%bvWk=Qgu9_|a>B{eBo201hV>y3>n zl)wKD)(#aWoxk++z#CO2Up9g-FPOrN-Zw7A3bM}P7t7{}CWt#|(cgj1?*bK@V-_@u zI41r^dbriM8SD^@-(5v>b%hjBBi>FUazV{RMsl1;<29n+hV$lTRj27BBjv!mrHpJk zJ4KImzC2B?n$GYy=P#NsHQ$20d`!DcpX5vyZdfTxWW!v55m^?UX{`2!|8ho z{XHN$o&AARW$7%QksYSnK*mJ8vqet}F2$g&M6;blnuy!_9pK;Ldb5iTq_|QtjN^M- zdDCbs2b0i?nIGG=zhRmbws8)ZBireHj`sdfas&@5-Wo(y7shZSVw&M_l(gp2o}5U7 zHN;TnU_z&G5{?VDUA8ev=*y0!&$BSaKAHKisd0Z;_ys_8L&O#Ws4O9suIu5N*34-; zwmEHH`xr2mnLuXPz)Aqc)*JHEK5qSAnlm^;MBE;j|m&V9DrzXEE)|m08(pHzfawP))jwI-vK&hq#KU!7~;_d4{6pkK}i;D5|N>3W5)vZ z&_Xev_ZP}c0d$W_MXFtjvjw~)Zz9-Q=t&?>u5-mK;DKz6J1pt2hdRv(Uac#6lWyXi z=&N+PbfxeiQ56ErQwSwFW8C4O0^hNQa)<7%)+4(DIEhxvFvY`$(mjvd>(*=iu)ccp}#N-4!GcT$1 z%A(pXx4@Ga5|~2S-QkXBj6(W}3q9FDUgEMKLYX<;jc@e&A4HBAXMK9-84;7tBwK_Y z@tZLr3O&wX>2&7`PME3l>3D0xym5v$%(2-FmkT-k7qaA20|{&y996cTzoVQ`jGhD>j_D-RZ=9v znb3oJKccWzJJs44RHlVqO46_!!4-t~kuS`svaD65J3tJ@THGEf1sV~W?=0PM%#Tij>06t1MRsLjApymwYHcfpNMF`SxZX* zl{4~jflsWvFdhh2s;xLoM|TA4miP~OY3X9@K;thB%*)bc9za7AN<0tVWn*t&k)xXZ z+wF|$i-rNCIExkV=%%#1>X2Utc_0=#(uGstpBpd@T>BCl=eMa#P>QHi)&22O*dtQ| z1jKf*j-VmS6p20gS}npz9)hL`z&6PDEKRl2&gmj+Y%_axqg(w)e{$^zoTZaXQfH!!v~Ix8<6Sf}~FU znH@(9R0y;l=RZ@voKqH9`s{Cfk}u`X!V0j+@o__4fP7RxC8krQ)Fi&xdm>b=un;AD zBH-K5nnrPY$MQWExi{h8uy%P#UMrcId8hA4LHQUV`|HAnMgrjd&oeqaB{hqfDQbcz zCJp(<68 z0uVcUR@s%KV*yG4pSZhR=aK+1H++HyYRX(zf=1>RCIrIZJ`vbNsDJR0KTeGmgn;Ai;{TsR?A|O@ALMI+HVMuBN;tGA3Ll>2wRu2Ercz(`rzmlxsSl zHOWdmHrac>b~)1j`M*g`=japDKt@@cEuVTh2|8er+4mi@5+lh9{w&P&#wyDm55ysS zOBMmoaF+tP2}p>QG`xBWLvTlgp?&EwrQYa2gEfJMYOuvs6!h$&3@((z^L%Ho$S%&m?Xos!?hY%ixF~-gKEV*C4x5KFdfhSf~8;DV?qM&Tz zccyDMb=7GxEQ6JNP#M9-2TsPpRM90Nc=mc2!GEREXDvVWu1*GFxSbrT0msn-ORmk} zbpVOh?Ym|Cw#t6I#kyHB&>HkT`;hAa5u)7AnuI-r56iz1dg_g6vyW)?$ z4IW*a?EB{dR4q|u54zU&Hj3^1@G%cl2t;kC&eY#3Z~FspDikvhwln5YU>TZ6y{PGj zV#iTbq#b2_`?8oN(q6XwFx%_Gs#<7^Wsoj{*RAwL37uHXpE>}Ur|nTu>pH+xT@Q~@ z!On7lc5Di)jMnc4^Rq}gW6i*Tj!;Zt)AbhtErlaaLVt;2*G3XzepGuS#4i)e((r>*IUllSR;etT@CWRm_SCiIqBp(B;0O!pc=E* z{|KTEIKufV%^Xh1tpTOZU|p~KR8BShp{3Np&* zp-dveL&oYHOxkZ-kN4oE4@ftvz&ff63y;o_=f)TQ!TkdD2k7#R;oA>Fb}oNi4R{1T%rB`8%<6H52{a>kvp7JNa)%m5=d85&H6&;pfuZ?1pM082j=*p zL&}WqAbb+fe2ZaDUcRhmvgw!M#jv{<7Ty(Aqh3HPQg@vo9aLEuU`^4^?9FHDccUC9 zTYtEHrj3sKQMB2Tr`gL#7z^Ce>JqZkVb*X9^rl^8*A}*o0k~~3eBRR3>Q^5uBwKqI zet`mGm#|fY`XSjB4y?FDeLAt)iJYz3)L~lNs5!AF$khqL&qrPG3fWRer>QzYU3PGW zYX4*mL8D5gytuGQ`bg{YjcE6CTy5Vl{E?QRIk)xr)QMs%z_u#Q(?BHi6=(JUl%gan zV#mpl25_EMiM$%Zo9Nl<@elsNOd!?f+&(k)H_%hY+khuvI<{j-HL|r1ExnH^*?+ORW)Uih3+rXJWZ=H-}S9+mn#Sj7eJ*>+B z&h*`mRU(Al`mTRQ5QTULzPQLAugJJ?PiJvOx1GDELi}PnqC;N5Gtt)z(jUmtOV;qcl&eU9W~*|?BTyky zCH9%`u&A8KXSbkKue$~y)Xg`ToX5ii@Io7xQ}M_Nf8`RdUA=}5wOS2=jtzHF=5f-_ z-PP|aC+sLUHeY&aN#~}Vcl|W$oku4u($6ZKOCe7*zYTQsSEzW5I`(p%6hCMsRuwId zNapgj2;iB@QDD*YXi4g3IIi?@Tga7^uVkHz-y|A{(CDZyiQRaXVhf7W?;-YUKaRbV zyl=-3vMl~r?Fj+&QvoQ)3OG0bhQZsA`iNef!rRwibDH(Ro6J)&HjGOIywpR4`g~e% zq12=d0^Vtur}OQM=s9FWYotfqE@(2p%#8U{I)QWCJ4}n?GRcL`Kk#+oD~}4YhM1M$ zZX5YOoVeCfL88(s^@%8RANoEdoN14dvvppV(LkM?jF$(##dI@~AfzBw2D>VcRvNAE(4l2y;k zEF;X-?N}fW%0s^_2$OFT6BlgS} zT`#9u&sp)7leKF3F&OqYhX7cO8)ok}i%^k*=M!u(7nSADVR7S?6_80|HcTrFY3E(w zB|9pX8srkUozVHt#)AG^H@y9riKL1;Au5l)&&iAj!H4^2wYmns34BVG^1Epi=-)s1 z4&Hi!k!65~G}tP|(o7l0b4d)R3!)A$mO{CD{=!mez?TJl`hieAj?F<{;BwBmn>8L6 zKm8S7P2J7ei#ym8(Hi`*`CnB`vUX$DA34-Kge8J_>KUmPm_0;j#G#2qr^UUBGQyUP z^Uj+HPT_vlTB&nM7$|^@8ZIN|(Tz}b$p(%88Mo_Jo8LCwH4umU;vWf-m!CyM0aoI# zZGAYFMA1n9HW(!k#o^roDk29hPZ;vQhxp(hIImb<7o?+HvLxW5wmWc3z|q(Xxx2h@ z)~K_J!*QSD=Ah7Kn|g|VaOw!aB;1W7KtqLqDbhxVa-$-f#4S(Q{x_+#5HvK&DC zr^TZXt4i!j5FYs~5W56`HHoE&AJ3R;rVg|dtvUnc!F7ElXvtvv0Gu?e{1n>VuFS9I zk5l`CAmsrJ;7|G76*(EST}#s=Fu`1q7E%^!hQfb_t#b(K-BJ=1ZQWn~rO5&eoNa*Zui`oQ}-u3$3FUnbdAGu-J zz}(U^q!X)ZJtS!$I~Ri^%U-}gl7=l9tE6P;Nm{G$i>9CR1wxe*h8+_9y&v;P*h%Z6 ztdvq{?!OXT6YWVe+Ih9NeE6BiGbn2#jSNFQaEK6~BRXa4sHS5;@4{KPZ0kluIz#R7w%$6HcF-@I$mQ-lIM(71ro7pCB} zO%2gX&_Hci&0vweO!cjzxB|J}<^m9u^HC|&BE)D-7t`(%X&}_qo(e`YkybIUE685R z&9s{$9=q)%wz*|HZd;ziXcfmNPLf_kUy3Bpa0SVENf+peBhsU11m|^TVi$My zzCgq-X{2a-{u?5KL^xuFKlti1d$| za>-O@X-ir#&&{-G+xiyQA4zl#Z>iO;b- zgC&G!__0$ez^tRKTosV9s`ReY$_GA~s{*%?5S*emrNlguPMD!2^lKc1O&MXu6W%k! z#)#U~3nYsSkApbo8Uh`ZAeNH9ZAa3&V?|wd<^4A)^??vBx=jUix}ztztLFTL*0aEa zrKC{iyh;1I-3U>!SAqR%&t z>Oe!^m8C)cg|AvUE$>J3g+zq|Q$OgCCujW43@o$q$WqbWWE6YPKOtyM+~FPjufPz% zFyivwD^+rkebl0R#s-ruOsDv92Y7zgqM)n*2=bI&I4@^eXu2rg*W8y!D^v=V;7k_h zXz7&BB*#k39MwRyOru`!UzM)lf2E~5_M*?&JxIVO02C<50TC&>j~)y3U_#<5WX1c%`(#zM{T} zSR8O?Br>zGCs5H9rk=QBmOtVNS3$=75#|Kp1segq5ng1Y7ejxOHdWkHMa3(Pu@%Ad z->{7C2@D1|MNqU2mjJ44ttq!2VoPObHH`-`)A@62S@r0&x!Vl3NT!AChT-vz6>o(qd}TTOV)i@}vT*P2VEqb5bou&qdf-lTqNp3VDWsk516h$zrT_+*hvkNrcM zq$A)hb<>F_SA%C9&iEQeFu{w2k%En0hOKflO^O0{~pH z5v#hS!P_>bu{kXU8M;S|QHKIuRd-<;oQ(J{dKiG3^-w;#gjqU>L#fxRb}cQ9gnI$# zGXrg_>189AtJF>`%~Y?i0J6C0#&g1^s+jGEPoZp-W$8LOAx}}h>arroHHBQ(Q8qmA zh=0R$3{ItJf3GgMay22@_upiqRfMZ<{9pV&(Vkv20;|fi%AKa8NnH5T!zfmjDcb$$ zLj<_Ar-m^A2U-z(R{R2MK=_2dzie9_Dqh6;a2&WeVqh9NbSC+v=Z}3;MBK7Aq2&(h z|D2BH<|-`pb=o}Q=KY$SVhg^`C?Q;%<0@Zji?iv9x_QthSknbmqWc%$5c8`18pLCc zm=5%Ie*h$XS?q+b8tMa#4c|dfpe%4gR0 zMNe_Qt$u9VKW@X~*zbNSv|F{JFn)N|=8lPzOLy(HED^J?7Y#>J(M!-5a5=*Jva$wV z*yLi*_3Q%^a3pr>vPJEbZky+fkRZc=*?mpFjIW=wM%0e#X`|*S+(oTf&+3E$MAoR+ zQEvDGeCJ0lHLZG7bjwm>Z4c)+KE|V*6O;R<%tORkAV@huOGjQWfo-dm5Ft4tcjpIA z?h|3CC^5fCS7)toBES~g9^d}f;>nPp#43|c5R9>a)Lr1;j1<#jf{U`fYe{gP0)N-G zDKuV1^;Gx-DDDcQL8eoAvj9ZBtAI3URjfi;-`*rsaOD<~2OWP3LExO~TjN${6u}2? z)+f`--($`e(EESnX(BO|ZB*~jI?MM;1& zhJ-XWT(lvwUqP%Dagk!HrW>qGIDYD zhGh+Xf2>?vD8~s8BDdwGH9v$a>n-x@$l0aIM0 z`63^KKx=MgUB3TSXTB4)zu%tcH@c=$j8d&BfvTPl&lW@4l5UNEYD6-4tp2B{G$VSz zC_e2~F1P_NX*D^;hn)N;c4W*&e5h^P7&tY!sMK`Yy_nSMxSH|w$yFGH89doD$6AHd z50^|5e4dPvd~K~34MtZIzIR7BsTiqqm>Tb1z*=v zY!uHamlQIL#uMeS@G1@DO(&PXOC=Y=#$?mwUQR^g;qLNr&%-&EXa_9@PKyb{sF2t1 z4H9Rj=7F0sp7S^ajMkr7DytTU@)a>$4n&QNxoNn%))^Kgh5_1v(m?T+3{PsEb^d#8 z!bO)tGRb3#P`0s6LU@NX&^B_ZacGIb8*UfQey6w1&SZAzg8Jptgx#3W3*Jl`xFpZ; z{q?sfa$iS;yXWEDUf^X~m9!SZ-Q`;iQ6c@`KKy_9(j30W`CvV53CfyU1&IMdIuyZ+ ze2|C`7$JG4lW)aj<=ysp29ma zK1~ySfSh>XKa8YjZJBuLB^J1-CP z`nfJ16QJ21f@!O{#ToeyYop;-b7Rz&x)F}6twv`3(LRGUtb)bk9!m}Wo^vT(!|el^ zLkb!SDhBtoVB$oIXt6x%1fPhIO84|-Eguiwn0l09!grNU*xuc^nt0yQ?cDy0D-{ozzS_ZK3gEHWW@RCB8e>3K|2aEIWaz!U>@iP``@FW7fB1 z)vGcKb$;#{3QFm5RQ@bj57EMEh!uy$A0;Hx8dn7sd$1j2_kGxW-h0j+i`8urp&x zo7IshOzulsc-`Nr;5#&CC`+@(DXa!+%UeKXaWb6u^Tvk`qA6^m#0Hnlw!)l|eh1wh zj%0UfQ!J`#5;eh~8ij@~nq9yp@;l#0f{g=SMhTwgfD2(pd0oXS2rS8^{o7Wj?aMmj zNI(4QHu6a+QRrYuyJXe0J?Z9IWgTRwJ+5SP_LKsLwZN{U(MdLIf+&^%+=|NYR}uo{ zl7{7cJ*ogx;k6B1MOw>`(>ctZbLF1gOVnzzzkX$H7r1cMK5*86()d4^WIZU1qQX2JYjAyton^={X+RJ#e5W>W&yt~E2@xb4Kpc}=Nl$7N2=>~rIYzT2r8$n zn7BK!%F5`>?I1vgPq_NG@P$I8XPCCvV7D#juB^*=jkRV4F2bK( z?euW+KYkA>ghfE^m)D1-ysk0rq>^rP^}H~lKN!;#Ur66H;$Voois#oqmOzkPRt`P2 z!#|ME2jMx9RbTX44>*}PsYmRIK8`-V=Dw6?)Lc5=_Vn2Lp^%HJ&cxKL#Z_@}@LWG* z8u;5rObwuLH2d*T!L0lCm!AGOKW^w5>y$nj^~u(PnxAj*po|Xq@1nwW#`sd*SF5|W ziLOGY$j#MwJ`s|u9o+INMxn}6JpA1bIT!yJQkzCU* zBAjzOEf+Uz2J>78Av&r{9f1EDx8Hp0M)M^iJyJyOl@j|-5X$wzwLR&JrzQAZr4Vt0g4%ghaJgfrI=;CaKXhq~cJS&7`-_8D{5#nNe$y9?Ugw0w z=|{M!=JR85%f~p#Dr4tZCmw)>Z8x%CjE(;cQwF4}%CYEd3mlpApp;!+8eg2q!kbc) z-nNjdX4D#J7v|M%;re4<2>?;XJdA=>c#}#q0yuz>tQK3oe&+NZ$zyQu8`%FT66-!a zt6=EB30d3ieaC&SjtwpCwlMOWL1^cY8Iz;1_uf{gDqm>9W5ynKc>Pbyv!-J_QGUf1 z`)3|dyqr<{v)86o1suN5-%upNpMW@y1NM`|kV2uU!V*WW^4z0h>Dw+Y_E%v*jcd@q zkUalFn-ZGR&@$iRV?L0Z! zQ`+--FnU&ZWyWnHOzEqJU4*A|A!d;LPGKv1{K#n;3sj`F2l4F+g{IL;QJ2^8rIGO< zoSslq@ilkV7F)daFWE!Ap=S&F!_)5ZZ{VZBXVC4_U`nIa(VukgClE-;A^*^=Z3p~Z z$F$5ZoS$w;gOQV=AzYiZl153@+8ga$a-(mm@QL5c>y8wpu2c9 zL7)~np5Dok6v+TJ?W4}g-cqm?NxQvV+w}`@J)+k1PuF?we51Ij(}LEdz)UCK305+{ z3%7J0s~)Z)kzXGo%`MO->%rHZdBd%qi%$B*zORVm>93l`F+n>Ur+T3nVQ2Xg>(zds zUWFE|us?W#Ip4gzO@3iK))Xy;JcCZ69O7vpaVH}q0)Ub(oFcNqa%2jmEgpTe)nm`S z9OdmxDTEQS-;?MBKT=IU`ih=Y*65L!e9|71NMfL9(b_S2yGeiW&)#kh7yCjZ}m>=7nI)1TfKsb-3XTnHxRb+?nOz!@m{ z&u39Ok6-b7@`TkBo0U6{x(i1_2ZeVRF_f-2c*0@gbj^IfZ z27yTf72qBO@%JH!ZIIiVq#ga`N;E+t`Y^D6SO~ip836I=^>gT^HswT0wl@KZ^VCqc z>aS;QKZ@Rk_7&yoi(M%9g|Hb*vPG{h9lNpil+-bG=BV$sVq|7c9;J(5YtBHaa0ib9 zY(3pf&|PXma~lwQxE>OM?2mR-kSMh0zqVy;OXv2cqK8oCv6hxeMD92I{ZOp!(@v_2 z;FjaT-?6-0On2-055mf^C9pg)s(Sc|wy3lXkaMW;b%zc;i@)nl0!DH2z!oT~)Gx6* zvb*kkw}>#AWEHb|v{Cjhj6HG3=4+{FX@h`lORnG3aI7QInEsJ2Fyw-CP+;zxH||$j z0h0Vtw^QaTTclH5bP~EByoYv!`O&|vdvOsrndO@VYDOV1Uu%>Ve|G){UC=P-rfbu2 z>=i{7Y~4(L3>Le4&@YFMvdfr5$efA?kXT-paA3|-E5B@(#Gs)@-oZI`Z}X0GE5^~R zB^UuyQ7x}fHWEKPRDz4zFKxwpsQGAvN*Z@LRQHss5u2%|Ye2QCPOoZG#^F_WPS&%9 zEJQl0ZmMklOiIN@u1f<_QZWvHEIGsPTP0so3J`4m4JCO1Fj%Xu;N z%Er<$P9FY&ABxv*}bS(|>fW>1-uQSt7%+b)oI-D~IFTiMO04eu<+4dlX zz_PG6w=B@OKWz&-d@#hRgfw3&U3V-zwwv9}?AnyyU_Dn4Vq{(vaHP687hkAPI9E}OJJXiz@uFjAs{EuJ)N5ZVGPJI0;Lu^EdP zQdAZFXb3qaY|l6n$Vaf4Pwj4aup&aFhFR3+u&xR{5g~mLPW%C9Hta?W$|V^m-k&!f zh5juU;J-|nTH9R5=;eT;bSbrx5APO4ELfWgSqGf98khv%F5X3Ay98|8jbL-4fhNo% zdY*aU_$(a!hjS8N%SSwIZ!jjPYl%v1VqtyCG?@5>;>gCHKS>Z#S!v@;q~N>KNevW= z^|}BnrYl=~!At|_DBJT$j4ly=4VF{`__uW)94GM9?8u5Jk%oPy;an=yFY@_DfV)Q4 z9%5T0SpT~sHy~d_DFE^#M*JfV4a=?=CJMzpL}Z#kzvtsl`fFu;5-4!#;U|HUB+I_a&%HOcspaw~C z?n!wtXg47zDYTCAgSK!l5K9~Xz2*UpF~XmGWko8J|AOyge*5I&8Z2{b_szLS6`ohP zupj3(!dRzC8))>Rax0UnOcxy*R@7cyUoWhZ&-;gliSfO<8!z@*YV7lTBkiG|8fO( z`c7ShoC*veL?|W{kq(7Ww);GeSggJu-OR_Ohm;v9mDFI*Y<{DNKG@#sP(9QZa~%Kf zZSQ-}U(*hg>9kwlAB1qn;WMOPXQzfG1aTIJ@C|m1JdWE5(3LQyiFVlItZbT*0+p>Y zQg9$&TeI1A8_bHVX7BrpVk9+O-xzwZi#(ctXffaZ5{fsp^MnM_3}Ru~@;8jv$z)9i z;}sWow99OPo<|f);uzHeQ#jSX^Jpa#vN4Mu*`}BBRLx`=1F+SqU8UcL8DrBBf&W`X zG=hT?mIB-rxDj(fNQAfDJ)+wZC*tb>fUWJikbEa#p4+Sb+ZTiBc z63`9et-$DqpI#t0@RcdO@{U09Z*A|&>8LbE$J94Jj~vN?Qk=t5fOq)0aL4ynGHZ^& zVB&0~56b!CGpO*AxDu~Vwa?TSp*8&RJ+iCcwie{6bdqp+%ZC;z&_yfSeg&=DwnuD& zZpU^Tp)a^q1@$kA>ceaXxo*>OS&e|uPqTU4qP(b`=>g5>9Hr{Q{y2D!VXjC{T|1#- zuzb#aevG9ELi=+4@=DI-t0P8KfX8IC0%^Nc6oqn7h*S7j|ilwC$OueKR8-vdkMtsGA`3eVnm|p}mI~)dL9HvcM>^yVjCMkQ) zDoj9ck3FDL!(YgwdbUa!=kkUnxx-m5=oO_hTkGA#Yq}_pW)-pR$lNm$f-3GnnUK-U zCNp@fiw5cI>@8S2r`}IDRUj|sAPBmJ{MY!S-uEw5T9^ZRLwr5=E?Ch>GCK6Zc&$9N zn|weS3mM1oTJ90E$9=-ZGg+AsQr*zAdZzQ|<;I7OXYh5Tz$4N@LTxJLK;*Xpr7@d) z4SCPS$cd3#tJC+8{b?OTB~wAIFsBf9%^6hGkDj)5;xJ>8{3#5sF#vXjACAT5Md)?T zTkBaSdWboH1~tt5J~o#do;(f3T*NQC!e--c5M{ZFI;;I-fm31DVdtEf+Mqy%3GfMf zS!gBG+Cbx!f7%!z*f+tr1*&KUA-W^hW+V(7;aCcf@tAo*m6C8RPcG6@u;HLIVg*+d zAF1dy$4Sg|qD4{(`%RQ#294@%sq}6A@mh$JkI z1_Nme3T;{Dvst}7BV?u(zt*q=cyH@XQ3Ci$FJKf; zGue4}w+-Xx-hAkPK+&1+A39zWLo^B_60ImX7-Y zSJD(r{~3w~2CRn+gVv-mP*rhwxJXt%dH~Z<20G@X;0M+zf4*(wRA44g$b+o;i%ksh zxSc7EblYU}xm!-TTJ;hXLo(?23WB5;Grw}5mvskNu_N~$*#vIX9P{^UyNOQzmUZK* ztB1JCf#+EtZAvV=G7%#rcs%e_d^UfvlGoL)i5uj$Jr#MXQBsuCABivmlPn;Vo~*Vu zB3>I##A>=dPq;qZUfZ;;$!cG_bBvqWfKwX6e?c7dhuA%o3BYj@JK~SB6d0pvqHMxh zx2$8z8^a^n-*MSnvtWk>hF2Nz0&2bmj2%a;@!TVLS<@9djxh?5e?tLw`n%#???@HZ z*R%QT-Bcls(MY5Vc=XbNQozOh;yqdg96H9W+NZVTn2~fn+K3#YM4Kn3oD6H+zRAi{ z%N}kb(RZ1SAgyO`8iAsuiV_Vb_M2-5mXDda(^kV#s^lqqf;hX33i^F9kjLoDvZ<6c z09i=yd7zl6abFdt9B19NT6NxmPNjL|H0IGSu4y#J3v&LAgdbbBUwfY6q9(#xAuK9@ z_#a63u<^9PdI~Ih;(mV!spS?F>O=!;!o{p1+hYRz^`$!p8H=eJo}X}CDmgmpkgi-1 zMfh5{kNRc+Uoshgh793r)^lIxGD^)(eRbKC9_@^4bhJcxL3xse5#wsE-%=Sas!(LG zp<6q%#P}Jw7E3Y(p0Gh}a-=MQCVBCTw<1j)#Hhul|D5G}+ZYMs;&b*nRK^o*?hfqs zTk5NMWK1A~n%LNlOu%gv?)W`nv-v4M3=BB57Ya*sXyrh_FCC{v!3x*i5B9V5^ezgN zO}?0+#deW7%?9SLy{-gFm(Y{!{POAZlhtEQ}y%6s!I?@!c1HoBZrf6M8w|fASL{cw!0>L&htV6SdCthrn zD$#$jSs{%;BeI;9aJAMB9X%b$MwoE}5!>8ylMET;Y54A(WsJr+;t#hX7ZS?lCAQCw zP&~S=RGhS;sBfAp4!&s&&}2z#i}rI)E-AOpbVE!HLzJr@H)4s>llC(FPTC#@0|$u6A2 z;QsFpubRB2ng3Ay67}Fg_p-Lzfu9l6Vg|dM;>CMHgm`|VuyVFSQs47PNau(tt;kcc z5cpQ^ztPanD5vgJ!-*OzX~zIIU(~dCb8HaArE7D~yfBB%eJbD`f6q*z8p-)uO{O(Q z6TCN-Ms2M~I9r*hF$x3`&k>L0!@d}M$6JdE-O6fCnlajX`4UF_9?_}Qp!cCrJ!V+p zbn`H@hq_@4l&m@DZPn!Yl%9Z-%;KqNA22r<#41rq%uAm{D%C;&ssZ*%2=Q|(^nQe$DRpwa_mu3zc?=}7oFOV-Sz?}NGbn>83BZ|2P>)s^Gqg|*;Uo7DwS%#9Isqy)f+4kbx%OQn~~m{cUP*IF|_ z%psd+3QH?rx=){hxslkK2humVeQpOfuOK)yT>uE3OxV2*xl(&9tu1^OGX2X%eJXQ@ zm7j_XWirw6AmCA7BEBA3dc|r(13ql540KAbDHY@+ODg0hN_fJk_D5T;@>!p1=t!IiZUQ8WW%g7NV=3ucTu@n*246TWx=q*7-w-Gge1OX=Xm!;+G z7G)(RyBRV|lA3n%9*G4qK*giWV;ST8Ra}(OX*9h8e(r?95BgmIp1}2^`92T@Jd1pA z#bIuJ7wUlbCr+SB^!2LRONXGA&;F_mBnrO0({g>E92(;Vx~r3^2l*Td?||Kt8BM%} zv=&>Q`XLdD(RKyh+B-olAJY(pU+x-0R7{3hyJcEpGZ2vk{PNyk&m@L|s;#-$ahyGe zEDcLr#f2F-CHOTy1j7Q{$4-)`CuHH2JU6QDJBNgk%cQf9xH$NFP55!R4E_s-S>0s0vv+Z&TLnhael9Ae4iuS^_7Z z6KnGz*B4D0+49E+Hda^_^+|o|&V}bSUKI5OgZ0T3@;-!30DeJoOcMdh%_Ci=;|uFN z;~=H7ZJyocUG<>IYd3d~mzsm;Q%M$n=vzfp0l4x1ar&;CW<1>_ZS?=ujne+Q26Tr} zn!;53wHo}7=>-W_wQx{l&%bhW=%ifSQqe(QFk~Yy`Kh~j7&rXN$*|Uuk8tUrcJWy0 zZ7>qm0484ijZ#PJgM4z7cXk*A9f~U%A*{Q@SD76_!MvB%a{;zm<@-VSk z!&$Chg%Eo`(LjZa7Wt-?S$c{r6Hh^SvZpe>{1bMw4)vY-X(B^yQ2UA;1%tiuapkPr z)jDiEm&J(KX$s5taAj&TgfF0P$#=F8@CZj$rLuh2MzrSHuzWg4^*>gu<#T5QgmuLo z;qr8Jwb{4rtMYE?0=5HeP&}844C(*d#!P63gUZIi1-c+4=|QOWz5KTBzJAYL$AMr} zN+|}gq;|CYej4>~S#AL~xxBeAuaid5(t29I?*zOV$nHam;QfQ>hXUvTnV?OGbIfF9 z3t@Z~y{*DL&7Rpuu7n3tqew)4G$%B3*Tk@s$`F6IX21Htq%%s+&*pVVl5(-c+KPek zy1P<3sU9bv{Hon#$ybjw(}KJiZtAsX${B=4BiZiqBZ`}SeS82Y5Fyo&NP6Z9mt+NNxOme4@o$9&Z`?McCg+9EoJRxf zcRv!9Au&t)z}hY^2%m43d;oI}Br3BEXgIfzexC=v9fhXvp41J#EKd*nc(wzca_5hQ zZjVFNUD$AN-9NKhT#Pyqx1!m{xX+6@d6>Qe1({j@U0v)-pN-JNXRFKbQ^@6@D zgjat};ZMkE{;@LGP&uh8rGnxwsmBUskGJtYF2ydU`41$-0mgNVT@_(yItm= zr=nNuBk5c94n$!+37SUn_pJ$?2!k5i)qD;LjV#ZtlxllF)D-Qsg6IN|=bai_h2%Da z-G6~WPRGBc`X+xd<#sKLF0Gas@^@M5t~68vUzhAP7_DX-BRUwzed+~t&^%VN!JAAt zbFrF|r}bU#kcMVB4?`LPz)ZI_X}-xHCxxwnS)<81U`PWTZGa(r`N>;zrzuiib>4Wb zWG#y~%>rB8jm>|sFDFNK+azN(!9tHF{knbRbolC}r%Y73mj_Y=huKOfOgvyB&^f$Z zu)%xC+}YjPbH%HqNC$Sptags3h5Xr4Cz!6>wQvKxOG+1EpqXI{rU$W5ne!#kLnQtjGL1);f^I3=^M8ii82LNX{`_ z1Y)Lu^?nOs2Ci9Zx}*)d)_47x3&9U8P?b?#v|4x$%P)z)E4zeK1J%QYpUncXU`;k+ zZ$HxPkD>R)r|mVA`ExL6BB=X~(6j3QW^Q#^xvK?dc2D^5XFH zkedB;!d8>MOsCn2Q~DBHZ`#jh55L1U3G&b+`k4)Gp{ihy(`uUn zHCg)5(7Z4bSdbG3{vcESWYy(YTH2FE(}y1#b|;|vd6OOGw2uHvp4ffjwbDQId57G; z?f-C)Y0lC^`xb`PqZO0>nNAm0bKl_)no8k0!qw(2*>#e{69E$6`j4jD;BoQ}I8HJA z3?2rDQZpJR$&EH8=xI&rzRH~9P0S=_EN?Y zMMs&9${{G)Hs9D#vy`pmuyKRbT4~JuLQ{i*P4KjaT(k`{Uug%{gG!I$V($IijW`wX zmU{F~$32!3|3)+ik~&&QMp&Q~d$m>W9DjGXEm@#O)=J;itT^O}R*1D;iZD8o!@b8- zD-3`!-ZdGep*#F9_9=KQ8$K(Udu%NeG$rwofGUK_(Ph0a)jMtK{)O1XI8I_Q#GSS5 zp;u!{^YmX`Ygi+@Py>eI)%I}%)NcL)Z6WFaS%dRNi^kKYkJdtdQe=eX9QgSVoyS(! zM91qTaSyy*sCX+6MO??wkfWa_)LIFQp4}EKFhZK~KcvnPQkTzBv28*5YiK#|_A$!X z|9n|dpQDRc*+kLL0#f}-jT#wSSETJrx;LiS@E_!IfXq z&vg>6#Fdr0hN0F_^vINB>$kp#2W5Z2VdDA}p5!2IC14ur4#Oh8M3>1?x2*f)gMYE1 z$Aw=`54JNxQVwsKQK;anPNBc?_AbKXd26bfqvh0)s>Yf_h5jhF6}Fbg=oo#g>sT;n z!OdF7@*F30Fdnld%A~MnXx1KsCP%3rBGkjacy$~&j`p+e_tB?S)C)T!nKq#366dZ^^gmnJ9>0X*5Cq9dCe{qes6M@OR zrSCRQNPK0}Vu;#mn0jx+i|sOJZOwCS1XAm~pnN?YvQCul{@7 zdzmjjV>B;yJ2p|!U5FVqs#VC!e=RdRm(u1>M! zXx;ZCZh5HjB+Oji!kZ?!J1DIvu`rSC9Ri`}y58?-55+myw+YVU^eH|FB9CR_@-R~s zbiTA3m#b9SuP{nEkQSJ)<$&tQ=-WZ}K0@%&@`caI(`=tZ9LRQUvgf&y(4 zHa`QChfI+C5bSdQDOS2DHGhn3oluMk5avi}>qB|>25mX-9`gZ%trUS)2t3KVt~dsh#hqC>l7TjyF{S1+vgA?(Me+NPi5CQVjQsCXzGbk&m)DmrQj)x{sM z#J(E({hWQae7Vvpcp zSX&Z*)11)zjeCN4+*oWCn})mW7-3Z$64G8f2u?tuK9!6%mGLHnz2jF7+<=reG#}E0 z5}V3gwg9qxAP0G`c;!OsJ&V6Ol>+;1+0Z=IBZYr0(8%kxaSMJls}!ewe_kT;d+VO2 ze#(_0DynuinjZxAu)Lbm5gOz(phC5O)iJVtCLtoI(!09yEgMa%SNw3Pu$1zS>aPV4 zPJnrR4Brn7Z?%I+o99Z>aFqmaWBFNVLtc504kZO-tKGXow z;Xw}N!7yoA4`;^@Nu#L7ykEHqXcJU!?9p&-vc;q<<&tbrmt`UoW4|(+UXClQcr2Kf z3L7=;OaSOQ5T5^Wvpr4qTDD{}@gO*MC7~Y9wZ6U*o*)K5Oe4T&84MR9sf=4ouER;`~)LHuN$Odz|~f zOLaud2v#-`8-chHtN#*c)waISUA3QxIwWlM{smaE6WlSE@T&f5M8QXo?rVE;te>w^ zG(E}tvveL|lPags83YgVn&>#+lTt%?Fq3Ytrg#hG=1vcH3vYNs;Dgh6;0LZwO0t&T zMc~d@H>}~HcW?g*4dVj=e4p+zb67%7XawU*mE_vwJVa+tn?Jr26_+n`LGVMtZXP*D zL%WyWa(EFC@$FlIavfFuzmc`=m;GKeuKB;d#tNR=>jPyLz}$80ZSOENdo& zQaWs_BdjFhc2Na*4upM5Fr};a%38+Yc8q0(P^IPpk4$^}d-Wz%|6>mxIhsr}jmxB_ zd<;}1{{+hbOnWxsRe;J+h$Zitx7NFm@U;y8uc5`jGERVpq@6klQs|63P=R`sUXn6x ztQL#32`)I!ezKyBJjhYHRbj9wUIUE#uiAY(HO8FI2K(n6OdAYm!!?CE%v0CJM=z5O)B9V#%d&TMx>B9hyDE13l8+0TdSW!NpB=8_Ztt$7G|gUv}TDt0b!_gm^Q ze#2|u)$Nae#76jnMyrbk?S;Edt--(8IJ7QFYvSi4it~WpUvXgnHkyrsO*ttKy7F~X zlKb}bR?6~Rz2wj;Imn_gEG9{#Oe-#YuAh3Z9rFb!gu$Wcn2_@~*2ny<=Qm)5F-x9` z92Y&M^~VkCK>u><1SvVi!0uTU1U(Um&5XXPMsKN+fXQR7a0WvUc;tbcA$Iw&G$RQF z0`Si0UsC?$PtDSLvRyqQAN0}67ue&ZE{Od5?;4#k8{xcFk-BcX_URI;?^M1YXCp<_ zKt5drGCzju@gA~(%TWGA`tk$QfqP+t44mK^qdz0?+&59?YrYsH&wb|7D>_^1&0E+F zkx2BLYh9=V7KhSf-Vs^el$S+md{rh4w&9b&{JnSW0?l{JTrKX+vjE; zNaVv&V5Y-vC<|g(?v!2pzm|dPVYPD{mGgNh^U>1T7OaJQ&-7}75OAMQ-oCMKZ9p&m zGPLvgHql2GeMW0wY*PORAj0>l3_6QH{CUE9T{ztQlyJq`)k0Jevf2xw+9^+{@N`Om#xDBITnoyQ5y3Dw~bY9w^(suADNlzuCfQ)`9)iF9U6y z!gn8^+zHWrKMSsSGB`;Lm{&oMV>>j$WM9G)*}qFsq$(@Ltal}m&Y{#4ryEo_(+D#N-l%=A7V%CS0!^Fb*Loi)BI z=7{22UxCzzm3GL3>{~Is%>^bFtFl~IavPl~3vFACBRL z0&UU{V(rB;2E}#HUvt=m`MhotG0Iwd@TFH1NP+(4Kb*WV2qDC`M)PNxKVuJ9iN-Zg zEi;ioAdR9Y?N*mKjE-I=V+2KXKkEMt#}~jR4{;Q zOjrkv%QL15Y#~C37}Yg6VK9lLkvrw+)qO36frq{%vt@wK82`XmpZ44~DWnm9ZscIK zNn%x!<}qqNe$mKWpS&AUUQ`2(u#AlH!Zn3hALSOCq-J~(Ku%~Ne$vw_fz?-XJMjhc zuT~o@N623fhNlLz{UkiB7&f-W^g0fe$ThYd6Yh_5MkQSMCX;a$S5l#o)IWj#x|Tg$ zD!m1s3+G&`kK5;u^S=|P@Mz;tF7pBtDmw(_h(T(y2{v_ zx`Rh#AmZNvpY68Rp(6@+R`V^m zr(0dA(h<#$B*>_xeX_v3PW)SQvWRdohYnA9=81!IF0tz$!TPbO!IOLhpp}?-)XPWv z4vEu&tk$hrZh&{oy?_^CMq2c+1?~whTAFHlzRytSVLZVa($`~Z8Q{}rGAMks%Ax+V zi$}^h%P_hYMHpku#OfX=7{^P}P$B;&Wd})4{{flS;<-9IW_!}`UXQhNI4~#ePXQ*d zMV2ylA_<4cAD!dWM`8G-QA_5clr?N2K&#z}Y|I*LlLSjEDzIqjZCNv!skbn+3<7wR^9V{1q`95d%+{~`iMm}p3KYDJkHk_ z@I3c@<8nv{(NL{|jQHg5NQDwIPfaUCqH6?8LZSdSH^JE4Y+-L=8Q&u}z>J%z-^rZq zPh}F*`+404j6iai{+P#}ZkcT~o7io)N%^0Ax3$ZLSt#Q=Cd}aE8RdRqBWCfl<~%nu$YBp8JW?ff zBNIt%o|4e)!^v1|^|X>R*%6r_0I{bXM&HzpR(t;b%y5$l3yBJZ>YfHuuE{E}Y@WO? z!7zDu$!ASD>sU#2^PBI|hjtjj^267SMD}H2rs6XBOUv6?#$?LZfnv&mHZ%EzFZJ;& zk2jmK=zAart;q!)q$#Yp@Ur8fMh_+N{}1|0;UchCVgDCjqF{PvyVY zxm1XKJ`&76C;nw9EgHE`4T-b9NPwF(vDhAvSR~j1H!&6}3~3r&%_L?3k--P16(8O( zYz4K|`LIV%zPpo?v@3t6a@%5={V>u7Nd|j-ppWPJ;YVUxTKU-=uh{ye?&JRLS(sNn z^d>cy@Z{ZKpYdh=pT zy2?`33YTv)Qcv~iM@3kq1cM#ElX(s`d53^jn7Np+MN1y?LBz)KH_)DR=FH-ea8IBQ z!ItqRVH5ustyb#S4u>)W?9BsNfvIh{?s=9i0&xkPH9c}Rpt<WmF z38r41^D3<&7^xc5YOa{tojj~Rx}QRL1J)KoC{|E@DayrSzY8I1XGE%$W#IZch1o&j zD^s{59%?0gCu`!4s3;j#Ns1^JsN!QVfp}nWn9Avzgo(+8bL9B)i{KcM@1Ec7-{XX2u2IB(*Z`C8?J3*y? zl~!v@7SfUL)^La~#m)KD^X5JXTz#Ihl82gv?~NDW(+*{XcRiYwz2ZC-RZt%I?r`p( z)^?z6IRmPo2!>XY+>KPEhB~cP+y!t;4_Zg+f@zzcFlgRJvYqcPg+FeERv=Flgr~9qeFW|FW$#&)^w)yw|CJn|h=#>}m-#u1c zM#mV?|LC!F7asusUIP;m0l&2VS(y4g($6B@HoKxxv^%+K7n^|i?_@t+Iu`zZ={^A z2~6hVzz~88rZuseQBN@Anb`3tAaCq;23m1D8-z2we4|}dn%rX zcHd`G9!akp`pBh~G*5Vvp+@Q*!_29g)c){qf`9~cWuN3X4R-~4wv&uY*__gU(yWt` zu5)`d+!C5r)4;^=gj>qlJ@K6vTKpSA$O;uPQWZ^%MmCET=H0p#ad>I7E>I6JOSP2dU2QouXtL?6#W|N$uJ~cWM$Kjbl|VcyFElZF8=9 z;-=jG3#yVtF>Si~Mnm2FsaE$f!a<7!$aj2pjwR`W2N;oFtP>O zAH;|R&_L0sVI_cV>mEAY(_$V{6LD{DMtX4i;ga?7KMx0J{3V3AnUlRk*HW`IoPXO1 zq#w&JAQU;rcv8zlUUuk@aU!SrSwt$J89c;*gEohIZe@9c(jj(tiZMnJN+6# z4K^2rFHb2H6%Y+DpvDCTb2dRz9edVyQ;Whcrdq#~ zodjiF$eH-b#>J)gE!_fdrd{Ze41g_Qb%;l0md`(%5OoYd$`LX7YK9P|y>luTVTOos zwwFVb@10%8N7U^yqb;@6CEaCOOCg*QJ5;Eb!^o)YJJtiexTD@BRbOSYPItZyDcJm0 z;>N#`WQ>=XIkKLb;k$WeKVwi>NAKmE7sr;2~g|5N{a`1Tiy0{o)C;Q#sl*VHHOzw`fWz6BC5 zkd3k3;u*{^w|St6P_=}iAQ{fEMB>bz;G7d(^V)ah0ekmPtjPZOR^=?s|j4C6MeX-jhVe*YfKG0}ZU1%!ET1ARzO*8pYwX209Vh12s zM-RxqWB82860y0*TIYBW`*&d@(>T#jgB=6esfBxIMX9AKXq{VT4>})dG5H49c*B_=C9&|Qtl3i#*kVXb13r~X+;Bnn(Lj9k z$JWGa`pgR%Wj|uC>yl~urpTZkZ}tYMFDU}8pxeF^ukNO*Kj?3E%wODAk>qnd3iv7D zYaK7uo>5u*$dg|eyX_0p+e&l?%iaVqL#QSWPsfxtn)~cYV1Z=~kcQwYJe^>>rnI_k zfwFq&B<=}OEmYhH zH$X>Ukb1A6562_i-Wv{OR$u(*C0IDfMqh*qeFk6nq8dx_&EQ5gai&bcjyuvNy+ro| zPcD857MLd9xLZ|V#jC+|RVi2P+cUg+)Tt>Pi&RXs%GRyY$pfx)oxJ&pFDj@29#*1n z);xAm^;`E_928hK)BNgHIb@-3YA`NtC8f5XUw;k7Isa4R!`?=KiFPEx0^ZZwB4} z9;`X~E-i)hQ1G1(8dmY*^w|#cQg9#bHFkQIGML)Oz~?owu`Fa<%Wk{H z*k^1*3mMcVzC1vX&^qt9hjY(i=O0_*)5`nFek4>`WZ!*MZZLmRQzxJysoNpYVUYYQ}c8+PtsQ{i)d7TQFUaxVd$7#h{X5Bg-z ztqp^q^9;}oTWGEJz_9Ix*e~4zy^1Cvo*mMnuc+gXZ<{>=QbHb*R8nPC&j6lsX6w1s zO0wsFo6F+lR_A>74|ZmwvavGK)BB=5bny)#8{utI7FyX?_p+6k`|!-2FJ+&SB4A4Z zfhzLCk9kBiPKd9Gt{({p;A=!4V|j{^h)%&T2v0clK%-AYC&Yv=LL?8l4W zGJA^a#8&N`$?;cT;ELy^2RPvUh<;f3&E=_Y;}Wu(ahj#f6?vVF{hQcPU#IP)#!lJM z-kI_V_#k0LQR26Qu7q;t9E8jtws2stV-qqsN4o8jq42hfLUDYzYfrFbynlC4ESAtm z=Y~NNJI=T8Nb~_g2u3}WFP$WV+dyZ3xH`@dCq-}>A#-*67<3t+pxWviv)2lydX*mmRh`M}SCyR?4@?{Y2lzB<-*9ksMbo z{2H$BPrm@w-CRn#?br40$wTv7SbF@Uajo3RjM5u`bbCFl6W#|$c5yBjrnf3L?w`54 z_Thd<&LUF~ryCNlf$5M9+{Lb;-g??K}*ydqd&M=A?m%M^npRRHMM7kDbQGC)z{zoV%?t(17g+}A_j%0#sh1IB{1;D~vgRp?4G zU(#?Tpiu^Wx87Tw+B92pzFRSSOtl5*r0YK&-K4i=|Ek&_#Bcvn90;m*G#I5$)^8xb z*D@N=qQuHRQtCs=PxI$#t5n7aC7jRiaYg(YJfN~dk_}LG7tG(y&G;$e(V;+BD6l#L zRwx{}?_<}lxQ|_ZR-t+nVOP#{FAV*>e(rG)2H0H$hK}!s`Zso@z9n%o)V({!hUgaTh9G+ruN}JX4 zhr*FNx2VlGO(X)QRLMDZapSUvHAjP!S|F3s9A<6Q=Ji)>^R=Os^m5dXiMCX+y^>Xk zm_GUtvq+xZf$>THNoD{_FbA*&oy_=SHL>)9Cxs-hgV%a39Ub(e)h{^Qo(xU_UT9f*Ms&MFlQR9F>2UpTbSR+npIv5cq zqu3K#I>8*Kb;J15Tayo3$NK_2 z3F{MsHA9dWXry^+jQKoTXGocwj-4bkqG=<`7d?n zQdrlvgvDFGQb!C2FeopOI)4vRwimVp6+JgzPkzAnB}d|zLJb3UA?J)%M2-993u}7C zlChD0Uh<%Jq&HQKz=XJr>P{y&iM`=ql-~7zxfD3|t8BnvHmi`hzt>9IvO6m`434>p z|5S_BVy-TIOL4=-VhWO0gl#@|LzV|2;*Mix4ruJZMwqIUH}^ z5(|*#HmB}%=hrhcsRD14VIZ>@kTa6U6twQ+Ia#1zpxYT9nVXw0>G$bj%>klTgF<|2#esj zfVD{oV_WGH=bDNII3UrL=1>(?EVn3{)X#VQ%pLA4ImW15#RXEFRuf0vQ&hE9G09@N zK}w;&v@*-Lr2PT+(b{l~R~e(NVogdVzh-OhWdERL+hjDK2ny@=r2EwM^i&-qIQ^a?VOGW%V{(_MRDP$k^pu!? zIN227I6Wn-DQ+`52=tQ6Btt~^^jKfL((DJU+*d>>_ZGI0Mr*TO~K_xC|)vUZ<=~2?%*@ zH6_&(Tg>>2&a7WGCk7WF%jjG-l)_vuSW`V64%PHTWqLAEcLV1-Z8BG@-XlgPquT?< zWa92C1y;EXvIUo%9*fenDv{1g4#bU{gYbg#d#n|r-n*p=8yKv9sH4w8UQjv?hDlsCjO=oUjwoW8%iL^Nn&q|U#_ zT_ru)gA$eN=|rc)WPQH%n1Wy0>?cC#6RPC>_KjlTTh_XTrQ`oRpv9TF0T$=PG)7Wg z=tq?OhJgP=xZvC2)9mi#k7#epC=8tQf%n$+wit~`_41UCQSp1MDTm=$`+XF%i>zUC zay@^+$1)`e{3eEkn50R&lI_vUfsu>2OtEBqjuBckJFO6O$q6oLju#uNTjBcNZuF+h zr@Oj^5jI0JK8Bd4cD3d1gPacPi&N^;rJ!ioxxtABoQ?(rd(8s*wUE7LaGs+cUS*&> zL5ZUAer4j>90J@Y!hNMb=jfKyN#c2huyj2$v7$*2u_-9KNX+6Yby>rK5beOi&OGbA z+dCrzu;x6*3MyZGDO$k}^tO<~6cr}U;>(A^@Z%lXd;W%fe=~!in5SskKHZjBUAyO30&wMhbTpc zc__FZlof<*c?PBSnEXzq(L39rZwDvF-*;%Lh|}|`?0G6=0v(!eZra+wYJX0y=f<%6 zK4lLEl&6VY!aI}TO1!+myapZMxlSSl{;MK7U%tL=x({hkD@>s1L28YVlYvycOyk2O z#avcb2Vf;D?r5~XK6adJI@Fh zh6{*3FY;^INXUU;;NtNDBNkD)A2)^i!q_?L*=Q^Xp04j2Y~0IKAfN z#A>D~w>!Fal3n3?`+j~AVUGnjBcYZl)WfDLm~+0im1_EwqF`d4i$!*sPY%I_p7%&$ z|262<3WQqP7E@P2_Y+g?d7t zcQFN$(6gl_X|yVgDL}1E87Ywvd)Ufp(S+BCEi0}*hYW%c|IEw$SIH`EanJj{p8g-l zae_0~m1{wM*ol?Qkzs$rdv$IYb1VEbF;L7ld57goAD>AVh+9oLOT{dD6|-*PkOXnH zItz%rR_$73Wmj}Xw=gQwK82o*xvjXmZ(E}=waqP)9d&#*v(OWS0@_)8`D9#`Q39P! zB#1$mU{H9csvbdU74Cu4V_!2Q>gVpah!(6hl5Jwo>*d#UT>xH#^1mtW!x_)a7q2y} z?))#bfOv>dH|h9$I5IvQ?`s`Nj_RWCGCKZ?mV(NH1YyVDAtv z-myvsg2cUErBMPZ7n1)RXR*L`^d|BQ#%m#M^(fP zPolM1pL++0vdVfRB|s13ObY;=a1p|I+YAFcj^{wOh3<-=iKGJ(H*ic{8sjMM(_H>f z*J8K8U#$1=3qW7L4zkzv;DH&>HORAZ7HC6mfjTnJtP?z1;u}^F&G{b_Am~ajY~h;> zs_Ql3@!;5{AX}=k--WWq{+N>?w$p?T^}kooO0Sz8e+Ri1P++mr4k=~duvxLzfv>8) ze-r9883X^@)U@E6nq%!H4w%{P+2K2IcYVaQH_>Fxl$ZB+Pw(C1q43nOGu*wjpFVQd z5FCc=s35%y$DGTFoYq!vPeN4Tt+21QsaMwX4Q=OiTHMpPzf={9wEofv{TfP4ejd6s z?Qdvj_+B*G8h)Hf9`2hOJJ_c=o;9(z~HBy`Vxagb@zb2Z1J+ zxlPG!S54vI-!M8K8IP-;oIVE8-1B{V&2^~Ngz`-_@_~eLddSB(;c)L>tgv;paobhc z^coYPnVzjG6he4fylYy1YCBauuX=F%g5qSXKQ5v|Zf&PUcj#IIUB@qU=fZxSvpoger&wZgl<#1Ar66Z@ zsk)_Haa5K%GRR@eBmXzXIMnAcwUm}BxM~lu%B2Li#c0^U7%0@7K~nY*gW{_uo?|`k zxQC4~!L&BtuBDCfppM7hdXd+>uuj3rvu|m-E{Zl~V@-{pv$Z zY-(WGU|78Ar%o+UlS=k}K1tW2@Y_t#;*p^u6s4|7g7iYv&cPG)+9#E>Bje>xYY>?a zC+erKhhA7k9yv3^gy)xDgrcjRx{xS-=owNOSV}RlCxceBy!usvAiI?uvS8V;92{#R zt+luS+`u670P5C&1Fi?(%C?Duod=4*KJb@e;}BJLUZ@rmmE7mTjl2>t#?99#8vXjl zytGP#J3v=0Od_6!g*NE>$kA+ZYiY4OrJPnijqhc*gO}*-Kimy4@U2rlyz6y;9;2jK zt)Q}^f-L*qn`YeHAB4|u2CNPYQ?-efLKChlEcdH?GPBHE&9^Uvx%*N~IGHAgrQJ$A z&(a`FXmm?X%1m?2EHq$!@b@or?6PLMAkxVrkMmq#dM4mRjZPR@s-P>iejWf(EUU;- z1=1WE2^jhBjVI3&xZDsbY0yfplyQDv1ufu&XnrS_t+7}qr-y)PsD|L6pWiA+u$t{e zo$+E^!aC0)Y4bk&T|RDo#}8ts8yX%JH|IWLA}8hO`9{toH8H;WcCMfdz-e{wQy=Vx zY*$>|A$8AlT)i6w-rC8vbdC))Dz?c%lC;OBmH^&|euJ0h1Nd0rlR!^^00!Jz7jCLFA~s5GCX_ zEo1>0PtXw_-kVLDKe5+rNwBZcyWIL^vCn$VqDmkKZ~mK%-ExJ@CtYcdnq!`J0-4SV z8Bd_iC}7Ay3f-6l=U~@CM>L{;CN=~qzGM}W@9@Ang;fG%mXB*F+{#me=88jPP^%OK zQ1`X}MumYW5jj2W@u4@U$Vfe%=%_$MKu#|CkMyGxYb3AoZ%IAM&yeZ#$c6n*we zhl%N_rZ|$d4bN0`%=p_FkB-EGv^$lOrF2bW%S4p$h_=1#FBAZCf_GDr>ajKWkWGEk zVoCH4BL!trESL^W{5VCKtl)f9A0$5-wtA0AG6>tPY!+)Bl()KH^+43g{l^nn3S?*w8;x~Iv17!w_c|pG<87t*ntY-PWnH;DzcBl+gcj%CoSAed1l)B2{3EOYpH-snRM&L)SDwwM(uVP$2 zb{s^KmMc8-fc9+3^*)q4uz%xk4rouif)T5ld6fuamx0l&5>5$Vy}^u^Jj26@P=1Ja z>=R$|Gx-sW=i=#Q?(9X-EevO5-|5lY0v*Z!}AG+%$mAe_27WYL5j zJ;efGq9moBsFIEp;74RmulD zHJ4wrAa}wnDLWVoKPqJ$D&nyAxHHyu^l5Env-p=AoO=epLgZiD!q1+Dx8eMt9ueC? zfG$na94^Z@E|%TJvUHG-B;sq-K+S@1bouj(fm_N00l^gBsJPDrHvyB)ByTVFe^D&) ze{yNDlw&2XM-=vF{yE_6#uK4)>wHb$noy_n!QT0sw0kq^2G-9OLJ13!Odd-gb#@qg z>%RwRV_mA%utTEz$=_BPSrgG0;@NFUC$!WV3{cr=1i@1Y=9Q?#-7?W^pt*DweV@$( znEOA5w}MDDOSJW02Js+9lY%L@(ji%pUXsEE9UO`G$i>6V)wI?gm{b=gZ|ys00IN846!J;>QOHy51RpUKuyi- zowsS24IqM3HvNB|v#B_Q0&RshMu{z(1VbBu0NL_yn&+}haH;eEY4LPIw}lfraF5h> zMJuLa?8S!gK!t7Z)j`KlqQE@0sbFCrN!~n%c8u%lviZqRvl(_c%F=n3Eh-Ws36%wt zd2ULJ&&XyMSXm$lJo1z-i44XOD=Ocnwl#FdbV*V?ZUs{(4ZBYf=DicLJua9Xb!*H7s5O}Uq zgIgB^SrjF@(z77|791R5KKNJF?-p0J~t<5!w0Z> znSECIF5vhpXK+K9SC2y}DT>^7CDOgpi5?%~Nqc0^k$@Y|J9e9-7%<66@7XKw>_9A3 zU9J0AiQdubdfl$x#JFCx8mUT=F-i@-e)d?N)w-(KR8{q{qdM~}s5@!#Si1T(MD0E^ zIAbD9nCqOJvp{V!!)x6KahA_1nt zOMs@`skJfd4A9aEa*T^c6ua0vpI-9?%k>b;YcYFWua%iYE#n81b!^?kh$-kPynE`1 zI_wP0vW6BXN#tHz!RhS0sU1Z32&>-l_Q7Hm%{Sub|I9tnO2bIPVucA$0%wtY1=PZ; z;J9jpU3T-FfKsV){-mroBU;E2icv%sp~|4)>0;xHo*ZT_j$<2mS#QyfO+2bsXISY^ zu@1!jAuOJ;cS9VLgxj4SZvwjBEX(s_9RiONO~uuYoiG^hq^zQ=^Z+jiX-OK!nt+_- zFh8%%N3roQHwTP-=(hO;%)WV1ZYOq%dLm%S;P>rIxim=j9awQnsP%^|XnKQ(O&>Hd z7Yf;*%n(`WXvo2{mK<@gGWA5Hp8`~2-PUzq#&{vV!Y^>mxr!|C6<>g zf8&yi*Gv&rL`j}+^W_dktSJDx(|Mva>q^VF^V*8EZl8X1&V+wb_3SaC!?>18Q*vu1 zMEr?$@!3pDzbhh>{Tf{SzM*8mCwm#TUTbXxi@okTHUCeEhAH@zlM8X`K}-w6HAjr) zpg*PViMSXn(tQ>0#RJyfQ)qTUgUrB37!($|RA%qwOk7#f6G0q-JSvo4Y#_G~5Wn4Z z&cHl+4R=OLQ0Sn%&xOScjRozapjbXJ2x*YGdGkeihcp)L9QJtPQEWXbf7PPHW=js- zJ+e|KL8gRTr|BwSBps%RkVo%f07oaDaMxtHlf3CqFK%y8orC|WG43ckOHXupojXXesU~_Q*?)gN1lmLmoFU&5B$M_E;6N1gT~H79o6h)H>y-xN3z< z70XFewdD>_j(vIwA4%37-{p=Ko*3s)mKD1hTR+uoFM`Pgk$`slOz(V(iLYhA6a^IcS+tsosjC7ah3G1>Q?QMdxcw$AC#x)C zDixD163kq32h$pMK;P=x7c&}YV^p_{dJM2l#F?t@M!2%SG_HGF_=I>x^VlGND7H1|0J}Wv2G1kjG-rOkTl^ z1v7QN-|6)vva<~&S)0v&F!1M92p{S!&#;_A@>vGIEO=0~OPN9QZZvlM(Vkmd!d_;H z4lZ0a00qiaTOgd5a{2rjWQ{WWu6P51O$_nO^&5qsbKWy~wdCY56*j$*j1|9EPn0_X zmhd{w_P5f#hD*CxjTW#3K8l16rI6G*)H`AgfeM&tD}FaWb?s@hMMK9_8(9jj?`<-% z!6%&PNG8YWlnIT=xo$IS%(l;_b3uOQI0VnjiuwDNs}UQ;H;5r}_fe-Mh{p3wVY zL|e`*lMQL!=u;<7SKq5sE;T;F8QT;CgyO=@y$lj^Flq!@6wqGfVjHjUgliC2bQ2F` z@VlF|zvf>AR@lg%Z7^?N<0;Vxu}fj_j5OUGQ^G6Kk(1&_MjGQiabXXORraMiE&US# z&KT>j?wMS!|E_8y+0bEHcm!_M$sQAMu-JJV8}KP-UvFW@>luULm<;`&l5s(@l~exYY30*yTKaKn-Ea9JeeyRJN?I{hd+cD|mI@AnKD-pWs- zjj{&UApG*&yd9Nt+s}t-J=(ao7r)G^1SP;7&LN&1a~)6bQvzWxH4X!G=^^sHY|9+u z7L6#W7wC8Dgdd41Te1B0$8i2K=)`?9*^&oF<+NW3F&L|@0Vrn-q`Zm`xi0#XJH$!A zMf$89ML~#J{xV|!!)y3AE&Yv|C_)hnP*7W?=*lfL2Iy$u)9ZLH;fP8Eo5+MNoQJDx z#p#J!Q{$x!Omyt@tE1qF|B=FhTM+qagP)!9O$c0=onU0G8d1B6|0f+Q&(&83F6@Es zVk~P9ihpHuhjP6cj1orf8dS1@Dk%b&2c8%Zn@JITBJW+<3L;Z9`YPMlJqbj%^#uU5 zr+cbjyZb04-J_YVWRP85(GOk5vu5ZA4-zsb6|J8W#Fa0Q6KUcDUc*P491Np981TiS zbc2V}ThL%lawSk2#Y7|C+e0|rs@rzT`#Pzsls@uBg@{0umU#frTl7N90_WF$RAGVX zz7R?UQu?>>U`Ak*$WD4`Ib?PFWftZtBc2$hzy7dw9$zm_2a0hGFL%){uWxAgTdf<= z&UQkJXs|kym!=l{5xZl{dZmxja=I2w>ife~{J2zu z*c*i4Nh(>>zIC}upiJ`B1}AZQLsVz2VnIiLc^t&N{Mqx-53h3~t-T^2h<-ZUi23E0 zF10ptYjS`TGMQy?T3i!a$SGR@{ddSG1kLhXr-D5acYOqwr8X@z^LBc%zRrSV^uMK2 zOcyEbCnre11?K4$u5;S@@-1;YY01cudjDv`@WK7Btw*yAQ4qI>#TQ*8kq3gvE^!gc z;>09@NDUXu%7z&h8SI}Lmb3b{E|XavTt#*rxsrSlja5$5U7JR!ZTC_S$-A*2dGhzX zi=K`3kyT{`4-yM?r90;r0FFdKcNVW;5M^Sl{UloCD2cis(a!nO z|HiZ-*yCy$AP9DoGDAa{6P-!yZIn4qjK6_Bw3}t&mi#7i1(ctlqaK+! zoad^~OL7sJyD+f$gOeCjO^w$d0|GKIZD@hkl=54pq<^QKEp7tcFMJf~?MHEpWZkn` zU=GeZn;+0+f!Ekdkmi5?%Fj_OXZb|2{HoT5uC*zCaJPR%Y3hRkC|yM53Ulo^Gq3#6 zY7|?zwe8XE5IkA)`}0w<_QADt+>o0!u5NTquTfQR$B-Cq5XzckBZBjR+vukkuBILN zBCeJ`vR$4B9Ud(dM)N}P&+)MV?ly((MwugzEeL{)r&K(r@8x9z87+1o!_-rMxHp-e zoh8IRmSoNt_=#f^RoL$CtWF;YoelwRWy{6_sjO~aK;G%Y?PRkSM;#3S8yGxvx`#7| zP^k+%vDg+eNjh76B^JL*L`5W{SvLn1r0Y^)SA-KI$2PVABR1Ihb#kYO-n-cHo^yu9 zBfvFee9OJcGF7}PoNM1vH!g2A68;6+3f(rNw+FXLeeQ$M0E+_#TIyCdnK8jl%9i^& zeG^L9X6;TcXsYwZ{cORXGI(er83yWeRhVUmD4Et_Qm-!I2?0rq<79MeNjt4DO`iEw zuE3`^ej~Il96WJ2Yi-ROEEI(q$(Y%4;cO?R8(**%``1!F5z+B?tI~TSQvKO9+&WM| zL2N!}+SUaTsP37rjCHi{o!PTDS;J43CwOC9Ko8XP@QwkTF+EoY=?=*vt}mf@{fE<`(s8+^oqJ z*@grm3pIS`mm_7m4epq}*Ez)ff0>ZoC}y^%7%U(j4sAjIMtk8bN1R**EDa{Q2Nn8H zCFFCfk<^dm6F!}k(tZk@sTkZbtqW=IXPW5Y;WC3F`U8tk#XZQ^<=yW1kDGy%WTevT zwEXm8cS)>2H_vRl9ZUH#4mwDJnP$7|w1q4h*B@nPaaYH!;Zx)IoR(2GD{_n5JHK3W ztGCdJuLB<`ER10>(5t#H7=udjrqwJ<2TK*2%ZgpqCs__9d9BOvCfR-Of31ZLlrN5 z;fmndgX1HX-8u|n6Tyw7J&ERBL1S;lND|L-G|l7;7!4*H4ZY}zJl?OV7;u%VCxY-| zRC40?ojRhS%Y`5GfWy^#gX5h7F2brbP|x~8iW01@o{4tEj%icyoA(KipO~-Z@4V)l zJ1*s!TO`bB)(bSX0sT!B$`9nHKe)fr{dfRGJL$U`a4P};Xv?!5YOX7V2|Ai-!uG^E zV|UBm%4BZvwTk7baf-?&-~Ny3u)=CicEWvJ!orHT0k|48i;EBGU*934vb6)Wukv3V zoYZON&8eQhoMR49CuMO&EU9_ZzM(c{f6Y-fL_Vd1B^&qV-Eb^NoH74!@a9%AKhxHJ zrt; zc=MNSeH8dB`kwV-Ezm{&B6~ZHP(PF2?K`_x>6NWEUyFw@=Ur|8CLzURpnQPsjEOeX zX_Qt}qokZk8|S^>Btx;(qx>b?z~}(dZ~E8s8F-N0^X(Ik1FfS3qE|ekl0%&hH1DkoLDyoXVgfK9refj{h6()x1c=c^@Ne{t!T2H?k3rwo~tw z>8fU1a7rgS#p3`wpOpaB2Cv@Q`|C}7_=03--jdgYes(gaR;mXr*t8ko>RDe-^uVZfI&Yei8Qs-*3?GWC`pdOO8H ze;7R)3Qk$UO;QCEshI}c=zQv|8T7B+Pe;Jy6^!A3?R%px?G2yH+I9hZ{TW6OLM<^_ znx7%p!h-xL#Q7ji3M_wn9~4AKJvH_b`$l)ZJl=vVBjOHVnR7DpBmTgK?LdTL(koeo z0|MWD?R3*xl$0&DbwsVr^ZYuX ze8t$$1K12eMz=F}BCXzDD_5+p@5 z(5uYPU+)bGa&`uT4}`0(pNV#VT6edhv@Z)(dxQ*RXB2pIv|?%6I!77h^Q~Z6I?y>{ zAF7bW38gQBJ-#t)B-lWGBnZBsg{s8J~hc1S+qyk(WVZe&*rB5S|H zF@f1&Gjjc;pq3kcL0Vp8)Iwl|ty;G(cn{KnheS9zY>6hkP*m614pV|0B!-Lb`Y|tY z?Z9A5obuT)E(u^7s|L(`yONME0J*y6oSmWq9hs?Tf+n=q06AH&OZK ziAGIRFEcVkevMss0xcv!5tlieVp1;R63{Kk116GY@_0xbmM-M2~g zQo8^fu(S|xo-Not8y`an@&P`de`u^kY4L0{)aC3%86=dHeS)(~tW=-KLZpzQHOw@c zstkL30&F*V)(@Jwf`NV{OAN}m9u!^S`us3SgzaDEX}@XPyvt5%mpdmNfioeO*MZXJ~?e-#jJsGvMS)jj~ z@!~1__ZwB)ZcUA5SU#uTx5>vP^Fh(jg+{H7F*V+pu~_&8*7ChtILT+RWn%4)+atMywfb{+!fGgo zCiT#i)8DK#@DZ;MGl)B-D%Ou@0-d?DB}Glr7o9jg({azY8hte%wFmPBlBHem%BEA3 z*$eOpz>}$9|0&rzRA&I!h7xkciv+XVSlZh@Y_HvYXuQ60pvZ6uteJ0MG|)fx3?f@i zln=eQng4{CC_9gIJ_*Z0AcqGeJ|YyFKjq0;XR!<)Wg0M20F0o?C)WEkTWB({C&KQb z6JIW1Cqhz|*v2EqVeTR<;b5;V9=8)kPYlD(rOdbfTIE{cnvzZ>0_7IB6dr)MJVxU- zkyw3h4(RLiahvo2L#3++mmWbe3HjlBczbZYeQ@Ox)shA;uDNAzK z7+3s*fFmD}mUqW5cByacVix0*_u-cP)%bKCC9ki*@Gh_#K^YyM1m<<%NWMpm!z|LR zPz3Kp@eWol;Nu=1{f~|LuL;?q4f!5bQu23p%XFc3QbuA|OvswbY{P5BrPpwqfDcC0 z`;M4?@w^1iAXMQ4rWa859clxgonfWnRXsaPhIJ|@Wx{?(NPuG`6!(ftdSN0ghNltu zK$U}{^lBf;`p(uEi}qo(3g%a{YeZP%9xekCsqBT?y-lps$;cSkAY zVIahg%wIUNeiSzwAcSes^kJt9Hne?@<8PMiSNN)qQS*_+)_VyoR$|)yne6#$NL3}K z=tDu~D4qH92>6b&6;#RD36>RLJ(IlfCa@_+v;J{;{Wz|de5 z+&sS56wp_=WRI?z3Z*FcHr=QU>sEVdmEk+wY#1<;o6}YgP29hDQzUFt;va<%*hy0r z;<1jSkiWQa*4<`G5Ck640{ko)-Jxa0aW)^c7{<{xxVHgkz>`3BB_7XE?a`Uwg8I3! z*}vNf8pbt~jKz8~w32>S=g%WS{#{ zf$^jR5w(=s@nm*fJUz}8TUK&+!C-Vfi^VE*=rrm^2p{8r-^$tq{Kn9ncrq`9*rXI*$U|b#}br9q*A*a^M>zafdEH%eNoa~Q_|HEA^^FWnvA zBtZ?i1jT96_?KJ?^8x!Ted>`n^5-5{ZQj+~A8;BlC64&`3K>HF;pGU*VIlZGM{THC zWiX{709lYm7R7X#xaId?=NBsJdD0O77T%im|M%=#I`JTRp6ez5 zBRdbj+%$7_b)wZV&cLssE;92DhI zhBZTfzDpM$SGCm?sbtvxg zZ7BymRNRt%JeYF*k0i`~BfnBN+s#Z-?7Yfz*2X*nl$7yyW)l#2*NrJKg( z+U9nqW~KJ7O1xO@dArQj2NwBy#F?SuAjgW~UXC8xiFJT*@&eRam5@NfvclUpDo{Byq zo7`vwQ>Fu;7JF<7Yk1M!>R6oI<)PMY`p_xxx0DbdY4W@CbF?(t6~xk|P49Lfg@qh< ze3G{aw6EN?7yb7&&^>N*B;=-dz?=>VGKHa>@OH(N1fDx!3TESxjYPq1#R%djW1+~Y ztV7c5&>He$5lQYA{Yg!uVWYtQaVSD6QL_&9mk?MMV$xq~6~4HrvgP|WTE8mw3dA^W zd>pC=U6d`|)rdNd+*U4EW*r6^N`Pq*s$RYf=e z2G(mekha1v`&pc{YkaR)W{aT~^CDT#q+G6lTAwHTVEsCAYUEtSmI?CHD}rH8Ij~o5 zg@n*h6)&?+u6<0`UpwB2uy(&X)JHL)z{fq|>kvp|IwX9lylzBTb@_GZS0a7??#>;D zzEE5P4w^Q_y%|lAwh695)L2lAvQ{Vd6>-C?_Q@fQ^)}O0jpguAf$YHv64~8DwhnQ( z9rStu!8b}Mb1P8JaThSj+RbHM_zC^G%h_L`l2`W#&O=F?%vL@aRmF*xwZaf^F@mbm zIzT*_DlS>PSN*V@qnm^-kjm2q_Nb+D*&7!cSOUf)pG5(OoZr%x5!HVNus-=o;rhUv zBRC8;T$~{#US_QKM0WzAoIxqI;bwY~t36Zc1PC@sv0(Fl1?1<2!BC+yUq`a9H__TgvC_U+cndKDJQ(Uy%qHoC zbJFuo5HG!>aGX?R+<86tXfY}{3s%+AgcMobKK@NtnFQ zzxYft&Fg}X&`4V7K7H{ajcI+hRLp`1n9J8yNaT;Cu68J}GqZ@rm6qjUrcr4-7vA%D zNJbTTYGj~QE2qk*)IKu7d-{4;iZ^yi0WUq!!gLOw4Ce;3!9XiVW5fQnAQY4-uI=#Q zPVYr=HCUB|vu)Dt$29w9p`t(LDD(C6Cs@z%^7Xi|jS{J2LDtJ7Q6f0sUXhc20;j*c z4^aHbTSmCZk*Jm?ClCl*rAsM5r@p^m2WTDVEDnK0jJI@gFSFv7y}D)O&Kd>Y^2?T= z;eT?hh1FOYS3-?7$m8{o&baexiG36QfHBh=rs&hq#P^ljt|D?YwkIThDUvDPp-b`% z2%b7X>UW9MizZ-xw3n_ZNYX{-$bkRJn`<;(_kAQb4374UJ(}%UW_YHgn zf$4A96_jCMZd14Q4Bjc1SDsSeYS#^*SyaLxH~-J>aiA^45N+LwW9}(dK7-fN1u?ii zGQe%l3sWJlsb#hsLdSidk3AV*D}EAoA1oYQ0;^bE)uKdc<}IPCC~Z6*tk)(lpAT~f zmT6pkg(mTkI`A^eZ(qa81$&ZU0N`1dt4G&>=c2~Ev6>Tp zob1e2?n;ny#2%7hO>=fwohxE{@8kel;@UynFI3jKHCsaoH<%t8I>K9HnOVWq&R>1d zC_o9XrjfA+0m5xYaMVkBw?yJQ$=&D~Y*@d<<^kW%w^@E)blLOL@rX_tWplEQT_Z@R z|4Xq1L8`O+R>;B@0HvFjrE6%jHyB+vXT2j`h~H2{8t~lZcyxdpxwVFNelvBjKLjz@ zUmZ7K=OtD}y7Ah-%GaIz$&vW*RuHAId5h%e8ZL5fNHallG+Q~>G`DW9#t@~1R8;&i zpEdq_F*!H6^h8&DLlb!d0gYhgY(|YJam0+zs;zl(n=42tF2A1iz4t-b4Ug^l|2y?j zPskAk<83HYzJQL^9b^DD@w=ZHG1r}|`NVhM)k$KS4~@*lVM>{Nu?p5{r4G%67S)Qh zeTPIE{iZzB`IJ*#C}%ipHFFze8IHd|37jHfT$1=mX_Pd$Bd#AfJgr*MrBv-gqLCxf||2Zgq6tFF9b>e7K zV#eR=^^%Bv4ST=aqz`#II8OtQu_%wA~DW7&;#g3+V2z6 zch?`q>doc6_p*ZcV!KC|3%Y#5SXr@44vd_VXlhNg&`;cKV1+8ed@40bqJn>IUpV@4 z<9t0c7{29hU&`f1$>sDZ(BUBWBYbo*)+jKZ zpds4=s?kAE_Kw+0wVX-OZp^6|RR|@eb8r_uaWEcjZ3F)rkm2JbLVSzZbtJKYth1M_Usv2KCpu*IdU}15 z(g;U`PUF7nNR*cdF%SzAu_RyTj&wXt?!o4k>KVHr`Z4tsjz}sEbWglpiVE*4pcLWo zlSzd`4!b)ANz;Q`;L4R1i&PlrjK_oXU2vr=4iP7W(x^->xw|R#t(Zk9bnSXU~O(`h`(EKYw1afX3DG`ygOg?f2Lq@DwB!)(f7d;8Ea z%)W&4C$>D`x+N}qblkWGYsADwFr1gtMx@=5csu~Qk*sgl_T=I5Tj<3kH7Dibp!5Aw zl(dx{5{V0N9Aspj1wYN3Fh&%Sh#*xd)_zwSH`W*gwCn+?*Z0-7t?Z2MlXE;_3K#bW zw~3K+^qyt_^D*u(b0>PZ?5>E=8%J}%xG(vVL#!jnCw93mH z$&Fv#EI75yV-SXRt7-223*(s#xEyqPm*_;N*|g<&4go_lZo)>~abgFRwXPi1leVah z2^y9v$Kg9oUSQTs==V@*ESGtBtqZzyYhm5_9jq`}z-*(&h4 zauU~XQts3|lRdf@--ymlOfl|=1cc2Ub@jGlKv7f)RO|w1Nw^ITH(7WA zz;X4Hx4`1RBXGXesIThV#Mrro^9F+NGn)J)ZIg}moD|@TFU49+wv8#|J-}w zKf1JxjNbOfkgEHQz0>5}M6G}^_;2k{zgoNIQ<(_4bB>=Bk7vv$!Qqy6yLkZ%|Nze5zmUEySo-m|feSYF~%eI`5h3Bgoa{S2LUh0wy|7R|9svJkT zJRQOyD`ak~R)n%IgzT|_ei+-fOO~iT+t*=8y-q9vH~AWKK=4>52rKh^{kT`P9M@SNQ8lO@%sTfPM@<^B^~#9=+~3dx@B>}hO1 z%Z3Xb<&&lS0Px>9aw~&N-GFErAdy9#{)oK50t>Yb0{sPhztgI+7lLA-z6XM?n+1i~ zsPJ9iWVwo|;`?;i6R}1}tD=jUx4uY_Gx3j@Z2o3S?i(*xp=sbwJfwsipuoynPv}$( zBW#`kwJ?z++;PqzCx!$8>-~2EF(?5SbJAq6r!)MIoh+dcc3(ispFnkK$`1hYCf+Oz zGQ(BPzQTPRM7LIfz4ARCVCp)G$@@=Zs1tcI|NLBuzOGA$z%2DDqhF9&L~;v*ph+8c zxE`=_@Hn)Dy9S>V9nix<5`Pw^8Gp}5h*Y1O=U6`3iiy&|EIUBpdN*^6ts*5qE&?i> zcHxN@1^1O3QOCK-Y{l~^O9rzG>;+~_+mM;_YPu$o+2ainHEYL;n!q!)c~eo#4uc={ zEgry=dCyGVrpHtX(WCe@dV~EsP#wV9h3MptT#`lM&+$kcx%BI@Jv}ZdJ6A*lE*eSh zgp1mR#lVlFBVM=qeWa+1BA;V#-k7YmWOqy!jn4<@Qk?@2wSwi5CpihBP&LR%m~=p8 z3StcdWd0#|Cdeizt?=G5N7YV0A}8c-&sMOxf_%MW31f?_T<} zrqfV9T7QxxjcQsi<9HGCxAXfFT(@g#`Y7Ke+MRnUxKPQ}t1G=YhW2LJoi$1RykMMplsds+c z$v(OFd*R|a_&C(lIdCfx6t;qLRuIuveGKGg6HomNZ9WBW9sbA(;Uvh=L8=`4yy-l2+)C^W~0wYUm zJ#-}l<@iI_tVjd0(C?kDKBuGqX7eVoK(|EYB2U<>ncPdbb>!9Gc5Epg-W|?c8~e3! z6Ke1CFH?+!K33B$4qAfhcxa%H>TFdXErpH?bLz#y6(zd#?fQxkA178`MQkLaXOr+p z1m`i;1M`Yu2Gag5r(6t1@IYxQ&|4Os{G-Z**DVEKX!d00)68bZrh+@ll zh4GM)A8hF7;g1x0eG^r408AhFS_4pO||vMv%Y-)57_H(D1-_>mYWmUp19-NuiM z%mR4K6`$FLMBWg$Ib7pr4xTNt5&^r0k!#_r+}{e(Vy=n}PH34(D*4*=Re6EiSORaI zu+^H)8zKls#UxrCWs`t#XS1AFMucI)C2-RQNCM-uM*X65FPMQ*^8W~{^xxbh?Vvc^ z*`;MUwk+UJcsGUR2@&0}MxU&uD6Ub&h!KkdwVB}11tfpfLBVk0=+Fs_%ukp4#Xeg2 z=Up;nOf-_T2VlF+6oyL;U+BuBOf3y`tihTQ7A()9u(^CRd@$q;%3&1x_esOSR|q5EAe0?A|xnQy0c899M%XJ7kPPf*VsCE4^;gVH@Hu zT{uE-1KhX&prwLC@sorY8QKhyucC-?5Fj5E9=FhUUeWJDGj4#r(ZBURsVCFMer*Oz zJ}!I&hVr{cW11~Kn9!L%!X;x8BDOqQvA;K`wEQ1K!FzeH5+59Mza!@OVU}_MXAvOJ zcP;I%TS@miGN>gmR3!SQ?41eZVvZisX3|(ECw3!ZD&;%=2CbS3#0rUpSokcr+^posyTc=3B%AOkF@Q)2SNE9M(fTysRz^HiDOZP zox%Mx7PH}=XfTut?4S?oSU|l!L(0@7bsE0}sA*n+0x&ggJkJLVZ`X2G803TD~d3`~)Q^0cN2&`>_$UHe&yWA;_`OoNhrswtx441;~i z4`Evo`35$#97-e+y*3a?ALXiW%xIyc6%xsB1zqs~Th>Zj61T*r^!O0y6|F}(r0>n2 zz*3LT=}cwxlYmmBf}^(cgKX;X%uebL*YlT(tHz85^tJ9Yft6i+1~f%}9^ps-Cp|87iuv&~2QqD~*PDM|-Ju-hs$Wpv zk1%2W>EVk;z5VV|8fev!Z^x#1jaq=m!H{ z{j@`q05`@f99|d=PP&sI(o|VScZA_zw$QPz4YEr%e9j}QXyL`zf`r!^7C_iu5&)uC z(5q)YdfWeYYpJyr*)@$Kh7DP?-_@G<>dnCTP8F#D;EHr@< zr-m5XNvQUSQ?oAIByz%Y>QDIVETlRq`O7hmGb%Qy6GOd%j8Bja|EpMDgt~c<64Zdo z`3W1`$Z#8cl%;|tPBl$Ukl98874QAaD_1El#vSK1Zfp#soM;-*&mKH&At^{FWFyzZ zNDp49+do2ZRf1%Rp^sp?T=(_>r)@cx|8@*=d&>16Mvg4U0pb!4J_j|pLL3-D>B{o} z1F)tBaAiDgh8pAI6Tcc>fjRpt(vnVfwh$&bf&TtU)8T0q3KQCSZ_dr_7dUYaS>n$S z`4ui-oUDnqUzml% zW(iX~ktOY77PriAfMDtxR&>?hjwh}6lo>L^^sGg-h)6;-M43qU#gQeE;e7kV3a@sz2bqJ!V^RE{j|XZm%W`AQNJ990pbQ>&5RYIc-5-t^h!|PuH+N?5KmT z*c4H4rz_6FVgLbg-R%k9^k%1WsALVcn~;bcBtx$}zds%XMXtFC<}R116v-6a173TO z!(u>kX|@iz&CK5rN(!A0NE-bSFS?&8@Z@AD2w|pqqn9{^`ChNWM_k~Z3rVIj;dEjg zQK#Xy82STqC*A~OYaB>l-bZ{=OuvMW3CNuPOat#95fPI$=#fMTkdM%?N+&QYQkpWB z;07T@NoD=Ms+sygs)ra)*~4mMOPN@UC-+o1wZ{1VaPK!*i`_Shw`P8I|GIGV(q5h*IA?lxs z!^zKHfQRGKHvR$FVW$Cms%roH??gQg)7@1Kd5DhSX42}|)u8xAT~S2Cu@O4)9%L;k zxFQj+bpp-9dr?Ppfgk^ZZ+h{{mM53;P2t1pSrENAF+e*RJOkJRVlIBsydo|4#L2(w zf!qfqn6>J;>oZ(_iNB&M03X4JMOxxnLD9H4g zTx{(v?ah^v(?2SBUD`qBP*3qDpMR|?)_k=byBJ<3i}Q&CButqJuHcBy_`_O%2vil`x3+czd*Ja_Gw-3C zW`gXT)Bd(Wp_Nn-?Xhklvq*oo0@o`^9a$-gBhbNO_vu&Z=sHIQ&5mVPT(>`y`Y594 zZtR%C-)!(o>-ph1TMh#~UTZY$)Ya=5v3EY=;foAjl;q5|byw095R2&nc<1M1Ga3}V zc5$DInU!L)u*IB-S_){B6ihp*#et(gs6{WnSS zd6t#jVp!{m8s~PE{U70A0;QzLOfgNfeXSftyu*)&RcZvyHX<)IMb%)~=|*|7Gy}(d zAbi$C=sKjDlKEL_AsyXmQKm&bE^Jq0dTGCZzMnIWvTv=MngOO21hRtff*?jHGo~n0 z1E6ap+*VA7dzdcmkBL%(g`MF<@HSXXj!{lMqb5b6Bh&)SNh~s7i)shvJM{J($ z?MmyV>@^jpQP~E%KIpeaFfb?$Z}EL*VM%KMA>s5}wJU!+l2zSD;XU_97b3Aq&#l_@ zg$6A6N$7n-SR5aSP`oLDsM`NWC`sa7>0XprnHfIWfwd#PZP*T2aEw|0E?T+K(lu6- z|6E!NQ+$Pe`J`8V4gByKbo432#@h8K1Cdk)Tgg-s3P9oB?b7~|HM{K&*utZtbWmT7 zbo}BYvZ=pmn`qS($s!P$P_tXU4^8v;IMih%@AWD(!5{^dv)`- zDBpIV^EA8~!$~_%+0O`Mh)%%_sj`5YQV4g-__1T)g3z&Kz4Rv7fh%(U#i;_ziw(Ga2K5fc=-)xTW~P zhK-N!y2#q&zDoMR!h^m12V@vwG&@8!&Z2711)eC7_^Dv+_XPAW7G3ohD*Akt*`xUH zjfiPWu)hVM!oV2wTGY8JL1)S?&EwJ2Bong@{aQc280>7k(M6W$PWLc;?Kc4->5Ch${Mi8;# zw5A44(ob*C=`hJcGar=ps5ZXNtujEER(q_ds*c3L7V&stE`*OAlV!M;kBRE?!#OtK0caVa&n`;HbaaWpOE8a2Tb+2*Jbms z47}`>Or2>gp~f$Et-Efq>}2y|%6ZHEB_2l3bMS5!Et;^(FJblj-Y@7PBGBQ>lg#C7`0DfX>QOKWwYNtN7#(;Wy zdIKO$G4k7FR5kv@$65>m%=#1ag|>KeZL@>)9GX8;wIB<5&0xKnP<&2#@PYG;0?&S- zfU;~NvA6J}DpXsoHFuH>YFB!K3F}?=2$VxPD?xujK_{0ET70cjv_7_!{dFxDo+l9J z9hH5lbv6Nc(xD%%jGSI78?;S|dr@VVy&u4|aI^!AuO7oUa=IFCEm%HswrUig$;Q44 zW&r<)H^~tp@DP?w&oF}wI`&(J ze6wHnrofYGhc5W|#rS$3i2@(;^(6tP4jyZhKKg)%5b8P`+}Z2W(WyZJnpNgC?qTdh zSi3}%bG#;WHW+*tmo(g2ujCcv`f&w&SlZ$B&kw0{EE5aZ%<+%>-+whq&)+YPwf#Ps|r`)n*QLh)9n^Yhjf9h0j?+`}SaP_9hQoKQErANo1?PJ#Jt5S$#_*gZ8L!%!C zr5_Ahoof$d+Aa0dUDffeB%=76(D8G-qHNpa%}L{)0YJb-se0e5?{Qbn1DQ+sH#vvS z6}HC=Zj0b%-1Mt{X#g(OjgZ9v9*;GAVMUr4c;5aHsInydKU-dC<$*?BU4-2BmloUZ zpEw`|Am>#+Xg$jf)s#bxt05glKWtPbYa4&$KBO7Ns# zLImCwWzImQ2G)JsVui8jkPAbg!eKpy_96bL%44_3wtM((DB>5=i9Wq``Ye8*w4^tU z^NccwTwi9)P?lY{Fo{>%;&N zbF_CbHI6m18<)hc+-G#EEUPtZrQ^Iy3u{6;hG6&=G``PUk3<+3lWCImI%$G5zpGL8UvjP>U*4th zBivX@`6S3ETOl!|3)T*8*$Mv8WJ!B@R>6}lD*Lq@7CikVor<)p<=;PXXW-8X_!PTh zqg=el7JhRH-H74IW^rrArH0&y_DF@1_dI91aGAW0e*>0USV!z1W86^+ZkJHntB{e& z&0m$RKOItR^s8?&CVtgt+w_TD3nZrDFMbr{BPiyCNczO`H`{r8lBAB;K2(}>f6Ym#Z z#^=l1y17#st?~OcH!^KhAd`48tSj#J2G%ru7w@XTe~D%P&}CEO9U}{&ClLBTM{!U( zS$~b=%GrN>2doEo+=W(jd;`z!^_=U<->uwf9c44uya8+qoUdV|tt;E2?rgnEBieQe zGR+>WLu_2<;^pVxy?zKRvU46Hy>Ws@q|jOFoscH?a!J$uJSjrHZ^K26psgG{uqs&- z)GIqXPrWwN`$sDT!8Hj+XX_8_3{c6LvsG9{IffXK@8-$k^Xe?Ra-;4h5;Q#mO0CPb z7>8pU7ZrVblEWYxIm$tLnRe0@g3 ze15CzgT)TH^|`j~sx+2hQv8qIy_!-^>tc$~d;c#2qrEry*ri8!r}a6_6DfZo*5DU)gpX^W|V z=&Vj|j64OFt0$fvH&GX7*&}HHK=h^oI;1ez4l<8e^Y*TiH8nX_=~+VQNrc9(JoLqu zP#^+x4907C-lm`}Qd|K~5iOB!1{DUj_r~J=5)fsOH(tlt0{10OKa&+!Ba0KZP1f>* zc|E#fZMTrGryTmwO9Hka`Aw+UJcdz}?Y|tjfBD>z$+Zke)`}5@OC#a)gMr&FJI<8{ zU-*8qHka|*>%VgY_qDMYBzIAQ)<7ZL-iNgjWSIBh@=Z}wu8z-2V|9p?)!cRGyYnX) zUR#8H4mHex{D-|6d!wGiJM+?%K?Aq|TC39oGM7rd`Rd?`&C+6?LrhYz2R53U!E8)a zk;K0A=wcghiE-ZpZ?@K;IabC^ zaPC{xO=6s?h0RG!Ow+{%(@#nBc&8~Gw?s!YEzmZngF4#_y8G{8sPYxsUJs?b~oa!0GSOv-si1XezmXm>vN zu|clSt1}@*>Y{W)rm*UEO-r%4xJ>S82AaY_F^I}h2^o9HA8H^vFI9hAOi}7UKw|7s zWoq_q4G%)4Rnec#Kb$27fmj?yw-HBuZ9<^og_DHj9J{seQ|u}-*8pYguuHMjYaQR! zq6aTTcqF<=t<8l^x*niDS`aDdI|kLpKjQA9iT9Uo5}QC%28AL$rNHEn`rF9^YfNFe zgaF#}GDFBUd*T)OsY?i&=q{LcUa1t>NH<_E;gojf3IbGBE9WqKi|G4!v;EkNdW9%l zMTjtVqK*+$xHF=<6>3?iPdGqc3(G=nTY$;8fVu!H=*x%s%Oz6ZL+Zww6g5ZV5=|q4 zX25HLc<9gH7ckff)cqHF!T3Q?MWbbk;ta=XE2~U51fD(b%Qy~i#nk^eAH84rJ^1(7 z8|XDAXWDKq8A0QYemv;Y@riK@m{;B-WU?ACbY7Q`nMQnRrtGN$vgQ+BY;<@)0Q}Ka@%UkAs!i3?V?NtXn=*B%QF%lLYBg4^IYBw+pp5Sx= zn&#!6VbecC{x^?p8<@sE5ByoUQ~B&7NOtsh+v6n)&r0a>7DcunK{fkVVD>Rm-(GVm zE*7r7&(gF<8=1#8W-_6{HL`_b?y}oK2QQoY)S|^YD^2pphbK#5$Y1Xo7J4JoJb^1& zI3eX02~gr{Uz&nUgV3^DOtRysF);91b(39xQKT3-SdA{G^IVcR;&CE+2AUg4SJv9W z`OUP@q2~L?cA<$)BsPWwq*r=AaJkb!-LG>ufT_9|KH(&$93k7EQnCD;ljF-cUL^@4tcDyLIV-SEN?+-*lV3+(9>jyr8#23qs zzDy_szbg{+*`D&E=nWeR@8UTOd!@W{&XUvn0NkrX$?V};Acv1i9=Ck5TiU&TR zncoAPwZfMj8QX7MUXYU3dDh#!WM<1w?jTo*wq6=`g&x(Gi7;qbQd6W;*w)#ksUK9Tm(jptoLU;dDqOYgQsRY}ZCg+<7ziVpMvQQShDHNF`U|FT`6cc12~EXd&b0QPBhMdD}^Y6l>CGbYX3RIzea^gX919 zJN{`su$>w(N79;*bI*{36m=6L3s;Me$OkgV z4l#@V{E!F}_W}0$0%yR~_l5VizZ;rd%7yrU&|`Pzw4Z-743;r9yq|{eeC&hp2Zn|D zYTEvQ!0+Gi@kv6s-uhu%D(ZCcLx|+ERVKS}zHr^4XoSe5hEPZg>j#f(@zM~H1BPiG z2+otA)(hX4$d@L#Di-1dXWdzC#zJnwj~uId8;{c%Gv;2dKkW?D)5l(^p5k^!0 z)vlP))Q*sihzZGZsW@XPZO&VV-!*;R5{hJ;K|@OcJ(%3q?FB;++GR+eISs$Tep!QI zP1jMp5Ej7YEuESQtFb*2&a1PQy9J@%aPj`JdjTdN`1ntW92R02i47UOKIu7D2Q$Su zQj2uyp2IA*ALyTCSAA+g2q*yNZd{#;ma%-FuszT3=o?iyvr_2Fef;}~$yo%!H~|T) z)Et(dW^Ic5AmWzC4V`IP=wdBbo z(jJCL1H_W_11Kn$U)fyqmBdZ+6$;--hU_=1(AD${RH5Pdo&Pz`-r^^S@AM9c%^kFi zC{wuB552(B$;>f;zLJow9x9hQTZFxH))>v~XgNy{-2^X>r-ESD*dHKpw(p?1hm;30 zaW`E?pdmUbouSv{-0TZ)$T+(A&`kn7{C(aw^D6WS(fqAbhpW;Isq{h$9C!uE2?qsN z@q`@ZPDTkK>@Qa4 zCLSv_!kg1y#$Dab1?xA#{#t(wNVEqJ zWGG$G-=WetrwyWlIM}z+7VV-E9 z^@Y(QAu?CCQdDmQvkTvI>@aA-q)p!F%qg%ZD|p96bJ!D+dA%|;;OIkdr!zYC=tQO6 z7z`@wqC%`Ho!H>b#;5{x&D?uK+h>^&uy4Sz!uodscjgzBoX2~Tbzu@D%z!8p1w(JM zRZWY$8D;~p*pjz06m@z9_8r^HfE|!;zX2>;w!$jKBf=lT|CgRSPoQM_vc_;js5iMQHM9Hj?x2m&2ziY zsE1FTzhM~x*R3)W@nC4cWn!Sse1{r-Ya|#eV`R{&TFPTe%Qww6BX?}3j}YUt{tJtv z+3PdFOC~|ihTsaJQ>wzj(E;Gan66M{Qv@XSz;v_dEC3|(uEZwLG*IiJUaGI+?_r7a zA0#?`RU;O&%Oj1|SdZBK8JV#@e_g2Fo!~OZv9~Y+=;zHXmy`(*XemD8rQ?$X913A% z?PO*f07u`9gslOu$0Qb1ec*Q4Yd&_%F=1L9;FcTD57W@edZtgkz4{12gBdFM7@H1z z6|Z|Vv$LbwDSSrhrv-nSlQP`~L5HXj_#x@BX`d~m+j7@h1eeH(2z1C1>R{_k^ZTQrsu77} zQRB2cGi@7(ouxU6D87riq%V1X_sJw<=C+B`&-a|QbRO+xT@A8}?c9A>kxb`ChHG0y z^TaGfk_|V}4)#*<}B3!Hw70p-@Cpn*=Z+SQz%w3K@ z=ecB zR7Xt%LOf3@6ye;v8n~qQ4`)j3<0N}$z@%&c=+*{3PcG7nwGCb``sA(I;)BO5Wo%qn zn^eUpRa*`8UK0xB3`I9)m&Yjy_X~gt6<|MgKiv(KDjR4fJHu%?bY=UzH6*rF{F4G4 zzmWVhT`8Z}hs=Hnyc!|hj{k!Mo0jfmHMxfa)>%{N&SyABVq2nLGKXG!^*_c+qG&xG zT++lX@_455OEH}V%FkCa1@oPtJpps7qZw;t$vCW2Vt5I)KZ&z=FkzJCdekUUu*#Jm z4@kA;VAnXu{8Q-fjrA!^l@q%x?qX7Kc>N}Ay-U>e-puI zC4cL^<2b6O>^w9CWb!OR9EuMi-DOYxIe`EzJvkW5Bd4LIy zq}cJfgn2RfUNVVuWs@{(v$~Myl<#CBb+^#)!w92a;2E_OiV3bXHHafXpZZI3EE4kG zUBb2}%OIVqIBZt16K*sur(uY`yh;U`S;;u_B&cX*rBl9hLnzl0%)lF})Lg-D5)stu z6f57qi1%_D7>@(!WkW6VU`e@)VG2@;t!7)u?A20=n_U_XRKI1Oe_PV_Hb9>vMw+a- zFJ}37WLSCwY~oH41i1pb2~gdX~fR6fB>l zDMd}+Q9>azzm+h;R6nh^6@Pfy?y^O1LV(Q!WSfSI8tQ~R!`{_xFy-qc5{BeEG7n*9 zJsoE+Dt^Mv;GeT@^-1)VcOQFUK!?!et17P|9?kxZ9RbOVXmvc&Z33GJ6dm(t-Sl>V98G{XV26XN{(&SQB z1@3LeY4(+HqfC#UI+kKtR|au!b`Trmy3-2WWHdgi1nLp@xuMk_$PSB}vycfntA$m5 zm_B4b2!hLIU%I^Q5N1t5bLyu8eGov0;NZa3NwqB;I-CY;uT>!wQ;2kLjfk6pGMwCn z?N!`J%!MDAmX?vvd46GDF}|V#x>9AJ;E;Si%SmOh3Zb}Uph)HcX50o_MV>U;+nU$` zOFfv>h=DhtIMS`ksU|7>0geZC*?TI~czs+hY?mTcpNz!S}LPhf&r<@0%&mOyCdpl=iM zC+YeZiE}DVRI)IHYQRKeCi1(WEKqL(OvmEk6e%*SaP`V0pkC&onz1$gL(Zn4;i{X~L+1OY`LlnM+vAZneBEJ_ z2(6nF6iOdoLzC=Y^KNopI7e1-T{7F)D>YX=A2?fQMuMlJcTs2fG}L49m8&t-7$o**eUx@~fH2;m`oop!hwE{0dy+yG&2Wc@90YyMPd(|gC(jJd5#LGCwCjvLcwHKxSgb;#`YMSg9({p#{7M+X65O2;} z+w5oHmzB_rNAW&ie7aM%N+x%)Z>q^Lm$eD)EKvL;mxA(LX?;+upMJCu$Y>Qs01eM=Otf#3>>d%H*?|A}^r$gVxIm33i zH5b*#Xs{kd4oAFunbSx4TAje%)2Vvlk}=H+pA(4KdN_w=XuG~pvTlNa3UTrDIQOZF zV#r;ff1!&ZyycYKwTygR9WuZ5s+gr_Bz*jlzv&8d rdypN=`|V4a_Uug7!Ja)jdhKU2Ed}o=5lp<;v9&-NB$z! zC;iXouf^Zkzft#t?I~}+gYZYTf7DNrf8zLy`#0A6<@;Cr-|}97UdsLF&BOe>_VEJ# ziT%gY@BO|^9>MGf`nT3!{+~79w12<$2>ZJ2d+9cWKHdT!vS{_*WUjLvr=ECTLIT;} zc(>Q9n%Kesua4Kzhxf^^Iq_JEe9Zb92~{!7#-S6E*WTGxypklgY+JH#D>@ypuN*w~ z1j&LfvRPC&o)y5IYNvzGyYs!*31jm7`l0sJb5y!yne<-@>H$oL!KNXo!Y+JKwxG8L zHHVB^z$9LE-ci%O)Cv3mqn9p}o-rjC)(W2xI*rh3`~{FJznMcDypvd-%-bnb4(S8+ zOZZEy8(@Z%+eY5PYZ`5@#&?*-D4)9(SZ0xeOXx&JDi)6hrhBw!SQ^8%qANTYZ*~|5 zE^@h(-Wx_f@N`PX6hqOn8$ZTNoTikCC#0TB&7hN!j7G15DjV9Sq!{|tGoDfeQojZpv20%ZY4M ziJiYt+0BGDqb0i0P5P*lvt~)jL3MhWs9RN2H0>V%+4eFSdR6LBVlbX< zyr;_M2AYLei(n77aSSfS{xQ1l*?Kwxg8mMnqTC#?Y!lTZG8Q@U5^szT=H$^`I1Lue zEPf-+ZJChWzBxSmJ?;i<*`aP|H5ldHv310ym(l>AvmpaTS zF4?xUpWz4`mPH5|Y!5n>m`2(?oK8yrPIFHk_4Ny?>kG_iE^4|f8vA1T%7i)trb#t8 zJ;YuQ+i={K7xA1^cZ)*2zTilNlie`SueW4qszI`PA{$?%;}uRw#^uZQK0lj*Uw??9JHQI8Gsb9KI3Yq_|t|~oT)Fekz4I_ zDjuo`Pqj9N0w*P#(NJMcBKTW6{R=>=B1bg)EgSK8q9A-peGmY0k z;N`Jf*_^jklhi+V5I9h5d=hg`tOp}Wh#mfkjFN+~A*O&(BPA!*_05an5^|tWOA!mV z;>2~%y~Kh%rsmdD8I2zNKKh&trysMV^J%{1e#Y(_aNM8@*9s=4tJjntfANmM6A+z4 z@0hBs-&Sl#6!Bf2@99z)`+#83ZD0@Z#{dvUNd)cGQcyFVik9)ms*#XY6~3BD`%+Tj zkF9M5?EN#>Idrf}W;Wlz*=h^j^AG~T!4tnyev)Y#TXJo(Vs6j7IXVok>|SZ&h~H)Q zk;_Vo8L#G8yNR3-i;B*9?041r=*Lw~rA<_`aWogP_zaf4?FCf7pmH(M91Y4ELc{s! z>yuXPuBokQGS4Gbh(3_|C?4`wcDGd_plu0u{i~5YQO(ZJ+4R>RwB|9thD$i2vyauP zcfd-*(Sd%vD&e)2y0KL>Tn{>3Vg^nE_$>L}(vl1YE9n~->gsmw#1J9%*>5{>$(T3( z^rW5f$!4pPCf$l{wMPbV2IStQTo)fq2N%$m9<8dYyihWeDl{k9L6Oah^y8^J-_JD; z88AL+d{FSRmvw36pjN)<6Ib>uc+>X{A?SS!hJg0$)%ffyVMc4oEaS60j4ox;W2FLj z{}<*{WqbryxE!vKUg#Tbum_PxR_^25NbY`{pejP8ITtxPCcv2TdW=Kmaa42-n7#HZ zqA!t}Jp`eZ^%}fvb`E8L!_$O5f7eZyyhyyl(3c5tnzlRhY-1JGwkO zXiz=AKGf=&mT$M-te}a}krY>2w%oxH2t|q8WDiX9^RiDxkwD0@8DlUiE-QipblOZ0!vN;QZH`{A#!T3oP2Ih| z_=>Ge+lt$Pyu`U3B93qwoZaBa>xIhYZv{=c@E;j-d!>rKz@=mJEqW{(+8t=f;EU^E^cmMdi_( z%lbBVz^B-c=Uj$FGVh^@1mqjZv2oX%AWcQD{K(*9SSK*BZa zKTHaabgQk0RA@S;+yPv^qlLSJ5J)42n*z^VF13<6^5jCejGi3;L47SDV@6tqF1|m? ziwlDu9cbbdLcMi!SGY4G`KM$cY--~Y&BjIQPrdQ+KnkM9hyn5yE*X~z*E41EQ0G|6 z=O^uQwrOR3hit>*ISDDT)-pa1_%h0XT60;kXk^q}H#b#U#!wj=1VvBpo1W#JhSjwM008N8 zxC$8;sRs=M#1bRhOmmHc`HM(o`{?14Yi{yv;8{`ai-T^BpVocbh6e=;(NIx;z-TO5aW-xpOCVJtrgp)oC)kn*%oTQ{ipOA z^ag{rZ+7IxDXh{)+b-80X7pE8;kB(`%A~#88{dsHbb=|)WDz-L_g!`5=Q@*;3771* zGZi~p&m|nO^0>TgA*t&fK8a_bh5Rp!qmwwk(k0!Ll$9o)F*MY^?5Y+q3e8KMkn1~k zb7b{L=_x=-zocISt*=x7xo5G^Np_8<7b;7QltaK# zI`5a69Z76asf{7P2{#=i4ubPSwm8Jsi*$|1EZeCbm%k)|(o895?Iw|fE)YgRKK1OY zt+zE5jXj%Qp-oWtw0z(_Wl5ETd8CXZ@}uQ*_A`+}mV`KhTPY$FkKX9;R!M&6ri*c-Vl!@}1?2duEf?a* z?GmqTt|-!g-n1+4bTy7hUc{v@?H%L8khTMS1G+hjNDjN0JE=j{YUqIC@EU7ycF>q@ z=sg*~5bzN!7v+ww70#aOCSYr|bX9e^T?LJ?qF9#ZNy(ERTsN&w3v48?SEvOOl2iX# zWti^)&;A9q=uUkt{n~3jk^T$TqYKqcpH>1&U2zos7AV>Dzn%3!btzd%4o+<953`=G zZdSo?4#hL*vL|lWUq^xA>>7cicd*=FQcjp*FrUdOrjaL7eFnao!|h@qzmB|H_Z>6R z6r4gmPqwd40K;nPEo%XT#9QhryK+tG&`g)-)(&}3JCn|ZC5RD*2Hx%B-kupvE9z*e z1%RqCTVJSvt&#v0aTlUTxJl>X%_MSf|9P*b@gdj54aY=^>;UHp`qZt;AeonTk`zKp z%_pAG_#RLU3U4w7+&*#2Q`#6tJ7-14#mLXGBs#zj@QJtJ3DG%1-PcAC8W31na0_4L zm!vY$gdd$aZOpv43De!;s&liN;zz$HYIYH{#JIIr_*^iTrz_gMk&aHugVOVRhhf`@ z+nWFgmhDq`Qa}SOLNi6`ZAAcu`|~{d&Y5rWi0j38th*Bf_3_-_|XL z7(eGC>XDcd@ud<@36gDTs-XR$G6M@<)_YWZ&&>;K)3xrCIHZJJhqXiJ{*W z0uL$zd`7ovrIe|ijoN*N)9et38`&hHuY2TU{Q;YbzvLoS|6KR5USyi^}C@3=AV>+{h;dnIoCuEO9AuMdbnT)g!7dY@bA`0&=n!3Q=MUd z?&?A);wOQG2DAp>tbErIKsR4iXT!v`$gx&1o80W-a{-utGk^r7MIAWI_=@D_1QPu^ zSD2W1&=wSL;b}tcMb!wRK#2cBh3CZ%CCeNeI|6}gGTLJ0w%}S$6x0-N@^=KGO{z9J zXr8t76jCRRvA$;vsHPTEfs}hr`bq#MVw!k>kfFH&LBLff`QScQ92rO3IOoR-(l-f} zsc}s=z?}M54Z0w}TRZ$lnZnViib@9OWRE5`ylmu`ZXcFC9_uk~PT^?IHfH+mk1zT9 zkn}=&mHh1-eXZfzV0|8{);V`>J9mhfXQx`y)qy zIs<3u+~jzpIV++gvd$9=XX9tF%N0=x)hnzo!__`bPUe*kM&-vaf#_|uVb0)(<`~8! zycDkf<7DvKoP_fDHnvj}G|kq!lf)_YMj~Pi$C!2(G6npZ(3a@s0l^&$fkb^G79Jzn zdZ$`Y>`31uRF3byg>1Gba63(}jUQVj6COG2;r=+QyO`L^XT?~8s=~r3?g({Z$A8p; zNabNI7BZ+?CZ9@Xi*|~Va$q?cJsE1bas!oDFL6pA`Adbli{;_l_VGKzko$FQc5C5$ z2dlHVpaIcNM0%5w&7|xyU0=>^&p>0>K%Q`J+2kx`BW@+4sLhu#Vz_~Gw^SUYZn8}XM zjtAwY5rG^;vMRO+u9;TcR{j(!LF+>^D2}iG_dqEuV=okGfhiFDDMQ2eowTL@eL`p> zV(CC7@}<=e{A1cPbH?K~B zM8cbhpCXU+b9Q1F@<0-cR?>dq7|}zWOFn?-f<-Fjqg7X6?-T_VfR9iovW|+gYA$>$ zYLpE@s!@yz&{NW$K(9nb@o><=Sb`?K7Y<8@H~I-QI^7KyXVe{dE{k)@(&WiQ-g5>Y zsl*-*UWP}++h=Iif7dpIT?p|4-A)9)ER(=hJO0C#7=MY@;|4?R69G2WPZ(R23|hub zh00y>ALC7*%M-(U$0)_VXFU1%nH^9wGqSKDkjsts63A;sJYe*XNvx~sKoGJM=QL`X? z<;Bza;QsPpHa|iK0`zHXAl*D^MIc-|7kq_@y3D1Z4_x_0`XFkQ-)}v$`Cf()wTjz= z10!4}j|%y3%vg?KLVO-T@g4-)W+zZ zwT#_#Rls^t0u>2qzAE9$G4BVWz`$EA#Ev=uHeUNe8wr%`U)9SJ!lj#K$UfH7ls>0z ziLQo*>3gRQEc@&~`?(~Nf_MRCqfV9S8D08Q6UiOeW;gMw@1@HD_irSOgBgsZ`HCF# z)WW(`p)5my$e3mt;5&mn&{zrB))IlKtJduW4`VvpF_b@$}pM5gxakxIiex#Be7C^IV1EAO+H z_Lb={+And%Ua?(#4*{98f+@nR!gaR!PbajbGUHsW*=IH5EdL`ix^%mw&!KE4!u!=* z3yf@M=m44iQ6O+GN@5^rCz3fRgL+7x<>kV5yWM;wJSa5CdY`_8E@h5&2mSW8Afzo; zbF@oPO-pa`>p1t)iD}sOepYKQc6QdVwPH-Pmtay(}^vT~a-Ne1;# zB^EI2K{zmnJI?5?vL(&5axlM(o!?aBZ zi3kvr=~6?tDShovM0)sYnH8V?<8tER4T=={=>;NuJR7kOsV(dD1M4co4vYqcaa4j? z|7BvZyb^26ig)UAGXkiEO)W|zWoy*T@EkVtNz`{Lxz5k(m~-BiGE6aT)9Y46K)DhT zG-|gCd;q0N`TYIkVt~jHupyUpnb3~vm4#I~9QLs~p@mV5Ai*B+!gG(wuRL}H@ zYC^)S�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 Nm2AVjuxaGL004vM%(DOh literal 0 HcmV?d00001 diff --git a/public/assets/lab/grain-xs.webp b/public/assets/lab/grain-xs.webp new file mode 100644 index 0000000000000000000000000000000000000000..7036bb7377e03842231c6fdf95ac88eda77e402c GIT binary patch literal 13112 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 (function () { var FONT_CSS = { - merriweather: "../../assets/fonts/merriweather.css", - geologica: "../../assets/fonts/geologica.css", - mono: "../../assets/fonts/mono.css", + merriweather: "/assets/fonts/merriweather.css", + geologica: "/assets/fonts/geologica.css", + mono: "/assets/fonts/mono.css", }; try { var t = JSON.parse(localStorage.getItem("siteThemeV1") || "{}"); @@ -89,20 +89,20 @@ })(); - - - - - - - - - - - + + + + + + + + + + + - + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

Figma · Plugin

+

AI assistant

+

Плагин для Figma с AI-функциями

+ +

+ +

+ +

Контекст

+

+ +

+ +

Задача

+

+ +

+ +

Решение

+

+ +

+ +

Результат

+

+ +

+
+
+ +
+ +
+ + + + + + + + + + + + + diff --git a/public/en/cases/ai-platform-primo-rpa-2/index.html b/public/en/cases/ai-platform-primo-rpa-2/index.html new file mode 100644 index 0000000..7eee300 --- /dev/null +++ b/public/en/cases/ai-platform-primo-rpa-2/index.html @@ -0,0 +1,1062 @@ + + + + + + + AI Server в Primo RPA — кейс (черновик 2) + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

Product Design · AI

+

Разработка интерфейса для Primo RPA AI Server

+

+ Платформа интеллектуальной обработки документов
+ и оркестрации ИИ-агентов +

+ +
+

Обзор

+

+ Внутри компании появился сервис, который заметно улучшил работу с моделями для распознавания текста на документах: загрузка датасета из документов компании, разметка полей, обучение модели и дальнейшее использование модели в других продуктах. Такой подход давал более высокое качество распознавания и соответствовал требованиям безопасности — данные оставались внутри контура компании. Уже в ходе работы в продукте появился функционал дообучения (fine-tuning) базовых нейросетей под задачи компании и работы со скилами: суммаризация, извлечение, генерация и другие сценарии. +

+

+ В проект меня пригласили как UX/UI дизайнера: интерфейс до этого собирали разработчики своими силами. В результате работы у сервиса снизился порог входа — продуктом могли пользоваться существенно больше людей без специальной подготовки в области ИИ и OCR. +

+ +
+ +
+

Было и стало

+

+ Сервис был собран на Ant Design под React с визуальной темой Hope UI для Bootstrap. У компании уже было несколько веб-продуктов с задачей унифицировать и освежить интерфейс, поэтому в разработке опирались на компоненты этих систем. +

+ +

+ Я погрузился в продукт и вместе с командой детально разобрал весь функционал. +

+ +

+ По текущим сценариям собрал юзерфлоу: они показали команде, где в интерфейсе и в маршрутах пользователя возникает путаница. На этой основе переработали сами процессы настройки: часть функций автоматизировали и спрятали, чтобы снизить риск ошибок и убрать лишнюю рутину. +

+

+ Новый подход изменил и восприятие продукта, и процесс настройки. Интерфейс ведёт по этапам настройки, при этом не ломает привычные сценарии: полностью линейный визард здесь невозможен — пользователю всё равно нужно возвращаться назад и уточнять параметры ради качества результата. +

+

Вопросы к команде по текущему состоянию

+

Ответы помогут зафиксировать эффект от внедрения в цифрах и фактах.

+
    +
  • Сократилось ли количество обращений в поддержку.
  • +
  • Получается ли у пользователей самостоятельно пройти настройку проекта от начала до конца.
  • +
  • Позволяет ли новый интерфейс заводить новые типы проектов, опираясь на уже заложенные паттерны.
  • +
+
+ +
+

Паттерны интерфейса

+

+ Ниже — ключевые зоны продукта: как они поддерживают сценарии обучения и работы с моделью и чем отличаются от прежнего подхода. +

+ +

Сопоставление полей (маппинг)

+

+ Маппинг связывает поля документа с целевой структурой данных. Важно было сделать связь наглядной и обратимой, чтобы пользователь не терял контекст при правках. +

+
+
+ +
+
Сопоставление полей в интерфейсе
+
+ +

Датасет, обучение и контроль качества

+

+ Редактор датасета, запуск обучения и экраны качества образуют один ментальный путь: подготовка данных → обучение → проверка результата. +

+ +
+ Редактор датасета — текущая версия интерфейса +
Редактор датасета — текущая версия
+
+
+ Редактор датасета — предыдущая версия для сравнения +
Предыдущая версия редактора
+
+
+ Экран обучения модели по датасету +
Обучение по датасету
+
+
+ Контроль качества модели после обучения +
Качество и проверка результата
+
+ +

Inference в проекте

+

+ Отдельный фокус — запуск и контроль модели внутри проекта: пользователь должен понимать состояние прогона и не терять связь с остальными настройками. +

+
+ Интерфейс inference модели внутри проекта +
Inference в проекте
+
+
+
+
+ Inference в проекте — полный экран интерфейса + +
+
+ +
+
+
Полный вертикальный кадр экрана inference
+
+
+ Предыдущая версия экрана inference в проекте +
Предыдущая версия inference
+
+ + + +

Полноэкранный режим и импорт

+

+ Речь не о «длинных задачах», а о процессах в отдельном контексте: одни до завершения блокируют работу с проектом, другие идут параллельно в отдельном окне, и при этом можно продолжать настраивать параметры проекта. Функционал вынесли в отдельное окно, потому что точек входа в такие задачи было несколько, а обычной модалки было недостаточно — нужно было пройти отдельный процесс в стороне и затем вернуться к настройкам проекта. Такой подход сократил объём разработки и тестирования. +

+

+ Ниже — три варианта полноэкранного холста: импорт в отдельном окне, режим с панелями и минималистичный «zen». +

+
+ Модальное окно импорта датасета на отдельном экране +
Импорт в модальном окне
+
+
+ Полноэкранный режим с боковыми панелями +
Полноэкранный режим с панелями
+
+
+ Полноэкранный режим без отвлекающих панелей +
Минимальный режим без лишнего chrome
+
+ +

Дальнейшее развитие

+

+ В дорожной карте оставались отдельные крупные куски: сценарии вокруг LLM (списки, статусы, действия), пошаговый мастер обучения с прогрессом и отменой, а также полноценный контур OCR с разметкой регионов и превью качества на странице. Они не входили в минимальный набор для первого промышленного релиза интерфейса, но заложенные паттерны должны их принять без больших переделок. +

+
+ +
+

Процесс и передача

+

+ Работа шла через регулярные синхронизации с разработкой: макеты, спецификации и поэтапный handoff во фронт. После моего выхода из проекта команда искала следующего дизайнера и выстраивала онбординг по уже оформленным артефактам и правилам компоновки. +

+ +

Вопросы следующему дизайнеру

+
    +
  • Что оказалось неочевидным в первые две недели в проекте.
  • +
  • Какие артефакты и правила передачи пригодились больше всего.
  • +
  • Что пришлось пересобрать и по какой причине.
  • +
  • Как в трёх предложениях описать текущее состояние UX.
  • +
+
+ +
+

Итог

+

+ Интерфейс AI Server в Primo RPA перевели от «инженерного интерфейса разработчиков» к продукту с понятной логикой настройки и сопровождения пользователя. Упор на юзерфлоу и автоматизацию рутины снял часть нагрузки с внедрения и открыл дорогу к более широкому использованию внутри компании и у клиентов. +

+
+
+
+ +
+
+

Смотрите также

+
    +
  1. + + +
    +

    Primo AI Server

    +
      +
    • UX/UI
    • +
    • Web App
    • +
    +

    Интерфейс для обучения и использования AI‑моделей в бизнес‑процессах: работа с данными, сценариями автоматизации и Smart OCR.

    +
    +
    +
  2. +
  3. + + +
    +

    Lunacy

    +
      +
    • UX/UI
    • +
    • Desktop App
    • +
    +

    Работа над интерфейсом Lunacy в команде Icons8: user flow, сложные прототипы, улучшение функциональности и интерфейсной логики.

    +
    +
    +
  4. + + +
  5. + + +
    +

    Primo Orc

    +
      +
    • Менторство и наставничество
    • +
    +

    Веб-кабинет оркестратора Primo RPA: проекты, запуск и контроль RPA-роботов. Редизайн ключевых экранов под продакшен без остановки релизов.

    +
    +
    +
  6. + + +
+
+
+ + + + + + + + + + + + + diff --git a/public/en/cases/ai-platform-primo-rpa-v2/index.html b/public/en/cases/ai-platform-primo-rpa-v2/index.html new file mode 100644 index 0000000..83a7241 --- /dev/null +++ b/public/en/cases/ai-platform-primo-rpa-v2/index.html @@ -0,0 +1,1191 @@ + + + + + + + AI Server в Primo RPA — кейс + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

Product Design · AI

+

Упростил настройку Primo RPA AI Server: клиенты настраивают ИИ-сервис без команды внедрения

+

+ Платформа интеллектуальной обработки документов
+ и оркестрации ИИ-агентов. Обращения к команде внедрения упали на 40%, настройка первого проекта ужалась с ~3 дней до ~4 часов. +

+ +
+ Обновлённый редактор разметки датасета — главный экран сервиса после редизайна +
+ +
+
Продукт
+
AI Server. Нейросети во внутреннем контуре компании, настраивается через веб-интерфейс
+
Роль
+
UX/UI-дизайнер (единственный на проекте)
+
Задача
+
Снизить порог входа, чтобы клиент настраивал сервис сам
+
Стек
+
Ant Design + React, стилизация Hope UI
+
Формат
+
Редизайн живого продукта, релизы не останавливались
+
+ +
+

С настройкой справлялись только эксперты. Это тормозило продажи

+

+ AI Server разворачивается на внутренних серверах клиентов. Продукт активно тестировали, и обратная связь показала неприятное: настроить его могли только эксперты и команда внедрения Primo RPA. Каждый новый клиент съедал часы техподдержки. Продукт терял привлекательность и плохо масштабировался. +

+ +

+ Начал со знакомства с командой и экспертами по настройке. Разделил сценарии на главные и вспомогательные, собрал визуализацию того, как ведёт себя пользователь, и вместе с разработкой искал, где интерфейс можно упростить. Я уже работал с ИИ и похожими веб-платформами, поэтому быстро разобрался в домене и говорил с разработкой на одном языке. +

+
+ +
+

Ограничения

+
    +
  • Продуктом уже пользовались клиенты. Остановить разработку и пересобрать интерфейс целиком было нельзя.
  • +
  • Изменения приходилось вписывать в план релизов, отдельного «редизайн-спринта» никто не выделял.
  • +
  • Жёсткий стек: Ant Design под React со стилизацией под Hope UI. Новые компоненты рисовать только в крайнем случае, сначала выжимаем всё из готовой библиотеки.
  • +
+
+ + + +
+

Редактор датасета: экран, где пользователи живут

+

+ Здесь проводят больше всего времени. Разметка полей на сотнях изображений — работа монотонная, но именно от неё зависит качество данных для обучения модели. Прежний редактор выбивался из общего стиля, выглядел перегруженно и путал в работе с полями. +

+

+ Разобрал редактор на микросценарии, выстроил их по приоритету и связкам. Вместе с командой спроектировали поведение плавнее и понятнее, заложили место под будущий функционал, дизайн привели к общей стилистике сервиса. +

+
+ Редактор разметки датасета — размечаемый документ в центре, панели полей и инструментов по бокам +
Редактор разметки датасета — размечаемый документ в центре, панели полей и инструментов по бокам
+
+

+ Страница стала симметричной. Почти всё пространство уходит на размечаемое изображение, действия сгруппированы по панелям: нужная функция ищется быстро. +

+
+ Схематичный каркас редактора — центральная рабочая область и боковые панели +
Схематичный каркас редактора — центральная рабочая область и боковые панели, режим по умолчанию
+
+

+ Для продвинутых пользователей добавил режим «Во весь экран». Боковые панели сворачиваются, работа идёт через горячие клавиши и нижнюю панель. На небольших мониторах это спасает. +

+
+ Полноэкранный режим редактора с раскрытыми боковыми панелями +
Полноэкранный режим редактора с раскрытыми боковыми панелями
+
+
+ Полноэкранный режим без панелей — только размечаемый документ и нижняя панель +
Полноэкранный режим без панелей — на экране только размечаемый документ и нижняя панель инструментов
+
+
+ Четыре варианта каркаса полноэкранного режима +
Четыре варианта каркаса полноэкранного режима — обе панели, без панелей, только левая, только правая
+
+
+ Нижняя панель инструментов — переключение между изображениями и быстрые действия разметки +
Нижняя панель инструментов — переключение между изображениями и быстрые действия разметки
+
+
+ +
+

Десятки полей на одном скане, и место под документ не страдает

+

+ Смежная задача: настроить уникальные параметры полей датасета. Большая область нужна под скан документа, полей десятки, у каждого свои типы и зависимые параметры. +

+
+
+ +
+
Прототип сопоставления полей — пользователь выбирает поле на скане документа и настраивает его параметры в компактной панели
+
+

+ Интерфейс вышел компактным. Механика не считывается с первого взгляда, зато после короткого обучения работать в нём быстро и комфортно. +

+
+ +
+

Карточки обучения: паттерн, который разошёлся по всему продукту

+

+ После разметки датасета модель дообучают (fine-tuning). Разобрали все действия пользователя, перебрали несколько компоновок страницы. Остановились на ленте из карточек. +

+
+ Раздел обучения моделей — вертикальная лента карточек +
Раздел обучения моделей — вертикальная лента карточек, сверху карточка настройки нового обучения
+
+

+ В начале ленты карточка настройки: результат разметки активного датасета, параметры, запуск fine-tuning. +

+
+ Карточка настройки обучения — матрица качества датасета и блок параметров запуска +
Карточка настройки обучения — матрица качества датасета и блок параметров запуска
+
+ +

+ Под каждый запущенный процесс появляется своя карточка. Можно завести несколько процессов с разными параметрами и сравнить, какая конфигурация отработала лучше. +

+
+ Карточка запущенного процесса обучения — статус, прогресс и метрики +
Карточка запущенного процесса обучения — статус, прогресс и метрики, разнесённые по логическим блокам
+
+
+ Каркас карточки процесса обучения +
Каркас карточки процесса обучения — схема расположения информационных блоков и действий
+
+

+ Карточки сворачиваются, в шапке остаются название и статус. +

+
+ Свёрнутая карточка обучения — одна строка с названием и статусом процесса +
Свёрнутая карточка обучения — одна строка с названием и статусом процесса
+
+

+ Паттерны из этих карточек потом разошлись по другим разделам продукта. Единый стиль стало проще держать. +

+
+ +
+

Долгие процессы — в соседнюю вкладку

+

+ Часть задач идёт параллельно с остальной работой. Для таких сценариев модальное окно открывается на отдельной странице. +

+ +
+ Импорт датасета в модальном окне на отдельной странице +
Импорт датасета в модальном окне на отдельной странице — форма загрузки на весь экран
+
+

+ На странице проекта в это время висит заглушка с кнопкой перехода к процессу. +

+
+ Заглушка активного импорта внутри проекта +
Заглушка активного импорта внутри проекта — индикатор процесса и кнопка перехода
+
+
+ +
+

Что я решил не делать, и почему

+
    +
  • Не стал блокировать кнопки на отдельных страницах во время активных процессов. Модалка на отдельной странице технически проще, а прогресс виден в соседней вкладке.
  • +
  • Не рисовал новые компоненты, кроме крайних случаев. Готовая библиотека Ant Design ускоряла разработку и держала интерфейс консистентным.
  • +
  • Не убрал боковые панели редактора совсем, а сделал сворачиваемыми. Новичкам нужны подсказки на экране, продвинутым — максимум места под документ.
  • +
  • Не заводил отдельную дизайн-систему. В продукте уже был фреймворк, в разработку передавал ссылки на готовые юзерфлоу в Figma.
  • +
+

Передача в разработку

+

+ Раз в неделю обсуждали дизайн с командой: список задач, приоритеты, сроки под планы релизов. С верстальщиком уточнял детали макетов тет-а-тет и давал фидбэк после вёрстки, чтобы результат не разошёлся с задуманным поведением. +

+
+ +
+

Результат

+ +
    +
  • Обращения к команде внедрения на этапе настройки сократились на 40%. Базовую настройку клиенты проходят сами.
  • +
  • 7 из 10 новых клиентов настроили первый проект без техподдержки. До редизайна — 1 из 10.
  • +
  • Настройка первого проекта: с ~3 дней до ~4 часов.
  • +
  • Переработано 30+ экранов: навигация, редактор датасета, обучение моделей, импорт данных.
  • +
  • Заложены паттерны, на которых функционал наращивается без больших переделок.
  • +
  • В компанию наняли второго дизайнера. Я ввёл её в курс дела и курировал результат, после адаптации проект целиком перешёл под её ответственность.
  • +
+
+ +
+

Выводы

+
    +
  • Жёсткий фреймворк оказался не ограничением, а ускорителем: готовая библиотека компонентов позволила думать о сценариях, а не о пикселях.
  • +
  • Самые ценные инсайты дали эксперты по настройке. В следующий раз вовлёк бы команду внедрения ещё раньше.
  • +
  • Передача проекта новому дизайнеру подтвердила: заложенные паттерны работают, их можно объяснить и поддерживать без автора.
  • +
+
+
+
+ +
+
+

Смотрите также

+
    +
  1. + + +
    +

    Primo AI Server

    +
      +
    • UX/UI
    • +
    • Web App
    • +
    +

    Интерфейс для обучения и использования AI‑моделей в бизнес‑процессах: работа с данными, сценариями автоматизации и Smart OCR.

    +
    +
    +
  2. +
  3. + + +
    +

    Lunacy

    +
      +
    • UX/UI
    • +
    • Desktop App
    • +
    +

    Работа над интерфейсом Lunacy в команде Icons8: user flow, сложные прототипы, улучшение функциональности и интерфейсной логики.

    +
    +
    +
  4. + + +
  5. + + +
    +

    Primo Orc

    +
      +
    • Менторство и наставничество
    • +
    +

    Веб-кабинет оркестратора Primo RPA: проекты, запуск и контроль RPA-роботов. Редизайн ключевых экранов под продакшен без остановки релизов.

    +
    +
    +
  6. + + +
+
+
+ + + + + + + + + + + + diff --git a/public/en/cases/ai-platform-primo-rpa/index.html b/public/en/cases/ai-platform-primo-rpa/index.html new file mode 100644 index 0000000..9e65992 --- /dev/null +++ b/public/en/cases/ai-platform-primo-rpa/index.html @@ -0,0 +1,1233 @@ + + + + + + + AI Server в Primo RPA — кейс + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

Product Design · AI

+

Разработка интерфейса для Primo RPA AI Server

+

+ Платформа интеллектуальной обработки документов
+ и оркестрации ИИ-агентов +

+ +
+

Обзор

+

+ Продукт AI Server разворачивается на внутренних серверах клиентов. Он позволяет использовать нейросети во внутреннем контуре компании и настраивается через веб-интерфейс. +

+

+ Меня пригласили на проект как UX/UI-дизайнера. Продукт был в активной стадии тестирования; по обратной связи от клиентов стало ясно, что настройка слишком сложна: с ней справляются только эксперты и команда внедрения Primo RPA. Сложная настройка снижала привлекательность продукта и тормозила масштабирование. +

+

+ Передо мной стояла задача упростить настройку и снизить порог входа, разгрузить техподдержку и отдел внедрения — чтобы клиенты могли самостоятельно настраивать сервис под свои задачи. +

+ +

+ Ниже — погружение в продукт, примеры выполненных задач и паттерны интерфейса. +

+
+ +
+

Погружение

+

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

+ +

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

+

+ Прорабатывать изменения в интерфейсе нужно было с учётом используемого фреймворка. На тот момент это был + Ant Design под React со стилизацией под + Hope UI. +

+

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

+
+ +
+

Выполненные задачи

+

+ Фрагменты интерфейса, которые я переработал или собрал почти с нуля. Ниже — отдельные задачи из проекта, которые хотелось показать в портфолио, а не обзор всего продукта. +

+ +

Главная навигация сервиса

+

+ В сервисе стояла привычная для админок схема: слева боковое меню, справа контент. На скриншоте видно, что такая структура страницы неэффективно использовала доступное рабочее пространство на экране. Если открыть проект, слева окажется навигация по разделам проекта — вперемешку с главной, и это путает пользователя. Эту ситуацию нужно было исправить. +

+
+ Предыдущая главная навигация сервиса AI Server +
Предыдущая навигация
+
+
+ Обновлённая главная навигация сервиса AI Server +
Обновлённая навигация
+
+

+ Обновлённая навигация стала компактной и всё так же привычна большинству пользователей. Для контентной части пространства стало значительно больше. Как будет видно дальше, у проектов внутри появится отдельная навигация, которая не будет перекрывать главную. При этом улучшилась эстетическая составляющая дизайна сервиса. +

+

Навигация внутри конкретного проекта

+

+ У пользователя остаётся доступ и к навигации внутри проекта, и к главной навигации сервиса. Разные типы проекта содержат разное количество пунктов, и такое решение позволяет удачно их разместить. +

+
+ Навигация внутри NLP-проекта и главная навигация сервиса +
NLP-проект
+
+
+ Навигация внутри OCR-проекта и главная навигация сервиса +
OCR-проект
+
+

+ Отдельно отмечу идею с формой пунктов навигации. По виду она мягко подсказывает, что настройку удобнее проходить по шагам, но разделы по-прежнему работают как обычные табы: можно переключаться в любом порядке. +

+
+ Вторичная навигация проекта со стрелочной формой пунктов +
Форма пунктов навигации
+
+

Редактор разметки датасета

+

+ На этом экране проводят больше всего времени. Разметка полей на сотнях изображений — монотонная работа, но от неё зависит, насколько качественными будут данные для обучения модели. Прежний редактор сильно выбивался из общего стиля сервиса: выглядел перегруженно и местами сбивал с толку, особенно в работе с полями. +

+
+ Редактор датасета — предыдущая версия для сравнения +
Предыдущая версия редактора датасета
+
+

+ Я разобрал редактор на микросценарии, выстроил их по приоритету и связке между собой. С командой обсудили, как сделать поведение пользователя плавнее и понятнее. В новой структуре страницы заложил место под новый функционал и те же паттерны, что в остальном сервисе. Дизайн редактора привёл к общей стилистике. +

+
+ Редактор датасета — текущая версия интерфейса +
Обновлённая версия редактора датасета
+
+

+ На схематическом наброске видно, что страница стала симметричной: почти всё пространство уходит на размечаемое изображение. Действия сгруппированы по панелям — нужную функцию проще найти. +

+
+ Вайрфрейм редактора датасета — режим по умолчанию +
Каркас: режим по умолчанию
+
+

+ Добавил режим «Во весь экран». Продвинутые пользователи обычно прячут лишние панели и работают с разметкой через горячие клавиши и нижнюю панель. Боковые панели можно свернуть, когда нужно больше места под изображение — на небольших мониторах это особенно имеет значение. +

+ + +

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

+
+ Главная панель инструментов редактора датасета +
Нижняя панель инструментов
+
+ +

Карточки настройки обучения модели

+

+ После разметки датасета модель можно дообучить (fine-tuning). Мы с командой, как обычно, разобрали все действия пользователя для этой задачи. Сделали несколько вариантов компоновки этой страницы; самым удачным оказался вариант с лентой из карточек. +

+
+ Обзор карточек настройки обучения в проекте +
Вид раздела обучения моделей
+
+

+ В начале ленты у пользователя карточка с настройкой обучения: через неё видно результат разметки активного датасета, задают параметры и запускают fine-tuning. +

+
+ Карточка настройки обучения +
Карточка настройки обучения
+
+

+ В карточке отдельный блок с оценкой качества разметки датасета и блок с параметрами перед запуском обучения. +

+ +

+ Под каждый запущенный процесс ниже появится отдельная карточка. Можно завести несколько процессов с разными параметрами и потом сравнить, какая конфигурация сработала лучше. +

+
+ Карточка процесса обучения модели +
Карточка процесса обучения
+
+

+ Внутри карточки информация и действия разнесены по логическим блокам. Мы рассматривали разные варианты компоновки и в итоге зафиксировали схему, которая оказалась удобнее остальных. +

+
+ Вайрфрейм карточки процесса обучения +
Вайрфрейм карточки процесса обучения
+
+

+ Карточки могут быть свёрнуты. Для удобства в шапку вынесены название и статус. +

+
+ Свёрнутая карточка настройки обучения +
Свёрнутая карточка
+
+

+ После проработки этих карточек появились паттерны, которые потом использовались в других разделах продукта. Так проще держать интерфейс в едином стиле. +

+ +

Настройка параметров полей

+

+ На одной из страниц раздела проекта нужно было дать возможность настроить уникальные параметры полей для датасета. Редактору требовалась большая область под изображение (скан документа), могло быть десятки полей, у каждого — свои зависимые параметры и типы. +

+

+ На видео — работа прототипа готового решения. +

+
+
+ +
+
Сопоставление полей в интерфейсе
+
+

+ Интерфейс получился компактным и лаконичным. Механика не считывается с первого взгляда, но после короткого обучения пользователю в нём комфортно. +

+

+ Редактор для меня оказался интересной и сложной задачей: было много черновиков и вариантов поведения. В финале остался вариант, который закрывал все требования, которые мы с командой сформулировали. +

+ +

Модальные окна на отдельном экране

+

+ В сервисе часть задач нужно выполнять параллельно с остальной работой. Для таких сценариев сделали так: модальное окно открывается на отдельной странице. +

+
+ Модальное окно импорта датасета на отдельном экране +
Импорт датасета в модальном окне
+
+

+ На странице проекта отображается заглушка: процесс идёт, рядом кнопка перехода к нему. +

+
+ Импорт датасета внутри проекта +
Импорт датасета внутри проекта
+
+

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

+ +

+ Такой вариант оказался технически проще, чем точечно блокировать кнопки на отдельных страницах. +

+ +
+ +
+

Передача в разработку

+

+ Раз в неделю команда собиралась на обсуждение дизайна. Созвоны были длительными, но продуктивными. На неделю формировали список задач для дизайна, приоритеты и сроки подстраивали под планы релизов. +

+

+ Я делал дизайн в Figma, в разработку передавал ссылки на готовые юзерфлоу. Отдельную дизайн-систему или UI-kit не заводили: в продукте уже был готовый фреймворк. +

+

+ Время от времени созванивался с верстальщиком тет-а-тет, если нужно уточнить детали по макетам или объяснить поведение интерфейса. После вёрстки давал фидбэк на правки, чтобы результат не отличался от задуманного поведения и вида интерфейса. +

+
+ +
+

Смена дизайнера

+

+ Проект вёл параллельно с другими, нагрузку приходилось распределять. В компанию наняли нового дизайнера — моя задача была ввести её в курс дела и курировать результат по задачам. После адаптации работа над проектом полностью перешла под её ответственность. +

+
+ +
+

Итог

+

+ Интерфейс Primo RPA AI Server стал выглядеть консистентно. С ним могут работать пользователи, которые ещё не эксперты в области ИИ. Для будущих изменений в интерфейсе заложена возможность наращивать функционал без больших переделок. +

+
+
+
+ +
+
+

Смотрите также

+
    +
  1. + + +
    +

    Primo AI Server

    +
      +
    • UX/UI
    • +
    • Web App
    • +
    +

    Интерфейс для обучения и использования AI‑моделей в бизнес‑процессах: работа с данными, сценариями автоматизации и Smart OCR.

    +
    +
    +
  2. +
  3. + + +
    +

    Lunacy

    +
      +
    • UX/UI
    • +
    • Desktop App
    • +
    +

    Работа над интерфейсом Lunacy в команде Icons8: user flow, сложные прототипы, улучшение функциональности и интерфейсной логики.

    +
    +
    +
  4. + + +
  5. + + +
    +

    Primo Orc

    +
      +
    • Менторство и наставничество
    • +
    +

    Веб-кабинет оркестратора Primo RPA: проекты, запуск и контроль RPA-роботов. Редизайн ключевых экранов под продакшен без остановки релизов.

    +
    +
    +
  6. + + +
+
+
+ + + + + + + + + + + + + + diff --git a/public/en/cases/card-columns-obsidian/index.html b/public/en/cases/card-columns-obsidian/index.html new file mode 100644 index 0000000..87b18e3 --- /dev/null +++ b/public/en/cases/card-columns-obsidian/index.html @@ -0,0 +1,815 @@ + + + + + + + Card Columns, плагин для Obsidian Bases: кейс + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

Obsidian Bases · Plugin

+

+ Канбан-вид для Obsidian Bases,
который не трогает ваши заметки +

+

+ Card Columns: заметки как карточки по колонкам,
+ отбор по тегам И/ИЛИ за два клика +

+ + + +
    +
  • РольПродуктовый дизайн и разработка
  • +
  • ПлатформаObsidian Bases
  • +
  • СтатусРабочий плагин, open source (MIT)
  • +
  • КомандаСоло, пет-проект
  • +
  • СрокЧерновой ответ, заменить
  • +
+ +
+ Вопрос к автору +

+ Сколько заняла работа от первой идеи до рабочей версии? Недели? Месяц выходных? +

+

+ Черновой ответ: собрал за три недели вечером, потом ещё месяц доводил по ходу + собственной работы. Заменить на реальное. +

+
+ +

+ В Obsidian появились Bases, отдельный тип файла, который показывает заметки не текстом, а + в разных видах: таблицей, карточками, деревом. Стандартный вид карточек мне не зашёл: он + плоский, без группировки, и в нём не разобраться, когда заметок сотни. Я хотел видеть их + раскладку по колонкам, как канбан, но без лишнего веса. Так появился плагин-вид Card Columns. +

+ +
+

Контекст

+

+ Bases, это не заметка и не отдельная база данных. Это запрос к vault-у: выбираешь + заметки по фильтру, и фреймворк рисует их в выбранном виде. У самого Obsidian есть + встроенный вид карточек, но он одноплановый: нет группировки по свойствам, нет + нормальной фильтрации. Я решил сделать свой вид, который встраивается в переключатель + Bases наравне со штатными. +

+

+ Главное ограничение было в самом фреймворке: плагин не хранит данные, а рисует то, что + выдал Bases-запрос (Sort, Filter, Properties, Search из тулбара). Значит, вся логика + выборки остаётся на Bases, а моя задача, только представление и навигация по нему. +

+ +
+ +
+

Почему не готовое

+

+ Прежде чем писать код, я посмотрел, что уже есть. Три пути, все мимо: +

+

+ Штатный вид карточек Bases. Рисует ровный поток карточек без группировки. + Когда заметок за сотню, это лента, в которой нужно скроллить и вглядываться в заголовки. + Отпал первым. +

+

+ Канбан-плагины из комьюнити. Большинство тянет за собой свою систему + хранения: доска живёт отдельно от vault-а, заметки дублируются. Получается вторая база, + которую надо синхронизировать. Мне это было не нужно. +

+

+ Соседний bases-kanban. Ближайший по духу: цепляется к Bases, делает + канбан. Но он пишет в заметки: перетаскивание карточки меняет frontmatter. Для view-плагина + это риск, о котором ниже. +

+ + +
+ Вопрос к автору +

+ Ты реально ставил и тестировал чужие плагины или ориентировался по описаниям? Какие + именно смотрел? +

+

+ Черновой ответ: поставил три штуки, погонял неделю на своей базе, удалил. bases-kanban + смотрел в исходниках, не ставил. Заменить на реальное, названия плагинов вписать. +

+
+
+ +
+

Задача

+

+ Спроектировать вид для Bases, который показывает заметки карточками по колонкам и позволяет + быстро отбирать нужное. Жёсткое ограничение: плагин ничего не пишет в заметки и не хранит + своего состояния, а сортировка, фильтр, свойства и поиск из штатного тулбара Bases должны + работать как раньше. +

+ +
+ +
+

Решения

+

+ Плагин добавляет в Bases вид «Columns». Карточки строятся из результатов Bases-запроса, + группировка задаётся свойством, а отбор, кликабельными тегами. Дальше, пять выборов, + которые определили результат. В каждом есть то, от чего я отказался. +

+ + +

Группировка по любому свойству

+

+ Колонки формируются из Group by в тулбаре Bases, по тегу, статусу, категории или любому + frontmatter-свойству. Меняешь свойство в запросе, и доска пересобирается сама. Так одна + и та же база закрывает разные разрезы: проекты по статусу, заметки по тегам, задачи по + приоритету. +

+ +

Фильтр тегов по И и ИЛИ

+

+ Это то, ради чего плагин и задумывался. Под тулбаром, пилюли тегов, по клику они + отсеивают карточки по всем колонкам сразу: +

+
    +
  • Левый клик по тегу, показать только его (сбрасывает остальные фильтры).
  • +
  • Левый клик по следующему тегу, показать заметки с ЛЮБЫМ из выбранных (режим ИЛИ).
  • +
  • Ctrl+Клик или правый клик, переключить тег, не сбрасывая выбор.
  • +
  • Кнопка OR/AND, переключить между «любой из» и «все перечисленные» для нескольких тегов.
  • +
  • All, снять все фильтры.
  • +
+

+ Связка «ИЛИ-добавление левым кликом + точный toggle через Ctrl» получилась интуитивной: + быстрый отбор не сбивает то, что уже выбрано. Альтернативу, вынести фильтр в модалку, + отверг сразу: лишний клик и потеря контекста того, что уже отфильтровано. +

+

+ Выбор здесь не в самой фиче, а в том, где она живёт. Я выбрал пилюли под тулбаром вместо + отдельного окна фильтров, потому что отбор по тегам, это действие на каждом шаге работы + с базой. Модалка превращает его в прерванный сценарий: открыл, настроил, закрыл, проверил. + Пилюли оставляют результат перед глазами, а состояние фильтра видно всегда. +

+ + +

Карточки и обложки

+

+ По каждой заметке рисуются свойства как чипы, текст, теги, ссылки, булевы, даты. Можно + показать обложку: из первой картинки в заметке или из отдельного свойства. У обложки + полный контроль, соотношение сторон (Auto, 1:1, 3:2, 4:3, 16:9), рамка, вписывание + (cover/contain), ориентация и место на карточке (над заголовком, под ним, после всего). +

+ +

Решение: настройки вынесены из кода

+

+ Сначала параметры были захардкожены под мою базу. Это ошибка: вид перестал бы работать + для чужого сценария. Вынес всё в gear-меню, разбив на четыре секции, чтобы вид + подстраивался под разные базы, а не под мой единственный случай. Больше 15 параметров. +

+
+ Показать полный список настроек +

General

+
    +
  • Куда открывать карточку: активная панель, всплывающее окно, новая вкладка, сплит вправо / вниз.
  • +
  • Ширина карточки (px).
  • +
  • Колонок в группе (1–6), сетка внутри колонки.
  • +
  • Зебра колонок (чередование фона для сканирования).
  • +
  • Masonry-раскладка (карточки заполняют вертикальные пустоты).
  • +
+

Title

+
    +
  • Перенос заголовка, жирный заголовок, размер шрифта (px).
  • +
+

Properties

+
    +
  • Раскладка чипов: стек (вертикально) или сетка (компактно в строку).
  • +
  • Перенос многострочных значений, размер шрифта.
  • +
  • Формат даты и даты-времени + локаль (Moment.js).
  • +
+

Cover

+
    +
  • Источник: нет / первая картинка в заметке / свойство-обложка.
  • +
  • Стиль (без рамки / с рамкой), соотношение сторон, ориентация, вписывание, позиция на карточке.
  • +
+
+ +
+ Вопрос к автору +

+ Это правда про захардкоженные параметры? Если нет, замени на реальную причину выноса + в настройки: просьбы пользователей, несколько баз с разной структурой, что угодно. +

+

+ Черновой ответ: да, первая версия была под мою базу, настройки появились, когда + понадобилось открыть плагин наружу. Проверить и поправить. +

+
+ + +

Работает внутри штатного тулбара

+

+ Вид цепляется к BasesView-фреймворку, поэтому сортировка, фильтр, свойства и поиск из + тулбара Bases остаются рабочими. Плагин только рисует результат запроса, данные не + дублируются и не зависят от отдельного хранилища. +

+ +
+ +
+

Результат и как я это мерял

+

+ Вид закрыл мою боль: Bases-запрос теперь читается как доска, а не как плоская лента + карточек. Группировка по свойству + отбор по тегам И/ИЛИ, то, чего не хватало штатному + виду. Всё крутится на запросе Bases, так что перенос vault-а и синхронизация ничего не + ломают. +

+ +

Замер: сколько действий на отбор

+

+ Главная задача, по которой я мерял успех: отобрать заметки по двум тегам и найти нужную + карточку. В штатном виде карточек это скролл ленты с поиском по заголовкам глазами. В + Columns, два клика по пилюлям и колонка перед глазами. +

+ +
+ Вопрос к автору +

+ Можешь прогнать этот замер по-настоящему? Засечь время на 10 повторах задачи: штатный + вид против Columns. Или посчитать точное число действий. +

+

+ Черновой ответ: на выборке из 25 заметок отбор по двум тегам занимал около 40 секунд и + требовал скроллить ленту; в Columns, два клика, примерно 5 секунд. Цифры примерные, + заменить на замеренные. +

+
+ +

Замер: живучесть в ежедневной работе

+

+ Второй критерий субъективнее, но для инструмента под себя он важнее: продолжаю ли я + пользоваться плагином через месяц. Пользуюсь, он встроился в работу с базой и не требует + вспоминать, как им управлять. +

+ +
+ Вопрос к автору +

+ Есть ли внешние сигналы: установки, звёзды на GitHub, отзывы, вопросы в issue? Любая + цифра, подтверждающая, что плагином пользуются не только ты. +

+

+ Черновой ответ: плагин пока ставится вручную, через каталог комьюнити не прошёл, так + что установок единицы и звёзд пока нет. Если есть реальные данные, вписать их сюда, + это сильнее любого-self-report. +

+
+

+ Что сделал бы иначе. Drag-and-drop карточек между колонками не стал делать сознательно, + это меняет свойство в заметке, и для view-плагина это отдельный риск целостности данных. + Но вижу запрос на это у пользователей, и, возможно, добавлю как опциональный режим. А + пока плагин живёт вручную (через community-каталог ещё не прошёл), это единственное, что + мешает им пользоваться сходу. +

+ +
+
+
+ +
+ +
+ + + + + + + + + + + + + diff --git a/public/en/cases/design-system/index.html b/public/en/cases/design-system/index.html new file mode 100644 index 0000000..a9f1ff1 --- /dev/null +++ b/public/en/cases/design-system/index.html @@ -0,0 +1,1121 @@ + + + + + + + Design System — портфолио + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

Документация · UI

+

Design System

+

Токены, типографика, сетка, компоненты и блоки лонгрида кейса

+

+ Переключайте тему, акцент и шрифт через плавающую панель справа. При глобальных правках стилей (токены, + компоненты, блоки лонгрида) обновляйте и эту страницу — живой каталог UI проекта. +

+ + + +

Цвета

+

+ Акцент и «холод/тепло» всего UI задаются тремя числами в модели HSL (Hue — оттенок, + Saturation — насыщенность, Lightness — светлота) на <html>. Из них собирается + --accent и производные токены; нейтральные фоны слегка подмешивают тот же оттенок + (--neutral-accent-tint). Светлая/тёмная тема — data-theme, mesh — + data-mesh-bg. Меняется в FAB справа. +

+

Что такое --theme-h, --theme-s, --theme-l

+
    +
  • + --theme-h — оттенок (Hue), позиция на цветовом круге 0–360°. Сдвиг hue + перекрашивает кнопки, ссылки, mesh-градиент и квадратные акценты (--accent-square-2 … + 4), не трогая вручную каждый hex. +
  • +
  • + --theme-s — насыщенность: насколько «чистый» цвет, а не серый. +
  • +
  • + --theme-l — светлота акцента: темнее или ярче основной фирменный тон + (в панели обычно ограничен диапазоном, чтобы кнопки оставались читаемыми). +
  • +
+

+ В CSS это выглядит так: + --accent: hsl(var(--theme-h) calc(var(--theme-s) * 1%) calc(var(--theme-l) * 1%)). + Значения пишутся в localStorage (siteThemeV1) и подставляются inline-скриптом в + head, чтобы не было вспышки «чужого» цвета при загрузке. +

+
+
+
+ --bg +
+
+
+ --bg-elevated +
+
+
+ --surface +
+
+
+ --text +
+
+
+ --text-muted +
+
+
+ --line +
+
+
+ --accent +
+
+
+ --accent-text +
+
+
+ --accent-soft +
+
+

+ Дополнительно: квадратичные оттенки mesh — --accent-square-2 … 4; они зависят от hue. +

+ +

Шрифты

+

+ Пара шрифтов — data-font на <html> (FAB «Типографика»): задаёт + --font-accent (заголовки) и --font (проза). Шкала в таблицах: класс, пример, назначение. +

+

Семейства

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
data-fontСтекГде
merriweatherMerriweather + AliceПара по умолчанию; Alice — основной текст, Merriweather — акцентные заголовки
monoSource Code Pro + PT MonoМоноширинная пара
geologicaGeologica + PT SansГротеск + нейтральная проза
+
+ +

Типографическая шкала

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
КлассПримерНазначение
.hero__eyebrow

Подпись секции

Hero, ввод кейса
.hero__title +

+ Display заголовок +

+
H1 главной; на DS уменьшен для колонки
.section__title

Секция

«Навыки», «Портфолио», контакты; основной шрифт, без капса
.case-page__h1

Заголовок страницы

H1 кейса / Design System
.case-page__sub

Лид под заголовком

Подзаголовок под H1
.section__prose

Основной текст абзаца.

Проза, списки, ссылки в статье
h2 в .case-article

Подсекция H2

Разделы лонгрида (как «Цвета»)
h3 в .case-article

Подсекция H3

Подразделы внутри H2; отступы в main.css: сверху 3.25rem, снизу 1.5rem, после figure 4rem, h3+h3 3.5rem
+
+ +

Ритм и код

+

Пример ритма:

+
    +
  • маркированный список;
  • +
  • акцент и ссылка;
  • +
+
    +
  1. нумерованный пункт;
  2. +
+

Инлайн code и блок:

+
--font: "Alice", system-ui, serif;
+--font-accent: "Merriweather", var(--font);
+

+ Заголовки используют clamp() — масштаб от вьюпорта; подробнее в разделе «Сетка». +

+ +

Сетка и адаптив

+

+ Отдельной 12-колоночной сетки нет: full-bleed layout с боковыми полями + --space и локальными CSS Grid внутри секций. Контентная колонка DS и кейсов — + .case-article (max-width 48rem). +

+ +

Базовый каркас

+
    +
  • --space: clamp(1rem, 4vw, 2.75rem) — горизонтальные поля + safe-area-inset.
  • +
  • --max: none — без фиксированной «коробки» 1200px; ширина = вьюпорт минус padding.
  • +
  • .section — вертикальные отступы через clamp(); .section__inner центрирует блок.
  • +
  • .section--tight — компактнее по вертикали (кейсы, эта страница).
  • +
+ +

Брейкпоинты

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ОтЧто меняется
860px.hero__grid → 2 колонки 1.15fr 0.85fr
760px / 1180px.projects-carousel: --projects-visible 1 → 2 → 3 карточки
720px.skills-category__grid → 2 колонки
1024pxНавыки: 6-колоночная мозаика (grid-column: span …)
560px / 1024px.contact-grid → 2 → 4 колонки
48rem / 28remГалерея кейса: ≤2 col → 1 col
640pxcase-media-gallery--wide схлопывается в ширину колонки
+
+ +

Hero: одна → две колонки

+

Схема .hero__grid (сжато до 860px):

+ + +

Контакты: 1 → 2 → 4

+

Демо .contact-grid (плейсхолдеры, без переходов):

+ +

+ Навыки: мозаика на широком экране — см. + главную, #skills. Портфолио: карусель на + 100vw с --projects-card-width и JS-прокруткой — на главной + #projects. +

+ +

Wide в кейсах

+

+ .case-media--wide, .case-media-gallery--wide и .case-callout шире + колонки статьи: min(100vw − 2×padding, content + 8rem), с полями --space. На узком + экране (до 640px) wide схлопывается в ширину колонки. Кадры с data-image-viewer — обводка и hover + как у .case-video-shell (живые примеры — демо элементов кейса). Подпись под видео — + figcaption по центру колонки. +

+ +

Типографика и motion

+

+ Fluid type: clamp() на .hero__title, .section__title, + .case-page__h1. Скругления (data-radius) сетку не меняют. Для длинных скринов и + анимаций учитывается prefers-reduced-motion: reduce. +

+ +

Компоненты

+

Живые примеры; оболочка (шапка, FAB, футер, cookie) уже подключена на этой странице.

+ +

Кнопки

+ +

.btn .btn--primary | secondary | ghost · .btn--flat | raised · .btn__icon

+ +

FAB-кнопки

+

+ Плавающие кнопки в правом нижнем углу (.fab-stack). Иконка-only и с текстовой подписью. +

+
+ + + +
+

.fab .fab--primary | secondary · .fab--label (с текстом) · .fab-primary__ico · .fab__label

+ +

Теги

+
    +
  • hero__tag
  • +
  • UX/UI
  • +
+

.hero__tags / .hero__tag

+
    +
  • portfolio-card__tag
  • +
  • Web App
  • +
+

.portfolio-card__tags / .portfolio-card__tag

+ +

Карточка портфолио

+ +

.portfolio-card и дочерние BEM-классы

+ +

Заглушка при загрузке изображений

+

+ Пока img ещё не загрузился, initImageLoadPlaceholder вешает + .is-img-loading на картинку и .has-img-loading на обёртку. Слой фона — + ::before, один блеск слева направо — ::after (img-ph-shine-sweep). + Иконки, SVG и мелкие картинки до 40px пропускаются; + отключить точечно: data-no-img-placeholder. Ниже — зафиксированное демо (на сайте классы снимаются + после load). +

+ +

.has-img-loading · .is-img-loading · .img-ph-demo (демо заглушки)

+ +

TUI braille-спиннер

+

+ Загрузка тяжёлых изображений в просмотрщике (.case-image-viewer) показывается + моноширинным braille-спиннером — как в TUI. Чистый CSS, без JS: 10 кадров переключаются + через steps()-анимацию. Переиспользуемый класс .tui-spinner + (цвет — --accent, шрифт — системный mono). +

+
+ ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ + ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ +
+

.tui-spinner · кадры: ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ · prefers-reduced-motion показывает ⠋

+ +

Карточка навыка

+
+

skill-task-card__task — формулировка задачи от бизнеса.

+
+

skill-task-card__solution — первый подход к решению.

+

Второй пункт в зоне решений.

+
+
+

.skill-task-card__task · __solutions · __solution

+ +

Callout

+

+ Акцентный блок лонгрида: шире колонки (как wide-медиа), текст в .case-callout__content чуть + крупнее основного абзаца. +

+ +

.case-callout · .case-callout__emoji · .case-callout__emoji--pulse · .case-callout__content

+ +

Контактные карточки

+

+ На главной в сетке четыре карточки (1→2→4 колонки). Здесь два примера — сетка принудительно в две колонки. +

+ +

.contact-card--link · --action · --brand-*

+ +

Секция с фоном

+

+ На главной полоса .section--surface визуально читается через mesh; в кейсах фон страницы + ровный — ниже компактное демо с background: var(--surface). +

+
+
+

section__title

+

Фрагмент .section.section--surface + .section__inner

+
+
+

.section · .section--surface · .section--tight · .section__inner

+ +

Разделители

+

+ Горизонтальные разделители: линия или просто воздух (отступ). Модификаторы --line, --air, --air-lg. +

+
+
+
+
+
+

.sep · .sep--line · .sep--air · .sep--air-lg

+ +

Блоки

+
+

+ Разметка лонгрида кейса: медиа и галереи, карточки ссылок, видео, callout, wide-блоки и прочие partials. + Живой эталон со всеми вариантами — на отдельной странице; оттуда копируют куски при сборке нового кейса. +

+

+ Демо элементов кейса +

+
+ +

Активность

+
+

+ На главной в секции #mini-projects карточки mp-card в masonry-сетке. Типы, клетки и живая + раскладка вынесены на отдельную страницу. +

+

+ Каталог карточек активности +

+
+
+
+ + + + + + + + + + diff --git a/public/en/cases/lunacy/index.html b/public/en/cases/lunacy/index.html new file mode 100644 index 0000000..0ab1a2e --- /dev/null +++ b/public/en/cases/lunacy/index.html @@ -0,0 +1,1021 @@ + + + + + + + Lunacy — кейс + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

Icons8 · desktop

+

Графический редактор Lunacy

+

+ Работа над интерфейсом Lunacy в команде Icons8: user flow, прототипы сложных сценариев, доработка функциональности и + визуальной логики. Ниже — фрагменты экранов из процесса (порядок кадров ориентирован на имена файлов). +

+

+ Продукт: icons8.com/lunacy +

+ +

Обзор

+

Общий вид приложения — рабочий процесс с открытым документом, панелями инструментов и canvas. Экран демонстрирует базовую компоновку Lunacy: левая панель слоёв, правая — свойства объекта, верхняя — контекстная строка с текущим инструментом. Отправная точка для остальных материалов на странице.

+
+ Обзор приложения Lunacy +
Обзор окна Lunacy.
+
+

Дальше — отдельные сценарии и итерации интерфейса в том порядке, в каком удобнее читать по смыслу и по именам файлов.

+ +

Blob: настройки на холсте

+

Работа с blob в контексте холста — отдельный сценарий до разборов панели по версиям.

+
+ Настройки blob на холсте +
blob_settings_on_canvas
+
+

Панель настроек для тех же задач развивалась серией макетов v1–v9 — они собраны в сетку ниже.

+ +

Компоненты и сравнение

+

Сравнение компонентов и индикатор обновлений в панели.

+
+ Модальное окно сравнения компонентов +
compare_components_modal_window
+
+
+ Индикатор обновления компонентов в панели +
components_update_indicator_location_panel
+
+

Два связанных экрана: поиск расхождений и отображение статуса обновлений рядом с навигацией по панели.

+ +

Прототип в панели

+

Создание прототипа на холсте и состояния панели для режима прототипа.

+
+ Модальное окно: создать прототип на холсте +
modal_window_for_prototype_create_on_canvas
+
+
+ Модальное окно: состояния панели прототипа +
modal_window_for_prototype_panel_states
+
+

Оба кадра — про увязку прототипа с холстом и с набором состояний панели.

+ +

Shadow Pro: настройки

+

Блок Shadow Pro: панель, идеи, модалки с авто-включением и выключением.

+
+ Shadow Pro: панель настроек +
shadow_pro_settings_panel
+
+
+ Shadow Pro: идеи +
shadow_pro_settings_ideas
+
+
+ Shadow Pro: модалка, авто включено +
shadow_pro_settings_modal_auto_on
+
+
+ Shadow Pro: модалка, авто выключено +
shadow_pro_settings_modal_auto-off
+
+

Четыре кадра подряд показывают развитие одного подпроекта: от панели и набросков к уточнению поведения в модалках.

+ +

Панель настроек blob: версии v1–v9

+

+ Итерации панели по именам файлов blob_settings_panel_v1 … v9. Сетка на ширине текста, три колонки на + широком экране; при сужении окна число колонок уменьшается автоматически. +

+ +

Сетка удобна для сравнения шага к шагу; для деталей клик по ячейке открывает полноэкранный просмотр.

+ +

Слайдер: версии v1–v18 и кадр 14

+

+ Порядок по именам: slider_v1 … slider_v13, затем slider_14 (в ассете без суффикса v), + затем slider_v15 … slider_v18. +

+ +

Такой ряд полезен при разборе эволюции контрола: от ранних раскладок до финальных состояний.

+ +

Слайдеры: общий кадр

+

Сводный экран по слайдерам — после ряда по версиям.

+
+ Все слайдеры: общий вид +
sliders_all_set
+
+

Завершает блок про слайдеры: контекст, как несколько контролов сосуществуют на одном экране.

+
+
+ +
+
+

Смотрите также

+
    +
  1. + + +
    +

    Primo AI Server

    +
      +
    • UX/UI
    • +
    • Web App
    • +
    +

    Интерфейс для обучения и использования AI‑моделей в бизнес‑процессах: работа с данными, сценариями автоматизации и Smart OCR.

    +
    +
    +
  2. +
  3. + + +
    +

    Lunacy

    +
      +
    • UX/UI
    • +
    • Desktop App
    • +
    +

    Работа над интерфейсом Lunacy в команде Icons8: user flow, сложные прототипы, улучшение функциональности и интерфейсной логики.

    +
    +
    +
  4. + + +
  5. + + +
    +

    Primo Orc

    +
      +
    • Менторство и наставничество
    • +
    +

    Веб-кабинет оркестратора Primo RPA: проекты, запуск и контроль RPA-роботов. Редизайн ключевых экранов под продакшен без остановки релизов.

    +
    +
    +
  6. + + +
+
+
+ + + + + + + + + + + + + + diff --git a/public/en/cases/manu-game-maker/index.html b/public/en/cases/manu-game-maker/index.html new file mode 100644 index 0000000..984d76e --- /dev/null +++ b/public/en/cases/manu-game-maker/index.html @@ -0,0 +1,763 @@ + + + + + + + Игровой движок MANU — кейс + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

Product Design · Desktop App

+

Игровой движок MANU

+

+ Редизайн и развитие интерфейса: новая концепция работы, обновлённая визуальная система и сотни проработанных пользовательских сценариев. +

+

+ Работа велась вокруг десктопного редактора и связанных панелей: нужно было удержать целостность визуального языка при росте функциональности и не потерять опытные сценарии силовых пользователей. +

+

+ Развёрнутый лонгрид кейса появится на этой странице позже; ниже — обложка для навигации из портфолио. +

+

Обложка

+
+ Интерфейс игрового движка MANU — обложка кейса +
Текущее превью для карточки портфолио
+
+
+
+ +
+
+

Смотрите также

+
    +
  1. + + +
    +

    Primo AI Server

    +
      +
    • UX/UI
    • +
    • Web App
    • +
    +

    Интерфейс для обучения и использования AI‑моделей в бизнес‑процессах: работа с данными, сценариями автоматизации и Smart OCR.

    +
    +
    +
  2. +
  3. + + +
    +

    Lunacy

    +
      +
    • UX/UI
    • +
    • Desktop App
    • +
    +

    Работа над интерфейсом Lunacy в команде Icons8: user flow, сложные прототипы, улучшение функциональности и интерфейсной логики.

    +
    +
    +
  4. + + +
  5. + + +
    +

    Primo Orc

    +
      +
    • Менторство и наставничество
    • +
    +

    Веб-кабинет оркестратора Primo RPA: проекты, запуск и контроль RPA-роботов. Редизайн ключевых экранов под продакшен без остановки релизов.

    +
    +
    +
  6. + + +
+
+
+ + + + + + + + + + + + + + diff --git a/public/en/cases/mini-projects-showcase/index.html b/public/en/cases/mini-projects-showcase/index.html new file mode 100644 index 0000000..46a8af9 --- /dev/null +++ b/public/en/cases/mini-projects-showcase/index.html @@ -0,0 +1,1277 @@ + + + + + + + Активность — каталог карточек + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+
+
+

Design System · Активность

+

Карточки активности

+

Все типы карточек и живая сетка для подбора раскладки на главной

+

+ Страница для верстки и контента: здесь те же классы и скрипты, что в секции на главной. Меняйте шрифт и тему в панели + справа, сужайте окно — сетка пересчитается. Подписи в заголовках карточек — подсказки, в проде их убрать. +

+ + + +

Как пользоваться

+

+ Сначала выберите тип (stack, horizontal, compact, decor), затем data-mp-cells (1–4) и + порядок li в списке. Карточки с data-mp-extra не влияют на высоту списка до кнопки «Показать все». + Подробности — в docs/mini-projects-cards.md. +

+ +

Типы карточек

+

+ Слева пример карточки с текстом, справа название типа и пояснение по классам. +

+
+
+ +
+

+ Stack со ссылкой. Превью на всю ширину, текст снизу. Для внешней ссылки: + mp-card--link и mp-card__ext в заголовке (как в блоке опыта). +

+
+
+ +
+
+

mp-card--stack без ссылки

+
+ +
+

Stack без ссылки

+
    +
  • заметка
  • +
+

+ Связка баз и rollup для недельного обзора, только для личного пользования. +

+
+
+
+
+

+ Stack без ссылки. Тот же вид. Обёртка div или article, без + mp-card--link. +

+
+
+ +
+ +
+

+ Horizontal со ссылкой. Миниатюра слева, текст справа. На узком экране карточка + складывается в столбик. +

+
+
+ +
+
+

mp-card--horizontal без ссылки

+
+ +
+

Horizontal без ссылки

+
    +
  • шаблон
  • +
+

+ Статический сайт с расписанием и формой заявки на отдельном сервисе. +

+
+
+
+
+

+ Horizontal без ссылки. Тот же расклад, карточка не ведёт на внешний URL. +

+
+
+ +
+ +
+

+ Compact со ссылкой. Только текст, без mp-card__media. В сетке часто с + data-mp-cells="1". +

+
+
+ +
+
+

mp-card--compact без ссылки

+
+
+

Compact без ссылки

+
    +
  • документ
  • +
+

+ Markdown-список проверок перед релизом мелких правок в интерфейсе. +

+
+
+
+
+

+ Compact без ссылки. Короткая заметка или чеклист без внешней ссылки. +

+
+
+ +
+
+

mp-card--decor

+ +
+
+

+ Декоративная ячейка. Заглушка в сетке, без проекта и ссылки. Внутри + span.mp-card__decor-pattern. +

+
+
+
+ + +

Ширина в клетках

+

+ Атрибут data-mp-cells на li.mini-projects-grid__item задаёт желаемую ширину от 1 до 4 клеток. + Скрипт не даст карточке быть шире текущего ряда. +

+

+ Для адаптивной ширины используйте data-mp-cells-sm (при 4 клетках в ряду, ≥ 880 px) и + data-mp-cells-xs (при 2 клетках, ≥ 520 px). Скрипт выбирает значение по текущему числу клеток в ряду. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
АтрибутКогда применяетсяПример
data-mp-cellsШирокий экран (6 клеток, ≥ 1180 px)data-mp-cells="3"
data-mp-cells-smСредний экран (4 клетки, ≥ 880 px)data-mp-cells-sm="2"
data-mp-cells-xsУзкий экран (2 клетки, ≥ 520 px)data-mp-cells-xs="1"
+
+

+ Пример: data-mp-cells="3" data-mp-cells-sm="2" — карточка занимает 3 клетки на широком экране, 2 на среднем, 1 на узком. +

+
+
+ +

≥ 1180 px — 6 клеток

+
+
3
+
3
+
2
+
2
+
2
+
1
+
2
+
3
+
4
+
2
+
+
+
+ +

≥ 880 px — 4 клетки

+
+
3
+
1
+
2
+
2
+
1
+
2
+
2
+
4
+
+
+
+ +

≥ 520 px — 2 клетки

+
+
1
+
1
+
2
+
2
+
1
+
1
+
+
+
+ Цифра в блоке = значение data-mp-cells. Карточка занимает столько клеток подряд. При узком экране клеток меньше — карточки с шириной > число клеток переносятся на следующий ряд. С data-mp-cells-sm / data-mp-cells-xs карточка меняет ширину при сужении. +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Ширина спискаКлеток в ряду
≥ 1180px6
≥ 880px4
≥ 520px2
уже1 (вся строка)
+

+ На шести клетках пара 3 + 3 или тройка 2 + 2 + 2 обычно смотрится ровнее случайного набора. + Декор (mp-card--decor) заполняет пустоты в сетке. +

+
+
+
+ +
+
+

Живая сетка masonry

+

+ Демо блока «Активность» с главной: здесь все типы карточек в одной живой сетке. В подписях у каждой — тип + раскладки и data-mp-cells. Меняйте ширину окна и шрифт в панели темы, смотрите, как сетка + перестраивается, и переносите удачный порядок на главную. +

+
    +
  1. + + +
    +

    + [2] stack · ссылка + +

    +
      +
    • веб-сайт
    • +
    +

    Базовая пара в ряду из шести клеток.

    +
    +
    +
  2. +
  3. +
    + +
    +

    [2] stack

    +
      +
    • заметка
    • +
    +

    Stack без внешней ссылки.

    +
    +
    +
  4. +
  5. +
    + +
    +

    [2] horizontal

    +
      +
    • утилита
    • +
    +

    Horizontal на две клетки, без ссылки.

    +
    +
    +
  6. +
  7. +
    +
    +

    [2] compact

    +
      +
    • шаблон
    • +
    +

    Текстовая карточка на две клетки.

    +
    +
    +
  8. +
  9. + +
    +

    + [1] compact · ссылка + +

    +
      +
    • cli
    • +
    +

    Узкая колонка со ссылкой.

    +
    +
    +
  10. +
  11. +
    +
    +

    [1] compact

    +
      +
    • документ
    • +
    +

    Compact на одну клетку.

    +
    +
    +
  12. +
  13. + +
  14. +
  15. + + +
    +

    + [3] stack · ссылка + +

    +
      +
    • приложение
    • +
    +

    + Половина шестиклеточной строки. Длиннее описание, чтобы проверить перенос и высоту после смены шрифта. +

    +
    +
    +
  16. +
  17. + + +
    +

    + [3] horizontal · ссылка + +

    +
      +
    • плагин
    • +
    +

    Широкий horizontal: превью и текст в одной полосе.

    +
    +
    +
  18. +
  19. +
    + +
    +

    [3] horizontal

    +
      +
    • виджет
    • +
    +

    Horizontal на три клетки, без ссылки.

    +
    +
    +
  20. +
+ +
+
+ +
+ +
+
+ + + + + + + + + diff --git a/public/en/cases/mobile-clinic-app/index.html b/public/en/cases/mobile-clinic-app/index.html new file mode 100644 index 0000000..9f62ccc --- /dev/null +++ b/public/en/cases/mobile-clinic-app/index.html @@ -0,0 +1,763 @@ + + + + + + + Мобильное приложение для сети клиник — кейс + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

UX/UI · Mobile App

+

Мобильное приложение для сети клиник

+

+ Запись к врачам и работа с медицинскими услугами: простой путь пользователя, понятная навигация и быстрый доступ к действиям. +

+

+ Фокус проекта — снять тревожность и лишние шаги у пациента: ясные статусы записи, предсказуемые формы и опора на привычные паттерны мобильных приложений. +

+

+ Полный разбор кейса будет опубликован здесь; пока страница служит точкой входа из блока портфолио на главной. +

+

Обложка

+
+ Мобильное приложение для сети клиник — обложка кейса +
Текущее превью для карточки портфолио
+
+
+
+ +
+
+

Смотрите также

+
    +
  1. + + +
    +

    Primo AI Server

    +
      +
    • UX/UI
    • +
    • Web App
    • +
    +

    Интерфейс для обучения и использования AI‑моделей в бизнес‑процессах: работа с данными, сценариями автоматизации и Smart OCR.

    +
    +
    +
  2. +
  3. + + +
    +

    Lunacy

    +
      +
    • UX/UI
    • +
    • Desktop App
    • +
    +

    Работа над интерфейсом Lunacy в команде Icons8: user flow, сложные прототипы, улучшение функциональности и интерфейсной логики.

    +
    +
    +
  4. + + +
  5. + + +
    +

    Primo Orc

    +
      +
    • Менторство и наставничество
    • +
    +

    Веб-кабинет оркестратора Primo RPA: проекты, запуск и контроль RPA-роботов. Редизайн ключевых экранов под продакшен без остановки релизов.

    +
    +
    +
  6. + + +
+
+
+ + + + + + + + + + + + + + diff --git a/public/en/cases/obsidian-plugin-columns-view/index.html b/public/en/cases/obsidian-plugin-columns-view/index.html new file mode 100644 index 0000000..734689b --- /dev/null +++ b/public/en/cases/obsidian-plugin-columns-view/index.html @@ -0,0 +1,790 @@ + + + + + + + Колоночный вид для Obsidian — кейс + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

Obsidian · Plugin

+

Колоночный вид — плагин для Obsidian

+

Дизайн и разработка интерфейса для многоколоночной работы с заметками

+ +

+ +

+ +

Контекст

+

+ +

+ +

Задача

+

+ +

+ +

Решение

+

+ +

+ +

Фильтр использования

+
+
+ +
+
Фильтрация заметок в колоночном виде.
+
+ +

Конфигурация отображения

+
+
+ +
+
Настройка колонок и отображения.
+
+ +

Результат

+

+ +

+
+
+ +
+
+

Смотрите также

+
    +
  1. + + +
    +

    Primo AI Server

    +
      +
    • UX/UI
    • +
    • Web App
    • +
    +

    Интерфейс для обучения и использования AI‑моделей в бизнес‑процессах: работа с данными, сценариями автоматизации и Smart OCR.

    +
    +
    +
  2. +
  3. + + +
    +

    Lunacy

    +
      +
    • UX/UI
    • +
    • Desktop App
    • +
    +

    Работа над интерфейсом Lunacy в команде Icons8: user flow, сложные прототипы, улучшение функциональности и интерфейсной логики.

    +
    +
    +
  4. + + +
  5. + + +
    +

    Primo Orc

    +
      +
    • Менторство и наставничество
    • +
    +

    Веб-кабинет оркестратора Primo RPA: проекты, запуск и контроль RPA-роботов. Редизайн ключевых экранов под продакшен без остановки релизов.

    +
    +
    +
  6. + + +
+
+
+ + + + + + + + + + + + + diff --git a/public/en/cases/orc-automation-panel/index.html b/public/en/cases/orc-automation-panel/index.html new file mode 100644 index 0000000..6c12a86 --- /dev/null +++ b/public/en/cases/orc-automation-panel/index.html @@ -0,0 +1,901 @@ + + + + + + + Веб-панель для оркестрации автоматизаций — кейс + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

Primo RPA · UX/UI · Web App

+

Веб-панель для оркестрации автоматизаций

+

+ Оркестратор Primo RPA: запуск роботов, проекты и контроль процессов в веб-кабинете для админов и разработчиков автоматизаций. +

+ +
+

Контекст

+

+ В стеке Primo RPA оркестратор отвечает за расписание, очередь и статусы RPA-роботов на инфраструктуре клиента. С ним работают не только из студии разработки, но и из браузера: смотрят проекты, перезапускают задания, разбирают сбои. +

+

+ К тому моменту продукт уже был в эксплуатации, но интерфейс заметно устарел: пользователи сталкивались с неудобствами в навигации, визуальным шумом в таблицах и неочевидными сценариями в некоторых разделах. Задача стояла освежить вид интерфейса, сделать его современным и исправить накопившиеся UX-проблемы. +

+
+ +
+

Роль и подход

+

+ Моя роль в этом проекте была скорее кураторской — я не делал дизайн самостоятельно, а выстраивал процесс так, чтобы команда дизайна и разработки работала слаженно. +

+ +

+ Параллельно я искал дизайнера, который взял бы на себя основную визуальную работу. Отбор прошла Елизавета — среди более чем 150 заявок она лучше всего подходила под предстоящий фронт работ. +

+

+ В течение трёх месяцев я погружал новую сотрудницу в детали проекта: ставил задачи, давал обратную связь по создаваемым макетам, делился контекстом по пользовательским сценариям. Вместе с командой разработки мы планировали порядок обновления дизайна сервиса — меняли экраны по приоритету, вкладывая изменения в текущие релизы, без «остановить всё и перерисовать». +

+
+ +
+

Технический стек и инструменты

+

+ В качестве фреймворка для интерфейса был выбран Ant Design последней версии. Он уже использовался в проекте, и его свежий релиз предлагал Theme Editor — возможность настраивать внешний вид компонентов через дизайн-токены без переопределения стилей вручную. Это давало и дизайнерам, и разработчикам единый язык описания интерфейса. +

+

+ Единым источником правды и мостом между дизайном и разработкой стал Storybook. В нём компоненты были не просто задокументированы, а реально работали в изолированной среде — это позволяло дизайнерам видеть реализацию элементов вживую, а разработчикам — быстрее интегрировать компоненты в панель. +

+
+ +
+

Дизайн-система и синхронизация

+

+ Для удобства работы мы синхронизировали токены из Ant Design с токенами в Figma — цвета, отступы, типографика и радиусы скругления были приведены к единым значениям. Работало это так: если в Figma токены обновлялись, их можно было выгрузить в JSON и без проблем сразу загрузить в код — вместо того, чтобы разработчик вручную искал изменения и правил каждый токен индивидуально. Такой подход также позволил смотреть на дизайн более системно. +

+ +

+ Компоненты в Figma создавались с нуля — по референсам Ant Design, но без использования готовых библиотек. Отказ от готовых решений был осознанным: нам нужна была полная настройка под наш стиль работы и возможность использовать новые функции Figma, которые не поддерживались в библиотеках из сообщества Figma. +

+

+ Для Storybook мы отобрали и настроили плагины, которые были уместны для наших задач — контроль доступности, просмотр состояний компонентов, переключение тем и локалей. Это сделало Storybook не просто витриной компонентов, а полноценным инструментом дизайн-ревью. +

+
+ +
+

Рабочая среда дизайнера

+

+ Мы настроили среду так, чтобы дизайнер мог видеть реализацию компонентов вживую у себя на компьютере, используя локальный Storybook и Git. Это позволяло точнее контролировать, как задуманный дизайн воплощается в коде, и оперативно замечать расхождения на ранних этапах. +

+

+ Обсуждался также шаг ускорить поддержку Storybook тем, чтобы дизайнер сам мог описывать JSX для реализованного компонента — это сокращало путь от макета до документированного компонента в сторибуке. +

+

+ Мы отладили рабочий процесс по передаче иконок в библиотеку. Особенность заключалась в том, чтобы иконки можно было отобразить в Storybook с фильтрацией и сортировкой. При этом дизайнер мог обновлять иконки без привлечения разработчика — просто пушил новые файлы в репозиторий. +

+

+ Увидеть обновлённую иконку сразу в браузере позволяло лучше её оптимизировать и убрать косяки отображения при использовании булевых операций. +

+

+ Для Figma также был разработан подход и правила, которые не только ограничивали дизайнера, но и позволяли гарантированно делать консистентные и рабочие иконки. +

+
+ +
+

UX-улучшения

+

+ За время обновления интерфейса Елизавета предложила ещё и ряд улучшений пользовательского опыта. Часть из них была успешно внедрена — например, более понятная группировка элементов в сложных формах и переработанные состояния загрузки. Но в приоритете было не ломать старый UX, к которому привыкли пользователи, поэтому многие идеи сохранились в бэклог для следующей итерации. +

+
+ +
+

Список проектов

+

+ Таблица проектов — один из самых частых экранов. На старой версии много колонок, плотная сетка и мелкий текст: статус и имя проекта читаются с трудом, особенно на ноутбуке. +

+

В новой версии:

+
    +
  • колонки свели к тому, что нужно для ежедневной работы;
  • +
  • статусы и действия вынесли в предсказуемые зоны строки;
  • +
  • отступы и типографику подтянули под остальную панель на Ant Design.
  • +
+ +

Было

+
+ Список проектов в оркестраторе — предыдущая версия таблицы +
+ +

Стало

+
+ Список проектов в оркестраторе — обновлённая таблица +
+
+ +
+

Результаты и рефлексия

+

+ Экран проектов стал проще сканировать: меньше визуального шума, быстрее находить нужный проект и его состояние. После раскатывания нового дизайна среди нескольких клиентов мы получили положительные отзывы и множество идей для дальнейших улучшений. +

+

+ Я оставил проект, когда Елизавета уже уверенно чувствовала себя в нём и могла самостоятельно вести дизайнерские задачи. Периодически я отвечал на точечные вопросы, но в целом передача прошла гладко. +

+

+ Спустя год с небольшим процессы в команде заметно изменились. По словам ребят, в создании и передаче дизайна теперь активное участие принимают инструменты с нейросетями — такие как Claude Design. То, что мы начинали выстраивать вручную через токены, Storybook и синхронизацию сред, сейчас в какой-то мере автоматизируется. +

+

+ Считаю, что проделанная работа была выполнена достаточно оперативно и качественно — в условиях постоянных изменений на рынке и внутри компании нам удалось не просто обновить интерфейс, но и выстроить устойчивый процесс, который продолжил работать уже без моего участия. +

+
+
+
+ +
+
+

Смотрите также

+
    +
  1. + + +
    +

    Primo AI Server

    +
      +
    • UX/UI
    • +
    • Web App
    • +
    +

    Интерфейс для обучения и использования AI‑моделей в бизнес‑процессах: работа с данными, сценариями автоматизации и Smart OCR.

    +
    +
    +
  2. +
  3. + + +
    +

    Lunacy

    +
      +
    • UX/UI
    • +
    • Desktop App
    • +
    +

    Работа над интерфейсом Lunacy в команде Icons8: user flow, сложные прототипы, улучшение функциональности и интерфейсной логики.

    +
    +
    +
  4. + + +
  5. + + +
    +

    Primo Orc

    +
      +
    • Менторство и наставничество
    • +
    +

    Веб-кабинет оркестратора Primo RPA: проекты, запуск и контроль RPA-роботов. Редизайн ключевых экранов под продакшен без остановки релизов.

    +
    +
    +
  6. + + +
+
+
+ + + + + + + + + + + + + diff --git a/public/en/cases/site-with-ai-ide/index.html b/public/en/cases/site-with-ai-ide/index.html new file mode 100644 index 0000000..8b17a7d --- /dev/null +++ b/public/en/cases/site-with-ai-ide/index.html @@ -0,0 +1,423 @@ + + + + + + + Этот сайт — Разработка с AI IDE — кейс + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

Разработка · AI IDE

+

Этот сайт

+

Разработка сайта с использованием AI IDE

+ +

+ +

+ +

Контекст

+

+ +

+ +

Задача

+

+ +

+ +

Решение

+

+ +

+ +

Результат

+

+ +

+
+
+ +
+ +
+ + + + + + + + + + + + + diff --git a/public/en/cases/task-distribution-concept/index.html b/public/en/cases/task-distribution-concept/index.html new file mode 100644 index 0000000..a4bd0ff --- /dev/null +++ b/public/en/cases/task-distribution-concept/index.html @@ -0,0 +1,763 @@ + + + + + + + Концепт сервиса распределения задач — кейс + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

UX/UI · Analytics

+

Концепт сервиса распределения задач

+

+ Продуктовый концепт: постановка, распределение и отслеживание задач с акцентом на прозрачность процессов и удобство командной работы. +

+

+ Задача концепта — показать, как команда видит поток работ без «чёрных ящиков»: кто что взял, где узкие места и какие статусы критичны для следующего шага. +

+

+ Детальный кейс появится здесь позже; страница уже связана с карточкой в портфолио на сайте. +

+

Обложка

+
+ Концепт сервиса распределения задач — обложка кейса +
Текущее превью для карточки портфолио
+
+
+
+ +
+
+

Смотрите также

+
    +
  1. + + +
    +

    Primo AI Server

    +
      +
    • UX/UI
    • +
    • Web App
    • +
    +

    Интерфейс для обучения и использования AI‑моделей в бизнес‑процессах: работа с данными, сценариями автоматизации и Smart OCR.

    +
    +
    +
  2. +
  3. + + +
    +

    Lunacy

    +
      +
    • UX/UI
    • +
    • Desktop App
    • +
    +

    Работа над интерфейсом Lunacy в команде Icons8: user flow, сложные прототипы, улучшение функциональности и интерфейсной логики.

    +
    +
    +
  4. + + +
  5. + + +
    +

    Primo Orc

    +
      +
    • Менторство и наставничество
    • +
    +

    Веб-кабинет оркестратора Primo RPA: проекты, запуск и контроль RPA-роботов. Редизайн ключевых экранов под продакшен без остановки релизов.

    +
    +
    +
  6. + + +
+
+
+ + + + + + + + + + + + + + diff --git a/public/en/cases/task-tracker/index.html b/public/en/cases/task-tracker/index.html new file mode 100644 index 0000000..eaa5ea6 --- /dev/null +++ b/public/en/cases/task-tracker/index.html @@ -0,0 +1,1194 @@ + + + + + + + Таск-трекер для бек-офис системы — кейс + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

Тестовое задание · back-office

+

Таск-трекер для внутренней бек-офис системы

+

Задание

+

+ Реализовать интерфейс для планирования и распределения задач между сотрудниками операционного отдела, работающими в некой бек-офис системе. Существует большое количество разноплановых задач, которые можно сгруппировать: Тип-1, Тип-2, Тип-3, Тип-4, Тип-5, Тип-6, Тип-7. У каждой задачи есть свой, выставляемый автоматически, приоритет. Пользователю доступны только задачи из определенных категорий. +

+

Нужно реализовать:

+
    +
  • + Общий экран: +
      +
    • где пользователь может посмотреть все открытые общие задачи и взять какие-то себе.
    • +
    • персональный ToDo лист, где отображаются как взятые вручную задачи, так и автоматически назначенные.
    • +
    • у взятой задачи есть возможность передать ее другому сотруднику или вернуть в общий список.
    • +
    +
  • +
  • Экран, где можно посмотреть, что у кого в работе.
  • +
+ +

Введение

+

+ Так как у меня лично есть обширный опыт работы с таск-трекерами, то я опираюсь на свой опыт и опыт моих коллег с учётом требований задания. +

+ +

Референсы

+
+ Референсы +
+
    +
  • Таск-трекеры (Trello, Monday, YouTrack и др.)
  • +
  • Сервисы для визуальной работы с базами данных (Notion, Airtable и др.)
  • +
+ +

Решение

+ +

Скринкаст

+
+
+ +
+
+ + + +

Вкладки (Tabs)

+

На вкладке All tasks сотрудник может ознакомиться со всеми доступными задачами.

+
+ Untitled +
+

+ На вкладке My tasks отображаются только те задачи, что закреплены за текущим сотрудником. Группировка идёт по статусам задач (статус в карточке задачи скрыт). +

+
+ Untitled +
+

На вкладке Task by assign отображены все задачи c разбивкой по исполнителю.

+
+ Untitled +
+

В правой части имеются опции для текущей вкладки.

+
+ Untitled +
+

+ Все изменения сохраняются после ухода с вкладки и после закрытия всей страницы Tasks. Эти настройки индивидуальны и каждый сотрудник может настроить страницу как ему удобно. +

+
    +
  • Group - позволяет создать дополнительную группировку по свойству таски (категория, приоритет, исполнитель, статус, дата создания, дата изменения).
  • +
  • View - позволяет изменить отображение текущих задач на другой вид: доска, список, таблица.
  • +
  • Filter - позволяет скрывать задачи по свойствам.
  • +
  • + Sort - позволяет определять порядок отображения задач по свойствам. Если включена сортировка по статусу High, Medium, Low то можно выбрать либо по алфавиту, либо в обратном порядке, а также можно создать свой порядок. С датами и цифрами работает принцип по возрастанию/убыванию. +
  • +
  • + Search on page - позволяет отобразить только те задачи, в названии или описании которых есть поисковое слово или фраза. (Можно улучшить функционал поиска, добавив поисковые операторы, если исследования докажут эту необходимость) +
  • +
+

+ На странице работает постепенная подгрузка задач. По мере скрола вниз, будут подгружаться новые задачи. Кол-во задач указывается в заголовках, это может быть полезно, чтобы оценить как далеко надо скролить, чтобы все посмотреть. Задач может быть очень много и тогда возможно имеет смысл добавить ещё и переход по страницам. +

+ +

Варианты отображения (Views)

+

+ Разные виды отображения одних и тех же задач могут быть полезны для разных контекстов использования страницы Tasks и также в угоду личным предпочтениям сотрудника, что повышает его эффективность. Сотрудник, которого не раздражает интерфейс работает продуктивнее и приносит больше пользы. +

+
+ Untitled +
+ +

+ Вид Board позволяет удобнее работать с функцией Drag&Drop (см. ниже). Также визуально легче определить кол-во задач в категории, подразумеваю что это может быть полезно. В случае использования группировки задач по статусам выполнения, перемещение карточек слева направо создаёт естественное горизонтальное течение задач из точки А в точку Б. +

+
+ Untitled +
+

+ Вид List позволяет увидеть больше информации на меньшей площади экрана. При этом этот вид визуально и функционально легче табличного вида, а значит меньше когнитивной нагрузки на пользователя. Также здесь все заголовки задач начинаются в крайнем левом положении, поэтому становится легко пробежаться глазами по списку. +

+
+ Untitled +
+

+ Вид Table позволяет сразу же работать со всеми полями задачи, без раскрытия её в модальном окне или отдельной странице. Таблица подразумевает пользовательскую настройку ширины и порядка столбцов, что может быть удобно в некоторых кейсах использования. +

+
+ Untitled +
+ +
+
+ +
+
+ + +

Drag&Drop

+

+ Во всех видах можно перетаскивать элементы (карточки/элементы списка/строки и столбцы таблицы). Это способствует более продуктивной работе и выглядит естественным для пользователя. Например, если требуется раскидать задачи между сотрудниками (для админа или менеджера), поменять статус для нескольких задач разом или в короткий отрезок времени. +

+

Board view

+
+
+ +
+
+ +

List view

+
+
+ +
+
+ +

Продвинутый интерактивный прототип в ProtoPie

+

+ Схематичное представление того как работает Drag&Drop. Такой прототип помогает дизайнеру лучше понять какие нюансы нужно учесть в юзерфлоу, а также и при подготовке спецификации для разработчика. Также попробовав данный прототип можно прийти к тому, что функционал должен работать совсем иначе и переделать его сразу. Это экономит общее время разработки. +

+
+
+ +
+
+ +

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

+ +

Стиль

+

+ Визуальный стиль может быть любым, не меняя самого каркаса. Например, чуть более красочным и с более выразительными шрифтами. +

+

Светлая цветная тема

+
+ Светлая цветная тема +
+ +

Нейтральная тёмная тема

+
+ Нейтральная тёмная тема +
+ + +

Исследования

+

+ На этапе прототипов Figma можно провести интервью с несколькими сотрудниками, например 5 человек. Предварительно в прототип нужно добавить реальные задачи из текущей системы, порядка 20 должно хватить. Далее можно созвониться по видеосвязи с шэрингом экрана респондента или встретиться лично и задать несколько заданий и вопросов. Во время сессии респондент может тыкать в прототип, но если что-то не сработает интервьюер может зафиксировать попытку и объяснить, что этот функционал пока не работает, а также поинтересоваться что респондент ожидает увидеть дальше. +

+

Задания

+
    +
  1. Найти свои актуальные задачи со страницы Tasks вкладки All tasks
  2. +
  3. Назначить себе задачи, чтобы взять в работу
  4. +
  5. Найти задачу про что-то конкретное (интервьюер приводит пример), которую он уже выполнил ранее
  6. +
  7. Показать задачи, которые относятся только к определённой категории и должны быть сделаны до конца недели
  8. +
  9. Попросить поменять приоритет у нескольких задач одновременно
  10. +
  11. Оценить визуальную привлекательность интерфейса по шкале от 1 до 10
  12. +
  13. Оценить общее впечатление от предложенного концепта по шкале от 1 до 10
  14. +
  15. Предложить ещё функции, которые были бы полезны
  16. +
+

Аналитика

+

Для оценки актуальности реализованного функционала необходимо собирать следующие показатели.

+
    +
  • + Кол-во просмотров страницы Tasks page_views_tasks с возможностью разбиения +
      +
    • + по вкладкам +
        +
      • All tasks all_tasks_tab
      • +
      • My tasks my_tasks_tab
      • +
      • Tasks by assign tasks_by_assign
      • +
      +
    • +
    • + по ролям +
        +
      • Admin admin
      • +
      • Manager manager
      • +
      • User user
      • +
      +
    • +
    • + по попыткам поиска +
        +
      • Search Used search used
      • +
      +
    • +
    +
  • +
  • + Кол-во включённых пользовательских настроек Custom Settings custom_settings с возможностью разбиения +
      +
    • + по вкладкам +
        +
      • All tasks all_tasks_tab
      • +
      • My tasks my_tasks_tab
      • +
      • Tasks by assign tasks_by_assign
      • +
      +
    • +
    • + по ролям +
        +
      • Admin admin
      • +
      • Manager manager
      • +
      • User user
      • +
      +
    • +
    • + по типам +
        +
      • Group group
      • +
      • Filter filter
      • +
      • Sort sort
      • +
      +
    • +
    +
  • +
  • + Кол-во включенных видов View Used view_used с возможностью разбиения +
      +
    • + по вкладкам +
        +
      • All tasks all_tasks_tab
      • +
      • My tasks my_tasks_tab
      • +
      • Tasks by assign tasks_by_assign
      • +
      +
    • +
    • + по ролям +
        +
      • Admin admin
      • +
      • Manager manager
      • +
      • User user
      • +
      +
    • +
    +
  • +
+

+ Далее в течение полугода (может и раньше, в зависимости от активности пользователей) можно будет увидеть результат и провести исследование (в виде интервью) по функциям, которые используются реже всего и тем на которые есть жалобы, чтобы получить качественные данные и составить полную картину происходящего. +

+ +

Дополнительные детали

+

+ При создании мокапов в Figma были использованы: возможности автолэаутов; интерактивные компоненты, плагины (Iconify, Fitter, Protofly, Shadow Maker, Dark Side и др.), файл Community (+40 Types of Cursor). Слои названы и организованы для более удобной работы где-то на 60%. Используется сетка 8pt + есть размеры 2pt, 4pt. Имеются цветовые стили для светлой и тёмной темы. Присутствует адаптивность под изменяющиеся размеры окна (мобильной версии нет). +

+ +
+
+ +
+
+

Смотрите также

+
    +
  1. + + +
    +

    Primo AI Server

    +
      +
    • UX/UI
    • +
    • Web App
    • +
    +

    Интерфейс для обучения и использования AI‑моделей в бизнес‑процессах: работа с данными, сценариями автоматизации и Smart OCR.

    +
    +
    +
  2. +
  3. + + +
    +

    Lunacy

    +
      +
    • UX/UI
    • +
    • Desktop App
    • +
    +

    Работа над интерфейсом Lunacy в команде Icons8: user flow, сложные прототипы, улучшение функциональности и интерфейсной логики.

    +
    +
    +
  4. + + +
  5. + + +
    +

    Primo Orc

    +
      +
    • Менторство и наставничество
    • +
    +

    Веб-кабинет оркестратора Primo RPA: проекты, запуск и контроль RPA-роботов. Редизайн ключевых экранов под продакшен без остановки релизов.

    +
    +
    +
  6. + + +
+
+
+ + + + + + + + + + + + + + + + + diff --git a/public/en/cases/template-elements-demo/index.html b/public/en/cases/template-elements-demo/index.html new file mode 100644 index 0000000..a2d4efd --- /dev/null +++ b/public/en/cases/template-elements-demo/index.html @@ -0,0 +1,1152 @@ + + + + + + + Демо элементов кейса — template + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+

Демо · template

+

Пример кейса: демо элементов

+

Рыба-текст и блоки для быстрой сборки лонгрида

+

Медиа для демо лежат в public/assets/ (сборка страниц их не копирует). См. public/assets/README.md.

+ +

Контекст

+

+ Этот раздел показывает пример рыбы для вступления. Здесь можно коротко описать продукт, аудиторию и ограничения проекта. + Текст нейтральный: цель — быстро набросать структуру и проверить визуальный ритм страницы. +

+ +

Задача

+

+ Нужно собрать демонстрационный лонгрид с основными UI-элементами: карточками ссылок, медиа-блоками и примерами оформления контента. + Дополнительно важно проверить, как блоки ведут себя на узких и широких экранах. +

+ +

Решение

+

Ниже собраны готовые примеры, которые можно копировать в реальные кейсы.

+ +

Карточки ссылок

+ + +

Картинка: 50% от оригинала (без превью)

+
+ Демо GIF в натуральном размере +
Изображение в уменьшенном натуральном размере, по центру.
+
+ +

Картинка: по ширине контента (с превью)

+
+ Демо изображения по ширине контента +
Изображение по ширине контента. Клик открывает viewer.
+
+ +

Картинка: чуть шире контента (с превью)

+
+ Демо изображения шире контента +
Изображение немного выходит за пределы контентной колонки.
+
+ +

Длинный скрин: свёрнутая высота и кнопка внутри превью

+

+ Блок data-case-tall-media: ширина задаётся атрибутом data-case-tall-width="wide" (как «широкая» картинка) или content (по колонке). В свёрнутом виде видна верхняя часть кадра, внизу превью — плотный «туман». Кнопка «Развернуть» / «Свернуть» с иконкой-шевроном поверх нижней части кадра, внутри рамки; после раскрытия туман скрывается, после сворачивания страница прокручивается к превью. Высоту превью — на .case-media-tall__clip через --case-tall-max (по умолчанию min(70vh, 32rem)). +

+
+
+
+ Демо: длинный вертикальный скрин интерфейса + +
+
+ +
+
+
Длинное изображение img-long.webp (исходник PNG рядом в public/assets/template-elements-demo/).
+
+ +

Галерея: сетка по ширине контента

+

+ Колонок на широком экране задаётся на figure: атрибут style="--case-gallery-cols: 3". Ниже порогов сетка схлопывается до двух и одной колонки. Клик по миниатюре открывает общий viewer кейса. +

+ + +

Галерея: сетка на ширину страницы с боковыми отступами

+

+ Класс case-media-gallery--wide — та же логика выхода за колонку, что у одиночного case-media--wide. Здесь четыре колонки на широком экране (--case-gallery-cols: 4). +

+ + +

Слайдер: одна картинка, стрелки и точки

+

+ Блок .case-media-slider показывает слайды по одному: навигация стрелками «пред/след», + пагинация точками (активная — акцентным цветом). Поддерживается клавиатура (←/→, Home/End на точках), + тач/мышь-свайп и ленивая подгрузка соседних кадров через data-src. Клик по активному + кадру открывает общий viewer кейса. +

+
+
+
+
+ Слайдер, кадр 1 +
+
+ Слайдер, кадр 2 +
+
+ Слайдер, кадр 3 +
+
+ Слайдер, кадр 4 +
+
+
+ + +
+
+
+ + + + +
+
+ +

+ Тот же слайдер, но с модификатором case-media--wide — выходит за колонку + контента, как одиночные широкие картинки. Кнопки пред/след и точки ведут себя так же. +

+
+
+
+
+ Слайдер wide, кадр 1 +
+
+ Слайдер wide, кадр 2 +
+
+ Слайдер wide, кадр 3 +
+
+ Слайдер wide, кадр 4 +
+
+ Слайдер wide, кадр 5 +
+
+ Слайдер wide, кадр 6 +
+
+ Слайдер wide, кадр 7 +
+
+
+ + +
+
+
+ + + + + + + +
+
+ +

+ Слайдер с горизонтальными (широкими) кадрами одного размера — все картинки + 1024×800. +

+
+
+
+
+ Широкий слайдер, кадр 1 +
+
+ Широкий слайдер, кадр 2 +
+
+ Широкий слайдер, кадр 3 +
+
+ Широкий слайдер, кадр 4 +
+
+ Широкий слайдер, кадр 5 +
+
+ Широкий слайдер, кадр 6 +
+
+ Широкий слайдер, кадр 7 +
+
+
+ + +
+
+
+ + + + + + + +
+
+ +

+ Тот же слайдер, но с вертикальными кадрами одного размера — все картинки + 600×800. +

+
+
+
+
+ Вертикальный слайдер, кадр 1 +
+
+ Вертикальный слайдер, кадр 2 +
+
+ Вертикальный слайдер, кадр 3 +
+
+ Вертикальный слайдер, кадр 4 +
+
+ Вертикальный слайдер, кадр 5 +
+
+ Вертикальный слайдер, кадр 6 +
+
+ Вертикальный слайдер, кадр 7 +
+
+
+ + +
+
+
+ + + + + + + +
+
+ +

+ Отдельный пример: переключатель режима анимации смены кадров — + slide (сдвиг), fade (кроссфейд) и none (без перехода). + Режим задаётся атрибутом data-slider-anim на фигуре. +

+
+
+ + + +
+
+
+
+ Пример анимации, кадр 1 +
+
+ Пример анимации, кадр 2 +
+
+ Пример анимации, кадр 3 +
+
+ Пример анимации, кадр 4 +
+
+
+ + +
+
+
+ + + + +
+
+ +

Callout: важный абзац с эмоджи

+ + +

Видео: карточка с тенью и фиксированными малыми углами

+
+
+ +
+
Пример видео-карточки в стиле кейса.
+
+ +

Результат

+

+ Шаблон готов для копирования в новый кейс: остаётся заменить рыбу на фактический текст, подставить реальные ссылки и медиа проекта. +

+
+
+ +
+
+

Portfolio

+
    +
  1. + + +
    +

    Primo AI Server

    +
      +
    • UX/UI
    • +
    • Web App
    • +
    +

    Interface for training and using AI models in business processes: data work, automation scenarios, and Smart OCR.

    +
    +
    +
  2. +
  3. + + +
    +

    Lunacy

    +
      +
    • UX/UI
    • +
    • Desktop App
    • +
    +

    Work on the Lunacy interface at Icons8: user flows, complex prototypes, improved functionality and interface logic.

    +
    +
    +
  4. + + +
  5. + + +
    +

    Primo Orc

    +
      +
    • Mentoring & coaching
    • +
    +

    Primo RPA orchestrator web console: projects, launch and control of RPA bots. Redesign of key screens for production without halting releases.

    +
    +
    +
  6. + + +
+
+
+ + + + + + + + + + + + + + + + diff --git a/public/en/cookie-policy/index.html b/public/en/cookie-policy/index.html new file mode 100644 index 0000000..413457f --- /dev/null +++ b/public/en/cookie-policy/index.html @@ -0,0 +1,365 @@ + + + + + + + Cookie Policy — Nikolai Mamadaev + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+ +
+ + + + + + + + + diff --git a/public/en/index.html b/public/en/index.html new file mode 100644 index 0000000..baf19b3 --- /dev/null +++ b/public/en/index.html @@ -0,0 +1,1375 @@ + + + + + + + Nikolay Mamadaev — Product Designer + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+
+
+

Nikolay Mamadaev

+

+ Taming complex interfaces + + web + +

+ +
+ +
+
+ + + +
+
+

Portfolio

+
    +
  1. + + +
    +

    Primo AI Server

    +
      +
    • UX/UI
    • +
    • Web App
    • +
    +

    Interface for training and using AI models in business processes: data work, automation scenarios, and Smart OCR.

    +
    +
    +
  2. +
  3. + + +
    +

    Lunacy

    +
      +
    • UX/UI
    • +
    • Desktop App
    • +
    +

    Work on the Lunacy interface at Icons8: user flows, complex prototypes, improved functionality and interface logic.

    +
    +
    +
  4. + + +
  5. + + +
    +

    Primo Orc

    +
      +
    • Mentoring & coaching
    • +
    +

    Primo RPA orchestrator web console: projects, launch and control of RPA bots. Redesign of key screens for production without halting releases.

    +
    +
    +
  6. + + +
+
+
+ + + +
+
+

Experience

+
    +
  1. +
    +
    + January 2026 — Present +
    +

    + Astratech +

    +

    + Development and support of digital systems and tools that boost team efficiency. +

    +
    +
  2. +
  3. + +
    + February 2023 — November 2025 + + 2 years 10 months +
    +

    + Primo RPA + +

    +

    + A business process automation platform with a suite of products: a studio for building bots, an orchestrator to manage them, an Idea Hub for process selection, and AI servers for models and data recognition. +

    +
    +
  4. +
  5. + +
    + July 2022 — January 2023 + + 7 months +
    +

    + Icons8 (Lunacy) + +

    +

    + A vector editor for UI/UX and web design with Sketch support, a built-in library of icons/photos/illustrations, and a set of AI tools to speed up routine work. +

    +
    +
  6. +
  7. + +
    + January 2019 — December 2021 + + 3 years +
    +

    + MANU Video Game Maker + +

    +

    + No-code/low-code platform for building 2D and 3D games without writing code. +

    +
    +
  8. + + + +
+
+ +
+
+
+ +
+
+

Skills

+
+
+
+

Nikolay M:

+

+ My experience covers both launching a product's first versions from scratch and evolving mature solutions — where the goal is not to break familiar flows but to refine them carefully. +

+ +
+
+
+
+

+ Below are examples of real business tasks and how I approached them +

+ +
+
+
+ +
+
+
+

+ +

+ +
+
+ +
+
+

+ +

+ +
+
+ +
+
+

+ +

+ +
+
+ +
+
+

+ +

+ +
+
+ +
+
+
+ +
+ +
+ + + + + + + + + + + + + + diff --git a/public/en/resume/index.html b/public/en/resume/index.html new file mode 100644 index 0000000..606ecfe --- /dev/null +++ b/public/en/resume/index.html @@ -0,0 +1,1020 @@ + + + + + + + Резюме — Николай Мамадаев + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ + +
+ +
+ + +
+
+
+ Николай Мамадаев +
+

Резюме

+

Николай Мамадаев

+

Senior UX/UI Designer, Product Designer

+
+
+ +
+ +
+
+
Портфолио
+
mamadaevv.site
+
+
+
Вилка по ЗП
+
250 000 – 310 000 руб.
+
+
+
Формат работы
+
удалённо
+
+
+
Почта
+
nik@mamadaevv.site
+
+
+
Соц.сети
+
@mamadaevv
+
+
+ +
+ +

Опыт работы

+ +
+ +
+
+

Astratech

+

Январь 2026 — Настоящее время

+
+

Разработка и поддержка цифровых систем и инструментов для повышения эффективности сотрудников.

+ +
+
+
+

Обязанности:

+
    +
  • Занимался дизайном интерфейсов
  • +
+
+
+

Достижения:

+
    +
  • Успешно закрывал входящие задачи
  • +
+
+
+

Стэк:

+
    +
  • Figma
  • +
  • HTML/CSS
  • +
  • AI/LLM
  • +
+
+
+
+
+ +
+ +
+
+

Primo RPA

+

Февраль 2023 — Ноябрь 2025 (2 года и 10 месяцев)·primo-rpa.com

+
+

Российская RPA-платформа корпоративного уровня: линейка из desktop-среды разработки роботов и веб-сервисов управления, аналитики и ML. Работал ведущим дизайнером на всю продуктовую линейку из 6 продуктов.

+ +
+

AI Server platform (Saas)

+

Панель управления для обучения/запуска/тестирования и других операций с нейросетями развёрнутыми внутри контура компании.

+
+
+

Обязанности:

+
    +
  • Проектирование интерфейса управления моделями: обучение, запуск, тестирование, версионирование, мониторинг
  • +
  • Проработка сценариев работы с Smart OCR — извлечение данных из документов различных типов
  • +
  • Проектирование разметки документов и валидации результатов распознавания
  • +
  • Работа с состояниями долгих процессов: обучение модели, прогресс, ошибки, откат
  • +
  • Подготовка интерактивных прототипов и авторский надзор за реализацией
  • +
  • Проведение интервью с ML-инженерами и бизнес-пользователями для сверки моделей поведения
  • +
+
+
+

Достижения:

+
    +
  • Упаковал сложную ML-логику в интерфейс, понятный не только ML-инженеру, но и бизнес-аналитику без технического бэкграунда
  • +
  • Свёл работу с моделями к единому предсказуемому сценарию из 4 шагов вместо 9 разрозненных экранов
  • +
  • Сократил время настройки новой модели с 40 до 15 минут по результатам замеров на тестировании
  • +
  • Спроектировал интерфейс разметки документов, который снял 60% обращений в поддержку по этой теме
  • +
  • Провёл 8 юзабилити-сессий, по итогам переработал ключевой сценарий обучения модели
  • +
+
+
+

Стэк:

+
    +
  • ML / нейросети
  • +
  • Smart OCR
  • +
+
+
+
+ +
+ +
+

Robot automation studio (Windows)

+

Среда для разработки и отладки роботов для автоматизации бизнес-процессов.

+
+
+

Обязанности:

+
    +
  • Проектирование интерфейса профессиональной desktop-IDE: рабочая область, панели инструментов, дерево процесса, отладчик
  • +
  • Развитие библиотеки компонентов и визуального языка продукта
  • +
  • Проработка сценариев отладки: точки останова, пошаговое выполнение, просмотр переменных
  • +
  • Проектирование работы с большими деревьями активностей и вложенностью
  • +
  • Работа в паре с разработкой по реализации компонентов, авторский надзор
  • +
  • Ведение регулярных дизайн-ревью внутри команды
  • +
+
+
+

Достижения:

+
    +
  • Обновил визуальный язык продукта до уровня Windows 10/11 — компоненты стали на поколение современнее
  • +
  • Переработал 80+ экранов и диалогов, приведя их к единой сетке и типографике
  • +
  • Собрал библиотеку из 140 компонентов, закрывшую 90% потребностей интерфейса
  • +
  • Перевёл иконки и часть иллюстраций на векторную графику: корректное отображение при любом масштабировании и DPI
  • +
  • Сократил время сборки нового экрана командой с 2 дней до 4 часов за счёт готовых компонентов
  • +
+
+
+

Стэк:

+
    +
  • Desktop IDE
  • +
  • Windows 10/11
  • +
  • Компонентная библиотека
  • +
+
+
+
+ +
+ +
+

Robot automation studio (Linux)

+

Среда для разработки и отладки роботов для автоматизации бизнес-процессов.

+
+
+

Обязанности:

+
    +
  • Адаптация дизайна Студии под Linux-версию на фреймворке Avalonia
  • +
  • Проектирование компонентов с учётом технических ограничений фреймворка
  • +
  • Синхронизация визуального языка между Windows- и Linux-версиями продукта
  • +
  • Совместная работа с разработкой над тем, что технически реализуемо в Avalonia, а что требует обходных решений
  • +
  • Подготовка спецификаций поведения компонентов для реализации
  • +
+
+
+

Достижения:

+
    +
  • Собрал расширенную библиотеку из 95 компонентов для Avalonia, которой в проекте не существовало до меня
  • +
  • Обеспечил единообразие продукта на двух платформах при разных технологических стеках
  • +
  • Сократил количество визуальных расхождений между версиями с нескольких десятков до единичных случаев
  • +
  • Дал команде Linux-версии готовый набор решений вместо дизайна каждого экрана с нуля
  • +
+
+
+

Стэк:

+
    +
  • Avalonia
  • +
  • .NET
  • +
  • Linux
  • +
+
+
+
+ +
+ +
+

Idea Hub Dashboard (Saas)

+

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

+
+
+

Обязанности:

+
    +
  • Проектирование дашбордов и аналитических представлений по KPI роботизации
  • +
  • Проработка жизненного цикла идеи: подача → оценка → приоритизация → реализация → замер эффекта
  • +
  • Информационная архитектура раздела, работа с плотными таблицами, фильтрами и массовыми операциями
  • +
  • Проектирование ролевой модели: сотрудник, аналитик, руководитель
  • +
  • Подбор типов визуализации данных под конкретные управленческие вопросы
  • +
+
+
+

Достижения:

+
    +
  • Свёл верхнеуровневую аналитику и работу с конкретной идеей в один непрерывный сценарий без переключения между разделами
  • +
  • Спроектировал представление данных так, что руководитель видит эффект от роботизации без ручных выгрузок в Excel
  • +
  • Сократил путь от подачи идеи до её оценки с 6 шагов до 3
  • +
  • Спроектировал 12 виджетов аналитики, покрывающих основные вопросы заказчиков о ROI роботизации
  • +
+
+
+

Стэк:

+
    +
  • Аналитика / дашборды
  • +
  • KPI
  • +
  • Визуализация данных
  • +
+
+
+
+ +
+ +
+

Orchestrator Automation Management Tool (Saas)

+

Обеспечивает слаженную работу системы и управление сотнями роботов: запуски, мониторинг и менеджмент.

+
+
+

Обязанности:

+
    +
  • Проектирование интерфейса управления парком из сотен роботов: расписания, очереди, приоритеты, мониторинг
  • +
  • Проработка состояний и обработки ошибок при массовых запусках
  • +
  • Проектирование системы уведомлений и алертов о сбоях
  • +
  • Работа со сложными таблицами: сортировка, фильтрация, массовые действия, сохранённые представления
  • +
  • Прототипирование сложных сценариев для валидации до начала разработки
  • +
+
+
+

Достижения:

+
    +
  • Спроектировал управление сотнями объектов так, что оператор не теряется в масштабе: от общей картины до конкретного робота за 2 клика
  • +
  • Привёл сервис к единой дизайн-системе вместе с остальными веб-продуктами линейки
  • +
  • Сократил время реакции оператора на сбой робота с 12 до 4 минут за счёт переработанной системы алертов
  • +
  • Свёл 5 разных представлений списка роботов к одному настраиваемому
  • +
+
+
+

Стэк:

+
    +
  • Мониторинг
  • +
  • Расписания / очереди
  • +
  • Уведомления / алерты
  • +
+
+
+
+ +
+ +
+

QnA portal (WEB)

+

Площадка для постинга вопросов и получения ответов от службы поддержки

+
+
+

Обязанности:

+
    +
  • Проектирование сценариев публикации вопроса, ответа и поиска по накопленной базе знаний
  • +
  • Дизайн интерфейса портала на базе общей дизайн-системы компании
  • +
  • Проработка ролевой модели: пользователь, эксперт, служба поддержки, модератор
  • +
  • Проектирование механики поиска и подсказок похожих вопросов при публикации
  • +
+
+
+

Достижения:

+
    +
  • Запустил портал на готовых компонентах дизайн-системы за 3 недели вместо запланированных 8
  • +
  • Снизил нагрузку на поддержку примерно на 25% за счёт переиспользования уже данных ответов
  • +
  • Механика подсказок похожих вопросов сократила количество дублей на 45%
  • +
  • Обеспечил визуальную преемственность с продуктами линейки — портал не выглядит сторонним сервисом
  • +
+
+
+

Стэк:

+
    +
  • Веб-портал
  • +
  • Дизайн-система
  • +
  • База знаний
  • +
+
+
+
+ +
+ +
+

Общее по компании

+
    +
  • Инициировал обновление внешнего вида всей продуктовой линейки — решение о редизайне компания приняла с моим приходом
  • +
  • Собрал и внедрил единую дизайн-систему для веб-сервисов, соединив фирменный стиль компании с новой структурой интерфейсов
  • +
  • Дизайн-систему приняли 4 продуктовые команды; она включает 210 компонентов и токены темизации
  • +
  • Сократил среднее время подготовки макетов по линейке на 35%
  • +
  • Создавал и поддерживал UI-kit для консистентности и ускорения разработки
  • +
  • Провёл более 20 пользовательских исследований и юзабилити-сессий за время работы
  • +
  • Вёл авторский надзор за реализацией, участвовал в дизайн-ревью, продуктовых обсуждениях и планировании релизов
  • +
  • Был единственным ведущим дизайнером на всю линейку из 6 продуктов и двух платформ
  • +
+
+
+ +
+ +
+
+

Lunacy (Icons8)

+

Июль 2022 — Январь 2023 (7 месяцев)·icons8.com/lunacy

+
+

Графический редактор для работы над дизайном интерфейсов и прочих макетов. Аналог Sketch App и Figma.

+ +
+
+
+

Обязанности:

+
    +
  • Проектирование интерфейса графического редактора: инструменты, панели свойств, работа с холстом и слоями
  • +
  • Оформление решений в виде юзерфлоу и передача разработчикам
  • +
  • Создание сложных интерактивных прототипов в Protopie для демонстрации поведения инструментов
  • +
  • Отрисовка недостающих иконок в стилистике продукта
  • +
  • Внесение предложений по улучшению функциональности редактора
  • +
  • Участие в обсуждениях продуктовых решений с командой
  • +
+
+
+

Достижения:

+
    +
  • Спроектировал 6 функциональных блоков редактора, включая работу с компонентами и стилями
  • +
  • Собрал 15+ прототипов в Protopie, что позволило отсечь неудачные решения до разработки
  • +
  • Часть моих предложений по функциональности вошла в релизы продукта
  • +
  • Отрисовал более 60 иконок, дополнивших системный набор редактора
  • +
  • Работал над продуктом для профессиональной аудитории дизайнеров, где требования к деталям интерфейса выше обычных
  • +
+
+
+

Стэк:

+
    +
  • Protopie
  • +
  • Графический редактор
  • +
  • Иконки / иллюстрации
  • +
+
+
+
+
+ +
+ +
+
+

MANU Video Game Maker

+

Январь 2019 — Декабрь 2021 (3 года)·manu.co

+
+

Игровой движок для создания видеоигр без написания кода.

+ +
+
+
+

Обязанности:

+
    +
  • Продуктовый дизайн движка целиком: от концепции взаимодействия до конечного UI
  • +
  • Проектирование поведения интерфейса для сложных пользовательских сценариев
  • +
  • Исследования пользователей: наблюдение, глубинные интервью, опросы
  • +
  • Составление портретов потенциальных пользователей и карт эмпатии
  • +
  • Работа с продуктовой аналитикой и метриками, завязанными на пользовательские сценарии
  • +
  • Вёрстка главной страницы сайта на Tilda, проектирование информационной архитектуры сайта вместе с командой
  • +
  • Контроль работы веб-графического дизайнера
  • +
  • Найм в продуктовый отдел: проведение собеседований и отбор специалистов
  • +
  • Руководство отделом, построение рабочих процессов всей продуктовой команды
  • +
  • Кураторство работ по созданию пользовательской документации
  • +
  • Внедрение инструментов автоматизации (Integromat, Zapier) для внутренних задач
  • +
+
+
+

Достижения:

+
    +
  • Провёл масштабный редизайн всей программы: заложил принципиально новую концепцию работы и стилистику
  • +
  • Спроектировал поведение интерфейса для 100+ сложных пользовательских сценариев
  • +
  • Спроектировал дизайн-систему продукта с нуля: 120 компонентов
  • +
  • Получили массу положительных, местами восторженных отзывов пользователей — в продукте стало значительно легче ориентироваться
  • +
  • Завершаемость онбординга выросла с 34% до 58% после редизайна
  • +
  • Провёл 25 глубинных интервью и 4 волны опросов, на их основе переработал 3 ключевых сценария
  • +
  • Нанял 4 специалистов в продуктовый отдел и вырос из дизайнера в тимлида команды
  • +
  • Выстроил процессы отдела: дизайн-ревью, работа с бэклогом, передача в разработку
  • +
  • Автоматизировал внутренние рутины команды, освободив время на продуктовые задачи
  • +
+
+
+

Стэк:

+
    +
  • Tilda
  • +
  • Integromat / Zapier
  • +
  • Исследования пользователей
  • +
+
+
+
+
+ +
+ +
+
+

Aegees Messenger

+

Октябрь 2017 — Декабрь 2018 (1 год и 3 месяца)·aegees.com

+
+

Кроссплатформенный мессенджер с защищенным хранилищем и аудиозвонками.

+ +
+
+
+

Обязанности:

+
    +
  • Дизайн мессенджера сразу под пять платформ: Android, iOS, macOS, Windows, Linux
  • +
  • Проработка сценариев защищённого хранилища и аудиозвонков
  • +
  • Проектирование сценариев регистрации, шифрования и восстановления доступа
  • +
  • Адаптация единой продуктовой логики под гайдлайны каждой платформы
  • +
  • Контроль работы зарубежных иллюстраторов по подготовке графических материалов
  • +
  • Подготовка материалов для магазинов приложений
  • +
+
+
+

Достижения:

+
    +
  • Выстроил единый визуальный язык продукта при соблюдении гайдлайнов пяти платформ одновременно
  • +
  • Спроектировал более 90 экранов в пяти платформенных вариантах
  • +
  • Обеспечил стабильный поток графических материалов от внешних подрядчиков без потери стилистики
  • +
  • Упростил сценарий защищённого хранилища так, чтобы им пользовались люди без технической подготовки
  • +
  • Работал в чувствительной области приватности и безопасности, где ошибка в интерфейсе стоит доверия пользователя
  • +
+
+
+

Стэк:

+
    +
  • Android
  • +
  • iOS
  • +
  • macOS
  • +
  • Windows
  • +
  • Linux
  • +
+
+
+
+
+ +
+ +
+
+

Orion Innovation (ex MERA)

+

Июль 2016 — Октябрь 2017 (1 год и 4 месяца)·orioninnovation.com

+
+

Разработка ПО для внешних заказчиков.

+ +
+
+
+

Обязанности:

+
    +
  • Работа в дизайн-отделе численностью до 8 человек
  • +
  • Создание анкет персон и составление карты функциональности продукта в соответствии с потребностями персон
  • +
  • Вайрфреймы и отрисовка конечного UI интерфейсов
  • +
  • Проектирование юзкейсов, карт экранов, демонстрации микроанимаций
  • +
  • Подготовка мудбордов и дизайн-концепций, обсуждение их с клиентами
  • +
  • Подготовка и проведение презентаций дизайн-концепций
  • +
  • Линковка мокапов в прототипы, написание сценариев и проведение качественных юзабилити-тестирований
  • +
  • Внесение правок по результатам тестирований
  • +
  • Авторский надзор над реализацией дизайна разработчиками
  • +
  • Участие в дизайн-ревью: разбор работ коллег и защита собственных решений
  • +
  • Прямая коммуникация с зарубежными клиентами для обсуждения дизайна и получения задач
  • +
+
+
+

Достижения:

+
    +
  • Провёл 10 сессий юзабилити-тестирования от написания сценария до внесения правок
  • +
  • Освоил новую для себя платформу Smart TV и довёл проект до успешной публикации в магазине приложений
  • +
  • Вёл коммуникацию с зарубежными заказчиками напрямую, без посредников
  • +
  • Активно участвовал в улучшении рабочего процесса дизайн-отдела компании
  • +
  • Прошёл полный цикл продуктового дизайна на 5 клиентских проектах
  • +
+
+
+

Стэк:

+
    +
  • Smart TV
  • +
  • Юзабилити-тестирование
  • +
  • Мудборды / дизайн-концепции
  • +
+
+
+
+
+ +
+ +
+
+

SpaceshipApps Studio

+

Июнь 2015 — Июнь 2016 (1 год и 1 месяц)·spaceshipapps.ru

+
+

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

+ +
+
+
+

Обязанности:

+
    +
  • Дизайн мобильных и веб-продуктов в потоковом режиме студии
  • +
  • Работа с разными доменами: соцгейминг, бьюти-услуги, доставка еды, голосовые интерфейсы, легал-сервисы, коллаборативные инструменты
  • +
  • Адаптивный веб-дизайн и дизайн административных панелей
  • +
  • Отрисовка иконок и подготовка графики для проектов
  • +
  • Подготовка макетов к передаче в разработку
  • +
+
+
+

Достижения:

+
    +
  • Выпустил 6 продуктов за год: геолокационная социальная игра с квестами в реальном мире, приложение записи к бьюти-специалистам, сервис доставки еды, голосовая игра со словами, сайт и админка сервиса регистрации авторского права, мобильная «белая доска» для совместной работы
  • +
  • Научился быстро входить в новую предметную область и держать качество при высоком темпе студии
  • +
  • Закрыл весь спектр задач: мобайл, веб, админ-панели, графика
  • +
  • Заложил базу для перехода от исполнительского дизайна к продуктовому
  • +
+
+
+

Стэк:

+
    +
  • Мобайл
  • +
  • Веб
  • +
  • Админ-панели
  • +
  • Графика
  • +
+
+
+
+
+ +
+ +

Навыки

+
+
+

Проектирование

+

UX/UI, product design, информационная архитектура, адаптивный дизайн, дизайн-системы

+
+
+

Исследования

+

Интервью, опросы, usability testing, JTBD, карты эмпатии, конверсия, retention, NPS

+
+
+

Инструменты

+

Figma, Protopie, Sketch, Adobe Creative Suite, Tilda, AI-инструменты

+
+
+

Управление

+

Построение команды, Agile, Kanban, авторский надзор, работа со стейкхолдерами

+
+
+ +
+ +

О себе

+

+ В дизайне больше 10 лет, последние годы — продуктовый дизайн сложных B2B-продуктов. Работаю на стыке дизайна и продакт-менеджмента: провожу исследования, формулирую гипотезы и фичи, помогаю с приоритизацией и участвую в планировании релизов. +

+

+ Люблю разбираться в сложном и делать его понятным — поэтому мне комфортно в продуктах вроде IDE, оркестраторов и ML-панелей, где обычному дизайнеру скучно или страшно. +

+

+ Не жду, пока задачу сделает кто-то другой: пишу себе инструменты сам. Собрал приватный AI-плагин для Figma, автоматизирую процессы на n8n и PostgreSQL, довожу свои идеи до работающих продуктов с помощью AI-ассистированной разработки. Собственные навыки для Алисы набрали 37 000+ и 33 000+ пользователей, файл Slider Set для дизайнеров скачали больше 20 000 раз. +

+

+ Участвую в жизни сообщества: веду Telegram-канал «Дизайн не пицца», выкладываю полезности в Figma Community, выступал на митапе Яндекса с докладом по аналитике, побеждал на хакатонах — самая приятная победа на хакатоне Яндекс.Денег в Петербурге за приложение оплаты по QR-коду. +

+

+ Английский — B1. Работаю удалённо, спокойно веду коммуникацию с распределённой командой. +

+
+
+ + + + + + + + diff --git a/public/index.html b/public/index.html index dff02db..7f7c672 100644 --- a/public/index.html +++ b/public/index.html @@ -11,9 +11,9 @@ - - - - - - - - - - - + + + + + + + + + + + @@ -127,6 +127,25 @@ + + + @@ -1351,7 +1370,7 @@