fix: заменить хардкодный зелёный (#3EDBB0) на акцентный цвет из темы

Все вхождения #3EDBB0, #2BC49A и rgba(62,219,176,...) заменены на
CSS-переменные --color-accent-400/500 из системы тем.

Затронутые компоненты:
- TrialOfferCard: кнопка, бордер, свечение, иконка
- Subscription: лейблы, иконки, кнопки паузы/копирования
- SubscriptionCardActive: бордер карточки, бейдж статуса
- SubscriptionPurchase: градиент триал-блока
- PurchaseCTAButton: градиент кнопки, иконка
- TrafficProgressBar: цвета прогресс-бара
- StatsGrid: убрана зависимость от useTrafficZone, используется accent напрямую
- trafficZone.ts: добавлены mainVar/mainVarRaw для CSS-переменных
- tailwind.config.js: trialGlow keyframes на accent цвет
This commit is contained in:
Fringg
2026-03-05 10:56:45 +03:00
parent 262303d623
commit d526d095de
10 changed files with 114 additions and 83 deletions

View File

@@ -1,14 +1,11 @@
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router';
import type { Subscription } from '../../types';
import { useCurrency } from '../../hooks/useCurrency';
import { useTheme } from '../../hooks/useTheme';
import { useTrafficZone } from '../../hooks/useTrafficZone';
import { getGlassColors } from '../../utils/glassTheme';
interface StatsGridProps {
balanceRubles: number;
subscription: Subscription | null;
referralCount: number;
earningsRubles: number;
refLoading: boolean;
@@ -35,7 +32,6 @@ const ChevronIcon = ({ color }: { color: string }) => (
export default function StatsGrid({
balanceRubles,
subscription,
referralCount,
earningsRubles,
refLoading,
@@ -45,13 +41,14 @@ export default function StatsGrid({
const { isDark } = useTheme();
const g = getGlassColors(isDark);
const zone = useTrafficZone(subscription?.traffic_used_percent ?? 0);
const accentColor = 'rgb(var(--color-accent-400))';
const accentBg = 'rgba(var(--color-accent-400), 0.07)';
const cards = [
{
label: t('dashboard.stats.balance'),
value: `${formatAmount(balanceRubles)} ${currencySymbol}`,
valueColor: zone.mainHex,
valueColor: accentColor,
to: '/balance',
icon: (color: string) => (
<svg
@@ -70,8 +67,8 @@ export default function StatsGrid({
<path d="M6 14h.01M10 14h.01" />
</svg>
),
iconBg: `${zone.mainHex}12`,
iconColor: zone.mainHex,
iconBg: accentBg,
iconColor: accentColor,
loading: false,
onboarding: 'balance',
},
@@ -80,7 +77,7 @@ export default function StatsGrid({
value: `${referralCount}`,
valueColor: g.text,
subtitle: `+${formatAmount(earningsRubles)} ${currencySymbol}`,
subtitleColor: zone.mainHex,
subtitleColor: accentColor,
to: '/referral',
icon: (color: string) => (
<svg

View File

@@ -71,12 +71,14 @@ export default function SubscriptionCardActive({
style={{
background: g.cardBg,
border: subscription.is_trial
? '1px solid rgba(62,219,176,0.15)'
? '1px solid rgba(var(--color-accent-400), 0.15)'
: isDark
? `1px solid ${g.cardBorder}`
: `1px solid ${zone.mainHex}25`,
: `1px solid rgba(${zone.mainVarRaw}, 0.14)`,
padding: '28px 28px 24px',
boxShadow: isDark ? g.shadow : `0 2px 16px ${zone.mainHex}12, 0 0 0 1px ${zone.mainHex}08`,
boxShadow: isDark
? g.shadow
: `0 2px 16px rgba(${zone.mainVarRaw}, 0.07), 0 0 0 1px rgba(${zone.mainVarRaw}, 0.03)`,
}}
>
{/* Trial shimmer border */}
@@ -96,7 +98,7 @@ export default function SubscriptionCardActive({
width: 200,
height: 200,
borderRadius: '50%',
background: `radial-gradient(circle, ${zone.mainHex}${g.glowAlpha} 0%, transparent 70%)`,
background: `radial-gradient(circle, rgba(${zone.mainVarRaw}, ${isDark ? 0.08 : 0.03}) 0%, transparent 70%)`,
transition: 'background 0.8s ease',
}}
aria-hidden="true"
@@ -110,15 +112,15 @@ export default function SubscriptionCardActive({
<div
className="h-2 w-2 rounded-full"
style={{
background: zone.mainHex,
boxShadow: `0 0 8px ${zone.mainHex}80`,
background: zone.mainVar,
boxShadow: `0 0 8px rgba(${zone.mainVarRaw}, 0.5)`,
transition: 'all 0.6s ease',
}}
aria-hidden="true"
/>
<span
className="font-mono text-[11px] font-semibold uppercase tracking-widest"
style={{ color: zone.mainHex, transition: 'color 0.6s ease' }}
style={{ color: zone.mainVar, transition: 'color 0.6s ease' }}
>
{isUnlimited ? t('dashboard.unlimited') : t(zone.labelKey)}
</span>
@@ -127,9 +129,9 @@ export default function SubscriptionCardActive({
className="inline-flex animate-trial-glow items-center gap-1 rounded-md px-2 py-0.5 font-mono text-[9px] font-bold uppercase tracking-widest"
style={{
background:
'linear-gradient(135deg, rgba(62,219,176,0.15), rgba(62,219,176,0.06))',
border: '1px solid rgba(62,219,176,0.2)',
color: '#3EDBB0',
'linear-gradient(135deg, rgba(var(--color-accent-400), 0.15), rgba(var(--color-accent-400), 0.06))',
border: '1px solid rgba(var(--color-accent-400), 0.2)',
color: 'rgb(var(--color-accent-400))',
}}
>
<svg
@@ -164,7 +166,7 @@ export default function SubscriptionCardActive({
<>
<div
className="font-display text-[28px] font-extrabold leading-none tracking-tight"
style={{ color: zone.mainHex }}
style={{ color: zone.mainVar }}
>
&#8734;
</div>
@@ -209,14 +211,14 @@ export default function SubscriptionCardActive({
{/* Monitor icon */}
<div
className="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] transition-colors duration-500"
style={{ background: `${zone.mainHex}12` }}
style={{ background: `rgba(${zone.mainVarRaw}, 0.07)` }}
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke={zone.mainHex}
stroke={zone.mainVar}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
@@ -249,8 +251,9 @@ export default function SubscriptionCardActive({
key={i}
className="h-[7px] w-[7px] rounded-full transition-all duration-300"
style={{
background: i < connectedDevices ? zone.mainHex : g.textGhost,
boxShadow: i < connectedDevices ? `0 0 6px ${zone.mainHex}50` : 'none',
background: i < connectedDevices ? zone.mainVar : g.textGhost,
boxShadow:
i < connectedDevices ? `0 0 6px rgba(${zone.mainVarRaw}, 0.31)` : 'none',
}}
/>
))}
@@ -265,8 +268,8 @@ export default function SubscriptionCardActive({
className="h-full rounded-full transition-all duration-500"
style={{
width: `${Math.round((connectedDevices / subscription.device_limit) * 100)}%`,
background: zone.mainHex,
boxShadow: `0 0 8px ${zone.mainHex}40`,
background: zone.mainVar,
boxShadow: `0 0 8px rgba(${zone.mainVarRaw}, 0.25)`,
minWidth: connectedDevices > 0 ? '4px' : '0px',
}}
/>
@@ -283,13 +286,13 @@ export default function SubscriptionCardActive({
to="/subscription"
className="flex-1 rounded-[14px] p-3.5 transition-all duration-500"
style={{
background: `linear-gradient(135deg, ${zone.mainHex}12, ${zone.mainHex}06)`,
border: `1px solid ${zone.mainHex}18`,
background: `linear-gradient(135deg, rgba(${zone.mainVarRaw}, 0.07), rgba(${zone.mainVarRaw}, 0.02))`,
border: `1px solid rgba(${zone.mainVarRaw}, 0.09)`,
}}
>
<div
className="mb-1.5 text-[10px] font-semibold uppercase tracking-wider opacity-70 transition-colors duration-500"
style={{ color: zone.mainHex }}
style={{ color: zone.mainVar }}
>
{t('dashboard.tariff')}
</div>
@@ -306,14 +309,17 @@ export default function SubscriptionCardActive({
className="flex-1 rounded-[14px] p-3.5 transition-colors duration-300"
style={{
background: g.innerBg,
border: daysLeft <= 3 ? '1px solid rgba(255,184,0,0.2)' : `1px solid ${g.innerBorder}`,
border:
daysLeft <= 3
? '1px solid rgba(var(--color-warning-400), 0.2)'
: `1px solid ${g.innerBorder}`,
}}
>
<div className="mb-1 flex items-center gap-1.5 text-[10px] font-medium uppercase tracking-wider text-dark-50/35">
<div
className="flex h-6 w-6 items-center justify-center rounded-[7px] transition-colors duration-300"
style={{
background: daysLeft <= 3 ? 'rgba(255,184,0,0.1)' : g.hoverBg,
background: daysLeft <= 3 ? 'rgba(var(--color-warning-400), 0.1)' : g.hoverBg,
}}
>
<svg
@@ -321,7 +327,7 @@ export default function SubscriptionCardActive({
height="13"
viewBox="0 0 24 24"
fill="none"
stroke={daysLeft <= 3 ? '#FFB800' : g.textSecondary}
stroke={daysLeft <= 3 ? 'rgb(var(--color-warning-400))' : g.textSecondary}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
@@ -336,7 +342,7 @@ export default function SubscriptionCardActive({
<div className="flex items-baseline gap-1">
<span
className="text-[22px] font-bold tracking-tight transition-colors duration-300"
style={{ color: daysLeft <= 3 ? '#FFB800' : g.text }}
style={{ color: daysLeft <= 3 ? 'rgb(var(--color-warning-400))' : g.text }}
>
{daysLeft}
</span>
@@ -382,7 +388,7 @@ export default function SubscriptionCardActive({
{t('dashboard.maxUsage', { amount: formatTraffic(Math.max(...dailyUsage)) })}
</span>
</div>
<Sparkline data={dailyUsage} width={440} height={44} color={zone.mainHex} />
<Sparkline data={dailyUsage} width={440} height={44} color={zone.mainVar} />
</div>
)}
</div>

View File

@@ -26,19 +26,23 @@ export default function TrafficProgressBar({
const { isDark } = useTheme();
const g = getGlassColors(isDark);
const zone = useTrafficZone(percent);
// Gradient always starts from the accent color (normal zone)
const startHex = zone.colors.accent || '#3b82f6';
const startColor = 'rgb(var(--color-accent-400))';
const clampedPercent = Math.min(percent, 100);
const barHeight = compact ? 8 : 14;
// Multi-segment gradient matching prototype
// Warning/danger hex colors are intentional — they represent fixed threshold tints,
// not theme-dynamic values, and must remain distinct from the zone-based mainVar.
const fillGradient = useMemo(() => {
if (percent < 50) return `linear-gradient(90deg, ${startHex}, ${zone.mainHex})`;
if (percent < 75) return `linear-gradient(90deg, ${startHex}, #FFB800, ${zone.mainHex})`;
if (percent < 50) return `linear-gradient(90deg, ${startColor}, ${zone.mainVar})`;
if (percent < 75)
return `linear-gradient(90deg, ${startColor}, rgb(var(--color-warning-400)), ${zone.mainVar})`;
if (percent < 90)
return `linear-gradient(90deg, ${startHex}, #FFB800, #FF6B35, ${zone.mainHex})`;
return `linear-gradient(90deg, ${startHex}, #FFB800, #FF6B35, #FF3B5C)`;
}, [percent, zone.mainHex, startHex]);
return `linear-gradient(90deg, ${startColor}, rgb(var(--color-warning-400)), rgb(var(--color-warning-300)), ${zone.mainVar})`;
return `linear-gradient(90deg, ${startColor}, rgb(var(--color-warning-400)), rgb(var(--color-warning-300)), rgb(var(--color-error-400)))`;
}, [percent, zone.mainVar, startColor]);
if (isUnlimited) {
return (
@@ -50,13 +54,13 @@ export default function TrafficProgressBar({
height: barHeight,
borderRadius: 10,
background: g.trackBg,
border: `1px solid ${zone.mainHex}20`,
border: `1px solid rgba(${zone.mainVarRaw}, 0.12)`,
}}
>
<div
className="absolute inset-0 animate-unlimited-flow"
style={{
background: `linear-gradient(90deg, ${zone.mainHex}50, ${zone.mainHex}, ${zone.mainHex}50)`,
background: `linear-gradient(90deg, rgba(${zone.mainVarRaw}, 0.31), ${zone.mainVar}, rgba(${zone.mainVarRaw}, 0.31))`,
backgroundSize: '200% 100%',
}}
/>
@@ -85,13 +89,13 @@ export default function TrafficProgressBar({
<div className="mt-2 flex items-center justify-between px-0.5">
<span
className="flex items-center gap-1.5 text-[11px] font-semibold"
style={{ color: zone.mainHex }}
style={{ color: zone.mainVar }}
>
<span
className="inline-block h-1.5 w-1.5 animate-unlimited-pulse rounded-full"
style={{
background: zone.mainHex,
boxShadow: `0 0 8px ${zone.mainHex}`,
background: zone.mainVar,
boxShadow: `0 0 8px ${zone.mainVar}`,
}}
aria-hidden="true"
/>
@@ -127,9 +131,9 @@ export default function TrafficProgressBar({
{/* Warning zone tint backgrounds */}
<div className="absolute inset-0 flex" aria-hidden="true">
<div style={{ flex: '50 0 0', background: 'transparent' }} />
<div style={{ flex: '25 0 0', background: 'rgba(255,184,0,0.03)' }} />
<div style={{ flex: '15 0 0', background: 'rgba(255,107,53,0.04)' }} />
<div style={{ flex: '10 0 0', background: 'rgba(255,59,92,0.05)' }} />
<div style={{ flex: '25 0 0', background: 'rgba(var(--color-warning-400), 0.03)' }} />
<div style={{ flex: '15 0 0', background: 'rgba(var(--color-warning-300), 0.04)' }} />
<div style={{ flex: '10 0 0', background: 'rgba(var(--color-error-400), 0.05)' }} />
</div>
{/* Fill bar */}
@@ -194,7 +198,7 @@ export default function TrafficProgressBar({
left: `calc(${clampedPercent}% - 8px)`,
width: 16,
borderRadius: '50%',
background: `radial-gradient(circle, ${zone.mainHex}60, transparent)`,
background: `radial-gradient(circle, rgba(${zone.mainVarRaw}, 0.38), transparent)`,
filter: 'blur(4px)',
transition: 'left 1.2s cubic-bezier(0.16, 1, 0.3, 1)',
}}

View File

@@ -36,12 +36,12 @@ export default function TrialOfferCard({
border: isDark
? `1px solid ${g.cardBorder}`
: isFree
? '1px solid rgba(62,219,176,0.2)'
? '1px solid rgba(var(--color-accent-400), 0.2)'
: '1px solid rgba(255,184,0,0.2)',
boxShadow: isDark
? g.shadow
: isFree
? '0 2px 16px rgba(62,219,176,0.12), 0 0 0 1px rgba(62,219,176,0.06)'
? '0 2px 16px rgba(var(--color-accent-400), 0.12), 0 0 0 1px rgba(var(--color-accent-400), 0.06)'
: '0 2px 16px rgba(255,184,0,0.12), 0 0 0 1px rgba(255,184,0,0.06)',
padding: '32px 28px 28px',
}}
@@ -55,7 +55,7 @@ export default function TrialOfferCard({
height: 300,
borderRadius: '50%',
background: isFree
? 'radial-gradient(circle, rgba(62,219,176,0.08) 0%, transparent 70%)'
? 'radial-gradient(circle, rgba(var(--color-accent-400), 0.08) 0%, transparent 70%)'
: 'radial-gradient(circle, rgba(255,184,0,0.07) 0%, transparent 70%)',
transition: 'background 0.5s ease',
}}
@@ -82,12 +82,14 @@ export default function TrialOfferCard({
style={{
background: isDark
? isFree
? 'linear-gradient(135deg, #1a3a30, #142824)'
? 'linear-gradient(135deg, rgba(var(--color-accent-900), 0.5), rgba(var(--color-accent-950), 0.6))'
: 'linear-gradient(135deg, #3a3020, #282418)'
: isFree
? 'linear-gradient(135deg, rgba(62,219,176,0.15), rgba(62,219,176,0.08))'
? 'linear-gradient(135deg, rgba(var(--color-accent-400), 0.15), rgba(var(--color-accent-400), 0.08))'
: 'linear-gradient(135deg, rgba(255,184,0,0.15), rgba(255,184,0,0.08))',
border: isFree ? '1px solid rgba(62,219,176,0.25)' : '1px solid rgba(255,184,0,0.25)',
border: isFree
? '1px solid rgba(var(--color-accent-400), 0.25)'
: '1px solid rgba(255,184,0,0.25)',
transition: 'all 0.5s ease',
}}
>
@@ -97,7 +99,7 @@ export default function TrialOfferCard({
height="26"
viewBox="0 0 24 24"
fill="none"
stroke="#3EDBB0"
stroke="rgb(var(--color-accent-400))"
strokeWidth="1.5"
aria-hidden="true"
>
@@ -128,7 +130,9 @@ export default function TrialOfferCard({
<div
className="absolute inset-[-1px] animate-trial-glow rounded-2xl"
style={{
boxShadow: isFree ? '0 0 20px rgba(62,219,176,0.15)' : '0 0 20px rgba(255,184,0,0.12)',
boxShadow: isFree
? '0 0 20px rgba(var(--color-accent-400), 0.15)'
: '0 0 20px rgba(255,184,0,0.12)',
}}
aria-hidden="true"
/>
@@ -250,14 +254,15 @@ export default function TrialOfferCard({
isDark
? {
background:
'linear-gradient(135deg, rgba(62,219,176,0.12) 0%, rgba(62,219,176,0.04) 100%)',
border: '1px solid rgba(62,219,176,0.25)',
'linear-gradient(135deg, rgba(var(--color-accent-400), 0.12) 0%, rgba(var(--color-accent-400), 0.04) 100%)',
border: '1px solid rgba(var(--color-accent-400), 0.25)',
color: '#fff',
}
: {
background: 'linear-gradient(135deg, #3EDBB0, #2BC49A)',
background:
'linear-gradient(135deg, rgb(var(--color-accent-400)), rgb(var(--color-accent-500)))',
color: '#0a2a1e',
boxShadow: '0 4px 20px rgba(62,219,176,0.25)',
boxShadow: '0 4px 20px rgba(var(--color-accent-400), 0.25)',
}
}
>