532 lines
18 KiB
HTML
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>
|