KBLLR commited on
Commit
bda8d6c
·
verified ·
1 Parent(s): 56abb95

can you load free models and environments from the threejs repo?

Browse files
Files changed (1) hide show
  1. index.html +371 -132
index.html CHANGED
@@ -12,72 +12,177 @@
12
  body {
13
  margin: 0;
14
  overflow: hidden;
15
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 
16
  }
17
  #canvas {
18
  display: block;
19
  }
20
  .transition-all {
21
- transition: all 0.3s ease;
 
 
 
 
 
 
22
  }
23
  .character-card:hover {
24
- transform: translateY(-5px);
25
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
 
26
  }
27
  .dialog-box {
28
- background: rgba(0, 0, 0, 0.8);
29
- backdrop-filter: blur(5px);
 
 
30
  }
31
  .character-preview {
32
  width: 100%;
33
  height: 200px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  background: rgba(255, 255, 255, 0.1);
35
- border-radius: 0.5rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  }
37
  </style>
38
  </head>
39
  <body class="bg-gray-900 text-white">
40
  <!-- Welcome Screen -->
41
- <div id="welcome-screen" class="fixed inset-0 flex items-center justify-center bg-gray-900 z-50 transition-all duration-500">
42
- <div class="text-center max-w-2xl p-8 bg-gray-800 rounded-xl shadow-2xl">
43
- <h1 class="text-5xl font-bold mb-6 bg-gradient-to-r from-purple-500 to-blue-500 bg-clip-text text-transparent">3D Character World</h1>
44
- <p class="text-xl mb-8 text-gray-300">Explore a vibrant 3D world with interactive characters. Select your avatar and meet others along your journey!</p>
45
- <button id="start-btn" class="px-8 py-3 bg-gradient-to-r from-purple-600 to-blue-600 rounded-full text-white font-bold text-lg hover:from-purple-700 hover:to-blue-700 transition-all transform hover:scale-105 shadow-lg">
46
- Begin Adventure
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  </div>
49
  </div>
50
  <!-- Character Selection Screen -->
51
- <div id="character-selection" class="fixed inset-0 bg-gray-900 z-40 transition-all duration-500 opacity-0 pointer-events-none">
52
- <div class="container mx-auto px-4 py-8 h-full flex flex-col">
53
- <h2 class="text-3xl font-bold mb-8 text-center">Choose Your Character</h2>
54
- <div class="flex-1 overflow-y-auto pb-24">
55
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
 
 
 
 
56
  <!-- Character cards will be dynamically inserted here -->
57
  </div>
58
  </div>
59
- <div class="fixed bottom-0 left-0 right-0 bg-gray-900 border-t border-gray-700 py-4 px-4 z-50">
60
  <div class="container mx-auto text-center">
61
- <button id="confirm-character" class="px-8 py-3 bg-green-600 rounded-full text-white font-bold text-lg hover:bg-green-700 transition-all transform hover:scale-105 shadow-lg opacity-0">
62
- Confirm Selection
 
 
 
 
 
63
  </button>
64
  </div>
65
  </div>
66
  </div>
67
  </div>
68
  <!-- World Selection Screen -->
69
- <div id="world-selection" class="fixed inset-0 bg-gray-900 z-30 transition-all duration-500 opacity-0 pointer-events-none">
70
- <div class="container mx-auto px-4 py-8 h-full flex flex-col">
71
- <h2 class="text-3xl font-bold mb-8 text-center">Select 5 Characters for Your World</h2>
72
- <div class="flex-1 overflow-y-auto pb-24">
73
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-6">
 
 
 
 
 
 
 
 
 
74
  <!-- World character cards will be dynamically inserted here -->
75
  </div>
76
  </div>
77
- <div class="fixed bottom-0 left-0 right-0 bg-gray-900 border-t border-gray-700 py-4 px-4 z-50">
78
  <div class="container mx-auto text-center">
79
- <button id="start-world" class="px-8 py-3 bg-green-600 rounded-full text-white font-bold text-lg hover:bg-green-700 transition-all transform hover:scale-105 shadow-lg opacity-0">
80
- Generate World
 
 
 
 
 
81
  </button>
82
  </div>
83
  </div>
@@ -146,20 +251,18 @@
146
  space: false
147
  }
148
  };
