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 "container",
125 "nav-content",
126 "font-anchor",
127 "font-trigger",
128 "cover-thumb",
129 "media-item",
130 "lightbox-content",
131 "lightbox-image",
132 "lightbox-video",
133 "lightbox-iframe",
134 "lightbox-title",
135 "lightbox-article-link",
136 "lightbox-close",
137 "lightbox-next",
138 "lightbox-prev",
139 "comments-toggle",
140 "comment-list",
141 "comment-item",
142 "comment-replies",
143 "comment-reply-btn",
144 "active",
152 "comment-author",
153 "comment-body",
154 "comment-date",
155 "comment-field",
156 "comment-form",
157 "comment-form-meta",
158 "comment-form-slot",
159 "comment-form-status",
160 "comment-form-submit",
161 "comment-header",
162 "comment-source-link",
163 "comments-chevron",
164 "comments-icon",
165 "empty",
166 "font-pill",
167 "has-sidebar-layout",
168 "latest-sidebar",
169 "lightbox",
170 "lightbox-caption",
171 "lightbox-nav",
172 "link-preview",
173 "link-preview-domain",
174 "link-preview-title",
175 "main-content",
176 "media-grid",
177 "media-overlay",
178 "media-title",
179 "minimal",
180 "page-wrapper",
181 "review-biblio",
182 "review-colophon",
183 "review-colophon-details",
184 "review-colophon-identity",
185 "review-colophon-subtitle",
186 "review-colophon-title",
187 "review-community-rating",
188 "review-links",
189 "review-rating",
190 "review-sep",
191 "sidebar-more",
192 "size-std",
193 "title",
194 "wikilink",
195];
196
197pub fn is_unprefixed_legacy(class: &str) -> bool {
202 class.starts_with("callout-") || UNPREFIXED_LEGACY_CLASSES.contains(&class)
203}
204
205pub const COMPONENTS: &[ComponentEntry] = &[
210 ComponentEntry {
211 class: "moss-cards",
212 kind: "container",
213 parent: "",
214 data_attrs: &[
215 DataAttr {
216 name: "data-layout",
217 values: &["grid", "list", "minimal"],
218 default: "grid",
219 description: "Card layout density. Grid: 2-3 cols with covers. List: single column with side covers. Minimal: text-only with year groupings.",
220 },
221 DataAttr {
222 name: "data-density",
223 values: &["default", "compact"],
224 default: "default",
225 description: "Vertical spacing density.",
226 },
227 DataAttr {
228 name: "data-list-axis",
229 values: &["date", "weight", "title"],
230 default: "title",
231 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).",
232 },
233 DataAttr {
234 name: "data-list-has-covers",
235 values: &[""],
236 default: "",
237 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.",
238 },
239 ],
240 example_html: r#"<div class="moss-cards-container">
241 <div class="moss-cards" data-layout="grid" data-list-axis="date" data-list-has-covers>
242 <a class="moss-card" href="...">...</a>
243 <a class="moss-card" href="...">...</a>
244 </div>
245</div>"#,
246 example_markdown: "",
247 status: Status::Confirmed,
248 since: "1",
249 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.",
250 },
251 ComponentEntry {
252 class: "moss-cards-container",
253 kind: "container",
254 parent: "",
255 data_attrs: &[],
256 example_html: r#"<div class="moss-cards-container">
257 <div class="moss-cards" data-layout="grid">...</div>
258</div>"#,
259 example_markdown: "",
260 status: Status::Confirmed,
261 since: "1",
262 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.",
263 },
264 ComponentEntry {
265 class: "moss-summary-layout",
266 kind: "container",
267 parent: "moss-cards",
268 data_attrs: &[],
269 example_html: r#"<div class="moss-cards" data-layout="list">...</div>"#,
270 example_markdown: "",
271 status: Status::Retired,
272 since: "1",
273 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.",
274 },
275 ComponentEntry {
281 class: "moss-cards-grid",
282 kind: "container",
283 parent: "",
284 data_attrs: &[],
285 example_html: r#"<div class="moss-cards-grid">
286 <a class="moss-card-grid" href="...">...</a>
287</div>"#,
288 example_markdown: "",
289 status: Status::Retired,
290 since: "0",
291 description: "Retired in Phase 1c — collapsed into `.moss-cards[data-layout=grid]`.",
292 },
293 ComponentEntry {
294 class: "moss-cards-list",
295 kind: "container",
296 parent: "",
297 data_attrs: &[],
298 example_html: r#"<div class="moss-cards-list">
299 <a class="moss-card-list" href="...">...</a>
300</div>"#,
301 example_markdown: "",
302 status: Status::Retired,
303 since: "0",
304 description: "Retired in Phase 1c — collapsed into `.moss-cards[data-layout=list]`.",
305 },
306 ComponentEntry {
307 class: "moss-cards-minimal-year-group",
308 kind: "container",
309 parent: "",
310 data_attrs: &[],
311 example_html: r#"<section class="moss-cards-minimal-year-group">
312 <h3>2024</h3>
313 <div class="moss-card-minimal">...</div>
314</section>"#,
315 example_markdown: "",
316 status: Status::Confirmed,
317 since: "0",
318 description: "Year-grouped section in minimal card layout (e.g. blog index). Modifier `--summary` collapses past years.",
319 },
320 ComponentEntry {
321 class: "minimal",
322 kind: "instance",
323 parent: "moss-cards-minimal-year-group",
324 data_attrs: &[],
325 example_html: r#"<section class="moss-cards-minimal-year-group minimal">…</section>"#,
326 example_markdown: "",
327 status: Status::Confirmed,
328 since: "0",
329 description: "Bare co-class on a minimal year group, alongside the prefixed name. Retained because themes in the wild key on it.",
330 },
331 ComponentEntry {
332 class: "moss-cards-minimal-year-group--summary",
333 kind: "container",
334 parent: "moss-cards-minimal-year-group",
335 data_attrs: &[],
336 example_html: r#"<section class="moss-cards-minimal-year-group moss-cards-minimal-year-group--summary">...</section>"#,
337 example_markdown: "",
338 status: Status::Confirmed,
339 since: "0",
340 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).",
341 },
342 ComponentEntry {
343 class: "moss-card",
344 kind: "instance",
345 parent: "moss-cards",
346 data_attrs: &[
347 DataAttr {
348 name: "data-linkblog",
349 values: &[],
350 default: "",
351 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>`).",
352 },
353 DataAttr {
354 name: "data-cover-color",
355 values: &[],
356 default: "",
357 description: "Presence flag: emitted IFF the cover-colour ladder produced a colour, which arrives alongside it as `--moss-cover-color` in the element's `style`. Absent for a card with no cover, and for an image cover whose file moss could not read.",
358 },
359 ],
360 example_html: r#"<a class="moss-card" href="...">...</a>"#,
361 example_markdown: "",
362 status: Status::Confirmed,
363 since: "1",
364 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]`).",
365 },
366 ComponentEntry {
367 class: "moss-card-cover",
368 kind: "instance",
369 parent: "moss-card",
370 data_attrs: &[],
371 example_html: r#"<div class="moss-card-cover"><img src="..." /></div>"#,
372 example_markdown: "",
373 status: Status::Confirmed,
374 since: "1",
375 description: "Cover media slot inside `.moss-card`. Gets `.moss-card-no-cover` modifier when no image is present.",
376 },
377 ComponentEntry {
378 class: "moss-card-no-cover",
379 kind: "instance",
380 parent: "moss-card",
381 data_attrs: &[],
382 example_html: r#"<div class="moss-card-cover moss-card-no-cover"></div>"#,
383 example_markdown: "",
384 status: Status::Confirmed,
385 since: "1",
386 description: "Modifier applied to `.moss-card-cover` when no cover media is available.",
387 },
388 ComponentEntry {
389 class: "moss-card-content",
390 kind: "instance",
391 parent: "moss-card",
392 data_attrs: &[],
393 example_html: r#"<div class="moss-card-content">...</div>"#,
394 example_markdown: "",
395 status: Status::Confirmed,
396 since: "1",
397 description: "Text content slot inside a grid-layout `.moss-card` (kicker + title + meta).",
398 },
399 ComponentEntry {
400 class: "moss-card-row",
401 kind: "instance",
402 parent: "moss-card",
403 data_attrs: &[],
404 example_html: r#"<div class="moss-card-row">...</div>"#,
405 example_markdown: "",
406 status: Status::Confirmed,
407 since: "1",
408 description: "Row wrapper inside a list-layout `.moss-card` holding body + cover side-by-side.",
409 },
410 ComponentEntry {
411 class: "moss-card-body",
412 kind: "instance",
413 parent: "moss-card",
414 data_attrs: &[],
415 example_html: r#"<div class="moss-card-body">...</div>"#,
416 example_markdown: "",
417 status: Status::Confirmed,
418 since: "1",
419 description: "Text body slot of a list-layout `.moss-card`.",
420 },
421 ComponentEntry {
422 class: "moss-card-head",
423 kind: "instance",
424 parent: "moss-card",
425 data_attrs: &[],
426 example_html: r#"<div class="moss-card-head">...</div>"#,
427 example_markdown: "",
428 status: Status::Confirmed,
429 since: "1",
430 description: "Header row of a `.moss-card-body` (title + kicker + meta).",
431 },
432 ComponentEntry {
433 class: "moss-card-title",
434 kind: "instance",
435 parent: "moss-card",
436 data_attrs: &[],
437 example_html: r#"<h3 class="moss-card-title">Page title</h3>"#,
438 example_markdown: "",
439 status: Status::Confirmed,
440 since: "1",
441 description: "Title inside `.moss-card`.",
442 },
443 ComponentEntry {
444 class: "moss-card-meta",
445 kind: "instance",
446 parent: "moss-card",
447 data_attrs: &[],
448 example_html: r#"<div class="moss-card-meta">2024-01-15</div>"#,
449 example_markdown: "",
450 status: Status::Confirmed,
451 since: "1",
452 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.",
453 },
454 ComponentEntry {
455 class: "moss-card-kicker",
456 kind: "instance",
457 parent: "moss-card",
458 data_attrs: &[],
459 example_html: r#"<span class="moss-card-kicker">Category</span>"#,
460 example_markdown: "",
461 status: Status::Confirmed,
462 since: "1",
463 description: "Eyebrow / overline above the title inside `.moss-card`.",
464 },
465 ComponentEntry {
466 class: "moss-card-permalink",
467 kind: "instance",
468 parent: "moss-card-kicker",
469 data_attrs: &[],
470 example_html: r#"<a class="moss-card-permalink" href="/posts/foo/" title="Permalink to 'Title'">★</a>"#,
471 example_markdown: "",
472 status: Status::Emerging,
473 since: "1",
474 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`.",
475 },
476 ComponentEntry {
477 class: "moss-card-title-link",
478 kind: "instance",
479 parent: "moss-card-head",
480 data_attrs: &[],
481 example_html: r#"<a class="moss-card-title-link" href="https://outlet.example/article"><h3 class="moss-card-title">Article Title</h3></a>"#,
482 example_markdown: "",
483 status: Status::Emerging,
484 since: "1",
485 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`).",
486 },
487 ComponentEntry {
488 class: "moss-card-cover-link",
489 kind: "instance",
490 parent: "moss-card-row",
491 data_attrs: &[],
492 example_html: r#"<a class="moss-card-cover-link" href="https://outlet.example/article"><div class="moss-card-cover">...</div></a>"#,
493 example_markdown: "",
494 status: Status::Emerging,
495 since: "1",
496 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.",
497 },
498 ComponentEntry {
499 class: "moss-card-description-link",
500 kind: "instance",
501 parent: "moss-card-body",
502 data_attrs: &[],
503 example_html: r#"<a class="moss-card-description-link" href="https://outlet.example/article"><p class="moss-card-description">…</p></a>"#,
504 example_markdown: "",
505 status: Status::Emerging,
506 since: "1",
507 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.",
508 },
509 ComponentEntry {
510 class: "moss-card-description",
511 kind: "instance",
512 parent: "moss-card",
513 data_attrs: &[],
514 example_html: r#"<p class="moss-card-description">Excerpt...</p>"#,
515 example_markdown: "",
516 status: Status::Confirmed,
517 since: "1",
518 description: "Excerpt / description paragraph inside a `.moss-card` — below the title in both grid- and list-layout cards.",
519 },
520 ComponentEntry {
521 class: "moss-card-count",
522 kind: "instance",
523 parent: "moss-card",
524 data_attrs: &[],
525 example_html: r#"<div class="moss-card-count">4 articles</div>"#,
526 example_markdown: "",
527 status: Status::Confirmed,
528 since: "1",
529 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.",
530 },
531 ComponentEntry {
532 class: "moss-embed-more",
533 kind: "instance",
534 parent: "moss-cards-container",
535 data_attrs: &[],
536 example_html: r#"<p class="moss-embed-more"><a href="/news/">More →</a></p>"#,
537 example_markdown: "",
538 status: Status::Confirmed,
539 since: "1",
540 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).",
541 },
542 ComponentEntry {
543 class: "moss-card-grid",
544 kind: "instance",
545 parent: "moss-cards-grid",
546 data_attrs: &[],
547 example_html: r#"<a class="moss-card-grid" href="...">...</a>"#,
548 example_markdown: "",
549 status: Status::Retired,
550 since: "0",
551 description: "Retired in Phase 1c — collapsed into `.moss-card` (with parent `.moss-cards[data-layout=grid]`).",
552 },
553 ComponentEntry {
554 class: "moss-card-grid-cover",
555 kind: "instance",
556 parent: "moss-card-grid",
557 data_attrs: &[],
558 example_html: r#"<div class="moss-card-grid-cover"><img src="..." /></div>"#,
559 example_markdown: "",
560 status: Status::Retired,
561 since: "0",
562 description: "Retired in Phase 1c — collapsed into `.moss-card-cover`.",
563 },
564 ComponentEntry {
565 class: "moss-card-grid-no-cover",
566 kind: "instance",
567 parent: "moss-card-grid",
568 data_attrs: &[],
569 example_html: r#"<div class="moss-card-grid-cover moss-card-grid-no-cover"></div>"#,
570 example_markdown: "",
571 status: Status::Retired,
572 since: "0",
573 description: "Retired in Phase 1c — collapsed into `.moss-card-no-cover`.",
574 },
575 ComponentEntry {
576 class: "moss-card-grid-content",
577 kind: "instance",
578 parent: "moss-card-grid",
579 data_attrs: &[],
580 example_html: r#"<div class="moss-card-grid-content">...</div>"#,
581 example_markdown: "",
582 status: Status::Retired,
583 since: "0",
584 description: "Retired in Phase 1c — collapsed into `.moss-card-content`.",
585 },
586 ComponentEntry {
587 class: "moss-card-grid-kicker",
588 kind: "instance",
589 parent: "moss-card-grid",
590 data_attrs: &[],
591 example_html: r#"<span class="moss-card-grid-kicker">Category</span>"#,
592 example_markdown: "",
593 status: Status::Retired,
594 since: "0",
595 description: "Retired in Phase 1c — collapsed into `.moss-card-kicker`.",
596 },
597 ComponentEntry {
598 class: "moss-card-grid-title",
599 kind: "instance",
600 parent: "moss-card-grid",
601 data_attrs: &[],
602 example_html: r#"<h3 class="moss-card-grid-title">Page title</h3>"#,
603 example_markdown: "",
604 status: Status::Retired,
605 since: "0",
606 description: "Retired in Phase 1c — collapsed into `.moss-card-title`.",
607 },
608 ComponentEntry {
609 class: "moss-card-grid-meta",
610 kind: "instance",
611 parent: "moss-card-grid",
612 data_attrs: &[],
613 example_html: r#"<div class="moss-card-grid-meta">2024-01-15</div>"#,
614 example_markdown: "",
615 status: Status::Retired,
616 since: "0",
617 description: "Retired in Phase 1c — collapsed into `.moss-card-meta`.",
618 },
619 ComponentEntry {
620 class: "moss-card-list",
621 kind: "instance",
622 parent: "moss-cards-list",
623 data_attrs: &[],
624 example_html: r#"<a class="moss-card-list" href="...">...</a>"#,
625 example_markdown: "",
626 status: Status::Retired,
627 since: "0",
628 description: "Retired in Phase 1c — collapsed into `.moss-card` (with parent `.moss-cards[data-layout=list]`).",
629 },
630 ComponentEntry {
631 class: "moss-card-list-row",
632 kind: "instance",
633 parent: "moss-card-list",
634 data_attrs: &[],
635 example_html: r#"<div class="moss-card-list-row">...</div>"#,
636 example_markdown: "",
637 status: Status::Retired,
638 since: "0",
639 description: "Retired in Phase 1c — collapsed into `.moss-card-row`.",
640 },
641 ComponentEntry {
642 class: "moss-card-list-cover",
643 kind: "instance",
644 parent: "moss-card-list",
645 data_attrs: &[],
646 example_html: r#"<div class="moss-card-list-cover"><img src="..." /></div>"#,
647 example_markdown: "",
648 status: Status::Retired,
649 since: "0",
650 description: "Retired in Phase 1c — collapsed into `.moss-card-cover`.",
651 },
652 ComponentEntry {
653 class: "moss-card-list-body",
654 kind: "instance",
655 parent: "moss-card-list",
656 data_attrs: &[],
657 example_html: r#"<div class="moss-card-list-body">...</div>"#,
658 example_markdown: "",
659 status: Status::Retired,
660 since: "0",
661 description: "Retired in Phase 1c — collapsed into `.moss-card-body`.",
662 },
663 ComponentEntry {
664 class: "moss-card-list-head",
665 kind: "instance",
666 parent: "moss-card-list",
667 data_attrs: &[],
668 example_html: r#"<div class="moss-card-list-head">...</div>"#,
669 example_markdown: "",
670 status: Status::Retired,
671 since: "0",
672 description: "Retired in Phase 1c — collapsed into `.moss-card-head`.",
673 },
674 ComponentEntry {
675 class: "moss-card-list-kicker",
676 kind: "instance",
677 parent: "moss-card-list",
678 data_attrs: &[],
679 example_html: r#"<span class="moss-card-list-kicker">Category</span>"#,
680 example_markdown: "",
681 status: Status::Retired,
682 since: "0",
683 description: "Retired in Phase 1c — collapsed into `.moss-card-kicker`.",
684 },
685 ComponentEntry {
686 class: "moss-card-list-title",
687 kind: "instance",
688 parent: "moss-card-list",
689 data_attrs: &[],
690 example_html: r#"<h3 class="moss-card-list-title">Page title</h3>"#,
691 example_markdown: "",
692 status: Status::Retired,
693 since: "0",
694 description: "Retired in Phase 1c — collapsed into `.moss-card-title`.",
695 },
696 ComponentEntry {
697 class: "moss-card-list-meta",
698 kind: "instance",
699 parent: "moss-card-list",
700 data_attrs: &[],
701 example_html: r#"<div class="moss-card-list-meta">2024-01-15</div>"#,
702 example_markdown: "",
703 status: Status::Retired,
704 since: "0",
705 description: "Retired in Phase 1c — collapsed into `.moss-card-meta`.",
706 },
707 ComponentEntry {
708 class: "moss-card-list-description",
709 kind: "instance",
710 parent: "moss-card-list",
711 data_attrs: &[],
712 example_html: r#"<p class="moss-card-list-description">Excerpt...</p>"#,
713 example_markdown: "",
714 status: Status::Retired,
715 since: "0",
716 description: "Retired in Phase 1c — collapsed into `.moss-card-description`.",
717 },
718 ComponentEntry {
719 class: "moss-card-minimal",
720 kind: "instance",
721 parent: "moss-cards-minimal-year-group",
722 data_attrs: &[],
723 example_html: r#"<div class="moss-card-minimal">
724 <a class="moss-prefix-link" href="...">...</a>
725</div>"#,
726 example_markdown: "",
727 status: Status::Retired,
728 since: "0",
729 description: "Retired in Phase 1c — collapsed into `.moss-card` (with parent `.moss-cards[data-layout=minimal]`).",
730 },
731 ComponentEntry {
732 class: "moss-folder-item",
733 kind: "instance",
734 parent: "moss-cards-minimal-year-group",
735 data_attrs: &[],
736 example_html: r#"<div class="moss-card-minimal moss-folder-item">
737 <a class="moss-prefix-link moss-folder-link" href="...">...</a>
738 <p class="moss-folder-description">...</p>
739</div>"#,
740 example_markdown: "",
741 status: Status::Confirmed,
742 since: "0",
743 description: "Modifier on `.moss-card-minimal` for folder-type entries in minimal listings.",
744 },
745 ComponentEntry {
746 class: "moss-folder-title",
747 kind: "instance",
748 parent: "moss-folder-item",
749 data_attrs: &[],
750 example_html: r#"<span class="moss-folder-title">Folder name</span>"#,
751 example_markdown: "",
752 status: Status::Confirmed,
753 since: "0",
754 description: "Title text of a folder entry in minimal listings.",
755 },
756 ComponentEntry {
757 class: "moss-folder-description",
758 kind: "instance",
759 parent: "moss-folder-item",
760 data_attrs: &[],
761 example_html: r#"<p class="moss-folder-description">Description...</p>"#,
762 example_markdown: "",
763 status: Status::Confirmed,
764 since: "0",
765 description: "Description paragraph of a folder entry in minimal listings.",
766 },
767 ComponentEntry {
768 class: "moss-folder-link",
769 kind: "instance",
770 parent: "moss-folder-item",
771 data_attrs: &[],
772 example_html: r#"<a class="moss-prefix-link moss-folder-link" href="...">...</a>"#,
773 example_markdown: "",
774 status: Status::Confirmed,
775 since: "0",
776 description: "Modifier on `.moss-prefix-link` for folder-type links in minimal listings.",
777 },
778 ComponentEntry {
782 class: "moss-prefix-link",
783 kind: "instance",
784 parent: "moss-card-minimal",
785 data_attrs: &[],
786 example_html: r#"<a class="moss-prefix-link" href="...">
787 <span class="moss-prefix-link-prefix">2024-01-15</span>
788 <span class="moss-prefix-link-title">Page title</span>
789</a>"#,
790 example_markdown: "",
791 status: Status::Emerging,
792 since: "0",
793 description: "Link with a prefix span (date or icon) and a title span. Used inside minimal cards.",
794 },
795 ComponentEntry {
796 class: "moss-prefix-link-prefix",
797 kind: "instance",
798 parent: "moss-prefix-link",
799 data_attrs: &[],
800 example_html: r#"<span class="moss-prefix-link-prefix">2024-01-15</span>"#,
801 example_markdown: "",
802 status: Status::Emerging,
803 since: "0",
804 description: "Prefix slot of a prefix-link (typically a date).",
805 },
806 ComponentEntry {
807 class: "moss-prefix-link-title",
808 kind: "instance",
809 parent: "moss-prefix-link",
810 data_attrs: &[],
811 example_html: r#"<span class="moss-prefix-link-title">Page title</span>"#,
812 example_markdown: "",
813 status: Status::Emerging,
814 since: "0",
815 description: "Title slot of a prefix-link.",
816 },
817 ComponentEntry {
818 class: "title",
819 kind: "instance",
820 parent: "moss-prefix-link-title",
821 data_attrs: &[],
822 example_html: r#"<span class="moss-prefix-link-title title">Article Title</span>"#,
823 example_markdown: "",
824 status: Status::Confirmed,
825 since: "0",
826 description: "Bare co-class beside `moss-prefix-link-title`, the counterpart of `date` on the prefix span. Both predate the prefixed names and are still emitted for themes that key on them.",
827 },
828 ComponentEntry {
829 class: "moss-prefix-link-suffix",
830 kind: "instance",
831 parent: "moss-prefix-link",
832 data_attrs: &[],
833 example_html: r#"<span class="moss-prefix-link-suffix">→</span>"#,
834 example_markdown: "",
835 status: Status::Emerging,
836 since: "0",
837 description: "Optional trailing slot of a prefix-link.",
838 },
839 ComponentEntry {
845 class: "moss-callout",
846 kind: "standalone",
847 parent: "",
848 data_attrs: &[],
849 example_html: r#"<div class="moss-callout callout" data-type="note">
850 <div class="callout-title">Note</div>
851 <div class="callout-content">Body...</div>
852</div>"#,
853 example_markdown: "> [!note]\n> Body...",
854 status: Status::Confirmed,
855 since: "0",
856 description: "Obsidian-style callout. The Obsidian-compat `.callout` class is co-emitted; type lives on `data-type` (v1).",
857 },
858 ComponentEntry {
859 class: "callout",
860 kind: "standalone",
861 parent: "",
862 data_attrs: &[
863 DataAttr {
864 name: "data-type",
865 values: &["note", "info", "tip", "warning", "pending"],
866 default: "note",
867 description: "v1 callout type. Theme authors target `.callout[data-type=...]` to style by variant.",
868 },
869 ],
870 example_html: r#"<div class="moss-callout callout" data-type="note">...</div>"#,
871 example_markdown: "",
872 status: Status::Confirmed,
873 since: "0",
874 description: "Obsidian-compat class co-emitted on every callout for theme parity. Type lives on `data-type` (v1).",
875 },
876 ComponentEntry {
877 class: "callout-title",
878 kind: "instance",
879 parent: "moss-callout",
880 data_attrs: &[],
881 example_html: r#"<div class="callout-title">Note</div>"#,
882 example_markdown: "",
883 status: Status::Confirmed,
884 since: "0",
885 description: "Title row of a callout.",
886 },
887 ComponentEntry {
888 class: "callout-content",
889 kind: "instance",
890 parent: "moss-callout",
891 data_attrs: &[],
892 example_html: r#"<div class="callout-content">Body...</div>"#,
893 example_markdown: "",
894 status: Status::Confirmed,
895 since: "0",
896 description: "Body container of a callout.",
897 },
898 ComponentEntry {
899 class: "callout-note",
900 kind: "instance",
901 parent: "moss-callout",
902 data_attrs: &[],
903 example_html: r#"<div class="moss-callout callout callout-note">...</div>"#,
904 example_markdown: "> [!note]\n> Body",
905 status: Status::Retired,
906 since: "0",
907 description: "Retired in Phase 1c — type lives on `.callout[data-type=note]`.",
908 },
909 ComponentEntry {
910 class: "callout-info",
911 kind: "instance",
912 parent: "moss-callout",
913 data_attrs: &[],
914 example_html: r#"<div class="moss-callout callout callout-info">...</div>"#,
915 example_markdown: "> [!info]\n> Body",
916 status: Status::Retired,
917 since: "0",
918 description: "Retired in Phase 1c — type lives on `.callout[data-type=info]`.",
919 },
920 ComponentEntry {
921 class: "callout-tip",
922 kind: "instance",
923 parent: "moss-callout",
924 data_attrs: &[],
925 example_html: r#"<div class="moss-callout callout callout-tip">...</div>"#,
926 example_markdown: "> [!tip]\n> Body",
927 status: Status::Retired,
928 since: "0",
929 description: "Retired in Phase 1c — type lives on `.callout[data-type=tip]`.",
930 },
931 ComponentEntry {
932 class: "callout-warning",
933 kind: "instance",
934 parent: "moss-callout",
935 data_attrs: &[],
936 example_html: r#"<div class="moss-callout callout callout-warning">...</div>"#,
937 example_markdown: "> [!warning]\n> Body",
938 status: Status::Retired,
939 since: "0",
940 description: "Retired in Phase 1c — type lives on `.callout[data-type=warning]`.",
941 },
942 ComponentEntry {
943 class: "callout-pending",
944 kind: "instance",
945 parent: "moss-callout",
946 data_attrs: &[],
947 example_html: r#"<div class="moss-callout callout callout-pending">...</div>"#,
948 example_markdown: "> [!pending]\n> Body",
949 status: Status::Retired,
950 since: "0",
951 description: "Retired in Phase 1c — type lives on `.callout[data-type=pending]`.",
952 },
953 ComponentEntry {
958 class: "moss-embed",
959 kind: "standalone",
960 parent: "",
961 data_attrs: &[
962 DataAttr {
963 name: "data-type",
964 values: &["audio", "video", "pdf", "notebook", "table", "iframe", "3d"],
965 default: "",
966 description: "v1 embed kind. Set on the embed element. Theme authors target `.moss-embed[data-type=...]`.",
967 },
968 DataAttr {
969 name: "data-loop",
970 values: &[],
971 default: "",
972 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.",
973 },
974 DataAttr {
975 name: "data-width",
976 values: &["body", "wide", "page", "screen"],
977 default: "body",
978 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9.",
979 },
980 DataAttr {
981 name: "data-provider",
982 values: &["youtube", "vimeo", "codepen"],
983 default: "",
984 description: "Identifies the embed provider for external URL embeds. Absent for generic iframes and local HTML embeds.",
985 },
986 ],
987 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>"#,
988 example_markdown: "![[clip.mp4|loop]]",
989 status: Status::Confirmed,
990 since: "0",
991 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.",
992 },
993 ComponentEntry {
994 class: "moss-embed-pending",
995 kind: "instance",
996 parent: "moss-embed",
997 data_attrs: &[],
998 example_html: r#"<div class="moss-embed moss-embed-pending">Downloading data.csv…</div>"#,
999 example_markdown: "",
1000 status: Status::Confirmed,
1001 since: "0",
1002 description: "Co-class on `.moss-embed` while the embedded file is still coming down from iCloud Drive. moss neither waits for the download nor bakes in an error box: it asks for the file, says plainly that it is still arriving, and lets the watcher rebuild the page when it lands. The default CSS does not style this — it is a hook for themes that want to mark the placeholder as provisional.",
1003 },
1004 ComponentEntry {
1005 class: "moss-embed-audio",
1006 kind: "instance",
1007 parent: "moss-embed",
1008 data_attrs: &[],
1009 example_html: r#"<div class="moss-embed moss-embed-audio"><audio controls src="..."></audio></div>"#,
1010 example_markdown: "![[track.mp3]]",
1011 status: Status::Retired,
1012 since: "0",
1013 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=audio]`.",
1014 },
1015 ComponentEntry {
1016 class: "moss-embed-video",
1017 kind: "instance",
1018 parent: "moss-embed",
1019 data_attrs: &[],
1020 example_html: r#"<div class="moss-embed moss-embed-video"><video controls src="..."></video></div>"#,
1021 example_markdown: "![[clip.mp4]]",
1022 status: Status::Retired,
1023 since: "0",
1024 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=video]`.",
1025 },
1026 ComponentEntry {
1027 class: "moss-embed-pdf",
1028 kind: "instance",
1029 parent: "moss-embed",
1030 data_attrs: &[],
1031 example_html: r#"<div class="moss-embed moss-embed-pdf"><iframe src="..."></iframe></div>"#,
1032 example_markdown: "![[paper.pdf]]",
1033 status: Status::Retired,
1034 since: "0",
1035 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=pdf]`.",
1036 },
1037 ComponentEntry {
1038 class: "moss-embed-iframe",
1039 kind: "instance",
1040 parent: "moss-embed",
1041 data_attrs: &[],
1042 example_html: r#"<div class="moss-embed moss-embed-iframe"><iframe src="..."></iframe></div>"#,
1043 example_markdown: "![[page.html]]",
1044 status: Status::Retired,
1045 since: "0",
1046 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=iframe]`.",
1047 },
1048 ComponentEntry {
1049 class: "moss-embed-notebook",
1050 kind: "instance",
1051 parent: "moss-embed",
1052 data_attrs: &[],
1053 example_html: r#"<div class="moss-embed moss-embed-notebook">...</div>"#,
1054 example_markdown: "![[analysis.ipynb]]",
1055 status: Status::Retired,
1056 since: "0",
1057 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=notebook]`.",
1058 },
1059 ComponentEntry {
1060 class: "moss-embed-ipynb",
1061 kind: "instance",
1062 parent: "moss-embed",
1063 data_attrs: &[],
1064 example_html: r#"<div class="moss-embed moss-embed-ipynb">...</div>"#,
1065 example_markdown: "",
1066 status: Status::Emerging,
1067 since: "0",
1068 description: "Alias of `.moss-embed-notebook`; consolidation pending.",
1069 },
1070 ComponentEntry {
1071 class: "moss-embed-table",
1072 kind: "instance",
1073 parent: "moss-embed",
1074 data_attrs: &[],
1075 example_html: r#"<div class="moss-embed moss-embed-table"><table>...</table></div>"#,
1076 example_markdown: "![[data.csv]]",
1077 status: Status::Retired,
1078 since: "0",
1079 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=table]`.",
1080 },
1081 ComponentEntry {
1082 class: "moss-embed-3d",
1083 kind: "instance",
1084 parent: "moss-embed",
1085 data_attrs: &[],
1086 example_html: r#"<div class="moss-embed moss-embed-3d">...</div>"#,
1087 example_markdown: "![[model.glb]]",
1088 status: Status::Retired,
1089 since: "0",
1090 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=3d]`.",
1091 },
1092 ComponentEntry {
1093 class: "moss-embed-error",
1094 kind: "instance",
1095 parent: "moss-embed",
1096 data_attrs: &[],
1097 example_html: r#"<div class="moss-embed moss-embed-error">File not found: ...</div>"#,
1098 example_markdown: "",
1099 status: Status::Confirmed,
1100 since: "0",
1101 description: "Error state for embeds whose target cannot be resolved.",
1102 },
1103 ComponentEntry {
1104 class: "moss-embed-missing",
1105 kind: "instance",
1106 parent: "moss-embed",
1107 data_attrs: &[],
1108 example_html: r#"<div class="moss-embed-missing">Folder not found: journal</div>"#,
1109 example_markdown: "",
1110 status: Status::Confirmed,
1111 since: "1",
1112 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.",
1113 },
1114 ComponentEntry {
1118 class: "moss-hero",
1119 kind: "standalone",
1120 parent: "",
1121 data_attrs: &[
1122 DataAttr {
1123 name: "data-width",
1124 values: &["body", "wide", "page", "screen"],
1125 default: "body",
1126 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.",
1127 },
1128 DataAttr {
1129 name: "data-slides",
1130 values: &["2", "3", "4", "5", "6"],
1131 default: "",
1132 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.",
1133 },
1134 DataAttr {
1135 name: "data-hero-tone",
1136 values: &["light"],
1137 default: "",
1138 description: "Marks a hero whose image is pale enough (scan-cached dominant colour above 0.4 relative luminance) that white overlay text cannot clear 4.5:1 over it. site.css responds by removing the legibility scrim and setting the overlay text DARK — a pale image needs no scrim to carry dark type, and darkening it hard enough to carry white type (this rule used to reach 0.78 black) greys out the picture the author chose. Applies to the two overlaid layouts; mobile-stacked text sits on `--moss-cover-color` and stays white. Emitted only when the hero also carries overlay text. Absent = mid-tone or dark image, no overlay, or an unparseable colour — all keep white type over the default ramp.",
1139 },
1140 DataAttr {
1141 name: "data-mobile",
1142 values: &["overlay"],
1143 default: "",
1144 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\"]`.",
1145 },
1146 DataAttr {
1147 name: "data-captioned",
1148 values: &[""],
1149 default: "",
1150 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.",
1151 },
1152 ],
1153 example_html: r#"<section class="moss-hero" data-width="page">
1154 <div class="moss-hero-content">...</div>
1155</section>"#,
1156 example_markdown: ":::hero {image=cover.jpg}\n:::\n\n:::hero {image=cover.jpg full mobile=overlay}\n# Title over the image\n:::\n",
1157 status: Status::Confirmed,
1158 since: "0",
1159 description: "Hero banner section at the top of a page (cover image + title). v1 adds `data-width` for author-controlled sizing.",
1160 },
1161 ComponentEntry {
1162 class: "moss-hero-content",
1163 kind: "instance",
1164 parent: "moss-hero",
1165 data_attrs: &[],
1166 example_html: r#"<div class="moss-hero-content">...</div>"#,
1167 example_markdown: "",
1168 status: Status::Confirmed,
1169 since: "0",
1170 description: "Text content slot inside `.moss-hero` — text laid ON the image. For text ABOUT the image, see `.moss-hero-caption`.",
1171 },
1172 ComponentEntry {
1173 class: "moss-hero-caption",
1174 kind: "standalone",
1175 parent: "",
1176 data_attrs: &[],
1177 example_html: r#"<p class="moss-hero-caption">封面:基輔米迦勒修道院門口的陣亡將士紀念牆(拍攝:糜緒洋)</p>"#,
1178 example_markdown: ":::hero {image=cover.jpg caption=\"Cover: the memorial wall (photo: A. Photographer)\"}\n:::\n",
1179 status: Status::Confirmed,
1180 since: "0",
1181 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.",
1182 },
1183 ComponentEntry {
1184 class: "moss-hero-slides",
1185 kind: "instance",
1186 parent: "moss-hero",
1187 data_attrs: &[],
1188 example_html: r#"<div class="moss-hero-slides"><div class="moss-hero-slide"><img src="portrait-1.jpg" alt="" /></div></div>"#,
1189 example_markdown: "",
1190 status: Status::Confirmed,
1191 since: "0",
1192 description: "Wrapper holding the `.moss-hero-slide` images of a multi-image hero; the CSS ambient crossfade cycles one slide visible at a time.",
1193 },
1194 ComponentEntry {
1195 class: "moss-hero-slide",
1196 kind: "instance",
1197 parent: "moss-hero",
1198 data_attrs: &[],
1199 example_html: r#"<div class="moss-hero-slide"><img src="portrait-1.jpg" alt="" /></div>"#,
1200 example_markdown: ":::hero
1201![[portrait-1.jpg]]
1202![[portrait-2.jpg]]
1203# Title
1204:::
1205",
1206 status: Status::Confirmed,
1207 since: "0",
1208 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.",
1209 },
1210 ComponentEntry {
1211 class: "moss-image",
1212 kind: "standalone",
1213 parent: "",
1214 data_attrs: &[
1215 DataAttr {
1216 name: "data-aspect",
1217 values: &["portrait", "square", "auto"],
1218 default: "auto",
1219 description: "v1 image aspect-ratio hint. Theme authors target `.moss-image[data-aspect=...]`. Emitter wiring lands in a follow-up.",
1220 },
1221 DataAttr {
1222 name: "data-width",
1223 values: &["body", "wide", "page", "screen"],
1224 default: "body",
1225 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9.",
1226 },
1227 ],
1228 example_html: r#"<figure class="moss-image" style="width:55%"><img src="..." alt="..." /></figure>"#,
1229 example_markdown: "",
1230 status: Status::Confirmed,
1231 since: "0",
1232 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.",
1233 },
1234 ComponentEntry {
1235 class: "moss-align-left",
1236 kind: "standalone",
1237 parent: "",
1238 data_attrs: &[],
1239 example_html: r#"<img src="..." alt="..." class="moss-align-left" />"#,
1240 example_markdown: "![[photo.jpg|align-left]]",
1241 status: Status::Confirmed,
1242 since: "0",
1243 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.",
1244 },
1245 ComponentEntry {
1246 class: "moss-align-right",
1247 kind: "standalone",
1248 parent: "",
1249 data_attrs: &[],
1250 example_html: r#"<img src="..." alt="..." class="moss-align-right" />"#,
1251 example_markdown: "![[photo.jpg|align-right]]",
1252 status: Status::Confirmed,
1253 since: "0",
1254 description: "Floats an image to the right of body text (editorial runaround). Symmetric counterpart to `.moss-align-left`. Mirrors WordPress's `alignright` convention.",
1255 },
1256 ComponentEntry {
1257 class: "moss-article-title",
1258 kind: "instance",
1259 parent: "",
1260 data_attrs: &[],
1261 example_html: r#"<h1 class="moss-article-title">Title</h1>"#,
1262 example_markdown: "",
1263 status: Status::Emerging,
1264 since: "0",
1265 description: "Article-page H1 title emitted from frontmatter.",
1266 },
1267 ComponentEntry {
1268 class: "moss-heading-anchor",
1269 kind: "instance",
1270 parent: "",
1271 data_attrs: &[],
1272 example_html: r##"<h2 id="setup">Setup<a class="moss-heading-anchor" href="#setup" aria-label="Permalink to this section"></a></h2>"##,
1273 example_markdown: "## Setup",
1274 status: Status::Emerging,
1275 since: "1",
1276 description: "Clickable permalink appended inside every author-written body heading that carries a slug id; links to the heading's `#`-fragment. The element is EMPTY — the `#` a reader sees is drawn by `site.css` as `::after` content, so selecting a heading never copies it. Not emitted for a display title: the auto-injected `moss-article-title` H1, a `:::hero` overlay heading, and a `:::grid` cell heading all get none.",
1277 },
1278 ComponentEntry {
1282 class: "moss-grid",
1283 kind: "container",
1284 parent: "",
1285 data_attrs: &[
1286 DataAttr {
1287 name: "data-width",
1288 values: &["body", "wide", "page", "screen"],
1289 default: "body",
1290 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9.",
1291 },
1292 DataAttr {
1293 name: "data-columns",
1294 values: &["1", "2", "3", "4"],
1295 default: "",
1296 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.",
1297 },
1298 ],
1299 example_html: r#"<div class="moss-grid" data-width="wide">
1300 <div class="moss-grid-card">...</div>
1301</div>"#,
1302 example_markdown: ":::grid {cols=2}\nLeft cell\n+++\nRight cell\n:::\n",
1303 status: Status::Confirmed,
1304 since: "0",
1305 description: "Generic grid container (used by profiles, link previews, etc.). Modifier classes: `profiles`, `featured`, `no-cards`. v1 adds `data-width` (P9).",
1306 },
1307 ComponentEntry {
1308 class: "moss-grid-card",
1309 kind: "instance",
1310 parent: "moss-grid",
1311 data_attrs: &[
1312 DataAttr {
1313 name: "data-kind",
1314 values: &["link", "friend", "card"],
1315 default: "card",
1316 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.",
1317 },
1318 DataAttr {
1319 name: "data-cover-color",
1320 values: &[],
1321 default: "",
1322 description: "Presence flag: emitted IFF the cell's first block is an image, alone in a paragraph or in a figure. The image's dominant colour arrives alongside it as `--moss-cover-color` in the element's `style`. moss paints nothing with it — the attribute exists so a theme can give a hand-built cell (cover, then the author's own text) the same colour band a collection card gets.",
1323 },
1324 ],
1325 example_html: r#"<a class="moss-grid-card" data-kind="link" href="...">...</a>"#,
1326 example_markdown: "",
1327 status: Status::Confirmed,
1328 since: "0",
1329 description: "Card instance inside `.moss-grid`. Today emits sibling classes `link-card` / `friend-card` / `no-cards`; v1 collapses to `data-kind`.",
1330 },
1331 ComponentEntry {
1332 class: "link-preview",
1333 kind: "instance",
1334 parent: "moss-grid-card",
1335 data_attrs: &[],
1336 example_html: r#"<a href="…" class="moss-grid-card link-preview" target="_blank" rel="noopener">…</a>"#,
1337 example_markdown: "",
1338 status: Status::Confirmed,
1339 since: "0",
1340 description: "Co-class on a grid card that links out to an external page, alongside `moss-grid-card`. Marks the card as a preview of somewhere else rather than of a page on this site.",
1341 },
1342 ComponentEntry {
1343 class: "link-preview-title",
1344 kind: "instance",
1345 parent: "link-preview",
1346 data_attrs: &[],
1347 example_html: r#"<span class="link-preview-title">…</span>"#,
1348 example_markdown: "",
1349 status: Status::Confirmed,
1350 since: "0",
1351 description: "Title of the linked page inside a link preview.",
1352 },
1353 ComponentEntry {
1354 class: "link-preview-domain",
1355 kind: "instance",
1356 parent: "link-preview",
1357 data_attrs: &[],
1358 example_html: r#"<span class="link-preview-domain">example.com</span>"#,
1359 example_markdown: "",
1360 status: Status::Confirmed,
1361 since: "0",
1362 description: "Domain of the linked page, shown so a reader can see where the link goes before following it.",
1363 },
1364 ComponentEntry {
1365 class: "moss-gallery",
1366 kind: "container",
1367 parent: "",
1368 data_attrs: &[
1369 DataAttr {
1370 name: "data-width",
1371 values: &["body", "wide", "page", "screen"],
1372 default: "body",
1373 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9.",
1374 },
1375 DataAttr {
1376 name: "data-columns",
1377 values: &[],
1378 default: "",
1379 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.",
1380 },
1381 ],
1382 example_html: r#"<div class="moss-gallery" data-width="page">
1383 <div class="moss-gallery-item">...</div>
1384</div>"#,
1385 example_markdown: ":::gallery\nphoto.jpg\n:::\n",
1386 status: Status::Confirmed,
1387 since: "0",
1388 description: "Image gallery container. v1 adds `data-width` (P9).",
1389 },
1390 ComponentEntry {
1391 class: "moss-gallery-item",
1392 kind: "instance",
1393 parent: "moss-gallery",
1394 data_attrs: &[],
1395 example_html: r#"<div class="moss-gallery-item"><img src="..." /></div>"#,
1396 example_markdown: "",
1397 status: Status::Confirmed,
1398 since: "0",
1399 description: "Single image entry inside `.moss-gallery`.",
1400 },
1401 ComponentEntry {
1402 class: "moss-buttons",
1403 kind: "container",
1404 parent: "",
1405 data_attrs: &[
1406 DataAttr {
1407 name: "data-style",
1408 values: &["default", "inverted"],
1409 default: "default",
1410 description: "v1 button-row style. Theme authors target `.moss-buttons[data-style=...]`.",
1411 },
1412 ],
1413 example_html: r#"<div class="moss-buttons" data-style="inverted">
1414 <a class="moss-btn" href="...">Click</a>
1415</div>"#,
1416 example_markdown: ":::buttons\n[Get started](https://example.com)\n:::\n",
1417 status: Status::Confirmed,
1418 since: "0",
1419 description: "Container for a row of `.moss-btn` buttons. v1: the inverted variant is on `data-style=\"inverted\"`.",
1420 },
1421 ComponentEntry {
1425 class: "moss-btn",
1426 kind: "standalone",
1427 parent: "",
1428 data_attrs: &[
1429 DataAttr {
1430 name: "data-role",
1431 values: &["default", "primary", "secondary"],
1432 default: "default",
1433 description: "v1 button role. Theme authors target `.moss-btn[data-role=...]`.",
1434 },
1435 ],
1436 example_html: r#"<button class="moss-btn" data-role="primary">
1437 <span class="moss-btn__label">Submit</span>
1438</button>"#,
1439 example_markdown: "",
1440 status: Status::Confirmed,
1441 since: "0",
1442 description: "Generic button primitive. Role on `data-role` (v1).",
1443 },
1444 ComponentEntry {
1445 class: "moss-btn__label",
1446 kind: "instance",
1447 parent: "moss-btn",
1448 data_attrs: &[],
1449 example_html: r#"<span class="moss-btn__label">Submit</span>"#,
1450 example_markdown: "",
1451 status: Status::Confirmed,
1452 since: "0",
1453 description: "Label span inside `.moss-btn`.",
1454 },
1455 ComponentEntry {
1456 class: "moss-btn__check",
1457 kind: "instance",
1458 parent: "moss-btn",
1459 data_attrs: &[],
1460 example_html: r#"<span class="moss-btn__check">✓</span>"#,
1461 example_markdown: "",
1462 status: Status::Confirmed,
1463 since: "0",
1464 description: "Success checkmark slot inside `.moss-btn`.",
1465 },
1466 ComponentEntry {
1467 class: "moss-btn__spinner",
1468 kind: "instance",
1469 parent: "moss-btn",
1470 data_attrs: &[],
1471 example_html: r#"<span class="moss-btn__spinner"></span>"#,
1472 example_markdown: "",
1473 status: Status::Confirmed,
1474 since: "0",
1475 description: "Loading spinner slot inside `.moss-btn`.",
1476 },
1477 ComponentEntry {
1481 class: "moss-subscribe",
1482 kind: "standalone",
1483 parent: "",
1484 data_attrs: &[],
1485 example_html: r#"<div class="moss-subscribe">
1486 <form class="moss-subscribe-form">...</form>
1487</div>"#,
1488 example_markdown: ":::subscribe\n:::\n",
1489 status: Status::Confirmed,
1490 since: "0",
1491 description: "Newsletter subscribe block (auto-injected into footer when email channel configured).",
1492 },
1493 ComponentEntry {
1494 class: "moss-subscribe-script",
1495 kind: "instance",
1496 parent: "",
1497 data_attrs: &[],
1498 example_html: r#"<script class="moss-subscribe-script">/* subscribe form handler */</script>"#,
1499 example_markdown: "",
1500 status: Status::Confirmed,
1501 since: "0",
1502 description: "Marks the `<script>` moss injects into `<head>` when a page carries a subscribe form. It is a handle, not a styling hook — a theme or a test uses it to find (or suppress) moss's own subscribe behaviour without matching on script contents.",
1503 },
1504 ComponentEntry {
1505 class: "moss-subscribe-form",
1506 kind: "instance",
1507 parent: "moss-subscribe",
1508 data_attrs: &[
1509 DataAttr {
1510 name: "data-position",
1511 values: &["inline", "apply"],
1512 default: "inline",
1513 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).",
1514 },
1515 DataAttr {
1516 name: "data-button-override",
1517 values: &["true"],
1518 default: "true",
1519 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.",
1520 },
1521 DataAttr {
1522 name: "data-moss-hosted",
1523 values: &["true"],
1524 default: "true",
1525 description: "Marks moss-hosted (seta) forms hydrated by subscribe.ts. Absent on 3rd-party provider forms.",
1526 },
1527 DataAttr {
1528 name: "data-state",
1529 values: &["idle", "loading", "success", "error"],
1530 default: "idle",
1531 description: "Runtime submit state machine, driven by subscribe.ts. Emitted as `idle`; theme authors target `.moss-subscribe-form[data-state=...]`.",
1532 },
1533 DataAttr {
1534 name: "data-moss-pending-site",
1535 values: &["true"],
1536 default: "true",
1537 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.",
1538 },
1539 ],
1540 example_html: r#"<form class="moss-subscribe-form">...</form>"#,
1541 example_markdown: "",
1542 status: Status::Emerging,
1543 since: "0",
1544 description: "Form element inside `.moss-subscribe`.",
1545 },
1546 ComponentEntry {
1547 class: "moss-btn-slot",
1548 kind: "instance",
1549 parent: "moss-subscribe",
1550 data_attrs: &[],
1551 example_html: r#"<div class="moss-btn-slot"><button class="moss-btn">...</button></div>"#,
1552 example_markdown: "",
1553 status: Status::Emerging,
1554 since: "0",
1555 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.",
1556 },
1557 ComponentEntry {
1558 class: "moss-subscribe-status",
1559 kind: "instance",
1560 parent: "moss-subscribe",
1561 data_attrs: &[],
1562 example_html: r#"<div class="moss-subscribe-status">
1563 <span class="moss-subscribe-status__icon"></span>
1564 Subscribed!
1565</div>"#,
1566 example_markdown: "",
1567 status: Status::Emerging,
1568 since: "0",
1569 description: "Status message shown after submit (success/error).",
1570 },
1571 ComponentEntry {
1572 class: "moss-subscribe-status__icon",
1573 kind: "instance",
1574 parent: "moss-subscribe-status",
1575 data_attrs: &[],
1576 example_html: r#"<span class="moss-subscribe-status__icon"></span>"#,
1577 example_markdown: "",
1578 status: Status::Emerging,
1579 since: "0",
1580 description: "Icon slot inside `.moss-subscribe-status`.",
1581 },
1582 ComponentEntry {
1583 class: "moss-subscribe-landing",
1584 kind: "standalone",
1585 parent: "",
1586 data_attrs: &[],
1587 example_html: r#"<section class="moss-subscribe-landing">...</section>"#,
1588 example_markdown: "",
1589 status: Status::Emerging,
1590 since: "0",
1591 description: "Standalone subscribe landing page surface (larger variant).",
1592 },
1593 ComponentEntry {
1597 class: "moss-apply",
1598 kind: "standalone",
1599 parent: "",
1600 data_attrs: &[],
1601 example_html: r#"<div class="moss-apply" data-state="idle">
1602 <form class="moss-subscribe-form moss-apply-form">...</form>
1603</div>"#,
1604 example_markdown: ":::apply\n:::\n",
1605 status: Status::Emerging,
1606 since: "0",
1607 description: "Apply / membership-request form block (:::apply shortcode).",
1608 },
1609 ComponentEntry {
1610 class: "moss-apply-form",
1611 kind: "instance",
1612 parent: "moss-apply",
1613 data_attrs: &[
1614 DataAttr {
1615 name: "data-position",
1616 values: &["apply"],
1617 default: "apply",
1618 description: "Position variant; always `apply` for this form. Drives CSS layout in email.css.",
1619 },
1620 DataAttr {
1621 name: "data-revert",
1622 values: &["false"],
1623 default: "false",
1624 description: "When `false`, success is terminal (no auto-revert). subscribe.ts reads this.",
1625 },
1626 ],
1627 example_html: r#"<form class="moss-subscribe-form moss-apply-form" data-position="apply" data-revert="false">...</form>"#,
1628 example_markdown: "",
1629 status: Status::Emerging,
1630 since: "0",
1631 description: "Form element inside `.moss-apply`. Also carries `.moss-subscribe-form` so subscribe.ts hydrates it.",
1632 },
1633 ComponentEntry {
1634 class: "moss-apply-matters",
1635 kind: "instance",
1636 parent: "moss-apply",
1637 data_attrs: &[],
1638 example_html: r#"<input type="text" name="matters" class="moss-input moss-apply-matters">"#,
1639 example_markdown: "",
1640 status: Status::Emerging,
1641 since: "0",
1642 description: "Second apply-form input inside `.moss-apply-form` — a Matters username OR a one-line pitch (placeholder-only, no visible label).",
1643 },
1644 ComponentEntry {
1645 class: "moss-apply-hp",
1646 kind: "instance",
1647 parent: "moss-apply",
1648 data_attrs: &[],
1649 example_html: r#"<input type="text" name="website" class="moss-apply-hp" tabindex="-1" aria-hidden="true">"#,
1650 example_markdown: "",
1651 status: Status::Emerging,
1652 since: "0",
1653 description: "Honeypot field (off-screen) inside `.moss-apply-form`. Bots fill it; humans don't.",
1654 },
1655 ComponentEntry {
1656 class: "moss-apply-status",
1657 kind: "instance",
1658 parent: "moss-apply",
1659 data_attrs: &[],
1660 example_html: r#"<div class="moss-subscribe-status moss-apply-status" aria-live="polite">...</div>"#,
1661 example_markdown: "",
1662 status: Status::Emerging,
1663 since: "0",
1664 description: "Status region inside `.moss-apply-form` (also carries `.moss-subscribe-status`).",
1665 },
1666 ComponentEntry {
1667 class: "moss-apply-helper",
1668 kind: "instance",
1669 parent: "moss-apply",
1670 data_attrs: &[],
1671 example_html: r#"<p class="moss-apply-helper" id="moss-apply-email-help">用于获取邀请及免费托管服务</p>"#,
1672 example_markdown: "",
1673 status: Status::Emerging,
1674 since: "0",
1675 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.",
1676 },
1677 ComponentEntry {
1681 class: "moss-series-nav",
1682 kind: "standalone",
1683 parent: "",
1684 data_attrs: &[],
1685 example_html: r#"<nav class="moss-series-nav">
1686 <div class="moss-series-nav-links">...</div>
1687</nav>"#,
1688 example_markdown: "",
1689 status: Status::Confirmed,
1690 since: "0",
1691 description: "Series navigation bar (prev/next/collection) on series pages.",
1692 },
1693 ComponentEntry {
1694 class: "moss-series-nav-links",
1695 kind: "instance",
1696 parent: "moss-series-nav",
1697 data_attrs: &[],
1698 example_html: r#"<div class="moss-series-nav-links">...</div>"#,
1699 example_markdown: "",
1700 status: Status::Confirmed,
1701 since: "0",
1702 description: "Row holding prev/next links in series nav.",
1703 },
1704 ComponentEntry {
1705 class: "moss-series-nav-link",
1706 kind: "instance",
1707 parent: "moss-series-nav",
1708 data_attrs: &[],
1709 example_html: r#"<a class="moss-series-nav-link moss-series-nav-prev" href="...">...</a>"#,
1710 example_markdown: "",
1711 status: Status::Confirmed,
1712 since: "0",
1713 description: "Individual link inside series nav. Modifiers: `moss-series-nav-prev`, `moss-series-nav-next`, `empty` (placeholder).",
1714 },
1715 ComponentEntry {
1716 class: "empty",
1717 kind: "instance",
1718 parent: "moss-series-nav-link",
1719 data_attrs: &[],
1720 example_html: r#"<span class="moss-series-nav-link moss-series-nav-prev empty"></span>"#,
1721 example_markdown: "",
1722 status: Status::Confirmed,
1723 since: "0",
1724 description: "Co-class on a series-nav link with nowhere to go — the previous link on the first entry, the next link on the last. The element is still emitted so the pair keeps its layout.",
1725 },
1726 ComponentEntry {
1727 class: "moss-series-nav-prev",
1728 kind: "instance",
1729 parent: "moss-series-nav",
1730 data_attrs: &[],
1731 example_html: r#"<a class="moss-series-nav-link moss-series-nav-prev" href="...">...</a>"#,
1732 example_markdown: "",
1733 status: Status::Confirmed,
1734 since: "0",
1735 description: "Previous-page modifier on a series nav link.",
1736 },
1737 ComponentEntry {
1738 class: "moss-series-nav-next",
1739 kind: "instance",
1740 parent: "moss-series-nav",
1741 data_attrs: &[],
1742 example_html: r#"<a class="moss-series-nav-link moss-series-nav-next" href="...">...</a>"#,
1743 example_markdown: "",
1744 status: Status::Confirmed,
1745 since: "0",
1746 description: "Next-page modifier on a series nav link.",
1747 },
1748 ComponentEntry {
1749 class: "moss-series-nav-arrow",
1750 kind: "instance",
1751 parent: "moss-series-nav",
1752 data_attrs: &[],
1753 example_html: r#"<span class="moss-series-nav-arrow">→</span>"#,
1754 example_markdown: "",
1755 status: Status::Confirmed,
1756 since: "0",
1757 description: "Arrow glyph inside a series-nav link.",
1758 },
1759 ComponentEntry {
1760 class: "moss-series-nav-title",
1761 kind: "instance",
1762 parent: "moss-series-nav",
1763 data_attrs: &[],
1764 example_html: r#"<span class="moss-series-nav-title">Next page title</span>"#,
1765 example_markdown: "",
1766 status: Status::Confirmed,
1767 since: "0",
1768 description: "Title text of the destination page in a series-nav link.",
1769 },
1770 ComponentEntry {
1771 class: "moss-series-nav-collection",
1772 kind: "instance",
1773 parent: "moss-series-nav",
1774 data_attrs: &[],
1775 example_html: r#"<div class="moss-series-nav-collection">...</div>"#,
1776 example_markdown: "",
1777 status: Status::Confirmed,
1778 since: "0",
1779 description: "Collection-listing slot in series nav (sibling pages).",
1780 },
1781 ComponentEntry {
1782 class: "moss-series-nav-collection-row",
1783 kind: "instance",
1784 parent: "moss-series-nav-collection",
1785 data_attrs: &[],
1786 example_html: r#"<div class="moss-series-nav-collection-row">...</div>"#,
1787 example_markdown: "",
1788 status: Status::Confirmed,
1789 since: "0",
1790 description: "Row inside the collection listing of series nav.",
1791 },
1792 ComponentEntry {
1793 class: "moss-series-nav-position",
1794 kind: "instance",
1795 parent: "moss-series-nav-collection-row",
1796 data_attrs: &[],
1797 example_html: r#"<span class="moss-series-nav-position">2 of 3</span>"#,
1798 example_markdown: "",
1799 status: Status::Confirmed,
1800 since: "0",
1801 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.",
1802 },
1803 ComponentEntry {
1807 class: "moss-collection-cover",
1808 kind: "standalone",
1809 parent: "",
1810 data_attrs: &[],
1811 example_html: r#"<section class="moss-collection-cover">
1812 <div class="moss-collection-cover-row">...</div>
1813</section>"#,
1814 example_markdown: "",
1815 status: Status::Emerging,
1816 since: "0",
1817 description: "Header surface on a collection landing page.",
1818 },
1819 ComponentEntry {
1820 class: "moss-collection-cover-row",
1821 kind: "instance",
1822 parent: "moss-collection-cover",
1823 data_attrs: &[],
1824 example_html: r#"<div class="moss-collection-cover-row">...</div>"#,
1825 example_markdown: "",
1826 status: Status::Emerging,
1827 since: "0",
1828 description: "Row inside `.moss-collection-cover`.",
1829 },
1830 ComponentEntry {
1831 class: "moss-collection-cover-body",
1832 kind: "instance",
1833 parent: "moss-collection-cover",
1834 data_attrs: &[],
1835 example_html: r#"<div class="moss-collection-cover-body">...</div>"#,
1836 example_markdown: "",
1837 status: Status::Emerging,
1838 since: "0",
1839 description: "Body content slot inside `.moss-collection-cover`.",
1840 },
1841 ComponentEntry {
1845 class: "moss-input",
1846 kind: "standalone",
1847 parent: "",
1848 data_attrs: &[],
1849 example_html: r#"<input class="moss-input" type="email" />"#,
1850 example_markdown: "",
1851 status: Status::Confirmed,
1852 since: "0",
1853 description: "Generic form input primitive.",
1854 },
1855 ComponentEntry {
1856 class: "moss-field",
1857 kind: "container",
1858 parent: "",
1859 data_attrs: &[],
1860 example_html: r#"<div class="moss-field">
1861 <label class="moss-label">Email</label>
1862 <input class="moss-input" />
1863</div>"#,
1864 example_markdown: "",
1865 status: Status::Confirmed,
1866 since: "0",
1867 description: "Form field group (label + input). Modifier `--inline` for horizontal layout.",
1868 },
1869 ComponentEntry {
1870 class: "moss-label",
1871 kind: "instance",
1872 parent: "moss-field",
1873 data_attrs: &[],
1874 example_html: r#"<label class="moss-label">Email</label>"#,
1875 example_markdown: "",
1876 status: Status::Confirmed,
1877 since: "0",
1878 description: "Label primitive for `.moss-field`. Modifier `--small` for compact form.",
1879 },
1880 ComponentEntry {
1881 class: "moss-link",
1882 kind: "standalone",
1883 parent: "",
1884 data_attrs: &[],
1885 example_html: r#"<a class="moss-link" href="...">Click me</a>"#,
1886 example_markdown: "",
1887 status: Status::Confirmed,
1888 since: "0",
1889 description: "Inline-link primitive (resets `<button>` chrome too). Use `--subtle` for muted variant.",
1890 },
1891 ComponentEntry {
1892 class: "moss-field--inline",
1893 kind: "instance",
1894 parent: "moss-field",
1895 data_attrs: &[],
1896 example_html: r#"<div class="moss-field moss-field--inline">
1897 <label class="moss-label">Email</label>
1898 <input class="moss-input" />
1899</div>"#,
1900 example_markdown: "",
1901 status: Status::Confirmed,
1902 since: "0",
1903 description: "BEM modifier on `.moss-field` for horizontal label+input layout (used by settings UI primitives).",
1904 },
1905 ComponentEntry {
1906 class: "moss-label--small",
1907 kind: "instance",
1908 parent: "moss-label",
1909 data_attrs: &[],
1910 example_html: r#"<label class="moss-label moss-label--small">Compact label</label>"#,
1911 example_markdown: "",
1912 status: Status::Confirmed,
1913 since: "0",
1914 description: "BEM modifier on `.moss-label` for compact form (used by services settings rows).",
1915 },
1916 ComponentEntry {
1917 class: "moss-info-grid",
1918 kind: "container",
1919 parent: "",
1920 data_attrs: &[],
1921 example_html: r#"<div class="moss-info-grid">
1922 <div class="moss-field moss-field--inline">...</div>
1923 <div class="moss-field moss-field--inline">...</div>
1924</div>"#,
1925 example_markdown: "",
1926 status: Status::Emerging,
1927 since: "0",
1928 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.",
1929 },
1930 ComponentEntry {
1931 class: "moss-row",
1932 kind: "container",
1933 parent: "",
1934 data_attrs: &[],
1935 example_html: r#"<div class="moss-row">
1936 <div class="moss-field">...</div>
1937 <div class="moss-field">...</div>
1938</div>"#,
1939 example_markdown: "",
1940 status: Status::Emerging,
1941 since: "0",
1942 description: "Horizontal flex row of equal-flex `.moss-field` children. Form-row layout helper shipped in the default theme.",
1943 },
1944 ComponentEntry {
1945 class: "moss-input-feedback",
1946 kind: "instance",
1947 parent: "moss-field",
1948 data_attrs: &[],
1949 example_html: r#"<span class="moss-input-feedback">Saving…</span>"#,
1950 example_markdown: "",
1951 status: Status::Emerging,
1952 since: "0",
1953 description: "Auto-save status hint slot under `.moss-field`. Three state modifiers: `--success`, `--error`, `--fade-out`.",
1954 },
1955 ComponentEntry {
1956 class: "moss-input-feedback--success",
1957 kind: "instance",
1958 parent: "moss-input-feedback",
1959 data_attrs: &[],
1960 example_html: r#"<span class="moss-input-feedback moss-input-feedback--success">Saved</span>"#,
1961 example_markdown: "",
1962 status: Status::Emerging,
1963 since: "0",
1964 description: "Success state modifier on `.moss-input-feedback`.",
1965 },
1966 ComponentEntry {
1967 class: "moss-input-feedback--error",
1968 kind: "instance",
1969 parent: "moss-input-feedback",
1970 data_attrs: &[],
1971 example_html: r#"<span class="moss-input-feedback moss-input-feedback--error">Failed to save</span>"#,
1972 example_markdown: "",
1973 status: Status::Emerging,
1974 since: "0",
1975 description: "Error state modifier on `.moss-input-feedback`.",
1976 },
1977 ComponentEntry {
1978 class: "moss-input-feedback--fade-out",
1979 kind: "instance",
1980 parent: "moss-input-feedback",
1981 data_attrs: &[],
1982 example_html: r#"<span class="moss-input-feedback moss-input-feedback--success moss-input-feedback--fade-out">Saved</span>"#,
1983 example_markdown: "",
1984 status: Status::Emerging,
1985 since: "0",
1986 description: "Transient fade-out modifier on `.moss-input-feedback` (applied after a success message to dismiss it).",
1987 },
1988 ComponentEntry {
1992 class: "moss-comments",
1993 kind: "standalone",
1994 parent: "",
1995 data_attrs: &[],
1996 example_html: r#"<section class="moss-comments">...</section>"#,
1997 example_markdown: "",
1998 status: Status::Confirmed,
1999 since: "0",
2000 description: "Comments surface (per-site SQLite backend or Artalk legacy).",
2001 },
2002 ComponentEntry {
2003 class: "comment-form-slot",
2004 kind: "instance",
2005 parent: "moss-comments",
2006 data_attrs: &[],
2007 example_html: r#"<div class="comment-form-slot" id="default-form-slot">…</div>"#,
2008 example_markdown: "",
2009 status: Status::Confirmed,
2010 since: "0",
2011 description: "Placeholder the comment form is rendered into. Kept distinct from the form itself so a reply form can be moved between slots without re-rendering the form.",
2012 },
2013 ComponentEntry {
2014 class: "comment-form",
2015 kind: "instance",
2016 parent: "comment-form-slot",
2017 data_attrs: &[],
2018 example_html: r#"<form class="comment-form" data-state="idle" id="moss-comment-form">…</form>"#,
2019 example_markdown: "",
2020 status: Status::Confirmed,
2021 since: "0",
2022 description: "The comment submission form. `data-state` carries the submission lifecycle, so a theme can style submitting and error states without watching the network.",
2023 },
2024 ComponentEntry {
2025 class: "comment-form-meta",
2026 kind: "instance",
2027 parent: "comment-form",
2028 data_attrs: &[],
2029 example_html: r#"<div class="comment-form-meta"><input class="comment-field" name="name"></div>"#,
2030 example_markdown: "",
2031 status: Status::Confirmed,
2032 since: "0",
2033 description: "Row holding the commenter identity fields (name, and email where configured), as distinct from the comment body textarea.",
2034 },
2035 ComponentEntry {
2036 class: "comment-form-status",
2037 kind: "instance",
2038 parent: "comment-form",
2039 data_attrs: &[],
2040 example_html: r#"<div class="comment-form-status" id="moss-comment-status" aria-live="assertive" aria-atomic="true"></div>"#,
2041 example_markdown: "",
2042 status: Status::Confirmed,
2043 since: "0",
2044 description: "Live region announcing the result of a comment submission. an assertive live region is deliberate: the reader has just acted and is waiting on the answer.",
2045 },
2046 ComponentEntry {
2047 class: "comment-form-submit",
2048 kind: "instance",
2049 parent: "comment-form",
2050 data_attrs: &[],
2051 example_html: r#"<button type="submit" class="moss-btn comment-form-submit" aria-busy="false">…</button>"#,
2052 example_markdown: "",
2053 status: Status::Confirmed,
2054 since: "0",
2055 description: "Co-class on the comment form's submit button, alongside `moss-btn`. `aria-busy` tracks the in-flight submission.",
2056 },
2057 ComponentEntry {
2058 class: "comment-field",
2059 kind: "instance",
2060 parent: "comment-form-meta",
2061 data_attrs: &[],
2062 example_html: r#"<input type="text" name="name" class="comment-field" autocomplete="name" required>"#,
2063 example_markdown: "",
2064 status: Status::Confirmed,
2065 since: "0",
2066 description: "A text input inside the comment form. Applied to every field so a theme styles them once rather than per name.",
2067 },
2068 ComponentEntry {
2069 class: "moss-service-inactive",
2070 kind: "instance",
2071 parent: "",
2072 data_attrs: &[],
2073 example_html: r#"<section class="moss-comments moss-service-inactive">...</section>"#,
2074 example_markdown: "",
2075 status: Status::Confirmed,
2076 since: "0",
2077 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.",
2078 },
2079 ComponentEntry {
2083 class: "moss-preview-popup",
2084 kind: "chrome",
2085 parent: "",
2086 data_attrs: &[],
2087 example_html: r#"<div class="moss-preview-popup" role="tooltip" aria-live="polite">
2088 <strong class="moss-preview-title">...</strong>
2089 <p class="moss-preview-desc">...</p>
2090 <p class="moss-preview-text">...</p>
2091</div>"#,
2092 example_markdown: "",
2093 status: Status::Confirmed,
2094 since: "0",
2095 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.",
2096 },
2097 ComponentEntry {
2098 class: "moss-preview-title",
2099 kind: "instance",
2100 parent: "moss-preview-popup",
2101 data_attrs: &[],
2102 example_html: r#"<strong class="moss-preview-title">Article title</strong>"#,
2103 example_markdown: "",
2104 status: Status::Confirmed,
2105 since: "0",
2106 description: "Title slot inside `.moss-preview-popup`.",
2107 },
2108 ComponentEntry {
2109 class: "moss-preview-desc",
2110 kind: "instance",
2111 parent: "moss-preview-popup",
2112 data_attrs: &[],
2113 example_html: r#"<p class="moss-preview-desc">Short description</p>"#,
2114 example_markdown: "",
2115 status: Status::Confirmed,
2116 since: "0",
2117 description: "Description slot inside `.moss-preview-popup` (from frontmatter `description`).",
2118 },
2119 ComponentEntry {
2120 class: "moss-preview-text",
2121 kind: "instance",
2122 parent: "moss-preview-popup",
2123 data_attrs: &[],
2124 example_html: r#"<p class="moss-preview-text">Excerpt of the linked article…</p>"#,
2125 example_markdown: "",
2126 status: Status::Confirmed,
2127 since: "0",
2128 description: "Excerpt slot inside `.moss-preview-popup` (auto-extracted from the linked article body).",
2129 },
2130 ComponentEntry {
2131 class: "moss-skip-link",
2132 kind: "chrome",
2133 parent: "",
2134 data_attrs: &[],
2135 example_html: r##"<a class="moss-skip-link" href="#main-content">Skip to content</a>"##,
2136 example_markdown: "",
2137 status: Status::Confirmed,
2138 since: "0",
2139 description: "First focusable element in `<body>`, before the nav island and header. Visually hidden until it receives keyboard focus, then jumps to `<main id=\"main-content\">` (WCAG 2.4.1). Themes may restyle it but should keep it off-screen at rest and visible on `:focus`.",
2140 },
2141 ComponentEntry {
2142 class: "moss-colophon",
2143 kind: "chrome",
2144 parent: "",
2145 data_attrs: &[],
2146 example_html: r#"<div class="moss-colophon">
2147 <a href="https://mosspub.com">
2148 <svg class="moss-colophon-icon"></svg>
2149 <span class="moss-colophon-label">Published with moss</span>
2150 </a>
2151</div>"#,
2152 example_markdown: "",
2153 status: Status::Confirmed,
2154 since: "0",
2155 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.",
2156 },
2157 ComponentEntry {
2158 class: "moss-colophon-icon",
2159 kind: "instance",
2160 parent: "moss-colophon",
2161 data_attrs: &[],
2162 example_html: r#"<svg class="moss-colophon-icon"></svg>"#,
2163 example_markdown: "",
2164 status: Status::Confirmed,
2165 since: "0",
2166 description: "The moss mark inside `.moss-colophon`. Decorative (`aria-hidden`) — `.moss-colophon-label` carries the accessible name.",
2167 },
2168 ComponentEntry {
2169 class: "moss-colophon-label",
2170 kind: "instance",
2171 parent: "moss-colophon",
2172 data_attrs: &[],
2173 example_html: r#"<span class="moss-colophon-label">Published with moss</span>"#,
2174 example_markdown: "",
2175 status: Status::Confirmed,
2176 since: "0",
2177 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.",
2178 },
2179 ComponentEntry {
2180 class: "moss-shell-frame",
2181 kind: "chrome",
2182 parent: "",
2183 data_attrs: &[],
2184 example_html: r#"<div class="moss-shell-frame">...</div>"#,
2185 example_markdown: "",
2186 status: Status::Emerging,
2187 since: "0",
2188 description: "App-shell frame surface (preview chrome).",
2189 },
2190 ComponentEntry {
2191 class: "moss-mobile-frame",
2192 kind: "chrome",
2193 parent: "moss-shell-frame",
2194 data_attrs: &[],
2195 example_html: r#"<html class="moss-shell-frame moss-mobile-frame">...</html>"#,
2196 example_markdown: "",
2197 status: Status::Emerging,
2198 since: "0",
2199 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.",
2200 },
2201 ComponentEntry {
2202 class: "main-nav",
2203 kind: "chrome",
2204 parent: "",
2205 data_attrs: &[],
2206 example_html: r#"<nav class="main-nav container">...</nav>"#,
2207 example_markdown: "",
2208 status: Status::Confirmed,
2209 since: "0",
2210 description: "Top site navigation bar. Legacy non-`moss-` prefix kept for theme parity.",
2211 },
2212 ComponentEntry {
2224 class: "date-line",
2225 kind: "chrome",
2226 parent: "",
2227 data_attrs: &[],
2228 example_html: r#"<div class="date-line"><span class="date">March 3, 2026</span><div class="font-anchor">...</div></div>"#,
2229 example_markdown: "",
2230 status: Status::Confirmed,
2231 since: "0",
2232 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`.",
2233 },
2234 ComponentEntry {
2235 class: "date",
2236 kind: "instance",
2237 parent: "date-line",
2238 data_attrs: &[],
2239 example_html: r#"<span class="date">March 3, 2026</span>"#,
2240 example_markdown: "",
2241 status: Status::Confirmed,
2242 since: "0",
2243 description: "The formatted publication date inside `.date-line`. Text is localized to the page's language.",
2244 },
2245 ComponentEntry {
2246 class: "moss-byline",
2247 kind: "container",
2248 parent: "",
2249 data_attrs: &[],
2250 example_html: r#"<div class="moss-byline"><div class="moss-byline-row">作者 糜緒洋</div><div class="moss-byline-row">編輯 謝丁</div></div>"#,
2251 example_markdown: "",
2252 status: Status::Confirmed,
2253 since: "1",
2254 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.",
2255 },
2256 ComponentEntry {
2257 class: "moss-byline-row",
2258 kind: "instance",
2259 parent: "moss-byline",
2260 data_attrs: &[],
2261 example_html: r#"<div class="moss-byline-row">首發媒體 <a href="https://theinitium.com/a">端傳媒</a></div>"#,
2262 example_markdown: "",
2263 status: Status::Confirmed,
2264 since: "1",
2265 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.",
2266 },
2267 ComponentEntry {
2268 class: "moss-article-colophon",
2269 kind: "container",
2270 parent: "",
2271 data_attrs: &[],
2272 example_html: r#"<div class="moss-article-colophon"><div class="moss-article-colophon-row">首發媒體 <a href="https://theinitium.com/a">端傳媒</a></div></div>"#,
2273 example_markdown: "",
2274 status: Status::Confirmed,
2275 since: "1",
2276 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.",
2277 },
2278 ComponentEntry {
2279 class: "moss-article-colophon-row",
2280 kind: "instance",
2281 parent: "moss-article-colophon",
2282 data_attrs: &[],
2283 example_html: r#"<div class="moss-article-colophon-row">封面 基輔米迦勒修道院門口的陣亡將士紀念牆(拍攝:糜緒洋)</div>"#,
2284 example_markdown: "",
2285 status: Status::Confirmed,
2286 since: "1",
2287 description: "One foot-credit line, rendered as inline markdown exactly like `.moss-byline-row`.",
2288 },
2289 ComponentEntry {
2290 class: "site-name",
2291 kind: "instance",
2292 parent: "main-nav",
2293 data_attrs: &[],
2294 example_html: r#"<a href="/" class="site-name">在場</a>"#,
2295 example_markdown: "",
2296 status: Status::Confirmed,
2297 since: "0",
2298 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`.",
2299 },
2300 ComponentEntry {
2301 class: "breadcrumb-segment",
2302 kind: "instance",
2303 parent: "main-nav",
2304 data_attrs: &[],
2305 example_html: r#"<a href="/awards/" class="breadcrumb-segment">獎項</a>"#,
2306 example_markdown: "",
2307 status: Status::Confirmed,
2308 since: "0",
2309 description: "One ancestor link in the nav-left breadcrumb trail, used in place of `.site-name` once the page is below the site root.",
2310 },
2311 ComponentEntry {
2312 class: "nav-icons",
2313 kind: "chrome",
2314 parent: "main-nav",
2315 data_attrs: &[],
2316 example_html: r#"<div class="nav-icons">...</div>"#,
2317 example_markdown: "",
2318 status: Status::Confirmed,
2319 since: "0",
2320 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.",
2321 },
2322 ComponentEntry {
2334 class: "nav-left",
2335 kind: "chrome",
2336 parent: "main-nav",
2337 data_attrs: &[],
2338 example_html: r#"<div class="nav-left"><a href="/" class="site-name">在場</a></div>"#,
2339 example_markdown: "",
2340 status: Status::Confirmed,
2341 since: "0",
2342 description: "Left group of the nav bar. Holds either `.site-name` or the breadcrumb trail, never both.",
2343 },
2344 ComponentEntry {
2345 class: "nav-right",
2346 kind: "chrome",
2347 parent: "main-nav",
2348 data_attrs: &[],
2349 example_html: r#"<div class="nav-right">…hamburger, .nav-links, .nav-icons…</div>"#,
2350 example_markdown: "",
2351 status: Status::Confirmed,
2352 since: "0",
2353 description: "Right group of the nav bar: the mobile menu button, the nav links, and the icon cluster, in that order.",
2354 },
2355 ComponentEntry {
2356 class: "nav-links",
2357 kind: "chrome",
2358 parent: "nav-right",
2359 data_attrs: &[],
2360 example_html: r#"<div class="nav-links"><a href="/about/" class="active">關於</a>…</div>"#,
2361 example_markdown: "",
2362 status: Status::Confirmed,
2363 since: "0",
2364 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.",
2365 },
2366 ComponentEntry {
2367 class: "site-logo",
2368 kind: "instance",
2369 parent: "site-name",
2370 data_attrs: &[],
2371 example_html: r#"<img class="site-logo" src="…" alt="" aria-hidden="true">"#,
2372 example_markdown: "",
2373 status: Status::Confirmed,
2374 since: "0",
2375 description: "Optional logo image inside the site-name link. Decorative by construction (`alt=\"\"` + `aria-hidden`), because the adjacent text already names the site.",
2376 },
2377 ComponentEntry {
2378 class: "breadcrumb-label",
2379 kind: "instance",
2380 parent: "nav-left",
2381 data_attrs: &[],
2382 example_html: r#"<span class="breadcrumb-label">獎項</span>"#,
2383 example_markdown: "",
2384 status: Status::Confirmed,
2385 since: "0",
2386 description: "The final, non-linked breadcrumb segment — the page you are on. `.breadcrumb-segment` is the linked form for ancestors.",
2387 },
2388 ComponentEntry {
2389 class: "breadcrumb-separator",
2390 kind: "instance",
2391 parent: "nav-left",
2392 data_attrs: &[],
2393 example_html: r#"<span class="breadcrumb-separator">/</span>"#,
2394 example_markdown: "",
2395 status: Status::Confirmed,
2396 since: "0",
2397 description: "The `/` between breadcrumb segments. Restyle or hide this rather than trying to remove it from the markup.",
2398 },
2399 ComponentEntry {
2400 class: "mobile-menu-button",
2401 kind: "chrome",
2402 parent: "nav-right",
2403 data_attrs: &[],
2404 example_html: r#"<button class="mobile-menu-button" aria-label="…"><svg>…</svg></button>"#,
2405 example_markdown: "",
2406 status: Status::Confirmed,
2407 since: "0",
2408 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.",
2409 },
2410 ComponentEntry {
2411 class: "nav-search-btn",
2412 kind: "instance",
2413 parent: "nav-icons",
2414 data_attrs: &[],
2415 example_html: r#"<button class="nav-search-btn" type="button" aria-label="…"><svg class="search-icon">…</svg></button>"#,
2416 example_markdown: "",
2417 status: Status::Confirmed,
2418 since: "0",
2419 description: "Search button in the nav icon cluster. Its glyph is `.search-icon`.",
2420 },
2421 ComponentEntry {
2422 class: "search-icon",
2423 kind: "instance",
2424 parent: "nav-search-btn",
2425 data_attrs: &[],
2426 example_html: r#"<svg class="search-icon" aria-hidden="true" width="1em" height="1em">…</svg>"#,
2427 example_markdown: "",
2428 status: Status::Confirmed,
2429 since: "0",
2430 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.",
2431 },
2432 ComponentEntry {
2433 class: "nav-theme-btn",
2434 kind: "instance",
2435 parent: "nav-icons",
2436 data_attrs: &[],
2437 example_html: r#"<button class="nav-theme-btn" type="button" aria-label="…"><svg class="theme-toggle-icon">…</svg></button>"#,
2438 example_markdown: "",
2439 status: Status::Confirmed,
2440 since: "0",
2441 description: "Light/dark toggle in the nav icon cluster. Its glyph is `.theme-toggle-icon`.",
2442 },
2443 ComponentEntry {
2444 class: "theme-toggle-icon",
2445 kind: "instance",
2446 parent: "nav-theme-btn",
2447 data_attrs: &[],
2448 example_html: r#"<svg class="theme-toggle-icon" aria-hidden="true" width="1em" height="1em">…</svg>"#,
2449 example_markdown: "",
2450 status: Status::Confirmed,
2451 since: "0",
2452 description: "The sun/moon glyph. One SVG whose clip path animates between states — restyle it, but do not expect two separate icons to swap.",
2453 },
2454 ComponentEntry {
2455 class: "nav-lang-toggle",
2456 kind: "chrome",
2457 parent: "nav-icons",
2458 data_attrs: &[],
2459 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>"#,
2460 example_markdown: "",
2461 status: Status::Confirmed,
2462 since: "0",
2463 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.",
2464 },
2465 ComponentEntry {
2466 class: "nav-lang-current",
2467 kind: "instance",
2468 parent: "nav-lang-toggle",
2469 data_attrs: &[],
2470 example_html: r#"<span class="nav-lang-current">繁</span>"#,
2471 example_markdown: "",
2472 status: Status::Confirmed,
2473 since: "0",
2474 description: "The edition being viewed, as inert text rather than a link — style the current-language affordance here.",
2475 },
2476 ComponentEntry {
2477 class: "nav-lang-link",
2478 kind: "instance",
2479 parent: "nav-lang-toggle",
2480 data_attrs: &[],
2481 example_html: r#"<a href="/en/" class="nav-lang-link" hreflang="en">EN</a>"#,
2482 example_markdown: "",
2483 status: Status::Confirmed,
2484 since: "0",
2485 description: "A link to another edition of the same page. Carries `hreflang`, so `[hreflang=\"en\"]` is a stable hook for per-language styling.",
2486 },
2487 ComponentEntry {
2495 class: "moss-nav-island",
2496 kind: "chrome",
2497 parent: "",
2498 data_attrs: &[DataAttr {
2499 name: "data-shown",
2500 values: &["false", "true"],
2501 default: "false",
2502 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.",
2503 }],
2504 example_html: r#"<div class="moss-nav-island" data-shown="true"><div class="moss-nav-island-bar">…</div></div>"#,
2505 example_markdown: "",
2506 status: Status::Emerging,
2507 since: "0",
2508 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`.",
2509 },
2510 ComponentEntry {
2511 class: "moss-nav-island-bar",
2512 kind: "instance",
2513 parent: "moss-nav-island",
2514 data_attrs: &[],
2515 example_html: r#"<div class="moss-nav-island-bar">…trail, actions, progress…</div>"#,
2516 example_markdown: "",
2517 status: Status::Emerging,
2518 since: "0",
2519 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.",
2520 },
2521 ComponentEntry {
2522 class: "moss-nav-island-trail",
2523 kind: "instance",
2524 parent: "moss-nav-island-bar",
2525 data_attrs: &[],
2526 example_html: r#"<nav class="moss-nav-island-trail" aria-label="Breadcrumb">…</nav>"#,
2527 example_markdown: "",
2528 status: Status::Emerging,
2529 since: "0",
2530 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.",
2531 },
2532 ComponentEntry {
2533 class: "moss-nav-island-current",
2534 kind: "instance",
2535 parent: "moss-nav-island-trail",
2536 data_attrs: &[],
2537 example_html: r#"<span class="breadcrumb-segment moss-nav-island-current" aria-current="page">末代女礦工</span>"#,
2538 example_markdown: "",
2539 status: Status::Emerging,
2540 since: "0",
2541 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.",
2542 },
2543 ComponentEntry {
2544 class: "moss-nav-island-more",
2545 kind: "instance",
2546 parent: "moss-nav-island-trail",
2547 data_attrs: &[],
2548 example_html: r#"<button class="moss-nav-island-more" aria-expanded="false">…</button>"#,
2549 example_markdown: "",
2550 status: Status::Emerging,
2551 since: "0",
2552 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.",
2553 },
2554 ComponentEntry {
2555 class: "moss-nav-island-actions",
2556 kind: "instance",
2557 parent: "moss-nav-island-bar",
2558 data_attrs: &[],
2559 example_html: r#"<span class="moss-nav-island-actions">…</span>"#,
2560 example_markdown: "",
2561 status: Status::Emerging,
2562 since: "0",
2563 description: "Button cluster at the island's end edge. Holds the sections button only — theme, language and search stay in the masthead.",
2564 },
2565 ComponentEntry {
2566 class: "moss-nav-island-sections",
2567 kind: "instance",
2568 parent: "moss-nav-island-actions",
2569 data_attrs: &[],
2570 example_html: r#"<button class="moss-nav-island-sections" aria-expanded="false"><svg>…</svg></button>"#,
2571 example_markdown: "",
2572 status: Status::Emerging,
2573 since: "0",
2574 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.",
2575 },
2576 ComponentEntry {
2577 class: "moss-nav-island-menu",
2578 kind: "instance",
2579 parent: "moss-nav-island",
2580 data_attrs: &[DataAttr {
2581 name: "data-island-menu",
2582 values: &["levels", "sections"],
2583 default: "levels",
2584 description: "Which of the two menus this is: the folded ancestor levels, or the page's sections.",
2585 }],
2586 example_html: r#"<div class="moss-nav-island-menu" data-island-menu="sections">…</div>"#,
2587 example_markdown: "",
2588 status: Status::Emerging,
2589 since: "0",
2590 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.",
2591 },
2592 ComponentEntry {
2593 class: "moss-breadcrumb-more",
2594 kind: "instance",
2595 parent: "",
2596 data_attrs: &[],
2597 example_html: r#"<button class="moss-breadcrumb-more" aria-expanded="false">…</button>"#,
2598 example_markdown: "",
2599 status: Status::Emerging,
2600 since: "0",
2601 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.",
2602 },
2603 ComponentEntry {
2604 class: "moss-breadcrumb-menu",
2605 kind: "instance",
2606 parent: "",
2607 data_attrs: &[],
2608 example_html: r#"<div class="moss-breadcrumb-menu" hidden>…</div>"#,
2609 example_markdown: "",
2610 status: Status::Emerging,
2611 since: "0",
2612 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`.",
2613 },
2614 ComponentEntry {
2615 class: "moss-nav-island-progress",
2616 kind: "instance",
2617 parent: "moss-nav-island-bar",
2618 data_attrs: &[],
2619 example_html: r#"<span class="moss-nav-island-progress"><span class="moss-nav-island-progress-fill"></span></span>"#,
2620 example_markdown: "",
2621 status: Status::Emerging,
2622 since: "0",
2623 description: "Reading-progress track along the island's own bottom edge — not a separate bar across the window. Currently measures document scroll.",
2624 },
2625 ComponentEntry {
2626 class: "moss-nav-island-progress-fill",
2627 kind: "instance",
2628 parent: "moss-nav-island-progress",
2629 data_attrs: &[],
2630 example_html: r#"<span class="moss-nav-island-progress-fill" style="width: 42%"></span>"#,
2631 example_markdown: "",
2632 status: Status::Emerging,
2633 since: "0",
2634 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.",
2635 },
2636 ComponentEntry {
2637 class: "footer-default",
2638 kind: "chrome",
2639 parent: "",
2640 data_attrs: &[],
2641 example_html: r#"<p class="footer-default"><a href="/rss.xml" class="footer-link" data-external>RSS</a>…</p>"#,
2642 example_markdown: "",
2643 status: Status::Confirmed,
2644 since: "0",
2645 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.",
2646 },
2647 ComponentEntry {
2648 class: "footer-link",
2649 kind: "instance",
2650 parent: "footer-default",
2651 data_attrs: &[],
2652 example_html: r#"<a href="/rss.xml" class="footer-link" data-external>RSS</a>"#,
2653 example_markdown: "",
2654 status: Status::Confirmed,
2655 since: "0",
2656 description: "One link in the generated footer row (RSS and similar). Off-site ones also carry `data-external`.",
2657 },
2658 ComponentEntry {
2659 class: "moss-child-section-divider",
2660 kind: "instance",
2661 parent: "",
2662 data_attrs: &[],
2663 example_html: r#"<hr class="moss-child-section-divider" />"#,
2664 example_markdown: "",
2665 status: Status::Emerging,
2666 since: "0",
2667 description: "Divider rule between auto-generated child sections.",
2668 },
2669 ComponentEntry {
2670 class: "moss-unknown-shortcode",
2676 kind: "standalone",
2677 parent: "",
2678 data_attrs: &[DataAttr {
2679 name: "data-name",
2680 values: &[],
2681 default: "",
2682 description: "The unrecognised shortcode name, as written by the author.",
2683 }],
2684 example_html: r#"<div class="moss-unknown-shortcode" data-name="foo">
2685
2686<p>body parsed as markdown</p>
2687
2688</div>"#,
2689 example_markdown: ":::foo\nbody parsed as markdown\n:::",
2690 status: Status::Confirmed,
2691 since: "0",
2692 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.",
2693 },
2694 ComponentEntry {
2698 class: "moss-hl-keyword",
2699 kind: "instance",
2700 parent: "",
2701 data_attrs: &[],
2702 example_html: r#"<span class="moss-hl-keyword">if</span>"#,
2703 example_markdown: "",
2704 status: Status::Emerging,
2705 since: "0",
2706 description: "Syntax-highlight token: keyword.",
2707 },
2708 ComponentEntry {
2709 class: "moss-hl-string",
2710 kind: "instance",
2711 parent: "",
2712 data_attrs: &[],
2713 example_html: r#"<span class="moss-hl-string">"hi"</span>"#,
2714 example_markdown: "",
2715 status: Status::Emerging,
2716 since: "0",
2717 description: "Syntax-highlight token: string literal.",
2718 },
2719 ComponentEntry {
2720 class: "moss-hl-comment",
2721 kind: "instance",
2722 parent: "",
2723 data_attrs: &[],
2724 example_html: r#"<span class="moss-hl-comment">// note</span>"#,
2725 example_markdown: "",
2726 status: Status::Emerging,
2727 since: "0",
2728 description: "Syntax-highlight token: comment.",
2729 },
2730 ComponentEntry {
2731 class: "moss-hl-function",
2732 kind: "instance",
2733 parent: "",
2734 data_attrs: &[],
2735 example_html: r#"<span class="moss-hl-function">render</span>"#,
2736 example_markdown: "",
2737 status: Status::Emerging,
2738 since: "0",
2739 description: "Syntax-highlight token: function name.",
2740 },
2741 ComponentEntry {
2742 class: "moss-hl-type",
2743 kind: "instance",
2744 parent: "",
2745 data_attrs: &[],
2746 example_html: r#"<span class="moss-hl-type">String</span>"#,
2747 example_markdown: "",
2748 status: Status::Emerging,
2749 since: "0",
2750 description: "Syntax-highlight token: type name.",
2751 },
2752 ComponentEntry {
2753 class: "moss-hl-number",
2754 kind: "instance",
2755 parent: "",
2756 data_attrs: &[],
2757 example_html: r#"<span class="moss-hl-number">42</span>"#,
2758 example_markdown: "",
2759 status: Status::Emerging,
2760 since: "0",
2761 description: "Syntax-highlight token: numeric literal.",
2762 },
2763 ComponentEntry {
2764 class: "moss-hl-operator",
2765 kind: "instance",
2766 parent: "",
2767 data_attrs: &[],
2768 example_html: r#"<span class="moss-hl-operator">+</span>"#,
2769 example_markdown: "",
2770 status: Status::Emerging,
2771 since: "0",
2772 description: "Syntax-highlight token: operator.",
2773 },
2774 ComponentEntry {
2775 class: "moss-hl-builtin",
2776 kind: "instance",
2777 parent: "",
2778 data_attrs: &[],
2779 example_html: r#"<span class="moss-hl-builtin">print</span>"#,
2780 example_markdown: "",
2781 status: Status::Emerging,
2782 since: "0",
2783 description: "Syntax-highlight token: builtin identifier.",
2784 },
2785 ComponentEntry {
2786 class: "moss-hl-tag",
2787 kind: "instance",
2788 parent: "",
2789 data_attrs: &[],
2790 example_html: r#"<span class="moss-hl-tag">div</span>"#,
2791 example_markdown: "",
2792 status: Status::Emerging,
2793 since: "0",
2794 description: "Syntax-highlight token: markup tag name.",
2795 },
2796 ComponentEntry {
2797 class: "moss-hl-attr",
2798 kind: "instance",
2799 parent: "",
2800 data_attrs: &[],
2801 example_html: r#"<span class="moss-hl-attr">class</span>"#,
2802 example_markdown: "",
2803 status: Status::Emerging,
2804 since: "0",
2805 description: "Syntax-highlight token: attribute name.",
2806 },
2807 ComponentEntry {
2808 class: "moss-hl-meta",
2809 kind: "instance",
2810 parent: "",
2811 data_attrs: &[],
2812 example_html: r#"<span class="moss-hl-meta">@derive</span>"#,
2813 example_markdown: "",
2814 status: Status::Emerging,
2815 since: "0",
2816 description: "Syntax-highlight token: meta/annotation.",
2817 },
2818 ComponentEntry {
2819 class: "moss-hl-addition-bg",
2820 kind: "instance",
2821 parent: "",
2822 data_attrs: &[],
2823 example_html: r#"<span class="moss-hl-addition-bg">+ added line</span>"#,
2824 example_markdown: "",
2825 status: Status::Emerging,
2826 since: "0",
2827 description: "Syntax-highlight diff token: added-line background.",
2828 },
2829 ComponentEntry {
2830 class: "moss-hl-deletion",
2831 kind: "instance",
2832 parent: "",
2833 data_attrs: &[],
2834 example_html: r#"<span class="moss-hl-deletion">- removed line</span>"#,
2835 example_markdown: "",
2836 status: Status::Emerging,
2837 since: "0",
2838 description: "Syntax-highlight diff token: removed-line text.",
2839 },
2840 ComponentEntry {
2841 class: "moss-hl-deletion-bg",
2842 kind: "instance",
2843 parent: "",
2844 data_attrs: &[],
2845 example_html: r#"<span class="moss-hl-deletion-bg">- removed line</span>"#,
2846 example_markdown: "",
2847 status: Status::Emerging,
2848 since: "0",
2849 description: "Syntax-highlight diff token: removed-line background.",
2850 },
2851 ComponentEntry {
2852 class: "moss-recent",
2853 kind: "container",
2854 parent: "",
2855 data_attrs: &[],
2856 example_html: r#"<ul class="moss-recent">
2857 <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>
2858</ul>"#,
2859 example_markdown: ":::recent {count=5 since=\"2026-01-01\"}\n:::\n",
2860 status: Status::Emerging,
2861 since: "0",
2862 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.",
2863 },
2864 ComponentEntry {
2865 class: "moss-recent__date",
2866 kind: "instance",
2867 parent: "moss-recent",
2868 data_attrs: &[],
2869 example_html: r#"<div class="moss-recent__date">2026-04-12</div>"#,
2870 example_markdown: "",
2871 status: Status::Emerging,
2872 since: "0",
2873 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.",
2874 },
2875 ComponentEntry {
2876 class: "moss-recent__desc",
2877 kind: "instance",
2878 parent: "moss-recent",
2879 data_attrs: &[],
2880 example_html: r#"<div class="moss-recent__desc">A walk through the garden.</div>"#,
2881 example_markdown: "",
2882 status: Status::Emerging,
2883 since: "0",
2884 description: "Per-entry description slot inside `.moss-recent` (BEM child). Sourced from frontmatter `description`; empty when unset.",
2885 },
2886 ComponentEntry {
2892 class: "moss-ambient-video",
2893 kind: "standalone",
2894 parent: "",
2895 data_attrs: &[
2896 DataAttr {
2897 name: "data-paused",
2898 values: &[],
2899 default: "",
2900 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.",
2901 },
2902 ],
2903 example_html: r#"<div class="moss-ambient-video">
2904 <video data-loop src="clip.mp4" autoplay muted loop playsinline preload="metadata"></video>
2905 <button class="moss-ambient-toggle" type="button" aria-label="Pause video">⏸</button>
2906</div>"#,
2907 example_markdown: "![[clip.mp4|loop]]",
2908 status: Status::Emerging,
2909 since: "1",
2910 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.",
2911 },
2912 ComponentEntry {
2913 class: "moss-ambient-toggle",
2914 kind: "instance",
2915 parent: "moss-ambient-video",
2916 data_attrs: &[],
2917 example_html: r#"<button class="moss-ambient-toggle" type="button" aria-label="Pause video">⏸</button>"#,
2918 example_markdown: "",
2919 status: Status::Emerging,
2920 since: "1",
2921 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).",
2922 },
2923 ComponentEntry {
2930 class: "moss-math",
2931 kind: "standalone",
2932 parent: "",
2933 data_attrs: &[
2934 DataAttr {
2935 name: "data-moss-math",
2936 values: &["inline", "display"],
2937 default: "inline",
2938 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.",
2939 },
2940 ],
2941 example_html: r#"<code class="moss-math" data-moss-math="inline">$E = mc^2$</code>"#,
2942 example_markdown: "Energy $E = mc^2$.",
2943 status: Status::Emerging,
2944 since: "1",
2945 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).",
2946 },
2947 ComponentEntry {
2948 class: "moss-math-scroll",
2949 kind: "container",
2950 parent: "",
2951 data_attrs: &[],
2952 example_html: r#"<div class="moss-math-scroll"><svg class="moss-math" data-moss-math="display">…</svg></div>"#,
2953 example_markdown: "$$E = mc^2$$",
2954 status: Status::Emerging,
2955 since: "1",
2956 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%`.",
2957 },
2958 ComponentEntry {
2959 class: "moss-table-scroll",
2960 kind: "container",
2961 parent: "",
2962 data_attrs: &[],
2963 example_html: r#"<div class="moss-table-scroll" tabindex="0">
2964 <table>…</table>
2965</div>"#,
2966 example_markdown: "| Name | Subs |\n| --- | --- |\n| a | 1,200 |",
2967 status: Status::Emerging,
2968 since: "1",
2969 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.",
2970 },
2971 ComponentEntry {
2972 class: "moss-col-right",
2973 kind: "instance",
2974 parent: "moss-table-scroll",
2975 data_attrs: &[],
2976 example_html: r#"<th class="moss-col-right">订阅数</th>
2977<td class="moss-col-right">1,457,776</td>"#,
2978 example_markdown: "| Subs |\n| --: |\n| 1,457,776 |",
2979 status: Status::Emerging,
2980 since: "1",
2981 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`.",
2982 },
2983 ComponentEntry {
2984 class: "moss-col-center",
2985 kind: "instance",
2986 parent: "moss-table-scroll",
2987 data_attrs: &[],
2988 example_html: r#"<th class="moss-col-center">Status</th>
2989<td class="moss-col-center">✓</td>"#,
2990 example_markdown: "| Status |\n| :-: |\n| ✓ |",
2991 status: Status::Emerging,
2992 since: "1",
2993 description: "Center-aligned table cell (`<th>`/`<td>`). Applied to a whole column the author center-aligned in GFM (`|:-:|`).",
2994 },
2995 ComponentEntry {
2996 class: "moss-search",
2997 kind: "chrome",
2998 parent: "",
2999 data_attrs: &[],
3000 example_html: r#"<div class="moss-search" id="moss-search" hidden>
3001 <div class="moss-search__backdrop"></div>
3002 <div class="moss-search__panel" role="dialog" aria-modal="true" aria-label="Search">
3003 <div class="moss-search__field"><input class="moss-search__input" role="combobox"></div>
3004 <div class="moss-search__progress" hidden></div>
3005 <div class="moss-search__seam"></div>
3006 <div class="moss-search__body">
3007 <p class="moss-search__status" role="status" hidden></p>
3008 <ul class="moss-search__results" role="listbox">
3009 <li class="moss-search__row">
3010 <a class="moss-search__link" role="option" href="/posts/foo/">
3011 <span class="moss-search__title">Title</span>
3012 <span class="moss-search__excerpt">…a <mark>match</mark>…</span>
3013 </a>
3014 </li>
3015 </ul>
3016 </div>
3017 </div>
3018</div>"#,
3019 example_markdown: "",
3020 status: Status::Emerging,
3021 since: "1",
3022 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.",
3023 },
3024 ComponentEntry {
3025 class: "moss-footnotes",
3026 kind: "container",
3027 parent: "",
3028 data_attrs: &[],
3029 example_html: r##"<section class="moss-footnotes" role="doc-endnotes">
3030<ol>
3031<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>
3032</li>
3033</ol>
3034</section>"##,
3035 example_markdown: "Text[^1].\n\n[^1]: The note.",
3036 status: Status::Emerging,
3037 since: "1",
3038 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.",
3039 },
3040 ComponentEntry {
3041 class: "moss-footnote-ref",
3042 kind: "instance",
3043 parent: "moss-footnotes",
3044 data_attrs: &[],
3045 example_html: r##"<sup class="moss-footnote-ref" id="fnref-1"><a href="#fn-1" role="doc-noteref">1</a></sup>"##,
3046 example_markdown: "Text[^1].\n\n[^1]: The note.",
3047 status: Status::Emerging,
3048 since: "1",
3049 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.",
3050 },
3051 ComponentEntry {
3052 class: "moss-footnote-backref",
3053 kind: "instance",
3054 parent: "moss-footnotes",
3055 data_attrs: &[],
3056 example_html: r##"<a class="moss-footnote-backref" href="#fnref-1" role="doc-backlink" aria-label="Back to reference 1">↩︎</a>"##,
3057 example_markdown: "Text[^1].\n\n[^1]: The note.",
3058 status: Status::Emerging,
3059 since: "1",
3060 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.",
3061 },
3062 ComponentEntry {
3073 class: "container",
3074 kind: "chrome",
3075 parent: "",
3076 data_attrs: &[
3077 DataAttr {
3078 name: "data-share-cover",
3079 values: &[],
3080 default: "",
3081 description: "The page's own cover image, as a same-origin URL: its `:::hero` image, else its `cover:` frontmatter. Emitted on the `<article>` only, and only when the page has one of those — absent means the page genuinely has no cover, and never the auto-generated og card. The share-card runtime reads it instead of hunting for the cover in the markup; a theme can use `article.container[data-share-cover]` to tell a page that has a cover picture from one that does not.",
3082 },
3083 ],
3084 example_html: r#"<article class="container" data-share-cover="/img/cover.webp">…</article>"#,
3085 example_markdown: "",
3086 status: Status::Confirmed,
3087 since: "0",
3088 description: "The reading-width wrapper. It is the `<article>` on a page or post, and also the `<nav class=\"main-nav container\">` — one class, one measure, so the masthead lines up with the text under it. Site JS treats `article.container` as \"the current document\": immersive mode promotes its direct-child iframes, and the share card reads its `data-share-cover`.",
3089 },
3090 ComponentEntry {
3091 class: "nav-content",
3092 kind: "chrome",
3093 parent: "main-nav",
3094 data_attrs: &[],
3095 example_html: r#"<nav class="main-nav container"><div class="nav-content">…</div></nav>"#,
3096 example_markdown: "",
3097 status: Status::Confirmed,
3098 since: "0",
3099 description: "The row inside the nav bar that holds `.nav-left` and `.nav-right`. It is the box the responsive nav measures itself against: when the two groups no longer fit on one line, `nav-split.ts` sets `data-nav-split` on this element and the links move to a second row.",
3100 },
3101 ComponentEntry {
3102 class: "font-anchor",
3103 kind: "chrome",
3104 parent: "date-line",
3105 data_attrs: &[],
3106 example_html: r#"<div class="font-anchor"><button class="font-trigger size-std" aria-expanded="false"></button><div class="font-pill" id="fontPill">…</div></div>"#,
3107 example_markdown: "",
3108 status: Status::Confirmed,
3109 since: "0",
3110 description: "Positioning box for the reading-preferences control that sits at the end of an article's date line. Holds the trigger and the size pills; exists so the pills can be positioned against the trigger rather than the page.",
3111 },
3112 ComponentEntry {
3113 class: "font-pill",
3114 kind: "instance",
3115 parent: "font-anchor",
3116 data_attrs: &[],
3117 example_html: r#"<div class="font-pill" id="fontPill"><button data-scale="small"></button>…</div>"#,
3118 example_markdown: "",
3119 status: Status::Confirmed,
3120 since: "0",
3121 description: "The reading-size control that opens from the font trigger. Each button carries `data-scale`; the one matching the current size additionally carries the bare class `active`.",
3122 },
3123 ComponentEntry {
3124 class: "font-trigger",
3125 kind: "instance",
3126 parent: "font-anchor",
3127 data_attrs: &[],
3128 example_html: r#"<button class="font-trigger size-std" aria-label="Reading preferences" aria-expanded="false" type="button"></button>"#,
3129 example_markdown: "",
3130 status: Status::Confirmed,
3131 since: "0",
3132 description: "The button that opens the reading-size pills. Its second class tracks the chosen size (`size-std` by default), so a theme can restyle the trigger per size without reading state from JS.",
3133 },
3134 ComponentEntry {
3135 class: "size-std",
3136 kind: "instance",
3137 parent: "font-trigger",
3138 data_attrs: &[],
3139 example_html: r#"<button class="font-trigger size-std" aria-expanded="false"></button>"#,
3140 example_markdown: "",
3141 status: Status::Confirmed,
3142 since: "0",
3143 description: "Co-class on the font trigger reflecting the current reading size (`size-std` at the default). Lets a theme show the active size on the closed control.",
3144 },
3145 ComponentEntry {
3146 class: "cover-thumb",
3147 kind: "instance",
3148 parent: "moss-card-cover",
3149 data_attrs: &[],
3150 example_html: r#"<div class="moss-card-cover"><video src="clip.mp4" muted loop playsinline preload="metadata"></video><img src="clip.thumb.jpg" alt="A clip" class="cover-thumb" /></div>"#,
3151 example_markdown: "",
3152 status: Status::Confirmed,
3153 since: "0",
3154 description: "The still frame stacked over a video cover. It is what the reader sees until they hover: `card-video.ts` fades this image out and starts the `<video>` underneath, and fades it back in on leave. Emitted only for video covers, inside `.moss-card-cover` or `.moss-collection-cover`.",
3155 },
3156 ComponentEntry {
3157 class: "media-item",
3158 kind: "instance",
3159 parent: "",
3160 data_attrs: &[
3161 DataAttr { name: "data-type", values: &["image", "video", "iframe"], default: "image", description: "Which lightbox surface opens for this item." },
3162 DataAttr { name: "data-src", values: &[], default: "", description: "Full-size source the lightbox loads." },
3163 DataAttr { name: "data-title", values: &[], default: "", description: "Caption shown under the lightbox." },
3164 DataAttr { name: "data-article", values: &[], default: "", description: "URL of the article the item came from, linked from the caption." },
3165 ],
3166 example_html: r#"<figure class="media-item" tabindex="0" data-type="image" data-src="/img/full.webp" data-title="Kyiv" data-article="/posts/kyiv/">…</figure>"#,
3167 example_markdown: "",
3168 status: Status::Confirmed,
3169 since: "0",
3170 description: "One tile on the media-collection page. `fullscreen.ts` collects these in document order to build the lightbox playlist, so their order on the page is the order the arrows step through.",
3171 },
3172 ComponentEntry {
3173 class: "lightbox-content",
3174 kind: "chrome",
3175 parent: "",
3176 data_attrs: &[],
3177 example_html: r#"<div class="lightbox-content"><img class="lightbox-image" hidden /><video class="lightbox-video" controls hidden></video><iframe class="lightbox-iframe" hidden></iframe></div>"#,
3178 example_markdown: "",
3179 status: Status::Confirmed,
3180 since: "0",
3181 description: "The stage of the media-collection lightbox. Holds all three players at once; `fullscreen.ts` unhides whichever one matches the opened item's `data-type` and leaves the others hidden.",
3182 },
3183 ComponentEntry {
3184 class: "lightbox-image",
3185 kind: "instance",
3186 parent: "lightbox-content",
3187 data_attrs: &[],
3188 example_html: r#"<img class="lightbox-image" src="" alt="" hidden />"#,
3189 example_markdown: "",
3190 status: Status::Confirmed,
3191 since: "0",
3192 description: "The lightbox's image player. Emitted empty and hidden; its `src` is filled in when an image item opens.",
3193 },
3194 ComponentEntry {
3195 class: "lightbox-video",
3196 kind: "instance",
3197 parent: "lightbox-content",
3198 data_attrs: &[],
3199 example_html: r#"<video class="lightbox-video" controls hidden></video>"#,
3200 example_markdown: "",
3201 status: Status::Confirmed,
3202 since: "0",
3203 description: "The lightbox's video player. Emitted empty and hidden; paused and cleared when the lightbox closes so audio never outlives the overlay.",
3204 },
3205 ComponentEntry {
3206 class: "lightbox-iframe",
3207 kind: "instance",
3208 parent: "lightbox-content",
3209 data_attrs: &[],
3210 example_html: r#"<iframe class="lightbox-iframe" hidden></iframe>"#,
3211 example_markdown: "",
3212 status: Status::Confirmed,
3213 since: "0",
3214 description: "The lightbox's embed surface, for media items that are an external player rather than a file.",
3215 },
3216 ComponentEntry {
3217 class: "lightbox-title",
3218 kind: "instance",
3219 parent: "",
3220 data_attrs: &[],
3221 example_html: r#"<p class="lightbox-title"></p>"#,
3222 example_markdown: "",
3223 status: Status::Confirmed,
3224 since: "0",
3225 description: "Caption line under the lightbox stage. Filled from the open item's `data-title`.",
3226 },
3227 ComponentEntry {
3228 class: "lightbox-article-link",
3229 kind: "instance",
3230 parent: "",
3231 data_attrs: &[],
3232 example_html: r#"<a class="lightbox-article-link" href="">View in article →</a>"#,
3233 example_markdown: "",
3234 status: Status::Confirmed,
3235 since: "0",
3236 description: "The way back from a media tile to the article it appeared in. Its `href` is filled from the open item's `data-article`, and it is hidden when the item has none.",
3237 },
3238 ComponentEntry {
3239 class: "lightbox-close",
3240 kind: "instance",
3241 parent: "",
3242 data_attrs: &[],
3243 example_html: r#"<button class="lightbox-close" aria-label="Close">×</button>"#,
3244 example_markdown: "",
3245 status: Status::Confirmed,
3246 since: "0",
3247 description: "Dismisses the media-collection lightbox. Escape does the same thing.",
3248 },
3249 ComponentEntry {
3250 class: "lightbox-next",
3251 kind: "instance",
3252 parent: "",
3253 data_attrs: &[],
3254 example_html: r#"<button class="lightbox-nav lightbox-next" aria-label="Next">›</button>"#,
3255 example_markdown: "",
3256 status: Status::Confirmed,
3257 since: "0",
3258 description: "Steps forward through the `.media-item` playlist, wrapping at the end. Carries `lightbox-nav` as well, which styles both arrows together.",
3259 },
3260 ComponentEntry {
3261 class: "lightbox-prev",
3262 kind: "instance",
3263 parent: "",
3264 data_attrs: &[],
3265 example_html: r#"<button class="lightbox-nav lightbox-prev" aria-label="Previous">‹</button>"#,
3266 example_markdown: "",
3267 status: Status::Confirmed,
3268 since: "0",
3269 description: "Steps backward through the `.media-item` playlist, wrapping at the start.",
3270 },
3271 ComponentEntry {
3272 class: "comments-toggle",
3273 kind: "instance",
3274 parent: "moss-comments",
3275 data_attrs: &[],
3276 example_html: r#"<summary class="comments-toggle"><svg class="comments-icon">…</svg><span>3 comments</span><svg class="comments-chevron">…</svg></summary>"#,
3277 example_markdown: "",
3278 status: Status::Confirmed,
3279 since: "0",
3280 description: "The `<summary>` that opens and closes the comment thread. Its `<span>` holds the count, which the client rewrites as comments arrive — so the span is a contract of its own, not decoration.",
3281 },
3282 ComponentEntry {
3283 class: "comments-icon",
3284 kind: "instance",
3285 parent: "comments-toggle",
3286 data_attrs: &[],
3287 example_html: r#"<summary class="comments-toggle"><svg class="comments-icon" width="18" height="18">…</svg></summary>"#,
3288 example_markdown: "",
3289 status: Status::Confirmed,
3290 since: "0",
3291 description: "Speech-bubble icon inside the comments disclosure summary.",
3292 },
3293 ComponentEntry {
3294 class: "comments-chevron",
3295 kind: "instance",
3296 parent: "comments-toggle",
3297 data_attrs: &[],
3298 example_html: r#"<summary class="comments-toggle">…<svg class="comments-chevron">…</svg></summary>"#,
3299 example_markdown: "",
3300 status: Status::Confirmed,
3301 since: "0",
3302 description: "Disclosure arrow inside the comments summary. Rotates with the `<details>` open state; the rotation is the only affordance saying the section collapses.",
3303 },
3304 ComponentEntry {
3305 class: "comment-list",
3306 kind: "container",
3307 parent: "moss-comments",
3308 data_attrs: &[],
3309 example_html: r#"<ol class="comment-list"><li class="comment-item" …>…</li></ol>"#,
3310 example_markdown: "",
3311 status: Status::Confirmed,
3312 since: "0",
3313 description: "The top-level comment thread, rendered server-side at build time and then hydrated in place (ADR-025). New comments are appended here by the client rather than replacing the list, so server-rendered and live comments share one shape.",
3314 },
3315 ComponentEntry {
3316 class: "comment-item",
3317 kind: "instance",
3318 parent: "comment-list",
3319 data_attrs: &[
3320 DataAttr { name: "data-comment-source", values: &["artalk"], default: "artalk", description: "Where the comment came from — moss's own server, or a syndicated platform." },
3321 DataAttr { name: "data-comment-id", values: &[], default: "", description: "Identifier within that source. Unique only per source, which is why nesting keys on the pair." },
3322 ],
3323 example_html: r#"<li class="comment-item" id="comment-artalk-12" data-comment-source="artalk" data-comment-id="12">…</li>"#,
3324 example_markdown: "",
3325 status: Status::Confirmed,
3326 since: "0",
3327 description: "One comment. Holds a `.comment-header`, a `.comment-body`, and — if it has replies — a nested `.comment-replies`. The `data-comment-*` pair is how the client matches a live comment to the one already on the page instead of rendering it twice.",
3328 },
3329 ComponentEntry {
3330 class: "comment-header",
3331 kind: "instance",
3332 parent: "comment-item",
3333 data_attrs: &[],
3334 example_html: r#"<div class="comment-header"><a class="comment-author">…</a><time class="comment-date">…</time></div>"#,
3335 example_markdown: "",
3336 status: Status::Confirmed,
3337 since: "0",
3338 description: "Attribution row of a single comment: who wrote it, when, and where it came from.",
3339 },
3340 ComponentEntry {
3341 class: "comment-body",
3342 kind: "instance",
3343 parent: "comment-item",
3344 data_attrs: &[],
3345 example_html: r#"<div class="comment-body">…</div>"#,
3346 example_markdown: "",
3347 status: Status::Confirmed,
3348 since: "0",
3349 description: "The comment text itself, separated from the attribution header so the two can be styled independently.",
3350 },
3351 ComponentEntry {
3352 class: "comment-author",
3353 kind: "instance",
3354 parent: "comment-header",
3355 data_attrs: &[],
3356 example_html: r#"<a href="…" class="comment-author" rel="nofollow ugc noopener" target="_blank">Name</a>"#,
3357 example_markdown: "",
3358 status: Status::Confirmed,
3359 since: "0",
3360 description: "The commenter's name, linked to the URL they supplied. Always carries `rel=`nofollow ugc`` — the link is reader-supplied content, not an endorsement by the site.",
3361 },
3362 ComponentEntry {
3363 class: "comment-date",
3364 kind: "instance",
3365 parent: "comment-header",
3366 data_attrs: &[],
3367 example_html: r#"<time class="comment-date" datetime="2026-08-21T10:00:00Z">21 August 2026</time>"#,
3368 example_markdown: "",
3369 status: Status::Confirmed,
3370 since: "0",
3371 description: "Publication time of a comment, as a `<time>` element carrying a machine-readable `datetime`.",
3372 },
3373 ComponentEntry {
3374 class: "comment-source-link",
3375 kind: "instance",
3376 parent: "comment-header",
3377 data_attrs: &[],
3378 example_html: r#"<a class="comment-source-link" href="…" target="_blank" rel="noopener nofollow">…</a>"#,
3379 example_markdown: "",
3380 status: Status::Confirmed,
3381 since: "0",
3382 description: "Link back to where a syndicated comment originated, for comments moss did not receive directly.",
3383 },
3384 ComponentEntry {
3385 class: "comment-replies",
3386 kind: "container",
3387 parent: "comment-item",
3388 data_attrs: &[],
3389 example_html: r#"<ol class="comment-replies"><li class="comment-item" …>…</li></ol>"#,
3390 example_markdown: "",
3391 status: Status::Confirmed,
3392 since: "0",
3393 description: "Nested replies under a comment, same shape as `.comment-list`. Emitted only when a comment has replies; the client creates one on demand when the first reply arrives.",
3394 },
3395 ComponentEntry {
3396 class: "comment-reply-btn",
3397 kind: "instance",
3398 parent: "comment-item",
3399 data_attrs: &[
3400 DataAttr { name: "data-reply-id", values: &[], default: "", description: "The comment being replied to." },
3401 DataAttr { name: "data-reply-name", values: &[], default: "", description: "Display name of its author, used to prefill the form." },
3402 ],
3403 example_html: r#"<button type="button" class="comment-reply-btn" data-reply-id="12" data-reply-name="Yi">↩︎ Reply</button>"#,
3404 example_markdown: "",
3405 status: Status::Confirmed,
3406 since: "0",
3407 description: "Moves the comment form under this comment so the reply is written where it will appear. Emitted only for comments moss can reply to; a syndicated comment gets a link out to its own platform instead.",
3408 },
3409 ComponentEntry {
3410 class: "review-colophon",
3411 kind: "standalone",
3412 parent: "",
3413 data_attrs: &[],
3414 example_html: r#"<footer class="review-colophon">…</footer>"#,
3415 example_markdown: "",
3416 status: Status::Confirmed,
3417 since: "0",
3418 description: "Footer block of a review page carrying the details of the work being reviewed. A `<footer>` because it describes the subject rather than continuing the argument.",
3419 },
3420 ComponentEntry {
3421 class: "review-colophon-details",
3422 kind: "instance",
3423 parent: "review-colophon",
3424 data_attrs: &[],
3425 example_html: r#"<div class="review-colophon-details">…</div>"#,
3426 example_markdown: "",
3427 status: Status::Confirmed,
3428 since: "0",
3429 description: "Container for the reviewed work's identifying fields inside the colophon.",
3430 },
3431 ComponentEntry {
3432 class: "review-biblio",
3433 kind: "instance",
3434 parent: "review-colophon",
3435 data_attrs: &[],
3436 example_html: r#"<div class="review-biblio">…</div>"#,
3437 example_markdown: "",
3438 status: Status::Confirmed,
3439 since: "0",
3440 description: "Full bibliographic citation of the reviewed work, formatted as one line.",
3441 },
3442 ComponentEntry {
3443 class: "review-rating",
3444 kind: "instance",
3445 parent: "review-colophon",
3446 data_attrs: &[],
3447 example_html: r#"<div class="review-rating">★★★★☆</div>"#,
3448 example_markdown: "",
3449 status: Status::Confirmed,
3450 since: "0",
3451 description: "The reviewer's own rating of the work.",
3452 },
3453 ComponentEntry {
3454 class: "review-community-rating",
3455 kind: "instance",
3456 parent: "review-colophon",
3457 data_attrs: &[],
3458 example_html: r#"<div class="review-community-rating">★ 7.8/10 · 1,204 ratings</div>"#,
3459 example_markdown: "",
3460 status: Status::Confirmed,
3461 since: "0",
3462 description: "Aggregate rating carried over from the source catalogue, kept visually distinct from the reviewer's own so the two are never read as one judgement.",
3463 },
3464 ComponentEntry {
3465 class: "review-links",
3466 kind: "instance",
3467 parent: "review-colophon",
3468 data_attrs: &[],
3469 example_html: r#"<nav class="review-links"><a href="…">…</a><span class="review-sep"> · </span><a href="…">…</a></nav>"#,
3470 example_markdown: "",
3471 status: Status::Confirmed,
3472 since: "0",
3473 description: "Navigation to the reviewed work elsewhere — catalogue entries, purchase pages, the publisher.",
3474 },
3475 ComponentEntry {
3476 class: "review-sep",
3477 kind: "instance",
3478 parent: "review-links",
3479 data_attrs: &[],
3480 example_html: r#"<span class="review-sep"> · </span>"#,
3481 example_markdown: "",
3482 status: Status::Confirmed,
3483 since: "0",
3484 description: "Separator between review links. An element rather than a `::before` so it can be hidden when links wrap.",
3485 },
3486 ComponentEntry {
3487 class: "review-colophon-title",
3488 kind: "instance",
3489 parent: "review-colophon-details",
3490 data_attrs: &[],
3491 example_html: r#"<div class="review-colophon-title">…</div>"#,
3492 example_markdown: "",
3493 status: Status::Confirmed,
3494 since: "0",
3495 description: "Title of the work being reviewed — not the title of the review itself, which is the page heading.",
3496 },
3497 ComponentEntry {
3498 class: "review-colophon-subtitle",
3499 kind: "instance",
3500 parent: "review-colophon-details",
3501 data_attrs: &[],
3502 example_html: r#"<div class="review-colophon-subtitle">…</div>"#,
3503 example_markdown: "",
3504 status: Status::Confirmed,
3505 since: "0",
3506 description: "Subtitle of the reviewed work, rendered only when one is present.",
3507 },
3508 ComponentEntry {
3509 class: "review-colophon-identity",
3510 kind: "instance",
3511 parent: "review-colophon-details",
3512 data_attrs: &[],
3513 example_html: r#"<div class="review-colophon-identity">…</div>"#,
3514 example_markdown: "",
3515 status: Status::Confirmed,
3516 since: "0",
3517 description: "The reviewed work's identifier (ISBN, DOI or equivalent), which is what makes a review resolvable to a specific edition.",
3518 },
3519 ComponentEntry {
3520 class: "page-wrapper",
3521 kind: "container",
3522 parent: "",
3523 data_attrs: &[],
3524 example_html: r#"<div class="page-wrapper"><div class="main-content">…</div></div>"#,
3525 example_markdown: "",
3526 status: Status::Confirmed,
3527 since: "0",
3528 description: "Outermost wrapper of a generated media-collection page.",
3529 },
3530 ComponentEntry {
3531 class: "main-content",
3532 kind: "instance",
3533 parent: "page-wrapper",
3534 data_attrs: &[],
3535 example_html: r#"<div class="main-content"><div class="media-grid">…</div></div>"#,
3536 example_markdown: "",
3537 status: Status::Confirmed,
3538 since: "0",
3539 description: "Primary content column of a media-collection page, inside the page wrapper.",
3540 },
3541 ComponentEntry {
3542 class: "media-grid",
3543 kind: "instance",
3544 parent: "main-content",
3545 data_attrs: &[],
3546 example_html: r#"<div class="media-grid">…</div>"#,
3547 example_markdown: "",
3548 status: Status::Confirmed,
3549 since: "0",
3550 description: "The grid of media items on a media-collection page.",
3551 },
3552 ComponentEntry {
3553 class: "media-overlay",
3554 kind: "instance",
3555 parent: "media-grid",
3556 data_attrs: &[],
3557 example_html: r#"<div class="media-overlay"><p class="media-title">…</p></div>"#,
3558 example_markdown: "",
3559 status: Status::Confirmed,
3560 since: "0",
3561 description: "Caption overlay drawn over a media thumbnail, typically revealed on hover or focus.",
3562 },
3563 ComponentEntry {
3564 class: "media-title",
3565 kind: "instance",
3566 parent: "media-overlay",
3567 data_attrs: &[],
3568 example_html: r#"<p class="media-title">…</p>"#,
3569 example_markdown: "",
3570 status: Status::Confirmed,
3571 since: "0",
3572 description: "Title of a single media item, inside its overlay.",
3573 },
3574 ComponentEntry {
3575 class: "lightbox",
3576 kind: "standalone",
3577 parent: "",
3578 data_attrs: &[],
3579 example_html: r#"<div id="lightbox" class="lightbox" hidden tabindex="-1">…</div>"#,
3580 example_markdown: "",
3581 status: Status::Confirmed,
3582 since: "0",
3583 description: "Full-screen viewer for a media collection. Ships `hidden` and `tabindex=`-1`` so it is out of the tab order until opened.",
3584 },
3585 ComponentEntry {
3586 class: "lightbox-caption",
3587 kind: "instance",
3588 parent: "lightbox",
3589 data_attrs: &[],
3590 example_html: r#"<div class="lightbox-caption">…</div>"#,
3591 example_markdown: "",
3592 status: Status::Confirmed,
3593 since: "0",
3594 description: "Caption area of the lightbox, describing the item currently shown.",
3595 },
3596 ComponentEntry {
3597 class: "lightbox-nav",
3598 kind: "instance",
3599 parent: "lightbox",
3600 data_attrs: &[],
3601 example_html: r#"<button class="lightbox-nav lightbox-prev" aria-label="Previous">‹</button>"#,
3602 example_markdown: "",
3603 status: Status::Confirmed,
3604 since: "0",
3605 description: "Previous/next control in the lightbox. Carries a direction co-class (`lightbox-prev` / `lightbox-next`) and always an `aria-label`, since the visible glyph is a chevron.",
3606 },
3607 ComponentEntry {
3608 class: "latest-sidebar",
3609 kind: "standalone",
3610 parent: "",
3611 data_attrs: &[],
3612 example_html: r#"<nav class="latest-sidebar"><h3>Latest</h3><ul>…</ul></nav>"#,
3613 example_markdown: "",
3614 status: Status::Confirmed,
3615 since: "0",
3616 description: "Sidebar listing the most recent entries. A `<nav>` because it is a navigational aid, not part of the page's argument.",
3617 },
3618 ComponentEntry {
3619 class: "sidebar-more",
3620 kind: "instance",
3621 parent: "latest-sidebar",
3622 data_attrs: &[],
3623 example_html: r#"<a href="…" class="sidebar-more">More →</a>"#,
3624 example_markdown: "",
3625 status: Status::Confirmed,
3626 since: "0",
3627 description: "Link from the latest-entries sidebar to the full listing.",
3628 },
3629 ComponentEntry {
3630 class: "active",
3631 kind: "instance",
3632 parent: "",
3633 data_attrs: &[],
3634 example_html: r#"<button data-scale="" class="active" aria-label="Standard"></button>"#,
3635 example_markdown: "",
3636 status: Status::Confirmed,
3637 since: "0",
3638 description: "Bare state co-class marking the currently-selected item in a set — the current page in `.nav-links`, the current size in `.font-pill`. Style it scoped to its container (`.nav-links .active`), never on its own.",
3639 },
3640 ComponentEntry {
3641 class: "has-sidebar-layout",
3642 kind: "instance",
3643 parent: "",
3644 data_attrs: &[],
3645 example_html: r#"<main class="has-sidebar-layout">…</main>"#,
3646 example_markdown: "",
3647 status: Status::Confirmed,
3648 since: "0",
3649 description: "Marks a page laid out with a sidebar, so the main column can reserve room for it without the sidebar having rendered yet.",
3650 },
3651 ComponentEntry {
3652 class: "wikilink",
3653 kind: "instance",
3654 parent: "",
3655 data_attrs: &[],
3656 example_html: r#"<a class="wikilink" href="/other-page/">Other page</a>"#,
3657 example_markdown: "",
3658 status: Status::Confirmed,
3659 since: "0",
3660 description: "Marks a link that came from `[[wikilink]]` syntax rather than a markdown link, so a theme can distinguish internal cross-references from ordinary links.",
3661 },
3662];
3663
3664const INTERNAL_CLASSES: &[&str] = &[
3670 "moss-apply",
3671 "moss-apply-form",
3672 "moss-apply-matters",
3673 "moss-apply-hp",
3674 "moss-apply-status",
3675 "moss-apply-helper",
3676];
3677
3678impl ComponentEntry {
3679 pub fn is_public(&self) -> bool {
3686 self.status != Status::Retired && !INTERNAL_CLASSES.contains(&self.class)
3687 }
3688}
3689
3690pub fn retired_class_names() -> impl Iterator<Item = &'static str> {
3696 COMPONENTS.iter()
3697 .filter(|e| e.status == Status::Retired)
3698 .map(|e| e.class)
3699}
3700
3701#[cfg(test)]
3702mod tests {
3703 use super::*;
3704
3705 #[test]
3711 fn every_internal_class_has_a_components_entry() {
3712 let component_classes: std::collections::HashSet<&'static str> =
3713 COMPONENTS.iter().map(|e| e.class).collect();
3714 for &internal in INTERNAL_CLASSES {
3715 assert!(
3716 component_classes.contains(internal),
3717 "INTERNAL_CLASSES entry '{}' has no matching entry in COMPONENTS — \
3718 add a ComponentEntry for it or remove it from INTERNAL_CLASSES",
3719 internal
3720 );
3721 }
3722 }
3723}