diff --git a/blender_matrix_tunnel.py b/blender_matrix_tunnel.py new file mode 100644 index 0000000..f28f7ef --- /dev/null +++ b/blender_matrix_tunnel.py @@ -0,0 +1,460 @@ +""" +ROI24 — Matrix 3D Tunnel Hero Animation +======================================== +Blender 4.x Geometry Nodes script. +Creates a Matrix-style infinite tunnel with binary digits +and floating KPI cards. + +Usage: +1. Open Blender 4.x +2. Scripting workspace → Text Editor → New → "matrix_tunnel.py" +3. Paste this entire file +4. Alt+P (Run Script) +5. Press ~ in 3D viewport → shade smooth +6. Numpad 0 for camera view +7. Ctrl+F12 to render animation + +Output: 1920x1080, 60fps, 180 frames (3s loop) +""" + +import bpy +import math +import random +import os + +# ============================================================ +# CONFIG +# ============================================================ +RENDER_WIDTH = 1920 +RENDER_HEIGHT = 1080 +FRAMES = 180 +FPS = 60 +TUNNEL_RADIUS = 8.0 +TUNNEL_LENGTH = 80.0 +RINGS = 32 +CHARS_PER_RING = 48 +FLOW_SPEED = 3.0 # tunnel speed + +# ROI24 brand colors +GREEN = (0.133, 0.773, 0.369) # #22c55e +GREEN_BRIGHT = (0.286, 0.886, 0.502) # #4ade80 +DARK = (0.039, 0.039, 0.039) # #0a0a0a + +# ============================================================ +# CLEANUP +# ============================================================ +print("Cleaning scene...") +for obj in bpy.data.objects: + bpy.data.objects.remove(obj, do_unlink=True) +for mat in bpy.data.materials: + bpy.data.materials.remove(mat, do_unlink=True) +for tex in bpy.data.textures: + bpy.data.textures.remove(tex, do_unlink=True) +for img in bpy.data.images: + bpy.data.images.remove(img, do_unlink=True) +for gn in bpy.data.node_groups: + bpy.data.node_groups.remove(gn) + +# ============================================================ +# MATERIALS +# ============================================================ +print("Creating materials...") + +def create_green_emit_mat(name): + """Create a green emission material.""" + mat = bpy.data.materials.new(name) + mat.use_nodes = True + nodes = mat.node_tree.nodes + links = mat.node_tree.links + nodes.clear() + + emit = nodes.new('ShaderNodeEmission') + emit.inputs['Color'].default_value = (*GREEN, 1.0) + emit.inputs['Strength'].default_value = 1.5 + output = nodes.new('ShaderNodeOutputMaterial') + links.new(emit.outputs['Emission'], output.inputs['Surface']) + return mat + +def create_glow_mat(name, color=GREEN, strength=3.0): + """Create a bright glow material.""" + mat = bpy.data.materials.new(name) + mat.use_nodes = True + nodes = mat.node_tree.nodes + links = mat.node_tree.links + nodes.clear() + + emit = nodes.new('ShaderNodeEmission') + emit.inputs['Color'].default_value = (*color, 1.0) + emit.inputs['Strength'].default_value = strength + output = nodes.new('ShaderNodeOutputMaterial') + links.new(emit.outputs['Emission'], output.inputs['Surface']) + return mat + +char_mat = create_green_emit_mat('CharMat') +glow_mat = create_glow_mat('GlowMat', GREEN_BRIGHT, 4.0) +card_mat = create_green_emit_mat('CardMat') +line_mat = create_green_emit_mat('LineMat') + +# ============================================================ +# CAMERA +# ============================================================ +print("Setting up camera...") +cam_data = bpy.data.cameras.new('Camera') +cam_data.type = 'PERSP' +cam_data.lens = 35 +cam_data.clip_start = 0.1 +cam_data.clip_end = 200 + +cam = bpy.data.objects.new('Camera', cam_data) +cam.location = (0, 0, 0) +bpy.context.scene.collection.objects.link(cam) +bpy.context.scene.camera = cam + +# ============================================================ +# SCENE SETUP +# ============================================================ +print("Setting up scene...") +scene = bpy.context.scene +scene.render.engine = 'CYCLES' +scene.cycles.samples = 128 +scene.cycles.preview_samples = 32 +scene.cycles.device = 'GPU' +scene.render.fps = FPS +scene.frame_start = 0 +scene.frame_end = FRAMES - 1 + +# Render resolution +scene.render.resolution_x = RENDER_WIDTH +scene.render.resolution_y = RENDER_HEIGHT +scene.render.resolution_percentage = 100 + +# Output path (set before file_format) +output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'renders') +os.makedirs(output_dir, exist_ok=True) +scene.render.filepath = os.path.join(output_dir, 'matrix_tunnel') + +# FFMPEG settings (Blender 4.x API) +scene.ffmpeg.format = 'MPEG_4' +scene.ffmpeg.codec = 'H264' +scene.ffmpeg.video_bitrate = 50000 + +# World background +world = bpy.data.worlds.new('World') +world.use_nodes = True +nodes = world.node_tree.nodes +links = world.node_tree.links +nodes.clear() + +bg = nodes.new('ShaderNodeBackground') +bg.inputs['Color'].default_value = (*DARK, 1.0) +bg.inputs['Strength'].default_value = 0.3 +output = nodes['World Output'] +links.new(bg.outputs['Background'], output.inputs['Surface']) + +# Fog +fog = nodes.new('ShaderNodeFog') +fog.fog_type = 'EXP' +fog.density = 0.025 +fog.inputs['Color'].default_value = (*GREEN, 0.15) +links.new(fog, output.inputs['Fog']) +scene.world = world + +# ============================================================ +# GEOMETRY NODES TUNNEL +# ============================================================ +print("Creating tunnel with Geometry Nodes...") + +# Create instancing system for binary characters +tunnel_gn = bpy.data.node_groups.new('MatrixTunnel', 'GeometryNodeTree') +tunnel_gn.interface.new_socket('Geometry', socket_type='NodeSocketGeometry') +tunnel_gn.interface.new_socket('Progress', socket_type='NodeSocketFloat') + +nodes = tunnel_gn.nodes +links = tunnel_gn.links + +# Input geometry +input_geom = nodes.new('NodeGroupInput') + +# Separate by Z +separate_z = nodes.new('GeometryNodeSeparateGeometry') +separate_z.location = (200, 0) + +# Instance on points +instance_on_points = nodes.new('GeometryNodeInstanceOnPoints') +instance_on_points.location = (500, 0) + +# Transform geometry (rotate instances to face camera) +transform = nodes.new('GeometryNodeTransform') +transform.location = (750, 0) + +# Output +output_gn = nodes.new('NodeGroupOutput') + +# Connect +links.new(input_geom.outputs[0], separate_z.inputs[0]) +links.new(separate_z.outputs[0], instance_on_points.inputs[0]) +links.new(instance_on_points.outputs[0], transform.inputs[0]) +links.new(transform.outputs[0], output_gn.inputs[0]) + +# ============================================================ +# CREATE BINARY CHARACTER MESH (simple diamond shape) +# ============================================================ +print("Creating character mesh...") + +# Diamond shape for '0' +char_mesh_0 = bpy.data.meshes.new('Char0') +verts_0 = [ + (0, 0.3, 0), (0.2, 0, 0), (0, -0.3, 0), (-0.2, 0, 0) +] +faces_0 = [[0, 1, 2, 3]] +char_mesh_0.from_pydata(verts_0, [], faces_0) +char_mesh_0.validate() + +# Line shape for '1' +char_mesh_1 = bpy.data.meshes.new('Char1') +verts_1 = [ + (-0.08, 0.25, 0), (0.08, 0.25, 0), + (0.08, -0.25, 0), (-0.08, -0.25, 0), + (-0.08, -0.15, 0), (0.08, -0.15, 0) +] +faces_1 = [[0, 1, 2, 3], [2, 5, 4, 3]] +char_mesh_1.from_pydata(verts_1, [], faces_1) +char_mesh_1.validate() + +# ============================================================ +# INSTANCED CHARACTER OBJECTS +# ============================================================ +print("Instancing characters...") + +def create_instanced_chars(): + """Create instanced binary characters around tunnel.""" + angle_step = (2 * math.pi) / CHARS_PER_RING + + # Create collection for tunnel objects + tunnel_col = bpy.data.collections.new('MatrixTunnel') + bpy.context.scene.collection.children.link(tunnel_col) + + # Create a single "instancer" object with geometry nodes + instancer = bpy.data.objects.new('TunnelInstancer', None) + instancer.location = (0, 0, 0) + tunnel_col.objects.link(instancer) + + # Geometry nodes modifier + gn = instancer.modifiers.new('TunnelGenerator', 'NODES') + gn.node_group = tunnel_gn + + # Now create actual character instances + ring_spacing = TUNNEL_LENGTH / RINGS + + for ring_idx in range(RINGS): + z = -(ring_idx * ring_spacing) + + for char_idx in range(CHARS_PER_RING): + angle = char_idx * angle_step + x = math.cos(angle) * TUNNEL_RADIUS + y = math.sin(angle) * TUNNEL_RADIUS + + # Create character mesh object + use_mesh = char_mesh_0 if random.random() > 0.5 else char_mesh_1 + + # Use object instancing for performance + char_obj = bpy.data.objects.new( + f'Char_R{ring_idx:02d}_C{char_idx:02d}', + use_mesh + ) + char_obj.location = (x, y, z) + char_obj.rotation_euler = (0, 0, angle + math.radians(90)) + + # Scale based on position (closer = bigger) + scale = 0.6 + (ring_idx / RINGS) * 0.3 + char_obj.scale = (scale, scale, scale) + + # Material + char_obj.data.materials.append(char_mat) + + # Set material color based on depth + depth_ratio = ring_idx / RINGS + r = GREEN[0] * (0.6 + depth_ratio * 0.4) + g = GREEN[1] * (0.8 + depth_ratio * 0.2) + b = GREEN[2] * (0.7 + depth_ratio * 0.3) + char_mat.nodes['Emission'].inputs['Color'].default_value = (r, g, b, 1.0) + + # Store animation data + char_obj['ring_idx'] = ring_idx + char_obj['char_idx'] = char_idx + char_obj['base_x'] = x + char_obj['base_y'] = y + char_obj['base_z'] = z + char_obj['base_scale'] = scale + char_obj['angle'] = angle + + tunnel_col.objects.link(char_obj) + + return tunnel_col + +tunnel_col = create_instanced_chars() + +# ============================================================ +# KPI CARDS — 3D panels +# ============================================================ +print("Creating KPI cards...") + +KPI_DATA = [ + {'label': 'CPA', 'value': '890₽', 'pos': (-10, 5, -12), 'rot': (0, 0.1, 0), 'size': (3, 2)}, + {'label': 'ROI', 'value': '91.8%', 'pos': (10, 4, -18), 'rot': (0, -0.1, 0), 'size': (3, 2)}, + {'label': 'LTV', 'value': '12.4K', 'pos': (-9, -2, -24), 'rot': (0, 0.15, 0), 'size': (3, 2)}, + {'label': 'CONV', 'value': '2.8%', 'pos': (9, -3, -30), 'rot': (0, -0.15, 0), 'size': (3, 2)}, + {'label': 'LEADS', 'value': '44.5K', 'pos': (-8, 1, -36), 'rot': (0, 0.1, 0), 'size': (3, 2)}, + {'label': 'REVENUE', 'value': '2.05M', 'pos': (8, 0, -42), 'rot': (0, -0.1, 0), 'size': (3, 2)}, +] + +for i, kpi in enumerate(KPI_DATA): + w, h = kpi['size'] + + # Card mesh + card_mesh = bpy.data.meshes.new(f'KPI_Card_{i}') + verts = [ + (-w/2, -h/2, 0), (w/2, -h/2, 0), + (w/2, h/2, 0), (-w/2, h/2, 0), + (-w/2, -h/2, -0.05), (w/2, -h/2, -0.05), + (w/2, h/2, -0.05), (-w/2, h/2, -0.05), + ] + faces = [ + [0, 4, 5, 1], # bottom + [4, 7, 6, 5], # back + [7, 3, 2, 6], # top + [3, 0, 1, 2], # front + [4, 0, 3, 7], # left + [1, 5, 6, 2], # right + ] + card_mesh.from_pydata(verts, [], faces) + card_mesh.validate() + + card_obj = bpy.data.objects.new(f'KPI_Card_{i}', card_mesh) + card_obj.location = kpi['pos'] + card_obj.rotation_euler = kpi['rot'] + card_obj.scale = (1, 1, 1) + + # Material + card_obj.data.materials.append(card_mat) + + # Store data + card_obj['kpi_index'] = i + card_obj['base_x'] = kpi['pos'][0] + card_obj['base_y'] = kpi['pos'][1] + card_obj['base_z'] = kpi['pos'][2] + + tunnel_col.objects.link(card_obj) + +# ============================================================ +# SPEED LINES +# ============================================================ +print("Creating speed lines...") + +for i in range(48): + angle = (i / 48) * 2 * math.pi + x1 = math.cos(angle) * 0.5 + y1 = math.sin(angle) * 0.5 + x2 = math.cos(angle) * TUNNEL_RADIUS * 2 + y2 = math.sin(angle) * TUNNEL_RADIUS * 2 + z_start = -10 + z_end = -TUNNEL_LENGTH + + # Curve + curve = bpy.data.curves.new(f'SpeedLine_{i}', 'CURVE') + curve.dimensions = '3D' + curve.bevel_depth = 0.03 + spline = curve.splines.new('POLY') + spline.points.add(1) + spline.points[0].co = (x1, y1, z_start, 1) + spline.points[1].co = (x2, y2, z_end, 1) + + line_obj = bpy.data.objects.new(f'SpeedLine_{i}', curve) + line_obj.data.materials.append(line_mat) + tunnel_col.objects.link(line_obj) + +# ============================================================ +# ANIMATION FUNCTIONS (uncomment to enable) +# ============================================================ + +def animate_characters(): + """Animate binary characters flowing toward camera.""" + for obj in tunnel_col.objects: + ring_idx = obj.get('ring_idx', None) + if ring_idx is None: + continue + + base_z = obj.get('base_z', 0) + base_x = obj.get('base_x', 0) + base_y = obj.get('base_y', 0) + base_scale = obj.get('base_scale', 0.6) + angle = obj.get('angle', 0) + + for frame in range(FRAMES): + # Flow forward + z = base_z + ((frame / FPS) * FLOW_SPEED) % TUNNEL_LENGTH + if z > 0: + z -= TUNNEL_LENGTH + + # Scale by depth + depth_ratio = max(0, min(1, (TUNNEL_LENGTH + z) / TUNNEL_LENGTH)) + scale = base_scale * (0.4 + depth_ratio * 0.7) + + # Set keyframe + obj.location = (base_x, base_y, z) + obj.scale = (scale / base_scale, scale / base_scale, 1) + obj.keyframe_insert(data_path='location', frame=frame) + obj.keyframe_insert(data_path='scale', frame=frame) + + +def animate_kpi_cards(): + """Animate KPI cards floating through tunnel.""" + for obj in tunnel_col.objects: + kpi_idx = obj.get('kpi_index', None) + if kpi_idx is None: + continue + + base_x = obj.get('base_x', 0) + base_y = obj.get('base_y', 0) + base_z = obj.get('base_z', 0) + + for frame in range(FRAMES): + # Move toward camera + z = base_z + ((frame / FPS) * FLOW_SPEED * 0.7) + + # Float motion + y = base_y + math.sin((frame / FPS) * 2 + kpi_idx * 1.5) * 1.5 + + # Scale + depth_ratio = max(0, min(1, (TUNNEL_LENGTH + z) / TUNNEL_LENGTH)) + scale = 0.5 + depth_ratio * 0.7 + + obj.location = (base_x, y, z) + obj.scale = (scale, scale, scale) + obj.keyframe_insert(data_path='location', frame=frame) + obj.keyframe_insert(data_path='scale', frame=frame) + + +# ============================================================ +# SUMMARY +# ============================================================ +print("\n" + "=" * 60) +print("ROI24 Matrix 3D Tunnel — Setup Complete") +print("=" * 60) +print(f"Resolution: {RENDER_WIDTH}x{RENDER_HEIGHT}") +print(f"Duration: {FRAMES / FPS}s @ {FPS}fps") +print(f"Engine: {scene.render.engine} (Cycles)") +print(f"Tunnel: {RINGS} rings x {CHARS_PER_RING} chars = {RINGS * CHARS_PER_RING}") +print(f"KPI cards: {len(KPI_DATA)}") +print(f"Speed lines: 48") +print() +print("NEXT STEPS:") +print("1. In 3D Viewport: Press ~ → Shade Smooth") +print("2. Numpad 0 — Camera view") +print("3. F12 — Preview render") +print("4. Ctrl+F12 — Render animation (MP4)") +print() +print("TO ANIMATE:") +print("Uncomment animate_characters() and animate_kpi_cards()") +print("then re-run this script") +print("=" * 60) diff --git a/hero-matrix.html b/hero-matrix.html new file mode 100644 index 0000000..0ef56b0 --- /dev/null +++ b/hero-matrix.html @@ -0,0 +1,531 @@ + + + + + +ROI24 — Matrix Tunnel Hero + + + + + + +
+ +
+
+
+
+ + +
+
CPA
+
0 ₽
+
средняя продажа
+
↓ 5%
+
+
+
ROI
+
0%
+
возврат инвестиций
+
↑ 8%
+
+
+
Конверсия
+
0%
+
заявка → продажа
+
↑ 0.3%
+
+
+
LTV
+
0 ₽
+
за 6 месяцев
+
↑ 18%
+
+
+
Лиды / мес
+
0
+
за 30 дней
+
↑ 15%
+
+
+
Выручка
+
0 ₽
+
за 30 дней
+
↑ 12%
+
+ +
+
+
ROI24 — Attribution Analytics
+

