anycoder-14b31731 / index.html
Mousco's picture
Upload folder using huggingface_hub
b5c217a verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AutoTech 3D - Détails & Assemblage</title>
<!-- Importation de Three.js et OrbitControls via CDN -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
<!-- Importation d'une police moderne et d'icônes -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary: #3b82f6;
--accent: #06b6d4;
--dark-bg: #0f172a;
--panel-bg: rgba(30, 41, 59, 0.85);
--text-main: #f8fafc;
--text-muted: #94a3b8;
--border: rgba(255, 255, 255, 0.1);
--glass: blur(12px);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--dark-bg);
color: var(--text-main);
overflow: hidden; /* Empêche le scroll global, on gère par panneaux */
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
}
/* HEADER */
header {
height: 60px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 2rem;
background: rgba(15, 23, 42, 0.9);
border-bottom: 1px solid var(--border);
z-index: 100;
}
.logo {
font-weight: 800;
font-size: 1.2rem;
background: linear-gradient(to right, var(--primary), var(--accent));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: -0.5px;
display: flex;
align-items: center;
gap: 10px;
}
.anycoder-link {
font-size: 0.85rem;
color: var(--text-muted);
text-decoration: none;
transition: color 0.3s;
display: flex;
align-items: center;
gap: 5px;
}
.anycoder-link:hover {
color: var(--primary);
}
/* MAIN LAYOUT */
main {
flex: 1;
display: grid;
grid-template-columns: 1fr 400px;
height: calc(100vh - 60px);
position: relative;
}
/* 3D VIEWER SECTION */
#scene-container {
position: relative;
background: radial-gradient(circle at center, #1e293b 0%, #0f172a 100%);
overflow: hidden;
cursor: grab;
}
#scene-container:active {
cursor: grabbing;
}
.scene-controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 1rem;
background: var(--panel-bg);
padding: 0.75rem 1.5rem;
border-radius: 50px;
backdrop-filter: var(--glass);
border: 1px solid var(--border);
z-index: 10;
}
.btn-control {
background: transparent;
border: none;
color: var(--text-main);
font-size: 1.2rem;
cursor: pointer;
transition: transform 0.2s, color 0.2s;
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.btn-control:hover {
background: rgba(255,255,255,0.1);
color: var(--accent);
transform: scale(1.1);
}
.btn-control.active {
color: var(--primary);
background: rgba(59, 130, 246, 0.2);
}
/* INFO / SIDEBAR SECTION */
aside {
background: var(--panel-bg);
backdrop-filter: var(--glass);
border-left: 1px solid var(--border);
display: flex;
flex-direction: column;
overflow-y: auto;
padding: 1.5rem;
}
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
background: rgba(0,0,0,0.2);
padding: 0.25rem;
border-radius: 8px;
}
.tab-btn {
flex: 1;
padding: 0.6rem;
border: none;
background: transparent;
color: var(--text-muted);
font-weight: 600;
cursor: pointer;
border-radius: 6px;
transition: all 0.3s;
}
.tab-btn.active {
background: var(--primary);
color: white;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.content-panel {
display: none;
animation: fadeIn 0.4s ease;
}
.content-panel.active {
display: block;
}
h2 {
font-size: 1.5rem;
margin-bottom: 1rem;
color: white;
border-bottom: 2px solid var(--primary);
padding-bottom: 0.5rem;
display: inline-block;
}
.part-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.part-item {
background: rgba(255,255,255,0.05);
padding: 1rem;
border-radius: 8px;
cursor: pointer;
border: 1px solid transparent;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 10px;
}
.part-item:hover, .part-item.selected {
background: rgba(59, 130, 246, 0.15);
border-color: var(--primary);
}
.part-icon {
color: var(--accent);
width: 24px;
text-align: center;
}
.part-details-view {
margin-top: 1.5rem;
background: rgba(0,0,0,0.2);
padding: 1.5rem;
border-radius: 12px;
border: 1px solid var(--border);
}
.detail-label {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-muted);
margin-bottom: 0.25rem;
}
.detail-value {
font-size: 1.1rem;
margin-bottom: 1rem;
color: var(--text-main);
}
/* TUTORIAL STYLES */
.step-container {
position: relative;
padding-left: 2rem;
border-left: 2px solid var(--border);
}
.step {
margin-bottom: 2rem;
position: relative;
}
.step::before {
content: '';
position: absolute;
left: -2.4rem;
top: 0;
width: 1rem;
height: 1rem;
background: var(--dark-bg);
border: 2px solid var(--text-muted);
border-radius: 50%;
transition: all 0.3s;
}
.step.active::before {
background: var(--accent);
border-color: var(--accent);
box-shadow: 0 0 10px var(--accent);
}
.step-title {
font-weight: 700;
font-size: 1.1rem;
margin-bottom: 0.5rem;
}
.step-desc {
font-size: 0.9rem;
color: var(--text-muted);
line-height: 1.5;
}
.tutorial-nav {
display: flex;
justify-content: space-between;
margin-top: 2rem;
}
.btn {
padding: 0.6rem 1.2rem;
border-radius: 6px;
border: none;
font-weight: 600;
cursor: pointer;
transition: 0.2s;
}
.btn-primary {
background: var(--primary);
color: white;
}
.btn-primary:hover { background: #2563eb; }
.btn-secondary {
background: rgba(255,255,255,0.1);
color: white;
}
.btn-secondary:hover { background: rgba(255,255,255,0.2); }
/* RESPONSIVE */
@media (max-width: 900px) {
main {
grid-template-columns: 1fr;
grid-template-rows: 50vh 1fr;
overflow-y: auto;
}
aside {
border-left: none;
border-top: 1px solid var(--border);
}
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Loading Overlay */
#loader {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: var(--dark-bg);
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
flex-direction: column;
gap: 1rem;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(255,255,255,0.1);
border-left-color: var(--accent);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin { 100% { transform: rotate(360deg); } }
</style>
</head>
<body>
<!-- LOADER -->
<div id="loader">
<div class="spinner"></div>
<p style="color: var(--text-muted); font-size: 0.9rem;">Chargement du modèle 3D...</p>
</div>
<!-- HEADER -->
<header>
<div class="logo">
<i class="fa-solid fa-cube"></i> AutoTech 3D
</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
<i class="fa-solid fa-code"></i> Built with anycoder
</a>
</header>
<!-- MAIN CONTENT -->
<main>
<!-- 3D SCENE -->
<section id="scene-container">
<div class="scene-controls">
<button class="btn-control active" id="btn-rotate" title="Rotation Auto"><i class="fa-solid fa-sync"></i></button>
<button class="btn-control" id="btn-explode" title="Vue Éclatée"><i class="fa-solid fa-arrows-alt-v"></i></button>
<button class="btn-control" id="btn-wireframe" title="Mode Fil de Fer"><i class="fa-solid fa-border-all"></i></button>
</div>
</section>
<!-- SIDEBAR INFO -->
<aside>
<div class="tabs">
<button class="tab-btn active" data-tab="parts">Pièces</button>
<button class="tab-btn" data-tab="specs">Specs</button>
<button class="tab-btn" data-tab="tutorial">Tutoriel</button>
</div>
<!-- TAB: PARTS -->
<div id="parts" class="content-panel active">
<h2>Composants</h2>
<p style="color: var(--text-muted); margin-bottom: 1rem; font-size: 0.9rem;">
Sélectionnez une pièce pour voir les détails techniques et sa localisation.
</p>
<ul class="part-list" id="part-list-container">
<!-- Javascript will populate this -->
</ul>
<div id="part-detail-box" class="part-details-view" style="display:none;">
<h3 id="detail-title" style="color: var(--accent); margin-bottom: 0.5rem;">Nom Pièce</h3>
<div class="detail-label">Description</div>
<p id="detail-desc" class="detail-value" style="font-size: 0.95rem; line-height: 1.4;">Description...</p>
<div class="detail-label">Matériau</div>
<p id="detail-material" class="detail-value">Acier / Alliage</p>
<div class="detail-label">Fonction</div>
<p id="detail-function" class="detail-value">Fonction principale</p>
</div>
</div>
<!-- TAB: TUTORIAL -->
<div id="tutorial" class="content-panel">
<h2>Guide d'Assemblage</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem; font-size: 0.9rem;">
Suivez ces étapes pour comprendre l'architecture du véhicule.
</p>
<div id="steps-container" class="step-container">
<!-- Steps generated by JS -->
</div>
<div class="tutorial-nav">
<button class="btn btn-secondary" id="prev-step">Précédent</button>
<span id="step-counter" style="display: flex; align-items: center; color: var(--text-muted);">1 / 5</span>
<button class="btn btn-primary" id="next-step">Suivant</button>
</div>
</div>
<!-- TAB: SPECS -->
<div id="specs" class="content-panel">
<h2>Spécifications Générales</h2>
<div style="display: grid; gap: 1rem;">
<div style="background: rgba(255,255,255,0.05); padding: 1rem; border-radius: 8px;">
<div class="detail-label">Type de Véhicule</div>
<div class="detail-value">Berline Sportive Compacte</div>
</div>
<div style="background: rgba(255,255,255,0.05); padding: 1rem; border-radius: 8px;">
<div class="detail-label">Moteur</div>
<div class="detail-value">V6 Bi-Turbo 3.0L</div>
</div>
<div style="background: rgba(255,255,255,0.05); padding: 1rem; border-radius: 8px;">
<div class="detail-label">Puissance</div>
<div class="detail-value">450 Ch</div>
</div>
<div style="background: rgba(255,255,255,0.05); padding: 1rem; border-radius: 8px;">
<div class="detail-label">Poids</div>
<div class="detail-value">1,450 kg</div>
</div>
</div>
</div>
</aside>
</main>
<!-- JAVASCRIPT LOGIC -->
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// --- DATA: Car Parts & Tutorial ---
const carData = {
chassis: {
name: "Châssis & Coque",
icon: "fa-car-side",
desc: "La structure principale qui assure la rigidité et la sécurité des passagers. Fabriqué en acier haute résistance et aluminium.",
material: "Acier/Aluminium",
function: "Supporter tous les composants et protéger les occupants."
},
engine: {
name: "Moteur",
icon: "fa-cogs",
desc: "Le cœur du véhicule. Il convertit l'énergie chimique du carburant en énergie mécanique pour entraîner les roues.",
material: "Alliage d'aluminium, Fonte",
function: "Générer la puissance motrice."
},
wheels: {
name: "Roues & Pneus",
icon: "fa-circle",
desc: "Assurent le contact avec la route, la transmission de la traction et le freinage.",
material: "Caucchouc, Jantes Alliage",
function: "Mobilité et adhérence routière."
},
interior: {
name: "Habitacle",
icon: "fa-chair",
desc: "L'espace passager incluant le tableau de bord, les sièges et les systèmes de contrôle.",
material: "Cuir, Polymères, tissus",
function: "Confort et interface de conduite."
}
};
const tutorialSteps = [
{
title: "1. Préparation du Châssis",
desc: "Le montage commence toujours par le châssis. C'est la fondation sur laquelle tout le reste sera boulonné.",
target: "chassis"
},
{
title: "2. Installation du Groupe Moto-Propulseur",
desc: "Le moteur et la boîte de vitesses sont abaissés avec précision dans le compartiment moteur et fixés aux silent-blocs.",
target: "engine"
},
{
title: "3. Système de Freinage & Roues",
desc: "Les étriers, disques et roues sont installés. Étape cruciale pour la sécurité dynamique du véhicule.",
target: "wheels"
},
{
title: "4. Câblage et Électronique",
desc: "Le faisceau électrique est connecté (ECU, capteurs, lumières) avant la fermeture de la carrosserie.",
target: "chassis" // Highlighting chassis as a proxy for wiring hidden inside
},
{
title: "5. Assemblage de l'Habitacle",
desc: "Pose du tableau de bord, des sièges et des garnitures intérieures pour finaliser le confort.",
target: "interior"
}
];
// --- THREE.JS VARIABLES ---
let scene, camera, renderer, controls;
let carGroup = new THREE.Group();
let partsMeshes = {}; // Map to store mesh references by name
let isAutoRotating = true;
let isExploded = false;
let originalPositions = {};
// --- INITIALIZATION ---
function init() {
const container = document.getElementById('scene-container');
// Scene setup
scene = new THREE.Scene();
// scene.background = new THREE.Color(0x0f172a); // Handled by CSS gradient mostly
// Camera setup
camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 0.1, 100);
camera.position.set(5, 3, 6);
// Renderer setup
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
// Controls
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 3;
controls.maxDistance = 15;
// Lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(5, 10, 7);
dirLight.castShadow = true;
scene.add(dirLight);
const blueLight = new THREE.PointLight(0x3b82f6, 2, 10);
blueLight.position.set(-2, 1, -2);
scene.add(blueLight);
// Build the procedural car
buildProceduralCar();
scene.add(carGroup);
// Remove Loader
document.getElementById('loader').style.display = 'none';
// Events
window.addEventListener('resize', onWindowResize);
animate();
// Init UI
initUI();
}
// --- PROCEDURAL CAR BUILDER ---
function buildProceduralCar() {
const materialBody = new THREE.MeshStandardMaterial({
color: 0x334155,
roughness: 0.4,
metalness: 0.8
});
const materialAccent = new THREE.MeshStandardMaterial({
color: 0x06b6d4,
roughness: 0.2,
metalness: 0.5,
emissive: 0x06b6d4,
emissiveIntensity: 0.2
});
const materialGlass = new THREE.MeshStandardMaterial({
color: 0x94a3b8,
roughness: 0.1,
metalness: 0.9,
transparent: true,
opacity: 0.7
});
const materialInterior = new THREE.MeshStandardMaterial({
color: 0x1e293b,
roughness: 0.8
});
// 1. CHASSIS (Lower Body)
const chassisGeo = new THREE.BoxGeometry(2, 0.5, 4.2);
const chassisMesh = new THREE.Mesh(chassisGeo, materialBody);
chassisMesh.position.y = 0.5;
chassisMesh.castShadow = true;
chassisMesh.name = "chassis";
carGroup.add(chassisMesh);
partsMeshes['chassis'] = chassisMesh;
// 2. INTERIOR (Cabin)
const cabinGeo = new THREE.BoxGeometry(1.6, 0.6, 2.2);
const cabinMesh = new THREE.Mesh(cabinGeo, materialInterior);
cabinMesh.position.y = 1.0;
cabinMesh.position.z = -0.2;
cabinMesh.name = "interior";
carGroup.add(cabinMesh);
partsMeshes['interior'] = cabinMesh;
// 3. GLASS (Windshield)
const glassGeo = new THREE.BoxGeometry(1.65, 0.55, 1.4);
// Angle it slightly
const glassMesh = new THREE.Mesh(glassGeo, materialGlass);
glassMesh.position.set(0, 1.35, -0.5);
glassMesh.rotation.x = -0.3;
carGroup.add(glassMesh);
// 4. ENGINE BLOCK (Visible slightly through rear or represented)
const engineGeo = new THREE.BoxGeometry(1.2, 0.6, 1.0);
const engineMesh = new THREE.Mesh(engineGeo, materialAccent);
engineMesh.position.set(0, 0.8, 1.4);
engineMesh.name = "engine";
carGroup.add(engineMesh);
partsMeshes['engine'] = engineMesh;
// 5. WHEELS (4 wheels)
const wheelGeo = new THREE.CylinderGeometry(0.35, 0.35, 0.25, 32);
const wheelMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.9 });
const rimGeo = new THREE.CylinderGeometry(0.2, 0.2, 0.26, 16);
const rimMat = new THREE.MeshStandardMaterial({ color: 0xcccccc, metalness: 0.9 });
const wheelPositions = [
{ x: -1.1, z: 1.2 }, { x: 1.1, z: 1.2 },
{ x: -1.1, z: -1.2 }, { x: 1.1, z: -1.2 }
];
// Create a group for wheels to handle them as one unit for highlighting
const wheelsGroup = new THREE.Group();
wheelsGroup.name = "wheels";
wheelPositions.forEach(pos => {
const wheel = new THREE.Mesh(wheelGeo, wheelMat);
wheel.rotation.z = Math.PI / 2;
wheel.position.set(pos.x, 0.35, pos.z);
const rim = new THREE.Mesh(rimGeo, rimMat);
rim.rotation.z = Math.PI / 2;
rim.position.set(pos.x, 0.35, pos.z);
wheelsGroup.add(wheel);
wheelsGroup.add(rim);
});
carGroup.add(wheelsGroup);
partsMeshes['wheels'] = wheelsGroup;
// Save original positions for explosion effect
carGroup.traverse((child) => {
if(child.isMesh || child.isGroup) {
originalPositions[child.uuid] = child.position.clone();
}
});
}
// --- ANIMATION LOOP ---
function animate() {
requestAnimationFrame(animate);
if (isAutoRotating) {
carGroup.rotation.y += 0.005;
}
controls.update();
renderer.render(scene, camera);
}
// --- WINDOW RESIZE ---
function onWindowResize() {
const container = document.getElementById('scene-container');
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
}
// --- UI LOGIC ---
function initUI() {
// 1. Populate Parts List
const listContainer = document.getElementById('part-list-container');
Object.keys(carData).forEach(key => {
const part = carData[key];
const li = document.createElement('li');
li.className = 'part-item';
li.innerHTML = `
<div class="part-icon"><i class="fa-solid ${part.icon}"></i></div>
<span>${part.name}</span>
`;
li.onclick = () => selectPart(key, li);
listContainer.appendChild(li);
});
// 2. Populate Tutorial
renderTutorialStep();
// 3. Tab Switching
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
// Remove active class from all tabs and contents
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.content-panel').forEach(c => c.classList.remove('active'));
// Add active to clicked
e.target.classList.add('active');
document.getElementById(e.target.dataset.tab).classList.add('active');
});
});
// 4. Scene Controls
document.getElementById('btn-rotate').onclick = function() {
isAutoRotating = !isAutoRotating;
this.classList.toggle('active');
};
document.getElementById('btn-explode').onclick = function() {
isExploded = !isExploded;
this.classList.toggle('active');
toggleExplosion();
};
document.getElementById('btn-wireframe').onclick = function() {
this.classList.toggle('active');
carGroup.traverse(child => {
if (child.isMesh) {
child.material.wireframe = !child.material.wireframe;
}
});
};
// Tutorial Navigation
document.getElementById('next-step').onclick = nextTutorialStep;
document.getElementById('prev-step').onclick = prevTutorialStep;
}
// --- INTERACTION FUNCTIONS ---
function highlightPart(partKey) {
// Reset all colors
carGroup.traverse(child => {
if(child.isMesh && child.userData.originalHex) {
child.material.emissive.setHex(child.userData.originalHex);
}
});
const target = partsMeshes[partKey];
if (target) {
if (target.isGroup) {
target.children.forEach(c => {
if(c.isMesh) {
c.userData.originalHex = c.material.emissive.getHex();
c.material.emissive.setHex(0x3b82f6);
c.material.emissiveIntensity = 0.8;
}
});
} else if (target.isMesh) {
target.userData.originalHex = target.material.emissive.getHex();
target.material.emissive.setHex(0x3b82f6);
target.material.emissiveIntensity = 0.8;
}
}
}
function selectPart(key, domElement) {
// UI Update
document.querySelectorAll('.part-item').forEach(i => i.classList.remove('selected'));
domElement.classList.add('selected');
// Show Details
const box = document.getElementById('part-detail-box');
box.style.display = 'block';
const data = carData[key];
document.getElementById('detail-title').innerText = data.name;
document.getElementById('detail-desc').innerText = data.desc;
document.getElementById('detail-material').innerText = data.material;
document.getElementById('detail-function').innerText = data.function;
// 3D Highlight
highlightPart(key);
// Stop rotation to let user see
isAutoRotating = false;
document.getElementById('btn-rotate').classList.remove('active');
}
function toggleExplosion() {
const offset = isExploded ? 0.5 : 0;
// Animate positions
carGroup.traverse((child) => {
if (child.name === 'wheels') {
child.position.x = (child.position.x > 0 ? 1 : -1) * (1.1 + offset * 2);
} else if (child.name === 'engine') {
child.position.z = 1.4 + offset * 2;
} else if (child.name === 'interior') {
child.position.y = 1.0 + offset * 2;
}
});
}
// --- TUTORIAL LOGIC ---
let currentStepIndex = 0;
function renderTutorialStep() {
const container = document.getElementById('steps-container');
container.innerHTML = '';
tutorialSteps.forEach((step, index) => {
const div = document.createElement('div');
div.className = `step ${index === currentStepIndex ? 'active' : ''}`;
div.innerHTML = `
<div class="step-title">${step.title}</div>
<div class="step-desc">${step.desc}</div>
`;
container.appendChild(div);
});
document.getElementById('step-counter').innerText = `${currentStepIndex + 1} / ${tutorialSteps.length}`;
// Highlight relevant part in 3D
const targetPart = tutorialSteps[currentStepIndex].target;
highlightPart(targetPart);
}
function nextTutorialStep() {
if (currentStepIndex < tutorialSteps.length - 1) {
currentStepIndex++;
renderTutorialStep();
}
}
function prevTutorialStep() {
if (currentStepIndex > 0) {
currentStepIndex--;
renderTutorialStep();
}
}
// Start App
init();
</script>
</body>
</html>