fix(theme): readable text on any operator palette + admin consistency pass

Контраст-аудит (WCAG-обход всех текстовых узлов, 4 палитры × юзер+админ
страницы) показал системные провалы читаемости: вторичный текст 1.5-2.5
на светлой/кастомных темах, статусные цвета 1.3-1.6, белый текст на
светлых акцентах 1.9.

Корневые фиксы:
- useThemeColors: контраст-кламп производных серых токенов — dark-400/
  champagne-600 (вторичный текст) держат >=5.0 к поверхности, dark-500/
  champagne-500 (подсказки) >=3.8; палитры с достаточным контрастом
  рендерятся байт-в-байт как раньше
- --color-on-accent/-success/-warning/-error: чёрный или белый текст
  по фактической яркости цвета оператора; text-white на accent-заливках
  заменён на text-on-accent (135 мест + variant примитива Button +
  .btn-primary)
- .light: статусные текстовые оттенки *-300/400 ремапятся на тёмные
  *-700 (жёлтая «Админка» 1.26 -> 3.8+, зелёные суммы 1.4 -> 3.4+,
  бейджи состояний)

Замер после: операторская тёмная палитра — 0 нарушений (всё >=4.5),
худшие точки остальных подняты с 1.26-1.6 до 3.2+.

Консистентность:
- заголовки админ-страниц приведены к text-xl font-bold text-dark-100
  (были 18/20/24/30px и 600/700 вперемешку)
- «Создать FAQ»: warning-500/80+белый -> warning-500+text-on-warning
- formatTraffic: единицы из i18n (убран микс «GB»/«ГБ» на одном экране)
- categoryCanvas -> categoryCANVAS в локалях (битый ключ на настройках)
- «Управление сквадами» -> «Управление локациями» (юзерский экран)
- мин-макс сумм на выборе метода: dark-600 -> dark-400
- неактивные пункты нижнего нава: dark-500 -> dark-400
This commit is contained in:
c0mrade
2026-06-11 15:49:17 +03:00
parent 47119c4c1f
commit 7f68e2cd5e
91 changed files with 287 additions and 176 deletions

View File

@@ -156,7 +156,7 @@ function CodeOnlySuccessState({
'flex w-full items-center justify-center gap-2 rounded-xl px-4 py-3.5 text-sm font-bold transition-all duration-200 active:scale-[0.98]',
copied
? 'bg-success-500/20 text-success-400'
: 'bg-accent-500 text-white shadow-lg shadow-accent-500/25 hover:bg-accent-400',
: 'bg-accent-500 text-on-accent shadow-lg shadow-accent-500/25 hover:bg-accent-400',
)}
>
{copied ? (
@@ -236,7 +236,7 @@ function DeliveredState({
<button
type="button"
onClick={() => navigate('/')}
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
>
{t('gift.backToDashboard', 'Back to dashboard')}
</button>
@@ -303,7 +303,7 @@ function PendingActivationState({
<button
type="button"
onClick={() => navigate('/')}
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
>
{t('gift.backToDashboard', 'Back to dashboard')}
</button>
@@ -335,7 +335,7 @@ function FailedState() {
<button
type="button"
onClick={() => navigate('/gift')}
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
>
{t('gift.tryAgain', 'Try again')}
</button>
@@ -372,7 +372,7 @@ function PollErrorState() {
<button
type="button"
onClick={() => navigate('/')}
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
>
{t('gift.backToDashboard', 'Back to dashboard')}
</button>
@@ -406,7 +406,7 @@ function PollTimedOutState({ onRetry }: { onRetry: () => void }) {
<button
type="button"
onClick={onRetry}
className="rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
className="rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
>
{t('gift.retry', 'Retry')}
</button>
@@ -436,7 +436,7 @@ function NoTokenState() {
<button
type="button"
onClick={() => navigate('/gift')}
className="rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
className="rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
>
{t('gift.backToGift', 'Go back')}
</button>