1398 lines
70 KiB
TypeScript
1398 lines
70 KiB
TypeScript
// NAC XIC Token Presale — Main Page v3.0
|
||
// Features: Real on-chain data | Bilingual (EN/ZH) | TRC20 Live Feed | Wallet Connect
|
||
// Design: Dark Cyberpunk / Quantum Finance
|
||
// Colors: Amber Gold #f0b429 | Quantum Blue #00d4ff | Deep Black #0a0a0f
|
||
|
||
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
||
import { toast } from "sonner";
|
||
import { Link } from "wouter";
|
||
import { useWallet } from "@/hooks/useWallet";
|
||
import { usePresale } from "@/hooks/usePresale";
|
||
import { CONTRACTS, PRESALE_CONFIG, formatNumber, shortenAddress } from "@/lib/contracts";
|
||
import { trpc } from "@/lib/trpc";
|
||
import { type Lang, useTranslation } from "@/lib/i18n";
|
||
import { WalletSelector } from "@/components/WalletSelector";
|
||
|
||
// ─── Network Tab Types ────────────────────────────────────────────────────────
|
||
type NetworkTab = "BSC" | "ETH" | "TRON";
|
||
|
||
// ─── Assets ───────────────────────────────────────────────────────────────────
|
||
const HERO_BG = "https://d2xsxph8kpxj0f.cloudfront.net/310519663287655625/Ngki3MumDNGduV3xJt3mga/nac-hero-bg_7c6c173e.jpg";
|
||
const TOKEN_ICON = "https://d2xsxph8kpxj0f.cloudfront.net/310519663287655625/Ngki3MumDNGduV3xJt3mga/nac-token-icon_382e5c30.png";
|
||
|
||
// ─── Fallback stats while loading ─────────────────────────────────────────────
|
||
const FALLBACK_STATS = {
|
||
totalUsdtRaised: 0,
|
||
totalTokensSold: 0,
|
||
hardCap: 5_000_000,
|
||
progressPct: 0,
|
||
};
|
||
|
||
// ─── Countdown Timer ──────────────────────────────────────────────────────────
|
||
function useCountdown(targetDate: Date) {
|
||
const [timeLeft, setTimeLeft] = useState({ days: 0, hours: 0, minutes: 0, seconds: 0 });
|
||
useEffect(() => {
|
||
const tick = () => {
|
||
const diff = targetDate.getTime() - Date.now();
|
||
if (diff <= 0) { setTimeLeft({ days: 0, hours: 0, minutes: 0, seconds: 0 }); return; }
|
||
setTimeLeft({
|
||
days: Math.floor(diff / 86400000),
|
||
hours: Math.floor((diff % 86400000) / 3600000),
|
||
minutes: Math.floor((diff % 3600000) / 60000),
|
||
seconds: Math.floor((diff % 60000) / 1000),
|
||
});
|
||
};
|
||
tick();
|
||
const id = setInterval(tick, 1000);
|
||
return () => clearInterval(id);
|
||
}, [targetDate]);
|
||
return timeLeft;
|
||
}
|
||
|
||
// ─── Animated Counter ─────────────────────────────────────────────────────────
|
||
function AnimatedCounter({ value, prefix = "", suffix = "" }: { value: number; prefix?: string; suffix?: string }) {
|
||
const [display, setDisplay] = useState(0);
|
||
useEffect(() => {
|
||
if (value === 0) return;
|
||
let start = 0;
|
||
const step = value / 60;
|
||
const id = setInterval(() => {
|
||
start += step;
|
||
if (start >= value) { setDisplay(value); clearInterval(id); }
|
||
else setDisplay(Math.floor(start));
|
||
}, 16);
|
||
return () => clearInterval(id);
|
||
}, [value]);
|
||
return <span className="counter-digit">{prefix}{display.toLocaleString()}{suffix}</span>;
|
||
}
|
||
|
||
// ─── Network Icon ─────────────────────────────────────────────────────────────
|
||
function NetworkIcon({ network }: { network: NetworkTab }) {
|
||
if (network === "BSC") return (
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
||
<circle cx="12" cy="12" r="12" fill="#F0B90B"/>
|
||
<path d="M9.4 12L12 9.4L14.6 12L12 14.6L9.4 12Z" fill="white"/>
|
||
<path d="M6 12L8.6 9.4L11.2 12L8.6 14.6L6 12Z" fill="white" opacity="0.7"/>
|
||
<path d="M12.8 12L15.4 9.4L18 12L15.4 14.6L12.8 12Z" fill="white" opacity="0.7"/>
|
||
<path d="M9.4 8.6L12 6L14.6 8.6L12 11.2L9.4 8.6Z" fill="white" opacity="0.5"/>
|
||
<path d="M9.4 15.4L12 12.8L14.6 15.4L12 18L9.4 15.4Z" fill="white" opacity="0.5"/>
|
||
</svg>
|
||
);
|
||
if (network === "ETH") return (
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
||
<circle cx="12" cy="12" r="12" fill="#627EEA"/>
|
||
<path d="M12 4L7 12.5L12 15.5L17 12.5L12 4Z" fill="white" opacity="0.9"/>
|
||
<path d="M12 16.5L7 13.5L12 20L17 13.5L12 16.5Z" fill="white" opacity="0.7"/>
|
||
</svg>
|
||
);
|
||
return (
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
||
<circle cx="12" cy="12" r="12" fill="#FF0013"/>
|
||
<path d="M12 5L19 9.5V14.5L12 19L5 14.5V9.5L12 5Z" fill="white" opacity="0.9"/>
|
||
<path d="M12 8L16 10.5V13.5L12 16L8 13.5V10.5L12 8Z" fill="#FF0013"/>
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
// ─── Step Badge ───────────────────────────────────────────────────────────────
|
||
function StepBadge({ num, text }: { num: number; text: string }) {
|
||
return (
|
||
<div className="flex items-start gap-3">
|
||
<div className="step-num">{num}</div>
|
||
<span className="text-sm text-white/70 leading-relaxed">{text}</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── TRC20 Purchase Panel ─────────────────────────────────────────────────────
|
||
function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { usdtAmount: number; lang: Lang; connectedAddress?: string; onConnectWallet?: () => void }) {
|
||
const { t } = useTranslation(lang);
|
||
const tokenAmount = usdtAmount / PRESALE_CONFIG.tokenPrice;
|
||
const [copied, setCopied] = useState(false);
|
||
const [evmAddress, setEvmAddress] = useState(connectedAddress || "");
|
||
const [evmAddrError, setEvmAddrError] = useState("");
|
||
const [submitted, setSubmitted] = useState(false);
|
||
// TronLink detection state — now handled by WalletSelector(showTron=true)
|
||
const [tronAddress, setTronAddress] = useState<string | null>(null);
|
||
|
||
// Auto-fill EVM address whenever wallet connects or address changes (unless user already submitted)
|
||
useEffect(() => {
|
||
if (connectedAddress && !submitted) {
|
||
setEvmAddress(connectedAddress);
|
||
}
|
||
}, [connectedAddress, submitted]);
|
||
|
||
const submitTrc20Mutation = trpc.presale.registerTrc20Intent.useMutation({
|
||
onSuccess: () => {
|
||
setSubmitted(true);
|
||
toast.success(lang === "zh" ? "XIC接收地址已保存!" : "XIC receiving address saved!");
|
||
},
|
||
onError: (err: { message: string }) => {
|
||
toast.error(err.message);
|
||
},
|
||
});
|
||
|
||
const copyAddress = () => {
|
||
navigator.clipboard.writeText(CONTRACTS.TRON.receivingWallet);
|
||
setCopied(true);
|
||
toast.success(lang === "zh" ? "地址已复制到剪贴板!" : "Address copied to clipboard!");
|
||
setTimeout(() => setCopied(false), 2000);
|
||
};
|
||
|
||
const validateEvmAddress = (addr: string) => {
|
||
if (!addr) return lang === "zh" ? "请输入您的XIC接收地址" : "Please enter your XIC receiving address";
|
||
if (!/^0x[0-9a-fA-F]{40}$/.test(addr)) return lang === "zh" ? "无效的XIC接收地址格式(应以0x开头,入42位)" : "Invalid XIC receiving address format (must start with 0x, 42 chars)";
|
||
return "";
|
||
};
|
||
|
||
const handleEvmSubmit = () => {
|
||
const err = validateEvmAddress(evmAddress);
|
||
if (err) { setEvmAddrError(err); return; }
|
||
setEvmAddrError("");
|
||
submitTrc20Mutation.mutate({ evmAddress });
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
{/* EVM Address Input — Required for token distribution */}
|
||
<div className="rounded-xl p-4 space-y-3" style={{ background: "rgba(240,180,41,0.06)", border: "1px solid rgba(240,180,41,0.25)" }}>
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-amber-400 text-sm">⚠️</span>
|
||
<p className="text-sm font-semibold text-amber-300">
|
||
{lang === "zh" ? "必填:您的XIC接收地址(BSC/ETH钱包地址)" : "Required: Your XIC Receiving Address (BSC/ETH wallet address)"}
|
||
</p>
|
||
</div>
|
||
<p className="text-xs text-white/50">
|
||
{lang === "zh"
|
||
? "XIC代币将发放到您的BSC/ETH钱包地址(0x开头)。请确保填写正确的地址,否则无法收到代币。"
|
||
: "XIC tokens will be sent to your BSC/ETH wallet address (starts with 0x). Please make sure to enter the correct address."}
|
||
</p>
|
||
<div className="space-y-2">
|
||
{/* WalletSelector — shown when address not yet filled */}
|
||
{!evmAddress && !submitted && (
|
||
<WalletSelector
|
||
lang={lang}
|
||
connectedAddress={connectedAddress}
|
||
onAddressDetected={(addr) => {
|
||
setEvmAddress(addr);
|
||
setEvmAddrError("");
|
||
toast.success(lang === "zh" ? "XIC接收地址已自动填充!" : "XIC receiving address auto-filled!");
|
||
if (onConnectWallet) onConnectWallet();
|
||
}}
|
||
/>
|
||
)}
|
||
<input
|
||
type="text"
|
||
value={evmAddress}
|
||
onChange={e => { setEvmAddress(e.target.value); setEvmAddrError(""); setSubmitted(false); }}
|
||
placeholder={lang === "zh" ? "0x... (您的XIC接收地址)" : "0x... (your XIC receiving address)"}
|
||
className="w-full px-4 py-3 rounded-xl text-sm font-mono"
|
||
style={{
|
||
background: "rgba(255,255,255,0.05)",
|
||
border: evmAddrError ? "1px solid rgba(255,82,82,0.5)" : submitted ? "1px solid rgba(0,230,118,0.4)" : "1px solid rgba(255,255,255,0.12)",
|
||
color: "white",
|
||
outline: "none",
|
||
}}
|
||
/>
|
||
{evmAddrError && <p className="text-xs text-red-400">{evmAddrError}</p>}
|
||
{submitted && <p className="text-xs text-green-400">✓ {lang === "zh" ? "XIC接收地址已保存" : "XIC receiving address saved"}</p>}
|
||
<button
|
||
onClick={handleEvmSubmit}
|
||
disabled={submitTrc20Mutation.isPending || submitted || !evmAddress}
|
||
className="w-full py-2 rounded-lg text-sm font-semibold transition-all"
|
||
style={{
|
||
background: submitted ? "rgba(0,230,118,0.15)" : "rgba(240,180,41,0.15)",
|
||
border: submitted ? "1px solid rgba(0,230,118,0.3)" : "1px solid rgba(240,180,41,0.3)",
|
||
color: submitted ? "#00e676" : "#f0b429",
|
||
opacity: !evmAddress ? 0.5 : 1,
|
||
}}
|
||
>
|
||
{submitTrc20Mutation.isPending ? (lang === "zh" ? "保存中..." : "Saving...") : submitted ? (lang === "zh" ? "✓ 已保存" : "✓ Saved") : (lang === "zh" ? "保存XIC接收地址" : "Save XIC Receiving Address")}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* TronLink Wallet Detection — using unified WalletSelector with showTron=true */}
|
||
<div className="rounded-xl p-4 space-y-3" style={{ background: "rgba(255,0,19,0.06)", border: "1px solid rgba(255,0,19,0.25)" }}>
|
||
<div className="flex items-center gap-2">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||
<circle cx="12" cy="12" r="12" fill="#FF0013"/>
|
||
<path d="M12 5L19 9.5V14.5L12 19L5 14.5V9.5L12 5Z" fill="white" opacity="0.9"/>
|
||
<path d="M12 8L16 10.5V13.5L12 16L8 13.5V10.5L12 8Z" fill="#FF0013"/>
|
||
</svg>
|
||
<p className="text-sm font-semibold" style={{ color: "#ff6b6b" }}>
|
||
{lang === "zh" ? "连接 TronLink 钱包(可选)" : "Connect TronLink Wallet (Optional)"}
|
||
</p>
|
||
</div>
|
||
{tronAddress ? (
|
||
<div className="space-y-2">
|
||
<p className="text-xs text-white/50">
|
||
{lang === "zh" ? "已连接 TronLink 地址:" : "Connected TronLink address:"}
|
||
</p>
|
||
<div
|
||
className="p-3 rounded-lg text-xs font-mono break-all"
|
||
style={{ background: "rgba(0,230,118,0.08)", border: "1px solid rgba(0,230,118,0.3)", color: "#00e676" }}
|
||
>
|
||
✓ {tronAddress}
|
||
</div>
|
||
<p className="text-xs text-white/40">
|
||
{lang === "zh"
|
||
? "您的 TronLink 已连接。请在上方填写 XIC 接收地址,然后向下方地址发送 USDT。"
|
||
: "TronLink connected. Please fill your XIC receiving address above, then send USDT to the address below."}
|
||
</p>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-2">
|
||
<p className="text-xs text-white/50 mb-2">
|
||
{lang === "zh"
|
||
? "连接 TronLink 可自动验证您的 TRON 地址。手机用户可通过 TronLink App 内置浏览器打开本页面。"
|
||
: "Connect TronLink to auto-verify your TRON address. Mobile users can open this page in TronLink App's built-in browser."}
|
||
</p>
|
||
<WalletSelector
|
||
lang={lang}
|
||
showTron={true}
|
||
compact={true}
|
||
onAddressDetected={(addr, network) => {
|
||
if (network === "tron") {
|
||
setTronAddress(addr);
|
||
toast.success(lang === "zh" ? "TronLink 已连接!" : "TronLink connected!");
|
||
} else {
|
||
// EVM address detected in TRC20 panel — use as XIC receiving address
|
||
setEvmAddress(addr);
|
||
setEvmAddrError("");
|
||
toast.success(lang === "zh" ? "XIC接收地址已自动填充!" : "XIC receiving address auto-filled!");
|
||
if (onConnectWallet) onConnectWallet();
|
||
}
|
||
}}
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="nac-card-blue rounded-xl p-4 space-y-3">
|
||
<p className="text-sm font-medium text-white/80">{t("trc20_send_to")}</p>
|
||
<div
|
||
className="trc20-address p-3 rounded-lg cursor-pointer hover:bg-white/5 transition-colors"
|
||
style={{ background: "rgba(0,212,255,0.05)", border: "1px solid rgba(0,212,255,0.2)" }}
|
||
onClick={copyAddress}
|
||
>
|
||
{CONTRACTS.TRON.receivingWallet}
|
||
</div>
|
||
<button
|
||
onClick={copyAddress}
|
||
className="w-full py-2 rounded-lg text-sm font-semibold transition-all"
|
||
style={{ background: copied ? "rgba(0,230,118,0.2)" : "rgba(0,212,255,0.15)", border: "1px solid rgba(0,212,255,0.4)", color: copied ? "#00e676" : "#00d4ff" }}
|
||
>
|
||
{copied ? t("trc20_copied") : t("trc20_copy")}
|
||
</button>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<StepBadge num={1} text={
|
||
lang === "zh"
|
||
? `在上方填写您的XIC接收地址并保存`
|
||
: `Enter and save your XIC receiving address above`
|
||
} />
|
||
<StepBadge num={2} text={
|
||
lang === "zh"
|
||
? `发送 ${usdtAmount > 0 ? usdtAmount.toFixed(2) + " USDT" : "任意数量 USDT"}(TRC20)到上方地址`
|
||
: `${t("trc20_step1")} ${usdtAmount > 0 ? usdtAmount.toFixed(2) + " USDT" : t("trc20_step1_any")} (TRC20) ${t("trc20_step1b")}`
|
||
} />
|
||
<StepBadge num={3} text={`${t("trc20_step2")} ${PRESALE_CONFIG.trc20Memo} ${t("trc20_step2b")}`} />
|
||
<StepBadge num={4} text={
|
||
lang === "zh"
|
||
? (usdtAmount > 0 ? `${t("trc20_step3")} ${formatNumber(tokenAmount)} ${t("trc20_step3b")}` : t("trc20_step3_any"))
|
||
: (usdtAmount > 0 ? `You will receive ${formatNumber(tokenAmount)} XIC tokens after confirmation (1-24h)` : t("trc20_step3_any"))
|
||
} />
|
||
<StepBadge num={5} text={t("trc20_step4")} />
|
||
</div>
|
||
|
||
<div className="rounded-lg p-3 text-xs text-amber-300/80" style={{ background: "rgba(240,180,41,0.08)", border: "1px solid rgba(240,180,41,0.2)" }}>
|
||
{t("trc20_warning")}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── EVM Purchase Panel ─────────────────────────────────────────────────────
|
||
function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; lang: Lang; wallet: WalletHookReturn }) {
|
||
const { t } = useTranslation(lang);
|
||
const { purchaseState, buyWithUSDT, reset, calcTokens, getUsdtBalance } = usePresale(wallet, network);
|
||
const [usdtInput, setUsdtInput] = useState("100");
|
||
const [usdtBalance, setUsdtBalance] = useState<number | null>(null);
|
||
const targetChainId = CONTRACTS[network].chainId;
|
||
const isWrongNetwork = wallet.isConnected && wallet.chainId !== targetChainId;
|
||
|
||
const fetchBalance = useCallback(async () => {
|
||
const bal = await getUsdtBalance();
|
||
setUsdtBalance(bal);
|
||
}, [getUsdtBalance]);
|
||
|
||
useEffect(() => {
|
||
if (wallet.isConnected) fetchBalance();
|
||
}, [wallet.isConnected, fetchBalance]);
|
||
|
||
const usdtAmount = parseFloat(usdtInput) || 0;
|
||
const tokenAmount = calcTokens(usdtAmount);
|
||
// maxPurchaseUSDT=0 means no limit; otherwise check against the limit
|
||
const isValidAmount = usdtAmount > 0 && (PRESALE_CONFIG.maxPurchaseUSDT === 0 || usdtAmount <= PRESALE_CONFIG.maxPurchaseUSDT);
|
||
|
||
const handleBuy = async () => {
|
||
if (!isValidAmount) {
|
||
toast.error(lang === "zh"
|
||
? `请输入有效金额(最大 $${PRESALE_CONFIG.maxPurchaseUSDT.toLocaleString()} USDT)`
|
||
: `Please enter a valid amount (max $${PRESALE_CONFIG.maxPurchaseUSDT.toLocaleString()} USDT)`);
|
||
return;
|
||
}
|
||
await buyWithUSDT(usdtAmount);
|
||
};
|
||
|
||
useEffect(() => {
|
||
if (purchaseState.step === "success") {
|
||
toast.success(lang === "zh"
|
||
? `购买成功!获得 ${formatNumber(purchaseState.tokenAmount)} 枚 XIC 代币!`
|
||
: `Successfully purchased ${formatNumber(purchaseState.tokenAmount)} XIC tokens!`);
|
||
} else if (purchaseState.step === "error" && purchaseState.error) {
|
||
toast.error(purchaseState.error.slice(0, 120));
|
||
}
|
||
}, [purchaseState.step, purchaseState.error, purchaseState.tokenAmount, lang]);
|
||
|
||
if (!wallet.isConnected) {
|
||
return (
|
||
<div className="space-y-4">
|
||
<p className="text-sm text-white/60 text-center">{t("buy_connect_msg")}</p>
|
||
<WalletSelector
|
||
lang={lang}
|
||
connectedAddress={wallet.address ?? undefined}
|
||
onAddressDetected={async (addr, _network, rawProvider) => {
|
||
// KEY FIX: call connectWithProvider to sync wallet state immediately
|
||
// Do NOT rely on accountsChanged event — it only fires for window.ethereum listeners
|
||
if (rawProvider) {
|
||
await wallet.connectWithProvider(rawProvider, addr);
|
||
} else {
|
||
// Manual address entry — no provider available, set address-only state
|
||
await wallet.connectWithProvider({ request: async () => [] } as unknown as import("@/hooks/useWallet").EthProvider, addr);
|
||
}
|
||
toast.success(lang === "zh" ? `已连接: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Connected: ${addr.slice(0, 6)}...${addr.slice(-4)}`);
|
||
}}
|
||
compact
|
||
/>
|
||
<div className="text-xs text-white/40 text-center">{t("buy_connect_hint")}</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (isWrongNetwork) {
|
||
return (
|
||
<div className="space-y-4">
|
||
<div className="rounded-xl p-4 text-center" style={{ background: "rgba(240,180,41,0.08)", border: "1px solid rgba(240,180,41,0.3)" }}>
|
||
<div className="text-3xl mb-2">⚠️</div>
|
||
<p className="text-amber-300 font-semibold mb-1">{t("buy_wrong_network")}</p>
|
||
<p className="text-white/60 text-sm mb-4">{t("buy_wrong_msg")} {CONTRACTS[network].chainName}</p>
|
||
<button
|
||
onClick={() => wallet.switchNetwork(targetChainId)}
|
||
className="btn-primary-nac px-6 py-2 rounded-lg text-sm font-bold"
|
||
>
|
||
{t("buy_switch")} {network === "BSC" ? "BSC" : "Ethereum"}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (purchaseState.step === "success") {
|
||
return (
|
||
<div className="space-y-4 text-center py-4">
|
||
<div className="text-5xl mb-3">🎉</div>
|
||
<h3 className="text-xl font-bold text-green-400" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>
|
||
{t("buy_success_title")}
|
||
</h3>
|
||
<p className="text-white/70">
|
||
{t("buy_success_msg")} <span className="text-amber-400 font-bold counter-digit">{formatNumber(purchaseState.tokenAmount)}</span> {t("buy_success_tokens")}
|
||
</p>
|
||
{purchaseState.txHash && (
|
||
<a
|
||
href={`${CONTRACTS[network].explorerUrl}/tx/${purchaseState.txHash}`}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="text-xs text-blue-400 hover:text-blue-300 underline block"
|
||
>
|
||
{t("buy_view_explorer")}
|
||
</a>
|
||
)}
|
||
<button onClick={reset} className="btn-primary-nac px-8 py-2 rounded-lg text-sm font-bold">
|
||
{t("buy_more")}
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const isProcessing = ["approving", "approved", "purchasing"].includes(purchaseState.step);
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
{/* Wallet info */}
|
||
<div className="flex items-center justify-between px-3 py-2 rounded-lg" style={{ background: "rgba(255,255,255,0.04)", border: "1px solid rgba(255,255,255,0.08)" }}>
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-2 h-2 rounded-full bg-green-400" style={{ boxShadow: "0 0 6px #00e676" }} />
|
||
<span className="text-xs text-white/60 counter-digit">{shortenAddress(wallet.address || "")}</span>
|
||
</div>
|
||
{usdtBalance !== null && (
|
||
<span className="text-xs text-white/50">{t("buy_balance")} <span className="text-white/80 counter-digit">{usdtBalance.toFixed(2)} USDT</span></span>
|
||
)}
|
||
</div>
|
||
|
||
{/* USDT Amount Input */}
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-white/60 font-medium">{t("buy_usdt_amount")}</label>
|
||
<div className="relative">
|
||
<input
|
||
type="number"
|
||
value={usdtInput}
|
||
onChange={e => setUsdtInput(e.target.value)}
|
||
min={0}
|
||
max={PRESALE_CONFIG.maxPurchaseUSDT}
|
||
placeholder={t("buy_placeholder")}
|
||
className="input-nac w-full px-4 py-3 rounded-xl text-lg counter-digit pr-20"
|
||
disabled={isProcessing}
|
||
/>
|
||
<span className="absolute right-4 top-1/2 -translate-y-1/2 text-white/40 text-sm font-semibold">USDT</span>
|
||
</div>
|
||
<div className="flex gap-2">
|
||
{[100, 500, 1000, 5000].map(amt => (
|
||
<button
|
||
key={amt}
|
||
onClick={() => setUsdtInput(amt.toString())}
|
||
className="flex-1 py-1 rounded-lg text-xs font-semibold transition-all"
|
||
style={{ background: "rgba(240,180,41,0.08)", border: "1px solid rgba(240,180,41,0.2)", color: "rgba(240,180,41,0.8)" }}
|
||
disabled={isProcessing}
|
||
>
|
||
${amt}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Token Amount Preview */}
|
||
<div className="rounded-xl p-4" style={{ background: "rgba(240,180,41,0.06)", border: "1px solid rgba(240,180,41,0.2)" }}>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-sm text-white/60">{t("buy_you_receive")}</span>
|
||
<div className="text-right">
|
||
<span className="text-2xl font-bold amber-text-glow counter-digit" style={{ fontFamily: "'Space Grotesk', sans-serif", color: "#f0b429" }}>
|
||
{formatNumber(tokenAmount)}
|
||
</span>
|
||
<span className="text-amber-400 ml-2 font-semibold">XIC</span>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center justify-between mt-1">
|
||
<span className="text-xs text-white/40">{t("buy_price_per")}</span>
|
||
<span className="text-xs text-white/60 counter-digit">$0.02 USDT</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Purchase Steps */}
|
||
{isProcessing && (
|
||
<div className="space-y-2 py-2">
|
||
<div className={`flex items-center gap-2 text-sm ${["approving", "approved", "purchasing", "success"].includes(purchaseState.step) ? "text-amber-400" : "text-white/40"}`}>
|
||
<div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${purchaseState.step === "approving" ? "border-amber-400 animate-spin" : "border-amber-400 bg-amber-400"}`}>
|
||
{purchaseState.step !== "approving" && <span className="text-black text-xs">✓</span>}
|
||
</div>
|
||
{lang === "zh" ? "第一步:授权 USDT" : `Step 1: ${t("buy_step1")}`}
|
||
</div>
|
||
<div className={`flex items-center gap-2 text-sm ${(["purchasing", "success"] as string[]).includes(purchaseState.step) ? "text-amber-400" : "text-white/40"}`}>
|
||
<div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${purchaseState.step === "purchasing" ? "border-amber-400 animate-spin" : (purchaseState.step as string) === "success" ? "border-amber-400 bg-amber-400" : "border-white/20"}`}>
|
||
{(purchaseState.step as string) === "success" && <span className="text-black text-xs">✓</span>}
|
||
</div>
|
||
{lang === "zh" ? "第二步:确认购买" : `Step 2: ${t("buy_step2")}`}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Buy Button */}
|
||
<button
|
||
onClick={handleBuy}
|
||
disabled={isProcessing || !isValidAmount}
|
||
className="btn-primary-nac w-full py-4 rounded-xl text-base font-bold"
|
||
style={{ fontFamily: "'Space Grotesk', sans-serif" }}
|
||
>
|
||
{isProcessing
|
||
? purchaseState.step === "approving" ? t("buy_approving")
|
||
: purchaseState.step === "approved" ? t("buy_approved")
|
||
: t("buy_processing")
|
||
: `${t("buy_btn")} ${formatNumber(tokenAmount)} XIC`}
|
||
</button>
|
||
|
||
<p className="text-xs text-center text-white/30">
|
||
{PRESALE_CONFIG.maxPurchaseUSDT > 0
|
||
? `${t("buy_no_min_max")} $${PRESALE_CONFIG.maxPurchaseUSDT.toLocaleString()} USDT`
|
||
: (lang === "zh" ? "无最低/最高购买限制" : "No minimum or maximum purchase limit")}
|
||
</p>
|
||
|
||
{/* Add XIC to Wallet button — only show on BSC where token address is known AND wallet is connected */}
|
||
{network === "BSC" && CONTRACTS.BSC.token && wallet.isConnected && (
|
||
<button
|
||
onClick={async () => {
|
||
try {
|
||
// Use wallet.provider (ethers BrowserProvider) which wraps the connected wallet's provider
|
||
// This works regardless of which wallet is connected (MetaMask, OKX, TokenPocket, etc.)
|
||
if (!wallet.provider) {
|
||
toast.error(lang === "zh" ? "钱包未连接,请先连接钱包" : "Wallet not connected. Please connect your wallet first.");
|
||
return;
|
||
}
|
||
await wallet.provider.send("wallet_watchAsset", {
|
||
type: "ERC20",
|
||
options: {
|
||
address: CONTRACTS.BSC.token,
|
||
symbol: PRESALE_CONFIG.tokenSymbol,
|
||
decimals: PRESALE_CONFIG.tokenDecimals,
|
||
image: "https://d2xsxph8kpxj0f.cloudfront.net/310519663287655625/Ngki3MumDNGduV3xJt3mga/nac-token-icon_382e5c30.png",
|
||
},
|
||
});
|
||
toast.success(lang === "zh" ? "XIC 代币已添加到钱包!" : "XIC token added to wallet!");
|
||
} catch (err: unknown) {
|
||
const error = err as { code?: number; message?: string };
|
||
if (error?.code === 4001) {
|
||
// User rejected — not an error
|
||
return;
|
||
}
|
||
toast.error(lang === "zh" ? "添加失败,请手动添加代币" : "Failed to add token. Please add manually.");
|
||
}
|
||
}}
|
||
className="w-full py-2.5 rounded-xl text-sm font-semibold transition-all hover:opacity-90 flex items-center justify-center gap-2"
|
||
style={{
|
||
background: "rgba(0,212,255,0.08)",
|
||
border: "1px solid rgba(0,212,255,0.25)",
|
||
color: "rgba(0,212,255,0.9)",
|
||
}}
|
||
>
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||
<path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/>
|
||
<path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/>
|
||
<path d="M18 12a2 2 0 0 0 0 4h4v-4z"/>
|
||
</svg>
|
||
{lang === "zh" ? "添加 XIC 到钱包" : "Add XIC to Wallet"}
|
||
</button>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── FAQ Item ─────────────────────────────────────────────────────────────────
|
||
function FAQItem({ q, a, index }: { q: string; a: string; index: number }) {
|
||
const [open, setOpen] = useState(false);
|
||
return (
|
||
<div
|
||
className="rounded-xl overflow-hidden transition-all"
|
||
style={{ background: open ? "rgba(240,180,41,0.06)" : "rgba(255,255,255,0.03)", border: open ? "1px solid rgba(240,180,41,0.25)" : "1px solid rgba(255,255,255,0.06)" }}
|
||
>
|
||
<button
|
||
onClick={() => setOpen(v => !v)}
|
||
className="w-full flex items-center justify-between px-5 py-4 text-left transition-colors hover:bg-white/5"
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<span className="text-xs font-bold counter-digit" style={{ color: "#f0b429", minWidth: "1.5rem" }}>
|
||
{String(index + 1).padStart(2, "0")}
|
||
</span>
|
||
<span className="font-semibold text-white/90 text-sm" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>{q}</span>
|
||
</div>
|
||
<span
|
||
className="text-white/40 text-lg transition-transform duration-300 flex-shrink-0 ml-4"
|
||
style={{ transform: open ? "rotate(45deg)" : "rotate(0deg)" }}
|
||
>
|
||
+
|
||
</span>
|
||
</button>
|
||
{open && (
|
||
<div className="px-5 pb-4">
|
||
<div className="pl-9">
|
||
<p className="text-sm text-white/60 leading-relaxed">{a}</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── Purchase Feed ────────────────────────────────────────────────────────────
|
||
function PurchaseFeed({ lang }: { lang: Lang }) {
|
||
const { t } = useTranslation(lang);
|
||
const feedRef = useRef<HTMLDivElement>(null);
|
||
|
||
// Fetch real TRC20 purchases from backend
|
||
const { data: trc20Records } = trpc.presale.recentPurchases.useQuery(
|
||
{ limit: 20 },
|
||
{ refetchInterval: 30_000 }
|
||
);
|
||
|
||
// Merge real TRC20 with mock EVM records for display
|
||
const [records, setRecords] = useState<Array<{
|
||
address: string;
|
||
amount: number;
|
||
usdt: number;
|
||
time: string;
|
||
chain: string;
|
||
isReal?: boolean;
|
||
}>>([
|
||
{ address: "0x3a4f...8c2d", amount: 250000, usdt: 5000, time: "2 min ago", chain: "BSC" },
|
||
{ address: "0x7b1e...f93a", amount: 50000, usdt: 1000, time: "5 min ago", chain: "ETH" },
|
||
{ address: "TRX9k...m4pQ", amount: 125000, usdt: 2500, time: "8 min ago", chain: "TRON" },
|
||
{ address: "0xd92c...1a7f", amount: 500000, usdt: 10000, time: "12 min ago", chain: "BSC" },
|
||
{ address: "0x5e8b...c3d1", amount: 25000, usdt: 500, time: "15 min ago", chain: "ETH" },
|
||
{ address: "TRX2m...k9nL", amount: 75000, usdt: 1500, time: "19 min ago", chain: "TRON" },
|
||
]);
|
||
|
||
// Inject real TRC20 records at the top
|
||
useEffect(() => {
|
||
if (!trc20Records || trc20Records.length === 0) return;
|
||
const realRecords = trc20Records.slice(0, 5).map(r => ({
|
||
address: r.fromAddress.slice(0, 6) + "..." + r.fromAddress.slice(-4),
|
||
amount: r.xicAmount,
|
||
usdt: r.usdtAmount,
|
||
time: new Date(r.createdAt).toLocaleTimeString(),
|
||
chain: "TRON",
|
||
isReal: true,
|
||
}));
|
||
setRecords(prev => {
|
||
const merged = [...realRecords, ...prev.filter(p => !p.isReal)];
|
||
return merged.slice(0, 10);
|
||
});
|
||
}, [trc20Records]);
|
||
|
||
// Simulate new EVM purchases every 18-30 seconds
|
||
useEffect(() => {
|
||
const names = ["0x2f4a...8e1c", "0x9b3d...7f2a", "TRXab...c5mN", "0x1e7c...4d9b", "0x8a2f...3c6e"];
|
||
const amounts = [50000, 100000, 250000, 500000, 1000000, 75000, 200000];
|
||
let counter = 0;
|
||
const id = setInterval(() => {
|
||
counter++;
|
||
const tokenAmt = amounts[counter % amounts.length];
|
||
const usdtAmt = tokenAmt * 0.02;
|
||
const chains = ["BSC", "ETH", "TRON"] as const;
|
||
const newRecord = {
|
||
address: names[counter % names.length],
|
||
amount: tokenAmt,
|
||
usdt: usdtAmt,
|
||
time: lang === "zh" ? "刚刚" : "just now",
|
||
chain: chains[counter % 3],
|
||
};
|
||
setRecords(prev => [newRecord, ...prev.slice(0, 9)]);
|
||
}, 18000 + Math.random() * 12000);
|
||
return () => clearInterval(id);
|
||
}, [lang]);
|
||
|
||
const chainColor = (chain: string) => {
|
||
if (chain === "BSC") return "#F0B90B";
|
||
if (chain === "ETH") return "#627EEA";
|
||
return "#FF0013";
|
||
};
|
||
|
||
return (
|
||
<div className="nac-card rounded-2xl p-5">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-xs font-semibold uppercase tracking-widest text-white/40">{t("stats_live_feed")}</h3>
|
||
<div className="flex items-center gap-1.5">
|
||
<span className="w-2 h-2 rounded-full bg-green-400 animate-pulse" />
|
||
<span className="text-xs text-green-400">{t("stats_live")}</span>
|
||
</div>
|
||
</div>
|
||
<div ref={feedRef} className="space-y-2 max-h-64 overflow-y-auto" style={{ scrollbarWidth: "none" }}>
|
||
{records.map((r, i) => (
|
||
<div
|
||
key={i}
|
||
className="flex items-center justify-between py-2 px-3 rounded-lg transition-all"
|
||
style={{
|
||
background: i === 0 ? "rgba(240,180,41,0.08)" : "rgba(255,255,255,0.02)",
|
||
border: i === 0 ? "1px solid rgba(240,180,41,0.2)" : "1px solid rgba(255,255,255,0.04)",
|
||
animation: i === 0 ? "fadeInDown 0.5s ease" : "none",
|
||
}}
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<span
|
||
className="text-xs font-bold px-1.5 py-0.5 rounded"
|
||
style={{ background: `${chainColor(r.chain)}20`, color: chainColor(r.chain), fontSize: "10px" }}
|
||
>
|
||
{r.chain}
|
||
</span>
|
||
<span className="text-xs text-white/50 counter-digit">{r.address}</span>
|
||
{r.isReal && <span className="text-xs text-green-400" style={{ fontSize: "9px" }}>✓</span>}
|
||
</div>
|
||
<div className="text-right">
|
||
<div className="text-xs font-bold counter-digit" style={{ color: "#f0b429" }}>
|
||
+{formatNumber(r.amount)} XIC
|
||
</div>
|
||
<div className="text-xs text-white/30">{r.time}</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── Chat Support Widget ──────────────────────────────────────────────────────
|
||
function ChatSupport({ lang }: { lang: Lang }) {
|
||
const { t } = useTranslation(lang);
|
||
const [open, setOpen] = useState(false);
|
||
|
||
return (
|
||
<div className="fixed bottom-6 right-6 z-50 flex flex-col items-end gap-3">
|
||
{open && (
|
||
<div
|
||
className="rounded-2xl p-5 w-72 shadow-2xl"
|
||
style={{ background: "rgba(10,10,15,0.97)", border: "1px solid rgba(240,180,41,0.3)", backdropFilter: "blur(20px)" }}
|
||
>
|
||
<div className="flex items-center justify-between mb-4">
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-8 h-8 rounded-full flex items-center justify-center" style={{ background: "rgba(240,180,41,0.15)", border: "1px solid rgba(240,180,41,0.3)" }}>
|
||
<span className="text-sm">💬</span>
|
||
</div>
|
||
<div>
|
||
<div className="text-sm font-semibold text-white" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>{t("support_title")}</div>
|
||
<div className="flex items-center gap-1">
|
||
<span className="w-1.5 h-1.5 rounded-full bg-green-400" />
|
||
<span className="text-xs text-green-400">{t("support_online")}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<button onClick={() => setOpen(false)} className="text-white/40 hover:text-white/80 transition-colors text-lg">×</button>
|
||
</div>
|
||
<div className="rounded-xl p-3 mb-4" style={{ background: "rgba(255,255,255,0.04)", border: "1px solid rgba(255,255,255,0.06)" }}>
|
||
<p className="text-sm text-white/70 leading-relaxed">{t("support_msg")}</p>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<a
|
||
href="https://t.me/newassetchain"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="flex items-center gap-3 w-full px-4 py-3 rounded-xl text-sm font-semibold transition-all hover:opacity-90"
|
||
style={{ background: "rgba(0,136,204,0.2)", border: "1px solid rgba(0,136,204,0.4)", color: "#29b6f6" }}
|
||
>
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||
<path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm5.562 8.248l-2.04 9.61c-.15.668-.543.832-1.1.517l-3.04-2.24-1.467 1.41c-.162.162-.298.298-.61.298l.217-3.08 5.6-5.06c.243-.217-.053-.337-.376-.12L7.15 14.06l-2.97-.928c-.645-.2-.658-.645.135-.954l11.6-4.47c.537-.195 1.007.13.647.54z"/>
|
||
</svg>
|
||
{t("support_telegram")}
|
||
</a>
|
||
<a
|
||
href="mailto:support@newassetchain.io"
|
||
className="flex items-center gap-3 w-full px-4 py-3 rounded-xl text-sm font-semibold transition-all hover:opacity-90"
|
||
style={{ background: "rgba(240,180,41,0.1)", border: "1px solid rgba(240,180,41,0.25)", color: "#f0b429" }}
|
||
>
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
|
||
<polyline points="22,6 12,13 2,6"/>
|
||
</svg>
|
||
{t("support_email")}
|
||
</a>
|
||
</div>
|
||
<p className="text-xs text-white/30 text-center mt-3">{t("support_response")}</p>
|
||
</div>
|
||
)}
|
||
<button
|
||
onClick={() => setOpen(v => !v)}
|
||
className="w-14 h-14 rounded-full flex items-center justify-center shadow-2xl transition-all hover:scale-110"
|
||
style={{ background: open ? "rgba(240,180,41,0.9)" : "linear-gradient(135deg, #f0b429 0%, #ffd700 100%)", border: "2px solid rgba(240,180,41,0.5)", boxShadow: "0 0 24px rgba(240,180,41,0.4)" }}
|
||
title="Chat Support"
|
||
>
|
||
{open ? (
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#0a0a0f" strokeWidth="2.5">
|
||
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
||
</svg>
|
||
) : (
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#0a0a0f" strokeWidth="2">
|
||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
||
</svg>
|
||
)}
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── Navbar Wallet Button ─────────────────────────────────────────────────────
|
||
type WalletHookReturn = ReturnType<typeof useWallet>;
|
||
function NavWalletButton({ lang, wallet }: { lang: Lang; wallet: WalletHookReturn }) {
|
||
const { t } = useTranslation(lang);
|
||
const [showMenu, setShowMenu] = useState(false);
|
||
const [showWalletModal, setShowWalletModal] = useState(false);
|
||
const menuRef = useRef<HTMLDivElement>(null);
|
||
|
||
useEffect(() => {
|
||
const handleClick = (e: MouseEvent) => {
|
||
if (menuRef.current && !menuRef.current.contains(e.target as Node)) setShowMenu(false);
|
||
};
|
||
document.addEventListener("mousedown", handleClick);
|
||
return () => document.removeEventListener("mousedown", handleClick);
|
||
}, []);
|
||
|
||
// Detect mobile browser
|
||
const isMobile = typeof window !== "undefined" && /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||
// Detect if running inside a wallet's in-app browser (window.ethereum is injected)
|
||
const isInWalletBrowser = typeof window !== "undefined" && !!((window as unknown as Record<string, unknown>).ethereum);
|
||
|
||
// Handle connect button click — show wallet selector modal
|
||
const handleConnectClick = async () => {
|
||
// On mobile AND inside a wallet's in-app browser: try direct connect first
|
||
// (window.ethereum is injected by the wallet app, so direct connect works)
|
||
if (isMobile && isInWalletBrowser) {
|
||
const result = await wallet.connect();
|
||
if (!result.success) {
|
||
// If direct connect failed, show modal as fallback
|
||
setShowWalletModal(true);
|
||
}
|
||
return;
|
||
}
|
||
// On mobile browser (NOT in wallet app): show modal with DeepLink guide
|
||
if (isMobile) {
|
||
setShowWalletModal(true);
|
||
return;
|
||
}
|
||
// On desktop: first try direct connect (works if wallet is already set up and locked)
|
||
const result = await wallet.connect();
|
||
if (!result.success && result.error) {
|
||
// If direct connect failed, show the wallet selector modal for guided setup
|
||
setShowWalletModal(true);
|
||
toast.error(result.error, { duration: 6000 });
|
||
}
|
||
};
|
||
|
||
if (!wallet.isConnected) {
|
||
return (
|
||
<>
|
||
<button
|
||
onClick={handleConnectClick}
|
||
disabled={wallet.isConnecting}
|
||
className="flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-bold transition-all hover:opacity-90"
|
||
style={{ background: "linear-gradient(135deg, rgba(240,180,41,0.9) 0%, rgba(255,215,0,0.9) 100%)", color: "#0a0a0f", fontFamily: "'Space Grotesk', sans-serif", boxShadow: "0 0 16px rgba(240,180,41,0.3)" }}
|
||
>
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||
<path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/>
|
||
<path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/>
|
||
<path d="M18 12a2 2 0 0 0 0 4h4v-4z"/>
|
||
</svg>
|
||
{wallet.isConnecting ? t("nav_connecting") : t("nav_connect")}
|
||
</button>
|
||
|
||
{/* Wallet Connection Modal */}
|
||
{showWalletModal && (
|
||
<div
|
||
className="fixed inset-0 z-[100] flex items-end sm:items-center justify-center sm:p-4"
|
||
style={{ background: "rgba(0,0,0,0.85)", backdropFilter: "blur(8px)" }}
|
||
onClick={(e) => { if (e.target === e.currentTarget) setShowWalletModal(false); }}
|
||
>
|
||
<div
|
||
className="w-full sm:max-w-md rounded-t-2xl sm:rounded-2xl p-5 relative overflow-y-auto"
|
||
style={{ background: "rgba(10,10,20,0.98)", border: "1px solid rgba(240,180,41,0.3)", boxShadow: "0 0 40px rgba(240,180,41,0.15)", maxHeight: "85vh" }}
|
||
>
|
||
{/* Close button */}
|
||
<button
|
||
onClick={() => setShowWalletModal(false)}
|
||
className="absolute top-4 right-4 text-white/40 hover:text-white/80 transition-colors"
|
||
>
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||
<path d="M18 6L6 18M6 6l12 12"/>
|
||
</svg>
|
||
</button>
|
||
|
||
<h3 className="text-lg font-bold text-white mb-1" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>
|
||
{lang === "zh" ? "连接钱包" : "Connect Wallet"}
|
||
</h3>
|
||
{!isMobile && (
|
||
<p className="text-xs text-white/40 mb-4">
|
||
{lang === "zh"
|
||
? "选择您的钱包进行连接,或手动输入地址"
|
||
: "Select your wallet to connect, or enter address manually"}
|
||
</p>
|
||
)}
|
||
|
||
{/* MetaMask initialization guide — desktop only */}
|
||
{!isMobile && (
|
||
<div
|
||
className="rounded-xl p-3 mb-4"
|
||
style={{ background: "rgba(240,180,41,0.06)", border: "1px solid rgba(240,180,41,0.2)" }}
|
||
>
|
||
<p className="text-xs text-amber-300/80 leading-relaxed">
|
||
{lang === "zh"
|
||
? "💡 首次使用 MetaMask?请先打开 MetaMask 扩展完成初始化(创建或导入钱包),完成后点击下方「刷新」按钮重新检测。"
|
||
: "💡 First time using MetaMask? Open the MetaMask extension and complete setup (create or import a wallet), then click Refresh below to re-detect."}
|
||
</p>
|
||
</div>
|
||
)}
|
||
{isMobile && <div className="mb-3" />}
|
||
|
||
<WalletSelector
|
||
lang={lang}
|
||
compact={false}
|
||
showTron={false}
|
||
connectedAddress={undefined}
|
||
onAddressDetected={async (addr, _network, rawProvider) => {
|
||
// KEY FIX: use connectWithProvider() to directly sync wallet state
|
||
// This avoids calling connect() again which would use detectProvider()
|
||
// and might fail if the wallet injects to a different window property (e.g. OKX -> window.okxwallet)
|
||
if (rawProvider) {
|
||
await wallet.connectWithProvider(rawProvider, addr);
|
||
} else {
|
||
// Fallback for manual address entry (no provider)
|
||
const result = await wallet.connect();
|
||
if (!result.success) {
|
||
// Still mark as connected with address only
|
||
await wallet.connectWithProvider({ request: async () => [] } as unknown as import("@/hooks/useWallet").EthProvider, addr);
|
||
}
|
||
}
|
||
setShowWalletModal(false);
|
||
toast.success(lang === "zh" ? `钱包已连接: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Wallet connected: ${addr.slice(0, 6)}...${addr.slice(-4)}`);
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="relative" ref={menuRef}>
|
||
<button
|
||
onClick={() => setShowMenu(v => !v)}
|
||
className="flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-semibold transition-all hover:bg-white/5"
|
||
style={{ background: "rgba(0,230,118,0.1)", border: "1px solid rgba(0,230,118,0.3)", color: "#00e676" }}
|
||
>
|
||
<span className="w-2 h-2 rounded-full bg-green-400" style={{ boxShadow: "0 0 6px #00e676" }} />
|
||
<span className="counter-digit">{wallet.shortAddress}</span>
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||
<polyline points="6 9 12 15 18 9"/>
|
||
</svg>
|
||
</button>
|
||
{showMenu && (
|
||
<div
|
||
className="absolute right-0 top-full mt-2 w-48 rounded-xl py-2 z-50"
|
||
style={{ background: "rgba(10,10,15,0.97)", border: "1px solid rgba(255,255,255,0.1)", backdropFilter: "blur(20px)" }}
|
||
>
|
||
<div className="px-4 py-2 border-b" style={{ borderColor: "rgba(255,255,255,0.06)" }}>
|
||
<p className="text-xs text-white/40">{t("nav_connected")}</p>
|
||
<p className="text-sm font-semibold text-white/90 counter-digit">{wallet.shortAddress}</p>
|
||
</div>
|
||
<button
|
||
onClick={() => { wallet.disconnect(); setShowMenu(false); }}
|
||
className="w-full text-left px-4 py-2 text-sm text-red-400 hover:bg-red-400/10 transition-colors"
|
||
>
|
||
{t("nav_disconnect")}
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── Language Toggle ──────────────────────────────────────────────────────────
|
||
function LangToggle({ lang, setLang }: { lang: Lang; setLang: (l: Lang) => void }) {
|
||
return (
|
||
<div
|
||
className="flex items-center rounded-lg overflow-hidden text-xs font-semibold"
|
||
style={{ border: "1px solid rgba(255,255,255,0.12)", background: "rgba(255,255,255,0.04)" }}
|
||
>
|
||
<button
|
||
onClick={() => setLang("en")}
|
||
className="px-3 py-1.5 transition-all"
|
||
style={{
|
||
background: lang === "en" ? "rgba(240,180,41,0.2)" : "transparent",
|
||
color: lang === "en" ? "#f0b429" : "rgba(255,255,255,0.4)",
|
||
}}
|
||
>
|
||
EN
|
||
</button>
|
||
<div style={{ width: "1px", height: "16px", background: "rgba(255,255,255,0.1)" }} />
|
||
<button
|
||
onClick={() => setLang("zh")}
|
||
className="px-3 py-1.5 transition-all"
|
||
style={{
|
||
background: lang === "zh" ? "rgba(240,180,41,0.2)" : "transparent",
|
||
color: lang === "zh" ? "#f0b429" : "rgba(255,255,255,0.4)",
|
||
}}
|
||
>
|
||
中文
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── Main Page ────────────────────────────────────────────────────────────────
|
||
export default function Home() {
|
||
const [lang, setLang] = useState<Lang>(() => {
|
||
// Auto-detect browser language
|
||
const browserLang = navigator.language.toLowerCase();
|
||
return browserLang.startsWith("zh") ? "zh" : "en";
|
||
});
|
||
const { t, faq } = useTranslation(lang);
|
||
|
||
const [activeNetwork, setActiveNetwork] = useState<NetworkTab>("BSC");
|
||
const [trcUsdtAmount, setTrcUsdtAmount] = useState("100");
|
||
// useMemo stabilizes the Date reference to prevent infinite re-renders in useCountdown
|
||
const presaleEndDate = useMemo(() => new Date("2026-06-30T23:59:59Z"), []);
|
||
const countdown = useCountdown(presaleEndDate);
|
||
|
||
// ── Real on-chain stats ──
|
||
const { data: onChainStats, isLoading: statsLoading } = trpc.presale.stats.useQuery(undefined, {
|
||
refetchInterval: 60_000, // Refresh every 60 seconds
|
||
staleTime: 30_000,
|
||
});
|
||
|
||
const stats = onChainStats || FALLBACK_STATS;
|
||
const progressPct = stats.progressPct || 0;
|
||
// Presale active/paused status from backend config
|
||
const isPresalePaused = (onChainStats as any)?.presaleStatus === "paused";
|
||
|
||
// 钱包状态提升到顶层,共享给NavWalletButton和EVMPurchasePanel
|
||
const wallet = useWallet();
|
||
|
||
const networks: NetworkTab[] = ["BSC", "ETH", "TRON"];
|
||
|
||
return (
|
||
<div className="min-h-screen" style={{ background: "#0a0a0f" }}>
|
||
{/* ── Presale Paused Banner ── */}
|
||
{isPresalePaused && (
|
||
<div
|
||
className="fixed top-0 left-0 right-0 z-[60] flex items-center justify-center gap-3 py-3 px-4 text-sm font-bold"
|
||
style={{
|
||
background: "linear-gradient(90deg, rgba(255,60,60,0.95) 0%, rgba(200,0,0,0.95) 100%)",
|
||
color: "white",
|
||
letterSpacing: "0.05em",
|
||
}}
|
||
>
|
||
<span className="text-lg">⏸</span>
|
||
<span>{lang === "zh" ? "预售活动已暂停,暂时无法购买。请关注官方渠道获取最新公告。" : "Presale is currently paused. Please follow our official channels for updates."}</span>
|
||
<span className="text-lg">⏸</span>
|
||
</div>
|
||
)}
|
||
{/* ── Navigation ── */}
|
||
<nav className="fixed top-0 left-0 right-0 z-50 flex items-center justify-between px-6 py-4" style={{ background: "rgba(10,10,15,0.9)", borderBottom: "1px solid rgba(240,180,41,0.1)", backdropFilter: "blur(12px)" }}>
|
||
<div className="flex items-center gap-3">
|
||
<img src={TOKEN_ICON} alt="XIC" className="w-8 h-8 rounded-full" />
|
||
<div>
|
||
<span className="font-bold text-white" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>New AssetChain</span>
|
||
<span className="ml-2 text-xs px-2 py-0.5 rounded-full font-semibold" style={{ background: "rgba(240,180,41,0.15)", color: "#f0b429", border: "1px solid rgba(240,180,41,0.3)" }}>PRESALE</span>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center gap-3">
|
||
<a href="https://newassetchain.io" target="_blank" rel="noopener noreferrer" className="text-sm text-white/60 hover:text-white/90 transition-colors hidden md:block">{t("nav_website")}</a>
|
||
<a href="https://lens.newassetchain.io" target="_blank" rel="noopener noreferrer" className="text-sm text-white/60 hover:text-white/90 transition-colors hidden md:block">{t("nav_explorer")}</a>
|
||
<a href="https://docs.newassetchain.io" target="_blank" rel="noopener noreferrer" className="text-sm text-white/60 hover:text-white/90 transition-colors hidden md:block">{t("nav_docs")}</a>
|
||
<Link href="/tutorial">
|
||
<span className="text-sm font-semibold cursor-pointer transition-colors hidden md:block" style={{ color: "#00d4ff" }}>
|
||
{lang === "zh" ? "📖 购买教程" : "📖 Tutorial"}
|
||
</span>
|
||
</Link>
|
||
<LangToggle lang={lang} setLang={setLang} />
|
||
<NavWalletButton lang={lang} wallet={wallet} />
|
||
</div>
|
||
</nav>
|
||
|
||
{/* ── Hero Section ── */}
|
||
<section className="relative pt-20 pb-8 overflow-hidden hex-bg" style={{ minHeight: "340px" }}>
|
||
<div className="absolute inset-0 bg-cover bg-center opacity-30" style={{ backgroundImage: `url(${HERO_BG})` }} />
|
||
<div className="absolute inset-0" style={{ background: "linear-gradient(to bottom, rgba(10,10,15,0.4) 0%, rgba(10,10,15,0.95) 100%)" }} />
|
||
<div className="relative container mx-auto px-4 pt-12 pb-4 text-center">
|
||
<div className="inline-flex items-center gap-2 mb-4 px-4 py-2 rounded-full text-sm font-semibold" style={{ background: "rgba(0,230,118,0.1)", border: "1px solid rgba(0,230,118,0.3)", color: "#00e676" }}>
|
||
<span className="w-2 h-2 rounded-full bg-green-400 animate-pulse" />
|
||
{t("hero_badge")}
|
||
</div>
|
||
<h1 className="text-4xl md:text-6xl font-bold mb-4 leading-tight" style={{ fontFamily: "'Space Grotesk', sans-serif", background: "linear-gradient(135deg, #f0b429 0%, #ffd700 50%, #f0b429 100%)", WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent" }}>
|
||
{t("hero_title")}
|
||
</h1>
|
||
<p className="text-lg text-white/70 max-w-2xl mx-auto mb-6">{t("hero_subtitle")}</p>
|
||
<div className="flex flex-wrap justify-center gap-4 text-sm text-white/50">
|
||
<span className="flex items-center gap-1"><span className="text-amber-400">✦</span> {t("hero_price")}</span>
|
||
<span className="flex items-center gap-1"><span className="text-amber-400">✦</span> {t("hero_supply")}</span>
|
||
<span className="flex items-center gap-1"><span className="text-amber-400">✦</span> {t("hero_networks")}</span>
|
||
<span className="flex items-center gap-1"><span className="text-amber-400">✦</span> {t("hero_no_min")}</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* ── Main Content ── */}
|
||
<section className="container mx-auto px-4 py-8">
|
||
<div className="grid grid-cols-1 lg:grid-cols-5 gap-6 max-w-6xl mx-auto">
|
||
|
||
{/* ── Left Panel: Stats & Info ── */}
|
||
<div className="lg:col-span-2 space-y-5 fade-in-up">
|
||
|
||
{/* Countdown */}
|
||
<div className="nac-card rounded-2xl p-5 scan-line">
|
||
<h3 className="text-xs font-semibold uppercase tracking-widest text-white/40 mb-4">{t("stats_ends_in")}</h3>
|
||
<div className="grid grid-cols-4 gap-2 text-center">
|
||
{[
|
||
{ label: t("stats_days"), value: countdown.days },
|
||
{ label: t("stats_hours"), value: countdown.hours },
|
||
{ label: t("stats_mins"), value: countdown.minutes },
|
||
{ label: t("stats_secs"), value: countdown.seconds },
|
||
].map(({ label, value }) => (
|
||
<div key={label} className="rounded-xl py-3" style={{ background: "rgba(240,180,41,0.08)", border: "1px solid rgba(240,180,41,0.15)" }}>
|
||
<div className="text-2xl font-bold counter-digit amber-text-glow" style={{ color: "#f0b429", fontFamily: "'Space Grotesk', sans-serif" }}>
|
||
{String(value).padStart(2, "0")}
|
||
</div>
|
||
<div className="text-xs text-white/40 mt-1">{label}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Progress — Real On-Chain Data */}
|
||
<div className="nac-card rounded-2xl p-5">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<h3 className="text-xs font-semibold uppercase tracking-widest text-white/40">{t("stats_raised")}</h3>
|
||
<div className="flex items-center gap-2">
|
||
{statsLoading ? (
|
||
<span className="text-xs text-white/30">{t("loading_stats")}</span>
|
||
) : (
|
||
<>
|
||
<span className="w-1.5 h-1.5 rounded-full bg-green-400 animate-pulse" />
|
||
<span className="text-xs text-green-400">{t("stats_live_data")}</span>
|
||
</>
|
||
)}
|
||
<span className="text-xs font-bold counter-digit" style={{ color: "#f0b429" }}>{progressPct.toFixed(1)}%</span>
|
||
</div>
|
||
</div>
|
||
<div className="h-3 rounded-full mb-3 overflow-hidden" style={{ background: "rgba(255,255,255,0.06)" }}>
|
||
<div className="h-full rounded-full progress-bar-animated" style={{ width: `${progressPct}%` }} />
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<div>
|
||
<div className="text-xl font-bold counter-digit amber-text-glow" style={{ color: "#f0b429", fontFamily: "'Space Grotesk', sans-serif" }}>
|
||
<AnimatedCounter value={stats.totalUsdtRaised} prefix="$" />
|
||
</div>
|
||
<div className="text-xs text-white/40">{t("stats_raised_label")}</div>
|
||
</div>
|
||
<div className="text-right">
|
||
<div className="text-xl font-bold counter-digit text-white/60" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>
|
||
${formatNumber(stats.hardCap)}
|
||
</div>
|
||
<div className="text-xs text-white/40">{t("stats_hard_cap")}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Stats Grid */}
|
||
<div className="grid grid-cols-2 gap-3">
|
||
{[
|
||
{ label: t("stats_tokens_sold"), value: formatNumber(stats.totalTokensSold), unit: "XIC" },
|
||
{ label: t("stats_token_price"), value: "$0.02", unit: "USDT" },
|
||
{ label: t("stats_listing"), value: "$0.10", unit: t("stats_target") },
|
||
{ label: t("hero_networks"), value: "3", unit: "BSC · ETH · TRC20" },
|
||
].map(({ label, value, unit }) => (
|
||
<div key={label} className="nac-card rounded-xl p-4">
|
||
<div className="text-lg font-bold counter-digit" style={{ fontFamily: "'Space Grotesk', sans-serif", color: "#f0b429" }}>{value}</div>
|
||
<div className="text-xs text-white/40 mt-0.5">{label}</div>
|
||
<div className="text-xs text-white/25">{unit}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Token Info */}
|
||
<div className="nac-card rounded-2xl p-5 space-y-3">
|
||
<h3 className="text-xs font-semibold uppercase tracking-widest text-white/40">{t("token_details")}</h3>
|
||
{[
|
||
{ label: t("token_name"), value: "New AssetChain Token" },
|
||
{ label: t("token_symbol"), value: "XIC" },
|
||
{ label: t("token_network"), value: "BSC (BEP-20)" },
|
||
{ label: t("token_decimals"), value: "18" },
|
||
{ label: t("token_supply"), value: "100,000,000,000" },
|
||
].map(({ label, value }) => (
|
||
<div key={label} className="flex items-center justify-between py-1" style={{ borderBottom: "1px solid rgba(255,255,255,0.04)" }}>
|
||
<span className="text-xs text-white/40">{label}</span>
|
||
<span className="text-xs font-medium text-white/80 counter-digit">{value}</span>
|
||
</div>
|
||
))}
|
||
<a
|
||
href={`https://bscscan.com/address/${CONTRACTS.BSC.token}`}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="block text-center text-xs py-2 rounded-lg transition-all hover:bg-white/5"
|
||
style={{ color: "#00d4ff", border: "1px solid rgba(0,212,255,0.2)" }}
|
||
>
|
||
{t("token_view_contract")}
|
||
</a>
|
||
</div>
|
||
|
||
{/* Live Purchase Feed */}
|
||
<PurchaseFeed lang={lang} />
|
||
</div>
|
||
|
||
{/* ── Right Panel: Purchase ── */}
|
||
<div className="lg:col-span-3">
|
||
<div className="nac-card rounded-2xl p-6 amber-glow">
|
||
{/* Token Icon + Title */}
|
||
<div className="flex items-center gap-4 mb-6">
|
||
<img src={TOKEN_ICON} alt="XIC" className="w-14 h-14 rounded-full" style={{ border: "2px solid rgba(240,180,41,0.4)" }} />
|
||
<div>
|
||
<h2 className="text-2xl font-bold" style={{ fontFamily: "'Space Grotesk', sans-serif", color: "#f0b429" }}>{t("buy_title")}</h2>
|
||
<p className="text-sm text-white/50">{t("buy_subtitle")} <span className="text-white/80 font-semibold">$0.02 USDT</span> · <span className="text-green-400 font-semibold">{t("buy_no_min")}</span></p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Network Selector */}
|
||
<div className="mb-6">
|
||
<p className="text-xs font-semibold uppercase tracking-widest text-white/40 mb-3">{t("buy_select_network")}</p>
|
||
<div className="grid grid-cols-3 gap-2">
|
||
{networks.map(net => (
|
||
<button
|
||
key={net}
|
||
onClick={() => setActiveNetwork(net)}
|
||
className={`network-tab rounded-xl py-3 px-2 flex flex-col items-center gap-1.5 ${activeNetwork === net ? "active" : ""}`}
|
||
>
|
||
<NetworkIcon network={net} />
|
||
<span className="text-xs font-semibold">{net === "BSC" ? "BSC" : net === "ETH" ? "Ethereum" : "TRON"}</span>
|
||
<span className="text-xs opacity-60">{net === "TRON" ? "TRC20" : "ERC20"} USDT</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Purchase Area */}
|
||
<div className="relative">
|
||
{/* Presale Paused Overlay */}
|
||
{isPresalePaused && (
|
||
<div
|
||
className="absolute inset-0 z-10 flex flex-col items-center justify-center rounded-2xl gap-3"
|
||
style={{
|
||
background: "rgba(10,10,15,0.88)",
|
||
border: "1.5px solid rgba(255,60,60,0.4)",
|
||
backdropFilter: "blur(4px)",
|
||
}}
|
||
>
|
||
<div className="text-4xl">⏸</div>
|
||
<p className="text-base font-bold" style={{ color: "#ff6060", fontFamily: "'Space Grotesk', sans-serif" }}>
|
||
{lang === "zh" ? "预售已暂停" : "Presale Paused"}
|
||
</p>
|
||
<p className="text-xs text-white/50 text-center px-4">
|
||
{lang === "zh" ? "请关注官方 Telegram / Twitter 获取最新公告" : "Follow our official Telegram / Twitter for updates"}
|
||
</p>
|
||
</div>
|
||
)}
|
||
{activeNetwork === "BSC" && <EVMPurchasePanel network="BSC" lang={lang} wallet={wallet} />}
|
||
{activeNetwork === "ETH" && <EVMPurchasePanel network="ETH" lang={lang} wallet={wallet} />}
|
||
{activeNetwork === "TRON" && (
|
||
<div className="space-y-4">
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-white/60 font-medium">{t("buy_usdt_trc20")}</label>
|
||
<div className="relative">
|
||
<input
|
||
type="number"
|
||
value={trcUsdtAmount}
|
||
onChange={e => setTrcUsdtAmount(e.target.value)}
|
||
placeholder={t("buy_placeholder")}
|
||
className="input-nac w-full px-4 py-3 rounded-xl text-lg counter-digit pr-20"
|
||
/>
|
||
<span className="absolute right-4 top-1/2 -translate-y-1/2 text-white/40 text-sm font-semibold">USDT</span>
|
||
</div>
|
||
<div className="flex gap-2">
|
||
{[100, 500, 1000, 5000].map(amt => (
|
||
<button
|
||
key={amt}
|
||
onClick={() => setTrcUsdtAmount(amt.toString())}
|
||
className="flex-1 py-1 rounded-lg text-xs font-semibold transition-all"
|
||
style={{ background: "rgba(240,180,41,0.08)", border: "1px solid rgba(240,180,41,0.2)", color: "rgba(240,180,41,0.8)" }}
|
||
>
|
||
${amt}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<TRC20Panel usdtAmount={parseFloat(trcUsdtAmount) || 0} lang={lang} connectedAddress={wallet.address || undefined} onConnectWallet={wallet.connect} />
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Presale Contract Links */}
|
||
<div className="mt-6 pt-4" style={{ borderTop: "1px solid rgba(255,255,255,0.06)" }}>
|
||
<p className="text-xs text-white/30 mb-2">{t("buy_contracts")}</p>
|
||
<div className="flex flex-wrap gap-2">
|
||
<a href={`https://bscscan.com/address/${CONTRACTS.BSC.presale}`} target="_blank" rel="noopener noreferrer" className="text-xs px-3 py-1 rounded-full transition-all hover:bg-white/5" style={{ color: "#00d4ff", border: "1px solid rgba(0,212,255,0.2)" }}>
|
||
{t("buy_bsc_contract")}
|
||
</a>
|
||
<a href={`https://etherscan.io/address/${CONTRACTS.ETH.presale}`} target="_blank" rel="noopener noreferrer" className="text-xs px-3 py-1 rounded-full transition-all hover:bg-white/5" style={{ color: "#00d4ff", border: "1px solid rgba(0,212,255,0.2)" }}>
|
||
{t("buy_eth_contract")}
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Why NAC */}
|
||
<div className="mt-5 grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
{[
|
||
{ icon: "🔗", title: t("why_rwa_title"), desc: t("why_rwa_desc") },
|
||
{ icon: "⚡", title: t("why_cbpp_title"), desc: t("why_cbpp_desc") },
|
||
{ icon: "🛡️", title: t("why_charter_title"), desc: t("why_charter_desc") },
|
||
].map(({ icon, title, desc }) => (
|
||
<div key={title} className="nac-card rounded-xl p-4">
|
||
<div className="text-2xl mb-2">{icon}</div>
|
||
<h4 className="font-semibold text-white/90 text-sm mb-1" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>{title}</h4>
|
||
<p className="text-xs text-white/50 leading-relaxed">{desc}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* ── FAQ Section ── */}
|
||
<section className="container mx-auto px-4 py-12 max-w-4xl">
|
||
<div className="text-center mb-10">
|
||
<h2 className="text-3xl font-bold mb-3" style={{ fontFamily: "'Space Grotesk', sans-serif", color: "#f0b429" }}>
|
||
{t("faq_title")}
|
||
</h2>
|
||
<p className="text-white/50 text-sm max-w-xl mx-auto">{t("faq_subtitle")}</p>
|
||
</div>
|
||
<div className="space-y-3">
|
||
{faq.map((item, i) => (
|
||
<FAQItem key={i} q={item.q} a={item.a} index={i} />
|
||
))}
|
||
</div>
|
||
<div className="mt-8 text-center">
|
||
<p className="text-white/40 text-sm mb-3">{t("faq_still")}</p>
|
||
<a
|
||
href="https://t.me/newassetchain"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="inline-flex items-center gap-2 px-6 py-3 rounded-xl text-sm font-semibold transition-all hover:opacity-90"
|
||
style={{ background: "rgba(240,180,41,0.1)", border: "1px solid rgba(240,180,41,0.3)", color: "#f0b429" }}
|
||
>
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
||
<path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm5.562 8.248l-2.04 9.61c-.15.668-.543.832-1.1.517l-3.04-2.24-1.467 1.41c-.162.162-.298.298-.61.298l.217-3.08 5.6-5.06c.243-.217-.053-.337-.376-.12L7.15 14.06l-2.97-.928c-.645-.2-.658-.645.135-.954l11.6-4.47c.537-.195 1.007.13.647.54z"/>
|
||
</svg>
|
||
{t("faq_ask")}
|
||
</a>
|
||
</div>
|
||
</section>
|
||
|
||
{/* ── Footer ── */}
|
||
<footer className="mt-8 py-8 text-center" style={{ borderTop: "1px solid rgba(240,180,41,0.1)" }}>
|
||
<div className="flex items-center justify-center gap-2 mb-3">
|
||
<img src={TOKEN_ICON} alt="XIC" className="w-6 h-6 rounded-full" />
|
||
<span className="font-semibold text-white/70" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>New AssetChain</span>
|
||
</div>
|
||
<p className="text-xs text-white/30 max-w-md mx-auto">{t("footer_risk")}</p>
|
||
<div className="flex justify-center gap-6 mt-4">
|
||
{[
|
||
{ label: t("footer_website"), href: "https://newassetchain.io" },
|
||
{ label: t("footer_explorer"), href: "https://lens.newassetchain.io" },
|
||
{ label: t("footer_telegram"), href: "https://t.me/newassetchain" },
|
||
{ label: t("footer_twitter"), href: "https://twitter.com/newassetchain" },
|
||
].map(({ label, href }) => (
|
||
<a key={label} href={href} target="_blank" rel="noopener noreferrer" className="text-xs text-white/40 hover:text-white/70 transition-colors">
|
||
{label}
|
||
</a>
|
||
))}
|
||
</div>
|
||
</footer>
|
||
|
||
{/* ── Chat Support Widget ── */}
|
||
<ChatSupport lang={lang} />
|
||
|
||
<style>{`
|
||
@keyframes fadeInDown {
|
||
from { opacity: 0; transform: translateY(-10px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
`}</style>
|
||
</div>
|
||
);
|
||
}
|