Загрузка...

Секция с WebGL-анимацией для демонстрации точной инженерии. Интерактивные элементы, карточки с ховером. Идеально для премиум-продуктов.
<section id="precision-engineering" class="bg-[#26221E] text-[#F2EFEA] py-32 relative overflow-hidden"
style="font-family: 'Inter', sans-serif;">
<!-- Component-Specific Styles -->
<style>
.card-flashlight {
position: relative;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.05);
transition: transform 0.3s ease;
}
.card-flashlight::before {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(600px circle at var(--mouse-x, 0) var(--mouse-y, 0), rgba(255, 255, 255, 0.08), transparent 40%);
z-index: 0;
opacity: 0;
transition: opacity 0.4s ease;
pointer-events: none;
}
.card-flashlight::after {
content: "";
position: absolute;
inset: 0;
padding: 1px;
border-radius: inherit;
background: radial-gradient(400px circle at var(--mouse-x, 0) var(--mouse-y, 0), rgba(255, 255, 255, 0.4), transparent 40%);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
z-index: 1;
opacity: 0;
transition: opacity 0.4s ease;
pointer-events: none;
}
.card-flashlight:hover::before,
.card-flashlight:hover::after {
opacity: 1;
}
.card-content {
position: relative;
z-index: 2;
}
</style>
<div class="max-w-[88rem] mx-auto px-6 lg:px-12 relative z-10">
<!-- Header -->
<div class="flex justify-between items-end mb-20 border-b border-white/10 pb-8">
<h2 class="text-5xl md:text-6xl tracking-tighter font-light"
style="font-family: 'Plus Jakarta Sans', sans-serif;">Precision<br>Engineering</h2>
<p class="text-xl tracking-tight opacity-50 font-light" style="font-family: 'Plus Jakarta Sans', sans-serif;">
SPEC_089</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-12 lg:gap-8 items-center">
<!-- Left Column: Series -->
<div class="space-y-6 text-sm font-medium tracking-tight">
<p class="opacity-40 text-xs uppercase mb-8" style="font-family: 'Geist', sans-serif;">Series Classification</p>
<div
class="flex items-center gap-4 group cursor-pointer border-b border-white/5 pb-4 hover:border-white/30 transition-colors">
<span class="opacity-40 text-xs w-6">01</span>
<span class="opacity-70 group-hover:opacity-100 transition-opacity">Kinetic Lounge</span>
</div>
<div
class="flex items-center gap-4 group cursor-pointer border-b border-white/5 pb-4 hover:border-white/30 transition-colors">
<span class="opacity-40 text-xs w-6">02</span>
<span class="opacity-70 group-hover:opacity-100 transition-opacity">Minimalist Rocker</span>
</div>
<div
class="flex items-center gap-4 group cursor-pointer border-b border-white/5 pb-4 hover:border-white/30 transition-colors">
<span class="opacity-40 text-xs w-6">03</span>
<span class="opacity-70 group-hover:opacity-100 transition-opacity">Ergo-Task Hybrid</span>
</div>
<div
class="flex items-center gap-4 group cursor-pointer border-b border-white/5 pb-4 hover:border-white/30 transition-colors">
<span class="opacity-40 text-xs w-6">04</span>
<span class="opacity-70 group-hover:opacity-100 transition-opacity">Modular Accent</span>
</div>
</div>
<!-- Center Image with WebGL -->
<div class="card-flashlight p-2 rounded-2xl h-[500px]">
<div class="card-content w-full h-full rounded-xl overflow-hidden relative">
<img id="webgl-target" src="https://hoirqrkdgbmvpwutwuwj.supabase.co/storage/v1/object/public/assets/assets/62ec4789-2963-4666-aa09-20586e294364_800w.webp" alt="Chair Detail" class="absolute inset-0 w-full h-full object-cover">
</div>
</div>
<!-- Right Column: Materials -->
<div class="space-y-6 text-sm font-medium tracking-tight text-right">
<p class="opacity-40 text-xs uppercase mb-8" style="font-family: 'Geist', sans-serif;">Material Synthesis</p>
<div
class="flex items-center justify-end gap-4 group cursor-pointer border-b border-white/5 pb-4 hover:border-white/30 transition-colors">
<span class="opacity-70 group-hover:opacity-100 transition-opacity">Aero-grade aluminum chassis</span>
<iconify-icon icon="solar:arrow-right-up-linear"
class="opacity-0 group-hover:opacity-100 transition-opacity"></iconify-icon>
</div>
<div
class="flex items-center justify-end gap-4 group cursor-pointer border-b border-white/5 pb-4 hover:border-white/30 transition-colors">
<span class="opacity-70 group-hover:opacity-100 transition-opacity">Thermo-responsive polymer mesh</span>
</div>
<div
class="flex items-center justify-end gap-4 group cursor-pointer border-b border-white/5 pb-4 hover:border-white/30 transition-colors">
<span class="opacity-70 group-hover:opacity-100 transition-opacity">Sustainably sourced walnut accents</span>
</div>
<div
class="flex items-center justify-end gap-4 group cursor-pointer border-b border-white/5 pb-4 hover:border-white/30 transition-colors">
<span class="opacity-70 group-hover:opacity-100 transition-opacity">Micro-calibration lumbar system</span>
</div>
</div>
</div>
</div>
<script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script>
<script>
(function() {
const section = document.getElementById('precision-engineering');
const cards = section.querySelectorAll('.card-flashlight');
cards.forEach(card => {
card.addEventListener('mousemove', (e) => {
const rect = card.getBoundingClientRect();
card.style.setProperty('--mouse-x', `${e.clientX - rect.left}px`);
card.style.setProperty('--mouse-y', `${e.clientY - rect.top}px`);
});
});
const vs = `attribute vec2 a;varying vec2 v;void main(){v=a*0.5+0.5;gl_Position=vec4(a,0,1);}`;
const fs = `precision highp float;varying vec2 v;uniform sampler2D t;uniform float p;void main(){float c=4.0,i=floor(v.x*c),s=0.15,d=i*s,l=clamp((p*(1.0+3.0*s)-d),0.0,1.0),e=pow(l-1.0,3.0)+1.0;if(v.y<1.0-e)discard;gl_FragColor=texture2D(t,v);}`;
const target = document.getElementById('webgl-target');
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth; canvas.height = img.naturalHeight;
canvas.className = target.className;
target.parentNode.insertBefore(canvas, target); target.style.display = 'none';
const gl = canvas.getContext('webgl', { alpha: true });
if (!gl) return;
const sh = (type, src) => { const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); return s; };
const pg = gl.createProgram(); gl.attachShader(pg, sh(gl.VERTEX_SHADER, vs)); gl.attachShader(pg, sh(gl.FRAGMENT_SHADER, fs)); gl.linkProgram(pg); gl.useProgram(pg);
const buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]), gl.STATIC_DRAW);
const al = gl.getAttribLocation(pg, "a"); gl.enableVertexAttribArray(al); gl.vertexAttribPointer(al, 2, gl.FLOAT, false, 0, 0);
const tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
const up = gl.getUniformLocation(pg, "p");
let start = null;
const draw = (now) => {
if (!start) start = now;
let prog = Math.min((now - start) / 1200, 1.0);
gl.viewport(0, 0, canvas.width, canvas.height); gl.clear(gl.COLOR_BUFFER_BIT);
gl.uniform1f(up, prog); gl.drawArrays(gl.TRIANGLES, 0, 6);
if (prog < 1) requestAnimationFrame(draw);
};
const obs = new IntersectionObserver((es) => { if(es[0].isIntersecting) { requestAnimationFrame(draw); obs.disconnect(); } }, { threshold: 0.1 });
obs.observe(canvas);
};
img.src = target.src;
})();
</script>
</section>