1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum Status {
28 Confirmed,
30 Emerging,
32 Retired,
34}
35
36pub struct DataAttr {
38 pub name: &'static str,
40 pub values: &'static [&'static str],
42 pub default: &'static str,
44 pub description: &'static str,
46}
47
48pub struct ComponentEntry {
50 pub class: &'static str,
52 pub kind: &'static str,
54 pub parent: &'static str,
56 pub data_attrs: &'static [DataAttr],
58 pub example_html: &'static str,
60 pub example_markdown: &'static str,
62 pub status: Status,
64 pub since: &'static str,
66 pub description: &'static str,
68}
69
70pub const UNPREFIXED_LEGACY_CLASSES: &[&str] = &[
93 "callout",
95 "main-nav",
97 "nav-left",
98 "nav-right",
99 "nav-links",
100 "nav-icons",
101 "nav-search-btn",
102 "nav-theme-btn",
103 "nav-lang-toggle",
104 "nav-lang-current",
105 "nav-lang-link",
106 "search-icon",
107 "theme-toggle-icon",
108 "mobile-menu-button",
109 "site-name",
110 "site-logo",
111 "breadcrumb-segment",
112 "breadcrumb-label",
113 "breadcrumb-separator",
114 "date-line",
116 "date",
117 "footer-default",
119 "footer-link",
120];
121
122pub fn is_unprefixed_legacy(class: &str) -> bool {
127 class.starts_with("callout-") || UNPREFIXED_LEGACY_CLASSES.contains(&class)
128}
129
130pub const COMPONENTS: &[ComponentEntry] = &[
135 ComponentEntry {
136 class: "moss-cards",
137 kind: "container",
138 parent: "",
139 data_attrs: &[
140 DataAttr {
141 name: "data-layout",
142 values: &["grid", "list", "minimal"],
143 default: "grid",
144 description: "Card layout density. Grid: 2-3 cols with covers. List: single column with side covers. Minimal: text-only with year groupings.",
145 },
146 DataAttr {
147 name: "data-density",
148 values: &["default", "compact"],
149 default: "default",
150 description: "Vertical spacing density.",
151 },
152 DataAttr {
153 name: "data-list-axis",
154 values: &["date", "weight", "title"],
155 default: "title",
156 description: "Sort axis for the listing (mirrors the folder's `sort:` frontmatter). Drives `--moss-card-min` density tuning and decides whether each `.moss-card-meta` slot is filled (date axis) or omitted (weight/title axes).",
157 },
158 DataAttr {
159 name: "data-list-has-covers",
160 values: &[""],
161 default: "",
162 description: "Boolean presence flag: emitted iff any child card has a cover. Combines with `data-list-axis` to widen `--moss-card-min` for cover-led layouts. Use `[data-list-has-covers]` in CSS to target it.",
163 },
164 ],
165 example_html: r#"<div class="moss-cards-container">
166 <div class="moss-cards" data-layout="grid" data-list-axis="date" data-list-has-covers>
167 <a class="moss-card" href="...">...</a>
168 <a class="moss-card" href="...">...</a>
169 </div>
170</div>"#,
171 example_markdown: "",
172 status: Status::Confirmed,
173 since: "1",
174 description: "Auto-generated listing of child pages. The single canonical container; layout density on `data-layout` (`grid` for cover-led tiles, `list` for cover+excerpt rows, `minimal` for text-only year-grouped indexes). Wrapped in `.moss-cards-container` to scope CSS container queries.",
175 },
176 ComponentEntry {
177 class: "moss-cards-container",
178 kind: "container",
179 parent: "",
180 data_attrs: &[],
181 example_html: r#"<div class="moss-cards-container">
182 <div class="moss-cards" data-layout="grid">...</div>
183</div>"#,
184 example_markdown: "",
185 status: Status::Confirmed,
186 since: "1",
187 description: "Outer wrapper around `.moss-cards` that carries `container-type: inline-size` so the grid can use `@container` queries instead of viewport `@media` queries. Layout-agnostic — wraps any `data-layout` variant.",
188 },
189 ComponentEntry {
190 class: "moss-summary-layout",
191 kind: "container",
192 parent: "moss-cards",
193 data_attrs: &[],
194 example_html: r#"<div class="moss-cards" data-layout="list">...</div>"#,
195 example_markdown: "",
196 status: Status::Retired,
197 since: "1",
198 description: "Retired: the additional co-class on `.moss-cards[data-layout=\"list\"]` had no matching rules in the default CSS once `children_style: summary` collapsed into the list-layout block, and its lingering emission broke themes that hid the class (e.g. SoCiviC's `.moss/theme/style.css` keyed `display: none` on it, erasing folder-embed listings). Theme authors targeting summary listings should use `.moss-cards[data-layout=\"list\"]` directly.",
199 },
200 ComponentEntry {
206 class: "moss-cards-grid",
207 kind: "container",
208 parent: "",
209 data_attrs: &[],
210 example_html: r#"<div class="moss-cards-grid">
211 <a class="moss-card-grid" href="...">...</a>
212</div>"#,
213 example_markdown: "",
214 status: Status::Retired,
215 since: "0",
216 description: "Retired in Phase 1c — collapsed into `.moss-cards[data-layout=grid]`.",
217 },
218 ComponentEntry {
219 class: "moss-cards-list",
220 kind: "container",
221 parent: "",
222 data_attrs: &[],
223 example_html: r#"<div class="moss-cards-list">
224 <a class="moss-card-list" href="...">...</a>
225</div>"#,
226 example_markdown: "",
227 status: Status::Retired,
228 since: "0",
229 description: "Retired in Phase 1c — collapsed into `.moss-cards[data-layout=list]`.",
230 },
231 ComponentEntry {
232 class: "moss-cards-minimal-year-group",
233 kind: "container",
234 parent: "",
235 data_attrs: &[],
236 example_html: r#"<section class="moss-cards-minimal-year-group">
237 <h3>2024</h3>
238 <div class="moss-card-minimal">...</div>
239</section>"#,
240 example_markdown: "",
241 status: Status::Confirmed,
242 since: "0",
243 description: "Year-grouped section in minimal card layout (e.g. blog index). Modifier `--summary` collapses past years.",
244 },
245 ComponentEntry {
246 class: "moss-cards-minimal-year-group--summary",
247 kind: "container",
248 parent: "moss-cards-minimal-year-group",
249 data_attrs: &[],
250 example_html: r#"<section class="moss-cards-minimal-year-group moss-cards-minimal-year-group--summary">...</section>"#,
251 example_markdown: "",
252 status: Status::Confirmed,
253 since: "0",
254 description: "BEM modifier on `.moss-cards-minimal-year-group`. Applied to year groups that should render in collapsed summary form (e.g. past years on a blog index).",
255 },
256 ComponentEntry {
257 class: "moss-card",
258 kind: "instance",
259 parent: "moss-cards",
260 data_attrs: &[
261 DataAttr {
262 name: "data-linkblog",
263 values: &[],
264 default: "",
265 description: "Presence flag: emitted IFF the card's source page has an `external_url:` frontmatter (linkblog pattern). When set, the element is a `<div>` rather than `<a>` so the kicker can host a nested `<a>★</a>` archive link; title, cover, and description carry their own inner anchors to the canonical URL. Absent on ordinary cards (single whole-card `<a>`).",
266 },
267 ],
268 example_html: r#"<a class="moss-card" href="...">...</a>"#,
269 example_markdown: "",
270 status: Status::Confirmed,
271 since: "1",
272 description: "v1 collapsed shape — single canonical instance class inside `.moss-cards`. Layout-specific styling targets `.moss-cards[data-layout=X] .moss-card`. Tag is `<a>` for ordinary cards and `<div>` for linkblog cards (`[data-linkblog]`).",
273 },
274 ComponentEntry {
275 class: "moss-card-cover",
276 kind: "instance",
277 parent: "moss-card",
278 data_attrs: &[],
279 example_html: r#"<div class="moss-card-cover"><img src="..." /></div>"#,
280 example_markdown: "",
281 status: Status::Confirmed,
282 since: "1",
283 description: "Cover media slot inside `.moss-card`. Gets `.moss-card-no-cover` modifier when no image is present.",
284 },
285 ComponentEntry {
286 class: "moss-card-no-cover",
287 kind: "instance",
288 parent: "moss-card",
289 data_attrs: &[],
290 example_html: r#"<div class="moss-card-cover moss-card-no-cover"></div>"#,
291 example_markdown: "",
292 status: Status::Confirmed,
293 since: "1",
294 description: "Modifier applied to `.moss-card-cover` when no cover media is available.",
295 },
296 ComponentEntry {
297 class: "moss-card-content",
298 kind: "instance",
299 parent: "moss-card",
300 data_attrs: &[],
301 example_html: r#"<div class="moss-card-content">...</div>"#,
302 example_markdown: "",
303 status: Status::Confirmed,
304 since: "1",
305 description: "Text content slot inside a grid-layout `.moss-card` (kicker + title + meta).",
306 },
307 ComponentEntry {
308 class: "moss-card-row",
309 kind: "instance",
310 parent: "moss-card",
311 data_attrs: &[],
312 example_html: r#"<div class="moss-card-row">...</div>"#,
313 example_markdown: "",
314 status: Status::Confirmed,
315 since: "1",
316 description: "Row wrapper inside a list-layout `.moss-card` holding body + cover side-by-side.",
317 },
318 ComponentEntry {
319 class: "moss-card-body",
320 kind: "instance",
321 parent: "moss-card",
322 data_attrs: &[],
323 example_html: r#"<div class="moss-card-body">...</div>"#,
324 example_markdown: "",
325 status: Status::Confirmed,
326 since: "1",
327 description: "Text body slot of a list-layout `.moss-card`.",
328 },
329 ComponentEntry {
330 class: "moss-card-head",
331 kind: "instance",
332 parent: "moss-card",
333 data_attrs: &[],
334 example_html: r#"<div class="moss-card-head">...</div>"#,
335 example_markdown: "",
336 status: Status::Confirmed,
337 since: "1",
338 description: "Header row of a `.moss-card-body` (title + kicker + meta).",
339 },
340 ComponentEntry {
341 class: "moss-card-title",
342 kind: "instance",
343 parent: "moss-card",
344 data_attrs: &[],
345 example_html: r#"<h3 class="moss-card-title">Page title</h3>"#,
346 example_markdown: "",
347 status: Status::Confirmed,
348 since: "1",
349 description: "Title inside `.moss-card`.",
350 },
351 ComponentEntry {
352 class: "moss-card-meta",
353 kind: "instance",
354 parent: "moss-card",
355 data_attrs: &[],
356 example_html: r#"<div class="moss-card-meta">2024-01-15</div>"#,
357 example_markdown: "",
358 status: Status::Confirmed,
359 since: "1",
360 description: "Type-aware metadata slot (date for articles, count for folders, domain for links). Renders ABOVE the title in horizontal mode — filling the kicker position when the explicit `kicker` slot is unset, per `docs/reference/design/preview-cards.md:22-30`. To the right of the title in vertical CJK mode (the horizontal kicker position transposed). Meta IS the visual kicker, with the same uppercase overline treatment.",
361 },
362 ComponentEntry {
363 class: "moss-card-kicker",
364 kind: "instance",
365 parent: "moss-card",
366 data_attrs: &[],
367 example_html: r#"<span class="moss-card-kicker">Category</span>"#,
368 example_markdown: "",
369 status: Status::Confirmed,
370 since: "1",
371 description: "Eyebrow / overline above the title inside `.moss-card`.",
372 },
373 ComponentEntry {
374 class: "moss-card-permalink",
375 kind: "instance",
376 parent: "moss-card-kicker",
377 data_attrs: &[],
378 example_html: r#"<a class="moss-card-permalink" href="/posts/foo/" title="Permalink to 'Title'">★</a>"#,
379 example_markdown: "",
380 status: Status::Emerging,
381 since: "1",
382 description: "Author's-archive link mark (★, U+2605) emitted INSIDE `.moss-card-kicker` for linkblog cards (those whose child page has `external_url:`). The card title links to the external canonical (publisher); the `★` links to the local archival copy at the page's slug. Reads as part of the kicker line — \"Publisher · Year ★\". Semantically distinct from Daring-Fireball's linkblog ★ (which marks discussion permalink alongside commentary) — here the local copy is the same content preserved for resilience and stable bylines, not added commentary. Putting `<a>★</a>` inside the kicker is valid because linkblog cards emit `<div class=\"moss-card\" data-linkblog>` (not `<a>`) as the outer element — see the `data-linkblog` attribute described on `.moss-card`.",
383 },
384 ComponentEntry {
385 class: "moss-card-title-link",
386 kind: "instance",
387 parent: "moss-card-head",
388 data_attrs: &[],
389 example_html: r#"<a class="moss-card-title-link" href="https://outlet.example/article"><h3 class="moss-card-title">Article Title</h3></a>"#,
390 example_markdown: "",
391 status: Status::Emerging,
392 since: "1",
393 description: "Anchor wrapping the `.moss-card-title` `<h3>` on linkblog cards. Ordinary cards have the whole-card `<a class=\"moss-card\">` as the link target — but linkblog cards switch the outer to `<div>` so the kicker can host a nested `★` anchor, which means the title needs its own anchor to stay clickable. Same canonical-URL target as the other inner anchors (`moss-card-cover-link`, `moss-card-description-link`).",
394 },
395 ComponentEntry {
396 class: "moss-card-cover-link",
397 kind: "instance",
398 parent: "moss-card-row",
399 data_attrs: &[],
400 example_html: r#"<a class="moss-card-cover-link" href="https://outlet.example/article"><div class="moss-card-cover">...</div></a>"#,
401 example_markdown: "",
402 status: Status::Emerging,
403 since: "1",
404 description: "Anchor wrapping the `.moss-card-cover` on linkblog cards — same role as `.moss-card-title-link` but for the cover image / media. Targets the canonical (external) URL.",
405 },
406 ComponentEntry {
407 class: "moss-card-description-link",
408 kind: "instance",
409 parent: "moss-card-body",
410 data_attrs: &[],
411 example_html: r#"<a class="moss-card-description-link" href="https://outlet.example/article"><p class="moss-card-description">…</p></a>"#,
412 example_markdown: "",
413 status: Status::Emerging,
414 since: "1",
415 description: "Anchor wrapping the `.moss-card-description` on linkblog cards — same role as `.moss-card-title-link` but for the description excerpt. Targets the canonical (external) URL.",
416 },
417 ComponentEntry {
418 class: "moss-card-description",
419 kind: "instance",
420 parent: "moss-card",
421 data_attrs: &[],
422 example_html: r#"<p class="moss-card-description">Excerpt...</p>"#,
423 example_markdown: "",
424 status: Status::Confirmed,
425 since: "1",
426 description: "Excerpt / description paragraph inside a `.moss-card` — below the title in both grid- and list-layout cards.",
427 },
428 ComponentEntry {
429 class: "moss-card-count",
430 kind: "instance",
431 parent: "moss-card",
432 data_attrs: &[],
433 example_html: r#"<div class="moss-card-count">4 articles</div>"#,
434 example_markdown: "",
435 status: Status::Confirmed,
436 since: "1",
437 description: "Tertiary subtitle line showing `N articles` for a folder card. Renders only on non-date listings when the folder card has no `description` to display.",
438 },
439 ComponentEntry {
440 class: "moss-embed-more",
441 kind: "instance",
442 parent: "moss-cards-container",
443 data_attrs: &[],
444 example_html: r#"<p class="moss-embed-more"><a href="/news/">More →</a></p>"#,
445 example_markdown: "",
446 status: Status::Confirmed,
447 since: "1",
448 description: "Trailing \"More →\" link on a truncated children listing (emitted when `children_limit` caps the embed); links to the folder's full index. Rendered as a sibling immediately after `.moss-cards-container`, so it sits outside the listing's flex `gap` and binds to the list via its own `margin-top` (see docs/reference/design/spacing.md).",
449 },
450 ComponentEntry {
451 class: "moss-card-grid",
452 kind: "instance",
453 parent: "moss-cards-grid",
454 data_attrs: &[],
455 example_html: r#"<a class="moss-card-grid" href="...">...</a>"#,
456 example_markdown: "",
457 status: Status::Retired,
458 since: "0",
459 description: "Retired in Phase 1c — collapsed into `.moss-card` (with parent `.moss-cards[data-layout=grid]`).",
460 },
461 ComponentEntry {
462 class: "moss-card-grid-cover",
463 kind: "instance",
464 parent: "moss-card-grid",
465 data_attrs: &[],
466 example_html: r#"<div class="moss-card-grid-cover"><img src="..." /></div>"#,
467 example_markdown: "",
468 status: Status::Retired,
469 since: "0",
470 description: "Retired in Phase 1c — collapsed into `.moss-card-cover`.",
471 },
472 ComponentEntry {
473 class: "moss-card-grid-no-cover",
474 kind: "instance",
475 parent: "moss-card-grid",
476 data_attrs: &[],
477 example_html: r#"<div class="moss-card-grid-cover moss-card-grid-no-cover"></div>"#,
478 example_markdown: "",
479 status: Status::Retired,
480 since: "0",
481 description: "Retired in Phase 1c — collapsed into `.moss-card-no-cover`.",
482 },
483 ComponentEntry {
484 class: "moss-card-grid-content",
485 kind: "instance",
486 parent: "moss-card-grid",
487 data_attrs: &[],
488 example_html: r#"<div class="moss-card-grid-content">...</div>"#,
489 example_markdown: "",
490 status: Status::Retired,
491 since: "0",
492 description: "Retired in Phase 1c — collapsed into `.moss-card-content`.",
493 },
494 ComponentEntry {
495 class: "moss-card-grid-kicker",
496 kind: "instance",
497 parent: "moss-card-grid",
498 data_attrs: &[],
499 example_html: r#"<span class="moss-card-grid-kicker">Category</span>"#,
500 example_markdown: "",
501 status: Status::Retired,
502 since: "0",
503 description: "Retired in Phase 1c — collapsed into `.moss-card-kicker`.",
504 },
505 ComponentEntry {
506 class: "moss-card-grid-title",
507 kind: "instance",
508 parent: "moss-card-grid",
509 data_attrs: &[],
510 example_html: r#"<h3 class="moss-card-grid-title">Page title</h3>"#,
511 example_markdown: "",
512 status: Status::Retired,
513 since: "0",
514 description: "Retired in Phase 1c — collapsed into `.moss-card-title`.",
515 },
516 ComponentEntry {
517 class: "moss-card-grid-meta",
518 kind: "instance",
519 parent: "moss-card-grid",
520 data_attrs: &[],
521 example_html: r#"<div class="moss-card-grid-meta">2024-01-15</div>"#,
522 example_markdown: "",
523 status: Status::Retired,
524 since: "0",
525 description: "Retired in Phase 1c — collapsed into `.moss-card-meta`.",
526 },
527 ComponentEntry {
528 class: "moss-card-list",
529 kind: "instance",
530 parent: "moss-cards-list",
531 data_attrs: &[],
532 example_html: r#"<a class="moss-card-list" href="...">...</a>"#,
533 example_markdown: "",
534 status: Status::Retired,
535 since: "0",
536 description: "Retired in Phase 1c — collapsed into `.moss-card` (with parent `.moss-cards[data-layout=list]`).",
537 },
538 ComponentEntry {
539 class: "moss-card-list-row",
540 kind: "instance",
541 parent: "moss-card-list",
542 data_attrs: &[],
543 example_html: r#"<div class="moss-card-list-row">...</div>"#,
544 example_markdown: "",
545 status: Status::Retired,
546 since: "0",
547 description: "Retired in Phase 1c — collapsed into `.moss-card-row`.",
548 },
549 ComponentEntry {
550 class: "moss-card-list-cover",
551 kind: "instance",
552 parent: "moss-card-list",
553 data_attrs: &[],
554 example_html: r#"<div class="moss-card-list-cover"><img src="..." /></div>"#,
555 example_markdown: "",
556 status: Status::Retired,
557 since: "0",
558 description: "Retired in Phase 1c — collapsed into `.moss-card-cover`.",
559 },
560 ComponentEntry {
561 class: "moss-card-list-body",
562 kind: "instance",
563 parent: "moss-card-list",
564 data_attrs: &[],
565 example_html: r#"<div class="moss-card-list-body">...</div>"#,
566 example_markdown: "",
567 status: Status::Retired,
568 since: "0",
569 description: "Retired in Phase 1c — collapsed into `.moss-card-body`.",
570 },
571 ComponentEntry {
572 class: "moss-card-list-head",
573 kind: "instance",
574 parent: "moss-card-list",
575 data_attrs: &[],
576 example_html: r#"<div class="moss-card-list-head">...</div>"#,
577 example_markdown: "",
578 status: Status::Retired,
579 since: "0",
580 description: "Retired in Phase 1c — collapsed into `.moss-card-head`.",
581 },
582 ComponentEntry {
583 class: "moss-card-list-kicker",
584 kind: "instance",
585 parent: "moss-card-list",
586 data_attrs: &[],
587 example_html: r#"<span class="moss-card-list-kicker">Category</span>"#,
588 example_markdown: "",
589 status: Status::Retired,
590 since: "0",
591 description: "Retired in Phase 1c — collapsed into `.moss-card-kicker`.",
592 },
593 ComponentEntry {
594 class: "moss-card-list-title",
595 kind: "instance",
596 parent: "moss-card-list",
597 data_attrs: &[],
598 example_html: r#"<h3 class="moss-card-list-title">Page title</h3>"#,
599 example_markdown: "",
600 status: Status::Retired,
601 since: "0",
602 description: "Retired in Phase 1c — collapsed into `.moss-card-title`.",
603 },
604 ComponentEntry {
605 class: "moss-card-list-meta",
606 kind: "instance",
607 parent: "moss-card-list",
608 data_attrs: &[],
609 example_html: r#"<div class="moss-card-list-meta">2024-01-15</div>"#,
610 example_markdown: "",
611 status: Status::Retired,
612 since: "0",
613 description: "Retired in Phase 1c — collapsed into `.moss-card-meta`.",
614 },
615 ComponentEntry {
616 class: "moss-card-list-description",
617 kind: "instance",
618 parent: "moss-card-list",
619 data_attrs: &[],
620 example_html: r#"<p class="moss-card-list-description">Excerpt...</p>"#,
621 example_markdown: "",
622 status: Status::Retired,
623 since: "0",
624 description: "Retired in Phase 1c — collapsed into `.moss-card-description`.",
625 },
626 ComponentEntry {
627 class: "moss-card-minimal",
628 kind: "instance",
629 parent: "moss-cards-minimal-year-group",
630 data_attrs: &[],
631 example_html: r#"<div class="moss-card-minimal">
632 <a class="moss-prefix-link" href="...">...</a>
633</div>"#,
634 example_markdown: "",
635 status: Status::Retired,
636 since: "0",
637 description: "Retired in Phase 1c — collapsed into `.moss-card` (with parent `.moss-cards[data-layout=minimal]`).",
638 },
639 ComponentEntry {
640 class: "moss-folder-item",
641 kind: "instance",
642 parent: "moss-cards-minimal-year-group",
643 data_attrs: &[],
644 example_html: r#"<div class="moss-card-minimal moss-folder-item">
645 <a class="moss-prefix-link moss-folder-link" href="...">...</a>
646 <p class="moss-folder-description">...</p>
647</div>"#,
648 example_markdown: "",
649 status: Status::Confirmed,
650 since: "0",
651 description: "Modifier on `.moss-card-minimal` for folder-type entries in minimal listings.",
652 },
653 ComponentEntry {
654 class: "moss-folder-title",
655 kind: "instance",
656 parent: "moss-folder-item",
657 data_attrs: &[],
658 example_html: r#"<span class="moss-folder-title">Folder name</span>"#,
659 example_markdown: "",
660 status: Status::Confirmed,
661 since: "0",
662 description: "Title text of a folder entry in minimal listings.",
663 },
664 ComponentEntry {
665 class: "moss-folder-description",
666 kind: "instance",
667 parent: "moss-folder-item",
668 data_attrs: &[],
669 example_html: r#"<p class="moss-folder-description">Description...</p>"#,
670 example_markdown: "",
671 status: Status::Confirmed,
672 since: "0",
673 description: "Description paragraph of a folder entry in minimal listings.",
674 },
675 ComponentEntry {
676 class: "moss-folder-link",
677 kind: "instance",
678 parent: "moss-folder-item",
679 data_attrs: &[],
680 example_html: r#"<a class="moss-prefix-link moss-folder-link" href="...">...</a>"#,
681 example_markdown: "",
682 status: Status::Confirmed,
683 since: "0",
684 description: "Modifier on `.moss-prefix-link` for folder-type links in minimal listings.",
685 },
686 ComponentEntry {
690 class: "moss-prefix-link",
691 kind: "instance",
692 parent: "moss-card-minimal",
693 data_attrs: &[],
694 example_html: r#"<a class="moss-prefix-link" href="...">
695 <span class="moss-prefix-link-prefix">2024-01-15</span>
696 <span class="moss-prefix-link-title">Page title</span>
697</a>"#,
698 example_markdown: "",
699 status: Status::Emerging,
700 since: "0",
701 description: "Link with a prefix span (date or icon) and a title span. Used inside minimal cards.",
702 },
703 ComponentEntry {
704 class: "moss-prefix-link-prefix",
705 kind: "instance",
706 parent: "moss-prefix-link",
707 data_attrs: &[],
708 example_html: r#"<span class="moss-prefix-link-prefix">2024-01-15</span>"#,
709 example_markdown: "",
710 status: Status::Emerging,
711 since: "0",
712 description: "Prefix slot of a prefix-link (typically a date).",
713 },
714 ComponentEntry {
715 class: "moss-prefix-link-title",
716 kind: "instance",
717 parent: "moss-prefix-link",
718 data_attrs: &[],
719 example_html: r#"<span class="moss-prefix-link-title">Page title</span>"#,
720 example_markdown: "",
721 status: Status::Emerging,
722 since: "0",
723 description: "Title slot of a prefix-link.",
724 },
725 ComponentEntry {
726 class: "moss-prefix-link-suffix",
727 kind: "instance",
728 parent: "moss-prefix-link",
729 data_attrs: &[],
730 example_html: r#"<span class="moss-prefix-link-suffix">→</span>"#,
731 example_markdown: "",
732 status: Status::Emerging,
733 since: "0",
734 description: "Optional trailing slot of a prefix-link.",
735 },
736 ComponentEntry {
742 class: "moss-callout",
743 kind: "standalone",
744 parent: "",
745 data_attrs: &[],
746 example_html: r#"<div class="moss-callout callout" data-type="note">
747 <div class="callout-title">Note</div>
748 <div class="callout-content">Body...</div>
749</div>"#,
750 example_markdown: "> [!note]\n> Body...",
751 status: Status::Confirmed,
752 since: "0",
753 description: "Obsidian-style callout. The Obsidian-compat `.callout` class is co-emitted; type lives on `data-type` (v1).",
754 },
755 ComponentEntry {
756 class: "callout",
757 kind: "standalone",
758 parent: "",
759 data_attrs: &[
760 DataAttr {
761 name: "data-type",
762 values: &["note", "info", "tip", "warning", "pending"],
763 default: "note",
764 description: "v1 callout type. Theme authors target `.callout[data-type=...]` to style by variant.",
765 },
766 ],
767 example_html: r#"<div class="moss-callout callout" data-type="note">...</div>"#,
768 example_markdown: "",
769 status: Status::Confirmed,
770 since: "0",
771 description: "Obsidian-compat class co-emitted on every callout for theme parity. Type lives on `data-type` (v1).",
772 },
773 ComponentEntry {
774 class: "callout-title",
775 kind: "instance",
776 parent: "moss-callout",
777 data_attrs: &[],
778 example_html: r#"<div class="callout-title">Note</div>"#,
779 example_markdown: "",
780 status: Status::Confirmed,
781 since: "0",
782 description: "Title row of a callout.",
783 },
784 ComponentEntry {
785 class: "callout-content",
786 kind: "instance",
787 parent: "moss-callout",
788 data_attrs: &[],
789 example_html: r#"<div class="callout-content">Body...</div>"#,
790 example_markdown: "",
791 status: Status::Confirmed,
792 since: "0",
793 description: "Body container of a callout.",
794 },
795 ComponentEntry {
796 class: "callout-note",
797 kind: "instance",
798 parent: "moss-callout",
799 data_attrs: &[],
800 example_html: r#"<div class="moss-callout callout callout-note">...</div>"#,
801 example_markdown: "> [!note]\n> Body",
802 status: Status::Retired,
803 since: "0",
804 description: "Retired in Phase 1c — type lives on `.callout[data-type=note]`.",
805 },
806 ComponentEntry {
807 class: "callout-info",
808 kind: "instance",
809 parent: "moss-callout",
810 data_attrs: &[],
811 example_html: r#"<div class="moss-callout callout callout-info">...</div>"#,
812 example_markdown: "> [!info]\n> Body",
813 status: Status::Retired,
814 since: "0",
815 description: "Retired in Phase 1c — type lives on `.callout[data-type=info]`.",
816 },
817 ComponentEntry {
818 class: "callout-tip",
819 kind: "instance",
820 parent: "moss-callout",
821 data_attrs: &[],
822 example_html: r#"<div class="moss-callout callout callout-tip">...</div>"#,
823 example_markdown: "> [!tip]\n> Body",
824 status: Status::Retired,
825 since: "0",
826 description: "Retired in Phase 1c — type lives on `.callout[data-type=tip]`.",
827 },
828 ComponentEntry {
829 class: "callout-warning",
830 kind: "instance",
831 parent: "moss-callout",
832 data_attrs: &[],
833 example_html: r#"<div class="moss-callout callout callout-warning">...</div>"#,
834 example_markdown: "> [!warning]\n> Body",
835 status: Status::Retired,
836 since: "0",
837 description: "Retired in Phase 1c — type lives on `.callout[data-type=warning]`.",
838 },
839 ComponentEntry {
840 class: "callout-pending",
841 kind: "instance",
842 parent: "moss-callout",
843 data_attrs: &[],
844 example_html: r#"<div class="moss-callout callout callout-pending">...</div>"#,
845 example_markdown: "> [!pending]\n> Body",
846 status: Status::Retired,
847 since: "0",
848 description: "Retired in Phase 1c — type lives on `.callout[data-type=pending]`.",
849 },
850 ComponentEntry {
855 class: "moss-embed",
856 kind: "standalone",
857 parent: "",
858 data_attrs: &[
859 DataAttr {
860 name: "data-type",
861 values: &["audio", "video", "pdf", "notebook", "table", "iframe", "3d"],
862 default: "",
863 description: "v1 embed kind. Set on the embed element. Theme authors target `.moss-embed[data-type=...]`.",
864 },
865 DataAttr {
866 name: "data-loop",
867 values: &[],
868 default: "",
869 description: "Ambient background video: autoplay + muted + loop + playsinline, controls off. Authored as `![[clip.mp4|loop]]`. Boolean presence flag (value is empty). JS reads it to apply the reduced-motion guard and mount the pause/play toggle.",
870 },
871 DataAttr {
872 name: "data-width",
873 values: &["body", "wide", "page", "screen"],
874 default: "body",
875 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9.",
876 },
877 DataAttr {
878 name: "data-provider",
879 values: &["youtube", "vimeo", "codepen"],
880 default: "",
881 description: "Identifies the embed provider for external URL embeds. Absent for generic iframes and local HTML embeds.",
882 },
883 ],
884 example_html: r#"<video class="moss-embed moss-embed-video" data-type="video" data-loop src="clip.mp4" autoplay muted loop playsinline preload="metadata"></video>"#,
885 example_markdown: "![[clip.mp4|loop]]",
886 status: Status::Confirmed,
887 since: "0",
888 description: "Base class on every typed embed. Kind on `data-type` (v1). Ambient video: add `data-loop` via `![[clip.mp4|loop]]`. `.moss-embed-audio` / `-video` / `-pdf` / `-notebook` / `-table` / `-iframe` / `-3d` retired in Phase 1c.",
889 },
890 ComponentEntry {
891 class: "moss-embed-audio",
892 kind: "instance",
893 parent: "moss-embed",
894 data_attrs: &[],
895 example_html: r#"<div class="moss-embed moss-embed-audio"><audio controls src="..."></audio></div>"#,
896 example_markdown: "![[track.mp3]]",
897 status: Status::Retired,
898 since: "0",
899 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=audio]`.",
900 },
901 ComponentEntry {
902 class: "moss-embed-video",
903 kind: "instance",
904 parent: "moss-embed",
905 data_attrs: &[],
906 example_html: r#"<div class="moss-embed moss-embed-video"><video controls src="..."></video></div>"#,
907 example_markdown: "![[clip.mp4]]",
908 status: Status::Retired,
909 since: "0",
910 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=video]`.",
911 },
912 ComponentEntry {
913 class: "moss-embed-pdf",
914 kind: "instance",
915 parent: "moss-embed",
916 data_attrs: &[],
917 example_html: r#"<div class="moss-embed moss-embed-pdf"><iframe src="..."></iframe></div>"#,
918 example_markdown: "![[paper.pdf]]",
919 status: Status::Retired,
920 since: "0",
921 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=pdf]`.",
922 },
923 ComponentEntry {
924 class: "moss-embed-iframe",
925 kind: "instance",
926 parent: "moss-embed",
927 data_attrs: &[],
928 example_html: r#"<div class="moss-embed moss-embed-iframe"><iframe src="..."></iframe></div>"#,
929 example_markdown: "![[page.html]]",
930 status: Status::Retired,
931 since: "0",
932 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=iframe]`.",
933 },
934 ComponentEntry {
935 class: "moss-embed-notebook",
936 kind: "instance",
937 parent: "moss-embed",
938 data_attrs: &[],
939 example_html: r#"<div class="moss-embed moss-embed-notebook">...</div>"#,
940 example_markdown: "![[analysis.ipynb]]",
941 status: Status::Retired,
942 since: "0",
943 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=notebook]`.",
944 },
945 ComponentEntry {
946 class: "moss-embed-ipynb",
947 kind: "instance",
948 parent: "moss-embed",
949 data_attrs: &[],
950 example_html: r#"<div class="moss-embed moss-embed-ipynb">...</div>"#,
951 example_markdown: "",
952 status: Status::Emerging,
953 since: "0",
954 description: "Alias of `.moss-embed-notebook`; consolidation pending.",
955 },
956 ComponentEntry {
957 class: "moss-embed-table",
958 kind: "instance",
959 parent: "moss-embed",
960 data_attrs: &[],
961 example_html: r#"<div class="moss-embed moss-embed-table"><table>...</table></div>"#,
962 example_markdown: "![[data.csv]]",
963 status: Status::Retired,
964 since: "0",
965 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=table]`.",
966 },
967 ComponentEntry {
968 class: "moss-embed-3d",
969 kind: "instance",
970 parent: "moss-embed",
971 data_attrs: &[],
972 example_html: r#"<div class="moss-embed moss-embed-3d">...</div>"#,
973 example_markdown: "![[model.glb]]",
974 status: Status::Retired,
975 since: "0",
976 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=3d]`.",
977 },
978 ComponentEntry {
979 class: "moss-embed-error",
980 kind: "instance",
981 parent: "moss-embed",
982 data_attrs: &[],
983 example_html: r#"<div class="moss-embed moss-embed-error">File not found: ...</div>"#,
984 example_markdown: "",
985 status: Status::Confirmed,
986 since: "0",
987 description: "Error state for embeds whose target cannot be resolved.",
988 },
989 ComponentEntry {
990 class: "moss-embed-missing",
991 kind: "instance",
992 parent: "moss-embed",
993 data_attrs: &[],
994 example_html: r#"<div class="moss-embed-missing">Folder not found: journal</div>"#,
995 example_markdown: "",
996 status: Status::Confirmed,
997 since: "1",
998 description: "Fallback rendered when a folder-list embed (`![[journal/]]`) targets a folder that does not exist or cannot be resolved. Distinct from `.moss-embed-error` (file/wikilink resolution failure) — this one is specifically the folder-listing path.",
999 },
1000 ComponentEntry {
1004 class: "moss-hero",
1005 kind: "standalone",
1006 parent: "",
1007 data_attrs: &[
1008 DataAttr {
1009 name: "data-width",
1010 values: &["body", "wide", "page", "screen"],
1011 default: "body",
1012 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9. Emitted from the authoring shortcode (e.g. `:::hero {full}` -> `data-width=\"screen\"`); on article children, site.css sizes the band via the content-width escape (ADR-021 Corollary 2). The hero itself escapes by DOM position (outside `<main>`), not by these rules.",
1013 },
1014 DataAttr {
1015 name: "data-slides",
1016 values: &["2", "3", "4", "5", "6"],
1017 default: "",
1018 description: "Slide count of a multi-image hero (consecutive leading media lines). Present only when > 1; drives the ambient CSS crossfade — one slide visible at a time, no controls. Absent = single-image hero, today's exact markup.",
1019 },
1020 DataAttr {
1021 name: "data-hero-tone",
1022 values: &["light"],
1023 default: "",
1024 description: "Marks a hero whose image is pale enough (scan-cached dominant colour above 0.4 relative luminance) that the default legibility scrim leaves white overlay text under 4.5:1; site.css swaps in a stronger gradient. Emitted only when the hero also carries overlay text. Absent = mid-tone or dark image, no overlay, or an unparseable colour — all keep the default ramp.",
1025 },
1026 DataAttr {
1027 name: "data-mobile",
1028 values: &["overlay"],
1029 default: "",
1030 description: "Below **48rem** (moss's mobile threshold), `overlay` keeps the title on top of the image instead of stacking it underneath. Emitted **only** for `:::hero {mobile=overlay}` — an author must ask for it; a hero with overlay text does not get it by default. The selector to fight if you want the other behaviour is `.moss-hero[data-mobile=\"overlay\"]`.",
1031 },
1032 DataAttr {
1033 name: "data-captioned",
1034 values: &[""],
1035 default: "",
1036 description: "Present when the hero carries `caption=\"…\"`. Such a hero is a photograph on display rather than a backdrop for overlay text, and a caption that names a subject is a promise the subject is in frame — so site.css shows the whole image instead of the default crop-to-fill: the box takes the picture's own shape, centred, bounded by `--moss-hero-max-height` rather than filling the frame. Put the crop back on the image itself (`image=cover.jpg|cover top`), which lands as an inline style and wins.",
1037 },
1038 ],
1039 example_html: r#"<section class="moss-hero" data-width="page">
1040 <div class="moss-hero-content">...</div>
1041</section>"#,
1042 example_markdown: ":::hero {image=cover.jpg}\n:::\n\n:::hero {image=cover.jpg full mobile=overlay}\n# Title over the image\n:::\n",
1043 status: Status::Confirmed,
1044 since: "0",
1045 description: "Hero banner section at the top of a page (cover image + title). v1 adds `data-width` for author-controlled sizing.",
1046 },
1047 ComponentEntry {
1048 class: "moss-hero-content",
1049 kind: "instance",
1050 parent: "moss-hero",
1051 data_attrs: &[],
1052 example_html: r#"<div class="moss-hero-content">...</div>"#,
1053 example_markdown: "",
1054 status: Status::Confirmed,
1055 since: "0",
1056 description: "Text content slot inside `.moss-hero` — text laid ON the image. For text ABOUT the image, see `.moss-hero-caption`.",
1057 },
1058 ComponentEntry {
1059 class: "moss-hero-caption",
1060 kind: "standalone",
1061 parent: "",
1062 data_attrs: &[],
1063 example_html: r#"<p class="moss-hero-caption">封面:基輔米迦勒修道院門口的陣亡將士紀念牆(拍攝:糜緒洋)</p>"#,
1064 example_markdown: ":::hero {image=cover.jpg caption=\"Cover: the memorial wall (photo: A. Photographer)\"}\n:::\n",
1065 status: Status::Confirmed,
1066 since: "0",
1067 description: "Caption or credit for a hero image, from `:::hero {caption=\"…\"}`. A SIBLING of `.moss-hero`, immediately after it — not a child: the section is a fixed-height cropping frame, and a photographer's credit has to survive as text below the picture rather than be printed across it. Rendered as inline markdown, so a credit can be a link, exactly like a `byline:` / `colophon:` row.",
1068 },
1069 ComponentEntry {
1070 class: "moss-hero-slides",
1071 kind: "instance",
1072 parent: "moss-hero",
1073 data_attrs: &[],
1074 example_html: r#"<div class="moss-hero-slides"><div class="moss-hero-slide"><img src="portrait-1.jpg" alt="" /></div></div>"#,
1075 example_markdown: "",
1076 status: Status::Confirmed,
1077 since: "0",
1078 description: "Wrapper holding the `.moss-hero-slide` images of a multi-image hero; the CSS ambient crossfade cycles one slide visible at a time.",
1079 },
1080 ComponentEntry {
1081 class: "moss-hero-slide",
1082 kind: "instance",
1083 parent: "moss-hero",
1084 data_attrs: &[],
1085 example_html: r#"<div class="moss-hero-slide"><img src="portrait-1.jpg" alt="" /></div>"#,
1086 example_markdown: ":::hero
1087![[portrait-1.jpg]]
1088![[portrait-2.jpg]]
1089# Title
1090:::
1091",
1092 status: Status::Confirmed,
1093 since: "0",
1094 description: "One background slide of a multi-image hero. Emitted only when the hero has 2+ images; slides crossfade ambiently via site.css keyed on the section's data-slides. First slide is the reduced-motion static fallback.",
1095 },
1096 ComponentEntry {
1097 class: "moss-image",
1098 kind: "standalone",
1099 parent: "",
1100 data_attrs: &[
1101 DataAttr {
1102 name: "data-aspect",
1103 values: &["portrait", "square", "auto"],
1104 default: "auto",
1105 description: "v1 image aspect-ratio hint. Theme authors target `.moss-image[data-aspect=...]`. Emitter wiring lands in a follow-up.",
1106 },
1107 DataAttr {
1108 name: "data-width",
1109 values: &["body", "wide", "page", "screen"],
1110 default: "body",
1111 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9.",
1112 },
1113 ],
1114 example_html: r#"<figure class="moss-image" style="width:55%"><img src="..." alt="..." /></figure>"#,
1115 example_markdown: "",
1116 status: Status::Confirmed,
1117 since: "0",
1118 description: "Wrapper around an inline `<img>` for sizing and figure semantics. `data-width` carries a named width token (body|wide|page|screen); a content-relative width is instead emitted as inline `style=\"width:NN%\"` (set by the editor drag-resize), which also forces the inner image to fill that percent box. Images narrower than the content column center horizontally.",
1119 },
1120 ComponentEntry {
1121 class: "moss-align-left",
1122 kind: "standalone",
1123 parent: "",
1124 data_attrs: &[],
1125 example_html: r#"<img src="..." alt="..." class="moss-align-left" />"#,
1126 example_markdown: "![[photo.jpg|align-left]]",
1127 status: Status::Confirmed,
1128 since: "0",
1129 description: "Floats an image to the left of body text (editorial runaround). Defaults max-width to 50% on desktop, collapses to full-width below 48rem. CSS `:has()` escalates the float to a wrapping `<figure class=\"moss-image\">` or `<picture>` when present. Mirrors WordPress's `alignleft` convention.",
1130 },
1131 ComponentEntry {
1132 class: "moss-align-right",
1133 kind: "standalone",
1134 parent: "",
1135 data_attrs: &[],
1136 example_html: r#"<img src="..." alt="..." class="moss-align-right" />"#,
1137 example_markdown: "![[photo.jpg|align-right]]",
1138 status: Status::Confirmed,
1139 since: "0",
1140 description: "Floats an image to the right of body text (editorial runaround). Symmetric counterpart to `.moss-align-left`. Mirrors WordPress's `alignright` convention.",
1141 },
1142 ComponentEntry {
1143 class: "moss-article-title",
1144 kind: "instance",
1145 parent: "",
1146 data_attrs: &[],
1147 example_html: r#"<h1 class="moss-article-title">Title</h1>"#,
1148 example_markdown: "",
1149 status: Status::Emerging,
1150 since: "0",
1151 description: "Article-page H1 title emitted from frontmatter.",
1152 },
1153 ComponentEntry {
1154 class: "moss-heading-anchor",
1155 kind: "instance",
1156 parent: "",
1157 data_attrs: &[],
1158 example_html: r##"<h2 id="setup">Setup<a class="moss-heading-anchor" href="#setup" aria-label="Permalink to this section"><span aria-hidden="true">#</span></a></h2>"##,
1159 example_markdown: "## Setup",
1160 status: Status::Emerging,
1161 since: "1",
1162 description: "Clickable permalink appended inside every author-written body heading that carries a slug id; links to the heading's `#`-fragment. The auto-injected `moss-article-title` H1 is emitted separately and gets no anchor.",
1163 },
1164 ComponentEntry {
1168 class: "moss-grid",
1169 kind: "container",
1170 parent: "",
1171 data_attrs: &[
1172 DataAttr {
1173 name: "data-width",
1174 values: &["body", "wide", "page", "screen"],
1175 default: "body",
1176 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9.",
1177 },
1178 DataAttr {
1179 name: "data-columns",
1180 values: &["1", "2", "3", "4"],
1181 default: "",
1182 description: "Column count, from `:::grid N`. Note the responsive default: below 768px moss collapses `[data-columns]` to a single column, which is right for a grid of cards and wrong for a grid of short text lines. Re-assert `grid-template-columns` inside your own media query if yours is the latter. A ratio (`:::grid 2 1:2`) arrives as the custom property `--moss-grid-ratio` on the element, so it stays overridable — the collapse applies to ratio grids too.",
1183 },
1184 ],
1185 example_html: r#"<div class="moss-grid" data-width="wide">
1186 <div class="moss-grid-card">...</div>
1187</div>"#,
1188 example_markdown: ":::grid {cols=2}\nLeft cell\n+++\nRight cell\n:::\n",
1189 status: Status::Confirmed,
1190 since: "0",
1191 description: "Generic grid container (used by profiles, link previews, etc.). Modifier classes: `profiles`, `featured`, `no-cards`. v1 adds `data-width` (P9).",
1192 },
1193 ComponentEntry {
1194 class: "moss-grid-card",
1195 kind: "instance",
1196 parent: "moss-grid",
1197 data_attrs: &[
1198 DataAttr {
1199 name: "data-kind",
1200 values: &["link", "friend", "card"],
1201 default: "card",
1202 description: "v1 grid-card variant. Today expressed via co-emitted classes (`.link-card`, `.friend-card`, `.no-cards`); Phase 1c collapses to this `data-kind` attribute.",
1203 },
1204 ],
1205 example_html: r#"<a class="moss-grid-card" data-kind="link" href="...">...</a>"#,
1206 example_markdown: "",
1207 status: Status::Confirmed,
1208 since: "0",
1209 description: "Card instance inside `.moss-grid`. Today emits sibling classes `link-card` / `friend-card` / `no-cards`; v1 collapses to `data-kind`.",
1210 },
1211 ComponentEntry {
1212 class: "moss-gallery",
1213 kind: "container",
1214 parent: "",
1215 data_attrs: &[
1216 DataAttr {
1217 name: "data-width",
1218 values: &["body", "wide", "page", "screen"],
1219 default: "body",
1220 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9.",
1221 },
1222 DataAttr {
1223 name: "data-columns",
1224 values: &[],
1225 default: "",
1226 description: "Column count, from `:::gallery N` — the author names it rather than moss inferring it. The **opposite** of `.moss-grid[data-columns]` on mobile: below 48rem the grid collapses to one column, while the gallery uses `auto-fill` to keep as many tracks as clear 88px. N becomes a maximum rather than a mandate, and a gallery that already fits stays at its authored count. Collapsing a wall of thumbnails to one column is wrong; collapsing prose cells is right.",
1227 },
1228 ],
1229 example_html: r#"<div class="moss-gallery" data-width="page">
1230 <div class="moss-gallery-item">...</div>
1231</div>"#,
1232 example_markdown: ":::gallery\nphoto.jpg\n:::\n",
1233 status: Status::Confirmed,
1234 since: "0",
1235 description: "Image gallery container. v1 adds `data-width` (P9).",
1236 },
1237 ComponentEntry {
1238 class: "moss-gallery-item",
1239 kind: "instance",
1240 parent: "moss-gallery",
1241 data_attrs: &[],
1242 example_html: r#"<div class="moss-gallery-item"><img src="..." /></div>"#,
1243 example_markdown: "",
1244 status: Status::Confirmed,
1245 since: "0",
1246 description: "Single image entry inside `.moss-gallery`.",
1247 },
1248 ComponentEntry {
1249 class: "moss-buttons",
1250 kind: "container",
1251 parent: "",
1252 data_attrs: &[
1253 DataAttr {
1254 name: "data-style",
1255 values: &["default", "inverted"],
1256 default: "default",
1257 description: "v1 button-row style. Theme authors target `.moss-buttons[data-style=...]`.",
1258 },
1259 ],
1260 example_html: r#"<div class="moss-buttons" data-style="inverted">
1261 <a class="moss-btn" href="...">Click</a>
1262</div>"#,
1263 example_markdown: ":::buttons\n[Get started](https://example.com)\n:::\n",
1264 status: Status::Confirmed,
1265 since: "0",
1266 description: "Container for a row of `.moss-btn` buttons. v1: the inverted variant is on `data-style=\"inverted\"`.",
1267 },
1268 ComponentEntry {
1272 class: "moss-btn",
1273 kind: "standalone",
1274 parent: "",
1275 data_attrs: &[
1276 DataAttr {
1277 name: "data-role",
1278 values: &["default", "primary", "secondary"],
1279 default: "default",
1280 description: "v1 button role. Theme authors target `.moss-btn[data-role=...]`.",
1281 },
1282 ],
1283 example_html: r#"<button class="moss-btn" data-role="primary">
1284 <span class="moss-btn__label">Submit</span>
1285</button>"#,
1286 example_markdown: "",
1287 status: Status::Confirmed,
1288 since: "0",
1289 description: "Generic button primitive. Role on `data-role` (v1).",
1290 },
1291 ComponentEntry {
1292 class: "moss-btn__label",
1293 kind: "instance",
1294 parent: "moss-btn",
1295 data_attrs: &[],
1296 example_html: r#"<span class="moss-btn__label">Submit</span>"#,
1297 example_markdown: "",
1298 status: Status::Confirmed,
1299 since: "0",
1300 description: "Label span inside `.moss-btn`.",
1301 },
1302 ComponentEntry {
1303 class: "moss-btn__check",
1304 kind: "instance",
1305 parent: "moss-btn",
1306 data_attrs: &[],
1307 example_html: r#"<span class="moss-btn__check">✓</span>"#,
1308 example_markdown: "",
1309 status: Status::Confirmed,
1310 since: "0",
1311 description: "Success checkmark slot inside `.moss-btn`.",
1312 },
1313 ComponentEntry {
1314 class: "moss-btn__spinner",
1315 kind: "instance",
1316 parent: "moss-btn",
1317 data_attrs: &[],
1318 example_html: r#"<span class="moss-btn__spinner"></span>"#,
1319 example_markdown: "",
1320 status: Status::Confirmed,
1321 since: "0",
1322 description: "Loading spinner slot inside `.moss-btn`.",
1323 },
1324 ComponentEntry {
1328 class: "moss-subscribe",
1329 kind: "standalone",
1330 parent: "",
1331 data_attrs: &[],
1332 example_html: r#"<div class="moss-subscribe">
1333 <form class="moss-subscribe-form">...</form>
1334</div>"#,
1335 example_markdown: ":::subscribe\n:::\n",
1336 status: Status::Confirmed,
1337 since: "0",
1338 description: "Newsletter subscribe block (auto-injected into footer when email channel configured).",
1339 },
1340 ComponentEntry {
1341 class: "moss-subscribe-form",
1342 kind: "instance",
1343 parent: "moss-subscribe",
1344 data_attrs: &[
1345 DataAttr {
1346 name: "data-position",
1347 values: &["inline", "apply"],
1348 default: "inline",
1349 description: "Placement/behavior variant. All moss-hosted subscribe forms are `inline` (the auto-injected footer form and the `:::subscribe` shortcode emit identical HTML — footer vs in-page styling keys on the `footer` ancestor in CSS, not this attribute). `apply` marks the `:::apply` form (terminal success, FormData body).",
1350 },
1351 DataAttr {
1352 name: "data-button-override",
1353 values: &["true"],
1354 default: "true",
1355 description: "Emitted only when the author overrides the button label (`:::subscribe{button=\"...\"}`). Signals subscribe.ts to leave the button label AND placeholder as authored instead of overwriting them with the language-default copy.",
1356 },
1357 DataAttr {
1358 name: "data-moss-hosted",
1359 values: &["true"],
1360 default: "true",
1361 description: "Marks moss-hosted (seta) forms hydrated by subscribe.ts. Absent on 3rd-party provider forms.",
1362 },
1363 DataAttr {
1364 name: "data-state",
1365 values: &["idle", "loading", "success", "error"],
1366 default: "idle",
1367 description: "Runtime submit state machine, driven by subscribe.ts. Emitted as `idle`; theme authors target `.moss-subscribe-form[data-state=...]`.",
1368 },
1369 DataAttr {
1370 name: "data-moss-pending-site",
1371 values: &["true"],
1372 default: "true",
1373 description: "Pre-first-publish pending wiring (`action=\"#\"`, no site_id yet). Hidden on published pages (body without `data-moss-preview`) via the email.css defense rule so a pending form never faces real readers.",
1374 },
1375 ],
1376 example_html: r#"<form class="moss-subscribe-form">...</form>"#,
1377 example_markdown: "",
1378 status: Status::Emerging,
1379 since: "0",
1380 description: "Form element inside `.moss-subscribe`.",
1381 },
1382 ComponentEntry {
1383 class: "moss-btn-slot",
1384 kind: "instance",
1385 parent: "moss-subscribe",
1386 data_attrs: &[],
1387 example_html: r#"<div class="moss-btn-slot"><button class="moss-btn">...</button></div>"#,
1388 example_markdown: "",
1389 status: Status::Emerging,
1390 since: "0",
1391 description: "Fixed-width slot wrapping a form's submit button; used by both the subscribe and comment forms to prevent layout shift across idle/loading/success states.",
1392 },
1393 ComponentEntry {
1394 class: "moss-subscribe-status",
1395 kind: "instance",
1396 parent: "moss-subscribe",
1397 data_attrs: &[],
1398 example_html: r#"<div class="moss-subscribe-status">
1399 <span class="moss-subscribe-status__icon"></span>
1400 Subscribed!
1401</div>"#,
1402 example_markdown: "",
1403 status: Status::Emerging,
1404 since: "0",
1405 description: "Status message shown after submit (success/error).",
1406 },
1407 ComponentEntry {
1408 class: "moss-subscribe-status__icon",
1409 kind: "instance",
1410 parent: "moss-subscribe-status",
1411 data_attrs: &[],
1412 example_html: r#"<span class="moss-subscribe-status__icon"></span>"#,
1413 example_markdown: "",
1414 status: Status::Emerging,
1415 since: "0",
1416 description: "Icon slot inside `.moss-subscribe-status`.",
1417 },
1418 ComponentEntry {
1419 class: "moss-subscribe-landing",
1420 kind: "standalone",
1421 parent: "",
1422 data_attrs: &[],
1423 example_html: r#"<section class="moss-subscribe-landing">...</section>"#,
1424 example_markdown: "",
1425 status: Status::Emerging,
1426 since: "0",
1427 description: "Standalone subscribe landing page surface (larger variant).",
1428 },
1429 ComponentEntry {
1433 class: "moss-apply",
1434 kind: "standalone",
1435 parent: "",
1436 data_attrs: &[],
1437 example_html: r#"<div class="moss-apply" data-state="idle">
1438 <form class="moss-subscribe-form moss-apply-form">...</form>
1439</div>"#,
1440 example_markdown: ":::apply\n:::\n",
1441 status: Status::Emerging,
1442 since: "0",
1443 description: "Apply / membership-request form block (:::apply shortcode).",
1444 },
1445 ComponentEntry {
1446 class: "moss-apply-form",
1447 kind: "instance",
1448 parent: "moss-apply",
1449 data_attrs: &[
1450 DataAttr {
1451 name: "data-position",
1452 values: &["apply"],
1453 default: "apply",
1454 description: "Position variant; always `apply` for this form. Drives CSS layout in email.css.",
1455 },
1456 DataAttr {
1457 name: "data-revert",
1458 values: &["false"],
1459 default: "false",
1460 description: "When `false`, success is terminal (no auto-revert). subscribe.ts reads this.",
1461 },
1462 ],
1463 example_html: r#"<form class="moss-subscribe-form moss-apply-form" data-position="apply" data-revert="false">...</form>"#,
1464 example_markdown: "",
1465 status: Status::Emerging,
1466 since: "0",
1467 description: "Form element inside `.moss-apply`. Also carries `.moss-subscribe-form` so subscribe.ts hydrates it.",
1468 },
1469 ComponentEntry {
1470 class: "moss-apply-matters",
1471 kind: "instance",
1472 parent: "moss-apply",
1473 data_attrs: &[],
1474 example_html: r#"<input type="text" name="matters" class="moss-input moss-apply-matters">"#,
1475 example_markdown: "",
1476 status: Status::Emerging,
1477 since: "0",
1478 description: "Second apply-form input inside `.moss-apply-form` — a Matters username OR a one-line pitch (placeholder-only, no visible label).",
1479 },
1480 ComponentEntry {
1481 class: "moss-apply-hp",
1482 kind: "instance",
1483 parent: "moss-apply",
1484 data_attrs: &[],
1485 example_html: r#"<input type="text" name="website" class="moss-apply-hp" tabindex="-1" aria-hidden="true">"#,
1486 example_markdown: "",
1487 status: Status::Emerging,
1488 since: "0",
1489 description: "Honeypot field (off-screen) inside `.moss-apply-form`. Bots fill it; humans don't.",
1490 },
1491 ComponentEntry {
1492 class: "moss-apply-status",
1493 kind: "instance",
1494 parent: "moss-apply",
1495 data_attrs: &[],
1496 example_html: r#"<div class="moss-subscribe-status moss-apply-status" aria-live="polite">...</div>"#,
1497 example_markdown: "",
1498 status: Status::Emerging,
1499 since: "0",
1500 description: "Status region inside `.moss-apply-form` (also carries `.moss-subscribe-status`).",
1501 },
1502 ComponentEntry {
1503 class: "moss-apply-helper",
1504 kind: "instance",
1505 parent: "moss-apply",
1506 data_attrs: &[],
1507 example_html: r#"<p class="moss-apply-helper" id="moss-apply-email-help">用于获取邀请及免费托管服务</p>"#,
1508 example_markdown: "",
1509 status: Status::Emerging,
1510 since: "0",
1511 description: "Helper text line beneath each field in `.moss-apply-form` (referenced by the field's aria-describedby). Internal — not part of the public component contract.",
1512 },
1513 ComponentEntry {
1517 class: "moss-series-nav",
1518 kind: "standalone",
1519 parent: "",
1520 data_attrs: &[],
1521 example_html: r#"<nav class="moss-series-nav">
1522 <div class="moss-series-nav-links">...</div>
1523</nav>"#,
1524 example_markdown: "",
1525 status: Status::Confirmed,
1526 since: "0",
1527 description: "Series navigation bar (prev/next/collection) on series pages.",
1528 },
1529 ComponentEntry {
1530 class: "moss-series-nav-links",
1531 kind: "instance",
1532 parent: "moss-series-nav",
1533 data_attrs: &[],
1534 example_html: r#"<div class="moss-series-nav-links">...</div>"#,
1535 example_markdown: "",
1536 status: Status::Confirmed,
1537 since: "0",
1538 description: "Row holding prev/next links in series nav.",
1539 },
1540 ComponentEntry {
1541 class: "moss-series-nav-link",
1542 kind: "instance",
1543 parent: "moss-series-nav",
1544 data_attrs: &[],
1545 example_html: r#"<a class="moss-series-nav-link moss-series-nav-prev" href="...">...</a>"#,
1546 example_markdown: "",
1547 status: Status::Confirmed,
1548 since: "0",
1549 description: "Individual link inside series nav. Modifiers: `moss-series-nav-prev`, `moss-series-nav-next`, `empty` (placeholder).",
1550 },
1551 ComponentEntry {
1552 class: "moss-series-nav-prev",
1553 kind: "instance",
1554 parent: "moss-series-nav",
1555 data_attrs: &[],
1556 example_html: r#"<a class="moss-series-nav-link moss-series-nav-prev" href="...">...</a>"#,
1557 example_markdown: "",
1558 status: Status::Confirmed,
1559 since: "0",
1560 description: "Previous-page modifier on a series nav link.",
1561 },
1562 ComponentEntry {
1563 class: "moss-series-nav-next",
1564 kind: "instance",
1565 parent: "moss-series-nav",
1566 data_attrs: &[],
1567 example_html: r#"<a class="moss-series-nav-link moss-series-nav-next" href="...">...</a>"#,
1568 example_markdown: "",
1569 status: Status::Confirmed,
1570 since: "0",
1571 description: "Next-page modifier on a series nav link.",
1572 },
1573 ComponentEntry {
1574 class: "moss-series-nav-arrow",
1575 kind: "instance",
1576 parent: "moss-series-nav",
1577 data_attrs: &[],
1578 example_html: r#"<span class="moss-series-nav-arrow">→</span>"#,
1579 example_markdown: "",
1580 status: Status::Confirmed,
1581 since: "0",
1582 description: "Arrow glyph inside a series-nav link.",
1583 },
1584 ComponentEntry {
1585 class: "moss-series-nav-title",
1586 kind: "instance",
1587 parent: "moss-series-nav",
1588 data_attrs: &[],
1589 example_html: r#"<span class="moss-series-nav-title">Next page title</span>"#,
1590 example_markdown: "",
1591 status: Status::Confirmed,
1592 since: "0",
1593 description: "Title text of the destination page in a series-nav link.",
1594 },
1595 ComponentEntry {
1596 class: "moss-series-nav-collection",
1597 kind: "instance",
1598 parent: "moss-series-nav",
1599 data_attrs: &[],
1600 example_html: r#"<div class="moss-series-nav-collection">...</div>"#,
1601 example_markdown: "",
1602 status: Status::Confirmed,
1603 since: "0",
1604 description: "Collection-listing slot in series nav (sibling pages).",
1605 },
1606 ComponentEntry {
1607 class: "moss-series-nav-collection-row",
1608 kind: "instance",
1609 parent: "moss-series-nav-collection",
1610 data_attrs: &[],
1611 example_html: r#"<div class="moss-series-nav-collection-row">...</div>"#,
1612 example_markdown: "",
1613 status: Status::Confirmed,
1614 since: "0",
1615 description: "Row inside the collection listing of series nav.",
1616 },
1617 ComponentEntry {
1618 class: "moss-series-nav-position",
1619 kind: "instance",
1620 parent: "moss-series-nav-collection-row",
1621 data_attrs: &[],
1622 example_html: r#"<span class="moss-series-nav-position">2 of 3</span>"#,
1623 example_markdown: "",
1624 status: Status::Confirmed,
1625 since: "0",
1626 description: "Where this page sits in its series — \"2 of 3\" / 「第 2 篇,共 3 篇」. Counts only the pages still in the reading order, so a page that stepped out with `series: false` is not in the total. Omitted when the folder holds a single page.",
1627 },
1628 ComponentEntry {
1632 class: "moss-collection-cover",
1633 kind: "standalone",
1634 parent: "",
1635 data_attrs: &[],
1636 example_html: r#"<section class="moss-collection-cover">
1637 <div class="moss-collection-cover-row">...</div>
1638</section>"#,
1639 example_markdown: "",
1640 status: Status::Emerging,
1641 since: "0",
1642 description: "Header surface on a collection landing page.",
1643 },
1644 ComponentEntry {
1645 class: "moss-collection-cover-row",
1646 kind: "instance",
1647 parent: "moss-collection-cover",
1648 data_attrs: &[],
1649 example_html: r#"<div class="moss-collection-cover-row">...</div>"#,
1650 example_markdown: "",
1651 status: Status::Emerging,
1652 since: "0",
1653 description: "Row inside `.moss-collection-cover`.",
1654 },
1655 ComponentEntry {
1656 class: "moss-collection-cover-body",
1657 kind: "instance",
1658 parent: "moss-collection-cover",
1659 data_attrs: &[],
1660 example_html: r#"<div class="moss-collection-cover-body">...</div>"#,
1661 example_markdown: "",
1662 status: Status::Emerging,
1663 since: "0",
1664 description: "Body content slot inside `.moss-collection-cover`.",
1665 },
1666 ComponentEntry {
1670 class: "moss-input",
1671 kind: "standalone",
1672 parent: "",
1673 data_attrs: &[],
1674 example_html: r#"<input class="moss-input" type="email" />"#,
1675 example_markdown: "",
1676 status: Status::Confirmed,
1677 since: "0",
1678 description: "Generic form input primitive.",
1679 },
1680 ComponentEntry {
1681 class: "moss-field",
1682 kind: "container",
1683 parent: "",
1684 data_attrs: &[],
1685 example_html: r#"<div class="moss-field">
1686 <label class="moss-label">Email</label>
1687 <input class="moss-input" />
1688</div>"#,
1689 example_markdown: "",
1690 status: Status::Confirmed,
1691 since: "0",
1692 description: "Form field group (label + input). Modifier `--inline` for horizontal layout.",
1693 },
1694 ComponentEntry {
1695 class: "moss-label",
1696 kind: "instance",
1697 parent: "moss-field",
1698 data_attrs: &[],
1699 example_html: r#"<label class="moss-label">Email</label>"#,
1700 example_markdown: "",
1701 status: Status::Confirmed,
1702 since: "0",
1703 description: "Label primitive for `.moss-field`. Modifier `--small` for compact form.",
1704 },
1705 ComponentEntry {
1706 class: "moss-link",
1707 kind: "standalone",
1708 parent: "",
1709 data_attrs: &[],
1710 example_html: r#"<a class="moss-link" href="...">Click me</a>"#,
1711 example_markdown: "",
1712 status: Status::Confirmed,
1713 since: "0",
1714 description: "Inline-link primitive (resets `<button>` chrome too). Use `--subtle` for muted variant.",
1715 },
1716 ComponentEntry {
1717 class: "moss-field--inline",
1718 kind: "instance",
1719 parent: "moss-field",
1720 data_attrs: &[],
1721 example_html: r#"<div class="moss-field moss-field--inline">
1722 <label class="moss-label">Email</label>
1723 <input class="moss-input" />
1724</div>"#,
1725 example_markdown: "",
1726 status: Status::Confirmed,
1727 since: "0",
1728 description: "BEM modifier on `.moss-field` for horizontal label+input layout (used by settings UI primitives).",
1729 },
1730 ComponentEntry {
1731 class: "moss-label--small",
1732 kind: "instance",
1733 parent: "moss-label",
1734 data_attrs: &[],
1735 example_html: r#"<label class="moss-label moss-label--small">Compact label</label>"#,
1736 example_markdown: "",
1737 status: Status::Confirmed,
1738 since: "0",
1739 description: "BEM modifier on `.moss-label` for compact form (used by services settings rows).",
1740 },
1741 ComponentEntry {
1742 class: "moss-info-grid",
1743 kind: "container",
1744 parent: "",
1745 data_attrs: &[],
1746 example_html: r#"<div class="moss-info-grid">
1747 <div class="moss-field moss-field--inline">...</div>
1748 <div class="moss-field moss-field--inline">...</div>
1749</div>"#,
1750 example_markdown: "",
1751 status: Status::Emerging,
1752 since: "0",
1753 description: "Two-column aligned label+value rows (CSS grid with `display: contents` children). Used by the deployment settings panel; ships in the default theme so authors can reuse the layout.",
1754 },
1755 ComponentEntry {
1756 class: "moss-row",
1757 kind: "container",
1758 parent: "",
1759 data_attrs: &[],
1760 example_html: r#"<div class="moss-row">
1761 <div class="moss-field">...</div>
1762 <div class="moss-field">...</div>
1763</div>"#,
1764 example_markdown: "",
1765 status: Status::Emerging,
1766 since: "0",
1767 description: "Horizontal flex row of equal-flex `.moss-field` children. Form-row layout helper shipped in the default theme.",
1768 },
1769 ComponentEntry {
1770 class: "moss-input-feedback",
1771 kind: "instance",
1772 parent: "moss-field",
1773 data_attrs: &[],
1774 example_html: r#"<span class="moss-input-feedback">Saving…</span>"#,
1775 example_markdown: "",
1776 status: Status::Emerging,
1777 since: "0",
1778 description: "Auto-save status hint slot under `.moss-field`. Three state modifiers: `--success`, `--error`, `--fade-out`.",
1779 },
1780 ComponentEntry {
1781 class: "moss-input-feedback--success",
1782 kind: "instance",
1783 parent: "moss-input-feedback",
1784 data_attrs: &[],
1785 example_html: r#"<span class="moss-input-feedback moss-input-feedback--success">Saved</span>"#,
1786 example_markdown: "",
1787 status: Status::Emerging,
1788 since: "0",
1789 description: "Success state modifier on `.moss-input-feedback`.",
1790 },
1791 ComponentEntry {
1792 class: "moss-input-feedback--error",
1793 kind: "instance",
1794 parent: "moss-input-feedback",
1795 data_attrs: &[],
1796 example_html: r#"<span class="moss-input-feedback moss-input-feedback--error">Failed to save</span>"#,
1797 example_markdown: "",
1798 status: Status::Emerging,
1799 since: "0",
1800 description: "Error state modifier on `.moss-input-feedback`.",
1801 },
1802 ComponentEntry {
1803 class: "moss-input-feedback--fade-out",
1804 kind: "instance",
1805 parent: "moss-input-feedback",
1806 data_attrs: &[],
1807 example_html: r#"<span class="moss-input-feedback moss-input-feedback--success moss-input-feedback--fade-out">Saved</span>"#,
1808 example_markdown: "",
1809 status: Status::Emerging,
1810 since: "0",
1811 description: "Transient fade-out modifier on `.moss-input-feedback` (applied after a success message to dismiss it).",
1812 },
1813 ComponentEntry {
1817 class: "moss-comments",
1818 kind: "standalone",
1819 parent: "",
1820 data_attrs: &[],
1821 example_html: r#"<section class="moss-comments">...</section>"#,
1822 example_markdown: "",
1823 status: Status::Confirmed,
1824 since: "0",
1825 description: "Comments surface (per-site SQLite backend or Artalk legacy).",
1826 },
1827 ComponentEntry {
1828 class: "moss-service-inactive",
1829 kind: "instance",
1830 parent: "",
1831 data_attrs: &[],
1832 example_html: r#"<section class="moss-comments moss-service-inactive">...</section>"#,
1833 example_markdown: "",
1834 status: Status::Confirmed,
1835 since: "0",
1836 description: "Co-class applied to `.moss-comments` and `.moss-subscribe-form` when the backing service is not configured. Hidden by default in published sites and revealed inside the preview chrome so authors can see the inactive surface during editing.",
1837 },
1838 ComponentEntry {
1842 class: "moss-preview-popup",
1843 kind: "chrome",
1844 parent: "",
1845 data_attrs: &[],
1846 example_html: r#"<div class="moss-preview-popup" role="tooltip" aria-live="polite">
1847 <strong class="moss-preview-title">...</strong>
1848 <p class="moss-preview-desc">...</p>
1849 <p class="moss-preview-text">...</p>
1850</div>"#,
1851 example_markdown: "",
1852 status: Status::Confirmed,
1853 since: "0",
1854 description: "Floating link-preview popover injected at `document.body` level by the runtime `preview.js`. Fetches `/_moss/previews.json` and renders a hover card with title, description, and excerpt for internal links.",
1855 },
1856 ComponentEntry {
1857 class: "moss-preview-title",
1858 kind: "instance",
1859 parent: "moss-preview-popup",
1860 data_attrs: &[],
1861 example_html: r#"<strong class="moss-preview-title">Article title</strong>"#,
1862 example_markdown: "",
1863 status: Status::Confirmed,
1864 since: "0",
1865 description: "Title slot inside `.moss-preview-popup`.",
1866 },
1867 ComponentEntry {
1868 class: "moss-preview-desc",
1869 kind: "instance",
1870 parent: "moss-preview-popup",
1871 data_attrs: &[],
1872 example_html: r#"<p class="moss-preview-desc">Short description</p>"#,
1873 example_markdown: "",
1874 status: Status::Confirmed,
1875 since: "0",
1876 description: "Description slot inside `.moss-preview-popup` (from frontmatter `description`).",
1877 },
1878 ComponentEntry {
1879 class: "moss-preview-text",
1880 kind: "instance",
1881 parent: "moss-preview-popup",
1882 data_attrs: &[],
1883 example_html: r#"<p class="moss-preview-text">Excerpt of the linked article…</p>"#,
1884 example_markdown: "",
1885 status: Status::Confirmed,
1886 since: "0",
1887 description: "Excerpt slot inside `.moss-preview-popup` (auto-extracted from the linked article body).",
1888 },
1889 ComponentEntry {
1890 class: "moss-colophon",
1891 kind: "chrome",
1892 parent: "",
1893 data_attrs: &[],
1894 example_html: r#"<div class="moss-colophon">
1895 <a href="https://mosspub.com">
1896 <svg class="moss-colophon-icon"></svg>
1897 <span class="moss-colophon-label">Published with moss</span>
1898 </a>
1899</div>"#,
1900 example_markdown: "",
1901 status: Status::Confirmed,
1902 since: "0",
1903 description: "Footer colophon credit appended by moss. Shows the moss mark alone at rest; the wording fades in beneath it on hover or keyboard focus, without moving the mark.",
1904 },
1905 ComponentEntry {
1906 class: "moss-colophon-icon",
1907 kind: "instance",
1908 parent: "moss-colophon",
1909 data_attrs: &[],
1910 example_html: r#"<svg class="moss-colophon-icon"></svg>"#,
1911 example_markdown: "",
1912 status: Status::Confirmed,
1913 since: "0",
1914 description: "The moss mark inside `.moss-colophon`. Decorative (`aria-hidden`) — `.moss-colophon-label` carries the accessible name.",
1915 },
1916 ComponentEntry {
1917 class: "moss-colophon-label",
1918 kind: "instance",
1919 parent: "moss-colophon",
1920 data_attrs: &[],
1921 example_html: r#"<span class="moss-colophon-label">Published with moss</span>"#,
1922 example_markdown: "",
1923 status: Status::Confirmed,
1924 since: "0",
1925 description: "Localized attribution wording inside `.moss-colophon`. Transparent at rest and faded in on hover/focus, positioned out of flow beneath the mark so the reveal shifts nothing — it stays in the DOM because it is the link's accessible name.",
1926 },
1927 ComponentEntry {
1928 class: "moss-shell-frame",
1929 kind: "chrome",
1930 parent: "",
1931 data_attrs: &[],
1932 example_html: r#"<div class="moss-shell-frame">...</div>"#,
1933 example_markdown: "",
1934 status: Status::Emerging,
1935 since: "0",
1936 description: "App-shell frame surface (preview chrome).",
1937 },
1938 ComponentEntry {
1939 class: "moss-mobile-frame",
1940 kind: "chrome",
1941 parent: "moss-shell-frame",
1942 data_attrs: &[],
1943 example_html: r#"<html class="moss-shell-frame moss-mobile-frame">...</html>"#,
1944 example_markdown: "",
1945 status: Status::Emerging,
1946 since: "0",
1947 description: "Runtime marker the preview bridge adds to `<html>` when the shell is in mobile device-preview mode. Since ADR-039 the shell owns chrome clearance by insetting the preview iframe, so no CSS keys off this class and it currently has no effect; it is retained as a revert path and may be removed.",
1948 },
1949 ComponentEntry {
1950 class: "main-nav",
1951 kind: "chrome",
1952 parent: "",
1953 data_attrs: &[],
1954 example_html: r#"<nav class="main-nav container">...</nav>"#,
1955 example_markdown: "",
1956 status: Status::Confirmed,
1957 since: "0",
1958 description: "Top site navigation bar. Legacy non-`moss-` prefix kept for theme parity.",
1959 },
1960 ComponentEntry {
1972 class: "date-line",
1973 kind: "chrome",
1974 parent: "",
1975 data_attrs: &[],
1976 example_html: r#"<div class="date-line"><span class="date">March 3, 2026</span><div class="font-anchor">...</div></div>"#,
1977 example_markdown: "",
1978 status: Status::Confirmed,
1979 since: "0",
1980 description: "Byline row under an article title: the publication date on the left, the reading-size control on the right. Emitted only when the page has a `date`.",
1981 },
1982 ComponentEntry {
1983 class: "date",
1984 kind: "instance",
1985 parent: "date-line",
1986 data_attrs: &[],
1987 example_html: r#"<span class="date">March 3, 2026</span>"#,
1988 example_markdown: "",
1989 status: Status::Confirmed,
1990 since: "0",
1991 description: "The formatted publication date inside `.date-line`. Text is localized to the page's language.",
1992 },
1993 ComponentEntry {
1994 class: "moss-byline",
1995 kind: "container",
1996 parent: "",
1997 data_attrs: &[],
1998 example_html: r#"<div class="moss-byline"><div class="moss-byline-row">作者 糜緒洋</div><div class="moss-byline-row">編輯 謝丁</div></div>"#,
1999 example_markdown: "",
2000 status: Status::Confirmed,
2001 since: "1",
2002 description: "Credit block under the page title, below `.date-line` when there is one. Emitted from the `byline` frontmatter field on every page kind — articles, folder indexes, the homepage and plain pages alike — one `.moss-byline-row` per authored line. On a page moss gives no title of its own (the homepage, a `home: true` folder page, a plain page) it sits under the author's own opening `<h1>`, or at the top of the page content when the body has none. Absent when the field is.",
2003 },
2004 ComponentEntry {
2005 class: "moss-byline-row",
2006 kind: "instance",
2007 parent: "moss-byline",
2008 data_attrs: &[],
2009 example_html: r#"<div class="moss-byline-row">首發媒體 <a href="https://theinitium.com/a">端傳媒</a></div>"#,
2010 example_markdown: "",
2011 status: Status::Confirmed,
2012 since: "1",
2013 description: "One credit line. Its content is the author's text rendered as inline markdown, so a row may contain links or emphasis. moss does not know which part is a role and which is a name — style the whole row.",
2014 },
2015 ComponentEntry {
2016 class: "moss-article-colophon",
2017 kind: "container",
2018 parent: "",
2019 data_attrs: &[],
2020 example_html: r#"<div class="moss-article-colophon"><div class="moss-article-colophon-row">首發媒體 <a href="https://theinitium.com/a">端傳媒</a></div></div>"#,
2021 example_markdown: "",
2022 status: Status::Confirmed,
2023 since: "1",
2024 description: "Credit block at the FOOT of the page, emitted from the `colophon` frontmatter field — where the piece first ran, contributor biographies, production credits. Same rows as `.moss-byline`, different end of the page. Emitted on every page kind: inside `<article>` on an article page, and last in the page content everywhere else — after the children listing on a folder index or the homepage — where the enclosing element is not an `<article>` despite the class name. Unrelated to `.review-colophon`, which is the review feature's book card.",
2025 },
2026 ComponentEntry {
2027 class: "moss-article-colophon-row",
2028 kind: "instance",
2029 parent: "moss-article-colophon",
2030 data_attrs: &[],
2031 example_html: r#"<div class="moss-article-colophon-row">封面 基輔米迦勒修道院門口的陣亡將士紀念牆(拍攝:糜緒洋)</div>"#,
2032 example_markdown: "",
2033 status: Status::Confirmed,
2034 since: "1",
2035 description: "One foot-credit line, rendered as inline markdown exactly like `.moss-byline-row`.",
2036 },
2037 ComponentEntry {
2038 class: "site-name",
2039 kind: "instance",
2040 parent: "main-nav",
2041 data_attrs: &[],
2042 example_html: r#"<a href="/" class="site-name">在場</a>"#,
2043 example_markdown: "",
2044 status: Status::Confirmed,
2045 since: "0",
2046 description: "The site title link at the left of the nav bar. On a non-home page the same slot may instead carry `.breadcrumb-segment`.",
2047 },
2048 ComponentEntry {
2049 class: "breadcrumb-segment",
2050 kind: "instance",
2051 parent: "main-nav",
2052 data_attrs: &[],
2053 example_html: r#"<a href="/awards/" class="breadcrumb-segment">獎項</a>"#,
2054 example_markdown: "",
2055 status: Status::Confirmed,
2056 since: "0",
2057 description: "One ancestor link in the nav-left breadcrumb trail, used in place of `.site-name` once the page is below the site root.",
2058 },
2059 ComponentEntry {
2060 class: "nav-icons",
2061 kind: "chrome",
2062 parent: "main-nav",
2063 data_attrs: &[],
2064 example_html: r#"<div class="nav-icons">...</div>"#,
2065 example_markdown: "",
2066 status: Status::Confirmed,
2067 since: "0",
2068 description: "Right-hand icon cluster in the nav bar (search, theme toggle, and similar). Stationary chrome, present whether or not the site has nav links.",
2069 },
2070 ComponentEntry {
2082 class: "nav-left",
2083 kind: "chrome",
2084 parent: "main-nav",
2085 data_attrs: &[],
2086 example_html: r#"<div class="nav-left"><a href="/" class="site-name">在場</a></div>"#,
2087 example_markdown: "",
2088 status: Status::Confirmed,
2089 since: "0",
2090 description: "Left group of the nav bar. Holds either `.site-name` or the breadcrumb trail, never both.",
2091 },
2092 ComponentEntry {
2093 class: "nav-right",
2094 kind: "chrome",
2095 parent: "main-nav",
2096 data_attrs: &[],
2097 example_html: r#"<div class="nav-right">…hamburger, .nav-links, .nav-icons…</div>"#,
2098 example_markdown: "",
2099 status: Status::Confirmed,
2100 since: "0",
2101 description: "Right group of the nav bar: the mobile menu button, the nav links, and the icon cluster, in that order.",
2102 },
2103 ComponentEntry {
2104 class: "nav-links",
2105 kind: "chrome",
2106 parent: "nav-right",
2107 data_attrs: &[],
2108 example_html: r#"<div class="nav-links"><a href="/about/" class="active">關於</a>…</div>"#,
2109 example_markdown: "",
2110 status: Status::Confirmed,
2111 since: "0",
2112 description: "The nav link list. The link for the page currently being viewed additionally carries the bare class `active` — style `.nav-links .active`, not a `moss-` class.",
2113 },
2114 ComponentEntry {
2115 class: "site-logo",
2116 kind: "instance",
2117 parent: "site-name",
2118 data_attrs: &[],
2119 example_html: r#"<img class="site-logo" src="…" alt="" aria-hidden="true">"#,
2120 example_markdown: "",
2121 status: Status::Confirmed,
2122 since: "0",
2123 description: "Optional logo image inside the site-name link. Decorative by construction (`alt=\"\"` + `aria-hidden`), because the adjacent text already names the site.",
2124 },
2125 ComponentEntry {
2126 class: "breadcrumb-label",
2127 kind: "instance",
2128 parent: "nav-left",
2129 data_attrs: &[],
2130 example_html: r#"<span class="breadcrumb-label">獎項</span>"#,
2131 example_markdown: "",
2132 status: Status::Confirmed,
2133 since: "0",
2134 description: "The final, non-linked breadcrumb segment — the page you are on. `.breadcrumb-segment` is the linked form for ancestors.",
2135 },
2136 ComponentEntry {
2137 class: "breadcrumb-separator",
2138 kind: "instance",
2139 parent: "nav-left",
2140 data_attrs: &[],
2141 example_html: r#"<span class="breadcrumb-separator">/</span>"#,
2142 example_markdown: "",
2143 status: Status::Confirmed,
2144 since: "0",
2145 description: "The `/` between breadcrumb segments. Restyle or hide this rather than trying to remove it from the markup.",
2146 },
2147 ComponentEntry {
2148 class: "mobile-menu-button",
2149 kind: "chrome",
2150 parent: "nav-right",
2151 data_attrs: &[],
2152 example_html: r#"<button class="mobile-menu-button" aria-label="…"><svg>…</svg></button>"#,
2153 example_markdown: "",
2154 status: Status::Confirmed,
2155 since: "0",
2156 description: "The hamburger. Emitted on every page and hidden by media query above the mobile breakpoint — it is not conditionally rendered, so a rule that shows it always will.",
2157 },
2158 ComponentEntry {
2159 class: "nav-search-btn",
2160 kind: "instance",
2161 parent: "nav-icons",
2162 data_attrs: &[],
2163 example_html: r#"<button class="nav-search-btn" type="button" aria-label="…"><svg class="search-icon">…</svg></button>"#,
2164 example_markdown: "",
2165 status: Status::Confirmed,
2166 since: "0",
2167 description: "Search button in the nav icon cluster. Its glyph is `.search-icon`.",
2168 },
2169 ComponentEntry {
2170 class: "search-icon",
2171 kind: "instance",
2172 parent: "nav-search-btn",
2173 data_attrs: &[],
2174 example_html: r#"<svg class="search-icon" aria-hidden="true" width="1em" height="1em">…</svg>"#,
2175 example_markdown: "",
2176 status: Status::Confirmed,
2177 since: "0",
2178 description: "The magnifier glyph. Sized in `em` and stroked with `currentColor`, so it follows the button's font-size and color rather than needing its own rule.",
2179 },
2180 ComponentEntry {
2181 class: "nav-theme-btn",
2182 kind: "instance",
2183 parent: "nav-icons",
2184 data_attrs: &[],
2185 example_html: r#"<button class="nav-theme-btn" type="button" aria-label="…"><svg class="theme-toggle-icon">…</svg></button>"#,
2186 example_markdown: "",
2187 status: Status::Confirmed,
2188 since: "0",
2189 description: "Light/dark toggle in the nav icon cluster. Its glyph is `.theme-toggle-icon`.",
2190 },
2191 ComponentEntry {
2192 class: "theme-toggle-icon",
2193 kind: "instance",
2194 parent: "nav-theme-btn",
2195 data_attrs: &[],
2196 example_html: r#"<svg class="theme-toggle-icon" aria-hidden="true" width="1em" height="1em">…</svg>"#,
2197 example_markdown: "",
2198 status: Status::Confirmed,
2199 since: "0",
2200 description: "The sun/moon glyph. One SVG whose clip path animates between states — restyle it, but do not expect two separate icons to swap.",
2201 },
2202 ComponentEntry {
2203 class: "nav-lang-toggle",
2204 kind: "chrome",
2205 parent: "nav-icons",
2206 data_attrs: &[],
2207 example_html: r#"<div class="nav-lang-toggle" aria-label="…"><span class="nav-lang-current">繁</span><a href="/en/" class="nav-lang-link" hreflang="en">EN</a></div>"#,
2208 example_markdown: "",
2209 status: Status::Confirmed,
2210 since: "0",
2211 description: "The language switcher. Present only when the site has more than one edition among the three the switcher resolves (English, Simplified Chinese, Traditional Chinese) — a `ja/` or `fr/` tree publishes but adds no entry here.",
2212 },
2213 ComponentEntry {
2214 class: "nav-lang-current",
2215 kind: "instance",
2216 parent: "nav-lang-toggle",
2217 data_attrs: &[],
2218 example_html: r#"<span class="nav-lang-current">繁</span>"#,
2219 example_markdown: "",
2220 status: Status::Confirmed,
2221 since: "0",
2222 description: "The edition being viewed, as inert text rather than a link — style the current-language affordance here.",
2223 },
2224 ComponentEntry {
2225 class: "nav-lang-link",
2226 kind: "instance",
2227 parent: "nav-lang-toggle",
2228 data_attrs: &[],
2229 example_html: r#"<a href="/en/" class="nav-lang-link" hreflang="en">EN</a>"#,
2230 example_markdown: "",
2231 status: Status::Confirmed,
2232 since: "0",
2233 description: "A link to another edition of the same page. Carries `hreflang`, so `[hreflang=\"en\"]` is a stable hook for per-language styling.",
2234 },
2235 ComponentEntry {
2243 class: "moss-nav-island",
2244 kind: "chrome",
2245 parent: "",
2246 data_attrs: &[DataAttr {
2247 name: "data-shown",
2248 values: &["false", "true"],
2249 default: "false",
2250 description: "Whether the island is currently revealed. Written by the site runtime; ABSENT in the emitted HTML, which is what keeps the island invisible with JavaScript off.",
2251 }],
2252 example_html: r#"<div class="moss-nav-island" data-shown="true"><div class="moss-nav-island-bar">…</div></div>"#,
2253 example_markdown: "",
2254 status: Status::Emerging,
2255 since: "0",
2256 description: "Floating navigation island: a one-line bar, aligned to the text column, revealed on scroll-up once the masthead has left the screen. Emitted on any page with a breadcrumb trail, but it only ever shows where it can take the reader somewhere — a trail of three or more crumbs (down the tree) or a page with headings (the sections panel). On a top-level page with neither, the markup stays dormant (ADR-049 §10). Turn it off site-wide with `[site].floating_nav = false` or `--moss-nav-island-display: none`.",
2257 },
2258 ComponentEntry {
2259 class: "moss-nav-island-bar",
2260 kind: "instance",
2261 parent: "moss-nav-island",
2262 data_attrs: &[],
2263 example_html: r#"<div class="moss-nav-island-bar">…trail, actions, progress…</div>"#,
2264 example_markdown: "",
2265 status: Status::Emerging,
2266 since: "0",
2267 description: "The visible rounded bar. Its width follows `--moss-nav-width`/`--moss-content-width`, so it lines up with the article text rather than with the window.",
2268 },
2269 ComponentEntry {
2270 class: "moss-nav-island-trail",
2271 kind: "instance",
2272 parent: "moss-nav-island-bar",
2273 data_attrs: &[],
2274 example_html: r#"<nav class="moss-nav-island-trail" aria-label="Breadcrumb">…</nav>"#,
2275 example_markdown: "",
2276 status: Status::Emerging,
2277 since: "0",
2278 description: "The breadcrumb inside the island. Same segment classes as the masthead's, plus the current page as a final crumb. It never wraps: ancestors fold into `.moss-nav-island-more` until the row fits.",
2279 },
2280 ComponentEntry {
2281 class: "moss-nav-island-current",
2282 kind: "instance",
2283 parent: "moss-nav-island-trail",
2284 data_attrs: &[],
2285 example_html: r#"<span class="breadcrumb-segment moss-nav-island-current" aria-current="page">末代女礦工</span>"#,
2286 example_markdown: "",
2287 status: Status::Emerging,
2288 since: "0",
2289 description: "The page you are on, as the trail's last crumb. The only crumb permitted to truncate — an ancestor either fits whole or folds away.",
2290 },
2291 ComponentEntry {
2292 class: "moss-nav-island-more",
2293 kind: "instance",
2294 parent: "moss-nav-island-trail",
2295 data_attrs: &[],
2296 example_html: r#"<button class="moss-nav-island-more" aria-expanded="false">…</button>"#,
2297 example_markdown: "",
2298 status: Status::Emerging,
2299 since: "0",
2300 description: "Stands in for the ancestor levels the trail had to drop. Opens the levels menu on click; names them on hover via `data-tooltip`. Never opens on hover — a touch device has none, and that is the width where folding happens.",
2301 },
2302 ComponentEntry {
2303 class: "moss-nav-island-actions",
2304 kind: "instance",
2305 parent: "moss-nav-island-bar",
2306 data_attrs: &[],
2307 example_html: r#"<span class="moss-nav-island-actions">…</span>"#,
2308 example_markdown: "",
2309 status: Status::Emerging,
2310 since: "0",
2311 description: "Button cluster at the island's end edge. Holds the sections button only — theme, language and search stay in the masthead.",
2312 },
2313 ComponentEntry {
2314 class: "moss-nav-island-sections",
2315 kind: "instance",
2316 parent: "moss-nav-island-actions",
2317 data_attrs: &[],
2318 example_html: r#"<button class="moss-nav-island-sections" aria-expanded="false"><svg>…</svg></button>"#,
2319 example_markdown: "",
2320 status: Status::Emerging,
2321 since: "0",
2322 description: "Opens this page's section list. Ships `hidden` and is unhidden only once headings have been found, so a page with no headings shows no dead glyph.",
2323 },
2324 ComponentEntry {
2325 class: "moss-nav-island-menu",
2326 kind: "instance",
2327 parent: "moss-nav-island",
2328 data_attrs: &[DataAttr {
2329 name: "data-island-menu",
2330 values: &["levels", "sections"],
2331 default: "levels",
2332 description: "Which of the two menus this is: the folded ancestor levels, or the page's sections.",
2333 }],
2334 example_html: r#"<div class="moss-nav-island-menu" data-island-menu="sections">…</div>"#,
2335 example_markdown: "",
2336 status: Status::Emerging,
2337 since: "0",
2338 description: "Popover opened by `.moss-nav-island-more` or `.moss-nav-island-sections`. Every row reserves a leading gutter for the current-row rule, so the labels line up in one column whether or not a row is marked.",
2339 },
2340 ComponentEntry {
2341 class: "moss-breadcrumb-more",
2342 kind: "instance",
2343 parent: "",
2344 data_attrs: &[],
2345 example_html: r#"<button class="moss-breadcrumb-more" aria-expanded="false">…</button>"#,
2346 example_markdown: "",
2347 status: Status::Emerging,
2348 since: "0",
2349 description: "The masthead trail's counterpart to `.moss-nav-island-more`: stands in for the ancestor levels the trail folded, opens the levels menu on click, names them on hover via `data-tooltip`. Emitted (hidden) only when the trail has a middle to fold — three or more crumbs.",
2350 },
2351 ComponentEntry {
2352 class: "moss-breadcrumb-menu",
2353 kind: "instance",
2354 parent: "",
2355 data_attrs: &[],
2356 example_html: r#"<div class="moss-breadcrumb-menu" hidden>…</div>"#,
2357 example_markdown: "",
2358 status: Status::Emerging,
2359 since: "0",
2360 description: "Popover listing the masthead trail's folded ancestor levels, opened by `.moss-breadcrumb-more`. A sibling of `.nav-left` (which clips its own overflow), positioned against `.nav-content`. Same shape as `.moss-nav-island-menu`.",
2361 },
2362 ComponentEntry {
2363 class: "moss-nav-island-progress",
2364 kind: "instance",
2365 parent: "moss-nav-island-bar",
2366 data_attrs: &[],
2367 example_html: r#"<span class="moss-nav-island-progress"><span class="moss-nav-island-progress-fill"></span></span>"#,
2368 example_markdown: "",
2369 status: Status::Emerging,
2370 since: "0",
2371 description: "Reading-progress track along the island's own bottom edge — not a separate bar across the window. Currently measures document scroll.",
2372 },
2373 ComponentEntry {
2374 class: "moss-nav-island-progress-fill",
2375 kind: "instance",
2376 parent: "moss-nav-island-progress",
2377 data_attrs: &[],
2378 example_html: r#"<span class="moss-nav-island-progress-fill" style="width: 42%"></span>"#,
2379 example_markdown: "",
2380 status: Status::Emerging,
2381 since: "0",
2382 description: "The filled portion of the progress track. Its `width` is written inline by the site runtime; with JavaScript off it stays at 0 and the track reads as empty.",
2383 },
2384 ComponentEntry {
2385 class: "footer-default",
2386 kind: "chrome",
2387 parent: "",
2388 data_attrs: &[],
2389 example_html: r#"<p class="footer-default"><a href="/rss.xml" class="footer-link" data-external>RSS</a>…</p>"#,
2390 example_markdown: "",
2391 status: Status::Confirmed,
2392 since: "0",
2393 description: "The generated footer link row, emitted only when the site has no authored `footer.md`. Authoring a footer replaces it, so a rule targeting this stops applying the moment the site gains one.",
2394 },
2395 ComponentEntry {
2396 class: "footer-link",
2397 kind: "instance",
2398 parent: "footer-default",
2399 data_attrs: &[],
2400 example_html: r#"<a href="/rss.xml" class="footer-link" data-external>RSS</a>"#,
2401 example_markdown: "",
2402 status: Status::Confirmed,
2403 since: "0",
2404 description: "One link in the generated footer row (RSS and similar). Off-site ones also carry `data-external`.",
2405 },
2406 ComponentEntry {
2407 class: "moss-child-section-divider",
2408 kind: "instance",
2409 parent: "",
2410 data_attrs: &[],
2411 example_html: r#"<hr class="moss-child-section-divider" />"#,
2412 example_markdown: "",
2413 status: Status::Emerging,
2414 since: "0",
2415 description: "Divider rule between auto-generated child sections.",
2416 },
2417 ComponentEntry {
2418 class: "moss-unknown-shortcode",
2424 kind: "standalone",
2425 parent: "",
2426 data_attrs: &[DataAttr {
2427 name: "data-name",
2428 values: &[],
2429 default: "",
2430 description: "The unrecognised shortcode name, as written by the author.",
2431 }],
2432 example_html: r#"<div class="moss-unknown-shortcode" data-name="foo">
2433
2434<p>body parsed as markdown</p>
2435
2436</div>"#,
2437 example_markdown: ":::foo\nbody parsed as markdown\n:::",
2438 status: Status::Confirmed,
2439 since: "0",
2440 description: "Fallback wrapper emitted for any `:::name` fence whose name is not a registered shortcode. The body is still parsed as markdown and a build warning names the shortcode, so a misspelling degrades to a styled region rather than losing content.",
2441 },
2442 ComponentEntry {
2446 class: "moss-hl-keyword",
2447 kind: "instance",
2448 parent: "",
2449 data_attrs: &[],
2450 example_html: r#"<span class="moss-hl-keyword">if</span>"#,
2451 example_markdown: "",
2452 status: Status::Emerging,
2453 since: "0",
2454 description: "Syntax-highlight token: keyword.",
2455 },
2456 ComponentEntry {
2457 class: "moss-hl-string",
2458 kind: "instance",
2459 parent: "",
2460 data_attrs: &[],
2461 example_html: r#"<span class="moss-hl-string">"hi"</span>"#,
2462 example_markdown: "",
2463 status: Status::Emerging,
2464 since: "0",
2465 description: "Syntax-highlight token: string literal.",
2466 },
2467 ComponentEntry {
2468 class: "moss-hl-comment",
2469 kind: "instance",
2470 parent: "",
2471 data_attrs: &[],
2472 example_html: r#"<span class="moss-hl-comment">// note</span>"#,
2473 example_markdown: "",
2474 status: Status::Emerging,
2475 since: "0",
2476 description: "Syntax-highlight token: comment.",
2477 },
2478 ComponentEntry {
2479 class: "moss-hl-function",
2480 kind: "instance",
2481 parent: "",
2482 data_attrs: &[],
2483 example_html: r#"<span class="moss-hl-function">render</span>"#,
2484 example_markdown: "",
2485 status: Status::Emerging,
2486 since: "0",
2487 description: "Syntax-highlight token: function name.",
2488 },
2489 ComponentEntry {
2490 class: "moss-hl-type",
2491 kind: "instance",
2492 parent: "",
2493 data_attrs: &[],
2494 example_html: r#"<span class="moss-hl-type">String</span>"#,
2495 example_markdown: "",
2496 status: Status::Emerging,
2497 since: "0",
2498 description: "Syntax-highlight token: type name.",
2499 },
2500 ComponentEntry {
2501 class: "moss-hl-number",
2502 kind: "instance",
2503 parent: "",
2504 data_attrs: &[],
2505 example_html: r#"<span class="moss-hl-number">42</span>"#,
2506 example_markdown: "",
2507 status: Status::Emerging,
2508 since: "0",
2509 description: "Syntax-highlight token: numeric literal.",
2510 },
2511 ComponentEntry {
2512 class: "moss-hl-operator",
2513 kind: "instance",
2514 parent: "",
2515 data_attrs: &[],
2516 example_html: r#"<span class="moss-hl-operator">+</span>"#,
2517 example_markdown: "",
2518 status: Status::Emerging,
2519 since: "0",
2520 description: "Syntax-highlight token: operator.",
2521 },
2522 ComponentEntry {
2523 class: "moss-hl-builtin",
2524 kind: "instance",
2525 parent: "",
2526 data_attrs: &[],
2527 example_html: r#"<span class="moss-hl-builtin">print</span>"#,
2528 example_markdown: "",
2529 status: Status::Emerging,
2530 since: "0",
2531 description: "Syntax-highlight token: builtin identifier.",
2532 },
2533 ComponentEntry {
2534 class: "moss-hl-tag",
2535 kind: "instance",
2536 parent: "",
2537 data_attrs: &[],
2538 example_html: r#"<span class="moss-hl-tag">div</span>"#,
2539 example_markdown: "",
2540 status: Status::Emerging,
2541 since: "0",
2542 description: "Syntax-highlight token: markup tag name.",
2543 },
2544 ComponentEntry {
2545 class: "moss-hl-attr",
2546 kind: "instance",
2547 parent: "",
2548 data_attrs: &[],
2549 example_html: r#"<span class="moss-hl-attr">class</span>"#,
2550 example_markdown: "",
2551 status: Status::Emerging,
2552 since: "0",
2553 description: "Syntax-highlight token: attribute name.",
2554 },
2555 ComponentEntry {
2556 class: "moss-hl-meta",
2557 kind: "instance",
2558 parent: "",
2559 data_attrs: &[],
2560 example_html: r#"<span class="moss-hl-meta">@derive</span>"#,
2561 example_markdown: "",
2562 status: Status::Emerging,
2563 since: "0",
2564 description: "Syntax-highlight token: meta/annotation.",
2565 },
2566 ComponentEntry {
2567 class: "moss-hl-addition-bg",
2568 kind: "instance",
2569 parent: "",
2570 data_attrs: &[],
2571 example_html: r#"<span class="moss-hl-addition-bg">+ added line</span>"#,
2572 example_markdown: "",
2573 status: Status::Emerging,
2574 since: "0",
2575 description: "Syntax-highlight diff token: added-line background.",
2576 },
2577 ComponentEntry {
2578 class: "moss-hl-deletion",
2579 kind: "instance",
2580 parent: "",
2581 data_attrs: &[],
2582 example_html: r#"<span class="moss-hl-deletion">- removed line</span>"#,
2583 example_markdown: "",
2584 status: Status::Emerging,
2585 since: "0",
2586 description: "Syntax-highlight diff token: removed-line text.",
2587 },
2588 ComponentEntry {
2589 class: "moss-hl-deletion-bg",
2590 kind: "instance",
2591 parent: "",
2592 data_attrs: &[],
2593 example_html: r#"<span class="moss-hl-deletion-bg">- removed line</span>"#,
2594 example_markdown: "",
2595 status: Status::Emerging,
2596 since: "0",
2597 description: "Syntax-highlight diff token: removed-line background.",
2598 },
2599 ComponentEntry {
2600 class: "moss-recent",
2601 kind: "container",
2602 parent: "",
2603 data_attrs: &[],
2604 example_html: r#"<ul class="moss-recent">
2605 <li><a href="/posts/spring-notes/">Spring notes</a><div class="moss-recent__date">2026-04-12</div><div class="moss-recent__desc">A walk through the garden.</div></li>
2606</ul>"#,
2607 example_markdown: ":::recent {count=5 since=\"2026-01-01\"}\n:::\n",
2608 status: Status::Emerging,
2609 since: "0",
2610 description: "Auto-generated list of recent posts. Sorted newest-first; date and description slots are filled per child. No default CSS in the bundled theme — theme authors style it freely. IMPORTANT: emitted by the EMAIL/newsletter path only. On a web page, `:::recent` renders its fallback body as ordinary markdown and emits no list, because the per-page processor has no access to the build's aggregate document slice; a `:::recent` block with an empty body therefore produces nothing at all on a web page. To list posts on a page today, rely on the automatic child listing a folder home emits (`moss-cards`), and give any `:::recent` block a fallback body.",
2611 },
2612 ComponentEntry {
2613 class: "moss-recent__date",
2614 kind: "instance",
2615 parent: "moss-recent",
2616 data_attrs: &[],
2617 example_html: r#"<div class="moss-recent__date">2026-04-12</div>"#,
2618 example_markdown: "",
2619 status: Status::Emerging,
2620 since: "0",
2621 description: "Per-entry date slot inside `.moss-recent` (BEM child). Format is `YYYY-MM-DD`, derived from frontmatter `date`. Empty string when the post lacks a parseable date.",
2622 },
2623 ComponentEntry {
2624 class: "moss-recent__desc",
2625 kind: "instance",
2626 parent: "moss-recent",
2627 data_attrs: &[],
2628 example_html: r#"<div class="moss-recent__desc">A walk through the garden.</div>"#,
2629 example_markdown: "",
2630 status: Status::Emerging,
2631 since: "0",
2632 description: "Per-entry description slot inside `.moss-recent` (BEM child). Sourced from frontmatter `description`; empty when unset.",
2633 },
2634 ComponentEntry {
2640 class: "moss-ambient-video",
2641 kind: "standalone",
2642 parent: "",
2643 data_attrs: &[
2644 DataAttr {
2645 name: "data-paused",
2646 values: &[],
2647 default: "",
2648 description: "Boolean presence flag set by ambient-video.ts when the video is paused (user-initiated or reduced-motion guard). CSS uses `[data-paused]` to keep the toggle visible.",
2649 },
2650 ],
2651 example_html: r#"<div class="moss-ambient-video">
2652 <video data-loop src="clip.mp4" autoplay muted loop playsinline preload="metadata"></video>
2653 <button class="moss-ambient-toggle" type="button" aria-label="Pause video">⏸</button>
2654</div>"#,
2655 example_markdown: "![[clip.mp4|loop]]",
2656 status: Status::Emerging,
2657 since: "1",
2658 description: "JS-injected wrapper around a `video[data-loop]` element. Provides the positioning context for `.moss-ambient-toggle` and the `[data-paused]` state hook. Not emitted by the Rust synthesizer — ambient-video.ts creates it at init.",
2659 },
2660 ComponentEntry {
2661 class: "moss-ambient-toggle",
2662 kind: "instance",
2663 parent: "moss-ambient-video",
2664 data_attrs: &[],
2665 example_html: r#"<button class="moss-ambient-toggle" type="button" aria-label="Pause video">⏸</button>"#,
2666 example_markdown: "",
2667 status: Status::Emerging,
2668 since: "1",
2669 description: "Chrome-free pause/play toggle button for ambient loop videos. Injected by ambient-video.ts. Keyboard-focusable; `aria-label` toggles between \"Pause video\" and \"Play video\". Visible on hover/focus of `.moss-ambient-video` and always visible when `[data-paused]`. Satisfies WCAG 2.2.2 Level A (Pause, Stop, Hide).",
2670 },
2671 ComponentEntry {
2678 class: "moss-math",
2679 kind: "standalone",
2680 parent: "",
2681 data_attrs: &[
2682 DataAttr {
2683 name: "data-moss-math",
2684 values: &["inline", "display"],
2685 default: "inline",
2686 description: "Which delimiter produced the equation: `inline` for `$…$`, `display` for `$$…$$`. Carries the distinction to CSS and to the typesetter so neither has to re-derive it from context — a theme can select on it today to centre display math; moss ships no math stylesheet of its own yet, so both variants currently inherit plain `<code>` styling.",
2687 },
2688 ],
2689 example_html: r#"<code class="moss-math" data-moss-math="inline">$E = mc^2$</code>"#,
2690 example_markdown: "Energy $E = mc^2$.",
2691 status: Status::Emerging,
2692 since: "1",
2693 description: "A LaTeX equation. In P1 the element holds the author's own markdown source — `$` / `$$` delimiters included — HTML-escaped: an honest fallback that never shows a blank where an equation was written, and never deletes the delimiters of prose that merely looked like math. Requires `[site].math` (default on).",
2694 },
2695 ComponentEntry {
2696 class: "moss-math-scroll",
2697 kind: "container",
2698 parent: "",
2699 data_attrs: &[],
2700 example_html: r#"<div class="moss-math-scroll"><svg class="moss-math" data-moss-math="display">…</svg></div>"#,
2701 example_markdown: "$$E = mc^2$$",
2702 status: Status::Emerging,
2703 since: "1",
2704 description: "Horizontal-scroll container the build path wraps around typeset display math (`svg.moss-math[data-moss-math=\"display\"]`). On a narrow viewport a wide equation scrolls inside this box at its natural size rather than shrinking to unreadability or pushing the page into horizontal overflow. Emitted only by P2's typeset path; the P1 `<code>` fallback is never wrapped. A display SVG left unwrapped still cannot overflow the page — it falls back to scaling down via `max-width: 100%`.",
2705 },
2706 ComponentEntry {
2707 class: "moss-table-scroll",
2708 kind: "container",
2709 parent: "",
2710 data_attrs: &[],
2711 example_html: r#"<div class="moss-table-scroll" tabindex="0">
2712 <table>…</table>
2713</div>"#,
2714 example_markdown: "| Name | Subs |\n| --- | --- |\n| a | 1,200 |",
2715 status: Status::Emerging,
2716 since: "1",
2717 description: "Horizontal-scroll wrapper the renderer emits around every Markdown table and every CSV/TSV embed (`.moss-embed[data-type=\"table\"]`). Keeps the `<table>` semantically intact (unlike a `display:block` table, which breaks column layout and assistive-tech table semantics) while letting a wide table scroll inside its own box instead of pushing the page into horizontal overflow. `tabindex=\"0\"` makes an overflowing table keyboard-scrollable.",
2718 },
2719 ComponentEntry {
2720 class: "moss-col-right",
2721 kind: "instance",
2722 parent: "moss-table-scroll",
2723 data_attrs: &[],
2724 example_html: r#"<th class="moss-col-right">订阅数</th>
2725<td class="moss-col-right">1,457,776</td>"#,
2726 example_markdown: "| Subs |\n| --: |\n| 1,457,776 |",
2727 status: Status::Emerging,
2728 since: "1",
2729 description: "Right-aligned table cell (`<th>`/`<td>`). Applied to a whole column when the author right-aligned it in GFM (`|--:|`) or when the column auto-detects as numeric, so figures register on their trailing digits. Pairs with the table's `font-variant-numeric: tabular-nums`.",
2730 },
2731 ComponentEntry {
2732 class: "moss-col-center",
2733 kind: "instance",
2734 parent: "moss-table-scroll",
2735 data_attrs: &[],
2736 example_html: r#"<th class="moss-col-center">Status</th>
2737<td class="moss-col-center">✓</td>"#,
2738 example_markdown: "| Status |\n| :-: |\n| ✓ |",
2739 status: Status::Emerging,
2740 since: "1",
2741 description: "Center-aligned table cell (`<th>`/`<td>`). Applied to a whole column the author center-aligned in GFM (`|:-:|`).",
2742 },
2743 ComponentEntry {
2744 class: "moss-search",
2745 kind: "chrome",
2746 parent: "",
2747 data_attrs: &[],
2748 example_html: r#"<div class="moss-search" id="moss-search" hidden>
2749 <div class="moss-search__backdrop"></div>
2750 <div class="moss-search__panel" role="dialog" aria-modal="true" aria-label="Search">
2751 <div class="moss-search__field"><input class="moss-search__input" role="combobox"></div>
2752 <div class="moss-search__progress" hidden></div>
2753 <div class="moss-search__seam"></div>
2754 <div class="moss-search__body">
2755 <p class="moss-search__status" role="status" hidden></p>
2756 <ul class="moss-search__results" role="listbox">
2757 <li class="moss-search__row">
2758 <a class="moss-search__link" role="option" href="/posts/foo/">
2759 <span class="moss-search__title">Title</span>
2760 <span class="moss-search__excerpt">…a <mark>match</mark>…</span>
2761 </a>
2762 </li>
2763 </ul>
2764 </div>
2765 </div>
2766</div>"#,
2767 example_markdown: "",
2768 status: Status::Emerging,
2769 since: "1",
2770 description: "Site-search overlay. Not emitted by the build — the client runtime (`_moss/js/search.<hash>.js`, shipped only when the build wrote a Pagefind index) constructs this subtree lazily on the first open, so a reader who never searches downloads no index and materializes no DOM. Opened by the nav's `.nav-search-btn`, by `/`, or by ⌘K/Ctrl+K. BEM children carry the interior: `__backdrop` (translucent page-coloured scrim, not an opaque modal takeover), `__panel` (top-anchored at 18vh, fixed 18px radius at any height), `__field`/`__input`, `__progress` (1px accent hairline, delayed 200ms so fast queries never flash it), `__seam` (hairline inset by the corner radius), `__status` (idle / no-matches line, sharing one vertical slot with the results so the panel never jumps), `__results`/`__row`/`__link`/`__title`/`__excerpt`. Selection is a 2px `--moss-color-ui-accent` left border plus a ~4% accent tint — never a solid fill block. `<mark>` inside `__excerpt` is Pagefind's own term highlighting, restyled to colour emphasis rather than a highlighter box.",
2771 },
2772 ComponentEntry {
2773 class: "moss-footnotes",
2774 kind: "container",
2775 parent: "",
2776 data_attrs: &[],
2777 example_html: r##"<section class="moss-footnotes" role="doc-endnotes">
2778<ol>
2779<li id="fn-1"><p>The note. <a class="moss-footnote-backref" href="#fnref-1" role="doc-backlink" aria-label="Back to reference 1">↩︎</a></p>
2780</li>
2781</ol>
2782</section>"##,
2783 example_markdown: "Text[^1].\n\n[^1]: The note.",
2784 status: Status::Emerging,
2785 since: "1",
2786 description: "The document's endnote section, appended after the body by the renderer. Holds one `<li id=\"fn-N\">` per footnote in first-reference order, whatever depth the author wrote the definition at — a definition inside a blockquote or a list item is hoisted here too. Present only on pages that define at least one footnote. `role=\"doc-endnotes\"` (DPUB-ARIA) names the region for assistive tech.",
2787 },
2788 ComponentEntry {
2789 class: "moss-footnote-ref",
2790 kind: "instance",
2791 parent: "moss-footnotes",
2792 data_attrs: &[],
2793 example_html: r##"<sup class="moss-footnote-ref" id="fnref-1"><a href="#fn-1" role="doc-noteref">1</a></sup>"##,
2794 example_markdown: "Text[^1].\n\n[^1]: The note.",
2795 status: Status::Emerging,
2796 since: "1",
2797 description: "The in-body footnote marker: a superscript number linking down to its note. The number is first-reference order, not the author's label, so `[^method]` and `[^1]` both print as ordinals. A second marker for the same note takes id `fnref-N-2`, `fnref-N-3`, … so each has its own back-link.",
2798 },
2799 ComponentEntry {
2800 class: "moss-footnote-backref",
2801 kind: "instance",
2802 parent: "moss-footnotes",
2803 data_attrs: &[],
2804 example_html: r##"<a class="moss-footnote-backref" href="#fnref-1" role="doc-backlink" aria-label="Back to reference 1">↩︎</a>"##,
2805 example_markdown: "Text[^1].\n\n[^1]: The note.",
2806 status: Status::Emerging,
2807 since: "1",
2808 description: "The return arrow at the end of a note, linking back to the marker that sent the reader there. One per marker, so a note referenced twice ends with two arrows. A note nobody referenced has none. The arrow carries VARIATION SELECTOR-15 (`︎`) so mobile Chrome renders it as plain text rather than a coloured emoji.",
2809 },
2810];
2811
2812const INTERNAL_CLASSES: &[&str] = &[
2818 "moss-apply",
2819 "moss-apply-form",
2820 "moss-apply-matters",
2821 "moss-apply-hp",
2822 "moss-apply-status",
2823 "moss-apply-helper",
2824];
2825
2826impl ComponentEntry {
2827 pub fn is_public(&self) -> bool {
2834 self.status != Status::Retired && !INTERNAL_CLASSES.contains(&self.class)
2835 }
2836}
2837
2838pub fn retired_class_names() -> impl Iterator<Item = &'static str> {
2844 COMPONENTS.iter()
2845 .filter(|e| e.status == Status::Retired)
2846 .map(|e| e.class)
2847}
2848
2849#[cfg(test)]
2850mod tests {
2851 use super::*;
2852
2853 #[test]
2859 fn every_internal_class_has_a_components_entry() {
2860 let component_classes: std::collections::HashSet<&'static str> =
2861 COMPONENTS.iter().map(|e| e.class).collect();
2862 for &internal in INTERNAL_CLASSES {
2863 assert!(
2864 component_classes.contains(internal),
2865 "INTERNAL_CLASSES entry '{}' has no matching entry in COMPONENTS — \
2866 add a ComponentEntry for it or remove it from INTERNAL_CLASSES",
2867 internal
2868 );
2869 }
2870 }
2871}