<script lang="ts">
	import '../app.css';
	import type { Snippet } from 'svelte';
	import { onMount, onDestroy } from 'svelte';
	import { page } from '$app/stores';
	import Toast from '$lib/components/Toast.svelte';
	import ThemeToggle from '$lib/components/ThemeToggle.svelte';
	import LocaleSwitcher from '$lib/components/LocaleSwitcher.svelte';
	import { IconDashboard, IconDeploy, IconEvents, IconWifi, IconSettings, IconMenu, IconX, IconLogout, IconBox, IconContainer, IconAlert, IconKey } from '$lib/components/icons';
	import { goto } from '$app/navigation';
	import { resolvedTheme, applyTheme } from '$lib/stores/theme';
	import { exchangeOidcToken, setAuthToken, clearAuth, isAuthenticated } from '$lib/auth';
	import { logout as apiLogout } from '$lib/api';
	import { t } from '$lib/i18n';
	import { navCounts, startNavCountsPolling, stopNavCountsPolling, refreshNavCounts } from '$lib/stores/navCounts';
	import { resetAllCaches } from '$lib/stores/caches';
	import { health, startHealthPolling, stopHealthPolling, refreshHealth } from '$lib/stores/health';
	import { effectiveTimezone, formatOffsetLabel } from '$lib/stores/timezone';
	import { fmt } from '$lib/format/datetime';

	interface Props {
		children: Snippet;
	}

	const { children }: Props = $props();

	type NavCountKey = 'apps' | 'workloads' | 'proxies' | 'containers' | 'eventsErrors';

	// Navigation entries are now grouped into named sections. The
	// renderer treats a `section:` marker entry as a visual divider with
	// an uppercase eyebrow label, but otherwise renders items as before.
	// Grouping the flat list (Events / Event Triggers / Log Rules sat
	// next to Apps / Containers without any visual separation) was the
	// biggest readability complaint from the earlier UI review.
	type NavSection = 'build' | 'observe' | 'system';
	const navItems: ReadonlyArray<{
		href: string;
		labelKey: string;
		icon: string;
		section: NavSection;
		countKey?: NavCountKey;
		/** When true the badge uses a danger style (red). */
		alert?: boolean;
	}> = [
		{ href: '/', labelKey: 'nav.dashboard', icon: 'dashboard', section: 'build' },
		{ href: '/apps', labelKey: 'nav.apps', icon: 'box', section: 'build', countKey: 'apps' },
		{ href: '/containers', labelKey: 'nav.containers', icon: 'containers', section: 'build', countKey: 'containers' },
		{ href: '/proxies', labelKey: 'nav.proxies', icon: 'proxies', section: 'build', countKey: 'proxies' },
		{ href: '/triggers', labelKey: 'nav.triggers', icon: 'deploy', section: 'build' },
		{ href: '/events', labelKey: 'nav.events', icon: 'events', section: 'observe', countKey: 'eventsErrors', alert: true },
		{ href: '/event-triggers', labelKey: 'nav.eventTriggers', icon: 'events', section: 'observe' },
		{ href: '/log-scan-rules', labelKey: 'nav.logScanRules', icon: 'events', section: 'observe' },
		{ href: '/metric-alert-rules', labelKey: 'nav.metricAlertRules', icon: 'alert', section: 'observe' },
		{ href: '/shared-secrets', labelKey: 'nav.sharedSecrets', icon: 'key', section: 'system' },
		{ href: '/settings', labelKey: 'nav.settings', icon: 'settings', section: 'system' }
	];

	// sectionLabels: eyebrow text rendered above the first item of each
	// section. `build` is left unlabelled — it's the default and adding
	// an eyebrow above Dashboard would feel redundant.
	// Localized via $t — $derived so a language switch re-renders the
	// eyebrows. `build` stays unlabelled (see above).
	const sectionLabels: Record<NavSection, string> = $derived({
		build: '',
		observe: $t('nav.sectionObserve'),
		system: $t('nav.sectionSystem')
	});

	function sectionStart(idx: number): NavSection | null {
		const cur = navItems[idx].section;
		if (idx === 0) return cur;
		const prev = navItems[idx - 1].section;
		return cur !== prev ? cur : null;
	}

	function isActive(href: string, pathname: string): boolean {
		if (href === '/') return pathname === '/';
		return pathname.startsWith(href);
	}

	let sidebarOpen = $state(false);
	let hintsExpanded = $state(false);
	let proxyHintsExpanded = $state(false);

	const dockerHealth = $derived($health.docker);
	const proxyHealth = $derived($health.proxy);
	const healthChecked = $derived($health.checked);

	// Live forge clock (refreshes every second). A small thing, but it makes
	// the sidebar feel alive and reinforces the "control room" aesthetic.
	// Renders in the user's chosen timezone via the shared formatter.
	let nowTick = $state(new Date());
	let clockTimer: ReturnType<typeof setInterval> | null = null;
	function tickClock() {
		nowTick = new Date();
	}
	const clockDisplay = $derived($fmt.clock(nowTick));
	const clockOffset = $derived(formatOffsetLabel($effectiveTimezone, nowTick));
	const clockTitle = $derived(`${$effectiveTimezone.replace(/_/g, ' ')} · ${clockOffset}`);

	// Keyboard quick-nav: "g" then a letter jumps to a section (vim-style).
	// g+d → dashboard, g+a → apps, g+n → containers, g+t → triggers,
	// g+r → proxies, g+e → events, g+c → settings
	let gPressedAt = 0;
	function handleKeydown(e: KeyboardEvent) {
		// Ignore when typing in inputs/textareas/contenteditable.
		const target = e.target as HTMLElement | null;
		if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)) return;
		if (e.metaKey || e.ctrlKey || e.altKey) return;

		if (e.key === 'g') {
			gPressedAt = Date.now();
			return;
		}
		if (Date.now() - gPressedAt > 1200) return;
		const map: Record<string, string> = {
			d: '/', a: '/apps', n: '/containers', t: '/triggers',
			r: '/proxies', e: '/events', c: '/settings'
		};
		const dest = map[e.key.toLowerCase()];
		if (dest) {
			e.preventDefault();
			gPressedAt = 0;
			goto(dest);
		}
	}

	const dockerConnected = $derived(dockerHealth?.connected ?? false);
	const proxyConnected = $derived(proxyHealth?.connected ?? true);
	const proxyProviderName = $derived(proxyHealth?.provider ?? '');

	// Hide sidebar and chrome on the login page.
	const isLoginPage = $derived($page.url.pathname === '/login');

	// Apply theme reactively.
	$effect(() => {
		applyTheme($resolvedTheme);
	});

	// Listen for system theme changes when in "system" mode.
	$effect(() => {
		if (typeof window === 'undefined') return;
		const mq = window.matchMedia('(prefers-color-scheme: dark)');
		const handler = () => applyTheme($resolvedTheme);
		mq.addEventListener('change', handler);
		return () => mq.removeEventListener('change', handler);
	});

	// Close sidebar on route change (mobile).
	$effect(() => {
		void $page.url.pathname;
		sidebarOpen = false;
	});

	onMount(async () => {
		// Handle OIDC redirect: exchange the HttpOnly session cookie for a bearer token.
		if ($page.url.searchParams.get('oidc') === 'success') {
			const token = await exchangeOidcToken();
			if (token) {
				setAuthToken(token);
				goto('/', { replaceState: true });
			}
		}
		tickClock();
		clockTimer = setInterval(tickClock, 1000);
		window.addEventListener('keydown', handleKeydown);
	});

	// Keep nav badges fresh. Poll kicks off once the user is authenticated;
	// a route change also triggers a one-shot refresh so badges reflect any
	// mutations just performed on the page we're leaving.
	$effect(() => {
		void $page.url.pathname;
		if (!isAuthenticated()) return;
		startNavCountsPolling();
		refreshNavCounts();
	});

	// Start health polling when authenticated. Shared store handles the
	// timer; this effect just nudges it whenever auth flips on.
	$effect(() => {
		void $page.url.pathname;
		if (!isAuthenticated()) return;
		startHealthPolling();
	});

	onDestroy(() => {
		if (clockTimer) clearInterval(clockTimer);
		if (typeof window !== 'undefined') window.removeEventListener('keydown', handleKeydown);
		stopNavCountsPolling();
		stopHealthPolling();
	});
