feat(landing): refine hero timeline and ctas
This commit is contained in:
parent
99e8e2e017
commit
98405b9040
9 changed files with 525 additions and 215 deletions
|
|
@ -516,51 +516,163 @@
|
|||
.cyber-hero__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
gap: 14px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.cyber-hero__action.v-btn {
|
||||
min-height: 52px !important;
|
||||
min-width: 148px !important;
|
||||
border-radius: var(--cyber-radius-sm) !important;
|
||||
padding-inline: 18px !important;
|
||||
font-weight: 800 !important;
|
||||
font-size: 0.9rem !important;
|
||||
letter-spacing: 0.01em !important;
|
||||
.cyber-action-button.v-btn {
|
||||
--action-accent-a: var(--cyber-cyan);
|
||||
--action-accent-b: var(--cyber-magenta);
|
||||
--action-bg:
|
||||
linear-gradient(135deg, rgba(0, 210, 255, 0.74) 0%, rgba(43, 103, 255, 0.56) 48%, rgba(207, 34, 215, 0.76) 100%),
|
||||
linear-gradient(180deg, rgba(7, 18, 42, 0.9), rgba(4, 10, 25, 0.92));
|
||||
--action-border: rgba(0, 234, 255, 0.78);
|
||||
--action-text: #f2fbff;
|
||||
--action-subtext: rgba(231, 240, 255, 0.72);
|
||||
--action-clip: polygon(18px 0, calc(100% - 12px) 0, 100% 12px, 100% calc(100% - 18px), calc(100% - 18px) 100%, 0 100%, 0 18px);
|
||||
|
||||
position: relative;
|
||||
min-height: 76px !important;
|
||||
min-width: 312px !important;
|
||||
overflow: hidden !important;
|
||||
border: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
padding: 0 26px !important;
|
||||
color: var(--action-text) !important;
|
||||
background: var(--action-bg) !important;
|
||||
clip-path: var(--action-clip);
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1) inset,
|
||||
0 0 22px color-mix(in srgb, var(--action-accent-a) 24%, transparent),
|
||||
0 0 34px color-mix(in srgb, var(--action-accent-b) 20%, transparent) !important;
|
||||
font-weight: 900 !important;
|
||||
letter-spacing: 0 !important;
|
||||
text-transform: uppercase !important;
|
||||
transition:
|
||||
transform 0.18s ease,
|
||||
box-shadow 0.18s ease,
|
||||
border-color 0.18s ease,
|
||||
background 0.18s ease !important;
|
||||
filter 0.18s ease !important;
|
||||
}
|
||||
|
||||
.cyber-hero__action.v-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
.cyber-action-button.v-btn .v-btn__content {
|
||||
position: static;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.cyber-hero__action--primary.v-btn {
|
||||
color: var(--cyber-action-primary-color) !important;
|
||||
background: linear-gradient(135deg, var(--cyber-cyan), var(--cyber-magenta)) !important;
|
||||
.cyber-action-button.v-btn .v-btn__overlay,
|
||||
.cyber-action-button.v-btn .v-btn__underlay {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.cyber-action-button.v-btn:hover {
|
||||
filter: brightness(1.08) saturate(1.08);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.cyber-action-button__glow {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.16), transparent 28%),
|
||||
radial-gradient(ellipse at 16% 48%, color-mix(in srgb, var(--action-accent-a) 22%, transparent), transparent 38%),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.08), transparent 52%);
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.cyber-action-button__frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 3;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
color: var(--action-border);
|
||||
pointer-events: none;
|
||||
filter:
|
||||
drop-shadow(0 0 8px color-mix(in srgb, var(--action-accent-a) 22%, transparent))
|
||||
drop-shadow(0 0 12px color-mix(in srgb, var(--action-accent-b) 12%, transparent));
|
||||
}
|
||||
|
||||
.cyber-action-button__frame path {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: square;
|
||||
stroke-linejoin: miter;
|
||||
stroke-width: 1.35;
|
||||
shape-rendering: geometricPrecision;
|
||||
}
|
||||
|
||||
.cyber-action-button__icon {
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
display: grid;
|
||||
flex: 0 0 auto;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
place-items: center;
|
||||
color: var(--action-accent-a);
|
||||
filter:
|
||||
drop-shadow(0 0 10px color-mix(in srgb, var(--action-accent-a) 54%, transparent))
|
||||
drop-shadow(0 0 18px color-mix(in srgb, var(--action-accent-b) 30%, transparent));
|
||||
}
|
||||
|
||||
.cyber-action-button--primary .cyber-action-button__icon {
|
||||
color: #00d8ff;
|
||||
filter:
|
||||
drop-shadow(0 0 10px rgba(0, 234, 255, 0.56))
|
||||
drop-shadow(0 0 16px rgba(35, 91, 255, 0.22));
|
||||
}
|
||||
|
||||
.cyber-action-button__copy {
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 6px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.cyber-action-button__label {
|
||||
color: var(--action-text);
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.cyber-action-button__subtitle {
|
||||
color: var(--action-subtext);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
text-transform: none;
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.cyber-action-button--primary.v-btn {
|
||||
--action-text: #f8fbff;
|
||||
--action-subtext: rgba(237, 247, 255, 0.74);
|
||||
}
|
||||
|
||||
.cyber-action-button--secondary.v-btn {
|
||||
--action-accent-a: #91b6ff;
|
||||
--action-accent-b: var(--cyber-cyan);
|
||||
--action-bg:
|
||||
linear-gradient(135deg, rgba(10, 18, 38, 0.96), rgba(7, 14, 31, 0.78)),
|
||||
rgba(1, 7, 18, 0.84);
|
||||
--action-border: rgba(123, 160, 231, 0.64);
|
||||
--action-text: #f3f7ff;
|
||||
--action-subtext: rgba(182, 198, 226, 0.76);
|
||||
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255, 255, 255, 0.16) inset,
|
||||
0 0 24px rgba(0, 234, 255, 0.34),
|
||||
0 0 34px rgba(255, 43, 255, 0.22) !important;
|
||||
}
|
||||
|
||||
.cyber-hero__action--watch.v-btn,
|
||||
.cyber-hero__action--docs.v-btn {
|
||||
color: var(--cyber-text) !important;
|
||||
border-color: rgba(0, 234, 255, 0.46) !important;
|
||||
background: var(--cyber-action-secondary-bg) !important;
|
||||
}
|
||||
|
||||
.cyber-hero__action--watch.v-btn:hover,
|
||||
.cyber-hero__action--docs.v-btn:hover {
|
||||
color: var(--cyber-cyan) !important;
|
||||
border-color: rgba(0, 234, 255, 0.74) !important;
|
||||
background: var(--cyber-action-secondary-hover-bg) !important;
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05) inset,
|
||||
0 0 24px rgba(80, 130, 255, 0.16),
|
||||
0 0 34px rgba(0, 234, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
.cyber-hero__terminal-note {
|
||||
|
|
@ -1210,31 +1322,67 @@
|
|||
.cyber-feature-rail-shell {
|
||||
position: relative;
|
||||
z-index: 7;
|
||||
margin: 28px auto 0;
|
||||
width: min(1540px, 96%);
|
||||
margin: clamp(42px, 5vw, 72px) auto 0;
|
||||
width: min(1580px, 96%);
|
||||
}
|
||||
|
||||
.cyber-feature-rail-shell::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
inset: -118px -3vw -96px;
|
||||
inset: -82px -4vw -76px;
|
||||
pointer-events: none;
|
||||
background: var(--cyber-feature-shell-bg);
|
||||
opacity: 0.86;
|
||||
mask-image: radial-gradient(ellipse at 50% 54%, black 0 48%, rgba(0, 0, 0, 0.5) 62%, transparent 78%);
|
||||
background:
|
||||
radial-gradient(ellipse at 50% 42%, rgba(0, 234, 255, 0.12), transparent 48%),
|
||||
radial-gradient(ellipse at 88% 54%, rgba(255, 43, 255, 0.12), transparent 42%);
|
||||
opacity: 0.72;
|
||||
mask-image: radial-gradient(ellipse at 50% 52%, black 0 48%, rgba(0, 0, 0, 0.5) 62%, transparent 78%);
|
||||
}
|
||||
|
||||
.cyber-feature-rail {
|
||||
--feature-line-top: 118px;
|
||||
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 0;
|
||||
width: 100%;
|
||||
padding: 17px 18px;
|
||||
background: var(--cyber-feature-rail-bg);
|
||||
box-shadow: var(--cyber-feature-rail-shadow);
|
||||
min-height: 300px;
|
||||
padding: 0 22px;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.cyber-feature-rail::before {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: var(--feature-line-top);
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
content: "";
|
||||
background:
|
||||
linear-gradient(90deg, transparent 0 2.5%, var(--cyber-cyan) 6%, rgba(0, 234, 255, 0.92) 78%, rgba(255, 43, 255, 0.95) 92%, transparent 98%),
|
||||
linear-gradient(90deg, transparent 0 3%, rgba(255, 255, 255, 0.34) 6%, transparent 13%, transparent 86%, rgba(255, 255, 255, 0.24) 92%, transparent 98%);
|
||||
box-shadow:
|
||||
0 0 18px rgba(0, 234, 255, 0.42),
|
||||
0 0 30px rgba(255, 43, 255, 0.18);
|
||||
}
|
||||
|
||||
.cyber-feature-rail::after {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: calc(var(--feature-line-top) - 7px);
|
||||
left: 0;
|
||||
width: 86px;
|
||||
height: 17px;
|
||||
content: "";
|
||||
background:
|
||||
repeating-linear-gradient(115deg, var(--cyber-cyan) 0 4px, transparent 4px 10px),
|
||||
linear-gradient(90deg, transparent, rgba(0, 234, 255, 0.6));
|
||||
clip-path: polygon(0 42%, 44% 42%, 54% 0, 100% 0, 100% 100%, 54% 100%, 44% 58%, 0 58%);
|
||||
filter: drop-shadow(0 0 10px rgba(0, 234, 255, 0.45));
|
||||
}
|
||||
|
||||
.cyber-feature-rail__collaboration {
|
||||
|
|
@ -1405,41 +1553,127 @@
|
|||
}
|
||||
|
||||
.cyber-feature-rail__item {
|
||||
--feature-accent: var(--cyber-cyan);
|
||||
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 46px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
grid-template-rows: 82px 54px minmax(0, 1fr);
|
||||
justify-items: center;
|
||||
align-items: start;
|
||||
gap: 0;
|
||||
min-width: 0;
|
||||
padding: 0 18px;
|
||||
border-right: 1px solid var(--cyber-feature-divider);
|
||||
padding: 0 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__item:last-child {
|
||||
border-right: 0;
|
||||
.cyber-feature-rail__item:nth-child(5) {
|
||||
--feature-accent: var(--cyber-magenta);
|
||||
}
|
||||
|
||||
.cyber-feature-rail__icon {
|
||||
position: relative;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
color: var(--cyber-cyan);
|
||||
border: 1px solid rgba(0, 234, 255, 0.44);
|
||||
border-radius: var(--cyber-radius-sm);
|
||||
box-shadow: 0 0 22px rgba(0, 234, 255, 0.16);
|
||||
width: 80px;
|
||||
height: 70px;
|
||||
color: var(--feature-accent);
|
||||
filter:
|
||||
drop-shadow(0 0 16px color-mix(in srgb, var(--feature-accent) 48%, transparent))
|
||||
drop-shadow(0 0 28px color-mix(in srgb, var(--feature-accent) 18%, transparent));
|
||||
}
|
||||
|
||||
.cyber-feature-rail__icon::before,
|
||||
.cyber-feature-rail__icon::after {
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
content: "";
|
||||
opacity: 0.82;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__icon::before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-top: 1px solid var(--feature-accent);
|
||||
border-left: 1px solid var(--feature-accent);
|
||||
}
|
||||
|
||||
.cyber-feature-rail__icon::after {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-right: 1px solid var(--feature-accent);
|
||||
border-bottom: 1px solid var(--feature-accent);
|
||||
}
|
||||
|
||||
.cyber-feature-rail__icon .v-icon {
|
||||
font-size: 46px !important;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__node {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
place-items: center;
|
||||
color: var(--feature-accent);
|
||||
background: rgba(2, 10, 24, 0.9);
|
||||
border: 2px solid var(--feature-accent);
|
||||
clip-path: polygon(28% 0, 72% 0, 100% 28%, 100% 72%, 72% 100%, 28% 100%, 0 72%, 0 28%);
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255, 255, 255, 0.08) inset,
|
||||
0 0 20px color-mix(in srgb, var(--feature-accent) 34%, transparent);
|
||||
font-family: var(--at-font-mono);
|
||||
font-size: 1.14rem;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__node::before {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: -26px;
|
||||
width: 1px;
|
||||
height: 25px;
|
||||
content: "";
|
||||
background: linear-gradient(180deg, var(--feature-accent), transparent);
|
||||
box-shadow: 0 0 12px color-mix(in srgb, var(--feature-accent) 46%, transparent);
|
||||
}
|
||||
|
||||
.cyber-feature-rail__copy {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
max-width: 235px;
|
||||
padding: 18px 12px 12px;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__copy::before {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
inset: 2px -8px -4px;
|
||||
content: "";
|
||||
background:
|
||||
linear-gradient(180deg, rgba(3, 8, 22, 0.46), rgba(3, 8, 22, 0.2)),
|
||||
radial-gradient(ellipse at 50% 38%, rgba(0, 234, 255, 0.08), transparent 72%);
|
||||
border: 1px solid rgba(0, 234, 255, 0.08);
|
||||
border-radius: 18px;
|
||||
opacity: 0.92;
|
||||
backdrop-filter: blur(11px) saturate(1.08);
|
||||
mask-image: radial-gradient(ellipse at 50% 50%, black 0 76%, rgba(0, 0, 0, 0.62) 88%, transparent 100%);
|
||||
}
|
||||
|
||||
.cyber-feature-rail__title {
|
||||
margin-bottom: 3px;
|
||||
margin-bottom: 11px;
|
||||
color: var(--cyber-feature-title);
|
||||
font-weight: 800;
|
||||
font-size: 0.92rem;
|
||||
font-size: clamp(0.98rem, 1.12vw, 1.22rem);
|
||||
line-height: 1.18;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__text {
|
||||
color: var(--cyber-feature-text);
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.45;
|
||||
font-size: clamp(0.82rem, 0.88vw, 1rem);
|
||||
line-height: 1.52;
|
||||
}
|
||||
|
||||
@keyframes cyberRobotBob {
|
||||
|
|
@ -1554,36 +1788,38 @@
|
|||
|
||||
.cyber-feature-rail {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
min-height: auto;
|
||||
row-gap: 34px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__collaboration {
|
||||
left: 50%;
|
||||
bottom: calc(100% - 14px);
|
||||
width: 132px;
|
||||
.cyber-feature-rail::before,
|
||||
.cyber-feature-rail::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__reviewer {
|
||||
--reviewer-robot-width: 78px;
|
||||
|
||||
right: 56px;
|
||||
gap: 10px;
|
||||
.cyber-feature-rail__item {
|
||||
grid-template-rows: 72px 48px minmax(0, 1fr);
|
||||
padding-inline: 12px;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__reviewer-card {
|
||||
width: 220px;
|
||||
font-size: 0.62rem;
|
||||
.cyber-feature-rail__icon {
|
||||
width: 72px;
|
||||
height: 62px;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__robot {
|
||||
width: var(--reviewer-robot-width);
|
||||
.cyber-feature-rail__icon .v-icon {
|
||||
font-size: 38px !important;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__item:nth-child(3) {
|
||||
border-right: 0;
|
||||
.cyber-feature-rail__node {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__item:nth-child(n + 4) {
|
||||
margin-top: 16px;
|
||||
.cyber-feature-rail__copy {
|
||||
padding-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1678,9 +1914,11 @@
|
|||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.cyber-hero__action.v-btn {
|
||||
.cyber-action-button.v-btn {
|
||||
width: 100%;
|
||||
min-height: 72px !important;
|
||||
min-width: 0 !important;
|
||||
padding-inline: 20px !important;
|
||||
}
|
||||
|
||||
.cyber-hero__terminal-note {
|
||||
|
|
@ -1751,26 +1989,64 @@
|
|||
|
||||
.cyber-feature-rail {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__collaboration {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__reviewer {
|
||||
display: none;
|
||||
gap: 20px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__item {
|
||||
grid-template-columns: 42px minmax(0, 1fr);
|
||||
padding: 12px 0;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid rgba(0, 234, 255, 0.14);
|
||||
grid-template-columns: 48px 44px minmax(0, 1fr);
|
||||
grid-template-rows: auto;
|
||||
align-items: center;
|
||||
justify-items: start;
|
||||
gap: 10px;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__item:last-child {
|
||||
border-bottom: 0;
|
||||
.cyber-feature-rail__icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__icon::before,
|
||||
.cyber-feature-rail__icon::after {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__icon .v-icon {
|
||||
font-size: 26px !important;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__node {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__node::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__copy {
|
||||
max-width: none;
|
||||
padding: 8px 10px 9px;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__copy::before {
|
||||
inset: 0 -6px;
|
||||
border-radius: 14px;
|
||||
backdrop-filter: blur(9px) saturate(1.06);
|
||||
}
|
||||
|
||||
.cyber-feature-rail__title {
|
||||
margin-bottom: 5px;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.cyber-feature-rail__text {
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.42;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
49
landing/components/hero/CyberHeroActionButton.vue
Normal file
49
landing/components/hero/CyberHeroActionButton.vue
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{
|
||||
href: string;
|
||||
icon: string;
|
||||
tone?: "primary" | "secondary";
|
||||
target?: string;
|
||||
subtitle?: string;
|
||||
}>(), {
|
||||
tone: "primary",
|
||||
target: undefined,
|
||||
subtitle: undefined,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-btn
|
||||
:href="href"
|
||||
:target="target"
|
||||
variant="flat"
|
||||
size="large"
|
||||
class="cyber-action-button"
|
||||
:class="`cyber-action-button--${tone}`"
|
||||
>
|
||||
<span class="cyber-action-button__glow" aria-hidden="true" />
|
||||
<svg
|
||||
class="cyber-action-button__frame"
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="none"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
>
|
||||
<path
|
||||
d="M 5 0 H 96 L 100 16 V 76 L 94 100 H 0 V 24 Z"
|
||||
vector-effect="non-scaling-stroke"
|
||||
/>
|
||||
</svg>
|
||||
<span class="cyber-action-button__icon" aria-hidden="true">
|
||||
<v-icon :icon="icon" size="28" />
|
||||
</span>
|
||||
<span class="cyber-action-button__copy">
|
||||
<span class="cyber-action-button__label">
|
||||
<slot />
|
||||
</span>
|
||||
<span v-if="subtitle" class="cyber-action-button__subtitle">
|
||||
{{ subtitle }}
|
||||
</span>
|
||||
</span>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
|
@ -6,24 +6,10 @@ import {
|
|||
mdiShieldCheckOutline,
|
||||
mdiMonitorDashboard,
|
||||
} from "@mdi/js";
|
||||
import {
|
||||
heroCollaborationFeature,
|
||||
getLocalizedHeroFeatureRail,
|
||||
getLocalizedHeroReviewerFeatureCard,
|
||||
type HeroMessage,
|
||||
type HeroMessagePhase,
|
||||
} from "~/data/heroScene";
|
||||
|
||||
const props = defineProps<{
|
||||
activeMessage?: HeroMessage | null;
|
||||
phase?: HeroMessagePhase;
|
||||
reducedMotion?: boolean;
|
||||
}>();
|
||||
import { getLocalizedHeroFeatureRail } from "~/data/heroScene";
|
||||
|
||||
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,
|
||||
|
|
@ -32,73 +18,11 @@ const icons = [
|
|||
mdiShieldCheckOutline,
|
||||
mdiMonitorDashboard,
|
||||
] as const;
|
||||
|
||||
const reviewerIsSender = computed(() =>
|
||||
props.activeMessage?.from === "reviewer" && props.phase !== "cooldown",
|
||||
);
|
||||
const reviewerIsReceiver = computed(() =>
|
||||
props.activeMessage?.to === "reviewer" && props.phase === "receiver",
|
||||
);
|
||||
const reviewerIsActive = computed(() => reviewerIsSender.value || reviewerIsReceiver.value);
|
||||
const reviewerBubbleText = computed(() => {
|
||||
if (!props.activeMessage || props.reducedMotion) return null;
|
||||
if (props.activeMessage.from === "reviewer" && (props.phase === "sender" || props.phase === "packet")) {
|
||||
return props.activeMessage.text;
|
||||
}
|
||||
if (props.activeMessage.to === "reviewer" && props.phase === "receiver") {
|
||||
return props.activeMessage.response;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="cyber-feature-rail-shell">
|
||||
<img
|
||||
class="cyber-feature-rail__collaboration"
|
||||
:src="heroCollaborationFeature.asset"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="cyber-feature-rail__reviewer"
|
||||
:class="{
|
||||
'cyber-feature-rail__reviewer--active': reviewerIsActive,
|
||||
'cyber-feature-rail__reviewer--sending': reviewerIsSender,
|
||||
'cyber-feature-rail__reviewer--receiving': reviewerIsReceiver,
|
||||
}"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Transition name="cyber-feature-bubble">
|
||||
<RobotSpeechBubble
|
||||
v-if="reviewerBubbleText"
|
||||
class="cyber-feature-rail__reviewer-bubble"
|
||||
tail="down"
|
||||
>
|
||||
{{ reviewerBubbleText }}
|
||||
</RobotSpeechBubble>
|
||||
</Transition>
|
||||
<div class="cyber-feature-rail__reviewer-card cyber-panel">
|
||||
<div class="cyber-feature-rail__reviewer-label">{{ localizedHeroReviewerFeatureCard.label }}</div>
|
||||
<ul class="cyber-feature-rail__reviewer-tasks">
|
||||
<li v-for="task in localizedHeroReviewerFeatureCard.tasks" :key="task">{{ task }}</li>
|
||||
</ul>
|
||||
<div class="cyber-feature-rail__reviewer-status">
|
||||
<span>{{ statusLabel }}</span>
|
||||
<strong>{{ localizedHeroReviewerFeatureCard.status }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
class="cyber-feature-rail__robot"
|
||||
:src="localizedHeroReviewerFeatureCard.asset"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
>
|
||||
</div>
|
||||
<div class="cyber-feature-rail cyber-panel">
|
||||
<div class="cyber-feature-rail">
|
||||
<div
|
||||
v-for="(feature, index) in localizedHeroFeatureRail"
|
||||
:key="feature.id"
|
||||
|
|
@ -107,6 +31,9 @@ const reviewerBubbleText = computed(() => {
|
|||
<div class="cyber-feature-rail__icon">
|
||||
<v-icon :icon="icons[index]" size="28" />
|
||||
</div>
|
||||
<div class="cyber-feature-rail__node">
|
||||
{{ (index + 1).toString().padStart(2, "0") }}
|
||||
</div>
|
||||
<div class="cyber-feature-rail__copy">
|
||||
<div class="cyber-feature-rail__title">{{ feature.title }}</div>
|
||||
<div class="cyber-feature-rail__text">{{ feature.text }}</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { mdiApple, mdiMicrosoftWindows, mdiPenguin, mdiDownload, mdiCheckCircle } from '@mdi/js';
|
||||
import robotAvatarSeatedMagenta from '~/assets/images/hero/robots/robot-avatar-seated-magenta-v1.webp';
|
||||
import { downloadAssets } from '~/data/downloads';
|
||||
import type { DownloadOs, DownloadArch } from '~/data/downloads';
|
||||
|
||||
const { content } = useLandingContent();
|
||||
|
|
@ -10,6 +9,7 @@ const downloadStore = useDownloadStore();
|
|||
const { data: releaseData, resolve } = useReleaseDownloads();
|
||||
const { trackDownloadClick } = useAnalytics();
|
||||
const { releaseDownloadUrl } = useGithubRepo();
|
||||
const { getDownloadArch, visibleDownloadAssets: visibleAssets } = useDownloadAssetPresentation();
|
||||
const isMounted = ref(false);
|
||||
const showLinuxRobotMessage = ref(false);
|
||||
const showFallingLinuxRobot = ref(false);
|
||||
|
|
@ -236,34 +236,10 @@ const platformColors: Record<string, string> = {
|
|||
linux: '#ffd700',
|
||||
};
|
||||
|
||||
const visibleAssets = computed(() => {
|
||||
const enriched = downloadAssets.map((asset) => {
|
||||
if (asset.os !== 'macos') return { ...asset };
|
||||
if (!downloadStore.isMacOs) return { ...asset };
|
||||
return {
|
||||
...asset,
|
||||
archLabel: downloadStore.macArch === 'arm64' ? 'Apple Silicon' : 'Intel',
|
||||
};
|
||||
});
|
||||
|
||||
// Reorder so detected OS is always in the center (index 1)
|
||||
const detectedIdx = enriched.findIndex((a) => a.id === downloadStore.selectedId);
|
||||
if (detectedIdx === -1 || detectedIdx === 1) return enriched;
|
||||
|
||||
const result = [...enriched];
|
||||
const [detected] = result.splice(detectedIdx, 1);
|
||||
const [first, ...rest] = result;
|
||||
return [first, detected, ...rest];
|
||||
});
|
||||
|
||||
const getDownloadUrl = (asset: { os: string; arch: string; fileName: string }) => {
|
||||
const getDownloadUrl = (asset: { os: DownloadOs; arch: DownloadArch; fileName: string }) => {
|
||||
if (!isMounted.value) return releaseDownloadUrl(asset.fileName);
|
||||
const arch = (asset.os === 'macos' ? downloadStore.macArch : asset.arch) as DownloadArch;
|
||||
return resolve(asset.os as DownloadOs, arch)?.url || releaseDownloadUrl(asset.fileName);
|
||||
};
|
||||
|
||||
const getDownloadArch = (asset: { os: string; arch: string }) => {
|
||||
return asset.os === 'macos' ? downloadStore.macArch : asset.arch;
|
||||
const arch = getDownloadArch(asset);
|
||||
return resolve(asset.os, arch)?.url || releaseDownloadUrl(asset.fileName);
|
||||
};
|
||||
|
||||
const releaseVersion = computed(() => releaseData.value?.version || null);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ let heroMotionQuery: MediaQueryList | null = null;
|
|||
const downloadStore = useDownloadStore();
|
||||
const { resolve, data: releaseData } = useReleaseDownloads();
|
||||
const { latestReleaseUrl, releaseDownloadUrl } = useGithubRepo();
|
||||
const { selectedDownloadAsset } = useDownloadAssetPresentation();
|
||||
const withBase = (path: string) => `${baseURL.replace(/\/?$/, "/")}${path.replace(/^\/+/, "")}`;
|
||||
|
||||
useCyberHeroParallax(heroRef);
|
||||
|
|
@ -71,6 +72,20 @@ const heroDownloadUrl = computed(() => {
|
|||
});
|
||||
|
||||
const docsHref = computed(() => withBase(locale.value === "ru" ? "docs/ru/" : "docs/"));
|
||||
const downloadActionSubtitle = computed(() => {
|
||||
if (!selectedDownloadAsset.value) {
|
||||
return locale.value === "ru"
|
||||
? "Для вашей платформы"
|
||||
: "For your platform";
|
||||
}
|
||||
|
||||
return selectedDownloadAsset.value.actionSubtitle;
|
||||
});
|
||||
const docsActionSubtitle = computed(() => (
|
||||
locale.value === "ru"
|
||||
? "Гайды и настройка"
|
||||
: "Guides and setup"
|
||||
));
|
||||
|
||||
function clearHeroMessageTimers() {
|
||||
heroMessageTimers.forEach(window.clearTimeout);
|
||||
|
|
@ -195,25 +210,23 @@ onUnmounted(() => {
|
|||
</div>
|
||||
|
||||
<div class="cyber-hero__actions">
|
||||
<v-btn
|
||||
variant="flat"
|
||||
size="large"
|
||||
<CyberHeroActionButton
|
||||
:href="heroDownloadUrl"
|
||||
target="_blank"
|
||||
class="cyber-hero__action cyber-hero__action--primary"
|
||||
:prepend-icon="mdiDownload"
|
||||
tone="primary"
|
||||
:icon="mdiDownload"
|
||||
:subtitle="downloadActionSubtitle"
|
||||
>
|
||||
{{ t("hero.downloadNow") }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
size="large"
|
||||
</CyberHeroActionButton>
|
||||
<CyberHeroActionButton
|
||||
:href="docsHref"
|
||||
class="cyber-hero__action cyber-hero__action--docs"
|
||||
:prepend-icon="mdiBookOpenPageVariantOutline"
|
||||
tone="secondary"
|
||||
:icon="mdiBookOpenPageVariantOutline"
|
||||
:subtitle="docsActionSubtitle"
|
||||
>
|
||||
{{ t("hero.ctaDocs") }}
|
||||
</v-btn>
|
||||
</CyberHeroActionButton>
|
||||
</div>
|
||||
|
||||
<p
|
||||
|
|
@ -239,9 +252,6 @@ onUnmounted(() => {
|
|||
|
||||
<CyberHeroFeatureStrip
|
||||
class="cyber-hero__feature-strip"
|
||||
:active-message="activeHeroMessage"
|
||||
:phase="heroMessagePhase"
|
||||
:reduced-motion="heroReducedMotion"
|
||||
/>
|
||||
</v-container>
|
||||
</section>
|
||||
|
|
|
|||
72
landing/composables/useDownloadAssetPresentation.ts
Normal file
72
landing/composables/useDownloadAssetPresentation.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { downloadAssets, type DownloadArch } from "~/data/downloads";
|
||||
|
||||
type DownloadAsset = (typeof downloadAssets)[number];
|
||||
type DownloadAssetLike = Pick<DownloadAsset, "id" | "os" | "arch" | "archLabel">;
|
||||
|
||||
export type PresentedDownloadAsset = DownloadAsset & {
|
||||
archLabel: string;
|
||||
actionSubtitle: string;
|
||||
resolvedArch: DownloadArch;
|
||||
};
|
||||
|
||||
export function useDownloadAssetPresentation() {
|
||||
const downloadStore = useDownloadStore();
|
||||
|
||||
const getDownloadArch = (asset: Pick<DownloadAsset, "os" | "arch">): DownloadArch => (
|
||||
asset.os === "macos" ? downloadStore.macArch : asset.arch
|
||||
);
|
||||
|
||||
const getDownloadArchLabel = (asset: DownloadAssetLike) => {
|
||||
if (asset.os === "macos" && downloadStore.isMacOs) {
|
||||
return downloadStore.macArch === "arm64" ? "Apple Silicon" : "Intel";
|
||||
}
|
||||
|
||||
return asset.archLabel;
|
||||
};
|
||||
|
||||
const getDownloadActionSubtitle = (asset: DownloadAssetLike) => {
|
||||
const archLabel = getDownloadArchLabel(asset);
|
||||
|
||||
if (asset.os === "macos") {
|
||||
const macArchLabel = archLabel === "Apple Silicon / Intel" ? "Apple Silicon & Intel" : archLabel;
|
||||
return `macOS 11+ · ${macArchLabel}`;
|
||||
}
|
||||
|
||||
if (asset.os === "windows") return `Windows 10+ · ${archLabel}`;
|
||||
|
||||
return `Linux · AppImage ${archLabel}`;
|
||||
};
|
||||
|
||||
const presentDownloadAsset = (asset: DownloadAsset): PresentedDownloadAsset => ({
|
||||
...asset,
|
||||
archLabel: getDownloadArchLabel(asset),
|
||||
actionSubtitle: getDownloadActionSubtitle(asset),
|
||||
resolvedArch: getDownloadArch(asset),
|
||||
});
|
||||
|
||||
const visibleDownloadAssets = computed(() => {
|
||||
const enriched = downloadAssets.map(presentDownloadAsset);
|
||||
|
||||
const detectedIdx = enriched.findIndex((asset) => asset.id === downloadStore.selectedId);
|
||||
if (detectedIdx === -1 || detectedIdx === 1) return enriched;
|
||||
|
||||
const result = [...enriched];
|
||||
const [detected] = result.splice(detectedIdx, 1);
|
||||
const [first, ...rest] = result;
|
||||
return [first, detected, ...rest];
|
||||
});
|
||||
|
||||
const selectedDownloadAsset = computed(() => {
|
||||
const asset = downloadStore.selectedAsset;
|
||||
return asset ? presentDownloadAsset(asset) : null;
|
||||
});
|
||||
|
||||
return {
|
||||
getDownloadActionSubtitle,
|
||||
getDownloadArch,
|
||||
getDownloadArchLabel,
|
||||
presentDownloadAsset,
|
||||
selectedDownloadAsset,
|
||||
visibleDownloadAssets,
|
||||
};
|
||||
}
|
||||
|
|
@ -193,7 +193,7 @@ export const heroFeatureRail = [
|
|||
{
|
||||
id: "local",
|
||||
title: "Your Machine, Your Code",
|
||||
text: "Local-first workflow with task logs, process control, and Git visibility.",
|
||||
text: "Track activity, logs, file changes, and what every agent is doing inside each task.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
|
|
@ -255,7 +255,7 @@ const ruHeroFeatureRail: Record<string, { title: string; text: string }> = {
|
|||
},
|
||||
local: {
|
||||
title: "Ваша машина, ваш код",
|
||||
text: "Локальный рабочий процесс с логами задач, управлением процессами и видимостью Git.",
|
||||
text: "Легко отслеживайте активность, логи, изменения файлов и работу каждого агента внутри каждой задачи.",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 643 KiB |
BIN
landing/docs/references/hero-button-reference.png
Normal file
BIN
landing/docs/references/hero-button-reference.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
Loading…
Reference in a new issue