Skip to content

Commit c96efd2

Browse files
committed
feat: enhance license plate components with hover effects and dynamic shimmer
1 parent 77fbe4c commit c96efd2

9 files changed

Lines changed: 218 additions & 134 deletions

src/components/LicensePlate.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
452452
bottom: 0,
453453
zIndex: 2,
454454
}}>
455-
<UKBand scale={scale} height="100%" showFlag={showUKFlag} isEV={isEV} borderRadius={5} />
455+
<UKBand scale={scale} height="100%" showFlag={showUKFlag} isEV={isEV} borderRadius={5} isHovering={isHovering} tilt={tilt} />
456456
</div>
457457
)}
458458

@@ -465,7 +465,7 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
465465
bottom: 0,
466466
zIndex: 2,
467467
}}>
468-
<NorwayBand scale={scale} height="100%" borderRadius={5} />
468+
<NorwayBand scale={scale} height="100%" borderRadius={5} isHovering={isHovering} tilt={tilt} />
469469
</div>
470470
)}
471471

@@ -590,7 +590,7 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
590590
{/* Austrian Bundesland Plakette - plain emblem */}
591591
{showStatePlakette && country === 'A' && (
592592
<div style={{ transformStyle: 'preserve-3d' }}>
593-
<AustrianStatePlakette state={state as AustrianState} scale={scale * 1.3} />
593+
<AustrianStatePlakette state={state as AustrianState} scale={scale * 1.3} isHovering={isHovering} tilt={tilt} />
594594
</div>
595595
)}
596596

@@ -606,7 +606,7 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
606606
{/* Hungarian national coat of arms */}
607607
{showStatePlakette && (
608608
<div style={{ transformStyle: 'preserve-3d' }}>
609-
<HungarianCoatOfArms scale={scale * 1.3} />
609+
<HungarianCoatOfArms scale={scale * 1.3} isHovering={isHovering} tilt={tilt} />
610610
</div>
611611
)}
612612

@@ -622,7 +622,7 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
622622
{/* Slovak national coat of arms */}
623623
{showStatePlakette && (
624624
<div style={{ transformStyle: 'preserve-3d' }}>
625-
<SlovakCoatOfArms scale={scale * 1.3} />
625+
<SlovakCoatOfArms scale={scale * 1.3} isHovering={isHovering} tilt={tilt} />
626626
</div>
627627
)}
628628

@@ -635,7 +635,7 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
635635
{/* Swiss coat of arms (left side) */}
636636
{showStatePlakette && (
637637
<div style={{ transformStyle: 'preserve-3d' }}>
638-
<SwissCoatOfArms scale={scale * 1.15} />
638+
<SwissCoatOfArms scale={scale * 1.15} isHovering={isHovering} tilt={tilt} />
639639
</div>
640640
)}
641641

