diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7549aaa..2f61b95 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,17 @@
## Не выпущено
+- Frontend `0.3.2`: устранены пересечения названия бренда с поиском и
+ мобильной навигации документа; навигационные ссылки получили общие quiet и
+ primary состояния.
+- Frontend `0.3.1`: уточнена компоновка поиска, лаборатории и кабинета;
+ splitter сохраняет минимальную ширину панелей, а на средних экранах фильтры
+ открываются доступной боковой панелью. Исправлено отображение футера на
+ маршруте пользователей и browser fixture смены пароля.
+- Frontend `0.3.0` / Backend `0.13.1`: добавлен доступный splitter для
+ изменения ширины результатов и документа. Язык поиска находится в фильтрах;
+ состояния открытия, оглавление правовых заголовков, лаборатория и кабинет
+ приведены к последовательной и компактной компоновке.
- Frontend `0.2.6` / Backend `0.13.1`: двухпанельная компоновка поиска
сохраняется до ширины 1175 px, чтобы три панели не обрезались при
промежуточных ширинах окна.
diff --git a/README.md b/README.md
index c3f7aea..854e61b 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ Telegram-бот — только часть рабочего окружения
## Текущее состояние
Сейчас реализованы Telegram-бот-секретарь версии `0.2.2`, backend версии
-`0.13.0` и frontend `0.2.3` с поисковым экраном и внутренней лабораторией.
+`0.13.1` и frontend `0.3.2` с поисковым экраном и внутренней лабораторией.
Авторизация хранится в
SQLite; первый пользователь `admin` создаётся интерактивной CLI-командой.
Поиск расширяет аббревиатуры организационно-правовых форм и поднимает
@@ -19,8 +19,8 @@ SQLite; первый пользователь `admin` создаётся инт
| Компонент | Версия | Состояние |
|---|---:|---|
| Telegram-бот | `0.2.2` | на Synology работает `0.2.1`; обновление после слияния |
-| Backend | `0.13.0` | авторизация, поиск с фильтрами, ограниченный staging и статические ресурсы |
-| Frontend | `0.2.3` | поиск по правовой базе, просмотр редакций и внутренняя лаборатория |
+| Backend | `0.13.1` | авторизация, поиск с фильтрами, ограниченный staging и статические ресурсы |
+| Frontend | `0.3.2` | поиск по правовой базе, просмотр редакций, настраиваемая ширина панелей и внутренняя лаборатория |
| Сбор и обработка правовых данных | `0.8.0` | добавлены relevance set и оценка выдачи |
| RAG и база знаний | — | ещё не созданы |
@@ -63,4 +63,4 @@ python3 -m unittest discover -s tools/telegram-bot -v
---
-Акылдаш · Telegram-бот v0.2.2 · Backend v0.13.0 · Frontend v0.2.3
+Акылдаш · Telegram-бот v0.2.2 · Backend v0.13.1 · Frontend v0.3.2
diff --git a/backend/search/minjust_opensearch.py b/backend/search/minjust_opensearch.py
index 807ef09..314a06a 100644
--- a/backend/search/minjust_opensearch.py
+++ b/backend/search/minjust_opensearch.py
@@ -465,7 +465,7 @@ def main() -> int:
else:
documents, fragments = export_bulk(arguments.input, arguments.output, arguments.index, arguments.limit)
destination = arguments.output
- print(f"documents={documents} fragments={fragments} destination={destination}\nAkyldash · Backend v{APP_VERSION} · Frontend v0.2.6")
+ print(f"documents={documents} fragments={fragments} destination={destination}\nAkyldash · Backend v{APP_VERSION} · Frontend v0.3.2")
return 0
diff --git a/backend/search/review.html b/backend/search/review.html
index 6a95198..df2d315 100644
--- a/backend/search/review.html
+++ b/backend/search/review.html
@@ -14,14 +14,14 @@
@font-face { font-family: "Noto Serif"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/static/fonts/noto-serif-cyrillic-ext.woff2") format("woff2"); unicode-range: U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F; }
@font-face { font-family: "Noto Serif"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/static/fonts/noto-serif-cyrillic.woff2") format("woff2"); unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116; }
@font-face { font-family: "Noto Serif"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/static/fonts/noto-serif-latin.woff2") format("woff2"); unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
- :root { color-scheme: light; font: 16px/1.5 "Noto Sans", sans-serif; color: #38262A; background: #F7F2E8; --ink: #38262A; --muted: #786469; --line: #DED3CE; --surface: #fff; --surface-muted: #FBF8F4; --brand: #9D2633; --brand-dark: #84202B; --accent: #A37B32; --focus: #245C78; --subtle: #F8EBED; --serif: "Noto Serif", serif; }
+ :root { color-scheme: light; font: 16px/1.5 "Noto Sans", sans-serif; color: #38262A; background: #F7F2E8; --ink: #38262A; --muted: #786469; --line: #DED3CE; --surface: #fff; --surface-muted: #FBF8F4; --brand: #9D2633; --brand-dark: #84202B; --accent: #A37B32; --focus: #245C78; --subtle: #F8EBED; --sans: "Noto Sans", sans-serif; --serif: "Noto Serif", serif; }
* { box-sizing: border-box; }
body { margin: 0; color: var(--ink); background: #F7F2E8; }
.review__skip { position: absolute; inset-block-start: 0; inset-inline-start: -10000px; padding: .5rem 1rem; background: var(--surface); }
.review__skip:focus { inset-inline-start: 1rem; z-index: 2; }
.review__header, .review__main { max-width: 1320px; margin: auto; padding-inline: clamp(1rem, 3vw, 3rem); }
.review__header { padding-block: 0 1.5rem; background: var(--surface); border-block-end: 1px solid var(--line); }
- .review__header h1 { max-width: 24ch; margin: 1.5rem 0 .4rem; font: 700 clamp(1.75rem, 3vw, 2rem)/1.2 var(--serif); }
+ .review__header h1 { max-width: none; margin: 1.5rem 0 .4rem; font: 700 clamp(1.75rem, 3vw, 2rem)/1.2 var(--serif); }
.review__header p { max-width: 65ch; margin: 0; color: var(--muted); }
.review__header-top { display: flex; justify-content: space-between; gap: 1rem; align-items: start; }
.review__eyebrow { display: inline-flex; align-items: center; gap: .5rem; color: var(--ink); font: 600 1.1rem/1.25 var(--serif); }
@@ -39,7 +39,8 @@
button:active { scale: .96; }
button:disabled { cursor: wait; opacity: .55; }
.review__button--primary { background: var(--brand); color: #fff; border-color: var(--brand); }
- .review__status { min-height: 1.7rem; margin-block: 1rem; color: var(--muted); }
+ .review__status { min-height: 0; margin-block: 0; color: var(--muted); }
+ .review__status:not(:empty) { margin-block: .5rem; }
.review__status--error { color: #9D2633; }
.review__workspace { display: grid; grid-template-columns: minmax(24rem, .9fr) minmax(28rem, 1.1fr); gap: 1rem; align-items: start; }
.review__results, .review__document { min-width: 0; background: var(--surface); border: 1px solid var(--line); border-radius: 6px; padding: clamp(1rem, 2vw, 1.5rem); }
@@ -48,13 +49,13 @@
.review__panel-hint { color: var(--muted); font-size: .875rem; }
.review__results-list { display: grid; gap: .75rem; margin: 0; padding: 0; list-style: none; }
.review__result { padding: 1rem 0; border: 0; border-block-end: 1px solid var(--line); border-radius: 0; background: var(--surface); transition-property: background-color, border-color; transition-duration: 150ms; transition-timing-function: cubic-bezier(.2, 0, 0, 1); }
- .review__result--selected { background: var(--subtle); box-shadow: none; }
+ .review__result--selected { padding: 8px 10px; background: var(--subtle); box-shadow: none; }
.review__result:first-child { border-block-start: 1px solid var(--line); }
.review__result-title { display: flex; gap: .65rem; align-items: baseline; width: 100%; min-height: 44px; text-align: start; font-weight: 700; border: 0; padding: 0; background: none; color: var(--brand-dark); }
.review__rank { flex: 0 0 auto; color: var(--brand); font-variant-numeric: tabular-nums; }
- .review__meta { margin-block: .5rem; color: var(--muted); font-size: .875rem; }
- .review__rating { display: grid; grid-template-columns: repeat(4, 1fr); gap: .4rem; margin-block: 1rem .75rem; padding: 0; border: 0; }
- .review__rating legend { width: 100%; margin-block-end: .35rem; font-size: .875rem; font-weight: 700; color: var(--muted); grid-column: 1 / -1; }
+ .review__meta { margin-block: 4px; color: var(--muted); font-size: .875rem; }
+ .review__rating { display: grid; grid-template-columns: repeat(4, 1fr); gap: .35rem; margin-block: 8px 6px; padding: 0; border: 0; }
+ .review__rating legend { width: 100%; margin-block-end: 2px; font-size: .875rem; font-weight: 700; color: var(--muted); grid-column: 1 / -1; }
.review__rating label { display: grid; place-items: center; min-width: 0; min-height: 44px; padding: .45rem .25rem; border: 1px solid var(--line); border-radius: 6px; color: var(--muted); text-align: center; font-size: .875rem; cursor: pointer; }
.review__rating label:has(input:checked) { border-color: var(--brand); background: var(--brand); color: #fff; }
.review__rating label:has(input:focus-visible) { outline: 3px solid var(--focus); outline-offset: 2px; }
@@ -110,7 +111,7 @@
.search__layout { display: grid; grid-template-columns: 230px minmax(320px, .9fr) minmax(420px, 1.2fr); min-height: 65vh; border-block: 1px solid var(--line); background: var(--surface); }
.search__filters, .search__results, .search__document { min-width: 0; padding: 1rem; }
.search__filters, .search__results { border-inline-end: 1px solid var(--line); }
- .search__filters h2, .search__results h2, .search__document h2 { margin: 0; font: 600 1.5rem/1.3 var(--serif); }
+ .search__filters > .review__panel-heading h2, .search__results > .review__panel-heading h2 { margin: 0; font: 600 1.25rem/1.3 var(--sans); }
.search__filters .review__panel-heading, .search__results .review__panel-heading { align-items: center; }
.search__filter { margin-block: 1.25rem; }
.search__filter h3 { margin: 0 0 .4rem; font-size: 1rem; }
@@ -161,9 +162,11 @@
.review__workspace { grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); }
.review__document { position: static; max-height: min(68dvh, 860px); overflow: auto; overscroll-behavior: contain; }
.review__document-body { max-width: 72ch; margin-inline: auto; }
+ .review__document-body > h2:first-child { font: 600 30px/38px var(--serif); }
.review__document-body table { display: block; max-width: 100%; overflow-x: auto; }
.review__document-body p:empty { display: none; }
.review__document-body a { color: var(--brand-dark); text-underline-offset: .18em; }
+ .review__document-meta a { color: var(--brand-dark); text-underline-offset: .18em; }
.search__layout[data-filters-hidden="true"] { grid-template-columns: minmax(320px, .8fr) minmax(0, 1.5fr); }
.search__layout[data-filters-hidden="true"] .search__filters { display: none; }
.search__layout.is-expanded { position: fixed; z-index: 15; inset: 0; display: block; overflow: auto; background: var(--surface); }
@@ -178,14 +181,15 @@
.search__layout:not([data-has-document="true"]) .search__toc,
.search__layout:not([data-has-document="true"]) #document-source { display: none; }
.search__layout:not([data-has-document="true"]) .search__document-body { display: none; }
+ .search__layout:not([data-has-document="true"]) .search__document-head h2 { display: none; }
.search__mobile-filters { display: inline-flex; }
.search__account button { display: inline-flex; width: auto; min-height: 44px; gap: 8px; align-items: center; padding: 8px 12px; }
.search__account-label { display: inline; }
.search__document-body table, .review__document-body table { display: block; max-width: 100%; overflow-x: auto; }
.search__table-scroll { max-width: 100%; margin-block: 1rem; overflow-x: auto; overscroll-behavior-inline: contain; }
.search__table-scroll table { width: max-content; min-width: 100%; border-collapse: collapse; }
- .search__filters .review__panel-heading { align-items: center; flex-wrap: wrap; }
- .search__filters .review__panel-heading h2 { flex: 1 1 auto; }
+ .search__filters .review__panel-heading { align-items: center; flex-wrap: nowrap; justify-content: space-between; gap: 8px; }
+ .search__filters .review__panel-heading h2 { flex: 0 1 auto; white-space: nowrap; }
.search__filters .review__panel-heading button { flex: 0 0 auto; }
.search__filter label { display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; align-items: center; gap: 8px; min-height: 40px; }
.search__filter label span { min-width: 0; overflow-wrap: anywhere; }
@@ -212,6 +216,8 @@
body.is-users-route { min-height: 100dvh; display: flex; flex-direction: column; }
body.is-users-route > .review__header { display: block; width: 100%; padding-block-end: 0; }
body.is-users-route > .review__header > .review__header-content { display: none; }
+ body.is-users-route > .review__actions { display: none; }
+ body.is-users-route > .search__footer { display: none; }
body.is-users-route > footer.review__header { display: block; margin-block-start: auto; }
.review__users-page { width: min(1200px, 100%); flex: 1; margin-inline: auto; padding: 28px clamp(16px, 4vw, 48px); }
.review__users-page[hidden] { display: none !important; }
@@ -233,7 +239,6 @@
.search__layout:not([data-filters-hidden="true"]) .search__filters > .search__dates,
.search__layout:not([data-filters-hidden="true"]) .search__filters > .search__date-label,
.search__layout:not([data-filters-hidden="true"]) .search__filters > #filter-fields { grid-column: 1 / -1; }
- .search__filters .search__ornament { display: none; }
}
@media (max-width: 900px) {
body:has(.search:not([hidden])) { height: auto; min-height: 100dvh; overflow: visible; }
@@ -281,12 +286,11 @@
body:has(.search:not([hidden])) > .search__chrome { display: block; }
.search__chrome { display: none; background: var(--surface); border-block-end: 1px solid var(--line); }
.search[hidden] { display: none !important; }
- .search__topbar { min-height: var(--search-header-height); display: grid; grid-template-columns: minmax(390px, 430px) minmax(0, 1fr) auto; gap: 16px; align-items: center; padding: 5px 26px; border-block-end: 1px solid var(--line); }
- .search__brand { display: flex; min-width: 0; align-items: center; gap: 12px; color: var(--ink); text-decoration: none; white-space: nowrap; }
+ .search__topbar { min-height: var(--search-header-height); display: grid; grid-template-columns: 166px minmax(0, 1fr) auto; gap: 16px; align-items: center; padding: 5px 26px; border-block-end: 1px solid var(--line); }
+ .search__brand { display: flex; min-width: 0; align-items: center; gap: 8px; color: var(--ink); text-decoration: none; white-space: nowrap; }
.search__brand-mark { width: 44px; height: 44px; flex: none; color: var(--brand); }
- .search__brand-name { font: 700 26px/1.1 var(--serif); }
- .search__brand-divider { width: 1px; height: 32px; background: var(--line); }
- .search__brand-description { color: var(--muted); font-size: 14px; }
+ .search__brand-name { font: 700 22px/1.1 var(--serif); }
+ .search__brand-divider, .search__brand-description { display: none; }
.search__query { display: grid; grid-template-columns: minmax(0, 1fr) 160px; gap: 12px; align-items: end; min-width: 0; padding: 0; border: 0; background: transparent; }
.search__query label { display: grid; gap: 4px; color: var(--muted); font-size: 14px; font-weight: 500; }
.search__query label > span { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
@@ -300,9 +304,10 @@
.search__ui-language button[aria-pressed="true"] { color: var(--ink); border-block-end: 2px solid var(--brand); }
.search__account { display: flex; align-items: center; gap: 6px; margin-inline-start: auto; }
.search__account button { width: 40px; min-height: 40px; padding: 8px; }
- .search__account-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-block-end: 16px; }
+ .search__account-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-block: 24px 0; }
.search__subnav { min-height: var(--search-nav-height); display: flex; align-items: center; gap: 12px; padding: 6px 26px; overflow-x: auto; white-space: nowrap; }
- .search__subnav a, .search__subnav button { min-height: 36px; padding: 5px 14px; text-decoration: none; }
+ .search__subnav a, .search__subnav button { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; padding: 5px 14px; text-align: center; text-decoration: none; }
+ .search__subnav a:not(.is-active) { color: var(--ink); }
.search__mobile-filters, .search__mobile-back, .search__filter-close, .search__filter-overlay { display: none; }
.search__subnav a.is-active { min-width: 97px; background: var(--brand); color: #fff; border-color: var(--brand); }
.search__subnav .search__nav-text { margin-inline: 11px 28px; padding-inline: 0; border: 0; color: var(--ink); font-size: 13px; }
@@ -311,8 +316,7 @@
.search__chips .search__chip { padding-inline: 4px; }
.search__chips .search__chip button { width: 16px; }
.search__language-chip { padding-inline: 4px; }
- .search__language-chip select { width: 78px; min-width: 0; min-height: 24px; border: 0; padding: 0 12px 0 0; background: transparent; font-size: 14px; }
- html[lang="ky"] .search__language-chip select { width: 100px; }
+ .search__language-chip select { width: auto; min-width: 126px; min-height: 36px; border: 0; padding: 0 12px 0 0; background: transparent; font-size: 14px; }
.search__chip button { min-height: 24px; width: 24px; padding: 0; border: 0; font-size: 18px; line-height: 1; }
.search { width: 100%; max-width: none; min-height: 0; flex: 1; display: flex; flex-direction: column; margin: 0; }
.search__sr-only { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
@@ -320,7 +324,9 @@
.search__layout { flex: 1; min-height: 0; grid-template-columns: minmax(210px, 226px) minmax(350px, 422px) minmax(400px, 1fr); border-block: 1px solid var(--line); }
.search__filters, .search__results, .search__document { min-height: 0; padding: 14px 16px 20px; }
.search__filters { position: relative; display: flex; flex-direction: column; overflow: auto; background: var(--surface-muted); padding-inline: 30px 16px; }
- .search__filters .review__panel-heading { margin-block-end: 18px; }
+ .search__filters .review__panel-heading { margin-block-end: 8px; }
+ #search-filters > .review__panel-heading { margin-block-end: 0; }
+ #search-filters > .search__filter-language { margin-block-start: 0; }
.search__filters .review__panel-heading button { min-height: 36px; padding: 2px; border: 0; color: var(--brand); text-decoration: underline; text-underline-offset: 3px; }
.search__filter { margin-block: 0 16px; }
.search__filter--document-type { margin-block-end: 24px; }
@@ -340,7 +346,6 @@
.search__dates label:not(.has-value) input { color: transparent; }
.search__date-placeholder { position: absolute; inset-inline-start: 9px; inset-block-start: 50%; color: var(--muted); font-size: 14px; pointer-events: none; transform: translateY(-50%); }
.search__dates label.has-value .search__date-placeholder { display: none; }
- .search__ornament { width: calc(100% - 12px); height: 56px; flex: none; margin: auto 6px 0; color: var(--accent); pointer-events: none; }
.search__results { overflow: auto; background: var(--surface-muted); }
.search__results .review__panel-heading { align-items: start; margin-block-end: 0; }
.search__results .review__panel-heading select { min-height: 40px; padding: 6px 10px; font-size: 14px; }
@@ -363,7 +368,7 @@
.search__document-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: start; }
.search__breadcrumbs { display: flex; gap: 12px; align-items: center; color: var(--muted); font-size: 14px; }
.search__breadcrumbs span:first-child { color: var(--brand); }
- .search__document-head h2 { grid-column: 1 / -1; margin-block: 0 4px; color: var(--ink); font: 700 36px/1 var(--serif); }
+ .search__document-head h2 { grid-column: 1 / -1; margin-block: 0 4px; color: var(--ink); font: 600 30px/38px var(--serif); }
.search__document-head .search__expand { min-height: 32px; border: 0; padding: 0 4px; }
.search__document-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; border: 0; color: var(--muted); }
.search__document-meta a { color: var(--brand); }
@@ -374,7 +379,7 @@
.search__document-toolbar .search__edition-label { display: block; }
.search__document-toolbar .search__edition-label select { width: 160px; max-width: 160px; }
.search__source-link { max-width: 150px; margin-inline-start: auto; color: var(--brand); font-size: 14px; white-space: nowrap; }
- .search__document-language { display: inline-flex !important; align-items: center; gap: 6px !important; margin-inline-start: auto; font-size: 12px !important; }
+ .search__document-language { display: inline-flex; align-items: center; gap: 6px; margin-inline-start: auto; font-size: 12px; }
.search__document-language select { min-height: 28px; max-width: 130px; padding: 4px 8px; font-size: 14px; }
.search__toc { margin-block-end: 0; }
.search__toc summary { width: fit-content; min-height: 42px; padding: 9px 8px; border: 1px solid var(--line); border-radius: 6px; cursor: pointer; font-size: 14px; }
@@ -428,8 +433,11 @@
.search__layout[data-mobile-view="results"] .search__document { display: none; }
.search__layout[data-mobile-view="results"] .search__results { display: block; }
.search__layout[data-mobile-view="document"] .search__filters { display: none; }
- .search__ornament { display: none; }
.search__document { min-height: 30rem; }
+ .search__document-head { grid-template-columns: minmax(0, 1fr); }
+ .search__document-head > div:nth-child(2) { display: flex; justify-content: space-between; gap: 8px; }
+ .search__breadcrumbs { min-width: 0; flex-wrap: wrap; }
+ .search__breadcrumbs #document-breadcrumb { min-width: 0; overflow-wrap: anywhere; }
}
@media (max-width: 600px) {
.search__topbar { grid-template-columns: minmax(0,1fr) auto; gap: 8px; padding: 10px 16px; }
@@ -466,6 +474,7 @@
body.is-users-route { width: 100%; height: auto; min-height: 100dvh; overflow: visible; display: flex; flex-direction: column; }
body.is-users-route > header.review__header { display: block; width: 100%; padding-block-end: 0; }
body.is-users-route > header.review__header > .review__header-content { display: none; }
+ body.is-users-route > .review__actions { display: none; }
body.is-users-route > footer.review__header { display: block; width: 100%; margin-block-start: auto; }
.search__layout { grid-template-columns: clamp(256px, 19.45vw, 280px) clamp(360px, 30vw, 408px) minmax(560px, 1fr); }
.search__account button { width: auto; min-width: 44px; min-height: 44px; padding: 8px 12px; }
@@ -486,14 +495,134 @@
.review__nav-links { order: 3; width: 100%; flex-wrap: wrap; }
.review__nav-account { margin-inline-start: auto; }
}
+ /* Shared follow-up corrections: consistent navigation, compact public search,
+ and a continuous workspace that keeps controls aligned to the same shell. */
+ .review__nav { width: min(100%, 1440px); max-width: 1440px; }
+ .review__nav-links a, .review__nav-links button, #search-review-nav {
+ display: inline-flex; align-items: center; justify-content: center; min-height: 44px;
+ padding: 8px 14px; border: 1px solid var(--line); border-radius: 6px;
+ color: var(--brand); background: var(--surface); text-decoration: none; font: inherit;
+ }
+ .review__nav-links a:hover, .review__nav-links button:hover, #search-review-nav:hover { border-color: var(--line); background: var(--surface-muted); color: var(--brand); }
+ .review__nav-links .is-active, #search-review-nav.is-active { border-color: var(--brand); background: var(--brand); color: #fff; }
+ .review__nav-links .is-active:hover, #search-review-nav.is-active:hover { border-color: var(--brand-dark); background: var(--brand-dark); color: #fff; }
+ .review__nav-links a:focus-visible, .search__subnav a:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
+ .review__nav-links [hidden], .search__account-actions [hidden] { display: none !important; }
+ .review__nav-account > span { color: var(--muted); }
+ .review__header-content { width: 100%; max-width: none; padding-inline: 24px; }
+ body:has(#review:not([hidden])) { min-height: 100dvh; height: 100dvh; overflow: hidden; }
+ body:has(#review:not([hidden])) > .review__header { padding: 0; }
+ body:has(#review:not([hidden])) > #review { width: 100%; max-width: none; min-height: 0; margin: 0; padding: 0; display: flex; flex-direction: column; }
+ body:has(#review:not([hidden])) > .review__actions { width: 100%; min-height: 96px; flex: 0 0 96px; box-sizing: border-box; margin: 0; padding: 0 24px; }
+ body:has(#review:not([hidden])) > footer.review__header { width: 100%; margin: 0; }
+ .review__header-content, .review__actions-inner { width: 100%; max-width: none; }
+ body:has(#review:not([hidden])) .review__nav { height: 64px; min-height: 64px; padding-block: 0; }
+ body:has(#review:not([hidden])) .review__header-content { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; height: 64px; }
+ body:has(#review:not([hidden])) .review__heading-copy { min-width: 0; display: grid; grid-template-rows: 32px 20px; align-content: center; }
+ body:has(#review:not([hidden])) .review__header-top { grid-column: 2; grid-row: 1; align-self: center; padding: 0; }
+ body:has(#review:not([hidden])) .review__header-content h1 { max-width: none; margin: 0; font: 600 26px/32px var(--sans); }
+ body:has(#review:not([hidden])) .review__header-content p { max-width: none; margin: 0; font: 400 14px/20px var(--sans); white-space: nowrap; }
+ .review__search { gap: 12px; padding: 12px 24px; border: 0; border-block-end: 1px solid var(--line); border-radius: 0; background: transparent; }
+ #status.review__status { grid-column: 1 / -1; margin: 0; }
+ #status.review__status:empty { display: none; }
+ #status.review__status--success { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
+ .review__workspace { min-height: 0; flex: 1 1 0; grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); gap: 0; align-items: stretch; overflow: hidden; border-block: 1px solid var(--line); background: var(--surface); }
+ .review__results, .review__document { min-height: 0; max-height: none; padding: 14px 24px; border: 0; border-radius: 0; background: var(--surface); }
+ .review__results { overflow: auto; border-inline-end: 1px solid var(--line); }
+ .review__document { position: static; overflow: auto; overscroll-behavior: contain; }
+ .review__document-meta { overflow-wrap: anywhere; }
+ .review__actions-inner { padding: 5px 24px; border: 0; border-block-start: 1px solid var(--line); border-radius: 0; background: transparent; }
+ body:has(#review:not([hidden])) > footer.review__header { height: 44px; min-height: 44px; }
+ body:has(#review:not([hidden])) > footer.review__header .review__header-content { display: flex; align-items: center; height: 44px; padding-block: 0; }
+ .search__subnav { flex-wrap: wrap; overflow: visible; white-space: normal; }
+ .search__subnav a.is-active { min-width: 0; text-align: center; }
+ .search__nav-text { flex: 0 0 auto; }
+ .search__chips { flex: 1 1 8rem; }
+ .search__filter-language { display: grid; gap: 5px; margin-block: 0 16px; color: var(--muted); font-size: .875rem; font-weight: 600; }
+ .search__filter-language select { min-height: 42px; width: 100%; padding: 7px 10px; color: var(--ink); background: var(--surface); border: 1px solid var(--line); }
+ .search__layout { grid-template-columns: minmax(264px, clamp(264px, calc(20vw + 8px), 288px)) minmax(360px, 38.1fr) 8px minmax(560px, 61.9fr); }
+ .search__layout[data-filters-hidden="true"] { grid-template-columns: minmax(360px, 38.1fr) 8px minmax(560px, 61.9fr); }
+ .search__filters { margin-inline-end: 8px; }
+ .search__mobile-filters { display: inline-flex; }
+ .search__layout[data-filters-hidden="true"] .search__filters { display: none; }
+ .search__splitter { position: relative; z-index: 1; align-self: stretch; justify-self: center; width: 12px; min-width: 12px; min-height: 100%; margin-inline: -2px; padding: 0; border: 0; border-radius: 0; background: linear-gradient(90deg, transparent 2px, var(--surface-muted) 2px 10px, transparent 10px); cursor: col-resize; touch-action: none; }
+ .search__splitter::before { position: absolute; inset-block: 0; inset-inline-start: 50%; width: 1px; background: var(--line); content: ''; transform: translateX(-50%); }
+ .search__splitter::after { position: absolute; inset-block-start: 50%; inset-inline-start: 50%; width: 3px; height: 40px; border-radius: 2px; background: var(--muted); content: ''; transform: translate(-50%, -50%); }
+ .search__splitter:hover::after, .search__splitter:focus-visible::after { background: var(--brand); }
+ body:has(#review:not([hidden])) > .review__header, body:has(#review:not([hidden])) > footer.review__header { width: 100%; max-width: none; margin: 0; padding-inline: 0; }
+ body:has(#review:not([hidden])) .review__nav, body:has(#review:not([hidden])) .review__header-content { width: 100%; max-width: none; padding-inline: 24px; }
+ body:has(#review:not([hidden])) .review__header-content h1 { font: 600 26px/32px var(--sans); }
+ body:has(#review:not([hidden])) .review__panel-heading h2 { font: 600 20px/26px var(--sans); }
+ body.is-users-route > .search__footer { display: none !important; }
+
+ .search__pagination[hidden] { display: none; }
+ .search__document-language[hidden] { display: none; }
+ .search__account-actions { display: grid; grid-template-columns: max-content max-content; justify-content: start; gap: 8px; }
+ .search__account-actions a { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; padding: 8px 12px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--brand-dark); font-weight: 600; text-decoration: none; }
+ .search__account-actions a:hover { border-color: var(--brand); background: var(--subtle); }
+ #search-logout { grid-column: 1 / -1; justify-self: start; min-height: 36px; margin-block-start: 2px; padding-inline: 0; border: 0; color: var(--muted); background: transparent; }
+ #account-dialog dl { display: grid; grid-template-columns: minmax(7.5rem, max-content) minmax(0, 1fr); gap: 8px 16px; margin: 16px 0 24px; }
+ #account-dialog { width: min(560px, calc(100vw - 32px)); max-width: none; }
+ #account-dialog > .review__page { padding: 16px; }
+ #account-dialog dt { color: var(--muted); }
+ #account-dialog dd { min-width: 0; margin: 0; overflow-wrap: anywhere; }
+ #account-dialog .review__dialog-heading { inset-block-start: -16px; align-items: center; min-height: 44px; height: 44px; margin: -16px -16px 12px; padding: 0 16px; }
+ .review__password-field { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 8px; }
+ .review__password-field input { min-width: 0; }
+ .review__password-toggle { min-height: 44px; padding-inline: 10px; white-space: nowrap; }
+ #account-dialog .review__dialog-heading h2 { max-width: none; padding: 0; font: 600 20px/28px var(--sans); }
+ #account-dialog .review__dialog-heading button { display: grid; place-items: center; flex: 0 0 44px; width: 44px; min-height: 44px; padding: 0; }
+ #public-document-body img, #document-body img { max-width: 100%; height: auto; object-fit: contain; }
+ @media (min-width: 901px) and (max-width: 1191px) {
+ #search-layout { grid-template-columns: minmax(360px, 38.1fr) minmax(560px, 61.9fr); }
+ #search-layout .search__filters { display: none; }
+ #search-layout .search__filters.is-open { position: fixed; z-index: 21; inset: 16px auto 16px 16px; display: block; width: min(360px, calc(100vw - 32px)); overflow: auto; padding: 20px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); box-shadow: 0 12px 40px rgb(34 28 28 / .2); }
+ .search__filter-close { display: inline-flex; }
+ .search__filter-overlay.is-open { position: fixed; z-index: 20; inset: 0; display: block; background: rgb(56 38 42 / .42); }
+ .search__splitter { display: none; }
+ }
+ @media (max-width: 900px) {
+ body:has(#review:not([hidden])) { min-height: 100svh; height: auto; overflow: visible; }
+ body:has(#review:not([hidden])) > #review { flex: none; padding-inline: 16px; }
+ body:has(#review:not([hidden])) .review__nav, body:has(#review:not([hidden])) .review__header-content { padding-inline: 16px; }
+ body:has(#review:not([hidden])) .review__header-content { display: flex; flex-direction: column; align-items: stretch; gap: 8px; height: auto; padding-block: 12px; }
+ body:has(#review:not([hidden])) .review__heading-copy { display: block; }
+ body:has(#review:not([hidden])) .review__header-top { align-self: flex-start; }
+ .review__search { padding-inline: 16px; }
+ body:has(#review:not([hidden])) .review__header-top { padding-block: 0 8px; }
+ body:has(#review:not([hidden])) .review__header-content h1, body:has(#review:not([hidden])) .review__header-content p { display: block; }
+ body:has(#review:not([hidden])) .review__header-content p { white-space: normal; }
+ body:has(#review:not([hidden])) > footer.review__header { height: auto; min-height: 44px; }
+ .review__workspace { min-height: 0; max-height: none; flex: none; display: grid; grid-template-columns: minmax(0, 1fr); overflow: visible; }
+ .review__results, .review__document { max-height: none; overflow: visible; border-inline: 0; }
+ .review__results { border-block-end: 1px solid var(--line); }
+ .review__document { display: block; }
+ .review__results, .review__document { padding-inline: 16px; }
+ body:has(#review:not([hidden])) > .review__actions { flex: 0 0 auto; padding-inline: 0; }
+ .review__actions-inner { padding-inline: 16px; }
+ body:has(#review:not([hidden])) > footer.review__header { height: auto; min-height: 44px; }
+ .search__layout, .search__layout[data-filters-hidden="true"] { display: block; }
+ .search__splitter { display: none; }
+ .search__filter-language { margin-block-end: 16px; }
+ .search__filters { display: none; }
+ .search__filters.is-open { display: block; }
+ .search__mobile-filters { display: inline-flex; }
+ }
+ @media (max-width: 600px) {
+ .search__subnav { gap: 4px; padding-inline: 12px; }
+ .search__subnav a, .search__subnav button { padding-inline: 10px; }
+ .search__chips { flex: 1 1 100%; order: 4; }
+ .search__account { margin-inline-start: auto; }
+ #account-dialog dl { grid-template-columns: minmax(0, 1fr); gap: 2px; }
+ #account-dialog dd { margin-block-end: 10px; }
+ }
Перейти к результатам
@@ -504,7 +633,7 @@
/
-
+
-
Результаты поиска
Оцените каждый документ- Начните с запросаВведите юридический вопрос выше, чтобы увидеть результаты.
Просмотр документа
Откройте результатВыберите название в списке, чтобы прочитать документ.
@@ -522,16 +651,17 @@
Поиск по правовой базе
-
-
Результаты
Введите запрос- Начните с поискаВведите запрос, чтобы найти документы.
-
Документ/Выберите результат
Документ
Выберите результат, чтобы прочитать доступную редакцию.
+
+
Результаты
- Введите запрос, чтобы начать поиск.
+
+
Документ/Выберите результат
Документ
Выберите документ в выдаче, чтобы открыть текст.
-
+
-
+
Пользователи
Права на действия определяются сервером.
/
@@ -545,7 +675,7 @@
-
+
`;
+const previewHtml = html.replace('', `${warning}`);
+const json = (response, status, value) => {
+ response.writeHead(status, {'Content-Type':'application/json; charset=utf-8','Cache-Control':'no-store','X-Content-Type-Options':'nosniff'});
+ response.end(JSON.stringify(value));
+};
+const readBody = (request) => new Promise((resolve) => {
+ let body = '';
+ request.on('data', (chunk) => { body += chunk; if (body.length > 32768) request.destroy(); });
+ request.on('end', () => { try { resolve(JSON.parse(body || '{}')); } catch { resolve({}); } });
+});
+const sendHtml = (response) => {
+ response.writeHead(200, {
+ 'Content-Type':'text/html; charset=utf-8',
+ 'Cache-Control':'no-store',
+ 'X-Content-Type-Options':'nosniff',
+ 'Referrer-Policy':'no-referrer',
+ 'Content-Security-Policy':"default-src 'self' data:; connect-src 'self'; img-src 'self' data:; font-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; form-action 'self'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; navigate-to 'self'"
+ });
+ response.end(previewHtml);
+};
+
+const server = http.createServer(async (request, response) => {
+ const url = new URL(request.url, 'http://127.0.0.1');
+ const method = request.method || 'GET';
+ if (method === 'GET' && ['/', '/review', '/users'].includes(url.pathname)) return sendHtml(response);
+ if (method === 'GET' && url.pathname === '/auth/me') return json(response, 200, {account:passwordChangeAccount});
+ if (method === 'POST' && url.pathname === '/auth/logout') return json(response, 200, {ok:true});
+ if (method === 'POST' && url.pathname === '/auth/change-password') return json(response, 200, {account:passwordChangeAccount});
+ if (method === 'GET' && url.pathname === '/search/filters') return json(response, 200, catalogs);
+ if (method === 'GET' && url.pathname === '/search') {
+ const query = url.searchParams.get('q') || '';
+ const lab = url.searchParams.has('page_size') && !url.searchParams.has('sort');
+ return json(response, 200, {results:query === 'not-found' ? [] : (lab ? visualReviewDocs : visualDocs),has_next:false,review_token:'fixture-only-token'});
+ }
+ if (method === 'GET' && url.pathname === '/documents/doc-2/editions') return json(response, 200, {available_languages:[],editions:[]});
+ if (method === 'GET' && url.pathname === '/documents/doc-3/editions') return json(response, 503, {error:'Fixture editions error'});
+ if (method === 'GET' && url.pathname === '/documents/doc-1/editions') return json(response, 200, {available_languages:['ru','ky'],editions:[{source_code:'2024',name:{ru:'Редакция от 12 января 2024 года',ky:'2024-жылдын 12-январындагы редакция'},available_languages:['ru','ky']},{source_code:'2023',name:{ru:'Редакция только на русском'},available_languages:['ru']}]});
+ const visualReviewEdition = url.pathname.match(/^\/documents\/(review-a|review-b)\/editions\/(2026|2025)$/);
+ if (method === 'GET' && visualReviewEdition) return json(response, 200, {content:{ru:{html:visualReviewArticles[visualReviewEdition[1]]}}});
+ const edition = url.pathname.match(/^\/documents\/doc-1\/editions\/(2024|2023)$/);
+ if (method === 'GET' && edition) {
+ const language = url.searchParams.get('language') || 'ru';
+ const content = edition[1] === '2023' ? 'Russian-only edition
Regression fixture.
' : language === 'ky' ? articleKy : visualArticle;
+ return json(response, 200, {content:{[language]:{html:content}}});
+ }
+ if (method === 'GET' && url.pathname === '/admin/users') return json(response, 200, {users});
+ if (method === 'POST' && url.pathname === '/admin/users') {
+ const body = await readBody(request); const username = String(body.username || 'fixture-created');
+ users.push({username,role:'user',must_change_password:true,last_login_at:null,first_login_at:1780000000,password_reset_at:null});
+ return json(response, 201, {username});
+ }
+ if (method === 'POST' && /^\/admin\/users\/[^/]+\/reset-password$/.test(url.pathname)) return json(response, 200, {ok:true});
+ if (method === 'DELETE' && /^\/admin\/users\/[^/]+$/.test(url.pathname)) return json(response, 200, {ok:true});
+ if (method === 'POST' && url.pathname === '/search-reviews') return json(response, 200, {id:42});
+ if (method === 'GET' && url.pathname.startsWith('/static/')) {
+ const name = path.basename(url.pathname);
+ const file = path.join(staticRoot, url.pathname.endsWith('.woff2') ? 'fonts' : '', name);
+ if (!fs.existsSync(file) || !fs.statSync(file).isFile()) return json(response, 404, {error:'Not found'});
+ const type = name.endsWith('.woff2') ? 'font/woff2' : name.endsWith('.svg') ? 'image/svg+xml' : 'image/png';
+ response.writeHead(200, {'Content-Type':type,'Cache-Control':'no-store','X-Content-Type-Options':'nosniff'});
+ return fs.createReadStream(file).pipe(response);
+ }
+ return json(response, 404, {error:'Fixture route not found'});
+});
+
+server.on('error', (error) => { process.stderr.write(`${error.code || error.name}\n`); process.exitCode = 1; });
+server.listen(port, '127.0.0.1', () => {
+ process.stdout.write(`fixture preview listening at http://127.0.0.1:${port}/ (pid ${process.pid})\n`);
+});