Загрузка...

UI-карточки метронома BeatWeave с 3 темами. Анимированный BPM, волна, циферблат, Tap Tempo. Для аудио-инструмента или виджета управления музыкой.
<section class="bg-gray-950 min-h-screen flex flex-col md:flex-row font-sans antialiased text-gray-100 selection:bg-gray-800 m-0 p-0 overflow-x-hidden">
<div class="flex-1 bg-slate-950 flex items-center justify-center p-8 min-h-screen relative overflow-hidden">
<div class="absolute inset-0 pointer-events-none opacity-[0.03]" style="background-image: radial-gradient(circle, #fff 1px, transparent 1px); background-size: 24px 24px;"></div>
<div class="absolute inset-y-0 left-1/2 -translate-x-1/2 w-[320px] border-x border-white/[0.05] pointer-events-none z-0"></div>
<div class="absolute inset-y-0 left-1/2 -translate-x-1/2 w-[500px] border-x border-white/[0.03] pointer-events-none z-0 hidden xl:block"></div>
<div class="absolute left-6 top-1/2 -translate-y-1/2 rotate-[-90deg] text-xs tracking-widest text-gray-500 font-light hidden lg:block">CELESTE</div>
<div class="w-full max-w-[320px] bg-slate-900 rounded-[2.5rem] p-8 flex flex-col items-center relative z-10 border border-white/[0.06]" style="box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);">
<div class="flex justify-between w-full items-center mb-6 pb-6 border-b border-white/[0.06]">
<iconify-icon icon="solar:tuning-square-linear" stroke-width="1.5" class="text-xl opacity-70"></iconify-icon>
<div class="flex items-center gap-2">
<canvas id="pulse1" width="40" height="20" class="w-10 h-5"></canvas>
<span class="text-xs font-light tracking-widest uppercase opacity-90">BeatWeave</span>
</div>
<iconify-icon icon="solar:hamburger-menu-linear" stroke-width="1.5" class="text-xl opacity-70"></iconify-icon>
</div>
<div class="text-lg font-extralight mb-3">6/8</div>
<div class="flex gap-3 mb-6 items-center h-2">
<div class="w-1 h-1 rounded-full bg-gray-600"></div>
<div class="w-1.5 h-1.5 rounded-full bg-teal-400"></div>
<div class="w-1 h-1 rounded-full bg-gray-600"></div>
<div class="w-1 h-1 rounded-full bg-gray-600"></div>
</div>
<div class="relative w-full flex items-center justify-center mb-8">
<div class="absolute w-full border-t border-white/10"></div>
<div class="z-10 bg-slate-900 px-3">
<iconify-icon icon="solar:settings-linear" stroke-width="1.5" class="text-lg opacity-50"></iconify-icon>
</div>
</div>
<div class="flex items-center justify-between w-full px-2 mb-4">
<button aria-label="Decrease BPM" onclick="adjustBpm('bpm1', -5)"><iconify-icon icon="solar:minus-linear" stroke-width="1.5" class="text-2xl opacity-70 cursor-pointer hover:opacity-100 transition-opacity"></iconify-icon></button>
<div class="flex flex-col items-center border-x border-white/[0.06] px-8">
<span id="bpm1" class="text-6xl font-thin tracking-tight" data-value="110">110</span>
<span class="text-xs opacity-50 tracking-widest mt-1 font-light">BPM</span>
</div>
<button aria-label="Increase BPM" onclick="adjustBpm('bpm1', 5)"><iconify-icon icon="solar:add-linear" stroke-width="1.5" class="text-2xl opacity-70 cursor-pointer hover:opacity-100 transition-opacity"></iconify-icon></button>
</div>
<div class="relative w-64 h-64 mt-4 flex items-center justify-center">
<canvas id="dial1" width="256" height="256" class="absolute inset-0 w-full h-full pointer-events-none"></canvas>
<button class="w-36 h-36 rounded-full bg-slate-900 flex items-center justify-center z-10 active:scale-95 transition-transform duration-150 group border border-white/5" style="box-shadow: 12px 12px 24px #020617, -12px -12px 24px #1e293b;">
<span class="text-xs font-light tracking-widest opacity-50 group-active:opacity-70 transition-opacity">TAP</span>
</button>
<div class="absolute bottom-6 left-8 text-xs tracking-widest opacity-40 font-light">MIN</div>
<div class="absolute bottom-6 right-8 text-xs tracking-widest opacity-40 font-light">MAX</div>
</div>
<div class="flex justify-between items-center w-full mt-8 pt-6 border-t border-white/[0.06] px-2">
<button aria-label="Volume"><iconify-icon icon="solar:volume-loud-linear" stroke-width="1.5" class="text-xl opacity-70 hover:opacity-100 transition-opacity"></iconify-icon></button>
<button aria-label="Play"><iconify-icon icon="solar:play-linear" stroke-width="1.5" class="text-2xl opacity-90 hover:opacity-100 transition-opacity"></iconify-icon></button>
<button aria-label="Music Notes"><iconify-icon icon="solar:music-note-linear" stroke-width="1.5" class="text-xl opacity-70 hover:opacity-100 transition-opacity"></iconify-icon></button>
</div>
</div>
</div>
<div class="flex-1 bg-gray-950 flex items-center justify-center p-8 min-h-screen relative overflow-hidden">
<div class="absolute inset-0 pointer-events-none opacity-[0.03]" style="background-image: radial-gradient(circle, #fff 1px, transparent 1px); background-size: 24px 24px;"></div>
<div class="absolute inset-y-0 left-1/2 -translate-x-1/2 w-[320px] border-x border-white/[0.05] pointer-events-none z-0"></div>
<div class="absolute inset-y-0 left-1/2 -translate-x-1/2 w-[500px] border-x border-white/[0.03] pointer-events-none z-0 hidden xl:block"></div>
<div class="absolute left-6 top-1/2 -translate-y-1/2 rotate-[-90deg] text-xs tracking-widest text-gray-500 font-light hidden lg:block">GLACIER</div>
<div class="w-full max-w-[320px] bg-gray-900 rounded-[2.5rem] p-8 flex flex-col items-center relative z-10 border border-white/[0.06]" style="box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);">
<div class="flex justify-between w-full items-center mb-6 pb-6 border-b border-white/[0.06]">
<iconify-icon icon="solar:tuning-square-linear" stroke-width="1.5" class="text-xl opacity-70"></iconify-icon>
<div class="flex items-center gap-2">
<canvas id="pulse2" width="40" height="20" class="w-10 h-5"></canvas>
<span class="text-xs font-light tracking-widest uppercase opacity-90">BeatWeave</span>
</div>
<iconify-icon icon="solar:hamburger-menu-linear" stroke-width="1.5" class="text-xl opacity-70"></iconify-icon>
</div>
<div class="text-lg font-extralight mb-3">3/4</div>
<div class="flex gap-3 mb-6 items-center h-2">
<div class="w-1 h-1 rounded-full bg-gray-600"></div>
<div class="w-1.5 h-1.5 rounded-full bg-violet-400"></div>
<div class="w-1 h-1 rounded-full bg-gray-600"></div>
</div>
<div class="relative w-full flex items-center justify-center mb-8">
<div class="absolute w-full border-t border-white/10"></div>
<div class="z-10 bg-gray-900 px-3">
<iconify-icon icon="solar:settings-linear" stroke-width="1.5" class="text-lg opacity-50"></iconify-icon>
</div>
</div>
<div class="flex items-center justify-between w-full px-2 mb-4">
<button aria-label="Decrease BPM" onclick="adjustBpm('bpm2', -5)"><iconify-icon icon="solar:minus-linear" stroke-width="1.5" class="text-2xl opacity-70 cursor-pointer hover:opacity-100 transition-opacity"></iconify-icon></button>
<div class="flex flex-col items-center border-x border-white/[0.06] px-8">
<span id="bpm2" class="text-6xl font-thin tracking-tight" data-value="90">90</span>
<span class="text-xs opacity-50 tracking-widest mt-1 font-light">BPM</span>
</div>
<button aria-label="Increase BPM" onclick="adjustBpm('bpm2', 5)"><iconify-icon icon="solar:add-linear" stroke-width="1.5" class="text-2xl opacity-70 cursor-pointer hover:opacity-100 transition-opacity"></iconify-icon></button>
</div>
<div class="relative w-64 h-64 mt-4 flex items-center justify-center">
<canvas id="dial2" width="256" height="256" class="absolute inset-0 w-full h-full pointer-events-none"></canvas>
<button class="w-36 h-36 rounded-full bg-gray-900 flex items-center justify-center z-10 active:scale-95 transition-transform duration-150 group border border-white/5" style="box-shadow: 12px 12px 24px #030712, -12px -12px 24px #1f2937;">
<span class="text-xs font-light tracking-widest opacity-50 group-active:opacity-70 transition-opacity">TAP</span>
</button>
<div class="absolute bottom-6 left-8 text-xs tracking-widest opacity-40 font-light">MIN</div>
<div class="absolute bottom-6 right-8 text-xs tracking-widest opacity-40 font-light">MAX</div>
</div>
<div class="flex justify-between items-center w-full mt-8 pt-6 border-t border-white/[0.06] px-2">
<button aria-label="Volume"><iconify-icon icon="solar:volume-loud-linear" stroke-width="1.5" class="text-xl opacity-70 hover:opacity-100 transition-opacity"></iconify-icon></button>
<button aria-label="Play"><iconify-icon icon="solar:play-linear" stroke-width="1.5" class="text-2xl opacity-90 hover:opacity-100 transition-opacity"></iconify-icon></button>
<button aria-label="Music Notes"><iconify-icon icon="solar:music-note-linear" stroke-width="1.5" class="text-xl opacity-70 hover:opacity-100 transition-opacity"></iconify-icon></button>
</div>
</div>
</div>
<div class="flex-1 bg-stone-950 flex items-center justify-center p-8 min-h-screen relative overflow-hidden">
<div class="absolute inset-0 pointer-events-none opacity-[0.03]" style="background-image: radial-gradient(circle, #fff 1px, transparent 1px); background-size: 24px 24px;"></div>
<div class="absolute inset-y-0 left-1/2 -translate-x-1/2 w-[320px] border-x border-white/[0.05] pointer-events-none z-0"></div>
<div class="absolute inset-y-0 left-1/2 -translate-x-1/2 w-[500px] border-x border-white/[0.03] pointer-events-none z-0 hidden xl:block"></div>
<div class="absolute left-6 top-1/2 -translate-y-1/2 rotate-[-90deg] text-xs tracking-widest text-stone-500 font-light hidden lg:block">BLOOM</div>
<div class="w-full max-w-[320px] bg-stone-900 rounded-[2.5rem] p-8 flex flex-col items-center relative z-10 border border-white/[0.06]" style="box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);">
<div class="flex justify-between w-full items-center mb-6 pb-6 border-b border-white/[0.06]">
<iconify-icon icon="solar:tuning-square-linear" stroke-width="1.5" class="text-xl opacity-70"></iconify-icon>
<div class="flex items-center gap-2">
<canvas id="pulse3" width="40" height="20" class="w-10 h-5"></canvas>
<span class="text-xs font-light tracking-widest uppercase opacity-90">BeatWeave</span>
</div>
<iconify-icon icon="solar:hamburger-menu-linear" stroke-width="1.5" class="text-xl opacity-70"></iconify-icon>
</div>
<div class="text-lg font-extralight mb-3">5/4</div>
<div class="flex gap-3 mb-6 items-center h-2">
<div class="w-1 h-1 rounded-full bg-stone-600"></div>
<div class="w-1.5 h-1.5 rounded-full bg-rose-400"></div>
<div class="w-1 h-1 rounded-full bg-stone-600"></div>
<div class="w-1 h-1 rounded-full bg-stone-600"></div>
<div class="w-1 h-1 rounded-full bg-stone-600"></div>
</div>
<div class="relative w-full flex items-center justify-center mb-8">
<div class="absolute w-full border-t border-white/10"></div>
<div class="z-10 bg-stone-900 px-3">
<iconify-icon icon="solar:settings-linear" stroke-width="1.5" class="text-lg opacity-50"></iconify-icon>
</div>
</div>
<div class="flex items-center justify-between w-full px-2 mb-4">
<button aria-label="Decrease BPM" onclick="adjustBpm('bpm3', -5)"><iconify-icon icon="solar:minus-linear" stroke-width="1.5" class="text-2xl opacity-70 cursor-pointer hover:opacity-100 transition-opacity"></iconify-icon></button>
<div class="flex flex-col items-center border-x border-white/[0.06] px-8">
<span id="bpm3" class="text-6xl font-thin tracking-tight" data-value="128">128</span>
<span class="text-xs opacity-50 tracking-widest mt-1 font-light">BPM</span>
</div>
<button aria-label="Increase BPM" onclick="adjustBpm('bpm3', 5)"><iconify-icon icon="solar:add-linear" stroke-width="1.5" class="text-2xl opacity-70 cursor-pointer hover:opacity-100 transition-opacity"></iconify-icon></button>
</div>
<div class="relative w-64 h-64 mt-4 flex items-center justify-center">
<canvas id="dial3" width="256" height="256" class="absolute inset-0 w-full h-full pointer-events-none"></canvas>
<button class="w-36 h-36 rounded-full bg-stone-900 flex items-center justify-center z-10 active:scale-95 transition-transform duration-150 group border border-white/5" style="box-shadow: 12px 12px 24px #0c0a09, -12px -12px 24px #292524;">
<span class="text-xs font-light tracking-widest opacity-50 group-active:opacity-70 transition-opacity">TAP</span>
</button>
<div class="absolute bottom-6 left-8 text-xs tracking-widest opacity-40 font-light">MIN</div>
<div class="absolute bottom-6 right-8 text-xs tracking-widest opacity-40 font-light">MAX</div>
</div>
<div class="flex justify-between items-center w-full mt-8 pt-6 border-t border-white/[0.06] px-2">
<button aria-label="Volume"><iconify-icon icon="solar:volume-loud-linear" stroke-width="1.5" class="text-xl opacity-70 hover:opacity-100 transition-opacity"></iconify-icon></button>
<button aria-label="Play"><iconify-icon icon="solar:play-linear" stroke-width="1.5" class="text-2xl opacity-90 hover:opacity-100 transition-opacity"></iconify-icon></button>
<button aria-label="Music Notes"><iconify-icon icon="solar:music-note-linear" stroke-width="1.5" class="text-xl opacity-70 hover:opacity-100 transition-opacity"></iconify-icon></button>
</div>
</div>
</div>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script>
<script>
// BPM Ticker Animation
const bpmTargets = {};
const bpmCurrents = {};
function initBpm(id, startVal) {
const el = document.getElementById(id);
bpmTargets[id] = startVal;
bpmCurrents[id] = startVal;
el.textContent = startVal;
el.setAttribute('data-value', startVal);
}
function adjustBpm(id, delta) {
const el = document.getElementById(id);
let current = parseInt(el.getAttribute('data-value'));
let newVal = Math.max(40, Math.min(200, current + delta));
bpmTargets[id] = newVal;
el.setAttribute('data-value', newVal);
}
function animateBpm() {
for (let id in bpmTargets) {
const target = bpmTargets[id];
const current = bpmCurrents[id];
if (current !== target) {
const diff = target - current;
const step = diff > 0 ? 1 : -1;
if (Math.abs(diff) < 1) {
bpmCurrents[id] = target;
} else {
bpmCurrents[id] = current + step;
}
document.getElementById(id).textContent = bpmCurrents[id];
}
}
requestAnimationFrame(animateBpm);
}
initBpm('bpm1', 110);
initBpm('bpm2', 90);
initBpm('bpm3', 128);
animateBpm();
const drawPulse = (ctx, w, h, t, color) => {
ctx.clearRect(0, 0, w, h);
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 1.5;
ctx.lineJoin = 'round';
const mid = h / 2;
ctx.moveTo(0, mid);
for (let i = 0; i < w; i++) {
let y = mid;
if (i > w/2 - 8 && i < w/2 + 8) {
const dist = Math.abs(i - w/2);
const spike = Math.cos(dist * 0.4) * 8;
y += spike * Math.sin(t * 3);
}
ctx.lineTo(i, y);
}
ctx.stroke();
};
const drawDial = (ctx, w, h, color, val, t) => {
ctx.clearRect(0, 0, w, h);
const cx = w / 2, cy = h / 2, r = cx - 20;
const startAngle = Math.PI * 0.75;
const range = Math.PI * 1.5;
const totalTicks = 60;
for (let i = 0; i <= totalTicks; i++) {
const angle = startAngle + (i / totalTicks) * range;
const isLit = (i / totalTicks) < val;
const glow = isLit ? Math.sin(t * 2 + i * 0.1) * 2 : 0;
const innerR = r - 6 + glow;
const x1 = cx + Math.cos(angle) * innerR;
const y1 = cy + Math.sin(angle) * innerR;
const x2 = cx + Math.cos(angle) * r;
const y2 = cy + Math.sin(angle) * r;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
if (isLit) {
ctx.strokeStyle = color;
ctx.lineWidth = 2;
} else {
ctx.strokeStyle = 'rgba(255,255,255,0.15)';
ctx.lineWidth = 1.5;
}
ctx.stroke();
}
};
let time = 0;
const animate = () => {
time += 0.03;
const p1 = document.getElementById('pulse1');
drawPulse(p1.getContext('2d'), p1.width, p1.height, time, 'rgba(255,255,255,0.7)');
const d1 = document.getElementById('dial1');
drawDial(d1.getContext('2d'), d1.width, d1.height, '#2dd4bf', 0.3, time);
const p2 = document.getElementById('pulse2');
drawPulse(p2.getContext('2d'), p2.width, p2.height, time + 1, 'rgba(255,255,255,0.7)');
const d2 = document.getElementById('dial2');
drawDial(d2.getContext('2d'), d2.width, d2.height, '#a78bfa', 0.15, time);
const p3 = document.getElementById('pulse3');
drawPulse(p3.getContext('2d'), p3.width, p3.height, time + 2, 'rgba(255,255,255,0.7)');
const d3 = document.getElementById('dial3');
drawDial(d3.getContext('2d'), d3.width, d3.height, '#fb7185', 0.25, time);
requestAnimationFrame(animate);
};
animate();
</script>
</section>