<script lang="ts">
	import { onMount } from 'svelte';
	import { slide } from 'svelte/transition';
	import { goto } from '$app/navigation';
	import { api, parseDate } from '$lib/api';
	import { requestHighlight } from '$lib/highlight';
	import { t } from '$lib/i18n';
	import {
		providersCache,
		notificationTrackersCache,
		targetsCache,
	} from '$lib/stores/caches.svelte';
	import Loading from '$lib/components/Loading.svelte';
	import MdiIcon from '$lib/components/MdiIcon.svelte';
	import NavIcon from '$lib/components/NavIcon.svelte';
	import EventChart from '$lib/components/EventChart.svelte';
	import IconGridSelect from '$lib/components/IconGridSelect.svelte';
	import ConfirmModal from '$lib/components/ConfirmModal.svelte';
	import EventDetailModal from '$lib/components/EventDetailModal.svelte';
	import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
	import { eventTypeFilterItems, refreshIntervalItems, sortFilterItems, providerStatsModeItems, providerDefaultIcon } from '$lib/grid-items';
	import { globalProviderFilter } from '$lib/stores/provider-filter.svelte';
	import { getDescriptor } from '$lib/providers';

	import type { DashboardStatus, EventLog } from '$lib/types';

	const SECTIONS_KEY = 'dashboard_section_state';
	type SectionKey = 'stream' | 'on_watch' | 'pulse' | 'wires';
	function loadSectionState(): Record<SectionKey, boolean> {
		const defaults: Record<SectionKey, boolean> = { stream: true, on_watch: true, pulse: true, wires: true };
		if (typeof localStorage === 'undefined') return defaults;
		try {
			const raw = localStorage.getItem(SECTIONS_KEY);
			if (raw) return { ...defaults, ...JSON.parse(raw) };
			// Migrate legacy single-key for the chart
			const legacy = localStorage.getItem('dashboard_chart_visible');
			if (legacy !== null) defaults.pulse = legacy !== 'false';
		} catch { /* ignore */ }
		return defaults;
	}
	let sectionExpanded = $state<Record<SectionKey, boolean>>(loadSectionState());
	function toggleSection(key: SectionKey) {
		sectionExpanded = { ...sectionExpanded, [key]: !sectionExpanded[key] };
		if (typeof localStorage !== 'undefined') {
			localStorage.setItem(SECTIONS_KEY, JSON.stringify(sectionExpanded));
		}
	}

	let status = $state<DashboardStatus | null>(null);
	let providers = $derived(providersCache.items);
	const providerFilterItems = $derived([
		{ value: '', label: t('dashboard.allProviders'), icon: 'mdiFilterOff' },
		...providers.map(p => ({ value: p.id, label: p.name, icon: providerDefaultIcon(p), desc: p.type })),
	]);
	let chartDays = $state<{ date: string; [eventType: string]: string | number }[]>([]);
	let loaded = $state(false);
	let error = $state('');

	let displayProviders = $state(0);
	let displayActive = $state(0);
	let displayTotal = $state(0);
	let displayTargets = $state(0);
	let displayCommandTrackers = $state(0);

	// Event filters
	let filterEventType = $state('');
	let filterProviderId = $state('');
	let effectiveProviderId = $derived(globalProviderFilter.id ? String(globalProviderFilter.id) : filterProviderId);
	let filterSearch = $state('');
	let filterSort = $state('newest');

	const EVENTS_PER_PAGE_KEY = 'dashboard_events_per_page';
	function loadEventsPerPage(): number {
		if (typeof localStorage === 'undefined') return 10;
		const stored = localStorage.getItem(EVENTS_PER_PAGE_KEY);
		return stored ? parseInt(stored, 10) || 10 : 10;
	}

	// "On watch" provider deck stats scope. ``'page'`` = derive counts from
	// the events visible on the current page (legacy behavior); ``'all'`` =
	// use the server-aggregated ``provider_event_counts`` map covering every
	// event that matches the active filters.
	const PROVIDER_STATS_MODE_KEY = 'dashboard_provider_stats_mode';
	function loadProviderStatsMode(): string {
		if (typeof localStorage === 'undefined') return 'page';
		const stored = localStorage.getItem(PROVIDER_STATS_MODE_KEY);
		return stored === 'all' ? 'all' : 'page';
	}

	// Auto-refresh: 0 = off, otherwise seconds between refreshes.
	// Allowed cadences are defined in ``refreshIntervalItems()`` — keep
	// this whitelist in sync with that helper so a stale localStorage
	// value can't smuggle in an unsupported interval (e.g. someone
	// hand-edits to 1).
	const EVENTS_REFRESH_KEY = 'dashboard_events_refresh_seconds';
	const ALLOWED_REFRESH_SECONDS = new Set([0, 10, 30, 60, 300]);
	function loadRefreshSeconds(): number {
		if (typeof localStorage === 'undefined') return 0;
		const stored = localStorage.getItem(EVENTS_REFRESH_KEY);
		const v = stored ? parseInt(stored, 10) : 0;
		return ALLOWED_REFRESH_SECONDS.has(v) ? v : 0;
	}

	let eventsLimit = $state(loadEventsPerPage());
	let eventsOffset = $state(0);
	let eventsLoading = $state(false);
	let confirmClearEvents = $state(false);
	let refreshSeconds = $state(loadRefreshSeconds());
	let providerStatsMode = $state(loadProviderStatsMode());
	let selectedEvent = $state<EventLog | null>(null);
	// Stagger entry animation should play once on initial load only —
	// without this, every pagination/filter change re-runs the cascade
	// (~600ms of fade-up per row) which reads as the panel "reconstructing".
	let eventsAnimated = $state(false);

	// Auto-refresh ticker — re-creates the interval whenever the user
	// changes the cadence. ``$effect`` returns a cleanup that fires on
	// destroy AND on any tracked dep change, so the prior timer is torn
	// down before a new one starts.
	$effect(() => {
		if (refreshSeconds <= 0) return;
		// Pause auto-refresh when the tab is hidden so we don't burn API
		// calls on a tab the user can't see — we'll catch up on the next
		// visibility flip via ``visibilitychange`` below.
		const tick = () => {
			if (typeof document !== 'undefined' && document.hidden) return;
			loadEvents({ silent: true });
			loadChart();
		};
		const handle = setInterval(tick, refreshSeconds * 1000);
		return () => clearInterval(handle);
	});

	// Persist whenever the cadence changes (the IconGridSelect mutates
	// ``refreshSeconds`` directly via bind:value).
	let _refreshHydrated = false;
	$effect(() => {
		const v = refreshSeconds;
		if (!_refreshHydrated) { _refreshHydrated = true; return; }
		if (typeof localStorage !== 'undefined') localStorage.setItem(EVENTS_REFRESH_KEY, String(v));
	});

	// Persist the provider deck stats mode the same way.
	let _providerStatsHydrated = false;
	$effect(() => {
		const v = providerStatsMode;
		if (!_providerStatsHydrated) { _providerStatsHydrated = true; return; }
		if (typeof localStorage !== 'undefined') localStorage.setItem(PROVIDER_STATS_MODE_KEY, v);
	});

	async function clearEvents() {
		try {
			const res = await api<{ deleted: number }>('/status/events', { method: 'DELETE' });
			snackSuccess(t('snack.eventsCleared').replace('{count}', String(res.deleted)));
			eventsOffset = 0;
			await loadEvents();
			await loadChart();
		} catch (err: unknown) {
			snackError(err instanceof Error ? err.message : t('common.error'));
		} finally {
			confirmClearEvents = false;
		}
	}

	let currentPage = $derived(Math.floor(eventsOffset / eventsLimit) + 1);
	let totalPages = $derived(status ? Math.ceil((status.total_events || 0) / eventsLimit) : 0);

	function animateCount(from: number, to: number, setter: (v: number) => void, duration = 600) {
		if (to === 0) { setter(0); return; }
		const start = performance.now();
		function frame(now: number) {
			const elapsed = now - start;
			const progress = Math.min(elapsed / duration, 1);
			const eased = 1 - Math.pow(1 - progress, 3);
			setter(Math.round(from + (to - from) * eased));
			if (progress < 1) requestAnimationFrame(frame);
		}
		requestAnimationFrame(frame);
	}

	/** Build filter query string (shared by events list + chart). */
	function buildFilterParams(): URLSearchParams {
		const params = new URLSearchParams();
		if (filterEventType) params.set('event_type', filterEventType);
		if (effectiveProviderId) params.set('provider_id', effectiveProviderId);
		if (filterSearch) params.set('search', filterSearch);
		return params;
	}

	/** Reload the events panel.
	 *
	 * ``silent`` is set by the auto-refresh ticker so the loading
	 * placeholder doesn't flash and the row list isn't disturbed when
	 * nothing actually changed.  We diff the new payload against the
	 * current ``status`` and reuse the existing ``recent_events`` array
	 * reference when the ID list is identical — that lets Svelte's keyed
	 * ``{#each}`` skip its diff entirely instead of patching every row.
	 */
	async function loadEvents(opts: { silent?: boolean } = {}) {
		if (!opts.silent) eventsLoading = true;
		try {
			const params = buildFilterParams();
			params.set('sort', filterSort);
			params.set('limit', String(eventsLimit));
			params.set('offset', String(eventsOffset));
			const qs = params.toString();
			const next = await api<DashboardStatus>(`/status${qs ? '?' + qs : ''}`);

			if (opts.silent && status && _sameEventIds(status.recent_events, next.recent_events)) {
				// Nothing changed in the visible page. Update only the
				// out-of-band counts so the header and pager stay accurate;
				// keep the existing array reference so no row re-renders.
				status = {
					...status,
					providers: next.providers,
					trackers: next.trackers,
					targets: next.targets,
					total_events: next.total_events,
					command_trackers: next.command_trackers,
					provider_event_counts: next.provider_event_counts,
				};
				return;
			}
			status = next;
		} catch (err: unknown) {
			error = err instanceof Error ? err.message : t('common.error');
		} finally {
			if (!opts.silent) eventsLoading = false;
		}
	}

	function _sameEventIds(a: { id: number }[], b: { id: number }[]): boolean {
		if (a.length !== b.length) return false;
		for (let i = 0; i < a.length; i++) if (a[i].id !== b[i].id) return false;
		return true;
	}

	async function loadChart() {
		try {
			const params = buildFilterParams();
			const qs = params.toString();
			const chartRes = await api<{ days: { date: string; [k: string]: string | number }[] }>(`/status/chart${qs ? '?' + qs : ''}`);
			chartDays = chartRes.days || [];
		} catch (e) { console.warn('Failed to load chart data:', e); }
	}

	// Auto-apply when filter values change (via IconGridSelect bind:value)
	let _prevFilterKey = '';
	$effect(() => {
		const key = `${filterEventType}|${effectiveProviderId}|${filterSort}`;
		if (loaded && key !== _prevFilterKey && _prevFilterKey !== '') {
			applyFilters();
		}
		_prevFilterKey = key;
	});

	function applyFilters() {
		eventsOffset = 0;
		loadEvents();
		loadChart();
	}

	function goToPage(page: number) {
		eventsOffset = (page - 1) * eventsLimit;
		loadEvents();
	}

	let searchTimeout: ReturnType<typeof setTimeout>;
	function onSearchInput() {
		clearTimeout(searchTimeout);
		searchTimeout = setTimeout(applyFilters, 300);
	}

	onMount(() => {
		loadInitial();
		return () => {
			clearTimeout(searchTimeout);
		};
	});

	async function loadInitial() {
		try {
			const [statusRes, , chartRes] = await Promise.all([
				api<DashboardStatus>(`/status?limit=${eventsLimit}`),
				providersCache.fetch(),
				api<{ days: { date: string; [k: string]: string | number }[] }>('/status/chart'),
			]);
			status = statusRes;
			chartDays = chartRes.days || [];
			setTimeout(() => {
				if (!status) return;
				animateCount(0, status.providers, (v) => displayProviders = v);
				animateCount(0, status.trackers.active, (v) => displayActive = v);
				animateCount(0, status.trackers.total, (v) => displayTotal = v);
				animateCount(0, status.targets, (v) => displayTargets = v);
				if (status.command_trackers !== undefined) {
					animateCount(0, status.command_trackers, (v) => displayCommandTrackers = v);
				}
			}, 200);
		} catch (err: unknown) {
			error = err instanceof Error ? err.message : t('common.error');
		} finally {
			loaded = true;
		}
	}

	// Disable stagger entry animation once the first non-empty list has
	// rendered + had time to play. Subsequent pagination/filter reloads
	// then settle in place instead of re-running the cascade.
	$effect(() => {
		if (eventsAnimated) return;
		if (!status?.recent_events?.length) return;
		const handle = setTimeout(() => { eventsAnimated = true; }, 700);
		return () => clearTimeout(handle);
	});

	const filteredProviderCount = $derived(globalProviderFilter.providerType
		? providers.filter(p => p.type === globalProviderFilter.providerType).length
		: displayProviders);

	// === Provider deck — derive activity counts from recent events ===
	//
	// ``providerStatsMode`` controls the scope: ``'page'`` derives counts
	// from the visible page (legacy), ``'all'`` uses the server-aggregated
	// ``provider_event_counts`` map covering every event under the active
	// filters regardless of pagination.
	const providerEventCounts = $derived.by(() => {
		const counts = new Map<string, number>();
		if (!status) return counts;
		if (providerStatsMode === 'all' && status.provider_event_counts) {
			for (const [name, total] of Object.entries(status.provider_event_counts)) {
				if (!name) continue;
				counts.set(name, total);
			}
			return counts;
		}
		for (const ev of status.recent_events) {
			const k = ev.provider_name || '';
			if (!k) continue;
			counts.set(k, (counts.get(k) || 0) + (ev.assets_count || 1));
		}
		return counts;
	});
	// Tracker-derived stats (only recompute when the tracker list itself
	// changes — not on every poll tick when only event counts move).
	const providerTrackerStats = $derived.by(() => {
		const stats = new Map<number, { trackerCount: number; armedCount: number }>();
		for (const t of notificationTrackersCache.items) {
			const cur = stats.get(t.provider_id) || { trackerCount: 0, armedCount: 0 };
			cur.trackerCount += 1;
			if ((t as { enabled?: boolean }).enabled !== false) cur.armedCount += 1;
			stats.set(t.provider_id, cur);
		}
		return stats;
	});
	// Provider static fields — only recompute when providers array changes.
	const providerStaticDeck = $derived(providers.map(p => ({
		id: p.id,
		name: p.name,
		type: p.type,
		icon: providerDefaultIcon(p),
		descriptor: getDescriptor(p.type),
	})));
	const providerDeck = $derived.by(() => {
		const max = Math.max(1, ...Array.from(providerEventCounts.values()));
		return providerStaticDeck.map(p => {
			const stats = providerTrackerStats.get(p.id) || { trackerCount: 0, armedCount: 0 };
			const events = providerEventCounts.get(p.name) || 0;
			return {
				...p,
				trackerCount: stats.trackerCount,
				armedCount: stats.armedCount,
				events,
				share: events / max,
			};
		}).sort((a, b) => b.events - a.events);
	});

	// === Active wires — derive top tracker → target routes ===
	// Static topology: only recomputes when trackers / targets / providers change,
	// not on every status poll. Event counts are merged in below.
	const wireTopology = $derived.by(() => {
		const list: Array<{
			trackerName: string;
			providerName: string;
			providerType: string;
			providerIcon: string;
			targetName: string;
			targetType: string;
			targetIcon: string;
		}> = [];
		const targetsById = new Map(targetsCache.items.map(tg => [tg.id, tg]));
		const providersById = new Map(providers.map(p => [p.id, p]));
		for (const tracker of notificationTrackersCache.items) {
			const provider = providersById.get(tracker.provider_id);
			if (!provider) continue;
			const links = (tracker as { tracker_targets?: { target_id: number }[] }).tracker_targets || [];
			for (const link of links) {
				const target = targetsById.get(link.target_id);
				if (!target) continue;
				list.push({
					trackerName: tracker.name,
					providerName: provider.name,
					providerType: provider.type,
					providerIcon: providerDefaultIcon(provider),
					targetName: target.name,
					targetType: target.type,
					targetIcon: target.icon || `mdi${target.type[0].toUpperCase() + target.type.slice(1)}`,
				});
			}
		}
		return list;
	});
	const trackerEventCount = $derived.by(() => {
		const map = new Map<string, number>();
		if (status) {
			for (const ev of status.recent_events) {
				const k = ev.tracker_name || '';
				if (!k) continue;
				map.set(k, (map.get(k) || 0) + (ev.assets_count || 1));
			}
		}
		return map;
	});
	const activeWires = $derived(
		wireTopology
			.map(w => ({ ...w, events: trackerEventCount.get(w.trackerName) || 0 }))
			.sort((a, b) => b.events - a.events)
			.slice(0, 6)
	);

	// === Hero summary sentence === */
	const heroSummary = $derived.by(() => {
		if (!status) return null;
		const failing = status.recent_events.filter(e => /fail|error/.test(e.event_type)).length;
		return {
			armed: status.trackers.active,
			total: status.trackers.total,
			providers: status.providers,
			targets: status.targets,
			throughput: status.total_events,
			failing,
			allOk: failing === 0,
		};
	});

	function scrollToEvents(e: MouseEvent) {
		e.preventDefault();
		const el = document.getElementById('events-section');
		if (!el) return;
		el.scrollIntoView({ behavior: 'smooth', block: 'start' });
	}

	function gotoProvider(e: MouseEvent, providerId: number) {
		e.preventDefault();
		requestHighlight(providerId);
		goto('/providers');
	}

	function timeAgo(dateStr: string): string {
		const diff = Date.now() - parseDate(dateStr).getTime();
		const mins = Math.floor(diff / 60000);
		if (mins < 1) return t('dashboard.justNow');
		if (mins < 60) return t('dashboard.minutesAgo').replace('{n}', String(mins));
		const hours = Math.floor(mins / 60);
		if (hours < 24) return t('dashboard.hoursAgo').replace('{n}', String(hours));
		return t('dashboard.daysAgo').replace('{n}', String(Math.floor(hours / 24)));
	}

	function timeShort(dateStr: string): string {
		const d = parseDate(dateStr);
		return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
	}

	const eventLabels: Record<string, string> = {
		assets_added: 'dashboard.assetsAdded',
		assets_removed: 'dashboard.assetsRemoved',
		collection_renamed: 'dashboard.collectionRenamed',
		collection_deleted: 'dashboard.collectionDeleted',
		sharing_changed: 'dashboard.sharingChanged',
		scheduled_message: 'dashboard.scheduledMessage',
		action_success: 'dashboard.actionSuccess',
		action_partial: 'dashboard.actionPartial',
		action_failed: 'dashboard.actionFailed',
		command_handled: 'dashboard.commandHandled',
		command_rate_limited: 'dashboard.commandRateLimited',
		command_failed: 'dashboard.commandFailed',
	};

	const eventIcons: Record<string, string> = {
		assets_added: 'mdiImagePlus', assets_removed: 'mdiImageMinus',
		collection_renamed: 'mdiRename', collection_deleted: 'mdiDeleteAlert', sharing_changed: 'mdiShareVariant',
		scheduled_message: 'mdiCalendarClock',
		action_success: 'mdiPlayCircle', action_partial: 'mdiAlertCircle', action_failed: 'mdiCloseCircle',
		command_handled: 'mdiChat', command_rate_limited: 'mdiTimerSandPaused', command_failed: 'mdiAlertCircle',
	};

	// Aurora gradient palette per event type — used for the avatar tile
	const eventGradients: Record<string, [string, string]> = {
		assets_added:        ['var(--color-mint)',    'var(--color-sky)'],
		assets_removed:      ['var(--color-coral)',   'var(--color-orchid)'],
		collection_renamed:  ['var(--color-primary)', 'var(--color-orchid)'],
		collection_deleted:  ['var(--color-coral)',   'var(--color-citrus)'],
		sharing_changed:     ['var(--color-citrus)',  'var(--color-coral)'],
		scheduled_message:   ['var(--color-sky)',     'var(--color-mint)'],
		action_success:      ['var(--color-mint)',    'var(--color-primary)'],
		action_partial:      ['var(--color-citrus)',  'var(--color-orchid)'],
		action_failed:       ['var(--color-coral)',   'var(--color-orchid)'],
		command_handled:     ['var(--color-sky)',     'var(--color-primary)'],
		command_rate_limited:['var(--color-citrus)',  'var(--color-orchid)'],
		command_failed:      ['var(--color-coral)',   'var(--color-orchid)'],
	};

	const STAT_ACCENTS = [
		'var(--color-primary)',
		'var(--color-sky)',
		'var(--color-citrus)',
		'var(--color-orchid)',
	];
