style: refine landing hero presentation
Adjust the landing hero, header, demo video, and Monterey background styling for the updated cyberpunk presentation without mixing in dependency or workflow changes.
This commit is contained in:
parent
edcb0a7433
commit
ec60c244a3
4 changed files with 318 additions and 18 deletions
|
|
@ -1819,10 +1819,6 @@
|
|||
scale(var(--agent-tablet-scale));
|
||||
}
|
||||
|
||||
.cyber-agent__card {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cyber-feature-rail-shell {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -1864,6 +1860,88 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1100px) {
|
||||
.cyber-agent[data-agent="planner"] {
|
||||
transform:
|
||||
translate3d(-50%, -100%, 0)
|
||||
scale(var(--agent-tablet-scale));
|
||||
}
|
||||
|
||||
.cyber-agent[data-agent="planner"] .cyber-agent__float {
|
||||
top: 4px;
|
||||
transform-origin: center bottom;
|
||||
}
|
||||
|
||||
.cyber-agent[data-agent="planner"] .cyber-agent__image {
|
||||
transform:
|
||||
scaleX(var(--agent-face))
|
||||
rotate(var(--agent-lean));
|
||||
}
|
||||
|
||||
.cyber-agent[data-agent="planner"] .cyber-agent__contact {
|
||||
left: 17%;
|
||||
right: 17%;
|
||||
bottom: 18px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.cyber-agent__card {
|
||||
display: block;
|
||||
width: 138px;
|
||||
padding: 8px 9px;
|
||||
font-size: 0.62rem;
|
||||
line-height: 1.24;
|
||||
}
|
||||
|
||||
.cyber-agent[data-agent="planner"] .cyber-agent__card,
|
||||
.cyber-agent[data-agent="lead"] .cyber-agent__card,
|
||||
.cyber-agent[data-agent="developer"] .cyber-agent__card {
|
||||
padding: 8px 9px;
|
||||
font-size: 0.62rem;
|
||||
}
|
||||
|
||||
.cyber-agent__label {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.cyber-agent__tasks {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cyber-agent__status {
|
||||
gap: 3px 5px;
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cyber-agent[data-agent="planner"] .cyber-agent__card {
|
||||
top: 35%;
|
||||
right: auto;
|
||||
left: 100%;
|
||||
width: 138px;
|
||||
transform: translate(8px, -12%) scale(2.27);
|
||||
transform-origin: left top;
|
||||
}
|
||||
|
||||
.cyber-agent[data-agent="lead"] .cyber-agent__card {
|
||||
top: 12%;
|
||||
right: auto;
|
||||
left: 100%;
|
||||
width: 138px;
|
||||
transform: translate(8px, -12%) scale(2.38);
|
||||
transform-origin: left top;
|
||||
}
|
||||
|
||||
.cyber-agent[data-agent="developer"] .cyber-agent__card {
|
||||
top: 12%;
|
||||
right: auto;
|
||||
left: 100%;
|
||||
width: 128px;
|
||||
transform: translate(8px, -12%) scale(2.5);
|
||||
transform-origin: left top;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.cyber-hero {
|
||||
padding: 84px 0 36px;
|
||||
|
|
@ -1889,12 +1967,14 @@
|
|||
|
||||
.cyber-hero__layout {
|
||||
min-width: 0;
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cyber-hero__copy {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.cyber-hero__brand-lockup {
|
||||
|
|
@ -1970,14 +2050,14 @@
|
|||
.cyber-scene {
|
||||
min-height: auto;
|
||||
aspect-ratio: auto;
|
||||
padding: 92px 0 12px;
|
||||
padding: 96px 0 14px;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.cyber-hero__scene {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-top: 18px;
|
||||
margin-top: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
|
@ -1996,18 +2076,20 @@
|
|||
|
||||
.cyber-scene__robots {
|
||||
inset: 0 0 auto;
|
||||
height: 92px;
|
||||
height: 96px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
gap: clamp(18px, 6vw, 34px);
|
||||
}
|
||||
|
||||
.cyber-agent {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
display: none;
|
||||
left: auto;
|
||||
top: auto;
|
||||
width: 76px;
|
||||
width: clamp(58px, 18vw, 74px);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
|
|
@ -2015,11 +2097,46 @@
|
|||
display: block;
|
||||
}
|
||||
|
||||
.cyber-agent[data-agent="planner"] {
|
||||
z-index: auto;
|
||||
width: clamp(58px, 18vw, 74px);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.cyber-agent[data-agent="planner"] .cyber-agent__float {
|
||||
top: 4px;
|
||||
transform-origin: center bottom;
|
||||
}
|
||||
|
||||
.cyber-agent[data-agent="planner"] .cyber-agent__image {
|
||||
transform:
|
||||
scaleX(var(--agent-face))
|
||||
rotate(var(--agent-lean));
|
||||
}
|
||||
|
||||
.cyber-agent[data-agent="planner"] .cyber-agent__contact {
|
||||
left: 17%;
|
||||
right: 17%;
|
||||
bottom: 18px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.cyber-agent[data-agent="lead"] .cyber-agent__float {
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.cyber-agent[data-agent="developer"] .cyber-agent__float {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.cyber-agent__float {
|
||||
animation-duration: 6s;
|
||||
}
|
||||
|
||||
.cyber-agent__card,
|
||||
.cyber-agent .cyber-agent__card,
|
||||
.cyber-agent[data-agent="planner"] .cyber-agent__card,
|
||||
.cyber-agent[data-agent="lead"] .cyber-agent__card,
|
||||
.cyber-agent[data-agent="developer"] .cyber-agent__card,
|
||||
.cyber-agent__eyes {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -2034,6 +2151,33 @@
|
|||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.cyber-feature-rail-shell {
|
||||
margin-top: clamp(104px, 24vw, 128px);
|
||||
}
|
||||
|
||||
.cyber-feature-rail__collaboration {
|
||||
left: 31%;
|
||||
bottom: calc(100% + 8px);
|
||||
width: clamp(96px, 30vw, 124px);
|
||||
}
|
||||
|
||||
.cyber-feature-rail__reviewer {
|
||||
--reviewer-robot-width: clamp(58px, 18vw, 72px);
|
||||
|
||||
right: clamp(6px, 3vw, 16px);
|
||||
bottom: calc(100% + 8px);
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__reviewer-card,
|
||||
.cyber-feature-rail__reviewer-bubble {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__robot {
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__item {
|
||||
grid-template-columns: 48px 44px minmax(0, 1fr);
|
||||
grid-template-rows: auto;
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ function shouldUseBackgroundVideo() {
|
|||
backgroundPlaybackId.value &&
|
||||
isVisible &&
|
||||
!motionQuery?.matches &&
|
||||
!mobileQuery?.matches &&
|
||||
!hasBackgroundVideoError.value,
|
||||
);
|
||||
}
|
||||
|
|
@ -248,7 +249,7 @@ onMounted(() => {
|
|||
motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
mobileQuery = window.matchMedia("(max-width: 700px)");
|
||||
motionQuery.addEventListener("change", syncMotionState);
|
||||
mobileQuery.addEventListener("change", syncGradient);
|
||||
mobileQuery.addEventListener("change", syncMotionState);
|
||||
|
||||
heroObserver = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
|
|
@ -267,7 +268,7 @@ onMounted(() => {
|
|||
onBeforeUnmount(() => {
|
||||
heroObserver?.disconnect();
|
||||
motionQuery?.removeEventListener("change", syncMotionState);
|
||||
mobileQuery?.removeEventListener("change", syncGradient);
|
||||
mobileQuery?.removeEventListener("change", syncMotionState);
|
||||
stopBackgroundVideo();
|
||||
destroyGradient();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ const menuOpen = ref(false);
|
|||
const withBase = (path: string) => `${baseURL.replace(/\/?$/, '/')}${path.replace(/^\/+/, '')}`;
|
||||
const docsHref = computed(() => withBase(locale.value === 'ru' ? 'docs/ru/' : 'docs/'));
|
||||
const isRu = computed(() => locale.value === 'ru');
|
||||
const openMenuLabel = computed(() => (isRu.value ? 'Открыть меню' : 'Open menu'));
|
||||
const closeMenuLabel = computed(() => (isRu.value ? 'Закрыть меню' : 'Close menu'));
|
||||
|
||||
const navItems = computed(() => [
|
||||
{ href: '#screenshots', label: t('nav.screenshots'), shortLabel: isRu.value ? 'Скрины' : 'Shots' },
|
||||
|
|
@ -134,7 +136,7 @@ const navItems = computed(() => [
|
|||
<ThemeToggle />
|
||||
</div>
|
||||
<div class="app-header__mobile-actions">
|
||||
<v-btn :icon="mdiMenu" variant="text" @click="menuOpen = true" />
|
||||
<v-btn :icon="mdiMenu" variant="text" :aria-label="openMenuLabel" @click="menuOpen = true" />
|
||||
<Teleport to="body">
|
||||
<Transition name="mobile-menu-fade">
|
||||
<div v-if="menuOpen" class="mobile-menu-overlay" @click.self="menuOpen = false">
|
||||
|
|
@ -142,7 +144,13 @@ const navItems = computed(() => [
|
|||
<div class="mobile-menu__header">
|
||||
<AppLogo />
|
||||
<div style="flex: 1" />
|
||||
<v-btn :icon="mdiClose" variant="text" @click="menuOpen = false" />
|
||||
<v-btn
|
||||
:icon="mdiClose"
|
||||
variant="text"
|
||||
class="mobile-menu__close"
|
||||
:aria-label="closeMenuLabel"
|
||||
@click="menuOpen = false"
|
||||
/>
|
||||
</div>
|
||||
<hr class="mobile-menu__divider">
|
||||
<nav class="mobile-menu__list">
|
||||
|
|
@ -825,10 +833,43 @@ const navItems = computed(() => [
|
|||
}
|
||||
|
||||
.mobile-menu__header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding-bottom: 12px;
|
||||
padding-bottom: 14px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.mobile-menu__close {
|
||||
width: 52px !important;
|
||||
min-width: 52px !important;
|
||||
height: 52px !important;
|
||||
color: var(--cyber-cyan) !important;
|
||||
border: 1px solid rgba(0, 234, 255, 0.82) !important;
|
||||
border-radius: 50% !important;
|
||||
background:
|
||||
radial-gradient(circle at 50% 50%, rgba(0, 234, 255, 0.18), transparent 58%),
|
||||
rgba(2, 10, 24, 0.94) !important;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255, 255, 255, 0.08) inset,
|
||||
0 0 18px rgba(0, 234, 255, 0.38),
|
||||
0 0 36px rgba(0, 234, 255, 0.16);
|
||||
}
|
||||
|
||||
.mobile-menu__close :deep(.v-icon) {
|
||||
font-size: 30px;
|
||||
filter: drop-shadow(0 0 10px rgba(0, 234, 255, 0.6));
|
||||
}
|
||||
|
||||
.mobile-menu__close:hover {
|
||||
color: #ffffff !important;
|
||||
border-color: rgba(255, 255, 255, 0.86) !important;
|
||||
background:
|
||||
radial-gradient(circle at 50% 50%, rgba(0, 234, 255, 0.28), transparent 62%),
|
||||
rgba(0, 234, 255, 0.16) !important;
|
||||
}
|
||||
|
||||
.mobile-menu__divider {
|
||||
|
|
|
|||
|
|
@ -29,10 +29,29 @@ const muxPlayerUrl = computed(() => {
|
|||
url.searchParams.set("video-title", muxVideoTitle.value);
|
||||
return url.toString();
|
||||
});
|
||||
const muxPosterUrl = computed(() => {
|
||||
if (!muxPlaybackId.value) return "";
|
||||
|
||||
const url = new URL(`https://image.mux.com/${encodeURIComponent(muxPlaybackId.value)}/thumbnail.webp`);
|
||||
url.searchParams.set("time", "0.1");
|
||||
url.searchParams.set("width", "900");
|
||||
url.searchParams.set("fit_mode", "preserve");
|
||||
return url.toString();
|
||||
});
|
||||
const isLoaded = ref(false);
|
||||
const hasError = ref(false);
|
||||
const isMobileViewport = ref(false);
|
||||
const playerActivated = ref(false);
|
||||
const shouldShowMobilePoster = computed(() => (
|
||||
Boolean(muxPlayerUrl.value) &&
|
||||
!hasError.value &&
|
||||
isMobileViewport.value &&
|
||||
!playerActivated.value
|
||||
));
|
||||
const shouldShowPlayer = computed(() => Boolean(muxPlayerUrl.value) && !hasError.value && !shouldShowMobilePoster.value);
|
||||
|
||||
let loadFallbackTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let mobileQuery: MediaQueryList | null = null;
|
||||
|
||||
function clearLoadFallback() {
|
||||
if (!loadFallbackTimer) return;
|
||||
|
|
@ -51,11 +70,25 @@ function markError() {
|
|||
clearLoadFallback();
|
||||
}
|
||||
|
||||
function syncMobileViewport() {
|
||||
isMobileViewport.value = Boolean(mobileQuery?.matches);
|
||||
}
|
||||
|
||||
function activatePlayer() {
|
||||
playerActivated.value = true;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
mobileQuery = window.matchMedia("(max-width: 700px)");
|
||||
syncMobileViewport();
|
||||
mobileQuery.addEventListener("change", syncMobileViewport);
|
||||
loadFallbackTimer = setTimeout(markLoaded, 2500);
|
||||
});
|
||||
|
||||
onUnmounted(clearLoadFallback);
|
||||
onUnmounted(() => {
|
||||
mobileQuery?.removeEventListener("change", syncMobileViewport);
|
||||
clearLoadFallback();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -70,7 +103,7 @@ onUnmounted(clearLoadFallback);
|
|||
|
||||
<ClientOnly>
|
||||
<iframe
|
||||
v-if="muxPlayerUrl && !hasError"
|
||||
v-if="shouldShowPlayer"
|
||||
class="hero-video__player"
|
||||
:class="{ 'hero-video__player--loaded': isLoaded }"
|
||||
:src="muxPlayerUrl"
|
||||
|
|
@ -94,7 +127,21 @@ onUnmounted(clearLoadFallback);
|
|||
</template>
|
||||
</ClientOnly>
|
||||
|
||||
<div v-if="!isLoaded && !hasError && muxPlayerUrl" class="hero-video__skeleton">
|
||||
<button
|
||||
v-if="shouldShowMobilePoster"
|
||||
type="button"
|
||||
class="hero-video__poster"
|
||||
:style="{ '--hero-video-poster': muxPosterUrl ? `url(${muxPosterUrl})` : 'url(/screenshots/2.jpg)' }"
|
||||
:aria-label="videoTitle"
|
||||
@click="activatePlayer"
|
||||
>
|
||||
<span class="hero-video__poster-play">
|
||||
<v-icon :icon="mdiPlay" size="40" />
|
||||
</span>
|
||||
<span class="hero-video__poster-label">{{ t("hero.watchDemo") }}</span>
|
||||
</button>
|
||||
|
||||
<div v-if="!isLoaded && !hasError && shouldShowPlayer" class="hero-video__skeleton">
|
||||
<div class="hero-video__skeleton-pulse" />
|
||||
<div class="hero-video__skeleton-content">
|
||||
<div class="hero-video__skeleton-spinner" />
|
||||
|
|
@ -265,6 +312,69 @@ onUnmounted(clearLoadFallback);
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
.hero-video__poster {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
border: none;
|
||||
border-radius: 14px;
|
||||
color: rgba(230, 251, 255, 0.94);
|
||||
background:
|
||||
linear-gradient(90deg, rgba(2, 6, 16, 0.18), rgba(2, 6, 16, 0.36)),
|
||||
linear-gradient(180deg, rgba(0, 234, 255, 0.06), rgba(255, 43, 255, 0.08)),
|
||||
var(--hero-video-poster) center / cover;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hero-video__poster::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(circle at 50% 50%, rgba(0, 240, 255, 0.16), transparent 34%),
|
||||
repeating-linear-gradient(to bottom, rgba(255, 255, 255, 0.08) 0 1px, transparent 1px 4px);
|
||||
mix-blend-mode: screen;
|
||||
opacity: 0.38;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hero-video__poster-play,
|
||||
.hero-video__poster-label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-video__poster-play {
|
||||
display: grid;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
place-items: center;
|
||||
border: 1px solid rgba(0, 240, 255, 0.58);
|
||||
border-radius: 50%;
|
||||
color: #ffffff;
|
||||
background: rgba(2, 10, 24, 0.68);
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255, 255, 255, 0.08) inset,
|
||||
0 0 24px rgba(0, 240, 255, 0.3);
|
||||
}
|
||||
|
||||
.hero-video__poster-label {
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
color: rgba(0, 240, 255, 0.9);
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0 0 16px rgba(0, 240, 255, 0.42);
|
||||
}
|
||||
|
||||
.hero-video__skeleton {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
|
@ -398,6 +508,10 @@ onUnmounted(clearLoadFallback);
|
|||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.hero-video__poster {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.hero-video__edge {
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
|
|
|
|||
Loading…
Reference in a new issue