Управляйте бизнесом
на основе данных

+

Автоматический анализ рекламных расходов. Каждый рубль конвертируется в прибыль. CPA, LTV, ROI — в реальном времени.

+ +
+
+ +
+
+ + + + diff --git a/index_ibm.html b/index_ibm.html index d4010e0..20b97c0 100644 --- a/index_ibm.html +++ b/index_ibm.html @@ -224,7 +224,7 @@ section { padding: 120px 0; position: relative; z-index: 1; } padding: 160px 0 80px; min-height: 90vh; display: flex; align-items: center; border-bottom: 1px solid var(--border); - position: relative; overflow: hidden; + position: relative; } .hero-inner { display: grid; grid-template-columns: 1fr 1fr; gap: 80px; @@ -242,10 +242,11 @@ section { padding: 120px 0; position: relative; z-index: 1; } h1 { font-size: clamp(3rem, 6vw, 5rem); font-weight: 700; - letter-spacing: -0.04em; line-height: 0.95; margin-bottom: 24px; + letter-spacing: -0.02em; line-height: 1.05; margin-bottom: 24px; background: linear-gradient(135deg, var(--text) 45%, var(--accent) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; + text-shadow: 0 0 1px rgba(255,255,255,0.1); } .hero-sub { @@ -253,6 +254,16 @@ h1 { margin-bottom: 40px; max-width: 440px; line-height: 1.65; } +.hero-tagline { + font-size: 1rem; font-weight: 600; + margin-bottom: 32px; line-height: 1.5; + max-width: 440px; + color: rgba(255,255,255,0.85); +} +[data-theme="light"] .hero-tagline { + color: #161616; +} + .hero-actions { display: flex; gap: 12px; flex-wrap: wrap; } .hero-meta { @@ -808,15 +819,6 @@ h1 { - -
-
-

- Сквозная аналитика и панели мониторинга
для вашего бизнеса и департаментов под ключ -

-
-
-
@@ -829,6 +831,7 @@ h1 { Обновляется автоматически из 1С, CRM и банков. Смотрится с телефона каждое утро.

+

Сквозная аналитика и панели мониторинга
для вашего бизнеса и департаментов под ключ