From 86115f5c758fd1a25189a7d383940916305d90f2 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Sun, 22 Mar 2026 00:13:53 +0300 Subject: [PATCH] fix: search palette triggers highlight, restore CSS keyframe blink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SearchPalette now calls requestHighlight(id) before goto() - Restore smooth CSS @keyframes cardHighlight (0%→none, 25%→glow, 75%→glow, 100%→none) instead of JS interval pulse - Inline style.animation overrides class-based stagger animation; cleanup sets animation:'none' (inline beats class, no stagger replay) --- frontend/src/app.css | 8 ++- .../src/lib/components/SearchPalette.svelte | 2 + frontend/src/lib/highlight.ts | 60 +++++-------------- 3 files changed, 25 insertions(+), 45 deletions(-) diff --git a/frontend/src/app.css b/frontend/src/app.css index 9eca936..1be9f4c 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -184,7 +184,13 @@ a:focus-visible { font-family: var(--font-mono); } -/* Card highlight dim overlay for cross-entity navigation */ +/* Card highlight for cross-entity navigation */ +@keyframes cardHighlight { + 0%, 100% { box-shadow: none; } + 25%, 75% { box-shadow: 0 0 0 3px var(--color-primary), 0 0 20px color-mix(in srgb, var(--color-primary) 30%, transparent); } +} + +/* Dim overlay behind highlighted card */ .nav-dim-overlay { position: fixed; inset: 0; diff --git a/frontend/src/lib/components/SearchPalette.svelte b/frontend/src/lib/components/SearchPalette.svelte index 8a446a6..fb28034 100644 --- a/frontend/src/lib/components/SearchPalette.svelte +++ b/frontend/src/lib/components/SearchPalette.svelte @@ -2,6 +2,7 @@ import { goto } from '$app/navigation'; import { t } from '$lib/i18n'; import MdiIcon from './MdiIcon.svelte'; + import { requestHighlight } from '$lib/highlight'; import { fetchAllCaches, providersCache, @@ -140,6 +141,7 @@ function navigateTo(result: SearchResult) { closePalette(); + requestHighlight(result.id); goto(result.href); } diff --git a/frontend/src/lib/highlight.ts b/frontend/src/lib/highlight.ts index 2cf9ab5..2a68d8a 100644 --- a/frontend/src/lib/highlight.ts +++ b/frontend/src/lib/highlight.ts @@ -1,19 +1,19 @@ /** * Card highlight system for cross-entity navigation. * - * CrossLink calls requestHighlight(id) before goto(), storing the ID globally. + * CrossLink/SearchPalette call requestHighlight(id) before goto(). * The destination page calls highlightFromUrl() after data loads, which - * picks up the pending ID and highlights the matching card. + * picks up the pending ID and highlights the matching card with a + * smooth CSS keyframe glow + dim overlay. */ -const HIGHLIGHT_DURATION = 2500; -const PULSE_INTERVAL = 400; +const HIGHLIGHT_DURATION = 2000; const WAIT_TIMEOUT = 5000; -/** Pending highlight ID — set by CrossLink before navigation. */ +/** Pending highlight ID — set before navigation. */ let _pendingHighlight: string | null = null; -/** Request a card highlight. Called by CrossLink before goto(). */ +/** Request a card highlight. Called before goto(). */ export function requestHighlight(id: string | number): void { _pendingHighlight = String(id); } @@ -39,7 +39,7 @@ export function highlightFromUrl(): void { if (!id) return; - // Wait a tick for DOM to render after loaded=true + // Wait for DOM to render after loaded=true requestAnimationFrame(() => { requestAnimationFrame(() => { const card = document.querySelector(`[data-entity-id="${id}"]`); @@ -55,49 +55,21 @@ export function highlightFromUrl(): void { function _highlightCard(card: HTMLElement): void { const overlay = _showDimOverlay(); + // Scroll to card card.scrollIntoView({ behavior: 'smooth', block: 'center' }); - // Save originals - const origBoxShadow = card.style.boxShadow; - const origZIndex = card.style.zIndex; - const origPosition = card.style.position; - const origBorderColor = card.style.borderColor; - - // Elevate above overlay + // Apply highlight via inline style (overrides stagger CSS class animation) + card.style.animation = 'cardHighlight 2s ease-in-out'; card.style.position = 'relative'; card.style.zIndex = '11'; - // Get primary color - const primary = getComputedStyle(document.documentElement) - .getPropertyValue('--color-primary').trim(); - - // Pulsing glow - let on = true; - const glowOn = `0 0 0 3px ${primary}, 0 0 24px ${primary}60`; - const glowOff = `0 0 0 2px ${primary}80`; - - card.style.boxShadow = glowOn; - card.style.borderColor = primary; - - const pulseTimer = setInterval(() => { - on = !on; - card.style.boxShadow = on ? glowOn : glowOff; - }, PULSE_INTERVAL); - - // Cleanup + // Cleanup: set animation to 'none' (inline beats class, prevents stagger replay) setTimeout(() => { - clearInterval(pulseTimer); - card.style.transition = 'box-shadow 0.3s ease, border-color 0.3s ease'; - card.style.boxShadow = origBoxShadow; - card.style.borderColor = origBorderColor; - - setTimeout(() => { - card.style.position = origPosition; - card.style.zIndex = origZIndex; - card.style.removeProperty('transition'); - overlay.classList.remove('active'); - setTimeout(() => overlay.remove(), 300); - }, 300); + card.style.animation = 'none'; + card.style.removeProperty('position'); + card.style.removeProperty('z-index'); + overlay.classList.remove('active'); + setTimeout(() => overlay.remove(), 300); }, HIGHLIGHT_DURATION); }