diff --git a/src/components/dashboard/SubscriptionCardActive.tsx b/src/components/dashboard/SubscriptionCardActive.tsx
index 0ca4e8c..099faaf 100644
--- a/src/components/dashboard/SubscriptionCardActive.tsx
+++ b/src/components/dashboard/SubscriptionCardActive.tsx
@@ -10,6 +10,7 @@ import { useTheme } from '../../hooks/useTheme';
import { getTrafficZone } from '../../utils/trafficZone';
import { formatTraffic } from '../../utils/formatTraffic';
import { getGlassColors } from '../../utils/glassTheme';
+import { HoverBorderGradient } from '../ui/hover-border-gradient';
import type { Subscription } from '../../types';
interface SubscriptionCardActiveProps {
@@ -196,15 +197,13 @@ export default function SubscriptionCardActive({
{/* ─── Connect Device Button ─── */}
{subscription.subscription_url && (
-
+
)}
{/* ─── Stats row: Tariff + Days Left ─── */}
diff --git a/src/components/ui/hover-border-gradient.tsx b/src/components/ui/hover-border-gradient.tsx
index 16deb68..a4302c6 100644
--- a/src/components/ui/hover-border-gradient.tsx
+++ b/src/components/ui/hover-border-gradient.tsx
@@ -1,30 +1,51 @@
import React from 'react';
import { cn } from '@/lib/utils';
+interface HoverBorderGradientProps extends React.HTMLAttributes {
+ /** Rendered HTML element. Default: 'div' */
+ as?: React.ElementType;
+ /** Hex color for the rotating gradient beam (e.g. '#00E5A0') */
+ accentColor?: string;
+ /** Full rotation duration in seconds. Default: 3 */
+ duration?: number;
+}
+
+/**
+ * Animated conic-gradient border that rotates around the element.
+ *
+ * Uses pure CSS `@property --border-angle` animation — no JS loops,
+ * GPU-friendly, works in Telegram Mini App WebView.
+ *
+ * Fallback: browsers without `@property` show a static gradient border.
+ */
export function HoverBorderGradient({
children,
- containerClassName,
className,
- as: Tag = 'button',
+ as: Tag = 'div',
+ accentColor,
+ duration,
+ style,
...props
-}: React.PropsWithChildren<
- {
- as?: React.ElementType;
- containerClassName?: string;
- className?: string;
- } & React.HTMLAttributes
->) {
+}: HoverBorderGradientProps) {
+ const cssVars: Record = {};
+
+ if (accentColor) {
+ cssVars['--accent-color'] = accentColor;
+ // 30% opacity hex suffix for hover glow
+ cssVars['--accent-glow'] = accentColor + '4D';
+ }
+
+ if (duration !== undefined) {
+ cssVars['--border-duration'] = `${duration}s`;
+ }
+
return (
-
- {children}
-
+ {children}
);
}
diff --git a/src/pages/Subscription.tsx b/src/pages/Subscription.tsx
index 7582779..a416c3f 100644
--- a/src/pages/Subscription.tsx
+++ b/src/pages/Subscription.tsx
@@ -10,6 +10,7 @@ import { getTrafficZone } from '../utils/trafficZone';
import { formatTraffic } from '../utils/formatTraffic';
import { getGlassColors } from '../utils/glassTheme';
import { useTheme } from '../hooks/useTheme';
+import { HoverBorderGradient } from '../components/ui/hover-border-gradient';
import type {
PurchaseSelection,
PeriodOption,
@@ -909,14 +910,12 @@ export default function Subscription() {
{/* ─── Connect Device Button ─── */}
{subscription.subscription_url && (
-
+
)}
{/* ─── Subscription URL ─── */}
diff --git a/src/styles/globals.css b/src/styles/globals.css
index deeb7f3..f6f16bc 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -10,10 +10,9 @@
}
.hover-border-gradient {
- --_bg: rgb(var(--color-dark-900));
- --_accent: rgb(var(--color-accent-400));
+ --_bg: var(--border-inner-bg, rgb(var(--color-dark-900)));
+ --_accent: var(--accent-color, rgb(var(--color-accent-400)));
border: 1.5px solid transparent;
- padding: 10px 16px;
background:
linear-gradient(var(--_bg), var(--_bg)) padding-box,
conic-gradient(
@@ -24,12 +23,22 @@
var(--_accent) 100%
)
border-box;
- animation: border-rotate 3s linear infinite;
- transition: box-shadow 0.3s ease;
+ animation: border-rotate var(--border-duration, 3s) linear infinite;
}
-.hover-border-gradient:hover {
- box-shadow: 0 0 16px rgba(var(--color-accent-400), 0.3);
+.hover-border-gradient:hover,
+.hover-border-gradient:active {
+ box-shadow: 0 0 20px var(--accent-glow, rgba(var(--color-accent-400), 0.25));
+}
+
+.light .hover-border-gradient {
+ --_bg: var(--border-inner-bg, rgb(var(--color-champagne-100)));
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .hover-border-gradient {
+ animation-play-state: paused;
+ }
}
@keyframes border-rotate {