feat: card highlight system for cross-entity navigation

When clicking a CrossLink, the target entity ID is passed as
?highlight=<id> in the URL. The destination page:
1. Shows a semi-transparent dim overlay (z-index: 10)
2. Finds the card with data-entity-id matching the ID
3. Scrolls to it smoothly (block: center)
4. Applies a pulsing primary-color box-shadow animation (z-index: 11)
5. Cleans up overlay + animation after 2 seconds

If the card isn't in DOM yet (data still loading), a MutationObserver
waits up to 5 seconds for it to appear.

Changes:
- New highlight.ts utility with highlightFromUrl(), MutationObserver,
  dim overlay management
- Card component accepts entityId prop → data-entity-id attribute
- CrossLink accepts entityId prop → appends ?highlight=<id> to href
- All 9 entity pages: Card elements have entityId, highlightFromUrl()
  called after data loads
- CSS: cardHighlight keyframe animation + nav-dim-overlay styles
This commit is contained in:
2026-03-21 23:59:25 +03:00
parent 227b9c2e92
commit f0f49db21e
13 changed files with 202 additions and 27 deletions
@@ -13,6 +13,7 @@
import IconButton from '$lib/components/IconButton.svelte';
import CrossLink from '$lib/components/CrossLink.svelte';
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
import { highlightFromUrl } from '$lib/highlight';
function templateName(id: number | null): string {
if (!id) return '';
@@ -67,7 +68,7 @@
commandTemplateConfigsCache.fetch(),
]);
} catch (err: any) { error = err.message || t('common.loadError'); snackError(error); }
finally { loaded = true; }
finally { loaded = true; highlightFromUrl(); }
}
function openNew() {
@@ -238,7 +239,7 @@
{:else}
<div class="space-y-3 stagger-children">
{#each configs as cfg}
<Card hover>
<Card hover entityId={cfg.id}>
<div class="flex items-center justify-between">
<div>
<div class="flex items-center gap-2">
@@ -256,7 +257,7 @@
&middot; {t('commandConfig.defaultCount')}: {cfg.default_count}
</span>
{#if cfg.command_template_config_id}
<CrossLink href="/command-template-configs" icon="mdiCodeBracesBox" label={templateName(cfg.command_template_config_id)} />
<CrossLink href="/command-template-configs" icon="mdiCodeBracesBox" label={templateName(cfg.command_template_config_id)} entityId={cfg.command_template_config_id} />
{/if}
</div>
</div>