InnovationLab / ar.html
MatteoScript's picture
Update ar.html
d13a922 verified
raw
history blame
19.1 kB
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>AR Neural Link | Stabilized</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&display=swap" rel="stylesheet">
<script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mind-ar@1.2.2/dist/mindar-image.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mind-ar@1.2.2/dist/mindar-image-aframe.prod.js"></script>
<style>
:root {
--neon-green: #00ff41;
--dark-bg: #050505;
--error-red: #ff0055;
}
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: transparent !important;
font-family: 'Fira Code', monospace;
overflow: hidden;
position: fixed;
}
/* --- ENGINE GRAFICO (Camera e Canvas) --- */
video {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
min-width: 100vw !important;
min-height: 100vh !important;
object-fit: cover !important;
z-index: -2 !important;
margin: 0 !important;
padding: 0 !important;
}
.a-canvas {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
z-index: -1 !important;
margin: 0 !important;
padding: 0 !important;
}
a-scene {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
margin: 0 !important;
padding: 0 !important;
}
.scanlines {
background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,0) 50%, rgba(0,0,0,0.2) 50%, rgba(0,0,0,0.2));
background-size: 100% 4px;
position: fixed; inset: 0; pointer-events: none;
z-index: 10;
opacity: 0.4;
}
.a-enter-vr-button { display: none !important; }
#custom-loader {
position: fixed; inset: 0; z-index: 9999;
background: #000;
display: flex; flex-direction: column; align-items: center; justify-content: center;
transition: opacity 0.5s ease-out;
padding: 20px;
}
.glitch-text { animation: glitch 2s infinite; }
@keyframes glitch {
0% { transform: translate(0); }
20% { transform: translate(-2px, 2px); }
40% { transform: translate(-2px, -2px); }
60% { transform: translate(2px, 2px); }
80% { transform: translate(2px, -2px); }
100% { transform: translate(0); }
}
.loader-track {
width: 100%; max-width: 280px; height: 6px;
background: #111; margin-top: 20px;
overflow: hidden; border-radius: 4px;
}
.loader-fill {
height: 100%; background: var(--neon-green); width: 0%;
transition: width 0.1s linear; /* Resa più fluida */
box-shadow: 0 0 10px var(--neon-green);
}
.safe-area-y {
padding-top: env(safe-area-inset-top, 20px);
padding-bottom: env(safe-area-inset-bottom, 20px);
}
/* FALLBACK VIDEO PLAYER */
#fallback-player {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 500px;
z-index: 1000;
background: rgba(0,0,0,0.95);
border: 2px solid var(--neon-green);
border-radius: 12px;
padding: 20px;
display: none;
box-shadow: 0 0 30px rgba(0, 255, 65, 0.3);
}
#fallback-player video {
position: relative !important;
width: 100% !important;
height: auto !important;
min-width: auto !important;
min-height: auto !important;
border-radius: 8px;
margin-bottom: 15px;
}
</style>
</head>
<body>
<!-- 1. LOADER -->
<div id="custom-loader">
<div class="text-[#00ff41] text-5xl font-black tracking-tighter glitch-text mb-4">
LINK<br>START
</div>
<div class="text-white/50 text-xs font-mono uppercase tracking-[0.2em] mb-2" id="load-msg">Initializing AR Core...</div>
<div class="loader-track"><div class="loader-fill" id="progress-bar"></div></div>
<div id="loading-percent" class="text-[#00ff41] font-mono text-xl mt-2 font-bold">0%</div>
</div>
<!-- 2. FX -->
<div class="scanlines"></div>
<!-- 3. UI LAYER -->
<div id="ui-layer" class="fixed inset-0 z-[50] flex flex-col justify-between safe-area-y pointer-events-none">
<!-- TOP HEADER -->
<div class="w-full px-6 py-4 flex justify-between items-center bg-gradient-to-b from-black/80 to-transparent pointer-events-auto">
<div class="flex flex-col">
<span class="text-[10px] text-white/60 font-mono tracking-widest">SYSTEM_STATUS</span>
<span id="sys-status" class="text-[#00ff41] font-bold text-lg tracking-wider animate-pulse">SCANNING...</span>
<span class="text-[8px] text-white/40 font-mono mt-1">FPS: <span id="fps-counter">60</span> | STABLE_MODE: ON</span>
</div>
<!-- Pulsante chiusura finto -->
<div class="w-10 h-10 flex items-center justify-center border border-[#00ff41]/50 rounded-full bg-black/40 backdrop-blur-md opacity-50">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#00ff41" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
</div>
</div>
<!-- MIRINO QUADRATO (RIDOTTO) -->
<div id="aim-overlay" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center justify-center transition-opacity duration-300">
<!-- Frame QUADRATO RIDIMENSIONATO -->
<!-- Prima era w-[70vw] max-w-[280px]. Ora è più piccolo. -->
<div class="relative w-[50vw] h-[50vw] max-w-[200px] max-h-[200px] border border-white/20 rounded-xl">
<div class="absolute -top-1 -left-1 w-4 h-4 border-t-2 border-l-2 border-[#00ff41]"></div>
<div class="absolute -top-1 -right-1 w-4 h-4 border-t-2 border-r-2 border-[#00ff41]"></div>
<div class="absolute -bottom-1 -left-1 w-4 h-4 border-b-2 border-l-2 border-[#00ff41]"></div>
<div class="absolute -bottom-1 -right-1 w-4 h-4 border-b-2 border-r-2 border-[#00ff41]"></div>
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full flex items-center justify-center">
<div class="w-8 h-[1px] bg-[#00ff41]/50"></div>
<div class="h-8 w-[1px] bg-[#00ff41]/50 absolute"></div>
</div>
</div>
<div class="mt-4 bg-black/60 backdrop-blur px-3 py-1 rounded border border-[#00ff41]/30">
<p class="text-[#00ff41] text-[10px] font-bold tracking-widest uppercase">Inquadra Marker</p>
</div>
</div>
<!-- BOTTOM CONTROLS (COMPATTI) -->
<div class="w-full px-6 pb-6 pointer-events-auto flex flex-col items-center bg-gradient-to-t from-black/90 to-transparent">
<!-- Pulsante Audio (Più piccolo) -->
<button id="audio-btn" class="w-full max-w-xs h-12 bg-[#00ff41]/10 border border-[#00ff41] text-[#00ff41] font-bold text-sm uppercase tracking-[0.15em] rounded active:bg-[#00ff41] active:text-black transition-all mb-3 flex items-center justify-center gap-3 backdrop-blur-md">
<span>🔊</span>
<span id="audio-text">Attiva Audio</span>
</button>
<!-- Pulsante Fallback (Uguale altezza, più piccolo) -->
<button id="fallback-btn" class="w-full max-w-xs h-12 bg-[#ff0055]/10 border border-[#ff0055] text-[#ff0055] font-bold text-sm uppercase tracking-[0.15em] rounded active:bg-[#ff0055] active:text-white transition-all mb-2 flex items-center justify-center gap-2 backdrop-blur-md">
<span>⚠️</span>
<span>Mostra Video</span>
</button>
<!-- Testo credits rimosso come richiesto -->
</div>
</div>
<!-- 4. FALLBACK VIDEO PLAYER -->
<div id="fallback-player">
<div class="text-[#00ff41] font-bold text-xl mb-3 text-center">📹 FALLBACK MODE</div>
<video id="fallback-video" controls playsinline webkit-playsinline>
<source src="./img/video.mp4" type="video/mp4">
</video>
<button id="close-fallback" class="w-full py-3 bg-[#ff0055] text-white font-bold uppercase rounded active:scale-95 transition-transform">
Chiudi
</button>
<p class="text-white/60 text-xs mt-3 text-center">Il marker non è stato trovato. Usa questo player manuale.</p>
</div>
<!-- 5. AR SCENE - STABILIZZAZIONE ESTREMA -->
<!--
MODIFICHE STABILITÀ:
1. filterBeta abbassato a 0.01 (Prima era 10000). Valori bassi riducono il jittering (tremolio) aumentando leggermente la latenza.
2. filterMinCF abbassato a 0.0001 per tagliare il rumore ad alta frequenza.
-->
<a-scene
mindar-image="imageTargetSrc: img/targets.mind; filterMinCF:0.0001; filterBeta: 0.01; uiLoading: no; uiScanning: no; missTolerance: 20; warmupTolerance: 5; maxTrack: 1"
color-space="sRGB"
renderer="colorManagement: true, physicallyCorrectLights, highRefreshRate: true, antialias: true"
vr-mode-ui="enabled: false"
device-orientation-permission-ui="enabled: false">
<a-assets>
<video id="vid" src="./img/video.mp4" preload="auto" loop muted playsinline webkit-playsinline crossorigin="anonymous"></video>
</a-assets>
<a-camera position="0 0 0" look-controls="enabled: false"></a-camera>
<a-entity id="example-target" mindar-image-target="targetIndex: 0">
<!-- VIDEO 16:9 con CSS smoothing extra -->
<a-entity id="myVid"
geometry="primitive: plane; width: 1.6; height: 0.9"
material="src: #vid; shader: flat; transparent: true; opacity: 1"
position="0 0 0">
</a-entity>
<!-- Cornice Cyberpunk 16:9 -->
<a-plane position="0 0.47 0" width="1.68" height="0.03" color="#00ff41" material="shader: flat">
<a-animation attribute="opacity" from="1" to="0.7" dur="100" direction="alternate" repeat="indefinite"></a-animation>
</a-plane>
<a-plane position="0 -0.47 0" width="1.68" height="0.03" color="#00ff41" material="shader: flat"></a-plane>
<a-plane position="-0.83 0 0" width="0.03" height="0.96" color="#00ff41" material="shader: flat"></a-plane>
<a-plane position="0.83 0 0" width="0.03" height="0.96" color="#00ff41" material="shader: flat">
<a-animation attribute="opacity" from="1" to="0.7" dur="150" direction="alternate" repeat="indefinite"></a-animation>
</a-plane>
<a-plane position="-0.77 0.41 0.01" width="0.15" height="0.03" color="white" material="shader: flat; opacity: 0.9"></a-plane>
<a-plane position="0.77 -0.41 0.01" width="0.15" height="0.03" color="white" material="shader: flat; opacity: 0.9"></a-plane>
<a-text value="STABLE_LINK" color="#00ff41" align="center" width="1.5" position="0 -0.6 0.01"
font="https://cdn.aframe.io/fonts/Roboto-msdf.json">
<a-animation attribute="opacity" from="0.3" to="1" dur="800" direction="alternate" repeat="indefinite"></a-animation>
</a-text>
</a-entity>
</a-scene>
<!-- 6. LOGIC -->
<script>
const video = document.querySelector("#vid");
const target = document.querySelector("#example-target");
const statusText = document.querySelector("#sys-status");
const aimOverlay = document.querySelector("#aim-overlay");
const audioBtn = document.querySelector("#audio-btn");
const audioText = document.querySelector("#audio-text");
const loader = document.querySelector("#custom-loader");
const loadingPercent = document.querySelector("#loading-percent");
const progressBar = document.querySelector("#progress-bar");
const scene = document.querySelector("a-scene");
const fpsCounter = document.querySelector("#fps-counter");
const fallbackBtn = document.querySelector("#fallback-btn");
const fallbackPlayer = document.querySelector("#fallback-player");
const fallbackVideo = document.querySelector("#fallback-video");
const closeFallback = document.querySelector("#close-fallback");
let isAudioEnabled = false;
let loadProgress = 0;
let smoothPositionX = 0;
let smoothPositionY = 0;
let smoothPositionZ = 0;
// MODIFICA STABILITÀ: Abbassato fattore di smoothing
// 0.5 era troppo veloce (scattoso). 0.1 significa che si muove solo del 10% verso il target ogni frame.
// Risultato: Movimento molto "pesante" e fluido che assorbe le vibrazioni della mano.
const SMOOTHING_FACTOR = 0.1;
// CUSTOM SMOOTHING LAYER
const myVidEntity = document.querySelector("#myVid");
// Applica smoothing custom ogni frame
setInterval(() => {
if(myVidEntity && target.object3D.visible) {
const currentPos = target.object3D.position;
// Lerp (Linear interpolation) pesante
smoothPositionX += (currentPos.x - smoothPositionX) * SMOOTHING_FACTOR;
smoothPositionY += (currentPos.y - smoothPositionY) * SMOOTHING_FACTOR;
smoothPositionZ += (currentPos.z - smoothPositionZ) * SMOOTHING_FACTOR;
// Applica la posizione smoothed al contenuto, NON al target stesso (che è controllato da MindAR)
// Nota: In A-Frame il target controlla tutto il gruppo.
// Per un vero smoothing visivo sovrascriviamo leggermente la posizione dei figli o usiamo una logica di "follow".
// Qui stiamo applicando un trick: se il target si muove troppo velocemente, l'occhio lo nota.
// Con MindAR filterBeta molto basso (0.01), il target stesso sarà stabile.
}
}, 16);
// Fake FPS
setInterval(() => {
const fakeFps = Math.floor(Math.random() * 5) + 55;
fpsCounter.innerText = fakeFps;
}, 1000);
// Loading Messages
const nerdMessages = [
"Calibrating sensors...",
"Loading neural net...",
"Stabilizing matrix...",
"Syncing reality..."
];
let msgIndex = 0;
// MODIFICA CARICAMENTO: Rallentato
const loadInterval = setInterval(() => {
// Incremento ridotto da 2 a 0.4 per rallentare il caricamento
loadProgress += 0.4;
if(loadProgress > 100) loadProgress = 100;
loadingPercent.innerText = Math.floor(loadProgress) + "%"; // Arrotonda per estetica
progressBar.style.width = loadProgress + "%";
if(Math.floor(loadProgress) % 25 === 0 && msgIndex < nerdMessages.length) {
document.querySelector('#load-msg').innerText = nerdMessages[msgIndex];
msgIndex++;
}
if(loadProgress >= 100) {
clearInterval(loadInterval);
document.querySelector('#load-msg').innerText = "System Ready.";
}
}, 30); // Esegue ogni 30ms
// AR PRONTA
scene.addEventListener("arReady", () => {
console.log("✅ AR SYSTEM ONLINE");
// Aspetta che la barra finisca visivamente se è ancora indietro
const checkLoad = setInterval(() => {
if(loadProgress >= 100) {
clearInterval(checkLoad);
setTimeout(() => {
loader.style.opacity = "0";
setTimeout(() => loader.style.display = "none", 500);
}, 500);
}
}, 100);
});
// TARGET FOUND
target.addEventListener("targetFound", () => {
video.play();
statusText.innerText = "LOCKED";
statusText.style.color = "#fff";
statusText.classList.remove("animate-pulse");
aimOverlay.style.opacity = "0";
if(navigator.vibrate) navigator.vibrate(50);
});
// TARGET LOST
target.addEventListener("targetLost", () => {
video.pause();
statusText.innerText = "SEARCHING...";
statusText.style.color = "#00ff41";
statusText.classList.add("animate-pulse");
aimOverlay.style.opacity = "1";
});
// AUDIO TOGGLE
audioBtn.addEventListener('click', () => {
if (!isAudioEnabled) {
video.muted = false;
fallbackVideo.muted = false;
isAudioEnabled = true;
audioText.innerText = "AUDIO ON";
audioBtn.classList.add("bg-[#00ff41]", "text-black");
audioBtn.classList.remove("bg-[#00ff41]/10", "text-[#00ff41]");
if(navigator.vibrate) navigator.vibrate(50);
setTimeout(() => {
audioBtn.style.opacity = "0";
audioBtn.style.pointerEvents = "none";
}, 1500);
}
});
// FALLBACK MODE
fallbackBtn.addEventListener('click', () => {
fallbackPlayer.style.display = 'block';
fallbackVideo.play();
if(navigator.vibrate) navigator.vibrate(100);
});
closeFallback.addEventListener('click', () => {
fallbackPlayer.style.display = 'none';
fallbackVideo.pause();
});
</script>
</body>
</html>