Tunnel engine: - 30 speed lines (near/mid/far) rushing left→right - 6 KPI cards at 3 depths: nearest blurry+large, far sharp+small - Cruise speed 0.8, boost 2.5× on CTA hover - 40% cards start in viewport for instant visual - Motion blur: cards blur proportional to speed (0→4px) - Glow expands & brightens with speed Drive-the-car metaphor: hover = gas pedal, cards & lines accelerate Like driving through a tunnel with business data flying by
463 lines
15 KiB
HTML
463 lines
15 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 — Tunnel Hero</title>
|
||
<link rel="preconnect" href="https://fonts.cdnfonts.com">
|
||
<link href="https://fonts.cdnfonts.com/css/inter" rel="stylesheet">
|
||
<link href="https://fonts.cdnfonts.com/css/jetbrains-mono" rel="stylesheet">
|
||
<style>
|
||
/* ============================================================
|
||
DESIGN TOKENS
|
||
============================================================ */
|
||
:root {
|
||
--bg: #0a0a0a;
|
||
--text: #f5f5f5;
|
||
--text-sec: #a1a1aa;
|
||
--text-dim: #52525b;
|
||
--accent: #22c55e;
|
||
--accent-dim: rgba(34,197,94,0.12);
|
||
--blue: #3b82f6;
|
||
--blue-dim: rgba(59,130,246,0.12);
|
||
--amber: #f59e0b;
|
||
--red: #ef4444;
|
||
--font: 'Inter', -apple-system, sans-serif;
|
||
--mono: 'JetBrains Mono', 'SF Mono', monospace;
|
||
}
|
||
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body {
|
||
background: var(--bg); color: var(--text); font-family: var(--font);
|
||
overflow-x: hidden; min-height: 100vh;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
/* ============================================================
|
||
TUNNEL CONTAINER — perspective origin at center
|
||
============================================================ */
|
||
.tunnel {
|
||
position: relative; width: 100%; height: 100vh;
|
||
overflow: hidden;
|
||
perspective: 800px;
|
||
perspective-origin: 50% 50%;
|
||
}
|
||
|
||
/* ============================================================
|
||
CENTER GLOW — the light at the end of the tunnel
|
||
============================================================ */
|
||
.tunnel-glow {
|
||
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.12) 0%,
|
||
rgba(34,197,94,0.04) 30%,
|
||
rgba(34,197,94,0.01) 55%,
|
||
transparent 70%
|
||
);
|
||
pointer-events: none; z-index: 1;
|
||
animation: glowBreath 3s ease-in-out infinite;
|
||
}
|
||
@keyframes glowBreath {
|
||
0%, 100% { opacity: 0.6; transform: translate(-50%, -50%) scale(1); }
|
||
50% { opacity: 1; transform: translate(-50%, -50%) scale(1.08); }
|
||
}
|
||
|
||
/* ============================================================
|
||
SPEED LINES — horizontal streaks rushing past
|
||
Three depth layers: near (fast), mid, far (slow)
|
||
============================================================ */
|
||
.speed-lines { position: absolute; inset: 0; pointer-events: none; z-index: 2; }
|
||
|
||
.speed-line {
|
||
position: absolute; height: 1px;
|
||
background: linear-gradient(90deg,
|
||
transparent 0%,
|
||
rgba(255,255,255,0.06) 30%,
|
||
rgba(34,197,94,0.15) 50%,
|
||
rgba(255,255,255,0.04) 70%,
|
||
transparent 100%
|
||
);
|
||
filter: blur(0.5px);
|
||
}
|
||
|
||
/* Near layer — thick, fast, blurred */
|
||
.speed-line.near {
|
||
height: 2px; opacity: 0.7;
|
||
filter: blur(1.5px);
|
||
background: linear-gradient(90deg,
|
||
transparent 0%,
|
||
rgba(255,255,255,0.1) 35%,
|
||
rgba(34,197,94,0.25) 50%,
|
||
rgba(255,255,255,0.06) 65%,
|
||
transparent 100%
|
||
);
|
||
}
|
||
.speed-line.mid { height: 1.5px; opacity: 0.4; filter: blur(0.8px); }
|
||
.speed-line.far { height: 1px; opacity: 0.2; filter: blur(0.3px); }
|
||
|
||
/* ============================================================
|
||
KPI CARDS — flying through the tunnel at different depths
|
||
============================================================ */
|
||
.kpi-card {
|
||
position: absolute;
|
||
background: rgba(22,22,24,0.8);
|
||
backdrop-filter: blur(8px);
|
||
border: 1px solid rgba(255,255,255,0.06);
|
||
border-radius: 12px;
|
||
padding: 14px 16px;
|
||
width: 200px;
|
||
pointer-events: none;
|
||
transform-style: preserve-3d;
|
||
transition: filter 0.3s ease;
|
||
z-index: 10;
|
||
}
|
||
|
||
/* Depth layers via translateZ */
|
||
.kpi-card.depth-1 { /* nearest — large, fast, blurred */
|
||
box-shadow: 0 0 40px rgba(34,197,94,0.08);
|
||
border-color: rgba(255,255,255,0.1);
|
||
}
|
||
.kpi-card.depth-2 { /* mid */
|
||
box-shadow: 0 0 20px rgba(0,0,0,0.4);
|
||
}
|
||
.kpi-card.depth-3 { /* far — small, slow, sharp */
|
||
box-shadow: none;
|
||
border-color: rgba(255,255,255,0.04);
|
||
}
|
||
|
||
.kpi-card .label {
|
||
font-size: 9px; text-transform: uppercase; letter-spacing: 0.08em;
|
||
color: var(--text-dim); font-weight: 600; margin-bottom: 4px;
|
||
}
|
||
.kpi-card .value {
|
||
font-family: var(--mono); font-weight: 700; line-height: 1.15;
|
||
margin-bottom: 3px;
|
||
}
|
||
.kpi-card .value.green { color: var(--accent); }
|
||
.kpi-card .value.blue { color: var(--blue); }
|
||
.kpi-card .value.amber { color: var(--amber); }
|
||
|
||
.kpi-card .trend {
|
||
display: inline-flex; align-items: center; gap: 3px;
|
||
padding: 2px 5px; border-radius: 4px;
|
||
font-family: var(--mono); font-weight: 600; font-size: 9px;
|
||
}
|
||
.kpi-card .trend.up { color: var(--accent); background: var(--accent-dim); }
|
||
.kpi-card .trend.down { color: var(--red); background: rgba(239,68,68,0.12); }
|
||
|
||
/* Motion blur on cards — depends on speed */
|
||
.kpi-card .trail {
|
||
position: absolute; inset: 0; border-radius: 12px;
|
||
background: inherit; opacity: 0;
|
||
pointer-events: none;
|
||
z-index: -1;
|
||
}
|
||
|
||
/* ============================================================
|
||
CENTER CONTENT — the destination
|
||
============================================================ */
|
||
.hero-center {
|
||
position: absolute; top: 50%; left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
text-align: center; z-index: 30; max-width: 600px;
|
||
width: 100%; padding: 0 24px;
|
||
}
|
||
|
||
.hero-badge {
|
||
display: inline-flex; align-items: center; gap: 8px;
|
||
padding: 5px 14px; border: 1px solid rgba(255,255,255,0.1);
|
||
border-radius: 100px; margin-bottom: 20px;
|
||
font-size: 11px; color: var(--text-sec); font-family: var(--mono);
|
||
background: rgba(255,255,255,0.02);
|
||
}
|
||
.badge-dot {
|
||
width: 5px; height: 5px; background: var(--accent); border-radius: 50%;
|
||
animation: dotPulse 1.5s ease-in-out infinite;
|
||
}
|
||
@keyframes dotPulse {
|
||
0%, 100% { box-shadow: 0 0 3px var(--accent); }
|
||
50% { box-shadow: 0 0 10px var(--accent); }
|
||
}
|
||
|
||
.hero-center h1 {
|
||
font-size: clamp(1.8rem, 5vw, 4rem);
|
||
font-weight: 800; letter-spacing: -0.04em;
|
||
line-height: 1.08; margin-bottom: 16px;
|
||
}
|
||
.hero-center h1 .accent { color: var(--accent); }
|
||
|
||
.hero-center .sub {
|
||
font-size: 1rem; color: rgba(255,255,255,0.4);
|
||
line-height: 1.6; margin-bottom: 32px; max-width: 460px;
|
||
margin-left: auto; margin-right: auto;
|
||
}
|
||
|
||
.btns { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
|
||
|
||
.btn {
|
||
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
|
||
padding: 14px 28px; border-radius: 100px; font-weight: 600;
|
||
font-size: 0.9rem; cursor: pointer; border: none;
|
||
transition: all 0.25s ease; text-decoration: none;
|
||
}
|
||
.btn-accent {
|
||
background: var(--accent); color: #000;
|
||
}
|
||
.btn-accent:hover {
|
||
background: #16a34a; transform: translateY(-1px);
|
||
box-shadow: 0 12px 40px rgba(34,197,94,0.25);
|
||
}
|
||
.btn-outline {
|
||
background: transparent; border: 1px solid rgba(255,255,255,0.2);
|
||
color: var(--text);
|
||
}
|
||
.btn-outline:hover {
|
||
border-color: rgba(255,255,255,0.4);
|
||
background: rgba(255,255,255,0.03);
|
||
}
|
||
|
||
/* ============================================================
|
||
RESPONSIVE
|
||
============================================================ */
|
||
@media (max-width: 768px) {
|
||
.kpi-card { width: 150px; padding: 10px 12px; }
|
||
.kpi-card .value { font-size: 16px; }
|
||
.speed-line.near { opacity: 0.4; }
|
||
}
|
||
@media (max-width: 480px) {
|
||
.kpi-card { width: 120px; padding: 8px 10px; }
|
||
.kpi-card .value { font-size: 14px; }
|
||
.kpi-card .label { font-size: 8px; }
|
||
.speed-line.near, .speed-line.mid { opacity: 0.25; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="tunnel" id="tunnel">
|
||
|
||
<!-- Center glow -->
|
||
<div class="tunnel-glow" id="tunnelGlow"></div>
|
||
|
||
<!-- Speed lines -->
|
||
<div class="speed-lines" id="speedLines"></div>
|
||
|
||
<!-- KPI cards — injected by JS -->
|
||
|
||
<!-- Center content -->
|
||
<div class="hero-center">
|
||
<div class="hero-badge">
|
||
<span class="badge-dot"></span>
|
||
АНАЛИТИКА ДЛЯ СОБСТВЕННИКОВ
|
||
</div>
|
||
<h1>Живые цифры<br>вашего <span class="accent">бизнеса</span></h1>
|
||
<p class="sub">
|
||
10 ключевых метрик из 1С, CRM и банков —<br>
|
||
автоматически, каждое утро в телефоне
|
||
</p>
|
||
<div class="btns">
|
||
<a href="#cta" class="btn btn-accent"
|
||
onmouseenter="setSpeed(1)"
|
||
onmouseleave="setSpeed(0)">
|
||
Получить демо за 3 дня
|
||
</a>
|
||
<a href="#demo" class="btn btn-outline"
|
||
onmouseenter="setSpeed(1)"
|
||
onmouseleave="setSpeed(0)">
|
||
Посмотреть пример
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
/* ============================================================
|
||
TUNNEL ENGINE — cards & speed lines fly past at variable speed
|
||
============================================================ */
|
||
|
||
const tunnel = document.getElementById('tunnel');
|
||
const speedLinesEl = document.getElementById('speedLines');
|
||
const glow = document.getElementById('tunnelGlow');
|
||
|
||
// ── SPEED STATE ──
|
||
let targetSpeed = 0; // 0 = cruise, 1 = full throttle
|
||
let currentSpeed = 0; // lerped
|
||
const CRUISE = 0.8; // baseline perpetual motion
|
||
const BOOST = 2.5; // max throttle multiplier
|
||
|
||
// ── SPEED LINES ──
|
||
const LINES_COUNT = 30;
|
||
const lines = [];
|
||
|
||
function createSpeedLines() {
|
||
speedLinesEl.innerHTML = '';
|
||
const vw = window.innerWidth;
|
||
for (let i = 0; i < LINES_COUNT; i++) {
|
||
const div = document.createElement('div');
|
||
const depthRoll = Math.random();
|
||
let cls, w;
|
||
if (depthRoll < 0.18) {
|
||
cls = 'near'; w = 80 + Math.random() * 120;
|
||
} else if (depthRoll < 0.45) {
|
||
cls = 'mid'; w = 40 + Math.random() * 80;
|
||
} else {
|
||
cls = 'far'; w = 20 + Math.random() * 40;
|
||
}
|
||
div.className = `speed-line ${cls}`;
|
||
div.style.top = Math.random() * 100 + '%';
|
||
div.style.width = w + '%';
|
||
speedLinesEl.appendChild(div);
|
||
// Start randomly across the viewport + off-screen right
|
||
const startX = -w * vw / 100 + Math.random() * (vw + w * vw / 100);
|
||
lines.push({ el: div, cls, width: w, offset: startX, speedMul: cls === 'near' ? 3.5 : cls === 'mid' ? 2 : 1 });
|
||
}
|
||
}
|
||
|
||
// ── KPI CARDS ──
|
||
const CARD_DATA = [
|
||
{ label: 'Выручка за 30 дней', value: '2 051 857 ₽', trend: '↑ +15%', dir: 'up', color: 'green', depth: 1, side: 'left' },
|
||
{ label: 'CPA средний', value: '890 ₽', trend: '↓ −5%', dir: 'down', color: 'blue', depth: 2, side: 'left' },
|
||
{ label: 'ROI рекламы', value: '91.8%', trend: '↑ +5%', dir: 'up', color: 'amber', depth: 2, side: 'right' },
|
||
{ label: 'Прибыль', value: '981 857 ₽', trend: '↑ +12%', dir: 'up', color: 'green', depth: 1, side: 'right' },
|
||
{ label: 'Продажи', value: '1 247 шт', trend: '↑ +12%', dir: 'up', color: 'blue', depth: 3, side: 'left' },
|
||
{ label: 'Конверсия', value: '2.8%', trend: '↑ +0.3%',dir: 'up', color: 'amber', depth: 3, side: 'right' },
|
||
];
|
||
|
||
const cards = [];
|
||
|
||
function createCards() {
|
||
CARD_DATA.forEach((d, i) => {
|
||
const div = document.createElement('div');
|
||
div.className = `kpi-card depth-${d.depth}`;
|
||
div.innerHTML = `
|
||
<div class="label">${d.label}</div>
|
||
<div class="value ${d.color}">${d.value}</div>
|
||
<span class="trend ${d.dir}">${d.trend}</span>
|
||
`;
|
||
tunnel.appendChild(div);
|
||
|
||
const rect = div.getBoundingClientRect();
|
||
const vw = window.innerWidth;
|
||
|
||
// Starting position: spread across viewport, some visible immediately
|
||
let startX;
|
||
if (Math.random() < 0.4) {
|
||
// Start inside viewport for immediate visibility
|
||
const buffer = rect.width + 40;
|
||
startX = d.side === 'left'
|
||
? buffer + Math.random() * (vw * 0.3)
|
||
: vw - buffer - Math.random() * (vw * 0.3);
|
||
} else {
|
||
startX = d.side === 'left'
|
||
? -rect.width - 100 - Math.random() * 300
|
||
: window.innerWidth + 100 + Math.random() * 200;
|
||
}
|
||
|
||
const startY = 15 + Math.random() * 70; // % of viewport height
|
||
|
||
div.style.top = startY + '%';
|
||
div.style.left = startX + 'px';
|
||
div.style.transform = `scale(${1 - d.depth * 0.12})`;
|
||
|
||
cards.push({
|
||
el: div,
|
||
depth: d.depth,
|
||
side: d.side,
|
||
x: startX,
|
||
y: startY,
|
||
scale: 1 - d.depth * 0.12,
|
||
speed: 0.5 + (4 - d.depth) * 0.5, // depth1=2.0, depth2=1.5, depth3=1.0
|
||
});
|
||
});
|
||
}
|
||
|
||
// ── ANIMATION LOOP ──
|
||
function loop() {
|
||
const dt = 1 / 60; // approximate frame delta
|
||
const lerp = 0.035;
|
||
|
||
// Smooth speed transition
|
||
const effectiveTarget = CRUISE + targetSpeed * BOOST;
|
||
currentSpeed += (effectiveTarget - currentSpeed) * lerp;
|
||
|
||
// ── Move speed lines ──
|
||
const vw = window.innerWidth;
|
||
lines.forEach(line => {
|
||
line.offset += currentSpeed * line.speedMul * 2.5;
|
||
const lineW = line.width * vw / 100;
|
||
// Reset when fully off-screen right
|
||
if (line.offset > vw + 100) {
|
||
line.offset = -lineW - Math.random() * 300;
|
||
line.el.style.top = Math.random() * 100 + '%';
|
||
}
|
||
line.el.style.transform = `translateX(${line.offset}px)`;
|
||
});
|
||
|
||
// ── Move KPI cards ──
|
||
cards.forEach(card => {
|
||
const cardSpeed = card.speed * currentSpeed * 1.2;
|
||
const dir = card.side === 'left' ? 1 : -1;
|
||
|
||
card.x += cardSpeed * dir;
|
||
|
||
const cardW = card.el.offsetWidth;
|
||
const cardH = card.el.offsetHeight;
|
||
|
||
// Reset when off-screen
|
||
const offRight = card.side === 'left' && card.x > vw + 100;
|
||
const offLeft = card.side === 'right' && card.x < -cardW - 100;
|
||
|
||
if (offRight || offLeft) {
|
||
card.x = card.side === 'left'
|
||
? -cardW - Math.random() * 400
|
||
: vw + Math.random() * 200;
|
||
card.y = 10 + Math.random() * 75;
|
||
card.el.style.top = card.y + '%';
|
||
}
|
||
|
||
// Motion blur via filter
|
||
const blur = Math.min(currentSpeed * 2.5, 4);
|
||
card.el.style.filter = `blur(${blur}px)`;
|
||
|
||
card.el.style.left = card.x + 'px';
|
||
card.el.style.transform = `scale(${card.scale})`;
|
||
});
|
||
|
||
// ── Glow reacts to speed ──
|
||
const glowScale = 1 + currentSpeed * 0.15;
|
||
const glowOpacity = 0.5 + currentSpeed * 0.6;
|
||
glow.style.transform = `translate(-50%, -50%) scale(${glowScale})`;
|
||
glow.style.opacity = Math.min(glowOpacity, 1.3);
|
||
|
||
requestAnimationFrame(loop);
|
||
}
|
||
|
||
// ── SPEED CONTROL ──
|
||
function setSpeed(active) {
|
||
targetSpeed = active ? 1 : 0;
|
||
}
|
||
|
||
// ── INIT ──
|
||
createSpeedLines();
|
||
createCards();
|
||
requestAnimationFrame(loop);
|
||
|
||
// Handle resize
|
||
window.addEventListener('resize', () => {
|
||
// Reset card positions to spread across viewport
|
||
const vw = window.innerWidth;
|
||
cards.forEach(card => {
|
||
if (card.x > vw + 200 || card.x < -card.el.offsetWidth - 200) {
|
||
card.x = card.side === 'left'
|
||
? -card.el.offsetWidth - Math.random() * 300
|
||
: vw + Math.random() * 200;
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|