slightly more UI animations
This commit is contained in:
@@ -3,6 +3,11 @@ import {
|
||||
createBuildHistoryManager,
|
||||
formatFileSize,
|
||||
} from "./build-history-manager.js";
|
||||
import {
|
||||
animateElement,
|
||||
revealElement,
|
||||
staggerChildren,
|
||||
} from "./motion.js";
|
||||
|
||||
const form = document.getElementById("build-form");
|
||||
const buildBtn = document.getElementById("build-btn");
|
||||
@@ -195,12 +200,22 @@ function updateServerUrlCurrentButton() {
|
||||
|
||||
let isBuilding = false;
|
||||
|
||||
function revealBuildResults() {
|
||||
if (buildResults.classList.contains("hidden")) {
|
||||
revealElement(buildResults, { duration: 240, offset: 8 });
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
staggerChildren(buildFilesDiv, { limit: 10, interval: 28, duration: 220, offset: 6 });
|
||||
});
|
||||
}
|
||||
|
||||
function initAccordions() {
|
||||
document.querySelectorAll(".accordion-section").forEach((section) => {
|
||||
const header = section.querySelector(".accordion-header");
|
||||
const body = section.querySelector(".accordion-body");
|
||||
const chevron = section.querySelector(".accordion-chevron");
|
||||
const startOpen = section.dataset.open !== "false";
|
||||
header.setAttribute("aria-expanded", String(startOpen));
|
||||
|
||||
if (!startOpen) {
|
||||
body.classList.add("collapsed");
|
||||
@@ -211,6 +226,12 @@ function initAccordions() {
|
||||
header.addEventListener("click", () => {
|
||||
const nowCollapsed = body.classList.toggle("collapsed");
|
||||
chevron.classList.toggle("rotated", !nowCollapsed);
|
||||
section.dataset.open = String(!nowCollapsed);
|
||||
header.setAttribute("aria-expanded", String(!nowCollapsed));
|
||||
animateElement(header, [
|
||||
{ backgroundColor: "rgba(51, 65, 85, 0.62)" },
|
||||
{ backgroundColor: "rgba(30, 41, 59, 0.5)" },
|
||||
], { duration: 180 });
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -226,6 +247,10 @@ function initBuilderTabs() {
|
||||
panels.forEach((p) => {
|
||||
if (p.dataset.builderPanel === tabName) {
|
||||
p.removeAttribute("hidden");
|
||||
animateElement(p, [
|
||||
{ opacity: 0, transform: "translateY(7px)" },
|
||||
{ opacity: 1, transform: "translateY(0)" },
|
||||
], { duration: 200 });
|
||||
} else {
|
||||
p.setAttribute("hidden", "");
|
||||
}
|
||||
@@ -2020,7 +2045,7 @@ async function startBuild(config) {
|
||||
setSgnTxtButtonState("Building TXT...", "fa-solid fa-spinner fa-spin");
|
||||
}
|
||||
|
||||
buildStatus.classList.remove("hidden");
|
||||
revealElement(buildStatus, { duration: 220, offset: 6 });
|
||||
buildStatusText.textContent = "Starting build...";
|
||||
buildStatus.querySelector("div").className =
|
||||
"flex items-center gap-2 p-3 rounded-lg bg-blue-900/40 border border-blue-700/60";
|
||||
@@ -2136,7 +2161,7 @@ async function streamBuildOutput(buildId, config = {}) {
|
||||
files: info.files,
|
||||
};
|
||||
saveBuildToStorage(buildData.id, buildData);
|
||||
buildResults.classList.remove("hidden");
|
||||
revealBuildResults();
|
||||
displayBuild(buildData);
|
||||
|
||||
if (pendingUpdateAll) {
|
||||
@@ -2181,7 +2206,7 @@ async function streamBuildOutput(buildId, config = {}) {
|
||||
files: info.files,
|
||||
};
|
||||
saveBuildToStorage(buildData.id, buildData);
|
||||
buildResults.classList.remove("hidden");
|
||||
revealBuildResults();
|
||||
displayBuild(buildData);
|
||||
}
|
||||
return;
|
||||
@@ -2250,7 +2275,7 @@ async function streamBuildOutput(buildId, config = {}) {
|
||||
};
|
||||
saveBuildToStorage(data.buildId, buildData);
|
||||
|
||||
buildResults.classList.remove("hidden");
|
||||
revealBuildResults();
|
||||
displayBuild(buildData);
|
||||
|
||||
if (uploadedShareFiles.length > 0) {
|
||||
@@ -2304,7 +2329,7 @@ async function streamBuildOutput(buildId, config = {}) {
|
||||
files: info.files,
|
||||
};
|
||||
saveBuildToStorage(buildData.id, buildData);
|
||||
buildResults.classList.remove("hidden");
|
||||
revealBuildResults();
|
||||
displayBuild(buildData);
|
||||
}
|
||||
} else {
|
||||
@@ -2394,7 +2419,7 @@ function renderShareLinksPanel(items) {
|
||||
}
|
||||
|
||||
function showBuildFiles(files, buildId, expiresAt) {
|
||||
buildResults.classList.remove("hidden");
|
||||
revealBuildResults();
|
||||
buildFilesDiv.innerHTML = "";
|
||||
|
||||
const buildInfoDiv = document.createElement("div");
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { escapeHtml, formatBytes } from "./format.js";
|
||||
import {
|
||||
animateTextChange,
|
||||
emphasizeElement,
|
||||
staggerChildren,
|
||||
swapPanels,
|
||||
} from "./motion.js";
|
||||
|
||||
const clientList = document.getElementById("client-list");
|
||||
const clientSearch = document.getElementById("client-search");
|
||||
@@ -27,22 +33,22 @@ const fetchUrlBtn = document.getElementById("fetch-url-btn");
|
||||
let activeTab = "upload";
|
||||
|
||||
function switchTab(tab) {
|
||||
if (tab === activeTab) return;
|
||||
const outgoing = activeTab === "upload" ? panelUpload : panelUrl;
|
||||
const incoming = tab === "upload" ? panelUpload : panelUrl;
|
||||
activeTab = tab;
|
||||
if (tab === "upload") {
|
||||
tabUpload.classList.add("bg-slate-700", "text-slate-100");
|
||||
tabUpload.classList.remove("text-slate-400");
|
||||
tabUrl.classList.remove("bg-slate-700", "text-slate-100");
|
||||
tabUrl.classList.add("text-slate-400");
|
||||
panelUpload.classList.remove("hidden");
|
||||
panelUrl.classList.add("hidden");
|
||||
} else {
|
||||
tabUrl.classList.add("bg-slate-700", "text-slate-100");
|
||||
tabUrl.classList.remove("text-slate-400");
|
||||
tabUpload.classList.remove("bg-slate-700", "text-slate-100");
|
||||
tabUpload.classList.add("text-slate-400");
|
||||
panelUrl.classList.remove("hidden");
|
||||
panelUpload.classList.add("hidden");
|
||||
}
|
||||
swapPanels(outgoing, incoming);
|
||||
}
|
||||
|
||||
tabUpload.addEventListener("click", () => switchTab("upload"));
|
||||
@@ -228,6 +234,8 @@ function renderClients() {
|
||||
cb.addEventListener("change", handleClientToggle);
|
||||
});
|
||||
|
||||
staggerChildren(clientList, { limit: 12, interval: 18, duration: 180, offset: 5 });
|
||||
|
||||
updateSelectedCount();
|
||||
}
|
||||
|
||||
@@ -243,6 +251,7 @@ function handleClientToggle(e) {
|
||||
|
||||
function updateSelectedCount() {
|
||||
selectedCountSpan.textContent = `${selectedClients.size} selected`;
|
||||
animateTextChange(selectedCountSpan);
|
||||
executeBtn.disabled = selectedClients.size === 0 || !uploaded;
|
||||
if (updateBtn) {
|
||||
updateBtn.disabled = selectedClients.size === 0 || !uploaded;
|
||||
@@ -269,6 +278,7 @@ function matchesClientOs(clientOs, targetOs) {
|
||||
function setUploadStatus(text, tone = "text-slate-400") {
|
||||
uploadStatus.className = `mt-3 text-sm ${tone}`;
|
||||
uploadStatus.textContent = text;
|
||||
animateTextChange(uploadStatus);
|
||||
}
|
||||
|
||||
function setOsBadge(os) {
|
||||
@@ -513,6 +523,7 @@ executeBtn.addEventListener("click", async () => {
|
||||
</div>`;
|
||||
})
|
||||
.join("");
|
||||
staggerChildren(outputContainer, { limit: 16, interval: 30, duration: 220, offset: 7 });
|
||||
|
||||
executeBtn.disabled = false;
|
||||
});
|
||||
@@ -747,6 +758,7 @@ function renderAutoDeployList(items) {
|
||||
</div>`;
|
||||
})
|
||||
.join("");
|
||||
staggerChildren(autoDeployList, { limit: 12, interval: 24, duration: 200, offset: 6 });
|
||||
|
||||
autoDeployList.querySelectorAll(".auto-deploy-toggle").forEach((btn) => {
|
||||
btn.addEventListener("click", async () => {
|
||||
@@ -759,6 +771,7 @@ function renderAutoDeployList(items) {
|
||||
body: JSON.stringify({ enabled: !currentlyEnabled }),
|
||||
});
|
||||
if (res.ok) loadAutoDeploys();
|
||||
if (res.ok) emphasizeElement(btn, "rgba(52, 211, 153, 0.24)");
|
||||
} catch (err) {
|
||||
console.error("Toggle failed:", err);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { formatBytes } from "./filebrowser-utils.js";
|
||||
import { concealElement, revealElement } from "./motion.js";
|
||||
|
||||
export function createTransferPanel({ onCancel }) {
|
||||
const transferPanel = document.getElementById("transfer-panel");
|
||||
@@ -43,7 +44,10 @@ export function createTransferPanel({ onCancel }) {
|
||||
}
|
||||
|
||||
transferList.appendChild(transferItem);
|
||||
transferPanel.classList.remove("hidden");
|
||||
if (transferPanel.classList.contains("hidden")) {
|
||||
revealElement(transferPanel, { duration: 180, offset: 6 });
|
||||
}
|
||||
revealElement(transferItem, { duration: 190, offset: 7 });
|
||||
}
|
||||
|
||||
function updateTransferProgress(transferId, progress, current, total) {
|
||||
@@ -61,14 +65,15 @@ export function createTransferPanel({ onCancel }) {
|
||||
}
|
||||
}
|
||||
|
||||
function removeTransfer(transferId) {
|
||||
async function removeTransfer(transferId) {
|
||||
const transferItem = document.getElementById(`transfer-${transferId}`);
|
||||
if (transferItem) {
|
||||
await concealElement(transferItem, { duration: 130, offset: -5 });
|
||||
transferItem.remove();
|
||||
}
|
||||
|
||||
if (transferList && transferList.children.length === 0) {
|
||||
transferPanel?.classList.add("hidden");
|
||||
await concealElement(transferPanel, { duration: 120, offset: 5 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,12 @@ import { checkFeatureAccess } from "./feature-gate.js";
|
||||
import { createFileHexHashManager } from "./filebrowser-hex-hash.js";
|
||||
import { createFilePreviewModal } from "./filebrowser-preview-modal.js";
|
||||
import { createTransferPanel } from "./filebrowser-transfer-panel.js";
|
||||
import {
|
||||
animateElement,
|
||||
concealElement,
|
||||
revealElement,
|
||||
staggerChildren,
|
||||
} from "./motion.js";
|
||||
import {
|
||||
KNOWN_BINARY_EXTS,
|
||||
PREVIEW_IMAGE_EXTS,
|
||||
@@ -1165,52 +1171,28 @@ function clearVirtualizedListMode() {
|
||||
|
||||
function renderDirectoryStandard(entries, canGoUp, parentPath, disableAnimations) {
|
||||
clearVirtualizedListMode();
|
||||
fileListEl.style.transition = "none";
|
||||
fileListEl.style.opacity = "1";
|
||||
fileListEl.style.transform = "translateX(0)";
|
||||
fileListEl.innerHTML = "";
|
||||
|
||||
if (!disableAnimations) {
|
||||
fileListEl.style.opacity = "0";
|
||||
fileListEl.style.transform = "translateX(20px)";
|
||||
if (canGoUp) fileListEl.appendChild(createParentRow(parentPath));
|
||||
|
||||
if (entries.length === 0 && !canGoUp) {
|
||||
fileListEl.innerHTML =
|
||||
'<div class="px-4 py-6 text-center text-slate-400"><i class="fa-solid fa-folder-open mr-2"></i>Empty directory</div>';
|
||||
} else {
|
||||
fileListEl.style.transition = "none";
|
||||
fileListEl.style.opacity = "1";
|
||||
fileListEl.style.transform = "translateX(0)";
|
||||
const fragment = document.createDocumentFragment();
|
||||
entries.forEach((entry) => fragment.appendChild(createFileRow(entry)));
|
||||
fileListEl.appendChild(fragment);
|
||||
}
|
||||
|
||||
const renderList = () => {
|
||||
fileListEl.innerHTML = "";
|
||||
|
||||
if (canGoUp) {
|
||||
fileListEl.appendChild(createParentRow(parentPath));
|
||||
}
|
||||
|
||||
if (entries.length === 0 && !canGoUp) {
|
||||
fileListEl.innerHTML =
|
||||
'<div class="px-4 py-6 text-center text-slate-400"><i class="fa-solid fa-folder-open mr-2"></i>Empty directory</div>';
|
||||
fileListEl.style.opacity = "1";
|
||||
fileListEl.style.transform = "translateX(0)";
|
||||
return;
|
||||
}
|
||||
|
||||
entries.forEach((entry, index) => {
|
||||
const row = createFileRow(entry);
|
||||
if (!disableAnimations) {
|
||||
row.style.animationDelay = `${index * 0.02}s`;
|
||||
row.classList.add("card-animate");
|
||||
}
|
||||
fileListEl.appendChild(row);
|
||||
});
|
||||
|
||||
if (!disableAnimations) {
|
||||
fileListEl.style.transition =
|
||||
"opacity 0.3s ease-out, transform 0.3s ease-out";
|
||||
fileListEl.style.opacity = "1";
|
||||
fileListEl.style.transform = "translateX(0)";
|
||||
}
|
||||
};
|
||||
|
||||
if (disableAnimations) {
|
||||
renderList();
|
||||
} else {
|
||||
setTimeout(renderList, 150);
|
||||
if (!disableAnimations) {
|
||||
animateElement(fileListEl, [
|
||||
{ opacity: 0.35, transform: "translateX(8px)" },
|
||||
{ opacity: 1, transform: "translateX(0)" },
|
||||
], { duration: 190 });
|
||||
staggerChildren(fileListEl, { limit: 14, interval: 18, duration: 190, offset: 5 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2384,6 +2366,7 @@ function handleCommandResult(msg) {
|
||||
function showContextMenu(x, y, entry) {
|
||||
contextMenu.style.left = `${x}px`;
|
||||
contextMenu.style.top = `${y}px`;
|
||||
contextMenu.style.transformOrigin = `${x > window.innerWidth / 2 ? "right" : "left"} top`;
|
||||
contextMenu.classList.add("show");
|
||||
contextMenu.dataset.path = entry.path;
|
||||
contextMenu.dataset.isDir = entry.isDir;
|
||||
@@ -3036,14 +3019,26 @@ const {
|
||||
requestFilePeek,
|
||||
} = fileHexHashManager;
|
||||
|
||||
function setPreviewPaneVisible(visible) {
|
||||
function setPreviewPaneVisible(visible, animate = true) {
|
||||
if (!previewPaneHost) return;
|
||||
if (visible) {
|
||||
previewPaneHost.classList.remove("hidden");
|
||||
previewPaneHost.classList.remove("preview-pane-user-hidden");
|
||||
if (animate) revealElement(previewPaneHost, { duration: 220, offset: 0, scale: 0.98 });
|
||||
else previewPaneHost.classList.remove("hidden");
|
||||
previewPaneShowBtnHost?.classList.add("hidden");
|
||||
} else {
|
||||
previewPaneHost.classList.add("hidden");
|
||||
previewPaneShowBtnHost?.classList.remove("hidden");
|
||||
if (animate) {
|
||||
concealElement(previewPaneHost, { duration: 140, offset: 0, scale: 0.98 })
|
||||
.then((concealed) => {
|
||||
if (!concealed) return;
|
||||
previewPaneHost.classList.add("preview-pane-user-hidden");
|
||||
revealElement(previewPaneShowBtnHost, { duration: 160, offset: 4 });
|
||||
});
|
||||
} else {
|
||||
previewPaneHost.classList.add("hidden");
|
||||
previewPaneHost.classList.add("preview-pane-user-hidden");
|
||||
previewPaneShowBtnHost?.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
try { localStorage.setItem(PREVIEW_PANE_STATE_KEY, visible ? "1" : "0"); } catch {}
|
||||
}
|
||||
@@ -3053,7 +3048,7 @@ previewPaneShowBtn?.addEventListener("click", () => setPreviewPaneVisible(true))
|
||||
(function initPreviewPaneVisibility() {
|
||||
let saved = "1";
|
||||
try { saved = localStorage.getItem(PREVIEW_PANE_STATE_KEY) || "1"; } catch {}
|
||||
setPreviewPaneVisible(saved !== "0");
|
||||
setPreviewPaneVisible(saved !== "0", false);
|
||||
})();
|
||||
|
||||
function clearPreviewPane() {
|
||||
|
||||
@@ -33,6 +33,29 @@
|
||||
--color-primary-hover: #818cf8;
|
||||
--color-success: #22c55e;
|
||||
--color-danger: #ef4444;
|
||||
|
||||
/* Shared interaction motion */
|
||||
--motion-fast: 140ms;
|
||||
--motion-normal: 220ms;
|
||||
--motion-slow: 360ms;
|
||||
--motion-ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
--motion-ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
}
|
||||
|
||||
.motion-surface {
|
||||
transition:
|
||||
border-color var(--motion-normal) var(--motion-ease-out),
|
||||
background-color var(--motion-normal) var(--motion-ease-out),
|
||||
box-shadow var(--motion-normal) var(--motion-ease-out),
|
||||
opacity var(--motion-normal) var(--motion-ease-out),
|
||||
transform var(--motion-normal) var(--motion-ease-out);
|
||||
}
|
||||
|
||||
.motion-status {
|
||||
transition:
|
||||
color var(--motion-fast) ease,
|
||||
background-color var(--motion-fast) ease,
|
||||
border-color var(--motion-fast) ease;
|
||||
}
|
||||
|
||||
.monaco-editor,
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
const reducedMotionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
const activeAnimations = new WeakMap();
|
||||
const visibilityIntent = new WeakMap();
|
||||
let panelSwapSequence = 0;
|
||||
|
||||
export function prefersReducedMotion() {
|
||||
return reducedMotionQuery.matches;
|
||||
}
|
||||
|
||||
export function animateElement(element, keyframes, options = {}) {
|
||||
if (!element || prefersReducedMotion() || typeof element.animate !== "function") {
|
||||
return null;
|
||||
}
|
||||
|
||||
activeAnimations.get(element)?.cancel();
|
||||
const animation = element.animate(keyframes, {
|
||||
duration: 220,
|
||||
easing: "cubic-bezier(0.2, 0.8, 0.2, 1)",
|
||||
fill: "none",
|
||||
...options,
|
||||
});
|
||||
activeAnimations.set(element, animation);
|
||||
animation.addEventListener("finish", () => {
|
||||
if (activeAnimations.get(element) === animation) activeAnimations.delete(element);
|
||||
}, { once: true });
|
||||
animation.addEventListener("cancel", () => {
|
||||
if (activeAnimations.get(element) === animation) activeAnimations.delete(element);
|
||||
}, { once: true });
|
||||
return animation;
|
||||
}
|
||||
|
||||
export function revealElement(element, options = {}) {
|
||||
if (!element) return null;
|
||||
visibilityIntent.set(element, true);
|
||||
element.classList.remove("hidden");
|
||||
element.removeAttribute("hidden");
|
||||
return animateElement(element, [
|
||||
{ opacity: 0, transform: `translateY(${options.offset ?? 8}px) scale(${options.scale ?? 0.99})` },
|
||||
{ opacity: 1, transform: "translateY(0) scale(1)" },
|
||||
], { duration: options.duration ?? 220 });
|
||||
}
|
||||
|
||||
export async function concealElement(element, options = {}) {
|
||||
if (!element || element.classList.contains("hidden") || element.hasAttribute("hidden")) return false;
|
||||
visibilityIntent.set(element, false);
|
||||
const animation = animateElement(element, [
|
||||
{ opacity: 1, transform: "translateY(0) scale(1)" },
|
||||
{ opacity: 0, transform: `translateY(${options.offset ?? -4}px) scale(${options.scale ?? 0.995})` },
|
||||
], { duration: options.duration ?? 110, easing: "ease-in" });
|
||||
if (animation) {
|
||||
try { await animation.finished; } catch {}
|
||||
}
|
||||
if (visibilityIntent.get(element) === false) {
|
||||
element.classList.add("hidden");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function swapPanels(outgoing, incoming) {
|
||||
if (!incoming || outgoing === incoming) return;
|
||||
const sequence = ++panelSwapSequence;
|
||||
if (outgoing) await concealElement(outgoing, { duration: 90, offset: -3 });
|
||||
if (sequence !== panelSwapSequence) return;
|
||||
revealElement(incoming, { duration: 190, offset: 6 });
|
||||
}
|
||||
|
||||
export function staggerChildren(container, options = {}) {
|
||||
if (!container || prefersReducedMotion()) return;
|
||||
const children = Array.from(container.children).slice(0, options.limit ?? 12);
|
||||
children.forEach((child, index) => {
|
||||
animateElement(child, [
|
||||
{ opacity: 0, transform: `translateY(${options.offset ?? 7}px)` },
|
||||
{ opacity: 1, transform: "translateY(0)" },
|
||||
], {
|
||||
duration: options.duration ?? 200,
|
||||
delay: index * (options.interval ?? 24),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function emphasizeElement(element, tone = "rgba(56, 189, 248, 0.28)") {
|
||||
return animateElement(element, [
|
||||
{ transform: "scale(1)", boxShadow: "0 0 0 0 transparent" },
|
||||
{ transform: "scale(1.012)", boxShadow: `0 0 0 3px ${tone}` },
|
||||
{ transform: "scale(1)", boxShadow: "0 0 0 0 transparent" },
|
||||
], { duration: 420, easing: "ease-out" });
|
||||
}
|
||||
|
||||
export function animateTextChange(element) {
|
||||
return animateElement(element, [
|
||||
{ opacity: 0.45, transform: "translateY(3px)" },
|
||||
{ opacity: 1, transform: "translateY(0)" },
|
||||
], { duration: 160 });
|
||||
}
|
||||
@@ -12,6 +12,13 @@ import {
|
||||
setEasterEggEnabled,
|
||||
showEnabledEasterEgg,
|
||||
} from "./easter-egg.js";
|
||||
import {
|
||||
animateElement,
|
||||
animateTextChange,
|
||||
concealElement,
|
||||
emphasizeElement,
|
||||
revealElement,
|
||||
} from "./motion.js";
|
||||
|
||||
const PREF_REFRESH_KEY = "overlord_refresh_interval_seconds";
|
||||
const NAV_MODE_KEY = "sb_mode";
|
||||
@@ -190,6 +197,7 @@ function showMessage(text, type = "ok") {
|
||||
} else {
|
||||
messageEl.classList.add("text-emerald-200", "border-emerald-700", "bg-emerald-900/30");
|
||||
}
|
||||
animateTextChange(messageEl);
|
||||
}
|
||||
|
||||
function formatDate(timestamp) {
|
||||
@@ -470,7 +478,11 @@ function applyTlsForm() {
|
||||
}
|
||||
|
||||
tlsTrustHelpBtn?.addEventListener("click", () => {
|
||||
tlsTrustInstructions?.classList.toggle("hidden");
|
||||
if (tlsTrustInstructions?.classList.contains("hidden")) {
|
||||
revealElement(tlsTrustInstructions, { duration: 200, offset: 5 });
|
||||
} else {
|
||||
concealElement(tlsTrustInstructions, { duration: 120, offset: -3 });
|
||||
}
|
||||
});
|
||||
|
||||
async function loadActiveTlsCertificate() {
|
||||
@@ -792,7 +804,7 @@ async function startMfaSetup() {
|
||||
if (mfaQrCode) mfaQrCode.innerHTML = data.qrSvg || "";
|
||||
if (mfaSecretText) mfaSecretText.textContent = data.secret || "";
|
||||
if (mfaOtpauthLink) mfaOtpauthLink.href = data.otpauthUrl || "#";
|
||||
if (mfaSetupPanel) mfaSetupPanel.classList.remove("hidden");
|
||||
revealElement(mfaSetupPanel, { duration: 240, offset: 8 });
|
||||
if (mfaEnableCodeInput) mfaEnableCodeInput.focus();
|
||||
} catch {
|
||||
showMfaMessage("Network error while starting MFA setup.", "error");
|
||||
@@ -813,7 +825,7 @@ async function enableMfa() {
|
||||
showMfaMessage(data.error || "Failed to enable MFA.", "error");
|
||||
return;
|
||||
}
|
||||
if (mfaSetupPanel) mfaSetupPanel.classList.add("hidden");
|
||||
concealElement(mfaSetupPanel, { duration: 130, offset: -4 });
|
||||
if (mfaEnableCodeInput) mfaEnableCodeInput.value = "";
|
||||
showMfaMessage("MFA enabled.");
|
||||
await loadMfaStatus();
|
||||
@@ -1396,9 +1408,18 @@ function initSettingsSidebar() {
|
||||
}
|
||||
if (sectionMap.size === 0) return;
|
||||
|
||||
let activeLink = null;
|
||||
function setActive(link) {
|
||||
if (link === activeLink) return;
|
||||
activeLink = link;
|
||||
links.forEach((l) => l.classList.remove("active"));
|
||||
if (link) link.classList.add("active");
|
||||
if (link) {
|
||||
link.classList.add("active");
|
||||
animateElement(link, [
|
||||
{ transform: "translateX(-2px)", opacity: 0.8 },
|
||||
{ transform: "translateX(0)", opacity: 1 },
|
||||
], { duration: 180 });
|
||||
}
|
||||
}
|
||||
|
||||
function isAtPageBottom() {
|
||||
@@ -1421,6 +1442,7 @@ function initSettingsSidebar() {
|
||||
updateSettingsScrollOffset();
|
||||
_clickLockUntil = Date.now() + 1200;
|
||||
setActive(link);
|
||||
emphasizeElement(target, "rgba(56, 189, 248, 0.18)");
|
||||
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
history.replaceState(null, "", `#${id}`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user