@tonycodes-ui/mortar 0.0.0-stage → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/AGENTS.md +10 -0
  2. package/CHANGELOG.md +268 -0
  3. package/CONTRIBUTING.md +60 -0
  4. package/DESIGN.md +51 -0
  5. package/LICENSE +21 -0
  6. package/README.md +155 -2
  7. package/TESTING.md +83 -0
  8. package/baseline-policy.json +16 -0
  9. package/bin/mortar.mjs +258 -0
  10. package/catalog.json +9900 -0
  11. package/cli/audit.mjs +943 -0
  12. package/cli/data.mjs +176 -0
  13. package/cli/mcp.mjs +216 -0
  14. package/cli/migrations.mjs +128 -0
  15. package/cli/packet.mjs +267 -0
  16. package/cli/search.mjs +246 -0
  17. package/cli/tools.mjs +229 -0
  18. package/design-principles.json +30 -0
  19. package/dist/agent-data.json +1 -0
  20. package/dist/application.d.ts +340 -0
  21. package/dist/application.js +240 -0
  22. package/dist/auth.d.ts +121 -0
  23. package/dist/auth.js +97 -0
  24. package/dist/brand.d.ts +69 -0
  25. package/dist/brand.js +266 -0
  26. package/dist/calendar.d.ts +75 -0
  27. package/dist/calendar.js +310 -0
  28. package/dist/choice.d.ts +36 -0
  29. package/dist/choice.js +36 -0
  30. package/dist/composer.d.ts +28 -0
  31. package/dist/composer.js +47 -0
  32. package/dist/display.d.ts +136 -0
  33. package/dist/display.js +75 -0
  34. package/dist/docs.d.ts +145 -0
  35. package/dist/docs.js +141 -0
  36. package/dist/ecommerce.d.ts +261 -0
  37. package/dist/ecommerce.js +177 -0
  38. package/dist/feedback.d.ts +80 -0
  39. package/dist/feedback.js +98 -0
  40. package/dist/foundation.d.ts +42 -0
  41. package/dist/foundation.js +121 -0
  42. package/dist/heading.d.ts +5 -0
  43. package/dist/heading.js +9 -0
  44. package/dist/icons.d.ts +50 -0
  45. package/dist/icons.js +70 -0
  46. package/dist/illustration.d.ts +47 -0
  47. package/dist/illustration.js +47 -0
  48. package/dist/index.d.ts +36 -0
  49. package/dist/index.js +18 -0
  50. package/dist/inspection.d.ts +24 -0
  51. package/dist/inspection.js +14 -0
  52. package/dist/internal.d.ts +514 -0
  53. package/dist/internal.js +332 -0
  54. package/dist/layout.d.ts +107 -0
  55. package/dist/layout.js +66 -0
  56. package/dist/marketing-sections.d.ts +161 -0
  57. package/dist/marketing-sections.js +58 -0
  58. package/dist/marketing.d.ts +277 -0
  59. package/dist/marketing.js +163 -0
  60. package/dist/navigation.d.ts +35 -0
  61. package/dist/navigation.js +44 -0
  62. package/dist/primitives.d.ts +258 -0
  63. package/dist/primitives.js +244 -0
  64. package/dist/showcase.d.ts +29 -0
  65. package/dist/showcase.js +10 -0
  66. package/dist/workspace.d.ts +80 -0
  67. package/dist/workspace.js +48 -0
  68. package/docs/agents.md +127 -0
  69. package/docs/auth-flows.md +71 -0
  70. package/docs/migrating-to-0.6.md +94 -0
  71. package/docs/theming.md +170 -0
  72. package/package.json +94 -4
  73. package/skills/mortar/SKILL.md +36 -0
  74. package/src/application.css +1487 -0
  75. package/src/application.tsx +1413 -0
  76. package/src/auth.tsx +452 -0
  77. package/src/brand.ts +335 -0
  78. package/src/calendar.css +468 -0
  79. package/src/calendar.tsx +868 -0
  80. package/src/choice.tsx +240 -0
  81. package/src/composer.tsx +158 -0
  82. package/src/data.css +690 -0
  83. package/src/display.tsx +452 -0
  84. package/src/docs.css +704 -0
  85. package/src/docs.tsx +737 -0
  86. package/src/ecommerce.css +810 -0
  87. package/src/ecommerce.tsx +1033 -0
  88. package/src/feedback.tsx +388 -0
  89. package/src/foundation.css +1867 -0
  90. package/src/foundation.tsx +227 -0
  91. package/src/heading.ts +13 -0
  92. package/src/icons.tsx +237 -0
  93. package/src/illustration.tsx +169 -0
  94. package/src/index.ts +233 -0
  95. package/src/inspection.tsx +91 -0
  96. package/src/internal.tsx +700 -0
  97. package/src/layout.tsx +342 -0
  98. package/src/marketing-sections.tsx +621 -0
  99. package/src/marketing.css +2579 -0
  100. package/src/marketing.tsx +1235 -0
  101. package/src/navigation.tsx +118 -0
  102. package/src/overlays.css +350 -0
  103. package/src/primitives.tsx +1135 -0
  104. package/src/showcase.tsx +120 -0
  105. package/src/tokens.css +449 -0
  106. package/src/workspace.tsx +333 -0
  107. package/styles.css +56 -0
