Skip to main content

yggdrasil/pages/
about.rs

1//! 关于页面:以世界树、引文与年轮呈现「书写抵抗遗忘」的主题。
2//! 内容直接 SSR;树冠只有本地开合状态,不请求或存储访客数据。
3
4use dioxus::prelude::*;
5
6use crate::router::Route;
7
8/// 静态文档不属于 SPA 路由,保留普通外链入口。
9const LINKS: &[(&str, &str, &str)] = &[(
10    "/doc/yggdrasil/index.html",
11    "站点文档",
12    "这棵树是如何长成的",
13)];
14
15#[component]
16pub fn About() -> Element {
17    rsx! {
18        article { class: "about-page",
19            header { class: "about-hero",
20                div { class: "about-hero-copy",
21                    p { class: "about-eyebrow", span {} "ABOUT / 关于这里" }
22                    h1 {
23                        "世界遗忘的,"
24                        span { "树记得。" }
25                    }
26                    p { class: "about-hero-description",
27                        "在时间的缝隙里,种下一些文字。"
28                        br {}
29                        "让想法生根,让记忆有迹可循。"
30                    }
31                    Link { class: "about-reading-link", to: Route::Archives {},
32                        "翻阅这里的故事"
33                        span { class: "about-arrow", aria_hidden: "true", "↗" }
34                    }
35                }
36                WorldTree {}
37            }
38
39            section { class: "about-story about-enter", style: "--about-delay: 120ms",
40                aria_labelledby: "about-name-title",
41                div { class: "about-section-label",
42                    span { class: "about-index", aria_hidden: "true", "01 / ROOTS" }
43                    h2 { id: "about-name-title", "名字的根须" }
44                }
45                div { class: "about-story-copy",
46                    p { class: "about-name", "Yggdrasil" span { "世界树" } }
47                    p { "北欧神话中的世界树,根须贯穿九界,枝叶承载记忆。" }
48                    p { "这里是它的一小片枝叶。写下所见、所想,也收留那些不想忘记的瞬间。" }
49                }
50            }
51
52            section { class: "about-memory about-enter", style: "--about-delay: 200ms",
53                aria_label: "关于记忆与书写",
54                span { class: "about-quote-mark", aria_hidden: "true", "“" }
55                blockquote {
56                    p { "世界……遗忘我……" }
57                    footer { "—— 大慈树王" }
58                }
59                div { class: "about-memory-response",
60                    span { class: "about-small-rule", aria_hidden: "true" }
61                    p { "人会遗忘,也终将被遗忘。" }
62                    p { "而写下的,会替我们记得。" }
63                }
64                details { class: "about-footnote",
65                    summary {
66                        span { "这句引文,与这棵树" }
67                        span { class: "about-plus", aria_hidden: "true" }
68                    }
69                    div { class: "about-footnote-copy",
70                        p { "这句话来自大慈树王将自己从世界树(Irminsul)中抹除时的请求。" }
71                        p { "一个故事里的世界树选择遗忘,另一个名字里的世界树收留记忆。于是,这里有了一个小小的回应:世界遗忘的,树记得。" }
72                    }
73                }
74            }
75
76            section { class: "about-rings about-enter", style: "--about-delay: 280ms",
77                aria_labelledby: "about-rings-title",
78                div { class: "about-rings-heading",
79                    div { class: "about-section-label",
80                        span { class: "about-index", aria_hidden: "true", "02 / GROWTH" }
81                        h2 { id: "about-rings-title", "生长的年轮" }
82                    }
83                    p { "每一次记录,都留下一圈纹路。" }
84                }
85                div { class: "about-link-grid",
86                    Link { class: "about-ring-card", to: Route::Changelog {},
87                        span { class: "about-card-top", aria_hidden: "true",
88                            span { class: "about-ring-symbol", span {} span {} span {} }
89                            span { class: "about-card-kicker", "THE CHANGELOG" }
90                        }
91                        span { class: "about-card-title", "更新日志" }
92                        span { class: "about-card-description", "每一圈年轮,都是一次生长" }
93                        span { class: "about-card-arrow about-arrow", aria_hidden: "true", "→" }
94                    }
95                    for (href, name, description) in LINKS.iter().copied() {
96                        a {
97                            key: "{href}",
98                            class: "about-ring-card about-doc-card",
99                            href,
100                            target: "_blank",
101                            rel: "noopener noreferrer",
102                            aria_label: "{name}:{description}(在新标签页打开)",
103                            span { class: "about-card-top", aria_hidden: "true",
104                                svg { class: "about-book-symbol", view_box: "0 0 40 40", fill: "none",
105                                    path { d: "M20 10C15 6 9 6 4 8V31C9 29 15 29 20 33C25 29 31 29 36 31V8C31 6 25 6 20 10ZM20 10V33M9 13C12 12 15 13 16 14M9 19C12 18 15 19 16 20M25 14C28 12 30 12 32 13", stroke: "currentColor", stroke_width: "1.3", stroke_linecap: "round", stroke_linejoin: "round" }
106                                }
107                                span { class: "about-card-kicker", "FIELD NOTES" }
108                            }
109                            span { class: "about-card-title", "{name}" }
110                            span { class: "about-card-description", "{description}" }
111                            span { class: "about-card-arrow about-arrow", aria_hidden: "true", "↗" }
112                        }
113                    }
114                }
115            }
116
117            footer { class: "about-colophon about-enter", style: "--about-delay: 340ms",
118                span { class: "about-colophon-star", aria_hidden: "true", "✳" }
119                p { "生长未完,故事继续。" }
120                span { class: "about-colophon-name", "YGGDRASIL · A PLACE TO REMEMBER" }
121            }
122        }
123    }
124}
125
126/// 原生按钮支持触屏、Enter 与空格;点击展开新叶,再次点击回到初态。
127#[component]
128fn WorldTree() -> Element {
129    let mut grown = use_signal(|| false);
130    let leaves = [
131        (200, 80, -40),
132        (184, 99, -110),
133        (218, 106, -10),
134        (168, 120, -135),
135        (235, 128, -5),
136        (143, 139, -135),
137        (256, 146, 5),
138        (114, 159, -145),
139        (281, 169, 15),
140        (154, 171, -100),
141        (244, 181, -5),
142        (177, 151, -120),
143        (222, 159, -25),
144        (132, 190, -150),
145        (263, 204, 20),
146        (100, 206, -150),
147        (298, 218, 25),
148        (168, 210, -120),
149        (229, 219, -15),
150        (144, 226, -160),
151        (250, 239, 25),
152        (186, 188, -110),
153        (212, 198, -30),
154        (199, 126, -55),
155    ];
156
157    rsx! {
158        figure { class: "about-tree-figure about-enter", style: "--about-delay: 100ms",
159            button {
160                class: "about-tree",
161                r#type: "button",
162                aria_label: "让记忆生长",
163                aria_pressed: "{grown}",
164                aria_describedby: "about-tree-caption",
165                "data-grown": "{grown}",
166                onclick: move |_| grown.toggle(),
167                span { class: "about-tree-glow", aria_hidden: "true" }
168                svg {
169                    class: "about-tree-art",
170                    view_box: "0 0 400 440",
171                    fill: "none",
172                    "aria-hidden": "true",
173                    g { class: "about-tree-orbits", stroke: "currentColor", stroke_width: "0.7",
174                        circle { cx: "200", cy: "208", r: "154" }
175                        circle { cx: "200", cy: "208", r: "137", stroke_dasharray: "2 7" }
176                        ellipse { cx: "200", cy: "208", rx: "170", ry: "115", transform: "rotate(-42 200 208)" }
177                        path { d: "M200 40V51M200 365V376M32 208H43M357 208H368" }
178                    }
179                    g { class: "about-tree-stars", fill: "currentColor",
180                        path { d: "M307 81L309 88L316 90L309 92L307 99L305 92L298 90L305 88ZM79 274L81 279L86 281L81 283L79 288L77 283L72 281L77 279Z" }
181                        circle { cx: "94", cy: "111", r: "2.5" }
182                        circle { cx: "330", cy: "247", r: "2" }
183                        circle { cx: "263", cy: "62", r: "1.5" }
184                    }
185                    g { class: "about-tree-roots", stroke: "currentColor", stroke_width: "1.2", stroke_linecap: "round",
186                        path { d: "M198 284C193 308 177 310 156 327L133 341M202 284C207 307 227 315 251 332L267 341M199 292L191 324L177 348M203 297L216 329L221 353M200 314V359M184 306L160 311L143 320M221 310L245 314L265 326M163 322L156 342M240 326L247 348M191 324L181 330M216 329L233 339" }
187                        path { class: "about-tree-ground", d: "M102 286C155 282 243 282 298 286" }
188                    }
189                    g { class: "about-tree-branches", stroke: "currentColor", stroke_linecap: "round", stroke_linejoin: "round",
190                        path { stroke_width: "3", d: "M198 287C202 263 194 244 201 221C207 198 193 182 200 159C205 136 195 111 200 84" }
191                        path { stroke_width: "1.7", d: "M201 263C180 240 150 245 128 224L104 209M199 246C220 232 261 241 281 226L296 219M200 224C180 207 157 207 145 188L117 162M200 206C222 195 258 198 278 174M198 183C172 171 163 148 146 142M202 170C217 159 239 154 253 149M200 149C179 140 178 128 171 122M201 137L231 131M198 113L187 102M199 123L215 110" }
192                        path { stroke_width: "1.1", d: "M158 240L146 229M165 241L170 214M256 239L252 241M233 235L230 223M147 189L134 192M159 202L155 174M247 197L246 184M259 195L263 204M179 167L179 154M218 161L221 161M192 201L187 191M203 219L211 202" }
193                    }
194                    g { class: "about-tree-leaves",
195                        for (index, (x, y, angle)) in leaves.iter().enumerate() {
196                            g { key: "{index}", transform: "translate({x} {y}) rotate({angle})",
197                                path {
198                                    class: if index % 3 == 0 { "about-tree-leaf about-tree-new-leaf" } else { "about-tree-leaf" },
199                                    style: "--leaf-delay: {index % 6 * 45}ms",
200                                    d: "M0 0C-6-10-4-22 0-27C8-20 10-8 0 0Z",
201                                }
202                            }
203                        }
204                    }
205                    circle { class: "about-tree-seed", cx: "200", cy: "284", r: "4", fill: "currentColor" }
206                }
207                span { class: "about-tree-touch", aria_hidden: "true", "✳" }
208            }
209            figcaption { id: "about-tree-caption", class: "about-tree-caption", aria_live: "polite",
210                if grown() {
211                    "一片新叶,一段被留下的记忆"
212                } else {
213                    "轻触这棵树,让记忆生长"
214                }
215            }
216        }
217    }
218}