Refine embedded portal homepage
This commit is contained in:
477
app/globals.css
477
app/globals.css
@@ -207,11 +207,16 @@ body {
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
overflow: hidden;
|
overflow-x: hidden;
|
||||||
padding: 2rem;
|
}
|
||||||
|
.portal-main {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.portal-container {
|
||||||
|
width: min(calc(100% - 3rem), 1440px);
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Background: animated grid ── */
|
/* ── Background: animated grid ── */
|
||||||
@@ -374,22 +379,15 @@ body {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
max-width: 960px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
|
||||||
.portal-cards {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Individual card */
|
/* Individual card */
|
||||||
.portal-card {
|
.portal-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
padding: 2.5rem 2rem 2rem;
|
padding: 2.5rem 2rem 2rem;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
@@ -400,6 +398,7 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: border-color 0.35s ease, box-shadow 0.35s ease;
|
transition: border-color 0.35s ease, box-shadow 0.35s ease;
|
||||||
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
.portal-card:hover {
|
.portal-card:hover {
|
||||||
border-color: var(--card-accent);
|
border-color: var(--card-accent);
|
||||||
@@ -476,7 +475,7 @@ body {
|
|||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
text-align: center;
|
text-align: left;
|
||||||
margin-bottom: 1.25rem;
|
margin-bottom: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,13 +489,13 @@ body {
|
|||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.06em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
opacity: 0;
|
opacity: 0.78;
|
||||||
transform: translateY(6px);
|
transform: none;
|
||||||
transition: all 0.35s ease;
|
transition: all 0.35s ease;
|
||||||
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
.portal-card:hover .portal-card-action {
|
.portal-card:hover .portal-card-action {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(0);
|
|
||||||
color: var(--card-accent);
|
color: var(--card-accent);
|
||||||
}
|
}
|
||||||
.portal-card-action-icon {
|
.portal-card-action-icon {
|
||||||
@@ -510,15 +509,18 @@ body {
|
|||||||
/* Bottom highlight bar */
|
/* Bottom highlight bar */
|
||||||
.portal-card-bar {
|
.portal-card-bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0; left: 50%;
|
bottom: 0;
|
||||||
transform: translateX(-50%);
|
left: 2rem;
|
||||||
width: 0; height: 2px;
|
width: 64px;
|
||||||
|
height: 2px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background: var(--card-accent);
|
background: var(--card-accent);
|
||||||
transition: width 0.4s cubic-bezier(0.22, 1, 0.36, 1);
|
opacity: 0.5;
|
||||||
|
transition: width 0.4s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.3s ease;
|
||||||
}
|
}
|
||||||
.portal-card:hover .portal-card-bar {
|
.portal-card:hover .portal-card-bar {
|
||||||
width: 60%;
|
width: calc(100% - 4rem);
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Footer ── */
|
/* ── Footer ── */
|
||||||
@@ -527,8 +529,9 @@ body {
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-top: 3rem;
|
padding: 2.5rem 0 2rem;
|
||||||
}
|
}
|
||||||
.portal-status-dot {
|
.portal-status-dot {
|
||||||
width: 6px; height: 6px;
|
width: 6px; height: 6px;
|
||||||
@@ -549,6 +552,436 @@ body {
|
|||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Portal Landing: Nav ── */
|
||||||
|
.portal-nav {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
padding: 0.85rem 0;
|
||||||
|
background: color-mix(in srgb, var(--background) 78%, transparent);
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
border-bottom: 1px solid var(--surface-border);
|
||||||
|
}
|
||||||
|
.portal-nav-inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
.portal-nav-logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.portal-nav-menu,
|
||||||
|
.portal-nav-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.portal-nav-menu a {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
.portal-nav-menu a:hover { color: var(--text-accent); }
|
||||||
|
|
||||||
|
/* ── Portal Landing: Hero ── */
|
||||||
|
.portal-hero {
|
||||||
|
position: relative;
|
||||||
|
min-height: calc(100vh - 78px);
|
||||||
|
padding: clamp(3.5rem, 7vw, 6rem) 0 5.5rem;
|
||||||
|
}
|
||||||
|
.portal-hero-scan {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(90deg, transparent, var(--accent), transparent);
|
||||||
|
animation: portal-hero-scan-move 4s linear infinite;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
@keyframes portal-hero-scan-move {
|
||||||
|
0% { transform: translateY(-100%); }
|
||||||
|
100% { transform: translateY(80vh); }
|
||||||
|
}
|
||||||
|
.portal-hero-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1.08fr) minmax(340px, 0.92fr);
|
||||||
|
gap: clamp(2rem, 4vw, 4rem);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.portal-hero-copy {
|
||||||
|
max-width: 760px;
|
||||||
|
}
|
||||||
|
.portal-hero-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--accent-dim);
|
||||||
|
border: 1px solid var(--surface-border);
|
||||||
|
padding: 0.35rem 0.9rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.portal-hero-badge::before {
|
||||||
|
content: '';
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background: var(--accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: portal-pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.portal-hero h1 {
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: clamp(3.25rem, 7vw, 6.25rem);
|
||||||
|
line-height: 0.98;
|
||||||
|
letter-spacing: -0.04em;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.portal-hero h1 .hero-sub {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0.8rem;
|
||||||
|
font-size: 0.28em;
|
||||||
|
font-weight: 300;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
-webkit-text-fill-color: var(--text-muted);
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.portal-hero-desc {
|
||||||
|
font-size: clamp(1rem, 1.25vw, 1.18rem);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
max-width: 640px;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
line-height: 1.9;
|
||||||
|
}
|
||||||
|
.portal-hero-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.85rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.portal-hero-panel {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 24px;
|
||||||
|
box-shadow: 0 16px 60px rgba(0, 0, 0, 0.16);
|
||||||
|
}
|
||||||
|
.portal-hero-panel::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.04), transparent 32%),
|
||||||
|
radial-gradient(circle at top right, rgba(0, 229, 255, 0.12), transparent 45%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.portal-panel-header,
|
||||||
|
.portal-panel-shortcuts {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.portal-panel-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.portal-panel-label {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.66rem;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--accent-dim);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.portal-panel-title {
|
||||||
|
font-size: clamp(1.35rem, 2vw, 1.85rem);
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
.portal-panel-shortcuts {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.85rem;
|
||||||
|
}
|
||||||
|
.portal-shortcut {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 3rem minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem 1.05rem;
|
||||||
|
border-radius: 18px;
|
||||||
|
border: 1px solid var(--surface-border);
|
||||||
|
background: color-mix(in srgb, var(--background) 36%, transparent);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: transform 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease, background 0.3s ease;
|
||||||
|
}
|
||||||
|
.portal-shortcut:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
border-color: var(--card-accent);
|
||||||
|
box-shadow: 0 0 28px var(--card-glow);
|
||||||
|
background: color-mix(in srgb, var(--background) 28%, transparent);
|
||||||
|
}
|
||||||
|
.portal-shortcut-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid color-mix(in srgb, var(--card-accent) 24%, var(--surface-border));
|
||||||
|
color: var(--card-accent);
|
||||||
|
background: color-mix(in srgb, var(--card-accent) 12%, transparent);
|
||||||
|
}
|
||||||
|
.portal-shortcut-copy {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.2rem;
|
||||||
|
}
|
||||||
|
.portal-shortcut-title {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
.portal-shortcut-desc {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.portal-shortcut-arrow {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
color: var(--card-accent);
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: transform 0.25s ease, opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
.portal-shortcut:hover .portal-shortcut-arrow {
|
||||||
|
transform: translate(2px, -2px);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.portal-hero-voltage {
|
||||||
|
position: absolute;
|
||||||
|
right: clamp(1rem, 3vw, 3rem);
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
opacity: 0.22;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.portal-voltage-bar {
|
||||||
|
width: 4px;
|
||||||
|
height: 40px;
|
||||||
|
background: var(--surface-border);
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.portal-voltage-bar::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--accent);
|
||||||
|
border-radius: 2px;
|
||||||
|
animation: portal-voltage 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes portal-voltage {
|
||||||
|
0%, 100% { height: 60%; }
|
||||||
|
25% { height: 85%; }
|
||||||
|
50% { height: 72%; }
|
||||||
|
75% { height: 95%; }
|
||||||
|
}
|
||||||
|
.portal-voltage-bar:nth-child(2)::after { animation-delay: 0.5s; }
|
||||||
|
.portal-voltage-bar:nth-child(3)::after { animation-delay: 1s; }
|
||||||
|
.portal-voltage-bar:nth-child(4)::after { animation-delay: 1.5s; }
|
||||||
|
.portal-voltage-bar:nth-child(5)::after { animation-delay: 0.3s; }
|
||||||
|
|
||||||
|
/* ── Portal Landing: Section common ── */
|
||||||
|
.portal-section {
|
||||||
|
padding: 4.75rem 0;
|
||||||
|
}
|
||||||
|
.portal-section-tag {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.68rem;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--accent-dim);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
.portal-section-tag::before {
|
||||||
|
content: '';
|
||||||
|
width: 20px;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
.portal-section-title {
|
||||||
|
font-size: clamp(1.7rem, 3.5vw, 2.7rem);
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
line-height: 1.15;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
max-width: 760px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Portal Landing: Feature cards ── */
|
||||||
|
.portal-features-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 1.4rem;
|
||||||
|
}
|
||||||
|
.portal-feature-card {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: transform 0.3s, border-color 0.3s, box-shadow 0.3s;
|
||||||
|
}
|
||||||
|
.portal-feature-card::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(90deg, var(--accent), transparent);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
.portal-feature-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
border-color: var(--input-border);
|
||||||
|
box-shadow: var(--glow-cyan);
|
||||||
|
}
|
||||||
|
.portal-feature-card:hover::after { opacity: 1; }
|
||||||
|
|
||||||
|
/* ── Portal Landing: Model/Tool cards ── */
|
||||||
|
.portal-model-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.portal-model-card {
|
||||||
|
text-align: center;
|
||||||
|
transition: transform 0.3s, border-color 0.3s, box-shadow 0.3s;
|
||||||
|
}
|
||||||
|
.portal-model-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
border-color: var(--input-border);
|
||||||
|
box-shadow: var(--glow-cyan);
|
||||||
|
}
|
||||||
|
.portal-tool-card {
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: transform 0.3s, border-color 0.3s;
|
||||||
|
}
|
||||||
|
.portal-tool-card::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(90deg, transparent, var(--accent-purple), transparent);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
.portal-tool-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
border-color: rgba(124, 77, 255, 0.2);
|
||||||
|
}
|
||||||
|
.portal-tool-card:hover::after { opacity: 1; }
|
||||||
|
|
||||||
|
/* ── Portal Landing: Disclaimer ── */
|
||||||
|
.portal-disclaimer {
|
||||||
|
border-top: 1px solid var(--surface-border);
|
||||||
|
padding: 4.5rem 0 0;
|
||||||
|
}
|
||||||
|
.portal-disclaimer-panel {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 2.2rem;
|
||||||
|
}
|
||||||
|
.portal-disclaimer-panel::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(90deg, var(--accent-dim), var(--accent), transparent);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Portal Landing: Responsive ── */
|
||||||
|
@media (max-width: 1180px) {
|
||||||
|
.portal-hero-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.portal-hero-voltage {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.portal-cards {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.portal-features-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.portal-container {
|
||||||
|
width: min(calc(100% - 1.5rem), 1440px);
|
||||||
|
}
|
||||||
|
.portal-nav-inner {
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
.portal-nav-menu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.portal-nav-controls {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.portal-hero {
|
||||||
|
min-height: auto;
|
||||||
|
padding: 2.75rem 0 4rem;
|
||||||
|
}
|
||||||
|
.portal-hero h1 {
|
||||||
|
font-size: clamp(2.7rem, 12vw, 4rem);
|
||||||
|
}
|
||||||
|
.portal-features-grid,
|
||||||
|
.portal-cards {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.portal-disclaimer-panel {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Recharts overrides */
|
/* Recharts overrides */
|
||||||
.recharts-cartesian-grid-horizontal line,
|
.recharts-cartesian-grid-horizontal line,
|
||||||
.recharts-cartesian-grid-vertical line {
|
.recharts-cartesian-grid-vertical line {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { motion } from "motion/react";
|
import { motion } from "motion/react";
|
||||||
import {
|
import {
|
||||||
Activity,
|
|
||||||
MonitorCheck,
|
MonitorCheck,
|
||||||
BookOpen,
|
BookOpen,
|
||||||
BarChart3,
|
BarChart3,
|
||||||
@@ -11,13 +10,21 @@ import {
|
|||||||
Zap,
|
Zap,
|
||||||
Shield,
|
Shield,
|
||||||
Server,
|
Server,
|
||||||
|
Globe,
|
||||||
|
RefreshCw,
|
||||||
|
Puzzle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useI18n } from "@/lib/i18n";
|
import { useI18n, type TranslationKey } from "@/lib/i18n";
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════
|
const PLATFORM_URL = "http://192.168.111.90:8016/console/personal";
|
||||||
Configure service URLs here.
|
|
||||||
Change these to your actual endpoints.
|
const SERVICE_URLS: Record<string, string> = {
|
||||||
═══════════════════════════════════════════════════════ */
|
monitoring: "http://192.168.111.90:8018",
|
||||||
|
docs: "http://192.168.111.90:8087/sinocode/current/",
|
||||||
|
analytics: "http://192.168.111.90:8019",
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ═══ Data ═══ */
|
||||||
const SERVICES = [
|
const SERVICES = [
|
||||||
{
|
{
|
||||||
key: "monitoring" as const,
|
key: "monitoring" as const,
|
||||||
@@ -42,11 +49,44 @@ const SERVICES = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const SERVICE_URLS: Record<string, string> = {
|
interface FeatureItem {
|
||||||
monitoring: "http://192.168.111.90:8018",
|
icon: typeof Zap;
|
||||||
docs: "http://192.168.111.90:8087/sinocode/current/",
|
titleKey: TranslationKey;
|
||||||
analytics: "http://192.168.111.90:8019",
|
descKey: TranslationKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FEATURES: FeatureItem[] = [
|
||||||
|
{ icon: Zap, titleKey: "portal.features.unified", descKey: "portal.features.unifiedDesc" },
|
||||||
|
{ icon: Shield, titleKey: "portal.features.security", descKey: "portal.features.securityDesc" },
|
||||||
|
{ icon: BarChart3, titleKey: "portal.features.monitor", descKey: "portal.features.monitorDesc" },
|
||||||
|
{ icon: Globe, titleKey: "portal.features.global", descKey: "portal.features.globalDesc" },
|
||||||
|
{ icon: RefreshCw, titleKey: "portal.features.failover", descKey: "portal.features.failoverDesc" },
|
||||||
|
{ icon: Puzzle, titleKey: "portal.features.flexible", descKey: "portal.features.flexibleDesc" },
|
||||||
|
];
|
||||||
|
|
||||||
|
interface ModelItem { name: string; provider: string; }
|
||||||
|
const MODELS: ModelItem[] = [
|
||||||
|
{ name: "Claude", provider: "Anthropic" },
|
||||||
|
{ name: "GPT", provider: "OpenAI" },
|
||||||
|
{ name: "Gemini", provider: "Google" },
|
||||||
|
{ name: "DeepSeek", provider: "DeepSeek" },
|
||||||
|
{ name: "Grok", provider: "xAI" },
|
||||||
|
];
|
||||||
|
|
||||||
|
interface ToolItem { name: string; tag: string; }
|
||||||
|
const TOOLS: ToolItem[] = [
|
||||||
|
{ name: "Claude Code", tag: "Anthropic" },
|
||||||
|
{ name: "Codex", tag: "OpenAI" },
|
||||||
|
{ name: "Gemini CLI", tag: "Google" },
|
||||||
|
{ name: "OpenCode", tag: "Community" },
|
||||||
|
{ name: "Droid", tag: "Community" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const fadeUp = {
|
||||||
|
hidden: { opacity: 0, y: 24 },
|
||||||
|
visible: { opacity: 1, y: 0, transition: { duration: 0.5, ease: "easeOut" as const } },
|
||||||
};
|
};
|
||||||
|
const stagger = { visible: { transition: { staggerChildren: 0.08 } } };
|
||||||
|
|
||||||
export default function PortalPage() {
|
export default function PortalPage() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@@ -59,13 +99,11 @@ export default function PortalPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="portal-page">
|
<div className="portal-page">
|
||||||
{/* ── Background layers ── */}
|
{/* ── Background layers (preserved) ── */}
|
||||||
<div className="portal-grid" />
|
<div className="portal-grid" />
|
||||||
<div className="portal-orb portal-orb--1" />
|
<div className="portal-orb portal-orb--1" />
|
||||||
<div className="portal-orb portal-orb--2" />
|
<div className="portal-orb portal-orb--2" />
|
||||||
<div className="portal-orb portal-orb--3" />
|
<div className="portal-orb portal-orb--3" />
|
||||||
|
|
||||||
{/* Decorative floating particles */}
|
|
||||||
<div className="portal-particles">
|
<div className="portal-particles">
|
||||||
{Array.from({ length: 6 }).map((_, i) => (
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
<div key={i} className="portal-particle" style={{
|
<div key={i} className="portal-particle" style={{
|
||||||
@@ -77,49 +115,137 @@ export default function PortalPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── Header / Branding ── */}
|
<main className="portal-main">
|
||||||
<motion.div
|
{/* ── 2. HERO ── */}
|
||||||
className="portal-header"
|
<section className="portal-hero">
|
||||||
initial={{ opacity: 0, y: -40 }}
|
<div className="portal-hero-scan" />
|
||||||
animate={{ opacity: 1, y: 0 }}
|
<div className="portal-container portal-hero-grid">
|
||||||
transition={{ duration: 0.9, ease: [0.22, 1, 0.36, 1] }}
|
<motion.div className="portal-hero-copy" initial="hidden" animate="visible" variants={stagger}>
|
||||||
>
|
<motion.div className="portal-hero-badge" variants={fadeUp}>
|
||||||
{/* Logo mark */}
|
{t("portal.hero.badge")}
|
||||||
<motion.div
|
|
||||||
className="portal-logo-mark"
|
|
||||||
initial={{ scale: 0.5, opacity: 0 }}
|
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
|
||||||
transition={{ delay: 0.2, duration: 0.6, ease: "backOut" }}
|
|
||||||
>
|
|
||||||
<Activity className="h-6 w-6" />
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
<motion.h1 variants={fadeUp}>
|
||||||
<h1 className="portal-title">
|
<span className="gradient-text">SinoCode</span>
|
||||||
<span className="portal-title-glow">Sino</span>
|
<span className="hero-sub">{t("portal.subtitle")}</span>
|
||||||
<span className="portal-title-accent">Code</span>
|
</motion.h1>
|
||||||
</h1>
|
<motion.p className="portal-hero-desc" variants={fadeUp}>
|
||||||
|
{t("portal.hero.desc")}
|
||||||
<motion.p
|
|
||||||
className="portal-subtitle"
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
transition={{ delay: 0.5, duration: 0.8 }}
|
|
||||||
>
|
|
||||||
{t("portal.subtitle")}
|
|
||||||
</motion.p>
|
</motion.p>
|
||||||
|
<motion.div className="portal-hero-actions" variants={fadeUp}>
|
||||||
{/* Animated scan divider */}
|
<a
|
||||||
<motion.div
|
href={PLATFORM_URL}
|
||||||
className="portal-divider"
|
target="_blank"
|
||||||
initial={{ scaleX: 0 }}
|
rel="noopener noreferrer"
|
||||||
animate={{ scaleX: 1 }}
|
className="btn-accent rounded-sm px-6 py-3 font-mono text-sm tracking-wider uppercase no-underline font-medium"
|
||||||
transition={{ delay: 0.6, duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
|
style={{ background: "var(--accent)", color: "var(--background)", borderColor: "var(--accent)" }}
|
||||||
>
|
>
|
||||||
<div className="portal-divider-scan" />
|
{t("portal.hero.cta")}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={SERVICE_URLS.docs}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="btn-accent rounded-sm px-6 py-3 font-mono text-sm tracking-wider uppercase no-underline"
|
||||||
|
>
|
||||||
|
{t("portal.hero.docs")} →
|
||||||
|
</a>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* ── Service Cards ── */}
|
<motion.div
|
||||||
|
className="portal-hero-panel glass"
|
||||||
|
initial={{ opacity: 0, x: 32, y: 24 }}
|
||||||
|
animate={{ opacity: 1, x: 0, y: 0 }}
|
||||||
|
transition={{ delay: 0.2, duration: 0.65, ease: [0.22, 1, 0.36, 1] }}
|
||||||
|
>
|
||||||
|
<div className="portal-panel-header">
|
||||||
|
<div>
|
||||||
|
<div className="portal-panel-label">{t("portal.services.tag")}</div>
|
||||||
|
<div className="portal-panel-title">{t("common.systemOnline")}</div>
|
||||||
|
</div>
|
||||||
|
<div className="portal-status-dot" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="portal-panel-shortcuts">
|
||||||
|
{SERVICES.map((service) => {
|
||||||
|
const Icon = service.icon;
|
||||||
|
const info = i18nMap[service.key];
|
||||||
|
const url = SERVICE_URLS[service.key];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
key={service.key}
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="portal-shortcut"
|
||||||
|
style={{
|
||||||
|
"--card-accent": `var(${service.accentVar})`,
|
||||||
|
"--card-glow": service.glowColor,
|
||||||
|
} as React.CSSProperties}
|
||||||
|
>
|
||||||
|
<div className="portal-shortcut-icon">
|
||||||
|
<Icon size={18} strokeWidth={1.7} />
|
||||||
|
</div>
|
||||||
|
<div className="portal-shortcut-copy">
|
||||||
|
<span className="portal-shortcut-title">{info.title}</span>
|
||||||
|
<span className="portal-shortcut-desc">{info.desc}</span>
|
||||||
|
</div>
|
||||||
|
<ArrowUpRight className="portal-shortcut-arrow" />
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="portal-hero-voltage" aria-hidden="true">
|
||||||
|
{Array.from({ length: 5 }).map((_, i) => (
|
||||||
|
<div key={i} className="portal-voltage-bar" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── 3. FEATURES ── */}
|
||||||
|
<section id="features" className="portal-section">
|
||||||
|
<div className="portal-container">
|
||||||
|
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeUp}>
|
||||||
|
<div className="portal-section-tag">{t("portal.features.tag")}</div>
|
||||||
|
<h2 className="portal-section-title">{t("portal.features.title")}</h2>
|
||||||
|
</motion.div>
|
||||||
|
<motion.div
|
||||||
|
className="portal-features-grid"
|
||||||
|
initial="hidden"
|
||||||
|
whileInView="visible"
|
||||||
|
viewport={{ once: true, amount: 0.2 }}
|
||||||
|
variants={stagger}
|
||||||
|
>
|
||||||
|
{FEATURES.map((f) => {
|
||||||
|
const Icon = f.icon;
|
||||||
|
return (
|
||||||
|
<motion.div key={f.titleKey} className="glass portal-feature-card p-6" variants={fadeUp}>
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-center w-10 h-10 rounded-lg mb-4"
|
||||||
|
style={{ background: "var(--btn-active-bg)", border: "1px solid var(--surface-border)", color: "var(--text-accent)" }}
|
||||||
|
>
|
||||||
|
<Icon size={20} />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-sm font-semibold tracking-wide mb-2 text-t-primary">{t(f.titleKey)}</h3>
|
||||||
|
<p className="text-sm leading-relaxed text-t-muted">{t(f.descKey)}</p>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── 4. SERVICES ── */}
|
||||||
|
<section id="services" className="portal-section">
|
||||||
|
<div className="portal-container">
|
||||||
|
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeUp}>
|
||||||
|
<div className="portal-section-tag">{t("portal.services.tag")}</div>
|
||||||
|
<h2 className="portal-section-title">{t("portal.services.title")}</h2>
|
||||||
|
</motion.div>
|
||||||
<div className="portal-cards">
|
<div className="portal-cards">
|
||||||
{SERVICES.map((service, i) => {
|
{SERVICES.map((service, i) => {
|
||||||
const Icon = service.icon;
|
const Icon = service.icon;
|
||||||
@@ -138,62 +264,99 @@ export default function PortalPage() {
|
|||||||
"--card-accent": `var(${service.accentVar})`,
|
"--card-accent": `var(${service.accentVar})`,
|
||||||
"--card-glow": service.glowColor,
|
"--card-glow": service.glowColor,
|
||||||
} as React.CSSProperties}
|
} as React.CSSProperties}
|
||||||
initial={{ opacity: 0, y: 50, scale: 0.95 }}
|
initial={{ opacity: 0, y: 40 }}
|
||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
transition={{
|
viewport={{ once: true }}
|
||||||
delay: 0.4 + i * 0.15,
|
transition={{ delay: i * 0.1, duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
|
||||||
duration: 0.7,
|
whileHover={{ y: -10, transition: { type: "spring", stiffness: 400, damping: 25 } }}
|
||||||
ease: [0.22, 1, 0.36, 1],
|
|
||||||
}}
|
|
||||||
whileHover={{
|
|
||||||
y: -10,
|
|
||||||
transition: { type: "spring", stiffness: 400, damping: 25 },
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{/* Corner brackets */}
|
|
||||||
<span className="portal-corner portal-corner--tl" />
|
<span className="portal-corner portal-corner--tl" />
|
||||||
<span className="portal-corner portal-corner--tr" />
|
<span className="portal-corner portal-corner--tr" />
|
||||||
<span className="portal-corner portal-corner--bl" />
|
<span className="portal-corner portal-corner--bl" />
|
||||||
<span className="portal-corner portal-corner--br" />
|
<span className="portal-corner portal-corner--br" />
|
||||||
|
|
||||||
{/* Decorative bg icon */}
|
|
||||||
<DecorIcon className="portal-card-decor" />
|
<DecorIcon className="portal-card-decor" />
|
||||||
|
<div className="portal-card-icon"><Icon strokeWidth={1.5} /></div>
|
||||||
{/* Icon container */}
|
|
||||||
<div className="portal-card-icon">
|
|
||||||
<Icon strokeWidth={1.5} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Text content */}
|
|
||||||
<h3 className="portal-card-title">{info.title}</h3>
|
<h3 className="portal-card-title">{info.title}</h3>
|
||||||
<p className="portal-card-desc">{info.desc}</p>
|
<p className="portal-card-desc">{info.desc}</p>
|
||||||
|
|
||||||
{/* Enter indicator */}
|
|
||||||
<div className="portal-card-action">
|
<div className="portal-card-action">
|
||||||
<span className="portal-card-action-text">{t("portal.enter")}</span>
|
<span>{t("portal.enter")}</span>
|
||||||
<ArrowUpRight className="portal-card-action-icon" />
|
<ArrowUpRight className="portal-card-action-icon" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom highlight bar */}
|
|
||||||
<div className="portal-card-bar" />
|
<div className="portal-card-bar" />
|
||||||
</motion.a>
|
</motion.a>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── 5. ECOSYSTEM ── */}
|
||||||
|
<section id="ecosystem" className="portal-section">
|
||||||
|
<div className="portal-container">
|
||||||
|
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeUp}>
|
||||||
|
<div className="portal-section-tag">{t("portal.ecosystem.tag")}</div>
|
||||||
|
<h2 className="portal-section-title">{t("portal.ecosystem.title")}</h2>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeUp}>
|
||||||
|
<h3 className="text-sm font-mono tracking-widest uppercase text-t-muted mb-4">{t("portal.ecosystem.models")}</h3>
|
||||||
|
</motion.div>
|
||||||
|
<motion.div className="portal-model-grid mb-10" initial="hidden" whileInView="visible" viewport={{ once: true, amount: 0.2 }} variants={stagger}>
|
||||||
|
{MODELS.map((m) => (
|
||||||
|
<motion.div key={m.name} className="glass portal-model-card p-5 rounded-xl" variants={fadeUp}>
|
||||||
|
<div className="text-base font-bold tracking-wide text-t-primary mb-1">{m.name}</div>
|
||||||
|
<div className="text-xs font-mono text-t-muted tracking-wider uppercase">{m.provider}</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeUp}>
|
||||||
|
<h3 className="text-sm font-mono tracking-widest uppercase text-t-muted mb-1">{t("portal.ecosystem.tools")}</h3>
|
||||||
|
<p className="text-sm text-t-muted mb-4">{t("portal.ecosystem.toolsDesc")}</p>
|
||||||
|
</motion.div>
|
||||||
|
<motion.div className="portal-model-grid" initial="hidden" whileInView="visible" viewport={{ once: true, amount: 0.2 }} variants={stagger}>
|
||||||
|
{TOOLS.map((tool) => (
|
||||||
|
<motion.div key={tool.name} className="glass portal-tool-card p-5 rounded-xl" variants={fadeUp}>
|
||||||
|
<div className="text-sm font-semibold tracking-wide text-t-primary mb-1">{tool.name}</div>
|
||||||
|
<div className="text-xs font-mono tracking-wider uppercase" style={{ color: "var(--accent-purple)" }}>{tool.tag}</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── 6. DISCLAIMER ── */}
|
||||||
|
<section className="portal-disclaimer">
|
||||||
|
<div className="portal-container">
|
||||||
|
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeUp}>
|
||||||
|
<div className="portal-section-tag">{t("portal.disclaimer.tag")}</div>
|
||||||
|
<h2 className="portal-section-title">{t("portal.disclaimer.title")}</h2>
|
||||||
|
</motion.div>
|
||||||
|
<motion.div className="glass portal-disclaimer-panel rounded-xl" initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeUp}>
|
||||||
|
<p className="text-sm leading-loose text-t-secondary mb-3">{t("portal.disclaimer.p1")}</p>
|
||||||
|
<p className="text-sm leading-loose text-t-secondary mb-4">{t("portal.disclaimer.p2")}</p>
|
||||||
|
<div className="pt-3 border-t" style={{ borderColor: "var(--surface-border)" }}>
|
||||||
|
<span className="text-xs font-mono text-t-muted tracking-wider">{t("portal.disclaimer.copyright")}</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{/* ── Footer status ── */}
|
{/* ── Footer status ── */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="portal-footer"
|
className="portal-footer portal-container"
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
whileInView={{ opacity: 1 }}
|
||||||
transition={{ delay: 1.2, duration: 0.6 }}
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
>
|
>
|
||||||
<div className="portal-status-dot" />
|
<div className="portal-status-dot" />
|
||||||
<span className="portal-status-text">
|
<span className="portal-status-text">
|
||||||
<Server className="inline-block h-3 w-3 mr-1" style={{ verticalAlign: "-1px" }} />
|
<Server className="inline-block h-3 w-3 mr-1" style={{ verticalAlign: "-1px" }} />
|
||||||
System Online
|
{t("common.systemOnline")}
|
||||||
</span>
|
</span>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
70
lib/i18n.tsx
70
lib/i18n.tsx
@@ -95,6 +95,40 @@ const translations = {
|
|||||||
"portal.analytics": "使用统计",
|
"portal.analytics": "使用统计",
|
||||||
"portal.analyticsDesc": "API 调用数据分析与可视化看板",
|
"portal.analyticsDesc": "API 调用数据分析与可视化看板",
|
||||||
"portal.enter": "进入",
|
"portal.enter": "进入",
|
||||||
|
"portal.nav.features": "功能",
|
||||||
|
"portal.nav.services": "服务",
|
||||||
|
"portal.nav.ecosystem": "生态",
|
||||||
|
"portal.nav.start": "开始使用",
|
||||||
|
"portal.hero.badge": "企业内部平台 · 系统运行中",
|
||||||
|
"portal.hero.desc": "统一 AI 模型接口,兼容 OpenAI 格式,支持平滑迁移与稳定响应。为企业内所有人提供统一、稳定的 AI 能力入口。",
|
||||||
|
"portal.hero.cta": "进入平台",
|
||||||
|
"portal.hero.docs": "查看文档",
|
||||||
|
"portal.features.tag": "核心能力",
|
||||||
|
"portal.features.title": "平台特性",
|
||||||
|
"portal.features.unified": "统一接口",
|
||||||
|
"portal.features.unifiedDesc": "完全兼容 OpenAI API 格式,一行代码切换 GPT、Claude、Gemini、DeepSeek 等主流模型",
|
||||||
|
"portal.features.security": "安全可靠",
|
||||||
|
"portal.features.securityDesc": "企业级加密传输,密钥隔离管理,完整审计日志,数据安全有保障",
|
||||||
|
"portal.features.monitor": "用量监控",
|
||||||
|
"portal.features.monitorDesc": "实时 Token 消耗统计,按模型、按渠道精细化分析,成本一目了然",
|
||||||
|
"portal.features.global": "全球加速",
|
||||||
|
"portal.features.globalDesc": "跨区域链路调度与稳定转发能力,自动选择更优访问路径,兼顾响应速度与可用性。",
|
||||||
|
"portal.features.failover": "自动容灾",
|
||||||
|
"portal.features.failoverDesc": "内置多渠道负载均衡与故障转移,单一渠道异常时自动切换,服务永不中断",
|
||||||
|
"portal.features.flexible": "灵活扩展",
|
||||||
|
"portal.features.flexibleDesc": "支持自定义模型映射、请求改写、速率限制,完善的 Webhook 与回调机制",
|
||||||
|
"portal.services.tag": "平台服务",
|
||||||
|
"portal.services.title": "服务入口",
|
||||||
|
"portal.ecosystem.tag": "生态接入",
|
||||||
|
"portal.ecosystem.title": "支持的模型与工具",
|
||||||
|
"portal.ecosystem.models": "主流 AI 模型",
|
||||||
|
"portal.ecosystem.tools": "AI 编程工具",
|
||||||
|
"portal.ecosystem.toolsDesc": "统一 API Key,即可接入以下工具",
|
||||||
|
"portal.disclaimer.tag": "合规声明",
|
||||||
|
"portal.disclaimer.title": "服务使用与内容责任说明",
|
||||||
|
"portal.disclaimer.p1": "本平台作为 AI 模型 API 网关服务提供者,严禁利用本平台生成任何涉及色情、暴力、政治敏感、侵犯知识产权或其他违反中华人民共和国法律法规的内容。如发现违规使用,平台有权立即终止服务并配合相关部门依法处理。",
|
||||||
|
"portal.disclaimer.p2": "用户须自觉遵守所在地区法律法规及相关政策。因用户自身行为产生的一切法律后果,由用户自行承担。",
|
||||||
|
"portal.disclaimer.copyright": "© 2025 SinoCode",
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
"nav.overview": "Overview",
|
"nav.overview": "Overview",
|
||||||
@@ -173,10 +207,44 @@ const translations = {
|
|||||||
"portal.analytics": "Usage Analytics",
|
"portal.analytics": "Usage Analytics",
|
||||||
"portal.analyticsDesc": "API usage data analytics & visualization dashboard",
|
"portal.analyticsDesc": "API usage data analytics & visualization dashboard",
|
||||||
"portal.enter": "Enter",
|
"portal.enter": "Enter",
|
||||||
|
"portal.nav.features": "Features",
|
||||||
|
"portal.nav.services": "Services",
|
||||||
|
"portal.nav.ecosystem": "Ecosystem",
|
||||||
|
"portal.nav.start": "Get Started",
|
||||||
|
"portal.hero.badge": "Enterprise Internal Platform · System Online",
|
||||||
|
"portal.hero.desc": "A unified AI model interface compatible with the OpenAI format, designed for smooth migration and stable response times. It provides a single, reliable AI access point for everyone across the enterprise.",
|
||||||
|
"portal.hero.cta": "Enter Platform",
|
||||||
|
"portal.hero.docs": "View Docs",
|
||||||
|
"portal.features.tag": "Core Capabilities",
|
||||||
|
"portal.features.title": "Platform Features",
|
||||||
|
"portal.features.unified": "Unified Interface",
|
||||||
|
"portal.features.unifiedDesc": "Fully compatible with OpenAI API format. Switch between GPT, Claude, Gemini, DeepSeek with a single line of code",
|
||||||
|
"portal.features.security": "Security & Reliability",
|
||||||
|
"portal.features.securityDesc": "Enterprise-grade encryption, isolated key management, complete audit logs, data security guaranteed",
|
||||||
|
"portal.features.monitor": "Usage Monitoring",
|
||||||
|
"portal.features.monitorDesc": "Real-time token consumption stats, granular analysis by model and channel, costs at a glance",
|
||||||
|
"portal.features.global": "Global Acceleration",
|
||||||
|
"portal.features.globalDesc": "Cross-region routing and resilient forwarding automatically choose the better path for responsive and reliable access.",
|
||||||
|
"portal.features.failover": "Auto Failover",
|
||||||
|
"portal.features.failoverDesc": "Built-in multi-channel load balancing and failover, automatic switching on channel failure, zero downtime",
|
||||||
|
"portal.features.flexible": "Flexible Extension",
|
||||||
|
"portal.features.flexibleDesc": "Custom model mapping, request rewriting, rate limiting, complete webhook and callback mechanisms",
|
||||||
|
"portal.services.tag": "Platform Services",
|
||||||
|
"portal.services.title": "Service Portal",
|
||||||
|
"portal.ecosystem.tag": "Ecosystem",
|
||||||
|
"portal.ecosystem.title": "Supported Models & Tools",
|
||||||
|
"portal.ecosystem.models": "AI Models",
|
||||||
|
"portal.ecosystem.tools": "AI Coding Tools",
|
||||||
|
"portal.ecosystem.toolsDesc": "One unified API Key for the following tools",
|
||||||
|
"portal.disclaimer.tag": "Compliance",
|
||||||
|
"portal.disclaimer.title": "Service Usage & Content Responsibility",
|
||||||
|
"portal.disclaimer.p1": "As an AI model API gateway, this platform strictly prohibits generating content involving pornography, violence, political sensitivity, IP infringement, or other violations of PRC laws. Violations will result in immediate service termination and cooperation with authorities.",
|
||||||
|
"portal.disclaimer.p2": "Users must comply with local laws and regulations. All legal consequences arising from user actions are the sole responsibility of the user.",
|
||||||
|
"portal.disclaimer.copyright": "© 2025 SinoCode",
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type TranslationKey = keyof typeof translations.zh;
|
export type TranslationKey = keyof typeof translations.zh;
|
||||||
|
|
||||||
interface I18nContextType {
|
interface I18nContextType {
|
||||||
locale: Locale;
|
locale: Locale;
|
||||||
|
|||||||
Reference in New Issue
Block a user