Skip to content
Open
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
33 changes: 33 additions & 0 deletions frontend/wallet/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,39 @@ body {
border-top-color: var(--off-white);
}

/* ── Biometric pulse ring (landing / create screen) ─────────────────── */
@keyframes pulse-ring {
0% { transform: scale(0.92); opacity: 0.7; }
70% { transform: scale(1.18); opacity: 0; }
100% { transform: scale(0.92); opacity: 0; }
}
@keyframes pulse-ring-2 {
0% { transform: scale(0.92); opacity: 0.4; }
70% { transform: scale(1.36); opacity: 0; }
100% { transform: scale(0.92); opacity: 0; }
}
.biometric-pulse {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.biometric-pulse::before,
.biometric-pulse::after {
content: '';
position: absolute;
inset: -12px;
border-radius: 50%;
border: 1.5px solid rgba(253, 218, 36, 0.55);
pointer-events: none;
}
.biometric-pulse::before {
animation: pulse-ring 2.4s var(--ease) infinite;
}
.biometric-pulse::after {
animation: pulse-ring-2 2.4s var(--ease) 0.8s infinite;
}

/* ── Wallet shell layout ─────────────────────────────────────────────── */
.wallet-shell {
min-height: 100dvh;
Expand Down
180 changes: 101 additions & 79 deletions frontend/wallet/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,97 +133,119 @@ export default function OnboardingPage() {
<>
<main>
{showTutorial && <OnboardingTutorial onComplete={handleTutorialComplete} />}
<div className="wallet-shell" style={{ justifyContent: 'center', alignItems: 'center', padding: '2rem 1.25rem', minHeight: '100dvh' }}>
<div style={{ maxWidth: 400, width: '100%' }}>

{/* Logo + wordmark */}
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1rem', marginBottom: '3rem' }}>
<div style={{ position: 'relative' }} className="biometric-pulse">
<VeilMark size={64} />
</div>
<div style={{ textAlign: 'center' }}>
<h1 style={{ fontFamily: 'Anton, Impact, sans-serif', fontSize: '2.5rem', letterSpacing: '0.08em', color: 'var(--gold)' }}>
VEIL
</h1>
<p style={{ fontSize: '0.9375rem', color: 'rgba(246,247,248,0.5)', marginTop: '0.25rem' }}>
Your passkey is your wallet
</p>
</div>
</div>
<div
className="wallet-shell"
style={{ justifyContent: 'center', alignItems: 'center', padding: '2rem 1.25rem', minHeight: '100dvh' }}
>
<div style={{ maxWidth: 400, width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>

{/* ── Hero ── */}
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2rem', marginBottom: '3rem', textAlign: 'center' }}>

{/* Biometric pulse ring */}
<div className="biometric-pulse" style={{ width: 96, height: 96 }}>
<VeilMark size={64} />
</div>

{/* Main card */}



{step === 'landing' && (
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
<button className="btn-gold" onClick={handleCreate}>
Create wallet
</button>
<button className="btn-ghost" onClick={() => router.push('/recover')}>
Recover existing wallet
</button>
{error && (
<p style={{ fontSize: '0.8125rem', color: 'var(--teal)', textAlign: 'center', marginTop: '0.5rem' }}>
{error}
{/* Headline + lede */}
<div>
<h1
style={{
fontFamily: 'Lora, Georgia, serif',
fontWeight: 600,
fontStyle: 'italic',
fontSize: 'clamp(1.75rem, 6vw, 2.25rem)',
lineHeight: 1.25,
color: 'var(--off-white)',
marginBottom: '1rem',
}}
>
Your passkey is{' '}
<span className="hl" style={{ color: 'var(--gold)', fontStyle: 'italic' }}>
your wallet.
</span>
</h1>
<p style={{ fontSize: '0.9375rem', color: 'rgba(246,247,248,0.5)', lineHeight: 1.6 }}>
No seed phrase. No password. Just your biometric — securing a smart contract wallet on Stellar.
</p>
)}
</div>
</div>
)}

{(step === 'registering' || step === 'deploying') && (
<div className="card" style={{ textAlign: 'center' }}>
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: '1rem' }}>
<div className="spinner spinner-light" />
{/* ── CTAs ── */}
{step === 'landing' && (
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', width: '100%' }}>
<button id="onboarding-create" className="btn-gold" onClick={handleCreate}>
Create wallet
</button>
<button id="onboarding-recover" className="btn-ghost" onClick={() => router.push('/recover')}>
Recover existing wallet
</button>
{error && (
<p style={{ fontSize: '0.8125rem', color: 'var(--teal)', textAlign: 'center', marginTop: '0.5rem' }}>
{error}
</p>
)}
</div>
<p style={{ fontFamily: 'Inter', fontWeight: 500, color: 'var(--off-white)' }}>
{step === 'registering' ? 'Waiting for biometric...' : 'Deploying wallet on-chain...'}
</p>
<p style={{ fontSize: '0.8125rem', color: 'rgba(246,247,248,0.4)', marginTop: '0.5rem' }}>
{step === 'registering'
? 'Approve the passkey prompt on your device'
: `Broadcasting to ${network.displayName}`}
</p>
</div>
)}

{step === 'done' && address && (
<div className="card" style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem' }}>
<div style={{ textAlign: 'center' }}>
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" style={{ margin: '0 auto 0.75rem' }}>
<circle cx="20" cy="20" r="19" stroke="var(--teal)" strokeWidth="1.5" />
<path d="M13 20.5l5 5 9-9" stroke="var(--teal)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
<p style={{ fontFamily: 'Lora, Georgia, serif', fontWeight: 600, fontStyle: 'italic', fontSize: '1.25rem', color: 'var(--off-white)' }}>
Wallet created
)}

{/* ── In-progress states ── */}
{(step === 'registering' || step === 'deploying') && (
<div className="card" style={{ textAlign: 'center', width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: '1rem' }}>
<div className="spinner spinner-light" />
</div>
<p style={{ fontFamily: 'Inter', fontWeight: 500, color: 'var(--off-white)' }}>
{step === 'registering' ? 'Waiting for biometric...' : 'Deploying wallet on-chain...'}
</p>
<p style={{ fontSize: '0.8125rem', color: 'rgba(246,247,248,0.4)', marginTop: '0.5rem' }}>
{step === 'registering'
? 'Approve the passkey prompt on your device'
: `Broadcasting to ${network.displayName}`}
</p>
</div>
)}

{/* ── Success ── */}
{step === 'done' && address && (
<div className="card" style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem', width: '100%' }}>
<div style={{ textAlign: 'center' }}>
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" style={{ margin: '0 auto 0.75rem' }}>
<circle cx="20" cy="20" r="19" stroke="var(--teal)" strokeWidth="1.5" />
<path d="M13 20.5l5 5 9-9" stroke="var(--teal)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
<p style={{ fontFamily: 'Lora, Georgia, serif', fontWeight: 600, fontStyle: 'italic', fontSize: '1.25rem', color: 'var(--off-white)' }}>
Wallet created
</p>
</div>

<div>
<p style={{ fontSize: '0.75rem', color: 'rgba(246,247,248,0.4)', marginBottom: '0.5rem', fontFamily: 'Inter' }}>
YOUR WALLET ADDRESS
</p>
<div className="address-chip" style={{ width: '100%', justifyContent: 'center', borderRadius: 12, padding: '0.75rem 1rem' }}>
{address.slice(0, 8)}...{address.slice(-8)}
<div>
<p style={{ fontSize: '0.75rem', color: 'rgba(246,247,248,0.4)', marginBottom: '0.5rem', fontFamily: 'Anton, Impact, sans-serif', letterSpacing: '0.06em' }}>
YOUR WALLET ADDRESS
</p>
<div className="address-chip" style={{ width: '100%', justifyContent: 'center', borderRadius: 12, padding: '0.75rem 1rem' }}>
{address.slice(0, 8)}...{address.slice(-8)}
</div>
</div>

<button className="btn-gold" onClick={handleContinue}>
Open wallet
</button>
</div>
)}

{/* ── WebAuthn footnote ── */}
<p
id="webauthn-footnote"
style={{ textAlign: 'center', fontSize: '0.75rem', color: 'rgba(246,247,248,0.3)', marginTop: '2.5rem' }}
>
Secured by{' '}
<span style={{ color: 'rgba(246,247,248,0.6)' }}>WebAuthn</span>
{' '}on{' '}
<span style={{ color: 'rgba(246,247,248,0.6)' }}>Stellar</span>
</p>

<button className="btn-gold" onClick={handleContinue}>
Open wallet
</button>
</div>
)}

{/* Footer */}
<p
id= "tutorial-title"
style={{ textAlign: 'center', fontSize: '0.75rem', color: '#7c7c7c', marginTop: '2rem' }}>
No seed phrase. No private key. Powered by{' '}
<span style={{ color: 'rgba(246,247,248,0.88)' }}>Stellar Soroban</span>
</p>

</div>
</div>
</div>
</main>
</>
)
Expand Down
Loading