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

3839 lines
170 KiB
HTML
Raw Normal View History

<!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>