149
-
150
- // Sample character data (in a real app, these would be fetched from Google Cloud)
151
  const characterData = [
152
- { id: 1, name: "Warrior", modelUrl: "https://storage.googleapis.com/your-bucket-name/warrior.glb", color: "#EF4444", dialog: ["I fight for honor!", "The battlefield calls to me.", "Stay sharp!"] },
153
- { id: 2, name: "Mage", modelUrl: "https://storage.googleapis.com/your-bucket-name/mage.glb", color: "#3B82F6", dialog: ["Magic flows through me.", "The arcane arts are limitless.", "Knowledge is power."] },
154
- { id: 3, name: "Rogue", modelUrl: "https://storage.googleapis.com/your-bucket-name/rogue.glb", color: "#10B981", dialog: ["Shadows are my friends.", "Quick and quiet.", "Gold is always the answer."] },
155
- { id: 4, name: "Archer", modelUrl: "https://storage.googleapis.com/your-bucket-name/archer.glb", color: "#F59E0B", dialog: ["My arrows never miss.", "The wind guides my shots.", "Aim true!"] },
156
- { id: 5, name: "Cleric", modelUrl: "https://storage.googleapis.com/your-bucket-name/cleric.glb", color: "#8B5CF6", dialog: ["The light protects us.", "Healing is my calling.", "Have faith!"] },
157
- { id: 6, name: "Bard", modelUrl: "https://storage.googleapis.com/your-bucket-name/bard.glb", color: "#EC4899", dialog: ["Let me sing you a tale!", "Music soothes the soul.", "Every story deserves a song."] },
158
- { id: 7, name: "Monk", modelUrl: "https://storage.googleapis.com/your-bucket-name/monk.glb", color: "#6366F1", dialog: ["Inner peace is key.", "The body and mind are one.", "Discipline brings strength."] },
159
- { id: 8, name: "Paladin", modelUrl: "https://storage.googleapis.com/your-bucket-name/paladin.glb", color: "#F97316", dialog: ["Justice will prevail!", "By my oath, I protect.", "Evil shall not pass."] }
160
  ];
161
-
162
- // Three.js variables
163
  let scene, camera, renderer, controls;
164
  let mixer, clock, loader;
165
  let world;
@@ -290,7 +393,6 @@
290
  container.appendChild(card);
291
  });
292
  }
293
-
294
  function initThreeJS() {
295
  // Set up Three.js scene
296
  scene = new THREE.Scene();
@@ -301,9 +403,15 @@
301
  camera.position.set(0, 5, 10);
302
 
303
  // Renderer
304
- renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('canvas'), antialias: true });
 
 
 
 
 
305
  renderer.setSize(window.innerWidth, window.innerHeight);
306
  renderer.shadowMap.enabled = true;
 
307
 
308
  // Clock for animations
309
  clock = new THREE.Clock();
@@ -312,14 +420,20 @@
312
  loader = new THREE.GLTFLoader();
313
 
314
  // Add lights
315
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
316
  scene.add(ambientLight);
317
 
318
  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
319
- directionalLight.position.set(5, 10, 7);
320
  directionalLight.castShadow = true;
321
  directionalLight.shadow.mapSize.width = 2048;
322
  directionalLight.shadow.mapSize.height = 2048;
 
 
 
 
 
 
323
  scene.add(directionalLight);
324
 
325
  // Create ground
@@ -353,8 +467,7 @@
353
  renderer.setSize(window.innerWidth, window.innerHeight);
354
  });
355
  }
356
-
357
- function addEnvironmentObjects() {
358
  // Add some trees
359
  const treeGeometry = new THREE.ConeGeometry(1, 3, 8);
360
  const treeMaterial = new THREE.MeshStandardMaterial({ color: 0x2e7d32 });
@@ -388,11 +501,64 @@
388
  scene.add(rock);
389
  }
390
  }
