Files
ForbiddenStarsApp/frontend/src/styles/global.css
T
NotBigGhostandClaude Opus 5 d31e998f87 Объявления: окно у игрока и редактор в админке
Окно — вариант E из макетов: заголовок в плашке-шапке, текст, точки очереди
и «Понятно», крестика нет. Очередь идёт от старого к новому; закрытое сразу
уходит из кэша, следующее открывается без ожидания сети. У повторно
показанного объявления под заголовком — красная пометка «обновлено».
AppShell показывает окно на любой странице и только когда пароль задан:
обязательное окно пароля всегда первое. Новые и изменённые объявления
приходят SSE-событием announcements, отложенное начало показа — перезапросом
раз в 5 минут.

Админка — вкладка «Объявления» с редактором R1: contenteditable и панель
(жирный, курсив, золотой и красный акцент, эмодзи, снятие оформления),
вставка и перетаскивание только простым текстом, счётчик символов, период
показа по МСК, переключатели «показывать новым игрокам» и «показать заново».
Предпросмотр — тем же окном, что у игрока. Список со статусом, счётчиком
«закрыли N из M» и действиями: изменить, снять с показа, дублировать,
удалить. Выделение текста в форме — полупрозрачный оранжевый вместо синего.

schema.d.ts пересобран из OpenAPI. #84

Перенесено в main без рейтинга из ab1fc3f; schema.d.ts сверен с OpenAPI этой ветки.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 21:59:32 +03:00

