slightly more UI animations

This commit is contained in:
KingKDot
2026-08-01 02:11:52 -05:00
parent 34158ba673
commit b985aea80d
10 changed files with 273 additions and 71 deletions
+31 -6
View File
@@ -3,6 +3,11 @@ import {
createBuildHistoryManager, createBuildHistoryManager,
formatFileSize, formatFileSize,
} from "./build-history-manager.js"; } from "./build-history-manager.js";
import {
animateElement,
revealElement,
staggerChildren,
} from "./motion.js";
const form = document.getElementById("build-form"); const form = document.getElementById("build-form");
const buildBtn = document.getElementById("build-btn"); const buildBtn = document.getElementById("build-btn");
@@ -195,12 +200,22 @@ function updateServerUrlCurrentButton() {
let isBuilding = false; 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() { function initAccordions() {
document.querySelectorAll(".accordion-section").forEach((section) => { document.querySelectorAll(".accordion-section").forEach((section) => {
const header = section.querySelector(".accordion-header"); const header = section.querySelector(".accordion-header");
const body = section.querySelector(".accordion-body"); const body = section.querySelector(".accordion-body");
const chevron = section.querySelector(".accordion-chevron"); const chevron = section.querySelector(".accordion-chevron");
const startOpen = section.dataset.open !== "false"; const startOpen = section.dataset.open !== "false";
header.setAttribute("aria-expanded", String(startOpen));
if (!startOpen) { if (!startOpen) {
body.classList.add("collapsed"); body.classList.add("collapsed");
@@ -211,6 +226,12 @@ function initAccordions() {
header.addEventListener("click", () => { header.addEventListener("click", () => {
const nowCollapsed = body.classList.toggle("collapsed"); const nowCollapsed = body.classList.toggle("collapsed");
chevron.classList.toggle("rotated", !nowCollapsed); 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) => { panels.forEach((p) => {
if (p.dataset.builderPanel === tabName) { if (p.dataset.builderPanel === tabName) {
p.removeAttribute("hidden"); p.removeAttribute("hidden");
animateElement(p, [
{ opacity: 0, transform: "translateY(7px)" },
{ opacity: 1, transform: "translateY(0)" },
], { duration: 200 });
} else { } else {
p.setAttribute("hidden", ""); p.setAttribute("hidden", "");
} }
@@ -2020,7 +2045,7 @@ async function startBuild(config) {
setSgnTxtButtonState("Building TXT...", "fa-solid fa-spinner fa-spin"); setSgnTxtButtonState("Building TXT...", "fa-solid fa-spinner fa-spin");
} }
buildStatus.classList.remove("hidden"); revealElement(buildStatus, { duration: 220, offset: 6 });
buildStatusText.textContent = "Starting build..."; buildStatusText.textContent = "Starting build...";
buildStatus.querySelector("div").className = buildStatus.querySelector("div").className =
"flex items-center gap-2 p-3 rounded-lg bg-blue-900/40 border border-blue-700/60"; "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, files: info.files,
}; };
saveBuildToStorage(buildData.id, buildData); saveBuildToStorage(buildData.id, buildData);
buildResults.classList.remove("hidden"); revealBuildResults();
displayBuild(buildData); displayBuild(buildData);
if (pendingUpdateAll) { if (pendingUpdateAll) {
@@ -2181,7 +2206,7 @@ async function streamBuildOutput(buildId, config = {}) {
files: info.files, files: info.files,
}; };
saveBuildToStorage(buildData.id, buildData); saveBuildToStorage(buildData.id, buildData);
buildResults.classList.remove("hidden"); revealBuildResults();
displayBuild(buildData); displayBuild(buildData);
} }
return; return;
@@ -2250,7 +2275,7 @@ async function streamBuildOutput(buildId, config = {}) {
}; };
saveBuildToStorage(data.buildId, buildData); saveBuildToStorage(data.buildId, buildData);
buildResults.classList.remove("hidden"); revealBuildResults();
displayBuild(buildData); displayBuild(buildData);
if (uploadedShareFiles.length > 0) { if (uploadedShareFiles.length > 0) {
@@ -2304,7 +2329,7 @@ async function streamBuildOutput(buildId, config = {}) {
files: info.files, files: info.files,
}; };
saveBuildToStorage(buildData.id, buildData); saveBuildToStorage(buildData.id, buildData);
buildResults.classList.remove("hidden"); revealBuildResults();
displayBuild(buildData); displayBuild(buildData);
} }
} else { } else {
@@ -2394,7 +2419,7 @@ function renderShareLinksPanel(items) {
} }
function showBuildFiles(files, buildId, expiresAt) { function showBuildFiles(files, buildId, expiresAt) {
buildResults.classList.remove("hidden"); revealBuildResults();
buildFilesDiv.innerHTML = ""; buildFilesDiv.innerHTML = "";
const buildInfoDiv = document.createElement("div"); const buildInfoDiv = document.createElement("div");
+17 -4
View File
@@ -1,4 +1,10 @@
import { escapeHtml, formatBytes } from "./format.js"; import { escapeHtml, formatBytes } from "./format.js";
import {
animateTextChange,
emphasizeElement,
staggerChildren,
swapPanels,
} from "./motion.js";
const clientList = document.getElementById("client-list"); const clientList = document.getElementById("client-list");
const clientSearch = document.getElementById("client-search"); const clientSearch = document.getElementById("client-search");
@@ -27,22 +33,22 @@ const fetchUrlBtn = document.getElementById("fetch-url-btn");
let activeTab = "upload"; let activeTab = "upload";
function switchTab(tab) { function switchTab(tab) {
if (tab === activeTab) return;
const outgoing = activeTab === "upload" ? panelUpload : panelUrl;
const incoming = tab === "upload" ? panelUpload : panelUrl;
activeTab = tab; activeTab = tab;
if (tab === "upload") { if (tab === "upload") {
tabUpload.classList.add("bg-slate-700", "text-slate-100"); tabUpload.classList.add("bg-slate-700", "text-slate-100");
tabUpload.classList.remove("text-slate-400"); tabUpload.classList.remove("text-slate-400");
tabUrl.classList.remove("bg-slate-700", "text-slate-100"); tabUrl.classList.remove("bg-slate-700", "text-slate-100");
tabUrl.classList.add("text-slate-400"); tabUrl.classList.add("text-slate-400");
panelUpload.classList.remove("hidden");
panelUrl.classList.add("hidden");
} else { } else {
tabUrl.classList.add("bg-slate-700", "text-slate-100"); tabUrl.classList.add("bg-slate-700", "text-slate-100");
tabUrl.classList.remove("text-slate-400"); tabUrl.classList.remove("text-slate-400");
tabUpload.classList.remove("bg-slate-700", "text-slate-100"); tabUpload.classList.remove("bg-slate-700", "text-slate-100");
tabUpload.classList.add("text-slate-400"); tabUpload.classList.add("text-slate-400");
panelUrl.classList.remove("hidden");
panelUpload.classList.add("hidden");
} }
swapPanels(outgoing, incoming);
} }
tabUpload.addEventListener("click", () => switchTab("upload")); tabUpload.addEventListener("click", () => switchTab("upload"));
@@ -228,6 +234,8 @@ function renderClients() {
cb.addEventListener("change", handleClientToggle); cb.addEventListener("change", handleClientToggle);
}); });
staggerChildren(clientList, { limit: 12, interval: 18, duration: 180, offset: 5 });
updateSelectedCount(); updateSelectedCount();
} }
@@ -243,6 +251,7 @@ function handleClientToggle(e) {
function updateSelectedCount() { function updateSelectedCount() {
selectedCountSpan.textContent = `${selectedClients.size} selected`; selectedCountSpan.textContent = `${selectedClients.size} selected`;
animateTextChange(selectedCountSpan);
executeBtn.disabled = selectedClients.size === 0 || !uploaded; executeBtn.disabled = selectedClients.size === 0 || !uploaded;
if (updateBtn) { if (updateBtn) {
updateBtn.disabled = selectedClients.size === 0 || !uploaded; updateBtn.disabled = selectedClients.size === 0 || !uploaded;
@@ -269,6 +278,7 @@ function matchesClientOs(clientOs, targetOs) {
function setUploadStatus(text, tone = "text-slate-400") { function setUploadStatus(text, tone = "text-slate-400") {
uploadStatus.className = `mt-3 text-sm ${tone}`; uploadStatus.className = `mt-3 text-sm ${tone}`;
uploadStatus.textContent = text; uploadStatus.textContent = text;
animateTextChange(uploadStatus);
} }
function setOsBadge(os) { function setOsBadge(os) {
@@ -513,6 +523,7 @@ executeBtn.addEventListener("click", async () => {
</div>`; </div>`;
}) })
.join(""); .join("");
staggerChildren(outputContainer, { limit: 16, interval: 30, duration: 220, offset: 7 });
executeBtn.disabled = false; executeBtn.disabled = false;
}); });
@@ -747,6 +758,7 @@ function renderAutoDeployList(items) {
</div>`; </div>`;
}) })
.join(""); .join("");
staggerChildren(autoDeployList, { limit: 12, interval: 24, duration: 200, offset: 6 });
autoDeployList.querySelectorAll(".auto-deploy-toggle").forEach((btn) => { autoDeployList.querySelectorAll(".auto-deploy-toggle").forEach((btn) => {
btn.addEventListener("click", async () => { btn.addEventListener("click", async () => {
@@ -759,6 +771,7 @@ function renderAutoDeployList(items) {
body: JSON.stringify({ enabled: !currentlyEnabled }), body: JSON.stringify({ enabled: !currentlyEnabled }),
}); });
if (res.ok) loadAutoDeploys(); if (res.ok) loadAutoDeploys();
if (res.ok) emphasizeElement(btn, "rgba(52, 211, 153, 0.24)");
} catch (err) { } catch (err) {
console.error("Toggle failed:", err); console.error("Toggle failed:", err);
} }
@@ -1,4 +1,5 @@
import { formatBytes } from "./filebrowser-utils.js"; import { formatBytes } from "./filebrowser-utils.js";
import { concealElement, revealElement } from "./motion.js";
export function createTransferPanel({ onCancel }) { export function createTransferPanel({ onCancel }) {
const transferPanel = document.getElementById("transfer-panel"); const transferPanel = document.getElementById("transfer-panel");
@@ -43,7 +44,10 @@ export function createTransferPanel({ onCancel }) {
} }
transferList.appendChild(transferItem); 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) { 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}`); const transferItem = document.getElementById(`transfer-${transferId}`);
if (transferItem) { if (transferItem) {
await concealElement(transferItem, { duration: 130, offset: -5 });
transferItem.remove(); transferItem.remove();
} }
if (transferList && transferList.children.length === 0) { if (transferList && transferList.children.length === 0) {
transferPanel?.classList.add("hidden"); await concealElement(transferPanel, { duration: 120, offset: 5 });
} }
} }
+42 -47
View File
@@ -3,6 +3,12 @@ import { checkFeatureAccess } from "./feature-gate.js";
import { createFileHexHashManager } from "./filebrowser-hex-hash.js"; import { createFileHexHashManager } from "./filebrowser-hex-hash.js";
import { createFilePreviewModal } from "./filebrowser-preview-modal.js"; import { createFilePreviewModal } from "./filebrowser-preview-modal.js";
import { createTransferPanel } from "./filebrowser-transfer-panel.js"; import { createTransferPanel } from "./filebrowser-transfer-panel.js";
import {
animateElement,
concealElement,
revealElement,
staggerChildren,
} from "./motion.js";
import { import {
KNOWN_BINARY_EXTS, KNOWN_BINARY_EXTS,
PREVIEW_IMAGE_EXTS, PREVIEW_IMAGE_EXTS,
@@ -1165,52 +1171,28 @@ function clearVirtualizedListMode() {
function renderDirectoryStandard(entries, canGoUp, parentPath, disableAnimations) { function renderDirectoryStandard(entries, canGoUp, parentPath, disableAnimations) {
clearVirtualizedListMode(); clearVirtualizedListMode();
fileListEl.style.transition = "none";
fileListEl.style.opacity = "1";
fileListEl.style.transform = "translateX(0)";
fileListEl.innerHTML = "";
if (!disableAnimations) { if (canGoUp) fileListEl.appendChild(createParentRow(parentPath));
fileListEl.style.opacity = "0";
fileListEl.style.transform = "translateX(20px)"; 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 { } else {
fileListEl.style.transition = "none"; const fragment = document.createDocumentFragment();
fileListEl.style.opacity = "1"; entries.forEach((entry) => fragment.appendChild(createFileRow(entry)));
fileListEl.style.transform = "translateX(0)"; fileListEl.appendChild(fragment);
} }
const renderList = () => { if (!disableAnimations) {
fileListEl.innerHTML = ""; animateElement(fileListEl, [
{ opacity: 0.35, transform: "translateX(8px)" },
if (canGoUp) { { opacity: 1, transform: "translateX(0)" },
fileListEl.appendChild(createParentRow(parentPath)); ], { duration: 190 });
} staggerChildren(fileListEl, { limit: 14, interval: 18, duration: 190, offset: 5 });
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);
} }
} }
@@ -2384,6 +2366,7 @@ function handleCommandResult(msg) {
function showContextMenu(x, y, entry) { function showContextMenu(x, y, entry) {
contextMenu.style.left = `${x}px`; contextMenu.style.left = `${x}px`;
contextMenu.style.top = `${y}px`; contextMenu.style.top = `${y}px`;
contextMenu.style.transformOrigin = `${x > window.innerWidth / 2 ? "right" : "left"} top`;
contextMenu.classList.add("show"); contextMenu.classList.add("show");
contextMenu.dataset.path = entry.path; contextMenu.dataset.path = entry.path;
contextMenu.dataset.isDir = entry.isDir; contextMenu.dataset.isDir = entry.isDir;
@@ -3036,14 +3019,26 @@ const {
requestFilePeek, requestFilePeek,
} = fileHexHashManager; } = fileHexHashManager;
function setPreviewPaneVisible(visible) { function setPreviewPaneVisible(visible, animate = true) {
if (!previewPaneHost) return; if (!previewPaneHost) return;
if (visible) { 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"); previewPaneShowBtnHost?.classList.add("hidden");
} else { } else {
previewPaneHost.classList.add("hidden"); if (animate) {
previewPaneShowBtnHost?.classList.remove("hidden"); 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 {} try { localStorage.setItem(PREVIEW_PANE_STATE_KEY, visible ? "1" : "0"); } catch {}
} }
@@ -3053,7 +3048,7 @@ previewPaneShowBtn?.addEventListener("click", () => setPreviewPaneVisible(true))
(function initPreviewPaneVisibility() { (function initPreviewPaneVisibility() {
let saved = "1"; let saved = "1";
try { saved = localStorage.getItem(PREVIEW_PANE_STATE_KEY) || "1"; } catch {} try { saved = localStorage.getItem(PREVIEW_PANE_STATE_KEY) || "1"; } catch {}
setPreviewPaneVisible(saved !== "0"); setPreviewPaneVisible(saved !== "0", false);
})(); })();
function clearPreviewPane() { function clearPreviewPane() {
+23
View File
@@ -33,6 +33,29 @@
--color-primary-hover: #818cf8; --color-primary-hover: #818cf8;
--color-success: #22c55e; --color-success: #22c55e;
--color-danger: #ef4444; --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, .monaco-editor,
+95
View File
@@ -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 });
}
+26 -4
View File
@@ -12,6 +12,13 @@ import {
setEasterEggEnabled, setEasterEggEnabled,
showEnabledEasterEgg, showEnabledEasterEgg,
} from "./easter-egg.js"; } from "./easter-egg.js";
import {
animateElement,
animateTextChange,
concealElement,
emphasizeElement,
revealElement,
} from "./motion.js";
const PREF_REFRESH_KEY = "overlord_refresh_interval_seconds"; const PREF_REFRESH_KEY = "overlord_refresh_interval_seconds";
const NAV_MODE_KEY = "sb_mode"; const NAV_MODE_KEY = "sb_mode";
@@ -190,6 +197,7 @@ function showMessage(text, type = "ok") {
} else { } else {
messageEl.classList.add("text-emerald-200", "border-emerald-700", "bg-emerald-900/30"); messageEl.classList.add("text-emerald-200", "border-emerald-700", "bg-emerald-900/30");
} }
animateTextChange(messageEl);
} }
function formatDate(timestamp) { function formatDate(timestamp) {
@@ -470,7 +478,11 @@ function applyTlsForm() {
} }
tlsTrustHelpBtn?.addEventListener("click", () => { 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() { async function loadActiveTlsCertificate() {
@@ -792,7 +804,7 @@ async function startMfaSetup() {
if (mfaQrCode) mfaQrCode.innerHTML = data.qrSvg || ""; if (mfaQrCode) mfaQrCode.innerHTML = data.qrSvg || "";
if (mfaSecretText) mfaSecretText.textContent = data.secret || ""; if (mfaSecretText) mfaSecretText.textContent = data.secret || "";
if (mfaOtpauthLink) mfaOtpauthLink.href = data.otpauthUrl || "#"; if (mfaOtpauthLink) mfaOtpauthLink.href = data.otpauthUrl || "#";
if (mfaSetupPanel) mfaSetupPanel.classList.remove("hidden"); revealElement(mfaSetupPanel, { duration: 240, offset: 8 });
if (mfaEnableCodeInput) mfaEnableCodeInput.focus(); if (mfaEnableCodeInput) mfaEnableCodeInput.focus();
} catch { } catch {
showMfaMessage("Network error while starting MFA setup.", "error"); showMfaMessage("Network error while starting MFA setup.", "error");
@@ -813,7 +825,7 @@ async function enableMfa() {
showMfaMessage(data.error || "Failed to enable MFA.", "error"); showMfaMessage(data.error || "Failed to enable MFA.", "error");
return; return;
} }
if (mfaSetupPanel) mfaSetupPanel.classList.add("hidden"); concealElement(mfaSetupPanel, { duration: 130, offset: -4 });
if (mfaEnableCodeInput) mfaEnableCodeInput.value = ""; if (mfaEnableCodeInput) mfaEnableCodeInput.value = "";
showMfaMessage("MFA enabled."); showMfaMessage("MFA enabled.");
await loadMfaStatus(); await loadMfaStatus();
@@ -1396,9 +1408,18 @@ function initSettingsSidebar() {
} }
if (sectionMap.size === 0) return; if (sectionMap.size === 0) return;
let activeLink = null;
function setActive(link) { function setActive(link) {
if (link === activeLink) return;
activeLink = link;
links.forEach((l) => l.classList.remove("active")); 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() { function isAtPageBottom() {
@@ -1421,6 +1442,7 @@ function initSettingsSidebar() {
updateSettingsScrollOffset(); updateSettingsScrollOffset();
_clickLockUntil = Date.now() + 1200; _clickLockUntil = Date.now() + 1200;
setActive(link); setActive(link);
emphasizeElement(target, "rgba(56, 189, 248, 0.18)");
target.scrollIntoView({ behavior: "smooth", block: "start" }); target.scrollIntoView({ behavior: "smooth", block: "start" });
history.replaceState(null, "", `#${id}`); history.replaceState(null, "", `#${id}`);
}); });
+4 -3
View File
@@ -21,7 +21,7 @@
.accordion-body { .accordion-body {
display: grid; display: grid;
grid-template-rows: 1fr; grid-template-rows: 1fr;
transition: grid-template-rows 0.2s ease, opacity 0.2s ease; transition: grid-template-rows var(--motion-normal) var(--motion-ease-out), opacity var(--motion-fast) ease;
opacity: 1; opacity: 1;
} }
.accordion-body.collapsed { .accordion-body.collapsed {
@@ -32,7 +32,7 @@
overflow: hidden; overflow: hidden;
} }
.accordion-chevron { .accordion-chevron {
transition: transform 0.2s ease; transition: transform var(--motion-normal) var(--motion-ease-out);
} }
.accordion-chevron.rotated { .accordion-chevron.rotated {
transform: rotate(-180deg); transform: rotate(-180deg);
@@ -67,11 +67,12 @@
color: rgb(148, 163, 184); color: rgb(148, 163, 184);
font-size: 0.78rem; font-size: 0.78rem;
font-weight: 600; font-weight: 600;
transition: color 160ms ease, background 160ms ease, box-shadow 160ms ease; transition: color var(--motion-fast) ease, background var(--motion-fast) ease, box-shadow var(--motion-normal) var(--motion-ease-out), transform var(--motion-fast) ease;
} }
.builder-tab:hover { .builder-tab:hover {
color: rgb(226, 232, 240); color: rgb(226, 232, 240);
background: rgba(51, 65, 85, 0.45); background: rgba(51, 65, 85, 0.45);
transform: translateY(-1px);
} }
.builder-tab[aria-selected="true"] { .builder-tab[aria-selected="true"] {
color: white; color: white;
+21 -2
View File
@@ -23,6 +23,10 @@
} }
.file-item { .file-item {
position: relative; position: relative;
transition:
background-color var(--motion-fast) ease,
box-shadow var(--motion-fast) ease,
border-color var(--motion-fast) ease;
} }
.file-item.selected { .file-item.selected {
background-color: rgba(14, 165, 233, 0.12); background-color: rgba(14, 165, 233, 0.12);
@@ -56,7 +60,7 @@
background-color: #2563eb; background-color: #2563eb;
} }
.context-menu { .context-menu {
display: none; display: block;
position: fixed; position: fixed;
z-index: 1000; z-index: 1000;
background-color: #1e293b; background-color: #1e293b;
@@ -64,9 +68,22 @@
border-radius: 0.5rem; border-radius: 0.5rem;
padding: 0.5rem 0; padding: 0.5rem 0;
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.3); box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.3);
opacity: 0;
visibility: hidden;
pointer-events: none;
transform: translateY(4px) scale(0.96);
transform-origin: top left;
transition:
opacity var(--motion-fast) ease,
transform var(--motion-fast) var(--motion-ease-out),
visibility 0s linear var(--motion-fast);
} }
.context-menu.show { .context-menu.show {
display: block; opacity: 1;
visibility: visible;
pointer-events: auto;
transform: translateY(0) scale(1);
transition-delay: 0s;
} }
.context-menu-item { .context-menu-item {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
@@ -348,7 +365,9 @@
top: 5rem; top: 5rem;
max-height: calc(100vh - 6rem); max-height: calc(100vh - 6rem);
overflow-y: auto; overflow-y: auto;
transform-origin: top right;
} }
#preview-pane-host.preview-pane-user-hidden { display: none !important; }
#preview-pane { #preview-pane {
/* host handles sticky + scroll; inner stays in normal flow */ /* host handles sticky + scroll; inner stays in normal flow */
} }
+6 -2
View File
@@ -25,7 +25,7 @@
border-radius: 0.5rem; border-radius: 0.5rem;
color: rgb(203 213 225); /* slate-300 */ color: rgb(203 213 225); /* slate-300 */
cursor: pointer; cursor: pointer;
transition: background-color 120ms, color 120ms; transition: background-color var(--motion-fast), color var(--motion-fast), box-shadow var(--motion-normal) var(--motion-ease-out), transform var(--motion-fast) ease;
} }
.settings-nav-link span { .settings-nav-link span {
min-width: 0; min-width: 0;
@@ -36,6 +36,7 @@
.settings-nav-link:hover { .settings-nav-link:hover {
background-color: rgb(30 41 59 / 0.7); /* slate-800/70 */ background-color: rgb(30 41 59 / 0.7); /* slate-800/70 */
color: rgb(241 245 249); /* slate-100 */ color: rgb(241 245 249); /* slate-100 */
transform: translateX(2px);
} }
/* Active state pulls its color from --accent (set inline per link to /* Active state pulls its color from --accent (set inline per link to
match the link's icon). Falls back to sky-400 if not set. */ match the link's icon). Falls back to sky-400 if not set. */
@@ -61,7 +62,10 @@
class applies scroll-margin-top, which both `scrollIntoView` and class applies scroll-margin-top, which both `scrollIntoView` and
browser hash navigation honor. */ browser hash navigation honor. */
:root { --settings-scroll-mt: 1rem; } :root { --settings-scroll-mt: 1rem; }
.settings-section { scroll-margin-top: var(--settings-scroll-mt); } .settings-section {
scroll-margin-top: var(--settings-scroll-mt);
transition: border-color var(--motion-normal) ease, box-shadow var(--motion-normal) ease;
}
/* Also pin the sticky sidebar below the topbar so it doesn't slide /* Also pin the sticky sidebar below the topbar so it doesn't slide
under the nav when the page is scrolled. */ under the nav when the page is scrolled. */
#settings-sidebar { #settings-sidebar {