feat: transform UI to dark cyberpunk theme

- Eliminate all white/light boxes and backgrounds
- Fix drag-drop zone with neon cyberpunk colors (#00f0ff, #ff006e, #16213e)
- Fix restored mnemonic display with matrix green (#39ff14)
- Fix security options panel with dark gradient
- Fix all remaining slate-700/slate-800 labels to cyberpunk neon
- Fix info banners and text colors
- Update badge components with cyberpunk color scheme
- Apply consistent dark theme across all components
This commit is contained in:
LC mac
2026-02-08 22:27:41 +08:00
parent 0ab99ce493
commit 7c4fc1460c
12 changed files with 649 additions and 515 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -61,6 +61,7 @@ function App() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const [showQRScanner, setShowQRScanner] = useState(false); const [showQRScanner, setShowQRScanner] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [isReadOnly, setIsReadOnly] = useState(false); const [isReadOnly, setIsReadOnly] = useState(false);
const [encryptedMnemonicCache, setEncryptedMnemonicCache] = useState<EncryptedBlob | null>(null); const [encryptedMnemonicCache, setEncryptedMnemonicCache] = useState<EncryptedBlob | null>(null);
const [showSecurityModal, setShowSecurityModal] = useState(false); const [showSecurityModal, setShowSecurityModal] = useState(false);
@@ -305,6 +306,78 @@ function App() {
}; };
const handleFileUpload = async (file: File) => {
setLoading(true);
setError('');
try {
// Handle image files (QR codes)
if (file.type.startsWith('image/')) {
const img = new Image();
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
img.src = URL.createObjectURL(file);
});
canvas.width = img.width;
canvas.height = img.height;
ctx?.drawImage(img, 0, 0);
const imageData = ctx?.getImageData(0, 0, canvas.width, canvas.height);
if (!imageData) throw new Error('Could not read image');
const jsqr = (await import('jsqr')).default;
const code = jsqr(imageData.data, imageData.width, imageData.height);
if (!code) throw new Error('No QR code found in image');
// Handle binary or text QR data
const isBinary = (code.binaryData.length === 16 || code.binaryData.length === 32);
if (isBinary) {
const hex = Array.from(code.binaryData).map(b => b.toString(16).padStart(2, '0')).join('');
setRestoreInput(hex);
} else {
setRestoreInput(code.data);
}
URL.revokeObjectURL(img.src);
}
// Handle text files (PGP armor, hex, numeric SeedQR)
else if (file.type === 'text/plain' || file.name.endsWith('.txt') || file.name.endsWith('.asc')) {
const text = await file.text();
setRestoreInput(text.trim());
}
else {
throw new Error('Unsupported file type. Use PNG/JPG (QR) or TXT/ASC (armor)');
}
} catch (err) {
setError(err instanceof Error ? err.message : 'File upload failed');
} finally {
setLoading(false);
}
};
const handleDrop = async (e: React.DragEvent) => {
e.preventDefault();
setIsDragging(false);
const file = e.dataTransfer.files[0]; // Access the first file
if (file) await handleFileUpload(file);
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
setIsDragging(true);
};
const handleDragLeave = () => {
setIsDragging(false);
};
const handleRestore = async () => { const handleRestore = async () => {
setLoading(true); setLoading(true);
setError(''); setError('');
@@ -416,7 +489,12 @@ function App() {
return ( return (
<div className="min-h-screen bg-slate-800 text-slate-100"> <div className="min-h-screen bg-[#0a0a0f] bg-[radial-gradient(ellipse_at_top,_#1a1a2e_0%,_#0a0a0f_100%)] relative">
{/* Cyberpunk grid overlay */}
<div className="absolute inset-0 bg-[linear-gradient(rgba(0,240,255,0.03)_1px,transparent_1px),linear-gradient(90deg,rgba(0,240,255,0.03)_1px,transparent_1px)] bg-[size:50px_50px] pointer-events-none" />
{/* Content wrapper */}
<div className="relative z-10">
<Header <Header
onOpenSecurityModal={() => setShowSecurityModal(true)} onOpenSecurityModal={() => setShowSecurityModal(true)}
localItems={localItems} localItems={localItems}
@@ -433,10 +511,14 @@ function App() {
onToggleLock={handleToggleLock} onToggleLock={handleToggleLock}
/> />
<main className="max-w-7xl mx-auto px-6 py-4"> <main className="max-w-7xl mx-auto px-6 py-4">
<div className="bg-[#1a1a2e] rounded-xl border-2 border-[#00f0ff]/30 shadow-[0_0_30px_rgba(0,240,255,0.3)] p-8">
<div className="p-6 md:p-8 space-y-6"> <div className="p-6 md:p-8 space-y-6">
{/* Error Display */} {/* Error Display */}
{error && ( {error && (
<div className="p-4 bg-red-50 border-l-4 border-red-500 rounded-r-xl flex gap-3 text-red-800 text-sm items-start animate-in slide-in-from-top-2"> <div
className="p-4 bg-[#1a1a2e] border-2 border-[#ff006e] rounded-lg text-[#ff006e] text-sm shadow-[0_0_20px_rgba(255,0,110,0.3)] flex gap-3 items-start animate-in slide-in-from-top-2"
style={{ textShadow: '0 0 5px rgba(255,0,110,0.5)' }}
>
<AlertCircle className="shrink-0 mt-0.5" size={20} /> <AlertCircle className="shrink-0 mt-0.5" size={20} />
<div> <div>
<p className="font-bold mb-1">Error</p> <p className="font-bold mb-1">Error</p>
@@ -447,10 +529,10 @@ function App() {
{/* Info Banner */} {/* Info Banner */}
{recipientFpr && activeTab === 'backup' && ( {recipientFpr && activeTab === 'backup' && (
<div className="p-3 bg-teal-50 border border-teal-200 rounded-lg flex items-start gap-3 text-teal-800 text-xs animate-in fade-in"> <div className="p-3 bg-[#16213e] border border-[#9d84b7] rounded-lg text-[#6ef3f7] text-xs shadow-[0_0_10px_rgba(157,132,183,0.3)] flex items-start gap-3 animate-in fade-in">
<Info size={16} className="shrink-0 mt-0.5" /> <Info size={16} className="shrink-0 mt-0.5" />
<div> <div>
<strong>Recipient Key:</strong> <code className="bg-teal-100 px-1.5 py-0.5 rounded font-mono">{recipientFpr}</code> <strong>Recipient Key:</strong> <code className="bg-[#16213e] border border-[#00f0ff]/50 px-1.5 py-0.5 rounded font-mono text-[#00f0ff]">{recipientFpr}</code>
</div> </div>
</div> </div>
)} )}
@@ -461,10 +543,14 @@ function App() {
{activeTab === 'backup' ? ( {activeTab === 'backup' ? (
<> <>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-semibold text-slate-200">BIP39 Mnemonic</label> <label className="text-sm font-bold text-[#00f0ff] uppercase tracking-widest" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>BIP39 Mnemonic</label>
<textarea <textarea
className={`w-full h-32 p-4 bg-slate-50 border border-slate-200 rounded-xl text-sm font-mono text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-teal-500 transition-all resize-none ${isReadOnly ? 'blur-sm select-none' : '' className={`w-full h-32 p-4 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-lg font-mono text-sm text-[#00f0ff] placeholder-[#9d84b7] focus:outline-none focus:border-[#ff006e] focus:shadow-[0_0_20px_rgba(255,0,110,0.5)] transition-all relative overflow-hidden ${isReadOnly ? 'blur-sm select-none' : ''
}`} }`}
style={{
backgroundImage: 'repeating-linear-gradient(0deg, rgba(0,240,255,0.03) 0px, transparent 1px, transparent 2px, rgba(0,240,255,0.03) 3px)',
textShadow: '0 0 5px rgba(0,240,255,0.5)'
}}
data-sensitive="BIP39 Mnemonic" data-sensitive="BIP39 Mnemonic"
placeholder="Enter your 12 or 24 word seed phrase..." placeholder="Enter your 12 or 24 word seed phrase..."
@@ -485,28 +571,62 @@ function App() {
</> </>
) : activeTab === 'restore' ? ( ) : activeTab === 'restore' ? (
<> <>
<div className="flex gap-2"> <div className="space-y-4">
{/* File Upload Zone */}
<div
className={`border-2 border-dashed rounded-lg p-8 text-center transition-colors ${isDragging ? 'border-[#ff006e] bg-[#16213e] shadow-[0_0_30px_rgba(255,0,110,0.5)]' : 'border-[#00f0ff]/50 bg-[#16213e]'
}`}
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
>
<div className="space-y-3">
<FileKey size={48} className="mx-auto text-[#00f0ff]" />
<p className="text-sm text-[#00f0ff] font-medium" style={{ textShadow: '0 0 10px rgba(0,240,255,0.5)' }}>
Drag & drop QR image or text file
</p>
<p className="text-xs text-[#6ef3f7]">
Supports: PNG/JPG (QR), TXT/ASC (PGP armor, hex, numeric)
</p>
<div className="flex gap-3 justify-center pt-2">
<label className="px-4 py-2 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-lg text-sm font-medium text-[#00f0ff] hover:bg-[#1a1a2e] hover:shadow-[0_0_15px_rgba(0,240,255,0.3)] cursor-pointer transition-all">
📁 Browse Files
<input
type="file"
accept="image/*,.txt,.asc"
className="hidden"
onChange={(e) => e.target.files?.length && handleFileUpload(e.target.files[0])}
/>
</label>
<button <button
onClick={() => setShowQRScanner(true)} onClick={() => setShowQRScanner(true)}
disabled={isReadOnly} className="px-4 py-2 bg-transparent text-[#00f0ff] rounded-lg font-medium border-2 border-[#00f0ff] hover:bg-[#00f0ff]/10 hover:shadow-[0_0_20px_rgba(0,240,255,0.5)] transition-all flex items-center gap-2"
className="flex-1 py-3 bg-gradient-to-r from-purple-600 to-purple-700 text-white rounded-xl font-semibold flex items-center justify-center gap-2 hover:from-purple-700 hover:to-purple-800 transition-all shadow-lg disabled:opacity-50"
> >
<QrCode size={18} /> 📷 Scan QR
Scan QR Code </button> </div>
</button> </div>
</div> </div>
<div className="space-y-2"> {/* Existing restore input textarea stays here */}
<label className="text-sm font-semibold text-slate-200">SEEDPGP1 Payload</label>
<textarea <textarea
className={`w-full h-32 p-4 bg-slate-50 border border-slate-200 rounded-xl text-xs font-mono text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-teal-500 transition-all resize-none ${isReadOnly ? 'blur-sm select-none' : '' className={`w-full p-3 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-lg font-mono text-xs text-[#00f0ff] placeholder-[#9d84b7] focus:outline-none focus:border-[#ff006e] focus:shadow-[0_0_20px_rgba(255,0,110,0.5)] transition-all relative overflow-hidden`}
}`} rows={6}
placeholder="Or paste encrypted data here..."
placeholder="SEEDPGP1:0:ABCD:..."
value={restoreInput} value={restoreInput}
onChange={(e) => setRestoreInput(e.target.value)} onChange={(e) => setRestoreInput(e.target.value)}
readOnly={isReadOnly} style={{
backgroundImage: 'repeating-linear-gradient(0deg, rgba(0,240,255,0.03) 0px, transparent 1px, transparent 2px, rgba(0,240,255,0.03) 3px)',
textShadow: '0 0 5px rgba(0,240,255,0.5)'
}}
/> />
{/* Auto-detection hint */}
{detectedMode && (
<p className="text-xs text-[#00f0ff] flex items-center gap-1">
<Info size={14} />
Detected: {detectedMode.toUpperCase()} format
</p>
)}
</div> </div>
<PgpKeyInput <PgpKeyInput
@@ -521,14 +641,15 @@ function App() {
{privateKeyInput && ( {privateKeyInput && (
<div className="space-y-2"> <div className="space-y-2">
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider">Private Key Passphrase</label> <label className="text-xs font-bold text-[#00f0ff] uppercase tracking-widest" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>Private Key Passphrase</label>
<div className="relative"> <div className="relative">
<Lock className="absolute left-3 top-3 text-slate-400" size={16} /> <Lock className="absolute left-3 top-3 text-[#6ef3f7]" size={16} />
<input <input
type="password" type="password"
data-sensitive="Message Password" data-sensitive="Message Password"
className={`w-full pl-10 pr-4 py-2.5 bg-slate-700 border-2 border-slate-600 rounded-lg text-white text-sm placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-teal-500 transition-all ${isReadOnly ? 'blur-sm select-none' : '' className={`w-full pl-10 pr-4 py-2.5 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-lg text-[#00f0ff] text-sm placeholder-[#9d84b7] focus:outline-none focus:border-[#ff006e] focus:shadow-[0_0_20px_rgba(255,0,110,0.5)] transition-all ${isReadOnly ? 'blur-sm select-none' : ''
}`} }`}
style={{ textShadow: '0 0 5px rgba(0,240,255,0.5)' }}
placeholder="Unlock private key..." placeholder="Unlock private key..."
value={privateKeyPassphrase} value={privateKeyPassphrase}
onChange={(e) => setPrivateKeyPassphrase(e.target.value)} onChange={(e) => setPrivateKeyPassphrase(e.target.value)}
@@ -550,27 +671,28 @@ function App() {
{/* Security Panel */} {/* Security Panel */}
{activeTab !== 'seedblender' && ( {activeTab !== 'seedblender' && (
<div className="space-y-2"> {/* Added space-y-2 wrapper */} <div className="space-y-2"> {/* Added space-y-2 wrapper */}
<label className="text-sm font-semibold text-slate-200 flex items-center gap-2"> <label className="text-sm font-semibold text-[#00f0ff] flex items-center gap-2" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>
<Lock size={14} /> SECURITY OPTIONS <Lock size={14} /> SECURITY OPTIONS
</label> </label>
<div className="p-5 bg-gradient-to-br from-slate-50 to-slate-100 rounded-2xl border-2 border-slate-200 shadow-inner space-y-4"> <div className="p-5 bg-[#16213e] rounded-2xl border-2 border-[#00f0ff]/30 shadow-[0_0_20px_rgba(0,240,255,0.2)] space-y-4">
{/* Removed h3 */} {/* Removed h3 */}
{/* Encryption Mode Toggle */} {/* Encryption Mode Toggle */}
<div className="space-y-2"> <div className="space-y-2">
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider">Encryption Mode</label> <label className="text-xs font-bold text-[#00f0ff] uppercase tracking-widest" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>Encryption Mode</label>
<select <select
value={encryptionMode} value={encryptionMode}
onChange={(e) => setEncryptionMode(e.target.value as 'pgp' | 'krux' | 'seedqr')} onChange={(e) => setEncryptionMode(e.target.value as 'pgp' | 'krux' | 'seedqr')}
disabled={isReadOnly} disabled={isReadOnly}
className="w-full px-3 py-2.5 bg-white border border-slate-200 rounded-lg text-sm text-slate-900 focus:outline-none focus:ring-2 focus:ring-teal-500 transition-all" className="w-full px-3 py-2.5 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-lg text-[#00f0ff] text-sm focus:outline-none focus:border-[#ff006e] focus:shadow-[0_0_20px_rgba(255,0,110,0.5)] transition-all appearance-none bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iOCIgPHBhdGggZD0iTTEgMUw2IDZMMTEgMSIgc3Ryb2tlPSIjMDBmMGZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvc3ZnPg==')] bg-[length:12px] bg-[position:right_12px_center] bg-no-repeat pr-10"
style={{ textShadow: '0 0 5px rgba(0,240,255,0.5)' }}
> >
<option value="pgp">PGP (Asymmetric)</option> <option value="pgp">PGP (Asymmetric)</option>
<option value="krux">Krux KEF (Passphrase)</option> <option value="krux">Krux KEF (Passphrase)</option>
<option value="seedqr">SeedQR (Unencrypted)</option> <option value="seedqr">SeedQR (Unencrypted)</option>
</select> </select>
<p className="text-[10px] text-slate-800 mt-1"> <p className="text-[10px] text-[#6ef3f7] mt-1">
{encryptionMode === 'pgp' {encryptionMode === 'pgp'
? 'Uses PGP keys or password' ? 'Uses PGP keys or password'
: 'Uses passphrase only (Krux compatible)'} : 'Uses passphrase only (Krux compatible)'}
@@ -580,17 +702,18 @@ function App() {
{/* SeedQR Format Toggle */} {/* SeedQR Format Toggle */}
{encryptionMode === 'seedqr' && ( {encryptionMode === 'seedqr' && (
<div className="space-y-2 pt-2"> <div className="space-y-2 pt-2">
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider">SeedQR Format</label> <label className="text-xs font-bold text-[#00f0ff] uppercase tracking-widest" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>SeedQR Format</label>
<select <select
value={seedQrFormat} value={seedQrFormat}
onChange={(e) => setSeedQrFormat(e.target.value as 'standard' | 'compact')} onChange={(e) => setSeedQrFormat(e.target.value as 'standard' | 'compact')}
disabled={isReadOnly} disabled={isReadOnly}
className="w-full px-3 py-2.5 bg-white border border-slate-200 rounded-lg text-sm text-slate-900 focus:outline-none focus:ring-2 focus:ring-teal-500 transition-all" className="w-full px-3 py-2.5 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-lg text-[#00f0ff] text-sm focus:outline-none focus:border-[#ff006e] focus:shadow-[0_0_20px_rgba(255,0,110,0.5)] transition-all appearance-none bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iOCIgPHBhdGggZD0iTTEgMUw2IDZMMTEgMSIgc3Ryb2tlPSIjMDBmMGZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvc3ZnPg==')] bg-[length:12px] bg-[position:right_12px_center] bg-no-repeat pr-10"
style={{ textShadow: '0 0 5px rgba(0,240,255,0.5)' }}
> >
<option value="standard">Standard (Numeric)</option> <option value="standard">Standard (Numeric)</option>
<option value="compact">Compact (Binary)</option> <option value="compact">Compact (Binary)</option>
</select> </select>
<p className="text-[10px] text-slate-800 mt-1"> <p className="text-[10px] text-[#6ef3f7] mt-1">
{seedQrFormat === 'standard' {seedQrFormat === 'standard'
? 'Numeric format, human-readable.' ? 'Numeric format, human-readable.'
: 'Compact binary format, smaller QR code.'} : 'Compact binary format, smaller QR code.'}
@@ -608,27 +731,28 @@ function App() {
)} )}
<div className="space-y-2"> <div className="space-y-2">
<label className="text-xs font-bold text-slate-200 uppercase tracking-wider">MESSAGE PASSWORD</label> <label className="text-xs font-bold text-[#00f0ff] uppercase tracking-widest" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>MESSAGE PASSWORD</label>
<div className="relative"> <div className="relative">
<Lock className="absolute left-3 top-3 text-slate-400" size={16} /> <Lock className="absolute left-3 top-3 text-[#6ef3f7]" size={16} />
<input <input
type="password" type="password"
className={`w-full pl-10 pr-4 py-2.5 bg-slate-700 border-2 border-slate-600 rounded-lg text-white text-sm placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-teal-500 transition-all ${isReadOnly ? 'blur-sm select-none' : '' className={`w-full pl-10 pr-4 py-2.5 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-lg text-[#00f0ff] text-sm placeholder-[#9d84b7] focus:outline-none focus:border-[#ff006e] focus:shadow-[0_0_20px_rgba(255,0,110,0.5)] transition-all ${isReadOnly ? 'blur-sm select-none' : ''
}`} }`}
style={{ textShadow: '0 0 5px rgba(0,240,255,0.5)' }}
placeholder={encryptionMode === 'krux' ? "Required for Krux encryption" : "Optional password..."} placeholder={encryptionMode === 'krux' ? "Required for Krux encryption" : "Optional password..."}
value={activeTab === 'backup' ? backupMessagePassword : restoreMessagePassword} value={activeTab === 'backup' ? backupMessagePassword : restoreMessagePassword}
onChange={(e) => activeTab === 'backup' ? setBackupMessagePassword(e.target.value) : setRestoreMessagePassword(e.target.value)} onChange={(e) => activeTab === 'backup' ? setBackupMessagePassword(e.target.value) : setRestoreMessagePassword(e.target.value)}
readOnly={isReadOnly} readOnly={isReadOnly}
/> />
</div> </div>
<p className="text-[10px] text-slate-400 mt-1"> <p className="text-[10px] text-[#6ef3f7] mt-1">
{encryptionMode === 'krux' {encryptionMode === 'krux'
? 'Required passphrase for Krux encryption' ? 'Required passphrase for Krux encryption'
: 'Symmetric encryption password (SKESK)'} : 'Symmetric encryption password (SKESK)'}
</p> </p>
</div> </div>
<div className="pt-3 border-t border-slate-300"> <div className="pt-3 border-t border-[#00f0ff]/30">
<div className="flex items-start gap-2 text-xs text-slate-600"> <div className="flex items-start gap-2 text-xs text-[#6ef3f7]">
<Info size={14} className="shrink-0 mt-0.5" /> <Info size={14} className="shrink-0 mt-0.5" />
<p> <p>
<strong>Krux Compatible Mode:</strong><br /> <strong>Krux Compatible Mode:</strong><br />
@@ -639,16 +763,16 @@ function App() {
{activeTab === 'backup' && ( {activeTab === 'backup' && (
<div className="pt-3 border-t border-slate-300"> <div className="pt-3 border-t border-[#00f0ff]/30">
<label className="flex items-center gap-2 cursor-pointer group"> <label className="flex items-center gap-2 cursor-pointer group">
<input <input
type="checkbox" type="checkbox"
checked={hasBip39Passphrase} checked={hasBip39Passphrase}
onChange={(e) => setHasBip39Passphrase(e.target.checked)} onChange={(e) => setHasBip39Passphrase(e.target.checked)}
disabled={isReadOnly} disabled={isReadOnly}
className="rounded text-teal-600 focus:ring-2 focus:ring-teal-500 transition-all" className="rounded text-[#00f0ff] focus:ring-2 focus:ring-[#00f0ff] transition-all"
/> />
<span className="text-xs font-medium text-slate-700 group-hover:text-slate-900 transition-colors"> <span className="text-xs font-medium text-[#6ef3f7] group-hover:text-[#00f0ff] transition-colors">
BIP39 25th word active BIP39 25th word active
</span> </span>
</label> </label>
@@ -662,7 +786,8 @@ function App() {
<button <button
onClick={handleBackup} onClick={handleBackup}
disabled={!mnemonic || loading || isReadOnly} disabled={!mnemonic || loading || isReadOnly}
className="w-full py-4 bg-gradient-to-r from-teal-500 to-cyan-600 text-white rounded-xl font-bold flex items-center justify-center gap-2 hover:from-teal-600 hover:to-cyan-700 transition-all shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:from-teal-500 disabled:hover:to-cyan-600" className="w-full py-4 bg-gradient-to-r from-[#ff006e] to-[#ff4d8f] text-white rounded-xl font-bold uppercase tracking-wider flex items-center justify-center gap-2 hover:shadow-[0_0_30px_rgba(255,0,110,0.8)] active:scale-95 transition-all disabled:opacity-50 disabled:cursor-not-allowed border border-[#ff006e]"
style={{ textShadow: '0 0 10px rgba(255,255,255,0.8)' }}
> >
{loading ? ( {loading ? (
<RefreshCw className="animate-spin" size={20} /> <RefreshCw className="animate-spin" size={20} />
@@ -675,7 +800,8 @@ function App() {
<button <button
onClick={handleRestore} onClick={handleRestore}
disabled={!restoreInput || loading || isReadOnly} disabled={!restoreInput || loading || isReadOnly}
className="w-full py-4 bg-gradient-to-r from-slate-800 to-slate-900 text-white rounded-xl font-bold flex items-center justify-center gap-2 hover:from-slate-900 hover:to-black transition-all shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed" className="w-full py-4 bg-gradient-to-r from-[#ff006e] to-[#ff4d8f] text-white rounded-xl font-bold uppercase tracking-wider flex items-center justify-center gap-2 hover:shadow-[0_0_30px_rgba(255,0,110,0.8)] active:scale-95 transition-all disabled:opacity-50 disabled:cursor-not-allowed border border-[#ff006e]"
style={{ textShadow: '0 0 10px rgba(255,255,255,0.8)' }}
> >
{loading ? ( {loading ? (
<RefreshCw className="animate-spin" size={20} /> <RefreshCw className="animate-spin" size={20} />
@@ -690,20 +816,20 @@ function App() {
{/* QR Output */} {/* QR Output */}
{qrPayload && activeTab === 'backup' && ( {qrPayload && activeTab === 'backup' && (
<div className="pt-6 border-t border-slate-200 space-y-6 animate-in fade-in slide-in-from-bottom-4"> <div className="pt-6 border-t border-[#00f0ff]/20 space-y-6 animate-in fade-in slide-in-from-bottom-4">
<div className={isReadOnly ? 'blur-lg' : ''}> <div className={isReadOnly ? 'blur-lg' : ''}>
<QrDisplay value={qrPayload} /> <QrDisplay value={qrPayload} />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider"> <label className="text-xs font-bold text-[#00f0ff] uppercase tracking-widest" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>
Raw payload (copy for backup) Raw payload (copy for backup)
</label> </label>
<button <button
type="button" type="button"
onClick={() => copyToClipboard(qrPayload)} onClick={() => copyToClipboard(qrPayload)}
className="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg bg-slate-900 text-white text-xs font-semibold hover:bg-black transition-colors" className="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg bg-[#16213e] border-2 border-[#00f0ff]/50 text-[#00f0ff] text-xs font-semibold hover:bg-[#1a1a2e] hover:shadow-[0_0_15px_rgba(0,240,255,0.3)] transition-all"
> >
{copied ? <CheckCircle2 size={14} /> : <QrCode size={14} />} {copied ? <CheckCircle2 size={14} /> : <QrCode size={14} />}
{copied ? "Copied" : "Copy"} {copied ? "Copied" : "Copy"}
@@ -714,9 +840,13 @@ function App() {
readOnly readOnly
value={typeof qrPayload === 'string' ? qrPayload : Array.from(qrPayload).map(b => b.toString(16).padStart(2, '0')).join('')} value={typeof qrPayload === 'string' ? qrPayload : Array.from(qrPayload).map(b => b.toString(16).padStart(2, '0')).join('')}
onFocus={(e) => e.currentTarget.select()} onFocus={(e) => e.currentTarget.select()}
className="w-full h-28 p-3 bg-slate-900 rounded-xl font-mono text-[10px] text-green-400 placeholder:text-slate-500 border border-slate-700 shadow-inner leading-relaxed resize-none focus:outline-none focus:ring-2 focus:ring-teal-500" className="w-full h-28 p-3 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-lg font-mono text-[10px] text-[#00f0ff] placeholder-[#9d84b7] focus:outline-none focus:border-[#ff006e] focus:shadow-[0_0_20px_rgba(255,0,110,0.5)] transition-all relative overflow-hidden"
style={{
backgroundImage: 'repeating-linear-gradient(0deg, rgba(0,240,255,0.03) 0px, transparent 1px, transparent 2px, rgba(0,240,255,0.03) 3px)',
textShadow: '0 0 5px rgba(0,240,255,0.5)'
}}
/> />
<p className="text-[11px] text-slate-500"> <p className="text-[11px] text-[#6ef3f7]">
Tip: click the box to select all, or use Copy. Tip: click the box to select all, or use Copy.
</p> </p>
</div> </div>
@@ -725,23 +855,24 @@ function App() {
{/* Restored Mnemonic */} {/* Restored Mnemonic */}
{decryptedRestoredMnemonic && activeTab === 'restore' && ( {decryptedRestoredMnemonic && activeTab === 'restore' && (
<div className="pt-6 border-t border-slate-200 animate-in zoom-in-95"> <div className="pt-6 border-t border-[#00f0ff]/20 animate-in zoom-in-95">
<div className="p-6 bg-gradient-to-br from-green-50 to-emerald-50 border-2 border-green-300 rounded-2xl shadow-lg"> <div className="p-6 bg-[#0a0a0f] border-2 border-[#39ff14] rounded-lg shadow-[0_0_30px_rgba(57,255,20,0.4)] relative overflow-hidden">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<span className="font-bold text-green-700 flex items-center gap-2 text-lg"> <span className="font-bold text-[#39ff14] flex items-center gap-2 text-lg" style={{ textShadow: '0 0 10px rgba(57,255,20,0.8)' }}>
<CheckCircle2 size={22} /> Mnemonic Recovered <CheckCircle2 size={22} /> Mnemonic Recovered
</span> </span>
<button <button
onClick={() => setDecryptedRestoredMnemonic(null)} onClick={() => setDecryptedRestoredMnemonic(null)}
className="p-2.5 hover:bg-green-100 rounded-xl transition-all text-green-700 hover:shadow" className="p-2.5 hover:bg-[#16213e] rounded-xl transition-all text-[#39ff14] hover:shadow-[0_0_15px_rgba(57,255,20,0.5)] flex items-center gap-2"
> >
<EyeOff size={22} /> Hide <EyeOff size={22} /> Hide
</button> </button>
</div> </div>
<div className="p-6 bg-white rounded-xl border-2 border-green-200 shadow-sm"> <div className="p-6 bg-[#0a0a0f] rounded-xl border-2 border-[#39ff14] shadow-[0_0_20px_rgba(57,255,20,0.3)]">
<p className={`font-mono text-center text-lg text-slate-800 tracking-wide leading-relaxed break-words ${isReadOnly ? 'blur-md select-none' : '' <p className={`font-mono text-center text-lg break-words text-[#39ff14] selection:bg-[#39ff14] selection:text-[#0a0a0f] ${isReadOnly ? 'blur-md select-none' : ''
}`}> }`}
style={{ textShadow: '0 0 8px rgba(57,255,20,0.8)' }}>
{decryptedRestoredMnemonic} {decryptedRestoredMnemonic}
</p> </p>
</div> </div>
@@ -749,6 +880,7 @@ function App() {
</div> </div>
)} )}
</div> </div>
</div> {/* Close new cyberpunk main container */}
</main> </main>
<Footer <Footer
appVersion={__APP_VERSION__} appVersion={__APP_VERSION__}
@@ -772,11 +904,11 @@ function App() {
onClick={() => setShowSecurityModal(false)} onClick={() => setShowSecurityModal(false)}
> >
<div <div
className="bg-slate-800 rounded-xl border border-slate-700 p-6 max-w-md w-full mx-4 shadow-2xl" className="bg-[#1a1a2e] rounded-xl border-2 border-[#00f0ff]/30 p-6 max-w-md w-full mx-4 shadow-[0_0_30px_rgba(0,240,255,0.3)]"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<h3 className="text-lg font-semibold text-white mb-4">Security Limitations</h3> <h3 className="text-lg font-semibold text-[#00f0ff] mb-4">Security Limitations</h3>
<div className="text-sm text-slate-300 space-y-2"> <div className="text-sm text-[#6ef3f7] space-y-2">
<SecurityWarnings /> <SecurityWarnings />
</div> </div>
</div> </div>
@@ -790,11 +922,11 @@ function App() {
onClick={() => setShowStorageModal(false)} onClick={() => setShowStorageModal(false)}
> >
<div <div
className="bg-slate-800 rounded-xl border border-slate-700 p-6 max-w-md w-full mx-4 shadow-2xl" className="bg-[#1a1a2e] rounded-xl border-2 border-[#00f0ff]/30 p-6 max-w-md w-full mx-4 shadow-[0_0_30px_rgba(0,240,255,0.3)]"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<h3 className="text-lg font-semibold text-white mb-4">Storage Details</h3> <h3 className="text-lg font-semibold text-[#00f0ff] mb-4">Storage Details</h3>
<div className="text-sm text-slate-300 space-y-2"> <div className="text-sm text-[#6ef3f7] space-y-2">
<StorageDetails localItems={localItems} sessionItems={sessionItems} /> <StorageDetails localItems={localItems} sessionItems={sessionItems} />
</div> </div>
</div> </div>
@@ -808,11 +940,11 @@ function App() {
onClick={() => setShowClipboardModal(false)} onClick={() => setShowClipboardModal(false)}
> >
<div <div
className="bg-slate-800 rounded-xl border border-slate-700 p-6 max-w-md w-full mx-4 shadow-2xl" className="bg-[#1a1a2e] rounded-xl border-2 border-[#00f0ff]/30 p-6 max-w-md w-full mx-4 shadow-[0_0_30px_rgba(0,240,255,0.3)]"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<h3 className="text-lg font-semibold text-white mb-4">Clipboard Activity</h3> <h3 className="text-lg font-semibold text-[#00f0ff] mb-4">Clipboard Activity</h3>
<div className="text-sm text-slate-300 space-y-2"> <div className="text-sm text-[#6ef3f7] space-y-2">
<ClipboardDetails events={clipboardEvents} onClear={clearClipboard} /> <ClipboardDetails events={clipboardEvents} onClear={clearClipboard} />
</div> </div>
</div> </div>
@@ -826,33 +958,33 @@ function App() {
onClick={() => setShowLockConfirm(false)} onClick={() => setShowLockConfirm(false)}
> >
<div <div
className="bg-slate-800 rounded-xl border border-slate-700 p-6 max-w-md w-full mx-4 shadow-2xl" className="bg-[#1a1a2e] rounded-xl border-2 border-[#ff006e] p-6 max-w-md w-full mx-4 shadow-[0_0_30px_rgba(255,0,110,0.3)]"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2"> <h3 className="text-lg font-semibold text-[#ff006e] mb-4 flex items-center gap-2">
<Lock className="w-5 h-5 text-amber-500" /> <Lock className="w-5 h-5 text-[#ff006e]" />
Lock Sensitive Data? Lock Sensitive Data?
</h3> </h3>
<div className="text-sm text-slate-300 space-y-3 mb-6"> <div className="text-sm text-[#6ef3f7] space-y-3 mb-6">
<p>This will:</p> <p>This will:</p>
<ul className="list-disc list-inside space-y-1 text-slate-400"> <ul className="list-disc list-inside space-y-1 text-[#9d84b7]">
<li>Blur all sensitive data (mnemonics, keys, passwords)</li> <li>Blur all sensitive data (mnemonics, keys, passwords)</li>
<li>Disable all inputs</li> <li>Disable all inputs</li>
<li>Prevent clipboard operations</li> <li>Prevent clipboard operations</li>
</ul> </ul>
<p className="text-xs text-slate-500 mt-2"> <p className="text-xs text-[#6ef3f7] mt-2">
Use this when showing the app to others or stepping away from your device. Use this when showing the app to others or stepping away from your device.
</p> </p>
</div> </div>
<div className="flex gap-3"> <div className="flex gap-3">
<button <button
className="flex-1 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg transition-colors" className="flex-1 py-2 bg-[#16213e] hover:bg-[#1a1a2e] text-[#6ef3f7] rounded-lg transition-all border-2 border-[#00f0ff]/30 hover:border-[#00f0ff]/50"
onClick={() => setShowLockConfirm(false)} onClick={() => setShowLockConfirm(false)}
> >
Cancel Cancel
</button> </button>
<button <button
className="flex-1 py-2 bg-amber-500 hover:bg-amber-600 text-white font-semibold rounded-lg transition-colors" className="flex-1 py-2 bg-[#ff006e] hover:bg-[#ff4d8f] text-white font-semibold rounded-lg transition-all hover:shadow-[0_0_15px_rgba(255,0,110,0.5)]"
onClick={confirmLock} onClick={confirmLock}
> >
Lock Data Lock Data
@@ -861,7 +993,8 @@ function App() {
</div> </div>
</div> </div>
)} )}
</div> </div> {/* Close relative z-10 content wrapper */}
</div> /* Close root min-h-screen */
); );
} }

View File

@@ -8,7 +8,7 @@ interface FooterProps {
const Footer: React.FC<FooterProps> = ({ appVersion, buildHash, buildTimestamp }) => { const Footer: React.FC<FooterProps> = ({ appVersion, buildHash, buildTimestamp }) => {
return ( return (
<footer className="text-center text-xs text-slate-400 p-4"> <footer className="text-center text-xs text-[#6ef3f7] p-4">
<p>SeedPGP v{appVersion} build {buildHash} {buildTimestamp}</p> <p>SeedPGP v{appVersion} build {buildHash} {buildTimestamp}</p>
<p className="mt-1">Never share your private keys or seed phrases. Always verify on an airgapped device.</p> <p className="mt-1">Never share your private keys or seed phrases. Always verify on an airgapped device.</p>
</footer> </footer>

View File

@@ -50,19 +50,19 @@ const Header: React.FC<HeaderProps> = ({
onToggleLock onToggleLock
}) => { }) => {
return ( return (
<header className="sticky top-0 z-50 bg-slate-900 border-b border-slate-800 backdrop-blur-sm"> <header className="sticky top-0 z-50 bg-[#0a0a0f] border-b border-[#00f0ff]/30 backdrop-blur-sm">
<div className="max-w-7xl mx-auto px-6 py-4"> <div className="max-w-7xl mx-auto px-6 py-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{/* Left: Logo & Title */} {/* Left: Logo & Title */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-10 h-10 bg-teal-500 rounded-lg flex items-center justify-center"> <div className="w-10 h-10 bg-[#00f0ff] rounded-lg flex items-center justify-center shadow-[0_0_15px_rgba(0,240,255,0.5)]">
<Shield className="w-6 h-6 text-white" /> <Shield className="w-6 h-6 text-[#0a0a0f]" />
</div> </div>
<div> <div>
<h1 className="text-lg font-semibold text-white"> <h1 className="text-lg font-semibold text-[#00f0ff]" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>
SeedPGP <span className="text-teal-400">v{appVersion}</span> SeedPGP <span className="text-[#ff006e]">v{appVersion}</span>
</h1> </h1>
<p className="text-xs text-slate-400">OpenPGP-secured BIP39 backup</p> <p className="text-xs text-[#6ef3f7]">OpenPGP-secured BIP39 backup</p>
</div> </div>
</div> </div>
@@ -83,35 +83,36 @@ const Header: React.FC<HeaderProps> = ({
{encryptedMnemonicCache && ( {encryptedMnemonicCache && (
<button <button
onClick={handleLockAndClear} onClick={handleLockAndClear}
className="flex items-center gap-2 text-sm text-red-400 bg-slate-800/50 px-3 py-1.5 rounded-lg hover:bg-red-900/50 transition-colors" className="flex items-center gap-2 text-sm text-[#ff006e] bg-[#16213e] px-3 py-1.5 rounded-lg hover:bg-[#ff006e]/20 border-2 border-[#ff006e]/50 transition-all hover:shadow-[0_0_15px_rgba(255,0,110,0.5)]"
> >
<Lock size={16} /> <Lock size={16} />
<span>Lock/Clear</span> <span>Lock/Clear</span>
</button> </button>
)} )}
<button <button
className={`px-4 py-2 rounded-lg ${activeTab === 'backup' ? 'bg-teal-500 hover:bg-teal-600' : 'bg-slate-700 hover:bg-slate-600'}`} className={`px-4 py-2 rounded-lg font-medium ${activeTab === 'backup' ? 'bg-[#1a1a2e] text-[#00f0ff] border-b-2 border-[#ff006e] shadow-[0_0_15px_rgba(255,0,110,0.5)] relative' : 'bg-[#0a0a0f] text-[#9d84b7] hover:text-[#6ef3f7] hover:bg-[#16213e] transition-all'}`}
style={activeTab === 'backup' ? { textShadow: '0 0 10px rgba(0,240,255,0.8)' } : undefined}
onClick={() => onRequestTabChange('backup')} onClick={() => onRequestTabChange('backup')}
> >
Backup Backup
</button> </button> <button
<button className={`px-4 py-2 rounded-lg font-medium ${activeTab === 'restore' ? 'bg-[#1a1a2e] text-[#00f0ff] border-b-2 border-[#ff006e] shadow-[0_0_15px_rgba(255,0,110,0.5)] relative' : 'bg-[#0a0a0f] text-[#9d84b7] hover:text-[#6ef3f7] hover:bg-[#16213e] transition-all'}`}
className={`px-4 py-2 rounded-lg ${activeTab === 'restore' ? 'bg-teal-500 hover:bg-teal-600' : 'bg-slate-700 hover:bg-slate-600'}`} style={activeTab === 'restore' ? { textShadow: '0 0 10px rgba(0,240,255,0.8)' } : undefined}
onClick={() => onRequestTabChange('restore')} onClick={() => onRequestTabChange('restore')}
> >
Restore Restore
</button> </button>
<button <button
className={`px-4 py-2 rounded-lg ${activeTab === 'seedblender' ? 'bg-teal-500 hover:bg-teal-600' : 'bg-slate-700 hover:bg-slate-600'}`} className={`px-4 py-2 rounded-lg font-medium ${activeTab === 'seedblender' ? 'bg-[#1a1a2e] text-[#00f0ff] border-b-2 border-[#ff006e] shadow-[0_0_15px_rgba(255,0,110,0.5)] relative' : 'bg-[#0a0a0f] text-[#9d84b7] hover:text-[#6ef3f7] hover:bg-[#16213e] transition-all'}`}
style={activeTab === 'seedblender' ? { textShadow: '0 0 10px rgba(0,240,255,0.8)' } : undefined}
onClick={() => onRequestTabChange('seedblender')} onClick={() => onRequestTabChange('seedblender')}
> >
Seed Blender Seed Blender
</button> </button> </div>
</div>
</div> </div>
{/* Mobile: Stack monitoring badges */} {/* Mobile: Stack monitoring badges */}
<div className="md:hidden flex items-center gap-3 mt-3 pt-3 border-t border-slate-800"> <div className="md:hidden flex items-center gap-3 mt-3 pt-3 border-t border-[#00f0ff]/30">
<SecurityBadge onClick={onOpenSecurityModal} /> <SecurityBadge onClick={onOpenSecurityModal} />
<div onClick={onOpenStorageModal} className="cursor-pointer"> <div onClick={onOpenStorageModal} className="cursor-pointer">
<StorageBadge localItems={localItems} sessionItems={sessionItems} /> <StorageBadge localItems={localItems} sessionItems={sessionItems} />

View File

@@ -52,12 +52,12 @@ export const PgpKeyInput: React.FC<PgpKeyInputProps> = ({
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-semibold text-slate-200 flex items-center justify-between"> <label className="text-sm font-semibold text-[#00f0ff] flex items-center justify-between" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
{Icon && <Icon size={14} />} {label} {Icon && <Icon size={14} />} {label}
</span> </span>
{!readOnly && ( {!readOnly && (
<span className="text-[10px] text-slate-400 font-normal bg-slate-100 px-2 py-0.5 rounded-full border border-slate-200"> <span className="text-[10px] text-[#6ef3f7] font-normal bg-[#16213e] px-2 py-0.5 rounded-full border border-[#00f0ff]/30">
Drag & Drop .asc file Drag & Drop .asc file
</span> </span>
)} )}
@@ -69,7 +69,7 @@ export const PgpKeyInput: React.FC<PgpKeyInputProps> = ({
onDrop={handleDrop} onDrop={handleDrop}
> >
<textarea <textarea
className={`w-full h-40 p-3 bg-slate-50 border rounded-xl text-xs font-mono transition-colors resize-none focus:outline-none focus:ring-2 focus:ring-teal-500 ${isDragging && !readOnly ? 'border-teal-500 bg-teal-50' : 'border-slate-200'} ${ className={`w-full h-40 p-3 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-xl text-xs font-mono text-[#00f0ff] placeholder-[#9d84b7] transition-colors resize-none focus:outline-none focus:border-[#ff006e] focus:shadow-[0_0_20px_rgba(255,0,110,0.5)] ${isDragging && !readOnly ? 'border-[#ff006e] bg-[#16213e]' : 'border-[#00f0ff]/50'} ${
readOnly ? 'blur-sm select-none' : '' readOnly ? 'blur-sm select-none' : ''
}`} }`}
placeholder={placeholder} placeholder={placeholder}
@@ -78,8 +78,8 @@ export const PgpKeyInput: React.FC<PgpKeyInputProps> = ({
readOnly={readOnly} readOnly={readOnly}
/> />
{isDragging && !readOnly && ( {isDragging && !readOnly && (
<div className="absolute inset-0 flex items-center justify-center bg-teal-50/90 rounded-xl border-2 border-dashed border-teal-500 pointer-events-none z-10"> <div className="absolute inset-0 flex items-center justify-center bg-[#16213e]/90 rounded-xl border-2 border-dashed border-[#ff006e] pointer-events-none z-10">
<div className="text-teal-600 font-bold flex flex-col items-center animate-bounce"> <div className="text-[#ff006e] font-bold flex flex-col items-center animate-bounce" style={{ textShadow: '0 0 10px rgba(255,0,110,0.5)' }}>
<Upload size={24} /> <Upload size={24} />
<span className="text-sm mt-2">Drop Key File Here</span> <span className="text-sm mt-2">Drop Key File Here</span>
</div> </div>

View File

@@ -128,43 +128,43 @@ export default function QRScanner({ onScanSuccess, onClose, onError }: QRScanner
return ( return (
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center z-50">
<div className="bg-slate-800 rounded-xl border border-slate-700 p-6 max-w-md w-full mx-4 shadow-2xl"> <div className="bg-[#16213e] rounded-xl border-2 border-[#00f0ff]/50 p-6 max-w-md w-full mx-4 shadow-[0_0_40px_rgba(0,240,255,0.3)]">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-white flex items-center gap-2"> <h3 className="text-lg font-semibold text-[#00f0ff] flex items-center gap-2" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>
<Camera size={20} /> <Camera size={20} />
Scan QR Code Scan QR Code
</h3> </h3>
<button onClick={onClose} className="p-2 hover:bg-slate-700 rounded-lg transition-colors"> <button onClick={onClose} className="p-2 hover:bg-[#1a1a2e] rounded-lg transition-colors border-2 border-[#00f0ff]/30">
<X size={20} className="text-slate-400" /> <X size={20} className="text-[#6ef3f7]" />
</button> </button>
</div> </div>
{internalError && ( {internalError && (
<div className="mb-4 p-3 bg-red-500/10 border border-red-500/50 rounded-lg flex items-start gap-2 text-red-400 text-sm"> <div className="mb-4 p-3 bg-[#ff006e]/10 border-2 border-[#ff006e]/30 rounded-lg flex items-start gap-2 text-[#ff006e] text-sm">
<AlertCircle size={16} className="shrink-0 mt-0.5" /> <AlertCircle size={16} className="shrink-0 mt-0.5" />
<span>{internalError}</span> <span>{internalError}</span>
</div> </div>
)} )}
{success && ( {success && (
<div className="mb-4 p-3 bg-green-500/10 border border-green-500/50 rounded-lg flex items-center gap-2 text-green-400 text-sm"> <div className="mb-4 p-3 bg-[#39ff14]/10 border-2 border-[#39ff14]/30 rounded-lg flex items-center gap-2 text-[#39ff14] text-sm">
<CheckCircle2 size={16} /> <CheckCircle2 size={16} />
<span>QR Code detected!</span> <span>QR Code detected!</span>
</div> </div>
)} )}
<div className="relative bg-black rounded-lg overflow-hidden"> <div className="relative bg-black rounded-lg overflow-hidden border-2 border-[#00f0ff]/30">
<video ref={videoRef} className="w-full h-64 object-cover" playsInline muted /> <video ref={videoRef} className="w-full h-64 object-cover" playsInline muted />
<canvas ref={canvasRef} className="hidden" /> <canvas ref={canvasRef} className="hidden" />
</div> </div>
{!hasPermission && !internalError && ( {!hasPermission && !internalError && (
<p className="text-sm text-slate-400 mt-3 text-center">Requesting camera access...</p> <p className="text-sm text-[#6ef3f7] mt-3 text-center">Requesting camera access...</p>
)} )}
<button <button
onClick={onClose} onClick={onClose}
className="w-full mt-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg text-white font-medium transition-colors" className="w-full mt-4 py-2 bg-[#1a1a2e] hover:bg-[#16213e] rounded-lg text-[#00f0ff] font-medium transition-all border-2 border-[#00f0ff]/50 hover:shadow-[0_0_15px_rgba(0,240,255,0.3)]"
> >
Cancel Cancel
</button> </button>

View File

@@ -93,26 +93,26 @@ export const QrDisplay: React.FC<QrDisplayProps> = ({ value }) => {
if (!dataUrl) return null; if (!dataUrl) return null;
return ( return (
<div className="space-y-4"> <div className="border-4 border-[#00f0ff] rounded-xl shadow-[0_0_40px_rgba(0,240,255,0.6)] p-4 bg-[#0a0a0f] space-y-4">
<div className="bg-white p-6 rounded-lg inline-block shadow-lg"> <div className="bg-[#16213e] p-6 rounded-lg inline-block shadow-[0_0_20px_rgba(0,240,255,0.3)] border-2 border-[#00f0ff]/30">
<img src={dataUrl} alt="QR Code" className="w-full h-auto" /> <img src={dataUrl} alt="QR Code" className="w-full h-auto" />
</div> </div>
{debugInfo && ( {debugInfo && (
<div className="text-xs text-slate-500 font-mono"> <div className="text-xs text-[#6ef3f7] font-mono">
{debugInfo} {debugInfo}
</div> </div>
)} )}
<button <button
onClick={handleDownload} onClick={handleDownload}
className="flex items-center gap-2 px-4 py-2 bg-teal-600 hover:bg-teal-700 text-white rounded-lg transition-colors" className="flex items-center gap-2 px-4 py-2 bg-[#00f0ff] hover:bg-[#00f0ff]/80 text-[#0a0a0f] rounded-lg transition-all hover:shadow-[0_0_15px_rgba(0,240,255,0.5)]"
> >
<Download size={16} /> <Download size={16} />
Download QR Code Download QR Code
</button> </button>
<p className="text-xs text-slate-500"> <p className="text-xs text-[#6ef3f7]">
Downloads as: SeedPGP_{new Date().toISOString().split('T')[0]}_HHMMSS.png Downloads as: SeedPGP_{new Date().toISOString().split('T')[0]}_HHMMSS.png
</p> </p>
</div> </div>

View File

@@ -11,26 +11,26 @@ const CSP_POLICY = `default-src 'self'; script-src 'self'; style-src 'self' 'uns
export function ReadOnly({ isReadOnly, onToggle, buildHash, appVersion }: ReadOnlyProps) { export function ReadOnly({ isReadOnly, onToggle, buildHash, appVersion }: ReadOnlyProps) {
return ( return (
<div className="pt-3 border-t border-slate-300"> <div className="pt-3 border-t border-[#00f0ff]/30">
<label className="flex items-center gap-2 cursor-pointer group"> <label className="flex items-center gap-2 cursor-pointer group">
<input <input
type="checkbox" type="checkbox"
checked={isReadOnly} checked={isReadOnly}
onChange={(e) => onToggle(e.target.checked)} onChange={(e) => onToggle(e.target.checked)}
className="rounded text-teal-600 focus:ring-2 focus:ring-teal-500 transition-all" className="rounded text-[#00f0ff] focus:ring-2 focus:ring-[#00f0ff] transition-all"
/> />
<span className="text-xs font-medium text-slate-700 group-hover:text-slate-900 transition-colors"> <span className="text-xs font-medium text-[#6ef3f7] group-hover:text-[#00f0ff] transition-colors">
Read-only Mode Read-only Mode
</span> </span>
</label> </label>
{isReadOnly && ( {isReadOnly && (
<div className="mt-4 p-3 bg-slate-800 text-slate-200 rounded-lg text-xs space-y-2 animate-in fade-in"> <div className="mt-4 p-3 bg-[#16213e] text-[#6ef3f7] rounded-lg text-xs space-y-2 animate-in fade-in border-2 border-[#00f0ff]/30">
<p className="font-bold flex items-center gap-2"><WifiOff size={14} /> Network & Persistence Disabled</p> <p className="font-bold flex items-center gap-2"><WifiOff size={14} /> Network & Persistence Disabled</p>
<div className="font-mono text-[10px] space-y-1"> <div className="font-mono text-[10px] space-y-1">
<p><span className="font-semibold text-slate-400">Version:</span> {appVersion}</p> <p><span className="font-semibold text-[#9d84b7]">Version:</span> {appVersion}</p>
<p><span className="font-semibold text-slate-400">Build:</span> {buildHash}</p> <p><span className="font-semibold text-[#9d84b7]">Build:</span> {buildHash}</p>
<p className="pt-1 font-semibold text-slate-400">Content Security Policy:</p> <p className="pt-1 font-semibold text-[#9d84b7]">Content Security Policy:</p>
<p className="text-sky-300 break-words">{CSP_POLICY}</p> <p className="text-[#00f0ff] break-words">{CSP_POLICY}</p>
</div> </div>
</div> </div>
)} )}

View File

@@ -39,8 +39,8 @@ export const SecurityWarnings: React.FC = () => {
description="If hosted online: DNS, HTTPS, CDN, and browser can see usage patterns. Use offline/local for maximum security." description="If hosted online: DNS, HTTPS, CDN, and browser can see usage patterns. Use offline/local for maximum security."
/> />
<div className="pt-3 border-t border-slate-600 text-xs text-slate-400"> <div className="pt-3 border-t border-[#00f0ff]/30 text-xs text-[#6ef3f7]">
<strong className="text-slate-300">Recommendation:</strong>{' '} <strong className="text-[#00f0ff]">Recommendation:</strong>{' '}
Use this tool on a dedicated offline device. Clear browser data after each use. Never use on shared/public computers. Use this tool on a dedicated offline device. Clear browser data after each use. Never use on shared/public computers.
</div> </div>
</div> </div>
@@ -59,8 +59,8 @@ const Warning = ({
<div className="flex gap-2 text-sm"> <div className="flex gap-2 text-sm">
<span className="text-lg flex-shrink-0">{icon}</span> <span className="text-lg flex-shrink-0">{icon}</span>
<div> <div>
<div className="font-semibold text-slate-200 mb-1">{title}</div> <div className="font-semibold text-[#00f0ff] mb-1" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>{title}</div>
<div className="text-slate-400 leading-relaxed">{description}</div> <div className="text-[#6ef3f7] leading-relaxed">{description}</div>
</div> </div>
</div> </div>
); );

View File

@@ -253,82 +253,82 @@ export function SeedBlender({ onDirtyStateChange, setMnemonicForBackup, requestT
}; };
const getBorderColor = (isValid: boolean | null) => { const getBorderColor = (isValid: boolean | null) => {
if (isValid === true) return 'border-green-500 focus:ring-green-500'; if (isValid === true) return 'border-[#39ff14] focus:ring-[#39ff14]';
if (isValid === false) return 'border-red-500 focus:ring-red-500'; if (isValid === false) return 'border-[#ff006e] focus:ring-[#ff006e]';
return 'border-slate-200 focus:ring-teal-500'; return 'border-[#00f0ff]/50 focus:ring-[#00f0ff]';
}; };
return ( return (
<> <>
<div className="space-y-6 pb-20"> <div className="space-y-6 pb-20">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-slate-100">Seed Blender</h2> <h2 className="text-2xl font-bold text-[#00f0ff]" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>Seed Blender</h2>
<button onClick={handleLockAndClear} className="flex items-center gap-2 text-sm text-red-400 bg-slate-800/50 px-3 py-1.5 rounded-lg hover:bg-red-900/50"><Lock size={16} /><span>Lock/Clear</span></button> <button onClick={handleLockAndClear} className="flex items-center gap-2 text-sm text-[#ff006e] bg-[#16213e] px-3 py-1.5 rounded-lg hover:bg-[#ff006e]/20 border-2 border-[#ff006e]/50"><Lock size={16} /><span>Lock/Clear</span></button>
</div> </div>
<div className="p-6 bg-slate-700/50 rounded-xl border border-slate-600"> <div className="p-6 bg-[#16213e] rounded-xl border-2 border-[#00f0ff]/30">
<h3 className="font-semibold text-lg mb-4 text-slate-200">Step 1: Input Mnemonics</h3> <h3 className="font-semibold text-lg mb-4 text-[#00f0ff]" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>Step 1: Input Mnemonics</h3>
<div className="space-y-4"> <div className="space-y-4">
{entries.map((entry, index) => ( {entries.map((entry, index) => (
<div key={entry.id} className="p-3 bg-slate-800/50 rounded-lg"> <div key={entry.id} className="p-3 bg-[#1a1a2e] rounded-lg border-2 border-[#00f0ff]/20">
{entry.passwordRequired ? ( {entry.passwordRequired ? (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"><label className="text-sm font-semibold text-slate-200">Decrypt {entry.inputType.toUpperCase()} Mnemonic</label><button onClick={() => updateEntry(index, createNewEntry())} className="text-xs text-slate-400 hover:text-white">&times; Cancel</button></div> <div className="flex items-center justify-between"><label className="text-sm font-semibold text-[#00f0ff]">Decrypt {entry.inputType.toUpperCase()} Mnemonic</label><button onClick={() => updateEntry(index, createNewEntry())} className="text-xs text-[#6ef3f7] hover:text-[#00f0ff]">&times; Cancel</button></div>
<p className="text-xs text-slate-400 truncate">Payload: <code className="text-slate-300">{entry.rawInput.substring(0, 40)}...</code></p> <p className="text-xs text-[#6ef3f7] truncate">Payload: <code className="text-[#9d84b7]">{entry.rawInput.substring(0, 40)}...</code></p>
<div className="flex gap-2"><input type="password" placeholder="Enter passphrase to decrypt..." value={entry.passwordInput} onChange={(e) => updateEntry(index, { passwordInput: e.target.value })} className="w-full p-2 bg-slate-50 border-2 border-slate-200 rounded-lg text-sm font-mono text-slate-900 focus:outline-none focus:ring-2 focus:ring-purple-500" /><button onClick={() => handleDecrypt(index)} className="px-4 bg-purple-600 text-white rounded-lg font-semibold hover:bg-purple-700"><Key size={16}/></button></div> <div className="flex gap-2"><input type="password" placeholder="Enter passphrase to decrypt..." value={entry.passwordInput} onChange={(e) => updateEntry(index, { passwordInput: e.target.value })} className="w-full p-2 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-lg text-sm font-mono text-[#00f0ff] placeholder-[#9d84b7] focus:outline-none focus:border-[#ff006e] focus:shadow-[0_0_20px_rgba(255,0,110,0.5)]" /><button onClick={() => handleDecrypt(index)} className="px-4 bg-[#ff006e] text-white rounded-lg font-semibold hover:bg-[#ff4d8f] hover:shadow-[0_0_15px_rgba(255,0,110,0.5)]"><Key size={16}/></button></div>
{entry.error && <p className="text-xs text-red-400">{entry.error}</p>} {entry.error && <p className="text-xs text-[#ff006e]">{entry.error}</p>}
</div> </div>
) : ( ) : (
<div className="space-y-1"> <div className="space-y-1">
<div className="flex flex-col sm:flex-row items-start gap-2"> <div className="flex flex-col sm:flex-row items-start gap-2">
<div className="relative w-full"> <div className="relative w-full">
<textarea value={entry.rawInput} onChange={(e) => updateEntry(index, { rawInput: e.target.value, decryptedMnemonic: e.target.value, isValid: null, error: null })} placeholder={`Mnemonic #${index + 1} (12 or 24 words)`} className={`w-full h-28 sm:h-24 p-3 pr-10 bg-slate-50 border-2 rounded-lg text-sm font-mono text-slate-900 placeholder:text-slate-400 ${getBorderColor(entry.isValid)}`} /> <textarea value={entry.rawInput} onChange={(e) => updateEntry(index, { rawInput: e.target.value, decryptedMnemonic: e.target.value, isValid: null, error: null })} placeholder={`Mnemonic #${index + 1} (12 or 24 words)`} className={`w-full h-28 sm:h-24 p-3 pr-10 bg-[#16213e] border-2 rounded-lg text-sm font-mono text-[#00f0ff] placeholder-[#9d84b7] ${getBorderColor(entry.isValid)}`} />
{entry.isValid === true && <CheckCircle2 className="absolute top-3 right-3 text-green-500" />} {entry.isValid === true && <CheckCircle2 className="absolute top-3 right-3 text-[#39ff14]" />}
{entry.isValid === false && <AlertTriangle className="absolute top-3 right-3 text-red-500" />} {entry.isValid === false && <AlertTriangle className="absolute top-3 right-3 text-[#ff006e]" />}
</div> </div>
<div className="flex items-center gap-2 shrink-0"> <div className="flex items-center gap-2 shrink-0">
<button onClick={() => handleScan(index)} className="p-3 h-full bg-purple-600/20 text-purple-300 hover:bg-purple-600/50 hover:text-white rounded-md"><QrCode size={20} /></button> <button onClick={() => handleScan(index)} className="p-3 h-full bg-[#ff006e]/20 text-[#ff006e] hover:bg-[#ff006e]/50 hover:text-white rounded-md border-2 border-[#ff006e]/30"><QrCode size={20} /></button>
<button onClick={() => handleRemoveEntry(entry.id)} className="p-3 h-full bg-red-600/20 text-red-400 hover:bg-red-600/50 hover:text-white rounded-md"><X size={20} /></button> <button onClick={() => handleRemoveEntry(entry.id)} className="p-3 h-full bg-[#ff006e]/20 text-[#ff006e] hover:bg-[#ff006e]/50 hover:text-white rounded-md border-2 border-[#ff006e]/30"><X size={20} /></button>
</div> </div>
</div> </div>
{entry.error && <p className="text-xs text-red-400 px-1">{entry.error}</p>} {entry.error && <p className="text-xs text-[#ff006e] px-1">{entry.error}</p>}
</div> </div>
)} )}
</div> </div>
))} ))}
<button onClick={handleAddEntry} className="w-full py-2.5 bg-slate-600/70 hover:bg-slate-600 rounded-lg font-semibold flex items-center justify-center gap-2"><Plus size={16} /> Add Another Mnemonic</button> <button onClick={handleAddEntry} className="w-full py-2.5 bg-[#1a1a2e] hover:bg-[#16213e] text-[#00f0ff] rounded-lg font-semibold flex items-center justify-center gap-2 border-2 border-[#00f0ff]/50"><Plus size={16} /> Add Another Mnemonic</button>
</div> </div>
</div> </div>
<div className="p-6 bg-slate-700/50 rounded-xl border border-slate-600 min-h-[10rem]"> <div className="p-6 bg-[#16213e] rounded-xl border-2 border-[#00f0ff]/30 min-h-[10rem]">
<h3 className="font-semibold text-lg mb-4 text-slate-200">Step 2: Blended Preview</h3> <h3 className="font-semibold text-lg mb-4 text-[#00f0ff]" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>Step 2: Blended Preview</h3>
{blending ? <p className="text-sm text-slate-400">Blending...</p> : !blendError && blendedResult ? (<div className="space-y-4 animate-in fade-in">{xorStrength?.isWeak && (<div className="p-3 bg-amber-500/10 border border-amber-500/30 text-amber-300 rounded-lg text-sm flex gap-3"><AlertTriangle /><div><span className="font-bold">Weak XOR Result:</span> Detected only {xorStrength.uniqueBytes} unique bytes. This can happen if seeds are identical or too similar.</div></div>)}<div className="space-y-1"><label className="text-xs font-semibold text-slate-400">Blended Mnemonic (12-word)</label><p data-sensitive="Blended Mnemonic (12-word)" className="p-3 bg-slate-800 rounded-md font-mono text-sm text-slate-100 break-words">{blendedResult.blendedMnemonic12}</p></div>{blendedResult.blendedMnemonic24 && (<div className="space-y-1"><label className="text-xs font-semibold text-slate-400">Blended Mnemonic (24-word)</label><p data-sensitive="Blended Mnemonic (24-word)" className="p-3 bg-slate-800 rounded-md font-mono text-sm text-slate-100 break-words">{blendedResult.blendedMnemonic24}</p></div>)}</div>) : (<p className="text-sm text-slate-400">{blendError || 'Previews will appear here once you enter one or more valid mnemonics.'}</p>)} {blending ? <p className="text-sm text-[#6ef3f7]">Blending...</p> : !blendError && blendedResult ? (<div className="space-y-4 animate-in fade-in">{xorStrength?.isWeak && (<div className="p-3 bg-[#ff006e]/10 border-2 border-[#ff006e]/30 text-[#ff006e] rounded-lg text-sm flex gap-3"><AlertTriangle /><div><span className="font-bold">Weak XOR Result:</span> Detected only {xorStrength.uniqueBytes} unique bytes. This can happen if seeds are identical or too similar.</div></div>)}<div className="space-y-1"><label className="text-xs font-semibold text-[#00f0ff] uppercase tracking-widest" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>Blended Mnemonic (12-word)</label><p data-sensitive="Blended Mnemonic (12-word)" className="p-3 bg-[#1a1a2e] rounded-md font-mono text-sm text-[#00f0ff] break-words border-2 border-[#00f0ff]/30">{blendedResult.blendedMnemonic12}</p></div>{blendedResult.blendedMnemonic24 && (<div className="space-y-1"><label className="text-xs font-semibold text-[#00f0ff] uppercase tracking-widest" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>Blended Mnemonic (24-word)</label><p data-sensitive="Blended Mnemonic (24-word)" className="p-3 bg-[#1a1a2e] rounded-md font-mono text-sm text-[#00f0ff] break-words border-2 border-[#00f0ff]/30">{blendedResult.blendedMnemonic24}</p></div>)}</div>) : (<p className="text-sm text-[#6ef3f7]">{blendError || 'Previews will appear here once you enter one or more valid mnemonics.'}</p>)}
</div> </div>
<div className="p-6 bg-slate-700/50 rounded-xl border border-slate-600"> <div className="p-6 bg-[#16213e] rounded-xl border-2 border-[#00f0ff]/30">
<h3 className="font-semibold text-lg mb-4 text-slate-200">Step 3: Input Dice Rolls</h3> <h3 className="font-semibold text-lg mb-4 text-[#00f0ff]" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>Step 3: Input Dice Rolls</h3>
<div className="space-y-4"> <div className="space-y-4">
<textarea value={diceRolls} onChange={(e) => setDiceRolls(e.target.value.replace(/[^1-6]/g, ''))} placeholder="Enter 99+ dice rolls (e.g., 16345...)" className="w-full h-32 p-3 bg-slate-50 border-2 border-slate-200 rounded-lg text-lg font-mono text-slate-900 placeholder:text-slate-400" /> <textarea value={diceRolls} onChange={(e) => setDiceRolls(e.target.value.replace(/[^1-6]/g, ''))} placeholder="Enter 99+ dice rolls (e.g., 16345...)" className="w-full h-32 p-3 bg-[#16213e] border-2 border-[#00f0ff]/50 rounded-lg text-lg font-mono text-[#00f0ff] placeholder-[#9d84b7]" />
{dicePatternWarning && (<div className="p-3 bg-amber-500/10 border border-amber-500/30 text-amber-300 rounded-lg text-sm flex gap-3"><AlertTriangle /><p><span className="font-bold">Warning:</span> {dicePatternWarning}</p></div>)} {dicePatternWarning && (<div className="p-3 bg-[#ff006e]/10 border-2 border-[#ff006e]/30 text-[#ff006e] rounded-lg text-sm flex gap-3"><AlertTriangle /><p><span className="font-bold">Warning:</span> {dicePatternWarning}</p></div>)}
{diceStats && diceStats.length > 0 && (<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-center"><div className="p-3 bg-slate-800 rounded-lg"><p className="text-xs text-slate-400">Rolls</p><p className="text-lg font-bold">{diceStats.length}</p></div><div className="p-3 bg-slate-800 rounded-lg"><p className="text-xs text-slate-400">Entropy (bits)</p><p className="text-lg font-bold">{diceStats.estimatedEntropyBits.toFixed(1)}</p></div><div className="p-3 bg-slate-800 rounded-lg"><p className="text-xs text-slate-400">Mean</p><p className="text-lg font-bold">{diceStats.mean.toFixed(2)}</p></div><div className="p-3 bg-slate-800 rounded-lg"><p className="text-xs text-slate-400">Chi-Square</p><p className={`text-lg font-bold ${diceStats.chiSquare > 11.07 ? 'text-amber-400' : ''}`}>{diceStats.chiSquare.toFixed(2)}</p></div></div>)} {diceStats && diceStats.length > 0 && (<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-center"><div className="p-3 bg-[#1a1a2e] rounded-lg border-2 border-[#00f0ff]/30"><p className="text-xs text-[#6ef3f7]">Rolls</p><p className="text-lg font-bold text-[#00f0ff]">{diceStats.length}</p></div><div className="p-3 bg-[#1a1a2e] rounded-lg border-2 border-[#00f0ff]/30"><p className="text-xs text-[#6ef3f7]">Entropy (bits)</p><p className="text-lg font-bold text-[#00f0ff]">{diceStats.estimatedEntropyBits.toFixed(1)}</p></div><div className="p-3 bg-[#1a1a2e] rounded-lg border-2 border-[#00f0ff]/30"><p className="text-xs text-[#6ef3f7]">Mean</p><p className="text-lg font-bold text-[#00f0ff]">{diceStats.mean.toFixed(2)}</p></div><div className="p-3 bg-[#1a1a2e] rounded-lg border-2 border-[#00f0ff]/30"><p className="text-xs text-[#6ef3f7]">Chi-Square</p><p className={`text-lg font-bold ${diceStats.chiSquare > 11.07 ? 'text-[#ff006e]' : 'text-[#00f0ff]'}`}>{diceStats.chiSquare.toFixed(2)}</p></div></div>)}
{diceOnlyMnemonic && (<div className="space-y-1 pt-2"><label className="text-xs font-semibold text-slate-400">Dice-Only Preview Mnemonic</label><p data-sensitive="Dice-Only Preview Mnemonic" className="p-3 bg-slate-800 rounded-md font-mono text-sm text-slate-100 break-words">{diceOnlyMnemonic}</p></div>)} {diceOnlyMnemonic && (<div className="space-y-1 pt-2"><label className="text-xs font-semibold text-[#00f0ff] uppercase tracking-widest" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>Dice-Only Preview Mnemonic</label><p data-sensitive="Dice-Only Preview Mnemonic" className="p-3 bg-[#1a1a2e] rounded-md font-mono text-sm text-[#00f0ff] break-words border-2 border-[#00f0ff]/30">{diceOnlyMnemonic}</p></div>)}
</div> </div>
</div> </div>
<div className="p-6 bg-slate-900/70 rounded-xl border-2 border-teal-500/50 shadow-lg"> <div className="p-6 bg-[#16213e] rounded-xl border-2 border-[#00f0ff]/50 shadow-[0_0_20px_rgba(0,240,255,0.3)]">
<h3 className="font-semibold text-lg mb-4 text-slate-200">Step 4: Generate Final Mnemonic</h3> <h3 className="font-semibold text-lg mb-4 text-[#00f0ff]" style={{ textShadow: '0 0 10px rgba(0,240,255,0.7)' }}>Step 4: Generate Final Mnemonic</h3>
{finalMnemonic ? ( {finalMnemonic ? (
<div className="p-4 bg-gradient-to-br from-green-50 to-emerald-50 border-2 border-green-300 rounded-2xl shadow-lg"> <div className="p-4 bg-[#0a0a0f] border-2 border-[#39ff14] rounded-2xl shadow-[0_0_20px_rgba(57,255,20,0.3)]">
<div className="flex items-center justify-between mb-4"><span className="font-bold text-green-700 flex items-center gap-2 text-lg"><CheckCircle2 size={22} /> Final Mnemonic Generated</span><button onClick={() => setFinalMnemonic(null)} className="p-2.5 hover:bg-green-100 rounded-xl"><EyeOff size={22} /></button></div> <div className="flex items-center justify-between mb-4"><span className="font-bold text-[#39ff14] flex items-center gap-2 text-lg" style={{ textShadow: '0 0 10px rgba(57,255,20,0.8)' }}><CheckCircle2 size={22} /> Final Mnemonic Generated</span><button onClick={() => setFinalMnemonic(null)} className="p-2.5 hover:bg-[#16213e] rounded-xl transition-all text-[#39ff14] hover:shadow-[0_0_15px_rgba(57,255,20,0.5)] flex items-center gap-2"><EyeOff size={22} /> Hide</button></div>
<div className="p-6 bg-white rounded-xl border-2 border-green-200 shadow-sm"><p data-sensitive="Final Blended Mnemonic" className="font-mono text-center text-lg text-slate-800 break-words">{finalMnemonic}</p></div> <div className="p-6 bg-[#0a0a0f] rounded-xl border-2 border-[#39ff14] shadow-[0_0_20px_rgba(57,255,20,0.3)]"><p data-sensitive="Final Blended Mnemonic" className="font-mono text-center text-lg break-words text-[#39ff14]">{finalMnemonic}</p></div>
<div className="mt-4 p-3 bg-red-500/10 text-red-300 rounded-lg text-xs flex gap-2"><AlertTriangle size={16} className="shrink-0 mt-0.5" /><span><strong>Security Warning:</strong> Write this down immediately. Do not save it digitally.</span></div> <div className="mt-4 p-3 bg-[#ff006e]/10 text-[#ff006e] rounded-lg text-xs flex gap-2 border-2 border-[#ff006e]/30"><AlertTriangle size={16} className="shrink-0 mt-0.5" /><span><strong>Security Warning:</strong> Write this down immediately. Do not save it digitally.</span></div>
<div className="grid grid-cols-2 gap-3 mt-4"> <div className="grid grid-cols-2 gap-3 mt-4">
<button onClick={() => setShowFinalQR(true)} className="w-full py-2.5 bg-slate-700 text-white rounded-lg font-semibold flex items-center justify-center gap-2"><QrCode size={16}/> Export as QR</button> <button onClick={() => setShowFinalQR(true)} className="w-full py-2.5 bg-[#1a1a2e] text-[#00f0ff] rounded-lg font-semibold flex items-center justify-center gap-2 border-2 border-[#00f0ff]/50 hover:bg-[#16213e] hover:shadow-[0_0_15px_rgba(0,240,255,0.3)]"><QrCode size={16}/> Export as QR</button>
<button onClick={handleTransfer} className="w-full py-2.5 bg-teal-600 text-white rounded-lg font-semibold flex items-center justify-center gap-2"><ArrowRight size={16}/> Transfer to Backup</button> <button onClick={handleTransfer} className="w-full py-2.5 bg-[#00f0ff] text-[#16213e] rounded-lg font-semibold flex items-center justify-center gap-2 border-2 border-[#00f0ff] hover:bg-[#00f0ff]/80 hover:shadow-[0_0_15px_rgba(0,240,255,0.5)]"><ArrowRight size={16}/> Transfer to Backup</button>
</div> </div>
</div> </div>
) : ( ) : (
<><p className="text-sm text-slate-400 mb-4">Once you have entered valid mnemonics and at least 50 dice rolls, you can generate the final mnemonic.</p><button onClick={handleFinalMix} disabled={!blendedResult || !diceRolls || diceRolls.length < 50 || mixing} className="w-full py-3 bg-gradient-to-r from-teal-500 to-cyan-600 text-white rounded-xl font-bold flex items-center justify-center gap-2 disabled:opacity-50">{mixing ? <RefreshCw className="animate-spin" size={20} /> : <Sparkles size={20} />}{mixing ? 'Generating...' : 'Mix Mnemonic + Dice'}</button></> <><p className="text-sm text-[#6ef3f7] mb-4">Once you have entered valid mnemonics and at least 50 dice rolls, you can generate the final mnemonic.</p><button onClick={handleFinalMix} disabled={!blendedResult || !diceRolls || diceRolls.length < 50 || mixing} className="w-full py-3 bg-gradient-to-r from-[#00f0ff] to-[#0066ff] text-[#16213e] rounded-xl font-bold flex items-center justify-center gap-2 disabled:opacity-50 hover:shadow-[0_0_20px_rgba(0,240,255,0.5)]">{mixing ? <RefreshCw className="animate-spin" size={20} /> : <Sparkles size={20} />}{mixing ? 'Generating...' : 'Mix Mnemonic + Dice'}</button></>
)} )}
</div> </div>
</div> </div>
@@ -340,7 +340,7 @@ export function SeedBlender({ onDirtyStateChange, setMnemonicForBackup, requestT
/>} />}
{showFinalQR && finalMnemonic && ( {showFinalQR && finalMnemonic && (
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center" onClick={() => setShowFinalQR(false)}> <div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center" onClick={() => setShowFinalQR(false)}>
<div className="bg-white rounded-2xl p-4" onClick={e => e.stopPropagation()}> <div className="bg-[#16213e] rounded-2xl p-4 border-2 border-[#00f0ff]/50" onClick={e => e.stopPropagation()}>
<QrDisplay value={finalMnemonic} /> <QrDisplay value={finalMnemonic} />
</div> </div>
</div> </div>

View File

@@ -18,10 +18,10 @@ const ClipboardBadge: React.FC<ClipboardBadgeProps> = ({ events, onOpenClipboard
// Determine badge style based on clipboard count // Determine badge style based on clipboard count
const badgeStyle = const badgeStyle =
count === 0 count === 0
? "text-green-500 bg-green-500/10 border-green-500/20" // Safe ? "text-[#39ff14] bg-[#39ff14]/10 border-[#39ff14]/20" // Safe
: count < 5 : count < 5
? "text-amber-500 bg-amber-500/10 border-amber-500/30 font-semibold" // Warning ? "text-[#ff006e] bg-[#ff006e]/10 border-[#ff006e]/30 font-semibold" // Warning
: "text-red-500 bg-red-500/10 border-red-500/30 font-bold animate-pulse"; // Danger : "text-[#ff006e] bg-[#ff006e]/10 border-[#ff006e]/30 font-bold animate-pulse"; // Danger
return ( return (
<button <button

View File

@@ -12,8 +12,8 @@ const EditLockBadge: React.FC<EditLockBadgeProps> = ({ isLocked, onToggle }) =>
onClick={onToggle} onClick={onToggle}
className={`flex items-center gap-2 px-3 py-1.5 rounded-lg border transition-all hover:scale-105 ${ className={`flex items-center gap-2 px-3 py-1.5 rounded-lg border transition-all hover:scale-105 ${
isLocked isLocked
? 'text-amber-500 bg-amber-500/10 border-amber-500/30 font-semibold' ? 'text-[#ff006e] bg-[#ff006e]/10 border-[#ff006e]/30 font-semibold'
: 'text-green-500 bg-green-500/10 border-green-500/30' : 'text-[#39ff14] bg-[#39ff14]/10 border-[#39ff14]/30'
}`} }`}
title={isLocked ? 'Click to unlock and edit' : 'Click to lock and blur sensitive data'} title={isLocked ? 'Click to unlock and edit' : 'Click to lock and blur sensitive data'}
> >