feat(ui): add AnimatedGlossyBorder and refine industrial interactions
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 39s
Build & Deploy / 🧪 QA (push) Successful in 1m44s
Build & Deploy / 🏗️ Build (push) Successful in 3m23s
Build & Deploy / 🚀 Deploy (push) Successful in 47s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m2s
Build & Deploy / 🔔 Notify (push) Successful in 2s
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 39s
Build & Deploy / 🧪 QA (push) Successful in 1m44s
Build & Deploy / 🏗️ Build (push) Successful in 3m23s
Build & Deploy / 🚀 Deploy (push) Successful in 47s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m2s
Build & Deploy / 🔔 Notify (push) Successful in 2s
This commit is contained in:
46
components/ui/AnimatedGlossyBorder.tsx
Normal file
46
components/ui/AnimatedGlossyBorder.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import { cn } from './utils';
|
||||
|
||||
interface AnimatedGlossyBorderProps {
|
||||
className?: string;
|
||||
borderWidth?: 1 | 2;
|
||||
opacity?: number;
|
||||
color?: 'white' | 'primary';
|
||||
}
|
||||
|
||||
export function AnimatedGlossyBorder({
|
||||
className,
|
||||
borderWidth = 1,
|
||||
color = 'white'
|
||||
}: AnimatedGlossyBorderProps) {
|
||||
const isWhite = color === 'white';
|
||||
const glowColor = isWhite ? 'rgba(255,255,255,0.6)' : 'rgba(17,124,97,0.6)';
|
||||
|
||||
const gradientColors = isWhite
|
||||
? 'transparent 0%, rgba(255,255,255,0.1) 15%, rgba(255,255,255,0.85) 25%, rgba(255,255,255,0.1) 35%, transparent 50%, transparent 50%, rgba(255,255,255,0.1) 65%, rgba(255,255,255,0.85) 75%, rgba(255,255,255,0.1) 85%, transparent 100%'
|
||||
: 'transparent 0%, rgba(17,124,97,0.1) 15%, rgba(17,124,97,0.85) 25%, rgba(17,124,97,0.1) 35%, transparent 50%, transparent 50%, rgba(17,124,97,0.1) 65%, rgba(17,124,97,0.85) 75%, rgba(17,124,97,0.1) 85%, transparent 100%';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('absolute inset-0 pointer-events-none rounded-[inherit] z-20 mix-blend-overlay', className)}
|
||||
style={{ filter: `drop-shadow(0 0 6px ${glowColor}) drop-shadow(0 0 2px ${glowColor})` }}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 rounded-[inherit]"
|
||||
style={{
|
||||
border: `${borderWidth}px solid transparent`,
|
||||
WebkitMask: 'linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0)',
|
||||
WebkitMaskComposite: 'xor',
|
||||
maskComposite: 'exclude'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[250%] aspect-square animate-[spin_8s_linear_infinite]"
|
||||
style={{
|
||||
background: `conic-gradient(from 0deg, ${gradientColors})`
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export function Badge({ children, className, variant = 'primary' }: { children:
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={cn('inline-flex items-center px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider', variants[variant], className)}>
|
||||
<span className={cn('inline-flex items-center px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider select-none', variants[variant], className)}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { TransitionLink } from '@/components/ui/TransitionLink';
|
||||
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
|
||||
import { cn } from './utils';
|
||||
|
||||
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
@@ -19,7 +20,7 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
|
||||
}
|
||||
|
||||
export const buttonBaseStyles =
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-full font-semibold transition-all duration-500 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none active:scale-95 hover:-translate-y-1 hover:scale-[1.02] relative overflow-hidden group/btn isolate';
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-full font-semibold transition-all duration-500 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none active:scale-95 hover:-translate-y-1 hover:scale-[1.02] relative overflow-hidden group/btn isolate select-none';
|
||||
|
||||
export const buttonVariantsMap = {
|
||||
primary: 'bg-primary text-white shadow-md hover:shadow-primary/30 hover:shadow-2xl',
|
||||
@@ -58,13 +59,23 @@ export function getButtonClasses(variant: keyof typeof buttonVariantsMap = 'prim
|
||||
}
|
||||
|
||||
export function ButtonOverlay({ variant = 'primary' }: { variant?: keyof typeof buttonVariantsMap }) {
|
||||
const isWhiteBg = variant === 'white' || variant === 'outline' || variant === 'ghost';
|
||||
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'absolute top-0 -left-[150%] h-[200%] w-[150%] bg-gradient-to-r from-transparent to-transparent skew-x-[-20deg] group-hover/btn:left-[100%] transition-all duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] z-30 pointer-events-none',
|
||||
buttonShineColorsMap[variant]
|
||||
<>
|
||||
<span
|
||||
className={cn(
|
||||
'absolute top-0 -left-[150%] h-[200%] w-[150%] bg-gradient-to-r from-transparent to-transparent skew-x-[-20deg] group-hover/btn:left-[100%] transition-all duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] z-30 pointer-events-none',
|
||||
buttonShineColorsMap[variant]
|
||||
)}
|
||||
/>
|
||||
{!isWhiteBg && (
|
||||
<AnimatedGlossyBorder
|
||||
color="white"
|
||||
className="opacity-40 group-hover/btn:opacity-100 transition-opacity duration-700"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export function Label({ className, ...props }: LabelProps) {
|
||||
return (
|
||||
<label
|
||||
className={cn(
|
||||
'text-[10px] md:text-xs font-extrabold text-primary uppercase tracking-widest',
|
||||
'text-[10px] md:text-xs font-extrabold text-primary uppercase tracking-widest select-none',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
20
components/ui/LogoArcs.tsx
Normal file
20
components/ui/LogoArcs.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
// Reusable SVG component that precisely mimics the overlapping, sweeping arcs of the E-TIB logo.
|
||||
// The circles are offset along a diagonal to bunch together at the bottom-left and spread apart at the top-right.
|
||||
export const LogoArcs = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
viewBox="0 0 200 200"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
stroke="currentColor"
|
||||
>
|
||||
{/* Outer ring */}
|
||||
<circle cx="100" cy="100" r="90" strokeWidth="1.5" />
|
||||
{/* Middle thicker ring */}
|
||||
<circle cx="92" cy="108" r="78" strokeWidth="3.5" />
|
||||
{/* Inner ring */}
|
||||
<circle cx="84" cy="116" r="66" strokeWidth="1.5" />
|
||||
</svg>
|
||||
);
|
||||
Reference in New Issue
Block a user