add style frerot le boss
This commit is contained in:
parent
5698eba6a8
commit
90e229a763
11
.claude/launch.json
Normal file
11
.claude/launch.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.0.1",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "rebours-dev",
|
||||||
|
"runtimeExecutable": "pnpm",
|
||||||
|
"runtimeArgs": ["dev"],
|
||||||
|
"port": 4321
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
577
public/main.js
577
public/main.js
@ -1,9 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* REBOUR — Main Script
|
* REBOURS — Main Script
|
||||||
|
* CAD/CAO-inspired interface · GSAP ScrollTrigger · Technical drawing overlays · Ambient sound
|
||||||
*/
|
*/
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
// ---- CONFIG ----
|
||||||
|
const isMobile = window.innerWidth <= 600;
|
||||||
|
const isTouch = 'ontouchstart' in window;
|
||||||
|
|
||||||
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
// ---- HEADER HEIGHT → CSS VAR ----
|
// ---- HEADER HEIGHT → CSS VAR ----
|
||||||
const setHeaderHeight = () => {
|
const setHeaderHeight = () => {
|
||||||
const h = document.querySelector('.header')?.offsetHeight || 44;
|
const h = document.querySelector('.header')?.offsetHeight || 44;
|
||||||
@ -12,58 +19,76 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
setHeaderHeight();
|
setHeaderHeight();
|
||||||
window.addEventListener('resize', setHeaderHeight);
|
window.addEventListener('resize', setHeaderHeight);
|
||||||
|
|
||||||
// ---- CUSTOM CURSOR ----
|
// ==========================================================
|
||||||
const cursorDot = document.querySelector('.cursor-dot');
|
// 1. CAD CROSSHAIR CURSOR WITH X/Y COORDINATES
|
||||||
const cursorOutline = document.querySelector('.cursor-outline');
|
// ==========================================================
|
||||||
|
|
||||||
let mouseX = 0, mouseY = 0;
|
let attachCursorHover = () => {};
|
||||||
let outlineX = 0, outlineY = 0;
|
|
||||||
let rafId = null;
|
if (!isMobile && !isTouch) {
|
||||||
|
const cursorH = document.createElement('div');
|
||||||
|
cursorH.className = 'cad-h';
|
||||||
|
const cursorV = document.createElement('div');
|
||||||
|
cursorV.className = 'cad-v';
|
||||||
|
const cursorCenter = document.createElement('div');
|
||||||
|
cursorCenter.className = 'cad-center';
|
||||||
|
const cursorCoords = document.createElement('div');
|
||||||
|
cursorCoords.className = 'cad-coords';
|
||||||
|
|
||||||
|
[cursorH, cursorV, cursorCenter, cursorCoords].forEach(el => document.body.appendChild(el));
|
||||||
|
|
||||||
|
let visible = false;
|
||||||
|
|
||||||
window.addEventListener('mousemove', (e) => {
|
window.addEventListener('mousemove', (e) => {
|
||||||
mouseX = e.clientX;
|
const x = e.clientX;
|
||||||
mouseY = e.clientY;
|
const y = e.clientY;
|
||||||
// Première fois : initialise l'outline à la position courante et rend visible
|
|
||||||
if (outlineX === 0 && outlineY === 0) {
|
|
||||||
outlineX = mouseX;
|
|
||||||
outlineY = mouseY;
|
|
||||||
cursorDot.style.opacity = '1';
|
|
||||||
cursorOutline.style.opacity = '1';
|
|
||||||
}
|
|
||||||
cursorDot.style.transform = `translate(calc(-50% + ${mouseX}px), calc(-50% + ${mouseY}px))`;
|
|
||||||
if (!rafId) rafId = requestAnimationFrame(animateOutline);
|
|
||||||
}, { once: false });
|
|
||||||
|
|
||||||
function animateOutline() {
|
cursorH.style.left = (x - 20) + 'px';
|
||||||
rafId = null;
|
cursorH.style.top = y + 'px';
|
||||||
outlineX += (mouseX - outlineX) * 0.18;
|
cursorV.style.left = x + 'px';
|
||||||
outlineY += (mouseY - outlineY) * 0.18;
|
cursorV.style.top = (y - 20) + 'px';
|
||||||
cursorOutline.style.transform = `translate(calc(-50% + ${outlineX}px), calc(-50% + ${outlineY}px))`;
|
cursorCenter.style.left = x + 'px';
|
||||||
if (Math.abs(mouseX - outlineX) > 0.1 || Math.abs(mouseY - outlineY) > 0.1) {
|
cursorCenter.style.top = y + 'px';
|
||||||
rafId = requestAnimationFrame(animateOutline);
|
cursorCoords.style.left = (x + 16) + 'px';
|
||||||
}
|
cursorCoords.style.top = (y + 14) + 'px';
|
||||||
}
|
|
||||||
|
|
||||||
let hoverCount = 0;
|
cursorCoords.textContent =
|
||||||
|
'X:' + String(Math.round(x)).padStart(4, '0') +
|
||||||
|
' Y:' + String(Math.round(y)).padStart(4, '0');
|
||||||
|
|
||||||
function attachCursorHover(elements) {
|
if (!visible) {
|
||||||
|
visible = true;
|
||||||
|
[cursorH, cursorV, cursorCenter, cursorCoords].forEach(el => {
|
||||||
|
el.style.opacity = '1';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
attachCursorHover = (elements) => {
|
||||||
elements.forEach(el => {
|
elements.forEach(el => {
|
||||||
el.addEventListener('mouseenter', () => {
|
el.addEventListener('mouseenter', () => {
|
||||||
cursorOutline.style.width = '38px';
|
cursorH.classList.add('cad-hover');
|
||||||
cursorOutline.style.height = '38px';
|
cursorV.classList.add('cad-hover');
|
||||||
|
cursorCenter.classList.add('cad-hover');
|
||||||
});
|
});
|
||||||
el.addEventListener('mouseleave', () => {
|
el.addEventListener('mouseleave', () => {
|
||||||
cursorOutline.style.width = '26px';
|
cursorH.classList.remove('cad-hover');
|
||||||
cursorOutline.style.height = '26px';
|
cursorV.classList.remove('cad-hover');
|
||||||
|
cursorCenter.classList.remove('cad-hover');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
attachCursorHover(document.querySelectorAll(
|
||||||
|
'a, button, input, .product-card, summary, .panel-close'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
attachCursorHover(document.querySelectorAll('a, button, input, .product-card, summary, .panel-close'));
|
// ==========================================================
|
||||||
|
// 2. INTERACTIVE GRID
|
||||||
|
// ==========================================================
|
||||||
|
|
||||||
// ---- INTERACTIVE GRID ----
|
|
||||||
const gridContainer = document.getElementById('interactive-grid');
|
const gridContainer = document.getElementById('interactive-grid');
|
||||||
const CELL = window.innerWidth <= 600 ? 38 : 60;
|
|
||||||
const COLORS = [
|
const COLORS = [
|
||||||
'rgba(232,168,0,0.45)',
|
'rgba(232,168,0,0.45)',
|
||||||
'rgba(232,168,0,0.32)',
|
'rgba(232,168,0,0.32)',
|
||||||
@ -73,7 +98,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
function buildGrid() {
|
function buildGrid() {
|
||||||
if (!gridContainer) return;
|
if (!gridContainer) return;
|
||||||
gridContainer.innerHTML = '';
|
gridContainer.innerHTML = '';
|
||||||
const CELL = window.innerWidth <= 600 ? 38 : 60;
|
const CELL = isMobile ? 38 : 60;
|
||||||
const cols = Math.ceil(window.innerWidth / CELL);
|
const cols = Math.ceil(window.innerWidth / CELL);
|
||||||
const rows = Math.ceil(window.innerHeight / CELL);
|
const rows = Math.ceil(window.innerHeight / CELL);
|
||||||
gridContainer.style.display = 'grid';
|
gridContainer.style.display = 'grid';
|
||||||
@ -99,12 +124,316 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
let rt;
|
let rt;
|
||||||
window.addEventListener('resize', () => { clearTimeout(rt); rt = setTimeout(buildGrid, 150); });
|
window.addEventListener('resize', () => { clearTimeout(rt); rt = setTimeout(buildGrid, 150); });
|
||||||
|
|
||||||
// ---- PRODUCT PANEL ----
|
// ==========================================================
|
||||||
const panel = document.getElementById('product-panel');
|
// 3. GSAP SCROLL ANIMATIONS — CAD REVEAL
|
||||||
const panelClose = document.getElementById('panel-close');
|
// ==========================================================
|
||||||
|
|
||||||
|
// ---- Hero parallax ----
|
||||||
|
const heroImg = document.querySelector('.hero-img');
|
||||||
|
if (heroImg) {
|
||||||
|
gsap.to(heroImg, {
|
||||||
|
yPercent: 15,
|
||||||
|
ease: 'none',
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: '.hero',
|
||||||
|
start: 'top top',
|
||||||
|
end: 'bottom top',
|
||||||
|
scrub: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Collection header: construction line draw-in ----
|
||||||
|
const collectionHeader = document.querySelector('.collection-header');
|
||||||
|
if (collectionHeader) {
|
||||||
|
// Add a construction line element
|
||||||
|
const line = document.createElement('div');
|
||||||
|
line.className = 'cad-construction-line';
|
||||||
|
collectionHeader.appendChild(line);
|
||||||
|
|
||||||
|
gsap.from(line, {
|
||||||
|
scaleX: 0,
|
||||||
|
transformOrigin: 'left center',
|
||||||
|
duration: 0.8,
|
||||||
|
ease: 'power2.out',
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: collectionHeader,
|
||||||
|
start: 'top 85%',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Product cards: staggered CAD reveal ----
|
||||||
const cards = document.querySelectorAll('.product-card');
|
const cards = document.querySelectorAll('.product-card');
|
||||||
|
|
||||||
// Champs du panel
|
cards.forEach((card, i) => {
|
||||||
|
const img = card.querySelector('.card-img-wrap img');
|
||||||
|
const meta = card.querySelector('.card-meta');
|
||||||
|
const imgWrap = card.querySelector('.card-img-wrap');
|
||||||
|
|
||||||
|
if (!img) return;
|
||||||
|
|
||||||
|
// Create a CAD scan line per card
|
||||||
|
const scanLine = document.createElement('div');
|
||||||
|
scanLine.className = 'card-scanline';
|
||||||
|
imgWrap.style.position = 'relative';
|
||||||
|
imgWrap.appendChild(scanLine);
|
||||||
|
|
||||||
|
// Create corner marks per card
|
||||||
|
const cornerOverlay = document.createElement('div');
|
||||||
|
cornerOverlay.className = 'card-corners';
|
||||||
|
cornerOverlay.innerHTML =
|
||||||
|
'<span class="cc cc-tl"></span>' +
|
||||||
|
'<span class="cc cc-tr"></span>' +
|
||||||
|
'<span class="cc cc-br"></span>' +
|
||||||
|
'<span class="cc cc-bl"></span>';
|
||||||
|
imgWrap.appendChild(cornerOverlay);
|
||||||
|
|
||||||
|
// Create coordinate annotation per card
|
||||||
|
const coordTag = document.createElement('div');
|
||||||
|
coordTag.className = 'card-coord-tag';
|
||||||
|
coordTag.textContent = `[${String(i + 1).padStart(3, '0')}] — ${img.naturalWidth || '1024'} × ${img.naturalHeight || '1024'} px`;
|
||||||
|
imgWrap.appendChild(coordTag);
|
||||||
|
|
||||||
|
// GSAP timeline triggered on scroll
|
||||||
|
const tl = gsap.timeline({
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: card,
|
||||||
|
start: 'top 88%',
|
||||||
|
toggleActions: 'play none none none',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 1. Image clip-path reveal (bottom → top scan)
|
||||||
|
tl.fromTo(img,
|
||||||
|
{ clipPath: 'inset(100% 0 0 0)', filter: 'brightness(1.8) grayscale(100%)' },
|
||||||
|
{ clipPath: 'inset(0% 0 0 0)', filter: 'brightness(1) grayscale(15%)',
|
||||||
|
duration: 0.9, ease: 'power3.out' },
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Scan line sweeps up
|
||||||
|
tl.fromTo(scanLine,
|
||||||
|
{ top: '100%', opacity: 1 },
|
||||||
|
{ top: '-2%', opacity: 0, duration: 0.9, ease: 'power3.out' },
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Corner brackets appear
|
||||||
|
tl.fromTo(cornerOverlay.querySelectorAll('.cc'),
|
||||||
|
{ opacity: 0, scale: 0.5 },
|
||||||
|
{ opacity: 1, scale: 1, duration: 0.4, stagger: 0.06, ease: 'back.out(2)' },
|
||||||
|
0.3
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Coordinate tag types in
|
||||||
|
tl.fromTo(coordTag,
|
||||||
|
{ opacity: 0, x: -10 },
|
||||||
|
{ opacity: 1, x: 0, duration: 0.4, ease: 'power2.out' },
|
||||||
|
0.5
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. Meta bar slides in
|
||||||
|
if (meta) {
|
||||||
|
tl.fromTo(meta,
|
||||||
|
{ opacity: 0, y: 8 },
|
||||||
|
{ opacity: 1, y: 0, duration: 0.4, ease: 'power2.out' },
|
||||||
|
0.4
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---- Newsletter section: slide in ----
|
||||||
|
const nlLeft = document.querySelector('.nl-left');
|
||||||
|
const nlRight = document.querySelector('.nl-right');
|
||||||
|
if (nlLeft && nlRight) {
|
||||||
|
gsap.from(nlLeft, {
|
||||||
|
opacity: 0, x: -30, duration: 0.7, ease: 'power2.out',
|
||||||
|
scrollTrigger: { trigger: '.newsletter', start: 'top 80%' },
|
||||||
|
});
|
||||||
|
gsap.from(nlRight, {
|
||||||
|
opacity: 0, x: 30, duration: 0.7, ease: 'power2.out', delay: 0.15,
|
||||||
|
scrollTrigger: { trigger: '.newsletter', start: 'top 80%' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// 4. TECHNICAL DRAWING OVERLAY (panel)
|
||||||
|
// ==========================================================
|
||||||
|
|
||||||
|
const panelImgCol = document.querySelector('.panel-img-col');
|
||||||
|
let techOverlay = null;
|
||||||
|
let techTimeline = null;
|
||||||
|
|
||||||
|
function createTechOverlay(card) {
|
||||||
|
if (techOverlay) techOverlay.remove();
|
||||||
|
|
||||||
|
techOverlay = document.createElement('div');
|
||||||
|
techOverlay.className = 'tech-overlay';
|
||||||
|
|
||||||
|
// Corner brackets
|
||||||
|
['tl', 'tr', 'br', 'bl'].forEach(pos => {
|
||||||
|
const corner = document.createElement('div');
|
||||||
|
corner.className = `tech-corner tech-corner--${pos}`;
|
||||||
|
techOverlay.appendChild(corner);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Center crosshair
|
||||||
|
const centerH = document.createElement('div');
|
||||||
|
centerH.className = 'tech-center-h';
|
||||||
|
const centerV = document.createElement('div');
|
||||||
|
centerV.className = 'tech-center-v';
|
||||||
|
techOverlay.appendChild(centerH);
|
||||||
|
techOverlay.appendChild(centerV);
|
||||||
|
|
||||||
|
// Horizontal dimension line
|
||||||
|
const dimH = document.createElement('div');
|
||||||
|
dimH.className = 'tech-dim tech-dim--h';
|
||||||
|
dimH.innerHTML =
|
||||||
|
'<span class="tech-dim-arrow">◂</span>' +
|
||||||
|
'<span class="tech-dim-line"></span>' +
|
||||||
|
'<span class="tech-dim-text">840</span>' +
|
||||||
|
'<span class="tech-dim-line"></span>' +
|
||||||
|
'<span class="tech-dim-arrow">▸</span>';
|
||||||
|
techOverlay.appendChild(dimH);
|
||||||
|
|
||||||
|
// Vertical dimension line
|
||||||
|
const dimV = document.createElement('div');
|
||||||
|
dimV.className = 'tech-dim tech-dim--v';
|
||||||
|
dimV.innerHTML =
|
||||||
|
'<span class="tech-dim-arrow">▴</span>' +
|
||||||
|
'<span class="tech-dim-line"></span>' +
|
||||||
|
'<span class="tech-dim-text">420</span>' +
|
||||||
|
'<span class="tech-dim-line"></span>' +
|
||||||
|
'<span class="tech-dim-arrow">▾</span>';
|
||||||
|
techOverlay.appendChild(dimV);
|
||||||
|
|
||||||
|
// Reference text
|
||||||
|
const ref = document.createElement('div');
|
||||||
|
ref.className = 'tech-ref';
|
||||||
|
const idx = card ? card.dataset.index : '001';
|
||||||
|
ref.textContent = `REF: ${idx} — SCALE 1:5 — UNIT: mm`;
|
||||||
|
techOverlay.appendChild(ref);
|
||||||
|
|
||||||
|
// Blueprint grid
|
||||||
|
const grid = document.createElement('div');
|
||||||
|
grid.className = 'tech-grid';
|
||||||
|
techOverlay.appendChild(grid);
|
||||||
|
|
||||||
|
// Scan line
|
||||||
|
const scanline = document.createElement('div');
|
||||||
|
scanline.className = 'tech-scanline';
|
||||||
|
techOverlay.appendChild(scanline);
|
||||||
|
|
||||||
|
if (panelImgCol) panelImgCol.appendChild(techOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
function animateTechOverlay() {
|
||||||
|
if (!techOverlay) return;
|
||||||
|
|
||||||
|
// Kill previous timeline
|
||||||
|
if (techTimeline) techTimeline.kill();
|
||||||
|
|
||||||
|
const corners = techOverlay.querySelectorAll('.tech-corner');
|
||||||
|
const centerH = techOverlay.querySelector('.tech-center-h');
|
||||||
|
const centerV = techOverlay.querySelector('.tech-center-v');
|
||||||
|
const dimH = techOverlay.querySelector('.tech-dim--h');
|
||||||
|
const dimV = techOverlay.querySelector('.tech-dim--v');
|
||||||
|
const ref = techOverlay.querySelector('.tech-ref');
|
||||||
|
const grid = techOverlay.querySelector('.tech-grid');
|
||||||
|
const scanline = techOverlay.querySelector('.tech-scanline');
|
||||||
|
|
||||||
|
techTimeline = gsap.timeline();
|
||||||
|
|
||||||
|
// 1. Blueprint grid fades in
|
||||||
|
techTimeline.fromTo(grid,
|
||||||
|
{ opacity: 0 },
|
||||||
|
{ opacity: 1, duration: 0.6, ease: 'power1.in' },
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Scan line sweeps
|
||||||
|
techTimeline.fromTo(scanline,
|
||||||
|
{ top: '0%', opacity: 1 },
|
||||||
|
{ top: '100%', opacity: 0, duration: 1.1, ease: 'power2.inOut' },
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Corner brackets draw in (scale from corner)
|
||||||
|
corners.forEach((c, i) => {
|
||||||
|
const origins = ['top left', 'top right', 'bottom right', 'bottom left'];
|
||||||
|
techTimeline.fromTo(c,
|
||||||
|
{ opacity: 0, scale: 0, borderColor: 'rgba(232,168,0,0)' },
|
||||||
|
{ opacity: 1, scale: 1, borderColor: 'rgba(232,168,0,0.6)',
|
||||||
|
transformOrigin: origins[i],
|
||||||
|
duration: 0.4, ease: 'back.out(1.5)' },
|
||||||
|
0.15 + i * 0.08
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Center crosshair extends
|
||||||
|
techTimeline.fromTo(centerH,
|
||||||
|
{ scaleX: 0, opacity: 0 },
|
||||||
|
{ scaleX: 1, opacity: 1, duration: 0.5, ease: 'power2.out' },
|
||||||
|
0.3
|
||||||
|
);
|
||||||
|
techTimeline.fromTo(centerV,
|
||||||
|
{ scaleY: 0, opacity: 0 },
|
||||||
|
{ scaleY: 1, opacity: 1, duration: 0.5, ease: 'power2.out' },
|
||||||
|
0.35
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. Dimension lines extend
|
||||||
|
techTimeline.fromTo(dimH,
|
||||||
|
{ opacity: 0, scaleX: 0 },
|
||||||
|
{ opacity: 1, scaleX: 1, transformOrigin: 'center center',
|
||||||
|
duration: 0.6, ease: 'power2.out' },
|
||||||
|
0.4
|
||||||
|
);
|
||||||
|
techTimeline.fromTo(dimV,
|
||||||
|
{ opacity: 0, scaleY: 0 },
|
||||||
|
{ opacity: 1, scaleY: 1, transformOrigin: 'center center',
|
||||||
|
duration: 0.6, ease: 'power2.out' },
|
||||||
|
0.45
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6. Reference text types in
|
||||||
|
techTimeline.fromTo(ref,
|
||||||
|
{ opacity: 0, x: -15 },
|
||||||
|
{ opacity: 1, x: 0, duration: 0.4, ease: 'power2.out' },
|
||||||
|
0.6
|
||||||
|
);
|
||||||
|
|
||||||
|
// 7. Panel image scan reveal
|
||||||
|
const panelImg = document.getElementById('panel-img');
|
||||||
|
if (panelImg) {
|
||||||
|
techTimeline.fromTo(panelImg,
|
||||||
|
{ clipPath: 'inset(0 0 100% 0)', filter: 'brightness(1.8) grayscale(100%)' },
|
||||||
|
{ clipPath: 'inset(0 0 0% 0)', filter: 'brightness(1) contrast(1) grayscale(0%)',
|
||||||
|
duration: 1, ease: 'power3.out' },
|
||||||
|
0.05
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideTechOverlay() {
|
||||||
|
if (techTimeline) techTimeline.kill();
|
||||||
|
if (techOverlay) {
|
||||||
|
gsap.to(techOverlay, { opacity: 0, duration: 0.3, onComplete: () => {
|
||||||
|
if (techOverlay) techOverlay.remove();
|
||||||
|
techOverlay = null;
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// 5. PRODUCT PANEL
|
||||||
|
// ==========================================================
|
||||||
|
|
||||||
|
const panel = document.getElementById('product-panel');
|
||||||
|
const panelClose = document.getElementById('panel-close');
|
||||||
|
const panelCards = document.querySelectorAll('.product-card');
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
img: document.getElementById('panel-img'),
|
img: document.getElementById('panel-img'),
|
||||||
index: document.getElementById('panel-index'),
|
index: document.getElementById('panel-index'),
|
||||||
@ -125,16 +454,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const checkoutForm = document.getElementById('checkout-form');
|
const checkoutForm = document.getElementById('checkout-form');
|
||||||
const checkoutSubmitBtn = document.getElementById('checkout-submit-btn');
|
const checkoutSubmitBtn = document.getElementById('checkout-submit-btn');
|
||||||
const checkoutPriceEl = document.querySelector('.checkout-price');
|
const checkoutPriceEl = document.querySelector('.checkout-price');
|
||||||
|
|
||||||
// Current product's stripe key (set dynamically when opening panel)
|
|
||||||
let currentStripeKey = null;
|
let currentStripeKey = null;
|
||||||
|
|
||||||
// Format price in cents to display string
|
|
||||||
function formatPrice(cents) {
|
function formatPrice(cents) {
|
||||||
return (cents / 100).toLocaleString('fr-FR') + ' €';
|
return (cents / 100).toLocaleString('fr-FR') + ' €';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle affichage du form
|
|
||||||
if (checkoutToggleBtn) {
|
if (checkoutToggleBtn) {
|
||||||
checkoutToggleBtn.addEventListener('click', () => {
|
checkoutToggleBtn.addEventListener('click', () => {
|
||||||
const isOpen = checkoutFormWrap.style.display !== 'none';
|
const isOpen = checkoutFormWrap.style.display !== 'none';
|
||||||
@ -145,7 +470,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit → appel API → redirect Stripe
|
|
||||||
if (checkoutForm) {
|
if (checkoutForm) {
|
||||||
checkoutForm.addEventListener('submit', async (e) => {
|
checkoutForm.addEventListener('submit', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -175,7 +499,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slug à partir du nom du produit : "Solar_Altar" → "solar-altar"
|
|
||||||
function toSlug(name) {
|
function toSlug(name) {
|
||||||
return name
|
return name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@ -197,7 +520,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
fields.specs.textContent = card.dataset.specs;
|
fields.specs.textContent = card.dataset.specs;
|
||||||
fields.notes.textContent = card.dataset.notes;
|
fields.notes.textContent = card.dataset.notes;
|
||||||
|
|
||||||
// Checkout : afficher uniquement si le produit a un prix et un stripe key
|
// Checkout
|
||||||
const price = card.dataset.price;
|
const price = card.dataset.price;
|
||||||
const stripeKey = card.dataset.stripeKey;
|
const stripeKey = card.dataset.stripeKey;
|
||||||
const isOrderable = price && stripeKey;
|
const isOrderable = price && stripeKey;
|
||||||
@ -211,24 +534,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
checkoutSection.style.display = 'none';
|
checkoutSection.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset form state
|
|
||||||
checkoutFormWrap.style.display = 'none';
|
checkoutFormWrap.style.display = 'none';
|
||||||
checkoutToggleBtn.textContent = '[ COMMANDER CETTE PIÈCE ]';
|
checkoutToggleBtn.textContent = '[ COMMANDER CETTE PIÈCE ]';
|
||||||
checkoutSubmitBtn.disabled = false;
|
checkoutSubmitBtn.disabled = false;
|
||||||
checkoutSubmitBtn.textContent = 'PROCÉDER AU PAIEMENT →';
|
checkoutSubmitBtn.textContent = 'PROCÉDER AU PAIEMENT →';
|
||||||
checkoutForm.reset();
|
checkoutForm.reset();
|
||||||
|
|
||||||
// Ferme les accordéons
|
|
||||||
panel.querySelectorAll('details').forEach(d => d.removeAttribute('open'));
|
panel.querySelectorAll('details').forEach(d => d.removeAttribute('open'));
|
||||||
|
|
||||||
panel.classList.add('is-open');
|
panel.classList.add('is-open');
|
||||||
panel.setAttribute('aria-hidden', 'false');
|
panel.setAttribute('aria-hidden', 'false');
|
||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
// Refresh cursor sur les nouveaux éléments
|
attachCursorHover(panel.querySelectorAll(
|
||||||
attachCursorHover(panel.querySelectorAll('summary, .panel-close, .checkout-btn, .checkout-submit'));
|
'summary, .panel-close, .checkout-btn, .checkout-submit'
|
||||||
|
));
|
||||||
|
|
||||||
|
// Technical drawing overlay (delayed for panel slide-in)
|
||||||
|
createTechOverlay(card);
|
||||||
|
setTimeout(() => animateTechOverlay(), 350);
|
||||||
|
|
||||||
// Mise à jour de l'URL
|
|
||||||
if (pushState) {
|
if (pushState) {
|
||||||
const slug = toSlug(card.dataset.name);
|
const slug = toSlug(card.dataset.name);
|
||||||
history.pushState({ slug }, '', `/collection/${slug}`);
|
history.pushState({ slug }, '', `/collection/${slug}`);
|
||||||
@ -236,6 +560,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closePanel(pushState = true) {
|
function closePanel(pushState = true) {
|
||||||
|
hideTechOverlay();
|
||||||
panel.classList.remove('is-open');
|
panel.classList.remove('is-open');
|
||||||
panel.setAttribute('aria-hidden', 'true');
|
panel.setAttribute('aria-hidden', 'true');
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
@ -245,37 +570,165 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cards.forEach(card => {
|
panelCards.forEach(card => {
|
||||||
card.addEventListener('click', () => openPanel(card));
|
card.addEventListener('click', () => openPanel(card));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ouverture automatique si on arrive directement sur /collection/[slug]
|
// Auto-open from direct URL (/collection/[slug])
|
||||||
if (window.__OPEN_PANEL__) {
|
if (window.__OPEN_PANEL__) {
|
||||||
const name = window.__OPEN_PANEL__;
|
const name = window.__OPEN_PANEL__;
|
||||||
const card = [...cards].find(c => c.dataset.name === name);
|
const card = [...panelCards].find(c => c.dataset.name === name);
|
||||||
if (card) openPanel(card, false);
|
if (card) openPanel(card, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (panelClose) panelClose.addEventListener('click', () => closePanel());
|
if (panelClose) panelClose.addEventListener('click', () => closePanel());
|
||||||
|
|
||||||
// Echap pour fermer
|
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Escape') closePanel();
|
if (e.key === 'Escape') closePanel();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bouton retour navigateur
|
|
||||||
window.addEventListener('popstate', () => {
|
window.addEventListener('popstate', () => {
|
||||||
if (panel.classList.contains('is-open')) {
|
if (panel.classList.contains('is-open')) {
|
||||||
closePanel(false);
|
closePanel(false);
|
||||||
} else {
|
} else {
|
||||||
// Tente de rouvrir si on navigue vers un slug connu
|
|
||||||
const match = location.pathname.match(/^\/collection\/(.+)$/);
|
const match = location.pathname.match(/^\/collection\/(.+)$/);
|
||||||
if (match) {
|
if (match) {
|
||||||
const slug = match[1];
|
const slug = match[1];
|
||||||
const card = [...cards].find(c => toSlug(c.dataset.name) === slug);
|
const card = [...panelCards].find(c => toSlug(c.dataset.name) === slug);
|
||||||
if (card) openPanel(card, false);
|
if (card) openPanel(card, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// 6. AMBIENT WORKSHOP SOUND
|
||||||
|
// ==========================================================
|
||||||
|
|
||||||
|
let audioCtx = null;
|
||||||
|
let soundOn = false;
|
||||||
|
let soundNodes = [];
|
||||||
|
let clickTimer = null;
|
||||||
|
|
||||||
|
const headerNav = document.querySelector('.header-nav');
|
||||||
|
if (headerNav) {
|
||||||
|
const soundBtn = document.createElement('button');
|
||||||
|
soundBtn.className = 'sound-toggle';
|
||||||
|
soundBtn.setAttribute('aria-label', 'Activer le son ambiant');
|
||||||
|
soundBtn.innerHTML =
|
||||||
|
'<span class="sound-bars">' +
|
||||||
|
'<span class="sound-bar"></span>' +
|
||||||
|
'<span class="sound-bar"></span>' +
|
||||||
|
'<span class="sound-bar"></span>' +
|
||||||
|
'</span>' +
|
||||||
|
'<span class="sound-label">SON</span>';
|
||||||
|
soundBtn.addEventListener('click', toggleSound);
|
||||||
|
headerNav.appendChild(soundBtn);
|
||||||
|
attachCursorHover([soundBtn]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAudioContext() {
|
||||||
|
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateBrownNoise(ctx, duration) {
|
||||||
|
const bufferSize = ctx.sampleRate * duration;
|
||||||
|
const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
|
||||||
|
const data = buffer.getChannelData(0);
|
||||||
|
let lastOut = 0;
|
||||||
|
for (let i = 0; i < bufferSize; i++) {
|
||||||
|
const white = Math.random() * 2 - 1;
|
||||||
|
data[i] = (lastOut + (0.02 * white)) / 1.02;
|
||||||
|
lastOut = data[i];
|
||||||
|
data[i] *= 3.5;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startAmbientSound() {
|
||||||
|
if (!audioCtx) createAudioContext();
|
||||||
|
if (audioCtx.state === 'suspended') audioCtx.resume();
|
||||||
|
|
||||||
|
const noiseBuffer = generateBrownNoise(audioCtx, 4);
|
||||||
|
const noiseSource = audioCtx.createBufferSource();
|
||||||
|
noiseSource.buffer = noiseBuffer;
|
||||||
|
noiseSource.loop = true;
|
||||||
|
|
||||||
|
const lowpass = audioCtx.createBiquadFilter();
|
||||||
|
lowpass.type = 'lowpass';
|
||||||
|
lowpass.frequency.value = 180;
|
||||||
|
|
||||||
|
const gain = audioCtx.createGain();
|
||||||
|
gain.gain.value = 0;
|
||||||
|
gain.gain.linearRampToValueAtTime(0.045, audioCtx.currentTime + 2);
|
||||||
|
|
||||||
|
noiseSource.connect(lowpass);
|
||||||
|
lowpass.connect(gain);
|
||||||
|
gain.connect(audioCtx.destination);
|
||||||
|
noiseSource.start();
|
||||||
|
|
||||||
|
soundNodes.push({ source: noiseSource, gain: gain });
|
||||||
|
scheduleMetalClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
function playMetalClick() {
|
||||||
|
if (!audioCtx || audioCtx.state !== 'running') return;
|
||||||
|
|
||||||
|
const osc = audioCtx.createOscillator();
|
||||||
|
osc.type = 'sine';
|
||||||
|
osc.frequency.value = 600 + Math.random() * 2400;
|
||||||
|
|
||||||
|
const gain = audioCtx.createGain();
|
||||||
|
gain.gain.value = 0.006 + Math.random() * 0.014;
|
||||||
|
gain.gain.exponentialRampToValueAtTime(
|
||||||
|
0.0001,
|
||||||
|
audioCtx.currentTime + 0.08 + Math.random() * 0.25
|
||||||
|
);
|
||||||
|
|
||||||
|
const filter = audioCtx.createBiquadFilter();
|
||||||
|
filter.type = 'bandpass';
|
||||||
|
filter.frequency.value = 1000 + Math.random() * 2000;
|
||||||
|
filter.Q.value = 12 + Math.random() * 25;
|
||||||
|
|
||||||
|
osc.connect(filter);
|
||||||
|
filter.connect(gain);
|
||||||
|
gain.connect(audioCtx.destination);
|
||||||
|
osc.start();
|
||||||
|
osc.stop(audioCtx.currentTime + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleMetalClick() {
|
||||||
|
if (!soundOn) return;
|
||||||
|
const delay = 2000 + Math.random() * 7000;
|
||||||
|
clickTimer = setTimeout(() => {
|
||||||
|
if (!soundOn) return;
|
||||||
|
playMetalClick();
|
||||||
|
scheduleMetalClick();
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAmbientSound() {
|
||||||
|
if (clickTimer) { clearTimeout(clickTimer); clickTimer = null; }
|
||||||
|
soundNodes.forEach(({ source, gain }) => {
|
||||||
|
gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 1);
|
||||||
|
setTimeout(() => { try { source.stop(); } catch (e) {} }, 1200);
|
||||||
|
});
|
||||||
|
soundNodes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSound() {
|
||||||
|
soundOn = !soundOn;
|
||||||
|
const btn = document.querySelector('.sound-toggle');
|
||||||
|
if (!btn) return;
|
||||||
|
|
||||||
|
if (soundOn) {
|
||||||
|
startAmbientSound();
|
||||||
|
btn.classList.add('sound-active');
|
||||||
|
btn.setAttribute('aria-label', 'Couper le son ambiant');
|
||||||
|
} else {
|
||||||
|
stopAmbientSound();
|
||||||
|
btn.classList.remove('sound-active');
|
||||||
|
btn.setAttribute('aria-label', 'Activer le son ambiant');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
306
public/style.css
306
public/style.css
@ -26,27 +26,55 @@ body {
|
|||||||
cursor: none;
|
cursor: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- CURSOR ---- */
|
/* ---- LEGACY CURSOR (hidden) ---- */
|
||||||
.cursor-dot {
|
.cursor-dot, .cursor-outline { display: none; }
|
||||||
width: 5px; height: 5px;
|
|
||||||
background: var(--clr-red);
|
/* ---- CAD CROSSHAIR CURSOR ---- */
|
||||||
position: fixed; top: 0; left: 0;
|
.cad-h, .cad-v {
|
||||||
transform: translate(-50%, -50%);
|
position: fixed;
|
||||||
pointer-events: none;
|
|
||||||
z-index: 999999;
|
|
||||||
opacity: 0;
|
|
||||||
will-change: transform;
|
|
||||||
}
|
|
||||||
.cursor-outline {
|
|
||||||
width: 26px; height: 26px;
|
|
||||||
border: 1px solid var(--clr-black);
|
|
||||||
position: fixed; top: 0; left: 0;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 999998;
|
z-index: 999998;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: width 0.15s, height 0.15s, opacity 0.15s;
|
transition: width 0.15s, height 0.15s;
|
||||||
will-change: transform;
|
}
|
||||||
|
.cad-h {
|
||||||
|
width: 40px; height: 1px;
|
||||||
|
background: var(--clr-black);
|
||||||
|
transform: translateY(-0.5px);
|
||||||
|
}
|
||||||
|
.cad-h.cad-hover { width: 56px; margin-left: -8px; }
|
||||||
|
.cad-v {
|
||||||
|
width: 1px; height: 40px;
|
||||||
|
background: var(--clr-black);
|
||||||
|
transform: translateX(-0.5px);
|
||||||
|
}
|
||||||
|
.cad-v.cad-hover { height: 56px; margin-top: -8px; }
|
||||||
|
.cad-center {
|
||||||
|
position: fixed;
|
||||||
|
width: 3px; height: 3px;
|
||||||
|
background: var(--clr-red);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 999999;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-1.5px, -1.5px);
|
||||||
|
}
|
||||||
|
.cad-center.cad-hover {
|
||||||
|
width: 5px; height: 5px;
|
||||||
|
transform: translate(-2.5px, -2.5px);
|
||||||
|
}
|
||||||
|
.cad-coords {
|
||||||
|
position: fixed;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 999999;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.55rem;
|
||||||
|
color: rgba(0,0,0,0.55);
|
||||||
|
opacity: 0;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
background: rgba(200,200,200,0.7);
|
||||||
|
padding: 1px 5px;
|
||||||
|
border: 1px solid rgba(0,0,0,0.08);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- INTERACTIVE GRID (derrière tout) ---- */
|
/* ---- INTERACTIVE GRID (derrière tout) ---- */
|
||||||
@ -592,6 +620,245 @@ hr { border: none; border-top: var(--border); margin: 0; }
|
|||||||
.footer a { color: var(--clr-black); text-decoration: none; }
|
.footer a { color: var(--clr-black); text-decoration: none; }
|
||||||
.footer a:hover { text-decoration: underline; }
|
.footer a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
|
/* ---- TECHNICAL DRAWING OVERLAY (panel image) ---- */
|
||||||
|
.tech-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 10;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.tech-corner {
|
||||||
|
position: absolute;
|
||||||
|
width: 28px; height: 28px;
|
||||||
|
border-color: rgba(232,168,0,0);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
transition: border-color 0.6s ease, width 0.5s ease, height 0.5s ease;
|
||||||
|
}
|
||||||
|
.tech-visible .tech-corner {
|
||||||
|
border-color: rgba(232,168,0,0.6);
|
||||||
|
}
|
||||||
|
.tech-corner--tl { top: 7%; left: 5%; border-top-width: 1px; border-left-width: 1px; }
|
||||||
|
.tech-corner--tr { top: 7%; right: 5%; border-top-width: 1px; border-right-width: 1px; }
|
||||||
|
.tech-corner--br { bottom: 7%; right: 5%; border-bottom-width: 1px; border-right-width: 1px; }
|
||||||
|
.tech-corner--bl { bottom: 7%; left: 5%; border-bottom-width: 1px; border-left-width: 1px; }
|
||||||
|
|
||||||
|
.tech-center-h, .tech-center-v {
|
||||||
|
position: absolute;
|
||||||
|
background: rgba(232,168,0,0);
|
||||||
|
transition: background 0.5s ease 0.15s;
|
||||||
|
}
|
||||||
|
.tech-visible .tech-center-h, .tech-visible .tech-center-v {
|
||||||
|
background: rgba(232,168,0,0.2);
|
||||||
|
}
|
||||||
|
.tech-center-h {
|
||||||
|
top: 50%; left: 38%; right: 38%;
|
||||||
|
height: 1px; transform: translateY(-0.5px);
|
||||||
|
}
|
||||||
|
.tech-center-v {
|
||||||
|
left: 50%; top: 38%; bottom: 38%;
|
||||||
|
width: 1px; transform: translateX(-0.5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dimension lines */
|
||||||
|
.tech-dim {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.5s ease 0.3s;
|
||||||
|
}
|
||||||
|
.tech-visible .tech-dim { opacity: 1; }
|
||||||
|
.tech-dim--h {
|
||||||
|
bottom: 4%; left: 5%; right: 5%;
|
||||||
|
}
|
||||||
|
.tech-dim--v {
|
||||||
|
right: 2.5%; top: 7%; bottom: 7%;
|
||||||
|
flex-direction: column;
|
||||||
|
writing-mode: vertical-lr;
|
||||||
|
}
|
||||||
|
.tech-dim-line {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.tech-dim--h .tech-dim-line { height: 1px; background: rgba(232,168,0,0.35); }
|
||||||
|
.tech-dim--v .tech-dim-line { width: 1px; background: rgba(232,168,0,0.35); }
|
||||||
|
.tech-dim-text {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.5rem;
|
||||||
|
color: rgba(232,168,0,0.65);
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
|
/* Dimension arrows */
|
||||||
|
.tech-dim-arrow {
|
||||||
|
font-size: 0.5rem;
|
||||||
|
color: rgba(232,168,0,0.5);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reference text */
|
||||||
|
.tech-ref {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1.5%; left: 5%;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.45rem;
|
||||||
|
color: rgba(232,168,0,0);
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
transition: color 0.5s ease 0.5s;
|
||||||
|
}
|
||||||
|
.tech-visible .tech-ref { color: rgba(232,168,0,0.45); }
|
||||||
|
|
||||||
|
/* Blueprint grid */
|
||||||
|
.tech-grid {
|
||||||
|
position: absolute;
|
||||||
|
inset: 7% 5%;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(232,168,0,0.04) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(232,168,0,0.04) 1px, transparent 1px);
|
||||||
|
background-size: 40px 40px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.8s ease 0.05s;
|
||||||
|
}
|
||||||
|
.tech-visible .tech-grid { opacity: 1; }
|
||||||
|
|
||||||
|
/* Scan line effect */
|
||||||
|
.tech-scanline {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(to bottom,
|
||||||
|
transparent,
|
||||||
|
rgba(232,168,0,0.6),
|
||||||
|
rgba(232,168,0,0.9),
|
||||||
|
rgba(232,168,0,0.6),
|
||||||
|
transparent);
|
||||||
|
box-shadow: 0 0 12px rgba(232,168,0,0.4);
|
||||||
|
z-index: 11;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.tech-visible .tech-scanline {
|
||||||
|
animation: techScanLine 1s ease-in-out forwards;
|
||||||
|
animation-delay: 0.1s;
|
||||||
|
}
|
||||||
|
@keyframes techScanLine {
|
||||||
|
0% { top: 0%; opacity: 1; }
|
||||||
|
90% { opacity: 0.8; }
|
||||||
|
100% { top: 100%; opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- CARD CAD ELEMENTS (scroll-triggered via GSAP) ---- */
|
||||||
|
.card-scanline {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(to bottom,
|
||||||
|
transparent,
|
||||||
|
rgba(232,168,0,0.7),
|
||||||
|
rgba(232,168,0,1),
|
||||||
|
rgba(232,168,0,0.7),
|
||||||
|
transparent);
|
||||||
|
box-shadow: 0 0 16px rgba(232,168,0,0.5), 0 0 4px rgba(232,168,0,0.8);
|
||||||
|
z-index: 5;
|
||||||
|
pointer-events: none;
|
||||||
|
top: 100%;
|
||||||
|
}
|
||||||
|
.card-corners {
|
||||||
|
position: absolute;
|
||||||
|
inset: 8%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.cc {
|
||||||
|
position: absolute;
|
||||||
|
width: 16px; height: 16px;
|
||||||
|
border-color: rgba(232,168,0,0.55);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
.cc-tl { top: 0; left: 0; border-top-width: 1px; border-left-width: 1px; }
|
||||||
|
.cc-tr { top: 0; right: 0; border-top-width: 1px; border-right-width: 1px; }
|
||||||
|
.cc-br { bottom: 0; right: 0; border-bottom-width: 1px; border-right-width: 1px; }
|
||||||
|
.cc-bl { bottom: 0; left: 0; border-bottom-width: 1px; border-left-width: 1px; }
|
||||||
|
|
||||||
|
.card-coord-tag {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 6px; left: 8px;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.45rem;
|
||||||
|
color: rgba(232,168,0,0.6);
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
z-index: 5;
|
||||||
|
pointer-events: none;
|
||||||
|
background: rgba(0,0,0,0.35);
|
||||||
|
padding: 1px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Construction line under collection header */
|
||||||
|
.cad-construction-line {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0; left: 0; right: 0;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(to right,
|
||||||
|
var(--clr-red),
|
||||||
|
var(--clr-red) 60%,
|
||||||
|
transparent);
|
||||||
|
transform-origin: left center;
|
||||||
|
}
|
||||||
|
.collection-header {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- SOUND TOGGLE ---- */
|
||||||
|
.sound-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
background: none;
|
||||||
|
border: 1px solid rgba(0,0,0,0.18);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
color: #999;
|
||||||
|
padding: 0.2rem 0.55rem;
|
||||||
|
cursor: none;
|
||||||
|
transition: color 0.2s, border-color 0.2s;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
.sound-toggle:hover {
|
||||||
|
color: var(--clr-black);
|
||||||
|
border-color: var(--clr-black);
|
||||||
|
}
|
||||||
|
.sound-toggle.sound-active {
|
||||||
|
color: var(--clr-red);
|
||||||
|
border-color: var(--clr-red);
|
||||||
|
}
|
||||||
|
.sound-icon { font-size: 0.7rem; line-height: 1; }
|
||||||
|
.sound-bars {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 1px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
.sound-bar {
|
||||||
|
width: 2px;
|
||||||
|
background: currentColor;
|
||||||
|
transition: height 0.2s;
|
||||||
|
}
|
||||||
|
.sound-bar:nth-child(1) { height: 3px; }
|
||||||
|
.sound-bar:nth-child(2) { height: 6px; }
|
||||||
|
.sound-bar:nth-child(3) { height: 4px; }
|
||||||
|
.sound-toggle.sound-active .sound-bar {
|
||||||
|
animation: soundPulse 0.8s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
.sound-toggle.sound-active .sound-bar:nth-child(2) { animation-delay: 0.15s; }
|
||||||
|
.sound-toggle.sound-active .sound-bar:nth-child(3) { animation-delay: 0.3s; }
|
||||||
|
@keyframes soundPulse {
|
||||||
|
0% { height: 2px; }
|
||||||
|
100% { height: 10px; }
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- RESPONSIVE ---- */
|
/* ---- RESPONSIVE ---- */
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.hero, .newsletter { grid-template-columns: 1fr; }
|
.hero, .newsletter { grid-template-columns: 1fr; }
|
||||||
@ -611,6 +878,7 @@ hr { border: none; border-top: var(--border); margin: 0; }
|
|||||||
.hero-left { min-height: unset; padding: 2rem var(--pad); order: -1; }
|
.hero-left { min-height: unset; padding: 2rem var(--pad); order: -1; }
|
||||||
.hero-right { height: 60vw; }
|
.hero-right { height: 60vw; }
|
||||||
|
|
||||||
.cursor-dot, .cursor-outline { display: none; }
|
.cursor-dot, .cursor-outline,
|
||||||
|
.cad-h, .cad-v, .cad-center, .cad-coords { display: none; }
|
||||||
body { cursor: auto; }
|
body { cursor: auto; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,6 +57,10 @@ const {
|
|||||||
<slot name="head" />
|
<slot name="head" />
|
||||||
|
|
||||||
<link rel="stylesheet" href="/style.css">
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
|
||||||
|
<!-- GSAP + ScrollTrigger (is:inline = pas de processing Vite/module) -->
|
||||||
|
<script is:inline src="https://cdn.jsdelivr.net/npm/gsap@3.12.7/dist/gsap.min.js"></script>
|
||||||
|
<script is:inline src="https://cdn.jsdelivr.net/npm/gsap@3.12.7/dist/ScrollTrigger.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user