ceramic-static/assiette.html
isUnknown f530bf7fcb
All checks were successful
Deploy / Deploy to Production (push) Successful in 11s
Fix TrackballControls : appel handleResize après affichage du canvas
Le contrôle s'initialisait avec des dimensions 0×0 (canvas display:none),
rendant toute interaction impossible. handleResize() est maintenant appelé
une fois le modèle chargé et le canvas visible, puis à chaque redimensionnement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 14:28:18 +02:00

463 lines
19 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Assiette complète — CERAMIQ+</title>
<link rel="stylesheet" href="style.css">
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.169.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.169.0/examples/jsm/"
}
}
</script>
<style>
.media-viewer::before { display: none; }
.media-viewer { background: var(--c-bg-subtle); }
.viewer-progress-track {
width: 160px;
height: 1px;
background: #ccc;
}
.viewer-progress-bar {
height: 100%;
width: 0%;
background: #111;
transition: width 0.12s linear;
}
</style>
</head>
<body>
<!-- ─── Header ──────────────────────────────────────── -->
<header class="site-header">
<a href="index.html" class="site-logo">CERAMIC+</a>
<nav class="header-nav">
<ul>
<li>Bibliothèque</li>
<li>Écosystème</li>
<li>Évènements</li>
</ul>
</nav>
<div class="header-actions">
<div class="search-wrap">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
</svg>
<input type="text" placeholder="Rechercher une publication…">
</div>
<button class="btn-account btn-account--primary">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
</svg>
Se connecter
</button>
</div>
</header>
<nav class="obj-nav">
<a href="index.html" class="obj-back">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
Retour aux résultats
</a>
<div class="obj-nav-context">
<span class="obj-nav-path">
<a href="index.html">Pièces</a>
<span class="bc-sep"></span>
<a href="#">Céramique traditionnelle</a>
</span>
</div>
<div class="result-nav-filters">
<div class="filter">Argile</div>
<div class="filter">Espagne</div>
<div class="filter">Moulage</div>
</div>
<div class="obj-nav-arrows">
<button class="obj-arrow" title="Fiche précédente">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
<span class="obj-position">1 / 18</span>
<button class="obj-arrow" title="Fiche suivante">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="9 18 15 12 9 6"/>
</svg>
</button>
</div>
</nav>
<!-- ─── Contenu fiche objet ───────────────────────── -->
<div class="obj-wrap">
<!-- ─── En-tête ────────────────────────────────── -->
<header class="obj-header">
<div>
<h1 class="obj-title">Assiette complète</h1>
<div class="card-meta" style="margin-top:10px">
<span class="meta-pill technique">Moulage</span>
<span class="meta-pill">Céramique traditionnelle</span>
<span class="meta-pill country">Espagne</span>
<span class="meta-pill">Argile</span>
<span class="meta-pill">XVIIIe siècle</span>
</div>
</div>
<div class="obj-header-actions">
<button class="btn-action">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
</svg>
Télécharger
</button>
<button class="btn-action" onclick="window.print()">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 6 2 18 2 18 9"/>
<path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/>
<rect x="6" y="14" width="12" height="8"/>
</svg>
Imprimer
</button>
</div>
</header>
<!-- ─── Corps : médias + info ────────────────────── -->
<div class="obj-body">
<!-- ─── Médias ──────────────────────────────────── -->
<section class="obj-media">
<div class="media-viewer">
<canvas id="viewer3d" style="position:absolute;inset:0;width:100%;height:100%;display:none;z-index:1;"></canvas>
<div class="media-viewer-inner" id="viewer-placeholder">
<div class="viewer-progress-track">
<div class="viewer-progress-bar" id="loader-bar"></div>
</div>
</div>
<div class="media-controls" style="z-index:3;">
<button class="ctrl-btn" title="Réinitialiser la vue">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="1 4 1 10 7 10"/>
<path d="M3.51 15a9 9 0 1 0 .49-4.75"/>
</svg>
</button>
<button class="ctrl-btn" title="Zoom avant">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
<line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/>
</svg>
</button>
<button class="ctrl-btn" title="Zoom arrière">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
<line x1="8" y1="11" x2="14" y2="11"/>
</svg>
</button>
<button class="ctrl-btn ctrl-btn--sep" title="Plein écran">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/>
<line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/>
</svg>
</button>
</div>
</div>
<div class="media-thumbs">
<div class="thumb thumb--3d is-active">
<svg class="thumb-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#111" stroke-width="1.5">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/>
</svg>
<span class="thumb-label">3D</span>
</div>
<div class="thumb">
<span class="thumb-label">IMG 1</span>
</div>
<div class="thumb">
<span class="thumb-label">IMG 2</span>
</div>
<div class="thumb thumb--portrait">
<span class="thumb-label">IMG 3</span>
</div>
<div class="thumb">
<svg class="thumb-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#111" stroke-width="1.5">
<polygon points="5 3 19 12 5 21 5 3"/>
</svg>
<span class="thumb-label">VID 1</span>
</div>
<div class="thumb thumb--pdf">
<svg class="thumb-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#111" stroke-width="1.5">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="15" y2="17"/>
</svg>
<span class="thumb-label">PDF</span>
</div>
</div>
</section>
<!-- ─── Panneau d'informations ─────────────────── -->
<aside class="obj-info">
<dl class="info-list">
<div class="info-row">
<dt>Pays / Région</dt>
<dd>Espagne<br><span class="info-sub">Castille-et-León</span></dd>
</div>
<hr class="info-divider">
<div class="info-row">
<dt>Partenaires</dt>
<dd>
Musée de la Céramique de Barcelone<br>
Universidad Complutense de Madrid<br>
<span class="info-sub">Réseau CERAMIQ, Partenaire associé</span>
</dd>
</div>
<hr class="info-divider">
<div class="info-row">
<dt>Auteur de la fiche</dt>
<dd>
Centre de Recherche Céramique Sud-Ouest<br>
</dd>
</div>
<hr class="info-divider">
<div class="info-row">
<dt>Date de publication</dt>
<dd>23 mars 2026<br><span class="info-sub">Mise à jour : 27 avril 2026</span></dd>
</div>
<hr class="info-divider">
<div class="info-row">
<dt>Lien vers la fiche sur le site du partenaire</dt>
<dd>23 mars 2026</dd>
</div>
</dl>
<button class="btn-contact-cta">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
<polyline points="22,6 12,13 2,6"/>
</svg>
Contacter l'auteur de la fiche
</button>
</aside>
</div>
<!-- ─── Description ────────────────────────────── -->
<section class="obj-section">
<h2 class="obj-section-title">Description</h2>
<div class="obj-description">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean congue ligula lorem, in cursus ligula condimentum eget. Fusce interdum nec sem quis euismod. Etiam turpis lorem, pharetra eu eros vel, ultricies dictum orci. Donec semper quam vitae nulla blandit, consectetur ultricies augue consequat. Maecenas fringilla efficitur metus, quis aliquet enim mattis sed. Morbi sed cursus ante. Vivamus rhoncus, mauris id posuere condimentum, augue nisi feugiat turpis, nec luctus sapien nisl vel ipsum.</p>
<p>Nullam auctor elit dictum, imperdiet magna eget, auctor sem. Pellentesque sodales, erat non varius tristique, massa quam scelerisque mi, et finibus purus diam ac turpis. Nam porta, leo ac iaculis blandit, dui augue tempor purus, nec fermentum arcu nulla sit amet erat.</p>
</div>
</section>
<!-- ─── Pièces similaires ─────────────────────── -->
<section class="obj-section">
<h2 class="obj-section-title">Pièces similaires</h2>
<div class="obj-related-grid">
<article class="piece-card">
<div class="card-image"><span class="card-tag">Argile</span></div>
<div class="card-body">
<h2 class="card-name">Écuelle à oreilles</h2>
<div class="card-meta">
<span class="meta-pill technique">Moulage</span>
<span class="meta-pill">Traditionnel</span>
<span class="meta-pill country">Espagne</span>
</div>
</div>
</article>
<article class="piece-card">
<div class="card-image"><span class="card-tag">Kaolin</span></div>
<div class="card-body">
<h2 class="card-name">Bol à poignée</h2>
<div class="card-meta">
<span class="meta-pill technique">Coulage</span>
<span class="meta-pill">Traditionnel</span>
<span class="meta-pill country">Portugal</span>
</div>
</div>
</article>
<article class="piece-card">
<div class="card-image"><span class="card-tag">Argile</span></div>
<div class="card-body">
<h2 class="card-name">Tasse campaniforme</h2>
<div class="card-meta">
<span class="meta-pill technique">Moulage</span>
<span class="meta-pill">Traditionnel</span>
<span class="meta-pill country">Occitanie</span>
</div>
</div>
</article>
<article class="piece-card">
<div class="card-image"><span class="card-tag">Feldspath</span></div>
<div class="card-body">
<h2 class="card-name">Pot à bouillon couvert</h2>
<div class="card-meta">
<span class="meta-pill technique">Coulage</span>
<span class="meta-pill">Traditionnel</span>
<span class="meta-pill country">Espagne</span>
</div>
</div>
</article>
</div>
</section>
</div>
<script type="module">
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
const canvas = document.getElementById('viewer3d');
const pholder = document.getElementById('viewer-placeholder');
const viewer = document.querySelector('.media-viewer');
const bar = document.getElementById('loader-bar');
// ── Renderer ──────────────────────────────────────────
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 0.9;
// ── Scene / Camera ────────────────────────────────────
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(40, 1, 0.001, 1000);
// ── Lights ────────────────────────────────────────────
scene.add(new THREE.AmbientLight(0xffffff, 1.5));
const key = new THREE.DirectionalLight(0xffffff, 2.5);
key.position.set(3, 5, 3);
scene.add(key);
const fill = new THREE.DirectionalLight(0xffffff, 0.6);
fill.position.set(-3, 2, -2);
scene.add(fill);
// ── Controls ──────────────────────────────────────────
const controls = new TrackballControls(camera, canvas);
controls.rotateSpeed = 3.0;
controls.dynamicDampingFactor = 0.12;
// ── Model ─────────────────────────────────────────────
let initCamPos, initTarget;
let mixer = null;
const clock = new THREE.Clock();
new GLTFLoader().load(
'assets/3D-samples/assiette-anim.gltf',
(gltf) => {
const model = gltf.scene;
const box = new THREE.Box3().setFromObject(model);
const c = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
const dim = Math.max(size.x, size.y, size.z);
model.position.sub(c);
scene.add(model);
if (gltf.animations.length) {
mixer = new THREE.AnimationMixer(model);
gltf.animations.forEach(clip => mixer.clipAction(clip).play());
}
camera.near = dim * 0.001;
camera.far = dim * 100;
camera.position.set(0, dim * 0.25, dim * 2.2);
controls.target.set(0, 0, 0);
controls.update();
initCamPos = camera.position.clone();
initTarget = controls.target.clone();
bar.style.width = '100%';
canvas.style.display = 'block';
pholder.style.display = 'none';
controls.handleResize();
},
({ loaded, total }) => {
if (total) bar.style.width = `${Math.round(loaded / total * 100)}%`;
},
(err) => {
bar.style.background = '#c00';
console.error(err);
}
);
// ── Resize ────────────────────────────────────────────
function resize() {
const w = viewer.clientWidth, h = viewer.clientHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
controls.handleResize();
}
resize();
new ResizeObserver(resize).observe(viewer);
// ── Render loop ───────────────────────────────────────
(function frame() {
requestAnimationFrame(frame);
const delta = clock.getDelta();
if (mixer) mixer.update(delta);
controls.update();
renderer.render(scene, camera);
})();
// ── Boutons de contrôle ───────────────────────────────
document.querySelector('[title="Réinitialiser la vue"]')?.addEventListener('click', () => {
if (!initCamPos) return;
camera.position.copy(initCamPos);
controls.target.copy(initTarget);
controls.update();
});
document.querySelector('[title="Zoom avant"]')?.addEventListener('click', () => {
const d = camera.position.distanceTo(controls.target);
const dir = new THREE.Vector3().subVectors(controls.target, camera.position).normalize();
camera.position.addScaledVector(dir, d * 0.25);
controls.update();
});
document.querySelector('[title="Zoom arrière"]')?.addEventListener('click', () => {
const d = camera.position.distanceTo(controls.target);
const dir = new THREE.Vector3().subVectors(controls.target, camera.position).normalize();
camera.position.addScaledVector(dir, -d * 0.3);
controls.update();
});
document.querySelector('[title="Plein écran"]')?.addEventListener('click', () => {
document.fullscreenElement ? document.exitFullscreen() : viewer.requestFullscreen();
});
</script>
<!-- ─── Footer ──────────────────────────────────────── -->
<footer class="site-footer">
<span class="footer-logo">CERAMIQ+</span>
<div class="footer-links">
<a href="#">Mentions légales</a>
<a href="#">RGPD</a>
<a href="#">Contact</a>
</div>
</footer>
</body>
</html>