@shift-css/core 0.4.0 → 0.6.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 ADDED
@@ -0,0 +1,110 @@
1
+ # @shift-css/core
2
+
3
+ A zero-runtime, OKLCH-native CSS framework with automatic theming.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@shift-css/core)](https://www.npmjs.com/package/@shift-css/core)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @shift-css/core
12
+ # or
13
+ bun add @shift-css/core
14
+ # or
15
+ pnpm add @shift-css/core
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```css
21
+ /* Full framework */
22
+ @import "@shift-css/core";
23
+
24
+ /* Or minified for production */
25
+ @import "@shift-css/core/min";
26
+ ```
27
+
28
+ ```html
29
+ <div s-surface="raised">
30
+ <h2>Hello Shift</h2>
31
+ <button s-btn="primary">Get Started</button>
32
+ </div>
33
+ ```
34
+
35
+ No configuration, no JavaScript, no build step for customization.
36
+
37
+ ## Features
38
+
39
+ - **Zero JavaScript** - Theming works without any runtime code
40
+ - **Perceptually uniform colors** - OKLCH ensures consistent brightness across your palette
41
+ - **Automatic contrast** - Text colors adapt to any background automatically
42
+ - **Clean specificity** - Cascade layers mean no more `!important` battles
43
+ - **One-line customization** - Change a single hue variable to transform your entire palette
44
+
45
+ ```css
46
+ /* Change your entire brand palette with one line */
47
+ :root {
48
+ --shift-hue-primary: 280; /* Purple instead of blue */
49
+ }
50
+ ```
51
+
52
+ ## Customization
53
+
54
+ Override seed hues to transform your entire palette:
55
+
56
+ ```css
57
+ :root {
58
+ --shift-hue-primary: 280; /* Purple */
59
+ --shift-hue-secondary: 200; /* Teal */
60
+ --shift-hue-accent: 45; /* Gold */
61
+ --shift-hue-neutral: 280; /* Purple-tinted grays */
62
+ }
63
+ ```
64
+
65
+ ## Modular Imports
66
+
67
+ Import only what you need:
68
+
69
+ ```css
70
+ /* Reset only */
71
+ @import "@shift-css/core/reset";
72
+
73
+ /* Tokens only */
74
+ @import "@shift-css/core/tokens";
75
+ ```
76
+
77
+ ## Bundle Size
78
+
79
+ | File | Size |
80
+ | --------------- | ------ |
81
+ | `shift.css` | ~55 KB |
82
+ | `shift.min.css` | ~41 KB |
83
+ | Gzipped | ~10 KB |
84
+
85
+ ## Browser Support
86
+
87
+ | Browser | Minimum Version |
88
+ | ------- | --------------- |
89
+ | Chrome | 131+ |
90
+ | Firefox | 133+ |
91
+ | Safari | 18+ |
92
+ | Edge | 131+ |
93
+
94
+ ## Documentation
95
+
96
+ Full documentation at [getshiftcss.com](https://getshiftcss.com)
97
+
98
+ ## CLI
99
+
100
+ Use the CLI to initialize Shift CSS with proper cascade layers:
101
+
102
+ ```bash
103
+ npx shift-css init
104
+ ```
105
+
106
+ See [@shift-css/cli](https://www.npmjs.com/package/@shift-css/cli) for details.
107
+
108
+ ## License
109
+
110
+ MIT
@@ -0,0 +1,162 @@
1
+ :where([s-badge]) {
2
+ --_badge-bg: var(--s-neutral-200);
3
+ --_badge-text: var(--s-text-primary);
4
+ justify-content: center;
5
+ align-items: center;
6
+ gap: var(--s-space-1);
7
+ padding: var(--s-space-1) var(--s-space-2);
8
+ border-radius: var(--s-radius-sm);
9
+ font-weight: var(--s-font-medium);
10
+ font-size: var(--s-text-xs);
11
+ white-space: nowrap;
12
+ vertical-align: middle;
13
+ background-color: var(--_badge-bg);
14
+ color: var(--_badge-text);
15
+ line-height: 1.25;
16
+ display: inline-flex;
17
+
18
+ @supports (color: oklch(from red l c h)) {
19
+ color: oklch(from var(--_badge-bg) clamp(.15, calc((.55 - l) * 1000 + .15), .95) 0 0);
20
+ }
21
+
22
+ @supports (color: contrast-color(red)) {
23
+ color: contrast-color(var(--_badge-bg));
24
+ }
25
+ }
26
+
27
+ [s-badge="primary"] {
28
+ --_badge-bg: var(--s-primary-700);
29
+ background-color: var(--s-primary-700);
30
+ color: #fff;
31
+ }
32
+
33
+ [s-badge="secondary"] {
34
+ --_badge-bg: var(--s-neutral-700);
35
+ background-color: var(--s-neutral-700);
36
+ color: #fff;
37
+ }
38
+
39
+ [s-badge="success"] {
40
+ --_badge-bg: var(--s-success-700);
41
+ background-color: var(--s-success-700);
42
+ color: #fff;
43
+ }
44
+
45
+ [s-badge="warning"] {
46
+ --_badge-bg: var(--s-warning-400);
47
+ background-color: var(--s-warning-400);
48
+ color: var(--s-warning-950);
49
+ }
50
+
51
+ [s-badge="danger"] {
52
+ --_badge-bg: var(--s-danger-700);
53
+ background-color: var(--s-danger-700);
54
+ color: #fff;
55
+ }
56
+
57
+ [s-badge="accent"] {
58
+ --_badge-bg: var(--s-accent-700);
59
+ background-color: var(--s-accent-700);
60
+ color: #fff;
61
+ }
62
+
63
+ [s-badge="outline"] {
64
+ --_badge-bg: transparent;
65
+ color: var(--s-text-primary);
66
+ box-shadow: inset 0 0 0 1px var(--s-border-default);
67
+ background-color: #0000;
68
+ }
69
+
70
+ [s-badge="outline-primary"] {
71
+ --_badge-bg: transparent;
72
+ background-color: oklch(from var(--s-primary-500) l c h / .1);
73
+ color: light-dark(var(--s-primary-700), var(--s-primary-300));
74
+ box-shadow: inset 0 0 0 1px light-dark(var(--s-primary-500), var(--s-primary-400));
75
+ }
76
+
77
+ [s-badge="outline-success"] {
78
+ --_badge-bg: transparent;
79
+ background-color: oklch(from var(--s-success-500) l c h / .1);
80
+ color: light-dark(var(--s-success-700), var(--s-success-300));
81
+ box-shadow: inset 0 0 0 1px light-dark(var(--s-success-500), var(--s-success-400));
82
+ }
83
+
84
+ [s-badge="outline-warning"] {
85
+ --_badge-bg: transparent;
86
+ background-color: oklch(from var(--s-warning-500) l c h / .1);
87
+ color: light-dark(var(--s-warning-800), var(--s-warning-300));
88
+ box-shadow: inset 0 0 0 1px light-dark(var(--s-warning-500), var(--s-warning-400));
89
+ }
90
+
91
+ [s-badge="outline-danger"] {
92
+ --_badge-bg: transparent;
93
+ background-color: oklch(from var(--s-danger-500) l c h / .1);
94
+ color: light-dark(var(--s-danger-800), var(--s-danger-300));
95
+ box-shadow: inset 0 0 0 1px light-dark(var(--s-danger-500), var(--s-danger-400));
96
+ }
97
+
98
+ [s-badge][s-size="sm"] {
99
+ padding: .125rem .375rem;
100
+ font-size: .625rem;
101
+ }
102
+
103
+ [s-badge][s-size="lg"] {
104
+ padding: var(--s-space-2) var(--s-space-4);
105
+ font-size: var(--s-text-sm);
106
+ }
107
+
108
+ [s-badge][s-pill] {
109
+ border-radius: var(--s-radius-full);
110
+ }
111
+
112
+ [s-badge][s-dot] {
113
+ border-radius: var(--s-radius-full);
114
+ width: .5rem;
115
+ height: .5rem;
116
+ padding: 0;
117
+ }
118
+
119
+ [s-badge][s-dot][s-size="sm"] {
120
+ width: .375rem;
121
+ height: .375rem;
122
+ }
123
+
124
+ [s-badge][s-dot][s-size="lg"] {
125
+ width: .75rem;
126
+ height: .75rem;
127
+ }
128
+
129
+ @media (forced-colors: active) {
130
+ [s-badge] {
131
+ border: 1px solid canvastext;
132
+ }
133
+ }
134
+
135
+ @supports (container-type: inline-size) {
136
+ @container surface style(--_surface-depth: 1) {
137
+ :where([s-badge=""]) {
138
+ --_badge-bg: var(--s-neutral-300);
139
+ }
140
+
141
+ :where([s-badge="outline"]) {
142
+ box-shadow: inset 0 0 0 1px var(--s-border-strong);
143
+ }
144
+ }
145
+
146
+ @container surface style(--_surface-depth: 2) {
147
+ :where([s-badge=""]) {
148
+ --_badge-bg: var(--s-neutral-300);
149
+ box-shadow: var(--s-shadow-sm);
150
+ }
151
+
152
+ :where([s-badge="outline"]) {
153
+ box-shadow: inset 0 0 0 1px var(--s-border-strong);
154
+ }
155
+ }
156
+
157
+ @container surface style(--_surface-depth: -1) {
158
+ :where([s-badge=""]) {
159
+ --_badge-bg: var(--s-neutral-100);
160
+ }
161
+ }
162
+ }
@@ -0,0 +1,214 @@
1
+ :where([s-btn]) {
2
+ --_btn-bg: var(--s-interactive-primary);
3
+ --_btn-text: var(--s-text-inverse);
4
+ --_btn-border: transparent;
5
+ justify-content: center;
6
+ align-items: center;
7
+ gap: var(--s-space-2);
8
+ padding: var(--s-space-2) var(--s-space-4);
9
+ border-radius: var(--s-radius-md);
10
+ font-weight: var(--s-font-semibold);
11
+ font-size: var(--s-text-sm);
12
+ white-space: nowrap;
13
+ background-color: var(--_btn-bg);
14
+ color: var(--_btn-text);
15
+ border: 1px solid var(--_btn-border);
16
+ line-height: 1;
17
+ display: inline-flex;
18
+
19
+ @supports (color: oklch(from red l c h)) {
20
+ color: oklch(from var(--_btn-bg) clamp(.15, calc((.55 - l) * 1000 + .15), .95) 0 0);
21
+ }
22
+
23
+ @supports (color: contrast-color(red)) {
24
+ color: contrast-color(var(--_btn-bg));
25
+ }
26
+
27
+ transition: background-color var(--s-duration-150) var(--s-ease-out), border-color var(--s-duration-150) var(--s-ease-out), transform var(--s-duration-100) var(--s-ease-out);
28
+ cursor: pointer;
29
+ user-select: none;
30
+
31
+ &:hover:not(:disabled) {
32
+ background-color: var(--s-interactive-primary-hover);
33
+
34
+ @supports (color: oklch(from red l c h)) and (color: light-dark(red, blue)) {
35
+ background-color: light-dark(oklch(from var(--_btn-bg) calc(l * .92) c h), oklch(from var(--_btn-bg) min(calc(l * 1.1), .95) c h));
36
+ }
37
+ }
38
+
39
+ &:active:not(:disabled) {
40
+ background-color: var(--s-interactive-primary-active);
41
+
42
+ @supports (color: oklch(from red l c h)) and (color: light-dark(red, blue)) {
43
+ background-color: light-dark(oklch(from var(--_btn-bg) calc(l * .85) c h), oklch(from var(--_btn-bg) min(calc(l * 1.2), .98) c h));
44
+ }
45
+
46
+ transform: scale(.98);
47
+ }
48
+
49
+ &:focus-visible {
50
+ outline: 2px solid var(--s-focus-ring);
51
+ outline-offset: 2px;
52
+ }
53
+
54
+ &:disabled {
55
+ opacity: .5;
56
+ cursor: not-allowed;
57
+ pointer-events: none;
58
+ }
59
+ }
60
+
61
+ [s-btn="primary"] {
62
+ --_btn-bg: var(--s-interactive-primary);
63
+ --_btn-text: var(--s-text-inverse);
64
+ }
65
+
66
+ [s-btn="secondary"] {
67
+ --_btn-bg: var(--s-surface-raised);
68
+ --_btn-text: var(--s-text-primary);
69
+ --_btn-border: var(--s-border-default);
70
+ color: var(--_btn-text);
71
+ }
72
+
73
+ [s-btn="ghost"] {
74
+ --_btn-bg: transparent;
75
+ --_btn-text: var(--s-interactive-primary);
76
+ color: var(--_btn-text);
77
+
78
+ &:hover:not(:disabled) {
79
+ --_btn-bg: var(--s-surface-sunken);
80
+ }
81
+ }
82
+
83
+ [s-btn="link"] {
84
+ --_btn-bg: transparent;
85
+ --_btn-text: var(--s-interactive-primary);
86
+ --_btn-border: transparent;
87
+ color: var(--_btn-text);
88
+ text-underline-offset: 2px;
89
+ text-decoration: underline;
90
+
91
+ &:hover:not(:disabled) {
92
+ --_btn-text: var(--s-interactive-primary-hover);
93
+ color: var(--_btn-text);
94
+ }
95
+ }
96
+
97
+ [s-btn="outline"] {
98
+ --_btn-bg: transparent;
99
+ --_btn-text: var(--s-interactive-primary);
100
+ --_btn-border: var(--s-interactive-primary);
101
+ color: var(--_btn-text);
102
+
103
+ &:hover:not(:disabled) {
104
+ --_btn-bg: var(--s-interactive-primary);
105
+ background-color: var(--s-interactive-primary);
106
+ color: var(--s-text-inverse);
107
+ }
108
+ }
109
+
110
+ [s-btn="danger"] {
111
+ --_btn-bg: var(--s-state-danger);
112
+ }
113
+
114
+ [s-btn="success"] {
115
+ --_btn-bg: var(--s-state-success);
116
+ }
117
+
118
+ [s-btn="warning"] {
119
+ --_btn-bg: var(--s-state-warning);
120
+ }
121
+
122
+ [s-btn][s-size="sm"] {
123
+ padding: var(--s-space-1) var(--s-space-3);
124
+ font-size: var(--s-text-xs);
125
+ }
126
+
127
+ [s-btn][s-size="lg"] {
128
+ padding: var(--s-space-3) var(--s-space-6);
129
+ font-size: var(--s-text-base);
130
+ }
131
+
132
+ [s-btn][s-size="xl"] {
133
+ padding: var(--s-space-4) var(--s-space-8);
134
+ font-size: var(--s-text-lg);
135
+ }
136
+
137
+ [s-btn][s-icon] {
138
+ padding: var(--s-space-2);
139
+ aspect-ratio: 1;
140
+ }
141
+
142
+ [s-btn][s-block] {
143
+ width: 100%;
144
+ }
145
+
146
+ [s-btn][s-loading] {
147
+ color: var(--_btn-bg);
148
+ pointer-events: none;
149
+ position: relative;
150
+
151
+ &:after {
152
+ content: "";
153
+ border: 2px solid var(--s-text-inverse);
154
+ border-right-color: #0000;
155
+ border-radius: 50%;
156
+ width: 1em;
157
+ height: 1em;
158
+ margin: auto;
159
+ animation: .6s linear infinite s-btn-spin;
160
+ position: absolute;
161
+ inset: 0;
162
+ }
163
+ }
164
+
165
+ @keyframes s-btn-spin {
166
+ to {
167
+ transform: rotate(360deg);
168
+ }
169
+ }
170
+
171
+ [s-btn-group] {
172
+ display: inline-flex;
173
+
174
+ & > [s-btn] {
175
+ border-radius: 0;
176
+
177
+ &:first-child {
178
+ border-radius: var(--s-radius-md) 0 0 var(--s-radius-md);
179
+ }
180
+
181
+ &:last-child {
182
+ border-radius: 0 var(--s-radius-md) var(--s-radius-md) 0;
183
+ }
184
+
185
+ &:not(:last-child) {
186
+ border-right: none;
187
+ }
188
+ }
189
+ }
190
+
191
+ @supports (container-type: inline-size) {
192
+ @container surface style(--_surface-depth: 1) {
193
+ :where([s-btn="secondary"]) {
194
+ --_btn-border: var(--s-border-strong);
195
+ }
196
+ }
197
+
198
+ @container surface style(--_surface-depth: 2) {
199
+ :where([s-btn="secondary"]) {
200
+ --_btn-border: var(--s-border-strong);
201
+ --_btn-bg: var(--s-surface-base);
202
+ }
203
+
204
+ :where([s-btn="ghost"]) {
205
+ --_btn-border: var(--s-border-muted);
206
+ }
207
+ }
208
+
209
+ @container surface style(--_surface-depth: -1) {
210
+ :where([s-btn="secondary"]) {
211
+ --_btn-bg: var(--s-surface-base);
212
+ }
213
+ }
214
+ }
@@ -0,0 +1,185 @@
1
+ :where([s-card]) {
2
+ --_card-bg: var(--s-surface-raised);
3
+ --_card-border: var(--s-border-muted);
4
+ --_card-radius: var(--s-radius-xl);
5
+ --_card-shadow: var(--s-shadow-base);
6
+ --_card-padding: var(--s-space-4);
7
+ background-color: var(--_card-bg);
8
+ border: 1px solid var(--_card-border);
9
+ border-radius: var(--_card-radius);
10
+ box-shadow: var(--_card-shadow);
11
+ color: var(--s-text-primary);
12
+ flex-direction: column;
13
+ display: flex;
14
+ overflow: hidden;
15
+ container-type: inline-size;
16
+
17
+ @supports (color: oklch(from red l c h)) {
18
+ color: oklch(from var(--_card-bg) clamp(.15, calc((.55 - l) * 1000 + .15), .95) 0 0);
19
+ }
20
+ }
21
+
22
+ [s-card-header] {
23
+ padding: var(--_card-padding, var(--s-space-4));
24
+ border-bottom: 1px solid var(--_card-border, var(--s-border-muted));
25
+ }
26
+
27
+ [s-card-body] {
28
+ padding: var(--_card-padding, var(--s-space-4));
29
+ flex: 1;
30
+ }
31
+
32
+ [s-card-footer] {
33
+ padding: var(--_card-padding, var(--s-space-4));
34
+ border-top: 1px solid var(--_card-border, var(--s-border-muted));
35
+ }
36
+
37
+ [s-card-media] {
38
+ aspect-ratio: 16 / 9;
39
+ overflow: hidden;
40
+
41
+ & > img {
42
+ object-fit: cover;
43
+ width: 100%;
44
+ height: 100%;
45
+ }
46
+ }
47
+
48
+ [s-card-title] {
49
+ font-size: var(--s-text-lg);
50
+ font-weight: var(--s-font-semibold);
51
+ line-height: var(--s-leading-tight);
52
+ margin-bottom: var(--s-space-1);
53
+ }
54
+
55
+ [s-card-subtitle] {
56
+ font-size: var(--s-text-sm);
57
+ color: var(--s-text-secondary);
58
+ }
59
+
60
+ [s-card="flat"] {
61
+ --_card-shadow: none;
62
+ --_card-border: var(--s-border-default);
63
+ }
64
+
65
+ [s-card="elevated"], [s-card][s-surface="raised"] {
66
+ --_card-shadow: var(--s-shadow-lg);
67
+ --_card-border: oklch(0% 0 0 / 0);
68
+ }
69
+
70
+ [s-card][s-surface="floating"] {
71
+ --_card-shadow: var(--s-shadow-xl);
72
+ --_card-border: oklch(0% 0 0 / 0);
73
+ }
74
+
75
+ [s-card="outline"] {
76
+ --_card-shadow: none;
77
+ --_card-border: var(--s-border-strong);
78
+ --_card-bg: transparent;
79
+ }
80
+
81
+ [s-card][s-interactive] {
82
+ cursor: pointer;
83
+ transition: transform var(--s-duration-200) var(--s-ease-out), box-shadow var(--s-duration-200) var(--s-ease-out), border-color var(--s-duration-200) var(--s-ease-out);
84
+
85
+ &:hover {
86
+ --_card-shadow: var(--s-shadow-xl);
87
+ transform: translateY(-4px);
88
+
89
+ @supports (color: oklch(from red l c h)) {
90
+ border-color: oklch(from var(--_card-border) max(calc(l - .1), .05) c h);
91
+ }
92
+ }
93
+
94
+ &:active {
95
+ transform: translateY(-2px);
96
+ }
97
+
98
+ &:focus-visible {
99
+ outline: 2px solid var(--s-focus-ring);
100
+ outline-offset: 2px;
101
+ }
102
+ }
103
+
104
+ [s-card][s-link] {
105
+ position: relative;
106
+
107
+ & a:after {
108
+ content: "";
109
+ position: absolute;
110
+ inset: 0;
111
+ }
112
+ }
113
+
114
+ [s-card][s-horizontal] {
115
+ flex-direction: row;
116
+
117
+ & [s-card-media] {
118
+ aspect-ratio: 1;
119
+ flex-shrink: 0;
120
+ width: 33%;
121
+ }
122
+ }
123
+
124
+ @container (width <= 24rem) {
125
+ [s-card][s-horizontal] {
126
+ flex-direction: column;
127
+
128
+ & [s-card-media] {
129
+ aspect-ratio: 16 / 9;
130
+ width: 100%;
131
+ }
132
+ }
133
+ }
134
+
135
+ [s-card][s-size="sm"] {
136
+ --_card-padding: var(--s-space-3);
137
+ --_card-radius: var(--s-radius-lg);
138
+ }
139
+
140
+ [s-card][s-size="lg"] {
141
+ --_card-padding: var(--s-space-6);
142
+ }
143
+
144
+ [s-card="feature"] {
145
+ text-align: center;
146
+ --_card-padding: var(--s-space-6);
147
+ }
148
+
149
+ [s-card-icon] {
150
+ font-size: var(--s-text-4xl);
151
+ margin-bottom: var(--s-space-4);
152
+ }
153
+
154
+ [s-card-grid] {
155
+ gap: var(--s-space-4);
156
+ grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr));
157
+ display: grid;
158
+ }
159
+
160
+ [s-card-stack] {
161
+ gap: var(--s-space-4);
162
+ flex-direction: column;
163
+ display: flex;
164
+ }
165
+
166
+ @supports (container-type: inline-size) {
167
+ @container surface style(--_surface-depth: 1) {
168
+ :where([s-card]) {
169
+ --_card-shadow: var(--s-shadow-sm);
170
+ }
171
+ }
172
+
173
+ @container surface style(--_surface-depth: 2) {
174
+ :where([s-card]) {
175
+ --_card-shadow: none;
176
+ --_card-border: var(--s-border-default);
177
+ }
178
+ }
179
+
180
+ @container surface style(--_surface-depth: -1) {
181
+ :where([s-card]) {
182
+ --_card-shadow: var(--s-shadow-md);
183
+ }
184
+ }
185
+ }