@@ -22,6 +22,8 @@ const COLOR_PRESETS = [
2222 { label : 'Matrix' , value : 'matrix' , colors : [ '#00ff41' , '#008f11' , '#003b00' , '#0d0208' ] } ,
2323 { label : 'Deep Sea' , value : 'ocean' , colors : [ '#0ea5e9' , '#2dd4bf' , '#1e1b4b' , '#f0f9ff' ] } ,
2424 { label : 'Monochrome' , value : 'mono' , colors : [ '#ffffff' , '#a3a3a3' , '#404040' , '#000000' ] } ,
25+ { label : 'Pip-Boy Amber' , value : 'pipboy_amber' , colors : [ '#ffb642' , '#8a5d00' , '#211500' , '#050505' ] } ,
26+ { label : 'Pip-Boy Green' , value : 'pipboy_green' , colors : [ '#18e73c' , '#005c00' , '#001a00' , '#050505' ] } ,
2527 { label : 'Custom' , value : 'custom' , colors : [ ] } ,
2628] ;
2729
@@ -39,6 +41,7 @@ const STYLES = [
3941 { label : 'Isometric Grid' , value : 'iso' } ,
4042 { label : 'Organic Noise' , value : 'noise' } ,
4143 { label : 'Type Matrix' , value : 'typematrix' } ,
44+ { label : 'Pip-Boy Interface' , value : 'pipboy' } ,
4245] ;
4346
4447const RESOLUTIONS = [
@@ -446,9 +449,179 @@ const WallpaperEnginePage = () => {
446449 ctx . strokeRect ( x - 5 , y - fontSize , ctx . measureText ( text ) . width + 10 , fontSize + 5 ) ;
447450 }
448451 }
449- }
452+ } else if ( style === 'pipboy' ) {
453+ const mainColor = colors [ 0 ] ;
454+ const bgColor = colors [ colors . length - 1 ] ;
455+
456+ // 1. CRT Background & Scanlines
457+ ctx . fillStyle = bgColor ;
458+ ctx . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
459+
460+ ctx . strokeStyle = mainColor ;
461+ ctx . lineWidth = 0.5 ;
462+ ctx . globalAlpha = 0.1 ;
463+ for ( let y = 0 ; y < canvas . height ; y += 4 ) {
464+ ctx . beginPath ( ) ; ctx . moveTo ( 0 , y ) ; ctx . lineTo ( canvas . width , y ) ; ctx . stroke ( ) ;
465+ }
466+
467+ // 2. Fixed Header UI
468+ ctx . globalAlpha = 1.0 ;
469+ ctx . fillStyle = mainColor ;
470+ ctx . font = `bold ${ Math . floor ( 24 * ( canvas . width / 1920 ) ) } px monospace` ;
471+ const tabs = [ 'STAT' , 'INV' , 'DATA' , 'MAP' , 'RADIO' ] ;
472+ tabs . forEach ( ( tab , i ) => {
473+ const x = 100 + i * ( canvas . width / 6 ) ;
474+ ctx . fillText ( tab , x , 80 ) ;
475+ if ( tab === 'DATA' ) { // Active tab indicator
476+ ctx . fillRect ( x - 10 , 90 , ctx . measureText ( tab ) . width + 20 , 4 ) ;
477+ }
478+ } ) ;
479+ ctx . fillRect ( 50 , 100 , canvas . width - 100 , 2 ) ;
480+
481+ // 3. Central Radar / Map Element
482+ const centerX = canvas . width * 0.75 ;
483+ const centerY = canvas . height * 0.5 ;
484+ const radius = 200 * ( canvas . width / 1920 ) ;
485+
486+ ctx . strokeStyle = mainColor ;
487+ ctx . lineWidth = 2 ;
488+ ctx . beginPath ( ) ;
489+ ctx . arc ( centerX , centerY , radius , 0 , Math . PI * 2 ) ;
490+ ctx . stroke ( ) ;
491+
492+ ctx . globalAlpha = 0.3 ;
493+ for ( let r = 1 ; r < 4 ; r ++ ) {
494+ ctx . beginPath ( ) ;
495+ ctx . arc ( centerX , centerY , ( radius / 4 ) * r , 0 , Math . PI * 2 ) ;
496+ ctx . stroke ( ) ;
497+ }
498+
499+ // Blips
500+ ctx . globalAlpha = 0.8 ;
501+ for ( let i = 0 ; i < 5 ; i ++ ) {
502+ const bx = centerX + ( nextRand ( ) - 0.5 ) * radius * 1.5 ;
503+ const by = centerY + ( nextRand ( ) - 0.5 ) * radius * 1.5 ;
504+ ctx . fillRect ( bx , by , 8 , 8 ) ;
505+ }
506+
507+ // 4. Data Listing
508+ ctx . font = `${ Math . floor ( 18 * ( canvas . width / 1920 ) ) } px monospace` ;
509+ const entries = [
510+ 'FEZ_CODEX_OS v4.0.2' ,
511+ 'MEMORY_BANK: OK' ,
512+ 'RAD_LEVEL: 0.02 mSv' ,
513+ 'LOCATION: NEW_VEGAS_STRIP' ,
514+ 'SIGNAL: INTERCEPTED' ,
515+ 'ENCRYPTION: ACTIVE' ,
516+ 'USER: COURIER_SIX'
517+ ] ;
518+
519+ entries . forEach ( ( text , i ) => {
520+ ctx . globalAlpha = 0.9 ;
521+ ctx . fillText ( `> ${ text } ` , 100 , 250 + i * 50 ) ;
522+
523+ // Progress Bars
524+ ctx . globalAlpha = 0.2 ;
525+ ctx . fillRect ( 100 , 260 + i * 50 , 300 , 10 ) ;
526+ ctx . globalAlpha = 0.7 ;
527+ ctx . fillRect ( 100 , 260 + i * 50 , nextRand ( ) * 300 , 10 ) ;
528+ } ) ;
529+
530+ // 5. Vertical Scan Bar
531+ const scanY = ( Date . now ( ) / 20 ) % canvas . height ;
532+ const grad = ctx . createLinearGradient ( 0 , scanY - 50 , 0 , scanY ) ;
533+ grad . addColorStop ( 0 , 'transparent' ) ;
534+ grad . addColorStop ( 1 , mainColor ) ;
535+ ctx . fillStyle = grad ;
536+ ctx . globalAlpha = 0.15 ;
537+ ctx . fillRect ( 0 , scanY - 100 , canvas . width , 100 ) ;
538+
539+ // 6. Corner Accents
540+
541+ ctx . globalAlpha = 1.0 ;
542+
543+ ctx . lineWidth = 4 ;
544+
545+ const cp = 40 ;
546+
547+ // Top Left
548+
549+ ctx . beginPath ( ) ; ctx . moveTo ( cp , cp + 50 ) ; ctx . lineTo ( cp , cp ) ; ctx . lineTo ( cp + 50 , cp ) ; ctx . stroke ( ) ;
550+
551+ // Bottom Right
552+
553+ ctx . beginPath ( ) ; ctx . moveTo ( canvas . width - cp , canvas . height - cp - 50 ) ; ctx . lineTo ( canvas . width - cp , canvas . height - cp ) ; ctx . lineTo ( canvas . width - cp - 50 , canvas . height - cp ) ; ctx . stroke ( ) ;
554+
555+ // 7. Horizontal Compass (Bottom)
556+
557+ const compassY = canvas . height - 150 ;
558+
559+ ctx . globalAlpha = 0.6 ;
560+
561+ ctx . fillRect ( 100 , compassY , canvas . width - 200 , 2 ) ;
562+
563+ ctx . font = `${ Math . floor ( 14 * ( canvas . width / 1920 ) ) } px monospace` ;
564+
565+ for ( let i = 0 ; i <= 20 ; i ++ ) {
566+ const x = 100 + i * ( ( canvas . width - 200 ) / 20 ) ;
567+
568+ const h = i % 5 === 0 ? 15 : 8 ;
569+
570+ ctx . fillRect ( x , compassY - h , 2 , h ) ;
571+
572+ if ( i % 5 === 0 ) {
573+ const dir = [ 'W' , 'NW' , 'N' , 'NE' , 'E' ] [ i / 5 ] ;
574+
575+ if ( dir ) ctx . fillText ( dir , x - 5 , compassY - 25 ) ;
576+ }
577+ }
578+
579+ // 8. Condition Monitor (Bottom Left Area)
580+
581+ const vbX = 100 ;
582+
583+ const vbY = 650 ;
584+
585+ ctx . globalAlpha = 1.0 ;
586+
587+ ctx . strokeRect ( vbX , vbY , 200 , 200 ) ;
588+
589+ ctx . font = `bold ${ Math . floor ( 12 * ( canvas . width / 1920 ) ) } px monospace` ;
590+
591+ ctx . fillText ( "F.C.D.X. STATUS" , vbX , vbY - 10 ) ;
592+
593+ // Simple procedural "Vault Boy" wireframe head
594+
595+ ctx . beginPath ( ) ;
596+
597+ ctx . arc ( vbX + 100 , vbY + 80 , 40 , 0 , Math . PI * 2 ) ; // Head
598+
599+ ctx . moveTo ( vbX + 100 , vbY + 120 ) ; ctx . lineTo ( vbX + 100 , vbY + 180 ) ; // Body
600+
601+ ctx . moveTo ( vbX + 100 , vbY + 140 ) ; ctx . lineTo ( vbX + 60 , vbY + 110 ) ; // Arm L
602+
603+ ctx . moveTo ( vbX + 100 , vbY + 140 ) ; ctx . lineTo ( vbX + 140 , vbY + 110 ) ; // Arm R
604+
605+ ctx . stroke ( ) ;
606+
607+ ctx . fillText ( "DISCONN" , vbX + 50 , vbY + 195 ) ;
608+
609+ // 9. Micro Metadata
610+
611+ ctx . font = `${ Math . floor ( 10 * ( canvas . width / 1920 ) ) } px monospace` ;
612+
613+ ctx . globalAlpha = 0.4 ;
614+
615+ ctx . fillText ( "AP: 85/85" , canvas . width - 200 , canvas . height - 80 ) ;
616+
617+ ctx . fillText ( "HP: 240/240" , canvas . width - 200 , canvas . height - 60 ) ;
618+
619+ ctx . fillText ( "VOLTAGE: 1.2V" , 100 , canvas . height - 80 ) ;
620+
621+ ctx . fillText ( "OS_BUILD: 0.8.7" , 100 , canvas . height - 60 ) ;
622+ }
450623
451- // Noise/Grain
624+ // Noise/Grain
452625 if ( noise > 0 ) {
453626 const imageData = ctx . getImageData ( 0 , 0 , canvas . width , canvas . height ) ;
454627 const data = imageData . data ;
0 commit comments