@@ -0,0 +1,120 @@
1
+ import type { ReactNode } from "react";
2
+ import { componentRoot, cx } from "./internal.js";
3
+
4
+ /** Framed product screenshot or illustration for heroes and feature sections. */
5
+ export function MockWindow({
6
+ id,
7
+ className,
8
+ label,
9
+ address,
10
+ variant = "browser",
11
+ children,
12
+ }: {
13
+ id?: string;
14
+ className?: string;
15
+ /** Accessible description of what the mock shows. */
16
+ label: string;
17
+ /** Optional URL or file name shown in the title bar. */
18
+ address?: string;
19
+ /** browser: title bar with dots. app: title bar without dots. plain: no chrome. phone: a portrait handset frame. */
20
+ variant?: "browser" | "app" | "plain" | "phone";
21
+ children: ReactNode;
22
+ }) {
23
+ return (
24
+ <figure
25
+ id={id}
26
+ className={cx("m-mock-window", className)}
27
+ data-variant={variant === "browser" ? undefined : variant}
28
+ aria-label={label}
29
+ {...componentRoot("MockWindow")}
30
+ >
31
+ {variant !== "plain" && variant !== "phone" && (
32
+ <div className="m-mock-bar" aria-hidden="true">
33
+ <span className="m-mock-dots">
34
+ <i />
35
+ <i />
36
+ <i />
37
+ </span>
38
+ {address && <span className="m-mock-address">{address}</span>}
39
+ </div>
40
+ )}
41
+ <div className="m-mock-body">{children}</div>
42
+ </figure>
43
+ );
44
+ }
45
+
46
+ export interface ChatMessage {
47
+ from: "user" | "assistant";
48
+ content: ReactNode;
49
+ }
50
+
51
+ /** Static, illustrative chat conversation for marketing a conversational product. */
52
+ export function ChatPreview({
53
+ id,
54
+ className,
55
+ title,
56
+ status,
57
+ messages,
58
+ typing = false,
59
+ suggestions = [],
60
+ placeholder = "Type a message…",
61
+ assistantName = "Assistant",
62
+ }: {
63
+ id?: string;
64
+ className?: string;
65
+ title: string;
66
+ status?: string;
67
+ messages: ChatMessage[];
68
+ typing?: boolean;
69
+ suggestions?: string[];
70
+ placeholder?: string;
71
+ assistantName?: string;
72
+ }) {
73
+ return (
74
+ <figure
75
+ id={id}
76
+ className={cx("m-chat-preview", className)}
77
+ aria-label={`${title} conversation`}
78
+ {...componentRoot("ChatPreview")}
79
+ >
80
+ <header>
81
+ <span className="m-chat-avatar" aria-hidden="true" />
82
+ <span>
83
+ <strong>{title}</strong>
84
+ {status && <small>{status}</small>}
85
+ </span>
86
+ </header>
87
+ <ol className="m-chat-log">
88
+ {messages.map((message, i) => (
89
+ <li key={i} data-from={message.from}>
90
+ <span className="m-sr-only">
91
+ {message.from === "user" ? "Visitor" : assistantName}:
92
+ </span>
93
+ <div className="m-chat-bubble">{message.content}</div>
94
+ </li>
95
+ ))}
96
+ {typing && (
97
+ <li data-from="assistant">
98
+ <span className="m-chat-typing">
99
+ <span className="m-sr-only">{assistantName} is typing</span>
100
+ <i aria-hidden="true" />
101
+ <i aria-hidden="true" />
102
+ <i aria-hidden="true" />
103
+ </span>
104
+ </li>
105
+ )}
106
+ </ol>
107
+ {suggestions.length > 0 && (
108
+ <ul className="m-chat-suggestions" aria-label="Suggested replies">
109
+ {suggestions.map((s) => (
110
+ <li key={s}>{s}</li>
111
+ ))}
112
+ </ul>
113
+ )}
114
+ <div className="m-chat-composer" aria-hidden="true">
115
+ <span>{placeholder}</span>
116
+ <span className="m-chat-send" />
117
+ </div>
118
+ </figure>
119
+ );
120
+ }
package/src/tokens.css ADDED
@@ -0,0 +1,449 @@
1
+ /* Mortar tokens, in three layers.
2
+ 1. Brand contract: a brand declares --m-brand-l-* (light), --m-brand-d-*
3
+ (dark) and --m-brand-font / --m-brand-font-heading, and nothing else.
4
+ Every value is optional; omitted surfaces keep the defaults below.
5
+ 2. Appearance: resolves the contract into the live --m-* tokens and into the
6
+ --m-inv-* tokens (the opposite appearance, used by inverse bands), so
7
+ dark mode and inverse sections never need brand-specific CSS. Hover and
8
+ active mix towards ink, which keeps on-primary contrast at least as high
9
+ as on the primary itself; selected tints the paper. --m-accent-fill is
10
+ an image (the brand's accent gradient, or flat accent) for opt-in
11
+ accent buttons and bands, painted over background-color: --m-accent.
12
+ 3. Scales: type, space, radius, sizing, density and stacking.
13
+ Status colours deliberately differ from brand colours. A light container
14
+ inside a dark document resolves light. */
15
+
16
+ /* 1. Brand contract */
17
+
18
+ /* Every brand starts from an empty contract, so a brand never inherits values
19
+ from an outer one. Zero specificity: any brand rule wins, in any order. */
20
+ :where([data-mortar-brand]) {
21
+ --m-brand-l-primary: initial;
22
+ --m-brand-l-primary-hover: initial;
23
+ --m-brand-l-primary-active: initial;
24
+ --m-brand-l-on-primary: initial;
25
+ --m-brand-l-accent: initial;
26
+ --m-brand-l-on-accent: initial;
27
+ --m-brand-l-focus: initial;
28
+ --m-brand-l-link: initial;
29
+ --m-brand-l-selected: initial;
30
+ --m-brand-l-paper: initial;
31
+ --m-brand-l-surface: initial;
32
+ --m-brand-l-sunken: initial;
33
+ --m-brand-l-accent-gradient: initial;
34
+ --m-brand-d-primary: initial;
35
+ --m-brand-d-primary-hover: initial;
36
+ --m-brand-d-primary-active: initial;
37
+ --m-brand-d-on-primary: initial;
38
+ --m-brand-d-accent: initial;
39
+ --m-brand-d-on-accent: initial;
40
+ --m-brand-d-focus: initial;
41
+ --m-brand-d-link: initial;
42
+ --m-brand-d-selected: initial;
43
+ --m-brand-d-paper: initial;
44
+ --m-brand-d-surface: initial;
45
+ --m-brand-d-sunken: initial;
46
+ --m-brand-d-accent-gradient: initial;
47
+ --m-brand-font: initial;
48
+ --m-brand-font-heading: initial;
49
+ }
50
+ /* Neutral: the appearance fallbacks supply its colours; it keeps its tuned
51
+ interaction states. */
52
+ :root:not([data-mortar-brand]),
53
+ [data-mortar-brand="neutral"] {
54
+ --m-brand-l-primary-hover: #29292c;
55
+ --m-brand-l-primary-active: #3a3a3c;
56
+ --m-brand-l-selected: #f2f2f4;
57
+ --m-brand-d-primary-hover: #e5e5ea;
58
+ --m-brand-d-primary-active: #c7c7cc;
59
+ --m-brand-d-selected: #1c1c1e;
60
+ }
61
+ [data-mortar-brand="green"] {
62
+ --m-brand-l-primary: #047857;
63
+ --m-brand-l-primary-hover: #065f46;
64
+ --m-brand-l-primary-active: #064e3b;
65
+ --m-brand-l-on-primary: #ffffff;
66
+ --m-brand-l-selected: #e6f5ed;
67
+ --m-brand-d-primary: #6ee7b7;
68
+ --m-brand-d-primary-hover: #a7f3d0;
69
+ --m-brand-d-primary-active: #34d399;
70
+ --m-brand-d-on-primary: #022c22;
71
+ --m-brand-d-focus: #6ee7b7;
72
+ --m-brand-d-link: #6ee7b7;
73
+ --m-brand-d-selected: #062d23;
74
+ }
75
+
76
+ /* 2. Appearance. A branded element without its own appearance resolves in
77
+ light, or in dark inside a dark-appearance ancestor. */
78
+ :root,
79
+ [data-mortar-appearance="light"],
80
+ [data-mortar-brand]:not([data-mortar-appearance]) {
81
+ --m-paper: var(--m-brand-l-paper, #ffffff);
82
+ --m-surface: var(--m-brand-l-surface, #ffffff);
83
+ --m-sunken: var(--m-brand-l-sunken, #f5f5f7);
84
+ --m-line: #e5e5ea;
85
+ --m-line-strong: #8b8b90;
86
+ --m-ink: #000000;
87
+ --m-secondary: #3a3a3c;
88
+ --m-muted: #6e6e73;
89
+ --m-success: #1f7a36;
90
+ --m-warning: #945700;
91
+ --m-danger: #c70014;
92
+ --m-info: #0060c7;
93
+ --m-on-feedback: #ffffff;
94
+ --m-code-bg: #000000;
95
+ --m-code-line: rgb(255 255 255 / 0.12);
96
+ --m-code-ink: #f5f5f7;
97
+ --m-code-dim: #8b8b90;
98
+ --m-float: 0 12px 32px -12px rgb(0 0 0 / 0.14);
99
+ --m-scrim: rgb(0 0 0 / 0.5);
100
+ --m-swatch-ring: rgb(0 0 0 / 0.12);
101
+ --m-primary: var(--m-brand-l-primary, #000000);
102
+ --m-primary-hover: var(
103
+ --m-brand-l-primary-hover,
104
+ color-mix(in oklab, var(--m-primary) 85%, var(--m-ink))
105
+ );
106
+ --m-primary-active: var(
107
+ --m-brand-l-primary-active,
108
+ color-mix(in oklab, var(--m-primary) 72%, var(--m-ink))
109
+ );
110
+ --m-on-primary: var(--m-brand-l-on-primary, #ffffff);
111
+ --m-accent: var(--m-brand-l-accent, var(--m-primary));
112
+ --m-on-accent: var(--m-brand-l-on-accent, var(--m-on-primary));
113
+ --m-accent-fill: var(
114
+ --m-brand-l-accent-gradient,
115
+ linear-gradient(var(--m-accent), var(--m-accent))
116
+ );
117
+ --m-focus: var(--m-brand-l-focus, var(--m-primary));
118
+ --m-link: var(--m-brand-l-link, var(--m-primary));
119
+ --m-selected: var(
120
+ --m-brand-l-selected,
121
+ color-mix(in oklab, var(--m-primary) 8%, var(--m-paper))
122
+ );
123
+ --m-inv-color-scheme: dark;
124
+ --m-inv-paper: var(--m-brand-d-paper, #000000);
125
+ --m-inv-surface: var(--m-brand-d-surface, #1c1c1e);
126
+ --m-inv-sunken: var(--m-brand-d-sunken, #1c1c1e);
127
+ --m-inv-line: #3a3a3c;
128
+ --m-inv-line-strong: #727278;
129
+ --m-inv-ink: #f5f5f7;
130
+ --m-inv-secondary: #c7c7cc;
131
+ --m-inv-muted: #a1a1a6;
132
+ --m-inv-success: #30d158;
133
+ --m-inv-warning: #ffd60a;
134
+ --m-inv-danger: #ff6961;
135
+ --m-inv-info: #5aa9ff;
136
+ --m-inv-on-feedback: #000000;
137
+ --m-inv-primary: var(--m-brand-d-primary, #ffffff);
138
+ --m-inv-primary-hover: var(
139
+ --m-brand-d-primary-hover,
140
+ color-mix(in oklab, var(--m-inv-primary) 85%, var(--m-inv-ink))
141
+ );
142
+ --m-inv-primary-active: var(
143
+ --m-brand-d-primary-active,
144
+ color-mix(in oklab, var(--m-inv-primary) 72%, var(--m-inv-ink))
145
+ );
146
+ --m-inv-on-primary: var(--m-brand-d-on-primary, #000000);
147
+ --m-inv-accent: var(--m-brand-d-accent, var(--m-inv-primary));
148
+ --m-inv-on-accent: var(--m-brand-d-on-accent, var(--m-inv-on-primary));
149
+ --m-inv-accent-fill: var(
150
+ --m-brand-d-accent-gradient,
151
+ linear-gradient(var(--m-inv-accent), var(--m-inv-accent))
152
+ );
153
+ --m-inv-focus: var(--m-brand-d-focus, var(--m-inv-primary));
154
+ --m-inv-link: var(--m-brand-d-link, var(--m-inv-ink));
155
+ --m-inv-selected: var(
156
+ --m-brand-d-selected,
157
+ color-mix(in oklab, var(--m-inv-primary) 10%, var(--m-inv-paper))
158
+ );
159
+ }
160
+ [data-mortar] {
161
+ color-scheme: light;
162
+ }
163
+ [data-mortar-appearance="dark"],
164
+ [data-mortar-appearance="dark"]
165
+ [data-mortar-brand]:not([data-mortar-appearance]) {
166
+ color-scheme: dark;
167
+ --m-paper: var(--m-brand-d-paper, #000000);
168
+ --m-surface: var(--m-brand-d-surface, #0e0e10);
169
+ --m-sunken: var(--m-brand-d-sunken, #1c1c1e);
170
+ --m-line: #29292c;
171
+ --m-line-strong: #727278;
172
+ --m-ink: #f5f5f7;
173
+ --m-secondary: #c7c7cc;
174
+ --m-muted: #98989d;
175
+ --m-success: #30d158;
176
+ --m-warning: #ffd60a;
177
+ --m-danger: #ff6961;
178
+ --m-info: #5aa9ff;
179
+ --m-on-feedback: #000000;
180
+ --m-code-bg: #0e0e10;
181
+ --m-code-line: rgb(255 255 255 / 0.1);
182
+ --m-float: none;
183
+ --m-swatch-ring: rgb(255 255 255 / 0.24);
184
+ --m-primary: var(--m-brand-d-primary, #ffffff);
185
+ --m-primary-hover: var(
186
+ --m-brand-d-primary-hover,
187
+ color-mix(in oklab, var(--m-primary) 85%, var(--m-ink))
188
+ );
189
+ --m-primary-active: var(
190
+ --m-brand-d-primary-active,
191
+ color-mix(in oklab, var(--m-primary) 72%, var(--m-ink))
192
+ );
193
+ --m-on-primary: var(--m-brand-d-on-primary, #000000);
194
+ --m-accent: var(--m-brand-d-accent, var(--m-primary));
195
+ --m-on-accent: var(--m-brand-d-on-accent, var(--m-on-primary));
196
+ --m-accent-fill: var(
197
+ --m-brand-d-accent-gradient,
198
+ linear-gradient(var(--m-accent), var(--m-accent))
199
+ );
200
+ --m-focus: var(--m-brand-d-focus, var(--m-primary));
201
+ --m-link: var(--m-brand-d-link, var(--m-ink));
202
+ --m-selected: var(
203
+ --m-brand-d-selected,
204
+ color-mix(in oklab, var(--m-primary) 10%, var(--m-paper))
205
+ );
206
+ --m-inv-color-scheme: light;
207
+ --m-inv-paper: var(--m-brand-l-paper, #f5f5f7);
208
+ --m-inv-surface: var(--m-brand-l-surface, #ffffff);
209
+ --m-inv-sunken: var(--m-brand-l-sunken, #ffffff);
210
+ --m-inv-line: #d1d1d6;
211
+ --m-inv-line-strong: #8b8b90;
212
+ --m-inv-ink: #000000;
213
+ --m-inv-secondary: #3a3a3c;
214
+ --m-inv-muted: #636366;
215
+ --m-inv-success: #1f7a36;
216
+ --m-inv-warning: #945700;
217
+ --m-inv-danger: #c70014;
218
+ --m-inv-info: #0060c7;
219
+ --m-inv-on-feedback: #ffffff;
220
+ --m-inv-primary: var(--m-brand-l-primary, #000000);
221
+ --m-inv-primary-hover: var(
222
+ --m-brand-l-primary-hover,
223
+ color-mix(in oklab, var(--m-inv-primary) 85%, var(--m-inv-ink))
224
+ );
225
+ --m-inv-primary-active: var(
226
+ --m-brand-l-primary-active,
227
+ color-mix(in oklab, var(--m-inv-primary) 72%, var(--m-inv-ink))
228
+ );
229
+ --m-inv-on-primary: var(--m-brand-l-on-primary, #ffffff);
230
+ --m-inv-accent: var(--m-brand-l-accent, var(--m-inv-primary));
231
+ --m-inv-on-accent: var(--m-brand-l-on-accent, var(--m-inv-on-primary));
232
+ --m-inv-accent-fill: var(
233
+ --m-brand-l-accent-gradient,
234
+ linear-gradient(var(--m-inv-accent), var(--m-inv-accent))
235
+ );
236
+ --m-inv-focus: var(--m-brand-l-focus, var(--m-inv-primary));
237
+ --m-inv-link: var(--m-brand-l-link, var(--m-inv-primary));
238
+ --m-inv-selected: var(
239
+ --m-brand-l-selected,
240
+ color-mix(in oklab, var(--m-inv-primary) 8%, var(--m-inv-paper))
241
+ );
242
+ }
243
+ /* Before a ThemeProvider writes data-mortar-appearance (server rendering, or
244
+ no JavaScript), follow the operating system so dark users see no light
245
+ flash. Set data-mortar-appearance="light" on the root to opt out. Keep this
246
+ block identical to the dark one above (tests/theming.test.mjs checks). */
247
+ @media (prefers-color-scheme: dark) {
248
+ :root:not([data-mortar-appearance]),
249
+ :root:not([data-mortar-appearance])
250
+ [data-mortar-brand]:not([data-mortar-appearance]) {
251
+ color-scheme: dark;
252
+ --m-paper: var(--m-brand-d-paper, #000000);
253
+ --m-surface: var(--m-brand-d-surface, #0e0e10);
254
+ --m-sunken: var(--m-brand-d-sunken, #1c1c1e);
255
+ --m-line: #29292c;
256
+ --m-line-strong: #727278;
257
+ --m-ink: #f5f5f7;
258
+ --m-secondary: #c7c7cc;
259
+ --m-muted: #98989d;
260
+ --m-success: #30d158;
261
+ --m-warning: #ffd60a;
262
+ --m-danger: #ff6961;
263
+ --m-info: #5aa9ff;
264
+ --m-on-feedback: #000000;
265
+ --m-code-bg: #0e0e10;
266
+ --m-code-line: rgb(255 255 255 / 0.1);
267
+ --m-float: none;
268
+ --m-swatch-ring: rgb(255 255 255 / 0.24);
269
+ --m-primary: var(--m-brand-d-primary, #ffffff);
270
+ --m-primary-hover: var(
271
+ --m-brand-d-primary-hover,
272
+ color-mix(in oklab, var(--m-primary) 85%, var(--m-ink))
273
+ );
274
+ --m-primary-active: var(
275
+ --m-brand-d-primary-active,
276
+ color-mix(in oklab, var(--m-primary) 72%, var(--m-ink))
277
+ );
278
+ --m-on-primary: var(--m-brand-d-on-primary, #000000);
279
+ --m-accent: var(--m-brand-d-accent, var(--m-primary));
280
+ --m-on-accent: var(--m-brand-d-on-accent, var(--m-on-primary));
281
+ --m-accent-fill: var(
282
+ --m-brand-d-accent-gradient,
283
+ linear-gradient(var(--m-accent), var(--m-accent))
284
+ );
285
+ --m-focus: var(--m-brand-d-focus, var(--m-primary));
286
+ --m-link: var(--m-brand-d-link, var(--m-ink));
287
+ --m-selected: var(
288
+ --m-brand-d-selected,
289
+ color-mix(in oklab, var(--m-primary) 10%, var(--m-paper))
290
+ );
291
+ --m-inv-color-scheme: light;
292
+ --m-inv-paper: var(--m-brand-l-paper, #f5f5f7);
293
+ --m-inv-surface: var(--m-brand-l-surface, #ffffff);
294
+ --m-inv-sunken: var(--m-brand-l-sunken, #ffffff);
295
+ --m-inv-line: #d1d1d6;
296
+ --m-inv-line-strong: #8b8b90;
297
+ --m-inv-ink: #000000;
298
+ --m-inv-secondary: #3a3a3c;
299
+ --m-inv-muted: #636366;
300
+ --m-inv-success: #1f7a36;
301
+ --m-inv-warning: #945700;
302
+ --m-inv-danger: #c70014;
303
+ --m-inv-info: #0060c7;
304
+ --m-inv-on-feedback: #ffffff;
305
+ --m-inv-primary: var(--m-brand-l-primary, #000000);
306
+ --m-inv-primary-hover: var(
307
+ --m-brand-l-primary-hover,
308
+ color-mix(in oklab, var(--m-inv-primary) 85%, var(--m-inv-ink))
309
+ );
310
+ --m-inv-primary-active: var(
311
+ --m-brand-l-primary-active,
312
+ color-mix(in oklab, var(--m-inv-primary) 72%, var(--m-inv-ink))
313
+ );
314
+ --m-inv-on-primary: var(--m-brand-l-on-primary, #ffffff);
315
+ --m-inv-accent: var(--m-brand-l-accent, var(--m-inv-primary));
316
+ --m-inv-on-accent: var(--m-brand-l-on-accent, var(--m-inv-on-primary));
317
+ --m-inv-accent-fill: var(
318
+ --m-brand-l-accent-gradient,
319
+ linear-gradient(var(--m-inv-accent), var(--m-inv-accent))
320
+ );
321
+ --m-inv-focus: var(--m-brand-l-focus, var(--m-inv-primary));
322
+ --m-inv-link: var(--m-brand-l-link, var(--m-inv-primary));
323
+ --m-inv-selected: var(
324
+ --m-brand-l-selected,
325
+ color-mix(in oklab, var(--m-inv-primary) 8%, var(--m-inv-paper))
326
+ );
327
+ }
328
+ :root:not([data-mortar-appearance]) [data-mortar] {
329
+ color-scheme: dark;
330
+ }
331
+ }
332
+
333
+ /* 3. Scales. Appearance-independent; override on :root or any container to
334
+ retheme shape and rhythm. Density adjusts the sizing scale below. */
335
+ :root {
336
+ --m-font:
337
+ -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue",
338
+ sans-serif;
339
+ /* Headings and component titles. Unset by default, so they keep the font
340
+ they inherit (the body's --m-font); a brand's fonts.heading
341
+ (--m-brand-font-heading) wins over it. */
342
+ --m-font-heading: initial;
343
+ --m-mono: "SF Mono", ui-monospace, Menlo, monospace;
344
+ --m-weight-regular: 400;
345
+ --m-weight-medium: 500;
346
+ --m-weight-semibold: 600;
347
+ --m-weight-bold: 700;
348
+ --m-tracking-display: -0.04em;
349
+ --m-tracking-heading: -0.02em;
350
+ --m-tracking-body: normal;
351
+ --m-tracking-caps: 0.04em;
352
+ --m-leading-display: 1.1;
353
+ --m-leading-heading: 1.3;
354
+ --m-leading-body: 1.6;
355
+ --m-space-1: 4px;
356
+ --m-space-2: 8px;
357
+ --m-space-3: 12px;
358
+ --m-space-4: 16px;
359
+ --m-space-6: 24px;
360
+ --m-space-8: 32px;
361
+ --m-space-10: 40px;
362
+ --m-space-12: 48px;
363
+ --m-radius-xs: 2px;
364
+ --m-radius-sm: 4px;
365
+ --m-radius: 8px;
366
+ --m-radius-lg: 12px;
367
+ --m-radius-xl: 16px;
368
+ --m-radius-full: 999px;
369
+ --m-control: 36px;
370
+ --m-pad: 14px;
371
+ --m-font-size: 14px;
372
+ --m-row-pad: 14px;
373
+ --m-icon: 16px;
374
+ --m-focus-offset: 3px;
375
+ /* Minimum hit size: 0 for fine pointers, 44px for coarse pointers (below). */
376
+ --m-touch-target: 0px;
377
+ /* Stacking scale: sticky chrome < dialogs < popovers < toasts < skip link. */
378
+ --m-z-sticky: 30;
379
+ --m-z-overlay: 100;
380
+ --m-z-popover: 110;
381
+ --m-z-toast: 130;
382
+ --m-z-skip: 140;
383
+ }
384
+ [data-mortar-density="comfortable"] {
385
+ --m-control: 36px;
386
+ --m-pad: 14px;
387
+ --m-font-size: 14px;
388
+ --m-row-pad: 14px;
389
+ }
390
+ [data-mortar-density="compact"] {
391
+ --m-control: 30px;
392
+ --m-pad: 10px;
393
+ --m-font-size: 13px;
394
+ --m-row-pad: 10px;
395
+ }
396
+ [data-mortar-density="spacious"] {
397
+ --m-control: 44px;
398
+ --m-pad: 18px;
399
+ --m-font-size: 15px;
400
+ --m-row-pad: 18px;
401
+ }
402
+ @media (pointer: coarse) {
403
+ [data-mortar] {
404
+ --m-control: 44px;
405
+ --m-touch-target: 44px;
406
+ }
407
+ }
408
+ /* Inverse tone: any Mortar section root with data-tone="inverse" (sections, CTA,
409
+ banner, hero, featured pricing plan, bento item) flips appearance locally. */
410
+ [data-tone="inverse"] {
411
+ color-scheme: var(--m-inv-color-scheme);
412
+ --m-line-strong: var(--m-inv-line-strong);
413
+ --m-primary-active: var(--m-inv-primary-active);
414
+ --m-selected: var(--m-inv-selected);
415
+ --m-success: var(--m-inv-success);
416
+ --m-warning: var(--m-inv-warning);
417
+ --m-info: var(--m-inv-info);
418
+ --m-danger: var(--m-inv-danger);
419
+ --m-on-feedback: var(--m-inv-on-feedback);
420
+ --m-paper: var(--m-inv-paper);
421
+ --m-surface: var(--m-inv-surface);
422
+ --m-sunken: var(--m-inv-sunken);
423
+ --m-line: var(--m-inv-line);
424
+ --m-ink: var(--m-inv-ink);
425
+ --m-secondary: var(--m-inv-secondary);
426
+ --m-muted: var(--m-inv-muted);
427
+ --m-primary: var(--m-inv-primary);
428
+ --m-primary-hover: var(--m-inv-primary-hover);
429
+ --m-on-primary: var(--m-inv-on-primary);
430
+ --m-accent: var(--m-inv-accent);
431
+ --m-on-accent: var(--m-inv-on-accent);
432
+ --m-accent-fill: var(--m-inv-accent-fill);
433
+ --m-focus: var(--m-inv-focus);
434
+ --m-link: var(--m-inv-link);
435
+ --m-band: var(--m-inv-paper);
436
+ }
437
+
438
+ /* Accent wash for the accent hover and active states: the on-accent colour
439
+ with its lightness flipped to 0 or 1, so the fill moves away from its text.
440
+ Declared wherever --m-on-accent changes (appearance, brand, inverse band).
441
+ Browsers without relative colours leave it unset and keep the plain fill. */
442
+ @supports (color: oklch(from white round(1 - l, 1) 0 0)) {
443
+ :root,
444
+ [data-mortar-appearance],
445
+ [data-mortar-brand],
446
+ [data-tone="inverse"] {
447
+ --m-accent-wash: oklch(from var(--m-on-accent) round(1 - l, 1) 0 0);
448
+ }
449
+ }