montana/Русский/Логистика/landing_v2_source.html

3839 lines
170 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>SeaFare Montana — AI Maritime Logistics Platform</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@500;600;700;800&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://accounts.google.com/gsi/client" async defer></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg-dark: #050d1a;
--bg-panel: #0a1929;
--bg-surface: #0f2137;
--bg-chat: #0c1522;
--bg-input: #162240;
--bg-hover: rgba(255,255,255,0.04);
--accent: #2dd4bf;
--accent2: #0ea5e9;
--accent3: #818cf8;
--accent-warm: #c9956b;
--text: #f0f4f8;
--text-dim: #94a3b8;
--text-muted: #64748b;
--user-msg: #1a3a5c;
--bot-msg: rgba(22,34,64,0.45);
--border: rgba(255,255,255,0.08);
--border-strong: rgba(255,255,255,0.14);
--success: #34d399;
--warn: #fbbf24;
--danger: #f87171;
}
body {
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: var(--bg-dark);
color: var(--text);
overflow-x: hidden;
scroll-behavior: smooth;
}
h1, h2, h3, .hero-title, .section-title {
font-family: 'Plus Jakarta Sans', 'Inter', sans-serif;
}
/* Sidebar */
.sidebar {
width: 280px;
background: var(--bg-panel);
border-right: 1px solid var(--border-strong);
display: flex;
flex-direction: column;
flex-shrink: 0;
}
.sidebar-header {
padding: 20px;
border-bottom: 1px solid var(--border);
}
.logo {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
.logo-icon {
width: 42px;
height: 42px;
background: linear-gradient(135deg, var(--accent), var(--accent2));
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
}
.logo-text h1 {
font-size: 16px;
font-weight: 600;
color: var(--text);
}
.logo-text span {
font-size: 11px;
color: var(--text-dim);
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
background: rgba(0, 200, 83, 0.1);
border: 1px solid rgba(0, 200, 83, 0.3);
border-radius: 20px;
font-size: 11px;
color: var(--success);
margin-top: 12px;
}
.status-dot {
width: 6px;
height: 6px;
background: var(--success);
border-radius: 50%;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* Services */
.services {
padding: 16px 20px;
flex: 1;
overflow-y: auto;
}
.services h3 {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-dim);
margin-bottom: 12px;
}
.service-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
border-radius: 8px;
margin-bottom: 4px;
cursor: pointer;
transition: background 0.2s;
}
.service-item:hover {
background: rgba(0, 180, 216, 0.1);
}
.service-icon {
font-size: 18px;
width: 32px;
text-align: center;
}
.service-info {
flex: 1;
}
.service-info .name {
font-size: 13px;
font-weight: 500;
}
.service-info .price {
font-size: 11px;
color: var(--text-dim);
}
.price-tag {
font-size: 11px;
padding: 2px 8px;
border-radius: 10px;
background: rgba(0, 180, 216, 0.15);
color: var(--accent);
font-weight: 600;
}
.price-free {
background: rgba(0, 200, 83, 0.15);
color: var(--success);
}
/* Sidebar footer */
.sidebar-footer {
padding: 16px 20px;
border-top: 1px solid var(--border);
font-size: 11px;
color: var(--text-dim);
text-align: center;
}
.sidebar-footer a {
color: var(--accent);
text-decoration: none;
}
/* Main chat area */
.main {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
/* Chat header */
.chat-header {
padding: 16px 24px;
background: var(--bg-panel);
border-bottom: 1px solid var(--border-strong);
display: flex;
align-items: center;
justify-content: space-between;
}
.chat-header-left {
display: flex;
align-items: center;
gap: 12px;
}
.chat-header-left h2 {
font-size: 15px;
font-weight: 600;
}
.chat-header-left span {
font-size: 12px;
color: var(--text-dim);
}
.header-actions {
display: flex;
gap: 8px;
align-items: center;
}
.header-btn {
padding: 6px 14px;
border: 1px solid var(--border);
border-radius: 6px;
background: transparent;
color: var(--text-dim);
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
.header-btn:hover {
border-color: var(--accent);
color: var(--accent);
}
/* Language switcher */
.lang-switcher {
display: flex;
gap: 2px;
background: var(--bg-input);
border-radius: 6px;
padding: 2px;
border: 1px solid var(--border);
}
.lang-btn {
padding: 4px 10px;
border: none;
border-radius: 4px;
background: transparent;
color: var(--text-dim);
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
font-weight: 500;
}
.lang-btn:hover {
color: var(--text);
}
.lang-btn.active {
background: var(--accent2);
color: white;
}
/* Messages */
.messages {
flex: 1;
overflow-y: auto;
padding: 28px 24px 100px;
display: flex;
flex-direction: column;
gap: 24px;
background: var(--bg-chat);
scroll-behavior: smooth;
}
.message {
display: flex;
gap: 12px;
max-width: 85%;
animation: messageIn 0.4s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes messageIn {
from { opacity: 0; transform: translateY(10px) scale(0.98); filter: blur(3px); }
to { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
}
.message.user {
align-self: flex-end;
flex-direction: row-reverse;
}
.msg-avatar {
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
flex-shrink: 0;
}
.message.bot .msg-avatar {
background: linear-gradient(135deg, var(--accent), var(--accent2));
}
.message.user .msg-avatar {
background: var(--user-msg);
}
.msg-content {
padding: 16px 20px;
border-radius: 18px;
font-size: 15px;
line-height: 1.65;
letter-spacing: -0.01em;
}
.message.bot .msg-content {
background: var(--bot-msg);
border-radius: 18px;
}
.message.user .msg-content {
background: linear-gradient(135deg, var(--accent2), #005a99);
color: white;
border-radius: 20px;
padding: 12px 18px;
}
.msg-content pre {
background: rgba(0, 0, 0, 0.35);
padding: 14px 18px;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.06);
margin: 10px 0;
font-size: 13px;
overflow-x: auto;
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
line-height: 1.6;
}
.msg-content code {
background: rgba(0, 0, 0, 0.3);
padding: 2px 7px;
border-radius: 5px;
font-size: 13px;
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
}
.msg-content .vessel-card {
background: rgba(0, 180, 216, 0.06);
border: 1px solid rgba(0, 180, 216, 0.15);
border-radius: 14px;
padding: 16px;
margin: 10px 0;
backdrop-filter: blur(8px);
}
.vessel-card .vessel-name {
font-size: 15px;
font-weight: 600;
color: var(--accent);
margin-bottom: 10px;
}
.vessel-card .vessel-row {
display: flex;
justify-content: space-between;
padding: 4px 0;
font-size: 13px;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}
.vessel-card .vessel-row:last-child { border-bottom: none; }
.vessel-card .vessel-label {
color: var(--text-dim);
}
.msg-time {
font-size: 10px;
color: var(--text-dim);
margin-top: 6px;
padding: 0 4px;
opacity: 0.7;
}
.message.user .msg-time {
text-align: right;
}
/* Typing indicator */
.typing {
display: flex;
gap: 5px;
padding: 8px 4px;
}
.typing span {
width: 6px;
height: 6px;
background: var(--accent);
border-radius: 50%;
animation: typingPulse 1.4s cubic-bezier(0.4, 0, 0.2, 1) infinite;
opacity: 0.4;
}
.typing span:nth-child(2) { animation-delay: 0.15s; }
.typing span:nth-child(3) { animation-delay: 0.3s; }
@keyframes typingPulse {
0%, 60%, 100% { transform: translateY(0) scale(1); opacity: 0.4; }
30% { transform: translateY(-5px) scale(1.2); opacity: 1; }
}
/* Input area */
.input-area {
padding: 0 24px 20px;
background: linear-gradient(to top, var(--bg-panel) 60%, transparent);
position: relative;
margin-top: -80px;
pointer-events: none;
padding-top: 80px;
}
.input-area > * { pointer-events: auto; }
.input-glass {
display: flex;
gap: 6px;
align-items: flex-end;
background: rgba(22, 34, 64, 0.65);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.10);
border-radius: 24px;
padding: 6px 6px 6px 18px;
transition: border-color 0.25s, box-shadow 0.25s;
}
.input-glass:focus-within {
border-color: rgba(0, 180, 216, 0.4);
box-shadow: 0 0 0 3px rgba(0, 180, 216, 0.08), 0 4px 24px rgba(0, 0, 0, 0.2);
}
.input-wrapper {
flex: 1;
position: relative;
}
.input-wrapper textarea {
width: 100%;
padding: 10px 0;
background: transparent;
border: none;
color: var(--text);
font-size: 15px;
font-family: inherit;
resize: none;
outline: none;
min-height: 40px;
max-height: 120px;
line-height: 1.5;
}
.input-wrapper textarea::placeholder {
color: var(--text-dim);
}
.send-btn {
width: 38px;
height: 38px;
background: linear-gradient(135deg, var(--accent), var(--accent2));
border: none;
border-radius: 12px;
color: white;
font-size: 16px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.15s, opacity 0.2s;
flex-shrink: 0;
}
.send-btn:hover { transform: scale(1.08); }
.send-btn:active { transform: scale(0.93); }
.send-btn:disabled { opacity: 0.3; cursor: not-allowed; transform: none; }
/* Voice input button */
.voice-btn {
width: 38px;
height: 38px;
background: transparent;
border: none;
border-radius: 12px;
color: var(--text-dim);
font-size: 18px;
cursor: pointer;
display: none;
align-items: center;
justify-content: center;
transition: all 0.2s;
flex-shrink: 0;
}
.voice-btn:hover { color: var(--text); background: rgba(255, 255, 255, 0.06); }
.voice-btn.recording {
background: rgba(255, 59, 48, 0.15);
color: #ff3b30;
animation: pulse-rec 1.5s ease infinite;
}
@keyframes pulse-rec {
0%, 100% { box-shadow: 0 0 0 0 rgba(255,59,48,0.4); }
50% { box-shadow: 0 0 0 8px rgba(255,59,48,0); }
}
.voice-btn.recording:hover { color: #ff3b30; }
.input-hint {
font-size: 11px;
color: var(--text-dim);
margin-top: 10px;
padding: 0 8px;
opacity: 0.7;
}
/* Quick actions */
.quick-actions {
display: flex;
gap: 8px;
margin-bottom: 14px;
flex-wrap: wrap;
}
.quick-btn {
padding: 8px 16px;
background: rgba(22, 34, 64, 0.5);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 20px;
color: var(--text-dim);
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.quick-btn:hover {
border-color: rgba(0, 180, 216, 0.3);
color: var(--accent);
background: rgba(0, 180, 216, 0.08);
}
/* Scrollbar */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.08); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.16); }
.messages { scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.08) transparent; }
/* Auth modal */
.auth-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(10, 22, 40, 0.92);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.auth-overlay.hidden { display: none; }
.auth-modal {
background: var(--bg-panel);
border: 1px solid var(--border);
border-radius: 16px;
padding: 36px;
width: 370px;
max-width: 90vw;
}
.auth-logo {
display: flex; align-items: center; gap: 12px;
justify-content: center; margin-bottom: 20px;
}
.auth-logo .logo-icon { width: 38px; height: 38px; font-size: 20px; }
.auth-logo h2 { font-size: 17px; font-weight: 600; }
.auth-modal h3 {
text-align: center; font-size: 15px;
margin-bottom: 18px; color: var(--text-dim);
}
.auth-field { margin-bottom: 12px; }
.auth-field input {
width: 100%; padding: 11px 14px;
background: var(--bg-input); border: 1px solid var(--border);
border-radius: 8px; color: var(--text);
font-size: 14px; font-family: inherit; outline: none;
transition: border-color 0.2s;
}
.auth-field input:focus { border-color: var(--accent); }
.auth-submit {
width: 100%; padding: 11px;
background: linear-gradient(135deg, var(--accent), var(--accent2));
border: none; border-radius: 8px; color: white;
font-size: 14px; font-weight: 600; cursor: pointer;
transition: opacity 0.2s; margin-top: 4px;
}
.auth-submit:hover { opacity: 0.9; }
.auth-submit:disabled { opacity: 0.5; cursor: not-allowed; }
.auth-divider {
display: flex; align-items: center; gap: 12px;
margin: 16px 0; color: var(--text-dim); font-size: 12px;
}
.auth-divider::before, .auth-divider::after {
content: ''; flex: 1; height: 1px; background: var(--border);
}
.google-btn-wrap {
display: flex; justify-content: center; margin-bottom: 4px;
}
.auth-switch { text-align: center; margin-top: 14px; }
.auth-switch a { color: var(--accent); text-decoration: none; font-size: 13px; }
.auth-close { text-align: center; margin-top: 10px; }
.auth-close a { color: var(--text-dim); text-decoration: none; font-size: 12px; }
.auth-close a:hover { color: var(--text); }
.auth-error {
background: rgba(255, 82, 82, 0.1);
border: 1px solid rgba(255, 82, 82, 0.3);
color: #ff5252; padding: 9px 14px; border-radius: 8px;
font-size: 13px; margin-bottom: 12px; text-align: center;
}
/* User info in header */
.user-info {
font-size: 13px; color: var(--text-dim);
display: flex; align-items: center; gap: 8px;
}
.admin-badge {
padding: 2px 8px; border-radius: 10px;
background: rgba(255, 107, 53, 0.2);
color: #ff6b35; font-size: 10px; font-weight: 700;
letter-spacing: 0.5px;
}
.admin-revenue-btn {
background: rgba(255, 107, 53, 0.15) !important;
color: #ff6b35 !important;
border: 1px solid rgba(255, 107, 53, 0.3) !important;
font-size: 12px !important;
padding: 3px 8px !important;
}
/* Revenue Modal */
.revenue-overlay {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.6); z-index: 1001;
display: flex; align-items: center; justify-content: center;
backdrop-filter: blur(4px);
}
.revenue-overlay.hidden { display: none; }
.revenue-modal {
background: var(--bg-panel); border-radius: 16px;
padding: 32px; width: 520px; max-width: 95vw;
max-height: 90vh; max-height: 90dvh; overflow-y: auto;
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
border: 1px solid var(--border);
}
.revenue-modal h2 {
color: var(--text); font-size: 20px; margin-bottom: 20px;
display: flex; align-items: center; gap: 10px;
}
.revenue-cards {
display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
margin-bottom: 20px;
}
.revenue-card {
background: rgba(22, 34, 64, 0.5); border-radius: 12px;
padding: 16px; text-align: center;
border: 1px solid var(--border);
}
.revenue-card .label {
font-size: 11px; color: var(--text-dim);
text-transform: uppercase; letter-spacing: 0.5px;
margin-bottom: 6px;
}
.revenue-card .value {
font-size: 22px; font-weight: 700; color: var(--text);
}
.revenue-card .value.green { color: var(--success); }
.revenue-card .value.orange { color: #ff6b35; }
.revenue-table {
width: 100%; border-collapse: collapse; margin-top: 16px;
}
.revenue-table th {
text-align: left; padding: 8px 10px; font-size: 11px;
color: var(--text-dim); text-transform: uppercase;
border-bottom: 1px solid var(--border);
}
.revenue-table td {
padding: 8px 10px; font-size: 13px; color: var(--text);
border-bottom: 1px solid rgba(255,255,255,0.04);
}
.revenue-close {
position: absolute; top: 12px; right: 16px;
background: none; border: none; color: var(--text-dim);
font-size: 24px; cursor: pointer;
}
.revenue-close:hover { color: var(--text); }
.balance-badge {
padding: 2px 8px; border-radius: 10px;
background: rgba(0, 200, 83, 0.15);
color: var(--success); font-size: 11px; font-weight: 600;
}
.topup-btn {
background: rgba(0, 200, 83, 0.15) !important;
color: var(--success) !important;
border: 1px solid rgba(0, 200, 83, 0.3) !important;
font-size: 12px !important;
padding: 3px 8px !important;
}
.topup-btn:hover {
background: rgba(0, 200, 83, 0.25) !important;
}
/* Deposit Modal */
.deposit-overlay {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.6); z-index: 1001;
display: flex; align-items: center; justify-content: center;
backdrop-filter: blur(4px);
}
.deposit-overlay.hidden { display: none; }
.deposit-modal {
background: var(--bg-panel); border-radius: 16px;
padding: 32px; width: 440px; max-width: 95vw;
max-height: 90vh; max-height: 90dvh; overflow-y: auto;
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
border: 1px solid var(--border);
text-align: center;
}
.deposit-modal h2 {
color: var(--text); font-size: 20px; margin-bottom: 16px;
display: flex; align-items: center; justify-content: center; gap: 10px;
}
.deposit-network {
display: inline-block;
padding: 4px 12px; border-radius: 8px;
background: rgba(0, 180, 216, 0.15); color: var(--accent);
font-size: 13px; font-weight: 600; margin-bottom: 16px;
}
.deposit-qr {
margin: 16px auto;
width: 200px; height: 200px;
background: #fff; border-radius: 12px;
padding: 8px;
display: flex; align-items: center; justify-content: center;
}
.deposit-qr img { width: 100%; height: 100%; }
.deposit-address-wrap {
background: var(--bg-input); border: 1px solid var(--border);
border-radius: 10px; padding: 12px; margin: 12px 0;
display: flex; align-items: center; gap: 8px;
}
.deposit-address {
flex: 1; font-family: monospace; font-size: 12px;
color: var(--text); word-break: break-all; text-align: left;
}
.deposit-copy-btn {
background: var(--accent); border: none; border-radius: 6px;
color: #fff; padding: 6px 12px; font-size: 12px;
cursor: pointer; flex-shrink: 0; transition: all 0.2s;
}
.deposit-copy-btn:hover { opacity: 0.85; }
.deposit-check-btn {
background: linear-gradient(135deg, var(--success), #00a844);
border: none; border-radius: 10px; color: #fff;
padding: 12px 24px; font-size: 14px; font-weight: 600;
cursor: pointer; width: 100%; margin-top: 16px;
transition: all 0.2s;
}
.deposit-check-btn:hover { opacity: 0.9; transform: scale(1.02); }
.deposit-check-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.deposit-result {
margin-top: 12px; padding: 10px; border-radius: 8px;
font-size: 13px;
}
.deposit-result.success {
background: rgba(0, 200, 83, 0.1); color: var(--success);
border: 1px solid rgba(0, 200, 83, 0.3);
}
.deposit-result.empty {
background: rgba(255, 167, 38, 0.1); color: var(--warn);
border: 1px solid rgba(255, 167, 38, 0.3);
}
.deposit-result.error {
background: rgba(255, 82, 82, 0.1); color: #ff5252;
border: 1px solid rgba(255, 82, 82, 0.3);
}
.deposit-close {
display: inline-block; margin-top: 16px;
color: var(--text-dim); cursor: pointer; font-size: 13px;
}
.deposit-close:hover { color: var(--text); }
.deposit-history { margin-top: 16px; text-align: left; }
.deposit-history h4 { font-size: 13px; color: var(--text-dim); margin-bottom: 8px; }
.deposit-tx {
font-size: 11px; color: var(--text-dim); padding: 4px 0;
border-bottom: 1px solid var(--border);
display: flex; justify-content: space-between;
}
.deposit-tx .amount { color: var(--success); font-weight: 600; }
.deposit-tx .amount.negative { color: #ff5252; }
/* Deposit/Withdraw Tabs */
.wallet-tabs {
display: flex; gap: 0; margin-bottom: 20px;
border-radius: 10px; overflow: hidden;
border: 1px solid var(--border);
}
.wallet-tab {
flex: 1; padding: 10px 16px; font-size: 13px; font-weight: 600;
cursor: pointer; border: none; transition: all 0.2s;
background: var(--bg-input); color: var(--text-dim);
}
.wallet-tab.active {
background: var(--accent); color: #fff;
}
.wallet-tab:hover:not(.active) { background: var(--bg-hover); }
.wallet-tab-content { display: none; }
.wallet-tab-content.active { display: block; }
/* Withdraw form */
.withdraw-form { text-align: left; }
.withdraw-field { margin-bottom: 14px; }
.withdraw-field label {
display: block; font-size: 12px; color: var(--text-dim);
margin-bottom: 4px; font-weight: 600;
}
.withdraw-field input {
width: 100%; padding: 10px 12px; font-size: 14px;
background: var(--bg-input); border: 1px solid var(--border);
border-radius: 8px; color: var(--text); outline: none;
box-sizing: border-box;
}
.withdraw-field input:focus { border-color: var(--accent); }
.withdraw-balance-info {
font-size: 12px; color: var(--text-dim); margin-bottom: 16px;
text-align: center;
}
.withdraw-balance-info strong { color: var(--success); }
.withdraw-submit-btn {
background: linear-gradient(135deg, #ff6b35, #ff4444);
border: none; border-radius: 10px; color: #fff;
padding: 12px 24px; font-size: 14px; font-weight: 600;
cursor: pointer; width: 100%; transition: all 0.2s;
}
.withdraw-submit-btn:hover { opacity: 0.9; transform: scale(1.02); }
.withdraw-submit-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.withdraw-result {
margin-top: 12px; padding: 10px; border-radius: 8px; font-size: 13px;
}
.withdraw-result.success {
background: rgba(0, 200, 83, 0.1); color: var(--success);
border: 1px solid rgba(0, 200, 83, 0.3);
}
.withdraw-result.error {
background: rgba(255, 82, 82, 0.1); color: #ff5252;
border: 1px solid rgba(255, 82, 82, 0.3);
}
.withdraw-history { margin-top: 16px; text-align: left; }
.withdraw-history h4 { font-size: 13px; color: var(--text-dim); margin-bottom: 8px; }
.withdraw-tx {
font-size: 11px; color: var(--text-dim); padding: 4px 0;
border-bottom: 1px solid var(--border);
display: flex; justify-content: space-between; align-items: center;
}
.withdraw-tx .amount { color: #ff5252; font-weight: 600; }
.withdraw-status {
font-size: 10px; padding: 2px 6px; border-radius: 4px; font-weight: 600;
}
.withdraw-status.pending { background: rgba(255,167,38,0.15); color: var(--warn); }
.withdraw-status.completed { background: rgba(0,200,83,0.15); color: var(--success); }
.withdraw-status.rejected { background: rgba(255,82,82,0.15); color: #ff5252; }
/* Profile Cabinet Modal */
.profile-overlay {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.6); z-index: 1001;
display: flex; align-items: center; justify-content: center;
backdrop-filter: blur(4px);
}
.profile-overlay.hidden { display: none; }
.profile-modal {
background: var(--bg-panel); border-radius: 16px;
padding: 32px; width: 560px; max-width: 95vw;
max-height: 90vh; max-height: 90dvh; overflow-y: auto;
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
border: 1px solid var(--border);
}
.profile-modal h2 {
color: var(--text); font-size: 20px; margin-bottom: 8px;
display: flex; align-items: center; gap: 10px;
}
.profile-modal .profile-subtitle {
color: var(--text-dim); font-size: 13px; margin-bottom: 20px;
}
.profile-section {
margin-bottom: 20px;
}
.profile-section h3 {
font-size: 14px; color: var(--accent); margin-bottom: 10px;
text-transform: uppercase; letter-spacing: 0.5px;
}
.profile-field {
margin-bottom: 12px;
}
.profile-field label {
display: block; font-size: 12px; color: var(--text-dim);
margin-bottom: 4px; font-weight: 600;
}
.profile-field input, .profile-field select, .profile-field textarea {
width: 100%; padding: 9px 12px;
background: var(--bg); border: 1px solid var(--border);
border-radius: 8px; color: var(--text); font-size: 14px;
font-family: inherit; box-sizing: border-box;
}
.profile-field input:focus, .profile-field select:focus, .profile-field textarea:focus {
border-color: var(--accent); outline: none;
}
.profile-field textarea { resize: vertical; min-height: 60px; }
.profile-field select { cursor: pointer; }
.chip-group {
display: flex; flex-wrap: wrap; gap: 6px;
}
.chip {
padding: 5px 12px; border-radius: 16px; font-size: 12px;
border: 1px solid var(--border); color: var(--text-dim);
cursor: pointer; transition: all 0.2s; user-select: none;
background: transparent;
}
.chip:hover { border-color: var(--accent); color: var(--accent); }
.chip.active {
background: rgba(0,122,255,0.15); border-color: var(--accent);
color: var(--accent); font-weight: 600;
}
.profile-vessels-list {
display: flex; flex-direction: column; gap: 6px; margin-top: 6px;
}
.profile-vessel-row {
display: flex; gap: 8px; align-items: center;
}
.profile-vessel-row input { flex: 1; }
.profile-vessel-row button {
background: transparent; border: 1px solid var(--border);
border-radius: 6px; color: var(--text-dim); cursor: pointer;
width: 30px; height: 30px; font-size: 16px;
display: flex; align-items: center; justify-content: center;
}
.profile-vessel-row button:hover { border-color: #ff5252; color: #ff5252; }
.add-vessel-btn {
background: transparent; border: 1px dashed var(--border);
border-radius: 8px; color: var(--text-dim); cursor: pointer;
padding: 8px; font-size: 13px; width: 100%; margin-top: 6px;
}
.add-vessel-btn:hover { border-color: var(--accent); color: var(--accent); }
.profile-actions {
display: flex; gap: 12px; margin-top: 24px;
}
.profile-save-btn {
flex: 1; padding: 11px;
background: linear-gradient(135deg, var(--accent), var(--accent2));
border: none; border-radius: 8px; color: white;
font-size: 14px; font-weight: 600; cursor: pointer;
}
.profile-save-btn:hover { opacity: 0.9; }
.profile-cancel-btn {
padding: 11px 24px;
background: transparent; border: 1px solid var(--border);
border-radius: 8px; color: var(--text-dim);
font-size: 14px; cursor: pointer;
}
.profile-cancel-btn:hover { border-color: var(--text); color: var(--text); }
.profile-saved-msg {
color: var(--success); font-size: 13px; text-align: center;
margin-top: 12px; display: none;
}
.purchased-contact-card {
background: var(--bg); border: 1px solid var(--border);
border-radius: 10px; padding: 12px 14px; margin-bottom: 8px;
}
.purchased-contact-card .pcc-company {
font-weight: 600; font-size: 14px; color: var(--text);
}
.purchased-contact-card .pcc-type {
display: inline-block; font-size: 11px; color: var(--accent);
background: rgba(0,122,255,0.1); border-radius: 10px;
padding: 2px 8px; margin-left: 6px; vertical-align: middle;
}
.purchased-contact-card .pcc-detail {
font-size: 12px; color: var(--text-dim); margin-top: 4px;
}
.purchased-contact-card .pcc-detail a {
color: var(--accent); text-decoration: none;
}
.purchased-contact-card .pcc-date {
font-size: 11px; color: var(--text-dim); opacity: 0.6; margin-top: 6px;
}
.purchased-empty {
color: var(--text-dim); font-size: 13px; text-align: center; padding: 16px 0;
}
/* ============ Responsive ============ */
/* Tablet (≤ 1024px) */
@media (max-width: 1024px) {
.sidebar { width: 240px; }
.chat-header { padding: 12px 16px; }
.messages { padding: 20px 16px 90px; gap: 20px; }
.input-area { padding: 0 16px 16px; margin-top: -70px; padding-top: 70px; }
}
/* Menu toggle button (hidden on desktop) */
.menu-toggle {
display: none;
width: 40px; height: 40px;
background: var(--accent);
border: none;
border-radius: 8px;
color: #fff;
font-size: 22px;
cursor: pointer;
align-items: center;
justify-content: center;
transition: all 0.2s;
flex-shrink: 0;
box-shadow: 0 2px 8px rgba(0,122,255,0.3);
}
.menu-toggle:hover { background: #0066d6; }
/* Mobile (≤ 768px) */
@media (max-width: 768px) {
body { flex-direction: column; }
/* Sidebar → slide-in drawer from left */
.sidebar {
position: fixed;
top: 0; left: 0; bottom: 0;
width: 300px;
max-width: 85vw;
z-index: 900;
overflow-y: auto;
transform: translateX(-100%);
transition: transform 0.3s ease;
box-shadow: none;
}
.sidebar.open {
transform: translateX(0);
box-shadow: 4px 0 20px rgba(0,0,0,0.3);
}
.main { width: 100%; height: var(--app-height, 100vh); }
/* Header: stack actions below title */
.chat-header {
flex-wrap: wrap;
gap: 8px;
padding: 10px 12px;
}
.chat-header-left h2 { font-size: 14px; }
.chat-header-left span { display: none; }
.header-actions {
flex-wrap: wrap;
gap: 6px;
width: 100%;
justify-content: flex-end;
}
.header-btn { padding: 5px 10px; font-size: 11px; }
.lang-btn { padding: 3px 8px; font-size: 11px; }
/* User info compact */
.user-info { font-size: 11px; gap: 4px; }
.balance-badge { font-size: 10px; padding: 1px 6px; }
/* Messages */
.messages { padding: 16px 10px 80px; gap: 16px; }
.message { max-width: 92%; }
.msg-content { padding: 12px 14px; font-size: 14px; border-radius: 16px; }
.msg-avatar { width: 26px; height: 26px; font-size: 12px; }
/* Input area */
.input-area { padding: 0 10px 12px; margin-top: -60px; padding-top: 60px; }
.input-glass {
border-radius: 20px;
padding: 4px 4px 4px 14px;
}
.input-wrapper textarea {
font-size: 14px;
min-height: 38px;
}
.send-btn { width: 36px; height: 36px; font-size: 14px; border-radius: 10px; }
.voice-btn { width: 36px; height: 36px; font-size: 16px; border-radius: 10px; }
.input-hint { font-size: 10px; margin-top: 6px; }
/* Quick actions scroll horizontally */
.quick-actions {
flex-wrap: nowrap;
overflow-x: auto;
margin-bottom: 8px;
gap: 6px;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.quick-actions::-webkit-scrollbar { display: none; }
.quick-btn { font-size: 11px; padding: 5px 12px; flex-shrink: 0; }
/* Auth modal */
.auth-modal { padding: 28px 20px; width: 95vw; }
.profile-modal { padding: 24px 16px; width: 95vw; }
.deposit-modal { padding: 24px 16px; width: 95vw; }
.chip { font-size: 11px; padding: 4px 10px; }
/* Menu toggle button visible */
.menu-toggle { display: flex; }
}
/* Small phones (≤ 480px) */
@media (max-width: 480px) {
.chat-header-left h2 { font-size: 13px; }
.header-actions { gap: 4px; }
.header-btn { padding: 4px 8px; font-size: 10px; }
.lang-btn { padding: 2px 6px; font-size: 10px; }
.message { max-width: 96%; }
.msg-content { font-size: 13px; padding: 10px 12px; }
.msg-avatar { width: 22px; height: 22px; font-size: 10px; }
}
/* Sidebar backdrop overlay */
.sidebar-backdrop {
display: none;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 899;
}
.sidebar-backdrop.open { display: block; }
/* Sidebar close button (only on mobile) */
.sidebar-close {
display: none;
position: absolute;
top: 16px; right: 16px;
background: transparent;
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-dim);
font-size: 18px;
width: 32px; height: 32px;
cursor: pointer;
align-items: center;
justify-content: center;
}
.sidebar-close:hover { color: var(--text); border-color: var(--accent); }
@media (max-width: 768px) {
.sidebar-close { display: flex; }
}
/* ========== TOP NAVIGATION ========== */
.top-nav {
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
display: flex; align-items: center; justify-content: space-between;
padding: 16px 5vw;
background: rgba(5,13,26,0.6);
backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
border-bottom: 1px solid transparent;
transition: all 0.3s ease;
}
.top-nav.scrolled {
background: rgba(10,25,41,0.92);
border-bottom-color: var(--border);
box-shadow: 0 4px 30px rgba(0,0,0,0.3);
}
.nav-logo { display: flex; align-items: center; gap: 10px; text-decoration: none; }
.nav-logo-icon {
width: 36px; height: 36px;
background: linear-gradient(135deg, var(--accent), var(--accent2));
border-radius: 10px; display: flex; align-items: center; justify-content: center;
font-size: 18px;
}
.nav-logo-text { font-family: 'Plus Jakarta Sans',sans-serif; font-size: 16px; font-weight: 700; color: var(--text); }
.nav-links { display: flex; align-items: center; gap: 32px; }
.nav-links a {
color: var(--text-dim); text-decoration: none; font-size: 14px; font-weight: 500;
transition: color 0.2s;
}
.nav-links a:hover { color: var(--text); }
.nav-right { display: flex; align-items: center; gap: 12px; }
.nav-cta {
padding: 8px 20px; border: none; border-radius: 8px; font-size: 13px; font-weight: 600;
background: linear-gradient(135deg, var(--accent), var(--accent2));
color: #020617; cursor: pointer; transition: transform 0.2s, box-shadow 0.3s;
}
.nav-cta:hover { transform: translateY(-1px); box-shadow: 0 4px 20px rgba(45,212,191,0.3); }
.nav-mobile-toggle {
display: none; background: none; border: none; color: var(--text);
font-size: 24px; cursor: pointer;
}
/* ========== HERO SECTION ========== */
.hero {
position: relative; min-height: 100vh;
display: flex; align-items: center; justify-content: center;
padding: 120px 5vw 80px; gap: 60px;
overflow: hidden;
}
.hero-bg { position: absolute; inset: 0; overflow: hidden; z-index: 0; }
.orb {
position: absolute; border-radius: 50%; filter: blur(80px); opacity: 0.35;
animation: orbFloat 20s ease-in-out infinite;
}
.orb-1 { width: 600px; height: 600px; background: radial-gradient(circle, var(--accent2) 0%, transparent 70%); top: -200px; right: -100px; animation-duration: 25s; }
.orb-2 { width: 400px; height: 400px; background: radial-gradient(circle, var(--accent3) 0%, transparent 70%); bottom: -100px; left: -50px; animation-duration: 30s; animation-delay: -5s; }
.orb-3 { width: 300px; height: 300px; background: radial-gradient(circle, var(--accent) 0%, transparent 70%); top: 40%; left: 30%; animation-duration: 22s; animation-delay: -10s; }
@keyframes orbFloat {
0%, 100% { transform: translate(0,0) scale(1); }
25% { transform: translate(30px,-40px) scale(1.05); }
50% { transform: translate(-20px,20px) scale(0.95); }
75% { transform: translate(15px,35px) scale(1.02); }
}
.hero-grid {
position: absolute; inset: 0;
background-image: linear-gradient(rgba(45,212,191,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(45,212,191,0.03) 1px, transparent 1px);
background-size: 60px 60px;
mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%);
-webkit-mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%);
}
.hero-particles { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }
.hero-particles span {
position: absolute; bottom: -20px; background: rgba(45,212,191,0.25); border-radius: 50%;
animation: particleRise linear infinite;
}
.hero-particles span:nth-child(1) { left:5%; width:4px; height:4px; animation-duration:18s; }
.hero-particles span:nth-child(2) { left:15%; width:8px; height:8px; animation-duration:22s; animation-delay:-3s; }
.hero-particles span:nth-child(3) { left:25%; width:3px; height:3px; animation-duration:16s; animation-delay:-7s; }
.hero-particles span:nth-child(4) { left:40%; width:6px; height:6px; animation-duration:20s; animation-delay:-2s; }
.hero-particles span:nth-child(5) { left:55%; width:5px; height:5px; animation-duration:24s; animation-delay:-5s; }
.hero-particles span:nth-child(6) { left:65%; width:7px; height:7px; animation-duration:19s; animation-delay:-8s; }
.hero-particles span:nth-child(7) { left:78%; width:4px; height:4px; animation-duration:21s; animation-delay:-1s; }
.hero-particles span:nth-child(8) { left:90%; width:6px; height:6px; animation-duration:17s; animation-delay:-6s; }
@keyframes particleRise {
0% { transform: translateY(0) translateX(0); opacity: 0; }
10% { opacity: 0.4; }
50% { transform: translateY(-50vh) translateX(20px); opacity: 0.25; }
100% { transform: translateY(-100vh) translateX(-10px); opacity: 0; }
}
.hero-content { position: relative; z-index: 2; max-width: 600px; }
.hero-badge {
display: inline-flex; align-items: center; gap: 8px;
padding: 6px 16px; background: rgba(45,212,191,0.1);
border: 1px solid rgba(45,212,191,0.25); border-radius: 100px;
font-size: 13px; font-weight: 500; color: var(--accent);
margin-bottom: 24px; backdrop-filter: blur(8px);
}
.hero-badge .badge-dot {
width: 6px; height: 6px; background: var(--accent); border-radius: 50%;
animation: pulse 2s infinite;
}
.hero-title {
font-size: clamp(36px,5vw,64px); font-weight: 800;
line-height: 1.1; letter-spacing: -0.02em; color: #f8fafc; margin-bottom: 20px;
}
.gradient-text {
background: linear-gradient(135deg, var(--accent), var(--accent2), var(--accent3));
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
}
.hero-subtitle {
font-size: 18px; line-height: 1.7; color: var(--text-dim);
margin-bottom: 36px; max-width: 480px;
}
.hero-cta-group { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; }
.cta-primary {
display: inline-flex; align-items: center; gap: 10px;
padding: 16px 32px; font-family: 'Plus Jakarta Sans',sans-serif;
font-size: 16px; font-weight: 600; color: #020617;
background: linear-gradient(135deg, var(--accent), var(--accent2));
border: none; border-radius: 12px; cursor: pointer;
position: relative; overflow: hidden;
transition: transform 0.2s, box-shadow 0.3s;
}
.cta-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(45,212,191,0.3);
}
.cta-primary::after {
content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
}
.cta-primary:hover::after { animation: ctaShine 0.6s ease forwards; }
@keyframes ctaShine { 0% { left: -100%; } 100% { left: 100%; } }
.cta-arrow { transition: transform 0.2s; }
.cta-primary:hover .cta-arrow { transform: translateX(4px); }
.cta-secondary {
display: inline-flex; align-items: center; gap: 10px;
padding: 16px 28px; font-size: 16px; font-weight: 500;
color: var(--text); background: transparent;
border: 1px solid rgba(255,255,255,0.15); border-radius: 12px;
cursor: pointer; backdrop-filter: blur(8px); transition: all 0.3s;
}
.cta-secondary:hover {
border-color: rgba(45,212,191,0.4); background: rgba(45,212,191,0.05);
}
.hero-preview {
position: relative; z-index: 2; flex-shrink: 0;
}
.glass-card {
background: rgba(15,23,42,0.6); backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255,255,255,0.08); border-radius: 16px;
padding: 24px; width: 380px;
box-shadow: 0 4px 30px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.05);
transition: border-color 0.3s, box-shadow 0.3s;
}
.glass-card:hover {
border-color: rgba(45,212,191,0.2);
box-shadow: 0 8px 40px rgba(0,0,0,0.4), 0 0 30px rgba(45,212,191,0.05);
}
.preview-header {
display: flex; align-items: center; gap: 8px;
font-size: 12px; color: var(--accent); font-weight: 600;
text-transform: uppercase; letter-spacing: 1px; margin-bottom: 16px;
}
.preview-header .live-dot {
width: 6px; height: 6px; background: var(--success); border-radius: 50%;
animation: pulse 2s infinite;
}
.preview-line {
display: flex; justify-content: space-between; padding: 8px 0;
font-size: 13px; border-bottom: 1px solid rgba(255,255,255,0.04);
}
.preview-line:last-child { border-bottom: none; }
.preview-label { color: var(--text-dim); }
.preview-highlight { color: var(--accent); font-weight: 600; }
.wave-container {
position: absolute; bottom: 0; left: 0; width: 100%; height: 120px; z-index: 1;
}
.wave { position: absolute; bottom: 0; left: 0; width: 200%; height: 100%; }
.wave path { fill: var(--bg-panel); }
.wave-1 { opacity: 0.6; animation: waveSlide 8s linear infinite; }
.wave-2 { opacity: 0.3; animation: waveSlide 12s linear infinite reverse; }
@keyframes waveSlide { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } }
/* ========== STATS BAR ========== */
.stats-bar {
background: var(--bg-panel); padding: 60px 5vw; position: relative;
}
.stats-grid {
display: grid; grid-template-columns: repeat(4,1fr); gap: 32px;
max-width: 1100px; margin: 0 auto;
}
.stat-card {
text-align: center; padding: 28px 16px;
background: rgba(255,255,255,0.02); border: 1px solid var(--border);
border-radius: 16px; transition: transform 0.3s, border-color 0.3s;
}
.stat-card:hover { transform: translateY(-4px); border-color: rgba(45,212,191,0.3); }
.stat-number {
font-family: 'Plus Jakarta Sans',sans-serif; font-size: 42px; font-weight: 800;
background: linear-gradient(135deg, var(--accent), var(--accent2));
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
margin-bottom: 6px;
}
.stat-label {
font-size: 13px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 1px;
}
/* ========== SECTION COMMON ========== */
.section-pad { padding: 100px 5vw; max-width: 1200px; margin: 0 auto; }
.section-badge {
display: inline-flex; align-items: center; gap: 6px;
padding: 5px 14px; background: rgba(45,212,191,0.08);
border: 1px solid rgba(45,212,191,0.2); border-radius: 100px;
font-size: 12px; font-weight: 600; color: var(--accent);
text-transform: uppercase; letter-spacing: 1px; margin-bottom: 16px;
}
.section-title {
font-size: clamp(28px,4vw,42px); font-weight: 800;
line-height: 1.2; letter-spacing: -0.02em; color: var(--text); margin-bottom: 16px;
}
.section-subtitle {
font-size: 17px; line-height: 1.7; color: var(--text-dim); max-width: 600px;
}
.section-header { text-align: center; margin-bottom: 60px; }
.section-header .section-subtitle { margin: 0 auto; }
/* ========== FEATURES ========== */
.features-section { background: var(--bg-dark); }
.features-grid {
display: grid; grid-template-columns: repeat(3,1fr); gap: 24px;
}
.feature-card {
background: rgba(15,23,42,0.5); border: 1px solid var(--border);
border-radius: 16px; padding: 32px; transition: all 0.3s;
position: relative; overflow: hidden;
}
.feature-card:hover {
border-color: rgba(45,212,191,0.25); transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(0,0,0,0.3);
}
.feature-card::before {
content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;
background: linear-gradient(90deg, var(--accent), var(--accent2));
opacity: 0; transition: opacity 0.3s;
}
.feature-card:hover::before { opacity: 1; }
.feature-icon {
width: 48px; height: 48px; border-radius: 12px;
background: rgba(45,212,191,0.1); display: flex; align-items: center;
justify-content: center; font-size: 24px; margin-bottom: 20px;
}
.feature-card h3 { font-size: 18px; font-weight: 700; margin-bottom: 10px; color: var(--text); }
.feature-card p { font-size: 14px; line-height: 1.6; color: var(--text-dim); }
.feature-tag {
display: inline-block; margin-top: 14px; padding: 3px 10px;
border-radius: 100px; font-size: 11px; font-weight: 600;
}
.tag-free { background: rgba(52,211,153,0.12); color: var(--success); }
.tag-paid { background: rgba(14,165,233,0.12); color: var(--accent2); }
/* ========== HOW IT WORKS ========== */
.how-section { background: var(--bg-panel); }
.how-steps {
display: grid; grid-template-columns: repeat(3,1fr); gap: 40px;
position: relative;
}
.how-steps::before {
content: ''; position: absolute; top: 40px; left: 15%; right: 15%; height: 2px;
background: linear-gradient(90deg, var(--accent), var(--accent2), var(--accent3));
opacity: 0.2; z-index: 0;
}
.how-step { text-align: center; position: relative; z-index: 1; }
.step-num {
width: 56px; height: 56px; border-radius: 50%; margin: 0 auto 20px;
background: linear-gradient(135deg, var(--accent), var(--accent2));
display: flex; align-items: center; justify-content: center;
font-family: 'Plus Jakarta Sans',sans-serif; font-size: 22px; font-weight: 800;
color: #020617;
}
.how-step h3 { font-size: 18px; font-weight: 700; margin-bottom: 10px; }
.how-step p { font-size: 14px; line-height: 1.6; color: var(--text-dim); max-width: 280px; margin: 0 auto; }
/* ========== CHAT SECTION ========== */
.chat-section { background: var(--bg-dark); padding: 100px 5vw; }
.chat-container {
display: flex; height: 700px; max-width: 1200px; margin: 0 auto;
border-radius: 16px; border: 1px solid var(--border-strong);
overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,0.4);
background: var(--bg-chat);
}
/* ========== CTA SECTION ========== */
.cta-section {
background: var(--bg-panel); padding: 100px 5vw; text-align: center;
position: relative; overflow: hidden;
}
.cta-section .orb { opacity: 0.2; }
.cta-inner { position: relative; z-index: 2; max-width: 600px; margin: 0 auto; }
.cta-inner h2 {
font-size: clamp(28px,4vw,42px); font-weight: 800;
margin-bottom: 16px; color: var(--text);
}
.cta-inner p { font-size: 17px; color: var(--text-dim); margin-bottom: 32px; line-height: 1.7; }
/* ========== FOOTER ========== */
.site-footer {
background: var(--bg-dark); border-top: 1px solid var(--border);
padding: 60px 5vw 30px;
}
.footer-grid {
display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 40px;
max-width: 1100px; margin: 0 auto 40px;
}
.footer-brand .nav-logo { margin-bottom: 12px; }
.footer-brand p { font-size: 13px; color: var(--text-dim); line-height: 1.6; max-width: 280px; }
.footer-col h4 {
font-size: 12px; text-transform: uppercase; letter-spacing: 1px;
color: var(--text-dim); margin-bottom: 16px;
}
.footer-col a {
display: block; color: var(--text-muted); text-decoration: none;
font-size: 14px; padding: 4px 0; transition: color 0.2s;
}
.footer-col a:hover { color: var(--accent); }
.footer-bottom {
text-align: center; padding-top: 30px; border-top: 1px solid var(--border);
font-size: 12px; color: var(--text-muted);
}
.footer-bottom a { color: var(--accent); text-decoration: none; }
/* ========== SCROLL REVEAL ========== */
.reveal {
opacity: 0; transform: translateY(30px);
transition: opacity 0.8s ease, transform 0.8s ease;
}
.reveal.visible { opacity: 1; transform: translateY(0); }
.reveal-delay-1 { transition-delay: 100ms; }
.reveal-delay-2 { transition-delay: 200ms; }
.reveal-delay-3 { transition-delay: 300ms; }
.reveal-delay-4 { transition-delay: 400ms; }
.reveal-delay-5 { transition-delay: 500ms; }
/* ========== LANDING RESPONSIVE ========== */
@media (max-width: 1024px) {
.hero { flex-direction: column; text-align: center; padding-top: 140px; }
.hero-content { max-width: 100%; }
.hero-subtitle { margin: 0 auto 36px; }
.hero-cta-group { justify-content: center; }
.hero-preview { display: none; }
.stats-grid { grid-template-columns: repeat(2,1fr); }
.features-grid { grid-template-columns: repeat(2,1fr); }
.footer-grid { grid-template-columns: 1fr 1fr; gap: 30px; }
}
@media (max-width: 768px) {
.top-nav { padding: 12px 4vw; }
.nav-links { display: none; }
.nav-mobile-toggle { display: block; }
.hero { min-height: auto; padding: 120px 5vw 60px; }
.hero-title { font-size: 32px; }
.hero-subtitle { font-size: 16px; }
.stats-grid { grid-template-columns: repeat(2,1fr); gap: 16px; }
.stat-number { font-size: 32px; }
.stat-card { padding: 20px 12px; }
.features-grid { grid-template-columns: 1fr; }
.how-steps { grid-template-columns: 1fr; gap: 30px; }
.how-steps::before { display: none; }
.chat-section { padding: 60px 3vw; }
.chat-container { height: 600px; border-radius: 12px; flex-direction: column; }
.chat-container .sidebar { display: none; }
.footer-grid { grid-template-columns: 1fr; gap: 24px; }
.cta-primary, .cta-secondary { width: 100%; justify-content: center; }
}
@media (max-width: 480px) {
.stats-grid { grid-template-columns: 1fr 1fr; gap: 10px; }
.stat-number { font-size: 28px; }
.chat-container { height: 500px; }
}
</style>
</head>
<body>
<!-- Auth Modal -->
<div class="auth-overlay hidden" id="authOverlay">
<div class="auth-modal">
<div class="auth-logo">
<div class="logo-icon">&#9875;</div>
<h2>SeaFare Montana</h2>
</div>
<h3 id="authTitle"></h3>
<div id="authError" class="auth-error" style="display:none;"></div>
<form id="authForm" onsubmit="handleAuth(event)">
<div id="nameField" class="auth-field" style="display:none;">
<input type="text" id="authName" autocomplete="name">
</div>
<div class="auth-field">
<input type="email" id="authEmail" required autocomplete="email">
</div>
<div class="auth-field">
<input type="password" id="authPassword" required minlength="6" autocomplete="current-password">
</div>
<button type="submit" class="auth-submit" id="authSubmitBtn"></button>
</form>
<div class="auth-divider" id="authDivider" style="display:none;"><span>or</span></div>
<div class="google-btn-wrap" id="googleBtnWrap" style="display:none;">
<div id="googleSignInBtn"></div>
</div>
<div class="auth-switch">
<a href="#" id="authSwitchLink" onclick="toggleAuthMode(event)"></a>
</div>
<div class="auth-close">
<a href="#" onclick="hideAuthModal(event)">&times; <span id="authCloseText"></span></a>
</div>
</div>
</div>
<!-- Profile Cabinet Modal -->
<!-- Deposit Modal -->
<div class="deposit-overlay hidden" id="depositOverlay">
<div class="deposit-modal">
<h2>&#128176; <span data-i18n="topup_title">Top Up Balance</span></h2>
<!-- Tabs -->
<div class="wallet-tabs">
<button class="wallet-tab active" onclick="switchWalletTab('deposit')" id="walletTabDeposit" data-i18n="wallet_tab_deposit">Deposit</button>
<button class="wallet-tab" onclick="switchWalletTab('withdraw')" id="walletTabWithdraw" data-i18n="wallet_tab_withdraw">Withdraw</button>
</div>
<!-- Deposit Tab -->
<div class="wallet-tab-content active" id="walletContentDeposit">
<div class="deposit-network">USDT — TRC20 (Tron)</div>
<p style="font-size:13px; color:var(--text-dim); margin-bottom:8px;" data-i18n="topup_instruction">
Send USDT (TRC20) to the address below. Your balance will be credited after confirmation.
</p>
<div class="fee-info" id="depositFeeInfo" style="font-size:12px; color:var(--accent); background:rgba(0,200,200,0.06); border-radius:10px; padding:8px 12px; margin-bottom:12px;">
<span data-i18n="deposit_fee_info">Service fee: 2%. Example: send $100 — receive $98.</span>
</div>
<div id="depositQR" class="deposit-qr" style="display:none;"></div>
<div id="depositAddressWrap" class="deposit-address-wrap" style="display:none;">
<span id="depositAddress" class="deposit-address"></span>
<button class="deposit-copy-btn" onclick="copyDepositAddress()" data-i18n="topup_copy">Copy</button>
</div>
<div id="depositLoading" style="padding:20px; color:var(--text-dim);">Loading...</div>
<button class="deposit-check-btn" id="depositCheckBtn" onclick="checkDeposits()" data-i18n="topup_check">
Check for deposits
</button>
<div id="depositResult" class="deposit-result" style="display:none;"></div>
<div id="depositHistory" class="deposit-history" style="display:none;"></div>
</div>
<!-- Withdraw Tab -->
<div class="wallet-tab-content" id="walletContentWithdraw">
<div class="deposit-network">USDT — TRC20 (Tron)</div>
<div class="withdraw-balance-info">
<span data-i18n="withdraw_your_balance">Your balance:</span>
<strong id="withdrawBalanceDisplay">$0.00</strong> USDT
</div>
<div class="withdraw-form">
<div class="withdraw-field">
<label data-i18n="withdraw_address_label">TRC20 Wallet Address</label>
<input type="text" id="withdrawAddress" placeholder="T..." maxlength="34">
</div>
<div class="withdraw-field">
<label data-i18n="withdraw_amount_label">Amount (USDT)</label>
<input type="number" id="withdrawAmount" placeholder="0.00" min="2" step="0.01" oninput="updateWithdrawTotal()">
</div>
<div class="withdraw-fee-breakdown" id="withdrawFeeBreakdown" style="font-size:12px; color:var(--text-dim); background:rgba(0,200,200,0.06); border-radius:10px; padding:8px 12px; margin-bottom:10px;">
<div><span data-i18n="withdraw_fee_label">Network fee:</span> <strong>1.00 USDT</strong></div>
<div id="withdrawTotalLine" style="display:none;"><span data-i18n="withdraw_total_label">Total deducted:</span> <strong id="withdrawTotalDisplay"></strong></div>
</div>
<p style="font-size:11px; color:var(--text-dim); margin-bottom:14px;" data-i18n="withdraw_note">
Minimum withdrawal: $2 USDT. Withdrawals are processed manually within 24 hours.
</p>
<button class="withdraw-submit-btn" id="withdrawSubmitBtn" onclick="submitWithdrawal()" data-i18n="withdraw_submit">
Request Withdrawal
</button>
<div id="withdrawResult" class="withdraw-result" style="display:none;"></div>
</div>
<div id="withdrawHistory" class="withdraw-history" style="display:none;"></div>
</div>
<span class="deposit-close" onclick="hideDeposit()" data-i18n="topup_close">Close</span>
</div>
</div>
<div class="profile-overlay hidden" id="profileOverlay">
<div class="profile-modal">
<h2>&#128188; <span data-i18n="profile_title">My Cabinet</span></h2>
<div class="profile-subtitle" data-i18n="profile_subtitle">Fill out your profile so SeaFare Montana can give you more accurate, personalized answers.</div>
<div class="profile-section" id="purchasedContactsSection" style="display:none">
<h3 data-i18n="purchased_contacts_title">Purchased Contacts</h3>
<div id="purchasedContactsList"></div>
</div>
<div class="profile-section">
<h3 data-i18n="profile_company_section">Company</h3>
<div class="profile-field">
<label data-i18n="profile_company">Company Name</label>
<input type="text" id="profCompany" placeholder="e.g. Pacific Shipping Ltd.">
</div>
<div class="profile-field">
<label data-i18n="profile_role">Your Role</label>
<select id="profRole">
<option value="">— Select —</option>
<option value="shipowner">Shipowner</option>
<option value="operator">Operator</option>
<option value="charterer">Charterer</option>
<option value="broker">Broker</option>
<option value="freight_forwarder">Freight Forwarder</option>
<option value="port_agent">Port Agent</option>
<option value="surveyor">Surveyor</option>
<option value="other">Other</option>
</select>
</div>
<div class="profile-field">
<label data-i18n="profile_experience">Experience (years)</label>
<input type="number" id="profExperience" min="0" max="60" placeholder="0">
</div>
<div class="profile-field">
<label data-i18n="profile_fleet_size">Fleet Size (vessels)</label>
<input type="number" id="profFleetSize" min="0" placeholder="0">
</div>
</div>
<div class="profile-section">
<h3 data-i18n="profile_vessel_types_title">Vessel Types</h3>
<div class="chip-group" id="profVesselTypes">
<span class="chip" data-val="bulk_carrier">Bulk Carrier</span>
<span class="chip" data-val="tanker">Tanker</span>
<span class="chip" data-val="container">Container</span>
<span class="chip" data-val="general_cargo">General Cargo</span>
<span class="chip" data-val="ro_ro">Ro-Ro</span>
<span class="chip" data-val="lng_carrier">LNG Carrier</span>
<span class="chip" data-val="chemical_tanker">Chemical Tanker</span>
<span class="chip" data-val="offshore">Offshore</span>
<span class="chip" data-val="tug">Tug</span>
<span class="chip" data-val="barge">Barge</span>
<span class="chip" data-val="passenger">Passenger</span>
</div>
</div>
<div class="profile-section">
<h3 data-i18n="profile_trade_routes_title">Trade Routes</h3>
<div class="chip-group" id="profTradeRoutes">
<span class="chip" data-val="Mediterranean">Mediterranean</span>
<span class="chip" data-val="Baltic">Baltic</span>
<span class="chip" data-val="Black Sea">Black Sea</span>
<span class="chip" data-val="North Sea">North Sea</span>
<span class="chip" data-val="Atlantic">Atlantic</span>
<span class="chip" data-val="Pacific">Pacific</span>
<span class="chip" data-val="Indian Ocean">Indian Ocean</span>
<span class="chip" data-val="Southeast Asia">Southeast Asia</span>
<span class="chip" data-val="Middle East / Persian Gulf">Middle East / Gulf</span>
<span class="chip" data-val="West Africa">West Africa</span>
<span class="chip" data-val="East Africa">East Africa</span>
<span class="chip" data-val="South America">South America</span>
<span class="chip" data-val="Caribbean">Caribbean</span>
<span class="chip" data-val="North America East Coast">N. America East</span>
<span class="chip" data-val="North America West Coast">N. America West</span>
<span class="chip" data-val="Australia / Oceania">Australia / Oceania</span>
<span class="chip" data-val="Arctic">Arctic</span>
</div>
</div>
<div class="profile-section">
<h3 data-i18n="profile_cargo_types_title">Cargo Types</h3>
<div class="chip-group" id="profCargoTypes">
<span class="chip" data-val="dry_bulk">Dry Bulk</span>
<span class="chip" data-val="liquid_bulk">Liquid Bulk</span>
<span class="chip" data-val="containerized">Containerized</span>
<span class="chip" data-val="breakbulk">Breakbulk</span>
<span class="chip" data-val="project_cargo">Project Cargo</span>
<span class="chip" data-val="reefer">Reefer</span>
<span class="chip" data-val="lng">LNG</span>
<span class="chip" data-val="lpg">LPG</span>
<span class="chip" data-val="chemicals">Chemicals</span>
<span class="chip" data-val="crude_oil">Crude Oil</span>
<span class="chip" data-val="refined_products">Refined Products</span>
<span class="chip" data-val="grain">Grain</span>
<span class="chip" data-val="coal">Coal</span>
<span class="chip" data-val="iron_ore">Iron Ore</span>
<span class="chip" data-val="fertilizer">Fertilizer</span>
</div>
</div>
<div class="profile-section">
<h3 data-i18n="profile_vessels_title">Vessels of Interest</h3>
<div class="profile-subtitle" data-i18n="profile_vessels_hint">Add vessel names or IMO numbers you want to track</div>
<div class="profile-vessels-list" id="profVesselsList"></div>
<button class="add-vessel-btn" onclick="addVesselRow()">+ <span data-i18n="profile_add_vessel">Add vessel</span></button>
</div>
<div class="profile-section">
<h3 data-i18n="profile_contact_section">Contact</h3>
<div class="profile-field">
<label data-i18n="profile_phone">Phone</label>
<input type="tel" id="profPhone" placeholder="+1 555 123 4567">
</div>
<div class="profile-field">
<label>Telegram</label>
<input type="text" id="profTelegram" placeholder="@username">
</div>
</div>
<div class="profile-section">
<h3 data-i18n="profile_notes_title">Notes</h3>
<div class="profile-field">
<textarea id="profNotes" rows="3" data-i18n-placeholder="profile_notes_placeholder" placeholder="Any additional info for the AI agent..."></textarea>
</div>
</div>
<div class="profile-actions">
<button class="profile-cancel-btn" onclick="hideProfile()" data-i18n="profile_cancel">Cancel</button>
<button class="profile-save-btn" onclick="saveProfile()" data-i18n="profile_save">Save Profile</button>
</div>
<div class="profile-saved-msg" id="profileSavedMsg" data-i18n="profile_saved">Profile saved successfully!</div>
</div>
</div>
<!-- Revenue Modal (admin only) -->
<div class="revenue-overlay hidden" id="revenueOverlay">
<div class="revenue-modal" style="position:relative;">
<button class="revenue-close" onclick="hideRevenue()">&times;</button>
<h2>&#128200; Platform Revenue</h2>
<div id="revenueContent">
<div style="text-align:center; padding:40px; color:var(--text-dim);">Loading...</div>
</div>
</div>
</div>
<!-- ========== TOP NAVIGATION ========== -->
<nav class="top-nav" id="topNav">
<a href="#" class="nav-logo" onclick="event.preventDefault();window.scrollTo({top:0,behavior:'smooth'})">
<div class="nav-logo-icon">&#9875;</div>
<span class="nav-logo-text">SeaFare Montana</span>
</a>
<div class="nav-links" id="navLinks">
<a href="#features" data-i18n="nav_features">Features</a>
<a href="#how-it-works" data-i18n="nav_how">How It Works</a>
<a href="#chat" data-i18n="nav_chat">Try Now</a>
</div>
<div class="nav-right">
<div class="lang-switcher">
<button class="lang-btn active" onclick="setLang('en')">EN</button>
<button class="lang-btn" onclick="setLang('ru')">RU</button>
<button class="lang-btn" onclick="setLang('es')">ES</button>
</div>
<button class="nav-cta" id="navLoginBtn" onclick="showAuthModal()" data-i18n="nav_start">Get Started</button>
<button class="nav-mobile-toggle" onclick="document.getElementById('navLinks').classList.toggle('show')">&#9776;</button>
</div>
</nav>
<!-- ========== HERO SECTION ========== -->
<section class="hero" id="hero">
<div class="hero-bg">
<div class="orb orb-1"></div>
<div class="orb orb-2"></div>
<div class="orb orb-3"></div>
<div class="hero-grid"></div>
</div>
<div class="hero-particles">
<span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span>
</div>
<div class="wave-container">
<svg class="wave wave-1" viewBox="0 0 1440 120" preserveAspectRatio="none">
<path d="M0,60 C360,120 720,0 1080,60 C1260,90 1380,75 1440,60 L1440,120 L0,120 Z"/>
</svg>
<svg class="wave wave-2" viewBox="0 0 1440 120" preserveAspectRatio="none">
<path d="M0,80 C240,20 480,100 720,60 C960,20 1200,90 1440,50 L1440,120 L0,120 Z"/>
</svg>
</div>
<div class="hero-content">
<div class="hero-badge">
<span class="badge-dot"></span>
<span data-i18n="hero_badge">AI-Powered Maritime Intelligence</span>
</div>
<h1 class="hero-title" data-i18n="hero_title">Navigate Global Shipping<br><span class="gradient-text">With AI Precision</span></h1>
<p class="hero-subtitle" data-i18n="hero_subtitle">Real-time vessel tracking, intelligent port analysis, and demurrage calculations. Powered by AI with 25 years of maritime expertise built in.</p>
<div class="hero-cta-group">
<a href="#chat" class="cta-primary" style="text-decoration:none;">
<span data-i18n="hero_cta">Start Free Consultation</span>
<span class="cta-arrow">&rarr;</span>
</a>
<a href="#features" class="cta-secondary" style="text-decoration:none;">
<span data-i18n="hero_cta2">See Features</span>
</a>
</div>
</div>
<div class="hero-preview">
<div class="glass-card">
<div class="preview-header">
<span class="live-dot"></span> Live Tracking
</div>
<div class="preview-line"><span class="preview-label">Vessel:</span><span>EVER GIVEN — IMO 9811000</span></div>
<div class="preview-line"><span class="preview-label">Position:</span><span>Suez Canal, 30.0N 32.5E</span></div>
<div class="preview-line"><span class="preview-label">Speed:</span><span>12.4 kn / Course 315&deg;</span></div>
<div class="preview-line"><span class="preview-label">ETA:</span><span class="preview-highlight">Rotterdam — 14h 32m</span></div>
<div class="preview-line"><span class="preview-label">Status:</span><span style="color:var(--success);">&#9679; Underway</span></div>
</div>
</div>
</section>
<!-- ========== STATS BAR ========== -->
<section class="stats-bar">
<div class="stats-grid">
<div class="stat-card reveal"><div class="stat-number" data-target="14000">0</div><div class="stat-label" data-i18n="stat_vessels">Vessels Tracked</div></div>
<div class="stat-card reveal reveal-delay-1"><div class="stat-number" data-target="200">0</div><div class="stat-label" data-i18n="stat_ports">Global Ports</div></div>
<div class="stat-card reveal reveal-delay-2"><div class="stat-number">24/7</div><div class="stat-label" data-i18n="stat_ai">AI Availability</div></div>
<div class="stat-card reveal reveal-delay-3"><div class="stat-number">&lt;2s</div><div class="stat-label" data-i18n="stat_speed">Response Time</div></div>
</div>
</section>
<!-- ========== FEATURES ========== -->
<section id="features" class="features-section">
<div class="section-pad">
<div class="section-header reveal">
<div class="section-badge" data-i18n="features_badge">Capabilities</div>
<h2 class="section-title" data-i18n="features_title">Everything You Need for Maritime Operations</h2>
<p class="section-subtitle" data-i18n="features_subtitle">From vessel tracking to freight consultation — all powered by AI with real-time maritime data.</p>
</div>
<div class="features-grid">
<div class="feature-card reveal" style="--i:0">
<div class="feature-icon">&#128674;</div>
<h3 data-i18n="feat_search">Vessel Search</h3>
<p data-i18n="feat_search_desc">Find any vessel worldwide by name, IMO, or MMSI. Get instant details on type, flag, tonnage, and classification.</p>
<span class="feature-tag tag-free" data-i18n="free">Free</span>
</div>
<div class="feature-card reveal reveal-delay-1" style="--i:1">
<div class="feature-icon">&#128205;</div>
<h3 data-i18n="feat_position">Live Position</h3>
<p data-i18n="feat_position_desc">Real-time AIS tracking with coordinates, speed, course, destination, and navigation status.</p>
<span class="feature-tag tag-free" data-i18n="free">Free</span>
</div>
<div class="feature-card reveal reveal-delay-2" style="--i:2">
<div class="feature-icon">&#127970;</div>
<h3 data-i18n="feat_owner">Owner & Operator</h3>
<p data-i18n="feat_owner_desc">Comprehensive company information — registered owner, operator, ISM manager, and classification society.</p>
<span class="feature-tag tag-free" data-i18n="free">Free</span>
</div>
<div class="feature-card reveal reveal-delay-3" style="--i:3">
<div class="feature-icon">&#128178;</div>
<h3 data-i18n="feat_demurrage">Demurrage Calculator</h3>
<p data-i18n="feat_demurrage_desc">Calculate delay costs instantly. Input laytime, actual days, and rate — get precise financial breakdown.</p>
<span class="feature-tag tag-free" data-i18n="free">Free</span>
</div>
<div class="feature-card reveal reveal-delay-4" style="--i:4">
<div class="feature-icon">&#128587;</div>
<h3 data-i18n="feat_contacts">Contact Introductions</h3>
<p data-i18n="feat_contacts_desc">Connect with verified operators, charterers, and port agents. Unlock full contact details for direct communication.</p>
<span class="feature-tag tag-paid">$10 USDT</span>
</div>
<div class="feature-card reveal reveal-delay-5" style="--i:5">
<div class="feature-icon">&#128220;</div>
<h3 data-i18n="feat_consult">Freight Consultation</h3>
<p data-i18n="feat_consult_desc">Expert market advice on routes, rates, and cargo matching. 25 years of maritime intelligence at your service.</p>
<span class="feature-tag tag-paid">$10 USDT</span>
</div>
</div>
</div>
</section>
<!-- ========== HOW IT WORKS ========== -->
<section id="how-it-works" class="how-section">
<div class="section-pad">
<div class="section-header reveal">
<div class="section-badge" data-i18n="how_badge">Simple Process</div>
<h2 class="section-title" data-i18n="how_title">How It Works</h2>
<p class="section-subtitle" data-i18n="how_subtitle">Get maritime intelligence in seconds, not hours. No manuals, no complex interfaces.</p>
</div>
<div class="how-steps">
<div class="how-step reveal">
<div class="step-num">1</div>
<h3 data-i18n="step1_title">Ask in Plain Language</h3>
<p data-i18n="step1_desc">Type your question naturally — "Find bulk carriers near Singapore" or "Route from Santos to Rotterdam."</p>
</div>
<div class="how-step reveal reveal-delay-2">
<div class="step-num">2</div>
<h3 data-i18n="step2_title">AI Analyzes Instantly</h3>
<p data-i18n="step2_desc">Our AI queries real-time maritime data, AIS feeds, and port intelligence to find the best answer.</p>
</div>
<div class="how-step reveal reveal-delay-4">
<div class="step-num">3</div>
<h3 data-i18n="step3_title">Get Actionable Results</h3>
<p data-i18n="step3_desc">Receive structured data — tables, routes, costs, contacts — ready for immediate business decisions.</p>
</div>
</div>
</div>
</section>
<!-- ========== CHAT SECTION ========== -->
<section id="chat" class="chat-section">
<div class="section-header reveal" style="max-width:1200px;margin:0 auto 40px;">
<div class="section-badge" data-i18n="chat_badge">Live AI Consultant</div>
<h2 class="section-title" data-i18n="chat_title">Try SeaFare Montana Now</h2>
<p class="section-subtitle" data-i18n="chat_subtitle">Start a conversation — ask about vessels, routes, cargo, or anything maritime.</p>
</div>
<div class="chat-container">
<!-- Sidebar -->
<div class="sidebar-backdrop" id="sidebarBackdrop" onclick="toggleSidebar()"></div>
<aside class="sidebar" id="sidebar">
<button class="sidebar-close" onclick="toggleSidebar()">&times;</button>
<div class="sidebar-header">
<div class="logo">
<div class="logo-icon">&#9875;</div>
<div class="logo-text">
<h1>SeaFare Montana</h1>
<span data-i18n="subtitle">Maritime Logistics Agent</span>
</div>
</div>
<div class="status-badge">
<span class="status-dot"></span>
<span data-i18n="status">Online — Ready</span>
</div>
</div>
<div class="services">
<h3 data-i18n="services_title">Services</h3>
<div class="service-item" data-quick="search_vessel">
<div class="service-icon">&#128674;</div>
<div class="service-info">
<div class="name" data-i18n="svc_search">Vessel Search</div>
<div class="price" data-i18n="svc_search_desc">Name, IMO, MMSI lookup</div>
</div>
<span class="price-tag price-free" data-i18n="free">Free</span>
</div>
<div class="service-item" data-quick="vessel_position">
<div class="service-icon">&#128205;</div>
<div class="service-info">
<div class="name" data-i18n="svc_position">Vessel Position</div>
<div class="price" data-i18n="svc_position_desc">AIS real-time tracking</div>
</div>
<span class="price-tag price-free" data-i18n="free">Free</span>
</div>
<div class="service-item" data-quick="owner_info">
<div class="service-icon">&#127970;</div>
<div class="service-info">
<div class="name" data-i18n="svc_owner">Owner / Operator</div>
<div class="price" data-i18n="svc_owner_desc">Company information</div>
</div>
<span class="price-tag price-free" data-i18n="free">Free</span>
</div>
<div class="service-item" data-quick="demurrage_calc">
<div class="service-icon">&#128178;</div>
<div class="service-info">
<div class="name" data-i18n="svc_demurrage">Demurrage Calculator</div>
<div class="price" data-i18n="svc_demurrage_desc">Delay cost estimation</div>
</div>
<span class="price-tag price-free" data-i18n="free">Free</span>
</div>
<div class="service-item" data-quick="contacts">
<div class="service-icon">&#128587;</div>
<div class="service-info">
<div class="name" data-i18n="svc_contacts">Contact Introduction</div>
<div class="price" data-i18n="svc_contacts_desc">Shipper &#8596; Operator</div>
</div>
<span class="price-tag">$10</span>
</div>
<div class="service-item" data-quick="consultation">
<div class="service-icon">&#128220;</div>
<div class="service-info">
<div class="name" data-i18n="svc_consult">Freight Consultation</div>
<div class="price" data-i18n="svc_consult_desc">Expert market advice</div>
</div>
<span class="price-tag">$10</span>
</div>
</div>
<div class="sidebar-footer">
Powered by <a href="https://moltbook.com/u/SeaFare_Montana">Moltbook</a><br>
<span data-i18n="footer">Montana Protocol &bull; 25 years expertise</span><br>
<span class="app-version-sidebar" style="font-size:10px;opacity:0.5;"></span>
</div>
</aside>
<!-- Main Chat -->
<main class="main">
<div class="chat-header">
<div class="chat-header-left">
<button class="menu-toggle" onclick="toggleSidebar()" title="Menu">&#9776;</button>
<h2>&#9875; SeaFare Montana</h2>
<span data-i18n="header_sub">Maritime Logistics Consultant</span>
</div>
<div class="header-actions">
<span id="userInfo" class="user-info" style="display:none;">
<span id="adminBadge" class="admin-badge" style="display:none;">ADMIN</span>
<button id="revenueBtn" class="header-btn admin-revenue-btn" onclick="showRevenue()" style="display:none;">&#128200;</button>
<span id="userName"></span>
<span id="userBalance" class="balance-badge" onclick="showDeposit()" style="cursor:pointer;" title="Top Up"></span>
<button class="header-btn topup-btn" onclick="showDeposit()" data-i18n="topup_btn">&#128176;</button>
<button class="header-btn" onclick="showProfile()" data-i18n="profile_btn" title="My Cabinet">&#128188;</button>
</span>
<button class="header-btn" onclick="clearChat()" data-i18n="clear_chat">Clear Chat</button>
<button class="header-btn" id="loginBtn" onclick="showAuthModal()" data-i18n="auth_login">Login</button>
<button class="header-btn" id="logoutBtn" onclick="doLogout()" style="display:none;" data-i18n="auth_logout">Logout</button>
</div>
</div>
<div class="messages" id="messages"></div>
<div class="input-area">
<div class="quick-actions" id="quickActions"></div>
<div class="input-glass">
<div class="input-wrapper">
<textarea id="userInput" rows="1" onkeydown="handleKey(event)"></textarea>
</div>
<button class="voice-btn" id="voiceBtn" onclick="toggleVoice()" title="Voice input">&#127908;</button>
<button class="send-btn" id="sendBtn" onclick="sendMessage()">&#10148;</button>
</div>
<div class="input-hint" data-i18n="input_hint">Press Enter to send &bull; Shift+Enter for new line</div>
</div>
</main>
</div><!-- /chat-container -->
</section><!-- /chat-section -->
<!-- ========== CTA SECTION ========== -->
<section class="cta-section">
<div class="orb orb-1" style="width:400px;height:400px;top:-100px;right:-50px;"></div>
<div class="orb orb-2" style="width:300px;height:300px;bottom:-50px;left:-50px;"></div>
<div class="cta-inner reveal">
<h2 data-i18n="cta_title">Ready to Optimize Your Maritime Operations?</h2>
<p data-i18n="cta_desc">Join shipping professionals who use SeaFare Montana to make faster, data-driven decisions.</p>
<a href="#chat" class="cta-primary" style="text-decoration:none;display:inline-flex;">
<span data-i18n="cta_btn">Start Free Consultation</span>
<span class="cta-arrow">&rarr;</span>
</a>
</div>
</section>
<!-- ========== FOOTER ========== -->
<footer class="site-footer">
<div class="footer-grid">
<div class="footer-brand">
<a href="#" class="nav-logo" onclick="event.preventDefault();window.scrollTo({top:0,behavior:'smooth'})">
<div class="nav-logo-icon">&#9875;</div>
<span class="nav-logo-text">SeaFare Montana</span>
</a>
<p data-i18n="footer_desc">AI-powered maritime logistics platform. 25 years of industry expertise, now available 24/7 through intelligent automation.</p>
</div>
<div class="footer-col">
<h4 data-i18n="footer_services">Services</h4>
<a href="#features" data-i18n="feat_search">Vessel Search</a>
<a href="#features" data-i18n="feat_position">Live Position</a>
<a href="#features" data-i18n="feat_demurrage">Demurrage Calculator</a>
<a href="#features" data-i18n="feat_contacts">Contact Introductions</a>
</div>
<div class="footer-col">
<h4 data-i18n="footer_platform">Platform</h4>
<a href="#how-it-works" data-i18n="nav_how">How It Works</a>
<a href="#chat" data-i18n="nav_chat">Try Now</a>
<a href="https://moltbook.com/u/SeaFare_Montana" target="_blank">Moltbook</a>
</div>
<div class="footer-col">
<h4 data-i18n="footer_contact">Contact</h4>
<a href="https://moltbook.com/u/SeaFare_Montana" target="_blank">Moltbook Profile</a>
</div>
</div>
<div class="footer-bottom">
&copy; 2025 SeaFare Montana &bull; Powered by <a href="https://moltbook.com/u/SeaFare_Montana" target="_blank">Moltbook</a>
&bull; <span data-i18n="footer_tagline">Montana Protocol &bull; 25 years expertise</span>
&bull; <span id="appVersion" style="opacity:0.5;"></span>
</div>
</footer>
<script>
// =============================================================================
// i18n — translations
// =============================================================================
const i18n = {
en: {
subtitle: 'Maritime Logistics Agent',
status: 'Online — Ready',
services_title: 'Services',
header_sub: 'Maritime Logistics Consultant',
clear_chat: 'Clear Chat',
input_hint: 'Press Enter to send \u2022 Shift+Enter for new line',
placeholder: 'Ask about vessels, positions, owners, demurrage...',
free: 'Free',
svc_search: 'Vessel Search',
svc_search_desc: 'Name, IMO, MMSI lookup',
svc_position: 'Vessel Position',
svc_position_desc: 'AIS real-time tracking',
svc_owner: 'Owner / Operator',
svc_owner_desc: 'Company information',
svc_demurrage: 'Demurrage Calculator',
svc_demurrage_desc: 'Delay cost estimation',
svc_contacts: 'Contact Introduction',
svc_contacts_desc: 'Shipper \u2194 Operator',
svc_consult: 'Freight Consultation',
svc_consult_desc: 'Expert market advice',
footer: 'Montana Protocol \u2022 25 years expertise',
welcome: '<strong>Welcome to SeaFare Montana!</strong><br><br>' +
"I'm your maritime logistics consultant with 25 years of industry expertise. I can help you with:<br><br>" +
'&#128674; <strong>Vessel search</strong> — find any ship by name, IMO, or MMSI<br>' +
'&#128205; <strong>Live position</strong> — real-time AIS tracking<br>' +
'&#127970; <strong>Owner & operator</strong> — company information<br>' +
'&#128178; <strong>Demurrage</strong> — calculate delay costs<br>' +
'&#128587; <strong>Introductions</strong> — connect with operators ($10 USDT)<br><br>' +
'Try the quick actions below or just ask me anything!',
chat_cleared: '<strong>Chat cleared.</strong><br>How can I help you today?',
confirm_clear: 'Are you sure you want to clear the chat history?',
error_generic: 'Sorry, something went wrong. Please try again.',
error_connection: 'Connection error. Make sure the server is running on port 5050.',
// Quick action buttons (labels)
qb_vessels_port: 'Vessels near port',
qb_route: 'Route A to B',
qb_cargo: 'Ship my cargo',
qb_search: 'Search vessel',
qb_demurrage: 'Demurrage calc',
qb_contacts: 'Find contacts',
// Quick action messages sent to bot
qm_btn_vessels_port: 'What vessels are near Rotterdam right now?',
qm_btn_route: 'Calculate route from Shanghai to Rotterdam for a container ship',
qm_btn_cargo: 'I need to ship 50000 tons of grain from Santos to Rotterdam',
qm_btn_search: 'Search vessel EVER GIVEN',
qm_btn_demurrage: 'Calculate demurrage: 10 agreed days, 14 actual, $25000/day',
qm_btn_contacts: 'Find contacts for bulk carrier operators',
// Auth
auth_login: 'Login',
auth_logout: 'Logout',
auth_title_login: 'Log In',
auth_title_register: 'Create Account',
auth_email: 'Email',
auth_password: 'Password',
auth_name: 'Name (optional)',
auth_login_btn: 'Log In',
auth_register_btn: 'Register',
auth_switch_to_register: "Don't have an account? Register",
auth_switch_to_login: 'Already have an account? Log In',
auth_close: 'Continue as guest',
auth_or: 'or',
auth_error_generic: 'Something went wrong. Try again.',
user_balance: 'Balance',
profile_btn: 'Cabinet',
profile_title: 'My Cabinet',
profile_subtitle: 'Fill out your profile so SeaFare Montana can give you more accurate, personalized answers.',
purchased_contacts_title: 'Purchased Contacts',
purchased_contacts_empty: 'No purchased contacts yet. Unlock contacts through the chat.',
profile_company_section: 'Company',
profile_company: 'Company Name',
profile_role: 'Your Role',
profile_experience: 'Experience (years)',
profile_fleet_size: 'Fleet Size (vessels)',
profile_vessel_types_title: 'Vessel Types',
profile_trade_routes_title: 'Trade Routes',
profile_cargo_types_title: 'Cargo Types',
profile_vessels_title: 'Vessels of Interest',
profile_vessels_hint: 'Add vessel names or IMO numbers you want to track',
profile_add_vessel: 'Add vessel',
profile_contact_section: 'Contact',
profile_phone: 'Phone',
profile_notes_title: 'Notes',
profile_notes_placeholder: 'Any additional info for the AI agent...',
profile_cancel: 'Cancel',
profile_save: 'Save Profile',
profile_saved: 'Profile saved successfully!',
voice_permission: 'Please allow microphone access to use voice input.',
voice_not_supported: 'Voice input is not supported in this browser. Try Chrome or Yandex Browser.',
voice_listening: 'Listening...',
voice_tooltip: 'Voice input',
topup_btn: 'Top Up',
topup_title: 'Top Up Balance',
topup_instruction: 'Send USDT (TRC20) to the address below. Your balance will be credited after confirmation.',
topup_copy: 'Copy',
topup_copied: 'Copied!',
topup_check: 'Check for deposits',
topup_checking: 'Checking...',
topup_found: 'Deposited: ${amount} USDT (fee 2%). Credited: ${credited} USDT. Balance: ${balance} USDT',
topup_none: 'No new deposits found. If you already sent USDT, wait a few minutes and try again.',
topup_error: 'Error checking deposits. Try again later.',
topup_close: 'Close',
topup_history: 'Deposit History',
deposit_fee_info: 'Service fee: 2%. Example: send $100 — receive $98.',
wallet_tab_deposit: 'Deposit',
wallet_tab_withdraw: 'Withdraw',
withdraw_your_balance: 'Your balance:',
withdraw_address_label: 'TRC20 Wallet Address',
withdraw_amount_label: 'Amount (USDT)',
withdraw_fee_label: 'Network fee:',
withdraw_total_label: 'Total deducted:',
withdraw_note: 'Minimum withdrawal: $2 USDT. Withdrawals are processed manually within 24 hours.',
withdraw_submit: 'Request Withdrawal',
withdraw_success: 'Withdrawal submitted! Amount: ${amount} USDT (fee: $1). New balance: ${balance} USDT',
withdraw_history: 'Withdrawal History',
withdraw_status_pending: 'Pending',
withdraw_status_completed: 'Completed',
withdraw_status_rejected: 'Rejected',
// Landing page
nav_features: 'Features',
nav_how: 'How It Works',
nav_chat: 'Try Now',
nav_start: 'Get Started',
hero_badge: 'AI-Powered Maritime Intelligence',
hero_title: 'Navigate Global Shipping<br><span class="gradient-text">With AI Precision</span>',
hero_subtitle: 'Real-time vessel tracking, intelligent port analysis, and demurrage calculations. Powered by AI with 25 years of maritime expertise built in.',
hero_cta: 'Start Free Consultation',
hero_cta2: 'See Features',
stat_vessels: 'Vessels Tracked',
stat_ports: 'Global Ports',
stat_ai: 'AI Availability',
stat_speed: 'Response Time',
features_badge: 'Capabilities',
features_title: 'Everything You Need for Maritime Operations',
features_subtitle: 'From vessel tracking to freight consultation — all powered by AI with real-time maritime data.',
feat_search: 'Vessel Search',
feat_search_desc: 'Find any vessel worldwide by name, IMO, or MMSI. Get instant details on type, flag, tonnage, and classification.',
feat_position: 'Live Position',
feat_position_desc: 'Real-time AIS tracking with coordinates, speed, course, destination, and navigation status.',
feat_owner: 'Owner & Operator',
feat_owner_desc: 'Comprehensive company information — registered owner, operator, ISM manager, and classification society.',
feat_demurrage: 'Demurrage Calculator',
feat_demurrage_desc: 'Calculate delay costs instantly. Input laytime, actual days, and rate — get precise financial breakdown.',
feat_contacts: 'Contact Introductions',
feat_contacts_desc: 'Connect with verified operators, charterers, and port agents. Unlock full contact details for direct communication.',
feat_consult: 'Freight Consultation',
feat_consult_desc: 'Expert market advice on routes, rates, and cargo matching. 25 years of maritime intelligence at your service.',
how_badge: 'Simple Process',
how_title: 'How It Works',
how_subtitle: 'Get maritime intelligence in seconds, not hours. No manuals, no complex interfaces.',
step1_title: 'Ask in Plain Language',
step1_desc: 'Type your question naturally — "Find bulk carriers near Singapore" or "Route from Santos to Rotterdam."',
step2_title: 'AI Analyzes Instantly',
step2_desc: 'Our AI queries real-time maritime data, AIS feeds, and port intelligence to find the best answer.',
step3_title: 'Get Actionable Results',
step3_desc: 'Receive structured data — tables, routes, costs, contacts — ready for immediate business decisions.',
chat_badge: 'Live AI Consultant',
chat_title: 'Try SeaFare Montana Now',
chat_subtitle: 'Start a conversation — ask about vessels, routes, cargo, or anything maritime.',
cta_title: 'Ready to Optimize Your Maritime Operations?',
cta_desc: 'Join shipping professionals who use SeaFare Montana to make faster, data-driven decisions.',
cta_btn: 'Start Free Consultation',
footer_desc: 'AI-powered maritime logistics platform. 25 years of industry expertise, now available 24/7 through intelligent automation.',
footer_services: 'Services',
footer_platform: 'Platform',
footer_contact: 'Contact',
footer_tagline: 'Montana Protocol \u2022 25 years expertise',
},
ru: {
subtitle: 'Агент морской логистики',
status: 'Онлайн — Готов',
services_title: 'Услуги',
header_sub: 'Консультант по морской логистике',
clear_chat: 'Очистить чат',
input_hint: 'Enter — отправить \u2022 Shift+Enter — новая строка',
placeholder: 'Спросите о судах, позициях, владельцах, демередже...',
free: 'Бесплатно',
svc_search: 'Поиск судна',
svc_search_desc: 'По названию, IMO, MMSI',
svc_position: 'Позиция судна',
svc_position_desc: 'AIS отслеживание',
svc_owner: 'Владелец / Оператор',
svc_owner_desc: 'Информация о компании',
svc_demurrage: 'Калькулятор демереджа',
svc_demurrage_desc: 'Расчёт стоимости простоя',
svc_contacts: 'Предоставление контактов',
svc_contacts_desc: 'Грузоотправитель \u2194 Оператор',
svc_consult: 'Фрахтовая консультация',
svc_consult_desc: 'Экспертный совет по рынку',
footer: 'Montana Protocol \u2022 25 лет опыта',
welcome: '<strong>Добро пожаловать в SeaFare Montana!</strong><br><br>' +
'Я ваш консультант по морской логистике с 25-летним опытом в индустрии. Я помогу вам с:<br><br>' +
'&#128674; <strong>Поиск судна</strong> — найти любое судно по названию, IMO или MMSI<br>' +
'&#128205; <strong>Позиция</strong> — отслеживание в реальном времени через AIS<br>' +
'&#127970; <strong>Владелец и оператор</strong> — информация о компаниях<br>' +
'&#128178; <strong>Демередж</strong> — расчёт стоимости простоя<br>' +
'&#128587; <strong>Контакты</strong> — связь с операторами ($10 USDT)<br><br>' +
'Попробуйте быстрые кнопки ниже или задайте вопрос!',
chat_cleared: '<strong>Чат очищен.</strong><br>Чем могу помочь?',
confirm_clear: 'Вы уверены, что хотите очистить историю чата?',
error_generic: 'Произошла ошибка. Попробуйте ещё раз.',
error_connection: 'Ошибка соединения. Убедитесь, что сервер запущен на порту 5050.',
qb_vessels_port: 'Суда у порта',
qb_route: 'Маршрут A-B',
qb_cargo: 'Отправить груз',
qb_search: 'Найти судно',
qb_demurrage: 'Демередж',
qb_contacts: 'Контакты',
qm_btn_vessels_port: 'Какие суда сейчас рядом с Роттердамом?',
qm_btn_route: 'Рассчитай маршрут из Шанхая в Роттердам для контейнеровоза',
qm_btn_cargo: 'Мне нужно отправить 50000 тонн зерна из Сантоса в Роттердам',
qm_btn_search: 'Найти судно EVER GIVEN',
qm_btn_demurrage: 'Рассчитать демередж: 10 согласованных дней, 14 фактических, $25000/день',
qm_btn_contacts: 'Найти контакты операторов балкеров',
// Auth
auth_login: 'Войти',
auth_logout: 'Выход',
auth_title_login: 'Вход',
auth_title_register: 'Регистрация',
auth_email: 'Email',
auth_password: 'Пароль',
auth_name: 'Имя (необязательно)',
auth_login_btn: 'Войти',
auth_register_btn: 'Зарегистрироваться',
auth_switch_to_register: 'Нет аккаунта? Зарегистрироваться',
auth_switch_to_login: 'Уже есть аккаунт? Войти',
auth_close: 'Продолжить как гость',
auth_or: 'или',
auth_error_generic: 'Произошла ошибка. Попробуйте ещё раз.',
user_balance: 'Баланс',
profile_btn: 'Кабинет',
profile_title: 'Мой кабинет',
profile_subtitle: 'Заполните профиль, чтобы SeaFare Montana давал более точные и персонализированные ответы.',
purchased_contacts_title: 'Купленные контакты',
purchased_contacts_empty: 'Пока нет купленных контактов. Разблокируйте контакты через чат.',
profile_company_section: 'Компания',
profile_company: 'Название компании',
profile_role: 'Ваша роль',
profile_experience: 'Опыт (лет)',
profile_fleet_size: 'Размер флота (судов)',
profile_vessel_types_title: 'Типы судов',
profile_trade_routes_title: 'Торговые маршруты',
profile_cargo_types_title: 'Типы грузов',
profile_vessels_title: 'Интересующие суда',
profile_vessels_hint: 'Добавьте названия судов или номера IMO для отслеживания',
profile_add_vessel: 'Добавить судно',
profile_contact_section: 'Контакт',
profile_phone: 'Телефон',
profile_notes_title: 'Заметки',
profile_notes_placeholder: 'Дополнительная информация для AI-агента...',
profile_cancel: 'Отмена',
profile_save: 'Сохранить профиль',
profile_saved: 'Профиль успешно сохранён!',
voice_permission: 'Разрешите доступ к микрофону для голосового ввода.',
voice_not_supported: 'Голосовой ввод не поддерживается в этом браузере. Попробуйте Chrome или Яндекс Браузер.',
voice_listening: 'Слушаю...',
voice_tooltip: 'Голосовой ввод',
topup_btn: 'Пополнить',
topup_title: 'Пополнение баланса',
topup_instruction: 'Отправьте USDT (TRC20) на адрес ниже. Баланс будет зачислен после подтверждения.',
topup_copy: 'Копировать',
topup_copied: 'Скопировано!',
topup_check: 'Проверить поступления',
topup_checking: 'Проверяю...',
topup_found: 'Получено: ${amount} USDT (комиссия 2%). Зачислено: ${credited} USDT. Баланс: ${balance} USDT',
topup_none: 'Новых поступлений нет. Если вы уже отправили USDT, подождите пару минут и попробуйте снова.',
topup_error: 'Ошибка проверки. Попробуйте позже.',
topup_close: 'Закрыть',
topup_history: 'История пополнений',
deposit_fee_info: 'Комиссия сервиса: 2%. Пример: отправляете $100 — получаете $98.',
wallet_tab_deposit: 'Пополнение',
wallet_tab_withdraw: 'Вывод',
withdraw_your_balance: 'Ваш баланс:',
withdraw_address_label: 'Адрес TRC20 кошелька',
withdraw_amount_label: 'Сумма (USDT)',
withdraw_fee_label: 'Комиссия сети:',
withdraw_total_label: 'Итого к списанию:',
withdraw_note: 'Минимальный вывод: $2 USDT. Выводы обрабатываются вручную в течение 24 часов.',
withdraw_submit: 'Запросить вывод',
withdraw_success: 'Заявка на вывод создана! Сумма: ${amount} USDT (комиссия: $1). Новый баланс: ${balance} USDT',
withdraw_history: 'История выводов',
withdraw_status_pending: 'В обработке',
withdraw_status_completed: 'Выполнен',
withdraw_status_rejected: 'Отклонён',
// Landing page
nav_features: 'Возможности',
nav_how: 'Как это работает',
nav_chat: 'Попробовать',
nav_start: 'Начать',
hero_badge: 'Морская разведка на базе ИИ',
hero_title: 'Управляйте глобальными перевозками<br><span class="gradient-text">с точностью ИИ</span>',
hero_subtitle: 'Отслеживание судов в реальном времени, анализ портов и расчёт демереджа. ИИ с 25-летним опытом в морской логистике.',
hero_cta: 'Бесплатная консультация',
hero_cta2: 'Возможности',
stat_vessels: 'Судов отслеживается',
stat_ports: 'Портов мира',
stat_ai: 'Доступность ИИ',
stat_speed: 'Время ответа',
features_badge: 'Возможности',
features_title: 'Всё необходимое для морских операций',
features_subtitle: 'От поиска судов до фрахтовых консультаций — всё на базе ИИ с данными в реальном времени.',
feat_search: 'Поиск судна',
feat_search_desc: 'Найдите любое судно по названию, IMO или MMSI. Мгновенная информация о типе, флаге, тоннаже и классе.',
feat_position: 'Позиция в реальном времени',
feat_position_desc: 'AIS-отслеживание с координатами, скоростью, курсом, пунктом назначения и навигационным статусом.',
feat_owner: 'Владелец и оператор',
feat_owner_desc: 'Полная информация о компании — зарегистрированный владелец, оператор, ISM-менеджер и классификационное общество.',
feat_demurrage: 'Калькулятор демереджа',
feat_demurrage_desc: 'Мгновенный расчёт стоимости простоя. Введите сталийное время, фактические дни и ставку — получите точный расчёт.',
feat_contacts: 'Предоставление контактов',
feat_contacts_desc: 'Связь с проверенными операторами, фрахтователями и портовыми агентами. Полные контактные данные.',
feat_consult: 'Фрахтовая консультация',
feat_consult_desc: 'Экспертный совет по маршрутам, ставкам и подбору грузов. 25 лет опыта в морской логистике.',
how_badge: 'Простой процесс',
how_title: 'Как это работает',
how_subtitle: 'Получайте информацию за секунды, а не часы. Без инструкций, без сложных интерфейсов.',
step1_title: 'Спросите на обычном языке',
step1_desc: 'Напишите вопрос как обычно — «Найди балкеры рядом с Сингапуром» или «Маршрут из Сантоса в Роттердам».',
step2_title: 'ИИ анализирует мгновенно',
step2_desc: 'Наш ИИ обращается к данным AIS, портовой разведке и морской аналитике для лучшего ответа.',
step3_title: 'Получите результат',
step3_desc: 'Структурированные данные — таблицы, маршруты, стоимость, контакты — готовые для бизнес-решений.',
chat_badge: 'ИИ-консультант онлайн',
chat_title: 'Попробуйте SeaFare Montana',
chat_subtitle: 'Начните разговор — спросите о судах, маршрутах, грузах или чём-то морском.',
cta_title: 'Готовы оптимизировать морские операции?',
cta_desc: 'Присоединяйтесь к профессионалам, которые используют SeaFare Montana для быстрых решений.',
cta_btn: 'Бесплатная консультация',
footer_desc: 'ИИ-платформа морской логистики. 25 лет отраслевого опыта, доступных 24/7 через интеллектуальную автоматизацию.',
footer_services: 'Услуги',
footer_platform: 'Платформа',
footer_contact: 'Контакты',
footer_tagline: 'Montana Protocol \u2022 25 лет опыта',
},
es: {
subtitle: 'Agente de Logística Marítima',
status: 'En línea — Listo',
services_title: 'Servicios',
header_sub: 'Consultor de Logística Marítima',
clear_chat: 'Limpiar chat',
input_hint: 'Enter para enviar \u2022 Shift+Enter nueva línea',
placeholder: 'Pregunte sobre buques, posiciones, propietarios, demora...',
free: 'Gratis',
svc_search: 'Búsqueda de buques',
svc_search_desc: 'Por nombre, IMO, MMSI',
svc_position: 'Posición del buque',
svc_position_desc: 'Seguimiento AIS en tiempo real',
svc_owner: 'Propietario / Operador',
svc_owner_desc: 'Información de la empresa',
svc_demurrage: 'Calculadora de demoras',
svc_demurrage_desc: 'Estimación de costos por demora',
svc_contacts: 'Contactos',
svc_contacts_desc: 'Cargador \u2194 Operador',
svc_consult: 'Consulta de flete',
svc_consult_desc: 'Asesoramiento experto',
footer: 'Montana Protocol \u2022 25 años de experiencia',
welcome: '<strong>¡Bienvenido a SeaFare Montana!</strong><br><br>' +
'Soy su consultor de logística marítima con 25 años de experiencia. Puedo ayudarle con:<br><br>' +
'&#128674; <strong>Búsqueda de buques</strong> — encontrar cualquier buque por nombre, IMO o MMSI<br>' +
'&#128205; <strong>Posición en vivo</strong> — seguimiento AIS en tiempo real<br>' +
'&#127970; <strong>Propietario y operador</strong> — información de empresas<br>' +
'&#128178; <strong>Demoras</strong> — calcular costos por demora<br>' +
'&#128587; <strong>Contactos</strong> — conexión con operadores ($10 USDT)<br><br>' +
'¡Pruebe las acciones rápidas o haga cualquier pregunta!',
chat_cleared: '<strong>Chat limpiado.</strong><br>¿En qué puedo ayudarle hoy?',
confirm_clear: '¿Está seguro de que desea borrar el historial del chat?',
error_generic: 'Lo siento, algo salió mal. Inténtelo de nuevo.',
error_connection: 'Error de conexión. Asegúrese de que el servidor esté en el puerto 5050.',
qb_vessels_port: 'Buques en puerto',
qb_route: 'Ruta A-B',
qb_cargo: 'Enviar carga',
qb_search: 'Buscar buque',
qb_demurrage: 'Demora',
qb_contacts: 'Contactos',
qm_btn_vessels_port: '¿Qué buques hay cerca de Róterdam ahora?',
qm_btn_route: 'Calcula la ruta de Shanghái a Róterdam para un portacontenedores',
qm_btn_cargo: 'Necesito enviar 50000 toneladas de grano de Santos a Róterdam',
qm_btn_search: 'Buscar buque EVER GIVEN',
qm_btn_demurrage: 'Calcular demora: 10 días acordados, 14 reales, $25000/día',
qm_btn_contacts: 'Buscar contactos de operadores de graneleros',
// Auth
auth_login: 'Entrar',
auth_logout: 'Salir',
auth_title_login: 'Iniciar sesión',
auth_title_register: 'Crear cuenta',
auth_email: 'Correo electrónico',
auth_password: 'Contraseña',
auth_name: 'Nombre (opcional)',
auth_login_btn: 'Iniciar sesión',
auth_register_btn: 'Registrarse',
auth_switch_to_register: '¿No tiene cuenta? Regístrese',
auth_switch_to_login: '¿Ya tiene cuenta? Inicie sesión',
auth_close: 'Continuar como invitado',
auth_or: 'o',
auth_error_generic: 'Algo salió mal. Inténtelo de nuevo.',
user_balance: 'Saldo',
profile_btn: 'Gabinete',
profile_title: 'Mi Gabinete',
profile_subtitle: 'Complete su perfil para que SeaFare Montana le dé respuestas más precisas y personalizadas.',
purchased_contacts_title: 'Contactos comprados',
purchased_contacts_empty: 'Aún no tiene contactos comprados. Desbloquee contactos a través del chat.',
profile_company_section: 'Empresa',
profile_company: 'Nombre de la empresa',
profile_role: 'Su rol',
profile_experience: 'Experiencia (años)',
profile_fleet_size: 'Tamaño de la flota (buques)',
profile_vessel_types_title: 'Tipos de buques',
profile_trade_routes_title: 'Rutas comerciales',
profile_cargo_types_title: 'Tipos de carga',
profile_vessels_title: 'Buques de interés',
profile_vessels_hint: 'Agregue nombres de buques o números IMO para seguimiento',
profile_add_vessel: 'Agregar buque',
profile_contact_section: 'Contacto',
profile_phone: 'Teléfono',
profile_notes_title: 'Notas',
profile_notes_placeholder: 'Información adicional para el agente AI...',
profile_cancel: 'Cancelar',
profile_save: 'Guardar perfil',
profile_saved: '¡Perfil guardado exitosamente!',
voice_permission: 'Permita el acceso al micrófono para la entrada de voz.',
voice_not_supported: 'La entrada de voz no es compatible con este navegador. Pruebe Chrome o Yandex Browser.',
voice_listening: 'Escuchando...',
voice_tooltip: 'Entrada de voz',
topup_btn: 'Recargar',
topup_title: 'Recargar saldo',
topup_instruction: 'Envíe USDT (TRC20) a la dirección de abajo. Su saldo se acreditará después de la confirmación.',
topup_copy: 'Copiar',
topup_copied: '¡Copiado!',
topup_check: 'Verificar depósitos',
topup_checking: 'Verificando...',
topup_found: 'Depositado: ${amount} USDT (comisión 2%). Acreditado: ${credited} USDT. Saldo: ${balance} USDT',
topup_none: 'No se encontraron nuevos depósitos. Si ya envió USDT, espere unos minutos e intente de nuevo.',
topup_error: 'Error al verificar. Intente más tarde.',
topup_close: 'Cerrar',
topup_history: 'Historial de depósitos',
deposit_fee_info: 'Comisión del servicio: 2%. Ejemplo: envía $100 — recibe $98.',
wallet_tab_deposit: 'Depósito',
wallet_tab_withdraw: 'Retiro',
withdraw_your_balance: 'Su saldo:',
withdraw_address_label: 'Dirección de billetera TRC20',
withdraw_amount_label: 'Monto (USDT)',
withdraw_fee_label: 'Comisión de red:',
withdraw_total_label: 'Total a deducir:',
withdraw_note: 'Retiro mínimo: $2 USDT. Los retiros se procesan manualmente en 24 horas.',
withdraw_submit: 'Solicitar retiro',
withdraw_success: '¡Retiro solicitado! Monto: ${amount} USDT (comisión: $1). Nuevo saldo: ${balance} USDT',
withdraw_history: 'Historial de retiros',
withdraw_status_pending: 'Pendiente',
withdraw_status_completed: 'Completado',
withdraw_status_rejected: 'Rechazado',
// Landing page
nav_features: 'Funciones',
nav_how: 'Cómo funciona',
nav_chat: 'Probar ahora',
nav_start: 'Comenzar',
hero_badge: 'Inteligencia Marítima con IA',
hero_title: 'Navegue el comercio global<br><span class="gradient-text">con precisión de IA</span>',
hero_subtitle: 'Seguimiento de buques en tiempo real, análisis de puertos y cálculos de demora. IA con 25 años de experiencia marítima.',
hero_cta: 'Consulta gratuita',
hero_cta2: 'Ver funciones',
stat_vessels: 'Buques rastreados',
stat_ports: 'Puertos globales',
stat_ai: 'Disponibilidad IA',
stat_speed: 'Tiempo de respuesta',
features_badge: 'Capacidades',
features_title: 'Todo lo que necesita para operaciones marítimas',
features_subtitle: 'Desde búsqueda de buques hasta consultoría de fletes — todo con IA y datos en tiempo real.',
feat_search: 'Búsqueda de buques',
feat_search_desc: 'Encuentre cualquier buque por nombre, IMO o MMSI. Información instantánea sobre tipo, bandera, tonelaje y clase.',
feat_position: 'Posición en vivo',
feat_position_desc: 'Seguimiento AIS con coordenadas, velocidad, rumbo, destino y estado de navegación.',
feat_owner: 'Propietario y operador',
feat_owner_desc: 'Información completa — propietario registrado, operador, gestor ISM y sociedad de clasificación.',
feat_demurrage: 'Calculadora de demoras',
feat_demurrage_desc: 'Calcule costos por demora al instante. Ingrese estadía, días reales y tarifa — obtenga desglose financiero.',
feat_contacts: 'Contactos',
feat_contacts_desc: 'Conéctese con operadores verificados, fletadores y agentes portuarios. Datos de contacto completos.',
feat_consult: 'Consulta de flete',
feat_consult_desc: 'Asesoramiento experto en rutas, tarifas y carga. 25 años de inteligencia marítima a su servicio.',
how_badge: 'Proceso simple',
how_title: 'Cómo funciona',
how_subtitle: 'Obtenga inteligencia marítima en segundos, no horas. Sin manuales, sin interfaces complejas.',
step1_title: 'Pregunte en lenguaje natural',
step1_desc: 'Escriba su pregunta naturalmente — "Buscar graneleros cerca de Singapur" o "Ruta de Santos a Róterdam."',
step2_title: 'La IA analiza al instante',
step2_desc: 'Nuestra IA consulta datos AIS, inteligencia portuaria y análisis marítimo para la mejor respuesta.',
step3_title: 'Obtenga resultados',
step3_desc: 'Datos estructurados — tablas, rutas, costos, contactos — listos para decisiones de negocio.',
chat_badge: 'Consultor IA en vivo',
chat_title: 'Pruebe SeaFare Montana',
chat_subtitle: 'Inicie una conversación — pregunte sobre buques, rutas, carga o cualquier tema marítimo.',
cta_title: '¿Listo para optimizar sus operaciones marítimas?',
cta_desc: 'Únase a profesionales que usan SeaFare Montana para decisiones más rápidas basadas en datos.',
cta_btn: 'Consulta gratuita',
footer_desc: 'Plataforma de logística marítima con IA. 25 años de experiencia, disponible 24/7 mediante automatización inteligente.',
footer_services: 'Servicios',
footer_platform: 'Plataforma',
footer_contact: 'Contacto',
footer_tagline: 'Montana Protocol \u2022 25 años de experiencia',
}
};
// =============================================================================
// State
// =============================================================================
let currentLang = localStorage.getItem('seafare_lang') || 'en';
let userHasSentMessage = false;
let sessionChecked = false;
let authToken = localStorage.getItem('seafare_token') || null;
let currentUser = null;
let authMode = 'login';
let googleClientId = null;
const API_BASE = '';
const messagesDiv = document.getElementById('messages');
const input = document.getElementById('userInput');
const sendBtn = document.getElementById('sendBtn');
function t(key) {
return (i18n[currentLang] && i18n[currentLang][key]) || i18n.en[key] || key;
}
// =============================================================================
// Auth
// =============================================================================
function showAuthModal() {
document.getElementById('authOverlay').classList.remove('hidden');
updateAuthForm();
// Re-init Google button after modal is visible (GSI needs visible container)
if (googleClientId) {
setTimeout(() => initGoogleButton(), 100);
}
}
function hideAuthModal(e) {
if (e) e.preventDefault();
document.getElementById('authOverlay').classList.add('hidden');
}
function toggleAuthMode(e) {
e.preventDefault();
authMode = authMode === 'login' ? 'register' : 'login';
updateAuthForm();
}
function updateAuthForm() {
const isLogin = authMode === 'login';
document.getElementById('authTitle').textContent = t(isLogin ? 'auth_title_login' : 'auth_title_register');
document.getElementById('authSubmitBtn').textContent = t(isLogin ? 'auth_login_btn' : 'auth_register_btn');
document.getElementById('authSwitchLink').textContent = t(isLogin ? 'auth_switch_to_register' : 'auth_switch_to_login');
document.getElementById('authCloseText').textContent = t('auth_close');
document.getElementById('nameField').style.display = isLogin ? 'none' : 'block';
document.getElementById('authError').style.display = 'none';
document.getElementById('authEmail').placeholder = t('auth_email');
document.getElementById('authPassword').placeholder = t('auth_password');
document.getElementById('authName').placeholder = t('auth_name');
// Google Sign-In
const hasGoogle = !!googleClientId;
document.getElementById('authDivider').style.display = hasGoogle ? 'flex' : 'none';
document.getElementById('googleBtnWrap').style.display = hasGoogle ? 'flex' : 'none';
if (hasGoogle) {
document.querySelector('#authDivider span').textContent = t('auth_or');
initGoogleButton();
}
}
let googleBtnRendered = false;
function initGoogleButton() {
if (!googleClientId || !window.google) return;
try {
if (!googleBtnRendered) {
google.accounts.id.initialize({
client_id: googleClientId,
callback: handleGoogleResponse,
ux_mode: 'popup',
});
}
// Re-render button each time modal opens (GSI needs visible container)
const container = document.getElementById('googleSignInBtn');
if (container) {
container.innerHTML = '';
const btnWidth = Math.min(300, window.innerWidth - 80);
google.accounts.id.renderButton(container, {
theme: 'filled_blue', size: 'large',
width: btnWidth, text: 'signin_with'
});
}
googleBtnRendered = true;
} catch (e) {
console.error('GSI init error:', e);
}
}
async function handleGoogleResponse(response) {
document.getElementById('authError').style.display = 'none';
if (!response || !response.credential) {
showAuthError('Google sign-in failed. Please try again.');
return;
}
try {
const resp = await fetch(API_BASE + '/api/v1/auth/google', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: response.credential, lang: currentLang })
});
const data = await resp.json();
if (data.success) {
authToken = data.token;
currentUser = data.user;
localStorage.setItem('seafare_token', authToken);
hideAuthModal();
onLoginSuccess();
} else {
showAuthError(data.error || t('auth_error_generic'));
}
} catch (err) {
console.error('Google auth error:', err);
showAuthError(t('auth_error_generic'));
}
}
async function handleAuth(e) {
e.preventDefault();
const btn = document.getElementById('authSubmitBtn');
btn.disabled = true;
document.getElementById('authError').style.display = 'none';
const email = document.getElementById('authEmail').value.trim();
const password = document.getElementById('authPassword').value;
const name = document.getElementById('authName').value.trim();
const endpoint = authMode === 'login' ? '/api/v1/auth/login' : '/api/v1/auth/register';
const body = { email, password, lang: currentLang };
if (authMode === 'register') body.name = name;
try {
const resp = await fetch(API_BASE + endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const data = await resp.json();
if (data.success) {
authToken = data.token;
currentUser = data.user;
localStorage.setItem('seafare_token', authToken);
hideAuthModal();
onLoginSuccess();
} else {
showAuthError(data.error || t('auth_error_generic'));
}
} catch (err) {
showAuthError(t('auth_error_generic'));
}
btn.disabled = false;
}
function showAuthError(msg) {
const el = document.getElementById('authError');
el.textContent = msg;
el.style.display = 'block';
}
async function checkSession() {
if (!authToken) return;
try {
const resp = await fetch(API_BASE + '/api/v1/auth/me', {
headers: { 'Authorization': 'Bearer ' + authToken }
});
if (resp.ok) {
const data = await resp.json();
currentUser = data.user;
onLoginSuccess();
} else {
localStorage.removeItem('seafare_token');
authToken = null;
}
} catch (e) {
// server unreachable — keep guest mode
}
}
async function onLoginSuccess() {
document.getElementById('userInfo').style.display = 'flex';
document.getElementById('userName').textContent = currentUser.name || currentUser.email;
document.getElementById('userBalance').textContent = '$' + (currentUser.balance || 0).toFixed(2);
document.getElementById('adminBadge').style.display = currentUser.is_admin ? '' : 'none';
document.getElementById('revenueBtn').style.display = currentUser.is_admin ? '' : 'none';
document.getElementById('logoutBtn').style.display = '';
document.getElementById('loginBtn').style.display = 'none';
updateNavAuth();
// Load saved chat history
try {
const resp = await fetch(API_BASE + '/api/v1/chat/history', {
headers: { 'Authorization': 'Bearer ' + authToken }
});
if (resp.ok) {
const data = await resp.json();
if (data.messages && data.messages.length > 0) {
messagesDiv.innerHTML = '';
userHasSentMessage = true;
for (const msg of data.messages) {
addMessage(
msg.role === 'user' ? msg.message : formatResponse(msg.message),
msg.role === 'user'
);
}
}
}
} catch (e) { /* ignore */ }
}
async function doLogout() {
try {
await fetch(API_BASE + '/api/v1/auth/logout', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + authToken }
});
} catch (e) { /* ignore */ }
authToken = null;
currentUser = null;
localStorage.removeItem('seafare_token');
document.getElementById('userInfo').style.display = 'none';
document.getElementById('adminBadge').style.display = 'none';
document.getElementById('revenueBtn').style.display = 'none';
document.getElementById('logoutBtn').style.display = 'none';
document.getElementById('loginBtn').style.display = '';
messagesDiv.innerHTML = '';
userHasSentMessage = false;
showWelcome();
updateNavAuth();
}
// =============================================================================
// Revenue Dashboard (admin)
// =============================================================================
async function showRevenue() {
if (!authToken || !currentUser || !currentUser.is_admin) return;
document.getElementById('revenueOverlay').classList.remove('hidden');
document.getElementById('revenueContent').innerHTML =
'<div style="text-align:center;padding:40px;color:var(--text-dim);">Loading...</div>';
try {
const resp = await fetch(API_BASE + '/api/v1/admin/revenue', {
headers: { 'Authorization': 'Bearer ' + authToken }
});
const data = await resp.json();
if (!data.success) {
document.getElementById('revenueContent').innerHTML =
'<div style="text-align:center;padding:40px;color:#ff6b6b;">Error: ' + (data.error || 'Unknown') + '</div>';
return;
}
let html = '<div class="revenue-cards">';
html += '<div class="revenue-card"><div class="label">Total Revenue</div><div class="value green">$' + data.total_revenue.toFixed(2) + '</div></div>';
html += '<div class="revenue-card"><div class="label">Today</div><div class="value green">$' + data.today_revenue.toFixed(2) + '</div></div>';
html += '<div class="revenue-card"><div class="label">Platform Profit</div><div class="value orange">$' + data.platform_profit.toFixed(2) + '</div></div>';
html += '<div class="revenue-card"><div class="label">User Balances</div><div class="value">$' + data.total_user_balances.toFixed(2) + '</div></div>';
html += '</div>';
// By service
if (data.by_service && data.by_service.length > 0) {
html += '<h3 style="color:var(--text);font-size:14px;margin:16px 0 8px;">By Service</h3>';
html += '<table class="revenue-table"><tr><th>Service</th><th>Count</th><th>Total</th></tr>';
for (const s of data.by_service) {
html += '<tr><td>' + s.service + '</td><td>' + s.count + '</td><td>$' + s.total.toFixed(2) + '</td></tr>';
}
html += '</table>';
}
// Recent charges
if (data.recent_charges && data.recent_charges.length > 0) {
html += '<h3 style="color:var(--text);font-size:14px;margin:20px 0 8px;">Recent Charges</h3>';
html += '<table class="revenue-table"><tr><th>User</th><th>Service</th><th>Amount</th><th>Date</th></tr>';
for (const c of data.recent_charges) {
const date = c.date ? c.date.split(' ')[0] : '-';
html += '<tr><td>' + c.user + '</td><td>' + c.service + '</td><td>$' + c.amount.toFixed(2) + '</td><td>' + date + '</td></tr>';
}
html += '</table>';
}
// Summary row
html += '<div style="margin-top:20px;padding:12px;background:rgba(22,34,64,0.5);border-radius:10px;font-size:12px;color:var(--text-dim);">';
html += 'Total deposited: $' + data.total_deposited.toFixed(2) +
' &nbsp;|&nbsp; Withdrawn: $' + data.total_withdrawn.toFixed(2) +
' &nbsp;|&nbsp; User balances: $' + data.total_user_balances.toFixed(2);
html += '</div>';
document.getElementById('revenueContent').innerHTML = html;
} catch (e) {
document.getElementById('revenueContent').innerHTML =
'<div style="text-align:center;padding:40px;color:#ff6b6b;">Connection error</div>';
}
}
function hideRevenue() {
document.getElementById('revenueOverlay').classList.add('hidden');
}
document.getElementById('revenueOverlay').addEventListener('click', (e) => {
if (e.target === e.currentTarget) hideRevenue();
});
// =============================================================================
// Profile Cabinet
// =============================================================================
let profileData = null;
function showProfile() {
document.getElementById('profileOverlay').classList.remove('hidden');
loadProfile();
}
function hideProfile() {
document.getElementById('profileOverlay').classList.add('hidden');
document.getElementById('profileSavedMsg').style.display = 'none';
}
async function loadProfile() {
if (!authToken) return;
try {
const resp = await fetch(API_BASE + '/api/v1/profile', {
headers: { 'Authorization': 'Bearer ' + authToken }
});
const data = await resp.json();
if (data.success && data.profile) {
profileData = data.profile;
fillProfileForm(data.profile);
} else {
profileData = null;
clearProfileForm();
}
} catch (e) {
console.error('Load profile error:', e);
}
loadPurchasedContacts();
}
async function loadPurchasedContacts() {
const section = document.getElementById('purchasedContactsSection');
const list = document.getElementById('purchasedContactsList');
if (!authToken) { section.style.display = 'none'; return; }
try {
const resp = await fetch(API_BASE + '/api/v1/purchased-contacts', {
headers: { 'Authorization': 'Bearer ' + authToken }
});
const data = await resp.json();
if (data.success && data.contacts && data.contacts.length > 0) {
section.style.display = '';
list.innerHTML = data.contacts.map(pc => {
const c = pc.contact || {};
const name = c.company_name || c.contact_person || pc.query || '—';
const typ = c.type || pc.contact_type || '';
const email = c.email ? `<div class="pcc-detail">&#9993; <a href="mailto:${c.email}">${c.email}</a></div>` : '';
const phone = c.phone ? `<div class="pcc-detail">&#128222; <a href="tel:${c.phone}">${c.phone}</a></div>` : '';
const addr = c.address ? `<div class="pcc-detail">&#128205; ${c.address}${c.country ? ', ' + c.country : ''}</div>` : '';
const website = c.website ? `<div class="pcc-detail">&#127760; <a href="${c.website}" target="_blank">${c.website}</a></div>` : '';
const person = c.contact_person && c.company_name ? `<div class="pcc-detail">&#128100; ${c.contact_person}</div>` : '';
const date = pc.purchased_at ? new Date(pc.purchased_at).toLocaleDateString() : '';
return `<div class="purchased-contact-card">
<span class="pcc-company">${name}</span>${typ ? `<span class="pcc-type">${typ}</span>` : ''}
${person}${email}${phone}${addr}${website}
${date ? `<div class="pcc-date">${date}</div>` : ''}
</div>`;
}).join('');
} else {
section.style.display = '';
list.innerHTML = `<div class="purchased-empty">${t('purchased_contacts_empty')}</div>`;
}
} catch (e) {
section.style.display = 'none';
}
}
function fillProfileForm(p) {
document.getElementById('profCompany').value = p.company_name || '';
document.getElementById('profRole').value = p.role || '';
document.getElementById('profExperience').value = p.experience_years || '';
document.getElementById('profFleetSize').value = p.fleet_size || '';
document.getElementById('profPhone').value = p.phone || '';
document.getElementById('profTelegram').value = p.telegram || '';
document.getElementById('profNotes').value = p.notes || '';
// Chips
setChips('profVesselTypes', p.vessel_types || []);
setChips('profTradeRoutes', p.trade_routes || []);
setChips('profCargoTypes', p.cargo_types || []);
// Vessels of interest
const list = document.getElementById('profVesselsList');
list.innerHTML = '';
(p.vessels_of_interest || []).forEach(v => addVesselRow(v));
}
function clearProfileForm() {
document.getElementById('profCompany').value = '';
document.getElementById('profRole').value = '';
document.getElementById('profExperience').value = '';
document.getElementById('profFleetSize').value = '';
document.getElementById('profPhone').value = '';
document.getElementById('profTelegram').value = '';
document.getElementById('profNotes').value = '';
setChips('profVesselTypes', []);
setChips('profTradeRoutes', []);
setChips('profCargoTypes', []);
document.getElementById('profVesselsList').innerHTML = '';
}
function setChips(groupId, selected) {
document.querySelectorAll('#' + groupId + ' .chip').forEach(chip => {
chip.classList.toggle('active', selected.includes(chip.dataset.val));
});
}
function getChips(groupId) {
return Array.from(document.querySelectorAll('#' + groupId + ' .chip.active'))
.map(c => c.dataset.val);
}
function addVesselRow(value) {
const list = document.getElementById('profVesselsList');
const row = document.createElement('div');
row.className = 'profile-vessel-row';
row.innerHTML = '<input type="text" placeholder="e.g. EVER GIVEN or IMO 9811000" value="' + (value || '') + '">' +
'<button onclick="this.parentElement.remove()">&times;</button>';
list.appendChild(row);
}
function getVessels() {
return Array.from(document.querySelectorAll('#profVesselsList input'))
.map(i => i.value.trim())
.filter(v => v.length > 0);
}
async function saveProfile() {
if (!authToken) return;
const body = {
company_name: document.getElementById('profCompany').value.trim(),
role: document.getElementById('profRole').value,
experience_years: parseInt(document.getElementById('profExperience').value) || 0,
fleet_size: parseInt(document.getElementById('profFleetSize').value) || 0,
vessel_types: getChips('profVesselTypes'),
trade_routes: getChips('profTradeRoutes'),
cargo_types: getChips('profCargoTypes'),
vessels_of_interest: getVessels(),
phone: document.getElementById('profPhone').value.trim(),
telegram: document.getElementById('profTelegram').value.trim(),
notes: document.getElementById('profNotes').value.trim(),
};
try {
const resp = await fetch(API_BASE + '/api/v1/profile', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + authToken
},
body: JSON.stringify(body)
});
const data = await resp.json();
if (data.success) {
profileData = data.profile;
const msg = document.getElementById('profileSavedMsg');
msg.style.display = 'block';
setTimeout(() => { msg.style.display = 'none'; }, 3000);
}
} catch (e) {
console.error('Save profile error:', e);
}
}
// Chip click handlers
document.querySelectorAll('.chip-group .chip').forEach(chip => {
chip.addEventListener('click', () => chip.classList.toggle('active'));
});
// Close profile on overlay click
document.getElementById('profileOverlay').addEventListener('click', (e) => {
if (e.target === e.currentTarget) hideProfile();
});
// =============================================================================
// Deposit / Top Up
// =============================================================================
let depositAddress = '';
async function showDeposit() {
if (!authToken) { showAuthModal(); return; }
document.getElementById('depositOverlay').classList.remove('hidden');
switchWalletTab('deposit');
document.getElementById('depositLoading').style.display = 'block';
document.getElementById('depositQR').style.display = 'none';
document.getElementById('depositAddressWrap').style.display = 'none';
document.getElementById('depositResult').style.display = 'none';
document.getElementById('depositHistory').style.display = 'none';
try {
const resp = await fetch(API_BASE + '/api/v1/wallet', {
headers: { 'Authorization': 'Bearer ' + authToken }
});
const data = await resp.json();
if (data.success) {
depositAddress = data.address;
document.getElementById('depositLoading').style.display = 'none';
document.getElementById('depositAddress').textContent = data.address;
document.getElementById('depositAddressWrap').style.display = 'flex';
// QR code via free API
const qrEl = document.getElementById('depositQR');
qrEl.innerHTML = '<img src="https://api.qrserver.com/v1/create-qr-code/?size=184x184&data=' +
encodeURIComponent(data.address) + '" alt="QR">';
qrEl.style.display = 'flex';
// Show deposit history
if (data.deposits && data.deposits.length > 0) {
renderDepositHistory(data.deposits);
}
} else {
document.getElementById('depositLoading').textContent = data.error || 'Error';
}
} catch (e) {
document.getElementById('depositLoading').textContent = 'Connection error';
}
}
function hideDeposit() {
document.getElementById('depositOverlay').classList.add('hidden');
}
function copyDepositAddress() {
if (!depositAddress) return;
navigator.clipboard.writeText(depositAddress).then(() => {
const btn = document.querySelector('.deposit-copy-btn');
const orig = btn.textContent;
btn.textContent = t('topup_copied');
setTimeout(() => { btn.textContent = orig; }, 2000);
}).catch(() => {
// Fallback for older browsers
const ta = document.createElement('textarea');
ta.value = depositAddress;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
});
}
async function checkDeposits() {
if (!authToken) return;
const btn = document.getElementById('depositCheckBtn');
const resultEl = document.getElementById('depositResult');
btn.disabled = true;
btn.textContent = t('topup_checking');
resultEl.style.display = 'none';
try {
const resp = await fetch(API_BASE + '/api/v1/wallet/check', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + authToken,
'Content-Type': 'application/json'
}
});
const data = await resp.json();
if (data.success) {
if (data.new_deposits > 0) {
resultEl.className = 'deposit-result success';
resultEl.textContent = t('topup_found')
.replace('${amount}', data.new_amount.toFixed(2))
.replace('${credited}', (data.credited_amount || data.new_amount).toFixed(2))
.replace('${balance}', data.balance.toFixed(2));
// Update header balance
if (currentUser) {
currentUser.balance = data.balance;
document.getElementById('userBalance').textContent = '$' + data.balance.toFixed(2);
}
} else {
resultEl.className = 'deposit-result empty';
resultEl.textContent = t('topup_none');
}
} else {
resultEl.className = 'deposit-result error';
resultEl.textContent = data.error || t('topup_error');
}
resultEl.style.display = 'block';
} catch (e) {
resultEl.className = 'deposit-result error';
resultEl.textContent = t('topup_error');
resultEl.style.display = 'block';
}
btn.disabled = false;
btn.textContent = t('topup_check');
}
function renderDepositHistory(deposits) {
const container = document.getElementById('depositHistory');
let html = '<h4>' + t('topup_history') + '</h4>';
deposits.forEach(d => {
const date = new Date(d.confirmed_at).toLocaleDateString();
const txShort = d.tx_id ? d.tx_id.substring(0, 12) + '...' : '';
html += '<div class="deposit-tx"><span>' + date + ' ' + txShort + '</span><span class="amount">+' + d.amount.toFixed(2) + ' USDT</span></div>';
});
container.innerHTML = html;
container.style.display = 'block';
}
// =============================================================================
// Wallet Tabs
// =============================================================================
function switchWalletTab(tab) {
document.getElementById('walletTabDeposit').classList.toggle('active', tab === 'deposit');
document.getElementById('walletTabWithdraw').classList.toggle('active', tab === 'withdraw');
document.getElementById('walletContentDeposit').classList.toggle('active', tab === 'deposit');
document.getElementById('walletContentWithdraw').classList.toggle('active', tab === 'withdraw');
if (tab === 'withdraw') {
// Update balance display
const bal = currentUser ? currentUser.balance : 0;
document.getElementById('withdrawBalanceDisplay').textContent = '$' + bal.toFixed(2);
loadWithdrawals();
}
}
// =============================================================================
// Withdrawal
// =============================================================================
function updateWithdrawTotal() {
const amount = parseFloat(document.getElementById('withdrawAmount').value) || 0;
const totalLine = document.getElementById('withdrawTotalLine');
const totalDisplay = document.getElementById('withdrawTotalDisplay');
if (amount >= 2) {
const total = (amount + 1).toFixed(2);
totalDisplay.textContent = '$' + total + ' USDT';
totalLine.style.display = 'block';
} else {
totalLine.style.display = 'none';
}
}
async function submitWithdrawal() {
if (!authToken) return;
const btn = document.getElementById('withdrawSubmitBtn');
const resultEl = document.getElementById('withdrawResult');
const address = document.getElementById('withdrawAddress').value.trim();
const amount = document.getElementById('withdrawAmount').value;
btn.disabled = true;
resultEl.style.display = 'none';
try {
const resp = await fetch(API_BASE + '/api/v1/wallet/withdraw', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + authToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ amount: parseFloat(amount), to_address: address, lang: currentLang })
});
const data = await resp.json();
if (data.success) {
resultEl.className = 'withdraw-result success';
resultEl.textContent = t('withdraw_success')
.replace('${amount}', data.withdrawal.amount.toFixed(2))
.replace('${balance}', data.balance.toFixed(2));
// Update user balance
if (currentUser) {
currentUser.balance = data.balance;
document.getElementById('userBalance').textContent = '$' + data.balance.toFixed(2);
document.getElementById('withdrawBalanceDisplay').textContent = '$' + data.balance.toFixed(2);
}
// Clear form
document.getElementById('withdrawAddress').value = '';
document.getElementById('withdrawAmount').value = '';
// Reload history
loadWithdrawals();
} else {
resultEl.className = 'withdraw-result error';
resultEl.textContent = data.error || 'Error';
}
resultEl.style.display = 'block';
} catch (e) {
resultEl.className = 'withdraw-result error';
resultEl.textContent = 'Connection error';
resultEl.style.display = 'block';
}
btn.disabled = false;
}
async function loadWithdrawals() {
if (!authToken) return;
try {
const resp = await fetch(API_BASE + '/api/v1/wallet/withdrawals', {
headers: { 'Authorization': 'Bearer ' + authToken }
});
const data = await resp.json();
if (data.success && data.withdrawals && data.withdrawals.length > 0) {
renderWithdrawHistory(data.withdrawals);
}
} catch (e) { /* silent */ }
}
function renderWithdrawHistory(withdrawals) {
const container = document.getElementById('withdrawHistory');
let html = '<h4>' + t('withdraw_history') + '</h4>';
withdrawals.forEach(w => {
const date = new Date(w.created_at).toLocaleDateString();
const addrShort = w.to_address ? w.to_address.substring(0, 8) + '...' + w.to_address.substring(30) : '';
const statusKey = 'withdraw_status_' + w.status;
const statusText = t(statusKey);
html += '<div class="withdraw-tx">' +
'<span>' + date + ' ' + addrShort + '</span>' +
'<span class="withdraw-status ' + w.status + '">' + statusText + '</span>' +
'<span class="amount">-' + w.amount.toFixed(2) + ' USDT</span>' +
'</div>';
});
container.innerHTML = html;
container.style.display = 'block';
}
// Close deposit on overlay click
document.getElementById('depositOverlay').addEventListener('click', (e) => {
if (e.target === e.currentTarget) hideDeposit();
});
// =============================================================================
// Sidebar toggle (mobile)
// =============================================================================
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('open');
document.getElementById('sidebarBackdrop').classList.toggle('open');
}
// Close sidebar when clicking a service item (mobile)
document.querySelectorAll('.service-item[data-quick]').forEach(item => {
item.addEventListener('click', () => {
document.getElementById('sidebar').classList.remove('open');
document.getElementById('sidebarBackdrop').classList.remove('open');
});
});
// =============================================================================
// Language switching
// =============================================================================
function setLang(lang) {
currentLang = lang;
localStorage.setItem('seafare_lang', lang);
// Update active button
document.querySelectorAll('.lang-btn').forEach(btn => {
btn.classList.toggle('active', btn.textContent.trim() === lang.toUpperCase());
});
// Update all data-i18n elements
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
el.innerHTML = t(key);
});
// Update placeholder
input.placeholder = t('placeholder');
// Update quick action buttons
renderQuickActions();
// Re-render welcome if user hasn't sent any messages yet
// Skip on initial load — init() will show welcome after checkSession()
if (!userHasSentMessage && sessionChecked) {
messagesDiv.innerHTML = '';
showWelcome();
}
}
function renderQuickActions() {
const qa = document.getElementById('quickActions');
qa.innerHTML = `
<button class="quick-btn" onclick="quickSend('${t('qm_btn_vessels_port')}')">${t('qb_vessels_port')}</button>
<button class="quick-btn" onclick="quickSend('${t('qm_btn_route')}')">${t('qb_route')}</button>
<button class="quick-btn" onclick="quickSend('${t('qm_btn_cargo')}')">${t('qb_cargo')}</button>
<button class="quick-btn" onclick="quickSend('${t('qm_btn_search')}')">${t('qb_search')}</button>
<button class="quick-btn" onclick="quickSend('${t('qm_btn_demurrage')}')">${t('qb_demurrage')}</button>
<button class="quick-btn" onclick="quickSend('${t('qm_btn_contacts')}')">${t('qb_contacts')}</button>
`;
}
// Sidebar service clicks
document.querySelectorAll('.service-item[data-quick]').forEach(item => {
item.addEventListener('click', () => {
const key = 'qm_' + item.getAttribute('data-quick');
quickSend(t(key));
});
});
// =============================================================================
// Chat functions
// =============================================================================
function getTime() {
return new Date().toLocaleTimeString(currentLang === 'ru' ? 'ru-RU' : currentLang === 'es' ? 'es-ES' : 'en-US',
{ hour: '2-digit', minute: '2-digit' });
}
function showWelcome() {
addMessage(t('welcome'), false);
}
function addMessage(text, isUser) {
const div = document.createElement('div');
div.className = `message ${isUser ? 'user' : 'bot'}`;
div.innerHTML = `
<div class="msg-avatar">${isUser ? '&#128100;' : '&#9875;'}</div>
<div>
<div class="msg-content">${text}</div>
<div class="msg-time">${getTime()}</div>
</div>
`;
messagesDiv.appendChild(div);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
return div;
}
function addTyping() {
const div = document.createElement('div');
div.className = 'message bot';
div.id = 'typing';
div.innerHTML = `
<div class="msg-avatar">&#9875;</div>
<div>
<div class="msg-content">
<div class="typing"><span></span><span></span><span></span></div>
</div>
</div>
`;
messagesDiv.appendChild(div);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
function removeTyping() {
const el = document.getElementById('typing');
if (el) el.remove();
}
function formatResponse(text) {
// Markdown tables → HTML tables
const lines = text.split('\n');
let result = [];
let inTable = false;
let tableRows = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.startsWith('|') && line.endsWith('|')) {
// Skip separator rows (|---|---|)
if (/^\|[\s\-:]+\|$/.test(line.replace(/\|/g, '|').replace(/[\s\-:]/g, ''))) {
continue;
}
if (!inTable) { inTable = true; tableRows = []; }
const cells = line.split('|').filter((c, idx, arr) => idx > 0 && idx < arr.length - 1).map(c => c.trim());
tableRows.push(cells);
} else {
if (inTable) {
result.push(buildTable(tableRows));
inTable = false;
tableRows = [];
}
result.push(line);
}
}
if (inTable) result.push(buildTable(tableRows));
text = result.join('\n');
text = text.replace(/\n/g, '<br>');
text = text.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, '<a href="$2" target="_blank" style="color:var(--accent);text-decoration:underline;">$1</a>');
text = text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
text = text.replace(/```([\s\S]*?)```/g, '<pre>$1</pre>');
text = text.replace(/`(.+?)`/g, '<code style="background:rgba(0,0,0,0.3);padding:2px 6px;border-radius:3px;font-size:12px;">$1</code>');
text = text.replace(/_(.+?)_/g, '<em>$1</em>');
return text;
}
function buildTable(rows) {
if (!rows.length) return '';
let html = '<table style="border-collapse:collapse;width:100%;margin:8px 0;font-size:13px;">';
rows.forEach((cells, idx) => {
const tag = idx === 0 ? 'th' : 'td';
const style = idx === 0
? 'padding:6px 10px;border-bottom:1px solid rgba(0,180,216,0.3);color:var(--accent);text-align:left;font-weight:600;'
: 'padding:5px 10px;border-bottom:1px solid rgba(255,255,255,0.05);';
html += '<tr>' + cells.map(c => `<${tag} style="${style}">${c}</${tag}>`).join('') + '</tr>';
});
html += '</table>';
return html;
}
async function sendMessage() {
const text = input.value.trim();
if (!text) return;
input.value = '';
input.style.height = 'auto';
sendBtn.disabled = true;
userHasSentMessage = true;
addMessage(text, true);
addTyping();
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 120000);
const headers = { 'Content-Type': 'application/json' };
if (authToken) headers['Authorization'] = 'Bearer ' + authToken;
const resp = await fetch(`${API_BASE}/api/v1/chat`, {
method: 'POST',
headers,
body: JSON.stringify({ message: text, lang: currentLang }),
signal: controller.signal
});
clearTimeout(timeoutId);
removeTyping();
if (resp.ok) {
const data = await resp.json();
addMessage(formatResponse(data.response), false);
} else if (resp.status === 401) {
doLogout();
} else {
addMessage(t('error_generic'), false);
}
} catch (e) {
removeTyping();
addMessage(t('error_connection'), false);
}
sendBtn.disabled = false;
input.focus();
}
function quickSend(text) {
input.value = text;
sendMessage();
}
function handleKey(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}
async function clearChat() {
if (!confirm(t('confirm_clear'))) return;
messagesDiv.innerHTML = '';
userHasSentMessage = false;
addMessage(t('chat_cleared'), false);
if (authToken) {
try {
await fetch(API_BASE + '/api/v1/chat/history', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + authToken }
});
} catch (e) { /* ignore */ }
}
}
// Auto-resize textarea
input.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
// =============================================================================
// Voice Input (Web Speech API)
// =============================================================================
const voiceBtn = document.getElementById('voiceBtn');
let recognition = null;
let isRecording = false;
let voiceFinalTranscript = '';
let voiceStartText = '';
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (SpeechRecognition) {
voiceBtn.style.display = 'flex';
recognition = new SpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;
recognition.maxAlternatives = 1;
recognition.onstart = function() {
isRecording = true;
voiceBtn.classList.add('recording');
voiceBtn.title = t('voice_listening');
voiceStartText = input.value;
voiceFinalTranscript = '';
};
recognition.onresult = function(event) {
let interim = '';
voiceFinalTranscript = '';
for (let i = 0; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
voiceFinalTranscript += transcript;
} else {
interim += transcript;
}
}
const prefix = voiceStartText ? voiceStartText.trimEnd() + ' ' : '';
input.value = prefix + voiceFinalTranscript + interim;
input.style.height = 'auto';
input.style.height = Math.min(input.scrollHeight, 120) + 'px';
};
recognition.onend = function() {
isRecording = false;
voiceBtn.classList.remove('recording');
voiceBtn.title = t('voice_tooltip');
if (voiceFinalTranscript) {
const prefix = voiceStartText ? voiceStartText.trimEnd() + ' ' : '';
input.value = fixPunctuation(prefix + voiceFinalTranscript.trim());
input.style.height = 'auto';
input.style.height = Math.min(input.scrollHeight, 120) + 'px';
}
input.focus();
};
recognition.onerror = function(event) {
isRecording = false;
voiceBtn.classList.remove('recording');
voiceBtn.title = t('voice_tooltip');
if (event.error === 'not-allowed' || event.error === 'service-not-allowed') {
alert(t('voice_permission'));
}
};
}
function toggleVoice() {
if (!recognition) return;
if (isRecording) {
recognition.stop();
} else {
const langMap = { en: 'en-US', ru: 'ru-RU', es: 'es-ES' };
recognition.lang = langMap[currentLang] || 'en-US';
voiceFinalTranscript = '';
try { recognition.start(); } catch(e) { /* already started */ }
}
}
function fixPunctuation(text) {
if (!text) return text;
text = text.trim();
// Capitalize first letter
text = text.charAt(0).toUpperCase() + text.slice(1);
// Capitalize after sentence-ending punctuation
text = text.replace(/([.!?])\s+([a-zа-яёáéíóúñ])/g, function(m, p, c) {
return p + ' ' + c.toUpperCase();
});
// Add period at end if no punctuation
if (text.length > 2 && !/[.!?…,;:]$/.test(text)) {
text += '.';
}
// Clean up double spaces
text = text.replace(/\s{2,}/g, ' ');
return text;
}
// =============================================================================
// Landing Page — scroll reveal, counters, sticky nav
// =============================================================================
const revealObserver = new IntersectionObserver((entries) => {
entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('visible'); });
}, { threshold: 0.15 });
document.querySelectorAll('.reveal').forEach(el => revealObserver.observe(el));
// Counter animation
const counterObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
const el = entry.target;
const target = parseInt(el.dataset.target);
if (!target) return;
let current = 0;
const increment = target / 60;
const timer = setInterval(() => {
current += increment;
if (current >= target) { current = target; clearInterval(timer); }
el.textContent = Math.floor(current).toLocaleString() + '+';
}, 25);
counterObserver.unobserve(el);
});
}, { threshold: 0.5 });
document.querySelectorAll('.stat-number[data-target]').forEach(el => counterObserver.observe(el));
// Sticky nav on scroll
window.addEventListener('scroll', () => {
const nav = document.getElementById('topNav');
if (nav) nav.classList.toggle('scrolled', window.scrollY > 50);
});
// Update nav login button when user logs in
function updateNavAuth() {
const btn = document.getElementById('navLoginBtn');
if (!btn) return;
if (currentUser) {
btn.textContent = currentUser.name || currentUser.email;
btn.onclick = () => document.getElementById('chat').scrollIntoView({ behavior: 'smooth' });
} else {
btn.textContent = t('nav_start');
btn.onclick = showAuthModal;
}
}
// =============================================================================
// Init
// =============================================================================
(async function init() {
// Load auth config (Google Client ID)
try {
const resp = await fetch(API_BASE + '/api/v1/auth/config');
if (resp.ok) {
const cfg = await resp.json();
googleClientId = cfg.google_client_id || null;
}
} catch (e) { /* offline */ }
setLang(currentLang);
await checkSession();
sessionChecked = true;
// Show welcome only if no chat history was loaded
if (!userHasSentMessage) {
showWelcome();
}
input.focus();
// Load app version
try {
const hResp = await fetch(API_BASE + '/health');
if (hResp.ok) {
const h = await hResp.json();
if (h.version) {
document.getElementById('appVersion').textContent = 'v' + h.version;
const sv = document.querySelector('.app-version-sidebar');
if (sv) sv.textContent = 'v' + h.version;
}
}
} catch (e) { /* offline */ }
updateNavAuth();
})();
</script>
</body>
</html>