Stylesheet mild refactor.
This commit is contained in:
248
static/style.css
Normal file
248
static/style.css
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
/* ============================================================================
|
||||||
|
Stegasoo - Main Stylesheet
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
CSS Variables
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
:root {
|
||||||
|
--gradient-start: #667eea;
|
||||||
|
--gradient-end: #764ba2;
|
||||||
|
--bg-dark-1: #1a1a2e;
|
||||||
|
--bg-dark-2: #16213e;
|
||||||
|
--bg-dark-3: #0f3460;
|
||||||
|
--text-muted: rgba(255, 255, 255, 0.5);
|
||||||
|
--border-light: rgba(255, 255, 255, 0.1);
|
||||||
|
--overlay-dark: rgba(0, 0, 0, 0.3);
|
||||||
|
--overlay-light: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Base Styles
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(135deg, var(--bg-dark-1) 0%, var(--bg-dark-2) 50%, var(--bg-dark-3) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Navigation
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
.navbar {
|
||||||
|
background: var(--overlay-dark) !important;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Cards
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
.card {
|
||||||
|
background: var(--overlay-light);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card {
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Buttons
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: linear-gradient(135deg, var(--gradient-end), var(--gradient-start));
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Forms
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
.form-control,
|
||||||
|
.form-select {
|
||||||
|
background: var(--overlay-light);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus,
|
||||||
|
.form-select:focus {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-color: var(--gradient-start);
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(102, 126, 234, 0.25);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control::placeholder {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix dropdown options for dark theme */
|
||||||
|
.form-select option {
|
||||||
|
background: var(--bg-dark-1);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Hero & Icons
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
.hero-icon {
|
||||||
|
font-size: 4rem;
|
||||||
|
background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Phrase Display
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
.phrase-display {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 1rem;
|
||||||
|
background: var(--overlay-dark);
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border-left: 4px solid var(--gradient-start);
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1.6;
|
||||||
|
word-spacing: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
PIN Display
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
.pin-display {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 0.75rem;
|
||||||
|
background: linear-gradient(135deg, #fef08a, #fcd34d, #fb923c);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pin-container {
|
||||||
|
background: var(--overlay-dark);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Story Cards (Memory Aid)
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
.story-word {
|
||||||
|
color: #ff6b6b;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.story-card {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-left: 3px solid var(--gradient-start);
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.story-card .day-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--gradient-start);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Alert / Message Display
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
.alert-message {
|
||||||
|
background: var(--overlay-dark);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Drop Zone (Drag & Drop File Upload)
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
.drop-zone {
|
||||||
|
position: relative;
|
||||||
|
border: 2px dashed rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone.drag-over {
|
||||||
|
border-color: var(--gradient-start);
|
||||||
|
background: rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone input[type="file"] {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone-label {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone-preview {
|
||||||
|
max-height: 120px;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Footer
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
footer {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Utility Classes
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
.bg-dark-subtle {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-box {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-icon {
|
||||||
|
font-size: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-icon {
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
@@ -19,8 +19,7 @@
|
|||||||
<h6 class="mt-4 mb-3">System Status</h6>
|
<h6 class="mt-4 mb-3">System Status</h6>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="d-flex align-items-center p-3 rounded"
|
<div class="d-flex align-items-center p-3 rounded status-box">
|
||||||
style="background: rgba(0,0,0,0.2);">
|
|
||||||
{% if has_argon2 %}
|
{% if has_argon2 %}
|
||||||
<i class="bi bi-check-circle-fill text-success fs-4 me-3"></i>
|
<i class="bi bi-check-circle-fill text-success fs-4 me-3"></i>
|
||||||
<div>
|
<div>
|
||||||
@@ -37,8 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="d-flex align-items-center p-3 rounded"
|
<div class="d-flex align-items-center p-3 rounded status-box">
|
||||||
style="background: rgba(0,0,0,0.2);">
|
|
||||||
<i class="bi bi-shield-fill-check text-success fs-4 me-3"></i>
|
<i class="bi bi-shield-fill-check text-success fs-4 me-3"></i>
|
||||||
<div>
|
<div>
|
||||||
<strong>AES-256-GCM</strong>
|
<strong>AES-256-GCM</strong>
|
||||||
|
|||||||
@@ -7,152 +7,7 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">
|
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
<style>
|
<link href="{{ url_for('static', filename='style.css') }}" rel="stylesheet">
|
||||||
:root {
|
|
||||||
--gradient-start: #667eea;
|
|
||||||
--gradient-end: #764ba2;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar {
|
|
||||||
background: rgba(0, 0, 0, 0.3) !important;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background: linear-gradient(135deg, var(--gradient-end), var(--gradient-start));
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-icon {
|
|
||||||
font-size: 4rem;
|
|
||||||
background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-card {
|
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control, .form-select {
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control:focus, .form-select:focus {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border-color: var(--gradient-start);
|
|
||||||
box-shadow: 0 0 0 0.25rem rgba(102, 126, 234, 0.25);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control::placeholder {
|
|
||||||
color: rgba(255, 255, 255, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fix dropdown options for dark theme */
|
|
||||||
.form-select option {
|
|
||||||
background: #1a1a2e;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phrase-display {
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 1rem;
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
border-left: 4px solid var(--gradient-start);
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 1.6;
|
|
||||||
word-spacing: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.story-word {
|
|
||||||
color: #ff6b6b;
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.story-card {
|
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
border-left: 3px solid var(--gradient-start);
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.story-card .day-label {
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--gradient-start);
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pin-display {
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 3rem;
|
|
||||||
font-weight: bold;
|
|
||||||
letter-spacing: 0.75rem;
|
|
||||||
background: linear-gradient(135deg, #fef08a, #fcd34d, #fb923c);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pin-container {
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 1.5rem 2rem;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-message {
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark">
|
<nav class="navbar navbar-expand-lg navbar-dark">
|
||||||
|
|||||||
@@ -32,8 +32,14 @@
|
|||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
<i class="bi bi-image me-1"></i> Reference Photo
|
<i class="bi bi-image me-1"></i> Reference Photo
|
||||||
</label>
|
</label>
|
||||||
<input type="file" name="reference_photo" class="form-control"
|
<div class="drop-zone">
|
||||||
accept="image/*" required>
|
<input type="file" name="reference_photo" accept="image/*" required>
|
||||||
|
<div class="drop-zone-label">
|
||||||
|
<i class="bi bi-cloud-arrow-up fs-3 d-block mb-2 text-muted"></i>
|
||||||
|
<span class="text-muted">Drop image or click to browse</span>
|
||||||
|
</div>
|
||||||
|
<img class="drop-zone-preview d-none">
|
||||||
|
</div>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
The same reference photo used for encoding
|
The same reference photo used for encoding
|
||||||
</div>
|
</div>
|
||||||
@@ -43,8 +49,14 @@
|
|||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
<i class="bi bi-file-earmark-image me-1"></i> Stego Image
|
<i class="bi bi-file-earmark-image me-1"></i> Stego Image
|
||||||
</label>
|
</label>
|
||||||
<input type="file" name="stego_image" class="form-control"
|
<div class="drop-zone">
|
||||||
accept="image/*" required>
|
<input type="file" name="stego_image" accept="image/*" required>
|
||||||
|
<div class="drop-zone-label">
|
||||||
|
<i class="bi bi-cloud-arrow-up fs-3 d-block mb-2 text-muted"></i>
|
||||||
|
<span class="text-muted">Drop image or click to browse</span>
|
||||||
|
</div>
|
||||||
|
<img class="drop-zone-preview d-none">
|
||||||
|
</div>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
The image containing the hidden message
|
The image containing the hidden message
|
||||||
</div>
|
</div>
|
||||||
@@ -113,10 +125,60 @@
|
|||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
|
// Form submit loading state
|
||||||
document.getElementById('decodeForm')?.addEventListener('submit', function() {
|
document.getElementById('decodeForm')?.addEventListener('submit', function() {
|
||||||
const btn = document.getElementById('decodeBtn');
|
const btn = document.getElementById('decodeBtn');
|
||||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Decoding...';
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Decoding...';
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Drag & drop with preview
|
||||||
|
document.querySelectorAll('.drop-zone').forEach(zone => {
|
||||||
|
const input = zone.querySelector('input[type="file"]');
|
||||||
|
const label = zone.querySelector('.drop-zone-label');
|
||||||
|
const preview = zone.querySelector('.drop-zone-preview');
|
||||||
|
|
||||||
|
// Drag events
|
||||||
|
['dragenter', 'dragover'].forEach(evt => {
|
||||||
|
zone.addEventListener(evt, e => {
|
||||||
|
e.preventDefault();
|
||||||
|
zone.classList.add('drag-over');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
['dragleave', 'drop'].forEach(evt => {
|
||||||
|
zone.addEventListener(evt, e => {
|
||||||
|
e.preventDefault();
|
||||||
|
zone.classList.remove('drag-over');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle drop
|
||||||
|
zone.addEventListener('drop', e => {
|
||||||
|
if (e.dataTransfer.files.length) {
|
||||||
|
input.files = e.dataTransfer.files;
|
||||||
|
showPreview(e.dataTransfer.files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle click selection
|
||||||
|
input.addEventListener('change', function() {
|
||||||
|
if (this.files && this.files[0]) {
|
||||||
|
showPreview(this.files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showPreview(file) {
|
||||||
|
if (!file.type.startsWith('image/')) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = e => {
|
||||||
|
preview.src = e.target.result;
|
||||||
|
preview.classList.remove('d-none');
|
||||||
|
label.innerHTML = '<i class="bi bi-check-circle text-success me-1"></i>' + file.name;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -16,8 +16,14 @@
|
|||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
<i class="bi bi-image me-1"></i> Reference Photo
|
<i class="bi bi-image me-1"></i> Reference Photo
|
||||||
</label>
|
</label>
|
||||||
<input type="file" name="reference_photo" class="form-control"
|
<div class="drop-zone" id="refDropZone">
|
||||||
accept="image/*" required>
|
<input type="file" name="reference_photo" accept="image/*" required>
|
||||||
|
<div class="drop-zone-label">
|
||||||
|
<i class="bi bi-cloud-arrow-up fs-3 d-block mb-2 text-muted"></i>
|
||||||
|
<span class="text-muted">Drop image or click to browse</span>
|
||||||
|
</div>
|
||||||
|
<img class="drop-zone-preview d-none" id="refPreview">
|
||||||
|
</div>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
The secret photo both parties have (NOT transmitted)
|
The secret photo both parties have (NOT transmitted)
|
||||||
</div>
|
</div>
|
||||||
@@ -27,8 +33,14 @@
|
|||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
<i class="bi bi-file-image me-1"></i> Carrier Image
|
<i class="bi bi-file-image me-1"></i> Carrier Image
|
||||||
</label>
|
</label>
|
||||||
<input type="file" name="carrier" class="form-control"
|
<div class="drop-zone" id="carrierDropZone">
|
||||||
accept="image/*" required>
|
<input type="file" name="carrier" accept="image/*" required>
|
||||||
|
<div class="drop-zone-label">
|
||||||
|
<i class="bi bi-cloud-arrow-up fs-3 d-block mb-2 text-muted"></i>
|
||||||
|
<span class="text-muted">Drop image or click to browse</span>
|
||||||
|
</div>
|
||||||
|
<img class="drop-zone-preview d-none" id="carrierPreview">
|
||||||
|
</div>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
The image to hide your message in (e.g., a meme)
|
The image to hide your message in (e.g., a meme)
|
||||||
</div>
|
</div>
|
||||||
@@ -39,8 +51,17 @@
|
|||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
<i class="bi bi-chat-left-text me-1"></i> Secret Message
|
<i class="bi bi-chat-left-text me-1"></i> Secret Message
|
||||||
</label>
|
</label>
|
||||||
<textarea name="message" class="form-control" rows="4"
|
<textarea name="message" class="form-control" rows="4" id="messageInput"
|
||||||
placeholder="Enter your secret message here..." required></textarea>
|
placeholder="Enter your secret message here..." required></textarea>
|
||||||
|
<div class="d-flex justify-content-between form-text">
|
||||||
|
<span>
|
||||||
|
<span id="charCount">0</span> / 50,000 characters
|
||||||
|
<span id="charWarning" class="text-warning d-none ms-2">
|
||||||
|
<i class="bi bi-exclamation-triangle"></i> Getting long!
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span id="charPercent" class="text-muted">0%</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -104,10 +125,81 @@
|
|||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
|
// Form submit loading state
|
||||||
document.getElementById('encodeForm').addEventListener('submit', function(e) {
|
document.getElementById('encodeForm').addEventListener('submit', function(e) {
|
||||||
const btn = document.getElementById('encodeBtn');
|
const btn = document.getElementById('encodeBtn');
|
||||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Encoding...';
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Encoding...';
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Character counter
|
||||||
|
const messageInput = document.getElementById('messageInput');
|
||||||
|
const charCount = document.getElementById('charCount');
|
||||||
|
const charWarning = document.getElementById('charWarning');
|
||||||
|
const charPercent = document.getElementById('charPercent');
|
||||||
|
const maxChars = 50000;
|
||||||
|
|
||||||
|
messageInput.addEventListener('input', function() {
|
||||||
|
const len = this.value.length;
|
||||||
|
charCount.textContent = len.toLocaleString();
|
||||||
|
|
||||||
|
const pct = Math.round((len / maxChars) * 100);
|
||||||
|
charPercent.textContent = pct + '%';
|
||||||
|
|
||||||
|
// Warning at 80%
|
||||||
|
charWarning.classList.toggle('d-none', len < maxChars * 0.8);
|
||||||
|
|
||||||
|
// Red text when near/over limit
|
||||||
|
charCount.classList.toggle('text-danger', len > maxChars * 0.95);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Drag & drop with preview
|
||||||
|
document.querySelectorAll('.drop-zone').forEach(zone => {
|
||||||
|
const input = zone.querySelector('input[type="file"]');
|
||||||
|
const label = zone.querySelector('.drop-zone-label');
|
||||||
|
const preview = zone.querySelector('.drop-zone-preview');
|
||||||
|
|
||||||
|
// Drag events
|
||||||
|
['dragenter', 'dragover'].forEach(evt => {
|
||||||
|
zone.addEventListener(evt, e => {
|
||||||
|
e.preventDefault();
|
||||||
|
zone.classList.add('drag-over');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
['dragleave', 'drop'].forEach(evt => {
|
||||||
|
zone.addEventListener(evt, e => {
|
||||||
|
e.preventDefault();
|
||||||
|
zone.classList.remove('drag-over');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle drop
|
||||||
|
zone.addEventListener('drop', e => {
|
||||||
|
if (e.dataTransfer.files.length) {
|
||||||
|
input.files = e.dataTransfer.files;
|
||||||
|
showPreview(e.dataTransfer.files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle click selection
|
||||||
|
input.addEventListener('change', function() {
|
||||||
|
if (this.files && this.files[0]) {
|
||||||
|
showPreview(this.files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showPreview(file) {
|
||||||
|
if (!file.type.startsWith('image/')) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = e => {
|
||||||
|
preview.src = e.target.result;
|
||||||
|
preview.classList.remove('d-none');
|
||||||
|
label.innerHTML = '<i class="bi bi-check-circle text-success me-1"></i>' + file.name;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<i class="bi bi-file-earmark-image text-success" style="font-size: 4rem;"></i>
|
<i class="bi bi-file-earmark-image text-success result-icon"></i>
|
||||||
<h5 class="mt-3">{{ filename }}</h5>
|
<h5 class="mt-3">{{ filename }}</h5>
|
||||||
<p class="text-muted">Your secret message is hidden in this image</p>
|
<p class="text-muted">Your secret message is hidden in this image</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user