Compare commits
2 Commits
18d66c2dba
...
d7a834a992
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7a834a992
|
||
|
|
3bd5752cb5
|
49
js/controls.js
vendored
49
js/controls.js
vendored
@@ -194,6 +194,42 @@ function initControls(manager, wsClient, components) {
|
|||||||
localStorage.setItem(STORAGE_API_KEY, apiKeyInput.value.trim());
|
localStorage.setItem(STORAGE_API_KEY, apiKeyInput.value.trim());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const importKeyBtn = document.getElementById('import-key-btn');
|
||||||
|
const importKeyFile = /** @type {HTMLInputElement | null} */ (
|
||||||
|
document.getElementById('import-key-file')
|
||||||
|
);
|
||||||
|
const promptKeyBtn = document.getElementById('prompt-key-btn');
|
||||||
|
|
||||||
|
function applyImportedKey(key) {
|
||||||
|
if (!apiKeyInput) return;
|
||||||
|
apiKeyInput.value = key;
|
||||||
|
localStorage.setItem(STORAGE_API_KEY, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importKeyBtn && importKeyFile) {
|
||||||
|
importKeyBtn.addEventListener('click', () => {
|
||||||
|
importKeyFile.value = '';
|
||||||
|
importKeyFile.click();
|
||||||
|
});
|
||||||
|
importKeyFile.addEventListener('change', () => {
|
||||||
|
const file = importKeyFile.files && importKeyFile.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function () {
|
||||||
|
var text = typeof reader.result === 'string' ? reader.result.trim() : '';
|
||||||
|
if (text) applyImportedKey(text);
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (promptKeyBtn) {
|
||||||
|
promptKeyBtn.addEventListener('click', () => {
|
||||||
|
var key = window.prompt('Paste or type your API key:');
|
||||||
|
if (key && key.trim()) applyImportedKey(key.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const volumeSlider = document.getElementById('volume-slider');
|
const volumeSlider = document.getElementById('volume-slider');
|
||||||
const volumeValue = document.getElementById('volume-value');
|
const volumeValue = document.getElementById('volume-value');
|
||||||
const themeSound = /** @type {HTMLAudioElement | null} */ (
|
const themeSound = /** @type {HTMLAudioElement | null} */ (
|
||||||
@@ -340,22 +376,25 @@ function initConnectionStatusHandler() {
|
|||||||
const wsDisconnectRow = document.getElementById('ws-disconnect-row');
|
const wsDisconnectRow = document.getElementById('ws-disconnect-row');
|
||||||
const apiUrlInput = document.getElementById('api-url-input');
|
const apiUrlInput = document.getElementById('api-url-input');
|
||||||
const apiKeyInput = document.getElementById('api-key-input');
|
const apiKeyInput = document.getElementById('api-key-input');
|
||||||
|
const importKeyBtn = document.getElementById('import-key-btn');
|
||||||
|
const promptKeyBtn = document.getElementById('prompt-key-btn');
|
||||||
|
|
||||||
return (state, message) => {
|
return (state, message) => {
|
||||||
if (wsStatusDot) wsStatusDot.className = `status-dot ${state}`;
|
if (wsStatusDot) wsStatusDot.className = `status-dot ${state}`;
|
||||||
if (wsStatusText) wsStatusText.textContent = message ?? String(state);
|
if (wsStatusText) wsStatusText.textContent = message ?? String(state);
|
||||||
|
|
||||||
if (state === 'connected') {
|
var isConnected = state === 'connected';
|
||||||
|
if (isConnected) {
|
||||||
if (wsConnectBtn) wsConnectBtn.style.display = 'none';
|
if (wsConnectBtn) wsConnectBtn.style.display = 'none';
|
||||||
if (wsDisconnectRow) wsDisconnectRow.style.display = 'flex';
|
if (wsDisconnectRow) wsDisconnectRow.style.display = 'flex';
|
||||||
if (apiUrlInput) apiUrlInput.disabled = true;
|
|
||||||
if (apiKeyInput) apiKeyInput.disabled = true;
|
|
||||||
} else {
|
} else {
|
||||||
if (wsConnectBtn) wsConnectBtn.style.display = 'block';
|
if (wsConnectBtn) wsConnectBtn.style.display = 'block';
|
||||||
if (wsDisconnectRow) wsDisconnectRow.style.display = 'none';
|
if (wsDisconnectRow) wsDisconnectRow.style.display = 'none';
|
||||||
if (apiUrlInput) apiUrlInput.disabled = false;
|
|
||||||
if (apiKeyInput) apiKeyInput.disabled = false;
|
|
||||||
}
|
}
|
||||||
|
if (apiUrlInput) apiUrlInput.disabled = isConnected;
|
||||||
|
if (apiKeyInput) apiKeyInput.disabled = isConnected;
|
||||||
|
if (importKeyBtn) importKeyBtn.disabled = isConnected;
|
||||||
|
if (promptKeyBtn) promptKeyBtn.disabled = isConnected;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,12 @@
|
|||||||
* @property {HTMLInputElement} line2AppearDuration
|
* @property {HTMLInputElement} line2AppearDuration
|
||||||
* @property {HTMLInputElement} line2HideTime
|
* @property {HTMLInputElement} line2HideTime
|
||||||
* @property {HTMLInputElement} line2HideDuration
|
* @property {HTMLInputElement} line2HideDuration
|
||||||
|
* @property {HTMLInputElement} glowColor
|
||||||
|
* @property {HTMLInputElement} glowIntensity
|
||||||
|
* @property {HTMLInputElement} glowOpacity
|
||||||
|
* @property {HTMLInputElement} glowWhiteIntensity
|
||||||
|
* @property {HTMLInputElement} glowOutline
|
||||||
|
* @property {HTMLInputElement} glowPulseDuration
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class RoomCodeDisplay {
|
class RoomCodeDisplay {
|
||||||
@@ -65,6 +71,8 @@ class RoomCodeDisplay {
|
|||||||
/** @type {number | null} */
|
/** @type {number | null} */
|
||||||
#meterRafId = null;
|
#meterRafId = null;
|
||||||
|
|
||||||
|
/** @type {HTMLStyleElement | null} */
|
||||||
|
#glowStyleEl = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {RoomCodeDisplayElements} elements
|
* @param {RoomCodeDisplayElements} elements
|
||||||
@@ -73,6 +81,12 @@ class RoomCodeDisplay {
|
|||||||
init(elements, inputs) {
|
init(elements, inputs) {
|
||||||
this.#elements = elements;
|
this.#elements = elements;
|
||||||
this.#inputs = inputs;
|
this.#inputs = inputs;
|
||||||
|
|
||||||
|
if (!this.#glowStyleEl) {
|
||||||
|
this.#glowStyleEl = document.createElement('style');
|
||||||
|
this.#glowStyleEl.id = 'glow-keyframes';
|
||||||
|
document.head.appendChild(this.#glowStyleEl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -217,6 +231,9 @@ class RoomCodeDisplay {
|
|||||||
footer.style.color = inputs.footerColor.value;
|
footer.style.color = inputs.footerColor.value;
|
||||||
footer.style.fontSize = `${inputs.footerSize.value}px`;
|
footer.style.fontSize = `${inputs.footerSize.value}px`;
|
||||||
footer.style.transform = `translateY(${inputs.footerOffset.value}px)`;
|
footer.style.transform = `translateY(${inputs.footerOffset.value}px)`;
|
||||||
|
|
||||||
|
this.#rebuildGlowKeyframes();
|
||||||
|
this.#checkFullPulse();
|
||||||
}
|
}
|
||||||
|
|
||||||
#startAnimation() {
|
#startAnimation() {
|
||||||
@@ -370,14 +387,52 @@ class RoomCodeDisplay {
|
|||||||
this.#meterRafId = requestAnimationFrame(step);
|
this.#meterRafId = requestAnimationFrame(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#rebuildGlowKeyframes() {
|
||||||
|
const inputs = this.#inputs;
|
||||||
|
const style = this.#glowStyleEl;
|
||||||
|
if (!inputs || !style) return;
|
||||||
|
|
||||||
|
const outline = Number(inputs.glowOutline?.value ?? 8);
|
||||||
|
const whiteGlow = Number(inputs.glowWhiteIntensity?.value ?? 40);
|
||||||
|
const colorGlow = Number(inputs.glowIntensity?.value ?? 80);
|
||||||
|
const opacity = Number(inputs.glowOpacity?.value ?? 0.6);
|
||||||
|
const duration = Number(inputs.glowPulseDuration?.value ?? 1.2);
|
||||||
|
|
||||||
|
const glowHex = inputs.glowColor?.value ?? '#f35dcb';
|
||||||
|
const r = parseInt(glowHex.slice(1, 3), 16);
|
||||||
|
const g = parseInt(glowHex.slice(3, 5), 16);
|
||||||
|
const b = parseInt(glowHex.slice(5, 7), 16);
|
||||||
|
|
||||||
|
const outlineShadow =
|
||||||
|
`drop-shadow(0 0 ${outline}px black) drop-shadow(0 0 ${outline}px black)`;
|
||||||
|
|
||||||
|
style.textContent = `
|
||||||
|
@keyframes meter-full-pulse {
|
||||||
|
0%, 100% {
|
||||||
|
filter: ${outlineShadow} drop-shadow(0 0 0 rgba(255,255,255,0));
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
filter: ${outlineShadow}
|
||||||
|
drop-shadow(0 0 ${whiteGlow}px rgba(255,255,255,0.95))
|
||||||
|
drop-shadow(0 0 ${colorGlow}px rgba(${r},${g},${b},${opacity}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#header.meter-full-pulse {
|
||||||
|
animation: meter-full-pulse ${duration}s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
#checkFullPulse() {
|
#checkFullPulse() {
|
||||||
const header = this.#elements?.header;
|
const header = this.#elements?.header;
|
||||||
if (!header) return;
|
if (!header) return;
|
||||||
|
|
||||||
if (this.#meterFill >= 1) {
|
if (this.#meterFill >= 1) {
|
||||||
|
this.#rebuildGlowKeyframes();
|
||||||
header.classList.add('meter-full-pulse');
|
header.classList.add('meter-full-pulse');
|
||||||
} else {
|
} else {
|
||||||
header.classList.remove('meter-full-pulse');
|
header.classList.remove('meter-full-pulse');
|
||||||
|
header.style.animation = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#header {
|
#header {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
white-space: nowrap;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%) translateY(-220px);
|
transform: translateX(-50%) translateY(-220px);
|
||||||
color: #f35dcb;
|
color: #f35dcb;
|
||||||
@@ -45,19 +46,12 @@
|
|||||||
filter: drop-shadow(3px 3px 8px rgba(0, 0, 0, 0.8));
|
filter: drop-shadow(3px 3px 8px rgba(0, 0, 0, 0.8));
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes meter-full-pulse {
|
/* meter-full-pulse keyframes are generated dynamically by RoomCodeDisplay */
|
||||||
0% { filter: drop-shadow(3px 3px 8px rgba(0,0,0,0.8)); }
|
|
||||||
50% { filter: drop-shadow(0 0 20px rgba(255,255,255,0.6)) drop-shadow(3px 3px 8px rgba(0,0,0,0.8)); }
|
|
||||||
100% { filter: drop-shadow(3px 3px 8px rgba(0,0,0,0.8)); }
|
|
||||||
}
|
|
||||||
|
|
||||||
#header.meter-full-pulse {
|
|
||||||
animation: meter-full-pulse 1.2s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
#footer {
|
#footer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transform: translateY(160px);
|
transform: translateY(160px);
|
||||||
color: #f35dcb;
|
color: #f35dcb;
|
||||||
@@ -77,6 +71,7 @@
|
|||||||
letter-spacing: 8px;
|
letter-spacing: 8px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.5s ease;
|
transition: opacity 0.5s ease;
|
||||||
@@ -542,6 +537,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Glow Effect Settings Section -->
|
||||||
|
<div class="control-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<span>Glow Effect Settings</span>
|
||||||
|
<span class="toggle-indicator"></span>
|
||||||
|
</div>
|
||||||
|
<div class="section-content">
|
||||||
|
<div class="control-row">
|
||||||
|
<label>Glow Color:</label>
|
||||||
|
<input type="color" id="glow-color-input" value="#f35dcb">
|
||||||
|
</div>
|
||||||
|
<div class="control-row">
|
||||||
|
<label>Glow Intensity (px):</label>
|
||||||
|
<input type="number" id="glow-intensity-input" value="80" min="10" max="200" step="5">
|
||||||
|
</div>
|
||||||
|
<div class="control-row">
|
||||||
|
<label>Glow Opacity:</label>
|
||||||
|
<input type="number" id="glow-opacity-input" value="0.6" min="0" max="1" step="0.05">
|
||||||
|
</div>
|
||||||
|
<div class="control-row">
|
||||||
|
<label>White Glow (px):</label>
|
||||||
|
<input type="number" id="glow-white-intensity-input" value="40" min="0" max="200" step="5">
|
||||||
|
</div>
|
||||||
|
<div class="control-row">
|
||||||
|
<label>Outline Thickness (px):</label>
|
||||||
|
<input type="number" id="glow-outline-input" value="8" min="0" max="30" step="1">
|
||||||
|
</div>
|
||||||
|
<div class="control-row">
|
||||||
|
<label>Pulse Duration (sec):</label>
|
||||||
|
<input type="number" id="glow-pulse-duration-input" value="1.2" min="0.2" max="5" step="0.1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Footer Settings Section -->
|
<!-- Footer Settings Section -->
|
||||||
<div class="control-section">
|
<div class="control-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -735,6 +764,11 @@
|
|||||||
<div class="control-row">
|
<div class="control-row">
|
||||||
<input type="password" id="api-key-input" placeholder="Enter API key">
|
<input type="password" id="api-key-input" placeholder="Enter API key">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="control-row" style="gap: 6px;">
|
||||||
|
<button id="import-key-btn" title="Import API key from a .txt file">Import from File</button>
|
||||||
|
<button id="prompt-key-btn" title="Enter API key via dialog (paste works)">Paste Key</button>
|
||||||
|
<input type="file" id="import-key-file" accept=".txt,.key" style="display: none;">
|
||||||
|
</div>
|
||||||
<div class="control-row">
|
<div class="control-row">
|
||||||
<button id="ws-connect-btn">Connect</button>
|
<button id="ws-connect-btn">Connect</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -855,6 +889,12 @@
|
|||||||
line2AppearDuration: document.getElementById('line2-appear-duration'),
|
line2AppearDuration: document.getElementById('line2-appear-duration'),
|
||||||
line2HideTime: document.getElementById('line2-hide-time'),
|
line2HideTime: document.getElementById('line2-hide-time'),
|
||||||
line2HideDuration: document.getElementById('line2-hide-duration'),
|
line2HideDuration: document.getElementById('line2-hide-duration'),
|
||||||
|
glowColor: document.getElementById('glow-color-input'),
|
||||||
|
glowIntensity: document.getElementById('glow-intensity-input'),
|
||||||
|
glowOpacity: document.getElementById('glow-opacity-input'),
|
||||||
|
glowWhiteIntensity: document.getElementById('glow-white-intensity-input'),
|
||||||
|
glowOutline: document.getElementById('glow-outline-input'),
|
||||||
|
glowPulseDuration: document.getElementById('glow-pulse-duration-input'),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
manager.registerComponent('roomCode', roomCodeDisplay);
|
manager.registerComponent('roomCode', roomCodeDisplay);
|
||||||
|
|||||||
Reference in New Issue
Block a user