@bundu/ui 0.1.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.
@@ -0,0 +1,78 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ import { cn } from "../lib/utils";
6
+
7
+ /**
8
+ * Tooltip — a minimal accessible tooltip.
9
+ *
10
+ * Deliberately NOT Radix (kept dependency-light): wraps a single
11
+ * trigger and shows `content` on hover AND keyboard focus (so it's not
12
+ * hover-only), wiring `aria-describedby` to the tooltip's generated id
13
+ * and `role="tooltip"`. Escape dismisses it. Positioning is CSS-only
14
+ * (absolute, `side` prop) — no floating-ui. Colours come from the
15
+ * semantic `foreground` / `background` tokens — no hex.
16
+ *
17
+ * The trigger must be a single focusable element (button/link). The
18
+ * wrapper is an inline span so it doesn't disturb layout.
19
+ */
20
+ export interface TooltipProps {
21
+ /** Tooltip text (or rich node) shown on hover/focus. */
22
+ content: React.ReactNode;
23
+ side?: "top" | "bottom" | "left" | "right";
24
+ class?: string;
25
+ className?: string;
26
+ children: React.ReactNode;
27
+ }
28
+
29
+ const sideClasses: Record<NonNullable<TooltipProps["side"]>, string> = {
30
+ top: "bottom-full left-1/2 -translate-x-1/2 mb-2",
31
+ bottom: "top-full left-1/2 -translate-x-1/2 mt-2",
32
+ left: "right-full top-1/2 -translate-y-1/2 mr-2",
33
+ right: "left-full top-1/2 -translate-y-1/2 ml-2",
34
+ };
35
+
36
+ export function Tooltip({
37
+ content,
38
+ side = "top",
39
+ class: astroClass,
40
+ className,
41
+ children,
42
+ }: TooltipProps) {
43
+ const [open, setOpen] = React.useState(false);
44
+ const id = React.useId();
45
+
46
+ return (
47
+ <span
48
+ className="relative inline-flex"
49
+ data-slot="tooltip"
50
+ onMouseEnter={() => setOpen(true)}
51
+ onMouseLeave={() => setOpen(false)}
52
+ onFocusCapture={() => setOpen(true)}
53
+ onBlurCapture={() => setOpen(false)}
54
+ onKeyDown={(e) => {
55
+ if (e.key === "Escape") setOpen(false);
56
+ }}
57
+ >
58
+ <span aria-describedby={open ? id : undefined} className="inline-flex">
59
+ {children}
60
+ </span>
61
+ <span
62
+ role="tooltip"
63
+ id={id}
64
+ hidden={!open}
65
+ data-slot="tooltip-content"
66
+ data-state={open ? "open" : "closed"}
67
+ className={cn(
68
+ "pointer-events-none absolute z-50 w-max max-w-xs rounded-md bg-foreground px-2.5 py-1.5 text-caption font-medium text-background shadow-md",
69
+ sideClasses[side],
70
+ astroClass,
71
+ className,
72
+ )}
73
+ >
74
+ {content}
75
+ </span>
76
+ </span>
77
+ );
78
+ }
@@ -0,0 +1,10 @@
1
+ /* brand-bundu — Bundu Foundation primary: terracotta.
2
+ Import AFTER globals.css to select this brand's primary + ring. */
3
+ :root {
4
+ --primary: var(--color-terracotta);
5
+ --ring: var(--color-terracotta);
6
+ }
7
+ .dark {
8
+ --primary: var(--color-terracotta);
9
+ --ring: var(--color-terracotta);
10
+ }
@@ -0,0 +1,10 @@
1
+ /* brand-mukoko — Mukoko primary: tanzanite.
2
+ Import AFTER globals.css to select this brand's primary + ring. */
3
+ :root {
4
+ --primary: var(--color-tanzanite);
5
+ --ring: var(--color-tanzanite);
6
+ }
7
+ .dark {
8
+ --primary: var(--color-tanzanite);
9
+ --ring: var(--color-tanzanite);
10
+ }
@@ -0,0 +1,10 @@
1
+ /* brand-nyuchi — Nyuchi primary: gold.
2
+ Import AFTER globals.css to select this brand's primary + ring. */
3
+ :root {
4
+ --primary: var(--color-gold);
5
+ --ring: var(--color-gold);
6
+ }
7
+ .dark {
8
+ --primary: var(--color-gold);
9
+ --ring: var(--color-gold);
10
+ }
@@ -0,0 +1,358 @@
1
+ @layer base {
2
+ :root {
3
+ /* === Seven African Minerals — light mode (canonical from mzizi.dev) === */
4
+ --color-cobalt: #0047ab;
5
+ --color-cobalt-container: #e3f2fd;
6
+ --color-cobalt-on-container: #002966;
7
+
8
+ --color-tanzanite: #4b0082;
9
+ --color-tanzanite-container: #f3e5f5;
10
+ --color-tanzanite-on-container: #2e004d;
11
+
12
+ --color-malachite: #004d40;
13
+ --color-malachite-container: #e0f2f1;
14
+ --color-malachite-on-container: #00332b;
15
+
16
+ --color-gold: #5d4037;
17
+ --color-gold-container: #fff8e1;
18
+ --color-gold-on-container: #3e2723;
19
+
20
+ --color-terracotta: #a0522d;
21
+ --color-terracotta-container: #f5e6d3;
22
+ --color-terracotta-on-container: #5d2906;
23
+
24
+ --color-sodalite: #283593;
25
+ --color-sodalite-container: #e8eaf6;
26
+ --color-sodalite-on-container: #141a5c;
27
+
28
+ --color-copper: #bf5a36;
29
+ --color-copper-container: #fbe4da;
30
+ --color-copper-on-container: #5c2410;
31
+
32
+ /* === Semantic tokens — light (canonical from mzizi.dev) === */
33
+ --background: #faf9f4; /* warm cream canvas */
34
+ --canvas: #faf9f4;
35
+ --foreground: #1a1a17; /* near-black ink */
36
+ --ink: #1a1a17;
37
+
38
+ --card: #ffffff;
39
+ --card-foreground: #1a1a17;
40
+ --popover: #ffffff;
41
+ --popover-foreground: #1a1a17;
42
+
43
+ /* Brand primary: cobalt is the canonical Mzizi / Design MCP default.
44
+ Each brand site overrides --primary and --ring to its own mineral
45
+ via the brand-*.css overlay imported after this file. */
46
+ --primary: var(--color-cobalt);
47
+ --primary-foreground: #ffffff;
48
+
49
+ --secondary: #f4f2ec;
50
+ --secondary-foreground: #1a1a17;
51
+
52
+ --muted: #f4f2ec;
53
+ --muted-foreground: #5d5c57;
54
+
55
+ --accent: var(--color-cobalt-container);
56
+ --accent-foreground: var(--color-cobalt-on-container);
57
+
58
+ --destructive: #b3261e;
59
+ --destructive-foreground: #ffffff;
60
+ --destructive-container: #fdeded;
61
+
62
+ --border: #e7e5e0; /* warm stone */
63
+ --input: #ffffff;
64
+ --ring: var(--color-cobalt);
65
+
66
+ --success: var(--color-malachite);
67
+ --warning: #7a5c00;
68
+ --error: #b3261e;
69
+ --info: var(--color-cobalt);
70
+
71
+ /* Touch targets */
72
+ --touch-target: 56px;
73
+ --touch-target-sm: 48px;
74
+ }
75
+
76
+ .dark {
77
+ --color-cobalt: #00b0ff;
78
+ --color-cobalt-container: #001f3f;
79
+ --color-cobalt-on-container: #b3e5fc;
80
+
81
+ --color-tanzanite: #b388ff;
82
+ --color-tanzanite-container: #1a0033;
83
+ --color-tanzanite-on-container: #e1bee7;
84
+
85
+ --color-malachite: #64ffda;
86
+ --color-malachite-container: #00251a;
87
+ --color-malachite-on-container: #a7ffeb;
88
+
89
+ --color-gold: #ffd740;
90
+ --color-gold-container: #332200;
91
+ --color-gold-on-container: #ffecb3;
92
+
93
+ --color-terracotta: #e1b07e;
94
+ --color-terracotta-container: #3e2817;
95
+ --color-terracotta-on-container: #f5e6d3;
96
+
97
+ --color-sodalite: #3d5afe;
98
+ --color-sodalite-container: #0d1442;
99
+ --color-sodalite-on-container: #c5cae9;
100
+
101
+ --color-copper: #ff8a65;
102
+ --color-copper-container: #3a1a0e;
103
+ --color-copper-on-container: #ffd3c2;
104
+
105
+ --background: #100f0e;
106
+ --canvas: #100f0e;
107
+ --foreground: #f0efe9;
108
+ --ink: #f0efe9;
109
+
110
+ --card: #1a1917;
111
+ --card-foreground: #f0efe9;
112
+ --popover: #1a1917;
113
+ --popover-foreground: #f0efe9;
114
+
115
+ --primary: var(--color-cobalt);
116
+ --primary-foreground: #1a1917;
117
+
118
+ --secondary: #2a2927;
119
+ --secondary-foreground: #f0efe9;
120
+
121
+ --muted: #2a2927;
122
+ --muted-foreground: #a8a6a0;
123
+
124
+ --accent: var(--color-cobalt-container);
125
+ --accent-foreground: var(--color-cobalt-on-container);
126
+
127
+ --destructive: #f2b8b5;
128
+ --destructive-foreground: #1a1917;
129
+ --destructive-container: #3e1818;
130
+
131
+ --border: #2a2927;
132
+ --input: #100f0e;
133
+ --ring: var(--color-cobalt);
134
+
135
+ --success: var(--color-malachite);
136
+ --warning: #ffd866;
137
+ --error: #f2b8b5;
138
+ --info: var(--color-cobalt);
139
+ }
140
+
141
+ html {
142
+ scroll-behavior: smooth;
143
+ }
144
+
145
+ body {
146
+ background-color: var(--background);
147
+ color: var(--foreground);
148
+ font-family: "Noto Sans", system-ui, sans-serif;
149
+ @apply antialiased;
150
+ }
151
+
152
+ h1,
153
+ h2,
154
+ h3,
155
+ h4,
156
+ h5,
157
+ h6 {
158
+ font-family: "Noto Serif", Georgia, serif;
159
+ color: var(--foreground);
160
+ }
161
+
162
+ code,
163
+ pre,
164
+ kbd,
165
+ samp {
166
+ font-family: "JetBrains Mono", ui-monospace, monospace;
167
+ }
168
+
169
+ *:focus-visible {
170
+ outline: 2px solid var(--ring);
171
+ outline-offset: 2px;
172
+ }
173
+
174
+ /* Selection */
175
+ ::selection {
176
+ background-color: var(--color-cobalt-container);
177
+ color: var(--color-cobalt-on-container);
178
+ }
179
+ }
180
+
181
+ @layer components {
182
+ /* Containers — Anthropic-style narrow editorial column + wide grid */
183
+ .container-custom {
184
+ @apply max-w-7xl mx-auto px-6 sm:px-8 lg:px-10;
185
+ }
186
+
187
+ .container-narrow {
188
+ @apply max-w-narrow mx-auto px-6 sm:px-8;
189
+ }
190
+
191
+ .container-prose {
192
+ @apply max-w-3xl mx-auto px-6 sm:px-8;
193
+ }
194
+
195
+ /* Pill buttons — design system mandate */
196
+ .btn-primary {
197
+ @apply inline-flex items-center justify-center gap-2 px-6 h-12
198
+ bg-primary text-primary-foreground font-medium rounded-full
199
+ transition-all duration-200 ease-soft
200
+ hover:opacity-90
201
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background
202
+ disabled:opacity-50 disabled:cursor-not-allowed;
203
+ }
204
+
205
+ .btn-secondary {
206
+ @apply inline-flex items-center justify-center gap-2 px-6 h-12
207
+ bg-foreground text-background font-medium rounded-full
208
+ transition-all duration-200 ease-soft
209
+ hover:opacity-90
210
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background
211
+ disabled:opacity-50 disabled:cursor-not-allowed;
212
+ }
213
+
214
+ .btn-outline {
215
+ @apply inline-flex items-center justify-center gap-2 px-6 h-12
216
+ border border-foreground text-foreground font-medium rounded-full
217
+ transition-all duration-200 ease-soft
218
+ hover:bg-foreground hover:text-background
219
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background
220
+ disabled:opacity-50 disabled:cursor-not-allowed;
221
+ }
222
+
223
+ .btn-ghost {
224
+ @apply inline-flex items-center justify-center gap-2 px-4 h-10
225
+ text-foreground font-medium rounded-full
226
+ transition-colors duration-200 ease-soft
227
+ hover:bg-muted
228
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background
229
+ disabled:opacity-50 disabled:cursor-not-allowed;
230
+ }
231
+
232
+ /* Cards */
233
+ .card {
234
+ @apply bg-card text-card-foreground rounded-lg
235
+ border border-border;
236
+ }
237
+
238
+ .card-hover {
239
+ @apply bg-card text-card-foreground rounded-lg border border-border hover:shadow-sm transition-all duration-200 ease-soft;
240
+ }
241
+
242
+ /* Sections */
243
+ .section {
244
+ @apply py-16 md:py-24;
245
+ }
246
+
247
+ .section-tight {
248
+ @apply py-12 md:py-16;
249
+ }
250
+
251
+ .section-loose {
252
+ @apply py-20 md:py-32;
253
+ }
254
+
255
+ .section-title {
256
+ @apply font-serif text-h1 text-foreground mb-4 text-balance;
257
+ }
258
+
259
+ .section-subtitle {
260
+ @apply text-body-lg text-muted-foreground max-w-2xl text-pretty;
261
+ }
262
+
263
+ .eyebrow {
264
+ @apply inline-block text-caption font-semibold uppercase tracking-wider text-muted-foreground mb-4;
265
+ }
266
+
267
+ /* Badges — pill */
268
+ .badge {
269
+ @apply inline-flex items-center px-3 py-1 rounded-full text-caption font-medium;
270
+ }
271
+
272
+ .badge-primary {
273
+ @apply inline-flex items-center px-3 py-1 rounded-full text-caption font-medium bg-cobalt-container text-cobalt-on-container;
274
+ }
275
+
276
+ .badge-success {
277
+ @apply inline-flex items-center px-3 py-1 rounded-full text-caption font-medium bg-malachite-container text-malachite-on-container;
278
+ }
279
+
280
+ .badge-warning {
281
+ @apply inline-flex items-center px-3 py-1 rounded-full text-caption font-medium bg-gold-container text-gold-on-container;
282
+ }
283
+
284
+ .badge-info {
285
+ @apply inline-flex items-center px-3 py-1 rounded-full text-caption font-medium bg-cobalt-container text-cobalt-on-container;
286
+ }
287
+
288
+ .badge-accent {
289
+ @apply inline-flex items-center px-3 py-1 rounded-full text-caption font-medium bg-terracotta-container text-terracotta-on-container;
290
+ }
291
+
292
+ .badge-premium {
293
+ @apply inline-flex items-center px-3 py-1 rounded-full text-caption font-medium bg-tanzanite-container text-tanzanite-on-container;
294
+ }
295
+
296
+ /* Ubuntu pull-quote */
297
+ .ubuntu-quote {
298
+ @apply relative py-8 px-6
299
+ bg-malachite-container text-malachite-on-container
300
+ border-l-4 border-malachite rounded-r-lg;
301
+ }
302
+
303
+ /* Inputs */
304
+ .input {
305
+ @apply w-full h-12 px-4 rounded-full
306
+ bg-input text-foreground border border-border
307
+ placeholder:text-muted-foreground
308
+ transition-colors duration-200
309
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background
310
+ disabled:opacity-50;
311
+ }
312
+
313
+ /* Editorial divider */
314
+ .rule {
315
+ @apply h-px w-full bg-border my-12;
316
+ }
317
+ }
318
+
319
+ @layer utilities {
320
+ .text-balance {
321
+ text-wrap: balance;
322
+ }
323
+ .text-pretty {
324
+ text-wrap: pretty;
325
+ }
326
+
327
+ /* Anthropic-style soft hero gradient — cream → muted */
328
+ .gradient-hero {
329
+ background: linear-gradient(
330
+ 135deg,
331
+ var(--canvas) 0%,
332
+ var(--secondary) 100%
333
+ );
334
+ }
335
+
336
+ /* Subtle mineral wash — for brand accent sections */
337
+ .wash-gold {
338
+ background: linear-gradient(
339
+ 180deg,
340
+ transparent 0%,
341
+ var(--color-gold-container) 100%
342
+ );
343
+ }
344
+ .wash-cobalt {
345
+ background: linear-gradient(
346
+ 180deg,
347
+ transparent 0%,
348
+ var(--color-cobalt-container) 100%
349
+ );
350
+ }
351
+ .wash-malachite {
352
+ background: linear-gradient(
353
+ 180deg,
354
+ transparent 0%,
355
+ var(--color-malachite-container) 100%
356
+ );
357
+ }
358
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * @bundu/ui — Tailwind preset.
3
+ *
4
+ * Maps the seven-African-mineral palette and the semantic tokens onto
5
+ * the CSS custom properties defined in `styles/globals.css` (+ the
6
+ * `brand-*.css` overlays), and ships the Nyuchi Design System type
7
+ * scale, radius scale, and timing functions. Consumers get every
8
+ * `bg-cobalt` / `text-h2` / `rounded-xl` utility by adding this one
9
+ * preset — the values still resolve through the CSS vars, so nothing
10
+ * here is a raw hex.
11
+ *
12
+ * Usage (tailwind.config.mjs):
13
+ * import preset from "@bundu/ui/tailwind-preset";
14
+ * export default {
15
+ * presets: [preset],
16
+ * content: ["./src/**\/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
17
+ * };
18
+ *
19
+ * NOTE: mineral utility classes are often composed from data
20
+ * (e.g. `bg-${mineral}`); safelist them in your own config if you build
21
+ * class names dynamically.
22
+ *
23
+ * @type {import('tailwindcss').Config}
24
+ */
25
+ export default {
26
+ darkMode: "class",
27
+ theme: {
28
+ extend: {
29
+ colors: {
30
+ // === Seven African Minerals (canonical Nyuchi Design System palette) ===
31
+ cobalt: {
32
+ DEFAULT: "var(--color-cobalt)",
33
+ container: "var(--color-cobalt-container)",
34
+ "on-container": "var(--color-cobalt-on-container)",
35
+ },
36
+ tanzanite: {
37
+ DEFAULT: "var(--color-tanzanite)",
38
+ container: "var(--color-tanzanite-container)",
39
+ "on-container": "var(--color-tanzanite-on-container)",
40
+ },
41
+ malachite: {
42
+ DEFAULT: "var(--color-malachite)",
43
+ container: "var(--color-malachite-container)",
44
+ "on-container": "var(--color-malachite-on-container)",
45
+ },
46
+ gold: {
47
+ DEFAULT: "var(--color-gold)",
48
+ container: "var(--color-gold-container)",
49
+ "on-container": "var(--color-gold-on-container)",
50
+ },
51
+ terracotta: {
52
+ DEFAULT: "var(--color-terracotta)",
53
+ container: "var(--color-terracotta-container)",
54
+ "on-container": "var(--color-terracotta-on-container)",
55
+ },
56
+ sodalite: {
57
+ DEFAULT: "var(--color-sodalite)",
58
+ container: "var(--color-sodalite-container)",
59
+ "on-container": "var(--color-sodalite-on-container)",
60
+ },
61
+ copper: {
62
+ DEFAULT: "var(--color-copper)",
63
+ container: "var(--color-copper-container)",
64
+ "on-container": "var(--color-copper-on-container)",
65
+ },
66
+
67
+ // === Semantic tokens ===
68
+ background: "var(--background)",
69
+ foreground: "var(--foreground)",
70
+ canvas: "var(--canvas)",
71
+ ink: "var(--ink)",
72
+ primary: {
73
+ DEFAULT: "var(--primary)",
74
+ foreground: "var(--primary-foreground)",
75
+ },
76
+ secondary: {
77
+ DEFAULT: "var(--secondary)",
78
+ foreground: "var(--secondary-foreground)",
79
+ },
80
+ muted: {
81
+ DEFAULT: "var(--muted)",
82
+ foreground: "var(--muted-foreground)",
83
+ },
84
+ accent: {
85
+ DEFAULT: "var(--accent)",
86
+ foreground: "var(--accent-foreground)",
87
+ },
88
+ destructive: {
89
+ DEFAULT: "var(--destructive)",
90
+ foreground: "var(--destructive-foreground)",
91
+ },
92
+ card: {
93
+ DEFAULT: "var(--card)",
94
+ foreground: "var(--card-foreground)",
95
+ },
96
+ popover: {
97
+ DEFAULT: "var(--popover)",
98
+ foreground: "var(--popover-foreground)",
99
+ },
100
+ border: "var(--border)",
101
+ input: "var(--input)",
102
+ ring: "var(--ring)",
103
+ success: "var(--success)",
104
+ warning: "var(--warning)",
105
+ error: "var(--error)",
106
+ info: "var(--info)",
107
+ },
108
+ fontFamily: {
109
+ sans: ['"Noto Sans"', "system-ui", "sans-serif"],
110
+ serif: ['"Noto Serif"', "Georgia", "serif"],
111
+ mono: ['"JetBrains Mono"', "ui-monospace", "monospace"],
112
+ },
113
+ fontSize: {
114
+ // Fluid type scale from the Nyuchi Design System
115
+ display: [
116
+ "clamp(2rem, 4vw + 0.75rem, 3.5rem)",
117
+ { lineHeight: "1.1", letterSpacing: "-0.025em", fontWeight: "700" },
118
+ ],
119
+ "display-sm": [
120
+ "clamp(1.875rem, 3vw + 0.75rem, 3rem)",
121
+ { lineHeight: "1.1", letterSpacing: "-0.02em", fontWeight: "700" },
122
+ ],
123
+ h1: [
124
+ "clamp(2rem, 3vw + 1rem, 3rem)",
125
+ { lineHeight: "1.15", letterSpacing: "-0.02em", fontWeight: "700" },
126
+ ],
127
+ h2: [
128
+ "clamp(1.75rem, 2vw + 1rem, 2.25rem)",
129
+ { lineHeight: "1.2", letterSpacing: "-0.015em", fontWeight: "600" },
130
+ ],
131
+ h3: [
132
+ "clamp(1.5rem, 1.5vw + 1rem, 1.875rem)",
133
+ { lineHeight: "1.25", letterSpacing: "-0.01em", fontWeight: "600" },
134
+ ],
135
+ h4: [
136
+ "1.5rem",
137
+ { lineHeight: "1.3", letterSpacing: "-0.005em", fontWeight: "600" },
138
+ ],
139
+ h5: ["1.25rem", { lineHeight: "1.4", fontWeight: "600" }],
140
+ h6: ["1rem", { lineHeight: "1.4", fontWeight: "600" }],
141
+ "body-lg": ["1.125rem", { lineHeight: "1.6" }],
142
+ body: ["1rem", { lineHeight: "1.6" }],
143
+ "body-sm": ["0.875rem", { lineHeight: "1.5" }],
144
+ caption: ["0.75rem", { lineHeight: "1.5", letterSpacing: "0.02em" }],
145
+ },
146
+ spacing: {
147
+ xxs: "0.125rem",
148
+ "xs-plus": "0.375rem",
149
+ "sm-plus": "0.625rem",
150
+ "base-plus": "1.25rem",
151
+ "xl-plus": "2.5rem",
152
+ "2xl-plus": "3.5rem",
153
+ 18: "4.5rem",
154
+ 88: "22rem",
155
+ },
156
+ borderRadius: {
157
+ sm: "7px",
158
+ md: "12px",
159
+ lg: "14px",
160
+ xl: "17px",
161
+ "2xl": "17px",
162
+ full: "9999px",
163
+ pill: "9999px",
164
+ },
165
+ maxWidth: {
166
+ prose: "65ch",
167
+ narrow: "42rem",
168
+ },
169
+ ringWidth: {
170
+ DEFAULT: "2px",
171
+ },
172
+ transitionTimingFunction: {
173
+ soft: "cubic-bezier(0.4, 0, 0.2, 1)",
174
+ },
175
+ },
176
+ },
177
+ };