22
33import Image from 'next/image' ;
44
5+ // カテゴリ色定義
6+ const CATEGORY_COLORS : Record < string , string > = {
7+ web : "#55aaff" ,
8+ ai : "#e8b849" ,
9+ security : "#e85555" ,
10+ infra : "#55cc55" ,
11+ design : "#cc66dd"
12+ } ;
13+
14+ interface Badge {
15+ id : number ;
16+ name : string ;
17+ type : 'trophy' | 'rank' ;
18+ image : string ;
19+ category ?: keyof typeof CATEGORY_COLORS ;
20+ rankLevel ?: number ;
21+ sortOrder : number ; // トロフィー=1, 初級=2, 中級=3, 上級=4
22+ }
23+
524export function AcquiredBadges ( ) {
6- const badges = [
7- { id : 1 , name : 'Trophy' , image : '/images/badges/Trophy.png' } ,
8- { id : 2 , name : 'AI Basic' , image : '/images/badges/AI_basic .png' } ,
9- { id : 3 , name : 'Web Basic' , image : '/images/badges/Web_base .png' } ,
10- { id : 4 , name : 'Seed ' , image : '/images/badges/Seed .png' } ,
11- { id : 5 , name : 'AI Master ' , image : '/images/badges/AI_basic .png' } ,
12- { id : 6 , name : 'Web Advanced' , image : '/images/badges/Web_base .png' } ,
25+ const badges : Badge [ ] = [
26+ { id : 1 , name : 'Trophy' , type : 'trophy' , image : '/images/badges/Trophy.png' , sortOrder : 1 } ,
27+ { id : 2 , name : 'AI Basic' , type : 'rank' , image : '/images/ranks/rank_tree_1 .png' , category : 'ai' , rankLevel : 1 , sortOrder : 2 } ,
28+ { id : 3 , name : 'Web Basic' , type : 'rank' , image : '/images/ranks/rank_tree_1 .png' , category : 'web' , rankLevel : 1 , sortOrder : 2 } ,
29+ { id : 4 , name : 'Security Basic ' , type : 'rank' , image : '/images/ranks/rank_tree_1 .png' , category : 'security' , rankLevel : 1 , sortOrder : 2 } ,
30+ { id : 5 , name : 'AI Intermediate ' , type : 'rank' , image : '/images/ranks/rank_tree_3 .png' , category : 'ai' , rankLevel : 3 , sortOrder : 3 } ,
31+ { id : 6 , name : 'Web Advanced' , type : 'rank' , image : '/images/ranks/rank_tree_5 .png' , category : 'web' , rankLevel : 5 , sortOrder : 4 } ,
1332 ] ;
1433
34+ // ソート: トロフィー → 初級 → 中級 → 上級
35+ const sortedBadges = [ ...badges ] . sort ( ( a , b ) => {
36+ if ( a . sortOrder !== b . sortOrder ) {
37+ return a . sortOrder - b . sortOrder ;
38+ }
39+ return a . id - b . id ;
40+ } ) ;
41+
1542 return (
1643 < div className = "mt-8 font-sans" >
1744 < h3 className = "mb-4 text-2xl font-bold tracking-widest text-[#2C5F2D] [text-shadow:2px_2px_0_#a3e635]" >
@@ -28,10 +55,11 @@ export function AcquiredBadges() {
2855 >
2956 { /* Badges in horizontal scroll */ }
3057 < div className = "flex gap-6 min-w-max items-end pb-4" >
31- { badges . map ( ( badge , index ) => {
32- const isTrophy = badge . name === 'Trophy ' ;
58+ { sortedBadges . map ( ( badge , index ) => {
59+ const isTrophy = badge . type === 'trophy ' ;
3360 const sizeClass = isTrophy ? 'h-80 w-80' : 'h-60 w-60' ;
3461 const animationDelay = index * 0.2 ;
62+ const backgroundColor = badge . category ? CATEGORY_COLORS [ badge . category ] : 'transparent' ;
3563
3664 return (
3765 < div
@@ -46,12 +74,46 @@ export function AcquiredBadges() {
4674 animationDelay : `${ animationDelay } s` ,
4775 } }
4876 >
49- < Image
50- src = { badge . image }
51- alt = { badge . name }
52- fill
53- className = "object-contain"
54- />
77+ { /* 光の粒エフェクト(ランクバッジのみ) */ }
78+ { ! isTrophy && badge . category && (
79+ < >
80+ { [ ...Array ( 16 ) ] . map ( ( _ , i ) => {
81+ const sparkleColor = CATEGORY_COLORS [ badge . category ! ] ;
82+ // 不規則な遅延とポジション
83+ const delays = [ 0 , 0.3 , 0.7 , 1.1 , 0.5 , 0.9 , 1.3 , 0.2 , 0.8 , 1.0 , 0.4 , 1.2 , 0.6 , 1.4 , 0.1 , 1.5 ] ;
84+ const positions = [ 5 , 15 , 25 , 35 , 45 , 55 , 65 , 75 , 10 , 20 , 30 , 40 , 50 , 60 , 70 , 80 ] ;
85+ const delay = delays [ i ] ;
86+ const leftPosition = positions [ i ] ;
87+
88+ return (
89+ < div
90+ key = { i }
91+ className = "absolute w-6 h-6 rounded-full"
92+ style = { {
93+ backgroundColor : sparkleColor ,
94+ left : `${ leftPosition } %` ,
95+ bottom : 0 ,
96+ opacity : 0.7 ,
97+ boxShadow : `0 0 10px ${ sparkleColor } ` ,
98+ animation : 'sparkle-rise 3s ease-in-out infinite' ,
99+ animationDelay : `${ delay } s` ,
100+ zIndex : 1 ,
101+ } }
102+ />
103+ ) ;
104+ } ) }
105+ </ >
106+ ) }
107+
108+ { /* バッジ画像 */ }
109+ < div className = "relative w-full h-full" style = { { zIndex : 2 } } >
110+ < Image
111+ src = { badge . image }
112+ alt = { badge . name }
113+ fill
114+ className = "object-contain"
115+ />
116+ </ div >
55117 </ div >
56118 </ div >
57119 ) ;
0 commit comments