1- import { useGLTF , Text , Image } from '@react-three/drei'
1+ import { useGLTF , Text , Image , Html } from '@react-three/drei'
22import { useLoader , useFrame } from '@react-three/fiber'
33import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'
44import * as THREE from 'three'
55import { useMemo , useState } from 'react'
66import { useOverlay } from '../../context/OverlayContext'
7+ import { useMusic } from '../../context/MusicContext'
78import { DEFAULT_FONT } from '../../constants/fonts'
89
910// MacBook component with Gemini app
@@ -96,36 +97,155 @@ const XiaomiDesktopIcon = ({
9697function XiaomiMonitor ( { position, rotation, scale } : { position : [ number , number , number ] , rotation : [ number , number , number ] , scale : number } ) {
9798 const { scene } = useGLTF ( '/models/xiaomi_4k_27_monitor/scene.gltf' )
9899 const { setOverlay } = useOverlay ( )
100+ const { musicActive, setMusicActive } = useMusic ( )
99101
100102 return (
101103 < group position = { position } rotation = { rotation } scale = { scale } >
102104 < primitive object = { scene . clone ( ) } scale = { 3.015 } position = { [ 0 , 0.3 , 0 ] } />
103105 { /* Screen Content - Desktop Icons */ }
104106 < group position = { [ 0 , 0.3 , 0.08 ] } >
105- < XiaomiDesktopIcon
106- position = { [ - 0.5 , 0.15 , 0 ] }
107- iconUrl = "/textures/logos/githublogo.png"
108- label = "GitHub"
109- onClick = { ( ) => window . open ( 'https://github.com/sametuca' , '_blank' ) }
110- />
111- < XiaomiDesktopIcon
112- position = { [ - 0.15 , 0.15 , 0 ] }
113- iconUrl = "/textures/logos/mediumlogo.png"
114- label = "Medium"
115- onClick = { ( ) => window . open ( 'https://medium.com/@sametuca' , '_blank' ) }
116- />
117- < XiaomiDesktopIcon
118- position = { [ 0.2 , 0.15 , 0 ] }
119- iconUrl = "/textures/logos/certificatelogo.png"
120- label = "Certificate"
121- onClick = { ( ) => setOverlay ( 'certificates' ) }
122- />
123- < XiaomiDesktopIcon
124- position = { [ 0.55 , 0.15 , 0 ] }
125- iconUrl = "/textures/logos/contactlogo.png"
126- label = "About"
127- onClick = { ( ) => setOverlay ( 'contact' ) }
128- />
107+ { /* Black Background */ }
108+ < mesh position = { [ 0 , 0 , - 0.01 ] } >
109+ < planeGeometry args = { [ 1.4 , 0.8 ] } />
110+ < meshBasicMaterial color = "#000000" />
111+ </ mesh >
112+
113+ { /* VMware Window Title Bar */ }
114+ < mesh position = { [ 0 , 0.37 , 0 ] } >
115+ < planeGeometry args = { [ 1.4 , 0.05 ] } />
116+ < meshBasicMaterial color = "#2d2d2d" />
117+ </ mesh >
118+
119+ { /* VMware Icon */ }
120+ < mesh position = { [ - 0.65 , 0.37 , 0.001 ] } >
121+ < planeGeometry args = { [ 0.025 , 0.025 ] } />
122+ < meshBasicMaterial color = "#00a8e1" />
123+ </ mesh >
124+
125+ { /* VMware Title Text */ }
126+ < Text
127+ position = { [ - 0.35 , 0.37 , 0.001 ] }
128+ fontSize = { 0.02 }
129+ color = "#ffffff"
130+ anchorX = "center"
131+ font = { DEFAULT_FONT }
132+ >
133+ VMware Workstation Pro
134+ </ Text >
135+
136+ { /* Window Control Buttons */ }
137+ < group position = { [ 0.62 , 0.37 , 0.001 ] } >
138+ { /* Minimize */ }
139+ < mesh position = { [ - 0.06 , 0 , 0 ] } >
140+ < planeGeometry args = { [ 0.025 , 0.025 ] } />
141+ < meshBasicMaterial color = "#404040" />
142+ </ mesh >
143+ < Text position = { [ - 0.06 , - 0.003 , 0.001 ] } fontSize = { 0.015 } color = "#ffffff" > _</ Text >
144+
145+ { /* Maximize */ }
146+ < mesh position = { [ - 0.03 , 0 , 0 ] } >
147+ < planeGeometry args = { [ 0.025 , 0.025 ] } />
148+ < meshBasicMaterial color = "#404040" />
149+ </ mesh >
150+ < Text position = { [ - 0.03 , 0 , 0.001 ] } fontSize = { 0.015 } color = "#ffffff" > □</ Text >
151+
152+ { /* Close */ }
153+ < mesh position = { [ 0 , 0 , 0 ] } >
154+ < planeGeometry args = { [ 0.025 , 0.025 ] } />
155+ < meshBasicMaterial color = "#e81123" />
156+ </ mesh >
157+ < Text position = { [ 0 , 0 , 0.001 ] } fontSize = { 0.015 } color = "#ffffff" > ✕</ Text >
158+ </ group >
159+
160+ { /* YouTube Music Player - Show when music is active */ }
161+ { musicActive && (
162+ < >
163+ < Html position = { [ 0 , 0 , 0.01 ] } transform occlude distanceFactor = { 0.7 } >
164+ < div style = { { width : '560px' , height : '314px' , background : '#000' , overflow : 'hidden' , pointerEvents : 'auto' } } >
165+ < iframe
166+ width = "560"
167+ height = "314"
168+ src = "https://www.youtube.com/embed/YVowLNuV4Zk?autoplay=1& start = 214 "
169+ title = "YouTube Music Player"
170+ frameBorder = "0"
171+ allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
172+ allowFullScreen
173+ />
174+ </ div >
175+ </ Html >
176+ { /* Close Button */ }
177+ < mesh position = { [ 0.65 , 0.32 , 0.02 ] } onClick = { ( e ) => { e . stopPropagation ( ) ; setMusicActive ( false ) } } >
178+ < planeGeometry args = { [ 0.06 , 0.06 ] } />
179+ < meshBasicMaterial color = "#ff0000" />
180+ </ mesh >
181+ < Text position = { [ 0.65 , 0.32 , 0.021 ] } fontSize = { 0.04 } color = "white" anchorX = "center" font = { DEFAULT_FONT } >
182+ ✕
183+ </ Text >
184+ </ >
185+ ) }
186+
187+ { /* Desktop Icons - Only show when music is not active */ }
188+ { ! musicActive && (
189+ < >
190+ < XiaomiDesktopIcon
191+ position = { [ - 0.5 , 0.15 , 0 ] }
192+ iconUrl = "/textures/logos/githublogo.png"
193+ label = "GitHub"
194+ onClick = { ( ) => window . open ( 'https://github.com/sametuca' , '_blank' ) }
195+ />
196+ < XiaomiDesktopIcon
197+ position = { [ - 0.15 , 0.15 , 0 ] }
198+ iconUrl = "/textures/logos/mediumlogo.png"
199+ label = "Medium"
200+ onClick = { ( ) => window . open ( 'https://medium.com/@sametuca' , '_blank' ) }
201+ />
202+ < XiaomiDesktopIcon
203+ position = { [ 0.2 , 0.15 , 0 ] }
204+ iconUrl = "/textures/logos/certificatelogo.png"
205+ label = "Certificate"
206+ onClick = { ( ) => setOverlay ( 'certificates' ) }
207+ />
208+ < XiaomiDesktopIcon
209+ position = { [ 0.55 , 0.15 , 0 ] }
210+ iconUrl = "/textures/logos/contactlogo.png"
211+ label = "About"
212+ onClick = { ( ) => setOverlay ( 'contact' ) }
213+ />
214+ </ >
215+ ) }
216+
217+ { /* Taskbar */ }
218+ < mesh position = { [ 0 , - 0.36 , 0 ] } >
219+ < planeGeometry args = { [ 1.4 , 0.06 ] } />
220+ < meshBasicMaterial color = "#1a1a1a" />
221+ </ mesh >
222+
223+ { /* Start Button - Opens YouTube Music */ }
224+ < group position = { [ - 0.63 , - 0.36 , 0.001 ] } onClick = { ( e ) => { e . stopPropagation ( ) ; setMusicActive ( true ) } } >
225+ < mesh >
226+ < planeGeometry args = { [ 0.08 , 0.04 ] } />
227+ < meshBasicMaterial color = "#0078d4" />
228+ </ mesh >
229+ < Text
230+ fontSize = { 0.025 }
231+ color = "#ffffff"
232+ anchorX = "center"
233+ font = { DEFAULT_FONT }
234+ >
235+ ⊞
236+ </ Text >
237+ </ group >
238+
239+ { /* Clock */ }
240+ < Text
241+ position = { [ 0.6 , - 0.36 , 0.001 ] }
242+ fontSize = { 0.018 }
243+ color = "#ffffff"
244+ anchorX = "center"
245+ font = { DEFAULT_FONT }
246+ >
247+ { new Date ( ) . toLocaleTimeString ( 'tr-TR' , { hour : '2-digit' , minute : '2-digit' } ) }
248+ </ Text >
129249 </ group >
130250 </ group >
131251 )
@@ -134,42 +254,161 @@ function XiaomiMonitor({ position, rotation, scale }: { position: [number, numbe
134254// Xiaomi Code Monitor component - for tech stack icons
135255function XiaomiCodeMonitor ( { position, rotation, scale } : { position : [ number , number , number ] , rotation : [ number , number , number ] , scale : number } ) {
136256 const { scene } = useGLTF ( '/models/xiaomi_4k_27_monitor/scene.gltf' )
257+ const { musicActive, setMusicActive } = useMusic ( )
137258
138259 return (
139260 < group position = { position } rotation = { rotation } scale = { scale } >
140261 < primitive object = { scene . clone ( ) } scale = { 3.015 } position = { [ 0 , 0.3 , 0 ] } />
141262 { /* Screen Content - Tech Stack Icons */ }
142263 < group position = { [ 0 , 0.3 , 0.08 ] } >
143- < XiaomiDesktopIcon
144- position = { [ - 0.6 , 0.2 , 0 ] }
145- iconUrl = "/textures/logos/reactlogo.webp"
146- label = "React"
147- onClick = { ( ) => window . open ( 'https://react.dev' , '_blank' ) }
148- />
149- < XiaomiDesktopIcon
150- position = { [ - 0.25 , 0.2 , 0 ] }
151- iconUrl = "/textures/logos/typescriptlogo.png"
152- label = "TypeScript"
153- onClick = { ( ) => window . open ( 'https://typescriptlang.org' , '_blank' ) }
154- />
155- < XiaomiDesktopIcon
156- position = { [ 0.1 , 0.2 , 0 ] }
157- iconUrl = "/textures/logos/nodejslogo.png"
158- label = "Node.js"
159- onClick = { ( ) => window . open ( 'https://nodejs.org' , '_blank' ) }
160- />
161- < XiaomiDesktopIcon
162- position = { [ 0.45 , 0.2 , 0 ] }
163- iconUrl = "/textures/logos/threejslogo.png"
164- label = "Three.js"
165- onClick = { ( ) => window . open ( 'https://threejs.org' , '_blank' ) }
166- />
167- < XiaomiDesktopIcon
168- position = { [ - 0.6 , - 0.1 , 0 ] }
169- iconUrl = "/textures/logos/nextjslogo.webp"
170- label = "Next.js"
171- onClick = { ( ) => window . open ( 'https://nextjs.org' , '_blank' ) }
172- />
264+ { /* Black Background */ }
265+ < mesh position = { [ 0 , 0 , - 0.01 ] } >
266+ < planeGeometry args = { [ 1.4 , 0.8 ] } />
267+ < meshBasicMaterial color = "#000000" />
268+ </ mesh >
269+
270+ { /* VMware Window Title Bar */ }
271+ < mesh position = { [ 0 , 0.37 , 0 ] } >
272+ < planeGeometry args = { [ 1.4 , 0.05 ] } />
273+ < meshBasicMaterial color = "#2d2d2d" />
274+ </ mesh >
275+
276+ { /* VMware Icon */ }
277+ < mesh position = { [ - 0.65 , 0.37 , 0.001 ] } >
278+ < planeGeometry args = { [ 0.025 , 0.025 ] } />
279+ < meshBasicMaterial color = "#00a8e1" />
280+ </ mesh >
281+
282+ { /* VMware Title Text */ }
283+ < Text
284+ position = { [ - 0.35 , 0.37 , 0.001 ] }
285+ fontSize = { 0.02 }
286+ color = "#ffffff"
287+ anchorX = "center"
288+ font = { DEFAULT_FONT }
289+ >
290+ VMware Workstation Pro
291+ </ Text >
292+
293+ { /* Window Control Buttons */ }
294+ < group position = { [ 0.62 , 0.37 , 0.001 ] } >
295+ { /* Minimize */ }
296+ < mesh position = { [ - 0.06 , 0 , 0 ] } >
297+ < planeGeometry args = { [ 0.025 , 0.025 ] } />
298+ < meshBasicMaterial color = "#404040" />
299+ </ mesh >
300+ < Text position = { [ - 0.06 , - 0.003 , 0.001 ] } fontSize = { 0.015 } color = "#ffffff" > _</ Text >
301+
302+ { /* Maximize */ }
303+ < mesh position = { [ - 0.03 , 0 , 0 ] } >
304+ < planeGeometry args = { [ 0.025 , 0.025 ] } />
305+ < meshBasicMaterial color = "#404040" />
306+ </ mesh >
307+ < Text position = { [ - 0.03 , 0 , 0.001 ] } fontSize = { 0.015 } color = "#ffffff" > □</ Text >
308+
309+ { /* Close */ }
310+ < mesh position = { [ 0 , 0 , 0 ] } >
311+ < planeGeometry args = { [ 0.025 , 0.025 ] } />
312+ < meshBasicMaterial color = "#e81123" />
313+ </ mesh >
314+ < Text position = { [ 0 , 0 , 0.001 ] } fontSize = { 0.015 } color = "#ffffff" > ✕</ Text >
315+ </ group >
316+
317+ { /* YouTube Music Player - Show when music is active */ }
318+ { musicActive && (
319+ < >
320+ < Html position = { [ 0 , 0 , 0.01 ] } transform occlude distanceFactor = { 0.7 } >
321+ < div style = { { width : '560px' , height : '314px' , background : '#000' , overflow : 'hidden' , pointerEvents : 'auto' } } >
322+ < iframe
323+ width = "560"
324+ height = "314"
325+ src = "https://www.youtube.com/embed/YVowLNuV4Zk?autoplay=1& start = 214 "
326+ title = "YouTube Music Player"
327+ frameBorder = "0"
328+ allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
329+ allowFullScreen
330+ />
331+ </ div >
332+ </ Html >
333+ { /* Close Button */ }
334+ < mesh position = { [ 0.65 , 0.32 , 0.02 ] } onClick = { ( e ) => { e . stopPropagation ( ) ; setMusicActive ( false ) } } >
335+ < planeGeometry args = { [ 0.06 , 0.06 ] } />
336+ < meshBasicMaterial color = "#ff0000" />
337+ </ mesh >
338+ < Text position = { [ 0.65 , 0.32 , 0.021 ] } fontSize = { 0.04 } color = "white" anchorX = "center" font = { DEFAULT_FONT } >
339+ ✕
340+ </ Text >
341+ </ >
342+ ) }
343+
344+ { /* Desktop Icons - Only show when music is not active */ }
345+ { ! musicActive && (
346+ < >
347+ < XiaomiDesktopIcon
348+ position = { [ - 0.6 , 0.2 , 0 ] }
349+ iconUrl = "/textures/logos/reactlogo.webp"
350+ label = "React"
351+ onClick = { ( ) => window . open ( 'https://react.dev' , '_blank' ) }
352+ />
353+ < XiaomiDesktopIcon
354+ position = { [ - 0.25 , 0.2 , 0 ] }
355+ iconUrl = "/textures/logos/typescriptlogo.png"
356+ label = "TypeScript"
357+ onClick = { ( ) => window . open ( 'https://typescriptlang.org' , '_blank' ) }
358+ />
359+ < XiaomiDesktopIcon
360+ position = { [ 0.1 , 0.2 , 0 ] }
361+ iconUrl = "/textures/logos/nodejslogo.png"
362+ label = "Node.js"
363+ onClick = { ( ) => window . open ( 'https://nodejs.org' , '_blank' ) }
364+ />
365+ < XiaomiDesktopIcon
366+ position = { [ 0.45 , 0.2 , 0 ] }
367+ iconUrl = "/textures/logos/threejslogo.png"
368+ label = "Three.js"
369+ onClick = { ( ) => window . open ( 'https://threejs.org' , '_blank' ) }
370+ />
371+ < XiaomiDesktopIcon
372+ position = { [ - 0.6 , - 0.1 , 0 ] }
373+ iconUrl = "/textures/logos/nextjslogo.webp"
374+ label = "Next.js"
375+ onClick = { ( ) => window . open ( 'https://nextjs.org' , '_blank' ) }
376+ />
377+ </ >
378+ ) }
379+
380+ { /* Taskbar */ }
381+ < mesh position = { [ 0 , - 0.36 , 0 ] } >
382+ < planeGeometry args = { [ 1.4 , 0.06 ] } />
383+ < meshBasicMaterial color = "#1a1a1a" />
384+ </ mesh >
385+
386+ { /* Start Button - Opens YouTube Music */ }
387+ < group position = { [ - 0.63 , - 0.36 , 0.001 ] } onClick = { ( e ) => { e . stopPropagation ( ) ; setMusicActive ( true ) } } >
388+ < mesh >
389+ < planeGeometry args = { [ 0.08 , 0.04 ] } />
390+ < meshBasicMaterial color = "#0078d4" />
391+ </ mesh >
392+ < Text
393+ fontSize = { 0.025 }
394+ color = "#ffffff"
395+ anchorX = "center"
396+ font = { DEFAULT_FONT }
397+ >
398+ ⊞
399+ </ Text >
400+ </ group >
401+
402+ { /* Clock */ }
403+ < Text
404+ position = { [ 0.6 , - 0.36 , 0.001 ] }
405+ fontSize = { 0.018 }
406+ color = "#ffffff"
407+ anchorX = "center"
408+ font = { DEFAULT_FONT }
409+ >
410+ { new Date ( ) . toLocaleTimeString ( 'tr-TR' , { hour : '2-digit' , minute : '2-digit' } ) }
411+ </ Text >
173412 </ group >
174413 </ group >
175414 )
0 commit comments