1pub struct CustomProp {
40 pub name: &'static str,
42 pub owner: &'static str,
46 pub default: &'static str,
49 pub description: &'static str,
51}
52
53pub const CUSTOM_PROPS: &[CustomProp] = &[
66 CustomProp {
67 name: "--moss-hero-max-height",
68 owner: "moss-hero",
69 default: "70vh",
70 description: "Cap on hero media height on desktop. Set `none` for a hero that fills its container. Note the wrapper has its own cap — `.moss-hero { max-height: min(80vh, 800px) }` reads the same property, so setting it once lifts both.",
71 },
72 CustomProp {
73 name: "--moss-hero-object-position",
74 owner: "moss-hero",
75 default: "top",
76 description: "Crop anchor for hero media, which is `object-fit: cover`. The default anchors the top, which suits landscapes; use `center` for portraits and faces.",
77 },
78 CustomProp {
79 name: "--moss-nav-island-display",
80 owner: "moss-nav-island",
81 default: "block",
82 description: "Set to `none` to turn the floating nav island off site-wide — the page then behaves as it did before ADR-049: the masthead scrolls away and nothing replaces it. This is the island's whole tuning surface on purpose; its measure already tracks `--moss-nav-width`/`--moss-content-width`, so widening the nav widens the island with it.",
83 },
84 CustomProp {
85 name: "--moss-hint-x",
86 owner: "[data-tooltip]",
87 default: "0px",
88 description: "Horizontal offset of a hover hint's pill from its host's start edge. Written per-element at runtime by theme.js (hint-place.ts), which measures the pill on hover/focus entry and clamps it into the viewport so a hint can never crop at a screen edge. Not a theme hook: a hand-set value is overwritten on the next hover.",
89 },
90 CustomProp {
91 name: "--moss-hint-max-w",
92 owner: "[data-tooltip]",
93 default: "calc(100vw - 24px)",
94 description: "Widest a hover hint's pill may get before it wraps. Written per-element at runtime by theme.js (hint-place.ts) alongside `--moss-hint-x`, because the CSS fallback's `100vw` counts the scrollbar gutter as usable space and no CSS length can subtract it. Not a theme hook: a hand-set value is overwritten on the next hover.",
95 },
96 CustomProp {
97 name: "--moss-grid-ratio",
98 owner: "moss-grid",
99 default: "repeat(N, minmax(0, 1fr))",
100 description: "Track widths for a `:::grid`, as a `grid-template-columns` value. moss sets it on the element when the author writes a ratio (`:::grid 2 1:2` → `2fr 1fr`); the fallback is the even split for whatever `data-columns` says, and a ratio-less grid with no `data-columns` falls back to `initial`. It is a property rather than an inline `grid-template-columns` on purpose: an inline declaration beats every stylesheet rule, including the mobile collapse, so a ratio grid stayed multi-column on a phone. A theme setting this by hand overrides the author's ratio at every width — the mobile collapse still wins, because that rule does not read the property.",
101 },
102 CustomProp {
103 name: "--moss-grid-image-ratio",
104 owner: "moss-grid-card",
105 default: "1 / 1",
106 description: "Aspect ratio of images inside a `:::grid`. Set to the source art's own ratio when the image is a designed artifact whose edges carry meaning (a poster, a titled tile) rather than a photograph.",
107 },
108 CustomProp {
109 name: "--moss-grid-image-radius",
110 owner: "moss-grid-card",
111 default: "8px",
112 description: "Corner radius of grid images. `50%` makes circular portraits; `0` suits art that has its own designed corners.",
113 },
114 CustomProp {
115 name: "--moss-grid-image-fit",
116 owner: "moss-grid-card",
117 default: "cover",
118 description: "`object-fit` for grid images. `contain` letterboxes onto the surface colour instead of cropping — the right choice for typographic work, where a crop costs words rather than scenery.",
119 },
120 CustomProp {
121 name: "--moss-card-cover-ratio",
122 owner: "moss-card-cover",
123 default: "4 / 3",
124 description: "Aspect ratio of card cover images. Same reasoning as `--moss-grid-image-ratio`, for `:::cards` rather than `:::grid`.",
125 },
126 CustomProp {
127 name: "--moss-card-cover-fit",
128 owner: "moss-card-cover",
129 default: "cover",
130 description: "`object-fit` for card covers. `contain` for artwork whose edges carry meaning; `cover` stays right for photography.",
131 },
132 CustomProp {
133 name: "--moss-card-min",
134 owner: "moss-cards",
135 default: "280px",
136 description: "Minimum column width in the auto-filled card grid. Lower it for denser grids of short items, raise it to force fewer, wider cards.",
137 },
138 CustomProp {
139 name: "--moss-cover-color",
140 owner: "moss-card",
141 default: "var(--moss-bg, var(--moss-color-bg, #fff))",
142 description: "Background behind card content when the card carries `data-cover-color`. moss sets this per-card from the cover image's dominant colour; a theme can override it to opt out of the extracted tint.",
143 },
144 CustomProp {
145 name: "--moss-bg",
146 owner: "moss-card",
147 default: "var(--moss-color-bg, #fff)",
148 description: "Fallback background in the `--moss-cover-color` chain, for a scope that wants a different neutral than the site background without redefining the `--moss-color-bg` token.",
149 },
150 CustomProp {
151 name: "--moss-nav-width",
152 owner: "main-nav",
153 default: "var(--moss-content-width)",
154 description: "Width of the header nav's inner row. Unset (the default) the nav tracks the `<body>`-level content width, so it stays aligned with the article column through every `content_width` preset. Set it only to deliberately break that alignment.",
155 },
156 CustomProp {
157 name: "--moss-escape",
158 owner: "[data-width]",
159 default: "100%",
160 description: "Width a `data-width` block escapes to. moss sets it per keyword (`wide`, `page`, `screen`); set it directly for a width the keywords do not cover. Always clamped by `min(…, 100cqw)`, so a narrow viewport stays safe.",
161 },
162 CustomProp {
163 name: "--moss-success",
164 owner: "moss-input-feedback",
165 default: "#10b981",
166 description: "Colour of a success message under a form field. Deliberately not a token: it is one accent moss does not want to spend a site-wide variable on.",
167 },
168 CustomProp {
169 name: "--moss-error",
170 owner: "moss-input-feedback",
171 default: "#c85450",
172 description: "Colour of an error message under a form field, and of comment-thread error states.",
173 },
174 CustomProp {
175 name: "--moss-radius-md",
176 owner: "moss-subscribe",
177 default: "0.5rem",
178 description: "Corner radius of the subscribe card. Set to `0` for a square-cornered form that matches a flat theme.",
179 },
180];
181
182pub struct ScopeAttr {
192 pub selector: &'static str,
194 pub name: &'static str,
196 pub values: &'static [&'static str],
198 pub description: &'static str,
200}
201
202pub const SCOPE_ATTRS: &[ScopeAttr] = &[
204 ScopeAttr {
205 selector: "body",
206 name: "data-page",
207 values: &["home"],
208 description: "Present as `home` on the site's front page only. Scope front-page-only rules to `body[data-page=\"home\"]` rather than to something merely unique to your homepage today — a hero that fills the screen, a suppressed footer, a different nav treatment.",
209 },
210 ScopeAttr {
211 selector: "html",
212 name: "data-theme",
213 values: &["light", "dark"],
214 description: "The resolved colour scheme, set on `<html>` before the first paint by an inline script, from the reader's stored choice or their OS preference. So `[data-theme=\"dark\"]` alone is sufficient for dark-mode rules — do not also write an `@media (prefers-color-scheme: dark)` block, which ignores the toggle and will disagree with it.",
215 },
216 ScopeAttr {
217 selector: "article > [data-width]",
218 name: "data-width",
219 values: &["body", "wide", "page", "screen"],
220 description: "Set by block shortcodes to escape the text column. The width resolves through `--moss-escape`, clamped by `min(…, 100cqw)` so a narrow viewport stays safe.",
221 },
222 ScopeAttr {
223 selector: "body",
224 name: "data-typesetting",
225 values: &["vertical"],
226 description: "Present as `vertical` when the page is set in vertical CJK writing mode, from `[site] typesetting` in `.moss/config.toml` or a page's `typesetting:` frontmatter. Absent means horizontal. It reorients roughly 50 rules — nav, article flow, scroll direction — so a theme for a vertical site scopes to `body[data-typesetting=\"vertical\"]` rather than reinventing the mode.",
227 },
228 ScopeAttr {
229 selector: "body",
230 name: "data-content-width",
231 values: &["wide", "full"],
232 description: "Present when a page widens its column via `content_width:` frontmatter; absent at the default reading measure. This is what `--moss-content-width` — and therefore `--moss-nav-width`, which tracks it — resolves against, so it is the hook for a layout that should respond to the preset rather than to a fixed width.",
233 },
234];