</script>

{#if isLoginPage}
	<!-- Login page: no sidebar, no chrome -->
	{@render children()}
{:else}
<div class="flex h-screen overflow-hidden bg-[var(--surface-page)]">
	<!-- Mobile overlay -->
	{#if sidebarOpen}
		<div
			class="fixed inset-0 z-40 bg-[var(--surface-overlay)] lg:hidden animate-fade-in"
			role="presentation"
			onclick={() => { sidebarOpen = false; }}
		></div>
	{/if}

	<!-- Sidebar -->
	<aside
		class="fixed inset-y-0 left-0 z-50 flex w-64 flex-col border-r border-[var(--border-primary)] bg-[var(--surface-sidebar)] transition-transform duration-300 lg:static lg:translate-x-0
			{sidebarOpen ? 'translate-x-0' : '-translate-x-full'}"
	>
		<!-- Brand block: title + daemon status chips -->
		<div class="brand-block border-b border-[var(--border-primary)] px-5 pt-4 pb-3">
			<div class="flex items-center gap-2.5">
				<span class="forge-ember brand-ember"></span>
				<span class="brand-name">{$t('app.name')}</span>

				<!-- Close sidebar (mobile) -->
				<button
					class="ml-auto rounded-md p-1 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] lg:hidden"
					onclick={() => { sidebarOpen = false; }}
					aria-label={$t('nav.closeSidebar')}
				>
					<IconX size={20} />
				</button>
			</div>

			<!-- Daemon health chips (Docker + proxy provider) -->
			<div class="brand-rail" aria-label={$t('layout.serviceStatus')}>
				{#if healthChecked}
					<button
						type="button"
						class="chip"
						class:chip-live={dockerConnected}
						class:chip-down={!dockerConnected}
						title={dockerConnected
							? `${$t('daemons.docker')} · ${dockerHealth?.version ?? $t('daemons.reachable')}`
							: dockerHealth?.error ?? $t('daemons.dockerUnreachable')}
						onclick={() => { if (!dockerConnected) hintsExpanded = !hintsExpanded; }}
					>
						<span class="chip-dot" aria-hidden="true"></span>
						<span class="chip-label">DKR</span>
						{#if dockerConnected && typeof dockerHealth?.running === 'number'}
							<span class="chip-meter" aria-hidden="true"></span>
							<span class="chip-count">{dockerHealth.running}</span>
						{/if}
					</button>

					{#if proxyHealth && proxyProviderName !== 'none'}
						<button
							type="button"
							class="chip"
							class:chip-live={proxyConnected}
							class:chip-down={!proxyConnected}
							title={proxyConnected
								? `${proxyProviderName.toUpperCase()} · ${proxyHealth.latency_ms ?? '?'} ms`
								: proxyHealth.error ?? $t('daemons.proxyUnreachable')}
							onclick={() => { if (!proxyConnected) proxyHintsExpanded = !proxyHintsExpanded; }}
						>
							<span class="chip-dot" aria-hidden="true"></span>
							<span class="chip-label">{proxyProviderName === 'npm' ? 'NPM' : 'TRF'}</span>
							{#if proxyConnected && typeof proxyHealth.proxy_hosts === 'number'}
								<span class="chip-meter" aria-hidden="true"></span>
								<span class="chip-count">{proxyHealth.proxy_hosts}</span>
							{/if}
						</button>
					{/if}
				{:else}
					<span class="chip chip-idle">
						<span class="chip-dot" aria-hidden="true"></span>
						<span class="chip-label">BOOT</span>
					</span>
				{/if}
			</div>

			<!-- Expandable error hints -->
			{#if healthChecked && !dockerConnected && hintsExpanded && dockerHealth?.error}
				<div class="chip-error">
					<code>{dockerHealth.error}</code>
					<button type="button" class="chip-retry" onclick={() => void refreshHealth()}>
						{$t('health.retryNow')}
					</button>
				</div>
			{/if}
			{#if healthChecked && !proxyConnected && proxyHintsExpanded && proxyHealth?.error}
				<div class="chip-error">
					<code>{proxyHealth.error}</code>
					<button type="button" class="chip-retry" onclick={() => void refreshHealth()}>
						{$t('health.retryNow')}
					</button>
				</div>
			{/if}
		</div>

		<!-- Navigation -->
		<nav class="flex-1 space-y-0.5 px-3 py-3">
			{#each navItems as item, idx}
				{@const active = isActive(item.href, $page.url.pathname)}
				{@const newSection = sectionStart(idx)}
				{#if newSection && sectionLabels[newSection]}
					<div class="nav-section-eyebrow" aria-hidden="true">{sectionLabels[newSection]}</div>
				{/if}
				<a
					href={item.href}
					class="nav-item group flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-150
						{active ? 'nav-active' : 'text-[var(--text-secondary)] hover:bg-[var(--surface-card-hover)] hover:text-[var(--text-primary)]'}"
				>
					{#if item.icon === 'dashboard'}
						<IconDashboard size={18} class="{active ? 'text-[var(--color-brand-600)]' : 'text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]'} transition-colors duration-150" />
					{:else if item.icon === 'box'}
						<IconBox size={18} class="{active ? 'text-[var(--color-brand-600)]' : 'text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]'} transition-colors duration-150" />
					{:else if item.icon === 'containers'}
						<IconContainer size={18} class="{active ? 'text-[var(--color-brand-600)]' : 'text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]'} transition-colors duration-150" />
					{:else if item.icon === 'deploy'}
						<IconDeploy size={18} class="{active ? 'text-[var(--color-brand-600)]' : 'text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]'} transition-colors duration-150" />
					{:else if item.icon === 'proxies'}
						<IconWifi size={18} class="{active ? 'text-[var(--color-brand-600)]' : 'text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]'} transition-colors duration-150" />
					{:else if item.icon === 'events'}
						<IconEvents size={18} class="{active ? 'text-[var(--color-brand-600)]' : 'text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]'} transition-colors duration-150" />
					{:else if item.icon === 'alert'}
						<IconAlert size={18} class="{active ? 'text-[var(--color-brand-600)]' : 'text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]'} transition-colors duration-150" />
					{:else if item.icon === 'key'}
						<IconKey size={18} class="{active ? 'text-[var(--color-brand-600)]' : 'text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]'} transition-colors duration-150" />
					{:else if item.icon === 'settings'}
						<IconSettings size={18} class="{active ? 'text-[var(--color-brand-600)]' : 'text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]'} transition-colors duration-150" />
					{/if}
					<span class="nav-label">{$t(item.labelKey)}</span>

					{#if item.countKey}
						{@const count = $navCounts[item.countKey]}
						{#if count !== null && count > 0}
							<span class="nav-badge" class:nav-badge-alert={item.alert} class:nav-badge-active={active}>
								{count > 99 ? '99+' : count}
							</span>
						{:else if count !== null && !item.alert}
							<span class="nav-badge nav-badge-muted" class:nav-badge-active={active}>0</span>
						{/if}
					{/if}

					{#if active}
						<div class="nav-active-dot"></div>
					{/if}
				</a>
			{/each}
		</nav>

		<!-- Footer controls -->
		<div class="space-y-3 border-t border-[var(--border-primary)] px-4 py-3">
			<div class="flex items-center justify-between">
				<ThemeToggle />
				<LocaleSwitcher />
				<button
					type="button"
					title={$t('nav.logout')}
					aria-label={$t('nav.logout')}
					onclick={async () => {
						try { await apiLogout(); } catch { /* best effort */ }
						clearAuth();
						resetAllCaches();
						goto('/login');
					}}
					class="rounded-md p-1.5 text-[var(--text-tertiary)] hover:text-[var(--color-danger)] transition-colors"
				>
					<IconLogout size={16} />
				</button>
			</div>
			<div class="forge-footline">
				<span class="forge-footline-version">{$t('app.name')} {$t('app.version')}</span>
				<span class="forge-footline-clock" title={clockTitle}>
					<span class="clock-dot"></span>
					<span class="clock-time">{clockDisplay}</span>
					<span class="clock-suffix">{clockOffset}</span>
				</span>
			</div>
			<p class="forge-nav-hint" title={$t('nav.quickNavTitle')}>
				<kbd>g</kbd><span class="arr">→</span><kbd>d</kbd><kbd>a</kbd><kbd>n</kbd><kbd>t</kbd>
				<span class="hint-label">{$t('nav.quickNavLabel')}</span>
			</p>
		</div>
	</aside>

	<!-- Main content -->
	<div class="flex flex-1 flex-col overflow-hidden">
		<!-- Top bar (mobile) -->
		<header class="flex h-14 items-center gap-3 border-b border-[var(--border-primary)] bg-[var(--surface-sidebar)] px-4 lg:hidden">
			<button
				class="rounded-md p-1.5 text-[var(--text-secondary)] hover:bg-[var(--surface-card-hover)] hover:text-[var(--text-primary)] transition-colors"
				onclick={() => { sidebarOpen = true; }}
				aria-label={$t('nav.openSidebar')}
			>
				<IconMenu size={22} />
			</button>
			<span class="forge-ember"></span>
			<span class="brand-name">{$t('app.name')}</span>
		</header>

		<!-- Page content -->
		<main class="flex-1 overflow-y-auto">
			<div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 sm:py-8">
				{@render children()}
			</div>
		</main>
	</div>
</div>
{/if}

<Toast />

<style>
	/* ── Forge-themed layout shell ─────────────────────────────────── */
	/* Page titles — larger + tighter tracking, but using the app's sans stack */
	:global(main h1) {
		font-family: var(--font-family-sans) !important;
		font-weight: 700 !important;
		letter-spacing: -0.02em !important;
		font-size: clamp(1.875rem, 4vw, 2.5rem) !important;
		line-height: 1.1 !important;
		color: var(--text-primary);
	}
	:global(main h2) {
		font-family: var(--font-family-sans);
		font-weight: 600;
		letter-spacing: -0.01em;
	}
	:global(main code) {
		font-family: var(--forge-mono, 'JetBrains Mono', monospace);
	}

	.brand-block {
		position: relative;
	}
	.brand-ember {
		width: 10px; height: 10px;
	}
	.brand-name {
		font-family: var(--font-family-sans);
		font-weight: 700;
		font-size: 1.05rem;
		line-height: 1;
		letter-spacing: -0.02em;
		color: var(--text-primary);
	}

	/* ── Daemon status chips under the brand title ─────────────────── */
	.brand-rail {
		display: flex;
		align-items: center;
		gap: 0.4rem;
		margin-top: 0.55rem;
		padding-left: 1.15rem; /* align with brand text (after ember) */
	}

	.chip {
		display: inline-flex;
		align-items: center;
		gap: 0.38rem;
		padding: 0.2rem 0.55rem 0.2rem 0.45rem;
		background: var(--surface-card);
		border: 1px solid var(--border-primary);
		border-radius: 999px;
		font-family: var(--forge-mono);
		font-size: 0.58rem;
		font-weight: 700;
		letter-spacing: 0.14em;
		text-transform: uppercase;
		color: var(--text-secondary);
		cursor: default;
		transition: border-color 150ms ease, background 150ms ease, color 150ms ease, transform 150ms ease;
		line-height: 1;
	}
	.chip:hover {
		transform: translateY(-1px);
	}
	.chip-idle {
		opacity: 0.6;
	}
	.chip-idle .chip-dot {
		background: var(--text-tertiary);
	}

	.chip-dot {
		width: 6px; height: 6px;
		border-radius: 50%;
		background: var(--text-tertiary);
		box-shadow: 0 0 0 0 transparent;
		flex-shrink: 0;
	}

	.chip-live {
		color: var(--color-success-dark);
		border-color: color-mix(in srgb, var(--color-success) 40%, transparent);
		background: color-mix(in srgb, var(--color-success) 7%, var(--surface-card));
	}
	.chip-live .chip-dot {
		background: var(--color-success);
		box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-success) 22%, transparent);
		animation: chip-pulse 1.9s ease-in-out infinite;
	}
	.chip-down {
		color: var(--color-danger-dark);
		border-color: color-mix(in srgb, var(--color-danger) 45%, transparent);
		background: color-mix(in srgb, var(--color-danger) 9%, var(--surface-card));
		cursor: pointer;
	}
	.chip-down .chip-dot {
		background: var(--color-danger);
		animation: chip-fault 0.9s steps(2) infinite;
	}
	.chip-down:hover {
		background: color-mix(in srgb, var(--color-danger) 14%, var(--surface-card));
	}

	:global([data-theme='dark']) .chip-live {
		color: #86efac;
		background: color-mix(in srgb, var(--color-success) 12%, transparent);
		border-color: color-mix(in srgb, var(--color-success) 35%, transparent);
	}
	:global([data-theme='dark']) .chip-down {
		color: #fca5a5;
		background: color-mix(in srgb, var(--color-danger) 14%, transparent);
		border-color: color-mix(in srgb, var(--color-danger) 40%, transparent);
	}

	.chip-meter {
		width: 1px;
		height: 0.7rem;
		background: currentColor;
		opacity: 0.25;
	}
	.chip-count {
		font-variant-numeric: tabular-nums;
		letter-spacing: 0.04em;
		opacity: 0.85;
	}

	@keyframes chip-pulse {
		0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-success) 22%, transparent); }
		50%      { box-shadow: 0 0 0 5px color-mix(in srgb, var(--color-success) 12%, transparent); }
	}
	@keyframes chip-fault {
		0%, 100% { opacity: 1; }
		50%      { opacity: 0.35; }
	}

	.chip-error {
		margin-top: 0.55rem;
		padding: 0.45rem 0.6rem;
		background: color-mix(in srgb, var(--color-danger) 8%, transparent);
		border: 1px solid color-mix(in srgb, var(--color-danger) 35%, transparent);
		border-radius: 8px;
	}
	.chip-error code {
		display: block;
		font-family: var(--forge-mono);
		font-size: 0.62rem;
		line-height: 1.5;
		color: var(--color-danger-dark);
		word-break: break-word;
	}
	:global([data-theme='dark']) .chip-error code { color: #fca5a5; }
	.chip-retry {
		margin-top: 0.4rem;
		width: 100%;
		padding: 0.28rem 0.5rem;
		border-radius: 6px;
		border: 1px solid color-mix(in srgb, var(--color-danger) 40%, transparent);
		background: transparent;
		font-family: var(--forge-mono);
		font-size: 0.6rem;
		font-weight: 600;
		letter-spacing: 0.12em;
		text-transform: uppercase;
		color: var(--color-danger-dark);
		cursor: pointer;
		transition: background 150ms ease;
	}
	.chip-retry:hover {
		background: color-mix(in srgb, var(--color-danger) 12%, transparent);
	}
	:global([data-theme='dark']) .chip-retry { color: #fca5a5; }

	.nav-item :global(svg) { flex-shrink: 0; }
	.nav-label {
		flex: 1;
		min-width: 0;
	}
	.nav-section-eyebrow {
		margin: 0.85rem 0.25rem 0.25rem;
		padding: 0 0.5rem;
		font-size: 0.62rem;
		letter-spacing: 0.18em;
		text-transform: uppercase;
		font-weight: 600;
		color: var(--text-tertiary);
	}
	.nav-active {
		background: var(--surface-card-hover);
		color: var(--text-primary) !important;
		position: relative;
	}
	.nav-active::before {
		content: '';
		position: absolute;
		left: -12px; top: 20%; bottom: 20%;
		width: 3px;
		background: var(--color-brand-600);
		border-radius: 0 3px 3px 0;
	}
	.nav-active-dot {
		width: 6px; height: 6px;
		border-radius: 50%;
		background: var(--color-brand-600);
		flex-shrink: 0;
	}

	/* ── Nav count badges ──────────────────────────────────────── */
	.nav-badge {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		min-width: 1.35rem;
		height: 1.1rem;
		padding: 0 0.4rem;
		border-radius: 999px;
		background: var(--surface-card-hover);
		color: var(--text-secondary);
		font-family: var(--forge-mono, 'JetBrains Mono', monospace);
		font-size: 0.65rem;
		font-weight: 600;
		letter-spacing: 0.02em;
		font-variant-numeric: tabular-nums;
		line-height: 1;
		border: 1px solid var(--border-primary);
		flex-shrink: 0;
	}
	.nav-badge-muted {
		opacity: 0.55;
	}
	.nav-badge-alert {
		background: var(--color-danger-light);
		color: var(--color-danger-dark);
		border-color: color-mix(in srgb, var(--color-danger) 45%, transparent);
	}
	:global([data-theme='dark']) .nav-badge-alert {
		background: color-mix(in srgb, var(--color-danger) 18%, transparent);
		color: #fca5a5;
		border-color: color-mix(in srgb, var(--color-danger) 45%, transparent);
	}
	.nav-active .nav-badge:not(.nav-badge-alert) {
		background: var(--surface-card);
		color: var(--text-primary);
	}

	/* ── Sidebar footline (version + live timezone-aware clock) ───────────── */
	.forge-footline {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 0.5rem;
		padding-top: 0.1rem;
	}
	.forge-footline-version {
		font-family: var(--forge-mono, 'JetBrains Mono', monospace);
		font-size: 0.66rem;
		letter-spacing: 0.08em;
		color: var(--text-tertiary);
	}
	.forge-footline-clock {
		display: inline-flex;
		align-items: center;
		gap: 0.4rem;
		font-family: var(--forge-mono, 'JetBrains Mono', monospace);
		font-size: 0.66rem;
		color: var(--text-secondary);
		font-variant-numeric: tabular-nums;
	}
	.clock-dot {
		width: 5px; height: 5px; border-radius: 50%;
		background: var(--color-brand-500);
		box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-brand-500) 20%, transparent);
		animation: forge-breathe 2.4s ease-in-out infinite;
	}
	.clock-suffix {
		font-size: 0.56rem;
		letter-spacing: 0.18em;
		color: var(--text-tertiary);
		text-transform: uppercase;
	}

	/* ── Keyboard quick-nav hint ───────────────────────────────── */
	.forge-nav-hint {
		display: flex;
		align-items: center;
		gap: 0.3rem;
		margin: 0.55rem 0 0;
		padding: 0;
		font-family: var(--forge-mono, 'JetBrains Mono', monospace);
		font-size: 0.58rem;
		color: var(--text-tertiary);
		letter-spacing: 0.06em;
	}
	.forge-nav-hint kbd {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		min-width: 14px;
		height: 14px;
		padding: 0 3px;
		border: 1px solid var(--border-primary);
		border-radius: 3px;
		background: var(--surface-card);
		color: var(--text-secondary);
		font-family: inherit;
		font-size: 0.6rem;
		line-height: 1;
		box-shadow: 0 1px 0 var(--border-primary);
	}
	.forge-nav-hint .arr {
		color: var(--text-tertiary);
		opacity: 0.5;
		font-size: 0.55rem;
		padding: 0 0.15rem;
	}
	.forge-nav-hint .hint-label {
		margin-left: auto;
		text-transform: uppercase;
		opacity: 0.7;
	}

	/* Apply dot-grid backdrop to main content */
	:global(main) {
		position: relative;
		isolation: isolate;
		/* Reserve space for the scrollbar even when content fits, so switching
		   between short and tall pages (e.g. inside Settings) doesn't shift the
		   layout horizontally by the scrollbar width. */
		scrollbar-gutter: stable;
	}
	:global(main)::before {
		content: '';
		position: absolute;
		top: 0; left: 0; right: 0; height: 480px;
		background-image: radial-gradient(var(--border-primary) 1px, transparent 1px);
		background-size: 22px 22px;
		-webkit-mask-image: radial-gradient(ellipse at 20% 0%, #000 0%, transparent 70%);
		mask-image: radial-gradient(ellipse at 20% 0%, #000 0%, transparent 70%);
		pointer-events: none;
		z-index: -1;
		opacity: 0.7;
	}
</style>
