Orbital engine: - 8 KPI cards on elliptical orbits (rx:320-380, ry:100-140) - Cards rotate around center — appear on left/right edges - Visibility = |cos(angle)| — brightest at sides, fade toward center - Scale: 0.6–1.0 based on horizontal position - 60 radial lines from center outward (forward motion feel) - Glow pulses & expands with speed - Center: clean headline + CTA (composio.dev style) - Hover = accelerate orbit speed (CRUISE 0.4 → BOOST 3.0) No more dots, no falling snow — coherent orbital dance around the text
392 lines
13 KiB
HTML
392 lines
13 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 — Orbital 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: hidden; height: 100vh;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
/* ============================================================
|
||
STAGE
|
||
============================================================ */
|
||
.stage {
|
||
position: relative; width: 100%; height: 100vh;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ============================================================
|
||
FX LAYER
|
||
============================================================ */
|
||
.fx-layer {
|
||
position: absolute; inset: 0; pointer-events: none; z-index: 1;
|
||
}
|
||
|
||
/* Noise */
|
||
.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.02'/%3E%3C/svg%3E");
|
||
opacity: 0.5;
|
||
}
|
||
|
||
/* ── Glow at center ── */
|
||
.goo {
|
||
position: absolute; top: 50%; left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 500px; height: 500px; border-radius: 50%;
|
||
background: radial-gradient(circle,
|
||
rgba(34,197,94,0.10) 0%,
|
||
rgba(34,197,94,0.03) 35%,
|
||
transparent 65%
|
||
);
|
||
transition: transform 0.5s ease, opacity 0.5s ease;
|
||
}
|
||
|
||
/* ── Radial lines from center ── */
|
||
.radial-line {
|
||
position: absolute; top: 50%; left: 50%;
|
||
height: 1px;
|
||
background: linear-gradient(90deg,
|
||
rgba(255,255,255,0.08) 0%,
|
||
rgba(34,197,94,0.2) 30%,
|
||
rgba(34,197,94,0.05) 60%,
|
||
transparent 100%
|
||
);
|
||
transform-origin: left center;
|
||
}
|
||
.radial-line.fast {
|
||
background: linear-gradient(90deg,
|
||
rgba(255,255,255,0.15) 0%,
|
||
rgba(34,197,94,0.3) 25%,
|
||
rgba(34,197,94,0.08) 55%,
|
||
transparent 100%
|
||
);
|
||
height: 1.5px;
|
||
}
|
||
|
||
/* ============================================================
|
||
ORBITING CARDS
|
||
============================================================ */
|
||
.orbit-card {
|
||
position: absolute;
|
||
background: rgba(22,22,24,0.88);
|
||
backdrop-filter: blur(6px);
|
||
border: 1px solid rgba(255,255,255,0.08);
|
||
border-radius: 10px;
|
||
padding: 12px 16px;
|
||
width: 190px;
|
||
pointer-events: none;
|
||
transition: opacity 0.2s ease;
|
||
}
|
||
.orbit-card .tl {
|
||
font-size: 9px; text-transform: uppercase; letter-spacing: 0.07em;
|
||
color: var(--text-dim); font-weight: 600; margin-bottom: 4px;
|
||
}
|
||
.orbit-card .tv {
|
||
font-family: var(--mono); font-size: 18px; font-weight: 700;
|
||
line-height: 1.15; margin-bottom: 3px;
|
||
}
|
||
.orbit-card .tv.g { color: var(--accent); }
|
||
.orbit-card .tv.b { color: var(--blue); }
|
||
.orbit-card .tv.a { color: var(--amber); }
|
||
|
||
.orbit-card .td {
|
||
display: inline-flex; align-items: center; gap: 3px;
|
||
padding: 2px 6px; border-radius: 4px;
|
||
font-family: var(--mono); font-weight: 600; font-size: 10px;
|
||
}
|
||
.orbit-card .td.up { color: var(--accent); background: var(--accent-dim); }
|
||
.orbit-card .td.down { color: var(--red); background: rgba(239,68,68,0.12); }
|
||
|
||
/* ============================================================
|
||
CENTER — clean headline
|
||
============================================================ */
|
||
.center {
|
||
position: absolute; top: 50%; left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
text-align: center; z-index: 30; max-width: 560px;
|
||
width: 100%; padding: 0 24px;
|
||
}
|
||
|
||
.badge {
|
||
display: inline-flex; align-items: center; gap: 8px;
|
||
padding: 4px 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: dPulse 1.5s ease-in-out infinite;
|
||
}
|
||
@keyframes dPulse {
|
||
0%,100% { box-shadow: 0 0 3px var(--accent); }
|
||
50% { box-shadow: 0 0 10px var(--accent); }
|
||
}
|
||
|
||
.center h1 {
|
||
font-size: clamp(1.8rem, 5vw, 4rem);
|
||
font-weight: 800; letter-spacing: -0.04em;
|
||
line-height: 1.08; margin-bottom: 16px;
|
||
}
|
||
.center h1 .accent { color: var(--accent); }
|
||
|
||
.center .sub {
|
||
font-size: 1rem; color: rgba(255,255,255,0.4);
|
||
line-height: 1.6; margin-bottom: 32px;
|
||
}
|
||
|
||
.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) {
|
||
.orbit-card { width: 150px; padding: 10px 12px; }
|
||
.orbit-card .tv { font-size: 15px; }
|
||
}
|
||
@media (max-width: 480px) {
|
||
.orbit-card { width: 120px; padding: 8px 10px; }
|
||
.orbit-card .tv { font-size: 13px; }
|
||
.orbit-card .tl { font-size: 8px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="stage" id="stage">
|
||
|
||
<div class="fx-layer" id="fxLayer">
|
||
<div class="goo" id="goo"></div>
|
||
</div>
|
||
|
||
<!-- Orbit cards injected by JS -->
|
||
|
||
<div class="center">
|
||
<div class="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>
|
||
/* ============================================================
|
||
ORBITAL ENGINE
|
||
Cards rotate around center on elliptical paths
|
||
============================================================ */
|
||
|
||
const stage = document.getElementById('stage');
|
||
const fxLayer = document.getElementById('fxLayer');
|
||
const goo = document.getElementById('goo');
|
||
|
||
// ── SPEED ──
|
||
let targetSpeed = 0;
|
||
let currentSpeed = 0;
|
||
const CRUISE = 0.4;
|
||
const BOOST = 3.0;
|
||
|
||
// ── CARD DATA ──
|
||
const CARD_DATA = [
|
||
{ label: 'Выручка за 30 дн', value: '2 051 857 ₽', trend: '+15%', clr: 'g', dir: 'up' },
|
||
{ label: 'CPA средний', value: '890 ₽', trend: '−5%', clr: 'b', dir: 'down' },
|
||
{ label: 'Продажи', value: '1 247 шт', trend: '+12%', clr: 'b', dir: 'up' },
|
||
{ label: 'ROI рекламы', value: '91.8%', trend: '+5%', clr: 'a', dir: 'up' },
|
||
{ label: 'Прибыль', value: '981 857 ₽', trend: '+12%', clr: 'g', dir: 'up' },
|
||
{ label: 'Конверсия', value: '2.8%', trend: '+0.3%',clr: 'a', dir: 'up' },
|
||
{ label: 'LTV клиента', value: '12 400 ₽', trend: '+18%', clr: 'g', dir: 'up' },
|
||
{ label: 'Ср. чек', value: '1 850 ₽', trend: '−2%', clr: 'b', dir: 'down' },
|
||
];
|
||
|
||
// ── ORBIT CARDS ──
|
||
let orbitCards = [];
|
||
// Each: { el, angle, orbitRx, orbitRy, yOffset, data, visible }
|
||
|
||
function createOrbitCards() {
|
||
CARD_DATA.forEach((d, i) => {
|
||
const div = document.createElement('div');
|
||
div.className = 'orbit-card';
|
||
div.innerHTML = `
|
||
<div class="tl">${d.label}</div>
|
||
<div class="tv ${d.clr}">${d.value}</div>
|
||
<span class="td ${d.dir}">${d.dir === 'up' ? '↑' : '↓'} ${d.trend}</span>
|
||
`;
|
||
stage.appendChild(div);
|
||
|
||
// Distribute cards evenly around the orbit
|
||
const angle = (i / CARD_DATA.length) * Math.PI * 2;
|
||
// Elliptical orbit: wider than tall
|
||
const orbitRx = 320 + Math.random() * 60; // horizontal radius
|
||
const orbitRy = 100 + Math.random() * 40; // vertical radius
|
||
const yOffset = (Math.random() - 0.5) * 60;
|
||
|
||
orbitCards.push({
|
||
el: div, angle, orbitRx, orbitRy, yOffset,
|
||
data: d,
|
||
speedMul: 0.7 + Math.random() * 0.6,
|
||
});
|
||
});
|
||
}
|
||
|
||
// ── RADIAL LINES ──
|
||
let radialLines = [];
|
||
function spawnRadialLine() {
|
||
const div = document.createElement('div');
|
||
const isFast = Math.random() < 0.2;
|
||
div.className = isFast ? 'radial-line fast' : 'radial-line';
|
||
const angle = Math.random() * 360;
|
||
const length = 30 + Math.random() * 120;
|
||
div.style.width = length + 'px';
|
||
div.style.transform = `rotate(${angle}deg) translateY(-50%)`;
|
||
div.style.opacity = '0';
|
||
fxLayer.appendChild(div);
|
||
return { el: div, angle, length, speedM: 0.3 + Math.random() * 1.0, life: 0 };
|
||
}
|
||
|
||
for (let i = 0; i < 60; i++) {
|
||
radialLines.push(spawnRadialLine());
|
||
}
|
||
|
||
// ── LOOP ──
|
||
function loop() {
|
||
const lerp = 0.04;
|
||
currentSpeed += (CRUISE + targetSpeed * BOOST - currentSpeed) * lerp;
|
||
const spd = currentSpeed;
|
||
|
||
const cx = window.innerWidth / 2;
|
||
const cy = window.innerHeight / 2;
|
||
|
||
// ── RADIAL LINES ──
|
||
radialLines.forEach(line => {
|
||
line.life += spd * line.speedM * 1.5;
|
||
const dist = line.life;
|
||
const alpha = dist < 10 ? dist / 10 : Math.max(0, 1 - (dist - 10) / 70);
|
||
line.el.style.opacity = alpha;
|
||
line.el.style.transform = `rotate(${line.angle}deg) translateX(${dist}px)`;
|
||
if (dist > 80) {
|
||
line.life = 0;
|
||
line.angle = Math.random() * 360;
|
||
line.el.style.transform = `rotate(${line.angle}deg) translateX(0px)`;
|
||
}
|
||
});
|
||
|
||
// ── ORBIT CARDS — circular motion around center ──
|
||
orbitCards.forEach(card => {
|
||
card.angle += spd * card.speedMul * 0.015;
|
||
|
||
const a = card.angle;
|
||
const rx = card.orbitRx;
|
||
const ry = card.orbitRy;
|
||
|
||
// Position on ellipse
|
||
const x = cx + rx * Math.cos(a);
|
||
const y = cy + ry * Math.sin(a) + card.yOffset;
|
||
|
||
// Z-depth: cards in front (cos(a) > 0) are larger/brighter
|
||
// Cards behind (cos(a) < 0) are faded
|
||
const frontness = Math.cos(a); // 1 = right, -1 = left, but we want the "arc" visibility
|
||
const depthFactor = Math.abs(Math.sin(a)); // 0 at left/right edges, 1 at top/bottom
|
||
|
||
// Visibility: most visible when near the horizontal axis (left or right of center)
|
||
const horizontalFactor = Math.abs(Math.cos(a)); // 1 at right/left, 0 at front/back
|
||
const opacity = Math.max(0.10, horizontalFactor * 0.9 + 0.1);
|
||
const scale = 0.6 + horizontalFactor * 0.4;
|
||
|
||
card.el.style.left = x + 'px';
|
||
card.el.style.top = y + 'px';
|
||
card.el.style.transform = `translate(-50%, -50%) scale(${scale})`;
|
||
card.el.style.opacity = opacity;
|
||
});
|
||
|
||
// ── GLOW ──
|
||
const glowScale = 1 + spd * 0.2;
|
||
const glowOpacity = 0.5 + spd * 0.4;
|
||
goo.style.transform = `translate(-50%, -50%) scale(${glowScale})`;
|
||
goo.style.opacity = glowOpacity;
|
||
|
||
requestAnimationFrame(loop);
|
||
}
|
||
|
||
// ── SPEED CONTROL ──
|
||
function setSpeed(active) {
|
||
targetSpeed = active ? 1 : 0;
|
||
}
|
||
|
||
// ── INIT ──
|
||
createOrbitCards();
|
||
requestAnimationFrame(loop);
|
||
|
||
window.addEventListener('resize', () => {
|
||
// Cards will naturally adjust to new center on next frame
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|