All Prompts
All Prompts

featuresectioninteractiveanimatedwebglgallerytailwind
Science of Posture Product Feature Section
Секция с интерактивной галереей WebGL для демонстрации мебели, улучшающей осанку. Адаптивный дизайн с Tailwind CSS.
Prompt
<section id="science-of-posture" class="py-32 relative overflow-hidden"
style="background-color: #F2EFEA; color: #2C2824;">
<script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script>
<style>
@keyframes sonar {
0% {
transform: scale(1);
opacity: 0.8;
}
100% {
transform: scale(2.5);
opacity: 0;
}
}
.sonar-ring {
position: absolute;
inset: 0;
border-radius: 50%;
border: 1px solid currentColor;
animation: sonar 2s cubic-bezier(0, 0, 0.2, 1) infinite;
pointer-events: none;
}
.card-flashlight {
position: relative;
border-radius: 1rem;
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(12px);
border: 1px solid rgba(0, 0, 0, 0.05);
overflow: hidden;
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.8), transparent 40%);
z-index: 0;
opacity: 0;
transition: opacity 0.4s ease;
pointer-events: none;
}
.card-flashlight:hover::before {
opacity: 1;
}
.card-content {
position: relative;
z-index: 2;
}
</style>
<div class="max-w-[88rem] mx-auto px-6 lg:px-12">
<!-- Header -->
<div class="flex flex-col md:flex-row justify-between items-start md:items-end mb-24 gap-8">
<h2 class="text-5xl md:text-6xl tracking-tighter font-light"
style="font-family: 'Plus Jakarta Sans', sans-serif;">
The Science of<br>Posture
</h2>
<a href="#"
class="inline-flex items-center gap-2 bg-[#2C2824] text-[#F2EFEA] px-6 py-3 rounded-full text-sm font-medium transition-transform hover:scale-105"
style="font-family: 'Geist', sans-serif;">
<span>Explore Specifications</span>
<iconify-icon icon="solar:arrow-right-up-linear"></iconify-icon>
</a>
</div>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-8 items-start">
<!-- Description -->
<div class="lg:col-span-4 max-w-sm">
<p class="text-sm font-medium tracking-tight opacity-50 mb-4 uppercase"
style="font-family: 'Geist', sans-serif;">( Method )</p>
<p class="text-xl leading-relaxed opacity-90 mb-8 font-light"
style="font-family: 'Plus Jakarta Sans', sans-serif;">
Every structural decision serves a biomechanical purpose. We strip away the superfluous, leaving only what
actively contributes to your physical wellbeing. Materials are tested for dynamic tension, ensuring longevity
without aesthetic compromise.
</p>
<div class="relative inline-flex text-[#C48C56] mt-4">
<iconify-icon icon="solar:asterisk-bold-duotone" class="text-3xl"></iconify-icon>
<div class="sonar-ring" style="animation-delay: 1s"></div>
</div>
</div>
<!-- Interactive Gallery -->
<div class="lg:col-span-8 grid grid-cols-1 md:grid-cols-2 gap-8 lg:gap-12 items-end">
<!-- Card 1 -->
<div class="card-flashlight p-6 pb-8 flex flex-col group cursor-pointer">
<div class="card-content">
<div class="aspect-square rounded-lg overflow-hidden bg-black/5 mb-6 relative">
<img src="https://images.unsplash.com/photo-1592078615290-033ee584e267?q=80&w=1000&auto=format&fit=crop"
alt="Lounge Chair"
class="webgl-reveal w-full h-full object-cover mix-blend-multiply transition-transform duration-700 group-hover:scale-110">
</div>
<div class="flex justify-between items-end text-sm" style="font-family: 'Geist', sans-serif;">
<div>
<span class="opacity-50 text-xs block mb-1">01</span>
<p class="font-medium tracking-tight">Kinetic Lounge V1</p>
</div>
<span class="opacity-60">240 Units</span>
</div>
</div>
</div>
<!-- Card 2 -->
<div class="card-flashlight p-6 pb-8 flex flex-col group cursor-pointer lg:-mt-24">
<div class="card-content">
<div class="aspect-[4/5] rounded-lg overflow-hidden bg-black/5 mb-6 relative">
<img src="https://images.unsplash.com/photo-1567538096630-e0c55bd6374c?q=80&w=1000&auto=format&fit=crop"
alt="Accent Chair"
class="webgl-reveal w-full h-full object-cover mix-blend-multiply transition-transform duration-700 group-hover:scale-110">
</div>
<div class="flex justify-between items-end text-sm" style="font-family: 'Geist', sans-serif;">
<div>
<span class="opacity-50 text-xs block mb-1">02</span>
<p class="font-medium tracking-tight">Architectural Accent</p>
</div>
<span class="opacity-60">850+ Units</span>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
(function() {
// Flashlight Logic
const section = document.getElementById('science-of-posture');
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`);
});
});
// WebGL Reveal Logic
const vs = `attribute vec2 p;varying vec2 u;void main(){u=p*0.5+0.5;gl_Position=vec4(p,0,1);}`;
const fs = `precision highp float;varying vec2 u;uniform sampler2D t;uniform float g;void main(){float c=4.0;float i=floor(u.x*c);float d=i*0.15;float p=clamp((g*(1.0+3.0*0.15)-d),0.0,1.0);float e=pow(p-1.0,3.0)+1.0;if(u.y<1.0-e)discard;gl_FragColor=texture2D(t,u);}`;
function initWebGL(el) {
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = () => {
const canv = document.createElement('canvas');
canv.width = img.naturalWidth; canv.height = img.naturalHeight;
canv.className = el.className; canv.style.cssText = el.style.cssText;
el.parentNode.insertBefore(canv, el); el.style.display = 'none';
const gl = canv.getContext('webgl', { alpha: true });
const s = (t, src) => { const sh = gl.createShader(t); gl.shaderSource(sh, src); gl.compileShader(sh); return sh; };
const prog = gl.createProgram();
gl.attachShader(prog, s(gl.VERTEX_SHADER, vs)); gl.attachShader(prog, s(gl.FRAGMENT_SHADER, fs));
gl.linkProgram(prog); gl.useProgram(prog);
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 loc = gl.getAttribLocation(prog, "p"); gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 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_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
const uG = gl.getUniformLocation(prog, "g");
let start = null;
const draw = (now) => {
if (!start) start = now;
let progVal = (now - start) / 1200;
if (progVal > 1) progVal = 1;
gl.clear(gl.COLOR_BUFFER_BIT); gl.uniform1f(uG, progVal);
gl.drawArrays(gl.TRIANGLES, 0, 6);
if (progVal < 1) requestAnimationFrame(draw);
};
new IntersectionObserver(ents => {
if (ents[0].isIntersecting) requestAnimationFrame(draw);
}, { threshold: 0.1 }).observe(canv);
};
img.src = el.src;
}
section.querySelectorAll('.webgl-reveal').forEach(initWebGL);
})();
</script>
</section>