﻿<script lang="ts">
	import { onMount } from "svelte";
	import type { AdminSnapshot } from "@/admin/types";
	import { renderAdminMarkdown } from "./markdown-preview";

	type NavKey =
		| "overview"
		| "site"
		| "appearance"
		| "navigation"
		| "posts"
		| "pages"
		| "media"
		| "advanced"
		| "build";

	type ToastState = {
		tone: "success" | "error" | "info";
		text: string;
	};

	type BuildState = {
		status: string;
		startedAt: string | null;
		finishedAt: string | null;
		lastBuiltAt: string | null;
		lastDurationMs: number | null;
		lastError: string;
		queueLength?: number;
		queuedReason?: string;
		logs: string[];
	};

	type DashboardState = {
		posts: number;
		drafts: number;
		pages: number;
		assets: number;
		tags: number;
		categories: number;
		build: BuildState;
	};

	type PostListItem = {
		slug: string;
		title: string;
		description?: string;
		published?: string;
		draft?: boolean;
		pinned?: boolean;
	};

	type EditablePost = {
		slug: string;
		title: string;
		published: string;
		updated: string;
		description: string;
		image: string;
		tags: string[];
		category: string;
		lang: string;
		draft: boolean;
		pinned: boolean;
		comment: boolean;
		author: string;
		sourceLink: string;
		licenseName: string;
		licenseUrl: string;
		body: string;
		extension: "md" | "mdx";
		__originalSlug?: string;
	};

	type PageRecord = {
		slug: string;
		filePath?: string;
		body: string;
		modifiedAt?: string;
	};

	type AssetRecord = {
		root: "public" | "content";
		path: string;
		name: string;
		directory: string;
		size: number;
		modifiedAt: string;
		previewUrl: string;
		reference: string;
	};

	type EditableConfig = {
		site: Record<string, any>;
		navbar: Record<string, any>;
		sidebar: Record<string, any>;
		profile: Record<string, any>;
		wallpaper: Record<string, any>;
		announcement: Record<string, any>;
		footer: Record<string, any>;
		comment: Record<string, any>;
		friends: Record<string, any>;
		sponsor: Record<string, any>;
		music: Record<string, any>;
		pio: Record<string, any>;
		ad: Record<string, any>;
		license: Record<string, any>;
		coverImage: Record<string, any>;
		font: Record<string, any>;
		sakura: Record<string, any>;
	};

	type JsonSectionKey =
		| "site"
		| "navbar"
		| "sidebar"
		| "friends"
		| "sponsor"
		| "ad"
		| "comment"
		| "music"
		| "pio"
		| "coverImage"
		| "font"
		| "sakura";

	export let initialSnapshot: AdminSnapshot;
	export let entryPath = "/admin";

	const navItems: Array<{ key: NavKey; label: string; hint: string }> = [
		{ key: "overview", label: "总览", hint: "状态、统计与快捷操作" },
		{ key: "site", label: "站点", hint: "标题、描述、分页与主题" },
		{ key: "appearance", label: "外观", hint: "资料、公告、页脚与壁纸" },
		{ key: "navigation", label: "导航", hint: "导航栏与侧栏布局" },
		{ key: "posts", label: "文章", hint: "Markdown 写作与预览" },
		{ key: "pages", label: "页面", hint: "关于页、友链页等固定页" },
		{ key: "media", label: "图片", hint: "上传、预览、复制引用路径" },
		{ key: "advanced", label: "扩展", hint: "评论、音乐、赞助等高级配置" },
		{ key: "build", label: "构建", hint: "构建日志与发布状态" },
	];

	function cloneData<T>(value: T): T {
		if (value === null || value === undefined || typeof value !== "object") {
			return value;
		}
		try {
			return structuredClone(value);
		} catch {
			return JSON.parse(JSON.stringify(value)) as T;
		}
	}

	function prettyJson(value: unknown) {
		return JSON.stringify(value, null, 2);
	}

	function buildInitialConfig(snapshot: AdminSnapshot): EditableConfig {
		return normalizeConfig({
			site: cloneData(snapshot.configs.siteConfig),
			navbar: { links: cloneData(snapshot.configs.navBarConfig.links) },
			sidebar: cloneData(snapshot.configs.sidebarConfig),
			profile: cloneData(snapshot.configs.profileConfig),
			wallpaper: cloneData(snapshot.configs.backgroundWallpaper),
			announcement: cloneData(snapshot.configs.announcementConfig),
			footer: cloneData(snapshot.configs.footerConfig),
			comment: cloneData(snapshot.configs.commentConfig),
			friends: {
				page: cloneData(snapshot.configs.friendsConfig.friendsPageConfig),
				items: cloneData(snapshot.configs.friendsConfig.friendsConfig),
			},
			sponsor: cloneData(snapshot.configs.sponsorConfig),
			music: cloneData(snapshot.configs.musicConfig),
			pio: {
				spine: cloneData(snapshot.configs.pioConfig.spineModelConfig),
				live2d: cloneData(snapshot.configs.pioConfig.live2dModelConfig),
			},
			ad: {
				ad1: cloneData(snapshot.configs.adConfig.adConfig1),
				ad2: cloneData(snapshot.configs.adConfig.adConfig2),
			},
			license: cloneData(snapshot.configs.licenseConfig),
			coverImage: cloneData(snapshot.configs.coverImageConfig),
			font: cloneData(snapshot.configs.fontConfig),
			sakura: cloneData(snapshot.configs.sakuraConfig),
		});
	}

	function normalizeConfig(input: EditableConfig): EditableConfig {
		const next = cloneData(input);
		next.site ||= {};
		next.site.themeColor ||= { hue: 165, fixed: false, defaultMode: "system" };
		next.site.card ||= { border: true };
		next.site.pages ||= { sponsor: false, guestbook: false, bangumi: false };
		next.site.postListLayout ||= { defaultMode: "list", allowSwitch: true, grid: { masonry: false, columns: 2 } };
		next.site.postListLayout.grid ||= { masonry: false, columns: 2 };
		next.site.pagination ||= { postsPerPage: 8 };
		next.site.analytics ||= { googleAnalyticsId: "", microsoftClarityId: "" };
		next.font ||= cloneData(next.site.font || {});
		next.site.font = cloneData(next.font);
		next.navbar ||= { links: [] };
		next.sidebar ||= { enable: true, leftComponents: [], rightComponents: [], mobileBottomComponents: [] };
		next.profile ||= { links: [] };
		next.profile.links ||= [];
		next.wallpaper ||= { mode: "banner", switchable: true, src: { desktop: "", mobile: "" } };
		if (typeof next.wallpaper.src !== "object" || next.wallpaper.src === null || Array.isArray(next.wallpaper.src)) {
			next.wallpaper.src = { desktop: typeof next.wallpaper.src === "string" ? next.wallpaper.src : "", mobile: "" };
		}
		next.wallpaper.banner ||= {};
		next.wallpaper.banner.homeText ||= { enable: true, title: "", subtitle: [], titleSize: "", subtitleSize: "", typewriter: { enable: true, speed: 100, deleteSpeed: 50, pauseTime: 2000 } };
		next.wallpaper.banner.homeText.typewriter ||= { enable: true, speed: 100, deleteSpeed: 50, pauseTime: 2000 };
		next.wallpaper.banner.credit ||= { enable: false, text: "", url: "" };
		next.wallpaper.banner.waves ||= { enable: true };
		next.wallpaper.banner.navbar ||= { transparentMode: "semifull", enableBlur: true, blur: 3 };
		next.announcement ||= { link: { enable: false, text: "", url: "", external: false } };
		next.announcement.link ||= { enable: false, text: "", url: "", external: false };
		next.footer ||= { enable: false, customHtml: "" };
		next.comment ||= {};
		next.friends ||= { page: { columns: 2 }, items: [] };
		next.friends.page ||= { columns: 2 };
		next.friends.items ||= [];
		next.sponsor ||= { methods: [], sponsors: [] };
		next.sponsor.methods ||= [];
		next.sponsor.sponsors ||= [];
		next.music ||= {};
		next.pio ||= { spine: {}, live2d: {} };
		next.pio.spine ||= {};
		next.pio.live2d ||= {};
		next.ad ||= { ad1: {}, ad2: {} };
		next.ad.ad1 ||= {};
		next.ad.ad2 ||= {};
		next.license ||= {};
		next.coverImage ||= { randomCoverImage: { enable: false, apis: [] } };
		next.coverImage.randomCoverImage ||= { enable: false, apis: [] };
		next.sakura ||= {};
		return next;
	}

	function createEmptyPost(): EditablePost {
		return {
			slug: "",
			title: "",
			published: new Date().toISOString().slice(0, 10),
			updated: "",
			description: "",
			image: "",
			tags: [],
			category: "",
			lang: "",
			draft: true,
			pinned: false,
			comment: true,
			author: "",
			sourceLink: "",
			licenseName: "",
			licenseUrl: "",
			body: "# 新文章\n\n从这里开始写作。",
			extension: "md",
		};
	}

	function mapPostDraft(value: Record<string, any>): EditablePost {
		return {
			...createEmptyPost(),
			...cloneData(value),
			body: String(value.body ?? value.content ?? ""),
			extension: value.extension === "mdx" ? "mdx" : "md",
			__originalSlug: String(value.slug ?? value.__originalSlug ?? "") || undefined,
		};
	}

	function mapSnapshotAsset(image: any): AssetRecord {
		const reference =
			image.sitePath ||
			(image.origin === "public"
				? `/${String(image.path || "").replace(/^public\//, "")}`
				: String(image.path || "").replace(/^src\/content\/posts\//, ""));
		return {
			root: image.origin,
			path:
				image.origin === "public"
					? String(image.path || "").replace(/^public\//, "")
					: String(image.path || "").replace(/^src\/content\/posts\//, ""),
			name: image.name,
			directory: image.directory || "",
			size: Number(image.size || 0),
			modifiedAt: image.updatedAt || new Date().toISOString(),
			previewUrl: image.url,
			reference,
		};
	}

	let sessionLoading = true;
	let sessionRefreshing = false;
	let sessionResolved = false;
	let authenticated = false;
	let configured = true;
	let username = "";
	let loginSubmitting = false;
	let activeNav: NavKey = "overview";
	let toast: ToastState | null = null;
	let loginForm = { username: "", password: "" };
	let configs: EditableConfig = buildInitialConfig(initialSnapshot);
	let sectionEditors: Record<JsonSectionKey, string> = {
		site: "{}",
		navbar: "{}",
		sidebar: "{}",
		friends: "{}",
		sponsor: "{}",
		ad: "{}",
		comment: "{}",
		music: "{}",
		pio: "{}",
		coverImage: "{}",
		font: "{}",
		sakura: "{}",
	};
	let profileLinksJson = "[]";
	let postsList: PostListItem[] = initialSnapshot.posts.map((post) => ({
		slug: post.slug,
		title: post.title,
		description: post.description,
		published: post.published,
		draft: post.draft,
		pinned: post.pinned,
	}));
	let postDraft: EditablePost = initialSnapshot.posts.length > 0 ? mapPostDraft(initialSnapshot.posts[0]) : createEmptyPost();
	let selectedPostSlug: string | null = initialSnapshot.posts[0]?.slug ?? null;
	let postSearch = "";
	let specPages: PageRecord[] = initialSnapshot.specPages.map((page) => cloneData(page));
	let selectedPageSlug = initialSnapshot.specPages[0]?.slug ?? "";
	let pageDraftBody = initialSnapshot.specPages[0]?.body ?? "";
	let mediaItems: AssetRecord[] = initialSnapshot.images.map((image) => mapSnapshotAsset(image));
	let mediaSearch = "";
	let uploadRoot: "public" | "content" = "public";
	let uploadFolder = "uploads/posts";
	let uploadName = "";
	let uploadDataUrl = "";
	let buildState: BuildState = {
		status: "idle",
		startedAt: null,
		finishedAt: null,
		lastBuiltAt: null,
		lastDurationMs: null,
		lastError: "",
		queueLength: 0,
		queuedReason: "",
		logs: [],
	};
	let dashboard: DashboardState = {
		posts: postsList.length,
		drafts: postsList.filter((post) => post.draft).length,
		pages: specPages.length,
		assets: mediaItems.length,
		tags: 0,
		categories: 0,
		build: buildState,
	};
	let filteredPosts: PostListItem[] = postsList;
	let filteredAssets: AssetRecord[] = mediaItems;
	let postPreview = renderAdminMarkdown(postDraft.body || "");
	let pagePreview = renderAdminMarkdown(pageDraftBody || "");

	function syncEditors() {
		configs = normalizeConfig(configs);
		sectionEditors.site = prettyJson(configs.site);
		sectionEditors.navbar = prettyJson(configs.navbar);
		sectionEditors.sidebar = prettyJson(configs.sidebar);
		sectionEditors.friends = prettyJson(configs.friends);
		sectionEditors.sponsor = prettyJson(configs.sponsor);
		sectionEditors.ad = prettyJson(configs.ad);
		sectionEditors.comment = prettyJson(configs.comment);
		sectionEditors.music = prettyJson(configs.music);
		sectionEditors.pio = prettyJson(configs.pio);
		sectionEditors.coverImage = prettyJson(configs.coverImage);
		sectionEditors.font = prettyJson(configs.font);
		sectionEditors.sakura = prettyJson(configs.sakura);
		profileLinksJson = prettyJson(configs.profile.links || []);
	}

	syncEditors();

	function showToast(tone: ToastState["tone"], text: string) {
		toast = { tone, text };
		window.setTimeout(() => {
			if (toast?.text === text) toast = null;
		}, 2600);
	}

	function formatDateTime(value?: string | null) {
		if (!value) return "-";
		const date = new Date(value);
		if (Number.isNaN(date.getTime())) return value;
		return date.toLocaleString("zh-CN", { hour12: false });
	}

	function formatBytes(bytes: number) {
		if (bytes < 1024) return `${bytes} B`;
		if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
		return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
	}

	function buildLabel(status: string) {
		switch (status) {
			case "building":
				return "构建中";
			case "success":
				return "最近成功";
			case "error":
				return "最近失败";
			default:
				return status === "idle" ? "待命中" : "排队中";
		}
	}

	async function api<T>(url: string, options: RequestInit = {}): Promise<T> {
		const response = await fetch(url, {
			credentials: "same-origin",
			...options,
			headers: {
				...(options.body ? { "Content-Type": "application/json" } : {}),
				...(options.headers || {}),
			},
		});
		const text = await response.text();
		const data = text ? JSON.parse(text) : {};
		if (!response.ok) {
			throw new Error(data.error || data.message || `Request failed (${response.status})`);
		}
		return data as T;
	}

	function applyBuild(data: any) {
		if (data?.build) {
			buildState = data.build;
		}
		if (dashboard) {
			dashboard = { ...dashboard, build: buildState };
		}
	}

	async function refreshSession() {
		sessionLoading = true;
		sessionRefreshing = sessionResolved;
		try {
			const data = await api<any>("/api/admin/session");
			authenticated = Boolean(data.authenticated);
			configured = data.configured !== false;
			username = String(data.username || "");
			if (data.build) applyBuild(data);
		} catch (error) {
			authenticated = false;
			configured = true;
			showToast(
				"error",
				error instanceof Error ? error.message : "后台会话检查失败",
			);
		} finally {
			sessionResolved = true;
			sessionLoading = false;
			sessionRefreshing = false;
		}
	}

	async function loadBuild() {
		const data = await api<BuildState>("/api/admin/build");
		buildState = data;
		if (dashboard) dashboard = { ...dashboard, build: buildState };
	}

	async function loadDashboard() {
		const data = await api<DashboardState>("/api/admin/dashboard");
		dashboard = data;
		buildState = data.build;
	}

	async function loadConfig() {
		const data = await api<{ config: EditableConfig }>("/api/admin/config");
		configs = normalizeConfig(data.config);
		syncEditors();
	}

	async function loadPosts() {
		const data = await api<{ posts: PostListItem[] }>("/api/admin/posts");
		postsList = data.posts || [];
		filteredPosts = postsList;
	}

	async function loadPages() {
		const data = await api<{ pages: PageRecord[] }>("/api/admin/pages");
		specPages = data.pages || [];
		if (selectedPageSlug) {
			const current = specPages.find((page) => page.slug === selectedPageSlug);
			if (current) pageDraftBody = current.body;
		}
	}

	async function loadAssets() {
		const data = await api<{ assets: AssetRecord[] }>("/api/admin/assets");
		mediaItems = data.assets || [];
		filteredAssets = mediaItems;
	}

	async function loadAll() {
		await Promise.all([loadConfig(), loadDashboard(), loadPosts(), loadPages(), loadAssets(), loadBuild()]);
	}

	function parseKeywords(value: string) {
		return value.split(",").map((item) => item.trim()).filter(Boolean);
	}

	function setWallpaperSubtitles(value: string) {
		configs.wallpaper.banner.homeText.subtitle = value
			.split(/\r?\n/)
			.map((item: string) => item.trim())
			.filter(Boolean);
	}

	function getWallpaperSubtitlesText() {
		const subtitle = configs.wallpaper.banner.homeText.subtitle;
		return Array.isArray(subtitle) ? subtitle.join("\n") : String(subtitle || "");
	}

	function parseEditor<T>(label: string, raw: string): T | null {
		try {
			return JSON.parse(raw) as T;
		} catch (error) {
			showToast("error", `${label} 不是有效的 JSON：${error instanceof Error ? error.message : "解析失败"}`);
			return null;
		}
	}

	async function saveSection(section: string, label: string, data: unknown) {
		const response = await api<any>(`/api/admin/config/${section}`, {
			method: "PUT",
			body: JSON.stringify(data),
		});
		applyBuild(response);
		showToast("success", `${label}已保存并加入构建队列`);
	}

	async function saveSite() {
		configs.site.font = cloneData(configs.font);
		await saveSection("site", "站点设置", configs.site);
		syncEditors();
	}

	async function saveProfile() {
		const parsed = parseEditor<any[]>("资料链接", profileLinksJson);
		if (!parsed) return;
		configs.profile.links = parsed;
		await saveSection("profile", "个人资料", configs.profile);
		syncEditors();
	}

	async function saveWallpaper() {
		await saveSection("wallpaper", "壁纸设置", configs.wallpaper);
	}

	async function saveAnnouncement() {
		await saveSection("announcement", "公告设置", configs.announcement);
	}

	async function saveFooter() {
		await saveSection("footer", "页脚设置", configs.footer);
	}

	async function saveJsonSection(section: JsonSectionKey, label: string) {
		const parsed = parseEditor<Record<string, any>>(label, sectionEditors[section]);
		if (!parsed) return;
		if (section === "font") {
			configs.font = parsed;
			configs.site.font = cloneData(parsed);
		} else {
			(configs as Record<string, any>)[section] = parsed;
		}
		await saveSection(section, label, parsed);
		syncEditors();
	}

	function applyLoginSearchFeedback() {
		const currentUrl = new URL(window.location.href);
		const loginStatus = currentUrl.searchParams.get("login");
		if (loginStatus === "invalid") {
			showToast("error", "账号或密码错误，请重新输入");
		} else if (loginStatus === "disabled") {
			showToast("error", "后台尚未启用，请先配置 ADMIN 和 PASSWORD");
		}
		if (!loginStatus) {
			return;
		}
		currentUrl.searchParams.delete("login");
		const nextPath = currentUrl.pathname + currentUrl.search + currentUrl.hash;
		window.history.replaceState({}, "", nextPath || currentUrl.pathname);
	}

	async function handleLogin(event?: SubmitEvent) {
		event?.preventDefault();
		loginSubmitting = true;
		try {
			const response = await api<any>("/api/admin/login", {
				method: "POST",
				body: JSON.stringify(loginForm),
			});
			authenticated = Boolean(response.authenticated);
			configured = response.configured !== false;
			sessionResolved = true;
			username = String(response.username || loginForm.username || "");
			loginForm.password = "";
			applyBuild(response);
			await loadAll();
			showToast("success", "登录成功");
		} catch (error) {
			showToast("error", error instanceof Error ? error.message : "\u767b\u5f55\u5931\u8d25");
		} finally {
			loginSubmitting = false;
		}
	}

	async function handleLogout() {
		await api("/api/admin/logout", { method: "POST", body: JSON.stringify({}) });
		authenticated = false;
		username = "";
		sessionResolved = true;
		loginForm.password = "";
		showToast("info", "已退出后台");
	}

	function createNewPost() {
		selectedPostSlug = null;
		postDraft = createEmptyPost();
		activeNav = "posts";
	}

	async function openPost(slug: string) {
		const data = await api<Record<string, any>>(`/api/admin/posts/${encodeURIComponent(slug)}`);
		selectedPostSlug = slug;
		postDraft = mapPostDraft(data);
	}

	function duplicatePost() {
		postDraft = {
			...cloneData(postDraft),
			slug: `${postDraft.slug || "post"}-copy`,
			title: `${postDraft.title || "未命名文章"}（副本）`,
			__originalSlug: undefined,
		};
		selectedPostSlug = null;
		showToast("info", "已创建文章副本，请检查 slug 后保存");
	}

	async function savePost(publish = false) {
		const payload = { ...postDraft, body: postDraft.body, publish };
		const url = selectedPostSlug
			? `/api/admin/posts/${encodeURIComponent(selectedPostSlug)}`
			: "/api/admin/posts";
		const method = selectedPostSlug ? "PUT" : "POST";
		const response = await api<any>(url, {
			method,
			body: JSON.stringify(payload),
		});
		selectedPostSlug = response.post.slug;
		postDraft = mapPostDraft(response.post);
		applyBuild(response);
		await Promise.all([loadPosts(), loadDashboard()]);
		showToast("success", publish ? "文章已保存并加入构建队列" : "文章已保存");
	}

	async function removePost() {
		if (!selectedPostSlug) return;
		if (!window.confirm(`确定删除文章 ${selectedPostSlug} 吗？`)) return;
		const response = await api<any>(`/api/admin/posts/${encodeURIComponent(selectedPostSlug)}`, {
			method: "DELETE",
		});
		applyBuild(response);
		createNewPost();
		await Promise.all([loadPosts(), loadDashboard()]);
		showToast("success", "文章已删除");
	}

	function selectPage(page: PageRecord) {
		selectedPageSlug = page.slug;
		pageDraftBody = page.body;
	}

	function createNewPage() {
		const slug = window.prompt("请输入新页面的 slug", "notes");
		if (!slug) return;
		selectedPageSlug = slug.trim();
		pageDraftBody = "# 新页面\n\n开始编辑这里的内容。";
		activeNav = "pages";
	}

	async function savePage(publish = false) {
		if (!selectedPageSlug) {
			showToast("error", "请先选择或创建页面");
			return;
		}
		const response = await api<any>(`/api/admin/pages/${encodeURIComponent(selectedPageSlug)}`, {
			method: "PUT",
			body: JSON.stringify({ body: pageDraftBody, publish }),
		});
		pageDraftBody = response.page.body;
		selectedPageSlug = response.page.slug;
		applyBuild(response);
		await Promise.all([loadPages(), loadDashboard()]);
		showToast("success", publish ? "页面已保存并加入构建队列" : "页面已保存");
	}

	function chooseUploadFile(event: Event) {
		const input = event.currentTarget as HTMLInputElement;
		const file = input.files?.[0];
		uploadName = file?.name || "";
		if (!file) {
			uploadDataUrl = "";
			return;
		}
		const reader = new FileReader();
		reader.onload = () => {
			uploadDataUrl = String(reader.result || "");
		};
		reader.readAsDataURL(file);
	}

	async function uploadAsset() {
		if (!uploadName || !uploadDataUrl) {
			showToast("error", "请先选择图片文件");
			return;
		}
		const response = await api<any>("/api/admin/assets", {
			method: "POST",
			body: JSON.stringify({
				root: uploadRoot,
				name: uploadName,
				folder: uploadFolder,
				dataUrl: uploadDataUrl,
			}),
		});
		applyBuild(response);
		uploadName = "";
		uploadDataUrl = "";
		await Promise.all([loadAssets(), loadDashboard()]);
		showToast("success", "图片已上传并加入构建队列");
	}

	async function removeAsset(asset: AssetRecord) {
		if (!window.confirm(`确定删除图片 ${asset.name} 吗？`)) return;
		const response = await api<any>(`/api/admin/assets?root=${encodeURIComponent(asset.root)}&path=${encodeURIComponent(asset.path)}`, {
			method: "DELETE",
		});
		applyBuild(response);
		await Promise.all([loadAssets(), loadDashboard()]);
		showToast("success", "图片已删除");
	}

	async function rebuildSite() {
		const response = await api<any>("/api/admin/rebuild", { method: "POST", body: JSON.stringify({}) });
		applyBuild(response);
		showToast("info", response.queued ? "构建已排队" : "已开始重新构建站点");
	}

	async function copyText(text: string, label: string) {
		try {
			await navigator.clipboard.writeText(text);
			showToast("success", `${label}已复制`);
		} catch {
			showToast("error", "复制失败，请手动复制");
		}
	}

	$: filteredPosts = postsList.filter((post) => {
		const keyword = postSearch.trim().toLowerCase();
		if (!keyword) return true;
		return `${post.title} ${post.slug} ${post.description || ""}`.toLowerCase().includes(keyword);
	});
	$: filteredAssets = mediaItems.filter((asset) => {
		const keyword = mediaSearch.trim().toLowerCase();
		if (!keyword) return true;
		return `${asset.name} ${asset.path} ${asset.directory} ${asset.reference}`.toLowerCase().includes(keyword);
	});
	$: postPreview = renderAdminMarkdown(postDraft.body || "");
	$: pagePreview = renderAdminMarkdown(pageDraftBody || "");

	onMount(() => {
		const timer = window.setInterval(async () => {
			if (!authenticated) return;
			try {
				await loadBuild();
			} catch {
				// ignore background build refresh failures
			}
		}, 3000);

		(async () => {
			applyLoginSearchFeedback();
			await refreshSession();
			if (authenticated) {
				await loadAll();
			}
		})();

		return () => window.clearInterval(timer);
	});
</script>

<div class="admin-shell">
	{#if toast}
		<div class={`admin-toast ${toast.tone}`}>{toast.text}</div>
	{/if}

	{#if !sessionResolved}
		<section class="admin-auth-shell admin-auth-shell-pending">
			<div class="card-base admin-panel admin-auth-copy">
				<div class="admin-login-badge">Firefly Admin</div>
				<div class="admin-auth-intro">
					<h2>正在连接后台</h2>
					<p>正在检查管理员会话与后台启用状态，确认完成后会显示登录入口或直接进入内容管理台。</p>
				</div>
				<div class="admin-loading-stack" aria-hidden="true">
					<div class="admin-loading-line"></div>
					<div class="admin-loading-line short"></div>
					<div class="admin-loading-line medium"></div>
				</div>
			</div>
			<div class="card-base admin-login admin-login-stage admin-login-pending">
				<div class="admin-login-badge">会话检查中</div>
				<h2>后台准备中</h2>
				<p class="admin-login-status">正在从当前 Space 读取后台环境变量并确认登录状态。</p>
			</div>
		</section>
	{:else if !configured}
		<div class="card-base admin-panel admin-empty">
			<h2>后台尚未启用</h2>
			<p>请在 Hugging Face Space 中配置环境变量 <code>ADMIN</code> 和 <code>PASSWORD</code> 后再访问管理页。</p>
		</div>
	{:else if !authenticated}
		<section class="admin-auth-shell">
			<div class="card-base admin-panel admin-auth-copy">
				<div class="admin-login-badge">Firefly Admin</div>
				<div class="admin-auth-intro">
					<h2>后台管理入口</h2>
					<p>在这里统一管理博客标题、介绍、导航、侧栏、文章、图片素材与构建状态，界面风格与前台博客保持一致。</p>
				</div>
				<div class="admin-auth-facts">
					<div class="admin-auth-fact">
						<strong>账号来源</strong>
						<span><code>ADMIN</code></span>
					</div>
					<div class="admin-auth-fact">
						<strong>密码来源</strong>
						<span><code>PASSWORD</code></span>
					</div>
					<div class="admin-auth-fact">
						<strong>内容存储</strong>
						<span>当前版本保存到容器本地目录</span>
					</div>
				</div>
			</div>
			<form class="card-base admin-login admin-login-stage" method="post" action={`${entryPath}/login`} on:submit|preventDefault={handleLogin}>
				<div class="admin-login-badge">管理员登录</div>
				<h2>输入管理员凭证</h2>
				<p>登录成功后将进入内容工作台，可继续管理站点配置、文章与图片资源。</p>
				<label class="admin-field">
					<span>管理员账号</span>
					<input class="admin-input" name="username" autocomplete="username" autocapitalize="none" placeholder="管理员账号" bind:value={loginForm.username} required />
				</label>
				<label class="admin-field">
					<span>登录密码</span>
					<input class="admin-input" name="password" autocomplete="current-password" type="password" placeholder="密码" bind:value={loginForm.password} required />
				</label>
				<button class="btn-regular admin-button primary" type="submit" disabled={loginSubmitting}>
					{loginSubmitting ? "登录中..." : "登录后台"}
				</button>
				<p class="admin-login-status">后台凭证直接读取自 Hugging Face Space 环境变量，不写入前端代码。</p>
				{#if sessionRefreshing || sessionLoading}
					<p class="admin-login-status">正在刷新当前后台会话...</p>
				{/if}
			</form>
		</section>
	{:else}
		<div class="admin-workbench">
			<aside class="admin-sidebar">
				<div class="card-base admin-panel admin-sidebar-card">
					<div class="admin-login-badge">Firefly Admin</div>
					<h3>内容后台</h3>
					<p>当前管理员：<strong>{username}</strong></p>
					<div class={`build-pill state-${buildState.status}`}>{buildLabel(buildState.status)}</div>
					<div class="admin-quick-actions">
						<button class="btn-regular admin-button primary" on:click={rebuildSite}>重新构建</button>
						<button class="btn-plain admin-button ghost" on:click={handleLogout}>退出登录</button>
					</div>
				</div>
				<div class="card-base admin-panel admin-sidebar-card nav-card">
					{#each navItems as item}
						<button class:active={activeNav === item.key} class="btn-plain admin-nav-button" on:click={() => (activeNav = item.key)}>
							<span>{item.label}</span>
							<small>{item.hint}</small>
						</button>
					{/each}
				</div>
			</aside>

			<section class="admin-main">
				{#if activeNav === "overview"}
					<div class="admin-grid metrics-grid">
						{#each [["文章", dashboard.posts], ["草稿", dashboard.drafts], ["页面", dashboard.pages], ["图片", dashboard.assets], ["标签", dashboard.tags], ["分类", dashboard.categories]] as [label, value]}
							<div class="card-base admin-panel metric-card">
								<span>{label}</span>
								<strong>{value}</strong>
							</div>
						{/each}
					</div>
					<div class="admin-grid two-col">
						<div class="card-base admin-panel">
							<div class="section-head"><h3>快捷操作</h3></div>
							<div class="admin-quick-actions wrap">
								<button class="btn-regular admin-button primary" on:click={createNewPost}>新建文章</button>
								<button class="btn-plain admin-button ghost" on:click={createNewPage}>新建页面</button>
								<button class="btn-plain admin-button ghost" on:click={() => (activeNav = "media")}>上传图片</button>
								<button class="btn-plain admin-button ghost" on:click={() => loadAll()}>刷新数据</button>
							</div>
							<div class="overview-list">
								{#each postsList.slice(0, 6) as post}
									<button class="btn-plain overview-item" on:click={() => openPost(post.slug)}>
										<strong>{post.title || post.slug}</strong>
										<small>{post.slug}</small>
									</button>
								{/each}
							</div>
						</div>
						<div class="card-base admin-panel">
							<div class="section-head"><h3>最近构建</h3></div>
							<p>状态：<strong>{buildLabel(buildState.status)}</strong></p>
							<p>开始时间：{formatDateTime(buildState.startedAt)}</p>
							<p>完成时间：{formatDateTime(buildState.finishedAt)}</p>
							<p>最近成功：{formatDateTime(buildState.lastBuiltAt)}</p>
							<p>排队数量：{buildState.queueLength || 0}</p>
							{#if buildState.lastError}<p class="danger-text">错误：{buildState.lastError}</p>{/if}
							<pre class="admin-code build-log">{buildState.logs.join("\n") || "这里会显示构建日志。"}</pre>
						</div>
					</div>
				{/if}

				{#if activeNav === "site"}
					<div class="admin-grid two-col">
						<div class="card-base admin-panel">
							<div class="section-head"><h3>基础信息</h3><button class="btn-regular admin-button primary" on:click={saveSite}>保存站点</button></div>
							<div class="field-grid">
								<input class="admin-input" placeholder="站点标题" bind:value={configs.site.title} />
								<input class="admin-input" placeholder="站点副标题" bind:value={configs.site.subtitle} />
								<input class="admin-input" placeholder="站点地址" bind:value={configs.site.site_url} />
								<input class="admin-input" placeholder="语言，例如 zh_CN" bind:value={configs.site.lang} />
								<input class="admin-input field-span-2" placeholder="站点描述" bind:value={configs.site.description} />
								<input class="admin-input field-span-2" placeholder="关键词，逗号分隔" value={Array.isArray(configs.site.keywords) ? configs.site.keywords.join(', ') : ''} on:input={(event) => (configs.site.keywords = parseKeywords((event.currentTarget as HTMLInputElement).value))} />
								<input class="admin-input" placeholder="站点开始日期 YYYY-MM-DD" bind:value={configs.site.siteStartDate} />
								<input class="admin-input" placeholder="时区，例如 Asia/Shanghai" bind:value={configs.site.timezone} />
							</div>
							<div class="field-grid compact-grid">
								<label>主题色 Hue<input class="admin-input" type="number" bind:value={configs.site.themeColor.hue} /></label>
								<label>默认模式<select class="admin-input" bind:value={configs.site.themeColor.defaultMode}><option value="system">system</option><option value="light">light</option><option value="dark">dark</option></select></label>
								<label>每页文章数<input class="admin-input" type="number" bind:value={configs.site.pagination.postsPerPage} /></label>
								<label>过期阈值<input class="admin-input" type="number" bind:value={configs.site.outdatedThreshold} /></label>
							</div>
							<div class="check-row">
								<label><input type="checkbox" bind:checked={configs.site.themeColor.fixed} /> 固定主题色</label>
								<label><input type="checkbox" bind:checked={configs.site.card.border} /> 启用卡片边框</label>
								<label><input type="checkbox" bind:checked={configs.site.showLastModified} /> 显示最后修改</label>
								<label><input type="checkbox" bind:checked={configs.site.sharePoster} /> 显示分享海报</label>
								<label><input type="checkbox" bind:checked={configs.site.pages.sponsor} /> 启用赞助页</label>
								<label><input type="checkbox" bind:checked={configs.site.pages.guestbook} /> 启用留言页</label>
								<label><input type="checkbox" bind:checked={configs.site.pages.bangumi} /> 启用 Bangumi 页</label>
							</div>
						</div>
						<div class="card-base admin-panel">
							<div class="section-head"><h3>站点 JSON</h3><button class="btn-plain admin-button ghost" on:click={() => saveJsonSection('site', '站点 JSON')}>按 JSON 保存</button></div>
							<textarea class="admin-code section-editor" bind:value={sectionEditors.site}></textarea>
						</div>
					</div>
				{/if}

				{#if activeNav === "appearance"}
					<div class="admin-grid two-col">
						<div class="card-base admin-panel">
							<div class="section-head"><h3>个人资料</h3><button class="btn-regular admin-button primary" on:click={saveProfile}>保存资料</button></div>
							<input class="admin-input" placeholder="头像路径" bind:value={configs.profile.avatar} />
							<input class="admin-input" placeholder="名称" bind:value={configs.profile.name} />
							<textarea class="admin-code small-editor" placeholder="个人简介" bind:value={configs.profile.bio}></textarea>
							<div class="section-subtitle">社交链接 JSON</div>
							<textarea class="admin-code small-editor" bind:value={profileLinksJson}></textarea>
						</div>
						<div class="card-base admin-panel">
							<div class="section-head"><h3>公告与页脚</h3><button class="btn-regular admin-button primary" on:click={saveAnnouncement}>保存公告</button></div>
							<input class="admin-input" placeholder="公告标题" bind:value={configs.announcement.title} />
							<textarea class="admin-code small-editor" placeholder="公告内容" bind:value={configs.announcement.content}></textarea>
							<input class="admin-input" placeholder="公告链接文字" bind:value={configs.announcement.link.text} />
							<input class="admin-input" placeholder="公告链接地址" bind:value={configs.announcement.link.url} />
							<div class="check-row"><label><input type="checkbox" bind:checked={configs.announcement.closable} /> 可关闭</label><label><input type="checkbox" bind:checked={configs.announcement.link.enable} /> 启用链接</label></div>
							<div class="section-head inline-head"><h4>页脚 HTML</h4><button class="btn-plain admin-button ghost" on:click={saveFooter}>保存页脚</button></div>
							<textarea class="admin-code section-editor" bind:value={configs.footer.customHtml}></textarea>
						</div>
						<div class="card-base admin-panel full-span">
							<div class="section-head"><h3>壁纸与首页文案</h3><button class="btn-regular admin-button primary" on:click={saveWallpaper}>保存壁纸</button></div>
							<div class="field-grid">
								<select class="admin-input" bind:value={configs.wallpaper.mode}><option value="banner">banner</option><option value="overlay">overlay</option><option value="none">none</option></select>
								<label class="checkbox-inline"><input type="checkbox" bind:checked={configs.wallpaper.switchable} /> 允许切换壁纸模式</label>
								<input class="admin-input" placeholder="桌面壁纸路径" bind:value={configs.wallpaper.src.desktop} />
								<input class="admin-input" placeholder="移动壁纸路径" bind:value={configs.wallpaper.src.mobile} />
								<input class="admin-input" placeholder="首页标题" bind:value={configs.wallpaper.banner.homeText.title} />
								<input class="admin-input" placeholder="标题尺寸，例如 3.8rem" bind:value={configs.wallpaper.banner.homeText.titleSize} />
								<input class="admin-input" placeholder="副标题尺寸，例如 1.4rem" bind:value={configs.wallpaper.banner.homeText.subtitleSize} />
								<input class="admin-input" placeholder="Banner 位置，例如 center" bind:value={configs.wallpaper.banner.position} />
							</div>
							<div class="check-row"><label><input type="checkbox" bind:checked={configs.wallpaper.banner.homeText.enable} /> 启用首页文案</label><label><input type="checkbox" bind:checked={configs.wallpaper.banner.homeText.typewriter.enable} /> 启用打字机</label></div>
							<textarea class="admin-code section-editor" placeholder="每行一个副标题" value={getWallpaperSubtitlesText()} on:input={(event) => setWallpaperSubtitles((event.currentTarget as HTMLTextAreaElement).value)}></textarea>
						</div>
					</div>
				{/if}

				{#if activeNav === "navigation"}
					<div class="admin-grid two-col">
						<div class="card-base admin-panel"><div class="section-head"><h3>导航栏 JSON</h3><button class="btn-regular admin-button primary" on:click={() => saveJsonSection('navbar', '导航栏配置')}>保存导航</button></div><textarea class="admin-code section-editor" bind:value={sectionEditors.navbar}></textarea></div>
						<div class="card-base admin-panel"><div class="section-head"><h3>侧栏 JSON</h3><button class="btn-regular admin-button primary" on:click={() => saveJsonSection('sidebar', '侧栏配置')}>保存侧栏</button></div><textarea class="admin-code section-editor" bind:value={sectionEditors.sidebar}></textarea></div>
					</div>
				{/if}

				{#if activeNav === "posts"}
					<div class="card-base admin-panel">
						<div class="section-head"><h3>文章管理</h3><div class="admin-quick-actions wrap"><button class="btn-plain admin-button ghost" on:click={createNewPost}>新建</button><button class="btn-plain admin-button ghost" on:click={duplicatePost}>复制</button><button class="btn-plain admin-button ghost" on:click={() => savePost(false)}>保存</button><button class="btn-regular admin-button primary" on:click={() => savePost(true)}>保存并发布</button><button class="btn-plain admin-button ghost danger" on:click={removePost}>删除</button></div></div>
						<div class="editor-layout posts-layout">
							<div class="editor-list"><input class="admin-input" placeholder="搜索文章" bind:value={postSearch} />{#each filteredPosts as post}<button class:active={selectedPostSlug === post.slug} class="btn-plain editor-item" on:click={() => openPost(post.slug)}><strong>{post.title || post.slug}</strong><small>{post.slug}</small></button>{/each}</div>
							<div class="post-form"><div class="field-grid"><input class="admin-input" placeholder="标题" bind:value={postDraft.title} /><input class="admin-input" placeholder="slug" bind:value={postDraft.slug} /><input class="admin-input" placeholder="发布日期 YYYY-MM-DD" bind:value={postDraft.published} /><input class="admin-input" placeholder="更新日期 YYYY-MM-DD" bind:value={postDraft.updated} /><input class="admin-input" placeholder="封面路径" bind:value={postDraft.image} /><input class="admin-input" placeholder="分类" bind:value={postDraft.category} /><input class="admin-input" placeholder="标签，逗号分隔" value={postDraft.tags.join(', ')} on:input={(event) => (postDraft.tags = parseKeywords((event.currentTarget as HTMLInputElement).value))} /><input class="admin-input" placeholder="语言" bind:value={postDraft.lang} /><input class="admin-input" placeholder="作者" bind:value={postDraft.author} /><input class="admin-input" placeholder="来源链接" bind:value={postDraft.sourceLink} /><input class="admin-input" placeholder="版权名称" bind:value={postDraft.licenseName} /><input class="admin-input" placeholder="版权链接" bind:value={postDraft.licenseUrl} /><textarea class="admin-code small-editor field-span-2" placeholder="摘要" bind:value={postDraft.description}></textarea></div><div class="check-row"><label><input type="checkbox" bind:checked={postDraft.draft} /> 草稿</label><label><input type="checkbox" bind:checked={postDraft.pinned} /> 置顶</label><label><input type="checkbox" bind:checked={postDraft.comment} /> 启用评论</label><label><input type="checkbox" checked={postDraft.extension === "mdx"} on:change={(event) => (postDraft.extension = (event.currentTarget as HTMLInputElement).checked ? "mdx" : "md")} /> 使用 MDX</label></div><textarea class="admin-code editor-input" bind:value={postDraft.body}></textarea></div>
							<div class="editor-preview custom-md prose dark:prose-invert max-w-none">{@html postPreview}</div>
						</div>
					</div>
				{/if}

				{#if activeNav === "pages"}
					<div class="card-base admin-panel">
						<div class="section-head"><h3>固定页面</h3><div class="admin-quick-actions wrap"><button class="btn-plain admin-button ghost" on:click={createNewPage}>新建页面</button><button class="btn-plain admin-button ghost" on:click={() => savePage(false)}>保存</button><button class="btn-regular admin-button primary" on:click={() => savePage(true)}>保存并发布</button></div></div>
						<div class="editor-layout"><div class="editor-list">{#each specPages as page}<button class:active={selectedPageSlug === page.slug} class="btn-plain editor-item" on:click={() => selectPage(page)}><strong>{page.slug}</strong><small>{page.filePath}</small></button>{/each}</div><textarea class="admin-code editor-input" bind:value={pageDraftBody}></textarea><div class="editor-preview custom-md prose dark:prose-invert max-w-none">{@html pagePreview}</div></div>
					</div>
				{/if}

				{#if activeNav === "media"}
					<div class="card-base admin-panel"><div class="section-head"><h3>图片与素材</h3><button class="btn-regular admin-button primary" on:click={uploadAsset}>上传图片</button></div><div class="field-grid"><select class="admin-input" bind:value={uploadRoot}><option value="public">public</option><option value="content">content/posts</option></select><input class="admin-input" placeholder="目录，例如 uploads/posts 或 covers" bind:value={uploadFolder} /><input class="admin-input" type="file" accept="image/*" on:change={chooseUploadFile} /><input class="admin-input" placeholder="搜索图片" bind:value={mediaSearch} /></div><div class="asset-grid">{#each filteredAssets as asset}<div class="card-base asset-card">{#if asset.previewUrl}<img src={asset.previewUrl} alt={asset.name} />{/if}<strong>{asset.name}</strong><small>{asset.root} / {asset.path}</small><small>引用：{asset.reference}</small><small>{formatBytes(asset.size)} · {formatDateTime(asset.modifiedAt)}</small><div class="asset-actions"><button class="btn-plain admin-button ghost" on:click={() => copyText(asset.reference, '引用路径')}>复制引用</button><button class="btn-plain admin-button ghost" on:click={() => copyText(asset.previewUrl, '预览链接')}>复制预览</button><button class="btn-plain admin-button ghost danger" on:click={() => removeAsset(asset)}>删除</button></div></div>{/each}</div></div>
				{/if}

				{#if activeNav === "advanced"}
					<div class="admin-grid two-col">{#each [['friends','友链配置'],['sponsor','赞助配置'],['ad','广告配置'],['comment','评论配置'],['music','音乐配置'],['pio','看板娘配置'],['coverImage','封面图配置'],['font','字体配置'],['sakura','樱花特效']] as [key, label]}<div class="card-base admin-panel"><div class="section-head"><h3>{label}</h3><button class="btn-regular admin-button primary" on:click={() => saveJsonSection(key as JsonSectionKey, label)}>保存</button></div><textarea class="admin-code section-editor" bind:value={sectionEditors[key as JsonSectionKey]}></textarea></div>{/each}</div>
				{/if}

				{#if activeNav === "build"}
					<div class="card-base admin-panel"><div class="section-head"><h3>构建与发布</h3><button class="btn-regular admin-button primary" on:click={rebuildSite}>手动构建</button></div><div class="build-meta"><p>状态：<strong>{buildLabel(buildState.status)}</strong></p><p>开始：{formatDateTime(buildState.startedAt)}</p><p>完成：{formatDateTime(buildState.finishedAt)}</p><p>最近成功：{formatDateTime(buildState.lastBuiltAt)}</p><p>队列：{buildState.queueLength || 0}</p>{#if buildState.queuedReason}<p>排队原因：{buildState.queuedReason}</p>{/if}{#if buildState.lastError}<p class="danger-text">错误：{buildState.lastError}</p>{/if}</div><pre class="admin-code build-log">{buildState.logs.join("\n") || "这里会显示完整构建日志。"}</pre></div>
				{/if}
			</section>
		</div>
	{/if}
</div>

<style>
	.admin-shell {
		display: flex;
		flex-direction: column;
		gap: 1rem;
	}
	.admin-workbench {
		display: grid;
		grid-template-columns: 18rem minmax(0, 1fr);
		gap: 1rem;
	}
	.admin-sidebar, .admin-main {
		display: flex;
		flex-direction: column;
		gap: 1rem;
	}
	.admin-panel, .admin-login {
		padding: 1.2rem;
	}
	.admin-auth-shell {
		display: grid;
		grid-template-columns: minmax(0, 1.15fr) minmax(22rem, 0.85fr);
		gap: 1rem;
		align-items: stretch;
	}
	.admin-auth-copy {
		position: relative;
		overflow: hidden;
		display: flex;
		flex-direction: column;
		justify-content: space-between;
		gap: 1.4rem;
		min-height: 22rem;
		background:
			radial-gradient(circle at top right, rgb(255 255 255 / 0.18), transparent 32%),
			linear-gradient(145deg, color-mix(in oklch, var(--card-bg) 78%, white 22%), color-mix(in oklch, var(--card-bg) 92%, var(--btn-card-bg-hover) 8%));
		border: 1px solid color-mix(in oklch, var(--line-divider) 72%, white 28%);
		box-shadow: 0 28px 80px rgb(8 20 28 / 0.12);
	}
	.admin-auth-intro {
		display: flex;
		flex-direction: column;
		gap: 0.75rem;
	}
	.admin-auth-intro h2 {
		margin: 0;
		font-size: clamp(2rem, 4vw, 3rem);
		line-height: 1.08;
	}
	.admin-auth-intro p {
		max-width: 40rem;
	}
	.admin-auth-facts {
		display: grid;
		grid-template-columns: repeat(3, minmax(0, 1fr));
		gap: 0.8rem;
	}
	.admin-auth-fact {
		display: flex;
		flex-direction: column;
		gap: 0.35rem;
		padding: 0.9rem;
		border-radius: 1rem;
		border: 1px solid color-mix(in oklch, var(--line-divider) 74%, white 26%);
		background: color-mix(in oklch, var(--card-bg) 82%, white 18%);
	}
	.admin-auth-fact strong {
		font-size: 0.95rem;
	}
	.admin-auth-fact span {
		font-size: 0.92rem;
		color: rgba(0,0,0,.72);
	}
	:root.dark .admin-auth-fact span {
		color: rgba(255,255,255,.72);
	}
	.admin-login {
		max-width: 30rem;
		margin: 0 auto;
		display: flex;
		flex-direction: column;
		gap: 0.9rem;
	}
	.admin-auth-shell .admin-login {
		max-width: none;
		width: 100%;
		margin: 0;
		justify-content: center;
	}
	.admin-field {
		display: flex;
		flex-direction: column;
		gap: 0.4rem;
	}
	.admin-field > span {
		font-size: 0.88rem;
		font-weight: 700;
	}
	.admin-login-pending {
		min-height: 22rem;
		justify-content: center;
	}
	.admin-loading-stack {
		display: flex;
		flex-direction: column;
		gap: 0.75rem;
	}
	.admin-loading-line {
		height: 0.9rem;
		border-radius: 999px;
		background: linear-gradient(90deg, rgb(255 255 255 / 0.28), rgb(255 255 255 / 0.62), rgb(255 255 255 / 0.22));
		background-size: 200% 100%;
		animation: adminPulse 1.8s ease-in-out infinite;
	}
	.admin-loading-line.short {
		width: 48%;
	}
	.admin-loading-line.medium {
		width: 70%;
	}
	.admin-login-stage {
		position: relative;
		z-index: 6;
		border: 1px solid color-mix(in oklch, var(--line-divider) 68%, white 32%);
		background: color-mix(in oklch, var(--card-bg) 86%, white 14%);
		backdrop-filter: blur(18px);
		box-shadow: 0 28px 80px rgb(8 20 28 / 0.18);
	}
	.admin-login-badge, .build-pill {
		display: inline-flex;
		align-items: center;
		gap: 0.4rem;
		padding: 0.35rem 0.75rem;
		border-radius: 999px;
		background: color-mix(in oklch, var(--card-bg) 68%, var(--btn-regular-bg-hover) 32%);
		font-size: 0.82rem;
		font-weight: 700;
		width: fit-content;
	}
	.build-pill { margin-top: 0.5rem; }
	.state-building, .state-success { color: var(--primary); }
	.state-error { color: oklch(0.62 0.2 25); }
	.admin-toast {
		padding: 0.85rem 1rem;
		border-radius: 1rem;
		border: 1px solid var(--line-divider);
	}
	.admin-toast.success { background: color-mix(in oklch, var(--primary) 14%, var(--card-bg) 86%); }
	.admin-toast.error { background: color-mix(in oklch, oklch(0.68 0.18 25) 16%, var(--card-bg) 84%); }
	.admin-toast.info { background: color-mix(in oklch, var(--btn-regular-bg-hover) 35%, var(--card-bg) 65%); }
	.admin-sidebar-card h3, .admin-panel h3, .admin-panel h4, .admin-login h2 { margin: 0; }
	.admin-sidebar-card p, .admin-panel p, .admin-login p, .admin-login-status { margin: 0; color: rgba(0,0,0,.72); }
	:root.dark .admin-sidebar-card p, :root.dark .admin-panel p, :root.dark .admin-login p, :root.dark .admin-login-status { color: rgba(255,255,255,.72); }
	.admin-quick-actions { display: flex; gap: 0.6rem; align-items: center; }
	.admin-quick-actions.wrap { flex-wrap: wrap; }
	.admin-button { min-height: 2.7rem; padding: 0.65rem 1rem; border-radius: 0.95rem; }
	.admin-button[disabled] { opacity: 0.7; cursor: wait; }
	.admin-button.ghost { border: 1px solid var(--line-divider); }
	.admin-button.danger { color: oklch(0.62 0.2 25); }
	.admin-nav-button, .editor-item, .overview-item {
		width: 100%;
		padding: 0.85rem 0.95rem;
		border-radius: 0.95rem;
		justify-content: flex-start;
		align-items: flex-start;
		text-align: left;
		display: flex;
		flex-direction: column;
		gap: 0.15rem;
	}
	.admin-nav-button.active, .editor-item.active { background: var(--btn-regular-bg-hover); color: var(--primary); }
	.admin-nav-button small, .editor-item small, .overview-item small { color: rgba(0,0,0,.55); }
	:root.dark .admin-nav-button small, :root.dark .editor-item small, :root.dark .overview-item small { color: rgba(255,255,255,.55); }
	.admin-grid { display: grid; gap: 1rem; }
	.metrics-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
	.two-col { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.metric-card { display: flex; flex-direction: column; gap: 0.45rem; }
	.metric-card strong { font-size: 1.8rem; }
	.field-grid { display: grid; gap: 0.8rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.field-grid.compact-grid { margin-top: 0.8rem; }
	.field-span-2, .full-span { grid-column: 1 / -1; }
	.admin-input, .admin-code {
		width: 100%;
		border: 1px solid var(--line-divider);
		background: color-mix(in oklch, var(--card-bg) 90%, var(--btn-card-bg-hover) 10%);
		border-radius: 0.95rem;
		padding: 0.8rem 0.95rem;
		color: inherit;
	}
	.admin-code { font-family: "JetBrains Mono Variable", ui-monospace, Consolas, monospace; line-height: 1.6; resize: vertical; }
	.small-editor { min-height: 7rem; }
	.section-editor { min-height: 18rem; }
	.editor-layout { display: grid; grid-template-columns: 15rem minmax(0, 1fr) minmax(0, 1fr); gap: 1rem; }
	.posts-layout { grid-template-columns: 16rem minmax(0, 1.1fr) minmax(0, 0.9fr); }
	.editor-list, .post-form, .overview-list { display: flex; flex-direction: column; gap: 0.7rem; }
	.editor-input, .editor-preview { min-height: 28rem; }
	.editor-preview { overflow: auto; padding: 1rem; border: 1px solid var(--line-divider); border-radius: 1rem; background: color-mix(in oklch, var(--card-bg) 92%, var(--btn-card-bg-hover) 8%); }
	.asset-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem; margin-top: 1rem; }
	.asset-card { padding: 0.9rem; display: flex; flex-direction: column; gap: 0.45rem; }
	.asset-card img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; border-radius: 0.85rem; }
	.asset-actions, .check-row, .section-head, .inline-head, .build-meta { display: flex; gap: 0.75rem; }
	.asset-actions, .check-row, .build-meta { flex-wrap: wrap; }
	.section-head, .inline-head { justify-content: space-between; align-items: center; margin-bottom: 0.8rem; }
	.section-subtitle { font-size: 0.88rem; font-weight: 700; margin-top: 0.4rem; }
	.build-log { max-height: 22rem; overflow: auto; }
	.danger-text { color: oklch(0.62 0.2 25); }
	.admin-empty { padding: 2rem; text-align: center; }
	@keyframes adminPulse {
		0% {
			background-position: 0% 50%;
		}
		100% {
			background-position: 100% 50%;
		}
	}
	@media (max-width: 1200px) {
		.admin-auth-shell,
		.admin-auth-facts,
		.admin-workbench,
		.two-col,
		.metrics-grid,
		.asset-grid,
		.editor-layout,
		.posts-layout {
			grid-template-columns: 1fr;
		}
	}
	@media (max-width: 800px) {
		.admin-login {
			max-width: 100%;
		}
		.field-grid { grid-template-columns: 1fr; }
		.field-span-2 { grid-column: auto; }
	}
</style>


