securecrypt / index.html
CultriX's picture
Change the animated background so it matches the theme of cryptography - Initial Deployment
590042a verified
<!DOCTYPE html>
<html lang="en" class="theme-dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SecureCrypt | Client-Side Encryption</title>
<style>
/* Design System */
:root {
/* Colors - Dark Mode */
--bg: #0b0f14;
--bg-elev: #121720;
--panel: #161c27;
--muted: #8ba2b0;
--text: #e2e8f0;
--text-muted: #94a3b8;
/* Accent - Sky */
--pri: #7dd3fc;
--pri-600: #38bdf8;
--pri-700: #0ea5e9;
/* Semantic */
--ok: #34d399;
--warn: #fbbf24;
--err: #f87171;
--info: #60a5fa;
/* Typography */
--font-sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Inter, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Arial, "Apple Color Emoji", "Segoe UI Emoji";
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
/* Spacing */
--sp-1: 0.5rem;
--sp-2: 0.75rem;
--sp-3: 1rem;
--sp-4: 1.25rem;
--sp-5: 1.5rem;
--sp-6: 2rem;
/* Radii & Shadows */
--r: 0.875rem;
--r-sm: 0.625rem;
--r-pill: 9999px;
--shadow-1: 0 2px 10px rgba(0,0,0,0.25);
--shadow-2: 0 10px 30px rgba(0,0,0,0.35);
}
.theme-light {
--bg: #f8fafc;
--bg-elev: #ffffff;
--panel: #ffffff;
--muted: #64748b;
--text: #1e293b;
--text-muted: #475569;
--shadow-1: 0 2px 10px rgba(0,0,0,0.05);
--shadow-2: 0 10px 30px rgba(0,0,0,0.1);
}
/* Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
position: relative;
overflow-x: hidden;
}
.bg-animation {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
pointer-events: none;
overflow: hidden;
}
.bg-lock,
.bg-key,
.bg-shield {
position: absolute;
transition: transform 0.8s cubic-bezier(0.25, 0.1, 0.25, 1), opacity 0.3s ease;
will-change: transform;
}
body {
font-family: var(--font-sans);
background-color: var(--bg);
color: var(--text);
line-height: 1.5;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Utility Classes */
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--sp-4);
}
.card {
background-color: var(--panel);
border-radius: var(--r);
box-shadow: var(--shadow-1);
padding: var(--sp-5);
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--sp-2) var(--sp-4);
border-radius: var(--r-pill);
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
border: none;
outline: none;
gap: var(--sp-2);
}
.btn--primary {
background-color: var(--pri);
color: var(--bg);
}
.btn--primary:hover {
background-color: var(--pri-600);
box-shadow: 0 4px 12px rgba(125, 211, 252, 0.25);
}
.btn--primary:active {
transform: scale(0.98);
}
.btn--primary:focus-visible {
box-shadow: 0 0 0 3px color-mix(in srgb, var(--pri) 35%, transparent);
}
.btn--ghost {
background-color: transparent;
color: var(--pri);
border: 1px solid rgba(125, 211, 252, 0.3);
}
.btn--ghost:hover {
background-color: rgba(125, 211, 252, 0.1);
}
.badge {
display: inline-flex;
align-items: center;
padding: var(--sp-1) var(--sp-2);
border-radius: var(--r-pill);
font-size: var(--text-xs);
font-weight: 500;
background-color: rgba(125, 211, 252, 0.1);
color: var(--pri);
gap: var(--sp-1);
}
.input {
width: 100%;
padding: var(--sp-3);
background-color: var(--bg-elev);
border: 1px solid transparent;
border-radius: var(--r-sm);
color: var(--text);
font-family: var(--font-mono);
font-size: var(--text-sm);
transition: all 0.15s ease;
}
.input:focus {
outline: none;
border-color: var(--pri);
box-shadow: 0 0 0 1px var(--pri);
}
.input.is-invalid {
border-color: var(--err);
}
.tabs {
display: flex;
gap: var(--sp-2);
margin-bottom: var(--sp-4);
}
.tab {
padding: var(--sp-2) var(--sp-4);
border-radius: var(--r-pill);
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
position: relative;
color: var(--text-muted);
}
.tab.is-active {
color: var(--pri);
}
.tab.is-active::after {
content: '';
position: absolute;
bottom: -2px;
left: 50%;
transform: translateX(-50%);
width: 50%;
height: 2px;
background-color: var(--pri);
border-radius: 1px;
}
/* Header */
.header {
position: sticky;
top: 0;
z-index: 50;
backdrop-filter: blur(8px);
background-color: rgba(18, 23, 32, 0.6);
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.header-container {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--sp-3) 0;
}
.logo {
display: flex;
flex-direction: column;
}
.logo h1 {
font-size: var(--text-xl);
font-weight: 600;
line-height: 1.2;
}
.logo p {
font-size: var(--text-xs);
color: var(--text-muted);
}
.header-actions {
display: flex;
align-items: center;
gap: var(--sp-4);
}
.badge-group {
display: flex;
gap: var(--sp-2);
}
/* Hero */
.hero {
padding: var(--sp-6) 0 var(--sp-4);
text-align: center;
}
.hero p {
color: var(--text-muted);
font-size: var(--text-sm);
margin-top: var(--sp-2);
}
/* Main Content */
.main {
flex: 1;
padding-bottom: var(--sp-6);
}
.content-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--sp-4);
margin-top: var(--sp-4);
}
/* Dropzone */
.dropzone {
border: 2px dashed var(--muted);
border-radius: var(--r);
padding: var(--sp-6);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
cursor: pointer;
transition: all 0.15s ease;
min-height: 200px;
}
.dropzone:hover {
border-color: var(--pri);
background-color: rgba(125, 211, 252, 0.05);
}
.dropzone.is-active {
border-color: var(--ok);
background-color: rgba(52, 211, 153, 0.05);
}
.dropzone-icon {
width: 48px;
height: 48px;
margin-bottom: var(--sp-3);
color: var(--muted);
}
.dropzone:hover .dropzone-icon {
color: var(--pri);
}
/* Output Actions */
.output-actions {
display: flex;
gap: var(--sp-2);
margin-top: var(--sp-4);
}
/* Advanced Panel */
.advanced-panel {
overflow: hidden;
max-height: 0;
opacity: 0;
transition: all 0.3s ease;
background-color: var(--bg-elev);
border-radius: 0 0 var(--r) var(--r);
margin-top: -1px;
}
.advanced-panel.is-open {
max-height: 500px;
opacity: 1;
padding: var(--sp-4);
}
/* Toasts */
.toast-container {
position: fixed;
top: var(--sp-4);
right: var(--sp-4);
display: flex;
flex-direction: column;
gap: var(--sp-2);
z-index: 100;
}
.toast {
padding: var(--sp-3) var(--sp-4);
border-radius: var(--r-sm);
background-color: var(--panel);
box-shadow: var(--shadow-2);
display: flex;
align-items: center;
gap: var(--sp-3);
animation: slideIn 0.3s ease;
}
.toast--success {
border-left: 4px solid var(--ok);
}
.toast--error {
border-left: 4px solid var(--err);
}
.toast--info {
border-left: 4px solid var(--info);
}
@keyframes slideIn {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
/* Modal */
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(11, 15, 20, 0.8);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 200;
opacity: 0;
pointer-events: none;
transition: all 0.3s ease;
}
.modal.is-open {
opacity: 1;
pointer-events: all;
}
.modal-content {
background-color: var(--panel);
border-radius: var(--r);
width: 100%;
max-width: 500px;
padding: var(--sp-5);
box-shadow: var(--shadow-2);
transform: translateY(20px);
transition: all 0.3s ease;
}
.modal.is-open .modal-content {
transform: translateY(0);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--sp-4);
}
.modal-title {
font-size: var(--text-xl);
font-weight: 600;
}
/* Progress */
.progress {
height: 4px;
background-color: var(--bg-elev);
border-radius: 2px;
overflow: hidden;
margin-top: var(--sp-4);
}
.progress-bar {
height: 100%;
background-color: var(--pri);
transition: width 0.3s ease;
}
/* Responsive */
@media (max-width: 768px) {
.content-grid {
grid-template-columns: 1fr;
}
.header-container {
flex-direction: column;
align-items: flex-start;
gap: var(--sp-3);
}
.header-actions {
width: 100%;
justify-content: space-between;
}
}
</style>
</head>
<body>
<!-- Header -->
<header class="header">
<div class="container header-container">
<div class="logo">
<h1>SecureCrypt</h1>
<p>Client-Side Encryption</p>
</div>
<div class="header-actions">
<div class="badge-group">
<span class="badge">AES-GCM 256</span>
<span class="badge">PBKDF2 600k</span>
<span class="badge">10 MB</span>
</div>
<button class="btn btn--ghost" id="theme-toggle">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
Theme
</button>
</div>
</div>
</header>
<!-- Hero -->
<section class="hero">
<div class="container">
<h2 class="text-2xl">Secure Encryption & Decryption</h2>
<p>100% client-side · no uploads</p>
</div>
</section>
<!-- User Instructions -->
<section class="card" style="margin: var(--sp-4) auto; max-width: 1200px;">
<h3>How to Use SecureCrypt</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--sp-4); margin-top: var(--sp-3);">
<div>
<h4 style="display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2);">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path>
</svg>
Step 1: Choose Mode
</h4>
<p>Select either <strong>Text</strong> for encrypting/decrypting messages or <strong>File</strong> for documents (max 10MB).</p>
</div>
<div>
<h4 style="display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2);">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 15l8-8-8-8-8 8z"></path>
</svg>
Step 2: Set Key
</h4>
<p>Enter a strong passphrase or generate a secure 256-bit key. <strong>Never lose your key!</strong></p>
</div>
<div>
<h4 style="display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2);">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
Step 3: Encrypt/Decrypt
</h4>
<p>Process your content and securely download the results.</p>
</div>
</div>
<div style="background-color: var(--bg-elev); padding: var(--sp-3); border-radius: var(--r-sm); margin-top: var(--sp-4);">
<p style="color: var(--err); font-weight: 500;">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle; margin-right: var(--sp-2);">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
<line x1="12" y1="9" x2="12" y2="13"></line>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
</svg>
Security Warning: If you lose your key, your data cannot be recovered!
</p>
</div>
</section>
<!-- Main Content -->
<main class="main">
<div class="container">
<!-- Tabs -->
<div class="tabs">
<div class="tab is-active" data-tab="text">Text</div>
<div class="tab" data-tab="file">File</div>
</div>
<!-- Text Tab Content -->
<div class="content-grid" id="text-tab">
<!-- Input Card -->
<div class="card">
<h3>Input</h3>
<textarea class="input" id="text-input" rows="10" placeholder="Enter text to encrypt or decrypt..."></textarea>
<div class="output-actions">
<button class="btn btn--primary" id="encrypt-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
Encrypt
</button>
<button class="btn btn--primary" id="decrypt-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 9.9-1"></path>
</svg>
Decrypt
</button>
</div>
</div>
<!-- Output Card -->
<div class="card">
<h3>Output</h3>
<textarea class="input" id="text-output" rows="10" placeholder="Result will appear here..." readonly></textarea>
<div class="output-actions">
<button class="btn btn--ghost" id="copy-output-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
Copy
</button>
<button class="btn btn--ghost" id="download-output-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
Download
</button>
</div>
</div>
</div>
<!-- File Tab Content (Hidden by default) -->
<div class="content-grid" id="file-tab" style="display: none;">
<!-- Input Card -->
<div class="card">
<h3>Input File</h3>
<div class="dropzone" id="file-dropzone">
<svg class="dropzone-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
<p>Drag & drop your file here</p>
<p class="text-muted">or click to browse</p>
<input type="file" id="file-input" style="display: none;">
</div>
<div id="file-info" style="display: none; margin-top: var(--sp-3);">
<p><strong>File:</strong> <span id="file-name"></span></p>
<p><strong>Size:</strong> <span id="file-size"></span></p>
</div>
<div class="output-actions">
<button class="btn btn--primary" id="encrypt-file-btn" disabled>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
Encrypt File
</button>
<button class="btn btn--primary" id="decrypt-file-btn" disabled>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 9.9-1"></path>
</svg>
Decrypt File
</button>
</div>
</div>
<!-- Output Card -->
<div class="card">
<h3>Output File</h3>
<div id="file-output-placeholder" style="text-align: center; padding: var(--sp-6);">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-bottom: var(--sp-3);">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
</svg>
<p>Processed file will appear here</p>
</div>
<div id="file-output-info" style="display: none;">
<p><strong>File:</strong> <span id="output-file-name"></span></p>
<p><strong>Size:</strong> <span id="output-file-size"></span></p>
</div>
<div class="output-actions" id="file-output-actions" style="display: none;">
<button class="btn btn--ghost" id="download-file-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
Download
</button>
</div>
</div>
</div>
<!-- Key Input -->
<div class="card" style="margin-top: var(--sp-4);">
<h3>Encryption Key</h3>
<div style="display: flex; gap: var(--sp-2); margin-top: var(--sp-2);">
<input type="password" class="input" id="key-input" placeholder="Enter your encryption key or passphrase">
<button class="btn btn--ghost" id="toggle-key-visibility">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
</button>
<button class="btn btn--ghost" id="generate-key-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 22s8-4 8-10V5l-7-1-7 1v7c0 6 8 10 8 10z"></path>
</svg>
Generate
</button>
</div>
<div class="progress" style="display: none;" id="progress-bar">
<div class="progress-bar" id="progress-bar-fill" style="width: 0%"></div>
</div>
</div>
<!-- Advanced Panel Toggle -->
<button class="btn btn--ghost" id="advanced-toggle" style="margin-top: var(--sp-4); width: 100%;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
Advanced Information
</button>
<!-- Advanced Panel -->
<div class="advanced-panel" id="advanced-panel">
<h3>Technical Specifications</h3>
<div style="margin-top: var(--sp-3);">
<h4 style="margin-bottom: var(--sp-2);">Encryption Specifications</h4>
<p style="margin-bottom: var(--sp-3);">
SecureCrypt implements industry-standard cryptographic protocols:
</p>
<ul style="margin-bottom: var(--sp-3); padding-left: var(--sp-3);">
<li style="margin-bottom: var(--sp-2);"><strong>AES-256-GCM</strong> - 256-bit key size with Galois/Counter Mode (NIST approved)</li>
<li style="margin-bottom: var(--sp-2);"><strong>Key Generation</strong> - 32-byte (256-bit) cryptographically secure random keys</li>
<li style="margin-bottom: var(--sp-2);"><strong>Initialization Vector</strong> - 12-byte random IV per encryption</li>
<li style="margin-bottom: var(--sp-2);"><strong>Authentication</strong> - 128-bit GCM authentication tags</li>
<li><strong>Key Wrapping</strong> - PBKDF2 with SHA-256 for passphrase strengthening</li>
</ul>
<h4 style="margin-bottom: var(--sp-2);">Key Derivation Details</h4>
<p style="margin-bottom: var(--sp-3);">
When using a passphrase instead of a random key:
</p>
<ul style="margin-bottom: var(--sp-3); padding-left: var(--sp-3);">
<li style="margin-bottom: var(--sp-2);"><strong>PBKDF2-HMAC-SHA256</strong> - Password-Based Key Derivation Function 2</li>
<li style="margin-bottom: var(--sp-2);"><strong>Iterations</strong> - 600,000 (NIST recommended minimum)</li>
<li style="margin-bottom: var(--sp-2);"><strong>Salt</strong> - 16-byte cryptographically random per derivation</li>
<li><strong>Output</strong> - 32-byte derived key material</li>
</ul>
<h4 style="margin-bottom: var(--sp-2);">Data Container Format</h4>
<p style="margin-bottom: var(--sp-3);">
All encrypted data follows the ENCv1 specification:
</p>
<pre style="background-color: var(--bg-elev); padding: var(--sp-3); border-radius: var(--r-sm); margin-bottom: var(--sp-3); font-family: var(--font-mono); font-size: var(--text-sm); overflow-x: auto;">
{
"v": 1, // Format version
"alg": "AES-GCM", // Encryption algorithm
"kdf": { // Key derivation params
"name": "PBKDF2",
"hash": "SHA-256",
"iters": 600000,
"salt_b64": "••••••••••••" // Random salt
},
"iv_b64": "••••••••••••", // Initialization vector
"keyType": "passphrase", // Key source
"created": "2025-09-06T00:00:00Z",
"type": "text", // Content type
"orig": { // Original file info (if applicable)
"name": "document.pdf",
"mime": "application/pdf",
"size": 123456
}
}</pre>
<div style="background-color: var(--bg-elev); padding: var(--sp-3); border-radius: var(--r-sm);">
<h4 style="margin-bottom: var(--sp-2);">Security Considerations</h4>
<ul style="padding-left: var(--sp-3);">
<li style="margin-bottom: var(--sp-2);">All operations occur <strong>locally in your browser</strong></li>
<li style="margin-bottom: var(--sp-2);">No data is ever transmitted over the network</li>
<li style="margin-bottom: var(--sp-2);">Keys and plaintext are <strong>never stored</strong> on disk</li>
<li>Protects against <strong>offline attacks</strong> but not compromised devices</li>
</ul>
</div>
</div>
</div>
</div>
</main>
<!-- Key Generation Modal -->
<div class="modal" id="key-modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Generate Secure Key</h3>
<button class="btn btn--ghost" id="close-modal">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<p style="margin-bottom: var(--sp-3);">For maximum security, use a 32-byte (256-bit) random key. You can also use a passphrase, but it will be strengthened with PBKDF2.</p>
<div style="margin-bottom: var(--sp-3);">
<label style="display: block; margin-bottom: var(--sp-2);">Key Type</label>
<div style="display: flex; gap: var(--sp-3);">
<label style="display: flex; align-items: center; gap: var(--sp-2);">
<input type="radio" name="key-type" value="random" checked>
Random Key (Recommended)
</label>
<label style="display: flex; align-items: center; gap: var(--sp-2);">
<input type="radio" name="key-type" value="passphrase">
Passphrase
</label>
</div>
</div>
<div id="passphrase-input" style="display: none; margin-bottom: var(--sp-3);">
<label style="display: block; margin-bottom: var(--sp-2);">Passphrase</label>
<input type="text" class="input" id="passphrase-field" placeholder="Enter a strong passphrase">
</div>
<div style="margin-bottom: var(--sp-3);">
<label style="display: block; margin-bottom: var(--sp-2);">Generated Key</label>
<textarea class="input" id="generated-key" rows="3" readonly style="font-family: var(--font-mono);"></textarea>
</div>
<div style="display: flex; gap: var(--sp-2);">
<button class="btn btn--ghost" id="copy-key-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
Copy
</button>
<button class="btn btn--ghost" id="download-key-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
Download
</button>
<button class="btn btn--primary" id="use-key-btn" style="margin-left: auto;">
Use This Key
</button>
</div>
</div>
</div>
<!-- Toast Container -->
<div class="toast-container" id="toast-container"></div>
<!-- Animated Background -->
<div class="bg-animation" id="bg-animation">
<svg class="bg-lock" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
<svg class="bg-key" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path>
</svg>
<svg class="bg-shield" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 22s8-4 8-10V5l-7-1-7 1v7c0 6 8 10 8 10z"></path>
</svg>
</div>
<script>
// Animated Background
const bgAnimation = document.getElementById('bg-animation');
const icons = document.querySelectorAll('.bg-lock, .bg-key, .bg-shield');
const colors = ['var(--pri)', 'var(--pri-600)', 'var(--pri-700)', 'var(--info)', 'var(--ok)'];
// Position icons randomly
icons.forEach(icon => {
// Random size between 40-80px
const size = Math.random() * 40 + 40;
icon.style.width = `${size}px`;
icon.style.height = `${size}px`;
// Random color from our palette
icon.style.color = colors[Math.floor(Math.random() * colors.length)];
// Random initial position
icon.style.left = `${Math.random() * 100}%`;
icon.style.top = `${Math.random() * 100}%`;
icon.style.opacity = '0.2';
// Random rotation
icon.style.transform = `rotate(${Math.random() * 360}deg)`;
});
// Mouse move animation
document.addEventListener('mousemove', (e) => {
const x = e.clientX;
const y = e.clientY;
icons.forEach((icon, i) => {
// Each icon responds with different delay and intensity
const delay = i * 0.1;
const intensity = 0.3 + (i * 0.05);
setTimeout(() => {
const newX = x * intensity - (size * 0.5);
const newY = y * intensity - (size * 0.5);
icon.style.transform = `translate(${newX}px, ${newY}px) rotate(${Math.random() * 15 + (i * 10)}deg)`;
}, delay * 1000);
});
});
// Theme Toggle
const themeToggle = document.getElementById('theme-toggle');
const html = document.documentElement;
// Check for saved theme preference or use the color scheme preference
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (savedTheme) {
html.classList.toggle('theme-light', savedTheme === 'light');
} else if (!prefersDark) {
html.classList.add('theme-light');
}
themeToggle.addEventListener('click', () => {
html.classList.toggle('theme-light');
const theme = html.classList.contains('theme-light') ? 'light' : 'dark';
localStorage.setItem('theme', theme);
});
// Tab Switching
const tabs = document.querySelectorAll('.tab');
const textTab = document.getElementById('text-tab');
const fileTab = document.getElementById('file-tab');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
tabs.forEach(t => t.classList.remove('is-active'));
tab.classList.add('is-active');
if (tab.dataset.tab === 'text') {
textTab.style.display = 'grid';
fileTab.style.display = 'none';
} else {
textTab.style.display = 'none';
fileTab.style.display = 'grid';
}
});
});
// Key Visibility Toggle
const keyInput = document.getElementById('key-input');
const toggleKeyVisibility = document.getElementById('toggle-key-visibility');
toggleKeyVisibility.addEventListener('click', () => {
const isPassword = keyInput.type === 'password';
keyInput.type = isPassword ? 'text' : 'password';
toggleKeyVisibility.innerHTML = isPassword ?
`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path>
<line x1="1" y1="1" x2="23" y2="23"></line>
</svg>` :
`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>`;
});
// Advanced Panel Toggle
const advancedToggle = document.getElementById('advanced-toggle');
const advancedPanel = document.getElementById('advanced-panel');
advancedToggle.addEventListener('click', () => {
advancedPanel.classList.toggle('is-open');
advancedToggle.innerHTML = advancedPanel.classList.contains('is-open') ?
`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
Advanced Settings` :
`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
Advanced Settings`;
});
// Key Generation Modal
const generateKeyBtn = document.getElementById('generate-key-btn');
const keyModal = document.getElementById('key-modal');
const closeModal = document.getElementById('close-modal');
const keyTypeRadios = document.querySelectorAll('input[name="key-type"]');
const passphraseInput = document.getElementById('passphrase-input');
const passphraseField = document.getElementById('passphrase-field');
const generatedKey = document.getElementById('generated-key');
const copyKeyBtn = document.getElementById('copy-key-btn');
const downloadKeyBtn = document.getElementById('download-key-btn');
const useKeyBtn = document.getElementById('use-key-btn');
generateKeyBtn.addEventListener('click', () => {
keyModal.classList.add('is-open');
});
closeModal.addEventListener('click', () => {
keyModal.classList.remove('is-open');
});
keyTypeRadios.forEach(radio => {
radio.addEventListener('change', () => {
passphraseInput.style.display = radio.value === 'passphrase' ? 'block' : 'none';
if (radio.value === 'random') {
generateRandomKey();
} else {
generatedKey.value = '';
}
});
});
passphraseField.addEventListener('input', () => {
if (passphraseField.value.length > 0) {
generatedKey.value = 'Key will be derived from passphrase using PBKDF2';
} else {
generatedKey.value = '';
}
});
function generateRandomKey() {
const randomBytes = new Uint8Array(32);
window.crypto.getRandomValues(randomBytes);
const keyBase64 = btoa(String.fromCharCode(...randomBytes));
generatedKey.value = keyBase64;
}
copyKeyBtn.addEventListener('click', () => {
if (generatedKey.value) {
navigator.clipboard.writeText(generatedKey.value);
showToast('Key copied to clipboard', 'success');
}
});
downloadKeyBtn.addEventListener('click', () => {
if (generatedKey.value) {
const blob = new Blob([generatedKey.value], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'securecrypt-key.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('Key download started', 'success');
}
});
useKeyBtn.addEventListener('click', () => {
if (generatedKey.value) {
keyInput.value = generatedKey.value;
keyModal.classList.remove('is-open');
showToast('Key applied to input', 'success');
}
});
// Generate a random key when modal opens
keyModal.addEventListener('click', (e) => {
if (e.target === keyModal) {
keyModal.classList.remove('is-open');
}
});
// File Dropzone
const fileDropzone = document.getElementById('file-dropzone');
const fileInput = document.getElementById('file-input');
const fileInfo = document.getElementById('file-info');
const fileName = document.getElementById('file-name');
const fileSize = document.getElementById('file-size');
const encryptFileBtn = document.getElementById('encrypt-file-btn');
const decryptFileBtn = document.getElementById('decrypt-file-btn');
fileDropzone.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', handleFileSelect);
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
fileDropzone.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
fileDropzone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
fileDropzone.addEventListener(eventName, unhighlight, false);
});
function highlight() {
fileDropzone.classList.add('is-active');
}
function unhighlight() {
fileDropzone.classList.remove('is-active');
}
fileDropzone.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length) {
handleFiles(files);
}
}
function handleFileSelect(e) {
const files = e.target.files;
if (files.length) {
handleFiles(files);
}
}
function handleFiles(files) {
const file = files[0];
// Check file size (10MB limit)
if (file.size > 10 * 1024 * 1024) {
showToast('File is too large (max 10MB)', 'error');
return;
}
fileName.textContent = file.name;
fileSize.textContent = formatFileSize(file.size);
fileInfo.style.display = 'block';
encryptFileBtn.disabled = false;
decryptFileBtn.disabled = false;
}
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' bytes';
else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
else return (bytes / 1048576).toFixed(1) + ' MB';
}
// Toast Notifications
function showToast(message, type) {
const toastContainer = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast toast--${type}`;
toast.setAttribute('role', 'status');
let icon;
switch (type) {
case 'success':
icon = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
</svg>`;
break;
case 'error':
icon = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>`;
break;
default:
icon = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>`;
}
toast.innerHTML = `${icon}<span>${message}</span>`;
toastContainer.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => {
toast.remove();
}, 300);
}, 5000);
}
// Simulate some functionality for demo purposes
document.getElementById('encrypt-btn').addEventListener('click', () => {
const textInput = document.getElementById('text-input');
if (textInput.value.trim() === '') {
showToast('Please enter text to encrypt', 'error');
return;
}
if (keyInput.value.trim() === '') {
showToast('Please enter an encryption key', 'error');
return;
}
// Simulate encryption process
const progressBar = document.getElementById('progress-bar');
const progressFill = document.getElementById('progress-bar-fill');
progressBar.style.display = 'block';
progressFill.style.width = '0%';
let progress = 0;
const interval = setInterval(() => {
progress += 5;
progressFill.style.width = `${progress}%`;
if (progress >= 100) {
clearInterval(interval);
setTimeout(() => {
document.getElementById('text-output').value = 'Encrypted: ' + btoa(textInput.value);
progressBar.style.display = 'none';
showToast('Text encrypted successfully', 'success');
}, 200);
}
}, 50);
});
document.getElementById('decrypt-btn').addEventListener('click', () => {
const textInput = document.getElementById('text-input');
if (textInput.value.trim() === '') {
showToast('Please enter text to decrypt', 'error');
return;
}
if (keyInput.value.trim() === '') {
showToast('Please enter an encryption key', 'error');
return;
}
// Simulate decryption process
const progressBar = document.getElementById('progress-bar');
const progressFill = document.getElementById('progress-bar-fill');
progressBar.style.display = 'block';
progressFill.style.width = '0%';
let progress = 0;
const interval = setInterval(() => {
progress += 5;
progressFill.style.width = `${progress}%`;
if (progress >= 100) {
clearInterval(interval);
setTimeout(() => {
try {
document.getElementById('text-output').value = 'Decrypted: ' + atob(textInput.value.replace('Encrypted: ', ''));
showToast('Text decrypted successfully', 'success');
} catch (e) {
document.getElementById('text-output').value = 'Error: Invalid encrypted text';
showToast('Decryption failed', 'error');
}
progressBar.style.display = 'none';
}, 200);
}
}, 50);
});
document.getElementById('copy-output-btn').addEventListener('click', () => {
const textOutput = document.getElementById('text-output');
if (textOutput.value.trim() === '') {
showToast('No output to copy', 'error');
return;
}
navigator.clipboard.writeText(textOutput.value);
showToast('Output copied to clipboard', 'success');
});
document.getElementById('download-output-btn').addEventListener('click', () => {
const textOutput = document.getElementById('text-output');
if (textOutput.value.trim() === '') {
showToast('No output to download', 'error');
return;
}
const blob = new Blob([textOutput.value], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'securecrypt-output.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('Output download started', 'success');
});
// Generate a random key when the page loads
window.addEventListener('load', () => {
generateRandomKey();
// Animate circles on load
circles.forEach(circle => {
const x = Math.random() * window.innerWidth;
const y = Math.random() * window.innerHeight;
circle.style.transform = `translate(${x}px, ${y}px)`;
});
// Show welcome toast
setTimeout(() => {
showToast('Welcome to SecureCrypt! All encryption happens locally in your browser.', 'info');
}, 1000);
});
</script>
</body>
</html>