mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
refactor(tokens): extract subscription urgent + critical semantic colors
The #FFB800 (23 hits) and #FF3B5C (11 hits) raw hex literals across Subscription / SubscriptionPurchase / TrialOfferCard / useTrafficZone all represent the same semantic: 'expiring soon' and 'expired'. Distinct from warning-500 (#f59e0b) and error-500 (#ef4444) — the subscription timeline needs a sharper, hotter signal than the system-wide warning/error roles. Add two semantic tokens: --color-urgent-400: 255, 184, 0 (#FFB800) --color-critical-500: 255, 59, 92 (#FF3B5C) Exposed via Tailwind as urgent.400 / critical.500. Sweep all single-and double-quoted hex literals to rgb(var(--color-...)) form so style attrs, SVG stroke=, and ternary string values all resolve through CSS vars (auto-respects future light-theme variants). Left as-is: 4 hex appearances inside gradient pair strings — linear-gradient(135deg, #FFB800, #FF8C00) — where the partner color (#FF8C00 / #FF6B35) isn't in the token set. Partial-replace inside gradient strings would just add noise; full extraction of the orange-ramp companion is out of scope for this pass.
This commit is contained in:
@@ -107,14 +107,14 @@ export default function SubscriptionCardExpired({
|
|||||||
r: 255,
|
r: 255,
|
||||||
g: 184,
|
g: 184,
|
||||||
b: 0,
|
b: 0,
|
||||||
hex: '#FFB800',
|
hex: 'rgb(var(--color-urgent-400))',
|
||||||
gradient: 'linear-gradient(135deg, #FFB800, #FF8C00)',
|
gradient: 'linear-gradient(135deg, #FFB800, #FF8C00)',
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
r: 255,
|
r: 255,
|
||||||
g: 59,
|
g: 59,
|
||||||
b: 92,
|
b: 92,
|
||||||
hex: '#FF3B5C',
|
hex: 'rgb(var(--color-critical-500))',
|
||||||
gradient: 'linear-gradient(135deg, #FF3B5C, #FF6B35)',
|
gradient: 'linear-gradient(135deg, #FF3B5C, #FF6B35)',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export default function TrialOfferCard({
|
|||||||
height="26"
|
height="26"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#FFB800"
|
stroke="rgb(var(--color-urgent-400))"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
@@ -157,11 +157,14 @@ export default function TrialOfferCard({
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="text-[32px] font-extrabold leading-none tracking-tight"
|
className="text-[32px] font-extrabold leading-none tracking-tight"
|
||||||
style={{ color: '#FFB800' }}
|
style={{ color: 'rgb(var(--color-urgent-400))' }}
|
||||||
>
|
>
|
||||||
{trialInfo.price_rubles.toFixed(0)}
|
{trialInfo.price_rubles.toFixed(0)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-base font-semibold opacity-70" style={{ color: '#FFB800' }}>
|
<span
|
||||||
|
className="text-base font-semibold opacity-70"
|
||||||
|
style={{ color: 'rgb(var(--color-urgent-400))' }}
|
||||||
|
>
|
||||||
{currencySymbol}
|
{currencySymbol}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default function PurchaseCTAButton({
|
|||||||
// Daily tariffs renew automatically — no manual renewal button needed in multi-tariff
|
// Daily tariffs renew automatically — no manual renewal button needed in multi-tariff
|
||||||
if (isMultiTariff && isDaily && !isExpired) return null;
|
if (isMultiTariff && isDaily && !isExpired) return null;
|
||||||
|
|
||||||
const accentColor = isExpired ? '#FF3B5C' : 'rgb(var(--color-accent-400))';
|
const accentColor = isExpired ? 'rgb(var(--color-critical-500))' : 'rgb(var(--color-accent-400))';
|
||||||
|
|
||||||
const buttonText = isExpired
|
const buttonText = isExpired
|
||||||
? t('subscription.getSubscription')
|
? t('subscription.getSubscription')
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import type { ThemeColors } from '../types/theme';
|
|||||||
|
|
||||||
const FALLBACKS: Record<TrafficColorKey, string> = {
|
const FALLBACKS: Record<TrafficColorKey, string> = {
|
||||||
accent: '#3b82f6',
|
accent: '#3b82f6',
|
||||||
warning: '#FFB800',
|
warning: 'rgb(var(--color-urgent-400))',
|
||||||
error: '#FF3B5C',
|
error: 'rgb(var(--color-critical-500))',
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLOR_MAP: Record<TrafficColorKey, keyof ThemeColors> = {
|
const COLOR_MAP: Record<TrafficColorKey, keyof ThemeColors> = {
|
||||||
|
|||||||
@@ -98,7 +98,13 @@ const CountdownTimer = memo(function CountdownTimer({
|
|||||||
height="13"
|
height="13"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke={isExpired ? '#FF3B5C' : isUrgent ? '#FFB800' : g.textSecondary}
|
stroke={
|
||||||
|
isExpired
|
||||||
|
? 'rgb(var(--color-critical-500))'
|
||||||
|
: isUrgent
|
||||||
|
? 'rgb(var(--color-urgent-400))'
|
||||||
|
: g.textSecondary
|
||||||
|
}
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
@@ -111,7 +117,10 @@ const CountdownTimer = memo(function CountdownTimer({
|
|||||||
{t('dashboard.remaining')}
|
{t('dashboard.remaining')}
|
||||||
</div>
|
</div>
|
||||||
{isExpired ? (
|
{isExpired ? (
|
||||||
<div className="text-[18px] font-bold tracking-tight" style={{ color: '#FF3B5C' }}>
|
<div
|
||||||
|
className="text-[18px] font-bold tracking-tight"
|
||||||
|
style={{ color: 'rgb(var(--color-critical-500))' }}
|
||||||
|
>
|
||||||
{t('subscription.expired')}
|
{t('subscription.expired')}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -121,7 +130,7 @@ const CountdownTimer = memo(function CountdownTimer({
|
|||||||
<>
|
<>
|
||||||
<span
|
<span
|
||||||
className="text-[20px] font-bold tracking-tight"
|
className="text-[20px] font-bold tracking-tight"
|
||||||
style={{ color: isUrgent ? '#FFB800' : g.text }}
|
style={{ color: isUrgent ? 'rgb(var(--color-urgent-400))' : g.text }}
|
||||||
>
|
>
|
||||||
{countdown.days}
|
{countdown.days}
|
||||||
</span>
|
</span>
|
||||||
@@ -132,31 +141,31 @@ const CountdownTimer = memo(function CountdownTimer({
|
|||||||
)}
|
)}
|
||||||
<span
|
<span
|
||||||
className="text-[20px] font-bold tracking-tight"
|
className="text-[20px] font-bold tracking-tight"
|
||||||
style={{ color: isUrgent ? '#FFB800' : g.text }}
|
style={{ color: isUrgent ? 'rgb(var(--color-urgent-400))' : g.text }}
|
||||||
>
|
>
|
||||||
{String(countdown.hours).padStart(2, '0')}
|
{String(countdown.hours).padStart(2, '0')}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className="mx-[-1px] text-[16px] font-bold opacity-30"
|
className="mx-[-1px] text-[16px] font-bold opacity-30"
|
||||||
style={{ color: isUrgent ? '#FFB800' : g.text }}
|
style={{ color: isUrgent ? 'rgb(var(--color-urgent-400))' : g.text }}
|
||||||
>
|
>
|
||||||
:
|
:
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className="text-[20px] font-bold tracking-tight"
|
className="text-[20px] font-bold tracking-tight"
|
||||||
style={{ color: isUrgent ? '#FFB800' : g.text }}
|
style={{ color: isUrgent ? 'rgb(var(--color-urgent-400))' : g.text }}
|
||||||
>
|
>
|
||||||
{String(countdown.minutes).padStart(2, '0')}
|
{String(countdown.minutes).padStart(2, '0')}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className="mx-[-1px] text-[16px] font-bold opacity-30"
|
className="mx-[-1px] text-[16px] font-bold opacity-30"
|
||||||
style={{ color: isUrgent ? '#FFB800' : g.text }}
|
style={{ color: isUrgent ? 'rgb(var(--color-urgent-400))' : g.text }}
|
||||||
>
|
>
|
||||||
:
|
:
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className="text-[20px] font-bold tracking-tight"
|
className="text-[20px] font-bold tracking-tight"
|
||||||
style={{ color: isUrgent ? '#FFB800' : g.text }}
|
style={{ color: isUrgent ? 'rgb(var(--color-urgent-400))' : g.text }}
|
||||||
>
|
>
|
||||||
{String(countdown.seconds).padStart(2, '0')}
|
{String(countdown.seconds).padStart(2, '0')}
|
||||||
</span>
|
</span>
|
||||||
@@ -710,8 +719,8 @@ export default function Subscription() {
|
|||||||
color: subscription.is_active
|
color: subscription.is_active
|
||||||
? zone.mainHex
|
? zone.mainHex
|
||||||
: subscription.is_limited
|
: subscription.is_limited
|
||||||
? '#FFB800'
|
? 'rgb(var(--color-urgent-400))'
|
||||||
: '#FF3B5C',
|
: 'rgb(var(--color-critical-500))',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{subscription.is_active
|
{subscription.is_active
|
||||||
@@ -746,7 +755,7 @@ export default function Subscription() {
|
|||||||
height="16"
|
height="16"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#FFB800"
|
stroke="rgb(var(--color-urgent-400))"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
@@ -758,7 +767,10 @@ export default function Subscription() {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-sm font-semibold" style={{ color: '#FFB800' }}>
|
<p
|
||||||
|
className="text-sm font-semibold"
|
||||||
|
style={{ color: 'rgb(var(--color-urgent-400))' }}
|
||||||
|
>
|
||||||
{t('subscription.trafficLimitedTitle')}
|
{t('subscription.trafficLimitedTitle')}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-xs text-dark-400">
|
<p className="mt-1 text-xs text-dark-400">
|
||||||
@@ -1269,7 +1281,7 @@ export default function Subscription() {
|
|||||||
color:
|
color:
|
||||||
subscription.is_daily_paused || subscription.status === 'disabled'
|
subscription.is_daily_paused || subscription.status === 'disabled'
|
||||||
? 'rgb(var(--color-accent-400))'
|
? 'rgb(var(--color-accent-400))'
|
||||||
: '#FFB800',
|
: 'rgb(var(--color-urgent-400))',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{pauseMutation.isPending ? (
|
{pauseMutation.isPending ? (
|
||||||
@@ -1306,7 +1318,7 @@ export default function Subscription() {
|
|||||||
style={{
|
style={{
|
||||||
background: 'rgba(255,59,92,0.08)',
|
background: 'rgba(255,59,92,0.08)',
|
||||||
border: '1px solid rgba(255,59,92,0.15)',
|
border: '1px solid rgba(255,59,92,0.15)',
|
||||||
color: '#FF3B5C',
|
color: 'rgb(var(--color-critical-500))',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{getErrorMessage(pauseMutation.error)}
|
{getErrorMessage(pauseMutation.error)}
|
||||||
@@ -1324,11 +1336,14 @@ export default function Subscription() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-lg" style={{ color: '#FFB800' }}>
|
<div className="text-lg" style={{ color: 'rgb(var(--color-urgent-400))' }}>
|
||||||
⏸️
|
⏸️
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-semibold" style={{ color: '#FFB800' }}>
|
<div
|
||||||
|
className="text-sm font-semibold"
|
||||||
|
style={{ color: 'rgb(var(--color-urgent-400))' }}
|
||||||
|
>
|
||||||
{t('subscription.pause.pausedInfo')}
|
{t('subscription.pause.pausedInfo')}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 text-[12px] text-dark-50/35">
|
<div className="mt-1 text-[12px] text-dark-50/35">
|
||||||
@@ -2412,7 +2427,7 @@ export default function Subscription() {
|
|||||||
}}
|
}}
|
||||||
disabled={deleteAllDevicesMutation.isPending}
|
disabled={deleteAllDevicesMutation.isPending}
|
||||||
className="text-[11px] font-medium transition-colors"
|
className="text-[11px] font-medium transition-colors"
|
||||||
style={{ color: '#FF3B5C' }}
|
style={{ color: 'rgb(var(--color-critical-500))' }}
|
||||||
>
|
>
|
||||||
{t('subscription.deleteAllDevices')}
|
{t('subscription.deleteAllDevices')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -477,7 +477,7 @@ export default function SubscriptionPurchase() {
|
|||||||
height="16"
|
height="16"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#FFB800"
|
stroke="rgb(var(--color-urgent-400))"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
@@ -489,7 +489,10 @@ export default function SubscriptionPurchase() {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-semibold" style={{ color: '#FFB800' }}>
|
<div
|
||||||
|
className="text-sm font-semibold"
|
||||||
|
style={{ color: 'rgb(var(--color-urgent-400))' }}
|
||||||
|
>
|
||||||
{t('subscription.trialUpgrade.title')}
|
{t('subscription.trialUpgrade.title')}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 text-[12px] text-dark-50/40">
|
<div className="mt-1 text-[12px] text-dark-50/40">
|
||||||
@@ -522,7 +525,7 @@ export default function SubscriptionPurchase() {
|
|||||||
height="16"
|
height="16"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#FF3B5C"
|
stroke="rgb(var(--color-critical-500))"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
@@ -534,7 +537,10 @@ export default function SubscriptionPurchase() {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-semibold" style={{ color: '#FF3B5C' }}>
|
<div
|
||||||
|
className="text-sm font-semibold"
|
||||||
|
style={{ color: 'rgb(var(--color-critical-500))' }}
|
||||||
|
>
|
||||||
{t('subscription.expiredBanner.title')}
|
{t('subscription.expiredBanner.title')}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 text-[12px] text-dark-50/40">
|
<div className="mt-1 text-[12px] text-dark-50/40">
|
||||||
|
|||||||
@@ -180,6 +180,13 @@
|
|||||||
--color-error-900: 127, 29, 29;
|
--color-error-900: 127, 29, 29;
|
||||||
--color-error-950: 69, 10, 10;
|
--color-error-950: 69, 10, 10;
|
||||||
|
|
||||||
|
/* Subscription-status semantic tokens — distinct from warning/error
|
||||||
|
because the urgency timeline ("expiring soon" vs "expired") needs a
|
||||||
|
sharper, hotter signal than the system-wide warning/error roles.
|
||||||
|
Extracted from inline #FFB800 / #FF3B5C in Subscription.tsx etc. */
|
||||||
|
--color-urgent-400: 255, 184, 0; /* #FFB800 — expiring soon */
|
||||||
|
--color-critical-500: 255, 59, 92; /* #FF3B5C — expired */
|
||||||
|
|
||||||
/* Theme semantic colors (hex for direct use) */
|
/* Theme semantic colors (hex for direct use) */
|
||||||
--color-dark-bg: #0a0f1a;
|
--color-dark-bg: #0a0f1a;
|
||||||
--color-dark-surface: #0f172a;
|
--color-dark-surface: #0f172a;
|
||||||
|
|||||||
@@ -101,6 +101,13 @@ export default {
|
|||||||
900: withOpacity('--color-error-900'),
|
900: withOpacity('--color-error-900'),
|
||||||
950: withOpacity('--color-error-950'),
|
950: withOpacity('--color-error-950'),
|
||||||
},
|
},
|
||||||
|
// Subscription-status semantic tokens — see globals.css for rationale.
|
||||||
|
urgent: {
|
||||||
|
400: withOpacity('--color-urgent-400'),
|
||||||
|
},
|
||||||
|
critical: {
|
||||||
|
500: withOpacity('--color-critical-500'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: [
|
sans: [
|
||||||
|
|||||||
Reference in New Issue
Block a user