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

This commit is contained in:
2026-05-13 10:23:47 +02:00
parent 7d6a52e1c6
commit 2cb0b50193
15 changed files with 224 additions and 54 deletions

View 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>
);
}

View File

@@ -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>
);

View File

@@ -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"
/>
)}
/>
</>
);
}

View File

@@ -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}

View 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>
);