diff --git a/api/openapi.yaml b/api/openapi.yaml
index c40297c..20c11ad 100644
--- a/api/openapi.yaml
+++ b/api/openapi.yaml
@@ -2014,6 +2014,10 @@ components:
web_player:
type: string
enum: [bar, island]
+ search_position:
+ type: string
+ enum: [center, left, right]
+ description: Где в топбаре веба поиск (по умолчанию center)
mobile_player:
type: string
enum: [cover, wave]
diff --git a/apps/web/src/pages/SettingsPage.tsx b/apps/web/src/pages/SettingsPage.tsx
index f0bd20c..96e6bfc 100644
--- a/apps/web/src/pages/SettingsPage.tsx
+++ b/apps/web/src/pages/SettingsPage.tsx
@@ -126,6 +126,19 @@ function AppearanceSection() {
]}
/>
+
+ {t('settings.search_position')}
+
+ label={t('settings.search_position')}
+ value={user.ui_prefs.search_position ?? 'center'}
+ onChange={(v) => void setPrefs({ search_position: v })}
+ options={[
+ { value: 'left', label: t('settings.search_left') },
+ { value: 'center', label: t('settings.search_center') },
+ { value: 'right', label: t('settings.search_right') },
+ ]}
+ />
+
{t('settings.ambient')}
diff --git a/apps/web/src/pages/home.module.css b/apps/web/src/pages/home.module.css
index 3f9dbe1..7eba488 100644
--- a/apps/web/src/pages/home.module.css
+++ b/apps/web/src/pages/home.module.css
@@ -7,6 +7,7 @@
gap: 28px;
padding: 24px 0;
text-align: center;
+ user-select: none; /* главная — только элементы управления волной */
}
.orbLabel {
diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css
index a65d120..311db56 100644
--- a/apps/web/src/styles.css
+++ b/apps/web/src/styles.css
@@ -41,6 +41,19 @@ button {
cursor: pointer;
}
+/* Элементы управления не выделяются при кликах и двойных кликах — текст остаётся выделяемым на страницах. */
+button,
+[role='button'],
+[role='tab'],
+[role='option'],
+[role='menuitem'],
+[role='listitem'],
+nav a,
+label {
+ user-select: none;
+ -webkit-user-select: none;
+}
+
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
diff --git a/apps/web/src/widgets/AppShell.module.css b/apps/web/src/widgets/AppShell.module.css
index 01bfdc5..fe304d9 100644
--- a/apps/web/src/widgets/AppShell.module.css
+++ b/apps/web/src/widgets/AppShell.module.css
@@ -31,10 +31,11 @@
.top {
grid-area: top;
z-index: 30;
- display: flex;
+ /* Три зоны: логотип, середина, профиль; поиск — в одной из них (настройка «Расположение поиска»). */
+ display: grid;
+ grid-template-columns: 1fr auto 1fr;
align-items: center;
- gap: 24px;
- justify-content: space-between;
+ gap: 16px;
padding: 0 20px;
border-bottom: 1px solid var(--line);
background: var(--bar-a);
@@ -53,9 +54,22 @@
}
}
+.topLeft {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ min-width: 0;
+}
+
+.topCenter {
+ display: flex;
+ justify-content: center;
+}
+
.topRight {
display: flex;
align-items: center;
+ justify-content: flex-end;
gap: 12px;
}
diff --git a/apps/web/src/widgets/AppShell.tsx b/apps/web/src/widgets/AppShell.tsx
index 6656aad..b26966a 100644
--- a/apps/web/src/widgets/AppShell.tsx
+++ b/apps/web/src/widgets/AppShell.tsx
@@ -93,6 +93,8 @@ function Shell() {
const name = user.display_name ?? user.username;
const staff = user.role !== 'user';
const island = user.ui_prefs.web_player === 'island';
+ const searchAt = user.ui_prefs.search_position ?? 'center';
+ const search = ;
const counts = useCollectionCounts();
const pending = usePendingUploads(user.role === 'admin');
@@ -100,11 +102,15 @@ function Shell() {
{user.ui_prefs.ambient !== false &&
}
-
-
-
+
+
+
+
+ {searchAt === 'left' && search}
+
+ {searchAt === 'center' && search}
-
+ {searchAt === 'right' && search}