391
-
392
  function loadPlayerCharacter() {
393
- // In a real app, we would load the GLTF model from the URL
394
- // For this example, we'll create a simple placeholder
395
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  const group = new THREE.Group();
397
 
398
  // Body
@@ -446,9 +612,9 @@
446
 
447
  gameState.player = {
448
  model: group,
449
- speed: 0.1,
450
- runSpeed: 0.2,
451
- rotationSpeed: 0.05,
452
  isMoving: false,
453
  animations: {
454
  idle: null,
@@ -461,51 +627,42 @@
461
 
462
  // Add a simple animation mixer for the player
463
  mixer = new THREE.AnimationMixer(group);
464
-
465
- // Create simple animations
466
  createPlayerAnimations();
467
-
468
- // Set initial animation
469
  setPlayerAnimation('idle');
470
  }
471
-
472
- function createPlayerAnimations() {
473
  // In a real app, these would come from the GLTF model
474
- // For this example, we'll create simple animations
475
 
476
  // Idle animation (slight bounce)
477
- const idleTrack = new THREE.VectorKeyframeTrack(
478
- '.position',
479
  [0, 0.5, 1],
480
- [
481
- 0, 0, 0, // Start position
482
- 0, 0.05, 0, // Up position
483
- 0, 0, 0 // Back to start
484
- ]
485
  );
486
  const idleClip = new THREE.AnimationClip('idle', 1, [idleTrack]);
487
  gameState.player.animations.idle = idleClip;
488
 
489
  // Walk animation (arm and leg movement)
490
- const leftArmTrack = new THREE.VectorKeyframeTrack(
491
  '.children[2].rotation[z]',
492
  [0, 0.5, 1],
493
- [0.5, -0.5, 0.5]
494
  );
495
- const rightArmTrack = new THREE.VectorKeyframeTrack(
496
  '.children[3].rotation[z]',
497
  [0, 0.5, 1],
498
- [-0.5, 0.5, -0.5]
499
  );
500
- const leftLegTrack = new THREE.VectorKeyframeTrack(
501
  '.children[4].position[y]',
502
  [0, 0.5, 1],
503
- [-0.4, -0.2, -0.4]
504
  );
505
- const rightLegTrack = new THREE.VectorKeyframeTrack(
506
  '.children[5].position[y]',
507
  [0, 0.5, 1],
508
- [-0.2, -0.4, -0.2]
509
  );
510
  const walkClip = new THREE.AnimationClip('walk', 0.5, [
511
  leftArmTrack, rightArmTrack, leftLegTrack, rightLegTrack
@@ -519,15 +676,14 @@
519
  gameState.player.animations.run = runClip;
520
 
521
  // Jump animation
522
- const jumpTrack = new THREE.VectorKeyframeTrack(
523
  '.position[y]',
524
  [0, 0.2, 0.4, 0.6, 0.8, 1],
525
- [0, 2, 1.5, 0.5, 0, 0]
526
  );
527
  const jumpClip = new THREE.AnimationClip('jump', 1, [jumpTrack]);
528
  gameState.player.animations.jump = jumpClip;
529
  }
530
-
531
  function setPlayerAnimation(name) {
532
  if (gameState.player.currentAnimation === name) return;
533
 
@@ -538,14 +694,14 @@
538
  if (clip) {
539
  const action = mixer.clipAction(clip);
540
  action.setLoop(THREE.LoopRepeat);
 
541
  action.play();
542
  }
543
 
544
  gameState.player.currentAnimation = name;
545
  }
546
  }
547
-
548
- function loadNPCCharacters() {
549
  gameState.selectedWorldCharacters.forEach((character, index) => {
550
  // In a real app, we would load the GLTF model from the URL
551
  // For this example, we'll create a simple placeholder
@@ -594,12 +750,9 @@
594
  });
595
  }
596
  function animate() {
597
- if (!gameState.player) {
598
- requestAnimationFrame(animate);
599
- return;
600
- }
601
 
602
- const delta = clock.getDelta();
603
 
604
  // Update player animation mixer
605
  if (mixer) {
@@ -613,59 +766,105 @@
613
  checkForNearbyNPCs();
614
 
615
  renderer.render(scene, camera);
616
-
617
- requestAnimationFrame(animate);
618
  }
619
- function handlePlayerMovement(delta) {
620
  if (!gameState.player) return;
621
 
622
  const player = gameState.player;
623
  let moving = false;
 
 
 
 
 
 
 
 
 
 
 
 
624
 
625
  // Forward/backward movement
626
  if (gameState.keys.w) {
627
- player.model.translateZ(-player.speed * (gameState.keys.shift ? player.runSpeed / player.speed : 1));
628
  moving = true;
629
  }
630
  if (gameState.keys.s) {
631
- player.model.translateZ(player.speed);
632
  moving = true;
633
  }
634
 
635
  // Left/right movement
636
  if (gameState.keys.a) {
637
- player.model.translateX(-player.speed);
638
  moving = true;
639
  }
640
  if (gameState.keys.d) {
641
- player.model.translateX(player.speed);
642
  moving = true;
643
  }
644
 
645
- // Rotation
646
  if (moving) {
647
- // Calculate target rotation based on movement direction
648
- const targetRotation = Math.atan2(
649
- (gameState.keys.a ? -1 : 0) + (gameState.keys.d ? 1 : 0),
650
- (gameState.keys.w ? -1 : 0) + (gameState.keys.s ? 1 : 0)
651
- );
652
 
653
- // Smoothly rotate towards target
654
- player.model.rotation.y = THREE.MathUtils.lerp(
655
- player.model.rotation.y,
656
- targetRotation,
657
- player.rotationSpeed
658
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
  }
660
 
661
  // Jumping - only trigger when not showing dialog and not already jumping
662
- if (gameState.keys.space && !gameState.isJumping && !document.getElementById('dialog-box').classList.contains('translate-y-full')) {
663
  gameState.isJumping = true;
664
  setPlayerAnimation('jump');
665
- setTimeout(() => {
666
- gameState.isJumping = false;
667
- updatePlayerAnimation();
668
- }, 1000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
669
  }
670
 
671
  // Update animation based on movement
@@ -674,10 +873,9 @@
674
  updatePlayerAnimation();
675
  }
676
 
677
- // Update camera position to follow player
678
- const cameraOffset = new THREE.Vector3(0, 5, 10);
679
- cameraOffset.applyQuaternion(player.model.quaternion);
680
- camera.position.copy(player.model.position.clone().add(cameraOffset));
681
  camera.lookAt(player.model.position);
682
  }
683
  function updatePlayerAnimation() {
@@ -757,32 +955,73 @@ function showDialog(npc) {
757
  existingPrompt.remove();
758
  }
759
  }
760
- function handleKeyDown(event) {
761
- switch (event.key.toLowerCase()) {
762
- case 'w': gameState.keys.w = true; break;
763
- case 'a': gameState.keys.a = true; break;
764
- case 's': gameState.keys.s = true; break;
765
- case 'd': gameState.keys.d = true; break;
766
- case 'shift': gameState.keys.shift = true; break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
767
  case ' ':
768
  gameState.keys.space = true;
769
- if (gameState.nearbyNpc && !gameState.isJumping) {
 
 
770
  showDialog(gameState.nearbyNpc);
771
- event.preventDefault(); // Prevent default space behavior
772
  }
773
  break;
774
  }
775
  }
776
- function handleKeyUp(event) {
777
- switch (event.key.toLowerCase()) {
778
- case 'w': gameState.keys.w = false; break;
779
- case 'a': gameState.keys.a = false; break;
780
- case 's': gameState.keys.s = false; break;
781
- case 'd': gameState.keys.d = false; break;
782
- case 'shift': gameState.keys.shift = false; break;
783
- case ' ': gameState.keys.space = false; break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
784
  }
785
  }
786
- </script>
787
  <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=KBLLR/character-selector" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
788
  </html>
 
12
  body {
13
  margin: 0;
14
  overflow: hidden;
15
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
17
  }
18
  #canvas {
19
  display: block;
20
  }
21
  .transition-all {
22
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
23
+ }
24
+ .character-card {
25
+ background: linear-gradient(145deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.9));
26
+ backdrop-filter: blur(10px);
27
+ border: 1px solid rgba(255, 255, 255, 0.1);
28
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
29
  }
30
  .character-card:hover {
31
+ transform: translateY(-8px) scale(1.02);
32
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
33
+ border-color: rgba(139, 92, 246, 0.5);
34
  }
35
  .dialog-box {
36
+ background: rgba(15, 23, 42, 0.95);
37
+ backdrop-filter: blur(20px);
38
+ border: 1px solid rgba(255, 255, 255, 0.1);
39
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
40
  }
41
  .character-preview {
42
  width: 100%;
43
  height: 200px;
44
+ background: linear-gradient(145deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02));
45
+ border-radius: 1rem;
46
+ border: 1px solid rgba(255, 255, 255, 0.1);
47
+ position: relative;
48
+ overflow: hidden;
49
+ }
50
+ .character-preview::before {
51
+ content: '';
52
+ position: absolute;
53
+ top: 0;
54
+ left: 0;
55
+ right: 0;
56
+ bottom: 0;
57
+ background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.05), transparent);
58
+ opacity: 0;
59
+ transition: opacity 0.3s ease;
60
+ }
61
+ .character-card:hover .character-preview::before {
62
+ opacity: 1;
63
+ }
64
+ .glass-effect {
65
  background: rgba(255, 255, 255, 0.1);
66
+ backdrop-filter: blur(10px);
67
+ border: 1px solid rgba(255, 255, 255, 0.2);
68
+ }
69
+ .gradient-text {
70
+ background: linear-gradient(135deg, #8b5cf6 0%, #3b82f6 100%);
71
+ -webkit-background-clip: text;
72
+ -webkit-text-fill-color: transparent;
73
+ background-clip: text;
74
+ }
75
+ .floating-animation {
76
+ animation: float 6s ease-in-out infinite;
77
+ }
78
+ @keyframes float {
79
+ 0%, 100% { transform: translateY(0px); }
80
+ 50% { transform: translateY(-10px); }
81
+ }
82
+ .pulse-glow {
83
+ animation: pulse-glow 2s ease-in-out infinite alternate;
84
+ }
85
+ @keyframes pulse-glow {
86
+ from { box-shadow: 0 0 20px rgba(139, 92, 246, 0.4); }
87
+ to { box-shadow: 0 0 30px rgba(139, 92, 246, 0.8); }
88
  }
89
  </style>
90
  </head>
91
  <body class="bg-gray-900 text-white">
92
  <!-- Welcome Screen -->
93
+ <div id="welcome-screen" class="fixed inset-0 flex items-center justify-center bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 z-50 transition-all duration-500">
94
+ <div class="absolute inset-0 overflow-hidden">
95
+ <div class="absolute -top-1/2 -left-1/2 w-full h-full bg-gradient-conic from-transparent via-purple-500/10 to-transparent animate-spin-slow"></div>
96
+ <div class="absolute -top-1/2 -right-1/2 w-full h-full bg-gradient-conic from-transparent via-blue-500/10 to-transparent animate-spin-slow" style="animation-delay: -3s;"></div>
97
+ </div>
98
+ <div class="text-center max-w-4xl p-12 glass-effect rounded-3xl shadow-2xl relative z-10 border border-white/10">
99
+ <div class="floating-animation mb-8">
100
+ <div class="w-32 h-32 mx-auto bg-gradient-to-r from-purple-500 to-blue-500 rounded-full flex items-center justify-center shadow-2xl">
101
+ <svg class="w-16 h-16 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
102
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
103
+ </svg>
104
+ </div>
105
+ </div>
106
+ <h1 class="text-6xl font-black mb-6 gradient-text tracking-tight">CharacterVerse</h1>
107
+ <p class="text-2xl mb-10 text-gray-200 leading-relaxed">Immerse yourself in an expansive 3D universe where every character has a story. Choose your avatar and embark on an unforgettable journey through dynamic worlds filled with interactive companions.</p>
108
+ <button id="start-btn" class="px-12 py-4 bg-gradient-to-r from-purple-600 to-blue-600 rounded-2xl text-white font-bold text-xl hover:from-purple-700 hover:to-blue-700 transition-all transform hover:scale-110 shadow-2xl pulse-glow border border-white/20">
109
+ <span class="flex items-center justify-center space-x-3">
110
+ <span>Begin Epic Journey</span>
111
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
112
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"></path>
113
+ </svg>
114
+ </span>
115
  </button>
116
+ <div class="mt-8 flex justify-center space-x-6 text-sm text-gray-400">
117
+ <div class="flex items-center space-x-2">
118
+ <div class="w-2 h-2 bg-green-500 rounded-full"></div>
119
+ <span>Real-time 3D Graphics</span>
120
+ </div>
121
+ <div class="flex items-center space-x-2">
122
+ <div class="w-2 h-2 bg-blue-500 rounded-full"></div>
123
+ <span>Interactive Characters</span>
124
+ </div>
125
+ <div class="flex items-center space-x-2">
126
+ <div class="w-2 h-2 bg-purple-500 rounded-full"></div>
127
+ <span>Dynamic Environments</span>
128
+ </div>
129
+ </div>
130
  </div>
131
  </div>
132
  <!-- Character Selection Screen -->
133
+ <div id="character-selection" class="fixed inset-0 bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 z-40 transition-all duration-500 opacity-0 pointer-events-none">
134
+ <div class="absolute inset-0 bg-black/20 backdrop-blur-sm"></div>
135
+ <div class="container mx-auto px-6 py-8 h-full flex flex-col relative z-10">
136
+ <div class="text-center mb-12">
137
+ <h2 class="text-5xl font-black mb-4 gradient-text">Choose Your Champion</h2>
138
+ <p class="text-xl text-gray-300 max-w-2xl mx-auto">Select the perfect avatar that represents your journey. Each character comes with unique abilities and personality traits.</p>
139
+ </div>
140
+ <div class="flex-1 overflow-y-auto pb-32">
141
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8 max-w-7xl mx-auto">
142
  <!-- Character cards will be dynamically inserted here -->
143
  </div>
144
  </div>
145
+ <div class="fixed bottom-0 left-0 right-0 bg-gradient-to-t from-slate-900/95 via-slate-900/80 to-transparent py-8 px-6 z-50 backdrop-blur-lg border-t border-white/10">
146
  <div class="container mx-auto text-center">
147
+ <button id="confirm-character" class="px-12 py-4 bg-gradient-to-r from-green-600 to-emerald-500 rounded-2xl text-white font-bold text-lg hover:from-green-700 hover:to-emerald-600 transition-all transform hover:scale-105 shadow-2xl border border-white/20 opacity-0 pulse-glow">
148
+ <span class="flex items-center justify-center space-x-3">
149
+ <span>Confirm Champion</span>
150
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
151
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
152
+ </svg>
153
+ </span>
154
  </button>
155
  </div>
156
  </div>
157
  </div>
158
  </div>
159
  <!-- World Selection Screen -->
160
+ <div id="world-selection" class="fixed inset-0 bg-gradient-to-br from-slate-900 via-blue-900 to-slate-900 z-30 transition-all duration-500 opacity-0 pointer-events-none">
161
+ <div class="absolute inset-0 bg-black/20 backdrop-blur-sm"></div>
162
+ <div class="container mx-auto px-6 py-8 h-full flex flex-col relative z-10">
163
+ <div class="text-center mb-12">
164
+ <h2 class="text-5xl font-black mb-4 gradient-text">Forge Your Universe</h2>
165
+ <p class="text-xl text-gray-300 max-w-2xl mx-auto">Select 5 unique characters to populate your world. Each will bring their own stories and interactions to your adventure.</p>
166
+ <div class="mt-4 inline-flex items-center space-x-4 bg-white/5 rounded-full px-6 py-3 border border-white/10">
167
+ <span class="text-green-400 font-semibold" id="selected-count">0</span>
168
+ <span class="text-gray-400">/</span>
169
+ <span class="text-gray-300">5 Characters Selected</span>
170
+ </div>
171
+ </div>
172
+ <div class="flex-1 overflow-y-auto pb-32">
173
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-8 max-w-7xl mx-auto">
174
  <!-- World character cards will be dynamically inserted here -->
175
  </div>
176
  </div>
177
+ <div class="fixed bottom-0 left-0 right-0 bg-gradient-to-t from-slate-900/95 via-slate-900/80 to-transparent py-8 px-6 z-50 backdrop-blur-lg border-t border-white/10">
178
  <div class="container mx-auto text-center">
179
+ <button id="start-world" class="px-12 py-4 bg-gradient-to-r from-purple-600 to-blue-600 rounded-2xl text-white font-bold text-lg hover:from-purple-700 hover:to-blue-700 transition-all transform hover:scale-105 shadow-2xl border border-white/20 opacity-0 pulse-glow">
180
+ <span class="flex items-center justify-center space-x-3">
181
+ <span>Generate Dynamic World</span>
182
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
183
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
184
+ </svg>
185
+ </span>
186
  </button>
187
  </div>
188
  </div>
 
251
  space: false
252
  }
