Загрузка...

Скевоморфная 3D карточка метрик аналитики: емкость, CPU, память, сеть. Анимированные SVG кольца, параллакс, Tailwind. Для дашбордов.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Premium Skeuomorphic Analytics Card</title>
<!-- Fonts: Inter for UI text, JetBrains Mono for numbers -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans:['Inter', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
colors: {
graphite: '#0b0b0d',
cardBase: '#151519',
cardTop: '#1d1d22',
}
}
}
}
</script>
<style>
/* Base Reset & Background */
body {
background-color: #0b0b0d;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
color: #ffffff;
overflow: hidden;
}
/* 3D Perspective Container */
.perspective-container {
perspective: 1400px;
}
/* Main Skeuomorphic Card Body */
.skeuo-card {
background: linear-gradient(145deg, #1c1c22 0%, #0e0e11 100%);
box-shadow:
/* Outer soft drop shadow */
25px 25px 50px rgba(0, 0, 0, 0.9),
/* Opposite light rim to simulate ambient occlusion */
-10px -10px 30px rgba(35, 35, 45, 0.2),
/* Micro-reflection on the top/left edge */
inset 1.5px 1.5px 2px rgba(255, 255, 255, 0.12),
/* Inner shadow on bottom/right for bevel depth */
inset -1.5px -1.5px 3px rgba(0, 0, 0, 0.7);
border: 1px solid #222228;
transform-style: preserve-3d;
will-change: transform;
}
/* Subtle Noise Texture for anodized metal look */
.noise-texture {
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
opacity: 0.04;
mix-blend-mode: overlay;
pointer-events: none;
border-radius: inherit;
}
/* Inset Surfaces (Dials / Screens) */
.skeuo-inset {
background: linear-gradient(145deg, #09090b 0%, #121216 100%);
box-shadow:
/* Deep dark inset */
inset 6px 6px 15px rgba(0, 0, 0, 0.8),
/* Inset opposite light to show inner lip */
inset -2px -2px 8px rgba(35, 35, 45, 0.3),
/* Outer lip reflection */
1px 1px 1px rgba(255, 255, 255, 0.06);
border: 1px solid #000;
}
/* Hardware Details: Screws */
.screw {
width: 6px;
height: 6px;
border-radius: 50%;
background: #111;
box-shadow:
inset 1px 1px 2px rgba(0, 0, 0, 0.9),
0 1px 1px rgba(255, 255, 255, 0.15);
}
/* Text Glows */
.glow-text-teal { text-shadow: 0 0 10px rgba(20, 184, 166, 0.6); }
.glow-text-violet { text-shadow: 0 0 10px rgba(139, 92, 246, 0.6); }
/* Parallax Depth Classes */
.parallax-layer { transform: translateZ(25px); }
.parallax-layer-lg { transform: translateZ(45px); }
.parallax-layer-xl { transform: translateZ(60px); }
/* Light Sweep Effect */
.sweep-light {
background: linear-gradient(
90deg,
transparent 0%,
rgba(255, 255, 255, 0.03) 40%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0.03) 60%,
transparent 100%
);
transform: translateX(-150%) skewX(-20deg);
transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}
.perspective-container:hover .sweep-light {
transform: translateX(150%) skewX(-20deg);
}
/* SVG Animation */
.progress-ring {
transition: stroke-dashoffset 2s cubic-bezier(0.16, 1, 0.3, 1);
}
</style>
</head>
<body>
<!-- 3D Perspective Container -->
<div class="perspective-container group relative w-[380px] h-[540px]">
<!-- Main Card Body -->
<div id="card" class="skeuo-card relative w-full h-full rounded-[32px] p-7 flex flex-col justify-between">
<!-- Noise Texture Overlay -->
<div class="noise-texture"></div>
<!-- Dynamic Specular Glare (Controlled by JS) -->
<div id="glare" class="absolute inset-0 rounded-[32px] pointer-events-none z-50 transition-opacity duration-300 opacity-0 group-hover:opacity-100"></div>
<!-- Light Sweep Layer (Triggered on hover) -->
<div class="absolute inset-0 rounded-[32px] pointer-events-none overflow-hidden z-40">
<div class="sweep-light absolute top-0 bottom-0 left-0 w-[200%] h-full"></div>
</div>
<!-- Hardware Detail: Corner Screws -->
<div class="absolute top-4 left-4 screw"></div>
<div class="absolute top-4 right-4 screw"></div>
<div class="absolute bottom-4 left-4 screw"></div>
<div class="absolute bottom-4 right-4 screw"></div>
<!-- Header Section -->
<div class="parallax-layer flex justify-between items-center z-10 mt-2">
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-violet-500 shadow-[0_0_8px_#8b5cf6]"></div>
<span class="text-[10px] font-bold tracking-[0.25em] text-gray-400">SYS.ANALYTICS</span>
</div>
<!-- Mini decorative grill -->
<div class="flex gap-1">
<div class="w-1 h-3 rounded-full skeuo-inset"></div>
<div class="w-1 h-3 rounded-full skeuo-inset"></div>
<div class="w-1 h-3 rounded-full skeuo-inset"></div>
</div>
</div>
<!-- Main Circular Ring Section -->
<div class="parallax-layer-lg relative flex flex-1 items-center justify-center z-10 my-6">
<!-- The Inset Dial Container -->
<div class="relative w-64 h-64 rounded-full skeuo-inset flex items-center justify-center">
<!-- SVG Progress Ring -->
<svg width="240" height="240" viewBox="0 0 240 240" class="absolute transform -rotate-90">
<defs>
<!-- Violet to Teal Gradient -->
<linearGradient id="ringGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#8b5cf6" />
<stop offset="100%" stop-color="#14b8a6" />
</linearGradient>
<!-- Drop Shadow Glow Filter for the progress line -->
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- Inner Shadow for the background track -->
<filter id="innerShadow">
<feOffset dx="1" dy="1"/>
<feGaussianBlur stdDeviation="3" result="offset-blur"/>
<feComposite operator="out" in="SourceGraphic" in2="offset-blur" result="inverse"/>
<feFlood flood-color="black" flood-opacity="0.9" result="color"/>
<feComposite operator="in" in="color" in2="inverse" result="shadow"/>
<feComposite operator="over" in="shadow" in2="SourceGraphic"/>
</filter>
</defs>
<!-- Track Circle (Dark Inset) -->
<circle cx="120" cy="120" r="100" fill="none" stroke="#101014" stroke-width="14" filter="url(#innerShadow)" />
<!-- Progress Circle (Glowing Segment) -->
<circle id="progressRing" cx="120" cy="120" r="100" fill="none" stroke="url(#ringGrad)" stroke-width="14" stroke-linecap="round" class="progress-ring" filter="url(#glow)" />
</svg>
<!-- Center Display Number -->
<div class="parallax-layer-xl flex flex-col items-center justify-center mt-2 relative z-20">
<div class="text-6xl font-mono text-white flex items-start leading-none tracking-tighter shadow-black drop-shadow-lg">
<span id="mainVal">0</span>
<span class="text-2xl text-violet-400 mt-1 ml-1 glow-text-violet">%</span>
</div>
<span class="text-[10px] font-bold tracking-[0.3em] text-gray-500 mt-3">CAPACITY</span>
</div>
</div>
</div>
<!-- Metrics Grid Section -->
<div class="parallax-layer grid grid-cols-3 gap-4 z-10 mb-2">
<!-- CPU Load Metric -->
<div class="skeuo-inset rounded-2xl p-4 flex flex-col items-center justify-center relative">
<span class="text-[9px] font-bold tracking-widest text-gray-500 mb-2">CPU LOAD</span>
<div class="flex items-baseline font-mono">
<span class="text-lg font-semibold text-teal-400 glow-text-teal" id="cpuVal">0</span>
<span class="text-[10px] text-teal-600 ml-0.5">%</span>
</div>
</div>
<!-- Memory Metric -->
<div class="skeuo-inset rounded-2xl p-4 flex flex-col items-center justify-center relative overflow-hidden">
<span class="text-[9px] font-bold tracking-widest text-gray-500 mb-2">MEMORY</span>
<div class="flex items-baseline font-mono">
<span class="text-lg font-semibold text-violet-400 glow-text-violet" id="memVal">0</span>
<span class="text-[10px] text-violet-600 ml-0.5">%</span>
</div>
<!-- Tiny physical active bar indicator -->
<div class="absolute bottom-0 left-0 h-[2px] bg-violet-500 shadow-[0_0_5px_#8b5cf6] transition-all duration-[2s] ease-out w-0" id="memBar"></div>
</div>
<!-- Network Metric -->
<div class="skeuo-inset rounded-2xl p-4 flex flex-col items-center justify-center relative">
<span class="text-[9px] font-bold tracking-widest text-gray-500 mb-2">NETWORK</span>
<div class="flex items-baseline font-mono">
<span class="text-lg font-semibold text-gray-200" id="netVal">0</span>
<span class="text-[10px] text-gray-500 ml-1">mb/s</span>
</div>
</div>
</div>
</div>
</div>
<!-- Vanilla JS Interactions & Animations -->
<script>
document.addEventListener("DOMContentLoaded", () => {
// --- 1. Ring and Count-Up Animation Logic ---
const ring = document.getElementById("progressRing");
const radius = ring.r.baseVal.value;
const circumference = 2 * Math.PI * radius;
// Initialize Ring Stroke
ring.style.strokeDasharray = `${circumference} ${circumference}`;
ring.style.strokeDashoffset = circumference; // Start completely hidden
// Define targets
const targets = {
main: 82, // Overall Capacity
cpu: 64, // CPU Load
mem: 78, // Memory
net: 412 // Network throughput
};
const animDuration = 2200; // ms
const startTime = performance.now();
// Custom easing function (easeOutExpo)
const easeOut = (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
function animateCounters(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / animDuration, 1);
const easedProgress = easeOut(progress);
// Animate SVG Ring
const offset = circumference - (targets.main / 100) * circumference * easedProgress;
ring.style.strokeDashoffset = offset;
// Animate Text Metrics
document.getElementById("mainVal").innerText = Math.floor(targets.main * easedProgress);
document.getElementById("cpuVal").innerText = Math.floor(targets.cpu * easedProgress);
document.getElementById("memVal").innerText = Math.floor(targets.mem * easedProgress);
document.getElementById("netVal").innerText = Math.floor(targets.net * easedProgress);
// Animate Memory Bar Width Indicator
document.getElementById("memBar").style.width = `${Math.floor(targets.mem * easedProgress)}%`;
if (progress < 1) {
requestAnimationFrame(animateCounters);
}
}
// Start counter animation
requestAnimationFrame(animateCounters);
// --- 2. 3D Tilt & Specular Glare (Lerped for premium feel) ---
const container = document.querySelector('.perspective-container');
const card = document.getElementById('card');
const glare = document.getElementById('glare');
// Tracking variables
let mouseX = 0, mouseY = 0;
let currentX = 0, currentY = 0;
const maxRotation = 12; // Maximum tilt angle in degrees
// Update mouse coordinates relative to center on mousemove
container.addEventListener('mousemove', (e) => {
const rect = container.getBoundingClientRect();
// Normalize values between -0.5 and 0.5
mouseX = ((e.clientX - rect.left) / rect.width) - 0.5;
mouseY = ((e.clientY - rect.top) / rect.height) - 0.5;
});
// Reset coordinates when mouse leaves
container.addEventListener('mouseleave', () => {
mouseX = 0;
mouseY = 0;
});
// Animation loop for smooth physical interpolation (lerping)
function renderTilt() {
// Lerp formula: current = current + (target - current) * friction
currentX += (mouseX - currentX) * 0.1;
currentY += (mouseY - currentY) * 0.1;
// Calculate Rotation: inverted Y for natural pitch, normal X for yaw
const rotateX = currentY * -maxRotation;
const rotateY = currentX * maxRotation;
card.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
// Dynamic Glare: Map lerped position to percentage (0% to 100%)
const glareX = (currentX + 0.5) * 100;
const glareY = (currentY + 0.5) * 100;
// Create a smooth radial gradient that acts as a specular highlight
glare.style.background = `radial-gradient(circle at ${glareX}% ${glareY}%, rgba(255, 255, 255, 0.08) 0%, transparent 60%)`;
requestAnimationFrame(renderTilt);
}
// Start tilt animation loop
renderTilt();
});
</script>
</body>
</html>