Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions app/app/connect/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
Expand All @@ -21,10 +22,14 @@ export default function ConnectPage() {
← Back to home
</Link>

<div className="rounded-2xl border border-veil-900/70 bg-gray-900/60 p-8">
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-veil-600 text-xl text-white">
</div>
<div className="hairline rounded-2xl bg-ink-900/60 p-8">
<Image
src="/brand/mascot.png"
alt=""
width={64}
height={64}
className="mb-4 h-16 w-16 animate-float object-contain"
/>
<h1 className="text-2xl font-bold">Connect your Freighter wallet</h1>
<p className="mt-2 text-sm text-gray-400">
Freighter is a free browser extension that manages your Stellar keys. VeilGate
Expand Down
39 changes: 28 additions & 11 deletions app/app/dashboard/history/page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,43 @@
'use client';

import Image from 'next/image';
import { useWallet } from '@/lib/providers/wallet-provider';
import { TxLog } from '@/components/tx-log';
import { ActivityLog } from '@/components/activity-log';

export default function HistoryPage() {
const { address } = useWallet();

return (
<div className="mx-auto max-w-2xl">
<h1 className="text-2xl font-bold">Transaction log</h1>
<div className="mx-auto max-w-5xl">
<h1 className="text-2xl font-bold">Activity</h1>
<p className="mt-1 text-sm text-gray-400">Transaction log</p>

<div
role="note"
className="mt-4 rounded-xl border border-veil-900/70 bg-veil-950/40 p-4 text-sm text-veil-200"
>
Each receipt is stored only in your browser. The on-chain record shows that a proof was
spent — not who deposited or who received. Your deposit and the payment are unlinkable
on-chain, even to VeilGate.
<div className="hairline relative mt-4 overflow-hidden rounded-2xl bg-ink-900/60 p-6">
<div className="relative z-10 flex max-w-md items-start gap-3">
<span
aria-hidden
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-veil-600/20 text-veil-300"
>
</span>
<p className="text-sm text-veil-100">
Every action leaves a proof in your browser. The on-chain record proves that a payment
was made and the settlement is final. Your deposit and the payment are{' '}
<span className="font-semibold text-veil-300">unlinkable on-chain</span>, even to the
VeilGate operator.
</p>
</div>
<Image
src="/brand/hero-home.png"
alt=""
width={480}
height={240}
className="pointer-events-none absolute -right-6 top-1/2 hidden w-[38%] max-w-md -translate-y-1/2 object-contain opacity-90 lg:block"
/>
</div>

<div className="mt-6">
<TxLog address={address} />
<ActivityLog address={address} />
</div>
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions app/app/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ export default function DashboardLayout({ children }: { children: React.ReactNod

return (
<HermesProvider>
<div className="flex min-h-screen flex-col">
<div className="flex h-screen flex-col">
<TopBar />
<div className="flex flex-1">
<div className="flex min-h-0 flex-1">
<NavRail />
<main className="flex-1 px-4 pb-24 pt-6 sm:px-8 sm:pb-10">{children}</main>
<main className="flex-1 overflow-y-auto px-4 pb-24 pt-6 sm:px-8 sm:pb-10">{children}</main>
</div>
<BottomTabBar />
<HermesFab />
Expand Down
167 changes: 118 additions & 49 deletions app/app/dashboard/wallet/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,96 @@

import { useRouter } from 'next/navigation';
import { useWallet } from '@/lib/providers/wallet-provider';
import { Card, CopyButton, NetworkPill } from '@/components/ui';
import { useShieldedTotal } from '@/lib/pending';
import { Card, CopyButton, NetworkPill, truncate } from '@/components/ui';

export default function WalletPage() {
const router = useRouter();
const { address, balances, balancesLoading, disconnect } = useWallet();
const xlm = balances.find((b) => b.asset === 'XLM');
const shielded = useShieldedTotal(address);

function handleDisconnect() {
disconnect();
router.push('/');
}

return (
<div className="mx-auto max-w-2xl">
<h1 className="text-2xl font-bold">Wallet</h1>
<div className="mx-auto max-w-5xl">
<h1 className="flex items-center gap-2 text-2xl font-bold">
Wallet
<span className="h-2 w-2 rounded-full bg-veil-400" aria-hidden />
</h1>
<p className="mt-1 text-sm text-gray-400">Manage your balance, wallet settings and network.</p>

<Card className="mt-6">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-400">Network</span>
<NetworkPill />
</div>
<div className="mt-3 flex items-center justify-between gap-2">
<span className="text-sm text-gray-400">Address</span>
<span className="mono flex items-center gap-1 text-sm text-gray-200">
{address ? address.slice(0, 8) + '…' + address.slice(-6) : '—'}
{address && <CopyButton value={address} label="address" />}
</span>
</div>
{address && (
<a
href={`https://stellar.expert/explorer/testnet/account/${address}`}
target="_blank"
rel="noreferrer noopener"
className="mt-2 inline-block text-xs text-veil-400 hover:underline"
>
View on Stellar Explorer →
</a>
)}
</Card>
<div className="mt-6 grid gap-4 lg:grid-cols-[2fr_3fr]">
<Card>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-400">Network</span>
<NetworkPill />
</div>
<div className="mt-3 flex items-center justify-between gap-2">
<span className="text-sm text-gray-400">Address</span>
<span className="mono flex items-center gap-1 text-sm text-gray-200">
{address ? truncate(address, 8, 6) : '—'}
{address && <CopyButton value={address} label="address" />}
</span>
</div>
{address && (
<a
href={`https://stellar.expert/explorer/testnet/account/${address}`}
target="_blank"
rel="noreferrer noopener"
className="mt-2 inline-block text-xs text-veil-400 hover:underline"
>
View on Stellar Explorer →
</a>
)}
</Card>

<Card className="mt-4">
<p className="mb-3 text-xs font-semibold uppercase tracking-wide text-veil-400">
Balances
</p>
{balancesLoading ? (
<p className="text-sm text-gray-500">Loading…</p>
) : (
<div className="space-y-2">
{balances.map((b) => (
<div key={b.asset} className="flex items-center justify-between text-sm">
<span className="text-gray-300">{b.asset}</span>
<span className="mono text-gray-100">{Number(b.amount).toLocaleString(undefined, { maximumFractionDigits: 7 })}</span>
</div>
))}
<Card>
<p className="text-xs font-semibold uppercase tracking-wide text-veil-400">Total balance</p>
{balancesLoading ? (
<p className="mt-2 text-sm text-gray-500">Loading…</p>
) : xlm ? (
<p className="mt-1 text-3xl font-bold text-white">
{Number(xlm.amount).toLocaleString(undefined, { maximumFractionDigits: 7 })}{' '}
<span className="text-veil-400">XLM</span>
</p>
) : (
<p className="mt-2 text-sm text-gray-500">No balance yet.</p>
)}
<div className="mt-4 flex gap-8 border-t border-veil-900/60 pt-3">
<div>
<p className="text-xs uppercase tracking-wide text-gray-500">Available</p>
<p className="mono mt-1 text-sm text-gray-200">
{xlm ? Number(xlm.amount).toLocaleString(undefined, { maximumFractionDigits: 7 }) : '—'} XLM
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-gray-500">Shielded</p>
<p className="mono mt-1 text-sm text-gray-200">
{shielded.toLocaleString(undefined, { maximumFractionDigits: 7 })} XLM
</p>
<p className="text-[10px] text-gray-600">pending payouts</p>
</div>
</div>
)}
</Card>
{balances.filter((b) => b.asset !== 'XLM').length > 0 && (
<div className="mt-4 space-y-2 border-t border-veil-900/60 pt-3">
{balances
.filter((b) => b.asset !== 'XLM')
.map((b) => (
<div key={b.asset} className="flex items-center justify-between text-sm">
<span className="text-gray-300">{b.asset}</span>
<span className="mono text-gray-100">
{Number(b.amount).toLocaleString(undefined, { maximumFractionDigits: 7 })}
</span>
</div>
))}
</div>
)}
</Card>
</div>

<Card className="mt-4">
<p className="text-sm text-gray-300">Need testnet XLM?</p>
Expand All @@ -71,13 +105,48 @@ export default function WalletPage() {
</a>
</Card>

<div className="mt-6 text-center">
<button
onClick={handleDisconnect}
className="rounded-xl border border-veil-900/70 px-6 py-2.5 text-sm text-gray-300 hover:bg-veil-900/40"
>
Disconnect wallet
</button>
<div className="mt-6">
<h2 className="font-semibold text-white">Wallet actions</h2>
<div className="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-3">
{address && (
<div className="hairline flex items-center gap-3 rounded-xl bg-ink-900/60 p-4 text-sm">
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-veil-900/60 text-veil-300">
</span>
<div className="min-w-0">
<p className="font-medium text-gray-200">Receive</p>
<p className="mono truncate text-xs text-gray-500">{truncate(address, 6, 4)}</p>
</div>
<CopyButton value={address} label="address" />
</div>
)}
<a
href={address ? `https://stellar.expert/explorer/testnet/account/${address}` : undefined}
target="_blank"
rel="noreferrer noopener"
className="hairline flex items-center gap-3 rounded-xl bg-ink-900/60 p-4 text-sm text-gray-300 transition hover:bg-ink-900/90 hover:text-white"
>
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-veil-900/60 text-veil-300">
</span>
<div>
<p className="font-medium">View on explorer</p>
<p className="text-xs text-gray-500">See this wallet on Stellar Explorer</p>
</div>
</a>
<button
onClick={handleDisconnect}
className="hairline flex items-center gap-3 rounded-xl bg-ink-900/60 p-4 text-left text-sm text-gray-300 transition hover:bg-ink-900/90 hover:text-white"
>
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-veil-900/60 text-veil-300">
</span>
<div>
<p className="font-medium">Disconnect wallet</p>
<p className="text-xs text-gray-500">Disconnect this wallet</p>
</div>
</button>
</div>
</div>
</div>
);
Expand Down
Binary file added app/app/favicon.ico
Binary file not shown.
90 changes: 90 additions & 0 deletions app/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,95 @@ body {
background: radial-gradient(circle, rgba(139, 92, 246, 0.55) 0%, rgba(139, 92, 246, 0) 70%);
}

/* Brand gradient text — the blue→violet of the logo mark. */
.text-veilgrad {
background: linear-gradient(100deg, #7b8cff 0%, #8b5cf6 55%, #a78bfa 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}

/* Scrims for full-bleed art: keep text legible without boxing the image. */
.scrim-l {
background: linear-gradient(
to right,
#050309 0%,
rgba(5, 3, 9, 0.92) 32%,
rgba(5, 3, 9, 0.45) 60%,
rgba(5, 3, 9, 0) 100%
);
}
.scrim-b {
background: linear-gradient(to top, #050309 0%, rgba(5, 3, 9, 0.6) 40%, rgba(5, 3, 9, 0) 100%);
}

/* Hairline border that reads premium on near-black. */
.hairline {
border: 1px solid rgba(139, 92, 246, 0.16);
}

.mono {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, 'Cascadia Code', monospace;
}

/* CSS-only violet particle field — ambient texture for slides without a
full-bleed art background (pitch deck). Base layer drifts slowly; two
pseudo-element layers of dots twinkle (opacity fade in/out) on their own
out-of-sync timers so the sparkle reads as randomized, not a single pulse. */
.particle-field {
background-image:
radial-gradient(1.5px 1.5px at 20% 30%, rgba(167, 139, 250, 0.45) 50%, transparent),
radial-gradient(2px 2px at 75% 70%, rgba(139, 92, 246, 0.4) 50%, transparent),
radial-gradient(1px 1px at 40% 85%, rgba(167, 139, 250, 0.35) 50%, transparent),
radial-gradient(1.5px 1.5px at 88% 15%, rgba(139, 92, 246, 0.4) 50%, transparent);
background-repeat: repeat;
background-size: 640px 640px, 720px 720px, 560px 560px, 680px 680px;
animation: particle-drift 80s linear infinite;
}

.particle-field::before,
.particle-field::after {
content: '';
position: absolute;
inset: 0;
}

.particle-field::before {
background-image:
radial-gradient(1.5px 1.5px at 15% 55%, rgba(196, 181, 253, 0.85) 50%, transparent),
radial-gradient(2px 2px at 65% 20%, rgba(167, 139, 250, 0.75) 50%, transparent);
background-repeat: repeat;
background-size: 700px 700px, 600px 600px;
animation: particle-twinkle 4.2s ease-in-out infinite;
}

.particle-field::after {
background-image:
radial-gradient(1px 1px at 82% 65%, rgba(196, 181, 253, 0.8) 50%, transparent),
radial-gradient(1.5px 1.5px at 30% 90%, rgba(139, 92, 246, 0.75) 50%, transparent);
background-repeat: repeat;
background-size: 650px 650px, 750px 750px;
animation: particle-twinkle 5.6s ease-in-out infinite -2.4s;
}

@keyframes particle-drift {
from {
background-position: 0 0, 0 0, 0 0, 0 0;
}
to {
background-position: 320px 320px, -360px 360px, 280px -280px, -340px 340px;
}
}

@keyframes particle-twinkle {
0%, 100% {
opacity: 0.1;
}
50% {
opacity: 1;
}
}

@media (prefers-reduced-motion: reduce) {
.animate-proof-pulse,
.animate-ring-expand,
Expand All @@ -40,4 +125,9 @@ body {
.proof-glow {
opacity: 0.6;
}
.particle-field,
.particle-field::before,
.particle-field::after {
animation: none;
}
}
Loading
Loading