diff --git a/frontend/src/lib/components/CrossLink.svelte b/frontend/src/lib/components/CrossLink.svelte
index 6e2b02b..3e4aa85 100644
--- a/frontend/src/lib/components/CrossLink.svelte
+++ b/frontend/src/lib/components/CrossLink.svelte
@@ -1,6 +1,7 @@
-
+
{label}
diff --git a/frontend/src/lib/highlight.ts b/frontend/src/lib/highlight.ts
index 03f975d..2cf9ab5 100644
--- a/frontend/src/lib/highlight.ts
+++ b/frontend/src/lib/highlight.ts
@@ -1,50 +1,63 @@
/**
* Card highlight system for cross-entity navigation.
*
- * When navigating via CrossLink, the target entity ID is passed as ?highlight=.
- * The destination page calls highlightFromUrl() after data loads, which:
- * 1. Shows a dim overlay behind everything
- * 2. Finds the card with [data-entity-id=""]
- * 3. Scrolls to it smoothly
- * 4. Applies a pulsing glow via direct style manipulation
- * 5. Cleans up after 2.5 seconds
+ * CrossLink calls requestHighlight(id) before goto(), storing the ID globally.
+ * The destination page calls highlightFromUrl() after data loads, which
+ * picks up the pending ID and highlights the matching card.
*/
const HIGHLIGHT_DURATION = 2500;
const PULSE_INTERVAL = 400;
const WAIT_TIMEOUT = 5000;
-/** Show dim overlay, find card, scroll & highlight. Call after data loads. */
+/** Pending highlight ID — set by CrossLink before navigation. */
+let _pendingHighlight: string | null = null;
+
+/** Request a card highlight. Called by CrossLink before goto(). */
+export function requestHighlight(id: string | number): void {
+ _pendingHighlight = String(id);
+}
+
+/** Check for pending highlight, find card, scroll & highlight. Call after data loads. */
export function highlightFromUrl(): void {
if (typeof window === 'undefined') return;
- const params = new URLSearchParams(window.location.search);
- const id = params.get('highlight');
+
+ // Check global pending first, then URL param as fallback
+ let id = _pendingHighlight;
+ _pendingHighlight = null;
+
+ if (!id) {
+ const params = new URLSearchParams(window.location.search);
+ id = params.get('highlight');
+ if (id) {
+ params.delete('highlight');
+ const qs = params.toString();
+ const cleanUrl = window.location.pathname + (qs ? '?' + qs : '');
+ window.history.replaceState(null, '', cleanUrl);
+ }
+ }
+
if (!id) return;
- // Clean the URL immediately (remove highlight param, keep others)
- params.delete('highlight');
- const qs = params.toString();
- const cleanUrl = window.location.pathname + (qs ? '?' + qs : '');
- window.history.replaceState(null, '', cleanUrl);
-
- // Try to find the card now, or wait for it
- const card = document.querySelector(`[data-entity-id="${id}"]`);
- if (card) {
- // Small delay for layout to settle after loaded=true
- setTimeout(() => _highlightCard(card as HTMLElement), 100);
- } else {
- _waitForCard(id);
- }
+ // Wait a tick for DOM to render after loaded=true
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ const card = document.querySelector(`[data-entity-id="${id}"]`);
+ if (card) {
+ _highlightCard(card as HTMLElement);
+ } else {
+ _waitForCard(id!);
+ }
+ });
+ });
}
function _highlightCard(card: HTMLElement): void {
- // Show dim overlay
const overlay = _showDimOverlay();
- // Scroll to card
card.scrollIntoView({ behavior: 'smooth', block: 'center' });
- // Save original styles
+ // Save originals
const origBoxShadow = card.style.boxShadow;
const origZIndex = card.style.zIndex;
const origPosition = card.style.position;
@@ -54,11 +67,11 @@ function _highlightCard(card: HTMLElement): void {
card.style.position = 'relative';
card.style.zIndex = '11';
- // Get primary color from CSS variable
+ // Get primary color
const primary = getComputedStyle(document.documentElement)
.getPropertyValue('--color-primary').trim();
- // Pulse effect via interval
+ // 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`;
@@ -74,7 +87,6 @@ function _highlightCard(card: HTMLElement): void {
// Cleanup
setTimeout(() => {
clearInterval(pulseTimer);
- // Fade out: transition the box-shadow away
card.style.transition = 'box-shadow 0.3s ease, border-color 0.3s ease';
card.style.boxShadow = origBoxShadow;
card.style.borderColor = origBorderColor;
@@ -96,7 +108,6 @@ function _showDimOverlay(): HTMLElement {
overlay.className = 'nav-dim-overlay';
document.body.appendChild(overlay);
}
- // Force reflow then activate
void overlay.offsetHeight;
overlay.classList.add('active');
return overlay;
@@ -109,7 +120,7 @@ function _waitForCard(id: string): void {
const card = document.querySelector(`[data-entity-id="${id}"]`);
if (card) {
observer.disconnect();
- setTimeout(() => _highlightCard(card as HTMLElement), 100);
+ setTimeout(() => _highlightCard(card as HTMLElement), 50);
return;
}
if (Date.now() - start > WAIT_TIMEOUT) {