diff --git a/landing/README.md b/landing/README.md
index 0dcfcd38..fb679a08 100644
--- a/landing/README.md
+++ b/landing/README.md
@@ -18,3 +18,5 @@ pnpm preview
- Static-first (SSG) by design.
- Locale auto-detection: cookie -> browser settings -> fallback `en`.
- Theme auto-detection: localStorage -> system preference -> fallback `light`.
+- Hero video uses the Mux Player embed. Set `NUXT_PUBLIC_MUX_PLAYBACK_ID` to override the default playback id without changing the code.
+- Hero background can use a separate Mux asset via `NUXT_PUBLIC_MUX_BACKGROUND_PLAYBACK_ID`; otherwise it reuses `NUXT_PUBLIC_MUX_PLAYBACK_ID`.
diff --git a/landing/assets/styles/cyberpunk-hero.scss b/landing/assets/styles/cyberpunk-hero.scss
index 5929e57f..e92ae270 100644
--- a/landing/assets/styles/cyberpunk-hero.scss
+++ b/landing/assets/styles/cyberpunk-hero.scss
@@ -34,8 +34,13 @@
--cyber-monterey-after-bg:
linear-gradient(180deg, rgba(2, 5, 13, 0.92) 0%, rgba(2, 5, 13, 0.62) 15%, rgba(2, 5, 13, 0.08) 44%, rgba(2, 5, 13, 0.68) 100%),
radial-gradient(circle at 64% 42%, transparent 0 26%, rgba(2, 5, 13, 0.24) 70%, rgba(2, 5, 13, 0.54) 100%);
- --cyber-monterey-canvas-opacity: 0.42;
- --cyber-monterey-canvas-filter: blur(4px) saturate(0.78) brightness(0.62) contrast(1.08);
+ --cyber-monterey-video-opacity: 1;
+ --cyber-monterey-video-filter: blur(0.8px) saturate(1.12) contrast(1.04);
+ --cyber-monterey-video-blend: normal;
+ --cyber-monterey-video-position: 74% 50%;
+ --cyber-monterey-video-scale: 1.16;
+ --cyber-monterey-canvas-opacity: 0.36;
+ --cyber-monterey-canvas-filter: blur(0.5px) saturate(0.92) brightness(0.78) contrast(1.08);
--cyber-monterey-canvas-blend: normal;
--cyber-background-bg:
radial-gradient(circle at 72% 28%, rgba(0, 234, 255, 0.1), transparent 30%),
@@ -116,113 +121,6 @@
--cyber-frame-cut: 18px;
}
-.v-theme--light .cyber-hero {
- --cyber-bg-0: #f8fcff;
- --cyber-bg-1: #eaf7fb;
- --cyber-panel-weak: rgba(255, 255, 255, 0.68);
- --cyber-panel: rgba(255, 255, 255, 0.78);
- --cyber-panel-strong: rgba(255, 255, 255, 0.92);
- --cyber-cyan: #008fb3;
- --cyber-blue: #2563eb;
- --cyber-magenta: #b832d8;
- --cyber-violet: #7c3aed;
- --cyber-text: #132238;
- --cyber-muted: #596b83;
- --cyber-border-cyan: rgba(8, 145, 178, 0.34);
- --cyber-border-magenta: rgba(184, 50, 216, 0.28);
- --cyber-panel-bg:
- linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(237, 249, 252, 0.68));
- --cyber-panel-shadow:
- 0 0 0 1px rgba(8, 145, 178, 0.1) inset,
- 0 18px 48px rgba(8, 145, 178, 0.12),
- 0 0 22px rgba(184, 50, 216, 0.07);
- --cyber-hero-bg:
- radial-gradient(circle at 76% 30%, rgba(0, 178, 214, 0.14), transparent 32%),
- radial-gradient(circle at 86% 70%, rgba(184, 50, 216, 0.13), transparent 36%),
- linear-gradient(180deg, #fbfdff 0%, #edf8fb 56%, #f8fcff 100%);
- --cyber-monterey-bg:
- radial-gradient(circle at 78% 24%, rgba(113, 185, 255, 0.28), transparent 31%),
- radial-gradient(circle at 22% 72%, rgba(221, 170, 255, 0.24), transparent 38%),
- radial-gradient(circle at 8% 32%, rgba(101, 218, 255, 0.18), transparent 34%),
- linear-gradient(180deg, #f8fcff 0%, #eaf7fb 48%, #fbf7ff 100%);
- --cyber-monterey-before-bg:
- radial-gradient(circle at 18% 34%, rgba(255, 255, 255, 0.46), rgba(255, 255, 255, 0.14) 34%, transparent 62%),
- linear-gradient(90deg, rgba(255, 255, 255, 0.22) 0%, rgba(255, 255, 255, 0.08) 42%, rgba(255, 255, 255, 0.03) 64%, rgba(237, 247, 252, 0.2) 100%);
- --cyber-monterey-after-bg:
- linear-gradient(180deg, rgba(248, 252, 255, 0.52) 0%, rgba(248, 252, 255, 0.22) 18%, rgba(248, 252, 255, 0.04) 48%, rgba(248, 252, 255, 0.58) 100%),
- radial-gradient(circle at 64% 42%, transparent 0 26%, rgba(255, 255, 255, 0.16) 70%, rgba(235, 247, 252, 0.42) 100%);
- --cyber-monterey-canvas-opacity: 1;
- --cyber-monterey-canvas-filter: blur(1px) saturate(1.34) brightness(1.12) contrast(1.16);
- --cyber-monterey-canvas-blend: multiply;
- --cyber-background-bg:
- radial-gradient(circle at 72% 28%, rgba(0, 178, 214, 0.12), transparent 31%),
- radial-gradient(circle at 88% 62%, rgba(184, 50, 216, 0.1), transparent 33%),
- linear-gradient(90deg, transparent 0 64px, rgba(8, 145, 178, 0.06) 65px 66px, transparent 67px 160px),
- linear-gradient(180deg, rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0.08) 38%, rgba(237, 247, 252, 0.64) 100%);
- --cyber-background-opacity: 0.5;
- --cyber-background-before-bg:
- linear-gradient(90deg, transparent 0 8%, rgba(8, 145, 178, 0.16) 8.1% 8.22%, transparent 8.34% 18%, rgba(184, 50, 216, 0.12) 18.1% 18.22%, transparent 18.34% 31%, rgba(8, 145, 178, 0.13) 31.1% 31.24%, transparent 31.36% 44%, rgba(37, 99, 235, 0.12) 44.1% 44.2%, transparent 44.34% 62%, rgba(184, 50, 216, 0.1) 62.1% 62.22%, transparent 62.34% 78%, rgba(8, 145, 178, 0.12) 78.1% 78.22%, transparent 78.34%),
- repeating-linear-gradient(90deg, transparent 0 78px, rgba(8, 145, 178, 0.06) 80px 82px, transparent 84px 116px),
- linear-gradient(to top, rgba(8, 145, 178, 0.12) 0%, rgba(8, 145, 178, 0.07) 13%, transparent 31%),
- linear-gradient(to top, rgba(184, 50, 216, 0.1) 0%, rgba(184, 50, 216, 0.05) 16%, transparent 38%),
- linear-gradient(to top, rgba(37, 99, 235, 0.09) 0%, rgba(37, 99, 235, 0.04) 20%, transparent 48%);
- --cyber-background-before-opacity: 0.58;
- --cyber-background-before-blend: multiply;
- --cyber-background-after-bg:
- repeating-linear-gradient(90deg, transparent 0 34px, rgba(8, 145, 178, 0.1) 35px 36px, transparent 37px 110px),
- repeating-linear-gradient(180deg, transparent 0 28px, rgba(184, 50, 216, 0.08) 29px 30px, transparent 31px 78px),
- linear-gradient(90deg, transparent, rgba(8, 145, 178, 0.07), transparent);
- --cyber-background-after-opacity: 0.18;
- --cyber-background-after-blend: multiply;
- --cyber-wash-bg:
- radial-gradient(circle at 18% 44%, rgba(255, 255, 255, 0.28), rgba(255, 255, 255, 0.12) 36%, transparent 60%),
- linear-gradient(90deg, rgba(255, 255, 255, 0.18) 0%, rgba(237, 248, 252, 0.1) 36%, rgba(255, 255, 255, 0.03) 68%, rgba(251, 247, 255, 0.16) 100%),
- linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(248, 252, 255, 0.04) 58%, rgba(248, 252, 255, 0.72));
- --cyber-gridlines-bg:
- linear-gradient(rgba(8, 145, 178, 0.055) 1px, transparent 1px),
- linear-gradient(90deg, rgba(8, 145, 178, 0.045) 1px, transparent 1px);
- --cyber-gridlines-opacity: 0.2;
- --cyber-scanlines-bg: repeating-linear-gradient(
- to bottom,
- rgba(8, 35, 50, 0.035) 0,
- rgba(8, 35, 50, 0.035) 1px,
- transparent 1px,
- transparent 4px
- );
- --cyber-scanlines-opacity: 0.08;
- --cyber-copy-aura: radial-gradient(circle at 28% 38%, rgba(255, 255, 255, 0.92), rgba(255, 255, 255, 0.38) 58%, transparent 78%);
- --cyber-title-color: rgba(19, 34, 56, 0.98);
- --cyber-description-color: rgba(50, 65, 88, 0.82);
- --cyber-action-primary-color: #061722;
- --cyber-action-secondary-bg: rgba(255, 255, 255, 0.62);
- --cyber-action-secondary-hover-bg: rgba(8, 145, 178, 0.08);
- --cyber-release-color: rgba(50, 65, 88, 0.68);
- --cyber-scene-floor-bg:
- radial-gradient(ellipse at 58% 84%, rgba(184, 50, 216, 0.16), transparent 18%),
- radial-gradient(ellipse at 56% 84%, rgba(8, 145, 178, 0.14), transparent 32%),
- repeating-radial-gradient(ellipse at 58% 84%, rgba(8, 145, 178, 0.08) 0 1px, transparent 1px 20px);
- --cyber-scene-floor-opacity: 0.38;
- --cyber-scene-foreground-bg:
- linear-gradient(90deg, transparent 0 4%, rgba(8, 145, 178, 0.07) 4.1%, transparent 4.4%),
- linear-gradient(180deg, transparent 0 88%, rgba(184, 50, 216, 0.06));
- --cyber-scene-foreground-opacity: 0.48;
- --cyber-video-frame-bg: rgba(255, 255, 255, 0.7);
- --cyber-video-content-bg: rgba(8, 20, 34, 0.9);
- --cyber-card-text: rgba(19, 34, 56, 0.88);
- --cyber-card-muted: rgba(67, 82, 105, 0.76);
- --cyber-card-subtle: rgba(67, 82, 105, 0.64);
- --cyber-card-inset: rgba(255, 255, 255, 0.62);
- --cyber-feature-shell-bg: transparent;
- --cyber-feature-rail-bg: transparent;
- --cyber-feature-rail-shadow:
- 0 0 0 1px rgba(8, 145, 178, 0.12) inset,
- 0 -1px 0 rgba(8, 145, 178, 0.18),
- 0 1px 0 rgba(8, 145, 178, 0.16);
- --cyber-feature-divider: rgba(8, 145, 178, 0.18);
- --cyber-feature-title: rgba(19, 34, 56, 0.94);
- --cyber-feature-text: rgba(67, 82, 105, 0.72);
-}
-
.cyber-panel {
position: relative;
border: 1px solid var(--cyber-border-cyan);
@@ -294,6 +192,65 @@
background: var(--cyber-monterey-after-bg);
}
+.cyber-hero__background,
+.cyber-hero__wash,
+.cyber-hero__monterey::before,
+.cyber-hero__monterey::after {
+ display: none;
+}
+
+.cyber-hero__monterey-video {
+ position: absolute;
+ inset: 0;
+ z-index: 0;
+ width: 100%;
+ height: 100%;
+ min-width: 100%;
+ min-height: 100%;
+ overflow: hidden;
+ opacity: var(--cyber-monterey-video-opacity);
+ filter: var(--cyber-monterey-video-filter);
+ mix-blend-mode: var(--cyber-monterey-video-blend);
+ background:
+ var(--cyber-monterey-video-poster) var(--cyber-monterey-video-position) / cover no-repeat,
+ var(--cyber-monterey-bg);
+ transform: scale(var(--cyber-monterey-video-scale));
+ will-change: opacity, transform;
+}
+
+.cyber-hero__monterey-video::after {
+ display: none;
+}
+
+.cyber-hero__monterey-video-player {
+ position: absolute;
+ inset: 0;
+ z-index: 1;
+ display: block;
+ width: 100%;
+ height: 100%;
+ min-width: 100%;
+ min-height: 100%;
+ opacity: 0;
+ background: transparent;
+ object-fit: cover;
+ object-position: var(--cyber-monterey-video-position);
+ --media-object-fit: cover;
+ --media-object-position: var(--cyber-monterey-video-position);
+ transition: opacity 0.45s ease;
+}
+
+.cyber-hero__monterey-video-player::part(video) {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ object-position: var(--cyber-monterey-video-position);
+}
+
+.cyber-hero__monterey-video-player--ready {
+ opacity: 1;
+}
+
.cyber-hero__monterey-canvas {
position: absolute;
inset: 0;
@@ -490,12 +447,72 @@
.cyber-hero__description {
max-width: 560px;
- margin: 0 0 30px;
+ margin: 0 0 20px;
color: var(--cyber-description-color);
font-size: clamp(1rem, 1.08vw, 1.22rem);
line-height: 1.7;
}
+.cyber-hero__providers {
+ width: min(680px, 100%);
+ margin: 0 0 20px;
+}
+
+.cyber-hero__provider-list {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 14px 26px;
+}
+
+.cyber-hero__provider {
+ --provider-accent: var(--cyber-cyan);
+
+ display: inline-flex;
+ align-items: center;
+ min-width: 0;
+ gap: 9px;
+ color: var(--cyber-text);
+ text-shadow:
+ 0 0 18px color-mix(in srgb, var(--provider-accent) 26%, transparent),
+ 0 0 1px rgba(255, 255, 255, 0.3);
+}
+
+.cyber-hero__provider--cyan {
+ --provider-accent: var(--cyber-cyan);
+}
+
+.cyber-hero__provider--amber {
+ --provider-accent: #ffcf5a;
+}
+
+.cyber-hero__provider--magenta {
+ --provider-accent: var(--cyber-magenta);
+}
+
+.cyber-hero__provider-icon {
+ display: grid;
+ flex: 0 0 auto;
+ width: 30px;
+ height: 30px;
+ place-items: center;
+ color: var(--provider-accent);
+ filter: drop-shadow(0 0 10px color-mix(in srgb, var(--provider-accent) 44%, transparent));
+}
+
+.cyber-hero__provider-icon svg {
+ width: 24px;
+ height: 24px;
+}
+
+.cyber-hero__provider-name {
+ overflow-wrap: anywhere;
+ color: var(--cyber-text);
+ font-size: 0.98rem;
+ font-weight: 900;
+ line-height: 1.1;
+}
+
.cyber-hero__actions {
display: flex;
flex-wrap: wrap;
@@ -1630,6 +1647,32 @@
line-height: 1.62;
}
+ .cyber-hero__providers {
+ margin-bottom: 22px;
+ }
+
+ .cyber-hero__provider-list {
+ gap: 12px 18px;
+ }
+
+ .cyber-hero__provider {
+ gap: 7px;
+ }
+
+ .cyber-hero__provider-icon {
+ width: 24px;
+ height: 24px;
+ }
+
+ .cyber-hero__provider-icon svg {
+ width: 20px;
+ height: 20px;
+ }
+
+ .cyber-hero__provider-name {
+ font-size: 0.82rem;
+ }
+
.cyber-hero__actions {
display: grid;
grid-template-columns: 1fr;
diff --git a/landing/components/common/ThemeToggle.vue b/landing/components/common/ThemeToggle.vue
index 1bc10b99..bdde56ce 100644
--- a/landing/components/common/ThemeToggle.vue
+++ b/landing/components/common/ThemeToggle.vue
@@ -33,7 +33,7 @@ const onToggle = () => {
:icon="mdiWeatherSunny"
variant="text"
size="small"
- aria-label="Toggle theme"
+ :aria-label="t('theme.light')"
/>
diff --git a/landing/components/hero/CyberHeroFeatureStrip.vue b/landing/components/hero/CyberHeroFeatureStrip.vue
index 33e75cd5..bf68c512 100644
--- a/landing/components/hero/CyberHeroFeatureStrip.vue
+++ b/landing/components/hero/CyberHeroFeatureStrip.vue
@@ -8,8 +8,8 @@ import {
} from "@mdi/js";
import {
heroCollaborationFeature,
- heroFeatureRail,
- heroReviewerFeatureCard,
+ getLocalizedHeroFeatureRail,
+ getLocalizedHeroReviewerFeatureCard,
type HeroMessage,
type HeroMessagePhase,
} from "~/data/heroScene";
@@ -20,6 +20,11 @@ const props = defineProps<{
reducedMotion?: boolean;
}>();
+const { locale } = useI18n();
+const localizedHeroFeatureRail = computed(() => getLocalizedHeroFeatureRail(locale.value));
+const localizedHeroReviewerFeatureCard = computed(() => getLocalizedHeroReviewerFeatureCard(locale.value));
+const statusLabel = computed(() => locale.value === "ru" ? "Статус:" : "Status:");
+
const icons = [
mdiRobotOutline,
mdiViewDashboardOutline,
@@ -76,18 +81,18 @@ const reviewerBubbleText = computed(() => {
-
{{ heroReviewerFeatureCard.label }}
+
{{ localizedHeroReviewerFeatureCard.label }}
- - {{ task }}
+ - {{ task }}
- Status:
- {{ heroReviewerFeatureCard.status }}
+ {{ statusLabel }}
+ {{ localizedHeroReviewerFeatureCard.status }}
{
diff --git a/landing/components/hero/CyberHeroMontereyBackground.vue b/landing/components/hero/CyberHeroMontereyBackground.vue
index 6e2165fa..1928d8ea 100644
--- a/landing/components/hero/CyberHeroMontereyBackground.vue
+++ b/landing/components/hero/CyberHeroMontereyBackground.vue
@@ -3,6 +3,22 @@ import type { NeatConfig, NeatController } from "@firecms/neat";
const canvasRef = ref
(null);
const isLive = ref(false);
+const shouldMountBackgroundVideo = ref(false);
+const isBackgroundVideoReady = ref(false);
+const hasBackgroundVideoError = ref(false);
+const config = useRuntimeConfig();
+const backgroundPlaybackId = computed(() => (
+ String(config.public.muxBackgroundPlaybackId || config.public.muxPlaybackId || "").trim()
+));
+const backgroundPosterUrl = computed(() => {
+ if (!backgroundPlaybackId.value) return "";
+
+ const url = new URL(`https://image.mux.com/${encodeURIComponent(backgroundPlaybackId.value)}/thumbnail.jpg`);
+ url.searchParams.set("time", "0.1");
+ url.searchParams.set("width", "1600");
+ url.searchParams.set("fit_mode", "preserve");
+ return url.toString();
+});
let gradient: NeatController | null = null;
let heroObserver: IntersectionObserver | null = null;
@@ -12,6 +28,8 @@ let isVisible = false;
let isInitializing = false;
let initToken = 0;
let revealTimer: number | null = null;
+let backgroundVideoTimer: number | null = null;
+let backgroundVideoIdleId: number | null = null;
const montereyConfig: NeatConfig = {
colors: [
@@ -35,7 +53,7 @@ const montereyConfig: NeatConfig = {
wireframe: false,
colorBlending: 9,
backgroundColor: "#030012",
- backgroundAlpha: 1,
+ backgroundAlpha: 0,
grainScale: 6,
grainSparsity: 0,
grainIntensity: 0.1,
@@ -137,28 +155,120 @@ function syncGradient() {
destroyGradient();
}
+function clearBackgroundVideoSchedule() {
+ if (backgroundVideoTimer !== null) {
+ window.clearTimeout(backgroundVideoTimer);
+ backgroundVideoTimer = null;
+ }
+
+ if (backgroundVideoIdleId !== null) {
+ const idleWindow = window as Window & { cancelIdleCallback?: (handle: number) => void };
+ idleWindow.cancelIdleCallback?.(backgroundVideoIdleId);
+ backgroundVideoIdleId = null;
+ }
+}
+
+function shouldUseBackgroundVideo() {
+ return Boolean(
+ backgroundPlaybackId.value &&
+ isVisible &&
+ !motionQuery?.matches &&
+ !hasBackgroundVideoError.value,
+ );
+}
+
+async function mountBackgroundVideo() {
+ clearBackgroundVideoSchedule();
+ if (shouldMountBackgroundVideo.value || !shouldUseBackgroundVideo()) return;
+
+ try {
+ await import("@mux/mux-video");
+ if (shouldUseBackgroundVideo()) {
+ shouldMountBackgroundVideo.value = true;
+ }
+ } catch (error) {
+ console.warn("Mux hero background video is unavailable", error);
+ hasBackgroundVideoError.value = true;
+ }
+}
+
+function scheduleBackgroundVideo() {
+ clearBackgroundVideoSchedule();
+ if (shouldMountBackgroundVideo.value || !shouldUseBackgroundVideo()) return;
+
+ const idleWindow = window as Window & {
+ requestIdleCallback?: (callback: () => void, options?: { timeout: number }) => number;
+ };
+
+ if (idleWindow.requestIdleCallback) {
+ backgroundVideoIdleId = idleWindow.requestIdleCallback(() => {
+ backgroundVideoIdleId = null;
+ void mountBackgroundVideo();
+ }, { timeout: 1600 });
+ return;
+ }
+
+ backgroundVideoTimer = window.setTimeout(() => {
+ backgroundVideoTimer = null;
+ void mountBackgroundVideo();
+ }, 450);
+}
+
+function stopBackgroundVideo() {
+ clearBackgroundVideoSchedule();
+ shouldMountBackgroundVideo.value = false;
+ isBackgroundVideoReady.value = false;
+}
+
+function syncBackgroundVideo() {
+ if (shouldUseBackgroundVideo()) {
+ scheduleBackgroundVideo();
+ return;
+ }
+
+ stopBackgroundVideo();
+}
+
+function markBackgroundVideoReady() {
+ if (!shouldUseBackgroundVideo()) return;
+ isBackgroundVideoReady.value = true;
+}
+
+function markBackgroundVideoError() {
+ hasBackgroundVideoError.value = true;
+ stopBackgroundVideo();
+}
+
+function syncMotionState() {
+ syncGradient();
+ syncBackgroundVideo();
+}
+
onMounted(() => {
motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
mobileQuery = window.matchMedia("(max-width: 700px)");
- motionQuery.addEventListener("change", syncGradient);
+ motionQuery.addEventListener("change", syncMotionState);
mobileQuery.addEventListener("change", syncGradient);
heroObserver = new IntersectionObserver(
([entry]) => {
isVisible = Boolean(entry?.isIntersecting);
syncGradient();
+ syncBackgroundVideo();
},
{ rootMargin: "160px 0px", threshold: 0.01 },
);
const target = canvasRef.value?.closest(".cyber-hero");
if (target) heroObserver.observe(target);
+ syncBackgroundVideo();
});
onBeforeUnmount(() => {
heroObserver?.disconnect();
- motionQuery?.removeEventListener("change", syncGradient);
+ motionQuery?.removeEventListener("change", syncMotionState);
mobileQuery?.removeEventListener("change", syncGradient);
+ stopBackgroundVideo();
destroyGradient();
});
@@ -169,6 +279,40 @@ onBeforeUnmount(() => {
:class="{ 'cyber-hero__monterey--live': isLive }"
aria-hidden="true"
>
+
+
+
+
+
diff --git a/landing/components/hero/CyberHeroRobot.vue b/landing/components/hero/CyberHeroRobot.vue
index a19f03ad..d6547404 100644
--- a/landing/components/hero/CyberHeroRobot.vue
+++ b/landing/components/hero/CyberHeroRobot.vue
@@ -7,10 +7,12 @@ const props = defineProps<{
activeReceiver?: HeroAgentRole | "video" | null;
}>();
+const { locale } = useI18n();
const isSender = computed(() => props.activeSender === props.agent.id);
const isReceiver = computed(() => props.activeReceiver === props.agent.id);
const imageLoading = computed(() => (props.agent.priority ? "eager" : "lazy"));
const imageFetchPriority = computed(() => (props.agent.priority ? "high" : "auto"));
+const statusLabel = computed(() => locale.value === "ru" ? "Статус:" : "Status:");
const rootStyle = computed(() => ({
"--agent-x": String(props.agent.desktop.x),
@@ -62,7 +64,7 @@ const rootStyle = computed(() => ({
{{ task }}
- Status:
+ {{ statusLabel }}
{{ agent.status }}
diff --git a/landing/components/hero/CyberHeroScene.vue b/landing/components/hero/CyberHeroScene.vue
index 3f97b340..3df6650a 100644
--- a/landing/components/hero/CyberHeroScene.vue
+++ b/landing/components/hero/CyberHeroScene.vue
@@ -1,11 +1,14 @@
+
- Team command feed
- Live demo
+ {{ isRu ? 'Командная лента' : 'Team command feed' }}
+ {{ isRu ? 'Живое демо' : 'Live demo' }}
diff --git a/landing/components/hero/CyberProviderIcon.vue b/landing/components/hero/CyberProviderIcon.vue
new file mode 100644
index 00000000..9e9df069
--- /dev/null
+++ b/landing/components/hero/CyberProviderIcon.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
diff --git a/landing/components/layout/AppFooter.vue b/landing/components/layout/AppFooter.vue
index da28e209..52be6429 100644
--- a/landing/components/layout/AppFooter.vue
+++ b/landing/components/layout/AppFooter.vue
@@ -5,6 +5,7 @@ const { t, locale } = useI18n();
const { repoUrl } = useGithubRepo();
const { baseURL } = useRuntimeConfig().app;
const year = new Date().getFullYear();
+const authorLabel = computed(() => locale.value === 'ru' ? 'Автор' : 'Author');
const docsHref = computed(() => {
const base = baseURL.replace(/\/?$/, '/');
return `${base}${locale.value === 'ru' ? 'docs/ru/' : 'docs/'}`;
@@ -31,7 +32,7 @@ const docsHref = computed(() => {
>{{ t('footer.copyright', { year }) }} · {{ t('footer.tagline') }}
@@ -186,7 +186,7 @@ const navItems = computed(() => [
--header-height: 126px;
--header-panel-height: 86px;
--header-action-size: clamp(54px, 3.25vw, 66px);
- --header-github-width: clamp(150px, 9.7vw, 204px);
+ --header-github-width: clamp(190px, 12vw, 236px);
--header-brand-icon: clamp(52px, 3.7vw, 68px);
--header-brand-text: clamp(23px, 1.42vw, 32px);
@@ -503,8 +503,8 @@ const navItems = computed(() => [
color: var(--header-cyan) !important;
font-family: var(--at-font-mono);
font-weight: 800 !important;
- font-size: clamp(14px, 1vw, 19px) !important;
- letter-spacing: 0.08em !important;
+ font-size: clamp(13px, 0.86vw, 16px) !important;
+ letter-spacing: 0.06em !important;
text-transform: uppercase !important;
background: rgba(0, 234, 255, 0.035) !important;
box-shadow:
@@ -519,6 +519,8 @@ const navItems = computed(() => [
justify-content: center;
width: 100%;
height: 100%;
+ min-width: 0;
+ overflow: hidden;
}
.app-header__github-icon {
@@ -527,7 +529,10 @@ const navItems = computed(() => [
}
.app-header__github-text {
+ min-width: 0;
+ overflow: hidden;
line-height: 1;
+ white-space: nowrap;
}
.app-header__github-btn:hover {
@@ -551,7 +556,7 @@ const navItems = computed(() => [
--header-height: 112px;
--header-panel-height: 72px;
--header-action-size: 54px;
- --header-github-width: 124px;
+ --header-github-width: 158px;
--header-brand-icon: 44px;
--header-brand-text: 15px;
}
@@ -591,7 +596,13 @@ const navItems = computed(() => [
}
.app-header__github-btn {
- font-size: 13px !important;
+ padding-inline: 8px !important;
+ font-size: 12px !important;
+ letter-spacing: 0.04em !important;
+ }
+
+ .app-header__github-btn :deep(.v-btn__content) {
+ gap: 6px;
}
}
diff --git a/landing/components/sections/ComparisonSection.vue b/landing/components/sections/ComparisonSection.vue
index b87d24cd..7c1fe0fe 100644
--- a/landing/components/sections/ComparisonSection.vue
+++ b/landing/components/sections/ComparisonSection.vue
@@ -1,11 +1,162 @@
@@ -319,7 +320,7 @@ const releaseDate = computed(() => {
class="download-section__card-robot-bubble"
tail="right"
>
- Готов начать!
+ {{ linuxRobotBubble }}
![]()
{
day: "numeric",
});
});
-const activeHeroMessage = computed(() => heroMessages[activeHeroMessageIndex.value] ?? null);
+const localizedHeroMessages = computed(() => getLocalizedHeroMessages(locale.value));
+const activeHeroMessage = computed(() => localizedHeroMessages.value[activeHeroMessageIndex.value] ?? null);
+const supportedProviders = [
+ {
+ id: "codex",
+ name: "Codex",
+ accent: "cyan",
+ },
+ {
+ id: "anthropic",
+ name: "Anthropic",
+ accent: "amber",
+ },
+ {
+ id: "opencode",
+ name: "OpenCode",
+ accent: "magenta",
+ },
+] as const;
+const supportedProvidersLabel = computed(() => (
+ locale.value === "ru"
+ ? "Поддерживаем AI-провайдеры"
+ : "Supported AI providers"
+));
+const heroSlogan = computed(() => (
+ locale.value === "ru"
+ ? "Делайте много, почти ничего не делая"
+ : "Get a lot done by doing very little"
+));
const heroDownloadUrl = computed(() => {
const asset = downloadStore.selectedAsset;
@@ -57,7 +85,7 @@ function setHeroMessageTimer(callback: () => void, delay: number) {
function runHeroMessageCycle() {
clearHeroMessageTimers();
- if (!isHeroVisible.value || heroReducedMotion.value || heroMessages.length === 0) {
+ if (!isHeroVisible.value || heroReducedMotion.value || localizedHeroMessages.value.length === 0) {
heroMessagePhase.value = "cooldown";
return;
}
@@ -73,7 +101,7 @@ function runHeroMessageCycle() {
heroMessagePhase.value = "cooldown";
}, 3900);
setHeroMessageTimer(() => {
- activeHeroMessageIndex.value = (activeHeroMessageIndex.value + 1) % heroMessages.length;
+ activeHeroMessageIndex.value = (activeHeroMessageIndex.value + 1) % localizedHeroMessages.value.length;
runHeroMessageCycle();
}, 4700);
}
@@ -138,13 +166,34 @@ onUnmounted(() => {
- Get a lot done by doing very little
+ {{ heroSlogan }}
{{ content.hero.subtitle }}
+
+
+
+
+
+
+
+ {{ provider.name }}
+
+
+
+
+
({
+const screenshots = computed(() => screenshotData.map((s) => ({
src: publicPath(s.path),
- alt: s.alt,
+ alt: locale.value === 'ru' ? (s.ruAlt ?? s.alt) : s.alt,
width: s.width,
height: s.height,
-}));
+})));
+const prevLabel = computed(() => locale.value === 'ru' ? 'Предыдущий' : 'Previous');
+const nextLabel = computed(() => locale.value === 'ru' ? 'Следующий' : 'Next');
const swiperRef = ref(null);
const swiperReady = ref(false);
@@ -45,11 +47,11 @@ function closeLightbox() {
}
function lightboxPrev() {
- lightboxIndex.value = (lightboxIndex.value - 1 + screenshots.length) % screenshots.length;
+ lightboxIndex.value = (lightboxIndex.value - 1 + screenshots.value.length) % screenshots.value.length;
}
function lightboxNext() {
- lightboxIndex.value = (lightboxIndex.value + 1) % screenshots.length;
+ lightboxIndex.value = (lightboxIndex.value + 1) % screenshots.value.length;
}
function onKeydown(e: KeyboardEvent) {
@@ -182,14 +184,14 @@ function slideNext() {