Add dynamic channel selector feedback with pulse highlight

- Channel select shows contextual info: Auto (server key), Public (no key), Custom (hidden)
- Gold pulse highlight on custom channel input when selected
- Smooth 0.4s animation with subtle glow effect
- Updated encode.html and decode.html with data-fingerprint attributes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-01-04 15:21:02 -05:00
parent fb55878727
commit 355a988405
4 changed files with 46 additions and 9 deletions

View File

@@ -765,18 +765,43 @@ const Stegasoo = {
const customInputId = config.customInputId || 'channelCustomInput'; const customInputId = config.customInputId || 'channelCustomInput';
const keyInputId = config.keyInputId || 'channelKeyInput'; const keyInputId = config.keyInputId || 'channelKeyInput';
const generateBtnId = config.generateBtnId; const generateBtnId = config.generateBtnId;
const serverInfoId = config.serverInfoId || 'channelServerInfo';
const select = document.getElementById(selectId); const select = document.getElementById(selectId);
const customInput = document.getElementById(customInputId); const customInput = document.getElementById(customInputId);
const keyInput = document.getElementById(keyInputId); const keyInput = document.getElementById(keyInputId);
const generateBtn = generateBtnId ? document.getElementById(generateBtnId) : null; const generateBtn = generateBtnId ? document.getElementById(generateBtnId) : null;
const serverInfo = document.getElementById(serverInfoId);
// Show/hide custom input based on selection // Show/hide custom input and server info based on selection
const updateVisibility = () => { const updateVisibility = () => {
const isCustom = select?.value === 'custom'; const value = select?.value;
const isCustom = value === 'custom';
const isPublic = value === 'none';
const isAuto = value === 'auto';
// Custom input visibility
customInput?.classList.toggle('d-none', !isCustom); customInput?.classList.toggle('d-none', !isCustom);
if (isCustom && keyInput) { if (isCustom && keyInput) {
keyInput.focus(); keyInput.focus();
// Pulse highlight effect
customInput?.classList.add('channel-highlight');
setTimeout(() => customInput?.classList.remove('channel-highlight'), 400);
}
// Server info: show for auto, hide for custom, show "no key" for public
if (serverInfo) {
if (isAuto) {
serverInfo.innerHTML = '<i class="bi bi-shield-lock me-1"></i>Server: <code>' + (serverInfo.dataset.fingerprint || '••••-••••-···-••••-••••') + '</code>';
serverInfo.className = 'small text-success mt-2';
serverInfo.classList.remove('d-none');
} else if (isPublic) {
serverInfo.innerHTML = '<i class="bi bi-globe me-1"></i>No channel key will be used';
serverInfo.className = 'small text-muted mt-2';
serverInfo.classList.remove('d-none');
} else {
serverInfo.classList.add('d-none');
}
} }
}; };
@@ -912,7 +937,8 @@ const Stegasoo = {
this.initChannelKey({ this.initChannelKey({
selectId: 'channelSelectDec', selectId: 'channelSelectDec',
customInputId: 'channelCustomInputDec', customInputId: 'channelCustomInputDec',
keyInputId: 'channelKeyInputDec' keyInputId: 'channelKeyInputDec',
serverInfoId: 'channelServerInfoDec'
}); });
// Form submission with channel key validation and mode display // Form submission with channel key validation and mode display

View File

@@ -161,6 +161,17 @@ body {
.btn-group .btn-outline-secondary:hover { .btn-group .btn-outline-secondary:hover {
background-color: rgba(255, 255, 255, 0.08); background-color: rgba(255, 255, 255, 0.08);
} }
/* Channel key highlight pulse */
.channel-highlight {
animation: channel-pulse 0.4s ease;
}
@keyframes channel-pulse {
0% { box-shadow: 0 0 0 0 rgba(254, 232, 98, 0); }
20% { box-shadow: 0 0 9px 1px rgba(254, 232, 98, 0.19); }
40% { box-shadow: 0 0 9px 1px rgba(254, 232, 98, 0.19); }
100% { box-shadow: 0 0 0 0 rgba(254, 232, 98, 0); }
}
.btn-group .btn-outline-primary:first-of-type, .btn-group .btn-outline-primary:first-of-type,
.btn-group .btn-outline-secondary:first-of-type { .btn-group .btn-outline-secondary:first-of-type {
color: #6b4d8a; color: #6b4d8a;

View File

@@ -362,12 +362,12 @@
</select> </select>
<!-- Server channel indicator (compact) --> <!-- Server channel indicator (compact) -->
{% if channel_configured %} <div class="small text-success mt-2 {% if not channel_configured %}d-none{% endif %}" id="channelServerInfoDec" data-fingerprint="{{ channel_fingerprint[:4] }}-••••-···-••••-{{ channel_fingerprint[-4:] if channel_fingerprint else '' }}">
<div class="small text-success mt-2"> {% if channel_configured %}
<i class="bi bi-shield-lock me-1"></i> <i class="bi bi-shield-lock me-1"></i>
Server: <code>{{ channel_fingerprint[:4] }}-••••-···-••••-{{ channel_fingerprint[-4:] }}</code> Server: <code>{{ channel_fingerprint[:4] }}-••••-···-••••-{{ channel_fingerprint[-4:] }}</code>
{% endif %}
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -429,12 +429,12 @@
</select> </select>
<!-- Server channel indicator (compact) --> <!-- Server channel indicator (compact) -->
{% if channel_configured %} <div class="small text-success mt-2 {% if not channel_configured %}d-none{% endif %}" id="channelServerInfo" data-fingerprint="{{ channel_fingerprint[:4] }}-••••-···-••••-{{ channel_fingerprint[-4:] if channel_fingerprint else '' }}">
<div class="small text-success mt-2" id="channelServerInfo"> {% if channel_configured %}
<i class="bi bi-shield-lock me-1"></i> <i class="bi bi-shield-lock me-1"></i>
Server: <code>{{ channel_fingerprint[:4] }}-••••-···-••••-{{ channel_fingerprint[-4:] }}</code> Server: <code>{{ channel_fingerprint[:4] }}-••••-···-••••-{{ channel_fingerprint[-4:] }}</code>
{% endif %}
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>