qss 0.2.0 → 0.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.
@@ -1,537 +1,537 @@
1
- /*
2
- * QSS Core — Quantized Spatial Styling DNA
3
- * Driven by config/grammar.yml (via exe/qss-sync -> _qss_defaults.scss; a consumer overrides it)
4
- */
5
-
6
- @use "sass:math";
7
- /* Core's own defaults (every setting !default). A consumer sets its values BEFORE importing this file. */
8
- @import "qss/qss_defaults";
9
-
10
- /* The QSS unit (qnt): one thousandth of the canvas width, computed by CSS alone. The canvas is a size container
11
- and each of its children declares --qnt as 0.1cqw; registered as a <length>, it resolves there once and
12
- inherits unchanged, so every descendant measures against its canvas, never a nearer container. Outside a
13
- canvas (e.g. an overlay portal at the document root) the unit approximates from the viewport width.
14
-
15
- Ultra-wide/extreme-resolution dampening (SETTLED 2026-10-17): clamped between the qnt value at a real
16
- narrow-mobile CSS viewport width (floor) and just past a maximized 1440p/27" monitor (cap), so ordinary
17
- desktop widths scale freely and only genuine ultra-wide/high-res canvases -- or genuine phone-narrow ones --
18
- get dampened. Beyond the cap, every structural dimension stays fixed regardless of how much wider the canvas
19
- gets; extra width becomes more usable space, not bigger borders/gaps/padding. See docs/DESIGN_HISTORY.md #11. */
20
- @property --qnt { syntax: "<length>"; inherits: true; initial-value: 1px; }
21
-
22
- $qss-dampen-floor: map-get($qss-dampen-scale, "floor-width") * 0.001 * 1px;
23
- $qss-dampen-cap: map-get($qss-dampen-scale, "cap-width") * 0.001 * 1px;
24
-
25
- :root {
26
- --qnt: clamp(#{$qss-dampen-floor}, 0.1vw, #{$qss-dampen-cap});
27
- /* Item 3: pure-CSS linear content unit, no JS, no clamp. Only icon-size[N] uses
28
- this today -- see the content_scale note in _qss_variables.scss. */
29
- --u: calc(100vw / #{map-get($qss-content-scale, "calibration-width")});
30
- }
31
-
32
- /* Item 1 (colors, SETTLED 2026-09-21): per-property alpha registered as typed custom
33
- properties so alpha never leaks to descendants (inherits: false), each defaulting
34
- to fully opaque (100) when no *-alpha[N] class is present. */
35
- @property --bg-a { syntax: "<number>"; inherits: false; initial-value: 100; }
36
- @property --border-a { syntax: "<number>"; inherits: false; initial-value: 100; }
37
- @property --text-a { syntax: "<number>"; inherits: false; initial-value: 100; }
38
-
39
- /* Item A (2026-09-23): accent color -- WHICH color a state-driven property takes,
40
- an axis independent of the alpha properties above (HOW opaque). Set by the
41
- accidental .accent-{bg,text,border}-{name} classes; read by the essential
42
- state/group rules. inherits: false so a parent's accent never leaks into a child.
43
- Unset fallback: transparent for bg/border; currentcolor for text, so a missing
44
- accent class leaves text readable rather than invisible. Note an essential rule
45
- with no accent class still claims the property (author origin beats UA styles). */
46
- @property --accent-bg { syntax: "<color>"; inherits: false; initial-value: transparent; }
47
- @property --accent-border { syntax: "<color>"; inherits: false; initial-value: transparent; }
48
- @property --accent-text { syntax: "<color>"; inherits: false; initial-value: currentcolor; }
49
-
50
- /* The QSS Stage (Ground Level) */
51
- html, body {
52
- margin: 0;
53
- padding: 0;
54
- width: 100%;
55
- height: 100%;
56
- overflow: hidden;
57
- font-family: map-get($qss-fonts, "sans");
58
- -webkit-font-smoothing: antialiased;
59
- -moz-osx-font-smoothing: grayscale;
60
- }
61
-
62
- /*
63
- * The Root Anchor
64
- * Using ID specificity to ensure QSS adjectives win without !important
65
- */
66
- #{$qss-anchor} {
67
- container-type: inline-size;
68
- position: relative;
69
- width: 100%;
70
- height: 100%;
71
- overflow: hidden;
72
- background-color: transparent;
73
- box-sizing: border-box;
74
-
75
- > * { --qnt: clamp(#{$qss-dampen-floor}, 0.1cqw, #{$qss-dampen-cap}); }
76
-
77
- /* The Base Nature of a Spatial Actor — ONLY inside the stage.
78
- :where() keeps this reset at the anchor's weight alone, (1,0,0): below every core utility, which is
79
- (1,1,0), so the compiled utilities work with no JS engine; still above a host app's plain classes. */
80
- :where(.qss:not(.foreign *)) {
81
- /* Every Steel Frame actor is its own size container (DESIGN_HISTORY #12.5), so Drywall content inside it can
82
- use a named container-query variant to react to that specific frame's own rendered width, independent of
83
- the canvas or any sibling frame -- not ::before/::after, which carry no id of their own to derive a
84
- container-name from. The name itself (container-name) needs the actor's id, which CSS can't read into an
85
- arbitrary property on its own; qss-engine.js sets it at runtime (applyContainerNames), skipping an actor
86
- whose container-name is already set by anything. --qnt is unaffected: it resolves once, at the canvas's
87
- direct children, and plainly inherits from there -- it never re-resolves via cqw at a nearer container. */
88
- container-type: inline-size;
89
-
90
- &, &::before, &::after {
91
- position: absolute;
92
- box-sizing: border-box;
93
- margin: 0;
94
- padding: 0;
95
- border: 0;
96
- display: block;
97
- flex-shrink: 0;
98
- font-family: map-get($qss-fonts, "sans");
99
- font-size: inherit;
100
- vertical-align: baseline;
101
- text-decoration: none;
102
- pointer-events: auto;
103
- user-select: none;
104
- min-width: calc(1 * var(--qnt));
105
- min-height: calc(1 * var(--qnt));
106
- width: auto;
107
- height: auto;
108
- }
109
- }
110
- }
111
-
112
- /* Global Semantic Spatial DSL — Available everywhere (Hybrid Identity) */
113
-
114
- /* Selector scope for every generated rule: the anchor, plus the consumer's overlay portal
115
- root when one is configured ($qss-portal-root, grammar config.portal_root). */
116
- $qss-scope: if($qss-portal-root == "", $qss-anchor, "#{$qss-anchor}, #{$qss-portal-root}");
117
-
118
- @mixin qss-dsl-boost($selector) {
119
- :is(#{$qss-scope}) #{$selector} {
120
- @content;
121
- }
122
- }
123
-
124
- /* Typography Adjectives */
125
- @each $name, $stack in $qss-fonts {
126
- @include qss-dsl-boost(".qss-font-#{$name}") { font-family: #{$stack}; }
127
- @include qss-dsl-boost(".qss-font\\[#{$name}\\]") { font-family: #{$stack}; }
128
- }
129
- @include qss-dsl-boost(".qss-font-black") { font-weight: 900; }
130
- @include qss-dsl-boost(".qss-font-bold") { font-weight: 700; }
131
- @include qss-dsl-boost(".qss-font-semibold") { font-weight: 600; }
132
- @include qss-dsl-boost(".qss-font-medium") { font-weight: 500; }
133
- @include qss-dsl-boost(".qss-font-normal") { font-weight: 400; }
134
- @include qss-dsl-boost(".qss-italic") { font-style: italic; }
135
- @include qss-dsl-boost(".qss-uppercase") { text-transform: uppercase; }
136
- @include qss-dsl-boost(".qss-lowercase") { text-transform: lowercase; }
137
- @include qss-dsl-boost(".qss-tracking-tighter") { letter-spacing: -0.05em; }
138
- @include qss-dsl-boost(".qss-tracking-tight") { letter-spacing: -0.025em; }
139
- @include qss-dsl-boost(".qss-tracking-wide") { letter-spacing: 0.025em; }
140
- @include qss-dsl-boost(".qss-tracking-widest") { letter-spacing: 0.1em; }
141
- @include qss-dsl-boost(".qss-leading-none") { line-height: 1; }
142
- @include qss-dsl-boost(".qss-tight") { line-height: 1; }
143
- @include qss-dsl-boost(".qss-leading-tight") { line-height: 1.25; }
144
- @include qss-dsl-boost(".qss-leading-normal") { line-height: 1.5; }
145
-
146
- /* Spatial Coordinates & Dimensions */
147
- @for $i from 0 through map-get($qss-quantization, "units") {
148
- @include qss-dsl-boost(".qss-pos-x\\[#{$i}\\]") { left: calc(#{$i} * var(--qnt)); }
149
- @include qss-dsl-boost(".qss-pos-y\\[#{$i}\\]") { top: calc(#{$i} * var(--qnt)); }
150
- @include qss-dsl-boost(".qss-w\\[#{$i}\\]") { width: calc(#{$i} * var(--qnt)); }
151
- @include qss-dsl-boost(".qss-h\\[#{$i}\\]") { height: calc(#{$i} * var(--qnt)); }
152
- @include qss-dsl-boost(".qss-pos-r\\[#{$i}\\]") { right: calc(#{$i} * var(--qnt)); }
153
- @include qss-dsl-boost(".qss-pos-b\\[#{$i}\\]") { bottom: calc(#{$i} * var(--qnt)); }
154
- @include qss-dsl-boost(".qss-min-w\\[#{$i}\\]") { min-width: calc(#{$i} * var(--qnt)); }
155
- @include qss-dsl-boost(".qss-min-h\\[#{$i}\\]") { min-height: calc(#{$i} * var(--qnt)); }
156
- @include qss-dsl-boost(".qss-px\\[#{$i}\\]") { padding-left: calc(#{$i} * var(--qnt)); padding-right: calc(#{$i} * var(--qnt)); }
157
- @include qss-dsl-boost(".qss-py\\[#{$i}\\]") { padding-top: calc(#{$i} * var(--qnt)); padding-bottom: calc(#{$i} * var(--qnt)); }
158
- @include qss-dsl-boost(".qss-p\\[#{$i}\\]") { padding: calc(#{$i} * var(--qnt)); }
159
- @include qss-dsl-boost(".qss-gap\\[#{$i}\\]") { gap: calc(#{$i} * var(--qnt)); }
160
-
161
- /* Percentage-based scaling (0.1% increments) */
162
- @include qss-dsl-boost(".qss-w-pct\\[#{$i}\\]") { width: percentage($i * 0.001); }
163
- @include qss-dsl-boost(".qss-h-pct\\[#{$i}\\]") { height: percentage($i * 0.001); }
164
- @include qss-dsl-boost(".qss-pos-x-pct\\[#{$i}\\]") { left: percentage($i * 0.001); }
165
- @include qss-dsl-boost(".qss-pos-y-pct\\[#{$i}\\]") { top: percentage($i * 0.001); }
166
-
167
- @if $i <= map-get($qss-quantization, "borders") {
168
- @include qss-dsl-boost(".qss-border\\[#{$i}\\]") { border-width: calc(#{$i} * var(--qnt)); border-style: solid; border-color: map-get($qss-palette, "slate-800"); }
169
- @include qss-dsl-boost(".qss-border-t\\[#{$i}\\]") { border-top-width: calc(#{$i} * var(--qnt)); border-top-style: solid; border-top-color: map-get($qss-palette, "slate-800"); }
170
- @include qss-dsl-boost(".qss-border-b\\[#{$i}\\]") { border-bottom-width: calc(#{$i} * var(--qnt)); border-bottom-style: solid; border-bottom-color: map-get($qss-palette, "slate-800"); }
171
- @include qss-dsl-boost(".qss-border-l\\[#{$i}\\]") { border-left-width: calc(#{$i} * var(--qnt)); border-left-style: solid; border-left-color: map-get($qss-palette, "slate-800"); }
172
- @include qss-dsl-boost(".qss-border-r\\[#{$i}\\]") { border-right-width: calc(#{$i} * var(--qnt)); border-right-style: solid; border-right-color: map-get($qss-palette, "slate-800"); }
173
- @include qss-dsl-boost(".qss-rounded\\[#{$i}\\]") { border-radius: calc(#{$i} * var(--qnt)); }
174
- }
175
- }
176
-
177
- @include qss-dsl-boost(".qss-rounded\\[full\\]") { border-radius: 9999px; }
178
- @include qss-dsl-boost(".qss-rounded-full") { border-radius: 9999px; }
179
-
180
- /* Palette Generation -- each color is also a real custom property (--c-{name}),
181
- not just a Sass compile-time value, so color-mix() can read it at the point of use. */
182
- :root {
183
- @each $name, $hex in $qss-palette {
184
- --c-#{$name}: #{$hex};
185
- }
186
- }
187
- @each $name, $hex in $qss-palette {
188
- @include qss-dsl-boost(".qss-bg-#{$name}") { background-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--bg-a, 100) * 1%), transparent); }
189
- @include qss-dsl-boost(".qss-text-#{$name}") { color: color-mix(in srgb, var(--c-#{$name}) calc(var(--text-a, 100) * 1%), transparent); }
190
- @include qss-dsl-boost(".qss-border-#{$name}") { border-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--border-a, 100) * 1%), transparent); }
191
- }
192
-
193
- @for $a from 0 through 100 {
194
- @include qss-dsl-boost(".qss-bg-alpha\\[#{$a}\\]") { --bg-a: #{$a}; }
195
- @include qss-dsl-boost(".qss-border-alpha\\[#{$a}\\]") { --border-a: #{$a}; }
196
- @include qss-dsl-boost(".qss-text-alpha\\[#{$a}\\]") { --text-a: #{$a}; }
197
- }
198
-
199
- /* Item A step 2 (2026-09-23): accidental accent-color classes -- one rule per palette
200
- color per property, no state involved. They only SET --accent-*; the essential
201
- {state}:bg-accent (etc.) rules below decide whether/when it is applied.
202
- SETTLED 2026-10-17: qss- is now the only spelling (DESIGN_HISTORY #12/the Tailwind
203
- boundary) -- the bare-form twins this comment used to describe are retired. */
204
- @each $name, $hex in $qss-palette {
205
- @include qss-dsl-boost(".qss-accent-bg-#{$name}") { --accent-bg: var(--c-#{$name}); }
206
- @include qss-dsl-boost(".qss-accent-text-#{$name}") { --accent-text: var(--c-#{$name}); }
207
- @include qss-dsl-boost(".qss-accent-border-#{$name}") { --accent-border: var(--c-#{$name}); }
208
- }
209
-
210
- /* Logical Centering */
211
- @include qss-dsl-boost(".qss-pos-x\\[center\\]") { left: 50%; transform: translateX(-50%); }
212
- @include qss-dsl-boost(".qss-pos-y\\[center\\]") { top: 50%; transform: translateY(-50%); }
213
- @include qss-dsl-boost(".qss-pos\\[center\\]") { left: 50%; top: 50%; transform: translate(-50%, -50%); }
214
-
215
- /* Dev Utilities */
216
- @include qss-dsl-boost(".qss-dev-border") {
217
- border: 1px solid map-get($qss-palette, "rose-500");
218
- }
219
-
220
- @include qss-dsl-boost(".qss-safety-gutter") {
221
- padding: 2px;
222
- background-color: map-get($qss-palette, "slate-900");
223
- }
224
-
225
- /* Layout Utilities — Semantic Spatial DSL
226
- SETTLED 2026-10-17 (DESIGN_HISTORY #12, the Tailwind/QSS boundary): qss- is now the
227
- only spelling for every utility below, named or bracketed. The project's original
228
- invented synonyms (detached, anchored, floating, stuck, horizontal, vertical, spaced,
229
- clickable, smooth) are retired in favour of whichever spelling already matched the
230
- underlying CSS mechanism (qss-absolute, qss-relative, qss-fixed, qss-sticky,
231
- qss-flex-row, qss-flex-col, qss-justify-between, qss-cursor-pointer, qss-transition-all)
232
- -- not because that happens to be Tailwind's own convention too, but because it's the
233
- clearest name for the CSS mechanism itself, and QSS owns this mechanism outright
234
- regardless of whatever fills Drywall. A bare, unprefixed vocabulary this close to
235
- Tailwind's own utility names is the exact collision the boundary exists to prevent:
236
- a content framework compiling its own unprefixed `.flex`/`.absolute`/`.w-full` would
237
- be indistinguishable, in source text, from QSS's own. */
238
- @include qss-dsl-boost(".qss-absolute") { position: absolute; }
239
- @include qss-dsl-boost(".qss-relative") { position: relative; }
240
- @include qss-dsl-boost(".qss-fixed") { position: fixed; }
241
- @include qss-dsl-boost(".qss-sticky") { position: sticky; }
242
-
243
- @include qss-dsl-boost(".qss-flex-row") { display: flex; flex-direction: row; }
244
- @include qss-dsl-boost(".qss-flex-col") { display: flex; flex-direction: column; }
245
- @include qss-dsl-boost(".qss-centered") { display: flex; align-items: center; justify-content: center; }
246
- @include qss-dsl-boost(".qss-v-start") { align-items: flex-start; }
247
- @include qss-dsl-boost(".qss-items-center") { align-items: center; }
248
- @include qss-dsl-boost(".qss-v-end") { align-items: flex-end; }
249
- @include qss-dsl-boost(".qss-h-start") { justify-content: flex-start; }
250
- @include qss-dsl-boost(".qss-justify-center") { justify-content: center; }
251
- @include qss-dsl-boost(".qss-h-end") { justify-content: flex-end; }
252
- @include qss-dsl-boost(".qss-justify-between") { justify-content: space-between; }
253
- @include qss-dsl-boost(".qss-around") { justify-content: space-around; }
254
- @include qss-dsl-boost(".qss-evenly") { justify-content: space-evenly; }
255
- @include qss-dsl-boost(".qss-flex-wrap") { flex-wrap: wrap; }
256
- @include qss-dsl-boost(".qss-flex-nowrap") { flex-wrap: nowrap; }
257
- @include qss-dsl-boost(".qss-flex-1") { flex: 1 1 0%; }
258
- @include qss-dsl-boost(".qss-flex-auto") { flex: 1 1 auto; }
259
- @include qss-dsl-boost(".qss-flex-none") { flex: none; }
260
- @include qss-dsl-boost(".qss-flex") { display: flex; }
261
- @include qss-dsl-boost(".qss-flex-shrink") { flex-shrink: 1; }
262
- @include qss-dsl-boost(".qss-flex-shrink-0") { flex-shrink: 0; }
263
- @include qss-dsl-boost(".qss-divide-x") { > * + * { border-left-width: 1px; border-left-style: solid; } }
264
- @include qss-dsl-boost(".qss-divide-y") { > * + * { border-top-width: 1px; border-top-style: solid; } }
265
- @include qss-dsl-boost(".qss-grid-cols-1") { grid-template-columns: repeat(1, minmax(0, 1fr)); }
266
- @include qss-dsl-boost(".qss-grid-cols-2") { grid-template-columns: repeat(2, minmax(0, 1fr)); }
267
- @include qss-dsl-boost(".qss-w-f") { width: 100%; }
268
- @include qss-dsl-boost(".qss-h-f") { height: 100%; }
269
- @include qss-dsl-boost(".qss-block") { display: block; }
270
- @include qss-dsl-boost(".qss-inline") { display: inline; }
271
- @include qss-dsl-boost(".qss-inline-block") { display: inline-block; }
272
- @include qss-dsl-boost(".qss-table") { display: table; }
273
- @include qss-dsl-boost(".qss-contents") { display: contents; }
274
- @include qss-dsl-boost(".qss-list-item") { display: list-item; }
275
- @include qss-dsl-boost(".qss-overflow-auto") { overflow: auto; }
276
- @include qss-dsl-boost(".qss-overflow-x-auto") { overflow-x: auto; }
277
- @include qss-dsl-boost(".qss-inset-0") { top: 0; right: 0; bottom: 0; left: 0; }
278
- @include qss-dsl-boost(".qss-truncate") { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
279
- @include qss-dsl-boost(".qss-text-xs") { font-size: 0.75rem; }
280
- @include qss-dsl-boost(".qss-text-sm") { font-size: 0.875rem; }
281
- @include qss-dsl-boost(".qss-text-md") { font-size: 1rem; }
282
- @include qss-dsl-boost(".qss-text-base") { font-size: 1rem; }
283
- @include qss-dsl-boost(".qss-text-lg") { font-size: 1.125rem; }
284
- @include qss-dsl-boost(".qss-text-xl") { font-size: 1.25rem; }
285
- @include qss-dsl-boost(".qss-text-2xl") { font-size: 1.5rem; }
286
- @include qss-dsl-boost(".qss-text-left") { text-align: left; }
287
- @include qss-dsl-boost(".qss-text-right") { text-align: right; }
288
- @include qss-dsl-boost(".qss-text-center") { text-align: center; }
289
- @include qss-dsl-boost(".qss-text-justify") { text-align: justify; }
290
- @include qss-dsl-boost(".qss-leading") { line-height: normal; }
291
- @include qss-dsl-boost(".qss-tracking") { letter-spacing: normal; }
292
- @include qss-dsl-boost(".qss-tracking-wider") { letter-spacing: 0.05em; }
293
- @include qss-dsl-boost(".qss-whitespace-nowrap") { white-space: nowrap; }
294
- @include qss-dsl-boost(".qss-border") { border-width: 1px; border-style: solid; }
295
- @include qss-dsl-boost(".qss-border-t") { border-top-width: 1px; border-top-style: solid; }
296
- @include qss-dsl-boost(".qss-border-b") { border-bottom-width: 1px; border-bottom-style: solid; }
297
- @include qss-dsl-boost(".qss-border-l") { border-left-width: 1px; border-left-style: solid; }
298
- @include qss-dsl-boost(".qss-border-r") { border-right-width: 1px; border-right-style: solid; }
299
- @include qss-dsl-boost(".qss-rounded") { border-radius: 0.25rem; }
300
- @include qss-dsl-boost(".qss-shadow") { box-shadow: 0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1); }
301
- @include qss-dsl-boost(".qss-opacity") { opacity: 1; }
302
- @include qss-dsl-boost(".qss-blur") { filter: blur(8px); }
303
- @include qss-dsl-boost(".qss-bright") { filter: brightness(1.1); }
304
- @include qss-dsl-boost(".qss-contrast") { filter: contrast(1.1); }
305
- @include qss-dsl-boost(".qss-grayscale") { filter: grayscale(100%); }
306
- @include qss-dsl-boost(".qss-hue") { filter: hue-rotate(90deg); }
307
- @include qss-dsl-boost(".qss-invert") { filter: invert(100%); }
308
- @include qss-dsl-boost(".qss-saturate") { filter: saturate(1.5); }
309
- @include qss-dsl-boost(".qss-sepia") { filter: sepia(100%); }
310
- @include qss-dsl-boost(".qss-blend") { mix-blend-mode: multiply; }
311
- @include qss-dsl-boost(".qss-backdrop-blur-sm") { backdrop-filter: blur(4px); }
312
- @include qss-dsl-boost(".qss-backdrop-blur-md") { backdrop-filter: blur(8px); }
313
- @include qss-dsl-boost(".qss-max-w-lg") { max-width: 32rem; }
314
- @include qss-dsl-boost(".qss-scroll") { overflow: scroll; }
315
- @include qss-dsl-boost(".qss-cursor-col-resize") { cursor: col-resize; }
316
- @include qss-dsl-boost(".qss-cursor-row-resize") { cursor: row-resize; }
317
- @include qss-dsl-boost(".qss-ring") { box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.5); }
318
- @include qss-dsl-boost(".qss-resize") { resize: both; }
319
- @include qss-dsl-boost(".qss-outline-none") { outline: none; }
320
-
321
-
322
- @include qss-dsl-boost(".qss-fill-h") { bottom: 0; }
323
- @include qss-dsl-boost(".qss-fill-w") { right: 0; }
324
- @include qss-dsl-boost(".qss-inline-flex") { display: inline-flex; }
325
- @include qss-dsl-boost(".qss-flex-grow") { flex-grow: 1; }
326
- @include qss-dsl-boost(".qss-w-full") { width: 100%; }
327
- @include qss-dsl-boost(".qss-h-full") { height: 100%; }
328
- @include qss-dsl-boost(".qss-hidden") { display: none; }
329
- @include qss-dsl-boost(".qss-visible") { visibility: visible; }
330
- @include qss-dsl-boost(".qss-invisible") { visibility: hidden; }
331
- @include qss-dsl-boost(".qss-overflow-hidden") { overflow: hidden; }
332
- @include qss-dsl-boost(".qss-overflow-y-auto") { overflow-y: auto; }
333
-
334
- @include qss-dsl-boost(".qss-cursor-pointer") { cursor: pointer; }
335
- @include qss-dsl-boost(".qss-busy") { cursor: wait; }
336
- @include qss-dsl-boost(".qss-ghost") { pointer-events: none; }
337
- @include qss-dsl-boost(".qss-unselectable") { user-select: none; }
338
-
339
- @include qss-dsl-boost(".qss-transition-all") { transition: all 0.2s ease-in-out; }
340
- @include qss-dsl-boost(".qss-transition-colors") { transition: background-color 0.2s, border-color 0.2s; }
341
- @include qss-dsl-boost(".qss-transition-none") { transition: none; }
342
- @include qss-dsl-boost(".qss-scroll-smooth") { scroll-behavior: smooth; }
343
-
344
- /* Typography DSL Extensions */
345
- @include qss-dsl-boost(".qss-underline") { text-decoration: underline; }
346
- @include qss-dsl-boost(".qss-nowrap") { white-space: nowrap; }
347
-
348
- /* Layout DSL Extensions */
349
- @include qss-dsl-boost(".qss-grid") { display: grid; }
350
- @include qss-dsl-boost(".qss-table-fixed") { table-layout: fixed; }
351
- @include qss-dsl-boost(".qss-contain") { object-fit: contain; }
352
-
353
- /* Layout containment for structural frames: the class the README documents and the native auditor mandates on
354
- frames whose id names a structural word (header, nav, sidebar, ...).
355
- Clips paint by default too (SETTLED 2026-10-17): `contain: layout` alone isolates layout math but not paint, so
356
- a frame's overflow could still cover its neighbours -- the exact gap DESIGN_HISTORY #11 named. Anything that
357
- needs to visually escape a frame on purpose (tooltips, dropdowns, modals) already goes through the portal
358
- (z-index outside it is its own red violation), so this does not reopen that case; qss-paint-override is the
359
- escape hatch for the rare frame that wants decorative overflow (a shadow, a hover scale) without the portal. */
360
- @include qss-dsl-boost(".qss-contain-layout") { contain: layout paint; }
361
- @include qss-dsl-boost(".qss-contain-layout.qss-paint-override") { contain: layout; }
362
- @include qss-dsl-boost(".qss-cover") { object-fit: cover; }
363
-
364
- /* Visual Atmosphere */
365
- @include qss-dsl-boost(".qss-shadow-sm") { box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); }
366
- @include qss-dsl-boost(".qss-shadow-md") { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); }
367
- @include qss-dsl-boost(".qss-shadow-lg") { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); }
368
- @include qss-dsl-boost(".qss-shadow-none") { box-shadow: none; }
369
-
370
- /* Text scale (item 2, SETTLED 2026-09-21): text-size[N] = N px when the canvas is
371
- calibration-width wide, scaling proportionally with --qnt at every other width,
372
- bounded by a floor/cap that are themselves functions of N (not a flat px floor). */
373
- $qss-text-ref: math.div(map-get($qss-text-scale, "calibration-width"), map-get($qss-quantization, "units"));
374
- @for $size from 1 through 96 {
375
- $qss-text-floor-mult: $size * map-get($qss-text-scale, "floor-multiplier");
376
- $qss-text-floor: if($qss-text-floor-mult > map-get($qss-text-scale, "floor-px"), $qss-text-floor-mult, map-get($qss-text-scale, "floor-px")) * 1px;
377
- $qss-text-cap: $size * map-get($qss-text-scale, "cap-multiplier") * 1px;
378
- @include qss-dsl-boost(".qss-text-size\\[#{$size}\\]") {
379
- font-size: clamp(#{$qss-text-floor}, calc(#{$size} * var(--qnt) / #{$qss-text-ref}), #{$qss-text-cap});
380
- }
381
- }
382
-
383
- /* Icon scale (item 2/3, SETTLED 2026-09-21): icon glyphs are non-text -- linear with
384
- the new content unit, no floor, no cap. Brand new token, zero existing usage, so
385
- it can use the content-scale unit today without waiting for Phase B's converter. */
386
- @for $size from 1 through 96 {
387
- @include qss-dsl-boost(".qss-icon-size\\[#{$size}\\]") {
388
- font-size: calc(#{$size} * var(--u));
389
- }
390
- }
391
-
392
- @include qss-dsl-boost(".qss-ergonomic-floor") {
393
- min-width: calc(44 * var(--qnt));
394
- min-height: calc(44 * var(--qnt));
395
- }
396
-
397
- /* Item 7 (auditor rule / JIT elimination, SETTLED 2026-09-22): compile-time
398
- variant-prefix and group-relational classes, replacing qss-engine.js's runtime
399
- JIT for these states. Two dedicated mixins rather than an extension of
400
- qss-dsl-boost, since the selector shape differs (same-element pseudo-class vs.
401
- ancestor pseudo-class + descendant combinator). Real usage is confirmed
402
- (2026-09-22 scan of a consuming project's views and scripts, 21 dead views
403
- excluded) to be state = hover/focus/focus-within/active, always in the form
404
- "{state}:{base}" (state itself is never qss-prefixed) -- the OLD block above
405
- generated ".qss-#{state}:bg-X", which has never matched real usage syntax.
406
- Removed as dead, superseded by this. */
407
-
408
- @mixin qss-state-boost($selector, $state) {
409
- :is(#{$qss-scope}) #{$selector}:#{$state} {
410
- @content;
411
- }
412
- }
413
-
414
- @mixin qss-group-boost($group-selector, $state, $descendant-selector) {
415
- :is(#{$qss-scope}) #{$group-selector}:#{$state} #{$descendant-selector} {
416
- @content;
417
- }
418
- }
419
-
420
- /* Incidental fix found while scanning real usage: opacity-100/qss-opacity-100 are
421
- used (including under hover:) but no base rule for them existed at all, with or
422
- without a state prefix -- same shape as the existing bare .opacity rule. */
423
- @include qss-dsl-boost(".qss-opacity-100") { opacity: 1; }
424
-
425
- /* ISOLATED TEST 2026-09-22 (self-referential only -- group-relational stays reverted
426
- at 2 states below): re-adding the 21-state expansion here alone, to determine
427
- whether the self-referential cross product by itself is what broke Cucumber, or
428
- whether it was specifically the group-relational x group-name cross product.
429
- Do not change this or any file while the verification Cucumber run is executing. */
430
- $qss-state-prefixes-test: (
431
- "hover", "focus", "focus-within", "active",
432
- "enabled", "disabled", "checked", "indeterminate", "default", "placeholder-shown",
433
- "valid", "invalid", "in-range", "out-of-range", "required", "optional",
434
- "read-only", "read-write", "user-valid", "user-invalid", "autofill"
435
- );
436
- @each $state in $qss-state-prefixes-test {
437
- @each $name, $hex in $qss-palette {
438
- @include qss-state-boost(".#{$state}\\:qss-bg-#{$name}", $state) { background-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--bg-a, 100) * 1%), transparent); }
439
- @include qss-state-boost(".#{$state}\\:qss-text-#{$name}", $state) { color: color-mix(in srgb, var(--c-#{$name}) calc(var(--text-a, 100) * 1%), transparent); }
440
- @include qss-state-boost(".#{$state}\\:qss-border-#{$name}", $state) { border-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--border-a, 100) * 1%), transparent); }
441
- }
442
- }
443
-
444
- /* Self-referential state variants of alpha classes -- same bounded reasoning. */
445
- @each $state in $qss-state-prefixes-test {
446
- @for $a from 0 through 100 {
447
- @include qss-state-boost(".#{$state}\\:qss-bg-alpha\\[#{$a}\\]", $state) { --bg-a: #{$a}; }
448
- @include qss-state-boost(".#{$state}\\:qss-border-alpha\\[#{$a}\\]", $state) { --border-a: #{$a}; }
449
- @include qss-state-boost(".#{$state}\\:qss-text-alpha\\[#{$a}\\]", $state) { --text-a: #{$a}; }
450
- }
451
- }
452
-
453
- /* Item A step 3 (2026-09-23): essential, self-referential state rules. One rule per
454
- state x property -- they decide WHETHER the property applies under the state and
455
- read WHICH color from --accent-* (step 2 classes) and HOW opaque from --*-a.
456
- Additive: adding a palette color adds zero rules here.
457
- State list = W3C convention (architecture doc 10.3), not consumer config.
458
- Held out deliberately: visited (privacy restrictions, needs its own test),
459
- local-link (no browser support), DOM-position selectors (different mechanism). */
460
- @if map-has-key($qss-palette, "accent") {
461
- @error "Palette color named 'accent' would collide with the {state}:bg-accent essential classes.";
462
- }
463
- $qss-accent-states: (
464
- "hover", "focus", "focus-within", "focus-visible", "active",
465
- "enabled", "disabled", "checked", "indeterminate", "default", "placeholder-shown",
466
- "valid", "invalid", "in-range", "out-of-range", "required", "optional",
467
- "read-only", "read-write", "user-valid", "user-invalid", "autofill",
468
- "link", "any-link", "target",
469
- "open", "popover-open", "modal", "fullscreen", "picture-in-picture", "defined"
470
- );
471
- @each $state in $qss-accent-states {
472
- @include qss-state-boost(".#{$state}\\:qss-bg-accent", $state) { background-color: color-mix(in srgb, var(--accent-bg) calc(var(--bg-a, 100) * 1%), transparent); }
473
- @include qss-state-boost(".#{$state}\\:qss-text-accent", $state) { color: color-mix(in srgb, var(--accent-text) calc(var(--text-a, 100) * 1%), transparent); }
474
- @include qss-state-boost(".#{$state}\\:qss-border-accent", $state) { border-color: color-mix(in srgb, var(--accent-border) calc(var(--border-a, 100) * 1%), transparent); }
475
- }
476
-
477
- /* Item A step 4 (2026-09-23): essential, group-relational state rules -- same
478
- 31-state list, gated by an ANCESTOR's state (bare .group, and each registered
479
- named group from $qss-group-names). Still additive: (1 + groups) x states x props,
480
- independent of palette size. Accent/alpha values come from the descendant itself. */
481
- @each $state in $qss-accent-states {
482
- @include qss-group-boost(".group", $state, ".group-#{$state}\\:qss-bg-accent") { background-color: color-mix(in srgb, var(--accent-bg) calc(var(--bg-a, 100) * 1%), transparent); }
483
- @include qss-group-boost(".group", $state, ".group-#{$state}\\:qss-text-accent") { color: color-mix(in srgb, var(--accent-text) calc(var(--text-a, 100) * 1%), transparent); }
484
- @include qss-group-boost(".group", $state, ".group-#{$state}\\:qss-border-accent") { border-color: color-mix(in srgb, var(--accent-border) calc(var(--border-a, 100) * 1%), transparent); }
485
- @each $group-name in $qss-group-names {
486
- @include qss-group-boost(".group\\/#{$group-name}", $state, ".group-#{$state}\\/#{$group-name}\\:qss-bg-accent") { background-color: color-mix(in srgb, var(--accent-bg) calc(var(--bg-a, 100) * 1%), transparent); }
487
- @include qss-group-boost(".group\\/#{$group-name}", $state, ".group-#{$state}\\/#{$group-name}\\:qss-text-accent") { color: color-mix(in srgb, var(--accent-text) calc(var(--text-a, 100) * 1%), transparent); }
488
- @include qss-group-boost(".group\\/#{$group-name}", $state, ".group-#{$state}\\/#{$group-name}\\:qss-border-accent") { border-color: color-mix(in srgb, var(--accent-border) calc(var(--border-a, 100) * 1%), transparent); }
489
- }
490
- }
491
-
492
-
493
- /* Group-relational: named groups confirmed in real usage (2026-09-22 scan), plus the
494
- unnamed/bare .group form. Palette crossed with group name x state is still bounded. */
495
- /* $qss-group-names now comes from _qss_variables.scss (grammar.yml group_names) -- see architecture-decisions section 9.3. */
496
-
497
- @each $state in ("hover", "focus-within") {
498
- @each $name, $hex in $qss-palette {
499
- @include qss-group-boost(".group", $state, ".group-#{$state}\\:qss-bg-#{$name}") { background-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--bg-a, 100) * 1%), transparent); }
500
- @include qss-group-boost(".group", $state, ".group-#{$state}\\:qss-text-#{$name}") { color: color-mix(in srgb, var(--c-#{$name}) calc(var(--text-a, 100) * 1%), transparent); }
501
- @include qss-group-boost(".group", $state, ".group-#{$state}\\:qss-border-#{$name}") { border-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--border-a, 100) * 1%), transparent); }
502
- }
503
- @each $group-name in $qss-group-names {
504
- @each $name, $hex in $qss-palette {
505
- @include qss-group-boost(".group\\/#{$group-name}", $state, ".group-#{$state}\\/#{$group-name}\\:qss-bg-#{$name}") { background-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--bg-a, 100) * 1%), transparent); }
506
- @include qss-group-boost(".group\\/#{$group-name}", $state, ".group-#{$state}\\/#{$group-name}\\:qss-text-#{$name}") { color: color-mix(in srgb, var(--c-#{$name}) calc(var(--text-a, 100) * 1%), transparent); }
507
- @include qss-group-boost(".group\\/#{$group-name}", $state, ".group-#{$state}\\/#{$group-name}\\:qss-border-#{$name}") { border-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--border-a, 100) * 1%), transparent); }
508
- }
509
- }
510
- }
511
-
512
-
513
- /* Responsive Quantization (Macro Layout only to prevent bloat). Already qss-prefixed on
514
- the breakpoint token itself (qss-sm:, qss-v-sm:, ...), which already satisfies "every
515
- QSS class starts with qss-" -- prefixing the utility word too would just double it. */
516
- @each $bp-name, $bp-width in $qss-breakpoints {
517
- @media (min-width: #{$bp-width}) {
518
- @for $i from 0 through map-get($qss-quantization, "units") {
519
- @include qss-dsl-boost(".qss-#{$bp-name}\\:w-pct\\[#{$i}\\]") { width: percentage($i * 0.001); }
520
- @include qss-dsl-boost(".qss-#{$bp-name}\\:h-pct\\[#{$i}\\]") { height: percentage($i * 0.001); }
521
- @include qss-dsl-boost(".qss-#{$bp-name}\\:pos-x-pct\\[#{$i}\\]") { left: percentage($i * 0.001); }
522
- @include qss-dsl-boost(".qss-#{$bp-name}\\:pos-y-pct\\[#{$i}\\]") { top: percentage($i * 0.001); }
523
- }
524
- }
525
- }
526
-
527
- /* Vertical Responsive Quantization (Macro-Y) */
528
- @each $bp-name, $bp-height in $qss-vertical-breakpoints {
529
- @media (min-height: #{$bp-height}) {
530
- @for $i from 0 through map-get($qss-quantization, "units") {
531
- @include qss-dsl-boost(".qss-#{$bp-name}\\:w-pct\\[#{$i}\\]") { width: percentage($i * 0.001); }
532
- @include qss-dsl-boost(".qss-#{$bp-name}\\:h-pct\\[#{$i}\\]") { height: percentage($i * 0.001); }
533
- @include qss-dsl-boost(".qss-#{$bp-name}\\:pos-x-pct\\[#{$i}\\]") { left: percentage($i * 0.001); }
534
- @include qss-dsl-boost(".qss-#{$bp-name}\\:pos-y-pct\\[#{$i}\\]") { top: percentage($i * 0.001); }
535
- }
536
- }
537
- }
1
+ /*
2
+ * QSS Core — Quantized Spatial Styling DNA
3
+ * Driven by config/grammar.yml (via exe/qss-sync -> _qss_defaults.scss; a consumer overrides it)
4
+ */
5
+
6
+ @use "sass:math";
7
+ /* Core's own defaults (every setting !default). A consumer sets its values BEFORE importing this file. */
8
+ @import "qss/qss_defaults";
9
+
10
+ /* The QSS unit (qnt): one thousandth of the canvas width, computed by CSS alone. The canvas is a size container
11
+ and each of its children declares --qnt as 0.1cqw; registered as a <length>, it resolves there once and
12
+ inherits unchanged, so every descendant measures against its canvas, never a nearer container. Outside a
13
+ canvas (e.g. an overlay portal at the document root) the unit approximates from the viewport width.
14
+
15
+ Ultra-wide/extreme-resolution dampening (SETTLED 2026-10-17): clamped between the qnt value at a real
16
+ narrow-mobile CSS viewport width (floor) and just past a maximized 1440p/27" monitor (cap), so ordinary
17
+ desktop widths scale freely and only genuine ultra-wide/high-res canvases -- or genuine phone-narrow ones --
18
+ get dampened. Beyond the cap, every structural dimension stays fixed regardless of how much wider the canvas
19
+ gets; extra width becomes more usable space, not bigger borders/gaps/padding. See docs/DESIGN_HISTORY.md #11. */
20
+ @property --qnt { syntax: "<length>"; inherits: true; initial-value: 1px; }
21
+
22
+ $qss-dampen-floor: map-get($qss-dampen-scale, "floor-width") * 0.001 * 1px;
23
+ $qss-dampen-cap: map-get($qss-dampen-scale, "cap-width") * 0.001 * 1px;
24
+
25
+ :root {
26
+ --qnt: clamp(#{$qss-dampen-floor}, 0.1vw, #{$qss-dampen-cap});
27
+ /* Item 3: pure-CSS linear content unit, no JS, no clamp. Only icon-size[N] uses
28
+ this today -- see the content_scale note in _qss_variables.scss. */
29
+ --u: calc(100vw / #{map-get($qss-content-scale, "calibration-width")});
30
+ }
31
+
32
+ /* Item 1 (colors, SETTLED 2026-09-21): per-property alpha registered as typed custom
33
+ properties so alpha never leaks to descendants (inherits: false), each defaulting
34
+ to fully opaque (100) when no *-alpha[N] class is present. */
35
+ @property --bg-a { syntax: "<number>"; inherits: false; initial-value: 100; }
36
+ @property --border-a { syntax: "<number>"; inherits: false; initial-value: 100; }
37
+ @property --text-a { syntax: "<number>"; inherits: false; initial-value: 100; }
38
+
39
+ /* Item A (2026-09-23): accent color -- WHICH color a state-driven property takes,
40
+ an axis independent of the alpha properties above (HOW opaque). Set by the
41
+ accidental .accent-{bg,text,border}-{name} classes; read by the essential
42
+ state/group rules. inherits: false so a parent's accent never leaks into a child.
43
+ Unset fallback: transparent for bg/border; currentcolor for text, so a missing
44
+ accent class leaves text readable rather than invisible. Note an essential rule
45
+ with no accent class still claims the property (author origin beats UA styles). */
46
+ @property --accent-bg { syntax: "<color>"; inherits: false; initial-value: transparent; }
47
+ @property --accent-border { syntax: "<color>"; inherits: false; initial-value: transparent; }
48
+ @property --accent-text { syntax: "<color>"; inherits: false; initial-value: currentcolor; }
49
+
50
+ /* The QSS Stage (Ground Level) */
51
+ html, body {
52
+ margin: 0;
53
+ padding: 0;
54
+ width: 100%;
55
+ height: 100%;
56
+ overflow: hidden;
57
+ font-family: map-get($qss-fonts, "sans");
58
+ -webkit-font-smoothing: antialiased;
59
+ -moz-osx-font-smoothing: grayscale;
60
+ }
61
+
62
+ /*
63
+ * The Root Anchor
64
+ * Using ID specificity to ensure QSS adjectives win without !important
65
+ */
66
+ #{$qss-anchor} {
67
+ container-type: inline-size;
68
+ position: relative;
69
+ width: 100%;
70
+ height: 100%;
71
+ overflow: hidden;
72
+ background-color: transparent;
73
+ box-sizing: border-box;
74
+
75
+ > * { --qnt: clamp(#{$qss-dampen-floor}, 0.1cqw, #{$qss-dampen-cap}); }
76
+
77
+ /* The Base Nature of a Spatial Actor — ONLY inside the stage.
78
+ :where() keeps this reset at the anchor's weight alone, (1,0,0): below every core utility, which is
79
+ (1,1,0), so the compiled utilities work with no JS engine; still above a host app's plain classes. */
80
+ :where(.qss:not(.foreign *)) {
81
+ /* Every Steel Frame actor is its own size container (DESIGN_HISTORY #12.5), so Drywall content inside it can
82
+ use a named container-query variant to react to that specific frame's own rendered width, independent of
83
+ the canvas or any sibling frame -- not ::before/::after, which carry no id of their own to derive a
84
+ container-name from. The name itself (container-name) needs the actor's id, which CSS can't read into an
85
+ arbitrary property on its own; qss-engine.js sets it at runtime (applyContainerNames), skipping an actor
86
+ whose container-name is already set by anything. --qnt is unaffected: it resolves once, at the canvas's
87
+ direct children, and plainly inherits from there -- it never re-resolves via cqw at a nearer container. */
88
+ container-type: inline-size;
89
+
90
+ &, &::before, &::after {
91
+ position: absolute;
92
+ box-sizing: border-box;
93
+ margin: 0;
94
+ padding: 0;
95
+ border: 0;
96
+ display: block;
97
+ flex-shrink: 0;
98
+ font-family: map-get($qss-fonts, "sans");
99
+ font-size: inherit;
100
+ vertical-align: baseline;
101
+ text-decoration: none;
102
+ pointer-events: auto;
103
+ user-select: none;
104
+ min-width: calc(1 * var(--qnt));
105
+ min-height: calc(1 * var(--qnt));
106
+ width: auto;
107
+ height: auto;
108
+ }
109
+ }
110
+ }
111
+
112
+ /* Global Semantic Spatial DSL — Available everywhere (Hybrid Identity) */
113
+
114
+ /* Selector scope for every generated rule: the anchor, plus the consumer's overlay portal
115
+ root when one is configured ($qss-portal-root, grammar config.portal_root). */
116
+ $qss-scope: if($qss-portal-root == "", $qss-anchor, "#{$qss-anchor}, #{$qss-portal-root}");
117
+
118
+ @mixin qss-dsl-boost($selector) {
119
+ :is(#{$qss-scope}) #{$selector} {
120
+ @content;
121
+ }
122
+ }
123
+
124
+ /* Typography Adjectives */
125
+ @each $name, $stack in $qss-fonts {
126
+ @include qss-dsl-boost(".qss-font-#{$name}") { font-family: #{$stack}; }
127
+ @include qss-dsl-boost(".qss-font\\[#{$name}\\]") { font-family: #{$stack}; }
128
+ }
129
+ @include qss-dsl-boost(".qss-font-black") { font-weight: 900; }
130
+ @include qss-dsl-boost(".qss-font-bold") { font-weight: 700; }
131
+ @include qss-dsl-boost(".qss-font-semibold") { font-weight: 600; }
132
+ @include qss-dsl-boost(".qss-font-medium") { font-weight: 500; }
133
+ @include qss-dsl-boost(".qss-font-normal") { font-weight: 400; }
134
+ @include qss-dsl-boost(".qss-italic") { font-style: italic; }
135
+ @include qss-dsl-boost(".qss-uppercase") { text-transform: uppercase; }
136
+ @include qss-dsl-boost(".qss-lowercase") { text-transform: lowercase; }
137
+ @include qss-dsl-boost(".qss-tracking-tighter") { letter-spacing: -0.05em; }
138
+ @include qss-dsl-boost(".qss-tracking-tight") { letter-spacing: -0.025em; }
139
+ @include qss-dsl-boost(".qss-tracking-wide") { letter-spacing: 0.025em; }
140
+ @include qss-dsl-boost(".qss-tracking-widest") { letter-spacing: 0.1em; }
141
+ @include qss-dsl-boost(".qss-leading-none") { line-height: 1; }
142
+ @include qss-dsl-boost(".qss-tight") { line-height: 1; }
143
+ @include qss-dsl-boost(".qss-leading-tight") { line-height: 1.25; }
144
+ @include qss-dsl-boost(".qss-leading-normal") { line-height: 1.5; }
145
+
146
+ /* Spatial Coordinates & Dimensions */
147
+ @for $i from 0 through map-get($qss-quantization, "units") {
148
+ @include qss-dsl-boost(".qss-pos-x\\[#{$i}\\]") { left: calc(#{$i} * var(--qnt)); }
149
+ @include qss-dsl-boost(".qss-pos-y\\[#{$i}\\]") { top: calc(#{$i} * var(--qnt)); }
150
+ @include qss-dsl-boost(".qss-w\\[#{$i}\\]") { width: calc(#{$i} * var(--qnt)); }
151
+ @include qss-dsl-boost(".qss-h\\[#{$i}\\]") { height: calc(#{$i} * var(--qnt)); }
152
+ @include qss-dsl-boost(".qss-pos-r\\[#{$i}\\]") { right: calc(#{$i} * var(--qnt)); }
153
+ @include qss-dsl-boost(".qss-pos-b\\[#{$i}\\]") { bottom: calc(#{$i} * var(--qnt)); }
154
+ @include qss-dsl-boost(".qss-min-w\\[#{$i}\\]") { min-width: calc(#{$i} * var(--qnt)); }
155
+ @include qss-dsl-boost(".qss-min-h\\[#{$i}\\]") { min-height: calc(#{$i} * var(--qnt)); }
156
+ @include qss-dsl-boost(".qss-px\\[#{$i}\\]") { padding-left: calc(#{$i} * var(--qnt)); padding-right: calc(#{$i} * var(--qnt)); }
157
+ @include qss-dsl-boost(".qss-py\\[#{$i}\\]") { padding-top: calc(#{$i} * var(--qnt)); padding-bottom: calc(#{$i} * var(--qnt)); }
158
+ @include qss-dsl-boost(".qss-p\\[#{$i}\\]") { padding: calc(#{$i} * var(--qnt)); }
159
+ @include qss-dsl-boost(".qss-gap\\[#{$i}\\]") { gap: calc(#{$i} * var(--qnt)); }
160
+
161
+ /* Percentage-based scaling (0.1% increments) */
162
+ @include qss-dsl-boost(".qss-w-pct\\[#{$i}\\]") { width: percentage($i * 0.001); }
163
+ @include qss-dsl-boost(".qss-h-pct\\[#{$i}\\]") { height: percentage($i * 0.001); }
164
+ @include qss-dsl-boost(".qss-pos-x-pct\\[#{$i}\\]") { left: percentage($i * 0.001); }
165
+ @include qss-dsl-boost(".qss-pos-y-pct\\[#{$i}\\]") { top: percentage($i * 0.001); }
166
+
167
+ @if $i <= map-get($qss-quantization, "borders") {
168
+ @include qss-dsl-boost(".qss-border\\[#{$i}\\]") { border-width: calc(#{$i} * var(--qnt)); border-style: solid; border-color: map-get($qss-palette, "slate-800"); }
169
+ @include qss-dsl-boost(".qss-border-t\\[#{$i}\\]") { border-top-width: calc(#{$i} * var(--qnt)); border-top-style: solid; border-top-color: map-get($qss-palette, "slate-800"); }
170
+ @include qss-dsl-boost(".qss-border-b\\[#{$i}\\]") { border-bottom-width: calc(#{$i} * var(--qnt)); border-bottom-style: solid; border-bottom-color: map-get($qss-palette, "slate-800"); }
171
+ @include qss-dsl-boost(".qss-border-l\\[#{$i}\\]") { border-left-width: calc(#{$i} * var(--qnt)); border-left-style: solid; border-left-color: map-get($qss-palette, "slate-800"); }
172
+ @include qss-dsl-boost(".qss-border-r\\[#{$i}\\]") { border-right-width: calc(#{$i} * var(--qnt)); border-right-style: solid; border-right-color: map-get($qss-palette, "slate-800"); }
173
+ @include qss-dsl-boost(".qss-rounded\\[#{$i}\\]") { border-radius: calc(#{$i} * var(--qnt)); }
174
+ }
175
+ }
176
+
177
+ @include qss-dsl-boost(".qss-rounded\\[full\\]") { border-radius: 9999px; }
178
+ @include qss-dsl-boost(".qss-rounded-full") { border-radius: 9999px; }
179
+
180
+ /* Palette Generation -- each color is also a real custom property (--c-{name}),
181
+ not just a Sass compile-time value, so color-mix() can read it at the point of use. */
182
+ :root {
183
+ @each $name, $hex in $qss-palette {
184
+ --c-#{$name}: #{$hex};
185
+ }
186
+ }
187
+ @each $name, $hex in $qss-palette {
188
+ @include qss-dsl-boost(".qss-bg-#{$name}") { background-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--bg-a, 100) * 1%), transparent); }
189
+ @include qss-dsl-boost(".qss-text-#{$name}") { color: color-mix(in srgb, var(--c-#{$name}) calc(var(--text-a, 100) * 1%), transparent); }
190
+ @include qss-dsl-boost(".qss-border-#{$name}") { border-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--border-a, 100) * 1%), transparent); }
191
+ }
192
+
193
+ @for $a from 0 through 100 {
194
+ @include qss-dsl-boost(".qss-bg-alpha\\[#{$a}\\]") { --bg-a: #{$a}; }
195
+ @include qss-dsl-boost(".qss-border-alpha\\[#{$a}\\]") { --border-a: #{$a}; }
196
+ @include qss-dsl-boost(".qss-text-alpha\\[#{$a}\\]") { --text-a: #{$a}; }
197
+ }
198
+
199
+ /* Item A step 2 (2026-09-23): accidental accent-color classes -- one rule per palette
200
+ color per property, no state involved. They only SET --accent-*; the essential
201
+ {state}:bg-accent (etc.) rules below decide whether/when it is applied.
202
+ SETTLED 2026-10-17: qss- is now the only spelling (DESIGN_HISTORY #12/the Tailwind
203
+ boundary) -- the bare-form twins this comment used to describe are retired. */
204
+ @each $name, $hex in $qss-palette {
205
+ @include qss-dsl-boost(".qss-accent-bg-#{$name}") { --accent-bg: var(--c-#{$name}); }
206
+ @include qss-dsl-boost(".qss-accent-text-#{$name}") { --accent-text: var(--c-#{$name}); }
207
+ @include qss-dsl-boost(".qss-accent-border-#{$name}") { --accent-border: var(--c-#{$name}); }
208
+ }
209
+
210
+ /* Logical Centering */
211
+ @include qss-dsl-boost(".qss-pos-x\\[center\\]") { left: 50%; transform: translateX(-50%); }
212
+ @include qss-dsl-boost(".qss-pos-y\\[center\\]") { top: 50%; transform: translateY(-50%); }
213
+ @include qss-dsl-boost(".qss-pos\\[center\\]") { left: 50%; top: 50%; transform: translate(-50%, -50%); }
214
+
215
+ /* Dev Utilities */
216
+ @include qss-dsl-boost(".qss-dev-border") {
217
+ border: 1px solid map-get($qss-palette, "rose-500");
218
+ }
219
+
220
+ @include qss-dsl-boost(".qss-safety-gutter") {
221
+ padding: 2px;
222
+ background-color: map-get($qss-palette, "slate-900");
223
+ }
224
+
225
+ /* Layout Utilities — Semantic Spatial DSL
226
+ SETTLED 2026-10-17 (DESIGN_HISTORY #12, the Tailwind/QSS boundary): qss- is now the
227
+ only spelling for every utility below, named or bracketed. The project's original
228
+ invented synonyms (detached, anchored, floating, stuck, horizontal, vertical, spaced,
229
+ clickable, smooth) are retired in favour of whichever spelling already matched the
230
+ underlying CSS mechanism (qss-absolute, qss-relative, qss-fixed, qss-sticky,
231
+ qss-flex-row, qss-flex-col, qss-justify-between, qss-cursor-pointer, qss-transition-all)
232
+ -- not because that happens to be Tailwind's own convention too, but because it's the
233
+ clearest name for the CSS mechanism itself, and QSS owns this mechanism outright
234
+ regardless of whatever fills Drywall. A bare, unprefixed vocabulary this close to
235
+ Tailwind's own utility names is the exact collision the boundary exists to prevent:
236
+ a content framework compiling its own unprefixed `.flex`/`.absolute`/`.w-full` would
237
+ be indistinguishable, in source text, from QSS's own. */
238
+ @include qss-dsl-boost(".qss-absolute") { position: absolute; }
239
+ @include qss-dsl-boost(".qss-relative") { position: relative; }
240
+ @include qss-dsl-boost(".qss-fixed") { position: fixed; }
241
+ @include qss-dsl-boost(".qss-sticky") { position: sticky; }
242
+
243
+ @include qss-dsl-boost(".qss-flex-row") { display: flex; flex-direction: row; }
244
+ @include qss-dsl-boost(".qss-flex-col") { display: flex; flex-direction: column; }
245
+ @include qss-dsl-boost(".qss-centered") { display: flex; align-items: center; justify-content: center; }
246
+ @include qss-dsl-boost(".qss-v-start") { align-items: flex-start; }
247
+ @include qss-dsl-boost(".qss-items-center") { align-items: center; }
248
+ @include qss-dsl-boost(".qss-v-end") { align-items: flex-end; }
249
+ @include qss-dsl-boost(".qss-h-start") { justify-content: flex-start; }
250
+ @include qss-dsl-boost(".qss-justify-center") { justify-content: center; }
251
+ @include qss-dsl-boost(".qss-h-end") { justify-content: flex-end; }
252
+ @include qss-dsl-boost(".qss-justify-between") { justify-content: space-between; }
253
+ @include qss-dsl-boost(".qss-around") { justify-content: space-around; }
254
+ @include qss-dsl-boost(".qss-evenly") { justify-content: space-evenly; }
255
+ @include qss-dsl-boost(".qss-flex-wrap") { flex-wrap: wrap; }
256
+ @include qss-dsl-boost(".qss-flex-nowrap") { flex-wrap: nowrap; }
257
+ @include qss-dsl-boost(".qss-flex-1") { flex: 1 1 0%; }
258
+ @include qss-dsl-boost(".qss-flex-auto") { flex: 1 1 auto; }
259
+ @include qss-dsl-boost(".qss-flex-none") { flex: none; }
260
+ @include qss-dsl-boost(".qss-flex") { display: flex; }
261
+ @include qss-dsl-boost(".qss-flex-shrink") { flex-shrink: 1; }
262
+ @include qss-dsl-boost(".qss-flex-shrink-0") { flex-shrink: 0; }
263
+ @include qss-dsl-boost(".qss-divide-x") { > * + * { border-left-width: 1px; border-left-style: solid; } }
264
+ @include qss-dsl-boost(".qss-divide-y") { > * + * { border-top-width: 1px; border-top-style: solid; } }
265
+ @include qss-dsl-boost(".qss-grid-cols-1") { grid-template-columns: repeat(1, minmax(0, 1fr)); }
266
+ @include qss-dsl-boost(".qss-grid-cols-2") { grid-template-columns: repeat(2, minmax(0, 1fr)); }
267
+ @include qss-dsl-boost(".qss-w-f") { width: 100%; }
268
+ @include qss-dsl-boost(".qss-h-f") { height: 100%; }
269
+ @include qss-dsl-boost(".qss-block") { display: block; }
270
+ @include qss-dsl-boost(".qss-inline") { display: inline; }
271
+ @include qss-dsl-boost(".qss-inline-block") { display: inline-block; }
272
+ @include qss-dsl-boost(".qss-table") { display: table; }
273
+ @include qss-dsl-boost(".qss-contents") { display: contents; }
274
+ @include qss-dsl-boost(".qss-list-item") { display: list-item; }
275
+ @include qss-dsl-boost(".qss-overflow-auto") { overflow: auto; }
276
+ @include qss-dsl-boost(".qss-overflow-x-auto") { overflow-x: auto; }
277
+ @include qss-dsl-boost(".qss-inset-0") { top: 0; right: 0; bottom: 0; left: 0; }
278
+ @include qss-dsl-boost(".qss-truncate") { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
279
+ @include qss-dsl-boost(".qss-text-xs") { font-size: 0.75rem; }
280
+ @include qss-dsl-boost(".qss-text-sm") { font-size: 0.875rem; }
281
+ @include qss-dsl-boost(".qss-text-md") { font-size: 1rem; }
282
+ @include qss-dsl-boost(".qss-text-base") { font-size: 1rem; }
283
+ @include qss-dsl-boost(".qss-text-lg") { font-size: 1.125rem; }
284
+ @include qss-dsl-boost(".qss-text-xl") { font-size: 1.25rem; }
285
+ @include qss-dsl-boost(".qss-text-2xl") { font-size: 1.5rem; }
286
+ @include qss-dsl-boost(".qss-text-left") { text-align: left; }
287
+ @include qss-dsl-boost(".qss-text-right") { text-align: right; }
288
+ @include qss-dsl-boost(".qss-text-center") { text-align: center; }
289
+ @include qss-dsl-boost(".qss-text-justify") { text-align: justify; }
290
+ @include qss-dsl-boost(".qss-leading") { line-height: normal; }
291
+ @include qss-dsl-boost(".qss-tracking") { letter-spacing: normal; }
292
+ @include qss-dsl-boost(".qss-tracking-wider") { letter-spacing: 0.05em; }
293
+ @include qss-dsl-boost(".qss-whitespace-nowrap") { white-space: nowrap; }
294
+ @include qss-dsl-boost(".qss-border") { border-width: 1px; border-style: solid; }
295
+ @include qss-dsl-boost(".qss-border-t") { border-top-width: 1px; border-top-style: solid; }
296
+ @include qss-dsl-boost(".qss-border-b") { border-bottom-width: 1px; border-bottom-style: solid; }
297
+ @include qss-dsl-boost(".qss-border-l") { border-left-width: 1px; border-left-style: solid; }
298
+ @include qss-dsl-boost(".qss-border-r") { border-right-width: 1px; border-right-style: solid; }
299
+ @include qss-dsl-boost(".qss-rounded") { border-radius: 0.25rem; }
300
+ @include qss-dsl-boost(".qss-shadow") { box-shadow: 0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1); }
301
+ @include qss-dsl-boost(".qss-opacity") { opacity: 1; }
302
+ @include qss-dsl-boost(".qss-blur") { filter: blur(8px); }
303
+ @include qss-dsl-boost(".qss-bright") { filter: brightness(1.1); }
304
+ @include qss-dsl-boost(".qss-contrast") { filter: contrast(1.1); }
305
+ @include qss-dsl-boost(".qss-grayscale") { filter: grayscale(100%); }
306
+ @include qss-dsl-boost(".qss-hue") { filter: hue-rotate(90deg); }
307
+ @include qss-dsl-boost(".qss-invert") { filter: invert(100%); }
308
+ @include qss-dsl-boost(".qss-saturate") { filter: saturate(1.5); }
309
+ @include qss-dsl-boost(".qss-sepia") { filter: sepia(100%); }
310
+ @include qss-dsl-boost(".qss-blend") { mix-blend-mode: multiply; }
311
+ @include qss-dsl-boost(".qss-backdrop-blur-sm") { backdrop-filter: blur(4px); }
312
+ @include qss-dsl-boost(".qss-backdrop-blur-md") { backdrop-filter: blur(8px); }
313
+ @include qss-dsl-boost(".qss-max-w-lg") { max-width: 32rem; }
314
+ @include qss-dsl-boost(".qss-scroll") { overflow: scroll; }
315
+ @include qss-dsl-boost(".qss-cursor-col-resize") { cursor: col-resize; }
316
+ @include qss-dsl-boost(".qss-cursor-row-resize") { cursor: row-resize; }
317
+ @include qss-dsl-boost(".qss-ring") { box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.5); }
318
+ @include qss-dsl-boost(".qss-resize") { resize: both; }
319
+ @include qss-dsl-boost(".qss-outline-none") { outline: none; }
320
+
321
+
322
+ @include qss-dsl-boost(".qss-fill-h") { bottom: 0; }
323
+ @include qss-dsl-boost(".qss-fill-w") { right: 0; }
324
+ @include qss-dsl-boost(".qss-inline-flex") { display: inline-flex; }
325
+ @include qss-dsl-boost(".qss-flex-grow") { flex-grow: 1; }
326
+ @include qss-dsl-boost(".qss-w-full") { width: 100%; }
327
+ @include qss-dsl-boost(".qss-h-full") { height: 100%; }
328
+ @include qss-dsl-boost(".qss-hidden") { display: none; }
329
+ @include qss-dsl-boost(".qss-visible") { visibility: visible; }
330
+ @include qss-dsl-boost(".qss-invisible") { visibility: hidden; }
331
+ @include qss-dsl-boost(".qss-overflow-hidden") { overflow: hidden; }
332
+ @include qss-dsl-boost(".qss-overflow-y-auto") { overflow-y: auto; }
333
+
334
+ @include qss-dsl-boost(".qss-cursor-pointer") { cursor: pointer; }
335
+ @include qss-dsl-boost(".qss-busy") { cursor: wait; }
336
+ @include qss-dsl-boost(".qss-ghost") { pointer-events: none; }
337
+ @include qss-dsl-boost(".qss-unselectable") { user-select: none; }
338
+
339
+ @include qss-dsl-boost(".qss-transition-all") { transition: all 0.2s ease-in-out; }
340
+ @include qss-dsl-boost(".qss-transition-colors") { transition: background-color 0.2s, border-color 0.2s; }
341
+ @include qss-dsl-boost(".qss-transition-none") { transition: none; }
342
+ @include qss-dsl-boost(".qss-scroll-smooth") { scroll-behavior: smooth; }
343
+
344
+ /* Typography DSL Extensions */
345
+ @include qss-dsl-boost(".qss-underline") { text-decoration: underline; }
346
+ @include qss-dsl-boost(".qss-nowrap") { white-space: nowrap; }
347
+
348
+ /* Layout DSL Extensions */
349
+ @include qss-dsl-boost(".qss-grid") { display: grid; }
350
+ @include qss-dsl-boost(".qss-table-fixed") { table-layout: fixed; }
351
+ @include qss-dsl-boost(".qss-contain") { object-fit: contain; }
352
+
353
+ /* Layout containment for structural frames: the class the README documents and the native auditor mandates on
354
+ frames whose id names a structural word (header, nav, sidebar, ...).
355
+ Clips paint by default too (SETTLED 2026-10-17): `contain: layout` alone isolates layout math but not paint, so
356
+ a frame's overflow could still cover its neighbours -- the exact gap DESIGN_HISTORY #11 named. Anything that
357
+ needs to visually escape a frame on purpose (tooltips, dropdowns, modals) already goes through the portal
358
+ (z-index outside it is its own red violation), so this does not reopen that case; qss-paint-override is the
359
+ escape hatch for the rare frame that wants decorative overflow (a shadow, a hover scale) without the portal. */
360
+ @include qss-dsl-boost(".qss-contain-layout") { contain: layout paint; }
361
+ @include qss-dsl-boost(".qss-contain-layout.qss-paint-override") { contain: layout; }
362
+ @include qss-dsl-boost(".qss-cover") { object-fit: cover; }
363
+
364
+ /* Visual Atmosphere */
365
+ @include qss-dsl-boost(".qss-shadow-sm") { box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); }
366
+ @include qss-dsl-boost(".qss-shadow-md") { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); }
367
+ @include qss-dsl-boost(".qss-shadow-lg") { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); }
368
+ @include qss-dsl-boost(".qss-shadow-none") { box-shadow: none; }
369
+
370
+ /* Text scale (item 2, SETTLED 2026-09-21): text-size[N] = N px when the canvas is
371
+ calibration-width wide, scaling proportionally with --qnt at every other width,
372
+ bounded by a floor/cap that are themselves functions of N (not a flat px floor). */
373
+ $qss-text-ref: math.div(map-get($qss-text-scale, "calibration-width"), map-get($qss-quantization, "units"));
374
+ @for $size from 1 through 96 {
375
+ $qss-text-floor-mult: $size * map-get($qss-text-scale, "floor-multiplier");
376
+ $qss-text-floor: if($qss-text-floor-mult > map-get($qss-text-scale, "floor-px"), $qss-text-floor-mult, map-get($qss-text-scale, "floor-px")) * 1px;
377
+ $qss-text-cap: $size * map-get($qss-text-scale, "cap-multiplier") * 1px;
378
+ @include qss-dsl-boost(".qss-text-size\\[#{$size}\\]") {
379
+ font-size: clamp(#{$qss-text-floor}, calc(#{$size} * var(--qnt) / #{$qss-text-ref}), #{$qss-text-cap});
380
+ }
381
+ }
382
+
383
+ /* Icon scale (item 2/3, SETTLED 2026-09-21): icon glyphs are non-text -- linear with
384
+ the new content unit, no floor, no cap. Brand new token, zero existing usage, so
385
+ it can use the content-scale unit today without waiting for Phase B's converter. */
386
+ @for $size from 1 through 96 {
387
+ @include qss-dsl-boost(".qss-icon-size\\[#{$size}\\]") {
388
+ font-size: calc(#{$size} * var(--u));
389
+ }
390
+ }
391
+
392
+ @include qss-dsl-boost(".qss-ergonomic-floor") {
393
+ min-width: calc(44 * var(--qnt));
394
+ min-height: calc(44 * var(--qnt));
395
+ }
396
+
397
+ /* Item 7 (auditor rule / JIT elimination, SETTLED 2026-09-22): compile-time
398
+ variant-prefix and group-relational classes, replacing qss-engine.js's runtime
399
+ JIT for these states. Two dedicated mixins rather than an extension of
400
+ qss-dsl-boost, since the selector shape differs (same-element pseudo-class vs.
401
+ ancestor pseudo-class + descendant combinator). Real usage is confirmed
402
+ (2026-09-22 scan of a consuming project's views and scripts, 21 dead views
403
+ excluded) to be state = hover/focus/focus-within/active, always in the form
404
+ "{state}:{base}" (state itself is never qss-prefixed) -- the OLD block above
405
+ generated ".qss-#{state}:bg-X", which has never matched real usage syntax.
406
+ Removed as dead, superseded by this. */
407
+
408
+ @mixin qss-state-boost($selector, $state) {
409
+ :is(#{$qss-scope}) #{$selector}:#{$state} {
410
+ @content;
411
+ }
412
+ }
413
+
414
+ @mixin qss-group-boost($group-selector, $state, $descendant-selector) {
415
+ :is(#{$qss-scope}) #{$group-selector}:#{$state} #{$descendant-selector} {
416
+ @content;
417
+ }
418
+ }
419
+
420
+ /* Incidental fix found while scanning real usage: opacity-100/qss-opacity-100 are
421
+ used (including under hover:) but no base rule for them existed at all, with or
422
+ without a state prefix -- same shape as the existing bare .opacity rule. */
423
+ @include qss-dsl-boost(".qss-opacity-100") { opacity: 1; }
424
+
425
+ /* ISOLATED TEST 2026-09-22 (self-referential only -- group-relational stays reverted
426
+ at 2 states below): re-adding the 21-state expansion here alone, to determine
427
+ whether the self-referential cross product by itself is what broke Cucumber, or
428
+ whether it was specifically the group-relational x group-name cross product.
429
+ Do not change this or any file while the verification Cucumber run is executing. */
430
+ $qss-state-prefixes-test: (
431
+ "hover", "focus", "focus-within", "active",
432
+ "enabled", "disabled", "checked", "indeterminate", "default", "placeholder-shown",
433
+ "valid", "invalid", "in-range", "out-of-range", "required", "optional",
434
+ "read-only", "read-write", "user-valid", "user-invalid", "autofill"
435
+ );
436
+ @each $state in $qss-state-prefixes-test {
437
+ @each $name, $hex in $qss-palette {
438
+ @include qss-state-boost(".#{$state}\\:qss-bg-#{$name}", $state) { background-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--bg-a, 100) * 1%), transparent); }
439
+ @include qss-state-boost(".#{$state}\\:qss-text-#{$name}", $state) { color: color-mix(in srgb, var(--c-#{$name}) calc(var(--text-a, 100) * 1%), transparent); }
440
+ @include qss-state-boost(".#{$state}\\:qss-border-#{$name}", $state) { border-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--border-a, 100) * 1%), transparent); }
441
+ }
442
+ }
443
+
444
+ /* Self-referential state variants of alpha classes -- same bounded reasoning. */
445
+ @each $state in $qss-state-prefixes-test {
446
+ @for $a from 0 through 100 {
447
+ @include qss-state-boost(".#{$state}\\:qss-bg-alpha\\[#{$a}\\]", $state) { --bg-a: #{$a}; }
448
+ @include qss-state-boost(".#{$state}\\:qss-border-alpha\\[#{$a}\\]", $state) { --border-a: #{$a}; }
449
+ @include qss-state-boost(".#{$state}\\:qss-text-alpha\\[#{$a}\\]", $state) { --text-a: #{$a}; }
450
+ }
451
+ }
452
+
453
+ /* Item A step 3 (2026-09-23): essential, self-referential state rules. One rule per
454
+ state x property -- they decide WHETHER the property applies under the state and
455
+ read WHICH color from --accent-* (step 2 classes) and HOW opaque from --*-a.
456
+ Additive: adding a palette color adds zero rules here.
457
+ State list = W3C convention (architecture doc 10.3), not consumer config.
458
+ Held out deliberately: visited (privacy restrictions, needs its own test),
459
+ local-link (no browser support), DOM-position selectors (different mechanism). */
460
+ @if map-has-key($qss-palette, "accent") {
461
+ @error "Palette color named 'accent' would collide with the {state}:bg-accent essential classes.";
462
+ }
463
+ $qss-accent-states: (
464
+ "hover", "focus", "focus-within", "focus-visible", "active",
465
+ "enabled", "disabled", "checked", "indeterminate", "default", "placeholder-shown",
466
+ "valid", "invalid", "in-range", "out-of-range", "required", "optional",
467
+ "read-only", "read-write", "user-valid", "user-invalid", "autofill",
468
+ "link", "any-link", "target",
469
+ "open", "popover-open", "modal", "fullscreen", "picture-in-picture", "defined"
470
+ );
471
+ @each $state in $qss-accent-states {
472
+ @include qss-state-boost(".#{$state}\\:qss-bg-accent", $state) { background-color: color-mix(in srgb, var(--accent-bg) calc(var(--bg-a, 100) * 1%), transparent); }
473
+ @include qss-state-boost(".#{$state}\\:qss-text-accent", $state) { color: color-mix(in srgb, var(--accent-text) calc(var(--text-a, 100) * 1%), transparent); }
474
+ @include qss-state-boost(".#{$state}\\:qss-border-accent", $state) { border-color: color-mix(in srgb, var(--accent-border) calc(var(--border-a, 100) * 1%), transparent); }
475
+ }
476
+
477
+ /* Item A step 4 (2026-09-23): essential, group-relational state rules -- same
478
+ 31-state list, gated by an ANCESTOR's state (bare .group, and each registered
479
+ named group from $qss-group-names). Still additive: (1 + groups) x states x props,
480
+ independent of palette size. Accent/alpha values come from the descendant itself. */
481
+ @each $state in $qss-accent-states {
482
+ @include qss-group-boost(".group", $state, ".group-#{$state}\\:qss-bg-accent") { background-color: color-mix(in srgb, var(--accent-bg) calc(var(--bg-a, 100) * 1%), transparent); }
483
+ @include qss-group-boost(".group", $state, ".group-#{$state}\\:qss-text-accent") { color: color-mix(in srgb, var(--accent-text) calc(var(--text-a, 100) * 1%), transparent); }
484
+ @include qss-group-boost(".group", $state, ".group-#{$state}\\:qss-border-accent") { border-color: color-mix(in srgb, var(--accent-border) calc(var(--border-a, 100) * 1%), transparent); }
485
+ @each $group-name in $qss-group-names {
486
+ @include qss-group-boost(".group\\/#{$group-name}", $state, ".group-#{$state}\\/#{$group-name}\\:qss-bg-accent") { background-color: color-mix(in srgb, var(--accent-bg) calc(var(--bg-a, 100) * 1%), transparent); }
487
+ @include qss-group-boost(".group\\/#{$group-name}", $state, ".group-#{$state}\\/#{$group-name}\\:qss-text-accent") { color: color-mix(in srgb, var(--accent-text) calc(var(--text-a, 100) * 1%), transparent); }
488
+ @include qss-group-boost(".group\\/#{$group-name}", $state, ".group-#{$state}\\/#{$group-name}\\:qss-border-accent") { border-color: color-mix(in srgb, var(--accent-border) calc(var(--border-a, 100) * 1%), transparent); }
489
+ }
490
+ }
491
+
492
+
493
+ /* Group-relational: named groups confirmed in real usage (2026-09-22 scan), plus the
494
+ unnamed/bare .group form. Palette crossed with group name x state is still bounded. */
495
+ /* $qss-group-names now comes from _qss_variables.scss (grammar.yml group_names) -- see architecture-decisions section 9.3. */
496
+
497
+ @each $state in ("hover", "focus-within") {
498
+ @each $name, $hex in $qss-palette {
499
+ @include qss-group-boost(".group", $state, ".group-#{$state}\\:qss-bg-#{$name}") { background-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--bg-a, 100) * 1%), transparent); }
500
+ @include qss-group-boost(".group", $state, ".group-#{$state}\\:qss-text-#{$name}") { color: color-mix(in srgb, var(--c-#{$name}) calc(var(--text-a, 100) * 1%), transparent); }
501
+ @include qss-group-boost(".group", $state, ".group-#{$state}\\:qss-border-#{$name}") { border-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--border-a, 100) * 1%), transparent); }
502
+ }
503
+ @each $group-name in $qss-group-names {
504
+ @each $name, $hex in $qss-palette {
505
+ @include qss-group-boost(".group\\/#{$group-name}", $state, ".group-#{$state}\\/#{$group-name}\\:qss-bg-#{$name}") { background-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--bg-a, 100) * 1%), transparent); }
506
+ @include qss-group-boost(".group\\/#{$group-name}", $state, ".group-#{$state}\\/#{$group-name}\\:qss-text-#{$name}") { color: color-mix(in srgb, var(--c-#{$name}) calc(var(--text-a, 100) * 1%), transparent); }
507
+ @include qss-group-boost(".group\\/#{$group-name}", $state, ".group-#{$state}\\/#{$group-name}\\:qss-border-#{$name}") { border-color: color-mix(in srgb, var(--c-#{$name}) calc(var(--border-a, 100) * 1%), transparent); }
508
+ }
509
+ }
510
+ }
511
+
512
+
513
+ /* Responsive Quantization (Macro Layout only to prevent bloat). Already qss-prefixed on
514
+ the breakpoint token itself (qss-sm:, qss-v-sm:, ...), which already satisfies "every
515
+ QSS class starts with qss-" -- prefixing the utility word too would just double it. */
516
+ @each $bp-name, $bp-width in $qss-breakpoints {
517
+ @media (min-width: #{$bp-width}) {
518
+ @for $i from 0 through map-get($qss-quantization, "units") {
519
+ @include qss-dsl-boost(".qss-#{$bp-name}\\:w-pct\\[#{$i}\\]") { width: percentage($i * 0.001); }
520
+ @include qss-dsl-boost(".qss-#{$bp-name}\\:h-pct\\[#{$i}\\]") { height: percentage($i * 0.001); }
521
+ @include qss-dsl-boost(".qss-#{$bp-name}\\:pos-x-pct\\[#{$i}\\]") { left: percentage($i * 0.001); }
522
+ @include qss-dsl-boost(".qss-#{$bp-name}\\:pos-y-pct\\[#{$i}\\]") { top: percentage($i * 0.001); }
523
+ }
524
+ }
525
+ }
526
+
527
+ /* Vertical Responsive Quantization (Macro-Y) */
528
+ @each $bp-name, $bp-height in $qss-vertical-breakpoints {
529
+ @media (min-height: #{$bp-height}) {
530
+ @for $i from 0 through map-get($qss-quantization, "units") {
531
+ @include qss-dsl-boost(".qss-#{$bp-name}\\:w-pct\\[#{$i}\\]") { width: percentage($i * 0.001); }
532
+ @include qss-dsl-boost(".qss-#{$bp-name}\\:h-pct\\[#{$i}\\]") { height: percentage($i * 0.001); }
533
+ @include qss-dsl-boost(".qss-#{$bp-name}\\:pos-x-pct\\[#{$i}\\]") { left: percentage($i * 0.001); }
534
+ @include qss-dsl-boost(".qss-#{$bp-name}\\:pos-y-pct\\[#{$i}\\]") { top: percentage($i * 0.001); }
535
+ }
536
+ }
537
+ }