Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
8d3f79cf
Commit
8d3f79cf
authored
Apr 03, 2026
by
陈曦
Browse files
继续修改首页、登录页、后台的所有设计风格
parent
b4412353
Changes
10
Show whitespace changes
Inline
Side-by-side
frontend/src/components/layout/AppHeader.vue
View file @
8d3f79cf
<
template
>
<header
class=
"
glass
sticky top-0 z-30 border-b border-gray-200/
5
0 dark:border-
dark-700/50
"
>
<header
class=
"
app-header
sticky top-0 z-30 border-b border-gray-200/
4
0 dark:border-
white/[0.05]
"
>
<div
class=
"flex h-16 items-center justify-between px-4 md:px-6"
>
<!-- Left: Mobile Menu Toggle + Page Title -->
<div
class=
"flex items-center gap-4"
>
...
...
@@ -335,4 +335,13 @@ onBeforeUnmount(() => {
opacity
:
0
;
transform
:
scale
(
0.95
)
translateY
(
-4px
);
}
.app-header
{
background
:
rgba
(
241
,
245
,
251
,
0.85
);
backdrop-filter
:
blur
(
20px
);
}
:global
(
.dark
)
.app-header
{
background
:
rgba
(
8
,
14
,
28
,
0.85
);
backdrop-filter
:
blur
(
20px
);
}
</
style
>
frontend/src/components/layout/AppLayout.vue
View file @
8d3f79cf
<
template
>
<div
class=
"min-h-screen
bg-gray-50 dark:bg-dark-950
"
>
<div
class=
"
app-root
min-h-screen"
>
<!-- Background Decoration -->
<div
class=
"pointer-events-none fixed inset-0
bg-mesh-gradient
"
></div>
<div
class=
"pointer-events-none fixed inset-0
app-bg-layer
"
></div>
<!-- Sidebar -->
<AppSidebar
/>
...
...
@@ -50,3 +50,25 @@ onMounted(() => {
defineExpose
({
replayTour
})
</
script
>
<
style
scoped
>
.app-root
{
background
:
#f1f5fb
;
}
:global
(
.dark
)
.app-root
{
background
:
#080e1c
;
}
.app-bg-layer
{
background
:
radial-gradient
(
at
15%
30%
,
rgba
(
99
,
102
,
241
,
0.06
)
0px
,
transparent
50%
),
radial-gradient
(
at
85%
10%
,
rgba
(
20
,
184
,
166
,
0.05
)
0px
,
transparent
50%
),
radial-gradient
(
at
60%
80%
,
rgba
(
139
,
92
,
246
,
0.04
)
0px
,
transparent
50%
);
}
:global
(
.dark
)
.app-bg-layer
{
background
:
radial-gradient
(
at
15%
30%
,
rgba
(
99
,
102
,
241
,
0.08
)
0px
,
transparent
50%
),
radial-gradient
(
at
85%
10%
,
rgba
(
20
,
184
,
166
,
0.06
)
0px
,
transparent
50%
),
radial-gradient
(
at
60%
80%
,
rgba
(
139
,
92
,
246
,
0.05
)
0px
,
transparent
50%
);
}
</
style
>
frontend/src/components/layout/AppSidebar.vue
View file @
8d3f79cf
...
...
@@ -60,7 +60,7 @@
<div
v-if=
"!sidebarCollapsed"
class=
"sidebar-section-title"
>
{{
t
(
'
nav.myAccount
'
)
}}
</div>
<div
v-else
class=
"mx-3 my-3 h-px
bg-gray-200 dark:bg-dark-700
"
></div>
<div
v-else
class=
"mx-3 my-3 h-px
"
style=
"background: rgba(255,255,255,0.06)
"
></div>
<router-link
v-for=
"item in personalNavItems"
...
...
@@ -105,7 +105,7 @@
</nav>
<!-- Bottom Section -->
<div
class=
"mt-auto
border-t border-gray-100 p-3 dark:border-dark-800
"
>
<div
class=
"mt-auto
p-3"
style=
"border-top: 1px solid rgba(255,255,255,0.05)
"
>
<!-- Theme Toggle -->
<button
@
click=
"toggleTheme"
...
...
frontend/src/components/layout/AuthLayout.vue
View file @
8d3f79cf
<
template
>
<div
class=
"auth-
root relative flex min-h-screen items-center justify-center overflow-hidden p-4
"
>
<div
class=
"auth-
split min-h-screen
"
>
<!-- Background -->
<!-- ══════════════════════════════════════════
LEFT PANEL — AI Visual (hidden on mobile)
══════════════════════════════════════════ -->
<div
class=
"auth-left hidden lg:flex"
>
<!-- Background layers -->
<div
class=
"pointer-events-none absolute inset-0 overflow-hidden"
>
<div
class=
"auth-orb auth-orb-tr"
></div>
<div
class=
"auth-orb auth-orb-bl"
></div>
<div
class=
"auth-grid"
></div>
<div
class=
"auth-scan"
></div>
<div
class=
"left-aurora left-aurora-1"
></div>
<div
class=
"left-aurora left-aurora-2"
></div>
</div>
<!-- Content -->
<div
class=
"relative z-10 w-full max-w-md"
>
<div
class=
"relative z-10 flex h-full flex-col px-12 py-10"
>
<!-- Brand -->
<div
class=
"
mb-8 text
-center"
>
<!--
Logo +
Brand -->
<div
class=
"
flex items
-center
gap-3
"
>
<template
v-if=
"settingsLoaded"
>
<!-- Logo -->
<div
class=
"auth-logo-wrap mb-5 inline-flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl"
>
<div
class=
"left-logo-wrap"
>
<img
:src=
"siteLogo || '/logo.png'"
alt=
"Logo"
class=
"h-full w-full object-contain"
/>
</div>
<div>
<div
class=
"text-base font-bold text-white"
>
{{
siteName
}}
</div>
<div
class=
"font-mono text-[10px] uppercase tracking-widest text-violet-400/60"
>
// AI Gateway
</div>
</div>
</
template
>
</div>
<!-- Center: Orbital AI Visualization -->
<div
class=
"flex flex-1 flex-col items-center justify-center"
>
<div
class=
"orbital-viz"
>
<!-- Hub -->
<div
class=
"hub-outer"
>
<div
class=
"hub-ring hub-ring-1"
></div>
<div
class=
"hub-ring hub-ring-2"
></div>
<div
class=
"hub-core"
>
<svg
class=
"h-8 w-8 text-white"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
stroke-width=
"1.5"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
d=
"M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"
/>
</svg>
</div>
</div>
<!-- Site name -->
<h1
class=
"auth-site-name mb-2 text-3xl font-black tracking-tight"
>
{{
siteName
}}
</h1>
<!-- Orbiting model chips -->
<div
class=
"orbit-chip chip-0"
>
<span
class=
"chip-dot"
style=
"background: #f97316"
></span>
<span>
Claude
</span>
</div>
<div
class=
"orbit-chip chip-1"
>
<span
class=
"chip-dot"
style=
"background: #22c55e"
></span>
<span>
GPT-4o
</span>
</div>
<div
class=
"orbit-chip chip-2"
>
<span
class=
"chip-dot"
style=
"background: #3b82f6"
></span>
<span>
Gemini
</span>
</div>
<div
class=
"orbit-chip chip-3"
>
<span
class=
"chip-dot"
style=
"background: #f43f5e"
></span>
<span>
DeepSeek
</span>
</div>
<div
class=
"orbit-chip chip-4"
>
<span
class=
"chip-dot"
style=
"background: #a855f7"
></span>
<span>
Llama
</span>
</div>
<!-- Subtitle -->
<p
class=
"auth-subtitle font-mono text-xs uppercase tracking-widest"
>
//
{{
siteSubtitle
}}
<!-- Connecting lines (SVG) -->
<svg
class=
"orbit-lines"
viewBox=
"0 0 340 340"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<!-- Lines from center (170,170) to chip positions -->
<line
x1=
"170"
y1=
"170"
x2=
"275"
y2=
"90"
stroke=
"rgba(139,92,246,0.18)"
stroke-width=
"1"
stroke-dasharray=
"4 4"
>
<animate
attributeName=
"stroke-dashoffset"
values=
"0;-16"
dur=
"1.2s"
repeatCount=
"indefinite"
/>
</line>
<line
x1=
"170"
y1=
"170"
x2=
"300"
y2=
"200"
stroke=
"rgba(20,184,166,0.18)"
stroke-width=
"1"
stroke-dasharray=
"4 4"
>
<animate
attributeName=
"stroke-dashoffset"
values=
"0;-16"
dur=
"1.6s"
repeatCount=
"indefinite"
/>
</line>
<line
x1=
"170"
y1=
"170"
x2=
"200"
y2=
"295"
stroke=
"rgba(59,130,246,0.18)"
stroke-width=
"1"
stroke-dasharray=
"4 4"
>
<animate
attributeName=
"stroke-dashoffset"
values=
"0;-16"
dur=
"2.0s"
repeatCount=
"indefinite"
/>
</line>
<line
x1=
"170"
y1=
"170"
x2=
"65"
y2=
"270"
stroke=
"rgba(244,63,94,0.18)"
stroke-width=
"1"
stroke-dasharray=
"4 4"
>
<animate
attributeName=
"stroke-dashoffset"
values=
"0;-16"
dur=
"1.4s"
repeatCount=
"indefinite"
/>
</line>
<line
x1=
"170"
y1=
"170"
x2=
"55"
y2=
"130"
stroke=
"rgba(168,85,247,0.18)"
stroke-width=
"1"
stroke-dasharray=
"4 4"
>
<animate
attributeName=
"stroke-dashoffset"
values=
"0;-16"
dur=
"1.8s"
repeatCount=
"indefinite"
/>
</line>
</svg>
</div>
<!-- Tagline -->
<div
class=
"mt-10 text-center"
>
<h2
class=
"mb-3 text-2xl font-bold text-white"
>
One Key.
<br>
Every Model.
</h2>
<p
class=
"max-w-xs text-sm leading-relaxed text-slate-400"
>
{{ siteSubtitle }}
</p>
</div>
</div>
<!-- Feature bullets -->
<div
class=
"space-y-4 pb-4"
>
<div
v-for=
"f in leftFeatures"
:key=
"f.text"
class=
"left-feature-row"
>
<div
class=
"left-feature-icon"
:style=
"{ background: f.iconBg }"
>
<component
:is=
"f.icon"
/>
</div>
<div>
<div
class=
"text-sm font-medium text-slate-200"
>
{{ f.text }}
</div>
<div
class=
"text-xs text-slate-500"
>
{{ f.desc }}
</div>
</div>
</div>
</div>
<!-- Copyright -->
<div
class=
"pt-6 font-mono text-[10px] text-slate-700"
>
©
{{ currentYear }} {{ siteName }}
</div>
</div>
</div>
<!-- ══════════════════════════════════════════
RIGHT PANEL — Form
══════════════════════════════════════════ -->
<div
class=
"auth-right"
>
<!-- Mobile: tiny logo strip -->
<div
class=
"mb-8 flex items-center gap-2.5 lg:hidden"
>
<
template
v-if=
"settingsLoaded"
>
<div
class=
"h-8 w-8 overflow-hidden rounded-lg shadow-glow"
>
<img
:src=
"siteLogo || '/logo.png'"
alt=
"Logo"
class=
"h-full w-full object-contain"
/>
</div>
<span
class=
"text-base font-bold text-gray-900 dark:text-white"
>
{{
siteName
}}
</span>
</
template
>
</div>
<!--
C
ard -->
<div
class=
"auth-
card rounded-2xl p-8
"
>
<!--
Form c
ard -->
<div
class=
"auth-
form-card
"
>
<slot
/>
</div>
<!-- Footer links -->
<div
class=
"mt-
6
text-center text-sm"
>
<div
class=
"mt-
5
text-center text-sm"
>
<slot
name=
"footer"
/>
</div>
<!-- Copyright -->
<div
class=
"mt-8 text-center font-mono text-xs text-slate-
6
00"
>
<!-- Copyright
(mobile only)
-->
<div
class=
"mt-8 text-center font-mono text-xs
text-gray-400 dark:
text-slate-
7
00
lg:hidden
"
>
©
{{ currentYear }} {{ siteName }}. All rights reserved.
</div>
</div>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
{
computed
,
onMounted
}
from
'
vue
'
import
{
computed
,
defineComponent
,
h
,
onMounted
}
from
'
vue
'
import
{
useAppStore
}
from
'
@/stores
'
import
{
sanitizeUrl
}
from
'
@/utils/url
'
...
...
@@ -61,117 +157,295 @@ const siteName = computed(() => appStore.siteName || 'TrafficAPI')
const
siteLogo
=
computed
(()
=>
sanitizeUrl
(
appStore
.
siteLogo
||
''
,
{
allowRelative
:
true
,
allowDataUrl
:
true
}))
const
siteSubtitle
=
computed
(()
=>
appStore
.
cachedPublicSettings
?.
site_subtitle
||
'
Intelligent AI Traffic Routing & Management
'
)
const
settingsLoaded
=
computed
(()
=>
appStore
.
publicSettingsLoaded
)
const
currentYear
=
computed
(()
=>
new
Date
().
getFullYear
())
// Inline icon components for feature bullets
const
ShieldIcon
=
defineComponent
({
render
:
()
=>
h
(
'
svg
'
,
{
class
:
'
h-4 w-4 text-white
'
,
fill
:
'
none
'
,
viewBox
:
'
0 0 24 24
'
,
stroke
:
'
currentColor
'
,
'
stroke-width
'
:
'
1.5
'
},
[
h
(
'
path
'
,
{
'
stroke-linecap
'
:
'
round
'
,
'
stroke-linejoin
'
:
'
round
'
,
d
:
'
M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z
'
})
])
})
const
BoltIcon
=
defineComponent
({
render
:
()
=>
h
(
'
svg
'
,
{
class
:
'
h-4 w-4 text-white
'
,
fill
:
'
none
'
,
viewBox
:
'
0 0 24 24
'
,
stroke
:
'
currentColor
'
,
'
stroke-width
'
:
'
1.5
'
},
[
h
(
'
path
'
,
{
'
stroke-linecap
'
:
'
round
'
,
'
stroke-linejoin
'
:
'
round
'
,
d
:
'
M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z
'
})
])
})
const
ChartIcon
=
defineComponent
({
render
:
()
=>
h
(
'
svg
'
,
{
class
:
'
h-4 w-4 text-white
'
,
fill
:
'
none
'
,
viewBox
:
'
0 0 24 24
'
,
stroke
:
'
currentColor
'
,
'
stroke-width
'
:
'
1.5
'
},
[
h
(
'
path
'
,
{
'
stroke-linecap
'
:
'
round
'
,
'
stroke-linejoin
'
:
'
round
'
,
d
:
'
M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z
'
})
])
})
const
leftFeatures
=
[
{
text
:
'
统一 API 接入点
'
,
desc
:
'
一个 Key 打通所有 AI 服务商,无需多套凭证
'
,
icon
:
ShieldIcon
,
iconBg
:
'
linear-gradient(135deg,#6366f1,#8b5cf6)
'
,
},
{
text
:
'
智能负载均衡
'
,
desc
:
'
自动分发流量,账户异常时即时切换,零中断
'
,
icon
:
BoltIcon
,
iconBg
:
'
linear-gradient(135deg,#14b8a6,#06b6d4)
'
,
},
{
text
:
'
实时流量分析
'
,
desc
:
'
请求量、延迟、费用全透明,按用户精细管控
'
,
icon
:
ChartIcon
,
iconBg
:
'
linear-gradient(135deg,#a855f7,#ec4899)
'
,
},
]
onMounted
(()
=>
{
appStore
.
fetchPublicSettings
()
})
</
script
>
<
style
scoped
>
/* ── Root ────────────────────────────────── */
.auth-root
{
background
:
#182a40
;
color
:
white
;
/* ══════════════════════════════════════════════
SPLIT LAYOUT
══════════════════════════════════════════════ */
.auth-split
{
display
:
flex
;
min-height
:
100vh
;
}
/* ── Background layers ───────────────────── */
.auth-orb
{
/* ── LEFT PANEL ────────────────────────────── */
.auth-left
{
position
:
relative
;
width
:
55%
;
max-width
:
600px
;
flex-shrink
:
0
;
background
:
#060c1a
;
overflow
:
hidden
;
flex-direction
:
column
;
}
/* Aurora blobs on left panel */
.left-aurora
{
position
:
absolute
;
border-radius
:
50%
;
filter
:
blur
(
9
0px
);
filter
:
blur
(
10
0px
);
pointer-events
:
none
;
}
.auth-orb-tr
{
top
:
-10%
;
right
:
-10%
;
width
:
420px
;
height
:
420px
;
background
:
radial-gradient
(
ellipse
,
rgba
(
20
,
184
,
166
,
0.18
)
0%
,
transparent
70%
);
.left-aurora-1
{
top
:
-15%
;
right
:
-15%
;
width
:
450px
;
height
:
450px
;
background
:
radial-gradient
(
ellipse
,
rgba
(
139
,
92
,
246
,
0.22
)
0%
,
transparent
65%
);
animation
:
la-drift-1
20s
ease-in-out
infinite
alternate
;
}
.
auth-orb-bl
{
.
left-aurora-2
{
bottom
:
-10%
;
left
:
-10%
;
width
:
380px
;
height
:
380px
;
background
:
radial-gradient
(
ellipse
,
rgba
(
6
,
182
,
212
,
0.13
)
0%
,
transparent
70%
);
width
:
350px
;
height
:
350px
;
background
:
radial-gradient
(
ellipse
,
rgba
(
20
,
184
,
166
,
0.18
)
0%
,
transparent
65%
);
animation
:
la-drift-2
24s
ease-in-out
infinite
alternate
;
}
@keyframes
la-drift-1
{
from
{
transform
:
translate
(
0
,
0
)
scale
(
1
);
}
to
{
transform
:
translate
(
-20px
,
20px
)
scale
(
1.06
);
}
}
@keyframes
la-drift-2
{
from
{
transform
:
translate
(
0
,
0
)
scale
(
1
);
}
to
{
transform
:
translate
(
20px
,
-20px
)
scale
(
1.04
);
}
}
.left-logo-wrap
{
width
:
36px
;
height
:
36px
;
border-radius
:
9px
;
overflow
:
hidden
;
box-shadow
:
0
0
0
1px
rgba
(
139
,
92
,
246
,
0.3
),
0
0
16px
rgba
(
139
,
92
,
246
,
0.15
);
flex-shrink
:
0
;
}
.auth-grid
{
/* ── ORBITAL VISUALIZATION ──────────────────── */
.orbital-viz
{
position
:
relative
;
width
:
340px
;
height
:
340px
;
}
/* Hub */
.hub-outer
{
position
:
absolute
;
inset
:
0
;
background-image
:
linear-gradient
(
rgba
(
20
,
184
,
166
,
0.07
)
1px
,
transparent
1px
),
linear-gradient
(
90deg
,
rgba
(
20
,
184
,
166
,
0.07
)
1px
,
transparent
1px
);
background-size
:
48px
48px
;
top
:
50%
;
left
:
50%
;
transform
:
translate
(
-50%
,
-50%
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
.hub-ring
{
position
:
absolute
;
border-radius
:
50%
;
border
:
1px
solid
rgba
(
139
,
92
,
246
,
0.15
);
animation
:
hub-pulse
3s
ease-in-out
infinite
;
}
.hub-ring-1
{
width
:
80px
;
height
:
80px
;
animation-delay
:
0s
;
}
.hub-ring-2
{
width
:
110px
;
height
:
110px
;
border-color
:
rgba
(
20
,
184
,
166
,
0.1
);
animation-delay
:
0.8s
;
}
.auth-scan
{
@keyframes
hub-pulse
{
0
%,
100
%
{
transform
:
scale
(
1
);
opacity
:
0.6
;
}
50
%
{
transform
:
scale
(
1.06
);
opacity
:
1
;
}
}
.hub-core
{
width
:
64px
;
height
:
64px
;
border-radius
:
16px
;
background
:
linear-gradient
(
135deg
,
#3730a3
,
#4f46e5
,
#7c3aed
);
box-shadow
:
0
0
32px
rgba
(
99
,
102
,
241
,
0.5
),
0
0
64px
rgba
(
99
,
102
,
241
,
0.2
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
position
:
relative
;
z-index
:
2
;
}
/* Orbit chips — positioned around center */
.orbit-chip
{
position
:
absolute
;
left
:
0
;
right
:
0
;
height
:
1px
;
background
:
linear-gradient
(
90deg
,
transparent
0%
,
rgba
(
20
,
184
,
166
,
0.45
)
30%
,
rgba
(
20
,
184
,
166
,
0.7
)
50%
,
rgba
(
20
,
184
,
166
,
0.45
)
70%
,
transparent
100%
);
animation
:
auth-scan
12s
linear
infinite
;
box-shadow
:
0
0
8px
rgba
(
20
,
184
,
166
,
0.35
);
}
@keyframes
auth-scan
{
0
%
{
top
:
0%
;
opacity
:
0
;
}
4
%
{
opacity
:
1
;
}
96
%
{
opacity
:
0.4
;
}
100
%
{
top
:
100%
;
opacity
:
0
;
}
}
/* ── Logo ────────────────────────────────── */
.auth-logo-wrap
{
box-shadow
:
0
0
0
1px
rgba
(
20
,
184
,
166
,
0.3
),
0
0
20px
rgba
(
20
,
184
,
166
,
0.18
),
0
8px
24px
rgba
(
0
,
0
,
0
,
0.4
);
}
/* ── Brand text ──────────────────────────── */
.auth-site-name
{
background
:
linear-gradient
(
135deg
,
#ffffff
0%
,
#7dd3c8
45%
,
#14b8a6
100%
);
-webkit-background-clip
:
text
;
-webkit-text-fill-color
:
transparent
;
background-clip
:
text
;
}
.auth-subtitle
{
color
:
rgba
(
20
,
184
,
166
,
0.5
);
}
/* ── Card ────────────────────────────────── */
.auth-card
{
background
:
rgba
(
26
,
46
,
72
,
0.72
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.09
);
backdrop-filter
:
blur
(
18px
);
box-shadow
:
0
0
0
1px
rgba
(
20
,
184
,
166
,
0.08
),
0
24px
60px
rgba
(
0
,
0
,
0
,
0.45
),
0
0
50px
rgba
(
20
,
184
,
166
,
0.05
);
}
/* ── Slot overrides: make child inputs/links readable ── */
display
:
flex
;
align-items
:
center
;
gap
:
6px
;
padding
:
5px
11px
5px
8px
;
border-radius
:
20px
;
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.08
);
background
:
rgba
(
15
,
22
,
40
,
0.85
);
backdrop-filter
:
blur
(
8px
);
font-size
:
11px
;
font-weight
:
500
;
color
:
#cbd5e1
;
white-space
:
nowrap
;
animation
:
chip-float
4s
ease-in-out
infinite
;
}
.chip-dot
{
width
:
7px
;
height
:
7px
;
border-radius
:
50%
;
flex-shrink
:
0
;
}
/* Position each chip around the orbital */
.chip-0
{
top
:
6%
;
left
:
62%
;
animation-delay
:
0.0s
;
}
.chip-1
{
top
:
44%
;
left
:
80%
;
animation-delay
:
0.6s
;
}
.chip-2
{
top
:
78%
;
left
:
52%
;
animation-delay
:
1.2s
;
}
.chip-3
{
top
:
72%
;
left
:
6%
;
animation-delay
:
1.8s
;
}
.chip-4
{
top
:
12%
;
left
:
2%
;
animation-delay
:
2.4s
;
}
@keyframes
chip-float
{
0
%,
100
%
{
transform
:
translateY
(
0
);
}
50
%
{
transform
:
translateY
(
-6px
);
}
}
/* SVG connecting lines */
.orbit-lines
{
position
:
absolute
;
inset
:
0
;
width
:
100%
;
height
:
100%
;
pointer-events
:
none
;
}
/* Feature rows */
.left-feature-row
{
display
:
flex
;
align-items
:
flex-start
;
gap
:
12px
;
}
.left-feature-icon
{
width
:
34px
;
height
:
34px
;
border-radius
:
8px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
flex-shrink
:
0
;
}
/* ── RIGHT PANEL ─────────────────────────── */
.auth-right
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
padding
:
40px
32px
;
background
:
#f8fafc
;
min-height
:
100vh
;
}
/* Dark mode right panel */
:global
(
.dark
)
.auth-right
{
background
:
#0f172a
;
}
.auth-form-card
{
width
:
100%
;
max-width
:
400px
;
background
:
white
;
border-radius
:
20px
;
border
:
1px
solid
rgba
(
0
,
0
,
0
,
0.06
);
box-shadow
:
0
4px
32px
rgba
(
0
,
0
,
0
,
0.08
),
0
1px
4px
rgba
(
0
,
0
,
0
,
0.04
);
padding
:
36px
32px
;
}
:global
(
.dark
)
.auth-form-card
{
background
:
#1e293b
;
border-color
:
rgba
(
255
,
255
,
255
,
0.08
);
box-shadow
:
0
4px
32px
rgba
(
0
,
0
,
0
,
0.4
);
}
/* Override slot content for right panel (light mode) */
:deep
(
h2
)
{
color
:
#0f172a
!important
;
}
:global
(
.dark
)
:deep
(
h2
)
{
color
:
white
!important
;
}
:deep
(
.input-label
)
{
color
:
#374151
;
}
:global
(
.dark
)
:deep
(
.input-label
)
{
color
:
#94a3b8
;
}
:deep
(
.input
)
{
background
:
#f9fafb
;
border-color
:
#e5e7eb
;
color
:
#0f172a
;
}
:global
(
.dark
)
:deep
(
.input
)
{
background
:
rgba
(
15
,
28
,
48
,
0.7
);
border-color
:
rgba
(
255
,
255
,
255
,
0.1
);
color
:
white
;
}
:deep
(
.input
:focus
)
{
border-color
:
rgba
(
20
,
184
,
166
,
0.5
)
;
box-shadow
:
0
0
0
3px
rgba
(
20
,
184
,
16
6
,
0.1
2
);
border-color
:
#8b5cf6
;
box-shadow
:
0
0
0
3px
rgba
(
139
,
92
,
24
6
,
0.1
5
);
}
:deep
(
.input
::placeholder
)
{
color
:
#
475569
;
color
:
#
9ca3af
;
}
:
deep
(
h2
)
{
color
:
white
!important
;
:
global
(
.dark
)
:deep
(
.input
::placeholder
)
{
color
:
#475569
;
}
:deep
(
.text-gray-500
),
:deep
(
.dark
\
:text-dark-400
)
{
color
:
#6b7280
!important
;
}
:global
(
.dark
)
:deep
(
.text-gray-500
),
:global
(
.dark
)
:deep
(
.dark
\
:text-dark-400
)
{
color
:
#64748b
!important
;
}
</
style
>
frontend/src/i18n/locales/en.ts
View file @
8d3f79cf
...
...
@@ -40,6 +40,13 @@ export default {
}
}
},
// Pipeline visualization labels
pipeline
:
{
gateway
:
'
Gateway
'
,
balancer
:
'
Balancer
'
,
aiPool
:
'
AI Pool
'
,
response
:
'
Response
'
,
},
// Solutions section
solutions
:
{
title
:
'
TrafficAPI Solves This
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
8d3f79cf
...
...
@@ -40,6 +40,13 @@ export default {
}
}
},
// 管道可视化标签
pipeline
:
{
gateway
:
'
网关
'
,
balancer
:
'
均衡器
'
,
aiPool
:
'
AI 池
'
,
response
:
'
响应
'
,
},
// 解决方案区块
solutions
:
{
title
:
'
TrafficAPI 为你解决
'
,
...
...
frontend/src/style.css
View file @
8d3f79cf
...
...
@@ -96,16 +96,26 @@
@apply
inline-flex
items-center
justify-center
gap-2;
@apply
rounded-xl
px-4
py-2.5
text-sm
font-medium;
@apply
transition-all
duration-200
ease-out;
@apply
focus
:
outline-none
focus
:
ring-2
focus
:
ring-primary-500
/
50
focus
:
ring-offset-2
;
@apply
focus
:
outline-none
;
@apply
disabled
:
transform-none
disabled
:
cursor-not-allowed
disabled
:
opacity-50
;
@apply
active
:
scale-
[
0.98
];
}
.btn-primary
{
@apply
bg-gradient-to-r
from-primary-500
to-primary-600;
@apply
text-white
shadow-md
shadow-primary-500/25;
@apply
hover
:
from-primary-600
hover
:
to-primary-700
hover
:
shadow-lg
hover
:
shadow-primary-500
/
30
;
@apply
dark
:
shadow-primary-500
/
20
;
background
:
linear-gradient
(
135deg
,
#7c3aed
,
#8b5cf6
);
color
:
white
;
box-shadow
:
0
4px
14px
rgba
(
139
,
92
,
246
,
0.35
);
}
.btn-primary
:hover
{
background
:
linear-gradient
(
135deg
,
#6d28d9
,
#7c3aed
);
box-shadow
:
0
6px
20px
rgba
(
139
,
92
,
246
,
0.45
);
transform
:
translateY
(
-1px
);
}
.btn-primary
:focus
{
outline
:
none
;
box-shadow
:
0
0
0
3px
rgba
(
139
,
92
,
246
,
0.35
),
0
4px
14px
rgba
(
139
,
92
,
246
,
0.3
);
}
.btn-secondary
{
...
...
@@ -165,7 +175,7 @@
@apply
text-gray-900
dark
:
text-gray-100
;
@apply
placeholder
:
text-gray-400
dark
:
placeholder
:
text-dark-400
;
@apply
transition-all
duration-200;
@apply
focus
:
border-primary-500
focus
:
outline-none
focus
:
ring-2
focus
:
ring-
primary-500
/
3
0
;
@apply
focus
:
outline-none
focus
:
border-
[
#8b5cf6
]
focus
:
ring-2
focus
:
ring-
[
#8b5cf6
]/
2
0
;
@apply
disabled
:
cursor-not-allowed
disabled
:
bg-gray-100
dark
:
disabled
:
bg-dark-900
;
}
...
...
@@ -522,11 +532,12 @@
@apply
border-l-primary-500;
}
/* ============ 侧边栏 ============ */
/* ============ 侧边栏
— 恒暗风格
============ */
.sidebar
{
@apply
fixed
inset-y-0
left-0
z-40;
@apply
w-64
bg-white
dark
:
bg-dark-900
;
@apply
border-r
border-gray-200
dark
:
border-dark-800
;
@apply
w-64;
background
:
#0c1220
;
border-right
:
1px
solid
rgba
(
139
,
92
,
246
,
0.08
);
@apply
flex
flex-col;
@apply
transition-transform
duration-300;
}
...
...
@@ -534,7 +545,7 @@
.sidebar-header
{
@apply
h-16
px-6;
@apply
flex
items-center
gap-3;
@apply
border-b
border-gray-100
dark
:
border-dark-800
;
border-bottom
:
1px
solid
rgba
(
255
,
255
,
255
,
0.05
)
;
}
.sidebar-nav
{
...
...
@@ -544,16 +555,26 @@
.sidebar-link
{
@apply
flex
items-center
gap-3
rounded-xl
px-3
py-2.5;
@apply
text-sm
font-medium;
@apply
text-gray-600
dark
:
text-dark-300
;
color
:
rgba
(
148
,
163
,
184
,
0.85
)
;
@apply
transition-all
duration-200;
@apply
hover
:
bg-gray-100
dark
:
hover
:
bg-dark-800
;
@apply
hover
:
text-gray-900
dark
:
hover
:
text-white
;
}
.sidebar-link
:hover
{
background
:
rgba
(
255
,
255
,
255
,
0.055
);
color
:
rgba
(
226
,
232
,
240
,
1
);
}
.sidebar-link-active
{
@apply
bg-primary-50
dark
:
bg-primary-900
/
20
;
@apply
text-primary-600
dark
:
text-primary-400
;
@apply
hover
:
bg-primary-100
dark
:
hover
:
bg-primary-900
/
30
;
background
:
linear-gradient
(
135deg
,
rgba
(
99
,
102
,
241
,
0.18
),
rgba
(
20
,
184
,
166
,
0.12
));
border-left
:
2px
solid
#14b8a6
;
padding-left
:
calc
(
0.75rem
-
2px
);
color
:
#5eead4
!important
;
box-shadow
:
inset
0
0
16px
rgba
(
20
,
184
,
166
,
0.04
);
}
.sidebar-link-active
:hover
{
background
:
linear-gradient
(
135deg
,
rgba
(
99
,
102
,
241
,
0.24
),
rgba
(
20
,
184
,
166
,
0.18
));
color
:
#5eead4
!important
;
}
.sidebar-section
{
...
...
@@ -563,7 +584,7 @@
.sidebar-section-title
{
@apply
mb-2
px-3;
@apply
text-xs
font-semibold
uppercase
tracking-wider;
@apply
text-gray-400
dark
:
text-dark-500
;
color
:
rgba
(
100
,
116
,
139
,
0.7
)
;
}
/* ============ 页面头部 ============ */
...
...
frontend/src/views/HomeView.vue
View file @
8d3f79cf
...
...
@@ -11,71 +11,55 @@
<div
v-else
v-html=
"homeContent"
></div>
</div>
<!-- Default Home Page —
Tech
Redesign -->
<!-- Default Home Page —
Aurora Bento
Redesign -->
<div
v-else
class=
"home-root relative flex min-h-screen flex-col overflow-hidden"
>
<!-- ══════════════════════════════════════════
BACKGROUND
AURORA
BACKGROUND
══════════════════════════════════════════ -->
<div
class=
"pointer-events-none absolute inset-0 overflow-hidden"
>
<!-- Glow orbs -->
<div
class=
"orb orb-tr"
></div>
<div
class=
"orb orb-bl"
></div>
<div
class=
"orb orb-center"
></div>
<!-- Grid -->
<div
class=
"grid-overlay"
></div>
<!-- Scan line -->
<div
class=
"scan-line"
></div>
<div
class=
"aurora aurora-violet"
></div>
<div
class=
"aurora aurora-teal"
></div>
<div
class=
"aurora aurora-indigo"
></div>
<!-- Subtle noise texture overlay -->
<div
class=
"noise-overlay"
></div>
</div>
<!-- ══════════════════════════════════════════
HEADER
══════════════════════════════════════════ -->
<header
class=
"home-header relative z-20
px-6 py-4
"
>
<nav
class=
"mx-auto flex max-w-
6
xl items-center justify-between"
>
<header
class=
"home-header relative z-20"
>
<nav
class=
"mx-auto flex max-w-
7
xl items-center justify-between
px-6 py-4
"
>
<!-- Logo + Brand -->
<div
class=
"flex items-center gap-3"
>
<div
class=
"logo-
ring h-9 w-9 overflow-hidden rounded-lg
"
>
<div
class=
"logo-
badge
"
>
<img
:src=
"siteLogo || '/logo.png'"
alt=
"Logo"
class=
"h-full w-full object-contain"
/>
</div>
<span
class=
"hidden font-mono text-sm font-semibold uppercase tracking-widest text-white/70 sm:block"
>
{{
siteName
}}
</span>
<span
class=
"hidden font-semibold text-white/80 sm:block"
>
{{
siteName
}}
</span>
</div>
<!-- Actions -->
<div
class=
"flex items-center gap-2"
>
<LocaleSwitcher
/>
<a
v-if=
"docUrl"
:href=
"docUrl"
target=
"_blank"
rel=
"noopener noreferrer"
class=
"nav-btn"
:title=
"t('home.viewDocs')"
class=
"nav-pill"
>
<Icon
name=
"book"
size=
"sm"
class=
"mr-1"
/>
<Icon
name=
"book"
size=
"sm"
class=
"mr-1
.5
"
/>
<span
class=
"hidden sm:inline"
>
{{
t
(
'
home.docs
'
)
}}
</span>
</a>
<button
@
click=
"toggleTheme"
class=
"nav-btn icon-only"
:title=
"isDark ? t('home.switchToLight') : t('home.switchToDark')"
>
<button
@
click=
"toggleTheme"
class=
"nav-icon-btn"
>
<Icon
v-if=
"isDark"
name=
"sun"
size=
"sm"
/>
<Icon
v-else
name=
"moon"
size=
"sm"
/>
</button>
<router-link
v-if=
"isAuthenticated"
:to=
"dashboardPath"
class=
"cta-nav-btn"
>
<span
class=
"user-dot"
>
{{
userInitial
}}
</span>
<router-link
v-if=
"isAuthenticated"
:to=
"dashboardPath"
class=
"cta-header-btn"
>
<span
class=
"avatar-dot"
>
{{
userInitial
}}
</span>
{{
t
(
'
home.dashboard
'
)
}}
<svg
class=
"h-3 w-3 opacity-60"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
stroke-width=
"2.5"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
d=
"M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25"
/>
</svg>
</router-link>
<router-link
v-else
to=
"/login"
class=
"cta-
nav
-btn"
>
<router-link
v-else
to=
"/login"
class=
"cta-
header
-btn"
>
{{
t
(
'
home.login
'
)
}}
</router-link>
</div>
...
...
@@ -85,184 +69,252 @@
<!-- ══════════════════════════════════════════
MAIN
══════════════════════════════════════════ -->
<main
class=
"relative z-10 flex-1
px-6
"
>
<main
class=
"relative z-10 flex-1"
>
<!-- ── HERO ────────────────────────────── -->
<section
class=
"mx-auto max-w-6xl py-20 lg:py-28"
>
<div
class=
"flex flex-col items-center gap-14 lg:flex-row lg:gap-20"
>
<!-- ── HERO (Centered) ─────────────────── -->
<section
class=
"mx-auto max-w-5xl px-6 pb-8 pt-20 text-center lg:pt-28"
>
<!-- Text side -->
<div
class=
"flex-1 text-center lg:text-left"
>
<!-- Badge -->
<div
class=
"mb-7 inline-flex items-center gap-2.5 rounded-full border border-teal-500/25 bg-teal-500/8 px-4 py-1.5 font-mono text-xs font-medium text-teal-400"
>
<span
class=
"h-1.5 w-1.5 animate-pulse rounded-full bg-teal-400 shadow-[0_0_6px_#14b8a6]"
></span>
AI API GATEWAY · ENTERPRISE GRADE
<div
class=
"mb-8 inline-flex items-center gap-2 rounded-full border border-violet-500/20 bg-violet-500/8 px-4 py-1.5"
>
<span
class=
"h-1.5 w-1.5 animate-pulse rounded-full bg-violet-400 shadow-[0_0_6px_#a78bfa]"
></span>
<span
class=
"font-mono text-[11px] font-medium uppercase tracking-widest text-violet-300"
>
AI API Gateway · Next Generation
</span>
</div>
<!-- Headline -->
<h1
class=
"
mb-5 font-black l
eadin
g-[1.08] tracking-tight
"
>
<span
class=
"gradient-text block whitespace-nowrap text-[clamp(1.6rem,4.5vw,3.75rem)]"
>
{{
t
(
'
home.heroSubtitle
'
)
}}
</span>
<h1
class=
"
hero-h
ead
l
in
e mb-6
"
>
{{
t
(
'
home.heroSubtitle
'
)
}}
</h1>
<!-- Mono accent line -->
<p
class=
"mb-5 font-mono text-[11px] uppercase tracking-[0.2em] text-teal-600/60"
>
//
{{
siteName
}}
· intelligent traffic layer
</p>
<p
class=
"mb-10 max-w-lg text-base leading-relaxed text-slate-400 lg:text-lg"
>
<!-- Subline -->
<p
class=
"mx-auto mb-10 max-w-2xl text-lg leading-relaxed text-slate-400"
>
{{
t
(
'
home.heroDescription
'
)
}}
</p>
<!-- CTA
buttons
-->
<div
class=
"flex flex-wrap items-center justify-center gap-4
lg:justify-start
"
>
<!-- CTA -->
<div
class=
"
mb-16
flex flex-wrap items-center justify-center gap-4"
>
<router-link
:to=
"isAuthenticated ? dashboardPath : '/login'"
class=
"cta-primary
inline-flex items-center gap-2 rounded-lg px-7 py-3 text-sm font-semibold text-white
"
class=
"cta-primary
-btn
"
>
{{
isAuthenticated
?
t
(
'
home.goToDashboard
'
)
:
t
(
'
home.getStarted
'
)
}}
<Icon
name=
"arrowRight"
size=
"sm"
:stroke-width=
"2.5"
/>
<svg
class=
"h-4 w-4"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
stroke-width=
"2.5"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
d=
"M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25"
/>
</svg>
</router-link>
<a
v-if=
"docUrl"
:href=
"docUrl"
target=
"_blank"
rel=
"noopener noreferrer"
class=
"
inline-flex items-center gap-2 rounded-lg border border-white/10 px-7 py-3 text-sm font-medium text-slate-300 transition-all hover:border-teal-500/30 hover:text-teal-300
"
class=
"
cta-ghost-btn
"
>
<Icon
name=
"book"
size=
"sm"
/>
{{
t
(
'
home.viewDocs
'
)
}}
</a>
</div>
<!-- Stats -->
<div
class=
"mt-12 flex flex-wrap items-center justify-center gap-8 lg:justify-start"
>
<div
v-for=
"s in stats"
:key=
"s.label"
class=
"stat-item"
>
<span
class=
"stat-value"
>
{{
s
.
value
}}
</span>
<span
class=
"stat-label"
>
{{
s
.
label
}}
</span>
</div>
<!-- ── Pipeline Visualization ─────────── -->
<div
class=
"pipeline-wrapper"
>
<div
class=
"pipeline-glow"
></div>
<div
class=
"pipeline-card"
>
<div
class=
"pipeline-inner"
>
<!-- Step: Client -->
<div
class=
"pipe-node"
>
<div
class=
"pipe-icon pipe-icon-client"
>
<svg
class=
"h-5 w-5"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
stroke-width=
"1.5"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
d=
"M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25m18 0A2.25 2.25 0 0018.75 3H5.25A2.25 2.25 0 003 5.25m18 0H3"
/>
</svg>
</div>
<span
class=
"pipe-label"
>
Client
</span>
</div>
<!-- Terminal side -->
<div
class=
"flex flex-1 justify-center lg:justify-end"
>
<div
class=
"terminal-wrap"
>
<div
class=
"terminal-glow"
></div>
<div
class=
"terminal-window"
>
<div
class=
"pipe-connector"
>
<div
class=
"pipe-line"
></div>
<div
class=
"pipe-dot pipe-dot-1"
></div>
</div>
<!--
Header bar
-->
<div
class=
"
terminal-hea
de
r
"
>
<div
class=
"
terminal-dots
"
>
<span
class=
"
dot-red"
></span
>
<
s
pa
n
class=
"dot-yellow"
></span
>
<span
class=
"dot-green"
></span
>
<!--
Step: Gateway
-->
<div
class=
"
pipe-no
de"
>
<div
class=
"
pipe-icon pipe-icon-gateway
"
>
<svg
class=
"
h-5 w-5"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
stroke-width=
"1.5"
>
<pa
th
stroke-linecap=
"round"
stroke-linejoin=
"round"
d=
"M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z"
/
>
</svg
>
</div>
<span
class=
"terminal-title"
>
trafficapi · gateway
</span>
<span
class=
"terminal-live"
>
<span
class=
"h-1.5 w-1.5 animate-pulse rounded-full bg-teal-400 inline-block shadow-[0_0_5px_#14b8a6]"
></span>
LIVE
</span>
<span
class=
"pipe-label"
>
{{
t
(
'
home.pipeline.gateway
'
)
}}
</span>
</div>
<!-- Code body -->
<div
class=
"terminal-body"
>
<div
class=
"t-line line-1"
>
<span
class=
"t-prompt"
>
→
</span>
<span
class=
"t-method"
>
POST
</span>
<span
class=
"t-path"
>
/v1/messages
</span>
<span
class=
"t-dim ml-auto"
>
claude-3-5-sonnet
</span>
<div
class=
"pipe-connector"
>
<div
class=
"pipe-line"
></div>
<div
class=
"pipe-dot pipe-dot-2"
></div>
</div>
<div
class=
"t-line line-2"
>
<span
class=
"t-comment"
>
⠿ routing to pool · 3 accounts available
</span>
<!-- Step: Balancer -->
<div
class=
"pipe-node"
>
<div
class=
"pipe-icon pipe-icon-balancer"
>
<svg
class=
"h-5 w-5"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
stroke-width=
"1.5"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
d=
"M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5"
/>
</svg>
</div>
<div
class=
"t-line line-3"
>
<span
class=
"t-ok"
>
✓ 200
</span>
<span
class=
"t-ms"
>
38ms
</span>
<span
class=
"t-dim"
>
· account[1] selected
</span>
<span
class=
"pipe-label"
>
{{
t
(
'
home.pipeline.balancer
'
)
}}
</span>
</div>
<div
class=
"t-sep sep-1"
></div>
<div
class=
"t-line line-4"
>
<span
class=
"t-prompt"
>
→
</span>
<span
class=
"t-method"
>
POST
</span>
<span
class=
"t-path"
>
/v1/chat/completions
</span>
<span
class=
"t-dim ml-auto"
>
gpt-4o
</span>
<div
class=
"pipe-connector"
>
<div
class=
"pipe-line"
></div>
<div
class=
"pipe-dot pipe-dot-3"
></div>
</div>
<div
class=
"t-line line-5"
>
<span
class=
"t-comment"
>
⠿ load balancing · 2 upstream accounts
</span>
<!-- Step: AI Pool -->
<div
class=
"pipe-node"
>
<div
class=
"pipe-icon pipe-icon-ai"
>
<svg
class=
"h-5 w-5"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
stroke-width=
"1.5"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
d=
"M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"
/>
</svg>
</div>
<div
class=
"t-line line-6"
>
<span
class=
"t-ok"
>
✓ 200
</span>
<span
class=
"t-ms"
>
57ms
</span>
<span
class=
"t-dim"
>
· account[3] selected
</span>
<span
class=
"pipe-label"
>
{{
t
(
'
home.pipeline.aiPool
'
)
}}
</span>
</div>
<div
class=
"t-sep sep-2"
></div>
<div
class=
"pipe-connector"
>
<div
class=
"pipe-line"
></div>
<div
class=
"pipe-dot pipe-dot-4"
></div>
</div>
<div
class=
"t-line line-7"
>
<span
class=
"t-stat"
>
📊 analytics
</span>
<span
class=
"t-dim"
>
req: 1.4k/min · p99: 68ms · err: 0%
</span>
<!-- Step: Response -->
<div
class=
"pipe-node"
>
<div
class=
"pipe-icon pipe-icon-response"
>
<svg
class=
"h-5 w-5"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
stroke-width=
"1.5"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
d=
"M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
<div
class=
"t-line line-8"
>
<span
class=
"t-prompt"
>
_
</span>
<span
class=
"t-cursor"
></span>
<span
class=
"pipe-label"
>
{{
t
(
'
home.pipeline.response
'
)
}}
</span>
</div>
</div>
<!-- Pipeline metrics bar -->
<div
class=
"pipe-metrics"
>
<div
v-for=
"m in pipeMetrics"
:key=
"m.label"
class=
"pipe-metric-item"
>
<span
class=
"pipe-metric-val"
>
{{
m
.
value
}}
</span>
<span
class=
"pipe-metric-label"
>
{{
m
.
label
}}
</span>
</div>
</div>
</div>
</div>
</section>
<!-- ── TAGS BAR ─────────────────────────── -->
<section
class=
"mx-auto max-w-6xl pb-14"
>
<div
class=
"flex flex-wrap items-center justify-center gap-3"
>
<div
v-for=
"tag in featureTags"
:key=
"tag.text"
class=
"tag-pill"
>
<span
class=
"tag-pip"
></span>
{{
tag
.
text
}}
</div>
<!-- ── BENTO FEATURES ─────────────────── -->
<section
class=
"mx-auto max-w-7xl px-6 pb-20"
>
<div
class=
"mb-12 text-center"
>
<h2
class=
"mb-3 text-3xl font-bold text-white"
>
<span
class=
"bento-title-gradient"
>
{{
t
(
'
home.solutions.title
'
)
}}
</span>
</h2>
<p
class=
"text-sm font-mono uppercase tracking-widest text-slate-600"
>
{{
t
(
'
home.solutions.subtitle
'
)
}}
</p>
</div>
</section>
<!-- ── FEATURES ─────────────────────────── -->
<section
class=
"mx-auto max-w-6xl pb-16"
>
<div
class=
"mb-10 text-center"
>
<h2
class=
"mb-2 text-2xl font-bold text-white md:text-3xl"
>
<span
class=
"gradient-text"
>
{{
t
(
'
home.solutions.title
'
)
}}
</span>
</h2>
<p
class=
"font-mono text-xs uppercase tracking-widest text-slate-600"
>
//
{{
t
(
'
home.solutions.subtitle
'
)
}}
<div
class=
"bento-grid"
>
<!-- LARGE CARD: Unified Gateway -->
<div
class=
"bento-card bento-large group"
>
<div
class=
"bento-accent-bar"
style=
"background: linear-gradient(180deg, #3b82f6, #6366f1)"
></div>
<div
class=
"flex h-full flex-col"
>
<div
class=
"bento-icon-wrap mb-5"
style=
"background: linear-gradient(135deg, #3b82f6, #6366f1); box-shadow: 0 8px 24px rgba(99,102,241,0.3)"
>
<component
:is=
"IconServer"
/>
</div>
<h3
class=
"mb-3 text-xl font-semibold text-white"
>
{{
t
(
`home.features.unifiedGateway`
)
}}
</h3>
<p
class=
"mb-6 text-sm leading-relaxed text-slate-500 group-hover:text-slate-400"
>
{{
t
(
`home.features.unifiedGatewayDesc`
)
}}
</p>
<!-- Code snippet decoration -->
<div
class=
"mt-auto rounded-lg border border-white/5 bg-black/30 p-3 font-mono text-xs"
>
<span
class=
"text-slate-600"
>
POST
</span>
<span
class=
"text-blue-400"
>
/v1/chat/completions
</span>
<br>
<span
class=
"text-slate-600"
>
Authorization:
</span>
<span
class=
"text-emerald-400"
>
Bearer sk-****
</span>
<br>
<span
class=
"text-violet-400"
>
→
</span>
<span
class=
"text-teal-400"
>
auto-routed to best provider
</span>
</div>
</div>
</div>
<div
class=
"grid gap-4 md:grid-cols-3"
>
<div
v-for=
"feat in features"
:key=
"feat.key"
class=
"feat-card group"
>
<div
class=
"feat-accent"
:style=
"
{ background: feat.color }">
</div>
<div
class=
"feat-corner-tr"
></div>
<div
class=
"feat-corner-bl"
></div>
<!-- REGULAR CARD: Load Balancing -->
<div
class=
"bento-card bento-regular group"
>
<div
class=
"bento-accent-bar"
style=
"background: linear-gradient(180deg, #14b8a6, #06b6d4)"
></div>
<div
class=
"bento-icon-wrap mb-4"
style=
"background: linear-gradient(135deg, #14b8a6, #0d9488); box-shadow: 0 8px 24px rgba(20,184,166,0.3)"
>
<component
:is=
"IconRoute"
/>
</div>
<h3
class=
"mb-2 text-lg font-semibold text-white"
>
{{
t
(
`home.features.multiAccount`
)
}}
</h3>
<p
class=
"text-sm leading-relaxed text-slate-500 group-hover:text-slate-400"
>
{{
t
(
`home.features.multiAccountDesc`
)
}}
</p>
<!-- Mini balance visualization -->
<div
class=
"mt-4 space-y-2"
>
<div
v-for=
"(bar, i) in loadBars"
:key=
"i"
class=
"flex items-center gap-2"
>
<span
class=
"w-14 font-mono text-[10px] text-slate-600"
>
{{
bar
.
label
}}
</span>
<div
class=
"h-1.5 flex-1 overflow-hidden rounded-full bg-white/5"
>
<div
class=
"h-full rounded-full transition-all"
:style=
"
{ width: bar.width, background: bar.color }">
</div>
</div>
<span
class=
"w-8 text-right font-mono text-[10px] text-teal-500"
>
{{
bar
.
pct
}}
</span>
</div>
</div>
</div>
<div
class=
"feat-icon mb-5"
:style=
"
{ background: feat.iconBg, boxShadow: `0 4px 20px ${feat.glow}` }"
>
<component
:is=
"feat.iconComponent"
/>
<!-- STATS CARD -->
<div
class=
"bento-card bento-stats"
>
<div
class=
"flex h-full flex-col justify-between"
>
<div
class=
"mb-2 font-mono text-[10px] uppercase tracking-wider text-slate-600"
>
// Live Stats
</div>
<div
class=
"space-y-4"
>
<div
v-for=
"s in stats"
:key=
"s.label"
class=
"stat-row"
>
<span
class=
"stat-big-value"
>
{{
s
.
value
}}
</span>
<span
class=
"stat-big-label"
>
{{
s
.
label
}}
</span>
</div>
</div>
<div
class=
"mt-4 flex items-center gap-1.5"
>
<span
class=
"h-1.5 w-1.5 animate-pulse rounded-full bg-emerald-400 shadow-[0_0_6px_#34d399]"
></span>
<span
class=
"font-mono text-[10px] text-emerald-500"
>
99.9% SLA Uptime
</span>
</div>
</div>
</div>
<h3
class=
"mb-2 font-semibold text-white"
>
{{
t
(
`home.features.${feat.key
}
`
)
}}
<
/h3
>
<
p
class
=
"
text-sm leading-relaxed text-slate-500 transition-colors group-hover:text-slate-400
"
>
{{
t
(
`home.features.${feat.key
}
Desc`
)
}}
<!-- WIDE CARD: Analytics -->
<div
class=
"bento-card bento-wide group"
>
<div
class=
"bento-accent-bar"
style=
"background: linear-gradient(180deg, #a855f7, #7c3aed)"
></div>
<div
class=
"flex h-full flex-col"
>
<div
class=
"mb-4 flex items-start justify-between"
>
<div>
<div
class=
"bento-icon-wrap mb-3"
style=
"background: linear-gradient(135deg, #a855f7, #7c3aed); box-shadow: 0 8px 24px rgba(168,85,247,0.3); width: 40px; height: 40px"
>
<component
:is=
"IconChart"
/>
</div>
<h3
class=
"text-lg font-semibold text-white"
>
{{
t
(
`home.features.balanceQuota`
)
}}
</h3>
</div>
<div
class=
"flex gap-1.5"
>
<div
v-for=
"tag in featureTags"
:key=
"tag.text"
class=
"mini-tag"
>
{{
tag
.
text
}}
</div>
</div>
</div>
<p
class=
"text-sm leading-relaxed text-slate-500 group-hover:text-slate-400"
>
{{
t
(
`home.features.balanceQuotaDesc`
)
}}
</p>
<!-- Sparkline decoration -->
<div
class=
"mt-4 flex items-end gap-1"
>
<div
v-for=
"(h, i) in sparkline"
:key=
"i"
class=
"sparkbar"
:style=
"
{ height: h + 'px', animationDelay: (i * 0.08) + 's' }"
>
</div>
</div>
</div>
</div>
</div>
</section>
<!--
──
PROVIDERS
──────────
────────────────
-->
<
section
class
=
"
mx-auto max-w-
6
xl pb-20
"
>
<!-- ── PROVIDERS
MARQUEE
──────────────── -->
<section
class=
"mx-auto max-w-
7
xl
px-6
pb-20"
>
<div
class=
"mb-8 text-center"
>
<h2
class=
"mb-2 text-xl font-bold text-white"
>
{{
t
(
'
home.providers.title
'
)
}}
</h2>
<p
class=
"font-mono text-xs uppercase tracking-[0.2em] text-slate-600"
>
...
...
@@ -270,24 +322,24 @@
</p>
</div>
<
div
class
=
"
flex flex-wrap items-center justify-center gap-3
"
>
<div
class=
"marquee-container"
>
<div
class=
"marquee-fade-left"
></div>
<div
class=
"marquee-fade-right"
></div>
<div
class=
"marquee-track"
>
<div
class=
"marquee-inner"
>
<div
v
-
for
=
"
p in providerList
"
:
key
=
"
p.name
"
class
=
"
provider-c
ard
"
:
class
=
"
{
'provider-
active': p.
live
,
'provider-
inactive': !p.live
}
"
v-for=
"p in
[...
providerList
, ...providerList]
"
:key=
"p.name
+ Math.random()
"
class=
"provider-c
hip
"
:class=
"
p.live ?
'provider-
chip-
live
' :
'provider-
chip-dim'
"
>
<
span
class
=
"
provider-status-dot
"
:
class
=
"
p.live ? 'dot-live' : 'dot-offline'
"
><
/span
>
<
div
class
=
"
provider-icon-badge
"
:
style
=
"
{ background: p.gradient
}
"
>
<
span
class
=
"
text-[11px] font-bold text-white
"
>
{{
p
.
letter
}}
<
/span
>
<
/div
>
<
span
class
=
"
text-sm font-medium text-slate-300
"
>
{{
p
.
name
}}
<
/span
>
<
span
class
=
"
provider-tag
"
:
class
=
"
p.live ? 'tag-live' : 'tag-soon'
"
>
{{
p
.
live
?
t
(
'
home.providers.supported
'
)
:
t
(
'
home.providers.soon
'
)
}}
<
/span
>
<div
class=
"provider-badge"
:style=
"
{ background: p.gradient }">
<span
class=
"text-[10px] font-bold text-white"
>
{{
p
.
letter
}}
</span>
</div>
<span
class=
"text-sm font-medium"
>
{{
p
.
name
}}
</span>
<span
v-if=
"p.live"
class=
"provider-live-dot"
></span>
</div>
</div>
</div>
</div>
</section>
...
...
@@ -297,29 +349,16 @@
<!-- ══════════════════════════════════════════
FOOTER
══════════════════════════════════════════ -->
<
footer
class
=
"
home-footer relative z-10
px-6 py-6
"
>
<
div
class
=
"
mx-auto flex max-w-
6
xl flex-col items-center justify-between gap-3 text-center sm:flex-row sm:text-left
"
>
<footer
class=
"home-footer relative z-10"
>
<div
class=
"mx-auto flex max-w-
7
xl flex-col items-center justify-between gap-3
px-6 py-6
text-center sm:flex-row sm:text-left"
>
<p
class=
"font-mono text-xs text-slate-700"
>
©
{{
currentYear
}}
{{
siteName
}}
·
{{
t
(
'
home.footer.allRightsReserved
'
)
}}
</p>
<div
class=
"flex items-center gap-5"
>
<
a
v
-
if
=
"
docUrl
"
:
href
=
"
docUrl
"
target
=
"
_blank
"
rel
=
"
noopener noreferrer
"
class
=
"
font-mono text-xs text-slate-700 transition-colors hover:text-teal-400
"
>
/doc
s
<
/a
>
<
a
:
href
=
"
githubUrl
"
target
=
"
_blank
"
rel
=
"
noopener noreferrer
"
class
=
"
font-mono text-xs text-slate-700 transition-colors hover:text-teal-400
"
>
/githu
b
<
/a
>
<a
v-if=
"docUrl"
:href=
"docUrl"
target=
"_blank"
rel=
"noopener noreferrer"
class=
"font-mono text-xs text-slate-700 transition-colors hover:text-teal-400"
>
/docs
</a>
<a
:href=
"githubUrl"
target=
"_blank"
rel=
"noopener noreferrer"
class=
"font-mono text-xs text-slate-700 transition-colors hover:text-teal-400"
>
/github
</a>
</div>
</div>
</footer>
...
...
@@ -338,7 +377,6 @@ const { t } = useI18n()
const
authStore
=
useAuthStore
()
const
appStore
=
useAppStore
()
// Site settings
const
siteName
=
computed
(()
=>
appStore
.
cachedPublicSettings
?.
site_name
||
appStore
.
siteName
||
'
TrafficAPI
'
)
const
siteLogo
=
computed
(()
=>
appStore
.
cachedPublicSettings
?.
site_logo
||
appStore
.
siteLogo
||
''
)
const
docUrl
=
computed
(()
=>
appStore
.
cachedPublicSettings
?.
doc_url
||
appStore
.
docUrl
||
''
)
...
...
@@ -349,13 +387,8 @@ const isHomeContentUrl = computed(() => {
return
content
.
startsWith
(
'
http://
'
)
||
content
.
startsWith
(
'
https://
'
)
})
// Theme
const
isDark
=
ref
(
document
.
documentElement
.
classList
.
contains
(
'
dark
'
))
// GitHub
const
githubUrl
=
'
https://github.com/Wei-Shaw/sub2api
'
// Auth
const
isAuthenticated
=
computed
(()
=>
authStore
.
isAuthenticated
)
const
isAdmin
=
computed
(()
=>
authStore
.
isAdmin
)
const
dashboardPath
=
computed
(()
=>
isAdmin
.
value
?
'
/admin/dashboard
'
:
'
/dashboard
'
)
...
...
@@ -364,24 +397,35 @@ const userInitial = computed(() => {
if
(
!
user
?.
email
)
return
''
return
user
.
email
.
charAt
(
0
).
toUpperCase
()
})
const
currentYear
=
computed
(()
=>
new
Date
().
getFullYear
())
// Stats
const
stats
=
[
{
value
:
'
3+
'
,
label
:
'
AI Providers
'
},
{
value
:
'
99.9%
'
,
label
:
'
SLA Uptime
'
},
{
value
:
'
1
'
,
label
:
'
API Key
'
},
]
// Feature tags
const
pipeMetrics
=
[
{
value
:
'
< 50ms
'
,
label
:
'
p99 Latency
'
},
{
value
:
'
0%
'
,
label
:
'
Error Rate
'
},
{
value
:
'
1.4k/m
'
,
label
:
'
Req/min
'
},
]
const
loadBars
=
[
{
label
:
'
acct[0]
'
,
width
:
'
72%
'
,
pct
:
'
72%
'
,
color
:
'
#14b8a6
'
},
{
label
:
'
acct[1]
'
,
width
:
'
45%
'
,
pct
:
'
45%
'
,
color
:
'
#06b6d4
'
},
{
label
:
'
acct[2]
'
,
width
:
'
61%
'
,
pct
:
'
61%
'
,
color
:
'
#0ea5e9
'
},
]
const
sparkline
=
[
12
,
20
,
16
,
28
,
22
,
35
,
30
,
42
,
38
,
50
,
45
,
55
,
48
,
60
,
55
,
65
,
58
,
70
,
62
,
75
]
const
featureTags
=
computed
(()
=>
[
{
text
:
t
(
'
home.tags.subscriptionToApi
'
)
},
{
text
:
t
(
'
home.tags.stickySession
'
)
},
{
text
:
t
(
'
home.tags.realtimeBilling
'
)
},
])
// SVG icon components
(inline)
//
Inline
SVG icon components
const
IconServer
=
defineComponent
({
render
:
()
=>
h
(
'
svg
'
,
{
class
:
'
h-5 w-5 text-white
'
,
fill
:
'
none
'
,
viewBox
:
'
0 0 24 24
'
,
stroke
:
'
currentColor
'
,
'
stroke-width
'
:
'
1.5
'
},
[
h
(
'
path
'
,
{
'
stroke-linecap
'
:
'
round
'
,
'
stroke-linejoin
'
:
'
round
'
,
d
:
'
M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z
'
})
...
...
@@ -393,37 +437,11 @@ const IconRoute = defineComponent({
])
})
const
IconChart
=
defineComponent
({
render
:
()
=>
h
(
'
svg
'
,
{
class
:
'
h-
5
w-
5
text-white
'
,
fill
:
'
none
'
,
viewBox
:
'
0 0 24 24
'
,
stroke
:
'
currentColor
'
,
'
stroke-width
'
:
'
1.5
'
}
,
[
render
:
()
=>
h
(
'
svg
'
,
{
class
:
'
h-
4
w-
4
text-white
'
,
fill
:
'
none
'
,
viewBox
:
'
0 0 24 24
'
,
stroke
:
'
currentColor
'
,
'
stroke-width
'
:
'
1.5
'
},
[
h
(
'
path
'
,
{
'
stroke-linecap
'
:
'
round
'
,
'
stroke-linejoin
'
:
'
round
'
,
d
:
'
M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z
'
})
])
})
// Features config — all icons use inline components, no string name needed
const
features
=
[
{
key
:
'
unifiedGateway
'
,
iconComponent
:
IconServer
,
iconBg
:
'
linear-gradient(135deg, #3b82f6, #1d4ed8)
'
,
glow
:
'
rgba(59,130,246,0.3)
'
,
color
:
'
#3b82f6
'
,
}
,
{
key
:
'
multiAccount
'
,
iconComponent
:
IconRoute
,
iconBg
:
'
linear-gradient(135deg, #14b8a6, #0d9488)
'
,
glow
:
'
rgba(20,184,166,0.3)
'
,
color
:
'
#14b8a6
'
,
}
,
{
key
:
'
balanceQuota
'
,
iconComponent
:
IconChart
,
iconBg
:
'
linear-gradient(135deg, #a855f7, #7c3aed)
'
,
glow
:
'
rgba(168,85,247,0.3)
'
,
color
:
'
#a855f7
'
,
}
,
]
// Providers
const
providerList
=
computed
(()
=>
[
{
name
:
t
(
'
home.providers.claude
'
),
letter
:
'
C
'
,
gradient
:
'
linear-gradient(135deg,#f97316,#ea580c)
'
,
live
:
true
},
{
name
:
'
GPT
'
,
letter
:
'
G
'
,
gradient
:
'
linear-gradient(135deg,#22c55e,#16a34a)
'
,
live
:
true
},
...
...
@@ -432,7 +450,6 @@ const providerList = computed(() => [
{
name
:
t
(
'
home.providers.more
'
),
letter
:
'
+
'
,
gradient
:
'
linear-gradient(135deg,#475569,#334155)
'
,
live
:
false
},
])
// Theme toggle
function
toggleTheme
()
{
isDark
.
value
=
!
isDark
.
value
document
.
documentElement
.
classList
.
toggle
(
'
dark
'
,
isDark
.
value
)
...
...
@@ -458,120 +475,130 @@ onMounted(() => {
<
style
scoped
>
/* ══════════════════════════════════════════════
ROOT
— always dark for this landing page
ROOT
══════════════════════════════════════════════ */
.home-root
{
background
:
#
182
a40
;
background
:
#
080e1a
;
color
:
white
;
}
/* ══════════════════════════════════════════════
BACKGROUND
LAYERS
AURORA
BACKGROUND
══════════════════════════════════════════════ */
.
or
b
{
.
aur
or
a
{
position
:
absolute
;
border-radius
:
50%
;
filter
:
blur
(
1
0
0
px
);
filter
:
blur
(
1
2
0px
);
pointer-events
:
none
;
}
.
orb
-
tr
{
top
:
-
15
%
;
right
:
-
15
%
;
.aurora-violet
{
top
:
-20%
;
right
:
-10%
;
width
:
700px
;
height
:
700px
;
background
:
radial-gradient
(
ellipse
,
rgba
(
139
,
92
,
246
,
0.18
)
0%
,
transparent
65%
);
animation
:
aurora-drift-1
18s
ease-in-out
infinite
alternate
;
}
.aurora-teal
{
bottom
:
-15%
;
left
:
-10%
;
width
:
600px
;
height
:
600px
;
background
:
radial
-
gradient
(
ellipse
,
rgba
(
20
,
184
,
166
,
0.18
)
0
%
,
transparent
70
%
);
background
:
radial-gradient
(
ellipse
,
rgba
(
20
,
184
,
166
,
0.15
)
0%
,
transparent
65%
);
animation
:
aurora-drift-2
22s
ease-in-out
infinite
alternate
;
}
.
orb
-
bl
{
bot
to
m
:
-
15
%
;
left
:
-
1
5
%
;
.
aurora-indigo
{
to
p
:
40
%
;
left
:
2
5%
;
width
:
500px
;
height
:
500px
;
background
:
radial
-
gradient
(
ellipse
,
rgba
(
6
,
182
,
212
,
0.13
)
0
%
,
transparent
70
%
);
background
:
radial-gradient
(
ellipse
,
rgba
(
99
,
102
,
241
,
0.10
)
0%
,
transparent
65%
);
animation
:
aurora-drift-3
26s
ease-in-out
infinite
alternate
;
}
.
orb
-
center
{
top
:
30
%
;
left
:
40
%
;
width
:
400
px
;
height
:
400
px
;
background
:
radial
-
gradient
(
ellipse
,
rgba
(
20
,
184
,
166
,
0.08
)
0
%
,
transparent
70
%
);
@keyframes
aurora-drift-1
{
from
{
transform
:
translate
(
0
,
0
)
scale
(
1
);
}
to
{
transform
:
translate
(
-40px
,
30px
)
scale
(
1.08
);
}
}
.
grid
-
overlay
{
position
:
absolute
;
inset
:
0
;
background
-
image
:
linear
-
gradient
(
rgba
(
20
,
184
,
166
,
0.09
)
1
px
,
transparent
1
px
),
linear
-
gradient
(
90
deg
,
rgba
(
20
,
184
,
166
,
0.09
)
1
px
,
transparent
1
px
);
background
-
size
:
48
px
48
px
;
@keyframes
aurora-drift-2
{
from
{
transform
:
translate
(
0
,
0
)
scale
(
1
);
}
to
{
transform
:
translate
(
30px
,
-40px
)
scale
(
1.05
);
}
}
.
scan
-
line
{
position
:
absolute
;
left
:
0
;
right
:
0
;
height
:
1
px
;
background
:
linear
-
gradient
(
90
deg
,
transparent
0
%
,
rgba
(
20
,
184
,
166
,
0.5
)
30
%
,
rgba
(
20
,
184
,
166
,
0.8
)
50
%
,
rgba
(
20
,
184
,
166
,
0.5
)
70
%
,
transparent
100
%
);
animation
:
scan
10
s
linear
infinite
;
box
-
shadow
:
0
0
8
px
rgba
(
20
,
184
,
166
,
0.4
);
@keyframes
aurora-drift-3
{
from
{
transform
:
translate
(
0
,
0
)
scale
(
1
);
}
to
{
transform
:
translate
(
-20px
,
-30px
)
scale
(
0.95
);
}
}
@
keyframes
scan
{
0
%
{
top
:
0
%
;
opacity
:
0
;
}
3
%
{
opacity
:
1
;
}
97
%
{
opacity
:
0.4
;
}
100
%
{
top
:
100
%
;
opacity
:
0
;
}
.noise-overlay
{
position
:
absolute
;
inset
:
0
;
opacity
:
0.025
;
background-image
:
url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)
'/%3E%3C/svg%3E");
background-repeat: repeat;
background-size: 128px 128px;
}
/* ══════════════════════════════════════════════
HEADER
══════════════════════════════════════════════ */
.home-header {
border
-
bottom
:
1
px
solid
rgba
(
20
,
184
,
166
,
0.12
);
backdrop
-
filter
:
blur
(
12
px
);
background
:
rgba
(
24
,
42
,
64
,
0.8
);
}
.
logo
-
ring
{
box
-
shadow
:
0
0
0
1
px
rgba
(
20
,
184
,
166
,
0.25
),
0
0
16
px
rgba
(
20
,
184
,
166
,
0.15
);
border-bottom: 1px solid rgba(255,255,255,0.05);
backdrop-filter: blur(16px);
background: rgba(8,14,26,0.7);
}
.logo-badge {
width: 34px;
height: 34px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 0 0 1px rgba(139,92,246,0.25), 0 0 16px rgba(139,92,246,0.12);
}
.
nav
-
btn
{
.nav-
pill
{
display: inline-flex;
align-items: center;
padding
:
6
px
10
px
;
padding:
5
px 10px;
border-radius: 6px;
font-size: 12px;
color: #94a3b8;
transition: all 0.2s;
}
.
nav
-
btn
:
hover
{
background
:
rgba
(
20
,
184
,
166
,
0.08
);
color
:
#
5
eead4
;
}
.
nav
-
btn
.
icon
-
only
{
padding
:
6
px
8
px
;
.nav-pill:hover { background: rgba(255,255,255,0.06); color: #e2e8f0; }
.nav-icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 7px;
border-radius: 6px;
color: #94a3b8;
transition: all 0.2s;
background: transparent;
border: none;
cursor: pointer;
}
.
cta
-
nav
-
btn
{
.nav-icon-btn:hover { background: rgba(255,255,255,0.06); color: #e2e8f0; }
.cta-header-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border
-
radius
:
7
px
;
border
:
1
px
solid
rgba
(
20
,
184
,
16
6
,
0.3
);
background
:
rgba
(
20
,
184
,
16
6
,
0.
08
);
border-radius:
8
px;
border: 1px solid rgba(
139,92,24
6,0.3);
background: rgba(
139,92,24
6,0.
1
);
font-size: 12px;
font-weight: 500;
color
:
#
5
eead4
;
color: #
c4b5fd
;
transition: all 0.2s;
}
.
cta
-
nav
-
btn
:
hover
{
border
-
color
:
rgba
(
20
,
184
,
16
6
,
0.6
);
background
:
rgba
(
20
,
184
,
16
6
,
0.1
5
);
box
-
shadow
:
0
0
1
4
px
rgba
(
20
,
184
,
16
6
,
0.2
);
.cta-
header
-btn:hover {
border-color: rgba(
139,92,24
6,0.6);
background: rgba(
139,92,24
6,0.1
8
);
box-shadow: 0 0 1
6
px rgba(
139,92,24
6,0.2);
}
.
use
r
-
dot
{
.
avata
r-dot {
display: flex;
align-items: center;
justify-content: center;
width
:
16
px
;
height
:
16
px
;
width: 16px; height: 16px;
border-radius: 50%;
background
:
linear
-
gradient
(
135
deg
,
#
14
b8a6
,
#
0
d9488
);
background: linear-gradient(135deg, #
8b5cf6, #6366f1
);
font-size: 9px;
font-weight: 700;
color: white;
...
...
@@ -580,374 +607,387 @@ onMounted(() => {
/* ══════════════════════════════════════════════
HERO
══════════════════════════════════════════════ */
.
gradient
-
text
{
background
:
linear
-
gradient
(
135
deg
,
#
ffffff
0
%
,
#
7
dd3c8
40
%
,
#
14
b8a6
100
%
);
.hero-headline {
font-size: clamp(2rem, 5vw, 4rem);
font-weight: 900;
line-height: 1.08;
letter-spacing: -0.03em;
background: linear-gradient(135deg, #ffffff 0%, #c4b5fd 35%, #5eead4 70%, #14b8a6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.
cta
-
primary
{
background
:
linear
-
gradient
(
135
deg
,
#
0
f766e
,
#
14
b8a6
,
#
0
d9488
);
/* CTA Buttons */
.cta-primary-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 28px;
border-radius: 10px;
font-size: 14px;
font-weight: 600;
color: white;
background: linear-gradient(135deg, #7c3aed, #8b5cf6, #6366f1);
background-size: 200% 200%;
animation
:
gradient
-
shift
4
s
ease
infinite
;
box
-
shadow
:
0
0
0
1
px
rgba
(
20
,
184
,
16
6
,
0.4
),
0
4
px
2
0
px
rgba
(
20
,
184
,
16
6
,
0.
25
);
animation:
btn-
gradient 4s ease infinite;
box-shadow: 0 0 0 1px rgba(
139,92,24
6,0.4), 0 4px 2
4
px rgba(
139,92,24
6,0.
3
);
transition: box-shadow 0.25s, transform 0.25s;
}
.
cta
-
primary
:
hover
{
box
-
shadow
:
0
0
0
1
px
rgba
(
20
,
184
,
16
6
,
0.7
),
0
6
px
2
8
px
rgba
(
20
,
184
,
16
6
,
0.4
);
.cta-primary
-btn
:hover {
box-shadow: 0 0 0 1px rgba(
139,92,24
6,0.7), 0
8
px
3
2px rgba(
139,92,24
6,0.4
5
);
transform: translateY(-2px);
}
@
keyframes
gradient
-
shift
{
@keyframes
btn-
gradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Stats */
.
stat
-
item
{
display
:
flex
;
flex
-
direction
:
column
;
gap
:
2
px
;
position
:
relative
;
}
.
stat
-
item
+
.
stat
-
item
::
before
{
content
:
''
;
position
:
absolute
;
left
:
-
16
px
;
top
:
50
%
;
transform
:
translateY
(
-
50
%
);
width
:
1
px
;
height
:
28
px
;
background
:
rgba
(
20
,
184
,
166
,
0.2
);
}
.
stat
-
value
{
font
-
size
:
1.6
rem
;
font
-
weight
:
900
;
font
-
family
:
ui
-
monospace
,
monospace
;
color
:
white
;
line
-
height
:
1
;
letter
-
spacing
:
-
0.02
em
;
.cta-ghost-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
border-radius: 10px;
font-size: 14px;
font-weight: 500;
color: #94a3b8;
border: 1px solid rgba(255,255,255,0.08);
transition: all 0.2s;
}
.
stat
-
label
{
font
-
size
:
10
px
;
color
:
#
475569
;
text
-
transform
:
uppercase
;
letter
-
spacing
:
0.1
em
;
.cta-ghost-btn:hover {
border-color: rgba(139,92,246,0.3);
color: #c4b5fd;
}
/* ══════════════════════════════════════════════
TERMINAL
PIPELINE VISUALIZATION
══════════════════════════════════════════════ */
.
terminal
-
wrap
{
.
pipeline
-wrap
per
{
position: relative;
display: inline-block;
width: 100%;
}
.
terminal
-
glow
{
.
pipeline
-glow {
position: absolute;
inset
:
-
3
0
px
;
background
:
radial
-
gradient
(
ellipse
at
center
,
rgba
(
20
,
184
,
166
,
0.
1
)
0
%
,
transparent
6
5
%
);
inset: -
2
0px;
background: radial-gradient(ellipse at center, rgba(
99,102,241
,0.
08
) 0%, transparent 6
0
%);
pointer-events: none;
z
-
index
:
0
;
}
.
terminal
-
window
{
.
pipeline-card
{
position: relative;
z
-
index
:
1
;
width
:
460
px
;
max
-
width
:
100
%
;
background
:
linear
-
gradient
(
160
deg
,
#
1
a2e48
0
%
,
#
162540
100
%
);
border
-
radius
:
13
px
;
border
:
1
px
solid
rgba
(
20
,
184
,
166
,
0.18
);
box
-
shadow
:
0
0
0
1
px
rgba
(
255
,
255
,
255
,
0.025
),
0
28
px
64
px
rgba
(
0
,
0
,
0
,
0.7
),
0
0
50
px
rgba
(
20
,
184
,
166
,
0.07
);
border-radius: 16px;
border: 1px solid rgba(255,255,255,0.07);
background: rgba(15,20,40,0.7);
backdrop-filter: blur(20px);
padding: 28px 24px 20px;
box-shadow: 0 0 0 1px rgba(99,102,241,0.06), 0 24px 60px rgba(0,0,0,0.6);
overflow: hidden;
transform
:
perspective
(
900
px
)
rotateX
(
1.5d
eg
)
rotateY
(
-
2.5d
eg
);
transition
:
transform
0.4
s
ease
,
box
-
shadow
0.4
s
ease
;
}
.
terminal
-
window
:
hover
{
transform
:
perspective
(
900
px
)
rotateX
(
0
deg
)
rotateY
(
0
deg
)
translateY
(
-
8
px
)
;
box
-
shadow
:
0
0
0
1
px
rgba
(
20
,
184
,
166
,
0.28
),
0
36
px
80
px
rgba
(
0
,
0
,
0
,
0.75
),
0
0
70
px
rgba
(
20
,
184
,
166
,
0.
12
);
.
pipeline-card::before
{
content: '
'
;
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
;
height
:
1px
;
background
:
linear-gradient
(
90deg
,
transparent
,
rgba
(
139
,
92
,
246
,
0.4
),
rgba
(
20
,
184
,
166
,
0.
4
),
transparent
);
}
.
terminal
-
head
er
{
.
pipeline-inn
er
{
display
:
flex
;
align-items
:
center
;
gap
:
10
px
;
padding
:
11
px
16
px
;
background
:
rgba
(
22
,
37
,
60
,
0.9
);
border
-
bottom
:
1
px
solid
rgba
(
20
,
184
,
166
,
0.12
);
justify-content
:
center
;
flex-wrap
:
nowrap
;
gap
:
0
;
overflow-x
:
auto
;
padding-bottom
:
4px
;
}
.
terminal
-
dots
{
.
pipe-node
{
display
:
flex
;
gap
:
6
px
;
}
.
terminal
-
dots
span
{
width
:
11
px
;
height
:
11
px
;
border
-
radius
:
50
%
;
}
.
dot
-
red
{
background
:
#
ef4444
;
}
.
dot
-
yellow
{
background
:
#
eab308
;
}
.
dot
-
green
{
background
:
#
22
c55e
;
}
.
terminal
-
title
{
flex
:
1
;
text
-
align
:
center
;
font
-
size
:
11
px
;
font
-
family
:
ui
-
monospace
,
monospace
;
color
:
#
334155
;
flex-direction
:
column
;
align-items
:
center
;
gap
:
8px
;
flex-shrink
:
0
;
}
.
terminal
-
live
{
.pipe-icon
{
width
:
48px
;
height
:
48px
;
border-radius
:
12px
;
display
:
flex
;
align-items
:
center
;
gap
:
5
px
;
justify-content
:
center
;
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.08
);
}
.pipe-icon-client
{
background
:
rgba
(
15
,
23
,
42
,
0.8
);
color
:
#94a3b8
;
}
.pipe-icon-gateway
{
background
:
linear-gradient
(
135deg
,
rgba
(
59
,
130
,
246
,
0.2
),
rgba
(
99
,
102
,
241
,
0.2
));
color
:
#93c5fd
;
border-color
:
rgba
(
99
,
102
,
241
,
0.2
);
}
.pipe-icon-balancer
{
background
:
linear-gradient
(
135deg
,
rgba
(
20
,
184
,
166
,
0.2
),
rgba
(
6
,
182
,
212
,
0.2
));
color
:
#5eead4
;
border-color
:
rgba
(
20
,
184
,
166
,
0.2
);
}
.pipe-icon-ai
{
background
:
linear-gradient
(
135deg
,
rgba
(
168
,
85
,
247
,
0.2
),
rgba
(
139
,
92
,
246
,
0.2
));
color
:
#d8b4fe
;
border-color
:
rgba
(
168
,
85
,
247
,
0.2
);
}
.pipe-icon-response
{
background
:
linear-gradient
(
135deg
,
rgba
(
52
,
211
,
153
,
0.2
),
rgba
(
16
,
185
,
129
,
0.2
));
color
:
#6ee7b7
;
border-color
:
rgba
(
52
,
211
,
153
,
0.2
);
}
.pipe-label
{
font-size
:
10px
;
font-family
:
ui-monospace
,
monospace
;
color
:
#
14
b8a6
;
let
te
r
-
spac
ing
:
0.06
em
;
color
:
#
475569
;
whi
te-spac
e
:
nowrap
;
}
.
terminal
-
body
{
padding
:
18
px
22
px
22
px
;
font
-
family
:
ui
-
monospace
,
'
Fira Code
'
,
monospace
;
font
-
size
:
12.5
px
;
line
-
height
:
1.85
;
.pipe-connector
{
position
:
relative
;
width
:
60px
;
height
:
2px
;
flex-shrink
:
0
;
margin-bottom
:
20px
;
}
/* Terminal lines */
.
t
-
line
{
.pipe-line
{
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
;
height
:
100%
;
background
:
linear-gradient
(
90deg
,
rgba
(
99
,
102
,
241
,
0.15
),
rgba
(
20
,
184
,
166
,
0.3
),
rgba
(
99
,
102
,
241
,
0.15
));
}
.pipe-dot
{
position
:
absolute
;
top
:
-3px
;
width
:
8px
;
height
:
8px
;
border-radius
:
50%
;
background
:
#14b8a6
;
box-shadow
:
0
0
8px
rgba
(
20
,
184
,
166
,
0.8
);
}
.pipe-dot-1
{
animation
:
pipe-flow
2.0s
linear
infinite
;
}
.pipe-dot-2
{
animation
:
pipe-flow
2.0s
linear
0.4s
infinite
;
}
.pipe-dot-3
{
animation
:
pipe-flow
2.0s
linear
0.8s
infinite
;
}
.pipe-dot-4
{
animation
:
pipe-flow
2.0s
linear
1.2s
infinite
;
}
@keyframes
pipe-flow
{
from
{
left
:
-5%
;
opacity
:
0
;
}
10
%
{
opacity
:
1
;
}
90
%
{
opacity
:
1
;
}
to
{
left
:
105%
;
opacity
:
0
;
}
}
.pipe-metrics
{
display
:
flex
;
align-items
:
center
;
gap
:
7
px
;
opacity
:
0
;
animation
:
t
-
appear
0.35
s
ease
forwards
;
justify-content
:
center
;
gap
:
32px
;
margin-top
:
20px
;
padding-top
:
16px
;
border-top
:
1px
solid
rgba
(
255
,
255
,
255
,
0.05
);
}
.
t
-
sep
{
height
:
1
px
;
background
:
rgba
(
20
,
184
,
166
,
0.07
);
margin
:
5
px
0
;
opacity
:
0
;
animation
:
t
-
appear
0.2
s
ease
forwards
;
}
.
line
-
1
{
animation
-
delay
:
0.2
s
;
}
.
line
-
2
{
animation
-
delay
:
0.7
s
;
}
.
line
-
3
{
animation
-
delay
:
1.2
s
;
}
.
sep
-
1
{
animation
-
delay
:
1.75
s
;
}
.
line
-
4
{
animation
-
delay
:
2.0
s
;
}
.
line
-
5
{
animation
-
delay
:
2.5
s
;
}
.
line
-
6
{
animation
-
delay
:
3.0
s
;
}
.
sep
-
2
{
animation
-
delay
:
3.5
s
;
}
.
line
-
7
{
animation
-
delay
:
3.8
s
;
}
.
line
-
8
{
animation
-
delay
:
4.4
s
;
}
@
keyframes
t
-
appear
{
from
{
opacity
:
0
;
transform
:
translateX
(
-
5
px
);
}
to
{
opacity
:
1
;
transform
:
translateX
(
0
);
}
}
.
t
-
prompt
{
color
:
#
14
b8a6
;
font
-
weight
:
700
;
}
.
t
-
method
{
color
:
#
38
bdf8
;
font
-
weight
:
600
;
}
.
t
-
path
{
color
:
#
bfdbfe
;
}
.
t
-
comment
{
color
:
#
1
e3a4a
;
font
-
style
:
italic
;
}
.
t
-
ok
{
color
:
#
4
ade80
;
background
:
rgba
(
74
,
222
,
128
,
0.07
);
border
:
1
px
solid
rgba
(
74
,
222
,
128
,
0.15
);
padding
:
1
px
6
px
;
border
-
radius
:
3
px
;
font
-
weight
:
700
;
font
-
size
:
11
px
;
.pipe-metric-item
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
gap
:
2px
;
}
.
t
-
ms
{
color
:
#
fcd34d
;
font
-
size
:
11
px
;
}
.
t
-
dim
{
color
:
#
1
e3a4a
;
font
-
size
:
11
px
;
}
.
t
-
stat
{
color
:
#
a78bfa
;
}
/* Cursor */
.
t
-
cursor
{
display
:
inline
-
block
;
width
:
7
px
;
height
:
13
px
;
background
:
#
14
b8a6
;
box
-
shadow
:
0
0
8
px
rgba
(
20
,
184
,
166
,
0.7
);
animation
:
blink
1.1
s
step
-
end
infinite
;
border
-
radius
:
1
px
;
.pipe-metric-val
{
font-family
:
ui-monospace
,
monospace
;
font-size
:
13px
;
font-weight
:
700
;
color
:
#5eead4
;
}
@
keyframes
blink
{
0
%
,
44
%
{
opacity
:
1
;
}
50
%
,
94
%
{
opacity
:
0
;
}
100
%
{
opacity
:
1
;
}
.pipe-metric-label
{
font-family
:
ui-monospace
,
monospace
;
font-size
:
10px
;
color
:
#334155
;
text-transform
:
uppercase
;
letter-spacing
:
0.08em
;
}
/* ══════════════════════════════════════════════
FEATURE
TAG
S
BENTO
FEATURES
══════════════════════════════════════════════ */
.
tag
-
pill
{
display
:
inline
-
flex
;
align
-
items
:
center
;
gap
:
8
px
;
border
:
1
px
solid
rgba
(
20
,
184
,
166
,
0.14
);
background
:
rgba
(
20
,
184
,
166
,
0.04
);
border
-
radius
:
9999
px
;
padding
:
7
px
18
px
;
font
-
size
:
12
px
;
font
-
weight
:
500
;
color
:
#
64748
b
;
transition
:
all
0.2
s
;
cursor
:
default
;
}
.
tag
-
pill
:
hover
{
border
-
color
:
rgba
(
20
,
184
,
166
,
0.3
);
background
:
rgba
(
20
,
184
,
166
,
0.08
);
color
:
#
94
a3b8
;
.bento-title-gradient
{
background
:
linear-gradient
(
135deg
,
#ffffff
,
#c4b5fd
50%
,
#5eead4
);
-webkit-background-clip
:
text
;
-webkit-text-fill-color
:
transparent
;
background-clip
:
text
;
}
.
tag
-
pip
{
width
:
5
px
;
height
:
5
px
;
border
-
radius
:
50
%
;
background
:
#
14
b8a6
;
box
-
shadow
:
0
0
7
px
rgba
(
20
,
184
,
166
,
0.9
);
flex
-
shrink
:
0
;
.bento-grid
{
display
:
grid
;
grid-template-columns
:
repeat
(
3
,
1
fr
);
grid-auto-rows
:
auto
;
gap
:
16px
;
}
/* ══════════════════════════════════════════════
FEATURE CARDS
══════════════════════════════════════════════ */
.
feat
-
card
{
.bento-card
{
position
:
relative
;
border-radius
:
16px
;
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.07
);
background
:
rgba
(
12
,
18
,
36
,
0.7
);
backdrop-filter
:
blur
(
16px
);
padding
:
26px
;
background
:
rgba
(
26
,
46
,
72
,
0.65
);
border
:
1
px
solid
rgba
(
255
,
255
,
255
,
0.09
);
border
-
radius
:
13
px
;
backdrop
-
filter
:
blur
(
14
px
);
overflow
:
hidden
;
transition
:
all
0.3
s
ease
;
transition
:
all
0.35s
ease
;
box-shadow
:
0
4px
24px
rgba
(
0
,
0
,
0
,
0.4
);
}
.
feat
-
card
:
hover
{
border
-
color
:
rgba
(
255
,
255
,
255
,
0.1
5
);
background
:
rgba
(
26
,
46
,
72
,
0.88
);
.
bento
-card
:hover
{
border-color
:
rgba
(
255
,
255
,
255
,
0.1
2
);
background
:
rgba
(
15
,
22
,
45
,
0.88
);
transform
:
translateY
(
-3px
);
box
-
shadow
:
0
20
px
50
px
rgba
(
0
,
0
,
0
,
0.5
);
box-shadow
:
0
16
px
48
px
rgba
(
0
,
0
,
0
,
0.5
);
}
/* left accent bar */
.
feat
-
accent
{
/* Bento layout sizes */
.bento-large
{
grid-column
:
span
2
;
min-height
:
260px
;
}
.bento-regular
{
grid-column
:
span
1
;
}
.bento-stats
{
grid-column
:
span
1
;
}
.bento-wide
{
grid-column
:
span
2
;
}
/* Left accent bar */
.bento-accent-bar
{
position
:
absolute
;
left
:
0
;
top
:
0
;
left
:
0
;
top
:
0
;
width
:
3px
;
height
:
100%
;
opacity
:
0.55
;
transition
:
opacity
0.3
s
,
box
-
shadow
0.3
s
;
}
.
feat
-
card
:
hover
.
feat
-
accent
{
opacity
:
1
;
box
-
shadow
:
0
0
12
px
currentColor
;
}
/* corner brackets */
.
feat
-
corner
-
tr
,
.
feat
-
corner
-
bl
{
position
:
absolute
;
width
:
10
px
;
height
:
10
px
;
opacity
:
0
;
opacity
:
0.6
;
border-radius
:
0
0
0
16px
;
transition
:
opacity
0.3s
;
}
.
feat
-
corner
-
tr
{
top
:
10
px
;
right
:
10
px
;
border
-
top
:
1
px
solid
rgba
(
20
,
184
,
166
,
0.45
);
border
-
right
:
1
px
solid
rgba
(
20
,
184
,
166
,
0.45
);
}
.
feat
-
corner
-
bl
{
bottom
:
10
px
;
right
:
10
px
;
border
-
bottom
:
1
px
solid
rgba
(
20
,
184
,
166
,
0.45
);
border
-
right
:
1
px
solid
rgba
(
20
,
184
,
166
,
0.45
);
}
.
feat
-
card
:
hover
.
feat
-
corner
-
tr
,
.
feat
-
card
:
hover
.
feat
-
corner
-
bl
{
opacity
:
1
;
}
.
feat
-
icon
{
.bento-card
:hover
.bento-accent-bar
{
opacity
:
1
;
}
.bento-icon-wrap
{
width
:
44px
;
height
:
44px
;
border-radius
:
10px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
transition
:
transform
0.3
s
,
box
-
shadow
0.3
s
;
}
.
feat
-
card
:
hover
.
feat
-
icon
{
transform
:
scale
(
1.08
);
/* Stats card specific */
.stat-row
{
display
:
flex
;
flex-direction
:
column
;
gap
:
2px
;
}
.stat-big-value
{
font-size
:
1.8rem
;
font-weight
:
900
;
font-family
:
ui-monospace
,
monospace
;
color
:
white
;
line-height
:
1
;
letter-spacing
:
-0.02em
;
}
.stat-big-label
{
font-size
:
10px
;
color
:
#334155
;
text-transform
:
uppercase
;
letter-spacing
:
0.1em
;
}
/* Mini tag */
.mini-tag
{
font-size
:
10px
;
padding
:
2px
8px
;
border-radius
:
9999px
;
border
:
1px
solid
rgba
(
139
,
92
,
246
,
0.2
);
background
:
rgba
(
139
,
92
,
246
,
0.08
);
color
:
#c4b5fd
;
white-space
:
nowrap
;
}
/* Sparkline bars */
.sparkbar
{
width
:
6px
;
border-radius
:
2px
;
background
:
linear-gradient
(
180deg
,
#a855f7
,
#7c3aed
);
opacity
:
0.7
;
animation
:
spark-grow
0.4s
ease
forwards
;
}
@keyframes
spark-grow
{
from
{
transform
:
scaleY
(
0
);
transform-origin
:
bottom
;
opacity
:
0
;
}
to
{
transform
:
scaleY
(
1
);
transform-origin
:
bottom
;
opacity
:
0.7
;
}
}
/* ══════════════════════════════════════════════
PROVIDERS
PROVIDERS
MARQUEE
══════════════════════════════════════════════ */
.
provider
-
card
{
.marquee-container
{
position
:
relative
;
overflow
:
hidden
;
}
.marquee-fade-left
,
.marquee-fade-right
{
position
:
absolute
;
top
:
0
;
bottom
:
0
;
width
:
80px
;
z-index
:
2
;
pointer-events
:
none
;
}
.marquee-fade-left
{
left
:
0
;
background
:
linear-gradient
(
90deg
,
#080e1a
,
transparent
);
}
.marquee-fade-right
{
right
:
0
;
background
:
linear-gradient
(
-90deg
,
#080e1a
,
transparent
);
}
.marquee-track
{
overflow
:
hidden
;
}
.marquee-inner
{
display
:
flex
;
gap
:
16px
;
width
:
max-content
;
animation
:
marquee-scroll
24s
linear
infinite
;
}
@keyframes
marquee-scroll
{
from
{
transform
:
translateX
(
0
);
}
to
{
transform
:
translateX
(
-50%
);
}
}
.provider-chip
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
padding
:
10
px
1
6
px
;
padding
:
10px
1
8
px
;
border-radius
:
10px
;
border
:
1
px
solid
rgba
(
255
,
255
,
255
,
0.
1
);
background
:
rgba
(
26
,
46
,
72
,
0.
55
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.
08
);
background
:
rgba
(
15
,
22
,
45
,
0.
6
);
backdrop-filter
:
blur
(
8px
);
white-space
:
nowrap
;
transition
:
all
0.25s
;
}
.
provider
-
active
:
hover
{
border
-
color
:
rgba
(
20
,
184
,
166
,
0.22
);
background
:
rgba
(
20
,
184
,
166
,
0.05
);
box
-
shadow
:
0
4
px
20
px
rgba
(
0
,
0
,
0
,
0.35
);
.provider-chip-live
{
color
:
#cbd5e1
;
}
.
provider
-
inactive
{
opacity
:
0.38
;
}
.
provider
-
status
-
dot
{
width
:
6
px
;
height
:
6
px
;
border
-
radius
:
50
%
;
flex
-
shrink
:
0
;
}
.
dot
-
live
{
background
:
#
34
d399
;
box
-
shadow
:
0
0
7
px
rgba
(
52
,
211
,
153
,
0.8
);
.provider-chip-dim
{
opacity
:
0.35
;
color
:
#475569
;
}
.
dot
-
offline
{
background
:
#
334155
;
.provider-chip-live
:hover
{
border-color
:
rgba
(
139
,
92
,
246
,
0.2
);
background
:
rgba
(
139
,
92
,
246
,
0.06
);
}
.
provider
-
icon
-
badge
{
width
:
2
8
px
;
height
:
2
8
px
;
.provider-badge
{
width
:
2
6
px
;
height
:
2
6
px
;
border-radius
:
6px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
flex-shrink
:
0
;
}
.
provider
-
tag
{
font
-
size
:
10
px
;
font
-
weight
:
600
;
padding
:
2
px
8
px
;
border
-
radius
:
9999
px
;
letter
-
spacing
:
0.05
em
;
text
-
transform
:
uppercase
;
}
.
tag
-
live
{
background
:
rgba
(
20
,
184
,
166
,
0.1
);
color
:
#
2
dd4bf
;
border
:
1
px
solid
rgba
(
20
,
184
,
166
,
0.2
);
}
.
tag
-
soon
{
background
:
rgba
(
71
,
85
,
105
,
0.2
);
color
:
#
475569
;
border
:
1
px
solid
rgba
(
71
,
85
,
105
,
0.25
);
.provider-live-dot
{
width
:
5px
;
height
:
5px
;
border-radius
:
50%
;
background
:
#34d399
;
box-shadow
:
0
0
6px
rgba
(
52
,
211
,
153
,
0.8
);
flex-shrink
:
0
;
}
/* ══════════════════════════════════════════════
FOOTER
══════════════════════════════════════════════ */
.home-footer
{
border
-
top
:
1
px
solid
rgba
(
20
,
184
,
166
,
0.1
);
background
:
rgba
(
24
,
42
,
64
,
0.8
);
border-top
:
1px
solid
rgba
(
255
,
255
,
255
,
0.05
);
background
:
rgba
(
8
,
14
,
26
,
0.8
);
}
/* Responsive */
@media
(
max-width
:
768px
)
{
.bento-large
{
grid-column
:
span
3
;
}
.bento-regular
{
grid-column
:
span
3
;
}
.bento-stats
{
grid-column
:
span
3
;
}
.bento-wide
{
grid-column
:
span
3
;
}
.pipeline-inner
{
justify-content
:
flex-start
;
}
.pipe-connector
{
width
:
32px
;
}
}
@media
(
min-width
:
769px
)
and
(
max-width
:
1024px
)
{
.bento-large
{
grid-column
:
span
2
;
}
.bento-wide
{
grid-column
:
span
3
;
}
}
</
style
>
frontend/src/views/auth/LoginView.vue
View file @
8d3f79cf
...
...
@@ -80,7 +80,7 @@
<router-link
v-if=
"passwordResetEnabled && !backendModeEnabled"
to=
"/forgot-password"
class=
"text-sm font-medium t
ext-primary-600 transition-colors hover:text-primary-500 dark:text-primary-400 dark:hover:text-primary-300
"
class=
"text-sm font-medium t
ransition-colors"
style=
"color: #8b5cf6;
"
>
{{
t
(
'
auth.forgotPassword
'
)
}}
</router-link>
...
...
frontend/tailwind.config.js
View file @
8d3f79cf
...
...
@@ -67,8 +67,8 @@ export default {
boxShadow
:
{
glass
:
'
0 8px 32px rgba(0, 0, 0, 0.08)
'
,
'
glass-sm
'
:
'
0 4px 16px rgba(0, 0, 0, 0.06)
'
,
glow
:
'
0 0
2
0px rgba(
20, 184, 166
, 0.
2
5)
'
,
'
glow-lg
'
:
'
0 0 40px rgba(
20
, 1
84, 166
, 0.35)
'
,
glow
:
'
0 0 0
1
px rgba(
99, 102, 241, 0.25), 0 0 20px rgba(99, 102, 241
, 0.
1
5)
'
,
'
glow-lg
'
:
'
0 0 40px rgba(
99
, 1
02, 241
, 0.35)
'
,
card
:
'
0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06)
'
,
'
card-hover
'
:
'
0 10px 40px rgba(0, 0, 0, 0.08)
'
,
'
inner-glow
'
:
'
inset 0 1px 0 rgba(255, 255, 255, 0.1)
'
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment