mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
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:
@@ -1,14 +1,11 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import type { Subscription } from '../../types';
|
|
||||||
import { useCurrency } from '../../hooks/useCurrency';
|
import { useCurrency } from '../../hooks/useCurrency';
|
||||||
import { useTheme } from '../../hooks/useTheme';
|
import { useTheme } from '../../hooks/useTheme';
|
||||||
import { useTrafficZone } from '../../hooks/useTrafficZone';
|
|
||||||
import { getGlassColors } from '../../utils/glassTheme';
|
import { getGlassColors } from '../../utils/glassTheme';
|
||||||
|
|
||||||
interface StatsGridProps {
|
interface StatsGridProps {
|
||||||
balanceRubles: number;
|
balanceRubles: number;
|
||||||
subscription: Subscription | null;
|
|
||||||
referralCount: number;
|
referralCount: number;
|
||||||
earningsRubles: number;
|
earningsRubles: number;
|
||||||
refLoading: boolean;
|
refLoading: boolean;
|
||||||
@@ -35,7 +32,6 @@ const ChevronIcon = ({ color }: { color: string }) => (
|
|||||||
|
|
||||||
export default function StatsGrid({
|
export default function StatsGrid({
|
||||||
balanceRubles,
|
balanceRubles,
|
||||||
subscription,
|
|
||||||
referralCount,
|
referralCount,
|
||||||
earningsRubles,
|
earningsRubles,
|
||||||
refLoading,
|
refLoading,
|
||||||
@@ -45,13 +41,14 @@ export default function StatsGrid({
|
|||||||
const { isDark } = useTheme();
|
const { isDark } = useTheme();
|
||||||
const g = getGlassColors(isDark);
|
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 = [
|
const cards = [
|
||||||
{
|
{
|
||||||
label: t('dashboard.stats.balance'),
|
label: t('dashboard.stats.balance'),
|
||||||
value: `${formatAmount(balanceRubles)} ${currencySymbol}`,
|
value: `${formatAmount(balanceRubles)} ${currencySymbol}`,
|
||||||
valueColor: zone.mainHex,
|
valueColor: accentColor,
|
||||||
to: '/balance',
|
to: '/balance',
|
||||||
icon: (color: string) => (
|
icon: (color: string) => (
|
||||||
<svg
|
<svg
|
||||||
@@ -70,8 +67,8 @@ export default function StatsGrid({
|
|||||||
<path d="M6 14h.01M10 14h.01" />
|
<path d="M6 14h.01M10 14h.01" />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
iconBg: `${zone.mainHex}12`,
|
iconBg: accentBg,
|
||||||
iconColor: zone.mainHex,
|
iconColor: accentColor,
|
||||||
loading: false,
|
loading: false,
|
||||||
onboarding: 'balance',
|
onboarding: 'balance',
|
||||||
},
|
},
|
||||||
@@ -80,7 +77,7 @@ export default function StatsGrid({
|
|||||||
value: `${referralCount}`,
|
value: `${referralCount}`,
|
||||||
valueColor: g.text,
|
valueColor: g.text,
|
||||||
subtitle: `+${formatAmount(earningsRubles)} ${currencySymbol}`,
|
subtitle: `+${formatAmount(earningsRubles)} ${currencySymbol}`,
|
||||||
subtitleColor: zone.mainHex,
|
subtitleColor: accentColor,
|
||||||
to: '/referral',
|
to: '/referral',
|
||||||
icon: (color: string) => (
|
icon: (color: string) => (
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -71,12 +71,14 @@ export default function SubscriptionCardActive({
|
|||||||
style={{
|
style={{
|
||||||
background: g.cardBg,
|
background: g.cardBg,
|
||||||
border: subscription.is_trial
|
border: subscription.is_trial
|
||||||
? '1px solid rgba(62,219,176,0.15)'
|
? '1px solid rgba(var(--color-accent-400), 0.15)'
|
||||||
: isDark
|
: isDark
|
||||||
? `1px solid ${g.cardBorder}`
|
? `1px solid ${g.cardBorder}`
|
||||||
: `1px solid ${zone.mainHex}25`,
|
: `1px solid rgba(${zone.mainVarRaw}, 0.14)`,
|
||||||
padding: '28px 28px 24px',
|
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 */}
|
{/* Trial shimmer border */}
|
||||||
@@ -96,7 +98,7 @@ export default function SubscriptionCardActive({
|
|||||||
width: 200,
|
width: 200,
|
||||||
height: 200,
|
height: 200,
|
||||||
borderRadius: '50%',
|
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',
|
transition: 'background 0.8s ease',
|
||||||
}}
|
}}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -110,15 +112,15 @@ export default function SubscriptionCardActive({
|
|||||||
<div
|
<div
|
||||||
className="h-2 w-2 rounded-full"
|
className="h-2 w-2 rounded-full"
|
||||||
style={{
|
style={{
|
||||||
background: zone.mainHex,
|
background: zone.mainVar,
|
||||||
boxShadow: `0 0 8px ${zone.mainHex}80`,
|
boxShadow: `0 0 8px rgba(${zone.mainVarRaw}, 0.5)`,
|
||||||
transition: 'all 0.6s ease',
|
transition: 'all 0.6s ease',
|
||||||
}}
|
}}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className="font-mono text-[11px] font-semibold uppercase tracking-widest"
|
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)}
|
{isUnlimited ? t('dashboard.unlimited') : t(zone.labelKey)}
|
||||||
</span>
|
</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"
|
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={{
|
style={{
|
||||||
background:
|
background:
|
||||||
'linear-gradient(135deg, rgba(62,219,176,0.15), rgba(62,219,176,0.06))',
|
'linear-gradient(135deg, rgba(var(--color-accent-400), 0.15), rgba(var(--color-accent-400), 0.06))',
|
||||||
border: '1px solid rgba(62,219,176,0.2)',
|
border: '1px solid rgba(var(--color-accent-400), 0.2)',
|
||||||
color: '#3EDBB0',
|
color: 'rgb(var(--color-accent-400))',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -164,7 +166,7 @@ export default function SubscriptionCardActive({
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="font-display text-[28px] font-extrabold leading-none tracking-tight"
|
className="font-display text-[28px] font-extrabold leading-none tracking-tight"
|
||||||
style={{ color: zone.mainHex }}
|
style={{ color: zone.mainVar }}
|
||||||
>
|
>
|
||||||
∞
|
∞
|
||||||
</div>
|
</div>
|
||||||
@@ -209,14 +211,14 @@ export default function SubscriptionCardActive({
|
|||||||
{/* Monitor icon */}
|
{/* Monitor icon */}
|
||||||
<div
|
<div
|
||||||
className="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] transition-colors duration-500"
|
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
|
<svg
|
||||||
width="16"
|
width="16"
|
||||||
height="16"
|
height="16"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke={zone.mainHex}
|
stroke={zone.mainVar}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
@@ -249,8 +251,9 @@ export default function SubscriptionCardActive({
|
|||||||
key={i}
|
key={i}
|
||||||
className="h-[7px] w-[7px] rounded-full transition-all duration-300"
|
className="h-[7px] w-[7px] rounded-full transition-all duration-300"
|
||||||
style={{
|
style={{
|
||||||
background: i < connectedDevices ? zone.mainHex : g.textGhost,
|
background: i < connectedDevices ? zone.mainVar : g.textGhost,
|
||||||
boxShadow: i < connectedDevices ? `0 0 6px ${zone.mainHex}50` : 'none',
|
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"
|
className="h-full rounded-full transition-all duration-500"
|
||||||
style={{
|
style={{
|
||||||
width: `${Math.round((connectedDevices / subscription.device_limit) * 100)}%`,
|
width: `${Math.round((connectedDevices / subscription.device_limit) * 100)}%`,
|
||||||
background: zone.mainHex,
|
background: zone.mainVar,
|
||||||
boxShadow: `0 0 8px ${zone.mainHex}40`,
|
boxShadow: `0 0 8px rgba(${zone.mainVarRaw}, 0.25)`,
|
||||||
minWidth: connectedDevices > 0 ? '4px' : '0px',
|
minWidth: connectedDevices > 0 ? '4px' : '0px',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -283,13 +286,13 @@ export default function SubscriptionCardActive({
|
|||||||
to="/subscription"
|
to="/subscription"
|
||||||
className="flex-1 rounded-[14px] p-3.5 transition-all duration-500"
|
className="flex-1 rounded-[14px] p-3.5 transition-all duration-500"
|
||||||
style={{
|
style={{
|
||||||
background: `linear-gradient(135deg, ${zone.mainHex}12, ${zone.mainHex}06)`,
|
background: `linear-gradient(135deg, rgba(${zone.mainVarRaw}, 0.07), rgba(${zone.mainVarRaw}, 0.02))`,
|
||||||
border: `1px solid ${zone.mainHex}18`,
|
border: `1px solid rgba(${zone.mainVarRaw}, 0.09)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="mb-1.5 text-[10px] font-semibold uppercase tracking-wider opacity-70 transition-colors duration-500"
|
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')}
|
{t('dashboard.tariff')}
|
||||||
</div>
|
</div>
|
||||||
@@ -306,14 +309,17 @@ export default function SubscriptionCardActive({
|
|||||||
className="flex-1 rounded-[14px] p-3.5 transition-colors duration-300"
|
className="flex-1 rounded-[14px] p-3.5 transition-colors duration-300"
|
||||||
style={{
|
style={{
|
||||||
background: g.innerBg,
|
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="mb-1 flex items-center gap-1.5 text-[10px] font-medium uppercase tracking-wider text-dark-50/35">
|
||||||
<div
|
<div
|
||||||
className="flex h-6 w-6 items-center justify-center rounded-[7px] transition-colors duration-300"
|
className="flex h-6 w-6 items-center justify-center rounded-[7px] transition-colors duration-300"
|
||||||
style={{
|
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
|
<svg
|
||||||
@@ -321,7 +327,7 @@ export default function SubscriptionCardActive({
|
|||||||
height="13"
|
height="13"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke={daysLeft <= 3 ? '#FFB800' : g.textSecondary}
|
stroke={daysLeft <= 3 ? 'rgb(var(--color-warning-400))' : g.textSecondary}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
@@ -336,7 +342,7 @@ export default function SubscriptionCardActive({
|
|||||||
<div className="flex items-baseline gap-1">
|
<div className="flex items-baseline gap-1">
|
||||||
<span
|
<span
|
||||||
className="text-[22px] font-bold tracking-tight transition-colors duration-300"
|
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}
|
{daysLeft}
|
||||||
</span>
|
</span>
|
||||||
@@ -382,7 +388,7 @@ export default function SubscriptionCardActive({
|
|||||||
{t('dashboard.maxUsage', { amount: formatTraffic(Math.max(...dailyUsage)) })}
|
{t('dashboard.maxUsage', { amount: formatTraffic(Math.max(...dailyUsage)) })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Sparkline data={dailyUsage} width={440} height={44} color={zone.mainHex} />
|
<Sparkline data={dailyUsage} width={440} height={44} color={zone.mainVar} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,19 +26,23 @@ export default function TrafficProgressBar({
|
|||||||
const { isDark } = useTheme();
|
const { isDark } = useTheme();
|
||||||
const g = getGlassColors(isDark);
|
const g = getGlassColors(isDark);
|
||||||
const zone = useTrafficZone(percent);
|
const zone = useTrafficZone(percent);
|
||||||
|
|
||||||
// Gradient always starts from the accent color (normal zone)
|
// 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 clampedPercent = Math.min(percent, 100);
|
||||||
const barHeight = compact ? 8 : 14;
|
const barHeight = compact ? 8 : 14;
|
||||||
|
|
||||||
// Multi-segment gradient matching prototype
|
// 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(() => {
|
const fillGradient = useMemo(() => {
|
||||||
if (percent < 50) return `linear-gradient(90deg, ${startHex}, ${zone.mainHex})`;
|
if (percent < 50) return `linear-gradient(90deg, ${startColor}, ${zone.mainVar})`;
|
||||||
if (percent < 75) return `linear-gradient(90deg, ${startHex}, #FFB800, ${zone.mainHex})`;
|
if (percent < 75)
|
||||||
|
return `linear-gradient(90deg, ${startColor}, rgb(var(--color-warning-400)), ${zone.mainVar})`;
|
||||||
if (percent < 90)
|
if (percent < 90)
|
||||||
return `linear-gradient(90deg, ${startHex}, #FFB800, #FF6B35, ${zone.mainHex})`;
|
return `linear-gradient(90deg, ${startColor}, rgb(var(--color-warning-400)), rgb(var(--color-warning-300)), ${zone.mainVar})`;
|
||||||
return `linear-gradient(90deg, ${startHex}, #FFB800, #FF6B35, #FF3B5C)`;
|
return `linear-gradient(90deg, ${startColor}, rgb(var(--color-warning-400)), rgb(var(--color-warning-300)), rgb(var(--color-error-400)))`;
|
||||||
}, [percent, zone.mainHex, startHex]);
|
}, [percent, zone.mainVar, startColor]);
|
||||||
|
|
||||||
if (isUnlimited) {
|
if (isUnlimited) {
|
||||||
return (
|
return (
|
||||||
@@ -50,13 +54,13 @@ export default function TrafficProgressBar({
|
|||||||
height: barHeight,
|
height: barHeight,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
background: g.trackBg,
|
background: g.trackBg,
|
||||||
border: `1px solid ${zone.mainHex}20`,
|
border: `1px solid rgba(${zone.mainVarRaw}, 0.12)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 animate-unlimited-flow"
|
className="absolute inset-0 animate-unlimited-flow"
|
||||||
style={{
|
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%',
|
backgroundSize: '200% 100%',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -85,13 +89,13 @@ export default function TrafficProgressBar({
|
|||||||
<div className="mt-2 flex items-center justify-between px-0.5">
|
<div className="mt-2 flex items-center justify-between px-0.5">
|
||||||
<span
|
<span
|
||||||
className="flex items-center gap-1.5 text-[11px] font-semibold"
|
className="flex items-center gap-1.5 text-[11px] font-semibold"
|
||||||
style={{ color: zone.mainHex }}
|
style={{ color: zone.mainVar }}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="inline-block h-1.5 w-1.5 animate-unlimited-pulse rounded-full"
|
className="inline-block h-1.5 w-1.5 animate-unlimited-pulse rounded-full"
|
||||||
style={{
|
style={{
|
||||||
background: zone.mainHex,
|
background: zone.mainVar,
|
||||||
boxShadow: `0 0 8px ${zone.mainHex}`,
|
boxShadow: `0 0 8px ${zone.mainVar}`,
|
||||||
}}
|
}}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
@@ -127,9 +131,9 @@ export default function TrafficProgressBar({
|
|||||||
{/* Warning zone tint backgrounds */}
|
{/* Warning zone tint backgrounds */}
|
||||||
<div className="absolute inset-0 flex" aria-hidden="true">
|
<div className="absolute inset-0 flex" aria-hidden="true">
|
||||||
<div style={{ flex: '50 0 0', background: 'transparent' }} />
|
<div style={{ flex: '50 0 0', background: 'transparent' }} />
|
||||||
<div style={{ flex: '25 0 0', background: 'rgba(255,184,0,0.03)' }} />
|
<div style={{ flex: '25 0 0', background: 'rgba(var(--color-warning-400), 0.03)' }} />
|
||||||
<div style={{ flex: '15 0 0', background: 'rgba(255,107,53,0.04)' }} />
|
<div style={{ flex: '15 0 0', background: 'rgba(var(--color-warning-300), 0.04)' }} />
|
||||||
<div style={{ flex: '10 0 0', background: 'rgba(255,59,92,0.05)' }} />
|
<div style={{ flex: '10 0 0', background: 'rgba(var(--color-error-400), 0.05)' }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Fill bar */}
|
{/* Fill bar */}
|
||||||
@@ -194,7 +198,7 @@ export default function TrafficProgressBar({
|
|||||||
left: `calc(${clampedPercent}% - 8px)`,
|
left: `calc(${clampedPercent}% - 8px)`,
|
||||||
width: 16,
|
width: 16,
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
background: `radial-gradient(circle, ${zone.mainHex}60, transparent)`,
|
background: `radial-gradient(circle, rgba(${zone.mainVarRaw}, 0.38), transparent)`,
|
||||||
filter: 'blur(4px)',
|
filter: 'blur(4px)',
|
||||||
transition: 'left 1.2s cubic-bezier(0.16, 1, 0.3, 1)',
|
transition: 'left 1.2s cubic-bezier(0.16, 1, 0.3, 1)',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ export default function TrialOfferCard({
|
|||||||
border: isDark
|
border: isDark
|
||||||
? `1px solid ${g.cardBorder}`
|
? `1px solid ${g.cardBorder}`
|
||||||
: isFree
|
: 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)',
|
: '1px solid rgba(255,184,0,0.2)',
|
||||||
boxShadow: isDark
|
boxShadow: isDark
|
||||||
? g.shadow
|
? g.shadow
|
||||||
: isFree
|
: 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)',
|
: '0 2px 16px rgba(255,184,0,0.12), 0 0 0 1px rgba(255,184,0,0.06)',
|
||||||
padding: '32px 28px 28px',
|
padding: '32px 28px 28px',
|
||||||
}}
|
}}
|
||||||
@@ -55,7 +55,7 @@ export default function TrialOfferCard({
|
|||||||
height: 300,
|
height: 300,
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
background: isFree
|
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%)',
|
: 'radial-gradient(circle, rgba(255,184,0,0.07) 0%, transparent 70%)',
|
||||||
transition: 'background 0.5s ease',
|
transition: 'background 0.5s ease',
|
||||||
}}
|
}}
|
||||||
@@ -82,12 +82,14 @@ export default function TrialOfferCard({
|
|||||||
style={{
|
style={{
|
||||||
background: isDark
|
background: isDark
|
||||||
? isFree
|
? 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)'
|
: 'linear-gradient(135deg, #3a3020, #282418)'
|
||||||
: isFree
|
: 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))',
|
: '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',
|
transition: 'all 0.5s ease',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -97,7 +99,7 @@ export default function TrialOfferCard({
|
|||||||
height="26"
|
height="26"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#3EDBB0"
|
stroke="rgb(var(--color-accent-400))"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
@@ -128,7 +130,9 @@ export default function TrialOfferCard({
|
|||||||
<div
|
<div
|
||||||
className="absolute inset-[-1px] animate-trial-glow rounded-2xl"
|
className="absolute inset-[-1px] animate-trial-glow rounded-2xl"
|
||||||
style={{
|
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"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
@@ -250,14 +254,15 @@ export default function TrialOfferCard({
|
|||||||
isDark
|
isDark
|
||||||
? {
|
? {
|
||||||
background:
|
background:
|
||||||
'linear-gradient(135deg, rgba(62,219,176,0.12) 0%, rgba(62,219,176,0.04) 100%)',
|
'linear-gradient(135deg, rgba(var(--color-accent-400), 0.12) 0%, rgba(var(--color-accent-400), 0.04) 100%)',
|
||||||
border: '1px solid rgba(62,219,176,0.25)',
|
border: '1px solid rgba(var(--color-accent-400), 0.25)',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
background: 'linear-gradient(135deg, #3EDBB0, #2BC49A)',
|
background:
|
||||||
|
'linear-gradient(135deg, rgb(var(--color-accent-400)), rgb(var(--color-accent-500)))',
|
||||||
color: '#0a2a1e',
|
color: '#0a2a1e',
|
||||||
boxShadow: '0 4px 20px rgba(62,219,176,0.25)',
|
boxShadow: '0 4px 20px rgba(var(--color-accent-400), 0.25)',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default function PurchaseCTAButton({ subscription }: PurchaseCTAButtonPro
|
|||||||
const isExpired = !subscription || (!subscription.is_active && !subscription.is_trial);
|
const isExpired = !subscription || (!subscription.is_active && !subscription.is_trial);
|
||||||
const isTrial = subscription?.is_trial;
|
const isTrial = subscription?.is_trial;
|
||||||
|
|
||||||
const accentColor = isExpired ? '#FF3B5C' : '#3EDBB0';
|
const accentColor = isExpired ? '#FF3B5C' : 'rgb(var(--color-accent-400))';
|
||||||
|
|
||||||
const buttonText = isExpired
|
const buttonText = isExpired
|
||||||
? t('subscription.getSubscription')
|
? t('subscription.getSubscription')
|
||||||
@@ -39,7 +39,7 @@ export default function PurchaseCTAButton({ subscription }: PurchaseCTAButtonPro
|
|||||||
style={{
|
style={{
|
||||||
background: isExpired
|
background: isExpired
|
||||||
? 'linear-gradient(135deg, rgba(255,59,92,0.08), rgba(255,107,53,0.06))'
|
? 'linear-gradient(135deg, rgba(255,59,92,0.08), rgba(255,107,53,0.06))'
|
||||||
: 'linear-gradient(135deg, rgba(62,219,176,0.08), rgba(0,229,160,0.06))',
|
: 'linear-gradient(135deg, rgba(var(--color-accent-400), 0.08), rgba(var(--color-accent-400), 0.06))',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Left: icon + text */}
|
{/* Left: icon + text */}
|
||||||
@@ -48,7 +48,9 @@ export default function PurchaseCTAButton({ subscription }: PurchaseCTAButtonPro
|
|||||||
<div
|
<div
|
||||||
className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl"
|
className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl"
|
||||||
style={{
|
style={{
|
||||||
background: isExpired ? 'rgba(255,59,92,0.12)' : 'rgba(62,219,176,0.12)',
|
background: isExpired
|
||||||
|
? 'rgba(255,59,92,0.12)'
|
||||||
|
: 'rgba(var(--color-accent-400), 0.12)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -263,7 +263,6 @@ export default function Dashboard() {
|
|||||||
{/* Stats Grid */}
|
{/* Stats Grid */}
|
||||||
<StatsGrid
|
<StatsGrid
|
||||||
balanceRubles={balanceData?.balance_rubles || 0}
|
balanceRubles={balanceData?.balance_rubles || 0}
|
||||||
subscription={subscription}
|
|
||||||
referralCount={referralInfo?.total_referrals || 0}
|
referralCount={referralInfo?.total_referrals || 0}
|
||||||
earningsRubles={referralInfo?.available_balance_rubles || 0}
|
earningsRubles={referralInfo?.available_balance_rubles || 0}
|
||||||
refLoading={refLoading}
|
refLoading={refLoading}
|
||||||
|
|||||||
@@ -447,7 +447,7 @@ export default function Subscription() {
|
|||||||
style={{
|
style={{
|
||||||
background: g.cardBg,
|
background: g.cardBg,
|
||||||
border: subscription.is_trial
|
border: subscription.is_trial
|
||||||
? '1px solid rgba(62,219,176,0.15)'
|
? '1px solid rgba(var(--color-accent-400), 0.15)'
|
||||||
: isDark
|
: isDark
|
||||||
? `1px solid ${g.cardBorder}`
|
? `1px solid ${g.cardBorder}`
|
||||||
: `1px solid ${zone.mainHex}25`,
|
: `1px solid ${zone.mainHex}25`,
|
||||||
@@ -535,21 +535,21 @@ export default function Subscription() {
|
|||||||
className="mb-6 rounded-[14px] p-4"
|
className="mb-6 rounded-[14px] p-4"
|
||||||
style={{
|
style={{
|
||||||
background:
|
background:
|
||||||
'linear-gradient(135deg, rgba(62,219,176,0.08), rgba(62,219,176,0.03))',
|
'linear-gradient(135deg, rgba(var(--color-accent-400), 0.08), rgba(var(--color-accent-400), 0.03))',
|
||||||
border: '1px solid rgba(62,219,176,0.12)',
|
border: '1px solid rgba(var(--color-accent-400), 0.12)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div
|
<div
|
||||||
className="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px]"
|
className="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px]"
|
||||||
style={{ background: 'rgba(62,219,176,0.12)' }}
|
style={{ background: 'rgba(var(--color-accent-400), 0.12)' }}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
width="16"
|
width="16"
|
||||||
height="16"
|
height="16"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#3EDBB0"
|
stroke="rgb(var(--color-accent-400))"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
@@ -559,7 +559,10 @@ export default function Subscription() {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="text-sm font-semibold" style={{ color: '#3EDBB0' }}>
|
<div
|
||||||
|
className="text-sm font-semibold"
|
||||||
|
style={{ color: 'rgb(var(--color-accent-400))' }}
|
||||||
|
>
|
||||||
{t('subscription.trialInfo.title')}
|
{t('subscription.trialInfo.title')}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 text-[12px] text-dark-50/40">
|
<div className="mt-1 text-[12px] text-dark-50/40">
|
||||||
@@ -569,7 +572,7 @@ export default function Subscription() {
|
|||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span
|
<span
|
||||||
className="font-mono text-[12px] font-semibold"
|
className="font-mono text-[12px] font-semibold"
|
||||||
style={{ color: '#3EDBB0' }}
|
style={{ color: 'rgb(var(--color-accent-400))' }}
|
||||||
>
|
>
|
||||||
{subscription.days_left > 0
|
{subscription.days_left > 0
|
||||||
? t('subscription.days', { count: subscription.days_left })
|
? t('subscription.days', { count: subscription.days_left })
|
||||||
@@ -582,7 +585,7 @@ export default function Subscription() {
|
|||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span
|
<span
|
||||||
className="font-mono text-[12px] font-semibold"
|
className="font-mono text-[12px] font-semibold"
|
||||||
style={{ color: '#3EDBB0' }}
|
style={{ color: 'rgb(var(--color-accent-400))' }}
|
||||||
>
|
>
|
||||||
{subscription.traffic_limit_gb || '∞'} {t('common.units.gb')}
|
{subscription.traffic_limit_gb || '∞'} {t('common.units.gb')}
|
||||||
</span>
|
</span>
|
||||||
@@ -593,7 +596,7 @@ export default function Subscription() {
|
|||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span
|
<span
|
||||||
className="font-mono text-[12px] font-semibold"
|
className="font-mono text-[12px] font-semibold"
|
||||||
style={{ color: '#3EDBB0' }}
|
style={{ color: 'rgb(var(--color-accent-400))' }}
|
||||||
>
|
>
|
||||||
{subscription.device_limit}
|
{subscription.device_limit}
|
||||||
</span>
|
</span>
|
||||||
@@ -749,9 +752,11 @@ export default function Subscription() {
|
|||||||
onClick={copyUrl}
|
onClick={copyUrl}
|
||||||
className="flex h-auto items-center rounded-[10px] px-3 transition-colors duration-300"
|
className="flex h-auto items-center rounded-[10px] px-3 transition-colors duration-300"
|
||||||
style={{
|
style={{
|
||||||
background: copied ? 'rgba(62,219,176,0.12)' : g.innerBorder,
|
background: copied ? 'rgba(var(--color-accent-400), 0.12)' : g.innerBorder,
|
||||||
border: copied ? '1px solid rgba(62,219,176,0.2)' : `1px solid ${g.trackBg}`,
|
border: copied
|
||||||
color: copied ? '#3EDBB0' : g.textMuted,
|
? '1px solid rgba(var(--color-accent-400), 0.2)'
|
||||||
|
: `1px solid ${g.trackBg}`,
|
||||||
|
color: copied ? 'rgb(var(--color-accent-400))' : g.textMuted,
|
||||||
}}
|
}}
|
||||||
title={t('subscription.copyLink')}
|
title={t('subscription.copyLink')}
|
||||||
>
|
>
|
||||||
@@ -979,12 +984,12 @@ export default function Subscription() {
|
|||||||
className="rounded-[10px] px-4 py-2 text-sm font-semibold transition-colors duration-300"
|
className="rounded-[10px] px-4 py-2 text-sm font-semibold transition-colors duration-300"
|
||||||
style={{
|
style={{
|
||||||
background: subscription.is_daily_paused
|
background: subscription.is_daily_paused
|
||||||
? 'rgba(62,219,176,0.12)'
|
? 'rgba(var(--color-accent-400), 0.12)'
|
||||||
: 'rgba(255,184,0,0.12)',
|
: 'rgba(255,184,0,0.12)',
|
||||||
border: subscription.is_daily_paused
|
border: subscription.is_daily_paused
|
||||||
? '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)',
|
: '1px solid rgba(255,184,0,0.2)',
|
||||||
color: subscription.is_daily_paused ? '#3EDBB0' : '#FFB800',
|
color: subscription.is_daily_paused ? 'rgb(var(--color-accent-400))' : '#FFB800',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{pauseMutation.isPending ? (
|
{pauseMutation.isPending ? (
|
||||||
|
|||||||
@@ -412,7 +412,8 @@ export default function SubscriptionPurchase() {
|
|||||||
<div
|
<div
|
||||||
className="mb-6 rounded-[14px] p-4"
|
className="mb-6 rounded-[14px] p-4"
|
||||||
style={{
|
style={{
|
||||||
background: 'linear-gradient(135deg, rgba(255,184,0,0.08), rgba(62,219,176,0.06))',
|
background:
|
||||||
|
'linear-gradient(135deg, rgba(255,184,0,0.08), rgba(var(--color-accent-400),0.06))',
|
||||||
border: '1px solid rgba(255,184,0,0.15)',
|
border: '1px solid rgba(255,184,0,0.15)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ interface TrafficZoneResult {
|
|||||||
labelKey: string;
|
labelKey: string;
|
||||||
gradientFrom: string;
|
gradientFrom: string;
|
||||||
gradientTo: string;
|
gradientTo: string;
|
||||||
|
/** CSS variable for the main zone color: `rgb(var(--color-accent-400))` */
|
||||||
|
mainVar: string;
|
||||||
|
/** Raw CSS variable reference for opacity manipulation: `var(--color-accent-400)` */
|
||||||
|
mainVarRaw: string;
|
||||||
/** Key into ThemeColors for resolving mainHex at runtime */
|
/** Key into ThemeColors for resolving mainHex at runtime */
|
||||||
colorKey: TrafficColorKey;
|
colorKey: TrafficColorKey;
|
||||||
}
|
}
|
||||||
@@ -22,6 +26,8 @@ const ZONES: Record<TrafficZone, Omit<TrafficZoneResult, 'zone'>> = {
|
|||||||
labelKey: 'dashboard.zone.normal',
|
labelKey: 'dashboard.zone.normal',
|
||||||
gradientFrom: 'rgb(var(--color-accent-500))',
|
gradientFrom: 'rgb(var(--color-accent-500))',
|
||||||
gradientTo: 'rgb(var(--color-accent-400))',
|
gradientTo: 'rgb(var(--color-accent-400))',
|
||||||
|
mainVar: 'rgb(var(--color-accent-400))',
|
||||||
|
mainVarRaw: 'var(--color-accent-400)',
|
||||||
colorKey: 'accent',
|
colorKey: 'accent',
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
@@ -31,6 +37,8 @@ const ZONES: Record<TrafficZone, Omit<TrafficZoneResult, 'zone'>> = {
|
|||||||
labelKey: 'dashboard.zone.warning',
|
labelKey: 'dashboard.zone.warning',
|
||||||
gradientFrom: 'rgb(var(--color-warning-500))',
|
gradientFrom: 'rgb(var(--color-warning-500))',
|
||||||
gradientTo: 'rgb(var(--color-warning-400))',
|
gradientTo: 'rgb(var(--color-warning-400))',
|
||||||
|
mainVar: 'rgb(var(--color-warning-400))',
|
||||||
|
mainVarRaw: 'var(--color-warning-400)',
|
||||||
colorKey: 'warning',
|
colorKey: 'warning',
|
||||||
},
|
},
|
||||||
danger: {
|
danger: {
|
||||||
@@ -40,6 +48,8 @@ const ZONES: Record<TrafficZone, Omit<TrafficZoneResult, 'zone'>> = {
|
|||||||
labelKey: 'dashboard.zone.danger',
|
labelKey: 'dashboard.zone.danger',
|
||||||
gradientFrom: 'rgb(var(--color-warning-600))',
|
gradientFrom: 'rgb(var(--color-warning-600))',
|
||||||
gradientTo: 'rgb(var(--color-warning-400))',
|
gradientTo: 'rgb(var(--color-warning-400))',
|
||||||
|
mainVar: 'rgb(var(--color-warning-400))',
|
||||||
|
mainVarRaw: 'var(--color-warning-400)',
|
||||||
colorKey: 'warning',
|
colorKey: 'warning',
|
||||||
},
|
},
|
||||||
critical: {
|
critical: {
|
||||||
@@ -49,6 +59,8 @@ const ZONES: Record<TrafficZone, Omit<TrafficZoneResult, 'zone'>> = {
|
|||||||
labelKey: 'dashboard.zone.critical',
|
labelKey: 'dashboard.zone.critical',
|
||||||
gradientFrom: 'rgb(var(--color-error-500))',
|
gradientFrom: 'rgb(var(--color-error-500))',
|
||||||
gradientTo: 'rgb(var(--color-error-400))',
|
gradientTo: 'rgb(var(--color-error-400))',
|
||||||
|
mainVar: 'rgb(var(--color-error-400))',
|
||||||
|
mainVarRaw: 'var(--color-error-400)',
|
||||||
colorKey: 'error',
|
colorKey: 'error',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -273,8 +273,8 @@ export default {
|
|||||||
'50%': { opacity: '0.5', transform: 'scale(0.7)' },
|
'50%': { opacity: '0.5', transform: 'scale(0.7)' },
|
||||||
},
|
},
|
||||||
trialGlow: {
|
trialGlow: {
|
||||||
'0%, 100%': { boxShadow: '0 0 15px rgba(62, 219, 176, 0.06)' },
|
'0%, 100%': { boxShadow: '0 0 15px rgba(var(--color-accent-400), 0.06)' },
|
||||||
'50%': { boxShadow: '0 0 30px rgba(62, 219, 176, 0.12)' },
|
'50%': { boxShadow: '0 0 30px rgba(var(--color-accent-400), 0.12)' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
transitionTimingFunction: {
|
transitionTimingFunction: {
|
||||||
|
|||||||
Reference in New Issue
Block a user