@@ -647,12 +647,16 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
647647
{/* Canton coat of arms (right side) */}
648648
{showStatePlakette && state && (
649649
<div style={{ transformStyle: 'preserve-3d' }}>
650-
<SwissCantonPlakette canton={state as SwissCanton} scale={scale * 1.15} />
650+
<SwissCantonPlakette canton={state as SwissCanton} scale={scale * 1.15} isHovering={isHovering} tilt={tilt} />
651651
</div>
652652
)}
653653
</>
654654
) : (
655-
<span style={{ ...textStyle, transform: 'translateZ(15px)', transformStyle: 'preserve-3d' }}>
655+
<span style={{
656+
...textStyle,
657+
transform: `translateZ(15px)${country === 'N' ? ' translateY(12px)' : ''}`,
658+
transformStyle: 'preserve-3d'
659+
}}>
656660
{country === 'S' ? (
657661
plateType === 'normal' ? `${plateText.slice(0, 3)}\u2009${plateText.slice(3, 6)}` : plateText
658662
) : (plateText || '')}

src/components/bands/NorwayBand.tsx

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,20 @@ interface NorwayBandProps {
77
scale?: number;
88
height?: number | string;
99
borderRadius?: number;
10+
isHovering?: boolean;
11+
tilt?: { rotateX: number; rotateY: number };
1012
}
1113

12-
export default function NorwayBand({ scale = 1, height, borderRadius = 5 }: NorwayBandProps) {
14+
export default function NorwayBand({ scale = 1, height, borderRadius = 5, isHovering = false, tilt = { rotateX: 0, rotateY: 0 } }: NorwayBandProps) {
1315
const width = 45 * scale;
1416
const defaultHeight = 110 * scale;
1517
const actualHeight = height || defaultHeight;
1618
const radius = borderRadius * scale;
1719

20+
// Calculate shimmer position based on tilt
21+
const shimmerX = 50 + (tilt.rotateY * 3);
22+
const shimmerY = 50 + (tilt.rotateX * 3);
23+
1824
return (
1925
<div
2026
style={{
@@ -31,20 +37,36 @@ export default function NorwayBand({ scale = 1, height, borderRadius = 5 }: Norw
3137
}}
3238
>
3339
{/* Norwegian Flag */}
34-
<div style={{
35-
width: `${32 * scale}px`,
36-
height: `${24 * scale}px`,
37-
position: 'relative',
38-
borderRadius: `${2 * scale}px`,
39-
overflow: 'hidden',
40-
boxShadow: `0 ${1 * scale}px ${3 * scale}px rgba(0,0,0,0.4)`,
41-
}}>
40+
<div
41+
style={{
42+
width: `${32 * scale}px`,
43+
height: `${24 * scale}px`,
44+
position: 'relative',
45+
borderRadius: `${2 * scale}px`,
46+
overflow: 'hidden',
47+
boxShadow: `0 ${1 * scale}px ${3 * scale}px rgba(0,0,0,0.4)`,
48+
}}
49+
>
4250
<Image
4351
src="/flags/no.svg"
4452
alt="Norwegian Flag"
4553
fill
4654
style={{ objectFit: 'cover' }}
4755
/>
56+
{/* Dynamic shimmer overlay */}
57+
{isHovering && (
58+
<div
59+
style={{
60+
position: 'absolute',
61+
top: 0,
62+
left: 0,
63+
right: 0,
64+
bottom: 0,
65+
background: `radial-gradient(ellipse at ${shimmerX}% ${shimmerY}%, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 50%)`,
66+
pointerEvents: 'none',
67+
}}
68+
/>
69+
)}
4870
</div>
4971

5072
{/* Country code N */}

src/components/bands/UKBand.tsx

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@ interface UKBandProps {
99
showFlag: boolean; // Show UK flag + text
1010
isEV: boolean; // Green background for EV
1111
borderRadius?: number;
12+
isHovering?: boolean;
13+
tilt?: { rotateX: number; rotateY: number };
1214
}
1315

14-
export default function UKBand({ scale = 1, height, showFlag, isEV, borderRadius = 5 }: UKBandProps) {
16+
export default function UKBand({ scale = 1, height, showFlag, isEV, borderRadius = 5, isHovering = false, tilt = { rotateX: 0, rotateY: 0 } }: UKBandProps) {
1517
const width = 40 * scale;
1618
const defaultHeight = 110 * scale;
1719
const actualHeight = height || defaultHeight;
1820
const radius = borderRadius * scale;
1921

22+
// Calculate shimmer position based on tilt
23+
const shimmerX = 50 + (tilt.rotateY * 3);
24+
const shimmerY = 50 + (tilt.rotateX * 3);
25+
2026
// Green only strip (EV without flag)
2127
if (!showFlag && isEV) {
2228
return (
@@ -48,20 +54,36 @@ export default function UKBand({ scale = 1, height, showFlag, isEV, borderRadius
4854
}}
4955
>
5056
{/* UK Flag */}
51-
<div style={{
52-
width: `${28 * scale}px`,
53-
height: `${18 * scale}px`,
54-
position: 'relative',
55-
borderRadius: `${2 * scale}px`,
56-
overflow: 'hidden',
57-
boxShadow: `0 ${1 * scale}px ${2 * scale}px rgba(0,0,0,0.3)`,
58-
}}>
57+
<div
58+
style={{
59+
width: `${28 * scale}px`,
60+
height: `${18 * scale}px`,
61+
position: 'relative',
62+
borderRadius: `${2 * scale}px`,
63+
overflow: 'hidden',
64+
boxShadow: `0 ${1 * scale}px ${2 * scale}px rgba(0,0,0,0.3)`,
65+
}}
66+
>
5967
<Image
6068
src="/flags/uk.svg"
6169
alt="UK Flag"
6270
fill
6371
style={{ objectFit: 'cover' }}
6472
/>
73+
{/* Dynamic shimmer overlay */}
74+
{isHovering && (
75+
<div
76+
style={{
77+
position: 'absolute',
78+
top: 0,
79+
left: 0,
80+
right: 0,
81+
bottom: 0,
82+
background: `radial-gradient(ellipse at ${shimmerX}% ${shimmerY}%, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 50%)`,
83+
pointerEvents: 'none',
84+
}}
85+
/>
86+
)}
6587
</div>
6688

6789
{/* UK text */}

src/components/plaketten/AustrianStatePlakette.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@
22

33
import React from 'react';
44
import { AustrianState, AUSTRIAN_STATE_NAMES } from '@/types/plate';
5+
import CoatOfArms from './CoatOfArms';
56

67
interface AustrianStatePlaketteProps {
78
state: AustrianState;
89
scale?: number;
10+
isHovering?: boolean;
11+
tilt?: { rotateX: number; rotateY: number };
912
}
1013

11-
export default function AustrianStatePlakette({ state, scale = 1 }: AustrianStatePlaketteProps) {
14+
export default function AustrianStatePlakette({ state, scale = 1, isHovering = false, tilt }: AustrianStatePlaketteProps) {
1215
const width = 50 * scale;
13-
const height = 60 * scale;
1416

1517
// Safety check for undefined state
1618
if (!state || !(state in AUSTRIAN_STATE_NAMES)) {
1719
return null;
1820
}
1921

20-
const imagePath = `/coa/at/${state}.svg`;
2122
const stateName = AUSTRIAN_STATE_NAMES[state].toUpperCase();
2223

2324
// Calculate compression based on text length
@@ -37,14 +38,14 @@ export default function AustrianStatePlakette({ state, scale = 1 }: AustrianStat
3738
}}
3839
>
3940
{/* Coat of arms - plain, no circle */}
40-
<img
41-
src={imagePath}
41+
<CoatOfArms
42+
src={`/coa/at/${state}.svg`}
4243
alt={stateName}
43-
style={{
44-
width: `${width}px`,
45-
height: `${width}px`,
46-
objectFit: 'contain',
47-
}}
44+
scale={scale}
45+
size={50}
46+
isHovering={isHovering}
47+
tilt={tilt}
48+
useNextImage={false}
4849
/>
4950

5051
{/* State name below - constrained to emblem width */}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import Image from 'next/image';
5+
6+
interface CoatOfArmsProps {
7+
src: string;
8+
alt: string;
9+
scale?: number;
10+
size?: number; // base size before scaling (default 50)
11+
isHovering?: boolean;
12+
tilt?: { rotateX: number; rotateY: number };
13+
useNextImage?: boolean; // use Next.js Image component for optimization
14+
}
15+
16+
export default function CoatOfArms({
17+
src,
18+
alt,
19+
scale = 1,
20+
size = 50,
21+
isHovering = false,
22+
tilt = { rotateX: 0, rotateY: 0 },
23+
useNextImage = true,
24+
}: CoatOfArmsProps) {
25+
const computedSize = size * scale;
26+
27+
// Calculate shimmer position based on tilt
28+
const shimmerX = 50 + (tilt.rotateY * 3);
29+
const shimmerY = 50 + (tilt.rotateX * 3);
30+
31+
return (
32+
<div
33+
style={{
34+
width: `${computedSize}px`,
35+
height: `${computedSize}px`,
36+
display: 'flex',
37+
alignItems: 'center',
38+
justifyContent: 'center',
39+
flexShrink: 0,
40+
position: 'relative',
41+
overflow: 'hidden',
42+
}}
43+
>
44+
{useNextImage ? (
45+
<Image
46+
src={src}
47+
alt={alt}
48+
width={computedSize}
49+
height={computedSize}
50+
style={{
51+
width: '100%',
52+
height: '100%',
53+
objectFit: 'contain',
54+
}}
55+
/>
56+
) : (
57+
<img
58+
src={src}
59+
alt={alt}
60+
style={{
61+
width: '100%',
62+
height: '100%',
63+
objectFit: 'contain',
64+
}}
65+
/>
66+
)}
67+
{/* Dynamic shimmer overlay */}
68+
{isHovering && (
69+
<div
70+
style={{
71+
position: 'absolute',
72+
top: 0,
73+
left: 0,
74+
right: 0,
75+
bottom: 0,
76+
background: `radial-gradient(ellipse at ${shimmerX}% ${shimmerY}%, rgba(255,255,255,0.15) 0%, rgba(255,255,255,0) 50%)`,
77+
pointerEvents: 'none',
78+
}}
79+
/>
80+
)}
81+
</div>
82+
);
83+
}
Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,24 @@
11
'use client';
22

33
import React from 'react';
4+
import CoatOfArms from './CoatOfArms';
45

56
interface HungarianCoatOfArmsProps {
67
scale?: number;
8+
isHovering?: boolean;
9+
tilt?: { rotateX: number; rotateY: number };
710
}
811

9-
export default function HungarianCoatOfArms({ scale = 1 }: HungarianCoatOfArmsProps) {
10-
const size = 50 * scale;
11-
12+
export default function HungarianCoatOfArms({ scale = 1, isHovering = false, tilt }: HungarianCoatOfArmsProps) {
1213
return (
13-
<div
14-
style={{
15-
width: `${size}px`,
16-
height: `${size}px`,
17-
display: 'flex',
18-
alignItems: 'center',
19-
justifyContent: 'center',
20-
flexShrink: 0,
21-
}}
22-
>
23-
<img
24-
src="/coa/hr.svg"
25-
alt="Hungarian coat of arms"
26-
style={{
27-
width: '100%',
28-
height: '100%',
29-
objectFit: 'contain',
30-
}}
31-
/>
32-
</div>
14+
<CoatOfArms
15+
src="/coa/hr.svg"
16+
alt="Hungarian coat of arms"
17+
scale={scale}
18+
size={50}
19+
isHovering={isHovering}
20+
tilt={tilt}
21+
useNextImage={false}
22+
/>
3323
);
3424
}

0 commit comments

Comments
 (0)