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 COMPONENTS: &[ComponentEntry] = &[
75 ComponentEntry {
76 class: "moss-cards",
77 kind: "container",
78 parent: "",
79 data_attrs: &[
80 DataAttr {
81 name: "data-layout",
82 values: &["grid", "list", "minimal"],
83 default: "grid",
84 description: "Card layout density. Grid: 2-3 cols with covers. List: single column with side covers. Minimal: text-only with year groupings.",
85 },
86 DataAttr {
87 name: "data-density",
88 values: &["default", "compact"],
89 default: "default",
90 description: "Vertical spacing density.",
91 },
92 DataAttr {
93 name: "data-list-axis",
94 values: &["date", "weight", "title"],
95 default: "title",
96 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).",
97 },
98 DataAttr {
99 name: "data-list-has-covers",
100 values: &[""],
101 default: "",
102 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.",
103 },
104 ],
105 example_html: r#"<div class="moss-cards-container">
106 <div class="moss-cards" data-layout="grid" data-list-axis="date" data-list-has-covers>
107 <a class="moss-card" href="...">...</a>
108 <a class="moss-card" href="...">...</a>
109 </div>
110</div>"#,
111 example_markdown: "",
112 status: Status::Confirmed,
113 since: "1",
114 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.",
115 },
116 ComponentEntry {
117 class: "moss-cards-container",
118 kind: "container",
119 parent: "",
120 data_attrs: &[],
121 example_html: r#"<div class="moss-cards-container">
122 <div class="moss-cards" data-layout="grid">...</div>
123</div>"#,
124 example_markdown: "",
125 status: Status::Confirmed,
126 since: "1",
127 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.",
128 },
129 ComponentEntry {
130 class: "moss-summary-layout",
131 kind: "container",
132 parent: "moss-cards",
133 data_attrs: &[],
134 example_html: r#"<div class="moss-cards" data-layout="list">...</div>"#,
135 example_markdown: "",
136 status: Status::Retired,
137 since: "1",
138 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.",
139 },
140 ComponentEntry {
146 class: "moss-cards-grid",
147 kind: "container",
148 parent: "",
149 data_attrs: &[],
150 example_html: r#"<div class="moss-cards-grid">
151 <a class="moss-card-grid" href="...">...</a>
152</div>"#,
153 example_markdown: "",
154 status: Status::Retired,
155 since: "0",
156 description: "Retired in Phase 1c — collapsed into `.moss-cards[data-layout=grid]`.",
157 },
158 ComponentEntry {
159 class: "moss-cards-list",
160 kind: "container",
161 parent: "",
162 data_attrs: &[],
163 example_html: r#"<div class="moss-cards-list">
164 <a class="moss-card-list" href="...">...</a>
165</div>"#,
166 example_markdown: "",
167 status: Status::Retired,
168 since: "0",
169 description: "Retired in Phase 1c — collapsed into `.moss-cards[data-layout=list]`.",
170 },
171 ComponentEntry {
172 class: "moss-cards-minimal-year-group",
173 kind: "container",
174 parent: "",
175 data_attrs: &[],
176 example_html: r#"<section class="moss-cards-minimal-year-group">
177 <h3>2024</h3>
178 <div class="moss-card-minimal">...</div>
179</section>"#,
180 example_markdown: "",
181 status: Status::Confirmed,
182 since: "0",
183 description: "Year-grouped section in minimal card layout (e.g. blog index). Modifier `--summary` collapses past years.",
184 },
185 ComponentEntry {
186 class: "moss-cards-minimal-year-group--summary",
187 kind: "container",
188 parent: "moss-cards-minimal-year-group",
189 data_attrs: &[],
190 example_html: r#"<section class="moss-cards-minimal-year-group moss-cards-minimal-year-group--summary">...</section>"#,
191 example_markdown: "",
192 status: Status::Confirmed,
193 since: "0",
194 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).",
195 },
196 ComponentEntry {
197 class: "moss-card",
198 kind: "instance",
199 parent: "moss-cards",
200 data_attrs: &[
201 DataAttr {
202 name: "data-linkblog",
203 values: &[],
204 default: "",
205 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>`).",
206 },
207 ],
208 example_html: r#"<a class="moss-card" href="...">...</a>"#,
209 example_markdown: "",
210 status: Status::Confirmed,
211 since: "1",
212 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]`).",
213 },
214 ComponentEntry {
215 class: "moss-card-cover",
216 kind: "instance",
217 parent: "moss-card",
218 data_attrs: &[],
219 example_html: r#"<div class="moss-card-cover"><img src="..." /></div>"#,
220 example_markdown: "",
221 status: Status::Confirmed,
222 since: "1",
223 description: "Cover media slot inside `.moss-card`. Gets `.moss-card-no-cover` modifier when no image is present.",
224 },
225 ComponentEntry {
226 class: "moss-card-no-cover",
227 kind: "instance",
228 parent: "moss-card",
229 data_attrs: &[],
230 example_html: r#"<div class="moss-card-cover moss-card-no-cover"></div>"#,
231 example_markdown: "",
232 status: Status::Confirmed,
233 since: "1",
234 description: "Modifier applied to `.moss-card-cover` when no cover media is available.",
235 },
236 ComponentEntry {
237 class: "moss-card-content",
238 kind: "instance",
239 parent: "moss-card",
240 data_attrs: &[],
241 example_html: r#"<div class="moss-card-content">...</div>"#,
242 example_markdown: "",
243 status: Status::Confirmed,
244 since: "1",
245 description: "Text content slot inside a grid-layout `.moss-card` (kicker + title + meta).",
246 },
247 ComponentEntry {
248 class: "moss-card-row",
249 kind: "instance",
250 parent: "moss-card",
251 data_attrs: &[],
252 example_html: r#"<div class="moss-card-row">...</div>"#,
253 example_markdown: "",
254 status: Status::Confirmed,
255 since: "1",
256 description: "Row wrapper inside a list-layout `.moss-card` holding body + cover side-by-side.",
257 },
258 ComponentEntry {
259 class: "moss-card-body",
260 kind: "instance",
261 parent: "moss-card",
262 data_attrs: &[],
263 example_html: r#"<div class="moss-card-body">...</div>"#,
264 example_markdown: "",
265 status: Status::Confirmed,
266 since: "1",
267 description: "Text body slot of a list-layout `.moss-card`.",
268 },
269 ComponentEntry {
270 class: "moss-card-head",
271 kind: "instance",
272 parent: "moss-card",
273 data_attrs: &[],
274 example_html: r#"<div class="moss-card-head">...</div>"#,
275 example_markdown: "",
276 status: Status::Confirmed,
277 since: "1",
278 description: "Header row of a `.moss-card-body` (title + kicker + meta).",
279 },
280 ComponentEntry {
281 class: "moss-card-title",
282 kind: "instance",
283 parent: "moss-card",
284 data_attrs: &[],
285 example_html: r#"<h3 class="moss-card-title">Page title</h3>"#,
286 example_markdown: "",
287 status: Status::Confirmed,
288 since: "1",
289 description: "Title inside `.moss-card`.",
290 },
291 ComponentEntry {
292 class: "moss-card-meta",
293 kind: "instance",
294 parent: "moss-card",
295 data_attrs: &[],
296 example_html: r#"<div class="moss-card-meta">2024-01-15</div>"#,
297 example_markdown: "",
298 status: Status::Confirmed,
299 since: "1",
300 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/design-system/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.",
301 },
302 ComponentEntry {
303 class: "moss-card-kicker",
304 kind: "instance",
305 parent: "moss-card",
306 data_attrs: &[],
307 example_html: r#"<span class="moss-card-kicker">Category</span>"#,
308 example_markdown: "",
309 status: Status::Confirmed,
310 since: "1",
311 description: "Eyebrow / overline above the title inside `.moss-card`.",
312 },
313 ComponentEntry {
314 class: "moss-card-permalink",
315 kind: "instance",
316 parent: "moss-card-kicker",
317 data_attrs: &[],
318 example_html: r#"<a class="moss-card-permalink" href="/posts/foo/" title="Permalink to 'Title'">★</a>"#,
319 example_markdown: "",
320 status: Status::Emerging,
321 since: "1",
322 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`.",
323 },
324 ComponentEntry {
325 class: "moss-card-title-link",
326 kind: "instance",
327 parent: "moss-card-head",
328 data_attrs: &[],
329 example_html: r#"<a class="moss-card-title-link" href="https://outlet.example/article"><h3 class="moss-card-title">Article Title</h3></a>"#,
330 example_markdown: "",
331 status: Status::Emerging,
332 since: "1",
333 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`).",
334 },
335 ComponentEntry {
336 class: "moss-card-cover-link",
337 kind: "instance",
338 parent: "moss-card-row",
339 data_attrs: &[],
340 example_html: r#"<a class="moss-card-cover-link" href="https://outlet.example/article"><div class="moss-card-cover">...</div></a>"#,
341 example_markdown: "",
342 status: Status::Emerging,
343 since: "1",
344 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.",
345 },
346 ComponentEntry {
347 class: "moss-card-description-link",
348 kind: "instance",
349 parent: "moss-card-body",
350 data_attrs: &[],
351 example_html: r#"<a class="moss-card-description-link" href="https://outlet.example/article"><p class="moss-card-description">…</p></a>"#,
352 example_markdown: "",
353 status: Status::Emerging,
354 since: "1",
355 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.",
356 },
357 ComponentEntry {
358 class: "moss-card-description",
359 kind: "instance",
360 parent: "moss-card",
361 data_attrs: &[],
362 example_html: r#"<p class="moss-card-description">Excerpt...</p>"#,
363 example_markdown: "",
364 status: Status::Confirmed,
365 since: "1",
366 description: "Excerpt / description paragraph inside a `.moss-card` — below the title in both grid- and list-layout cards.",
367 },
368 ComponentEntry {
369 class: "moss-card-count",
370 kind: "instance",
371 parent: "moss-card",
372 data_attrs: &[],
373 example_html: r#"<div class="moss-card-count">4 articles</div>"#,
374 example_markdown: "",
375 status: Status::Confirmed,
376 since: "1",
377 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.",
378 },
379 ComponentEntry {
380 class: "moss-embed-more",
381 kind: "instance",
382 parent: "moss-cards-container",
383 data_attrs: &[],
384 example_html: r#"<p class="moss-embed-more"><a href="/news/">More →</a></p>"#,
385 example_markdown: "",
386 status: Status::Confirmed,
387 since: "1",
388 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/architecture/ui-design/spacing.md).",
389 },
390 ComponentEntry {
391 class: "moss-card-grid",
392 kind: "instance",
393 parent: "moss-cards-grid",
394 data_attrs: &[],
395 example_html: r#"<a class="moss-card-grid" href="...">...</a>"#,
396 example_markdown: "",
397 status: Status::Retired,
398 since: "0",
399 description: "Retired in Phase 1c — collapsed into `.moss-card` (with parent `.moss-cards[data-layout=grid]`).",
400 },
401 ComponentEntry {
402 class: "moss-card-grid-cover",
403 kind: "instance",
404 parent: "moss-card-grid",
405 data_attrs: &[],
406 example_html: r#"<div class="moss-card-grid-cover"><img src="..." /></div>"#,
407 example_markdown: "",
408 status: Status::Retired,
409 since: "0",
410 description: "Retired in Phase 1c — collapsed into `.moss-card-cover`.",
411 },
412 ComponentEntry {
413 class: "moss-card-grid-no-cover",
414 kind: "instance",
415 parent: "moss-card-grid",
416 data_attrs: &[],
417 example_html: r#"<div class="moss-card-grid-cover moss-card-grid-no-cover"></div>"#,
418 example_markdown: "",
419 status: Status::Retired,
420 since: "0",
421 description: "Retired in Phase 1c — collapsed into `.moss-card-no-cover`.",
422 },
423 ComponentEntry {
424 class: "moss-card-grid-content",
425 kind: "instance",
426 parent: "moss-card-grid",
427 data_attrs: &[],
428 example_html: r#"<div class="moss-card-grid-content">...</div>"#,
429 example_markdown: "",
430 status: Status::Retired,
431 since: "0",
432 description: "Retired in Phase 1c — collapsed into `.moss-card-content`.",
433 },
434 ComponentEntry {
435 class: "moss-card-grid-kicker",
436 kind: "instance",
437 parent: "moss-card-grid",
438 data_attrs: &[],
439 example_html: r#"<span class="moss-card-grid-kicker">Category</span>"#,
440 example_markdown: "",
441 status: Status::Retired,
442 since: "0",
443 description: "Retired in Phase 1c — collapsed into `.moss-card-kicker`.",
444 },
445 ComponentEntry {
446 class: "moss-card-grid-title",
447 kind: "instance",
448 parent: "moss-card-grid",
449 data_attrs: &[],
450 example_html: r#"<h3 class="moss-card-grid-title">Page title</h3>"#,
451 example_markdown: "",
452 status: Status::Retired,
453 since: "0",
454 description: "Retired in Phase 1c — collapsed into `.moss-card-title`.",
455 },
456 ComponentEntry {
457 class: "moss-card-grid-meta",
458 kind: "instance",
459 parent: "moss-card-grid",
460 data_attrs: &[],
461 example_html: r#"<div class="moss-card-grid-meta">2024-01-15</div>"#,
462 example_markdown: "",
463 status: Status::Retired,
464 since: "0",
465 description: "Retired in Phase 1c — collapsed into `.moss-card-meta`.",
466 },
467 ComponentEntry {
468 class: "moss-card-list",
469 kind: "instance",
470 parent: "moss-cards-list",
471 data_attrs: &[],
472 example_html: r#"<a class="moss-card-list" href="...">...</a>"#,
473 example_markdown: "",
474 status: Status::Retired,
475 since: "0",
476 description: "Retired in Phase 1c — collapsed into `.moss-card` (with parent `.moss-cards[data-layout=list]`).",
477 },
478 ComponentEntry {
479 class: "moss-card-list-row",
480 kind: "instance",
481 parent: "moss-card-list",
482 data_attrs: &[],
483 example_html: r#"<div class="moss-card-list-row">...</div>"#,
484 example_markdown: "",
485 status: Status::Retired,
486 since: "0",
487 description: "Retired in Phase 1c — collapsed into `.moss-card-row`.",
488 },
489 ComponentEntry {
490 class: "moss-card-list-cover",
491 kind: "instance",
492 parent: "moss-card-list",
493 data_attrs: &[],
494 example_html: r#"<div class="moss-card-list-cover"><img src="..." /></div>"#,
495 example_markdown: "",
496 status: Status::Retired,
497 since: "0",
498 description: "Retired in Phase 1c — collapsed into `.moss-card-cover`.",
499 },
500 ComponentEntry {
501 class: "moss-card-list-body",
502 kind: "instance",
503 parent: "moss-card-list",
504 data_attrs: &[],
505 example_html: r#"<div class="moss-card-list-body">...</div>"#,
506 example_markdown: "",
507 status: Status::Retired,
508 since: "0",
509 description: "Retired in Phase 1c — collapsed into `.moss-card-body`.",
510 },
511 ComponentEntry {
512 class: "moss-card-list-head",
513 kind: "instance",
514 parent: "moss-card-list",
515 data_attrs: &[],
516 example_html: r#"<div class="moss-card-list-head">...</div>"#,
517 example_markdown: "",
518 status: Status::Retired,
519 since: "0",
520 description: "Retired in Phase 1c — collapsed into `.moss-card-head`.",
521 },
522 ComponentEntry {
523 class: "moss-card-list-kicker",
524 kind: "instance",
525 parent: "moss-card-list",
526 data_attrs: &[],
527 example_html: r#"<span class="moss-card-list-kicker">Category</span>"#,
528 example_markdown: "",
529 status: Status::Retired,
530 since: "0",
531 description: "Retired in Phase 1c — collapsed into `.moss-card-kicker`.",
532 },
533 ComponentEntry {
534 class: "moss-card-list-title",
535 kind: "instance",
536 parent: "moss-card-list",
537 data_attrs: &[],
538 example_html: r#"<h3 class="moss-card-list-title">Page title</h3>"#,
539 example_markdown: "",
540 status: Status::Retired,
541 since: "0",
542 description: "Retired in Phase 1c — collapsed into `.moss-card-title`.",
543 },
544 ComponentEntry {
545 class: "moss-card-list-meta",
546 kind: "instance",
547 parent: "moss-card-list",
548 data_attrs: &[],
549 example_html: r#"<div class="moss-card-list-meta">2024-01-15</div>"#,
550 example_markdown: "",
551 status: Status::Retired,
552 since: "0",
553 description: "Retired in Phase 1c — collapsed into `.moss-card-meta`.",
554 },
555 ComponentEntry {
556 class: "moss-card-list-description",
557 kind: "instance",
558 parent: "moss-card-list",
559 data_attrs: &[],
560 example_html: r#"<p class="moss-card-list-description">Excerpt...</p>"#,
561 example_markdown: "",
562 status: Status::Retired,
563 since: "0",
564 description: "Retired in Phase 1c — collapsed into `.moss-card-description`.",
565 },
566 ComponentEntry {
567 class: "moss-card-minimal",
568 kind: "instance",
569 parent: "moss-cards-minimal-year-group",
570 data_attrs: &[],
571 example_html: r#"<div class="moss-card-minimal">
572 <a class="moss-prefix-link" href="...">...</a>
573</div>"#,
574 example_markdown: "",
575 status: Status::Retired,
576 since: "0",
577 description: "Retired in Phase 1c — collapsed into `.moss-card` (with parent `.moss-cards[data-layout=minimal]`).",
578 },
579 ComponentEntry {
580 class: "moss-folder-item",
581 kind: "instance",
582 parent: "moss-cards-minimal-year-group",
583 data_attrs: &[],
584 example_html: r#"<div class="moss-card-minimal moss-folder-item">
585 <a class="moss-prefix-link moss-folder-link" href="...">...</a>
586 <p class="moss-folder-description">...</p>
587</div>"#,
588 example_markdown: "",
589 status: Status::Confirmed,
590 since: "0",
591 description: "Modifier on `.moss-card-minimal` for folder-type entries in minimal listings.",
592 },
593 ComponentEntry {
594 class: "moss-folder-title",
595 kind: "instance",
596 parent: "moss-folder-item",
597 data_attrs: &[],
598 example_html: r#"<span class="moss-folder-title">Folder name</span>"#,
599 example_markdown: "",
600 status: Status::Confirmed,
601 since: "0",
602 description: "Title text of a folder entry in minimal listings.",
603 },
604 ComponentEntry {
605 class: "moss-folder-description",
606 kind: "instance",
607 parent: "moss-folder-item",
608 data_attrs: &[],
609 example_html: r#"<p class="moss-folder-description">Description...</p>"#,
610 example_markdown: "",
611 status: Status::Confirmed,
612 since: "0",
613 description: "Description paragraph of a folder entry in minimal listings.",
614 },
615 ComponentEntry {
616 class: "moss-folder-link",
617 kind: "instance",
618 parent: "moss-folder-item",
619 data_attrs: &[],
620 example_html: r#"<a class="moss-prefix-link moss-folder-link" href="...">...</a>"#,
621 example_markdown: "",
622 status: Status::Confirmed,
623 since: "0",
624 description: "Modifier on `.moss-prefix-link` for folder-type links in minimal listings.",
625 },
626 ComponentEntry {
630 class: "moss-prefix-link",
631 kind: "instance",
632 parent: "moss-card-minimal",
633 data_attrs: &[],
634 example_html: r#"<a class="moss-prefix-link" href="...">
635 <span class="moss-prefix-link-prefix">2024-01-15</span>
636 <span class="moss-prefix-link-title">Page title</span>
637</a>"#,
638 example_markdown: "",
639 status: Status::Emerging,
640 since: "0",
641 description: "Link with a prefix span (date or icon) and a title span. Used inside minimal cards.",
642 },
643 ComponentEntry {
644 class: "moss-prefix-link-prefix",
645 kind: "instance",
646 parent: "moss-prefix-link",
647 data_attrs: &[],
648 example_html: r#"<span class="moss-prefix-link-prefix">2024-01-15</span>"#,
649 example_markdown: "",
650 status: Status::Emerging,
651 since: "0",
652 description: "Prefix slot of a prefix-link (typically a date).",
653 },
654 ComponentEntry {
655 class: "moss-prefix-link-title",
656 kind: "instance",
657 parent: "moss-prefix-link",
658 data_attrs: &[],
659 example_html: r#"<span class="moss-prefix-link-title">Page title</span>"#,
660 example_markdown: "",
661 status: Status::Emerging,
662 since: "0",
663 description: "Title slot of a prefix-link.",
664 },
665 ComponentEntry {
666 class: "moss-prefix-link-suffix",
667 kind: "instance",
668 parent: "moss-prefix-link",
669 data_attrs: &[],
670 example_html: r#"<span class="moss-prefix-link-suffix">→</span>"#,
671 example_markdown: "",
672 status: Status::Emerging,
673 since: "0",
674 description: "Optional trailing slot of a prefix-link.",
675 },
676 ComponentEntry {
682 class: "moss-callout",
683 kind: "standalone",
684 parent: "",
685 data_attrs: &[],
686 example_html: r#"<div class="moss-callout callout" data-type="note">
687 <div class="callout-title">Note</div>
688 <div class="callout-content">Body...</div>
689</div>"#,
690 example_markdown: "> [!note]\n> Body...",
691 status: Status::Confirmed,
692 since: "0",
693 description: "Obsidian-style callout. The Obsidian-compat `.callout` class is co-emitted; type lives on `data-type` (v1).",
694 },
695 ComponentEntry {
696 class: "callout",
697 kind: "standalone",
698 parent: "",
699 data_attrs: &[
700 DataAttr {
701 name: "data-type",
702 values: &["note", "info", "tip", "warning", "pending"],
703 default: "note",
704 description: "v1 callout type. Theme authors target `.callout[data-type=...]` to style by variant.",
705 },
706 ],
707 example_html: r#"<div class="moss-callout callout" data-type="note">...</div>"#,
708 example_markdown: "",
709 status: Status::Confirmed,
710 since: "0",
711 description: "Obsidian-compat class co-emitted on every callout for theme parity. Type lives on `data-type` (v1).",
712 },
713 ComponentEntry {
714 class: "callout-title",
715 kind: "instance",
716 parent: "moss-callout",
717 data_attrs: &[],
718 example_html: r#"<div class="callout-title">Note</div>"#,
719 example_markdown: "",
720 status: Status::Confirmed,
721 since: "0",
722 description: "Title row of a callout.",
723 },
724 ComponentEntry {
725 class: "callout-content",
726 kind: "instance",
727 parent: "moss-callout",
728 data_attrs: &[],
729 example_html: r#"<div class="callout-content">Body...</div>"#,
730 example_markdown: "",
731 status: Status::Confirmed,
732 since: "0",
733 description: "Body container of a callout.",
734 },
735 ComponentEntry {
736 class: "callout-note",
737 kind: "instance",
738 parent: "moss-callout",
739 data_attrs: &[],
740 example_html: r#"<div class="moss-callout callout callout-note">...</div>"#,
741 example_markdown: "> [!note]\n> Body",
742 status: Status::Retired,
743 since: "0",
744 description: "Retired in Phase 1c — type lives on `.callout[data-type=note]`.",
745 },
746 ComponentEntry {
747 class: "callout-info",
748 kind: "instance",
749 parent: "moss-callout",
750 data_attrs: &[],
751 example_html: r#"<div class="moss-callout callout callout-info">...</div>"#,
752 example_markdown: "> [!info]\n> Body",
753 status: Status::Retired,
754 since: "0",
755 description: "Retired in Phase 1c — type lives on `.callout[data-type=info]`.",
756 },
757 ComponentEntry {
758 class: "callout-tip",
759 kind: "instance",
760 parent: "moss-callout",
761 data_attrs: &[],
762 example_html: r#"<div class="moss-callout callout callout-tip">...</div>"#,
763 example_markdown: "> [!tip]\n> Body",
764 status: Status::Retired,
765 since: "0",
766 description: "Retired in Phase 1c — type lives on `.callout[data-type=tip]`.",
767 },
768 ComponentEntry {
769 class: "callout-warning",
770 kind: "instance",
771 parent: "moss-callout",
772 data_attrs: &[],
773 example_html: r#"<div class="moss-callout callout callout-warning">...</div>"#,
774 example_markdown: "> [!warning]\n> Body",
775 status: Status::Retired,
776 since: "0",
777 description: "Retired in Phase 1c — type lives on `.callout[data-type=warning]`.",
778 },
779 ComponentEntry {
780 class: "callout-pending",
781 kind: "instance",
782 parent: "moss-callout",
783 data_attrs: &[],
784 example_html: r#"<div class="moss-callout callout callout-pending">...</div>"#,
785 example_markdown: "> [!pending]\n> Body",
786 status: Status::Retired,
787 since: "0",
788 description: "Retired in Phase 1c — type lives on `.callout[data-type=pending]`.",
789 },
790 ComponentEntry {
795 class: "moss-embed",
796 kind: "standalone",
797 parent: "",
798 data_attrs: &[
799 DataAttr {
800 name: "data-type",
801 values: &["audio", "video", "pdf", "notebook", "table", "iframe", "3d"],
802 default: "",
803 description: "v1 embed kind. Set on the embed element. Theme authors target `.moss-embed[data-type=...]`.",
804 },
805 DataAttr {
806 name: "data-loop",
807 values: &[],
808 default: "",
809 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.",
810 },
811 DataAttr {
812 name: "data-width",
813 values: &["body", "wide", "page", "screen"],
814 default: "body",
815 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9.",
816 },
817 DataAttr {
818 name: "data-provider",
819 values: &["youtube", "vimeo", "codepen"],
820 default: "",
821 description: "Identifies the embed provider for external URL embeds. Absent for generic iframes and local HTML embeds.",
822 },
823 ],
824 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>"#,
825 example_markdown: "![[clip.mp4|loop]]",
826 status: Status::Confirmed,
827 since: "0",
828 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.",
829 },
830 ComponentEntry {
831 class: "moss-embed-audio",
832 kind: "instance",
833 parent: "moss-embed",
834 data_attrs: &[],
835 example_html: r#"<div class="moss-embed moss-embed-audio"><audio controls src="..."></audio></div>"#,
836 example_markdown: "![[track.mp3]]",
837 status: Status::Retired,
838 since: "0",
839 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=audio]`.",
840 },
841 ComponentEntry {
842 class: "moss-embed-video",
843 kind: "instance",
844 parent: "moss-embed",
845 data_attrs: &[],
846 example_html: r#"<div class="moss-embed moss-embed-video"><video controls src="..."></video></div>"#,
847 example_markdown: "![[clip.mp4]]",
848 status: Status::Retired,
849 since: "0",
850 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=video]`.",
851 },
852 ComponentEntry {
853 class: "moss-embed-pdf",
854 kind: "instance",
855 parent: "moss-embed",
856 data_attrs: &[],
857 example_html: r#"<div class="moss-embed moss-embed-pdf"><iframe src="..."></iframe></div>"#,
858 example_markdown: "![[paper.pdf]]",
859 status: Status::Retired,
860 since: "0",
861 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=pdf]`.",
862 },
863 ComponentEntry {
864 class: "moss-embed-iframe",
865 kind: "instance",
866 parent: "moss-embed",
867 data_attrs: &[],
868 example_html: r#"<div class="moss-embed moss-embed-iframe"><iframe src="..."></iframe></div>"#,
869 example_markdown: "![[page.html]]",
870 status: Status::Retired,
871 since: "0",
872 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=iframe]`.",
873 },
874 ComponentEntry {
875 class: "moss-embed-notebook",
876 kind: "instance",
877 parent: "moss-embed",
878 data_attrs: &[],
879 example_html: r#"<div class="moss-embed moss-embed-notebook">...</div>"#,
880 example_markdown: "![[analysis.ipynb]]",
881 status: Status::Retired,
882 since: "0",
883 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=notebook]`.",
884 },
885 ComponentEntry {
886 class: "moss-embed-ipynb",
887 kind: "instance",
888 parent: "moss-embed",
889 data_attrs: &[],
890 example_html: r#"<div class="moss-embed moss-embed-ipynb">...</div>"#,
891 example_markdown: "",
892 status: Status::Emerging,
893 since: "0",
894 description: "Alias of `.moss-embed-notebook`; consolidation pending.",
895 },
896 ComponentEntry {
897 class: "moss-embed-table",
898 kind: "instance",
899 parent: "moss-embed",
900 data_attrs: &[],
901 example_html: r#"<div class="moss-embed moss-embed-table"><table>...</table></div>"#,
902 example_markdown: "![[data.csv]]",
903 status: Status::Retired,
904 since: "0",
905 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=table]`.",
906 },
907 ComponentEntry {
908 class: "moss-embed-3d",
909 kind: "instance",
910 parent: "moss-embed",
911 data_attrs: &[],
912 example_html: r#"<div class="moss-embed moss-embed-3d">...</div>"#,
913 example_markdown: "![[model.glb]]",
914 status: Status::Retired,
915 since: "0",
916 description: "Retired in Phase 1c — collapsed to `.moss-embed[data-type=3d]`.",
917 },
918 ComponentEntry {
919 class: "moss-embed-error",
920 kind: "instance",
921 parent: "moss-embed",
922 data_attrs: &[],
923 example_html: r#"<div class="moss-embed moss-embed-error">File not found: ...</div>"#,
924 example_markdown: "",
925 status: Status::Confirmed,
926 since: "0",
927 description: "Error state for embeds whose target cannot be resolved.",
928 },
929 ComponentEntry {
930 class: "moss-embed-missing",
931 kind: "instance",
932 parent: "moss-embed",
933 data_attrs: &[],
934 example_html: r#"<div class="moss-embed-missing">Folder not found: journal</div>"#,
935 example_markdown: "",
936 status: Status::Confirmed,
937 since: "1",
938 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.",
939 },
940 ComponentEntry {
944 class: "moss-hero",
945 kind: "standalone",
946 parent: "",
947 data_attrs: &[
948 DataAttr {
949 name: "data-width",
950 values: &["body", "wide", "page", "screen"],
951 default: "body",
952 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9. Phase 1c will emit this from authoring shortcode (e.g. `:::hero {full}` -> `data-width=\"screen\"`).",
953 },
954 ],
955 example_html: r#"<section class="moss-hero" data-width="page">
956 <div class="moss-hero-content">...</div>
957</section>"#,
958 example_markdown: ":::hero {image=cover.jpg}\n:::\n",
959 status: Status::Confirmed,
960 since: "0",
961 description: "Hero banner section at the top of a page (cover image + title). v1 adds `data-width` for author-controlled sizing.",
962 },
963 ComponentEntry {
964 class: "moss-hero-content",
965 kind: "instance",
966 parent: "moss-hero",
967 data_attrs: &[],
968 example_html: r#"<div class="moss-hero-content">...</div>"#,
969 example_markdown: "",
970 status: Status::Confirmed,
971 since: "0",
972 description: "Text content slot inside `.moss-hero`.",
973 },
974 ComponentEntry {
975 class: "moss-image",
976 kind: "standalone",
977 parent: "",
978 data_attrs: &[
979 DataAttr {
980 name: "data-aspect",
981 values: &["portrait", "square", "auto"],
982 default: "auto",
983 description: "v1 image aspect-ratio hint. Theme authors target `.moss-image[data-aspect=...]`. Emitter wiring lands in a follow-up.",
984 },
985 DataAttr {
986 name: "data-width",
987 values: &["body", "wide", "page", "screen"],
988 default: "body",
989 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9.",
990 },
991 ],
992 example_html: r#"<figure class="moss-image" style="width:55%"><img src="..." alt="..." /></figure>"#,
993 example_markdown: "",
994 status: Status::Confirmed,
995 since: "0",
996 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.",
997 },
998 ComponentEntry {
999 class: "moss-align-left",
1000 kind: "standalone",
1001 parent: "",
1002 data_attrs: &[],
1003 example_html: r#"<img src="..." alt="..." class="moss-align-left" />"#,
1004 example_markdown: "![[photo.jpg|align-left]]",
1005 status: Status::Confirmed,
1006 since: "0",
1007 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.",
1008 },
1009 ComponentEntry {
1010 class: "moss-align-right",
1011 kind: "standalone",
1012 parent: "",
1013 data_attrs: &[],
1014 example_html: r#"<img src="..." alt="..." class="moss-align-right" />"#,
1015 example_markdown: "![[photo.jpg|align-right]]",
1016 status: Status::Confirmed,
1017 since: "0",
1018 description: "Floats an image to the right of body text (editorial runaround). Symmetric counterpart to `.moss-align-left`. Mirrors WordPress's `alignright` convention.",
1019 },
1020 ComponentEntry {
1021 class: "moss-article-title",
1022 kind: "instance",
1023 parent: "",
1024 data_attrs: &[],
1025 example_html: r#"<h1 class="moss-article-title">Title</h1>"#,
1026 example_markdown: "",
1027 status: Status::Emerging,
1028 since: "0",
1029 description: "Article-page H1 title emitted from frontmatter.",
1030 },
1031 ComponentEntry {
1032 class: "moss-heading-anchor",
1033 kind: "instance",
1034 parent: "",
1035 data_attrs: &[],
1036 example_html: r##"<h2 id="setup">Setup<a class="moss-heading-anchor" href="#setup" aria-label="Permalink to this section"><span aria-hidden="true">#</span></a></h2>"##,
1037 example_markdown: "## Setup",
1038 status: Status::Emerging,
1039 since: "1",
1040 description: "Clickable permalink appended inside every author-written body heading that carries a slug id; links to the heading's `#`-fragment. The auto-injected `moss-article-title` H1 is emitted separately and gets no anchor.",
1041 },
1042 ComponentEntry {
1046 class: "moss-grid",
1047 kind: "container",
1048 parent: "",
1049 data_attrs: &[
1050 DataAttr {
1051 name: "data-width",
1052 values: &["body", "wide", "page", "screen"],
1053 default: "body",
1054 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9.",
1055 },
1056 ],
1057 example_html: r#"<div class="moss-grid" data-width="wide">
1058 <div class="moss-grid-card">...</div>
1059</div>"#,
1060 example_markdown: ":::grid {cols=2}\nLeft cell\n+++\nRight cell\n:::\n",
1061 status: Status::Confirmed,
1062 since: "0",
1063 description: "Generic grid container (used by profiles, link previews, etc.). Modifier classes: `profiles`, `featured`, `no-cards`. v1 adds `data-width` (P9).",
1064 },
1065 ComponentEntry {
1066 class: "moss-grid-card",
1067 kind: "instance",
1068 parent: "moss-grid",
1069 data_attrs: &[
1070 DataAttr {
1071 name: "data-kind",
1072 values: &["link", "friend", "card"],
1073 default: "card",
1074 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.",
1075 },
1076 ],
1077 example_html: r#"<a class="moss-grid-card" data-kind="link" href="...">...</a>"#,
1078 example_markdown: "",
1079 status: Status::Confirmed,
1080 since: "0",
1081 description: "Card instance inside `.moss-grid`. Today emits sibling classes `link-card` / `friend-card` / `no-cards`; v1 collapses to `data-kind`.",
1082 },
1083 ComponentEntry {
1084 class: "moss-gallery",
1085 kind: "container",
1086 parent: "",
1087 data_attrs: &[
1088 DataAttr {
1089 name: "data-width",
1090 values: &["body", "wide", "page", "screen"],
1091 default: "body",
1092 description: "Display width — text-column (body), wider than text (wide), page-width (page), or viewport-width (screen). See spec § P9.",
1093 },
1094 ],
1095 example_html: r#"<div class="moss-gallery" data-width="page">
1096 <figure class="moss-gallery-item">...</figure>
1097</div>"#,
1098 example_markdown: ":::gallery\nphoto.jpg\n:::\n",
1099 status: Status::Confirmed,
1100 since: "0",
1101 description: "Image gallery container. v1 adds `data-width` (P9).",
1102 },
1103 ComponentEntry {
1104 class: "moss-gallery-item",
1105 kind: "instance",
1106 parent: "moss-gallery",
1107 data_attrs: &[],
1108 example_html: r#"<figure class="moss-gallery-item"><img src="..." /></figure>"#,
1109 example_markdown: "",
1110 status: Status::Confirmed,
1111 since: "0",
1112 description: "Single image entry inside `.moss-gallery`.",
1113 },
1114 ComponentEntry {
1115 class: "moss-buttons",
1116 kind: "container",
1117 parent: "",
1118 data_attrs: &[
1119 DataAttr {
1120 name: "data-style",
1121 values: &["default", "inverted"],
1122 default: "default",
1123 description: "v1 button-row style. Theme authors target `.moss-buttons[data-style=...]`.",
1124 },
1125 ],
1126 example_html: r#"<div class="moss-buttons" data-style="inverted">
1127 <a class="moss-btn" href="...">Click</a>
1128</div>"#,
1129 example_markdown: ":::buttons\n[Get started](https://example.com)\n:::\n",
1130 status: Status::Confirmed,
1131 since: "0",
1132 description: "Container for a row of `.moss-btn` buttons. v1: the inverted variant is on `data-style=\"inverted\"`.",
1133 },
1134 ComponentEntry {
1138 class: "moss-btn",
1139 kind: "standalone",
1140 parent: "",
1141 data_attrs: &[
1142 DataAttr {
1143 name: "data-role",
1144 values: &["default", "primary", "secondary"],
1145 default: "default",
1146 description: "v1 button role. Theme authors target `.moss-btn[data-role=...]`.",
1147 },
1148 ],
1149 example_html: r#"<button class="moss-btn" data-role="primary">
1150 <span class="moss-btn__label">Submit</span>
1151</button>"#,
1152 example_markdown: "",
1153 status: Status::Confirmed,
1154 since: "0",
1155 description: "Generic button primitive. Role on `data-role` (v1).",
1156 },
1157 ComponentEntry {
1158 class: "moss-btn__label",
1159 kind: "instance",
1160 parent: "moss-btn",
1161 data_attrs: &[],
1162 example_html: r#"<span class="moss-btn__label">Submit</span>"#,
1163 example_markdown: "",
1164 status: Status::Confirmed,
1165 since: "0",
1166 description: "Label span inside `.moss-btn`.",
1167 },
1168 ComponentEntry {
1169 class: "moss-btn__check",
1170 kind: "instance",
1171 parent: "moss-btn",
1172 data_attrs: &[],
1173 example_html: r#"<span class="moss-btn__check">✓</span>"#,
1174 example_markdown: "",
1175 status: Status::Confirmed,
1176 since: "0",
1177 description: "Success checkmark slot inside `.moss-btn`.",
1178 },
1179 ComponentEntry {
1180 class: "moss-btn__spinner",
1181 kind: "instance",
1182 parent: "moss-btn",
1183 data_attrs: &[],
1184 example_html: r#"<span class="moss-btn__spinner"></span>"#,
1185 example_markdown: "",
1186 status: Status::Confirmed,
1187 since: "0",
1188 description: "Loading spinner slot inside `.moss-btn`.",
1189 },
1190 ComponentEntry {
1194 class: "moss-subscribe",
1195 kind: "standalone",
1196 parent: "",
1197 data_attrs: &[],
1198 example_html: r#"<div class="moss-subscribe">
1199 <form class="moss-subscribe-form">...</form>
1200</div>"#,
1201 example_markdown: ":::subscribe\n:::\n",
1202 status: Status::Confirmed,
1203 since: "0",
1204 description: "Newsletter subscribe block (auto-injected into footer when email channel configured).",
1205 },
1206 ComponentEntry {
1207 class: "moss-subscribe-form",
1208 kind: "instance",
1209 parent: "moss-subscribe",
1210 data_attrs: &[
1211 DataAttr {
1212 name: "data-position",
1213 values: &["inline", "apply"],
1214 default: "inline",
1215 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).",
1216 },
1217 DataAttr {
1218 name: "data-button-override",
1219 values: &["true"],
1220 default: "true",
1221 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.",
1222 },
1223 DataAttr {
1224 name: "data-moss-hosted",
1225 values: &["true"],
1226 default: "true",
1227 description: "Marks moss-hosted (seta) forms hydrated by subscribe.ts. Absent on 3rd-party provider forms.",
1228 },
1229 DataAttr {
1230 name: "data-state",
1231 values: &["idle", "loading", "success", "error"],
1232 default: "idle",
1233 description: "Runtime submit state machine, driven by subscribe.ts. Emitted as `idle`; theme authors target `.moss-subscribe-form[data-state=...]`.",
1234 },
1235 DataAttr {
1236 name: "data-moss-pending-site",
1237 values: &["true"],
1238 default: "true",
1239 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.",
1240 },
1241 ],
1242 example_html: r#"<form class="moss-subscribe-form">...</form>"#,
1243 example_markdown: "",
1244 status: Status::Emerging,
1245 since: "0",
1246 description: "Form element inside `.moss-subscribe`.",
1247 },
1248 ComponentEntry {
1249 class: "moss-btn-slot",
1250 kind: "instance",
1251 parent: "moss-subscribe",
1252 data_attrs: &[],
1253 example_html: r#"<div class="moss-btn-slot"><button class="moss-btn">...</button></div>"#,
1254 example_markdown: "",
1255 status: Status::Emerging,
1256 since: "0",
1257 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.",
1258 },
1259 ComponentEntry {
1260 class: "moss-subscribe-status",
1261 kind: "instance",
1262 parent: "moss-subscribe",
1263 data_attrs: &[],
1264 example_html: r#"<div class="moss-subscribe-status">
1265 <span class="moss-subscribe-status__icon"></span>
1266 Subscribed!
1267</div>"#,
1268 example_markdown: "",
1269 status: Status::Emerging,
1270 since: "0",
1271 description: "Status message shown after submit (success/error).",
1272 },
1273 ComponentEntry {
1274 class: "moss-subscribe-status__icon",
1275 kind: "instance",
1276 parent: "moss-subscribe-status",
1277 data_attrs: &[],
1278 example_html: r#"<span class="moss-subscribe-status__icon"></span>"#,
1279 example_markdown: "",
1280 status: Status::Emerging,
1281 since: "0",
1282 description: "Icon slot inside `.moss-subscribe-status`.",
1283 },
1284 ComponentEntry {
1285 class: "moss-subscribe-landing",
1286 kind: "standalone",
1287 parent: "",
1288 data_attrs: &[],
1289 example_html: r#"<section class="moss-subscribe-landing">...</section>"#,
1290 example_markdown: "",
1291 status: Status::Emerging,
1292 since: "0",
1293 description: "Standalone subscribe landing page surface (larger variant).",
1294 },
1295 ComponentEntry {
1299 class: "moss-apply",
1300 kind: "standalone",
1301 parent: "",
1302 data_attrs: &[],
1303 example_html: r#"<div class="moss-apply" data-state="idle">
1304 <form class="moss-subscribe-form moss-apply-form">...</form>
1305</div>"#,
1306 example_markdown: ":::apply\n:::\n",
1307 status: Status::Emerging,
1308 since: "0",
1309 description: "Apply / membership-request form block (:::apply shortcode).",
1310 },
1311 ComponentEntry {
1312 class: "moss-apply-form",
1313 kind: "instance",
1314 parent: "moss-apply",
1315 data_attrs: &[
1316 DataAttr {
1317 name: "data-position",
1318 values: &["apply"],
1319 default: "apply",
1320 description: "Position variant; always `apply` for this form. Drives CSS layout in email.css.",
1321 },
1322 DataAttr {
1323 name: "data-revert",
1324 values: &["false"],
1325 default: "false",
1326 description: "When `false`, success is terminal (no auto-revert). subscribe.ts reads this.",
1327 },
1328 ],
1329 example_html: r#"<form class="moss-subscribe-form moss-apply-form" data-position="apply" data-revert="false">...</form>"#,
1330 example_markdown: "",
1331 status: Status::Emerging,
1332 since: "0",
1333 description: "Form element inside `.moss-apply`. Also carries `.moss-subscribe-form` so subscribe.ts hydrates it.",
1334 },
1335 ComponentEntry {
1336 class: "moss-apply-matters",
1337 kind: "instance",
1338 parent: "moss-apply",
1339 data_attrs: &[],
1340 example_html: r#"<input type="text" name="matters" class="moss-input moss-apply-matters">"#,
1341 example_markdown: "",
1342 status: Status::Emerging,
1343 since: "0",
1344 description: "Second apply-form input inside `.moss-apply-form` — a Matters username OR a one-line pitch (placeholder-only, no visible label).",
1345 },
1346 ComponentEntry {
1347 class: "moss-apply-hp",
1348 kind: "instance",
1349 parent: "moss-apply",
1350 data_attrs: &[],
1351 example_html: r#"<input type="text" name="website" class="moss-apply-hp" tabindex="-1" aria-hidden="true">"#,
1352 example_markdown: "",
1353 status: Status::Emerging,
1354 since: "0",
1355 description: "Honeypot field (off-screen) inside `.moss-apply-form`. Bots fill it; humans don't.",
1356 },
1357 ComponentEntry {
1358 class: "moss-apply-status",
1359 kind: "instance",
1360 parent: "moss-apply",
1361 data_attrs: &[],
1362 example_html: r#"<div class="moss-subscribe-status moss-apply-status" aria-live="polite">...</div>"#,
1363 example_markdown: "",
1364 status: Status::Emerging,
1365 since: "0",
1366 description: "Status region inside `.moss-apply-form` (also carries `.moss-subscribe-status`).",
1367 },
1368 ComponentEntry {
1369 class: "moss-apply-helper",
1370 kind: "instance",
1371 parent: "moss-apply",
1372 data_attrs: &[],
1373 example_html: r#"<p class="moss-apply-helper" id="moss-apply-email-help">用于获取邀请及免费托管服务</p>"#,
1374 example_markdown: "",
1375 status: Status::Emerging,
1376 since: "0",
1377 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.",
1378 },
1379 ComponentEntry {
1383 class: "moss-series-nav",
1384 kind: "standalone",
1385 parent: "",
1386 data_attrs: &[],
1387 example_html: r#"<nav class="moss-series-nav">
1388 <div class="moss-series-nav-links">...</div>
1389</nav>"#,
1390 example_markdown: "",
1391 status: Status::Confirmed,
1392 since: "0",
1393 description: "Series navigation bar (prev/next/collection) on series pages.",
1394 },
1395 ComponentEntry {
1396 class: "moss-series-nav-links",
1397 kind: "instance",
1398 parent: "moss-series-nav",
1399 data_attrs: &[],
1400 example_html: r#"<div class="moss-series-nav-links">...</div>"#,
1401 example_markdown: "",
1402 status: Status::Confirmed,
1403 since: "0",
1404 description: "Row holding prev/next links in series nav.",
1405 },
1406 ComponentEntry {
1407 class: "moss-series-nav-link",
1408 kind: "instance",
1409 parent: "moss-series-nav",
1410 data_attrs: &[],
1411 example_html: r#"<a class="moss-series-nav-link moss-series-nav-prev" href="...">...</a>"#,
1412 example_markdown: "",
1413 status: Status::Confirmed,
1414 since: "0",
1415 description: "Individual link inside series nav. Modifiers: `moss-series-nav-prev`, `moss-series-nav-next`, `empty` (placeholder).",
1416 },
1417 ComponentEntry {
1418 class: "moss-series-nav-prev",
1419 kind: "instance",
1420 parent: "moss-series-nav",
1421 data_attrs: &[],
1422 example_html: r#"<a class="moss-series-nav-link moss-series-nav-prev" href="...">...</a>"#,
1423 example_markdown: "",
1424 status: Status::Confirmed,
1425 since: "0",
1426 description: "Previous-page modifier on a series nav link.",
1427 },
1428 ComponentEntry {
1429 class: "moss-series-nav-next",
1430 kind: "instance",
1431 parent: "moss-series-nav",
1432 data_attrs: &[],
1433 example_html: r#"<a class="moss-series-nav-link moss-series-nav-next" href="...">...</a>"#,
1434 example_markdown: "",
1435 status: Status::Confirmed,
1436 since: "0",
1437 description: "Next-page modifier on a series nav link.",
1438 },
1439 ComponentEntry {
1440 class: "moss-series-nav-arrow",
1441 kind: "instance",
1442 parent: "moss-series-nav",
1443 data_attrs: &[],
1444 example_html: r#"<span class="moss-series-nav-arrow">→</span>"#,
1445 example_markdown: "",
1446 status: Status::Confirmed,
1447 since: "0",
1448 description: "Arrow glyph inside a series-nav link.",
1449 },
1450 ComponentEntry {
1451 class: "moss-series-nav-title",
1452 kind: "instance",
1453 parent: "moss-series-nav",
1454 data_attrs: &[],
1455 example_html: r#"<span class="moss-series-nav-title">Next page title</span>"#,
1456 example_markdown: "",
1457 status: Status::Confirmed,
1458 since: "0",
1459 description: "Title text of the destination page in a series-nav link.",
1460 },
1461 ComponentEntry {
1462 class: "moss-series-nav-collection",
1463 kind: "instance",
1464 parent: "moss-series-nav",
1465 data_attrs: &[],
1466 example_html: r#"<div class="moss-series-nav-collection">...</div>"#,
1467 example_markdown: "",
1468 status: Status::Confirmed,
1469 since: "0",
1470 description: "Collection-listing slot in series nav (sibling pages).",
1471 },
1472 ComponentEntry {
1473 class: "moss-series-nav-collection-row",
1474 kind: "instance",
1475 parent: "moss-series-nav-collection",
1476 data_attrs: &[],
1477 example_html: r#"<div class="moss-series-nav-collection-row">...</div>"#,
1478 example_markdown: "",
1479 status: Status::Confirmed,
1480 since: "0",
1481 description: "Row inside the collection listing of series nav.",
1482 },
1483 ComponentEntry {
1487 class: "moss-collection-cover",
1488 kind: "standalone",
1489 parent: "",
1490 data_attrs: &[],
1491 example_html: r#"<section class="moss-collection-cover">
1492 <div class="moss-collection-cover-row">...</div>
1493</section>"#,
1494 example_markdown: "",
1495 status: Status::Emerging,
1496 since: "0",
1497 description: "Header surface on a collection landing page.",
1498 },
1499 ComponentEntry {
1500 class: "moss-collection-cover-row",
1501 kind: "instance",
1502 parent: "moss-collection-cover",
1503 data_attrs: &[],
1504 example_html: r#"<div class="moss-collection-cover-row">...</div>"#,
1505 example_markdown: "",
1506 status: Status::Emerging,
1507 since: "0",
1508 description: "Row inside `.moss-collection-cover`.",
1509 },
1510 ComponentEntry {
1511 class: "moss-collection-cover-body",
1512 kind: "instance",
1513 parent: "moss-collection-cover",
1514 data_attrs: &[],
1515 example_html: r#"<div class="moss-collection-cover-body">...</div>"#,
1516 example_markdown: "",
1517 status: Status::Emerging,
1518 since: "0",
1519 description: "Body content slot inside `.moss-collection-cover`.",
1520 },
1521 ComponentEntry {
1525 class: "moss-input",
1526 kind: "standalone",
1527 parent: "",
1528 data_attrs: &[],
1529 example_html: r#"<input class="moss-input" type="email" />"#,
1530 example_markdown: "",
1531 status: Status::Confirmed,
1532 since: "0",
1533 description: "Generic form input primitive.",
1534 },
1535 ComponentEntry {
1536 class: "moss-field",
1537 kind: "container",
1538 parent: "",
1539 data_attrs: &[],
1540 example_html: r#"<div class="moss-field">
1541 <label class="moss-label">Email</label>
1542 <input class="moss-input" />
1543</div>"#,
1544 example_markdown: "",
1545 status: Status::Confirmed,
1546 since: "0",
1547 description: "Form field group (label + input). Modifier `--inline` for horizontal layout.",
1548 },
1549 ComponentEntry {
1550 class: "moss-label",
1551 kind: "instance",
1552 parent: "moss-field",
1553 data_attrs: &[],
1554 example_html: r#"<label class="moss-label">Email</label>"#,
1555 example_markdown: "",
1556 status: Status::Confirmed,
1557 since: "0",
1558 description: "Label primitive for `.moss-field`. Modifier `--small` for compact form.",
1559 },
1560 ComponentEntry {
1561 class: "moss-link",
1562 kind: "standalone",
1563 parent: "",
1564 data_attrs: &[],
1565 example_html: r#"<a class="moss-link" href="...">Click me</a>"#,
1566 example_markdown: "",
1567 status: Status::Confirmed,
1568 since: "0",
1569 description: "Inline-link primitive (resets `<button>` chrome too). Use `--subtle` for muted variant.",
1570 },
1571 ComponentEntry {
1572 class: "moss-field--inline",
1573 kind: "instance",
1574 parent: "moss-field",
1575 data_attrs: &[],
1576 example_html: r#"<div class="moss-field moss-field--inline">
1577 <label class="moss-label">Email</label>
1578 <input class="moss-input" />
1579</div>"#,
1580 example_markdown: "",
1581 status: Status::Confirmed,
1582 since: "0",
1583 description: "BEM modifier on `.moss-field` for horizontal label+input layout (used by settings UI primitives).",
1584 },
1585 ComponentEntry {
1586 class: "moss-label--small",
1587 kind: "instance",
1588 parent: "moss-label",
1589 data_attrs: &[],
1590 example_html: r#"<label class="moss-label moss-label--small">Compact label</label>"#,
1591 example_markdown: "",
1592 status: Status::Confirmed,
1593 since: "0",
1594 description: "BEM modifier on `.moss-label` for compact form (used by services settings rows).",
1595 },
1596 ComponentEntry {
1597 class: "moss-info-grid",
1598 kind: "container",
1599 parent: "",
1600 data_attrs: &[],
1601 example_html: r#"<div class="moss-info-grid">
1602 <div class="moss-field moss-field--inline">...</div>
1603 <div class="moss-field moss-field--inline">...</div>
1604</div>"#,
1605 example_markdown: "",
1606 status: Status::Emerging,
1607 since: "0",
1608 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.",
1609 },
1610 ComponentEntry {
1611 class: "moss-row",
1612 kind: "container",
1613 parent: "",
1614 data_attrs: &[],
1615 example_html: r#"<div class="moss-row">
1616 <div class="moss-field">...</div>
1617 <div class="moss-field">...</div>
1618</div>"#,
1619 example_markdown: "",
1620 status: Status::Emerging,
1621 since: "0",
1622 description: "Horizontal flex row of equal-flex `.moss-field` children. Form-row layout helper shipped in the default theme.",
1623 },
1624 ComponentEntry {
1625 class: "moss-input-feedback",
1626 kind: "instance",
1627 parent: "moss-field",
1628 data_attrs: &[],
1629 example_html: r#"<span class="moss-input-feedback">Saving…</span>"#,
1630 example_markdown: "",
1631 status: Status::Emerging,
1632 since: "0",
1633 description: "Auto-save status hint slot under `.moss-field`. Three state modifiers: `--success`, `--error`, `--fade-out`.",
1634 },
1635 ComponentEntry {
1636 class: "moss-input-feedback--success",
1637 kind: "instance",
1638 parent: "moss-input-feedback",
1639 data_attrs: &[],
1640 example_html: r#"<span class="moss-input-feedback moss-input-feedback--success">Saved</span>"#,
1641 example_markdown: "",
1642 status: Status::Emerging,
1643 since: "0",
1644 description: "Success state modifier on `.moss-input-feedback`.",
1645 },
1646 ComponentEntry {
1647 class: "moss-input-feedback--error",
1648 kind: "instance",
1649 parent: "moss-input-feedback",
1650 data_attrs: &[],
1651 example_html: r#"<span class="moss-input-feedback moss-input-feedback--error">Failed to save</span>"#,
1652 example_markdown: "",
1653 status: Status::Emerging,
1654 since: "0",
1655 description: "Error state modifier on `.moss-input-feedback`.",
1656 },
1657 ComponentEntry {
1658 class: "moss-input-feedback--fade-out",
1659 kind: "instance",
1660 parent: "moss-input-feedback",
1661 data_attrs: &[],
1662 example_html: r#"<span class="moss-input-feedback moss-input-feedback--success moss-input-feedback--fade-out">Saved</span>"#,
1663 example_markdown: "",
1664 status: Status::Emerging,
1665 since: "0",
1666 description: "Transient fade-out modifier on `.moss-input-feedback` (applied after a success message to dismiss it).",
1667 },
1668 ComponentEntry {
1672 class: "moss-comments",
1673 kind: "standalone",
1674 parent: "",
1675 data_attrs: &[],
1676 example_html: r#"<section class="moss-comments">...</section>"#,
1677 example_markdown: "",
1678 status: Status::Confirmed,
1679 since: "0",
1680 description: "Comments surface (per-site SQLite backend or Artalk legacy).",
1681 },
1682 ComponentEntry {
1683 class: "moss-service-inactive",
1684 kind: "instance",
1685 parent: "",
1686 data_attrs: &[],
1687 example_html: r#"<section class="moss-comments moss-service-inactive">...</section>"#,
1688 example_markdown: "",
1689 status: Status::Confirmed,
1690 since: "0",
1691 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.",
1692 },
1693 ComponentEntry {
1697 class: "moss-preview-popup",
1698 kind: "chrome",
1699 parent: "",
1700 data_attrs: &[],
1701 example_html: r#"<div class="moss-preview-popup" role="tooltip" aria-live="polite">
1702 <strong class="moss-preview-title">...</strong>
1703 <p class="moss-preview-desc">...</p>
1704 <p class="moss-preview-text">...</p>
1705</div>"#,
1706 example_markdown: "",
1707 status: Status::Confirmed,
1708 since: "0",
1709 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.",
1710 },
1711 ComponentEntry {
1712 class: "moss-preview-title",
1713 kind: "instance",
1714 parent: "moss-preview-popup",
1715 data_attrs: &[],
1716 example_html: r#"<strong class="moss-preview-title">Article title</strong>"#,
1717 example_markdown: "",
1718 status: Status::Confirmed,
1719 since: "0",
1720 description: "Title slot inside `.moss-preview-popup`.",
1721 },
1722 ComponentEntry {
1723 class: "moss-preview-desc",
1724 kind: "instance",
1725 parent: "moss-preview-popup",
1726 data_attrs: &[],
1727 example_html: r#"<p class="moss-preview-desc">Short description</p>"#,
1728 example_markdown: "",
1729 status: Status::Confirmed,
1730 since: "0",
1731 description: "Description slot inside `.moss-preview-popup` (from frontmatter `description`).",
1732 },
1733 ComponentEntry {
1734 class: "moss-preview-text",
1735 kind: "instance",
1736 parent: "moss-preview-popup",
1737 data_attrs: &[],
1738 example_html: r#"<p class="moss-preview-text">Excerpt of the linked article…</p>"#,
1739 example_markdown: "",
1740 status: Status::Confirmed,
1741 since: "0",
1742 description: "Excerpt slot inside `.moss-preview-popup` (auto-extracted from the linked article body).",
1743 },
1744 ComponentEntry {
1750 class: "moss-img-fallback",
1751 kind: "chrome",
1752 parent: "",
1753 data_attrs: &[],
1754 example_html: r#"<img class="site-logo moss-img-fallback" src="data:image/svg+xml,..." alt="" aria-hidden="true">"#,
1755 example_markdown: "",
1756 status: Status::Confirmed,
1757 since: "0",
1758 description: "Marker class a capture-phase `error` listener (inlined in shell.html, not a separate hashed asset) adds to any <img> whose load fails (deleted/renamed/typo'd source — never enters moss's AssetRegistry, so nothing server-side can placeholder it). The browser's native broken-image icon never appears: the script swaps the <img>'s OWN `src` in place to a self-contained blueprint-grid-pattern SVG data URI (same blueprint-blue as the animated frontend/app/components/blueprint-grid.ts canvas, without the per-instance canvas/RAF cost) and strips any enclosing <picture>'s <source> children — it does NOT replace the element, so every context-specific sizing/fit rule (.moss-card-cover > img, .moss-hero img, .site-logo, …) keeps applying because the <img>'s tag, class list, and other attributes are untouched.",
1759 },
1760 ComponentEntry {
1761 class: "moss-colophon",
1762 kind: "chrome",
1763 parent: "",
1764 data_attrs: &[],
1765 example_html: r#"<footer class="moss-colophon">
1766 <span class="moss-colophon-icon"></span>
1767 Built with moss
1768</footer>"#,
1769 example_markdown: "",
1770 status: Status::Confirmed,
1771 since: "0",
1772 description: "Footer colophon credit appended by moss.",
1773 },
1774 ComponentEntry {
1775 class: "moss-colophon-icon",
1776 kind: "instance",
1777 parent: "moss-colophon",
1778 data_attrs: &[],
1779 example_html: r#"<span class="moss-colophon-icon"></span>"#,
1780 example_markdown: "",
1781 status: Status::Confirmed,
1782 since: "0",
1783 description: "Icon slot inside `.moss-colophon`.",
1784 },
1785 ComponentEntry {
1786 class: "moss-shell-frame",
1787 kind: "chrome",
1788 parent: "",
1789 data_attrs: &[],
1790 example_html: r#"<div class="moss-shell-frame">...</div>"#,
1791 example_markdown: "",
1792 status: Status::Emerging,
1793 since: "0",
1794 description: "App-shell frame surface (preview chrome).",
1795 },
1796 ComponentEntry {
1797 class: "moss-mobile-frame",
1798 kind: "chrome",
1799 parent: "moss-shell-frame",
1800 data_attrs: &[],
1801 example_html: r#"<html class="moss-shell-frame moss-mobile-frame">...</html>"#,
1802 example_markdown: "",
1803 status: Status::Emerging,
1804 since: "0",
1805 description: "Runtime marker the preview bridge adds to `<html>` when the shell is in mobile device-preview mode; drops the titlebar-clearance padding (the phone frame sits below the titlebar).",
1806 },
1807 ComponentEntry {
1808 class: "main-nav",
1809 kind: "chrome",
1810 parent: "",
1811 data_attrs: &[],
1812 example_html: r#"<nav class="main-nav container">...</nav>"#,
1813 example_markdown: "",
1814 status: Status::Confirmed,
1815 since: "0",
1816 description: "Top site navigation bar. Legacy non-`moss-` prefix kept for theme parity.",
1817 },
1818 ComponentEntry {
1819 class: "moss-child-section-divider",
1820 kind: "instance",
1821 parent: "",
1822 data_attrs: &[],
1823 example_html: r#"<hr class="moss-child-section-divider" />"#,
1824 example_markdown: "",
1825 status: Status::Emerging,
1826 since: "0",
1827 description: "Divider rule between auto-generated child sections.",
1828 },
1829 ComponentEntry {
1830 class: "moss-unknown-shortcode",
1831 kind: "standalone",
1832 parent: "",
1833 data_attrs: &[],
1834 example_html: r#"<div class="moss-unknown-shortcode">Unknown shortcode: foo</div>"#,
1835 example_markdown: "{{< foo >}}",
1836 status: Status::Confirmed,
1837 since: "0",
1838 description: "Fallback emitted when a shortcode tag is not recognised by any plugin.",
1839 },
1840 ComponentEntry {
1844 class: "moss-hl-keyword",
1845 kind: "instance",
1846 parent: "",
1847 data_attrs: &[],
1848 example_html: r#"<span class="moss-hl-keyword">if</span>"#,
1849 example_markdown: "",
1850 status: Status::Emerging,
1851 since: "0",
1852 description: "Syntax-highlight token: keyword.",
1853 },
1854 ComponentEntry {
1855 class: "moss-hl-string",
1856 kind: "instance",
1857 parent: "",
1858 data_attrs: &[],
1859 example_html: r#"<span class="moss-hl-string">"hi"</span>"#,
1860 example_markdown: "",
1861 status: Status::Emerging,
1862 since: "0",
1863 description: "Syntax-highlight token: string literal.",
1864 },
1865 ComponentEntry {
1866 class: "moss-hl-comment",
1867 kind: "instance",
1868 parent: "",
1869 data_attrs: &[],
1870 example_html: r#"<span class="moss-hl-comment">// note</span>"#,
1871 example_markdown: "",
1872 status: Status::Emerging,
1873 since: "0",
1874 description: "Syntax-highlight token: comment.",
1875 },
1876 ComponentEntry {
1877 class: "moss-hl-function",
1878 kind: "instance",
1879 parent: "",
1880 data_attrs: &[],
1881 example_html: r#"<span class="moss-hl-function">render</span>"#,
1882 example_markdown: "",
1883 status: Status::Emerging,
1884 since: "0",
1885 description: "Syntax-highlight token: function name.",
1886 },
1887 ComponentEntry {
1888 class: "moss-hl-type",
1889 kind: "instance",
1890 parent: "",
1891 data_attrs: &[],
1892 example_html: r#"<span class="moss-hl-type">String</span>"#,
1893 example_markdown: "",
1894 status: Status::Emerging,
1895 since: "0",
1896 description: "Syntax-highlight token: type name.",
1897 },
1898 ComponentEntry {
1899 class: "moss-hl-number",
1900 kind: "instance",
1901 parent: "",
1902 data_attrs: &[],
1903 example_html: r#"<span class="moss-hl-number">42</span>"#,
1904 example_markdown: "",
1905 status: Status::Emerging,
1906 since: "0",
1907 description: "Syntax-highlight token: numeric literal.",
1908 },
1909 ComponentEntry {
1910 class: "moss-hl-operator",
1911 kind: "instance",
1912 parent: "",
1913 data_attrs: &[],
1914 example_html: r#"<span class="moss-hl-operator">+</span>"#,
1915 example_markdown: "",
1916 status: Status::Emerging,
1917 since: "0",
1918 description: "Syntax-highlight token: operator.",
1919 },
1920 ComponentEntry {
1921 class: "moss-hl-builtin",
1922 kind: "instance",
1923 parent: "",
1924 data_attrs: &[],
1925 example_html: r#"<span class="moss-hl-builtin">print</span>"#,
1926 example_markdown: "",
1927 status: Status::Emerging,
1928 since: "0",
1929 description: "Syntax-highlight token: builtin identifier.",
1930 },
1931 ComponentEntry {
1932 class: "moss-hl-tag",
1933 kind: "instance",
1934 parent: "",
1935 data_attrs: &[],
1936 example_html: r#"<span class="moss-hl-tag">div</span>"#,
1937 example_markdown: "",
1938 status: Status::Emerging,
1939 since: "0",
1940 description: "Syntax-highlight token: markup tag name.",
1941 },
1942 ComponentEntry {
1943 class: "moss-hl-attr",
1944 kind: "instance",
1945 parent: "",
1946 data_attrs: &[],
1947 example_html: r#"<span class="moss-hl-attr">class</span>"#,
1948 example_markdown: "",
1949 status: Status::Emerging,
1950 since: "0",
1951 description: "Syntax-highlight token: attribute name.",
1952 },
1953 ComponentEntry {
1954 class: "moss-hl-meta",
1955 kind: "instance",
1956 parent: "",
1957 data_attrs: &[],
1958 example_html: r#"<span class="moss-hl-meta">@derive</span>"#,
1959 example_markdown: "",
1960 status: Status::Emerging,
1961 since: "0",
1962 description: "Syntax-highlight token: meta/annotation.",
1963 },
1964 ComponentEntry {
1965 class: "moss-hl-addition-bg",
1966 kind: "instance",
1967 parent: "",
1968 data_attrs: &[],
1969 example_html: r#"<span class="moss-hl-addition-bg">+ added line</span>"#,
1970 example_markdown: "",
1971 status: Status::Emerging,
1972 since: "0",
1973 description: "Syntax-highlight diff token: added-line background.",
1974 },
1975 ComponentEntry {
1976 class: "moss-hl-deletion",
1977 kind: "instance",
1978 parent: "",
1979 data_attrs: &[],
1980 example_html: r#"<span class="moss-hl-deletion">- removed line</span>"#,
1981 example_markdown: "",
1982 status: Status::Emerging,
1983 since: "0",
1984 description: "Syntax-highlight diff token: removed-line text.",
1985 },
1986 ComponentEntry {
1987 class: "moss-hl-deletion-bg",
1988 kind: "instance",
1989 parent: "",
1990 data_attrs: &[],
1991 example_html: r#"<span class="moss-hl-deletion-bg">- removed line</span>"#,
1992 example_markdown: "",
1993 status: Status::Emerging,
1994 since: "0",
1995 description: "Syntax-highlight diff token: removed-line background.",
1996 },
1997 ComponentEntry {
1998 class: "moss-recent",
1999 kind: "container",
2000 parent: "",
2001 data_attrs: &[],
2002 example_html: r#"<ul class="moss-recent">
2003 <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>
2004</ul>"#,
2005 example_markdown: ":::recent {count=5 since=\"2026-01-01\"}\n:::\n",
2006 status: Status::Emerging,
2007 since: "0",
2008 description: "Auto-generated list of recent posts emitted by the `:::recent` shortcode. Sorted newest-first; date and description slots are filled per child. No default CSS in the bundled theme — theme authors style it freely.",
2009 },
2010 ComponentEntry {
2011 class: "moss-recent__date",
2012 kind: "instance",
2013 parent: "moss-recent",
2014 data_attrs: &[],
2015 example_html: r#"<div class="moss-recent__date">2026-04-12</div>"#,
2016 example_markdown: "",
2017 status: Status::Emerging,
2018 since: "0",
2019 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.",
2020 },
2021 ComponentEntry {
2022 class: "moss-recent__desc",
2023 kind: "instance",
2024 parent: "moss-recent",
2025 data_attrs: &[],
2026 example_html: r#"<div class="moss-recent__desc">A walk through the garden.</div>"#,
2027 example_markdown: "",
2028 status: Status::Emerging,
2029 since: "0",
2030 description: "Per-entry description slot inside `.moss-recent` (BEM child). Sourced from frontmatter `description`; empty when unset.",
2031 },
2032 ComponentEntry {
2038 class: "moss-ambient-video",
2039 kind: "standalone",
2040 parent: "",
2041 data_attrs: &[
2042 DataAttr {
2043 name: "data-paused",
2044 values: &[],
2045 default: "",
2046 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.",
2047 },
2048 ],
2049 example_html: r#"<div class="moss-ambient-video">
2050 <video data-loop src="clip.mp4" autoplay muted loop playsinline preload="metadata"></video>
2051 <button class="moss-ambient-toggle" type="button" aria-label="Pause video">⏸</button>
2052</div>"#,
2053 example_markdown: "![[clip.mp4|loop]]",
2054 status: Status::Emerging,
2055 since: "1",
2056 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.",
2057 },
2058 ComponentEntry {
2059 class: "moss-ambient-toggle",
2060 kind: "instance",
2061 parent: "moss-ambient-video",
2062 data_attrs: &[],
2063 example_html: r#"<button class="moss-ambient-toggle" type="button" aria-label="Pause video">⏸</button>"#,
2064 example_markdown: "",
2065 status: Status::Emerging,
2066 since: "1",
2067 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).",
2068 },
2069 ComponentEntry {
2076 class: "moss-math",
2077 kind: "standalone",
2078 parent: "",
2079 data_attrs: &[
2080 DataAttr {
2081 name: "data-moss-math",
2082 values: &["inline", "display"],
2083 default: "inline",
2084 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.",
2085 },
2086 ],
2087 example_html: r#"<code class="moss-math" data-moss-math="inline">$E = mc^2$</code>"#,
2088 example_markdown: "Energy $E = mc^2$.",
2089 status: Status::Emerging,
2090 since: "1",
2091 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).",
2092 },
2093 ComponentEntry {
2094 class: "moss-math-scroll",
2095 kind: "container",
2096 parent: "",
2097 data_attrs: &[],
2098 example_html: r#"<div class="moss-math-scroll"><svg class="moss-math" data-moss-math="display">…</svg></div>"#,
2099 example_markdown: "$$E = mc^2$$",
2100 status: Status::Emerging,
2101 since: "1",
2102 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%`.",
2103 },
2104];
2105
2106const INTERNAL_CLASSES: &[&str] = &[
2112 "moss-apply",
2113 "moss-apply-form",
2114 "moss-apply-matters",
2115 "moss-apply-hp",
2116 "moss-apply-status",
2117 "moss-apply-helper",
2118];
2119
2120impl ComponentEntry {
2121 pub fn is_public(&self) -> bool {
2128 self.status != Status::Retired && !INTERNAL_CLASSES.contains(&self.class)
2129 }
2130}
2131
2132pub fn retired_class_names() -> impl Iterator<Item = &'static str> {
2138 COMPONENTS.iter()
2139 .filter(|e| e.status == Status::Retired)
2140 .map(|e| e.class)
2141}
2142
2143#[cfg(test)]
2144mod tests {
2145 use super::*;
2146
2147 #[test]
2153 fn every_internal_class_has_a_components_entry() {
2154 let component_classes: std::collections::HashSet<&'static str> =
2155 COMPONENTS.iter().map(|e| e.class).collect();
2156 for &internal in INTERNAL_CLASSES {
2157 assert!(
2158 component_classes.contains(internal),
2159 "INTERNAL_CLASSES entry '{}' has no matching entry in COMPONENTS — \
2160 add a ComponentEntry for it or remove it from INTERNAL_CLASSES",
2161 internal
2162 );
2163 }
2164 }
2165}