@keenmate/pure-css 1.1.1 → 1.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.
package/package.json CHANGED
@@ -1,83 +1,86 @@
1
- {
2
- "name": "@keenmate/pure-css",
3
- "version": "1.1.1",
4
- "description": "The Keenmate CSS foundation — --base-* theming contract, PureCSS grid and utility classes. Extracted from pure-admin-core so docs sites, standalone pages and every web/svelte component share one theming layer.",
5
- "style": "dist/css/pure-css.css",
6
- "exports": {
7
- ".": "./dist/css/pure-css.css",
8
- "./css": "./dist/css/pure-css.css",
9
- "./css/*": "./dist/css/*",
10
- "./base": "./dist/css/base.css",
11
- "./reboot": "./dist/css/reboot.css",
12
- "./component-reset": "./dist/css/component-reset.css",
13
- "./scrollbars": "./dist/css/scrollbars.css",
14
- "./grid": "./dist/css/grid.css",
15
- "./utilities": "./dist/css/utilities.css",
16
- "./scss": "./src/scss/pure-css.scss",
17
- "./scss/*": "./src/scss/*",
18
- "./js": "./src/js/pure-css.js",
19
- "./js/*": "./src/js/*"
20
- },
21
- "files": [
22
- "dist/",
23
- "src/scss/",
24
- "src/js/",
25
- "README.md",
26
- "CHANGELOG.md",
27
- "LICENSE"
28
- ],
29
- "scripts": {
30
- "build:bundle": "sass src/scss/pure-css.scss dist/css/pure-css.css --no-source-map --silence-deprecation=import",
31
- "build:base": "sass src/scss/base.scss dist/css/base.css --no-source-map --silence-deprecation=import",
32
- "build:reboot": "sass src/scss/reboot.scss dist/css/reboot.css --no-source-map --silence-deprecation=import",
33
- "build:component-reset": "sass src/scss/component-reset.scss dist/css/component-reset.css --no-source-map --silence-deprecation=import",
34
- "build:scrollbars": "sass src/scss/scrollbars.scss dist/css/scrollbars.css --no-source-map --silence-deprecation=import",
35
- "build:grid": "sass src/scss/grid.scss dist/css/grid.css --no-source-map --silence-deprecation=import",
36
- "build:utilities": "sass src/scss/utilities.scss dist/css/utilities.css --no-source-map --silence-deprecation=import",
37
- "build": "npm run build:bundle && npm run build:base && npm run build:reboot && npm run build:component-reset && npm run build:scrollbars && npm run build:grid && npm run build:utilities",
38
- "watch": "sass --watch src/scss/pure-css.scss:dist/css/pure-css.css --silence-deprecation=import",
39
- "dev": "vite --port 12500",
40
- "test": "npm run test:e2e",
41
- "test:e2e": "playwright test",
42
- "test:e2e:ui": "playwright test --ui",
43
- "test:e2e:headed": "playwright test --headed",
44
- "test:e2e:install": "playwright install chromium",
45
- "prepublishOnly": "npm run build"
46
- },
47
- "keywords": [
48
- "css",
49
- "scss",
50
- "purecss",
51
- "grid",
52
- "utilities",
53
- "css-variables",
54
- "theming",
55
- "keenmate",
56
- "pure-admin"
57
- ],
58
- "author": "Keenmate",
59
- "license": "MIT",
60
- "peerDependencies": {
61
- "sass": "^1.0.0"
62
- },
63
- "devDependencies": {
64
- "@playwright/test": "^1.60.0",
65
- "sass": "^1.70.0",
66
- "vite": "^5.4.0"
67
- },
68
- "repository": {
69
- "type": "git",
70
- "url": "git+https://github.com/Keenmate/pure-css.git"
71
- },
72
- "bugs": {
73
- "url": "https://github.com/Keenmate/pure-css/issues"
74
- },
75
- "homepage": "https://github.com/Keenmate/pure-css#readme",
76
- "publishConfig": {
77
- "access": "public"
78
- },
79
- "allowScripts": {
80
- "esbuild@0.21.5": true,
81
- "@parcel/watcher@2.6.0": true
82
- }
83
- }
1
+ {
2
+ "name": "@keenmate/pure-css",
3
+ "version": "1.2.0",
4
+ "description": "The Keenmate CSS foundation — --base-* theming contract, PureCSS grid and utility classes. Extracted from pure-admin-core so docs sites, standalone pages and every web/svelte component share one theming layer.",
5
+ "style": "dist/css/pure-css.css",
6
+ "exports": {
7
+ ".": "./dist/css/pure-css.css",
8
+ "./css": "./dist/css/pure-css.css",
9
+ "./css/*": "./dist/css/*",
10
+ "./base": "./dist/css/base.css",
11
+ "./reboot": "./dist/css/reboot.css",
12
+ "./component-reset": "./dist/css/component-reset.css",
13
+ "./scrollbars": "./dist/css/scrollbars.css",
14
+ "./grid": "./dist/css/grid.css",
15
+ "./utilities": "./dist/css/utilities.css",
16
+ "./scss": "./src/scss/pure-css.scss",
17
+ "./scss/*": "./src/scss/*",
18
+ "./js": "./src/js/pure-css.js",
19
+ "./js/*": "./src/js/*"
20
+ },
21
+ "files": [
22
+ "dist/",
23
+ "src/scss/",
24
+ "src/js/",
25
+ "README.md",
26
+ "CHANGELOG.md",
27
+ "LICENSE",
28
+ "components.json",
29
+ "COMPONENTS.md"
30
+ ],
31
+ "scripts": {
32
+ "build:bundle": "sass src/scss/pure-css.scss dist/css/pure-css.css --no-source-map --silence-deprecation=import",
33
+ "build:base": "sass src/scss/base.scss dist/css/base.css --no-source-map --silence-deprecation=import",
34
+ "build:reboot": "sass src/scss/reboot.scss dist/css/reboot.css --no-source-map --silence-deprecation=import",
35
+ "build:component-reset": "sass src/scss/component-reset.scss dist/css/component-reset.css --no-source-map --silence-deprecation=import",
36
+ "build:scrollbars": "sass src/scss/scrollbars.scss dist/css/scrollbars.css --no-source-map --silence-deprecation=import",
37
+ "build:grid": "sass src/scss/grid.scss dist/css/grid.css --no-source-map --silence-deprecation=import",
38
+ "build:utilities": "sass src/scss/utilities.scss dist/css/utilities.css --no-source-map --silence-deprecation=import",
39
+ "build": "npm run build:bundle && npm run build:base && npm run build:reboot && npm run build:component-reset && npm run build:scrollbars && npm run build:grid && npm run build:utilities",
40
+ "watch": "sass --watch src/scss/pure-css.scss:dist/css/pure-css.css --silence-deprecation=import",
41
+ "dev": "vite --port 12500",
42
+ "test": "npm run test:e2e",
43
+ "test:e2e": "playwright test",
44
+ "test:e2e:ui": "playwright test --ui",
45
+ "test:e2e:headed": "playwright test --headed",
46
+ "test:e2e:install": "playwright install chromium",
47
+ "prepublishOnly": "npm run build && npm run catalog",
48
+ "catalog": "node scripts/build-catalog.mjs"
49
+ },
50
+ "keywords": [
51
+ "css",
52
+ "scss",
53
+ "purecss",
54
+ "grid",
55
+ "utilities",
56
+ "css-variables",
57
+ "theming",
58
+ "keenmate",
59
+ "pure-admin"
60
+ ],
61
+ "author": "Keenmate",
62
+ "license": "MIT",
63
+ "peerDependencies": {
64
+ "sass": "^1.0.0"
65
+ },
66
+ "devDependencies": {
67
+ "@playwright/test": "^1.60.0",
68
+ "sass": "^1.70.0",
69
+ "vite": "^5.4.0"
70
+ },
71
+ "repository": {
72
+ "type": "git",
73
+ "url": "git+https://github.com/Keenmate/pure-css.git"
74
+ },
75
+ "bugs": {
76
+ "url": "https://github.com/Keenmate/pure-css/issues"
77
+ },
78
+ "homepage": "https://github.com/Keenmate/pure-css#readme",
79
+ "publishConfig": {
80
+ "access": "public"
81
+ },
82
+ "allowScripts": {
83
+ "esbuild@0.21.5": true,
84
+ "@parcel/watcher@2.6.0": true
85
+ }
86
+ }
@@ -188,6 +188,10 @@
188
188
  --base-border-radius-sm: #{$base-border-radius-sm};
