@@ -8,25 +8,23 @@ interface BadgeListProps {
88 badges : Badge [ ] ;
99}
1010
11- // バッジタイプに応じた画像マッピング関数
12- const getBadgeImage = ( badge : Badge ) => {
13- // まずtypeで判別
14- if ( badge . type === 'trophy' ) return '/images/badges/Trophy.png' ;
15- if ( badge . type === 'gold' ) return '/images/badges/AI_basic.png' ;
16- if ( badge . type === 'silver' ) return '/images/badges/Web_base.png' ;
17- if ( badge . type === 'bronze' ) return '/images/badges/Seed.png' ;
18-
19- // IDや名前でも判別(後方互換性)
20- if ( badge . id . includes ( 'web' ) ) return '/images/badges/Web_base.png' ;
21- if ( badge . id . includes ( 'ai' ) ) return '/images/badges/AI_basic.png' ;
22- if ( badge . name . includes ( 'Seed' ) ) return '/images/badges/Seed.png' ;
23- if ( badge . name . includes ( 'Trophy' ) || badge . id . includes ( 'trophy' ) ) return '/images/badges/Trophy.png' ;
24-
25- // デフォルト
26- return '/images/badges/Trophy.png' ;
11+ // カテゴリ色定義
12+ const CATEGORY_COLORS : Record < string , string > = {
13+ web : "#55aaff" ,
14+ ai : "#e8b849" ,
15+ security : "#e85555" ,
16+ infra : "#55cc55" ,
17+ design : "#cc66dd" ,
2718} ;
2819
2920export const BadgeList : React . FC < BadgeListProps > = ( { badges } ) => {
21+ // ソート: トロフィー → 初級 → 中級 → 上級
22+ const sortedBadges = [ ...badges ] . sort ( ( a , b ) => {
23+ if ( a . sortOrder !== b . sortOrder ) {
24+ return a . sortOrder - b . sortOrder ;
25+ }
26+ return parseInt ( a . id ) - parseInt ( b . id ) ;
27+ } ) ;
3028 return (
3129 < div className = "mx-auto w-full max-w-5xl px-4" >
3230 < div className = "mb-4 flex items-center gap-4 pl-2" >
@@ -53,8 +51,8 @@ export const BadgeList: React.FC<BadgeListProps> = ({ badges }) => {
5351 { /* バッジ一覧を横スクロール表示 */ }
5452 { badges . length > 0 ? (
5553 < div className = "flex min-w-max gap-6 items-end pb-4" >
56- { badges . map ( ( badge , index ) => {
57- const isTrophy = badge . name . includes ( 'Trophy' ) || badge . id . includes ( 'trophy' ) ;
54+ { sortedBadges . map ( ( badge , index ) => {
55+ const isTrophy = badge . type === 'trophy' ;
5856 const sizeClass = isTrophy ? 'h-80 w-80' : 'h-60 w-60' ;
5957 const animationDelay = index * 0.2 ;
6058
@@ -63,20 +61,62 @@ export const BadgeList: React.FC<BadgeListProps> = ({ badges }) => {
6361 key = { badge . id }
6462 className = "flex flex-col items-center gap-2 group"
6563 >
66- < span className = "text-xl font-bold text-[#1a4023] opacity-0 group-hover:opacity-100 group-hover:animate-bounce transition-opacity" > ▼</ span >
64+ < span className = "text-xl font-bold text-[#2C5F2D] opacity-0 group-hover:opacity-100 group-hover:animate-bounce transition-opacity" >
65+ ▼
66+ </ span >
6767 < div
68- className = { `relative ${ sizeClass } filter drop-shadow-[4px_4px_0_rgba(26,64,35 ,0.2)]` }
68+ className = { `relative ${ sizeClass } filter drop-shadow-[4px_4px_0_rgba(0,0,0 ,0.2)]` }
6969 style = { {
7070 animation : 'float 2s steps(2) infinite' ,
7171 animationDelay : `${ animationDelay } s` ,
7272 } }
7373 >
74- < Image
75- src = { getBadgeImage ( badge ) }
76- alt = { badge . name }
77- fill
78- className = "object-contain"
79- />
74+ { /* 光の粒エフェクト(ランクバッジのみ) */ }
75+ { ! isTrophy && badge . category && (
76+ < >
77+ { [ ...Array ( 16 ) ] . map ( ( _ , i ) => {
78+ const sparkleColor = CATEGORY_COLORS [ badge . category ! ] ;
79+ // 不規則な遅延とポジション
80+ const delays = [
81+ 0 , 0.3 , 0.7 , 1.1 , 0.5 , 0.9 , 1.3 , 0.2 , 0.8 , 1.0 , 0.4 ,
82+ 1.2 , 0.6 , 1.4 , 0.1 , 1.5 ,
83+ ] ;
84+ const positions = [
85+ 5 , 15 , 25 , 35 , 45 , 55 , 65 , 75 , 10 , 20 , 30 , 40 , 50 , 60 ,
86+ 70 , 80 ,
87+ ] ;
88+ const delay = delays [ i ] ;
89+ const leftPosition = positions [ i ] ;
90+
91+ return (
92+ < div
93+ key = { i }
94+ className = "absolute w-6 h-6 rounded-full"
95+ style = { {
96+ backgroundColor : sparkleColor ,
97+ left : `${ leftPosition } %` ,
98+ bottom : 0 ,
99+ opacity : 0.7 ,
100+ boxShadow : `0 0 10px ${ sparkleColor } ` ,
101+ animation : "sparkle-rise 3s ease-in-out infinite" ,
102+ animationDelay : `${ delay } s` ,
103+ zIndex : 1 ,
104+ } }
105+ />
106+ ) ;
107+ } ) }
108+ </ >
109+ ) }
110+
111+ { /* バッジ画像 */ }
112+ < div className = "relative w-full h-full" style = { { zIndex : 2 } } >
113+ < Image
114+ src = { badge . image }
115+ alt = { badge . name }
116+ fill
117+ className = "object-contain"
118+ />
119+ </ div >
80120 </ div >
81121 < span className = "mt-2 text-center text-sm font-medium text-[#1a4023]" >
82122 { badge . name }
0 commit comments