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

@@ -74,7 +74,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
</p>
<button
onClick={() => window.location.reload()}
className="rounded-xl bg-accent-500 px-6 py-3 font-medium text-white transition-colors hover:bg-accent-600"
className="rounded-xl bg-accent-500 px-6 py-3 font-medium text-on-accent transition-colors hover:bg-accent-600"
>
Reload page
</button>
@@ -110,7 +110,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
</p>
<button
onClick={() => window.location.reload()}
className="rounded-xl bg-accent-500 px-6 py-3 font-medium text-white transition-colors hover:bg-accent-600"
className="rounded-xl bg-accent-500 px-6 py-3 font-medium text-on-accent transition-colors hover:bg-accent-600"
>
{isChunk ? 'Reload' : 'Try again'}
</button>

View File

@@ -281,7 +281,7 @@ export default function SuccessNotificationModal() {
{isSubscription && (
<button
onClick={handleGoToSubscription}
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 py-3.5 font-bold text-white shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600"
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 py-3.5 font-bold text-on-accent shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600"
>
<RocketIcon className="h-8 w-8" />
<span>{t('successNotification.goToSubscription', 'Go to Subscription')}</span>
@@ -301,7 +301,7 @@ export default function SuccessNotificationModal() {
{isDevicesPurchased && (
<button
onClick={handleGoToSubscription}
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 py-3.5 font-bold text-white shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600"
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 py-3.5 font-bold text-on-accent shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600"
>
<DevicesIcon className="h-8 w-8" />
<span>{t('successNotification.goToSubscription', 'Go to Subscription')}</span>

View File

@@ -114,7 +114,7 @@ export function AnalyticsTab() {
<button
onClick={handleSaveYandex}
disabled={updateMutation.isPending}
className="rounded-xl bg-accent-500 px-4 py-2.5 text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
className="rounded-xl bg-accent-500 px-4 py-2.5 text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
>
<CheckIcon />
</button>
@@ -253,7 +253,7 @@ export function AnalyticsTab() {
<button
onClick={handleSaveGoogleId}
disabled={updateMutation.isPending}
className="rounded-xl bg-accent-500 px-4 py-2.5 text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
className="rounded-xl bg-accent-500 px-4 py-2.5 text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
>
<CheckIcon />
</button>
@@ -307,7 +307,7 @@ export function AnalyticsTab() {
<button
onClick={handleSaveGoogleLabel}
disabled={updateMutation.isPending}
className="rounded-xl bg-accent-500 px-4 py-2.5 text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
className="rounded-xl bg-accent-500 px-4 py-2.5 text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
>
<CheckIcon />
</button>

View File

@@ -68,7 +68,7 @@ export function BackgroundEditor() {
'w-full rounded-xl py-3 text-sm font-medium transition-colors',
saveStatus === 'saved'
? 'bg-success-500/20 text-success-400'
: 'bg-accent-500 text-white hover:bg-accent-600 disabled:opacity-50',
: 'bg-accent-500 text-on-accent hover:bg-accent-600 disabled:opacity-50',
)}
>
{saveStatus === 'saving'

View File

@@ -168,7 +168,7 @@ export function BrandingTab({ accentColor = '#3b82f6' }: BrandingTabProps) {
<button
onClick={() => updateBrandingMutation.mutate(newName)}
disabled={updateBrandingMutation.isPending}
className="rounded-xl bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
className="rounded-xl bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
>
<CheckIcon />
</button>

View File

@@ -228,7 +228,7 @@ export function ButtonsTab() {
? 'bg-success-500 text-white'
: cfg.style === 'danger'
? 'bg-error-500 text-white'
: 'bg-accent-500 text-white'
: 'bg-accent-500 text-on-accent'
}`}
>
{t(`admin.buttons.styles.${cfg.style}`)}
@@ -321,7 +321,7 @@ export function ButtonsTab() {
<button
onClick={handleSave}
disabled={updateMutation.isPending}
className="rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
className="rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
>
{updateMutation.isPending ? t('common.saving') : t('common.save')}
</button>

View File

@@ -346,7 +346,7 @@ export function ColoredItemCombobox({
type="button"
onClick={handleCreate}
disabled={isCreating}
className="flex min-h-[44px] w-full items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
className="flex min-h-[44px] w-full items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2.5 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
>
{isCreating ? (
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" />

View File

@@ -72,7 +72,7 @@ function MaxPerRowSelector({ value, onChange }: MaxPerRowSelectorProps) {
onClick={() => onChange(n)}
className={`flex h-7 w-7 items-center justify-center rounded-lg text-xs font-semibold transition-all ${
value === n
? 'bg-accent-500 text-white'
? 'bg-accent-500 text-on-accent'
: 'bg-dark-700/50 text-dark-400 hover:bg-dark-600 hover:text-dark-300'
}`}
>
@@ -788,7 +788,7 @@ export function MenuEditorTab() {
<button
onClick={handleSave}
disabled={updateMutation.isPending}
className="rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
className="rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
>
{updateMutation.isPending ? t('common.saving') : t('common.save')}
</button>

View File

@@ -122,7 +122,7 @@ export function SettingInput({ setting, onUpdate, disabled }: SettingInputProps)
</button>
<button
onClick={handleSave}
className="flex items-center gap-1.5 rounded-lg bg-accent-500 px-3 py-1.5 text-sm text-white transition-colors hover:bg-accent-600"
className="flex items-center gap-1.5 rounded-lg bg-accent-500 px-3 py-1.5 text-sm text-on-accent transition-colors hover:bg-accent-600"
>
<CheckIcon />
{t('admin.settings.saveButton')}
@@ -159,7 +159,7 @@ export function SettingInput({ setting, onUpdate, disabled }: SettingInputProps)
/>
<button
onClick={handleSave}
className="rounded-lg bg-accent-500 p-2 text-white transition-colors hover:bg-accent-600"
className="rounded-lg bg-accent-500 p-2 text-on-accent transition-colors hover:bg-accent-600"
title={t('admin.settings.saveHint')}
>
<CheckIcon />

View File

@@ -215,7 +215,7 @@ export function SortableSelectedMethodCard({
className={cn(
'flex h-3.5 w-3.5 items-center justify-center rounded',
enabled
? 'bg-accent-500 text-white'
? 'bg-accent-500 text-on-accent'
: 'border border-dark-600 bg-dark-700',
)}
>

View File

@@ -428,7 +428,7 @@ export function ThemeTab() {
<button
onClick={() => updateColorsMutation.mutate(draftColors)}
disabled={updateColorsMutation.isPending}
className="rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
className="rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
>
{updateColorsMutation.isPending
? t('common.saving', t('common.save'))

View File

@@ -621,7 +621,7 @@ export function ActionModal({
<button
onClick={onClose}
className="w-full rounded-xl bg-accent-500 px-4 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-accent-600"
className="w-full rounded-xl bg-accent-500 px-4 py-2.5 text-sm font-semibold text-on-accent transition-colors hover:bg-accent-600"
>
{t('common.close')}
</button>
@@ -647,7 +647,7 @@ export function ActionModal({
<button
onClick={handleSubmit}
disabled={isConfirmDisabled}
className="flex min-h-[44px] flex-1 items-center justify-center gap-2 rounded-xl bg-accent-500 px-4 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
className="flex min-h-[44px] flex-1 items-center justify-center gap-2 rounded-xl bg-accent-500 px-4 py-2.5 text-sm font-semibold text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
>
{t('admin.bulkActions.confirm')}
</button>

View File

@@ -208,7 +208,7 @@ export function FloatingActionBar({
<div className="relative ml-auto">
<button
onClick={() => setOpen(!open)}
className="flex items-center gap-2 rounded-xl bg-accent-500 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-accent-600"
className="flex items-center gap-2 rounded-xl bg-accent-500 px-4 py-2 text-sm font-semibold text-on-accent transition-colors hover:bg-accent-600"
>
{t('common.actions')}
<ChevronDownIcon />

View File

@@ -52,7 +52,7 @@ export function CountryFilter({
>
<GlobeIcon className="h-4 w-4" />
{activeCount > 0 && (
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-white">
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-on-accent">
{activeCount}
</span>
)}

View File

@@ -56,7 +56,7 @@ export function NodeFilter({
<ServerIcon className="h-4 w-4" />
{t('admin.trafficUsage.nodes')}
{activeCount > 0 && (
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-white">
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-on-accent">
{activeCount}
</span>
)}

View File

@@ -79,7 +79,7 @@ export function PeriodSelector({
onClick={() => onChange(p)}
className={`rounded-lg px-2.5 py-1 text-xs font-medium transition-colors ${
value === p
? 'bg-accent-500 text-white'
? 'bg-accent-500 text-on-accent'
: 'bg-dark-800 text-dark-400 hover:bg-dark-700 hover:text-dark-200'
}`}
>

View File

@@ -67,7 +67,7 @@ export function StatusFilter({
<StatusIcon className="h-4 w-4" />
{t('admin.trafficUsage.status')}
{activeCount > 0 && (
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-white">
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-on-accent">
{activeCount}
</span>
)}

View File

@@ -54,7 +54,7 @@ export function TariffFilter({
<FilterIcon className="h-4 w-4" />
{t('admin.trafficUsage.tariff')}
{activeCount > 0 && (
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-white">
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-on-accent">
{activeCount}
</span>
)}

View File

@@ -378,7 +378,7 @@ export function InfoTab(props: InfoTabProps) {
<button
onClick={onUpdateReferralCommission}
disabled={actionLoading}
className="w-full rounded-lg bg-accent-500 px-2 py-1 text-xs text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
className="w-full rounded-lg bg-accent-500 px-2 py-1 text-xs text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
>
{actionLoading ? t('common.loading') : t('common.save')}
</button>
@@ -475,7 +475,7 @@ export function InfoTab(props: InfoTabProps) {
disabled={actionLoading}
className={`rounded-lg px-3 py-2 text-sm font-medium transition-all disabled:opacity-50 ${
confirmingAction === 'resetTrial'
? 'bg-accent-500 text-white'
? 'bg-accent-500 text-on-accent'
: 'bg-accent-500/15 text-accent-400 hover:bg-accent-500/25'
}`}
>

View File

@@ -464,7 +464,7 @@ export function SubscriptionTab(props: SubscriptionTabProps) {
<button
onClick={() => selectedTrafficGb && onAddTraffic(Number(selectedTrafficGb))}
disabled={actionLoading || !selectedTrafficGb}
className="shrink-0 rounded-lg bg-accent-500 px-4 py-2 text-sm text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
className="shrink-0 rounded-lg bg-accent-500 px-4 py-2 text-sm text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
>
{t('admin.users.detail.subscription.addButton')}
</button>

View File

@@ -348,7 +348,7 @@ function ChatView({
onClick={onReply}
disabled={!replyText.trim() || replySending}
aria-label={t('admin.tickets.sendReply', 'Send reply')}
className="min-h-[44px] min-w-[44px] shrink-0 self-end rounded-lg bg-accent-500 px-4 py-2 text-sm text-white transition-colors hover:bg-accent-600 disabled:opacity-50 sm:min-h-0 sm:min-w-0"
className="min-h-[44px] min-w-[44px] shrink-0 self-end rounded-lg bg-accent-500 px-4 py-2 text-sm text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50 sm:min-h-0 sm:min-w-0"
>
{replySending ? (
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />

View File

@@ -57,7 +57,7 @@ export default function PendingGiftCard({ gifts, className }: PendingGiftCardPro
{/* Activate button */}
<Link
to={`/gift?tab=activate&code=${gift.token}`}
className="shrink-0 rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-400"
className="shrink-0 rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
>
{t('gift.pending.activate')}
</Link>

View File

@@ -80,7 +80,7 @@ export function MobileBottomNav({
onClick={handleNavClick}
className={cn(
'relative flex min-w-[56px] flex-1 shrink-0 flex-col items-center justify-center rounded-2xl px-3 py-2.5 transition-all duration-200',
isActive(item.path) ? 'text-accent-400' : 'text-dark-500 hover:text-dark-300',
isActive(item.path) ? 'text-accent-400' : 'text-dark-400 hover:text-dark-200',
)}
>
{isActive(item.path) && (

View File

@@ -13,7 +13,7 @@ export const buttonVariants = cva(
variants: {
variant: {
primary: [
'bg-accent-500 text-white',
'bg-accent-500 text-on-accent',
'hover:bg-accent-600',
'active:bg-accent-700',
'shadow-linear-sm hover:shadow-linear',

View File

@@ -107,7 +107,7 @@ export function TariffPickerGrid({
</p>
<button
onClick={() => navigate('/subscriptions')}
className="rounded-xl bg-accent-500 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-600"
className="rounded-xl bg-accent-500 px-6 py-2.5 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600"
>
{t('subscription.backToList', 'Мои подписки')}
</button>