Skip to main content

yggdrasil/pages/components_showcase/
layout_article_previews.rs

1//! 布局与文章组件的本地样例。所有展示复用正式组件,演示操作仅改本地状态。
2
3use dioxus::prelude::*;
4
5use crate::components::admin_layout::{AdminShell, AdminSidebar};
6use crate::components::footer::FooterView;
7use crate::components::frontend_layout::FrontendShell;
8use crate::components::header::{Header, SearchIconLink};
9use crate::components::nav::build_nav_items;
10use crate::components::post::breadcrumbs::Breadcrumbs;
11use crate::components::post::post_content::PostContent;
12use crate::components::post::post_footer::PostFooter;
13use crate::components::post::post_header::PostHeader;
14use crate::components::post::post_meta::PostMeta;
15use crate::components::post::post_nav_links::PostNavLinks;
16use crate::components::post::post_toc::PostToc;
17use crate::components::skeletons::home_skeleton::HomeSkeleton;
18use crate::models::post::{PostNav, PostStatus};
19use crate::router::Route;
20use crate::theme::ThemeToggle;
21
22pub(super) fn preview(slug: &str, detail: bool) -> Option<Element> {
23    match slug {
24        "admin-layout" => Some(rsx! { AdminLayoutPreview { detail } }),
25        "header" => Some(rsx! { HeaderPreview { detail } }),
26        "search-icon-link" => Some(rsx! { SearchIconLinkPreview { detail } }),
27        "footer" => Some(rsx! { FooterPreview { detail } }),
28        "frontend-layout" => Some(rsx! { FrontendLayoutPreview { detail } }),
29        "breadcrumbs" => Some(rsx! { BreadcrumbsPreview { detail } }),
30        "post-header" => Some(rsx! { PostHeaderPreview { detail } }),
31        "post-meta" => Some(rsx! { PostMetaPreview {} }),
32        "post-content" => Some(rsx! { PostContentPreview { detail } }),
33        "post-footer" => Some(rsx! { PostFooterPreview { detail } }),
34        "post-nav-links" => Some(rsx! { PostNavLinksPreview { detail } }),
35        "post-toc" => Some(rsx! { PostTocPreview { detail } }),
36        _ => None,
37    }
38}
39
40#[component]
41fn PostMetaPreview() -> Element {
42    let post = super::sample_post();
43
44    rsx! {
45        article { class: "showcase-post-meta-demo", "data-showcase-preview": "post-meta",
46            p { class: "showcase-post-meta-kicker", "设计手记" }
47            h3 { class: "post-title", "{post.title}" }
48            p { class: "post-description", "{post.summary.as_deref().unwrap_or_default()}" }
49            PostMeta { post }
50        }
51    }
52}
53
54#[component]
55fn PostHeaderPreview(detail: bool) -> Element {
56    let mut post = super::sample_post();
57    post.summary = Some(
58        "在时间的缝隙里,种下一些文字。记录设计、代码与日常,让零散的念头慢慢长成一片风景。"
59            .to_string(),
60    );
61    let mut draft = post.clone();
62    draft.title = "还在生长的想法".to_string();
63    draft.summary = Some("这一页,留给尚未完成的念头。".to_string());
64    draft.status = PostStatus::Draft;
65    draft.published_at = None;
66    let mut without_summary = post.clone();
67    without_summary.title = "在时间的缝隙里种下一些文字:从零开始构建自己的数字花园".to_string();
68    without_summary.summary = None;
69
70    rsx! {
71        div { class: "showcase-post-header-demo", "data-showcase-preview": "post-header",
72            article { class: "showcase-post-header-sheet", "data-showcase-sample": "published", inert: "true",
73                PostHeader { post }
74            }
75            if detail {
76                div { class: "showcase-post-header-examples",
77                    section { class: "showcase-post-header-example",
78                        h3 { "草稿" span { "标题旁显示草稿标记" } }
79                        article { class: "showcase-post-header-sheet", "data-showcase-sample": "draft", inert: "true",
80                            PostHeader { post: draft }
81                        }
82                    }
83                    section { class: "showcase-post-header-example",
84                        h3 { "长标题 · 无摘要" span { "自然换行" } }
85                        article { class: "showcase-post-header-sheet", "data-showcase-sample": "no-summary", inert: "true",
86                            PostHeader { post: without_summary }
87                        }
88                    }
89                }
90            }
91        }
92    }
93}
94
95#[component]
96fn BreadcrumbsPreview(detail: bool) -> Element {
97    let title = "让想法生根";
98    let long_title = "在时间的缝隙里种下一些文字:从零开始构建 Rust 与 Dioxus 全栈博客的设计与实践";
99
100    rsx! {
101        div { class: "showcase-breadcrumb-demo", "data-showcase-preview": "breadcrumbs",
102            article { class: "showcase-breadcrumb-context",
103                div { class: "showcase-breadcrumb-path", inert: "true",
104                    Breadcrumbs { title: title.to_string() }
105                }
106                div { class: "showcase-breadcrumb-copy",
107                    p { class: "showcase-breadcrumb-kicker", "随笔 / 设计" }
108                    h3 { "{title}" }
109                    p { class: "showcase-breadcrumb-summary", "把零散的念头,写成有迹可循的文字。" }
110                }
111            }
112            if detail {
113                div { class: "showcase-breadcrumb-examples",
114                    section { class: "showcase-breadcrumb-example",
115                        h3 { "长标题" span { "单行省略" } }
116                        div { class: "showcase-breadcrumb-path", inert: "true",
117                            Breadcrumbs { title: long_title.to_string() }
118                        }
119                    }
120                    section { class: "showcase-breadcrumb-example",
121                        h3 { "窄屏" span { "≤ 280px" } }
122                        div { class: "showcase-breadcrumb-path showcase-breadcrumb-path--narrow", inert: "true",
123                            Breadcrumbs { title: long_title.to_string() }
124                        }
125                    }
126                }
127                p { class: "showcase-breadcrumb-note", "保留首页入口,当前标题随可用宽度省略。" }
128            }
129        }
130    }
131}
132
133#[component]
134fn AdminLayoutPreview(detail: bool) -> Element {
135    let mut active = use_signal(|| "全部文章");
136    let mut status = use_signal(|| "侧栏操作只更新这份本地演示。");
137
138    let active_name = active();
139    let (stat_1_label, stat_1_val, stat_2_label, stat_2_val) = match active_name {
140        "仪表盘" => ("总访客", "1,280", "今日阅读", "342"),
141        "写文章" => ("当前字数", "1,420", "自动保存", "2分钟前"),
142        "笔记" | "笔记本" => ("公开笔记", "24", "笔记本数", "05"),
143        "回收站" => ("待清理", "02", "保留期限", "30天"),
144        "评论管理" => ("待审核", "01", "累计评论", "156"),
145        "素材" => ("媒体文件", "48", "已占用", "12.8MB"),
146        "友链" => ("正常链接", "18", "待确认", "02"),
147        "个人信息" => ("账号角色", "管理员", "安全状态", "双重认证"),
148        "设置" | "系统" => ("系统状态", "正常", "运行时间", "14天"),
149        "日志" => ("今日日志", "84条", "错误告警", "0"),
150        _ => ("草稿", "03", "已发布", "12"),
151    };
152
153    rsx! {
154        div { class: "showcase-admin-window", "data-showcase-preview": "admin-layout",
155            AdminShell {
156                preview: true,
157                sidebar: rsx! { AdminSidebar {
158                    route: Route::Admin {},
159                    user_name: Some("图鉴访客".to_string()),
160                    avatar_url: None,
161                    demo_active: Some(active().to_string()),
162                    preview: true,
163                    on_demo_navigate: move |label| {
164                        active.set(label);
165                        status.set("已切换本地示例栏目。");
166                    },
167                    on_logout: move |_| status.set("这里只演示退出按钮,不会退出当前账号。"),
168                } },
169                content: rsx! {
170                    article { class: "showcase-admin-content flex flex-col h-full",
171                        div { class: "flex items-center justify-between",
172                            p { class: "text-xs font-mono text-paper-secondary tracking-wider", "ADMIN / LOCAL DEMO" }
173                            if detail {
174                                span { class: "text-xs px-2 py-0.5 rounded-full bg-[var(--color-paper-entry)] text-paper-secondary border border-[var(--color-paper-border)]", "本地演示" }
175                            }
176                        }
177                        h2 { class: "text-xl font-semibold mt-1.5 flex items-center gap-2", "{active()}" }
178                        p { class: "text-sm text-paper-secondary mt-1", "{status()}" }
179
180                        div { class: "mt-4 grid grid-cols-2 gap-3",
181                            div { class: "rounded-2xl bg-paper-entry p-3.5 border border-[var(--color-paper-border)]/50",
182                                small { class: "text-paper-secondary text-xs", "{stat_1_label}" }
183                                strong { class: "block text-lg mt-0.5 font-bold tracking-tight", "{stat_1_val}" }
184                            }
185                            div { class: "rounded-2xl bg-paper-entry p-3.5 border border-[var(--color-paper-border)]/50",
186                                small { class: "text-paper-secondary text-xs", "{stat_2_label}" }
187                                strong { class: "block text-lg mt-0.5 font-bold tracking-tight", "{stat_2_val}" }
188                            }
189                        }
190
191                        if !detail {
192                            div { class: "mt-2.5 rounded-lg bg-paper-entry/50 border border-[var(--color-paper-border)]/40 px-2.5 py-1.5 flex items-center justify-between text-xs",
193                                div { class: "flex items-center gap-1.5 min-w-0",
194                                    span { class: "w-1.5 h-1.5 rounded-full bg-emerald-500 flex-shrink-0" }
195                                    span { class: "truncate font-medium text-[11px]", "响应式设计实践" }
196                                }
197                                span { class: "text-paper-secondary text-[10px] font-mono flex-shrink-0 ml-1", "已发布" }
198                            }
199                        }
200
201                        if detail {
202                            div { class: "mt-5 flex-1 min-h-0 flex flex-col",
203                                p { class: "text-xs font-medium text-paper-secondary mb-2 tracking-wide", "示例内容列表" }
204                                div { class: "rounded-xl border border-[var(--color-paper-border)] divide-y divide-[var(--color-paper-border)] bg-paper-entry/40 overflow-hidden text-xs",
205                                    div { class: "px-3.5 py-2.5 flex items-center justify-between hover:bg-paper-entry/80 transition-colors",
206                                        div { class: "flex items-center gap-2 min-w-0",
207                                            span { class: "w-1.5 h-1.5 rounded-full bg-emerald-500 flex-shrink-0" }
208                                            span { class: "truncate font-medium", "Dioxus 0.7 响应式设计实践" }
209                                        }
210                                        span { class: "text-paper-secondary flex-shrink-0 ml-2 font-mono text-[11px]", "09-22" }
211                                    }
212                                    div { class: "px-3.5 py-2.5 flex items-center justify-between hover:bg-paper-entry/80 transition-colors",
213                                        div { class: "flex items-center gap-2 min-w-0",
214                                            span { class: "w-1.5 h-1.5 rounded-full bg-amber-500 flex-shrink-0" }
215                                            span { class: "truncate font-medium", "Rust 全栈博客架构与布局演进" }
216                                        }
217                                        span { class: "text-paper-secondary flex-shrink-0 ml-2 font-mono text-[11px]", "09-18" }
218                                    }
219                                    div { class: "px-3.5 py-2.5 flex items-center justify-between hover:bg-paper-entry/80 transition-colors",
220                                        div { class: "flex items-center gap-2 min-w-0",
221                                            span { class: "w-1.5 h-1.5 rounded-full bg-emerald-500 flex-shrink-0" }
222                                            span { class: "truncate font-medium", "Tailwind CSS v4 样式系统接入记录" }
223                                        }
224                                        span { class: "text-paper-secondary flex-shrink-0 ml-2 font-mono text-[11px]", "09-15" }
225                                    }
226                                }
227                                p { class: "text-xs text-paper-secondary mt-auto pt-3", "可展开内容管理或工具菜单,点击任意栏目查看联动状态。" }
228                            }
229                        }
230                    }
231                },
232            }
233        }
234    }
235}
236
237fn sample_route(label: &str) -> Route {
238    match label {
239        "笔记" => Route::Notes {},
240        "归档" => Route::Archives {},
241        "友链" => Route::Friends {},
242        "关于" => Route::About {},
243        "搜索" => Route::Search {},
244        _ => Route::Home {},
245    }
246}
247
248#[derive(Clone, Copy, PartialEq, Eq)]
249enum HeaderViewportMode {
250    Desktop,
251    Mobile,
252}
253
254#[component]
255fn HeaderPreview(detail: bool) -> Element {
256    let mut active = use_signal(|| "笔记");
257    let mut viewport_mode = use_signal(|| HeaderViewportMode::Desktop);
258    let mut max_width_choice = use_signal(|| "max-w-6xl");
259    let menu_id = if detail {
260        "mobile-nav-menu-showcase-header-detail"
261    } else {
262        "mobile-nav-menu-showcase-header-card"
263    };
264
265    let search_button = rsx! {
266        button {
267            class: if active() == "搜索" {
268                "relative p-2 rounded-full text-paper-accent transition-colors duration-200"
269            } else {
270                "relative p-2 rounded-full text-paper-secondary hover:text-paper-accent transition-colors duration-200"
271            },
272            r#type: "button",
273            aria_label: "演示搜索操作",
274            title: "搜索",
275            onclick: move |_| active.set("搜索"),
276            svg {
277                xmlns: "http://www.w3.org/2000/svg",
278                class: "w-4 h-4 md:w-5 md:h-5",
279                view_box: "0 -960 960 960",
280                fill: "currentColor",
281                path { d: "M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z" }
282            }
283            if active() == "搜索" {
284                span { class: "nav-indicator hidden md:block", "aria-hidden": "true" }
285            }
286        }
287    };
288
289    if !detail {
290        return rsx! {
291            div { class: "showcase-layout-window", "data-showcase-preview": "header",
292                Header {
293                    nav_items: build_nav_items(sample_route(active())),
294                    right_content: rsx! { {search_button} },
295                    max_width: "max-w-6xl",
296                    menu_id,
297                    page_shell: false,
298                    on_demo_navigate: move |label| active.set(label),
299                }
300                div { class: "showcase-header-card-body",
301                    div { class: "showcase-header-card-hero",
302                        div {
303                            p { class: "text-[11px] font-mono text-paper-secondary", "ACTIVE / 当前栏目" }
304                            p { class: "text-sm font-semibold text-paper-primary mt-0.5", "{active()}" }
305                        }
306                        span { class: "text-[10px] px-2 py-0.5 rounded-full bg-paper-accent-soft text-paper-accent font-medium", "交互演示" }
307                    }
308                    div { class: "showcase-header-card-tags",
309                        span { "自适应居中" }
310                        span { "平滑下划指示" }
311                        span { "窄屏自动折叠" }
312                    }
313                }
314            }
315        };
316    }
317
318    rsx! {
319        div { class: "w-full flex flex-col gap-3",
320            div { class: "flex flex-wrap items-center justify-between gap-3 p-3 rounded-xl bg-paper-entry/50 border border-[var(--sc-line)]",
321                div { class: "flex items-center gap-2",
322                    span { class: "text-xs font-mono text-paper-secondary tracking-wider", "VIEWPORT" }
323                    div { class: "showcase-picker-mode-switch",
324                        button {
325                            r#type: "button",
326                            aria_pressed: "{viewport_mode() == HeaderViewportMode::Desktop}",
327                            onclick: move |_| viewport_mode.set(HeaderViewportMode::Desktop),
328                            "🖥️ 桌面端"
329                        }
330                        button {
331                            r#type: "button",
332                            aria_pressed: "{viewport_mode() == HeaderViewportMode::Mobile}",
333                            onclick: move |_| viewport_mode.set(HeaderViewportMode::Mobile),
334                            "📱 移动端 (380px)"
335                        }
336                    }
337                }
338                if viewport_mode() == HeaderViewportMode::Desktop {
339                    div { class: "flex items-center gap-2",
340                        span { class: "text-xs font-mono text-paper-secondary tracking-wider", "WIDTH" }
341                        div { class: "showcase-picker-mode-switch",
342                            button {
343                                r#type: "button",
344                                aria_pressed: "{max_width_choice() == \"max-w-3xl\"}",
345                                onclick: move |_| max_width_choice.set("max-w-3xl"),
346                                "3xl (768px)"
347                            }
348                            button {
349                                r#type: "button",
350                                aria_pressed: "{max_width_choice() == \"max-w-4xl\"}",
351                                onclick: move |_| max_width_choice.set("max-w-4xl"),
352                                "4xl (896px)"
353                            }
354                            button {
355                                r#type: "button",
356                                aria_pressed: "{max_width_choice() == \"max-w-6xl\"}",
357                                onclick: move |_| max_width_choice.set("max-w-6xl"),
358                                "6xl (1152px)"
359                            }
360                        }
361                    }
362                }
363            }
364
365            div {
366                class: if viewport_mode() == HeaderViewportMode::Mobile { "showcase-layout-phone-wrap" } else { "w-full" },
367                div {
368                    class: if viewport_mode() == HeaderViewportMode::Mobile {
369                        "showcase-mobile-phone-frame"
370                    } else {
371                        "showcase-layout-window showcase-detail-window-scroll"
372                    },
373                    "data-showcase-preview": "header",
374
375                    if viewport_mode() == HeaderViewportMode::Mobile {
376                        div { class: "showcase-phone-notch",
377                            span { "09:41" }
378                            div { class: "showcase-phone-notch-pill" }
379                            span { "5G 100%" }
380                        }
381                    }
382
383                    Header {
384                        nav_items: build_nav_items(sample_route(active())),
385                        right_content: rsx! { {search_button} },
386                        max_width: if viewport_mode() == HeaderViewportMode::Mobile { "w-full" } else { max_width_choice() },
387                        menu_id,
388                        page_shell: false,
389                        on_demo_navigate: move |label| active.set(label),
390                    }
391
392                    div {
393                        class: if viewport_mode() == HeaderViewportMode::Mobile {
394                            "showcase-phone-scroll p-4 space-y-4"
395                        } else {
396                            "flex-1 p-6 md:p-8 space-y-6 max-w-4xl mx-auto w-full"
397                        },
398                        div { class: "rounded-2xl border border-[var(--sc-line)] bg-paper-entry/40 p-5",
399                            div { class: "flex items-center justify-between",
400                                span { class: "text-xs font-mono text-paper-secondary uppercase tracking-wider", "当前演示栏目" }
401                                span { class: "text-xs px-2.5 py-0.5 rounded-full bg-paper-accent-soft text-paper-accent font-semibold", "{active()}" }
402                            }
403                            h3 { class: "text-lg font-semibold mt-2 text-paper-primary",
404                                match active() {
405                                    "首页" => "首页 · 最新动态与精选文章",
406                                    "笔记" => "笔记 · 碎片记录与灵感闪光",
407                                    "归档" => "归档 · 按年份组织的历史文章",
408                                    "友链" => "友链 · 互联互通的网络邻居",
409                                    "关于" => "关于 · 站点介绍与设计故事",
410                                    "搜索" => "搜索 · 全局快速文本检索",
411                                    _ => "栏目演示",
412                                }
413                            }
414                            p { class: "text-xs text-paper-secondary mt-1.5 leading-relaxed",
415                                if viewport_mode() == HeaderViewportMode::Mobile {
416                                    "点击右上角汉堡按钮,可展开移动端全屏抽屉菜单;点击任意栏目自动收起并切换当前状态。"
417                                } else {
418                                    "点击上方导航栏项目可切换激活项;向下滚动本区域可体验 Header 粘性吸顶 (sticky) 与磨砂毛玻璃 (backdrop-blur) 效果。"
419                                }
420                            }
421                        }
422
423                        div { class: "rounded-2xl border border-[var(--sc-line)]/60 bg-paper-entry/20 p-5 space-y-3",
424                            p { class: "text-xs font-mono text-paper-secondary tracking-wider", "FEATURE HIGHLIGHTS / 特性说明" }
425                            ul { class: "text-xs text-paper-secondary space-y-2 list-disc list-inside",
426                                li { "自适应居中对齐:通过 max_width 参数确保顶部导航项左右边缘与下方文章正文精准对齐。" }
427                                li { "指示器平滑过渡:激活项下方带独立的 nav-indicator 下划线,栏目切换时平滑滑动。" }
428                                li { "移动端交互:在移动端或窄屏视口下自动转换为旋转图标汉堡按钮与平滑展开面板。" }
429                            }
430                        }
431
432                        div { class: "py-12 text-center text-xs font-mono text-paper-tertiary",
433                            "— 往下滚动查看 Header 磨砂吸顶效果 —"
434                        }
435                    }
436                }
437            }
438        }
439    }
440}
441
442#[derive(Clone, Copy, PartialEq, Eq)]
443enum SearchSceneMode {
444    HeaderNav,
445    SearchPalette,
446    DesignSpecs,
447}
448
449struct SampleSearchResult {
450    title: &'static str,
451    snippet: &'static str,
452    tag: &'static str,
453    date: &'static str,
454}
455
456const SAMPLE_SEARCH_ITEMS: &[SampleSearchResult] = &[
457    SampleSearchResult {
458        title: "让想法生根:Yggdrasil 设计体系与架构总览",
459        snippet: "在时间的缝隙里,种下一些文字。探讨 Rust、Dioxus 与响应式设计。",
460        tag: "设计",
461        date: "2026-09-23",
462    },
463    SampleSearchResult {
464        title: "响应式导航栏与自适应平滑指示器实践",
465        snippet: "如何通过 nav-indicator 独立下划线实现多栏目与搜索入口间的平滑视觉对齐。",
466        tag: "前端",
467        date: "2026-09-18",
468    },
469    SampleSearchResult {
470        title: "Dioxus 0.7 全栈组件化重构笔记",
471        snippet: "从桌面端全宽到移动端 380px 折叠抽屉,打造极致纯粹的 Web 交互体验。",
472        tag: "Rust",
473        date: "2026-09-10",
474    },
475    SampleSearchResult {
476        title: "Tailwind CSS v4 现代暗黑模式调色板指南",
477        snippet: "基于 currentColor 与 paper 主题语义变量的自适应图标与组件规范。",
478        tag: "样式",
479        date: "2026-08-28",
480    },
481];
482
483#[component]
484fn SearchIconLinkPreview(detail: bool) -> Element {
485    let mut scene_mode = use_signal(|| SearchSceneMode::HeaderNav);
486    let mut is_active_route = use_signal(|| false);
487    let mut query = use_signal(String::new);
488
489    // 卡片预览视图(在组件图鉴列表卡片中展示)
490    if !detail {
491        return rsx! {
492            div {
493                class: "showcase-layout-window flex flex-col justify-between p-3 select-none h-full",
494                "data-showcase-preview": "search-icon-link",
495
496                // 顶部:精美的迷你模拟 Header 导航栏
497                div { class: "rounded-xl border border-[var(--sc-line)] bg-paper-entry/60 px-3 py-2 flex items-center justify-between shadow-xs",
498                    div { class: "flex items-center gap-2",
499                        span { class: "w-2 h-2 rounded-full bg-paper-accent animate-pulse" }
500                        span { class: "text-xs font-serif font-bold text-paper-primary tracking-wide", "Yggdrasil" }
501                        div { class: "hidden sm:flex items-center gap-1.5 ml-1 text-[10px] text-paper-secondary/80",
502                            span { class: "px-1.5 py-0.5 rounded bg-paper-bg/60 border border-[var(--sc-line)]/50", "首页" }
503                            span { class: "px-1.5 py-0.5 rounded bg-paper-bg/60 border border-[var(--sc-line)]/50", "笔记" }
504                        }
505                    }
506                    div { class: "flex items-center gap-1",
507                        // 主角组件:真实渲染 SearchIconLink 与 ThemeToggle 并列
508                        SearchIconLink { active: Some(false) }
509                        ThemeToggle {}
510                    }
511                }
512
513                // 中间:状态与交互特性直观对比
514                div { class: "grid grid-cols-2 gap-2 my-auto py-1",
515                    div { class: "rounded-lg border border-[var(--sc-line)]/60 bg-paper-entry/30 p-2 flex items-center gap-2",
516                        div { class: "shrink-0 scale-90 -m-1 pointer-events-none",
517                            SearchIconLink { active: Some(false) }
518                        }
519                        div { class: "min-w-0 flex flex-col",
520                            span { class: "text-[11px] font-mono text-paper-secondary font-medium", "默认状态" }
521                            span { class: "text-[10px] text-paper-tertiary truncate", "次级色 · 悬停高亮" }
522                        }
523                    }
524                    div { class: "rounded-lg border border-paper-accent/30 bg-paper-accent/5 p-2 flex items-center gap-2",
525                        div { class: "shrink-0 scale-90 -m-1 pointer-events-none",
526                            SearchIconLink { active: Some(true) }
527                        }
528                        div { class: "min-w-0 flex flex-col",
529                            span { class: "text-[11px] font-mono text-paper-accent font-medium", "激活状态" }
530                            span { class: "text-[10px] text-paper-accent/80 truncate", "下划线 · /search" }
531                        }
532                    }
533                }
534
535                // 底部微标签
536                div { class: "flex items-center justify-between text-[10px] text-paper-secondary/80 pt-1.5 border-t border-[var(--sc-line)]/40 font-mono",
537                    span { "ARIA 语义友好" }
538                    span { "快捷键 ⌘K" }
539                    span { "40px 规范热区" }
540                }
541            }
542        };
543    }
544
545    // 详情页视图(丰富交互与场景全景)
546    let current_q = query().trim().to_lowercase();
547    let mock_url = if is_active_route() {
548        "https://yggdrasil.site/search"
549    } else {
550        "https://yggdrasil.site/about"
551    };
552    let filtered_items: Vec<_> = SAMPLE_SEARCH_ITEMS
553        .iter()
554        .filter(|item| {
555            if current_q.is_empty() {
556                true
557            } else {
558                item.title.to_lowercase().contains(&current_q)
559                    || item.snippet.to_lowercase().contains(&current_q)
560                    || item.tag.to_lowercase().contains(&current_q)
561            }
562        })
563        .collect();
564
565    rsx! {
566        div { class: "w-full flex flex-col gap-4",
567            // 顶部交互控制栏
568            div { class: "flex flex-wrap items-center justify-between gap-3 p-3 rounded-xl bg-paper-entry/50 border border-[var(--sc-line)]",
569                div { class: "flex items-center gap-2",
570                    span { class: "text-xs font-mono text-paper-secondary tracking-wider", "SCENE / 演示场景" }
571                    div { class: "showcase-picker-mode-switch",
572                        button {
573                            r#type: "button",
574                            aria_pressed: "{scene_mode() == SearchSceneMode::HeaderNav}",
575                            onclick: move |_| scene_mode.set(SearchSceneMode::HeaderNav),
576                            "🖥️ 导航栏场景"
577                        }
578                        button {
579                            r#type: "button",
580                            aria_pressed: "{scene_mode() == SearchSceneMode::SearchPalette}",
581                            onclick: move |_| scene_mode.set(SearchSceneMode::SearchPalette),
582                            "🔍 检索面板联动"
583                        }
584                        button {
585                            r#type: "button",
586                            aria_pressed: "{scene_mode() == SearchSceneMode::DesignSpecs}",
587                            onclick: move |_| scene_mode.set(SearchSceneMode::DesignSpecs),
588                            "📐 规范与无障碍"
589                        }
590                    }
591                }
592
593                div { class: "flex items-center gap-2",
594                    span { class: "text-xs font-mono text-paper-secondary tracking-wider", "ROUTE / 路由模拟" }
595                    div { class: "showcase-picker-mode-switch",
596                        button {
597                            r#type: "button",
598                            aria_pressed: "{!is_active_route()}",
599                            onclick: move |_| is_active_route.set(false),
600                            "常规页面 (/about)"
601                        }
602                        button {
603                            r#type: "button",
604                            aria_pressed: "{is_active_route()}",
605                            onclick: move |_| is_active_route.set(true),
606                            "搜索页面 (/search)"
607                        }
608                    }
609                }
610            }
611
612            // 核心展示区域
613            div { class: "rounded-2xl border border-[var(--sc-line)] bg-paper-theme overflow-hidden shadow-xs",
614                match scene_mode() {
615                    // 场景 1:在真实完整 Header 导航栏中的场景
616                    SearchSceneMode::HeaderNav => rsx! {
617                        div { class: "p-6 flex flex-col gap-6",
618                            div { class: "rounded-xl border border-[var(--sc-line)] bg-paper-entry/30 overflow-hidden shadow-xs",
619                                // 模拟顶部窗口标题栏
620                                div { class: "flex items-center justify-between px-3.5 py-2 border-b border-[var(--sc-line)] bg-paper-entry/50 text-[11px] font-mono text-paper-secondary",
621                                    div { class: "flex items-center gap-1.5",
622                                        span { class: "w-2.5 h-2.5 rounded-full bg-rose-500/70" }
623                                        span { class: "w-2.5 h-2.5 rounded-full bg-amber-500/70" }
624                                        span { class: "w-2.5 h-2.5 rounded-full bg-emerald-500/70" }
625                                    }
626                                    span { "{mock_url}" }
627                                    span { class: "text-[10px] text-paper-tertiary", "Header 预览" }
628                                }
629
630                                // 真实模拟导航栏
631                                div { class: "px-4 md:px-6 py-2.5 flex items-center justify-between bg-paper-theme/80 backdrop-blur-sm",
632                                    // 左侧 Logo
633                                    div { class: "flex items-center gap-2",
634                                        span { class: "w-2 h-2 rounded-full bg-paper-accent" }
635                                        span { class: "font-serif font-bold text-sm tracking-wide text-paper-primary", "Yggdrasil" }
636                                    }
637
638                                    // 中间导航项
639                                    div { class: "hidden md:flex items-center gap-4 text-xs font-medium text-paper-secondary",
640                                        span { class: if !is_active_route() { "text-paper-accent relative pb-1 border-b-2 border-paper-accent" } else { "hover:text-paper-primary transition-colors cursor-pointer" }, "关于" }
641                                        span { class: "hover:text-paper-primary transition-colors cursor-pointer", "首页" }
642                                        span { class: "hover:text-paper-primary transition-colors cursor-pointer", "笔记" }
643                                        span { class: "hover:text-paper-primary transition-colors cursor-pointer", "归档" }
644                                    }
645
646                                    // 右侧操作项:SearchIconLink 与 ThemeToggle
647                                    div { class: "flex items-center gap-2",
648                                        div {
649                                            class: "relative cursor-pointer group flex items-center justify-center",
650                                            title: "点击可切换激活状态",
651                                            // 真实渲染 SearchIconLink
652                                            SearchIconLink {
653                                                active: Some(is_active_route()),
654                                                on_click: move |_| is_active_route.toggle(),
655                                            }
656                                            // 悬浮指示角标
657                                            span { class: "absolute -top-1 -right-1 flex h-2 w-2",
658                                                span { class: "animate-ping absolute inline-flex h-full w-full rounded-full bg-paper-accent opacity-60" }
659                                                span { class: "relative inline-flex rounded-full h-2 w-2 bg-paper-accent" }
660                                            }
661                                        }
662                                        div { class: "text-paper-secondary",
663                                            ThemeToggle {}
664                                        }
665                                    }
666                                }
667                            }
668
669                            // 状态与属性说明条
670                            div { class: "grid grid-cols-1 md:grid-cols-3 gap-3 text-xs",
671                                div { class: "p-3.5 rounded-xl border border-[var(--sc-line)] bg-paper-entry/40",
672                                    p { class: "font-mono text-[10px] text-paper-secondary tracking-wider", "CURRENT STATUS / 当前状态" }
673                                    p { class: "font-semibold text-paper-primary mt-1 flex items-center gap-1.5",
674                                        if is_active_route() {
675                                            span { class: "w-2 h-2 rounded-full bg-paper-accent" }
676                                            "已激活 · 位于搜索页 (/search)"
677                                        } else {
678                                            span { class: "w-2 h-2 rounded-full bg-paper-secondary" }
679                                            "默认待命 · 常规页面 (/about)"
680                                        }
681                                    }
682                                    p { class: "text-[11px] text-paper-secondary mt-1 leading-relaxed",
683                                        if is_active_route() {
684                                            "当前图标呈现 text-paper-accent,底部挂载独立的 nav-indicator 绿色下划线指示条。"
685                                        } else {
686                                            "当前图标呈现 text-paper-secondary,鼠标悬浮时平滑过渡至强调色。"
687                                        }
688                                    }
689                                }
690
691                                div { class: "p-3.5 rounded-xl border border-[var(--sc-line)] bg-paper-entry/40",
692                                    p { class: "font-mono text-[10px] text-paper-secondary tracking-wider", "A11Y SEMANTICS / 无障碍语义" }
693                                    div { class: "mt-1 space-y-1 font-mono text-[11px] text-paper-primary",
694                                        p { "aria-label = \"搜索\"" }
695                                        p { "title = \"搜索\"" }
696                                        p { if is_active_route() { "aria-current = \"page\"" } else { "aria-current = (none)" } }
697                                    }
698                                    p { class: "text-[11px] text-paper-secondary mt-1", "屏幕阅读器与辅助工具可精准感知当前定位。" }
699                                }
700
701                                div { class: "p-3.5 rounded-xl border border-[var(--sc-line)] bg-paper-entry/40 flex flex-col justify-between",
702                                    div {
703                                        p { class: "font-mono text-[10px] text-paper-secondary tracking-wider", "INTERACTION / 交互提示" }
704                                        p { class: "text-xs text-paper-primary font-medium mt-1", "点击上方搜索图标直接联动" }
705                                        p { class: "text-[11px] text-paper-secondary mt-0.5", "通过 on_click 事件拦截即可就地演示激活与指示条过渡效果。" }
706                                    }
707                                    button {
708                                        r#type: "button",
709                                        class: "mt-2 self-start px-2.5 py-1 text-[11px] rounded-lg bg-paper-entry border border-[var(--sc-line)] text-paper-primary hover:border-paper-accent transition-colors",
710                                        onclick: move |_| is_active_route.toggle(),
711                                        "↻ 切换激活状态"
712                                    }
713                                }
714                            }
715                        }
716                    },
717
718                    // 场景 2:检索面板与全局搜索浮层模拟
719                    SearchSceneMode::SearchPalette => rsx! {
720                        div { class: "p-6 flex flex-col gap-4",
721                            div { class: "flex items-center justify-between text-xs text-paper-secondary font-mono",
722                                span { "SEARCH OVERLAY / 模拟点击搜索入口后唤起的快捷检索面板" }
723                                span { "共收录 {SAMPLE_SEARCH_ITEMS.len()} 篇样例索引" }
724                            }
725
726                            // 模拟 Spotlight 搜索弹层
727                            div { class: "rounded-2xl border border-[var(--sc-line)] bg-paper-entry/40 backdrop-blur-md p-4 sm:p-5 flex flex-col gap-3 shadow-md",
728                                // 搜索输入框
729                                div { class: "relative flex items-center",
730                                    span { class: "absolute left-3.5 text-paper-secondary",
731                                        svg {
732                                            xmlns: "http://www.w3.org/2000/svg",
733                                            class: "w-4 h-4",
734                                            view_box: "0 0 24 24",
735                                            fill: "none",
736                                            stroke: "currentColor",
737                                            stroke_width: "1.6",
738                                            stroke_linecap: "round",
739                                            stroke_linejoin: "round",
740                                            "aria-hidden": "true",
741                                            circle { cx: "10.75", cy: "10.75", r: "6.75" }
742                                            path { d: "m16 16 4.5 4.5" }
743                                        }
744                                    }
745                                    input {
746                                        class: "w-full pl-10 pr-20 py-2.5 text-sm rounded-xl border border-[var(--sc-line)] bg-paper-theme text-paper-primary placeholder:text-paper-tertiary focus:outline-none focus:border-paper-accent focus:ring-1 focus:ring-paper-accent/30 transition-all",
747                                        r#type: "text",
748                                        placeholder: "输入关键词快速检索,如 Rust、设计、Dioxus…",
749                                        value: "{query()}",
750                                        oninput: move |e| query.set(e.value()),
751                                    }
752                                    div { class: "absolute right-3 flex items-center gap-1.5",
753                                        if !query().is_empty() {
754                                            button {
755                                                r#type: "button",
756                                                class: "text-xs text-paper-secondary hover:text-paper-primary p-1 rounded-md",
757                                                onclick: move |_| query.set(String::new()),
758                                                "✕"
759                                            }
760                                        }
761                                        span { class: "text-[10px] font-mono px-1.5 py-0.5 rounded border border-[var(--sc-line)] bg-paper-entry text-paper-tertiary", "ESC" }
762                                    }
763                                }
764
765                                // 热门推荐标签
766                                div { class: "flex flex-wrap items-center gap-1.5 text-xs",
767                                    span { class: "text-[11px] text-paper-secondary mr-1", "快速填入:" }
768                                    for tag in &["Rust", "设计", "Dioxus", "Tailwind", "前端"] {
769                                        button {
770                                            r#type: "button",
771                                            class: "px-2 py-0.5 text-[11px] rounded-full border border-[var(--sc-line)] bg-paper-theme/60 text-paper-secondary hover:text-paper-accent hover:border-paper-accent transition-colors",
772                                            onclick: {
773                                                let t = tag.to_string();
774                                                move |_| query.set(t.clone())
775                                            },
776                                            "{tag}"
777                                        }
778                                    }
779                                }
780
781                                // 结果列表
782                                div { class: "mt-1 flex flex-col gap-2 max-h-60 overflow-y-auto pr-1",
783                                    if filtered_items.is_empty() {
784                                        div { class: "py-8 text-center text-xs text-paper-secondary",
785                                            "没有匹配到相关结果,换个关键词试试。"
786                                        }
787                                    } else {
788                                        for (idx, item) in filtered_items.iter().enumerate() {
789                                            div {
790                                                key: "{idx}",
791                                                class: "p-3 rounded-xl border border-[var(--sc-line)]/60 bg-paper-theme/40 hover:bg-paper-theme/80 hover:border-paper-accent/50 transition-all flex flex-col gap-1 cursor-pointer",
792                                                div { class: "flex items-center justify-between gap-2",
793                                                    h4 { class: "text-xs font-semibold text-paper-primary truncate", "{item.title}" }
794                                                    div { class: "flex items-center gap-1.5 shrink-0",
795                                                        span { class: "text-[10px] font-mono text-paper-tertiary", "{item.date}" }
796                                                        span { class: "text-[10px] font-mono px-1.5 py-0.5 rounded bg-paper-entry text-paper-secondary", "{item.tag}" }
797                                                    }
798                                                }
799                                                p { class: "text-[11px] text-paper-secondary line-clamp-1", "{item.snippet}" }
800                                            }
801                                        }
802                                    }
803                                }
804                            }
805                        }
806                    },
807
808                    // 场景 3:设计规范与无障碍解构
809                    SearchSceneMode::DesignSpecs => rsx! {
810                        div { class: "p-6 flex flex-col gap-6",
811                            // 双态大图特写
812                            div { class: "grid grid-cols-1 md:grid-cols-2 gap-4",
813                                // 默认态特写
814                                div { class: "p-6 rounded-2xl border border-[var(--sc-line)] bg-paper-entry/30 flex flex-col items-center justify-center gap-3 text-center",
815                                    div { class: "relative p-4 rounded-full border-2 border-dashed border-paper-border/60 bg-paper-theme shadow-xs",
816                                        SearchIconLink { active: Some(false) }
817                                        span { class: "absolute -bottom-2 px-2 py-0.5 text-[9px] font-mono rounded bg-paper-entry text-paper-secondary border border-[var(--sc-line)]", "40×40px" }
818                                    }
819                                    div {
820                                        h4 { class: "text-sm font-semibold text-paper-primary", "默认待命态 (Default State)" }
821                                        p { class: "text-xs text-paper-secondary mt-1", "柔和次级色调 text-paper-secondary,提供平滑悬停过渡色" }
822                                    }
823                                }
824
825                                // 激活态特写
826                                div { class: "p-6 rounded-2xl border border-paper-accent/30 bg-paper-accent/5 flex flex-col items-center justify-center gap-3 text-center",
827                                    div { class: "relative p-4 rounded-full border-2 border-dashed border-paper-accent/40 bg-paper-theme shadow-xs",
828                                        SearchIconLink { active: Some(true) }
829                                        span { class: "absolute -bottom-2 px-2 py-0.5 text-[9px] font-mono rounded bg-paper-accent text-white", "Active" }
830                                    }
831                                    div {
832                                        h4 { class: "text-sm font-semibold text-paper-accent", "路由激活态 (Active State)" }
833                                        p { class: "text-xs text-paper-secondary mt-1", "强调色 text-paper-accent,底部显示独立的 nav-indicator 指示条" }
834                                    }
835                                }
836                            }
837
838                            // 规范对照参数表
839                            div { class: "rounded-xl border border-[var(--sc-line)] overflow-hidden text-xs",
840                                div { class: "grid grid-cols-3 bg-paper-entry/60 px-4 py-2 font-mono text-[11px] text-paper-secondary border-b border-[var(--sc-line)]",
841                                    span { "参数维度" }
842                                    span { "实现规范" }
843                                    span { "设计目的" }
844                                }
845                                div { class: "divide-y divide-[var(--sc-line)] bg-paper-theme/50",
846                                    div { class: "grid grid-cols-3 px-4 py-2.5 items-center",
847                                        span { class: "font-mono font-medium text-paper-primary", "Touch Target" }
848                                        span { class: "text-paper-secondary font-mono", "40px × 40px (p-2 rounded-full)" }
849                                        span { class: "text-paper-secondary", "符合 WCAG 移动触控最小热区标准" }
850                                    }
851                                    div { class: "grid grid-cols-3 px-4 py-2.5 items-center",
852                                        span { class: "font-mono font-medium text-paper-primary", "Vector Icon" }
853                                        span { class: "text-paper-secondary font-mono", "24px × 24px (统一线性放大镜)" }
854                                        span { class: "text-paper-secondary", "与 /search 搜索页面图标风格保持 100% 呼应" }
855                                    }
856                                    div { class: "grid grid-cols-3 px-4 py-2.5 items-center",
857                                        span { class: "font-mono font-medium text-paper-primary", "Color & Stroke" }
858                                        span { class: "text-paper-secondary font-mono", "stroke=\"currentColor\"" }
859                                        span { class: "text-paper-secondary", "1.6px 极简线条,跟随纸质主题自适应着色" }
860                                    }
861                                    div { class: "grid grid-cols-3 px-4 py-2.5 items-center",
862                                        span { class: "font-mono font-medium text-paper-primary", "Indicator" }
863                                        span { class: "text-paper-secondary font-mono", "nav-indicator (hidden md:block)" }
864                                        span { class: "text-paper-secondary", "与文字导航保持一致的激活视觉语法" }
865                                    }
866                                }
867                            }
868                        }
869                    },
870                }
871            }
872
873            // 底部设计说明卡片
874            div { class: "grid grid-cols-1 md:grid-cols-3 gap-3.5",
875                div { class: "p-4 rounded-xl border border-[var(--sc-line)]/70 bg-paper-entry/20 flex flex-col gap-1.5",
876                    div { class: "flex items-center gap-1.5 text-paper-accent font-medium text-xs",
877                        span { "🌲" }
878                        span { "路由自动感知" }
879                    }
880                    p { class: "text-xs text-paper-secondary leading-relaxed",
881                        "组件内部通过 use_route::<Route>() 自动感知是否匹配 Route::Search,零额外代码即可同步高亮状态。"
882                    }
883                }
884
885                div { class: "p-4 rounded-xl border border-[var(--sc-line)]/70 bg-paper-entry/20 flex flex-col gap-1.5",
886                    div { class: "flex items-center gap-1.5 text-paper-accent font-medium text-xs",
887                        span { "🎨" }
888                        span { "图标组视觉统一" }
889                    }
890                    p { class: "text-xs text-paper-secondary leading-relaxed",
891                        "与 ThemeToggle 主题切换按钮共享一致的内边距、圆角与过渡时长,维持导航栏右侧图标组严格对齐。"
892                    }
893                }
894
895                div { class: "p-4 rounded-xl border border-[var(--sc-line)]/70 bg-paper-entry/20 flex flex-col gap-1.5",
896                    div { class: "flex items-center gap-1.5 text-paper-accent font-medium text-xs",
897                        span { "♿" }
898                        span { "无障碍可达性" }
899                    }
900                    p { class: "text-xs text-paper-secondary leading-relaxed",
901                        "内置 aria-label=\"搜索\" 与 aria-current 状态宣告,全键盘操作友好,支持右键新标签页打开。"
902                    }
903                }
904            }
905        }
906    }
907}
908
909#[component]
910fn FooterPreview(detail: bool) -> Element {
911    let scroll_id = if detail {
912        "showcase-footer-scroll-detail"
913    } else {
914        "showcase-footer-scroll-card"
915    };
916    rsx! {
917        div { class: "showcase-layout-window", "data-showcase-preview": "footer",
918            div { id: scroll_id, class: "showcase-footer-scroll",
919                p { "页脚会陪你到页面尽头。" }
920                if detail {
921                    p { "向下滚动这一小块内容,再点击页脚右侧按钮,就会回到演示顶部。" }
922                    div { style: "height: 180px" }
923                    p { "这里是演示内容的末尾。" }
924                }
925            }
926            FooterView {
927                github_url: Some("https://github.com".to_string()),
928                top_visible: true,
929                page_shell: false,
930                inline_top: true,
931                on_top: move |_| scroll_demo_to_top(scroll_id),
932            }
933        }
934    }
935}
936
937#[derive(Clone, Copy, PartialEq, Eq)]
938enum FrontendLayoutMode {
939    Desktop,
940    Mobile,
941    Skeleton,
942}
943
944#[component]
945fn FrontendLayoutPreview(detail: bool) -> Element {
946    let mut active = use_signal(|| "首页");
947    let mut layout_mode = use_signal(|| FrontendLayoutMode::Desktop);
948    let mut max_width_choice = use_signal(|| "max-w-4xl");
949    let menu_id = if detail {
950        "mobile-nav-menu-showcase-layout-detail"
951    } else {
952        "mobile-nav-menu-showcase-layout-card"
953    };
954    let scroll_id = if detail {
955        "showcase-frontend-scroll-detail"
956    } else {
957        "showcase-frontend-scroll-card"
958    };
959
960    let search_button = rsx! {
961        button {
962            class: if active() == "搜索" {
963                "p-2 rounded-full text-paper-accent transition-colors duration-200"
964            } else {
965                "p-2 rounded-full text-paper-secondary hover:text-paper-accent transition-colors duration-200"
966            },
967            r#type: "button",
968            aria_label: "演示搜索操作",
969            onclick: move |_| active.set("搜索"),
970            svg {
971                xmlns: "http://www.w3.org/2000/svg",
972                class: "w-4 h-4 md:w-5 md:h-5",
973                view_box: "0 -960 960 960",
974                fill: "currentColor",
975                path { d: "M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z" }
976            }
977        }
978    };
979
980    if !detail {
981        return rsx! {
982            div { id: scroll_id, class: "showcase-layout-window", "data-showcase-preview": "frontend-layout",
983                FrontendShell {
984                    min_height: "h-full min-h-0",
985                    max_width: "max-w-4xl",
986                    header: rsx! {
987                        Header {
988                            nav_items: build_nav_items(sample_route(active())),
989                            right_content: rsx! { {search_button} },
990                            max_width: "max-w-4xl",
991                            menu_id,
992                            page_shell: false,
993                            on_demo_navigate: move |label| active.set(label),
994                        }
995                    },
996                    main_content: rsx! {
997                        article { class: "flex flex-col justify-center h-full",
998                            h2 { class: "font-semibold text-paper-primary", "一页内容,安心阅读" }
999                            p { class: "text-paper-secondary mt-1", "前台布局将导航、正文和页脚排成清晰的阅读路线。" }
1000                        }
1001                    },
1002                    footer: rsx! {
1003                        FooterView {
1004                            github_url: Some("https://github.com".to_string()),
1005                            top_visible: false,
1006                            page_shell: false,
1007                            inline_top: true,
1008                            on_top: move |_| scroll_demo_to_top(scroll_id),
1009                        }
1010                    },
1011                }
1012            }
1013        };
1014    }
1015
1016    rsx! {
1017        div { class: "w-full flex flex-col gap-3",
1018            div { class: "flex flex-wrap items-center justify-between gap-3 p-3 rounded-xl bg-paper-entry/50 border border-[var(--sc-line)]",
1019                div { class: "flex items-center gap-2",
1020                    span { class: "text-xs font-mono text-paper-secondary tracking-wider", "LAYOUT" }
1021                    div { class: "showcase-picker-mode-switch",
1022                        button {
1023                            r#type: "button",
1024                            aria_pressed: "{layout_mode() == FrontendLayoutMode::Desktop}",
1025                            onclick: move |_| layout_mode.set(FrontendLayoutMode::Desktop),
1026                            "🖥️ 完整页面"
1027                        }
1028                        button {
1029                            r#type: "button",
1030                            aria_pressed: "{layout_mode() == FrontendLayoutMode::Mobile}",
1031                            onclick: move |_| layout_mode.set(FrontendLayoutMode::Mobile),
1032                            "📱 移动端布局"
1033                        }
1034                        button {
1035                            r#type: "button",
1036                            aria_pressed: "{layout_mode() == FrontendLayoutMode::Skeleton}",
1037                            onclick: move |_| layout_mode.set(FrontendLayoutMode::Skeleton),
1038                            "🦴 骨架屏加载"
1039                        }
1040                    }
1041                }
1042                if layout_mode() == FrontendLayoutMode::Desktop {
1043                    div { class: "flex items-center gap-2",
1044                        span { class: "text-xs font-mono text-paper-secondary tracking-wider", "WIDTH" }
1045                        div { class: "showcase-picker-mode-switch",
1046                            button {
1047                                r#type: "button",
1048                                aria_pressed: "{max_width_choice() == \"max-w-4xl\"}",
1049                                onclick: move |_| max_width_choice.set("max-w-4xl"),
1050                                "4xl (896px 常规)"
1051                            }
1052                            button {
1053                                r#type: "button",
1054                                aria_pressed: "{max_width_choice() == \"max-w-6xl\"}",
1055                                onclick: move |_| max_width_choice.set("max-w-6xl"),
1056                                "6xl (1152px 宽屏)"
1057                            }
1058                        }
1059                    }
1060                }
1061            }
1062
1063            div {
1064                class: if layout_mode() == FrontendLayoutMode::Mobile { "showcase-layout-phone-wrap" } else { "w-full" },
1065                div {
1066                    id: scroll_id,
1067                    class: if layout_mode() == FrontendLayoutMode::Mobile {
1068                        "showcase-mobile-phone-frame"
1069                    } else {
1070                        "showcase-layout-window showcase-detail-window-scroll"
1071                    },
1072                    "data-showcase-preview": "frontend-layout",
1073
1074                    if layout_mode() == FrontendLayoutMode::Mobile {
1075                        div { class: "showcase-phone-notch",
1076                            span { "09:41" }
1077                            div { class: "showcase-phone-notch-pill" }
1078                            span { "5G 100%" }
1079                        }
1080                    }
1081
1082                    FrontendShell {
1083                        min_height: "min-h-[480px]",
1084                        max_width: if layout_mode() == FrontendLayoutMode::Mobile { "w-full" } else { max_width_choice() },
1085                        header: rsx! {
1086                            Header {
1087                                nav_items: build_nav_items(sample_route(active())),
1088                                right_content: rsx! { {search_button} },
1089                                max_width: if layout_mode() == FrontendLayoutMode::Mobile { "w-full" } else { max_width_choice() },
1090                                menu_id,
1091                                page_shell: false,
1092                                on_demo_navigate: move |label| active.set(label),
1093                            }
1094                        },
1095                        main_content: rsx! {
1096                            if layout_mode() == FrontendLayoutMode::Skeleton {
1097                                div { class: "space-y-4 py-4",
1098                                    div { class: "p-3 rounded-xl bg-paper-accent-soft text-paper-accent text-xs font-mono flex items-center justify-between",
1099                                        span { "SUSPENSE BOUNDARY SKELETON PLACEHOLDER" }
1100                                        span { "路由加载中" }
1101                                    }
1102                                    HomeSkeleton { current_page: 1 }
1103                                }
1104                            } else {
1105                                article { class: "space-y-6",
1106                                    div { class: "border-b border-[var(--sc-line)] pb-5",
1107                                        span { class: "text-xs font-mono text-paper-secondary", "FRONTEND LAYOUT SPECIMEN" }
1108                                        h2 { class: "text-2xl font-bold tracking-tight text-paper-primary mt-1",
1109                                            match active() {
1110                                                "首页" => "一页内容,安心阅读",
1111                                                "笔记" => "公开笔记 · 随笔与灵感",
1112                                                "归档" => "岁月归档 · 时间线检索",
1113                                                "友链" => "友情链接 · 志趣相投的伙伴",
1114                                                "关于" => "关于本站 · 架构设计与理念",
1115                                                "搜索" => "站内检索 · 快速定位内容",
1116                                                _ => "前台内容展示",
1117                                            }
1118                                        }
1119                                        p { class: "mt-2 text-sm text-paper-secondary leading-relaxed",
1120                                            "前台布局通过统一的 FrontendShell 组合顶部 Header、弹性内容主区 main 与底部 Footer。当前选中栏目:{active()}。"
1121                                        }
1122                                    }
1123
1124                                    div { class: "space-y-4",
1125                                        div { class: "p-5 rounded-2xl border border-[var(--sc-line)] bg-paper-entry/40 hover:bg-paper-entry/70 transition-colors",
1126                                            div { class: "flex items-center gap-2 text-xs text-paper-secondary font-mono",
1127                                                span { class: "px-2 py-0.5 rounded-full bg-paper-accent-soft text-paper-accent font-medium", "精选文章" }
1128                                                span { "•" }
1129                                                span { "2026-09-28" }
1130                                                span { "•" }
1131                                                span { "5 分钟阅读" }
1132                                            }
1133                                            h3 { class: "text-lg font-semibold text-paper-primary mt-2", "Dioxus 0.7 与全栈 Rust 博客布局演进" }
1134                                            p { class: "text-xs text-paper-secondary mt-1.5 leading-relaxed",
1135                                                "探讨全栈 Rust 前端在 SSR、水合、骨架屏与页面过渡动画上的工程实践,兼顾极致的加载性能与阅读体验。"
1136                                            }
1137                                        }
1138
1139                                        div { class: "p-5 rounded-2xl border border-[var(--sc-line)] bg-paper-entry/40 hover:bg-paper-entry/70 transition-colors",
1140                                            div { class: "flex items-center gap-2 text-xs text-paper-secondary font-mono",
1141                                                span { class: "px-2 py-0.5 rounded-full bg-paper-entry text-paper-secondary border border-[var(--sc-line)]", "设计系统" }
1142                                                span { "•" }
1143                                                span { "2026-09-15" }
1144                                            }
1145                                            h3 { class: "text-lg font-semibold text-paper-primary mt-2", "统一宽度与响应式对齐的排版艺术" }
1146                                            p { class: "text-xs text-paper-secondary mt-1.5 leading-relaxed",
1147                                                "导航栏 max-w 与正文容器严格对齐,保证视口放大缩小时视觉焦点稳定不飘移。"
1148                                            }
1149                                        }
1150                                    }
1151
1152                                    div { class: "pt-8 pb-4 text-center text-xs font-mono text-paper-tertiary",
1153                                        "— 滚动至底部可点击页脚回到顶部 —"
1154                                    }
1155                                }
1156                            }
1157                        },
1158                        footer: rsx! {
1159                            FooterView {
1160                                github_url: Some("https://github.com".to_string()),
1161                                top_visible: true,
1162                                page_shell: false,
1163                                inline_top: true,
1164                                on_top: move |_| scroll_demo_to_top(scroll_id),
1165                            }
1166                        },
1167                    }
1168                }
1169            }
1170        }
1171    }
1172}
1173
1174fn scroll_demo_to_top(id: &str) {
1175    #[cfg(target_arch = "wasm32")]
1176    if let Some(element) = web_sys::window()
1177        .and_then(|window| window.document())
1178        .and_then(|document| document.get_element_by_id(id))
1179    {
1180        element.set_scroll_top(0);
1181    }
1182    #[cfg(not(target_arch = "wasm32"))]
1183    let _ = id;
1184}
1185
1186fn article_html(prefix: &str, alternate: bool) -> String {
1187    let opening = if alternate {
1188        "雨停之后,树叶留下细小的光。"
1189    } else {
1190        "一片叶子,也记得来时的风。"
1191    };
1192    format!(
1193        r#"<h2 id="{prefix}-opening">从一片叶子开始</h2>
1194<p>{opening} 这段固定文章展示正文排版和图片,不连接任何服务。</p>
1195<h3 id="{prefix}-details">沿途所见</h3>
1196<ul><li>清晨的光</li><li>缓慢生长的枝条</li></ul>
1197<blockquote><p>让想法,像树一样生长。</p></blockquote>
1198<pre><code class="language-rust">let leaf = "Yggdrasil";
1199println!("{{leaf}}");</code></pre>
1200<h2 id="{prefix}-image">留下一张照片</h2>
1201<p><img src="/images/empty-state/dog-camera.webp" alt="相机里的两只小狗" loading="lazy"></p>"#
1202    )
1203}
1204
1205fn toc_html(prefix: &str) -> String {
1206    format!(
1207        r##"<ul><li><a href="#{prefix}-opening">从一片叶子开始</a><ul><li><a href="#{prefix}-details">沿途所见</a></li></ul></li><li><a href="#{prefix}-image">留下一张照片</a></li></ul>"##
1208    )
1209}
1210
1211#[component]
1212fn PostContentPreview(detail: bool) -> Element {
1213    let mut alternate = use_signal(|| false);
1214    let (scope_id, heading_prefix) = if detail {
1215        (
1216            "showcase-post-content-detail",
1217            "showcase-post-content-detail",
1218        )
1219    } else {
1220        ("showcase-post-content-card", "showcase-post-content-card")
1221    };
1222    rsx! {
1223        div { class: "showcase-article-window", "data-showcase-preview": "post-content",
1224            if detail {
1225                button {
1226                    class: "mb-4 text-sm text-paper-accent",
1227                    r#type: "button",
1228                    onclick: move |_| alternate.toggle(),
1229                    "切换文章样例"
1230                }
1231            }
1232            for variant in std::iter::once(alternate()) {
1233                PostContent {
1234                    key: "sample-{variant}",
1235                    content_html: article_html(heading_prefix, variant),
1236                    scope_id: Some(scope_id),
1237                }
1238            }
1239        }
1240    }
1241}
1242
1243#[component]
1244fn PostFooterPreview(detail: bool) -> Element {
1245    let mut post = super::sample_post();
1246    post.tags = vec!["设计".to_string(), "阅读".to_string(), "Rust".to_string()];
1247    post.prev_post = Some(PostNav {
1248        title: "沿着风的方向".to_string(),
1249        slug: "sample-previous".to_string(),
1250    });
1251    post.next_post = Some(PostNav {
1252        title: "下一圈年轮".to_string(),
1253        slug: "sample-next".to_string(),
1254    });
1255    rsx! {
1256        div { class: "showcase-article-window", "data-showcase-preview": "post-footer",
1257            PostFooter { post, sample: true }
1258            if detail {
1259                p { class: "showcase-post-footer-note", "样例标签和相邻文章不跳转。" }
1260            }
1261        }
1262    }
1263}
1264
1265#[component]
1266fn PostNavLinksPreview(detail: bool) -> Element {
1267    let prev = PostNav {
1268        title: "一片叶子,落在代码之间".to_string(),
1269        slug: "sample-previous".to_string(),
1270    };
1271    let next = PostNav {
1272        title: "下一圈年轮:让想法慢慢长成一座数字花园".to_string(),
1273        slug: "sample-next".to_string(),
1274    };
1275
1276    rsx! {
1277        div { class: "showcase-post-nav-demo", "data-showcase-preview": "post-nav-links",
1278            section { class: "showcase-post-nav-sheet", "data-showcase-sample": "both",
1279                h3 { class: "showcase-post-nav-heading", "继续阅读" span { "沿着文字,走向下一页" } }
1280                PostNavLinks { prev: Some(prev.clone()), next: Some(next.clone()), sample: true }
1281            }
1282            if detail {
1283                div { class: "showcase-post-nav-examples",
1284                    section { class: "showcase-post-nav-example", "data-showcase-sample": "next-only",
1285                        h3 { "第一篇文章" span { "只有下一篇" } }
1286                        PostNavLinks { prev: None, next: Some(next), sample: true }
1287                    }
1288                    section { class: "showcase-post-nav-example", "data-showcase-sample": "prev-only",
1289                        h3 { "最后一篇文章" span { "只有上一篇" } }
1290                        PostNavLinks { prev: Some(prev), next: None, sample: true }
1291                    }
1292                    section { class: "showcase-post-nav-empty", "data-showcase-sample": "empty",
1293                        PostNavLinks { prev: None, next: None, sample: true }
1294                        p { "没有相邻文章时,不展示导航入口。" }
1295                    }
1296                }
1297                p { class: "showcase-post-nav-note", "试试悬停或用 Tab 聚焦卡片。样例链接停留在本页。" }
1298            }
1299        }
1300    }
1301}
1302
1303#[component]
1304fn PostTocPreview(detail: bool) -> Element {
1305    let (scroll_id, nav_id, content_id) = if detail {
1306        (
1307            "showcase-post-toc-scroll-detail",
1308            "showcase-post-toc-nav-detail",
1309            "showcase-post-toc-content-detail",
1310        )
1311    } else {
1312        (
1313            "showcase-post-toc-scroll-card",
1314            "showcase-post-toc-nav-card",
1315            "showcase-post-toc-content-card",
1316        )
1317    };
1318    rsx! {
1319        div {
1320            id: scroll_id,
1321            class: "showcase-toc-window showcase-article-window",
1322            "data-showcase-preview": "post-toc",
1323            "data-local-anchor-scroll": "true",
1324            PostToc {
1325                toc_html: toc_html(content_id),
1326                title: "文章目录",
1327                nav_id: Some(nav_id),
1328                content_id: Some(content_id),
1329                scroll_id: Some(scroll_id),
1330            }
1331            PostContent { content_html: article_html(content_id, false), scope_id: Some(content_id) }
1332        }
1333    }
1334}
1335
1336#[cfg(test)]
1337mod tests {
1338    use super::*;
1339
1340    #[test]
1341    fn article_and_toc_share_scoped_headings() {
1342        let html = article_html("sample", false);
1343        let toc = toc_html("sample");
1344        for id in ["sample-opening", "sample-details", "sample-image"] {
1345            assert!(html.contains(&format!("id=\"{id}\"")));
1346            assert!(toc.contains(&format!("href=\"#{id}\"")));
1347        }
1348        assert!(!html.contains("data-runnable"));
1349    }
1350
1351    #[test]
1352    fn alternate_article_changes_real_content() {
1353        assert_ne!(article_html("sample", false), article_html("sample", true));
1354    }
1355}