@cloudgrid-io/ui 0.28.0 → 0.29.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.
package/README.md CHANGED
@@ -35,6 +35,7 @@ component tree.
35
35
 
36
36
  @import "@cloudgrid-io/ui/fonts.css";
37
37
  @import "@cloudgrid-io/ui/tokens.css";
38
+ @import "@cloudgrid-io/ui/console.css"; /* optional, see below */
38
39
  @import "@cloudgrid-io/ui/theme.css";
39
40
  @import "@cloudgrid-io/ui/shadcn.css";
40
41
  ```
@@ -44,10 +45,15 @@ The order matters, and getting it wrong fails quietly rather than loudly.
44
45
  - `theme.css` maps Tailwind utilities onto the tokens. Before `tokens.css` has run there is
45
46
  nothing to map, so every utility resolves to nothing and the page renders unstyled
46
47
  instead of erroring.
48
+ - `console.css` is a **product preset**, not part of the base. It re-declares the body and
49
+ display faces as the UI face, Manrope. The CloudGrid Console imports it; so should any
50
+ product whose type is the UI face rather than the display face. Everyone else leaves the
51
+ line out and keeps the default. It has to sit after `tokens.css`, which is what it
52
+ re-declares, and before `theme.css`, which maps the utilities onto the result.
47
53
  - `shadcn.css` must come after whatever `:root` your own `shadcn init` wrote, because it
48
54
  overrides those values. Put it earlier and the stock grey palette wins.
49
55
 
50
- Not using shadcn: skip the last line. Not using Tailwind: `tokens.css` alone gives you every
56
+ Not using the Console's face: skip the `console.css` line. Not using shadcn: skip the last line. Not using Tailwind: `tokens.css` alone gives you every
51
57
  value as a custom property.
52
58
 
53
59
  ## Point Tailwind at the package
@@ -92,6 +98,7 @@ property of a container, never a page-level theme.
92
98
  | | |
93
99
  |---|---|
94
100
  | `@cloudgrid-io/ui/tokens.css` | every value as a CSS custom property |
101
+ | `@cloudgrid-io/ui/console.css` | product preset: body and display type set to Manrope |
95
102
  | `@cloudgrid-io/ui/theme.css` | maps Tailwind utilities onto the tokens |
96
103
  | `@cloudgrid-io/ui/shadcn.css` | maps shadcn's variables onto the tokens |
97
104
  | `@cloudgrid-io/ui/fonts.css` | the three self-hosted families |
@@ -0,0 +1,65 @@
1
+ /*
2
+ A PRODUCT PRESET, not a theme and not a second brand.
3
+
4
+ The Console's type is the UI face, Manrope, for body and for display. This
5
+ file states that in the library, so the Console does not carry a font stack of
6
+ its own. ui#250, 2026-09-20.
7
+
8
+ Two declarations, and deliberately nothing else: no colour, no spacing, no
9
+ size, no component. A preset that grew a second concern would become a theme,
10
+ and this package has one brand.
11
+
12
+ WHY IT EXISTS. #243 moved --cg-font-body to Space Grotesk and --cg-font-display
13
+ to Tanker, measured against the six canonical marketing frames, and that is
14
+ correct for the marketing site. The Console deleted its own @theme font
15
+ forwards (console#652) and inherits every family from here, so installing
16
+ 0.28.0 changed every face in it at once. The marketing default is untouched by
17
+ this file; the Console imports it and gets back exactly the two values both
18
+ tokens held before ef8dc00.
19
+
20
+ WHY var(--cg-font-ui) RATHER THAN THE STACK. --cg-font-ui is already the
21
+ Manrope stack, and its docblock in tokens.css records that its value is
22
+ --cg-font-body's previous value byte for byte. Aliasing keeps one copy of the
23
+ fallback chain in the package. A consumer that spelled the stack out instead
24
+ went stale the moment the library's fallbacks moved — cloudgrid-landing PR
25
+ #132 did exactly that with the display stack — and a copy one repo further
26
+ away goes stale the same way.
27
+
28
+ IMPORT ORDER. After tokens.css, which is what it re-declares, and before
29
+ theme.css, which maps the Tailwind utilities onto the result:
30
+
31
+ @import "tailwindcss";
32
+ @import "@cloudgrid-io/ui/fonts.css";
33
+ @import "@cloudgrid-io/ui/tokens.css";
34
+ @import "@cloudgrid-io/ui/console.css"; <- here
35
+ @import "@cloudgrid-io/ui/theme.css";
36
+ @import "@cloudgrid-io/ui/shadcn.css";
37
+
38
+ Import it before tokens.css and tokens.css wins: same specificity, later
39
+ declaration, and the page renders Space Grotesk with no error anywhere.
40
+
41
+ THE SELECTOR LIST IS LOAD-BEARING, for the reason shadcn.css states at length.
42
+ A custom property that references another is substituted where it is DECLARED,
43
+ and descendants inherit the already-resolved value. tokens.css declares the
44
+ font families only at :root today, because a family has no on-purple value, so
45
+ a :root-only preset would render correctly as things stand. It is repeated on
46
+ both surface selectors anyway: the day --cg-font-ui gains a per-surface value,
47
+ a :root-only alias freezes to the root stack and silently stops following it.
48
+ Three selectors is a cheaper price than that failure, and
49
+ test/console-preset.test.mjs pins it.
50
+
51
+ WHO IMPORTS IT. The Console first. Any product whose type is the UI face
52
+ rather than the display face imports the same file; nothing here is named for
53
+ the Console except this sentence.
54
+
55
+ SIDE EFFECT WORTH KNOWING. StatTile values read as typed again. Tanker is an
56
+ all-caps face, so `15s` and `Coming soon` rendered as `15S` and `COMING SOON`
57
+ under the marketing display token. No component changed to fix that.
58
+ */
59
+
60
+ :root,
61
+ [data-cg-surface='panel'],
62
+ [data-cg-surface='ink'] {
63
+ --cg-font-body: var(--cg-font-ui);
64
+ --cg-font-display: var(--cg-font-ui);
65
+ }
@@ -60,6 +60,6 @@ import { type ClassValue } from "clsx";
60
60
  * to write `font-regular` rediscovers this.
61
61
  */
62
62
  export declare const BRAND_FONT_WEIGHTS: readonly ["heavy", "regular"];
63
- export declare const BRAND_FONT_SIZES: readonly ["display-3xl", "display-2xl", "display-xl", "display-lg", "display-md", "display-sm", "title-lg", "title-md", "title-sm", "heading-lg", "heading-md", "heading-sm", "body-xl", "body-lg", "body-md", "body-sm", "caption", "mono", "label", "eyebrow"];
63
+ export declare const BRAND_FONT_SIZES: readonly ["display-3xl", "display-2xl", "display-xl", "display-48", "display-lg", "display-md", "display-sm", "title-lg", "title-36", "title-32", "title-md", "title-24", "title-sm", "heading-lg", "heading-md", "heading-sm", "body-xl", "body-lg", "body-18", "body-md", "body-sm", "caption", "mono", "label", "eyebrow", "stat"];
64
64
  export declare function cn(...inputs: ClassValue[]): string;
65
65
  //# sourceMappingURL=cloudgrid-cn.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cloudgrid-cn.d.ts","sourceRoot":"","sources":["../../src/lib/cloudgrid-cn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAG5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,kBAAkB,+BAAgC,CAAA;AAE/D,eAAO,MAAM,gBAAgB,kQAqBnB,CAAA;AAWV,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
1
+ {"version":3,"file":"cloudgrid-cn.d.ts","sourceRoot":"","sources":["../../src/lib/cloudgrid-cn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAG5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,kBAAkB,+BAAgC,CAAA;AAE/D,eAAO,MAAM,gBAAgB,uUA+BnB,CAAA;AAWV,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
@@ -65,23 +65,33 @@ export const BRAND_FONT_SIZES = [
65
65
  "display-3xl",
66
66
  "display-2xl",
67
67
  "display-xl",
68
+ // Px-named rungs (#248). They do not match tailwind-merge's t-shirt size
69
+ // regex either -- `^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$` anchors on a t-shirt
70
+ // word, and "48" has none -- so they fall through to text-color exactly the
71
+ // way the role names do, and belong here for the same reason.
72
+ "display-48",
68
73
  "display-lg",
69
74
  "display-md",
70
75
  "display-sm",
71
76
  "title-lg",
77
+ "title-36",
78
+ "title-32",
72
79
  "title-md",
80
+ "title-24",
73
81
  "title-sm",
74
82
  "heading-lg",
75
83
  "heading-md",
76
84
  "heading-sm",
77
85
  "body-xl",
78
86
  "body-lg",
87
+ "body-18",
79
88
  "body-md",
80
89
  "body-sm",
81
90
  "caption",
82
91
  "mono",
83
92
  "label",
84
93
  "eyebrow",
94
+ "stat",
85
95
  ];
86
96
  const twMerge = extendTailwindMerge({
87
97
  extend: {
@@ -1 +1 @@
1
- {"version":3,"file":"cloudgrid-cn.js","sourceRoot":"","sources":["../../src/lib/cloudgrid-cn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,MAAM,CAAA;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,SAAS,CAAU,CAAA;AAE/D,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,aAAa;IACb,aAAa;IACb,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,UAAU;IACV,UAAU;IACV,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,MAAM;IACN,OAAO;IACP,SAAS;CACD,CAAA;AAEV,MAAM,OAAO,GAAG,mBAAmB,CAAC;IAClC,MAAM,EAAE;QACN,WAAW,EAAE;YACX,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,gBAAgB,CAAC,EAAE,CAAC;YAC9C,aAAa,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,kBAAkB,CAAC,EAAE,CAAC;SACnD;KACF;CACF,CAAC,CAAA;AAEF,MAAM,UAAU,EAAE,CAAC,GAAG,MAAoB;IACxC,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;AAC9B,CAAC"}
1
+ {"version":3,"file":"cloudgrid-cn.js","sourceRoot":"","sources":["../../src/lib/cloudgrid-cn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,MAAM,CAAA;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,SAAS,CAAU,CAAA;AAE/D,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,aAAa;IACb,aAAa;IACb,YAAY;IACZ,yEAAyE;IACzE,0EAA0E;IAC1E,4EAA4E;IAC5E,8DAA8D;IAC9D,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,UAAU;IACV,UAAU;IACV,UAAU;IACV,UAAU;IACV,UAAU;IACV,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,MAAM;IACN,OAAO;IACP,SAAS;IACT,MAAM;CACE,CAAA;AAEV,MAAM,OAAO,GAAG,mBAAmB,CAAC;IAClC,MAAM,EAAE;QACN,WAAW,EAAE;YACX,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,gBAAgB,CAAC,EAAE,CAAC;YAC9C,aAAa,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,kBAAkB,CAAC,EAAE,CAAC;SACnD;KACF;CACF,CAAC,CAAA;AAEF,MAAM,UAAU,EAAE,CAAC,GAAG,MAAoB;IACxC,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;AAC9B,CAAC"}
package/dist/theme.css CHANGED
@@ -289,6 +289,20 @@
289
289
  --text-display-xl: var(--cg-display-xl);
290
290
  --text-display-xl--line-height: var(--cg-display-line);
291
291
  --text-display-xl--letter-spacing: var(--cg-display-tracking);
292
+ /* Like 2xl and 3xl, and unlike the four rungs below it, this one is
293
+ leading-none with no tracking because its node is (#248). The legacy
294
+ --cg-display-line 1.06 and --cg-display-tracking 0.012em were tuned for a
295
+ condensed face this package no longer ships and are kept only for the rungs
296
+ that already rendered with them. */
297
+ --text-display-48: var(--cg-display-48);
298
+ --text-display-48--line-height: var(--cg-display-line-tight);
299
+ --text-display-48--letter-spacing: var(--cg-title-tracking);
300
+ /* --cg-display-64 has NO utility here, deliberately. Its two nodes disagree on
301
+ a leading -- thegrid sets 70px, home leaves Figma's Auto -- and a --text-*
302
+ entry cannot exist without a --line-height companion, so the utility could
303
+ only be built by inventing one. Same reasoning as --cg-body-14, and the same
304
+ reasoning that keeps 96px out of the ramp entirely. Spell it as
305
+ `[font-size:var(--cg-display-64)]` with the leading the page wants. */
292
306
  --text-display-lg: var(--cg-display-lg);
293
307
  --text-display-lg--line-height: var(--cg-display-line);
294
308
  --text-display-lg--letter-spacing: var(--cg-display-tracking);
@@ -306,9 +320,21 @@
306
320
  --text-title-lg: var(--cg-title-lg);
307
321
  --text-title-lg--line-height: var(--cg-title-line);
308
322
  --text-title-lg--letter-spacing: var(--cg-title-tracking);
323
+ /* Three rungs named by pixel value (#248): every size this round falls
324
+ strictly between two shipped ones, so there is no free t-shirt name in the
325
+ right place. tokens.css's --cg-display-64 note has the reasoning. */
326
+ --text-title-36: var(--cg-title-36);
327
+ --text-title-36--line-height: var(--cg-title-line);
328
+ --text-title-36--letter-spacing: var(--cg-title-tracking);
329
+ --text-title-32: var(--cg-title-32);
330
+ --text-title-32--line-height: var(--cg-title-line);
331
+ --text-title-32--letter-spacing: var(--cg-title-tracking);
309
332
  --text-title-md: var(--cg-title-md);
310
333
  --text-title-md--line-height: var(--cg-title-line);
311
334
  --text-title-md--letter-spacing: var(--cg-title-tracking);
335
+ --text-title-24: var(--cg-title-24);
336
+ --text-title-24--line-height: var(--cg-title-line);
337
+ --text-title-24--letter-spacing: var(--cg-title-tracking);
312
338
  --text-title-sm: var(--cg-title-sm);
313
339
  --text-title-sm--line-height: var(--cg-title-line);
314
340
  --text-title-sm--letter-spacing: var(--cg-title-tracking);
@@ -324,10 +350,20 @@
324
350
  --text-body-xl--line-height: var(--cg-body-line);
325
351
  --text-body-lg: var(--cg-body-lg);
326
352
  --text-body-lg--line-height: var(--cg-body-line);
353
+ /* pricing's three plan names, at --cg-body-line exactly. A body rung and not a
354
+ heading one: nothing at 18 in six frames is near --cg-heading-line's 1.2
355
+ (#248). */
356
+ --text-body-18: var(--cg-body-18);
357
+ --text-body-18--line-height: var(--cg-body-line);
327
358
  --text-body-md: var(--cg-body-md);
328
359
  --text-body-md--line-height: var(--cg-body-line);
329
360
  --text-body-sm: var(--cg-body-sm);
330
361
  --text-body-sm--line-height: var(--cg-body-line);
362
+ /* --cg-body-14 has NO utility, for the same reason as --cg-display-64: its
363
+ three non-eyebrow runs read 1.295 and 1.55 and there is no leading to ship.
364
+ Sixteen of the nineteen 14px runs in the frames are the eyebrow, which is
365
+ --text-eyebrow below and already carries its own leading, tracking and
366
+ weight. */
331
367
  --text-caption: var(--cg-caption);
332
368
  --text-caption--line-height: var(--cg-body-line);
333
369
  --text-mono: var(--cg-mono-size);
@@ -344,6 +380,14 @@
344
380
  --text-eyebrow--line-height: var(--cg-eyebrow-line);
345
381
  --text-eyebrow--letter-spacing: var(--cg-eyebrow-tracking);
346
382
  --text-eyebrow--font-weight: var(--cg-eyebrow-weight);
383
+ /* The big figure in a stat block: the UI face at leading-none, which no other
384
+ rung on this ramp is (heading is 1.2, body is 1.46). Ten nodes on home and
385
+ enterprise, all Manrope Medium 32 (#248). A role set rather than a bare
386
+ size, on the eyebrow's pattern, so the leading and the weight cannot drift
387
+ into a component inline. */
388
+ --text-stat: var(--cg-stat-size);
389
+ --text-stat--line-height: var(--cg-stat-line);
390
+ --text-stat--font-weight: var(--cg-stat-weight);
347
391
 
348
392
  /* ---- weights ----
349
393
  `--font-weight-display` is unreachable and kept only for completeness. Both
@@ -926,7 +926,7 @@
926
926
  }
927
927
  },
928
928
  "h1-display-ramp": {
929
- "$description": "H1 display ramp: uppercase, condensed, Primary or white\n\nStepped down one notch 2026-08-06 (operator: main headings read too\ndominant). The SOURCE values were xl 64 / lg 48 / md 42 / sm 32.",
929
+ "$description": "H1 display ramp: uppercase, condensed, Primary or white\n\nStepped down one notch 2026-08-06 (operator: main headings read too\ndominant). The SOURCE values were xl 64 / lg 48 / md 42 / sm 32.\n\n2026-09-19 (#248): display-64 and display-48 added, named by pixel value — see\ndisplay-64's note. With the eight \"SET FONT TO TANKER\" placeholder nodes\nexcluded, ALL 68 remaining runs at 56 across all six frames are explicit\nleading-none with zero tracking, as are all of 48, 36 and 32. #248's comment\nreports 56 carrying two ratios and two trackings; that was one un-converted Anton\nnode (home 8216:1762) and three of its runs. This does NOT reopen the proposal to\nmove --cg-display-line and --cg-display-tracking: 26 (77 runs) genuinely carries\n1.0, 1.1 and Auto, so no single library default is right for it, and\n--cg-display-line-tight already covers the cases that want 1. Both shipped values\nstay exactly as they are.",
930
930
  "display-3xl": {
931
931
  "$value": "120px",
932
932
  "$type": "dimension",
@@ -947,6 +947,16 @@
947
947
  }
948
948
  }
949
949
  },
950
+ "display-64": {
951
+ "$value": "64px",
952
+ "$type": "dimension",
953
+ "$description": "SOURCE: home 8204:3110 (\"WHERE DOES IT LIVE?\"), thegrid 8364:4363 (\"Say what it\nneeds\"). Two nodes, two frames, Tanker, no tracking. The widest hole in the ramp:\n36px separate --cg-display-xl 56 from --cg-display-2xl 92, and both consumers\nwere clamping their own value into it.\n\nNAMED BY PIXEL VALUE, 2026-09-19 (#248). Every size added in this round falls\nSTRICTLY BETWEEN two shipped rungs, which is the second consecutive census to\nhit that wall — the tanker-title-ramp note records the first. A t-shirt suffix\nencodes ORDER, and order is what an insertion breaks; renaming a shipped rung is\nbreaking for two published consumers, and a third Tanker ramp would carry the\nsame face, leading and roles as the two that exist and therefore no information.\nSo the ramp prefix still means (face, leading, role) and the suffix becomes the\nsize. A px-named rung cannot be made wrong by a later insertion.\n\nSIZE ONLY, AND DELIBERATELY.\n\nA rung gets a --text-* utility only where the frames settled on a leading. Where\nthey did not, it ships as a custom property alone: theme.css's own rule is that\n\"a companion that invents a value is worse than an absent one\", and\ntokens.test.mjs requires every --text-* to carry a --line-height, so a utility\nhere could only exist by inventing one.\n\nThe two nodes do not agree on a leading and only one of them expresses an\nopinion: thegrid sets 70px (ratio 1.094) and home leaves lineHeightUnit at\nINTRINSIC_%, i.e. Figma's Auto, which is Tanker's own 1.35 default and not a\ndesign decision at all. A leading shipped here would be wrong for one of its two\nusers. The same reasoning is why 96px (thegrid 8364:3957, lh 56 — a ratio of\n0.583, 96px type in a 56px line box) is recorded and still has no rung.",
954
+ "$extensions": {
955
+ "io.cloudgrid": {
956
+ "css": "--cg-display-64"
957
+ }
958
+ }
959
+ },
950
960
  "display-xl": {
951
961
  "$value": "56px",
952
962
  "$type": "dimension",
@@ -956,6 +966,16 @@
956
966
  }
957
967
  }
958
968
  },
969
+ "display-48": {
970
+ "$value": "48px",
971
+ "$type": "dimension",
972
+ "$description": "SOURCE: home 8216:1868, the \"Copy this command INTO your aI tool to Plug in\"\nstatement. Tanker, explicit leading-none (lineHeightUnit FONT_SIZE_% at 100), no\ntracking.\n\nONE NODE ON ONE FRAME, stated plainly because the count in #248 reads as four:\nthe node carries four per-character colour runs and a run census counts each of\nthem. One node is not disqualifying here — --cg-display-3xl 120 is also a single\nnode on a single frame and 0.28.0 shipped it — but the evidence is one headline,\nnot four.\n\nNamed by pixel value; --cg-display-64's note has the reasoning (#248).\n\nIt carries display-line-tight and title-tracking rather than --cg-display-line\nand --cg-display-tracking, the same as --cg-display-2xl and --cg-display-3xl:\nthe node is explicitly 1.0 / 0, and the two legacy values were tuned for a\ncondensed face this package no longer ships (#240).",
973
+ "$extensions": {
974
+ "io.cloudgrid": {
975
+ "css": "--cg-display-48"
976
+ }
977
+ }
978
+ },
959
979
  "display-lg": {
960
980
  "$value": "44px",
961
981
  "$type": "dimension",
@@ -1013,7 +1033,7 @@
1013
1033
  }
1014
1034
  },
1015
1035
  "tanker-title-ramp": {
1016
- "$description": "The display face at CARD scale, 2026-09-18.\n\nFour rungs the canonical frames use constantly and this package could spell\nnone of. They are separated from h1-display-ramp rather than wedged into it\nbecause the two ramps disagree everywhere they overlap, and the disagreement is\nnot resolvable by addition:\n\n frames 120 96 92 64 56 40 38 32 30 26 20\n package 56 44 38 30\n\n56, 38 and 30 coincide. 44 is used by nothing in any of the six frames. Every\nother size the frames ask for falls BETWEEN two shipped rungs — 40 between 44\nand 38, 26 between 30 and heading-lg's 22 — so there is no name left on the\nt-shirt ramp that is both free and in the right place, and renaming a shipped\nrung is a breaking change for two published consumers.\n\nThey are also not headings. --cg-heading-* is the Manrope ramp at 1.2 leading;\nthis is the display FACE at leading-none, and --cg-title-sm and\n--cg-heading-md are both 20px precisely because a face and a leading are what\nseparate them, not a size.\n\nSIZES, with where they were read:\n 40 home 8204:2589, enterprise 7038:1244, pricing 7785:9809, thegrid 8364:4382\n 26 teams 7297:8606, builders 6916:8648, enterprise 7038:1192,\n pricing 7788:10264, thegrid 8364:3882\n 20 teams 7172:7760, builders 6919:8783, thegrid petal labels\n\npricing 7785:10059 draws one 32px Tanker number and thegrid draws 15px Tanker\neyebrows. Single-frame, so recorded and not given a rung.",
1036
+ "$description": "The display face at CARD scale, 2026-09-18.\n\nFour rungs the canonical frames use constantly and this package could spell\nnone of. They are separated from h1-display-ramp rather than wedged into it\nbecause the two ramps disagree everywhere they overlap, and the disagreement is\nnot resolvable by addition:\n\n frames 120 96 92 64 56 40 38 32 30 26 20\n package 56 44 38 30\n\n56, 38 and 30 coincide. 44 is used by nothing in any of the six frames. Every\nother size the frames ask for falls BETWEEN two shipped rungs — 40 between 44\nand 38, 26 between 30 and heading-lg's 22 — so there is no name left on the\nt-shirt ramp that is both free and in the right place, and renaming a shipped\nrung is a breaking change for two published consumers.\n\nThey are also not headings. --cg-heading-* is the Manrope ramp at 1.2 leading;\nthis is the display FACE at leading-none, and --cg-title-sm and\n--cg-heading-md are both 20px precisely because a face and a leading are what\nseparate them, not a size.\n\nSIZES, with where they were read:\n 40 home 8204:2589, enterprise 7038:1244, pricing 7785:9809, thegrid 8364:4382\n 26 teams 7297:8606, builders 6916:8648, enterprise 7038:1192,\n pricing 7788:10264, thegrid 8364:3882\n 20 teams 7172:7760, builders 6919:8783, thegrid petal labels\n\npricing 7785:10059 draws one 32px Tanker number and thegrid draws 15px Tanker\neyebrows. Single-frame, so recorded and not given a rung.\n\n---\n\n2026-09-19 (#248). Three more rungs — title-36, title-32, title-24 — and the\nt-shirt suffix stops here. See --cg-display-64's note for why they are named by\npixel value and the group note above for why this ramp is separate from\nh1-display-ramp in the first place.\n\nRECORDED AND DELIBERATELY NOT GIVEN A RUNG:\n\n 28px. #248 proposes it on two home nodes. Both are placeholders the designer\n has not converted: 8204:3066 \"lead - SET FONT TO TANKER\" (lineHeight 92.6%,\n letterSpacing 0.24) and 8216:1784 \"txt:RIGHT HERE - SET FONT TO TANKER\"\n (106%, 0.42). They keep the previous face's metrics — a third node in the same\n set, 8216:1762, is still literally set in Anton — so there is no settled 28px\n anything in the six frames. Six home nodes and two thegrid nodes carry that\n \"SET FONT TO TANKER\" marker; every Tanker node in the frames that emits a\n letter-spacing is one of them.\n\n 32px on Inter (builders 7172:8458, the \"$ grid plug\" pill). Off-system in the\n design itself; #243 recorded it.\n\nON AUTO LEADING. Figma's lineHeightUnit distinguishes a decision from an\nabsence: FONT_SIZE_% and PIXELS mean the designer set a leading, INTRINSIC_%\nmeans Auto and the number in the file is the FACE's default, which for Tanker is\n1.35. Every 1.35 in a Tanker census is INTRINSIC_%. A rung built on one would\nencode Tanker's metrics as a brand decision.",
1017
1037
  "title-lg": {
1018
1038
  "$value": "40px",
1019
1039
  "$type": "dimension",
@@ -1024,6 +1044,26 @@
1024
1044
  }
1025
1045
  }
1026
1046
  },
1047
+ "title-36": {
1048
+ "$value": "36px",
1049
+ "$type": "dimension",
1050
+ "$description": "SOURCE: enterprise 7039:5124, 7039:5119, 7039:5129 — the three audience column\nheadings (\"Platform and security teams\", \"Innovation Leaders\", \"Business\nfunctions\"). Three nodes, one frame, all explicit leading-none, no tracking.\n\nOne frame, and 2px from --cg-display-md 38. It is a rung rather than an\napproximation of 38 because it is the card/column role, not the section-heading\nrole, and because rounding a measured 36 onto 38 is the kind of \"close enough\"\nthat #243 had to unpick when ui#239's table called this ramp's 26 a \"24px card\ntitle\".\n\nNamed by pixel value; --cg-display-64's note has the reasoning (#248).",
1051
+ "$extensions": {
1052
+ "io.cloudgrid": {
1053
+ "css": "--cg-title-36"
1054
+ }
1055
+ }
1056
+ },
1057
+ "title-32": {
1058
+ "$value": "32px",
1059
+ "$type": "dimension",
1060
+ "$description": "SOURCE: home 8204:2590 (\"Learn how the grid can help you\"), pricing 7785:10059 /\n10068 / 10073 / 10076 / 10081 / 10084 (the six plan prices). Seven nodes, two\nframes, all explicit leading-none, no tracking.\n\nTHE DISPLAY FACE HALF OF 32. The frames also draw 32 on the UI face, ten times,\nand that is --cg-stat-size on the Manrope ramp, not this. Two rungs at one size\nis the ramp rule working as intended, exactly as --cg-title-sm and\n--cg-heading-md are both 20px: a rung is a face, a leading and a role, not a\nnumber.\n\n#243 recorded pricing's 32 as \"one 32px Tanker number, single-frame, so recorded\nand not given a rung\". The rebuilt extract finds seven nodes on two frames.\n\nNamed by pixel value; --cg-display-64's note has the reasoning (#248).",
1061
+ "$extensions": {
1062
+ "io.cloudgrid": {
1063
+ "css": "--cg-title-32"
1064
+ }
1065
+ }
1066
+ },
1027
1067
  "title-md": {
1028
1068
  "$value": "26px",
1029
1069
  "$type": "dimension",
@@ -1034,6 +1074,16 @@
1034
1074
  }
1035
1075
  }
1036
1076
  },
1077
+ "title-24": {
1078
+ "$value": "24px",
1079
+ "$type": "dimension",
1080
+ "$description": "SOURCE: home 8204:2813, 8204:2753 / 2758 / 2763; enterprise 7039:5084, 7039:5088,\n7039:5093, 7039:5097, 7395:666. Twelve nodes, two frames. Nine set an explicit\nleading-none; two leave lineHeightUnit at INTRINSIC_% (Figma's Auto, no\ndecision); one, home 8216:1908, sets 30px. No node carries tracking.\n\nTWO PIXELS FROM --cg-title-md 26, AND BOTH ARE REAL. 26 is 77 runs on six frames\nand 24 is 12 on two; they coexist in the same design and in some cases in the\nsame section. Note that #239's type table called the 26 rung \"24px card title\"\nfrom a consumer's approximation — this rung is the actual 24, measured, and does\nnot retroactively make that table right.\n\nNamed by pixel value; --cg-display-64's note has the reasoning (#248).",
1081
+ "$extensions": {
1082
+ "io.cloudgrid": {
1083
+ "css": "--cg-title-24"
1084
+ }
1085
+ }
1086
+ },
1037
1087
  "title-sm": {
1038
1088
  "$value": "20px",
1039
1089
  "$type": "dimension",
@@ -1124,6 +1174,16 @@
1124
1174
  }
1125
1175
  }
1126
1176
  },
1177
+ "body-18": {
1178
+ "$value": "18px",
1179
+ "$type": "dimension",
1180
+ "$description": "SOURCE: pricing 7785:9832 / 9858 / 9872, the three plan names — Space Grotesk\nBold 18 at line-height 1.46, which is --cg-body-line exactly. Also thegrid\n8364:4352 / 4355 / 4358 and 8364:4385, Manrope 18 at 1.5.\n\nA BODY RUNG, NOT A HEADING ONE. #248 asks which ramp 18 belongs on, since it\nsits between --cg-heading-sm 17 and --cg-heading-md 20. Every node at 18 that\nexpresses a leading at all reads 1.46 or 1.5; nothing is near\n--cg-heading-line's 1.2. home 8204:3090 and 8204:3105 draw Tanker at 18 and both\nare INTRINSIC_% (Auto), so they express no opinion and do not make this a\ndisplay rung either.\n\n#243 recorded pricing's 18 as \"one 18px bold plan name. Single node, one frame,\n1px off --cg-body-lg\". It is three nodes, and the 1px is the point: a consumer\notherwise rounds it onto --cg-body-lg 19 and the plan-name row drifts by a pixel\nagainst the Figma it is being checked against.\n\nNamed by pixel value; --cg-display-64's note has the reasoning (#248).",
1181
+ "$extensions": {
1182
+ "io.cloudgrid": {
1183
+ "css": "--cg-body-18"
1184
+ }
1185
+ }
1186
+ },
1127
1187
  "body-md": {
1128
1188
  "$value": "16px",
1129
1189
  "$type": "dimension",
@@ -1144,6 +1204,16 @@
1144
1204
  }
1145
1205
  }
1146
1206
  },
1207
+ "body-14": {
1208
+ "$value": "14px",
1209
+ "$type": "dimension",
1210
+ "$description": "SOURCE: home 8216:1768, 8216:1769, 8216:1771 — the three lines of the prompt-and-\nreply mock. Manrope Regular 14. Three runs, one frame.\n\nREAD THIS BEFORE TREATING 14 AS A GAP. #248 counts nineteen 14px runs on four\nframes and calls it one of its two strongest cases. SIXTEEN OF THE NINETEEN ARE\nTHE EYEBROW — Manrope 600, leading 1.2, tracking 1.4px, uppercase — which 0.28.0\nalready ships whole as --cg-eyebrow-size / -line / -tracking / -weight, and\nthree of the nodes the issue cites (teams 7172:8463, builders 6916:8053,\nenterprise 7038:1184) are the very nodes in that token's own docblock. 14 was\nalready closed; the four frames are the eyebrow's four frames.\n\nSo this rung exists for the residue, and the residue is thin. It ships anyway\nbecause without it the only 14 in the package is --cg-eyebrow-size, and a\nconsumer who wants plain 14px text will reach for it and sooner or later reach\nfor text-eyebrow with it, inheriting a weight and a tracking nobody asked for.\n--cg-mono-size is also 14 and is the mono face.\n\nSIZE ONLY.\n\nA rung gets a --text-* utility only where the frames settled on a leading. Where\nthey did not, it ships as a custom property alone: theme.css's own rule is that\n\"a companion that invents a value is worse than an absent one\", and\ntokens.test.mjs requires every --text-* to carry a --line-height, so a utility\nhere could only exist by inventing one. The three runs read 1.295 and 1.55; there is nothing\nto ship.",
1211
+ "$extensions": {
1212
+ "io.cloudgrid": {
1213
+ "css": "--cg-body-14"
1214
+ }
1215
+ }
1216
+ },
1147
1217
  "caption": {
1148
1218
  "$value": "13px",
1149
1219
  "$type": "dimension",
@@ -1244,6 +1314,35 @@
1244
1314
  "css": "--cg-eyebrow-weight"
1245
1315
  }
1246
1316
  }
1317
+ },
1318
+ "stat-size": {
1319
+ "$value": "32px",
1320
+ "$type": "dimension",
1321
+ "$description": "The big figure in a stat block: the UI face at leading-none.\n\nSOURCE: home 8204:2769 / 2774 / 2779 / 2784 / 2789 and enterprise 7038:4564 /\n4569 / 4574 / 4579 / 4584 — \"2\", \"184\", \"4\", \"8\", \"$0\". Ten nodes, two frames,\nManrope Medium 32, every one of them explicit leading-none with no tracking.\n\nTHE UI-FACE HALF OF 32; --cg-title-32 is the display-face half. This is a role\nthe Manrope ramp could not spell at all: --cg-heading-* is 1.2 and --cg-body-* is\n1.46, and nothing on this face was at 1.\n\nIt is a role set rather than a bare size, on the --cg-eyebrow-* pattern, for the\nreason that block gives: the figure carries a leading and a weight as well as a\nsize, and spelling two of the three inline is how a component drifts.\n\nKNOWN DIVERGENCE, not fixed here: StatTile renders its value as\n`font-display text-display-sm` — Tanker 30 at --cg-display-line. The frames say\nManrope Medium 32 at leading-none. Repointing it changes what every StatTile\nrenders today, so it is a separate change with its own changeset.\n\nThe same five figures appear again at 18.149px inside scaled product mocks on\nhome, thegrid and enterprise. That is the per-mock scale factor #248 documents,\nnot a second rung.",
1322
+ "$extensions": {
1323
+ "io.cloudgrid": {
1324
+ "css": "--cg-stat-size"
1325
+ }
1326
+ }
1327
+ },
1328
+ "stat-line": {
1329
+ "$value": "1",
1330
+ "$type": "number",
1331
+ "$description": "leading-none, from all ten nodes. Spelled as a number rather than aliased onto\n--cg-display-line-tight: that alias exists so a retune of the DISPLAY leading\nreaches the title ramp too, and the stat figure is on the UI face and must not\nmove with it.",
1332
+ "$extensions": {
1333
+ "io.cloudgrid": {
1334
+ "css": "--cg-stat-line"
1335
+ }
1336
+ }
1337
+ },
1338
+ "stat-weight": {
1339
+ "$value": "{weights.weight-medium}",
1340
+ "$description": "500, from all ten nodes. Manrope Medium, not the eyebrow's 600 and not\n--cg-label-weight's 500 by coincidence — aliased onto the weight rung so it\ntracks the brand's Medium.",
1341
+ "$extensions": {
1342
+ "io.cloudgrid": {
1343
+ "css": "--cg-stat-weight"
1344
+ }
1345
+ }
1247
1346
  }
1248
1347
  },
1249
1348
  "spacing": {
package/dist/tokens.css CHANGED
@@ -889,7 +889,18 @@
889
889
  H1 display ramp: uppercase, condensed, Primary or white
890
890
 
891
891
  Stepped down one notch 2026-08-06 (operator: main headings read too
892
- dominant). The SOURCE values were xl 64 / lg 48 / md 42 / sm 32. */
892
+ dominant). The SOURCE values were xl 64 / lg 48 / md 42 / sm 32.
893
+
894
+ 2026-09-19 (#248): display-64 and display-48 added, named by pixel value — see
895
+ display-64's note. With the eight "SET FONT TO TANKER" placeholder nodes
896
+ excluded, ALL 68 remaining runs at 56 across all six frames are explicit
897
+ leading-none with zero tracking, as are all of 48, 36 and 32. #248's comment
898
+ reports 56 carrying two ratios and two trackings; that was one un-converted Anton
899
+ node (home 8216:1762) and three of its runs. This does NOT reopen the proposal to
900
+ move --cg-display-line and --cg-display-tracking: 26 (77 runs) genuinely carries
901
+ 1.0, 1.1 and Auto, so no single library default is right for it, and
902
+ --cg-display-line-tight already covers the cases that want 1. Both shipped values
903
+ stay exactly as they are. */
893
904
  /*
894
905
  SOURCE: home 8204:2556, the hero headline, Tanker at leading-none.
895
906
 
@@ -909,7 +920,55 @@
909
920
  not rungs. A value invented to complete a grid is the thing this file keeps
910
921
  refusing to add. */
911
922
  --cg-display-2xl: 92px;
923
+ /*
924
+ SOURCE: home 8204:3110 ("WHERE DOES IT LIVE?"), thegrid 8364:4363 ("Say what it
925
+ needs"). Two nodes, two frames, Tanker, no tracking. The widest hole in the ramp:
926
+ 36px separate --cg-display-xl 56 from --cg-display-2xl 92, and both consumers
927
+ were clamping their own value into it.
928
+
929
+ NAMED BY PIXEL VALUE, 2026-09-19 (#248). Every size added in this round falls
930
+ STRICTLY BETWEEN two shipped rungs, which is the second consecutive census to
931
+ hit that wall — the tanker-title-ramp note records the first. A t-shirt suffix
932
+ encodes ORDER, and order is what an insertion breaks; renaming a shipped rung is
933
+ breaking for two published consumers, and a third Tanker ramp would carry the
934
+ same face, leading and roles as the two that exist and therefore no information.
935
+ So the ramp prefix still means (face, leading, role) and the suffix becomes the
936
+ size. A px-named rung cannot be made wrong by a later insertion.
937
+
938
+ SIZE ONLY, AND DELIBERATELY.
939
+
940
+ A rung gets a --text-* utility only where the frames settled on a leading. Where
941
+ they did not, it ships as a custom property alone: theme.css's own rule is that
942
+ "a companion that invents a value is worse than an absent one", and
943
+ tokens.test.mjs requires every --text-* to carry a --line-height, so a utility
944
+ here could only exist by inventing one.
945
+
946
+ The two nodes do not agree on a leading and only one of them expresses an
947
+ opinion: thegrid sets 70px (ratio 1.094) and home leaves lineHeightUnit at
948
+ INTRINSIC_%, i.e. Figma's Auto, which is Tanker's own 1.35 default and not a
949
+ design decision at all. A leading shipped here would be wrong for one of its two
950
+ users. The same reasoning is why 96px (thegrid 8364:3957, lh 56 — a ratio of
951
+ 0.583, 96px type in a 56px line box) is recorded and still has no rung. */
952
+ --cg-display-64: 64px;
912
953
  --cg-display-xl: 56px;
954
+ /*
955
+ SOURCE: home 8216:1868, the "Copy this command INTO your aI tool to Plug in"
956
+ statement. Tanker, explicit leading-none (lineHeightUnit FONT_SIZE_% at 100), no
957
+ tracking.
958
+
959
+ ONE NODE ON ONE FRAME, stated plainly because the count in #248 reads as four:
960
+ the node carries four per-character colour runs and a run census counts each of
961
+ them. One node is not disqualifying here — --cg-display-3xl 120 is also a single
962
+ node on a single frame and 0.28.0 shipped it — but the evidence is one headline,
963
+ not four.
964
+
965
+ Named by pixel value; --cg-display-64's note has the reasoning (#248).
966
+
967
+ It carries display-line-tight and title-tracking rather than --cg-display-line
968
+ and --cg-display-tracking, the same as --cg-display-2xl and --cg-display-3xl:
969
+ the node is explicitly 1.0 / 0, and the two legacy values were tuned for a
970
+ condensed face this package no longer ships (#240). */
971
+ --cg-display-48: 48px;
913
972
  --cg-display-lg: 44px;
914
973
  /* SOURCE: onboarding H1 (was 42px) */
915
974
  --cg-display-md: 38px;
@@ -957,15 +1016,85 @@
957
1016
  20 teams 7172:7760, builders 6919:8783, thegrid petal labels
958
1017
 
959
1018
  pricing 7785:10059 draws one 32px Tanker number and thegrid draws 15px Tanker
960
- eyebrows. Single-frame, so recorded and not given a rung. */
1019
+ eyebrows. Single-frame, so recorded and not given a rung.
1020
+
1021
+ ---
1022
+
1023
+ 2026-09-19 (#248). Three more rungs — title-36, title-32, title-24 — and the
1024
+ t-shirt suffix stops here. See --cg-display-64's note for why they are named by
1025
+ pixel value and the group note above for why this ramp is separate from
1026
+ h1-display-ramp in the first place.
1027
+
1028
+ RECORDED AND DELIBERATELY NOT GIVEN A RUNG:
1029
+
1030
+ 28px. #248 proposes it on two home nodes. Both are placeholders the designer
1031
+ has not converted: 8204:3066 "lead - SET FONT TO TANKER" (lineHeight 92.6%,
1032
+ letterSpacing 0.24) and 8216:1784 "txt:RIGHT HERE - SET FONT TO TANKER"
1033
+ (106%, 0.42). They keep the previous face's metrics — a third node in the same
1034
+ set, 8216:1762, is still literally set in Anton — so there is no settled 28px
1035
+ anything in the six frames. Six home nodes and two thegrid nodes carry that
1036
+ "SET FONT TO TANKER" marker; every Tanker node in the frames that emits a
1037
+ letter-spacing is one of them.
1038
+
1039
+ 32px on Inter (builders 7172:8458, the "$ grid plug" pill). Off-system in the
1040
+ design itself; #243 recorded it.
1041
+
1042
+ ON AUTO LEADING. Figma's lineHeightUnit distinguishes a decision from an
1043
+ absence: FONT_SIZE_% and PIXELS mean the designer set a leading, INTRINSIC_%
1044
+ means Auto and the number in the file is the FACE's default, which for Tanker is
1045
+ 1.35. Every 1.35 in a Tanker census is INTRINSIC_%. A rung built on one would
1046
+ encode Tanker's metrics as a brand decision. */
961
1047
  /*
962
1048
  Four frames. The governance card title, the plan price, the sub-heading under a section statement. */
963
1049
  --cg-title-lg: 40px;
1050
+ /*
1051
+ SOURCE: enterprise 7039:5124, 7039:5119, 7039:5129 — the three audience column
1052
+ headings ("Platform and security teams", "Innovation Leaders", "Business
1053
+ functions"). Three nodes, one frame, all explicit leading-none, no tracking.
1054
+
1055
+ One frame, and 2px from --cg-display-md 38. It is a rung rather than an
1056
+ approximation of 38 because it is the card/column role, not the section-heading
1057
+ role, and because rounding a measured 36 onto 38 is the kind of "close enough"
1058
+ that #243 had to unpick when ui#239's table called this ramp's 26 a "24px card
1059
+ title".
1060
+
1061
+ Named by pixel value; --cg-display-64's note has the reasoning (#248). */
1062
+ --cg-title-36: 36px;
1063
+ /*
1064
+ SOURCE: home 8204:2590 ("Learn how the grid can help you"), pricing 7785:10059 /
1065
+ 10068 / 10073 / 10076 / 10081 / 10084 (the six plan prices). Seven nodes, two
1066
+ frames, all explicit leading-none, no tracking.
1067
+
1068
+ THE DISPLAY FACE HALF OF 32. The frames also draw 32 on the UI face, ten times,
1069
+ and that is --cg-stat-size on the Manrope ramp, not this. Two rungs at one size
1070
+ is the ramp rule working as intended, exactly as --cg-title-sm and
1071
+ --cg-heading-md are both 20px: a rung is a face, a leading and a role, not a
1072
+ number.
1073
+
1074
+ #243 recorded pricing's 32 as "one 32px Tanker number, single-frame, so recorded
1075
+ and not given a rung". The rebuilt extract finds seven nodes on two frames.
1076
+
1077
+ Named by pixel value; --cg-display-64's note has the reasoning (#248). */
1078
+ --cg-title-32: 32px;
964
1079
  /*
965
1080
  Five frames. The card title and the FAQ question — the most common display size in the design after the section heading.
966
1081
 
967
1082
  ui#239's type table calls this rung "24px card title". Measured on five frames it is 26, not 24, and nothing in any frame is 24. The table was written from a consumer's approximation rather than from the node. */
968
1083
  --cg-title-md: 26px;
1084
+ /*
1085
+ SOURCE: home 8204:2813, 8204:2753 / 2758 / 2763; enterprise 7039:5084, 7039:5088,
1086
+ 7039:5093, 7039:5097, 7395:666. Twelve nodes, two frames. Nine set an explicit
1087
+ leading-none; two leave lineHeightUnit at INTRINSIC_% (Figma's Auto, no
1088
+ decision); one, home 8216:1908, sets 30px. No node carries tracking.
1089
+
1090
+ TWO PIXELS FROM --cg-title-md 26, AND BOTH ARE REAL. 26 is 77 runs on six frames
1091
+ and 24 is 12 on two; they coexist in the same design and in some cases in the
1092
+ same section. Note that #239's type table called the 26 rung "24px card title"
1093
+ from a consumer's approximation — this rung is the actual 24, measured, and does
1094
+ not retroactively make that table right.
1095
+
1096
+ Named by pixel value; --cg-display-64's note has the reasoning (#248). */
1097
+ --cg-title-24: 24px;
969
1098
  /*
970
1099
  Three frames. The label inside a step circle or a diagram petal. Same size as --cg-heading-md and a different face and leading; see the group note. */
971
1100
  --cg-title-sm: 20px;
@@ -1014,10 +1143,56 @@
1014
1143
  --cg-body-xl: 20px;
1015
1144
  /* SOURCE: H1 subline */
1016
1145
  --cg-body-lg: 19px;
1146
+ /*
1147
+ SOURCE: pricing 7785:9832 / 9858 / 9872, the three plan names — Space Grotesk
1148
+ Bold 18 at line-height 1.46, which is --cg-body-line exactly. Also thegrid
1149
+ 8364:4352 / 4355 / 4358 and 8364:4385, Manrope 18 at 1.5.
1150
+
1151
+ A BODY RUNG, NOT A HEADING ONE. #248 asks which ramp 18 belongs on, since it
1152
+ sits between --cg-heading-sm 17 and --cg-heading-md 20. Every node at 18 that
1153
+ expresses a leading at all reads 1.46 or 1.5; nothing is near
1154
+ --cg-heading-line's 1.2. home 8204:3090 and 8204:3105 draw Tanker at 18 and both
1155
+ are INTRINSIC_% (Auto), so they express no opinion and do not make this a
1156
+ display rung either.
1157
+
1158
+ #243 recorded pricing's 18 as "one 18px bold plan name. Single node, one frame,
1159
+ 1px off --cg-body-lg". It is three nodes, and the 1px is the point: a consumer
1160
+ otherwise rounds it onto --cg-body-lg 19 and the plan-name row drifts by a pixel
1161
+ against the Figma it is being checked against.
1162
+
1163
+ Named by pixel value; --cg-display-64's note has the reasoning (#248). */
1164
+ --cg-body-18: 18px;
1017
1165
  /* SOURCE: default UI text */
1018
1166
  --cg-body-md: 16px;
1019
1167
  /* SOURCE: card body, legal */
1020
1168
  --cg-body-sm: 15px;
1169
+ /*
1170
+ SOURCE: home 8216:1768, 8216:1769, 8216:1771 — the three lines of the prompt-and-
1171
+ reply mock. Manrope Regular 14. Three runs, one frame.
1172
+
1173
+ READ THIS BEFORE TREATING 14 AS A GAP. #248 counts nineteen 14px runs on four
1174
+ frames and calls it one of its two strongest cases. SIXTEEN OF THE NINETEEN ARE
1175
+ THE EYEBROW — Manrope 600, leading 1.2, tracking 1.4px, uppercase — which 0.28.0
1176
+ already ships whole as --cg-eyebrow-size / -line / -tracking / -weight, and
1177
+ three of the nodes the issue cites (teams 7172:8463, builders 6916:8053,
1178
+ enterprise 7038:1184) are the very nodes in that token's own docblock. 14 was
1179
+ already closed; the four frames are the eyebrow's four frames.
1180
+
1181
+ So this rung exists for the residue, and the residue is thin. It ships anyway
1182
+ because without it the only 14 in the package is --cg-eyebrow-size, and a
1183
+ consumer who wants plain 14px text will reach for it and sooner or later reach
1184
+ for text-eyebrow with it, inheriting a weight and a tracking nobody asked for.
1185
+ --cg-mono-size is also 14 and is the mono face.
1186
+
1187
+ SIZE ONLY.
1188
+
1189
+ A rung gets a --text-* utility only where the frames settled on a leading. Where
1190
+ they did not, it ships as a custom property alone: theme.css's own rule is that
1191
+ "a companion that invents a value is worse than an absent one", and
1192
+ tokens.test.mjs requires every --text-* to carry a --line-height, so a utility
1193
+ here could only exist by inventing one. The three runs read 1.295 and 1.55; there is nothing
1194
+ to ship. */
1195
+ --cg-body-14: 14px;
1021
1196
  /* SOURCE: footer, helper */
1022
1197
  --cg-caption: 13px;
1023
1198
  /*
@@ -1069,6 +1244,41 @@
1069
1244
  Note this is NOT --cg-label-weight, which is 500. The eyebrow and the label are
1070
1245
  different rungs; see the group note. */
1071
1246
  --cg-eyebrow-weight: var(--cg-weight-bold);
1247
+ /*
1248
+ The big figure in a stat block: the UI face at leading-none.
1249
+
1250
+ SOURCE: home 8204:2769 / 2774 / 2779 / 2784 / 2789 and enterprise 7038:4564 /
1251
+ 4569 / 4574 / 4579 / 4584 — "2", "184", "4", "8", "$0". Ten nodes, two frames,
1252
+ Manrope Medium 32, every one of them explicit leading-none with no tracking.
1253
+
1254
+ THE UI-FACE HALF OF 32; --cg-title-32 is the display-face half. This is a role
1255
+ the Manrope ramp could not spell at all: --cg-heading-* is 1.2 and --cg-body-* is
1256
+ 1.46, and nothing on this face was at 1.
1257
+
1258
+ It is a role set rather than a bare size, on the --cg-eyebrow-* pattern, for the
1259
+ reason that block gives: the figure carries a leading and a weight as well as a
1260
+ size, and spelling two of the three inline is how a component drifts.
1261
+
1262
+ KNOWN DIVERGENCE, not fixed here: StatTile renders its value as
1263
+ `font-display text-display-sm` — Tanker 30 at --cg-display-line. The frames say
1264
+ Manrope Medium 32 at leading-none. Repointing it changes what every StatTile
1265
+ renders today, so it is a separate change with its own changeset.
1266
+
1267
+ The same five figures appear again at 18.149px inside scaled product mocks on
1268
+ home, thegrid and enterprise. That is the per-mock scale factor #248 documents,
1269
+ not a second rung. */
1270
+ --cg-stat-size: 32px;
1271
+ /*
1272
+ leading-none, from all ten nodes. Spelled as a number rather than aliased onto
1273
+ --cg-display-line-tight: that alias exists so a retune of the DISPLAY leading
1274
+ reaches the title ramp too, and the stat figure is on the UI face and must not
1275
+ move with it. */
1276
+ --cg-stat-line: 1;
1277
+ /*
1278
+ 500, from all ten nodes. Manrope Medium, not the eyebrow's 600 and not
1279
+ --cg-label-weight's 500 by coincidence — aliased onto the weight rung so it
1280
+ tracks the brand's Medium. */
1281
+ --cg-stat-weight: var(--cg-weight-medium);
1072
1282
 
1073
1283
  /* ---- spacing ----
1074
1284
  spacing: 4px base */
package/dist/tokens.json CHANGED
@@ -101,7 +101,9 @@
101
101
  "--cg-weight-display": "700",
102
102
  "--cg-display-3xl": "120px",
103
103
  "--cg-display-2xl": "92px",
104
+ "--cg-display-64": "64px",
104
105
  "--cg-display-xl": "56px",
106
+ "--cg-display-48": "48px",
105
107
  "--cg-display-lg": "44px",
106
108
  "--cg-display-md": "38px",
107
109
  "--cg-display-sm": "30px",
@@ -109,7 +111,10 @@
109
111
  "--cg-display-tracking": "0.012em",
110
112
  "--cg-display-line-tight": "1",
111
113
  "--cg-title-lg": "40px",
114
+ "--cg-title-36": "36px",
115
+ "--cg-title-32": "32px",
112
116
  "--cg-title-md": "26px",
117
+ "--cg-title-24": "24px",
113
118
  "--cg-title-sm": "20px",
114
119
  "--cg-title-line": "var(--cg-display-line-tight)",
115
120
  "--cg-title-tracking": "0em",
@@ -119,8 +124,10 @@
119
124
  "--cg-heading-line": "1.2",
120
125
  "--cg-body-xl": "20px",
121
126
  "--cg-body-lg": "19px",
127
+ "--cg-body-18": "18px",
122
128
  "--cg-body-md": "16px",
123
129
  "--cg-body-sm": "15px",
130
+ "--cg-body-14": "14px",
124
131
  "--cg-caption": "13px",
125
132
  "--cg-body-line": "1.46",
126
133
  "--cg-body-tracking": "0em",
@@ -132,6 +139,9 @@
132
139
  "--cg-eyebrow-line": "1.2",
133
140
  "--cg-eyebrow-tracking": "0.1em",
134
141
  "--cg-eyebrow-weight": "var(--cg-weight-bold)",
142
+ "--cg-stat-size": "32px",
143
+ "--cg-stat-line": "1",
144
+ "--cg-stat-weight": "var(--cg-weight-medium)",
135
145
  "--cg-space-1": "4px",
136
146
  "--cg-space-2": "8px",
137
147
  "--cg-space-3": "12px",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudgrid-io/ui",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "CloudGrid design system: React components, brand design tokens, Tailwind theme, self-hosted fonts.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",
@@ -21,6 +21,7 @@
21
21
  "default": "./dist/index.js"
22
22
  },
23
23
  "./tokens.css": "./dist/tokens.css",
24
+ "./console.css": "./dist/console.css",
24
25
  "./theme.css": "./dist/theme.css",
25
26
  "./shadcn.css": "./dist/shadcn.css",
26
27
  "./fonts.css": "./dist/fonts.css",