Files
roi24/hero-matrix.html
T

532 lines
18 KiB
HTML

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ROI24 — Matrix Tunnel Hero</title>
<link href="https://fonts.cdnfonts.com/css/inter" rel="stylesheet">
<link href="https://fonts.cdnfonts.com/css/jetbrains-mono" rel="stylesheet">
<style>
:root {
--bg: #0a0a0a;
--text: #f5f5f5;
--text-sec: #a1a1aa;
--text-dim: #52525b;
--accent: #22c55e;
--accent-dim: rgba(34,197,94,0.12);
--accent-glow: rgba(34,197,94,0.3);
--mono: 'JetBrains Mono', monospace;
--font: 'Inter', sans-serif;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
background: var(--bg); color: var(--text); font-family: var(--font);
overflow: hidden; height: 100vh;
-webkit-font-smoothing: antialiased;
}
.stage {
position: relative; width: 100%; height: 100vh; overflow: hidden;
}
/* ── Matrix Tunnel Canvas ── */
#matrix-canvas {
position: absolute; inset: 0; z-index: 1;
}
/* ── FX Layer ── */
.fx-layer {
position: absolute; inset: 0; pointer-events: none; z-index: 2;
}
.fx-layer::before {
content: ''; position: absolute; inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");
opacity: 0.5;
}
.goo {
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 600px; height: 600px; border-radius: 50%;
background: radial-gradient(circle,
rgba(34,197,94,0.06) 0%,
rgba(34,197,94,0.02) 40%,
transparent 70%
);
}
/* ── Radial speed lines ── */
.radial-lines {
position: absolute; inset: 0; pointer-events: none;
}
.radial-line {
position: absolute; top: 50%; left: 50%;
height: 1px;
background: linear-gradient(90deg,
rgba(34,197,94,0.1) 0%,
rgba(34,197,94,0.04) 60%,
transparent 100%
);
transform-origin: left center;
}
.radial-line.fast {
background: linear-gradient(90deg,
rgba(34,197,94,0.18) 0%,
rgba(34,197,94,0.06) 50%,
transparent 100%
);
height: 1.5px;
}
/* ── Content overlay ── */
.content {
position: absolute; inset: 0; z-index: 10;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
pointer-events: none;
}
.hero-headline {
text-align: center; margin-bottom: 24px;
pointer-events: auto;
}
.hero-headline .tag {
display: inline-flex; align-items: center; gap: 6px;
font-family: var(--mono); font-size: 11px; font-weight: 600;
color: var(--accent); background: var(--accent-dim);
border: 1px solid rgba(34,197,94,0.2);
border-radius: 20px; padding: 4px 14px;
margin-bottom: 20px; letter-spacing: 0.05em; text-transform: uppercase;
}
.hero-headline .tag .dot {
width: 6px; height: 6px; background: var(--accent);
border-radius: 50%; box-shadow: 0 0 6px var(--accent-glow);
animation: pulse 2s ease-in-out infinite;
}
.hero-headline h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800; letter-spacing: -0.03em;
line-height: 1.15; margin-bottom: 16px;
background: linear-gradient(135deg, #ffffff 0%, #a1a1aa 100%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-headline h1 .accent {
background: linear-gradient(135deg, var(--accent) 0%, #4ade80 100%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-headline p {
font-size: clamp(0.9rem, 1.5vw, 1.1rem);
color: var(--text-sec); max-width: 560px;
margin: 0 auto 28px; line-height: 1.6;
}
.cta-group {
display: flex; gap: 12px; align-items: center;
pointer-events: auto;
}
.cta-primary {
display: inline-flex; align-items: center; gap: 8px;
padding: 12px 28px;
background: var(--accent);
color: #0a0a0a;
font-family: var(--mono); font-size: 13px; font-weight: 700;
border: none; border-radius: 10px;
cursor: pointer; letter-spacing: 0.02em;
box-shadow: 0 0 20px rgba(34,197,94,0.25);
transition: all 0.3s ease;
text-decoration: none;
}
.cta-primary:hover {
background: #4ade80;
box-shadow: 0 0 30px rgba(34,197,94,0.4);
transform: translateY(-1px);
}
.cta-secondary {
display: inline-flex; align-items: center; gap: 6px;
padding: 12px 24px;
background: transparent;
color: var(--text-sec);
font-family: var(--mono); font-size: 13px; font-weight: 500;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 10px;
cursor: pointer; letter-spacing: 0.02em;
transition: all 0.3s ease;
text-decoration: none;
}
.cta-secondary:hover {
color: var(--text); border-color: rgba(255,255,255,0.2);
}
/* ── KPI Cards ── */
.kpi-card {
position: absolute;
background: rgba(10,10,10,0.85);
backdrop-filter: blur(8px);
border: 1px solid rgba(34,197,94,0.15);
border-radius: 12px;
padding: 14px 18px;
pointer-events: auto;
min-width: 140px;
box-shadow: 0 4px 24px rgba(0,0,0,0.4);
transition: border-color 0.3s ease, box-shadow 0.3s ease;
opacity: 0;
animation: cardIn 0.6s ease forwards;
}
.kpi-card:hover {
border-color: rgba(34,197,94,0.35);
box-shadow: 0 0 20px rgba(34,197,94,0.15);
}
.kpi-card .kpi-label {
font-family: var(--mono); font-size: 9px; font-weight: 600;
color: var(--text-dim); text-transform: uppercase;
letter-spacing: 0.1em; margin-bottom: 6px;
}
.kpi-card .kpi-value {
font-family: var(--mono); font-size: 22px; font-weight: 700;
color: var(--accent); margin-bottom: 4px; line-height: 1;
}
.kpi-card .kpi-sub {
font-family: var(--mono); font-size: 10px;
color: var(--text-dim);
}
.kpi-card .kpi-trend {
display: inline-flex; align-items: center; gap: 3px;
font-family: var(--mono); font-size: 10px; font-weight: 600;
padding: 2px 6px; border-radius: 4px;
margin-top: 6px;
}
.kpi-card .kpi-trend.up { color: var(--accent); background: var(--accent-dim); }
.kpi-card .kpi-trend.down { color: var(--red); background: rgba(239,68,68,0.12); }
.scanlines {
position: absolute; inset: 0; z-index: 4;
pointer-events: none;
background: repeating-linear-gradient(
0deg, transparent, transparent 2px,
rgba(0,0,0,0.03) 2px, rgba(0,0,0,0.03) 4px
);
}
@keyframes pulse {
0%,100% { box-shadow: 0 0 6px var(--accent-glow); }
50% { box-shadow: 0 0 14px rgba(34,197,94,0.25); }
}
@keyframes cardIn {
from { opacity:0; transform: scale(0.85) translateY(15px); }
to { opacity:1; transform: scale(1) translateY(0); }
}
@media(max-width:768px){
.kpi-card { min-width: 110px; padding: 10px 14px; }
.kpi-card .kpi-value { font-size: 18px; }
.cta-group { flex-direction: column; }
}
</style>
</head>
<body>
<div class="stage">
<canvas id="matrix-canvas"></canvas>
<div class="fx-layer">
<div class="goo"></div>
<div class="radial-lines" id="radial-lines"></div>
</div>
<!-- KPI Cards -->
<div class="kpi-card" id="kpi-cpa">
<div class="kpi-label">CPA</div>
<div class="kpi-value" data-target="890" data-suffix=" ₽">0 ₽</div>
<div class="kpi-sub">средняя продажа</div>
<div class="kpi-trend up">↓ 5%</div>
</div>
<div class="kpi-card" id="kpi-roi">
<div class="kpi-label">ROI</div>
<div class="kpi-value" data-target="91.8" data-suffix="%" data-decimals="1">0%</div>
<div class="kpi-sub">возврат инвестиций</div>
<div class="kpi-trend up">↑ 8%</div>
</div>
<div class="kpi-card" id="kpi-conversion">
<div class="kpi-label">Конверсия</div>
<div class="kpi-value" data-target="2.8" data-suffix="%" data-decimals="1">0%</div>
<div class="kpi-sub">заявка → продажа</div>
<div class="kpi-trend up">↑ 0.3%</div>
</div>
<div class="kpi-card" id="kpi-ltv">
<div class="kpi-label">LTV</div>
<div class="kpi-value" data-target="12400" data-suffix=" ₽">0 ₽</div>
<div class="kpi-sub">за 6 месяцев</div>
<div class="kpi-trend up">↑ 18%</div>
</div>
<div class="kpi-card" id="kpi-leads">
<div class="kpi-label">Лиды / мес</div>
<div class="kpi-value" data-target="44500" data-suffix="">0</div>
<div class="kpi-sub">за 30 дней</div>
<div class="kpi-trend up">↑ 15%</div>
</div>
<div class="kpi-card" id="kpi-revenue">
<div class="kpi-label">Выручка</div>
<div class="kpi-value" data-target="2051857" data-suffix=" ₽">0 ₽</div>
<div class="kpi-sub">за 30 дней</div>
<div class="kpi-trend up">↑ 12%</div>
</div>
<div class="content">
<div class="hero-headline">
<div class="tag"><span class="dot"></span>ROI24 — Attribution Analytics</div>
<h1>Управляйте бизнесом<br>на основе <span class="accent">данных</span></h1>
<p>Автоматический анализ рекламных расходов. Каждый рубль конвертируется в прибыль. CPA, LTV, ROI — в реальном времени.</p>
<div class="cta-group">
<a href="#" class="cta-primary">
<span>Начать бесплатно</span>
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 7h12M8 2l5 5-5 5"/></svg>
</a>
<a href="#" class="cta-secondary">Демо</a>
</div>
</div>
</div>
<div class="scanlines"></div>
</div>
<script>
(function() {
'use strict';
// ============================================================
// MATRIX TUNNEL — Canvas 2D 3D Projection
// ============================================================
const canvas = document.getElementById('matrix-canvas');
const ctx = canvas.getContext('2d');
let W, H, CX, CY;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W * Math.min(window.devicePixelRatio, 2);
canvas.height = H * Math.min(window.devicePixelRatio, 2);
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.scale(Math.min(window.devicePixelRatio, 2), Math.min(window.devicePixelRatio, 2));
CX = W / 2;
CY = H / 2;
}
resize();
// ── Tunnel Config ──
const RING_COUNT = 50;
const CHARS_PER_RING = 64;
const TUNNEL_RADIUS = 300;
const TUNNEL_DEPTH = 3000;
const FOCAL = 500;
// ── Tunnel Ring Data ──
const rings = [];
for (let r = 0; r < RING_COUNT; r++) {
const ring = { z: -r * (TUNNEL_DEPTH / RING_COUNT), chars: [] };
for (let c = 0; c < CHARS_PER_RING; c++) {
const angle = (c / CHARS_PER_RING) * Math.PI * 2;
ring.chars.push({
x: Math.cos(angle) * TUNNEL_RADIUS,
y: Math.sin(angle) * TUNNEL_RADIUS,
angle: angle,
value: Math.random() > 0.5 ? '0' : '1'
});
}
rings.push(ring);
}
// ── Floating particles ──
const PARTICLE_COUNT = 80;
const particles = [];
for (let i = 0; i < PARTICLE_COUNT; i++) {
particles.push({
x: (Math.random() - 0.5) * TUNNEL_RADIUS * 3,
y: (Math.random() - 0.5) * TUNNEL_RADIUS * 3,
z: -Math.random() * TUNNEL_DEPTH,
speed: 0.5 + Math.random() * 1.5,
size: 1 + Math.random() * 2
});
}
let zOffset = 0;
const FLOW_SPEED = 200;
function project(x, y, z) {
const scale = FOCAL / (FOCAL + z);
return {
sx: x * scale + CX,
sy: y * scale + CY,
scale: scale
};
}
function draw(time) {
const dt = 1 / 60;
ctx.clearRect(0, 0, W, H);
// Update flow
zOffset = (zOffset + FLOW_SPEED * dt) % (TUNNEL_DEPTH / RING_COUNT);
// ── Draw tunnel rings ──
for (let r = 0; r < RING_COUNT; r++) {
const baseZ = r * (TUNNEL_DEPTH / RING_COUNT);
let z = baseZ + zOffset;
if (z > TUNNEL_DEPTH) z -= TUNNEL_DEPTH;
const depthRatio = 1 - (z / TUNNEL_DEPTH);
for (let c = 0; c < CHARS_PER_RING; c++) {
const char = rings[r].chars[c];
const px = char.x;
const py = char.y;
const p = project(px, py, z);
const fontSize = Math.max(6, 28 * p.scale);
// Distance from center in screen space
const dx = p.sx - CX;
const dy = p.sy - CY;
const distFromCenter = Math.sqrt(dx * dx + dy * dy);
// Only draw if within screen bounds
if (distFromCenter > Math.max(W, H) * 0.65) continue;
if (p.scale < 0.1) continue;
// Opacity: fade in at far end, fade out at near end
let alpha;
if (depthRatio > 0.85) {
alpha = (1 - depthRatio) / 0.15 * 0.6;
} else if (depthRatio < 0.05) {
alpha = depthRatio / 0.05 * 0.8;
} else {
alpha = 0.6 + depthRatio * 0.2;
}
// Green intensity based on depth
const green = Math.floor(180 + depthRatio * 75);
ctx.fillStyle = `rgba(34, ${green}, 94, ${alpha})`;
ctx.font = `bold ${fontSize}px JetBrains Mono, monospace`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Add glow
if (p.scale > 0.3) {
ctx.shadowColor = 'rgba(34, 197, 94, 0.3)';
ctx.shadowBlur = 4 + depthRatio * 6;
} else {
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
}
ctx.fillText(char.value, p.sx, p.sy);
}
}
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
// ── Draw particles ──
for (const part of particles) {
part.z += part.speed * 2;
if (part.z > 100) part.z = -TUNNEL_DEPTH;
const p = project(part.x, part.y, part.z);
if (p.scale < 0.05 || p.scale > 3) continue;
const alpha = Math.min(1, p.scale * 0.5) * 0.4;
const size = Math.max(1, part.size * p.scale);
ctx.fillStyle = `rgba(34, 197, 94, ${alpha})`;
ctx.beginPath();
ctx.arc(p.sx, p.sy, size, 0, Math.PI * 2);
ctx.fill();
}
// ── Radial speed lines on canvas ──
const LINE_COUNT = 36;
for (let i = 0; i < LINE_COUNT; i++) {
const angle = (i / LINE_COUNT) * Math.PI * 2 + (time * 0.00005);
const innerR = 50;
const outerR = TUNNEL_RADIUS * 1.5;
const x1 = Math.cos(angle) * innerR + CX;
const y1 = Math.sin(angle) * innerR + CY;
const x2 = Math.cos(angle) * outerR + CX;
const y2 = Math.sin(angle) * outerR + CY;
ctx.strokeStyle = 'rgba(34, 197, 94, 0.04)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
// ── Radial lines (DOM) ──
const linesContainer = document.getElementById('radial-lines');
for (let i = 0; i < LINE_COUNT; i++) {
const line = document.createElement('div');
const isFast = Math.random() > 0.7;
line.className = 'radial-line' + (isFast ? ' fast' : '');
line.style.width = (30 + Math.random() * 35) + 'vw';
line.style.transform = `rotate(${(i / LINE_COUNT) * 360}deg)`;
line.style.opacity = 0.2 + Math.random() * 0.4;
linesContainer.appendChild(line);
}
// ── KPI cards stagger ──
document.querySelectorAll('.kpi-card').forEach((card, i) => {
card.style.animationDelay = `${0.3 + i * 0.12}s`;
});
// ── Animate KPI values ──
document.querySelectorAll('.kpi-value[data-target]').forEach(el => {
const target = parseFloat(el.dataset.target);
const decimals = parseInt(el.dataset.decimals) || 0;
const suffix = el.dataset.suffix || '';
const duration = 1800;
const start = performance.now();
function fmt(v) {
if (target >= 10000) return v.toLocaleString('ru-RU', { maximumFractionDigits: 0 }) + suffix;
return v.toFixed(decimals) + suffix;
}
function tick(now) {
const progress = Math.min((now - start) / duration, 1);
const ease = 1 - Math.pow(1 - progress, 3);
el.textContent = fmt(target * ease);
if (progress < 1) requestAnimationFrame(tick);
}
setTimeout(() => requestAnimationFrame(tick), 400 + Math.random() * 400);
});
// ── Mouse parallax ──
let mx = 0, my = 0;
document.addEventListener('mousemove', (e) => {
mx = (e.clientX / W - 0.5) * 0.3;
my = (e.clientY / H - 0.5) * 0.3;
});
function updateParallax() {
canvas.style.transform = `translate(${mx * 10}px, ${my * 10}px)`;
requestAnimationFrame(updateParallax);
}
requestAnimationFrame(updateParallax);
// ── Resize ──
window.addEventListener('resize', () => {
ctx.setTransform(1, 0, 0, 1, 0, 0);
resize();
});
})();
</script>
</body>
</html>