Skip to content

Commit 1ffa16b

Browse files
committed
feat: add UK support with UK band and flag, update country handling
1 parent b9e537a commit 1ffa16b

9 files changed

Lines changed: 202 additions & 39 deletions

File tree

public/flags/uk.svg

Lines changed: 9 additions & 0 deletions
Loading

public/fonts/UKNumberPlate.ttf

72 KB
Binary file not shown.

src/app/globals.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@
1616
font-display: swap;
1717
}
1818

19+
@font-face {
20+
font-family: 'UKNumberPlate';
21+
src: url('/fonts/UKNumberPlate.ttf') format('truetype');
22+
font-weight: normal;
23+
font-style: normal;
24+
font-display: swap;
25+
}
26+
1927
@font-face {
2028
font-family: 'Google Sans';
2129
src: url('/fonts/GoogleSans-Regular.woff2') format('woff2');

src/components/EUBand.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
'use client';
22

33
import React from 'react';
4-
import { EUCountry } from '@/types/plate';
4+
import { Country } from '@/types/plate';
55

66
interface EUBandProps {
77
scale?: number;
8-
countryCode?: EUCountry;
8+
countryCode?: Country;
99
height?: number | string; // Optional height for constrained layouts (e.g., Austria)
1010
noBorderRadius?: boolean; // Remove border radius (for Austria)
1111
showDinGepruft?: boolean; // Show DIN-GEPRÜFT seal for German plates
12+
borderRadius?: number; // Border radius in scaled pixels (default 5)
1213
}
1314

14-
export default function EUBand({ scale = 1, countryCode = 'D', height, noBorderRadius = false, showDinGepruft = false }: EUBandProps) {
15+
export default function EUBand({ scale = 1, countryCode = 'D', height, noBorderRadius = false, showDinGepruft = false, borderRadius = 5 }: EUBandProps) {
1516
const width = 45 * scale;
1617
const defaultHeight = 110 * scale;
1718
const actualHeight = height || defaultHeight;
19+
const radius = borderRadius * scale;
1820

1921
return (
2022
<div
@@ -27,7 +29,7 @@ export default function EUBand({ scale = 1, countryCode = 'D', height, noBorderR
2729
width: `${width}px`,
2830
height: actualHeight,
2931
backgroundColor: '#003399',
30-
borderRadius: noBorderRadius ? '0' : `${6 * scale}px 0 0 ${6 * scale}px`,
32+
borderRadius: noBorderRadius ? '0' : `${radius}px 0 0 ${radius}px`,
3133
padding: `${6 * scale}px ${4 * scale}px`,
3234
}}
3335
>

src/components/LicensePlate.tsx

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import React, { forwardRef, useRef, useState, useLayoutEffect, useEffect } from 'react';
44
import { GermanPlateConfig, PlateStyle, GermanState, AustrianState, SwissCanton } from '@/types/plate';
55
import EUBand from './EUBand';
6+
import UKBand from './UKBand';
67
import StatePlakette from './StatePlakette';
78
import AustrianStatePlakette from './AustrianStatePlakette';
89
import HungarianCoatOfArms from './HungarianCoatOfArms';
@@ -92,7 +93,7 @@ function getCountryFeatures(country: string): {
9293

9394
const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
9495
({ config, scale = 1 }, ref) => {
95-
const { cityCode, letters, numbers, suffix, showStatePlakette, showHUPlakette, state, city, huYear, huMonth, width, plateStyle, plateType, country, fontColor, backgroundColor, plateText, rightBandText, seasonalPlate } = config;
96+
const { cityCode, letters, numbers, suffix, showStatePlakette, showHUPlakette, state, city, huYear, huMonth, width, plateStyle, plateType, country, fontColor, backgroundColor, plateText, rightBandText, seasonalPlate, showUKFlag, isEV } = config;
9697

9798
const contentRef = useRef<HTMLDivElement>(null);
9899
const plateRef = useRef<HTMLDivElement>(null);
@@ -127,7 +128,8 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
127128
const fontPromises = [
128129
document.fonts.load('105px EuroPlate'),
129130
document.fonts.load('105px "Google Sans"'),
130-
document.fonts.load('105px Tratex')
131+
document.fonts.load('105px Tratex'),
132+
document.fonts.load('105px UKNumberPlate')
131133
];
132134

133135
Promise.all(fontPromises).then(() => {
@@ -162,13 +164,16 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
162164
const textColor = fontColor;
163165
const plateBgColor = backgroundColor;
164166
const redStripeHeight = 2 * scale;
167+
const hasUkBand = country === 'GB' && (showUKFlag || isEV);
168+
const ukBandWidth = hasUkBand ? 40 * scale : 0; // UK band width when shown
165169

166170
// Available width for content (after EU band, borders, padding, and right band if present)
167-
// Switzerland (CH) has no EU band
168-
const effectiveEuBandWidth = country === 'CH' ? 0 : euBandWidth;
171+
// Switzerland (CH) has no EU band, UK (GB) has UK band instead
172+
const effectiveEuBandWidth = (country === 'CH' || country === 'GB') ? 0 : euBandWidth;
173+
const effectiveLeftOffset = country === 'GB' ? ukBandWidth : effectiveEuBandWidth;
169174
const rightBandWidth = countryFeatures.hasRightBand ? euBandWidth : 0;
170175
const seasonalPlateWidth = (isGermany && seasonalPlate) ? 37 * scale : 0; // Reserve space for seasonal numbers
171-
const availableWidth = plateWidth - effectiveEuBandWidth - rightBandWidth - seasonalPlateWidth - (borderWidth * 2) - (padding * 2);
176+
const availableWidth = plateWidth - effectiveLeftOffset - rightBandWidth - seasonalPlateWidth - (borderWidth * 2) - (padding * 2);
172177

173178
// Create a content key that changes when content changes - forces remeasurement
174179
const contentKey = `${cityCode}-${letters}-${numbers}-${suffix}-${showStatePlakette}-${showHUPlakette}-${plateText}-${plateType}-${country}-${seasonalPlate?.startMonth}-${seasonalPlate?.endMonth}`;
@@ -221,12 +226,19 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
221226
}
222227
}, [contentKey, availableWidth, scale, fontLoaded, width, euBandWidth, effectiveEuBandWidth, borderWidth, padding, isGermany, seasonalPlate, minCompactWidth, countryFeatures.hasRightBand]);
223228

229+
// Select font based on country
230+
const getFontFamily = () => {
231+
if (country === 'S') return 'Tratex, Normal';
232+
if (country === 'GB') return 'UKNumberPlate, sans-serif';
233+
return 'EuroPlate, sans-serif';
234+
};
235+
224236
const baseTextStyle: React.CSSProperties = {
225237
fontSize: `${fontSize}px`,
226238
fontWeight: 'normal',
227239
letterSpacing: `${2 * scale}px`,
228240
whiteSpace: 'nowrap',
229-
fontFamily: country === 'S' ? 'Tratex, Normal' : 'EuroPlate, sans-serif',
241+
fontFamily: getFontFamily(),
230242
};
231243

232244
// Carbon fiber pattern - woven checkerboard
@@ -330,7 +342,7 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
330342
backgroundColor: plateBgColor,
331343
border: `${borderWidth}px solid ${country === 'A' ? 'transparent' : styles.borderColor}`,
332344
borderRadius: `${8 * scale}px`,
333-
fontFamily: country === 'S' ? 'Tratex, Normal' : 'EuroPlate, sans-serif',
345+
fontFamily: getFontFamily(),
334346
transformStyle: 'preserve-3d',
335347
overflow: 'hidden',
336348
boxShadow: styles.is3D
@@ -382,8 +394,8 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
382394
</>
383395
)}
384396

385-
{/* EU Band or German Flag for military - not shown for Switzerland */}
386-
{country === 'CH' ? null : country === 'D' && cityCode === 'Y' ? (
397+
{/* EU Band or German Flag for military - not shown for Switzerland or UK */}
398+
{country === 'CH' || country === 'GB' ? null : country === 'D' && cityCode === 'Y' ? (
387399
/* German Flag for military plates */
388400
<div style={{
389401
position: 'absolute',
@@ -424,7 +436,20 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
424436
padding: country === 'A' ? `0 ${3 * scale}px` : '0',
425437
zIndex: 2,
426438
}}>
427-
<EUBand scale={scale} countryCode={country} height={country === 'A' ? '100%' : undefined} noBorderRadius={country === 'A'} showDinGepruft={isGermany} />
439+
<EUBand scale={scale} countryCode={country} height={country === 'A' ? '100%' : undefined} noBorderRadius={country === 'A'} showDinGepruft={isGermany} borderRadius={5} />
440+
</div>
441+
)}
442+
443+
{/* UK Band - with flag and UK text, green for EV */}
444+
{country === 'GB' && (showUKFlag || isEV) && (
445+
<div style={{
446+
position: 'absolute',
447+
left: 0,
448+
top: 0,
449+
bottom: 0,
450+
zIndex: 2,
451+
}}>
452+
<UKBand scale={scale} height="100%" showFlag={showUKFlag} isEV={isEV} borderRadius={5} />
428453
</div>
429454
)}
430455

@@ -457,7 +482,7 @@ const LicensePlate = forwardRef<HTMLDivElement, LicensePlateProps>(
457482
<div
458483
style={{
459484
position: 'absolute',
460-
left: country === 'CH' ? 0 : `${euBandWidth}px`,
485+
left: country === 'CH' ? 0 : (country === 'GB' && hasUkBand) ? `${ukBandWidth}px` : country === 'GB' ? 0 : `${euBandWidth}px`,
461486
right: countryFeatures.hasRightBand ? `${euBandWidth}px` : 0,
462487
top: 0,
463488
bottom: 0,

src/components/PlateGenerator.tsx

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33
import { useState, useRef, useCallback, useEffect } from 'react';
44
import { domToPng } from 'modern-screenshot';
5-
import { GermanPlateConfig, GermanState, STATE_NAMES, AustrianState, AUSTRIAN_STATE_NAMES, HungarianState, SlovakState, SwissCanton, SWISS_CANTON_NAMES, PlateWidth, PlateSuffix, PlateStyle, PlateType, EUCountry } from '@/types/plate';
5+
import { GermanPlateConfig, GermanState, STATE_NAMES, AustrianState, AUSTRIAN_STATE_NAMES, HungarianState, SlovakState, SwissCanton, SWISS_CANTON_NAMES, PlateWidth, PlateSuffix, PlateStyle, PlateType, Country } from '@/types/plate';
66
import LicensePlate from './LicensePlate';
77
import { useTranslation, Language, LANGUAGE_NAMES, LANGUAGE_FLAGS, SUPPORTED_LANGUAGES } from '@/i18n';
88

99

1010
const CURRENT_YEAR = new Date().getFullYear();
1111

1212
// Country flag emojis
13-
const COUNTRY_FLAGS: Record<EUCountry, string> = {
13+
const COUNTRY_FLAGS: Record<Country, string> = {
1414
'D': '🇩🇪',
1515
'A': '🇦🇹',
1616
'B': '🇧🇪',
@@ -23,6 +23,7 @@ const COUNTRY_FLAGS: Record<EUCountry, string> = {
2323
'EST': '🇪🇪',
2424
'FIN': '🇫🇮',
2525
'F': '🇫🇷',
26+
'GB': '🇬🇧',
2627
'GR': '🇬🇷',
2728
'H': '🇭🇺',
2829
'IRL': '🇮🇪',
@@ -42,7 +43,7 @@ const COUNTRY_FLAGS: Record<EUCountry, string> = {
4243
};
4344

4445
// Country-specific default colors and settings
45-
function getCountryDefaults(country: EUCountry): { fontColor: string; backgroundColor: string; rightBandText: string } {
46+
function getCountryDefaults(country: Country): { fontColor: string; backgroundColor: string; rightBandText: string } {
4647
switch (country) {
4748
// Yellow plates
4849
case 'NL': // Netherlands - yellow
@@ -51,6 +52,8 @@ function getCountryDefaults(country: EUCountry): { fontColor: string; background
5152
return { fontColor: '#000000', backgroundColor: '#FCD116', rightBandText: '' };
5253
case 'CY': // Cyprus - yellow
5354
return { fontColor: '#000000', backgroundColor: '#F4C430', rightBandText: '' };
55+
case 'GB': // United Kingdom - yellow (rear plate)
56+
return { fontColor: '#000000', backgroundColor: '#F7D117', rightBandText: '' };
5457

5558
// Red text plates
5659
case 'B': // Belgium - white with red text
@@ -112,6 +115,8 @@ const DEFAULT_CONFIG: GermanPlateConfig = {
112115
plateText: 'NIKLAS',
113116
rightBandText: '',
114117
seasonalPlate: null,
118+
showUKFlag: false,
119+
isEV: false,
115120
};
116121

117122
// Parse config from URL hash
@@ -141,12 +146,14 @@ function parseConfigFromHash(): GermanPlateConfig {
141146
width: (params.get('width') as PlateWidth) || DEFAULT_CONFIG.width,
142147
plateStyle: (params.get('style') as PlateStyle) || DEFAULT_CONFIG.plateStyle,
143148
plateType: (params.get('plateType') as PlateType) || DEFAULT_CONFIG.plateType,
144-
country: (params.get('country') as EUCountry) || DEFAULT_CONFIG.country,
149+
country: (params.get('country') as Country) || DEFAULT_CONFIG.country,
145150
fontColor: params.get('fontColor') || DEFAULT_CONFIG.fontColor,
146151
backgroundColor: params.get('bgColor') || DEFAULT_CONFIG.backgroundColor,
147152
plateText: params.get('text') || DEFAULT_CONFIG.plateText,
148153
rightBandText: params.get('rightBand') || DEFAULT_CONFIG.rightBandText,
149154
seasonalPlate,
155+
showUKFlag: params.get('ukFlag') === '1',
156+
isEV: params.get('ev') === '1',
150157
};
151158
}
152159

@@ -175,6 +182,8 @@ function configToHash(config: GermanPlateConfig): string {
175182
params.set('seasonStart', String(config.seasonalPlate.startMonth));
176183
params.set('seasonEnd', String(config.seasonalPlate.endMonth));
177184
}
185+
if (config.showUKFlag) params.set('ukFlag', '1');
186+
if (config.isEV) params.set('ev', '1');
178187
return params.toString();
179188
}
180189

@@ -421,7 +430,7 @@ export default function PlateGenerator() {
421430
<select
422431
value={config.country}
423432
onChange={(e) => {
424-
const newCountry = e.target.value as EUCountry;
433+
const newCountry = e.target.value as Country;
425434
const countryDefaults = getCountryDefaults(newCountry);
426435
setConfig(prev => ({
427436
...prev,
@@ -430,7 +439,7 @@ export default function PlateGenerator() {
430439
backgroundColor: countryDefaults.backgroundColor,
431440
rightBandText: countryDefaults.rightBandText,
432441
// Reset state to appropriate default when switching countries
433-
state: newCountry === 'A' ? 'W' : newCountry === 'H' ? 'HU' : newCountry === 'SK' ? 'SK' : newCountry === 'CH' ? 'ZH' : 'BY',
442+
state: newCountry === 'A' ? 'W' : newCountry === 'H' ? 'HU' : newCountry === 'SK' ? 'SK' : newCountry === 'CH' ? 'ZH' : 'NW',
434443
// Reset cityCode for Swiss plates to canton code
435444
cityCode: newCountry === 'CH' ? 'ZH' : prev.cityCode,
436445
numbers: newCountry === 'CH' ? '123456' : prev.numbers,
@@ -442,7 +451,7 @@ export default function PlateGenerator() {
442451
>
443452
{Object.keys(t.countries).map((code) => (
444453
<option key={code} value={code}>
445-
{COUNTRY_FLAGS[code as EUCountry]} {t.countries[code as keyof typeof t.countries]} ({code})
454+
{COUNTRY_FLAGS[code as Country]} {t.countries[code as keyof typeof t.countries]} ({code})
446455
</option>
447456
))}
448457
</select>
@@ -791,6 +800,30 @@ export default function PlateGenerator() {
791800
</select>
792801
</div>
793802
)}
803+
804+
{/* UK Options */}
805+
{config.country === 'GB' && (
806+
<div className="flex flex-col gap-3">
807+
<label className="flex items-center gap-3 text-gray-700 dark:text-gray-300 cursor-pointer group">
808+
<input
809+
type="checkbox"
810+
checked={config.showUKFlag}
811+
onChange={(e) => handleChange('showUKFlag', e.target.checked)}
812+
className="w-5 h-5 rounded-lg text-blue-600 focus:ring-blue-500 border-gray-300 dark:border-gray-600 transition-all"
813+
/>
814+
<span className="group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">🇬🇧 Show UK Flag + Text</span>
815+
</label>
816+
<label className="flex items-center gap-3 text-gray-700 dark:text-gray-300 cursor-pointer group">
817+
<input
818+
type="checkbox"
819+
checked={config.isEV}
820+
onChange={(e) => handleChange('isEV', e.target.checked)}
821+
className="w-5 h-5 rounded-lg text-green-600 focus:ring-green-500 border-gray-300 dark:border-gray-600 transition-all"
822+
/>
823+
<span className="group-hover:text-green-600 dark:group-hover:text-green-400 transition-colors">⚡ Electric Vehicle (Green Strip)</span>
824+
</label>
825+
</div>
826+
)}
794827
</div>
795828

796829
{/* German-specific options - collapsible */}

src/components/UKBand.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import Image from 'next/image';
5+
6+
interface UKBandProps {
7+
scale?: number;
8+
height?: number | string;
9+
showFlag: boolean; // Show UK flag + text
10+
isEV: boolean; // Green background for EV
11+
borderRadius?: number;
12+
}
13+
14+
export default function UKBand({ scale = 1, height, showFlag, isEV, borderRadius = 5 }: UKBandProps) {
15+
const width = 40 * scale;
16+
const defaultHeight = 110 * scale;
17+
const actualHeight = height || defaultHeight;
18+
const radius = borderRadius * scale;
19+
20+
// Green only strip (EV without flag)
21+
if (!showFlag && isEV) {
22+
return (
23+
<div
24+
style={{
25+
width: `${width}px`,
26+
height: actualHeight,
27+
backgroundColor: '#00A651',
28+
borderRadius: `${radius}px 0 0 ${radius}px`,
29+
}}
30+
/>
31+
);
32+
}
33+
34+
// Flag + UK text (green for EV, transparent otherwise)
35+
return (
36+
<div
37+
style={{
38+
display: 'flex',
39+
flexDirection: 'column',
40+
alignItems: 'center',
41+
justifyContent: 'center',
42+
gap: `${6 * scale}px`,
43+
width: `${width}px`,
44+
height: actualHeight,
45+
backgroundColor: isEV ? '#00A651' : 'transparent',
46+
borderRadius: `${radius}px 0 0 ${radius}px`,
47+
padding: `${6 * scale}px ${2 * scale}px`,
48+
}}
49+
>
50+
{/* 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+
}}>
59+
<Image
60+
src="/flags/uk.svg"
61+
alt="UK Flag"
62+
fill
63+
style={{ objectFit: 'cover' }}
64+
/>
65+
</div>
66+
67+
{/* UK text */}
68+
<span
69+
style={{
70+
color: isEV ? '#FFFFFF' : '#000000',
71+
fontSize: `${18 * scale}px`,
72+
fontWeight: 'bold',
73+
fontFamily: 'Arial, sans-serif',
74+
letterSpacing: '1px',
75+
textShadow: isEV ? `0 ${1 * scale}px ${2 * scale}px rgba(0,0,0,0.3)` : 'none',
76+
}}
77+
>
78+
UK
79+
</span>
80+
</div>
81+
);
82+
}

0 commit comments

Comments
 (0)