commit
8314f02f85
9 changed files with 3451 additions and 0 deletions
@ -0,0 +1,28 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||||
|
<title>Disco Party 🕺</title> |
||||||
|
<style> |
||||||
|
* { |
||||||
|
margin: 0; |
||||||
|
padding: 0; |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
body { |
||||||
|
overflow: hidden; |
||||||
|
font-family: 'Arial', sans-serif; |
||||||
|
background: #000; |
||||||
|
} |
||||||
|
#root { |
||||||
|
width: 100vw; |
||||||
|
height: 100vh; |
||||||
|
} |
||||||
|
</style> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="root"></div> |
||||||
|
<script type="module" src="/src/main.jsx"></script> |
||||||
|
</body> |
||||||
|
</html> |
||||||
@ -0,0 +1,24 @@ |
|||||||
|
{ |
||||||
|
"name": "disco-party", |
||||||
|
"version": "1.0.0", |
||||||
|
"description": "3D Disco Party Scene with React Three Fiber", |
||||||
|
"type": "module", |
||||||
|
"scripts": { |
||||||
|
"dev": "vite", |
||||||
|
"build": "vite build", |
||||||
|
"preview": "vite preview" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"react": "^18.2.0", |
||||||
|
"react-dom": "^18.2.0", |
||||||
|
"@react-three/fiber": "^8.15.11", |
||||||
|
"@react-three/drei": "^9.88.13", |
||||||
|
"three": "^0.160.0" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@types/react": "^18.2.43", |
||||||
|
"@types/react-dom": "^18.2.17", |
||||||
|
"@vitejs/plugin-react": "^4.2.1", |
||||||
|
"vite": "^5.0.0" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
import { Canvas } from '@react-three/fiber'; |
||||||
|
import { OrbitControls } from '@react-three/drei'; |
||||||
|
import Scene from './Scene'; |
||||||
|
|
||||||
|
function App() { |
||||||
|
return ( |
||||||
|
<Canvas |
||||||
|
shadows |
||||||
|
camera={{ position: [0, 8, 12], fov: 75 }} |
||||||
|
gl={{ antialias: true }} |
||||||
|
> |
||||||
|
<color attach="background" args={['#050505']} /> |
||||||
|
<fog attach="fog" args={['#050505', 15, 50]} /> |
||||||
|
|
||||||
|
<ambientLight intensity={0.2} /> |
||||||
|
|
||||||
|
<Scene /> |
||||||
|
|
||||||
|
<OrbitControls |
||||||
|
enableDamping |
||||||
|
dampingFactor={0.05} |
||||||
|
minDistance={5} |
||||||
|
maxDistance={30} |
||||||
|
/> |
||||||
|
</Canvas> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default App; |
||||||
@ -0,0 +1,778 @@ |
|||||||
|
import { useRef, useMemo } from 'react'; |
||||||
|
import { useFrame } from '@react-three/fiber'; |
||||||
|
import { SpotLight, Text } from '@react-three/drei'; |
||||||
|
import * as THREE from 'three'; |
||||||
|
|
||||||
|
function DanceFloor() { |
||||||
|
return ( |
||||||
|
<mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow> |
||||||
|
<planeGeometry args={[30, 30]} /> |
||||||
|
<meshStandardMaterial |
||||||
|
color="#8b4513" |
||||||
|
roughness={0.6} |
||||||
|
metalness={0.0} |
||||||
|
/> |
||||||
|
</mesh> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function Balloons() { |
||||||
|
const balloons = useMemo(() => { |
||||||
|
const positions = []; |
||||||
|
for (let i = 0; i < 15; i++) { |
||||||
|
positions.push([ |
||||||
|
(Math.random() - 0.5) * 25, |
||||||
|
2 + Math.random() * 8, |
||||||
|
(Math.random() - 0.5) * 25 |
||||||
|
]); |
||||||
|
} |
||||||
|
return positions; |
||||||
|
}, []); |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
{balloons.map((pos, i) => ( |
||||||
|
<group key={i} position={pos}> |
||||||
|
<mesh> |
||||||
|
<sphereGeometry args={[0.3, 16, 16]} /> |
||||||
|
<meshStandardMaterial |
||||||
|
color={['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff'][i % 6]} |
||||||
|
roughness={0.3} |
||||||
|
metalness={0.1} |
||||||
|
/> |
||||||
|
</mesh> |
||||||
|
<mesh position={[0, -0.3, 0]}> |
||||||
|
<cylinderGeometry args={[0.01, 0.01, 1, 8]} /> |
||||||
|
<meshStandardMaterial color="#333" /> |
||||||
|
</mesh> |
||||||
|
</group> |
||||||
|
))} |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function Confetti() { |
||||||
|
const confettiRef = useRef(); |
||||||
|
const count = 250; |
||||||
|
|
||||||
|
const positions = useMemo(() => { |
||||||
|
const pos = new Float32Array(count * 3); |
||||||
|
for (let i = 0; i < count * 3; i += 3) { |
||||||
|
pos[i] = (Math.random() - 0.5) * 30; |
||||||
|
pos[i + 1] = Math.random() * 15; |
||||||
|
pos[i + 2] = (Math.random() - 0.5) * 30; |
||||||
|
} |
||||||
|
return pos; |
||||||
|
}, []); |
||||||
|
|
||||||
|
const colors = useMemo(() => { |
||||||
|
const cols = new Float32Array(count * 3); |
||||||
|
const colorArray = [ |
||||||
|
[1, 0, 0], [0, 1, 0], [0, 0, 1], |
||||||
|
[1, 1, 0], [1, 0, 1], [0, 1, 1] |
||||||
|
]; |
||||||
|
for (let i = 0; i < count; i++) { |
||||||
|
const color = colorArray[Math.floor(Math.random() * colorArray.length)]; |
||||||
|
cols[i * 3] = color[0]; |
||||||
|
cols[i * 3 + 1] = color[1]; |
||||||
|
cols[i * 3 + 2] = color[2]; |
||||||
|
} |
||||||
|
return cols; |
||||||
|
}, []); |
||||||
|
|
||||||
|
useFrame((state) => { |
||||||
|
if (confettiRef.current) { |
||||||
|
confettiRef.current.rotation.y += 0.001; |
||||||
|
const positions = confettiRef.current.geometry.attributes.position.array; |
||||||
|
for (let i = 1; i < positions.length; i += 3) { |
||||||
|
positions[i] -= 0.01; |
||||||
|
if (positions[i] < 0) { |
||||||
|
positions[i] = 15; |
||||||
|
} |
||||||
|
} |
||||||
|
confettiRef.current.geometry.attributes.position.needsUpdate = true; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<points ref={confettiRef}> |
||||||
|
<bufferGeometry> |
||||||
|
<bufferAttribute |
||||||
|
attach="attributes-position" |
||||||
|
count={count} |
||||||
|
array={positions} |
||||||
|
itemSize={3} |
||||||
|
/> |
||||||
|
<bufferAttribute |
||||||
|
attach="attributes-color" |
||||||
|
count={count} |
||||||
|
array={colors} |
||||||
|
itemSize={3} |
||||||
|
/> |
||||||
|
</bufferGeometry> |
||||||
|
<pointsMaterial |
||||||
|
size={0.1} |
||||||
|
vertexColors |
||||||
|
transparent |
||||||
|
opacity={0.8} |
||||||
|
blending={THREE.AdditiveBlending} |
||||||
|
/> |
||||||
|
</points> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function Person({ position, index, lookAt }) { |
||||||
|
const groupRef = useRef(); |
||||||
|
const leftArmRef = useRef(); |
||||||
|
const rightArmRef = useRef(); |
||||||
|
|
||||||
|
// Randomize skin tones |
||||||
|
const skinTones = ['#ffdbac', '#f1c27d', '#e0ac69', '#c68642', '#8d5524']; |
||||||
|
const skinColor = skinTones[index % skinTones.length]; |
||||||
|
|
||||||
|
// More color variety for shirts |
||||||
|
const shirtColors = [ |
||||||
|
'#ff6b6b', '#4ecdc4', '#45b7d1', '#f9ca24', '#6c5ce7', |
||||||
|
'#a29bfe', '#fd79a8', '#00b894', '#e17055', '#74b9ff', |
||||||
|
'#a29bfe', '#fd79a8', '#fdcb6e', '#e84393', '#00cec9' |
||||||
|
]; |
||||||
|
const shirtColor = shirtColors[index % shirtColors.length]; |
||||||
|
|
||||||
|
useFrame(() => { |
||||||
|
if (groupRef.current && lookAt) { |
||||||
|
// Face the person they're talking to |
||||||
|
const dx = lookAt[0] - position[0]; |
||||||
|
const dz = lookAt[2] - position[2]; |
||||||
|
groupRef.current.rotation.y = Math.atan2(dx, dz); |
||||||
|
|
||||||
|
// Subtle idle animation - slight head/body movement |
||||||
|
const t = Date.now() * 0.005; |
||||||
|
const offset = index * 0.3; |
||||||
|
groupRef.current.rotation.y += Math.sin(t * 0.05 + offset) * 0.05; |
||||||
|
|
||||||
|
// Subtle arm gestures while talking |
||||||
|
if (leftArmRef.current) { |
||||||
|
leftArmRef.current.rotation.x = Math.sin(t * 1.7 + offset) * 0.1; |
||||||
|
leftArmRef.current.rotation.z = Math.sin(t * 1.5 + offset) * 0.05; |
||||||
|
} |
||||||
|
if (rightArmRef.current) { |
||||||
|
rightArmRef.current.rotation.x = Math.sin(t * 1.5 + offset + 1) * 0.1; |
||||||
|
rightArmRef.current.rotation.z = Math.sin(t * 1.5 + offset + 1) * 0.05; |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<group ref={groupRef} position={position}> |
||||||
|
{/* Head */} |
||||||
|
<mesh position={[0, 1.45, 0]}> |
||||||
|
<sphereGeometry args={[0.15, 16, 16]} /> |
||||||
|
<meshStandardMaterial color={skinColor} /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Torso */} |
||||||
|
<mesh position={[0, 1.1, 0]}> |
||||||
|
<boxGeometry args={[0.35, 0.5, 0.25]} /> |
||||||
|
<meshStandardMaterial color={shirtColor} /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Left Arm */} |
||||||
|
<group ref={leftArmRef} position={[-0.18, 1.1, 0]}> |
||||||
|
<mesh position={[-0.15, 0, 0]}> |
||||||
|
<boxGeometry args={[0.3, 0.12, 0.12]} /> |
||||||
|
<meshStandardMaterial color={skinColor} /> |
||||||
|
</mesh> |
||||||
|
</group> |
||||||
|
|
||||||
|
{/* Right Arm */} |
||||||
|
<group ref={rightArmRef} position={[0.18, 1.1, 0]}> |
||||||
|
<mesh position={[0.15, 0, 0]}> |
||||||
|
<boxGeometry args={[0.3, 0.12, 0.12]} /> |
||||||
|
<meshStandardMaterial color={skinColor} /> |
||||||
|
</mesh> |
||||||
|
</group> |
||||||
|
|
||||||
|
{/* Left Leg - standing still */} |
||||||
|
<mesh position={[-0.1, 0.7, 0]}> |
||||||
|
<boxGeometry args={[0.12, 0.7, 0.12]} /> |
||||||
|
<meshStandardMaterial color="#333" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[-0.1, 0.25, 0.05]}> |
||||||
|
<boxGeometry args={[0.15, 0.1, 0.2]} /> |
||||||
|
<meshStandardMaterial color="#222" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Right Leg - standing still */} |
||||||
|
<mesh position={[0.1, 0.7, 0]}> |
||||||
|
<boxGeometry args={[0.12, 0.7, 0.12]} /> |
||||||
|
<meshStandardMaterial color="#333" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[0.1, 0.25, 0.05]}> |
||||||
|
<boxGeometry args={[0.15, 0.1, 0.2]} /> |
||||||
|
<meshStandardMaterial color="#222" /> |
||||||
|
</mesh> |
||||||
|
</group> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function People() { |
||||||
|
// Create groups of people talking to each other - 80 total people |
||||||
|
const groups = useMemo(() => { |
||||||
|
const peopleGroups = []; |
||||||
|
const groupCount = 180; // 30 groups |
||||||
|
|
||||||
|
for (let g = 0; g < groupCount; g++) { |
||||||
|
const groupAngle = (g / groupCount) * Math.PI * 3; |
||||||
|
const groupRadius = 2 + Math.random() * 10; // Spread around the room |
||||||
|
const groupCenter = [ |
||||||
|
Math.cos(groupAngle) * groupRadius, |
||||||
|
0, |
||||||
|
Math.sin(groupAngle) * groupRadius |
||||||
|
]; |
||||||
|
|
||||||
|
// Vary group sizes to get close to 80 total (mostly groups of 3, some 2) |
||||||
|
const peopleInGroup = g < 25 ? 3 : 2; |
||||||
|
const group = []; |
||||||
|
|
||||||
|
for (let p = 0; p < peopleInGroup; p++) { |
||||||
|
const personAngle = (p / peopleInGroup) * Math.PI * 2; |
||||||
|
const personRadius = 0.8; |
||||||
|
const personPos = [ |
||||||
|
groupCenter[0] + Math.cos(personAngle) * personRadius, |
||||||
|
0, |
||||||
|
groupCenter[2] + Math.sin(personAngle) * personRadius |
||||||
|
]; |
||||||
|
|
||||||
|
// Person they're looking at (next person in circle, or center) |
||||||
|
const lookAtIndex = (p + 1) % peopleInGroup; |
||||||
|
const lookAtPos = [ |
||||||
|
groupCenter[0] + Math.cos((lookAtIndex / peopleInGroup) * Math.PI * 2) * personRadius, |
||||||
|
0, |
||||||
|
groupCenter[2] + Math.sin((lookAtIndex / peopleInGroup) * Math.PI * 2) * personRadius |
||||||
|
]; |
||||||
|
|
||||||
|
group.push({ position: personPos, lookAt: lookAtPos }); |
||||||
|
} |
||||||
|
|
||||||
|
peopleGroups.push(...group); |
||||||
|
} |
||||||
|
|
||||||
|
return peopleGroups; |
||||||
|
}, []); |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
{groups.map((person, i) => ( |
||||||
|
<Person key={i} position={person.position} index={i} lookAt={person.lookAt} /> |
||||||
|
))} |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function YouTubeBanner() { |
||||||
|
// Create triangle geometry for play button |
||||||
|
const triangleShape = useMemo(() => { |
||||||
|
const shape = new THREE.Shape(); |
||||||
|
shape.moveTo(0, 0.4); |
||||||
|
shape.lineTo(0.7, 0); |
||||||
|
shape.lineTo(0, -0.4); |
||||||
|
shape.lineTo(0, 0.4); |
||||||
|
return shape; |
||||||
|
}, []); |
||||||
|
|
||||||
|
return ( |
||||||
|
<group position={[0, -1, 0.1]}> |
||||||
|
{/* Red background */} |
||||||
|
<mesh> |
||||||
|
<planeGeometry args={[5, 2]} /> |
||||||
|
<meshStandardMaterial color="#ff0000" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* White play button triangle */} |
||||||
|
<mesh position={[0, 0, 0.01]}> |
||||||
|
<shapeGeometry args={[triangleShape]} /> |
||||||
|
<meshStandardMaterial color="#ffffff" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* YouTube text */} |
||||||
|
<Text |
||||||
|
position={[0.3, 0, 0.01]} |
||||||
|
fontSize={0.35} |
||||||
|
color="#ffffff" |
||||||
|
anchorX="left" |
||||||
|
anchorY="middle" |
||||||
|
fontWeight="bold" |
||||||
|
> |
||||||
|
YouTube |
||||||
|
</Text> |
||||||
|
</group> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function DJ() { |
||||||
|
const groupRef = useRef(); |
||||||
|
const leftArmRef = useRef(); |
||||||
|
const rightArmRef = useRef(); |
||||||
|
|
||||||
|
useFrame((state) => { |
||||||
|
if (groupRef.current) { |
||||||
|
const t = state.clock.elapsedTime; |
||||||
|
// Subtle head bobbing to the beat |
||||||
|
groupRef.current.position.y = Math.sin(t * 2) * 0.05; |
||||||
|
|
||||||
|
// Arm movements - mixing/scratching |
||||||
|
if (leftArmRef.current) { |
||||||
|
leftArmRef.current.rotation.x = Math.sin(t * 3) * 0.2; |
||||||
|
leftArmRef.current.rotation.z = Math.sin(t * 3) * 0.1; |
||||||
|
} |
||||||
|
if (rightArmRef.current) { |
||||||
|
rightArmRef.current.rotation.x = Math.sin(t * 3 + 1) * 0.2; |
||||||
|
rightArmRef.current.rotation.z = Math.sin(t * 3 + 1) * 0.1; |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<group ref={groupRef} position={[0, 2, -13.3]}> |
||||||
|
{/* Head */} |
||||||
|
<mesh position={[0, 1.6, 0]}> |
||||||
|
<sphereGeometry args={[0.18, 16, 16]} /> |
||||||
|
<meshStandardMaterial color="#d4a574" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Headphones */} |
||||||
|
<mesh position={[0, 1.6, 0]} rotation={[0, 0, 0]}> |
||||||
|
<torusGeometry args={[0.25, 0.05, 8, 16]} /> |
||||||
|
<meshStandardMaterial color="#1a1a1a" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[-0.25, 1.6, 0]}> |
||||||
|
<cylinderGeometry args={[0.12, 0.12, 0.15, 16]} /> |
||||||
|
<meshStandardMaterial color="#1a1a1a" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[0.25, 1.6, 0]}> |
||||||
|
<cylinderGeometry args={[0.12, 0.12, 0.15, 16]} /> |
||||||
|
<meshStandardMaterial color="#1a1a1a" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Torso */} |
||||||
|
<mesh position={[0, 1.1, 0]}> |
||||||
|
<boxGeometry args={[0.35, 0.5, 0.25]} /> |
||||||
|
<meshStandardMaterial color="#000000" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Left Arm */} |
||||||
|
<group ref={leftArmRef} position={[-0.25, 1.1, 0]}> |
||||||
|
<mesh position={[-0.15, 0, 0]}> |
||||||
|
<boxGeometry args={[0.3, 0.12, 0.12]} /> |
||||||
|
<meshStandardMaterial color="#d4a574" /> |
||||||
|
</mesh> |
||||||
|
</group> |
||||||
|
|
||||||
|
{/* Right Arm */} |
||||||
|
<group ref={rightArmRef} position={[0.25, 1.1, 0]}> |
||||||
|
<mesh position={[0.15, 0, 0]}> |
||||||
|
<boxGeometry args={[0.3, 0.12, 0.12]} /> |
||||||
|
<meshStandardMaterial color="#d4a574" /> |
||||||
|
</mesh> |
||||||
|
</group> |
||||||
|
|
||||||
|
{/* Legs */} |
||||||
|
<mesh position={[-0.1, 0.7, 0]}> |
||||||
|
<boxGeometry args={[0.12, 0.7, 0.12]} /> |
||||||
|
<meshStandardMaterial color="#333" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[0.1, 0.7, 0]}> |
||||||
|
<boxGeometry args={[0.12, 0.7, 0.12]} /> |
||||||
|
<meshStandardMaterial color="#333" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Shoes */} |
||||||
|
<mesh position={[-0.1, 0.25, 0.05]}> |
||||||
|
<boxGeometry args={[0.18, 0.1, 0.25]} /> |
||||||
|
<meshStandardMaterial color="#0a0a0a" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[0.1, 0.25, 0.05]}> |
||||||
|
<boxGeometry args={[0.18, 0.1, 0.25]} /> |
||||||
|
<meshStandardMaterial color="#0a0a0a" /> |
||||||
|
</mesh> |
||||||
|
</group> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function DJBooth() { |
||||||
|
return ( |
||||||
|
<group position={[0, 0, -14]}> |
||||||
|
{/* Bar base - larger and more prominent */} |
||||||
|
<mesh position={[0, 0.5, 0]}> |
||||||
|
<boxGeometry args={[6, 1, 2]} /> |
||||||
|
<meshStandardMaterial color="#2a1a0a" roughness={0.3} metalness={0.2} /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Bar top */} |
||||||
|
<mesh position={[0, 1.2, 0]}> |
||||||
|
<boxGeometry args={[6.2, 0.2, 2.2]} /> |
||||||
|
<meshStandardMaterial color="#1a0a0a" roughness={0.1} metalness={0.5} /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* DJ equipment section - raised */} |
||||||
|
<mesh position={[0, 2, 0.6]}> |
||||||
|
<boxGeometry args={[4, 1.5, 0.8]} /> |
||||||
|
<meshStandardMaterial color="#0a0a0a" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Equipment panel */} |
||||||
|
<mesh position={[0, 2.5, 0.61]}> |
||||||
|
<boxGeometry args={[3.5, 0.4, 0.1]} /> |
||||||
|
<meshStandardMaterial color="#1a1a1a" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Control buttons - more prominent */} |
||||||
|
<mesh position={[-1.2, 2.5, 0.62]}> |
||||||
|
<cylinderGeometry args={[0.2, 0.2, 0.08, 16]} /> |
||||||
|
<meshStandardMaterial color="#ff0000" emissive="#ff0000" emissiveIntensity={0.5} /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[0, 2.5, 0.62]}> |
||||||
|
<cylinderGeometry args={[0.2, 0.2, 0.08, 16]} /> |
||||||
|
<meshStandardMaterial color="#00ff00" emissive="#00ff00" emissiveIntensity={0.5} /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[1.2, 2.5, 0.62]}> |
||||||
|
<cylinderGeometry args={[0.2, 0.2, 0.08, 16]} /> |
||||||
|
<meshStandardMaterial color="#0000ff" emissive="#0000ff" emissiveIntensity={0.5} /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Bar stools */} |
||||||
|
<mesh position={[-2.5, 0.6, -1.2]} rotation={[0, 0, 0]}> |
||||||
|
<cylinderGeometry args={[0.15, 0.15, 1.2, 16]} /> |
||||||
|
<meshStandardMaterial color="#654321" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[-2.5, 1.2, -1.2]}> |
||||||
|
<cylinderGeometry args={[0.4, 0.4, 0.1, 16]} /> |
||||||
|
<meshStandardMaterial color="#8b4513" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
<mesh position={[2.5, 0.6, -1.2]} rotation={[0, 0, 0]}> |
||||||
|
<cylinderGeometry args={[0.15, 0.15, 1.2, 16]} /> |
||||||
|
<meshStandardMaterial color="#654321" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[2.5, 1.2, -1.2]}> |
||||||
|
<cylinderGeometry args={[0.4, 0.4, 0.1, 16]} /> |
||||||
|
<meshStandardMaterial color="#8b4513" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Banner hanging from above - bigger and more prominent */} |
||||||
|
<group position={[0, 5.5, 0]}> |
||||||
|
|
||||||
|
|
||||||
|
{/* YouTube logo banner background */} |
||||||
|
<YouTubeBanner /> |
||||||
|
|
||||||
|
{/* Banner text - bigger */} |
||||||
|
<Text |
||||||
|
position={[0, -1, 0.12]} |
||||||
|
rotation={[0, 0, 0]} |
||||||
|
fontSize={0.4} |
||||||
|
color="#ffffff" |
||||||
|
anchorX="center" |
||||||
|
anchorY="middle" |
||||||
|
fontWeight="bold" |
||||||
|
outlineWidth={0.02} |
||||||
|
outlineColor="#000000" |
||||||
|
> |
||||||
|
1 MILLION SUBSCRIBERS |
||||||
|
</Text> |
||||||
|
|
||||||
|
{/* Rope/string to hang it */} |
||||||
|
|
||||||
|
</group> |
||||||
|
|
||||||
|
{/* Spotlight on the bar */} |
||||||
|
<pointLight position={[0, 5, -14]} intensity={50} distance={15} color="#ffffff" /> |
||||||
|
</group> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function DJBoothSpotlight() { |
||||||
|
const spotlightRef = useRef(); |
||||||
|
const targetRef = useRef(); |
||||||
|
|
||||||
|
useFrame(() => { |
||||||
|
if (spotlightRef.current && targetRef.current) { |
||||||
|
spotlightRef.current.target = targetRef.current; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<group> |
||||||
|
<spotLight |
||||||
|
ref={spotlightRef} |
||||||
|
position={[0, 10, -14]} |
||||||
|
angle={Math.PI / 6} |
||||||
|
penumbra={0.3} |
||||||
|
intensity={300} |
||||||
|
distance={20} |
||||||
|
decay={1} |
||||||
|
color="#ffffff" |
||||||
|
castShadow |
||||||
|
/> |
||||||
|
<object3D ref={targetRef} position={[0, 2, -14]} /> |
||||||
|
</group> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function Speakers() { |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<group position={[-14, 0, 0]}> |
||||||
|
<mesh position={[0, 1.5, 0]}> |
||||||
|
<boxGeometry args={[1, 3, 1]} /> |
||||||
|
<meshStandardMaterial color="#222" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[0, 1.5, 0.51]}> |
||||||
|
<cylinderGeometry args={[0.4, 0.4, 0.1, 32]} /> |
||||||
|
<meshStandardMaterial color="#111" /> |
||||||
|
</mesh> |
||||||
|
</group> |
||||||
|
<group position={[14, 0, 0]}> |
||||||
|
<mesh position={[0, 1.5, 0]}> |
||||||
|
<boxGeometry args={[1, 3, 1]} /> |
||||||
|
<meshStandardMaterial color="#222" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[0, 1.5, 0.51]}> |
||||||
|
<cylinderGeometry args={[0.4, 0.4, 0.1, 32]} /> |
||||||
|
<meshStandardMaterial color="#111" /> |
||||||
|
</mesh> |
||||||
|
</group> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function ThePrimeagen() { |
||||||
|
const groupRef = useRef(); |
||||||
|
const leftArmRef = useRef(); |
||||||
|
const rightArmRef = useRef(); |
||||||
|
|
||||||
|
useFrame((state) => { |
||||||
|
if (groupRef.current) { |
||||||
|
const t = state.clock.elapsedTime; |
||||||
|
// Subtle animation |
||||||
|
groupRef.current.rotation.y = Math.sin(t * 0.5) * 0.1; |
||||||
|
|
||||||
|
if (leftArmRef.current) { |
||||||
|
leftArmRef.current.rotation.x = Math.sin(t * 2) * 0.2; |
||||||
|
} |
||||||
|
if (rightArmRef.current) { |
||||||
|
rightArmRef.current.rotation.x = -Math.sin(t * 2) * 0.2; |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<group ref={groupRef} position={[10, 0, -8]}> |
||||||
|
{/* Head */} |
||||||
|
<mesh position={[0, 1.6, 0]}> |
||||||
|
<sphereGeometry args={[0.18, 16, 16]} /> |
||||||
|
<meshStandardMaterial color="#d4a574" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Beard */} |
||||||
|
<mesh position={[0, 1.45, 0.12]}> |
||||||
|
<boxGeometry args={[0.25, 0.2, 0.08]} /> |
||||||
|
<meshStandardMaterial color="#2a2a2a" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[-0.08, 1.4, 0.12]}> |
||||||
|
<boxGeometry args={[0.1, 0.15, 0.08]} /> |
||||||
|
<meshStandardMaterial color="#2a2a2a" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[0.08, 1.4, 0.12]}> |
||||||
|
<boxGeometry args={[0.1, 0.15, 0.08]} /> |
||||||
|
<meshStandardMaterial color="#2a2a2a" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Glasses */} |
||||||
|
<mesh position={[-0.08, 1.58, 0.1]}> |
||||||
|
<torusGeometry args={[0.06, 0.01, 8, 16]} /> |
||||||
|
<meshStandardMaterial color="#1a1a1a" metalness={0.8} roughness={0.2} /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[0.08, 1.58, 0.1]}> |
||||||
|
<torusGeometry args={[0.06, 0.01, 8, 16]} /> |
||||||
|
<meshStandardMaterial color="#1a1a1a" metalness={0.8} roughness={0.2} /> |
||||||
|
</mesh> |
||||||
|
{/* Bridge */} |
||||||
|
<mesh position={[0, 1.58, 0.1]} rotation={[0, 0, Math.PI / 2]}> |
||||||
|
<boxGeometry args={[0.16, 0.01, 0.01]} /> |
||||||
|
<meshStandardMaterial color="#1a1a1a" metalness={0.8} roughness={0.2} /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Hoodie */} |
||||||
|
<mesh position={[0, 1.1, 0]}> |
||||||
|
<boxGeometry args={[0.5, 0.6, 0.3]} /> |
||||||
|
<meshStandardMaterial color="#1a1a1a" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Hood */} |
||||||
|
<mesh position={[0, 1.5, -0.05]}> |
||||||
|
<coneGeometry args={[0.3, 0.3, 8]} /> |
||||||
|
<meshStandardMaterial color="#1a1a1a" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Left Arm */} |
||||||
|
<group ref={leftArmRef} position={[-0.3, 1.1, 0]}> |
||||||
|
<mesh position={[-0.15, 0, 0]}> |
||||||
|
<boxGeometry args={[0.3, 0.15, 0.15]} /> |
||||||
|
<meshStandardMaterial color="#1a1a1a" /> |
||||||
|
</mesh> |
||||||
|
</group> |
||||||
|
|
||||||
|
{/* Right Arm */} |
||||||
|
<group ref={rightArmRef} position={[0.3, 1.1, 0]}> |
||||||
|
<mesh position={[0.15, 0, 0]}> |
||||||
|
<boxGeometry args={[0.3, 0.15, 0.15]} /> |
||||||
|
<meshStandardMaterial color="#1a1a1a" /> |
||||||
|
</mesh> |
||||||
|
</group> |
||||||
|
|
||||||
|
{/* Legs */} |
||||||
|
<mesh position={[-0.1, 0.7, 0]}> |
||||||
|
<boxGeometry args={[0.15, 0.7, 0.15]} /> |
||||||
|
<meshStandardMaterial color="#2a2a2a" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[0.1, 0.7, 0]}> |
||||||
|
<boxGeometry args={[0.15, 0.7, 0.15]} /> |
||||||
|
<meshStandardMaterial color="#2a2a2a" /> |
||||||
|
</mesh> |
||||||
|
|
||||||
|
{/* Shoes */} |
||||||
|
<mesh position={[-0.1, 0.25, 0.05]}> |
||||||
|
<boxGeometry args={[0.18, 0.1, 0.25]} /> |
||||||
|
<meshStandardMaterial color="#0a0a0a" /> |
||||||
|
</mesh> |
||||||
|
<mesh position={[0.1, 0.25, 0.05]}> |
||||||
|
<boxGeometry args={[0.18, 0.1, 0.25]} /> |
||||||
|
<meshStandardMaterial color="#0a0a0a" /> |
||||||
|
</mesh> |
||||||
|
</group> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function Scene() { |
||||||
|
const spot1Ref = useRef(); |
||||||
|
const spot2Ref = useRef(); |
||||||
|
const spot3Ref = useRef(); |
||||||
|
const spot4Ref = useRef(); |
||||||
|
const spot5Ref = useRef(); |
||||||
|
|
||||||
|
useFrame((state) => { |
||||||
|
const t = state.clock.elapsedTime; |
||||||
|
|
||||||
|
if (spot1Ref.current) { |
||||||
|
const phase = t * 0.6; |
||||||
|
spot1Ref.current.target.position.x = Math.cos(phase) * 3; |
||||||
|
spot1Ref.current.target.position.z = Math.sin(phase) * 3; |
||||||
|
spot1Ref.current.target.position.y = 2.5 + Math.sin(t * 1.2) * 0.5; |
||||||
|
} |
||||||
|
|
||||||
|
if (spot2Ref.current) { |
||||||
|
const phase = t * 0.6 + (Math.PI * 2 / 5); |
||||||
|
spot2Ref.current.target.position.x = Math.cos(phase) * 3; |
||||||
|
spot2Ref.current.target.position.z = Math.sin(phase) * 3; |
||||||
|
spot2Ref.current.target.position.y = 2.5 + Math.sin(t * 1.2) * 0.5; |
||||||
|
} |
||||||
|
|
||||||
|
if (spot3Ref.current) { |
||||||
|
const phase = t * 0.6 + (Math.PI * 4 / 5); |
||||||
|
spot3Ref.current.target.position.x = Math.cos(phase) * 3; |
||||||
|
spot3Ref.current.target.position.z = Math.sin(phase) * 3; |
||||||
|
spot3Ref.current.target.position.y = 2.5 + Math.sin(t * 1.2) * 0.5; |
||||||
|
} |
||||||
|
|
||||||
|
if (spot4Ref.current) { |
||||||
|
const phase = t * 0.6 + (Math.PI * 6 / 5); |
||||||
|
spot4Ref.current.target.position.x = Math.cos(phase) * 3; |
||||||
|
spot4Ref.current.target.position.z = Math.sin(phase) * 3; |
||||||
|
spot4Ref.current.target.position.y = 2.5 + Math.sin(t * 1.2) * 0.5; |
||||||
|
} |
||||||
|
|
||||||
|
if (spot5Ref.current) { |
||||||
|
const phase = t * 0.6 + (Math.PI * 8 / 5); |
||||||
|
spot5Ref.current.target.position.x = Math.cos(phase) * 3; |
||||||
|
spot5Ref.current.target.position.z = Math.sin(phase) * 3; |
||||||
|
spot5Ref.current.target.position.y = 2.5 + Math.sin(t * 1.2) * 0.5; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<DanceFloor /> |
||||||
|
<Balloons /> |
||||||
|
<Confetti /> |
||||||
|
<People /> |
||||||
|
<ThePrimeagen /> |
||||||
|
<DJBooth /> |
||||||
|
<DJ /> |
||||||
|
<DJBoothSpotlight /> |
||||||
|
<Speakers /> |
||||||
|
|
||||||
|
<SpotLight |
||||||
|
ref={spot1Ref} |
||||||
|
position={[10, 12, 10]} |
||||||
|
angle={Math.PI / 7} |
||||||
|
penumbra={0.5} |
||||||
|
intensity={250} |
||||||
|
distance={60} |
||||||
|
decay={1} |
||||||
|
color="#ff3366" |
||||||
|
castShadow |
||||||
|
/> |
||||||
|
|
||||||
|
<SpotLight |
||||||
|
ref={spot2Ref} |
||||||
|
position={[-10, 12, 10]} |
||||||
|
angle={Math.PI / 7} |
||||||
|
penumbra={0.5} |
||||||
|
intensity={250} |
||||||
|
distance={60} |
||||||
|
decay={1} |
||||||
|
color="#33ffcc" |
||||||
|
castShadow |
||||||
|
/> |
||||||
|
|
||||||
|
<SpotLight |
||||||
|
ref={spot3Ref} |
||||||
|
position={[0, 12, -12]} |
||||||
|
angle={Math.PI / 7} |
||||||
|
penumbra={0.5} |
||||||
|
intensity={250} |
||||||
|
distance={60} |
||||||
|
decay={1} |
||||||
|
color="#3366ff" |
||||||
|
castShadow |
||||||
|
/> |
||||||
|
|
||||||
|
<SpotLight |
||||||
|
ref={spot4Ref} |
||||||
|
position={[-10, 12, -10]} |
||||||
|
angle={Math.PI / 7} |
||||||
|
penumbra={0.5} |
||||||
|
intensity={250} |
||||||
|
distance={60} |
||||||
|
decay={1} |
||||||
|
color="#ffff00" |
||||||
|
castShadow |
||||||
|
/> |
||||||
|
|
||||||
|
<SpotLight |
||||||
|
ref={spot5Ref} |
||||||
|
position={[10, 12, -10]} |
||||||
|
angle={Math.PI / 7} |
||||||
|
penumbra={0.5} |
||||||
|
intensity={250} |
||||||
|
distance={60} |
||||||
|
decay={1} |
||||||
|
color="#ff00ff" |
||||||
|
castShadow |
||||||
|
/> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default Scene; |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import ReactDOM from 'react-dom/client'; |
||||||
|
import App from './App'; |
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')).render( |
||||||
|
<React.StrictMode> |
||||||
|
<App /> |
||||||
|
</React.StrictMode> |
||||||
|
); |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
* { |
||||||
|
margin: 0; |
||||||
|
padding: 0; |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
|
||||||
|
body { |
||||||
|
overflow: hidden; |
||||||
|
font-family: 'Arial', sans-serif; |
||||||
|
background: #000; |
||||||
|
} |
||||||
|
|
||||||
|
#container { |
||||||
|
width: 100vw; |
||||||
|
height: 100vh; |
||||||
|
} |
||||||
|
|
||||||
|
#info { |
||||||
|
position: absolute; |
||||||
|
top: 20px; |
||||||
|
left: 50%; |
||||||
|
transform: translateX(-50%); |
||||||
|
text-align: center; |
||||||
|
color: #fff; |
||||||
|
z-index: 100; |
||||||
|
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5); |
||||||
|
pointer-events: none; |
||||||
|
} |
||||||
|
|
||||||
|
#info h1 { |
||||||
|
font-size: 2.5em; |
||||||
|
margin-bottom: 10px; |
||||||
|
animation: pulse 2s ease-in-out infinite; |
||||||
|
} |
||||||
|
|
||||||
|
#info p { |
||||||
|
font-size: 1em; |
||||||
|
opacity: 0.8; |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes pulse { |
||||||
|
0%, 100% { |
||||||
|
transform: scale(1); |
||||||
|
} |
||||||
|
50% { |
||||||
|
transform: scale(1.05); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue