Compact mode UI with smart output options
Encode page: - Inline mode buttons: DCT | LSB | Color | Gray | JPEG | PNG - LSB mode auto-selects Color+PNG and disables Gray/JPEG - Dynamic hint text with icons below mode buttons Decode page: - Compact inline mode buttons: Auto | LSB | DCT - Dynamic hints that change per mode selection CSS: - Disabled btn-check styling for dimmed unavailable options 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -91,6 +91,31 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Compact inline mode buttons */
|
||||||
|
.mode-btn.mode-btn-sm {
|
||||||
|
padding: 0.35rem 0.6rem;
|
||||||
|
padding-left: 1.75rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-btn.mode-btn-sm .form-check-input {
|
||||||
|
left: 8px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-btn.mode-btn-sm i {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled button labels for btn-check groups */
|
||||||
|
.btn-check:disabled + .btn {
|
||||||
|
opacity: 0.4;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
Security Factor Boxes - Matches drop-zone dashed border style
|
Security Factor Boxes - Matches drop-zone dashed border style
|
||||||
---------------------------------------------------------------------------- */
|
---------------------------------------------------------------------------- */
|
||||||
|
|||||||
@@ -273,27 +273,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Extraction Mode -->
|
<!-- Extraction Mode (compact inline) -->
|
||||||
<label class="form-label"><i class="bi bi-cpu me-1"></i> Extraction Mode</label>
|
<div class="d-flex gap-2 align-items-center flex-wrap mb-2">
|
||||||
<div class="d-flex gap-2 mb-2">
|
<label class="mode-btn mode-btn-sm active" id="autoModeCard" for="modeAuto">
|
||||||
<label class="mode-btn flex-fill active" id="autoModeCard" for="modeAuto">
|
|
||||||
<input class="form-check-input" type="radio" name="embed_mode" id="modeAuto" value="auto" checked>
|
<input class="form-check-input" type="radio" name="embed_mode" id="modeAuto" value="auto" checked>
|
||||||
<i class="bi bi-magic text-success ms-2"></i>
|
<i class="bi bi-magic text-success"></i> Auto
|
||||||
<span class="ms-2"><strong>Auto</strong> <span class="text-muted d-none d-sm-inline">· Try both</span></span>
|
|
||||||
</label>
|
</label>
|
||||||
<label class="mode-btn flex-fill" id="lsbModeCard" for="modeLsb">
|
<label class="mode-btn mode-btn-sm" id="lsbModeCard" for="modeLsb">
|
||||||
<input class="form-check-input" type="radio" name="embed_mode" id="modeLsb" value="lsb">
|
<input class="form-check-input" type="radio" name="embed_mode" id="modeLsb" value="lsb">
|
||||||
<i class="bi bi-grid-3x3-gap text-primary ms-2"></i>
|
<i class="bi bi-grid-3x3-gap text-primary"></i> LSB
|
||||||
<span class="ms-2"><strong>LSB</strong> <span class="text-muted d-none d-sm-inline">· Email</span></span>
|
|
||||||
</label>
|
</label>
|
||||||
<label class="mode-btn flex-fill {% if not has_dct %}opacity-50{% endif %}" id="dctModeCard" for="modeDct">
|
<label class="mode-btn mode-btn-sm {% if not has_dct %}opacity-50{% endif %}" id="dctModeCard" for="modeDct">
|
||||||
<input class="form-check-input" type="radio" name="embed_mode" id="modeDct" value="dct" {% if not has_dct %}disabled{% endif %}>
|
<input class="form-check-input" type="radio" name="embed_mode" id="modeDct" value="dct" {% if not has_dct %}disabled{% endif %}>
|
||||||
<i class="bi bi-soundwave text-warning ms-2"></i>
|
<i class="bi bi-soundwave text-warning"></i> DCT
|
||||||
<span class="ms-2"><strong>DCT</strong> <span class="text-muted d-none d-sm-inline">· Social</span></span>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text">
|
<div class="form-text" id="modeHint">
|
||||||
<i class="bi bi-lightbulb me-1"></i><strong>Auto</strong> tries LSB first, then DCT.
|
<i class="bi bi-lightning me-1"></i>Tries LSB first, then DCT
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -461,6 +457,25 @@
|
|||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='js/stegasoo.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/stegasoo.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
|
// ============================================================================
|
||||||
|
// MODE HINT - Dynamic text based on selected extraction mode
|
||||||
|
// ============================================================================
|
||||||
|
const modeHints = {
|
||||||
|
auto: { icon: 'lightning', text: 'Tries LSB first, then DCT' },
|
||||||
|
lsb: { icon: 'hdd', text: 'For email and direct transfers' },
|
||||||
|
dct: { icon: 'phone', text: 'For social media images' }
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelectorAll('input[name="embed_mode"]').forEach(radio => {
|
||||||
|
radio.addEventListener('change', function() {
|
||||||
|
const hint = document.getElementById('modeHint');
|
||||||
|
const data = modeHints[this.value];
|
||||||
|
if (hint && data) {
|
||||||
|
hint.innerHTML = `<i class="bi bi-${data.icon} me-1"></i>${data.text}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// ACCORDION SUMMARY UPDATES
|
// ACCORDION SUMMARY UPDATES
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -238,37 +238,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Embedding Mode -->
|
<!-- Embedding Mode (compact inline) -->
|
||||||
<label class="form-label"><i class="bi bi-cpu me-1"></i> Embedding Mode</label>
|
<div class="d-flex gap-2 align-items-center flex-wrap mb-2">
|
||||||
<div class="d-flex gap-2 mb-2">
|
<label class="mode-btn mode-btn-sm {% if not has_dct %}opacity-50{% endif %} {% if has_dct %}active{% endif %}" id="dctModeCard" for="modeDct">
|
||||||
<label class="mode-btn flex-fill {% if not has_dct %}opacity-50{% endif %} {% if has_dct %}active{% endif %}" id="dctModeCard" for="modeDct">
|
|
||||||
<input class="form-check-input" type="radio" name="embed_mode" id="modeDct" value="dct" {% if has_dct %}checked{% endif %} {% if not has_dct %}disabled{% endif %}>
|
<input class="form-check-input" type="radio" name="embed_mode" id="modeDct" value="dct" {% if has_dct %}checked{% endif %} {% if not has_dct %}disabled{% endif %}>
|
||||||
<i class="bi bi-soundwave text-warning ms-2"></i>
|
<i class="bi bi-soundwave text-warning"></i> DCT
|
||||||
<span class="ms-2"><strong>DCT</strong> <span class="text-muted d-none d-sm-inline">· Social</span></span>
|
|
||||||
</label>
|
</label>
|
||||||
<label class="mode-btn flex-fill {% if not has_dct %}active{% endif %}" id="lsbModeCard" for="modeLsb">
|
<label class="mode-btn mode-btn-sm {% if not has_dct %}active{% endif %}" id="lsbModeCard" for="modeLsb">
|
||||||
<input class="form-check-input" type="radio" name="embed_mode" id="modeLsb" value="lsb" {% if not has_dct %}checked{% endif %}>
|
<input class="form-check-input" type="radio" name="embed_mode" id="modeLsb" value="lsb" {% if not has_dct %}checked{% endif %}>
|
||||||
<i class="bi bi-grid-3x3-gap text-primary ms-2"></i>
|
<i class="bi bi-grid-3x3-gap text-primary"></i> LSB
|
||||||
<span class="ms-2"><strong>LSB</strong> <span class="text-muted d-none d-sm-inline">· Email</span></span>
|
|
||||||
</label>
|
</label>
|
||||||
|
<span class="d-flex gap-2 align-items-center" id="outputOptions">
|
||||||
|
<span class="text-muted">|</span>
|
||||||
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
|
<input type="radio" class="btn-check" name="dct_color_mode" id="colorMode" value="color" checked>
|
||||||
|
<label class="btn btn-outline-secondary btn-sm" for="colorMode">Color</label>
|
||||||
|
<input type="radio" class="btn-check" name="dct_color_mode" id="grayMode" value="grayscale">
|
||||||
|
<label class="btn btn-outline-secondary btn-sm" for="grayMode" id="grayModeLabel">Gray</label>
|
||||||
</div>
|
</div>
|
||||||
|
<span class="text-muted">|</span>
|
||||||
<!-- DCT Options (inline, compact) -->
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
<div class="{% if not has_dct %}d-none{% endif %}" id="dctOptionsInline">
|
<input type="radio" class="btn-check" name="dct_output_format" id="jpegFormat" value="jpeg" checked>
|
||||||
<div class="row g-2 mt-2" id="dctOptionsRow">
|
<label class="btn btn-outline-secondary btn-sm" for="jpegFormat" id="jpegFormatLabel">JPEG</label>
|
||||||
<div class="col-6">
|
<input type="radio" class="btn-check" name="dct_output_format" id="pngFormat" value="png">
|
||||||
<select class="form-select form-select-sm" name="dct_color_mode" id="dctColorSelect">
|
<label class="btn btn-outline-secondary btn-sm" for="pngFormat">PNG</label>
|
||||||
<option value="color" selected>Color</option>
|
|
||||||
<option value="grayscale">Grayscale</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<select class="form-select form-select-sm" name="dct_output_format" id="dctFormatSelect">
|
|
||||||
<option value="jpeg" selected>JPEG</option>
|
|
||||||
<option value="png">PNG</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-text" id="modeHint">
|
||||||
|
<i class="bi bi-{% if has_dct %}phone{% else %}hdd{% endif %} me-1"></i>{% if has_dct %}Survives social media compression{% else %}Higher capacity for direct transfers{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -483,6 +481,24 @@
|
|||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='js/stegasoo.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/stegasoo.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
|
// ============================================================================
|
||||||
|
// MODE HINT - Dynamic text based on selected embedding mode
|
||||||
|
// ============================================================================
|
||||||
|
const modeHints = {
|
||||||
|
dct: { icon: 'phone', text: 'Survives social media compression' },
|
||||||
|
lsb: { icon: 'hdd', text: 'Higher capacity, outputs Color PNG' }
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelectorAll('input[name="embed_mode"]').forEach(radio => {
|
||||||
|
radio.addEventListener('change', function() {
|
||||||
|
const hint = document.getElementById('modeHint');
|
||||||
|
const data = modeHints[this.value];
|
||||||
|
if (hint && data) {
|
||||||
|
hint.innerHTML = `<i class="bi bi-${data.icon} me-1"></i>${data.text}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// ACCORDION SUMMARY UPDATES
|
// ACCORDION SUMMARY UPDATES
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -666,20 +682,45 @@ carrierInput?.addEventListener('change', function() {
|
|||||||
|
|
||||||
const modeRadios = document.querySelectorAll('input[name="embed_mode"]');
|
const modeRadios = document.querySelectorAll('input[name="embed_mode"]');
|
||||||
const modeBtns = { 'dct': document.getElementById('dctModeCard'), 'lsb': document.getElementById('lsbModeCard') };
|
const modeBtns = { 'dct': document.getElementById('dctModeCard'), 'lsb': document.getElementById('lsbModeCard') };
|
||||||
const dctOptionsRow = document.getElementById('dctOptionsRow');
|
const grayModeInput = document.getElementById('grayMode');
|
||||||
|
const grayModeLabel = document.getElementById('grayModeLabel');
|
||||||
|
const jpegFormatInput = document.getElementById('jpegFormat');
|
||||||
|
const jpegFormatLabel = document.getElementById('jpegFormatLabel');
|
||||||
|
const colorModeInput = document.getElementById('colorMode');
|
||||||
|
const pngFormatInput = document.getElementById('pngFormat');
|
||||||
|
|
||||||
|
function updateOutputOptions(mode) {
|
||||||
|
const isLsb = mode === 'lsb';
|
||||||
|
// LSB only supports Color + PNG
|
||||||
|
if (isLsb) {
|
||||||
|
// Force Color and PNG selection
|
||||||
|
colorModeInput.checked = true;
|
||||||
|
pngFormatInput.checked = true;
|
||||||
|
// Disable Gray and JPEG
|
||||||
|
grayModeInput.disabled = true;
|
||||||
|
jpegFormatInput.disabled = true;
|
||||||
|
grayModeLabel?.classList.add('disabled', 'text-muted');
|
||||||
|
jpegFormatLabel?.classList.add('disabled', 'text-muted');
|
||||||
|
} else {
|
||||||
|
// DCT: enable all options
|
||||||
|
grayModeInput.disabled = false;
|
||||||
|
jpegFormatInput.disabled = false;
|
||||||
|
grayModeLabel?.classList.remove('disabled', 'text-muted');
|
||||||
|
jpegFormatLabel?.classList.remove('disabled', 'text-muted');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
modeRadios.forEach(radio => {
|
modeRadios.forEach(radio => {
|
||||||
radio.addEventListener('change', () => {
|
radio.addEventListener('change', () => {
|
||||||
Object.values(modeBtns).forEach(btn => btn?.classList.remove('active'));
|
Object.values(modeBtns).forEach(btn => btn?.classList.remove('active'));
|
||||||
modeBtns[radio.value]?.classList.add('active');
|
modeBtns[radio.value]?.classList.add('active');
|
||||||
dctOptionsRow?.classList.toggle('d-none', radio.value !== 'dct');
|
updateOutputOptions(radio.value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show DCT options if DCT selected initially
|
// Initialize output options based on initial mode
|
||||||
if (document.getElementById('modeDct')?.checked) {
|
const initialMode = document.querySelector('input[name="embed_mode"]:checked')?.value || 'lsb';
|
||||||
dctOptionsRow?.classList.remove('d-none');
|
updateOutputOptions(initialMode);
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// DUPLICATE FILE CHECK
|
// DUPLICATE FILE CHECK
|
||||||
|
|||||||
Reference in New Issue
Block a user