551 lines
17 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@import "./tokens.css";
* {
box-sizing: border-box;
/* Убираем синий/серый прямоугольник подсветки при тапе на мобильных. */
-webkit-tap-highlight-color: transparent;
}
html, body, #root {
height: 100%;
margin: 0;
}
/* Высота каркаса — по видимой области (dvh): на мобильных 100% берётся от layout
viewport, который меняется при сворачивании адресной строки. Правило выше остаётся
фоллбэком для движков без dvh. */
#root { height: 100dvh; }
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
Arial, sans-serif;
-webkit-font-smoothing: antialiased;
overscroll-behavior-y: none;
overflow: hidden; /* прокручивается только .app-main */
}
a { color: var(--accent-2); text-decoration: none; }
h1, h2, h3 { margin: 0 0 8px; line-height: 1.2; }
h1 { font-size: 22px; }
h2 { font-size: 18px; }
h3 { font-size: 15px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
button {
font: inherit;
cursor: pointer;
}
input, select, textarea {
font: inherit;
width: 100%;
background: var(--surface-2);
color: var(--text);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 10px 12px;
outline: none;
}
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 14px;
margin-bottom: 12px;
color: var(--text); /* карточки-кнопки (список партий) — белый текст по умолчанию */
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
background: var(--surface-2);
color: var(--text);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 10px 14px;
font-weight: 600;
}
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-ghost { background: transparent; }
.btn-danger { background: transparent; color: var(--danger); border-color: var(--danger); }
.btn-danger-solid { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn[disabled] { opacity: 0.5; pointer-events: none; }
.btn-block { width: 100%; }
.row { display: flex; align-items: center; gap: 10px; }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.stack { display: flex; flex-direction: column; gap: 10px; }
.muted { color: var(--text-muted); }
.small { font-size: 13px; }
.center { text-align: center; }
.label { font-size: 13px; color: var(--text-muted); margin-bottom: 4px; display: block; }
.field { margin-bottom: 12px; }
.badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 999px;
background: var(--surface-2);
border: 1px solid var(--border);
font-size: 12px;
}
/* Бейдж незавершённой партии («идёт») */
.badge-live { color: var(--accent-2); }
.error-text { color: var(--danger); font-size: 13px; }
/* Формула в справке */
.formula {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 13px;
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 10px 12px;
text-align: center;
color: var(--text);
}
/* Модальное окно подтверждения */
.modal-overlay {
position: fixed;
inset: 0;
z-index: 300;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
}
.modal {
width: 100%;
max-width: 360px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 18px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
}
.modal-title { font-weight: 700; font-size: 16px; margin-bottom: 8px; }
.modal-msg { color: var(--text-muted); font-size: 14px; margin: 0 0 16px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; }
/* «Барабан» рандома фракции — мелькающее название вместо select */
.faction-spin {
display: flex;
align-items: center;
justify-content: center;
padding: 10px 12px;
border: 1px solid var(--accent);
border-radius: var(--radius-sm);
background: var(--surface-2);
color: var(--text);
font-weight: 700;
white-space: nowrap;
overflow: hidden;
animation: faction-roll 0.45s ease-in-out infinite;
}
@keyframes faction-roll {
0% { opacity: 0.5; transform: translateY(-3px); }
50% { opacity: 1; transform: translateY(0); }
100% { opacity: 0.5; transform: translateY(3px); }
}
/* Тумблер-переключатель (вкл/выкл) */
.switch { position: relative; display: inline-block; width: 44px; height: 26px; flex: 0 0 auto; }
.switch input {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.switch-track {
display: inline-flex;
align-items: center;
width: 44px;
height: 26px;
padding: 2px;
border-radius: 999px;
background: var(--surface-2);
border: 1px solid var(--border);
transition: background 0.15s ease, border-color 0.15s ease;
}
.switch-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--text);
transition: transform 0.15s ease;
}
.switch input:checked + .switch-track { background: var(--accent); border-color: var(--accent); }
.switch input:checked + .switch-track .switch-thumb { transform: translateX(18px); background: #fff; }
.switch input:disabled + .switch-track { opacity: 0.5; }
/* ─── Уведомления ─────────────────────────────────────────────────────────── */
/* Зоны верхней панели: слева back/заголовок, по центру название, справа колокольчик */
.top-bar-left { display: flex; align-items: center; min-width: 0; flex: 0 1 auto; }
.top-bar-center {
flex: 1;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 8px;
}
.top-bar-right { display: flex; align-items: center; flex: none; }
.top-bar-home {
background: none;
border: none;
padding: 0;
color: var(--text);
font-weight: 700;
font-size: 17px;
/* На узких экранах длинное название иначе распирает шапку по горизонтали. */
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.notif-bell-wrap { position: relative; }
.notif-bell {
position: relative;
display: grid;
place-items: center;
width: 38px;
height: 38px;
border: none;
background: none;
color: var(--text);
border-radius: 50%;
}
.notif-badge {
position: absolute;
top: 2px;
right: 2px;
min-width: 16px;
height: 16px;
padding: 0 4px;
border-radius: 999px;
background: var(--accent);
color: #fff;
font-size: 10px;
font-weight: 700;
line-height: 16px;
text-align: center;
}
.notif-panel {
position: absolute;
top: calc(100% + 8px);
right: 0;
width: min(320px, 86vw);
max-height: 70dvh;
overflow-y: auto;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
z-index: 50;
}
.notif-panel-head {
padding: 10px 14px;
font-weight: 700;
border-bottom: 1px solid var(--border);
}
.notif-empty { padding: 18px 14px; text-align: center; }
.notif-list { display: flex; flex-direction: column; }
.notif-item {
display: block;
width: 100%;
text-align: left;
padding: 10px 14px;
background: none;
border: none;
border-bottom: 1px solid var(--border);
color: var(--text);
}
.notif-item:last-child { border-bottom: none; }
.notif-item.unread { background: var(--surface-2); }
.notif-item-title { font-weight: 600; }
.notif-item-body { margin-top: 2px; }
.notif-item-time { margin-top: 4px; }
/* Подсказки ников под инпутом (приглашение игрока и т.п.) */
.suggest-wrap { position: relative; flex: 1; min-width: 0; }
.suggest-list {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
background: var(--surface-2); /* как у input/select — выпадашка продолжает поле */
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
z-index: 50;
overflow: hidden;
}
.suggest-row {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 10px;
background: none;
border: none;
border-bottom: 1px solid var(--border);
color: var(--text);
font: inherit;
font-weight: 600;
text-align: left;
cursor: pointer;
touch-action: manipulation; /* тап без задержки двойного тапа */
}
.suggest-row:last-child { border-bottom: none; }
.suggest-row:hover, .suggest-row:active { background: var(--border); }
.suggest-row .nick { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Кнопка-триггер PickerSelect: выглядит как select, раскрывает .suggest-list. */
.picker-trigger {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
min-height: 42px; /* высота обычного input/select — строки формы не «прыгают» */
background: var(--surface-2);
color: var(--text);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 8px 12px;
font: inherit;
text-align: left;
cursor: pointer;
outline: none;
touch-action: manipulation;
}
.picker-trigger:focus { border-color: var(--accent); }
.picker-trigger .nick { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.picker-placeholder { color: var(--muted); }
.picker-chevron { margin-left: auto; flex: none; color: var(--muted); }
/* ─── Редактор мест перетаскиванием (PlaceEditor, завершение партии) ─── */
.rank-list { position: relative; display: flex; flex-direction: column; gap: 8px; }
.rank-block {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 8px 10px;
user-select: none;
-webkit-user-select: none;
}
.rank-row { display: flex; align-items: center; gap: 10px; }
.rank-grip {
touch-action: none; /* хват: drag вместо скролла страницы */
cursor: grab;
color: var(--text-muted);
font-size: 18px;
padding: 6px 4px;
flex: none;
}
.rank-info { flex: 1; min-width: 0; }
.rank-nick { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rank-faction { font-size: 12px; color: var(--text-muted); }
.rank-comment {
display: block;
width: 100%;
margin-top: 8px;
font-size: 13px;
padding: 7px 10px;
border-radius: 8px;
}
.rank-unlink {
flex: none;
width: 30px; height: 30px;
border-radius: 8px;
border: 1px solid var(--border);
background: none;
color: var(--text-muted);
cursor: pointer;
display: flex; align-items: center; justify-content: center;
}
/* Блок ничьей: общая рамка, общий хват и общее место */
.rank-tie {
display: flex;
align-items: center;
gap: 6px;
border: 1px solid var(--accent-2);
border-radius: var(--radius-sm);
padding: 6px;
background: rgba(240, 160, 75, 0.05);
}
.rank-tie-col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.rank-tie-badge {
flex: none;
display: flex; flex-direction: column; align-items: center; gap: 2px;
padding: 0 2px;
}
.rank-tie-badge .tag {
font-size: 10px;
color: var(--accent-2);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.dragging {
position: relative;
z-index: 30;
opacity: 0.95;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6);
}
.merge-target { outline: 2px solid var(--accent-2); outline-offset: 1px; }
.drop-indicator {
position: absolute;
left: 0; right: 0;
height: 3px; border-radius: 2px;
background: var(--accent-2);
display: none;
z-index: 20;
pointer-events: none;
}
.elim-zone {
border: 1px dashed var(--border);
border-radius: var(--radius-sm);
padding: 10px;
margin-top: 12px;
}
.elim-zone.highlight { border-color: var(--accent); background: rgba(224, 83, 61, 0.08); }
.elim-zone .zone-title { font-size: 12px; color: var(--text-muted); }
.elim-zone .rank-block { opacity: 0.6; margin-top: 8px; }
/* Всплывашки сверху экрана в момент прихода */
.toast-top-wrap {
position: fixed;
top: 10px;
left: 50%;
transform: translateX(-50%);
width: min(var(--app-max-w), 94vw);
display: flex;
flex-direction: column;
gap: 8px;
z-index: 200;
pointer-events: none;
}
.toast-top {
pointer-events: auto;
display: block;
width: 100%;
text-align: left;
background: var(--surface-2);
border: 1px solid var(--border);
border-left: 3px solid var(--accent);
color: var(--text);
padding: 10px 14px;
border-radius: var(--radius-sm);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
}
.toast-top-error { border-left-color: var(--danger); }
.toast-top-title { font-weight: 700; }
.toast-top-body { margin-top: 2px; font-size: 13px; color: var(--text-muted); }
/* ─── Объявления администрации (#84) ─── */
/* Окно (вариант E): заголовок — в плашке-шапке на всю ширину модалки. */
.ann-modal { max-height: calc(100dvh - 48px); overflow-y: auto; }
.ann-ribbon {
margin: -18px -18px 14px;
padding: 12px 18px;
background: var(--surface-2);
border-bottom: 1px solid var(--border);
border-radius: var(--radius) var(--radius) 0 0;
}
.ann-ribbon-title { font-weight: 700; font-size: 16px; line-height: 1.3; overflow-wrap: anywhere; }
/* Пометка повторного показа — мелкой строкой под заголовком, красным. */
.ann-updated { margin-top: 2px; font-size: 12px; color: var(--danger); }
.ann-actions { margin-top: 16px; justify-content: space-between; align-items: center; }
.ann-dots { display: flex; gap: 6px; align-items: center; }
.ann-dots i { display: block; width: 6px; height: 6px; border-radius: 50%; background: var(--border); }
.ann-dots i.on { background: var(--accent-2); }
/* Текст объявления — и в окне, и в редакторе админки (там он выглядит так же). */
.ann-body { font-size: 14px; line-height: 1.5; color: var(--text); overflow-wrap: anywhere; }
.ann-body p { margin: 0; }
.ann-body p + p { margin-top: 10px; }
/* Акценты: золотой — цвет ссылок и бейджа «идёт», красный — для предупреждений. */
.ann-body mark { background: none; color: var(--accent-2); font-weight: 600; }
.ann-body mark.red { color: var(--danger); }
/* Редактор (вариант R1) */
.rte-toolbar {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
padding: 8px;
background: var(--surface-2);
border: 1px solid var(--border);
border-bottom: none;
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}
.rte-tool {
min-width: 32px;
height: 32px;
padding: 0 8px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--surface);
color: var(--text);
font-size: 14px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.rte-tool:hover { border-color: var(--accent-2); }
.rte-gold { color: var(--accent-2); font-weight: 700; }
.rte-red { color: var(--danger); font-weight: 700; }
.rte-sep { width: 1px; height: 22px; background: var(--border); }
.rte-editor {
min-height: 130px;
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: 0 0 var(--radius-sm) var(--radius-sm);
padding: 10px 12px;
outline: none;
}
.rte-editor:focus { border-color: var(--accent); }
.rte-editor:empty::before { content: attr(data-placeholder); color: var(--text-muted); }
.rte-emoji {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 6px;
padding: 8px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
}
.rte-emoji button {
width: 34px;
height: 34px;
border: none;
background: none;
font-size: 18px;
border-radius: 8px;
}
.rte-emoji button:hover { background: var(--surface-2); }
.rte-counter { font-size: 12px; color: var(--text-muted); margin-top: 4px; text-align: right; }
.rte-counter.over { color: var(--danger); }
/* Выделение текста в форме объявления — полупрозрачный --accent-2 вместо системного синего. */
.ann-form ::selection { background: rgba(240, 160, 75, 0.35); }