@robomous/ui-core 0.1.0 → 0.2.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 (135) hide show
  1. package/README.md +19 -7
  2. package/dist/{primitives → components}/alert.js +1 -1
  3. package/dist/{primitives → components}/badge.js +1 -1
  4. package/dist/{primitives → components}/button.d.ts +1 -1
  5. package/dist/{primitives → components}/button.js +4 -1
  6. package/dist/{primitives → components}/card.d.ts +1 -1
  7. package/dist/{primitives → components}/card.js +2 -2
  8. package/dist/{primitives → components}/combobox.js +4 -4
  9. package/dist/{primitives → components}/dialog.js +1 -1
  10. package/dist/{primitives → components}/dropdown-menu.js +12 -7
  11. package/dist/{primitives → components}/field.js +2 -4
  12. package/dist/{primitives → components}/input-group.js +1 -1
  13. package/dist/{primitives → components}/input.js +1 -1
  14. package/dist/{primitives → components}/label.js +1 -1
  15. package/dist/components/progress.js +7 -0
  16. package/dist/{primitives → components}/select.d.ts +3 -1
  17. package/dist/{primitives → components}/select.js +7 -4
  18. package/dist/{primitives → components}/separator.js +1 -1
  19. package/dist/{primitives → components}/sheet.js +1 -1
  20. package/dist/{primitives → components}/skeleton.js +1 -1
  21. package/dist/{primitives → components}/sonner.js +17 -12
  22. package/dist/{primitives → components}/table.d.ts +1 -1
  23. package/dist/{primitives → components}/table.js +3 -3
  24. package/dist/{primitives → components}/tabs.js +1 -1
  25. package/dist/{primitives → components}/textarea.js +1 -1
  26. package/dist/{primitives → components}/tooltip.js +1 -1
  27. package/dist/gates/index.d.ts +28 -0
  28. package/dist/gates/index.js +142 -0
  29. package/dist/index.d.ts +31 -32
  30. package/dist/index.js +32 -33
  31. package/dist/{tokens.d.ts → theme/tokens.d.ts} +5 -5
  32. package/dist/{tokens.js → theme/tokens.js} +12 -12
  33. package/package.json +23 -12
  34. package/{shadcn → src/components}/alert.tsx +14 -21
  35. package/src/{primitives → components}/badge.tsx +15 -21
  36. package/src/{primitives → components}/button.tsx +13 -10
  37. package/{shadcn → src/components}/card.tsx +16 -31
  38. package/src/{primitives → components}/combobox.test.tsx +15 -2
  39. package/src/{primitives → components}/combobox.tsx +45 -74
  40. package/src/{primitives/primitives.test.tsx → components/components.test.tsx} +106 -69
  41. package/src/{primitives → components}/dialog.tsx +31 -54
  42. package/src/{primitives → components}/dropdown-menu.tsx +51 -65
  43. package/src/{primitives → components}/field.tsx +48 -62
  44. package/src/{primitives → components}/input-group.tsx +37 -49
  45. package/{shadcn → src/components}/input.tsx +5 -5
  46. package/{shadcn → src/components}/label.tsx +7 -10
  47. package/src/{primitives → components}/progress.tsx +7 -6
  48. package/{shadcn → src/components}/select.tsx +40 -40
  49. package/{shadcn → src/components}/separator.tsx +7 -7
  50. package/src/{primitives → components}/sheet.tsx +27 -44
  51. package/{shadcn → src/components}/skeleton.tsx +3 -3
  52. package/src/{primitives → components}/sonner.test.tsx +12 -4
  53. package/src/components/sonner.tsx +67 -0
  54. package/{shadcn → src/components}/table.tsx +17 -44
  55. package/{shadcn → src/components}/tabs.tsx +16 -26
  56. package/{shadcn → src/components}/textarea.tsx +5 -5
  57. package/{shadcn → src/components}/tooltip.tsx +12 -16
  58. package/src/gates/design.test.ts +188 -0
  59. package/src/gates/index.test.ts +11 -0
  60. package/src/gates/index.ts +165 -0
  61. package/src/gates/tokens.test.ts +215 -0
  62. package/src/harness.test.tsx +1 -1
  63. package/src/index.ts +63 -33
  64. package/src/theme/imports.test.ts +160 -0
  65. package/src/{statusTone.test.ts → theme/statusTone.test.ts} +5 -1
  66. package/src/{styles.css → theme/styles.css} +45 -31
  67. package/src/theme/tailwind.css +646 -0
  68. package/src/{tokens.test.ts → theme/tokens.test.ts} +30 -23
  69. package/src/{tokens.ts → theme/tokens.ts} +12 -12
  70. package/components.json +0 -13
  71. package/dist/lib/button.d.ts +0 -1
  72. package/dist/lib/button.js +0 -4
  73. package/dist/lib/cn.d.ts +0 -18
  74. package/dist/lib/cn.js +0 -21
  75. package/dist/lib/menu.d.ts +0 -1
  76. package/dist/lib/menu.js +0 -16
  77. package/dist/lib/progress.d.ts +0 -3
  78. package/dist/lib/progress.js +0 -6
  79. package/dist/lib/select.d.ts +0 -1
  80. package/dist/lib/select.js +0 -3
  81. package/dist/primitives/progress.js +0 -7
  82. package/gates/canonical.test.mjs +0 -71
  83. package/gates/extensions.test.mjs +0 -373
  84. package/gates/index.d.mts +0 -51
  85. package/gates/index.mjs +0 -514
  86. package/gates/tokens.test.mjs +0 -276
  87. package/shadcn/README.md +0 -13
  88. package/shadcn/badge.tsx +0 -49
  89. package/shadcn/button.tsx +0 -67
  90. package/shadcn/combobox.tsx +0 -299
  91. package/shadcn/dialog.tsx +0 -166
  92. package/shadcn/dropdown-menu.tsx +0 -267
  93. package/shadcn/field.tsx +0 -236
  94. package/shadcn/input-group.tsx +0 -154
  95. package/shadcn/progress.tsx +0 -29
  96. package/shadcn/sheet.tsx +0 -145
  97. package/shadcn/sonner.tsx +0 -49
  98. package/src/lib/button.ts +0 -4
  99. package/src/lib/cn.test.ts +0 -33
  100. package/src/lib/cn.ts +0 -23
  101. package/src/lib/menu.ts +0 -16
  102. package/src/lib/progress.ts +0 -6
  103. package/src/lib/select.ts +0 -4
  104. package/src/primitives/alert.tsx +0 -76
  105. package/src/primitives/card.tsx +0 -103
  106. package/src/primitives/input.tsx +0 -19
  107. package/src/primitives/label.tsx +0 -22
  108. package/src/primitives/select.tsx +0 -190
  109. package/src/primitives/separator.tsx +0 -28
  110. package/src/primitives/skeleton.tsx +0 -13
  111. package/src/primitives/sonner.tsx +0 -65
  112. package/src/primitives/table.tsx +0 -114
  113. package/src/primitives/tabs.tsx +0 -88
  114. package/src/primitives/textarea.tsx +0 -18
  115. package/src/primitives/tooltip.tsx +0 -57
  116. /package/dist/{primitives → components}/alert.d.ts +0 -0
  117. /package/dist/{primitives → components}/badge.d.ts +0 -0
  118. /package/dist/{primitives → components}/combobox.d.ts +0 -0
  119. /package/dist/{primitives → components}/dialog.d.ts +0 -0
  120. /package/dist/{primitives → components}/dropdown-menu.d.ts +0 -0
  121. /package/dist/{primitives → components}/field.d.ts +0 -0
  122. /package/dist/{primitives → components}/input-group.d.ts +0 -0
  123. /package/dist/{primitives → components}/input.d.ts +0 -0
  124. /package/dist/{primitives → components}/label.d.ts +0 -0
  125. /package/dist/{primitives → components}/progress.d.ts +0 -0
  126. /package/dist/{primitives → components}/separator.d.ts +0 -0
  127. /package/dist/{primitives → components}/sheet.d.ts +0 -0
  128. /package/dist/{primitives → components}/skeleton.d.ts +0 -0
  129. /package/dist/{primitives → components}/sonner.d.ts +0 -0
  130. /package/dist/{primitives → components}/tabs.d.ts +0 -0
  131. /package/dist/{primitives → components}/textarea.d.ts +0 -0
  132. /package/dist/{primitives → components}/tooltip.d.ts +0 -0
  133. /package/dist/{statusTone.d.ts → theme/statusTone.d.ts} +0 -0
  134. /package/dist/{statusTone.js → theme/statusTone.js} +0 -0
  135. /package/src/{statusTone.ts → theme/statusTone.ts} +0 -0