189
189
  --base-border-radius-md: #{$base-border-radius-md};
190
190
  --base-border-radius-lg: #{$base-border-radius-lg};
191
+ // Checkbox BOX scale — multiplies the box's own size (NOT a transform, which
192
+ // would pixel-snap the mask glyph off-centre). One knob every component with a
193
+ // checkbox reads; pairs with --base-icon-check-size.
194
+ --base-checkbox-scale: #{$base-checkbox-scale};
191
195
 
192
196
  // === Rem base for web components ===
193
197
  --base-rem: #{$base-rem};
@@ -21,110 +21,130 @@
21
21
  --pc-easing-snappy: #{$easing-snappy};
22
22
  }
23
23
 
24
- // Responsive Design - Mobile and Tablet
25
- @media (max-width: $mobile-breakpoint) {
26
- // Mobile: the sidebar is an off-canvas drawer that SLIDES in/out, in sync with
27
- // the profile panel (same $transition-medium / $easing-snappy). It's always
28
- // position:fixed at the drawer size and translated off-canvas at the
29
- // inline-start edge; `.sidebar-visible` slides it to translateX(0). This
30
- // replaces the old snap between width:0 and a fixed overlay, which popped in
31
- // with no animation. Content takes full width in both states because the
32
- // drawer is out of flow (fixed).
33
- .pc-layout__sidebar,
34
- .pc-layout__sidebar--icon-collapse {
35
- position: fixed;
36
- top: $navbar-height;
37
- inset-inline-start: 0; // RTL: flips to right
38
- bottom: 0;
39
- width: $mobile-sidebar-width;
40
- z-index: 1050;
41
- background-color: var(--pc-sidebar-bg, var(--base-page-bg));
42
- opacity: 1;
43
- overflow-y: auto;
44
- overscroll-behavior: contain; // drawer's own scroll doesn't chain to the page
45
- border-inline-end: none; // RTL: flips to left
46
- transform: translateX(-100%); // off-canvas (slid out past the inline-start edge)
47
- visibility: hidden; // inert + unfocusable while closed; flips visible on open
48
-
49
- // Gate the transition on body.loaded like the sidebar links, so the drawer
50
- // doesn't animate from off-canvas on first paint.
51
- body.loaded & {
52
- transition: transform $transition-medium $easing-snappy,
53
- visibility $transition-medium $easing-snappy;
24
+ // Off-canvas drawer overlay — the sidebar SLIDES in/out as a fixed sheet in sync
25
+ // with the profile panel (same $transition-medium / $easing-snappy), translated
26
+ // off-canvas at the inline-start edge; `.sidebar-visible` slides it to
27
+ // translateX(0). Content takes full width in both states because the drawer is
28
+ // out of flow (fixed), so the flex sibling `.pc-layout__content {flex:1}` grows.
29
+ //
30
+ // Shared by two callers via $bodyQualifier:
31
+ // • '' → every sidebar below $mobile-breakpoint (media query).
32
+ // • '.sidebar-overlay' → the opt-in desktop "overlay" sidebar behaviour, at any
33
+ // width (burger opens a temporary floating drawer).
34
+ // The qualifier composes onto `body` so the body.loaded transition gate and the
35
+ // .sidebar-visible open state resolve as SAME-ELEMENT compounds (body.loaded,
36
+ // body.sidebar-visible) — not broken descendant selectors.
37
+ //
38
+ // $drawerWidth differs per caller: mobile uses a near-full-width 90vw sheet;
39
+ // desktop overlay uses the normal $sidebar-width (288px) so the content stays
40
+ // usable behind it and the dismiss zone isn't a sliver.
41
+ @mixin sidebar-drawer-overlay($bodyQualifier: '', $drawerWidth: $mobile-sidebar-width) {
42
+ body#{$bodyQualifier} {
43
+ .pc-layout__sidebar,
44
+ .pc-layout__sidebar--icon-collapse {
45
+ position: fixed;
46
+ top: $navbar-height;
47
+ inset-inline-start: 0; // RTL: flips to right
48
+ bottom: 0;
49
+ width: $drawerWidth;
50
+ z-index: 1050;
51
+ background-color: var(--pc-sidebar-bg, var(--base-page-bg));
52
+ opacity: 1;
53
+ overflow-y: auto;
54
+ overscroll-behavior: contain; // drawer's own scroll doesn't chain to the page
55
+ border-inline-end: none; // RTL: flips to left
56
+ transform: translateX(-100%); // off-canvas (slid out past the inline-start edge)
57
+ visibility: hidden; // inert + unfocusable while closed; flips visible on open
54
58
  }
55
- }
56
59
 
57
- // RTL: the drawer sits at the inline-start (right) edge, so it slides off to
58
- // the right when closed.
59
- [dir="rtl"] .pc-layout__sidebar,
60
- [dir="rtl"] .pc-layout__sidebar--icon-collapse {
61
- transform: translateX(100%);
62
- }
60
+ // RTL: the drawer sits at the inline-start (right) edge, so it slides off to
61
+ // the right when closed.
62
+ [dir="rtl"] & {
63
+ .pc-layout__sidebar,
64
+ .pc-layout__sidebar--icon-collapse {
65
+ transform: translateX(100%);
66
+ }
67
+ }
63
68
 
64
- // icon-collapse mode is a full overlay drawer on mobile — force labels visible.
65
- .pc-layout__sidebar--icon-collapse .pc-sidebar__label {
66
- opacity: 1 !important;
67
- width: auto !important;
68
- overflow: visible !important;
69
- position: static !important;
70
- }
69
+ // icon-collapse mode is a full labelled drawer here — force labels visible.
70
+ .pc-layout__sidebar--icon-collapse .pc-sidebar__label {
71
+ opacity: 1 !important;
72
+ width: auto !important;
73
+ overflow: visible !important;
74
+ position: static !important;
75
+ }
71
76
 
72
- // Backdrop scrim: an always-present fixed pseudo that FADES its opacity, so it
73
- // eases in/out with the drawer (a pseudo that only existed while open couldn't
74
- // fade out). Inert + invisible when closed. On body::before so it's positioned
75
- // against the viewport (not a transformed ancestor) — the toggle class lives
76
- // on body too.
77
- body::before {
78
- content: "";
79
- position: fixed;
80
- top: $navbar-height;
81
- inset-inline: 0; // RTL: covers full width
82
- bottom: 0;
83
- background-color: $mobile-backdrop-bg;
84
- z-index: 1040;
85
- opacity: 0;
86
- visibility: hidden;
87
- pointer-events: none;
88
-
89
- body.loaded & {
90
- transition: opacity $transition-medium $easing-snappy,
91
- visibility $transition-medium $easing-snappy;
77
+ // Backdrop scrim: an always-present fixed pseudo that FADES its opacity, so it
78
+ // eases in/out with the drawer (a pseudo that only existed while open couldn't
79
+ // fade out). Inert + invisible when closed. On the body pseudo so it's
80
+ // positioned against the viewport — the toggle class lives on body too.
81
+ &::before {
82
+ content: "";
83
+ position: fixed;
84
+ top: $navbar-height;
85
+ inset-inline: 0; // RTL: covers full width
86
+ bottom: 0;
87
+ background-color: $mobile-backdrop-bg;
88
+ z-index: 1040;
89
+ opacity: 0;
90
+ visibility: hidden;
91
+ pointer-events: none;
92
92
  }
93
- }
94
93
 
95
- // Mobile: drawer open — slide it in, reveal the scrim, lock background scroll.
96
- .sidebar-visible {
97
- // Standard modal-drawer scroll-lock: while the drawer + scrim are open the
98
- // page behind must not scroll (otherwise dragging the scrim strip scrolls
99
- // the content under the fixed drawer, exposing that the drawer is only
100
- // viewport-high). This covers desktop browsers + Android; iOS Safari ignores
101
- // body{overflow:hidden} for touch scroll, so the mobile toggle also applies
102
- // a position:fixed body lock in JS (demo layout.mustache). See
103
- // docs/mobile-sidebar-overlay.md.
104
- overflow: hidden;
94
+ // Gate the drawer + scrim transitions on body.loaded so neither animates from
95
+ // its closed position on first paint.
96
+ &.loaded {
97
+ .pc-layout__sidebar,
98
+ .pc-layout__sidebar--icon-collapse {
99
+ transition: transform $transition-medium $easing-snappy,
100
+ visibility $transition-medium $easing-snappy;
101
+ }
102
+
103
+ &::before {
104
+ transition: opacity $transition-medium $easing-snappy,
105
+ visibility $transition-medium $easing-snappy;
106
+ }
107
+ }
105
108
 
106
- .pc-layout__sidebar,
107
- .pc-layout__sidebar--icon-collapse {
108
- transform: translateX(0); // slide in (source order beats the RTL closed rule)
109
- visibility: visible;
109
+ // Open — slide the drawer in, reveal the scrim, lock background scroll.
110
+ // Listed LAST so translateX(0) beats both the default and the RTL closed
111
+ // transform at equal specificity (source order wins).
112
+ &.sidebar-visible {
113
+ // Standard modal-drawer scroll-lock: while the drawer + scrim are open the
114
+ // page behind must not scroll (otherwise dragging the scrim strip scrolls
115
+ // the content under the fixed drawer, exposing that the drawer is only
116
+ // viewport-high). This covers desktop browsers + Android; iOS Safari ignores
117
+ // body{overflow:hidden} for touch scroll, so the toggle also applies a
118
+ // position:fixed body lock in JS (demo layout.mustache). See
119
+ // docs/mobile-sidebar-overlay.md.
120
+ overflow: hidden;
121
+
122
+ .pc-layout__sidebar,
123
+ .pc-layout__sidebar--icon-collapse {
124
+ transform: translateX(0);
125
+ visibility: visible;
126
+ }
127
+
128
+ &::before {
129
+ opacity: 1;
130
+ visibility: visible;
131
+ pointer-events: auto; // tap the scrim to dismiss
132
+ }
110
133
  }
111
134
 
112
- &::before {
113
- opacity: 1;
114
- visibility: visible;
115
- pointer-events: auto; // tap the scrim to dismiss
135
+ // Resize handle is meaningless on a fixed overlay drawer (and would ride the
136
+ // overlay edge straddling the backdrop-dismiss tap zone). Touch resize still
137
+ // works in the tablet band above $mobile-breakpoint.
138
+ .pc-sidebar-resize {
139
+ display: none;
116
140
  }
117
141
  }
142
+ }
118
143
 
119
- // Mobile overlay: the sidebar is a fixed 90vw sheet and resize is disabled in
120
- // JS at this width (sidebar-resize.js MOBILE_MAX = $mobile-breakpoint). Hide
121
- // the handle so the coarse-pointer grip pill / edge line don't render as a
122
- // dead affordance riding the overlay's edge (where it also straddles the
123
- // backdrop-dismiss tap zone). Touch resize still works in the tablet band
124
- // above this breakpoint, where the coarse-pointer affordance is kept.
125
- .pc-sidebar-resize {
126
- display: none;
127
- }
144
+ // Responsive Design - Mobile and Tablet
145
+ @media (max-width: $mobile-breakpoint) {
146
+ // Below the mobile breakpoint EVERY sidebar is an off-canvas drawer.
147
+ @include sidebar-drawer-overlay;
128
148
 
129
149
  // Adjust navbar for mobile
130
150
  .pc-navbar__inner {
@@ -146,6 +166,17 @@
146
166
  }
147
167
  }
148
168
 
169
+ // Desktop opt-in: `body.sidebar-overlay` turns the sidebar into a temporary
170
+ // floating drawer — the "overlay" sidebar behaviour. The burger toggles
171
+ // `.sidebar-visible`; a tap on the scrim/outside closes it (demo JS). Gated
172
+ // ABOVE the mobile breakpoint: at/below it the media query already renders the
173
+ // sidebar as a 90vw drawer, so the desktop rules would only fight it (and widen
174
+ // a phone drawer to 288px). Uses the normal $sidebar-width so the drawer floats
175
+ // over content rather than swallowing the viewport.
176
+ @media (min-width: ($mobile-breakpoint + 1)) {
177
+ @include sidebar-drawer-overlay('.sidebar-overlay', $sidebar-width);
178
+ }
179
+
149
180
  @media (max-width: $tablet-breakpoint) and (min-width: $tablet-breakpoint-min) {
150
181
  // Tablet: reduce the sidebar by default, but honour an explicit resize.
151
182
  // The default (un-resized) width caps at $sidebar-width-tablet via min() so
@@ -709,16 +709,65 @@ $spacers: (
709
709
  .text-warning { color: var(--pc-warning-text) !important; }
710
710
  .text-info { color: var(--pc-info-text) !important; }
711
711
 
712
- // Text color utilities (theme custom colors 1-9)
713
- .text-color-1 { color: var(--pc-color-1) !important; }
714
- .text-color-2 { color: var(--pc-color-2) !important; }
715
- .text-color-3 { color: var(--pc-color-3) !important; }
716
- .text-color-4 { color: var(--pc-color-4) !important; }
717
- .text-color-5 { color: var(--pc-color-5) !important; }
718
- .text-color-6 { color: var(--pc-color-6) !important; }
719
- .text-color-7 { color: var(--pc-color-7) !important; }
720
- .text-color-8 { color: var(--pc-color-8) !important; }
721
- .text-color-9 { color: var(--pc-color-9) !important; }
712
+ // Role SURFACE utilities — the role parallel of the numeric
713
+ // .{bg,text-on,surface}-color-N family below. Fill = --pc-{role} (primary →
714
+ // --pc-accent); on-fill contrast text = --base-text-on-{role} (the only on-role
715
+ // contrast token — foreground .text-{role} above is a DIFFERENT colour). All
716
+ // !important for cascade parity with the role text utilities.
717
+ //
718
+ // .bg-{role} role colour as background (no text change)
719
+ // .text-on-{role} contrast text to sit on a role fill (pair with .bg-{role})
720
+ // .surface-{role} role background + contrast text in one class
721
+ @each $role, $fill in (primary: accent, success: success, warning: warning, danger: danger, info: info) {
722
+ .bg-#{$role} { background-color: var(--pc-#{$fill}) !important; }
723
+ .text-on-#{$role} { color: var(--base-text-on-#{$role}) !important; }
724
+ .surface-#{$role} {
725
+ background-color: var(--pc-#{$fill}) !important;
726
+ color: var(--base-text-on-#{$role}) !important;
727
+ }
728
+ }
729
+
730
+ // Text colour — neutral hierarchy. Maps to --pc-text-color-N (text HIERARCHY),
731
+ // a DIFFERENT scale from the numbered .text-color-N palette below (--pc-color-N
732
+ // data colours). `.text-secondary` is the flat muted/subdued text utility (the
733
+ // muted token used to live only inside a component modifier); `.text-body` is the
734
+ // explicit default body colour. !important so the colour wins when composed onto
735
+ // a component that carries its own colour.
736
+ .text-body { color: var(--pc-text-color-1) !important; } // default body text (explicit)
737
+ .text-secondary { color: var(--pc-text-color-2) !important; } // muted / subdued body text
738
+
739
+ // Semantic text shorthands (compound).
740
+ .text-caption {
741
+ font-size: $font-size-xs;
742
+ color: var(--pc-text-color-2);
743
+ margin-bottom: $spacing-sm;
744
+ }
745
+ .text-lead {
746
+ font-size: $font-size-base;
747
+ line-height: $line-height-relaxed;
748
+ }
749
+
750
+ // Theme palette colour utilities (theme custom colors 1-9).
751
+ // Two token layers per slot: --pc-color-N (the slot) + --pc-color-N-text (its
752
+ // guaranteed-contrasting text). Each form applies the slot to one property;
753
+ // `.surface-color-N` is the composite (slot background + readable text). All
754
+ // !important for cascade parity with the role colours above.
755
+ //
756
+ // .text-color-N slot as text colour
757
+ // .bg-color-N slot as background
758
+ // .border-color-N slot as border colour
759
+ // .text-on-color-N contrast text only (pair with .bg-color-N yourself)
760
+ // .surface-color-N slot background + contrast text in one class
761
+ @for $i from 1 through 9 {
762
+ .text-color-#{$i} { color: var(--pc-color-#{$i}) !important; }
763
+ .bg-color-#{$i} { background-color: var(--pc-color-#{$i}) !important; }
764
+ .border-color-#{$i} { border-color: var(--pc-color-#{$i}) !important; }
765
+ .text-on-color-#{$i} { color: var(--pc-color-#{$i}-text) !important; }
766
+ .surface-color-#{$i} {
767
+ background-color: var(--pc-color-#{$i}) !important;
768
+ color: var(--pc-color-#{$i}-text) !important;
769
+ }
770
+ }
722
771
 
723
772
  // Rounded utilities
724
773
  .rounded {
@@ -296,6 +296,14 @@ $base-border-radius-sm: 0.4 !default; // 4px
296
296
  $base-border-radius-md: 0.6 !default; // 6px
297
297
  $base-border-radius-lg: 0.8 !default; // 8px
298
298
 
299
+ // Checkbox BOX scale (web-component bridge: --base-checkbox-scale) — multiplies
300
+ // the box's OWN width/height, NOT a CSS transform (a transform pixel-snaps the
301
+ // fractionally-positioned mask glyph and shifts it off-centre). The box sizes as
302
+ // `calc(<base-size> * var(--base-checkbox-scale))`, the glyph via
303
+ // --base-icon-check-size. One knob every component with a checkbox reads, so a
304
+ // theme resizes all of them in lockstep. 1 = the component's base size.
305
+ $base-checkbox-scale: 1 !default;
306
+
299
307
  // Rem base for web components (bridge: --base-rem). They size themselves as
300
308
  // `calc(<unitless-multiplier> * var(--base-rem, 10px))`. pure-css pins
301
309
  // `html { font-size: 10px }` (reboot), so `1rem` tracks that base adaptively.