253
  };
254
+ // Sample character data with free models from Three.js examples
 
255
  const characterData = [
256
+ { id: 1, name: "Robot", modelUrl: "https://threejs.org/examples/models/gltf/RobotExpressive/RobotExpressive.glb", color: "#EF4444", dialog: ["Beep boop! I am a robot.", "Processing your request...", "01010100 01101000 01110010 01100101 01100101"] },
257
+ { id: 2, name: "Fox", modelUrl: "https://threejs.org/examples/models/gltf/Fox/Fox.glb", color: "#3B82F6", dialog: ["What does the fox say?", "The forest is my home.", "Sly as a fox!"] },
258
+ { id: 3, name: "Parrot", modelUrl: "https://threejs.org/examples/models/gltf/Parrot.glb", color: "#10B981", dialog: ["Polly want a cracker?", "Squawk! Beautiful day!", "I can talk, can you fly?"] },
259
+ { id: 4, name: "Flamingo", modelUrl: "https://threejs.org/examples/models/gltf/Flamingo.glb", color: "#F59E0B", dialog: ["Standing on one leg is easy!", "Pink is the new black.", "Elegance is my middle name."] },
260
+ { id: 5, name: "Stork", modelUrl: "https://threejs.org/examples/models/gltf/Stork.glb", color: "#8B5CF6", dialog: ["I deliver special packages!", "Long flights are my specialty.", "Graceful in the sky."] },
261
+ { id: 6, name: "Damaged Helmet", modelUrl: "https://threejs.org/examples/models/gltf/DamagedHelmet/DamagedHelmet.glb", color: "#EC4899", dialog: ["I've seen many battles.", "This helmet tells a story.", "Ancient warrior's spirit."] },
262
+ { id: 7, name: "Cesium Man", modelUrl: "https://threejs.org/examples/models/gltf/CesiumMan/CesiumMan.glb", color: "#6366F1", dialog: ["I represent the future!", "Technology evolves rapidly.", "Digital consciousness rising."] },
263
+ { id: 8, name: "Flight Helmet", modelUrl: "https://threejs.org/examples/models/gltf/FlightHelmet/FlightHelmet.glb", color: "#F97316", dialog: ["Ready for takeoff!", "The sky is my domain.", "Pilot's best friend."] }
264
  ];