</script>

{#if !loaded}
	<Loading lines={4} />
{:else if error}
	<div class="hero-card error-card">
		<div class="flex items-center gap-2" style="color: var(--color-error-fg);">
			<MdiIcon name="mdiAlertCircle" size={20} />
			<p class="text-sm">{error}</p>
		</div>
	</div>
{:else if status && heroSummary}

	<!-- ==================== HERO ==================== -->
	<section class="hero-card">
		<div class="hero-row">
			<div class="hero-main">
				<div class="hero-crumb">
					<span>{t('dashboard.title')}</span>
					<span class="sep">·</span>
					<span class="live">
						<span class="live-dot"></span>
						{heroSummary.allOk ? t('dashboard.live') : t('dashboard.attention')}
					</span>
				</div>
				<h1 class="hero-title">
					{t('dashboard.heroPrefix')} <em>{t('dashboard.heroEmphasis')}</em><br>
					{t('dashboard.heroSuffix')}
				</h1>
				<p class="hero-sub">
					{t('dashboard.heroSummary')
						.replace('{providers}', String(heroSummary.providers))
						.replace('{armed}', String(heroSummary.armed))
						.replace('{total}', String(heroSummary.total))
						.replace('{throughput}', String(heroSummary.throughput))
						.replace('{targets}', String(heroSummary.targets))}
				</p>
			</div>
			<div class="hero-meter">
				<div class="hero-meter-label">{t('dashboard.throughput24h')}</div>
				<div class="hero-meter-value font-mono">{heroSummary.throughput.toLocaleString()}<sup>{t('dashboard.eventsShort')}</sup></div>
				<div class="hero-meter-row">
					<span class="pill"><span class="dot" style="background: var(--color-mint)"></span><b>{heroSummary.armed}/{heroSummary.total}</b> {t('dashboard.armedShort')}</span>
					<span class="pill"><span class="dot" style="background: var(--color-sky)"></span><b>{heroSummary.providers}</b> {t('dashboard.providersShort')}</span>
					<span class="pill"><span class="dot" style="background: var(--color-orchid)"></span><b>{heroSummary.targets}</b> {t('dashboard.targetsShort')}</span>
				</div>
			</div>
		</div>
	</section>

	<!-- ==================== STATS ==================== -->
	{#snippet statCardSnippet(card: {icon: string; label: string; literalLabel?: string; value: number; literalValue?: string; suffix?: string; accent: string; href: string; onclick?: (e: MouseEvent) => void}, idx: number)}
		<a class="stat-card" style="--accent: {card.accent}" href={card.href} onclick={card.onclick}>
			<div class="stat-card-inner">
				<div class="flex items-center gap-3">
					<div class="stat-icon" style="color: {card.accent};">
						<MdiIcon name={card.icon} size={20} />
					</div>
					<div class="min-w-0">
						<p class="text-sm" style="color: var(--color-muted-foreground);">{card.literalLabel || t(card.label)}</p>
						<p class="{card.literalValue ? 'stat-value-text' : 'stat-value'} font-mono" style="animation-delay: {idx * 80 + 200}ms;">
							{#if card.literalValue}{card.literalValue}{:else}{card.value}{/if}{#if card.suffix}<span class="stat-suffix">{card.suffix}</span>{/if}
						</p>
					</div>
				</div>
			</div>
		</a>
	{/snippet}

	{#snippet statCards()}
		<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 stagger-children">
			{#if globalProviderFilter.provider}
				{@render statCardSnippet({
					icon: providerDefaultIcon(globalProviderFilter.provider),
					label: '',
					literalLabel: getDescriptor(globalProviderFilter.provider.type)?.defaultName ?? globalProviderFilter.provider.type,
					value: 0,
					literalValue: globalProviderFilter.provider.name,
					accent: STAT_ACCENTS[0],
					href: '/providers',
				}, 0)}
			{:else}
				{@render statCardSnippet({
					icon: 'mdiServer',
					label: 'dashboard.providers',
					value: filteredProviderCount,
					accent: STAT_ACCENTS[0],
					href: '/providers',
				}, 0)}
			{/if}
			{@render statCardSnippet({
				icon: 'mdiRadar',
				label: 'dashboard.activeTrackers',
				value: displayActive,
				suffix: ` / ${displayTotal}`,
				accent: STAT_ACCENTS[1],
				href: '/notification-trackers',
			}, 1)}
			{@render statCardSnippet({
				icon: 'mdiTarget',
				label: 'dashboard.targets',
				value: displayTargets,
				accent: STAT_ACCENTS[2],
				href: '/targets',
			}, 2)}
			{#if status?.command_trackers !== undefined}
				{@render statCardSnippet({
					icon: 'mdiConsoleLine',
					label: 'nav.commandTrackers',
					value: displayCommandTrackers,
					accent: STAT_ACCENTS[3],
					href: '/command-trackers',
				}, 3)}
			{:else}
				{@render statCardSnippet({
					icon: 'mdiPulse',
					label: 'dashboard.eventsTotal',
					value: heroSummary?.throughput ?? 0,
					accent: STAT_ACCENTS[3],
					href: '#events-section',
					onclick: scrollToEvents,
				}, 3)}
			{/if}
		</div>
	{/snippet}
	{@render statCards()}

	<!-- ==================== TWO COL: stream + provider deck ==================== -->
	<div class="two-col" class:two-col--single={!!globalProviderFilter.id}>
		<!-- Signal stream -->
		<section class="panel" id="events-section">
			<header class="panel-head">
				<div>
					<h2 class="panel-title">{t('dashboard.streamTitle')} <em>{t('dashboard.streamEmphasis')}</em></h2>
					<p class="panel-meta">
						<b>{status.total_events}</b> {t('dashboard.eventsLabel')}
					</p>
				</div>
				<div class="panel-head-actions">
					{#if status.total_events > 0}
						<button type="button" onclick={() => confirmClearEvents = true}
							class="clear-events-btn flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg transition-colors"
							title={t('dashboard.clearEvents')}>
							<MdiIcon name="mdiTrashCanOutline" size={14} />
							<span class="hidden sm:inline">{t('dashboard.clearEvents')}</span>
						</button>
					{/if}
					<button type="button" onclick={() => toggleSection('stream')}
						class="ghost-icon-btn"
						title={sectionExpanded.stream ? t('common.hide') : t('common.show')}>
						<NavIcon name={sectionExpanded.stream ? 'mdiChevronDown' : 'mdiChevronRight'} size={16} />
					</button>
				</div>
			</header>

			{#if sectionExpanded.stream}
			<div in:slide={{ duration: 200 }}>
			<div class="panel-filters">
				<div class="flex-1 min-w-[150px] max-w-[280px]">
					<input type="text" bind:value={filterSearch} oninput={onSearchInput}
						placeholder={t('dashboard.searchEvents')}
						class="search-input" />
				</div>
				<div class="w-40"><IconGridSelect items={eventTypeFilterItems()} bind:value={filterEventType} columns={3} compact /></div>
				{#if !globalProviderFilter.id}<div class="w-40"><IconGridSelect items={providerFilterItems} bind:value={filterProviderId} columns={2} compact /></div>{/if}
				<div class="w-36"><IconGridSelect items={sortFilterItems()} bind:value={filterSort} columns={2} compact /></div>
				<div class="w-44" title={t('dashboard.autoRefreshTitle')}>
					<IconGridSelect items={refreshIntervalItems()}
						bind:value={refreshSeconds}
						columns={5} compact />
				</div>
			</div>

			{#snippet paginator()}
				<div class="flex items-center justify-center gap-1">
					{#if totalPages > 1}
						<button onclick={() => goToPage(currentPage - 1)} disabled={currentPage <= 1}
							class="paginator-btn">
							<NavIcon name="mdiChevronLeft" size={14} />
						</button>
						{#each Array.from({ length: totalPages }, (_, i) => i + 1) as p}
							{#if p === 1 || p === totalPages || (p >= currentPage - 1 && p <= currentPage + 1)}
								<button onclick={() => goToPage(p)}
									class="paginator-num font-mono {p === currentPage ? 'active' : ''}">
									{p}
								</button>
							{:else if p === currentPage - 2 || p === currentPage + 2}
								<span class="paginator-ellipsis">…</span>
							{/if}
						{/each}
						<button onclick={() => goToPage(currentPage + 1)} disabled={currentPage >= totalPages}
							class="paginator-btn">
							<NavIcon name="mdiChevronRight" size={14} />
						</button>
					{/if}
					<select value={eventsLimit}
						onchange={(e) => { const v = parseInt((e.target as HTMLSelectElement).value, 10); eventsLimit = v; eventsOffset = 0; if (typeof localStorage !== 'undefined') localStorage.setItem(EVENTS_PER_PAGE_KEY, String(v)); loadEvents(); }}
						class="paginator-select">
						{#each [5, 10, 20, 50] as n}
							<option value={n}>{n}</option>
						{/each}
					</select>
				</div>
			{/snippet}

			{#if status.recent_events.length === 0}
				{#if eventsLoading}
					<div class="empty-state"><p>{t('dashboard.loadingEvents')}</p></div>
				{:else}
					<div class="empty-state">
						<MdiIcon name="mdiCalendarBlank" size={36} />
						<p>{t('dashboard.noEvents')}</p>
					</div>
				{/if}
			{:else}
				<div class="signal-list"
					class:stagger-children={!eventsAnimated}
					class:signal-list--reloading={eventsLoading}
					aria-busy={eventsLoading}>
					{#each status.recent_events as event, i (event.id)}
						<button type="button" class="signal-row signal-row--clickable"
							style={eventsAnimated ? '' : `animation-delay: ${i * 60}ms;`}
							onclick={() => selectedEvent = event}
							aria-label={t('events.detailTitle')}>
							<div class="signal-avatar"
								style="--g1: {eventGradients[event.event_type]?.[0] ?? 'var(--color-primary)'}; --g2: {eventGradients[event.event_type]?.[1] ?? 'var(--color-orchid)'};">
								<MdiIcon name={eventIcons[event.event_type] || 'mdiBell'} size={18} />
							</div>
							<div class="signal-body min-w-0">
								<div class="signal-sentence">
									{#if event.provider_name}<b class="signal-emph">{event.provider_name}</b>{/if}
									<span class="signal-verb">{t(eventLabels[event.event_type] || event.event_type)}</span>
									{#if event.assets_count > 0}
										<b class="signal-emph">{event.assets_count} {event.assets_count === 1 ? t('dashboard.asset') : t('dashboard.assets')}</b>
									{/if}
									{#if event.collection_name}
										<span class="signal-conn">·</span>
										<b class="signal-emph signal-emph--accent truncate">«{event.collection_name}»</b>
									{/if}
								</div>
								{#if event.details?.dispatch_status === 'deferred' && event.details?.deferred_until}
									<span class="dispatch-badge dispatch-badge--deferred"
										title={t('dashboard.deferredTitle')}>
										<MdiIcon name="mdiPauseCircleOutline" size={12} />
										{t('dashboard.heldUntil')} {timeShort(event.details.deferred_until)}
									</span>
								{:else if event.details?.dispatch_status === 'delivered_after_quiet_hours'}
									<span class="dispatch-badge dispatch-badge--late"
										title={t('dashboard.deliveredLateTitle')}>
										<MdiIcon name="mdiClockCheckOutline" size={12} />
										{t('dashboard.deliveredLate')}
									</span>
								{:else if event.details?.dispatch_status === 'deferred_then_dropped'}
									<span class="dispatch-badge dispatch-badge--dropped"
										title={t('dashboard.deferredThenDroppedTitle')}>
										<MdiIcon name="mdiCloseCircleOutline" size={12} />
										{t('dashboard.deferredThenDropped')}
									</span>
								{:else if event.details?.dispatch_status === 'deferred_then_failed'}
									<span class="dispatch-badge dispatch-badge--dropped"
										title={event.details?.reason ?? ''}>
										<MdiIcon name="mdiAlertCircleOutline" size={12} />
										{t('dashboard.deferredThenFailed')}
									</span>
								{:else if event.details?.dispatch_status === 'suppressed_quiet_hours_nondeferrable'}
									<span class="dispatch-badge dispatch-badge--dropped"
										title={t('dashboard.suppressedNondeferrableTitle')}>
										<MdiIcon name="mdiVolumeOff" size={12} />
										{t('dashboard.suppressedQuietHours')}
									</span>
								{/if}
								{#if event.event_type?.startsWith('command_')}
									{@const issuer = event.details?.issuer as { id?: number; username?: string; first_name?: string; last_name?: string } | undefined}
									{@const issuerLabel = issuer
										? (issuer.username ? '@' + issuer.username : [issuer.first_name, issuer.last_name].filter(Boolean).join(' ') || ('id ' + issuer.id))
										: ''}
									<div class="signal-trail">
										{#if event.bot_name}
											<span class="ch"><MdiIcon name="mdiRobotHappy" size={11} />{event.bot_name}</span>
										{/if}
										{#if event.collection_id}
											{#if event.bot_name}<span class="arrow">→</span>{/if}
											<span class="ch"><MdiIcon name="mdiChatProcessing" size={11} />{event.collection_id}</span>
										{/if}
										{#if issuerLabel}
											<span class="arrow">→</span>
											<span class="ch"><MdiIcon name="mdiAccount" size={11} />{issuerLabel}</span>
										{/if}
										{#if event.provider_name}
											<span class="arrow">→</span>
											<span class="ch"><MdiIcon name="mdiServer" size={11} />{event.provider_name}</span>
										{/if}
									</div>
								{:else if event.tracker_name}
									<div class="signal-trail">
										<span class="ch"><MdiIcon name="mdiRadar" size={11} />{event.tracker_name}</span>
										{#if event.provider_name}
											<span class="arrow">→</span>
											<span class="ch"><MdiIcon name="mdiServer" size={11} />{event.provider_name}</span>
										{/if}
									</div>
								{/if}
							</div>
							<div class="signal-when font-mono">
								<b>{timeShort(event.created_at)}</b>
								<small>{timeAgo(event.created_at)}</small>
							</div>
						</button>
					{/each}
				</div>

				<div class="panel-foot">
					{@render paginator()}
				</div>
			{/if}
			</div>
			{/if}
		</section>

		<!-- On watch — provider deck. Hidden when a global provider filter is
		     active (the deck would only show that one provider — redundant). -->
		{#if !globalProviderFilter.id}
		<section class="panel">
			<header class="panel-head">
				<div>
					<h2 class="panel-title">{t('dashboard.onWatchTitle')} <em>{t('dashboard.onWatchEmphasis')}</em></h2>
					<p class="panel-meta"><b>{providerDeck.length}</b> {t('dashboard.providersShort')}</p>
				</div>
				<div class="panel-head-actions">
					<div class="w-32" title={t('dashboard.statsModeTitle')}>
						<IconGridSelect items={providerStatsModeItems()}
							bind:value={providerStatsMode}
							columns={2} compact />
					</div>
					<button type="button" onclick={() => toggleSection('on_watch')}
						class="ghost-icon-btn"
						title={sectionExpanded.on_watch ? t('common.hide') : t('common.show')}>
						<NavIcon name={sectionExpanded.on_watch ? 'mdiChevronDown' : 'mdiChevronRight'} size={16} />
					</button>
				</div>
			</header>

			{#if sectionExpanded.on_watch}
			<div in:slide={{ duration: 200 }}>
			{#if providerDeck.length === 0}
				<div class="empty-state">
					<MdiIcon name="mdiServerNetwork" size={32} />
					<p>{t('dashboard.noProviders')}</p>
					<a href="/providers" class="empty-state-link">{t('dashboard.addProvider')} →</a>
				</div>
			{:else}
				<div class="provider-deck">
					{#each providerDeck as p}
						<a href="/providers" class="provider-row" style="--accent: {STAT_ACCENTS[p.id % STAT_ACCENTS.length]}" onclick={(e) => gotoProvider(e, p.id)}>
							<div class="provider-icon">
								<MdiIcon name={p.icon} size={20} />
							</div>
							<div class="min-w-0 flex-1">
								<div class="provider-name">
									{#if p.armedCount > 0}<span class="aurora-pulse"></span>{:else}<span class="aurora-pulse idle"></span>{/if}
									<span class="truncate min-w-0">{p.name}</span>
								</div>
								<div class="provider-sub font-mono">
									{p.descriptor?.defaultName ?? p.type} · {p.trackerCount} {t('dashboard.trackersShort')}
								</div>
							</div>
							<div class="provider-meter">
								<div class="provider-num font-mono">{p.events}</div>
								<div class="provider-bar"><div class="provider-bar-fill" style="width: {Math.max(6, p.share * 100)}%"></div></div>
							</div>
						</a>
					{/each}
					<a href="/providers/new" class="provider-row provider-row--add">
						<div class="provider-icon dashed">
							<NavIcon name="mdiPlus" size={18} />
						</div>
						<div class="min-w-0 flex-1">
							<div class="provider-name muted">{t('dashboard.addProvider')}</div>
							<div class="provider-sub font-mono">{t('dashboard.addProviderHint')}</div>
						</div>
					</a>
				</div>
			{/if}
			</div>
			{/if}
		</section>
		{/if}
	</div>

	<!-- ==================== PULSE CHART ==================== -->
	<section class="panel">
		<header class="panel-head">
			<div>
				<h2 class="panel-title">{t('dashboard.pulseTitle')} <em>{t('dashboard.pulseEmphasis')}</em></h2>
				<p class="panel-meta">{t('dashboard.pulseSub')}</p>
			</div>
			<button type="button" onclick={() => toggleSection('pulse')}
				class="ghost-icon-btn"
				title={sectionExpanded.pulse ? t('common.hide') : t('common.show')}>
				<NavIcon name={sectionExpanded.pulse ? 'mdiChevronDown' : 'mdiChevronRight'} size={16} />
			</button>
		</header>
		{#if sectionExpanded.pulse}
			<div class="panel-body" in:slide={{ duration: 200 }}>
				<EventChart days={chartDays} />
			</div>
		{/if}
	</section>

	<!-- ==================== ACTIVE WIRES ==================== -->
	{#if activeWires.length > 0}
		<section class="panel">
			<header class="panel-head">
				<div>
					<h2 class="panel-title">{t('dashboard.wiresTitle')} <em>{t('dashboard.wiresEmphasis')}</em></h2>
					<p class="panel-meta"><b>{activeWires.length}</b> {t('dashboard.wiresSub')}</p>
				</div>
				<button type="button" onclick={() => toggleSection('wires')}
					class="ghost-icon-btn"
					title={sectionExpanded.wires ? t('common.hide') : t('common.show')}>
					<NavIcon name={sectionExpanded.wires ? 'mdiChevronDown' : 'mdiChevronRight'} size={16} />
				</button>
			</header>
			{#if sectionExpanded.wires}
			<div class="wires-body" in:slide={{ duration: 200 }}>
				{#each activeWires as wire}
					<div class="wire">
						<div class="wire-from">
							<MdiIcon name={wire.providerIcon} size={15} />
							<div class="min-w-0">
								<div class="wire-name truncate">{wire.trackerName}</div>
								<div class="wire-sub font-mono truncate">{wire.providerName}</div>
							</div>
						</div>
						<div class="wire-pipe">
							<span class="wire-count font-mono">{wire.events > 0 ? wire.events : '—'}</span>
						</div>
						<div class="wire-to">
							<div class="min-w-0">
								<div class="wire-name truncate">{wire.targetName}</div>
								<div class="wire-sub font-mono truncate">{wire.targetType}</div>
							</div>
							<MdiIcon name={wire.targetIcon} size={15} />
						</div>
					</div>
				{/each}
			</div>
			{/if}
		</section>
	{/if}

	<!-- ==================== COMPOSE BAND ==================== -->
	<section class="compose-band">
		<div class="compose-body">
			<h3 class="compose-title">
				{t('dashboard.composeTitle')} <em>{t('dashboard.composeEmphasis')}</em>
			</h3>
			<p class="compose-sub">{t('dashboard.composeSub')}</p>
		</div>
		<div class="compose-cta">
			<a href="/notification-trackers" class="ghost-btn">
				<NavIcon name="mdiRadar" size={14} />
				{t('dashboard.viewTrackers')}
			</a>
			<a href="/notification-trackers" class="primary-btn-aurora">
				<NavIcon name="mdiArrowRight" size={14} />
				{t('dashboard.newTracker')}
			</a>
		</div>
	</section>
{/if}

<ConfirmModal open={confirmClearEvents} message={t('dashboard.confirmClearEvents')}
	onconfirm={clearEvents} oncancel={() => confirmClearEvents = false} />

<EventDetailModal event={selectedEvent} onclose={() => selectedEvent = null} />

<style>
	/* ============================================================
	   HERO
	============================================================ */
	.hero-card {
		position: relative;
		background: var(--color-glass);
		backdrop-filter: blur(28px) saturate(160%);
		-webkit-backdrop-filter: blur(28px) saturate(160%);
		border: 1px solid var(--color-border);
		border-radius: 22px;
		box-shadow: var(--shadow-card);
		padding: 2rem 2.4rem;
		margin-bottom: 1.5rem;
		overflow: hidden;
	}
	.hero-card::after {
		content: '';
		position: absolute; inset: 0;
		border-radius: inherit;
		pointer-events: none;
		background: linear-gradient(180deg, var(--color-highlight), transparent 30%);
		opacity: 0.4;
	}
	.error-card { padding: 1rem 1.4rem; }
	.hero-row {
		position: relative; z-index: 1;
		display: grid;
		grid-template-columns: 1fr auto;
		gap: 2rem;
		align-items: end;
	}
	.hero-crumb {
		display: flex; align-items: center; gap: 0.6rem;
		font-size: 0.78rem;
		color: var(--color-muted-foreground);
		margin-bottom: 0.85rem;
		font-weight: 500;
	}
	.hero-crumb .sep { color: var(--color-rule-strong); }
	.hero-crumb .live {
		display: inline-flex; align-items: center; gap: 0.4rem;
		color: var(--color-mint);
	}
	.live-dot {
		width: 6px; height: 6px; border-radius: 50%;
		background: var(--color-mint);
		box-shadow: 0 0 8px var(--color-mint);
		animation: aurora-pulse 1.4s ease-in-out infinite;
	}
	.hero-title {
		font-family: var(--font-display);
		font-weight: 400;
		font-size: 3rem;
		line-height: 1;
		letter-spacing: -0.025em;
		margin: 0 0 0.85rem;
		color: var(--color-foreground);
	}
	.hero-title em {
		font-style: italic;
		background: linear-gradient(135deg, var(--color-orchid), var(--color-primary) 60%, var(--color-sky));
		-webkit-background-clip: text;
		background-clip: text;
		color: transparent;
	}
	.hero-sub {
		font-size: 0.95rem;
		color: var(--color-muted-foreground);
		max-width: 36rem;
		line-height: 1.55;
	}
	.hero-meter { text-align: right; }
	.hero-meter-label {
		font-size: 0.68rem;
		text-transform: uppercase;
		letter-spacing: 0.13em;
		color: var(--color-muted-foreground);
		margin-bottom: 0.55rem;
		font-weight: 500;
	}
	.hero-meter-value {
		font-size: 3rem;
		font-weight: 500;
		font-variant-numeric: tabular-nums;
		line-height: 1;
		letter-spacing: -0.03em;
		color: var(--color-foreground);
	}
	.hero-meter-value sup {
		font-size: 0.75rem;
		color: var(--color-muted-foreground);
		margin-left: 0.4rem;
		font-weight: 400;
		background: var(--color-glass-strong);
		padding: 0.25rem 0.5rem;
		border-radius: 0.5rem;
		vertical-align: middle;
		font-family: var(--font-sans);
	}
	.hero-meter-row {
		margin-top: 0.85rem;
		display: flex; gap: 0.5rem;
		justify-content: flex-end;
		flex-wrap: wrap;
	}
	.pill {
		display: inline-flex; align-items: center; gap: 0.4rem;
		padding: 0.3rem 0.7rem;
		border-radius: 999px;
		background: var(--color-glass-strong);
		border: 1px solid var(--color-border);
		font-size: 0.72rem;
		color: var(--color-muted-foreground);
	}
	.pill b {
		color: var(--color-foreground);
		font-weight: 500;
		font-variant-numeric: tabular-nums;
	}
	.pill .dot { width: 6px; height: 6px; border-radius: 50%; }

	@media (max-width: 880px) {
		.hero-card { padding: 1.4rem 1.4rem 1.6rem; }
		.hero-row { grid-template-columns: 1fr; }
		.hero-title { font-size: 2.2rem; }
		.hero-meter { text-align: left; }
		.hero-meter-row { justify-content: flex-start; }
		.hero-meter-value { font-size: 2.4rem; }
	}
	@media (max-width: 480px) {
		.hero-card { padding: 1.1rem 1.1rem 1.3rem; }
		.hero-title { font-size: 1.7rem; }
		.hero-meter-value { font-size: 1.9rem; }
	}

	/* ============================================================
	   STAT CARDS — same pattern as foundation pass
	============================================================ */
	.stat-card {
		position: relative;
		display: block;
		border-radius: 22px;
		background: var(--color-glass);
		backdrop-filter: blur(28px) saturate(160%);
		-webkit-backdrop-filter: blur(28px) saturate(160%);
		border: 1px solid var(--color-border);
		box-shadow: var(--shadow-card);
		overflow: hidden;
		transition: transform 0.25s cubic-bezier(.4,.4,0,1);
		cursor: pointer;
		text-decoration: none;
		color: inherit;
	}
	.stat-card:hover { text-decoration: none; }
	.stat-card::before {
		content: '';
		position: absolute;
		width: 220px; height: 220px;
		border-radius: 50%;
		right: -100px; top: -90px;
		background: radial-gradient(circle, var(--accent) 0%, transparent 70%);
		opacity: 0.35;
		pointer-events: none;
		filter: blur(20px);
		transition: transform 0.4s;
	}
	.stat-card::after {
		content: '';
		position: absolute;
		inset: 0;
		border-radius: inherit;
		pointer-events: none;
		background: linear-gradient(180deg, var(--color-highlight), transparent 30%);
		opacity: 0.4;
	}
	.stat-card:hover { transform: translateY(-3px); }
	.stat-card:hover::before { transform: scale(1.15); opacity: 0.55; }
	.stat-card-inner { position: relative; z-index: 1; padding: 1.3rem 1.4rem; }
	.stat-icon {
		display: flex; align-items: center; justify-content: center;
		width: 2.5rem; height: 2.5rem;
		border-radius: 0.85rem;
		flex-shrink: 0;
		background: var(--color-glass-elev) !important;
		border: 1px solid var(--color-border);
		box-shadow: inset 0 1px 0 var(--color-highlight);
	}
	.stat-value {
		font-size: 1.85rem;
		font-weight: 500;
		line-height: 1;
		letter-spacing: -0.025em;
		font-variant-numeric: tabular-nums;
		animation: countUp 0.5s ease-out both;
	}
	.stat-value-text {
		font-size: 1rem;
		font-weight: 600;
		line-height: 1.3;
		animation: countUp 0.5s ease-out both;
	}
	.stat-suffix { font-size: 1rem; font-weight: 400; color: var(--color-muted-foreground); }

	/* ============================================================
	   TWO COL — stream + provider deck
	============================================================ */
	.two-col {
		display: grid;
		grid-template-columns: 1.55fr 1fr;
		gap: 1.5rem;
		margin-top: 1.5rem;
	}
	.two-col.two-col--single { grid-template-columns: 1fr; }
	@media (max-width: 1100px) { .two-col { grid-template-columns: 1fr; } }

	/* ============================================================
	   PANEL — generic glass panel for all sections
	============================================================ */
	.panel {
		position: relative;
		background: var(--color-glass);
		backdrop-filter: blur(28px) saturate(160%);
		-webkit-backdrop-filter: blur(28px) saturate(160%);
		border: 1px solid var(--color-border);
		border-radius: 22px;
		box-shadow: var(--shadow-card);
		overflow: hidden;
		margin-top: 1.5rem;
	}
	.panel::after {
		content: '';
		position: absolute; inset: 0;
		border-radius: inherit;
		pointer-events: none;
		background: linear-gradient(180deg, var(--color-highlight), transparent 30%);
		opacity: 0.4;
	}
	.two-col > .panel { margin-top: 0; }
	.panel-head {
		position: relative; z-index: 1;
		display: flex; align-items: flex-start; justify-content: space-between;
		padding: 1.25rem 1.5rem 0.85rem;
		gap: 1rem;
	}
	.panel-head-actions {
		display: flex; align-items: center; gap: 0.5rem;
		flex-shrink: 0;
	}
	.panel-title {
		font-family: var(--font-display);
		font-weight: 400;
		font-size: 1.35rem;
		letter-spacing: -0.02em;
		color: var(--color-foreground);
		margin: 0;
		line-height: 1.1;
	}
	.panel-title em {
		font-style: italic;
		background: linear-gradient(135deg, var(--color-orchid), var(--color-primary));
		-webkit-background-clip: text;
		background-clip: text;
		color: transparent;
	}
	.panel-meta {
		font-size: 0.72rem;
		color: var(--color-muted-foreground);
		margin-top: 0.25rem;
	}
	.panel-meta b {
		color: var(--color-foreground);
		font-weight: 500;
		font-variant-numeric: tabular-nums;
		font-family: var(--font-mono);
		margin-right: 0.25rem;
	}
	.panel-body {
		position: relative; z-index: 1;
		padding: 0.5rem 1.5rem 1.5rem;
	}
	.panel-foot {
		position: relative; z-index: 1;
		padding: 0.85rem 1.5rem 1.25rem;
		border-top: 1px solid var(--color-border);
	}
	.panel-filters {
		position: relative; z-index: 1;
		display: flex; flex-wrap: wrap; gap: 0.5rem;
		padding: 0.5rem 1.5rem 0.85rem;
		border-bottom: 1px solid var(--color-border);
		align-items: center;
	}
	.search-input {
		width: 100%;
		padding: 0.5rem 0.85rem;
		border-radius: 10px;
		background: var(--color-input-bg);
		border: 1px solid var(--color-border);
		color: var(--color-foreground);
		font-size: 0.85rem;
	}

	/* Empty state */
	.empty-state {
		position: relative; z-index: 1;
		padding: 3rem 1.5rem;
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 0.75rem;
		color: var(--color-muted-foreground);
		text-align: center;
	}
	.empty-state p { font-size: 0.85rem; }
	.empty-state-link {
		font-size: 0.85rem;
		color: var(--color-primary);
		text-decoration: none;
		font-weight: 500;
		margin-top: 0.5rem;
	}
	.empty-state-link:hover { text-decoration: underline; text-underline-offset: 4px; }

	/* ============================================================
	   SIGNAL STREAM — events with routing trail
	============================================================ */
	.signal-list { position: relative; z-index: 1; padding-bottom: 0.25rem; }
	/* Soft dim while a page change / filter reload is in flight. We keep
	   the previous rows mounted (avoids the layout collapsing to a tiny
	   "Loading…" placeholder) and just nudge opacity so the swap feels
	   like a refresh rather than a teardown. */
	.signal-list--reloading { opacity: 0.55; pointer-events: none; transition: opacity 0.15s ease; }
	.signal-row {
		display: grid;
		grid-template-columns: 40px 1fr auto;
		gap: 0.85rem;
		align-items: center;
		padding: 0.85rem 1.5rem;
		transition: background 0.15s;
		cursor: pointer;
	}
	.signal-row + .signal-row { border-top: 1px solid var(--color-border); }
	.signal-row:hover { background: var(--color-glass-strong); }
	/* Row is rendered as <button> for clickability — strip default chrome
	   and align children left like the prior <div> layout. */
	.signal-row--clickable {
		width: 100%;
		text-align: left;
		font: inherit;
		color: inherit;
		background: transparent;
		border: 0;
	}
	.signal-row--clickable:focus-visible {
		outline: 2px solid var(--color-primary);
		outline-offset: -2px;
	}
	.signal-avatar {
		width: 40px; height: 40px;
		border-radius: 12px;
		display: grid; place-items: center;
		background: linear-gradient(135deg, var(--g1, var(--color-primary)), var(--g2, var(--color-orchid)));
		color: white;
		box-shadow: 0 4px 14px -4px var(--g1, var(--color-primary)), inset 0 1px 0 rgba(255,255,255,0.3);
	}
	.signal-body { min-width: 0; }
	.signal-sentence {
		display: flex; align-items: baseline;
		gap: 0.4rem;
		font-size: 0.92rem;
		line-height: 1.45;
		color: var(--color-foreground);
		flex-wrap: wrap;
	}
	.signal-emph {
		font-weight: 600;
		color: var(--color-foreground);
	}
	.signal-emph--accent {
		background: linear-gradient(135deg, var(--color-primary), var(--color-orchid));
		-webkit-background-clip: text;
		background-clip: text;
		color: transparent;
		font-weight: 600;
		max-width: 24ch;
	}
	.signal-verb {
		font-weight: 400;
		color: var(--color-muted-foreground);
	}
	.signal-conn {
		color: var(--color-rule-strong);
	}
	.signal-trail {
		display: flex; align-items: center; gap: 0.4rem;
		font-size: 0.72rem;
		color: var(--color-muted-foreground);
		margin-top: 0.35rem;
		flex-wrap: wrap;
	}
	.signal-trail .ch {
		display: inline-flex; align-items: center; gap: 0.3rem;
		color: var(--color-foreground);
		font-family: var(--font-mono);
		font-size: 0.7rem;
		background: var(--color-glass-strong);
		padding: 0.15rem 0.45rem;
		border-radius: 6px;
	}
	.signal-trail .arrow { color: var(--color-muted-foreground); }
	/* Dispatch lifecycle badges (quiet-hours deferral, late delivery, drops).
	 * Coloured to match the verb (held = primary glow, late = success, drop
	 * = muted). The icon is intentionally small so the badge doesn't pull
	 * focus from the event verb itself. */
	.dispatch-badge {
		display: inline-flex; align-items: center; gap: 0.25rem;
		font-size: 0.68rem;
		font-family: var(--font-mono);
		padding: 0.1rem 0.4rem;
		border-radius: 999px;
		border: 1px solid var(--color-border);
		background: var(--color-glass-strong);
		color: var(--color-muted-foreground);
		margin-left: 0.4rem;
		white-space: nowrap;
	}
	.dispatch-badge--deferred {
		color: var(--color-primary);
		border-color: color-mix(in srgb, var(--color-primary) 35%, transparent);
		background: color-mix(in srgb, var(--color-primary) 10%, var(--color-glass-strong));
	}
	.dispatch-badge--late {
		color: var(--color-success, #16a34a);
		border-color: color-mix(in srgb, var(--color-success, #16a34a) 35%, transparent);
		background: color-mix(in srgb, var(--color-success, #16a34a) 10%, var(--color-glass-strong));
	}
	.dispatch-badge--dropped {
		color: var(--color-muted-foreground);
		opacity: 0.85;
	}
	.signal-when {
		text-align: right;
		font-size: 0.7rem;
		color: var(--color-muted-foreground);
	}
	.signal-when b {
		display: block;
		color: var(--color-foreground);
		font-size: 0.82rem;
		font-weight: 500;
		font-variant-numeric: tabular-nums;
	}
	.signal-when small { font-size: 0.65rem; color: var(--color-muted-foreground); }

	@media (max-width: 640px) {
		.signal-row {
			grid-template-columns: 36px 1fr;
			gap: 0.65rem;
			padding: 0.75rem 1rem;
		}
		.signal-avatar { width: 36px; height: 36px; }
		.signal-when {
			grid-column: 2;
			text-align: left;
			margin-top: 0.2rem;
		}
		.signal-when b { display: inline; margin-right: 0.4rem; }
	}

	.clear-events-btn {
		color: var(--color-muted-foreground);
		background: var(--color-glass-strong);
		border: 1px solid var(--color-border);
	}
	.clear-events-btn:hover {
		background: var(--color-error-bg);
		border-color: color-mix(in srgb, var(--color-error-fg) 40%, var(--color-border));
		color: var(--color-error-fg);
	}

	/* Paginator */
	.paginator-btn {
		padding: 0.3rem 0.6rem;
		border-radius: 8px;
		border: 1px solid var(--color-border);
		background: var(--color-glass-strong);
		color: var(--color-foreground);
		cursor: pointer;
		transition: all 0.15s;
	}
	.paginator-btn:hover { background: var(--color-glass-elev); }
	.paginator-btn:disabled { opacity: 0.4; cursor: default; }
	.paginator-num {
		padding: 0.3rem 0.65rem;
		border-radius: 8px;
		border: 1px solid var(--color-border);
		background: transparent;
		color: var(--color-muted-foreground);
		cursor: pointer;
		transition: all 0.15s;
		font-size: 0.75rem;
	}
	.paginator-num:hover { color: var(--color-foreground); background: var(--color-glass-strong); }
	.paginator-num.active {
		background: linear-gradient(135deg, var(--color-primary), var(--color-orchid));
		color: white;
		border-color: transparent;
		box-shadow: 0 4px 12px -4px var(--color-glow-strong);
	}
	.paginator-ellipsis { padding: 0 0.4rem; color: var(--color-muted-foreground); font-size: 0.7rem; }
	.paginator-select {
		margin-left: 0.5rem;
		padding: 0.3rem 0.5rem;
		border-radius: 8px;
		border: 1px solid var(--color-border);
		background: var(--color-input-bg);
		color: var(--color-foreground);
		font-size: 0.7rem;
	}

	/* ============================================================
	   PROVIDER DECK
	============================================================ */
	.provider-deck { position: relative; z-index: 1; padding-bottom: 0.5rem; }
	.provider-row {
		display: grid;
		grid-template-columns: 44px 1fr auto;
		gap: 0.85rem;
		align-items: center;
		padding: 0.85rem 1.5rem;
		text-decoration: none;
		color: inherit;
		transition: background 0.15s;
	}
	.provider-row + .provider-row { border-top: 1px solid var(--color-border); }
	.provider-row:hover { background: var(--color-glass-strong); }
	.provider-icon {
		width: 44px; height: 44px;
		border-radius: 14px;
		display: grid; place-items: center;
		background: var(--color-glass-elev);
		border: 1px solid var(--color-border);
		color: var(--accent, var(--color-primary));
		box-shadow: inset 0 1px 0 var(--color-highlight);
	}
	.provider-icon.dashed {
		border-style: dashed;
		color: var(--color-muted-foreground);
		background: transparent;
		box-shadow: none;
	}
	.provider-name {
		font-size: 0.92rem;
		font-weight: 500;
		color: var(--color-foreground);
		display: flex; align-items: center; gap: 0.5rem;
	}
	.provider-name.muted { color: var(--color-muted-foreground); }
	.provider-sub {
		font-size: 0.7rem;
		color: var(--color-muted-foreground);
		margin-top: 0.2rem;
	}
	.provider-meter {
		text-align: right;
		min-width: 80px;
		padding: 4px 4px 4px 0;
	}
	.provider-num {
		font-size: 1rem;
		color: var(--color-foreground);
		font-weight: 500;
		font-variant-numeric: tabular-nums;
		line-height: 1;
	}
	.provider-bar {
		margin-top: 0.4rem;
		height: 4px;
		border-radius: 2px;
		background: var(--color-glass-strong);
	}
	.provider-bar-fill {
		height: 100%;
		background: linear-gradient(90deg, var(--accent, var(--color-primary)), var(--color-orchid));
		border-radius: 2px;
		box-shadow: 0 0 8px -2px var(--accent, var(--color-primary));
	}
	.provider-row--add { opacity: 0.7; }
	.provider-row--add:hover { opacity: 1; }

	/* ============================================================
	   ACTIVE WIRES
	============================================================ */
	.wires-body {
		position: relative; z-index: 1;
		padding: 0.4rem 1.5rem 1.25rem;
	}
	.wire {
		display: grid;
		grid-template-columns: minmax(0, 1fr) minmax(220px, 1.6fr) minmax(0, 1fr);
		gap: 1rem;
		align-items: center;
		padding: 0.85rem 0;
	}
	.wire + .wire { border-top: 1px dashed var(--color-border); }
	.wire-from, .wire-to {
		display: flex; align-items: center; gap: 0.6rem;
		font-size: 0.85rem;
		min-width: 0;
	}
	.wire-to { justify-content: flex-end; text-align: right; }
	.wire-from, .wire-to { color: var(--color-muted-foreground); }
	.wire-from .wire-name, .wire-to .wire-name { color: var(--color-foreground); }
	.wire-name { color: var(--color-foreground); font-weight: 500; }
	.wire-sub { font-size: 0.65rem; color: var(--color-muted-foreground); margin-top: 0.15rem; }
	.wire-pipe {
		position: relative;
		display: flex; align-items: center; justify-content: center;
		height: 26px;
	}
	.wire-pipe::before {
		content: '';
		position: absolute; left: 0; right: 0; top: 50%;
		height: 3px; transform: translateY(-50%);
		background: linear-gradient(90deg,
			color-mix(in srgb, var(--color-primary) 35%, transparent),
			var(--color-primary),
			var(--color-orchid),
			var(--color-mint),
			color-mix(in srgb, var(--color-mint) 35%, transparent));
		opacity: 0.85;
		border-radius: 3px;
		box-shadow: 0 0 12px -2px var(--color-glow-strong);
	}
	.wire-pipe::after {
		content: '';
		position: absolute; left: 0; right: 0; top: 50%;
		height: 1px; transform: translateY(-50%);
		background: linear-gradient(90deg, transparent 8%, rgba(255,255,255,0.35) 50%, transparent 92%);
		pointer-events: none;
	}
	.wire-count {
		position: relative; z-index: 1;
		background: var(--color-glass-elev);
		border: 1px solid var(--color-rule-strong);
		padding: 0.2rem 0.7rem;
		border-radius: 999px;
		font-size: 0.72rem;
		font-weight: 500;
		color: var(--color-foreground);
		font-variant-numeric: tabular-nums;
		box-shadow: 0 2px 10px -4px var(--color-glow), inset 0 1px 0 var(--color-highlight);
	}

	@media (max-width: 880px) {
		.wire {
			grid-template-columns: 1fr;
			gap: 0.4rem;
		}
		.wire-to { justify-content: flex-start; text-align: left; }
		.wire-pipe { height: 18px; }
	}

	/* ============================================================
	   COMPOSE BAND
	============================================================ */
	.compose-band {
		position: relative;
		margin-top: 1.5rem;
		padding: 2rem 2.4rem;
		border-radius: 22px;
		background:
			linear-gradient(135deg, rgba(126, 232, 196, 0.10), rgba(142, 201, 255, 0.10), rgba(184, 167, 255, 0.12)),
			var(--color-glass);
		backdrop-filter: blur(28px) saturate(160%);
		-webkit-backdrop-filter: blur(28px) saturate(160%);
		border: 1px solid var(--color-border);
		box-shadow: var(--shadow-card);
		display: grid;
		grid-template-columns: 1fr auto;
		gap: 2rem;
		align-items: center;
		overflow: hidden;
	}
	.compose-band::after {
		content: '';
		position: absolute; inset: 0;
		border-radius: inherit;
		pointer-events: none;
		background: linear-gradient(180deg, var(--color-highlight), transparent 30%);
		opacity: 0.4;
	}
	.compose-body { position: relative; z-index: 1; }
	.compose-title {
		font-family: var(--font-display);
		font-weight: 400;
		font-size: 1.85rem;
		line-height: 1.1;
		letter-spacing: -0.02em;
		margin: 0 0 0.5rem;
		color: var(--color-foreground);
	}
	.compose-title em {
		font-style: italic;
		background: linear-gradient(135deg, var(--color-mint), var(--color-sky));
		-webkit-background-clip: text;
		background-clip: text;
		color: transparent;
	}
	.compose-sub {
		font-size: 0.9rem;
		color: var(--color-muted-foreground);
		max-width: 36rem;
		line-height: 1.55;
	}
	.compose-cta {
		position: relative; z-index: 1;
		display: flex; gap: 0.6rem;
	}
	.ghost-btn {
		display: inline-flex; align-items: center; gap: 0.5rem;
		padding: 0 1rem; height: 40px;
		border-radius: 12px;
		border: 1px solid var(--color-rule-strong);
		background: var(--color-glass-strong);
		color: var(--color-foreground);
		font-size: 0.82rem;
		font-weight: 500;
		text-decoration: none;
		cursor: pointer;
		transition: all 0.15s;
	}
	.ghost-btn:hover { background: var(--color-glass-elev); }
	.primary-btn-aurora {
		display: inline-flex; align-items: center; gap: 0.5rem;
		padding: 0 1.25rem; height: 40px;
		border-radius: 12px;
		border: 0;
		background: linear-gradient(135deg, var(--color-primary), var(--color-orchid));
		color: white;
		font-size: 0.82rem;
		font-weight: 500;
		text-decoration: none;
		cursor: pointer;
		flex-direction: row-reverse;
		box-shadow: 0 6px 20px -8px var(--color-glow-strong), inset 0 1px 0 rgba(255,255,255,0.3);
		transition: transform 0.15s;
	}
	.primary-btn-aurora:hover { transform: translateY(-1px); }
	.ghost-icon-btn {
		width: 32px; height: 32px;
		display: grid; place-items: center;
		border-radius: 10px;
		background: var(--color-glass-strong);
		border: 1px solid var(--color-border);
		color: var(--color-muted-foreground);
		cursor: pointer;
		transition: all 0.15s;
	}
	.ghost-icon-btn:hover { background: var(--color-glass-elev); color: var(--color-foreground); }

	@media (max-width: 880px) {
		.compose-band { grid-template-columns: 1fr; padding: 1.4rem; }
		.compose-title { font-size: 1.4rem; }
	}
</style>
