@gradeui/ui 3.1.0 → 3.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradeui/ui",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "Grade Design System — React components, theme engine, and design tokens",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -146,6 +146,7 @@
146
146
  "@radix-ui/react-toggle": "^1.1.10",
147
147
  "@radix-ui/react-toggle-group": "^1.1.11",
148
148
  "@radix-ui/react-tooltip": "^1.2.8",
149
+ "@tanstack/react-table": "^8.20.0",
149
150
  "class-variance-authority": "^0.7.1",
150
151
  "clsx": "^2.1.1",
151
152
  "cmdk": "^1.1.1",
@@ -48,102 +48,108 @@
48
48
  @theme {
49
49
  --text-2xs: 0.6875rem;
50
50
  --text-2xs--line-height: 1rem;
51
+ /* Accent font role — gives a `font-accent` utility that references the
52
+ runtime --font-accent var (set by the theme generator / apply.ts).
53
+ Plain @theme (not inline) so the utility stays var-referencing and
54
+ re-skins live; falls back to the display/sans stack until a theme
55
+ sets one. */
56
+ --font-accent: var(--font-display, var(--font-sans));
51
57
  }
52
58
 
53
59
  @theme inline reference {
54
60
  /* -- Brand color ramps — values live in @gradeui/core as --gds-* vars. -- */
55
- --color-rds-green: var(--gds-green);
56
- --color-rds-green-50: var(--gds-green-50);
57
- --color-rds-green-100: var(--gds-green-100);
58
- --color-rds-green-200: var(--gds-green-200);
59
- --color-rds-green-300: var(--gds-green-300);
60
- --color-rds-green-400: var(--gds-green-400);
61
- --color-rds-green-500: var(--gds-green-500);
62
- --color-rds-green-600: var(--gds-green-600);
63
- --color-rds-green-700: var(--gds-green-700);
64
- --color-rds-green-800: var(--gds-green-800);
65
- --color-rds-green-900: var(--gds-green-900);
66
- --color-rds-green-950: var(--gds-green-950);
67
- --color-rds-yellow: var(--gds-yellow);
68
- --color-rds-yellow-50: var(--gds-yellow-50);
69
- --color-rds-yellow-100: var(--gds-yellow-100);
70
- --color-rds-yellow-200: var(--gds-yellow-200);
71
- --color-rds-yellow-300: var(--gds-yellow-300);
72
- --color-rds-yellow-400: var(--gds-yellow-400);
73
- --color-rds-yellow-500: var(--gds-yellow-500);
74
- --color-rds-yellow-600: var(--gds-yellow-600);
75
- --color-rds-yellow-700: var(--gds-yellow-700);
76
- --color-rds-yellow-800: var(--gds-yellow-800);
77
- --color-rds-yellow-900: var(--gds-yellow-900);
78
- --color-rds-orange: var(--gds-orange);
79
- --color-rds-orange-50: var(--gds-orange-50);
80
- --color-rds-orange-100: var(--gds-orange-100);
81
- --color-rds-orange-200: var(--gds-orange-200);
82
- --color-rds-orange-300: var(--gds-orange-300);
83
- --color-rds-orange-400: var(--gds-orange-400);
84
- --color-rds-orange-500: var(--gds-orange-500);
85
- --color-rds-orange-600: var(--gds-orange-600);
86
- --color-rds-orange-700: var(--gds-orange-700);
87
- --color-rds-orange-800: var(--gds-orange-800);
88
- --color-rds-orange-900: var(--gds-orange-900);
89
- --color-rds-red: var(--gds-red);
90
- --color-rds-red-50: var(--gds-red-50);
91
- --color-rds-red-100: var(--gds-red-100);
92
- --color-rds-red-200: var(--gds-red-200);
93
- --color-rds-red-300: var(--gds-red-300);
94
- --color-rds-red-400: var(--gds-red-400);
95
- --color-rds-red-500: var(--gds-red-500);
96
- --color-rds-red-600: var(--gds-red-600);
97
- --color-rds-red-700: var(--gds-red-700);
98
- --color-rds-red-800: var(--gds-red-800);
99
- --color-rds-red-900: var(--gds-red-900);
100
- --color-rds-teal: var(--gds-teal);
101
- --color-rds-teal-50: var(--gds-teal-50);
102
- --color-rds-teal-100: var(--gds-teal-100);
103
- --color-rds-teal-200: var(--gds-teal-200);
104
- --color-rds-teal-300: var(--gds-teal-300);
105
- --color-rds-teal-400: var(--gds-teal-400);
106
- --color-rds-teal-500: var(--gds-teal-500);
107
- --color-rds-teal-600: var(--gds-teal-600);
108
- --color-rds-teal-700: var(--gds-teal-700);
109
- --color-rds-teal-800: var(--gds-teal-800);
110
- --color-rds-teal-900: var(--gds-teal-900);
111
- --color-rds-teal-950: var(--gds-teal-950);
112
- --color-rds-navy: var(--gds-navy);
113
- --color-rds-navy-50: var(--gds-navy-50);
114
- --color-rds-navy-100: var(--gds-navy-100);
115
- --color-rds-navy-200: var(--gds-navy-200);
116
- --color-rds-navy-300: var(--gds-navy-300);
117
- --color-rds-navy-400: var(--gds-navy-400);
118
- --color-rds-navy-500: var(--gds-navy-500);
119
- --color-rds-navy-600: var(--gds-navy-600);
120
- --color-rds-navy-700: var(--gds-navy-700);
121
- --color-rds-navy-800: var(--gds-navy-800);
122
- --color-rds-navy-900: var(--gds-navy-900);
123
- --color-rds-blue: var(--gds-blue);
124
- --color-rds-blue-50: var(--gds-blue-50);
125
- --color-rds-blue-100: var(--gds-blue-100);
126
- --color-rds-blue-200: var(--gds-blue-200);
127
- --color-rds-blue-300: var(--gds-blue-300);
128
- --color-rds-blue-400: var(--gds-blue-400);
129
- --color-rds-blue-500: var(--gds-blue-500);
130
- --color-rds-blue-600: var(--gds-blue-600);
131
- --color-rds-blue-700: var(--gds-blue-700);
132
- --color-rds-blue-800: var(--gds-blue-800);
133
- --color-rds-blue-900: var(--gds-blue-900);
134
- --color-rds-gray-50: var(--gds-gray-50);
135
- --color-rds-gray-100: var(--gds-gray-100);
136
- --color-rds-gray-200: var(--gds-gray-200);
137
- --color-rds-gray-300: var(--gds-gray-300);
138
- --color-rds-gray-400: var(--gds-gray-400);
139
- --color-rds-gray-500: var(--gds-gray-500);
140
- --color-rds-gray-600: var(--gds-gray-600);
141
- --color-rds-gray-700: var(--gds-gray-700);
142
- --color-rds-gray-800: var(--gds-gray-800);
143
- --color-rds-gray-900: var(--gds-gray-900);
144
- --color-rds-gray-950: var(--gds-gray-950);
145
- --color-rds-black: var(--gds-black);
146
- --color-rds-white: var(--gds-white);
61
+ --color-gds-green: var(--gds-green);
62
+ --color-gds-green-50: var(--gds-green-50);
63
+ --color-gds-green-100: var(--gds-green-100);
64
+ --color-gds-green-200: var(--gds-green-200);
65
+ --color-gds-green-300: var(--gds-green-300);
66
+ --color-gds-green-400: var(--gds-green-400);
67
+ --color-gds-green-500: var(--gds-green-500);
68
+ --color-gds-green-600: var(--gds-green-600);
69
+ --color-gds-green-700: var(--gds-green-700);
70
+ --color-gds-green-800: var(--gds-green-800);
71
+ --color-gds-green-900: var(--gds-green-900);
72
+ --color-gds-green-950: var(--gds-green-950);
73
+ --color-gds-yellow: var(--gds-yellow);
74
+ --color-gds-yellow-50: var(--gds-yellow-50);
75
+ --color-gds-yellow-100: var(--gds-yellow-100);
76
+ --color-gds-yellow-200: var(--gds-yellow-200);
77
+ --color-gds-yellow-300: var(--gds-yellow-300);
78
+ --color-gds-yellow-400: var(--gds-yellow-400);
79
+ --color-gds-yellow-500: var(--gds-yellow-500);
80
+ --color-gds-yellow-600: var(--gds-yellow-600);
81
+ --color-gds-yellow-700: var(--gds-yellow-700);
82
+ --color-gds-yellow-800: var(--gds-yellow-800);
83
+ --color-gds-yellow-900: var(--gds-yellow-900);
84
+ --color-gds-orange: var(--gds-orange);
85
+ --color-gds-orange-50: var(--gds-orange-50);
86
+ --color-gds-orange-100: var(--gds-orange-100);
87
+ --color-gds-orange-200: var(--gds-orange-200);
88
+ --color-gds-orange-300: var(--gds-orange-300);
89
+ --color-gds-orange-400: var(--gds-orange-400);
90
+ --color-gds-orange-500: var(--gds-orange-500);
91
+ --color-gds-orange-600: var(--gds-orange-600);
92
+ --color-gds-orange-700: var(--gds-orange-700);
93
+ --color-gds-orange-800: var(--gds-orange-800);
94
+ --color-gds-orange-900: var(--gds-orange-900);
95
+ --color-gds-red: var(--gds-red);
96
+ --color-gds-red-50: var(--gds-red-50);
97
+ --color-gds-red-100: var(--gds-red-100);
98
+ --color-gds-red-200: var(--gds-red-200);
99
+ --color-gds-red-300: var(--gds-red-300);
100
+ --color-gds-red-400: var(--gds-red-400);
101
+ --color-gds-red-500: var(--gds-red-500);
102
+ --color-gds-red-600: var(--gds-red-600);
103
+ --color-gds-red-700: var(--gds-red-700);
104
+ --color-gds-red-800: var(--gds-red-800);
105
+ --color-gds-red-900: var(--gds-red-900);
106
+ --color-gds-teal: var(--gds-teal);
107
+ --color-gds-teal-50: var(--gds-teal-50);
108
+ --color-gds-teal-100: var(--gds-teal-100);
109
+ --color-gds-teal-200: var(--gds-teal-200);
110
+ --color-gds-teal-300: var(--gds-teal-300);
111
+ --color-gds-teal-400: var(--gds-teal-400);
112
+ --color-gds-teal-500: var(--gds-teal-500);
113
+ --color-gds-teal-600: var(--gds-teal-600);
114
+ --color-gds-teal-700: var(--gds-teal-700);
115
+ --color-gds-teal-800: var(--gds-teal-800);
116
+ --color-gds-teal-900: var(--gds-teal-900);
117
+ --color-gds-teal-950: var(--gds-teal-950);
118
+ --color-gds-navy: var(--gds-navy);
119
+ --color-gds-navy-50: var(--gds-navy-50);
120
+ --color-gds-navy-100: var(--gds-navy-100);
121
+ --color-gds-navy-200: var(--gds-navy-200);
122
+ --color-gds-navy-300: var(--gds-navy-300);
123
+ --color-gds-navy-400: var(--gds-navy-400);
124
+ --color-gds-navy-500: var(--gds-navy-500);
125
+ --color-gds-navy-600: var(--gds-navy-600);
126
+ --color-gds-navy-700: var(--gds-navy-700);
127
+ --color-gds-navy-800: var(--gds-navy-800);
128
+ --color-gds-navy-900: var(--gds-navy-900);
129
+ --color-gds-blue: var(--gds-blue);
130
+ --color-gds-blue-50: var(--gds-blue-50);
131
+ --color-gds-blue-100: var(--gds-blue-100);
132
+ --color-gds-blue-200: var(--gds-blue-200);
133
+ --color-gds-blue-300: var(--gds-blue-300);
134
+ --color-gds-blue-400: var(--gds-blue-400);
135
+ --color-gds-blue-500: var(--gds-blue-500);
136
+ --color-gds-blue-600: var(--gds-blue-600);
137
+ --color-gds-blue-700: var(--gds-blue-700);
138
+ --color-gds-blue-800: var(--gds-blue-800);
139
+ --color-gds-blue-900: var(--gds-blue-900);
140
+ --color-gds-gray-50: var(--gds-gray-50);
141
+ --color-gds-gray-100: var(--gds-gray-100);
142
+ --color-gds-gray-200: var(--gds-gray-200);
143
+ --color-gds-gray-300: var(--gds-gray-300);
144
+ --color-gds-gray-400: var(--gds-gray-400);
145
+ --color-gds-gray-500: var(--gds-gray-500);
146
+ --color-gds-gray-600: var(--gds-gray-600);
147
+ --color-gds-gray-700: var(--gds-gray-700);
148
+ --color-gds-gray-800: var(--gds-gray-800);
149
+ --color-gds-gray-900: var(--gds-gray-900);
150
+ --color-gds-gray-950: var(--gds-gray-950);
151
+ --color-gds-black: var(--gds-black);
152
+ --color-gds-white: var(--gds-white);
147
153
 
148
154
  /* -- Semantic roles — runtime vars hold bare "L C H" OKLCH triplets,
149
155
  wrapped with oklch() at use. Opacity shortcuts (bg-primary/50) come
@@ -767,6 +773,8 @@
767
773
  ---------------------------------------- */
768
774
  --background: 0.985 0.0018 85; /* neutral 50 */
769
775
  --foreground: 0.170 0.0048 85; /* neutral 950 */
776
+ --bg-base: 0.985 0.0018 85; /* stable mirror for scope-inverse */
777
+ --fg-base: 0.170 0.0048 85;
770
778
  --card: 1 0 0; /* pure white */
771
779
  --card-foreground: 0.170 0.0048 85;
772
780
  --popover: 1 0 0;
@@ -869,6 +877,19 @@
869
877
  --gds-map-marker-border: oklch(var(--border));
870
878
  --gds-map-marker-shadow: 0 1px 3px oklch(0 0 0 / 0.22),
871
879
  0 0 0 1px oklch(0 0 0 / 0.05);
880
+ /* Halo behind floating map labels (the text-stroke that separates a
881
+ label from busy tiles). Mode-aware: a white halo reads on pale
882
+ light tiles; the dark-mode override flips it to near-black so a
883
+ light label on dark tiles isn't a white-on-white wash-out. Applied
884
+ via the `.gds-map-label` helper. */
885
+ --gds-map-label-halo: oklch(1 0 0);
886
+ /* Map canvas — the backing colour BEHIND the raster tiles. Leaflet's
887
+ default is `#dddddd`, so subpixel gaps between tiles flash light
888
+ "seams" over a dark basemap. Defaulting this to the tile base
889
+ colour (here, the CARTO voyager land tone) makes those gaps blend
890
+ away. Neutral on purpose: tint it and the seams come back as
891
+ coloured lines unless your tiles match. */
892
+ --gds-map-canvas: oklch(0.97 0.004 95);
872
893
 
873
894
  /* Sidebar knobs — the width pair drives the collapsed/expanded animation
874
895
  on `<Sidebar>` (compound nav primitive). Header height + section
@@ -881,6 +902,33 @@
881
902
  --gds-sidebar-section-px: 0.5rem;
882
903
  --gds-sidebar-section-gap: 0.125rem;
883
904
 
905
+ /* Property list knobs — <PropertyList> renders a single record as
906
+ label / value rows (a Table row transposed). The label column width
907
+ and the row/column rhythm are exposed so a dense inspector and an
908
+ airy settings page share one primitive. Read as
909
+ `var(--gds-property-list-* , <fallback>)` in the component, so a
910
+ missing stanza degrades gracefully. */
911
+ --gds-property-list-label-width: 8.5rem;
912
+ --gds-property-list-col-gap: 1rem;
913
+ --gds-property-list-row-gap: 0.625rem;
914
+ --gds-property-list-row-gap-compact: 0.375rem;
915
+ --gds-property-list-row-gap-relaxed: 1rem;
916
+ --gds-property-list-icon-size: 0.875rem;
917
+ --gds-property-list-label-color: oklch(var(--muted-foreground));
918
+ --gds-property-list-divider-color: oklch(var(--border));
919
+
920
+ /* Data view knobs — <DataView> draws one dataset as a table, cards, or a
921
+ grid. The card / grid tiles auto-fill, so these min column widths set
922
+ how many tiles sit per row at a given container width; the pinned and
923
+ header backgrounds back the sticky column / header so scrolled content
924
+ doesn't bleed through. */
925
+ --gds-data-view-card-min: 18rem;
926
+ --gds-data-view-grid-min: 11rem;
927
+ --gds-data-view-gap: 0.75rem;
928
+ --gds-data-view-pinned-bg: oklch(var(--background));
929
+ --gds-data-view-header-bg: oklch(var(--background));
930
+ --gds-data-view-table-max-h: 28rem; /* scroll height when stickyHeader is on */
931
+
884
932
  /* Carousel knobs — every visual dimension of <Carousel> is driven from
885
933
  this stanza so consumers can re-skin the slideshow (dot shape, arrow
886
934
  chrome, transition feel) without prop-drilling. The component reads
@@ -962,6 +1010,8 @@
962
1010
  ---------------------------------------- */
963
1011
  --background: 0.170 0.0048 85;
964
1012
  --foreground: 0.985 0.0018 85;
1013
+ --bg-base: 0.170 0.0048 85;
1014
+ --fg-base: 0.985 0.0018 85;
965
1015
  --card: 0.245 0.0096 85; /* neutral 900 */
966
1016
  --card-foreground: 0.985 0.0018 85;
967
1017
  --popover: 0.245 0.0096 85;
@@ -1008,6 +1058,12 @@
1008
1058
  --gds-map-marker-border: oklch(1 0 0 / 0.3);
1009
1059
  --gds-map-marker-shadow: 0 1px 3px oklch(0 0 0 / 0.5),
1010
1060
  0 0 0 1px oklch(0 0 0 / 0.4);
1061
+ /* Dark-mode label halo — near-black so light labels stay legible on
1062
+ dark tiles (mirror of the light-mode white halo above). */
1063
+ --gds-map-label-halo: oklch(0.18 0 0);
1064
+ /* Dark-mode map canvas — near-black to match the CARTO dark_all base,
1065
+ so tile seams vanish on the dark basemap. */
1066
+ --gds-map-canvas: oklch(0.17 0 0);
1011
1067
 
1012
1068
  /* Alert surface pairs — dark-mode variant: tint sits just above the dark
1013
1069
  background without going gray; deep text stays bright and readable. */
@@ -1071,6 +1127,8 @@
1071
1127
  :root[data-grade-theme="energy"] {
1072
1128
  --background: 0.985 0.0012 175; /* neutral 50 */
1073
1129
  --foreground: 0.170 0.0032 175; /* neutral 950 */
1130
+ --bg-base: 0.985 0.0012 175;
1131
+ --fg-base: 0.170 0.0032 175;
1074
1132
  --card: 1 0 0;
1075
1133
  --card-foreground: 0.170 0.0032 175;
1076
1134
  --popover: 1 0 0;
@@ -1095,6 +1153,8 @@
1095
1153
  .dark[data-grade-theme="energy"] {
1096
1154
  --background: 0.170 0.0032 175;
1097
1155
  --foreground: 0.985 0.0012 175;
1156
+ --bg-base: 0.170 0.0032 175;
1157
+ --fg-base: 0.985 0.0012 175;
1098
1158
  --card: 0.245 0.0064 175;
1099
1159
  --card-foreground: 0.985 0.0012 175;
1100
1160
  --popover: 0.245 0.0064 175;
@@ -1673,21 +1733,23 @@
1673
1733
  }
1674
1734
 
1675
1735
  /* ============================================
1676
- MAP MARKER LIFT every pin gets a border
1736
+ MAP MARKER — intentionally unstyled
1677
1737
  ============================================
1678
- Map tiles are external imagery and don't follow the theme, so marker
1679
- content (Badge price pins, numbered markers, avatars) can melt into
1680
- them illegible dark-on-dark pins were the symptom. Guarantee the
1681
- lift at the DS level: every DIRECT child of a MapMarker's content
1682
- wrapper gets a 1px border + ambient shadow from the mode-aware
1683
- `--gds-map-marker-*` pair (light hairline on dark tiles, dark line
1684
- on light). Being unlayered, this wins over a Badge's own
1685
- border-transparent utility — deliberate: the border is the floor,
1686
- not a suggestion. Backgrounds/text are NOT touched; content keeps
1687
- its own surface tokens. */
1688
- [data-gds-part="map-marker-content"] > * {
1689
- border: 1px solid var(--gds-map-marker-border);
1690
- box-shadow: var(--gds-map-marker-shadow);
1738
+ Marker content (Badge price pins, numbered markers, avatars, custom pin
1739
+ types) is the CONSUMER's design. The DS used to force a 1px border + ambient
1740
+ shadow on every direct child of a marker as a legibility "floor", but that's
1741
+ too opinionated for a primitive it imposed a look on content you supply.
1742
+ Removed. The mode-aware `--gds-map-marker-border` / `--gds-map-marker-shadow`
1743
+ tokens remain defined, and `.gds-map-label` (below) still gives text a halo,
1744
+ so legibility on busy tiles is OPT-IN, not mandatory. */
1745
+
1746
+ /* Floating map label halo put this class on a text element inside a
1747
+ MapMarker to give it a mode-aware outline (white on light tiles,
1748
+ near-black on dark) instead of hard-coding a white text-stroke that
1749
+ washes out in dark mode. Pairs with the `--gds-map-label-halo` token. */
1750
+ .gds-map-label {
1751
+ -webkit-text-stroke: 1.5px var(--gds-map-label-halo);
1752
+ paint-order: stroke;
1691
1753
  }
1692
1754
 
1693
1755
  /* Vendor font reset — Leaflet (`.leaflet-container`) and MapLibre
@@ -1702,6 +1764,32 @@
1702
1764
  font-family: var(--font-sans);
1703
1765
  }
1704
1766
 
1767
+ /* Vendor z-index neutralizer — Leaflet's stylesheet sets `z-index: 200`
1768
+ on every `<svg>` inside the map. That hijacks an inline SVG used as
1769
+ marker content (e.g. a pin shield) and paints it ABOVE later sibling
1770
+ DOM — so a count/label sitting on top of the shield vanishes behind
1771
+ it. The Mapbox/MapLibre/Google adapters have no such rule, so the
1772
+ same marker markup renders fine there; this made pins look provider-
1773
+ dependent ("numbers show on Mapbox, not Leaflet"). Reset marker SVGs
1774
+ to `z-index: auto` so author paint order (DOM order) holds on every
1775
+ provider. `!important` to beat Leaflet's vendor selector. */
1776
+ [data-gds-part="map-marker-content"] svg {
1777
+ z-index: auto !important;
1778
+ }
1779
+
1780
+ /* Map canvas backing — sits behind the tiles so Leaflet's default
1781
+ `#dddddd` no longer flashes through the subpixel gaps between raster
1782
+ tiles ("seams"), most visible on a dark basemap. Harmless on the GL
1783
+ providers (their opaque canvas covers it). Mode-aware via the token.
1784
+ NB the selector is compounded with `[data-gds-part="map"]` on purpose:
1785
+ Leaflet stamps `.leaflet-container` (which sets `background:#ddd`) onto
1786
+ the SAME element, and its stylesheet loads after ours — a bare
1787
+ `.gds-map` ties on specificity and loses. The data-part attribute
1788
+ (present on every provider's root) lifts us above Leaflet's default. */
1789
+ .gds-map[data-gds-part="map"] {
1790
+ background-color: var(--gds-map-canvas);
1791
+ }
1792
+
1705
1793
  /* ============================================
1706
1794
  STREAM-IN — data-gds-streaming on <html>
1707
1795
  ============================================
@@ -1985,6 +2073,153 @@ html[data-gds-streaming] body * {
1985
2073
  }
1986
2074
  }
1987
2075
 
2076
+ @layer utilities {
2077
+ /* Colour SCOPES (STUDIO-COLOR.md) — a local colour mode, the same idea as
2078
+ light/dark but scoped to a wrapper and focused on surfaces.
2079
+ Apply `scope-inverse` (etc.) to any element and it becomes a mini themed
2080
+ region: it re-points the SURFACE family of tokens — page background, card,
2081
+ popover, muted, border, and every foreground — at the named pair, so the
2082
+ whole subtree re-tones (a dark band with light text, cards that sit on the
2083
+ band instead of glaring white). Descendants keep using the ordinary tokens
2084
+ (bg-background / text-foreground / bg-card); only what they resolve to
2085
+ changes. It deliberately leaves the ACTION colours (--primary / --accent /
2086
+ --secondary / --destructive) untouched, so a CTA stays branded and vivid
2087
+ on top of the band.
2088
+ `inverse` reads --fg-base / --bg-base (stable literal mirrors of the page
2089
+ ink/paper that the generator emits) so the fg/bg swap can't form a custom-
2090
+ property cycle; the other scopes read the theme's own tokens directly. */
2091
+ .scope-default,
2092
+ .scope-inverse,
2093
+ .scope-brand,
2094
+ .scope-accent,
2095
+ .scope-muted,
2096
+ .scope-card {
2097
+ background-color: oklch(var(--background));
2098
+ color: oklch(var(--foreground));
2099
+ }
2100
+ /* default = identity: paints the page tokens as-is, no overrides. */
2101
+ .scope-inverse {
2102
+ --background: var(--fg-base);
2103
+ --foreground: var(--bg-base);
2104
+ --card: var(--fg-base);
2105
+ --card-foreground: var(--bg-base);
2106
+ --popover: var(--fg-base);
2107
+ --popover-foreground: var(--bg-base);
2108
+ --muted: var(--fg-base);
2109
+ --muted-foreground: var(--bg-base);
2110
+ --border: var(--bg-base);
2111
+ --input: var(--bg-base);
2112
+ }
2113
+ .scope-brand {
2114
+ --background: var(--primary);
2115
+ --foreground: var(--primary-foreground);
2116
+ --card: var(--primary);
2117
+ --card-foreground: var(--primary-foreground);
2118
+ --popover: var(--primary);
2119
+ --popover-foreground: var(--primary-foreground);
2120
+ --muted: var(--primary);
2121
+ --muted-foreground: var(--primary-foreground);
2122
+ --border: var(--primary-foreground);
2123
+ --input: var(--primary-foreground);
2124
+ }
2125
+ .scope-accent {
2126
+ --background: var(--accent);
2127
+ --foreground: var(--accent-foreground);
2128
+ --card: var(--accent);
2129
+ --card-foreground: var(--accent-foreground);
2130
+ --popover: var(--accent);
2131
+ --popover-foreground: var(--accent-foreground);
2132
+ --muted: var(--accent);
2133
+ --muted-foreground: var(--accent-foreground);
2134
+ --border: var(--accent-foreground);
2135
+ --input: var(--accent-foreground);
2136
+ }
2137
+ .scope-muted {
2138
+ --background: var(--muted);
2139
+ --card: var(--muted);
2140
+ --popover: var(--muted);
2141
+ /* foreground + muted-foreground stay the page's: already tuned to read on
2142
+ a muted surface. */
2143
+ }
2144
+ .scope-card {
2145
+ --background: var(--card);
2146
+ --foreground: var(--card-foreground);
2147
+ --popover: var(--card);
2148
+ --popover-foreground: var(--card-foreground);
2149
+ --muted-foreground: var(--card-foreground);
2150
+ }
2151
+ }
2152
+
2153
+ /* ============================================
2154
+ EXPRESSIVE colour layer — see STUDIO-EXPRESSIVE.md
2155
+ A highlight layer for painting whole SECTIONS, deliberately louder than the
2156
+ neutral product chrome (marketing bands, feature cards, promo strips).
2157
+ Orthogonal to the surface scopes above: it does NOT touch --primary/--accent,
2158
+ so it stacks on top of any theme. Two attributes resolve a section's colour:
2159
+ data-expressive="accent1..5" picks one of 5 accent slots
2160
+ data-expressive-tier="…" picks one of 4 tiers (default: light)
2161
+ …landing an accessible pair on --gds-expressive-bg / --gds-expressive-fg,
2162
+ which the `.expressive` helper paints. Values mirror the Figma "Expressive"
2163
+ collection (accent1..5 = rose, orange, amber, yellow, olive). The accents are
2164
+ fixed ramps today; the generator will later write them from one hue per slot
2165
+ — the token shape stays identical. Tiers use ramp steps 100/300/700/900. */
2166
+ :root {
2167
+ --gds-expressive-accent1-100: #ffe9e6; --gds-expressive-accent1-300: #ffaaa2; --gds-expressive-accent1-700: #8b181d; --gds-expressive-accent1-900: #3f0b0b;
2168
+ --gds-expressive-accent2-100: #ffeae0; --gds-expressive-accent2-300: #ffae89; --gds-expressive-accent2-700: #822c00; --gds-expressive-accent2-900: #3c1100;
2169
+ --gds-expressive-accent3-100: #ffecd8; --gds-expressive-accent3-300: #f6b575; --gds-expressive-accent3-700: #743b00; --gds-expressive-accent3-900: #331900;
2170
+ --gds-expressive-accent4-100: #fdefd2; --gds-expressive-accent4-300: #e5bf6d; --gds-expressive-accent4-700: #634600; --gds-expressive-accent4-900: #2d1d00;
2171
+ --gds-expressive-accent5-100: #f4f2d4; --gds-expressive-accent5-300: #cfc871; --gds-expressive-accent5-700: #544e00; --gds-expressive-accent5-900: #252100;
2172
+ }
2173
+
2174
+ /* accent slot → generic step vars */
2175
+ [data-expressive="accent1"] { --exp-100: var(--gds-expressive-accent1-100); --exp-300: var(--gds-expressive-accent1-300); --exp-700: var(--gds-expressive-accent1-700); --exp-900: var(--gds-expressive-accent1-900); }
2176
+ [data-expressive="accent2"] { --exp-100: var(--gds-expressive-accent2-100); --exp-300: var(--gds-expressive-accent2-300); --exp-700: var(--gds-expressive-accent2-700); --exp-900: var(--gds-expressive-accent2-900); }
2177
+ [data-expressive="accent3"] { --exp-100: var(--gds-expressive-accent3-100); --exp-300: var(--gds-expressive-accent3-300); --exp-700: var(--gds-expressive-accent3-700); --exp-900: var(--gds-expressive-accent3-900); }
2178
+ [data-expressive="accent4"] { --exp-100: var(--gds-expressive-accent4-100); --exp-300: var(--gds-expressive-accent4-300); --exp-700: var(--gds-expressive-accent4-700); --exp-900: var(--gds-expressive-accent4-900); }
2179
+ [data-expressive="accent5"] { --exp-100: var(--gds-expressive-accent5-100); --exp-300: var(--gds-expressive-accent5-300); --exp-700: var(--gds-expressive-accent5-700); --exp-900: var(--gds-expressive-accent5-900); }
2180
+
2181
+ /* tier → which steps become bg / fg (default = light) */
2182
+ [data-expressive] { --gds-expressive-bg: var(--exp-300); --gds-expressive-fg: var(--exp-900); }
2183
+ [data-expressive][data-expressive-tier="superlight"] { --gds-expressive-bg: var(--exp-100); --gds-expressive-fg: var(--exp-900); }
2184
+ [data-expressive][data-expressive-tier="light"] { --gds-expressive-bg: var(--exp-300); --gds-expressive-fg: var(--exp-900); }
2185
+ [data-expressive][data-expressive-tier="dark"] { --gds-expressive-bg: var(--exp-700); --gds-expressive-fg: var(--exp-100); }
2186
+ [data-expressive][data-expressive-tier="superdark"] { --gds-expressive-bg: var(--exp-900); --gds-expressive-fg: var(--exp-100); }
2187
+
2188
+ /* the surface helper a section wears: paints the resolved expressive pair */
2189
+ .expressive { background-color: var(--gds-expressive-bg); color: var(--gds-expressive-fg); }
2190
+
2191
+ /* ============================================
2192
+ REVEAL — scroll-in motion for sections (and any element). Token-driven:
2193
+ reuses the DS transition durations + eases (above), adds a reveal distance.
2194
+ An element with `data-reveal` starts hidden/offset; an IntersectionObserver
2195
+ in the consumer adds `.in-view` to animate it in. Entirely disabled under
2196
+ prefers-reduced-motion (the hidden state lives inside the media query, so
2197
+ content is always visible if motion is off or JS never runs). */
2198
+ :root {
2199
+ --gds-reveal-distance: 1.25rem;
2200
+ --gds-reveal-duration: var(--gds-transition-slower); /* 500ms */
2201
+ --gds-reveal-ease: var(--gds-ease-out);
2202
+ }
2203
+
2204
+ @media (prefers-reduced-motion: no-preference) {
2205
+ [data-reveal] {
2206
+ opacity: 0;
2207
+ transform: translateY(var(--gds-reveal-distance));
2208
+ transition:
2209
+ opacity var(--gds-reveal-duration) var(--gds-reveal-ease),
2210
+ transform var(--gds-reveal-duration) var(--gds-reveal-ease);
2211
+ will-change: opacity, transform;
2212
+ }
2213
+ [data-reveal="fade"] { transform: none; }
2214
+ [data-reveal="fade-down"] { transform: translateY(calc(-1 * var(--gds-reveal-distance))); }
2215
+ [data-reveal="fade-left"] { transform: translateX(var(--gds-reveal-distance)); }
2216
+ [data-reveal="fade-right"] { transform: translateX(calc(-1 * var(--gds-reveal-distance))); }
2217
+ [data-reveal].in-view {
2218
+ opacity: 1;
2219
+ transform: none;
2220
+ }
2221
+ }
2222
+
1988
2223
  /* Keyframes */
1989
2224
  @keyframes fadeIn {
1990
2225
  from { opacity: 0; }