@@ -0,0 +1,160 @@
1
+ // @vitest-environment node
2
+ /**
3
+ * Everything `styles.css` imports really resolves — packages and siblings both.
4
+ *
5
+ * The stylesheet ships as **source**: `exports["./styles.css"]` hands a consumer
6
+ * the file itself, and the consumer's Tailwind build is what resolves the
7
+ * `@import`s in it. So a package named there is a genuine runtime dependency of
8
+ * this one, and a sibling named there is a file that has to be published. Get
9
+ * either wrong and every consumer breaks while `pnpm lint`, `pnpm build` and
10
+ * `pnpm test` all stay green — nothing in this repository compiles CSS, so
11
+ * nothing here would otherwise notice. A rule nothing checks is a preference;
12
+ * this is the check.
13
+ *
14
+ * Both halves matter, and they fail differently. A missing **package** is a
15
+ * dependency that was dropped from `package.json` because a grep for
16
+ * `from "..."` never sees a CSS `@import`. A missing **sibling** is a file that
17
+ * exists on the author's disk but was never committed, or was left out of
18
+ * `files` — so it resolves here and nowhere else.
19
+ *
20
+ * Package resolution goes through the package's own `exports` map with the
21
+ * `style` condition first, because that is the condition a CSS bundler asks for
22
+ * and some packages expose their stylesheet under no other — Node's own
23
+ * `import.meta.resolve` asks for `import`/`require` and would report a
24
+ * correctly installed package as missing.
25
+ */
26
+
27
+ import { spawnSync } from "node:child_process";
28
+ import { existsSync, readFileSync } from "node:fs";
29
+ import path from "node:path";
30
+ import { fileURLToPath } from "node:url";
31
+
32
+ import { expect, test } from "vitest";
33
+
34
+ const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
35
+ const MODULES = path.join(REPO, "node_modules");
36
+ const STYLESHEET_DIR = path.join(REPO, "src/theme");
37
+ const STYLESHEET = readFileSync(path.join(STYLESHEET_DIR, "styles.css"), "utf8");
38
+
39
+ /** The conditions a CSS bundler offers, most specific first. */
40
+ const CONDITIONS = ["style", "default", "import", "require"];
41
+
42
+ /** Every `@import` specifier in the stylesheet, in source order. */
43
+ function importsIn(css: string): string[] {
44
+ return [...css.matchAll(/^\s*@import\s+["']([^"']+)["']/gm)].map((match) => match[1]);
45
+ }
46
+
47
+ /** A URL needs no filesystem, so it is not this gate's business. */
48
+ const isRemote = (spec: string) => /^https?:/.test(spec);
49
+
50
+ /** `"./tailwind.css"`, `"../a/b.css"` — resolved against the stylesheet itself. */
51
+ const isRelative = (spec: string) => /^\.{1,2}\//.test(spec);
52
+
53
+ /** `"@scope/pkg/a/b"` → `["@scope/pkg", "./a/b"]`; `"pkg"` → `["pkg", "."]`. */
54
+ function splitSpecifier(spec: string): [name: string, subpath: string] {
55
+ const segments = spec.split("/");
56
+ const name = spec.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
57
+ const rest = spec.slice(name.length);
58
+ return [name, rest === "" ? "." : `.${rest}`];
59
+ }
60
+
61
+ /** The first branch of a conditional export any of `CONDITIONS` selects. */
62
+ function pickCondition(node: unknown): string | null {
63
+ if (typeof node === "string") return node;
64
+ if (node === null || typeof node !== "object") return null;
65
+ const branches = node as Record<string, unknown>;
66
+ for (const condition of CONDITIONS) {
67
+ if (condition in branches) {
68
+ const picked = pickCondition(branches[condition]);
69
+ if (picked !== null) return picked;
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+
75
+ /** Where a package's `exports` (or its legacy fields) send one subpath. */
76
+ function targetOf(manifest: Record<string, unknown>, subpath: string): string | null {
77
+ const map = manifest.exports;
78
+ if (map === undefined) {
79
+ // No exports map: "." is the package's own stylesheet field, and any other
80
+ // subpath is a plain path inside the package.
81
+ if (subpath !== ".") return subpath;
82
+ return (manifest.style as string) ?? (manifest.main as string) ?? null;
83
+ }
84
+ if (typeof map === "string") return subpath === "." ? map : null;
85
+ const entries = map as Record<string, unknown>;
86
+ if (subpath in entries) return pickCondition(entries[subpath]);
87
+ // An exports map that is conditions all the way down is shorthand for ".".
88
+ if (subpath === "." && !Object.keys(entries).some((key) => key.startsWith("."))) {
89
+ return pickCondition(entries);
90
+ }
91
+ return null;
92
+ }
93
+
94
+ /** The file a specifier lands on, or why it lands nowhere. */
95
+ function resolveStyleImport(spec: string): { file: string } | { reason: string } {
96
+ const [name, subpath] = splitSpecifier(spec);
97
+ const root = path.join(MODULES, ...name.split("/"));
98
+ const manifestPath = path.join(root, "package.json");
99
+ if (!existsSync(manifestPath)) {
100
+ return { reason: `${name} is not installed — it is missing from package.json dependencies` };
101
+ }
102
+ const target = targetOf(JSON.parse(readFileSync(manifestPath, "utf8")), subpath);
103
+ if (target === null) {
104
+ return { reason: `${name} declares no export for ${subpath}` };
105
+ }
106
+ const file = path.join(root, target);
107
+ if (!existsSync(file)) {
108
+ return { reason: `${name} exports ${subpath} as ${target}, which does not exist` };
109
+ }
110
+ return { file };
111
+ }
112
+
113
+ test("every package the stylesheet imports is installed and exports the file it names", () => {
114
+ const specs = importsIn(STYLESHEET).filter((spec) => !isRemote(spec) && !isRelative(spec));
115
+ expect(specs.length > 0, "the stylesheet named no packages, so this proves nothing").toBe(true);
116
+
117
+ const broken = specs
118
+ .map((spec) => ({ spec, result: resolveStyleImport(spec) }))
119
+ .filter((entry) => "reason" in entry.result)
120
+ .map((entry) => `@import "${entry.spec}": ${(entry.result as { reason: string }).reason}`);
121
+
122
+ expect(
123
+ broken,
124
+ "styles.css ships as source and the consumer's Tailwind build resolves these, so an " +
125
+ "unresolvable @import breaks every consumer and nothing in this repository compiles " +
126
+ `CSS to catch it:\n${broken.join("\n")}`,
127
+ ).toEqual([]);
128
+ });
129
+
130
+ test("every sibling the stylesheet imports exists and is committed", () => {
131
+ const specs = importsIn(STYLESHEET).filter(isRelative);
132
+ expect(specs.length > 0, "the stylesheet named no siblings, so this proves nothing").toBe(true);
133
+
134
+ const broken = specs.flatMap((spec) => {
135
+ const file = path.resolve(STYLESHEET_DIR, spec);
136
+ if (!existsSync(file)) {
137
+ return [`@import "${spec}": ${path.relative(REPO, file)} does not exist`];
138
+ }
139
+ // On disk is not enough. `files: ["src"]` publishes what the working tree
140
+ // holds, so an uncommitted sibling packs fine here and is absent from the
141
+ // clone CI builds and publishes from — the failure lands on consumers only.
142
+ const tracked = spawnSync("git", ["ls-files", "--error-unmatch", file], {
143
+ cwd: REPO,
144
+ encoding: "utf8",
145
+ });
146
+ if (tracked.status !== 0) {
147
+ return [
148
+ `@import "${spec}": ${path.relative(REPO, file)} exists but is not tracked by git — ` +
149
+ "commit it, or the published package will not carry it",
150
+ ];
151
+ }
152
+ return [];
153
+ });
154
+
155
+ expect(
156
+ broken,
157
+ "styles.css ships as source, so a sibling it imports has to reach the consumer with it:\n" +
158
+ broken.join("\n"),
159
+ ).toEqual([]);
160
+ });
@@ -13,7 +13,11 @@ describe("status tones", () => {
13
13
  expect(STATUS_INK.warning).toBe("text-amber-700 dark:text-amber-400");
14
14
  expect(STATUS_INK.success).toBe("text-emerald-700 dark:text-emerald-400");
15
15
  expect(STATUS_INK.info).toBe("text-sky-700 dark:text-sky-400");
16
- for (const v of [...Object.values(TONE_FILL), ...Object.values(TONE_BORDER), ...Object.values(STATUS_INK)]) {
16
+ for (const v of [
17
+ ...Object.values(TONE_FILL),
18
+ ...Object.values(TONE_BORDER),
19
+ ...Object.values(STATUS_INK),
20
+ ]) {
17
21
  expect(v).not.toMatch(/\b(?:bg|border|text)-(?:success|warning)\b/);
18
22
  }
19
23
  });
@@ -1,17 +1,22 @@
1
1
  /*
2
2
  * @robomous/ui-core — the design system's stylesheet.
3
3
  *
4
- * This is the shadcn preset `b2iH` (style `nova` on the Radix base, base colour
5
- * `neutral`, chart palette `neutral`, icons `lucide`, Geist throughout with the
6
- * heading face inheriting the body's, radius medium, menu inverted/subtle, and
7
- * the pointer cursor on pressable controls) — the CLI's exact generated output,
8
- * transcribed verbatim from a Docker scratch project built with `shadcn` 4.19.0. The preset is the source of truth for every
9
- * name below except one: `brand` is Robomous's own, justified in
10
- * `DESIGN.md` and following shadcn's own extension convention a value
11
- * in `:root`, a dark counterpart in `.dark`, exposure through `@theme
12
- * inline`. Consumers add their extensions in their own stylesheet after
13
- * importing this one. `src/tokens.ts` is the TypeScript mirror of this file
14
- * and `tokens.test.ts` asserts the two agree, declaration for declaration.
4
+ * **This file is the design system's one home for a colour.** Tailwind v4 is
5
+ * CSS-first, so this is not a mirror of a config — it *is* the config, and
6
+ * every utility in this package and in every consuming app resolves through
7
+ * the names below. `brand` is Robomous's own identity colour, justified in
8
+ * `DESIGN.md`, and it is declared the way every other name here is: a value in
9
+ * `:root`, a dark counterpart in `.dark`, exposure through `@theme inline`.
10
+ * Consumers add their extensions in their own stylesheet after importing this
11
+ * one. `tokens.ts` is the TypeScript mirror of this file, for callers that
12
+ * cannot read CSS, and `tokens.test.ts` asserts the two agree declaration for
13
+ * declaration.
14
+ *
15
+ * `./tailwind.css` carries the variant and utility layer the components paint
16
+ * through: the `data-*` variants that key off the `data-state` attributes the
17
+ * behaviour libraries emit, plus the scroll-mask and shimmer utilities. It is
18
+ * imported ahead of the token blocks because a variant has to exist before a
19
+ * utility can qualify on it.
15
20
  *
16
21
  * A consuming app imports exactly this:
17
22
  *
@@ -24,7 +29,7 @@
24
29
 
25
30
  @import "tailwindcss";
26
31
  @import "tw-animate-css";
27
- @import "shadcn/tailwind.css";
32
+ @import "./tailwind.css";
28
33
  @import "@fontsource-variable/geist";
29
34
 
30
35
  /*
@@ -38,15 +43,15 @@
38
43
 
39
44
  :root {
40
45
  --background: oklch(1 0 0);
41
- --foreground: oklch(0.145 0 0);
46
+ --foreground: oklch(0.2 0 0);
42
47
  --card: oklch(1 0 0);
43
48
  --card-foreground: oklch(0.145 0 0);
44
49
  --popover: oklch(1 0 0);
45
50
  --popover-foreground: oklch(0.145 0 0);
46
- --primary: oklch(0.205 0 0);
51
+ --primary: oklch(0.2 0 0);
47
52
  --primary-foreground: oklch(0.985 0 0);
48
53
  --secondary: oklch(0.97 0 0);
49
- --secondary-foreground: oklch(0.205 0 0);
54
+ --secondary-foreground: oklch(0.269 0 0);
50
55
  --muted: oklch(0.97 0 0);
51
56
  --muted-foreground: oklch(0.556 0 0);
52
57
  --accent: oklch(0.97 0 0);
@@ -71,11 +76,15 @@
71
76
  --sidebar-ring: oklch(0.708 0 0);
72
77
 
73
78
  /*
74
- * Robomous coral. Identity only — the wordmark and its styleguide swatch,
75
- * named in `DESIGN.md` — never a functional-UI colour. A third site
79
+ * Robomous orange (#F5580B). Identity only — the wordmark and its styleguide
80
+ * swatch, named in `DESIGN.md` — never a functional-UI colour. A third site
76
81
  * is a design decision, not a styling one.
82
+ *
83
+ * At 3.34:1 on `background` it clears 3:1 for large text and non-text marks
84
+ * and misses 4.5:1 for body copy, which is one more reason the wordmark is
85
+ * where it belongs.
77
86
  */
78
- --brand: oklch(0.653 0.178 32.3);
87
+ --brand: oklch(0.663 0.205 39.9);
79
88
  }
80
89
 
81
90
  .dark {
@@ -111,11 +120,11 @@
111
120
  --sidebar-border: oklch(1 0 0 / 10%);
112
121
  --sidebar-ring: oklch(0.556 0 0);
113
122
 
114
- --brand: oklch(0.653 0.178 32.3);
123
+ --brand: oklch(0.663 0.205 39.9);
115
124
  }
116
125
 
117
126
  @theme inline {
118
- --font-sans: 'Geist Variable', sans-serif;
127
+ --font-sans: "Geist Variable", sans-serif;
119
128
  --font-heading: var(--font-sans);
120
129
  --color-sidebar-ring: var(--sidebar-ring);
121
130
  --color-sidebar-border: var(--sidebar-border);
@@ -156,15 +165,16 @@
156
165
  --radius-3xl: calc(var(--radius) * 2.2);
157
166
  --radius-4xl: calc(var(--radius) * 2.6);
158
167
 
159
- /* Robomous's one identity token, exposed the same way as every preset colour. */
168
+ /* Robomous's one identity token, exposed the same way as every other colour. */
160
169
  --color-brand: var(--brand);
161
170
  }
162
171
 
163
172
  @layer base {
164
173
  /* The hairline and the outline *colour* — the two things every element
165
- inherits. The focus treatment itself belongs to the primitives: each one
166
- carries Nova's own `ring-3` ring in `ring/50` (DESIGN.md, Borders and
167
- focus), so the base layer names the colour and stays out of the geometry.
174
+ inherits. The focus treatment itself belongs to the components: each
175
+ focusable one carries its own `ring-3` ring in `ring/50` (DESIGN.md,
176
+ Borders and focus), so the base layer names the colour and stays out of
177
+ the geometry.
168
178
  Nothing here may declare focus geometry again: one blanket declaration in
169
179
  this layer is how thirteen components' rings got overridden at once. */
170
180
  * {
@@ -186,19 +196,23 @@
186
196
  body {
187
197
  @apply bg-background text-foreground;
188
198
  }
189
- /* The preset's pointer option. Scoped to what is actually pressable and away
190
- from what is not: a disabled control keeps the arrow, because a hand over
191
- something that will not respond is the cursor telling a lie. */
192
- button:not(:disabled), [role="button"]:not(:disabled) {
199
+ /* Scoped to what is actually pressable and away from what is not: a disabled
200
+ control keeps the arrow, because a hand over something that will not
201
+ respond is the cursor telling a lie. */
202
+ button:not(:disabled),
203
+ [role="button"]:not(:disabled) {
193
204
  cursor: pointer;
194
205
  }
195
206
  html {
196
207
  @apply font-sans;
197
208
  }
198
- /* One family now: the preset sets `--font-heading` to `--font-sans`, so this
199
- rule keeps the semantic hook without changing the face. A later preset that
209
+ /* One family now: `--font-heading` resolves to `--font-sans`, so this rule
210
+ keeps the semantic hook without changing the face. A later decision that
200
211
  splits them again lands here without a diff at every heading. */
201
- h1, h2, h3, h4 {
212
+ h1,
213
+ h2,
214
+ h3,
215
+ h4 {
202
216
  @apply font-heading;
203
217
  }
204
218
  }