v© £üaÍïêjj&@Ú:ø'À_Ϭ cordQ text/html MÈ<!DOCTYPE html>
<html lang="ja">
<head>
<title>DRiVE - Hand Tracking Ver.</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<style>
body {
margin: 0;
background: #000;
overscroll-behavior: none;
cursor: crosshair;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
touch-action: none;
user-select: none;
position: relative;
}
canvas {
display: block;
touch-action: none;
position: absolute;
top: 0;
left: 0;
z-index: 1;
cursor: crosshair;
}
/* æ åå
¥åã¯é表示ï¼è¨ç®ã®ã¿ã«ä½¿ç¨ï¼ */
#input_video {
display: none;
/* iOSã§ã®ã¬ã¤ã¢ã¦ãå´©ã鲿¢ */
position: absolute;
top: 0;
left: 0;
width: 1px;
height: 1px;
opacity: 0;
}
/* ã«ã¡ã©ã¹ãã¼ã¿ã¹è¡¨ç¤º */
#camera-status {
position: fixed;
bottom: 60px; /* ãªã¼ãã£ãªæ
å ±ã¨ãã¶ããªãããã«å°ãä¸ã« */
left: 50%;
transform: translateX(-50%);
color: rgba(0, 255, 255, 0.7);
font-size: 12px;
pointer-events: none;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.8);
width: 90%;
text-align: center;
z-index: 202;
}
.sound-overlay {
position: fixed;
top: 20px;
right: 20px;
z-index: 200;
opacity: 0.3;
transition: opacity 0.3s ease;
pointer-events: auto;
}
.sound-overlay:hover {
opacity: 1
}
.sound-info {
font-size: 12px;
cursor: pointer;
padding: 10px 18px;
border-radius: 8px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
user-select: none;
min-width: 80px;
display: flex;
align-items: center;
justify-content: center;
-webkit-tap-highlight-color: transparent;
pointer-events: auto;
position: relative;
z-index: 201;
}
.sound-info.sound-on {
color: rgba(255, 100, 255, 0.95);
background: linear-gradient(135deg, rgba(255, 100, 255, 0.2) 0%, rgba(100, 255, 255, 0.15) 50%, rgba(255, 255, 100, 0.1) 100%);
border: 1px solid rgba(255, 150, 255, 0.4);
box-shadow: 0 2px 12px rgba(255, 100, 255, 0.3)
}
.sound-info.sound-on:hover {
transform: translateY(-1px)
}
.sound-info.sound-on.compact {
font-size: 0;
padding: 10px;
border-radius: 50%;
min-width: 20px;
width: 20px;
height: 20px
}
.sound-info.sound-on.compact::after {
content: 'â';
font-size: 12px
}
.sound-info.sound-off {
color: rgba(150, 150, 150, 0.8);
background: linear-gradient(135deg, rgba(100, 100, 100, 0.1) 0%, rgba(120, 120, 120, 0.05) 100%);
border: 1px solid rgba(100, 100, 100, 0.3);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2)
}
.sound-info.sound-off:hover {
background: linear-gradient(135deg, rgba(100, 100, 100, 0.2) 0%, rgba(120, 120, 120, 0.15) 100%);
transform: translateY(-1px)
}
.sound-info.loading {
animation: pulse 1.5s ease-in-out infinite
}
.audio-info {
position: fixed;
bottom: 20px;
left: 20px;
color: rgba(255, 150, 255, 0.8);
font-size: 12px;
z-index: 100;
backdrop-filter: blur(10px);
background: rgba(20, 0, 40, 0.6);
padding: 10px 15px;
border-radius: 8px;
border: 1px solid rgba(255, 100, 255, 0.3);
opacity: 0;
transition: opacity 0.3s ease;
display: none !important
}
.audio-info.visible {
opacity: 1
}
@keyframes pulse {
0%,
100% {
opacity: 0.6
}
50% {
opacity: 1
}
}
@media (max-width:768px) {
body,
canvas {
cursor: none;
}
.sound-overlay {
top: 15px;
right: 15px
}
.sound-info {
font-size: 9px;
padding: 6px 10px;
min-width: 60px
}
.sound-info.sound-on.compact {
width: 16px;
height: 16px;
padding: 6px
}
.sound-info.sound-on.compact::after {
font-size: 10px
}
.audio-info {
bottom: 15px;
left: 15px;
font-size: 11px
}
}
</style>
<!-- MediaPipe Dependencies -->
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
</head>
<body>
<!-- iOS対å¿: playsinline, muted, autoplay ã追å -->
<video id="input_video" playsinline webkit-playsinline muted autoplay></video>
<div class="sound-overlay">
<div class="sound-info sound-off" id="sound-info">Sound: OFF</div>
</div>
<div id="camera-status">Initializing Hand Tracking...</div>
<div class="audio-info" id="audio-info">
<div>BSV Ordinals Audio Reactive</div>
<div>Frequency: <span id="dominant-freq">-</span> Hz</div>
<div>Volume: <span id="volume-level">-</span>%</div>
</div>
<script src="https://ordfs.network/content/0d2c3eea58ecf14689372d0a72be5aa91ad1701c5d16c7c06ec88d200709ed7f_0"></script>
<script src="https://ordfs.network/content/265aad29e9f6392db7eb74c63835c9d945dd644bf263d46ab747a5ae21883fbf_0"></script>
<script src="https://ordfs.network/content/f052fc0e81a5f9209b0941e6975f22159fce8ddd2abc1c66b1ba9718793e3d81_0"></script>
<script src="https://ordfs.network/content/ee7958cbce11bd78febb9c7860346d737671d9ca2292b881e62d7c2ca3dc8516_0"></script>
<script src="https://ordfs.network/content/1917b05e87f2f254666b6724a909f84adfcc13e5b528dd4ee377de40162bb0df_0"></script>
<script src="https://ordfs.network/content/a9a3e86aea42d09a50d81fd55696f75a15c5fc2fa0dd9306adbc3eb04e316b5b_0"></script>
<script src="https://ordfs.network/content/88b397007487bbad40403924cc1f84fe078aa515b5539f93291e135c07f6ef44_0"></script>
<script src="https://ordfs.network/content/1f2fa8750464cbe0a0a8da84244f0cf29b07064e84a5820eab1549b7c4524bcf_0"></script>
<script src="https://ordfs.network/content/b4f4295b35f47ed58a8960d78146b6d14d39087626c8fdebb85884e45534708d_0"></script>
<script>
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
// --- Hand Tracking System Class ---
class HandController {
constructor() {
this.active = false;
this.isTwoHands = false;
this.center = { x: 0, y: 0 }; // -0.5 to 0.5
this.distance = 0; // Normalized distance between hands
this.statusElement = document.getElementById('camera-status');
// ã¢ãã¤ã«å¤å®
this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
this.init();
}
init() {
const videoElement = document.getElementById('input_video');
const hands = new Hands({locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
}});
hands.setOptions({
maxNumHands: 2,
// ã¢ãã¤ã«æã¯è»½éã¢ãã«(0)ãPCæã¯é«ç²¾åº¦ã¢ãã«(1)
modelComplexity: this.isMobile ? 0 : 1,
minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5
});
hands.onResults(this.onResults.bind(this));
const camera = new Camera(videoElement, {
onFrame: async () => {
await hands.send({image: videoElement});
},
// ã¢ãã¤ã«ããã©ã¼ãã³ã¹ã®ããã«è§£å度ã調æ´
width: this.isMobile ? 360 : 640,
height: this.isMobile ? 270 : 480,
// ã¤ã³ã«ã¡ã©ãå¼·å¶
facingMode: 'user'
});
camera.start()
.then(() => {
this.statusElement.innerText = this.isMobile ? "Camera Active. (Mobile Mode)" : "Camera Active. Show Hands!";
setTimeout(() => { this.statusElement.style.opacity = 0; }, 4000);
})
.catch(e => {
console.error(e);
this.statusElement.innerText = "Camera Error. Check permissions.";
this.statusElement.style.color = "red";
});
}
onResults(results) {
if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
this.active = true;
const landmarks = results.multiHandLandmarks;
if (landmarks.length === 2) {
this.isTwoHands = true;
const h1 = landmarks[0][9]; // Middle finger MCP
const h2 = landmarks[1][9];
// Center
this.center.x = (h1.x + h2.x) / 2 - 0.5;
this.center.y = (h1.y + h2.y) / 2 - 0.5;
// Distance
const dx = h1.x - h2.x;
const dy = h1.y - h2.y;
this.distance = Math.sqrt(dx*dx + dy*dy);
} else {
this.isTwoHands = false;
const h1 = landmarks[0][9];
this.center.x = h1.x - 0.5;
this.center.y = h1.y - 0.5;
}
} else {
this.active = false;
}
}
}
class RevolutionaryAudioSystem {
constructor() {
this.audio = null;
this.audioContext = null;
this.analyser = null;
this.isLoaded = false;
this.isPlaying = false;
this.isLoading = false;
this.hasUserGesture = false;
this.ordinalsUrl = 'https://ordfs.network/content/5a8af75b2b4d20e6d356c4d78c2c3b1e0e839d5563a6e7004f73fd3793470783_0';
this.rawData = {bass: 0, mid: 0, treble: 0, volume: 0, dominantFreq: 0};
this.revolutionaryData = {chaos: 0, rainbow: 0, explosion: 0, morphing: 0, individual: 0};
this.beatDetection = {lastBeat: 0, strength: 0, isActive: false};
this.explosionTrigger = false;
this.initMobileSupport();
}
initMobileSupport() {
const gestureEvents = ['touchstart', 'touchend', 'click', 'keydown'];
const handleFirstGesture = () => {
this.hasUserGesture = true;
gestureEvents.forEach(event =>
document.removeEventListener(event, handleFirstGesture, true)
);
};
gestureEvents.forEach(event =>
document.addEventListener(event, handleFirstGesture, {capture: true, once: true})
);
}
async initialize() {
if (this.isLoaded || this.isLoading) return;
this.isLoading = true;
try {
if (isMobile && !this.hasUserGesture) {
throw new Error('User gesture required for mobile audio');
}
this.audio = new Audio();
this.audio.crossOrigin = "anonymous";
this.audio.loop = true;
this.audio.volume = 0.7;
if (isMobile) {
this.audio.preload = "metadata";
this.audio.playsInline = true;
} else {
this.audio.preload = "auto";
}
await new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => reject(new Error('Audio timeout')), 15000);
const resolveOnce = () => { clearTimeout(timeoutId); resolve(); };
this.audio.addEventListener('canplaythrough', resolveOnce, { once: true });
this.audio.addEventListener('error', (e) => {
clearTimeout(timeoutId);
reject(new Error('Audio error'));
}, { once: true });
this.audio.src = this.ordinalsUrl;
this.audio.load();
});
this.isLoaded = true;
} catch (error) {
console.error('Audio failed:', error);
this.isLoaded = false;
throw error;
} finally {
this.isLoading = false;
}
}
setupAudioContext() {
if (this.audioContext) return;
try {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
const source = this.audioContext.createMediaElementSource(this.audio);
this.analyser = this.audioContext.createAnalyser();
this.analyser.fftSize = isMobile ? 1024 : 2048;
this.analyser.smoothingTimeConstant = 0.1;
source.connect(this.analyser);
this.analyser.connect(this.audioContext.destination);
} catch (error) {
console.error('AudioContext setup failed:', error);
throw error;
}
}
async play() {
if (!this.isLoaded && !this.isLoading) await this.initialize();
if (this.audio && this.isLoaded && !this.isPlaying) {
this.setupAudioContext();
if (this.audioContext && this.audioContext.state === 'suspended') {
await this.audioContext.resume();
}
await this.audio.play();
this.isPlaying = true;
}
}
stop() {
if (this.audio && this.isPlaying) {
this.audio.pause();
this.audio.currentTime = 0;
this.isPlaying = false;
}
}
updateRevolutionaryData() {
if (!this.analyser || !this.isPlaying) return;
const bufferLength = this.analyser.frequencyBinCount;
const frequencyData = new Uint8Array(bufferLength);
this.analyser.getByteFrequencyData(frequencyData);
const bassEnd = Math.floor(bufferLength * 0.1);
const midEnd = Math.floor(bufferLength * 0.6);
this.rawData.bass = this.getMaxLevel(frequencyData, 0, bassEnd) / 255;
this.rawData.mid = this.getMaxLevel(frequencyData, bassEnd, midEnd) / 255;
this.rawData.treble = this.getMaxLevel(frequencyData, midEnd, bufferLength) / 255;
this.rawData.volume = this.getAverageLevel(frequencyData, 0, bufferLength) / 255;
this.rawData.dominantFreq = this.getDominantFreq(frequencyData);
this.revolutionaryData.chaos = Math.pow(this.rawData.bass * 0.08 + this.rawData.treble * 0.08, 1.5) * 0.4;
this.revolutionaryData.rainbow = (this.rawData.mid * 0.1 + this.rawData.treble * 0.12) * 0.3;
this.revolutionaryData.individual = this.rawData.volume * this.rawData.treble * 0.4;
this.revolutionaryData.morphing = Math.pow(this.rawData.volume, 2) * 0.3;
const now = Date.now();
const bassThreshold = 0.8;
if (this.rawData.bass > bassThreshold && now - this.beatDetection.lastBeat > 200) {
this.beatDetection.lastBeat = now;
this.beatDetection.strength = this.rawData.bass;
this.beatDetection.isActive = true;
if (this.rawData.bass > 0.9) this.explosionTrigger = true;
}
if (now - this.beatDetection.lastBeat > 300) this.beatDetection.isActive = false;
this.revolutionaryData.explosion = this.beatDetection.isActive ? this.beatDetection.strength * 3 : 0;
}
getAverageLevel(data, start, end) {
let sum = 0;
for (let i = start; i < end; i++) sum += data[i];
return sum / (end - start);
}
getMaxLevel(data, start, end) {
let max = 0;
for (let i = start; i < end; i++) max = Math.max(max, data[i]);
return max;
}
getDominantFreq(data) {
let maxIndex = 0, maxValue = 0;
for (let i = 0; i < data.length; i++) {
if (data[i] > maxValue) {
maxValue = data[i];
maxIndex = i;
}
}
return (maxIndex / data.length) * 22050;
}
}
let camera, scene, renderer, particleGroup, particles, mouse = new THREE.Vector2(-100, -100), customCursor, composer, glitchPass, bloomPass;
let isMouseDown = false, previousMouse = {x: 0, y: 0}, touches = [], lastPinchDistance = null, baseCameraZ = 2500;
const audioSystem = new RevolutionaryAudioSystem();
const PARTICLE_COUNT = 90000;
const particlesData = [];
let revolutionaryTime = 0, explosionParticles = [];
let explosionRipples = [], lastExplosionTime = 0;
// Hand Controller Instance
let handController;
// Cyberpunk Cityscape Generation Function
function generateCyberpunkCityPosition(index, total) {
const gridSize = Math.floor(Math.sqrt(total));
const gridX = index % gridSize;
const gridZ = Math.floor(index / gridSize);
const spacing = isMobile ? 15 : 25;
const x = (gridX - gridSize / 2) * spacing;
const z = (gridZ - gridSize / 2) * spacing;
// Generate building height with Perlin-like noise
const noiseScale = 0.1;
const heightNoise = perlinNoise(gridX * noiseScale, gridZ * noiseScale);
let y = heightNoise * 400;
// Add some random taller "skyscrapers"
if (Math.random() < 0.01) {
y += 300 + Math.random() * 300;
}
// Create canyons/streets
if (gridX % 10 === 0 || gridZ % 10 === 0) {
y *= 0.2;
}
return {
x: x + (Math.random() - 0.5) * 5,
y: y * (isMobile ? 0.5 : 1),
z: z + (Math.random() - 0.5) * 5,
gridX: gridX,
gridZ: gridZ
};
}
// Simple 2D Perlin Noise function
function perlinNoise(x, y) {
const n = x + y * 57;
const nn = (n << 13) ^ n;
return (1.0 - ((nn * (nn * nn * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0);
}
function generateSprite() {
const canvas = document.createElement('canvas');
canvas.width = canvas.height = 128;
const ctx = canvas.getContext('2d');
const gradient = ctx.createRadialGradient(64, 64, 0, 64, 64, 64);
gradient.addColorStop(0, 'rgba(255,255,255,1)');
gradient.addColorStop(0.3, 'rgba(255,200,255,0.8)');
gradient.addColorStop(0.6, 'rgba(200,255,255,0.4)');
gradient.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 128, 128);
return new THREE.CanvasTexture(canvas);
}
function init() {
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 5000);
if (isMobile) {
baseCameraZ = 1000; // æããºã¼ã ã¤ã³ããç¶æ
}
camera.position.z = baseCameraZ;
scene = new THREE.Scene();
particleGroup = new THREE.Group();
scene.add(particleGroup);
particleGroup.rotation.y = Math.PI; // Start from the opposite side
const positions = new Float32Array(PARTICLE_COUNT * 3);
const colors = new Float32Array(PARTICLE_COUNT * 3);
const sizes = new Float32Array(PARTICLE_COUNT);
for (let i = 0; i < PARTICLE_COUNT; i++) {
// Generate cyberpunk city position
const cityPos = generateCyberpunkCityPosition(i, PARTICLE_COUNT);
const i3 = i * 3;
positions[i3] = cityPos.x;
positions[i3 + 1] = cityPos.y;
positions[i3 + 2] = cityPos.z;
// Initial color: cyberpunk gradient
const baseHue = 0.6 + (cityPos.y / 800) * 0.2; // Height-based hue
const baseColor = new THREE.Color().setHSL(baseHue, 0.9, 0.7);
colors[i3] = baseColor.r;
colors[i3 + 1] = baseColor.g;
colors[i3 + 2] = baseColor.b;
sizes[i] = 2 + Math.random() * 3;
particlesData.push({
velocity: new THREE.Vector3((Math.random() - 0.5) * 0.6, (Math.random() - 0.5) * 0.6, (Math.random() - 0.5) * 0.6),
explosionVelocity: new THREE.Vector3(0, 0, 0),
originalPos: new THREE.Vector3(cityPos.x, cityPos.y, cityPos.z),
isBeam: false,
personalPhase: Math.random() * Math.PI * 2,
personalFreq: 0.8 + Math.random() * 0.4,
personalAmp: 0.5 + Math.random() * 1.5,
audioSensitivity: Math.random() * 0.8 + 0.2,
frequencyBand: Math.floor(Math.random() * 4),
baseSize: 2 + Math.random() * 3,
chaosMultiplier: 0.5 + Math.random() * 1.5,
rainbowSensitivity: Math.random() * 0.9 + 0.1,
audioReactivity: {
bassWeight: Math.random() * 0.5 + 0.5,
midWeight: Math.random() * 0.8 + 0.2,
trebleWeight: Math.random() * 0.6 + 0.4,
directionBias: Math.random() * Math.PI * 2,
temperament: Math.random(),
spiralTendency: Math.random(),
pulseSync: Math.random() * 0.5 + 0.5,
chaosThreshold: Math.random() * 0.3 + 0.4
}
});
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
particles = new THREE.Points(geometry, new THREE.PointsMaterial({
size: 20,
map: generateSprite(),
vertexColors: true,
sizeAttenuation: true,
blending: THREE.AdditiveBlending,
transparent: true,
depthWrite: false
}));
particleGroup.add(particles);
customCursor = new THREE.Mesh(new THREE.RingGeometry(2, 2.5, 32),
new THREE.MeshBasicMaterial({color: 0xff00ff, side: THREE.DoubleSide, transparent: true, opacity: 0.8}));
scene.add(customCursor);
renderer = new THREE.WebGLRenderer({antialias: !isMobile});
renderer.setPixelRatio(Math.min(window.devicePixelRatio, isMobile ? 1.5 : 2));
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Post-processing
composer = new THREE.EffectComposer(renderer);
composer.addPass(new THREE.RenderPass(scene, camera));
bloomPass = new THREE.UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
composer.addPass(bloomPass);
glitchPass = new THREE.GlitchPass();
composer.addPass(glitchPass);
// Initialize Hand Controller
handController = new HandController();
// ã¤ãã³ããªã¹ãã¼
document.getElementById('sound-info').addEventListener('click', toggleSound);
// ã¿ããã»ãã¦ã¹ã¤ãã³ã
['resize', 'mousemove', 'mousedown', 'mouseup', 'wheel', 'touchstart', 'touchmove', 'touchend'].forEach(event => {
if (event === 'resize') window.addEventListener(event, onWindowResize);
else document.addEventListener(event, {
mousemove: onMouseMove, mousedown: onMouseDown, mouseup: onMouseUp, wheel: onMouseWheel,
touchstart: onTouchStart, touchmove: onTouchMove, touchend: onTouchEnd
}[event], event.startsWith('touch') ? {passive: false} : undefined);
});
}
async function toggleSound() {
const soundInfo = document.getElementById('sound-info');
if (!soundInfo || audioSystem.isLoading) return;
if (!audioSystem.isPlaying) {
soundInfo.classList.add('loading');
soundInfo.textContent = 'Loading...';
try {
await audioSystem.play();
soundInfo.classList.remove('sound-off', 'loading');
soundInfo.classList.add('sound-on');
soundInfo.textContent = 'Sound: ON';
setTimeout(() => soundInfo.classList.add('compact'), 1000);
} catch (error) {
console.error('Audio play failed:', error);
soundInfo.classList.remove('loading');
soundInfo.classList.add('sound-off');
soundInfo.textContent = 'Sound: OFF';
}
} else {
audioSystem.stop();
soundInfo.classList.remove('sound-on', 'compact');
soundInfo.classList.add('sound-off');
soundInfo.textContent = 'Sound: OFF';
}
}
function animate() {
requestAnimationFrame(animate);
revolutionaryTime += 0.03;
const dt = 0.016;
audioSystem.updateRevolutionaryData();
// --- Beat Detection for Post-Processing ---
const timeSinceBeat = Date.now() - audioSystem.beatDetection.lastBeat;
const beatDecay = 150; // ms for the flash effect
let beatIntensity = 0;
if (audioSystem.isPlaying && timeSinceBeat < beatDecay) {
// Creates a value from 1 down to 0 over the decay period
beatIntensity = (1 - (timeSinceBeat / beatDecay)) * audioSystem.beatDetection.strength;
}
let handGlitchTrigger = false;
// --- Hand Tracking Interaction & Logic ---
if (handController && handController.active) {
// 1. Rotation based on Hand Center
// Map -0.5..0.5 to Rotation Angle
const targetRotY = handController.center.x * 3 + Math.PI; // +PI to align with initial state
const targetRotX = handController.center.y * 1.5;
// Smooth Lerp
particleGroup.rotation.y += (targetRotY - particleGroup.rotation.y) * 0.1;
particleGroup.rotation.x += (targetRotX - particleGroup.rotation.x) * 0.1;
// 2. Zoom based on Hand Distance
// Distance usually ranges 0.05 (close) to 0.6 (far)
if (handController.isTwoHands) {
const d = Math.max(0.05, Math.min(0.6, handController.distance));
// Close hands = Far away (Zoom out), Open hands = Close up (Zoom in)
// Map 0.05..0.6 to Z 4000..1000
const normalizedD = (d - 0.05) / 0.55;
const targetZ = 4000 - (normalizedD * 3000);
camera.position.z += (targetZ - camera.position.z) * 0.1;
// 3. Hand Glitch Trigger (Clap)
if (handController.distance < 0.1) {
handGlitchTrigger = true;
}
}
}
if (audioSystem.isPlaying || handGlitchTrigger) {
// --- Glitch Pass on Beat OR Hand Trigger ---
if (beatIntensity > 0.3 || handGlitchTrigger) {
glitchPass.enabled = true;
} else {
glitchPass.enabled = false;
}
// --- Bloom Pass Strength ---
let bloomStrength = 1.5 + Math.min(audioSystem.rawData.volume * 2, 2.5);
bloomStrength += beatIntensity * 3.0;
if(handGlitchTrigger) bloomStrength += 2.0;
bloomPass.strength = bloomStrength;
} else {
glitchPass.enabled = false;
bloomPass.strength = 1.5;
}
const revData = audioSystem.revolutionaryData;
// UI update
if (audioSystem.isPlaying) {
document.getElementById('dominant-freq').textContent = Math.round(audioSystem.rawData.dominantFreq);
document.getElementById('volume-level').textContent = Math.round(audioSystem.rawData.volume * 100);
if (audioSystem.explosionTrigger) {
audioSystem.explosionTrigger = false;
}
}
// 1. Enhanced Rotation Speed (Fallback when hands not active or additive)
if (!handController.active && !isMouseDown) {
let rotSpeed = 0.0005;
if (audioSystem.isPlaying) {
const audioIntensity = audioSystem.rawData.volume;
const mid = audioSystem.rawData.mid;
let speedIncrease = (audioIntensity * 0.006) + (mid * 0.01);
rotSpeed += Math.min(speedIncrease, 0.04);
}
particleGroup.rotation.y += rotSpeed;
}
// 2. Sound-Reactive Zoom (Combined with Hand Zoom)
if (!handController.active) {
if (audioSystem.isPlaying) {
const bass = audioSystem.rawData.bass;
let targetZ = baseCameraZ;
if (bass > 0.7) {
targetZ += bass * 500;
}
camera.position.z += (targetZ - camera.position.z) * 0.05;
} else {
camera.position.z += (baseCameraZ - camera.position.z) * 0.05;
}
}
// Clamp rotations
particleGroup.rotation.x = THREE.MathUtils.clamp(particleGroup.rotation.x, -Math.PI / 2, Math.PI / 2);
const positions = particles.geometry.attributes.position.array;
const colors = particles.geometry.attributes.color.array;
const sizes = particles.geometry.attributes.size.array;
// --- Cyberpunk City Particle Animation - Dynamic Effects ---
for (let i = 0; i < PARTICLE_COUNT; i++) {
const i3 = i * 3;
const data = particlesData[i];
const currentPos = new THREE.Vector3(positions[i3], positions[i3 + 1], positions[i3 + 2]);
let targetPos = data.originalPos.clone();
if (audioSystem.isPlaying) {
const audioIntensity = audioSystem.rawData.volume;
const bass = audioSystem.rawData.bass;
const mid = audioSystem.rawData.mid;
const treble = audioSystem.rawData.treble;
// 3. Enhanced Vertical Movement (Upwards-focused)
if (bass > 0.6) {
const verticalPulse = (Math.sin(revolutionaryTime * 10 + i * 0.1) + 1) / 2; // Map to 0-1 range
targetPos.y += verticalPulse * bass * 120; // Increased multiplier
}
targetPos.y += audioIntensity * 20;
// 2. Beam Lights
if (treble > 0.8 && Math.random() < 0.001) {
data.velocity.z += 50 + Math.random() * 50; // Shoot forward
data.isBeam = true;
}
}
// Physics
const returnForce = targetPos.clone().sub(currentPos).multiplyScalar(0.05);
data.velocity.add(returnForce).multiplyScalar(0.9);
// Reset beam particles when they get too far
if(currentPos.z > camera.position.z) {
data.velocity.z = 0;
positions[i3] = data.originalPos.x;
positions[i3 + 1] = data.originalPos.y;
positions[i3 + 2] = data.originalPos.z;
data.isBeam = false;
}
positions[i3] += data.velocity.x;
positions[i3 + 1] += data.velocity.y;
positions[i3 + 2] += data.velocity.z;
// Color and Size
let finalColor;
if (data.isBeam) {
finalColor = new THREE.Color(0xccffff); // Bright cyan for beams
} else if (audioSystem.isPlaying) {
const audioIntensity = audioSystem.rawData.volume;
let hue = 0.6 + (data.originalPos.y / 1000);
let saturation = 0.9;
let lightness = 0.5 + audioIntensity * 0.4;
finalColor = new THREE.Color().setHSL(hue, saturation, Math.min(1.0, lightness));
} else {
finalColor = new THREE.Color().setHSL(0.6, 0.9, 0.6);
}
colors[i3] = finalColor.r;
colors[i3 + 1] = finalColor.g;
colors[i3 + 2] = finalColor.b;
let size = data.isBeam ? data.baseSize * 5 : data.baseSize;
sizes[i] = Math.min(size, 25);
}
customCursor.position.set(mouse.x, mouse.y, 0);
composer.render();
}
function updateMousePosition(clientX, clientY) {
const rect = renderer.domElement.getBoundingClientRect();
const x = clientX - rect.left;
const y = clientY - rect.top;
const vector = new THREE.Vector3((x / rect.width) * 2 - 1, -(y / rect.height) * 2 + 1, 0.5);
vector.unproject(camera);
const dir = vector.sub(camera.position).normalize();
const distance = -camera.position.z / dir.z;
const pos = camera.position.clone().add(dir.multiplyScalar(distance));
mouse.x = pos.x;
mouse.y = pos.y;
}
function handleRotation(clientX, clientY) {
// HandControllerãã¢ã¯ãã£ããªæã¯ãã¦ã¹å転ãç¡å¹å
if (handController.active) return;
const deltaX = clientX - previousMouse.x;
const deltaY = clientY - previousMouse.y;
let rotationSpeed = 0.005;
if (audioSystem.isPlaying && audioSystem.rawData.mid > 0.4) {
rotationSpeed *= (1 + audioSystem.rawData.mid * 4);
}
particleGroup.rotation.y += deltaX * rotationSpeed;
particleGroup.rotation.x += deltaY * rotationSpeed;
// Clamp vertical rotation to avoid flipping
particleGroup.rotation.x = THREE.MathUtils.clamp(particleGroup.rotation.x, -Math.PI / 2, Math.PI / 2);
previousMouse.x = clientX;
previousMouse.y = clientY;
}
function handleZoom(delta, intensity) {
// HandControllerãã¢ã¯ãã£ããªæã¯ãºã¼ã ãç¡å¹å
if (handController.active) return;
baseCameraZ = THREE.MathUtils.clamp(baseCameraZ + delta * 2, 1000, 4000);
}
// ã¤ãã³ããã³ãã©ã¼
function onMouseMove(event) {
updateMousePosition(event.clientX, event.clientY);
if (isMouseDown) handleRotation(event.clientX, event.clientY);
}
function onMouseDown(event) {
isMouseDown = true;
previousMouse.x = event.clientX;
previousMouse.y = event.clientY;
}
function onMouseUp() {
isMouseDown = false;
}
function onMouseWheel(event) {
handleZoom(event.deltaY, Math.abs(event.deltaY) / 100);
}
function onTouchStart(event) {
// Hand Trackingä¸ã¯ã¿ããæä½ãå¶éï¼ãã¿ã³çã¯åå¿ããããã«ï¼
if (event.target.classList.contains('sound-info')) return;
event.preventDefault();
touches = Array.from(event.touches);
if (touches.length === 1) {
const touch = touches[0];
updateMousePosition(touch.clientX, touch.clientY);
isMouseDown = true;
previousMouse.x = touch.clientX;
previousMouse.y = touch.clientY;
} else if (touches.length === 2) {
lastPinchDistance = Math.sqrt(
Math.pow(touches[0].clientX - touches[1].clientX, 2) +
Math.pow(touches[0].clientY - touches[1].clientY, 2)
);
isMouseDown = false;
}
}
function onTouchMove(event) {
if (event.target.classList.contains('sound-info')) return;
event.preventDefault();
touches = Array.from(event.touches);
if (touches.length === 1) {
const touch = touches[0];
updateMousePosition(touch.clientX, touch.clientY);
if (isMouseDown) handleRotation(touch.clientX, touch.clientY);
} else if (touches.length === 2) {
const distance = Math.sqrt(
Math.pow(touches[0].clientX - touches[1].clientX, 2) +
Math.pow(touches[0].clientY - touches[1].clientY, 2)
);
if (lastPinchDistance !== null) {
handleZoom(-(distance - lastPinchDistance) * 3, 1);
}
lastPinchDistance = distance;
}
}
function onTouchEnd(event) {
if (event.target.classList.contains('sound-info')) return;
event.preventDefault();
touches = Array.from(event.touches);
if (touches.length === 0) {
isMouseDown = false;
lastPinchDistance = null;
} else if (touches.length === 1) {
updateMousePosition(touches[0].clientX, touches[0].clientY);
lastPinchDistance = null;
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// ã¯ãªã¼ã³ã¢ãã
window.addEventListener('beforeunload', () => audioSystem.stop());
window.addEventListener('DOMContentLoaded', () => {
init();
animate();
const soundButton = document.getElementById('sound-info');
if (soundButton) {
soundButton.addEventListener('click', toggleSound);
soundButton.addEventListener('touchend', (e) => {
e.preventDefault();
toggleSound();
});
}
});
</script>
</body>
</html>
</body>
</html>hj"1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5SETapp3D OrditypeordnameDRiVE - Hand Tracking Ver -subTypecollectionItem royaltiesLX[{"type":"address","destination":"1AwjKNmoFkZJQbyAUSW85jvFoM8XWPDNcA","percentage":0.2}]subTypeDataL{"collectionId":"7646b921593742586f4b80745d493172495b6def14e8579f605d1161d55130b5_0","mintNumber":"5","rarityLabel":"Mythical"}|SIGMABSM"1CSc46zqPYZcjotCFpg276UpoANEeE9mG7A øA¿ãP¦ÂÏÙôð¶ d8Áá ØÊ¶gXCW¶,L=zea£]í¢sþuã@kd`é.F¨Õl0
https://whatsonchain.com/tx/undefined