265
+ // Three.js variables
 
266
  let scene, camera, renderer, controls;
267
  let mixer, clock, loader;
268
  let world;
 
393
  container.appendChild(card);
394
  });
395
  }
 
396
  function initThreeJS() {
397
  // Set up Three.js scene
398
  scene = new THREE.Scene();
 
403
  camera.position.set(0, 5, 10);
404
 
405
  // Renderer
406
+ const canvas = document.getElementById('canvas');
407
+ renderer = new THREE.WebGLRenderer({
408
+ canvas: canvas,
409
+ antialias: true,
410
+ alpha: true
411
+ });
412
  renderer.setSize(window.innerWidth, window.innerHeight);
413
  renderer.shadowMap.enabled = true;
414
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
415
 
416
  // Clock for animations
417
  clock = new THREE.Clock();
 
420
  loader = new THREE.GLTFLoader();
421
 
422
  // Add lights
423
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
424
  scene.add(ambientLight);
425
 
426
  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
427
+ directionalLight.position.set(10, 20, 15);
428
  directionalLight.castShadow = true;
429
  directionalLight.shadow.mapSize.width = 2048;
430
  directionalLight.shadow.mapSize.height = 2048;
431
+ directionalLight.shadow.camera.near = 0.5;
432
+ directionalLight.shadow.camera.far = 50;
433
+ directionalLight.shadow.camera.left = -20;
434
+ directionalLight.shadow.camera.right = 20;
435
+ directionalLight.shadow.camera.top = 20;
436
+ directionalLight.shadow.camera.bottom = -20;
437
  scene.add(directionalLight);
438
 
439
  // Create ground
 
467
  renderer.setSize(window.innerWidth, window.innerHeight);
468
  });
469
  }
470
+ function addEnvironmentObjects() {
 
471
  // Add some trees
472
  const treeGeometry = new THREE.ConeGeometry(1, 3, 8);
473
  const treeMaterial = new THREE.MeshStandardMaterial({ color: 0x2e7d32 });
 
501
  scene.add(rock);
502
  }
503
  }
 
504
  function loadPlayerCharacter() {
505
+ loader.load(gameState.selectedCharacter.modelUrl, (gltf) => {
506
+ const model = gltf.scene;
507
+
508
+ // Scale and position the model
509
+ model.scale.set(1, 1, 1);
510
+ model.position.set(0, 0, 0);
511
+
512
+ // Enable shadows for all children
513
+ model.traverse((child) => {
514
+ if (child.isMesh) {
515
+ child.castShadow = true;
516
+ child.receiveShadow = true;
517
+ }
518
+ });
519
+
520
+ scene.add(model);
521
+
522
+ // Set up animations if available
523
+ const mixer = new THREE.AnimationMixer(model);
524
+ const animations = gltf.animations;
525
+
526
+ gameState.player = {
527
+ model: model,
528
+ speed: 0.08,
529
+ runSpeed: 0.15,
530
+ rotationSpeed: 0.08,
531
+ isMoving: false,
532
+ mixer: mixer,
533
+ animations: animations,
534
+ currentAnimation: null,
535
+ animationActions: {}
536
+ };
537
+
538
+ // Set up animation actions
539
+ if (animations && animations.length > 0) {
540
+ animations.forEach((clip) => {
541
+ gameState.player.animationActions[clip.name] = mixer.clipAction(clip);
542
+ });
543
+
544
+ // Play the first animation by default
545
+ if (animations[0]) {
546
+ gameState.player.animationActions[animations[0].name].play();
547
+ gameState.player.currentAnimation = animations[0].name;
548
+ }
549
+ } else {
550
+ // If no animations, create simple placeholder animations
551
+ createPlayerAnimations();
552
+ }
553
+
554
+ }, undefined, (error) => {
555
+ console.error('Error loading player model:', error);
556
+ // Fallback to placeholder
557
+ loadPlayerCharacterFallback();
558
+ });
559
+ }
560
+
561
+ function loadPlayerCharacterFallback() {
562
  const group = new THREE.Group();
563
 
564
  // Body
 
612
 
613
  gameState.player = {
614
  model: group,
615
+ speed: 0.08,
616
+ runSpeed: 0.15,
617
+ rotationSpeed: 0.08,
618
  isMoving: false,
619
  animations: {
620
  idle: null,
 
627
 
628
  // Add a simple animation mixer for the player
629
  mixer = new THREE.AnimationMixer(group);
 
 
630
  createPlayerAnimations();
 
 
631
  setPlayerAnimation('idle');
632
  }
633
+ function createPlayerAnimations() {
 
634
  // In a real app, these would come from the GLTF model
635
+ // For this example, we'll create simple animations using NumberKeyframeTrack
636
 
637
  // Idle animation (slight bounce)
638
+ const idleTrack = new THREE.NumberKeyframeTrack(
639
+ '.position[y]',
640
  [0, 0.5, 1],
641
+ [0, 0.1, 0]
 
 
 
 
642
  );
643
  const idleClip = new THREE.AnimationClip('idle', 1, [idleTrack]);
644
  gameState.player.animations.idle = idleClip;
645
 
646
  // Walk animation (arm and leg movement)
647
+ const leftArmTrack = new THREE.NumberKeyframeTrack(
648
  '.children[2].rotation[z]',
649
  [0, 0.5, 1],
650
+ [0.3, -0.3, 0.3]
651
  );
652
+ const rightArmTrack = new THREE.NumberKeyframeTrack(
653
  '.children[3].rotation[z]',
654
  [0, 0.5, 1],
655
+ [-0.3, 0.3, -0.3]
656
  );
657
+ const leftLegTrack = new THREE.NumberKeyframeTrack(
658
  '.children[4].position[y]',
659
  [0, 0.5, 1],
660
+ [-0.4, -0.3, -0.4]
661
  );
662
+ const rightLegTrack = new THREE.NumberKeyframeTrack(
663
  '.children[5].position[y]',
664
  [0, 0.5, 1],
665
+ [-0.3, -0.4, -0.3]
666
  );
667
  const walkClip = new THREE.AnimationClip('walk', 0.5, [
668
  leftArmTrack, rightArmTrack, leftLegTrack, rightLegTrack
 
676
  gameState.player.animations.run = runClip;
677
 
678
  // Jump animation
679
+ const jumpTrack = new THREE.NumberKeyframeTrack(
680
  '.position[y]',
681
  [0, 0.2, 0.4, 0.6, 0.8, 1],
682
+ [0, 1.5, 1, 0.3, 0, 0]
683
  );
684
  const jumpClip = new THREE.AnimationClip('jump', 1, [jumpTrack]);
685
  gameState.player.animations.jump = jumpClip;
686
  }
 
687
  function setPlayerAnimation(name) {
688
  if (gameState.player.currentAnimation === name) return;
689
 
 
694
  if (clip) {
695
  const action = mixer.clipAction(clip);
696
  action.setLoop(THREE.LoopRepeat);
697
+ action.clampWhenFinished = false;
698
  action.play();
699
  }
700
 
701
  gameState.player.currentAnimation = name;
702
  }
703
  }
704
+ function loadNPCCharacters() {
 
705
  gameState.selectedWorldCharacters.forEach((character, index) => {
706
  // In a real app, we would load the GLTF model from the URL
707
  // For this example, we'll create a simple placeholder
 
750
  });
751
  }
752
  function animate() {
753
+ requestAnimationFrame(animate);
 
 
 
754
 
755
+ const delta = Math.min(clock.getDelta(), 0.1); // Cap delta to prevent large jumps
756
 
757
  // Update player animation mixer
758
  if (mixer) {
 
766
  checkForNearbyNPCs();
767
 
768
  renderer.render(scene, camera);
 
 
769
  }
770
+ function handlePlayerMovement(delta) {
771
  if (!gameState.player) return;
772
 
773
  const player = gameState.player;
774
  let moving = false;
775
+ let moveDirection = new THREE.Vector3();
776
+
777
+ // Check if dialog is open - disable movement if dialog is active
778
+ const isDialogOpen = !document.getElementById('dialog-box').classList.contains('translate-y-full');
779
+ if (isDialogOpen) {
780
+ // Stop any movement and reset animation
781
+ if (player.isMoving) {
782
+ player.isMoving = false;
783
+ updatePlayerAnimation();
784
+ }
785
+ return;
786
+ }
787
 
788
  // Forward/backward movement
789
  if (gameState.keys.w) {
790
+ moveDirection.z = -1;
791
  moving = true;
792
  }
793
  if (gameState.keys.s) {
794
+ moveDirection.z = 1;
795
  moving = true;
796
  }
797
 
798
  // Left/right movement
799
  if (gameState.keys.a) {
800
+ moveDirection.x = -1;
801
  moving = true;
802
  }
803
  if (gameState.keys.d) {
804
+ moveDirection.x = 1;
805
  moving = true;
806
  }
807
 
808
+ // Normalize direction vector if moving diagonally
809
  if (moving) {
810
+ moveDirection.normalize();
 
 
 
 
811
 
812
+ // Calculate speed with delta time for consistent movement
813
+ const speed = (gameState.keys.shift ? player.runSpeed : player.speed) * delta * 60;
814
+
815
+ // Apply movement relative to camera direction
816
+ const cameraForward = new THREE.Vector3();
817
+ camera.getWorldDirection(cameraForward);
818
+ cameraForward.y = 0;
819
+ cameraForward.normalize();
820
+
821
+ const cameraRight = new THREE.Vector3();
822
+ cameraRight.crossVectors(cameraForward, new THREE.Vector3(0, 1, 0));
823
+ cameraRight.normalize();
824
+
825
+ const moveVector = new THREE.Vector3();
826
+ moveVector.addScaledVector(cameraForward, moveDirection.z * speed);
827
+ moveVector.addScaledVector(cameraRight, moveDirection.x * speed);
828
+
829
+ player.model.position.add(moveVector);
830
+
831
+ // Update player rotation to face movement direction
832
+ if (moveDirection.length() > 0.1) {
833
+ const targetRotation = Math.atan2(moveDirection.x, moveDirection.z) + camera.rotation.y;
834
+ player.model.rotation.y = THREE.MathUtils.lerp(player.model.rotation.y, targetRotation, player.rotationSpeed * delta * 60);
835
+ }
836
  }
837
 
838
  // Jumping - only trigger when not showing dialog and not already jumping
839
+ if (gameState.keys.space && !gameState.isJumping && !isDialogOpen) {
840
  gameState.isJumping = true;
841
  setPlayerAnimation('jump');
842
+
843
+ // Create a more robust jump animation
844
+ let jumpStartTime = performance.now();
845
+ const jumpDuration = 1000; // 1 second
846
+ const originalY = player.model.position.y;
847
+ const jumpHeight = 1.5;
848
+
849
+ function performJump() {
850
+ const currentTime = performance.now();
851
+ const elapsed = currentTime - jumpStartTime;
852
+ const progress = Math.min(elapsed / jumpDuration, 1);
853
+
854
+ // Parabolic jump curve
855
+ const jumpProgress = 1 - Math.pow(2 * progress - 1, 2);
856
+ player.model.position.y = originalY + jumpHeight * jumpProgress;
857
+
858
+ if (progress < 1) {
859
+ requestAnimationFrame(performJump);
860
+ } else {
861
+ gameState.isJumping = false;
862
+ player.model.position.y = originalY;
863
+ updatePlayerAnimation();
864
+ }
865
+ }
866
+
867
+ performJump();
868
  }
869
 
870
  // Update animation based on movement
 
873
  updatePlayerAnimation();
874
  }
875
 
876
+ // Update camera position to follow player with smooth interpolation
877
+ const targetCameraPosition = player.model.position.clone().add(new THREE.Vector3(0, 5, 10));
878
+ camera.position.lerp(targetCameraPosition, 0.1 * delta * 60);
 
879
  camera.lookAt(player.model.position);
880
  }
881
  function updatePlayerAnimation() {
 
955
  existingPrompt.remove();
956
  }
957
  }
958
+ function handleKeyDown(event) {
959
+ // Prevent default for space to avoid page scrolling
960
+ if (event.key === ' ') {
961
+ event.preventDefault();
962
+ }
963
+
964
+ // Ignore key repeats
965
+ if (event.repeat) return;
966
+
967
+ const key = event.key.toLowerCase();
968
+ switch (key) {
969
+ case 'w':
970
+ gameState.keys.w = true;
971
+ break;
972
+ case 'a':
973
+ gameState.keys.a = true;
974
+ break;
975
+ case 's':
976
+ gameState.keys.s = true;
977
+ break;
978
+ case 'd':
979
+ gameState.keys.d = true;
980
+ break;
981
+ case 'shift':
982
+ gameState.keys.shift = true;
983
+ break;
984
  case ' ':
985
  gameState.keys.space = true;
986
+ // Check if we're near an NPC and dialog isn't already open
987
+ const isDialogOpen = !document.getElementById('dialog-box').classList.contains('translate-y-full');
988
+ if (gameState.nearbyNpc && !gameState.isJumping && !isDialogOpen) {
989
  showDialog(gameState.nearbyNpc);
 
990
  }
991
  break;
992
  }
993
  }
994
+ function handleKeyUp(event) {
995
+ // Ignore key repeats
996
+ if (event.repeat) return;
997
+
998
+ const key = event.key.toLowerCase();
999
+ switch (key) {
1000
+ case 'w':
1001
+ gameState.keys.w = false;
1002
+ break;
1003
+ case 'a':
1004
+ gameState.keys.a = false;
1005
+ break;
1006
+ case 's':
1007
+ gameState.keys.s = false;
1008
+ break;
1009
+ case 'd':
1010
+ gameState.keys.d = false;
1011
+ break;
1012
+ case 'shift':
1013
+ gameState.keys.shift = false;
1014
+ break;
1015
+ case ' ':
1016
+ gameState.keys.space = false;
1017
+ break;
1018
+ }
1019
+
1020
+ // Update animation when keys are released
1021
+ if (gameState.player) {
1022
+ updatePlayerAnimation();
1023
  }
1024
  }
1025
+ </script>
1026
  <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=KBLLR/character-selector" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1027
  </html>